Compare commits

...

31 Commits

Author SHA1 Message Date
xiaozhi bb68d9e227 Merge remote-tracking branch 'origin/future/6.X' into future/6.X 2026-06-13 18:02:17 +08:00
lau b177620e66 fix 客户端导出报错修复,暂不支持List字段导出 2026-06-13 18:02:12 +08:00
疯狂的狮子Li bd57356912 update springboot 4.1.0
update springdoc 3.0.3
update fesod 2.0.2
update hutool 5.8.46
update redisson 4.5.0
update mica-mqtt 2.6.5
2026-06-12 09:27:35 +08:00
疯狂的狮子Li d50631575a update springboot 4.1.0
update springdoc 3.0.3
update fesod 2.0.2
update hutool 5.8.46
update redisson 4.5.0
update mica-mqtt 2.6.5
2026-06-11 14:48:47 +08:00
AprilWind 0cab98bc8a !859 update 优化日期处理方法,简化时间格式化逻辑
* update 优化日期处理方法,简化时间格式化逻辑
2026-06-11 06:26:50 +00:00
lau 415a72b7c6 add 未知异常和系统异常增加随机编号方便查找 2026-06-10 17:19:51 +08:00
lau 4498b9c9d5 add 新增上传附件保存扩展信息 2026-06-10 17:08:49 +08:00
AprilWind 0b37392a8f !856 update 优化代码,简化类型检查
* update 优化代码,简化类型检查
2026-06-10 06:38:46 +00:00
AprilWind fc43e368b7 update 升级bouncycastle版本至1.84 2026-06-10 11:34:44 +08:00
AprilWind e38e99b7f8 !854 update 优化使用getFirst()替代get(0)以提高代码可读性
* update 优化使用getFirst()替代get(0)以提高代码可读性
2026-06-10 02:18:35 +00:00
疯狂的狮子Li 83eb74c56d fix 修复 代码生成默认菜单上级id错误 2026-06-10 09:37:26 +08:00
疯狂的狮子Li a87dcea74b update 优化 增加snailai-server相关docker配置 2026-06-09 11:07:07 +08:00
疯狂的狮子Li 060da45f84 update 优化 登录异常提示 2026-06-09 10:30:05 +08:00
疯狂的狮子Li 1cb5d841d9 update 恢复前端异常返回内容 2026-06-09 10:22:07 +08:00
AprilWind 00a1c1037c update 增加对客户端ID与Token不匹配的错误信息处理 2026-06-09 09:51:06 +08:00
AprilWind 9e192f5197 update 优化NotLoginException处理,提供更详细的错误信息 2026-06-09 09:30:56 +08:00
疯狂的狮子Li 4875594bcb update 优化 代码生成前端模板 2026-06-08 18:48:14 +08:00
疯狂的狮子Li 90f8142d0b update 优化代码小优化项
- 抄送历史任务为空时改为明确的 ServiceException;当前任务办理人查询改为 ID 去重后批量远程查
- 流程图提示里用户 ID 去重,部门名称从循环内逐条Dubbo 调用改为一次批量查询
2026-06-08 17:26:32 +08:00
疯狂的狮子Li 3a6d62d695 fix 修复 一些发现的小bug
- 短信验证码限流 key 改为 #phoneNumber,验证码改为发送成功后再写 Redis,失败消息避免 NPE
- 邮箱验证码改为邮件发送成功后再写 Redis
- ValidatorUtils 增加 NPE 校验
- 部门导入路径不存在时明确报错,不再静默转成 null
- 重复提交切面完整扫描数组、集合、Map 中的过滤对象
- Excel 级联下拉补齐一级选项的 Excel 名称规则校验和重复校验
2026-06-08 16:37:18 +08:00
疯狂的狮子Li 14f96acd60 update 排除本地配置文件 2026-06-08 12:01:57 +08:00
疯狂的狮子Li 0ca43166d2 update snail-ai 升级 0.0.4 2026-06-08 09:27:41 +08:00
疯狂的狮子Li c64fb7a16a update 重构 头像上传改为先上传oss后更新用户数据 2026-06-05 16:41:12 +08:00
疯狂的狮子Li 9e21304dbf update 优化 防止工作流保存被xss过滤 2026-06-05 15:01:07 +08:00
疯狂的狮子Li 9888150523 update 更新系统标题和描述 2026-06-04 17:19:50 +08:00
疯狂的狮子Li 02cf3546fb update mysql 8.0.42 => 8.4.9
update nginx 1.23.4 => 1.31.1
update redis 7.2.8 => 8.6.3
update minio RELEASE.2026-04-17T00-00-00Z
2026-06-04 17:13:20 +08:00
疯狂的狮子Li 2d4d7b6fb1 update 格式化全部代码 2026-06-04 16:08:04 +08:00
AprilWind e08160afb6 update 优化冗余分隔符 2026-06-04 15:08:13 +08:00
AprilWind f52bb99419 add 新增spring ai docker run配置 2026-06-04 14:28:35 +08:00
AprilWind d20e03ea61 update 优化spring ai名称 2026-06-04 14:15:44 +08:00
疯狂的狮子Li 8c36b41072 update 回滚错误修改 2026-06-04 13:04:01 +08:00
AprilWind 94d7cd3489 update 优化媒体工具类,添加私有构造函数以防止实例化 2026-06-04 12:19:41 +08:00
238 changed files with 1112 additions and 1461 deletions
+3
View File
@@ -50,3 +50,6 @@ nbdist/
!*/build/*.xml !*/build/*.xml
.flattened-pom.xml .flattened-pom.xml
application-local.yml
application-ga.yml
+13
View File
@@ -0,0 +1,13 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ruoyi-snailai-server" type="docker-deploy" factoryName="dockerfile"
server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-snailai-server:5.5.3"/>
<option name="buildOnly" value="true"/>
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailai-server/Dockerfile"/>
</settings>
</deployment>
<method v="2"/>
</configuration>
</component>
+2 -3
View File
@@ -22,8 +22,7 @@
> 系统演示: [传送门](https://plus-doc.dromara.org/#/common/demo_system) > 系统演示: [传送门](https://plus-doc.dromara.org/#/common/demo_system)
> > 官方前端项目地址: [gitee](https://gitee.com/JavaLionLi/plus-ui) - [github](https://github.com/CrazyLionCat/plus-ui) - [gitcode](https://gitcode.com/dromara/plus-ui)<br>
官方前端项目地址: [gitee](https://gitee.com/JavaLionLi/plus-ui) - [github](https://github.com/CrazyLionCat/plus-ui) - [gitcode](https://gitcode.com/dromara/plus-ui)<br>
> 成员前端项目地址: 基于vben5 [ruoyi-plus-vben5](https://github.com/imdap/ruoyi-plus-vben5)<br> > 成员前端项目地址: 基于vben5 [ruoyi-plus-vben5](https://github.com/imdap/ruoyi-plus-vben5)<br>
> 成员前端项目地址: 基于soybean [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)<br> > 成员前端项目地址: 基于soybean [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)<br>
> 成员项目地址: 删除多租户与工作流 [RuoYi-Vue-Plus-Single](https://gitee.com/ColorDreams/RuoYi-Vue-Plus-Single)<br> > 成员项目地址: 删除多租户与工作流 [RuoYi-Vue-Plus-Single](https://gitee.com/ColorDreams/RuoYi-Vue-Plus-Single)<br>
@@ -123,7 +122,7 @@ Topiam IAM/IDaaS身份管理平台 - https://www.topiam.cn/ <br>
使用框架前请仔细阅读文档重点注意事项 使用框架前请仔细阅读文档重点注意事项
<br> <br>
> [初始化项目 必看](https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/init) >[初始化项目 必看](https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/init)
>> [https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/init](https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/init) >> [https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/init](https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/init)
> >
>[专栏与视频 入门必看](https://plus-doc.dromara.org/#/common/column) >[专栏与视频 入门必看](https://plus-doc.dromara.org/#/common/column)
+10 -10
View File
@@ -10,34 +10,34 @@
<name>RuoYi-Vue-Plus</name> <name>RuoYi-Vue-Plus</name>
<url>https://gitee.com/dromara/RuoYi-Vue-Plus</url> <url>https://gitee.com/dromara/RuoYi-Vue-Plus</url>
<description>Dromara RuoYi-Vue-Plus管理系统</description> <description>Dromara RuoYi-Vue-Plus后台管理系统</description>
<properties> <properties>
<revision>5.5.3</revision> <revision>5.5.3</revision>
<spring-boot.version>4.0.6</spring-boot.version> <spring-boot.version>4.1.0</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>21</java.version> <java.version>21</java.version>
<mybatis.version>3.5.19</mybatis.version> <mybatis.version>3.5.19</mybatis.version>
<springdoc.version>3.0.2</springdoc.version> <springdoc.version>3.0.3</springdoc.version>
<therapi-javadoc.version>0.15.0</therapi-javadoc.version> <therapi-javadoc.version>0.15.0</therapi-javadoc.version>
<fesod.version>2.0.1-incubating</fesod.version> <fesod.version>2.0.2-incubating</fesod.version>
<velocity.version>2.4.1</velocity.version> <velocity.version>2.4.1</velocity.version>
<satoken.version>1.45.0</satoken.version> <satoken.version>1.45.0</satoken.version>
<mybatis-plus.version>3.5.16</mybatis-plus.version> <mybatis-plus.version>3.5.16</mybatis-plus.version>
<mybatis-plus-join.version>1.5.6</mybatis-plus-join.version> <mybatis-plus-join.version>1.5.6</mybatis-plus-join.version>
<p6spy.version>3.9.1</p6spy.version> <p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.8.44</hutool.version> <hutool.version>5.8.46</hutool.version>
<spring-boot-admin.version>4.0.4</spring-boot-admin.version> <spring-boot-admin.version>4.0.4</spring-boot-admin.version>
<redisson.version>4.4.0</redisson.version> <redisson.version>4.5.0</redisson.version>
<lock4j.version>2.2.7</lock4j.version> <lock4j.version>2.2.7</lock4j.version>
<dynamic-ds.version>4.5.0</dynamic-ds.version> <dynamic-ds.version>4.5.0</dynamic-ds.version>
<snailjob.version>2.0.0</snailjob.version> <snailjob.version>2.0.0</snailjob.version>
<snailai.version>0.0.3</snailai.version> <snailai.version>0.0.4</snailai.version>
<mapstruct-plus.version>1.5.0</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.42</lombok.version> <lombok.version>1.18.42</lombok.version>
<bouncycastle.version>1.83</bouncycastle.version> <bouncycastle.version>1.84</bouncycastle.version>
<justauth.version>1.16.7</justauth.version> <justauth.version>1.16.7</justauth.version>
<!-- 离线IP地址定位库 --> <!-- 离线IP地址定位库 -->
<ip2region.version>3.3.6</ip2region.version> <ip2region.version>3.3.6</ip2region.version>
@@ -52,7 +52,7 @@
<!-- 工作流配置 --> <!-- 工作流配置 -->
<warm-flow.version>1.8.7</warm-flow.version> <warm-flow.version>1.8.7</warm-flow.version>
<!-- mqtt客户端 --> <!-- mqtt客户端 -->
<mica-mqtt.version>2.5.12</mica-mqtt.version> <mica-mqtt.version>2.6.5</mica-mqtt.version>
<easy-es.version>3.0.2</easy-es.version> <easy-es.version>3.0.2</easy-es.version>
<elasticsearch-client.version>7.17.28</elasticsearch-client.version> <elasticsearch-client.version>7.17.28</elasticsearch-client.version>
<!-- Spring AI 2.0 预览版,正式版发布后仅需调整此版本号 --> <!-- Spring AI 2.0 预览版,正式版发布后仅需调整此版本号 -->
@@ -66,7 +66,7 @@
<!-- 统一版本号管理:Maven3.X需要,Maven4.0之后已原生支持 --> <!-- 统一版本号管理:Maven3.X需要,Maven4.0之后已原生支持 -->
<flatten-maven-plugin.version>1.7.3</flatten-maven-plugin.version> <flatten-maven-plugin.version>1.7.3</flatten-maven-plugin.version>
<!-- 打包默认跳过测试 --> <!-- 打包默认跳过测试 -->
<skipTests>true</skipTests> <maven.test.skip>true</maven.test.skip>
</properties> </properties>
<profiles> <profiles>
+6 -4
View File
@@ -11,18 +11,20 @@ RUN mkdir -p /ruoyi/server/logs \
WORKDIR /ruoyi/server WORKDIR /ruoyi/server
ENV SERVER_PORT=8080 SNAIL_PORT=28080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS="" ENV SERVER_PORT=8080 SNAIL_JOB_PORT=28080 SNAIL_AI_PORT=38080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
EXPOSE ${SERVER_PORT} EXPOSE ${SERVER_PORT}
# 暴露 snail job 客户端端口 用于定时任务调度中心通信 # 暴露 snail 客户端端口 用于定时任务调度中心通信
EXPOSE ${SNAIL_PORT} EXPOSE ${SNAIL_JOB_PORT}
EXPOSE ${SNAIL_AI_PORT}
ADD ./target/ruoyi-admin.jar ./app.jar ADD ./target/ruoyi-admin.jar ./app.jar
SHELL ["/bin/bash", "-c"] SHELL ["/bin/bash", "-c"]
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \ ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
-Dsnail-job.port=${SNAIL_PORT} \ -Dsnail-job.port=${SNAIL_JOB_PORT} \
-Dsnail-ai.port=${SNAIL_AI_PORT} \
# 应用名称 如果想区分集群节点监控 改成不同的名称即可 # 应用名称 如果想区分集群节点监控 改成不同的名称即可
#-Dskywalking.agent.service_name=ruoyi-server \ #-Dskywalking.agent.service_name=ruoyi-server \
#-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \ #-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \
-24
View File
@@ -17,30 +17,6 @@
<dependencies> <dependencies>
<!-- ruoyi-admin 同时引入 SnailJob Client 与 SnailAI AgentSnailAI Agent 0.0.3 需要 protobuf 4.x -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>4.35.0</version>
</dependency>
<!-- 局部提升 ruoyi-admin 的 gRPC 运行时,避免被 SnailJob Client 的 1.76.0 抢先选中 -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.81.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.81.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.81.0</version>
</dependency>
<!-- Mysql驱动包 --> <!-- Mysql驱动包 -->
<dependency> <dependency>
<groupId>com.mysql</groupId> <groupId>com.mysql</groupId>
@@ -58,7 +58,7 @@ public class CaptchaController {
* @param phoneNumber 用户手机号 * @param phoneNumber 用户手机号
* @return 操作结果 * @return 操作结果
*/ */
@RateLimiter(key = "#phonenumber", time = 60, count = 1) @RateLimiter(key = "#phoneNumber", time = 60, count = 1)
@GetMapping("/resource/sms/code") @GetMapping("/resource/sms/code")
public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phoneNumber) { public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phoneNumber) {
if (!RegexValidator.isMobile(phoneNumber)) { if (!RegexValidator.isMobile(phoneNumber)) {
@@ -66,7 +66,6 @@ public class CaptchaController {
} }
String key = GlobalConstants.CAPTCHA_CODE_KEY + phoneNumber; String key = GlobalConstants.CAPTCHA_CODE_KEY + phoneNumber;
String code = RandomUtil.randomNumbers(4); String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
// 验证码模板id 自行处理 (查数据库或写死均可) // 验证码模板id 自行处理 (查数据库或写死均可)
String templateId = ""; String templateId = "";
LinkedHashMap<String, String> map = new LinkedHashMap<>(1); LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
@@ -75,8 +74,10 @@ public class CaptchaController {
SmsResponse smsResponse = smsBlend.sendMessage(phoneNumber, templateId, map); SmsResponse smsResponse = smsBlend.sendMessage(phoneNumber, templateId, map);
if (!smsResponse.isSuccess()) { if (!smsResponse.isSuccess()) {
log.error("验证码短信发送异常 => {}", smsResponse); log.error("验证码短信发送异常 => {}", smsResponse);
return R.fail(smsResponse.getData().toString()); Object data = smsResponse.getData();
return R.fail(data == null ? "验证码短信发送失败" : data.toString());
} }
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
return R.ok(); return R.ok();
} }
@@ -107,13 +108,13 @@ public class CaptchaController {
public void emailCodeImpl(String email) { public void emailCodeImpl(String email) {
String key = GlobalConstants.CAPTCHA_CODE_KEY + email; String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
String code = RandomUtil.randomNumbers(4); String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
try { try {
MailBuilder.of() MailBuilder.of()
.to(email) .to(email)
.subject("登录验证码") .subject("登录验证码")
.text("您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。") .text("您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。")
.send(); .send();
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
} catch (Exception e) { } catch (Exception e) {
log.error("验证码短信发送异常 => {}", e.getMessage()); log.error("验证码短信发送异常 => {}", e.getMessage());
throw new ServiceException(e.getMessage()); throw new ServiceException(e.getMessage());
@@ -51,7 +51,7 @@ public interface IAuthStrategy {
/** /**
* 按客户端配置构建统一登录参数,并预留自定义扩展入口。 * 按客户端配置构建统一登录参数,并预留自定义扩展入口。
* *
* @param client 客户端配置 * @param client 客户端配置
* @param customizer 自定义扩展逻辑 * @param customizer 自定义扩展逻辑
* @return Sa-Token 登录参数 * @return Sa-Token 登录参数
*/ */
@@ -97,7 +97,7 @@ public class SysLoginService {
sysSocialService.insertByBo(bo); sysSocialService.insertByBo(bo);
} else { } else {
// 更新用户信息 // 更新用户信息
bo.setId(list.get(0).getId()); bo.setId(list.getFirst().getId());
sysSocialService.updateByBo(bo); sysSocialService.updateByBo(bo);
// 如果要绑定的平台账号已经被绑定过了 是否抛异常自行决断 // 如果要绑定的平台账号已经被绑定过了 是否抛异常自行决断
// throw new ServiceException("此平台账号已经被绑定!"); // throw new ServiceException("此平台账号已经被绑定!");
@@ -48,8 +48,8 @@ public class SocialAuthStrategy implements IAuthStrategy {
/** /**
* 执行第三方授权登录,并校验授权账号与系统账号的绑定关系。 * 执行第三方授权登录,并校验授权账号与系统账号的绑定关系。
* *
* @param body 登录信息 * @param body 登录信息
* @param client 客户端信息 * @param client 客户端信息
* @return 登录结果 * @return 登录结果
*/ */
@Override @Override
@@ -57,8 +57,8 @@ public class SocialAuthStrategy implements IAuthStrategy {
SocialLoginBody loginBody = JsonUtils.parseObject(body, SocialLoginBody.class); SocialLoginBody loginBody = JsonUtils.parseObject(body, SocialLoginBody.class);
ValidatorUtils.validate(loginBody); ValidatorUtils.validate(loginBody);
AuthResponse<AuthUser> response = SocialUtils.loginAuth( AuthResponse<AuthUser> response = SocialUtils.loginAuth(
loginBody.getSource(), loginBody.getSocialCode(), loginBody.getSource(), loginBody.getSocialCode(),
loginBody.getSocialState(), socialProperties); loginBody.getSocialState(), socialProperties);
if (!response.ok()) { if (!response.ok()) {
throw new ServiceException(response.getMsg()); throw new ServiceException(response.getMsg());
} }
@@ -68,7 +68,7 @@ public class SocialAuthStrategy implements IAuthStrategy {
if (CollUtil.isEmpty(list)) { if (CollUtil.isEmpty(list)) {
throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!"); throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!");
} }
SysUserVo user = loadUser(list.get(0).getUserId()); SysUserVo user = loadUser(list.getFirst().getUserId());
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了 // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
LoginUser loginUser = loginService.buildLoginUser(user); LoginUser loginUser = loginService.buildLoginUser(user);
loginUser.setClientKey(client.getClientKey()); loginUser.setClientKey(client.getClientKey());
@@ -137,7 +137,7 @@ spring.data:
# 数据库索引 # 数据库索引
database: 0 database: 0
# redis 密码必须配置 # redis 密码必须配置
password: ruoyi123 # password: ruoyi123
# 连接超时时间 # 连接超时时间
timeout: 10s timeout: 10s
# 是否开启ssl # 是否开启ssl
@@ -161,7 +161,7 @@ springdoc:
enabled: true enabled: true
info: info:
# 标题 # 标题
title: '标题:RuoYi-Vue-Plus管理系统_接口文档' title: '标题:RuoYi-Vue-Plus后台管理系统_接口文档'
# 描述 # 描述
description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...' description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
# 版本 # 版本
@@ -191,6 +191,7 @@ xss:
# 排除链接 # 排除链接
excludeUrls: excludeUrls:
- /system/notice - /system/notice
- /warm-flow/save-json
--- # 分布式锁 lock4j 全局配置 --- # 分布式锁 lock4j 全局配置
lock4j: lock4j:
@@ -262,8 +263,8 @@ mqtt.client:
timeout: 5 timeout: 5
# 重连时间,默认 5000 毫秒 # 重连时间,默认 5000 毫秒
re-interval: 5000 re-interval: 5000
# mqtt 协议版本,可选 MQTT_3_1、mqtt_3_1_1、mqtt_5,默认:mqtt_3_1_1 # mqtt 协议版本,可选 MQTT_3_1、mqtt_3_1_1、mqtt_5,默认:mqtt_5
version: mqtt_3_1_1 version: mqtt_5
# 接收数据的 buffer size,默认:8k # 接收数据的 buffer size,默认:8k
read-buffer-size: 8KB read-buffer-size: 8KB
# 消息解析最大 bytes 长度,默认:10M # 消息解析最大 bytes 长度,默认:10M
@@ -57,10 +57,10 @@ public class PushPayloadDTO implements Serializable {
/** /**
* 构建推送消息体,缺省消息类型与来源时使用系统默认值。 * 构建推送消息体,缺省消息类型与来源时使用系统默认值。
* *
* @param type 消息类型 * @param type 消息类型
* @param source 消息来源 * @param source 消息来源
* @param message 文本消息 * @param message 文本消息
* @param data 扩展数据 * @param data 扩展数据
* @return 推送消息体 * @return 推送消息体
*/ */
public static PushPayloadDTO of(String type, String source, String message, Object data) { public static PushPayloadDTO of(String type, String source, String message, Object data) {
@@ -76,10 +76,10 @@ public class PushPayloadDTO implements Serializable {
/** /**
* 通过枚举值构建推送消息体。 * 通过枚举值构建推送消息体。
* *
* @param type 消息类型枚举 * @param type 消息类型枚举
* @param source 消息来源枚举 * @param source 消息来源枚举
* @param message 文本消息 * @param message 文本消息
* @param data 扩展数据 * @param data 扩展数据
* @return 推送消息体 * @return 推送消息体
*/ */
public static PushPayloadDTO of(PushTypeEnum type, PushSourceEnum source, String message, Object data) { public static PushPayloadDTO of(PushTypeEnum type, PushSourceEnum source, String message, Object data) {
@@ -94,11 +94,11 @@ public class PushPayloadDTO implements Serializable {
/** /**
* 构建带前端跳转路径的推送消息体。 * 构建带前端跳转路径的推送消息体。
* *
* @param type 消息类型枚举 * @param type 消息类型枚举
* @param source 消息来源枚举 * @param source 消息来源枚举
* @param message 文本消息 * @param message 文本消息
* @param data 扩展数据 * @param data 扩展数据
* @param path 前端跳转路径 * @param path 前端跳转路径
* @return 推送消息体 * @return 推送消息体
*/ */
public static PushPayloadDTO of(PushTypeEnum type, PushSourceEnum source, String message, Object data, String path) { public static PushPayloadDTO of(PushTypeEnum type, PushSourceEnum source, String message, Object data, String path) {
@@ -38,7 +38,7 @@ public class TaskAssigneeDTO implements Serializable {
* 创建任务受让人分页结果。 * 创建任务受让人分页结果。
* *
* @param total 总大小 * @param total 总大小
* @param list 受让人列表 * @param list 受让人列表
*/ */
public TaskAssigneeDTO(Long total, List<TaskHandler> list) { public TaskAssigneeDTO(Long total, List<TaskHandler> list) {
this.total = total; this.total = total;
@@ -98,9 +98,8 @@ public class ThreadPoolConfig {
* @param t 任务执行过程中抛出的异常 * @param t 任务执行过程中抛出的异常
*/ */
public static void printException(Runnable r, Throwable t) { public static void printException(Runnable r, Throwable t) {
if (t == null && r instanceof Future<?>) { if (t == null && r instanceof Future<?> future) {
try { try {
Future<?> future = (Future<?>) r;
if (future.isDone()) { if (future.isDone()) {
future.get(); future.get();
} }
@@ -85,7 +85,7 @@ public interface SystemConstants {
/** /**
* 排除敏感属性字段 * 排除敏感属性字段
*/ */
String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" }; String[] EXCLUDE_PROPERTIES = {"password", "oldPassword", "newPassword", "confirmPassword"};
} }
@@ -1,152 +0,0 @@
package org.dromara.common.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.common.core.utils.StringUtils;
/*
* 日期格式
* "yyyy"4位数的年份,例如:2023年表示为"2023"。
* "yy"2位数的年份,例如:2023年表示为"23"。
* "MM"2位数的月份,取值范围为01到12,例如:7月表示为"07"。
* "M":不带前导零的月份,取值范围为1到12,例如:7月表示为"7"。
* "dd"2位数的日期,取值范围为01到31,例如:22日表示为"22"。
* "d":不带前导零的日期,取值范围为1到31,例如:22日表示为"22"。
* "EEEE":星期的全名,例如:星期三表示为"Wednesday"。
* "E":星期的缩写,例如:星期三表示为"Wed"。
* "DDD" 或 "D":一年中的第几天,取值范围为001到366,例如:第200天表示为"200"。
* 时间格式
* "HH"24小时制的小时数,取值范围为00到23,例如:下午5点表示为"17"。
* "hh"12小时制的小时数,取值范围为01到12,例如:下午5点表示为"05"。
* "mm":分钟数,取值范围为00到59,例如:30分钟表示为"30"。
* "ss":秒数,取值范围为00到59,例如:45秒表示为"45"。
* "SSS":毫秒数,取值范围为000到999,例如:123毫秒表示为"123"。
*/
/**
* 日期格式与时间格式枚举
*/
@Getter
@AllArgsConstructor
public enum FormatsType {
/**
* 例如:2023年表示为"23"
*/
YY("yy"),
/**
* 例如:2023年表示为"2023"
*/
YYYY("yyyy"),
/**
* 例例如,2023年7月可以表示为 "2023-07"
*/
YYYY_MM("yyyy-MM"),
/**
* 例如,日期 "2023年7月22日" 可以表示为 "2023-07-22"
*/
YYYY_MM_DD("yyyy-MM-dd"),
/**
* 例如,当前时间如果是 "2023年7月22日下午3点30分",则可以表示为 "2023-07-22 15:30"
*/
YYYY_MM_DD_HH_MM("yyyy-MM-dd HH:mm"),
/**
* 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023-07-22 15:30:45"
*/
YYYY_MM_DD_HH_MM_SS("yyyy-MM-dd HH:mm:ss"),
/**
* 例如:下午3点30分45秒,表示为 "15:30:45"
*/
HH_MM_SS("HH:mm:ss"),
/**
* 例例如,2023年7月可以表示为 "2023/07"
*/
YYYY_MM_SLASH("yyyy/MM"),
/**
* 例如,日期 "2023年7月22日" 可以表示为 "2023/07/22"
*/
YYYY_MM_DD_SLASH("yyyy/MM/dd"),
/**
* 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023/07/22 15:30:45"
*/
YYYY_MM_DD_HH_MM_SLASH("yyyy/MM/dd HH:mm"),
/**
* 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023/07/22 15:30:45"
*/
YYYY_MM_DD_HH_MM_SS_SLASH("yyyy/MM/dd HH:mm:ss"),
/**
* 例例如,2023年7月可以表示为 "2023.07"
*/
YYYY_MM_DOT("yyyy.MM"),
/**
* 例如,日期 "2023年7月22日" 可以表示为 "2023.07.22"
*/
YYYY_MM_DD_DOT("yyyy.MM.dd"),
/**
* 例如,当前时间如果是 "2023年7月22日下午3点30分",则可以表示为 "2023.07.22 15:30"
*/
YYYY_MM_DD_HH_MM_DOT("yyyy.MM.dd HH:mm"),
/**
* 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023.07.22 15:30:45"
*/
YYYY_MM_DD_HH_MM_SS_DOT("yyyy.MM.dd HH:mm:ss"),
/**
* 例如,2023年7月可以表示为 "202307"
*/
YYYYMM("yyyyMM"),
/**
* 例如,2023年7月22日可以表示为 "20230722"
*/
YYYYMMDD("yyyyMMdd"),
/**
* 例如,2023年7月22日下午3点可以表示为 "2023072215"
*/
YYYYMMDDHH("yyyyMMddHH"),
/**
* 例如,2023年7月22日下午3点30分可以表示为 "202307221530"
*/
YYYYMMDDHHMM("yyyyMMddHHmm"),
/**
* 例如,2023年7月22日下午3点30分45秒可以表示为 "20230722153045"
*/
YYYYMMDDHHMMSS("yyyyMMddHHmmss");
/**
* 时间格式
*/
private final String timeFormat;
/**
* 根据字符串内容匹配时间格式类型。
*
* @param str 字符串内容
* @return 时间格式类型
*/
public static FormatsType getFormatsType(String str) {
for (FormatsType value : values()) {
if (StringUtils.contains(str, value.getTimeFormat())) {
return value;
}
}
throw new RuntimeException("'FormatsType' not found By " + str);
}
}
@@ -50,7 +50,7 @@ public final class ServiceException extends RuntimeException {
* 使用错误消息和错误码构造业务异常。 * 使用错误消息和错误码构造业务异常。
* *
* @param message 错误消息 * @param message 错误消息
* @param code 错误码 * @param code 错误码
*/ */
public ServiceException(String message, Integer code) { public ServiceException(String message, Integer code) {
this.message = message; this.message = message;
@@ -61,7 +61,7 @@ public final class ServiceException extends RuntimeException {
* 使用错误消息和根因构造业务异常。 * 使用错误消息和根因构造业务异常。
* *
* @param message 错误消息 * @param message 错误消息
* @param cause 根因 * @param cause 根因
*/ */
public ServiceException(String message, Throwable cause) { public ServiceException(String message, Throwable cause) {
super(cause); super(cause);
@@ -72,7 +72,7 @@ public final class ServiceException extends RuntimeException {
* 使用占位符参数格式化错误消息。 * 使用占位符参数格式化错误消息。
* *
* @param message 模板消息 * @param message 模板消息
* @param args 参数 * @param args 参数
*/ */
public ServiceException(String message, Object... args) { public ServiceException(String message, Object... args) {
this.message = StrFormatter.format(message, args); this.message = StrFormatter.format(message, args);
@@ -49,7 +49,7 @@ public final class SseException extends RuntimeException {
* 使用错误消息和错误码构造 SSE 异常。 * 使用错误消息和错误码构造 SSE 异常。
* *
* @param message 错误消息 * @param message 错误消息
* @param code 错误码 * @param code 错误码
*/ */
public SseException(String message, Integer code) { public SseException(String message, Integer code) {
this.message = message; this.message = message;
@@ -1,11 +1,11 @@
package org.dromara.common.core.exception.base; package org.dromara.common.core.exception.base;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.StringUtils;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.StringUtils;
import java.io.Serial; import java.io.Serial;
@@ -47,8 +47,8 @@ public class BaseException extends RuntimeException {
* 使用模块、错误码和参数构造异常。 * 使用模块、错误码和参数构造异常。
* *
* @param module 所属模块 * @param module 所属模块
* @param code 错误码 * @param code 错误码
* @param args 参数 * @param args 参数
*/ */
public BaseException(String module, String code, Object[] args) { public BaseException(String module, String code, Object[] args) {
this(module, code, args, null); this(module, code, args, null);
@@ -57,7 +57,7 @@ public class BaseException extends RuntimeException {
/** /**
* 使用模块和默认消息构造异常。 * 使用模块和默认消息构造异常。
* *
* @param module 所属模块 * @param module 所属模块
* @param defaultMessage 默认消息 * @param defaultMessage 默认消息
*/ */
public BaseException(String module, String defaultMessage) { public BaseException(String module, String defaultMessage) {
@@ -12,7 +12,7 @@ public interface PermissionService {
/** /**
* 获取角色数据权限 * 获取角色数据权限
* *
* @param userId 用户id * @param userId 用户id
* @return 角色权限信息 * @return 角色权限信息
*/ */
Set<String> getRolePermission(Long userId); Set<String> getRolePermission(Long userId);
@@ -20,7 +20,7 @@ public interface PermissionService {
/** /**
* 获取菜单数据权限 * 获取菜单数据权限
* *
* @param userId 用户id * @param userId 用户id
* @return 菜单权限信息 * @return 菜单权限信息
*/ */
Set<String> getMenuPermission(Long userId); Set<String> getMenuPermission(Long userId);
@@ -1,347 +1,96 @@
package org.dromara.common.core.utils; package org.dromara.common.core.utils;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.BetweenFormatter;
import cn.hutool.core.date.DateUnit; import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import org.apache.commons.lang3.time.DateFormatUtils; import cn.hutool.core.lang.Assert;
import org.dromara.common.core.enums.FormatsType; import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.exception.ServiceException;
import java.lang.management.ManagementFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Date; import java.util.Date;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* 时间工具类 * 时间工具类
* *
* @author ruoyi * @author AprilWind
*/ */
public class DateUtils extends org.apache.commons.lang3.time.DateUtils { @NoArgsConstructor(access = AccessLevel.PRIVATE)
/** public class DateUtils extends DateUtil {
* 日期解析格式集合。
*/
private static final String[] PARSE_PATTERNS = {
"yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
"yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
"yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
/** /**
* 工具类不允许实例化。 * 计算时间差并格式化(精确到秒)
*/
@Deprecated
private DateUtils() {
}
/**
* 获取当前日期和时间
* *
* @return 当前日期和时间的 Date 对象表示 * @param start 开始时间(支持 Date/LocalDateTime)
* @param end 结束时间(支持 Date/LocalDateTime)
* @return 时分秒格式时间差
*/ */
public static Date getNowDate() { public static String formatBetweenBySecond(Object start, Object end) {
return new Date(); return formatTimeBetween(start, end, BetweenFormatter.Level.SECOND);
} }
/** /**
* 获取当前日期的字符串表示,格式为YYYY-MM-DD * 通用时间差格式化
* *
* @return 当前日期的字符串表示 * @param startDate 开始时间
* @param endDate 结束时间
* @param level 精度级别
* @return 格式化时长
*/ */
public static String getDate() { public static String formatTimeBetween(Object startDate, Object endDate, BetweenFormatter.Level level) {
return dateTimeNow(FormatsType.YYYY_MM_DD); // 非空校验
Assert.notNull(startDate, "开始时间不能为空");
Assert.notNull(endDate, "结束时间不能为空");
Assert.notNull(level, "时间精度级别不能为空");
// 统一转为Date并校验格式合法性
Date start = Convert.toDate(startDate);
Date end = Convert.toDate(endDate);
Assert.notNull(start, "开始时间格式错误,无法解析为时间");
Assert.notNull(end, "结束时间格式错误,无法解析为时间");
long diffMillis = Math.abs(end.getTime() - start.getTime());
return formatBetween(diffMillis, level);
} }
/** /**
* 获取当前日期的字符串表示,格式为yyyyMMdd * 校验日期范围及最大时间跨度
*
* @return 当前日期的字符串表示
*/
public static String getCurrentDate() {
return DateFormatUtils.format(new Date(), FormatsType.YYYYMMDD.getTimeFormat());
}
/**
* 获取当前日期的路径格式字符串,格式为"yyyy/MM/dd"
*
* @return 当前日期的路径格式字符串
*/
public static String datePath() {
Date now = new Date();
return DateFormatUtils.format(now, FormatsType.YYYY_MM_DD_SLASH.getTimeFormat());
}
/**
* 获取当前时间的字符串表示,格式为YYYY-MM-DD HH:MM:SS
*
* @return 当前时间的字符串表示
*/
public static String getTime() {
return dateTimeNow(FormatsType.YYYY_MM_DD_HH_MM_SS);
}
/**
* 获取当前时间的字符串表示,格式为 "HH:MM:SS"
*
* @return 当前时间的字符串表示,格式为 "HH:MM:SS"
*/
public static String getTimeWithHourMinuteSecond() {
return dateTimeNow(FormatsType.HH_MM_SS);
}
/**
* 获取当前日期和时间的字符串表示,格式为YYYYMMDDHHMMSS
*
* @return 当前日期和时间的字符串表示
*/
public static String dateTimeNow() {
return dateTimeNow(FormatsType.YYYYMMDDHHMMSS);
}
/**
* 获取当前日期和时间的指定格式的字符串表示
*
* @param format 日期时间格式,例如"YYYY-MM-DD HH:MM:SS"
* @return 当前日期和时间的字符串表示
*/
public static String dateTimeNow(final FormatsType format) {
return parseDateToStr(format, new Date());
}
/**
* 将指定日期格式化为 YYYY-MM-DD 格式的字符串
*
* @param date 要格式化的日期对象
* @return 格式化后的日期字符串
*/
public static String formatDate(final Date date) {
return parseDateToStr(FormatsType.YYYY_MM_DD, date);
}
/**
* 将指定日期格式化为 YYYY-MM-DD HH:MM:SS 格式的字符串
*
* @param date 要格式化的日期对象
* @return 格式化后的日期时间字符串
*/
public static String formatDateTime(final Date date) {
return parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM_SS, date);
}
/**
* 将 LocalDateTime 格式化为 YYYY-MM-DD HH:MM:SS 格式的字符串
*
* @param dateTime 要格式化的 LocalDateTime 对象
* @return 格式化后的日期时间字符串
*/
public static String formatDateTime(final LocalDateTime dateTime) {
return dateTime.format(DateTimeFormatter.ofPattern(FormatsType.YYYY_MM_DD_HH_MM_SS.getTimeFormat()));
}
/**
* 将指定日期按照指定格式进行格式化
*
* @param format 要使用的日期时间格式,例如"YYYY-MM-DD HH:MM:SS"
* @param date 要格式化的日期对象
* @return 格式化后的日期时间字符串
*/
public static String parseDateToStr(final FormatsType format, final Date date) {
return new SimpleDateFormat(format.getTimeFormat()).format(date);
}
/**
* 将指定格式的日期时间字符串转换为 Date 对象
*
* @param format 要解析的日期时间格式,例如"YYYY-MM-DD HH:MM:SS"
* @param ts 要解析的日期时间字符串
* @return 解析后的 Date 对象
* @throws RuntimeException 如果解析过程中发生异常
*/
public static Date parseDateTime(final FormatsType format, final String ts) {
try {
return new SimpleDateFormat(format.getTimeFormat()).parse(ts);
} catch (ParseException e) {
throw new ServiceException("日期时间解析失败:" + ts, e);
}
}
/**
* 将对象转换为日期对象
*
* @param str 要转换的对象,通常是字符串
* @return 转换后的日期对象,如果转换失败或输入为null,则返回null
*/
public static Date parseDate(Object str) {
if (str == null) {
return null;
}
try {
return parseDate(str.toString(), PARSE_PATTERNS);
} catch (ParseException e) {
return null;
}
}
/**
* 获取服务器启动时间
*
* @return 服务器启动时间的 Date 对象表示
*/
public static Date getServerStartDate() {
long time = ManagementFactory.getRuntimeMXBean().getStartTime();
return new Date(time);
}
/**
* 计算两个时间之间的时间差,并以指定单位返回(绝对值)
*
* @param start 起始时间
* @param end 结束时间
* @param unit 所需返回的时间单位(DAYS、HOURS、MINUTES、SECONDS、MILLISECONDS、MICROSECONDS、NANOSECONDS
* @return 时间差的绝对值,以指定单位表示
*/
public static long difference(Date start, Date end, TimeUnit unit) {
// 计算时间差,单位为毫秒,取绝对值避免负数
long diffInMillis = Math.abs(end.getTime() - start.getTime());
// 根据目标单位转换时间差
return switch (unit) {
case DAYS -> diffInMillis / TimeUnit.DAYS.toMillis(1);
case HOURS -> diffInMillis / TimeUnit.HOURS.toMillis(1);
case MINUTES -> diffInMillis / TimeUnit.MINUTES.toMillis(1);
case SECONDS -> diffInMillis / TimeUnit.SECONDS.toMillis(1);
case MILLISECONDS -> diffInMillis;
case MICROSECONDS -> TimeUnit.MILLISECONDS.toMicros(diffInMillis);
case NANOSECONDS -> TimeUnit.MILLISECONDS.toNanos(diffInMillis);
};
}
/**
* 计算两个日期之间的时间差,并以天、小时和分钟的格式返回
*
* @param endDate 结束日期
* @param nowDate 当前日期
* @return 表示时间差的字符串,格式为"天 小时 分钟"
*/
public static String getDatePoor(Date endDate, Date nowDate) {
long diffInMillis = endDate.getTime() - nowDate.getTime();
long day = TimeUnit.MILLISECONDS.toDays(diffInMillis);
long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24;
long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60;
return String.format("%d天 %d小时 %d分钟", day, hour, min);
}
/**
* 计算两个时间点的差值(天、小时、分钟、秒),当值为0时不显示该单位
*
* @param endDate 结束时间
* @param nowDate 当前时间
* @return 时间差字符串,格式为 "x天 x小时 x分钟 x秒",若为 0 则不显示
*/
public static String getTimeDifference(Date endDate, Date nowDate) {
long diffInMillis = endDate.getTime() - nowDate.getTime();
long day = TimeUnit.MILLISECONDS.toDays(diffInMillis);
long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24;
long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60;
long sec = TimeUnit.MILLISECONDS.toSeconds(diffInMillis) % 60;
// 构建时间差字符串,条件是值不为0才显示
StringBuilder result = new StringBuilder();
if (day > 0) {
result.append(String.format("%d天 ", day));
}
if (hour > 0) {
result.append(String.format("%d小时 ", hour));
}
if (min > 0) {
result.append(String.format("%d分钟 ", min));
}
if (sec > 0) {
result.append(String.format("%d秒", sec));
}
return !result.isEmpty() ? result.toString().trim() : "0秒";
}
/**
* 计算两个 LocalDateTime 时间点的差值(天、小时、分钟、秒),当值为0时不显示该单位
*
* @param endDate 结束时间
* @param nowDate 当前时间
* @return 时间差字符串,格式为 "x天 x小时 x分钟 x秒",若为 0 则不显示
*/
public static String getTimeDifference(LocalDateTime endDate, LocalDateTime nowDate) {
long diffInMillis = java.time.Duration.between(nowDate, endDate).toMillis();
long day = TimeUnit.MILLISECONDS.toDays(diffInMillis);
long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24;
long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60;
long sec = TimeUnit.MILLISECONDS.toSeconds(diffInMillis) % 60;
StringBuilder result = new StringBuilder();
if (day > 0) {
result.append(String.format("%d天 ", day));
}
if (hour > 0) {
result.append(String.format("%d小时 ", hour));
}
if (min > 0) {
result.append(String.format("%d分钟 ", min));
}
if (sec > 0) {
result.append(String.format("%d秒", sec));
}
return !result.isEmpty() ? result.toString().trim() : "0秒";
}
/**
* 将 LocalDateTime 对象转换为 Date 对象
*
* @param temporalAccessor 要转换的 LocalDateTime 对象
* @return 转换后的 Date 对象
*/
public static Date toDate(LocalDateTime temporalAccessor) {
ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
return Date.from(zdt.toInstant());
}
/**
* 将 LocalDate 对象转换为 Date 对象
*
* @param temporalAccessor 要转换的 LocalDate 对象
* @return 转换后的 Date 对象
*/
public static Date toDate(LocalDate temporalAccessor) {
LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
return Date.from(zdt.toInstant());
}
/**
* 校验日期范围
* *
* @param startDate 开始日期 * @param startDate 开始日期
* @param endDate 结束日期 * @param endDate 结束日期
* @param maxValue 最大时间跨度限制值 * @param maxValue 最大时间跨度限制值
* @param unit 时间跨度的单位,可选择 "DAYS"、"HOURS" 或 "MINUTES" * @param unit 时间单位
*/ */
public static void validateDateRange(Date startDate, Date endDate, int maxValue, TimeUnit unit) { public static void validateDateRange(Object startDate, Object endDate, int maxValue, TimeUnit unit) {
// 校验结束日期不能早于开始日期 // 基础非空校验
if (endDate.before(startDate)) { Assert.notNull(startDate, "开始日期不能为空");
Assert.notNull(endDate, "结束日期不能为空");
Assert.notNull(unit, "时间单位不能为空");
// 统一转换并校验时间合法性
Date start = Convert.toDate(startDate);
Date end = Convert.toDate(endDate);
Assert.notNull(start, "开始日期格式错误,无法解析为时间");
Assert.notNull(end, "结束日期格式错误,无法解析为时间");
// 校验结束时间不能早于开始时间
if (end.before(start)) {
throw new ServiceException("结束日期不能早于开始日期"); throw new ServiceException("结束日期不能早于开始日期");
} }
// 计算时间跨度 long diffMillis = end.getTime() - start.getTime();
long diffInMillis = endDate.getTime() - startDate.getTime();
// 根据单位转换时间跨度
long diff = switch (unit) { long diff = switch (unit) {
case DAYS -> TimeUnit.MILLISECONDS.toDays(diffInMillis); case DAYS -> TimeUnit.MILLISECONDS.toDays(diffMillis);
case HOURS -> TimeUnit.MILLISECONDS.toHours(diffInMillis); case HOURS -> TimeUnit.MILLISECONDS.toHours(diffMillis);
case MINUTES -> TimeUnit.MILLISECONDS.toMinutes(diffInMillis); case MINUTES -> TimeUnit.MILLISECONDS.toMinutes(diffMillis);
default -> throw new IllegalArgumentException("不支持的时间单位"); default -> throw new IllegalArgumentException("不支持的时间单位" + unit.name());
}; };
// 校验时间跨度不超过最大限制
if (diff > maxValue) { if (diff > maxValue) {
throw new ServiceException("最大时间跨度为 {} {}", maxValue, unit.toString().toLowerCase()); String msg = String.format("最大时间跨度为 %d %s", maxValue, unit.name().toLowerCase());
throw new ServiceException(msg);
} }
} }
@@ -352,7 +101,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
* @return 时间段描述 * @return 时间段描述
*/ */
public static String getTodayHour(Date date) { public static String getTodayHour(Date date) {
int hour = DateUtil.hour(date, true); int hour = hour(date, true);
if (hour <= 6) { if (hour <= 6) {
return "凌晨"; return "凌晨";
} else if (hour < 12) { } else if (hour < 12) {
@@ -387,38 +136,37 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
if (date == null) { if (date == null) {
return ""; return "";
} }
Date now = DateUtil.date(); Date now = new Date();
// 未来时间或非今年 // 未来时间或非今年
if (date.after(now) || DateUtil.year(date) != DateUtil.year(now)) { if (date.after(now) || year(date) != year(now)) {
return parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM, date); return formatDateTime(now);
} }
// 今天 // 今天
if (DateUtil.isSameDay(date, now)) { if (isSameDay(date, now)) {
long minutes = DateUtil.between(date, now, DateUnit.MINUTE); long minutes = between(date, now, DateUnit.MINUTE);
if (minutes < 1) { if (minutes < 1) {
return "刚刚"; return "刚刚";
} }
if (minutes < 60) { if (minutes < 60) {
return minutes + "分钟前"; return minutes + "分钟前";
} }
return getTodayHour(date) + " " + DateUtil.format(date, "HH:mm"); return getTodayHour(date) + " " + format(date, "HH:mm");
} }
// 昨天 // 昨天
if (DateUtil.isSameDay(date, DateUtil.yesterday())) { if (isSameDay(date, yesterday())) {
return "昨天 " + DateUtil.format(date, "HH:mm"); return "昨天 " + format(date, "HH:mm");
} }
// 本周 // 本周
if (DateUtil.isSameWeek(date, now, true)) { if (isSameWeek(date, now, true)) {
return DateUtil.dayOfWeekEnum(date).toChinese("") return dayOfWeekEnum(date).toChinese("") + " " + format(date, "HH:mm");
+ " " + DateUtil.format(date, "HH:mm");
} }
// 今年内其它时间 // 今年内其它时间
return DateUtil.format(date, "MM-dd HH:mm"); return format(date, "MM-dd HH:mm");
} }
} }
@@ -14,7 +14,6 @@ import java.util.Map;
* Mapstruct 工具类 * Mapstruct 工具类
* <p>参考文档:<a href="https://mapstruct.plus/introduction/quick-start.html">mapstruct-plus</a></p> * <p>参考文档:<a href="https://mapstruct.plus/introduction/quick-start.html">mapstruct-plus</a></p>
* *
*
* @author Michelle.Chung * @author Michelle.Chung
*/ */
@NoArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PRIVATE)
@@ -86,7 +86,7 @@ public class NetUtils extends NetUtil {
/** /**
* 匹配 IP 规则,支持精确值、通配符与 CIDR。 * 匹配 IP 规则,支持精确值、通配符与 CIDR。
* *
* @param rule IP 规则 * @param rule IP 规则
* @param clientIp 客户端 IP * @param clientIp 客户端 IP
* @return 是否匹配 * @return 是否匹配
*/ */
@@ -114,7 +114,7 @@ public class NetUtils extends NetUtil {
/** /**
* 匹配 CIDR 网段。 * 匹配 CIDR 网段。
* *
* @param cidr CIDR 规则 * @param cidr CIDR 规则
* @param clientIp 客户端 IP * @param clientIp 客户端 IP
* @return 是否命中 * @return 是否命中
*/ */
@@ -17,7 +17,7 @@ public class ObjectUtils extends ObjectUtil {
/** /**
* 如果对象不为空,则获取对象中的某个字段 ObjectUtils.notNullGetter(user, User::getName); * 如果对象不为空,则获取对象中的某个字段 ObjectUtils.notNullGetter(user, User::getName);
* *
* @param obj 对象 * @param obj 对象
* @param func 获取方法 * @param func 获取方法
* @return 对象字段 * @return 对象字段
*/ */
@@ -60,7 +60,7 @@ public class StreamUtils {
* @return 找到符合条件的第一个元素,没有则返回 null * @return 找到符合条件的第一个元素,没有则返回 null
*/ */
public static <E> E findFirstValue(Collection<E> collection, Predicate<E> function) { public static <E> E findFirstValue(Collection<E> collection, Predicate<E> function) {
return findFirst(collection,function).orElse(null); return findFirst(collection, function).orElse(null);
} }
/** /**
@@ -87,7 +87,7 @@ public class StreamUtils {
* @return 找到符合条件的任意一个元素,没有则返回null * @return 找到符合条件的任意一个元素,没有则返回null
*/ */
public static <E> E findAnyValue(Collection<E> collection, Predicate<E> function) { public static <E> E findAnyValue(Collection<E> collection, Predicate<E> function) {
return findAny(collection,function).orElse(null); return findAny(collection, function).orElse(null);
} }
/** /**
@@ -179,11 +179,12 @@ public class StreamUtils {
/** /**
* 获取 map 中的数据作为新 Map 的 value key 不变 * 获取 map 中的数据作为新 Map 的 value key 不变
* @param map 需要处理的map *
* @param map 需要处理的map
* @param take 取值函数 * @param take 取值函数
* @param <K> map中的key类型 * @param <K> map中的key类型
* @param <E> map中的value类型 * @param <E> map中的value类型
* @param <V> 新map中的value类型 * @param <V> 新map中的value类型
* @return 新的map * @return 新的map
*/ */
public static <K, E, V> Map<K, V> toMap(Map<K, E> map, BiFunction<K, E, V> take) { public static <K, E, V> Map<K, V> toMap(Map<K, E> map, BiFunction<K, E, V> take) {
@@ -23,6 +23,8 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
public static final String SLASH = "/"; public static final String SLASH = "/";
public static final String COLON = ":";
private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher(); private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();
@Deprecated @Deprecated
@@ -363,6 +365,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
return input; return input;
} }
} }
/** /**
* 将可迭代对象中的元素使用逗号拼接成字符串 * 将可迭代对象中的元素使用逗号拼接成字符串
* *
@@ -397,7 +400,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
/** /**
* 判断字符串是否在指定的字符串列表中 * 判断字符串是否在指定的字符串列表中
* *
* @param string 字符串 * @param string 字符串
* @param searchStrings 字符串列表 * @param searchStrings 字符串列表
* @return 是否在列表中 * @return 是否在列表中
*/ */
@@ -408,7 +411,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
/** /**
* 忽略大小写判断字符串是否在指定的字符串列表中 * 忽略大小写判断字符串是否在指定的字符串列表中
* *
* @param string 字符串 * @param string 字符串
* @param searchStrings 字符串列表 * @param searchStrings 字符串列表
* @return 是否在列表中 * @return 是否在列表中
*/ */
@@ -499,8 +502,8 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
/** /**
* 移除字符串中的指定字符序列。 * 移除字符串中的指定字符序列。
* *
* @param str 要处理的字符串,不能为null * @param str 要处理的字符串,不能为null
* @param remove 要移除的字符序列,不能为null * @param remove 要移除的字符序列,不能为null
* @return 处理后的字符串 * @return 处理后的字符串
*/ */
public static String remove(final String str, final String remove) { public static String remove(final String str, final String remove) {
@@ -6,7 +6,10 @@ import lombok.NoArgsConstructor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.*; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.Supplier; import java.util.function.Supplier;
/** /**
@@ -64,7 +64,7 @@ public class TreeBuildUtils extends TreeUtil {
if (CollUtil.isEmpty(list)) { if (CollUtil.isEmpty(list)) {
return CollUtil.newArrayList(); return CollUtil.newArrayList();
} }
K k = ReflectUtils.invokeGetter(list.get(0), "parentId"); K k = ReflectUtils.invokeGetter(list.getFirst(), "parentId");
return TreeUtil.build(list, k, DEFAULT_CONFIG, nodeParser); return TreeUtil.build(list, k, DEFAULT_CONFIG, nodeParser);
} }
@@ -26,6 +26,9 @@ public class ValidatorUtils {
* @throws ConstraintViolationException 如果校验不通过,则抛出参数校验异常 * @throws ConstraintViolationException 如果校验不通过,则抛出参数校验异常
*/ */
public static <T> void validate(T object, Class<?>... groups) { public static <T> void validate(T object, Class<?>... groups) {
if (object == null) {
throw new RuntimeException("请求参数不能为空");
}
Set<ConstraintViolation<T>> validate = VALID.validate(object, groups); Set<ConstraintViolation<T>> validate = VALID.validate(object, groups);
if (!validate.isEmpty()) { if (!validate.isEmpty()) {
throw new ConstraintViolationException("参数校验异常", validate); throw new ConstraintViolationException("参数校验异常", validate);
@@ -1,60 +1,64 @@
package org.dromara.common.core.utils.file; package org.dromara.common.core.utils.file;
import cn.hutool.core.util.StrUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
/** /**
* 媒体类型工具类 * 媒体类型工具类
* *
* @author ruoyi * @author ruoyi
*/ */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MimeTypeUtils { public class MimeTypeUtils {
/** /**
* PNG 图片 MIME 类型 * PNG 图片 MIME 类型
*/ */
public static final String IMAGE_PNG = "image/png"; public static final String IMAGE_PNG = "image/png";
/** /**
* JPG 图片 MIME 类型 * JPG 图片 MIME 类型
*/ */
public static final String IMAGE_JPG = "image/jpg"; public static final String IMAGE_JPG = "image/jpg";
/** /**
* JPEG 图片 MIME 类型 * JPEG 图片 MIME 类型
*/ */
public static final String IMAGE_JPEG = "image/jpeg"; public static final String IMAGE_JPEG = "image/jpeg";
/** /**
* BMP 图片 MIME 类型 * BMP 图片 MIME 类型
*/ */
public static final String IMAGE_BMP = "image/bmp"; public static final String IMAGE_BMP = "image/bmp";
/** /**
* GIF 图片 MIME 类型 * GIF 图片 MIME 类型
*/ */
public static final String IMAGE_GIF = "image/gif"; public static final String IMAGE_GIF = "image/gif";
/** /**
* 图片扩展名集合。 * 图片扩展名数组
*/ */
public static final String[] IMAGE_EXTENSION = {"bmp", "gif", "jpg", "jpeg", "png"}; public static final String[] IMAGE_EXTENSION = {"bmp", "gif", "jpg", "jpeg", "png"};
/** /**
* Flash 扩展名集合。 * Flash 文件扩展名数组
*/ */
public static final String[] FLASH_EXTENSION = {"swf", "flv"}; public static final String[] FLASH_EXTENSION = {"swf", "flv"};
/** /**
* 媒体扩展名集合。 * 视频文件扩展名数组
*/
public static final String[] MEDIA_EXTENSION = {"swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
"asf", "rm", "rmvb"};
/**
* 视频扩展名集合。
*/ */
public static final String[] VIDEO_EXTENSION = {"mp4", "avi", "rmvb"}; public static final String[] VIDEO_EXTENSION = {"mp4", "avi", "rmvb"};
/** /**
* 默认允许上传扩展名集合。 * 媒体文件扩展名数组(音频+视频+Flash)
*/
public static final String[] MEDIA_EXTENSION = {"swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg", "asf", "rm", "rmvb"};
/**
* 默认允许上传的文件扩展名数组
*/ */
public static final String[] DEFAULT_ALLOWED_EXTENSION = { public static final String[] DEFAULT_ALLOWED_EXTENSION = {
// 图片 // 图片
@@ -66,6 +70,47 @@ public class MimeTypeUtils {
// 视频格式 // 视频格式
"mp4", "avi", "rmvb", "mp4", "avi", "rmvb",
// pdf // pdf
"pdf"}; "pdf"
};
/**
* 判断文件扩展名是否为图片格式(忽略大小写)
*
* @param extension 文件扩展名
* @return 是图片返回 true,否则返回 false
*/
public static boolean isImage(String extension) {
return StrUtil.equalsAnyIgnoreCase(extension, IMAGE_EXTENSION);
}
/**
* 判断文件扩展名是否为视频格式(忽略大小写)
*
* @param extension 文件扩展名
* @return 是视频返回 true,否则返回 false
*/
public static boolean isVideo(String extension) {
return StrUtil.equalsAnyIgnoreCase(extension, VIDEO_EXTENSION);
}
/**
* 判断文件扩展名是否为媒体格式(忽略大小写)
*
* @param extension 文件扩展名
* @return 是媒体返回 true,否则返回 false
*/
public static boolean isMedia(String extension) {
return StrUtil.equalsAnyIgnoreCase(extension, MEDIA_EXTENSION);
}
/**
* 判断文件扩展名是否在默认允许上传范围内(忽略大小写)
*
* @param extension 文件扩展名
* @return 允许返回 true,不允许返回 false
*/
public static boolean isDefaultAllowed(String extension) {
return StrUtil.equalsAnyIgnoreCase(extension, DEFAULT_ALLOWED_EXTENSION);
}
} }
@@ -29,7 +29,7 @@ public class AddressUtils {
*/ */
public static String getRealAddressByIP(String ip) { public static String getRealAddressByIP(String ip) {
// 处理空串并过滤HTML标签 // 处理空串并过滤HTML标签
ip = HtmlUtil.cleanHtmlTag(StringUtils.blankToDefault(ip,"")); ip = HtmlUtil.cleanHtmlTag(StringUtils.blankToDefault(ip, ""));
// 判断是否为IPv4 // 判断是否为IPv4
boolean isIPv4 = NetUtils.isIPv4(ip); boolean isIPv4 = NetUtils.isIPv4(ip);
// 判断是否为IPv6 // 判断是否为IPv6
@@ -1,6 +1,8 @@
package org.dromara.common.core.utils.ip; package org.dromara.common.core.utils.ip;
import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.io.resource.ResourceUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
@@ -19,6 +21,7 @@ import java.time.Duration;
* @author 秋辞未寒 * @author 秋辞未寒
*/ */
@Slf4j @Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class RegionUtils { public class RegionUtils {
// 默认IPv4地址库文件路径 // 默认IPv4地址库文件路径
@@ -23,7 +23,7 @@ public final class AnnotationUtils extends AnnotationUtil {
/** /**
* 获取指定注解 * 获取指定注解
* *
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission * @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission
* @param annotationTypeName 注解类型名称 * @param annotationTypeName 注解类型名称
* @return 注解对象 * @return 注解对象
*/ */
@@ -34,7 +34,7 @@ public final class AnnotationUtils extends AnnotationUtil {
} catch (final ClassNotFoundException | ClassCastException e) { } catch (final ClassNotFoundException | ClassCastException e) {
// ignore // ignore
log.error("AnnotationUtils.getAnnotation(AnnotatedElement, String) error.", e); log.error("AnnotationUtils.getAnnotation(AnnotatedElement, String) error.", e);
return null; return null;
} }
} }
@@ -42,7 +42,7 @@ public final class AnnotationUtils extends AnnotationUtil {
* 获取指定注解中所有属性值<br> * 获取指定注解中所有属性值<br>
* 如果无指定的属性方法返回null * 如果无指定的属性方法返回null
* *
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission * @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission
* @param annotationTypeName 注解类型名称 * @param annotationTypeName 注解类型名称
* @return 注解对象所有属性键值 * @return 注解对象所有属性键值
* @throws UtilException 调用注解中的方法时执行异常 * @throws UtilException 调用注解中的方法时执行异常
@@ -55,7 +55,7 @@ public final class AnnotationUtils extends AnnotationUtil {
} catch (final ClassNotFoundException | ClassCastException e) { } catch (final ClassNotFoundException | ClassCastException e) {
// ignore // ignore
log.error("AnnotationUtils.getAnnotationValueMap(AnnotatedElement, String) error.", e); log.error("AnnotationUtils.getAnnotationValueMap(AnnotatedElement, String) error.", e);
return null; return null;
} }
} }
@@ -1,9 +1,9 @@
package org.dromara.common.core.utils.reflect; package org.dromara.common.core.utils.reflect;
import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ReflectUtil;
import org.dromara.common.core.utils.StringUtils;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.StringUtils;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@@ -12,7 +12,6 @@ import java.lang.reflect.Method;
* *
* @author Lion Li * @author Lion Li
*/ */
@SuppressWarnings("rawtypes")
@NoArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ReflectUtils extends ReflectUtil { public class ReflectUtils extends ReflectUtil {
@@ -2,6 +2,8 @@ package org.dromara.common.core.utils.regex;
import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.ReUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.constant.RegexConstants; import org.dromara.common.core.constant.RegexConstants;
/** /**
@@ -9,6 +11,7 @@ import org.dromara.common.core.constant.RegexConstants;
* *
* @author AprilWind * @author AprilWind
*/ */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class RegexUtils extends ReUtil { public final class RegexUtils extends ReUtil {
/** /**
@@ -2,6 +2,8 @@ package org.dromara.common.core.utils.regex;
import cn.hutool.core.exceptions.ValidateException; import cn.hutool.core.exceptions.ValidateException;
import cn.hutool.core.lang.Validator; import cn.hutool.core.lang.Validator;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.factory.RegexPatternPoolFactory; import org.dromara.common.core.factory.RegexPatternPoolFactory;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -12,6 +14,7 @@ import java.util.regex.Pattern;
* *
* @author AprilWind * @author AprilWind
*/ */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class RegexValidator extends Validator { public class RegexValidator extends Validator {
/** /**
@@ -3,7 +3,10 @@ package org.dromara.common.core.validate.enums;
import jakarta.validation.Constraint; import jakarta.validation.Constraint;
import jakarta.validation.Payload; import jakarta.validation.Payload;
import java.lang.annotation.*; import java.lang.annotation.Documented;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*; import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME; import static java.lang.annotation.RetentionPolicy.RUNTIME;
@@ -2,6 +2,7 @@ package org.dromara.common.core.xss;
import jakarta.validation.Constraint; import jakarta.validation.Constraint;
import jakarta.validation.Payload; import jakarta.validation.Payload;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@@ -6,9 +6,9 @@ import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityRequirement;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.doc.config.properties.SpringDocProperties;
import org.dromara.common.doc.core.customizer.ClassTagOperationCustomizer; import org.dromara.common.doc.core.customizer.ClassTagOperationCustomizer;
import org.dromara.common.doc.core.customizer.JavadocOperationCustomizer; import org.dromara.common.doc.core.customizer.JavadocOperationCustomizer;
import org.dromara.common.doc.config.properties.SpringDocProperties;
import org.dromara.common.doc.core.resolver.JavadocResolver; import org.dromara.common.doc.core.resolver.JavadocResolver;
import org.dromara.common.doc.core.resolver.SaTokenAnnotationMetadataJavadocResolver; import org.dromara.common.doc.core.resolver.SaTokenAnnotationMetadataJavadocResolver;
import org.springdoc.core.configuration.SpringDocConfiguration; import org.springdoc.core.configuration.SpringDocConfiguration;
@@ -56,7 +56,7 @@ public class SpringDocProperties {
* </p> * </p>
* *
* @see io.swagger.v3.oas.models.info.Info * @see io.swagger.v3.oas.models.info.Info
* * <p>
* 为了 springboot 自动生产配置提示信息,所以这里复制一个类出来 * 为了 springboot 自动生产配置提示信息,所以这里复制一个类出来
*/ */
@Data @Data
@@ -18,13 +18,7 @@ import org.springframework.util.CollectionUtils;
import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethod;
import java.io.StringReader; import java.io.StringReader;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -86,7 +86,7 @@ public class SaTokenSecurityMetadata {
return sb.toString(); return sb.toString();
} }
if (!ignore && permissions.isEmpty() && roles.isEmpty()){ if (!ignore && permissions.isEmpty() && roles.isEmpty()) {
sb.append("> **权限策略**:需要登录<br><br>"); sb.append("> **权限策略**:需要登录<br><br>");
return sb.toString(); return sb.toString();
} }
@@ -82,76 +82,84 @@ public abstract class AbstractMetadataJavadocResolver<M> implements JavadocResol
/** /**
* 执行解析并返回解析到的 Javadoc 内容 * 执行解析并返回解析到的 Javadoc 内容
*
* @param handlerMethod 处理器方法 * @param handlerMethod 处理器方法
* @param operation Swagger Operation实例 * @param operation Swagger Operation实例
* @param metadata 元信息 * @param metadata 元信息
* @return 解析到的 Javadoc 内容 * @return 解析到的 Javadoc 内容
*/ */
public abstract String resolve(HandlerMethod handlerMethod, Operation operation, M metadata); public abstract String resolve(HandlerMethod handlerMethod, Operation operation, M metadata);
/** /**
* 检查处理器方法所属的类上是否存在注解 * 检查处理器方法所属的类上是否存在注解
* @param handlerMethod 处理器方法 *
* @param handlerMethod 处理器方法
* @param annotationClass 注解类 * @param annotationClass 注解类
* @return 是否存在注解 * @return 是否存在注解
*/ */
public boolean hasClassAnnotation(HandlerMethod handlerMethod,Class<? extends Annotation> annotationClass){ public boolean hasClassAnnotation(HandlerMethod handlerMethod, Class<? extends Annotation> annotationClass) {
return AnnotationUtil.hasAnnotation(handlerMethod.getBeanType(), annotationClass); return AnnotationUtil.hasAnnotation(handlerMethod.getBeanType(), annotationClass);
} }
/** /**
* 检查处理器方法所属的类上是否存在注解 * 检查处理器方法所属的类上是否存在注解
* @param handlerMethod 处理器方法 *
* @param handlerMethod 处理器方法
* @param annotationTypeName 注解类名称 * @param annotationTypeName 注解类名称
* @return 是否存在注解 * @return 是否存在注解
*/ */
public boolean hasClassAnnotation(HandlerMethod handlerMethod, String annotationTypeName){ public boolean hasClassAnnotation(HandlerMethod handlerMethod, String annotationTypeName) {
return AnnotationUtil.hasAnnotation(handlerMethod.getBeanType(), annotationTypeName); return AnnotationUtil.hasAnnotation(handlerMethod.getBeanType(), annotationTypeName);
} }
/** /**
* 检查处理器方法上是否存在注解 * 检查处理器方法上是否存在注解
* @param handlerMethod 处理器方法 *
* @param handlerMethod 处理器方法
* @param annotationClass 注解类 * @param annotationClass 注解类
* @return 是否存在注解 * @return 是否存在注解
*/ */
public boolean hasMethodAnnotation(HandlerMethod handlerMethod,Class<? extends Annotation> annotationClass){ public boolean hasMethodAnnotation(HandlerMethod handlerMethod, Class<? extends Annotation> annotationClass) {
return AnnotationUtil.hasAnnotation(handlerMethod.getMethod(), annotationClass); return AnnotationUtil.hasAnnotation(handlerMethod.getMethod(), annotationClass);
} }
/** /**
* 检查处理器方法上是否存在注解 * 检查处理器方法上是否存在注解
* @param handlerMethod 处理器方法 *
* @param handlerMethod 处理器方法
* @param annotationTypeName 注解类名称 * @param annotationTypeName 注解类名称
* @return 是否存在注解 * @return 是否存在注解
*/ */
public boolean hasMethodAnnotation(HandlerMethod handlerMethod, String annotationTypeName){ public boolean hasMethodAnnotation(HandlerMethod handlerMethod, String annotationTypeName) {
return AnnotationUtil.hasAnnotation(handlerMethod.getMethod(), annotationTypeName); return AnnotationUtil.hasAnnotation(handlerMethod.getMethod(), annotationTypeName);
} }
/** /**
* 检查处理器方法上是否存在注解 * 检查处理器方法上是否存在注解
* @param handlerMethod 处理器方法 *
* @param handlerMethod 处理器方法
* @param annotationClass 注解类 * @param annotationClass 注解类
* @return 是否存在注解 * @return 是否存在注解
*/ */
public boolean hasAnnotation(HandlerMethod handlerMethod,Class<? extends Annotation> annotationClass){ public boolean hasAnnotation(HandlerMethod handlerMethod, Class<? extends Annotation> annotationClass) {
return this.hasClassAnnotation(handlerMethod, annotationClass) || this.hasMethodAnnotation(handlerMethod, annotationClass); return this.hasClassAnnotation(handlerMethod, annotationClass) || this.hasMethodAnnotation(handlerMethod, annotationClass);
} }
/** /**
* 检查处理器方法上是否存在注解 * 检查处理器方法上是否存在注解
* @param handlerMethod 处理器方法 *
* @param handlerMethod 处理器方法
* @param annotationTypeName 注解类名称 * @param annotationTypeName 注解类名称
* @return 是否存在注解 * @return 是否存在注解
*/ */
public boolean hasAnnotation(HandlerMethod handlerMethod, String annotationTypeName){ public boolean hasAnnotation(HandlerMethod handlerMethod, String annotationTypeName) {
return this.hasClassAnnotation(handlerMethod, annotationTypeName) || this.hasMethodAnnotation(handlerMethod, annotationTypeName); return this.hasClassAnnotation(handlerMethod, annotationTypeName) || this.hasMethodAnnotation(handlerMethod, annotationTypeName);
} }
/** /**
* 获取处理器方法所属类上的注解的值 * 获取处理器方法所属类上的注解的值
* @param handlerMethod 处理器方法 *
* @param handlerMethod 处理器方法
* @param annotationClass 注解类 * @param annotationClass 注解类
* @return 注解的值 * @return 注解的值
*/ */
@@ -161,7 +169,8 @@ public abstract class AbstractMetadataJavadocResolver<M> implements JavadocResol
/** /**
* 获取处理器方法所属类上的注解的值 * 获取处理器方法所属类上的注解的值
* @param handlerMethod 处理器方法 *
* @param handlerMethod 处理器方法
* @param annotationClassName 注解类名称 * @param annotationClassName 注解类名称
* @return 注解的值 * @return 注解的值
*/ */
@@ -173,7 +182,8 @@ public abstract class AbstractMetadataJavadocResolver<M> implements JavadocResol
/** /**
* 获取处理器方法上的注解的值 * 获取处理器方法上的注解的值
* @param handlerMethod 处理器方法 *
* @param handlerMethod 处理器方法
* @param annotationClass 注解类 * @param annotationClass 注解类
* @return 注解的值 * @return 注解的值
*/ */
@@ -183,7 +193,8 @@ public abstract class AbstractMetadataJavadocResolver<M> implements JavadocResol
/** /**
* 获取处理器方法所属类上的注解的值 * 获取处理器方法所属类上的注解的值
* @param handlerMethod 处理器方法 *
* @param handlerMethod 处理器方法
* @param annotationClassName 注解类名称 * @param annotationClassName 注解类名称
* @return 注解的值 * @return 注解的值
*/ */
@@ -25,7 +25,7 @@ public interface JavadocResolver extends Comparable<JavadocResolver>, Ordered {
* 执行解析并返回解析到的 Javadoc 内容 * 执行解析并返回解析到的 Javadoc 内容
* *
* @param handlerMethod 处理器方法 * @param handlerMethod 处理器方法
* @param operation Swagger Operation实例 * @param operation Swagger Operation实例
* @return 解析到的 Javadoc 内容 * @return 解析到的 Javadoc 内容
*/ */
String resolve(HandlerMethod handlerMethod, Operation operation); String resolve(HandlerMethod handlerMethod, Operation operation);
@@ -106,17 +106,17 @@ public class SaTokenAnnotationMetadataJavadocResolver extends AbstractMetadataJa
* @param order 顺序值 * @param order 顺序值
*/ */
public SaTokenAnnotationMetadataJavadocResolver(int order) { public SaTokenAnnotationMetadataJavadocResolver(int order) {
this(DEFAULT_METADATA_PROVIDER,order); this(DEFAULT_METADATA_PROVIDER, order);
} }
/** /**
* 使用自定义元数据提供者和顺序创建解析器。 * 使用自定义元数据提供者和顺序创建解析器。
* *
* @param metadataProvider 元数据提供者 * @param metadataProvider 元数据提供者
* @param order 顺序值 * @param order 顺序值
*/ */
public SaTokenAnnotationMetadataJavadocResolver(Supplier<SaTokenSecurityMetadata> metadataProvider, int order) { public SaTokenAnnotationMetadataJavadocResolver(Supplier<SaTokenSecurityMetadata> metadataProvider, int order) {
super(metadataProvider,order); super(metadataProvider, order);
} }
/** /**
@@ -134,14 +134,14 @@ public class SaTokenAnnotationMetadataJavadocResolver extends AbstractMetadataJa
* 解析 Sa-Token 注解并转换为文档说明。 * 解析 Sa-Token 注解并转换为文档说明。
* *
* @param handlerMethod Handler 方法 * @param handlerMethod Handler 方法
* @param operation OpenAPI 操作对象 * @param operation OpenAPI 操作对象
* @param metadata 权限元数据 * @param metadata 权限元数据
* @return Markdown 描述 * @return Markdown 描述
*/ */
@Override @Override
public String resolve(HandlerMethod handlerMethod, Operation operation, SaTokenSecurityMetadata metadata) { public String resolve(HandlerMethod handlerMethod, Operation operation, SaTokenSecurityMetadata metadata) {
// 检查是否忽略校验 // 检查是否忽略校验
if(hasAnnotation(handlerMethod, SA_IGNORE_CLASS_NAME)){ if (hasAnnotation(handlerMethod, SA_IGNORE_CLASS_NAME)) {
metadata.setIgnore(true); metadata.setIgnore(true);
return metadata.toMarkdownString(); return metadata.toMarkdownString();
} }
@@ -176,10 +176,10 @@ public class SaTokenAnnotationMetadataJavadocResolver extends AbstractMetadataJa
private void resolvePermissionAnnotation(SaTokenSecurityMetadata metadata, Map<String, Object> annotationValueMap) { private void resolvePermissionAnnotation(SaTokenSecurityMetadata metadata, Map<String, Object> annotationValueMap) {
try { try {
// 反射获取注解属性 // 反射获取注解属性
Object value = annotationValueMap.get( "value"); Object value = annotationValueMap.get("value");
Object mode = annotationValueMap.get( "mode"); Object mode = annotationValueMap.get("mode");
Object type = annotationValueMap.get( "type"); Object type = annotationValueMap.get("type");
Object orRole = annotationValueMap.get( "orRole"); Object orRole = annotationValueMap.get("orRole");
String[] values = Convert.toStrArray(value); String[] values = Convert.toStrArray(value);
String modeStr = mode != null ? mode.toString() : "AND"; String modeStr = mode != null ? mode.toString() : "AND";
@@ -25,9 +25,9 @@ public class ApiDecryptAutoConfiguration {
/** /**
* 注册 API 加解密过滤器。 * 注册 API 加解密过滤器。
* *
* @param properties API 解密配置 * @param properties API 解密配置
* @param requestMappingHandlerMapping 请求映射处理器 * @param requestMappingHandlerMapping 请求映射处理器
* @param handlerExceptionResolver 异常处理器 * @param handlerExceptionResolver 异常处理器
* @return API 加解密过滤器 * @return API 加解密过滤器
*/ */
@Bean @Bean
@@ -1,8 +1,8 @@
package org.dromara.common.encrypt.core; package org.dromara.common.encrypt.core;
import lombok.Data;
import org.dromara.common.encrypt.enums.AlgorithmType; import org.dromara.common.encrypt.enums.AlgorithmType;
import org.dromara.common.encrypt.enums.EncodeType; import org.dromara.common.encrypt.enums.EncodeType;
import lombok.Data;
/** /**
* 加密上下文 用于encryptor传递必要的参数。 * 加密上下文 用于encryptor传递必要的参数。
@@ -171,10 +171,10 @@ public class EncryptorManager {
/** /**
* 加密器缓存键。 * 加密器缓存键。
* *
* @param algorithm 加密算法 * @param algorithm 加密算法
* @param encode 编码方式 * @param encode 编码方式
* @param password 密码 * @param password 密码
* @param publicKey 公钥 * @param publicKey 公钥
* @param privateKey 私钥 * @param privateKey 私钥
*/ */
private record EncryptorCacheKey( private record EncryptorCacheKey(
@@ -28,7 +28,7 @@ public interface IEncryptor {
/** /**
* 解密 * 解密
* *
* @param value 待加密字符串 * @param value 待加密字符串
* @return 解密后的字符串 * @return 解密后的字符串
*/ */
String decrypt(String value); String decrypt(String value);
@@ -46,7 +46,7 @@ public class AesEncryptor extends AbstractEncryptor {
/** /**
* 解密 * 解密
* *
* @param value 待加密字符串 * @param value 待加密字符串
*/ */
@Override @Override
public String decrypt(String value) { public String decrypt(String value) {
@@ -44,7 +44,7 @@ public class Base64Encryptor extends AbstractEncryptor {
/** /**
* 解密 * 解密
* *
* @param value 待加密字符串 * @param value 待加密字符串
*/ */
@Override @Override
public String decrypt(String value) { public String decrypt(String value) {
@@ -21,6 +21,6 @@ public enum EncodeType {
/** /**
* 16进制编码 * 16进制编码
*/ */
HEX; HEX
} }
@@ -31,7 +31,7 @@ public class DecryptRequestBodyWrapper extends HttpServletRequestWrapper {
/** /**
* 解密请求体并缓存为可重复读取的 JSON 请求体。 * 解密请求体并缓存为可重复读取的 JSON 请求体。
* *
* @param request 原始请求 * @param request 原始请求
* @param privateKey RSA 私钥 * @param privateKey RSA 私钥
* @param headerFlag 加密密钥请求头 * @param headerFlag 加密密钥请求头
* @throws IOException 读取请求体异常 * @throws IOException 读取请求体异常
@@ -5,6 +5,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
/** /**
* api解密属性配置类 * api解密属性配置类
*
* @author wdhcr * @author wdhcr
*/ */
@Data @Data
@@ -1,8 +1,8 @@
package org.dromara.common.encrypt.properties; package org.dromara.common.encrypt.properties;
import lombok.Data;
import org.dromara.common.encrypt.enums.AlgorithmType; import org.dromara.common.encrypt.enums.AlgorithmType;
import org.dromara.common.encrypt.enums.EncodeType; import org.dromara.common.encrypt.enums.EncodeType;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
/** /**
@@ -259,9 +259,9 @@ public class EncryptUtils {
/** /**
* SM2公钥验签(Hex编码) * SM2公钥验签(Hex编码)
* *
* @param dataHex 原文数据(Hex编码) * @param dataHex 原文数据(Hex编码)
* @param signHex 签名值(Hex编码) * @param signHex 签名值(Hex编码)
* @param publicKey 公钥 * @param publicKey 公钥
* @return true-验签成功,false-验签失败 * @return true-验签成功,false-验签失败
*/ */
public static boolean verifySm2SignHex(String dataHex, String signHex, String publicKey) { public static boolean verifySm2SignHex(String dataHex, String signHex, String publicKey) {
@@ -6,7 +6,7 @@ import java.lang.annotation.*;
/** /**
* excel 列单元格合并(合并列相同项) * excel 列单元格合并(合并列相同项)
* * <p>
* 需搭配 {@link CellMergeStrategy} 策略使用 * 需搭配 {@link CellMergeStrategy} 策略使用
* *
* @author Lion Li * @author Lion Li
@@ -16,10 +16,10 @@ import java.lang.annotation.*;
@Inherited @Inherited
public @interface CellMerge { public @interface CellMerge {
/** /**
* col index * col index
*/ */
int index() default -1; int index() default -1;
/** /**
* 合并需要依赖的其他字段名称 * 合并需要依赖的其他字段名称
@@ -7,6 +7,7 @@ import java.lang.annotation.Target;
/** /**
* 批注 此注解仅用于单表头 不支持多层级表头 * 批注 此注解仅用于单表头 不支持多层级表头
*
* @author guzhouyanyu * @author guzhouyanyu
*/ */
@Target({ElementType.FIELD}) @Target({ElementType.FIELD})
@@ -9,6 +9,7 @@ import java.lang.annotation.Target;
/** /**
* 是否必填 此注解仅用于单表头 不支持多层级表头 * 是否必填 此注解仅用于单表头 不支持多层级表头
*
* @author guzhouyanyu * @author guzhouyanyu
*/ */
@Target({ElementType.FIELD}) @Target({ElementType.FIELD})
@@ -44,8 +44,8 @@ public class ExcelBigNumberConvert implements Converter<Long> {
/** /**
* 将 Excel 单元格数据转换为 Long。 * 将 Excel 单元格数据转换为 Long。
* *
* @param cellData 单元格数据 * @param cellData 单元格数据
* @param contentProperty 内容属性 * @param contentProperty 内容属性
* @param globalConfiguration 全局配置 * @param globalConfiguration 全局配置
* @return Long 值 * @return Long 值
*/ */
@@ -57,8 +57,8 @@ public class ExcelBigNumberConvert implements Converter<Long> {
/** /**
* 将 Long 转换为 Excel 单元格数据,超长数字按字符串写出。 * 将 Long 转换为 Excel 单元格数据,超长数字按字符串写出。
* *
* @param object Java 值 * @param object Java 值
* @param contentProperty 内容属性 * @param contentProperty 内容属性
* @param globalConfiguration 全局配置 * @param globalConfiguration 全局配置
* @return Excel 写入数据 * @return Excel 写入数据
*/ */
@@ -3,6 +3,7 @@ package org.dromara.common.excel.convert;
import cn.hutool.core.annotation.AnnotationUtil; import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.fesod.sheet.converters.Converter; import org.apache.fesod.sheet.converters.Converter;
import org.apache.fesod.sheet.enums.CellDataTypeEnum; import org.apache.fesod.sheet.enums.CellDataTypeEnum;
import org.apache.fesod.sheet.metadata.GlobalConfiguration; import org.apache.fesod.sheet.metadata.GlobalConfiguration;
@@ -11,7 +12,6 @@ import org.apache.fesod.sheet.metadata.data.WriteCellData;
import org.apache.fesod.sheet.metadata.property.ExcelContentProperty; import org.apache.fesod.sheet.metadata.property.ExcelContentProperty;
import org.dromara.common.core.utils.reflect.ReflectUtils; import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.excel.annotation.ExcelEnumFormat; import org.dromara.common.excel.annotation.ExcelEnumFormat;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.HashMap; import java.util.HashMap;
@@ -58,8 +58,8 @@ public class ExcelEnumConvert implements Converter<Object> {
/** /**
* 将 Excel 枚举文本转换为 Java 字段值。 * 将 Excel 枚举文本转换为 Java 字段值。
* *
* @param cellData 单元格数据 * @param cellData 单元格数据
* @param contentProperty 内容属性 * @param contentProperty 内容属性
* @param globalConfiguration 全局配置 * @param globalConfiguration 全局配置
* @return Java 字段值 * @return Java 字段值
*/ */
@@ -103,8 +103,8 @@ public class ExcelEnumConvert implements Converter<Object> {
/** /**
* 将 Java 枚举编码转换为 Excel 显示文本。 * 将 Java 枚举编码转换为 Excel 显示文本。
* *
* @param object Java 字段值 * @param object Java 字段值
* @param contentProperty 内容属性 * @param contentProperty 内容属性
* @param globalConfiguration 全局配置 * @param globalConfiguration 全局配置
* @return Excel 写入数据 * @return Excel 写入数据
*/ */
@@ -3,10 +3,10 @@ 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.ReflectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import lombok.SneakyThrows;
import org.apache.fesod.sheet.annotation.ExcelIgnore; import org.apache.fesod.sheet.annotation.ExcelIgnore;
import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated; import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
import org.apache.fesod.sheet.annotation.ExcelProperty; import org.apache.fesod.sheet.annotation.ExcelProperty;
import lombok.SneakyThrows;
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.core.utils.reflect.ReflectUtils;
import org.dromara.common.excel.annotation.CellMerge; import org.dromara.common.excel.annotation.CellMerge;
@@ -66,7 +66,7 @@ public class CellMergeHandler {
} }
// 获取有合并注解的字段 // 获取有合并注解的字段
Map<Field, FieldColumnIndex> mergeFields = getFieldColumnIndexMap(rows.get(0).getClass()); Map<Field, FieldColumnIndex> mergeFields = getFieldColumnIndexMap(rows.getFirst().getClass());
// 如果没有需要合并的字段则返回空集 // 如果没有需要合并的字段则返回空集
if (CollUtil.isEmpty(mergeFields)) { if (CollUtil.isEmpty(mergeFields)) {
return Collections.emptyList(); return Collections.emptyList();
@@ -165,8 +165,8 @@ public class CellMergeHandler {
* 判断当前行是否满足依赖字段合并条件。 * 判断当前行是否满足依赖字段合并条件。
* *
* @param currentRow 当前行数据 * @param currentRow 当前行数据
* @param preRow 上一行数据 * @param preRow 上一行数据
* @param cellMerge 合并配置 * @param cellMerge 合并配置
* @return 是否允许合并 * @return 是否允许合并
*/ */
private boolean isMerge(Object currentRow, Object preRow, CellMerge cellMerge) { private boolean isMerge(Object currentRow, Object preRow, CellMerge cellMerge) {
@@ -198,10 +198,10 @@ public class CellMergeHandler {
/** /**
* 追加有效的合并区域。 * 追加有效的合并区域。
* *
* @param result 合并区域结果集 * @param result 合并区域结果集
* @param repeatCell 连续重复单元格信息 * @param repeatCell 连续重复单元格信息
* @param endIndex 结束行索引 * @param endIndex 结束行索引
* @param colNum 列索引 * @param colNum 列索引
*/ */
private void appendMergeResult(List<CellRangeAddress> result, RepeatCell repeatCell, int endIndex, int colNum) { private void appendMergeResult(List<CellRangeAddress> result, RepeatCell repeatCell, int endIndex, int colNum) {
if (repeatCell == null || endIndex <= repeatCell.current()) { if (repeatCell == null || endIndex <= repeatCell.current()) {
@@ -217,7 +217,7 @@ public class CellMergeHandler {
/** /**
* 创建连续重复单元格信息。 * 创建连续重复单元格信息。
* *
* @param value 单元格值 * @param value 单元格值
* @param current 当前行索引 * @param current 当前行索引
* @return 连续重复单元格信息 * @return 连续重复单元格信息
*/ */
@@ -233,7 +233,7 @@ public class CellMergeHandler {
/** /**
* 创建字段列索引和合并注解信息。 * 创建字段列索引和合并注解信息。
* *
* @param colIndex 列索引 * @param colIndex 列索引
* @param cellMerge 合并注解 * @param cellMerge 合并注解
* @return 字段列索引和合并注解信息 * @return 字段列索引和合并注解信息
*/ */
@@ -38,7 +38,7 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements SheetWri
/** /**
* 根据数据列表创建单元格合并策略。 * 根据数据列表创建单元格合并策略。
* *
* @param list 数据列表 * @param list 数据列表
* @param hasTitle 是否存在标题行 * @param hasTitle 是否存在标题行
*/ */
public CellMergeStrategy(List<?> list, boolean hasTitle) { public CellMergeStrategy(List<?> list, boolean hasTitle) {
@@ -48,7 +48,7 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements SheetWri
/** /**
* 根据数据列表和起始行创建单元格合并策略。 * 根据数据列表和起始行创建单元格合并策略。
* *
* @param list 数据列表 * @param list 数据列表
* @param hasTitle 是否存在标题行 * @param hasTitle 是否存在标题行
* @param rowIndex 起始行索引 * @param rowIndex 起始行索引
*/ */
@@ -59,9 +59,9 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements SheetWri
/** /**
* 写入单元格时清理合并区域非首行内容。 * 写入单元格时清理合并区域非首行内容。
* *
* @param sheet 工作表 * @param sheet 工作表
* @param cell 当前单元格 * @param cell 当前单元格
* @param head 表头 * @param head 表头
* @param relativeRowIndex 相对行索引 * @param relativeRowIndex 相对行索引
*/ */
@Override @Override
@@ -83,7 +83,7 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements SheetWri
* 工作表创建后写入合并区域。 * 工作表创建后写入合并区域。
* *
* @param writeWorkbookHolder 工作簿上下文 * @param writeWorkbookHolder 工作簿上下文
* @param writeSheetHolder 工作表上下文 * @param writeSheetHolder 工作表上下文
*/ */
@Override @Override
public void afterSheetCreate(final WriteWorkbookHolder writeWorkbookHolder, final WriteSheetHolder writeSheetHolder) { public void afterSheetCreate(final WriteWorkbookHolder writeWorkbookHolder, final WriteSheetHolder writeSheetHolder) {
@@ -1,6 +1,10 @@
package org.dromara.common.excel.core; package org.dromara.common.excel.core;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fesod.sheet.context.AnalysisContext; import org.apache.fesod.sheet.context.AnalysisContext;
import org.apache.fesod.sheet.event.AnalysisEventListener; import org.apache.fesod.sheet.event.AnalysisEventListener;
import org.apache.fesod.sheet.exception.ExcelAnalysisException; import org.apache.fesod.sheet.exception.ExcelAnalysisException;
@@ -8,10 +12,6 @@ import org.apache.fesod.sheet.exception.ExcelDataConvertException;
import org.dromara.common.core.utils.StreamUtils; import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.ValidatorUtils; import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils; import org.dromara.common.json.utils.JsonUtils;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -59,7 +59,7 @@ public class DefaultExcelListener<T> extends AnalysisEventListener<T> implements
* 构造 Excel 导入监听器。 * 构造 Excel 导入监听器。
* *
* @param isValidate 是否执行 Validator 校验 * @param isValidate 是否执行 Validator 校验
* @param failFast 发生异常时是否立即终止读取 * @param failFast 发生异常时是否立即终止读取
*/ */
public DefaultExcelListener(boolean isValidate, boolean failFast) { public DefaultExcelListener(boolean isValidate, boolean failFast) {
this.isValidate = isValidate; this.isValidate = isValidate;
@@ -118,7 +118,7 @@ public class DefaultExcelListener<T> extends AnalysisEventListener<T> implements
/** /**
* 处理一行导入数据。 * 处理一行导入数据。
* *
* @param data 行数据 * @param data 行数据
* @param context 解析上下文 * @param context 解析上下文
*/ */
@Override @Override
@@ -37,7 +37,7 @@ public class DefaultExcelResult<T> implements ExcelResult<T> {
/** /**
* 创建导入结果。 * 创建导入结果。
* *
* @param list 成功数据列表 * @param list 成功数据列表
* @param errorList 错误信息列表 * @param errorList 错误信息列表
*/ */
public DefaultExcelResult(List<T> list, List<String> errorList) { public DefaultExcelResult(List<T> list, List<String> errorList) {
@@ -25,27 +25,35 @@ import java.util.stream.Collectors;
@NoArgsConstructor @NoArgsConstructor
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class DropDownOptions { public class DropDownOptions {
/** /**
* 一级下拉所在列index,从0开始算 * 一级下拉所在列index,从0开始算
*/ */
private int index = 0; private int index = 0;
/** /**
* 二级下拉所在的index,从0开始算,不能与一级相同 * 二级下拉所在的index,从0开始算,不能与一级相同
*/ */
private int nextIndex = 0; private int nextIndex = 0;
/** /**
* 一级下拉所包含的数据 * 一级下拉所包含的数据
*/ */
private List<String> options = new ArrayList<>(); private List<String> options = new ArrayList<>();
/** /**
* 二级下拉所包含的数据Map * 二级下拉所包含的数据Map
* <p>以每一个一级选项值为Key,每个一级选项对应的二级数据为Value</p> * <p>以每一个一级选项值为Key,每个一级选项对应的二级数据为Value</p>
*/ */
private Map<String, List<String>> nextOptions = new HashMap<>(); private Map<String, List<String>> nextOptions = new HashMap<>();
/** /**
* 分隔符 * 分隔符
*/ */
private static final String DELIMITER = "_"; private static final String DELIMITER = "_";
private static final String OPTION_PART_REGEX = "^[A-Za-z0-9\\u4e00-\\u9fa5]+$";
private static final String EXCEL_NAME_REGEX = "^[A-Za-z_\\u4e00-\\u9fa5][A-Za-z0-9_\\u4e00-\\u9fa5]*$";
private static final String CELL_REFERENCE_REGEX = "^[A-Za-z]{1,3}[1-9][0-9]*$";
/** /**
* 创建只有一级的下拉选 * 创建只有一级的下拉选
@@ -64,10 +72,9 @@ public class DropDownOptions {
*/ */
public static String createOptionValue(Object... vars) { public static String createOptionValue(Object... vars) {
StringBuilder stringBuffer = new StringBuilder(); StringBuilder stringBuffer = new StringBuilder();
String regex = "^[\\S\\d\\u4e00-\\u9fa5]+$";
for (int i = 0; i < vars.length; i++) { for (int i = 0; i < vars.length; i++) {
String var = StrUtil.trimToEmpty(Convert.toStr(vars[i])); String var = StrUtil.trimToEmpty(Convert.toStr(vars[i]));
if (!var.matches(regex)) { if (!var.matches(OPTION_PART_REGEX)) {
throw new ServiceException("选项数据不符合规则,仅允许使用中英文字符以及数字"); throw new ServiceException("选项数据不符合规则,仅允许使用中英文字符以及数字");
} }
stringBuffer.append(var); stringBuffer.append(var);
@@ -76,10 +83,29 @@ public class DropDownOptions {
stringBuffer.append(DELIMITER); stringBuffer.append(DELIMITER);
} }
} }
if (stringBuffer.toString().matches("^\\d_*$")) { String optionValue = stringBuffer.toString();
validateOptionValue(optionValue);
return optionValue;
}
/**
* 校验级联下拉选项值是否可作为 Excel 名称管理器名称。
*
* @param optionValue 选项值
*/
public static void validateOptionValue(String optionValue) {
if (StrUtil.isBlank(optionValue)) {
throw new ServiceException("选项数据不能为空");
}
if (optionValue.matches("^[0-9].*")) {
throw new ServiceException("禁止以数字开头"); throw new ServiceException("禁止以数字开头");
} }
return stringBuffer.toString(); if (!optionValue.matches(EXCEL_NAME_REGEX)) {
throw new ServiceException("选项数据不符合Excel名称规则,仅允许中英文、数字或下划线,且不能以数字开头");
}
if (optionValue.matches(CELL_REFERENCE_REGEX)) {
throw new ServiceException("选项数据不能为Excel单元格引用");
}
} }
/** /**
@@ -128,7 +154,7 @@ public class DropDownOptions {
sonList.forEach(everySon -> { sonList.forEach(everySon -> {
if (parentGroupByIdMap.containsKey(sonHowToGetParentIdFunction.apply(everySon))) { if (parentGroupByIdMap.containsKey(sonHowToGetParentIdFunction.apply(everySon))) {
// 找到对应的上级 // 找到对应的上级
T parentObj = parentGroupByIdMap.get(sonHowToGetParentIdFunction.apply(everySon)).get(0); T parentObj = parentGroupByIdMap.get(sonHowToGetParentIdFunction.apply(everySon)).getFirst();
// 提取名称和ID作为Key // 提取名称和ID作为Key
String key = howToBuildEveryOption.apply(parentObj); String key = howToBuildEveryOption.apply(parentObj);
// Key对应的Value // Key对应的Value
@@ -202,6 +202,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
if (CollUtil.isEmpty(firstOptions)) { if (CollUtil.isEmpty(firstOptions)) {
return; return;
} }
validateLinkedOptionNames(firstOptions);
Map<String, List<String>> secoundOptionsMap = new HashMap<>(); Map<String, List<String>> secoundOptionsMap = new HashMap<>();
options.getNextOptions().forEach((k, v) -> secoundOptionsMap.put(k, new ArrayList<>(v))); options.getNextOptions().forEach((k, v) -> secoundOptionsMap.put(k, new ArrayList<>(v)));
@@ -281,10 +282,10 @@ public class ExcelDownHandler implements SheetWriteHandler {
continue; continue;
} }
// 取第一个 // 取第一个
String str = data.get(0); String str = data.getFirst();
rowData.add(str); rowData.add(str);
// 通过移除的方式避免重复 // 通过移除的方式避免重复
data.remove(0); data.removeFirst();
// 设置可以继续 // 设置可以继续
flag = true; flag = true;
} }
@@ -313,6 +314,19 @@ public class ExcelDownHandler implements SheetWriteHandler {
currentLinkedOptionsSheetIndex++; currentLinkedOptionsSheetIndex++;
} }
/**
* 校验级联下拉一级选项可作为 Excel 名称,且不能重复。
*/
private void validateLinkedOptionNames(List<String> firstOptions) {
Set<String> names = new HashSet<>();
for (String firstOption : firstOptions) {
DropDownOptions.validateOptionValue(firstOption);
if (!names.add(firstOption)) {
throw new ServiceException("级联下拉一级选项重复:" + firstOption);
}
}
}
/** /**
* <h2>额外表格形式的普通下拉框</h2> * <h2>额外表格形式的普通下拉框</h2>
* 由于下拉框可选值数量过多,为提升Excel打开效率,使用额外表格形式做下拉 * 由于下拉框可选值数量过多,为提升Excel打开效率,使用额外表格形式做下拉
@@ -25,19 +25,10 @@ import org.apache.fesod.sheet.write.style.row.SimpleRowHeightStyleStrategy;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.FileUtils; import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.excel.convert.ExcelBigNumberConvert; import org.dromara.common.excel.convert.ExcelBigNumberConvert;
import org.dromara.common.excel.core.CellMergeStrategy; import org.dromara.common.excel.core.*;
import org.dromara.common.excel.core.DefaultExcelListener;
import org.dromara.common.excel.core.DropDownOptions;
import org.dromara.common.excel.core.ExcelDownHandler;
import org.dromara.common.excel.core.ExcelListener;
import org.dromara.common.excel.core.ExcelResult;
import org.dromara.common.excel.handler.DataWriteHandler; import org.dromara.common.excel.handler.DataWriteHandler;
import java.io.ByteArrayOutputStream; import java.io.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
@@ -173,7 +164,7 @@ public final class ExcelBuilder<T> {
/** /**
* 创建 Excel 导出构造器。 * 创建 Excel 导出构造器。
* *
* @param data 导出数据 * @param data 导出数据
* @param headType 表头类型 * @param headType 表头类型
*/ */
private ExcelBuilder(List<T> data, Class<T> headType) { private ExcelBuilder(List<T> data, Class<T> headType) {
@@ -580,7 +571,7 @@ public final class ExcelBuilder<T> {
/** /**
* 构建 ZIP 内单个 Excel 文件内容。 * 构建 ZIP 内单个 Excel 文件内容。
* *
* @param pageData 当前分页数据 * @param pageData 当前分页数据
* @param exportSheetName 导出工作表名称 * @param exportSheetName 导出工作表名称
* @return Excel 文件字节 * @return Excel 文件字节
*/ */
@@ -720,7 +711,7 @@ public final class ExcelBuilder<T> {
* 创建 Excel 导入读取构造器。 * 创建 Excel 导入读取构造器。
* *
* @param inputStream 输入流 * @param inputStream 输入流
* @param headType 表头类型 * @param headType 表头类型
*/ */
private ReadBuilder(InputStream inputStream, Class<T> headType) { private ReadBuilder(InputStream inputStream, Class<T> headType) {
this.inputStream = inputStream; this.inputStream = inputStream;
@@ -121,12 +121,10 @@ public record ExcelWriterWrapper<T>(ExcelWriter excelWriter) {
* @param excelWriter ExcelWriter * @param excelWriter ExcelWriter
* @return ExcelWriterWrapper * @return ExcelWriterWrapper
*/ */
public static <T> ExcelWriterWrapper<T> of(ExcelWriter excelWriter) { public static <T> ExcelWriterWrapper<T> of(ExcelWriter excelWriter) {
return new ExcelWriterWrapper<>(excelWriter); return new ExcelWriterWrapper<>(excelWriter);
} }
// -------------------------------- sheet start
/** /**
* 创建工作表 * 创建工作表
* *
@@ -207,10 +205,6 @@ public record ExcelWriterWrapper<T>(ExcelWriter excelWriter) {
return FesodSheet.writerSheet(); return FesodSheet.writerSheet();
} }
// -------------------------------- sheet end
// -------------------------------- table start
/** /**
* 创建表格 * 创建表格
* *
@@ -109,16 +109,20 @@ public class JsonValueEnhancer {
* @param visited 已访问对象集合用于避免循环引用 * @param visited 已访问对象集合用于避免循环引用
*/ */
private void collectValue(Object value, JsonEnhancementContext context, IdentityHashMap<Object, Boolean> visited) { private void collectValue(Object value, JsonEnhancementContext context, IdentityHashMap<Object, Boolean> visited) {
if (value == null) { switch (value) {
return; case null -> {
} return;
if (value instanceof Map<?, ?> map) { }
map.values().forEach(child -> collectValue(child, context, visited)); case Map<?, ?> map -> {
return; map.values().forEach(child -> collectValue(child, context, visited));
} return;
if (value instanceof Iterable<?> iterable) { }
iterable.forEach(child -> collectValue(child, context, visited)); case Iterable<?> iterable -> {
return; iterable.forEach(child -> collectValue(child, context, visited));
return;
}
default -> {
}
} }
if (value.getClass().isArray()) { if (value.getClass().isArray()) {
int length = Array.getLength(value); int length = Array.getLength(value);
@@ -354,7 +358,7 @@ public class JsonValueEnhancer {
* JSON 属性元数据 * JSON 属性元数据
* *
* @param propertyName 属性名称 * @param propertyName 属性名称
* @param member Jackson 属性成员 * @param member Jackson 属性成员
*/ */
private record PropertyMetadata(String propertyName, AnnotatedMember member) { private record PropertyMetadata(String propertyName, AnnotatedMember member) {
@@ -13,6 +13,10 @@ import tools.jackson.databind.ser.jdk.NumberSerializer;
@JacksonStdImpl @JacksonStdImpl
public class BigNumberSerializer extends NumberSerializer { public class BigNumberSerializer extends NumberSerializer {
/**
* 提供实例
*/
public static final BigNumberSerializer INSTANCE = new BigNumberSerializer(Number.class);
/** /**
* 根据 JS Number.MAX_SAFE_INTEGER Number.MIN_SAFE_INTEGER 得来 * 根据 JS Number.MAX_SAFE_INTEGER Number.MIN_SAFE_INTEGER 得来
*/ */
@@ -22,11 +26,6 @@ public class BigNumberSerializer extends NumberSerializer {
*/ */
private static final long MIN_SAFE_INTEGER = -9007199254740991L; private static final long MIN_SAFE_INTEGER = -9007199254740991L;
/**
* 提供实例
*/
public static final BigNumberSerializer INSTANCE = new BigNumberSerializer(Number.class);
/** /**
* 构造大数字序列化器 * 构造大数字序列化器
* *
@@ -9,9 +9,9 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch; import org.apache.commons.lang3.time.StopWatch;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.dromara.common.core.constant.SystemConstants; import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.utils.ServletUtils; import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.SpringUtils;
@@ -58,7 +58,7 @@ public class LogAspect {
/** /**
* 执行目标方法并记录操作日志 * 执行目标方法并记录操作日志
* *
* @param joinPoint 切点 * @param joinPoint 切点
* @param controllerLog 日志注解 * @param controllerLog 日志注解
* @return 目标方法返回值 * @return 目标方法返回值
* @throws Throwable 目标方法异常 * @throws Throwable 目标方法异常
@@ -80,11 +80,11 @@ public class LogAspect {
/** /**
* 组装并发布操作日志事件 * 组装并发布操作日志事件
* *
* @param joinPoint 切点 * @param joinPoint 切点
* @param controllerLog 日志注解 * @param controllerLog 日志注解
* @param e 异常信息 * @param e 异常信息
* @param jsonResult 返回结果 * @param jsonResult 返回结果
* @param stopWatch 耗时统计 * @param stopWatch 耗时统计
*/ */
protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult, StopWatch stopWatch) { protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult, StopWatch stopWatch) {
try { try {
@@ -138,9 +138,9 @@ public class LogAspect {
/** /**
* 获取注解中对方法的描述信息 用于Controller层注解 * 获取注解中对方法的描述信息 用于Controller层注解
* *
* @param joinPoint 切点 * @param joinPoint 切点
* @param log 日志 * @param log 日志
* @param operLog 操作日志 * @param operLog 操作日志
* @param jsonResult 返回结果 * @param jsonResult 返回结果
* @throws Exception 异常 * @throws Exception 异常
*/ */
@@ -165,8 +165,8 @@ public class LogAspect {
/** /**
* 获取请求的参数放到log中 * 获取请求的参数放到log中
* *
* @param joinPoint 切点 * @param joinPoint 切点
* @param operLog 操作日志 * @param operLog 操作日志
* @param excludeParamNames 排除参数名 * @param excludeParamNames 排除参数名
* @throws Exception 异常 * @throws Exception 异常
*/ */
@@ -186,7 +186,7 @@ public class LogAspect {
/** /**
* 将方法参数序列化为日志字符串 * 将方法参数序列化为日志字符串
* *
* @param paramsArray 参数数组 * @param paramsArray 参数数组
* @param excludeParamNames 排除字段名 * @param excludeParamNames 排除字段名
* @return 参数字符串 * @return 参数字符串
*/ */
@@ -207,7 +207,7 @@ public class LogAspect {
/** /**
* 序列化单个方法参数并移除排除字段 * 序列化单个方法参数并移除排除字段
* *
* @param arg 参数对象 * @param arg 参数对象
* @param exclude 排除字段名 * @param exclude 排除字段名
* @return 参数日志字符串 * @return 参数日志字符串
*/ */
@@ -234,7 +234,7 @@ public class LogAspect {
/** /**
* 将参数转为已排除指定字段的字典 * 将参数转为已排除指定字段的字典
* *
* @param value 参数值 * @param value 参数值
* @param exclude 排除字段名 * @param exclude 排除字段名
* @return 已过滤字段的字典 * @return 已过滤字段的字典
*/ */
@@ -250,7 +250,7 @@ public class LogAspect {
/** /**
* 限制日志字段长度 * 限制日志字段长度
* *
* @param value 原始字符串 * @param value 原始字符串
* @param maxLength 最大长度 * @param maxLength 最大长度
* @return 截断后的字符串 * @return 截断后的字符串
*/ */
@@ -303,6 +303,6 @@ public class LogAspect {
*/ */
private boolean isFilterValue(Object value) { private boolean isFilterValue(Object value) {
return value instanceof MultipartFile || value instanceof HttpServletRequest || value instanceof HttpServletResponse return value instanceof MultipartFile || value instanceof HttpServletRequest || value instanceof HttpServletResponse
|| value instanceof BindingResult; || value instanceof BindingResult;
} }
} }
@@ -1,8 +1,7 @@
package org.dromara.common.log.event; package org.dromara.common.log.event;
import lombok.Data;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import lombok.Data;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
@@ -18,7 +18,7 @@ public record McpResourceReadResult(
* MCP SDK 资源读取结果转换为带服务端名称的项目结果对象 * MCP SDK 资源读取结果转换为带服务端名称的项目结果对象
* *
* @param serverName MCP 服务端名称 * @param serverName MCP 服务端名称
* @param result MCP SDK 资源读取结果 * @param result MCP SDK 资源读取结果
* @return MCP 资源读取结果 * @return MCP 资源读取结果
*/ */
public static McpResourceReadResult of(String serverName, McpSchema.ReadResourceResult result) { public static McpResourceReadResult of(String serverName, McpSchema.ReadResourceResult result) {
@@ -20,7 +20,7 @@ public record McpToolCallResult(
* MCP SDK 工具调用结果转换为带服务端名称的项目结果对象 * MCP SDK 工具调用结果转换为带服务端名称的项目结果对象
* *
* @param serverName MCP 服务端名称 * @param serverName MCP 服务端名称
* @param result MCP SDK 工具调用结果 * @param result MCP SDK 工具调用结果
* @return MCP 工具调用结果 * @return MCP 工具调用结果
*/ */
public static McpToolCallResult of(String serverName, McpSchema.CallToolResult result) { public static McpToolCallResult of(String serverName, McpSchema.CallToolResult result) {
@@ -1,5 +1,8 @@
package org.dromara.common.mqtt.config; package org.dromara.common.mqtt.config;
import net.dreamlu.mica.net.utils.thread.ThreadUtils;
import net.dreamlu.mica.net.utils.thread.pool.SynThreadPoolExecutor;
import net.dreamlu.mica.net.utils.thread.pool.TioCallerRunsPolicy;
import org.dromara.common.mqtt.listener.MqttClientConnectListener; import org.dromara.common.mqtt.listener.MqttClientConnectListener;
import org.dromara.common.mqtt.listener.MqttClientGlobalMessageListener; import org.dromara.common.mqtt.listener.MqttClientGlobalMessageListener;
import org.dromara.mica.mqtt.core.client.MqttClientCreator; import org.dromara.mica.mqtt.core.client.MqttClientCreator;
@@ -8,9 +11,6 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.core.task.VirtualThreadTaskExecutor; import org.springframework.core.task.VirtualThreadTaskExecutor;
import org.tio.utils.thread.ThreadUtils;
import org.tio.utils.thread.pool.SynThreadPoolExecutor;
import org.tio.utils.thread.pool.TioCallerRunsPolicy;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
@@ -1,9 +1,9 @@
package org.dromara.common.mqtt.listener; package org.dromara.common.mqtt.listener;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.net.core.ChannelContext;
import org.dromara.mica.mqtt.core.client.IMqttClientConnectListener; import org.dromara.mica.mqtt.core.client.IMqttClientConnectListener;
import org.dromara.mica.mqtt.core.client.MqttClientCreator; import org.dromara.mica.mqtt.core.client.MqttClientCreator;
import org.tio.core.ChannelContext;
/** /**
* 客户端连接状态监听 * 客户端连接状态监听
@@ -1,9 +1,9 @@
package org.dromara.common.mqtt.listener; package org.dromara.common.mqtt.listener;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.net.core.ChannelContext;
import org.dromara.mica.mqtt.codec.message.MqttPublishMessage; import org.dromara.mica.mqtt.codec.message.MqttPublishMessage;
import org.dromara.mica.mqtt.core.client.IMqttClientGlobalMessageListener; import org.dromara.mica.mqtt.core.client.IMqttClientGlobalMessageListener;
import org.tio.core.ChannelContext;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@@ -19,7 +19,7 @@ public class MqttClientGlobalMessageListener implements IMqttClientGlobalMessage
* 接收并记录全局 MQTT 消息 * 接收并记录全局 MQTT 消息
* *
* @param context 通道上下文 * @param context 通道上下文
* @param topic 主题 * @param topic 主题
* @param message MQTT 消息对象 * @param message MQTT 消息对象
* @param payload 消息载荷 * @param payload 消息载荷
*/ */
@@ -27,7 +27,7 @@ public class DataPermissionPointcutAdvisor extends AbstractPointcutAdvisor {
*/ */
public DataPermissionPointcutAdvisor() { public DataPermissionPointcutAdvisor() {
this.advice = new DataPermissionAdvice(); this.advice = new DataPermissionAdvice();
this.pointcut = new DataPermissionPointcut(); this.pointcut = new DataPermissionPointcut();
} }
/** /**
@@ -122,6 +122,7 @@ public class LambdaCrudChainWrapper<T, V> extends AbstractLambdaWrapper<T, Lambd
* @param columns 查询字段 * @param columns 查询字段
* @return this * @return this
*/ */
@Override
@SafeVarargs @SafeVarargs
public final LambdaCrudChainWrapper<T, V> select(SFunction<T, ?>... columns) { public final LambdaCrudChainWrapper<T, V> select(SFunction<T, ?>... columns) {
return select(true, CollectionUtils.toList(columns)); return select(true, CollectionUtils.toList(columns));
@@ -134,6 +135,7 @@ public class LambdaCrudChainWrapper<T, V> extends AbstractLambdaWrapper<T, Lambd
* @param columns 查询字段 * @param columns 查询字段
* @return this * @return this
*/ */
@Override
@SafeVarargs @SafeVarargs
public final LambdaCrudChainWrapper<T, V> select(boolean condition, SFunction<T, ?>... columns) { public final LambdaCrudChainWrapper<T, V> select(boolean condition, SFunction<T, ?>... columns) {
return select(condition, CollectionUtils.toList(columns)); return select(condition, CollectionUtils.toList(columns));
@@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.support.LambdaMeta; import com.baomidou.mybatisplus.core.toolkit.support.LambdaMeta;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.ibatis.reflection.property.PropertyNamer; import org.apache.ibatis.reflection.property.PropertyNamer;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -14,6 +16,7 @@ import java.util.regex.Pattern;
* *
* @author Lion Li * @author Lion Li
*/ */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class AggregateSelectUtils { public final class AggregateSelectUtils {
/** /**
@@ -21,12 +24,6 @@ public final class AggregateSelectUtils {
*/ */
private static final Pattern ALIAS_PATTERN = Pattern.compile("[A-Za-z_][A-Za-z0-9_]*"); private static final Pattern ALIAS_PATTERN = Pattern.compile("[A-Za-z_][A-Za-z0-9_]*");
/**
* 工具类不允许实例化
*/
private AggregateSelectUtils() {
}
/** /**
* 追加查询字段 SQL * 追加查询字段 SQL
* *
@@ -400,6 +400,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param value 条件值 * @param value 条件值
* @return 当前查询构造辅助对象 * @return 当前查询构造辅助对象
*/ */
@Override
public LambdaQueryBuilder<T> eq(boolean condition, SFunction<T, ?> column, Object value) { public LambdaQueryBuilder<T> eq(boolean condition, SFunction<T, ?> column, Object value) {
wrapper.eq(condition, column, value); wrapper.eq(condition, column, value);
return this; return this;
@@ -425,6 +426,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param value 条件值 * @param value 条件值
* @return 当前查询构造辅助对象 * @return 当前查询构造辅助对象
*/ */
@Override
public LambdaQueryBuilder<T> ne(boolean condition, SFunction<T, ?> column, Object value) { public LambdaQueryBuilder<T> ne(boolean condition, SFunction<T, ?> column, Object value) {
wrapper.ne(condition, column, value); wrapper.ne(condition, column, value);
return this; return this;
@@ -450,6 +452,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param value 条件值 * @param value 条件值
* @return 当前查询构造辅助对象 * @return 当前查询构造辅助对象
*/ */
@Override
public LambdaQueryBuilder<T> gt(boolean condition, SFunction<T, ?> column, Object value) { public LambdaQueryBuilder<T> gt(boolean condition, SFunction<T, ?> column, Object value) {
wrapper.gt(condition, column, value); wrapper.gt(condition, column, value);
return this; return this;
@@ -475,6 +478,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param value 条件值 * @param value 条件值
* @return 当前查询构造辅助对象 * @return 当前查询构造辅助对象
*/ */
@Override
public LambdaQueryBuilder<T> ge(boolean condition, SFunction<T, ?> column, Object value) { public LambdaQueryBuilder<T> ge(boolean condition, SFunction<T, ?> column, Object value) {
wrapper.ge(condition, column, value); wrapper.ge(condition, column, value);
return this; return this;
@@ -500,6 +504,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param value 条件值 * @param value 条件值
* @return 当前查询构造辅助对象 * @return 当前查询构造辅助对象
*/ */
@Override
public LambdaQueryBuilder<T> lt(boolean condition, SFunction<T, ?> column, Object value) { public LambdaQueryBuilder<T> lt(boolean condition, SFunction<T, ?> column, Object value) {
wrapper.lt(condition, column, value); wrapper.lt(condition, column, value);
return this; return this;
@@ -525,6 +530,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param value 条件值 * @param value 条件值
* @return 当前查询构造辅助对象 * @return 当前查询构造辅助对象
*/ */
@Override
public LambdaQueryBuilder<T> le(boolean condition, SFunction<T, ?> column, Object value) { public LambdaQueryBuilder<T> le(boolean condition, SFunction<T, ?> column, Object value) {
wrapper.le(condition, column, value); wrapper.le(condition, column, value);
return this; return this;
@@ -550,6 +556,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param value 条件值 * @param value 条件值
* @return 当前查询构造辅助对象 * @return 当前查询构造辅助对象
*/ */
@Override
public LambdaQueryBuilder<T> like(boolean condition, SFunction<T, ?> column, Object value) { public LambdaQueryBuilder<T> like(boolean condition, SFunction<T, ?> column, Object value) {
wrapper.like(condition, column, value); wrapper.like(condition, column, value);
return this; return this;
@@ -575,6 +582,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param value 条件值 * @param value 条件值
* @return 当前查询构造辅助对象 * @return 当前查询构造辅助对象
*/ */
@Override
public LambdaQueryBuilder<T> notLike(boolean condition, SFunction<T, ?> column, Object value) { public LambdaQueryBuilder<T> notLike(boolean condition, SFunction<T, ?> column, Object value) {
wrapper.notLike(condition, column, value); wrapper.notLike(condition, column, value);
return this; return this;
@@ -600,6 +608,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param value 条件值 * @param value 条件值
* @return 当前查询构造辅助对象 * @return 当前查询构造辅助对象
*/ */
@Override
public LambdaQueryBuilder<T> likeLeft(boolean condition, SFunction<T, ?> column, Object value) { public LambdaQueryBuilder<T> likeLeft(boolean condition, SFunction<T, ?> column, Object value) {
wrapper.likeLeft(condition, column, value); wrapper.likeLeft(condition, column, value);
return this; return this;
@@ -625,6 +634,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param value 条件值 * @param value 条件值
* @return 当前查询构造辅助对象 * @return 当前查询构造辅助对象
*/ */
@Override
public LambdaQueryBuilder<T> likeRight(boolean condition, SFunction<T, ?> column, Object value) { public LambdaQueryBuilder<T> likeRight(boolean condition, SFunction<T, ?> column, Object value) {
wrapper.likeRight(condition, column, value); wrapper.likeRight(condition, column, value);
return this; return this;
@@ -652,6 +662,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param end 结束值 * @param end 结束值
* @return 当前查询构造辅助对象 * @return 当前查询构造辅助对象
*/ */
@Override
public LambdaQueryBuilder<T> between(boolean condition, SFunction<T, ?> column, Object begin, Object end) { public LambdaQueryBuilder<T> between(boolean condition, SFunction<T, ?> column, Object begin, Object end) {
wrapper.between(condition, column, begin, end); wrapper.between(condition, column, begin, end);
return this; return this;
@@ -679,6 +690,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param end 结束值 * @param end 结束值
* @return 当前查询构造辅助对象 * @return 当前查询构造辅助对象
*/ */
@Override
public LambdaQueryBuilder<T> notBetween(boolean condition, SFunction<T, ?> column, Object begin, Object end) { public LambdaQueryBuilder<T> notBetween(boolean condition, SFunction<T, ?> column, Object begin, Object end) {
wrapper.notBetween(condition, column, begin, end); wrapper.notBetween(condition, column, begin, end);
return this; return this;
@@ -718,7 +730,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @return 当前查询构造辅助对象 * @return 当前查询构造辅助对象
*/ */
public LambdaQueryBuilder<T> allEq(BiPredicate<SFunction<T, ?>, Object> filter, Map<?, ?> params, boolean null2IsNull) { public LambdaQueryBuilder<T> allEq(BiPredicate<SFunction<T, ?>, Object> filter, Map<?, ?> params, boolean null2IsNull) {
wrapper.allEq(true, (BiPredicate) filter, (Map) params, null2IsNull); wrapper.allEq(true, filter, (Map) params, null2IsNull);
return this; return this;
} }
@@ -732,7 +744,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @return 当前查询构造辅助对象 * @return 当前查询构造辅助对象
*/ */
public LambdaQueryBuilder<T> allEq(boolean condition, BiPredicate<SFunction<T, ?>, Object> filter, Map<?, ?> params, boolean null2IsNull) { public LambdaQueryBuilder<T> allEq(boolean condition, BiPredicate<SFunction<T, ?>, Object> filter, Map<?, ?> params, boolean null2IsNull) {
wrapper.allEq(condition, (BiPredicate) filter, (Map) params, null2IsNull); wrapper.allEq(condition, filter, (Map) params, null2IsNull);
return this; return this;
} }
@@ -802,6 +814,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param values 条件值集合 * @param values 条件值集合
* @return 当前查询构造辅助对象 * @return 当前查询构造辅助对象
*/ */
@Override
public LambdaQueryBuilder<T> in(boolean condition, SFunction<T, ?> column, Collection<?> values) { public LambdaQueryBuilder<T> in(boolean condition, SFunction<T, ?> column, Collection<?> values) {
wrapper.in(condition, column, values); wrapper.in(condition, column, values);
return this; return this;
@@ -827,6 +840,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param values 条件值数组 * @param values 条件值数组
* @return 当前查询构造辅助对象 * @return 当前查询构造辅助对象
*/ */
@Override
public LambdaQueryBuilder<T> in(boolean condition, SFunction<T, ?> column, Object... values) { public LambdaQueryBuilder<T> in(boolean condition, SFunction<T, ?> column, Object... values) {
wrapper.in(condition, column, values); wrapper.in(condition, column, values);
return this; return this;
@@ -852,6 +866,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param values 条件值集合 * @param values 条件值集合
* @return 当前查询构造辅助对象 * @return 当前查询构造辅助对象
*/ */
@Override
public LambdaQueryBuilder<T> notIn(boolean condition, SFunction<T, ?> column, Collection<?> values) { public LambdaQueryBuilder<T> notIn(boolean condition, SFunction<T, ?> column, Collection<?> values) {
wrapper.notIn(condition, column, values); wrapper.notIn(condition, column, values);
return this; return this;
@@ -877,6 +892,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param values 条件值数组 * @param values 条件值数组
* @return 当前查询构造辅助对象 * @return 当前查询构造辅助对象
*/ */
@Override
public LambdaQueryBuilder<T> notIn(boolean condition, SFunction<T, ?> column, Object... values) { public LambdaQueryBuilder<T> notIn(boolean condition, SFunction<T, ?> column, Object... values) {
wrapper.notIn(condition, column, values); wrapper.notIn(condition, column, values);
return this; return this;
@@ -1219,6 +1235,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param values SQL 片段参数 * @param values SQL 片段参数
* @return 当前查询构造辅助对象 * @return 当前查询构造辅助对象
*/ */
@Override
public LambdaQueryBuilder<T> apply(boolean condition, String applySql, Object... values) { public LambdaQueryBuilder<T> apply(boolean condition, String applySql, Object... values) {
wrapper.apply(condition, applySql, values); wrapper.apply(condition, applySql, values);
return this; return this;
@@ -1368,7 +1385,6 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* *
* @return 聚合查询包装器 * @return 聚合查询包装器
*/ */
@SuppressWarnings("unchecked")
private AggregateLambdaQueryWrapper<T> aggregateWrapper() { private AggregateLambdaQueryWrapper<T> aggregateWrapper() {
return (AggregateLambdaQueryWrapper<T>) wrapper; return (AggregateLambdaQueryWrapper<T>) wrapper;
} }
@@ -2,22 +2,13 @@ package org.dromara.common.mybatis.core.query;
import com.baomidou.mybatisplus.core.metadata.TableInfo; import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.Assert; import com.baomidou.mybatisplus.core.toolkit.*;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.support.ColumnCache; import com.baomidou.mybatisplus.core.toolkit.support.ColumnCache;
import com.baomidou.mybatisplus.core.toolkit.support.LambdaMeta; import com.baomidou.mybatisplus.core.toolkit.support.LambdaMeta;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import org.apache.ibatis.reflection.property.PropertyNamer; import org.apache.ibatis.reflection.property.PropertyNamer;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -23,7 +23,7 @@ public class MybatisExceptionHandler {
/** /**
* 处理主键或唯一索引冲突异常 * 处理主键或唯一索引冲突异常
* *
* @param e 异常信息 * @param e 异常信息
* @param request 当前请求 * @param request 当前请求
* @return 统一失败响应 * @return 统一失败响应
*/ */
@@ -37,7 +37,7 @@ public class MybatisExceptionHandler {
/** /**
* 处理 MyBatis 系统异常 * 处理 MyBatis 系统异常
* *
* @param e 异常信息 * @param e 异常信息
* @param request 当前请求 * @param request 当前请求
* @return 统一失败响应 * @return 统一失败响应
*/ */
@@ -65,8 +65,8 @@ public class PlusDataPermissionHandler {
/** /**
* 获取数据过滤条件的 SQL 片段 * 获取数据过滤条件的 SQL 片段
* *
* @param where 原始的查询条件表达式 * @param where 原始的查询条件表达式
* @param isSelect 是否为查询语句 * @param isSelect 是否为查询语句
* @return 数据过滤条件的 SQL 片段 * @return 数据过滤条件的 SQL 片段
*/ */
public Expression getSqlSegment(Expression where, boolean isSelect) { public Expression getSqlSegment(Expression where, boolean isSelect) {
@@ -112,7 +112,7 @@ public class PlusDataPermissionHandler {
} }
Object defaultValue = "-1"; Object defaultValue = "-1";
NullSafeStandardEvaluationContext context = new NullSafeStandardEvaluationContext(defaultValue); NullSafeStandardEvaluationContext context = new NullSafeStandardEvaluationContext(defaultValue);
context.addPropertyAccessor(new NullSafePropertyAccessor(context.getPropertyAccessors().get(0), defaultValue)); context.addPropertyAccessor(new NullSafePropertyAccessor(context.getPropertyAccessors().getFirst(), defaultValue));
context.setBeanResolver(beanResolver); context.setBeanResolver(beanResolver);
DataPermissionHelper.getContext().forEach(context::setVariable); DataPermissionHelper.getContext().forEach(context::setVariable);
Set<String> conditions = new HashSet<>(); Set<String> conditions = new HashSet<>();
@@ -283,7 +283,7 @@ public class PlusDataPermissionHandler {
/** /**
* 优先从方法再从类上查找指定注解 * 优先从方法再从类上查找指定注解
* *
* @param handlerMethod 当前请求处理方法 * @param handlerMethod 当前请求处理方法
* @param annotationType 注解类型 * @param annotationType 注解类型
* @param <A> 注解类型 * @param <A> 注解类型
* @return 注解对象未配置时返回 null * @return 注解对象未配置时返回 null
@@ -35,10 +35,7 @@ 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.time.ZoneOffset; import java.time.ZoneOffset;
import java.util.Collection; import java.util.*;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@@ -203,7 +200,7 @@ public abstract class AbstractOssClientImpl implements OssClient {
.orElse(""); .orElse("");
String mergedPrefix = mergePrefix(defaultPrefix, businessPrefix); String mergedPrefix = mergePrefix(defaultPrefix, businessPrefix);
String suffix = suffix(fileName); String suffix = suffix(fileName);
String datePath = DateUtils.datePath(); String datePath = DateUtils.format(new Date(), "yyyy/MM/dd");
String uuid = IdUtil.fastSimpleUUID(); String uuid = IdUtil.fastSimpleUUID();
String path = mergedPrefix.isEmpty() ? datePath + StringUtils.SLASH + uuid : mergedPrefix + StringUtils.SLASH + datePath + StringUtils.SLASH + uuid; String path = mergedPrefix.isEmpty() ? datePath + StringUtils.SLASH + uuid : mergedPrefix + StringUtils.SLASH + datePath + StringUtils.SLASH + uuid;
return path + suffix; return path + suffix;
@@ -29,7 +29,7 @@ public class DefaultOssClientImpl extends AbstractOssClientImpl {
* 构造默认 S3 存储客户端 * 构造默认 S3 存储客户端
* *
* @param clientId 客户端 ID * @param clientId 客户端 ID
* @param config S3 存储客户端配置 * @param config S3 存储客户端配置
*/ */
public DefaultOssClientImpl(String clientId, OssClientConfig config) { public DefaultOssClientImpl(String clientId, OssClientConfig config) {
super(clientId, config); super(clientId, config);
@@ -133,9 +133,9 @@ public interface OssClient extends AutoCloseable {
/** /**
* 将本地路径对应的文件上传到指定存储桶 * 将本地路径对应的文件上传到指定存储桶
* *
* @param bucket 存储桶名称 * @param bucket 存储桶名称
* @param key 对象键 * @param key 对象键
* @param path 文件路径 * @param path 文件路径
* @param options 可选项 * @param options 可选项
* @return 上传结果 * @return 上传结果
*/ */
@@ -154,9 +154,9 @@ public interface OssClient extends AutoCloseable {
/** /**
* 将文件上传到指定存储桶 * 将文件上传到指定存储桶
* *
* @param bucket 存储桶名称 * @param bucket 存储桶名称
* @param key 对象键 * @param key 对象键
* @param file 文件对象 * @param file 文件对象
* @param options 可选项 * @param options 可选项
* @return 上传结果 * @return 上传结果
*/ */
@@ -175,9 +175,9 @@ public interface OssClient extends AutoCloseable {
/** /**
* 将随机访问文件上传到指定存储桶 * 将随机访问文件上传到指定存储桶
* *
* @param bucket 存储桶名称 * @param bucket 存储桶名称
* @param key 对象键 * @param key 对象键
* @param file 随机访问文件 * @param file 随机访问文件
* @param options 可选项 * @param options 可选项
* @return 上传结果 * @return 上传结果
*/ */
@@ -200,7 +200,7 @@ public interface OssClient extends AutoCloseable {
* @param key 对象键 * @param key 对象键
* @param channel 数据通道 * @param channel 数据通道
* @param contentLength 内容长度 * @param contentLength 内容长度
* @param options 可选项 * @param options 可选项
* @return 上传结果 * @return 上传结果
*/ */
PutObjectResult bucketUpload(String bucket, String key, ReadableByteChannel channel, long contentLength, Options options); PutObjectResult bucketUpload(String bucket, String key, ReadableByteChannel channel, long contentLength, Options options);
@@ -223,7 +223,7 @@ public interface OssClient extends AutoCloseable {
* @param key 对象键 * @param key 对象键
* @param in 输入流 * @param in 输入流
* @param contentLength 内容长度 * @param contentLength 内容长度
* @param options 可选项 * @param options 可选项
* @return 上传结果 * @return 上传结果
*/ */
PutObjectResult bucketUpload(String bucket, String key, InputStream in, long contentLength, Options options); PutObjectResult bucketUpload(String bucket, String key, InputStream in, long contentLength, Options options);
@@ -242,9 +242,9 @@ public interface OssClient extends AutoCloseable {
/** /**
* 将字节数组上传到指定存储桶 * 将字节数组上传到指定存储桶
* *
* @param bucket 存储桶名称 * @param bucket 存储桶名称
* @param key 对象键 * @param key 对象键
* @param data 字节数组 * @param data 字节数组
* @param options 可选项 * @param options 可选项
* @return 上传结果 * @return 上传结果
*/ */
@@ -374,8 +374,8 @@ public interface OssClient extends AutoCloseable {
/** /**
* 将本地路径对应的文件上传到默认存储桶 * 将本地路径对应的文件上传到默认存储桶
* *
* @param key 对象键 * @param key 对象键
* @param path 文件路径 * @param path 文件路径
* @param options 可选项 * @param options 可选项
* @return 上传结果 * @return 上传结果
*/ */
@@ -393,8 +393,8 @@ public interface OssClient extends AutoCloseable {
/** /**
* 将文件上传到默认存储桶 * 将文件上传到默认存储桶
* *
* @param key 对象键 * @param key 对象键
* @param file 文件对象 * @param file 文件对象
* @param options 可选项 * @param options 可选项
* @return 上传结果 * @return 上传结果
*/ */
@@ -412,8 +412,8 @@ public interface OssClient extends AutoCloseable {
/** /**
* 将随机访问文件上传到默认存储桶 * 将随机访问文件上传到默认存储桶
* *
* @param key 对象键 * @param key 对象键
* @param file 随机访问文件 * @param file 随机访问文件
* @param options 可选项 * @param options 可选项
* @return 上传结果 * @return 上传结果
*/ */
@@ -431,10 +431,10 @@ public interface OssClient extends AutoCloseable {
/** /**
* 将可读通道中的数据上传到默认存储桶 * 将可读通道中的数据上传到默认存储桶
* *
* @param key 对象键 * @param key 对象键
* @param channel 数据通道 * @param channel 数据通道
* @param contentLength 内容长度 * @param contentLength 内容长度
* @param options 可选项 * @param options 可选项
* @return 上传结果 * @return 上传结果
*/ */
PutObjectResult upload(String key, ReadableByteChannel channel, long contentLength, Options options); PutObjectResult upload(String key, ReadableByteChannel channel, long contentLength, Options options);
@@ -452,10 +452,10 @@ public interface OssClient extends AutoCloseable {
/** /**
* 将输入流中的数据上传到默认存储桶 * 将输入流中的数据上传到默认存储桶
* *
* @param key 对象键 * @param key 对象键
* @param in 输入流 * @param in 输入流
* @param contentLength 内容长度 * @param contentLength 内容长度
* @param options 可选项 * @param options 可选项
* @return 上传结果 * @return 上传结果
*/ */
PutObjectResult upload(String key, InputStream in, long contentLength, Options options); PutObjectResult upload(String key, InputStream in, long contentLength, Options options);
@@ -473,8 +473,8 @@ public interface OssClient extends AutoCloseable {
/** /**
* 将字节数组上传到默认存储桶 * 将字节数组上传到默认存储桶
* *
* @param key 对象键 * @param key 对象键
* @param data 字节数组 * @param data 字节数组
* @param options 可选项 * @param options 可选项
* @return 上传结果 * @return 上传结果
*/ */
@@ -5,19 +5,20 @@ package org.dromara.common.oss.config;
* *
* @param <T> 配置类型 * @param <T> 配置类型
* @param <B> 配置构建器类型 * @param <B> 配置构建器类型
*
* @author 秋辞未寒 * @author 秋辞未寒
*/ */
public interface Config<T,B> { public interface Config<T, B> {
/** /**
* 配置对象拷贝 * 配置对象拷贝
*
* @return 拷贝后的新配置对象 * @return 拷贝后的新配置对象
*/ */
T copy(); T copy();
/** /**
* 转为构建器对象 * 转为构建器对象
*
* @return 构建器对象 * @return 构建器对象
*/ */
B toBuilder(); B toBuilder();
@@ -35,6 +35,6 @@ public interface OssConstant {
/** /**
* 云服务商 * 云服务商
*/ */
String[] CLOUD_SERVICE = new String[] {"aliyun", "qcloud", "qiniu", "obs"}; String[] CLOUD_SERVICE = new String[]{"aliyun", "qcloud", "qiniu", "obs"};
} }
@@ -20,17 +20,17 @@ public enum AccessPolicy {
/** /**
* 私有 * 私有
*/ */
PRIVATE(0,BucketCannedACL.PRIVATE, ObjectCannedACL.PRIVATE), PRIVATE(0, BucketCannedACL.PRIVATE, ObjectCannedACL.PRIVATE),
/** /**
* 公有读写 * 公有读写
*/ */
PUBLIC_READ_WRITE(1,BucketCannedACL.PUBLIC_READ_WRITE, ObjectCannedACL.PUBLIC_READ_WRITE), PUBLIC_READ_WRITE(1, BucketCannedACL.PUBLIC_READ_WRITE, ObjectCannedACL.PUBLIC_READ_WRITE),
/** /**
* 公有只读 * 公有只读
*/ */
PUBLIC_READ(2,BucketCannedACL.PUBLIC_READ, ObjectCannedACL.PUBLIC_READ); PUBLIC_READ(2, BucketCannedACL.PUBLIC_READ, ObjectCannedACL.PUBLIC_READ);
/** /**
* 访问策略类型 * 访问策略类型
@@ -19,36 +19,36 @@ import java.util.Map;
* @author 秋辞未寒 * @author 秋辞未寒
*/ */
public record GetObjectResult( public record GetObjectResult(
String key, String key,
String eTag, String eTag,
LocalDateTime lastModified, LocalDateTime lastModified,
long size, long size,
String contentType, String contentType,
String contentDisposition, String contentDisposition,
String contentRange, String contentRange,
String contentEncoding, String contentEncoding,
String contentLanguage, String contentLanguage,
Map<String, String> metadata Map<String, String> metadata
) { ) {
/** /**
* 构建文件对象获取结果 * 构建文件对象获取结果
* *
* @param key 对象 Key * @param key 对象 Key
* @param eTag 对象 ETag * @param eTag 对象 ETag
* @param lastModified 最后修改时间 * @param lastModified 最后修改时间
* @param size 对象大小 * @param size 对象大小
* @param contentType 内容类型 * @param contentType 内容类型
* @param contentDisposition 内容处置方式 * @param contentDisposition 内容处置方式
* @param contentRange 内容范围 * @param contentRange 内容范围
* @param contentEncoding 内容编码 * @param contentEncoding 内容编码
* @param contentLanguage 内容语言 * @param contentLanguage 内容语言
* @param metadata 自定义元数据 * @param metadata 自定义元数据
* @return 文件对象获取结果 * @return 文件对象获取结果
*/ */
public static GetObjectResult form(String key, String eTag, LocalDateTime lastModified, long size public static GetObjectResult form(String key, String eTag, LocalDateTime lastModified, long size
, String contentType, String contentDisposition, String contentRange, String contentEncoding, String contentLanguage , String contentType, String contentDisposition, String contentRange, String contentEncoding, String contentLanguage
, Map<String, String> metadata) { , Map<String, String> metadata) {
return new GetObjectResult(key, eTag, lastModified, size, contentType, contentDisposition, contentRange, contentEncoding, contentLanguage, metadata); return new GetObjectResult(key, eTag, lastModified, size, contentType, contentDisposition, contentRange, contentEncoding, contentLanguage, metadata);
} }
@@ -11,8 +11,8 @@ import java.util.Optional;
* @author 秋辞未寒 * @author 秋辞未寒
*/ */
public record HandleAsyncResult<T>( public record HandleAsyncResult<T>(
T result, T result,
Throwable error Throwable error
) { ) {
/** /**
@@ -60,7 +60,7 @@ public record HandleAsyncResult<T>(
* @return 异步处理结果 * @return 异步处理结果
*/ */
public static <T> HandleAsyncResult<T> of(T result, Throwable error) { public static <T> HandleAsyncResult<T> of(T result, Throwable error) {
return new HandleAsyncResult<T>(result, error); return new HandleAsyncResult<>(result, error);
} }
/** /**
@@ -71,7 +71,7 @@ public record HandleAsyncResult<T>(
* @return 异步处理结果 * @return 异步处理结果
*/ */
public static <T> HandleAsyncResult<T> success(T result) { public static <T> HandleAsyncResult<T> success(T result) {
return new HandleAsyncResult<T>(result, null); return new HandleAsyncResult<>(result, null);
} }
/** /**
@@ -82,6 +82,6 @@ public record HandleAsyncResult<T>(
* @return 异步处理结果 * @return 异步处理结果
*/ */
public static <T> HandleAsyncResult<T> failure(Throwable error) { public static <T> HandleAsyncResult<T> failure(Throwable error) {
return new HandleAsyncResult<T>(null, error); return new HandleAsyncResult<>(null, error);
} }
} }
@@ -10,10 +10,10 @@ package org.dromara.common.oss.model;
* @author 秋辞未寒 * @author 秋辞未寒
*/ */
public record PutObjectResult( public record PutObjectResult(
String url, String url,
String key, String key,
String eTag, String eTag,
long size long size
) { ) {
/** /**
@@ -3,11 +3,7 @@ package org.dromara.common.push.annotation;
import org.dromara.common.push.condition.MessageTransportCondition; import org.dromara.common.push.condition.MessageTransportCondition;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
import java.lang.annotation.Documented; import java.lang.annotation.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** /**
* 按消息推送传输方式启用组件 * 按消息推送传输方式启用组件
@@ -19,7 +19,7 @@ public class MessageTransportCondition implements Condition {
/** /**
* 判断当前消息推送配置是否匹配注解声明的传输方式 * 判断当前消息推送配置是否匹配注解声明的传输方式
* *
* @param context 条件上下文 * @param context 条件上下文
* @param metadata 注解元数据 * @param metadata 注解元数据
* @return 是否匹配 * @return 是否匹配
*/ */
@@ -1,10 +1,10 @@
package org.dromara.common.push.config; package org.dromara.common.push.config;
import org.dromara.common.push.annotation.ConditionalOnMessageTransport; import org.dromara.common.push.annotation.ConditionalOnMessageTransport;
import org.dromara.common.push.listener.MessageTopicListener;
import org.dromara.common.push.core.WebSocketSessionManager; import org.dromara.common.push.core.WebSocketSessionManager;
import org.dromara.common.push.handler.PlusWebSocketHandler; import org.dromara.common.push.handler.PlusWebSocketHandler;
import org.dromara.common.push.interceptor.PlusWebSocketInterceptor; import org.dromara.common.push.interceptor.PlusWebSocketInterceptor;
import org.dromara.common.push.listener.MessageTopicListener;
import org.dromara.common.push.properties.MessageProperties; import org.dromara.common.push.properties.MessageProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@@ -37,7 +37,7 @@ public class SseEmitterSessionManager implements PushSessionManager {
* 构造 SSE 会话管理器并启动心跳检测 * 构造 SSE 会话管理器并启动心跳检测
* *
* @param scheduledExecutorService 定时任务线程池 * @param scheduledExecutorService 定时任务线程池
* @param messageProperties 消息推送配置 * @param messageProperties 消息推送配置
*/ */
public SseEmitterSessionManager(ScheduledExecutorService scheduledExecutorService, MessageProperties messageProperties) { public SseEmitterSessionManager(ScheduledExecutorService scheduledExecutorService, MessageProperties messageProperties) {
this.messageProperties = messageProperties; this.messageProperties = messageProperties;
@@ -218,7 +218,7 @@ public class SseEmitterSessionManager implements PushSessionManager {
/** /**
* 向指定用户的全部本地 SSE 会话发送统一 JSON 消息 * 向指定用户的全部本地 SSE 会话发送统一 JSON 消息
* *
* @param userId 要发送消息的用户id * @param userId 要发送消息的用户id
* @param pushDTO 要发送的消息内容 * @param pushDTO 要发送的消息内容
*/ */
public void sendMessage(Long userId, PushDTO pushDTO) { public void sendMessage(Long userId, PushDTO pushDTO) {
@@ -53,7 +53,7 @@ public class RateLimiterAspect {
/** /**
* 在限流注解方法执行前扣减令牌令牌不足时阻断请求 * 在限流注解方法执行前扣减令牌令牌不足时阻断请求
* *
* @param point 切点信息 * @param point 切点信息
* @param rateLimiter 限流注解配置 * @param rateLimiter 限流注解配置
*/ */
@Before("@annotation(rateLimiter)") @Before("@annotation(rateLimiter)")
@@ -89,7 +89,7 @@ public class RateLimiterAspect {
* 组装限流缓存键 * 组装限流缓存键
* *
* @param rateLimiter 限流注解配置 * @param rateLimiter 限流注解配置
* @param point 切点信息 * @param point 切点信息
* @return 限流缓存键 * @return 限流缓存键
*/ */
private String getCombineKey(RateLimiter rateLimiter, JoinPoint point) { private String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
@@ -112,13 +112,13 @@ public class RateLimiterAspect {
key = expression.getValue(context, String.class); key = expression.getValue(context, String.class);
} }
StringBuilder stringBuffer = new StringBuilder(GlobalConstants.RATE_LIMIT_KEY); StringBuilder stringBuffer = new StringBuilder(GlobalConstants.RATE_LIMIT_KEY);
stringBuffer.append(ServletUtils.getRequest().getRequestURI()).append(":"); stringBuffer.append(ServletUtils.getRequest().getRequestURI()).append(StringUtils.COLON);
if (rateLimiter.limitType() == LimitType.IP) { if (rateLimiter.limitType() == LimitType.IP) {
// 获取请求ip // 获取请求ip
stringBuffer.append(ServletUtils.getClientIP()).append(":"); stringBuffer.append(ServletUtils.getClientIP()).append(StringUtils.COLON);
} else if (rateLimiter.limitType() == LimitType.CLUSTER) { } else if (rateLimiter.limitType() == LimitType.CLUSTER) {
// 获取客户端实例id // 获取客户端实例id
stringBuffer.append(RedisUtils.getClient().getId()).append(":"); stringBuffer.append(RedisUtils.getClient().getId()).append(StringUtils.COLON);
} }
return stringBuffer.append(key).toString(); return stringBuffer.append(key).toString();
} }

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