diff --git a/pom.xml b/pom.xml
index c5da82755..06c1a61d0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,15 +29,19 @@
3.4.3.4
3.9.1
5.7.15
+ 29.0-jre
3.0.3
+ 3.0.3
11.6
4.9.1
2.5.2
3.16.3
+ 2.8.0
2.2.1
3.4.1
1.3.3
2.3.0
+ 1.5.2.Final
3.0.1
@@ -53,7 +57,6 @@
http://${docker.registry.url}:2375
ruoyi
1.2.2
-
@@ -88,6 +91,7 @@
+
io.swagger
swagger-annotations
@@ -168,6 +172,18 @@
${hutool.version}
+
+ com.google.guava
+ guava
+ ${guava.version}
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-gateway
+ ${gateway.version}
+
+
org.springframework.cloud
@@ -178,6 +194,10 @@
feign-core
io.github.openfeign
+
+ org.bouncycastle
+ bcprov-jdk15on
+
@@ -186,6 +206,11 @@
feign-okhttp
${feign-okhttp.version}
+
+ com.squareup.okio
+ okio
+ ${okio.version}
+
com.squareup.okhttp3
@@ -204,7 +229,24 @@
${spring-boot-admin.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+ io.lettuce
+ lettuce-core
+
+
+ ${spring-boot.version}
+
+
+ org.redisson
+ redisson
+ ${redisson.version}
+
org.redisson
redisson-spring-boot-starter
@@ -216,11 +258,24 @@
${lock4j.version}
+
+
+ org.wildfly.common
+ wildfly-common
+ ${wildfly-common.version}
+
+
com.xuxueli
xxl-job-core
${xxl-job.version}
+
+
+ org.javassist
+ javassist
+
+
@@ -306,6 +361,23 @@
${ruoyi-vue-plus.version}
+
+
+ com.ruoyi
+ ruoyi-isc-parent
+ ${ruoyi-vue-plus.version}
+
+
+ com.ruoyi
+ ruoyi-isc-common
+ ${ruoyi-vue-plus.version}
+
+
+ com.ruoyi
+ ruoyi-isc
+ ${ruoyi-vue-plus.version}
+
+
com.ruoyi
@@ -327,6 +399,7 @@
ruoyi-demo
ruoyi-extend
ruoyi-oss
+ ruoyi-isc-parent
pom
diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml
index 9ec68cdd1..a768215aa 100644
--- a/ruoyi-admin/pom.xml
+++ b/ruoyi-admin/pom.xml
@@ -52,10 +52,11 @@
ruoyi-job
+
@@ -63,6 +64,12 @@
ruoyi-generator
+
+
+ com.ruoyi
+ ruoyi-isc
+
+
com.ruoyi
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java
index 98f5838fc..8a467060d 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java
@@ -1,11 +1,11 @@
package com.ruoyi.web.controller.system;
+import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.util.ArrayUtil;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
-import com.ruoyi.common.core.domain.TreeSelect;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.StringUtils;
@@ -76,7 +76,7 @@ public class SysDeptController extends BaseController {
*/
@ApiOperation("获取部门下拉树列表")
@GetMapping("/treeselect")
- public AjaxResult> treeselect(SysDept dept) {
+ public AjaxResult>> treeselect(SysDept dept) {
List depts = deptService.selectDeptList(dept);
return AjaxResult.success(deptService.buildDeptTreeSelect(depts));
}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java
index 1fd181637..11371f843 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java
@@ -1,10 +1,10 @@
package com.ruoyi.web.controller.system;
+import cn.hutool.core.lang.tree.Tree;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
-import com.ruoyi.common.core.domain.TreeSelect;
import com.ruoyi.common.core.domain.entity.SysMenu;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.StringUtils;
@@ -61,7 +61,7 @@ public class SysMenuController extends BaseController {
*/
@ApiOperation("获取菜单下拉树列表")
@GetMapping("/treeselect")
- public AjaxResult> treeselect(SysMenu menu) {
+ public AjaxResult>> treeselect(SysMenu menu) {
List menus = menuService.selectMenuList(menu, getUserId());
return AjaxResult.success(menuService.buildMenuTreeSelect(menus));
}
diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml
index 015b3028e..f26cedf82 100644
--- a/ruoyi-admin/src/main/resources/application-dev.yml
+++ b/ruoyi-admin/src/main/resources/application-dev.yml
@@ -10,8 +10,8 @@ spring:
url: http://localhost:9090/admin
instance:
prefer-ip: true # 注册实例时,优先使用 IP
- username: ruoyi
- password: 123456
+ username: admin
+ password: admin123
--- # xxl-job 配置
xxl:
@@ -51,7 +51,7 @@ spring:
# 主库数据源
master:
driverClassName: com.mysql.cj.jdbc.Driver
- url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true
+ url: jdbc:mysql://192.168.64.128:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true
username: root
password: root
# 从库数据源
@@ -113,7 +113,7 @@ spring:
spring:
redis:
# 地址
- host: localhost
+ host: 192.168.64.128
# 端口,默认为6379
port: 6379
# 数据库索引
diff --git a/ruoyi-admin/src/main/resources/application-local.yml b/ruoyi-admin/src/main/resources/application-local.yml
new file mode 100644
index 000000000..123b1c508
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/application-local.yml
@@ -0,0 +1,158 @@
+--- # 监控配置
+spring:
+ boot:
+ admin:
+ # Spring Boot Admin Client 客户端的相关配置
+ client:
+ # 增加客户端开关
+ enabled: true
+ # 设置 Spring Boot Admin Server 地址
+ url: http://localhost:9090/admin
+ instance:
+ prefer-ip: true # 注册实例时,优先使用 IP
+ username: admin
+ password: admin123
+
+--- # xxl-job 配置
+xxl:
+ job:
+ # 执行器开关
+ enabled: true
+ # 调度中心地址:如调度中心集群部署存在多个地址则用逗号分隔。
+ admin-addresses: http://localhost:9100/xxl-job-admin
+ # 执行器通讯TOKEN:非空时启用
+ access-token: xxl-job
+ # 执行器配置
+ executor:
+ # 执行器AppName:执行器心跳注册分组依据;为空则关闭自动注册
+ appname: xxl-job-executor
+ # 执行器端口号 执行器从9101开始往后写
+ port: 9101
+ # 执行器注册:默认IP:PORT
+ address:
+ # 执行器IP:默认自动获取IP
+ ip:
+ # 执行器运行日志文件存储磁盘路径
+ logpath: ./logs/xxl-job
+ # 执行器日志文件保存天数:大于3生效
+ logretentiondays: 30
+
+# 数据源配置
+spring:
+ datasource:
+ type: com.alibaba.druid.pool.DruidDataSource
+ # 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
+ dynamic:
+ # 性能分析插件(有性能损耗 不建议生产环境使用)
+ p6spy: true
+ # 设置默认的数据源或者数据源组,默认值即为 master
+ primary: master
+ datasource:
+ # 主库数据源
+ master:
+ driverClassName: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://127.0.0.1:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true
+ username: root
+ password: root
+ # 从库数据源
+ slave:
+ lazy: true
+ driverClassName: com.mysql.cj.jdbc.Driver
+ url:
+ username:
+ password:
+ druid:
+ # 初始连接数
+ initialSize: 5
+ # 最小连接池数量
+ minIdle: 10
+ # 最大连接池数量
+ maxActive: 20
+ # 配置获取连接等待超时的时间
+ maxWait: 60000
+ # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+ timeBetweenEvictionRunsMillis: 60000
+ # 配置一个连接在池中最小生存的时间,单位是毫秒
+ minEvictableIdleTimeMillis: 300000
+ # 配置一个连接在池中最大生存的时间,单位是毫秒
+ maxEvictableIdleTimeMillis: 900000
+ # 配置检测连接是否有效
+ validationQuery: SELECT 1 FROM DUAL
+ testWhileIdle: true
+ testOnBorrow: false
+ testOnReturn: false
+ # 注意这个值和druid原生不一致,默认启动了stat
+ filters: stat
+
+--- # druid 配置
+spring:
+ datasource:
+ druid:
+ webStatFilter:
+ enabled: true
+ statViewServlet:
+ enabled: true
+ # 设置白名单,不填则允许所有访问
+ allow:
+ url-pattern: /druid/*
+ # 控制台管理用户名和密码
+ login-username: ruoyi
+ login-password: 123456
+ filter:
+ stat:
+ enabled: true
+ # 慢SQL记录
+ log-slow-sql: true
+ slow-sql-millis: 1000
+ merge-sql: true
+ wall:
+ config:
+ multi-statement-allow: true
+
+--- # redis 配置
+spring:
+ redis:
+ # 地址
+ host: 127.0.0.1
+ # 端口,默认为6379
+ port: 6379
+ # 数据库索引
+ database: 11
+ # 密码
+ password:
+ # 连接超时时间
+ timeout: 10s
+ # 是否开启ssl
+ ssl: false
+
+redisson:
+ # 线程池数量
+ threads: 16
+ # Netty线程池数量
+ nettyThreads: 32
+ # 传输模式
+ transportMode: "NIO"
+ # 单节点配置
+ singleServerConfig:
+ # 客户端名称
+ clientName: ${ruoyi.name}
+ # 最小空闲连接数
+ connectionMinimumIdleSize: 32
+ # 连接池大小
+ connectionPoolSize: 64
+ # 连接空闲超时,单位:毫秒
+ idleConnectionTimeout: 10000
+ # 命令等待超时,单位:毫秒
+ timeout: 3000
+ # 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
+ retryAttempts: 3
+ # 命令重试发送时间间隔,单位:毫秒
+ retryInterval: 1500
+ # 发布和订阅连接的最小空闲连接数
+ subscriptionConnectionMinimumIdleSize: 1
+ # 发布和订阅连接池大小
+ subscriptionConnectionPoolSize: 50
+ # 单个连接最大订阅数量
+ subscriptionsPerConnection: 5
+ # DNS监测时间间隔,单位:毫秒
+ dnsMonitoringInterval: 5000
diff --git a/ruoyi-admin/src/main/resources/application-prod.yml b/ruoyi-admin/src/main/resources/application-prod.yml
index 9aac0e3b3..3e55fac37 100644
--- a/ruoyi-admin/src/main/resources/application-prod.yml
+++ b/ruoyi-admin/src/main/resources/application-prod.yml
@@ -51,7 +51,7 @@ spring:
# 主库数据源
master:
driverClassName: com.mysql.cj.jdbc.Driver
- url: jdbc:mysql://172.30.0.36:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true
+ url: jdbc:mysql://172.17.0.1:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&autoReconnect=true
username: root
password: root
# 从库数据源
@@ -113,7 +113,7 @@ spring:
spring:
redis:
# 地址
- host: 172.30.0.48
+ host: 172.17.0.1
# 端口,默认为6379
port: 6379
# 数据库索引
diff --git a/ruoyi-admin/src/main/resources/logback.xml b/ruoyi-admin/src/main/resources/logback.xml
index 33a169725..8b19a63ef 100644
--- a/ruoyi-admin/src/main/resources/logback.xml
+++ b/ruoyi-admin/src/main/resources/logback.xml
@@ -2,8 +2,8 @@
-
+ value="%red(%d{yyyy-MM-dd HH:mm:ss}) %highlight(%-5level) %green([%thread]) %boldMagenta(%logger{36}) - %msg%n"/>
+
@@ -31,7 +31,7 @@
INFO
-
+
${log.path}/sys-info.log
@@ -54,7 +54,7 @@
DENY
-
+
${log.path}/sys-error.log
@@ -76,7 +76,7 @@
DENY
-
+
${log.path}/sys-user.log
@@ -90,7 +90,7 @@
${log.pattern}
-
+
@@ -99,16 +99,16 @@
-
+
-
+
-
\ No newline at end of file
+
diff --git a/ruoyi-admin/src/main/resources/spy.properties b/ruoyi-admin/src/main/resources/spy.properties
index 0ed983b3b..3af094c2a 100644
--- a/ruoyi-admin/src/main/resources/spy.properties
+++ b/ruoyi-admin/src/main/resources/spy.properties
@@ -1,26 +1,26 @@
-# p6spy 性能分析插件配置文件
+# p6spy \u6027\u80FD\u5206\u6790\u63D2\u4EF6\u914D\u7F6E\u6587\u4EF6
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
-# 自定义日志打印
+# \u81EA\u5B9A\u4E49\u65E5\u5FD7\u6253\u5370
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
-#日志输出到控制台
+#\u65E5\u5FD7\u8F93\u51FA\u5230\u63A7\u5236\u53F0
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
-# 使用日志系统记录 sql
+# \u4F7F\u7528\u65E5\u5FD7\u7CFB\u7EDF\u8BB0\u5F55 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
-# 设置 p6spy driver 代理
+# \u8BBE\u7F6E p6spy driver \u4EE3\u7406
#deregisterdrivers=true
-# 取消JDBC URL前缀
+# \u53D6\u6D88JDBC URL\u524D\u7F00
useprefix=true
-# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
+# \u914D\u7F6E\u8BB0\u5F55 Log \u4F8B\u5916,\u53EF\u53BB\u6389\u7684\u7ED3\u679C\u96C6\u6709error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
-# 日期格式
+# \u65E5\u671F\u683C\u5F0F
dateformat=yyyy-MM-dd HH:mm:ss
-# 实际驱动可多个
+# \u5B9E\u9645\u9A71\u52A8\u53EF\u591A\u4E2A
#driverlist=org.h2.Driver
-# 是否开启慢SQL记录
+# \u662F\u5426\u5F00\u542F\u6162SQL\u8BB0\u5F55
outagedetection=true
-# 慢SQL记录标准 2 秒
+# \u6162SQL\u8BB0\u5F55\u6807\u51C6 2 \u79D2
outagedetectioninterval=2
-# 是否过滤 Log
+# \u662F\u5426\u8FC7\u6EE4 Log
filter=true
-# 过滤 Log 时所排除的表名列表,以逗号分隔
+# \u8FC7\u6EE4 Log \u65F6\u6240\u6392\u9664\u7684\u8868\u540D\u5217\u8868\uFF0C\u4EE5\u9017\u53F7\u5206\u9694
exclude=QRTZ_
diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml
index 12f14a06e..ba6f235b6 100644
--- a/ruoyi-common/pom.xml
+++ b/ruoyi-common/pom.xml
@@ -46,6 +46,10 @@
org.apache.commons
commons-lang3
+
+ com.google.guava
+ guava
+
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java
index 6d83fe30e..4b1ae5101 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java
@@ -142,4 +142,14 @@ public class Constants {
* LDAP 远程方法调用
*/
public static final String LOOKUP_LDAP = "ldap://";
+
+ /** 待审核 */
+ public static final String AUDIT_WAIT = "0";
+
+ /** 审核通过 */
+ public static final String AUDIT_PASS = "1";
+
+ /** 审核驳回 */
+ public static final String AUDIT_REJECT = "2";
+
}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/IscConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/IscConstants.java
new file mode 100644
index 000000000..793140ed5
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/IscConstants.java
@@ -0,0 +1,16 @@
+package com.ruoyi.common.constant;
+
+public class IscConstants {
+
+ /** 在线状态 在线 */
+ public static final String ONLINE_STATUS_ON = "1";
+
+ /** 申请类型 申请服务 */
+ public static final String APPLY_TYPE_APPLY = "0";
+
+ /** 申请类型 服务续期 */
+ public static final String APPLY_TYPE_RENEWAL = "1";
+
+ /** 申请类型 申请修改 */
+ public static final String APPLY_TYPE_MODIFY = "2";
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java
index 963ac5c4d..e69de29bb 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java
@@ -1,67 +0,0 @@
-package com.ruoyi.common.core.domain;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.ruoyi.common.core.domain.entity.SysDept;
-import com.ruoyi.common.core.domain.entity.SysMenu;
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import lombok.experimental.Accessors;
-
-import java.io.Serializable;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * Treeselect树结构实体类
- *
- * @author Lion Li
- */
-
-@Data
-@NoArgsConstructor
-@Accessors(chain = true)
-@ApiModel("树结构实体类")
-public class TreeSelect implements Serializable {
-
- private static final long serialVersionUID = 1L;
-
- /**
- * 节点ID
- */
- @ApiModelProperty(value = "节点ID")
- private Long id;
-
- /**
- * 节点名称
- */
- @ApiModelProperty(value = "节点名称")
- private String label;
-
- /**
- * 子节点
- */
- @ApiModelProperty(value = "子节点")
- @JsonInclude(JsonInclude.Include.NON_EMPTY)
- private List children;
-
- public TreeSelect(SysDept dept) {
- this.id = dept.getDeptId();
- this.label = dept.getDeptName();
- this.children = dept.getChildren()
- .stream()
- .map(d -> new TreeSelect((SysDept) d))
- .collect(Collectors.toList());
- }
-
- public TreeSelect(SysMenu menu) {
- this.id = menu.getMenuId();
- this.label = menu.getMenuName();
- this.children = menu.getChildren()
- .stream()
- .map(d -> new TreeSelect((SysMenu) d))
- .collect(Collectors.toList());
- }
-
-}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java
index 2d2f2e463..23024fe32 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java
@@ -55,4 +55,9 @@ public enum BusinessType {
* 清空数据
*/
CLEAN,
+
+ /**
+ * 审核
+ */
+ AUDIT
}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java
index 1e27f2494..c8c7adb08 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java
@@ -1,10 +1,12 @@
package com.ruoyi.common.utils;
+import cn.hutool.core.date.LocalDateTimeUtil;
import org.apache.commons.lang3.time.DateFormatUtils;
import java.lang.management.ManagementFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
+import java.time.ZoneId;
import java.util.Date;
/**
@@ -133,4 +135,30 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
// long sec = diff % nd % nh % nm / ns;
return day + "天" + hour + "小时" + min + "分钟";
}
+
+ /**
+ * 获取一天的开始时间 即00:00:00
+ *
+ * @param date 日期
+ * @return 一天的开始时间
+ */
+ public static Date beginOfDay(Date date)
+ {
+ return Date.from(LocalDateTimeUtil.beginOfDay(LocalDateTimeUtil.of(date))
+ .atZone(ZoneId.systemDefault()).toInstant());
+ }
+
+
+
+ /**
+ * 获取一天的结束时间 即23:59:59
+ *
+ * @param date 日期
+ * @return 一天的最后一秒
+ */
+ public static Date endOfDay(Date date)
+ {
+ return Date.from(LocalDateTimeUtil.endOfDay(LocalDateTimeUtil.of(date))
+ .atZone(ZoneId.systemDefault()).toInstant());
+ }
}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/FullPathUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/FullPathUtils.java
new file mode 100644
index 000000000..861ecf311
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/FullPathUtils.java
@@ -0,0 +1,87 @@
+package com.ruoyi.common.utils;
+
+public final class FullPathUtils {
+
+ public static final int FULL_PATH_LENGTH = 30;
+ public static final int FULL_PATH_LEVEL_LENGTH = 3;
+ public static final char REPEAT_CHAR = '0';
+ public static final char REPEAT_CHAR_MAX = '9';
+
+ /**
+ * 生成 FULL_PATH
+ *
+ * @param parentFullPath 父级full_path (可空)
+ * @param maxFullPath 同级最大 full_path(可空)
+ * @return
+ */
+ public static String genFullPath(String parentFullPath, String maxFullPath)
+ {
+ int maxNum = 0;
+ String parentPath = StringUtils.EMPTY;
+ if (StringUtils.isNotBlank(maxFullPath))
+ {
+ int start = maxFullPath.length() - FULL_PATH_LEVEL_LENGTH;
+ int end = maxFullPath.length() - 1;
+ int temp = 0;
+ while (start > -1)
+ {
+ temp = Integer.parseInt(maxFullPath.substring(start, end));
+ if (temp > 0)
+ {
+ parentPath = maxFullPath.substring(0, start);
+ break;
+ }
+ end = start;
+ start -= FULL_PATH_LEVEL_LENGTH;
+ }
+ maxNum = temp;
+ } else if (StringUtils.isNotBlank(parentFullPath))
+ {
+ parentPath = getParentPath(parentFullPath);
+ }
+ int repeat = FULL_PATH_LENGTH - parentPath.length() - FULL_PATH_LEVEL_LENGTH;
+ String suffix = StringUtils.repeat(REPEAT_CHAR, repeat);
+ return String.format("%s%03d%s", parentPath, ++maxNum, suffix);
+ }
+
+ /**
+ * 根据FULL_PATH 获取最大 子FULL_PATH
+ *
+ * @param fullPath
+ */
+ public static String genMaxFullPath(String fullPath)
+ {
+ String parentPath = StringUtils.EMPTY;
+ if (StringUtils.isNotBlank(fullPath))
+ {
+ parentPath = getParentPath(fullPath);
+ }
+ int repeat = FULL_PATH_LENGTH - parentPath.length();
+ String suffix = StringUtils.repeat(REPEAT_CHAR_MAX, repeat);
+ return parentPath + suffix;
+ }
+
+ /**
+ * 获取父 Path
+ *
+ * @param fullPath
+ * @return
+ */
+ private static String getParentPath(String fullPath)
+ {
+ int temp;
+ int start = fullPath.length() - FULL_PATH_LEVEL_LENGTH;
+ int end = fullPath.length() - 1;
+ while (start > -1)
+ {
+ temp = Integer.parseInt(fullPath.substring(start, end));
+ if (temp > 0)
+ {
+ return fullPath.substring(0, end);
+ }
+ end = start;
+ start -= FULL_PATH_LEVEL_LENGTH;
+ }
+ return StringUtils.EMPTY;
+ }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/TreeUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/TreeUtils.java
new file mode 100644
index 000000000..73246dbe4
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/TreeUtils.java
@@ -0,0 +1,132 @@
+package com.ruoyi.common.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.core.lang.tree.TreeNodeConfig;
+import cn.hutool.core.lang.tree.TreeUtil;
+import cn.hutool.core.lang.tree.parser.NodeParser;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 树结构工具类
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-08
+ */
+public class TreeUtils
+{
+
+ public static final TreeNodeConfig DEFAULT_TREE_NODE_CONFIG = new TreeNodeConfig();
+ public static final long DEFAULT_PARENT_ID = 0L;
+ public static final String FIELD_NODE_EXIST = "exist";
+
+ static {
+ DEFAULT_TREE_NODE_CONFIG.setNameKey("label");
+ }
+
+ /**
+ * 设置默认 parentId
+ *
+ * @param list
+ * @param nodeParser
+ * @param
+ * @return
+ */
+ public static List> build(List list, NodeParser nodeParser)
+ {
+ return TreeUtil.build(list, DEFAULT_PARENT_ID, DEFAULT_TREE_NODE_CONFIG, nodeParser);
+ }
+
+
+ /**
+ * 树构建
+ *
+ * @param 转换的实体 为数据源里的对象类型
+ * @param ID类型
+ * @param list 源数据集合
+ * @param parentId 最顶层父id值 一般为 0 之类
+ * @param nodeParser 转换器
+ * @param needExistField 是否需要 已存在字段
+ * @return List
+ */
+ public static List> build(List list, E parentId, NodeParser nodeParser,
+ boolean needExistField)
+ {
+ final List> treeList = CollUtil.newArrayList();
+ final TreeNodeConfig treeNodeConfig = DEFAULT_TREE_NODE_CONFIG;
+ Tree tree;
+ for (T obj : list) {
+ tree = new Tree<>(treeNodeConfig);
+ nodeParser.parse(obj, tree);
+ treeList.add(tree);
+ }
+
+ List> finalTreeList = CollUtil.newArrayList();
+ for (Tree node : treeList) {
+ if (ObjectUtil.equals(parentId, node.getParentId())) {
+ finalTreeList.add(node);
+ innerBuild(treeList, node, 0, treeNodeConfig.getDeep(), needExistField);
+ }
+ }
+ // 内存每层已经排过了 这是最外层排序
+ finalTreeList = finalTreeList.stream().sorted().collect(Collectors.toList());
+ return finalTreeList;
+ }
+
+ /**
+ * 递归处理
+ *
+ * @param treeNodes 数据集合
+ * @param parentNode 当前节点
+ * @param deep 已递归深度
+ * @param maxDeep 最大递归深度 可能为null即不限制
+ * @param needExistField 是否需要 已存在字段
+ */
+ private static void innerBuild(List> treeNodes, Tree parentNode, int deep, Integer maxDeep,
+ boolean needExistField)
+ {
+ if (CollUtil.isEmpty(treeNodes)) {
+ return;
+ }
+ //maxDeep 可能为空
+ if (maxDeep != null && deep >= maxDeep) {
+ return;
+ }
+ // 每层排序 TreeNodeMap 实现了Comparable接口
+ treeNodes = treeNodes.stream().sorted().collect(Collectors.toList());
+ for (Tree childNode : treeNodes) {
+ if (parentNode.getId().equals(childNode.getParentId())) {
+ List> children = parentNode.getChildren();
+ if (children == null) {
+ children = CollUtil.newArrayList();
+ parentNode.setChildren(children);
+ }
+ children.add(childNode);
+ childNode.setParent(parentNode);
+ if (needExistField) {
+ Object pobj = parentNode.get(FIELD_NODE_EXIST);
+ boolean exist = pobj == null ? true : (boolean) pobj;
+ if (exist && !CollectionUtils.isEmpty(children)) {
+ long count = children.stream().filter(t -> {
+ Object obj = t.get(FIELD_NODE_EXIST);
+ if (obj != null) {
+ return !(boolean) obj;
+ } else {
+ return false;
+ }
+ }).count();
+ if (count > 0) {
+ exist = false;
+ }
+ }
+ parentNode.put(FIELD_NODE_EXIST, exist);
+ }
+ innerBuild(treeNodes, childNode, deep + 1, maxDeep, needExistField);
+ }
+ }
+ }
+}
diff --git a/ruoyi-extend/pom.xml b/ruoyi-extend/pom.xml
index e44078a52..8910dfef2 100644
--- a/ruoyi-extend/pom.xml
+++ b/ruoyi-extend/pom.xml
@@ -14,6 +14,7 @@
ruoyi-monitor-admin
ruoyi-xxl-job-admin
+ ruoyi-isc-gateway
diff --git a/ruoyi-extend/ruoyi-isc-gateway/pom.xml b/ruoyi-extend/ruoyi-isc-gateway/pom.xml
new file mode 100644
index 000000000..ec6d5d414
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/pom.xml
@@ -0,0 +1,46 @@
+
+
+
+ ruoyi-vue-plus
+ com.ruoyi
+ 3.3.0
+ ../../pom.xml
+
+ 4.0.0
+
+ ruoyi-isc-gateway
+
+
+
+ com.ruoyi
+ ruoyi-isc-common
+
+
+ org.springframework.cloud
+ spring-cloud-starter-gateway
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+ org.redisson
+ redisson-spring-boot-starter
+
+
+ de.codecentric
+ spring-boot-admin-starter-client
+
+
+ org.projectlombok
+ lombok
+
+
+ cn.hutool
+ hutool-all
+
+
+
+
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/GatewayApplication.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/GatewayApplication.java
new file mode 100644
index 000000000..8fbc70508
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/GatewayApplication.java
@@ -0,0 +1,20 @@
+package com.ruoyi.gateway;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * 启动程序
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-11
+ */
+@SpringBootApplication
+public class GatewayApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(GatewayApplication.class, args);
+ System.out.println("Gateway 启动成功" );
+ }
+
+}
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/config/GatewayConfig.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/config/GatewayConfig.java
new file mode 100644
index 000000000..85cfe323f
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/config/GatewayConfig.java
@@ -0,0 +1,112 @@
+package com.ruoyi.gateway.config;
+
+import cn.hutool.extra.spring.SpringUtil;
+import com.ruoyi.gateway.config.handler.GlobalErrorWebExceptionHandler;
+import com.ruoyi.gateway.config.provider.RedisRouteDefinitionRepository;
+import com.ruoyi.gateway.filter.CustomerGlobalFilter;
+import com.ruoyi.gateway.ratelimit.CustomerRedisRateLimiter;
+import com.ruoyi.gateway.utils.caching.CachingRule;
+import com.ruoyi.isc.common.utils.beans.TopicMsg;
+import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RTopic;
+import org.redisson.api.RedissonClient;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
+import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
+import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
+import org.springframework.cloud.gateway.support.ConfigurationService;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.ApplicationEventPublisherAware;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.data.redis.core.script.RedisScript;
+import org.springframework.lang.NonNull;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+import static com.ruoyi.isc.common.constant.IscGatewayContants.*;
+
+/**
+ * Gateway 配置文件
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-11
+ */
+@Slf4j
+@Configuration
+public class GatewayConfig implements ApplicationEventPublisherAware {
+ public ApplicationEventPublisher publisher;
+ private final RedissonClient client = SpringUtil.getBean(RedissonClient.class);
+
+ {
+ subscribe(TOPIC_GATEWAY_REFRESH_ROUTE, TopicMsg.class, msg -> {
+ publisher.publishEvent(new RefreshRoutesEvent(this));
+ log.info("网关接收通知[{}]刷新本地路由!", msg);
+ });
+ subscribe(TOPIC_GATEWAY_RULE, TopicMsg.class, msg -> {
+ switch (msg.getType()) {
+ case ADD:
+ case UPDATE:
+ case DELETE:
+ CachingRule.evict(msg.getId());
+ log.info("网关接收通知id:[{}], type:[{}], msg:[{}]清除本地规则!", msg.getId(), msg.getType(), msg.getMsg());
+ break;
+ case REFRESH:
+ CachingRule.refresh();
+ log.info("网关接收通知id:[{}], type:[{}], msg:[{}]本地所有规则!", msg.getId(), msg.getType(), msg.getMsg());
+ break;
+ default:
+ break;
+ }
+ });
+ }
+
+ /**
+ * Redis 路由仓库
+ *
+ * @return redis路由仓库
+ */
+ @Bean
+ public RouteDefinitionRepository redisRouteDefinitionRepository() {
+ return new RedisRouteDefinitionRepository(client);
+ }
+
+ @Bean
+ public CustomerRedisRateLimiter customerRedisRateLimiter(ReactiveStringRedisTemplate redisTemplate,
+ @Qualifier(RedisRateLimiter.REDIS_SCRIPT_NAME) RedisScript> redisScript,
+ ConfigurationService configurationService, DefaultRedisScript timeRedisScript) {
+ return new CustomerRedisRateLimiter(redisTemplate, redisScript, configurationService, timeRedisScript);
+ }
+
+ @Bean
+ public GlobalFilter customerGlobalFilter(CustomerRedisRateLimiter customerRedisRateLimiter) {
+ return new CustomerGlobalFilter(customerRedisRateLimiter);
+ }
+
+ @Bean
+ public ErrorWebExceptionHandler errorWebExceptionHandler() {
+ return new GlobalErrorWebExceptionHandler();
+ }
+
+ /**
+ * 订阅通道接收消息
+ *
+ * @param channelKey 通道key
+ * @param clazz 消息类型
+ * @param consumer 自定义处理
+ */
+ public void subscribe(String channelKey, Class clazz, Consumer consumer) {
+ RTopic topic = client.getTopic(channelKey);
+ topic.addListener(clazz, (channel, msg) -> consumer.accept(msg));
+ }
+
+ @Override
+ public void setApplicationEventPublisher(@NonNull ApplicationEventPublisher publisher) {
+ this.publisher = publisher;
+ }
+}
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/config/RedisConfig.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/config/RedisConfig.java
new file mode 100644
index 000000000..c238e3b2e
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/config/RedisConfig.java
@@ -0,0 +1,109 @@
+package com.ruoyi.gateway.config;
+
+import cn.hutool.core.util.StrUtil;
+import com.ruoyi.gateway.config.provider.RedissonProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.redisson.Redisson;
+import org.redisson.api.RedissonClient;
+import org.redisson.codec.JsonJacksonCodec;
+import org.redisson.config.Config;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+
+import java.io.IOException;
+
+/**
+ * redis配置
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@Configuration
+@EnableCaching
+public class RedisConfig extends CachingConfigurerSupport {
+
+ private static final String REDIS_PROTOCOL_PREFIX = "redis://";
+ private static final String REDISS_PROTOCOL_PREFIX = "rediss://";
+
+ @Autowired
+ private RedisProperties redisProperties;
+
+ @Autowired
+ private RedissonProperties redissonProperties;
+
+ @Bean(destroyMethod = "shutdown")
+ @ConditionalOnMissingBean(RedissonClient.class)
+ public RedissonClient redisson() throws IOException {
+ String prefix = REDIS_PROTOCOL_PREFIX;
+ if (redisProperties.isSsl()) {
+ prefix = REDISS_PROTOCOL_PREFIX;
+ }
+ Config config = new Config();
+ config.setThreads(redissonProperties.getThreads())
+ .setNettyThreads(redissonProperties.getNettyThreads())
+ .setCodec(JsonJacksonCodec.INSTANCE)
+ .setTransportMode(redissonProperties.getTransportMode());
+
+ RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
+ // 使用单机模式
+ config.useSingleServer()
+ .setAddress(prefix + redisProperties.getHost() + ":" + redisProperties.getPort())
+ .setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue())
+ .setDatabase(redisProperties.getDatabase())
+ .setPassword(StrUtil.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null)
+ .setTimeout(singleServerConfig.getTimeout())
+ .setRetryAttempts(singleServerConfig.getRetryAttempts())
+ .setRetryInterval(singleServerConfig.getRetryInterval())
+ .setSubscriptionsPerConnection(singleServerConfig.getSubscriptionsPerConnection())
+ .setClientName(singleServerConfig.getClientName())
+ .setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
+ .setSubscriptionConnectionMinimumIdleSize(singleServerConfig.getSubscriptionConnectionMinimumIdleSize())
+ .setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
+ .setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
+ .setConnectionPoolSize(singleServerConfig.getConnectionPoolSize())
+ .setDnsMonitoringInterval(singleServerConfig.getDnsMonitoringInterval());
+ RedissonClient redissonClient = Redisson.create(config);
+ log.info("初始化 redis 配置");
+ return redissonClient;
+ }
+
+ @Bean
+ public DefaultRedisScript limitScript() {
+ DefaultRedisScript redisScript = new DefaultRedisScript<>();
+ redisScript.setScriptText(limitScriptText());
+ redisScript.setResultType(Long.class);
+ return redisScript;
+ }
+
+ /**
+ * 限流脚本
+ */
+ private String limitScriptText() {
+ return StrUtil.builder()
+ .append("local key = KEYS[1]\n")
+ .append("local count = tonumber(ARGV[1])\n")
+ .append("local time = tonumber(ARGV[2])\n")
+ .append("local current = tonumber(redis.call('get', key));\n")
+ .append("if current == nil then\n")
+ .append(" current = 1 \n")
+ .append("end\n")
+ //.append("redis.log(redis.LOG_WARNING, \"current \" .. current)\n")
+ .append("if current > count then\n")
+ .append(" return current;\n")
+ .append("end\n")
+ .append("current = tonumber(redis.call('incr', key))\n")
+ //.append("redis.log(redis.LOG_WARNING, \"current \" .. current)\n")
+ .append("if current == 1 then\n")
+ .append(" redis.call('expire', key, time)\n")
+ .append("end\n")
+ .append("return current;")
+ .toString();
+ }
+
+}
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/config/handler/GlobalErrorWebExceptionHandler.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/config/handler/GlobalErrorWebExceptionHandler.java
new file mode 100644
index 000000000..e6684d255
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/config/handler/GlobalErrorWebExceptionHandler.java
@@ -0,0 +1,100 @@
+package com.ruoyi.gateway.config.handler;
+
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
+import org.springframework.core.annotation.Order;
+import org.springframework.core.io.buffer.DataBufferFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.server.RequestPath;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.web.server.ResponseStatusException;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.handler.ResponseStatusExceptionHandler;
+import reactor.core.publisher.Mono;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * 网关异常通用处理器,只作用在webflux 环境下 , 优先级低于 {@link ResponseStatusExceptionHandler} 执行
+ * @author Wenchao Gong
+ * @date 2021-10-18
+ */
+@Slf4j
+@Order(-1)
+public class GlobalErrorWebExceptionHandler implements ErrorWebExceptionHandler {
+
+ public static final String NOT_FOUND_MESSAGE = "服务不存在";
+ public static final String SERVICE_UNAVAILABLE_MESSAGE = "服务暂时不可用";
+ public static final String GATEWAY_TIMEOUT_MESSAGE = "服务响应超时";
+
+ @Override
+ public Mono handle(ServerWebExchange exchange, Throwable ex)
+ {
+ ServerHttpResponse response = exchange.getResponse();
+
+ if (response.isCommitted()) {
+ return Mono.error(ex);
+ }
+
+ // header set
+ response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
+ if (ex instanceof ResponseStatusException) {
+ final ResponseStatusException statusException = (ResponseStatusException) ex;
+ response.setStatusCode(statusException.getStatus());
+ }
+ Optional code = Optional.ofNullable(response.getRawStatusCode());
+ final String message = getMessage(ex);
+
+ return response.writeWith(Mono.fromSupplier(() -> {
+ DataBufferFactory bufferFactory = response.bufferFactory();
+ return bufferFactory.wrap(JSONUtil.toJsonStr(result(code, message, exchange.getRequest().getPath()))
+ .getBytes(StandardCharsets.UTF_8));
+ }));
+ }
+
+ private String getMessage(Throwable ex)
+ {
+ String reason;
+ if(ex instanceof ResponseStatusException) {
+ ResponseStatusException statusException = (ResponseStatusException) ex;
+ switch (statusException.getStatus()) {
+ case NOT_FOUND:
+ reason = NOT_FOUND_MESSAGE;
+ break;
+ case SERVICE_UNAVAILABLE:
+ reason = SERVICE_UNAVAILABLE_MESSAGE;
+ break;
+ case GATEWAY_TIMEOUT:
+ reason = GATEWAY_TIMEOUT_MESSAGE;
+ break;
+ default:
+ reason = statusException.getReason();
+ }
+ if(log.isDebugEnabled()){
+ log.debug(reason, ex);
+ }
+ if(StrUtil.isNotBlank(reason)) {
+ return reason;
+ }
+ }
+ String message = ex.getMessage();
+ log.error(message, ex);
+ return message;
+ }
+
+ private Map result(Optional code, String message, RequestPath path) {
+ final HashMap result = MapUtil.newHashMap(3);
+ result.put("code", code.orElse(HttpStatus.INTERNAL_SERVER_ERROR.value()));
+ result.put("message", message);
+ result.put("path", path.value());
+ result.put("data", null);
+ return result;
+ }
+}
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/config/provider/RedisRouteDefinitionRepository.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/config/provider/RedisRouteDefinitionRepository.java
new file mode 100644
index 000000000..492de65d2
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/config/provider/RedisRouteDefinitionRepository.java
@@ -0,0 +1,61 @@
+package com.ruoyi.gateway.config.provider;
+
+import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RMap;
+import org.redisson.api.RedissonClient;
+import org.redisson.client.codec.Codec;
+import org.redisson.codec.TypedJsonJacksonCodec;
+import org.springframework.cloud.gateway.route.RouteDefinition;
+import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import static com.ruoyi.isc.common.constant.IscRedisKeys.KEY_ROUTES;
+
+/**
+ * Redis 路由仓库
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-11
+ */
+@Slf4j
+public class RedisRouteDefinitionRepository implements RouteDefinitionRepository
+{
+ public static final Codec ROUTE_CODES_INSTANCE = new TypedJsonJacksonCodec(String.class, RouteDefinition.class);
+ private final RedissonClient redissonClient;
+
+ public RedisRouteDefinitionRepository(RedissonClient redissonClient)
+ {
+ this.redissonClient = redissonClient;
+ }
+
+ @Override
+ public Flux getRouteDefinitions()
+ {
+ RMap map = redissonClient.getMap(KEY_ROUTES, ROUTE_CODES_INSTANCE);
+ log.debug("Routes from redis size:[{}]!", map.size());
+ return Flux.fromIterable(map.values());
+ }
+
+ @Override
+ public Mono save(Mono route)
+ {
+ return route.flatMap(definition -> {
+ RMap map = redissonClient.getMap(KEY_ROUTES, ROUTE_CODES_INSTANCE);
+ map.put(definition.getId(), definition);
+ log.debug("Saving route to redis id:[{}]!", definition.getId());
+ return Mono.empty();
+ });
+ }
+
+ @Override
+ public Mono delete(Mono routeId)
+ {
+ return routeId.flatMap(id -> {
+ RMap map = redissonClient.getMap(KEY_ROUTES, ROUTE_CODES_INSTANCE);
+ map.remove(id);
+ log.debug("Remove route from redis id:[{}]!", id);
+ return Mono.empty();
+ });
+ }
+}
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/config/provider/RedissonProperties.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/config/provider/RedissonProperties.java
new file mode 100644
index 000000000..9a74bc703
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/config/provider/RedissonProperties.java
@@ -0,0 +1,134 @@
+package com.ruoyi.gateway.config.provider;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.redisson.config.TransportMode;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * Redisson 配置属性
+ *
+ * @author Lion Li
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "redisson")
+public class RedissonProperties
+{
+
+ /**
+ * 线程池数量,默认值 = 当前处理核数量 * 2
+ */
+ private int threads;
+
+ /**
+ * Netty线程池数量,默认值 = 当前处理核数量 * 2
+ */
+ private int nettyThreads;
+
+ /**
+ * 传输模式
+ */
+ private TransportMode transportMode;
+
+ /**
+ * 单机服务配置
+ */
+ private SingleServerConfig singleServerConfig;
+
+ /**
+ * 缓存组
+ */
+ private List cacheGroup;
+
+ @Data
+ @NoArgsConstructor
+ public static class SingleServerConfig {
+
+ /**
+ * 客户端名称
+ */
+ private String clientName;
+
+ /**
+ * 最小空闲连接数
+ */
+ private int connectionMinimumIdleSize;
+
+ /**
+ * 连接池大小
+ */
+ private int connectionPoolSize;
+
+ /**
+ * 连接空闲超时,单位:毫秒
+ */
+ private int idleConnectionTimeout;
+
+ /**
+ * 命令等待超时,单位:毫秒
+ */
+ private int timeout;
+
+ /**
+ * 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
+ */
+ private int retryAttempts;
+
+ /**
+ * 命令重试发送时间间隔,单位:毫秒
+ */
+ private int retryInterval;
+
+ /**
+ * 发布和订阅连接的最小空闲连接数
+ */
+ private int subscriptionConnectionMinimumIdleSize;
+
+ /**
+ * 发布和订阅连接池大小
+ */
+ private int subscriptionConnectionPoolSize;
+
+ /**
+ * 单个连接最大订阅数量
+ */
+ private int subscriptionsPerConnection;
+
+ /**
+ * DNS监测时间间隔,单位:毫秒
+ */
+ private int dnsMonitoringInterval;
+
+ }
+
+ @Data
+ @NoArgsConstructor
+ public static class CacheGroup {
+
+ /**
+ * 组id
+ */
+ private String groupId;
+
+ /**
+ * 组过期时间
+ */
+ private long ttl;
+
+ /**
+ * 组最大空闲时间
+ */
+ private long maxIdleTime;
+
+ /**
+ * 组最大长度
+ */
+ private int maxSize;
+
+ }
+
+}
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/constant/ErrorMessageConstant.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/constant/ErrorMessageConstant.java
new file mode 100644
index 000000000..917c53a5e
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/constant/ErrorMessageConstant.java
@@ -0,0 +1,15 @@
+package com.ruoyi.gateway.constant;
+
+/**
+ * 错误消息常量
+ * @author Wenchao Gong
+ * @date 2021-10-18
+ */
+public interface ErrorMessageConstant {
+ String AK_REQUIRE = "没有找到授权码[%s]";
+ String CONTENT_TYPE_NOT_SUPPORTED = "请求ContentType[%s]不支持";
+ String METHOD_NOT_SUPPORTED = "请求Method[%s]不支持";
+ String RATE_LIMIT = "调用次数[>%d/%s]过多";
+ String RULE_EXPIRED = "规则已过期[%s]";
+ String RULE_NOT_EXIST = "授权码[%s]对应服务[%s]规则不存在";
+}
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/AkRequireException.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/AkRequireException.java
new file mode 100644
index 000000000..a51c555db
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/AkRequireException.java
@@ -0,0 +1,17 @@
+package com.ruoyi.gateway.exception;
+
+import com.ruoyi.gateway.constant.ErrorMessageConstant;
+
+/**
+ * 网关异常-AK必要
+ *
+ * @author Wenchao Gong
+ * @date 2021-10-04
+ */
+public class AkRequireException extends GatewayException {
+
+ public AkRequireException(String ak)
+ {
+ super(String.format(ErrorMessageConstant.AK_REQUIRE, ak));
+ }
+}
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/ContentTypeNotSupportedException.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/ContentTypeNotSupportedException.java
new file mode 100644
index 000000000..4d133af0d
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/ContentTypeNotSupportedException.java
@@ -0,0 +1,18 @@
+package com.ruoyi.gateway.exception;
+
+import com.ruoyi.gateway.constant.ErrorMessageConstant;
+import org.springframework.http.MediaType;
+
+/**
+ * 网关异常-请求方法不支持
+ *
+ * @author Wenchao Gong
+ * @date 2021-10-04
+ */
+public class ContentTypeNotSupportedException extends GatewayException {
+
+ public ContentTypeNotSupportedException(MediaType contentType)
+ {
+ super(String.format(ErrorMessageConstant.CONTENT_TYPE_NOT_SUPPORTED, contentType));
+ }
+}
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/GatewayException.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/GatewayException.java
new file mode 100644
index 000000000..cd7f79a42
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/GatewayException.java
@@ -0,0 +1,38 @@
+package com.ruoyi.gateway.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.server.ResponseStatusException;
+
+/**
+ * 网关异常
+ *
+ * @author Wenchao Gong
+ * @date 2021-10-04
+ */
+public class GatewayException extends ResponseStatusException {
+
+ public GatewayException(String reason)
+ {
+ super(HttpStatus.INTERNAL_SERVER_ERROR, reason);
+ }
+
+ public GatewayException(HttpStatus status)
+ {
+ super(status);
+ }
+
+ public GatewayException(HttpStatus status, String reason)
+ {
+ super(status, reason);
+ }
+
+ public GatewayException(HttpStatus status, String reason, Throwable cause)
+ {
+ super(status, reason, cause);
+ }
+
+ public GatewayException(int rawStatusCode, String reason, Throwable cause)
+ {
+ super(rawStatusCode, reason, cause);
+ }
+}
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/MethodNotSupportedException.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/MethodNotSupportedException.java
new file mode 100644
index 000000000..15fe606ff
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/MethodNotSupportedException.java
@@ -0,0 +1,18 @@
+package com.ruoyi.gateway.exception;
+
+import com.ruoyi.gateway.constant.ErrorMessageConstant;
+import org.springframework.http.HttpMethod;
+
+/**
+ * 网关异常-请求方法不支持
+ *
+ * @author Wenchao Gong
+ * @date 2021-10-04
+ */
+public class MethodNotSupportedException extends GatewayException {
+
+ public MethodNotSupportedException(HttpMethod httpMethod)
+ {
+ super(String.format(ErrorMessageConstant.METHOD_NOT_SUPPORTED, httpMethod));
+ }
+}
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/RateLimitException.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/RateLimitException.java
new file mode 100644
index 000000000..e29728513
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/RateLimitException.java
@@ -0,0 +1,20 @@
+package com.ruoyi.gateway.exception;
+
+import com.ruoyi.gateway.constant.ErrorMessageConstant;
+import org.springframework.http.HttpStatus;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 网关异常-限流异常
+ *
+ * @author Wenchao Gong
+ * @date 2021-10-04
+ */
+public class RateLimitException extends GatewayException {
+
+ public RateLimitException(long limit, TimeUnit timeUnit)
+ {
+ super(HttpStatus.TOO_MANY_REQUESTS, String.format(ErrorMessageConstant.RATE_LIMIT, limit, timeUnit.name()));
+ }
+}
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/RuleExpiredException.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/RuleExpiredException.java
new file mode 100644
index 000000000..d2e1e918e
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/RuleExpiredException.java
@@ -0,0 +1,20 @@
+package com.ruoyi.gateway.exception;
+
+import cn.hutool.core.date.DateUtil;
+import com.ruoyi.gateway.constant.ErrorMessageConstant;
+
+import java.util.Date;
+
+/**
+ * 网关异常-规则已过期
+ *
+ * @author Wenchao Gong
+ * @date 2021-10-04
+ */
+public class RuleExpiredException extends GatewayException {
+
+ public RuleExpiredException(Date endTime)
+ {
+ super(String.format(ErrorMessageConstant.RULE_EXPIRED, DateUtil.formatDateTime(endTime)));
+ }
+}
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/RuleNotExistException.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/RuleNotExistException.java
new file mode 100644
index 000000000..15cca6e2f
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/RuleNotExistException.java
@@ -0,0 +1,17 @@
+package com.ruoyi.gateway.exception;
+
+import com.ruoyi.gateway.constant.ErrorMessageConstant;
+
+/**
+ * 网关异常-规则不存在
+ *
+ * @author Wenchao Gong
+ * @date 2021-10-04
+ */
+public class RuleNotExistException extends GatewayException {
+
+ public RuleNotExistException(String ak, String path)
+ {
+ super(String.format(ErrorMessageConstant.RULE_NOT_EXIST, ak, path));
+ }
+}
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/filter/CustomerGlobalFilter.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/filter/CustomerGlobalFilter.java
new file mode 100644
index 000000000..4b49b0796
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/filter/CustomerGlobalFilter.java
@@ -0,0 +1,378 @@
+package com.ruoyi.gateway.filter;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONException;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.ruoyi.gateway.exception.*;
+import com.ruoyi.gateway.ratelimit.CustomerRedisRateLimiter;
+import com.ruoyi.gateway.utils.GatewayUtils;
+import com.ruoyi.isc.common.utils.beans.IscRule;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.cloud.gateway.route.Route;
+import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
+import org.springframework.core.Ordered;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.reactive.function.server.HandlerStrategies;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.util.UriComponentsBuilder;
+import reactor.core.publisher.Mono;
+
+import java.net.URI;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import static com.ruoyi.gateway.filter.CustomerGlobalFilter.AccessKey.AccessKeyType;
+import static com.ruoyi.gateway.filter.CustomerGlobalFilter.AccessKey.AccessKeyType.*;
+import static com.ruoyi.isc.common.constant.IscGatewayContants.*;
+import static org.springframework.util.CollectionUtils.unmodifiableMultiValueMap;
+
+/**
+ * 自定义全局过滤器,操作如下:
+ * 1.ak 是否存在
+ * 2.是否到期
+ * 3.是否限流
+ * 4.删除参数ak
+ * 5.添加隐藏参数
+ *
+ * @author Wenchao Gong
+ * @date 2021-10-15
+ */
+public class CustomerGlobalFilter implements GlobalFilter, Ordered {
+ private final CustomerRedisRateLimiter rateLimiter;
+
+ public CustomerGlobalFilter(CustomerRedisRateLimiter rateLimiter) {
+ this.rateLimiter = rateLimiter;
+ }
+
+ private static final TimeUnit[] TIME_UNITS = {TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS, TimeUnit.DAYS};
+
+ @Override
+ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+ final Route route = exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
+ final Map metadata = route.getMetadata();
+ final ServerHttpRequest request = exchange.getRequest();
+ //ak 是否存在
+ final HttpMethod httpMethod = request.getMethod();
+ AccessKey accessKey = new AccessKey(String.valueOf(metadata.getOrDefault(CONFIG_ACCESS_KEY_NAME_KEY,
+ ACCESS_KEY_NAME_DEFAULT)));
+ accessKey.set(GatewayUtils.getValue(null, () -> request.getHeaders().get(accessKey.name)), HEADER);
+ MultiValueMap queryParams = request.getQueryParams().containsKey(accessKey.name) ?
+ new LinkedMultiValueMap<>(request.getQueryParams()) : request.getQueryParams();
+ if (HttpMethod.GET.equals(httpMethod)) {
+ return handleGetRequest(exchange, chain, route, request, accessKey, queryParams);
+ } else if (HttpMethod.POST.equals(httpMethod)) {
+ return handlePostRequest(exchange, chain, route, request, accessKey, queryParams);
+ }
+ throw new MethodNotSupportedException(httpMethod);
+ }
+
+ /**
+ * 处理POST 请求
+ *
+ * @param exchange 当前服务交换器
+ * @param chain 当前过滤链
+ * @param route 路由信息
+ * @param request 请求信息
+ * @param accessKey AK信息
+ * @param queryParams URL查询参数
+ * @return 指示请求处理何时完成
+ */
+ private Mono handlePostRequest(ServerWebExchange exchange, GatewayFilterChain chain, Route route,
+ ServerHttpRequest request, AccessKey accessKey,
+ MultiValueMap queryParams) {
+ //处理 URL AccessKey
+ handleAccessKey(accessKey, () -> queryParams.get(accessKey.name), URI, false);
+ ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
+ final Mono modifiedBody = serverRequest.bodyToMono(String.class).defaultIfEmpty(StrUtil.EMPTY)
+ .flatMap(body -> {
+ MediaType mediaType = request.getHeaders().getContentType();
+ if (Objects.isNull(mediaType) || MediaType.APPLICATION_JSON.equals(mediaType)) {
+ try {
+ JSONObject jsonObj = StrUtil.isBlank(body) ? new JSONObject() : JSONUtil.parseObj(body);
+ return handlePostRequestJson(exchange, route, request, accessKey, jsonObj, queryParams);
+ }catch (JSONException e) {
+ if(Objects.nonNull(mediaType)) {
+ return Mono.error(e);
+ }
+ }
+ } else if (MediaType.APPLICATION_FORM_URLENCODED.equals(mediaType)) {
+ return handlePostRequestFormUrlencoded(exchange, route, request, accessKey, body, queryParams);
+ }
+ return Mono.error(() -> new ContentTypeNotSupportedException(mediaType));
+ });
+ //如果 AccessKey 在URI部分 URI需要重新生成
+ URI uri = URI.equals(accessKey.type) ? getNewUri().apply(request, queryParams) : null;
+ return GatewayUtils.modifyBody(exchange, chain, modifiedBody, uri);
+ }
+
+ /**
+ * 处理POST APPLICATION_FORM_URLENCODED 请求
+ *
+ * @param exchange 当前服务交换器
+ * @param route 路由信息
+ * @param request 请求信息
+ * @param accessKey AK信息
+ * @param body 请求Body信息
+ * @return 处理后的Body信息
+ */
+ private Mono extends String> handlePostRequestFormUrlencoded(ServerWebExchange exchange, Route route,
+ ServerHttpRequest request, AccessKey accessKey,
+ String body, MultiValueMap queryParams) {
+
+ if (StrUtil.isBlank(body)) {
+ Assert.notBlank(accessKey.value, () -> new AkRequireException(accessKey.name));
+ }
+ final List srcParams = Arrays.stream(body.split("&")).map(param -> param.split("="))
+ .collect(Collectors.toList());
+ final IscRule rule = handleRule(accessKey, () -> srcParams.stream()
+ .filter(param -> param.length > 0 && accessKey.name.equals(param[0]))
+ .map(param -> param[1]).collect(Collectors.toList()), route, BODY);
+ Supplier> rateLimiterAfterSupplier = () -> {
+ removeParam(accessKey, request, null, queryParams);
+ final List params = srcParams.stream().filter(param -> !accessKey.name.equals(param[0]))
+ .collect(Collectors.toList());
+ handleHiddenParams(route, params, (next, list) -> {
+ Object value;
+ if (Objects.isNull(value = next.getValue())) {
+ list.add(new String[]{next.getKey(), StrUtil.EMPTY});
+ } else if (value instanceof JSONArray) {
+ ((JSONArray) value).stream().map(String::valueOf)
+ .forEach(v -> list.add(new String[]{next.getKey(), v}));
+ } else {
+ list.add(new String[]{next.getKey(), next.getValue().toString()});
+ }
+ });
+ return Mono.just(params.stream().map(param -> param[0] + '=' + param[1]).collect(Collectors.joining("&")));
+ };
+
+ return rateLimiter(exchange, rule, route, 0, rateLimiterAfterSupplier);
+ }
+
+ /**
+ * 处理POST APPLICATION_JSON 请求
+ *
+ * @param exchange 当前服务交换器
+ * @param route 路由信息
+ * @param request 请求信息
+ * @param accessKey AK信息
+ * @param jsonObj 请求Body JSON信息
+ * @return 处理后的Body信息
+ */
+ private Mono extends String> handlePostRequestJson(ServerWebExchange exchange, Route route,
+ ServerHttpRequest request, AccessKey accessKey,
+ JSONObject jsonObj, MultiValueMap queryParams) {
+ final IscRule rule = handleRule(accessKey, () -> Collections.singletonList(jsonObj.get(accessKey.name,
+ String.class, true)), route, BODY);
+ Supplier> rateLimiterAfterSupplier = () -> {
+ removeParam(accessKey, request, () -> jsonObj.remove(accessKey.name), queryParams);
+ handleHiddenParams(route, jsonObj, (next, map) -> map.set(next.getKey(), next.getValue()));
+ return Mono.just(jsonObj.toString());
+ };
+
+ return rateLimiter(exchange, rule, route, 0, rateLimiterAfterSupplier);
+ }
+
+ /**
+ * 处理GET 请求
+ *
+ * @param exchange 当前服务交换器
+ * @param chain 当前过滤链
+ * @param route 路由信息
+ * @param request 请求信息
+ * @param accessKey AK信息
+ * @param queryParams URL查询参数
+ * @return 指示请求处理何时完成
+ */
+ private Mono handleGetRequest(ServerWebExchange exchange, GatewayFilterChain chain, Route route,
+ ServerHttpRequest request, AccessKey accessKey,
+ MultiValueMap queryParams) {
+ final IscRule rule = handleRule(accessKey, () -> queryParams.get(accessKey.name), route, URI);
+ Supplier> rateLimiterAfterSupplier = () -> {
+ removeParam(accessKey, request, null, queryParams);
+ handleHiddenParams(route, queryParams, (next, map) -> {
+ Object value;
+ if (Objects.isNull(value = next.getValue())) {
+ map.add(next.getKey(), StrUtil.EMPTY);
+ } else if (value instanceof JSONArray) {
+ map.put(next.getKey(), ((JSONArray) value).toList(String.class));
+ } else {
+ map.add(next.getKey(), value.toString());
+ }
+ });
+ ServerHttpRequest req = exchange.getRequest().mutate().uri(getNewUri().apply(request, queryParams)).build();
+ return chain.filter(exchange.mutate().request(req).build());
+ };
+ return rateLimiter(exchange, rule, route, 0, rateLimiterAfterSupplier);
+ }
+
+ private BiFunction, URI> getNewUri() {
+ return (request, queryParams) -> UriComponentsBuilder.fromUri(request.getURI())
+ .replaceQueryParams(unmodifiableMultiValueMap(queryParams)).build().toUri();
+ }
+
+ @Override
+ public int getOrder() {
+ return -1;
+ }
+
+ /**
+ * 处理规则
+ *
+ * @param accessKey AK信息
+ * @param valueSupplier valueList 生产者
+ * @param route 路由信息
+ * @param type AK类型
+ * @return AK对应服务规则信息
+ */
+ private IscRule handleRule(AccessKey accessKey, Supplier> valueSupplier, Route route, AccessKeyType type) {
+ if(Objects.nonNull(valueSupplier) && Objects.nonNull(type)) {
+ handleAccessKey(accessKey, valueSupplier, type, true);
+ }
+ //获取规则
+ final IscRule rule = GatewayUtils.getRequiredValue(() -> GatewayUtils.getRule(accessKey.value, route.getId()),
+ () -> new RuleNotExistException(accessKey.value, route.getId()));
+ //是否到期
+ GatewayUtils.isBefore(rule, () -> new RuleExpiredException(rule.getExpire()));
+ //设置AK 到ID 为了传参方便
+ rule.setId(accessKey.value);
+ return rule;
+ }
+
+ /**
+ * 处理 accessKey
+ *
+ * @param accessKey AccessKey
+ * @param valueSupplier valueList 生产者
+ * @param type AccessKeyType
+ * @param throwException 是否抛出异常
+ */
+ private void handleAccessKey(AccessKey accessKey, Supplier> valueSupplier, AccessKeyType type, boolean throwException) {
+ //获取AK
+ String value;
+ if(throwException) {
+ value = GatewayUtils.getValue(accessKey.value, valueSupplier, () -> new AkRequireException(accessKey.name));
+ }else {
+ value = GatewayUtils.getValue(accessKey.value, valueSupplier, null);
+ }
+ accessKey.set(value, type);
+ }
+
+ /**
+ * 删除参数
+ *
+ * @param accessKey AK信息
+ * @param request 请求
+ * @param removeSupplier 删除参数提供者
+ * @param queryParams URI请求参数
+ * @param 类型
+ */
+ private void removeParam(AccessKey accessKey, ServerHttpRequest request, Supplier removeSupplier,
+ MultiValueMap queryParams) {
+ //如果header中有AK,则删除
+ if (HEADER.equals(accessKey.type)) {
+ final HttpHeaders headers = request.getHeaders();
+ if(headers.containsKey(accessKey.name)) {
+ request.mutate().headers(headMap -> headMap.remove(accessKey.name)).build();
+ }
+ }else if(URI.equals(accessKey.type)) {
+ queryParams.remove(accessKey.name);
+ }
+ if (Objects.nonNull(removeSupplier)) {
+ removeSupplier.get();
+ }
+ }
+
+ /**
+ * 处理隐藏参数
+ *
+ * @param route 路由信息
+ * @param result 参数
+ * @param mapper 处理参数方法
+ * @param 参数类型
+ */
+ private void handleHiddenParams(Route route, U result, BiConsumer, U> mapper) {
+ final Object obj = route.getMetadata().get(CONFIG_ADD_PARAM_KEY);
+ if (Objects.isNull(obj)) {
+ return;
+ }
+ final JSONObject json = JSONUtil.parseObj(obj.toString());
+ if (json.isEmpty()) {
+ return;
+ }
+ for (Map.Entry next : json.entrySet()) {
+ mapper.accept(next, result);
+ }
+ }
+
+ /**
+ * 限流
+ *
+ * @param exchange 当前服务交换器
+ * @param rule 限流规则
+ * @param route 路由
+ * @param rateLimiterAfterSupplier 限流后操作(删除参数、添加隐藏参数,跳转)
+ * @param 最终返回类型
+ * @return rateLimiterAfterSupplier 返回类型
+ */
+ private Mono rateLimiter(ServerWebExchange exchange, IscRule rule, Route route, final int index,
+ Supplier> rateLimiterAfterSupplier) {
+ final TimeUnit timeUnit = TIME_UNITS[index];
+ final Long limit = TimeUnit.SECONDS.equals(timeUnit) ? rule.getSecondsLimit() : TimeUnit.MINUTES.equals(timeUnit)
+ ? rule.getMinutesLimit() : TimeUnit.HOURS.equals(timeUnit) ? rule.getHoursLimit() : rule.getDaysLimit();
+ if (Objects.isNull(limit) || limit <= 0L) {
+ if (TimeUnit.DAYS.equals(timeUnit)) {
+ return rateLimiterAfterSupplier.get();
+ }
+ return rateLimiter(exchange, rule, route, index + 1, rateLimiterAfterSupplier);
+ }
+ return rateLimiter.isAllowed(route.getId(), rule.getId(), limit, timeUnit).flatMap(response -> {
+ for (Map.Entry header : response.getHeaders().entrySet()) {
+ exchange.getResponse().getHeaders().add(header.getKey(), header.getValue());
+ }
+ if (response.isAllowed()) {
+ if (TimeUnit.DAYS.equals(timeUnit)) {
+ return rateLimiterAfterSupplier.get();
+ }
+ return rateLimiter(exchange, rule, route, index + 1, rateLimiterAfterSupplier);
+ }
+ return Mono.error(() -> new RateLimitException(limit, timeUnit));
+ });
+ }
+
+ /**
+ * AK 信息
+ */
+ public static class AccessKey {
+ public AccessKey(String name) {
+ this.name = name;
+ }
+ public void set(String value, AccessKeyType type) {
+ if(StrUtil.isNotBlank(value)) {
+ this.value = value;
+ this.type = type;
+ }
+ }
+ public enum AccessKeyType {
+ HEADER,
+ URI,
+ BODY
+ }
+ private final String name;
+ private String value;
+ private AccessKeyType type;
+ }
+}
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/ratelimit/CustomerRedisRateLimiter.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/ratelimit/CustomerRedisRateLimiter.java
new file mode 100644
index 000000000..0eea20016
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/ratelimit/CustomerRedisRateLimiter.java
@@ -0,0 +1,199 @@
+package com.ruoyi.gateway.ratelimit;
+
+import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.hutool.core.util.StrUtil;
+import com.ruoyi.gateway.utils.GatewayUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
+import org.springframework.cloud.gateway.support.ConfigurationService;
+import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.data.redis.core.script.RedisScript;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.temporal.ChronoUnit;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * @author Wenchao Gong
+ * @date 2021-10-16
+ */
+@Slf4j
+public class CustomerRedisRateLimiter extends RedisRateLimiter {
+
+ private final ReactiveStringRedisTemplate redisTemplate;
+
+ private final RedisScript> script;
+ private final RedisScript timeRedisScript;
+
+ private final AtomicBoolean initialized = new AtomicBoolean(false);
+
+ public CustomerRedisRateLimiter(ReactiveStringRedisTemplate redisTemplate, RedisScript> script,
+ ConfigurationService configurationService, DefaultRedisScript timeRedisScript) {
+ super(redisTemplate, script, configurationService);
+ this.redisTemplate = redisTemplate;
+ this.script = script;
+ this.timeRedisScript = timeRedisScript;
+ this.initialized.compareAndSet(false, true);
+ }
+
+ public Mono isAllowed(String routeId, String ak, Long replenishRate, TimeUnit timeUnit) {
+ Config routeConfig = new Config().setReplenishRate(replenishRate.intValue());
+ String id = GatewayUtils.getRouteKey(ak, routeId);
+ int time = 60;
+ LocalDateTime now = LocalDateTime.now();
+ List keys;
+ switch (timeUnit) {
+ case DAYS:
+ time += LocalDateTimeUtil.between(now, LocalDateTime.now().with(LocalTime.MAX), ChronoUnit.SECONDS);
+ keys = getKeys(id, now.getDayOfMonth(), timeUnit);
+ break;
+ case HOURS:
+ time += (60 - now.getMinute()) * 60 - now.getSecond();
+ keys = getKeys(id, now.getHour(), timeUnit);
+ break;
+ case MINUTES:
+ time += 60 - now.getSecond();
+ keys = getKeys(id, now.getMinute(), timeUnit);
+ break;
+ case SECONDS:
+ return isAllowed(routeConfig, id);
+ default:
+ return Mono.just(new Response(true, getHeaders(routeConfig, -1L)));
+ }
+ return isAllowed(routeConfig, keys, time, timeUnit);
+ }
+
+ public Mono isAllowed(Config routeConfig, String id) {
+ if (!this.initialized.get()) {
+ throw new IllegalStateException("RedisRateLimiter is not initialized");
+ }
+
+ // How many requests per second do you want a user to be allowed to do?
+ int replenishRate = routeConfig.getReplenishRate();
+
+ // How much bursting do you want to allow?
+ int burstCapacity = routeConfig.getBurstCapacity();
+
+ // How many tokens are requested per request?
+ int requestedTokens = routeConfig.getRequestedTokens();
+
+ try {
+ List keys = getKeys(id);
+
+ // The arguments to the LUA script. time() returns unixtime in seconds.
+ List scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "",
+ Instant.now().getEpochSecond() + "", requestedTokens + "");
+ // allowed, tokens_left = redis.eval(SCRIPT, keys, args)
+ Flux> flux = this.redisTemplate.execute(this.script, keys, scriptArgs);
+ // .log("redisratelimiter", Level.FINER);
+ return flux.onErrorResume(throwable -> {
+ if (log.isDebugEnabled()) {
+ log.debug("Error calling rate limiter lua", throwable);
+ }
+ return Flux.just(Arrays.asList(1L, -1L));
+ }).reduce(new ArrayList(), (longs, l) -> {
+ longs.addAll(l);
+ return longs;
+ }).map(results -> {
+ boolean allowed = results.get(0) == 1L;
+ Long tokensLeft = results.get(1);
+
+ Response response = new Response(allowed, getHeaders(routeConfig, tokensLeft));
+
+ if (log.isDebugEnabled()) {
+ log.debug("response: " + response);
+ }
+ return response;
+ });
+ }
+ catch (Exception e) {
+ /*
+ * We don't want a hard dependency on Redis to allow traffic. Make sure to set
+ * an alert so you know if this is happening too much. Stripe's observed
+ * failure rate is 0.01%.
+ */
+ log.error("Error determining if user allowed from redis", e);
+ }
+ return Mono.just(new Response(true, getHeaders(routeConfig, -1L)));
+ }
+
+ public Mono isAllowed(Config routeConfig, List keys, int time, TimeUnit timeUnit) {
+ if (!this.initialized.get()) {
+ throw new IllegalStateException("RedisRateLimiter is not initialized");
+ }
+
+ // How many requests per second do you want a user to be allowed to do?
+ int replenishRate = routeConfig.getReplenishRate();
+
+ // How much bursting do you want to allow?
+ int burstCapacity = routeConfig.getBurstCapacity();
+
+ // How many tokens are requested per request?
+ int requestedTokens = routeConfig.getRequestedTokens();
+
+ try {
+ // The arguments to the LUA script. time() returns unixtime in seconds.
+ List scriptArgs = Arrays.asList(replenishRate + "", time + "");
+ // allowed, tokens_left = redis.eval(SCRIPT, keys, args)
+ Flux flux = this.redisTemplate.execute(this.timeRedisScript, keys, scriptArgs);
+ // .log("redisratelimiter", Level.FINER);
+ return flux.next().onErrorResume(throwable -> {
+ if (log.isDebugEnabled()) {
+ log.debug("Error calling rate limiter lua", throwable);
+ }
+ return Mono.just(-1L);
+ }).map(tokensLeft -> {
+ boolean allowed = tokensLeft > 0 && tokensLeft < replenishRate;
+ Response response = new Response(allowed, getHeaders(routeConfig, tokensLeft, timeUnit));
+ if (log.isDebugEnabled()) {
+ log.debug("response: " + response);
+ }
+ return response;
+ });
+ }
+ catch (Exception e) {
+ /*
+ * We don't want a hard dependency on Redis to allow traffic. Make sure to set
+ * an alert so you know if this is happening too much. Stripe's observed
+ * failure rate is 0.01%.
+ */
+ log.error("Error determining if user allowed from redis", e);
+ }
+ return Mono.just(new Response(true, getHeaders(routeConfig, -1L, timeUnit)));
+ }
+
+ public Map getHeaders(Config config, Long tokensLeft, TimeUnit timeUnit) {
+ Map headers = new HashMap<>();
+ if (isIncludeHeaders()) {
+ String suffix = StrUtil.upperFirst(timeUnit.name().toLowerCase());
+ headers.put(this.getRemainingHeader() + '-' + suffix, tokensLeft.toString());
+ headers.put(this.getReplenishRateHeader() + '-' + suffix, String.valueOf(config.getReplenishRate()));
+ }
+ return headers;
+ }
+
+ static List getKeys(String id) {
+ // use `{}` around keys to use Redis Key hash tags
+ // this allows for using redis cluster
+
+ // Make a unique key per user.
+ String prefix = "rate_limiter.{" + id;
+
+ // You need two Redis keys for Token Bucket.
+ String tokenKey = prefix + "}.tokens";
+ String timestampKey = prefix + "}.timestamp";
+ return Arrays.asList(tokenKey, timestampKey);
+ }
+
+ static List getKeys(String id, int now, TimeUnit timeUnit) {
+ String tokenKey = "rate_limiter.{" + id + ':' + now + "}." + timeUnit.name();
+ return Arrays.asList(tokenKey);
+ }
+}
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/utils/GatewayUtils.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/utils/GatewayUtils.java
new file mode 100644
index 000000000..05035d4ba
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/utils/GatewayUtils.java
@@ -0,0 +1,187 @@
+package com.ruoyi.gateway.utils;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.extra.spring.SpringUtil;
+import com.ruoyi.gateway.utils.caching.CachingRule;
+import com.ruoyi.isc.common.utils.beans.IscRule;
+import org.redisson.api.RMap;
+import org.redisson.api.RedissonClient;
+import org.redisson.client.codec.Codec;
+import org.redisson.codec.TypedJsonJacksonCodec;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
+import org.springframework.cloud.gateway.support.BodyInserterContext;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
+import org.springframework.util.StringUtils;
+import org.springframework.web.reactive.function.BodyInserter;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.net.URI;
+import java.sql.Date;
+import java.time.Instant;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+import static com.ruoyi.isc.common.constant.IscGatewayContants.RULE_CODES_INSTANCE;
+import static com.ruoyi.isc.common.constant.IscRedisKeys.KEY_RULES;
+
+/**
+ * @author Wenchao Gong
+ * @date 2021-10-15
+ */
+public class GatewayUtils extends com.ruoyi.isc.common.utils.GatewayUtils {
+ private static RedissonClient client = SpringUtil.getBean(RedissonClient.class);
+
+ /**
+ * 修改请求Body
+ *
+ * @param exchange 当前服务交换器
+ * @param chain 当前过滤链
+ * @param publisher body体提供者
+ * @param newUri 新的URI(去掉AccessKey)
+ * @return 指示请求处理何时完成
+ */
+ public static Mono modifyBody(ServerWebExchange exchange, GatewayFilterChain chain, Mono publisher, URI newUri) {
+ BodyInserter bodyInserter = BodyInserters.fromPublisher(publisher, String.class);
+ HttpHeaders headers = new HttpHeaders();
+ headers.putAll(exchange.getRequest().getHeaders());
+ headers.remove(HttpHeaders.CONTENT_LENGTH);
+ CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
+ return bodyInserter.insert(outputMessage, new BodyInserterContext())
+ .then(Mono.defer(() -> {
+ ServerHttpRequest request = decorate(exchange, headers, outputMessage, newUri);
+ return chain.filter(exchange.mutate().request(request).build());
+ }));
+ }
+
+
+ public static ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers,
+ CachedBodyOutputMessage outputMessage, URI newUri) {
+ return new ServerHttpRequestDecorator(exchange.getRequest()) {
+ @Override
+ public HttpHeaders getHeaders() {
+ long contentLength = headers.getContentLength();
+ HttpHeaders httpHeaders = new HttpHeaders();
+ httpHeaders.putAll(super.getHeaders());
+ if (contentLength > 0L) {
+ httpHeaders.setContentLength(contentLength);
+ } else {
+ httpHeaders.set("Transfer-Encoding", "chunked");
+ }
+ return httpHeaders;
+ }
+
+ @Override
+ public Flux getBody() {
+ return outputMessage.getBody();
+ }
+
+ @Override
+ public URI getURI()
+ {
+ return Objects.isNull(newUri) ? super.getURI() : newUri;
+ }
+ };
+ }
+
+ /**
+ * 从 values 中获取第一个不为空 的值
+ *
+ * @param before 获取之前的值 如果不为空直接返回
+ * @param valueSupplier 值列表生产者
+ * @return
+ */
+ public static String getValue(String before, Supplier> valueSupplier) {
+ return getValue(before, valueSupplier, null);
+ }
+
+ /**
+ * 从 values 中获取第一个不为空 的值
+ *
+ * @param before 获取之前的值 如果不为空直接返回
+ * @param valueSupplier 值列表生产者
+ * @param errorMsgSupplier 异常信息生产者
+ * @param
+ * @return
+ */
+ public static String getValue(String before, Supplier> valueSupplier,
+ Supplier errorMsgSupplier) {
+ if (StringUtils.hasText(before)) {
+ return before;
+ }
+ final List values = valueSupplier.get();
+ if (CollectionUtil.isNotEmpty(values)) {
+ for (String value : values) {
+ if (StringUtils.hasText(value)) {
+ before = value;
+ break;
+ }
+ }
+ }
+ if (Objects.nonNull(errorMsgSupplier)) {
+ Assert.notBlank(before, errorMsgSupplier);
+ }
+ return before;
+ }
+
+ /**
+ * 获取必要值,可抛出异常
+ *
+ * @param valueSupplier
+ * @param errorMsgSupplier
+ * @param
+ * @param
+ * @return
+ */
+ public static T getRequiredValue(Supplier valueSupplier,
+ Supplier errorMsgSupplier) {
+ final T value = valueSupplier.get();
+ if (Objects.nonNull(errorMsgSupplier)) {
+ Assert.notNull(value, errorMsgSupplier);
+ }
+ return value;
+ }
+
+
+ /**
+ * 获取规则
+ *
+ * @param ak AK
+ * @param routeId 路由ID
+ * @return
+ */
+ public static IscRule getRule(String ak, String routeId) {
+ String key = getRouteKey(ak, routeId);
+ return CachingRule.getRule(key, GatewayUtils::getRedisRule);
+ }
+
+ /**
+ * 获取Redis Rule
+ * @param key
+ * @return
+ */
+ private static IscRule getRedisRule(String key) {
+ final RMap map = client.getMap(KEY_RULES, RULE_CODES_INSTANCE);
+ return map.get(key);
+ }
+
+ /**
+ * 断言是否超过结束时间
+ *
+ * @param rule
+ * @param errorMsgSupplier
+ * @param
+ */
+ public static void isBefore(IscRule rule, Supplier errorMsgSupplier) {
+ Assert.isTrue(Objects.nonNull(rule.getExpire()) && Date.from(Instant.now()).before(rule.getExpire()),
+ errorMsgSupplier);
+ }
+}
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/utils/caching/CachingRule.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/utils/caching/CachingRule.java
new file mode 100644
index 000000000..5ff5db1fc
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/utils/caching/CachingRule.java
@@ -0,0 +1,53 @@
+package com.ruoyi.gateway.utils.caching;
+
+import com.ruoyi.isc.common.utils.beans.IscRule;
+import org.springframework.cache.Cache;
+import org.springframework.cache.concurrent.ConcurrentMapCache;
+
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * @author Wenchao Gong
+ * @date 2021-10-22
+ */
+public class CachingRule {
+ public static final String RULE_CACHE_NAME = "RULE_CACHE";
+ public static final Cache RULE_CACHE = new ConcurrentMapCache(RULE_CACHE_NAME);
+
+ /**
+ * 通过 Key 获取缓存中的 Rule
+ *
+ * @param key 缓存key
+ * @param mapper 如果缓存中没有 key 从mapper 中获取
+ * @return
+ */
+ public static IscRule getRule(String key, Function mapper) {
+ Cache.ValueWrapper value = RULE_CACHE.get(key);
+ if (Objects.nonNull(value)) {
+ return (IscRule) value.get();
+ }
+ IscRule rule = mapper.apply(key);
+ RULE_CACHE.put(key, rule);
+ return rule;
+ }
+
+ /**
+ * 清除 缓存 如果key存在
+ *
+ * @param key 缓存key
+ * @return 是否清除
+ */
+ public static boolean evict(String key) {
+ return RULE_CACHE.evictIfPresent(key);
+ }
+
+ /**
+ * 清除所有缓存 一刷新整个缓存
+ * @return
+ */
+ public static boolean refresh() {
+ RULE_CACHE.clear();
+ return true;
+ }
+}
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/resources/application-dev.yml b/ruoyi-extend/ruoyi-isc-gateway/src/main/resources/application-dev.yml
new file mode 100644
index 000000000..7a45512e0
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/resources/application-dev.yml
@@ -0,0 +1,76 @@
+--- # 监控配置
+spring:
+ boot:
+ admin:
+ # Spring Boot Admin Client 客户端的相关配置
+ client:
+ # 增加客户端开关
+ enabled: true
+ # 设置 Spring Boot Admin Server 地址
+ url: http://localhost:9090/admin
+ instance:
+ prefer-ip: true # 注册实例时,优先使用 IP
+ username: admin
+ password: admin123
+
+--- # redis 配置
+spring:
+ redis:
+ # 地址
+ host: 192.168.64.128
+ # 端口,默认为6379
+ port: 6379
+ # 数据库索引
+ database: 0
+ # 密码
+ password:
+ # 连接超时时间
+ timeout: 10s
+ # 是否开启ssl
+ ssl: false
+
+redisson:
+ # 线程池数量
+ threads: 16
+ # Netty线程池数量
+ nettyThreads: 32
+ # 传输模式
+ transportMode: "NIO"
+ # 单节点配置
+ singleServerConfig:
+ # 客户端名称
+ clientName: ${ruoyi.name}
+ # 最小空闲连接数
+ connectionMinimumIdleSize: 32
+ # 连接池大小
+ connectionPoolSize: 64
+ # 连接空闲超时,单位:毫秒
+ idleConnectionTimeout: 10000
+ # 命令等待超时,单位:毫秒
+ timeout: 3000
+ # 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
+ retryAttempts: 3
+ # 命令重试发送时间间隔,单位:毫秒
+ retryInterval: 1500
+ # 发布和订阅连接的最小空闲连接数
+ subscriptionConnectionMinimumIdleSize: 1
+ # 发布和订阅连接池大小
+ subscriptionConnectionPoolSize: 50
+ # 单个连接最大订阅数量
+ subscriptionsPerConnection: 5
+ # DNS监测时间间隔,单位:毫秒
+ dnsMonitoringInterval: 5000
+
+--- # Actuator 监控端点的配置项
+management:
+ endpoints:
+ web:
+ # Actuator 提供的 API 接口的根目录。默认为 /actuator
+ base-path: /actuator
+ exposure:
+ # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。
+ # 生产环境不建议放开所有 根据项目需求放开即可
+ include: @endpoints.include@
+ endpoint:
+ logfile:
+ external-file: ./logs/sys-console.log
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/resources/application-local.yml b/ruoyi-extend/ruoyi-isc-gateway/src/main/resources/application-local.yml
new file mode 100644
index 000000000..4f07d97b8
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/resources/application-local.yml
@@ -0,0 +1,76 @@
+--- # 监控配置
+spring:
+ boot:
+ admin:
+ # Spring Boot Admin Client 客户端的相关配置
+ client:
+ # 增加客户端开关
+ enabled: true
+ # 设置 Spring Boot Admin Server 地址
+ url: http://localhost:9090/admin
+ instance:
+ prefer-ip: true # 注册实例时,优先使用 IP
+ username: admin
+ password: admin123
+
+--- # redis 配置
+spring:
+ redis:
+ # 地址
+ host: 127.0.0.1
+ # 端口,默认为6379
+ port: 6379
+ # 数据库索引
+ database: 11
+ # 密码
+ password:
+ # 连接超时时间
+ timeout: 10s
+ # 是否开启ssl
+ ssl: false
+
+redisson:
+ # 线程池数量
+ threads: 16
+ # Netty线程池数量
+ nettyThreads: 32
+ # 传输模式
+ transportMode: "NIO"
+ # 单节点配置
+ singleServerConfig:
+ # 客户端名称
+ clientName: ${ruoyi.name}
+ # 最小空闲连接数
+ connectionMinimumIdleSize: 32
+ # 连接池大小
+ connectionPoolSize: 64
+ # 连接空闲超时,单位:毫秒
+ idleConnectionTimeout: 10000
+ # 命令等待超时,单位:毫秒
+ timeout: 3000
+ # 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
+ retryAttempts: 3
+ # 命令重试发送时间间隔,单位:毫秒
+ retryInterval: 1500
+ # 发布和订阅连接的最小空闲连接数
+ subscriptionConnectionMinimumIdleSize: 1
+ # 发布和订阅连接池大小
+ subscriptionConnectionPoolSize: 50
+ # 单个连接最大订阅数量
+ subscriptionsPerConnection: 5
+ # DNS监测时间间隔,单位:毫秒
+ dnsMonitoringInterval: 5000
+
+--- # Actuator 监控端点的配置项
+management:
+ endpoints:
+ web:
+ # Actuator 提供的 API 接口的根目录。默认为 /actuator
+ base-path: /actuator
+ exposure:
+ # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。
+ # 生产环境不建议放开所有 根据项目需求放开即可
+ include: @endpoints.include@
+ endpoint:
+ logfile:
+ external-file: ./logs/sys-console.log
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/resources/application-prod.yml b/ruoyi-extend/ruoyi-isc-gateway/src/main/resources/application-prod.yml
new file mode 100644
index 000000000..0cedf863f
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/resources/application-prod.yml
@@ -0,0 +1,76 @@
+--- # 监控配置
+spring:
+ boot:
+ admin:
+ # Spring Boot Admin Client 客户端的相关配置
+ client:
+ # 增加客户端开关
+ enabled: true
+ # 设置 Spring Boot Admin Server 地址
+ url: http://172.17.0.1:9090/admin
+ instance:
+ prefer-ip: true # 注册实例时,优先使用 IP
+ username: admin
+ password: admin@123456
+
+--- # redis 配置
+spring:
+ redis:
+ # 地址
+ host: 172.17.0.1
+ # 端口,默认为6379
+ port: 6379
+ # 数据库索引
+ database: 0
+ # 密码
+ password:
+ # 连接超时时间
+ timeout: 10s
+ # 是否开启ssl
+ ssl: false
+
+redisson:
+ # 线程池数量
+ threads: 16
+ # Netty线程池数量
+ nettyThreads: 32
+ # 传输模式
+ transportMode: "NIO"
+ # 单节点配置
+ singleServerConfig:
+ # 客户端名称
+ clientName: ${ruoyi.name}
+ # 最小空闲连接数
+ connectionMinimumIdleSize: 32
+ # 连接池大小
+ connectionPoolSize: 64
+ # 连接空闲超时,单位:毫秒
+ idleConnectionTimeout: 10000
+ # 命令等待超时,单位:毫秒
+ timeout: 3000
+ # 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
+ retryAttempts: 3
+ # 命令重试发送时间间隔,单位:毫秒
+ retryInterval: 1500
+ # 发布和订阅连接的最小空闲连接数
+ subscriptionConnectionMinimumIdleSize: 1
+ # 发布和订阅连接池大小
+ subscriptionConnectionPoolSize: 50
+ # 单个连接最大订阅数量
+ subscriptionsPerConnection: 5
+ # DNS监测时间间隔,单位:毫秒
+ dnsMonitoringInterval: 5000
+
+--- # Actuator 监控端点的配置项
+management:
+ endpoints:
+ web:
+ # Actuator 提供的 API 接口的根目录。默认为 /actuator
+ base-path: /actuator
+ exposure:
+ # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。
+ # 生产环境不建议放开所有 根据项目需求放开即可
+ include: @endpoints.include@
+ endpoint:
+ logfile:
+ external-file: ./logs/sys-console.log
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/resources/application.yml b/ruoyi-extend/ruoyi-isc-gateway/src/main/resources/application.yml
new file mode 100644
index 000000000..99a081b1e
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/resources/application.yml
@@ -0,0 +1,20 @@
+# 项目相关配置
+ruoyi:
+ # 名称
+ name: RuoYi-Vue-Plus
+ # 版本
+ version: ${ruoyi-vue-plus.version}
+ # 版权年份
+ copyrightYear: 2021
+ # 实例演示开关
+ demoEnabled: true
+ # 获取ip地址开关
+ addressEnabled: true
+server:
+ port: 8090
+# Spring配置
+spring:
+ application:
+ name: RuoYi-Gateway
+ profiles:
+ active: @profiles.active@
diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/resources/logback.xml b/ruoyi-extend/ruoyi-isc-gateway/src/main/resources/logback.xml
new file mode 100644
index 000000000..663eef0c5
--- /dev/null
+++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/resources/logback.xml
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+ ${console.log.pattern}
+ utf-8
+
+
+
+
+
+ ${log.path}/sys-console.log
+
+
+ ${log.path}/sys-console.%d{yyyy-MM-dd}.log
+
+ 1
+
+
+ ${log.pattern}
+ utf-8
+
+
+
+ INFO
+
+
+
+
+
+ ${log.path}/sys-info.log
+
+
+
+ ${log.path}/sys-info.%d{yyyy-MM-dd}.log
+
+ 60
+
+
+ ${log.pattern}
+
+
+
+ INFO
+
+ ACCEPT
+
+ DENY
+
+
+
+
+ ${log.path}/sys-error.log
+
+
+
+ ${log.path}/sys-error.%d{yyyy-MM-dd}.log
+
+ 60
+
+
+ ${log.pattern}
+
+
+
+ ERROR
+
+ ACCEPT
+
+ DENY
+
+
+
+
+
+ ${log.path}/sys-user.log
+
+
+ ${log.path}/sys-user.%d{yyyy-MM-dd}.log
+
+ 60
+
+
+ ${log.pattern}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ruoyi-extend/ruoyi-monitor-admin/pom.xml b/ruoyi-extend/ruoyi-monitor-admin/pom.xml
index 33a78516d..1724e05a0 100644
--- a/ruoyi-extend/ruoyi-monitor-admin/pom.xml
+++ b/ruoyi-extend/ruoyi-monitor-admin/pom.xml
@@ -28,6 +28,22 @@
de.codecentric
spring-boot-admin-starter-server
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+
+ mysql
+ mysql-connector-java
+
+
+
+
+ com.alibaba
+ druid-spring-boot-starter
+
diff --git a/ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/config/SecurityConfig.java b/ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/config/SecurityConfig.java
index ca9072c78..2c41b3811 100644
--- a/ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/config/SecurityConfig.java
+++ b/ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/config/SecurityConfig.java
@@ -1,13 +1,19 @@
package com.ruoyi.monitor.admin.config;
import de.codecentric.boot.admin.server.config.AdminServerProperties;
+import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
+import javax.sql.DataSource;
+
/**
* admin 监控 安全配置
*
@@ -45,4 +51,20 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
.headers().frameOptions().disable();
}
+ @Bean
+ public UserDetailsService userDetailsService(DataSource dataSource) {
+ JdbcDaoImpl service = new JdbcDaoImpl();
+ service.setDataSource(dataSource);
+ return service;
+ }
+
+ /**
+ * 强散列哈希加密实现
+ */
+ @Bean
+ public BCryptPasswordEncoder bCryptPasswordEncoder()
+ {
+ return new BCryptPasswordEncoder();
+ }
+
}
diff --git a/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application-dev.yml b/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application-dev.yml
new file mode 100644
index 000000000..89832e3ff
--- /dev/null
+++ b/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application-dev.yml
@@ -0,0 +1,7 @@
+spring:
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ type: com.alibaba.druid.pool.DruidDataSource
+ url: jdbc:mysql://192.168.64.128:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true
+ username: root
+ password: root
\ No newline at end of file
diff --git a/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application-local.yml b/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application-local.yml
new file mode 100644
index 000000000..0a9ae9bf1
--- /dev/null
+++ b/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application-local.yml
@@ -0,0 +1,7 @@
+spring:
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ type: com.alibaba.druid.pool.DruidDataSource
+ url: jdbc:mysql://127.0.0.1:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true
+ username: root
+ password: root
\ No newline at end of file
diff --git a/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application-prod.yml b/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application-prod.yml
new file mode 100644
index 000000000..68fb54360
--- /dev/null
+++ b/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application-prod.yml
@@ -0,0 +1,7 @@
+spring:
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ type: com.alibaba.druid.pool.DruidDataSource
+ url: jdbc:mysql://172.17.0.1:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&autoReconnect=true
+ username: root
+ password: root
\ No newline at end of file
diff --git a/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml b/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml
index 631f3e772..012bdab41 100644
--- a/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml
+++ b/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml
@@ -1,11 +1,8 @@
server:
port: 9090
-
spring:
- security:
- user:
- name: ruoyi
- password: 123456
boot:
admin:
context-path: /admin
+ profiles:
+ active: @profiles.active@
\ No newline at end of file
diff --git a/ruoyi-extend/ruoyi-xxl-job-admin/src/main/resources/application-dev.yml b/ruoyi-extend/ruoyi-xxl-job-admin/src/main/resources/application-dev.yml
index 065b34262..0aa77cb64 100644
--- a/ruoyi-extend/ruoyi-xxl-job-admin/src/main/resources/application-dev.yml
+++ b/ruoyi-extend/ruoyi-xxl-job-admin/src/main/resources/application-dev.yml
@@ -3,12 +3,12 @@ spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
- url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
+ url: jdbc:mysql://192.168.64.128:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true
username: root
password: root
hikari:
auto-commit: true
- connection-test-query: SELECT 1
+ connection-test-query: SELECT 1 FROM DUAL
connection-timeout: 10000
idle-timeout: 30000
max-lifetime: 900000
diff --git a/ruoyi-extend/ruoyi-xxl-job-admin/src/main/resources/application-local.yml b/ruoyi-extend/ruoyi-xxl-job-admin/src/main/resources/application-local.yml
new file mode 100644
index 000000000..192d9226a
--- /dev/null
+++ b/ruoyi-extend/ruoyi-xxl-job-admin/src/main/resources/application-local.yml
@@ -0,0 +1,36 @@
+--- # 数据库配置
+spring:
+ datasource:
+ type: com.zaxxer.hikari.HikariDataSource
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://127.0.0.1:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true
+ username: root
+ password: root
+ hikari:
+ auto-commit: true
+ connection-test-query: SELECT 1 FROM DUAL
+ connection-timeout: 10000
+ idle-timeout: 30000
+ max-lifetime: 900000
+ maximum-pool-size: 30
+ minimum-idle: 10
+ pool-name: HikariCP
+ validation-timeout: 1000
+
+--- # 邮件配置
+spring:
+ mail:
+ from: xxx@qq.com
+ host: smtp.qq.com
+ username: xxx@qq.com
+ password: xxx
+ port: 25
+ properties:
+ mail:
+ smtp:
+ auth: true
+ socketFactory:
+ class: javax.net.ssl.SSLSocketFactory
+ starttls:
+ enable: true
+ required: true
diff --git a/ruoyi-extend/ruoyi-xxl-job-admin/src/main/resources/application-prod.yml b/ruoyi-extend/ruoyi-xxl-job-admin/src/main/resources/application-prod.yml
index 774d7f81c..2c221267f 100644
--- a/ruoyi-extend/ruoyi-xxl-job-admin/src/main/resources/application-prod.yml
+++ b/ruoyi-extend/ruoyi-xxl-job-admin/src/main/resources/application-prod.yml
@@ -3,12 +3,12 @@ spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
- url: jdbc:mysql://172.30.0.92:3306/ry-vue?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
+ url: jdbc:mysql://172.17.0.1:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&autoReconnect=true
username: root
password: root
hikari:
auto-commit: true
- connection-test-query: SELECT 1
+ connection-test-query: SELECT 1 FROM DUAL
connection-timeout: 10000
idle-timeout: 30000
max-lifetime: 900000
diff --git a/ruoyi-generator/src/main/resources/vm/java/service.java.vm b/ruoyi-generator/src/main/resources/vm/java/service.java.vm
index 0041c398c..1cba5626b 100644
--- a/ruoyi-generator/src/main/resources/vm/java/service.java.vm
+++ b/ruoyi-generator/src/main/resources/vm/java/service.java.vm
@@ -18,43 +18,43 @@ import java.util.List;
* @date ${datetime}
*/
public interface I${ClassName}Service extends IServicePlus<${ClassName}, ${ClassName}Vo> {
- /**
- * 查询单个
- * @return
- */
- ${ClassName}Vo queryById(${pkColumn.javaType} ${pkColumn.javaField});
+ /**
+ * 查询单个
+ * @return
+ */
+ ${ClassName}Vo queryById(${pkColumn.javaType} ${pkColumn.javaField});
#if($table.crud || $table.sub)
- /**
- * 查询列表
- */
+ /**
+ * 查询列表
+ */
TableDataInfo<${ClassName}Vo> queryPageList(${ClassName}Bo bo);
#end
- /**
- * 查询列表
- */
- List<${ClassName}Vo> queryList(${ClassName}Bo bo);
+ /**
+ * 查询列表
+ */
+ List<${ClassName}Vo> queryList(${ClassName}Bo bo);
- /**
- * 根据新增业务对象插入${functionName}
- * @param bo ${functionName}新增业务对象
- * @return
- */
- Boolean insertByBo(${ClassName}Bo bo);
+ /**
+ * 根据新增业务对象插入${functionName}
+ * @param bo ${functionName}新增业务对象
+ * @return
+ */
+ Boolean insertByBo(${ClassName}Bo bo);
- /**
- * 根据编辑业务对象修改${functionName}
- * @param bo ${functionName}编辑业务对象
- * @return
- */
- Boolean updateByBo(${ClassName}Bo bo);
+ /**
+ * 根据编辑业务对象修改${functionName}
+ * @param bo ${functionName}编辑业务对象
+ * @return
+ */
+ Boolean updateByBo(${ClassName}Bo bo);
- /**
- * 校验并删除数据
- * @param ids 主键集合
- * @param isValid 是否校验,true-删除前校验,false-不校验
- * @return
- */
- Boolean deleteWithValidByIds(Collection<${pkColumn.javaType}> ids, Boolean isValid);
+ /**
+ * 校验并删除数据
+ * @param ids 主键集合
+ * @param isValid 是否校验,true-删除前校验,false-不校验
+ * @return
+ */
+ Boolean deleteWithValidByIds(Collection<${pkColumn.javaType}> ids, Boolean isValid);
}
diff --git a/ruoyi-isc-parent/pom.xml b/ruoyi-isc-parent/pom.xml
new file mode 100644
index 000000000..1b0fcf665
--- /dev/null
+++ b/ruoyi-isc-parent/pom.xml
@@ -0,0 +1,33 @@
+
+
+
+ ruoyi-vue-plus
+ com.ruoyi
+ 3.3.0
+
+ 4.0.0
+
+ ruoyi-isc-parent
+ pom
+
+ ruoyi-isc-common
+ ruoyi-isc
+
+
+
+
+
+ com.ruoyi
+ ruoyi-isc-common
+ ${project.version}
+
+
+ com.ruoyi
+ ruoyi-isc
+ ${project.version}
+
+
+
+
\ No newline at end of file
diff --git a/ruoyi-isc-parent/ruoyi-isc-common/pom.xml b/ruoyi-isc-parent/ruoyi-isc-common/pom.xml
new file mode 100644
index 000000000..6e911eae8
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc-common/pom.xml
@@ -0,0 +1,30 @@
+
+
+
+ ruoyi-isc-parent
+ com.ruoyi
+ 3.3.0
+
+ 4.0.0
+
+ ruoyi-isc-common
+
+
+
+ org.projectlombok
+ lombok
+ compile
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
+ org.redisson
+ redisson
+
+
+
+
\ No newline at end of file
diff --git a/ruoyi-isc-parent/ruoyi-isc-common/src/main/java/com/ruoyi/isc/common/constant/IscGatewayContants.java b/ruoyi-isc-parent/ruoyi-isc-common/src/main/java/com/ruoyi/isc/common/constant/IscGatewayContants.java
new file mode 100644
index 000000000..c11959f5b
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc-common/src/main/java/com/ruoyi/isc/common/constant/IscGatewayContants.java
@@ -0,0 +1,31 @@
+package com.ruoyi.isc.common.constant;
+
+import com.ruoyi.isc.common.utils.beans.IscRule;
+import org.redisson.client.codec.Codec;
+import org.redisson.codec.TypedJsonJacksonCodec;
+
+/**
+ * Isc Gateway 公共常量
+ * @author Wenchao Gong
+ * @date 2021-10-30
+ */
+public interface IscGatewayContants {
+
+ /** AccessKey 默认名称 */
+ String ACCESS_KEY_NAME_DEFAULT = "ak";
+
+ /** 路由刷新通知主题 */
+ String TOPIC_GATEWAY_REFRESH_ROUTE = "TOPIC_GATEWAY_REFRESH_ROUTE";
+
+ /** 规则刷新通知主题 */
+ String TOPIC_GATEWAY_RULE = "TOPIC_GATEWAY_RULE";
+
+ /** AccessKeyName 路由配置名称 */
+ String CONFIG_ACCESS_KEY_NAME_KEY = "accessKeyName";
+
+ /** 添加参数 路由配置名称 */
+ String CONFIG_ADD_PARAM_KEY = "addParam";
+
+ /** 规则编解码器 */
+ Codec RULE_CODES_INSTANCE = new TypedJsonJacksonCodec(String.class, IscRule.class);
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc-common/src/main/java/com/ruoyi/isc/common/constant/IscRedisKeys.java b/ruoyi-isc-parent/ruoyi-isc-common/src/main/java/com/ruoyi/isc/common/constant/IscRedisKeys.java
new file mode 100644
index 000000000..af8e847c5
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc-common/src/main/java/com/ruoyi/isc/common/constant/IscRedisKeys.java
@@ -0,0 +1,19 @@
+package com.ruoyi.isc.common.constant;
+
+/**
+ * Redis Keys
+ * @author Wenchao Gong
+ * @date 2021-10-30
+ */
+public interface IscRedisKeys {
+
+ /**
+ * Gateway 路由信息Redis Key
+ */
+ String KEY_ROUTES = "ROUTES:";
+
+ /**
+ * Gateway 服务对应AK规则
+ */
+ String KEY_RULES = "RULES:";
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc-common/src/main/java/com/ruoyi/isc/common/utils/GatewayUtils.java b/ruoyi-isc-parent/ruoyi-isc-common/src/main/java/com/ruoyi/isc/common/utils/GatewayUtils.java
new file mode 100644
index 000000000..620e4a8da
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc-common/src/main/java/com/ruoyi/isc/common/utils/GatewayUtils.java
@@ -0,0 +1,30 @@
+package com.ruoyi.isc.common.utils;
+
+/**
+ * @author Wenchao Gong
+ * @date 2021-10-30
+ */
+public class GatewayUtils {
+
+ /**
+ * 获取 路由Key
+ *
+ * @param ak AccessKey
+ * @param serviceId 服务ID
+ * @return 路由Key
+ */
+ public static String getRouteKey(String ak, String serviceId) {
+ return ak + ':' + serviceId;
+ }
+
+ /**
+ * 获取 路由Key
+ *
+ * @param ak AccessKey
+ * @param serviceId 服务ID
+ * @return 路由Key
+ */
+ public static String getRouteKey(String ak, Long serviceId) {
+ return getRouteKey(ak, String.valueOf(serviceId));
+ }
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc-common/src/main/java/com/ruoyi/isc/common/utils/beans/IscRule.java b/ruoyi-isc-parent/ruoyi-isc-common/src/main/java/com/ruoyi/isc/common/utils/beans/IscRule.java
new file mode 100644
index 000000000..a637fa164
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc-common/src/main/java/com/ruoyi/isc/common/utils/beans/IscRule.java
@@ -0,0 +1,26 @@
+package com.ruoyi.isc.common.utils.beans;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 网关 规则信息(服务对应AK处理规则)
+ * @author Wechao Gong
+ * @date 2021-10-16
+ */
+@Getter
+@Setter
+public class IscRule implements Serializable
+{
+ @JsonIgnore
+ private String id;
+ private Date expire;
+ private Long daysLimit;
+ private Long hoursLimit;
+ private Long minutesLimit;
+ private Long secondsLimit;
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc-common/src/main/java/com/ruoyi/isc/common/utils/beans/TopicMsg.java b/ruoyi-isc-parent/ruoyi-isc-common/src/main/java/com/ruoyi/isc/common/utils/beans/TopicMsg.java
new file mode 100644
index 000000000..91d64e5ee
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc-common/src/main/java/com/ruoyi/isc/common/utils/beans/TopicMsg.java
@@ -0,0 +1,37 @@
+package com.ruoyi.isc.common.utils.beans;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * @author Wenchao Gong
+ * @date 2021-10-26
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class TopicMsg implements Serializable {
+ /** 消息ID */
+ private String id;
+
+ /** 消息内容 */
+ private String msg;
+
+ /** 消息类型 */
+ private Type type;
+
+ /** 消息类型枚举 */
+ public static enum Type {
+ /** 新增单条记录 */
+ ADD,
+ /** 更新单条记录 */
+ UPDATE,
+ /** 删除单条记录 */
+ DELETE,
+ /** 刷新所有 */
+ REFRESH
+ }
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/pom.xml b/ruoyi-isc-parent/ruoyi-isc/pom.xml
new file mode 100644
index 000000000..25ee9f7be
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/pom.xml
@@ -0,0 +1,25 @@
+
+
+
+ ruoyi-isc-parent
+ com.ruoyi
+ 3.3.0
+
+ 4.0.0
+
+ ruoyi-isc
+
+
+
+ com.ruoyi
+ ruoyi-isc-common
+
+
+ com.ruoyi
+ ruoyi-system
+
+
+
+
\ No newline at end of file
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/controller/IscAppServiceApplyController.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/controller/IscAppServiceApplyController.java
new file mode 100644
index 000000000..7da1fdbbb
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/controller/IscAppServiceApplyController.java
@@ -0,0 +1,87 @@
+package com.ruoyi.isc.controller;
+
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.annotation.RepeatSubmit;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.core.validate.EditGroup;
+import com.ruoyi.common.core.validate.QueryGroup;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.isc.domain.bo.IscAppServiceApplyBo;
+import com.ruoyi.isc.domain.bo.IscAuditBo;
+import com.ruoyi.isc.domain.vo.IscAppServiceApplyVo;
+import com.ruoyi.isc.service.IIscAppServiceApplyService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * 应用服务申请信息Controller
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-09
+ */
+@Validated
+@Api(value = "应用服务申请信息控制器", tags = {"应用服务申请信息管理"})
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+@RestController
+@RequestMapping("/isc/serviceapply")
+public class IscAppServiceApplyController extends BaseController {
+
+ private final IIscAppServiceApplyService iIscAppServiceApplyService;
+
+ /**
+ * 查询应用服务申请信息列表
+ */
+ @ApiOperation("查询应用服务申请信息列表")
+ @PreAuthorize("@ss.hasPermi('isc:serviceapply:list')")
+ @GetMapping("/list")
+ public TableDataInfo list(@Validated(QueryGroup.class) IscAppServiceApplyBo bo) {
+ return iIscAppServiceApplyService.queryPageList(bo);
+ }
+
+ /**
+ * 导出应用服务申请信息列表
+ */
+ @ApiOperation("导出应用服务申请信息列表")
+ @PreAuthorize("@ss.hasPermi('isc:serviceapply:export')")
+ @Log(title = "应用服务申请信息", businessType = BusinessType.EXPORT)
+ @GetMapping("/export")
+ public void export(@Validated IscAppServiceApplyBo bo, HttpServletResponse response) {
+ List list = iIscAppServiceApplyService.queryList(bo);
+ ExcelUtil.exportExcel(list, "应用服务申请信息", IscAppServiceApplyVo.class, response);
+ }
+
+ /**
+ * 获取应用服务申请信息详细信息
+ */
+ @ApiOperation("获取应用服务申请信息详细信息")
+ @PreAuthorize("@ss.hasPermi('isc:serviceapply:query')")
+ @GetMapping("/{applyId}")
+ public AjaxResult getInfo(@NotNull(message = "主键不能为空")
+ @PathVariable("applyId") Long applyId) {
+ return AjaxResult.success(iIscAppServiceApplyService.queryById(applyId));
+ }
+
+ /**
+ * 应用服务申请信息审核
+ */
+ @ApiOperation("应用服务申请信息审核")
+ @PreAuthorize("@ss.hasPermi('isc:serviceapply:audit')")
+ @Log(title = "应用服务申请信息审核", businessType = BusinessType.AUDIT)
+ @RepeatSubmit()
+ @PutMapping("/audit")
+ public AjaxResult audit(@Validated @RequestBody IscAuditBo bo) {
+ return toAjax(iIscAppServiceApplyService.audit(bo) ? 1 : 0);
+ }
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/controller/IscAppServiceController.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/controller/IscAppServiceController.java
new file mode 100644
index 000000000..833e750ab
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/controller/IscAppServiceController.java
@@ -0,0 +1,124 @@
+package com.ruoyi.isc.controller;
+
+import cn.hutool.core.lang.tree.Tree;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.annotation.RepeatSubmit;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.core.validate.AddGroup;
+import com.ruoyi.common.core.validate.EditGroup;
+import com.ruoyi.common.core.validate.QueryGroup;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.isc.domain.bo.IscAppServiceBo;
+import com.ruoyi.isc.domain.vo.IscAppServiceVo;
+import com.ruoyi.isc.service.IIscAppServiceService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 应用服务Controller
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-08
+ */
+@Validated
+@Api(value = "应用服务控制器", tags = {"应用服务管理"})
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+@RestController
+@RequestMapping("/isc/appservice")
+public class IscAppServiceController extends BaseController {
+
+ private final IIscAppServiceService appServiceService;
+
+ /**
+ * 查询应用服务列表
+ */
+ @ApiOperation("查询应用服务列表")
+ @PreAuthorize("@ss.hasPermi('isc:appservice:list')")
+ @GetMapping("/list")
+ public TableDataInfo list(@Validated(QueryGroup.class) IscAppServiceBo bo) {
+ return appServiceService.queryPageList(bo);
+ }
+
+ /**
+ * 导出应用服务列表
+ */
+ @ApiOperation("导出应用服务列表")
+ @PreAuthorize("@ss.hasPermi('isc:appservice:export')")
+ @Log(title = "应用服务", businessType = BusinessType.EXPORT)
+ @GetMapping("/export")
+ public void export(@Validated IscAppServiceBo bo, HttpServletResponse response) {
+ List list = appServiceService.queryList(bo);
+ ExcelUtil.exportExcel(list, "应用服务", IscAppServiceVo.class, response);
+ }
+
+ /**
+ * 获取应用服务详细信息
+ */
+ @ApiOperation("获取应用服务详细信息")
+ @PreAuthorize("@ss.hasPermi('isc:appservice:query')")
+ @GetMapping("/{appServiceId}")
+ public AjaxResult getInfo(@NotNull(message = "主键不能为空")
+ @PathVariable("appServiceId") Long appServiceId) {
+ return AjaxResult.success(appServiceService.queryById(appServiceId));
+ }
+
+ /**
+ * 新增应用服务
+ */
+ @ApiOperation("新增应用服务")
+ @PreAuthorize("@ss.hasPermi('isc:appservice:add')")
+ @Log(title = "应用服务", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping()
+ public AjaxResult add(@Validated(AddGroup.class) @RequestBody IscAppServiceBo bo) {
+ return toAjax(appServiceService.insertByBo(bo) ? 1 : 0);
+ }
+
+ /**
+ * 修改应用服务
+ */
+ @ApiOperation("修改应用服务")
+ @PreAuthorize("@ss.hasPermi('isc:appservice:edit')")
+ @Log(title = "应用服务", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping()
+ public AjaxResult edit(@Validated(EditGroup.class) @RequestBody IscAppServiceBo bo) {
+ return toAjax(appServiceService.updateByBo(bo) ? 1 : 0);
+ }
+
+ /**
+ * 删除应用服务
+ */
+ @ApiOperation("删除应用服务")
+ @PreAuthorize("@ss.hasPermi('isc:appservice:remove')")
+ @Log(title = "应用服务" , businessType = BusinessType.DELETE)
+ @DeleteMapping("/{appServiceIds}")
+ public AjaxResult remove(@NotEmpty(message = "主键不能为空")
+ @PathVariable Long[] appServiceIds) {
+ return toAjax(appServiceService.deleteWithValidByIds(Arrays.asList(appServiceIds), true) ? 1 : 0);
+ }
+
+ /**
+ * 获取应用服务下拉树列表
+ */
+ @GetMapping("/treeselect/{applicationId}")
+ @ApiOperation("获取应用服务下拉树列表")
+ public AjaxResult>> treeselect(@NotNull(message = "应用主键不能为空") @PathVariable Long applicationId)
+ {
+ return AjaxResult.success(appServiceService.genAppServiceTree(applicationId));
+ }
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/controller/IscApplicationController.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/controller/IscApplicationController.java
new file mode 100644
index 000000000..e743203c0
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/controller/IscApplicationController.java
@@ -0,0 +1,113 @@
+package com.ruoyi.isc.controller;
+
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.annotation.RepeatSubmit;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.core.validate.AddGroup;
+import com.ruoyi.common.core.validate.EditGroup;
+import com.ruoyi.common.core.validate.QueryGroup;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.isc.domain.bo.IscApplicationBo;
+import com.ruoyi.isc.domain.vo.IscApplicationVo;
+import com.ruoyi.isc.service.IIscApplicationService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 应用信息Controller
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-08
+ */
+@Validated
+@Api(value = "应用信息控制器", tags = {"应用信息管理"})
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+@RestController
+@RequestMapping("/isc/application")
+public class IscApplicationController extends BaseController {
+
+ private final IIscApplicationService iIscApplicationService;
+
+ /**
+ * 查询应用信息列表
+ */
+ @ApiOperation("查询应用信息列表")
+ @PreAuthorize("@ss.hasPermi('isc:application:list')")
+ @GetMapping("/list")
+ public TableDataInfo list(@Validated(QueryGroup.class) IscApplicationBo bo) {
+ return iIscApplicationService.queryPageList(bo);
+ }
+
+ /**
+ * 导出应用信息列表
+ */
+ @ApiOperation("导出应用信息列表")
+ @PreAuthorize("@ss.hasPermi('isc:application:export')")
+ @Log(title = "应用信息", businessType = BusinessType.EXPORT)
+ @GetMapping("/export")
+ public void export(@Validated IscApplicationBo bo, HttpServletResponse response) {
+ List list = iIscApplicationService.queryList(bo);
+ ExcelUtil.exportExcel(list, "应用信息", IscApplicationVo.class, response);
+ }
+
+ /**
+ * 获取应用信息详细信息
+ */
+ @ApiOperation("获取应用信息详细信息")
+ @PreAuthorize("@ss.hasPermi('isc:application:query')")
+ @GetMapping("/{applicationId}")
+ public AjaxResult getInfo(@NotNull(message = "主键不能为空")
+ @PathVariable("applicationId") Long applicationId) {
+ return AjaxResult.success(iIscApplicationService.queryById(applicationId));
+ }
+
+ /**
+ * 新增应用信息
+ */
+ @ApiOperation("新增应用信息")
+ @PreAuthorize("@ss.hasPermi('isc:application:add')")
+ @Log(title = "应用信息", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping()
+ public AjaxResult add(@Validated(AddGroup.class) @RequestBody IscApplicationBo bo) {
+ return toAjax(iIscApplicationService.insertByBo(bo) ? 1 : 0);
+ }
+
+ /**
+ * 修改应用信息
+ */
+ @ApiOperation("修改应用信息")
+ @PreAuthorize("@ss.hasPermi('isc:application:edit')")
+ @Log(title = "应用信息", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping()
+ public AjaxResult edit(@Validated(EditGroup.class) @RequestBody IscApplicationBo bo) {
+ return toAjax(iIscApplicationService.updateByBo(bo) ? 1 : 0);
+ }
+
+ /**
+ * 删除应用信息
+ */
+ @ApiOperation("删除应用信息")
+ @PreAuthorize("@ss.hasPermi('isc:application:remove')")
+ @Log(title = "应用信息" , businessType = BusinessType.DELETE)
+ @DeleteMapping("/{applicationIds}")
+ public AjaxResult remove(@NotEmpty(message = "主键不能为空")
+ @PathVariable Long[] applicationIds) {
+ return toAjax(iIscApplicationService.deleteWithValidByIds(Arrays.asList(applicationIds), true) ? 1 : 0);
+ }
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/controller/IscServiceCateController.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/controller/IscServiceCateController.java
new file mode 100644
index 000000000..15b57cd13
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/controller/IscServiceCateController.java
@@ -0,0 +1,135 @@
+package com.ruoyi.isc.controller;
+
+import java.util.List;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.core.lang.tree.TreeUtil;
+import cn.hutool.core.lang.tree.parser.NodeParser;
+import com.ruoyi.common.core.domain.entity.SysMenu;
+import com.ruoyi.common.utils.TreeUtils;
+import com.ruoyi.isc.domain.IscServiceCate;
+import lombok.RequiredArgsConstructor;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.constraints.*;
+
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import com.ruoyi.common.annotation.RepeatSubmit;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.validate.AddGroup;
+import com.ruoyi.common.core.validate.EditGroup;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.isc.domain.vo.IscServiceCateVo;
+import com.ruoyi.isc.domain.bo.IscServiceCateBo;
+import com.ruoyi.isc.service.IIscServiceCateService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * 服务分类Controller
+ *
+ * @author Wenchao Gong
+ * @date 2021-08-22
+ */
+@Validated
+@Api(value = "服务分类控制器", tags = {"服务分类管理"})
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+@RestController
+@RequestMapping("/isc/cate")
+public class IscServiceCateController extends BaseController {
+
+ private final IIscServiceCateService cateService;
+
+ /**
+ * 查询服务分类列表
+ */
+ @ApiOperation("查询服务分类列表")
+ @PreAuthorize("@ss.hasPermi('isc:cate:list')")
+ @GetMapping("/list")
+ public AjaxResult> list(@Validated IscServiceCateBo bo)
+ {
+ List list = cateService.queryList(bo);
+ return AjaxResult.success(list);
+ }
+
+ /**
+ * 获取分类下拉树列表
+ */
+ @GetMapping("/treeselect")
+ @ApiOperation("获取分类下拉树列表")
+ public AjaxResult>> treeselect()
+ {
+ List cates = cateService.selectCateList();
+ return AjaxResult.success(cateService.genCateTree(cates));
+ }
+
+ /**
+ * 导出服务分类列表
+ */
+ @ApiOperation("导出服务分类列表")
+ @PreAuthorize("@ss.hasPermi('isc:cate:export')")
+ @Log(title = "服务分类", businessType = BusinessType.EXPORT)
+ @GetMapping("/export")
+ public void export(@Validated IscServiceCateBo bo, HttpServletResponse response)
+ {
+ List list = cateService.queryList(bo);
+ ExcelUtil.exportExcel(list, "服务分类", IscServiceCateVo.class, response);
+ }
+
+ /**
+ * 获取服务分类详细信息
+ */
+ @ApiOperation("获取服务分类详细信息")
+ @PreAuthorize("@ss.hasPermi('isc:cate:query')")
+ @GetMapping("/{cateId}")
+ public AjaxResult getInfo(@NotNull(message = "主键不能为空") @PathVariable("cateId") Long cateId)
+ {
+ return AjaxResult.success(cateService.queryById(cateId));
+ }
+
+ /**
+ * 新增服务分类
+ */
+ @ApiOperation("新增服务分类")
+ @PreAuthorize("@ss.hasPermi('isc:cate:add')")
+ @Log(title = "服务分类", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping()
+ public AjaxResult add(@Validated(AddGroup.class) @RequestBody IscServiceCateBo bo)
+ {
+ return toAjax(cateService.insertByBo(bo) ? 1 : 0);
+ }
+
+ /**
+ * 修改服务分类
+ */
+ @ApiOperation("修改服务分类")
+ @PreAuthorize("@ss.hasPermi('isc:cate:edit')")
+ @Log(title = "服务分类", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping()
+ public AjaxResult edit(@Validated(EditGroup.class) @RequestBody IscServiceCateBo bo)
+ {
+ return toAjax(cateService.updateByBo(bo) ? 1 : 0);
+ }
+
+ /**
+ * 删除服务分类
+ */
+ @ApiOperation("删除服务分类")
+ @PreAuthorize("@ss.hasPermi('isc:cate:remove')")
+ @Log(title = "服务分类", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{cateIds}")
+ public AjaxResult remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] cateIds)
+ {
+ return toAjax(cateService.deleteWithValidByIds(Arrays.asList(cateIds), true) ? 1 : 0);
+ }
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/controller/IscServiceController.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/controller/IscServiceController.java
new file mode 100644
index 000000000..8faea9e73
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/controller/IscServiceController.java
@@ -0,0 +1,125 @@
+package com.ruoyi.isc.controller;
+
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.annotation.RepeatSubmit;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.core.validate.AddGroup;
+import com.ruoyi.common.core.validate.EditGroup;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.isc.domain.bo.IscAuditBo;
+import com.ruoyi.isc.domain.bo.IscServiceBo;
+import com.ruoyi.isc.domain.vo.IscServiceVo;
+import com.ruoyi.isc.service.IIscServiceService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 服务信息Controller
+ *
+ * @author Wenchao Gong
+ * @date 2021-08-22
+ */
+@Validated
+@Api(value = "服务信息控制器", tags = {"服务信息管理"})
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+@RestController
+@RequestMapping("/isc/service")
+public class IscServiceController extends BaseController {
+
+ private final IIscServiceService iIscServiceService;
+
+ /**
+ * 查询服务信息列表
+ */
+ @ApiOperation("查询服务信息列表")
+ @PreAuthorize("@ss.hasPermi('isc:service:list')")
+ @GetMapping("/list")
+ public TableDataInfo list(@Validated IscServiceBo bo) {
+ return iIscServiceService.queryPageList(bo);
+ }
+
+ /**
+ * 导出服务信息列表
+ */
+ @ApiOperation("导出服务信息列表")
+ @PreAuthorize("@ss.hasPermi('isc:service:export')")
+ @Log(title = "服务信息", businessType = BusinessType.EXPORT)
+ @GetMapping("/export")
+ public void export(@Validated IscServiceBo bo, HttpServletResponse response) {
+ List list = iIscServiceService.queryList(bo);
+ ExcelUtil.exportExcel(list, "服务信息", IscServiceVo.class, response);
+ }
+
+ /**
+ * 获取服务信息详细信息
+ */
+ @ApiOperation("获取服务信息详细信息")
+ @PreAuthorize("@ss.hasPermi('isc:service:query')")
+ @GetMapping("/{serviceId}")
+ public AjaxResult getInfo(@NotNull(message = "主键不能为空")
+ @PathVariable("serviceId") Long serviceId) {
+ return AjaxResult.success(iIscServiceService.queryById(serviceId));
+ }
+
+ /**
+ * 新增服务信息
+ */
+ @ApiOperation("新增服务信息")
+ @PreAuthorize("@ss.hasPermi('isc:service:add')")
+ @Log(title = "服务信息", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping()
+ public AjaxResult add(@Validated(AddGroup.class) @RequestBody IscServiceBo bo) {
+ return toAjax(iIscServiceService.insertByBo(bo) ? 1 : 0);
+ }
+
+ /**
+ * 修改服务信息
+ */
+ @ApiOperation("修改服务信息")
+ @PreAuthorize("@ss.hasPermi('isc:service:edit')")
+ @Log(title = "服务信息", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping()
+ public AjaxResult edit(@Validated(EditGroup.class) @RequestBody IscServiceBo bo) {
+ return toAjax(iIscServiceService.updateByBo(bo) ? 1 : 0);
+ }
+
+ /**
+ * 删除服务信息
+ */
+ @ApiOperation("删除服务信息")
+ @PreAuthorize("@ss.hasPermi('isc:service:remove')")
+ @Log(title = "服务信息" , businessType = BusinessType.DELETE)
+ @DeleteMapping("/{serviceIds}")
+ public AjaxResult remove(@NotEmpty(message = "主键不能为空")
+ @PathVariable Long[] serviceIds) {
+ return toAjax(iIscServiceService.deleteWithValidByIds(Arrays.asList(serviceIds), true) ? 1 : 0);
+ }
+
+ /**
+ * 服务信息审核
+ */
+ @ApiOperation("服务信息审核")
+ @PreAuthorize("@ss.hasPermi('isc:service:audit')")
+ @Log(title = "服务信息审核", businessType = BusinessType.AUDIT)
+ @RepeatSubmit()
+ @PutMapping("/audit")
+ public AjaxResult audit(@Validated @RequestBody IscAuditBo bo) {
+ return toAjax(iIscServiceService.audit(bo) ? 1 : 0);
+ }
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/IscAppService.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/IscAppService.java
new file mode 100644
index 000000000..fd1d2d1f3
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/IscAppService.java
@@ -0,0 +1,127 @@
+package com.ruoyi.isc.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 应用服务对象 isc_app_service
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-08
+ */
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+@TableName("isc_app_service")
+public class IscAppService implements Serializable {
+
+ private static final long serialVersionUID=1L;
+
+
+ /**
+ * 应用服务ID
+ */
+ @TableId(value = "app_service_id")
+ private Long appServiceId;
+
+ /**
+ * 服务ID
+ */
+ private Long serviceId;
+
+ /**
+ * 应用ID
+ */
+ private Long applicationId;
+
+ /**
+ * 用户ID
+ */
+ private Long userId;
+
+ /**
+ * 启用状态(0启用 1停用)
+ */
+ private String enabled;
+
+ /**
+ * 审核状态(0待审核 1审核通过 2驳回)
+ */
+ private String status;
+
+ /**
+ * 审核意见
+ */
+ private String auditMind;
+
+ /**
+ * 虚拟地址
+ */
+ private String virtualAddr;
+
+ /**
+ * 到期时间
+ */
+ private Date endTime;
+
+ /**
+ * 天配额
+ */
+ private Long quotaDays;
+
+ /**
+ * 小时配额
+ */
+ private Long quotaHours;
+
+ /**
+ * 分钟配额
+ */
+ private Long quotaMinutes;
+
+ /**
+ * 秒配额
+ */
+ private Long quotaSeconds;
+
+ /**
+ * 创建者
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private String createBy;
+
+ /**
+ * 创建时间
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private Date createTime;
+
+ /**
+ * 更新者
+ */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private String updateBy;
+
+ /**
+ * 更新时间
+ */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private Date updateTime;
+
+ /**
+ * 删除标志(0代表存在 2代表删除)
+ */
+ @TableLogic
+ private String delFlag;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/IscAppServiceApply.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/IscAppServiceApply.java
new file mode 100644
index 000000000..1f8cc6195
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/IscAppServiceApply.java
@@ -0,0 +1,112 @@
+package com.ruoyi.isc.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 应用服务申请信息对象 isc_app_service_apply
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-09
+ */
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+@TableName("isc_app_service_apply")
+public class IscAppServiceApply implements Serializable {
+
+ private static final long serialVersionUID=1L;
+
+
+ /**
+ * 申请ID
+ */
+ @TableId(value = "apply_id")
+ private Long applyId;
+
+ /**
+ * 应用服务ID
+ */
+ private Long appServiceId;
+
+ /**
+ * 申请类型(0申请 1续期)
+ */
+ private String applyType;
+
+ /**
+ * 审核状态(0待审核 1审核通过 2驳回)
+ */
+ private String status;
+
+ /**
+ * 审核意见
+ */
+ private String auditMind;
+
+ /**
+ * 续期时长(单位月)
+ */
+ private Integer renewalDuration;
+
+ /**
+ * 天配额
+ */
+ private Long quotaDays;
+
+ /**
+ * 小时配额
+ */
+ private Long quotaHours;
+
+ /**
+ * 分钟配额
+ */
+ private Long quotaMinutes;
+
+ /**
+ * 秒配额
+ */
+ private Long quotaSeconds;
+
+ /**
+ * 创建者
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private String createBy;
+
+ /**
+ * 创建时间
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private Date createTime;
+
+ /**
+ * 更新者
+ */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private String updateBy;
+
+ /**
+ * 更新时间
+ */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private Date updateTime;
+
+ /**
+ * 删除标志(0代表存在 2代表删除)
+ */
+ @TableLogic
+ private String delFlag;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/IscApplication.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/IscApplication.java
new file mode 100644
index 000000000..fb57b868a
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/IscApplication.java
@@ -0,0 +1,82 @@
+package com.ruoyi.isc.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import java.io.Serializable;
+import java.util.Date;
+import java.math.BigDecimal;
+
+/**
+ * 应用信息对象 isc_application
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-08
+ */
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+@TableName("isc_application")
+public class IscApplication implements Serializable {
+
+ private static final long serialVersionUID=1L;
+
+
+ /**
+ * 应用ID
+ */
+ @TableId(value = "application_id")
+ private Long applicationId;
+
+ /**
+ * 应用名称
+ */
+ private String applicationName;
+
+ /**
+ * 应用密钥
+ */
+ private String accessKey;
+
+ /**
+ * 用户ID
+ */
+ private Long userId;
+
+ /**
+ * 创建者
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private String createBy;
+
+ /**
+ * 创建时间
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private Date createTime;
+
+ /**
+ * 更新者
+ */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private String updateBy;
+
+ /**
+ * 更新时间
+ */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private Date updateTime;
+
+ /**
+ * 删除标志(0代表存在 2代表删除)
+ */
+ @TableLogic
+ private String delFlag;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/IscService.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/IscService.java
new file mode 100644
index 000000000..e6ae66828
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/IscService.java
@@ -0,0 +1,132 @@
+package com.ruoyi.isc.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import java.io.Serializable;
+import java.util.Date;
+import java.math.BigDecimal;
+
+/**
+ * 服务信息对象 isc_service
+ *
+ * @author Wenchao Gong
+ * @date 2021-08-22
+ */
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+@TableName("isc_service")
+public class IscService implements Serializable {
+
+ private static final long serialVersionUID=1L;
+
+
+ /**
+ * 服务ID
+ */
+ @TableId(value = "service_id")
+ private Long serviceId;
+
+ /**
+ * 服务名称
+ */
+ private String serviceName;
+
+ /**
+ * 服务地址
+ */
+ private String serviceAddr;
+
+ /**
+ * 探活地址
+ */
+ private String probeActiveAddr;
+
+ /**
+ * 请求方式(默认GET)
+ */
+ private String requestMethod;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+ /**
+ * 跨域标志(Y是 N否)
+ */
+ private String corsFlag;
+
+ /**
+ * 隐藏参数
+ */
+ private String hiddenParams;
+
+ /**
+ * 是否在线(0离线 1在线)
+ */
+ private String onlineStatus;
+
+ /**
+ * 审核状态(0待审核 1审核通过 2驳回)
+ */
+ private String status;
+
+ /**
+ * JSON文档
+ */
+ private String apiDoc;
+
+ /**
+ * 审核意见
+ */
+ private String auditMind;
+
+ /**
+ * 服务状态(0启用 1停用)
+ */
+ private String enabled;
+
+ /**
+ * 服务分类全路径
+ */
+ private String cateFullPath;
+
+ /**
+ * 用户ID
+ */
+ private Long userId;
+
+ /**
+ * 创建者
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private String createBy;
+
+ /**
+ * 创建时间
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private Date createTime;
+
+ /**
+ * 更新者
+ */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private String updateBy;
+
+ /**
+ * 更新时间
+ */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private Date updateTime;
+
+ /**
+ * 删除标志(0代表存在 2代表删除)
+ */
+ @TableLogic
+ private String delFlag;
+
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/IscServiceCate.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/IscServiceCate.java
new file mode 100644
index 000000000..195c1d90d
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/IscServiceCate.java
@@ -0,0 +1,101 @@
+package com.ruoyi.isc.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.ruoyi.common.core.domain.entity.SysMenu;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 服务分类对象 isc_service_cate
+ *
+ * @author Wenchao Gong
+ * @date 2021-08-22
+ */
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+@TableName("isc_service_cate")
+public class IscServiceCate implements Serializable {
+
+ private static final long serialVersionUID=1L;
+
+
+ /**
+ * 分类ID
+ */
+ @TableId(value = "cate_id")
+ private Long cateId;
+
+ /**
+ * 父分类ID
+ */
+ private Long parentId;
+
+ /**
+ * 分类名称
+ */
+ private String cateName;
+
+ /**
+ * 搜索全路径
+ */
+ private String fullPath;
+
+ /**
+ * 启用状态(0启用 1停用)
+ */
+ private String enabled;
+
+ /**
+ * 创建者
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private String createBy;
+
+ /**
+ * 显示顺序
+ */
+ private Long orderNum;
+
+ /**
+ * 创建时间
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private Date createTime;
+
+ /**
+ * 更新者
+ */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private String updateBy;
+
+ /**
+ * 更新时间
+ */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private Date updateTime;
+
+ /**
+ * 删除标志(0代表存在 2代表删除)
+ */
+ @TableLogic
+ private String delFlag;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+ /**
+ * 子分类
+ */
+ @TableField(exist = false)
+ private List children = new ArrayList();
+
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/bo/IscAppServiceApplyBo.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/bo/IscAppServiceApplyBo.java
new file mode 100644
index 000000000..f324bdfc2
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/bo/IscAppServiceApplyBo.java
@@ -0,0 +1,111 @@
+package com.ruoyi.isc.domain.bo;
+
+import com.ruoyi.common.core.domain.BaseEntity;
+import com.ruoyi.common.core.validate.AddGroup;
+import com.ruoyi.common.core.validate.EditGroup;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 应用服务申请信息业务对象 isc_app_service_apply
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-09
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ApiModel("应用服务申请信息业务对象")
+public class IscAppServiceApplyBo extends BaseEntity {
+
+ /**
+ * 申请ID
+ */
+ @ApiModelProperty(value = "申请ID")
+ private Long applyId;
+
+ /**
+ * 应用服务ID
+ */
+ @ApiModelProperty(value = "应用服务ID", required = true)
+ @NotNull(message = "应用服务ID不能为空", groups = { AddGroup.class, EditGroup.class })
+ private Long appServiceId;
+
+ /**
+ * 申请类型(0申请 1续期)
+ */
+ @ApiModelProperty(value = "申请类型(0申请 1续期)")
+ private String applyType;
+
+ /**
+ * 审核状态(0待审核 1审核通过 2驳回)
+ */
+ @ApiModelProperty(value = "审核状态(0待审核 1审核通过 2驳回)")
+ private String status;
+
+ /**
+ * 续期时长(单位月)
+ */
+ @ApiModelProperty(value = "续期时长(单位月)")
+ private Integer renewalDuration;
+
+ /**
+ * 天配额
+ */
+ @ApiModelProperty(value = "天配额")
+ private Long quotaDays;
+
+ /**
+ * 小时配额
+ */
+ @ApiModelProperty(value = "小时配额")
+ private Long quotaHours;
+
+ /**
+ * 分钟配额
+ */
+ @ApiModelProperty(value = "分钟配额")
+ private Long quotaMinutes;
+
+ /**
+ * 秒配额
+ */
+ @ApiModelProperty(value = "秒配额")
+ private Long quotaSeconds;
+
+
+ /**
+ * 分页大小
+ */
+ @ApiModelProperty("分页大小")
+ private Integer pageSize;
+
+ /**
+ * 当前页数
+ */
+ @ApiModelProperty("当前页数")
+ private Integer pageNum;
+
+ /**
+ * 排序列
+ */
+ @ApiModelProperty("排序列")
+ private String orderByColumn;
+
+ /**
+ * 排序的方向desc或者asc
+ */
+ @ApiModelProperty(value = "排序的方向", example = "asc,desc")
+ private String isAsc;
+
+ /**
+ * 备注
+ */
+ @ApiModelProperty(value = "备注")
+ private String remark;
+
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/bo/IscAppServiceBo.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/bo/IscAppServiceBo.java
new file mode 100644
index 000000000..1442d6709
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/bo/IscAppServiceBo.java
@@ -0,0 +1,132 @@
+package com.ruoyi.isc.domain.bo;
+
+import com.ruoyi.common.core.domain.BaseEntity;
+import com.ruoyi.common.core.validate.AddGroup;
+import com.ruoyi.common.core.validate.EditGroup;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+
+/**
+ * 应用服务业务对象 isc_app_service
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-08
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ApiModel("应用服务业务对象")
+public class IscAppServiceBo extends BaseEntity {
+
+ /**
+ * 应用服务ID
+ */
+ @ApiModelProperty(value = "应用服务ID")
+ private Long appServiceId;
+
+ /**
+ * 服务ID
+ */
+ @ApiModelProperty(value = "服务ID", required = true)
+ @NotNull(message = "服务ID不能为空", groups = { AddGroup.class, EditGroup.class })
+ private Long serviceId;
+
+ /**
+ * 应用ID
+ */
+ @ApiModelProperty(value = "应用ID", required = true)
+ @NotNull(message = "应用ID不能为空", groups = { AddGroup.class, EditGroup.class })
+ private Long applicationId;
+
+ /**
+ * 启用状态(0启用 1停用)
+ */
+ @ApiModelProperty(value = "启用状态(0启用 1停用)", required = true)
+ @NotBlank(message = "启用状态(0启用 1停用)不能为空", groups = { AddGroup.class, EditGroup.class })
+ private String enabled;
+
+ /**
+ * 审核状态(0待审核 1审核通过 2驳回)
+ */
+ @ApiModelProperty(value = "审核状态(0待审核 1审核通过 2驳回)")
+ private String status;
+
+ /**
+ * 到期时间
+ */
+ @ApiModelProperty(value = "到期时间")
+ private Date endTime;
+
+ /**
+ * 天配额
+ */
+ @ApiModelProperty(value = "天配额")
+ private Long quotaDays;
+
+ /**
+ * 小时配额
+ */
+ @ApiModelProperty(value = "小时配额")
+ private Long quotaHours;
+
+ /**
+ * 分钟配额
+ */
+ @ApiModelProperty(value = "分钟配额")
+ private Long quotaMinutes;
+
+ /**
+ * 秒配额
+ */
+ @ApiModelProperty(value = "秒配额")
+ private Long quotaSeconds;
+
+ /**
+ * 续期时长(单位月)
+ */
+ @NotNull(message = "续期时长不能为空", groups = { AddGroup.class })
+ private Integer renewalDuration;
+
+ /**
+ * 申请类型(0申请 1续期)
+ */
+ @NotBlank(message = "申请类型必填")
+ private String applyType;
+
+
+ /**
+ * 分页大小
+ */
+ @ApiModelProperty("分页大小")
+ private Integer pageSize;
+
+ /**
+ * 当前页数
+ */
+ @ApiModelProperty("当前页数")
+ private Integer pageNum;
+
+ /**
+ * 排序列
+ */
+ @ApiModelProperty("排序列")
+ private String orderByColumn;
+
+ /**
+ * 排序的方向desc或者asc
+ */
+ @ApiModelProperty(value = "排序的方向", example = "asc,desc")
+ private String isAsc;
+
+ /**
+ * 备注
+ */
+ @ApiModelProperty(value = "备注")
+ private String remark;
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/bo/IscApplicationBo.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/bo/IscApplicationBo.java
new file mode 100644
index 000000000..1ad040acb
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/bo/IscApplicationBo.java
@@ -0,0 +1,68 @@
+package com.ruoyi.isc.domain.bo;
+
+import com.ruoyi.common.core.domain.BaseEntity;
+import com.ruoyi.common.core.validate.AddGroup;
+import com.ruoyi.common.core.validate.EditGroup;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 应用信息业务对象 isc_application
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-08
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ApiModel("应用信息业务对象")
+public class IscApplicationBo extends BaseEntity {
+
+ /**
+ * 应用ID
+ */
+ @ApiModelProperty(value = "应用ID")
+ private Long applicationId;
+
+ /**
+ * 应用名称
+ */
+ @ApiModelProperty(value = "应用名称", required = true)
+ @NotBlank(message = "应用名称不能为空", groups = { AddGroup.class, EditGroup.class })
+ private String applicationName;
+
+ /**
+ * 应用密钥
+ */
+ @ApiModelProperty(value = "应用密钥", required = true)
+ private String accessKey;
+
+ /**
+ * 分页大小
+ */
+ @ApiModelProperty("分页大小")
+ private Integer pageSize;
+
+ /**
+ * 当前页数
+ */
+ @ApiModelProperty("当前页数")
+ private Integer pageNum;
+
+ /**
+ * 排序列
+ */
+ @ApiModelProperty("排序列")
+ private String orderByColumn;
+
+ /**
+ * 排序的方向desc或者asc
+ */
+ @ApiModelProperty(value = "排序的方向", example = "asc,desc")
+ private String isAsc;
+
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/bo/IscAuditBo.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/bo/IscAuditBo.java
new file mode 100644
index 000000000..fd52ce73b
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/bo/IscAuditBo.java
@@ -0,0 +1,50 @@
+package com.ruoyi.isc.domain.bo;
+
+import com.ruoyi.common.core.domain.BaseEntity;
+import com.ruoyi.common.core.validate.AddGroup;
+import com.ruoyi.common.core.validate.EditGroup;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 服务管控审核
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-09
+ */
+
+@Data
+@ApiModel("服务管控审核业务对象")
+public class IscAuditBo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * ID集合
+ */
+ @ApiModelProperty(value = "ID集合")
+ @NotEmpty(message = "请选择记录")
+ private List ids;
+
+ /**
+ * 审核状态(0待审核 1审核通过 2驳回)
+ */
+ @ApiModelProperty(value = "审核状态(0待审核 1审核通过 2驳回)")
+ @NotBlank(message = "请填写审核结果")
+ private String status;
+
+ /**
+ * 备注
+ */
+ @ApiModelProperty(value = "备注")
+ private String remark;
+
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/bo/IscServiceBo.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/bo/IscServiceBo.java
new file mode 100644
index 000000000..0ec2e8b2b
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/bo/IscServiceBo.java
@@ -0,0 +1,131 @@
+package com.ruoyi.isc.domain.bo;
+
+import com.ruoyi.common.core.domain.BaseEntity;
+import com.ruoyi.common.core.validate.AddGroup;
+import com.ruoyi.common.core.validate.EditGroup;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 服务信息业务对象 isc_service
+ *
+ * @author Wenchao Gong
+ * @date 2021-08-22
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ApiModel("服务信息业务对象")
+public class IscServiceBo extends BaseEntity {
+
+ /**
+ * 服务ID
+ */
+ @ApiModelProperty(value = "服务ID", required = true)
+ @NotNull(message = "服务ID不能为空", groups = { EditGroup.class })
+ private Long serviceId;
+
+ /**
+ * 服务名称
+ */
+ @ApiModelProperty(value = "服务名称", required = true)
+ @NotBlank(message = "服务名称不能为空", groups = { AddGroup.class, EditGroup.class })
+ private String serviceName;
+
+ /**
+ * 服务地址
+ */
+ @ApiModelProperty(value = "服务地址", required = true)
+ @NotBlank(message = "服务地址不能为空", groups = { AddGroup.class, EditGroup.class })
+ private String serviceAddr;
+
+ /**
+ * 探活地址
+ */
+ @ApiModelProperty(value = "探活地址", required = true)
+ @NotBlank(message = "探活地址不能为空", groups = { AddGroup.class, EditGroup.class })
+ private String probeActiveAddr;
+
+ /**
+ * 请求方式(默认GET)
+ */
+ @ApiModelProperty(value = "请求方式(默认GET)", required = true)
+ @NotBlank(message = "请求方式(默认GET)不能为空", groups = { AddGroup.class, EditGroup.class })
+ private String requestMethod;
+
+ /**
+ * 跨域标志(Y是 N否)
+ */
+ @ApiModelProperty(value = "跨域标志(Y是 N否)", required = true)
+ @NotBlank(message = "跨域标志(Y是 N否)不能为空", groups = { AddGroup.class, EditGroup.class })
+ private String corsFlag;
+
+ /**
+ * 隐藏参数
+ */
+ @ApiModelProperty(value = "隐藏参数")
+ private String hiddenParams;
+
+ /**
+ * 是否在线(0离线 1在线)
+ */
+ @ApiModelProperty(value = "是否在线(0离线 1在线)")
+ private String onlineStatus;
+
+ /**
+ * 审核状态(0待审核 1审核通过 2驳回)
+ */
+ @ApiModelProperty(value = "审核状态(0待审核 1审核通过 2驳回)")
+ private String status;
+
+ /**
+ * JSON文档
+ */
+ @ApiModelProperty(value = "JSON文档", required = true)
+ @NotBlank(message = "JSON文档不能为空", groups = { AddGroup.class, EditGroup.class })
+ private String apiDoc;
+
+ /**
+ * 服务状态(0启用 1停用)
+ */
+ @ApiModelProperty(value = "服务状态(0启用 1停用)", required = true)
+ @NotBlank(message = "服务状态(0启用 1停用)不能为空", groups = { AddGroup.class, EditGroup.class })
+ private String enabled;
+
+ /**
+ * 服务分类
+ */
+ @ApiModelProperty(value = "服务分类", required = true)
+ @NotNull(message = "服务分类不能为空", groups = { AddGroup.class, EditGroup.class })
+ private String cateFullPath;
+
+ /**
+ * 分页大小
+ */
+ @ApiModelProperty("分页大小")
+ private Integer pageSize;
+
+ /**
+ * 当前页数
+ */
+ @ApiModelProperty("当前页数")
+ private Integer pageNum;
+
+ /**
+ * 排序列
+ */
+ @ApiModelProperty("排序列")
+ private String orderByColumn;
+
+ /**
+ * 排序的方向desc或者asc
+ */
+ @ApiModelProperty(value = "排序的方向", example = "asc,desc")
+ private String isAsc;
+
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/bo/IscServiceCateBo.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/bo/IscServiceCateBo.java
new file mode 100644
index 000000000..4582e7c96
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/bo/IscServiceCateBo.java
@@ -0,0 +1,69 @@
+package com.ruoyi.isc.domain.bo;
+
+import com.ruoyi.common.core.domain.TreeEntity;
+import com.ruoyi.common.core.validate.AddGroup;
+import com.ruoyi.common.core.validate.EditGroup;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 服务分类业务对象 isc_service_cate
+ *
+ * @author Wenchao Gong
+ * @date 2021-08-22
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ApiModel("服务分类业务对象")
+public class IscServiceCateBo extends TreeEntity {
+
+ /**
+ * 分类ID
+ */
+ @ApiModelProperty(value = "分类ID")
+ private Long cateId;
+
+ /**
+ * 分类名称
+ */
+ @ApiModelProperty(value = "分类名称")
+ private String cateName;
+
+ /**
+ * 启用状态(0启用 1停用)
+ */
+ @ApiModelProperty(value = "启用状态(0启用 1停用)", required = true)
+ @NotBlank(message = "启用状态(0启用 1停用)不能为空", groups = { AddGroup.class, EditGroup.class })
+ private String enabled;
+
+
+ /**
+ * 分页大小
+ */
+ @ApiModelProperty("分页大小")
+ private Integer pageSize;
+
+ /**
+ * 当前页数
+ */
+ @ApiModelProperty("当前页数")
+ private Integer pageNum;
+
+ /**
+ * 排序列
+ */
+ @ApiModelProperty("排序列")
+ private String orderByColumn;
+
+ /**
+ * 排序的方向desc或者asc
+ */
+ @ApiModelProperty(value = "排序的方向", example = "asc,desc")
+ private String isAsc;
+
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/vo/IscAppServiceApplyVo.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/vo/IscAppServiceApplyVo.java
new file mode 100644
index 000000000..0d461831a
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/vo/IscAppServiceApplyVo.java
@@ -0,0 +1,127 @@
+package com.ruoyi.isc.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.ruoyi.common.annotation.ExcelDictFormat;
+import com.ruoyi.common.convert.ExcelDictConvert;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import java.util.Date;
+
+
+
+/**
+ * 应用服务申请信息视图对象 isc_app_service_apply
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-09
+ */
+@Data
+@ApiModel("应用服务申请信息视图对象")
+@ExcelIgnoreUnannotated
+public class IscAppServiceApplyVo {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 申请ID
+ */
+ @ApiModelProperty("申请ID")
+ private Long applyId;
+
+ /**
+ * 应用服务ID
+ */
+ @ApiModelProperty("应用服务ID")
+ private Long appServiceId;
+
+ /**
+ * 应用名称
+ */
+ @ExcelProperty(value = "应用名称")
+ @ApiModelProperty("应用名称")
+ private String applicationName;
+
+ /**
+ * 服务名称
+ */
+ @ExcelProperty(value = "服务名称")
+ @ApiModelProperty("服务名称")
+ private String serviceName;
+
+ /**
+ * 申请类型(0申请 1续期)
+ */
+ @ExcelProperty(value = "申请类型(0申请 1续期)", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "isc_apply_type")
+ @ApiModelProperty("申请类型(0申请 1续期)")
+ private String applyType;
+
+ /**
+ * 审核状态(0待审核 1审核通过 2驳回)
+ */
+ @ExcelProperty(value = "审核状态", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_audit_status")
+ @ApiModelProperty("审核状态(0待审核 1审核通过 2驳回)")
+ private String status;
+
+ /**
+ * 续期时长(单位月)
+ */
+ @ExcelProperty(value = "续期时长", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(readConverterExp = "单=位月")
+ @ApiModelProperty("续期时长(单位月)")
+ private Integer renewalDuration;
+
+ /**
+ * 天配额
+ */
+ @ExcelProperty(value = "天配额")
+ @ApiModelProperty("天配额")
+ private Long quotaDays;
+
+ /**
+ * 小时配额
+ */
+ @ExcelProperty(value = "小时配额")
+ @ApiModelProperty("小时配额")
+ private Long quotaHours;
+
+ /**
+ * 分钟配额
+ */
+ @ExcelProperty(value = "分钟配额")
+ @ApiModelProperty("分钟配额")
+ private Long quotaMinutes;
+
+ /**
+ * 秒配额
+ */
+ @ExcelProperty(value = "秒配额")
+ @ApiModelProperty("秒配额")
+ private Long quotaSeconds;
+
+ /**
+ * 更新者
+ */
+ @ExcelProperty(value = "更新者")
+ @ApiModelProperty("更新者")
+ private String updateBy;
+
+ /**
+ * 更新时间
+ */
+ @ExcelProperty(value = "更新时间")
+ @ApiModelProperty("更新时间")
+ private Date updateTime;
+
+ /**
+ * 备注
+ */
+ @ExcelProperty(value = "备注")
+ @ApiModelProperty("备注")
+ private String remark;
+
+
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/vo/IscAppServiceVo.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/vo/IscAppServiceVo.java
new file mode 100644
index 000000000..a4eba30ef
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/vo/IscAppServiceVo.java
@@ -0,0 +1,150 @@
+package com.ruoyi.isc.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.ruoyi.common.annotation.ExcelDictFormat;
+import com.ruoyi.common.convert.ExcelDictConvert;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+
+
+/**
+ * 应用服务视图对象 isc_app_service
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-08
+ */
+@Data
+@ApiModel("应用服务视图对象")
+@ExcelIgnoreUnannotated
+public class IscAppServiceVo {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 应用服务ID
+ */
+ @ApiModelProperty("应用服务ID")
+ private Long appServiceId;
+
+ /**
+ * 服务ID
+ */
+ @ApiModelProperty("服务ID")
+ private Long serviceId;
+
+ /**
+ * 服务名称
+ */
+ @ExcelProperty(value = "服务名称")
+ @ApiModelProperty("服务名称")
+ private String serviceName;
+
+ /**
+ * 应用ID
+ */
+ @ExcelProperty(value = "应用ID")
+ @ApiModelProperty("应用ID")
+ private Long applicationId;
+
+ /**
+ * 用户ID
+ */
+ @ExcelProperty(value = "用户ID")
+ @ApiModelProperty("用户ID")
+ private Long userId;
+
+ /**
+ * 启用状态(0启用 1停用)
+ */
+ @ExcelProperty(value = "启用状态", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_normal_disable")
+ @ApiModelProperty("启用状态(0启用 1停用)")
+ private String enabled;
+
+ /**
+ * 申请类型(0申请 1续期)
+ */
+ @ExcelProperty(value = "申请类型(0申请 1续期)", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "isc_apply_type")
+ @ApiModelProperty("申请类型(0申请 1续期)")
+ private String applyType;
+
+ /**
+ * 审核状态(0待审核 1审核通过 2驳回)
+ */
+ @ExcelProperty(value = "审核状态", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_audit_status")
+ @ApiModelProperty("审核状态(0待审核 1审核通过 2驳回)")
+ private String status;
+
+ /**
+ * 审核意见
+ */
+ @ApiModelProperty("审核意见")
+ private String auditMind;
+
+ /**
+ * 虚拟地址
+ */
+ @ExcelProperty(value = "虚拟地址")
+ @ApiModelProperty("虚拟地址")
+ private String virtualAddr;
+
+ /**
+ * 到期时间
+ */
+ @ExcelProperty(value = "到期时间")
+ @ApiModelProperty("到期时间")
+ private Date endTime;
+
+ /**
+ * 天配额
+ */
+ @ApiModelProperty(value = "天配额")
+ private Long quotaDays;
+
+ /**
+ * 小时配额
+ */
+ @ApiModelProperty(value = "小时配额")
+ private Long quotaHours;
+
+ /**
+ * 分钟配额
+ */
+ @ApiModelProperty(value = "分钟配额")
+ private Long quotaMinutes;
+
+ /**
+ * 秒配额
+ */
+ @ApiModelProperty(value = "秒配额")
+ private Long quotaSeconds;
+
+ /**
+ * 备注
+ */
+ @ApiModelProperty(value = "备注")
+ private String remark;
+
+ /**
+ * 更新者
+ */
+ @ExcelProperty(value = "更新者")
+ @ApiModelProperty("更新者")
+ private String updateBy;
+
+ /**
+ * 更新时间
+ */
+ @ExcelProperty(value = "更新时间")
+ @ApiModelProperty("更新时间")
+ private Date updateTime;
+
+
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/vo/IscApplicationVo.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/vo/IscApplicationVo.java
new file mode 100644
index 000000000..bfe4503b6
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/vo/IscApplicationVo.java
@@ -0,0 +1,67 @@
+package com.ruoyi.isc.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+
+
+/**
+ * 应用信息视图对象 isc_application
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-08
+ */
+@Data
+@ApiModel("应用信息视图对象")
+@ExcelIgnoreUnannotated
+public class IscApplicationVo {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 应用ID
+ */
+ @ApiModelProperty("应用ID")
+ private Long applicationId;
+
+ /**
+ * 应用名称
+ */
+ @ExcelProperty(value = "应用名称")
+ @ApiModelProperty("应用名称")
+ private String applicationName;
+
+ /**
+ * 应用密钥
+ */
+ @ExcelProperty(value = "应用密钥")
+ @ApiModelProperty("应用密钥")
+ private String accessKey;
+
+ /**
+ * 备注
+ */
+ @ApiModelProperty(value = "备注")
+ private String remark;
+
+ /**
+ * 更新者
+ */
+ @ExcelProperty(value = "更新者")
+ @ApiModelProperty("更新者")
+ private String updateBy;
+
+ /**
+ * 更新时间
+ */
+ @ExcelProperty(value = "更新时间")
+ @ApiModelProperty("更新时间")
+ private Date updateTime;
+
+
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/vo/IscServiceCateVo.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/vo/IscServiceCateVo.java
new file mode 100644
index 000000000..d7de8ce86
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/vo/IscServiceCateVo.java
@@ -0,0 +1,84 @@
+package com.ruoyi.isc.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.ruoyi.common.annotation.ExcelDictFormat;
+import com.ruoyi.common.convert.ExcelDictConvert;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import java.util.Date;
+
+
+
+/**
+ * 服务分类视图对象 isc_service_cate
+ *
+ * @author Wenchao Gong
+ * @date 2021-08-22
+ */
+@Data
+@ApiModel("服务分类视图对象")
+@ExcelIgnoreUnannotated
+public class IscServiceCateVo {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 分类ID
+ */
+ @ApiModelProperty("分类ID")
+ private Long cateId;
+
+ /**
+ * 父分类ID
+ */
+ @ExcelProperty(value = "父分类ID")
+ @ApiModelProperty("父分类ID")
+ private Long parentId;
+
+ /**
+ * 分类名称
+ */
+ @ExcelProperty(value = "分类名称")
+ @ApiModelProperty("分类名称")
+ private String cateName;
+
+ /**
+ * 启用状态(0启用 1停用)
+ */
+ @ExcelProperty(value = "启用状态", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_normal_disable")
+ @ApiModelProperty("启用状态(0启用 1停用)")
+ private String enabled;
+
+ /**
+ * 显示顺序
+ */
+ @ExcelProperty(value = "显示顺序")
+ @ApiModelProperty("显示顺序")
+ private Long orderNum;
+
+ /**
+ * 更新者
+ */
+ @ExcelProperty(value = "更新者")
+ @ApiModelProperty("更新者")
+ private String updateBy;
+
+ /**
+ * 更新时间
+ */
+ @ExcelProperty(value = "更新时间")
+ @ApiModelProperty("更新时间")
+ private Date updateTime;
+
+ /**
+ * 备注
+ */
+ @ExcelProperty(value = "备注")
+ @ApiModelProperty("备注")
+ private String remark;
+
+
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/vo/IscServiceVo.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/vo/IscServiceVo.java
new file mode 100644
index 000000000..1eb67f1de
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/domain/vo/IscServiceVo.java
@@ -0,0 +1,138 @@
+package com.ruoyi.isc.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.ruoyi.common.annotation.ExcelDictFormat;
+import com.ruoyi.common.convert.ExcelDictConvert;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import java.util.Date;
+
+
+
+/**
+ * 服务信息视图对象 isc_service
+ *
+ * @author ruoyi
+ * @date 2021-08-22
+ */
+@Data
+@ApiModel("服务信息视图对象")
+@ExcelIgnoreUnannotated
+public class IscServiceVo {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 服务ID
+ */
+ @ApiModelProperty("服务ID")
+ private Long serviceId;
+
+ /**
+ * 服务名称
+ */
+ @ExcelProperty(value = "服务名称")
+ @ApiModelProperty("服务名称")
+ private String serviceName;
+
+ /**
+ * 服务地址
+ */
+ @ExcelProperty(value = "服务地址")
+ @ApiModelProperty("服务地址")
+ private String serviceAddr;
+
+ /**
+ * 探活地址
+ */
+ @ExcelProperty(value = "探活地址")
+ @ApiModelProperty("探活地址")
+ private String probeActiveAddr;
+
+ /**
+ * 请求方式(默认GET)
+ */
+ @ExcelProperty(value = "请求方式")
+ @ApiModelProperty("请求方式(默认GET)")
+ private String requestMethod;
+
+ /**
+ * 备注
+ */
+ @ApiModelProperty("备注")
+ private String remark;
+
+ /**
+ * 跨域标志(Y是 N否)
+ */
+ @ExcelProperty(value = "跨域标志", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_yes_no")
+ @ApiModelProperty("跨域标志(Y是 N否)")
+ private String corsFlag;
+
+ /**
+ * 隐藏参数
+ */
+ @ApiModelProperty("隐藏参数")
+ private String hiddenParams;
+
+ /**
+ * JSON文档
+ */
+ @ApiModelProperty("JSON文档")
+ private String apiDoc;
+
+ /**
+ * 是否在线(0离线 1在线)
+ */
+ @ExcelProperty(value = "是否在线", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "isc_online_status")
+ @ApiModelProperty("是否在线(0离线 1在线)")
+ private String onlineStatus;
+
+ /**
+ * 审核状态(0待审核 1审核通过 2驳回)
+ */
+ @ExcelProperty(value = "审核状态", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_audit_status")
+ @ApiModelProperty("审核状态(0待审核 1审核通过 2驳回)")
+ private String status;
+
+ /**
+ * 服务状态(0启用 1停用)
+ */
+ @ExcelProperty(value = "服务状态", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_normal_disable")
+ @ApiModelProperty("服务状态(0启用 1停用)")
+ private String enabled;
+
+ /**
+ * 服务分类
+ */
+ @ApiModelProperty("服务分类")
+ private String cateFullPath;
+
+ /**
+ * 服务分类名称
+ */
+ @ExcelProperty(value = "服务分类")
+ @ApiModelProperty("服务分类名称")
+ private String cateName;
+
+ /**
+ * 更新人
+ */
+ @ExcelProperty(value = "更新人")
+ @ApiModelProperty("更新人")
+ private String updateBy;
+
+ /**
+ * 更新时间
+ */
+ @ExcelProperty(value = "更新时间")
+ @ApiModelProperty("更新时间")
+ private Date updateTime;
+
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/mapper/IscAppServiceApplyMapper.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/mapper/IscAppServiceApplyMapper.java
new file mode 100644
index 000000000..0e4ea03d7
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/mapper/IscAppServiceApplyMapper.java
@@ -0,0 +1,16 @@
+package com.ruoyi.isc.mapper;
+
+import com.ruoyi.isc.domain.IscAppServiceApply;
+import com.ruoyi.common.core.mybatisplus.core.BaseMapperPlus;
+import com.ruoyi.common.core.mybatisplus.cache.MybatisPlusRedisCache;
+import org.apache.ibatis.annotations.CacheNamespace;
+
+/**
+ * 应用服务申请信息Mapper接口
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-09
+ */
+public interface IscAppServiceApplyMapper extends BaseMapperPlus {
+
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/mapper/IscAppServiceMapper.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/mapper/IscAppServiceMapper.java
new file mode 100644
index 000000000..2e0d5d22f
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/mapper/IscAppServiceMapper.java
@@ -0,0 +1,16 @@
+package com.ruoyi.isc.mapper;
+
+import com.ruoyi.isc.domain.IscAppService;
+import com.ruoyi.common.core.mybatisplus.core.BaseMapperPlus;
+import com.ruoyi.common.core.mybatisplus.cache.MybatisPlusRedisCache;
+import org.apache.ibatis.annotations.CacheNamespace;
+
+/**
+ * 应用服务Mapper接口
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-08
+ */
+public interface IscAppServiceMapper extends BaseMapperPlus {
+
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/mapper/IscApplicationMapper.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/mapper/IscApplicationMapper.java
new file mode 100644
index 000000000..23bd1a174
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/mapper/IscApplicationMapper.java
@@ -0,0 +1,14 @@
+package com.ruoyi.isc.mapper;
+
+import com.ruoyi.common.core.mybatisplus.core.BaseMapperPlus;
+import com.ruoyi.isc.domain.IscApplication;
+
+/**
+ * 应用信息Mapper接口
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-08
+ */
+public interface IscApplicationMapper extends BaseMapperPlus {
+
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/mapper/IscServiceCateMapper.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/mapper/IscServiceCateMapper.java
new file mode 100644
index 000000000..201d6fdcc
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/mapper/IscServiceCateMapper.java
@@ -0,0 +1,16 @@
+package com.ruoyi.isc.mapper;
+
+import com.ruoyi.isc.domain.IscServiceCate;
+import com.ruoyi.common.core.mybatisplus.core.BaseMapperPlus;
+import com.ruoyi.common.core.mybatisplus.cache.MybatisPlusRedisCache;
+import org.apache.ibatis.annotations.CacheNamespace;
+
+/**
+ * 服务分类Mapper接口
+ *
+ * @author Wenchao Gong
+ * @date 2021-08-22
+ */
+public interface IscServiceCateMapper extends BaseMapperPlus {
+
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/mapper/IscServiceMapper.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/mapper/IscServiceMapper.java
new file mode 100644
index 000000000..d3969891e
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/mapper/IscServiceMapper.java
@@ -0,0 +1,16 @@
+package com.ruoyi.isc.mapper;
+
+import com.ruoyi.isc.domain.IscService;
+import com.ruoyi.common.core.mybatisplus.core.BaseMapperPlus;
+import com.ruoyi.common.core.mybatisplus.cache.MybatisPlusRedisCache;
+import org.apache.ibatis.annotations.CacheNamespace;
+
+/**
+ * 服务信息Mapper接口
+ *
+ * @author Wenchao Gong
+ * @date 2021-08-22
+ */
+public interface IscServiceMapper extends BaseMapperPlus {
+
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/IIscAppServiceApplyService.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/IIscAppServiceApplyService.java
new file mode 100644
index 000000000..26cc0f3e6
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/IIscAppServiceApplyService.java
@@ -0,0 +1,64 @@
+package com.ruoyi.isc.service;
+
+import com.ruoyi.common.core.mybatisplus.core.IServicePlus;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.isc.domain.IscAppServiceApply;
+import com.ruoyi.isc.domain.bo.IscAppServiceApplyBo;
+import com.ruoyi.isc.domain.bo.IscAuditBo;
+import com.ruoyi.isc.domain.vo.IscAppServiceApplyVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 应用服务申请信息Service接口
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-09
+ */
+public interface IIscAppServiceApplyService extends IServicePlus {
+ /**
+ * 查询单个
+ * @return
+ */
+ IscAppServiceApplyVo queryById(Long applyId);
+
+ /**
+ * 查询列表
+ */
+ TableDataInfo queryPageList(IscAppServiceApplyBo bo);
+
+ /**
+ * 查询列表
+ */
+ List queryList(IscAppServiceApplyBo bo);
+
+ /**
+ * 根据新增业务对象插入应用服务申请信息
+ * @param bo 应用服务申请信息新增业务对象
+ * @return
+ */
+ Boolean insertByBo(IscAppServiceApplyBo bo);
+
+ /**
+ * 根据编辑业务对象修改应用服务申请信息
+ * @param bo 应用服务申请信息编辑业务对象
+ * @return
+ */
+ Boolean updateByBo(IscAppServiceApplyBo bo);
+
+ /**
+ * 校验并删除数据
+ * @param ids 主键集合
+ * @param isValid 是否校验,true-删除前校验,false-不校验
+ * @return
+ */
+ Boolean deleteWithValidByIds(Collection ids, Boolean isValid);
+
+ /**
+ * 应用服务审核
+ * @param bo 审核业务数据
+ * @return 是否审核成功
+ */
+ Boolean audit(IscAuditBo bo);
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/IIscAppServiceService.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/IIscAppServiceService.java
new file mode 100644
index 000000000..f3d74ece8
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/IIscAppServiceService.java
@@ -0,0 +1,76 @@
+package com.ruoyi.isc.service;
+
+import cn.hutool.core.lang.tree.Tree;
+import com.ruoyi.common.core.mybatisplus.core.IServicePlus;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.isc.domain.IscAppService;
+import com.ruoyi.isc.domain.bo.IscAppServiceBo;
+import com.ruoyi.isc.domain.vo.IscAppServiceVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 应用服务Service接口
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-08
+ */
+public interface IIscAppServiceService extends IServicePlus {
+ /**
+ * 查询单个
+ * @return
+ */
+ IscAppServiceVo queryById(Long appServiceId);
+
+ /**
+ * 查询列表
+ */
+ TableDataInfo queryPageList(IscAppServiceBo bo);
+
+ /**
+ * 查询列表
+ */
+ List queryList(IscAppServiceBo bo);
+
+ /**
+ * 根据新增业务对象插入应用服务
+ * @param bo 应用服务新增业务对象
+ * @return
+ */
+ Boolean insertByBo(IscAppServiceBo bo);
+
+ /**
+ * 根据编辑业务对象修改应用服务
+ * @param bo 应用服务编辑业务对象
+ * @return
+ */
+ Boolean updateByBo(IscAppServiceBo bo);
+
+ /**
+ * 校验并删除数据
+ * @param ids 主键集合
+ * @param isValid 是否校验,true-删除前校验,false-不校验
+ * @return
+ */
+ Boolean deleteWithValidByIds(Collection ids, Boolean isValid);
+
+ /**
+ * 构造 应用服务树结构
+ * @param applicationId 应用ID
+ * @return 应用服务树结构 (标识服务是否存在)
+ */
+ List> genAppServiceTree(Long applicationId);
+
+ /**
+ * 通过 Id集合 返回列表
+ * @param ids 应用服务ID
+ * @return 应用服务列表
+ */
+ List getAppServiceListByIds(Collection ids);
+
+ /**
+ * 刷新规则信息(服务对应AK)
+ */
+ void refreshRules();
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/IIscApplicationService.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/IIscApplicationService.java
new file mode 100644
index 000000000..4294390aa
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/IIscApplicationService.java
@@ -0,0 +1,64 @@
+package com.ruoyi.isc.service;
+
+import com.ruoyi.common.core.mybatisplus.core.IServicePlus;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.isc.domain.IscApplication;
+import com.ruoyi.isc.domain.bo.IscApplicationBo;
+import com.ruoyi.isc.domain.vo.IscApplicationVo;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 应用信息Service接口
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-08
+ */
+public interface IIscApplicationService extends IServicePlus {
+ /**
+ * 查询单个
+ * @return
+ */
+ IscApplicationVo queryById(Long applicationId);
+
+ /**
+ * 查询列表
+ */
+ TableDataInfo queryPageList(IscApplicationBo bo);
+
+ /**
+ * 查询列表
+ */
+ List queryList(IscApplicationBo bo);
+
+ /**
+ * 根据新增业务对象插入应用信息
+ * @param bo 应用信息新增业务对象
+ * @return
+ */
+ Boolean insertByBo(IscApplicationBo bo);
+
+ /**
+ * 根据编辑业务对象修改应用信息
+ * @param bo 应用信息编辑业务对象
+ * @return
+ */
+ Boolean updateByBo(IscApplicationBo bo);
+
+ /**
+ * 校验并删除数据
+ * @param ids 主键集合
+ * @param isValid 是否校验,true-删除前校验,false-不校验
+ * @return
+ */
+ Boolean deleteWithValidByIds(Collection ids, Boolean isValid);
+
+ /**
+ * 通过应用ID集合批量查询 应用名称
+ * @param applicationIds 应用ID集合
+ * @return 应用名称Map
+ */
+ Map getNameMap(Collection applicationIds);
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/IIscServiceCateService.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/IIscServiceCateService.java
new file mode 100644
index 000000000..6f40e00bd
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/IIscServiceCateService.java
@@ -0,0 +1,84 @@
+package com.ruoyi.isc.service;
+
+import cn.hutool.core.lang.tree.Tree;
+import com.ruoyi.common.core.mybatisplus.core.IServicePlus;
+import com.ruoyi.isc.domain.IscService;
+import com.ruoyi.isc.domain.IscServiceCate;
+import com.ruoyi.isc.domain.bo.IscServiceCateBo;
+import com.ruoyi.isc.domain.vo.IscServiceCateVo;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 服务分类Service接口
+ *
+ * @author Wenchao Gong
+ * @date 2021-08-22
+ */
+public interface IIscServiceCateService extends IServicePlus {
+ /**
+ * 查询单个
+ * @return
+ */
+ IscServiceCateVo queryById(Long cateId);
+
+
+ /**
+ * 查询列表
+ */
+ List queryList(IscServiceCateBo bo);
+
+ /**
+ * 根据新增业务对象插入服务分类
+ * @param bo 服务分类新增业务对象
+ * @return
+ */
+ Boolean insertByBo(IscServiceCateBo bo);
+
+ /**
+ * 根据编辑业务对象修改服务分类
+ * @param bo 服务分类编辑业务对象
+ * @return
+ */
+ Boolean updateByBo(IscServiceCateBo bo);
+
+ /**
+ * 校验并删除数据
+ * @param ids 主键集合
+ * @param isValid 是否校验,true-删除前校验,false-不校验
+ * @return
+ */
+ Boolean deleteWithValidByIds(Collection ids, Boolean isValid);
+
+ /**
+ * 获取分类信息
+ * @return 分类列表
+ */
+ List selectCateList();
+
+ /**
+ * 批量获取分类名称
+ * @param fullPathList
+ * @return
+ */
+ Map batchCateName(Set fullPathList);
+
+ /**
+ * 组装 服务分类 树结构
+ * @param cates 所有分类
+ * @return 树结构信息
+ */
+ List> genCateTree(List cates);
+
+ /**
+ * 组装 服务分类 树结构
+ * @param cates 所有分类
+ * @param serviceList 可用服务列表
+ * @param exitsIds 已存在服务ID集合
+ * @return 树结构信息
+ */
+ List> genCateTree(List cates, List serviceList, Set exitsIds);
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/IIscServiceService.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/IIscServiceService.java
new file mode 100644
index 000000000..48a675940
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/IIscServiceService.java
@@ -0,0 +1,98 @@
+package com.ruoyi.isc.service;
+
+import cn.hutool.core.lang.tree.Tree;
+import com.ruoyi.common.core.mybatisplus.core.IServicePlus;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.isc.domain.IscService;
+import com.ruoyi.isc.domain.bo.IscAuditBo;
+import com.ruoyi.isc.domain.bo.IscServiceBo;
+import com.ruoyi.isc.domain.vo.IscServiceVo;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 服务信息Service接口
+ *
+ * @author Wenchao Gong
+ * @date 2021-08-22
+ */
+public interface IIscServiceService extends IServicePlus {
+ /**
+ * 查询单个
+ * @return
+ */
+ IscServiceVo queryById(Long serviceId);
+
+ /**
+ * 查询列表
+ */
+ TableDataInfo queryPageList(IscServiceBo bo);
+
+ /**
+ * 查询列表
+ */
+ List queryList(IscServiceBo bo);
+
+ /**
+ * 根据新增业务对象插入服务信息
+ * @param bo 服务信息新增业务对象
+ * @return
+ */
+ Boolean insertByBo(IscServiceBo bo);
+
+ /**
+ * 根据编辑业务对象修改服务信息
+ * @param bo 服务信息编辑业务对象
+ * @return
+ */
+ Boolean updateByBo(IscServiceBo bo);
+
+ /**
+ * 校验并删除数据
+ * @param ids 主键集合
+ * @param isValid 是否校验,true-删除前校验,false-不校验
+ * @return
+ */
+ Boolean deleteWithValidByIds(Collection ids, Boolean isValid);
+
+ /**
+ * 通过服务ID集合 查询服务名称map
+ * @param serviceIds 服务ID集合
+ * @return 服务名称map
+ */
+ Map getNameMap(Collection serviceIds);
+
+ /**
+ * 组装 服务树结构
+ * @return 服务树结构
+ */
+ List> genServiceTree();
+
+ /**
+ * 组装 服务树结构
+ * @param exitsIds 已存在服务ID集合
+ * @return 服务树结构
+ */
+ List> genServiceTree(Set exitsIds);
+
+ /**
+ * 服务信息审核
+ * @param bo 审核信息
+ * @return 操作结果
+ */
+ Boolean audit(IscAuditBo bo);
+
+ /**
+ * 校验 审核参数
+ * @param bo 审核业务对象
+ */
+ void checkAuditBO(IscAuditBo bo);
+
+ /**
+ * 刷新路由信息
+ */
+ void refreshRoutes();
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/impl/IscAppServiceApplyServiceImpl.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/impl/IscAppServiceApplyServiceImpl.java
new file mode 100644
index 000000000..8d6b7588f
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/impl/IscAppServiceApplyServiceImpl.java
@@ -0,0 +1,263 @@
+package com.ruoyi.isc.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.lang.Assert;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.constant.IscConstants;
+import com.ruoyi.common.core.mybatisplus.core.ServicePlusImpl;
+import com.ruoyi.common.core.page.PagePlus;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.PageUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.isc.domain.IscAppService;
+import com.ruoyi.isc.domain.IscAppServiceApply;
+import com.ruoyi.isc.domain.IscApplication;
+import com.ruoyi.isc.domain.IscService;
+import com.ruoyi.isc.domain.bo.IscAppServiceApplyBo;
+import com.ruoyi.isc.domain.bo.IscAuditBo;
+import com.ruoyi.isc.domain.vo.IscAppServiceApplyVo;
+import com.ruoyi.isc.mapper.IscAppServiceApplyMapper;
+import com.ruoyi.isc.service.IIscAppServiceApplyService;
+import com.ruoyi.isc.service.IIscAppServiceService;
+import com.ruoyi.isc.service.IIscApplicationService;
+import com.ruoyi.isc.service.IIscServiceService;
+import com.ruoyi.isc.utils.RouteUtils;
+import com.ruoyi.isc.utils.beans.IscRouteDefinition;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 应用服务申请信息Service业务层处理
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-09
+ */
+@Service
+public class IscAppServiceApplyServiceImpl extends ServicePlusImpl implements IIscAppServiceApplyService {
+
+ @Resource
+ private IIscAppServiceService appServiceService;
+ @Resource
+ private IIscApplicationService applicationService;
+ @Resource
+ private IIscServiceService serviceService;
+
+ @Override
+ public IscAppServiceApplyVo queryById(Long applyId){
+ IscAppServiceApplyVo vo = getVoById(applyId);
+ genServiceNameAndApplicationName(Arrays.asList(vo));
+ return vo;
+ }
+
+ @Override
+ public TableDataInfo queryPageList(IscAppServiceApplyBo bo) {
+ PagePlus result = pageVo(PageUtils.buildPagePlus(), buildQueryWrapper(bo));
+ genServiceNameAndApplicationName(result.getRecordsVo());
+ return PageUtils.buildDataInfo(result);
+ }
+
+ @Override
+ public List queryList(IscAppServiceApplyBo bo) {
+ List results = listVo(buildQueryWrapper(bo));
+ genServiceNameAndApplicationName(results);
+ return results;
+ }
+
+ /**
+ * 组装服务、应用名称
+ * @param list 申请记录
+ */
+ private void genServiceNameAndApplicationName(List list)
+ {
+ if(CollectionUtil.isEmpty(list)) {
+ return;
+ }
+ Set appServiceIds = list.stream().map(IscAppServiceApplyVo::getAppServiceId).collect(Collectors.toSet());
+ List appServiceList = appServiceService.getAppServiceListByIds(appServiceIds);
+ if(CollectionUtil.isEmpty(appServiceList)) {
+ return;
+ }
+ Map appServiceMap = appServiceList.stream()
+ .collect(Collectors.toMap(IscAppService::getAppServiceId, Function.identity()));
+ Map serviceNameMap = serviceService.getNameMap(appServiceList.stream()
+ .map(IscAppService::getServiceId).collect(Collectors.toSet()));
+ Map applicationNameMap = applicationService.getNameMap(appServiceList.stream()
+ .map(IscAppService::getApplicationId).collect(Collectors.toSet()));
+ for (IscAppServiceApplyVo vo : list) {
+ IscAppService appService = appServiceMap.get(vo.getAppServiceId());
+ if(Objects.isNull(appService)) {
+ continue;
+ }
+ vo.setServiceName(serviceNameMap.get(appService.getServiceId()));
+ vo.setApplicationName(applicationNameMap.get(appService.getApplicationId()));
+ }
+ }
+
+ private LambdaQueryWrapper buildQueryWrapper(IscAppServiceApplyBo bo) {
+ Map params = bo.getParams();
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StringUtils.isNotBlank(bo.getApplyType()), IscAppServiceApply::getApplyType, bo.getApplyType());
+ lqw.eq(StringUtils.isNotBlank(bo.getStatus()), IscAppServiceApply::getStatus, bo.getStatus());
+ return lqw;
+ }
+
+ @Override
+ public Boolean insertByBo(IscAppServiceApplyBo bo) {
+ IscAppServiceApply add = BeanUtil.toBean(bo, IscAppServiceApply.class);
+ validEntityBeforeSave(add);
+ return save(add);
+ }
+
+ @Override
+ public Boolean updateByBo(IscAppServiceApplyBo bo) {
+ IscAppServiceApply update = BeanUtil.toBean(bo, IscAppServiceApply.class);
+ validEntityBeforeSave(update);
+ return updateById(update);
+ }
+
+ /**
+ * 保存前的数据校验
+ *
+ * @param entity 实体类数据
+ */
+ private void validEntityBeforeSave(IscAppServiceApply entity){
+ //TODO 做一些数据校验,如唯一约束
+ }
+
+ @Override
+ public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) {
+ if(isValid){
+ //TODO 做一些业务上的校验,判断是否需要校验
+ }
+ return removeByIds(ids);
+ }
+
+ @Override
+ public Boolean audit(IscAuditBo bo)
+ {
+ serviceService.checkAuditBO(bo);
+ boolean result = true;
+ for (Long id : bo.getIds())
+ {
+ IscAppServiceApply apply = getOne(Wrappers.lambdaQuery()
+ .eq(IscAppServiceApply::getApplyId, id)
+ .eq(IscAppServiceApply::getStatus, Constants.AUDIT_WAIT), false);
+ if(Objects.isNull(apply)) {
+ continue;
+ }
+ result = updateById(new IscAppServiceApply().setApplyId(id).setStatus(bo.getStatus())
+ .setAuditMind(bo.getRemark()));
+ IscAppService appService = appServiceService.getById(apply.getAppServiceId());
+ Assert.notNull(appService, () -> new ServiceException("申请信息不存在"));
+
+ IscAppService updateData = null;
+ IscService service = null;
+ if(Constants.AUDIT_PASS.equals(bo.getStatus())) {
+ service = serviceService.getById(appService.getServiceId());
+ Assert.notNull(service, () -> new ServiceException("服务信息不存在"));
+ updateData = genAuditPassData(apply, appService, service, bo.getStatus());
+ updateData.setAuditMind(bo.getRemark());
+ }else{
+ //如果 应用服务 状态为不通过 时需要同步状态
+ updateData = genAuditRejectData(appService, bo);
+ }
+ if(Objects.nonNull(updateData)) {
+ result = appServiceService.updateById(updateData);
+ if(Objects.nonNull(service)) {
+ IscAppService dbAppService = appServiceService.getById(apply.getAppServiceId());
+ IscApplication application = applicationService.getById(dbAppService.getApplicationId());
+ RouteUtils.saveRule(RouteUtils.generateRule(dbAppService, application.getAccessKey()));
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * 审核通过处理
+ * @param apply 申请信息
+ * @param appService 应用服务信息
+ * @param service 服务信息
+ * @param status 操作状态
+ * @return 更新数据
+ */
+ private IscAppService genAuditPassData(IscAppServiceApply apply, IscAppService appService, IscService service,
+ String status)
+ {
+ IscAppService updateData = new IscAppService().setAppServiceId(appService.getAppServiceId());
+ //如果是 服务申请 修改 应用服务 审核状态、生成虚拟地址、服务到期时间
+ //如果是续期申请 修改 服务到期时间
+ switch (apply.getApplyType()) {
+ case IscConstants.APPLY_TYPE_APPLY:
+ genServicePassInit(apply, appService, status, updateData, service);
+ break;
+ case IscConstants.APPLY_TYPE_MODIFY:
+ //如果 应用服务 状态不为通过 则修改状态(如果是通过:续期不能影响使用)
+ if(!Constants.AUDIT_PASS.equals(appService.getStatus())) {
+ genServicePassInit(apply, appService, status, updateData, service);
+ }
+ updateData.setQuotaDays(apply.getQuotaDays());
+ updateData.setQuotaHours(apply.getQuotaHours());
+ updateData.setQuotaMinutes(apply.getQuotaMinutes());
+ updateData.setQuotaSeconds(apply.getQuotaSeconds());
+ break;
+ case IscConstants.APPLY_TYPE_RENEWAL:
+ Date startDate = Objects.isNull(appService.getEndTime()) ? DateUtils.getNowDate() : appService.getEndTime();
+ updateData.setEndTime(DateUtils.beginOfDay(DateUtils.addMonths(startDate, apply.getRenewalDuration())));
+ break;
+ default:
+ throw new ServiceException("申请类型异常");
+ }
+ return updateData;
+ }
+
+ /**
+ * 审核通过 初始化生成 虚拟地址、服务结束时间
+ * @param apply 申请信息
+ * @param appService 应用服务信息
+ * @param status 审核状态
+ * @param updateData 需要修改的数据
+ * @param service 服务信息
+ */
+ private void genServicePassInit(IscAppServiceApply apply, IscAppService appService, String status,
+ IscAppService updateData, IscService service)
+ {
+ updateData.setStatus(status);
+ Integer renewalDuration = apply.getRenewalDuration();
+ //如果 申请时长为空,需要查询审核记录 中的审核时长
+ if(Objects.isNull(renewalDuration)) {
+ IscAppServiceApply log = getOne(Wrappers.lambdaQuery()
+ .eq(IscAppServiceApply::getAppServiceId, appService.getAppServiceId())
+ .eq(IscAppServiceApply::getApplyType, IscConstants.APPLY_TYPE_APPLY)
+ .last("LIMIT 1"), false);
+ renewalDuration = Objects.nonNull(log) ? log.getRenewalDuration() : 1;
+ }
+ updateData.setEndTime(DateUtils.beginOfDay(DateUtils.addMonths(DateUtils.getNowDate(), renewalDuration)));
+ updateData.setVirtualAddr(RouteUtils.genVirtualAddrPath(service.getServiceAddr()));
+ }
+
+ /**
+ * 审核不通过处理
+ * @param appService 应用服务信息
+ * @param bo 审核数据
+ * @return 需要更新的信息
+ */
+ private IscAppService genAuditRejectData(IscAppService appService, IscAuditBo bo) {
+ if(!Constants.AUDIT_PASS.equals(appService.getStatus())) {
+ IscAppService updateData = new IscAppService().setAppServiceId(appService.getAppServiceId());
+ updateData.setStatus(bo.getStatus());
+ updateData.setAuditMind(bo.getRemark());
+ return updateData;
+ }
+ return null;
+ }
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/impl/IscAppServiceServiceImpl.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/impl/IscAppServiceServiceImpl.java
new file mode 100644
index 000000000..c3be743d5
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/impl/IscAppServiceServiceImpl.java
@@ -0,0 +1,262 @@
+package com.ruoyi.isc.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.lang.tree.Tree;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.constant.IscConstants;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.mybatisplus.core.ServicePlusImpl;
+import com.ruoyi.common.core.page.PagePlus;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.PageUtils;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.isc.common.utils.beans.IscRule;
+import com.ruoyi.isc.domain.IscAppService;
+import com.ruoyi.isc.domain.IscAppServiceApply;
+import com.ruoyi.isc.domain.IscApplication;
+import com.ruoyi.isc.domain.bo.IscAppServiceApplyBo;
+import com.ruoyi.isc.domain.bo.IscAppServiceBo;
+import com.ruoyi.isc.domain.vo.IscAppServiceVo;
+import com.ruoyi.isc.mapper.IscAppServiceMapper;
+import com.ruoyi.isc.service.IIscAppServiceApplyService;
+import com.ruoyi.isc.service.IIscAppServiceService;
+import com.ruoyi.isc.service.IIscApplicationService;
+import com.ruoyi.isc.service.IIscServiceService;
+import com.ruoyi.isc.utils.RouteUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 应用服务Service业务层处理
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-08
+ */
+@Slf4j
+@Service
+public class IscAppServiceServiceImpl extends ServicePlusImpl implements IIscAppServiceService
+{
+
+ @Resource
+ private IIscServiceService serviceService;
+ @Resource
+ private IIscAppServiceApplyService applyService;
+ @Resource
+ private IIscApplicationService applicationService;
+
+ @PostConstruct
+ public void init() {
+ refreshRules();
+ }
+
+ @Override
+ public IscAppServiceVo queryById(Long appServiceId)
+ {
+ IscAppServiceVo vo = getVoById(appServiceId);
+ genServiceName(Arrays.asList(vo));
+ return vo;
+ }
+
+ @Override
+ public TableDataInfo queryPageList(IscAppServiceBo bo)
+ {
+ PagePlus result = pageVo(PageUtils.buildPagePlus(), buildQueryWrapper(bo));
+ genServiceName(result.getRecordsVo());
+ return PageUtils.buildDataInfo(result);
+ }
+
+ @Override
+ public List queryList(IscAppServiceBo bo)
+ {
+ final List results = listVo(buildQueryWrapper(bo));
+ genServiceName(results);
+ return results;
+ }
+
+ /**
+ * 组装服务名称
+ *
+ * @param results 应用服务列表
+ */
+ private void genServiceName(List results)
+ {
+ if (CollectionUtil.isEmpty(results)) {
+ return;
+ }
+ Set serviceIds = results.stream().map(IscAppServiceVo::getServiceId).collect(Collectors.toSet());
+ Map nameMap = serviceService.getNameMap(serviceIds);
+ for (IscAppServiceVo result : results) {
+ result.setServiceName(nameMap.get(result.getServiceId()));
+ }
+ }
+
+ private LambdaQueryWrapper buildQueryWrapper(IscAppServiceBo bo)
+ {
+ Map params = bo.getParams();
+ Long userId = SecurityUtils.getUserId();
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(bo.getServiceId() != null, IscAppService::getServiceId, bo.getServiceId());
+ lqw.eq(bo.getApplicationId() != null, IscAppService::getApplicationId, bo.getApplicationId());
+ lqw.eq(!SecurityUtils.isAdmin(userId), IscAppService::getUserId, userId);
+ lqw.eq(StringUtils.isNotBlank(bo.getEnabled()), IscAppService::getEnabled, bo.getEnabled());
+ lqw.eq(StringUtils.isNotBlank(bo.getStatus()), IscAppService::getStatus, bo.getStatus());
+ return lqw;
+ }
+
+ @Override
+ public Boolean insertByBo(IscAppServiceBo bo)
+ {
+ IscAppService add = BeanUtil.toBean(bo, IscAppService.class);
+ validEntityBeforeSave(add);
+ add.setStatus(Constants.AUDIT_WAIT);
+ add.setUserId(SecurityUtils.getUserId());
+ boolean result = save(add);
+ addApplyRecord(bo, IscConstants.APPLY_TYPE_APPLY, add.getAppServiceId());
+ return result;
+ }
+
+ @Override
+ public Boolean updateByBo(IscAppServiceBo bo)
+ {
+ IscAppService appService = getById(bo.getAppServiceId());
+ Assert.notNull(appService, () -> new ServiceException("申请信息不存在"));
+ IscAppService update = new IscAppService().setAppServiceId(bo.getAppServiceId());
+ validEntityBeforeSave(update);
+ boolean result = true;
+ boolean changeStatus = !Constants.AUDIT_PASS.equals(appService.getStatus());
+ boolean changeRemark = !IscConstants.APPLY_TYPE_RENEWAL.equals(bo.getApplyType());
+ if (changeStatus || changeRemark)
+ {
+ if (changeStatus)
+ {
+ update.setStatus(Constants.AUDIT_WAIT);
+ }
+ if (changeRemark)
+ {
+ update.setRemark(bo.getRemark());
+ }
+ result = updateById(update);
+ }
+ String applyType = IscConstants.APPLY_TYPE_RENEWAL.equals(bo.getApplyType())
+ ? IscConstants.APPLY_TYPE_RENEWAL : IscConstants.APPLY_TYPE_MODIFY;
+ addApplyRecord(bo, applyType, bo.getAppServiceId());
+ return result;
+ }
+
+ /**
+ * 保存前的数据校验
+ *
+ * @param entity 实体类数据
+ */
+ private void validEntityBeforeSave(IscAppService entity)
+ {
+ //判断是否有申请未审核
+ if (Objects.nonNull(entity.getAppServiceId())) {
+ long count = applyService.count(Wrappers.lambdaQuery()
+ .eq(IscAppServiceApply::getAppServiceId, entity.getAppServiceId())
+ .eq(IscAppServiceApply::getStatus, Constants.AUDIT_WAIT));
+ Assert.isFalse(SqlHelper.retBool(count), () -> new ServiceException("有申请未审核, 不能再申请"));
+ }
+ }
+
+ @Override
+ public Boolean deleteWithValidByIds(Collection ids, Boolean isValid)
+ {
+ if (isValid) {
+ //TODO 做一些业务上的校验,判断是否需要校验
+ }
+ applyService.remove(Wrappers.lambdaQuery().in(IscAppServiceApply::getAppServiceId, ids));
+ return removeByIds(ids);
+ }
+
+ @Override
+ public List> genAppServiceTree(Long applicationId)
+ {
+ List serviceIds = listObjs(Wrappers.lambdaQuery()
+ .select(IscAppService::getServiceId)
+ .eq(IscAppService::getApplicationId, applicationId), o -> (Long) o);
+ return serviceService.genServiceTree(CollectionUtil.newHashSet(serviceIds));
+ }
+
+ @Override
+ public List getAppServiceListByIds(Collection ids)
+ {
+ return list(Wrappers.lambdaQuery()
+ .select(IscAppService::getAppServiceId, IscAppService::getApplicationId, IscAppService::getServiceId)
+ .in(IscAppService::getAppServiceId, ids));
+ }
+
+ @Override
+ public void refreshRules()
+ {
+ List list = list(Wrappers.lambdaQuery()
+ .eq(IscAppService::getEnabled, UserConstants.NORMAL)
+ .eq(IscAppService::getStatus, Constants.AUDIT_PASS)
+ .gt(IscAppService::getEndTime, DateUtils.getNowDate()));
+ if(CollectionUtil.isEmpty(list)) {
+ return;
+ }
+ Set applicationIds = list.stream().map(IscAppService::getApplicationId).collect(Collectors.toSet());
+ Map accessKeyMap = applicationService.list(Wrappers.lambdaQuery()
+ .select(IscApplication::getApplicationId, IscApplication::getAccessKey)
+ .in(IscApplication::getApplicationId, applicationIds)).stream()
+ .collect(Collectors.toMap(IscApplication::getApplicationId, IscApplication::getAccessKey));
+ List routes = new ArrayList<>();
+
+ for (IscAppService appService : list)
+ {
+ String ak = accessKeyMap.get(appService.getApplicationId());
+ if(StringUtils.isBlank(ak)) {
+ log.error("路由初始化失败: id:[{}],ak[{}]信息不存在!", appService.getAppServiceId(), appService.getApplicationId());
+ continue;
+ }
+ routes.add(RouteUtils.generateRule(appService, ak));
+ }
+ RouteUtils.refreshRules(routes);
+ }
+
+ /**
+ * 添加 服务申请记录
+ *
+ * @param appService 应用服务信息
+ * @param applyType 申请类型
+ * @param appServiceId 应用服务ID
+ * @return 是否添加成功
+ */
+ private Boolean addApplyRecord(IscAppServiceBo appService, String applyType, Long appServiceId)
+ {
+ IscAppServiceApplyBo entity = new IscAppServiceApplyBo();
+ entity.setAppServiceId(appServiceId);
+ entity.setApplyType(applyType);
+ entity.setStatus(Constants.AUDIT_WAIT);
+ entity.setRemark(appService.getRemark());
+ switch (applyType) {
+ case IscConstants.APPLY_TYPE_APPLY:
+ case IscConstants.APPLY_TYPE_MODIFY:
+ entity.setRenewalDuration(appService.getRenewalDuration());
+ entity.setQuotaDays(appService.getQuotaDays());
+ entity.setQuotaHours(appService.getQuotaHours());
+ entity.setQuotaMinutes(appService.getQuotaMinutes());
+ entity.setQuotaSeconds(appService.getQuotaSeconds());
+ break;
+ case IscConstants.APPLY_TYPE_RENEWAL:
+ entity.setRenewalDuration(appService.getRenewalDuration());
+ break;
+ default:
+ }
+ return applyService.insertByBo(entity);
+ }
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/impl/IscApplicationServiceImpl.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/impl/IscApplicationServiceImpl.java
new file mode 100644
index 000000000..3a466a11b
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/impl/IscApplicationServiceImpl.java
@@ -0,0 +1,106 @@
+package com.ruoyi.isc.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.IdUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.ruoyi.common.core.mybatisplus.core.ServicePlusImpl;
+import com.ruoyi.common.core.page.PagePlus;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.utils.PageUtils;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.isc.domain.IscApplication;
+import com.ruoyi.isc.domain.bo.IscApplicationBo;
+import com.ruoyi.isc.domain.vo.IscApplicationVo;
+import com.ruoyi.isc.mapper.IscApplicationMapper;
+import com.ruoyi.isc.service.IIscApplicationService;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 应用信息Service业务层处理
+ *
+ * @author Wenchao Gong
+ * @date 2021-09-08
+ */
+@Service
+public class IscApplicationServiceImpl extends ServicePlusImpl implements IIscApplicationService {
+
+ @Override
+ public IscApplicationVo queryById(Long applicationId){
+ return getVoById(applicationId);
+ }
+
+ @Override
+ public TableDataInfo queryPageList(IscApplicationBo bo) {
+ PagePlus result = pageVo(PageUtils.buildPagePlus(), buildQueryWrapper(bo));
+ return PageUtils.buildDataInfo(result);
+ }
+
+ @Override
+ public List queryList(IscApplicationBo bo) {
+ return listVo(buildQueryWrapper(bo));
+ }
+
+ private LambdaQueryWrapper buildQueryWrapper(IscApplicationBo bo) {
+ Map params = bo.getParams();
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.like(StringUtils.isNotBlank(bo.getApplicationName()), IscApplication::getApplicationName, bo.getApplicationName());
+ lqw.eq(StringUtils.isNotBlank(bo.getAccessKey()), IscApplication::getAccessKey, bo.getAccessKey());
+ Long userId = SecurityUtils.getUserId();
+ lqw.eq(!SecurityUtils.isAdmin(userId), IscApplication::getUserId, userId);
+ return lqw;
+ }
+
+ @Override
+ public Boolean insertByBo(IscApplicationBo bo) {
+ IscApplication add = BeanUtil.toBean(bo, IscApplication.class);
+ validEntityBeforeSave(add);
+ add.setAccessKey(IdUtil.simpleUUID());
+ add.setUserId(SecurityUtils.getUserId());
+ return save(add);
+ }
+
+ @Override
+ public Boolean updateByBo(IscApplicationBo bo) {
+ IscApplication update = BeanUtil.toBean(bo, IscApplication.class);
+ validEntityBeforeSave(update);
+ return updateById(update);
+ }
+
+ /**
+ * 保存前的数据校验
+ *
+ * @param entity 实体类数据
+ */
+ private void validEntityBeforeSave(IscApplication entity){
+ //TODO 做一些数据校验,如唯一约束
+ }
+
+ @Override
+ public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) {
+ if(isValid){
+ //TODO 做一些业务上的校验,判断是否需要校验
+ }
+ return removeByIds(ids);
+ }
+
+ @Override
+ public Map getNameMap(Collection applicationIds)
+ {
+ if(CollectionUtil.isEmpty(applicationIds)) {
+ return MapUtil.empty();
+ }
+ return list(Wrappers.lambdaQuery()
+ .select(IscApplication::getApplicationId, IscApplication::getApplicationName)
+ .in(IscApplication::getApplicationId, applicationIds)).stream()
+ .collect(Collectors.toMap(IscApplication::getApplicationId, IscApplication::getApplicationName));
+ }
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/impl/IscServiceCateServiceImpl.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/impl/IscServiceCateServiceImpl.java
new file mode 100644
index 000000000..ec38e68c6
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/impl/IscServiceCateServiceImpl.java
@@ -0,0 +1,221 @@
+package com.ruoyi.isc.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.core.lang.tree.parser.NodeParser;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.mybatisplus.core.ServicePlusImpl;
+import com.ruoyi.common.utils.FullPathUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.TreeUtils;
+import com.ruoyi.isc.domain.IscService;
+import com.ruoyi.isc.domain.IscServiceCate;
+import com.ruoyi.isc.domain.bo.IscServiceCateBo;
+import com.ruoyi.isc.domain.vo.IscServiceCateVo;
+import com.ruoyi.isc.mapper.IscServiceCateMapper;
+import com.ruoyi.isc.service.IIscServiceCateService;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 服务分类Service业务层处理
+ *
+ * @author Wenchao Gong
+ * @date 2021-08-22
+ */
+@Service
+public class IscServiceCateServiceImpl extends ServicePlusImpl implements IIscServiceCateService {
+
+ public static final String FIELD_FULL_PATH = "fullPath";
+ public static final String FIELD_NODE_TYPE = "type";
+ public static final int NODE_TYPE_SERVICE = 1;
+ public static final int NODE_TYPE_CATE = 0;
+
+ @Override
+ public IscServiceCateVo queryById(Long cateId){
+ return getVoById(cateId);
+ }
+
+
+ @Override
+ public List queryList(IscServiceCateBo bo) {
+ return listVo(buildQueryWrapper(bo));
+ }
+
+ private LambdaQueryWrapper buildQueryWrapper(IscServiceCateBo bo) {
+ Map params = bo.getParams();
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.like(StringUtils.isNotBlank(bo.getCateName()), IscServiceCate::getCateName, bo.getCateName());
+ lqw.eq(StringUtils.isNotBlank(bo.getEnabled()), IscServiceCate::getEnabled, bo.getEnabled());
+ return lqw;
+ }
+
+ @Override
+ public Boolean insertByBo(IscServiceCateBo bo) {
+ IscServiceCate add = BeanUtil.toBean(bo, IscServiceCate.class);
+ validEntityBeforeSave(add);
+ add.setFullPath(getFullPath(bo.getParentId()));
+ return save(add);
+ }
+
+ /**
+ * 通过父ID生成 FULL_PATH
+ * @param parentId
+ * @return
+ */
+ private String getFullPath(Long parentId)
+ {
+ final boolean isRoot = parentId == 0L;
+ IscServiceCate cate = getOne(Wrappers.lambdaQuery()
+ .select(IscServiceCate::getCateId, IscServiceCate::getFullPath)
+ .eq(isRoot, IscServiceCate::getParentId, 0L).and(!isRoot, w -> w
+ .eq(IscServiceCate::getParentId, parentId)
+ .or()
+ .eq(IscServiceCate::getCateId, parentId))
+ .orderByDesc(IscServiceCate::getFullPath)
+ .last("LIMIT 1"));
+ String parentFullPath = null;
+ String maxFullPath = null;
+ if(Objects.nonNull(cate)){
+ if(cate.getCateId().intValue() == parentId)
+ {
+ parentFullPath = cate.getFullPath();
+ }else{
+ maxFullPath = cate.getFullPath();
+ }
+ }
+ final String fullPath = FullPathUtils.genFullPath(parentFullPath, maxFullPath);
+ return fullPath;
+ }
+
+ @Override
+ public Boolean updateByBo(IscServiceCateBo bo) {
+ IscServiceCate update = BeanUtil.toBean(bo, IscServiceCate.class);
+ validEntityBeforeSave(update);
+ return updateById(update);
+ }
+
+ /**
+ * 保存前的数据校验
+ *
+ * @param entity 实体类数据
+ */
+ private void validEntityBeforeSave(IscServiceCate entity){
+ //TODO 做一些数据校验,如唯一约束
+ }
+
+ @Override
+ public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) {
+ if(isValid){
+ //TODO 做一些业务上的校验,判断是否需要校验
+ }
+ return removeByIds(ids);
+ }
+
+ @Override
+ public List selectCateList()
+ {
+ return list(Wrappers.lambdaQuery()
+ .eq(IscServiceCate::getEnabled, UserConstants.DICT_NORMAL)
+ .orderByAsc(IscServiceCate::getParentId)
+ .orderByAsc(IscServiceCate::getOrderNum));
+ }
+
+ @Override
+ public Map batchCateName(Set fullPathList)
+ {
+ Map results = new HashMap<>();
+ if(CollectionUtils.isEmpty(fullPathList)) {
+ return results;
+ }
+ final List list = list(Wrappers.lambdaQuery()
+ .select(IscServiceCate::getCateId, IscServiceCate::getCateName, IscServiceCate::getFullPath, IscServiceCate::getParentId)
+ .in(IscServiceCate::getFullPath, fullPathList));
+ Map cateNameMap = batchCateName(list);
+ for (IscServiceCate cate : list)
+ {
+ results.put(cate.getFullPath(), cateNameMap.get(cate.getCateId()));
+ }
+ return results;
+ }
+
+ @Override
+ public List> genCateTree(List cates)
+ {
+ return genCateTree(cates, null, CollectionUtil.newHashSet());
+ }
+
+ @Override
+ public List> genCateTree(List cates, List serviceList, Set exitsIds)
+ {
+ boolean isServiceTree = Objects.nonNull(serviceList);
+ final Map> cateServiceMap = isServiceTree ? serviceList.stream()
+ .collect(Collectors.groupingBy(IscService::getCateFullPath)) : null;
+
+ NodeParser parser = (cate, tree) -> {
+ tree.setId(cate.getCateId());
+ tree.setParentId(cate.getParentId());
+ tree.setName(cate.getCateName());
+ tree.setWeight(cate.getOrderNum());
+ tree.putExtra(FIELD_FULL_PATH, cate.getFullPath());
+ if(isServiceTree) {
+ tree.putExtra(FIELD_NODE_TYPE, NODE_TYPE_CATE);
+ boolean exist = true;
+ List services = cateServiceMap.get(cate.getFullPath());
+ if(CollectionUtil.isEmpty(services)) {
+ tree.putExtra(TreeUtils.FIELD_NODE_EXIST, true);
+ return;
+ }
+ for (int index = 0; index < services.size(); index++)
+ {
+ IscService service = services.get(index);
+ List> children = tree.getChildren();
+ if(Objects.isNull(children)) {
+ children = CollectionUtil.newArrayList();
+ tree.setChildren(children);
+ }
+ final Tree child = new Tree<>(TreeUtils.DEFAULT_TREE_NODE_CONFIG);
+ child.setId(service.getServiceId());
+ child.setName(service.getServiceName());
+ child.setWeight(index);
+ child.putExtra(FIELD_NODE_TYPE, NODE_TYPE_SERVICE);
+ boolean contains = exitsIds.contains(service.getServiceId());
+ child.putExtra(TreeUtils.FIELD_NODE_EXIST, contains);
+ children.add(child);
+ if(exist && !contains) {
+ exist = false;
+ }
+ }
+ tree.putExtra(TreeUtils.FIELD_NODE_EXIST, exist);
+ }
+ };
+ return TreeUtils.build(cates, TreeUtils.DEFAULT_PARENT_ID, parser, isServiceTree);
+ }
+
+ private Map batchCateName(List list) {
+ Map results = new HashMap<>();
+ final Set parentIds = list.stream().filter(m -> m.getParentId() != 0L).map(IscServiceCate::getParentId).collect(Collectors.toSet());
+ if(CollectionUtils.isNotEmpty(parentIds)) {
+ final List parentList = list(Wrappers.lambdaQuery()
+ .select(IscServiceCate::getCateId, IscServiceCate::getCateName, IscServiceCate::getFullPath, IscServiceCate::getParentId)
+ .in(IscServiceCate::getCateId, parentIds));
+ results.putAll(batchCateName(parentList));
+ }
+ for (IscServiceCate cate : list)
+ {
+ final String parentName = results.get(cate.getParentId());
+ if(StringUtils.isBlank(parentName)) {
+ results.put(cate.getCateId(), cate.getCateName());
+ }else{
+ results.put(cate.getCateId(), String.format("%s/%s", parentName, cate.getCateName()));
+ }
+ }
+ return results;
+ }
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/impl/IscServiceServiceImpl.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/impl/IscServiceServiceImpl.java
new file mode 100644
index 000000000..4bbff4b59
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/service/impl/IscServiceServiceImpl.java
@@ -0,0 +1,236 @@
+package com.ruoyi.isc.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.core.map.MapUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.constant.IscConstants;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.mybatisplus.core.ServicePlusImpl;
+import com.ruoyi.common.core.page.PagePlus;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.FullPathUtils;
+import com.ruoyi.common.utils.PageUtils;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.isc.domain.IscService;
+import com.ruoyi.isc.domain.bo.IscAuditBo;
+import com.ruoyi.isc.domain.bo.IscServiceBo;
+import com.ruoyi.isc.domain.vo.IscServiceVo;
+import com.ruoyi.isc.mapper.IscServiceMapper;
+import com.ruoyi.isc.service.IIscServiceCateService;
+import com.ruoyi.isc.service.IIscServiceService;
+import com.ruoyi.isc.utils.RouteUtils;
+import com.ruoyi.isc.utils.beans.IscRouteDefinition;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 服务信息Service业务层处理
+ *
+ * @author Wenchao Gong
+ * @date 2021-08-22
+ */
+@Service
+public class IscServiceServiceImpl extends ServicePlusImpl implements IIscServiceService {
+
+ @Resource
+ private IIscServiceCateService cateService;
+
+ @PostConstruct
+ public void init() {
+ refreshRoutes();
+ }
+
+ @Override
+ public IscServiceVo queryById(Long serviceId)
+ {
+ final IscServiceVo vo = getVoById(serviceId);
+ genVoInfo(Arrays.asList(vo));
+ return vo;
+ }
+
+ @Override
+ public TableDataInfo queryPageList(IscServiceBo bo)
+ {
+ PagePlus result = pageVo(PageUtils.buildPagePlus(), buildQueryWrapper(bo));
+ genVoInfo(result.getRecordsVo());
+ return PageUtils.buildDataInfo(result);
+ }
+
+ @Override
+ public List queryList(IscServiceBo bo)
+ {
+ final List results = listVo(buildQueryWrapper(bo));
+ genVoInfo(results);
+ return results;
+ }
+
+ private void genVoInfo(List results)
+ {
+ if (CollectionUtils.isNotEmpty(results))
+ {
+ Map cateNameMap = cateService.batchCateName(results.stream()
+ .map(IscServiceVo::getCateFullPath).collect(Collectors.toSet()));
+ for (IscServiceVo result : results)
+ {
+ result.setCateName(cateNameMap.get(result.getCateFullPath()));
+ }
+ }
+ }
+
+ private LambdaQueryWrapper buildQueryWrapper(IscServiceBo bo)
+ {
+ Map params = bo.getParams();
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.like(StringUtils.isNotBlank(bo.getServiceName()), IscService::getServiceName, bo.getServiceName());
+ lqw.eq(StringUtils.isNotBlank(bo.getOnlineStatus()), IscService::getOnlineStatus, bo.getOnlineStatus());
+ lqw.eq(StringUtils.isNotBlank(bo.getStatus()), IscService::getStatus, bo.getStatus());
+ lqw.eq(StringUtils.isNotBlank(bo.getEnabled()), IscService::getEnabled, bo.getEnabled());
+ lqw.ge(StringUtils.isNotBlank(bo.getCateFullPath()), IscService::getCateFullPath, bo.getCateFullPath());
+ lqw.le(StringUtils.isNotBlank(bo.getCateFullPath()), IscService::getCateFullPath, FullPathUtils.genMaxFullPath(bo.getCateFullPath()));
+ Long userId = SecurityUtils.getUserId();
+ lqw.eq(!SecurityUtils.isAdmin(userId), IscService::getUserId, userId);
+ return lqw;
+ }
+
+ @Override
+ public Boolean insertByBo(IscServiceBo bo)
+ {
+ IscService add = BeanUtil.toBean(bo, IscService.class);
+ validEntityBeforeSave(add);
+ add.setUserId(SecurityUtils.getUserId());
+ add.setOnlineStatus(IscConstants.ONLINE_STATUS_ON);
+ add.setStatus(Constants.AUDIT_WAIT);
+ return save(add);
+ }
+
+ @Override
+ public Boolean updateByBo(IscServiceBo bo)
+ {
+ IscService update = BeanUtil.toBean(bo, IscService.class);
+ validEntityBeforeSave(update);
+ boolean result = updateById(update);
+ RouteUtils.updateRoute(RouteUtils.generateRoute(update));
+ return result;
+ }
+
+ /**
+ * 保存前的数据校验
+ *
+ * @param entity 实体类数据
+ */
+ private void validEntityBeforeSave(IscService entity)
+ {
+ //TODO 做一些数据校验,如唯一约束
+ }
+
+ @Override
+ public Boolean deleteWithValidByIds(Collection ids, Boolean isValid)
+ {
+ if (isValid)
+ {
+ //TODO 做一些业务上的校验,判断是否需要校验
+ }
+ boolean result = removeByIds(ids);
+ RouteUtils.deleteRoute(ids.stream().map(String::valueOf).collect(Collectors.toSet()));
+ return result;
+ }
+
+ @Override
+ public Map getNameMap(Collection serviceIds)
+ {
+ if(CollectionUtil.isEmpty(serviceIds)) {
+ return MapUtil.empty();
+ }
+ return list(Wrappers.lambdaQuery()
+ .select(IscService::getServiceId, IscService::getServiceName)
+ .in(IscService::getServiceId, serviceIds)).stream()
+ .collect(Collectors.toMap(IscService::getServiceId, IscService::getServiceName));
+ }
+
+ @Override
+ public List> genServiceTree()
+ {
+ return genServiceTree(CollectionUtil.newHashSet());
+ }
+
+ @Override
+ public List> genServiceTree(Set exitsIds)
+ {
+ List serviceList = list(Wrappers.lambdaQuery()
+ .select(IscService::getServiceId, IscService::getServiceName, IscService::getCateFullPath)
+ .eq(IscService::getStatus, Constants.AUDIT_PASS)
+ .eq(IscService::getEnabled, UserConstants.DICT_NORMAL)
+ .orderByDesc(IscService::getUpdateTime));
+ return cateService.genCateTree(cateService.selectCateList(), serviceList, exitsIds);
+ }
+
+ @Override
+ public Boolean audit(IscAuditBo bo)
+ {
+ checkAuditBO(bo);
+ boolean result = true;
+ boolean pass = Constants.AUDIT_PASS.equals(bo.getStatus());
+ Collection routes = pass ? ListUtil.list(false) : null;
+ for (Long id : bo.getIds())
+ {
+ IscService service = getOne(Wrappers.lambdaQuery()
+ .eq(IscService::getServiceId, id)
+ .eq(IscService::getStatus, Constants.AUDIT_WAIT), false);
+ if(Objects.isNull(service)) {
+ continue;
+ }
+ result = updateById(new IscService().setServiceId(id).setStatus(bo.getStatus())
+ .setAuditMind(bo.getRemark()));
+ if(pass) {
+ //生成路由
+ routes.add(RouteUtils.generateRoute(service));
+ }
+ }
+ if(pass) {
+ //保存路由信息
+ RouteUtils.saveRoute(routes);
+ }
+ return result;
+ }
+
+ @Override
+ public void checkAuditBO(IscAuditBo bo)
+ {
+ switch (bo.getStatus()) {
+ case Constants.AUDIT_PASS:
+ break;
+ case Constants.AUDIT_REJECT:
+ Assert.notBlank(bo.getRemark(), () -> new ServiceException("审核意见不能为空"));
+ break;
+ default:
+ throw new ServiceException("审核状态异常");
+ }
+ }
+
+ @Override
+ public void refreshRoutes()
+ {
+ final List serviceList = list(Wrappers.lambdaQuery()
+ .select(IscService::getServiceId, IscService::getServiceAddr, IscService::getHiddenParams, IscService::getRequestMethod)
+ .eq(IscService::getStatus, Constants.AUDIT_PASS));
+ List routes = new ArrayList<>();
+ for (IscService service : serviceList)
+ {
+ routes.add(RouteUtils.generateRoute(service));
+ }
+ RouteUtils.refreshRoute(routes);
+ }
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/utils/RouteUtils.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/utils/RouteUtils.java
new file mode 100644
index 000000000..d13a5ab27
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/utils/RouteUtils.java
@@ -0,0 +1,330 @@
+package com.ruoyi.isc.utils;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.date.DateUtil;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import com.ruoyi.isc.common.constant.IscRedisKeys;
+import com.ruoyi.isc.common.utils.GatewayUtils;
+import com.ruoyi.isc.common.utils.beans.IscRule;
+import com.ruoyi.isc.common.utils.beans.TopicMsg;
+import com.ruoyi.isc.common.utils.beans.TopicMsg.Type;
+import com.ruoyi.isc.domain.IscAppService;
+import com.ruoyi.isc.domain.IscService;
+import com.ruoyi.isc.utils.beans.*;
+import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RMap;
+import org.redisson.api.RTopic;
+import org.redisson.api.RedissonClient;
+import org.redisson.client.codec.Codec;
+import org.redisson.codec.TypedJsonJacksonCodec;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static com.ruoyi.isc.common.constant.IscGatewayContants.*;
+
+/**
+ * @author Wenchao Gong
+ * @date 2021/9/10 14:46
+ */
+@Slf4j
+public class RouteUtils {
+
+ public static final Codec ROUTE_CODES_INSTANCE = new TypedJsonJacksonCodec(String.class, IscRouteDefinition.class);
+ /**
+ * Gateway 虚拟路径前缀
+ */
+ public static final String PATH_PREFIX = "/proxy";
+ private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class);
+
+ /**
+ * 获取虚拟URL Path 部分
+ *
+ * @param serviceAddr 服务地址
+ * @return 虚拟地址Path
+ */
+ public static String genVirtualAddrPath(String serviceAddr)
+ {
+ return PATH_PREFIX + getPathUri(serviceAddr);
+ }
+
+ /**
+ * 返回URI 的Path部分
+ *
+ * @param uri URI
+ * @return path
+ */
+ public static String getPathUri(String uri)
+ {
+ return getURI(uri).getPath();
+ }
+
+ /**
+ * 通过 URI字符串 生成URI对象
+ *
+ * @param uri URI字符串
+ * @return URI
+ */
+ public static URI getURI(String uri)
+ {
+ try {
+ return new URI(uri);
+ } catch (URISyntaxException e)
+ {
+ throw new ServiceException("URL格式异常");
+ }
+ }
+
+ /**
+ * 发布通道消息
+ *
+ * @param channelKey 通道key
+ * @param msg 发送数据
+ * @param consumer 自定义处理
+ */
+ public static void publish(String channelKey, T msg, Consumer consumer) {
+ RTopic topic = CLIENT.getTopic(channelKey);
+ topic.publish(msg);
+ consumer.accept(msg);
+ }
+
+ /**
+ * 发布路由刷新通知
+ * @param consumer 发送消息后执行
+ */
+ public static void sendRefreshRouteToGateway(String id, Type type, Consumer consumer) {
+ publish(TOPIC_GATEWAY_REFRESH_ROUTE, new TopicMsg(id, DateUtil.now(), type), consumer);
+ }
+
+ /**
+ * 发布规则通知
+ * @param consumer 发送消息后执行
+ */
+ public static void sendRuleNoticeToGateway(String id, Type type, Consumer consumer) {
+ publish(TOPIC_GATEWAY_RULE, new TopicMsg(id, DateUtil.now(), type), consumer);
+ }
+
+ /**
+ * 刷新路由信息 先清空再批量新增
+ *
+ * @param routes 路由信息
+ * @return 是否成功
+ */
+ public static boolean refreshRoute(List routes)
+ {
+ Map routeMap = routes.stream().collect(Collectors.toMap(IscRouteDefinition::getId,
+ Function.identity(), (o1, o2) -> o2));
+ final RMap map = CLIENT.getMap(IscRedisKeys.KEY_ROUTES, ROUTE_CODES_INSTANCE);
+ map.clear();
+ map.putAll(routeMap);
+ sendRefreshRouteToGateway(null, TopicMsg.Type.REFRESH,
+ (msg) -> log.info("路由刷新完成,通知网关刷新路由![{}]", msg.getMsg()));
+ return true;
+ }
+
+ /**
+ * 保存路由信息
+ *
+ * @param routes 路由信息
+ * @return 是否成功
+ */
+ public static boolean saveRoute(Collection routes) {
+ return saveRoute(routes, null);
+ }
+
+ /**
+ * 保存路由信息
+ *
+ * @param routes 路由信息
+ * @param consumer 回调操作
+ * @return 是否成功
+ */
+ public static boolean saveRoute(Collection routes, Consumer consumer)
+ {
+ if(CollectionUtil.isEmpty(routes)) {
+ return true;
+ }
+ final RMap map = CLIENT.getMap(IscRedisKeys.KEY_ROUTES, ROUTE_CODES_INSTANCE);
+ Map collect = routes.stream().collect(Collectors.toMap(IscRouteDefinition::getId, Function.identity()));
+ map.putAll(collect);
+ final String id = String.join(",", collect.keySet());
+ Type type = Type.UPDATE;
+ if(Objects.isNull(consumer)) {
+ type = Type.ADD;
+ consumer = (msg) -> log.info("保存[{}]路由[{}]完成,通知网关刷新路由![{}]", msg.getType(), msg.getId(), msg.getMsg());
+ }
+ sendRefreshRouteToGateway(id, type, consumer);
+ return true;
+ }
+
+ /**
+ * 删除路由信息
+ *
+ * @param routeIds 路由ID
+ * @return 是否成功
+ */
+ public static boolean deleteRoute(Collection routeIds)
+ {
+ final RMap map = CLIENT.getMap(IscRedisKeys.KEY_ROUTES, ROUTE_CODES_INSTANCE);
+ final String[] ids = routeIds.toArray(new String[0]);
+ map.fastRemove(ids);
+ final String id = String.join(",", routeIds);
+ sendRefreshRouteToGateway(id, Type.DELETE,
+ (msg) -> log.info("删除[{}]路由[{}]完成,通知网关刷新路由![{}]", msg.getType(), msg.getId(), msg.getMsg()));
+ return true;
+ }
+
+ /**
+ * 更新路由信息
+ *
+ * @param route 路由信息
+ * @return 是否成功
+ */
+ public static boolean updateRoute(IscRouteDefinition route)
+ {
+ return saveRoute(Collections.singletonList(route),
+ (msg) -> log.info("保存[{}]路由[{}]完成,通知网关刷新路由![{}]", msg.getType(), msg.getId(), msg.getMsg()));
+ }
+
+ /**
+ * 生成 路由信息
+ *
+ * @param service 服务信息
+ * @return 路由信息
+ */
+ public static IscRouteDefinition generateRoute(IscService service)
+ {
+ IscRouteDefinition route = new IscRouteDefinition();
+ route.setId(String.valueOf(service.getServiceId()));
+ final String serviceAddr = service.getServiceAddr();
+ route.setUri(getURI(serviceAddr.substring(0, serviceAddr.length() - getPathUri(serviceAddr).length())));
+
+ //断言
+ IscPredicateDefinition methodPredicate = new IscPredicateDefinition();
+ methodPredicate.setName("Method");
+ methodPredicate.getArgs().put("methods", service.getRequestMethod());
+
+ IscPredicateDefinition pathPredicate = new IscPredicateDefinition();
+ pathPredicate.setName("Path");
+ pathPredicate.getArgs().put("pattern", genVirtualAddrPath(service.getServiceAddr()));
+ route.setPredicates(Arrays.asList(methodPredicate, pathPredicate));
+
+ //过滤器
+ List filters = new ArrayList<>();
+ IscFilterDefinition stripPrefixFilter = new IscFilterDefinition();
+ stripPrefixFilter.setName("StripPrefix");
+ stripPrefixFilter.getArgs().put("parts", "1");
+ filters.add(stripPrefixFilter);
+
+ IscFilterDefinition retryFilter = new IscFilterDefinition();
+ retryFilter.setName("Retry");
+ retryFilter.getArgs().put("retries", "1");
+ filters.add(retryFilter);
+ route.setFilters(filters);
+
+ //其他信息
+ Map metadata = route.getMetadata();
+ metadata.put(CONFIG_ACCESS_KEY_NAME_KEY, ACCESS_KEY_NAME_DEFAULT);
+ metadata.put(CONFIG_ADD_PARAM_KEY, service.getHiddenParams());
+ return route;
+ }
+
+ /**
+ * 生成 服务对应AK调用规则
+ *
+ * @param appService 服务申请信息
+ * @param accessKey AccessKey
+ * @return 规则信息
+ */
+ public static IscRule generateRule(IscAppService appService, String accessKey)
+ {
+ IscRule rule = new IscRule();
+ rule.setId(GatewayUtils.getRouteKey(accessKey, appService.getServiceId()));
+ rule.setExpire(appService.getEndTime());
+ rule.setDaysLimit(appService.getQuotaDays());
+ rule.setHoursLimit(appService.getQuotaHours());
+ rule.setMinutesLimit(appService.getQuotaMinutes());
+ rule.setSecondsLimit(appService.getQuotaSeconds());
+ return rule;
+ }
+
+ /**
+ * 刷新规则信息 先清空再批量新增
+ *
+ * @param rules 规则信息
+ * @return 是否成功
+ */
+ public static boolean refreshRules(List rules)
+ {
+ Map ruleMap = rules.stream().collect(Collectors.toMap(IscRule::getId, Function.identity(), (o1, o2) -> o2));
+ final RMap map = CLIENT.getMap(IscRedisKeys.KEY_RULES, RULE_CODES_INSTANCE);
+ map.clear();
+ map.putAll(ruleMap);
+ final String id = String.join(",", ruleMap.keySet());
+ sendRuleNoticeToGateway(id, Type.REFRESH,
+ (msg) -> log.info("刷新[{}]所有规则[{}]完成,通知网关刷新规则![{}]", msg.getType(), msg.getId(), msg.getMsg()));
+ return true;
+ }
+
+ /**
+ * 保存规则信息
+ *
+ * @param rule 规则信息
+ * @return 是否成功
+ */
+ public static boolean saveRule(IscRule rule) {
+ return saveRule(rule, null);
+ }
+
+ /**
+ * 保存规则信息
+ *
+ * @param rule 规则信息
+ * @param consumer 回调
+ * @return 是否成功
+ */
+ public static boolean saveRule(IscRule rule, Consumer consumer)
+ {
+ final RMap map = CLIENT.getMap(IscRedisKeys.KEY_RULES, RULE_CODES_INSTANCE);
+ map.put(rule.getId(), rule);
+ Type type = Type.UPDATE;
+ if(Objects.isNull(consumer)) {
+ type = Type.ADD;
+ consumer = (msg) -> log.info("新增[{}]规则[{}]完成,通知网关刷新规则![{}]", msg.getType(), msg.getId(), msg.getMsg());
+ }
+ sendRuleNoticeToGateway(rule.getId(), type, consumer);
+ return true;
+ }
+
+ /**
+ * 删除规则信息
+ *
+ * @param ruleId 规则ID
+ * @return 是否成功
+ */
+ public static boolean deleteRule(String ruleId)
+ {
+ final RMap map = CLIENT.getMap(IscRedisKeys.KEY_RULES, RULE_CODES_INSTANCE);
+ map.remove(ruleId);
+ sendRuleNoticeToGateway(ruleId, Type.DELETE,
+ (msg) -> log.info("删除[{}]规则[{}]完成,通知网关刷新规则![{}]", msg.getType(), msg.getId(), msg.getMsg()));
+ return true;
+ }
+
+ /**
+ * 更新规则信息
+ *
+ * @param rule 规则信息
+ * @return 是否成功
+ */
+ public static boolean updateRule(IscRule rule)
+ {
+ return saveRule(rule,
+ (msg) -> log.info("更新[{}]规则[{}]完成,通知网关刷新规则![{}]", msg.getType(), msg.getId(), msg.getMsg()));
+ }
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/utils/beans/IscFilterDefinition.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/utils/beans/IscFilterDefinition.java
new file mode 100644
index 000000000..ffee7ab81
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/utils/beans/IscFilterDefinition.java
@@ -0,0 +1,22 @@
+package com.ruoyi.isc.utils.beans;
+
+import lombok.Data;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotNull;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @author Wenchao Gong
+ * @date 2021-09-28
+ */
+@Data
+@Validated
+public class IscFilterDefinition {
+
+ @NotNull
+ private String name;
+
+ private Map args = new LinkedHashMap<>();
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/utils/beans/IscPredicateDefinition.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/utils/beans/IscPredicateDefinition.java
new file mode 100644
index 000000000..efcee83c1
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/utils/beans/IscPredicateDefinition.java
@@ -0,0 +1,22 @@
+package com.ruoyi.isc.utils.beans;
+
+import lombok.Data;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotNull;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @author Wenchao Gong
+ * @date 2021-09-28
+ */
+@Data
+@Validated
+public class IscPredicateDefinition {
+
+ @NotNull
+ private String name;
+
+ private Map args = new LinkedHashMap<>();
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/utils/beans/IscRouteDefinition.java b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/utils/beans/IscRouteDefinition.java
new file mode 100644
index 000000000..f5cbb42d2
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/java/com/ruoyi/isc/utils/beans/IscRouteDefinition.java
@@ -0,0 +1,38 @@
+package com.ruoyi.isc.utils.beans;
+
+import lombok.Data;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Wenchao Gong
+ * @date 2021-09-28
+ */
+@Data
+@Validated
+public class IscRouteDefinition {
+
+ private String id;
+
+ @NotEmpty
+ @Valid
+ private List predicates = new ArrayList<>();
+
+ @Valid
+ private List filters = new ArrayList<>();
+
+ @NotNull
+ private URI uri;
+
+ private Map metadata = new HashMap<>();
+
+ private int order = 0;
+}
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/resources/mapper/isc/IscAppServiceApplyMapper.xml b/ruoyi-isc-parent/ruoyi-isc/src/main/resources/mapper/isc/IscAppServiceApplyMapper.xml
new file mode 100644
index 000000000..6951d02aa
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/resources/mapper/isc/IscAppServiceApplyMapper.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/resources/mapper/isc/IscAppServiceMapper.xml b/ruoyi-isc-parent/ruoyi-isc/src/main/resources/mapper/isc/IscAppServiceMapper.xml
new file mode 100644
index 000000000..599d22355
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/resources/mapper/isc/IscAppServiceMapper.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/resources/mapper/isc/IscApplicationMapper.xml b/ruoyi-isc-parent/ruoyi-isc/src/main/resources/mapper/isc/IscApplicationMapper.xml
new file mode 100644
index 000000000..607808cf4
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/resources/mapper/isc/IscApplicationMapper.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/resources/mapper/isc/IscServiceCateMapper.xml b/ruoyi-isc-parent/ruoyi-isc/src/main/resources/mapper/isc/IscServiceCateMapper.xml
new file mode 100644
index 000000000..c08b45e9d
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/resources/mapper/isc/IscServiceCateMapper.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ruoyi-isc-parent/ruoyi-isc/src/main/resources/mapper/isc/IscServiceMapper.xml b/ruoyi-isc-parent/ruoyi-isc/src/main/resources/mapper/isc/IscServiceMapper.xml
new file mode 100644
index 000000000..e7924dd40
--- /dev/null
+++ b/ruoyi-isc-parent/ruoyi-isc/src/main/resources/mapper/isc/IscServiceMapper.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ruoyi-oss/pom.xml b/ruoyi-oss/pom.xml
index adbb55502..c2d847318 100644
--- a/ruoyi-oss/pom.xml
+++ b/ruoyi-oss/pom.xml
@@ -42,6 +42,10 @@
org.slf4j
slf4j-log4j12
+
+ org.bouncycastle
+ bcprov-jdk15on
+
diff --git a/ruoyi-system/pom.xml b/ruoyi-system/pom.xml
index bf0a5c9c2..d3a596371 100644
--- a/ruoyi-system/pom.xml
+++ b/ruoyi-system/pom.xml
@@ -16,7 +16,11 @@
-
+
+ org.jsoup
+ jsoup
+ 1.13.1
+
com.ruoyi
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java
index ea6afa2e5..bd8c5ffbf 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java
@@ -1,7 +1,7 @@
package com.ruoyi.system.service;
+import cn.hutool.core.lang.tree.Tree;
import com.baomidou.mybatisplus.extension.service.IService;
-import com.ruoyi.common.core.domain.TreeSelect;
import com.ruoyi.common.core.domain.entity.SysDept;
import java.util.List;
@@ -20,21 +20,13 @@ public interface ISysDeptService extends IService {
*/
List selectDeptList(SysDept dept);
- /**
- * 构建前端所需要树结构
- *
- * @param depts 部门列表
- * @return 树结构列表
- */
- List buildDeptTree(List depts);
-
/**
* 构建前端所需要下拉树结构
*
* @param depts 部门列表
* @return 下拉树结构列表
*/
- List buildDeptTreeSelect(List depts);
+ List> buildDeptTreeSelect(List depts);
/**
* 根据角色ID查询部门树信息
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java
index eea882e67..bc4c960af 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java
@@ -1,7 +1,7 @@
package com.ruoyi.system.service;
+import cn.hutool.core.lang.tree.Tree;
import com.baomidou.mybatisplus.extension.service.IService;
-import com.ruoyi.common.core.domain.TreeSelect;
import com.ruoyi.common.core.domain.entity.SysMenu;
import com.ruoyi.system.domain.vo.RouterVo;
@@ -64,21 +64,13 @@ public interface ISysMenuService extends IService {
*/
List buildMenus(List menus);
- /**
- * 构建前端所需要树结构
- *
- * @param menus 菜单列表
- * @return 树结构列表
- */
- List buildMenuTree(List menus);
-
/**
* 构建前端所需要下拉树结构
*
* @param menus 菜单列表
* @return 下拉树结构列表
*/
- List buildMenuTreeSelect(List menus);
+ List> buildMenuTreeSelect(List menus);
/**
* 根据菜单ID查询信息
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java
index 45c6faed6..62ccbec6f 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java
@@ -1,11 +1,11 @@
package com.ruoyi.system.service.impl;
import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.tree.Tree;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.ruoyi.common.annotation.DataScope;
import com.ruoyi.common.constant.UserConstants;
-import com.ruoyi.common.core.domain.TreeSelect;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
@@ -14,6 +14,7 @@ import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
+import com.ruoyi.common.utils.TreeUtils;
import com.ruoyi.system.mapper.SysDeptMapper;
import com.ruoyi.system.mapper.SysRoleMapper;
import com.ruoyi.system.mapper.SysUserMapper;
@@ -21,10 +22,8 @@ import com.ruoyi.system.service.ISysDeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.stream.Collectors;
/**
* 部门管理 服务实现
@@ -52,32 +51,6 @@ public class SysDeptServiceImpl extends ServicePlusImpl buildDeptTree(List depts) {
- List returnList = new ArrayList();
- List tempList = new ArrayList();
- for (SysDept dept : depts) {
- tempList.add(dept.getDeptId());
- }
- for (SysDept dept : depts) {
- // 如果是顶级节点, 遍历该父节点的所有子节点
- if (!tempList.contains(dept.getParentId())) {
- recursionFn(depts, dept);
- returnList.add(dept);
- }
- }
- if (returnList.isEmpty()) {
- returnList = depts;
- }
- return returnList;
- }
-
/**
* 构建前端所需要下拉树结构
*
@@ -85,9 +58,13 @@ public class SysDeptServiceImpl extends ServicePlusImpl buildDeptTreeSelect(List depts) {
- List deptTrees = buildDeptTree(depts);
- return deptTrees.stream().map(TreeSelect::new).collect(Collectors.toList());
+ public List> buildDeptTreeSelect(List depts) {
+ return TreeUtils.build(depts, (dept, tree) -> {
+ tree.setId(dept.getDeptId());
+ tree.setParentId(dept.getParentId());
+ tree.setName(dept.getDeptName());
+ tree.setWeight(dept.getOrderNum());
+ });
}
/**
@@ -273,37 +250,4 @@ public class SysDeptServiceImpl extends ServicePlusImpl list, SysDept t) {
- // 得到子节点列表
- List childList = getChildList(list, t);
- t.setChildren(childList);
- for (SysDept tChild : childList) {
- if (hasChild(list, tChild)) {
- recursionFn(list, tChild);
- }
- }
- }
-
- /**
- * 得到子节点列表
- */
- private List getChildList(List list, SysDept t) {
- List tlist = new ArrayList();
- for (SysDept n : list) {
- if (StringUtils.isNotNull(n.getParentId()) && n.getParentId().longValue() == t.getDeptId().longValue()) {
- tlist.add(n);
- }
- }
- return tlist;
- }
-
- /**
- * 判断是否有子节点
- */
- private boolean hasChild(List