Pre Merge pull request !108 from wenchaogong/develop

This commit is contained in:
wenchaogong 2021-10-31 13:28:41 +00:00 committed by Gitee
commit a08f703e45
129 changed files with 10154 additions and 263 deletions

75
pom.xml
View File

@ -29,15 +29,19 @@
<mybatis-plus.version>3.4.3.4</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.7.15</hutool.version>
<guava.version>29.0-jre</guava.version>
<feign.version>3.0.3</feign.version>
<gateway.version>3.0.3</gateway.version>
<feign-okhttp.version>11.6</feign-okhttp.version>
<okhttp.version>4.9.1</okhttp.version>
<spring-boot-admin.version>2.5.2</spring-boot-admin.version>
<redisson.version>3.16.3</redisson.version>
<okio.version>2.8.0</okio.version>
<lock4j.version>2.2.1</lock4j.version>
<dynamic-ds.version>3.4.1</dynamic-ds.version>
<tlog.version>1.3.3</tlog.version>
<xxl-job.version>2.3.0</xxl-job.version>
<wildfly-common.version>1.5.2.Final</wildfly-common.version>
<!-- jdk11 缺失依赖 jaxb-->
<jaxb.version>3.0.1</jaxb.version>
@ -53,7 +57,6 @@
<docker.registry.host>http://${docker.registry.url}:2375</docker.registry.host>
<docker.namespace>ruoyi</docker.namespace>
<docker.plugin.version>1.2.2</docker.plugin.version>
</properties>
<!-- 依赖声明 -->
@ -88,6 +91,7 @@
</exclusions>
</dependency>
<!-- 指定 swagger-annotations 版本冲突-->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
@ -168,6 +172,18 @@
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>${gateway.version}</version>
</dependency>
<!-- @deprecated 由于使用人数较少 决定与 3.4.0 版本移除 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
@ -178,6 +194,10 @@
<artifactId>feign-core</artifactId>
<groupId>io.github.openfeign</groupId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- @deprecated 由于使用人数较少 决定与 3.4.0 版本移除 -->
@ -186,6 +206,11 @@
<artifactId>feign-okhttp</artifactId>
<version>${feign-okhttp.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
<version>${okio.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
@ -204,7 +229,24 @@
<version>${spring-boot-admin.version}</version>
</dependency>
<!-- redis 缓存操作 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
<version>${spring-boot.version}</version>
</dependency>
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>${redisson.version}</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
@ -216,11 +258,24 @@
<version>${lock4j.version}</version>
</dependency>
<!-- undertow 中的wildfly-common包冲突处理 -->
<dependency>
<groupId>org.wildfly.common</groupId>
<artifactId>wildfly-common</artifactId>
<version>${wildfly-common.version}</version>
</dependency>
<!-- xxl-job-core -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>${xxl-job.version}</version>
<exclusions>
<exclusion>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@ -306,6 +361,23 @@
<version>${ruoyi-vue-plus.version}</version>
</dependency>
<!-- isc模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-isc-parent</artifactId>
<version>${ruoyi-vue-plus.version}</version>
</dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-isc-common</artifactId>
<version>${ruoyi-vue-plus.version}</version>
</dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-isc</artifactId>
<version>${ruoyi-vue-plus.version}</version>
</dependency>
<!-- demo模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
@ -327,6 +399,7 @@
<module>ruoyi-demo</module>
<module>ruoyi-extend</module>
<module>ruoyi-oss</module>
<module>ruoyi-isc-parent</module>
</modules>
<packaging>pom</packaging>

View File

@ -52,10 +52,11 @@
<artifactId>ruoyi-job</artifactId>
</dependency>
<!--
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-oss</artifactId>
</dependency>
</dependency>-->
<!-- 代码生成-->
<dependency>
@ -63,6 +64,12 @@
<artifactId>ruoyi-generator</artifactId>
</dependency>
<!-- isc模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-isc</artifactId>
</dependency>
<!-- demo模块 -->
<dependency>
<groupId>com.ruoyi</groupId>

View File

@ -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<List<TreeSelect>> treeselect(SysDept dept) {
public AjaxResult<List<Tree<Long>>> treeselect(SysDept dept) {
List<SysDept> depts = deptService.selectDeptList(dept);
return AjaxResult.success(deptService.buildDeptTreeSelect(depts));
}

View File

@ -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<List<TreeSelect>> treeselect(SysMenu menu) {
public AjaxResult<List<Tree<Long>>> treeselect(SysMenu menu) {
List<SysMenu> menus = menuService.selectMenuList(menu, getUserId());
return AjaxResult.success(menuService.buildMenuTreeSelect(menus));
}

View File

@ -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
# 数据库索引

View File

@ -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

View File

@ -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
# 数据库索引

View File

@ -2,8 +2,8 @@
<configuration>
<property name="log.path" value="./logs"/>
<property name="console.log.pattern"
value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>
value="%red(%d{yyyy-MM-dd HH:mm:ss}) %highlight(%-5level) %green([%thread]) %boldMagenta(%logger{36}) - %msg%n"/>
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{36} - %msg%n"/>
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
@ -31,7 +31,7 @@
<level>INFO</level>
</filter>
</appender>
<!-- 系统日志输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-info.log</file>
@ -54,7 +54,7 @@
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-error.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
@ -76,7 +76,7 @@
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 用户访问日志输出 -->
<appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-user.log</file>
@ -90,7 +90,7 @@
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系统模块日志级别控制 -->
<logger name="com.ruoyi" level="info" />
<!-- Spring日志级别控制 -->
@ -99,16 +99,16 @@
<root level="info">
<appender-ref ref="console" />
</root>
<!--系统操作日志-->
<root level="info">
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
<appender-ref ref="file_console" />
</root>
<!--系统用户操作日志-->
<logger name="sys-user" level="info">
<appender-ref ref="sys-user"/>
</logger>
</configuration>
</configuration>

View File

@ -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_

View File

@ -46,6 +46,10 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<!-- JSON工具类 -->
<dependency>

View File

@ -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";
}

View File

@ -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";
}

View File

@ -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<TreeSelect> 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());
}
}

View File

@ -55,4 +55,9 @@ public enum BusinessType {
* 清空数据
*/
CLEAN,
/**
* 审核
*/
AUDIT
}

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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 <T>
* @return
*/
public static <T> List<Tree<Long>> build(List<T> list, NodeParser<T, Long> nodeParser)
{
return TreeUtil.build(list, DEFAULT_PARENT_ID, DEFAULT_TREE_NODE_CONFIG, nodeParser);
}
/**
* 树构建
*
* @param <T> 转换的实体 为数据源里的对象类型
* @param <E> ID类型
* @param list 源数据集合
* @param parentId 最顶层父id值 一般为 0 之类
* @param nodeParser 转换器
* @param needExistField 是否需要 已存在字段
* @return List
*/
public static <T, E> List<Tree<E>> build(List<T> list, E parentId, NodeParser<T, E> nodeParser,
boolean needExistField)
{
final List<Tree<E>> treeList = CollUtil.newArrayList();
final TreeNodeConfig treeNodeConfig = DEFAULT_TREE_NODE_CONFIG;
Tree<E> tree;
for (T obj : list) {
tree = new Tree<>(treeNodeConfig);
nodeParser.parse(obj, tree);
treeList.add(tree);
}
List<Tree<E>> finalTreeList = CollUtil.newArrayList();
for (Tree<E> 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 <T> void innerBuild(List<Tree<T>> treeNodes, Tree<T> 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<T> childNode : treeNodes) {
if (parentNode.getId().equals(childNode.getParentId())) {
List<Tree<T>> 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);
}
}
}
}

View File

@ -14,6 +14,7 @@
<modules>
<module>ruoyi-monitor-admin</module>
<module>ruoyi-xxl-job-admin</module>
<module>ruoyi-isc-gateway</module>
</modules>
</project>

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.3.0</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-isc-gateway</artifactId>
<dependencies>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-isc-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -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 启动成功" );
}
}

View File

@ -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<List<Long>> redisScript,
ConfigurationService configurationService, DefaultRedisScript<Long> 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 <T> void subscribe(String channelKey, Class<T> clazz, Consumer<T> consumer) {
RTopic topic = client.getTopic(channelKey);
topic.addListener(clazz, (channel, msg) -> consumer.accept(msg));
}
@Override
public void setApplicationEventPublisher(@NonNull ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
}

View File

@ -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<Long> limitScript() {
DefaultRedisScript<Long> 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();
}
}

View File

@ -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<Void> 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<Integer> 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<String, Object> result(Optional<Integer> code, String message, RequestPath path) {
final HashMap<String, Object> 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;
}
}

View File

@ -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<RouteDefinition> getRouteDefinitions()
{
RMap<String, RouteDefinition> map = redissonClient.getMap(KEY_ROUTES, ROUTE_CODES_INSTANCE);
log.debug("Routes from redis size:[{}]!", map.size());
return Flux.fromIterable(map.values());
}
@Override
public Mono<Void> save(Mono<RouteDefinition> route)
{
return route.flatMap(definition -> {
RMap<String, RouteDefinition> 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<Void> delete(Mono<String> routeId)
{
return routeId.flatMap(id -> {
RMap<String, RouteDefinition> map = redissonClient.getMap(KEY_ROUTES, ROUTE_CODES_INSTANCE);
map.remove(id);
log.debug("Remove route from redis id:[{}]!", id);
return Mono.empty();
});
}
}

View File

@ -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> 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;
}
}

View File

@ -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]规则不存在";
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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()));
}
}

View File

@ -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)));
}
}

View File

@ -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));
}
}

View File

@ -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<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
final Route route = exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
final Map<String, Object> 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<String, String> 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<Void> handlePostRequest(ServerWebExchange exchange, GatewayFilterChain chain, Route route,
ServerHttpRequest request, AccessKey accessKey,
MultiValueMap<String, String> queryParams) {
//处理 URL AccessKey
handleAccessKey(accessKey, () -> queryParams.get(accessKey.name), URI, false);
ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
final Mono<String> 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<String, String> queryParams) {
if (StrUtil.isBlank(body)) {
Assert.notBlank(accessKey.value, () -> new AkRequireException(accessKey.name));
}
final List<String[]> 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<Mono<String>> rateLimiterAfterSupplier = () -> {
removeParam(accessKey, request, null, queryParams);
final List<String[]> 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<String, String> queryParams) {
final IscRule rule = handleRule(accessKey, () -> Collections.singletonList(jsonObj.get(accessKey.name,
String.class, true)), route, BODY);
Supplier<Mono<String>> 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<Void> handleGetRequest(ServerWebExchange exchange, GatewayFilterChain chain, Route route,
ServerHttpRequest request, AccessKey accessKey,
MultiValueMap<String, String> queryParams) {
final IscRule rule = handleRule(accessKey, () -> queryParams.get(accessKey.name), route, URI);
Supplier<Mono<Void>> 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<ServerHttpRequest, MultiValueMap<String, String>, 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<List<String>> 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<List<String>> 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 <T> 类型
*/
private <T> void removeParam(AccessKey accessKey, ServerHttpRequest request, Supplier<T> removeSupplier,
MultiValueMap<String, String> 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 <U> 参数类型
*/
private <U> void handleHiddenParams(Route route, U result, BiConsumer<Map.Entry<String, Object>, 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<String, Object> next : json.entrySet()) {
mapper.accept(next, result);
}
}
/**
* 限流
*
* @param exchange 当前服务交换器
* @param rule 限流规则
* @param route 路由
* @param rateLimiterAfterSupplier 限流后操作(删除参数添加隐藏参数跳转)
* @param <T> 最终返回类型
* @return rateLimiterAfterSupplier 返回类型
*/
private <T> Mono<T> rateLimiter(ServerWebExchange exchange, IscRule rule, Route route, final int index,
Supplier<Mono<T>> 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<String, String> 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;
}
}

View File

@ -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<List<Long>> script;
private final RedisScript<Long> timeRedisScript;
private final AtomicBoolean initialized = new AtomicBoolean(false);
public CustomerRedisRateLimiter(ReactiveStringRedisTemplate redisTemplate, RedisScript<List<Long>> script,
ConfigurationService configurationService, DefaultRedisScript<Long> timeRedisScript) {
super(redisTemplate, script, configurationService);
this.redisTemplate = redisTemplate;
this.script = script;
this.timeRedisScript = timeRedisScript;
this.initialized.compareAndSet(false, true);
}
public Mono<Response> 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<String> 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<Response> 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<String> keys = getKeys(id);
// The arguments to the LUA script. time() returns unixtime in seconds.
List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "",
Instant.now().getEpochSecond() + "", requestedTokens + "");
// allowed, tokens_left = redis.eval(SCRIPT, keys, args)
Flux<List<Long>> 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<Long>(), (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<Response> isAllowed(Config routeConfig, List<String> 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<String> scriptArgs = Arrays.asList(replenishRate + "", time + "");
// allowed, tokens_left = redis.eval(SCRIPT, keys, args)
Flux<Long> 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<String, String> getHeaders(Config config, Long tokensLeft, TimeUnit timeUnit) {
Map<String, String> 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<String> 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<String> getKeys(String id, int now, TimeUnit timeUnit) {
String tokenKey = "rate_limiter.{" + id + ':' + now + "}." + timeUnit.name();
return Arrays.asList(tokenKey);
}
}

View File

@ -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<Void> modifyBody(ServerWebExchange exchange, GatewayFilterChain chain, Mono<String> 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<DataBuffer> 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<List<String>> valueSupplier) {
return getValue(before, valueSupplier, null);
}
/**
* values 中获取第一个不为空 的值
*
* @param before 获取之前的值 如果不为空直接返回
* @param valueSupplier 值列表生产者
* @param errorMsgSupplier 异常信息生产者
* @param <X>
* @return
*/
public static <X extends RuntimeException> String getValue(String before, Supplier<List<String>> valueSupplier,
Supplier<X> errorMsgSupplier) {
if (StringUtils.hasText(before)) {
return before;
}
final List<String> 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 <T>
* @param <X>
* @return
*/
public static <T, X extends RuntimeException> T getRequiredValue(Supplier<T> valueSupplier,
Supplier<X> 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<String, IscRule> map = client.getMap(KEY_RULES, RULE_CODES_INSTANCE);
return map.get(key);
}
/**
* 断言是否超过结束时间
*
* @param rule
* @param errorMsgSupplier
* @param <X>
*/
public static <X extends RuntimeException> void isBefore(IscRule rule, Supplier<X> errorMsgSupplier) {
Assert.isTrue(Objects.nonNull(rule.getExpire()) && Date.from(Instant.now()).before(rule.getExpire()),
errorMsgSupplier);
}
}

View File

@ -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<String, IscRule> 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;
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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@

View File

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="log.path" value="./logs"/>
<property name="console.log.pattern"
value="%red(%d{yyyy-MM-dd HH:mm:ss}) %highlight(%-5level) %green([%thread]) %boldMagenta(%logger{36}) - %msg%n"/>
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{36} - %msg%n"/>
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${console.log.pattern}</pattern>
<charset>utf-8</charset>
</encoder>
</appender>
<!-- 控制台输出 -->
<appender name="file_console" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-console.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-console.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大 1天 -->
<maxHistory>1</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
<charset>utf-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
</filter>
</appender>
<!-- 系统日志输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-info.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-error.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>ERROR</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 用户访问日志输出 -->
<appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-user.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天回滚 daily -->
<fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系统模块日志级别控制 -->
<logger name="com.ruoyi" level="info" />
<!-- Spring日志级别控制 -->
<logger name="org.springframework" level="warn" />
<root level="info">
<appender-ref ref="console" />
</root>
<!--系统操作日志-->
<root level="info">
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
<appender-ref ref="file_console" />
</root>
<!--系统用户操作日志-->
<logger name="sys-user" level="info">
<appender-ref ref="sys-user"/>
</logger>
</configuration>

View File

@ -28,6 +28,22 @@
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -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();
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,11 +1,8 @@
server:
port: 9090
spring:
security:
user:
name: ruoyi
password: 123456
boot:
admin:
context-path: /admin
profiles:
active: @profiles.active@

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
}

33
ruoyi-isc-parent/pom.xml Normal file
View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.3.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-isc-parent</artifactId>
<packaging>pom</packaging>
<modules>
<module>ruoyi-isc-common</module>
<module>ruoyi-isc</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-isc-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-isc</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ruoyi-isc-parent</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.3.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-isc-common</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -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);
}

View File

@ -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:";
}

View File

@ -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));
}
}

View File

@ -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;
}

View File

@ -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
}
}

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ruoyi-isc-parent</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.3.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-isc</artifactId>
<dependencies>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-isc-common</artifactId>
</dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-system</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -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<IscAppServiceApplyVo> 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<IscAppServiceApplyVo> list = iIscAppServiceApplyService.queryList(bo);
ExcelUtil.exportExcel(list, "应用服务申请信息", IscAppServiceApplyVo.class, response);
}
/**
* 获取应用服务申请信息详细信息
*/
@ApiOperation("获取应用服务申请信息详细信息")
@PreAuthorize("@ss.hasPermi('isc:serviceapply:query')")
@GetMapping("/{applyId}")
public AjaxResult<IscAppServiceApplyVo> 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<Void> audit(@Validated @RequestBody IscAuditBo bo) {
return toAjax(iIscAppServiceApplyService.audit(bo) ? 1 : 0);
}
}

View File

@ -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<IscAppServiceVo> 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<IscAppServiceVo> list = appServiceService.queryList(bo);
ExcelUtil.exportExcel(list, "应用服务", IscAppServiceVo.class, response);
}
/**
* 获取应用服务详细信息
*/
@ApiOperation("获取应用服务详细信息")
@PreAuthorize("@ss.hasPermi('isc:appservice:query')")
@GetMapping("/{appServiceId}")
public AjaxResult<IscAppServiceVo> 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<Void> 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<Void> 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<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] appServiceIds) {
return toAjax(appServiceService.deleteWithValidByIds(Arrays.asList(appServiceIds), true) ? 1 : 0);
}
/**
* 获取应用服务下拉树列表
*/
@GetMapping("/treeselect/{applicationId}")
@ApiOperation("获取应用服务下拉树列表")
public AjaxResult<List<Tree<Long>>> treeselect(@NotNull(message = "应用主键不能为空") @PathVariable Long applicationId)
{
return AjaxResult.success(appServiceService.genAppServiceTree(applicationId));
}
}

View File

@ -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<IscApplicationVo> 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<IscApplicationVo> list = iIscApplicationService.queryList(bo);
ExcelUtil.exportExcel(list, "应用信息", IscApplicationVo.class, response);
}
/**
* 获取应用信息详细信息
*/
@ApiOperation("获取应用信息详细信息")
@PreAuthorize("@ss.hasPermi('isc:application:query')")
@GetMapping("/{applicationId}")
public AjaxResult<IscApplicationVo> 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<Void> 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<Void> 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<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] applicationIds) {
return toAjax(iIscApplicationService.deleteWithValidByIds(Arrays.asList(applicationIds), true) ? 1 : 0);
}
}

View File

@ -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<IscServiceCateVo>> list(@Validated IscServiceCateBo bo)
{
List<IscServiceCateVo> list = cateService.queryList(bo);
return AjaxResult.success(list);
}
/**
* 获取分类下拉树列表
*/
@GetMapping("/treeselect")
@ApiOperation("获取分类下拉树列表")
public AjaxResult<List<Tree<Long>>> treeselect()
{
List<IscServiceCate> 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<IscServiceCateVo> list = cateService.queryList(bo);
ExcelUtil.exportExcel(list, "服务分类", IscServiceCateVo.class, response);
}
/**
* 获取服务分类详细信息
*/
@ApiOperation("获取服务分类详细信息")
@PreAuthorize("@ss.hasPermi('isc:cate:query')")
@GetMapping("/{cateId}")
public AjaxResult<IscServiceCateVo> 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<Void> 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<Void> 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<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] cateIds)
{
return toAjax(cateService.deleteWithValidByIds(Arrays.asList(cateIds), true) ? 1 : 0);
}
}

View File

@ -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<IscServiceVo> 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<IscServiceVo> list = iIscServiceService.queryList(bo);
ExcelUtil.exportExcel(list, "服务信息", IscServiceVo.class, response);
}
/**
* 获取服务信息详细信息
*/
@ApiOperation("获取服务信息详细信息")
@PreAuthorize("@ss.hasPermi('isc:service:query')")
@GetMapping("/{serviceId}")
public AjaxResult<IscServiceVo> 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<Void> 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<Void> 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<Void> 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<Void> audit(@Validated @RequestBody IscAuditBo bo) {
return toAjax(iIscServiceService.audit(bo) ? 1 : 0);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<SysMenu> children = new ArrayList<SysMenu>();
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<Long> ids;
/**
* 审核状态0待审核 1审核通过 2驳回
*/
@ApiModelProperty(value = "审核状态0待审核 1审核通过 2驳回")
@NotBlank(message = "请填写审核结果")
private String status;
/**
* 备注
*/
@ApiModelProperty(value = "备注")
private String remark;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<IscAppServiceApply> {
}

View File

@ -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<IscAppService> {
}

View File

@ -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<IscApplication> {
}

View File

@ -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<IscServiceCate> {
}

View File

@ -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<IscService> {
}

View File

@ -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<IscAppServiceApply, IscAppServiceApplyVo> {
/**
* 查询单个
* @return
*/
IscAppServiceApplyVo queryById(Long applyId);
/**
* 查询列表
*/
TableDataInfo<IscAppServiceApplyVo> queryPageList(IscAppServiceApplyBo bo);
/**
* 查询列表
*/
List<IscAppServiceApplyVo> 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<Long> ids, Boolean isValid);
/**
* 应用服务审核
* @param bo 审核业务数据
* @return 是否审核成功
*/
Boolean audit(IscAuditBo bo);
}

View File

@ -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<IscAppService, IscAppServiceVo> {
/**
* 查询单个
* @return
*/
IscAppServiceVo queryById(Long appServiceId);
/**
* 查询列表
*/
TableDataInfo<IscAppServiceVo> queryPageList(IscAppServiceBo bo);
/**
* 查询列表
*/
List<IscAppServiceVo> 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<Long> ids, Boolean isValid);
/**
* 构造 应用服务树结构
* @param applicationId 应用ID
* @return 应用服务树结构 标识服务是否存在
*/
List<Tree<Long>> genAppServiceTree(Long applicationId);
/**
* 通过 Id集合 返回列表
* @param ids 应用服务ID
* @return 应用服务列表
*/
List<IscAppService> getAppServiceListByIds(Collection<Long> ids);
/**
* 刷新规则信息服务对应AK
*/
void refreshRules();
}

View File

@ -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<IscApplication, IscApplicationVo> {
/**
* 查询单个
* @return
*/
IscApplicationVo queryById(Long applicationId);
/**
* 查询列表
*/
TableDataInfo<IscApplicationVo> queryPageList(IscApplicationBo bo);
/**
* 查询列表
*/
List<IscApplicationVo> 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<Long> ids, Boolean isValid);
/**
* 通过应用ID集合批量查询 应用名称
* @param applicationIds 应用ID集合
* @return 应用名称Map
*/
Map<Long, String> getNameMap(Collection<Long> applicationIds);
}

View File

@ -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<IscServiceCate, IscServiceCateVo> {
/**
* 查询单个
* @return
*/
IscServiceCateVo queryById(Long cateId);
/**
* 查询列表
*/
List<IscServiceCateVo> 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<Long> ids, Boolean isValid);
/**
* 获取分类信息
* @return 分类列表
*/
List<IscServiceCate> selectCateList();
/**
* 批量获取分类名称
* @param fullPathList
* @return
*/
Map<String, String> batchCateName(Set<String> fullPathList);
/**
* 组装 服务分类 树结构
* @param cates 所有分类
* @return 树结构信息
*/
List<Tree<Long>> genCateTree(List<IscServiceCate> cates);
/**
* 组装 服务分类 树结构
* @param cates 所有分类
* @param serviceList 可用服务列表
* @param exitsIds 已存在服务ID集合
* @return 树结构信息
*/
List<Tree<Long>> genCateTree(List<IscServiceCate> cates, List<IscService> serviceList, Set<Long> exitsIds);
}

View File

@ -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<IscService, IscServiceVo> {
/**
* 查询单个
* @return
*/
IscServiceVo queryById(Long serviceId);
/**
* 查询列表
*/
TableDataInfo<IscServiceVo> queryPageList(IscServiceBo bo);
/**
* 查询列表
*/
List<IscServiceVo> 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<Long> ids, Boolean isValid);
/**
* 通过服务ID集合 查询服务名称map
* @param serviceIds 服务ID集合
* @return 服务名称map
*/
Map<Long, String> getNameMap(Collection<Long> serviceIds);
/**
* 组装 服务树结构
* @return 服务树结构
*/
List<Tree<Long>> genServiceTree();
/**
* 组装 服务树结构
* @param exitsIds 已存在服务ID集合
* @return 服务树结构
*/
List<Tree<Long>> genServiceTree(Set<Long> exitsIds);
/**
* 服务信息审核
* @param bo 审核信息
* @return 操作结果
*/
Boolean audit(IscAuditBo bo);
/**
* 校验 审核参数
* @param bo 审核业务对象
*/
void checkAuditBO(IscAuditBo bo);
/**
* 刷新路由信息
*/
void refreshRoutes();
}

View File

@ -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<IscAppServiceApplyMapper, IscAppServiceApply, IscAppServiceApplyVo> 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<IscAppServiceApplyVo> queryPageList(IscAppServiceApplyBo bo) {
PagePlus<IscAppServiceApply, IscAppServiceApplyVo> result = pageVo(PageUtils.buildPagePlus(), buildQueryWrapper(bo));
genServiceNameAndApplicationName(result.getRecordsVo());
return PageUtils.buildDataInfo(result);
}
@Override
public List<IscAppServiceApplyVo> queryList(IscAppServiceApplyBo bo) {
List<IscAppServiceApplyVo> results = listVo(buildQueryWrapper(bo));
genServiceNameAndApplicationName(results);
return results;
}
/**
* 组装服务应用名称
* @param list 申请记录
*/
private void genServiceNameAndApplicationName(List<IscAppServiceApplyVo> list)
{
if(CollectionUtil.isEmpty(list)) {
return;
}
Set<Long> appServiceIds = list.stream().map(IscAppServiceApplyVo::getAppServiceId).collect(Collectors.toSet());
List<IscAppService> appServiceList = appServiceService.getAppServiceListByIds(appServiceIds);
if(CollectionUtil.isEmpty(appServiceList)) {
return;
}
Map<Long, IscAppService> appServiceMap = appServiceList.stream()
.collect(Collectors.toMap(IscAppService::getAppServiceId, Function.identity()));
Map<Long, String> serviceNameMap = serviceService.getNameMap(appServiceList.stream()
.map(IscAppService::getServiceId).collect(Collectors.toSet()));
Map<Long, String> 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<IscAppServiceApply> buildQueryWrapper(IscAppServiceApplyBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<IscAppServiceApply> 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<Long> 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.<IscAppServiceApply>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.<IscAppServiceApply>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;
}
}

View File

@ -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<IscAppServiceMapper, IscAppService, IscAppServiceVo> 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<IscAppServiceVo> queryPageList(IscAppServiceBo bo)
{
PagePlus<IscAppService, IscAppServiceVo> result = pageVo(PageUtils.buildPagePlus(), buildQueryWrapper(bo));
genServiceName(result.getRecordsVo());
return PageUtils.buildDataInfo(result);
}
@Override
public List<IscAppServiceVo> queryList(IscAppServiceBo bo)
{
final List<IscAppServiceVo> results = listVo(buildQueryWrapper(bo));
genServiceName(results);
return results;
}
/**
* 组装服务名称
*
* @param results 应用服务列表
*/
private void genServiceName(List<IscAppServiceVo> results)
{
if (CollectionUtil.isEmpty(results)) {
return;
}
Set<Long> serviceIds = results.stream().map(IscAppServiceVo::getServiceId).collect(Collectors.toSet());
Map<Long, String> nameMap = serviceService.getNameMap(serviceIds);
for (IscAppServiceVo result : results) {
result.setServiceName(nameMap.get(result.getServiceId()));
}
}
private LambdaQueryWrapper<IscAppService> buildQueryWrapper(IscAppServiceBo bo)
{
Map<String, Object> params = bo.getParams();
Long userId = SecurityUtils.getUserId();
LambdaQueryWrapper<IscAppService> 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.<IscAppServiceApply>lambdaQuery()
.eq(IscAppServiceApply::getAppServiceId, entity.getAppServiceId())
.eq(IscAppServiceApply::getStatus, Constants.AUDIT_WAIT));
Assert.isFalse(SqlHelper.retBool(count), () -> new ServiceException("有申请未审核, 不能再申请"));
}
}
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid)
{
if (isValid) {
//TODO 做一些业务上的校验,判断是否需要校验
}
applyService.remove(Wrappers.<IscAppServiceApply>lambdaQuery().in(IscAppServiceApply::getAppServiceId, ids));
return removeByIds(ids);
}
@Override
public List<Tree<Long>> genAppServiceTree(Long applicationId)
{
List<Long> serviceIds = listObjs(Wrappers.<IscAppService>lambdaQuery()
.select(IscAppService::getServiceId)
.eq(IscAppService::getApplicationId, applicationId), o -> (Long) o);
return serviceService.genServiceTree(CollectionUtil.newHashSet(serviceIds));
}
@Override
public List<IscAppService> getAppServiceListByIds(Collection<Long> ids)
{
return list(Wrappers.<IscAppService>lambdaQuery()
.select(IscAppService::getAppServiceId, IscAppService::getApplicationId, IscAppService::getServiceId)
.in(IscAppService::getAppServiceId, ids));
}
@Override
public void refreshRules()
{
List<IscAppService> list = list(Wrappers.<IscAppService>lambdaQuery()
.eq(IscAppService::getEnabled, UserConstants.NORMAL)
.eq(IscAppService::getStatus, Constants.AUDIT_PASS)
.gt(IscAppService::getEndTime, DateUtils.getNowDate()));
if(CollectionUtil.isEmpty(list)) {
return;
}
Set<Long> applicationIds = list.stream().map(IscAppService::getApplicationId).collect(Collectors.toSet());
Map<Long, String> accessKeyMap = applicationService.list(Wrappers.<IscApplication>lambdaQuery()
.select(IscApplication::getApplicationId, IscApplication::getAccessKey)
.in(IscApplication::getApplicationId, applicationIds)).stream()
.collect(Collectors.toMap(IscApplication::getApplicationId, IscApplication::getAccessKey));
List<IscRule> 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);
}
}

View File

@ -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<IscApplicationMapper, IscApplication, IscApplicationVo> implements IIscApplicationService {
@Override
public IscApplicationVo queryById(Long applicationId){
return getVoById(applicationId);
}
@Override
public TableDataInfo<IscApplicationVo> queryPageList(IscApplicationBo bo) {
PagePlus<IscApplication, IscApplicationVo> result = pageVo(PageUtils.buildPagePlus(), buildQueryWrapper(bo));
return PageUtils.buildDataInfo(result);
}
@Override
public List<IscApplicationVo> queryList(IscApplicationBo bo) {
return listVo(buildQueryWrapper(bo));
}
private LambdaQueryWrapper<IscApplication> buildQueryWrapper(IscApplicationBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<IscApplication> 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<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return removeByIds(ids);
}
@Override
public Map<Long, String> getNameMap(Collection<Long> applicationIds)
{
if(CollectionUtil.isEmpty(applicationIds)) {
return MapUtil.empty();
}
return list(Wrappers.<IscApplication>lambdaQuery()
.select(IscApplication::getApplicationId, IscApplication::getApplicationName)
.in(IscApplication::getApplicationId, applicationIds)).stream()
.collect(Collectors.toMap(IscApplication::getApplicationId, IscApplication::getApplicationName));
}
}

View File

@ -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<IscServiceCateMapper, IscServiceCate, IscServiceCateVo> 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<IscServiceCateVo> queryList(IscServiceCateBo bo) {
return listVo(buildQueryWrapper(bo));
}
private LambdaQueryWrapper<IscServiceCate> buildQueryWrapper(IscServiceCateBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<IscServiceCate> 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.<IscServiceCate>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<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return removeByIds(ids);
}
@Override
public List<IscServiceCate> selectCateList()
{
return list(Wrappers.<IscServiceCate>lambdaQuery()
.eq(IscServiceCate::getEnabled, UserConstants.DICT_NORMAL)
.orderByAsc(IscServiceCate::getParentId)
.orderByAsc(IscServiceCate::getOrderNum));
}
@Override
public Map<String, String> batchCateName(Set<String> fullPathList)
{
Map<String, String> results = new HashMap<>();
if(CollectionUtils.isEmpty(fullPathList)) {
return results;
}
final List<IscServiceCate> list = list(Wrappers.<IscServiceCate>lambdaQuery()
.select(IscServiceCate::getCateId, IscServiceCate::getCateName, IscServiceCate::getFullPath, IscServiceCate::getParentId)
.in(IscServiceCate::getFullPath, fullPathList));
Map<Long, String> cateNameMap = batchCateName(list);
for (IscServiceCate cate : list)
{
results.put(cate.getFullPath(), cateNameMap.get(cate.getCateId()));
}
return results;
}
@Override
public List<Tree<Long>> genCateTree(List<IscServiceCate> cates)
{
return genCateTree(cates, null, CollectionUtil.newHashSet());
}
@Override
public List<Tree<Long>> genCateTree(List<IscServiceCate> cates, List<IscService> serviceList, Set<Long> exitsIds)
{
boolean isServiceTree = Objects.nonNull(serviceList);
final Map<String, List<IscService>> cateServiceMap = isServiceTree ? serviceList.stream()
.collect(Collectors.groupingBy(IscService::getCateFullPath)) : null;
NodeParser<IscServiceCate, Long> 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<IscService> 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<Tree<Long>> children = tree.getChildren();
if(Objects.isNull(children)) {
children = CollectionUtil.newArrayList();
tree.setChildren(children);
}
final Tree<Long> 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<Long, String> batchCateName(List<IscServiceCate> list) {
Map<Long, String> results = new HashMap<>();
final Set<Long> parentIds = list.stream().filter(m -> m.getParentId() != 0L).map(IscServiceCate::getParentId).collect(Collectors.toSet());
if(CollectionUtils.isNotEmpty(parentIds)) {
final List<IscServiceCate> parentList = list(Wrappers.<IscServiceCate>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;
}
}

View File

@ -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<IscServiceMapper, IscService, IscServiceVo> 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<IscServiceVo> queryPageList(IscServiceBo bo)
{
PagePlus<IscService, IscServiceVo> result = pageVo(PageUtils.buildPagePlus(), buildQueryWrapper(bo));
genVoInfo(result.getRecordsVo());
return PageUtils.buildDataInfo(result);
}
@Override
public List<IscServiceVo> queryList(IscServiceBo bo)
{
final List<IscServiceVo> results = listVo(buildQueryWrapper(bo));
genVoInfo(results);
return results;
}
private void genVoInfo(List<IscServiceVo> results)
{
if (CollectionUtils.isNotEmpty(results))
{
Map<String, String> cateNameMap = cateService.batchCateName(results.stream()
.map(IscServiceVo::getCateFullPath).collect(Collectors.toSet()));
for (IscServiceVo result : results)
{
result.setCateName(cateNameMap.get(result.getCateFullPath()));
}
}
}
private LambdaQueryWrapper<IscService> buildQueryWrapper(IscServiceBo bo)
{
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<IscService> 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<Long> 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<Long, String> getNameMap(Collection<Long> serviceIds)
{
if(CollectionUtil.isEmpty(serviceIds)) {
return MapUtil.empty();
}
return list(Wrappers.<IscService>lambdaQuery()
.select(IscService::getServiceId, IscService::getServiceName)
.in(IscService::getServiceId, serviceIds)).stream()
.collect(Collectors.toMap(IscService::getServiceId, IscService::getServiceName));
}
@Override
public List<Tree<Long>> genServiceTree()
{
return genServiceTree(CollectionUtil.newHashSet());
}
@Override
public List<Tree<Long>> genServiceTree(Set<Long> exitsIds)
{
List<IscService> serviceList = list(Wrappers.<IscService>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<IscRouteDefinition> routes = pass ? ListUtil.list(false) : null;
for (Long id : bo.getIds())
{
IscService service = getOne(Wrappers.<IscService>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<IscService> serviceList = list(Wrappers.<IscService>lambdaQuery()
.select(IscService::getServiceId, IscService::getServiceAddr, IscService::getHiddenParams, IscService::getRequestMethod)
.eq(IscService::getStatus, Constants.AUDIT_PASS));
List<IscRouteDefinition> routes = new ArrayList<>();
for (IscService service : serviceList)
{
routes.add(RouteUtils.generateRoute(service));
}
RouteUtils.refreshRoute(routes);
}
}

View File

@ -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 <T> void publish(String channelKey, T msg, Consumer<T> consumer) {
RTopic topic = CLIENT.getTopic(channelKey);
topic.publish(msg);
consumer.accept(msg);
}
/**
* 发布路由刷新通知
* @param consumer 发送消息后执行
*/
public static void sendRefreshRouteToGateway(String id, Type type, Consumer<TopicMsg> consumer) {
publish(TOPIC_GATEWAY_REFRESH_ROUTE, new TopicMsg(id, DateUtil.now(), type), consumer);
}
/**
* 发布规则通知
* @param consumer 发送消息后执行
*/
public static void sendRuleNoticeToGateway(String id, Type type, Consumer<TopicMsg> consumer) {
publish(TOPIC_GATEWAY_RULE, new TopicMsg(id, DateUtil.now(), type), consumer);
}
/**
* 刷新路由信息 先清空再批量新增
*
* @param routes 路由信息
* @return 是否成功
*/
public static boolean refreshRoute(List<IscRouteDefinition> routes)
{
Map<String, IscRouteDefinition> routeMap = routes.stream().collect(Collectors.toMap(IscRouteDefinition::getId,
Function.identity(), (o1, o2) -> o2));
final RMap<String, IscRouteDefinition> 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<IscRouteDefinition> routes) {
return saveRoute(routes, null);
}
/**
* 保存路由信息
*
* @param routes 路由信息
* @param consumer 回调操作
* @return 是否成功
*/
public static boolean saveRoute(Collection<IscRouteDefinition> routes, Consumer<TopicMsg> consumer)
{
if(CollectionUtil.isEmpty(routes)) {
return true;
}
final RMap<String, IscRouteDefinition> map = CLIENT.getMap(IscRedisKeys.KEY_ROUTES, ROUTE_CODES_INSTANCE);
Map<String, IscRouteDefinition> 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<String> routeIds)
{
final RMap<String, IscRouteDefinition> 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<IscFilterDefinition> 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<String, Object> 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<IscRule> rules)
{
Map<String, IscRule> ruleMap = rules.stream().collect(Collectors.toMap(IscRule::getId, Function.identity(), (o1, o2) -> o2));
final RMap<String, IscRule> 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<TopicMsg> consumer)
{
final RMap<String, IscRule> 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<String, IscRule> 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()));
}
}

View File

@ -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<String, String> args = new LinkedHashMap<>();
}

View File

@ -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<String, String> args = new LinkedHashMap<>();
}

View File

@ -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<IscPredicateDefinition> predicates = new ArrayList<>();
@Valid
private List<IscFilterDefinition> filters = new ArrayList<>();
@NotNull
private URI uri;
private Map<String, Object> metadata = new HashMap<>();
private int order = 0;
}

Some files were not shown because too many files have changed in this diff Show More