418 Commits

Author SHA1 Message Date
9d0084409e update 优化 支持后端监听器解析节点扩展数据到流程变量(按钮权限 抄送人 扩展变量) 2025-08-28 17:56:10 +08:00
ee02f46dfd update 优化 支持前端返回节点扩展数据(按钮权限 抄送人 扩展变量) 2025-08-28 17:55:04 +08:00
25de0b3530 update 解析扩展属性 JSON,构建 Node 扩展属性对象,增强代码可读性 2025-08-28 16:25:33 +08:00
aa76859a05 update 添加抄送设置和变量枚举,优化扩展节点配置逻辑 2025-08-28 15:00:32 +08:00
71b70a59fe fix 修复 判断错误导致新增报错问题 2025-08-28 10:59:00 +08:00
05c9528549 !752 update 优化流程实例业务扩展的保存和删除逻辑,增强代码可读性
* update 优化流程实例业务扩展的保存和删除逻辑,增强代码可读性
2025-08-27 11:10:03 +00:00
1feb2a3861 fix 修复 菜单与部门 未做角色状态判断 2025-08-27 17:54:05 +08:00
237e78e80c update hutool 5.8.38 => 5.8.40 默认支持了验证码不生成负数 2025-08-27 11:58:07 +08:00
ffc3dcaec9 upadte 优化Excel单元格合并处理器代码逻辑分支 2025-08-26 17:13:35 +08:00
a94e474069 fix 修复 时间解析类异常问题 2025-08-26 16:10:17 +08:00
40a0e57870 fix 修复 时间解析类异常问题 2025-08-26 16:02:11 +08:00
c01ed34602 update fastexcel 1.2.0 => 1.3.0 2025-08-25 13:50:09 +08:00
26a99003d2 update springdoc 2.8.10 => 2.8.11
update redisson 3.50.0 => 3.51.0
2025-08-25 09:58:46 +08:00
93c886d3ed update 优化 对三方授权 redirectUri 回调地址进行url编码 2025-08-25 09:58:06 +08:00
9e1027690b update 更新 warm-flow 版本至 1.8.1 2025-08-22 10:23:37 +08:00
cc120c06fd !746 update 优化代码生成模板空格对齐
* update 优化代码生成模板空格对齐
2025-08-22 02:16:51 +00:00
3827da078a update 移除不必要的流程状态颜色配置 2025-08-22 09:54:40 +08:00
70d3505b94 update springdoc 2.8.9 => 2.8.10 2025-08-21 10:07:15 +08:00
a39a69cac5 reset 回滚错误提交 2025-08-21 09:25:56 +08:00
1dbce3ab7c fix 修复 校验租户账号余额 查询语句错误 2025-08-21 09:20:35 +08:00
9742b1b596 fix 修复 流程重新提交报错问题 2025-08-19 17:59:29 +08:00
d98d11ae2d fix 修复 流程重新提交报错问题 2025-08-19 17:48:30 +08:00
6742dcb33e fix 修复 sql书写错误 2025-08-19 17:18:37 +08:00
09a51478a5 add 新增 请假表 申请编号字段sql 2025-08-18 09:44:22 +08:00
f02601ab2c update 优化 表sql书写格式 2025-08-16 11:31:45 +08:00
ac56ca0e81 add 补充流程扩展sql 2025-08-15 21:35:42 +08:00
0fcf77e2ed add 增加流程业务扩展 2025-08-15 21:18:25 +08:00
0f0a3a181e !743 update 注册功能同步优化 验证码校验逻辑
* update 注册功能同步优化 验证码校验逻辑
2025-08-15 08:29:49 +00:00
e24e2c51e4 update 优化 验证码校验逻辑 2025-08-15 14:04:17 +08:00
2b0dd82d3d update 优化 工作流后台发起或审批可以手动设置办理人 2025-08-15 11:18:46 +08:00
b97f711eb4 update snailjob 1.7.0 => 1.7.2 2025-08-14 09:52:46 +08:00
0250ca4eb8 update warm-flow 升级 1.8.0 正式版 2025-08-14 09:51:44 +08:00
23338995d7 update warm-flow 升级 1.8.0-m3 2025-08-13 10:27:52 +08:00
84fd02e7d8 update 优化数据库类型获取和判断逻辑,增强代码可读性 2025-08-12 21:55:24 +08:00
ae5bec994d upadte 优化Excel单元格合并代码逻辑,明确处理类职责 2025-08-12 18:26:18 +08:00
8f3a1b589e upadte 优化OSS文件下载代码 2025-08-12 16:37:06 +08:00
ad6b3d4b3f update 改用发布订阅的方式替代阻塞流,优化大文件下载时的内存占用 2025-08-12 16:17:07 +08:00
e2801037cf update 优化 删除监控无用配置代码(升级之后不需要了) 2025-08-11 14:04:39 +08:00
65061f17fe update 优化 由spring自己初始化线程池 2025-08-11 09:47:08 +08:00
d0f4d93615 !739 支持 @ExcelIgnoreUnannotated 注解,修复未注解字段导致列合并错位的问题
* 优化避免每个字段都进行ExcelIgnoreUnannotated.class判断
* 支持 @ExcelIgnoreUnannotated 注解,修复未注解字段导致列合并错位的问题
2025-08-07 09:11:42 +00:00
5d69832423 update 优化以逗号拼接元素 2025-08-06 11:43:37 +08:00
0c1e39ea14 update 优化以逗号拼接元素 2025-08-06 11:18:06 +08:00
a39bc870d1 update 优化发布流程定义抛出异常
- StringUtils.join 默认会跳过 null,不会抛异常
2025-08-06 10:51:51 +08:00
7357912681 update 新增支持占位符格式的 ServiceException 构造方法
- 新增 ServiceException(String message, Object... args) 构造器,内部使用 Hutool StrFormatter.format 格式化消息
- 解决日志打印和异常抛出信息格式不统一的问题,统一使用 {} 占位符
- 优化异常消息书写,简化拼接,提升代码可读性和维护性
2025-08-06 10:43:41 +08:00
901992674e add 增加赞助商 2025-08-06 10:21:12 +08:00
7ceb85ffa0 add 增加赞助商 2025-08-06 10:08:22 +08:00
4672d7de4d fix 修复 撤销终止等操作 都变成退回的问题 2025-08-05 11:55:15 +08:00
87ab6e1744 remove 删除无用代码 2025-08-04 18:43:39 +08:00
ae0a03728b Merge branch 'dev' of https://gitee.com/dromara/RuoYi-Vue-Plus into dev 2025-08-04 18:41:02 +08:00
6fc82a59f1 add 增加启动流程并办理第一个任务接口 2025-08-04 18:40:14 +08:00
6c33fa48ec update snailjob 1.6.0 => 1.7.0(新增的网卡配置有异常等待官方修复 不耽误正常使用) 2025-08-04 14:32:56 +08:00
343d5d21d8 update 更新工作流sql 2025-08-04 11:15:57 +08:00
0ba909c52e update work-flow 1.6.8 => 1.8.0-m1 2025-08-01 18:34:09 +08:00
808ce9c25a update 优化办理人权限设置列表 2025-08-01 17:57:46 +08:00
4351fc5239 update 优化 升级jdk版本 2025-08-01 17:22:47 +08:00
a545f7fc44 update 优化 setCacheObject 简化写法 2025-08-01 16:30:33 +08:00
acfcdf4d9a update 全局替换为 Convert.toStr,优化 null 字符串处理 2025-08-01 15:29:36 +08:00
9683252783 fix 修复 配置缺失 2025-08-01 09:19:15 +08:00
49c00e162b update 删除无用依赖引入 2025-07-31 11:25:07 +08:00
076a0a44fa update 优化 isLogin 判断逻辑 2025-07-31 10:17:27 +08:00
0d93589d99 update 优化 PlusSaTokenDao 删除key同步删除本地缓存 2025-07-31 10:11:46 +08:00
54a8189e27 fix 修复 依赖漏删 2025-07-31 09:21:18 +08:00
84f17011ad fix 修复 监控代码报错问题 2025-07-30 16:57:57 +08:00
f47bd39644 update 优化 springboot 3.5 新特性与过期代码 2025-07-30 16:10:00 +08:00
89f9617ccb update springboot 3.4.7 => 3.5.4
update springboot-admin 3.4.7 => 3.5.1
update springdoc 2.8.8 => 2.8.9
update lombok 1.18.36 => 1.18.38
2025-07-30 15:09:18 +08:00
bf10a13088 update 优化 增加selectOne使用注意事项 2025-07-30 15:07:38 +08:00
f2e0361fb6 !734 update 重写selectOne方法
* update 重写selectOne方法
2025-07-30 02:46:51 +00:00
554152635d update 优化数据权限注解切点逻辑,使切点逻辑更清晰 2025-07-29 15:52:08 +08:00
b379574637 update 优化 压制配置警告 2025-07-29 14:57:42 +08:00
6a556cc6ff fix 修复 snailjob 未判断配置空的情况 2025-07-29 14:27:18 +08:00
a6950275ad update 优化 调整包名 2025-07-29 11:27:42 +08:00
58b1bf5c33 !731 update 把数据权限注解全部交给AOP处理,使用自定义动态方法匹配器匹配注解
* update 把数据权限注解全部交给AOP处理,使用自定义动态方法匹配器匹配注解
2025-07-29 03:25:59 +00:00
c85f693ca6 update 优化 调整自动审批代码逻辑 2025-07-28 17:09:45 +08:00
5f466fd0c4 update 优化 getBackTaskNode 获取驳回节点接口 如果是委派直接返回当前节点 不允许驳回到其他节点 2025-07-28 16:25:20 +08:00
127eaf936c update 增加国内文档加速地址 2025-07-28 13:38:27 +08:00
fcd8556076 !732 [fix] 解决委托、转办时nextTasks为空导致空指针的问题
* [fix] 解决委托、转办时nextTasks为空导致空指针的问题
2025-07-28 02:24:27 +00:00
0512781513 update 优化代码 2025-07-28 09:58:40 +08:00
may
2472359adb add 增加下一节点执行人是当前任务处理人自动审批
fix 修复已完成的实例删除失败
2025-07-27 10:44:57 +08:00
29d4bb4e59 fix 修复权限判断逻辑 2025-07-26 16:58:26 +08:00
cce95424ce fix 修复权限判断逻辑 2025-07-26 16:40:07 +08:00
may
8d7358e663 update 调整变量修改 2025-07-25 23:18:28 +08:00
may
240f10ab45 update 调整变量修改 2025-07-25 23:16:22 +08:00
48213bc9c9 fix 修复 个人中心数据被脱敏问题 2025-07-25 16:36:24 +08:00
3995d9699d fix 修复 权限为null导致报错问题 2025-07-25 15:47:16 +08:00
ecd4e3eaf0 fix 修复 监听器变量使用错误 2025-07-24 15:23:23 +08:00
2e3a42c669 update 优化 调整上传超时时间 2025-07-23 17:46:30 +08:00
82997fc6cd update 优化 getInfo 接口忽略数据权限 2025-07-22 18:08:44 +08:00
0dce571270 add 增加催办接口,调整消息发送 2025-07-22 11:46:04 +08:00
9375578925 add 增加修改流程变量接口 2025-07-22 10:39:17 +08:00
e19ccf5064 remove 删除无用导入 2025-07-22 09:49:40 +08:00
1cea7b72d7 update 优化 去除无用配置 2025-07-19 23:48:30 +08:00
5da9ddf5e3 update 新增赞助商 2025-07-19 23:47:29 +08:00
93ee01c6b9 update 更新snailjob服务端配置 2025-07-19 18:07:09 +08:00
acd30fda3c update 优化 删除无用sql 2025-07-18 23:52:28 +08:00
3f62a76cc8 update 更新snailjob版本到1.6.0 2025-07-18 18:26:09 +08:00
b0b4e573f6 fix 修复 oracle数据库无法使用不等于语法问题 2025-07-17 16:16:28 +08:00
de61899eed update 优化 对登录也租户列表接口进行限流 防止盗刷 2025-07-17 15:15:28 +08:00
3a9bdb36f1 update 优化 增加请求流程后端发起demo案例 2025-07-17 14:28:37 +08:00
may
b815b8e574 fix 修复退回后审批记录申请人错误 #ICMEJ1 2025-07-15 20:22:07 +08:00
45edee4e63 update 优化 支持在监听器设置流程变量 2025-07-15 16:57:44 +08:00
868bc492a2 update 优化 支持在监听器设置流程变量 2025-07-15 16:56:57 +08:00
90fef1bb17 update 优化 菜单权限查询 2025-07-14 10:58:12 +08:00
may
d79b48ea99 fix 修复设计器画线驳回驳回到申请人后状态未修改 2025-07-12 14:49:32 +08:00
f6993a1491 update 优化 日志脱敏支持List参数 2025-07-11 18:14:14 +08:00
6b0b7382a6 update 优化判断流程是否已结束 2025-07-10 17:36:42 +08:00
c41add355f update 优化 任务创建监听器 使用下一个节点的任务数据 2025-07-10 17:05:33 +08:00
74e3d232f5 update 优化 工作流任务创建监听器 传递流程参数 2025-07-09 10:43:22 +08:00
03fca40c7d update 优化 监控使用springSecurity新语法 2025-07-09 09:56:32 +08:00
b2ad257bd8 fix 修复 代码生成 setIsRequired 标志写反 2025-07-09 09:35:35 +08:00
7e4f0d73f4 update 优化流程分类新增修改 2025-07-08 18:01:38 +08:00
2ec802f17f fix 修复 satokenDao 无法更新已存在数据的ttl问题 2025-07-08 14:33:50 +08:00
e0ce662c28 update 优化SpEL表达式回显查询条件 2025-07-07 16:10:34 +08:00
3d9ed1b92f update 优化 sse 登录校验 避免大量报错 2025-07-07 15:45:34 +08:00
d4e6e70c43 update 优化 sql版本号更新 2025-07-07 15:30:22 +08:00
2095a96e67 update 优化 删除无用接口 2025-07-07 15:27:49 +08:00
328b61b252 !726 update 优化租户字典同步逻辑代码并添加注释
* update 优化字典同步逻辑代码并添加注释
2025-07-07 01:11:31 +00:00
781463417c update 优化SpEL表达式回显名称 2025-07-06 19:24:37 +08:00
446a14b928 update SpEL表达式回显名称 2025-07-06 19:21:23 +08:00
6c2518640b update 补充工作流动态启用注解 2025-07-06 17:50:25 +08:00
d8d138092f update 优化接口防重和加锁 2025-07-06 17:04:46 +08:00
ec31b736c7 !725 更新 pr!723 xml
* update: 更新 mapper.xml ;
* update: 更新 FlowSpelBo 参数空值校验 ;
2025-07-06 06:08:44 +00:00
e7467b2c5c !724 更新 pr!723 参数校验以及sql脚本
* update: 更新 FlowSpelBo 参数空值校验 ;
2025-07-06 06:02:44 +00:00
8050e2f1b1 update 优化 !pr723 排除租户全局使用 优化sql脚本 2025-07-06 11:16:23 +08:00
7de4559b4a update 优化 !pr723 排除租户全局使用 优化sql脚本 2025-07-06 11:10:18 +08:00
fc9c0d7657 update 优化 !pr723 排除租户全局使用 优化sql脚本 2025-07-06 11:05:46 +08:00
ab3037dc4f !723 新增工作流扩展spel表达式
* update: 更新变量名称以及初始化数据 ;
* add: 新增 FlowSpel sql 脚本 ;
2025-07-06 02:54:04 +00:00
8281b838b9 update 优化 删除无用注释 导致代码报null问题 2025-07-06 10:41:18 +08:00
2bc7171abd fix 修复密码校验误删字段 2025-07-04 19:29:30 +08:00
4f99487d24 update 优化密码校验 2025-07-04 19:18:42 +08:00
a7cddc8d40 update 优化校验角色是否有数据权限 2025-07-04 18:52:29 +08:00
f3c4c02d73 update 优化校验角色是否有数据权限 2025-07-04 18:39:54 +08:00
eb631360f4 update 优化 删除无用注释 2025-07-04 18:19:50 +08:00
a62bf04428 !721 update 发号器工具类便利性优化
* update 发号器工具类便利性优化
2025-07-04 09:46:23 +00:00
7147f81b42 fix 修复 错误修改导致页面逻辑错误 2025-07-04 17:42:19 +08:00
c9098563ca fix 修复 数据权限字段编辑错误 2025-07-04 16:55:20 +08:00
d4a8c25eab update 优化数据权限 2025-07-04 16:53:18 +08:00
0ddba506bf update 优化新增角色信息 2025-07-04 16:16:12 +08:00
d02bea85cb update 优化新增用户岗位信息判空逻辑 2025-07-04 15:54:39 +08:00
d27c58bfe8 docs 补充重构数据权限注释 2025-07-04 15:35:04 +08:00
34bb51f5c0 update 优化 增加岗位修改校验 2025-07-04 15:29:51 +08:00
64c37aaec6 update 重构用户 角色 部门 菜单的数据权限设计逻辑更符合实际业务场景与优化查询写法提高效率 2025-07-04 14:50:33 +08:00
3de036adde update 优化 屏蔽掉无用接口 2025-07-04 09:34:15 +08:00
e0df8c15d8 update 优化 修改类名 避免无用扫描 2025-07-04 09:19:29 +08:00
176793e15b update 优化 工作流代码写法 2025-07-03 14:48:53 +08:00
589ec1fdbc update 优化 增加oss扩展contentType存储 2025-07-03 10:39:23 +08:00
b421c8d017 !711 update 优化附件扩展字段对象(存储在 SysOss.ext1 的 JSON 字符串中)
* update 优化文件上传附件扩展字段对象
* update 优化保存文件的大小,方便前端进行分片下载
2025-07-03 02:20:53 +00:00
e2200bac71 update 优化流程图按审批人分组去重 2025-07-02 19:08:52 +08:00
f8950d1e20 update 优化获取流程记录 2025-07-02 18:21:53 +08:00
9dfe9f610d !715 update 优化工作流待办任务查询
* update 优化工作流待办任务查询
2025-07-02 09:15:56 +00:00
17610e8721 remove 删除 无用注解 2025-07-02 14:55:44 +08:00
f29b787767 fix 修复 有某些无聊人士 对一个demo案例提漏洞 CVE-2025-6925 2025-07-02 14:35:25 +08:00
9775283a24 !714 update 优化工作流小改动
* update 优化工作流小改动
2025-07-02 05:17:43 +00:00
debc73d7d4 !713 update 优化StreamUtils使用以及岗位删除优化
* update 优化命名含义
* update 优化StreamUtils使用以及岗位删除优化
2025-07-02 05:17:18 +00:00
d501e82541 🐳🐳🐳发布 5.4.1 小步迭代修复问题 2025-07-01 09:11:10 +08:00
3002585e63 fix 修复 修改数据权限漏改语句 2025-06-30 16:31:58 +08:00
60aca2eef3 !710 update 使用新版数据权限
* update 使用新版数据权限
2025-06-30 01:42:51 +00:00
5baf342478 !708 update 优化代码小改动
* update 优化代码小改动的逻辑性错误
* update 优化代码小改动
2025-06-27 05:34:43 +00:00
3f9919fbee update 优化 Redis缓存监控接口 手动归还连接给连接池 提高效率 2025-06-27 10:19:26 +08:00
682d8b0099 !705 update 优化分页写法
* update 优化分页写法
2025-06-27 01:32:55 +00:00
bbabffe191 !702 update 优化参数配置
* update 优化参数配置
2025-06-27 01:10:26 +00:00
6722f2eeed !703 update 优化客户端管理
* update 优化客户端管理
2025-06-27 01:07:11 +00:00
5a9728c868 update 优化流程查询以及多根节点构建树结构 2025-06-26 17:20:04 +08:00
eea96e87d9 update 优化构建多根节点的树结构(支持多个顶级节点) 2025-06-26 15:10:17 +08:00
e659740cb8 Revert "update 升级warm-flow1.7.4->1.7.5-m2 优化流程图悬浮窗"
This reverts commit 8f5d60f543.
2025-06-26 07:07:03 +00:00
314909a536 fix 修复 excel 备注与必填注解指定下标位置问题 去除下标跟随主要注解顺序 2025-06-26 14:04:36 +08:00
8f5d60f543 update 升级warm-flow1.7.4->1.7.5-m2 优化流程图悬浮窗 2025-06-26 11:26:17 +08:00
1ad0d5387b fix 修复 单元格样式覆盖问题 2025-06-26 10:41:06 +08:00
770c3bd03e fix 修复 删除错误的注解导致前端时间不显示问题 2025-06-26 09:53:40 +08:00
4b04a4bf09 update 优化全局日期格式转换配置,提升日期参数解析兼容性 2025-06-24 16:10:26 +08:00
1598447f6b Revert "update 优化SSE连接"
This reverts commit a8a1db4463.
2025-06-24 06:51:28 +00:00
a8a1db4463 update 优化SSE连接 2025-06-24 14:37:02 +08:00
4b47053dcf fix 修复 超时时间单位设置错误 应该是毫秒 2025-06-24 11:42:22 +08:00
03054fc1e8 reset 回滚aws-s3版本 有未知问题 2025-06-24 11:24:28 +08:00
9dce540a09 fix 修复 sqlserver 字段长度错误 2025-06-23 18:17:41 +08:00
534182deff !698 fix 修复 办理任务时未传参数,导致执行任务无法获取到任务参数的问题
* fix 修复 办理任务时未传参数,导致执行任务无法获取到任务参数的问题
2025-06-23 08:57:30 +00:00
4577c45110 fix 修复 升级anyline返回值类型变更导致问题 2025-06-23 14:55:51 +08:00
0796791ec9 fix 修复 升级anyline返回值类型变更导致问题 2025-06-23 14:42:06 +08:00
84baac0a4f update 优化 sse 超时时间设置为一天 避免连接之后直接关闭浏览器导致连接停滞 2025-06-22 16:39:05 +08:00
74d257a610 update 更新工作流sql(小改动) 2025-06-20 11:01:17 +08:00
cb8fa6ff9a update QueueUtils 与相关代码标记过期(redisson 新版本已经将队列功能标记删除 一些技术问题无法解决 建议搭建MQ使用) 2025-06-20 10:25:43 +08:00
c157012807 update spring-boot 3.4.6 => 3.4.7
update satoken 1.42.0 => 1.44.0
update hutool 5.8.35 => 5.8.38
update redisson 3.45.1 => 3.50.0
update aws-s3 2.28.22 => 2.31.67
update anyline 8.7.2-20250101 => 8.7.2-20250603
update maven-jar-plugin 3.2.2 => 3.4.2
update maven-war-plugin 3.2.2 => 3.4.0
update maven-compiler-plugin 3.11.0 => 3.14.0
update maven-surefire-plugin 3.1.2 => 3.5.3
2025-06-20 10:25:42 +08:00
ffa01bdb3a update 优化类型转换逻辑、删除冗余代码 2025-06-20 10:25:02 +08:00
3fa572f0a8 update 优化 去除自动注入日志警告改为默认值 避免一大堆人去定时任务搞什么登录 2025-06-20 09:27:49 +08:00
f868de1b7b fix 临时修复流程图提示信息,后续版本将通过defjson获取流程实例ID 2025-06-19 19:02:22 +08:00
fb785dc17f update 优化工作流的流程图提示信息查询 2025-06-19 18:31:58 +08:00
f3d475438f update 优化工作流的流程图提示信息 2025-06-19 18:13:34 +08:00
d7af327248 update 升级warm-flow1.7.3->1.7.4 支持流程图悬浮窗 2025-06-19 17:16:18 +08:00
bd88e27c82 update 优化 加密模块 解密拦截器 将参数一起解密了 防止参数被多次加密不正常 2025-06-17 11:03:20 +08:00
83b6addbba update 优化工作流设计器获取任务执行人默认正常状态 2025-06-17 09:49:31 +08:00
d51f3b9f4e update 优化工作流内置变量表达式解析日志级别 2025-06-17 09:17:49 +08:00
9256432532 update 优化工作流,跳过以 $ 或 # 开头的内置变量表达式解析 2025-06-16 19:55:34 +08:00
97d3a31aba fix 修复 snailjob的oracle.sql书写错误 2025-06-16 11:27:49 +08:00
d9cc85187a update 删除无用功能 2025-06-13 17:30:01 +08:00
2ff2d89b2d update 删除无用功能 2025-06-13 14:24:27 +08:00
be2e5059fd update 优化 去除snailjob的jvm参数 默认不限制 2025-06-12 17:39:34 +08:00
fad91f01ff update 优化 去除正则校验 无用配置导致问题 2025-06-09 14:54:03 +08:00
8f95374cef update 优化默认部门,不允许删除 2025-06-06 10:06:23 +08:00
7471fa7ee0 update 优化根部门不允许删除以及办理人权限名称回显 2025-06-06 09:45:06 +08:00
529f1e5dbb update 优化 租户套餐菜单查询过滤掉 租户管理相关菜单 2025-06-05 18:27:44 +08:00
eff131a1ed update 优化 忽略租户表判断改为精确匹配 2025-06-05 16:47:34 +08:00
60b0faa3c6 update 优化 将debian换为更新更契合的rockylinux(centos作者写的稳定) 升级jdk版本避免漏洞 2025-06-03 17:11:39 +08:00
b2d694b90b reset 修复 satoken异步调用需要手动传递上下文 (跟satoken无关的场景不用处理) 2025-06-03 16:13:05 +08:00
fecc564099 fix 修复 部分数据库转移符解析问题导致路由不生效 统一改为使用单斜杠处理 2025-06-03 11:17:17 +08:00
297e920179 fix 修复 satoken异步调用需要手动传递上下文 2025-06-03 10:14:13 +08:00
ea9379a52f fix 修复 justauth 官方代码bug 2025-05-30 23:40:39 +08:00
0b0f2ee8ea !692 fix: 需要传递 version 字段才能启用乐观锁
* fix: 需要传递 version 字段才能启用乐观锁
2025-05-30 08:07:24 +00:00
6d2f104a43 fixbug 修复地址解析工具类报错#ICBHUQ 2025-05-29 19:50:50 +08:00
2e50e30778 fix 修复 流程数据重复更新 状态被覆盖 无法完成流程问题 2025-05-29 18:15:47 +08:00
daf79683b3 update 优化 给测试用户增加菜单权限(可不更新) 2025-05-29 17:22:14 +08:00
5849ddc160 update 优化 给测试用户增加菜单权限(可不更新) 2025-05-29 17:05:29 +08:00
c88367939c update 优化 PermissionService 无实现类也可以启动服务 2025-05-29 16:28:09 +08:00
a748d0d62c fix 修复 监听器 flowParams 为null报错问题 2025-05-29 15:38:29 +08:00
cd531f1d39 🐳🐳🐳发布 5.4.0 正式版
fix
2025-05-29 11:13:39 +08:00
92f73a4a72 update warmflow 升级到正式版 1.7.3 2025-05-29 10:41:06 +08:00
a4e3f7ea5e update 用户查询添加用户昵称条件 2025-05-29 10:39:56 +08:00
26b4561a71 update minio 更新到最新 RELEASE.2025-04-22T22-12-26Z minio 最后一个未阉割版本 不能再进行升级 在往上的版本功能被阉割 2025-05-29 10:13:33 +08:00
dbe276a33b update 调整任务监听名称 2025-05-29 09:05:36 +08:00
4ab4e1685c update 优化事件发布 2025-05-28 20:56:27 +08:00
aab87d322c update 优化 删除重复执行的代码 2025-05-28 16:56:09 +08:00
79ee168293 update 优化禁止删除默认流程分类 2025-05-28 14:51:43 +08:00
10e4b0618c update 优化 调整菜单顺序 相关菜单放到一起 2025-05-28 11:07:23 +08:00
7c3316e116 update 调整查询流程任务记录 2025-05-27 22:21:10 +08:00
8460316632 update 调整工作流放行uri 2025-05-27 22:05:55 +08:00
5d356aa6c4 update 优化 工作流代码 2025-05-27 17:29:07 +08:00
a776d28294 update 优化 删除工作流字体文件(不需要了 改成前端渲染了) 2025-05-27 17:13:32 +08:00
a002a4e7a1 !690 新增通过前端显示流程图方式和新增办理人转换接口
* feat 新增通过前端显示流程图方式
2025-05-27 09:02:05 +00:00
79ec850eca !689 update 删除退回任务bo关于驳回的节点的非空校验
* update 删除退回任务bo关于驳回的节点的非空校验
2025-05-26 12:50:03 +00:00
d1889c42a3 update 优化 权限获取 增加用户登录了但是查询的loginId是别人的场景 2025-05-26 16:31:10 +08:00
a7ea096319 fix 修复 解决通过loginId查询角色和菜单权限 而非当前用户时 报错问题 2025-05-26 15:59:04 +08:00
4e3fc7002d update minio 更新到最新 RELEASE.2025-05-24T17-08-30Z 2025-05-26 14:43:37 +08:00
1752695751 update mapstruct-plus 1.4.6 => 1.4.8 2025-05-26 14:43:05 +08:00
2b89c3f8d0 update 优化 表格增加border 2025-05-26 12:17:33 +08:00
6b387b2456 update springboot 3.4.5 => 3.4.6
update springdoc 2.8.5 => 2.8.8
update mybatis-plus 3.5.11 => 3.5.12
update springboot-admin 3.4.5 => 3.4.7
2025-05-26 11:56:05 +08:00
ffc971cf92 fix 修复 flowParams 为null导致的报错 2025-05-26 10:07:08 +08:00
887d5e85d0 add 增加logicflow流程图预览 2025-05-25 11:48:01 +08:00
8c603ff8d7 update 调整优化监听 2025-05-24 00:16:27 +08:00
a0831dda45 fix 修复 请假表单菜单sql 展示状态错误问题
Signed-off-by: 疯狂的狮子Li <15040126243@163.com>
2025-05-23 10:07:25 +00:00
336b2e8cc3 !686 feat 新增批量级联删除菜单接口
* feat 新增批量级联删除菜单接口
2025-05-23 10:04:44 +00:00
cea4855f57 update 代码生成ServiceImpl层增加日志注解 2025-05-23 11:51:09 +08:00
9fc043b105 !684 fix: sql补全分号
* fix: sql补全分号
2025-05-22 13:39:05 +00:00
d729c8ecde update 调整流程监听 2025-05-22 20:34:25 +08:00
b726a91cdb update 新增发号器工具类方法 2025-05-22 17:52:23 +08:00
qxy
05d5d9be2c !683 update 优化 nginx代理snail-job websocket参数, 解决部署到服务器后,查看日志会显示ws连接失败
* update snail-job websocket配置, 解决部署到服务器后,查看日志会显示ws连接失败
2025-05-22 09:49:27 +00:00
c40a8b2f0b update 优化 动态路由迁移到菜单管理 2025-05-22 17:41:35 +08:00
8232908b3f update 优化统一请假日期字段格式处理 2025-05-22 17:07:05 +08:00
1db0bc83b2 update 优化 工作流创建事件 将状态交给业务方处理 2025-05-21 15:42:42 +08:00
74a0ec1ec3 update 优化 字典项校验注解增加分隔符属性 2025-05-20 14:19:21 +08:00
1228e8f3ea update 统一校验器名称风格 2025-05-20 14:03:11 +08:00
737838d92f add 新增自定义字典值校验器 2025-05-20 12:31:49 +08:00
c054029cfc update snailjob 1.4.0 => 1.5.0 2025-05-19 21:23:45 +08:00
62bbd78033 add 增加 成员项目地址 2025-05-19 17:09:38 +08:00
82a5ed632f add 增加 成员项目地址 2025-05-19 16:06:17 +08:00
52ddccba3e update 升级JustAuth的钉钉和微信第三方登录 2025-05-19 15:13:12 +08:00
1a12aecd49 docs 优化工作流自定义条件注解注释 2025-05-19 12:27:56 +08:00
777ae645c5 update 手动合并冲突 优化 工作流模块下一个节点指定办理人、角色和部门转具体用户、抄送人和消息推送,改到通过全局分派监听器和完成监听器处理
update 修复退回申请人无法发送消息问题
2025-05-19 11:31:01 +08:00
21c87eee9a update 升级warm-flow1.7.0->1.7.2 2025-05-16 20:29:30 +08:00
0c8ac12e4d fix 修复 类名书写错误 2025-05-15 17:48:33 +08:00
cf871d9387 update 优化 mysql建议版本升级到8.0.42 2025-05-14 18:08:58 +08:00
90fb26fbf1 update 优化 redis建议版本升级到7.2.8 2025-05-14 18:01:46 +08:00
fdfca0b33a update 优化 代码写法 2025-05-12 18:28:24 +08:00
facd3e351f fix 修复 重构导致的问题 2025-05-12 18:22:11 +08:00
a4ad56f0eb fix 继续修复查询办理人错误使用 2025-05-12 18:18:10 +08:00
b9e5914bab fix 修复查询办理人错误使用 2025-05-12 18:11:32 +08:00
553fca28a2 update 优化假分页方法 2025-05-12 16:51:58 +08:00
97caabe0a2 update 优化 update sql 书写错误 2025-05-12 13:01:55 +08:00
122f2770b2 update 优化 !pr678 代码结构 2025-05-12 12:56:24 +08:00
748c95b30f update 优化 增加 其他数据库的升级sql 2025-05-12 10:59:12 +08:00
e0672fc753 update 优化 增加对接gitea对接前端的pr地址 2025-05-12 10:12:20 +08:00
5a1523564b !677 add 新增 对接 gitea 三方单点登录
* add 新增 对接 gitea 三方单点登录
2025-05-12 02:07:46 +00:00
0c2fe34d92 !678 add 新增自定义 Date 类型反序列化处理器(支持多种格式)
* add 新增自定义 Date 类型反序列化处理器(支持多种格式)
2025-05-12 02:04:43 +00:00
2dde42168f update 更新 readme 增加新成员项目 2025-05-12 09:33:32 +08:00
a5c2093c76 docs 修正验证码注释 2025-05-11 18:12:49 +08:00
ea74803ccc add 新增请求体读取异常处理 2025-05-11 17:57:42 +08:00
9df837f047 update 重构办理人接口 2025-05-10 16:51:56 +08:00
d6758dc47b update 调整获取申请人节点接口 2025-05-10 16:41:50 +08:00
5a8dc8e1cf update 升级warm-flow1.7.0
update 调整流程撤销
remove 删除无用代码
2025-05-10 15:51:17 +08:00
eac7f1b4e2 docs 优化EncryptUtils加解密注释 2025-05-10 14:48:34 +08:00
3749e7e724 update 更新 readme 2025-05-09 16:04:05 +08:00
b709bc0214 update 更新 readme 2025-05-09 15:00:37 +08:00
a09414110e update 优化 compose编排增加snailjob端口防止集群冲突 2025-05-08 23:14:07 +08:00
053dc50c4d update 更新 readme 2025-05-08 22:14:07 +08:00
5382722867 update 优化 多租户忽略表判断支持忽略大小写 2025-05-06 13:30:03 +08:00
5e51077347 update 优化 直接从ClassPath加载ip2region数据库文件 2025-04-29 19:01:45 +08:00
6d44069364 !675 update 查询系统菜单列表新增菜单类型与父级ID查询条件
* update 查询系统菜单列表新增菜单类型与父级ID查询条件
2025-04-29 08:29:46 +00:00
e1e3843ec0 fix 修复 snailjob http basic验证判断错误 2025-04-27 22:09:14 +08:00
15905b7022 update 放开申请人附件与抄送限制 附件改为按钮权限控制 2025-04-26 12:52:36 +08:00
1c5ae2f168 update 优化 获取地址支持IPv6逻辑 2025-04-26 00:20:34 +08:00
f29e0223a7 update 优化 获取地址支持IPv6判断而不是抛异常 2025-04-26 00:11:58 +08:00
9fbe3cf399 update springboot 3.4.4 => 3.4.5 2025-04-25 16:40:13 +08:00
e4f1da30fc update 优化日期与字符串工具类 2025-04-25 13:38:41 +08:00
21deab4bf1 update 删除已经不存在的功能 2025-04-25 09:52:01 +08:00
d7d7dcbcf7 docs 优化枚举类型注释 2025-04-23 12:30:07 +08:00
3f680385a9 update 更新协议地址 2025-04-22 16:08:38 +08:00
7ecf4bbf1c update 优化 关于excel说明 2025-04-18 17:47:05 +08:00
ae65985fbc !668 update EasyExcel升级原作者FastExcel
* remove 移除之前暂时性控制漏洞依赖
* update EasyExcel升级1.2.0并升级commons-io到最新版解决导出报方法找不到异常
* update EasyExcel升级原作者FastExcel
2025-04-18 09:30:33 +00:00
2a34c3ebb2 !672 fix: 修复 excel 合并单元格在导出在最后一行无法合并时,之前的数据合并失效问题
* fix: 修复 excel 合并单元格在导出在最后一行无法合并时,之前的数据合并失效问题
2025-04-18 05:30:12 +00:00
3b46f8c8cf update 优化返回任务指派的列表增加时间查询条件 2025-04-18 11:48:31 +08:00
7c2efb1aef update 优化 getNextNodeList 只获取中间节点用于审批 过滤其他无用节点 2025-04-17 14:00:13 +08:00
Q&Q
ea25474529 !670 fix 修复 sqlserver 脚本内的缺失符号
* fix 修复 sqlserver 脚本内的缺失符号
2025-04-16 06:22:07 +00:00
33e1d34ce5 update 优化 缓存注解支持关闭本地缓存 2025-04-14 10:09:03 +08:00
142fb33d81 update 优化 实体类统一使用包装类型 2025-04-14 09:33:36 +08:00
ee6c0388da update warm-flow 1.6.8 => 1.6.10 2025-04-14 09:05:15 +08:00
c171817d6a add 新增 一大堆snailjob的demo案例(感谢 老马) 2025-04-13 19:16:57 +08:00
71dddee146 update 优化Mybatis异常处理器 2025-04-13 13:28:40 +08:00
d456ff64f1 Revert "update 优化返回vo的boolean类型统一使用包装类"
This reverts commit 9e78fcccf7.
2025-04-13 12:49:03 +08:00
9e78fcccf7 update 优化返回vo的boolean类型统一使用包装类 2025-04-13 12:46:24 +08:00
878cd7e9f0 update 优化工作流用户查询构建 2025-04-12 17:22:44 +08:00
5c9721cfac update 优化工作流权限按钮获取,若需要扩展更多按钮权限,只需在 sources 中新增对应的枚举类或字典类型 2025-04-11 16:01:03 +08:00
31502dccc7 update satoken 1.40.0 => 1.42.0 适配所有升级项(改动较多)
SaLoginModel -> SaLoginParameter
device -> deviceType
satoken BCrypt -> hutool BCrypt(satoken不维护了)
SaTokenDao -> SaTokenDaoBySessionFollowObject(satoken做了重构封装)
sse 适配新satoken版本拦截器变化
2025-04-11 15:36:38 +08:00
538aa8d908 update 优化 统一流程demo 权限人分隔符 2025-04-11 15:07:18 +08:00
00003b2c57 fix 修复 sqlserver 脚本内的多余符号 2025-04-11 10:18:05 +08:00
a2c238d466 update bouncycastle 1.76 => 1.80 2025-04-10 17:29:44 +08:00
d89f147c54 fix 修复 临时解决sa-token使用秒 redis是毫秒导致1秒的精度问题 手动补偿(等satoken官方修复) 2025-04-10 17:22:34 +08:00
53cf1b2013 update 优化工作流获取流程变量 2025-04-10 16:44:51 +08:00
564ab331d7 update 统一工作流FlowParams构造方式为建造者模式,提升代码可读性 2025-04-10 16:01:29 +08:00
a690ece164 update 优化 调整监听器事件参数代码 2025-04-09 11:53:40 +08:00
b50904c6ff update 优化工作流流程监听增加节点信息 2025-04-09 10:57:29 +08:00
70aa14ecf8 update 优化工作流办理人权限处理器 2025-04-09 10:35:19 +08:00
c37b92978a update 调整注释 删除不存在的错误的东西 2025-04-09 10:23:59 +08:00
ef39ad7107 update 增加赞助商 2025-04-08 10:16:31 +08:00
48d3ef9818 update 优化 统一校验注解长度 2025-04-08 10:16:20 +08:00
5bf901cdcd update 优化 增加api审批简化方法 2025-04-03 13:34:41 +08:00
8e99dd306a https://gitee.com/dromara/RuoYi-Vue-Plus/issues/IBYCY7
fix 修复选择弹窗会签人员后,会签审批出现每个任务的审批人都是选择的多人
2025-04-02 21:03:58 +08:00
07fdc240d7 fix 修复 在线用户设置过期时间与客户端不同步问题 2025-04-02 14:04:41 +08:00
023ceaaf91 !666 fix: 模板导出多个字段下拉值超过100个异常,采用多个sheet的方案解决。
* fix: 模板导出多个字段下拉值超过100个异常,采用多个sheet的方案解决。
2025-04-02 05:20:53 +00:00
9e551a0b2a !665 admin Dockerfile 构建文件新增暴露 snail job 客户端端口 用于定时任务调度中心通信
* admin Dockerfile 构建文件新增暴露 snail job 客户端端口 用于定时任务调度中心通信
2025-04-01 06:29:08 +00:00
7c3f3523ea update 优化 使用 record 简化vo代码 2025-03-31 11:31:05 +08:00
40eac07789 update 优化 FlwNodeExtServiceImpl 代码实现 2025-03-31 10:56:03 +08:00
5868fadbf5 update 优化 sse 删除之后 手动触发完成 防止内存泄漏 2025-03-31 09:42:30 +08:00
124bcc4bba update 优化 sse 删除之后 手动触发完成 防止内存泄漏 2025-03-31 09:41:53 +08:00
e71d6fa983 update 优化 支持excel方法抛出json异常 2025-03-28 23:12:40 +08:00
7129ad4fac Revert "update 优化 支持excel方法抛出json异常"
This reverts commit 16923cc86a.
2025-03-28 23:11:11 +08:00
16923cc86a update 优化 支持excel方法抛出json异常 2025-03-28 22:51:32 +08:00
3761473967 🐳发布 5.3.1 正式版 2025-03-27 10:45:00 +08:00
34031cae8d update 优化 删除无用配置 2025-03-27 10:38:48 +08:00
26abb98747 fix 修复 excel模板导出数据被覆盖的问题 2025-03-27 10:03:16 +08:00
c2f67b4a77 update 优化 统一用户密码校验长度 2025-03-27 09:32:16 +08:00
4d925a4d62 update mybatis-plus 3.5.10.1 => 3.5.11
update snailjob 1.4.0-beta2 => 1.4.0 正式版
2025-03-26 15:18:39 +08:00
4e62054bd1 fix 修复 跨域未设置请求头问题(cloud版本不需要 vue版本需要) 2025-03-26 14:30:25 +08:00
fe40d7db32 发布 5.3.1-BETA2 公测版本 2025-03-21 15:17:31 +08:00
c0eeafb5cd update 优化 excel导出 下拉框支持顺序 2025-03-21 15:14:54 +08:00
5626b97a19 update 优化 兼容老版本数据权限用户写法 2025-03-21 14:42:58 +08:00
0f95e9393d update springboot 3.4.3 => 3.4.4 2025-03-21 13:55:38 +08:00
5de1ffff90 update 优化 excel导出 下拉框支持顺序 2025-03-21 13:53:23 +08:00
33698ee448 fix 修复 转换id 参数为null报错问题 2025-03-20 16:53:15 +08:00
5e5d478cf2 !660 fix 修复 pg强类型校验问题
* fix 修复 pg强类型校验问题
2025-03-19 06:50:24 +00:00
778096d100 !658 fix 修复pg数据库 强类型转换报错
* fix 修复pg数据库 强类型转换报错
2025-03-19 05:23:34 +00:00
bba163f7b4 update warm-flow 1.6.7 => 1.6.8 2025-03-19 11:34:53 +08:00
d4f792810e update 优化 简化 SysTaskAssigneeServiceImpl 代码实现 2025-03-19 10:09:32 +08:00
d14ee59912 update springboot-admin 3.4.2 => 3.4.5
update redisson 3.44.0 => 3.45.1
update sms4j 3.3.3 => 3.3.4
2025-03-18 17:25:01 +08:00
be5d69d5a5 update snailjob 1.3.0 => 1.4.0-beta2 2025-03-18 17:18:50 +08:00
97c36674e4 update 优化 打包默认跳过测试 减少心智难度 2025-03-18 13:44:35 +08:00
1f1564fad9 update 优化 修改oss枚举包名与其他模块统一 2025-03-17 09:32:53 +08:00
c79e053bea update 调整流程定义,修复使用leave6提交错误问题 2025-03-15 22:53:31 +08:00
92e9ed771b update 优化 代码书写格式 2025-03-13 17:47:32 +08:00
800c6c8ff3 update 优化工作流办理人标识符解析 2025-03-13 17:26:05 +08:00
865627fdad update 优化获取节点扩展属性,简化节点编码 2025-03-13 16:20:48 +08:00
192537672e !654 update 优化函数注释以准确描述SSE会话
* update 优化函数注释以准确描述SSE会话
2025-03-13 07:37:22 +00:00
384f9528e7 fix 修复 实体类书写错误 2025-03-13 13:39:36 +08:00
5fc76b6426 发布 5.3.1-BETA 公测版本 2025-03-13 13:26:46 +08:00
4d8a45204c fix 修复 部门树对应前端新树结构缺少字段问题 2025-03-13 12:51:42 +08:00
2de9397db8 remove 删除无用代码 2025-03-13 11:36:49 +08:00
bfc73ed214 update 优化 将crt客户端替换为Netty客户端 节约17M打包大小 2025-03-13 11:34:26 +08:00
8bf741fd5b fix 修复 关闭验证码后 限流注解仍然生效问题 2025-03-12 17:29:48 +08:00
460545a75e update 优化 字典方法命名 2025-03-11 17:56:02 +08:00
a93b30ec91 update warm-flow 1.6.7 升级正式版 2025-03-11 14:15:20 +08:00
34bac1add9 update 优化权限标识符增加通配符* 2025-03-10 11:12:39 +08:00
f028cb76fc add 增加示例 2025-03-08 00:36:44 +08:00
5a4be5fba1 add 增加条件表达式示例
fix 修复提交修改变量参数没有覆盖变量问题
2025-03-08 00:35:57 +08:00
23245b78ca fix 修复获取下一环节排他网关出现条件错误
add 增加示例
2025-03-08 00:09:08 +08:00
13ac302525 update 优化 getLoginUser 方法 支持返回多种类型登陆实体 2025-03-07 15:09:44 +08:00
96a62a3564 update 回滚错误修改 2025-03-07 15:06:36 +08:00
7adf702283 update 优化 getLoginUser 方法 支持返回多种类型登陆实体 2025-03-07 15:05:14 +08:00
279c8e014a update 补充注释 2025-03-06 22:23:48 +08:00
b7517cbbd4 update 优化 重构将 WorkflowUtils 工具类改为 FlwCommonService 更通用的业务处理 2025-03-06 17:36:47 +08:00
45eac02f4f update 优化 将工作流消息发送从工具类迁移到业务内 便于扩展 2025-03-06 16:46:39 +08:00
a6b7c3afe6 fix 修复 前端树处理优化后 后端缺字段问题 2025-03-06 16:08:15 +08:00
e99e4f6c58 update 调整获取下一节点 2025-03-05 22:32:18 +08:00
bcb97bc406 add 添加设置下一审批人 2025-03-05 22:26:31 +08:00
ad01406fc1 update 优化 text 设置默认值某些版本可能有问题 改为默认null 2025-03-05 15:33:11 +08:00
15eb08c065 update 调整获取下一节点,增加用户分页查询参数 2025-03-04 21:51:16 +08:00
2340556091 update 优化 调整字段长度 2025-03-04 10:03:13 +08:00
65c54184e8 update 优化 sys_oss 表增加扩展字段 ext1 2025-03-04 09:51:46 +08:00
9dcb7c6a12 update 优化 增加5.3.0-5.3.1升级sql 2025-03-04 09:44:11 +08:00
0c6faa751a update 删除无用sql 2025-03-04 09:34:17 +08:00
b465cb34de Merge branch 'dev' of https://gitee.com/dromara/RuoYi-Vue-Plus into dev 2025-03-03 21:02:13 +08:00
21c12a791a add 添加获取节点接口 2025-03-03 21:02:03 +08:00
723a0b6d9c update mybatis-plus 3.5.10 => 3.5.10.1 修复个bug 2025-03-03 13:49:58 +08:00
c4ef053958 add 增加节点查询 2025-03-02 00:09:18 +08:00
055d1f3bb2 update 调整流程图颜色 2025-03-01 23:55:48 +08:00
fe27d8920a add 增加按钮权限 2025-03-01 23:46:20 +08:00
df65670d3d update 优化校验框架配置类加载顺序,确保优先于默认的验证配置 2025-02-28 16:06:16 +08:00
2623d0b343 update 调整注释错误,补充注释 2025-02-28 14:46:59 +08:00
c0e0b41d13 update 更新warm-flow版本到1.6.7-M2,权限按钮改为枚举类 2025-02-28 12:05:35 +08:00
8763bfa3d3 update 优化删除无用排除 2025-02-27 17:31:08 +08:00
71180584da update 优化根据字典类型查询信息增加一级缓存 2025-02-26 09:54:20 +08:00
319a89e320 fix 修复 oracle 同步字典报错问题 2025-02-25 23:39:02 +08:00
0673cf8849 update 优化流程设计器-节点扩展属性注释 2025-02-25 22:56:27 +08:00
b537899e62 update 更新warm-flow版本到1.6.7-M1 2025-02-25 22:18:58 +08:00
7b679e60e0 update 优化工作流设计器获取任务执行人查询正常状态 2025-02-25 17:55:54 +08:00
bb475a6088 update 优化 删除无用配置类 2025-02-21 20:31:58 +08:00
a217c495d1 update 优化 租户表企业名与部门表长度保持一致 防止长度不一致报错 2025-02-21 15:40:31 +08:00
bdb86e2b3a update springboot 3.4.2 => 3.4.3
update springdoc 2.8.4 => 2.8.5
2025-02-21 10:10:09 +08:00
e8700ac44b update 优化 删除多余响应头设置 2025-02-21 09:37:24 +08:00
d80f6ab695 fix 修复 oracle 新建租户报错问题 2025-02-20 17:46:04 +08:00
381be5a1a1 fix 修复 oracle 表别名不能写as关键字 2025-02-19 13:27:46 +08:00
214cbac9a6 update 优化 ProcessTaskEvent 改名为 ProcessCreateTaskEvent 避免错误理解 2025-02-19 09:43:17 +08:00
906a031172 update 优化部门下岗位名称重复 2025-02-19 09:07:46 +08:00
236dd6e054 update 删除无用文件 2025-02-17 15:22:47 +08:00
eb17eb6559 !646 fix 修复Caffeine缓存未清空导致的部门创建显示延迟问题
* fix 修复Caffeine缓存未清空导致的部门创建显示延迟问题
2025-02-15 04:58:37 +00:00
2746af21f0 update 优化 根部门祖级列表常量和备注,以避免歧义 2025-02-13 17:39:30 +08:00
78abb617ce update 优化 nginx开启静态资源压缩 增加静态文件传输效率 2025-02-11 16:03:14 +08:00
3c57c0e7f9 update springboot-admin 3.4.1 => 3.4.2 修复重新登录404问题 2025-02-11 09:51:31 +08:00
934bbe8bd7 update springboot 3.4.1 => 3.4.2
update springdoc 2.8.3 => 2.8.4
update satoken 1.39.0 => 1.40.0
update redisson 3.43.0 => 3.44.0
2025-02-07 14:15:54 +08:00
718a010c0f Revert "fix 修复 monitor 设置 context-path 导致退出重新登录404问题"
This reverts commit d194b39e57.
2025-02-07 13:07:53 +08:00
a87071b834 fix 修复 结束监听器 flowParam 可能为null问题 2025-02-06 16:16:14 +08:00
2c598f93ab fix 修复 splitTo 转换后的list包含null问题 2025-02-06 16:15:19 +08:00
0937093851 fix 修复 sse关闭 用户id或token为空报错问题 2025-02-06 16:14:51 +08:00
373 changed files with 10524 additions and 4109 deletions

View File

@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.3.0" />
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.4.1" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
</settings>

View File

@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-server:5.3.0" />
<option name="imageTag" value="ruoyi/ruoyi-server:5.4.1" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
</settings>

View File

@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.3.0" />
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.4.1" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
</settings>

View File

@ -7,10 +7,10 @@
[![码云Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus/badge/star.svg?theme=blue)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![GitHub](https://img.shields.io/github/stars/dromara/RuoYi-Vue-Plus.svg?style=social&label=Stars)](https://github.com/dromara/RuoYi-Vue-Plus)
[![Star](https://gitcode.com/dromara/RuoYi-Vue-Plus/star/badge.svg)](https://gitcode.com/dromara/RuoYi-Vue-Plus)
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/LICENSE)
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/5.X/LICENSE)
[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
<br>
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.3.0-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.4.1-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.4-blue.svg)]()
[![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]()
[![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]()
@ -22,10 +22,12 @@
> 系统演示: [传送门](https://plus-doc.dromara.org/#/common/demo_system)
> 官方前端项目地址: [plus-ui](https://gitee.com/JavaLionLi/plus-ui)<br>
> 成员前端项目地址: 基于vben5 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)
> 官方前端项目地址: [gitee](https://gitee.com/JavaLionLi/plus-ui) - [github](https://github.com/JavaLionLi/plus-ui) - [gitcode](https://gitcode.com/dromara/plus-ui)<br>
> 成员前端项目地址: 基于vben5 [ruoyi-plus-vben5](https://gitee.com/dapppp/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>
> 文档地址: [plus-doc](https://plus-doc.dromara.org)
> 文档地址: [plus-doc](https://plus-doc.dromara.org) 国内加速: [plus-doc.top](https://plus-doc.top)
## 赞助商
@ -34,7 +36,11 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
数舵科技 软件定制开发APP小程序等 - http://www.shuduokeji.com/ <br>
引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
<font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><br>
[如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group)
Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br>
aizuda flowlong 工作流 - https://gitee.com/aizuda/flowlong <br>
Ruoyi-Plus-Uniapp - https://ruoyi.plus <br>
[如何成为赞助商 加群联系作者详谈 每日PV2500-3000 IP1700-2500](https://plus-doc.dromara.org/#/common/add_group)
# 本框架与RuoYi的功能差异
@ -75,7 +81,7 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
| 邮件 | 采用 mail-api 通用协议支持大部分邮件厂商 | 不支持 |
| 接口文档 | 采用 SpringDoc、javadoc 无注解零入侵基于java注释<br/>只需把注释写好 无需再写一大堆的文档注解了 | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成 |
| 校验框架 | 采用 Validation 支持注解与工具类校验 注解支持国际化 | 仅支持注解 且注解不支持国际化 |
| Excel框架 | 采用 Alibaba EasyExcel 基于插件化<br/>框架对其增加了很多功能 例如 自动合并相同内容 自动排列布局 字典翻译等 | 基于 POI 手写实现 功能有限 复杂 扩展性差 |
| Excel框架 | 采用 FastExcel(原Alibaba EasyExcel) 基于插件化<br/>框架对其增加了很多功能 例如 自动合并相同内容 自动排列布局 字典翻译等 | 基于 POI 手写实现 功能有限 复杂 扩展性差 |
| 工作流支持 | 支持各种复杂审批 转办 委派 加减签 会签 或签 票签 等功能 | 无 |
| 工具类框架 | 采用 Hutool、Lombok 上百种工具覆盖90%的使用需求 基于注解自动生成 get set 等简化框架大量代码 | 手写工具稳定性差易出问题 工具数量有限 代码臃肿需自己手写 get set 等 |
| 监控框架 | 采用 SpringBoot-Admin 基于SpringBoot官方 actuator 探针机制<br/>实时监控服务状态 框架还为其扩展了在线日志查看监控 | 无 |
@ -113,7 +119,6 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
| 系统接口 | 根据业务代码自动生成相关的api接口文档 | 支持 | 支持 |
| 服务监控 | 监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等 | 支持 | 仅支持单机CPU、内存、磁盘监控 |
| 缓存监控 | 对系统的缓存信息查询,命令统计等。 | 支持 | 支持 |
| 在线构建器 | 拖动表单元素生成相应的HTML代码。 | 支持 | 支持 |
| 使用案例 | 系统的一些功能案例 | 支持 | 不支持 |
## 参考文档

108
pom.xml
View File

@ -13,51 +13,51 @@
<description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
<properties>
<revision>5.3.0</revision>
<spring-boot.version>3.4.1</spring-boot.version>
<revision>5.4.1</revision>
<spring-boot.version>3.5.4</spring-boot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
<mybatis.version>3.5.16</mybatis.version>
<springdoc.version>2.8.3</springdoc.version>
<springdoc.version>2.8.11</springdoc.version>
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
<easyexcel.version>4.0.3</easyexcel.version>
<fastexcel.version>1.3.0</fastexcel.version>
<velocity.version>2.3</velocity.version>
<satoken.version>1.39.0</satoken.version>
<mybatis-plus.version>3.5.10</mybatis-plus.version>
<satoken.version>1.44.0</satoken.version>
<mybatis-plus.version>3.5.12</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.8.35</hutool.version>
<spring-boot-admin.version>3.4.1</spring-boot-admin.version>
<redisson.version>3.43.0</redisson.version>
<hutool.version>5.8.40</hutool.version>
<spring-boot-admin.version>3.5.1</spring-boot-admin.version>
<redisson.version>3.51.0</redisson.version>
<lock4j.version>2.2.7</lock4j.version>
<dynamic-ds.version>4.3.1</dynamic-ds.version>
<snailjob.version>1.3.0</snailjob.version>
<mapstruct-plus.version>1.4.6</mapstruct-plus.version>
<snailjob.version>1.7.2</snailjob.version>
<mapstruct-plus.version>1.4.8</mapstruct-plus.version>
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
<lombok.version>1.18.36</lombok.version>
<bouncycastle.version>1.76</bouncycastle.version>
<lombok.version>1.18.38</lombok.version>
<bouncycastle.version>1.80</bouncycastle.version>
<justauth.version>1.16.7</justauth.version>
<!-- 离线IP地址定位库 -->
<ip2region.version>2.7.0</ip2region.version>
<!-- OSS 配置 -->
<aws.sdk.version>2.28.22</aws.sdk.version>
<aws.crt.version>0.31.3</aws.crt.version>
<!-- SMS 配置 -->
<sms4j.version>3.3.3</sms4j.version>
<sms4j.version>3.3.4</sms4j.version>
<!-- 限制框架中的fastjson版本 -->
<fastjson.version>1.2.83</fastjson.version>
<!-- 面向运行时的D-ORM依赖 -->
<anyline.version>8.7.2-20250101</anyline.version>
<!--工作流配置-->
<warm-flow.version>1.6.6</warm-flow.version>
<anyline.version>8.7.2-20250603</anyline.version>
<!-- 工作流配置 -->
<warm-flow.version>1.8.1</warm-flow.version>
<!-- 插件版本 -->
<maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
<maven-war-plugin.version>3.2.2</maven-war-plugin.version>
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
<maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
<maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
<maven-war-plugin.version>3.4.0</maven-war-plugin.version>
<maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
<maven-surefire-plugin.version>3.5.3</maven-surefire-plugin.version>
<flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
<!-- 打包默认跳过测试 -->
<skipTests>true</skipTests>
</properties>
<profiles>
@ -118,25 +118,6 @@
<scope>import</scope>
</dependency>
<!-- Warm-Flow国产工作流引擎, 在线文档http://warm-flow.cn/ -->
<dependency>
<groupId>org.dromara.warm</groupId>
<artifactId>warm-flow-mybatis-plus-sb3-starter</artifactId>
<version>${warm-flow.version}</version>
</dependency>
<dependency>
<groupId>org.dromara.warm</groupId>
<artifactId>warm-flow-plugin-ui-sb-web</artifactId>
<version>${warm-flow.version}</version>
</dependency>
<!-- JustAuth 的依赖配置-->
<dependency>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
<version>${justauth.version}</version>
</dependency>
<!-- common 的依赖配置-->
<dependency>
<groupId>org.dromara</groupId>
@ -165,9 +146,9 @@
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>${easyexcel.version}</version>
<groupId>cn.idev.excel</groupId>
<artifactId>fastexcel</artifactId>
<version>${fastexcel.version}</version>
</dependency>
<!-- velocity代码生成使用模板 -->
@ -245,18 +226,18 @@
<artifactId>s3</artifactId>
<version>${aws.sdk.version}</version>
</dependency>
<!-- 使用AWS基于 CRT 的 S3 客户端 -->
<dependency>
<groupId>software.amazon.awssdk.crt</groupId>
<artifactId>aws-crt</artifactId>
<version>${aws.crt.version}</version>
</dependency>
<!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3-transfer-manager</artifactId>
<version>${aws.sdk.version}</version>
</dependency>
<!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>netty-nio-client</artifactId>
<version>${aws.sdk.version}</version>
</dependency>
<!--短信sms4j-->
<dependency>
<groupId>org.dromara.sms4j</groupId>
@ -313,6 +294,25 @@
<version>${mapstruct-plus.version}</version>
</dependency>
<!-- Warm-Flow国产工作流引擎, 在线文档http://warm-flow.cn/ -->
<dependency>
<groupId>org.dromara.warm</groupId>
<artifactId>warm-flow-mybatis-plus-sb3-starter</artifactId>
<version>${warm-flow.version}</version>
</dependency>
<dependency>
<groupId>org.dromara.warm</groupId>
<artifactId>warm-flow-plugin-ui-sb-web</artifactId>
<version>${warm-flow.version}</version>
</dependency>
<!-- JustAuth 的依赖配置-->
<dependency>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
<version>${justauth.version}</version>
</dependency>
<!-- 离线IP地址定位库 ip2region -->
<dependency>
<groupId>org.lionsoul</groupId>
@ -320,12 +320,6 @@
<version>${ip2region.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.15.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>

View File

@ -1,6 +1,6 @@
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
#FROM bellsoft/liberica-openjdk-debian:21.0.5-cds
FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds
#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds
#FROM findepi/graalvm:java17-native
LABEL maintainer="Lion Li"
@ -11,17 +11,18 @@ RUN mkdir -p /ruoyi/server/logs \
WORKDIR /ruoyi/server
ENV SERVER_PORT=8080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
ENV SERVER_PORT=8080 SNAIL_PORT=28080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
EXPOSE ${SERVER_PORT}
# 暴露 snail job 客户端端口 用于定时任务调度中心通信
EXPOSE ${SNAIL_PORT}
ADD ./target/ruoyi-admin.jar ./app.jar
# 工作流字体文件
ADD ./zhFonts/ /usr/share/fonts/zhFonts/
SHELL ["/bin/bash", "-c"]
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
-Dsnail-job.port=${SNAIL_PORT} \
# 应用名称 如果想区分集群节点监控 改成不同的名称即可
#-Dskywalking.agent.service_name=ruoyi-server \
#-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \

View File

@ -21,6 +21,8 @@ import org.dromara.common.core.domain.model.SocialLoginBody;
import org.dromara.common.core.utils.*;
import org.dromara.common.encrypt.annotation.ApiEncrypt;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.ratelimiter.annotation.RateLimiter;
import org.dromara.common.ratelimiter.enums.LimitType;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
import org.dromara.common.social.config.properties.SocialProperties;
@ -198,6 +200,7 @@ public class AuthController {
*
* @return 租户列表
*/
@RateLimiter(time = 60, count = 20, limitType = LimitType.IP)
@GetMapping("/tenant/list")
public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
// 返回对象

View File

@ -11,6 +11,7 @@ import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.reflect.ReflectUtils;
@ -79,12 +80,21 @@ public class CaptchaController {
*
* @param email 邮箱
*/
@RateLimiter(key = "#email", time = 60, count = 1)
@GetMapping("/resource/email/code")
public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
if (!mailProperties.getEnabled()) {
return R.fail("当前系统没有开启邮箱功能!");
}
SpringUtils.getAopProxy(this).emailCodeImpl(email);
return R.ok();
}
/**
* 邮箱验证码
* 独立方法避免验证码关闭之后仍然走限流
*/
@RateLimiter(key = "#email", time = 60, count = 1)
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));
@ -92,45 +102,56 @@ public class CaptchaController {
MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
} catch (Exception e) {
log.error("验证码短信发送异常 => {}", e.getMessage());
return R.fail(e.getMessage());
throw new ServiceException(e.getMessage());
}
return R.ok();
}
/**
* 生成验证码
*/
@RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
@GetMapping("/auth/code")
public R<CaptchaVo> getCode() {
CaptchaVo captchaVo = new CaptchaVo();
boolean captchaEnabled = captchaProperties.getEnable();
if (!captchaEnabled) {
CaptchaVo captchaVo = new CaptchaVo();
captchaVo.setCaptchaEnabled(false);
return R.ok(captchaVo);
}
return R.ok(SpringUtils.getAopProxy(this).getCodeImpl());
}
/**
* 生成验证码
* 独立方法避免验证码关闭之后仍然走限流
*/
@RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
public CaptchaVo getCodeImpl() {
// 保存验证码信息
String uuid = IdUtil.simpleUUID();
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
// 生成验证码
CaptchaType captchaType = captchaProperties.getType();
boolean isMath = CaptchaType.MATH == captchaType;
Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength();
CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length);
CodeGenerator codeGenerator;
if (CaptchaType.MATH == captchaType) {
codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getNumberLength(), false);
} else {
codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getCharLength());
}
AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
captcha.setGenerator(codeGenerator);
captcha.createCode();
// 如果是数学验证码使用SpEL表达式处理验证码结果
String code = captcha.getCode();
if (isMath) {
if (CaptchaType.MATH == captchaType) {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
code = exp.getValue(String.class);
}
RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
CaptchaVo captchaVo = new CaptchaVo();
captchaVo.setUuid(uuid);
captchaVo.setImg(captcha.getImageBase64());
return R.ok(captchaVo);
return captchaVo;
}
}

View File

@ -1,9 +1,9 @@
package org.dromara.web.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import org.dromara.common.core.config.RuoYiConfig;
import org.dromara.common.core.utils.StringUtils;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@ -17,16 +17,12 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
public class IndexController {
/**
* 系统基础配置
*/
private final RuoYiConfig ruoyiConfig;
/**
* 访问首页,提示语
*/
@GetMapping("/")
public String index() {
return StringUtils.format("欢迎使用{}后台管理框架,当前版本v{}请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion());
return StringUtils.format("欢迎使用{}后台管理框架,请通过前端地址访问。", SpringUtils.getApplicationName());
}
}

View File

@ -1,9 +1,8 @@
package org.dromara.web.listener;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.convert.Convert;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
@ -35,14 +34,13 @@ import java.time.Duration;
@Slf4j
public class UserActionListener implements SaTokenListener {
private final SaTokenConfig tokenConfig;
private final SysLoginService loginService;
/**
* 每次登录时触发
*/
@Override
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginParameter loginParameter) {
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = ServletUtils.getClientIP();
UserOnlineDTO dto = new UserOnlineDTO();
@ -52,17 +50,17 @@ public class UserActionListener implements SaTokenListener {
dto.setOs(userAgent.getOs().getName());
dto.setLoginTime(System.currentTimeMillis());
dto.setTokenId(tokenValue);
String username = (String) loginModel.getExtra(LoginHelper.USER_NAME_KEY);
String tenantId = (String) loginModel.getExtra(LoginHelper.TENANT_KEY);
String username = (String) loginParameter.getExtra(LoginHelper.USER_NAME_KEY);
String tenantId = (String) loginParameter.getExtra(LoginHelper.TENANT_KEY);
dto.setUserName(username);
dto.setClientKey((String) loginModel.getExtra(LoginHelper.CLIENT_KEY));
dto.setDeviceType(loginModel.getDevice());
dto.setDeptName((String) loginModel.getExtra(LoginHelper.DEPT_NAME_KEY));
dto.setClientKey((String) loginParameter.getExtra(LoginHelper.CLIENT_KEY));
dto.setDeviceType(loginParameter.getDeviceType());
dto.setDeptName((String) loginParameter.getExtra(LoginHelper.DEPT_NAME_KEY));
TenantHelper.dynamic(tenantId, () -> {
if(tokenConfig.getTimeout() == -1) {
if(loginParameter.getTimeout() == -1) {
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
} else {
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(tokenConfig.getTimeout()));
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(loginParameter.getTimeout()));
}
});
// 记录登录日志
@ -74,7 +72,7 @@ public class UserActionListener implements SaTokenListener {
logininforEvent.setRequest(ServletUtils.getRequest());
SpringUtils.context().publishEvent(logininforEvent);
// 更新登录信息
loginService.recordLoginInfo((Long) loginModel.getExtra(LoginHelper.USER_KEY), ip);
loginService.recordLoginInfo((Long) loginParameter.getExtra(LoginHelper.USER_KEY), ip);
log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
}
@ -160,6 +158,6 @@ public class UserActionListener implements SaTokenListener {
* 每次Token续期时触发
*/
@Override
public void doRenewTimeout(String tokenValue, Object loginId, long timeout) {
public void doRenewTimeout(String loginType, Object loginId, String tokenValue, long timeout) {
}
}

View File

@ -1,6 +1,6 @@
package org.dromara.web.service;
import cn.dev33.satoken.secure.BCrypt;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.Constants;
@ -87,7 +87,7 @@ public class SysRegisterService {
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
if (!StringUtils.equalsIgnoreCase(code, captcha)) {
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException();
}

View File

@ -1,7 +1,7 @@
package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
@ -58,8 +58,8 @@ public class EmailAuthStrategy implements IAuthStrategy {
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter();
model.setDeviceType(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());

View File

@ -1,9 +1,9 @@
package org.dromara.web.service.impl;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -70,8 +70,8 @@ public class PasswordAuthStrategy implements IAuthStrategy {
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter();
model.setDeviceType(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
@ -102,7 +102,7 @@ public class PasswordAuthStrategy implements IAuthStrategy {
loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
if (!StringUtils.equalsIgnoreCase(code, captcha)) {
loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException();
}

View File

@ -1,7 +1,7 @@
package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
@ -58,8 +58,8 @@ public class SmsAuthStrategy implements IAuthStrategy {
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter();
model.setDeviceType(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());

View File

@ -1,12 +1,9 @@
package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthResponse;
@ -68,15 +65,6 @@ public class SocialAuthStrategy implements IAuthStrategy {
throw new ServiceException(response.getMsg());
}
AuthUser authUserData = response.getData();
if ("GITEE".equals(authUserData.getSource())) {
// 如用户使用 gitee 登录顺手 star 给作者一点支持 拒绝白嫖
HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Vue-Plus")
.formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
.executeAsync();
HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Cloud-Plus")
.formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
.executeAsync();
}
List<SysSocialVo> list = sysSocialService.selectByAuthId(authUserData.getSource() + authUserData.getUuid());
if (CollUtil.isEmpty(list)) {
@ -99,8 +87,8 @@ public class SocialAuthStrategy implements IAuthStrategy {
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter();
model.setDeviceType(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());

View File

@ -1,7 +1,7 @@
package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -76,8 +76,8 @@ public class XcxAuthStrategy implements IAuthStrategy {
loginUser.setDeviceType(client.getDeviceType());
loginUser.setOpenid(openid);
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter();
model.setDeviceType(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());

View File

@ -2,7 +2,7 @@
spring.boot.admin.client:
# 增加客户端开关
enabled: true
url: http://localhost:9090
url: http://localhost:9090/admin
instance:
service-host-type: IP
metadata:
@ -27,8 +27,6 @@ snail-job:
port: 2${server.port}
# 客户端ip指定
host:
# RPC类型: netty, grpc
rpc-type: grpc
--- # 数据源配置
spring:
@ -120,8 +118,8 @@ redisson:
nettyThreads: 8
# 单节点配置
singleServerConfig:
# 客户端名称
clientName: ${ruoyi.name}
# 客户端名称 不能用中文
clientName: RuoYi-Vue-Plus
# 最小空闲连接数
connectionMinimumIdleSize: 8
# 连接池大小
@ -263,3 +261,10 @@ justauth:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=gitlab
gitea:
# 前端改动 https://gitee.com/JavaLionLi/plus-ui/pulls/204
# gitea 服务器地址
server-url: https://demo.gitea.com
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=gitea

View File

@ -5,7 +5,7 @@ spring.servlet.multipart.location: /ruoyi/server/temp
spring.boot.admin.client:
# 增加客户端开关
enabled: true
url: http://localhost:9090
url: http://localhost:9090/admin
instance:
service-host-type: IP
metadata:
@ -30,8 +30,6 @@ snail-job:
port: 2${server.port}
# 客户端ip指定
host:
# RPC类型: netty, grpc
rpc-type: grpc
--- # 数据源配置
spring:
@ -123,8 +121,8 @@ redisson:
nettyThreads: 32
# 单节点配置
singleServerConfig:
# 客户端名称
clientName: ${ruoyi.name}
# 客户端名称 不能用中文
clientName: RuoYi-Vue-Plus
# 最小空闲连接数
connectionMinimumIdleSize: 32
# 连接池大小
@ -265,3 +263,10 @@ justauth:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=gitlab
gitea:
# 前端改动 https://gitee.com/JavaLionLi/plus-ui/pulls/204
# gitea 服务器地址
server-url: https://demo.gitea.com
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=gitea

View File

@ -1,24 +1,3 @@
# 项目相关配置
ruoyi:
# 名称
name: RuoYi-Vue-Plus
# 版本
version: ${revision}
# 版权年份
copyrightYear: 2024
captcha:
enable: true
# 页面 <参数设置> 可开启关闭 验证码校验
# 验证码类型 math 数组计算 char 字符验证
type: MATH
# line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
category: CIRCLE
# 数字验证码位数
numberLength: 1
# 字符验证码长度
charLength: 4
# 开发环境配置
server:
# 服务器的HTTP端口默认为8080
@ -41,6 +20,18 @@ server:
# 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
worker: 256
captcha:
# 是否启用验证码校验
enable: true
# 验证码类型 math 数组计算 char 字符验证
type: MATH
# line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
category: CIRCLE
# 数字验证码位数
numberLength: 1
# 字符验证码长度
charLength: 4
# 日志配置
logging:
level:
@ -61,11 +52,18 @@ user:
# Spring配置
spring:
application:
name: ${ruoyi.name}
name: RuoYi-Vue-Plus
threads:
# 开启虚拟线程 仅jdk21可用
virtual:
enabled: false
task:
execution:
# 从 springboot 3.5 开始 spring自带线程池
# 不再需要 AsyncConfig与ThreadPoolConfig 可直接注入线程池使用
thread-name-prefix: async-
# 由spring自己初始化线程池
mode: force
# 资源信息
messages:
# 国际化资源文件路径
@ -119,7 +117,7 @@ security:
- /error
- /*/api-docs
- /*/api-docs/**
- /warm-flow-ui/token-name
- /warm-flow-ui/config
# 多租户配置
tenant:
@ -136,6 +134,7 @@ tenant:
- sys_user_role
- sys_client
- sys_oss_config
- flow_spel
# MyBatisPlus配置
# https://baomidou.com/config/
@ -186,28 +185,18 @@ springdoc:
api-docs:
# 是否开启接口文档
enabled: true
# swagger-ui:
# # 持久化认证数据
# persistAuthorization: true
info:
# 标题
title: '标题:${ruoyi.name}多租户管理系统_接口文档'
title: '标题:RuoYi-Vue-Plus多租户管理系统_接口文档'
# 描述
description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
# 版本
version: '版本号: ${ruoyi.version}'
version: '版本号: ${project.version}'
# 作者信息
contact:
name: Lion Li
email: crazylionli@163.com
url: https://gitee.com/dromara/RuoYi-Vue-Plus
components:
# 鉴权方式配置
security-schemes:
apiKey:
type: APIKEY
in: HEADER
name: ${sa-token.token-name}
#这里定义了两个分组,可定义多个,也可以不定义
group-configs:
- group: 1.演示模块
@ -225,20 +214,9 @@ springdoc:
xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
# 排除链接
excludeUrls:
- /system/notice
- /warm-flow/save-xml
# 全局线程池相关配置
# 如使用JDK21请直接使用虚拟线程 不要开启此配置
thread-pool:
# 是否开启线程池
enabled: false
# 队列最大长度
queueCapacity: 128
# 线程池维护线程所允许的空闲时间
keepAliveSeconds: 300
--- # 分布式锁 lock4j 全局配置
lock4j:
@ -279,5 +257,7 @@ warm-flow:
enabled: true
# 是否开启设计器ui
ui: true
# 是否显示流程图顶部文字
top-text-show: true
# 默认Authorization如果有多个token用逗号分隔
token-name: ${sa-token.token-name},clientid

View File

@ -17,6 +17,7 @@ user.username.length.valid=账户长度必须在{min}到{max}个字符之间
user.password.not.blank=用户密码不能为空
user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
user.password.not.valid=* 5-50个字符
user.password.format.valid=密码必须包含大写字母、小写字母、数字和特殊字符
user.email.not.valid=邮箱格式错误
user.email.not.blank=邮箱不能为空
user.phonenumber.not.blank=用户手机号不能为空

View File

@ -17,6 +17,7 @@ user.username.length.valid=Account length must be between {min} and {max} charac
user.password.not.blank=Password cannot be empty
user.password.length.valid=Password length must be between {min} and {max} characters
user.password.not.valid=* 5-50 characters
user.password.format.valid=Password must contain uppercase, lowercase, digit, and special character
user.email.not.valid=Mailbox format error
user.email.not.blank=Mailbox cannot be blank
user.phonenumber.not.blank=Phone number cannot be blank

View File

@ -17,6 +17,7 @@ user.username.length.valid=账户长度必须在{min}到{max}个字符之间
user.password.not.blank=用户密码不能为空
user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
user.password.not.valid=* 5-50个字符
user.password.format.valid=密码必须包含大写字母、小写字母、数字和特殊字符
user.email.not.valid=邮箱格式错误
user.email.not.blank=邮箱不能为空
user.phonenumber.not.blank=用户手机号不能为空

View File

@ -1,6 +1,6 @@
package org.dromara.test;
import org.dromara.common.core.config.RuoYiConfig;
import org.dromara.common.web.config.properties.CaptchaProperties;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@ -17,19 +17,19 @@ import java.util.concurrent.TimeUnit;
public class DemoUnitTest {
@Autowired
private RuoYiConfig ruoYiConfig;
private CaptchaProperties captchaProperties;
@DisplayName("测试 @SpringBootTest @Test @DisplayName 注解")
@Test
public void testTest() {
System.out.println(ruoYiConfig);
System.out.println(captchaProperties);
}
@Disabled
@DisplayName("测试 @Disabled 注解")
@Test
public void testDisabled() {
System.out.println(ruoYiConfig);
System.out.println(captchaProperties);
}
@Timeout(value = 2L, unit = TimeUnit.SECONDS)
@ -37,7 +37,7 @@ public class DemoUnitTest {
@Test
public void testTimeout() throws InterruptedException {
Thread.sleep(3000);
System.out.println(ruoYiConfig);
System.out.println(captchaProperties);
}

View File

@ -1 +0,0 @@
3f2ee348-0303-40ca-bf03-03f48d2d2141

Binary file not shown.

View File

@ -1,4 +0,0 @@
3
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso10646-1
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso8859-1
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-koi8-r

View File

@ -1,4 +0,0 @@
3
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso10646-1
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso8859-1
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-koi8-r

View File

@ -14,7 +14,7 @@
</description>
<properties>
<revision>5.3.0</revision>
<revision>5.4.1</revision>
</properties>
<dependencyManagement>

View File

@ -1,52 +0,0 @@
package org.dromara.common.core.config;
import cn.hutool.core.util.ArrayUtil;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.core.task.VirtualThreadTaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import java.util.Arrays;
import java.util.concurrent.Executor;
/**
* 异步配置
* <p>
* 如果未使用虚拟线程则生效
*
* @author Lion Li
*/
@AutoConfiguration
public class AsyncConfig implements AsyncConfigurer {
/**
* 自定义 @Async 注解使用系统线程池
*/
@Override
public Executor getAsyncExecutor() {
if(SpringUtils.isVirtual()) {
return new VirtualThreadTaskExecutor("async-");
}
return SpringUtils.getBean("scheduledExecutorService");
}
/**
* 异步执行异常处理
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, objects) -> {
throwable.printStackTrace();
StringBuilder sb = new StringBuilder();
sb.append("Exception message - ").append(throwable.getMessage())
.append(", Method name - ").append(method.getName());
if (ArrayUtil.isNotEmpty(objects)) {
sb.append(", Parameter value - ").append(Arrays.toString(objects));
}
throw new ServiceException(sb.toString());
};
}
}

View File

@ -1,33 +0,0 @@
package org.dromara.common.core.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 读取项目相关配置
*
* @author Lion Li
*/
@Data
@Component
@ConfigurationProperties(prefix = "ruoyi")
public class RuoYiConfig {
/**
* 项目名称
*/
private String name;
/**
* 版本
*/
private String version;
/**
* 版权年份
*/
private String copyrightYear;
}

View File

@ -7,11 +7,9 @@ import org.dromara.common.core.config.properties.ThreadPoolProperties;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.Threads;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.task.VirtualThreadTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
@ -34,18 +32,6 @@ public class ThreadPoolConfig {
private ScheduledExecutorService scheduledExecutorService;
@Bean(name = "threadPoolTaskExecutor")
@ConditionalOnProperty(prefix = "thread-pool", name = "enabled", havingValue = "true")
public ThreadPoolTaskExecutor threadPoolTaskExecutor(ThreadPoolProperties threadPoolProperties) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(core);
executor.setMaxPoolSize(core * 2);
executor.setQueueCapacity(threadPoolProperties.getQueueCapacity());
executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds());
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
/**
* 执行周期性或定时任务
*/

View File

@ -3,6 +3,7 @@ package org.dromara.common.core.config;
import jakarta.validation.Validator;
import org.hibernate.validator.HibernateValidator;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
@ -14,11 +15,11 @@ import java.util.Properties;
*
* @author Lion Li
*/
@AutoConfiguration
@AutoConfiguration(before = ValidationAutoConfiguration.class)
public class ValidatorConfig {
/**
* 配置校验框架 快速返回模式
* 配置校验框架 快速失败模式
*/
@Bean
public Validator validator(MessageSource messageSource) {
@ -28,7 +29,7 @@ public class ValidatorConfig {
// 设置使用 HibernateValidator 校验器
factoryBean.setProviderClass(HibernateValidator.class);
Properties properties = new Properties();
// 设置 快速异常返回
// 设置快速失败模式fail-fast即校验过程中一旦遇到失败立即停止并返回错误
properties.setProperty("hibernate.validator.fail_fast", "true");
factoryBean.setValidationProperties(properties);
// 加载配置

View File

@ -3,13 +3,14 @@ package org.dromara.common.core.constant;
/**
* 缓存组名称常量
* <p>
* key 格式为 cacheNames#ttl#maxIdleTime#maxSize
* key 格式为 cacheNames#ttl#maxIdleTime#maxSize#local
* <p>
* ttl 过期时间 如果设置为0则不过期 默认为0
* maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0
* maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
* local 默认开启本地缓存为1 关闭本地缓存为0
* <p>
* 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500
* 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500、test#1h#0#500#0
*
* @author Lion Li
*/
@ -30,6 +31,11 @@ public interface CacheNames {
*/
String SYS_DICT = "sys_dict";
/**
* 数据字典类型
*/
String SYS_DICT_TYPE = "sys_dict_type";
/**
* 租户
*/

View File

@ -17,9 +17,14 @@ public interface RegexConstants extends RegexPool {
String DICTIONARY_TYPE = "^[a-z][a-z0-9_]*$";
/**
* 权限标识必须符合 tool:build:list 格式,或者空字符串
* 权限标识必须符合以下格式:
* 1. 标准格式xxx:yyy:zzz
* - 第一部分xxx只能包含字母、数字和下划线_不能使用 `*`
* - 第二部分yyy可以包含字母、数字、下划线_和 `*`
* - 第三部分zzz可以包含字母、数字、下划线_和 `*`
* 2. 允许空字符串(""),表示没有权限标识
*/
String PERMISSION_STRING = "^(|^[a-zA-Z0-9_]+:[a-zA-Z0-9_]+:[a-zA-Z0-9_]+)$";
String PERMISSION_STRING = "^$|^[a-zA-Z0-9_]+:[a-zA-Z0-9_*]+:[a-zA-Z0-9_*]+$";
/**
* 身份证号码后6位

View File

@ -72,4 +72,14 @@ public interface SystemConstants {
*/
Long SUPER_ADMIN_ID = 1L;
/**
* 根部门祖级列表
*/
String ROOT_DEPT_ANCESTORS = "0";
/**
* 默认部门 ID
*/
Long DEFAULT_DEPT_ID = 100L;
}

View File

@ -50,6 +50,11 @@ public class CompleteTaskDTO implements Serializable {
*/
private String notice;
/**
* 办理人(可不填 用于覆盖当前节点办理人)
*/
private String handler;
/**
* 流程变量
*/

View File

@ -11,7 +11,6 @@ import java.io.Serializable;
*
* @author AprilWind
*/
@Data
@NoArgsConstructor
public class DeptDTO implements Serializable {

View File

@ -0,0 +1,41 @@
package org.dromara.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 字典数据DTO
*
* @author AprilWind
*/
@Data
@NoArgsConstructor
public class DictDataDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 字典标签
*/
private String dictLabel;
/**
* 字典键值
*/
private String dictValue;
/**
* 是否默认Y是 N否
*/
private String isDefault;
/**
* 备注
*/
private String remark;
}

View File

@ -0,0 +1,41 @@
package org.dromara.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 字典类型DTO
*
* @author AprilWind
*/
@Data
@NoArgsConstructor
public class DictTypeDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 字典主键
*/
private Long dictId;
/**
* 字典名称
*/
private String dictName;
/**
* 字典类型
*/
private String dictType;
/**
* 备注
*/
private String remark;
}

View File

@ -35,7 +35,7 @@ public class RoleDTO implements Serializable {
private String roleKey;
/**
* 数据范围1所有数据权限2自定数据权限3本部门数据权限4本部门及以下数据权限5仅本人数据权限
* 数据范围1全部数据权限 2自定数据权限 3本部门数据权限 4本部门及以下数据权限 5仅本人数据权限 6部门及以下或本人数据权限
*/
private String dataScope;

View File

@ -30,6 +30,11 @@ public class StartProcessDTO implements Serializable {
*/
private String flowCode;
/**
* 办理人(可不填 用于覆盖当前节点办理人)
*/
private String handler;
/**
* 流程变量,前端会提交一个元素{'entity': {业务详情数据对象}}
*/

View File

@ -52,17 +52,17 @@ public class TaskAssigneeDTO implements Serializable {
*/
public static <T> List<TaskHandler> convertToHandlerList(
List<T> sourceList,
Function<T, Long> storageId,
Function<T, String> storageId,
Function<T, String> handlerCode,
Function<T, String> handlerName,
Function<T, Long> groupName,
Function<T, String> groupName,
Function<T, Date> createTimeMapper) {
return sourceList.stream()
.map(item -> new TaskHandler(
String.valueOf(storageId.apply(item)),
storageId.apply(item),
handlerCode.apply(item),
handlerName.apply(item),
groupName != null ? String.valueOf(groupName.apply(item)) : null,
groupName.apply(item),
createTimeMapper.apply(item)
)).collect(Collectors.toList());
}

View File

@ -27,13 +27,33 @@ public class ProcessEvent implements Serializable {
*/
private String flowCode;
/**
* 实例id
*/
private Long instanceId;
/**
* 业务id
*/
private String businessId;
/**
* 状态
* 节点类型0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关
*/
private Integer nodeType;
/**
* 流程节点编码
*/
private String nodeCode;
/**
* 流程节点名称
*/
private String nodeName;
/**
* 流程状态
*/
private String status;
@ -45,6 +65,6 @@ public class ProcessEvent implements Serializable {
/**
* 当为true时为申请人节点办理
*/
private boolean submit;
private Boolean submit;
}

View File

@ -4,9 +4,10 @@ import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Map;
/**
* 流程办理监听
* 流程任务监听
*
* @author may
*/
@ -27,18 +28,43 @@ public class ProcessTaskEvent implements Serializable {
private String flowCode;
/**
* 审批节点编码
* 节点类型0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关
*/
private Integer nodeType;
/**
* 流程节点编码
*/
private String nodeCode;
/**
* 流程节点名称
*/
private String nodeName;
/**
* 任务id
*/
private Long taskId;
/**
* 实例id
*/
private Long instanceId;
/**
* 业务id
*/
private String businessId;
/**
* 流程状态
*/
private String status;
/**
* 办理参数
*/
private Map<String, Object> params;
}

View File

@ -18,14 +18,15 @@ public class PasswordLoginBody extends LoginBody {
* 用户名
*/
@NotBlank(message = "{user.username.not.blank}")
@Length(min = 2, max = 20, message = "{user.username.length.valid}")
@Length(min = 2, max = 30, message = "{user.username.length.valid}")
private String username;
/**
* 用户密码
*/
@NotBlank(message = "{user.password.not.blank}")
@Length(min = 5, max = 20, message = "{user.password.length.valid}")
@Length(min = 5, max = 30, message = "{user.password.length.valid}")
// @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}")
private String password;
}

View File

@ -18,16 +18,20 @@ public class RegisterBody extends LoginBody {
* 用户名
*/
@NotBlank(message = "{user.username.not.blank}")
@Length(min = 2, max = 20, message = "{user.username.length.valid}")
@Length(min = 2, max = 30, message = "{user.username.length.valid}")
private String username;
/**
* 用户密码
*/
@NotBlank(message = "{user.password.not.blank}")
@Length(min = 5, max = 20, message = "{user.password.length.valid}")
@Length(min = 5, max = 30, message = "{user.password.length.valid}")
// @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}")
private String password;
/**
* 用户类型
*/
private String userType;
}

View File

@ -5,7 +5,6 @@ import lombok.Getter;
/**
* 设备类型
* 针对一套 用户体系
*
* @author Lion Li
*/
@ -29,9 +28,12 @@ public enum DeviceType {
XCX("xcx"),
/**
* social第三方端
* 第三方社交登录平台
*/
SOCIAL("social");
/**
* 设备标识
*/
private final String device;
}

View File

@ -1,12 +1,11 @@
package org.dromara.common.core.enums;
import org.dromara.common.core.utils.StringUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.common.core.utils.StringUtils;
/**
* 设备类型
* 针对多套 用户体系
* 用户类型
*
* @author Lion Li
*/
@ -15,15 +14,18 @@ import lombok.Getter;
public enum UserType {
/**
* pc端
* 后台系统用户
*/
SYS_USER("sys_user"),
/**
* app端
* 移动客户端用户
*/
APP_USER("app_user");
/**
* 用户类型标识(用于 token、权限识别等
*/
private final String userType;
public static UserType getUserType(String str) {

View File

@ -1,11 +1,15 @@
package org.dromara.common.core.exception;
import lombok.*;
import cn.hutool.core.text.StrFormatter;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.Serial;
/**
* 业务异常
* 业务异常(支持占位符 {}
*
* @author ruoyi
*/
@ -42,6 +46,10 @@ public final class ServiceException extends RuntimeException {
this.code = code;
}
public ServiceException(String message, Object... args) {
this.message = StrFormatter.format(message, args);
}
@Override
public String getMessage() {
return message;

View File

@ -3,6 +3,7 @@ package org.dromara.common.core.service;
import org.dromara.common.core.domain.dto.DeptDTO;
import java.util.List;
import java.util.Map;
/**
* 通用 部门服务
@ -34,4 +35,12 @@ public interface DeptService {
*/
List<DeptDTO> selectDeptsByList();
/**
* 根据部门 ID 列表查询部门名称映射关系
*
* @param deptIds 部门 ID 列表
* @return Map其中 key 为部门 IDvalue 为对应的部门名称
*/
Map<Long, String> selectDeptNamesByIds(List<Long> deptIds);
}

View File

@ -1,5 +1,9 @@
package org.dromara.common.core.service;
import org.dromara.common.core.domain.dto.DictDataDTO;
import org.dromara.common.core.domain.dto.DictTypeDTO;
import java.util.List;
import java.util.Map;
/**
@ -64,4 +68,20 @@ public interface DictService {
*/
Map<String, String> getAllDictByDictType(String dictType);
/**
* 根据字典类型查询详细信息
*
* @param dictType 字典类型
* @return 字典类型详细信息
*/
DictTypeDTO getDictType(String dictType);
/**
* 根据字典类型查询字典数据列表
*
* @param dictType 字典类型
* @return 字典数据列表
*/
List<DictDataDTO> getDictData(String dictType);
}

View File

@ -0,0 +1,28 @@
package org.dromara.common.core.service;
import java.util.Set;
/**
* 用户权限处理
*
* @author Lion Li
*/
public interface PermissionService {
/**
* 获取角色数据权限
*
* @param userId 用户id
* @return 角色权限信息
*/
Set<String> getRolePermission(Long userId);
/**
* 获取菜单数据权限
*
* @param userId 用户id
* @return 菜单权限信息
*/
Set<String> getMenuPermission(Long userId);
}

View File

@ -1,5 +1,8 @@
package org.dromara.common.core.service;
import java.util.List;
import java.util.Map;
/**
* 通用 岗位服务
*
@ -7,4 +10,12 @@ package org.dromara.common.core.service;
*/
public interface PostService {
/**
* 根据岗位 ID 列表查询岗位名称映射关系
*
* @param postIds 岗位 ID 列表
* @return Map其中 key 为岗位 IDvalue 为对应的岗位名称
*/
Map<Long, String> selectPostNamesByIds(List<Long> postIds);
}

View File

@ -1,5 +1,8 @@
package org.dromara.common.core.service;
import java.util.List;
import java.util.Map;
/**
* 通用 角色服务
*
@ -7,4 +10,12 @@ package org.dromara.common.core.service;
*/
public interface RoleService {
/**
* 根据角色 ID 列表查询角色名称映射关系
*
* @param roleIds 角色 ID 列表
* @return Map其中 key 为角色 IDvalue 为对应的角色名称
*/
Map<Long, String> selectRoleNamesByIds(List<Long> roleIds);
}

View File

@ -3,6 +3,7 @@ package org.dromara.common.core.service;
import org.dromara.common.core.domain.dto.UserDTO;
import java.util.List;
import java.util.Map;
/**
* 通用 用户服务
@ -91,4 +92,12 @@ public interface UserService {
*/
List<UserDTO> selectUsersByPostIds(List<Long> postIds);
/**
* 根据用户 ID 列表查询用户名称映射关系
*
* @param userIds 用户 ID 列表
* @return Map其中 key 为用户 IDvalue 为对应的用户名称
*/
Map<Long, String> selectUserNamesByIds(List<Long> userIds);
}

View File

@ -78,9 +78,28 @@ public interface WorkflowService {
/**
* 办理任务
* 系统后台发起审批 无用户信息 需要忽略权限
* completeTask.getVariables().put("ignore", true);
*
* @param completeTask 参数
* @return 结果
*/
boolean completeTask(CompleteTaskDTO completeTask);
/**
* 办理任务
*
* @param taskId 任务ID
* @param message 办理意见
* @return 结果
*/
boolean completeTask(Long taskId, String message);
/**
* 启动流程并办理第一个任务
*
* @param startProcess 参数
* @return 结果
*/
boolean startCompleteTask(StartProcessDTO startProcess);
}

View File

@ -175,14 +175,27 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
}
/**
* 计算两个日期之间的天数差(以毫秒为单位
* 计算两个时间之间的时间差,并以指定单位返回(绝对值
*
* @param date1 第一个日期
* @param date2 第二个日期
* @return 两个日期之间的天数差的绝对值
* @param start 起始时间
* @param end 结束时间
* @param unit 所需返回的时间单位DAYS、HOURS、MINUTES、SECONDS、MILLISECONDS、MICROSECONDS、NANOSECONDS
* @return 时间差的绝对值,以指定单位表示
*/
public static int differentDaysByMillisecond(Date date1, Date date2) {
return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
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);
};
}
/**
@ -280,7 +293,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
// 校验时间跨度不超过最大限制
if (diff > maxValue) {
throw new ServiceException("最大时间跨度为 " + maxValue + " " + unit.toString().toLowerCase());
throw new ServiceException("最大时间跨度为 {} {}", maxValue, unit.toString().toLowerCase());
}
}

View File

@ -0,0 +1,84 @@
package org.dromara.common.core.utils;
import cn.hutool.core.lang.PatternPool;
import cn.hutool.core.net.NetUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.regex.RegexUtils;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* 增强网络相关工具类
*
* @author 秋辞未寒
*/
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class NetUtils extends NetUtil {
/**
* 判断是否为IPv6地址
*
* @param ip IP地址
* @return 是否为IPv6地址
*/
public static boolean isIPv6(String ip) {
try {
// 判断是否为IPv6地址
return InetAddress.getByName(ip) instanceof Inet6Address;
} catch (UnknownHostException e) {
return false;
}
}
/**
* 判断IPv6地址是否为内网地址
* <br><br>
* 以下地址将归类为本地地址,如有业务场景有需要,请根据需求自行处理:
* <pre>
* 通配符地址 0:0:0:0:0:0:0:0
* 链路本地地址 fe80::/10
* 唯一本地地址 fec0::/10
* 环回地址 ::1
* </pre>
*
* @param ip IP地址
* @return 是否为内网地址
*/
public static boolean isInnerIPv6(String ip) {
try {
// 判断是否为IPv6地址
if (InetAddress.getByName(ip) instanceof Inet6Address inet6Address) {
// isAnyLocalAddress 判断是否为通配符地址,通常不会将其视为内网地址,根据业务场景自行处理判断
// isLinkLocalAddress 判断是否为链路本地地址,通常不算内网地址,是否划分归属于内网需要根据业务场景自行处理判断
// isLoopbackAddress 判断是否为环回地址与IPv4的 127.0.0.1 同理,用于表示本机
// isSiteLocalAddress 判断是否为本地站点地址IPv6唯一本地地址Unique Local Addresses简称ULA
if (inet6Address.isAnyLocalAddress()
|| inet6Address.isLinkLocalAddress()
|| inet6Address.isLoopbackAddress()
|| inet6Address.isSiteLocalAddress()) {
return true;
}
}
} catch (UnknownHostException e) {
// 注意isInnerIPv6方法和isIPv6方法的适用范围不同所以此处不能忽略其异常信息。
throw new IllegalArgumentException("Invalid IPv6 address!", e);
}
return false;
}
/**
* 判断是否为IPv4地址
*
* @param ip IP地址
* @return 是否为IPv4地址
*/
public static boolean isIPv4(String ip) {
return RegexUtils.isMatch(PatternPool.IPV4, ip);
}
}

View File

@ -115,7 +115,7 @@ public class ServletUtils extends JakartaServletUtil {
public static Map<String, String> getParamMap(ServletRequest request) {
Map<String, String> params = new HashMap<>();
for (Map.Entry<String, String[]> entry : getParams(request).entrySet()) {
params.put(entry.getKey(), StringUtils.join(entry.getValue(), StringUtils.SEPARATOR));
params.put(entry.getKey(), StringUtils.joinComma(entry.getValue()));
}
return params;
}

View File

@ -7,7 +7,6 @@ import lombok.NoArgsConstructor;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

View File

@ -6,6 +6,7 @@ import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil;
import org.springframework.util.AntPathMatcher;
import java.nio.charset.Charset;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -259,13 +260,13 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
if (s != null) {
final int len = s.length();
if (s.length() <= size) {
sb.append(String.valueOf(c).repeat(size - len));
sb.append(Convert.toStr(c).repeat(size - len));
sb.append(s);
} else {
return s.substring(len - size, len);
}
} else {
sb.append(String.valueOf(c).repeat(Math.max(0, size)));
sb.append(Convert.toStr(c).repeat(Math.max(0, size)));
}
return sb.toString();
}
@ -318,6 +319,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
.stream()
.filter(Objects::nonNull)
.map(mapper)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
@ -338,4 +340,45 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
return false;
}
/**
* 将字符串从源字符集转换为目标字符集
*
* @param input 原始字符串
* @param fromCharset 源字符集
* @param toCharset 目标字符集
* @return 转换后的字符串
*/
public static String convert(String input, Charset fromCharset, Charset toCharset) {
if (isBlank(input)) {
return input;
}
try {
// 从源字符集获取字节
byte[] bytes = input.getBytes(fromCharset);
// 使用目标字符集解码
return new String(bytes, toCharset);
} catch (Exception e) {
return input;
}
}
/**
* 将可迭代对象中的元素使用逗号拼接成字符串
*
* @param iterable 可迭代对象,如 List、Set 等
* @return 拼接后的字符串
*/
public static String joinComma(Iterable<?> iterable) {
return StringUtils.join(iterable, SEPARATOR);
}
/**
* 将数组中的元素使用逗号拼接成字符串
*
* @param array 任意类型的数组
* @return 拼接后的字符串
*/
public static String joinComma(Object[] array) {
return StringUtils.join(array, SEPARATOR);
}
}

View File

@ -10,6 +10,8 @@ import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -60,6 +62,31 @@ public class TreeBuildUtils extends TreeUtil {
return TreeUtil.build(list, parentId, DEFAULT_CONFIG, nodeParser);
}
/**
* 构建多根节点的树结构(支持多个顶级节点)
*
* @param list 原始数据列表
* @param getId 获取节点 ID 的方法引用例如node -> node.getId()
* @param getParentId 获取节点父级 ID 的方法引用例如node -> node.getParentId()
* @param parser 树节点属性映射器,用于将原始节点 T 转为 Tree 节点
* @param <T> 原始数据类型如实体类、DTO 等)
* @param <K> 节点 ID 类型(如 Long、String
* @return 构建完成的树形结构(可能包含多个顶级根节点)
*/
public static <T, K> List<Tree<K>> buildMultiRoot(List<T> list, Function<T, K> getId, Function<T, K> getParentId, NodeParser<T, K> parser) {
if (CollUtil.isEmpty(list)) {
return CollUtil.newArrayList();
}
Set<K> rootParentIds = StreamUtils.toSet(list, getParentId);
rootParentIds.removeAll(StreamUtils.toSet(list, getId));
// 构建每一个根 parentId 下的树,并合并成最终结果列表
return rootParentIds.stream()
.flatMap(rootParentId -> TreeUtil.build(list, rootParentId, parser).stream())
.collect(Collectors.toList());
}
/**
* 获取节点列表中所有节点的叶子节点
*

View File

@ -1,11 +1,11 @@
package org.dromara.common.core.utils.ip;
import cn.hutool.core.net.NetUtil;
import cn.hutool.http.HtmlUtil;
import org.dromara.common.core.utils.StringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.NetUtils;
import org.dromara.common.core.utils.StringUtils;
/**
* 获取地址类
@ -16,18 +16,55 @@ import lombok.extern.slf4j.Slf4j;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class AddressUtils {
// 未知IP
public static final String UNKNOWN_IP = "XX XX";
// 内网地址
public static final String LOCAL_ADDRESS = "内网IP";
// 未知地址
public static final String UNKNOWN = "XX XX";
public static final String UNKNOWN_ADDRESS = "未知";
public static String getRealAddressByIP(String ip) {
if (StringUtils.isBlank(ip)) {
return UNKNOWN;
// 处理空串并过滤HTML标签
ip = HtmlUtil.cleanHtmlTag(StringUtils.blankToDefault(ip,""));
// 判断是否为IPv4
if (NetUtils.isIPv4(ip)) {
return resolverIPv4Region(ip);
}
// 判断是否为IPv6
if (NetUtils.isIPv6(ip)) {
return resolverIPv6Region(ip);
}
// 如果不是IPv4或IPv6则返回未知IP
return UNKNOWN_IP;
}
/**
* 根据IPv4地址查询IP归属行政区域
* @param ip ipv4地址
* @return 归属行政区域
*/
private static String resolverIPv4Region(String ip){
// 内网不查询
ip = StringUtils.contains(ip, "0:0:0:0:0:0:0:1") ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
if (NetUtil.isInnerIP(ip)) {
return "内网IP";
if (NetUtils.isInnerIP(ip)) {
return LOCAL_ADDRESS;
}
return RegionUtils.getCityInfo(ip);
}
/**
* 根据IPv6地址查询IP归属行政区域
* @param ip ipv6地址
* @return 归属行政区域
*/
private static String resolverIPv6Region(String ip){
// 内网不查询
if (NetUtils.isInnerIPv6(ip)) {
return LOCAL_ADDRESS;
}
log.warn("ip2region不支持IPV6地址解析{}", ip);
// 不支持IPv6不再进行没有必要的IP地址信息的解析直接返回
// 如有需要可自行实现IPv6地址信息解析逻辑并在这里返回
return UNKNOWN_ADDRESS;
}
}

View File

@ -1,15 +1,12 @@
package org.dromara.common.core.utils.ip;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.ObjectUtil;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.file.FileUtils;
import cn.hutool.core.io.resource.NoResourceException;
import cn.hutool.core.io.resource.ResourceUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import org.lionsoul.ip2region.xdb.Searcher;
import java.io.File;
/**
* 根据ip地址定位工具类离线方式
* 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a>
@ -19,31 +16,19 @@ import java.io.File;
@Slf4j
public class RegionUtils {
// IP地址库文件名称
public static final String IP_XDB_FILENAME = "ip2region.xdb";
private static final Searcher SEARCHER;
static {
String fileName = "/ip2region.xdb";
File existFile = FileUtils.file(FileUtil.getTmpDir() + FileUtil.FILE_SEPARATOR + fileName);
if (!FileUtils.exist(existFile)) {
ClassPathResource fileStream = new ClassPathResource(fileName);
if (ObjectUtil.isEmpty(fileStream.getStream())) {
throw new ServiceException("RegionUtils初始化失败原因IP地址库数据不存在");
}
FileUtils.writeFromStream(fileStream.getStream(), existFile);
}
String dbPath = existFile.getPath();
// 1、从 dbPath 加载整个 xdb 到内存。
byte[] cBuff;
try {
cBuff = Searcher.loadContentFromFile(dbPath);
} catch (Exception e) {
throw new ServiceException("RegionUtils初始化失败原因从ip2region.xdb文件加载内容失败" + e.getMessage());
}
// 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
try {
SEARCHER = Searcher.newWithBuffer(cBuff);
// 1、将 ip2region 数据库文件 xdb 从 ClassPath 加载到内存。
// 2、基于加载到内存的 xdb 数据创建一个 Searcher 查询对象。
SEARCHER = Searcher.newWithBuffer(ResourceUtil.readBytes(IP_XDB_FILENAME));
log.info("RegionUtils初始化成功加载IP地址库数据成功");
} catch (NoResourceException e) {
throw new ServiceException("RegionUtils初始化失败原因IP地址库数据不存在");
} catch (Exception e) {
throw new ServiceException("RegionUtils初始化失败原因" + e.getMessage());
}
@ -54,9 +39,8 @@ public class RegionUtils {
*/
public static String getCityInfo(String ip) {
try {
ip = ip.trim();
// 3、执行查询
String region = SEARCHER.search(ip);
String region = SEARCHER.search(StringUtils.trim(ip));
return region.replace("0|", "").replace("|0", "");
} catch (Exception e) {
log.error("IP地址离线获取城市异常 {}", ip);

View File

@ -0,0 +1,40 @@
package org.dromara.common.core.validate.dicts;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 字典项校验注解
*
* @author AprilWind
*/
@Constraint(validatedBy = DictPatternValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface DictPattern {
/**
* 字典类型,如 "sys_user_sex"
*/
String dictType();
/**
* 分隔符
*/
String separator();
/**
* 默认校验失败提示信息
*/
String message() default "字典值无效";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,55 @@
package org.dromara.common.core.validate.dicts;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.dromara.common.core.service.DictService;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
/**
* 自定义字典值校验器
*
* @author AprilWind
*/
public class DictPatternValidator implements ConstraintValidator<DictPattern, String> {
/**
* 字典类型
*/
private String dictType;
/**
* 分隔符
*/
private String separator = ",";
/**
* 初始化校验器,提取注解上的字典类型
*
* @param annotation 注解实例
*/
@Override
public void initialize(DictPattern annotation) {
this.dictType = annotation.dictType();
if (StringUtils.isNotBlank(annotation.separator())) {
this.separator = annotation.separator();
}
}
/**
* 校验字段值是否为指定字典类型中的合法值
*
* @param value 被校验的字段值
* @param context 校验上下文(可用于构建错误信息)
* @return true 表示校验通过合法字典值false 表示不通过
*/
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (StringUtils.isBlank(dictType) || StringUtils.isBlank(value)) {
return false;
}
String dictLabel = SpringUtils.getBean(DictService.class).getDictLabel(dictType, value, separator);
return StringUtils.isNotBlank(dictLabel);
}
}

View File

@ -1,37 +1,37 @@
package org.dromara.common.core.validate.enumd;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.reflect.ReflectUtils;
/**
* 自定义枚举校验注解实现
*
* @author 秋辞未寒
* @date 2024-12-09
*/
public class EnumPatternValidator implements ConstraintValidator<EnumPattern, String> {
private EnumPattern annotation;;
@Override
public void initialize(EnumPattern annotation) {
ConstraintValidator.super.initialize(annotation);
this.annotation = annotation;
}
@Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
if (StringUtils.isNotBlank(value)) {
String fieldName = annotation.fieldName();
for (Object e : annotation.type().getEnumConstants()) {
if (value.equals(ReflectUtils.invokeGetter(e, fieldName))) {
return true;
}
}
}
return false;
}
}
package org.dromara.common.core.validate.enumd;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.reflect.ReflectUtils;
/**
* 自定义枚举校验注解实现
*
* @author 秋辞未寒
* @date 2024-12-09
*/
public class EnumPatternValidator implements ConstraintValidator<EnumPattern, String> {
private EnumPattern annotation;
@Override
public void initialize(EnumPattern annotation) {
ConstraintValidator.super.initialize(annotation);
this.annotation = annotation;
}
@Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
if (StringUtils.isNotBlank(value)) {
String fieldName = annotation.fieldName();
for (Object e : annotation.type().getEnumConstants()) {
if (value.equals(ReflectUtils.invokeGetter(e, fieldName))) {
return true;
}
}
}
return false;
}
}

View File

@ -1,6 +1,4 @@
org.dromara.common.core.config.ApplicationConfig
org.dromara.common.core.config.AsyncConfig
org.dromara.common.core.config.RuoYiConfig
org.dromara.common.core.config.ThreadPoolConfig
org.dromara.common.core.config.ValidatorConfig
org.dromara.common.core.utils.SpringUtils

View File

@ -30,7 +30,7 @@ import java.util.Optional;
import java.util.Set;
/**
* Swagger 文档配置
* 接口文档配置
*
* @author Lion Li
*/
@ -54,14 +54,15 @@ public class SpringDocConfig {
openApi.externalDocs(properties.getExternalDocs());
openApi.tags(properties.getTags());
openApi.paths(properties.getPaths());
openApi.components(properties.getComponents());
Set<String> keySet = properties.getComponents().getSecuritySchemes().keySet();
List<SecurityRequirement> list = new ArrayList<>();
SecurityRequirement securityRequirement = new SecurityRequirement();
keySet.forEach(securityRequirement::addList);
list.add(securityRequirement);
openApi.security(list);
if (properties.getComponents() != null) {
openApi.components(properties.getComponents());
Set<String> keySet = properties.getComponents().getSecuritySchemes().keySet();
List<SecurityRequirement> list = new ArrayList<>();
SecurityRequirement securityRequirement = new SecurityRequirement();
keySet.forEach(securityRequirement::addList);
list.add(securityRequirement);
openApi.security(list);
}
return openApi;
}

View File

@ -6,6 +6,7 @@ import org.dromara.common.encrypt.properties.ApiDecryptProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistration;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
@ -20,13 +21,14 @@ import org.springframework.context.annotation.Bean;
public class ApiDecryptAutoConfiguration {
@Bean
public FilterRegistrationBean<CryptoFilter> cryptoFilterRegistration(ApiDecryptProperties properties) {
FilterRegistrationBean<CryptoFilter> registration = new FilterRegistrationBean<>();
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new CryptoFilter(properties));
registration.addUrlPatterns("/*");
registration.setName("cryptoFilter");
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
return registration;
@FilterRegistration(
name = "cryptoFilter",
urlPatterns = "/*",
order = FilterRegistrationBean.HIGHEST_PRECEDENCE,
dispatcherTypes = DispatcherType.REQUEST
)
public CryptoFilter cryptoFilter(ApiDecryptProperties properties) {
return new CryptoFilter(properties);
}
}

View File

@ -76,12 +76,14 @@ public class EncryptResponseBodyWrapper extends HttpServletResponseWrapper {
String encryptPassword = EncryptUtils.encryptByRsa(encryptAes, publicKey);
// 设置响应头
// vue版本需要设置
servletResponse.addHeader("Access-Control-Expose-Headers", headerFlag);
servletResponse.setHeader(headerFlag, encryptPassword);
servletResponse.setHeader("Access-Control-Allow-Origin", "*");
servletResponse.setHeader("Access-Control-Allow-Methods", "*");
servletResponse.setHeader(headerFlag, encryptPassword);
servletResponse.setCharacterEncoding(StandardCharsets.UTF_8.toString());
// 获取原始内容
String originalBody = this.getContent();
// 对内容进行加密

View File

@ -5,6 +5,7 @@ import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import org.dromara.common.core.utils.StringUtils;
@ -39,12 +40,23 @@ public class MybatisDecryptInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 开始进行参数解密
ResultSetHandler resultSetHandler = (ResultSetHandler) invocation.getTarget();
Field parameterHandlerField = resultSetHandler.getClass().getDeclaredField("parameterHandler");
parameterHandlerField.setAccessible(true);
Object target = parameterHandlerField.get(resultSetHandler);
if (target instanceof ParameterHandler parameterHandler) {
Object parameterObject = parameterHandler.getParameterObject();
if (ObjectUtil.isNotNull(parameterObject) && !(parameterObject instanceof String)) {
this.decryptHandler(parameterObject);
}
}
// 获取执行mysql执行结果
Object result = invocation.proceed();
if (result == null) {
return null;
}
decryptHandler(result);
this.decryptHandler(result);
return result;
}

View File

@ -108,7 +108,7 @@ public class EncryptUtils {
}
/**
* sm4加密
* SM4加密Base64编码
*
* @param data 待加密数据
* @param password 秘钥字符串
@ -127,11 +127,11 @@ public class EncryptUtils {
}
/**
* sm4加密
* SM4加密Hex编码
*
* @param data 待加密数据
* @param password 秘钥字符串
* @return 加密后字符串, 采用Base64编码
* @return 加密后字符串, 采用Hex编码
*/
public static String encryptBySm4Hex(String data, String password) {
if (StrUtil.isBlank(password)) {
@ -148,7 +148,7 @@ public class EncryptUtils {
/**
* sm4解密
*
* @param data 待解密数据
* @param data 待解密数据可以是Base64或Hex编码
* @param password 秘钥字符串
* @return 解密后字符串
*/

View File

@ -22,8 +22,8 @@
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<groupId>cn.idev.excel</groupId>
<artifactId>fastexcel</artifactId>
</dependency>
</dependencies>

View File

@ -6,17 +6,13 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 批注
* 批注 此注解仅用于单表头 不支持多层级表头
* @author guzhouyanyu
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelNotation {
/**
* col index
*/
int index() default -1;
/**
* 批注内容
*/

View File

@ -8,17 +8,13 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 是否必填
* 是否必填 此注解仅用于单表头 不支持多层级表头
* @author guzhouyanyu
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelRequired {
/**
* col index
*/
int index() default -1;
/**
* 字体颜色
*/

View File

@ -2,12 +2,12 @@ package org.dromara.common.excel.convert;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import cn.idev.excel.converters.Converter;
import cn.idev.excel.enums.CellDataTypeEnum;
import cn.idev.excel.metadata.GlobalConfiguration;
import cn.idev.excel.metadata.data.ReadCellData;
import cn.idev.excel.metadata.data.WriteCellData;
import cn.idev.excel.metadata.property.ExcelContentProperty;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;

View File

@ -3,12 +3,12 @@ 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 com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import cn.idev.excel.converters.Converter;
import cn.idev.excel.enums.CellDataTypeEnum;
import cn.idev.excel.metadata.GlobalConfiguration;
import cn.idev.excel.metadata.data.ReadCellData;
import cn.idev.excel.metadata.data.WriteCellData;
import cn.idev.excel.metadata.property.ExcelContentProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.core.service.DictService;
import org.dromara.common.core.utils.SpringUtils;

View File

@ -3,12 +3,12 @@ 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 com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import cn.idev.excel.converters.Converter;
import cn.idev.excel.enums.CellDataTypeEnum;
import cn.idev.excel.metadata.GlobalConfiguration;
import cn.idev.excel.metadata.data.ReadCellData;
import cn.idev.excel.metadata.data.WriteCellData;
import cn.idev.excel.metadata.property.ExcelContentProperty;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.excel.annotation.ExcelEnumFormat;
import lombok.extern.slf4j.Slf4j;

View File

@ -0,0 +1,200 @@
package org.dromara.common.excel.core;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.idev.excel.annotation.ExcelIgnore;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import lombok.SneakyThrows;
import org.apache.poi.ss.util.CellRangeAddress;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.excel.annotation.CellMerge;
import java.lang.reflect.Field;
import java.util.*;
/**
* 单元格合并处理器
*
* @author Lion Li
*/
public class CellMergeHandler {
private final boolean hasTitle;
private int rowIndex;
private CellMergeHandler(final boolean hasTitle) {
this.hasTitle = hasTitle;
// 行合并开始下标
this.rowIndex = hasTitle ? 1 : 0;
}
@SneakyThrows
public List<CellRangeAddress> handle(List<?> rows) {
// 如果入参为空集合则返回空集
if (CollUtil.isEmpty(rows)) {
return Collections.emptyList();
}
// 获取有合并注解的字段
Map<Field, FieldColumnIndex> mergeFields = getFieldColumnIndexMap(rows.get(0).getClass());
// 如果没有需要合并的字段则返回空集
if (CollUtil.isEmpty(mergeFields)) {
return Collections.emptyList();
}
// 结果集
List<CellRangeAddress> result = new ArrayList<>();
// 生成两两合并单元格
Map<Field, RepeatCell> rowRepeatCellMap = new HashMap<>();
for (Map.Entry<Field, FieldColumnIndex> item : mergeFields.entrySet()) {
Field field = item.getKey();
FieldColumnIndex itemValue = item.getValue();
int colNum = itemValue.colIndex();
CellMerge cellMerge = itemValue.cellMerge();
for (int i = 0; i < rows.size(); i++) {
// 当前行数据
Object currentRowObj = rows.get(i);
// 当前行数据字段值
Object currentRowObjFieldVal = ReflectUtils.invokeGetter(currentRowObj, field.getName());
// 空值跳过不处理
if (currentRowObjFieldVal == null || "".equals(currentRowObjFieldVal)) {
continue;
}
// 单元格合并Map是否存在数据如果不存在则添加当前行的字段值
if (!rowRepeatCellMap.containsKey(field)) {
rowRepeatCellMap.put(field, RepeatCell.of(currentRowObjFieldVal, i));
continue;
}
// 获取 单元格合并Map 中字段值
RepeatCell repeatCell = rowRepeatCellMap.get(field);
Object cellValue = repeatCell.value();
int current = repeatCell.current();
// 检查是否满足合并条件
// currentRowObj 当前行数据
// rows.get(i - 1) 上一行数据 注:由于 if (!rowRepeatCellMap.containsKey(field)) 条件的存在,所以该 i 必不可能小于1
// cellMerge 当前行字段合并注解
boolean merge = isMerge(currentRowObj, rows.get(i - 1), cellMerge);
// 是否添加到结果集
boolean isAddResult = false;
// 最新行
int lastRow = i + rowIndex - 1;
// 如果当前行字段值和缓存中的字段值不相等,或不满足合并条件,则替换
if (!currentRowObjFieldVal.equals(cellValue) || !merge) {
rowRepeatCellMap.put(field, RepeatCell.of(currentRowObjFieldVal, i));
isAddResult = true;
}
// 如果最后一行不能合并,检查之前的数据是否需要合并;如果最后一行可以合并,则直接合并到最后
if (i == rows.size() - 1) {
isAddResult = true;
if (i > current) {
lastRow = i + rowIndex;
}
}
if (isAddResult && i > current) {
result.add(new CellRangeAddress(current + rowIndex, lastRow, colNum, colNum));
}
}
}
return result;
}
/**
* 获取带有合并注解的字段列索引和合并注解信息Map集
*/
private Map<Field, FieldColumnIndex> getFieldColumnIndexMap(Class<?> clazz) {
boolean annotationPresent = clazz.isAnnotationPresent(ExcelIgnoreUnannotated.class);
Field[] fields = ReflectUtils.getFields(clazz, field -> {
if ("serialVersionUID".equals(field.getName())) {
return false;
}
if (field.isAnnotationPresent(ExcelIgnore.class)) {
return false;
}
return !annotationPresent || field.isAnnotationPresent(ExcelProperty.class);
});
// 有注解的字段
Map<Field, FieldColumnIndex> mergeFields = new HashMap<>();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
if (!field.isAnnotationPresent(CellMerge.class)) {
continue;
}
CellMerge cm = field.getAnnotation(CellMerge.class);
int index = cm.index() == -1 ? i : cm.index();
mergeFields.put(field, FieldColumnIndex.of(index, cm));
if (hasTitle) {
ExcelProperty property = field.getAnnotation(ExcelProperty.class);
rowIndex = Math.max(rowIndex, property.value().length);
}
}
return mergeFields;
}
private boolean isMerge(Object currentRow, Object preRow, CellMerge cellMerge) {
final String[] mergeBy = cellMerge.mergeBy();
if (StrUtil.isAllNotBlank(mergeBy)) {
//比对当前行和上一行的各个属性值一一比对 如果全为真 则为真
for (String fieldName : mergeBy) {
final Object valCurrent = ReflectUtil.getFieldValue(currentRow, fieldName);
final Object valPre = ReflectUtil.getFieldValue(preRow, fieldName);
if (!Objects.equals(valPre, valCurrent)) {
//依赖字段如有任一不等值,则标记为不可合并
return false;
}
}
}
return true;
}
/**
* 单元格合并
*/
record RepeatCell(Object value, int current) {
static RepeatCell of(Object value, int current) {
return new RepeatCell(value, current);
}
}
/**
* 字段列索引和合并注解信息
*/
record FieldColumnIndex(int colIndex, CellMerge cellMerge) {
static FieldColumnIndex of(int colIndex, CellMerge cellMerge) {
return new FieldColumnIndex(colIndex, cellMerge);
}
}
/**
* 创建一个单元格合并处理器实例
*
* @param hasTitle 是否合并标题
* @return 单元格合并处理器
*/
public static CellMergeHandler of(final boolean hasTitle) {
return new CellMergeHandler(hasTitle);
}
/**
* 创建一个单元格合并处理器实例(默认不合并标题)
*
* @return 单元格合并处理器
*/
public static CellMergeHandler of() {
return new CellMergeHandler(false);
}
}

View File

@ -1,24 +1,15 @@
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 com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.WorkbookWriteHandler;
import com.alibaba.excel.write.handler.context.WorkbookWriteHandlerContext;
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.SneakyThrows;
import cn.idev.excel.metadata.Head;
import cn.idev.excel.write.handler.WorkbookWriteHandler;
import cn.idev.excel.write.handler.context.WorkbookWriteHandlerContext;
import cn.idev.excel.write.merge.AbstractMergeStrategy;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.excel.annotation.CellMerge;
import java.lang.reflect.Field;
import java.util.*;
/**
@ -30,128 +21,39 @@ import java.util.*;
public class CellMergeStrategy extends AbstractMergeStrategy implements WorkbookWriteHandler {
private final List<CellRangeAddress> cellList;
private final boolean hasTitle;
private int rowIndex;
public CellMergeStrategy(List<CellRangeAddress> cellList) {
this.cellList = cellList;
}
public CellMergeStrategy(List<?> list, boolean hasTitle) {
this.hasTitle = hasTitle;
// 行合并开始下标
this.rowIndex = hasTitle ? 1 : 0;
this.cellList = handle(list, hasTitle);
this.cellList = CellMergeHandler.of(hasTitle).handle(list);
}
@Override
protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
if (CollUtil.isEmpty(cellList)){
return;
}
//单元格写入了,遍历合并区域,如果该Cell在区域内,但非首行,则清空
final int rowIndex = cell.getRowIndex();
if (CollUtil.isNotEmpty(cellList)){
for (CellRangeAddress cellAddresses : cellList) {
final int firstRow = cellAddresses.getFirstRow();
if (cellAddresses.isInRange(cell) && rowIndex != firstRow){
cell.setBlank();
}
for (CellRangeAddress cellAddresses : cellList) {
final int firstRow = cellAddresses.getFirstRow();
if (cellAddresses.isInRange(cell) && rowIndex != firstRow){
cell.setBlank();
}
}
}
@Override
public void afterWorkbookDispose(final WorkbookWriteHandlerContext context) {
if (CollUtil.isEmpty(cellList)){
return;
}
//当前表格写完后,统一写入
if (CollUtil.isNotEmpty(cellList)){
for (CellRangeAddress item : cellList) {
context.getWriteContext().writeSheetHolder().getSheet().addMergedRegion(item);
}
for (CellRangeAddress item : cellList) {
context.getWriteContext().writeSheetHolder().getSheet().addMergedRegion(item);
}
}
@SneakyThrows
private List<CellRangeAddress> handle(List<?> list, boolean hasTitle) {
List<CellRangeAddress> cellList = new ArrayList<>();
if (CollUtil.isEmpty(list)) {
return cellList;
}
Field[] fields = ReflectUtils.getFields(list.get(0).getClass(), field -> !"serialVersionUID".equals(field.getName()));
// 有注解的字段
List<Field> mergeFields = new ArrayList<>();
List<Integer> mergeFieldsIndex = new ArrayList<>();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
if (field.isAnnotationPresent(CellMerge.class)) {
CellMerge cm = field.getAnnotation(CellMerge.class);
mergeFields.add(field);
mergeFieldsIndex.add(cm.index() == -1 ? i : cm.index());
if (hasTitle) {
ExcelProperty property = field.getAnnotation(ExcelProperty.class);
rowIndex = Math.max(rowIndex, property.value().length);
}
}
}
Map<Field, RepeatCell> map = new HashMap<>();
// 生成两两合并单元格
for (int i = 0; i < list.size(); i++) {
for (int j = 0; j < mergeFields.size(); j++) {
Field field = mergeFields.get(j);
Object val = ReflectUtils.invokeGetter(list.get(i), field.getName());
int colNum = mergeFieldsIndex.get(j);
if (!map.containsKey(field)) {
map.put(field, new RepeatCell(val, i));
} else {
RepeatCell repeatCell = map.get(field);
Object cellValue = repeatCell.getValue();
if (cellValue == null || "".equals(cellValue)) {
// 空值跳过不合并
continue;
}
if (!cellValue.equals(val)) {
if ((i - repeatCell.getCurrent() > 1)) {
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
}
map.put(field, new RepeatCell(val, i));
} else if (i == list.size() - 1) {
if (i > repeatCell.getCurrent() && isMerge(list, i, field)) {
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
}
} else if (!isMerge(list, i, field)) {
if ((i - repeatCell.getCurrent() > 1)) {
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
}
map.put(field, new RepeatCell(val, i));
}
}
}
}
return cellList;
}
private boolean isMerge(List<?> list, int i, Field field) {
boolean isMerge = true;
CellMerge cm = field.getAnnotation(CellMerge.class);
final String[] mergeBy = cm.mergeBy();
if (StrUtil.isAllNotBlank(mergeBy)) {
//比对当前list(i)和list(i - 1)的各个属性值一一比对 如果全为真 则为真
for (String fieldName : mergeBy) {
final Object valCurrent = ReflectUtil.getFieldValue(list.get(i), fieldName);
final Object valPre = ReflectUtil.getFieldValue(list.get(i - 1), fieldName);
if (!Objects.equals(valPre, valCurrent)) {
//依赖字段如有任一不等值,则标记为不可合并
isMerge = false;
}
}
}
return isMerge;
}
@Data
@AllArgsConstructor
static class RepeatCell {
private Object value;
private int current;
}
}

View File

@ -1,10 +1,10 @@
package org.dromara.common.excel.core;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelAnalysisException;
import com.alibaba.excel.exception.ExcelDataConvertException;
import cn.idev.excel.context.AnalysisContext;
import cn.idev.excel.event.AnalysisEventListener;
import cn.idev.excel.exception.ExcelAnalysisException;
import cn.idev.excel.exception.ExcelDataConvertException;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;

View File

@ -1,5 +1,6 @@
package org.dromara.common.excel.core;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
@ -65,7 +66,7 @@ public class DropDownOptions {
StringBuilder stringBuffer = new StringBuilder();
String regex = "^[\\S\\d\\u4e00-\\u9fa5]+$";
for (int i = 0; i < vars.length; i++) {
String var = StrUtil.trimToEmpty(String.valueOf(vars[i]));
String var = StrUtil.trimToEmpty(Convert.toStr(vars[i]));
if (!var.matches(regex)) {
throw new ServiceException("选项数据不符合规则,仅允许使用中英文字符以及数字");
}

View File

@ -1,16 +1,17 @@
package org.dromara.common.excel.core;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.metadata.FieldCache;
import com.alibaba.excel.metadata.FieldWrapper;
import com.alibaba.excel.util.ClassUtils;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import cn.idev.excel.metadata.FieldCache;
import cn.idev.excel.metadata.FieldWrapper;
import cn.idev.excel.util.ClassUtils;
import cn.idev.excel.write.handler.SheetWriteHandler;
import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
import cn.idev.excel.write.metadata.holder.WriteWorkbookHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
@ -103,7 +104,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
if (StringUtils.isNotBlank(dictType)) {
// 如果传递了字典名,则依据字典建立下拉
Collection<String> values = Optional.ofNullable(dictService.getAllDictByDictType(dictType))
.orElseThrow(() -> new ServiceException(String.format("字典 %s 不存在", dictType)))
.orElseThrow(() -> new ServiceException("字典 {} 不存在", dictType))
.values();
options = new ArrayList<>(values);
} else if (StringUtils.isNotBlank(converterExp)) {
@ -115,7 +116,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
// 否则如果指定了@ExcelEnumFormat则使用枚举的逻辑
ExcelEnumFormat format = field.getDeclaredAnnotation(ExcelEnumFormat.class);
List<Object> values = EnumUtil.getFieldValues(format.enumClass(), format.textField());
options = StreamUtils.toList(values, String::valueOf);
options = StreamUtils.toList(values, Convert::toStr);
}
if (ObjectUtil.isNotEmpty(options)) {
// 仅当下拉可选项不为空时执行
@ -175,7 +176,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
List<String> firstOptions = options.getOptions();
Map<String, List<String>> secoundOptionsMap = options.getNextOptions();
// 采用按行填充数据的方式,避免EasyExcel出现数据无法写入的问题
// 采用按行填充数据的方式,避免出现数据无法写入的问题
// Attempting to write a row in the range that is already written to disk
// 使用ArrayList记载数据防止乱序
@ -291,9 +292,11 @@ public class ExcelDownHandler implements SheetWriteHandler {
* @param value 下拉选可选值
*/
private void dropDownWithSheet(DataValidationHelper helper, Workbook workbook, Sheet sheet, Integer celIndex, List<String> value) {
//由于poi的写出相关问题超过100个会被临时写进硬盘导致后续内存合并会出Attempting to write a row[] in the range [] that is already written to disk
String tmpOptionsSheetName = OPTIONS_SHEET_NAME + "_" + currentOptionsColumnIndex;
// 创建下拉数据表
Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)))
.orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)));
Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(tmpOptionsSheetName)))
.orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(tmpOptionsSheetName)));
// 将下拉表隐藏
workbook.setSheetHidden(workbook.getSheetIndex(simpleDataSheet), true);
// 完善纵向的一级选项数据表
@ -302,9 +305,9 @@ public class ExcelDownHandler implements SheetWriteHandler {
// 获取每一选项行,如果没有则创建
Row row = Optional.ofNullable(simpleDataSheet.getRow(i))
.orElseGet(() -> simpleDataSheet.createRow(finalI));
// 获取本级选项对应的选项列,如果没有则创建
Cell cell = Optional.ofNullable(row.getCell(currentOptionsColumnIndex))
.orElseGet(() -> row.createCell(currentOptionsColumnIndex));
// 获取本级选项对应的选项列,如果没有则创建。上述采用多个sheet,默认索引为1列
Cell cell = Optional.ofNullable(row.getCell(0))
.orElseGet(() -> row.createCell(0));
// 设置值
cell.setCellValue(value.get(i));
}
@ -312,13 +315,13 @@ public class ExcelDownHandler implements SheetWriteHandler {
// 创建名称管理器
Name name = workbook.createName();
// 设置名称管理器的别名
String nameName = String.format("%s_%d", OPTIONS_SHEET_NAME, celIndex);
String nameName = String.format("%s_%d", tmpOptionsSheetName, celIndex);
name.setNameName(nameName);
// 以纵向第一列创建一级下拉拼接引用位置
String function = String.format("%s!$%s$1:$%s$%d",
OPTIONS_SHEET_NAME,
getExcelColumnName(currentOptionsColumnIndex),
getExcelColumnName(currentOptionsColumnIndex),
tmpOptionsSheetName,
getExcelColumnName(0),
getExcelColumnName(0),
value.size());
// 设置名称管理器的引用位置
name.setRefersToFormula(function);

View File

@ -1,6 +1,6 @@
package org.dromara.common.excel.core;
import com.alibaba.excel.read.listener.ReadListener;
import cn.idev.excel.read.listener.ReadListener;
/**
* Excel 导入监听

View File

@ -1,19 +1,19 @@
package org.dromara.common.excel.handler;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.excel.metadata.data.DataFormatData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.util.StyleUtil;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.metadata.data.DataFormatData;
import cn.idev.excel.metadata.data.WriteCellData;
import cn.idev.excel.util.StyleUtil;
import cn.idev.excel.write.handler.CellWriteHandler;
import cn.idev.excel.write.handler.SheetWriteHandler;
import cn.idev.excel.write.handler.context.CellWriteHandlerContext;
import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
import cn.idev.excel.write.metadata.style.WriteCellStyle;
import cn.idev.excel.write.metadata.style.WriteFont;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.excel.annotation.ExcelNotation;
import org.dromara.common.excel.annotation.ExcelRequired;
@ -31,12 +31,12 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
/**
* 批注
*/
private final Map<Integer, String> notationMap;
private final Map<String, String> notationMap;
/**
* 头列字体颜色
*/
private final Map<Integer, Short> headColumnMap;
private final Map<String, Short> headColumnMap;
public DataWriteHandler(Class<?> clazz) {
@ -49,15 +49,16 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
if (CollUtil.isEmpty(notationMap) && CollUtil.isEmpty(headColumnMap)) {
return;
}
// 第一行
WriteCellData<?> cellData = context.getFirstCellData();
// 第一个格子
WriteCellStyle writeCellStyle = cellData.getOrCreateStyle();
DataFormatData dataFormatData = new DataFormatData();
// 单元格设置为文本格式
dataFormatData.setIndex((short) 49);
writeCellStyle.setDataFormatData(dataFormatData);
if (context.getHead()) {
DataFormatData dataFormatData = new DataFormatData();
// 单元格设置为文本格式
dataFormatData.setIndex((short) 49);
writeCellStyle.setDataFormatData(dataFormatData);
Cell cell = context.getCell();
WriteSheetHolder writeSheetHolder = context.getWriteSheetHolder();
Sheet sheet = writeSheetHolder.getSheet();
@ -67,17 +68,17 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
WriteFont headWriteFont = new WriteFont();
// 加粗
headWriteFont.setBold(true);
if (CollUtil.isNotEmpty(headColumnMap) && headColumnMap.containsKey(cell.getColumnIndex())) {
if (CollUtil.isNotEmpty(headColumnMap) && headColumnMap.containsKey(cell.getStringCellValue())) {
// 设置字体颜色
headWriteFont.setColor(headColumnMap.get(cell.getColumnIndex()));
headWriteFont.setColor(headColumnMap.get(cell.getStringCellValue()));
}
writeCellStyle.setWriteFont(headWriteFont);
CellStyle cellStyle = StyleUtil.buildCellStyle(workbook, null, writeCellStyle);
cell.setCellStyle(cellStyle);
if (CollUtil.isNotEmpty(notationMap) && notationMap.containsKey(cell.getColumnIndex())) {
if (CollUtil.isNotEmpty(notationMap) && notationMap.containsKey(cell.getStringCellValue())) {
// 批注内容
String notationContext = notationMap.get(cell.getColumnIndex());
String notationContext = notationMap.get(cell.getStringCellValue());
// 创建绘图对象
Comment comment = drawing.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), 0, (short) 5, 5));
comment.setString(new XSSFRichTextString(notationContext));
@ -89,23 +90,16 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
/**
* 获取必填列
*/
private static Map<Integer, Short> getRequiredMap(Class<?> clazz) {
Map<Integer, Short> requiredMap = new HashMap<>();
private static Map<String, Short> getRequiredMap(Class<?> clazz) {
Map<String, Short> requiredMap = new HashMap<>();
Field[] fields = clazz.getDeclaredFields();
// 检查 fields 数组是否为空
if (fields.length == 0) {
return requiredMap;
}
Field[] filteredFields = ReflectUtils.getFields(clazz, field -> !"serialVersionUID".equals(field.getName()));
for (int i = 0; i < filteredFields.length; i++) {
Field field = filteredFields[i];
for (Field field : fields) {
if (!field.isAnnotationPresent(ExcelRequired.class)) {
continue;
}
ExcelRequired excelRequired = field.getAnnotation(ExcelRequired.class);
int columnIndex = excelRequired.index() == -1 ? i : excelRequired.index();
requiredMap.put(columnIndex, excelRequired.fontColor().getIndex());
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
requiredMap.put(excelProperty.value()[0], excelRequired.fontColor().getIndex());
}
return requiredMap;
}
@ -113,22 +107,16 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
/**
* 获取批注
*/
private static Map<Integer, String> getNotationMap(Class<?> clazz) {
Map<Integer, String> notationMap = new HashMap<>();
private static Map<String, String> getNotationMap(Class<?> clazz) {
Map<String, String> notationMap = new HashMap<>();
Field[] fields = clazz.getDeclaredFields();
// 检查 fields 数组是否为空
if (fields.length == 0) {
return notationMap;
}
Field[] filteredFields = ReflectUtils.getFields(clazz, field -> !"serialVersionUID".equals(field.getName()));
for (int i = 0; i < filteredFields.length; i++) {
Field field = filteredFields[i];
for (Field field : fields) {
if (!field.isAnnotationPresent(ExcelNotation.class)) {
continue;
}
ExcelNotation excelNotation = field.getAnnotation(ExcelNotation.class);
int columnIndex = excelNotation.index() == -1 ? i : excelNotation.index();
notationMap.put(columnIndex, excelNotation.value());
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
notationMap.put(excelProperty.value()[0], excelNotation.value());
}
return notationMap;
}

View File

@ -3,13 +3,13 @@ package org.dromara.common.excel.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.IdUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.alibaba.excel.write.metadata.fill.FillWrapper;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import cn.idev.excel.FastExcel;
import cn.idev.excel.ExcelWriter;
import cn.idev.excel.write.builder.ExcelWriterSheetBuilder;
import cn.idev.excel.write.metadata.WriteSheet;
import cn.idev.excel.write.metadata.fill.FillConfig;
import cn.idev.excel.write.metadata.fill.FillWrapper;
import cn.idev.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AccessLevel;
@ -43,7 +43,7 @@ public class ExcelUtil {
* @return 转换后集合
*/
public static <T> List<T> importExcel(InputStream is, Class<T> clazz) {
return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
return FastExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
}
@ -57,7 +57,7 @@ public class ExcelUtil {
*/
public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate) {
DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate);
EasyExcel.read(is, clazz, listener).sheet().doRead();
FastExcel.read(is, clazz, listener).sheet().doRead();
return listener.getExcelResult();
}
@ -70,7 +70,7 @@ public class ExcelUtil {
* @return 转换后集合
*/
public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelListener<T> listener) {
EasyExcel.read(is, clazz, listener).sheet().doRead();
FastExcel.read(is, clazz, listener).sheet().doRead();
return listener.getExcelResult();
}
@ -186,7 +186,7 @@ public class ExcelUtil {
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge,
OutputStream os, List<DropDownOptions> options) {
ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
ExcelWriterSheetBuilder builder = FastExcel.write(os, clazz)
.autoCloseStream(false)
// 自动适配
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
@ -215,6 +215,9 @@ public class ExcelUtil {
*/
public static <T> void exportTemplate(List<T> data, String filename, String templatePath, HttpServletResponse response) {
try {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
resetResponse(filename, response);
ServletOutputStream os = response.getOutputStream();
exportTemplate(data, templatePath, os);
@ -233,21 +236,19 @@ public class ExcelUtil {
* @param os 输出流
*/
public static <T> void exportTemplate(List<T> data, String templatePath, OutputStream os) {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = EasyExcel.write(os)
ExcelWriter excelWriter = FastExcel.write(os)
.withTemplate(templateResource.getStream())
.autoCloseStream(false)
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.registerWriteHandler(new DataWriteHandler(data.get(0).getClass()))
.build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
WriteSheet writeSheet = FastExcel.writerSheet().build();
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
// 单表多数据导出 模板格式为 {.属性}
for (T d : data) {
excelWriter.fill(d, writeSheet);
excelWriter.fill(d, fillConfig, writeSheet);
}
excelWriter.finish();
}
@ -264,6 +265,9 @@ public class ExcelUtil {
*/
public static void exportTemplateMultiList(Map<String, Object> data, String filename, String templatePath, HttpServletResponse response) {
try {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
resetResponse(filename, response);
ServletOutputStream os = response.getOutputStream();
exportTemplateMultiList(data, templatePath, os);
@ -284,6 +288,9 @@ public class ExcelUtil {
*/
public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String filename, String templatePath, HttpServletResponse response) {
try {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
resetResponse(filename, response);
ServletOutputStream os = response.getOutputStream();
exportTemplateMultiSheet(data, templatePath, os);
@ -302,17 +309,14 @@ public class ExcelUtil {
* @param os 输出流
*/
public static void exportTemplateMultiList(Map<String, Object> data, String templatePath, OutputStream os) {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = EasyExcel.write(os)
ExcelWriter excelWriter = FastExcel.write(os)
.withTemplate(templateResource.getStream())
.autoCloseStream(false)
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
WriteSheet writeSheet = FastExcel.writerSheet().build();
for (Map.Entry<String, Object> map : data.entrySet()) {
// 设置列表后续还有数据
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
@ -320,7 +324,7 @@ public class ExcelUtil {
// 多表导出必须使用 FillWrapper
excelWriter.fill(new FillWrapper(map.getKey(), (Collection<?>) map.getValue()), fillConfig, writeSheet);
} else {
excelWriter.fill(map.getValue(), writeSheet);
excelWriter.fill(map.getValue(), fillConfig, writeSheet);
}
}
excelWriter.finish();
@ -336,18 +340,15 @@ public class ExcelUtil {
* @param os 输出流
*/
public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String templatePath, OutputStream os) {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = EasyExcel.write(os)
ExcelWriter excelWriter = FastExcel.write(os)
.withTemplate(templateResource.getStream())
.autoCloseStream(false)
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.build();
for (int i = 0; i < data.size(); i++) {
WriteSheet writeSheet = EasyExcel.writerSheet(i).build();
WriteSheet writeSheet = FastExcel.writerSheet(i).build();
for (Map.Entry<String, Object> map : data.get(i).entrySet()) {
// 设置列表后续还有数据
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();

View File

@ -4,8 +4,9 @@ import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.dromara.common.json.handler.BigNumberSerializer;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.json.handler.BigNumberSerializer;
import org.dromara.common.json.handler.CustomDateDeserializer;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
@ -15,6 +16,7 @@ import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.TimeZone;
/**
@ -38,6 +40,7 @@ public class JacksonConfig {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
javaTimeModule.addDeserializer(Date.class, new CustomDateDeserializer());
builder.modules(javaTimeModule);
builder.timeZone(TimeZone.getDefault());
log.info("初始化 jackson 配置");

View File

@ -0,0 +1,37 @@
package org.dromara.common.json.handler;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import org.dromara.common.core.utils.ObjectUtils;
import java.io.IOException;
import java.util.Date;
/**
* 自定义 Date 类型反序列化处理器(支持多种格式)
*
* @author AprilWind
*/
public class CustomDateDeserializer extends JsonDeserializer<Date> {
/**
* 反序列化逻辑:将字符串转换为 Date 对象
*
* @param p JSON 解析器,用于获取字符串值
* @param ctxt 上下文环境(可用于获取更多配置)
* @return 转换后的 Date 对象,若为空字符串返回 null
* @throws IOException 当字符串格式非法或转换失败时抛出
*/
@Override
public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
DateTime parse = DateUtil.parse(p.getText());
if (ObjectUtils.isNull(parse)) {
return null;
}
return parse.toJdkDate();
}
}

View File

@ -27,9 +27,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import java.util.Collection;
import java.util.Map;
import java.util.StringJoiner;
import java.util.*;
/**
* 操作日志记录处理
@ -176,14 +174,28 @@ public class LogAspect {
if (ArrayUtil.isEmpty(paramsArray)) {
return params.toString();
}
String[] exclude = ArrayUtil.addAll(excludeParamNames, EXCLUDE_PROPERTIES);
for (Object o : paramsArray) {
if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
String str = JsonUtils.toJsonString(o);
Dict dict = JsonUtils.parseMap(str);
if (MapUtil.isNotEmpty(dict)) {
MapUtil.removeAny(dict, EXCLUDE_PROPERTIES);
MapUtil.removeAny(dict, excludeParamNames);
str = JsonUtils.toJsonString(dict);
String str = "";
if (o instanceof List<?> list) {
List<Dict> list1 = new ArrayList<>();
for (Object obj : list) {
String str1 = JsonUtils.toJsonString(obj);
Dict dict = JsonUtils.parseMap(str1);
if (MapUtil.isNotEmpty(dict)) {
MapUtil.removeAny(dict, exclude);
list1.add(dict);
}
}
str = JsonUtils.toJsonString(list1);
} else {
str = JsonUtils.toJsonString(o);
Dict dict = JsonUtils.parseMap(str);
if (MapUtil.isNotEmpty(dict)) {
MapUtil.removeAny(dict, exclude);
str = JsonUtils.toJsonString(dict);
}
}
params.add(str);
}

View File

@ -0,0 +1,54 @@
package org.dromara.common.mybatis.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.dromara.common.mybatis.annotation.DataPermission;
import org.dromara.common.mybatis.helper.DataPermissionHelper;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 数据权限注解Advice
*
* @author 秋辞未寒
*/
@Slf4j
public class DataPermissionAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object target = invocation.getThis();
Method method = invocation.getMethod();
Object[] args = invocation.getArguments();
// 设置权限注解
DataPermissionHelper.setPermission(getDataPermissionAnnotation(target, method, args));
try {
// 执行代理方法
return invocation.proceed();
} finally {
// 清除权限注解
DataPermissionHelper.removePermission();
}
}
/**
* 获取数据权限注解
*/
private DataPermission getDataPermissionAnnotation(Object target, Method method,Object[] args){
DataPermission dataPermission = method.getAnnotation(DataPermission.class);
// 优先获取方法上的注解
if (dataPermission != null) {
return dataPermission;
}
// 方法上没有注解,则获取类上的注解
Class<?> targetClass = target.getClass();
// 如果是 JDK 动态代理则获取真实的Class实例
if (Proxy.isProxyClass(targetClass)) {
targetClass = targetClass.getInterfaces()[0];
}
dataPermission = targetClass.getAnnotation(DataPermission.class);
return dataPermission;
}
}

View File

@ -1,50 +0,0 @@
package org.dromara.common.mybatis.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.dromara.common.mybatis.annotation.DataPermission;
import org.dromara.common.mybatis.helper.DataPermissionHelper;
/**
* 数据权限处理
*
* @author Lion Li
*/
@Slf4j
@Aspect
public class DataPermissionAspect {
/**
* 处理请求前执行
*/
@Before(value = "@annotation(dataPermission)")
public void doBefore(JoinPoint joinPoint, DataPermission dataPermission) {
DataPermissionHelper.setPermission(dataPermission);
}
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "@annotation(dataPermission)")
public void doAfterReturning(JoinPoint joinPoint, DataPermission dataPermission) {
DataPermissionHelper.removePermission();
}
/**
* 拦截异常操作
*
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "@annotation(dataPermission)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, DataPermission dataPermission, Exception e) {
DataPermissionHelper.removePermission();
}
}

View File

@ -0,0 +1,39 @@
package org.dromara.common.mybatis.aspect;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.mybatis.annotation.DataPermission;
import org.springframework.aop.support.StaticMethodMatcherPointcut;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 数据权限匹配切点
*
* @author 秋辞未寒
*/
@Slf4j
@SuppressWarnings("all")
public class DataPermissionPointcut extends StaticMethodMatcherPointcut {
@Override
public boolean matches(Method method, Class<?> targetClass) {
// 优先匹配方法
// 数据权限注解不对继承生效,所以检查当前方法是否有注解即可,不再往上匹配父类或接口
if (method.isAnnotationPresent(DataPermission.class)) {
return true;
}
// MyBatis 的 Mapper 就是通过 JDK 动态代理实现的,所以这里需要检查是否匹配 JDK 的动态代理
Class<?> targetClassRef = targetClass;
if (Proxy.isProxyClass(targetClassRef)) {
// 数据权限注解不对继承生效,但由于 SpringIOC 容器拿到的实际上是 MyBatis 代理过后的 Mapper而 targetClass.isAnnotationPresent 实际匹配的是 Proxy 类的注解,不会查找代理类。
// 所以这里不能用 targetClass.isAnnotationPresent只能用 AnnotatedElementUtils.hasAnnotation 或 targetClass.getInterfaces()[0].isAnnotationPresent 去做匹配,以检查被代理的 MapperClass 是否具有注解
// 原理JDK 动态代理本质上就是对接口进行实现然后对具体的接口实现做代理,所以直接通过接口可以拿到实际的 MapperClass
targetClassRef = targetClass.getInterfaces()[0];
}
return targetClassRef.isAnnotationPresent(DataPermission.class);
}
}

View File

@ -0,0 +1,33 @@
package org.dromara.common.mybatis.aspect;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
/**
* 数据权限注解切面定义
*
* @author 秋辞未寒
*/
@SuppressWarnings("all")
public class DataPermissionPointcutAdvisor extends AbstractPointcutAdvisor {
private final Advice advice;
private final Pointcut pointcut;
public DataPermissionPointcutAdvisor() {
this.advice = new DataPermissionAdvice();
this.pointcut = new DataPermissionPointcut();
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public Advice getAdvice() {
return this.advice;
}
}

View File

@ -11,15 +11,17 @@ import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerIntercept
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.dromara.common.core.factory.YmlPropertySourceFactory;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.mybatis.aspect.DataPermissionAspect;
import org.dromara.common.mybatis.aspect.DataPermissionPointcutAdvisor;
import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler;
import org.dromara.common.mybatis.handler.MybatisExceptionHandler;
import org.dromara.common.mybatis.handler.PlusPostInitTableInfoHandler;
import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.Role;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
@ -27,6 +29,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
*
* @author Lion Li
*/
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@EnableTransactionManagement(proxyTargetClass = true)
@MapperScan("${mybatis-plus.mapperPackage}")
@PropertySource(value = "classpath:common-mybatis.yml", factory = YmlPropertySourceFactory.class)
@ -54,15 +57,16 @@ public class MybatisPlusConfig {
* 数据权限拦截器
*/
public PlusDataPermissionInterceptor dataPermissionInterceptor() {
return new PlusDataPermissionInterceptor(SpringUtils.getProperty("mybatis-plus.mapperPackage"));
return new PlusDataPermissionInterceptor();
}
/**
* 数据权限切面处理器
*/
@Bean
public DataPermissionAspect dataPermissionAspect() {
return new DataPermissionAspect();
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public DataPermissionPointcutAdvisor dataPermissionPointcutAdvisor() {
return new DataPermissionPointcutAdvisor();
}
/**

View File

@ -6,9 +6,11 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.reflect.GenericTypeUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.Db;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.dromara.common.core.utils.MapstructUtils;
@ -130,7 +132,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @return 查询到的单个VO对象
*/
default V selectVoById(Serializable id) {
return selectVoById(id, this.currentVoClass());
return this.selectVoById(id, this.currentVoClass());
}
/**
@ -156,7 +158,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @return 查询到的VO对象列表
*/
default List<V> selectVoByIds(Collection<? extends Serializable> idList) {
return selectVoByIds(idList, this.currentVoClass());
return this.selectVoByIds(idList, this.currentVoClass());
}
/**
@ -182,7 +184,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @return 查询到的VO对象列表
*/
default List<V> selectVoByMap(Map<String, Object> map) {
return selectVoByMap(map, this.currentVoClass());
return this.selectVoByMap(map, this.currentVoClass());
}
/**
@ -208,7 +210,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @return 查询到的单个VO对象
*/
default V selectVoOne(Wrapper<T> wrapper) {
return selectVoOne(wrapper, this.currentVoClass());
return this.selectVoOne(wrapper, this.currentVoClass());
}
/**
@ -219,11 +221,12 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @return 查询到的单个VO对象
*/
default V selectVoOne(Wrapper<T> wrapper, boolean throwEx) {
return selectVoOne(wrapper, this.currentVoClass(), throwEx);
return this.selectVoOne(wrapper, this.currentVoClass(), throwEx);
}
/**
* 根据条件查询单个VO对象并指定返回的VO对象的类型
* 根据条件查询单个VO对象并指定返回的VO对象的类型(自动拼接 limit 1)
* 注意不要再自己添加 limit 1 做限制了
*
* @param wrapper 查询条件Wrapper
* @param voClass 返回的VO对象的Class对象
@ -231,11 +234,12 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @return 查询到的单个VO对象经过类型转换为指定的VO类后返回
*/
default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
return selectVoOne(wrapper, voClass, true);
return this.selectVoOne(wrapper, voClass, true);
}
/**
* 根据条件查询单个实体对象并将其转换为指定的VO对象
* 根据条件查询单个实体对象并将其转换为指定的VO对象(自动拼接 limit 1)
* 注意不要再自己添加 limit 1 做限制了
*
* @param wrapper 查询条件Wrapper
* @param voClass 要转换的VO类的Class对象
@ -251,13 +255,33 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
return MapstructUtils.convert(obj, voClass);
}
/**
* 根据条件查询单条记录(自动拼接 limit 1 限制返回 1 条数据,不依赖 {@code throwEx} 参数)
* 注意不要再自己添加 limit 1 做限制了
* <p>
* <strong>注意:</strong>
* 1. 使用 {@code Page<>(1, 1)} 强制分页查询,确保 SQL 自动添加 {@code LIMIT 1},因此 {@code throwEx} 参数不再生效
* 2. 原方法的 {@code throwEx} 逻辑(多条数据抛异常)已被优化掉,因为分页查询不会返回多条记录
* </p>
*
* @param queryWrapper 查询条件(可为 null
* @param throwEx <del>是否抛出异常(已弃用,此参数不再生效)</del>
* @return 单条记录或无数据时返回 null
*/
@Override
default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper, boolean throwEx) {
// 强制分页查询LIMIT 1确保最多返回 1 条记录
List<T> list = this.selectList(new Page<>(1, 1), queryWrapper);
return CollUtil.isEmpty(list) ? null : list.get(0);
}
/**
* 查询所有VO对象列表
*
* @return 查询到的VO对象列表
*/
default List<V> selectVoList() {
return selectVoList(new QueryWrapper<>(), this.currentVoClass());
return this.selectVoList(new QueryWrapper<>(), this.currentVoClass());
}
/**
@ -294,7 +318,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @return 查询到的VO对象分页列表
*/
default <P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper) {
return selectVoPage(page, wrapper, this.currentVoClass());
return this.selectVoPage(page, wrapper, this.currentVoClass());
}
/**

View File

@ -1,5 +1,6 @@
package org.dromara.common.mybatis.core.page;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.http.HttpStatus;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.Data;
@ -88,4 +89,19 @@ public class TableDataInfo<T> implements Serializable {
return rspData;
}
/**
* 根据原始数据列表和分页参数,构建表格分页数据对象(用于假分页)
*
* @param list 原始数据列表(全部数据)
* @param page 分页参数对象(包含当前页码、每页大小等)
* @return 构造好的分页结果 TableDataInfo<T>
*/
public static <T> TableDataInfo<T> build(List<T> list, IPage<T> page) {
if (CollUtil.isEmpty(list)) {
return TableDataInfo.build();
}
List<T> pageList = CollUtil.page((int) page.getCurrent() - 1, (int) page.getSize(), list);
return new TableDataInfo<>(pageList, list.size());
}
}

View File

@ -42,17 +42,46 @@ public enum DataBaseType {
* 根据数据库产品名称查找对应的数据库类型
*
* @param databaseProductName 数据库产品名称
* @return 对应的数据库类型枚举值,如果未找到则返回 null
* @return 对应的数据库类型枚举值
*/
public static DataBaseType find(String databaseProductName) {
if (StringUtils.isBlank(databaseProductName)) {
return null;
return MY_SQL;
}
for (DataBaseType type : values()) {
if (type.getType().equals(databaseProductName)) {
return type;
}
}
return null;
return MY_SQL;
}
/**
* 判断是否为 MySQL 类型
*/
public boolean isMySql() {
return this == MY_SQL;
}
/**
* 判断是否为 Oracle 类型
*/
public boolean isOracle() {
return this == ORACLE;
}
/**
* 判断是否为 PostgreSQL 类型
*/
public boolean isPostgreSql() {
return this == POSTGRE_SQL;
}
/**
* 判断是否为 SQL Server 类型
*/
public boolean isSqlServer() {
return this == SQL_SERVER;
}
}

View File

@ -22,6 +22,11 @@ import java.util.Date;
@Slf4j
public class InjectionMetaObjectHandler implements MetaObjectHandler {
/**
* 如果用户不存在默认注入-1代表无用户
*/
private static final Long DEFAULT_USER_ID = -1L;
/**
* 插入填充方法,用于在插入数据时自动填充实体对象中的创建时间、更新时间、创建人、更新人等信息
*
@ -45,6 +50,11 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
baseEntity.setCreateBy(userId);
baseEntity.setUpdateBy(userId);
baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), loginUser.getDeptId()));
} else {
// 填充创建人、更新人和创建部门信息
baseEntity.setCreateBy(DEFAULT_USER_ID);
baseEntity.setUpdateBy(DEFAULT_USER_ID);
baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), DEFAULT_USER_ID));
}
}
} else {
@ -74,6 +84,8 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
Long userId = LoginHelper.getUserId();
if (ObjectUtil.isNotNull(userId)) {
baseEntity.setUpdateBy(userId);
} else {
baseEntity.setUpdateBy(DEFAULT_USER_ID);
}
} else {
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
@ -93,7 +105,6 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
try {
loginUser = LoginHelper.getLoginUser();
} catch (Exception e) {
log.warn("自动注入警告 => 用户未登录");
return null;
}
return loginUser;

View File

@ -1,5 +1,6 @@
package org.dromara.common.mybatis.handler;
import cn.hutool.http.HttpStatus;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
@ -25,7 +26,7 @@ public class MybatisExceptionHandler {
public R<Void> handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',数据库中已存在记录'{}'", requestURI, e.getMessage());
return R.fail("数据库中已存在该记录,请联系管理员确认");
return R.fail(HttpStatus.HTTP_CONFLICT, "数据库中已存在该记录,请联系管理员确认");
}
/**
@ -35,12 +36,12 @@ public class MybatisExceptionHandler {
public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
String message = e.getMessage();
if (StringUtils.contains("CannotFindDataSourceException", message)) {
if (StringUtils.contains(message, "CannotFindDataSourceException")) {
log.error("请求地址'{}', 未找到数据源", requestURI);
return R.fail("未找到数据源,请联系管理员确认");
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "未找到数据源,请联系管理员确认");
}
log.error("请求地址'{}', Mybatis系统异常", requestURI, e);
return R.fail(message);
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, message);
}
}

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