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
.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)
>
官方前端项目地址: [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>
> 成员前端项目地址: 基于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>
@@ -123,7 +122,7 @@ Topiam IAM/IDaaS身份管理平台 - https://www.topiam.cn/ <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/#/common/column)
+10 -10
View File
@@ -10,34 +10,34 @@
<name>RuoYi-Vue-Plus</name>
<url>https://gitee.com/dromara/RuoYi-Vue-Plus</url>
<description>Dromara RuoYi-Vue-Plus管理系统</description>
<description>Dromara RuoYi-Vue-Plus后台管理系统</description>
<properties>
<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.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>21</java.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>
<fesod.version>2.0.1-incubating</fesod.version>
<fesod.version>2.0.2-incubating</fesod.version>
<velocity.version>2.4.1</velocity.version>
<satoken.version>1.45.0</satoken.version>
<mybatis-plus.version>3.5.16</mybatis-plus.version>
<mybatis-plus-join.version>1.5.6</mybatis-plus-join.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>
<redisson.version>4.4.0</redisson.version>
<redisson.version>4.5.0</redisson.version>
<lock4j.version>2.2.7</lock4j.version>
<dynamic-ds.version>4.5.0</dynamic-ds.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.lombok.version>0.2.0</mapstruct-plus.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>
<!-- 离线IP地址定位库 -->
<ip2region.version>3.3.6</ip2region.version>
@@ -52,7 +52,7 @@
<!-- 工作流配置 -->
<warm-flow.version>1.8.7</warm-flow.version>
<!-- 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>
<elasticsearch-client.version>7.17.28</elasticsearch-client.version>
<!-- Spring AI 2.0 预览版,正式版发布后仅需调整此版本号 -->
@@ -66,7 +66,7 @@
<!-- 统一版本号管理:Maven3.X需要,Maven4.0之后已原生支持 -->
<flatten-maven-plugin.version>1.7.3</flatten-maven-plugin.version>
<!-- 打包默认跳过测试 -->
<skipTests>true</skipTests>
<maven.test.skip>true</maven.test.skip>
</properties>
<profiles>
+6 -4
View File
@@ -11,18 +11,20 @@ RUN mkdir -p /ruoyi/server/logs \
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}
# 暴露 snail job 客户端端口 用于定时任务调度中心通信
EXPOSE ${SNAIL_PORT}
# 暴露 snail 客户端端口 用于定时任务调度中心通信
EXPOSE ${SNAIL_JOB_PORT}
EXPOSE ${SNAIL_AI_PORT}
ADD ./target/ruoyi-admin.jar ./app.jar
SHELL ["/bin/bash", "-c"]
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 \
#-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \
-24
View File
@@ -17,30 +17,6 @@
<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驱动包 -->
<dependency>
<groupId>com.mysql</groupId>
@@ -58,7 +58,7 @@ public class CaptchaController {
* @param phoneNumber 用户手机号
* @return 操作结果
*/
@RateLimiter(key = "#phonenumber", time = 60, count = 1)
@RateLimiter(key = "#phoneNumber", time = 60, count = 1)
@GetMapping("/resource/sms/code")
public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phoneNumber) {
if (!RegexValidator.isMobile(phoneNumber)) {
@@ -66,7 +66,6 @@ public class CaptchaController {
}
String key = GlobalConstants.CAPTCHA_CODE_KEY + phoneNumber;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
// 验证码模板id 自行处理 (查数据库或写死均可)
String templateId = "";
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
@@ -75,8 +74,10 @@ public class CaptchaController {
SmsResponse smsResponse = smsBlend.sendMessage(phoneNumber, templateId, map);
if (!smsResponse.isSuccess()) {
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();
}
@@ -107,13 +108,13 @@ public class CaptchaController {
public void emailCodeImpl(String email) {
String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
try {
MailBuilder.of()
.to(email)
.subject("登录验证码")
.text("您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。")
.send();
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
} catch (Exception e) {
log.error("验证码短信发送异常 => {}", e.getMessage());
throw new ServiceException(e.getMessage());
@@ -51,7 +51,7 @@ public interface IAuthStrategy {
/**
* 按客户端配置构建统一登录参数,并预留自定义扩展入口。
*
* @param client 客户端配置
* @param client 客户端配置
* @param customizer 自定义扩展逻辑
* @return Sa-Token 登录参数
*/
@@ -97,7 +97,7 @@ public class SysLoginService {
sysSocialService.insertByBo(bo);
} else {
// 更新用户信息
bo.setId(list.get(0).getId());
bo.setId(list.getFirst().getId());
sysSocialService.updateByBo(bo);
// 如果要绑定的平台账号已经被绑定过了 是否抛异常自行决断
// throw new ServiceException("此平台账号已经被绑定!");
@@ -48,8 +48,8 @@ public class SocialAuthStrategy implements IAuthStrategy {
/**
* 执行第三方授权登录,并校验授权账号与系统账号的绑定关系。
*
* @param body 登录信息
* @param client 客户端信息
* @param body 登录信息
* @param client 客户端信息
* @return 登录结果
*/
@Override
@@ -57,8 +57,8 @@ public class SocialAuthStrategy implements IAuthStrategy {
SocialLoginBody loginBody = JsonUtils.parseObject(body, SocialLoginBody.class);
ValidatorUtils.validate(loginBody);
AuthResponse<AuthUser> response = SocialUtils.loginAuth(
loginBody.getSource(), loginBody.getSocialCode(),
loginBody.getSocialState(), socialProperties);
loginBody.getSource(), loginBody.getSocialCode(),
loginBody.getSocialState(), socialProperties);
if (!response.ok()) {
throw new ServiceException(response.getMsg());
}
@@ -68,7 +68,7 @@ public class SocialAuthStrategy implements IAuthStrategy {
if (CollUtil.isEmpty(list)) {
throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!");
}
SysUserVo user = loadUser(list.get(0).getUserId());
SysUserVo user = loadUser(list.getFirst().getUserId());
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
LoginUser loginUser = loginService.buildLoginUser(user);
loginUser.setClientKey(client.getClientKey());
@@ -137,7 +137,7 @@ spring.data:
# 数据库索引
database: 0
# redis 密码必须配置
password: ruoyi123
# password: ruoyi123
# 连接超时时间
timeout: 10s
# 是否开启ssl
@@ -161,7 +161,7 @@ springdoc:
enabled: true
info:
# 标题
title: '标题:RuoYi-Vue-Plus管理系统_接口文档'
title: '标题:RuoYi-Vue-Plus后台管理系统_接口文档'
# 描述
description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
# 版本
@@ -191,6 +191,7 @@ xss:
# 排除链接
excludeUrls:
- /system/notice
- /warm-flow/save-json
--- # 分布式锁 lock4j 全局配置
lock4j:
@@ -262,8 +263,8 @@ mqtt.client:
timeout: 5
# 重连时间,默认 5000 毫秒
re-interval: 5000
# mqtt 协议版本,可选 MQTT_3_1、mqtt_3_1_1、mqtt_5,默认:mqtt_3_1_1
version: mqtt_3_1_1
# mqtt 协议版本,可选 MQTT_3_1、mqtt_3_1_1、mqtt_5,默认:mqtt_5
version: mqtt_5
# 接收数据的 buffer size,默认:8k
read-buffer-size: 8KB
# 消息解析最大 bytes 长度,默认:10M
@@ -57,10 +57,10 @@ public class PushPayloadDTO implements Serializable {
/**
* 构建推送消息体,缺省消息类型与来源时使用系统默认值。
*
* @param type 消息类型
* @param source 消息来源
* @param type 消息类型
* @param source 消息来源
* @param message 文本消息
* @param data 扩展数据
* @param data 扩展数据
* @return 推送消息体
*/
public static PushPayloadDTO of(String type, String source, String message, Object data) {
@@ -76,10 +76,10 @@ public class PushPayloadDTO implements Serializable {
/**
* 通过枚举值构建推送消息体。
*
* @param type 消息类型枚举
* @param source 消息来源枚举
* @param type 消息类型枚举
* @param source 消息来源枚举
* @param message 文本消息
* @param data 扩展数据
* @param data 扩展数据
* @return 推送消息体
*/
public static PushPayloadDTO of(PushTypeEnum type, PushSourceEnum source, String message, Object data) {
@@ -94,11 +94,11 @@ public class PushPayloadDTO implements Serializable {
/**
* 构建带前端跳转路径的推送消息体。
*
* @param type 消息类型枚举
* @param source 消息来源枚举
* @param type 消息类型枚举
* @param source 消息来源枚举
* @param message 文本消息
* @param data 扩展数据
* @param path 前端跳转路径
* @param data 扩展数据
* @param path 前端跳转路径
* @return 推送消息体
*/
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 list 受让人列表
* @param list 受让人列表
*/
public TaskAssigneeDTO(Long total, List<TaskHandler> list) {
this.total = total;
@@ -98,9 +98,8 @@ public class ThreadPoolConfig {
* @param t 任务执行过程中抛出的异常
*/
public static void printException(Runnable r, Throwable t) {
if (t == null && r instanceof Future<?>) {
if (t == null && r instanceof Future<?> future) {
try {
Future<?> future = (Future<?>) r;
if (future.isDone()) {
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 code 错误码
* @param code 错误码
*/
public ServiceException(String message, Integer code) {
this.message = message;
@@ -61,7 +61,7 @@ public final class ServiceException extends RuntimeException {
* 使用错误消息和根因构造业务异常
*
* @param message 错误消息
* @param cause 根因
* @param cause 根因
*/
public ServiceException(String message, Throwable cause) {
super(cause);
@@ -72,7 +72,7 @@ public final class ServiceException extends RuntimeException {
* 使用占位符参数格式化错误消息
*
* @param message 模板消息
* @param args 参数
* @param args 参数
*/
public ServiceException(String message, Object... args) {
this.message = StrFormatter.format(message, args);
@@ -49,7 +49,7 @@ public final class SseException extends RuntimeException {
* 使用错误消息和错误码构造 SSE 异常
*
* @param message 错误消息
* @param code 错误码
* @param code 错误码
*/
public SseException(String message, Integer code) {
this.message = message;
@@ -1,11 +1,11 @@
package org.dromara.common.core.exception.base;
import lombok.AllArgsConstructor;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.StringUtils;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.StringUtils;
import java.io.Serial;
@@ -47,8 +47,8 @@ public class BaseException extends RuntimeException {
* 使用模块错误码和参数构造异常
*
* @param module 所属模块
* @param code 错误码
* @param args 参数
* @param code 错误码
* @param args 参数
*/
public BaseException(String module, String code, Object[] args) {
this(module, code, args, null);
@@ -57,7 +57,7 @@ public class BaseException extends RuntimeException {
/**
* 使用模块和默认消息构造异常
*
* @param module 所属模块
* @param module 所属模块
* @param defaultMessage 默认消息
*/
public BaseException(String module, String defaultMessage) {
@@ -12,7 +12,7 @@ public interface PermissionService {
/**
* 获取角色数据权限
*
* @param userId 用户id
* @param userId 用户id
* @return 角色权限信息
*/
Set<String> getRolePermission(Long userId);
@@ -20,7 +20,7 @@ public interface PermissionService {
/**
* 获取菜单数据权限
*
* @param userId 用户id
* @param userId 用户id
* @return 菜单权限信息
*/
Set<String> getMenuPermission(Long userId);
@@ -1,347 +1,96 @@
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.DateUtil;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.dromara.common.core.enums.FormatsType;
import cn.hutool.core.lang.Assert;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
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.concurrent.TimeUnit;
/**
* 时间工具类
*
* @author ruoyi
* @author AprilWind
*/
public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
/**
* 日期解析格式集合
*/
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"};
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class DateUtils extends DateUtil {
/**
* 工具类不允许实例化
*/
@Deprecated
private DateUtils() {
}
/**
* 获取当前日期和时间
* 计算时间差并格式化精确到秒
*
* @return 当前日期和时间的 Date 对象表示
* @param start 开始时间(支持 Date/LocalDateTime)
* @param end 结束时间(支持 Date/LocalDateTime)
* @return 时分秒格式时间差
*/
public static Date getNowDate() {
return new Date();
public static String formatBetweenBySecond(Object start, Object end) {
return formatTimeBetween(start, end, BetweenFormatter.Level.SECOND);
}
/**
* 获取当前日期的字符串表示格式为YYYY-MM-DD
* 通用时间差格式化
*
* @return 当前日期的字符串表示
* @param startDate 开始时间
* @param endDate 结束时间
* @param level 精度级别
* @return 格式化时长
*/
public static String getDate() {
return dateTimeNow(FormatsType.YYYY_MM_DD);
public static String formatTimeBetween(Object startDate, Object endDate, BetweenFormatter.Level level) {
// 非空校验
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 所需返回的时间单位DAYSHOURSMINUTESSECONDSMILLISECONDSMICROSECONDSNANOSECONDS
* @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 endDate 结束日期
* @param maxValue 最大时间跨度限制值
* @param unit 时间跨度的单位可选择 "DAYS""HOURS" "MINUTES"
* @param maxValue 最大时间跨度限制值
* @param unit 时间单位
*/
public static void validateDateRange(Date startDate, Date endDate, int maxValue, TimeUnit unit) {
// 校验结束日期不能早于开始日期
if (endDate.before(startDate)) {
public static void validateDateRange(Object startDate, Object endDate, int maxValue, TimeUnit unit) {
// 基础非空校验
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("结束日期不能早于开始日期");
}
// 计算时间跨度
long diffInMillis = endDate.getTime() - startDate.getTime();
// 根据单位转换时间跨度
long diffMillis = end.getTime() - start.getTime();
long diff = switch (unit) {
case DAYS -> TimeUnit.MILLISECONDS.toDays(diffInMillis);
case HOURS -> TimeUnit.MILLISECONDS.toHours(diffInMillis);
case MINUTES -> TimeUnit.MILLISECONDS.toMinutes(diffInMillis);
default -> throw new IllegalArgumentException("不支持的时间单位");
case DAYS -> TimeUnit.MILLISECONDS.toDays(diffMillis);
case HOURS -> TimeUnit.MILLISECONDS.toHours(diffMillis);
case MINUTES -> TimeUnit.MILLISECONDS.toMinutes(diffMillis);
default -> throw new IllegalArgumentException("不支持的时间单位" + unit.name());
};
// 校验时间跨度不超过最大限制
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 时间段描述
*/
public static String getTodayHour(Date date) {
int hour = DateUtil.hour(date, true);
int hour = hour(date, true);
if (hour <= 6) {
return "凌晨";
} else if (hour < 12) {
@@ -387,38 +136,37 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
if (date == null) {
return "";
}
Date now = DateUtil.date();
Date now = new Date();
// 未来时间或非今年
if (date.after(now) || DateUtil.year(date) != DateUtil.year(now)) {
return parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM, date);
if (date.after(now) || year(date) != year(now)) {
return formatDateTime(now);
}
// 今天
if (DateUtil.isSameDay(date, now)) {
long minutes = DateUtil.between(date, now, DateUnit.MINUTE);
if (isSameDay(date, now)) {
long minutes = between(date, now, DateUnit.MINUTE);
if (minutes < 1) {
return "刚刚";
}
if (minutes < 60) {
return minutes + "分钟前";
}
return getTodayHour(date) + " " + DateUtil.format(date, "HH:mm");
return getTodayHour(date) + " " + format(date, "HH:mm");
}
// 昨天
if (DateUtil.isSameDay(date, DateUtil.yesterday())) {
return "昨天 " + DateUtil.format(date, "HH:mm");
if (isSameDay(date, yesterday())) {
return "昨天 " + format(date, "HH:mm");
}
// 本周
if (DateUtil.isSameWeek(date, now, true)) {
return DateUtil.dayOfWeekEnum(date).toChinese("")
+ " " + DateUtil.format(date, "HH:mm");
if (isSameWeek(date, now, true)) {
return dayOfWeekEnum(date).toChinese("") + " " + 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 工具类
* <p>参考文档<a href="https://mapstruct.plus/introduction/quick-start.html">mapstruct-plus</a></p>
*
*
* @author Michelle.Chung
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@@ -86,7 +86,7 @@ public class NetUtils extends NetUtil {
/**
* 匹配 IP 规则支持精确值通配符与 CIDR
*
* @param rule IP 规则
* @param rule IP 规则
* @param clientIp 客户端 IP
* @return 是否匹配
*/
@@ -114,7 +114,7 @@ public class NetUtils extends NetUtil {
/**
* 匹配 CIDR 网段
*
* @param cidr CIDR 规则
* @param cidr CIDR 规则
* @param clientIp 客户端 IP
* @return 是否命中
*/
@@ -17,7 +17,7 @@ public class ObjectUtils extends ObjectUtil {
/**
* 如果对象不为空则获取对象中的某个字段 ObjectUtils.notNullGetter(user, User::getName);
*
* @param obj 对象
* @param obj 对象
* @param func 获取方法
* @return 对象字段
*/
@@ -60,7 +60,7 @@ public class StreamUtils {
* @return 找到符合条件的第一个元素没有则返回 null
*/
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
*/
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 不变
* @param map 需要处理的map
*
* @param map 需要处理的map
* @param take 取值函数
* @param <K> map中的key类型
* @param <E> map中的value类型
* @param <V> 新map中的value类型
* @param <K> map中的key类型
* @param <E> map中的value类型
* @param <V> 新map中的value类型
* @return 新的map
*/
public static <K, E, V> Map<K, V> toMap(Map<K, E> map, BiFunction<K, E, V> take) {
@@ -23,6 +23,8 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
public static final String SLASH = "/";
public static final String COLON = ":";
private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();
@Deprecated
@@ -363,6 +365,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
return input;
}
}
/**
* 将可迭代对象中的元素使用逗号拼接成字符串
*
@@ -397,7 +400,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
/**
* 判断字符串是否在指定的字符串列表中
*
* @param string 字符串
* @param string 字符串
* @param searchStrings 字符串列表
* @return 是否在列表中
*/
@@ -408,7 +411,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
/**
* 忽略大小写判断字符串是否在指定的字符串列表中
*
* @param string 字符串
* @param string 字符串
* @param searchStrings 字符串列表
* @return 是否在列表中
*/
@@ -499,8 +502,8 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
/**
* 移除字符串中的指定字符序列
*
* @param str 要处理的字符串不能为null
* @param remove 要移除的字符序列不能为null
* @param str 要处理的字符串不能为null
* @param remove 要移除的字符序列不能为null
* @return 处理后的字符串
*/
public static String remove(final String str, final String remove) {
@@ -6,7 +6,10 @@ import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.Collection;
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;
/**
@@ -64,7 +64,7 @@ public class TreeBuildUtils extends TreeUtil {
if (CollUtil.isEmpty(list)) {
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);
}
@@ -26,6 +26,9 @@ public class ValidatorUtils {
* @throws ConstraintViolationException 如果校验不通过则抛出参数校验异常
*/
public static <T> void validate(T object, Class<?>... groups) {
if (object == null) {
throw new RuntimeException("请求参数不能为空");
}
Set<ConstraintViolation<T>> validate = VALID.validate(object, groups);
if (!validate.isEmpty()) {
throw new ConstraintViolationException("参数校验异常", validate);
@@ -1,60 +1,64 @@
package org.dromara.common.core.utils.file;
import cn.hutool.core.util.StrUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
/**
* 媒体类型工具类
*
* @author ruoyi
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MimeTypeUtils {
/**
* PNG 图片 MIME 类型
* PNG 图片 MIME 类型
*/
public static final String IMAGE_PNG = "image/png";
/**
* JPG 图片 MIME 类型
* JPG 图片 MIME 类型
*/
public static final String IMAGE_JPG = "image/jpg";
/**
* JPEG 图片 MIME 类型
* JPEG 图片 MIME 类型
*/
public static final String IMAGE_JPEG = "image/jpeg";
/**
* BMP 图片 MIME 类型
* BMP 图片 MIME 类型
*/
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_EXTENSION = {"bmp", "gif", "jpg", "jpeg", "png"};
/**
* Flash 扩展名集合
* Flash 文件扩展名数组
*/
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"};
/**
* 默认允许上传扩展名集合
* 媒体文件扩展名数组音频+视频+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 = {
// 图片
@@ -66,6 +70,47 @@ public class MimeTypeUtils {
// 视频格式
"mp4", "avi", "rmvb",
// 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) {
// 处理空串并过滤HTML标签
ip = HtmlUtil.cleanHtmlTag(StringUtils.blankToDefault(ip,""));
ip = HtmlUtil.cleanHtmlTag(StringUtils.blankToDefault(ip, ""));
// 判断是否为IPv4
boolean isIPv4 = NetUtils.isIPv4(ip);
// 判断是否为IPv6
@@ -1,6 +1,8 @@
package org.dromara.common.core.utils.ip;
import cn.hutool.core.io.resource.ResourceUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
@@ -19,6 +21,7 @@ import java.time.Duration;
* @author 秋辞未寒
*/
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class RegionUtils {
// 默认IPv4地址库文件路径
@@ -23,7 +23,7 @@ public final class AnnotationUtils extends AnnotationUtil {
/**
* 获取指定注解
*
* @param annotationEle {@link AnnotatedElement}可以是ClassMethodFieldConstructorReflectPermission
* @param annotationEle {@link AnnotatedElement}可以是ClassMethodFieldConstructorReflectPermission
* @param annotationTypeName 注解类型名称
* @return 注解对象
*/
@@ -34,7 +34,7 @@ public final class AnnotationUtils extends AnnotationUtil {
} catch (final ClassNotFoundException | ClassCastException e) {
// ignore
log.error("AnnotationUtils.getAnnotation(AnnotatedElement, String) error.", e);
return null;
return null;
}
}
@@ -42,7 +42,7 @@ public final class AnnotationUtils extends AnnotationUtil {
* 获取指定注解中所有属性值<br>
* 如果无指定的属性方法返回null
*
* @param annotationEle {@link AnnotatedElement}可以是ClassMethodFieldConstructorReflectPermission
* @param annotationEle {@link AnnotatedElement}可以是ClassMethodFieldConstructorReflectPermission
* @param annotationTypeName 注解类型名称
* @return 注解对象所有属性键值
* @throws UtilException 调用注解中的方法时执行异常
@@ -55,7 +55,7 @@ public final class AnnotationUtils extends AnnotationUtil {
} catch (final ClassNotFoundException | ClassCastException e) {
// ignore
log.error("AnnotationUtils.getAnnotationValueMap(AnnotatedElement, String) error.", e);
return null;
return null;
}
}
@@ -1,9 +1,9 @@
package org.dromara.common.core.utils.reflect;
import cn.hutool.core.util.ReflectUtil;
import org.dromara.common.core.utils.StringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.StringUtils;
import java.lang.reflect.Method;
@@ -12,7 +12,6 @@ import java.lang.reflect.Method;
*
* @author Lion Li
*/
@SuppressWarnings("rawtypes")
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ReflectUtils extends ReflectUtil {
@@ -2,6 +2,8 @@ package org.dromara.common.core.utils.regex;
import cn.hutool.core.util.ReUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.constant.RegexConstants;
/**
@@ -9,6 +11,7 @@ import org.dromara.common.core.constant.RegexConstants;
*
* @author AprilWind
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
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.lang.Validator;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.factory.RegexPatternPoolFactory;
import java.util.regex.Pattern;
@@ -12,6 +14,7 @@ import java.util.regex.Pattern;
*
* @author AprilWind
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class RegexValidator extends Validator {
/**
@@ -3,7 +3,10 @@ package org.dromara.common.core.validate.enums;
import jakarta.validation.Constraint;
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.RetentionPolicy.RUNTIME;
@@ -2,6 +2,7 @@ package org.dromara.common.core.xss;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
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 lombok.RequiredArgsConstructor;
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.JavadocOperationCustomizer;
import org.dromara.common.doc.config.properties.SpringDocProperties;
import org.dromara.common.doc.core.resolver.JavadocResolver;
import org.dromara.common.doc.core.resolver.SaTokenAnnotationMetadataJavadocResolver;
import org.springdoc.core.configuration.SpringDocConfiguration;
@@ -56,7 +56,7 @@ public class SpringDocProperties {
* </p>
*
* @see io.swagger.v3.oas.models.info.Info
*
* <p>
* 为了 springboot 自动生产配置提示信息所以这里复制一个类出来
*/
@Data
@@ -18,13 +18,7 @@ import org.springframework.util.CollectionUtils;
import org.springframework.web.method.HandlerMethod;
import java.io.StringReader;
import java.util.ArrayList;
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.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -86,7 +86,7 @@ public class SaTokenSecurityMetadata {
return sb.toString();
}
if (!ignore && permissions.isEmpty() && roles.isEmpty()){
if (!ignore && permissions.isEmpty() && roles.isEmpty()) {
sb.append("> **权限策略**:需要登录<br><br>");
return sb.toString();
}
@@ -82,76 +82,84 @@ public abstract class AbstractMetadataJavadocResolver<M> implements JavadocResol
/**
* 执行解析并返回解析到的 Javadoc 内容
*
* @param handlerMethod 处理器方法
* @param operation Swagger Operation实例
* @param metadata 元信息
* @param operation Swagger Operation实例
* @param metadata 元信息
* @return 解析到的 Javadoc 内容
*/
public abstract String resolve(HandlerMethod handlerMethod, Operation operation, M metadata);
/**
* 检查处理器方法所属的类上是否存在注解
* @param handlerMethod 处理器方法
*
* @param handlerMethod 处理器方法
* @param annotationClass 注解类
* @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);
}
/**
* 检查处理器方法所属的类上是否存在注解
* @param handlerMethod 处理器方法
*
* @param handlerMethod 处理器方法
* @param annotationTypeName 注解类名称
* @return 是否存在注解
*/
public boolean hasClassAnnotation(HandlerMethod handlerMethod, String annotationTypeName){
public boolean hasClassAnnotation(HandlerMethod handlerMethod, String annotationTypeName) {
return AnnotationUtil.hasAnnotation(handlerMethod.getBeanType(), annotationTypeName);
}
/**
* 检查处理器方法上是否存在注解
* @param handlerMethod 处理器方法
*
* @param handlerMethod 处理器方法
* @param annotationClass 注解类
* @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);
}
/**
* 检查处理器方法上是否存在注解
* @param handlerMethod 处理器方法
*
* @param handlerMethod 处理器方法
* @param annotationTypeName 注解类名称
* @return 是否存在注解
*/
public boolean hasMethodAnnotation(HandlerMethod handlerMethod, String annotationTypeName){
public boolean hasMethodAnnotation(HandlerMethod handlerMethod, String annotationTypeName) {
return AnnotationUtil.hasAnnotation(handlerMethod.getMethod(), annotationTypeName);
}
/**
* 检查处理器方法上是否存在注解
* @param handlerMethod 处理器方法
*
* @param handlerMethod 处理器方法
* @param annotationClass 注解类
* @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);
}
/**
* 检查处理器方法上是否存在注解
* @param handlerMethod 处理器方法
*
* @param handlerMethod 处理器方法
* @param annotationTypeName 注解类名称
* @return 是否存在注解
*/
public boolean hasAnnotation(HandlerMethod handlerMethod, String annotationTypeName){
public boolean hasAnnotation(HandlerMethod handlerMethod, String annotationTypeName) {
return this.hasClassAnnotation(handlerMethod, annotationTypeName) || this.hasMethodAnnotation(handlerMethod, annotationTypeName);
}
/**
* 获取处理器方法所属类上的注解的值
* @param handlerMethod 处理器方法
*
* @param handlerMethod 处理器方法
* @param annotationClass 注解类
* @return 注解的值
*/
@@ -161,7 +169,8 @@ public abstract class AbstractMetadataJavadocResolver<M> implements JavadocResol
/**
* 获取处理器方法所属类上的注解的值
* @param handlerMethod 处理器方法
*
* @param handlerMethod 处理器方法
* @param annotationClassName 注解类名称
* @return 注解的值
*/
@@ -173,7 +182,8 @@ public abstract class AbstractMetadataJavadocResolver<M> implements JavadocResol
/**
* 获取处理器方法上的注解的值
* @param handlerMethod 处理器方法
*
* @param handlerMethod 处理器方法
* @param annotationClass 注解类
* @return 注解的值
*/
@@ -183,7 +193,8 @@ public abstract class AbstractMetadataJavadocResolver<M> implements JavadocResol
/**
* 获取处理器方法所属类上的注解的值
* @param handlerMethod 处理器方法
*
* @param handlerMethod 处理器方法
* @param annotationClassName 注解类名称
* @return 注解的值
*/
@@ -25,7 +25,7 @@ public interface JavadocResolver extends Comparable<JavadocResolver>, Ordered {
* 执行解析并返回解析到的 Javadoc 内容
*
* @param handlerMethod 处理器方法
* @param operation Swagger Operation实例
* @param operation Swagger Operation实例
* @return 解析到的 Javadoc 内容
*/
String resolve(HandlerMethod handlerMethod, Operation operation);
@@ -106,17 +106,17 @@ public class SaTokenAnnotationMetadataJavadocResolver extends AbstractMetadataJa
* @param order 顺序值
*/
public SaTokenAnnotationMetadataJavadocResolver(int order) {
this(DEFAULT_METADATA_PROVIDER,order);
this(DEFAULT_METADATA_PROVIDER, order);
}
/**
* 使用自定义元数据提供者和顺序创建解析器
*
* @param metadataProvider 元数据提供者
* @param order 顺序值
* @param 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 注解并转换为文档说明
*
* @param handlerMethod Handler 方法
* @param operation OpenAPI 操作对象
* @param metadata 权限元数据
* @param operation OpenAPI 操作对象
* @param metadata 权限元数据
* @return Markdown 描述
*/
@Override
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);
return metadata.toMarkdownString();
}
@@ -176,10 +176,10 @@ public class SaTokenAnnotationMetadataJavadocResolver extends AbstractMetadataJa
private void resolvePermissionAnnotation(SaTokenSecurityMetadata metadata, Map<String, Object> annotationValueMap) {
try {
// 反射获取注解属性
Object value = annotationValueMap.get( "value");
Object mode = annotationValueMap.get( "mode");
Object type = annotationValueMap.get( "type");
Object orRole = annotationValueMap.get( "orRole");
Object value = annotationValueMap.get("value");
Object mode = annotationValueMap.get("mode");
Object type = annotationValueMap.get("type");
Object orRole = annotationValueMap.get("orRole");
String[] values = Convert.toStrArray(value);
String modeStr = mode != null ? mode.toString() : "AND";
@@ -25,9 +25,9 @@ public class ApiDecryptAutoConfiguration {
/**
* 注册 API 加解密过滤器
*
* @param properties API 解密配置
* @param properties API 解密配置
* @param requestMappingHandlerMapping 请求映射处理器
* @param handlerExceptionResolver 异常处理器
* @param handlerExceptionResolver 异常处理器
* @return API 加解密过滤器
*/
@Bean
@@ -1,8 +1,8 @@
package org.dromara.common.encrypt.core;
import lombok.Data;
import org.dromara.common.encrypt.enums.AlgorithmType;
import org.dromara.common.encrypt.enums.EncodeType;
import lombok.Data;
/**
* 加密上下文 用于encryptor传递必要的参数
@@ -171,10 +171,10 @@ public class EncryptorManager {
/**
* 加密器缓存键
*
* @param algorithm 加密算法
* @param encode 编码方式
* @param password 密码
* @param publicKey 公钥
* @param algorithm 加密算法
* @param encode 编码方式
* @param password 密码
* @param publicKey 公钥
* @param privateKey 私钥
*/
private record EncryptorCacheKey(
@@ -28,7 +28,7 @@ public interface IEncryptor {
/**
* 解密
*
* @param value 待加密字符串
* @param value 待加密字符串
* @return 解密后的字符串
*/
String decrypt(String value);
@@ -46,7 +46,7 @@ public class AesEncryptor extends AbstractEncryptor {
/**
* 解密
*
* @param value 待加密字符串
* @param value 待加密字符串
*/
@Override
public String decrypt(String value) {
@@ -44,7 +44,7 @@ public class Base64Encryptor extends AbstractEncryptor {
/**
* 解密
*
* @param value 待加密字符串
* @param value 待加密字符串
*/
@Override
public String decrypt(String value) {
@@ -21,6 +21,6 @@ public enum EncodeType {
/**
* 16进制编码
*/
HEX;
HEX
}
@@ -31,7 +31,7 @@ public class DecryptRequestBodyWrapper extends HttpServletRequestWrapper {
/**
* 解密请求体并缓存为可重复读取的 JSON 请求体
*
* @param request 原始请求
* @param request 原始请求
* @param privateKey RSA 私钥
* @param headerFlag 加密密钥请求头
* @throws IOException 读取请求体异常
@@ -5,6 +5,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* api解密属性配置类
*
* @author wdhcr
*/
@Data
@@ -1,8 +1,8 @@
package org.dromara.common.encrypt.properties;
import lombok.Data;
import org.dromara.common.encrypt.enums.AlgorithmType;
import org.dromara.common.encrypt.enums.EncodeType;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
@@ -259,9 +259,9 @@ public class EncryptUtils {
/**
* SM2公钥验签Hex编码
*
* @param dataHex 原文数据Hex编码
* @param signHex 签名值Hex编码
* @param publicKey 公钥
* @param dataHex 原文数据Hex编码
* @param signHex 签名值Hex编码
* @param publicKey 公钥
* @return true-验签成功false-验签失败
*/
public static boolean verifySm2SignHex(String dataHex, String signHex, String publicKey) {
@@ -6,7 +6,7 @@ import java.lang.annotation.*;
/**
* excel 列单元格合并(合并列相同项)
*
* <p>
* 需搭配 {@link CellMergeStrategy} 策略使用
*
* @author Lion Li
@@ -16,10 +16,10 @@ import java.lang.annotation.*;
@Inherited
public @interface CellMerge {
/**
* col index
*/
int index() default -1;
/**
* col index
*/
int index() default -1;
/**
* 合并需要依赖的其他字段名称
@@ -7,6 +7,7 @@ import java.lang.annotation.Target;
/**
* 批注 此注解仅用于单表头 不支持多层级表头
*
* @author guzhouyanyu
*/
@Target({ElementType.FIELD})
@@ -9,6 +9,7 @@ import java.lang.annotation.Target;
/**
* 是否必填 此注解仅用于单表头 不支持多层级表头
*
* @author guzhouyanyu
*/
@Target({ElementType.FIELD})
@@ -44,8 +44,8 @@ public class ExcelBigNumberConvert implements Converter<Long> {
/**
* Excel 单元格数据转换为 Long
*
* @param cellData 单元格数据
* @param contentProperty 内容属性
* @param cellData 单元格数据
* @param contentProperty 内容属性
* @param globalConfiguration 全局配置
* @return Long
*/
@@ -57,8 +57,8 @@ public class ExcelBigNumberConvert implements Converter<Long> {
/**
* Long 转换为 Excel 单元格数据超长数字按字符串写出
*
* @param object Java
* @param contentProperty 内容属性
* @param object Java
* @param contentProperty 内容属性
* @param globalConfiguration 全局配置
* @return Excel 写入数据
*/
@@ -3,6 +3,7 @@ package org.dromara.common.excel.convert;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.fesod.sheet.converters.Converter;
import org.apache.fesod.sheet.enums.CellDataTypeEnum;
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.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.excel.annotation.ExcelEnumFormat;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.util.HashMap;
@@ -58,8 +58,8 @@ public class ExcelEnumConvert implements Converter<Object> {
/**
* Excel 枚举文本转换为 Java 字段值
*
* @param cellData 单元格数据
* @param contentProperty 内容属性
* @param cellData 单元格数据
* @param contentProperty 内容属性
* @param globalConfiguration 全局配置
* @return Java 字段值
*/
@@ -103,8 +103,8 @@ public class ExcelEnumConvert implements Converter<Object> {
/**
* Java 枚举编码转换为 Excel 显示文本
*
* @param object Java 字段值
* @param contentProperty 内容属性
* @param object Java 字段值
* @param contentProperty 内容属性
* @param globalConfiguration 全局配置
* @return Excel 写入数据
*/
@@ -3,10 +3,10 @@ package org.dromara.common.excel.core;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.SneakyThrows;
import org.apache.fesod.sheet.annotation.ExcelIgnore;
import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
import org.apache.fesod.sheet.annotation.ExcelProperty;
import lombok.SneakyThrows;
import org.apache.poi.ss.util.CellRangeAddress;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.excel.annotation.CellMerge;
@@ -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)) {
return Collections.emptyList();
@@ -165,8 +165,8 @@ public class CellMergeHandler {
* 判断当前行是否满足依赖字段合并条件
*
* @param currentRow 当前行数据
* @param preRow 上一行数据
* @param cellMerge 合并配置
* @param preRow 上一行数据
* @param cellMerge 合并配置
* @return 是否允许合并
*/
private boolean isMerge(Object currentRow, Object preRow, CellMerge cellMerge) {
@@ -198,10 +198,10 @@ public class CellMergeHandler {
/**
* 追加有效的合并区域
*
* @param result 合并区域结果集
* @param result 合并区域结果集
* @param repeatCell 连续重复单元格信息
* @param endIndex 结束行索引
* @param colNum 列索引
* @param endIndex 结束行索引
* @param colNum 列索引
*/
private void appendMergeResult(List<CellRangeAddress> result, RepeatCell repeatCell, int endIndex, int colNum) {
if (repeatCell == null || endIndex <= repeatCell.current()) {
@@ -217,7 +217,7 @@ public class CellMergeHandler {
/**
* 创建连续重复单元格信息
*
* @param value 单元格值
* @param value 单元格值
* @param current 当前行索引
* @return 连续重复单元格信息
*/
@@ -233,7 +233,7 @@ public class CellMergeHandler {
/**
* 创建字段列索引和合并注解信息
*
* @param colIndex 列索引
* @param colIndex 列索引
* @param cellMerge 合并注解
* @return 字段列索引和合并注解信息
*/
@@ -38,7 +38,7 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements SheetWri
/**
* 根据数据列表创建单元格合并策略
*
* @param list 数据列表
* @param list 数据列表
* @param 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 rowIndex 起始行索引
*/
@@ -59,9 +59,9 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements SheetWri
/**
* 写入单元格时清理合并区域非首行内容
*
* @param sheet 工作表
* @param cell 当前单元格
* @param head 表头
* @param sheet 工作表
* @param cell 当前单元格
* @param head 表头
* @param relativeRowIndex 相对行索引
*/
@Override
@@ -83,7 +83,7 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements SheetWri
* 工作表创建后写入合并区域
*
* @param writeWorkbookHolder 工作簿上下文
* @param writeSheetHolder 工作表上下文
* @param writeSheetHolder 工作表上下文
*/
@Override
public void afterSheetCreate(final WriteWorkbookHolder writeWorkbookHolder, final WriteSheetHolder writeSheetHolder) {
@@ -1,6 +1,10 @@
package org.dromara.common.excel.core;
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.event.AnalysisEventListener;
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.ValidatorUtils;
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.Set;
@@ -59,7 +59,7 @@ public class DefaultExcelListener<T> extends AnalysisEventListener<T> implements
* 构造 Excel 导入监听器
*
* @param isValidate 是否执行 Validator 校验
* @param failFast 发生异常时是否立即终止读取
* @param failFast 发生异常时是否立即终止读取
*/
public DefaultExcelListener(boolean isValidate, boolean failFast) {
this.isValidate = isValidate;
@@ -118,7 +118,7 @@ public class DefaultExcelListener<T> extends AnalysisEventListener<T> implements
/**
* 处理一行导入数据
*
* @param data 行数据
* @param data 行数据
* @param context 解析上下文
*/
@Override
@@ -37,7 +37,7 @@ public class DefaultExcelResult<T> implements ExcelResult<T> {
/**
* 创建导入结果
*
* @param list 成功数据列表
* @param list 成功数据列表
* @param errorList 错误信息列表
*/
public DefaultExcelResult(List<T> list, List<String> errorList) {
@@ -25,27 +25,35 @@ import java.util.stream.Collectors;
@NoArgsConstructor
@SuppressWarnings("unused")
public class DropDownOptions {
/**
* 一级下拉所在列index从0开始算
*/
private int index = 0;
/**
* 二级下拉所在的index从0开始算不能与一级相同
*/
private int nextIndex = 0;
/**
* 一级下拉所包含的数据
*/
private List<String> options = new ArrayList<>();
/**
* 二级下拉所包含的数据Map
* <p>以每一个一级选项值为Key每个一级选项对应的二级数据为Value</p>
*/
private Map<String, List<String>> nextOptions = new HashMap<>();
/**
* 分隔符
*/
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) {
StringBuilder stringBuffer = new StringBuilder();
String regex = "^[\\S\\d\\u4e00-\\u9fa5]+$";
for (int i = 0; i < vars.length; i++) {
String var = StrUtil.trimToEmpty(Convert.toStr(vars[i]));
if (!var.matches(regex)) {
if (!var.matches(OPTION_PART_REGEX)) {
throw new ServiceException("选项数据不符合规则,仅允许使用中英文字符以及数字");
}
stringBuffer.append(var);
@@ -76,10 +83,29 @@ public class DropDownOptions {
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("禁止以数字开头");
}
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 -> {
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
String key = howToBuildEveryOption.apply(parentObj);
// Key对应的Value
@@ -202,6 +202,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
if (CollUtil.isEmpty(firstOptions)) {
return;
}
validateLinkedOptionNames(firstOptions);
Map<String, List<String>> secoundOptionsMap = new HashMap<>();
options.getNextOptions().forEach((k, v) -> secoundOptionsMap.put(k, new ArrayList<>(v)));
@@ -281,10 +282,10 @@ public class ExcelDownHandler implements SheetWriteHandler {
continue;
}
// 取第一个
String str = data.get(0);
String str = data.getFirst();
rowData.add(str);
// 通过移除的方式避免重复
data.remove(0);
data.removeFirst();
// 设置可以继续
flag = true;
}
@@ -313,6 +314,19 @@ public class ExcelDownHandler implements SheetWriteHandler {
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>
* 由于下拉框可选值数量过多为提升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.file.FileUtils;
import org.dromara.common.excel.convert.ExcelBigNumberConvert;
import org.dromara.common.excel.core.CellMergeStrategy;
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.core.*;
import org.dromara.common.excel.handler.DataWriteHandler;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -173,7 +164,7 @@ public final class ExcelBuilder<T> {
/**
* 创建 Excel 导出构造器
*
* @param data 导出数据
* @param data 导出数据
* @param headType 表头类型
*/
private ExcelBuilder(List<T> data, Class<T> headType) {
@@ -580,7 +571,7 @@ public final class ExcelBuilder<T> {
/**
* 构建 ZIP 内单个 Excel 文件内容
*
* @param pageData 当前分页数据
* @param pageData 当前分页数据
* @param exportSheetName 导出工作表名称
* @return Excel 文件字节
*/
@@ -720,7 +711,7 @@ public final class ExcelBuilder<T> {
* 创建 Excel 导入读取构造器
*
* @param inputStream 输入流
* @param headType 表头类型
* @param headType 表头类型
*/
private ReadBuilder(InputStream inputStream, Class<T> headType) {
this.inputStream = inputStream;
@@ -121,12 +121,10 @@ public record ExcelWriterWrapper<T>(ExcelWriter excelWriter) {
* @param excelWriter ExcelWriter
* @return ExcelWriterWrapper
*/
public static <T> ExcelWriterWrapper<T> of(ExcelWriter excelWriter) {
public static <T> ExcelWriterWrapper<T> of(ExcelWriter excelWriter) {
return new ExcelWriterWrapper<>(excelWriter);
}
// -------------------------------- sheet start
/**
* 创建工作表
*
@@ -207,10 +205,6 @@ public record ExcelWriterWrapper<T>(ExcelWriter excelWriter) {
return FesodSheet.writerSheet();
}
// -------------------------------- sheet end
// -------------------------------- table start
/**
* 创建表格
*
@@ -109,16 +109,20 @@ public class JsonValueEnhancer {
* @param visited 已访问对象集合用于避免循环引用
*/
private void collectValue(Object value, JsonEnhancementContext context, IdentityHashMap<Object, Boolean> visited) {
if (value == null) {
return;
}
if (value instanceof Map<?, ?> map) {
map.values().forEach(child -> collectValue(child, context, visited));
return;
}
if (value instanceof Iterable<?> iterable) {
iterable.forEach(child -> collectValue(child, context, visited));
return;
switch (value) {
case null -> {
return;
}
case Map<?, ?> map -> {
map.values().forEach(child -> collectValue(child, context, visited));
return;
}
case Iterable<?> iterable -> {
iterable.forEach(child -> collectValue(child, context, visited));
return;
}
default -> {
}
}
if (value.getClass().isArray()) {
int length = Array.getLength(value);
@@ -354,7 +358,7 @@ public class JsonValueEnhancer {
* JSON 属性元数据
*
* @param propertyName 属性名称
* @param member Jackson 属性成员
* @param member Jackson 属性成员
*/
private record PropertyMetadata(String propertyName, AnnotatedMember member) {
@@ -13,6 +13,10 @@ import tools.jackson.databind.ser.jdk.NumberSerializer;
@JacksonStdImpl
public class BigNumberSerializer extends NumberSerializer {
/**
* 提供实例
*/
public static final BigNumberSerializer INSTANCE = new BigNumberSerializer(Number.class);
/**
* 根据 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;
/**
* 提供实例
*/
public static final BigNumberSerializer INSTANCE = new BigNumberSerializer(Number.class);
/**
* 构造大数字序列化器
*
@@ -9,9 +9,9 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils;
@@ -58,7 +58,7 @@ public class LogAspect {
/**
* 执行目标方法并记录操作日志
*
* @param joinPoint 切点
* @param joinPoint 切点
* @param controllerLog 日志注解
* @return 目标方法返回值
* @throws Throwable 目标方法异常
@@ -80,11 +80,11 @@ public class LogAspect {
/**
* 组装并发布操作日志事件
*
* @param joinPoint 切点
* @param joinPoint 切点
* @param controllerLog 日志注解
* @param e 异常信息
* @param jsonResult 返回结果
* @param stopWatch 耗时统计
* @param e 异常信息
* @param jsonResult 返回结果
* @param stopWatch 耗时统计
*/
protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult, StopWatch stopWatch) {
try {
@@ -138,9 +138,9 @@ public class LogAspect {
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param joinPoint 切点
* @param log 日志
* @param operLog 操作日志
* @param joinPoint 切点
* @param log 日志
* @param operLog 操作日志
* @param jsonResult 返回结果
* @throws Exception 异常
*/
@@ -165,8 +165,8 @@ public class LogAspect {
/**
* 获取请求的参数放到log中
*
* @param joinPoint 切点
* @param operLog 操作日志
* @param joinPoint 切点
* @param operLog 操作日志
* @param excludeParamNames 排除参数名
* @throws Exception 异常
*/
@@ -186,7 +186,7 @@ public class LogAspect {
/**
* 将方法参数序列化为日志字符串
*
* @param paramsArray 参数数组
* @param paramsArray 参数数组
* @param excludeParamNames 排除字段名
* @return 参数字符串
*/
@@ -207,7 +207,7 @@ public class LogAspect {
/**
* 序列化单个方法参数并移除排除字段
*
* @param arg 参数对象
* @param arg 参数对象
* @param exclude 排除字段名
* @return 参数日志字符串
*/
@@ -234,7 +234,7 @@ public class LogAspect {
/**
* 将参数转为已排除指定字段的字典
*
* @param value 参数值
* @param value 参数值
* @param exclude 排除字段名
* @return 已过滤字段的字典
*/
@@ -250,7 +250,7 @@ public class LogAspect {
/**
* 限制日志字段长度
*
* @param value 原始字符串
* @param value 原始字符串
* @param maxLength 最大长度
* @return 截断后的字符串
*/
@@ -303,6 +303,6 @@ public class LogAspect {
*/
private boolean isFilterValue(Object value) {
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;
import lombok.Data;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@@ -18,7 +18,7 @@ public record McpResourceReadResult(
* MCP SDK 资源读取结果转换为带服务端名称的项目结果对象
*
* @param serverName MCP 服务端名称
* @param result MCP SDK 资源读取结果
* @param result MCP SDK 资源读取结果
* @return MCP 资源读取结果
*/
public static McpResourceReadResult of(String serverName, McpSchema.ReadResourceResult result) {
@@ -20,7 +20,7 @@ public record McpToolCallResult(
* MCP SDK 工具调用结果转换为带服务端名称的项目结果对象
*
* @param serverName MCP 服务端名称
* @param result MCP SDK 工具调用结果
* @param result MCP SDK 工具调用结果
* @return MCP 工具调用结果
*/
public static McpToolCallResult of(String serverName, McpSchema.CallToolResult result) {
@@ -1,5 +1,8 @@
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.MqttClientGlobalMessageListener;
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.context.annotation.Bean;
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.ThreadFactory;
@@ -1,9 +1,9 @@
package org.dromara.common.mqtt.listener;
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.MqttClientCreator;
import org.tio.core.ChannelContext;
/**
* 客户端连接状态监听
@@ -1,9 +1,9 @@
package org.dromara.common.mqtt.listener;
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.core.client.IMqttClientGlobalMessageListener;
import org.tio.core.ChannelContext;
import java.nio.charset.StandardCharsets;
@@ -19,7 +19,7 @@ public class MqttClientGlobalMessageListener implements IMqttClientGlobalMessage
* 接收并记录全局 MQTT 消息
*
* @param context 通道上下文
* @param topic 主题
* @param topic 主题
* @param message MQTT 消息对象
* @param payload 消息载荷
*/
@@ -27,7 +27,7 @@ public class DataPermissionPointcutAdvisor extends AbstractPointcutAdvisor {
*/
public DataPermissionPointcutAdvisor() {
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 查询字段
* @return this
*/
@Override
@SafeVarargs
public final LambdaCrudChainWrapper<T, V> select(SFunction<T, ?>... columns) {
return select(true, CollectionUtils.toList(columns));
@@ -134,6 +135,7 @@ public class LambdaCrudChainWrapper<T, V> extends AbstractLambdaWrapper<T, Lambd
* @param columns 查询字段
* @return this
*/
@Override
@SafeVarargs
public final LambdaCrudChainWrapper<T, V> select(boolean condition, SFunction<T, ?>... 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.support.LambdaMeta;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.ibatis.reflection.property.PropertyNamer;
import java.util.regex.Pattern;
@@ -14,6 +16,7 @@ import java.util.regex.Pattern;
*
* @author Lion Li
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
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 AggregateSelectUtils() {
}
/**
* 追加查询字段 SQL
*
@@ -400,6 +400,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param value 条件值
* @return 当前查询构造辅助对象
*/
@Override
public LambdaQueryBuilder<T> eq(boolean condition, SFunction<T, ?> column, Object value) {
wrapper.eq(condition, column, value);
return this;
@@ -425,6 +426,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param value 条件值
* @return 当前查询构造辅助对象
*/
@Override
public LambdaQueryBuilder<T> ne(boolean condition, SFunction<T, ?> column, Object value) {
wrapper.ne(condition, column, value);
return this;
@@ -450,6 +452,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param value 条件值
* @return 当前查询构造辅助对象
*/
@Override
public LambdaQueryBuilder<T> gt(boolean condition, SFunction<T, ?> column, Object value) {
wrapper.gt(condition, column, value);
return this;
@@ -475,6 +478,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param value 条件值
* @return 当前查询构造辅助对象
*/
@Override
public LambdaQueryBuilder<T> ge(boolean condition, SFunction<T, ?> column, Object value) {
wrapper.ge(condition, column, value);
return this;
@@ -500,6 +504,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param value 条件值
* @return 当前查询构造辅助对象
*/
@Override
public LambdaQueryBuilder<T> lt(boolean condition, SFunction<T, ?> column, Object value) {
wrapper.lt(condition, column, value);
return this;
@@ -525,6 +530,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param value 条件值
* @return 当前查询构造辅助对象
*/
@Override
public LambdaQueryBuilder<T> le(boolean condition, SFunction<T, ?> column, Object value) {
wrapper.le(condition, column, value);
return this;
@@ -550,6 +556,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param value 条件值
* @return 当前查询构造辅助对象
*/
@Override
public LambdaQueryBuilder<T> like(boolean condition, SFunction<T, ?> column, Object value) {
wrapper.like(condition, column, value);
return this;
@@ -575,6 +582,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param value 条件值
* @return 当前查询构造辅助对象
*/
@Override
public LambdaQueryBuilder<T> notLike(boolean condition, SFunction<T, ?> column, Object value) {
wrapper.notLike(condition, column, value);
return this;
@@ -600,6 +608,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param value 条件值
* @return 当前查询构造辅助对象
*/
@Override
public LambdaQueryBuilder<T> likeLeft(boolean condition, SFunction<T, ?> column, Object value) {
wrapper.likeLeft(condition, column, value);
return this;
@@ -625,6 +634,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param value 条件值
* @return 当前查询构造辅助对象
*/
@Override
public LambdaQueryBuilder<T> likeRight(boolean condition, SFunction<T, ?> column, Object value) {
wrapper.likeRight(condition, column, value);
return this;
@@ -652,6 +662,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param end 结束值
* @return 当前查询构造辅助对象
*/
@Override
public LambdaQueryBuilder<T> between(boolean condition, SFunction<T, ?> column, Object begin, Object end) {
wrapper.between(condition, column, begin, end);
return this;
@@ -679,6 +690,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param end 结束值
* @return 当前查询构造辅助对象
*/
@Override
public LambdaQueryBuilder<T> notBetween(boolean condition, SFunction<T, ?> column, Object begin, Object end) {
wrapper.notBetween(condition, column, begin, end);
return this;
@@ -718,7 +730,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @return 当前查询构造辅助对象
*/
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;
}
@@ -732,7 +744,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @return 当前查询构造辅助对象
*/
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;
}
@@ -802,6 +814,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param values 条件值集合
* @return 当前查询构造辅助对象
*/
@Override
public LambdaQueryBuilder<T> in(boolean condition, SFunction<T, ?> column, Collection<?> values) {
wrapper.in(condition, column, values);
return this;
@@ -827,6 +840,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param values 条件值数组
* @return 当前查询构造辅助对象
*/
@Override
public LambdaQueryBuilder<T> in(boolean condition, SFunction<T, ?> column, Object... values) {
wrapper.in(condition, column, values);
return this;
@@ -852,6 +866,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param values 条件值集合
* @return 当前查询构造辅助对象
*/
@Override
public LambdaQueryBuilder<T> notIn(boolean condition, SFunction<T, ?> column, Collection<?> values) {
wrapper.notIn(condition, column, values);
return this;
@@ -877,6 +892,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param values 条件值数组
* @return 当前查询构造辅助对象
*/
@Override
public LambdaQueryBuilder<T> notIn(boolean condition, SFunction<T, ?> column, Object... values) {
wrapper.notIn(condition, column, values);
return this;
@@ -1219,6 +1235,7 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
* @param values SQL 片段参数
* @return 当前查询构造辅助对象
*/
@Override
public LambdaQueryBuilder<T> apply(boolean condition, String applySql, Object... values) {
wrapper.apply(condition, applySql, values);
return this;
@@ -1368,7 +1385,6 @@ public final class LambdaQueryBuilder<T> implements LambdaQueryCondition<T, Lamb
*
* @return 聚合查询包装器
*/
@SuppressWarnings("unchecked")
private AggregateLambdaQueryWrapper<T> aggregateWrapper() {
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.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.Assert;
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.*;
import com.baomidou.mybatisplus.core.toolkit.support.ColumnCache;
import com.baomidou.mybatisplus.core.toolkit.support.LambdaMeta;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import org.apache.ibatis.reflection.property.PropertyNamer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -23,7 +23,7 @@ public class MybatisExceptionHandler {
/**
* 处理主键或唯一索引冲突异常
*
* @param e 异常信息
* @param e 异常信息
* @param request 当前请求
* @return 统一失败响应
*/
@@ -37,7 +37,7 @@ public class MybatisExceptionHandler {
/**
* 处理 MyBatis 系统异常
*
* @param e 异常信息
* @param e 异常信息
* @param request 当前请求
* @return 统一失败响应
*/
@@ -65,8 +65,8 @@ public class PlusDataPermissionHandler {
/**
* 获取数据过滤条件的 SQL 片段
*
* @param where 原始的查询条件表达式
* @param isSelect 是否为查询语句
* @param where 原始的查询条件表达式
* @param isSelect 是否为查询语句
* @return 数据过滤条件的 SQL 片段
*/
public Expression getSqlSegment(Expression where, boolean isSelect) {
@@ -112,7 +112,7 @@ public class PlusDataPermissionHandler {
}
Object defaultValue = "-1";
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);
DataPermissionHelper.getContext().forEach(context::setVariable);
Set<String> conditions = new HashSet<>();
@@ -283,7 +283,7 @@ public class PlusDataPermissionHandler {
/**
* 优先从方法再从类上查找指定注解
*
* @param handlerMethod 当前请求处理方法
* @param handlerMethod 当前请求处理方法
* @param annotationType 注解类型
* @param <A> 注解类型
* @return 注解对象未配置时返回 null
@@ -35,10 +35,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.time.ZoneOffset;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -203,7 +200,7 @@ public abstract class AbstractOssClientImpl implements OssClient {
.orElse("");
String mergedPrefix = mergePrefix(defaultPrefix, businessPrefix);
String suffix = suffix(fileName);
String datePath = DateUtils.datePath();
String datePath = DateUtils.format(new Date(), "yyyy/MM/dd");
String uuid = IdUtil.fastSimpleUUID();
String path = mergedPrefix.isEmpty() ? datePath + StringUtils.SLASH + uuid : mergedPrefix + StringUtils.SLASH + datePath + StringUtils.SLASH + uuid;
return path + suffix;
@@ -29,7 +29,7 @@ public class DefaultOssClientImpl extends AbstractOssClientImpl {
* 构造默认 S3 存储客户端
*
* @param clientId 客户端 ID
* @param config S3 存储客户端配置
* @param config S3 存储客户端配置
*/
public DefaultOssClientImpl(String clientId, OssClientConfig config) {
super(clientId, config);
@@ -133,9 +133,9 @@ public interface OssClient extends AutoCloseable {
/**
* 将本地路径对应的文件上传到指定存储桶
*
* @param bucket 存储桶名称
* @param key 对象键
* @param path 文件路径
* @param bucket 存储桶名称
* @param key 对象键
* @param path 文件路径
* @param options 可选项
* @return 上传结果
*/
@@ -154,9 +154,9 @@ public interface OssClient extends AutoCloseable {
/**
* 将文件上传到指定存储桶
*
* @param bucket 存储桶名称
* @param key 对象键
* @param file 文件对象
* @param bucket 存储桶名称
* @param key 对象键
* @param file 文件对象
* @param options 可选项
* @return 上传结果
*/
@@ -175,9 +175,9 @@ public interface OssClient extends AutoCloseable {
/**
* 将随机访问文件上传到指定存储桶
*
* @param bucket 存储桶名称
* @param key 对象键
* @param file 随机访问文件
* @param bucket 存储桶名称
* @param key 对象键
* @param file 随机访问文件
* @param options 可选项
* @return 上传结果
*/
@@ -200,7 +200,7 @@ public interface OssClient extends AutoCloseable {
* @param key 对象键
* @param channel 数据通道
* @param contentLength 内容长度
* @param options 可选项
* @param options 可选项
* @return 上传结果
*/
PutObjectResult bucketUpload(String bucket, String key, ReadableByteChannel channel, long contentLength, Options options);
@@ -223,7 +223,7 @@ public interface OssClient extends AutoCloseable {
* @param key 对象键
* @param in 输入流
* @param contentLength 内容长度
* @param options 可选项
* @param options 可选项
* @return 上传结果
*/
PutObjectResult bucketUpload(String bucket, String key, InputStream in, long contentLength, Options options);
@@ -242,9 +242,9 @@ public interface OssClient extends AutoCloseable {
/**
* 将字节数组上传到指定存储桶
*
* @param bucket 存储桶名称
* @param key 对象键
* @param data 字节数组
* @param bucket 存储桶名称
* @param key 对象键
* @param data 字节数组
* @param options 可选项
* @return 上传结果
*/
@@ -374,8 +374,8 @@ public interface OssClient extends AutoCloseable {
/**
* 将本地路径对应的文件上传到默认存储桶
*
* @param key 对象键
* @param path 文件路径
* @param key 对象键
* @param path 文件路径
* @param options 可选项
* @return 上传结果
*/
@@ -393,8 +393,8 @@ public interface OssClient extends AutoCloseable {
/**
* 将文件上传到默认存储桶
*
* @param key 对象键
* @param file 文件对象
* @param key 对象键
* @param file 文件对象
* @param options 可选项
* @return 上传结果
*/
@@ -412,8 +412,8 @@ public interface OssClient extends AutoCloseable {
/**
* 将随机访问文件上传到默认存储桶
*
* @param key 对象键
* @param file 随机访问文件
* @param key 对象键
* @param file 随机访问文件
* @param options 可选项
* @return 上传结果
*/
@@ -431,10 +431,10 @@ public interface OssClient extends AutoCloseable {
/**
* 将可读通道中的数据上传到默认存储桶
*
* @param key 对象键
* @param channel 数据通道
* @param key 对象键
* @param channel 数据通道
* @param contentLength 内容长度
* @param options 可选项
* @param options 可选项
* @return 上传结果
*/
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 contentLength 内容长度
* @param options 可选项
* @param options 可选项
* @return 上传结果
*/
PutObjectResult upload(String key, InputStream in, long contentLength, Options options);
@@ -473,8 +473,8 @@ public interface OssClient extends AutoCloseable {
/**
* 将字节数组上传到默认存储桶
*
* @param key 对象键
* @param data 字节数组
* @param key 对象键
* @param data 字节数组
* @param options 可选项
* @return 上传结果
*/
@@ -5,19 +5,20 @@ package org.dromara.common.oss.config;
*
* @param <T> 配置类型
* @param <B> 配置构建器类型
*
* @author 秋辞未寒
*/
public interface Config<T,B> {
public interface Config<T, B> {
/**
* 配置对象拷贝
*
* @return 拷贝后的新配置对象
*/
T copy();
/**
* 转为构建器对象
*
* @return 构建器对象
*/
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 秋辞未寒
*/
public record GetObjectResult(
String key,
String eTag,
LocalDateTime lastModified,
long size,
String contentType,
String contentDisposition,
String contentRange,
String contentEncoding,
String contentLanguage,
Map<String, String> metadata
String key,
String eTag,
LocalDateTime lastModified,
long size,
String contentType,
String contentDisposition,
String contentRange,
String contentEncoding,
String contentLanguage,
Map<String, String> metadata
) {
/**
* 构建文件对象获取结果
*
* @param key 对象 Key
* @param eTag 对象 ETag
* @param lastModified 最后修改时间
* @param size 对象大小
* @param contentType 内容类型
* @param key 对象 Key
* @param eTag 对象 ETag
* @param lastModified 最后修改时间
* @param size 对象大小
* @param contentType 内容类型
* @param contentDisposition 内容处置方式
* @param contentRange 内容范围
* @param contentEncoding 内容编码
* @param contentLanguage 内容语言
* @param metadata 自定义元数据
* @param contentRange 内容范围
* @param contentEncoding 内容编码
* @param contentLanguage 内容语言
* @param metadata 自定义元数据
* @return 文件对象获取结果
*/
public static GetObjectResult form(String key, String eTag, LocalDateTime lastModified, long size
, String contentType, String contentDisposition, String contentRange, String contentEncoding, String contentLanguage
, Map<String, String> metadata) {
, String contentType, String contentDisposition, String contentRange, String contentEncoding, String contentLanguage
, Map<String, String> metadata) {
return new GetObjectResult(key, eTag, lastModified, size, contentType, contentDisposition, contentRange, contentEncoding, contentLanguage, metadata);
}
@@ -11,8 +11,8 @@ import java.util.Optional;
* @author 秋辞未寒
*/
public record HandleAsyncResult<T>(
T result,
Throwable error
T result,
Throwable error
) {
/**
@@ -60,7 +60,7 @@ public record HandleAsyncResult<T>(
* @return 异步处理结果
*/
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 异步处理结果
*/
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 异步处理结果
*/
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 秋辞未寒
*/
public record PutObjectResult(
String url,
String key,
String eTag,
long size
String url,
String key,
String eTag,
long size
) {
/**
@@ -3,11 +3,7 @@ package org.dromara.common.push.annotation;
import org.dromara.common.push.condition.MessageTransportCondition;
import org.springframework.context.annotation.Conditional;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.*;
/**
* 按消息推送传输方式启用组件
@@ -19,7 +19,7 @@ public class MessageTransportCondition implements Condition {
/**
* 判断当前消息推送配置是否匹配注解声明的传输方式
*
* @param context 条件上下文
* @param context 条件上下文
* @param metadata 注解元数据
* @return 是否匹配
*/
@@ -1,10 +1,10 @@
package org.dromara.common.push.config;
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.handler.PlusWebSocketHandler;
import org.dromara.common.push.interceptor.PlusWebSocketInterceptor;
import org.dromara.common.push.listener.MessageTopicListener;
import org.dromara.common.push.properties.MessageProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
@@ -37,7 +37,7 @@ public class SseEmitterSessionManager implements PushSessionManager {
* 构造 SSE 会话管理器并启动心跳检测
*
* @param scheduledExecutorService 定时任务线程池
* @param messageProperties 消息推送配置
* @param messageProperties 消息推送配置
*/
public SseEmitterSessionManager(ScheduledExecutorService scheduledExecutorService, MessageProperties messageProperties) {
this.messageProperties = messageProperties;
@@ -218,7 +218,7 @@ public class SseEmitterSessionManager implements PushSessionManager {
/**
* 向指定用户的全部本地 SSE 会话发送统一 JSON 消息
*
* @param userId 要发送消息的用户id
* @param userId 要发送消息的用户id
* @param pushDTO 要发送的消息内容
*/
public void sendMessage(Long userId, PushDTO pushDTO) {
@@ -53,7 +53,7 @@ public class RateLimiterAspect {
/**
* 在限流注解方法执行前扣减令牌令牌不足时阻断请求
*
* @param point 切点信息
* @param point 切点信息
* @param rateLimiter 限流注解配置
*/
@Before("@annotation(rateLimiter)")
@@ -89,7 +89,7 @@ public class RateLimiterAspect {
* 组装限流缓存键
*
* @param rateLimiter 限流注解配置
* @param point 切点信息
* @param point 切点信息
* @return 限流缓存键
*/
private String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
@@ -112,13 +112,13 @@ public class RateLimiterAspect {
key = expression.getValue(context, String.class);
}
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) {
// 获取请求ip
stringBuffer.append(ServletUtils.getClientIP()).append(":");
stringBuffer.append(ServletUtils.getClientIP()).append(StringUtils.COLON);
} else if (rateLimiter.limitType() == LimitType.CLUSTER) {
// 获取客户端实例id
stringBuffer.append(RedisUtils.getClient().getId()).append(":");
stringBuffer.append(RedisUtils.getClient().getId()).append(StringUtils.COLON);
}
return stringBuffer.append(key).toString();
}

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