mirror of
https://github.com/dromara/RuoYi-Vue-Plus.git
synced 2025-09-22 19:26:39 +08:00
Compare commits
No commits in common. "5.X" and "v5.4.1" have entirely different histories.
@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
<configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
||||||
<deployment type="dockerfile">
|
<deployment type="dockerfile">
|
||||||
<settings>
|
<settings>
|
||||||
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.5.0" />
|
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.4.1" />
|
||||||
<option name="buildOnly" value="true" />
|
<option name="buildOnly" value="true" />
|
||||||
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
|
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
|
||||||
</settings>
|
</settings>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
<configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
||||||
<deployment type="dockerfile">
|
<deployment type="dockerfile">
|
||||||
<settings>
|
<settings>
|
||||||
<option name="imageTag" value="ruoyi/ruoyi-server:5.5.0" />
|
<option name="imageTag" value="ruoyi/ruoyi-server:5.4.1" />
|
||||||
<option name="buildOnly" value="true" />
|
<option name="buildOnly" value="true" />
|
||||||
<option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
|
<option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
|
||||||
</settings>
|
</settings>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
<configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
||||||
<deployment type="dockerfile">
|
<deployment type="dockerfile">
|
||||||
<settings>
|
<settings>
|
||||||
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.5.0" />
|
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.4.1" />
|
||||||
<option name="buildOnly" value="true" />
|
<option name="buildOnly" value="true" />
|
||||||
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
|
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
|
||||||
</settings>
|
</settings>
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/5.X/LICENSE)
|
[](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/5.X/LICENSE)
|
||||||
[](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
|
[](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
|
||||||
<br>
|
<br>
|
||||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus)
|
[](https://gitee.com/dromara/RuoYi-Vue-Plus)
|
||||||
[]()
|
[]()
|
||||||
[]()
|
[]()
|
||||||
[]()
|
[]()
|
||||||
@ -27,7 +27,7 @@
|
|||||||
> 成员前端项目地址: 基于soybean [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)<br>
|
> 成员前端项目地址: 基于soybean [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)<br>
|
||||||
> 成员项目地址: 删除多租户与工作流 [RuoYi-Vue-Plus-Single](https://gitee.com/ColorDreams/RuoYi-Vue-Plus-Single)<br>
|
> 成员项目地址: 删除多租户与工作流 [RuoYi-Vue-Plus-Single](https://gitee.com/ColorDreams/RuoYi-Vue-Plus-Single)<br>
|
||||||
|
|
||||||
> 文档地址: [plus-doc](https://plus-doc.dromara.org) 国内加速: [plus-doc.top](https://plus-doc.top)
|
> 文档地址: [plus-doc](https://plus-doc.dromara.org) 文档在华为云上如果打不开大概率是DNS问题 可以尝试切换网络等方式(或者科学上网)
|
||||||
|
|
||||||
## 赞助商
|
## 赞助商
|
||||||
|
|
||||||
@ -37,10 +37,7 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
|
|||||||
引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
|
引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
|
||||||
<font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><br>
|
<font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><br>
|
||||||
Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br>
|
Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br>
|
||||||
aizuda flowlong 工作流 - https://gitee.com/aizuda/flowlong <br>
|
[如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group)
|
||||||
Ruoyi-Plus-Uniapp - https://ruoyi.plus <br>
|
|
||||||
|
|
||||||
[如何成为赞助商 加群联系作者详谈 每日PV2500-3000 IP1700-2500](https://plus-doc.dromara.org/#/common/add_group)
|
|
||||||
|
|
||||||
# 本框架与RuoYi的功能差异
|
# 本框架与RuoYi的功能差异
|
||||||
|
|
||||||
|
64
pom.xml
64
pom.xml
@ -13,28 +13,28 @@
|
|||||||
<description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
|
<description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>5.5.0</revision>
|
<revision>5.4.1</revision>
|
||||||
<spring-boot.version>3.5.6</spring-boot.version>
|
<spring-boot.version>3.4.7</spring-boot.version>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
<java.version>17</java.version>
|
<java.version>17</java.version>
|
||||||
<mybatis.version>3.5.16</mybatis.version>
|
<mybatis.version>3.5.16</mybatis.version>
|
||||||
<springdoc.version>2.8.13</springdoc.version>
|
<springdoc.version>2.8.8</springdoc.version>
|
||||||
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
|
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
|
||||||
<fastexcel.version>1.3.0</fastexcel.version>
|
<fastexcel.version>1.2.0</fastexcel.version>
|
||||||
<velocity.version>2.3</velocity.version>
|
<velocity.version>2.3</velocity.version>
|
||||||
<satoken.version>1.44.0</satoken.version>
|
<satoken.version>1.44.0</satoken.version>
|
||||||
<mybatis-plus.version>3.5.14</mybatis-plus.version>
|
<mybatis-plus.version>3.5.12</mybatis-plus.version>
|
||||||
<p6spy.version>3.9.1</p6spy.version>
|
<p6spy.version>3.9.1</p6spy.version>
|
||||||
<hutool.version>5.8.40</hutool.version>
|
<hutool.version>5.8.38</hutool.version>
|
||||||
<spring-boot-admin.version>3.5.3</spring-boot-admin.version>
|
<spring-boot-admin.version>3.4.7</spring-boot-admin.version>
|
||||||
<redisson.version>3.51.0</redisson.version>
|
<redisson.version>3.50.0</redisson.version>
|
||||||
<lock4j.version>2.2.7</lock4j.version>
|
<lock4j.version>2.2.7</lock4j.version>
|
||||||
<dynamic-ds.version>4.3.1</dynamic-ds.version>
|
<dynamic-ds.version>4.3.1</dynamic-ds.version>
|
||||||
<snailjob.version>1.8.0</snailjob.version>
|
<snailjob.version>1.5.0</snailjob.version>
|
||||||
<mapstruct-plus.version>1.5.0</mapstruct-plus.version>
|
<mapstruct-plus.version>1.4.8</mapstruct-plus.version>
|
||||||
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
|
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
|
||||||
<lombok.version>1.18.40</lombok.version>
|
<lombok.version>1.18.36</lombok.version>
|
||||||
<bouncycastle.version>1.80</bouncycastle.version>
|
<bouncycastle.version>1.80</bouncycastle.version>
|
||||||
<justauth.version>1.16.7</justauth.version>
|
<justauth.version>1.16.7</justauth.version>
|
||||||
<!-- 离线IP地址定位库 -->
|
<!-- 离线IP地址定位库 -->
|
||||||
@ -42,13 +42,13 @@
|
|||||||
<!-- OSS 配置 -->
|
<!-- OSS 配置 -->
|
||||||
<aws.sdk.version>2.28.22</aws.sdk.version>
|
<aws.sdk.version>2.28.22</aws.sdk.version>
|
||||||
<!-- SMS 配置 -->
|
<!-- SMS 配置 -->
|
||||||
<sms4j.version>3.3.5</sms4j.version>
|
<sms4j.version>3.3.4</sms4j.version>
|
||||||
<!-- 限制框架中的fastjson版本 -->
|
<!-- 限制框架中的fastjson版本 -->
|
||||||
<fastjson.version>1.2.83</fastjson.version>
|
<fastjson.version>1.2.83</fastjson.version>
|
||||||
<!-- 面向运行时的D-ORM依赖 -->
|
<!-- 面向运行时的D-ORM依赖 -->
|
||||||
<anyline.version>8.7.2-20250603</anyline.version>
|
<anyline.version>8.7.2-20250603</anyline.version>
|
||||||
<!-- 工作流配置 -->
|
<!-- 工作流配置 -->
|
||||||
<warm-flow.version>1.8.1</warm-flow.version>
|
<warm-flow.version>1.7.4</warm-flow.version>
|
||||||
|
|
||||||
<!-- 插件版本 -->
|
<!-- 插件版本 -->
|
||||||
<maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
|
<maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
|
||||||
@ -118,6 +118,25 @@
|
|||||||
<scope>import</scope>
|
<scope>import</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Warm-Flow国产工作流引擎, 在线文档:http://warm-flow.cn/ -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara.warm</groupId>
|
||||||
|
<artifactId>warm-flow-mybatis-plus-sb3-starter</artifactId>
|
||||||
|
<version>${warm-flow.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara.warm</groupId>
|
||||||
|
<artifactId>warm-flow-plugin-ui-sb-web</artifactId>
|
||||||
|
<version>${warm-flow.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- JustAuth 的依赖配置-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>me.zhyd.oauth</groupId>
|
||||||
|
<artifactId>JustAuth</artifactId>
|
||||||
|
<version>${justauth.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- common 的依赖配置-->
|
<!-- common 的依赖配置-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.dromara</groupId>
|
<groupId>org.dromara</groupId>
|
||||||
@ -294,25 +313,6 @@
|
|||||||
<version>${mapstruct-plus.version}</version>
|
<version>${mapstruct-plus.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Warm-Flow国产工作流引擎, 在线文档:http://warm-flow.cn/ -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.dromara.warm</groupId>
|
|
||||||
<artifactId>warm-flow-mybatis-plus-sb3-starter</artifactId>
|
|
||||||
<version>${warm-flow.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.dromara.warm</groupId>
|
|
||||||
<artifactId>warm-flow-plugin-ui-sb-web</artifactId>
|
|
||||||
<version>${warm-flow.version}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- JustAuth 的依赖配置-->
|
|
||||||
<dependency>
|
|
||||||
<groupId>me.zhyd.oauth</groupId>
|
|
||||||
<artifactId>JustAuth</artifactId>
|
|
||||||
<version>${justauth.version}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- 离线IP地址定位库 ip2region -->
|
<!-- 离线IP地址定位库 ip2region -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.lionsoul</groupId>
|
<groupId>org.lionsoul</groupId>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
|
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
|
||||||
FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds
|
FROM bellsoft/liberica-openjdk-rocky:17.0.15-cds
|
||||||
#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds
|
#FROM bellsoft/liberica-openjdk-rocky:21.0.7-cds
|
||||||
#FROM findepi/graalvm:java17-native
|
#FROM findepi/graalvm:java17-native
|
||||||
|
|
||||||
LABEL maintainer="Lion Li"
|
LABEL maintainer="Lion Li"
|
||||||
|
@ -21,8 +21,6 @@ import org.dromara.common.core.domain.model.SocialLoginBody;
|
|||||||
import org.dromara.common.core.utils.*;
|
import org.dromara.common.core.utils.*;
|
||||||
import org.dromara.common.encrypt.annotation.ApiEncrypt;
|
import org.dromara.common.encrypt.annotation.ApiEncrypt;
|
||||||
import org.dromara.common.json.utils.JsonUtils;
|
import org.dromara.common.json.utils.JsonUtils;
|
||||||
import org.dromara.common.ratelimiter.annotation.RateLimiter;
|
|
||||||
import org.dromara.common.ratelimiter.enums.LimitType;
|
|
||||||
import org.dromara.common.satoken.utils.LoginHelper;
|
import org.dromara.common.satoken.utils.LoginHelper;
|
||||||
import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
|
import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
|
||||||
import org.dromara.common.social.config.properties.SocialProperties;
|
import org.dromara.common.social.config.properties.SocialProperties;
|
||||||
@ -200,7 +198,6 @@ public class AuthController {
|
|||||||
*
|
*
|
||||||
* @return 租户列表
|
* @return 租户列表
|
||||||
*/
|
*/
|
||||||
@RateLimiter(time = 60, count = 20, limitType = LimitType.IP)
|
|
||||||
@GetMapping("/tenant/list")
|
@GetMapping("/tenant/list")
|
||||||
public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
|
public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
|
||||||
// 返回对象
|
// 返回对象
|
||||||
|
@ -131,18 +131,15 @@ public class CaptchaController {
|
|||||||
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
|
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
|
||||||
// 生成验证码
|
// 生成验证码
|
||||||
CaptchaType captchaType = captchaProperties.getType();
|
CaptchaType captchaType = captchaProperties.getType();
|
||||||
CodeGenerator codeGenerator;
|
boolean isMath = CaptchaType.MATH == captchaType;
|
||||||
if (CaptchaType.MATH == captchaType) {
|
Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength();
|
||||||
codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getNumberLength(), false);
|
CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length);
|
||||||
} else {
|
|
||||||
codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getCharLength());
|
|
||||||
}
|
|
||||||
AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
|
AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
|
||||||
captcha.setGenerator(codeGenerator);
|
captcha.setGenerator(codeGenerator);
|
||||||
captcha.createCode();
|
captcha.createCode();
|
||||||
// 如果是数学验证码,使用SpEL表达式处理验证码结果
|
// 如果是数学验证码,使用SpEL表达式处理验证码结果
|
||||||
String code = captcha.getCode();
|
String code = captcha.getCode();
|
||||||
if (CaptchaType.MATH == captchaType) {
|
if (isMath) {
|
||||||
ExpressionParser parser = new SpelExpressionParser();
|
ExpressionParser parser = new SpelExpressionParser();
|
||||||
Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
|
Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
|
||||||
code = exp.getValue(String.class);
|
code = exp.getValue(String.class);
|
||||||
|
@ -87,7 +87,7 @@ public class SysRegisterService {
|
|||||||
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
|
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
|
||||||
throw new CaptchaExpireException();
|
throw new CaptchaExpireException();
|
||||||
}
|
}
|
||||||
if (!StringUtils.equalsIgnoreCase(code, captcha)) {
|
if (!code.equalsIgnoreCase(captcha)) {
|
||||||
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
|
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
|
||||||
throw new CaptchaException();
|
throw new CaptchaException();
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ public class PasswordAuthStrategy implements IAuthStrategy {
|
|||||||
loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
|
loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
|
||||||
throw new CaptchaExpireException();
|
throw new CaptchaExpireException();
|
||||||
}
|
}
|
||||||
if (!StringUtils.equalsIgnoreCase(code, captcha)) {
|
if (!code.equalsIgnoreCase(captcha)) {
|
||||||
loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
|
loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
|
||||||
throw new CaptchaException();
|
throw new CaptchaException();
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,8 @@ snail-job:
|
|||||||
port: 2${server.port}
|
port: 2${server.port}
|
||||||
# 客户端ip指定
|
# 客户端ip指定
|
||||||
host:
|
host:
|
||||||
|
# RPC类型: netty, grpc
|
||||||
|
rpc-type: grpc
|
||||||
|
|
||||||
--- # 数据源配置
|
--- # 数据源配置
|
||||||
spring:
|
spring:
|
||||||
|
@ -30,6 +30,8 @@ snail-job:
|
|||||||
port: 2${server.port}
|
port: 2${server.port}
|
||||||
# 客户端ip指定
|
# 客户端ip指定
|
||||||
host:
|
host:
|
||||||
|
# RPC类型: netty, grpc
|
||||||
|
rpc-type: grpc
|
||||||
|
|
||||||
--- # 数据源配置
|
--- # 数据源配置
|
||||||
spring:
|
spring:
|
||||||
|
@ -57,13 +57,6 @@ spring:
|
|||||||
# 开启虚拟线程 仅jdk21可用
|
# 开启虚拟线程 仅jdk21可用
|
||||||
virtual:
|
virtual:
|
||||||
enabled: false
|
enabled: false
|
||||||
task:
|
|
||||||
execution:
|
|
||||||
# 从 springboot 3.5 开始 spring自带线程池
|
|
||||||
# 不再需要 AsyncConfig与ThreadPoolConfig 可直接注入线程池使用
|
|
||||||
thread-name-prefix: async-
|
|
||||||
# 由spring自己初始化线程池
|
|
||||||
mode: force
|
|
||||||
# 资源信息
|
# 资源信息
|
||||||
messages:
|
messages:
|
||||||
# 国际化资源文件路径
|
# 国际化资源文件路径
|
||||||
@ -134,7 +127,6 @@ tenant:
|
|||||||
- sys_user_role
|
- sys_user_role
|
||||||
- sys_client
|
- sys_client
|
||||||
- sys_oss_config
|
- sys_oss_config
|
||||||
- flow_spel
|
|
||||||
|
|
||||||
# MyBatisPlus配置
|
# MyBatisPlus配置
|
||||||
# https://baomidou.com/config/
|
# https://baomidou.com/config/
|
||||||
@ -197,6 +189,13 @@ springdoc:
|
|||||||
name: Lion Li
|
name: Lion Li
|
||||||
email: crazylionli@163.com
|
email: crazylionli@163.com
|
||||||
url: https://gitee.com/dromara/RuoYi-Vue-Plus
|
url: https://gitee.com/dromara/RuoYi-Vue-Plus
|
||||||
|
components:
|
||||||
|
# 鉴权方式配置
|
||||||
|
security-schemes:
|
||||||
|
apiKey:
|
||||||
|
type: APIKEY
|
||||||
|
in: HEADER
|
||||||
|
name: ${sa-token.token-name}
|
||||||
#这里定义了两个分组,可定义多个,也可以不定义
|
#这里定义了两个分组,可定义多个,也可以不定义
|
||||||
group-configs:
|
group-configs:
|
||||||
- group: 1.演示模块
|
- group: 1.演示模块
|
||||||
@ -214,10 +213,20 @@ springdoc:
|
|||||||
xss:
|
xss:
|
||||||
# 过滤开关
|
# 过滤开关
|
||||||
enabled: true
|
enabled: true
|
||||||
# 排除链接
|
# 排除链接(多个用逗号分隔)
|
||||||
excludeUrls:
|
excludeUrls:
|
||||||
- /system/notice
|
- /system/notice
|
||||||
|
|
||||||
|
# 全局线程池相关配置
|
||||||
|
# 如使用JDK21请直接使用虚拟线程 不要开启此配置
|
||||||
|
thread-pool:
|
||||||
|
# 是否开启线程池
|
||||||
|
enabled: false
|
||||||
|
# 队列最大长度
|
||||||
|
queueCapacity: 128
|
||||||
|
# 线程池维护线程所允许的空闲时间
|
||||||
|
keepAliveSeconds: 300
|
||||||
|
|
||||||
--- # 分布式锁 lock4j 全局配置
|
--- # 分布式锁 lock4j 全局配置
|
||||||
lock4j:
|
lock4j:
|
||||||
# 获取分布式锁超时时间,默认为 3000 毫秒
|
# 获取分布式锁超时时间,默认为 3000 毫秒
|
||||||
@ -257,9 +266,13 @@ warm-flow:
|
|||||||
enabled: true
|
enabled: true
|
||||||
# 是否开启设计器ui
|
# 是否开启设计器ui
|
||||||
ui: true
|
ui: true
|
||||||
# 是否显示流程图顶部文字
|
|
||||||
top-text-show: true
|
|
||||||
# 是否渲染节点悬浮提示,默认true
|
|
||||||
node-tooltip: true
|
|
||||||
# 默认Authorization,如果有多个token,用逗号分隔
|
# 默认Authorization,如果有多个token,用逗号分隔
|
||||||
token-name: ${sa-token.token-name},clientid
|
token-name: ${sa-token.token-name},clientid
|
||||||
|
# 流程状态对应的三元色
|
||||||
|
chart-status-color:
|
||||||
|
## 未办理
|
||||||
|
- 62,62,62
|
||||||
|
## 待办理
|
||||||
|
- 255,205,23
|
||||||
|
## 已办理
|
||||||
|
- 157,255,0
|
||||||
|
@ -17,7 +17,6 @@ user.username.length.valid=账户长度必须在{min}到{max}个字符之间
|
|||||||
user.password.not.blank=用户密码不能为空
|
user.password.not.blank=用户密码不能为空
|
||||||
user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
|
user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
|
||||||
user.password.not.valid=* 5-50个字符
|
user.password.not.valid=* 5-50个字符
|
||||||
user.password.format.valid=密码必须包含大写字母、小写字母、数字和特殊字符
|
|
||||||
user.email.not.valid=邮箱格式错误
|
user.email.not.valid=邮箱格式错误
|
||||||
user.email.not.blank=邮箱不能为空
|
user.email.not.blank=邮箱不能为空
|
||||||
user.phonenumber.not.blank=用户手机号不能为空
|
user.phonenumber.not.blank=用户手机号不能为空
|
||||||
|
@ -17,7 +17,6 @@ user.username.length.valid=Account length must be between {min} and {max} charac
|
|||||||
user.password.not.blank=Password cannot be empty
|
user.password.not.blank=Password cannot be empty
|
||||||
user.password.length.valid=Password length must be between {min} and {max} characters
|
user.password.length.valid=Password length must be between {min} and {max} characters
|
||||||
user.password.not.valid=* 5-50 characters
|
user.password.not.valid=* 5-50 characters
|
||||||
user.password.format.valid=Password must contain uppercase, lowercase, digit, and special character
|
|
||||||
user.email.not.valid=Mailbox format error
|
user.email.not.valid=Mailbox format error
|
||||||
user.email.not.blank=Mailbox cannot be blank
|
user.email.not.blank=Mailbox cannot be blank
|
||||||
user.phonenumber.not.blank=Phone number cannot be blank
|
user.phonenumber.not.blank=Phone number cannot be blank
|
||||||
|
@ -17,7 +17,6 @@ user.username.length.valid=账户长度必须在{min}到{max}个字符之间
|
|||||||
user.password.not.blank=用户密码不能为空
|
user.password.not.blank=用户密码不能为空
|
||||||
user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
|
user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
|
||||||
user.password.not.valid=* 5-50个字符
|
user.password.not.valid=* 5-50个字符
|
||||||
user.password.format.valid=密码必须包含大写字母、小写字母、数字和特殊字符
|
|
||||||
user.email.not.valid=邮箱格式错误
|
user.email.not.valid=邮箱格式错误
|
||||||
user.email.not.blank=邮箱不能为空
|
user.email.not.blank=邮箱不能为空
|
||||||
user.phonenumber.not.blank=用户手机号不能为空
|
user.phonenumber.not.blank=用户手机号不能为空
|
||||||
|
Binary file not shown.
@ -38,7 +38,7 @@
|
|||||||
<!-- 循环政策:基于时间创建日志文件 -->
|
<!-- 循环政策:基于时间创建日志文件 -->
|
||||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
<!-- 日志文件名格式 -->
|
<!-- 日志文件名格式 -->
|
||||||
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
|
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||||
<!-- 日志最大的历史 60天 -->
|
<!-- 日志最大的历史 60天 -->
|
||||||
<maxHistory>60</maxHistory>
|
<maxHistory>60</maxHistory>
|
||||||
</rollingPolicy>
|
</rollingPolicy>
|
||||||
@ -60,7 +60,7 @@
|
|||||||
<!-- 循环政策:基于时间创建日志文件 -->
|
<!-- 循环政策:基于时间创建日志文件 -->
|
||||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
<!-- 日志文件名格式 -->
|
<!-- 日志文件名格式 -->
|
||||||
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
|
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||||
<!-- 日志最大的历史 60天 -->
|
<!-- 日志最大的历史 60天 -->
|
||||||
<maxHistory>60</maxHistory>
|
<maxHistory>60</maxHistory>
|
||||||
</rollingPolicy>
|
</rollingPolicy>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
</description>
|
</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>5.5.0</revision>
|
<revision>5.4.1</revision>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
package org.dromara.common.core.config;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
|
import org.dromara.common.core.exception.ServiceException;
|
||||||
|
import org.dromara.common.core.utils.SpringUtils;
|
||||||
|
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
|
import org.springframework.core.task.VirtualThreadTaskExecutor;
|
||||||
|
import org.springframework.scheduling.annotation.AsyncConfigurer;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步配置
|
||||||
|
* <p>
|
||||||
|
* 如果未使用虚拟线程则生效
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
@AutoConfiguration
|
||||||
|
public class AsyncConfig implements AsyncConfigurer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义 @Async 注解使用系统线程池
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Executor getAsyncExecutor() {
|
||||||
|
if(SpringUtils.isVirtual()) {
|
||||||
|
return new VirtualThreadTaskExecutor("async-");
|
||||||
|
}
|
||||||
|
return SpringUtils.getBean("scheduledExecutorService");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步执行异常处理
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||||
|
return (throwable, method, objects) -> {
|
||||||
|
throwable.printStackTrace();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Exception message - ").append(throwable.getMessage())
|
||||||
|
.append(", Method name - ").append(method.getName());
|
||||||
|
if (ArrayUtil.isNotEmpty(objects)) {
|
||||||
|
sb.append(", Parameter value - ").append(Arrays.toString(objects));
|
||||||
|
}
|
||||||
|
throw new ServiceException(sb.toString());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -7,9 +7,11 @@ import org.dromara.common.core.config.properties.ThreadPoolProperties;
|
|||||||
import org.dromara.common.core.utils.SpringUtils;
|
import org.dromara.common.core.utils.SpringUtils;
|
||||||
import org.dromara.common.core.utils.Threads;
|
import org.dromara.common.core.utils.Threads;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.core.task.VirtualThreadTaskExecutor;
|
import org.springframework.core.task.VirtualThreadTaskExecutor;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||||
@ -32,6 +34,18 @@ public class ThreadPoolConfig {
|
|||||||
|
|
||||||
private ScheduledExecutorService scheduledExecutorService;
|
private ScheduledExecutorService scheduledExecutorService;
|
||||||
|
|
||||||
|
@Bean(name = "threadPoolTaskExecutor")
|
||||||
|
@ConditionalOnProperty(prefix = "thread-pool", name = "enabled", havingValue = "true")
|
||||||
|
public ThreadPoolTaskExecutor threadPoolTaskExecutor(ThreadPoolProperties threadPoolProperties) {
|
||||||
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||||
|
executor.setCorePoolSize(core);
|
||||||
|
executor.setMaxPoolSize(core * 2);
|
||||||
|
executor.setQueueCapacity(threadPoolProperties.getQueueCapacity());
|
||||||
|
executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds());
|
||||||
|
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行周期性或定时任务
|
* 执行周期性或定时任务
|
||||||
*/
|
*/
|
||||||
|
@ -72,10 +72,5 @@ public interface Constants {
|
|||||||
*/
|
*/
|
||||||
Long TOP_PARENT_ID = 0L;
|
Long TOP_PARENT_ID = 0L;
|
||||||
|
|
||||||
/**
|
|
||||||
* 加密头
|
|
||||||
*/
|
|
||||||
String ENCRYPT_HEADER = "ENC_";
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,11 +50,6 @@ public class CompleteTaskDTO implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private String notice;
|
private String notice;
|
||||||
|
|
||||||
/**
|
|
||||||
* 办理人(可不填 用于覆盖当前节点办理人)
|
|
||||||
*/
|
|
||||||
private String handler;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流程变量
|
* 流程变量
|
||||||
*/
|
*/
|
||||||
|
@ -30,11 +30,6 @@ public class StartProcessDTO implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private String flowCode;
|
private String flowCode;
|
||||||
|
|
||||||
/**
|
|
||||||
* 办理人(可不填 用于覆盖当前节点办理人)
|
|
||||||
*/
|
|
||||||
private String handler;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流程变量,前端会提交一个元素{'entity': {业务详情数据对象}}
|
* 流程变量,前端会提交一个元素{'entity': {业务详情数据对象}}
|
||||||
*/
|
*/
|
||||||
|
@ -52,17 +52,17 @@ public class TaskAssigneeDTO implements Serializable {
|
|||||||
*/
|
*/
|
||||||
public static <T> List<TaskHandler> convertToHandlerList(
|
public static <T> List<TaskHandler> convertToHandlerList(
|
||||||
List<T> sourceList,
|
List<T> sourceList,
|
||||||
Function<T, String> storageId,
|
Function<T, Long> storageId,
|
||||||
Function<T, String> handlerCode,
|
Function<T, String> handlerCode,
|
||||||
Function<T, String> handlerName,
|
Function<T, String> handlerName,
|
||||||
Function<T, String> groupName,
|
Function<T, Long> groupName,
|
||||||
Function<T, Date> createTimeMapper) {
|
Function<T, Date> createTimeMapper) {
|
||||||
return sourceList.stream()
|
return sourceList.stream()
|
||||||
.map(item -> new TaskHandler(
|
.map(item -> new TaskHandler(
|
||||||
storageId.apply(item),
|
String.valueOf(storageId.apply(item)),
|
||||||
handlerCode.apply(item),
|
handlerCode.apply(item),
|
||||||
handlerName.apply(item),
|
handlerName.apply(item),
|
||||||
groupName.apply(item),
|
groupName != null ? String.valueOf(groupName.apply(item)) : null,
|
||||||
createTimeMapper.apply(item)
|
createTimeMapper.apply(item)
|
||||||
)).collect(Collectors.toList());
|
)).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
@ -27,11 +27,6 @@ public class ProcessEvent implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private String flowCode;
|
private String flowCode;
|
||||||
|
|
||||||
/**
|
|
||||||
* 实例id
|
|
||||||
*/
|
|
||||||
private Long instanceId;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 业务id
|
* 业务id
|
||||||
*/
|
*/
|
||||||
|
@ -4,7 +4,6 @@ import lombok.Data;
|
|||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流程任务监听
|
* 流程任务监听
|
||||||
@ -47,11 +46,6 @@ public class ProcessTaskEvent implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private Long taskId;
|
private Long taskId;
|
||||||
|
|
||||||
/**
|
|
||||||
* 实例id
|
|
||||||
*/
|
|
||||||
private Long instanceId;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 业务id
|
* 业务id
|
||||||
*/
|
*/
|
||||||
@ -62,9 +56,4 @@ public class ProcessTaskEvent implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
/**
|
|
||||||
* 办理参数
|
|
||||||
*/
|
|
||||||
private Map<String, Object> params;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ public class PasswordLoginBody extends LoginBody {
|
|||||||
*/
|
*/
|
||||||
@NotBlank(message = "{user.password.not.blank}")
|
@NotBlank(message = "{user.password.not.blank}")
|
||||||
@Length(min = 5, max = 30, message = "{user.password.length.valid}")
|
@Length(min = 5, max = 30, message = "{user.password.length.valid}")
|
||||||
// @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}")
|
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,12 +26,8 @@ public class RegisterBody extends LoginBody {
|
|||||||
*/
|
*/
|
||||||
@NotBlank(message = "{user.password.not.blank}")
|
@NotBlank(message = "{user.password.not.blank}")
|
||||||
@Length(min = 5, max = 30, message = "{user.password.length.valid}")
|
@Length(min = 5, max = 30, message = "{user.password.length.valid}")
|
||||||
// @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}")
|
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户类型
|
|
||||||
*/
|
|
||||||
private String userType;
|
private String userType;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
package org.dromara.common.core.exception;
|
package org.dromara.common.core.exception;
|
||||||
|
|
||||||
import cn.hutool.core.text.StrFormatter;
|
import lombok.*;
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 业务异常(支持占位符 {} )
|
* 业务异常
|
||||||
*
|
*
|
||||||
* @author ruoyi
|
* @author ruoyi
|
||||||
*/
|
*/
|
||||||
@ -46,10 +42,6 @@ public final class ServiceException extends RuntimeException {
|
|||||||
this.code = code;
|
this.code = code;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServiceException(String message, Object... args) {
|
|
||||||
this.message = StrFormatter.format(message, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getMessage() {
|
public String getMessage() {
|
||||||
return message;
|
return message;
|
||||||
|
@ -3,7 +3,6 @@ package org.dromara.common.core.service;
|
|||||||
import org.dromara.common.core.domain.dto.DeptDTO;
|
import org.dromara.common.core.domain.dto.DeptDTO;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用 部门服务
|
* 通用 部门服务
|
||||||
@ -35,12 +34,4 @@ public interface DeptService {
|
|||||||
*/
|
*/
|
||||||
List<DeptDTO> selectDeptsByList();
|
List<DeptDTO> selectDeptsByList();
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据部门 ID 列表查询部门名称映射关系
|
|
||||||
*
|
|
||||||
* @param deptIds 部门 ID 列表
|
|
||||||
* @return Map,其中 key 为部门 ID,value 为对应的部门名称
|
|
||||||
*/
|
|
||||||
Map<Long, String> selectDeptNamesByIds(List<Long> deptIds);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
package org.dromara.common.core.service;
|
package org.dromara.common.core.service;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用 岗位服务
|
* 通用 岗位服务
|
||||||
*
|
*
|
||||||
@ -10,12 +7,4 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public interface PostService {
|
public interface PostService {
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据岗位 ID 列表查询岗位名称映射关系
|
|
||||||
*
|
|
||||||
* @param postIds 岗位 ID 列表
|
|
||||||
* @return Map,其中 key 为岗位 ID,value 为对应的岗位名称
|
|
||||||
*/
|
|
||||||
Map<Long, String> selectPostNamesByIds(List<Long> postIds);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
package org.dromara.common.core.service;
|
package org.dromara.common.core.service;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用 角色服务
|
* 通用 角色服务
|
||||||
*
|
*
|
||||||
@ -10,12 +7,4 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public interface RoleService {
|
public interface RoleService {
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据角色 ID 列表查询角色名称映射关系
|
|
||||||
*
|
|
||||||
* @param roleIds 角色 ID 列表
|
|
||||||
* @return Map,其中 key 为角色 ID,value 为对应的角色名称
|
|
||||||
*/
|
|
||||||
Map<Long, String> selectRoleNamesByIds(List<Long> roleIds);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -100,4 +100,28 @@ public interface UserService {
|
|||||||
*/
|
*/
|
||||||
Map<Long, String> selectUserNamesByIds(List<Long> userIds);
|
Map<Long, String> selectUserNamesByIds(List<Long> userIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据角色 ID 列表查询角色名称映射关系
|
||||||
|
*
|
||||||
|
* @param roleIds 角色 ID 列表
|
||||||
|
* @return Map,其中 key 为角色 ID,value 为对应的角色名称
|
||||||
|
*/
|
||||||
|
Map<Long, String> selectRoleNamesByIds(List<Long> roleIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据部门 ID 列表查询部门名称映射关系
|
||||||
|
*
|
||||||
|
* @param deptIds 部门 ID 列表
|
||||||
|
* @return Map,其中 key 为部门 ID,value 为对应的部门名称
|
||||||
|
*/
|
||||||
|
Map<Long, String> selectDeptNamesByIds(List<Long> deptIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据岗位 ID 列表查询岗位名称映射关系
|
||||||
|
*
|
||||||
|
* @param postIds 岗位 ID 列表
|
||||||
|
* @return Map,其中 key 为岗位 ID,value 为对应的岗位名称
|
||||||
|
*/
|
||||||
|
Map<Long, String> selectPostNamesByIds(List<Long> postIds);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,6 @@ public interface WorkflowService {
|
|||||||
* completeTask.getVariables().put("ignore", true);
|
* completeTask.getVariables().put("ignore", true);
|
||||||
*
|
*
|
||||||
* @param completeTask 参数
|
* @param completeTask 参数
|
||||||
* @return 结果
|
|
||||||
*/
|
*/
|
||||||
boolean completeTask(CompleteTaskDTO completeTask);
|
boolean completeTask(CompleteTaskDTO completeTask);
|
||||||
|
|
||||||
@ -91,15 +90,6 @@ public interface WorkflowService {
|
|||||||
*
|
*
|
||||||
* @param taskId 任务ID
|
* @param taskId 任务ID
|
||||||
* @param message 办理意见
|
* @param message 办理意见
|
||||||
* @return 结果
|
|
||||||
*/
|
*/
|
||||||
boolean completeTask(Long taskId, String message);
|
boolean completeTask(Long taskId, String message);
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动流程并办理第一个任务
|
|
||||||
*
|
|
||||||
* @param startProcess 参数
|
|
||||||
* @return 结果
|
|
||||||
*/
|
|
||||||
boolean startCompleteTask(StartProcessDTO startProcess);
|
|
||||||
}
|
}
|
||||||
|
@ -293,7 +293,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
|
|||||||
|
|
||||||
// 校验时间跨度不超过最大限制
|
// 校验时间跨度不超过最大限制
|
||||||
if (diff > maxValue) {
|
if (diff > maxValue) {
|
||||||
throw new ServiceException("最大时间跨度为 {} {}", maxValue, unit.toString().toLowerCase());
|
throw new ServiceException("最大时间跨度为 " + maxValue + " " + unit.toString().toLowerCase());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ public class ServletUtils extends JakartaServletUtil {
|
|||||||
public static Map<String, String> getParamMap(ServletRequest request) {
|
public static Map<String, String> getParamMap(ServletRequest request) {
|
||||||
Map<String, String> params = new HashMap<>();
|
Map<String, String> params = new HashMap<>();
|
||||||
for (Map.Entry<String, String[]> entry : getParams(request).entrySet()) {
|
for (Map.Entry<String, String[]> entry : getParams(request).entrySet()) {
|
||||||
params.put(entry.getKey(), StringUtils.joinComma(entry.getValue()));
|
params.put(entry.getKey(), StringUtils.join(entry.getValue(), StringUtils.SEPARATOR));
|
||||||
}
|
}
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import lombok.NoArgsConstructor;
|
|||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -30,10 +31,8 @@ public class StreamUtils {
|
|||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return CollUtil.newArrayList();
|
return CollUtil.newArrayList();
|
||||||
}
|
}
|
||||||
return collection.stream()
|
|
||||||
.filter(function)
|
|
||||||
// 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
|
// 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
|
||||||
.collect(Collectors.toList());
|
return collection.stream().filter(function).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -41,26 +40,13 @@ public class StreamUtils {
|
|||||||
*
|
*
|
||||||
* @param collection 需要查询的集合
|
* @param collection 需要查询的集合
|
||||||
* @param function 过滤方法
|
* @param function 过滤方法
|
||||||
* @return 找到符合条件的第一个元素,没有则返回 Optional.empty()
|
* @return 找到符合条件的第一个元素,没有则返回null
|
||||||
*/
|
*/
|
||||||
public static <E> Optional<E> findFirst(Collection<E> collection, Predicate<E> function) {
|
public static <E> E findFirst(Collection<E> collection, Predicate<E> function) {
|
||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return Optional.empty();
|
return null;
|
||||||
}
|
}
|
||||||
return collection.stream()
|
return collection.stream().filter(function).findFirst().orElse(null);
|
||||||
.filter(function)
|
|
||||||
.findFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 找到流中满足条件的第一个元素值
|
|
||||||
*
|
|
||||||
* @param collection 需要查询的集合
|
|
||||||
* @param function 过滤方法
|
|
||||||
* @return 找到符合条件的第一个元素,没有则返回 null
|
|
||||||
*/
|
|
||||||
public static <E> E findFirstValue(Collection<E> collection, Predicate<E> function) {
|
|
||||||
return findFirst(collection,function).orElse(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,26 +54,13 @@ public class StreamUtils {
|
|||||||
*
|
*
|
||||||
* @param collection 需要查询的集合
|
* @param collection 需要查询的集合
|
||||||
* @param function 过滤方法
|
* @param function 过滤方法
|
||||||
* @return 找到符合条件的任意一个元素,没有则返回 Optional.empty()
|
* @return 找到符合条件的任意一个元素,没有则返回null
|
||||||
*/
|
*/
|
||||||
public static <E> Optional<E> findAny(Collection<E> collection, Predicate<E> function) {
|
public static <E> Optional<E> findAny(Collection<E> collection, Predicate<E> function) {
|
||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
return collection.stream()
|
return collection.stream().filter(function).findAny();
|
||||||
.filter(function)
|
|
||||||
.findAny();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 找到流中任意一个满足条件的元素值
|
|
||||||
*
|
|
||||||
* @param collection 需要查询的集合
|
|
||||||
* @param function 过滤方法
|
|
||||||
* @return 找到符合条件的任意一个元素,没有则返回null
|
|
||||||
*/
|
|
||||||
public static <E> E findAnyValue(Collection<E> collection, Predicate<E> function) {
|
|
||||||
return findAny(collection,function).orElse(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -113,10 +86,7 @@ public class StreamUtils {
|
|||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return StringUtils.EMPTY;
|
return StringUtils.EMPTY;
|
||||||
}
|
}
|
||||||
return collection.stream()
|
return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.joining(delimiter));
|
||||||
.map(function)
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.collect(Collectors.joining(delimiter));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -130,11 +100,8 @@ public class StreamUtils {
|
|||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return CollUtil.newArrayList();
|
return CollUtil.newArrayList();
|
||||||
}
|
}
|
||||||
return collection.stream()
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.sorted(comparing)
|
|
||||||
// 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
|
// 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
|
||||||
.collect(Collectors.toList());
|
return collection.stream().filter(Objects::nonNull).sorted(comparing).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -151,9 +118,7 @@ public class StreamUtils {
|
|||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return MapUtil.newHashMap();
|
return MapUtil.newHashMap();
|
||||||
}
|
}
|
||||||
return collection.stream()
|
return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, Function.identity(), (l, r) -> l));
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.collect(Collectors.toMap(key, Function.identity(), (l, r) -> l));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -172,25 +137,7 @@ public class StreamUtils {
|
|||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return MapUtil.newHashMap();
|
return MapUtil.newHashMap();
|
||||||
}
|
}
|
||||||
return collection.stream()
|
return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, value, (l, r) -> l));
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.collect(Collectors.toMap(key, value, (l, r) -> l));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 map 中的数据作为新 Map 的 value ,key 不变
|
|
||||||
* @param map 需要处理的map
|
|
||||||
* @param take 取值函数
|
|
||||||
* @param <K> map中的key类型
|
|
||||||
* @param <E> map中的value类型
|
|
||||||
* @param <V> 新map中的value类型
|
|
||||||
* @return 新的map
|
|
||||||
*/
|
|
||||||
public static <K, E, V> Map<K, V> toMap(Map<K, E> map, BiFunction<K, E, V> take) {
|
|
||||||
if (CollUtil.isEmpty(map)) {
|
|
||||||
return MapUtil.newHashMap();
|
|
||||||
}
|
|
||||||
return toMap(map.entrySet(), Map.Entry::getKey, entry -> take.apply(entry.getKey(), entry.getValue()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -207,8 +154,8 @@ public class StreamUtils {
|
|||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return MapUtil.newHashMap();
|
return MapUtil.newHashMap();
|
||||||
}
|
}
|
||||||
return collection.stream()
|
return collection
|
||||||
.filter(Objects::nonNull)
|
.stream().filter(Objects::nonNull)
|
||||||
.collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList()));
|
.collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,8 +175,8 @@ public class StreamUtils {
|
|||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return MapUtil.newHashMap();
|
return MapUtil.newHashMap();
|
||||||
}
|
}
|
||||||
return collection.stream()
|
return collection
|
||||||
.filter(Objects::nonNull)
|
.stream().filter(Objects::nonNull)
|
||||||
.collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.groupingBy(key2, LinkedHashMap::new, Collectors.toList())));
|
.collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.groupingBy(key2, LinkedHashMap::new, Collectors.toList())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,11 +193,11 @@ public class StreamUtils {
|
|||||||
* @return 分类后的map
|
* @return 分类后的map
|
||||||
*/
|
*/
|
||||||
public static <E, T, U> Map<T, Map<U, E>> group2Map(Collection<E> collection, Function<E, T> key1, Function<E, U> key2) {
|
public static <E, T, U> Map<T, Map<U, E>> group2Map(Collection<E> collection, Function<E, T> key1, Function<E, U> key2) {
|
||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection) || key1 == null || key2 == null) {
|
||||||
return MapUtil.newHashMap();
|
return MapUtil.newHashMap();
|
||||||
}
|
}
|
||||||
return collection.stream()
|
return collection
|
||||||
.filter(Objects::nonNull)
|
.stream().filter(Objects::nonNull)
|
||||||
.collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.toMap(key2, Function.identity(), (l, r) -> l)));
|
.collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.toMap(key2, Function.identity(), (l, r) -> l)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,7 +215,8 @@ public class StreamUtils {
|
|||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return CollUtil.newArrayList();
|
return CollUtil.newArrayList();
|
||||||
}
|
}
|
||||||
return collection.stream()
|
return collection
|
||||||
|
.stream()
|
||||||
.map(function)
|
.map(function)
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
// 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
|
// 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
|
||||||
@ -286,10 +234,11 @@ public class StreamUtils {
|
|||||||
* @return 转化后的Set
|
* @return 转化后的Set
|
||||||
*/
|
*/
|
||||||
public static <E, T> Set<T> toSet(Collection<E> collection, Function<E, T> function) {
|
public static <E, T> Set<T> toSet(Collection<E> collection, Function<E, T> function) {
|
||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection) || function == null) {
|
||||||
return CollUtil.newHashSet();
|
return CollUtil.newHashSet();
|
||||||
}
|
}
|
||||||
return collection.stream()
|
return collection
|
||||||
|
.stream()
|
||||||
.map(function)
|
.map(function)
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
@ -309,20 +258,26 @@ public class StreamUtils {
|
|||||||
* @return 合并后的map
|
* @return 合并后的map
|
||||||
*/
|
*/
|
||||||
public static <K, X, Y, V> Map<K, V> merge(Map<K, X> map1, Map<K, Y> map2, BiFunction<X, Y, V> merge) {
|
public static <K, X, Y, V> Map<K, V> merge(Map<K, X> map1, Map<K, Y> map2, BiFunction<X, Y, V> merge) {
|
||||||
if (CollUtil.isEmpty(map1) && CollUtil.isEmpty(map2)) {
|
if (MapUtil.isEmpty(map1) && MapUtil.isEmpty(map2)) {
|
||||||
// 如果两个 map 都为空,则直接返回空的 map
|
|
||||||
return MapUtil.newHashMap();
|
return MapUtil.newHashMap();
|
||||||
} else if (CollUtil.isEmpty(map1)) {
|
} else if (MapUtil.isEmpty(map1)) {
|
||||||
// 如果 map1 为空,则直接处理返回 map2
|
map1 = MapUtil.newHashMap();
|
||||||
return toMap(map2.entrySet(), Map.Entry::getKey, entry -> merge.apply(null, entry.getValue()));
|
} else if (MapUtil.isEmpty(map2)) {
|
||||||
} else if (CollUtil.isEmpty(map2)) {
|
map2 = MapUtil.newHashMap();
|
||||||
// 如果 map2 为空,则直接处理返回 map1
|
|
||||||
return toMap(map1.entrySet(), Map.Entry::getKey, entry -> merge.apply(entry.getValue(), null));
|
|
||||||
}
|
}
|
||||||
Set<K> keySet = new HashSet<>();
|
Set<K> key = new HashSet<>();
|
||||||
keySet.addAll(map1.keySet());
|
key.addAll(map1.keySet());
|
||||||
keySet.addAll(map2.keySet());
|
key.addAll(map2.keySet());
|
||||||
return toMap(keySet, key -> key, key -> merge.apply(map1.get(key), map2.get(key)));
|
Map<K, V> map = new HashMap<>();
|
||||||
|
for (K t : key) {
|
||||||
|
X x = map1.get(t);
|
||||||
|
Y y = map2.get(t);
|
||||||
|
V z = merge.apply(x, y);
|
||||||
|
if (z != null) {
|
||||||
|
map.put(t, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -260,13 +260,13 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
|
|||||||
if (s != null) {
|
if (s != null) {
|
||||||
final int len = s.length();
|
final int len = s.length();
|
||||||
if (s.length() <= size) {
|
if (s.length() <= size) {
|
||||||
sb.append(Convert.toStr(c).repeat(size - len));
|
sb.append(String.valueOf(c).repeat(size - len));
|
||||||
sb.append(s);
|
sb.append(s);
|
||||||
} else {
|
} else {
|
||||||
return s.substring(len - size, len);
|
return s.substring(len - size, len);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sb.append(Convert.toStr(c).repeat(Math.max(0, size)));
|
sb.append(String.valueOf(c).repeat(Math.max(0, size)));
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
@ -361,24 +361,5 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
|
|||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* 将可迭代对象中的元素使用逗号拼接成字符串
|
|
||||||
*
|
|
||||||
* @param iterable 可迭代对象,如 List、Set 等
|
|
||||||
* @return 拼接后的字符串
|
|
||||||
*/
|
|
||||||
public static String joinComma(Iterable<?> iterable) {
|
|
||||||
return StringUtils.join(iterable, SEPARATOR);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将数组中的元素使用逗号拼接成字符串
|
|
||||||
*
|
|
||||||
* @param array 任意类型的数组
|
|
||||||
* @return 拼接后的字符串
|
|
||||||
*/
|
|
||||||
public static String joinComma(Object[] array) {
|
|
||||||
return StringUtils.join(array, SEPARATOR);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
org.dromara.common.core.config.ApplicationConfig
|
org.dromara.common.core.config.ApplicationConfig
|
||||||
|
org.dromara.common.core.config.AsyncConfig
|
||||||
org.dromara.common.core.config.ThreadPoolConfig
|
org.dromara.common.core.config.ThreadPoolConfig
|
||||||
org.dromara.common.core.config.ValidatorConfig
|
org.dromara.common.core.config.ValidatorConfig
|
||||||
org.dromara.common.core.utils.SpringUtils
|
org.dromara.common.core.utils.SpringUtils
|
||||||
|
@ -54,7 +54,6 @@ public class SpringDocConfig {
|
|||||||
openApi.externalDocs(properties.getExternalDocs());
|
openApi.externalDocs(properties.getExternalDocs());
|
||||||
openApi.tags(properties.getTags());
|
openApi.tags(properties.getTags());
|
||||||
openApi.paths(properties.getPaths());
|
openApi.paths(properties.getPaths());
|
||||||
if (properties.getComponents() != null) {
|
|
||||||
openApi.components(properties.getComponents());
|
openApi.components(properties.getComponents());
|
||||||
Set<String> keySet = properties.getComponents().getSecuritySchemes().keySet();
|
Set<String> keySet = properties.getComponents().getSecuritySchemes().keySet();
|
||||||
List<SecurityRequirement> list = new ArrayList<>();
|
List<SecurityRequirement> list = new ArrayList<>();
|
||||||
@ -62,7 +61,7 @@ public class SpringDocConfig {
|
|||||||
keySet.forEach(securityRequirement::addList);
|
keySet.forEach(securityRequirement::addList);
|
||||||
list.add(securityRequirement);
|
list.add(securityRequirement);
|
||||||
openApi.security(list);
|
openApi.security(list);
|
||||||
}
|
|
||||||
return openApi;
|
return openApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ import org.dromara.common.encrypt.properties.ApiDecryptProperties;
|
|||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.boot.web.servlet.FilterRegistration;
|
|
||||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
@ -21,14 +20,13 @@ import org.springframework.context.annotation.Bean;
|
|||||||
public class ApiDecryptAutoConfiguration {
|
public class ApiDecryptAutoConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@FilterRegistration(
|
public FilterRegistrationBean<CryptoFilter> cryptoFilterRegistration(ApiDecryptProperties properties) {
|
||||||
name = "cryptoFilter",
|
FilterRegistrationBean<CryptoFilter> registration = new FilterRegistrationBean<>();
|
||||||
urlPatterns = "/*",
|
registration.setDispatcherTypes(DispatcherType.REQUEST);
|
||||||
order = FilterRegistrationBean.HIGHEST_PRECEDENCE,
|
registration.setFilter(new CryptoFilter(properties));
|
||||||
dispatcherTypes = DispatcherType.REQUEST
|
registration.addUrlPatterns("/*");
|
||||||
)
|
registration.setName("cryptoFilter");
|
||||||
public CryptoFilter cryptoFilter(ApiDecryptProperties properties) {
|
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
|
||||||
return new CryptoFilter(properties);
|
return registration;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import cn.hutool.core.util.ReflectUtil;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.ibatis.io.Resources;
|
import org.apache.ibatis.io.Resources;
|
||||||
import org.dromara.common.core.constant.Constants;
|
|
||||||
import org.dromara.common.core.utils.ObjectUtils;
|
import org.dromara.common.core.utils.ObjectUtils;
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
import org.dromara.common.encrypt.annotation.EncryptField;
|
import org.dromara.common.encrypt.annotation.EncryptField;
|
||||||
@ -93,12 +92,8 @@ public class EncryptorManager {
|
|||||||
* @param encryptContext 加密相关的配置信息
|
* @param encryptContext 加密相关的配置信息
|
||||||
*/
|
*/
|
||||||
public String encrypt(String value, EncryptContext encryptContext) {
|
public String encrypt(String value, EncryptContext encryptContext) {
|
||||||
if (StringUtils.startsWith(value, Constants.ENCRYPT_HEADER)) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);
|
IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);
|
||||||
String encrypt = encryptor.encrypt(value, encryptContext.getEncode());
|
return encryptor.encrypt(value, encryptContext.getEncode());
|
||||||
return Constants.ENCRYPT_HEADER + encrypt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,12 +103,8 @@ public class EncryptorManager {
|
|||||||
* @param encryptContext 加密相关的配置信息
|
* @param encryptContext 加密相关的配置信息
|
||||||
*/
|
*/
|
||||||
public String decrypt(String value, EncryptContext encryptContext) {
|
public String decrypt(String value, EncryptContext encryptContext) {
|
||||||
if (!StringUtils.startsWith(value, Constants.ENCRYPT_HEADER)) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);
|
IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);
|
||||||
String str = StringUtils.removeStart(value, Constants.ENCRYPT_HEADER);
|
return encryptor.decrypt(value);
|
||||||
return encryptor.decrypt(str);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,200 +0,0 @@
|
|||||||
package org.dromara.common.excel.core;
|
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
|
||||||
import cn.hutool.core.util.ReflectUtil;
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import cn.idev.excel.annotation.ExcelIgnore;
|
|
||||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
|
||||||
import cn.idev.excel.annotation.ExcelProperty;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import org.apache.poi.ss.util.CellRangeAddress;
|
|
||||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
|
|
||||||
import org.dromara.common.excel.annotation.CellMerge;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 单元格合并处理器
|
|
||||||
*
|
|
||||||
* @author Lion Li
|
|
||||||
*/
|
|
||||||
public class CellMergeHandler {
|
|
||||||
|
|
||||||
private final boolean hasTitle;
|
|
||||||
private int rowIndex;
|
|
||||||
|
|
||||||
private CellMergeHandler(final boolean hasTitle) {
|
|
||||||
this.hasTitle = hasTitle;
|
|
||||||
// 行合并开始下标
|
|
||||||
this.rowIndex = hasTitle ? 1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public List<CellRangeAddress> handle(List<?> rows) {
|
|
||||||
// 如果入参为空集合则返回空集
|
|
||||||
if (CollUtil.isEmpty(rows)) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取有合并注解的字段
|
|
||||||
Map<Field, FieldColumnIndex> mergeFields = getFieldColumnIndexMap(rows.get(0).getClass());
|
|
||||||
// 如果没有需要合并的字段则返回空集
|
|
||||||
if (CollUtil.isEmpty(mergeFields)) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 结果集
|
|
||||||
List<CellRangeAddress> result = new ArrayList<>();
|
|
||||||
|
|
||||||
// 生成两两合并单元格
|
|
||||||
Map<Field, RepeatCell> rowRepeatCellMap = new HashMap<>();
|
|
||||||
for (Map.Entry<Field, FieldColumnIndex> item : mergeFields.entrySet()) {
|
|
||||||
Field field = item.getKey();
|
|
||||||
FieldColumnIndex itemValue = item.getValue();
|
|
||||||
int colNum = itemValue.colIndex();
|
|
||||||
CellMerge cellMerge = itemValue.cellMerge();
|
|
||||||
|
|
||||||
for (int i = 0; i < rows.size(); i++) {
|
|
||||||
// 当前行数据
|
|
||||||
Object currentRowObj = rows.get(i);
|
|
||||||
// 当前行数据字段值
|
|
||||||
Object currentRowObjFieldVal = ReflectUtils.invokeGetter(currentRowObj, field.getName());
|
|
||||||
|
|
||||||
// 空值跳过不处理
|
|
||||||
if (currentRowObjFieldVal == null || "".equals(currentRowObjFieldVal)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 单元格合并Map是否存在数据,如果不存在则添加当前行的字段值
|
|
||||||
if (!rowRepeatCellMap.containsKey(field)) {
|
|
||||||
rowRepeatCellMap.put(field, RepeatCell.of(currentRowObjFieldVal, i));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取 单元格合并Map 中字段值
|
|
||||||
RepeatCell repeatCell = rowRepeatCellMap.get(field);
|
|
||||||
Object cellValue = repeatCell.value();
|
|
||||||
int current = repeatCell.current();
|
|
||||||
|
|
||||||
// 检查是否满足合并条件
|
|
||||||
// currentRowObj 当前行数据
|
|
||||||
// rows.get(i - 1) 上一行数据 注:由于 if (!rowRepeatCellMap.containsKey(field)) 条件的存在,所以该 i 必不可能小于1
|
|
||||||
// cellMerge 当前行字段合并注解
|
|
||||||
boolean merge = isMerge(currentRowObj, rows.get(i - 1), cellMerge);
|
|
||||||
|
|
||||||
// 是否添加到结果集
|
|
||||||
boolean isAddResult = false;
|
|
||||||
// 最新行
|
|
||||||
int lastRow = i + rowIndex - 1;
|
|
||||||
|
|
||||||
// 如果当前行字段值和缓存中的字段值不相等,或不满足合并条件,则替换
|
|
||||||
if (!currentRowObjFieldVal.equals(cellValue) || !merge) {
|
|
||||||
rowRepeatCellMap.put(field, RepeatCell.of(currentRowObjFieldVal, i));
|
|
||||||
isAddResult = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果最后一行不能合并,检查之前的数据是否需要合并;如果最后一行可以合并,则直接合并到最后
|
|
||||||
if (i == rows.size() - 1) {
|
|
||||||
isAddResult = true;
|
|
||||||
if (i > current) {
|
|
||||||
lastRow = i + rowIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAddResult && i > current) {
|
|
||||||
result.add(new CellRangeAddress(current + rowIndex, lastRow, colNum, colNum));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取带有合并注解的字段列索引和合并注解信息Map集
|
|
||||||
*/
|
|
||||||
private Map<Field, FieldColumnIndex> getFieldColumnIndexMap(Class<?> clazz) {
|
|
||||||
boolean annotationPresent = clazz.isAnnotationPresent(ExcelIgnoreUnannotated.class);
|
|
||||||
Field[] fields = ReflectUtils.getFields(clazz, field -> {
|
|
||||||
if ("serialVersionUID".equals(field.getName())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (field.isAnnotationPresent(ExcelIgnore.class)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return !annotationPresent || field.isAnnotationPresent(ExcelProperty.class);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 有注解的字段
|
|
||||||
Map<Field, FieldColumnIndex> mergeFields = new HashMap<>();
|
|
||||||
for (int i = 0; i < fields.length; i++) {
|
|
||||||
Field field = fields[i];
|
|
||||||
if (!field.isAnnotationPresent(CellMerge.class)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
CellMerge cm = field.getAnnotation(CellMerge.class);
|
|
||||||
int index = cm.index() == -1 ? i : cm.index();
|
|
||||||
mergeFields.put(field, FieldColumnIndex.of(index, cm));
|
|
||||||
|
|
||||||
if (hasTitle) {
|
|
||||||
ExcelProperty property = field.getAnnotation(ExcelProperty.class);
|
|
||||||
rowIndex = Math.max(rowIndex, property.value().length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mergeFields;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isMerge(Object currentRow, Object preRow, CellMerge cellMerge) {
|
|
||||||
final String[] mergeBy = cellMerge.mergeBy();
|
|
||||||
if (StrUtil.isAllNotBlank(mergeBy)) {
|
|
||||||
//比对当前行和上一行的各个属性值一一比对 如果全为真 则为真
|
|
||||||
for (String fieldName : mergeBy) {
|
|
||||||
final Object valCurrent = ReflectUtil.getFieldValue(currentRow, fieldName);
|
|
||||||
final Object valPre = ReflectUtil.getFieldValue(preRow, fieldName);
|
|
||||||
if (!Objects.equals(valPre, valCurrent)) {
|
|
||||||
//依赖字段如有任一不等值,则标记为不可合并
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 单元格合并
|
|
||||||
*/
|
|
||||||
record RepeatCell(Object value, int current) {
|
|
||||||
static RepeatCell of(Object value, int current) {
|
|
||||||
return new RepeatCell(value, current);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 字段列索引和合并注解信息
|
|
||||||
*/
|
|
||||||
record FieldColumnIndex(int colIndex, CellMerge cellMerge) {
|
|
||||||
static FieldColumnIndex of(int colIndex, CellMerge cellMerge) {
|
|
||||||
return new FieldColumnIndex(colIndex, cellMerge);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建一个单元格合并处理器实例
|
|
||||||
*
|
|
||||||
* @param hasTitle 是否合并标题
|
|
||||||
* @return 单元格合并处理器
|
|
||||||
*/
|
|
||||||
public static CellMergeHandler of(final boolean hasTitle) {
|
|
||||||
return new CellMergeHandler(hasTitle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建一个单元格合并处理器实例(默认不合并标题)
|
|
||||||
*
|
|
||||||
* @return 单元格合并处理器
|
|
||||||
*/
|
|
||||||
public static CellMergeHandler of() {
|
|
||||||
return new CellMergeHandler(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,15 +1,24 @@
|
|||||||
package org.dromara.common.excel.core;
|
package org.dromara.common.excel.core;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.idev.excel.annotation.ExcelProperty;
|
||||||
import cn.idev.excel.metadata.Head;
|
import cn.idev.excel.metadata.Head;
|
||||||
import cn.idev.excel.write.handler.WorkbookWriteHandler;
|
import cn.idev.excel.write.handler.WorkbookWriteHandler;
|
||||||
import cn.idev.excel.write.handler.context.WorkbookWriteHandlerContext;
|
import cn.idev.excel.write.handler.context.WorkbookWriteHandlerContext;
|
||||||
import cn.idev.excel.write.merge.AbstractMergeStrategy;
|
import cn.idev.excel.write.merge.AbstractMergeStrategy;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.poi.ss.usermodel.Cell;
|
import org.apache.poi.ss.usermodel.Cell;
|
||||||
import org.apache.poi.ss.usermodel.Sheet;
|
import org.apache.poi.ss.usermodel.Sheet;
|
||||||
import org.apache.poi.ss.util.CellRangeAddress;
|
import org.apache.poi.ss.util.CellRangeAddress;
|
||||||
|
import org.dromara.common.core.utils.reflect.ReflectUtils;
|
||||||
|
import org.dromara.common.excel.annotation.CellMerge;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,22 +30,21 @@ import java.util.*;
|
|||||||
public class CellMergeStrategy extends AbstractMergeStrategy implements WorkbookWriteHandler {
|
public class CellMergeStrategy extends AbstractMergeStrategy implements WorkbookWriteHandler {
|
||||||
|
|
||||||
private final List<CellRangeAddress> cellList;
|
private final List<CellRangeAddress> cellList;
|
||||||
|
private final boolean hasTitle;
|
||||||
public CellMergeStrategy(List<CellRangeAddress> cellList) {
|
private int rowIndex;
|
||||||
this.cellList = cellList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CellMergeStrategy(List<?> list, boolean hasTitle) {
|
public CellMergeStrategy(List<?> list, boolean hasTitle) {
|
||||||
this.cellList = CellMergeHandler.of(hasTitle).handle(list);
|
this.hasTitle = hasTitle;
|
||||||
|
// 行合并开始下标
|
||||||
|
this.rowIndex = hasTitle ? 1 : 0;
|
||||||
|
this.cellList = handle(list, hasTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
|
protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
|
||||||
if (CollUtil.isEmpty(cellList)){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//单元格写入了,遍历合并区域,如果该Cell在区域内,但非首行,则清空
|
//单元格写入了,遍历合并区域,如果该Cell在区域内,但非首行,则清空
|
||||||
final int rowIndex = cell.getRowIndex();
|
final int rowIndex = cell.getRowIndex();
|
||||||
|
if (CollUtil.isNotEmpty(cellList)){
|
||||||
for (CellRangeAddress cellAddresses : cellList) {
|
for (CellRangeAddress cellAddresses : cellList) {
|
||||||
final int firstRow = cellAddresses.getFirstRow();
|
final int firstRow = cellAddresses.getFirstRow();
|
||||||
if (cellAddresses.isInRange(cell) && rowIndex != firstRow){
|
if (cellAddresses.isInRange(cell) && rowIndex != firstRow){
|
||||||
@ -44,16 +52,112 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements Workbook
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterWorkbookDispose(final WorkbookWriteHandlerContext context) {
|
public void afterWorkbookDispose(final WorkbookWriteHandlerContext context) {
|
||||||
if (CollUtil.isEmpty(cellList)){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//当前表格写完后,统一写入
|
//当前表格写完后,统一写入
|
||||||
|
if (CollUtil.isNotEmpty(cellList)){
|
||||||
for (CellRangeAddress item : cellList) {
|
for (CellRangeAddress item : cellList) {
|
||||||
context.getWriteContext().writeSheetHolder().getSheet().addMergedRegion(item);
|
context.getWriteContext().writeSheetHolder().getSheet().addMergedRegion(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
private List<CellRangeAddress> handle(List<?> list, boolean hasTitle) {
|
||||||
|
List<CellRangeAddress> cellList = new ArrayList<>();
|
||||||
|
if (CollUtil.isEmpty(list)) {
|
||||||
|
return cellList;
|
||||||
|
}
|
||||||
|
Field[] fields = ReflectUtils.getFields(list.get(0).getClass(), field -> !"serialVersionUID".equals(field.getName()));
|
||||||
|
|
||||||
|
// 有注解的字段
|
||||||
|
List<Field> mergeFields = new ArrayList<>();
|
||||||
|
List<Integer> mergeFieldsIndex = new ArrayList<>();
|
||||||
|
for (int i = 0; i < fields.length; i++) {
|
||||||
|
Field field = fields[i];
|
||||||
|
if (field.isAnnotationPresent(CellMerge.class)) {
|
||||||
|
CellMerge cm = field.getAnnotation(CellMerge.class);
|
||||||
|
mergeFields.add(field);
|
||||||
|
mergeFieldsIndex.add(cm.index() == -1 ? i : cm.index());
|
||||||
|
if (hasTitle) {
|
||||||
|
ExcelProperty property = field.getAnnotation(ExcelProperty.class);
|
||||||
|
rowIndex = Math.max(rowIndex, property.value().length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Field, RepeatCell> map = new HashMap<>();
|
||||||
|
// 生成两两合并单元格
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
for (int j = 0; j < mergeFields.size(); j++) {
|
||||||
|
Field field = mergeFields.get(j);
|
||||||
|
Object val = ReflectUtils.invokeGetter(list.get(i), field.getName());
|
||||||
|
|
||||||
|
int colNum = mergeFieldsIndex.get(j);
|
||||||
|
if (!map.containsKey(field)) {
|
||||||
|
map.put(field, new RepeatCell(val, i));
|
||||||
|
} else {
|
||||||
|
RepeatCell repeatCell = map.get(field);
|
||||||
|
Object cellValue = repeatCell.getValue();
|
||||||
|
if (cellValue == null || "".equals(cellValue)) {
|
||||||
|
// 空值跳过不合并
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cellValue.equals(val)) {
|
||||||
|
if ((i - repeatCell.getCurrent() > 1)) {
|
||||||
|
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
|
||||||
|
}
|
||||||
|
map.put(field, new RepeatCell(val, i));
|
||||||
|
} else if (i == list.size() - 1) {
|
||||||
|
if (!isMerge(list, i, field)) {
|
||||||
|
// 如果最后一行不能合并,检查之前的数据是否需要合并
|
||||||
|
if (i - repeatCell.getCurrent() > 1) {
|
||||||
|
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
|
||||||
|
}
|
||||||
|
} else if (i > repeatCell.getCurrent()) {
|
||||||
|
// 如果最后一行可以合并,则直接合并到最后
|
||||||
|
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
|
||||||
|
}
|
||||||
|
} else if (!isMerge(list, i, field)) {
|
||||||
|
if ((i - repeatCell.getCurrent() > 1)) {
|
||||||
|
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
|
||||||
|
}
|
||||||
|
map.put(field, new RepeatCell(val, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cellList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isMerge(List<?> list, int i, Field field) {
|
||||||
|
boolean isMerge = true;
|
||||||
|
CellMerge cm = field.getAnnotation(CellMerge.class);
|
||||||
|
final String[] mergeBy = cm.mergeBy();
|
||||||
|
if (StrUtil.isAllNotBlank(mergeBy)) {
|
||||||
|
//比对当前list(i)和list(i - 1)的各个属性值一一比对 如果全为真 则为真
|
||||||
|
for (String fieldName : mergeBy) {
|
||||||
|
final Object valCurrent = ReflectUtil.getFieldValue(list.get(i), fieldName);
|
||||||
|
final Object valPre = ReflectUtil.getFieldValue(list.get(i - 1), fieldName);
|
||||||
|
if (!Objects.equals(valPre, valCurrent)) {
|
||||||
|
//依赖字段如有任一不等值,则标记为不可合并
|
||||||
|
isMerge = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isMerge;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
static class RepeatCell {
|
||||||
|
|
||||||
|
private Object value;
|
||||||
|
|
||||||
|
private int current;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.dromara.common.excel.core;
|
package org.dromara.common.excel.core;
|
||||||
|
|
||||||
import cn.hutool.core.convert.Convert;
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@ -66,7 +65,7 @@ public class DropDownOptions {
|
|||||||
StringBuilder stringBuffer = new StringBuilder();
|
StringBuilder stringBuffer = new StringBuilder();
|
||||||
String regex = "^[\\S\\d\\u4e00-\\u9fa5]+$";
|
String regex = "^[\\S\\d\\u4e00-\\u9fa5]+$";
|
||||||
for (int i = 0; i < vars.length; i++) {
|
for (int i = 0; i < vars.length; i++) {
|
||||||
String var = StrUtil.trimToEmpty(Convert.toStr(vars[i]));
|
String var = StrUtil.trimToEmpty(String.valueOf(vars[i]));
|
||||||
if (!var.matches(regex)) {
|
if (!var.matches(regex)) {
|
||||||
throw new ServiceException("选项数据不符合规则,仅允许使用中英文字符以及数字");
|
throw new ServiceException("选项数据不符合规则,仅允许使用中英文字符以及数字");
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package org.dromara.common.excel.core;
|
package org.dromara.common.excel.core;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.convert.Convert;
|
|
||||||
import cn.hutool.core.util.ArrayUtil;
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
import cn.hutool.core.util.EnumUtil;
|
import cn.hutool.core.util.EnumUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
@ -104,7 +103,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
if (StringUtils.isNotBlank(dictType)) {
|
if (StringUtils.isNotBlank(dictType)) {
|
||||||
// 如果传递了字典名,则依据字典建立下拉
|
// 如果传递了字典名,则依据字典建立下拉
|
||||||
Collection<String> values = Optional.ofNullable(dictService.getAllDictByDictType(dictType))
|
Collection<String> values = Optional.ofNullable(dictService.getAllDictByDictType(dictType))
|
||||||
.orElseThrow(() -> new ServiceException("字典 {} 不存在", dictType))
|
.orElseThrow(() -> new ServiceException(String.format("字典 %s 不存在", dictType)))
|
||||||
.values();
|
.values();
|
||||||
options = new ArrayList<>(values);
|
options = new ArrayList<>(values);
|
||||||
} else if (StringUtils.isNotBlank(converterExp)) {
|
} else if (StringUtils.isNotBlank(converterExp)) {
|
||||||
@ -116,7 +115,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
// 否则如果指定了@ExcelEnumFormat,则使用枚举的逻辑
|
// 否则如果指定了@ExcelEnumFormat,则使用枚举的逻辑
|
||||||
ExcelEnumFormat format = field.getDeclaredAnnotation(ExcelEnumFormat.class);
|
ExcelEnumFormat format = field.getDeclaredAnnotation(ExcelEnumFormat.class);
|
||||||
List<Object> values = EnumUtil.getFieldValues(format.enumClass(), format.textField());
|
List<Object> values = EnumUtil.getFieldValues(format.enumClass(), format.textField());
|
||||||
options = StreamUtils.toList(values, Convert::toStr);
|
options = StreamUtils.toList(values, String::valueOf);
|
||||||
}
|
}
|
||||||
if (ObjectUtil.isNotEmpty(options)) {
|
if (ObjectUtil.isNotEmpty(options)) {
|
||||||
// 仅当下拉可选项不为空时执行
|
// 仅当下拉可选项不为空时执行
|
||||||
|
@ -27,7 +27,6 @@ import java.io.UnsupportedEncodingException;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Excel相关处理
|
* Excel相关处理
|
||||||
@ -204,44 +203,6 @@ public class ExcelUtil {
|
|||||||
builder.doWrite(list);
|
builder.doWrite(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 导出excel
|
|
||||||
*
|
|
||||||
* @param headType 带Excel注解的类型
|
|
||||||
* @param os 输出流
|
|
||||||
* @param options Excel下拉可选项
|
|
||||||
* @param consumer 导出助手消费函数
|
|
||||||
*/
|
|
||||||
public static <T> void exportExcel(Class<T> headType, OutputStream os, List<DropDownOptions> options, Consumer<ExcelWriterWrapper<T>> consumer) {
|
|
||||||
try (ExcelWriter writer = FastExcel.write(os, headType)
|
|
||||||
.autoCloseStream(false)
|
|
||||||
// 自动适配
|
|
||||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
|
||||||
// 大数值自动转换 防止失真
|
|
||||||
.registerConverter(new ExcelBigNumberConvert())
|
|
||||||
// 批注必填项处理
|
|
||||||
.registerWriteHandler(new DataWriteHandler(headType))
|
|
||||||
// 添加下拉框操作
|
|
||||||
.registerWriteHandler(new ExcelDownHandler(options))
|
|
||||||
.build()) {
|
|
||||||
// 执行消费函数
|
|
||||||
consumer.accept(ExcelWriterWrapper.of(writer));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 导出excel
|
|
||||||
*
|
|
||||||
* @param headType 带Excel注解的类型
|
|
||||||
* @param os 输出流
|
|
||||||
* @param consumer 导出助手消费函数
|
|
||||||
*/
|
|
||||||
public static <T> void exportExcel(Class<T> headType, OutputStream os, Consumer<ExcelWriterWrapper<T>> consumer) {
|
|
||||||
exportExcel(headType, os, null, consumer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单表多数据模板导出 模板格式为 {.属性}
|
* 单表多数据模板导出 模板格式为 {.属性}
|
||||||
*
|
*
|
||||||
|
@ -1,127 +0,0 @@
|
|||||||
package org.dromara.common.excel.utils;
|
|
||||||
|
|
||||||
import cn.idev.excel.ExcelWriter;
|
|
||||||
import cn.idev.excel.FastExcel;
|
|
||||||
import cn.idev.excel.context.WriteContext;
|
|
||||||
import cn.idev.excel.write.builder.ExcelWriterSheetBuilder;
|
|
||||||
import cn.idev.excel.write.builder.ExcelWriterTableBuilder;
|
|
||||||
import cn.idev.excel.write.metadata.WriteSheet;
|
|
||||||
import cn.idev.excel.write.metadata.WriteTable;
|
|
||||||
import cn.idev.excel.write.metadata.fill.FillConfig;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ExcelWriterWrapper Excel写出包装器
|
|
||||||
* <br>
|
|
||||||
* 提供了一组与 ExcelWriter 一一对应的写出方法,避免直接提供 ExcelWriter 而导致的一些不可控问题(比如提前关闭了IO流等)
|
|
||||||
*
|
|
||||||
* @author 秋辞未寒
|
|
||||||
* @see ExcelWriter
|
|
||||||
*/
|
|
||||||
public record ExcelWriterWrapper<T>(ExcelWriter excelWriter) {
|
|
||||||
|
|
||||||
public void write(Collection<T> data, WriteSheet writeSheet) {
|
|
||||||
excelWriter.write(data, writeSheet);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void write(Supplier<Collection<T>> supplier, WriteSheet writeSheet) {
|
|
||||||
excelWriter.write(supplier.get(), writeSheet);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void write(Collection<T> data, WriteSheet writeSheet, WriteTable writeTable) {
|
|
||||||
excelWriter.write(data, writeSheet, writeTable);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void write(Supplier<Collection<T>> supplier, WriteSheet writeSheet, WriteTable writeTable) {
|
|
||||||
excelWriter.write(supplier.get(), writeSheet, writeTable);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void fill(Object data, WriteSheet writeSheet) {
|
|
||||||
excelWriter.fill(data, writeSheet);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void fill(Object data, FillConfig fillConfig, WriteSheet writeSheet) {
|
|
||||||
excelWriter.fill(data, fillConfig, writeSheet);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void fill(Supplier<Object> supplier, WriteSheet writeSheet) {
|
|
||||||
excelWriter.fill(supplier, writeSheet);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void fill(Supplier<Object> supplier, FillConfig fillConfig, WriteSheet writeSheet) {
|
|
||||||
excelWriter.fill(supplier, fillConfig, writeSheet);
|
|
||||||
}
|
|
||||||
|
|
||||||
public WriteContext writeContext() {
|
|
||||||
return excelWriter.writeContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建一个 ExcelWriterWrapper
|
|
||||||
*
|
|
||||||
* @param excelWriter ExcelWriter
|
|
||||||
* @return ExcelWriterWrapper
|
|
||||||
*/
|
|
||||||
public static <T> ExcelWriterWrapper<T> of(ExcelWriter excelWriter) {
|
|
||||||
return new ExcelWriterWrapper<>(excelWriter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------- sheet start
|
|
||||||
|
|
||||||
public static WriteSheet buildSheet(Integer sheetNo, String sheetName) {
|
|
||||||
return sheetBuilder(sheetNo, sheetName).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static WriteSheet buildSheet(Integer sheetNo) {
|
|
||||||
return sheetBuilder(sheetNo).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static WriteSheet buildSheet(String sheetName) {
|
|
||||||
return sheetBuilder(sheetName).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static WriteSheet buildSheet() {
|
|
||||||
return sheetBuilder().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ExcelWriterSheetBuilder sheetBuilder(Integer sheetNo, String sheetName) {
|
|
||||||
return FastExcel.writerSheet(sheetNo, sheetName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ExcelWriterSheetBuilder sheetBuilder(Integer sheetNo) {
|
|
||||||
return FastExcel.writerSheet(sheetNo);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ExcelWriterSheetBuilder sheetBuilder(String sheetName) {
|
|
||||||
return FastExcel.writerSheet(sheetName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ExcelWriterSheetBuilder sheetBuilder() {
|
|
||||||
return FastExcel.writerSheet();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------- sheet end
|
|
||||||
|
|
||||||
// -------------------------------- table start
|
|
||||||
|
|
||||||
public static WriteTable buildTable(Integer tableNo) {
|
|
||||||
return tableBuilder(tableNo).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static WriteTable buildTable() {
|
|
||||||
return tableBuilder().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ExcelWriterTableBuilder tableBuilder(Integer tableNo) {
|
|
||||||
return FastExcel.writerTable(tableNo);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ExcelWriterTableBuilder tableBuilder() {
|
|
||||||
return FastExcel.writerTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------- table end
|
|
||||||
|
|
||||||
}
|
|
@ -1,6 +1,5 @@
|
|||||||
package org.dromara.common.json.config;
|
package org.dromara.common.json.config;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.Module;
|
|
||||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||||
@ -30,7 +29,8 @@ import java.util.TimeZone;
|
|||||||
public class JacksonConfig {
|
public class JacksonConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public Module registerJavaTimeModule() {
|
public Jackson2ObjectMapperBuilderCustomizer customizer() {
|
||||||
|
return builder -> {
|
||||||
// 全局配置序列化返回 JSON 处理
|
// 全局配置序列化返回 JSON 处理
|
||||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||||
javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE);
|
javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE);
|
||||||
@ -41,12 +41,7 @@ public class JacksonConfig {
|
|||||||
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
|
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
|
||||||
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
|
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
|
||||||
javaTimeModule.addDeserializer(Date.class, new CustomDateDeserializer());
|
javaTimeModule.addDeserializer(Date.class, new CustomDateDeserializer());
|
||||||
return javaTimeModule;
|
builder.modules(javaTimeModule);
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public Jackson2ObjectMapperBuilderCustomizer customizer() {
|
|
||||||
return builder -> {
|
|
||||||
builder.timeZone(TimeZone.getDefault());
|
builder.timeZone(TimeZone.getDefault());
|
||||||
log.info("初始化 jackson 配置");
|
log.info("初始化 jackson 配置");
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
package org.dromara.common.json.handler;
|
package org.dromara.common.json.handler;
|
||||||
|
|
||||||
import cn.hutool.core.date.DateTime;
|
|
||||||
import cn.hutool.core.date.DateUtil;
|
import cn.hutool.core.date.DateUtil;
|
||||||
import com.fasterxml.jackson.core.JsonParser;
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
import org.dromara.common.core.utils.ObjectUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -27,11 +25,7 @@ public class CustomDateDeserializer extends JsonDeserializer<Date> {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||||
DateTime parse = DateUtil.parse(p.getText());
|
return DateUtil.parse(p.getText());
|
||||||
if (ObjectUtils.isNull(parse)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parse.toJdkDate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,9 @@ import org.springframework.http.HttpMethod;
|
|||||||
import org.springframework.validation.BindingResult;
|
import org.springframework.validation.BindingResult;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.StringJoiner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 操作日志记录处理
|
* 操作日志记录处理
|
||||||
@ -174,29 +176,15 @@ public class LogAspect {
|
|||||||
if (ArrayUtil.isEmpty(paramsArray)) {
|
if (ArrayUtil.isEmpty(paramsArray)) {
|
||||||
return params.toString();
|
return params.toString();
|
||||||
}
|
}
|
||||||
String[] exclude = ArrayUtil.addAll(excludeParamNames, EXCLUDE_PROPERTIES);
|
|
||||||
for (Object o : paramsArray) {
|
for (Object o : paramsArray) {
|
||||||
if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
|
if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
|
||||||
String str = "";
|
String str = JsonUtils.toJsonString(o);
|
||||||
if (o instanceof List<?> list) {
|
|
||||||
List<Dict> list1 = new ArrayList<>();
|
|
||||||
for (Object obj : list) {
|
|
||||||
String str1 = JsonUtils.toJsonString(obj);
|
|
||||||
Dict dict = JsonUtils.parseMap(str1);
|
|
||||||
if (MapUtil.isNotEmpty(dict)) {
|
|
||||||
MapUtil.removeAny(dict, exclude);
|
|
||||||
list1.add(dict);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
str = JsonUtils.toJsonString(list1);
|
|
||||||
} else {
|
|
||||||
str = JsonUtils.toJsonString(o);
|
|
||||||
Dict dict = JsonUtils.parseMap(str);
|
Dict dict = JsonUtils.parseMap(str);
|
||||||
if (MapUtil.isNotEmpty(dict)) {
|
if (MapUtil.isNotEmpty(dict)) {
|
||||||
MapUtil.removeAny(dict, exclude);
|
MapUtil.removeAny(dict, EXCLUDE_PROPERTIES);
|
||||||
|
MapUtil.removeAny(dict, excludeParamNames);
|
||||||
str = JsonUtils.toJsonString(dict);
|
str = JsonUtils.toJsonString(dict);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
params.add(str);
|
params.add(str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
package org.dromara.common.mybatis.aspect;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.aopalliance.intercept.MethodInterceptor;
|
|
||||||
import org.aopalliance.intercept.MethodInvocation;
|
|
||||||
import org.dromara.common.mybatis.annotation.DataPermission;
|
|
||||||
import org.dromara.common.mybatis.helper.DataPermissionHelper;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Proxy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数据权限注解Advice
|
|
||||||
*
|
|
||||||
* @author 秋辞未寒
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class DataPermissionAdvice implements MethodInterceptor {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
|
||||||
Object target = invocation.getThis();
|
|
||||||
Method method = invocation.getMethod();
|
|
||||||
Object[] args = invocation.getArguments();
|
|
||||||
// 设置权限注解
|
|
||||||
DataPermissionHelper.setPermission(getDataPermissionAnnotation(target, method, args));
|
|
||||||
try {
|
|
||||||
// 执行代理方法
|
|
||||||
return invocation.proceed();
|
|
||||||
} finally {
|
|
||||||
// 清除权限注解
|
|
||||||
DataPermissionHelper.removePermission();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取数据权限注解
|
|
||||||
*/
|
|
||||||
private DataPermission getDataPermissionAnnotation(Object target, Method method,Object[] args){
|
|
||||||
DataPermission dataPermission = method.getAnnotation(DataPermission.class);
|
|
||||||
// 优先获取方法上的注解
|
|
||||||
if (dataPermission != null) {
|
|
||||||
return dataPermission;
|
|
||||||
}
|
|
||||||
// 方法上没有注解,则获取类上的注解
|
|
||||||
Class<?> targetClass = target.getClass();
|
|
||||||
// 如果是 JDK 动态代理,则获取真实的Class实例
|
|
||||||
if (Proxy.isProxyClass(targetClass)) {
|
|
||||||
targetClass = targetClass.getInterfaces()[0];
|
|
||||||
}
|
|
||||||
dataPermission = targetClass.getAnnotation(DataPermission.class);
|
|
||||||
return dataPermission;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,50 @@
|
|||||||
|
package org.dromara.common.mybatis.aspect;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.aspectj.lang.JoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.AfterReturning;
|
||||||
|
import org.aspectj.lang.annotation.AfterThrowing;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.aspectj.lang.annotation.Before;
|
||||||
|
import org.dromara.common.mybatis.annotation.DataPermission;
|
||||||
|
import org.dromara.common.mybatis.helper.DataPermissionHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限处理
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Aspect
|
||||||
|
public class DataPermissionAspect {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理请求前执行
|
||||||
|
*/
|
||||||
|
@Before(value = "@annotation(dataPermission)")
|
||||||
|
public void doBefore(JoinPoint joinPoint, DataPermission dataPermission) {
|
||||||
|
DataPermissionHelper.setPermission(dataPermission);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理完请求后执行
|
||||||
|
*
|
||||||
|
* @param joinPoint 切点
|
||||||
|
*/
|
||||||
|
@AfterReturning(pointcut = "@annotation(dataPermission)")
|
||||||
|
public void doAfterReturning(JoinPoint joinPoint, DataPermission dataPermission) {
|
||||||
|
DataPermissionHelper.removePermission();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拦截异常操作
|
||||||
|
*
|
||||||
|
* @param joinPoint 切点
|
||||||
|
* @param e 异常
|
||||||
|
*/
|
||||||
|
@AfterThrowing(value = "@annotation(dataPermission)", throwing = "e")
|
||||||
|
public void doAfterThrowing(JoinPoint joinPoint, DataPermission dataPermission, Exception e) {
|
||||||
|
DataPermissionHelper.removePermission();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,39 +0,0 @@
|
|||||||
package org.dromara.common.mybatis.aspect;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.dromara.common.mybatis.annotation.DataPermission;
|
|
||||||
import org.springframework.aop.support.StaticMethodMatcherPointcut;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Proxy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数据权限匹配切点
|
|
||||||
*
|
|
||||||
* @author 秋辞未寒
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@SuppressWarnings("all")
|
|
||||||
public class DataPermissionPointcut extends StaticMethodMatcherPointcut {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean matches(Method method, Class<?> targetClass) {
|
|
||||||
// 优先匹配方法
|
|
||||||
// 数据权限注解不对继承生效,所以检查当前方法是否有注解即可,不再往上匹配父类或接口
|
|
||||||
if (method.isAnnotationPresent(DataPermission.class)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// MyBatis 的 Mapper 就是通过 JDK 动态代理实现的,所以这里需要检查是否匹配 JDK 的动态代理
|
|
||||||
Class<?> targetClassRef = targetClass;
|
|
||||||
if (Proxy.isProxyClass(targetClassRef)) {
|
|
||||||
// 数据权限注解不对继承生效,但由于 SpringIOC 容器拿到的实际上是 MyBatis 代理过后的 Mapper,而 targetClass.isAnnotationPresent 实际匹配的是 Proxy 类的注解,不会查找代理类。
|
|
||||||
// 所以这里不能用 targetClass.isAnnotationPresent,只能用 AnnotatedElementUtils.hasAnnotation 或 targetClass.getInterfaces()[0].isAnnotationPresent 去做匹配,以检查被代理的 MapperClass 是否具有注解
|
|
||||||
// 原理:JDK 动态代理本质上就是对接口进行实现然后对具体的接口实现做代理,所以直接通过接口可以拿到实际的 MapperClass
|
|
||||||
targetClassRef = targetClass.getInterfaces()[0];
|
|
||||||
|
|
||||||
}
|
|
||||||
return targetClassRef.isAnnotationPresent(DataPermission.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package org.dromara.common.mybatis.aspect;
|
|
||||||
|
|
||||||
import org.aopalliance.aop.Advice;
|
|
||||||
import org.springframework.aop.Pointcut;
|
|
||||||
import org.springframework.aop.support.AbstractPointcutAdvisor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数据权限注解切面定义
|
|
||||||
*
|
|
||||||
* @author 秋辞未寒
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("all")
|
|
||||||
public class DataPermissionPointcutAdvisor extends AbstractPointcutAdvisor {
|
|
||||||
|
|
||||||
private final Advice advice;
|
|
||||||
private final Pointcut pointcut;
|
|
||||||
|
|
||||||
public DataPermissionPointcutAdvisor() {
|
|
||||||
this.advice = new DataPermissionAdvice();
|
|
||||||
this.pointcut = new DataPermissionPointcut();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pointcut getPointcut() {
|
|
||||||
return this.pointcut;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Advice getAdvice() {
|
|
||||||
return this.advice;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -11,17 +11,15 @@ import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerIntercept
|
|||||||
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
||||||
import org.dromara.common.core.factory.YmlPropertySourceFactory;
|
import org.dromara.common.core.factory.YmlPropertySourceFactory;
|
||||||
import org.dromara.common.core.utils.SpringUtils;
|
import org.dromara.common.core.utils.SpringUtils;
|
||||||
import org.dromara.common.mybatis.aspect.DataPermissionPointcutAdvisor;
|
import org.dromara.common.mybatis.aspect.DataPermissionAspect;
|
||||||
import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler;
|
import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler;
|
||||||
import org.dromara.common.mybatis.handler.MybatisExceptionHandler;
|
import org.dromara.common.mybatis.handler.MybatisExceptionHandler;
|
||||||
import org.dromara.common.mybatis.handler.PlusPostInitTableInfoHandler;
|
import org.dromara.common.mybatis.handler.PlusPostInitTableInfoHandler;
|
||||||
import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor;
|
import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor;
|
||||||
import org.mybatis.spring.annotation.MapperScan;
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
import org.springframework.beans.factory.config.BeanDefinition;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.PropertySource;
|
import org.springframework.context.annotation.PropertySource;
|
||||||
import org.springframework.context.annotation.Role;
|
|
||||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,7 +27,6 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
|||||||
*
|
*
|
||||||
* @author Lion Li
|
* @author Lion Li
|
||||||
*/
|
*/
|
||||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
|
||||||
@EnableTransactionManagement(proxyTargetClass = true)
|
@EnableTransactionManagement(proxyTargetClass = true)
|
||||||
@MapperScan("${mybatis-plus.mapperPackage}")
|
@MapperScan("${mybatis-plus.mapperPackage}")
|
||||||
@PropertySource(value = "classpath:common-mybatis.yml", factory = YmlPropertySourceFactory.class)
|
@PropertySource(value = "classpath:common-mybatis.yml", factory = YmlPropertySourceFactory.class)
|
||||||
@ -57,16 +54,15 @@ public class MybatisPlusConfig {
|
|||||||
* 数据权限拦截器
|
* 数据权限拦截器
|
||||||
*/
|
*/
|
||||||
public PlusDataPermissionInterceptor dataPermissionInterceptor() {
|
public PlusDataPermissionInterceptor dataPermissionInterceptor() {
|
||||||
return new PlusDataPermissionInterceptor();
|
return new PlusDataPermissionInterceptor(SpringUtils.getProperty("mybatis-plus.mapperPackage"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据权限切面处理器
|
* 数据权限切面处理器
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
public DataPermissionAspect dataPermissionAspect() {
|
||||||
public DataPermissionPointcutAdvisor dataPermissionPointcutAdvisor() {
|
return new DataPermissionAspect();
|
||||||
return new DataPermissionPointcutAdvisor();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,46 +42,17 @@ public enum DataBaseType {
|
|||||||
* 根据数据库产品名称查找对应的数据库类型
|
* 根据数据库产品名称查找对应的数据库类型
|
||||||
*
|
*
|
||||||
* @param databaseProductName 数据库产品名称
|
* @param databaseProductName 数据库产品名称
|
||||||
* @return 对应的数据库类型枚举值
|
* @return 对应的数据库类型枚举值,如果未找到则返回 null
|
||||||
*/
|
*/
|
||||||
public static DataBaseType find(String databaseProductName) {
|
public static DataBaseType find(String databaseProductName) {
|
||||||
if (StringUtils.isBlank(databaseProductName)) {
|
if (StringUtils.isBlank(databaseProductName)) {
|
||||||
return MY_SQL;
|
return null;
|
||||||
}
|
}
|
||||||
for (DataBaseType type : values()) {
|
for (DataBaseType type : values()) {
|
||||||
if (type.getType().equals(databaseProductName)) {
|
if (type.getType().equals(databaseProductName)) {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return MY_SQL;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断是否为 MySQL 类型
|
|
||||||
*/
|
|
||||||
public boolean isMySql() {
|
|
||||||
return this == MY_SQL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断是否为 Oracle 类型
|
|
||||||
*/
|
|
||||||
public boolean isOracle() {
|
|
||||||
return this == ORACLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断是否为 PostgreSQL 类型
|
|
||||||
*/
|
|
||||||
public boolean isPostgreSql() {
|
|
||||||
return this == POSTGRE_SQL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断是否为 SQL Server 类型
|
|
||||||
*/
|
|
||||||
public boolean isSqlServer() {
|
|
||||||
return this == SQL_SERVER;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package org.dromara.common.mybatis.handler;
|
package org.dromara.common.mybatis.handler;
|
||||||
|
|
||||||
|
import cn.hutool.core.annotation.AnnotationUtil;
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
@ -9,6 +10,7 @@ import net.sf.jsqlparser.expression.Expression;
|
|||||||
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
||||||
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
|
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
|
||||||
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
|
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
|
||||||
|
import org.apache.ibatis.io.Resources;
|
||||||
import org.dromara.common.core.domain.dto.RoleDTO;
|
import org.dromara.common.core.domain.dto.RoleDTO;
|
||||||
import org.dromara.common.core.domain.model.LoginUser;
|
import org.dromara.common.core.domain.model.LoginUser;
|
||||||
import org.dromara.common.core.exception.ServiceException;
|
import org.dromara.common.core.exception.ServiceException;
|
||||||
@ -20,13 +22,22 @@ import org.dromara.common.mybatis.annotation.DataPermission;
|
|||||||
import org.dromara.common.mybatis.enums.DataScopeType;
|
import org.dromara.common.mybatis.enums.DataScopeType;
|
||||||
import org.dromara.common.mybatis.helper.DataPermissionHelper;
|
import org.dromara.common.mybatis.helper.DataPermissionHelper;
|
||||||
import org.dromara.common.satoken.utils.LoginHelper;
|
import org.dromara.common.satoken.utils.LoginHelper;
|
||||||
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
import org.springframework.context.expression.BeanFactoryResolver;
|
import org.springframework.context.expression.BeanFactoryResolver;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||||
|
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||||
|
import org.springframework.core.type.ClassMetadata;
|
||||||
|
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
|
||||||
import org.springframework.expression.*;
|
import org.springframework.expression.*;
|
||||||
import org.springframework.expression.common.TemplateParserContext;
|
import org.springframework.expression.common.TemplateParserContext;
|
||||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,6 +49,11 @@ import java.util.function.Function;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class PlusDataPermissionHandler {
|
public class PlusDataPermissionHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类名称与注解的映射关系缓存(由于aop无法拦截mybatis接口类上的注解 只能通过启动预扫描的方式进行)
|
||||||
|
*/
|
||||||
|
private final Map<String, DataPermission> dataPermissionCacheMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* spel 解析器
|
* spel 解析器
|
||||||
*/
|
*/
|
||||||
@ -48,17 +64,27 @@ public class PlusDataPermissionHandler {
|
|||||||
*/
|
*/
|
||||||
private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory());
|
private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造方法,扫描指定包下的 Mapper 类并初始化缓存
|
||||||
|
*
|
||||||
|
* @param mapperPackage Mapper 类所在的包路径
|
||||||
|
*/
|
||||||
|
public PlusDataPermissionHandler(String mapperPackage) {
|
||||||
|
scanMapperClasses(mapperPackage);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取数据过滤条件的 SQL 片段
|
* 获取数据过滤条件的 SQL 片段
|
||||||
*
|
*
|
||||||
* @param where 原始的查询条件表达式
|
* @param where 原始的查询条件表达式
|
||||||
|
* @param mappedStatementId Mapper 方法的 ID
|
||||||
* @param isSelect 是否为查询语句
|
* @param isSelect 是否为查询语句
|
||||||
* @return 数据过滤条件的 SQL 片段
|
* @return 数据过滤条件的 SQL 片段
|
||||||
*/
|
*/
|
||||||
public Expression getSqlSegment(Expression where, boolean isSelect) {
|
public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) {
|
||||||
try {
|
try {
|
||||||
// 获取数据权限配置
|
// 获取数据权限配置
|
||||||
DataPermission dataPermission = getDataPermission();
|
DataPermission dataPermission = getDataPermission(mappedStatementId);
|
||||||
// 获取当前登录用户信息
|
// 获取当前登录用户信息
|
||||||
LoginUser currentUser = DataPermissionHelper.getVariable("user");
|
LoginUser currentUser = DataPermissionHelper.getVariable("user");
|
||||||
if (ObjectUtil.isNull(currentUser)) {
|
if (ObjectUtil.isNull(currentUser)) {
|
||||||
@ -180,22 +206,92 @@ public class PlusDataPermissionHandler {
|
|||||||
return StringUtils.EMPTY;
|
return StringUtils.EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扫描指定包下的 Mapper 类,并查找其中带有特定注解的方法或类
|
||||||
|
*
|
||||||
|
* @param mapperPackage Mapper 类所在的包路径
|
||||||
|
*/
|
||||||
|
private void scanMapperClasses(String mapperPackage) {
|
||||||
|
// 创建资源解析器和元数据读取工厂
|
||||||
|
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||||
|
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
|
||||||
|
// 将 Mapper 包路径按分隔符拆分为数组
|
||||||
|
String[] packagePatternArray = StringUtils.splitPreserveAllTokens(mapperPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
|
||||||
|
String classpath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX;
|
||||||
|
try {
|
||||||
|
for (String packagePattern : packagePatternArray) {
|
||||||
|
// 将包路径转换为资源路径
|
||||||
|
String path = ClassUtils.convertClassNameToResourcePath(packagePattern);
|
||||||
|
// 获取指定路径下的所有 .class 文件资源
|
||||||
|
Resource[] resources = resolver.getResources(classpath + path + "/*.class");
|
||||||
|
for (Resource resource : resources) {
|
||||||
|
// 获取资源的类元数据
|
||||||
|
ClassMetadata classMetadata = factory.getMetadataReader(resource).getClassMetadata();
|
||||||
|
// 获取资源对应的类对象
|
||||||
|
Class<?> clazz = Resources.classForName(classMetadata.getClassName());
|
||||||
|
// 查找类中的特定注解
|
||||||
|
findAnnotation(clazz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("初始化数据安全缓存时出错:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在指定的类中查找特定的注解 DataPermission,并将带有这个注解的方法或类存储到 dataPermissionCacheMap 中
|
||||||
|
*
|
||||||
|
* @param clazz 要查找的类
|
||||||
|
*/
|
||||||
|
private void findAnnotation(Class<?> clazz) {
|
||||||
|
DataPermission dataPermission;
|
||||||
|
for (Method method : clazz.getMethods()) {
|
||||||
|
if (method.isDefault() || method.isVarArgs()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String mappedStatementId = clazz.getName() + "." + method.getName();
|
||||||
|
if (AnnotationUtil.hasAnnotation(method, DataPermission.class)) {
|
||||||
|
dataPermission = AnnotationUtil.getAnnotation(method, DataPermission.class);
|
||||||
|
dataPermissionCacheMap.put(mappedStatementId, dataPermission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) {
|
||||||
|
dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class);
|
||||||
|
dataPermissionCacheMap.put(clazz.getName(), dataPermission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据映射语句 ID 或类名获取对应的 DataPermission 注解对象
|
* 根据映射语句 ID 或类名获取对应的 DataPermission 注解对象
|
||||||
*
|
*
|
||||||
|
* @param mapperId 映射语句 ID
|
||||||
* @return DataPermission 注解对象,如果不存在则返回 null
|
* @return DataPermission 注解对象,如果不存在则返回 null
|
||||||
*/
|
*/
|
||||||
public DataPermission getDataPermission() {
|
public DataPermission getDataPermission(String mapperId) {
|
||||||
|
// 检查上下文中是否包含映射语句 ID 对应的 DataPermission 注解对象
|
||||||
|
if (DataPermissionHelper.getPermission() != null) {
|
||||||
return DataPermissionHelper.getPermission();
|
return DataPermissionHelper.getPermission();
|
||||||
}
|
}
|
||||||
|
// 检查缓存中是否包含映射语句 ID 对应的 DataPermission 注解对象
|
||||||
|
if (dataPermissionCacheMap.containsKey(mapperId)) {
|
||||||
|
return dataPermissionCacheMap.get(mapperId);
|
||||||
|
}
|
||||||
|
// 如果缓存中不包含映射语句 ID 对应的 DataPermission 注解对象,则尝试使用类名作为键查找
|
||||||
|
String clazzName = mapperId.substring(0, mapperId.lastIndexOf("."));
|
||||||
|
if (dataPermissionCacheMap.containsKey(clazzName)) {
|
||||||
|
return dataPermissionCacheMap.get(clazzName);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查给定的映射语句 ID 是否有效,即是否能够找到对应的 DataPermission 注解对象
|
* 检查给定的映射语句 ID 是否有效,即是否能够找到对应的 DataPermission 注解对象
|
||||||
*
|
*
|
||||||
|
* @param mapperId 映射语句 ID
|
||||||
* @return 如果找到对应的 DataPermission 注解对象,则返回 false;否则返回 true
|
* @return 如果找到对应的 DataPermission 注解对象,则返回 false;否则返回 true
|
||||||
*/
|
*/
|
||||||
public boolean invalid() {
|
public boolean invalid(String mapperId) {
|
||||||
return getDataPermission() == null;
|
return getDataPermission(mapperId) == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,14 +26,7 @@ public class DataBaseHelper {
|
|||||||
private static final DynamicRoutingDataSource DS = SpringUtils.getBean(DynamicRoutingDataSource.class);
|
private static final DynamicRoutingDataSource DS = SpringUtils.getBean(DynamicRoutingDataSource.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前数据源对应的数据库类型
|
* 获取当前数据库类型
|
||||||
* <p>
|
|
||||||
* 通过 DynamicRoutingDataSource 获取当前线程绑定的数据源,
|
|
||||||
* 然后从数据源获取数据库连接,利用连接的元数据获取数据库产品名称,
|
|
||||||
* 最后调用 DataBaseType.find 方法将数据库名称转换为对应的枚举类型
|
|
||||||
*
|
|
||||||
* @return 当前数据库对应的 DataBaseType 枚举,找不到时默认返回 MY_SQL
|
|
||||||
* @throws ServiceException 当获取数据库连接或元数据出现异常时抛出业务异常
|
|
||||||
*/
|
*/
|
||||||
public static DataBaseType getDataBaseType() {
|
public static DataBaseType getDataBaseType() {
|
||||||
DataSource dataSource = DS.determineDataSource();
|
DataSource dataSource = DS.determineDataSource();
|
||||||
@ -46,31 +39,37 @@ public class DataBaseHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static boolean isMySql() {
|
||||||
* 根据当前数据库类型,生成兼容的 FIND_IN_SET 语句片段
|
return DataBaseType.MY_SQL == getDataBaseType();
|
||||||
* <p>
|
}
|
||||||
* 用于判断指定值是否存在于逗号分隔的字符串列中,SQL写法根据不同数据库方言自动切换:
|
|
||||||
* - Oracle 使用 instr 函数
|
public static boolean isOracle() {
|
||||||
* - PostgreSQL 使用 strpos 函数
|
return DataBaseType.ORACLE == getDataBaseType();
|
||||||
* - SQL Server 使用 charindex 函数
|
}
|
||||||
* - 其他默认使用 MySQL 的 find_in_set 函数
|
|
||||||
*
|
public static boolean isPostgerSql() {
|
||||||
* @param var1 要查找的值(支持任意类型,内部会转换成字符串)
|
return DataBaseType.POSTGRE_SQL == getDataBaseType();
|
||||||
* @param var2 存储逗号分隔值的数据库列名
|
}
|
||||||
* @return 适用于当前数据库的 SQL 条件字符串,通常用于 where 或 apply 中拼接
|
|
||||||
*/
|
public static boolean isSqlServer() {
|
||||||
|
return DataBaseType.SQL_SERVER == getDataBaseType();
|
||||||
|
}
|
||||||
|
|
||||||
public static String findInSet(Object var1, String var2) {
|
public static String findInSet(Object var1, String var2) {
|
||||||
|
DataBaseType dataBasyType = getDataBaseType();
|
||||||
String var = Convert.toStr(var1);
|
String var = Convert.toStr(var1);
|
||||||
return switch (getDataBaseType()) {
|
if (dataBasyType == DataBaseType.SQL_SERVER) {
|
||||||
// instr(',0,100,101,' , ',100,') <> 0
|
|
||||||
case ORACLE -> "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var);
|
|
||||||
// (select strpos(',0,100,101,' , ',100,')) <> 0
|
|
||||||
case POSTGRE_SQL -> "(select strpos(','||%s||',' , ',%s,')) <> 0".formatted(var2, var);
|
|
||||||
// charindex(',100,' , ',0,100,101,') <> 0
|
// charindex(',100,' , ',0,100,101,') <> 0
|
||||||
case SQL_SERVER -> "charindex(',%s,' , ','+%s+',') <> 0".formatted(var, var2);
|
return "charindex(',%s,' , ','+%s+',') <> 0".formatted(var, var2);
|
||||||
|
} else if (dataBasyType == DataBaseType.POSTGRE_SQL) {
|
||||||
|
// (select strpos(',0,100,101,' , ',100,')) <> 0
|
||||||
|
return "(select strpos(','||%s||',' , ',%s,')) <> 0".formatted(var2, var);
|
||||||
|
} else if (dataBasyType == DataBaseType.ORACLE) {
|
||||||
|
// instr(',0,100,101,' , ',100,') <> 0
|
||||||
|
return "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var);
|
||||||
|
}
|
||||||
// find_in_set(100 , '0,100,101')
|
// find_in_set(100 , '0,100,101')
|
||||||
default -> "find_in_set('%s' , %s) <> 0".formatted(var, var2);
|
return "find_in_set('%s' , %s) <> 0".formatted(var, var2);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,7 +35,16 @@ import java.util.List;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class PlusDataPermissionInterceptor extends BaseMultiTableInnerInterceptor implements InnerInterceptor {
|
public class PlusDataPermissionInterceptor extends BaseMultiTableInnerInterceptor implements InnerInterceptor {
|
||||||
|
|
||||||
private final PlusDataPermissionHandler dataPermissionHandler = new PlusDataPermissionHandler();
|
private final PlusDataPermissionHandler dataPermissionHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数,初始化 PlusDataPermissionHandler 实例
|
||||||
|
*
|
||||||
|
* @param mapperPackage 扫描的映射器包
|
||||||
|
*/
|
||||||
|
public PlusDataPermissionInterceptor(String mapperPackage) {
|
||||||
|
this.dataPermissionHandler = new PlusDataPermissionHandler(mapperPackage);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在执行查询之前,检查并处理数据权限相关逻辑
|
* 在执行查询之前,检查并处理数据权限相关逻辑
|
||||||
@ -55,7 +64,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 检查是否缺少有效的数据权限注解
|
// 检查是否缺少有效的数据权限注解
|
||||||
if (dataPermissionHandler.invalid()) {
|
if (dataPermissionHandler.invalid(ms.getId())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 解析 sql 分配对应方法
|
// 解析 sql 分配对应方法
|
||||||
@ -83,7 +92,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 检查是否缺少有效的数据权限注解
|
// 检查是否缺少有效的数据权限注解
|
||||||
if (dataPermissionHandler.invalid()) {
|
if (dataPermissionHandler.invalid(ms.getId())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
|
PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
|
||||||
@ -119,7 +128,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void processUpdate(Update update, int index, String sql, Object obj) {
|
protected void processUpdate(Update update, int index, String sql, Object obj) {
|
||||||
Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), false);
|
Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), (String) obj, false);
|
||||||
if (null != sqlSegment) {
|
if (null != sqlSegment) {
|
||||||
update.setWhere(sqlSegment);
|
update.setWhere(sqlSegment);
|
||||||
}
|
}
|
||||||
@ -135,7 +144,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void processDelete(Delete delete, int index, String sql, Object obj) {
|
protected void processDelete(Delete delete, int index, String sql, Object obj) {
|
||||||
Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), false);
|
Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), (String) obj, false);
|
||||||
if (null != sqlSegment) {
|
if (null != sqlSegment) {
|
||||||
delete.setWhere(sqlSegment);
|
delete.setWhere(sqlSegment);
|
||||||
}
|
}
|
||||||
@ -148,7 +157,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
|
|||||||
* @param mappedStatementId 映射语句的 ID
|
* @param mappedStatementId 映射语句的 ID
|
||||||
*/
|
*/
|
||||||
protected void setWhere(PlainSelect plainSelect, String mappedStatementId) {
|
protected void setWhere(PlainSelect plainSelect, String mappedStatementId) {
|
||||||
Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), true);
|
Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true);
|
||||||
if (null != sqlSegment) {
|
if (null != sqlSegment) {
|
||||||
plainSelect.setWhere(sqlSegment);
|
plainSelect.setWhere(sqlSegment);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package org.dromara.common.oss.core;
|
|||||||
|
|
||||||
import cn.hutool.core.io.IoUtil;
|
import cn.hutool.core.io.IoUtil;
|
||||||
import cn.hutool.core.util.IdUtil;
|
import cn.hutool.core.util.IdUtil;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.dromara.common.core.constant.Constants;
|
import org.dromara.common.core.constant.Constants;
|
||||||
import org.dromara.common.core.utils.DateUtils;
|
import org.dromara.common.core.utils.DateUtils;
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
@ -14,7 +13,9 @@ import org.dromara.common.oss.exception.OssException;
|
|||||||
import org.dromara.common.oss.properties.OssProperties;
|
import org.dromara.common.oss.properties.OssProperties;
|
||||||
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
|
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
|
||||||
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
|
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
|
||||||
import software.amazon.awssdk.core.async.*;
|
import software.amazon.awssdk.core.ResponseInputStream;
|
||||||
|
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
|
||||||
|
import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody;
|
||||||
import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
|
import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
|
||||||
import software.amazon.awssdk.regions.Region;
|
import software.amazon.awssdk.regions.Region;
|
||||||
import software.amazon.awssdk.services.s3.S3AsyncClient;
|
import software.amazon.awssdk.services.s3.S3AsyncClient;
|
||||||
@ -28,12 +29,9 @@ import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;
|
|||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.channels.Channels;
|
|
||||||
import java.nio.channels.WritableByteChannel;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,7 +40,6 @@ import java.util.function.Consumer;
|
|||||||
*
|
*
|
||||||
* @author AprilWind
|
* @author AprilWind
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
|
||||||
public class OssClient {
|
public class OssClient {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -180,12 +177,12 @@ public class OssClient {
|
|||||||
// 创建异步请求体(length如果为空会报错)
|
// 创建异步请求体(length如果为空会报错)
|
||||||
BlockingInputStreamAsyncRequestBody body = BlockingInputStreamAsyncRequestBody.builder()
|
BlockingInputStreamAsyncRequestBody body = BlockingInputStreamAsyncRequestBody.builder()
|
||||||
.contentLength(length)
|
.contentLength(length)
|
||||||
.subscribeTimeout(Duration.ofSeconds(120))
|
.subscribeTimeout(Duration.ofSeconds(30))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// 使用 transferManager 进行上传
|
// 使用 transferManager 进行上传
|
||||||
Upload upload = transferManager.upload(
|
Upload upload = transferManager.upload(
|
||||||
x -> x.requestBody(body).addTransferListener(LoggingTransferListener.create())
|
x -> x.requestBody(body)
|
||||||
.putObjectRequest(
|
.putObjectRequest(
|
||||||
y -> y.bucket(properties.getBucketName())
|
y -> y.bucket(properties.getBucketName())
|
||||||
.key(key)
|
.key(key)
|
||||||
@ -240,61 +237,30 @@ public class OssClient {
|
|||||||
* @param key 文件在 Amazon S3 中的对象键
|
* @param key 文件在 Amazon S3 中的对象键
|
||||||
* @param out 输出流
|
* @param out 输出流
|
||||||
* @param consumer 自定义处理逻辑
|
* @param consumer 自定义处理逻辑
|
||||||
|
* @return 输出流中写入的字节数(长度)
|
||||||
* @throws OssException 如果下载失败,抛出自定义异常
|
* @throws OssException 如果下载失败,抛出自定义异常
|
||||||
*/
|
*/
|
||||||
public void download(String key, OutputStream out, Consumer<Long> consumer) {
|
public void download(String key, OutputStream out, Consumer<Long> consumer) {
|
||||||
try {
|
|
||||||
this.download(key, consumer).writeTo(out);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new OssException("文件下载失败,错误信息:[" + e.getMessage() + "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 下载文件从 Amazon S3 到 输出流
|
|
||||||
*
|
|
||||||
* @param key 文件在 Amazon S3 中的对象键
|
|
||||||
* @param contentLengthConsumer 文件大小消费者函数
|
|
||||||
* @return 写出订阅器
|
|
||||||
* @throws OssException 如果下载失败,抛出自定义异常
|
|
||||||
*/
|
|
||||||
public WriteOutSubscriber<OutputStream> download(String key, Consumer<Long> contentLengthConsumer) {
|
|
||||||
try {
|
try {
|
||||||
// 构建下载请求
|
// 构建下载请求
|
||||||
DownloadRequest<ResponsePublisher<GetObjectResponse>> publisherDownloadRequest = DownloadRequest.builder()
|
DownloadRequest<ResponseInputStream<GetObjectResponse>> downloadRequest = DownloadRequest.builder()
|
||||||
// 文件对象
|
// 文件对象
|
||||||
.getObjectRequest(y -> y.bucket(properties.getBucketName())
|
.getObjectRequest(y -> y.bucket(properties.getBucketName())
|
||||||
.key(key)
|
.key(key)
|
||||||
.build())
|
.build())
|
||||||
.addTransferListener(LoggingTransferListener.create())
|
.addTransferListener(LoggingTransferListener.create())
|
||||||
// 使用发布订阅转换器
|
// 使用订阅转换器
|
||||||
.responseTransformer(AsyncResponseTransformer.toPublisher())
|
.responseTransformer(AsyncResponseTransformer.toBlockingInputStream())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// 使用 S3TransferManager 下载文件
|
// 使用 S3TransferManager 下载文件
|
||||||
Download<ResponsePublisher<GetObjectResponse>> publisherDownload = transferManager.download(publisherDownloadRequest);
|
Download<ResponseInputStream<GetObjectResponse>> responseFuture = transferManager.download(downloadRequest);
|
||||||
// 获取下载发布订阅转换器
|
// 输出到流中
|
||||||
ResponsePublisher<GetObjectResponse> publisher = publisherDownload.completionFuture().join().result();
|
try (ResponseInputStream<GetObjectResponse> responseStream = responseFuture.completionFuture().join().result()) { // auto-closeable stream
|
||||||
// 执行文件大小消费者函数
|
if (consumer != null) {
|
||||||
Optional.ofNullable(contentLengthConsumer)
|
consumer.accept(responseStream.response().contentLength());
|
||||||
.ifPresent(lengthConsumer -> lengthConsumer.accept(publisher.response().contentLength()));
|
|
||||||
|
|
||||||
// 构建写出订阅器对象
|
|
||||||
return out -> {
|
|
||||||
// 创建可写入的字节通道
|
|
||||||
try(WritableByteChannel channel = Channels.newChannel(out)){
|
|
||||||
// 订阅数据
|
|
||||||
publisher.subscribe(byteBuffer -> {
|
|
||||||
while (byteBuffer.hasRemaining()) {
|
|
||||||
try {
|
|
||||||
channel.write(byteBuffer);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
|
responseStream.transferTo(out); // 阻塞调用线程 blocks the calling thread
|
||||||
}
|
}
|
||||||
}).join();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new OssException("文件下载失败,错误信息:[" + e.getMessage() + "]");
|
throw new OssException("文件下载失败,错误信息:[" + e.getMessage() + "]");
|
||||||
}
|
}
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
package org.dromara.common.oss.core;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 写出订阅器
|
|
||||||
*
|
|
||||||
* @author 秋辞未寒
|
|
||||||
*/
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface WriteOutSubscriber<T> {
|
|
||||||
|
|
||||||
void writeTo(T out) throws IOException;
|
|
||||||
|
|
||||||
}
|
|
@ -129,9 +129,9 @@ public class RedisUtils {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
long timeToLive = bucket.remainTimeToLive();
|
long timeToLive = bucket.remainTimeToLive();
|
||||||
if (timeToLive == -1) {
|
if (timeToLive == -1) {
|
||||||
bucket.set(value);
|
setCacheObject(key, value);
|
||||||
} else {
|
} else {
|
||||||
bucket.set(value, Duration.ofMillis(timeToLive));
|
setCacheObject(key, value, Duration.ofMillis(timeToLive));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -147,8 +147,11 @@ public class RedisUtils {
|
|||||||
* @param duration 时间
|
* @param duration 时间
|
||||||
*/
|
*/
|
||||||
public static <T> void setCacheObject(final String key, final T value, final Duration duration) {
|
public static <T> void setCacheObject(final String key, final T value, final Duration duration) {
|
||||||
RBucket<T> bucket = CLIENT.getBucket(key);
|
RBatch batch = CLIENT.createBatch();
|
||||||
bucket.set(value, duration);
|
RBucketAsync<T> bucket = batch.getBucket(key);
|
||||||
|
bucket.setAsync(value);
|
||||||
|
bucket.expireAsync(duration);
|
||||||
|
batch.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package org.dromara.common.redis.utils;
|
package org.dromara.common.redis.utils;
|
||||||
|
|
||||||
import cn.hutool.core.convert.Convert;
|
|
||||||
import cn.hutool.core.date.DatePattern;
|
import cn.hutool.core.date.DatePattern;
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import org.dromara.common.core.utils.SpringUtils;
|
import org.dromara.common.core.utils.SpringUtils;
|
||||||
@ -10,10 +10,6 @@ import org.redisson.api.RIdGenerator;
|
|||||||
import org.redisson.api.RedissonClient;
|
import org.redisson.api.RedissonClient;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.time.temporal.TemporalAccessor;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发号器工具类
|
* 发号器工具类
|
||||||
@ -27,12 +23,12 @@ public class SequenceUtils {
|
|||||||
/**
|
/**
|
||||||
* 默认初始值
|
* 默认初始值
|
||||||
*/
|
*/
|
||||||
public static final long DEFAULT_INIT_VALUE = 1L;
|
public static final Long DEFAULT_INIT_VALUE = 1L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认步长
|
* 默认步长
|
||||||
*/
|
*/
|
||||||
public static final long DEFAULT_STEP_VALUE = 1L;
|
public static final Long DEFAULT_STEP_VALUE = 1L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认过期时间-天
|
* 默认过期时间-天
|
||||||
@ -44,11 +40,6 @@ public class SequenceUtils {
|
|||||||
*/
|
*/
|
||||||
public static final Duration DEFAULT_EXPIRE_TIME_MINUTE = Duration.ofMinutes(1);
|
public static final Duration DEFAULT_EXPIRE_TIME_MINUTE = Duration.ofMinutes(1);
|
||||||
|
|
||||||
/**
|
|
||||||
* 默认最小ID容量位数 - 6位数(即至少可以生成的ID为999999个)
|
|
||||||
*/
|
|
||||||
public static final int DEFAULT_MIN_ID_CAPACITY_BITS = 6;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取Redisson客户端实例
|
* 获取Redisson客户端实例
|
||||||
*/
|
*/
|
||||||
@ -63,11 +54,14 @@ public class SequenceUtils {
|
|||||||
* @param stepValue ID步长
|
* @param stepValue ID步长
|
||||||
* @return ID生成器
|
* @return ID生成器
|
||||||
*/
|
*/
|
||||||
public static RIdGenerator getIdGenerator(String key, Duration expireTime, long initValue, long stepValue) {
|
private static RIdGenerator getIdGenerator(String key, Duration expireTime, Long initValue, Long stepValue) {
|
||||||
|
if (initValue == null || initValue <= 0) {
|
||||||
|
initValue = DEFAULT_INIT_VALUE;
|
||||||
|
}
|
||||||
|
if (stepValue == null || stepValue <= 0) {
|
||||||
|
stepValue = DEFAULT_STEP_VALUE;
|
||||||
|
}
|
||||||
RIdGenerator idGenerator = REDISSON_CLIENT.getIdGenerator(key);
|
RIdGenerator idGenerator = REDISSON_CLIENT.getIdGenerator(key);
|
||||||
// 初始值和步长不能小于等于0
|
|
||||||
initValue = initValue <= 0 ? DEFAULT_INIT_VALUE : initValue;
|
|
||||||
stepValue = stepValue <= 0 ? DEFAULT_STEP_VALUE : stepValue;
|
|
||||||
// 设置初始值和步长
|
// 设置初始值和步长
|
||||||
idGenerator.tryInit(initValue, stepValue);
|
idGenerator.tryInit(initValue, stepValue);
|
||||||
// 设置过期时间
|
// 设置过期时间
|
||||||
@ -75,17 +69,6 @@ public class SequenceUtils {
|
|||||||
return idGenerator;
|
return idGenerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取ID生成器
|
|
||||||
*
|
|
||||||
* @param key 业务key
|
|
||||||
* @param expireTime 过期时间
|
|
||||||
* @return ID生成器
|
|
||||||
*/
|
|
||||||
public static RIdGenerator getIdGenerator(String key, Duration expireTime) {
|
|
||||||
return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定业务key的唯一id
|
* 获取指定业务key的唯一id
|
||||||
*
|
*
|
||||||
@ -95,21 +78,10 @@ public class SequenceUtils {
|
|||||||
* @param stepValue ID步长
|
* @param stepValue ID步长
|
||||||
* @return 唯一id
|
* @return 唯一id
|
||||||
*/
|
*/
|
||||||
public static long getNextId(String key, Duration expireTime, long initValue, long stepValue) {
|
public static long nextId(String key, Duration expireTime, Long initValue, Long stepValue) {
|
||||||
return getIdGenerator(key, expireTime, initValue, stepValue).nextId();
|
return getIdGenerator(key, expireTime, initValue, stepValue).nextId();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取指定业务key的唯一id (ID初始值=1,ID步长=1)
|
|
||||||
*
|
|
||||||
* @param key 业务key
|
|
||||||
* @param expireTime 过期时间
|
|
||||||
* @return 唯一id
|
|
||||||
*/
|
|
||||||
public static long getNextId(String key, Duration expireTime) {
|
|
||||||
return getIdGenerator(key, expireTime).nextId();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定业务key的唯一id字符串
|
* 获取指定业务key的唯一id字符串
|
||||||
*
|
*
|
||||||
@ -119,8 +91,19 @@ public class SequenceUtils {
|
|||||||
* @param stepValue ID步长
|
* @param stepValue ID步长
|
||||||
* @return 唯一id
|
* @return 唯一id
|
||||||
*/
|
*/
|
||||||
public static String getNextIdString(String key, Duration expireTime, long initValue, long stepValue) {
|
public static String nextIdStr(String key, Duration expireTime, Long initValue, Long stepValue) {
|
||||||
return Convert.toStr(getNextId(key, expireTime, initValue, stepValue));
|
return String.valueOf(nextId(key, expireTime, initValue, stepValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定业务key的唯一id (ID初始值=1,ID步长=1)
|
||||||
|
*
|
||||||
|
* @param key 业务key
|
||||||
|
* @param expireTime 过期时间
|
||||||
|
* @return 唯一id
|
||||||
|
*/
|
||||||
|
public static long nextId(String key, Duration expireTime) {
|
||||||
|
return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -130,8 +113,8 @@ public class SequenceUtils {
|
|||||||
* @param expireTime 过期时间
|
* @param expireTime 过期时间
|
||||||
* @return 唯一id
|
* @return 唯一id
|
||||||
*/
|
*/
|
||||||
public static String getNextIdString(String key, Duration expireTime) {
|
public static String nextIdStr(String key, Duration expireTime) {
|
||||||
return Convert.toStr(getNextId(key, expireTime));
|
return String.valueOf(nextId(key, expireTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -142,210 +125,56 @@ public class SequenceUtils {
|
|||||||
* @param width 位数,不足左补0
|
* @param width 位数,不足左补0
|
||||||
* @return 补零后的唯一id字符串
|
* @return 补零后的唯一id字符串
|
||||||
*/
|
*/
|
||||||
public static String getPaddedNextIdString(String key, Duration expireTime, Integer width) {
|
public static String nextPaddedIdStr(String key, Duration expireTime, Integer width) {
|
||||||
return StringUtils.leftPad(getNextIdString(key, expireTime), width, '0');
|
return StringUtils.leftPad(nextIdStr(key, expireTime), width, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 yyyyMMdd 格式的唯一id
|
* 获取 yyyyMMdd 开头的唯一id
|
||||||
*
|
*
|
||||||
* @return 唯一id
|
* @return 唯一id
|
||||||
* @deprecated 请使用 {@link #getDateId(String)} 或 {@link #getDateId(String, boolean)}、{@link #getDateId(String, boolean, int)},确保不同业务的ID连续性
|
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
public static String nextIdDate() {
|
||||||
public static String getDateId() {
|
return nextIdDate("");
|
||||||
return getDateId("");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 prefix + yyyyMMdd 格式的唯一id
|
* 获取 prefix + yyyyMMdd 开头的唯一id
|
||||||
*
|
*
|
||||||
* @param prefix 业务前缀
|
* @param prefix 业务前缀
|
||||||
* @return 唯一id
|
* @return 唯一id
|
||||||
*/
|
*/
|
||||||
public static String getDateId(String prefix) {
|
public static String nextIdDate(String prefix) {
|
||||||
return getDateId(prefix, true);
|
// 前缀+日期 构建 prefixKey
|
||||||
}
|
String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_FORMATTER));
|
||||||
|
// 获取下一个id
|
||||||
/**
|
long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_DAY, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
|
||||||
* 获取 prefix + yyyyMMdd 格式的唯一id
|
// 返回完整id
|
||||||
*
|
|
||||||
* @param prefix 业务前缀
|
|
||||||
* @param isWithPrefix id是否携带业务前缀
|
|
||||||
* @return 唯一id
|
|
||||||
*/
|
|
||||||
public static String getDateId(String prefix, boolean isWithPrefix) {
|
|
||||||
return getDateId(prefix, isWithPrefix, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 prefix + yyyyMMdd 格式的唯一id (启用ID补位,补位长度 = {@link #DEFAULT_MIN_ID_CAPACITY_BITS})})
|
|
||||||
*
|
|
||||||
* @param prefix 业务前缀
|
|
||||||
* @param isWithPrefix id是否携带业务前缀
|
|
||||||
* @return 唯一id
|
|
||||||
*/
|
|
||||||
public static String getPaddedDateId(String prefix, boolean isWithPrefix) {
|
|
||||||
return getDateId(prefix, isWithPrefix, DEFAULT_MIN_ID_CAPACITY_BITS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 prefix + yyyyMMdd 格式的唯一id
|
|
||||||
*
|
|
||||||
* @param prefix 业务前缀
|
|
||||||
* @param isWithPrefix id是否携带业务前缀
|
|
||||||
* @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位)
|
|
||||||
* @return 唯一id
|
|
||||||
*/
|
|
||||||
public static String getDateId(String prefix, boolean isWithPrefix, int minIdCapacityBits) {
|
|
||||||
return getDateId(prefix, isWithPrefix, minIdCapacityBits, LocalDate.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 prefix + yyyyMMdd 格式的唯一id
|
|
||||||
*
|
|
||||||
* @param prefix 业务前缀
|
|
||||||
* @param isWithPrefix id是否携带业务前缀
|
|
||||||
* @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位)
|
|
||||||
* @param time 时间
|
|
||||||
* @return 唯一id
|
|
||||||
*/
|
|
||||||
public static String getDateId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDate time) {
|
|
||||||
return getDateId(prefix, isWithPrefix, minIdCapacityBits, time, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 prefix + yyyyMMdd 格式的唯一id
|
|
||||||
*
|
|
||||||
* @param prefix 业务前缀
|
|
||||||
* @param isWithPrefix id是否携带业务前缀
|
|
||||||
* @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位)
|
|
||||||
* @param time 时间
|
|
||||||
* @param initValue ID初始值
|
|
||||||
* @param stepValue ID步长
|
|
||||||
* @return 唯一id
|
|
||||||
*/
|
|
||||||
public static String getDateId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDate time, long initValue, long stepValue) {
|
|
||||||
return getDatePatternId(prefix, isWithPrefix, minIdCapacityBits, time, DatePattern.PURE_DATE_FORMATTER, DEFAULT_EXPIRE_TIME_DAY, initValue, stepValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 yyyyMMddHHmmss 格式的唯一id
|
|
||||||
*
|
|
||||||
* @return 唯一id
|
|
||||||
* @deprecated 请使用 {@link #getDateTimeId(String)} 或 {@link #getDateTimeId(String, boolean)}、{@link #getDateTimeId(String, boolean, int)},确保不同业务的ID连续性
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public static String getDateTimeId() {
|
|
||||||
return getDateTimeId("", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 prefix + yyyyMMddHHmmss 格式的唯一id
|
|
||||||
*
|
|
||||||
* @param prefix 业务前缀
|
|
||||||
* @return 唯一id
|
|
||||||
*/
|
|
||||||
public static String getDateTimeId(String prefix) {
|
|
||||||
return getDateTimeId(prefix, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 prefix + yyyyMMddHHmmss 格式的唯一id
|
|
||||||
*
|
|
||||||
* @param prefix 业务前缀
|
|
||||||
* @param isWithPrefix id是否携带业务前缀
|
|
||||||
* @return 唯一id
|
|
||||||
*/
|
|
||||||
public static String getDateTimeId(String prefix, boolean isWithPrefix) {
|
|
||||||
return getDateTimeId(prefix, isWithPrefix, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 prefix + yyyyMMddHHmmss 格式的唯一id (启用ID补位,补位长度 = {@link #DEFAULT_MIN_ID_CAPACITY_BITS})})
|
|
||||||
*
|
|
||||||
* @param prefix 业务前缀
|
|
||||||
* @param isWithPrefix id是否携带业务前缀
|
|
||||||
* @return 唯一id
|
|
||||||
*/
|
|
||||||
public static String getPaddedDateTimeId(String prefix, boolean isWithPrefix) {
|
|
||||||
return getDateTimeId(prefix, isWithPrefix, DEFAULT_MIN_ID_CAPACITY_BITS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 prefix + yyyyMMddHHmmss 格式的唯一id
|
|
||||||
*
|
|
||||||
* @param prefix 业务前缀
|
|
||||||
* @param isWithPrefix id是否携带业务前缀
|
|
||||||
* @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位)
|
|
||||||
* @return 唯一id
|
|
||||||
*/
|
|
||||||
public static String getDateTimeId(String prefix, boolean isWithPrefix, int minIdCapacityBits) {
|
|
||||||
return getDateTimeId(prefix, isWithPrefix, minIdCapacityBits, LocalDateTime.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 prefix + yyyyMMddHHmmss 格式的唯一id
|
|
||||||
*
|
|
||||||
* @param prefix 业务前缀
|
|
||||||
* @param isWithPrefix id是否携带业务前缀
|
|
||||||
* @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位)
|
|
||||||
* @param time 时间
|
|
||||||
* @return 唯一id
|
|
||||||
*/
|
|
||||||
public static String getDateTimeId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDateTime time) {
|
|
||||||
return getDateTimeId(prefix, isWithPrefix, minIdCapacityBits, time, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 prefix + yyyyMMddHHmmss 格式的唯一id
|
|
||||||
*
|
|
||||||
* @param prefix 业务前缀
|
|
||||||
* @param isWithPrefix id是否携带业务前缀
|
|
||||||
* @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位)
|
|
||||||
* @param initValue ID初始值
|
|
||||||
* @param stepValue ID步长
|
|
||||||
* @return 唯一id
|
|
||||||
*/
|
|
||||||
public static String getDateTimeId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDateTime time, long initValue, long stepValue) {
|
|
||||||
return getDatePatternId(prefix, isWithPrefix, minIdCapacityBits, time, DatePattern.PURE_DATETIME_FORMATTER, DEFAULT_EXPIRE_TIME_MINUTE, initValue, stepValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取指定业务key的指定时间格式的ID
|
|
||||||
*
|
|
||||||
* @param prefix 业务前缀
|
|
||||||
* @param isWithPrefix id是否携带业务前缀
|
|
||||||
* @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位)
|
|
||||||
* @param temporalAccessor 时间访问器
|
|
||||||
* @param timeFormatter 时间格式
|
|
||||||
* @param expireTime 过期时间
|
|
||||||
* @param initValue ID初始值
|
|
||||||
* @param stepValue ID步长
|
|
||||||
* @return 唯一id
|
|
||||||
*/
|
|
||||||
private static String getDatePatternId(String prefix, boolean isWithPrefix, int minIdCapacityBits, TemporalAccessor temporalAccessor, DateTimeFormatter timeFormatter, Duration expireTime, long initValue, long stepValue) {
|
|
||||||
// 时间前缀
|
|
||||||
String timePrefix = timeFormatter.format(temporalAccessor);
|
|
||||||
// 业务前缀 + 时间前缀 构建 prefixKey
|
|
||||||
String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), timePrefix);
|
|
||||||
|
|
||||||
// 获取id,例 -> 1
|
|
||||||
String nextId = getNextIdString(prefixKey, expireTime, initValue, stepValue);
|
|
||||||
|
|
||||||
// minIdCapacityBits 大于0,且 nextId 的长度小于 minIdCapacityBits,则左补0
|
|
||||||
if (minIdCapacityBits > 0 && nextId.length() < minIdCapacityBits) {
|
|
||||||
nextId = StringUtils.leftPad(nextId, minIdCapacityBits, '0');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 是否携带业务前缀
|
|
||||||
if (isWithPrefix) {
|
|
||||||
// 例 -> P202507031
|
|
||||||
// 其中 P 为业务前缀,202507031 为 yyyyMMdd 格式时间, 1 为nextId
|
|
||||||
return StringUtils.format("{}{}", prefixKey, nextId);
|
return StringUtils.format("{}{}", prefixKey, nextId);
|
||||||
}
|
}
|
||||||
// 例 -> 202507031
|
|
||||||
// 其中 202507031 为 yyyyMMdd 格式时间, 1 为nextId
|
/**
|
||||||
return StringUtils.format("{}{}", timePrefix, nextId);
|
* 获取 yyyyMMddHHmmss 开头的唯一id
|
||||||
|
*
|
||||||
|
* @return 唯一id
|
||||||
|
*/
|
||||||
|
public static String nextIdDateTime() {
|
||||||
|
return nextIdDateTime("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 prefix + yyyyMMddHHmmss 开头的唯一id
|
||||||
|
*
|
||||||
|
* @param prefix 业务前缀
|
||||||
|
* @return 唯一id
|
||||||
|
*/
|
||||||
|
public static String nextIdDateTime(String prefix) {
|
||||||
|
// 前缀+日期时间 构建 prefixKey
|
||||||
|
String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_FORMATTER));
|
||||||
|
// 获取下一个id
|
||||||
|
long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_MINUTE, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
|
||||||
|
// 返回完整id
|
||||||
|
return StringUtils.format("{}{}", prefixKey, nextId);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -52,9 +52,13 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
|||||||
// 判断是否为永不过期
|
// 判断是否为永不过期
|
||||||
if (timeout == NEVER_EXPIRE) {
|
if (timeout == NEVER_EXPIRE) {
|
||||||
RedisUtils.setCacheObject(key, value);
|
RedisUtils.setCacheObject(key, value);
|
||||||
|
} else {
|
||||||
|
if (RedisUtils.hasKey(key)) {
|
||||||
|
RedisUtils.setCacheObject(key, value, true);
|
||||||
} else {
|
} else {
|
||||||
RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
|
RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
CAFFEINE.invalidate(key);
|
CAFFEINE.invalidate(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,9 +78,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void delete(String key) {
|
public void delete(String key) {
|
||||||
if (RedisUtils.deleteObject(key)) {
|
RedisUtils.deleteObject(key);
|
||||||
CAFFEINE.invalidate(key);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -131,9 +133,13 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
|||||||
// 判断是否为永不过期
|
// 判断是否为永不过期
|
||||||
if (timeout == NEVER_EXPIRE) {
|
if (timeout == NEVER_EXPIRE) {
|
||||||
RedisUtils.setCacheObject(key, object);
|
RedisUtils.setCacheObject(key, object);
|
||||||
|
} else {
|
||||||
|
if (RedisUtils.hasKey(key)) {
|
||||||
|
RedisUtils.setCacheObject(key, object, true);
|
||||||
} else {
|
} else {
|
||||||
RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout));
|
RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
CAFFEINE.invalidate(key);
|
CAFFEINE.invalidate(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,9 +159,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void deleteObject(String key) {
|
public void deleteObject(String key) {
|
||||||
if (RedisUtils.deleteObject(key)) {
|
RedisUtils.deleteObject(key);
|
||||||
CAFFEINE.invalidate(key);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package org.dromara.common.satoken.core.service;
|
package org.dromara.common.satoken.core.service;
|
||||||
|
|
||||||
import cn.dev33.satoken.stp.StpInterface;
|
import cn.dev33.satoken.stp.StpInterface;
|
||||||
import cn.hutool.core.collection.CollUtil;
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import org.dromara.common.core.domain.model.LoginUser;
|
import org.dromara.common.core.domain.model.LoginUser;
|
||||||
import org.dromara.common.core.enums.UserType;
|
import org.dromara.common.core.enums.UserType;
|
||||||
@ -40,12 +39,8 @@ public class SaPermissionImpl implements StpInterface {
|
|||||||
if (userType == UserType.APP_USER) {
|
if (userType == UserType.APP_USER) {
|
||||||
// 其他端 自行根据业务编写
|
// 其他端 自行根据业务编写
|
||||||
}
|
}
|
||||||
if (CollUtil.isNotEmpty(loginUser.getMenuPermission())) {
|
|
||||||
// SYS_USER 默认返回权限
|
// SYS_USER 默认返回权限
|
||||||
return new ArrayList<>(loginUser.getMenuPermission());
|
return new ArrayList<>(loginUser.getMenuPermission());
|
||||||
} else {
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,12 +62,8 @@ public class SaPermissionImpl implements StpInterface {
|
|||||||
if (userType == UserType.APP_USER) {
|
if (userType == UserType.APP_USER) {
|
||||||
// 其他端 自行根据业务编写
|
// 其他端 自行根据业务编写
|
||||||
}
|
}
|
||||||
if (CollUtil.isNotEmpty(loginUser.getRolePermission())) {
|
|
||||||
// SYS_USER 默认返回权限
|
// SYS_USER 默认返回权限
|
||||||
return new ArrayList<>(loginUser.getRolePermission());
|
return new ArrayList<>(loginUser.getRolePermission());
|
||||||
} else {
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PermissionService getPermissionService() {
|
private PermissionService getPermissionService() {
|
||||||
|
@ -207,8 +207,7 @@ public class LoginHelper {
|
|||||||
*/
|
*/
|
||||||
public static boolean isLogin() {
|
public static boolean isLogin() {
|
||||||
try {
|
try {
|
||||||
StpUtil.checkLogin();
|
return getLoginUser() != null;
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.dromara.common.sensitive.core;
|
package org.dromara.common.sensitive.core;
|
||||||
|
|
||||||
import cn.hutool.core.convert.Convert;
|
|
||||||
import cn.hutool.core.util.DesensitizedUtil;
|
import cn.hutool.core.util.DesensitizedUtil;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
@ -53,7 +52,7 @@ public enum SensitiveStrategy {
|
|||||||
/**
|
/**
|
||||||
* 用户ID
|
* 用户ID
|
||||||
*/
|
*/
|
||||||
USER_ID(s -> Convert.toStr(DesensitizedUtil.userId())),
|
USER_ID(s -> String.valueOf(DesensitizedUtil.userId())),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 密码
|
* 密码
|
||||||
|
@ -14,9 +14,6 @@ import org.dromara.common.social.gitea.AuthGiteaRequest;
|
|||||||
import org.dromara.common.social.maxkey.AuthMaxKeyRequest;
|
import org.dromara.common.social.maxkey.AuthMaxKeyRequest;
|
||||||
import org.dromara.common.social.topiam.AuthTopIamRequest;
|
import org.dromara.common.social.topiam.AuthTopIamRequest;
|
||||||
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 认证授权工具类
|
* 认证授权工具类
|
||||||
*
|
*
|
||||||
@ -43,7 +40,7 @@ public class SocialUtils {
|
|||||||
AuthConfig.AuthConfigBuilder builder = AuthConfig.builder()
|
AuthConfig.AuthConfigBuilder builder = AuthConfig.builder()
|
||||||
.clientId(obj.getClientId())
|
.clientId(obj.getClientId())
|
||||||
.clientSecret(obj.getClientSecret())
|
.clientSecret(obj.getClientSecret())
|
||||||
.redirectUri(URLEncoder.encode(obj.getRedirectUri(), StandardCharsets.UTF_8))
|
.redirectUri(obj.getRedirectUri())
|
||||||
.scopes(obj.getScopes());
|
.scopes(obj.getScopes());
|
||||||
return switch (source.toLowerCase()) {
|
return switch (source.toLowerCase()) {
|
||||||
case "dingtalk" -> new AuthDingTalkV2Request(builder.build(), STATE_CACHE);
|
case "dingtalk" -> new AuthDingTalkV2Request(builder.build(), STATE_CACHE);
|
||||||
|
@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import org.dromara.common.core.domain.R;
|
import org.dromara.common.core.domain.R;
|
||||||
import org.dromara.common.satoken.utils.LoginHelper;
|
import org.dromara.common.satoken.utils.LoginHelper;
|
||||||
import org.dromara.common.sse.core.SseEmitterManager;
|
import org.dromara.common.sse.core.SseEmitterManager;
|
||||||
|
import org.dromara.common.sse.dto.SseMessageDto;
|
||||||
import org.springframework.beans.factory.DisposableBean;
|
import org.springframework.beans.factory.DisposableBean;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@ -13,6 +14,8 @@ import org.springframework.web.bind.annotation.GetMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SSE 控制器
|
* SSE 控制器
|
||||||
*
|
*
|
||||||
@ -30,9 +33,7 @@ public class SseController implements DisposableBean {
|
|||||||
*/
|
*/
|
||||||
@GetMapping(value = "${sse.path}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
@GetMapping(value = "${sse.path}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||||
public SseEmitter connect() {
|
public SseEmitter connect() {
|
||||||
if (!StpUtil.isLogin()) {
|
StpUtil.checkLogin();
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String tokenValue = StpUtil.getTokenValue();
|
String tokenValue = StpUtil.getTokenValue();
|
||||||
Long userId = LoginHelper.getUserId();
|
Long userId = LoginHelper.getUserId();
|
||||||
return sseEmitterManager.connect(userId, tokenValue);
|
return sseEmitterManager.connect(userId, tokenValue);
|
||||||
@ -50,32 +51,31 @@ public class SseController implements DisposableBean {
|
|||||||
return R.ok();
|
return R.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 以下为demo仅供参考 禁止使用 请在业务逻辑中使用工具发送而不是用接口发送
|
/**
|
||||||
// /**
|
* 向特定用户发送消息
|
||||||
// * 向特定用户发送消息
|
*
|
||||||
// *
|
* @param userId 目标用户的 ID
|
||||||
// * @param userId 目标用户的 ID
|
* @param msg 要发送的消息内容
|
||||||
// * @param msg 要发送的消息内容
|
*/
|
||||||
// */
|
@GetMapping(value = "${sse.path}/send")
|
||||||
// @GetMapping(value = "${sse.path}/send")
|
public R<Void> send(Long userId, String msg) {
|
||||||
// public R<Void> send(Long userId, String msg) {
|
SseMessageDto dto = new SseMessageDto();
|
||||||
// SseMessageDto dto = new SseMessageDto();
|
dto.setUserIds(List.of(userId));
|
||||||
// dto.setUserIds(List.of(userId));
|
dto.setMessage(msg);
|
||||||
// dto.setMessage(msg);
|
sseEmitterManager.publishMessage(dto);
|
||||||
// sseEmitterManager.publishMessage(dto);
|
return R.ok();
|
||||||
// return R.ok();
|
}
|
||||||
// }
|
|
||||||
//
|
/**
|
||||||
// /**
|
* 向所有用户发送消息
|
||||||
// * 向所有用户发送消息
|
*
|
||||||
// *
|
* @param msg 要发送的消息内容
|
||||||
// * @param msg 要发送的消息内容
|
*/
|
||||||
// */
|
@GetMapping(value = "${sse.path}/sendAll")
|
||||||
// @GetMapping(value = "${sse.path}/sendAll")
|
public R<Void> send(String msg) {
|
||||||
// public R<Void> send(String msg) {
|
sseEmitterManager.publishAll(msg);
|
||||||
// sseEmitterManager.publishAll(msg);
|
return R.ok();
|
||||||
// return R.ok();
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清理资源。此方法目前不执行任何操作,但避免因未实现而导致错误
|
* 清理资源。此方法目前不执行任何操作,但避免因未实现而导致错误
|
||||||
|
@ -21,6 +21,11 @@
|
|||||||
<artifactId>ruoyi-common-json</artifactId>
|
<artifactId>ruoyi-common-json</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- SpringBoot Web容器 -->
|
<!-- SpringBoot Web容器 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
@ -7,7 +7,6 @@ import org.dromara.common.web.filter.XssFilter;
|
|||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.boot.web.servlet.FilterRegistration;
|
|
||||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
@ -22,20 +21,24 @@ public class FilterConfig {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
|
@ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
|
||||||
@FilterRegistration(
|
public FilterRegistrationBean<XssFilter> xssFilterRegistration() {
|
||||||
name = "xssFilter",
|
FilterRegistrationBean<XssFilter> registration = new FilterRegistrationBean<>();
|
||||||
urlPatterns = "/*",
|
registration.setDispatcherTypes(DispatcherType.REQUEST);
|
||||||
order = FilterRegistrationBean.HIGHEST_PRECEDENCE + 1,
|
registration.setFilter(new XssFilter());
|
||||||
dispatcherTypes = DispatcherType.REQUEST
|
registration.addUrlPatterns("/*");
|
||||||
)
|
registration.setName("xssFilter");
|
||||||
public XssFilter xssFilter() {
|
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE + 1);
|
||||||
return new XssFilter();
|
return registration;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@FilterRegistration(name = "repeatableFilter", urlPatterns = "/*")
|
public FilterRegistrationBean<RepeatableFilter> someFilterRegistration() {
|
||||||
public RepeatableFilter repeatableFilter() {
|
FilterRegistrationBean<RepeatableFilter> registration = new FilterRegistrationBean<>();
|
||||||
return new RepeatableFilter();
|
registration.setFilter(new RepeatableFilter());
|
||||||
|
registration.addUrlPatterns("/*");
|
||||||
|
registration.setName("repeatableFilter");
|
||||||
|
registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
|
||||||
|
return registration;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
package org.dromara.common.web.config;
|
package org.dromara.common.web.config;
|
||||||
|
|
||||||
import cn.hutool.core.date.DateTime;
|
|
||||||
import cn.hutool.core.date.DateUtil;
|
import cn.hutool.core.date.DateUtil;
|
||||||
import org.dromara.common.core.utils.ObjectUtils;
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
import org.dromara.common.web.handler.GlobalExceptionHandler;
|
import org.dromara.common.web.handler.GlobalExceptionHandler;
|
||||||
import org.dromara.common.web.interceptor.PlusWebInvokeTimeInterceptor;
|
import org.dromara.common.web.interceptor.PlusWebInvokeTimeInterceptor;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
@ -35,11 +34,10 @@ public class ResourcesConfig implements WebMvcConfigurer {
|
|||||||
public void addFormatters(FormatterRegistry registry) {
|
public void addFormatters(FormatterRegistry registry) {
|
||||||
// 全局日期格式转换配置
|
// 全局日期格式转换配置
|
||||||
registry.addConverter(String.class, Date.class, source -> {
|
registry.addConverter(String.class, Date.class, source -> {
|
||||||
DateTime parse = DateUtil.parse(source);
|
if (StringUtils.isBlank(source)) {
|
||||||
if (ObjectUtils.isNull(parse)) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return parse.toJdkDate();
|
return DateUtil.parse(source);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package org.dromara.common.web.enums;
|
package org.dromara.common.web.enums;
|
||||||
|
|
||||||
import cn.hutool.captcha.generator.CodeGenerator;
|
import cn.hutool.captcha.generator.CodeGenerator;
|
||||||
import cn.hutool.captcha.generator.MathGenerator;
|
|
||||||
import cn.hutool.captcha.generator.RandomGenerator;
|
import cn.hutool.captcha.generator.RandomGenerator;
|
||||||
|
import org.dromara.common.web.utils.UnsignedMathGenerator;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ public enum CaptchaType {
|
|||||||
/**
|
/**
|
||||||
* 数字
|
* 数字
|
||||||
*/
|
*/
|
||||||
MATH(MathGenerator.class),
|
MATH(UnsignedMathGenerator.class),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字符
|
* 字符
|
||||||
|
@ -0,0 +1,88 @@
|
|||||||
|
package org.dromara.common.web.utils;
|
||||||
|
|
||||||
|
import cn.hutool.captcha.generator.CodeGenerator;
|
||||||
|
import cn.hutool.core.math.Calculator;
|
||||||
|
import cn.hutool.core.util.CharUtil;
|
||||||
|
import cn.hutool.core.util.RandomUtil;
|
||||||
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无符号计算生成器
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public class UnsignedMathGenerator implements CodeGenerator {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = -5514819971774091076L;
|
||||||
|
|
||||||
|
private static final String OPERATORS = "+-*";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参与计算数字最大长度
|
||||||
|
*/
|
||||||
|
private final int numberLength;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*/
|
||||||
|
public UnsignedMathGenerator() {
|
||||||
|
this(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param numberLength 参与计算最大数字位数
|
||||||
|
*/
|
||||||
|
public UnsignedMathGenerator(int numberLength) {
|
||||||
|
this.numberLength = numberLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String generate() {
|
||||||
|
final int limit = getLimit();
|
||||||
|
int a = RandomUtil.randomInt(limit);
|
||||||
|
int b = RandomUtil.randomInt(limit);
|
||||||
|
String max = Integer.toString(Math.max(a,b));
|
||||||
|
String min = Integer.toString(Math.min(a,b));
|
||||||
|
max = StringUtils.rightPad(max, this.numberLength, CharUtil.SPACE);
|
||||||
|
min = StringUtils.rightPad(min, this.numberLength, CharUtil.SPACE);
|
||||||
|
|
||||||
|
return max + RandomUtil.randomChar(OPERATORS) + min + '=';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean verify(String code, String userInputCode) {
|
||||||
|
int result;
|
||||||
|
try {
|
||||||
|
result = Integer.parseInt(userInputCode);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// 用户输入非数字
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int calculateResult = (int) Calculator.conversion(code);
|
||||||
|
return result == calculateResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取验证码长度
|
||||||
|
*
|
||||||
|
* @return 验证码长度
|
||||||
|
*/
|
||||||
|
public int getLength() {
|
||||||
|
return this.numberLength * 2 + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据长度获取参与计算数字最大值
|
||||||
|
*
|
||||||
|
* @return 最大值
|
||||||
|
*/
|
||||||
|
private int getLimit() {
|
||||||
|
return Integer.parseInt("1" + StringUtils.repeat('0', this.numberLength));
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
|
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
|
||||||
FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds
|
FROM bellsoft/liberica-openjdk-rocky:17.0.15-cds
|
||||||
#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds
|
#FROM bellsoft/liberica-openjdk-rocky:21.0.7-cds
|
||||||
#FROM findepi/graalvm:java17-native
|
#FROM findepi/graalvm:java17-native
|
||||||
|
|
||||||
LABEL maintainer="Lion Li"
|
LABEL maintainer="Lion Li"
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.dromara.monitor.admin;
|
package org.dromara.monitor.admin;
|
||||||
|
|
||||||
import de.codecentric.boot.admin.server.config.EnableAdminServer;
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
@ -9,7 +8,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|||||||
*
|
*
|
||||||
* @author Lion Li
|
* @author Lion Li
|
||||||
*/
|
*/
|
||||||
@EnableAdminServer
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class MonitorAdminApplication {
|
public class MonitorAdminApplication {
|
||||||
|
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package org.dromara.monitor.admin.config;
|
||||||
|
|
||||||
|
import de.codecentric.boot.admin.server.config.EnableAdminServer;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
|
||||||
|
import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* springboot-admin server配置类
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableAdminServer
|
||||||
|
public class AdminServerConfig {
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Bean(name = TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
|
||||||
|
@ConditionalOnMissingBean(Executor.class)
|
||||||
|
public ThreadPoolTaskExecutor applicationTaskExecutor(ThreadPoolTaskExecutorBuilder builder) {
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -10,7 +10,7 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt
|
|||||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
||||||
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* admin 监控 安全配置
|
* admin 监控 安全配置
|
||||||
@ -32,14 +32,14 @@ public class SecurityConfig {
|
|||||||
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
|
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
|
||||||
successHandler.setTargetUrlParameter("redirectTo");
|
successHandler.setTargetUrlParameter("redirectTo");
|
||||||
successHandler.setDefaultTargetUrl(adminContextPath + "/");
|
successHandler.setDefaultTargetUrl(adminContextPath + "/");
|
||||||
PathPatternRequestMatcher.Builder mvc = PathPatternRequestMatcher.withDefaults();
|
|
||||||
return httpSecurity
|
return httpSecurity
|
||||||
.headers((header) ->
|
.headers((header) ->
|
||||||
header.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
|
header.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
|
||||||
.authorizeHttpRequests((authorize) ->
|
.authorizeHttpRequests((authorize) ->
|
||||||
authorize.requestMatchers(
|
authorize.requestMatchers(
|
||||||
mvc.matcher(adminContextPath + "/assets/**"),
|
new AntPathRequestMatcher(adminContextPath + "/assets/**"),
|
||||||
mvc.matcher(adminContextPath + "/login")
|
new AntPathRequestMatcher(adminContextPath + "/login")
|
||||||
).permitAll()
|
).permitAll()
|
||||||
.anyRequest().authenticated())
|
.anyRequest().authenticated())
|
||||||
.formLogin((formLogin) ->
|
.formLogin((formLogin) ->
|
||||||
|
@ -20,9 +20,6 @@ spring:
|
|||||||
ui:
|
ui:
|
||||||
title: RuoYi-Vue-Plus服务监控中心
|
title: RuoYi-Vue-Plus服务监控中心
|
||||||
context-path: /admin
|
context-path: /admin
|
||||||
# 忽略无用警告
|
|
||||||
thymeleaf:
|
|
||||||
check-template-location: false
|
|
||||||
|
|
||||||
--- # Actuator 监控端点的配置项
|
--- # Actuator 监控端点的配置项
|
||||||
management:
|
management:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
|
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
|
||||||
FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds
|
FROM bellsoft/liberica-openjdk-rocky:17.0.15-cds
|
||||||
#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds
|
#FROM bellsoft/liberica-openjdk-rocky:21.0.7-cds
|
||||||
#FROM findepi/graalvm:java17-native
|
#FROM findepi/graalvm:java17-native
|
||||||
|
|
||||||
LABEL maintainer="Lion Li"
|
LABEL maintainer="Lion Li"
|
||||||
|
@ -1,146 +0,0 @@
|
|||||||
package com.aizuda.snailjob.server.common.register;
|
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
|
||||||
import cn.hutool.core.util.IdUtil;
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import com.aizuda.snailjob.common.core.enums.NodeTypeEnum;
|
|
||||||
import com.aizuda.snailjob.common.core.util.JsonUtil;
|
|
||||||
import com.aizuda.snailjob.common.core.util.NetUtil;
|
|
||||||
import com.aizuda.snailjob.common.core.util.SnailJobVersion;
|
|
||||||
import com.aizuda.snailjob.common.core.util.StreamUtils;
|
|
||||||
import com.aizuda.snailjob.common.log.SnailJobLog;
|
|
||||||
import com.aizuda.snailjob.server.common.cache.CacheConsumerGroup;
|
|
||||||
import com.aizuda.snailjob.server.common.config.SystemProperties;
|
|
||||||
import com.aizuda.snailjob.server.common.convert.RegisterNodeInfoConverter;
|
|
||||||
import com.aizuda.snailjob.server.common.dto.ServerNodeExtAttrs;
|
|
||||||
import com.aizuda.snailjob.server.common.handler.InstanceManager;
|
|
||||||
import com.aizuda.snailjob.template.datasource.persistence.po.ServerNode;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentMap;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 服务端注册
|
|
||||||
*
|
|
||||||
* @author opensnail
|
|
||||||
* @date 2023-06-07
|
|
||||||
* @since 1.6.0
|
|
||||||
*/
|
|
||||||
@Component(ServerRegister.BEAN_NAME)
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class ServerRegister extends AbstractRegister {
|
|
||||||
public static final String BEAN_NAME = "serverRegister";
|
|
||||||
private final ScheduledExecutorService serverRegisterNode = Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "server-register-node"));
|
|
||||||
public static final int DELAY_TIME = 30;
|
|
||||||
public static final String CURRENT_CID;
|
|
||||||
public static final String GROUP_NAME = "DEFAULT_SERVER";
|
|
||||||
public static final String NAMESPACE_ID = "DEFAULT_SERVER_NAMESPACE_ID";
|
|
||||||
private final InstanceManager instanceManager;
|
|
||||||
private final SystemProperties systemProperties;
|
|
||||||
private final ServerProperties serverProperties;
|
|
||||||
|
|
||||||
static {
|
|
||||||
CURRENT_CID = IdUtil.getSnowflakeNextIdStr();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supports(int type) {
|
|
||||||
return getNodeType().equals(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void beforeProcessor(RegisterContext context) {
|
|
||||||
// 新增扩展参数
|
|
||||||
ServerNodeExtAttrs serverNodeExtAttrs = new ServerNodeExtAttrs();
|
|
||||||
serverNodeExtAttrs.setWebPort(serverProperties.getPort());
|
|
||||||
serverNodeExtAttrs.setSystemVersion(SnailJobVersion.getVersion());
|
|
||||||
|
|
||||||
context.setGroupName(GROUP_NAME);
|
|
||||||
context.setHostId(CURRENT_CID);
|
|
||||||
String serverHost = systemProperties.getServerHost();
|
|
||||||
if (StrUtil.isEmptyIfStr(serverHost)) {
|
|
||||||
serverHost = NetUtil.getLocalIpStr();
|
|
||||||
}
|
|
||||||
context.setHostIp(serverHost);
|
|
||||||
context.setHostPort(systemProperties.getServerPort());
|
|
||||||
context.setContextPath(Optional.ofNullable(serverProperties.getServlet().getContextPath()).orElse(StrUtil.EMPTY));
|
|
||||||
context.setNamespaceId(NAMESPACE_ID);
|
|
||||||
context.setExtAttrs(JsonUtil.toJsonString(serverNodeExtAttrs));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected LocalDateTime getExpireAt() {
|
|
||||||
return LocalDateTime.now().plusSeconds(DELAY_TIME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean doRegister(RegisterContext context, ServerNode serverNode) {
|
|
||||||
refreshExpireAt(Lists.newArrayList(serverNode));
|
|
||||||
return Boolean.TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void afterProcessor(final ServerNode serverNode) {
|
|
||||||
try {
|
|
||||||
// 同步当前POD消费的组的节点信息
|
|
||||||
// netty的client只会注册到一个服务端,若组分配的和client连接的不是一个POD则会导致当前POD没有其他客户端的注册信息
|
|
||||||
ConcurrentMap<String /*groupName*/, Set<String>/*namespaceId*/> allConsumerGroupName = CacheConsumerGroup.getAllConsumerGroupName();
|
|
||||||
if (CollUtil.isNotEmpty(allConsumerGroupName)) {
|
|
||||||
Set<String> namespaceIdSets = StreamUtils.toSetByFlatMap(allConsumerGroupName.values(), Set::stream);
|
|
||||||
if (CollUtil.isEmpty(namespaceIdSets)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ServerNode> serverNodes = serverNodeMapper.selectList(
|
|
||||||
new LambdaQueryWrapper<ServerNode>()
|
|
||||||
.eq(ServerNode::getNodeType, NodeTypeEnum.CLIENT.getType())
|
|
||||||
.in(ServerNode::getNamespaceId, namespaceIdSets)
|
|
||||||
.in(ServerNode::getGroupName, allConsumerGroupName.keySet()));
|
|
||||||
for (final ServerNode node : serverNodes) {
|
|
||||||
// 刷新全量本地缓存
|
|
||||||
instanceManager.registerOrUpdate(RegisterNodeInfoConverter.INSTANCE.toRegisterNodeInfo(node));
|
|
||||||
// 刷新过期时间
|
|
||||||
CacheConsumerGroup.addOrUpdate(node.getGroupName(), node.getNamespaceId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
SnailJobLog.LOCAL.error("Client refresh failed", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Integer getNodeType() {
|
|
||||||
return NodeTypeEnum.SERVER.getType();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
|
||||||
SnailJobLog.LOCAL.info("ServerRegister start");
|
|
||||||
|
|
||||||
serverRegisterNode.scheduleAtFixedRate(() -> {
|
|
||||||
try {
|
|
||||||
this.register(new RegisterContext());
|
|
||||||
} catch (Exception e) {
|
|
||||||
SnailJobLog.LOCAL.error("Server-side registration failed", e);
|
|
||||||
}
|
|
||||||
}, 0, DELAY_TIME * 2 / 3, TimeUnit.SECONDS);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
SnailJobLog.LOCAL.info("ServerRegister close");
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,26 +16,15 @@ spring:
|
|||||||
|
|
||||||
--- # snail-job 服务端配置
|
--- # snail-job 服务端配置
|
||||||
snail-job:
|
snail-job:
|
||||||
# 服务端节点IP(默认按照`NetUtil.getLocalIpStr()`)
|
# 拉取重试数据的每批次的大小
|
||||||
server-host:
|
retry-pull-page-size: 1000
|
||||||
# 服务端端口号
|
# 拉取重试数据的每批次的大小
|
||||||
|
job-pull-page-size: 1000
|
||||||
|
# 服务器端口
|
||||||
server-port: 17888
|
server-port: 17888
|
||||||
# 合并日志默认保存天数
|
# 日志保存时间(单位: day)
|
||||||
merge-Log-days: 1
|
|
||||||
# 合并日志默认的条数
|
|
||||||
merge-Log-num: 500
|
|
||||||
# 配置每批次拉取重试数据的大小
|
|
||||||
retry-pull-page-size: 100
|
|
||||||
# 配置日志保存时间(单位:天)
|
|
||||||
log-storage: 7
|
log-storage: 7
|
||||||
# bucket的总数量
|
rpc-type: grpc
|
||||||
bucket-total: 128
|
|
||||||
# Dashboard 任务容错天数
|
|
||||||
summary-day: 7
|
|
||||||
# 配置负载均衡周期时间
|
|
||||||
load-balance-cycle-time: 10
|
|
||||||
# 重试任务拉取的并行度
|
|
||||||
retry-max-pull-parallel: 2
|
|
||||||
|
|
||||||
--- # 监控中心配置
|
--- # 监控中心配置
|
||||||
spring.boot.admin.client:
|
spring.boot.admin.client:
|
||||||
|
@ -16,26 +16,15 @@ spring:
|
|||||||
|
|
||||||
--- # snail-job 服务端配置
|
--- # snail-job 服务端配置
|
||||||
snail-job:
|
snail-job:
|
||||||
# 服务端节点IP(默认按照`NetUtil.getLocalIpStr()`)
|
# 拉取重试数据的每批次的大小
|
||||||
server-host:
|
retry-pull-page-size: 1000
|
||||||
# 服务端端口号
|
# 拉取重试数据的每批次的大小
|
||||||
|
job-pull-page-size: 1000
|
||||||
|
# 服务器端口
|
||||||
server-port: 17888
|
server-port: 17888
|
||||||
# 合并日志默认保存天数
|
# 日志保存时间(单位: day)
|
||||||
merge-Log-days: 1
|
|
||||||
# 合并日志默认的条数
|
|
||||||
merge-Log-num: 500
|
|
||||||
# 配置每批次拉取重试数据的大小
|
|
||||||
retry-pull-page-size: 100
|
|
||||||
# 配置日志保存时间(单位:天)
|
|
||||||
log-storage: 7
|
log-storage: 7
|
||||||
# bucket的总数量
|
rpc-type: grpc
|
||||||
bucket-total: 128
|
|
||||||
# Dashboard 任务容错天数
|
|
||||||
summary-day: 7
|
|
||||||
# 配置负载均衡周期时间
|
|
||||||
load-balance-cycle-time: 10
|
|
||||||
# 重试任务拉取的并行度
|
|
||||||
retry-max-pull-parallel: 2
|
|
||||||
|
|
||||||
--- # 监控中心配置
|
--- # 监控中心配置
|
||||||
spring.boot.admin.client:
|
spring.boot.admin.client:
|
||||||
|
@ -85,7 +85,6 @@
|
|||||||
<!-- 控制台输出日志级别 -->
|
<!-- 控制台输出日志级别 -->
|
||||||
<root level="info">
|
<root level="info">
|
||||||
<appender-ref ref="console" />
|
<appender-ref ref="console" />
|
||||||
<appender-ref ref="file_console" />
|
|
||||||
<appender-ref ref="async_info" />
|
<appender-ref ref="async_info" />
|
||||||
<appender-ref ref="async_error" />
|
<appender-ref ref="async_error" />
|
||||||
<appender-ref ref="snail_log_server_appender" />
|
<appender-ref ref="snail_log_server_appender" />
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package org.dromara.demo.controller;
|
package org.dromara.demo.controller;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.annotation.SaIgnore;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.dromara.common.core.domain.R;
|
import org.dromara.common.core.domain.R;
|
||||||
import org.dromara.common.mail.utils.MailUtils;
|
import org.dromara.common.mail.utils.MailUtils;
|
||||||
@ -17,11 +18,12 @@ import java.util.Arrays;
|
|||||||
*
|
*
|
||||||
* @author Michelle.Chung
|
* @author Michelle.Chung
|
||||||
*/
|
*/
|
||||||
|
@SaIgnore
|
||||||
@Validated
|
@Validated
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/demo/mail")
|
@RequestMapping("/demo/mail")
|
||||||
public class MailSendController {
|
public class MailController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送邮件
|
* 发送邮件
|
||||||
@ -42,11 +44,11 @@ public class MailSendController {
|
|||||||
* @param to 接收人
|
* @param to 接收人
|
||||||
* @param subject 标题
|
* @param subject 标题
|
||||||
* @param text 内容
|
* @param text 内容
|
||||||
|
* @param filePath 附件路径
|
||||||
*/
|
*/
|
||||||
@GetMapping("/sendMessageWithAttachment")
|
@GetMapping("/sendMessageWithAttachment")
|
||||||
public R<Void> sendMessageWithAttachment(String to, String subject, String text) {
|
public R<Void> sendMessageWithAttachment(String to, String subject, String text, String filePath) {
|
||||||
// 附件路径 禁止前端传递 有任意读取系统文件风险
|
MailUtils.sendText(to, subject, text, new File(filePath));
|
||||||
MailUtils.sendText(to, subject, text, new File("/xxx/xxx"));
|
|
||||||
return R.ok();
|
return R.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,11 +58,10 @@ public class MailSendController {
|
|||||||
* @param to 接收人
|
* @param to 接收人
|
||||||
* @param subject 标题
|
* @param subject 标题
|
||||||
* @param text 内容
|
* @param text 内容
|
||||||
|
* @param paths 附件路径
|
||||||
*/
|
*/
|
||||||
@GetMapping("/sendMessageWithAttachments")
|
@GetMapping("/sendMessageWithAttachments")
|
||||||
public R<Void> sendMessageWithAttachments(String to, String subject, String text) {
|
public R<Void> sendMessageWithAttachments(String to, String subject, String text, String[] paths) {
|
||||||
// 附件路径 禁止前端传递 有任意读取系统文件风险
|
|
||||||
String[] paths = new String[]{"/xxx/xxx", "/xxx/xxx"};
|
|
||||||
File[] array = Arrays.stream(paths).map(File::new).toArray(File[]::new);
|
File[] array = Arrays.stream(paths).map(File::new).toArray(File[]::new);
|
||||||
MailUtils.sendText(to, subject, text, array);
|
MailUtils.sendText(to, subject, text, array);
|
||||||
return R.ok();
|
return R.ok();
|
@ -1,6 +1,5 @@
|
|||||||
package org.dromara.demo.controller;
|
package org.dromara.demo.controller;
|
||||||
|
|
||||||
import cn.dev33.satoken.annotation.SaIgnore;
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
@ -15,7 +14,6 @@ import org.springframework.http.MediaType;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -96,16 +94,6 @@ public class TestExcelController {
|
|||||||
exportExcelService.exportWithOptions(response);
|
exportExcelService.exportWithOptions(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义导出
|
|
||||||
*
|
|
||||||
* @param response /
|
|
||||||
*/
|
|
||||||
@GetMapping("/customExport")
|
|
||||||
public void customExport(HttpServletResponse response) throws IOException {
|
|
||||||
exportExcelService.customExport(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 多个sheet导出
|
* 多个sheet导出
|
||||||
*/
|
*/
|
||||||
|
@ -2,8 +2,6 @@ package org.dromara.demo.service;
|
|||||||
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出下拉框Excel示例
|
* 导出下拉框Excel示例
|
||||||
*
|
*
|
||||||
@ -17,11 +15,4 @@ public interface IExportExcelService {
|
|||||||
* @param response /
|
* @param response /
|
||||||
*/
|
*/
|
||||||
void exportWithOptions(HttpServletResponse response);
|
void exportWithOptions(HttpServletResponse response);
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义导出
|
|
||||||
*
|
|
||||||
* @param response /
|
|
||||||
*/
|
|
||||||
void customExport(HttpServletResponse response) throws IOException;
|
|
||||||
}
|
}
|
||||||
|
@ -2,21 +2,17 @@ package org.dromara.demo.service.impl;
|
|||||||
|
|
||||||
import cn.hutool.core.util.RandomUtil;
|
import cn.hutool.core.util.RandomUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.idev.excel.write.metadata.WriteSheet;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.dromara.common.core.constant.SystemConstants;
|
import org.dromara.common.core.constant.SystemConstants;
|
||||||
import org.dromara.common.core.utils.StreamUtils;
|
import org.dromara.common.core.utils.StreamUtils;
|
||||||
import org.dromara.common.core.utils.file.FileUtils;
|
|
||||||
import org.dromara.common.excel.core.DropDownOptions;
|
import org.dromara.common.excel.core.DropDownOptions;
|
||||||
import org.dromara.common.excel.utils.ExcelUtil;
|
import org.dromara.common.excel.utils.ExcelUtil;
|
||||||
import org.dromara.common.excel.utils.ExcelWriterWrapper;
|
|
||||||
import org.dromara.demo.domain.vo.ExportDemoVo;
|
import org.dromara.demo.domain.vo.ExportDemoVo;
|
||||||
import org.dromara.demo.service.IExportExcelService;
|
import org.dromara.demo.service.IExportExcelService;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -237,61 +233,4 @@ public class ExportExcelServiceImpl implements IExportExcelService {
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void customExport(HttpServletResponse response) throws IOException {
|
|
||||||
String filename = ExcelUtil.encodingFilename("自定义导出");
|
|
||||||
FileUtils.setAttachmentResponseHeader(response, filename);
|
|
||||||
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
|
|
||||||
|
|
||||||
ExcelUtil.exportExcel(ExportDemoVo.class, response.getOutputStream(), wrapper -> {
|
|
||||||
// 创建表格数据,业务中一般通过数据库查询
|
|
||||||
List<ExportDemoVo> excelDataList = new ArrayList<>();
|
|
||||||
for (int i = 0; i < 30; i++) {
|
|
||||||
// 模拟数据库中的一条数据
|
|
||||||
ExportDemoVo everyRowData = new ExportDemoVo();
|
|
||||||
everyRowData.setNickName("用户-" + i);
|
|
||||||
everyRowData.setUserStatus(SystemConstants.NORMAL);
|
|
||||||
everyRowData.setGender("1");
|
|
||||||
everyRowData.setPhoneNumber(String.format("175%08d", i));
|
|
||||||
everyRowData.setEmail(String.format("175%08d", i) + "@163.com");
|
|
||||||
everyRowData.setProvinceId(i);
|
|
||||||
everyRowData.setCityId(i);
|
|
||||||
everyRowData.setAreaId(i);
|
|
||||||
excelDataList.add(everyRowData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建表格
|
|
||||||
WriteSheet sheet = ExcelWriterWrapper.sheetBuilder("自定义导出demo")
|
|
||||||
// 合并单元格
|
|
||||||
// .registerWriteHandler(new CellMergeStrategy(excelDataList, true))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
|
|
||||||
wrapper.write(excelDataList, sheet);
|
|
||||||
|
|
||||||
List<ExportDemoVo> excelDataList2 = new ArrayList<>();
|
|
||||||
for (int i = 0; i < 20; i++) {
|
|
||||||
int index = 1000 + i;
|
|
||||||
// 模拟数据库中的一条数据
|
|
||||||
ExportDemoVo everyRowData = new ExportDemoVo();
|
|
||||||
everyRowData.setNickName("用户-" + index);
|
|
||||||
everyRowData.setUserStatus(SystemConstants.NORMAL);
|
|
||||||
everyRowData.setGender("1");
|
|
||||||
everyRowData.setPhoneNumber(String.format("175%08d", index));
|
|
||||||
everyRowData.setEmail(String.format("175%08d", index) + "@163.com");
|
|
||||||
everyRowData.setProvinceId(index);
|
|
||||||
everyRowData.setCityId(index);
|
|
||||||
everyRowData.setAreaId(index);
|
|
||||||
excelDataList2.add(everyRowData);
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapper.write(excelDataList2, sheet);
|
|
||||||
|
|
||||||
// 或者在同一个excel中创建多个表格
|
|
||||||
// WriteSheet sheet2 = ExcelWriterWrapper.sheetBuilder("自定义导出demo2").build();
|
|
||||||
// wrapper.write(excelDataList2, sheet2);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -42,11 +42,6 @@
|
|||||||
<artifactId>ruoyi-common-log</artifactId>
|
<artifactId>ruoyi-common-log</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.dromara</groupId>
|
|
||||||
<artifactId>ruoyi-common-idempotent</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!--velocity代码生成使用模板 -->
|
<!--velocity代码生成使用模板 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.velocity</groupId>
|
<groupId>org.apache.velocity</groupId>
|
||||||
|
@ -3,11 +3,9 @@ package org.dromara.generator.controller;
|
|||||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
import cn.hutool.core.convert.Convert;
|
import cn.hutool.core.convert.Convert;
|
||||||
import cn.hutool.core.io.IoUtil;
|
import cn.hutool.core.io.IoUtil;
|
||||||
import com.baomidou.lock.annotation.Lock4j;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.dromara.common.core.domain.R;
|
import org.dromara.common.core.domain.R;
|
||||||
import org.dromara.common.idempotent.annotation.RepeatSubmit;
|
|
||||||
import org.dromara.common.log.annotation.Log;
|
import org.dromara.common.log.annotation.Log;
|
||||||
import org.dromara.common.log.enums.BusinessType;
|
import org.dromara.common.log.enums.BusinessType;
|
||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
@ -52,7 +50,6 @@ public class GenController extends BaseController {
|
|||||||
*
|
*
|
||||||
* @param tableId 表ID
|
* @param tableId 表ID
|
||||||
*/
|
*/
|
||||||
@RepeatSubmit()
|
|
||||||
@SaCheckPermission("tool:gen:query")
|
@SaCheckPermission("tool:gen:query")
|
||||||
@GetMapping(value = "/{tableId}")
|
@GetMapping(value = "/{tableId}")
|
||||||
public R<Map<String, Object>> getInfo(@PathVariable Long tableId) {
|
public R<Map<String, Object>> getInfo(@PathVariable Long tableId) {
|
||||||
@ -94,7 +91,6 @@ public class GenController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
@SaCheckPermission("tool:gen:import")
|
@SaCheckPermission("tool:gen:import")
|
||||||
@Log(title = "代码生成", businessType = BusinessType.IMPORT)
|
@Log(title = "代码生成", businessType = BusinessType.IMPORT)
|
||||||
@RepeatSubmit()
|
|
||||||
@PostMapping("/importTable")
|
@PostMapping("/importTable")
|
||||||
public R<Void> importTableSave(String tables, String dataName) {
|
public R<Void> importTableSave(String tables, String dataName) {
|
||||||
String[] tableNames = Convert.toStrArray(tables);
|
String[] tableNames = Convert.toStrArray(tables);
|
||||||
@ -109,7 +105,6 @@ public class GenController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
@SaCheckPermission("tool:gen:edit")
|
@SaCheckPermission("tool:gen:edit")
|
||||||
@Log(title = "代码生成", businessType = BusinessType.UPDATE)
|
@Log(title = "代码生成", businessType = BusinessType.UPDATE)
|
||||||
@RepeatSubmit()
|
|
||||||
@PutMapping
|
@PutMapping
|
||||||
public R<Void> editSave(@Validated @RequestBody GenTable genTable) {
|
public R<Void> editSave(@Validated @RequestBody GenTable genTable) {
|
||||||
genTableService.validateEdit(genTable);
|
genTableService.validateEdit(genTable);
|
||||||
@ -175,7 +170,6 @@ public class GenController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
@SaCheckPermission("tool:gen:edit")
|
@SaCheckPermission("tool:gen:edit")
|
||||||
@Log(title = "代码生成", businessType = BusinessType.UPDATE)
|
@Log(title = "代码生成", businessType = BusinessType.UPDATE)
|
||||||
@Lock4j
|
|
||||||
@GetMapping("/synchDb/{tableId}")
|
@GetMapping("/synchDb/{tableId}")
|
||||||
public R<Void> synchDb(@PathVariable("tableId") Long tableId) {
|
public R<Void> synchDb(@PathVariable("tableId") Long tableId) {
|
||||||
genTableService.synchDb(tableId);
|
genTableService.synchDb(tableId);
|
||||||
|
@ -302,7 +302,7 @@ public class GenTableServiceImpl implements IGenTableService {
|
|||||||
tableColumn.setColumnComment(column.getComment());
|
tableColumn.setColumnComment(column.getComment());
|
||||||
tableColumn.setColumnType(column.getOriginType().toLowerCase());
|
tableColumn.setColumnType(column.getOriginType().toLowerCase());
|
||||||
tableColumn.setSort(column.getPosition());
|
tableColumn.setSort(column.getPosition());
|
||||||
tableColumn.setIsRequired(column.isNullable() ? "0" : "1");
|
tableColumn.setIsRequired(column.isNullable() ? "1" : "0");
|
||||||
tableColumn.setIsIncrement(column.isAutoIncrement() ? "1" : "0");
|
tableColumn.setIsIncrement(column.isAutoIncrement() ? "1" : "0");
|
||||||
tableColumns.add(tableColumn);
|
tableColumns.add(tableColumn);
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,6 @@ package org.dromara.generator.util;
|
|||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.convert.Convert;
|
import cn.hutool.core.convert.Convert;
|
||||||
import cn.hutool.core.lang.Dict;
|
import cn.hutool.core.lang.Dict;
|
||||||
import org.dromara.common.mybatis.enums.DataBaseType;
|
|
||||||
import org.dromara.generator.constant.GenConstants;
|
import org.dromara.generator.constant.GenConstants;
|
||||||
import org.dromara.common.core.utils.DateUtils;
|
import org.dromara.common.core.utils.DateUtils;
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
@ -119,12 +118,11 @@ public class VelocityUtils {
|
|||||||
templates.add("vm/java/serviceImpl.java.vm");
|
templates.add("vm/java/serviceImpl.java.vm");
|
||||||
templates.add("vm/java/controller.java.vm");
|
templates.add("vm/java/controller.java.vm");
|
||||||
templates.add("vm/xml/mapper.xml.vm");
|
templates.add("vm/xml/mapper.xml.vm");
|
||||||
DataBaseType dataBaseType = DataBaseHelper.getDataBaseType();
|
if (DataBaseHelper.isOracle()) {
|
||||||
if (dataBaseType.isOracle()) {
|
|
||||||
templates.add("vm/sql/oracle/sql.vm");
|
templates.add("vm/sql/oracle/sql.vm");
|
||||||
} else if (dataBaseType.isPostgreSql()) {
|
} else if (DataBaseHelper.isPostgerSql()) {
|
||||||
templates.add("vm/sql/postgres/sql.vm");
|
templates.add("vm/sql/postgres/sql.vm");
|
||||||
} else if (dataBaseType.isSqlServer()) {
|
} else if (DataBaseHelper.isSqlServer()) {
|
||||||
templates.add("vm/sql/sqlserver/sql.vm");
|
templates.add("vm/sql/sqlserver/sql.vm");
|
||||||
} else {
|
} else {
|
||||||
templates.add("vm/sql/sql.vm");
|
templates.add("vm/sql/sql.vm");
|
||||||
|
@ -59,3 +59,6 @@ export interface ${BusinessName}Query #if(!${treeCode})extends PageQuery #end{
|
|||||||
*/
|
*/
|
||||||
params?: any;
|
params?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@
|
|||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
|
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tooltip content="修改" placement="top">
|
<el-tooltip content="修改" placement="top">
|
||||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']" />
|
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']" />
|
||||||
|
@ -120,7 +120,7 @@
|
|||||||
<el-table-column label="${comment}" align="center" prop="${javaField}" />
|
<el-table-column label="${comment}" align="center" prop="${javaField}" />
|
||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
|
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tooltip content="修改" placement="top">
|
<el-tooltip content="修改" placement="top">
|
||||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']"></el-button>
|
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']"></el-button>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<!DOCTYPE mapper
|
<!DOCTYPE mapper
|
||||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
<mapper namespace="${packageName}.mapper.${ClassName}Mapper">
|
<mapper namespace="${packageName}.mapper.${ClassName}Mapper">
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
@ -4,8 +4,8 @@ import cn.hutool.core.date.DateUtil;
|
|||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
|
import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
|
||||||
import com.aizuda.snailjob.client.job.core.dto.JobArgs;
|
import com.aizuda.snailjob.client.job.core.dto.JobArgs;
|
||||||
|
import com.aizuda.snailjob.client.model.ExecuteResult;
|
||||||
import com.aizuda.snailjob.common.log.SnailJobLog;
|
import com.aizuda.snailjob.common.log.SnailJobLog;
|
||||||
import com.aizuda.snailjob.model.dto.ExecuteResult;
|
|
||||||
import org.dromara.common.json.utils.JsonUtils;
|
import org.dromara.common.json.utils.JsonUtils;
|
||||||
import org.dromara.job.entity.BillDto;
|
import org.dromara.job.entity.BillDto;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
@ -3,8 +3,8 @@ package org.dromara.job.snailjob;
|
|||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
|
import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
|
||||||
import com.aizuda.snailjob.client.job.core.dto.JobArgs;
|
import com.aizuda.snailjob.client.job.core.dto.JobArgs;
|
||||||
|
import com.aizuda.snailjob.client.model.ExecuteResult;
|
||||||
import com.aizuda.snailjob.common.log.SnailJobLog;
|
import com.aizuda.snailjob.common.log.SnailJobLog;
|
||||||
import com.aizuda.snailjob.model.dto.ExecuteResult;
|
|
||||||
import org.dromara.common.json.utils.JsonUtils;
|
import org.dromara.common.json.utils.JsonUtils;
|
||||||
import org.dromara.job.entity.BillDto;
|
import org.dromara.job.entity.BillDto;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user