mirror of
https://github.com/dromara/RuoYi-Vue-Plus.git
synced 2025-11-25 02:16:46 +08:00
Compare commits
148 Commits
49c00e162b
...
5.X
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b58085fde1 | ||
|
|
5ea8d8c950 | ||
|
|
3318109044 | ||
|
|
aa1f89e253 | ||
|
|
35c77403d6 | ||
|
|
603fb7b92d | ||
|
|
6cf0c79433 | ||
|
|
3934e119d6 | ||
|
|
33a6a21fdf | ||
|
|
7800b1259f | ||
|
|
3623fc33d9 | ||
|
|
f8612eb52e | ||
|
|
8d32b0311a | ||
|
|
60bcd2d6e9 | ||
|
|
5ccb511064 | ||
|
|
78baf6497a | ||
|
|
0719e53f01 | ||
|
|
5f2c4205a5 | ||
|
|
2fe4c96706 | ||
|
|
5c634940c2 | ||
|
|
6036f8750b | ||
|
|
dbcd8f58eb | ||
|
|
8905e232e5 | ||
|
|
4f15158486 | ||
|
|
d2413abd5c | ||
|
|
f7ffadeaff | ||
|
|
f9eec856e7 | ||
|
|
62562650fe | ||
|
|
df171097c3 | ||
|
|
1977aabc9a | ||
|
|
483c4e6d0a | ||
|
|
b30ffa952f | ||
|
|
f616c6931c | ||
|
|
26e10293f5 | ||
|
|
60e578f763 | ||
|
|
5cd4d8ca11 | ||
|
|
41a6230b6e | ||
|
|
effda4f6e8 | ||
|
|
af4c38e439 | ||
|
|
fafa8cd573 | ||
|
|
8909b8a7d4 | ||
|
|
8ae9bde731 | ||
|
|
a703cb2ad1 | ||
|
|
a918b880d6 | ||
|
|
e795e315eb | ||
|
|
81869cfeb3 | ||
|
|
fc6f61bc95 | ||
|
|
d44e45ad3b | ||
|
|
00ed9ddd10 | ||
|
|
341fc144a1 | ||
|
|
b6b1b2de18 | ||
|
|
c19f2b9e4e | ||
|
|
3a11f18656 | ||
|
|
5a43212ccc | ||
|
|
f4cfd1c913 | ||
|
|
26ce8f30c9 | ||
|
|
2258962770 | ||
|
|
655e84012c | ||
|
|
f683ef00b8 | ||
|
|
424b2ea164 | ||
|
|
7bb4838132 | ||
|
|
20516758ea | ||
|
|
2d5f84ebc2 | ||
|
|
6bc28e41de | ||
|
|
a4fb3fadaf | ||
|
|
cfa67fcd8c | ||
|
|
e5e8d305d2 | ||
|
|
9d0084409e | ||
|
|
ee02f46dfd | ||
|
|
25de0b3530 | ||
|
|
aa76859a05 | ||
|
|
71b70a59fe | ||
|
|
05c9528549 | ||
|
|
1feb2a3861 | ||
|
|
237e78e80c | ||
|
|
ffc3dcaec9 | ||
|
|
a94e474069 | ||
|
|
40a0e57870 | ||
|
|
c01ed34602 | ||
|
|
26a99003d2 | ||
|
|
93c886d3ed | ||
|
|
9e1027690b | ||
|
|
cc120c06fd | ||
|
|
3827da078a | ||
|
|
70d3505b94 | ||
|
|
a39a69cac5 | ||
|
|
1dbce3ab7c | ||
|
|
9742b1b596 | ||
|
|
d98d11ae2d | ||
|
|
6742dcb33e | ||
|
|
09a51478a5 | ||
|
|
f02601ab2c | ||
|
|
ac56ca0e81 | ||
|
|
0fcf77e2ed | ||
|
|
0f0a3a181e | ||
|
|
e24e2c51e4 | ||
|
|
2b0dd82d3d | ||
|
|
b97f711eb4 | ||
|
|
0250ca4eb8 | ||
|
|
23338995d7 | ||
|
|
84fd02e7d8 | ||
|
|
ae5bec994d | ||
|
|
8f3a1b589e | ||
|
|
ad6b3d4b3f | ||
|
|
e2801037cf | ||
|
|
65061f17fe | ||
|
|
d0f4d93615 | ||
|
|
5d69832423 | ||
|
|
0c1e39ea14 | ||
|
|
a39bc870d1 | ||
|
|
7357912681 | ||
|
|
901992674e | ||
|
|
7ceb85ffa0 | ||
|
|
4672d7de4d | ||
|
|
87ab6e1744 | ||
|
|
ae0a03728b | ||
|
|
6fc82a59f1 | ||
|
|
6c33fa48ec | ||
|
|
343d5d21d8 | ||
|
|
0ba909c52e | ||
|
|
808ce9c25a | ||
|
|
4351fc5239 | ||
|
|
a545f7fc44 | ||
|
|
acfcdf4d9a | ||
|
|
9683252783 | ||
|
|
fd5d028e95 | ||
|
|
64100cf1ff | ||
|
|
7e7d857ba5 | ||
|
|
d22b2a10df | ||
|
|
957a4d1fcd | ||
|
|
49ef8378fe | ||
|
|
57dd6831d3 | ||
|
|
8aa60abb1f | ||
|
|
7a9f51fc7a | ||
|
|
159e30c982 | ||
|
|
7334d91d6b | ||
|
|
95c01301f6 | ||
|
|
296466fa13 | ||
|
|
3c8d864b5f | ||
|
|
ea50a57602 | ||
|
|
7e14b98676 | ||
|
|
015b406001 | ||
|
|
098d3347a0 | ||
|
|
08d4493994 | ||
|
|
367d739e2d | ||
|
|
d6688a367d | ||
|
|
0b331796e2 | ||
|
|
456620b638 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -38,6 +38,7 @@ nbdist/
|
|||||||
######################################################################
|
######################################################################
|
||||||
# Others
|
# Others
|
||||||
*.log
|
*.log
|
||||||
|
*.log.gz
|
||||||
*.xml.versionsBackup
|
*.xml.versionsBackup
|
||||||
*.swp
|
*.swp
|
||||||
|
|
||||||
|
|||||||
@@ -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.4.1" />
|
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.5.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.4.1" />
|
<option name="imageTag" value="ruoyi/ruoyi-server:5.5.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.4.1" />
|
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.5.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)
|
||||||
[]()
|
[]()
|
||||||
[]()
|
[]()
|
||||||
[]()
|
[]()
|
||||||
@@ -38,8 +38,9 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <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>
|
aizuda flowlong 工作流 - https://gitee.com/aizuda/flowlong <br>
|
||||||
|
Ruoyi-Plus-Uniapp - https://ruoyi.plus <br>
|
||||||
|
|
||||||
[如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group)
|
[如何成为赞助商 加群联系作者详谈 每日PV2500-3000 IP1700-2500](https://plus-doc.dromara.org/#/common/add_group)
|
||||||
|
|
||||||
# 本框架与RuoYi的功能差异
|
# 本框架与RuoYi的功能差异
|
||||||
|
|
||||||
|
|||||||
26
pom.xml
26
pom.xml
@@ -13,28 +13,28 @@
|
|||||||
<description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
|
<description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>5.4.1</revision>
|
<revision>5.5.1</revision>
|
||||||
<spring-boot.version>3.5.4</spring-boot.version>
|
<spring-boot.version>3.5.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.9</springdoc.version>
|
<springdoc.version>2.8.13</springdoc.version>
|
||||||
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
|
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
|
||||||
<fastexcel.version>1.2.0</fastexcel.version>
|
<fastexcel.version>1.3.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.12</mybatis-plus.version>
|
<mybatis-plus.version>3.5.14</mybatis-plus.version>
|
||||||
<p6spy.version>3.9.1</p6spy.version>
|
<p6spy.version>3.9.1</p6spy.version>
|
||||||
<hutool.version>5.8.38</hutool.version>
|
<hutool.version>5.8.40</hutool.version>
|
||||||
<spring-boot-admin.version>3.5.1</spring-boot-admin.version>
|
<spring-boot-admin.version>3.5.5</spring-boot-admin.version>
|
||||||
<redisson.version>3.50.0</redisson.version>
|
<redisson.version>3.51.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.6.0</snailjob.version>
|
<snailjob.version>1.8.0</snailjob.version>
|
||||||
<mapstruct-plus.version>1.4.8</mapstruct-plus.version>
|
<mapstruct-plus.version>1.5.0</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.38</lombok.version>
|
<lombok.version>1.18.40</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.4</sms4j.version>
|
<sms4j.version>3.3.5</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.7.4</warm-flow.version>
|
<warm-flow.version>1.8.2</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>
|
||||||
|
|||||||
@@ -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.15-cds
|
FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds
|
||||||
#FROM bellsoft/liberica-openjdk-rocky:21.0.7-cds
|
#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds
|
||||||
#FROM findepi/graalvm:java17-native
|
#FROM findepi/graalvm:java17-native
|
||||||
|
|
||||||
LABEL maintainer="Lion Li"
|
LABEL maintainer="Lion Li"
|
||||||
|
|||||||
@@ -131,15 +131,18 @@ 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();
|
||||||
boolean isMath = CaptchaType.MATH == captchaType;
|
CodeGenerator codeGenerator;
|
||||||
Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength();
|
if (CaptchaType.MATH == captchaType) {
|
||||||
CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length);
|
codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getNumberLength(), false);
|
||||||
|
} 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 (isMath) {
|
if (CaptchaType.MATH == captchaType) {
|
||||||
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 (!code.equalsIgnoreCase(captcha)) {
|
if (!StringUtils.equalsIgnoreCase(code, 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 (!code.equalsIgnoreCase(captcha)) {
|
if (!StringUtils.equalsIgnoreCase(code, 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,8 +27,6 @@ snail-job:
|
|||||||
port: 2${server.port}
|
port: 2${server.port}
|
||||||
# 客户端ip指定
|
# 客户端ip指定
|
||||||
host:
|
host:
|
||||||
# RPC类型: netty, grpc
|
|
||||||
rpc-type: grpc
|
|
||||||
|
|
||||||
--- # 数据源配置
|
--- # 数据源配置
|
||||||
spring:
|
spring:
|
||||||
|
|||||||
@@ -30,8 +30,6 @@ snail-job:
|
|||||||
port: 2${server.port}
|
port: 2${server.port}
|
||||||
# 客户端ip指定
|
# 客户端ip指定
|
||||||
host:
|
host:
|
||||||
# RPC类型: netty, grpc
|
|
||||||
rpc-type: grpc
|
|
||||||
|
|
||||||
--- # 数据源配置
|
--- # 数据源配置
|
||||||
spring:
|
spring:
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ spring:
|
|||||||
# 从 springboot 3.5 开始 spring自带线程池
|
# 从 springboot 3.5 开始 spring自带线程池
|
||||||
# 不再需要 AsyncConfig与ThreadPoolConfig 可直接注入线程池使用
|
# 不再需要 AsyncConfig与ThreadPoolConfig 可直接注入线程池使用
|
||||||
thread-name-prefix: async-
|
thread-name-prefix: async-
|
||||||
|
# 由spring自己初始化线程池
|
||||||
|
mode: force
|
||||||
# 资源信息
|
# 资源信息
|
||||||
messages:
|
messages:
|
||||||
# 国际化资源文件路径
|
# 国际化资源文件路径
|
||||||
@@ -255,13 +257,9 @@ 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
|
|
||||||
|
|||||||
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</fileNamePattern>
|
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log.gz</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</fileNamePattern>
|
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
|
||||||
<!-- 日志最大的历史 60天 -->
|
<!-- 日志最大的历史 60天 -->
|
||||||
<maxHistory>60</maxHistory>
|
<maxHistory>60</maxHistory>
|
||||||
</rollingPolicy>
|
</rollingPolicy>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</description>
|
</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>5.4.1</revision>
|
<revision>5.5.1</revision>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
|
|||||||
@@ -5,15 +5,12 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
||||||
import org.dromara.common.core.config.properties.ThreadPoolProperties;
|
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.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
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 java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 线程池配置
|
* 线程池配置
|
||||||
@@ -50,7 +47,7 @@ public class ThreadPoolConfig {
|
|||||||
@Override
|
@Override
|
||||||
protected void afterExecute(Runnable r, Throwable t) {
|
protected void afterExecute(Runnable r, Throwable t) {
|
||||||
super.afterExecute(r, t);
|
super.afterExecute(r, t);
|
||||||
Threads.printException(r, t);
|
printException(r, t);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.scheduledExecutorService = scheduledThreadPoolExecutor;
|
this.scheduledExecutorService = scheduledThreadPoolExecutor;
|
||||||
@@ -59,15 +56,57 @@ public class ThreadPoolConfig {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 销毁事件
|
* 销毁事件
|
||||||
|
* 停止线程池
|
||||||
|
* 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
|
||||||
|
* 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
|
||||||
|
* 如果仍然超時,則強制退出.
|
||||||
|
* 另对在shutdown时线程本身被调用中断做了处理.
|
||||||
*/
|
*/
|
||||||
@PreDestroy
|
@PreDestroy
|
||||||
public void destroy() {
|
public void destroy() {
|
||||||
try {
|
try {
|
||||||
log.info("====关闭后台任务任务线程池====");
|
log.info("====关闭后台任务任务线程池====");
|
||||||
Threads.shutdownAndAwaitTermination(scheduledExecutorService);
|
ScheduledExecutorService pool = scheduledExecutorService;
|
||||||
|
if (pool != null && !pool.isShutdown()) {
|
||||||
|
pool.shutdown();
|
||||||
|
try {
|
||||||
|
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
|
||||||
|
pool.shutdownNow();
|
||||||
|
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
|
||||||
|
log.info("Pool did not terminate");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
pool.shutdownNow();
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印线程异常信息
|
||||||
|
*/
|
||||||
|
public static void printException(Runnable r, Throwable t) {
|
||||||
|
if (t == null && r instanceof Future<?>) {
|
||||||
|
try {
|
||||||
|
Future<?> future = (Future<?>) r;
|
||||||
|
if (future.isDone()) {
|
||||||
|
future.get();
|
||||||
|
}
|
||||||
|
} catch (CancellationException ce) {
|
||||||
|
t = ce;
|
||||||
|
} catch (ExecutionException ee) {
|
||||||
|
t = ee.getCause();
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (t != null) {
|
||||||
|
log.error(t.getMessage(), t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,5 +72,10 @@ public interface Constants {
|
|||||||
*/
|
*/
|
||||||
Long TOP_PARENT_ID = 0L;
|
Long TOP_PARENT_ID = 0L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加密头
|
||||||
|
*/
|
||||||
|
String ENCRYPT_HEADER = "ENC_";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ public class CompleteTaskDTO implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private String notice;
|
private String notice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 办理人(可不填 用于覆盖当前节点办理人)
|
||||||
|
*/
|
||||||
|
private String handler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流程变量
|
* 流程变量
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package org.dromara.common.core.domain.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程实例业务扩展对象
|
||||||
|
*
|
||||||
|
* @author may
|
||||||
|
* @date 2025-08-05
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class FlowInstanceBizExtDTO implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程实例ID
|
||||||
|
*/
|
||||||
|
private Long instanceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务ID
|
||||||
|
*/
|
||||||
|
private String businessId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务编码
|
||||||
|
*/
|
||||||
|
private String businessCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务标题
|
||||||
|
*/
|
||||||
|
private String businessTitle;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.dromara.common.core.domain.dto;
|
package org.dromara.common.core.domain.dto;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
@@ -30,11 +31,21 @@ public class StartProcessDTO implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private String flowCode;
|
private String flowCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 办理人(可不填 用于覆盖当前节点办理人)
|
||||||
|
*/
|
||||||
|
private String handler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流程变量,前端会提交一个元素{'entity': {业务详情数据对象}}
|
* 流程变量,前端会提交一个元素{'entity': {业务详情数据对象}}
|
||||||
*/
|
*/
|
||||||
private Map<String, Object> variables;
|
private Map<String, Object> variables;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程业务扩展信息
|
||||||
|
*/
|
||||||
|
private FlowInstanceBizExtDTO bizExt;
|
||||||
|
|
||||||
public Map<String, Object> getVariables() {
|
public Map<String, Object> getVariables() {
|
||||||
if (variables == null) {
|
if (variables == null) {
|
||||||
return new HashMap<>(16);
|
return new HashMap<>(16);
|
||||||
@@ -42,4 +53,11 @@ public class StartProcessDTO implements Serializable {
|
|||||||
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
|
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
|
||||||
return variables;
|
return variables;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FlowInstanceBizExtDTO getBizExt() {
|
||||||
|
if (ObjectUtil.isNull(bizExt)) {
|
||||||
|
bizExt = new FlowInstanceBizExtDTO();
|
||||||
|
}
|
||||||
|
return bizExt;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,14 +55,14 @@ public class TaskAssigneeDTO implements Serializable {
|
|||||||
Function<T, String> storageId,
|
Function<T, String> storageId,
|
||||||
Function<T, String> handlerCode,
|
Function<T, String> handlerCode,
|
||||||
Function<T, String> handlerName,
|
Function<T, String> handlerName,
|
||||||
Function<T, Long> groupName,
|
Function<T, String> 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),
|
storageId.apply(item),
|
||||||
handlerCode.apply(item),
|
handlerCode.apply(item),
|
||||||
handlerName.apply(item),
|
handlerName.apply(item),
|
||||||
groupName != null ? String.valueOf(groupName.apply(item)) : null,
|
groupName.apply(item),
|
||||||
createTimeMapper.apply(item)
|
createTimeMapper.apply(item)
|
||||||
)).collect(Collectors.toList());
|
)).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package org.dromara.common.core.exception;
|
package org.dromara.common.core.exception;
|
||||||
|
|
||||||
import lombok.*;
|
import cn.hutool.core.text.StrFormatter;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 业务异常
|
* 业务异常(支持占位符 {} )
|
||||||
*
|
*
|
||||||
* @author ruoyi
|
* @author ruoyi
|
||||||
*/
|
*/
|
||||||
@@ -42,6 +46,10 @@ 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;
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ 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);
|
||||||
|
|
||||||
@@ -90,6 +91,15 @@ 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.join(entry.getValue(), StringUtils.SEPARATOR));
|
params.put(entry.getKey(), StringUtils.joinComma(entry.getValue()));
|
||||||
}
|
}
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ 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;
|
||||||
@@ -31,8 +30,10 @@ public class StreamUtils {
|
|||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return CollUtil.newArrayList();
|
return CollUtil.newArrayList();
|
||||||
}
|
}
|
||||||
// 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
|
return collection.stream()
|
||||||
return collection.stream().filter(function).collect(Collectors.toList());
|
.filter(function)
|
||||||
|
// 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
|
||||||
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,13 +41,26 @@ public class StreamUtils {
|
|||||||
*
|
*
|
||||||
* @param collection 需要查询的集合
|
* @param collection 需要查询的集合
|
||||||
* @param function 过滤方法
|
* @param function 过滤方法
|
||||||
* @return 找到符合条件的第一个元素,没有则返回null
|
* @return 找到符合条件的第一个元素,没有则返回 Optional.empty()
|
||||||
*/
|
*/
|
||||||
public static <E> E findFirst(Collection<E> collection, Predicate<E> function) {
|
public static <E> Optional<E> findFirst(Collection<E> collection, Predicate<E> function) {
|
||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return null;
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
return collection.stream().filter(function).findFirst().orElse(null);
|
return collection.stream()
|
||||||
|
.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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,13 +68,26 @@ public class StreamUtils {
|
|||||||
*
|
*
|
||||||
* @param collection 需要查询的集合
|
* @param collection 需要查询的集合
|
||||||
* @param function 过滤方法
|
* @param function 过滤方法
|
||||||
* @return 找到符合条件的任意一个元素,没有则返回null
|
* @return 找到符合条件的任意一个元素,没有则返回 Optional.empty()
|
||||||
*/
|
*/
|
||||||
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().filter(function).findAny();
|
return collection.stream()
|
||||||
|
.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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,7 +113,10 @@ public class StreamUtils {
|
|||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return StringUtils.EMPTY;
|
return StringUtils.EMPTY;
|
||||||
}
|
}
|
||||||
return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.joining(delimiter));
|
return collection.stream()
|
||||||
|
.map(function)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.joining(delimiter));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -100,8 +130,11 @@ public class StreamUtils {
|
|||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return CollUtil.newArrayList();
|
return CollUtil.newArrayList();
|
||||||
}
|
}
|
||||||
// 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
|
return collection.stream()
|
||||||
return collection.stream().filter(Objects::nonNull).sorted(comparing).collect(Collectors.toList());
|
.filter(Objects::nonNull)
|
||||||
|
.sorted(comparing)
|
||||||
|
// 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
|
||||||
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -118,7 +151,9 @@ public class StreamUtils {
|
|||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return MapUtil.newHashMap();
|
return MapUtil.newHashMap();
|
||||||
}
|
}
|
||||||
return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, Function.identity(), (l, r) -> l));
|
return collection.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toMap(key, Function.identity(), (l, r) -> l));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -137,7 +172,25 @@ public class StreamUtils {
|
|||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return MapUtil.newHashMap();
|
return MapUtil.newHashMap();
|
||||||
}
|
}
|
||||||
return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, value, (l, r) -> l));
|
return collection.stream()
|
||||||
|
.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()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -154,8 +207,8 @@ public class StreamUtils {
|
|||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return MapUtil.newHashMap();
|
return MapUtil.newHashMap();
|
||||||
}
|
}
|
||||||
return collection
|
return collection.stream()
|
||||||
.stream().filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList()));
|
.collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,8 +228,8 @@ public class StreamUtils {
|
|||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return MapUtil.newHashMap();
|
return MapUtil.newHashMap();
|
||||||
}
|
}
|
||||||
return collection
|
return collection.stream()
|
||||||
.stream().filter(Objects::nonNull)
|
.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())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,11 +246,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) || key1 == null || key2 == null) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return MapUtil.newHashMap();
|
return MapUtil.newHashMap();
|
||||||
}
|
}
|
||||||
return collection
|
return collection.stream()
|
||||||
.stream().filter(Objects::nonNull)
|
.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)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,8 +268,7 @@ public class StreamUtils {
|
|||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return CollUtil.newArrayList();
|
return CollUtil.newArrayList();
|
||||||
}
|
}
|
||||||
return collection
|
return collection.stream()
|
||||||
.stream()
|
|
||||||
.map(function)
|
.map(function)
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
// 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
|
// 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
|
||||||
@@ -234,11 +286,10 @@ 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) || function == null) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return CollUtil.newHashSet();
|
return CollUtil.newHashSet();
|
||||||
}
|
}
|
||||||
return collection
|
return collection.stream()
|
||||||
.stream()
|
|
||||||
.map(function)
|
.map(function)
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
@@ -258,26 +309,20 @@ 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 (MapUtil.isEmpty(map1) && MapUtil.isEmpty(map2)) {
|
if (CollUtil.isEmpty(map1) && CollUtil.isEmpty(map2)) {
|
||||||
|
// 如果两个 map 都为空,则直接返回空的 map
|
||||||
return MapUtil.newHashMap();
|
return MapUtil.newHashMap();
|
||||||
} else if (MapUtil.isEmpty(map1)) {
|
} else if (CollUtil.isEmpty(map1)) {
|
||||||
map1 = MapUtil.newHashMap();
|
// 如果 map1 为空,则直接处理返回 map2
|
||||||
} else if (MapUtil.isEmpty(map2)) {
|
return toMap(map2.entrySet(), Map.Entry::getKey, entry -> merge.apply(null, entry.getValue()));
|
||||||
map2 = MapUtil.newHashMap();
|
} else if (CollUtil.isEmpty(map2)) {
|
||||||
|
// 如果 map2 为空,则直接处理返回 map1
|
||||||
|
return toMap(map1.entrySet(), Map.Entry::getKey, entry -> merge.apply(entry.getValue(), null));
|
||||||
}
|
}
|
||||||
Set<K> key = new HashSet<>();
|
Set<K> keySet = new HashSet<>();
|
||||||
key.addAll(map1.keySet());
|
keySet.addAll(map1.keySet());
|
||||||
key.addAll(map2.keySet());
|
keySet.addAll(map2.keySet());
|
||||||
Map<K, V> map = new HashMap<>();
|
return toMap(keySet, key -> key, key -> merge.apply(map1.get(key), map2.get(key)));
|
||||||
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(String.valueOf(c).repeat(size - len));
|
sb.append(Convert.toStr(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(String.valueOf(c).repeat(Math.max(0, size)));
|
sb.append(Convert.toStr(c).repeat(Math.max(0, size)));
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
@@ -361,5 +361,24 @@ 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,63 +0,0 @@
|
|||||||
package org.dromara.common.core.utils;
|
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 线程相关工具类.
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
|
||||||
public class Threads {
|
|
||||||
/**
|
|
||||||
* 停止线程池
|
|
||||||
* 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
|
|
||||||
* 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
|
|
||||||
* 如果仍然超時,則強制退出.
|
|
||||||
* 另对在shutdown时线程本身被调用中断做了处理.
|
|
||||||
*/
|
|
||||||
public static void shutdownAndAwaitTermination(ExecutorService pool) {
|
|
||||||
if (pool != null && !pool.isShutdown()) {
|
|
||||||
pool.shutdown();
|
|
||||||
try {
|
|
||||||
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
|
|
||||||
pool.shutdownNow();
|
|
||||||
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
|
|
||||||
log.info("Pool did not terminate");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (InterruptedException ie) {
|
|
||||||
pool.shutdownNow();
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 打印线程异常信息
|
|
||||||
*/
|
|
||||||
public static void printException(Runnable r, Throwable t) {
|
|
||||||
if (t == null && r instanceof Future<?>) {
|
|
||||||
try {
|
|
||||||
Future<?> future = (Future<?>) r;
|
|
||||||
if (future.isDone()) {
|
|
||||||
future.get();
|
|
||||||
}
|
|
||||||
} catch (CancellationException ce) {
|
|
||||||
t = ce;
|
|
||||||
} catch (ExecutionException ee) {
|
|
||||||
t = ee.getCause();
|
|
||||||
} catch (InterruptedException ie) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (t != null) {
|
|
||||||
log.error(t.getMessage(), t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,7 @@ 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;
|
||||||
@@ -92,8 +93,12 @@ 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);
|
||||||
return encryptor.encrypt(value, encryptContext.getEncode());
|
String encrypt = encryptor.encrypt(value, encryptContext.getEncode());
|
||||||
|
return Constants.ENCRYPT_HEADER + encrypt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -103,8 +108,12 @@ 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);
|
||||||
return encryptor.decrypt(value);
|
String str = StringUtils.removeStart(value, Constants.ENCRYPT_HEADER);
|
||||||
|
return encryptor.decrypt(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public class ExcelBigNumberConvert implements Converter<Long> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CellDataTypeEnum supportExcelTypeKey() {
|
public CellDataTypeEnum supportExcelTypeKey() {
|
||||||
return CellDataTypeEnum.STRING;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,200 @@
|
|||||||
|
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,24 +1,15 @@
|
|||||||
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.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,134 +21,39 @@ 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;
|
|
||||||
private int rowIndex;
|
public CellMergeStrategy(List<CellRangeAddress> cellList) {
|
||||||
|
this.cellList = cellList;
|
||||||
|
}
|
||||||
|
|
||||||
public CellMergeStrategy(List<?> list, boolean hasTitle) {
|
public CellMergeStrategy(List<?> list, boolean hasTitle) {
|
||||||
this.hasTitle = hasTitle;
|
this.cellList = CellMergeHandler.of(hasTitle).handle(list);
|
||||||
// 行合并开始下标
|
|
||||||
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){
|
cell.setBlank();
|
||||||
cell.setBlank();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@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,5 +1,6 @@
|
|||||||
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;
|
||||||
@@ -65,7 +66,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(String.valueOf(vars[i]));
|
String var = StrUtil.trimToEmpty(Convert.toStr(vars[i]));
|
||||||
if (!var.matches(regex)) {
|
if (!var.matches(regex)) {
|
||||||
throw new ServiceException("选项数据不符合规则,仅允许使用中英文字符以及数字");
|
throw new ServiceException("选项数据不符合规则,仅允许使用中英文字符以及数字");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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;
|
||||||
@@ -103,7 +104,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(String.format("字典 %s 不存在", dictType)))
|
.orElseThrow(() -> new ServiceException("字典 {} 不存在", dictType))
|
||||||
.values();
|
.values();
|
||||||
options = new ArrayList<>(values);
|
options = new ArrayList<>(values);
|
||||||
} else if (StringUtils.isNotBlank(converterExp)) {
|
} else if (StringUtils.isNotBlank(converterExp)) {
|
||||||
@@ -115,7 +116,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, String::valueOf);
|
options = StreamUtils.toList(values, Convert::toStr);
|
||||||
}
|
}
|
||||||
if (ObjectUtil.isNotEmpty(options)) {
|
if (ObjectUtil.isNotEmpty(options)) {
|
||||||
// 仅当下拉可选项不为空时执行
|
// 仅当下拉可选项不为空时执行
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ 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相关处理
|
||||||
@@ -203,6 +204,44 @@ 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);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单表多数据模板导出 模板格式为 {.属性}
|
* 单表多数据模板导出 模板格式为 {.属性}
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
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,5 +1,6 @@
|
|||||||
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;
|
||||||
@@ -28,20 +29,24 @@ import java.util.TimeZone;
|
|||||||
@AutoConfiguration(before = JacksonAutoConfiguration.class)
|
@AutoConfiguration(before = JacksonAutoConfiguration.class)
|
||||||
public class JacksonConfig {
|
public class JacksonConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Module registerJavaTimeModule() {
|
||||||
|
// 全局配置序列化返回 JSON 处理
|
||||||
|
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||||
|
javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE);
|
||||||
|
javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE);
|
||||||
|
javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE);
|
||||||
|
javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);
|
||||||
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
|
||||||
|
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
|
||||||
|
javaTimeModule.addDeserializer(Date.class, new CustomDateDeserializer());
|
||||||
|
return javaTimeModule;
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public Jackson2ObjectMapperBuilderCustomizer customizer() {
|
public Jackson2ObjectMapperBuilderCustomizer customizer() {
|
||||||
return builder -> {
|
return builder -> {
|
||||||
// 全局配置序列化返回 JSON 处理
|
|
||||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
|
||||||
javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE);
|
|
||||||
javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE);
|
|
||||||
javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE);
|
|
||||||
javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);
|
|
||||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
||||||
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
|
|
||||||
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
|
|
||||||
javaTimeModule.addDeserializer(Date.class, new CustomDateDeserializer());
|
|
||||||
builder.modules(javaTimeModule);
|
|
||||||
builder.timeZone(TimeZone.getDefault());
|
builder.timeZone(TimeZone.getDefault());
|
||||||
log.info("初始化 jackson 配置");
|
log.info("初始化 jackson 配置");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
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;
|
||||||
@@ -25,7 +27,11 @@ 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 {
|
||||||
return DateUtil.parse(p.getText());
|
DateTime parse = DateUtil.parse(p.getText());
|
||||||
|
if (ObjectUtils.isNull(parse)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parse.toJdkDate();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import cn.hutool.core.util.ArrayUtil;
|
|||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
|
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
@@ -167,4 +168,58 @@ public class JsonUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断字符串是否为合法 JSON(对象或数组)
|
||||||
|
*
|
||||||
|
* @param str 待校验字符串
|
||||||
|
* @return true = 合法 JSON,false = 非法或空
|
||||||
|
*/
|
||||||
|
public static boolean isJson(String str) {
|
||||||
|
if (StringUtils.isBlank(str)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
OBJECT_MAPPER.readTree(str);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断字符串是否为 JSON 对象({})
|
||||||
|
*
|
||||||
|
* @param str 待校验字符串
|
||||||
|
* @return true = JSON 对象
|
||||||
|
*/
|
||||||
|
public static boolean isJsonObject(String str) {
|
||||||
|
if (StringUtils.isBlank(str)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JsonNode node = OBJECT_MAPPER.readTree(str);
|
||||||
|
return node.isObject();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断字符串是否为 JSON 数组([])
|
||||||
|
*
|
||||||
|
* @param str 待校验字符串
|
||||||
|
* @return true = JSON 数组
|
||||||
|
*/
|
||||||
|
public static boolean isJsonArray(String str) {
|
||||||
|
if (StringUtils.isBlank(str)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JsonNode node = OBJECT_MAPPER.readTree(str);
|
||||||
|
return node.isArray();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package org.dromara.common.json.validate;
|
||||||
|
|
||||||
|
import jakarta.validation.Constraint;
|
||||||
|
import jakarta.validation.Payload;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON 格式校验注解
|
||||||
|
*
|
||||||
|
* @author AprilWind
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Target({ElementType.METHOD, ElementType.FIELD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Constraint(validatedBy = JsonPatternValidator.class)
|
||||||
|
public @interface JsonPattern {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 限制 JSON 类型,默认为 {@link JsonType#ANY},即对象或数组都允许
|
||||||
|
*/
|
||||||
|
JsonType type() default JsonType.ANY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验失败时的提示消息
|
||||||
|
*/
|
||||||
|
String message() default "不是有效的 JSON 格式";
|
||||||
|
|
||||||
|
Class<?>[] groups() default {};
|
||||||
|
|
||||||
|
Class<? extends Payload>[] payload() default {};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package org.dromara.common.json.validate;
|
||||||
|
|
||||||
|
import jakarta.validation.ConstraintValidator;
|
||||||
|
import jakarta.validation.ConstraintValidatorContext;
|
||||||
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
|
import org.dromara.common.json.utils.JsonUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON 格式校验器
|
||||||
|
*
|
||||||
|
* @author AprilWind
|
||||||
|
*/
|
||||||
|
public class JsonPatternValidator implements ConstraintValidator<JsonPattern, String> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注解中指定的 JSON 类型枚举
|
||||||
|
*/
|
||||||
|
private JsonType jsonType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化校验器,从注解中提取 JSON 类型
|
||||||
|
*
|
||||||
|
* @param annotation 注解实例
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void initialize(JsonPattern annotation) {
|
||||||
|
this.jsonType = annotation.type();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验字符串是否为合法 JSON
|
||||||
|
*
|
||||||
|
* @param value 待校验字符串
|
||||||
|
* @param context 校验上下文,可用于自定义错误信息
|
||||||
|
* @return true = 合法 JSON 或为空,false = 非法 JSON
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isValid(String value, ConstraintValidatorContext context) {
|
||||||
|
if (StringUtils.isBlank(value)) {
|
||||||
|
// 交给 @NotBlank 或 @NotNull 控制是否允许为空
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 根据 JSON 类型进行不同的校验
|
||||||
|
return switch (jsonType) {
|
||||||
|
case ANY -> JsonUtils.isJson(value);
|
||||||
|
case OBJECT -> JsonUtils.isJsonObject(value);
|
||||||
|
case ARRAY -> JsonUtils.isJsonArray(value);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package org.dromara.common.json.validate;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON 类型枚举
|
||||||
|
*
|
||||||
|
* @author AprilWind
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum JsonType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON 对象,例如 {"a":1}
|
||||||
|
*/
|
||||||
|
OBJECT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON 数组,例如 [1,2,3]
|
||||||
|
*/
|
||||||
|
ARRAY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任意 JSON 类型,对象或数组都可以
|
||||||
|
*/
|
||||||
|
ANY
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,11 +6,9 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
|
||||||
import com.baomidou.mybatisplus.core.toolkit.reflect.GenericTypeUtils;
|
import com.baomidou.mybatisplus.core.toolkit.reflect.GenericTypeUtils;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.toolkit.Db;
|
import com.baomidou.mybatisplus.extension.toolkit.Db;
|
||||||
import org.apache.ibatis.annotations.Param;
|
|
||||||
import org.apache.ibatis.logging.Log;
|
import org.apache.ibatis.logging.Log;
|
||||||
import org.apache.ibatis.logging.LogFactory;
|
import org.apache.ibatis.logging.LogFactory;
|
||||||
import org.dromara.common.core.utils.MapstructUtils;
|
import org.dromara.common.core.utils.MapstructUtils;
|
||||||
@@ -132,7 +130,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
|
|||||||
* @return 查询到的单个VO对象
|
* @return 查询到的单个VO对象
|
||||||
*/
|
*/
|
||||||
default V selectVoById(Serializable id) {
|
default V selectVoById(Serializable id) {
|
||||||
return this.selectVoById(id, this.currentVoClass());
|
return selectVoById(id, this.currentVoClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -158,7 +156,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
|
|||||||
* @return 查询到的VO对象列表
|
* @return 查询到的VO对象列表
|
||||||
*/
|
*/
|
||||||
default List<V> selectVoByIds(Collection<? extends Serializable> idList) {
|
default List<V> selectVoByIds(Collection<? extends Serializable> idList) {
|
||||||
return this.selectVoByIds(idList, this.currentVoClass());
|
return selectVoByIds(idList, this.currentVoClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -184,7 +182,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
|
|||||||
* @return 查询到的VO对象列表
|
* @return 查询到的VO对象列表
|
||||||
*/
|
*/
|
||||||
default List<V> selectVoByMap(Map<String, Object> map) {
|
default List<V> selectVoByMap(Map<String, Object> map) {
|
||||||
return this.selectVoByMap(map, this.currentVoClass());
|
return selectVoByMap(map, this.currentVoClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -210,7 +208,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
|
|||||||
* @return 查询到的单个VO对象
|
* @return 查询到的单个VO对象
|
||||||
*/
|
*/
|
||||||
default V selectVoOne(Wrapper<T> wrapper) {
|
default V selectVoOne(Wrapper<T> wrapper) {
|
||||||
return this.selectVoOne(wrapper, this.currentVoClass());
|
return selectVoOne(wrapper, this.currentVoClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -221,12 +219,11 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
|
|||||||
* @return 查询到的单个VO对象
|
* @return 查询到的单个VO对象
|
||||||
*/
|
*/
|
||||||
default V selectVoOne(Wrapper<T> wrapper, boolean throwEx) {
|
default V selectVoOne(Wrapper<T> wrapper, boolean throwEx) {
|
||||||
return this.selectVoOne(wrapper, this.currentVoClass(), throwEx);
|
return selectVoOne(wrapper, this.currentVoClass(), throwEx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据条件查询单个VO对象,并指定返回的VO对象的类型(自动拼接 limit 1)
|
* 根据条件查询单个VO对象,并指定返回的VO对象的类型
|
||||||
* 注意不要再自己添加 limit 1 做限制了
|
|
||||||
*
|
*
|
||||||
* @param wrapper 查询条件Wrapper
|
* @param wrapper 查询条件Wrapper
|
||||||
* @param voClass 返回的VO对象的Class对象
|
* @param voClass 返回的VO对象的Class对象
|
||||||
@@ -234,12 +231,11 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
|
|||||||
* @return 查询到的单个VO对象,经过类型转换为指定的VO类后返回
|
* @return 查询到的单个VO对象,经过类型转换为指定的VO类后返回
|
||||||
*/
|
*/
|
||||||
default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
|
default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
|
||||||
return this.selectVoOne(wrapper, voClass, true);
|
return selectVoOne(wrapper, voClass, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据条件查询单个实体对象,并将其转换为指定的VO对象(自动拼接 limit 1)
|
* 根据条件查询单个实体对象,并将其转换为指定的VO对象
|
||||||
* 注意不要再自己添加 limit 1 做限制了
|
|
||||||
*
|
*
|
||||||
* @param wrapper 查询条件Wrapper
|
* @param wrapper 查询条件Wrapper
|
||||||
* @param voClass 要转换的VO类的Class对象
|
* @param voClass 要转换的VO类的Class对象
|
||||||
@@ -255,33 +251,13 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
|
|||||||
return MapstructUtils.convert(obj, voClass);
|
return MapstructUtils.convert(obj, voClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据条件查询单条记录(自动拼接 limit 1 限制返回 1 条数据,不依赖 {@code throwEx} 参数)
|
|
||||||
* 注意不要再自己添加 limit 1 做限制了
|
|
||||||
* <p>
|
|
||||||
* <strong>注意:</strong>
|
|
||||||
* 1. 使用 {@code Page<>(1, 1)} 强制分页查询,确保 SQL 自动添加 {@code LIMIT 1},因此 {@code throwEx} 参数不再生效
|
|
||||||
* 2. 原方法的 {@code throwEx} 逻辑(多条数据抛异常)已被优化掉,因为分页查询不会返回多条记录
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param queryWrapper 查询条件(可为 null)
|
|
||||||
* @param throwEx <del>是否抛出异常(已弃用,此参数不再生效)</del>
|
|
||||||
* @return 单条记录或无数据时返回 null
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper, boolean throwEx) {
|
|
||||||
// 强制分页查询(LIMIT 1),确保最多返回 1 条记录
|
|
||||||
List<T> list = this.selectList(new Page<>(1, 1), queryWrapper);
|
|
||||||
return CollUtil.isEmpty(list) ? null : list.get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询所有VO对象列表
|
* 查询所有VO对象列表
|
||||||
*
|
*
|
||||||
* @return 查询到的VO对象列表
|
* @return 查询到的VO对象列表
|
||||||
*/
|
*/
|
||||||
default List<V> selectVoList() {
|
default List<V> selectVoList() {
|
||||||
return this.selectVoList(new QueryWrapper<>(), this.currentVoClass());
|
return selectVoList(new QueryWrapper<>(), this.currentVoClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -318,7 +294,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
|
|||||||
* @return 查询到的VO对象分页列表
|
* @return 查询到的VO对象分页列表
|
||||||
*/
|
*/
|
||||||
default <P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper) {
|
default <P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper) {
|
||||||
return this.selectVoPage(page, wrapper, this.currentVoClass());
|
return selectVoPage(page, wrapper, this.currentVoClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -42,17 +42,46 @@ public enum DataBaseType {
|
|||||||
* 根据数据库产品名称查找对应的数据库类型
|
* 根据数据库产品名称查找对应的数据库类型
|
||||||
*
|
*
|
||||||
* @param databaseProductName 数据库产品名称
|
* @param databaseProductName 数据库产品名称
|
||||||
* @return 对应的数据库类型枚举值,如果未找到则返回 null
|
* @return 对应的数据库类型枚举值
|
||||||
*/
|
*/
|
||||||
public static DataBaseType find(String databaseProductName) {
|
public static DataBaseType find(String databaseProductName) {
|
||||||
if (StringUtils.isBlank(databaseProductName)) {
|
if (StringUtils.isBlank(databaseProductName)) {
|
||||||
return null;
|
return MY_SQL;
|
||||||
}
|
}
|
||||||
for (DataBaseType type : values()) {
|
for (DataBaseType type : values()) {
|
||||||
if (type.getType().equals(databaseProductName)) {
|
if (type.getType().equals(databaseProductName)) {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return MY_SQL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为 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,10 +1,11 @@
|
|||||||
package org.dromara.common.mybatis.handler;
|
package org.dromara.common.mybatis.handler;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.exception.NotLoginException;
|
||||||
import cn.hutool.http.HttpStatus;
|
import cn.hutool.http.HttpStatus;
|
||||||
|
import com.baomidou.dynamic.datasource.exception.CannotFindDataSourceException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.dromara.common.core.domain.R;
|
import org.dromara.common.core.domain.R;
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
|
||||||
import org.mybatis.spring.MyBatisSystemException;
|
import org.mybatis.spring.MyBatisSystemException;
|
||||||
import org.springframework.dao.DuplicateKeyException;
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
@@ -35,13 +36,54 @@ public class MybatisExceptionHandler {
|
|||||||
@ExceptionHandler(MyBatisSystemException.class)
|
@ExceptionHandler(MyBatisSystemException.class)
|
||||||
public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
|
public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
|
||||||
String requestURI = request.getRequestURI();
|
String requestURI = request.getRequestURI();
|
||||||
String message = e.getMessage();
|
Throwable root = getRootCause(e);
|
||||||
if (StringUtils.contains(message, "CannotFindDataSourceException")) {
|
if (root instanceof NotLoginException) {
|
||||||
|
log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, root.getMessage());
|
||||||
|
return R.fail(HttpStatus.HTTP_UNAUTHORIZED, "认证失败,无法访问系统资源");
|
||||||
|
}
|
||||||
|
if (root instanceof CannotFindDataSourceException) {
|
||||||
log.error("请求地址'{}', 未找到数据源", requestURI);
|
log.error("请求地址'{}', 未找到数据源", requestURI);
|
||||||
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "未找到数据源,请联系管理员确认");
|
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "未找到数据源,请联系管理员确认");
|
||||||
}
|
}
|
||||||
log.error("请求地址'{}', Mybatis系统异常", requestURI, e);
|
log.error("请求地址'{}', Mybatis系统异常", requestURI, e);
|
||||||
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, message);
|
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取异常的根因(递归查找)
|
||||||
|
*
|
||||||
|
* @param e 当前异常
|
||||||
|
* @return 根因异常(最底层的 cause)
|
||||||
|
* <p>
|
||||||
|
* 逻辑说明:
|
||||||
|
* 1. 如果 e 没有 cause,说明 e 本身就是根因,直接返回
|
||||||
|
* 2. 如果 e 的 cause 和自身相同(防止循环引用),也返回 e
|
||||||
|
* 3. 否则递归调用,继续向下寻找最底层的 cause
|
||||||
|
*/
|
||||||
|
public static Throwable getRootCause(Throwable e) {
|
||||||
|
Throwable cause = e.getCause();
|
||||||
|
if (cause == null || cause == e) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
return getRootCause(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在异常链中查找指定类型的异常
|
||||||
|
*
|
||||||
|
* @param e 当前异常
|
||||||
|
* @param clazz 目标异常类
|
||||||
|
* @return 找到的指定类型异常,如果没有找到返回 null
|
||||||
|
*/
|
||||||
|
public static Throwable findCause(Throwable e, Class<? extends Throwable> clazz) {
|
||||||
|
Throwable t = e;
|
||||||
|
while (t != null && t != t.getCause()) {
|
||||||
|
if (clazz.isInstance(t)) {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
t = t.getCause();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
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;
|
||||||
@@ -10,7 +9,6 @@ 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;
|
||||||
@@ -22,22 +20,13 @@ 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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -26,7 +26,14 @@ 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();
|
||||||
@@ -39,37 +46,31 @@ public class DataBaseHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isMySql() {
|
/**
|
||||||
return DataBaseType.MY_SQL == getDataBaseType();
|
* 根据当前数据库类型,生成兼容的 FIND_IN_SET 语句片段
|
||||||
}
|
* <p>
|
||||||
|
* 用于判断指定值是否存在于逗号分隔的字符串列中,SQL写法根据不同数据库方言自动切换:
|
||||||
public static boolean isOracle() {
|
* - Oracle 使用 instr 函数
|
||||||
return DataBaseType.ORACLE == getDataBaseType();
|
* - PostgreSQL 使用 strpos 函数
|
||||||
}
|
* - SQL Server 使用 charindex 函数
|
||||||
|
* - 其他默认使用 MySQL 的 find_in_set 函数
|
||||||
public static boolean isPostgerSql() {
|
*
|
||||||
return DataBaseType.POSTGRE_SQL == getDataBaseType();
|
* @param var1 要查找的值(支持任意类型,内部会转换成字符串)
|
||||||
}
|
* @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);
|
||||||
if (dataBasyType == DataBaseType.SQL_SERVER) {
|
return switch (getDataBaseType()) {
|
||||||
// charindex(',100,' , ',0,100,101,') <> 0
|
|
||||||
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
|
// instr(',0,100,101,' , ',100,') <> 0
|
||||||
return "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var);
|
case ORACLE -> "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var);
|
||||||
}
|
// (select strpos(',0,100,101,' , ',100,')) <> 0
|
||||||
// find_in_set(100 , '0,100,101')
|
case POSTGRE_SQL -> "(select strpos(','||%s||',' , ',%s,')) <> 0".formatted(var2, var);
|
||||||
return "find_in_set('%s' , %s) <> 0".formatted(var, var2);
|
// charindex(',100,' , ',0,100,101,') <> 0
|
||||||
|
case SQL_SERVER -> "charindex(',%s,' , ','+%s+',') <> 0".formatted(var, var2);
|
||||||
|
// find_in_set(100 , '0,100,101')
|
||||||
|
default -> "find_in_set('%s' , %s) <> 0".formatted(var, var2);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ 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;
|
||||||
@@ -13,9 +14,7 @@ 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.ResponseInputStream;
|
import software.amazon.awssdk.core.async.*;
|
||||||
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;
|
||||||
@@ -29,9 +28,12 @@ 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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,6 +42,7 @@ import java.util.function.Consumer;
|
|||||||
*
|
*
|
||||||
* @author AprilWind
|
* @author AprilWind
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class OssClient {
|
public class OssClient {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -237,30 +240,61 @@ 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<ResponseInputStream<GetObjectResponse>> downloadRequest = DownloadRequest.builder()
|
DownloadRequest<ResponsePublisher<GetObjectResponse>> publisherDownloadRequest = 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.toBlockingInputStream())
|
.responseTransformer(AsyncResponseTransformer.toPublisher())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// 使用 S3TransferManager 下载文件
|
// 使用 S3TransferManager 下载文件
|
||||||
Download<ResponseInputStream<GetObjectResponse>> responseFuture = transferManager.download(downloadRequest);
|
Download<ResponsePublisher<GetObjectResponse>> publisherDownload = transferManager.download(publisherDownloadRequest);
|
||||||
// 输出到流中
|
// 获取下载发布订阅转换器
|
||||||
try (ResponseInputStream<GetObjectResponse> responseStream = responseFuture.completionFuture().join().result()) { // auto-closeable stream
|
ResponsePublisher<GetObjectResponse> publisher = publisherDownload.completionFuture().join().result();
|
||||||
if (consumer != null) {
|
// 执行文件大小消费者函数
|
||||||
consumer.accept(responseStream.response().contentLength());
|
Optional.ofNullable(contentLengthConsumer)
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).join();
|
||||||
}
|
}
|
||||||
responseStream.transferTo(out); // 阻塞调用线程 blocks the calling thread
|
};
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new OssException("文件下载失败,错误信息:[" + e.getMessage() + "]");
|
throw new OssException("文件下载失败,错误信息:[" + e.getMessage() + "]");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
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) {
|
||||||
setCacheObject(key, value);
|
bucket.set(value);
|
||||||
} else {
|
} else {
|
||||||
setCacheObject(key, value, Duration.ofMillis(timeToLive));
|
bucket.set(value, Duration.ofMillis(timeToLive));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -147,11 +147,8 @@ 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) {
|
||||||
RBatch batch = CLIENT.createBatch();
|
RBucket<T> bucket = CLIENT.getBucket(key);
|
||||||
RBucketAsync<T> bucket = batch.getBucket(key);
|
bucket.set(value, duration);
|
||||||
bucket.setAsync(value);
|
|
||||||
bucket.expireAsync(duration);
|
|
||||||
batch.execute();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
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 lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
@@ -119,7 +120,7 @@ public class SequenceUtils {
|
|||||||
* @return 唯一id
|
* @return 唯一id
|
||||||
*/
|
*/
|
||||||
public static String getNextIdString(String key, Duration expireTime, long initValue, long stepValue) {
|
public static String getNextIdString(String key, Duration expireTime, long initValue, long stepValue) {
|
||||||
return String.valueOf(getNextId(key, expireTime, initValue, stepValue));
|
return Convert.toStr(getNextId(key, expireTime, initValue, stepValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -130,7 +131,7 @@ public class SequenceUtils {
|
|||||||
* @return 唯一id
|
* @return 唯一id
|
||||||
*/
|
*/
|
||||||
public static String getNextIdString(String key, Duration expireTime) {
|
public static String getNextIdString(String key, Duration expireTime) {
|
||||||
return String.valueOf(getNextId(key, expireTime));
|
return Convert.toStr(getNextId(key, expireTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import cn.dev33.satoken.interceptor.SaInterceptor;
|
|||||||
import cn.dev33.satoken.router.SaRouter;
|
import cn.dev33.satoken.router.SaRouter;
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
import cn.dev33.satoken.util.SaResult;
|
import cn.dev33.satoken.util.SaResult;
|
||||||
|
import cn.dev33.satoken.util.SaTokenConsts;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.dromara.common.core.constant.HttpStatus;
|
import org.dromara.common.core.constant.HttpStatus;
|
||||||
@@ -55,6 +57,8 @@ public class SecurityConfig implements WebMvcConfigurer {
|
|||||||
// 对未排除的路径进行检查
|
// 对未排除的路径进行检查
|
||||||
.check(() -> {
|
.check(() -> {
|
||||||
HttpServletRequest request = ServletUtils.getRequest();
|
HttpServletRequest request = ServletUtils.getRequest();
|
||||||
|
HttpServletResponse response = ServletUtils.getResponse();
|
||||||
|
response.setContentType(SaTokenConsts.CONTENT_TYPE_APPLICATION_JSON);
|
||||||
// 检查是否登录 是否有token
|
// 检查是否登录 是否有token
|
||||||
StpUtil.checkLogin();
|
StpUtil.checkLogin();
|
||||||
|
|
||||||
@@ -94,7 +98,11 @@ public class SecurityConfig implements WebMvcConfigurer {
|
|||||||
.setAuth(obj -> {
|
.setAuth(obj -> {
|
||||||
SaHttpBasicUtil.check(username + ":" + password);
|
SaHttpBasicUtil.check(username + ":" + password);
|
||||||
})
|
})
|
||||||
.setError(e -> SaResult.error(e.getMessage()).setCode(HttpStatus.UNAUTHORIZED));
|
.setError(e -> {
|
||||||
|
HttpServletResponse response = ServletUtils.getResponse();
|
||||||
|
response.setContentType(SaTokenConsts.CONTENT_TYPE_APPLICATION_JSON);
|
||||||
|
return SaResult.error(e.getMessage()).setCode(HttpStatus.UNAUTHORIZED);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
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;
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@ public enum SensitiveStrategy {
|
|||||||
/**
|
/**
|
||||||
* 用户ID
|
* 用户ID
|
||||||
*/
|
*/
|
||||||
USER_ID(s -> String.valueOf(DesensitizedUtil.userId())),
|
USER_ID(s -> Convert.toStr(DesensitizedUtil.userId())),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 密码
|
* 密码
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package me.zhyd.oauth.request;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.xkcoding.http.support.HttpHeader;
|
||||||
|
import me.zhyd.oauth.cache.AuthStateCache;
|
||||||
|
import me.zhyd.oauth.config.AuthConfig;
|
||||||
|
import me.zhyd.oauth.config.AuthDefaultSource;
|
||||||
|
import me.zhyd.oauth.enums.scope.AuthDingTalkScope;
|
||||||
|
import me.zhyd.oauth.exception.AuthException;
|
||||||
|
import me.zhyd.oauth.model.AuthCallback;
|
||||||
|
import me.zhyd.oauth.model.AuthToken;
|
||||||
|
import me.zhyd.oauth.model.AuthUser;
|
||||||
|
import me.zhyd.oauth.utils.AuthScopeUtils;
|
||||||
|
import me.zhyd.oauth.utils.GlobalAuthUtils;
|
||||||
|
import me.zhyd.oauth.utils.HttpUtils;
|
||||||
|
import me.zhyd.oauth.utils.UrlBuilder;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新版钉钉二维码登录
|
||||||
|
*
|
||||||
|
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
|
||||||
|
* @since 1.16.7
|
||||||
|
*/
|
||||||
|
public class AuthDingTalkV2Request extends AuthDefaultRequest {
|
||||||
|
|
||||||
|
public AuthDingTalkV2Request(AuthConfig config) {
|
||||||
|
super(config, AuthDefaultSource.DINGTALK_V2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthDingTalkV2Request(AuthConfig config, AuthStateCache authStateCache) {
|
||||||
|
super(config, AuthDefaultSource.DINGTALK_V2, authStateCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String authorize(String state) {
|
||||||
|
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||||
|
.queryParam("response_type", "code")
|
||||||
|
.queryParam("client_id", config.getClientId())
|
||||||
|
.queryParam("scope", this.getScopes(",", true, AuthScopeUtils.getDefaultScopes(AuthDingTalkScope.values())))
|
||||||
|
.queryParam("redirect_uri", GlobalAuthUtils.urlEncode(config.getRedirectUri()))
|
||||||
|
.queryParam("prompt", "consent")
|
||||||
|
.queryParam("org_type", config.getDingTalkOrgType())
|
||||||
|
.queryParam("corpId", config.getDingTalkCorpId())
|
||||||
|
.queryParam("exclusiveLogin", config.isDingTalkExclusiveLogin())
|
||||||
|
.queryParam("exclusiveCorpId", config.getDingTalkExclusiveCorpId())
|
||||||
|
.queryParam("state", getRealState(state))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthToken getAccessToken(AuthCallback authCallback) {
|
||||||
|
Map<String, String> params = new HashMap<>();
|
||||||
|
params.put("grantType", "authorization_code");
|
||||||
|
params.put("clientId", config.getClientId());
|
||||||
|
params.put("clientSecret", config.getClientSecret());
|
||||||
|
params.put("code", authCallback.getCode());
|
||||||
|
String response = new HttpUtils(config.getHttpConfig()).post(this.source.accessToken(), JSONObject.toJSONString(params)).getBody();
|
||||||
|
JSONObject accessTokenObject = JSONObject.parseObject(response);
|
||||||
|
if (!accessTokenObject.containsKey("accessToken")) {
|
||||||
|
throw new AuthException(JSONObject.toJSONString(response), source);
|
||||||
|
}
|
||||||
|
return AuthToken.builder()
|
||||||
|
.accessToken(accessTokenObject.getString("accessToken"))
|
||||||
|
.refreshToken(accessTokenObject.getString("refreshToken"))
|
||||||
|
.expireIn(accessTokenObject.getIntValue("expireIn"))
|
||||||
|
.corpId(accessTokenObject.getString("corpId"))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthUser getUserInfo(AuthToken authToken) {
|
||||||
|
HttpHeader header = new HttpHeader();
|
||||||
|
header.add("x-acs-dingtalk-access-token", authToken.getAccessToken());
|
||||||
|
|
||||||
|
String response = new HttpUtils(config.getHttpConfig()).get(this.source.userInfo(), null, header, false).getBody();
|
||||||
|
JSONObject object = JSONObject.parseObject(response);
|
||||||
|
|
||||||
|
authToken.setOpenId(object.getString("openId"));
|
||||||
|
authToken.setUnionId(object.getString("unionId"));
|
||||||
|
return AuthUser.builder()
|
||||||
|
.rawUserInfo(object)
|
||||||
|
.uuid(object.getString("unionId"))
|
||||||
|
.username(object.getString("nick"))
|
||||||
|
.nickname(object.getString("nick"))
|
||||||
|
.avatar(object.getString("avatarUrl"))
|
||||||
|
.snapshotUser(object.getBooleanValue("visitor"))
|
||||||
|
.token(authToken)
|
||||||
|
.source(source.toString())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回获取accessToken的url
|
||||||
|
*
|
||||||
|
* @param code 授权码
|
||||||
|
* @return 返回获取accessToken的url
|
||||||
|
*/
|
||||||
|
protected String accessTokenUrl(String code) {
|
||||||
|
return UrlBuilder.fromBaseUrl(source.accessToken())
|
||||||
|
.queryParam("code", code)
|
||||||
|
.queryParam("clientId", config.getClientId())
|
||||||
|
.queryParam("clientSecret", config.getClientSecret())
|
||||||
|
.queryParam("grantType", "authorization_code")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,21 @@
|
|||||||
package org.dromara.common.sse.core;
|
package org.dromara.common.sse.core;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.map.MapUtil;
|
import cn.hutool.core.map.MapUtil;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.dromara.common.core.utils.SpringUtils;
|
||||||
import org.dromara.common.redis.utils.RedisUtils;
|
import org.dromara.common.redis.utils.RedisUtils;
|
||||||
import org.dromara.common.sse.dto.SseMessageDto;
|
import org.dromara.common.sse.dto.SseMessageDto;
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,6 +33,12 @@ public class SseEmitterManager {
|
|||||||
|
|
||||||
private final static Map<Long, Map<String, SseEmitter>> USER_TOKEN_EMITTERS = new ConcurrentHashMap<>();
|
private final static Map<Long, Map<String, SseEmitter>> USER_TOKEN_EMITTERS = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public SseEmitterManager() {
|
||||||
|
// 定时执行 SSE 心跳检测
|
||||||
|
SpringUtils.getBean(ScheduledExecutorService.class)
|
||||||
|
.scheduleWithFixedDelay(this::sseMonitor, 60L, 60L, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 建立与指定用户的 SSE 连接
|
* 建立与指定用户的 SSE 连接
|
||||||
*
|
*
|
||||||
@@ -38,6 +51,12 @@ public class SseEmitterManager {
|
|||||||
// 每个用户可以有多个 SSE 连接,通过 token 进行区分
|
// 每个用户可以有多个 SSE 连接,通过 token 进行区分
|
||||||
Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.computeIfAbsent(userId, k -> new ConcurrentHashMap<>());
|
Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.computeIfAbsent(userId, k -> new ConcurrentHashMap<>());
|
||||||
|
|
||||||
|
// 关闭已存在的SseEmitter,防止超过最大连接数
|
||||||
|
SseEmitter oldEmitter = emitters.remove(token);
|
||||||
|
if (oldEmitter != null) {
|
||||||
|
oldEmitter.complete();
|
||||||
|
}
|
||||||
|
|
||||||
// 创建一个新的 SseEmitter 实例,超时时间设置为一天 避免连接之后直接关闭浏览器导致连接停滞
|
// 创建一个新的 SseEmitter 实例,超时时间设置为一天 避免连接之后直接关闭浏览器导致连接停滞
|
||||||
SseEmitter emitter = new SseEmitter(86400000L);
|
SseEmitter emitter = new SseEmitter(86400000L);
|
||||||
|
|
||||||
@@ -97,6 +116,44 @@ public class SseEmitterManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SSE 心跳检测,关闭无效连接
|
||||||
|
*/
|
||||||
|
public void sseMonitor() {
|
||||||
|
final SseEmitter.SseEventBuilder heartbeat = SseEmitter.event().comment("heartbeat");
|
||||||
|
// 记录需要移除的用户ID
|
||||||
|
List<Long> toRemoveUsers = new ArrayList<>();
|
||||||
|
|
||||||
|
USER_TOKEN_EMITTERS.forEach((userId, emitterMap) -> {
|
||||||
|
if (CollUtil.isEmpty(emitterMap)) {
|
||||||
|
toRemoveUsers.add(userId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emitterMap.entrySet().removeIf(entry -> {
|
||||||
|
try {
|
||||||
|
entry.getValue().send(heartbeat);
|
||||||
|
return false;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
try {
|
||||||
|
entry.getValue().complete();
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
// 忽略重复关闭异常
|
||||||
|
}
|
||||||
|
return true; // 发送失败 → 移除该连接
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 移除空连接用户
|
||||||
|
if (emitterMap.isEmpty()) {
|
||||||
|
toRemoveUsers.add(userId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 循环结束后统一清理空用户,避免并发修改异常
|
||||||
|
toRemoveUsers.forEach(USER_TOKEN_EMITTERS::remove);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订阅SSE消息主题,并提供一个消费者函数来处理接收到的消息
|
* 订阅SSE消息主题,并提供一个消费者函数来处理接收到的消息
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -46,8 +46,14 @@ public class TranslationHandler extends JsonSerializer<Object> implements Contex
|
|||||||
gen.writeNull();
|
gen.writeNull();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Object result = trans.translation(value, translation.other());
|
try {
|
||||||
gen.writeObject(result);
|
Object result = trans.translation(value, translation.other());
|
||||||
|
gen.writeObject(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("翻译处理异常,type: {}, value: {}", translation.type(), value, e);
|
||||||
|
// 出现异常时输出原始值而不是中断序列化
|
||||||
|
gen.writeObject(value);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
gen.writeObject(value);
|
gen.writeObject(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
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.StringUtils;
|
import org.dromara.common.core.utils.ObjectUtils;
|
||||||
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;
|
||||||
@@ -34,10 +35,11 @@ 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 -> {
|
||||||
if (StringUtils.isBlank(source)) {
|
DateTime parse = DateUtil.parse(source);
|
||||||
|
if (ObjectUtils.isNull(parse)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return DateUtil.parse(source);
|
return parse.toJdkDate();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(UnsignedMathGenerator.class),
|
MATH(MathGenerator.class),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字符
|
* 字符
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import org.springframework.web.bind.MissingPathVariableException;
|
|||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
|
||||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||||
import org.springframework.web.servlet.NoHandlerFoundException;
|
import org.springframework.web.servlet.NoHandlerFoundException;
|
||||||
|
|
||||||
@@ -123,7 +124,7 @@ public class GlobalExceptionHandler {
|
|||||||
*/
|
*/
|
||||||
@ResponseStatus(org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR)
|
@ResponseStatus(org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
@ExceptionHandler(IOException.class)
|
@ExceptionHandler(IOException.class)
|
||||||
public void handleRuntimeException(IOException e, HttpServletRequest request) {
|
public void handleIoException(IOException e, HttpServletRequest request) {
|
||||||
String requestURI = request.getRequestURI();
|
String requestURI = request.getRequestURI();
|
||||||
if (requestURI.contains("sse")) {
|
if (requestURI.contains("sse")) {
|
||||||
// sse 经常性连接中断 例如关闭浏览器 直接屏蔽
|
// sse 经常性连接中断 例如关闭浏览器 直接屏蔽
|
||||||
@@ -132,6 +133,13 @@ public class GlobalExceptionHandler {
|
|||||||
log.error("请求地址'{}',连接中断", requestURI, e);
|
log.error("请求地址'{}',连接中断", requestURI, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sse 连接超时异常 不需要处理
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(AsyncRequestTimeoutException.class)
|
||||||
|
public void handleRuntimeException(AsyncRequestTimeoutException e) {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 拦截未知的运行时异常
|
* 拦截未知的运行时异常
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
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.15-cds
|
FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds
|
||||||
#FROM bellsoft/liberica-openjdk-rocky:21.0.7-cds
|
#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds
|
||||||
#FROM findepi/graalvm:java17-native
|
#FROM findepi/graalvm:java17-native
|
||||||
|
|
||||||
LABEL maintainer="Lion Li"
|
LABEL maintainer="Lion Li"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
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;
|
||||||
|
|
||||||
@@ -8,6 +9,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|||||||
*
|
*
|
||||||
* @author Lion Li
|
* @author Lion Li
|
||||||
*/
|
*/
|
||||||
|
@EnableAdminServer
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class MonitorAdminApplication {
|
public class MonitorAdminApplication {
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -20,6 +20,9 @@ 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.15-cds
|
FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds
|
||||||
#FROM bellsoft/liberica-openjdk-rocky:21.0.7-cds
|
#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds
|
||||||
#FROM findepi/graalvm:java17-native
|
#FROM findepi/graalvm:java17-native
|
||||||
|
|
||||||
LABEL maintainer="Lion Li"
|
LABEL maintainer="Lion Li"
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ spring:
|
|||||||
snail-job:
|
snail-job:
|
||||||
# 服务端节点IP(默认按照`NetUtil.getLocalIpStr()`)
|
# 服务端节点IP(默认按照`NetUtil.getLocalIpStr()`)
|
||||||
server-host:
|
server-host:
|
||||||
# 服务端netty的端口号
|
# 服务端端口号
|
||||||
server-port: 17888
|
server-port: 17888
|
||||||
# 合并日志默认保存天数
|
# 合并日志默认保存天数
|
||||||
merge-Log-days: 1
|
merge-Log-days: 1
|
||||||
@@ -34,8 +34,6 @@ snail-job:
|
|||||||
summary-day: 7
|
summary-day: 7
|
||||||
# 配置负载均衡周期时间
|
# 配置负载均衡周期时间
|
||||||
load-balance-cycle-time: 10
|
load-balance-cycle-time: 10
|
||||||
# 通知类型默认使用grpc(netty 已经下线)
|
|
||||||
rpc-type: grpc
|
|
||||||
# 重试任务拉取的并行度
|
# 重试任务拉取的并行度
|
||||||
retry-max-pull-parallel: 2
|
retry-max-pull-parallel: 2
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ spring:
|
|||||||
snail-job:
|
snail-job:
|
||||||
# 服务端节点IP(默认按照`NetUtil.getLocalIpStr()`)
|
# 服务端节点IP(默认按照`NetUtil.getLocalIpStr()`)
|
||||||
server-host:
|
server-host:
|
||||||
# 服务端netty的端口号
|
# 服务端端口号
|
||||||
server-port: 17888
|
server-port: 17888
|
||||||
# 合并日志默认保存天数
|
# 合并日志默认保存天数
|
||||||
merge-Log-days: 1
|
merge-Log-days: 1
|
||||||
@@ -34,8 +34,6 @@ snail-job:
|
|||||||
summary-day: 7
|
summary-day: 7
|
||||||
# 配置负载均衡周期时间
|
# 配置负载均衡周期时间
|
||||||
load-balance-cycle-time: 10
|
load-balance-cycle-time: 10
|
||||||
# 通知类型默认使用grpc(netty 已经下线)
|
|
||||||
rpc-type: grpc
|
|
||||||
# 重试任务拉取的并行度
|
# 重试任务拉取的并行度
|
||||||
retry-max-pull-parallel: 2
|
retry-max-pull-parallel: 2
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,7 @@
|
|||||||
<!-- 控制台输出日志级别 -->
|
<!-- 控制台输出日志级别 -->
|
||||||
<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 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;
|
||||||
@@ -14,6 +15,7 @@ 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;
|
||||||
@@ -94,6 +96,16 @@ 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,6 +2,8 @@ package org.dromara.demo.service;
|
|||||||
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出下拉框Excel示例
|
* 导出下拉框Excel示例
|
||||||
*
|
*
|
||||||
@@ -15,4 +17,11 @@ public interface IExportExcelService {
|
|||||||
* @param response /
|
* @param response /
|
||||||
*/
|
*/
|
||||||
void exportWithOptions(HttpServletResponse response);
|
void exportWithOptions(HttpServletResponse response);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义导出
|
||||||
|
*
|
||||||
|
* @param response /
|
||||||
|
*/
|
||||||
|
void customExport(HttpServletResponse response) throws IOException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,21 @@ 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;
|
||||||
@@ -233,4 +237,61 @@ 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ 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;
|
||||||
@@ -118,11 +119,12 @@ 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");
|
||||||
if (DataBaseHelper.isOracle()) {
|
DataBaseType dataBaseType = DataBaseHelper.getDataBaseType();
|
||||||
|
if (dataBaseType.isOracle()) {
|
||||||
templates.add("vm/sql/oracle/sql.vm");
|
templates.add("vm/sql/oracle/sql.vm");
|
||||||
} else if (DataBaseHelper.isPostgerSql()) {
|
} else if (dataBaseType.isPostgreSql()) {
|
||||||
templates.add("vm/sql/postgres/sql.vm");
|
templates.add("vm/sql/postgres/sql.vm");
|
||||||
} else if (DataBaseHelper.isSqlServer()) {
|
} else if (dataBaseType.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");
|
||||||
|
|||||||
@@ -54,11 +54,8 @@ export interface ${BusinessName}Query #if(!${treeCode})extends PageQuery #end{
|
|||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
/**
|
/**
|
||||||
* 日期范围参数
|
* 日期范围参数
|
||||||
*/
|
*/
|
||||||
params?: any;
|
params?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -123,7 +123,7 @@
|
|||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
<el-table-column label="操作" align="center" fixed="right" 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" class-name="small-padding fixed-width">
|
<el-table-column label="操作" align="center" fixed="right" 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,7 +1,6 @@
|
|||||||
<?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;
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package org.dromara.job.snailjob;
|
|||||||
|
|
||||||
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.core.util.JsonUtil;
|
import com.aizuda.snailjob.common.core.util.JsonUtil;
|
||||||
import com.aizuda.snailjob.common.log.SnailJobLog;
|
import com.aizuda.snailjob.common.log.SnailJobLog;
|
||||||
|
import com.aizuda.snailjob.model.dto.ExecuteResult;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package org.dromara.job.snailjob;
|
|||||||
import cn.hutool.core.util.RandomUtil;
|
import cn.hutool.core.util.RandomUtil;
|
||||||
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 lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package org.dromara.job.snailjob;
|
|||||||
|
|
||||||
import com.aizuda.snailjob.client.job.core.dto.JobArgs;
|
import com.aizuda.snailjob.client.job.core.dto.JobArgs;
|
||||||
import com.aizuda.snailjob.client.job.core.executor.AbstractJobExecutor;
|
import com.aizuda.snailjob.client.job.core.executor.AbstractJobExecutor;
|
||||||
import com.aizuda.snailjob.client.model.ExecuteResult;
|
import com.aizuda.snailjob.model.dto.ExecuteResult;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import com.aizuda.snailjob.client.job.core.MapHandler;
|
|||||||
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.annotation.MapExecutor;
|
import com.aizuda.snailjob.client.job.core.annotation.MapExecutor;
|
||||||
import com.aizuda.snailjob.client.job.core.dto.MapArgs;
|
import com.aizuda.snailjob.client.job.core.dto.MapArgs;
|
||||||
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.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import com.aizuda.snailjob.client.job.core.annotation.MapExecutor;
|
|||||||
import com.aizuda.snailjob.client.job.core.annotation.ReduceExecutor;
|
import com.aizuda.snailjob.client.job.core.annotation.ReduceExecutor;
|
||||||
import com.aizuda.snailjob.client.job.core.dto.MapArgs;
|
import com.aizuda.snailjob.client.job.core.dto.MapArgs;
|
||||||
import com.aizuda.snailjob.client.job.core.dto.ReduceArgs;
|
import com.aizuda.snailjob.client.job.core.dto.ReduceArgs;
|
||||||
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.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package org.dromara.job.snailjob;
|
package org.dromara.job.snailjob;
|
||||||
|
|
||||||
|
import cn.hutool.core.convert.Convert;
|
||||||
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.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,7 +18,7 @@ import org.springframework.stereotype.Component;
|
|||||||
public class TestStaticShardingJob {
|
public class TestStaticShardingJob {
|
||||||
|
|
||||||
public ExecuteResult jobExecute(JobArgs jobArgs) {
|
public ExecuteResult jobExecute(JobArgs jobArgs) {
|
||||||
String jobParams = String.valueOf(jobArgs.getJobParams());
|
String jobParams = Convert.toStr(jobArgs.getJobParams());
|
||||||
SnailJobLog.LOCAL.info("开始执行分片任务,参数:{}", jobParams);
|
SnailJobLog.LOCAL.info("开始执行分片任务,参数:{}", jobParams);
|
||||||
// 获得jobArgs 中传入的开始id和结束id
|
// 获得jobArgs 中传入的开始id和结束id
|
||||||
String[] split = jobParams.split(",");
|
String[] split = jobParams.split(",");
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -53,6 +53,13 @@ public class CacheController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存监控列表信息
|
||||||
|
*
|
||||||
|
* @param info 信息
|
||||||
|
* @param dbSize 数据库
|
||||||
|
* @param commandStats 命令统计
|
||||||
|
*/
|
||||||
public record CacheListInfoVo(Properties info, Long dbSize, List<Map<String, String>> commandStats) {}
|
public record CacheListInfoVo(Properties info, Long dbSize, List<Map<String, String>> commandStats) {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,27 @@
|
|||||||
package org.dromara.system.controller.system;
|
package org.dromara.system.controller.system;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import jakarta.validation.constraints.*;
|
|
||||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
import org.dromara.common.idempotent.annotation.RepeatSubmit;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import org.dromara.common.log.annotation.Log;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.dromara.common.web.core.BaseController;
|
|
||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
|
||||||
import org.dromara.common.core.domain.R;
|
import org.dromara.common.core.domain.R;
|
||||||
import org.dromara.common.core.validate.AddGroup;
|
import org.dromara.common.core.validate.AddGroup;
|
||||||
import org.dromara.common.core.validate.EditGroup;
|
import org.dromara.common.core.validate.EditGroup;
|
||||||
import org.dromara.common.log.enums.BusinessType;
|
|
||||||
import org.dromara.common.excel.utils.ExcelUtil;
|
import org.dromara.common.excel.utils.ExcelUtil;
|
||||||
import org.dromara.system.domain.vo.SysClientVo;
|
import org.dromara.common.idempotent.annotation.RepeatSubmit;
|
||||||
import org.dromara.system.domain.bo.SysClientBo;
|
import org.dromara.common.log.annotation.Log;
|
||||||
import org.dromara.system.service.ISysClientService;
|
import org.dromara.common.log.enums.BusinessType;
|
||||||
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
|
import org.dromara.common.web.core.BaseController;
|
||||||
|
import org.dromara.system.domain.bo.SysClientBo;
|
||||||
|
import org.dromara.system.domain.vo.SysClientVo;
|
||||||
|
import org.dromara.system.service.ISysClientService;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 客户端管理
|
* 客户端管理
|
||||||
@@ -76,6 +77,9 @@ public class SysClientController extends BaseController {
|
|||||||
@RepeatSubmit()
|
@RepeatSubmit()
|
||||||
@PostMapping()
|
@PostMapping()
|
||||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysClientBo bo) {
|
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysClientBo bo) {
|
||||||
|
if (!sysClientService.checkClickKeyUnique(bo)) {
|
||||||
|
return R.fail("新增客户端'" + bo.getClientKey() + "'失败,客户端key已存在");
|
||||||
|
}
|
||||||
return toAjax(sysClientService.insertByBo(bo));
|
return toAjax(sysClientService.insertByBo(bo));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +91,9 @@ public class SysClientController extends BaseController {
|
|||||||
@RepeatSubmit()
|
@RepeatSubmit()
|
||||||
@PutMapping()
|
@PutMapping()
|
||||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysClientBo bo) {
|
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysClientBo bo) {
|
||||||
|
if (!sysClientService.checkClickKeyUnique(bo)) {
|
||||||
|
return R.fail("修改客户端'" + bo.getClientKey() + "'失败,客户端key已存在");
|
||||||
|
}
|
||||||
return toAjax(sysClientService.updateByBo(bo));
|
return toAjax(sysClientService.updateByBo(bo));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -179,6 +179,12 @@ public class SysMenuController extends BaseController {
|
|||||||
return toAjax(menuService.deleteMenuById(menuId));
|
return toAjax(menuService.deleteMenuById(menuId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色菜单列表树信息
|
||||||
|
*
|
||||||
|
* @param checkedKeys 选中菜单列表
|
||||||
|
* @param menus 菜单下拉树结构列表
|
||||||
|
*/
|
||||||
public record MenuTreeSelectVo(List<Long> checkedKeys, List<Tree<Long>> menus) {
|
public record MenuTreeSelectVo(List<Long> checkedKeys, List<Tree<Long>> menus) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.dromara.system.controller.system;
|
package org.dromara.system.controller.system;
|
||||||
|
|
||||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
|
import cn.hutool.core.lang.tree.Tree;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -13,8 +14,10 @@ import org.dromara.common.log.enums.BusinessType;
|
|||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
import org.dromara.common.web.core.BaseController;
|
import org.dromara.common.web.core.BaseController;
|
||||||
|
import org.dromara.system.domain.bo.SysDeptBo;
|
||||||
import org.dromara.system.domain.bo.SysPostBo;
|
import org.dromara.system.domain.bo.SysPostBo;
|
||||||
import org.dromara.system.domain.vo.SysPostVo;
|
import org.dromara.system.domain.vo.SysPostVo;
|
||||||
|
import org.dromara.system.service.ISysDeptService;
|
||||||
import org.dromara.system.service.ISysPostService;
|
import org.dromara.system.service.ISysPostService;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
@@ -35,6 +38,7 @@ import java.util.List;
|
|||||||
public class SysPostController extends BaseController {
|
public class SysPostController extends BaseController {
|
||||||
|
|
||||||
private final ISysPostService postService;
|
private final ISysPostService postService;
|
||||||
|
private final ISysDeptService deptService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取岗位列表
|
* 获取岗位列表
|
||||||
@@ -134,4 +138,14 @@ public class SysPostController extends BaseController {
|
|||||||
return R.ok(list);
|
return R.ok(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取部门树列表
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("system:post:list")
|
||||||
|
@GetMapping("/deptTree")
|
||||||
|
public R<List<Tree<Long>>> deptTree(SysDeptBo dept) {
|
||||||
|
return R.ok(deptService.selectDeptTreeList(dept));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,8 +129,20 @@ public class SysProfileController extends BaseController {
|
|||||||
return R.fail("上传图片异常,请联系管理员");
|
return R.fail("上传图片异常,请联系管理员");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户头像信息
|
||||||
|
*
|
||||||
|
* @param imgUrl 头像地址
|
||||||
|
*/
|
||||||
public record AvatarVo(String imgUrl) {}
|
public record AvatarVo(String imgUrl) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户个人信息
|
||||||
|
*
|
||||||
|
* @param user 用户信息
|
||||||
|
* @param roleGroup 用户所属角色组
|
||||||
|
* @param postGroup 用户所属岗位组
|
||||||
|
*/
|
||||||
public record ProfileVo(ProfileUserVo user, String roleGroup, String postGroup) {}
|
public record ProfileVo(ProfileUserVo user, String roleGroup, String postGroup) {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,6 +235,12 @@ public class SysRoleController extends BaseController {
|
|||||||
return R.ok(selectVo);
|
return R.ok(selectVo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色部门列表树信息
|
||||||
|
*
|
||||||
|
* @param checkedKeys 选中部门列表
|
||||||
|
* @param depts 下拉树结构列表
|
||||||
|
*/
|
||||||
public record DeptTreeSelectVo(List<Long> checkedKeys, List<Tree<Long>> depts) {}
|
public record DeptTreeSelectVo(List<Long> checkedKeys, List<Tree<Long>> depts) {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,4 +193,19 @@ public class SysTenantController extends BaseController {
|
|||||||
return R.ok("同步租户字典成功");
|
return R.ok("同步租户字典成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步租户参数配置
|
||||||
|
*/
|
||||||
|
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
|
||||||
|
@Log(title = "租户管理", businessType = BusinessType.INSERT)
|
||||||
|
@Lock4j
|
||||||
|
@GetMapping("/syncTenantConfig")
|
||||||
|
public R<Void> syncTenantConfig() {
|
||||||
|
if (!TenantHelper.isEnable()) {
|
||||||
|
return R.fail("当前未开启租户模式");
|
||||||
|
}
|
||||||
|
tenantService.syncTenantConfig();
|
||||||
|
return R.ok("同步租户参数配置成功");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import jakarta.validation.constraints.Size;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.dromara.common.core.constant.RegexConstants;
|
import org.dromara.common.core.constant.RegexConstants;
|
||||||
|
import org.dromara.common.json.validate.JsonPattern;
|
||||||
|
import org.dromara.common.json.validate.JsonType;
|
||||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||||
import org.dromara.system.domain.SysMenu;
|
import org.dromara.system.domain.SysMenu;
|
||||||
|
|
||||||
@@ -61,6 +63,7 @@ public class SysMenuBo extends BaseEntity {
|
|||||||
/**
|
/**
|
||||||
* 路由参数
|
* 路由参数
|
||||||
*/
|
*/
|
||||||
|
@JsonPattern(type = JsonType.OBJECT, message = "路由参数必须符合JSON格式")
|
||||||
private String queryParam;
|
private String queryParam;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ public class SysTenantPackageBo extends BaseEntity {
|
|||||||
/**
|
/**
|
||||||
* 关联菜单id
|
* 关联菜单id
|
||||||
*/
|
*/
|
||||||
@AutoMapping(target = "menuIds", expression = "java(org.dromara.common.core.utils.StringUtils.join(source.getMenuIds(), \",\"))")
|
@AutoMapping(target = "menuIds", expression = "java(org.dromara.common.core.utils.StringUtils.joinComma(source.getMenuIds()))")
|
||||||
private Long[] menuIds;
|
private Long[] menuIds;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ public class MetaVo {
|
|||||||
*/
|
*/
|
||||||
private String link;
|
private String link;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 激活菜单
|
||||||
|
*/
|
||||||
|
private String activeMenu;
|
||||||
|
|
||||||
public MetaVo(String title, String icon) {
|
public MetaVo(String title, String icon) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.icon = icon;
|
this.icon = icon;
|
||||||
@@ -58,4 +63,16 @@ public class MetaVo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MetaVo(String title, String icon, Boolean noCache, String link, String activeMenu) {
|
||||||
|
this.title = title;
|
||||||
|
this.icon = icon;
|
||||||
|
this.noCache = noCache;
|
||||||
|
if (StringUtils.ishttp(link)) {
|
||||||
|
this.link = link;
|
||||||
|
}
|
||||||
|
if (StringUtils.startWithAnyIgnoreCase(activeMenu, "/")) {
|
||||||
|
this.activeMenu = activeMenu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ public interface SysDeptMapper extends BaseMapperPlus<SysDept, SysDeptVo> {
|
|||||||
*/
|
*/
|
||||||
default String buildDeptByRoleSql(Long roleId) {
|
default String buildDeptByRoleSql(Long roleId) {
|
||||||
return """
|
return """
|
||||||
select dept_id from sys_role_dept where role_id = %d
|
select srd.dept_id from sys_role_dept srd
|
||||||
|
left join sys_role sr on sr.role_id = srd.role_id
|
||||||
|
where srd.role_id = %d and sr.status = '0'
|
||||||
""".formatted(roleId);
|
""".formatted(roleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +49,9 @@ public interface SysDeptMapper extends BaseMapperPlus<SysDept, SysDeptVo> {
|
|||||||
default String buildParentDeptByRoleSql(Long roleId) {
|
default String buildParentDeptByRoleSql(Long roleId) {
|
||||||
return """
|
return """
|
||||||
select parent_id from sys_dept where dept_id in (
|
select parent_id from sys_dept where dept_id in (
|
||||||
select dept_id from sys_role_dept where role_id = %d
|
select srd.dept_id from sys_role_dept srd
|
||||||
|
left join sys_role sr on sr.role_id = srd.role_id
|
||||||
|
where srd.role_id = %d and sr.status = '0'
|
||||||
)
|
)
|
||||||
""".formatted(roleId);
|
""".formatted(roleId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ public interface SysMenuMapper extends BaseMapperPlus<SysMenu, SysMenuVo> {
|
|||||||
default String buildMenuByUserSql(Long userId) {
|
default String buildMenuByUserSql(Long userId) {
|
||||||
return """
|
return """
|
||||||
select menu_id from sys_role_menu where role_id in (
|
select menu_id from sys_role_menu where role_id in (
|
||||||
select role_id from sys_user_role where user_id = %d
|
select sur.role_id from sys_user_role sur
|
||||||
|
left join sys_role sr on sr.role_id = sur.role_id
|
||||||
|
where sur.user_id = %d and sr.status = '0'
|
||||||
)
|
)
|
||||||
""".formatted(userId);
|
""".formatted(userId);
|
||||||
}
|
}
|
||||||
@@ -50,7 +52,9 @@ public interface SysMenuMapper extends BaseMapperPlus<SysMenu, SysMenuVo> {
|
|||||||
*/
|
*/
|
||||||
default String buildMenuByRoleSql(Long roleId) {
|
default String buildMenuByRoleSql(Long roleId) {
|
||||||
return """
|
return """
|
||||||
select menu_id from sys_role_menu where role_id = %d
|
select srm.menu_id from sys_role_menu srm
|
||||||
|
left join sys_role sr on sr.role_id = srm.role_id
|
||||||
|
where srm.role_id = %d and sr.status = '0'
|
||||||
""".formatted(roleId);
|
""".formatted(roleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +72,9 @@ public interface SysMenuMapper extends BaseMapperPlus<SysMenu, SysMenuVo> {
|
|||||||
default String buildParentMenuByRoleSql(Long roleId) {
|
default String buildParentMenuByRoleSql(Long roleId) {
|
||||||
return """
|
return """
|
||||||
select parent_id from sys_menu where menu_id in (
|
select parent_id from sys_menu where menu_id in (
|
||||||
select menu_id from sys_role_menu where role_id = %d
|
select srm.menu_id from sys_role_menu srm
|
||||||
|
left join sys_role sr on sr.role_id = srm.role_id
|
||||||
|
where srm.role_id = %d and sr.status = '0'
|
||||||
)
|
)
|
||||||
""".formatted(roleId);
|
""".formatted(roleId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
package org.dromara.system.service;
|
package org.dromara.system.service;
|
||||||
|
|
||||||
import org.dromara.system.domain.SysClient;
|
|
||||||
import org.dromara.system.domain.vo.SysClientVo;
|
|
||||||
import org.dromara.system.domain.bo.SysClientBo;
|
|
||||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
|
||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
|
import org.dromara.system.domain.bo.SysClientBo;
|
||||||
|
import org.dromara.system.domain.vo.SysClientVo;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -57,4 +56,11 @@ public interface ISysClientService {
|
|||||||
*/
|
*/
|
||||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验客户端key是否唯一
|
||||||
|
*
|
||||||
|
* @param client 客户端信息
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
boolean checkClickKeyUnique(SysClientBo client);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package org.dromara.system.service;
|
package org.dromara.system.service;
|
||||||
|
|
||||||
import org.dromara.system.domain.vo.SysTenantVo;
|
|
||||||
import org.dromara.system.domain.bo.SysTenantBo;
|
|
||||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
|
||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
|
import org.dromara.system.domain.bo.SysTenantBo;
|
||||||
|
import org.dromara.system.domain.vo.SysTenantVo;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -84,4 +84,9 @@ public interface ISysTenantService {
|
|||||||
* 同步租户字典
|
* 同步租户字典
|
||||||
*/
|
*/
|
||||||
void syncTenantDict();
|
void syncTenantDict();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步租户参数配置
|
||||||
|
*/
|
||||||
|
void syncTenantConfig();
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user