276 Commits

Author SHA1 Message Date
296466fa13 !640 发布 5.3.0 新春版 祝大家新年快乐
Merge pull request !640 from 疯狂的狮子Li/dev
2025-01-24 05:08:28 +00:00
b528f0bd14 🧨🧨🧨发布 5.3.0 新春版 祝大家新年快乐 2025-01-24 13:06:31 +08:00
7c2c82fc0a fix 修复 postgres sql缺少字段问题 2025-01-24 12:42:48 +08:00
ffe8b16ff3 fix 修复 翻译报错问题 2025-01-24 12:17:36 +08:00
ecf7ebad53 update snailjob 升级 1.3.0 正式版
update warmflow 升级 1.6.6 正式版
2025-01-24 11:30:46 +08:00
3bf26cd509 fix 修复 pg强类型校验问题 2025-01-24 11:30:18 +08:00
a671d4a8a8 update warm-flow 升级 1.6.0 正式版 2025-01-22 16:41:14 +08:00
d9713d0f8c update 优化 测试用户增加工作流业务查看审批权限 2025-01-22 16:32:17 +08:00
aeaa33ebd3 fix 修复 params可能为null问题 2025-01-22 14:24:56 +08:00
c64de03d27 update 优化 WorkflowService 增加获取流程变量方法 2025-01-22 11:24:06 +08:00
2d99304396 update 优化 WorkflowService 增加获取流程变量方法 2025-01-22 11:19:51 +08:00
a22dc9537f update 优化 业务事件监听器增加流程审批参数传递 更方便的对接业务数据 2025-01-22 11:13:24 +08:00
6c28f8a0dd update 优化 驳回增加附件上传 2025-01-22 11:12:38 +08:00
c100168374 update 删除无用链接 2025-01-20 18:26:54 +08:00
8636d8b3e8 update snailjob 1.3.0-beta2 2025-01-20 15:08:40 +08:00
37b2d648b1 update 优化 发起流程api返回值使用对象封装 2025-01-20 12:52:55 +08:00
27b4992f6e fix 修复 流程类别回显问题 2025-01-20 12:38:10 +08:00
3c8d864b5f !639 发布 5.3.0-BETA 公测版本
Merge pull request !639 from 疯狂的狮子Li/dev
2025-01-20 03:35:45 +00:00
001297ca7a 发布 5.3.0-BETA 公测版本 2025-01-20 11:35:05 +08:00
4f3cbc4bc2 fix 修复分页构建没有默认值 2025-01-18 10:37:42 +08:00
008e02a406 update 优化任务办理 2025-01-17 21:58:43 +08:00
e8acfac091 fix 修复 文件下载 设置content-length无效问题 2025-01-17 18:03:00 +08:00
e1a26b0388 update 优化 缺少导包问题 2025-01-17 17:12:57 +08:00
a6fc47b4f6 fix 修复 部署工作流缺少字体文件问题 2025-01-17 17:08:50 +08:00
ce7f5121b0 update 优化 查询oss图片url接口改为query标识符 2025-01-17 17:08:50 +08:00
b38ca837d6 update 优化 绑定三方与解绑三方校验token是否存在 2025-01-17 17:08:50 +08:00
a2714fb9f7 update 优化 OSS私有桶的临时URL获取方法
Signed-off-by: 秋辞未寒 <545073804@qq.com>
2025-01-17 07:55:17 +00:00
8c57d694c5 update 优化 编译警告问题 替换过期方法与类 2025-01-17 10:49:32 +08:00
0ae521a7dc update springboot 3.3.5 => 3.4.1
update springdoc 2.7.0 => 2.8.3
update mybatis-plus 3.5.9 => 3.5.10
update hutool 5.8.31 => 5.8.35
update springboot-admin 3.3.4 => 3.4.1
update redisson 3.39.0 => 3.43.0
update mapstruct-plus 1.4.5 => 1.4.6
update lombok 1.18.34 => 1.18.36
update anyline 20241022 => 20250101
update snailjob beta1 => beta1.1
2025-01-17 10:42:31 +08:00
79ec3fd2c9 update 优化 升级warmflow 1.6.0-m5 解决监听器错误触发问题 2025-01-17 09:29:22 +08:00
69b95b3e7a update 优化 默认注释掉slave数据源 2025-01-16 17:56:29 +08:00
9d8b9fabbe update 优化 ws模块替换session的时候关闭session连接 2025-01-16 11:50:19 +08:00
875c9fa77c fix 修复 升级之后部署异常问题 2025-01-16 11:00:38 +08:00
6bc2d9d4a7 update 优化 升级sqlserver warmflow sql脚本 2025-01-16 10:23:55 +08:00
fe5a1f358d update 优化 更正类命名 2025-01-16 09:34:31 +08:00
1972537176 update 回退update.sql修改 2025-01-15 20:50:56 +08:00
8f21e9e2fd update 优化代码以及注释 2025-01-15 20:46:11 +08:00
089a79002f update 升级warm-flow的sql语句 2025-01-15 19:06:42 +08:00
ece1dee990 update 将部署文件迁移到更适合的文件夹下 2025-01-15 17:41:57 +08:00
652f5161a9 update 适配 新版本将xml转为json 2025-01-15 17:40:31 +08:00
4ba4ea4fcc update 升级warm-flow到1.6.0-m4 2025-01-15 16:56:09 +08:00
3444b50da6 update 优化 工作流配置书写错误 2025-01-15 09:56:08 +08:00
62b7d96551 fix 修复 实体类方法书写错误(不影响功能) 2025-01-14 23:00:04 +08:00
cb71df8a42 update 优化 工作流使用系统自带雪花生成器 2025-01-14 14:41:01 +08:00
dd54cc972a fix 修复 配置名书写错误 2025-01-14 14:12:19 +08:00
6f14c91d30 update 优化 !pr636 改为注解方式关闭 2025-01-14 14:05:30 +08:00
6f0dd8dc89 !636 update 优化 支持通过配置文件关闭工作流
* update 优化 支持通过配置文件关闭工作流
2025-01-14 05:40:07 +00:00
7d82f954ac update 优化 部分代码与bug 2025-01-13 19:37:45 +08:00
69130a76e4 fix 修复 多角色未拆分问题 2025-01-13 19:09:13 +08:00
9086d32bee fix 修复 多角色未拆分问题 2025-01-13 18:52:52 +08:00
a680310f80 fix 修复 satoken dao层获取timeout为秒导致丢失毫秒进度问题(临时修复 等satoken官方解决) 2025-01-13 15:03:23 +08:00
a07880e1d0 Revert "fix 修复 satoken dao层获取timeout为秒导致丢失毫秒进度问题(临时修复 等satoken官方解决)"
This reverts commit e36e8f7758.
2025-01-13 14:26:31 +08:00
ae584d54a6 !635 合并 warmflow 功能分支
* update 优化 工作流设计器支持token传输 只需要放行token头获取即可
* Merge remote-tracking branch 'origin/dev' into warm-flow-future
* update 优化 无需多余set变量
* update 优化 避免重复处理
* update 优化 实体类隔离
* add 增加流程启动,办理接口
* update 调整流程驳回
* Merge remote-tracking branch 'origin/dev' into warm-flow-future
* update warmflow 1.3.7
* update 增加 warmflow oracle pg sqlserver sql脚本文件
* update 增加 warmflow oracle pg sqlserver sql脚本文件
* add 新增workflow不同的sql语句
* add 新增添加租户同步默认流程定义
* update 优化 流程列表查询 删除无用mapper
* update 导入流程 支持并发多文件上传
* update 调整流程定义查询
* update 优化 统一书写格式
* Merge remote-tracking branch 'origin/dev' into warm-flow-future
* update 优化发布事件增加租户ID
* update 调整驳回记录
* Revert "update 获取用户简略信息,祖级部门列表,部门负责人等"
* update 获取用户简略信息,祖级部门列表,部门负责人等
* update 更新warm-flow版本到v1.3.6-m1
* update 更新注释信息
* fix 临时修复 warm参数读取问题
* update warm-flow 1.3.4 => 1.3.5 优化流程图导入
* update 更新warm-flow版本到v1.3.5 2024-12-20
* update 增加抄送人名称
* update 我的抄送增加申请人以及更新时间
* update 优化监听事件注释
* update 优化流程分类名称翻译回显
* fix 修改根据流程分类id查询
* update 新增流程分类id查询
* fix 修复抄送错误
* fix 修复错误判空
* fix 修复错误判空
* update 新增删除流程事件
* Merge remote-tracking branch 'origin/dev' into warm-flow-future
* Merge remote-tracking branch 'origin/dev' into warm-flow-future
* update 调整sql书写顺序
* fix 修复 抄送缺数据问题 与 已完成任务数据取错问题
* update 新增根据业务id查询流程实例详细信息
* update 调整变量参数
* update 调整分类接口
* update 统一业务id参数
* update 调整返回参数
* update 增加业务id通用查询条件
* update 优化代码 修复bug
* update 优化新增流程分类判断
* Merge remote-tracking branch 'origin/dev' into warm-flow-future
* update 调整驳回
* update 优化错误注释
* update 优化流程分类sql语句
* update 调整驳回
* update 删除流程分类状态
* update 增加流程定义防重
* [fix]
* update 优化代码 修复bug
* update 优化流程定义增加类别树查询
* update 新增翻译根据流程分类ID查询流程分类名称
* update 根据分页对象构建表格分页数据对象
* update 优化流程定义列表名称
* update 优化流程分类校验
* update 导入流程 支持自定义类别
* update 流程案例 增加表单路径
* update 流程定义查询返回表单路径
* update 优化任务业务层
* fix 修复 请假天数不准确问题
* update 优化驳回 撤销
* update 删除类别查询权限 通用查询不需要加权限
* fix 修复 变量名修改错误
* Merge branch 'warm-flow-future' of https://gitee.com/dromara/RuoYi-Vue…
* update 调整驳回
* update 表名类名统一命名
* Merge branch 'warm-flow-future' of https://gitee.com/dromara/RuoYi-Vue…
* update 挑战者驳回 撤销
* Merge remote-tracking branch 'origin/dev' into warm-flow-future
* fix 修复会签 票签撤销问题
* update 调整并行环节撤销错误 增加办理校验
* Merge branch 'warm-flow-future' of https://gitee.com/dromara/RuoYi-Vue…
* update 优化获取办理人
* update 优化 workflow模块增加doc依赖输出接口文档
* Revert "update 优化 删除无用方法"
* Merge remote-tracking branch 'origin/dev' into warm-flow-future
* Merge branch 'warm-flow-future' of https://gitee.com/dromara/RuoYi-Vue…
* update 调整代办人查询错误 增加示例
* update 优化 重构代码
* update 优化 重构代码
* update 优化 将工作流消息推送改为sse
* add 添加模型
* update 调整审批记录
* update 调整请假案例
* fix 修复流程定义查询错误
* update 调整流程实例查询错误
* update 调整获取当前登录任务实例
* add 添加消息发送
* update 调整办理监听
* [add]
* [fix]
* update 重构 将工作流查询逻辑封装为单独的service类
* [update]
* [add]
* 办理附件提交
* 申请人查询修改
* update 回退优化 删除无用方法
* update 优化任务办理人翻译实现
* update 优化任务分配人枚举
* update 优化 删除无用方法
* Merge remote-tracking branch 'origin/dev' into warm-flow-future
* Merge remote-tracking branch 'origin/dev' into warm-flow-future
* [add]
* update 优化全局任务办理监听
* update 删除无用引入
* Merge remote-tracking branch 'origin/dev' into warm-flow-future
* update 优化激活/挂起取反逻辑
* !614 style workflow xml 格式
* style workflow xml 格式
* !612 fix FlwInstanceMapper xml错误
* fix FlwInstanceMapper xml错误
* update 优化 后端代码
* update 优化接口请求路径
* update 调整已办排序
* add 添加任务作废
* update 调整任务办理操作
* update 调整加签,减签校验
* update 调整加签
* add 添加获取当前任务的办理人接口
* add 添加任务查询会签,票签比例
* update 调整任务,实例查询
* update 调整任务委托,转办,优化部分代码等
* update 调整流程定义视图
* add 添加任务,流程实例常用查询接口
* Merge branch 'dev' into warm-flow-future
* update 优化工作流常量使用
* update 优化流程记录运行时长获取
* update 优化任务管理控制层
* update 回复业务状态枚举
* update 优化业务状态枚举
* update 调整昵称翻译
* update 调整修改办理人接口
* update 调整流程全局监听,调整任务办理人批量修改,优化代码
* update 优化查询可驳回节点
* update 优化添加抄送人
* update 优化增加人员类型枚举,删除无用常量
* update 优化请假天数工具类,删除缓存,加锁处理,可以采用外部传参的形式处理redis部分
* update 优化请假天数计算
* update 优化任务完成时间处理
* update 优化任务办理人获取
* update 优化任务操作,委派、转办、加签、减签、修改办理人等
* add 新增warm-flow-all.sql
* del 删除多余SQL
* del 删除无用vo
* del 删除表单管理信息
* del 删除节点配置信息
* update 优化节点类型常量获取
* update 优化权限办理人获取
* update 优化权限办理人获取判断
* update 提交等待新版本待优化的开始监听信息
* update 新增获取部门负责人
* add 新增分派办理人监听器
* update 优化或者字符串用户ID
* update 用户前缀去掉
* update 调整流程实例状态查询
* add 添加流程撤销
* add 添加流程抄送
* add 新增办理人权限处理器
* update 升级warm-flow1.3.4
* Merge branch 'dev' into warm-flow-future
* update 调整流程定义复制
* update 调整流程启动设置启动人变量
* update 调整流程定义删除
* update 调整流程定义导入
* update 调整工作流任务,流程定义等查询
* update 调整工作流人员翻译查询
* update 调整转办,加签等参数
* update 调整流程办理设置办理人
* update 调整流程设计保存
* update 调整流程状态,移除过时方法,调整查询办理人
* update 补充工作流请求增加鉴权
* update 工作流请求增加鉴权
* update 新增流程完成监听器
* update 新增流程启动监听器
* Merge remote-tracking branch 'origin/dev' into warm-flow-future
* update 更新warm-flow版本
* update 优化workflow表sql格式
* update 优化,增加岗位权限判空处理
* update 当前用户所有权限岗位ID改为从token获取
* update 当前用户所有权限增加岗位ID权限
* update 更新通过岗位ID查询用户
* update 优化激活/挂起流程定义判断
* Revert "add 新增异常处理器和工作流封装包"
* add 新增异常处理器和工作流封装包
* update 优化待办任务查询以及任务流转
* update 优化工作流工具,避免多次获取请求
* fix 修改无效标识
* Merge remote-tracking branch 'origin/dev' into warm-flow-future
* update 查询部门并返回任务指派的列表根据部门树搜索
* Merge remote-tracking branch 'origin/dev' into warm-flow-future
* update 优化任务办理人分组
* update 优化任务办理人查询
* update 优化任务办理人查询
* update 优化任务办理人
* update 完善任务办理人
* add 新增角色办理人
* !596 优化工作流代码逻辑
* update 优化工作流工具冗余代码
* update 优化工作流代码逻辑
* update 调整办理 驳回 终止等状态
* update 调整流程定义查询
* 解决冲突提交 warmflow代码
* update 升级warm-flow到1.3.0 调整流程办理 ,驳回,终止等 添加自定义监听
* Merge remote-tracking branch 'origin/dev' into warm-flow-future
* update 调整流程xml查询
* update 调整驳回
* update 升级warm-flow
* update 调整任务办理设置办理人
* 调整转办,委托参数
* update warm-flow 1.2.4 => 1.2.7
* Merge remote-tracking branch 'origin/dev' into warm-flow-future
* update 调整抄送错误
* 添加已办,未办
* Merge branch 'warm-flow-future' of https://gitee.com/dromara/RuoYi-Vue…
* add 添加我发起的单据接口
* Merge remote-tracking branch 'origin/dev' into warm-flow-future
* update 调整流程实例,待办查询
* remove 删除无用校验
* add 添加待办人查询
* Merge branch 'dev' into warm-flow-future
* update 调整字段错误,流程导入
* add 添加流程激活/挂起接口 升级warm-flow到1.2.4
* add 添加历史流程定义查询 调整流程发布
* Merge remote-tracking branch 'origin/dev' into warm-flow-future
* Merge remote-tracking branch 'origin/dev' into warm-flow-future
* Merge remote-tracking branch 'origin/dev' into warm-flow-future
* Merge remote-tracking branch 'origin/dev' into warm-flow-future
* update 优化 TenantSpringCacheManager 处理逻辑
* fix 修复 一级缓存key未区分租户问题
* update 优化 角色权限判断
* update 更新 readme
* update 优化 增加删除标志位常量优化查询代码
* fix 修复 登出无法正确删除对应的租户数据问题
* update 优化 sse 拦截网络中断io异常
* fix 修复 登录错误锁定不区分租户问题
* update 优化 sse 关闭连接各种异常问题
* update 优化 监控使用独立web依赖
* fix 修复 代码生成 错误匹配表名问题
* update 优化 适配 anyline 新改动
* update anyline 8.7.2-20240728
* update 脱敏策略优化增加密码
* add 新增 更多脱敏策略
* update 优化oss查询代码
* update 优化 sse发送消息 增加token有效期判断
* fix 修复 登出后重新登录 sse推送报错问题
* fix 修复 代码生成 数据源切换问题
* update anyline 8.7.2-20240726
* fix 修复 代码生成 表结构缓存问题
* update snailjob 1.1.0 => 1.1.1
* fix 修复 代码生成 表结构缓存问题
* update 优化 sse 自动装配
* 初始化添加warm-flow
2025-01-13 06:08:15 +00:00
e36e8f7758 fix 修复 satoken dao层获取timeout为秒导致丢失毫秒进度问题(临时修复 等satoken官方解决) 2025-01-13 13:36:12 +08:00
b47798ef19 update 优化 无需多余set变量 2025-01-12 20:36:22 +08:00
55b1a67637 update 优化 避免重复处理 2025-01-12 20:36:21 +08:00
d2b9cd2797 update 优化 数据权限 判断当前注解不满足模板则跳过 2025-01-09 14:27:52 +08:00
9a95c46578 update 优化 将servlet改为saholder 兼容其他场景 2025-01-08 15:50:38 +08:00
a58f72868a update 优化 工具类封装 2025-01-08 14:47:57 +08:00
820db87604 update 优化 使用request存储动态租户 避免单请求多次查询redis获取 2025-01-08 10:57:14 +08:00
254e61ab01 fix 修复 postgresql的表元数据没有创建时间这个东西(好奇葩) 只能new Date代替 2025-01-07 13:52:29 +08:00
ad53965626 !634 docs: 修改应用配置文件中的注释错别字
* docs: 修改应用配置文件中的注释错别字
2025-01-07 03:59:34 +00:00
4de9fa33b7 update 优化修改部门信息增加事务 2025-01-07 05:23:27 +08:00
08e40b611b fix 修复 数据权限 多角色多注解包含忽略权限标识符逻辑不正确问题 2025-01-05 23:00:56 +08:00
dap
8bd2e27653 update: 增加菜单选择拓展参数 2025-01-04 16:08:57 +08:00
d023510f7e !632 update snailjob 1.2.0 => 1.3.0-beta1
* update snailjob 1.2.0 => 1.3.0-beta1
2025-01-03 07:13:22 +00:00
ec5ca0a08f fix 修复 redisson发号器未初始化发号器步长导致过期时间未生效的问题
Signed-off-by: 秋辞未寒 <545073804@qq.com>
2025-01-03 01:44:39 +00:00
a46c798e01 update 优化 延迟线程池 支持虚拟线程 2025-01-02 15:42:24 +08:00
41a3bdf73d !629 update 优化jdk21环境开启虚拟线程时的定时任务池
* update 优化jdk21环境开启虚拟线程时的定时任务池
2025-01-02 07:34:40 +00:00
a7b83672ba update 优化 sse 如果获取token列表为空 删除userid对应的存储 2024-12-27 11:17:09 +08:00
c2746c2392 update 优化 数据权限处理器 增加默认值处理 针对于表达式变量与注解不对应或者表达式变量为null的情况 2024-12-26 13:26:18 +08:00
492e7dab26 fix 修复 未开启sse 找不到bean问题 2024-12-25 09:30:14 +08:00
5480e419b6 update sqlkeyword 2024-12-25 09:23:13 +08:00
251a617ecc !626 fix 修复excel模板导出异常
* fix: 修复excel模板导出异常
2024-12-24 03:04:27 +00:00
36de389fa4 update 增加 赞助商 2024-12-21 13:38:24 +08:00
f4f052deb4 update 增加 赞助商 2024-12-21 13:32:19 +08:00
3a0fbd45ae update 增加 赞助商 2024-12-21 13:30:19 +08:00
f20c271972 !622 fix 修正注释错别字和顺序
* fix 修正注释错别字和顺序
2024-12-19 16:26:26 +00:00
ad85fa2016 update 优化 sse工具类 2024-12-19 10:54:08 +08:00
1a403361c9 update 优化 关闭sse后 使用工具报错 2024-12-19 09:19:12 +08:00
9ad64521d3 update 优化 !pr_610 代码实现 2024-12-18 17:46:53 +08:00
ff76df9ae0 增加导出模板必填、备注
增加单独的导出模板 方法
2024-12-18 17:27:16 +08:00
da1cd55c1d update 优化 增加mybatis-plus一键开启/关闭逻辑删除功能 2024-12-18 13:30:09 +08:00
a65baf5d67 update 优化 增加mybatis-plus一键开启/关闭逻辑删除功能 2024-12-18 11:32:22 +08:00
489cb52976 !620 update 修改日志颜色
* update 修改日志颜色
* update 修改日志颜色
2024-12-17 07:34:15 +00:00
591f86e4e9 !619 fix 修复数据权限导致的个人中心的修改头像和修改密码接口错误
Merge pull request !619 from QianRj/dev
2024-12-17 05:22:15 +00:00
9768023d38 fix: 修复数据权限导致的个人中心的修改头像和修改密码接口错误 2024-12-17 12:35:19 +08:00
60af92ed2d !618 update 适配 TOPAIM 2.0 单点登录
Merge pull request !618 from 马铃薯头/dev
2024-12-16 11:27:49 +00:00
4d566071db Merge remote-tracking branch 'origin/dev' into dev 2024-12-16 19:26:37 +08:00
aface5ded1 update 适配 TOPIAM 2.0 单点登录 2024-12-16 18:53:58 +08:00
7f6b71d938 fix 修复 升级justauth底层方法修改导致代码编译问题 2024-12-15 16:24:52 +08:00
c8ed71d010 update 使用justauth扩展功能支持微信小程序登录 2024-12-14 23:06:09 +08:00
df6649907d update justauth 1.16.6 => 1.16.7 支持多种登录方式 不限于三方登录 2024-12-14 23:05:15 +08:00
2fb1c99f56 !616 fix: 修复部门数据权限缓存错误
Merge pull request !616 from QianRj/dev
2024-12-14 14:30:14 +00:00
fcebda8987 fix: 修复数据权限缓存错误 2024-12-13 20:37:55 +08:00
0185a468bd update 优化 删除无用方法 2024-12-13 00:15:49 +08:00
c566f2ae28 update 优化 调整方法位置 2024-12-12 23:38:16 +08:00
29c5ff89ba update 优化 重构DateUtils工具类 更加实用 2024-12-12 23:33:00 +08:00
ab3e4978b1 update 优化 为部门角色岗位用户增加一些常用查询方法 2024-12-12 23:32:33 +08:00
0027f671d2 update 优化 登录用户增加岗位数据 2024-12-12 23:31:26 +08:00
06a8ab0ab2 !608 add 新增 基于Redisson的发号器工具
Merge pull request !608 from 秋辞未寒/dev
2024-12-12 14:28:25 +00:00
4352b3fe4a update 回退一些不应该被优化的代码 2024-12-12 22:20:32 +08:00
dd17246086 !613 refactor 优化代码
* update 优化代码
2024-12-12 14:15:59 +00:00
d7b0dc91d5 update 优化 去除部门查询状态校验 改为前端过滤 便于查看禁用部门下的其他数据 2024-12-11 18:12:31 +08:00
4598d4d843 update 优化 部门树增加禁用标志位 2024-12-11 15:31:17 +08:00
8a731efe0d update 优化 workflow 模块增加接口文档生成功能 2024-12-11 14:01:41 +08:00
59fd7eeeb3 add 新增 基于Redisson的发号器工具 2024-12-10 09:55:11 +00:00
552e543471 update 优化 pr代码 2024-12-10 17:46:45 +08:00
b1badca062 !607 add 新增 validation支持枚举校验
Merge pull request !607 from 秋辞未寒/dev
2024-12-10 09:34:50 +00:00
9f34edee4f reset 回滚错误提交 2024-12-10 12:41:59 +08:00
d257de7882 update 兼容云厂商自定义域名 2024-12-10 11:47:13 +08:00
4821902fdc update 更新 枚举校验通过枚举字段的getter方法取值
Signed-off-by: 秋辞未寒 <545073804@qq.com>
2024-12-09 10:16:49 +00:00
a1f82a7d08 删除文件 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/enumd/ValidateEnum.java 2024-12-09 09:43:01 +00:00
9009a90ef2 update 更新 枚举校验通过获取枚举值,不再需要实现接口
Signed-off-by: 秋辞未寒 <545073804@qq.com>
2024-12-09 09:41:52 +00:00
fc9c90ad99 fix 修复 三方缺失参数问题 2024-12-09 17:35:53 +08:00
9e613488f1 add 新增 validation支持枚举校验
Signed-off-by: 秋辞未寒 <545073804@qq.com>
2024-12-09 09:03:31 +00:00
5e6cb0dd3c update 优化 代码生成 增加buildQueryWrapper默认排序规则 2024-12-09 13:31:17 +08:00
2bb787886d update 优化 代码生成 增加buildQueryWrapper默认排序规则 2024-12-09 12:42:36 +08:00
ed6f8262c6 update 优化 代码生成 创建更新时间被覆盖问题 2024-12-09 12:39:27 +08:00
64d574cf06 update 优化代码生成排序问题 2024-12-09 11:20:05 +08:00
e0b0ffcb28 update 优化 书写错误 2024-12-06 14:02:46 +08:00
c17225abb1 update 优化 在线用户查询 优先查询租户下数据 减少数据量 2024-12-06 10:11:38 +08:00
19aed0a1e4 update 优化 项目名前增加社区名 2024-12-04 10:11:41 +08:00
14c1bde958 update 优化 租户域名使用忽略大小写匹配 2024-12-03 11:01:01 +08:00
eed929b9fe fix 修复 代码生成 表名中间带有特殊字符被过滤问题 改为开头过滤 2024-12-02 23:24:30 +08:00
eda67dd572 update 优化 代码生成器 将数据库字段默认转为小写 避免某些数据库大写出现的问题 2024-12-02 10:05:25 +08:00
fef2d5207b update 优化 由于sse重试机制导致经常输出认证失败日志过多 将sse失败改为debug 2024-12-02 09:36:46 +08:00
87294b41af update 优化 有界队列销毁方式 应该使用特殊销毁方法 2024-11-29 16:26:56 +08:00
99d9c516fc update 优化 销毁策略 2024-11-29 16:21:19 +08:00
476c7a77c8 update 优化 redis序列化 支持更快的apache二进制跨语言序列化方案 2024-11-27 18:23:10 +08:00
8dc4b5cf6b update 优化 租户日志模块名 2024-11-27 15:22:05 +08:00
eba6b48daf update springdoc 2.6.0 => 2.7.0
update redisson 3.37.0 => 3.39.0
2024-11-26 16:29:26 +08:00
1aff46bc1c fix 修复 字段长度超出数据库限制问题 2024-11-26 15:38:22 +08:00
e78684886d update 优化 数据权限处理 2024-11-25 11:58:39 +08:00
7f35925794 update 优化 增加默认数据权限 "部门及以下或本人数据权限" 选项 2024-11-25 10:33:42 +08:00
71a59d3e5f fix 修复 过滤器正则错误
Signed-off-by: 疯狂的狮子Li <15040126243@163.com>
2024-11-23 07:59:40 +00:00
4af46a6045 update 优化 校验租户判空逻辑
Signed-off-by: 秋辞未寒 <545073804@qq.com>
2024-11-23 06:57:11 +00:00
d194b39e57 fix 修复 monitor 设置 context-path 导致退出重新登录404问题 2024-11-22 17:54:16 +08:00
cd08f66c59 update 优化 代码生成器 pg数据库 主键获取不精确问题 2024-11-22 17:18:27 +08:00
ef919b9f3d update 优化 代码生成器类型获取 2024-11-22 17:01:05 +08:00
847b158283 update 优化 个人中心强退设备接口路径 2024-11-22 15:59:40 +08:00
dd2abd95c9 fix 修复 aop 无法拦截mapper接口上的注解导致的问题 类上依旧使用扫描处理 2024-11-22 10:43:44 +08:00
ea50a57602 update 优化 xss包装器 Parameter 处理 兼容某些容器不允许改参数的情况 2024-11-21 10:17:34 +08:00
b5908d52d7 !600 update 升级SnailJob版本到1.2.0
Merge pull request !600 from dhb52/dev
2024-11-20 05:30:10 +00:00
bf515042d0 update 升级SnailJob版本到1.2.0 2024-11-20 12:44:22 +08:00
e94fccc784 update 优化 Dockerfile 消除warn警告 2024-11-20 11:03:55 +08:00
6d45199592 update 修改 readme 增加成员项目说明 2024-11-15 16:28:33 +08:00
57b3b329a8 docs 补充客户端工具类注释 2024-11-15 15:17:36 +08:00
a0fc268bb9 docs 补充Undertow自定义配置信息注释 2024-11-15 14:51:34 +08:00
631739733f Revert "update 优化 加密拦截器plugin方法按规定返回代理对象"
This reverts commit f6d9bec16c.
2024-11-15 11:11:42 +08:00
2d2bd48963 update 优化 拦截爬虫跟踪等垃圾请求 2024-11-15 11:03:20 +08:00
f6d9bec16c update 优化 加密拦截器plugin方法按规定返回代理对象 2024-11-15 10:30:06 +08:00
0f1118e03a docs 修正EncryptUtils加解密错误注释 2024-11-15 09:14:52 +08:00
009ac75229 fix 修复 数据权限多角色与权限标识符共用导致的问题 https://gitee.com/dromara/RuoYi-Vue-Plus/issues/IB4CS4 2024-11-14 16:57:10 +08:00
b64ac8d7f6 update 优化 将Log记录异常长度改为5000 2024-11-14 16:23:52 +08:00
41240fc415 update 优化 将Log记录参数长度扩充为5000更符合实际需求 2024-11-14 16:21:56 +08:00
a18e430056 update 优化部署配置 2024-11-14 15:56:16 +08:00
1a993a7899 update 优化 xss包装器 Parameter 处理 兼容某些容器不允许改参数的情况 2024-11-12 18:17:47 +08:00
d3b5220dc3 update 排除 websocket包内包含的tomcat依赖 2024-11-12 15:10:04 +08:00
4f92c0317e update 优化 删除无意义的方法 2024-11-12 10:50:10 +08:00
a098565c37 fix 修复 误删代码 2024-11-12 10:48:28 +08:00
0d5fe5d91e update 优化 支持脱敏传多角色多权限标识 2024-11-12 10:39:41 +08:00
5c66f3b90c update 优化 支持脱敏传多角色多权限标识 2024-11-12 10:37:51 +08:00
63d22b9b33 update 优化 角色删除清理缓存 2024-11-11 13:58:29 +08:00
b4678b74ab update 优化 使用ObjectUtils新增方法封装代码 2024-11-11 13:52:16 +08:00
a82ed1e9dd update 优化 数据权限查询增加缓存 2024-11-11 13:30:58 +08:00
d2ffbfb80b !599 add 新增 对象工具类
Merge pull request !599 from 秋辞未寒/dev
2024-11-11 03:48:03 +00:00
5d61782a6c add 新增 对象工具类
Signed-off-by: 秋辞未寒 <545073804@qq.com>
2024-11-11 03:40:48 +00:00
1b8c9fdaa9 update 优化 代码生成器数字类别判断 2024-11-11 10:45:00 +08:00
90328ae79b update 优化 逻辑删除状态改为1 避免误解 2024-11-11 10:26:34 +08:00
9cdcbbccbf update 重构 将UserConstants改为SystemConstants 统一常量使用 降低使用难度避免误解 2024-11-11 10:26:34 +08:00
b820a98c6c docs:补充Mapper层注释 2024-11-11 10:05:52 +08:00
8e749c472a fix 修复 PageQuery 转json报错问题 2024-11-09 21:01:40 +08:00
70a5077291 fix 修复 sse 关闭接口无法断连问题 2024-11-08 00:30:43 +08:00
8a5d8cc9b9 add 增加 邮件多附件demo 2024-11-07 22:26:40 +08:00
0a0a16f969 update 优化 封装部门基于父id查询方法 2024-11-07 15:48:34 +08:00
73d5fbd085 fix 修复 PlusSmsDao#clean 方法书写错误 2024-11-06 22:14:24 +08:00
9da3d25292 !595 修复大数量级联下拉导致的禁止写入错误
Merge pull request !595 from Emil.Zhang/dev
2024-11-06 07:40:32 +00:00
0385a444c2 fixed(级联下拉框数据错误)
1.修复过多级联下拉选项导致的无法写入问题
2024-11-06 15:01:38 +08:00
7d856b030b update 优化 不传用户id不校验数据权限 2024-11-05 16:52:10 +08:00
8c0441b91a !594 update 优化代码
Merge pull request !594 from DoubleH/dev
2024-11-04 09:53:56 +00:00
cxh
baef55ae1b update 优化代码
1. 删除内部if对部门不为null的多余判断,可以提前判断
2. 使用内部工具类StreamUtils
2024-11-04 17:42:37 +08:00
80e6943d2e add 增加 gitcode 链接地址 2024-11-04 14:08:46 +08:00
2ffdd56301 update 优化 部门树多基点展示问题 支持相同名称节点并排展示 2024-11-04 11:38:55 +08:00
53635da552 fix 修复 某些模块不存在 mp 依赖导致方法报错问题 2024-11-02 20:02:08 +08:00
5e7fb88762 update 优化 去除OSS桶检测 桶不存在自然会报错无需额外检测 2024-10-31 17:59:56 +08:00
d89727725b update 优化 删除无用注释 2024-10-31 15:55:28 +08:00
8cd7e3c924 update 优化 限流注解增加固定清理时间 2024-10-30 16:43:30 +08:00
0baf2c5861 fix 修复 新版本mp默认使用最新 sqlserver 语法导致代码生成分页报错问题 2024-10-30 10:51:45 +08:00
b0548f9a56 update 优化 sys_social表 租户id增加默认值 2024-10-29 20:21:58 +08:00
2d190cfb19 update 优化 jackson 过期方法 2024-10-29 19:12:19 +08:00
3b5858b114 update mybatis-plus 3.5.8 => 3.5.9 2024-10-29 19:12:07 +08:00
112157ade0 update 优化 多租户插件初始化流程 2024-10-29 16:55:41 +08:00
8cd30ae86b update 优化 多租户插件初始化流程 2024-10-29 16:42:53 +08:00
9b6b288e73 update 优化 多租户插件初始化流程 2024-10-29 16:42:04 +08:00
1757e5519d update 优化 去除GenUtils设置createby逻辑 统一走自动注入设置 2024-10-29 11:19:08 +08:00
a21fa666fd update springboot 3.2.11 => 3.3.5
update springboot-admin 3.2.3 => 3.3.4
2024-10-29 09:39:45 +08:00
df9a57c379 !592 替换RedisUtils中的过时方法
* update:替换RedisUtils中的废弃方法getKeysStreamByPattern及trySetRate
2024-10-29 01:29:15 +00:00
5f31efd33e update 优化 删除桶自动创建代码逻辑(云厂商限制不允许操作桶) 2024-10-28 18:10:49 +08:00
2a9f245b39 update 优化 删除桶自动创建代码逻辑(云厂商限制不允许操作桶) 2024-10-28 18:09:32 +08:00
7e14b98676 reset 回滚错误修改
Signed-off-by: 疯狂的狮子Li <15040126243@163.com>
2024-10-28 09:46:28 +00:00
761586cc3c reset 回滚错误修改 2024-10-28 17:44:21 +08:00
6731b7947b update 优化 角色清理在线用户代码逻辑 2024-10-28 10:21:55 +08:00
ddc8bd1139 [重大更新] update (实验性功能慎更)重构数据权限实现逻辑 支持任意mapper方法标注注解 无需再找真实mapper标注 2024-10-27 23:25:22 +08:00
6ae9bbdb31 fix 修复 注册日志记录状态错误 2024-10-25 11:30:56 +08:00
015b406001 !591 发布 5.2.3 正式版
Merge pull request !591 from 疯狂的狮子Li/dev
2024-10-25 03:09:23 +00:00
6c950c9569 🎀发布 5.2.3 正式版 2024-10-25 10:58:01 +08:00
3ce3ffca05 update springboot 3.2.10 => 3.2.11
update redisson 3.36.0 => 3.37.0
update mapstruct-plus 1.4.4 => 1.4.5
update anyline 8.7.2-20240930 => 8.7.2-20241022
2024-10-25 10:32:52 +08:00
d3ccc43d68 update 优化 postgres适配findInSet写法 提高查询效率 2024-10-24 14:47:58 +08:00
4c96440686 fix 修复 设置流程变量 代码使用错误问题 2024-10-23 17:38:21 +08:00
8d8d76364b fix 修复 xss过滤器 未过滤url参数问题 2024-10-21 13:59:39 +08:00
82af98c205 fix 修复 代码书写错误 2024-10-21 13:38:09 +08:00
fdbe8c2395 update 优化 过滤器初始化写法 2024-10-20 11:43:39 +08:00
59715b1e02 fix 修复 及其特殊场景下获取 StopWatch 为null问题 2024-10-20 00:23:33 +08:00
f2ec530065 fix 修复 字典同步 数据异常问题 2024-10-19 14:50:58 +08:00
ac89cb46f5 fix 修复重新生成租户ID未生效的问题 2024-10-17 22:38:12 +08:00
abfa995a32 update 优化 监听器兼容所有demo案例 2024-10-17 18:12:26 +08:00
51cfbef887 update 优化 操作日志记录DELETE请求参数 2024-10-17 17:34:33 +08:00
7171ed1508 update 优化 snailjob客户端ip配置说明 2024-10-17 17:25:02 +08:00
6727f3e6a4 fix 修复oss上传10秒超时,设置默认时间一分钟 2024-10-16 15:28:38 +08:00
81006c758a docs 自行开启云存储访问控制ACl策略注释 2024-10-16 10:47:26 +08:00
01025c4024 update 更新AWS SDK版本 2024-10-16 10:39:58 +08:00
517277132e fix 修复腾讯云oss不支持高危权限设置ACL 2024-10-16 10:36:43 +08:00
dc5c92ee2d fix 修复同步云厂商要求明确配置访问样式(路径样式访问) 2024-10-16 10:33:11 +08:00
57e142e160 fix 修复 特性情况下自定义验证异常处理器报null问题 2024-10-15 17:35:51 +08:00
c563afed59 fix 修复 EncryptorManager 缓存失效问题导致的内存膨胀 2024-10-14 00:38:07 +08:00
556cf87f0e update 优化 补全 pg 数据类型 2024-10-12 16:23:17 +08:00
e0a00cfe98 update 优化 补全 @Override 注解 2024-10-08 16:30:04 +08:00
d60774a7b8 reset 回滚错误提交 2024-10-08 16:10:27 +08:00
e3d40b75cb fix 修复 同一个用户不同token连接不同服务导致发送不到问题(改为全局发送) 2024-10-08 14:43:59 +08:00
eb4479e940 fix 修复 同步字典存储是未忽略租户 2024-10-01 10:52:07 +08:00
7e6d0a5c64 update anyline 20240808 => 20240930 2024-09-30 17:56:04 +08:00
88dd4165cb update sms4j 3.3.2 => 3.3.3 2024-09-29 17:41:36 +08:00
e96118c574 fix 修复 部分web异常被CryptoFilter截胡问题 2024-09-29 17:41:16 +08:00
39c4e5fe55 !589 fix postgres数据库脚本租户管理名称错误
Merge pull request !589 from LionZzzi/fix
2024-09-25 02:14:52 +00:00
46141dc114 fix(postgres数据库脚本): 变更租户管理目录的主菜单为租户管理 2024-09-25 10:00:33 +08:00
e3a25f2425 fix 修复 代码生成器 postgres 数据库主键类型映射错误问题 2024-09-24 18:17:49 +08:00
64289c16f3 update 优化 适配mp新版本 方法名改动 2024-09-20 15:43:56 +08:00
6b05ddb576 update 优化 redis操作 如果无法忽略租户id则全局处理 2024-09-19 23:13:00 +08:00
88ee252fce update springboot 3.2.9 => 3.2.10 2024-09-19 18:09:03 +08:00
2add7291ab update 优化 sse 异常单独处理 避免出现异常报错问题 2024-09-19 17:53:41 +08:00
501be029c6 update 优化 sse 异常单独处理 避免出现异常报错问题 2024-09-19 17:40:42 +08:00
d86652dee1 update easyexcel 4.0.2 => 4.0.3
update redisson 3.34.1 => 3.36.0
2024-09-19 13:55:46 +08:00
272ca613ee update mybatis-plus 3.5.7 => 3.5.8 代码适配更改 2024-09-19 13:45:57 +08:00
74af811a3b update 优化 删除掉有问题的方法(使用RedisUtils) 2024-09-18 18:07:28 +08:00
fc72b67090 update 优化 全局开启xss过滤 提高安全性 与cloud版本保持一致 2024-09-13 18:02:44 +08:00
e33f76d710 update 优化 去除返回前端的异常信息里包含html标签问题 2024-09-13 17:42:21 +08:00
1c3d594947 docs 查询表名列表增加注释 2024-09-13 15:00:16 +08:00
64d9b27310 update 优化 判断当前会话是否已经登录 2024-09-13 14:41:01 +08:00
f3f3593cfe update 优化 删除不应该set的属性 2024-09-13 09:38:56 +08:00
e5e8e3ce7c update 优化 租户状态更改接口严谨性 2024-09-12 16:58:47 +08:00
a7fd7ba72c fix 修复 临时处理 scala库版本漏洞问题 2024-09-11 16:00:39 +08:00
020f090f4a update 优化 删除okhttp无用版本限制(spring已经限制过了) 2024-09-11 15:59:54 +08:00
5e3231d59b !582 优化admin配置文件读取
* update 优化admin监控配置读取名称
* update 优化admin监控配置读取
2024-09-11 04:02:16 +00:00
b522bc015d update 优化 操作日志查询代码 2024-09-11 10:23:16 +08:00
6e64fd7fd7 !585 修复工作流分页查询不兼容sql server的问题
Merge pull request !585 from 赖小麦/fix_workflow_page_query_error_to_sqlserver
2024-09-11 01:27:20 +00:00
8b44f5cdbc 修复:工作流中(我的待办、我的已办、我的抄送默认)的分页查询语句不兼容sqlserver的问题 2024-09-10 12:05:08 +08:00
f9b7d955aa add 新增TreeUtil获取节点列表中所有节点的叶子节点 2024-09-10 10:15:01 +08:00
6ea2a2fc51 update 优化 统一sql文件命名方式 2024-09-09 11:37:02 +08:00
760c8d7200 update 优化 提供生产环境默认组配置 2024-09-09 11:30:40 +08:00
32ad28c3dc update 优化通过角色ID查询用户逻辑 2024-09-06 14:24:06 +08:00
6886e9fd5b update 优化查询用户时多余重复判断以及去重 2024-09-06 14:14:15 +08:00
f20130d3db fix 修复 commons-io 依赖冲突问题 2024-09-05 18:50:28 +08:00
df9cc881f1 update 优化 连接SSE token过期导致的 Servlet异常 2024-09-05 18:50:00 +08:00
4044988afa update 优化 代码生成菜单id匹配写法 2024-09-04 16:16:53 +08:00
d3360e81b9 update sa-token 1.38.0 => 1.39.0 2024-09-03 13:52:50 +08:00
df070b7d78 update 调整 xml 格式 2024-09-02 14:16:40 +08:00
83dd98faf3 fix 修复 开启子部门 父部门未关联开启问题 2024-09-02 14:12:02 +08:00
37f89f560f update 优化 更新sql关键字 2024-09-02 13:06:31 +08:00
918ed0d6d0 update 优化 删除多余的引号 2024-08-31 23:30:50 +08:00
a3c9edde78 fix 修复 升级依赖导致的依赖冲突 2024-08-29 20:53:39 +08:00
cac0a4cd16 update 优化 RegexUtils#extractFromString 方法未匹配返回null不返回默认值问题 2024-08-29 14:12:07 +08:00
581b6e03d5 update 优化 oss上传直接从请求头获取文件类型 2024-08-29 10:49:41 +08:00
801cc584e5 update 优化 代码生成表名判断 使用开头判断避免误判 2024-08-29 10:41:45 +08:00
b82ff8617e add 增加 同步租户字典功能 2024-08-26 17:56:19 +08:00
c87016c1af update 优化 excel导入 适配异常结构 2024-08-26 15:38:48 +08:00
379 changed files with 12252 additions and 12821 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.2.2" />
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.3.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
</settings>

View File

@ -1,12 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="演示机">
<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.2.2" />
<option name="imageTag" value="ruoyi/ruoyi-server:5.3.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>
</component>

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.2.2" />
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.3.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
</settings>

View File

@ -6,22 +6,24 @@
[![码云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)
[![使用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.2.2-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.2-blue.svg)]()
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.3.0-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)]()
> RuoYi-Vue-Plus 是重写 RuoYi-Vue 针对 `分布式集群与多租户` 场景全方位升级(不兼容原框架)
> Dromara RuoYi-Vue-Plus 是重写 RuoYi-Vue 针对 `分布式集群与多租户` 场景全方位升级(不兼容原框架)
> 项目代码、文档 均开源免费可商用 遵循开源协议在项目中保留开源协议文件即可<br>
活到老写到老 为兴趣而开源 为学习而开源 为让大家真正可以学到技术而开源
> 系统演示: [传送门](https://plus-doc.dromara.org/#/common/demo_system)
> 前端项目地址: [plus-ui](https://gitee.com/JavaLionLi/plus-ui)
> 官方前端项目地址: [plus-ui](https://gitee.com/JavaLionLi/plus-ui)<br>
> 成员前端项目地址: 基于vben5 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)
> 文档地址: [plus-doc](https://plus-doc.dromara.org)
@ -31,6 +33,7 @@ MaxKey 业界领先单点登录产品 - https://gitee.com/dromara/MaxKey <br>
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)
# 本框架与RuoYi的功能差异
@ -165,8 +168,8 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
| ![输入图片说明](https://foruda.gitee.com/images/1680078779416197879/75e3ed02_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078802329118061/77e10915_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078893627848351/34a1c342_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078928175016986/f126ec4a_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078941718318363/b68a0f72_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078963175518631/3bb769a1_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078982294090567/b31c343d_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079000642440444/77ca82a9_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680079020995074177/03b7d52e_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079039367822173/76811806_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1735829153637063344/3c21fd4c_1419627.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1735829181303499815/4522cefa_1419627.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1735829377205259767/76a705d7_1419627.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1722959592856812900/e2d0d342_1419627.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680079274333484664/4dfdc7c0_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079290467458224/d6715fcf_1766278.png "屏幕截图") |

98
pom.xml
View File

@ -10,54 +10,52 @@
<name>RuoYi-Vue-Plus</name>
<url>https://gitee.com/dromara/RuoYi-Vue-Plus</url>
<description>RuoYi-Vue-Plus多租户管理系统</description>
<description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
<properties>
<revision>5.2.2</revision>
<spring-boot.version>3.2.9</spring-boot.version>
<revision>5.3.0</revision>
<spring-boot.version>3.4.1</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.6.0</springdoc.version>
<springdoc.version>2.8.3</springdoc.version>
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
<easyexcel.version>4.0.2</easyexcel.version>
<easyexcel.version>4.0.3</easyexcel.version>
<velocity.version>2.3</velocity.version>
<satoken.version>1.38.0</satoken.version>
<mybatis-plus.version>3.5.7</mybatis-plus.version>
<satoken.version>1.39.0</satoken.version>
<mybatis-plus.version>3.5.10</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.8.31</hutool.version>
<okhttp.version>4.10.0</okhttp.version>
<spring-boot-admin.version>3.2.3</spring-boot-admin.version>
<redisson.version>3.34.1</redisson.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>
<lock4j.version>2.2.7</lock4j.version>
<dynamic-ds.version>4.3.1</dynamic-ds.version>
<snailjob.version>1.1.2</snailjob.version>
<mapstruct-plus.version>1.4.4</mapstruct-plus.version>
<snailjob.version>1.3.0</snailjob.version>
<mapstruct-plus.version>1.4.6</mapstruct-plus.version>
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
<lombok.version>1.18.34</lombok.version>
<lombok.version>1.18.36</lombok.version>
<bouncycastle.version>1.76</bouncycastle.version>
<justauth.version>1.16.6</justauth.version>
<justauth.version>1.16.7</justauth.version>
<!-- 离线IP地址定位库 -->
<ip2region.version>2.7.0</ip2region.version>
<undertow.version>2.3.15.Final</undertow.version>
<!-- OSS 配置 -->
<aws.sdk.version>2.25.15</aws.sdk.version>
<aws.crt.version>0.29.13</aws.crt.version>
<aws.sdk.version>2.28.22</aws.sdk.version>
<aws.crt.version>0.31.3</aws.crt.version>
<!-- SMS 配置 -->
<sms4j.version>3.3.2</sms4j.version>
<sms4j.version>3.3.3</sms4j.version>
<!-- 限制框架中的fastjson版本 -->
<fastjson.version>1.2.83</fastjson.version>
<!-- 面向运行时的D-ORM依赖 -->
<anyline.version>8.7.2-20240808</anyline.version>
<anyline.version>8.7.2-20250101</anyline.version>
<!--工作流配置-->
<flowable.version>7.0.1</flowable.version>
<warm-flow.version>1.6.6</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.verison>3.11.0</maven-compiler-plugin.verison>
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
<maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
<flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
</properties>
@ -69,6 +67,8 @@
<!-- 环境标识,需要与配置文件的名称相对应 -->
<profiles.active>local</profiles.active>
<logging.level>info</logging.level>
<monitor.username>ruoyi</monitor.username>
<monitor.password>123456</monitor.password>
</properties>
</profile>
<profile>
@ -77,6 +77,8 @@
<!-- 环境标识,需要与配置文件的名称相对应 -->
<profiles.active>dev</profiles.active>
<logging.level>info</logging.level>
<monitor.username>ruoyi</monitor.username>
<monitor.password>123456</monitor.password>
</properties>
<activation>
<!-- 默认环境 -->
@ -88,6 +90,8 @@
<properties>
<profiles.active>prod</profiles.active>
<logging.level>warn</logging.level>
<monitor.username>ruoyi</monitor.username>
<monitor.password>123456</monitor.password>
</properties>
</profile>
</profiles>
@ -114,12 +118,16 @@
<scope>import</scope>
</dependency>
<!-- Warm-Flow国产工作流引擎, 在线文档http://warm-flow.cn/ -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-bom</artifactId>
<version>${flowable.version}</version>
<type>pom</type>
<scope>import</scope>
<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 的依赖配置-->
@ -212,6 +220,12 @@
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-annotation</artifactId>
@ -225,12 +239,6 @@
<version>${p6spy.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<!-- AWS SDK for Java 2.x -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
@ -313,25 +321,9 @@
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-core</artifactId>
<version>${undertow.version}</version>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>
<version>${undertow.version}</version>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-websockets-jsr</artifactId>
<version>${undertow.version}</version>
</dependency>
<dependency>
<artifactId>commons-compress</artifactId>
<groupId>org.apache.commons</groupId>
<version>1.26.2</version>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.15.0</version>
</dependency>
<dependency>
@ -387,7 +379,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.verison}</version>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>

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.3-cds
#FROM bellsoft/liberica-openjdk-debian:21.0.5-cds
#FROM findepi/graalvm:java17-native
LABEL maintainer="Lion Li"
@ -16,6 +16,10 @@ ENV SERVER_PORT=8080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
EXPOSE ${SERVER_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} \
# 应用名称 如果想区分集群节点监控 改成不同的名称即可

View File

@ -2,6 +2,7 @@ package org.dromara.web.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
@ -12,7 +13,7 @@ import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.LoginBody;
import org.dromara.common.core.domain.model.RegisterBody;
@ -92,7 +93,7 @@ public class AuthController {
if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) {
log.info("客户端id: {} 认证类型:{} 异常!.", clientId, grantType);
return R.fail(MessageUtils.message("auth.grant.type.error"));
} else if (!UserConstants.NORMAL.equals(client.getStatus())) {
} else if (!SystemConstants.NORMAL.equals(client.getStatus())) {
return R.fail(MessageUtils.message("auth.grant.type.blocked"));
}
// 校验租户
@ -111,7 +112,7 @@ public class AuthController {
}
/**
* 第三方登录请求
* 获取跳转URL
*
* @param source 登录来源
* @return 结果
@ -133,13 +134,15 @@ public class AuthController {
}
/**
* 第三方登录回调业务处理 绑定授权
* 前端回调绑定授权(需要token)
*
* @param loginBody 请求体
* @return 结果
*/
@PostMapping("/social/callback")
public R<Void> socialCallback(@RequestBody SocialLoginBody loginBody) {
// 校验token
StpUtil.checkLogin();
// 获取第三方登录信息
AuthResponse<AuthUser> response = SocialUtils.loginAuth(
loginBody.getSource(), loginBody.getSocialCode(),
@ -155,12 +158,14 @@ public class AuthController {
/**
* 取消授权
* 取消授权(需要token)
*
* @param socialId socialId
*/
@DeleteMapping(value = "/unlock/{socialId}")
public R<Void> unlockSocial(@PathVariable Long socialId) {
// 校验token
StpUtil.checkLogin();
Boolean rows = socialUserService.deleteWithValidById(socialId);
return rows ? R.ok() : R.fail("取消授权失败");
}
@ -226,7 +231,7 @@ public class AuthController {
}
// 根据域名进行筛选
List<TenantListVo> list = StreamUtils.filter(voList, vo ->
StringUtils.equals(vo.getDomain(), host));
StringUtils.equalsIgnoreCase(vo.getDomain(), host));
result.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
return R.ok(result);
}

View File

@ -12,11 +12,12 @@ import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthUser;
import org.dromara.common.core.constant.CacheConstants;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.domain.dto.PostDTO;
import org.dromara.common.core.domain.dto.RoleDTO;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.enums.LoginType;
import org.dromara.common.core.enums.TenantStatus;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.*;
@ -60,6 +61,7 @@ public class SysLoginService {
private final ISysSocialService sysSocialService;
private final ISysRoleService roleService;
private final ISysDeptService deptService;
private final ISysPostService postService;
private final SysUserMapper userMapper;
@ -148,21 +150,24 @@ public class SysLoginService {
*/
public LoginUser buildLoginUser(SysUserVo user) {
LoginUser loginUser = new LoginUser();
Long userId = user.getUserId();
loginUser.setTenantId(user.getTenantId());
loginUser.setUserId(user.getUserId());
loginUser.setUserId(userId);
loginUser.setDeptId(user.getDeptId());
loginUser.setUsername(user.getUserName());
loginUser.setNickname(user.getNickName());
loginUser.setUserType(user.getUserType());
loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId()));
loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId()));
loginUser.setMenuPermission(permissionService.getMenuPermission(userId));
loginUser.setRolePermission(permissionService.getRolePermission(userId));
if (ObjectUtil.isNotNull(user.getDeptId())) {
Opt<SysDeptVo> deptOpt = Opt.of(user.getDeptId()).map(deptService::selectDeptById);
loginUser.setDeptName(deptOpt.map(SysDeptVo::getDeptName).orElse(StringUtils.EMPTY));
loginUser.setDeptCategory(deptOpt.map(SysDeptVo::getDeptCategory).orElse(StringUtils.EMPTY));
}
List<SysRoleVo> roles = roleService.selectRolesByUserId(user.getUserId());
List<SysRoleVo> roles = roleService.selectRolesByUserId(userId);
List<SysPostVo> posts = postService.selectPostsByUserId(userId);
loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
loginUser.setPosts(BeanUtil.copyToList(posts, PostDTO.class));
return loginUser;
}
@ -223,17 +228,17 @@ public class SysLoginService {
if (!TenantHelper.isEnable()) {
return;
}
if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
return;
}
if (StringUtils.isBlank(tenantId)) {
throw new TenantException("tenant.number.not.blank");
}
if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
return;
}
SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
if (ObjectUtil.isNull(tenant)) {
log.info("登录租户:{} 不存在.", tenantId);
throw new TenantException("tenant.not.exists");
} else if (TenantStatus.DISABLE.getCode().equals(tenant.getStatus())) {
} else if (SystemConstants.DISABLE.equals(tenant.getStatus())) {
log.info("登录租户:{} 已被停用.", tenantId);
throw new TenantException("tenant.blocked");
} else if (ObjectUtil.isNotNull(tenant.getExpireTime())

View File

@ -84,11 +84,11 @@ public class SysRegisterService {
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.expire"));
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.error"));
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException();
}
}

View File

@ -8,10 +8,10 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.model.EmailLoginBody;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.enums.LoginType;
import org.dromara.common.core.enums.UserStatus;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.MessageUtils;
@ -92,7 +92,7 @@ public class EmailAuthStrategy implements IAuthStrategy {
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", email);
throw new UserException("user.not.exists", email);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", email);
throw new UserException("user.blocked", email);
}

View File

@ -9,10 +9,10 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.domain.model.PasswordLoginBody;
import org.dromara.common.core.enums.LoginType;
import org.dromara.common.core.enums.UserStatus;
import org.dromara.common.core.exception.user.CaptchaException;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.exception.user.UserException;
@ -113,7 +113,7 @@ public class PasswordAuthStrategy implements IAuthStrategy {
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
throw new UserException("user.not.exists", username);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new UserException("user.blocked", username);
}

View File

@ -8,10 +8,10 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.domain.model.SmsLoginBody;
import org.dromara.common.core.enums.LoginType;
import org.dromara.common.core.enums.UserStatus;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.MessageUtils;
@ -92,7 +92,7 @@ public class SmsAuthStrategy implements IAuthStrategy {
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", phonenumber);
throw new UserException("user.not.exists", phonenumber);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", phonenumber);
throw new UserException("user.blocked", phonenumber);
}

View File

@ -11,9 +11,9 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.domain.model.SocialLoginBody;
import org.dromara.common.core.enums.UserStatus;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.StreamUtils;
@ -121,7 +121,7 @@ public class SocialAuthStrategy implements IAuthStrategy {
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", "");
throw new UserException("user.not.exists", "");
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", "");
throw new UserException("user.blocked", "");
}

View File

@ -5,13 +5,20 @@ import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.request.AuthWechatMiniProgramRequest;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.model.XcxLoginBody;
import org.dromara.common.core.domain.model.XcxLoginUser;
import org.dromara.common.core.enums.UserStatus;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.SysClient;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.web.domain.vo.LoginVo;
@ -40,12 +47,24 @@ public class XcxAuthStrategy implements IAuthStrategy {
// 多个小程序识别使用
String appid = loginBody.getAppid();
// todo 以下自行实现
// 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid
String openid = "";
AuthRequest authRequest = new AuthWechatMiniProgramRequest(AuthConfig.builder()
.clientId(appid).clientSecret("自行填写密钥 可根据不同appid填入不同密钥")
.ignoreCheckRedirectUri(true).ignoreCheckState(true).build());
AuthCallback authCallback = new AuthCallback();
authCallback.setCode(xcxCode);
AuthResponse<AuthUser> resp = authRequest.login(authCallback);
String openid, unionId;
if (resp.ok()) {
AuthToken token = resp.getData().getToken();
openid = token.getOpenId();
// 微信小程序只有关联到微信开放平台下之后才能获取到 unionId因此unionId不一定能返回。
unionId = token.getUnionId();
} else {
throw new ServiceException(resp.getMsg());
}
// 框架登录不限制从什么表查询 只要最终构建出 LoginUser 即可
SysUserVo user = loadUserByOpenid(openid);
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
XcxLoginUser loginUser = new XcxLoginUser();
loginUser.setTenantId(user.getTenantId());
@ -82,7 +101,7 @@ public class XcxAuthStrategy implements IAuthStrategy {
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", openid);
// todo 用户不存在 业务逻辑自行实现
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", openid);
// todo 用户已被停用 业务逻辑自行实现
}

View File

@ -2,29 +2,33 @@
spring.boot.admin.client:
# 增加客户端开关
enabled: true
url: http://localhost:9090/admin
url: http://localhost:9090
instance:
service-host-type: IP
metadata:
username: ${spring.boot.admin.client.username}
userpassword: ${spring.boot.admin.client.password}
username: ruoyi
password: 123456
username: @monitor.username@
password: @monitor.password@
--- # snail-job 配置
snail-job:
enabled: true
# 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
group: "ruoyi_group"
# SnailJob 接入验证令牌 详见 script/sql/snail_job.sql `sj_group_config` 表
# SnailJob 接入验证令牌 详见 script/sql/ry_job.sql `sj_group_config` 表
token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
server:
host: 127.0.0.1
port: 17888
# 详见 script/sql/snail_job.sql `sj_namespace`
# 命名空间UUID 详见 script/sql/ry_job.sql `sj_namespace`表`unique_id`字段
namespace: ${spring.profiles.active}
# 随主应用端口飘逸
# 随主应用端口漂移
port: 2${server.port}
# 客户端ip指定
host:
# RPC类型: netty, grpc
rpc-type: grpc
--- # 数据源配置
spring:
@ -48,14 +52,14 @@ spring:
url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
password: root
# 从库数据源
slave:
lazy: true
type: ${spring.datasource.type}
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username:
password:
# # 从库数据源
# slave:
# lazy: true
# type: ${spring.datasource.type}
# driverClassName: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
# username:
# password:
# oracle:
# type: ${spring.datasource.type}
# driverClassName: oracle.jdbc.OracleDriver
@ -198,7 +202,7 @@ justauth:
redirect-uri: ${justauth.address}/social-callback?source=maxkey
topiam:
# topiam 服务器地址
server-url: http://127.0.0.1:1989/api/v1/authorize/y0q************spq***********8ol
server-url: http://127.0.0.1:1898/api/v1/authorize/y0q************spq***********8ol
client-id: 449c4*********937************759
client-secret: ac7***********1e0************28d
redirect-uri: ${justauth.address}/social-callback?source=topiam

View File

@ -5,29 +5,33 @@ spring.servlet.multipart.location: /ruoyi/server/temp
spring.boot.admin.client:
# 增加客户端开关
enabled: true
url: http://localhost:9090/admin
url: http://localhost:9090
instance:
service-host-type: IP
metadata:
username: ${spring.boot.admin.client.username}
userpassword: ${spring.boot.admin.client.password}
username: ruoyi
password: 123456
username: @monitor.username@
password: @monitor.password@
--- # snail-job 配置
snail-job:
enabled: true
# 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
group: "ruoyi_group"
# SnailJob 接入验证令牌 详见 script/sql/snail_job.sql `sj_group_config`
# SnailJob 接入验证令牌 详见 script/sql/ry_job.sql `sj_group_config`表
token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
server:
host: 127.0.0.1
port: 17888
# 详见 script/sql/snail_job.sql `sj_namespace`
# 命名空间UUID 详见 script/sql/ry_job.sql `sj_namespace`表`unique_id`字段
namespace: ${spring.profiles.active}
# 随主应用端口飘逸
# 随主应用端口漂移
port: 2${server.port}
# 客户端ip指定
host:
# RPC类型: netty, grpc
rpc-type: grpc
--- # 数据源配置
spring:
@ -51,14 +55,14 @@ spring:
url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
password: root
# 从库数据源
slave:
lazy: true
type: ${spring.datasource.type}
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username:
password:
# # 从库数据源
# slave:
# lazy: true
# type: ${spring.datasource.type}
# driverClassName: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
# username:
# password:
# oracle:
# type: ${spring.datasource.type}
# driverClassName: oracle.jdbc.OracleDriver

View File

@ -47,6 +47,7 @@ logging:
org.dromara: @logging.level@
org.springframework: warn
org.mybatis.spring.mapper: error
org.apache.fury: warn
config: classpath:logback-plus.xml
# 用户配置
@ -110,17 +111,15 @@ sa-token:
security:
# 排除路径
excludes:
# 静态资源
- /*.html
- /**/*.html
- /**/*.css
- /**/*.js
# 公共路径
- /favicon.ico
- /error
# swagger 文档配置
- /*/api-docs
- /*/api-docs/**
- /warm-flow-ui/token-name
# 多租户配置
tenant:
@ -141,6 +140,8 @@ tenant:
# MyBatisPlus配置
# https://baomidou.com/config/
mybatis-plus:
# 自定义配置 是否全局开启逻辑删除 关闭后 所有逻辑删除功能将失效
enableLogicDelete: true
# 多包名使用 例如 org.dromara.**.mapper,org.xxx.**.mapper
mapperPackage: org.dromara.**.mapper
# 对应的 XML 文件位置
@ -217,15 +218,17 @@ springdoc:
packages-to-scan: org.dromara.system
- group: 4.代码生成模块
packages-to-scan: org.dromara.generator
- group: 5.工作流模块
packages-to-scan: org.dromara.workflow
# 防止XSS攻击
xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
excludes: /system/notice
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
excludeUrls:
- /system/notice
- /warm-flow/save-xml
# 全局线程池相关配置
# 如使用JDK21请直接使用虚拟线程 不要开启此配置
@ -270,24 +273,11 @@ websocket:
# 设置访问源地址
allowedOrigins: '*'
--- #flowable配置
flowable:
# 开关 用于启动/停用工作流
--- # warm-flow工作流配置
warm-flow:
# 是否开启工作流默认true
enabled: true
process.enabled: ${flowable.enabled}
eventregistry.enabled: ${flowable.enabled}
async-executor-activate: false #关闭定时任务JOB
# 将databaseSchemaUpdate设置为true。当Flowable发现库与数据库表结构不一致时会自动将数据库表结构升级至新版本。
database-schema-update: true
activity-font-name: 宋体
label-font-name: 宋体
annotation-font-name: 宋体
# 关闭各个模块生成表,目前只使用工作流基础表
idm:
enabled: false
cmmn:
enabled: false
dmn:
enabled: false
app:
enabled: false
# 是否开启设计器ui
ui: true
# 默认Authorization如果有多个token用逗号分隔
token-name: ${sa-token.token-name},clientid

View File

@ -2,7 +2,7 @@
<configuration>
<property name="log.path" value="./logs"/>
<property name="console.log.pattern"
value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
value="%cyan(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>
<!-- 控制台输出 -->

View File

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

Binary file not shown.

View File

@ -0,0 +1,4 @@
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

@ -0,0 +1,4 @@
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.2.2</revision>
<revision>5.3.0</revision>
</properties>
<dependencyManagement>

View File

@ -4,11 +4,13 @@ import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
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;
@ -49,8 +51,15 @@ public class ThreadPoolConfig {
*/
@Bean(name = "scheduledExecutorService")
protected ScheduledExecutorService scheduledExecutorService() {
// daemon 必须为 true
BasicThreadFactory.Builder builder = new BasicThreadFactory.Builder().daemon(true);
if (SpringUtils.isVirtual()) {
builder.namingPattern("virtual-schedule-pool-%d").wrappedFactory(new VirtualThreadTaskExecutor().getVirtualThreadFactory());
} else {
builder.namingPattern("schedule-pool-%d");
}
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(core,
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
builder.build(),
new ThreadPoolExecutor.CallerRunsPolicy()) {
@Override
protected void afterExecute(Runnable r, Throwable t) {

View File

@ -60,6 +60,16 @@ public interface CacheNames {
*/
String SYS_OSS = "sys_oss#30d";
/**
* 角色自定义权限
*/
String SYS_ROLE_CUSTOM = "sys_role_custom#30d";
/**
* 部门及以下权限
*/
String SYS_DEPT_AND_CHILD = "sys_dept_and_child#30d";
/**
* OSS配置
*/

View File

@ -68,12 +68,7 @@ public interface Constants {
Integer CAPTCHA_EXPIRATION = 2;
/**
* 令牌
*/
String TOKEN = "token";
/**
* 顶级部门id
* 顶级父级id
*/
Long TOP_PARENT_ID = 0L;

View File

@ -0,0 +1,75 @@
package org.dromara.common.core.constant;
/**
* 系统常量信息
*
* @author Lion Li
*/
public interface SystemConstants {
/**
* 正常状态
*/
String NORMAL = "0";
/**
* 异常状态
*/
String DISABLE = "1";
/**
* 是否为系统默认(是)
*/
String YES = "Y";
/**
* 是否为系统默认(否)
*/
String NO = "N";
/**
* 是否菜单外链(是)
*/
String YES_FRAME = "0";
/**
* 是否菜单外链(否)
*/
String NO_FRAME = "1";
/**
* 菜单类型(目录)
*/
String TYPE_DIR = "M";
/**
* 菜单类型(菜单)
*/
String TYPE_MENU = "C";
/**
* 菜单类型(按钮)
*/
String TYPE_BUTTON = "F";
/**
* Layout组件标识
*/
String LAYOUT = "Layout";
/**
* ParentView组件标识
*/
String PARENT_VIEW = "ParentView";
/**
* InnerLink组件标识
*/
String INNER_LINK = "InnerLink";
/**
* 超级管理员ID
*/
Long SUPER_ADMIN_ID = 1L;
}

View File

@ -7,16 +7,6 @@ package org.dromara.common.core.constant;
*/
public interface TenantConstants {
/**
* 租户正常状态
*/
String NORMAL = "0";
/**
* 租户封禁状态
*/
String DISABLE = "1";
/**
* 超级管理员ID
*/

View File

@ -1,152 +0,0 @@
package org.dromara.common.core.constant;
/**
* 用户常量信息
*
* @author ruoyi
*/
public interface UserConstants {
/**
* 平台内系统用户的唯一标志
*/
String SYS_USER = "SYS_USER";
/**
* 正常状态
*/
String NORMAL = "0";
/**
* 异常状态
*/
String EXCEPTION = "1";
/**
* 用户正常状态
*/
String USER_NORMAL = "0";
/**
* 用户封禁状态
*/
String USER_DISABLE = "1";
/**
* 角色正常状态
*/
String ROLE_NORMAL = "0";
/**
* 角色封禁状态
*/
String ROLE_DISABLE = "1";
/**
* 部门正常状态
*/
String DEPT_NORMAL = "0";
/**
* 部门停用状态
*/
String DEPT_DISABLE = "1";
/**
* 岗位正常状态
*/
String POST_NORMAL = "0";
/**
* 岗位停用状态
*/
String POST_DISABLE = "1";
/**
* 字典正常状态
*/
String DICT_NORMAL = "0";
/**
* 通用存在标志
*/
String DEL_FLAG_NORMAL = "0";
/**
* 通用删除标志
*/
String DEL_FLAG_REMOVED = "2";
/**
* 是否为系统默认(是)
*/
String YES = "Y";
/**
* 是否菜单外链(是)
*/
String YES_FRAME = "0";
/**
* 是否菜单外链(否)
*/
String NO_FRAME = "1";
/**
* 菜单正常状态
*/
String MENU_NORMAL = "0";
/**
* 菜单停用状态
*/
String MENU_DISABLE = "1";
/**
* 菜单类型(目录)
*/
String TYPE_DIR = "M";
/**
* 菜单类型(菜单)
*/
String TYPE_MENU = "C";
/**
* 菜单类型(按钮)
*/
String TYPE_BUTTON = "F";
/**
* Layout组件标识
*/
String LAYOUT = "Layout";
/**
* ParentView组件标识
*/
String PARENT_VIEW = "ParentView";
/**
* InnerLink组件标识
*/
String INNER_LINK = "InnerLink";
/**
* 用户名长度限制
*/
int USERNAME_MIN_LENGTH = 2;
int USERNAME_MAX_LENGTH = 20;
/**
* 密码长度限制
*/
int PASSWORD_MIN_LENGTH = 5;
int PASSWORD_MAX_LENGTH = 20;
/**
* 超级管理员ID
*/
Long SUPER_ADMIN_ID = 1L;
}

View File

@ -0,0 +1,71 @@
package org.dromara.common.core.domain.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 办理任务请求对象
*
* @author may
*/
@Data
public class CompleteTaskDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 任务id
*/
private Long taskId;
/**
* 附件id
*/
private String fileId;
/**
* 抄送人员
*/
private List<FlowCopyDTO> flowCopyList;
/**
* 消息类型
*/
private List<String> messageType;
/**
* 办理意见
*/
private String message;
/**
* 消息通知
*/
private String notice;
/**
* 流程变量
*/
private Map<String, Object> variables;
/**
* 扩展变量(此处为逗号分隔的ossId)
*/
private String ext;
public Map<String, Object> getVariables() {
if (variables == null) {
return new HashMap<>(16);
}
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
return variables;
}
}

View File

@ -0,0 +1,37 @@
package org.dromara.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 部门
*
* @author AprilWind
*/
@Data
@NoArgsConstructor
public class DeptDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 部门ID
*/
private Long deptId;
/**
* 父部门ID
*/
private Long parentId;
/**
* 部门名称
*/
private String deptName;
}

View File

@ -0,0 +1,30 @@
package org.dromara.common.core.domain.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 抄送
*
* @author may
*/
@Data
public class FlowCopyDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 用户id
*/
private Long userId;
/**
* 用户名称
*/
private String userName;
}

View File

@ -0,0 +1,46 @@
package org.dromara.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 岗位
*
* @author AprilWind
*/
@Data
@NoArgsConstructor
public class PostDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 岗位ID
*/
private Long postId;
/**
* 部门id
*/
private Long deptId;
/**
* 岗位编码
*/
private String postCode;
/**
* 岗位名称
*/
private String postName;
/**
* 岗位类别编码
*/
private String postCategory;
}

View File

@ -0,0 +1,45 @@
package org.dromara.common.core.domain.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 启动流程对象
*
* @author may
*/
@Data
public class StartProcessDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 业务唯一值id
*/
private String businessId;
/**
* 流程定义编码
*/
private String flowCode;
/**
* 流程变量,前端会提交一个元素{'entity': {业务详情数据对象}}
*/
private Map<String, Object> variables;
public Map<String, Object> getVariables() {
if (variables == null) {
return new HashMap<>(16);
}
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
return variables;
}
}

View File

@ -0,0 +1,30 @@
package org.dromara.common.core.domain.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 启动流程返回对象
*
* @author Lion Li
*/
@Data
public class StartProcessReturnDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 流程实例id
*/
private Long processInstanceId;
/**
* 任务id
*/
private Long taskId;
}

View File

@ -0,0 +1,101 @@
package org.dromara.common.core.domain.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 任务受让人
*
* @author AprilWind
*/
@Data
@NoArgsConstructor
public class TaskAssigneeDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 总大小
*/
private Long total = 0L;
/**
*
*/
private List<TaskHandler> list;
public TaskAssigneeDTO(Long total, List<TaskHandler> list) {
this.total = total;
this.list = list;
}
/**
* 将源列表转换为 TaskHandler 列表
*
* @param <T> 通用类型
* @param sourceList 待转换的源列表
* @param storageId 提取 storageId 的函数
* @param handlerCode 提取 handlerCode 的函数
* @param handlerName 提取 handlerName 的函数
* @param groupName 提取 groupName 的函数
* @param createTimeMapper 提取 createTime 的函数
* @return 转换后的 TaskHandler 列表
*/
public static <T> List<TaskHandler> convertToHandlerList(
List<T> sourceList,
Function<T, Long> storageId,
Function<T, String> handlerCode,
Function<T, String> handlerName,
Function<T, Long> groupName,
Function<T, Date> createTimeMapper) {
return sourceList.stream()
.map(item -> new TaskHandler(
String.valueOf(storageId.apply(item)),
handlerCode.apply(item),
handlerName.apply(item),
groupName != null ? String.valueOf(groupName.apply(item)) : null,
createTimeMapper.apply(item)
)).collect(Collectors.toList());
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class TaskHandler {
/**
* 主键
*/
private String storageId;
/**
* 权限编码
*/
private String handlerCode;
/**
* 权限名称
*/
private String handlerName;
/**
* 权限分组
*/
private String groupName;
/**
* 创建时间
*/
private Date createTime;
}
}

View File

@ -0,0 +1,34 @@
package org.dromara.common.core.domain.event;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 删除流程监听
*
* @author AprilWind
*/
@Data
public class ProcessDeleteEvent implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 租户ID
*/
private String tenantId;
/**
* 流程定义编码
*/
private String flowCode;
/**
* 业务id
*/
private String businessId;
}

View File

@ -4,13 +4,13 @@ import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Map;
/**
* 总体流程监听
*
* @author may
*/
@Data
public class ProcessEvent implements Serializable {
@ -18,24 +18,33 @@ public class ProcessEvent implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 流程定义key
* 租户ID
*/
private String key;
private String tenantId;
/**
* 流程定义编码
*/
private String flowCode;
/**
* 业务id
*/
private String businessKey;
private String businessId;
/**
* 状态
*/
private String status;
/**
* 办理参数
*/
private Map<String, Object> params;
/**
* 当为true时为申请人节点办理
*/
private boolean submit;
}

View File

@ -10,7 +10,6 @@ import java.io.Serializable;
*
* @author may
*/
@Data
public class ProcessTaskEvent implements Serializable {
@ -18,23 +17,28 @@ public class ProcessTaskEvent implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 流程定义key
* 租户ID
*/
private String key;
private String tenantId;
/**
* 审批节点key
* 流程定义编码
*/
private String taskDefinitionKey;
private String flowCode;
/**
* 审批节点编码
*/
private String nodeCode;
/**
* 任务id
*/
private String taskId;
private Long taskId;
/**
* 业务id
*/
private String businessKey;
private String businessId;
}

View File

@ -1,8 +1,9 @@
package org.dromara.common.core.domain.model;
import org.dromara.common.core.domain.dto.RoleDTO;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.dromara.common.core.domain.dto.PostDTO;
import org.dromara.common.core.domain.dto.RoleDTO;
import java.io.Serial;
import java.io.Serializable;
@ -111,6 +112,11 @@ public class LoginUser implements Serializable {
*/
private List<RoleDTO> roles;
/**
* 岗位对象
*/
private List<PostDTO> posts;
/**
* 数据权限 当前角色ID
*/

View File

@ -5,8 +5,6 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.validator.constraints.Length;
import static org.dromara.common.core.constant.UserConstants.*;
/**
* 密码登录对象
*
@ -20,14 +18,14 @@ public class PasswordLoginBody extends LoginBody {
* 用户名
*/
@NotBlank(message = "{user.username.not.blank}")
@Length(min = USERNAME_MIN_LENGTH, max = USERNAME_MAX_LENGTH, message = "{user.username.length.valid}")
@Length(min = 2, max = 20, message = "{user.username.length.valid}")
private String username;
/**
* 用户密码
*/
@NotBlank(message = "{user.password.not.blank}")
@Length(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}")
@Length(min = 5, max = 20, message = "{user.password.length.valid}")
private String password;
}

View File

@ -5,8 +5,6 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.validator.constraints.Length;
import static org.dromara.common.core.constant.UserConstants.*;
/**
* 用户注册对象
*
@ -20,14 +18,14 @@ public class RegisterBody extends LoginBody {
* 用户名
*/
@NotBlank(message = "{user.username.not.blank}")
@Length(min = USERNAME_MIN_LENGTH, max = USERNAME_MAX_LENGTH, message = "{user.username.length.valid}")
@Length(min = 2, max = 20, message = "{user.username.length.valid}")
private String username;
/**
* 用户密码
*/
@NotBlank(message = "{user.password.not.blank}")
@Length(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}")
@Length(min = 5, max = 20, message = "{user.password.length.valid}")
private String password;
private String userType;

View File

@ -0,0 +1,56 @@
package org.dromara.common.core.domain.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 任务受让人
*
* @author AprilWind
*/
@Data
@NoArgsConstructor
public class TaskAssigneeBody implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 权限编码
*/
private String handlerCode;
/**
* 权限名称
*/
private String handlerName;
/**
* 权限分组
*/
private String groupId;
/**
* 开始时间
*/
private String beginTime;
/**
* 结束时间
*/
private String endTime;
/**
* 当前页
*/
private Integer pageNum = 1;
/**
* 每页显示条数
*/
private Integer pageSize = 10;
}

View File

@ -7,6 +7,10 @@ import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 业务状态枚举
@ -16,30 +20,37 @@ import java.util.Arrays;
@Getter
@AllArgsConstructor
public enum BusinessStatusEnum {
/**
* 已撤销
*/
CANCEL("cancel", "已撤销"),
/**
* 草稿
*/
DRAFT("draft", "草稿"),
/**
* 待审核
*/
WAITING("waiting", "待审核"),
/**
* 已完成
*/
FINISH("finish", "已完成"),
/**
* 已作废
*/
INVALID("invalid", "已作废"),
/**
* 已退回
*/
BACK("back", "已退回"),
/**
* 已终止
*/
@ -55,20 +66,72 @@ public enum BusinessStatusEnum {
*/
private final String desc;
private static final Map<String, BusinessStatusEnum> STATUS_MAP = Arrays.stream(BusinessStatusEnum.values())
.collect(Collectors.toConcurrentMap(BusinessStatusEnum::getStatus, Function.identity()));
/**
* 获取业务状态
* 根据状态获取对应的 BusinessStatusEnum 枚举
*
* @param status 状态
* @param status 业务状态
* @return 对应的 BusinessStatusEnum 枚举,如果找不到则返回 null
*/
public static BusinessStatusEnum getByStatus(String status) {
// 使用 STATUS_MAP 获取对应的枚举,若找不到则返回 null
return STATUS_MAP.get(status);
}
/**
* 根据状态获取对应的业务状态描述信息
*
* @param status 业务状态码
* @return 返回业务状态描述,若状态码为空或未找到对应的枚举,返回空字符串
*/
public static String findByStatus(String status) {
if (StringUtils.isBlank(status)) {
return StrUtil.EMPTY;
}
return Arrays.stream(BusinessStatusEnum.values())
.filter(statusEnum -> statusEnum.getStatus().equals(status))
.findFirst()
.map(BusinessStatusEnum::getDesc)
.orElse(StrUtil.EMPTY);
BusinessStatusEnum statusEnum = STATUS_MAP.get(status);
return (statusEnum != null) ? statusEnum.getDesc() : StrUtil.EMPTY;
}
/**
* 判断是否为指定的状态之一:草稿、已撤销或已退回
*
* @param status 要检查的状态
* @return 如果状态为草稿、已撤销或已退回之一,则返回 true否则返回 false
*/
public static boolean isDraftOrCancelOrBack(String status) {
return DRAFT.status.equals(status) || CANCEL.status.equals(status) || BACK.status.equals(status);
}
/**
* 判断是否为撤销,退回,作废,终止
*
* @param status status
* @return 结果
*/
public static boolean initialState(String status) {
return CANCEL.status.equals(status) || BACK.status.equals(status) || INVALID.status.equals(status) || TERMINATION.status.equals(status);
}
/**
* 获取运行中的实例状态列表
*
* @return 包含运行中实例状态的不可变列表
* (包含 DRAFT、WAITING、BACK 和 CANCEL 状态)
*/
public static List<String> runningStatus() {
return Arrays.asList(DRAFT.status, WAITING.status, BACK.status, CANCEL.status);
}
/**
* 获取结束实例的状态列表
*
* @return 包含结束实例状态的不可变列表
* (包含 FINISH、INVALID 和 TERMINATION 状态)
*/
public static List<String> finishStatus() {
return Arrays.asList(FINISH.status, INVALID.status, TERMINATION.status);
}
/**
@ -148,5 +211,5 @@ public enum BusinessStatusEnum {
throw new ServiceException("流程状态为空!");
}
}
}
}

View File

@ -0,0 +1,146 @@
package org.dromara.common.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.common.core.utils.StringUtils;
/*
* 日期格式
* "yyyy"4位数的年份例如2023年表示为"2023"。
* "yy"2位数的年份例如2023年表示为"23"。
* "MM"2位数的月份取值范围为01到12例如7月表示为"07"。
* "M"不带前导零的月份取值范围为1到12例如7月表示为"7"。
* "dd"2位数的日期取值范围为01到31例如22日表示为"22"。
* "d"不带前导零的日期取值范围为1到31例如22日表示为"22"。
* "EEEE":星期的全名,例如:星期三表示为"Wednesday"。
* "E":星期的缩写,例如:星期三表示为"Wed"。
* "DDD" 或 "D"一年中的第几天取值范围为001到366例如第200天表示为"200"。
* 时间格式
* "HH"24小时制的小时数取值范围为00到23例如下午5点表示为"17"。
* "hh"12小时制的小时数取值范围为01到12例如下午5点表示为"05"。
* "mm"分钟数取值范围为00到59例如30分钟表示为"30"。
* "ss"秒数取值范围为00到59例如45秒表示为"45"。
* "SSS"毫秒数取值范围为000到999例如123毫秒表示为"123"。
*/
/**
* 日期格式与时间格式枚举
*/
@Getter
@AllArgsConstructor
public enum FormatsType {
/**
* 例如2023年表示为"23"
*/
YY("yy"),
/**
* 例如2023年表示为"2023"
*/
YYYY("yyyy"),
/**
* 例例如2023年7月可以表示为 "2023-07"
*/
YYYY_MM("yyyy-MM"),
/**
* 例如,日期 "2023年7月22日" 可以表示为 "2023-07-22"
*/
YYYY_MM_DD("yyyy-MM-dd"),
/**
* 例如,当前时间如果是 "2023年7月22日下午3点30分",则可以表示为 "2023-07-22 15:30"
*/
YYYY_MM_DD_HH_MM("yyyy-MM-dd HH:mm"),
/**
* 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023-07-22 15:30:45"
*/
YYYY_MM_DD_HH_MM_SS("yyyy-MM-dd HH:mm:ss"),
/**
* 例如下午3点30分45秒表示为 "15:30:45"
*/
HH_MM_SS("HH:mm:ss"),
/**
* 例例如2023年7月可以表示为 "2023/07"
*/
YYYY_MM_SLASH("yyyy/MM"),
/**
* 例如,日期 "2023年7月22日" 可以表示为 "2023/07/22"
*/
YYYY_MM_DD_SLASH("yyyy/MM/dd"),
/**
* 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023/07/22 15:30:45"
*/
YYYY_MM_DD_HH_MM_SLASH("yyyy/MM/dd HH:mm"),
/**
* 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023/07/22 15:30:45"
*/
YYYY_MM_DD_HH_MM_SS_SLASH("yyyy/MM/dd HH:mm:ss"),
/**
* 例例如2023年7月可以表示为 "2023.07"
*/
YYYY_MM_DOT("yyyy.MM"),
/**
* 例如,日期 "2023年7月22日" 可以表示为 "2023.07.22"
*/
YYYY_MM_DD_DOT("yyyy.MM.dd"),
/**
* 例如,当前时间如果是 "2023年7月22日下午3点30分",则可以表示为 "2023.07.22 15:30"
*/
YYYY_MM_DD_HH_MM_DOT("yyyy.MM.dd HH:mm"),
/**
* 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023.07.22 15:30:45"
*/
YYYY_MM_DD_HH_MM_SS_DOT("yyyy.MM.dd HH:mm:ss"),
/**
* 例如2023年7月可以表示为 "202307"
*/
YYYYMM("yyyyMM"),
/**
* 例如2023年7月22日可以表示为 "20230722"
*/
YYYYMMDD("yyyyMMdd"),
/**
* 例如2023年7月22日下午3点可以表示为 "2023072215"
*/
YYYYMMDDHH("yyyyMMddHH"),
/**
* 例如2023年7月22日下午3点30分可以表示为 "202307221530"
*/
YYYYMMDDHHMM("yyyyMMddHHmm"),
/**
* 例如2023年7月22日下午3点30分45秒可以表示为 "20230722153045"
*/
YYYYMMDDHHMMSS("yyyyMMddHHmmss");
/**
* 时间格式
*/
private final String timeFormat;
public static FormatsType getFormatsType(String str) {
for (FormatsType value : values()) {
if (StringUtils.contains(str, value.getTimeFormat())) {
return value;
}
}
throw new RuntimeException("'FormatsType' not found By " + str);
}
}

View File

@ -1,30 +0,0 @@
package org.dromara.common.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 用户状态
*
* @author LionLi
*/
@Getter
@AllArgsConstructor
public enum TenantStatus {
/**
* 正常
*/
OK("0", "正常"),
/**
* 停用
*/
DISABLE("1", "停用"),
/**
* 删除
*/
DELETED("2", "删除");
private final String code;
private final String info;
}

View File

@ -0,0 +1,62 @@
package org.dromara.common.core.exception;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.Serial;
/**
* sse 特制异常
*
* @author LionLi
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public final class SseException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;
/**
* 错误码
*/
private Integer code;
/**
* 错误提示
*/
private String message;
/**
* 错误明细,内部调试错误
*/
private String detailMessage;
public SseException(String message) {
this.message = message;
}
public SseException(String message, Integer code) {
this.message = message;
this.code = code;
}
@Override
public String getMessage() {
return message;
}
public SseException setMessage(String message) {
this.message = message;
return this;
}
public SseException setDetailMessage(String detailMessage) {
this.detailMessage = detailMessage;
return this;
}
}

View File

@ -1,5 +1,9 @@
package org.dromara.common.core.service;
import org.dromara.common.core.domain.dto.DeptDTO;
import java.util.List;
/**
* 通用 部门服务
*
@ -15,4 +19,19 @@ public interface DeptService {
*/
String selectDeptNameByIds(String deptIds);
/**
* 根据部门ID查询部门负责人
*
* @param deptId 部门ID用于指定需要查询的部门
* @return 返回该部门的负责人ID
*/
Long selectDeptLeaderById(Long deptId);
/**
* 查询部门
*
* @return 部门列表
*/
List<DeptDTO> selectDeptsByList();
}

View File

@ -0,0 +1,10 @@
package org.dromara.common.core.service;
/**
* 通用 岗位服务
*
* @author AprilWind
*/
public interface PostService {
}

View File

@ -0,0 +1,10 @@
package org.dromara.common.core.service;
/**
* 通用 角色服务
*
* @author AprilWind
*/
public interface RoleService {
}

View File

@ -0,0 +1,45 @@
package org.dromara.common.core.service;
import org.dromara.common.core.domain.dto.TaskAssigneeDTO;
import org.dromara.common.core.domain.model.TaskAssigneeBody;
/**
* 工作流设计器获取任务执行人
*
* @author Lion Li
*/
public interface TaskAssigneeService {
/**
* 查询角色并返回任务指派的列表,支持分页
*
* @param taskQuery 查询条件
* @return 办理人
*/
TaskAssigneeDTO selectRolesByTaskAssigneeList(TaskAssigneeBody taskQuery);
/**
* 查询岗位并返回任务指派的列表,支持分页
*
* @param taskQuery 查询条件
* @return 办理人
*/
TaskAssigneeDTO selectPostsByTaskAssigneeList(TaskAssigneeBody taskQuery);
/**
* 查询部门并返回任务指派的列表,支持分页
*
* @param taskQuery 查询条件
* @return 办理人
*/
TaskAssigneeDTO selectDeptsByTaskAssigneeList(TaskAssigneeBody taskQuery);
/**
* 查询用户并返回任务指派的列表,支持分页
*
* @param taskQuery 查询条件
* @return 办理人
*/
TaskAssigneeDTO selectUsersByTaskAssigneeList(TaskAssigneeBody taskQuery);
}

View File

@ -82,4 +82,13 @@ public interface UserService {
* @return 用户
*/
List<UserDTO> selectUsersByDeptIds(List<Long> deptIds);
/**
* 通过岗位ID查询用户
*
* @param postIds 岗位ids
* @return 用户
*/
List<UserDTO> selectUsersByPostIds(List<Long> postIds);
}

View File

@ -1,5 +1,9 @@
package org.dromara.common.core.service;
import org.dromara.common.core.domain.dto.CompleteTaskDTO;
import org.dromara.common.core.domain.dto.StartProcessDTO;
import org.dromara.common.core.domain.dto.StartProcessReturnDTO;
import java.util.List;
import java.util.Map;
@ -13,64 +17,70 @@ public interface WorkflowService {
/**
* 运行中的实例 删除程实例,删除历史记录,删除业务与流程关联信息
*
* @param businessKeys 业务id
* @param businessIds 业务id
* @return 结果
*/
boolean deleteRunAndHisInstance(List<String> businessKeys);
boolean deleteInstance(List<Long> businessIds);
/**
* 获取当前流程状态
*
* @param taskId 任务id
* @return 状态
*/
String getBusinessStatusByTaskId(String taskId);
String getBusinessStatusByTaskId(Long taskId);
/**
* 获取当前流程状态
*
* @param businessKey 业务id
* @param businessId 业务id
* @return 状态
*/
String getBusinessStatus(String businessKey);
String getBusinessStatus(String businessId);
/**
* 设置流程变量(全局变量)
* 设置流程变量
*
* @param taskId 任务id
* @param variableName 变量名称
* @param value 变量值
* @param instanceId 流程实例id
* @param variable 流程变量
*/
void setVariable(String taskId, String variableName, Object value);
void setVariable(Long instanceId, Map<String, Object> variable);
/**
* 设置流程变量(全局变量)
* 获取流程变量
*
* @param taskId 任务id
* @param variables 流程变量
* @param instanceId 流程实例id
*/
void setVariables(String taskId, Map<String, Object> variables);
/**
* 设置流程变量(本地变量,非全局变量)
*
* @param taskId 任务id
* @param variableName 变量名称
* @param value 变量值
*/
void setVariableLocal(String taskId, String variableName, Object value);
/**
* 设置流程变量(本地变量,非全局变量)
*
* @param taskId 任务id
* @param variables 流程变量
*/
void setVariablesLocal(String taskId, Map<String, Object> variables);
Map<String, Object> instanceVariable(Long instanceId);
/**
* 按照业务id查询流程实例id
*
* @param businessKey 业务id
* @param businessId 业务id
* @return 结果
*/
String getInstanceIdByBusinessKey(String businessKey);
Long getInstanceIdByBusinessId(String businessId);
/**
* 新增租户流程定义
*
* @param tenantId 租户id
*/
void syncDef(String tenantId);
/**
* 启动流程
*
* @param startProcess 参数
* @return 结果
*/
StartProcessReturnDTO startWorkFlow(StartProcessDTO startProcess);
/**
* 办理任务
*
* @param completeTask 参数
* @return 结果
*/
boolean completeTask(CompleteTaskDTO completeTask);
}

View File

@ -1,106 +1,157 @@
package org.dromara.common.core.utils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.dromara.common.core.enums.FormatsType;
import org.dromara.common.core.exception.ServiceException;
import java.lang.management.ManagementFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.*;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* 时间工具类
*
* @author ruoyi
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
public static final String YYYY = "yyyy";
public static final String YYYY_MM = "yyyy-MM";
public static final String YYYY_MM_DD = "yyyy-MM-dd";
public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
private static final String[] PARSE_PATTERNS = {
"yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
"yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
"yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
@Deprecated
private DateUtils() {
}
/**
* 获取当前Date型日期
* 获取当前日期和时间
*
* @return Date() 当前日期
* @return 当前日期和时间的 Date 对象表示
*/
public static Date getNowDate() {
return new Date();
}
/**
* 获取当前日期, 默认格式为yyyy-MM-dd
* 获取当前日期的字符串表示格式为YYYY-MM-DD
*
* @return String
* @return 当前日期的字符串表示
*/
public static String getDate() {
return dateTimeNow(YYYY_MM_DD);
return dateTimeNow(FormatsType.YYYY_MM_DD);
}
/**
* 获取当前日期的字符串表示格式为yyyyMMdd
*
* @return 当前日期的字符串表示
*/
public static String getCurrentDate() {
return DateFormatUtils.format(new Date(), FormatsType.YYYYMMDD.getTimeFormat());
}
/**
* 获取当前日期的路径格式字符串,格式为"yyyy/MM/dd"
*
* @return 当前日期的路径格式字符串
*/
public static String datePath() {
Date now = new Date();
return DateFormatUtils.format(now, FormatsType.YYYY_MM_DD_SLASH.getTimeFormat());
}
/**
* 获取当前时间的字符串表示格式为YYYY-MM-DD HH:MM:SS
*
* @return 当前时间的字符串表示
*/
public static String getTime() {
return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
return dateTimeNow(FormatsType.YYYY_MM_DD_HH_MM_SS);
}
/**
* 获取当前时间的字符串表示,格式为 "HH:MM:SS"
*
* @return 当前时间的字符串表示,格式为 "HH:MM:SS"
*/
public static String getTimeWithHourMinuteSecond() {
return dateTimeNow(FormatsType.HH_MM_SS);
}
/**
* 获取当前日期和时间的字符串表示格式为YYYYMMDDHHMMSS
*
* @return 当前日期和时间的字符串表示
*/
public static String dateTimeNow() {
return dateTimeNow(YYYYMMDDHHMMSS);
return dateTimeNow(FormatsType.YYYYMMDDHHMMSS);
}
public static String dateTimeNow(final String format) {
/**
* 获取当前日期和时间的指定格式的字符串表示
*
* @param format 日期时间格式,例如"YYYY-MM-DD HH:MM:SS"
* @return 当前日期和时间的字符串表示
*/
public static String dateTimeNow(final FormatsType format) {
return parseDateToStr(format, new Date());
}
public static String dateTime(final Date date) {
return parseDateToStr(YYYY_MM_DD, date);
/**
* 将指定日期格式化为 YYYY-MM-DD 格式的字符串
*
* @param date 要格式化的日期对象
* @return 格式化后的日期字符串
*/
public static String formatDate(final Date date) {
return parseDateToStr(FormatsType.YYYY_MM_DD, date);
}
public static String parseDateToStr(final String format, final Date date) {
return new SimpleDateFormat(format).format(date);
/**
* 将指定日期格式化为 YYYY-MM-DD HH:MM:SS 格式的字符串
*
* @param date 要格式化的日期对象
* @return 格式化后的日期时间字符串
*/
public static String formatDateTime(final Date date) {
return parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM_SS, date);
}
public static Date dateTime(final String format, final String ts) {
/**
* 将指定日期按照指定格式进行格式化
*
* @param format 要使用的日期时间格式,例如"YYYY-MM-DD HH:MM:SS"
* @param date 要格式化的日期对象
* @return 格式化后的日期时间字符串
*/
public static String parseDateToStr(final FormatsType format, final Date date) {
return new SimpleDateFormat(format.getTimeFormat()).format(date);
}
/**
* 将指定格式的日期时间字符串转换为 Date 对象
*
* @param format 要解析的日期时间格式,例如"YYYY-MM-DD HH:MM:SS"
* @param ts 要解析的日期时间字符串
* @return 解析后的 Date 对象
* @throws RuntimeException 如果解析过程中发生异常
*/
public static Date parseDateTime(final FormatsType format, final String ts) {
try {
return new SimpleDateFormat(format).parse(ts);
return new SimpleDateFormat(format.getTimeFormat()).parse(ts);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
/**
* 日期路径 即年/月/日 如2018/08/08
*/
public static String datePath() {
Date now = new Date();
return DateFormatUtils.format(now, "yyyy/MM/dd");
}
/**
* 日期路径 即年/月/日 如20180808
*/
public static String dateTime() {
Date now = new Date();
return DateFormatUtils.format(now, "yyyyMMdd");
}
/**
* 日期型字符串转化为日期 格式
* 将对象转换为日期对象
*
* @param str 要转换的对象,通常是字符串
* @return 转换后的日期对象如果转换失败或输入为null则返回null
*/
public static Date parseDate(Object str) {
if (str == null) {
@ -115,6 +166,8 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
/**
* 获取服务器启动时间
*
* @return 服务器启动时间的 Date 对象表示
*/
public static Date getServerStartDate() {
long time = ManagementFactory.getRuntimeMXBean().getStartTime();
@ -122,35 +175,66 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
}
/**
* 计算相差天数
* 计算两个日期之间的天数差(以毫秒为单位)
*
* @param date1 第一个日期
* @param date2 第二个日期
* @return 两个日期之间的天数差的绝对值
*/
public static int differentDaysByMillisecond(Date date1, Date date2) {
return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
}
/**
* 计算两个时间差
* 计算两个日期之间的时间差,并以天、小时和分钟的格式返回
*
* @param endDate 结束日期
* @param nowDate 当前日期
* @return 表示时间差的字符串,格式为"天 小时 分钟"
*/
public static String getDatePoor(Date endDate, Date nowDate) {
long nd = 1000 * 24 * 60 * 60;
long nh = 1000 * 60 * 60;
long nm = 1000 * 60;
// long ns = 1000;
// 获得两个时间的毫秒时间差异
long diff = endDate.getTime() - nowDate.getTime();
// 计算差多少天
long day = diff / nd;
// 计算差多少小时
long hour = diff % nd / nh;
// 计算差多少分钟
long min = diff % nd % nh / nm;
// 计算差多少秒//输出结果
// long sec = diff % nd % nh % nm / ns;
return day + "" + hour + "小时" + min + "分钟";
long diffInMillis = endDate.getTime() - nowDate.getTime();
long day = TimeUnit.MILLISECONDS.toDays(diffInMillis);
long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24;
long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60;
return String.format("%d天 %d小时 %d分钟", day, hour, min);
}
/**
* 增加 LocalDateTime ==> Date
* 计算两个时间点的差值天、小时、分钟、秒当值为0时不显示该单位
*
* @param endDate 结束时间
* @param nowDate 当前时间
* @return 时间差字符串,格式为 "x天 x小时 x分钟 x秒",若为 0 则不显示
*/
public static String getTimeDifference(Date endDate, Date nowDate) {
long diffInMillis = endDate.getTime() - nowDate.getTime();
long day = TimeUnit.MILLISECONDS.toDays(diffInMillis);
long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24;
long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60;
long sec = TimeUnit.MILLISECONDS.toSeconds(diffInMillis) % 60;
// 构建时间差字符串条件是值不为0才显示
StringBuilder result = new StringBuilder();
if (day > 0) {
result.append(String.format("%d天 ", day));
}
if (hour > 0) {
result.append(String.format("%d小时 ", hour));
}
if (min > 0) {
result.append(String.format("%d分钟 ", min));
}
if (sec > 0) {
result.append(String.format("%d秒", sec));
}
return result.length() > 0 ? result.toString().trim() : "0秒";
}
/**
* 将 LocalDateTime 对象转换为 Date 对象
*
* @param temporalAccessor 要转换的 LocalDateTime 对象
* @return 转换后的 Date 对象
*/
public static Date toDate(LocalDateTime temporalAccessor) {
ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
@ -158,11 +242,46 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
}
/**
* 增加 LocalDate ==> Date
* LocalDate 对象转换为 Date 对象
*
* @param temporalAccessor 要转换的 LocalDate 对象
* @return 转换后的 Date 对象
*/
public static Date toDate(LocalDate temporalAccessor) {
LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
return Date.from(zdt.toInstant());
}
/**
* 校验日期范围
*
* @param startDate 开始日期
* @param endDate 结束日期
* @param maxValue 最大时间跨度的限制值
* @param unit 时间跨度的单位,可选择 "DAYS"、"HOURS" 或 "MINUTES"
*/
public static void validateDateRange(Date startDate, Date endDate, int maxValue, TimeUnit unit) {
// 校验结束日期不能早于开始日期
if (endDate.before(startDate)) {
throw new ServiceException("结束日期不能早于开始日期");
}
// 计算时间跨度
long diffInMillis = endDate.getTime() - startDate.getTime();
// 根据单位转换时间跨度
long diff = switch (unit) {
case DAYS -> TimeUnit.MILLISECONDS.toDays(diffInMillis);
case HOURS -> TimeUnit.MILLISECONDS.toHours(diffInMillis);
case MINUTES -> TimeUnit.MILLISECONDS.toMinutes(diffInMillis);
default -> throw new IllegalArgumentException("不支持的时间单位");
};
// 校验时间跨度不超过最大限制
if (diff > maxValue) {
throw new ServiceException("最大时间跨度为 " + maxValue + " " + unit.toString().toLowerCase());
}
}
}

View File

@ -0,0 +1,60 @@
package org.dromara.common.core.utils;
import cn.hutool.core.util.ObjectUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.function.Function;
/**
* 对象工具类
*
* @author 秋辞未寒
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ObjectUtils extends ObjectUtil {
/**
* 如果对象不为空,则获取对象中的某个字段 ObjectUtils.notNullGetter(user, User::getName);
*
* @param obj 对象
* @param func 获取方法
* @return 对象字段
*/
public static <T, E> E notNullGetter(T obj, Function<T, E> func) {
if (isNotNull(obj) && isNotNull(func)) {
return func.apply(obj);
}
return null;
}
/**
* 如果对象不为空,则获取对象中的某个字段,否则返回默认值
*
* @param obj 对象
* @param func 获取方法
* @param defaultValue 默认值
* @return 对象字段
*/
public static <T, E> E notNullGetter(T obj, Function<T, E> func, E defaultValue) {
if (isNotNull(obj) && isNotNull(func)) {
return func.apply(obj);
}
return defaultValue;
}
/**
* 如果值不为空,则返回值,否则返回默认值
*
* @param obj 对象
* @param defaultValue 默认值
* @return 对象字段
*/
public static <T> T notNull(T obj, T defaultValue) {
if (isNotNull(obj)) {
return obj;
}
return defaultValue;
}
}

View File

@ -25,7 +25,7 @@ import java.util.HashMap;
import java.util.Map;
/**
* 客户端工具类
* 客户端工具类,提供获取请求参数、响应处理、头部信息等常用操作
*
* @author ruoyi
*/
@ -33,52 +33,73 @@ import java.util.Map;
public class ServletUtils extends JakartaServletUtil {
/**
* 获取String参数
* 获取指定名称的 String 类型的请求参数
*
* @param name 参数名
* @return 参数值
*/
public static String getParameter(String name) {
return getRequest().getParameter(name);
}
/**
* 获取String参数
* 获取指定名称的 String 类型的请求参数,若参数不存在,则返回默认值
*
* @param name 参数名
* @param defaultValue 默认值
* @return 参数值或默认值
*/
public static String getParameter(String name, String defaultValue) {
return Convert.toStr(getRequest().getParameter(name), defaultValue);
}
/**
* 获取Integer参数
* 获取指定名称的 Integer 类型的请求参数
*
* @param name 参数名
* @return 参数值
*/
public static Integer getParameterToInt(String name) {
return Convert.toInt(getRequest().getParameter(name));
}
/**
* 获取Integer参数
* 获取指定名称的 Integer 类型的请求参数,若参数不存在,则返回默认值
*
* @param name 参数名
* @param defaultValue 默认值
* @return 参数值或默认值
*/
public static Integer getParameterToInt(String name, Integer defaultValue) {
return Convert.toInt(getRequest().getParameter(name), defaultValue);
}
/**
* 获取Boolean参数
* 获取指定名称的 Boolean 类型的请求参数
*
* @param name 参数名
* @return 参数值
*/
public static Boolean getParameterToBool(String name) {
return Convert.toBool(getRequest().getParameter(name));
}
/**
* 获取Boolean参数
* 获取指定名称的 Boolean 类型的请求参数,若参数不存在,则返回默认值
*
* @param name 参数名
* @param defaultValue 默认值
* @return 参数值或默认值
*/
public static Boolean getParameterToBool(String name, Boolean defaultValue) {
return Convert.toBool(getRequest().getParameter(name), defaultValue);
}
/**
* 获所有请求参数
* 获所有请求参数(以 Map 的形式返回)
*
* @param request 请求对象{@link ServletRequest}
* @return Map
* @return 请求参数的 Map键为参数名值为参数值数组
*/
public static Map<String, String[]> getParams(ServletRequest request) {
final Map<String, String[]> map = request.getParameterMap();
@ -86,10 +107,10 @@ public class ServletUtils extends JakartaServletUtil {
}
/**
* 获所有请求参数
* 获所有请求参数(以 Map 的形式返回,值为字符串形式的拼接)
*
* @param request 请求对象{@link ServletRequest}
* @return Map
* @return 请求参数的 Map键为参数名值为拼接后的字符串
*/
public static Map<String, String> getParamMap(ServletRequest request) {
Map<String, String> params = new HashMap<>();
@ -100,7 +121,9 @@ public class ServletUtils extends JakartaServletUtil {
}
/**
* 获取request
* 获取当前 HTTP 请求对象
*
* @return 当前 HTTP 请求对象
*/
public static HttpServletRequest getRequest() {
try {
@ -111,7 +134,9 @@ public class ServletUtils extends JakartaServletUtil {
}
/**
* 获取response
* 获取当前 HTTP 响应对象
*
* @return 当前 HTTP 响应对象
*/
public static HttpServletResponse getResponse() {
try {
@ -122,12 +147,25 @@ public class ServletUtils extends JakartaServletUtil {
}
/**
* 获取session
* 获取当前请求的 HttpSession 对象
* <p>
* 如果当前请求已经关联了一个会话(即已经存在有效的 session ID
* 则返回该会话对象;如果没有关联会话,则会创建一个新的会话对象并返回。
* <p>
* HttpSession 用于存储会话级别的数据,如用户登录信息、购物车内容等,
* 可以在多个请求之间共享会话数据
*
* @return 当前请求的 HttpSession 对象
*/
public static HttpSession getSession() {
return getRequest().getSession();
}
/**
* 获取当前请求的请求属性
*
* @return {@link ServletRequestAttributes} 请求属性对象
*/
public static ServletRequestAttributes getRequestAttributes() {
try {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
@ -137,6 +175,13 @@ public class ServletUtils extends JakartaServletUtil {
}
}
/**
* 获取指定请求头的值,如果头部为空则返回空字符串
*
* @param request 请求对象
* @param name 头部名称
* @return 头部值
*/
public static String getHeader(HttpServletRequest request, String name) {
String value = request.getHeader(name);
if (StringUtils.isEmpty(value)) {
@ -145,6 +190,12 @@ public class ServletUtils extends JakartaServletUtil {
return urlDecode(value);
}
/**
* 获取所有请求头的 Map键为头部名称值为头部值
*
* @param request 请求对象
* @return 请求头的 Map
*/
public static Map<String, String> getHeaders(HttpServletRequest request) {
Map<String, String> map = new LinkedCaseInsensitiveMap<>();
Enumeration<String> enumeration = request.getHeaderNames();
@ -159,7 +210,7 @@ public class ServletUtils extends JakartaServletUtil {
}
/**
* 将字符串渲染到客户端
* 将字符串渲染到客户端(以 JSON 格式返回)
*
* @param response 渲染对象
* @param string 待渲染的字符串
@ -176,37 +227,47 @@ public class ServletUtils extends JakartaServletUtil {
}
/**
* 是否是Ajax异步请求
* 判断当前请求是否为 Ajax 异步请求
*
* @param request
* @param request 请求对象
* @return 是否为 Ajax 请求
*/
public static boolean isAjaxRequest(HttpServletRequest request) {
// 判断 Accept 头部是否包含 application/json
String accept = request.getHeader("accept");
if (accept != null && accept.contains(MediaType.APPLICATION_JSON_VALUE)) {
return true;
}
// 判断 X-Requested-With 头部是否包含 XMLHttpRequest
String xRequestedWith = request.getHeader("X-Requested-With");
if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) {
return true;
}
// 判断 URI 后缀是否为 .json 或 .xml
String uri = request.getRequestURI();
if (StringUtils.equalsAnyIgnoreCase(uri, ".json", ".xml")) {
return true;
}
// 判断请求参数 __ajax 是否为 json 或 xml
String ajax = request.getParameter("__ajax");
return StringUtils.equalsAnyIgnoreCase(ajax, "json", "xml");
}
/**
* 获取客户端 IP 地址
*
* @return 客户端 IP 地址
*/
public static String getClientIP() {
return getClientIP(getRequest());
}
/**
* 内容编码
* 内容进行 URL 编码
*
* @param str 内容
* @return 编码后的内容
@ -216,7 +277,7 @@ public class ServletUtils extends JakartaServletUtil {
}
/**
* 内容解码
* 内容进行 URL 解码
*
* @param str 内容
* @return 解码后的内容

View File

@ -4,8 +4,6 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.util.AntPathMatcher;
import java.util.*;
@ -17,13 +15,16 @@ import java.util.stream.Collectors;
*
* @author Lion Li
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class StringUtils extends org.apache.commons.lang3.StringUtils {
public static final String SEPARATOR = ",";
public static final String SLASH = "/";
@Deprecated
private StringUtils() {
}
/**
* 获取参数不为空值
*
@ -320,4 +321,21 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
.collect(Collectors.toList());
}
/**
* 不区分大小写检查 CharSequence 是否以指定的前缀开头。
*
* @param str 要检查的 CharSequence 可能为 null
* @param prefixs 要查找的前缀可能为 null
* @return 是否包含
*/
public static boolean startWithAnyIgnoreCase(CharSequence str, CharSequence... prefixs) {
// 判断是否是以指定字符串开头
for (CharSequence prefix : prefixs) {
if (StringUtils.startsWithIgnoreCase(str, prefix)) {
return true;
}
}
return false;
}
}

View File

@ -14,18 +14,6 @@ import java.util.concurrent.*;
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Threads {
/**
* sleep等待,单位为毫秒
*/
public static void sleep(long milliseconds) {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
return;
}
}
/**
* 停止线程池
* 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.

View File

@ -5,11 +5,13 @@ import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.lang.tree.parser.NodeParser;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 扩展 hutool TreeUtil 封装系统树构建
@ -24,12 +26,71 @@ public class TreeBuildUtils extends TreeUtil {
*/
public static final TreeNodeConfig DEFAULT_CONFIG = TreeNodeConfig.DEFAULT_CONFIG.setNameKey("label");
/**
* 构建树形结构
*
* @param <T> 输入节点的类型
* @param <K> 节点ID的类型
* @param list 节点列表,其中包含了要构建树形结构的所有节点
* @param nodeParser 解析器,用于将输入节点转换为树节点
* @return 构建好的树形结构列表
*/
public static <T, K> List<Tree<K>> build(List<T> list, NodeParser<T, K> nodeParser) {
if (CollUtil.isEmpty(list)) {
return null;
return CollUtil.newArrayList();
}
K k = ReflectUtils.invokeGetter(list.get(0), "parentId");
return TreeUtil.build(list, k, DEFAULT_CONFIG, nodeParser);
}
/**
* 构建树形结构
*
* @param <T> 输入节点的类型
* @param <K> 节点ID的类型
* @param parentId 顶级节点
* @param list 节点列表,其中包含了要构建树形结构的所有节点
* @param nodeParser 解析器,用于将输入节点转换为树节点
* @return 构建好的树形结构列表
*/
public static <T, K> List<Tree<K>> build(List<T> list, K parentId, NodeParser<T, K> nodeParser) {
if (CollUtil.isEmpty(list)) {
return CollUtil.newArrayList();
}
return TreeUtil.build(list, parentId, DEFAULT_CONFIG, nodeParser);
}
/**
* 获取节点列表中所有节点的叶子节点
*
* @param <K> 节点ID的类型
* @param nodes 节点列表
* @return 包含所有叶子节点的列表
*/
public static <K> List<Tree<K>> getLeafNodes(List<Tree<K>> nodes) {
if (CollUtil.isEmpty(nodes)) {
return CollUtil.newArrayList();
}
return nodes.stream()
.flatMap(TreeBuildUtils::extractLeafNodes)
.collect(Collectors.toList());
}
/**
* 获取指定节点下的所有叶子节点
*
* @param <K> 节点ID的类型
* @param node 要查找叶子节点的根节点
* @return 包含所有叶子节点的列表
*/
private static <K> Stream<Tree<K>> extractLeafNodes(Tree<K> node) {
if (!node.hasChild()) {
return Stream.of(node);
} else {
// 递归调用,获取所有子节点的叶子节点
return node.getChildren().stream()
.flatMap(TreeBuildUtils::extractLeafNodes);
}
}
}

View File

@ -21,7 +21,8 @@ public final class RegexUtils extends ReUtil {
*/
public static String extractFromString(String input, String regex, String defaultInput) {
try {
return ReUtil.get(regex, input, 1);
String str = ReUtil.get(regex, input, 1);
return str == null ? defaultInput : str;
} catch (Exception e) {
return defaultInput;
}

View File

@ -15,7 +15,7 @@ public class SqlUtil {
/**
* 定义常用的 sql关键字
*/
public static final String SQL_REGEX = "select |insert |delete |update |drop |count |exec |chr |mid |master |truncate |char |and |declare ";
public static String SQL_REGEX = "\u000B|and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()";
/**
* 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)

View File

@ -0,0 +1,48 @@
package org.dromara.common.core.validate.enumd;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 自定义枚举校验
*
* @author 秋辞未寒
* @date 2024-12-09
*/
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Repeatable(EnumPattern.List.class) // 允许在同一元素上多次使用该注解
@Constraint(validatedBy = {EnumPatternValidator.class})
public @interface EnumPattern {
/**
* 需要校验的枚举类型
*/
Class<? extends Enum<?>> type();
/**
* 枚举类型校验值字段名称
* 需确保该字段实现了 getter 方法
*/
String fieldName();
String message() default "输入值不在枚举范围内";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@interface List {
EnumPattern[] value();
}
}

View File

@ -0,0 +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;
}
}

View File

@ -1,11 +1,11 @@
package org.dromara.common.encrypt.core;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.io.Resources;
import org.dromara.common.core.utils.ObjectUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.encrypt.annotation.EncryptField;
import org.springframework.context.ConfigurableApplicationContext;
@ -17,7 +17,10 @@ import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.util.ClassUtils;
import java.lang.reflect.Field;
import java.util.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@ -34,7 +37,7 @@ public class EncryptorManager {
/**
* 缓存加密器
*/
Map<EncryptContext, IEncryptor> encryptorMap = new ConcurrentHashMap<>();
Map<Integer, IEncryptor> encryptorMap = new ConcurrentHashMap<>();
/**
* 类加密字段缓存
@ -55,10 +58,7 @@ public class EncryptorManager {
* 获取类加密字段缓存
*/
public Set<Field> getFieldCache(Class<?> sourceClazz) {
if (ObjectUtil.isNotNull(fieldCache)) {
return fieldCache.get(sourceClazz);
}
return null;
return ObjectUtils.notNullGetter(fieldCache, f -> f.get(sourceClazz));
}
/**
@ -67,11 +67,12 @@ public class EncryptorManager {
* @param encryptContext 加密执行者需要的相关配置参数
*/
public IEncryptor registAndGetEncryptor(EncryptContext encryptContext) {
if (encryptorMap.containsKey(encryptContext)) {
return encryptorMap.get(encryptContext);
int key = encryptContext.hashCode();
if (encryptorMap.containsKey(key)) {
return encryptorMap.get(key);
}
IEncryptor encryptor = ReflectUtil.newInstance(encryptContext.getAlgorithm().getClazz(), encryptContext);
encryptorMap.put(encryptContext, encryptor);
encryptorMap.put(key, encryptor);
return encryptor;
}
@ -81,7 +82,7 @@ public class EncryptorManager {
* @param encryptContext 加密执行者需要的相关配置参数
*/
public void removeEncryptor(EncryptContext encryptContext) {
this.encryptorMap.remove(encryptContext);
this.encryptorMap.remove(encryptContext.hashCode());
}
/**

View File

@ -99,7 +99,7 @@ public class CryptoFilter implements Filter {
}
}
} catch (Exception e) {
throw new RuntimeException(e);
return null;
}
return null;
}

View File

@ -19,10 +19,12 @@ import java.util.Map;
* @author 老马
*/
public class EncryptUtils {
/**
* 公钥
*/
public static final String PUBLIC_KEY = "publicKey";
/**
* 私钥
*/
@ -51,7 +53,7 @@ public class EncryptUtils {
/**
* AES加密
*
* @param data 待密数据
* @param data 待密数据
* @param password 秘钥字符串
* @return 加密后字符串, 采用Base64编码
*/
@ -70,7 +72,7 @@ public class EncryptUtils {
/**
* AES加密
*
* @param data 待密数据
* @param data 待密数据
* @param password 秘钥字符串
* @return 加密后字符串, 采用Hex编码
*/
@ -208,7 +210,7 @@ public class EncryptUtils {
/**
* sm2私钥解密
*
* @param data 待密数据
* @param data 待密数据
* @param privateKey 私钥
* @return 解密后字符串
*/
@ -266,7 +268,7 @@ public class EncryptUtils {
/**
* rsa私钥解密
*
* @param data 待密数据
* @param data 待密数据
* @param privateKey 私钥
* @return 解密后字符串
*/

View File

@ -25,11 +25,6 @@
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
</dependency>
<dependency>
<artifactId>commons-compress</artifactId>
<groupId>org.apache.commons</groupId>
<version>1.26.2</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,24 @@
package org.dromara.common.excel.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
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;
/**
* 批注内容
*/
String value() default "";
}

View File

@ -0,0 +1,26 @@
package org.dromara.common.excel.annotation;
import org.apache.poi.ss.usermodel.IndexedColors;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
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;
/**
* 字体颜色
*/
IndexedColors fontColor() default IndexedColors.RED;
}

View File

@ -55,6 +55,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
* 下拉可选项
*/
private final List<DropDownOptions> dropDownOptions;
private final DictService dictService;
/**
* 当前单选进度
*/
@ -63,7 +64,6 @@ public class ExcelDownHandler implements SheetWriteHandler {
* 当前联动选择进度
*/
private int currentLinkedOptionsSheetIndex;
private final DictService dictService;
public ExcelDownHandler(List<DropDownOptions> options) {
this.dropDownOptions = options;
@ -139,8 +139,8 @@ public class ExcelDownHandler implements SheetWriteHandler {
} else if (everyOptions.getOptions().size() > 10) {
// 当一级选项参数个数大于10使用额外表的形式
dropDownWithSheet(helper, workbook, sheet, everyOptions.getIndex(), everyOptions.getOptions());
} else if (everyOptions.getOptions().size() != 0) {
// 当一级选项个数不为空,使用默认形式
} else {
// 否则使用默认形式
dropDownWithSimple(helper, sheet, everyOptions.getIndex(), everyOptions.getOptions());
}
});
@ -171,10 +171,24 @@ public class ExcelDownHandler implements SheetWriteHandler {
Sheet linkedOptionsDataSheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(linkedOptionsSheetName));
// 将下拉表隐藏
workbook.setSheetHidden(workbook.getSheetIndex(linkedOptionsDataSheet), true);
// 完善横向的一级选项数据
// 选项数据
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记载数据防止乱序
List<String> columnNames = new ArrayList<>();
// 写入第一行,即第一级的数据
Row firstRow = linkedOptionsDataSheet.createRow(0);
for (int columnIndex = 0; columnIndex < firstOptions.size(); columnIndex++) {
String columnName = firstOptions.get(columnIndex);
firstRow.createCell(columnIndex)
.setCellValue(columnName);
columnNames.add(columnName);
}
// 创建名称管理器
Name name = workbook.createName();
// 设置名称管理器的别名
@ -190,28 +204,12 @@ public class ExcelDownHandler implements SheetWriteHandler {
// 设置数据校验为序列模式,引用的是名称管理器中的别名
this.markOptionsToSheet(helper, sheet, options.getIndex(), helper.createFormulaListConstraint(linkedOptionsSheetName));
for (int columIndex = 0; columIndex < firstOptions.size(); columIndex++) {
// 先提取主表中一级下拉的列名
// 创建二级选项的名称管理器
for (int columIndex = 0; columIndex < columnNames.size(); columIndex++) {
// 列名
String firstOptionsColumnName = getExcelColumnName(columIndex);
// 一次循环是每一个一级选项
int finalI = columIndex;
// 本次循环的一级选项值
String thisFirstOptionsValue = firstOptions.get(columIndex);
// 创建第一行的数据
Optional.ofNullable(linkedOptionsDataSheet.getRow(0))
// 如果不存在则创建第一行
.orElseGet(() -> linkedOptionsDataSheet.createRow(finalI))
// 第一行当前列
.createCell(columIndex)
// 设置值为当前一级选项值
.setCellValue(thisFirstOptionsValue);
// 第二行开始,设置第二级别选项参数
List<String> secondOptions = secoundOptionsMap.get(thisFirstOptionsValue);
if (CollUtil.isEmpty(secondOptions)) {
// 必须保证至少有一个关联选项否则将导致Excel解析错误
secondOptions = Collections.singletonList("暂无_0");
}
// 对应的一级值
String thisFirstOptionsValue = columnNames.get(columIndex);
// 以该一级选项值创建子名称管理器
Name sonName = workbook.createName();
@ -222,7 +220,9 @@ public class ExcelDownHandler implements SheetWriteHandler {
linkedOptionsSheetName,
firstOptionsColumnName,
firstOptionsColumnName,
secondOptions.size() + 1
// 二级选项存在则设置为(选项个数+1)行否则设置为2行
Math.max(Optional.ofNullable(secoundOptionsMap.get(thisFirstOptionsValue))
.orElseGet(ArrayList::new).size(), 1) + 1
);
// 设置名称管理器的引用位置
sonName.setRefersToFormula(sonFunction);
@ -235,25 +235,51 @@ public class ExcelDownHandler implements SheetWriteHandler {
// 二级只能主表每一行的每一列添加二级校验
markLinkedOptionsToSheet(helper, sheet, i, options.getNextIndex(), helper.createFormulaListConstraint(secondOptionsFunction));
}
}
for (int rowIndex = 0; rowIndex < secondOptions.size(); rowIndex++) {
// 从第二行开始填充二级选项
int finalRowIndex = rowIndex + 1;
int finalColumIndex = columIndex;
Row row = Optional.ofNullable(linkedOptionsDataSheet.getRow(finalRowIndex))
// 没有则创建
.orElseGet(() -> linkedOptionsDataSheet.createRow(finalRowIndex));
Optional
// 在本级一级选项所在的列
.ofNullable(row.getCell(finalColumIndex))
// 不存在则创建
.orElseGet(() -> row.createCell(finalColumIndex))
// 设置二级选项值
.setCellValue(secondOptions.get(rowIndex));
// 将二级数据处理为按行区分
Map<Integer, List<String>> columnValueMap = new HashMap<>();
int currentRow = 1;
while (currentRow >= 0) {
boolean flag = false;
List<String> rowData = new ArrayList<>();
for (String columnName : columnNames) {
List<String> data = secoundOptionsMap.get(columnName);
if (CollUtil.isEmpty(data)) {
// 添加空字符串填充位置
rowData.add(" ");
continue;
}
// 取第一个
String str = data.get(0);
rowData.add(str);
// 通过移除的方式避免重复
data.remove(0);
// 设置可以继续
flag = true;
}
columnValueMap.put(currentRow, rowData);
// 可以继续,则增加行数,否则置为负数跳出循环
if (flag) {
currentRow++;
} else {
currentRow = -1;
}
}
// 填充第二级选项数据
columnValueMap.forEach((rowIndex, rowValues) -> {
Row row = linkedOptionsDataSheet.createRow(rowIndex);
for (int columnIndex = 0; columnIndex < rowValues.size(); columnIndex++) {
String rowValue = rowValues.get(columnIndex);
// 填充位置的部分不渲染
if (StrUtil.isNotBlank(rowValue)) {
row.createCell(columnIndex)
.setCellValue(rowValue);
}
}
});
currentLinkedOptionsSheetIndex++;
}

View File

@ -0,0 +1,135 @@
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 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;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
/**
* 批注、必填
*
* @author guzhouyanyu
*/
public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
/**
* 批注
*/
private final Map<Integer, String> notationMap;
/**
* 头列字体颜色
*/
private final Map<Integer, Short> headColumnMap;
public DataWriteHandler(Class<?> clazz) {
notationMap = getNotationMap(clazz);
headColumnMap = getRequiredMap(clazz);
}
@Override
public void afterCellDispose(CellWriteHandlerContext context) {
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()) {
Cell cell = context.getCell();
WriteSheetHolder writeSheetHolder = context.getWriteSheetHolder();
Sheet sheet = writeSheetHolder.getSheet();
Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
Drawing<?> drawing = sheet.createDrawingPatriarch();
// 设置标题字体样式
WriteFont headWriteFont = new WriteFont();
// 加粗
headWriteFont.setBold(true);
if (CollUtil.isNotEmpty(headColumnMap) && headColumnMap.containsKey(cell.getColumnIndex())) {
// 设置字体颜色
headWriteFont.setColor(headColumnMap.get(cell.getColumnIndex()));
}
writeCellStyle.setWriteFont(headWriteFont);
CellStyle cellStyle = StyleUtil.buildCellStyle(workbook, null, writeCellStyle);
cell.setCellStyle(cellStyle);
if (CollUtil.isNotEmpty(notationMap) && notationMap.containsKey(cell.getColumnIndex())) {
// 批注内容
String notationContext = notationMap.get(cell.getColumnIndex());
// 创建绘图对象
Comment comment = drawing.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), 0, (short) 5, 5));
comment.setString(new XSSFRichTextString(notationContext));
cell.setCellComment(comment);
}
}
}
/**
* 获取必填列
*/
private static Map<Integer, Short> getRequiredMap(Class<?> clazz) {
Map<Integer, 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];
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());
}
return requiredMap;
}
/**
* 获取批注
*/
private static Map<Integer, String> getNotationMap(Class<?> clazz) {
Map<Integer, 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];
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());
}
return notationMap;
}
}

View File

@ -18,6 +18,7 @@ import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.excel.convert.ExcelBigNumberConvert;
import org.dromara.common.excel.core.*;
import org.dromara.common.excel.handler.DataWriteHandler;
import java.io.IOException;
import java.io.InputStream;
@ -191,6 +192,7 @@ public class ExcelUtil {
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.registerWriteHandler(new DataWriteHandler(clazz))
.sheet(sheetName);
if (merge) {
// 合并处理器
@ -211,7 +213,7 @@ public class ExcelUtil {
* @param data 模板需要的数据
* @param response 响应体
*/
public static void exportTemplate(List<Object> data, String filename, String templatePath, HttpServletResponse response) {
public static <T> void exportTemplate(List<T> data, String filename, String templatePath, HttpServletResponse response) {
try {
resetResponse(filename, response);
ServletOutputStream os = response.getOutputStream();
@ -230,20 +232,21 @@ public class ExcelUtil {
* @param data 模板需要的数据
* @param os 输出流
*/
public static void exportTemplate(List<Object> data, String templatePath, OutputStream 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)
.withTemplate(templateResource.getStream())
.autoCloseStream(false)
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.registerWriteHandler(new DataWriteHandler(data.get(0).getClass()))
.build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
// 单表多数据导出 模板格式为 {.属性}
for (Object d : data) {
for (T d : data) {
excelWriter.fill(d, writeSheet);
}
excelWriter.finish();
@ -299,6 +302,9 @@ 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)
.withTemplate(templateResource.getStream())
@ -307,9 +313,6 @@ public class ExcelUtil {
.registerConverter(new ExcelBigNumberConvert())
.build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
for (Map.Entry<String, Object> map : data.entrySet()) {
// 设置列表后续还有数据
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
@ -333,6 +336,9 @@ 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)
.withTemplate(templateResource.getStream())
@ -340,9 +346,6 @@ public class ExcelUtil {
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.build();
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
for (int i = 0; i < data.size(); i++) {
WriteSheet writeSheet = EasyExcel.writerSheet(i).build();
for (Map.Entry<String, Object> map : data.get(i).entrySet()) {

View File

@ -32,7 +32,7 @@ public class BigNumberSerializer extends NumberSerializer {
@Override
public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException {
// 超出范围 序列化字符串
// 超出范围 序列化字符串
if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) {
super.serialize(value, gen, provider);
} else {

View File

@ -100,7 +100,7 @@ public class LogAspect {
if (e != null) {
operLog.setStatus(BusinessStatus.FAIL.ordinal());
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 3800));
}
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
@ -113,13 +113,12 @@ public class LogAspect {
// 设置消耗时间
StopWatch stopWatch = KEY_CACHE.get();
stopWatch.stop();
operLog.setCostTime(stopWatch.getTime());
operLog.setCostTime(stopWatch.getDuration().toMillis());
// 发布事件保存数据库
SpringUtils.context().publishEvent(operLog);
} catch (Exception exp) {
// 记录本地异常日志
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
} finally {
KEY_CACHE.remove();
}
@ -146,7 +145,7 @@ public class LogAspect {
}
// 是否需要保存response参数和值
if (log.isSaveResponseData() && ObjectUtil.isNotNull(jsonResult)) {
operLog.setJsonResult(StringUtils.substring(JsonUtils.toJsonString(jsonResult), 0, 2000));
operLog.setJsonResult(StringUtils.substring(JsonUtils.toJsonString(jsonResult), 0, 3800));
}
}
@ -159,14 +158,13 @@ public class LogAspect {
private void setRequestValue(JoinPoint joinPoint, OperLogEvent operLog, String[] excludeParamNames) throws Exception {
Map<String, String> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
String requestMethod = operLog.getRequestMethod();
if (MapUtil.isEmpty(paramsMap)
&& HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
if (MapUtil.isEmpty(paramsMap) && StringUtils.equalsAny(requestMethod, HttpMethod.PUT.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name())) {
String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
operLog.setOperParam(StringUtils.substring(params, 0, 3800));
} else {
MapUtil.removeAny(paramsMap, EXCLUDE_PROPERTIES);
MapUtil.removeAny(paramsMap, excludeParamNames);
operLog.setOperParam(StringUtils.substring(JsonUtils.toJsonString(paramsMap), 0, 2000));
operLog.setOperParam(StringUtils.substring(JsonUtils.toJsonString(paramsMap), 0, 3800));
}
}

View File

@ -43,7 +43,13 @@ public class MailProperties {
private String pass;
/**
* 发送方遵循RFC-822标准
* 发送方遵循RFC-822标准<br>
* 发件人可以是以下形式:
*
* <pre>
* 1. user@xxx.xx
* 2. name &lt;user@xxx.xx&gt;
* </pre>
*/
private String from;

View File

@ -37,6 +37,11 @@
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
</dependency>
<!-- sql性能分析插件 -->
<dependency>
<groupId>p6spy</groupId>

View File

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

View File

@ -2,6 +2,7 @@ package org.dromara.common.mybatis.config;
import cn.hutool.core.net.NetUtil;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
@ -10,8 +11,10 @@ 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.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;
@ -54,6 +57,14 @@ public class MybatisPlusConfig {
return new PlusDataPermissionInterceptor(SpringUtils.getProperty("mybatis-plus.mapperPackage"));
}
/**
* 数据权限切面处理器
*/
@Bean
public DataPermissionAspect dataPermissionAspect() {
return new DataPermissionAspect();
}
/**
* 分页插件,自动识别数据库类型
*/
@ -96,6 +107,14 @@ public class MybatisPlusConfig {
return new MybatisExceptionHandler();
}
/**
* 初始化表对象处理器
*/
@Bean
public PostInitTableInfoHandler postInitTableInfoHandler() {
return new PlusPostInitTableInfoHandler();
}
/**
* PaginationInnerInterceptor 分页插件,自动识别数据库类型
* https://baomidou.com/pages/97710a/

View File

@ -18,9 +18,7 @@ import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 自定义 Mapper 接口, 实现 自定义扩展
@ -69,9 +67,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @return 插入操作是否成功的布尔值
*/
default boolean insertBatch(Collection<T> entityList) {
Db.saveBatch(entityList);
// 临时解决 新版本 mp 插入状态判断错误问题
return true;
return Db.saveBatch(entityList);
}
/**
@ -81,9 +77,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @return 更新操作是否成功的布尔值
*/
default boolean updateBatchById(Collection<T> entityList) {
Db.updateBatchById(entityList);
// 临时解决 新版本 mp 插入状态判断错误问题
return true;
return Db.updateBatchById(entityList);
}
/**
@ -93,9 +87,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @return 插入或更新操作是否成功的布尔值
*/
default boolean insertOrUpdateBatch(Collection<T> entityList) {
Db.saveOrUpdateBatch(entityList);
// 临时解决 新版本 mp 插入状态判断错误问题
return true;
return Db.saveOrUpdateBatch(entityList);
}
/**
@ -106,9 +98,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @return 插入操作是否成功的布尔值
*/
default boolean insertBatch(Collection<T> entityList, int batchSize) {
Db.saveBatch(entityList, batchSize);
// 临时解决 新版本 mp 插入状态判断错误问题
return true;
return Db.saveBatch(entityList, batchSize);
}
/**
@ -119,9 +109,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @return 更新操作是否成功的布尔值
*/
default boolean updateBatchById(Collection<T> entityList, int batchSize) {
Db.updateBatchById(entityList, batchSize);
// 临时解决 新版本 mp 插入状态判断错误问题
return true;
return Db.updateBatchById(entityList, batchSize);
}
/**
@ -132,9 +120,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @return 插入或更新操作是否成功的布尔值
*/
default boolean insertOrUpdateBatch(Collection<T> entityList, int batchSize) {
Db.saveOrUpdateBatch(entityList, batchSize);
// 临时解决 新版本 mp 插入状态判断错误问题
return true;
return Db.saveOrUpdateBatch(entityList, batchSize);
}
/**
@ -169,8 +155,8 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @param idList 主键ID集合
* @return 查询到的VO对象列表
*/
default List<V> selectVoBatchIds(Collection<? extends Serializable> idList) {
return selectVoBatchIds(idList, this.currentVoClass());
default List<V> selectVoByIds(Collection<? extends Serializable> idList) {
return selectVoByIds(idList, this.currentVoClass());
}
/**
@ -181,8 +167,8 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @param <C> VO类的类型
* @return 查询到的VO对象列表经过转换为指定的VO类后返回
*/
default <C> List<C> selectVoBatchIds(Collection<? extends Serializable> idList, Class<C> voClass) {
List<T> list = this.selectBatchIds(idList);
default <C> List<C> selectVoByIds(Collection<? extends Serializable> idList, Class<C> voClass) {
List<T> list = this.selectByIds(idList);
if (CollUtil.isEmpty(list)) {
return CollUtil.newArrayList();
}

View File

@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
@ -113,8 +114,14 @@ public class PageQuery implements Serializable {
return list;
}
@JsonIgnore
public Integer getFirstNum() {
return (pageNum - 1) * pageSize;
}
public PageQuery(Integer pageSize, Integer pageNum) {
this.pageSize = pageSize;
this.pageNum = pageNum;
}
}

View File

@ -50,6 +50,8 @@ public class TableDataInfo<T> implements Serializable {
public TableDataInfo(List<T> list, long total) {
this.rows = list;
this.total = total;
this.code = HttpStatus.HTTP_OK;
this.msg = "查询成功";
}
/**

View File

@ -48,7 +48,12 @@ public enum DataScopeType {
/**
* 仅本人数据权限
*/
SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 ");
SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 "),
/**
* 部门及以下或本人数据权限
*/
DEPT_AND_CHILD_OR_SELF("6", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} ) OR #{#userName} = #{#user.userId} ", " 1 = 0 ");
private final String code;

View File

@ -7,6 +7,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.ObjectUtils;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.satoken.utils.LoginHelper;
@ -31,8 +32,7 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
try {
if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
// 获取当前时间作为创建时间和更新时间,如果创建时间不为空,则使用创建时间,否则使用当前时间
Date current = ObjectUtil.isNotNull(baseEntity.getCreateTime())
? baseEntity.getCreateTime() : new Date();
Date current = ObjectUtils.notNull(baseEntity.getCreateTime(), new Date());
baseEntity.setCreateTime(current);
baseEntity.setUpdateTime(current);
@ -44,8 +44,7 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
// 填充创建人、更新人和创建部门信息
baseEntity.setCreateBy(userId);
baseEntity.setUpdateBy(userId);
baseEntity.setCreateDept(ObjectUtil.isNotNull(baseEntity.getCreateDept())
? baseEntity.getCreateDept() : loginUser.getDeptId());
baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), loginUser.getDeptId()));
}
}
} else {

View File

@ -3,11 +3,12 @@ package org.dromara.common.mybatis.handler;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import org.apache.ibatis.io.Resources;
import org.dromara.common.core.domain.dto.RoleDTO;
@ -28,19 +29,13 @@ import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.*;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.ClassUtils;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
@ -54,7 +49,7 @@ import java.util.function.Function;
public class PlusDataPermissionHandler {
/**
* 方法或类(名称) 与 注解的映射关系缓存
* 类名称与注解的映射关系缓存(由于aop无法拦截mybatis接口类上的注解 只能通过启动预扫描的方式进行)
*/
private final Map<String, DataPermission> dataPermissionCacheMap = new ConcurrentHashMap<>();
@ -86,27 +81,27 @@ public class PlusDataPermissionHandler {
* @return 数据过滤条件的 SQL 片段
*/
public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) {
// 获取数据权限配置
DataPermission dataPermission = getDataPermission(mappedStatementId);
// 获取当前登录用户信息
LoginUser currentUser = DataPermissionHelper.getVariable("user");
if (ObjectUtil.isNull(currentUser)) {
currentUser = LoginHelper.getLoginUser();
DataPermissionHelper.setVariable("user", currentUser);
}
// 如果是超级管理员或租户管理员,则不过滤数据
if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
return where;
}
// 构造数据过滤条件的 SQL 片段
String dataFilterSql = buildDataFilter(dataPermission, isSelect);
if (StringUtils.isBlank(dataFilterSql)) {
return where;
}
try {
// 获取数据权限配置
DataPermission dataPermission = getDataPermission(mappedStatementId);
// 获取当前登录用户信息
LoginUser currentUser = DataPermissionHelper.getVariable("user");
if (ObjectUtil.isNull(currentUser)) {
currentUser = LoginHelper.getLoginUser();
DataPermissionHelper.setVariable("user", currentUser);
}
// 如果是超级管理员或租户管理员,则不过滤数据
if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
return where;
}
// 构造数据过滤条件的 SQL 片段
String dataFilterSql = buildDataFilter(dataPermission, isSelect);
if (StringUtils.isBlank(dataFilterSql)) {
return where;
}
Expression expression = CCJSqlParserUtil.parseExpression(dataFilterSql);
// 数据权限使用单独的括号 防止与其他条件冲突
Parenthesis parenthesis = new Parenthesis(expression);
ParenthesedExpressionList<Expression> parenthesis = new ParenthesedExpressionList<>(expression);
if (ObjectUtil.isNotNull(where)) {
return new AndExpression(where, parenthesis);
} else {
@ -114,6 +109,8 @@ public class PlusDataPermissionHandler {
}
} catch (JSQLParserException e) {
throw new ServiceException("数据权限解析异常 => " + e.getMessage());
} finally {
DataPermissionHelper.removePermission();
}
}
@ -132,10 +129,33 @@ public class PlusDataPermissionHandler {
joinStr = " " + dataPermission.joinStr() + " ";
}
LoginUser user = DataPermissionHelper.getVariable("user");
StandardEvaluationContext context = new StandardEvaluationContext();
Object defaultValue = "-1";
NullSafeStandardEvaluationContext context = new NullSafeStandardEvaluationContext(defaultValue);
context.addPropertyAccessor(new NullSafePropertyAccessor(context.getPropertyAccessors().get(0), defaultValue));
context.setBeanResolver(beanResolver);
DataPermissionHelper.getContext().forEach(context::setVariable);
Set<String> conditions = new HashSet<>();
// 优先设置变量
List<String> keys = new ArrayList<>();
Map<DataColumn, Boolean> ignoreMap = new HashMap<>();
for (DataColumn dataColumn : dataPermission.value()) {
if (dataColumn.key().length != dataColumn.value().length) {
throw new ServiceException("角色数据范围异常 => key与value长度不匹配");
}
// 包含权限标识符 这直接跳过
if (StringUtils.isNotBlank(dataColumn.permission()) &&
CollUtil.contains(user.getMenuPermission(), dataColumn.permission())
) {
ignoreMap.put(dataColumn, Boolean.TRUE);
continue;
}
// 设置注解变量 key 为表达式变量 value 为变量值
for (int i = 0; i < dataColumn.key().length; i++) {
context.setVariable(dataColumn.key()[i], dataColumn.value()[i]);
}
keys.addAll(Arrays.stream(dataColumn.key()).map(key -> "#" + key).toList());
}
for (RoleDTO role : user.getRoles()) {
user.setRoleId(role.getRoleId());
// 获取角色权限泛型
@ -145,33 +165,30 @@ public class PlusDataPermissionHandler {
}
// 全部数据权限直接返回
if (type == DataScopeType.ALL) {
return "";
return StringUtils.EMPTY;
}
boolean isSuccess = false;
for (DataColumn dataColumn : dataPermission.value()) {
if (dataColumn.key().length != dataColumn.value().length) {
throw new ServiceException("角色数据范围异常 => key与value长度不匹配");
}
// 不包含 key 变量 则不处理
if (!StringUtils.containsAny(type.getSqlTemplate(),
Arrays.stream(dataColumn.key()).map(key -> "#" + key).toArray(String[]::new)
)) {
continue;
}
// 包含权限标识符 这直接跳过
if (StringUtils.isNotBlank(dataColumn.permission()) &&
CollUtil.contains(user.getMenuPermission(), dataColumn.permission())
) {
if (ignoreMap.containsKey(dataColumn)) {
// 修复多角色与权限标识符共用问题 https://gitee.com/dromara/RuoYi-Vue-Plus/issues/IB4CS4
conditions.add(joinStr + " 1 = 1 ");
isSuccess = true;
continue;
}
// 设置注解变量 key 为表达式变量 value 为变量值
for (int i = 0; i < dataColumn.key().length; i++) {
context.setVariable(dataColumn.key()[i], dataColumn.value()[i]);
// 不包含 key 变量 则不处理
if (!StringUtils.containsAny(type.getSqlTemplate(), keys.toArray(String[]::new))) {
continue;
}
// 当前注解不满足模板 不处理
if (!StringUtils.containsAny(type.getSqlTemplate(), dataColumn.key())) {
continue;
}
// 忽略数据权限 防止spel表达式内有其他sql查询导致死循环调用
String sql = DataPermissionHelper.ignore(() ->
parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class)
);
// 解析sql模板并填充
String sql = parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class);
conditions.add(joinStr + sql);
isSuccess = true;
}
@ -185,7 +202,7 @@ public class PlusDataPermissionHandler {
String sql = StreamUtils.join(conditions, Function.identity(), "");
return sql.substring(joinStr.length());
}
return "";
return StringUtils.EMPTY;
}
/**
@ -212,7 +229,10 @@ public class PlusDataPermissionHandler {
// 获取资源对应的类对象
Class<?> clazz = Resources.classForName(classMetadata.getClassName());
// 查找类中的特定注解
findAnnotation(clazz);
if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) {
DataPermission dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class);
dataPermissionCacheMap.put(clazz.getName(), dataPermission);
}
}
}
} catch (Exception e) {
@ -220,29 +240,6 @@ public class PlusDataPermissionHandler {
}
}
/**
* 在指定的类中查找特定的注解 DataPermission并将带有这个注解的方法或类存储到 dataPermissionCacheMap 中
*
* @param clazz 要查找的类
*/
private void findAnnotation(Class<?> clazz) {
DataPermission dataPermission;
for (Method method : clazz.getMethods()) {
if (method.isDefault() || method.isVarArgs()) {
continue;
}
String mappedStatementId = clazz.getName() + "." + method.getName();
if (AnnotationUtil.hasAnnotation(method, DataPermission.class)) {
dataPermission = AnnotationUtil.getAnnotation(method, DataPermission.class);
dataPermissionCacheMap.put(mappedStatementId, dataPermission);
}
}
if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) {
dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class);
dataPermissionCacheMap.put(clazz.getName(), dataPermission);
}
}
/**
* 根据映射语句 ID 或类名获取对应的 DataPermission 注解对象
*
@ -250,9 +247,9 @@ public class PlusDataPermissionHandler {
* @return DataPermission 注解对象,如果不存在则返回 null
*/
public DataPermission getDataPermission(String mapperId) {
// 检查缓存中是否包含映射语句 ID 对应的 DataPermission 注解对象
if (dataPermissionCacheMap.containsKey(mapperId)) {
return dataPermissionCacheMap.get(mapperId);
// 检查上下文中是否包含映射语句 ID 对应的 DataPermission 注解对象
if (DataPermissionHelper.getPermission() != null) {
return DataPermissionHelper.getPermission();
}
// 如果缓存中不包含映射语句 ID 对应的 DataPermission 注解对象,则尝试使用类名作为键查找
String clazzName = mapperId.substring(0, mapperId.lastIndexOf("."));
@ -271,4 +268,65 @@ public class PlusDataPermissionHandler {
public boolean invalid(String mapperId) {
return getDataPermission(mapperId) == null;
}
/**
* 对所有null变量找不到的变量返回默认值
*/
@AllArgsConstructor
private static class NullSafeStandardEvaluationContext extends StandardEvaluationContext {
private final Object defaultValue;
@Override
public Object lookupVariable(String name) {
Object obj = super.lookupVariable(name);
// 如果读取到的值是 null则返回默认值
if (obj == null) {
return defaultValue;
}
return obj;
}
}
/**
* 对所有null变量找不到的变量返回默认值 委托模式 将不需要处理的方法委托给原处理器
*/
@AllArgsConstructor
private static class NullSafePropertyAccessor implements PropertyAccessor {
private final PropertyAccessor delegate;
private final Object defaultValue;
@Override
public Class<?>[] getSpecificTargetClasses() {
return delegate.getSpecificTargetClasses();
}
@Override
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
return delegate.canRead(context, target, name);
}
@Override
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
TypedValue value = delegate.read(context, target, name);
// 如果读取到的值是 null则返回默认值
if (value.getValue() == null) {
return new TypedValue(defaultValue);
}
return value;
}
@Override
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
return delegate.canWrite(context, target, name);
}
@Override
public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
delegate.write(context, target, name, newValue);
}
}
}

View File

@ -0,0 +1,27 @@
package org.dromara.common.mybatis.handler;
import cn.hutool.core.convert.Convert;
import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import org.apache.ibatis.session.Configuration;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.reflect.ReflectUtils;
/**
* 修改表信息初始化方式
* 目前用于全局修改是否使用逻辑删除
*
* @author Lion Li
*/
public class PlusPostInitTableInfoHandler implements PostInitTableInfoHandler {
@Override
public void postTableInfo(TableInfo tableInfo, Configuration configuration) {
String flag = SpringUtils.getProperty("mybatis-plus.enableLogicDelete", "true");
// 只有关闭时 统一设置false 为true时mp自动判断不处理
if (!Convert.toBool(flag)) {
ReflectUtils.setFieldValue(tableInfo, "withLogicDelete", false);
}
}
}

View File

@ -62,8 +62,8 @@ public class DataBaseHelper {
// charindex(',100,' , ',0,100,101,') <> 0
return "charindex(',%s,' , ','+%s+',') <> 0".formatted(var, var2);
} else if (dataBasyType == DataBaseType.POSTGRE_SQL) {
// (select position(',100,' in ',0,100,101,')) <> 0
return "(select position(',%s,' in ','||%s||',')) <> 0".formatted(var, var2);
// (select strpos(',0,100,101,' , ',100,')) <> 0
return "(select strpos(','||%s||',' , ',%s,')) <> 0".formatted(var2, var);
} else if (dataBasyType == DataBaseType.ORACLE) {
// instr(',0,100,101,' , ',100,') <> 0
return "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var);

View File

@ -9,6 +9,7 @@ import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.mybatis.annotation.DataPermission;
import java.util.HashMap;
import java.util.Map;
@ -29,6 +30,33 @@ public class DataPermissionHelper {
private static final ThreadLocal<Stack<Integer>> REENTRANT_IGNORE = ThreadLocal.withInitial(Stack::new);
private static final ThreadLocal<DataPermission> PERMISSION_CACHE = new ThreadLocal<>();
/**
* 获取当前执行mapper权限注解
*
* @return 返回当前执行mapper权限注解
*/
public static DataPermission getPermission() {
return PERMISSION_CACHE.get();
}
/**
* 设置当前执行mapper权限注解
*
* @param dataPermission 数据权限注解
*/
public static void setPermission(DataPermission dataPermission) {
PERMISSION_CACHE.set(dataPermission);
}
/**
* 删除当前执行mapper权限注解
*/
public static void removePermission() {
PERMISSION_CACHE.remove();
}
/**
* 从上下文中获取指定键的变量值,并将其转换为指定的类型
*

View File

@ -24,8 +24,8 @@ mybatis-plus:
# 主键类型
# AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID
idType: ASSIGN_ID
# 逻辑已删除值(框架表均使用此值 禁止随意修改)
logicDeleteValue: 2
# 逻辑已删除值(可按需求随意修改)
logicDeleteValue: 1
# 逻辑未删除值
logicNotDeleteValue: 0
insertStrategy: NOT_NULL

View File

@ -9,21 +9,18 @@ import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.oss.constant.OssConstant;
import org.dromara.common.oss.entity.UploadResult;
import org.dromara.common.oss.enumd.AccessPolicyType;
import org.dromara.common.oss.enumd.PolicyType;
import org.dromara.common.oss.exception.OssException;
import org.dromara.common.oss.properties.OssProperties;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.core.async.AsyncRequestBody;
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.crt.S3CrtHttpConfiguration;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.NoSuchBucketException;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.*;
@ -35,6 +32,7 @@ import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.function.Consumer;
/**
* S3 存储协议 所有兼容S3协议的云厂商均支持
@ -83,10 +81,10 @@ public class OssClient {
StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey()));
//MinIO 使用 HTTPS 限制使用域名访问,站点填域名。需要启用路径样式访问
// MinIO 使用 HTTPS 限制使用域名访问,站点填域名。需要启用路径样式访问
boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE);
//创建AWS基于 CRT 的 S3 客户端
// 创建AWS基于 CRT 的 S3 客户端
this.client = S3AsyncClient.crtBuilder()
.credentialsProvider(credentialsProvider)
.endpointOverride(URI.create(getEndpoint()))
@ -95,6 +93,9 @@ public class OssClient {
.minimumPartSizeInBytes(10 * 1025 * 1024L)
.checksumValidationEnabled(false)
.forcePathStyle(isStyle)
.httpConfiguration(S3CrtHttpConfiguration.builder()
.connectionTimeout(Duration.ofSeconds(60)) // 设置连接超时
.build())
.build();
//AWS基于 CRT 的 S3 AsyncClient 实例用作 S3 传输管理器的底层客户端
@ -112,8 +113,6 @@ public class OssClient {
.serviceConfiguration(config)
.build();
// 创建存储桶
createBucket();
} catch (Exception e) {
if (e instanceof OssException) {
throw e;
@ -122,43 +121,6 @@ public class OssClient {
}
}
/**
* 同步创建存储桶
* 如果存储桶不存在,会进行创建;如果存储桶存在,不执行任何操作
*
* @throws OssException 当创建存储桶时发生异常时抛出
*/
public void createBucket() {
String bucketName = properties.getBucketName();
try {
// 尝试获取存储桶的信息
client.headBucket(
x -> x.bucket(bucketName)
.build())
.join();
} catch (Exception ex) {
if (ex.getCause() instanceof NoSuchBucketException) {
try {
// 存储桶不存在,尝试创建存储桶
client.createBucket(
x -> x.bucket(bucketName))
.join();
// 设置存储桶的访问策略Bucket Policy
client.putBucketPolicy(
x -> x.bucket(bucketName)
.policy(getPolicy(bucketName, getAccessPolicy().getPolicyType())))
.join();
} catch (S3Exception e) {
// 存储桶创建或策略设置失败
throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]");
}
} else {
throw new OssException("判断Bucket是否存在失败请核对配置信息:[" + ex.getMessage() + "]");
}
}
}
/**
* 上传文件到 Amazon S3并返回上传结果
*
@ -178,7 +140,9 @@ public class OssClient {
.key(key)
.contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null)
.contentType(contentType)
.acl(getAccessPolicy().getObjectCannedACL())
// 用于设置对象的访问控制列表ACL。不同云厂商对ACL的支持和实现方式有所不同
// 因此根据具体的云服务提供商你可能需要进行不同的配置自行开启阿里云有acl权限配置腾讯云没有acl权限配置
//.acl(getAccessPolicy().getObjectCannedACL())
.build())
.addTransferListener(LoggingTransferListener.create())
.source(filePath).build());
@ -215,7 +179,10 @@ public class OssClient {
}
try {
// 创建异步请求体length如果为空会报错
BlockingInputStreamAsyncRequestBody body = AsyncRequestBody.forBlockingInputStream(length);
BlockingInputStreamAsyncRequestBody body = BlockingInputStreamAsyncRequestBody.builder()
.contentLength(length)
.subscribeTimeout(Duration.ofSeconds(30))
.build();
// 使用 transferManager 进行上传
Upload upload = transferManager.upload(
@ -224,7 +191,9 @@ public class OssClient {
y -> y.bucket(properties.getBucketName())
.key(key)
.contentType(contentType)
.acl(getAccessPolicy().getObjectCannedACL())
// 用于设置对象的访问控制列表ACL。不同云厂商对ACL的支持和实现方式有所不同
// 因此根据具体的云服务提供商你可能需要进行不同的配置自行开启阿里云有acl权限配置腾讯云没有acl权限配置
//.acl(getAccessPolicy().getObjectCannedACL())
.build())
.build());
@ -271,10 +240,11 @@ public class OssClient {
*
* @param key 文件在 Amazon S3 中的对象键
* @param out 输出流
* @param consumer 自定义处理逻辑
* @return 输出流中写入的字节数(长度)
* @throws OssException 如果下载失败,抛出自定义异常
*/
public long download(String key, OutputStream out) {
public void download(String key, OutputStream out, Consumer<Long> consumer) {
try {
// 构建下载请求
DownloadRequest<ResponseInputStream<GetObjectResponse>> downloadRequest = DownloadRequest.builder()
@ -290,7 +260,10 @@ public class OssClient {
Download<ResponseInputStream<GetObjectResponse>> responseFuture = transferManager.download(downloadRequest);
// 输出到流中
try (ResponseInputStream<GetObjectResponse> responseStream = responseFuture.completionFuture().join().result()) { // auto-closeable stream
return responseStream.transferTo(out); // 阻塞调用线程 blocks the calling thread
if (consumer != null) {
consumer.accept(responseStream.response().contentLength());
}
responseStream.transferTo(out); // 阻塞调用线程 blocks the calling thread
}
} catch (Exception e) {
throw new OssException("文件下载失败,错误信息:[" + e.getMessage() + "]");
@ -316,13 +289,13 @@ public class OssClient {
/**
* 获取私有URL链接
*
* @param objectKey 对象KEY
* @param second 授权时间
* @param objectKey 对象KEY
* @param expiredTime 链接授权到期时间
*/
public String getPrivateUrl(String objectKey, Integer second) {
public String getPrivateUrl(String objectKey, Duration expiredTime) {
// 使用 AWS S3 预签名 URL 的生成器 获取对象的预签名 URL
URL url = presigner.presignGetObject(
x -> x.signatureDuration(Duration.ofSeconds(second))
x -> x.signatureDuration(expiredTime)
.getObjectRequest(
y -> y.bucket(properties.getBucketName())
.key(objectKey)
@ -340,8 +313,8 @@ public class OssClient {
* @return UploadResult 包含上传后的文件信息
* @throws OssException 如果上传失败,抛出自定义异常
*/
public UploadResult uploadSuffix(byte[] data, String suffix) {
return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Long.valueOf(data.length), FileUtils.getMimeType(suffix));
public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Long.valueOf(data.length), contentType);
}
/**
@ -353,8 +326,8 @@ public class OssClient {
* @return UploadResult 包含上传后的文件信息
* @throws OssException 如果上传失败,抛出自定义异常
*/
public UploadResult uploadSuffix(InputStream inputStream, String suffix, Long length) {
return upload(inputStream, getPath(properties.getPrefix(), suffix), length, FileUtils.getMimeType(suffix));
public UploadResult uploadSuffix(InputStream inputStream, String suffix, Long length, String contentType) {
return upload(inputStream, getPath(properties.getPrefix(), suffix), length, contentType);
}
/**
@ -519,77 +492,4 @@ public class OssClient {
return AccessPolicyType.getByType(properties.getAccessPolicy());
}
/**
* 生成 AWS S3 存储桶访问策略
*
* @param bucketName 存储桶
* @param policyType 桶策略类型
* @return 符合 AWS S3 存储桶访问策略格式的字符串
*/
private static String getPolicy(String bucketName, PolicyType policyType) {
String policy = switch (policyType) {
case WRITE -> """
{
"Version": "2012-10-17",
"Statement": []
}
""";
case READ_WRITE -> """
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetBucketLocation",
"s3:ListBucket",
"s3:ListBucketMultipartUploads"
],
"Resource": "arn:aws:s3:::bucketName"
},
{
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:AbortMultipartUpload",
"s3:DeleteObject",
"s3:GetObject",
"s3:ListMultipartUploadParts",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::bucketName/*"
}
]
}
""";
case READ -> """
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:GetBucketLocation"],
"Resource": "arn:aws:s3:::bucketName"
},
{
"Effect": "Deny",
"Principal": "*",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::bucketName"
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::bucketName/*"
}
]
}
""";
};
return policy.replaceAll("bucketName", bucketName);
}
}

View File

@ -17,17 +17,17 @@ public enum AccessPolicyType {
/**
* private
*/
PRIVATE("0", BucketCannedACL.PRIVATE, ObjectCannedACL.PRIVATE, PolicyType.WRITE),
PRIVATE("0", BucketCannedACL.PRIVATE, ObjectCannedACL.PRIVATE),
/**
* public
*/
PUBLIC("1", BucketCannedACL.PUBLIC_READ_WRITE, ObjectCannedACL.PUBLIC_READ_WRITE, PolicyType.READ_WRITE),
PUBLIC("1", BucketCannedACL.PUBLIC_READ_WRITE, ObjectCannedACL.PUBLIC_READ_WRITE),
/**
* custom
*/
CUSTOM("2", BucketCannedACL.PUBLIC_READ, ObjectCannedACL.PUBLIC_READ, PolicyType.READ);
CUSTOM("2", BucketCannedACL.PUBLIC_READ, ObjectCannedACL.PUBLIC_READ);
/**
* 桶 权限类型(数据库值)
@ -44,11 +44,6 @@ public enum AccessPolicyType {
*/
private final ObjectCannedACL objectCannedACL;
/**
* 桶策略类型
*/
private final PolicyType policyType;
public static AccessPolicyType getByType(String type) {
for (AccessPolicyType value : values()) {
if (value.getType().equals(type)) {

View File

@ -1,35 +0,0 @@
package org.dromara.common.oss.enumd;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* minio策略配置
*
* @author Lion Li
*/
@Getter
@AllArgsConstructor
public enum PolicyType {
/**
* 只读
*/
READ("read-only"),
/**
* 只写
*/
WRITE("write-only"),
/**
* 读写
*/
READ_WRITE("read-write");
/**
* 类型
*/
private final String type;
}

View File

@ -38,4 +38,10 @@ public @interface RateLimiter {
* 提示消息 支持国际化 格式为 {code}
*/
String message() default "{rate.limiter.message}";
/**
* 限流策略超时时间 默认一天(策略存活时间 会清除已存在的策略数据)
*/
int timeout() default 86400;
}

View File

@ -54,13 +54,14 @@ public class RateLimiterAspect {
public void doBefore(JoinPoint point, RateLimiter rateLimiter) {
int time = rateLimiter.time();
int count = rateLimiter.count();
int timeout = rateLimiter.timeout();
try {
String combineKey = getCombineKey(rateLimiter, point);
RateType rateType = RateType.OVERALL;
if (rateLimiter.limitType() == LimitType.CLUSTER) {
rateType = RateType.PER_CLIENT;
}
long number = RedisUtils.rateLimiter(combineKey, rateType, count, time);
long number = RedisUtils.rateLimiter(combineKey, rateType, count, time, timeout);
if (number == -1) {
String message = rateLimiter.message();
if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) {

View File

@ -42,6 +42,18 @@
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<!-- &lt;!&ndash; redis序列化替代方案 比json快无数的跨语言二进制序列化 &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.apache.fury</groupId>-->
<!-- <artifactId>fury-core</artifactId>-->
<!-- <version>0.9.0</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-api</artifactId>-->
<!-- </dependency>-->
</dependencies>
</project>

View File

@ -53,6 +53,9 @@ public class RedisConfig {
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型类必须是非final修饰的。序列化时将对象全类名一起保存下来
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
// LoggerFactory.useSlf4jLogging(true);
// FuryCodec furyCodec = new FuryCodec();
// CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, furyCodec, furyCodec);
TypedJsonJacksonCodec jsonCodec = new TypedJsonJacksonCodec(Object.class, om);
// 组合序列化 key 使用 String 内容使用通用 json 格式
CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, jsonCodec, jsonCodec);

View File

@ -44,6 +44,7 @@ public class CaffeineCacheDecorator implements Cache {
}
@SuppressWarnings("unchecked")
@Override
public <T> T get(Object key, Class<T> type) {
Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key, type));
return (T) o;
@ -55,6 +56,7 @@ public class CaffeineCacheDecorator implements Cache {
cache.put(key, value);
}
@Override
public ValueWrapper putIfAbsent(Object key, Object value) {
CAFFEINE.invalidate(getUniqueKey(key));
return cache.putIfAbsent(key, value);
@ -65,6 +67,7 @@ public class CaffeineCacheDecorator implements Cache {
evictIfPresent(key);
}
@Override
public boolean evictIfPresent(Object key) {
boolean b = cache.evictIfPresent(key);
if (b) {
@ -78,6 +81,7 @@ public class CaffeineCacheDecorator implements Cache {
cache.clear();
}
@Override
public boolean invalidate() {
return cache.invalidate();
}

View File

@ -1,19 +1,15 @@
package org.dromara.common.redis.utils;
import org.dromara.common.core.utils.SpringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.redisson.api.RMap;
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import java.util.Set;
/**
* 缓存操作工具类 {@link }
* 缓存操作工具类
*
* @author Michelle.Chung
* @date 2022/8/13
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings(value = {"unchecked"})
@ -21,16 +17,6 @@ public class CacheUtils {
private static final CacheManager CACHE_MANAGER = SpringUtils.getBean(CacheManager.class);
/**
* 获取缓存组内所有的KEY
*
* @param cacheNames 缓存组名称
*/
public static Set<Object> keys(String cacheNames) {
RMap<Object, Object> rmap = (RMap<Object, Object>) CACHE_MANAGER.getCache(cacheNames).getNativeCache();
return rmap.keySet();
}
/**
* 获取缓存值
*

View File

@ -174,12 +174,12 @@ public class QueueUtils {
*
* @param queueName 队列名
* @param capacity 容量
* @param destroy 已存在是否销毁
* @param destroy 是否销毁
*/
public static <T> boolean trySetBoundedQueueCapacity(String queueName, int capacity, boolean destroy) {
RBoundedBlockingQueue<T> boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName);
if (boundedBlockingQueue.isExists() && destroy) {
destroyQueue(queueName);
if (destroy) {
boundedBlockingQueue.delete();
}
return boundedBlockingQueue.trySetCapacity(capacity);
}

View File

@ -4,6 +4,7 @@ import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.SpringUtils;
import org.redisson.api.*;
import org.redisson.api.options.KeysScanOptions;
import java.time.Duration;
import java.util.Collection;
@ -36,8 +37,22 @@ public class RedisUtils {
* @return -1 表示失败
*/
public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) {
return rateLimiter(key, rateType, rate, rateInterval, 0);
}
/**
* 限流
*
* @param key 限流key
* @param rateType 限流类型
* @param rate 速率
* @param rateInterval 速率间隔
* @param timeout 超时时间
* @return -1 表示失败
*/
public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval, int timeout) {
RRateLimiter rateLimiter = CLIENT.getRateLimiter(key);
rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
rateLimiter.trySetRate(rateType, rate, Duration.ofSeconds(rateInterval), Duration.ofSeconds(timeout));
if (rateLimiter.tryAcquire()) {
return rateLimiter.availablePermits();
} else {
@ -517,18 +532,39 @@ public class RedisUtils {
}
/**
* 获得缓存的基本对象列表
*
* 获得缓存的基本对象列表(全局匹配忽略租户 自行拼接租户id)
* <P>
* limit-设置扫描的限制数量(默认为0,查询全部)
* pattern-设置键的匹配模式(默认为null)
* chunkSize-设置每次扫描的块大小(默认为0,本方法设置为1000)
* type-设置键的类型(默认为null,查询全部类型)
* </P>
* @see KeysScanOptions
* @param pattern 字符串前缀
* @return 对象列表
*/
public static Collection<String> keys(final String pattern) {
Stream<String> stream = CLIENT.getKeys().getKeysStreamByPattern(pattern);
return stream.collect(Collectors.toList());
return keys(KeysScanOptions.defaults().pattern(pattern).chunkSize(1000));
}
/**
* 删除缓存的基本对象列表
* 通过扫描参数获取缓存的基本对象列表
* @param keysScanOptions 扫描参数
* <P>
* limit-设置扫描的限制数量(默认为0,查询全部)
* pattern-设置键的匹配模式(默认为null)
* chunkSize-设置每次扫描的块大小(默认为0)
* type-设置键的类型(默认为null,查询全部类型)
* </P>
* @see KeysScanOptions
*/
public static Collection<String> keys(final KeysScanOptions keysScanOptions) {
Stream<String> keysStream = CLIENT.getKeys().getKeysStream(keysScanOptions);
return keysStream.collect(Collectors.toList());
}
/**
* 删除缓存的基本对象列表(全局匹配忽略租户 自行拼接租户id)
*
* @param pattern 字符串前缀
*/

View File

@ -0,0 +1,165 @@
package org.dromara.common.redis.utils;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.redisson.api.RIdGenerator;
import org.redisson.api.RedissonClient;
import java.time.Duration;
/**
* 发号器工具类
*
* @author 秋辞未寒
* @date 2024-12-10
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SequenceUtils {
/**
* 默认初始值
*/
public static final Long DEFAULT_INIT_VALUE = 1L;
/**
* 默认步长
*/
public static final Long DEFAULT_STEP_VALUE = 1L;
/**
* 默认过期时间-天
*/
public static final Duration DEFAULT_EXPIRE_TIME_DAY = Duration.ofDays(1);
/**
* 默认过期时间-分钟
*/
public static final Duration DEFAULT_EXPIRE_TIME_MINUTE = Duration.ofMinutes(1);
/**
* 获取Redisson客户端实例
*/
private static final RedissonClient REDISSON_CLIENT = SpringUtils.getBean(RedissonClient.class);
/**
* 获取ID生成器
*
* @param key 业务key
* @param expireTime 过期时间
* @param initValue ID初始值
* @param stepValue ID步长
* @return ID生成器
*/
private static RIdGenerator getIdGenerator(String key, Duration expireTime, Long initValue, Long stepValue) {
if (initValue == null || initValue <= 0) {
initValue = DEFAULT_INIT_VALUE;
}
if (stepValue == null || stepValue <= 0) {
stepValue = DEFAULT_STEP_VALUE;
}
RIdGenerator idGenerator = REDISSON_CLIENT.getIdGenerator(key);
// 设置初始值和步长
idGenerator.tryInit(initValue, stepValue);
// 设置过期时间
idGenerator.expire(expireTime);
return idGenerator;
}
/**
* 获取指定业务key的唯一id
*
* @param key 业务key
* @param expireTime 过期时间
* @param initValue ID初始值
* @param stepValue ID步长
* @return 唯一id
*/
public static long nextId(String key, Duration expireTime, Long initValue, Long stepValue) {
return getIdGenerator(key, expireTime, initValue, stepValue).nextId();
}
/**
* 获取指定业务key的唯一id字符串
*
* @param key 业务key
* @param expireTime 过期时间
* @param initValue ID初始值
* @param stepValue ID步长
* @return 唯一id
*/
public static String nextIdStr(String key, Duration expireTime, Long initValue, Long stepValue) {
return String.valueOf(nextId(key, expireTime, initValue, stepValue));
}
/**
* 获取指定业务key的唯一id (ID初始值=1,ID步长=1)
*
* @param key 业务key
* @param expireTime 过期时间
* @return 唯一id
*/
public static long nextId(String key, Duration expireTime) {
return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
}
/**
* 获取指定业务key的唯一id字符串 (ID初始值=1,ID步长=1)
*
* @param key 业务key
* @param expireTime 过期时间
* @return 唯一id
*/
public static String nextIdStr(String key, Duration expireTime) {
return String.valueOf(nextId(key, expireTime));
}
/**
* 获取 yyyyMMdd 开头的唯一id
*
* @return 唯一id
*/
public static String nextIdDate() {
return nextIdDate("");
}
/**
* 获取 prefix + yyyyMMdd 开头的唯一id
*
* @param prefix 业务前缀
* @return 唯一id
*/
public static String nextIdDate(String prefix) {
// 前缀+日期 构建 prefixKey
String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_FORMATTER));
// 获取下一个id
long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_DAY, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
// 返回完整id
return StringUtils.format("{}{}", prefixKey, nextId);
}
/**
* 获取 yyyyMMddHHmmss 开头的唯一id
*
* @return 唯一id
*/
public static String nextIdDateTime() {
return nextIdDateTime("");
}
/**
* 获取 prefix + yyyyMMddHHmmss 开头的唯一id
*
* @param prefix 业务前缀
* @return 唯一id
*/
public static String nextIdDateTime(String prefix) {
// 前缀+日期时间 构建 prefixKey
String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_FORMATTER));
// 获取下一个id
long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_MINUTE, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
// 返回完整id
return StringUtils.format("{}{}", prefixKey, nextId);
}
}

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