423 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
098d3347a0 !577 发布 5.2.2 正式版 安全性提升
Merge pull request !577 from 疯狂的狮子Li/dev
2024-08-26 03:43:59 +00:00
463048e017 😴发布 5.2.2 正式版 安全性提升 2024-08-26 11:39:22 +08:00
79aee1d312 add 添加按照部门id,角色id查询用户 2024-08-24 11:16:39 +08:00
eb038e91dd update springboot 3.2.8 => 3.2.9 2024-08-23 10:52:53 +08:00
308c22f922 fix 修复 三方登录构建去除无用代码 2024-08-23 10:30:29 +08:00
457e59e61c fix 修复 多线程对同一个session发送ws消息报错问题 2024-08-22 17:53:55 +08:00
a964ccbd10 update snailjob 1.1.1 => 1.1.2
update mapstruct-plus 1.4.3 => 1.4.4
2024-08-22 11:19:43 +08:00
0af532f4f1 update 优化 去除日志部署环境判断 通过日志级别控制 2024-08-22 11:19:13 +08:00
4743eb1d3b update 优化 忽略租户与忽略数据权限支持嵌套使用 2024-08-21 13:54:40 +08:00
23aafe1cfe update 优化 忽略租户与忽略数据权限支持嵌套使用 2024-08-21 13:54:01 +08:00
3f2499feac update 优化 租户相关controller 增加租户开关配置控制是否注册 2024-08-19 09:14:50 +08:00
fb97649151 remove 移除 alibaba ttl 与线程池搭配有问题(可传递但无法清除与更新) 2024-08-17 10:30:03 +08:00
b33b645ef0 remove 移除 alibaba ttl 与线程池搭配有问题(可传递但无法清除与更新) 2024-08-17 10:29:33 +08:00
9318f182b0 reset 回滚 修改spring源码上下文持有者(存在数据未清理内存泄漏问题) 2024-08-17 10:10:51 +08:00
facbb7f28f update 优化 个人中心编辑 忽略数据权限 2024-08-15 20:28:52 +08:00
4de45ce170 update 优化 兼容部分用户不想给用户分配角色与部门的场景 2024-08-15 19:52:03 +08:00
96d57bd263 update 优化 租户套餐重名校验 2024-08-15 19:34:02 +08:00
bb4587fe05 update 优化 部门下存在岗位不允许删除 2024-08-15 19:29:04 +08:00
19c83f02aa update 优化 角色编辑状态未校验问题 2024-08-15 19:14:51 +08:00
0e1fcbfe9c update 优化 用户脱敏增加编辑权限标识符 2024-08-15 18:52:01 +08:00
eda882433a update 优化 删除报警注释 2024-08-15 17:45:06 +08:00
e6847605cc update 优化 删除无用注释 2024-08-15 17:43:57 +08:00
5bdffdb368 update 优化 代码生成器 自动适配oss翻译 2024-08-15 14:10:14 +08:00
0ad52b18b8 update 优化 代码生成器 自动适配oss翻译 2024-08-15 14:09:20 +08:00
77f44574c0 update 优化 修改spring源码上下文持有者 支持线程切换传递上下文数据 支持一切异步获取用户信息等操作 2024-08-15 11:53:15 +08:00
ca06a2311d update hutool 5.8.29 => 5.8.31 解决hutool不兼容jakarta问题 2024-08-13 13:32:34 +08:00
b8d9af65e2 update 增加sse注释说明 2024-08-09 17:24:44 +08:00
bc2b4876b6 fix 修复代码生成集合类型工具判断 2024-08-09 14:08:19 +08:00
23b70ca0be update anyline 8.7.2-20240808 2024-08-09 11:54:47 +08:00
44d776a76f update sms4j 3.2.1 => 3.3.2 2024-08-09 10:38:43 +08:00
f03c00b2c1 reset 回滚 sms4j 版本升级(有问题) 2024-08-07 22:27:05 +08:00
7f60ba9888 update redisson 3.33.0 => 3.34.1
update mapstruct-plus 1.3.6 => 1.4.3
update sms4j 3.2.1 => 3.3.1
update lombok 1.18.32 => 1.18.34
2024-08-07 11:22:14 +08:00
31569646b0 fix 修复 依赖漏洞 限制部分依赖版本 2024-08-07 11:20:34 +08:00
3fc37d6362 update easyexcel 3.3.4 => 4.0.2 2024-08-07 11:19:16 +08:00
6d28072167 update 优化 临时升级 undertow 版本 解决虚拟线程溢出问题 2024-08-07 09:58:11 +08:00
f124fbd6aa fix 修复 关闭应用sse销毁报错问题 2024-08-07 09:57:20 +08:00
88a4a51956 update 默认开启工作流 2024-08-06 16:10:11 +08:00
4306ea4181 update 优化 支持通过配置文件关闭工作流 2024-08-06 16:07:22 +08:00
e19140462d fix 修复 关闭 sse 报错问题 2024-08-06 15:38:16 +08:00
20cc8a6d6c fix 修复 excel 基于其他字段 合并错误问题 2024-08-06 13:53:40 +08:00
a9d7a42c65 update 优化 增加mp填充器兜底策略 2024-08-04 22:58:17 +08:00
f51e6d81b1 update 优化 TenantSpringCacheManager 处理逻辑 2024-08-04 10:45:25 +08:00
f119d082cf fix 修复 一级缓存key未区分租户问题 2024-08-04 10:40:22 +08:00
ecfaa9ad5c update 优化 角色权限判断 2024-08-02 17:09:46 +08:00
f32d0266ee update 更新 readme 2024-08-02 11:37:21 +08:00
7393a61305 fix 修复 id字符串格式转换错误问题 2024-08-02 10:36:34 +08:00
2b0efd1f93 update 优化 增加删除标志位常量优化查询代码 2024-08-02 10:07:47 +08:00
b615a3b088 fix 修复 id字符串格式转换错误问题 2024-08-02 10:03:17 +08:00
85403e975f fix 修复 登出无法正确删除对应的租户数据问题 2024-08-02 00:55:42 +08:00
615ad918ca update 优化 sse 拦截网络中断io异常 2024-08-02 00:55:11 +08:00
b886f3a04b fix 修复 登录错误锁定不区分租户问题 2024-08-01 23:20:29 +08:00
588a47897a fix 修复 转换模型缺少分类字段 2024-08-01 15:15:54 +08:00
2869d590e6 update 优化 sse 关闭连接各种异常问题 2024-08-01 14:55:43 +08:00
6b14bce25e update 优化 监控使用独立web依赖 2024-07-31 17:09:16 +08:00
5aa346327f fix 修复 代码生成 错误匹配表名问题 2024-07-31 13:02:33 +08:00
fcf8516f0d update 优化 适配 anyline 新改动 2024-07-31 09:48:05 +08:00
2a340d4d83 update anyline 8.7.2-20240728 2024-07-29 17:47:01 +08:00
508d7a37e3 update 脱敏策略优化增加密码 2024-07-29 15:18:23 +08:00
08fece39d8 add 新增 更多脱敏策略 2024-07-29 15:04:54 +08:00
857a0b1006 update 优化oss查询代码 2024-07-29 14:33:54 +08:00
239d59c864 update 优化 sse发送消息 增加token有效期判断 2024-07-29 12:44:45 +08:00
7297053dd6 fix 修复 登出后重新登录 sse推送报错问题 2024-07-29 12:28:01 +08:00
bd872f624a fix 修复 代码生成 数据源切换问题 2024-07-27 23:57:13 +08:00
86acb14f05 update anyline 8.7.2-20240726 2024-07-27 15:09:33 +08:00
9825f349ac fix 修复 代码生成 表结构缓存问题 2024-07-27 14:22:57 +08:00
19fd562c24 update snailjob 1.1.0 => 1.1.1 2024-07-27 14:20:20 +08:00
b6d939a9ff fix 修复 代码生成 表结构缓存问题 2024-07-27 14:00:50 +08:00
1619edb8a1 update 优化 sse 自动装配 2024-07-27 14:00:35 +08:00
782821aeb2 update 优化 设置nginx sse相关代理参数 2024-07-26 18:11:36 +08:00
51498958fa fix 修复 权限标识符处理未设置成功状态问题 2024-07-26 17:17:18 +08:00
51edb74474 fix 修复 后端发消息发送失败无限重试问题 2024-07-26 17:14:23 +08:00
d5ab2a7557 update 优化 调整默认推送使用SSE 2024-07-26 16:24:26 +08:00
ee3525cfb2 add 增加 ruoyi-common-sse 模块 支持SSE推送 比ws更轻量更稳定的推送 2024-07-26 16:05:35 +08:00
e9bd0858e2 update 优化 删除无用配置 2024-07-25 13:21:32 +08:00
f46d881866 add 增加 snailjob 健康检查 actuator 账号密码认证 2024-07-25 13:12:58 +08:00
ee5e718f83 !567 update 优化 Monitor监控服务通知分类打印
Merge pull request !567 from AprilWind/dev
2024-07-25 03:10:35 +00:00
e25083aea4 del 删除无用依赖 2024-07-25 10:55:46 +08:00
e74f0ca6f8 update 优化Monitor监控服务信息事件通知 2024-07-25 10:37:27 +08:00
52b0fa9a54 update 优化Monitor监控服务通知分类(可扩展邮箱发送,短信发送) 2024-07-25 10:00:10 +08:00
105c007f03 add 增加 springboot actuator 账号密码认证 杜绝内外网信息泄漏问题 2024-07-24 18:56:40 +08:00
0a3d5fd5d4 update 优化 代码生成分页实现 避免数据误传等问题 2024-07-23 17:05:46 +08:00
ae3c02d4b2 update 优化 代码生成分页实现 避免数据误传等问题 2024-07-23 17:05:20 +08:00
9e17d07a17 update 优化 限流注解 又写key又不是表达式的情况 2024-07-23 16:34:53 +08:00
dcfab4e011 update 优化 代码生成 相关sql 2024-07-23 13:21:15 +08:00
0b78f9361d fix 修复 已经导入的表 未过滤问题 2024-07-23 11:44:39 +08:00
0c4e9dc813 update anyline 8.7.2-20240722 2024-07-23 10:37:15 +08:00
d894cae073 update 优化 代码生成屏蔽无用表 2024-07-23 10:35:22 +08:00
84f553a911 fix 修复 代码生成 报错与警告 2024-07-23 10:06:15 +08:00
05580deaa9 fix 修复 无法导入 bpmn 类型文件问题 2024-07-23 09:58:18 +08:00
aac83bbb91 update 获取表元数据 字段是否必填 和 是否自增 2024-07-22 17:06:46 +08:00
249f1f48a6 reset 回滚错误提交 2024-07-19 17:55:13 +08:00
bfb92fe667 update 优化 依赖配置 2024-07-19 17:54:44 +08:00
640dc43bbe !565 流程发送消息未查询用户邮箱和手机号
Merge pull request !565 from 愿丶/dev-pr
2024-07-19 09:43:47 +00:00
8859d915b0 update workflowUtils查询用户信息发送消息未查询邮件和手机号 2024-07-19 17:39:19 +08:00
82fdb37c6b fix 修复判断表名为空错误 2024-07-19 16:50:40 +08:00
49c18dab63 update 优化根据表名称查询列信息 2024-07-19 16:44:12 +08:00
f5b8a22bde update 更改D-ORM依赖为全依赖 2024-07-19 15:55:31 +08:00
b6b0f9c47d update 优化代码生成表名查询数据库 2024-07-19 15:54:37 +08:00
a2a2fa2311 update 优化查询数据库列表 2024-07-19 15:29:38 +08:00
34690e3e65 update springboot 3.2.6 => 3.2.8
update springdoc 2.5.0 => 2.6.0
update hutool 5.8.27 => 5.8.29
update redisson 3.31.0 => 3.33.0
update flowable 7.0.0 => 7.0.1
2024-07-19 10:08:28 +08:00
54f58257f9 update 优化 注释掉其他数据库 jdbc 依赖 由用户手动添加 2024-07-16 11:04:48 +08:00
58b6c4668f update 优化 oracle snailjob 兼容低版本oracle索引名称长度限制 2024-07-16 09:42:50 +08:00
d0e7eb8409 !564 升级SnailJob版本到1.1.0
Merge pull request !564 from dhb52/dev
2024-07-15 14:49:55 +00:00
77a7a8f30e chore: 升级SnailJob版本到1.1.0 2024-07-15 18:16:43 +08:00
08d4493994 update 优化 bug 模板 2024-07-15 15:19:22 +08:00
f76738e02b update 优化 bug 模板 2024-07-15 15:18:29 +08:00
ab147df2f1 update 优化 数据权限支持通过菜单标识符获取数据所有权 2024-07-12 13:51:34 +08:00
5444ccc857 update 优化 数据权限支持自定义连接符 2024-07-12 13:15:23 +08:00
fc89d62f1a update 优化 TestDemo 删除前校验数据权限 2024-07-12 12:57:45 +08:00
367d739e2d Merge remote-tracking branch 'origin/5.X' into 5.X 2024-07-09 16:38:43 +08:00
94467273c5 update 优化 更换docker镜像底层系统 避免无字体情况 2024-07-09 16:37:05 +08:00
d6688a367d !562 ♥️发布 5.2.1 正式版本
Merge pull request !562 from 疯狂的狮子Li/dev
2024-07-09 02:42:40 +00:00
835de64bea ♥️发布 5.2.1 正式版本 2024-07-09 10:42:16 +08:00
113da3437b fix 修复 isLogin 方法抛异常无法正常返回值问题 2024-07-09 09:23:01 +08:00
b0b6d01357 update 优化 工作流相关代码方法 2024-07-06 14:42:08 +08:00
6cc24dc763 !561 使用封装好的StreamUtils工具类代替项目中的部分stream操作
* refactor : 使用封装好的StreamUtils工具类代替项目中的部分stream操作
2024-07-06 05:58:26 +00:00
0cb3105cea update 优化 为需要字体的服务 补充字体 2024-07-05 10:12:09 +08:00
00502a4689 update 优化 更新使用 Spring 官方推荐 JDK 2024-07-04 15:54:43 +08:00
ebb7242b71 update 更新 redis 密码策略 2024-07-03 11:43:59 +08:00
0fbb96c4ac fix 修复 sql修改错误 2024-07-03 10:03:34 +08:00
e942ffed71 update 优化 删除无用校验 2024-07-02 13:19:08 +08:00
319270bf2b update 优化 删除无用校验 2024-07-02 13:18:35 +08:00
8b69de0d54 update 优化 删除无用校验 2024-07-02 13:17:20 +08:00
f929513310 update 优化 webscoket 配置与异常拦截 2024-07-02 12:00:08 +08:00
0d25b82087 update 优化 isTenantAdmin 空校验 2024-07-01 15:21:02 +08:00
c75857b1ea update 优化 修改 snailjob 默认端口 2024-07-01 14:56:05 +08:00
6d353869ef update 优化 修改 snailjob 默认端口 避免与系统内置端口冲突问题 2024-07-01 14:34:51 +08:00
6ba7249a75 update 优化 修改 snailjob 默认端口 避免与系统内置端口冲突问题 2024-07-01 14:33:38 +08:00
2ee543e2a4 update 优化 去除已经用不上的配置 2024-07-01 12:46:49 +08:00
871dfa9a67 !559 refactor: 修改路由name命名规则
Merge pull request !559 from 玲娜贝er/dev
2024-07-01 01:12:32 +00:00
dap
17fe2d5863 refactor: 修改路由name命名规则 2024-07-01 08:47:13 +08:00
d5b62a2126 fix 修复 pg数据库 查询报错问题 2024-06-28 16:28:36 +08:00
a4a833f15f !557 README文档错别字修复
Merge pull request !557 from mxyyyy/dev
2024-06-28 06:12:15 +00:00
a4fe077a23 update 优化 大数据量下join卡顿问题 使用子查询提高性能 2024-06-28 12:49:58 +08:00
bfa77361b7 docs: README文档错别字 2024-06-28 12:05:03 +08:00
3dff529920 !556 新增mybatis注释
* docs 新增mybatis注释
2024-06-27 09:46:17 +00:00
3681150010 !555 优化用户ID查询角色列表
Merge pull request !555 from AprilWind/dev
2024-06-27 07:38:36 +00:00
f0b4fcbdf0 update 优化用户ID查询角色列表 2024-06-27 15:30:24 +08:00
5c7e8c5381 !553 优化获取用户账户
Merge pull request !553 from AprilWind/dev
2024-06-27 06:49:59 +00:00
a144fa449b update 优化获取用户账户 2024-06-27 14:43:33 +08:00
4f9ceb0a80 fix 修复 spring重大bug 导致 actuator 泄漏问题 2024-06-21 16:31:05 +08:00
12338fc0b4 update 优化 租户列表接口 避免登录之后列表被域名过滤 2024-06-21 15:51:39 +08:00
6d2cc6e87d update 优化 替换过期方法 2024-06-21 14:59:50 +08:00
52598e5c5c fix 修复 新版上传未设置acl问题 2024-06-21 12:35:54 +08:00
903d810edc fix 修复 新版上传未设置acl问题 2024-06-21 12:34:50 +08:00
475b169952 update 优化 更改prod环境 snailjob状态 默认启用 2024-06-21 10:50:41 +08:00
ba1f66367b fix 修复 postgres flowable sql 缺失字段问题 2024-06-21 10:17:03 +08:00
439 changed files with 14298 additions and 15024 deletions

View File

@ -1,49 +0,0 @@
### 使用版本(未按照模板填写直接删除)
- jdk版本(带上尾号): 例如 1.8.0_202
- 框架版本(项目启动时输出的版本号): 例如 4.4.0
- 其他依赖版本(你觉得有必要的):
### 问题前提
> 功能不好用 不会用 是否已经看过项目文档
> 项目运行报错 是否已经拿着报错信息去百度 常见报错百度百度足以
> 是否搜索过其他issue 一些已经解决的问题 会在issue内留下解决方法
> 无法线上解决或者与框架无关的问题的欢迎加VIP群跟作者一对一谈
### 异常模块
> 此报错都涉及到那些系统模块
例如 ruoyi-system ruoyi-auth 等等
### 问题描述
> 越详细越容易直击问题所在
已知: XXX功能不好用 或 XXX数据不正常 等等
### 希望结果
> 想知道你觉得怎么样是正常或者合理的
希望功能可以有XXX结果 或者 XXX现象
### 重现步骤
> 作者并不知道这个问题是如何出现的
- 1
- 2
- 3
### 相关代码与报错信息(请勿发混乱格式)
> 代码可按照如下形式提供或者截图均可 越详细越好
> 大多数问题都是 代码编写错误问题 逻辑问题 或者用法错误等问题
```java
public class XXX {
}
```

View File

@ -9,8 +9,9 @@ body:
label: 版本
description: 你当前正在使用我们软件的哪个版本(pom文件内的版本号)
value: |
jdk版本(带上尾号): 例如 17.0.8
框架版本(项目启动时输出的版本号): 例如 5.1.1
注意: 未填写版本号不予处理直接关闭或删除
jdk版本(带上尾号):
框架版本(项目启动时输出的版本号):
其他依赖版本(你觉得有必要的):
validations:
required: true

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.0" />
<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

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

View File

@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.2.0" />
<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.0-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的功能差异
@ -56,11 +59,12 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
| 数据加解密 | 采用 注解 + mybatis 拦截器 对存取数据期间自动加解密<br/>支持多种策略 如BASE64、AES、RSA、SM2、SM4等 | 无 |
| 接口传输加密 | 采用 动态 AES + RSA 加密请求 body 每一次请求秘钥都不同大幅度降低可破解性 | 无 |
| 数据翻译 | 采用 注解 + jackson 序列化期间动态修改数据 数据进行翻译<br/>支持多种模式: `映射翻译` `直接翻译` `其他扩展条件翻译` 接口化两步即可完成自定义扩展 内置多种翻译实现 | 无 |
| 多数据源框架 | 采用 dynamic-datasource 支持面大部分数据库<br/>通过yml配置即可动态管理异构不同种类的数据库 也可通过前端页面添加数据源<br/>支持spel表达式从请求头参数等条件切换数据源 | 基于 druid 手动编写代码配置数据源 配置繁琐 支持性差 |
| 多数据源框架 | 采用 dynamic-datasource 支持面大部分数据库<br/>通过yml配置即可动态管理异构不同种类的数据库 也可通过前端页面添加数据源<br/>支持spel表达式从请求头参数等条件切换数据源 | 基于 druid 手动编写代码配置数据源 配置繁琐 支持性差 |
| 多数据源事务 | 采用 dynamic-datasource 支持多数据源不同种类的数据库事务回滚 | 不支持 |
| 数据库连接池 | 采用 HikariCP Spring官方内置连接池 配置简单 以性能与稳定性闻名天下 | 采用 druid bug众多 社区维护差 活跃度低 配置众多繁琐性能一般 |
| 数据库主键 | 采用 雪花ID 基于时间戳的 有序增长 唯一ID 再也不用为分库分表 数据合并主键冲突重复而发愁 | 采用 数据库自增ID 支持数据量有限 不支持多数据源主键唯一 |
| WebSocket协议 | 基于 Spring 封装的 WebSocket 协议 扩展了Token鉴权与分布式会话同步 不再只是基于单机的废物 | 无 |
| SSE推送 | 采用 Spring SSE 实现 扩展了Token鉴权与分布式会话同步 | 无 |
| 序列化 | 采用 Jackson Spring官方内置序列化 靠谱!!! | 采用 fastjson bugjson 远近闻名 |
| 分布式幂等 | 参考美团GTIS防重系统简化实现(细节可看文档) | 手动编写注解基于aop实现 |
| 分布式锁 | 采用 Lock4j 底层基于 Redisson | 无 |
@ -72,6 +76,7 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
| 接口文档 | 采用 SpringDoc、javadoc 无注解零入侵基于java注释<br/>只需把注释写好 无需再写一大堆的文档注解了 | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成 |
| 校验框架 | 采用 Validation 支持注解与工具类校验 注解支持国际化 | 仅支持注解 且注解不支持国际化 |
| Excel框架 | 采用 Alibaba EasyExcel 基于插件化<br/>框架对其增加了很多功能 例如 自动合并相同内容 自动排列布局 字典翻译等 | 基于 POI 手写实现 功能有限 复杂 扩展性差 |
| 工作流支持 | 支持各种复杂审批 转办 委派 加减签 会签 或签 票签 等功能 | 无 |
| 工具类框架 | 采用 Hutool、Lombok 上百种工具覆盖90%的使用需求 基于注解自动生成 get set 等简化框架大量代码 | 手写工具稳定性差易出问题 工具数量有限 代码臃肿需自己手写 get set 等 |
| 监控框架 | 采用 SpringBoot-Admin 基于SpringBoot官方 actuator 探针机制<br/>实时监控服务状态 框架还为其扩展了在线日志查看监控 | 无 |
| 链路追踪 | 采用 Apache SkyWalking 还在为请求不知道去哪了 到哪出了问题而烦恼吗<br/>用了它即可实时查看请求经过的每一处每一个节点 | 无 |
@ -163,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 "屏幕截图") |

105
pom.xml
View File

@ -10,53 +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.0</revision>
<spring-boot.version>3.2.6</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.5.0</springdoc.version>
<springdoc.version>2.8.3</springdoc.version>
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
<poi.version>5.2.3</poi.version>
<easyexcel.version>3.3.4</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.27</hutool.version>
<okhttp.version>4.10.0</okhttp.version>
<spring-boot-admin.version>3.2.3</spring-boot-admin.version>
<redisson.version>3.31.0</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>
<alibaba-ttl.version>2.14.4</alibaba-ttl.version>
<snailjob.version>1.0.1</snailjob.version>
<mapstruct-plus.version>1.3.6</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.32</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>
<!-- 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.2.1</sms4j.version>
<sms4j.version>3.3.3</sms4j.version>
<!-- 限制框架中的fastjson版本 -->
<fastjson.version>1.2.83</fastjson.version>
<!-- 面向运行时的D-ORM依赖 -->
<anyline.version>8.7.2-20250101</anyline.version>
<!--工作流配置-->
<flowable.version>7.0.0</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>
@ -68,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>
@ -76,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>
<!-- 默认环境 -->
@ -87,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>
@ -113,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 的依赖配置-->
@ -155,26 +164,10 @@
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>${poi.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${poi.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>${easyexcel.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- velocity代码生成使用模板 -->
@ -227,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>
@ -240,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>
@ -307,12 +300,6 @@
<version>${snailjob.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>${alibaba-ttl.version}</version>
</dependency>
<!-- 加密包引入 -->
<dependency>
<groupId>org.bouncycastle</groupId>
@ -333,6 +320,12 @@
<version>${ip2region.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.15.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
@ -386,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,7 +1,9 @@
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
#FROM bellsoft/liberica-openjdk-debian:21.0.5-cds
#FROM findepi/graalvm:java17-native
FROM openjdk:17.0.2-oraclelinux8
MAINTAINER Lion Li
LABEL maintainer="Lion Li"
RUN mkdir -p /ruoyi/server/logs \
/ruoyi/server/temp \
@ -14,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

@ -22,21 +22,28 @@
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- Oracle -->
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
</dependency>
<!-- PostgreSql -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<!-- SqlServer -->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
</dependency>
<!-- &lt;!&ndash; mp支持的数据库均支持 只需要增加对应的jdbc依赖即可 &ndash;&gt;-->
<!-- &lt;!&ndash; Oracle &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>com.oracle.database.jdbc</groupId>-->
<!-- <artifactId>ojdbc8</artifactId>-->
<!-- </dependency>-->
<!-- &lt;!&ndash; 兼容oracle低版本 &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>com.oracle.database.nls</groupId>-->
<!-- <artifactId>orai18n</artifactId>-->
<!-- </dependency>-->
<!-- &lt;!&ndash; PostgreSql &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.postgresql</groupId>-->
<!-- <artifactId>postgresql</artifactId>-->
<!-- </dependency>-->
<!-- &lt;!&ndash; SqlServer &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>com.microsoft.sqlserver</groupId>-->
<!-- <artifactId>mssql-jdbc</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.dromara</groupId>

View File

@ -1,6 +1,8 @@
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;
@ -11,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;
@ -23,9 +25,9 @@ import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
import org.dromara.common.social.config.properties.SocialProperties;
import org.dromara.common.social.utils.SocialUtils;
import org.dromara.common.sse.dto.SseMessageDto;
import org.dromara.common.sse.utils.SseMessageUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.common.websocket.dto.WebSocketMessageDto;
import org.dromara.common.websocket.utils.WebSocketUtils;
import org.dromara.system.domain.bo.SysTenantBo;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysTenantVo;
@ -91,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"));
}
// 校验租户
@ -101,16 +103,16 @@ public class AuthController {
Long userId = LoginHelper.getUserId();
scheduledExecutorService.schedule(() -> {
WebSocketMessageDto dto = new WebSocketMessageDto();
SseMessageDto dto = new SseMessageDto();
dto.setMessage("欢迎登录RuoYi-Vue-Plus后台管理系统");
dto.setSessionKeys(List.of(userId));
WebSocketUtils.publishMessage(dto);
}, 3, TimeUnit.SECONDS);
dto.setUserIds(List.of(userId));
SseMessageUtils.publishMessage(dto);
}, 5, TimeUnit.SECONDS);
return R.ok(loginVo);
}
/**
* 第三方登录请求
* 获取跳转URL
*
* @param source 登录来源
* @return 结果
@ -132,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(),
@ -154,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("取消授权失败");
}
@ -194,8 +200,26 @@ public class AuthController {
*/
@GetMapping("/tenant/list")
public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
// 返回对象
LoginTenantVo result = new LoginTenantVo();
boolean enable = TenantHelper.isEnable();
result.setTenantEnabled(enable);
// 如果未开启租户这直接返回
if (!enable) {
return R.ok(result);
}
List<SysTenantVo> tenantList = tenantService.queryList(new SysTenantBo());
List<TenantListVo> voList = MapstructUtils.convert(tenantList, TenantListVo.class);
try {
// 如果只超管返回所有租户
if (LoginHelper.isSuperAdmin()) {
result.setVoList(voList);
return R.ok(result);
}
} catch (NotLoginException ignored) {
}
// 获取域名
String host;
String referer = request.getHeader("referer");
@ -207,12 +231,9 @@ public class AuthController {
}
// 根据域名进行筛选
List<TenantListVo> list = StreamUtils.filter(voList, vo ->
StringUtils.equals(vo.getDomain(), host));
// 返回对象
LoginTenantVo vo = new LoginTenantVo();
vo.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
vo.setTenantEnabled(TenantHelper.isEnable());
return R.ok(vo);
StringUtils.equalsIgnoreCase(vo.getDomain(), host));
result.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
return R.ok(result);
}
}

View File

@ -3,6 +3,8 @@ package org.dromara.web.listener;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import lombok.RequiredArgsConstructor;
@ -81,7 +83,10 @@ public class UserActionListener implements SaTokenListener {
*/
@Override
public void doLogout(String loginType, Object loginId, String tokenValue) {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
log.info("user doLogout, userId:{}, token:{}", loginId, tokenValue);
}
@ -90,7 +95,10 @@ public class UserActionListener implements SaTokenListener {
*/
@Override
public void doKickout(String loginType, Object loginId, String tokenValue) {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
log.info("user doKickout, userId:{}, token:{}", loginId, tokenValue);
}
@ -99,7 +107,10 @@ public class UserActionListener implements SaTokenListener {
*/
@Override
public void doReplaced(String loginType, Object loginId, String tokenValue) {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
log.info("user doReplaced, userId:{}, token:{}", loginId, tokenValue);
}

View File

@ -4,18 +4,20 @@ import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.lock.annotation.Lock4j;
import lombok.RequiredArgsConstructor;
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.GlobalConstants;
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.*;
@ -59,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;
@ -147,24 +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()));
TenantHelper.dynamic(user.getTenantId(), () -> {
SysDeptVo dept = null;
if (ObjectUtil.isNotNull(user.getDeptId())) {
dept = deptService.selectDeptById(user.getDeptId());
}
loginUser.setDeptName(ObjectUtil.isNull(dept) ? "" : dept.getDeptName());
loginUser.setDeptCategory(ObjectUtil.isNull(dept) ? "" : dept.getDeptCategory());
List<SysRoleVo> roles = roleService.selectRolesByUserId(user.getUserId());
loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
});
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(userId);
List<SysPostVo> posts = postService.selectPostsByUserId(userId);
loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
loginUser.setPosts(BeanUtil.copyToList(posts, PostDTO.class));
return loginUser;
}
@ -186,7 +189,7 @@ public class SysLoginService {
* 登录校验
*/
public void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) {
String errorKey = GlobalConstants.PWD_ERR_CNT_KEY + username;
String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
String loginFail = Constants.LOGIN_FAIL;
// 获取用户登录错误次数默认为0 (可自定义限制策略 例如: key + username + ip)
@ -225,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;
@ -21,7 +21,6 @@ import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.SysClient;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo;
@ -51,13 +50,12 @@ public class EmailAuthStrategy implements IAuthStrategy {
String tenantId = loginBody.getTenantId();
String email = loginBody.getEmail();
String emailCode = loginBody.getEmailCode();
// 通过邮箱查找用户
SysUserVo user = loadUserByEmail(tenantId, email);
loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
LoginUser loginUser = loginService.buildLoginUser(user);
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
SysUserVo user = loadUserByEmail(email);
loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
return loginService.buildLoginUser(user);
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
@ -89,18 +87,16 @@ public class EmailAuthStrategy implements IAuthStrategy {
return code.equals(emailCode);
}
private SysUserVo loadUserByEmail(String tenantId, String email) {
return TenantHelper.dynamic(tenantId, () -> {
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, email));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", email);
throw new UserException("user.not.exists", email);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", email);
throw new UserException("user.blocked", email);
}
return user;
});
private SysUserVo loadUserByEmail(String email) {
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, email));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", email);
throw new UserException("user.not.exists", email);
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", email);
throw new UserException("user.blocked", email);
}
return user;
}
}

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;
@ -24,7 +24,6 @@ import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.common.web.config.properties.CaptchaProperties;
import org.dromara.system.domain.SysClient;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo;
@ -63,11 +62,12 @@ public class PasswordAuthStrategy implements IAuthStrategy {
if (captchaEnabled) {
validateCaptcha(tenantId, username, code, uuid);
}
SysUserVo user = loadUserByUsername(tenantId, username);
loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
// 此处可根据登录用户的数据不同 自行创建 loginUser
LoginUser loginUser = loginService.buildLoginUser(user);
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
SysUserVo user = loadUserByUsername(username);
loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
// 此处可根据登录用户的数据不同 自行创建 loginUser
return loginService.buildLoginUser(user);
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
@ -95,7 +95,7 @@ public class PasswordAuthStrategy implements IAuthStrategy {
* @param uuid 唯一标识
*/
private void validateCaptcha(String tenantId, String username, String code, String uuid) {
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
@ -108,18 +108,16 @@ public class PasswordAuthStrategy implements IAuthStrategy {
}
}
private SysUserVo loadUserByUsername(String tenantId, String username) {
return TenantHelper.dynamic(tenantId, () -> {
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, username));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
throw new UserException("user.not.exists", username);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new UserException("user.blocked", username);
}
return user;
});
private SysUserVo loadUserByUsername(String username) {
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, username));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
throw new UserException("user.not.exists", username);
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new UserException("user.blocked", username);
}
return user;
}
}

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;
@ -21,7 +21,6 @@ import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.SysClient;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo;
@ -51,13 +50,12 @@ public class SmsAuthStrategy implements IAuthStrategy {
String tenantId = loginBody.getTenantId();
String phonenumber = loginBody.getPhonenumber();
String smsCode = loginBody.getSmsCode();
// 通过手机号查找用户
SysUserVo user = loadUserByPhonenumber(tenantId, phonenumber);
loginService.checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
LoginUser loginUser = loginService.buildLoginUser(user);
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
SysUserVo user = loadUserByPhonenumber(phonenumber);
loginService.checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
return loginService.buildLoginUser(user);
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
@ -89,18 +87,16 @@ public class SmsAuthStrategy implements IAuthStrategy {
return code.equals(smsCode);
}
private SysUserVo loadUserByPhonenumber(String tenantId, String phonenumber) {
return TenantHelper.dynamic(tenantId, () -> {
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, phonenumber));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", phonenumber);
throw new UserException("user.not.exists", phonenumber);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", phonenumber);
throw new UserException("user.blocked", phonenumber);
}
return user;
});
private SysUserVo loadUserByPhonenumber(String phonenumber) {
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, phonenumber));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", phonenumber);
throw new UserException("user.not.exists", phonenumber);
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", phonenumber);
throw new UserException("user.blocked", phonenumber);
}
return user;
}
}

View File

@ -11,11 +11,12 @@ 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;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.satoken.utils.LoginHelper;
@ -83,7 +84,7 @@ public class SocialAuthStrategy implements IAuthStrategy {
}
SysSocialVo social;
if (TenantHelper.isEnable()) {
Optional<SysSocialVo> opt = list.stream().filter(x -> x.getTenantId().equals(loginBody.getTenantId())).findAny();
Optional<SysSocialVo> opt = StreamUtils.findAny(list, x -> x.getTenantId().equals(loginBody.getTenantId()));
if (opt.isEmpty()) {
throw new ServiceException("对不起,你没有权限登录当前租户!");
}
@ -91,11 +92,11 @@ public class SocialAuthStrategy implements IAuthStrategy {
} else {
social = list.get(0);
}
// 查找用户
SysUserVo user = loadUser(social.getTenantId(), social.getUserId());
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
LoginUser loginUser = loginService.buildLoginUser(user);
LoginUser loginUser = TenantHelper.dynamic(social.getTenantId(), () -> {
SysUserVo user = loadUser(social.getUserId());
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
return loginService.buildLoginUser(user);
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
@ -115,18 +116,16 @@ public class SocialAuthStrategy implements IAuthStrategy {
return loginVo;
}
private SysUserVo loadUser(String tenantId, Long userId) {
return TenantHelper.dynamic(tenantId, () -> {
SysUserVo user = userMapper.selectVoById(userId);
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", "");
throw new UserException("user.not.exists", "");
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", "");
throw new UserException("user.blocked", "");
}
return user;
});
private SysUserVo loadUser(Long userId) {
SysUserVo user = userMapper.selectVoById(userId);
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", "");
throw new UserException("user.not.exists", "");
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", "");
throw new UserException("user.blocked", "");
}
return user;
}
}

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,26 +2,33 @@
spring.boot.admin.client:
# 增加客户端开关
enabled: true
url: http://localhost:9090/admin
url: http://localhost:9090
instance:
service-host-type: IP
username: ruoyi
password: 123456
metadata:
username: ${spring.boot.admin.client.username}
userpassword: ${spring.boot.admin.client.password}
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: 1788
# 详见 script/sql/snail_job.sql `sj_namespace`
port: 17888
# 命名空间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:
@ -45,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
@ -96,8 +103,8 @@ spring.data:
port: 6379
# 数据库索引
database: 0
# 密码(如没有密码请注释掉)
# password:
# redis 密码必须配置
password: ruoyi123
# 连接超时时间
timeout: 10s
# 是否开启ssl
@ -195,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,26 +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
username: ruoyi
password: 123456
metadata:
username: ${spring.boot.admin.client.username}
userpassword: ${spring.boot.admin.client.password}
username: @monitor.username@
password: @monitor.password@
--- # snail-job 配置
snail-job:
enabled: false
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: 1788
# 详见 script/sql/snail_job.sql `sj_namespace`
port: 17888
# 命名空间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 +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
@ -99,8 +106,8 @@ spring.data:
port: 6379
# 数据库索引
database: 0
# 密码(如没有密码请注释掉)
# password:
# redis 密码必须配置
password: ruoyi123
# 连接超时时间
timeout: 10s
# 是否开启ssl

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,20 +111,15 @@ sa-token:
security:
# 排除路径
excludes:
# 静态资源
- /*.html
- /**/*.html
- /**/*.css
- /**/*.js
# 公共路径
- /favicon.ico
- /error
# swagger 文档配置
- /*/api-docs
- /*/api-docs/**
# actuator 监控配置
- /actuator
- /actuator/**
- /warm-flow-ui/token-name
# 多租户配置
tenant:
@ -144,6 +140,8 @@ tenant:
# MyBatisPlus配置
# https://baomidou.com/config/
mybatis-plus:
# 自定义配置 是否全局开启逻辑删除 关闭后 所有逻辑删除功能将失效
enableLogicDelete: true
# 多包名使用 例如 org.dromara.**.mapper,org.xxx.**.mapper
mapperPackage: org.dromara.**.mapper
# 对应的 XML 文件位置
@ -220,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请直接使用虚拟线程 不要开启此配置
@ -259,29 +259,25 @@ management:
logfile:
external-file: ./logs/sys-console.log
--- # 默认/推荐使用sse推送
sse:
enabled: true
path: /resource/sse
--- # websocket
websocket:
# 如果关闭 需要和前端开关一起关闭
enabled: true
enabled: false
# 路径
path: /resource/websocket
# 设置访问源地址
allowedOrigins: '*'
--- #flowable配置
flowable:
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
--- # warm-flow工作流配置
warm-flow:
# 是否开启工作流默认true
enabled: true
# 是否开启设计器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

@ -33,6 +33,7 @@
<module>ruoyi-common-encrypt</module>
<module>ruoyi-common-tenant</module>
<module>ruoyi-common-websocket</module>
<module>ruoyi-common-sse</module>
</modules>
<artifactId>ruoyi-common</artifactId>

View File

@ -14,7 +14,7 @@
</description>
<properties>
<revision>5.2.0</revision>
<revision>5.3.0</revision>
</properties>
<dependencyManagement>
@ -172,6 +172,13 @@
<version>${revision}</version>
</dependency>
<!-- SSE模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-sse</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@ -94,11 +94,6 @@
<artifactId>ip2region</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
</dependency>
</dependencies>
</project>

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

@ -22,4 +22,9 @@ public interface CacheConstants {
*/
String SYS_DICT_KEY = "sys_dict:";
/**
* 登录账户密码错误次数 redis key
*/
String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
}

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

@ -27,11 +27,6 @@ public interface GlobalConstants {
*/
String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:";
/**
* 登录账户密码错误次数 redis key
*/
String PWD_ERR_CNT_KEY = GLOBAL_REDIS_KEY + "pwd_err_cnt:";
/**
* 三方认证 redis key
*/

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,142 +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 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

@ -66,4 +66,29 @@ public interface UserService {
* @return 用户ids
*/
List<Long> selectUserIdsByRoleIds(List<Long> roleIds);
/**
* 通过角色ID查询用户
*
* @param roleIds 角色ids
* @return 用户
*/
List<UserDTO> selectUsersByRoleIds(List<Long> roleIds);
/**
* 通过部门ID查询用户
*
* @param deptIds 部门ids
* @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

@ -7,6 +7,7 @@ import lombok.NoArgsConstructor;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@ -34,6 +35,34 @@ public class StreamUtils {
return collection.stream().filter(function).collect(Collectors.toList());
}
/**
* 找到流中满足条件的第一个元素
*
* @param collection 需要查询的集合
* @param function 过滤方法
* @return 找到符合条件的第一个元素没有则返回null
*/
public static <E> E findFirst(Collection<E> collection, Predicate<E> function) {
if (CollUtil.isEmpty(collection)) {
return null;
}
return collection.stream().filter(function).findFirst().orElse(null);
}
/**
* 找到流中任意一个满足条件的元素
*
* @param collection 需要查询的集合
* @param function 过滤方法
* @return 找到符合条件的任意一个元素没有则返回null
*/
public static <E> Optional<E> findAny(Collection<E> collection, Predicate<E> function) {
if (CollUtil.isEmpty(collection)) {
return Optional.empty();
}
return collection.stream().filter(function).findAny();
}
/**
* 将collection拼接
*

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

@ -11,6 +11,7 @@ import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.oas.models.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
import org.springdoc.core.properties.SpringDocConfigProperties;
@ -230,7 +231,7 @@ public class OpenApiHandler extends OpenAPIService {
.flatMap(x -> Stream.of(x.value())).collect(Collectors.toSet());
methodTags.addAll(AnnotatedElementUtils.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class));
if (!CollectionUtils.isEmpty(methodTags)) {
tagsStr.addAll(methodTags.stream().map(tag -> propertyResolverUtils.resolve(tag.name(), locale)).collect(Collectors.toSet()));
tagsStr.addAll(StreamUtils.toSet(methodTags, tag -> propertyResolverUtils.resolve(tag.name(), locale)));
List<io.swagger.v3.oas.annotations.tags.Tag> allTags = new ArrayList<>(methodTags);
addTags(allTags, tags, locale);
}

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

@ -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

@ -107,7 +107,7 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements Workbook
}
if (!cellValue.equals(val)) {
if ((i - repeatCell.getCurrent() > 1) && isMerge(list, i, field)) {
if ((i - repeatCell.getCurrent() > 1)) {
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
}
map.put(field, new RepeatCell(val, i));
@ -115,6 +115,11 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements Workbook
if (i > repeatCell.getCurrent() && isMerge(list, i, field)) {
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
}
} else if (!isMerge(list, i, field)) {
if ((i - repeatCell.getCurrent() > 1)) {
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
}
map.put(field, new RepeatCell(val, i));
}
}
}

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

@ -1,7 +1,7 @@
package org.dromara.common.mail.config;
import cn.hutool.extra.mail.MailAccount;
import org.dromara.common.mail.config.properties.MailProperties;
import org.dromara.common.mail.utils.MailAccount;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

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

@ -1,46 +0,0 @@
package org.dromara.common.mail.utils;
import cn.hutool.core.io.IORuntimeException;
/**
* 全局邮件帐户,依赖于邮件配置文件{@link MailAccount#MAIL_SETTING_PATHS}
*
* @author looly
*/
public enum GlobalMailAccount {
INSTANCE;
private final MailAccount mailAccount;
/**
* 构造
*/
GlobalMailAccount() {
mailAccount = createDefaultAccount();
}
/**
* 获得邮件帐户
*
* @return 邮件帐户
*/
public MailAccount getAccount() {
return this.mailAccount;
}
/**
* 创建默认帐户
*
* @return MailAccount
*/
private MailAccount createDefaultAccount() {
for (String mailSettingPath : MailAccount.MAIL_SETTING_PATHS) {
try {
return new MailAccount(mailSettingPath);
} catch (IORuntimeException ignore) {
//ignore
}
}
return null;
}
}

View File

@ -1,108 +0,0 @@
package org.dromara.common.mail.utils;
import cn.hutool.core.util.ArrayUtil;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeUtility;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 邮件内部工具类
*
* @author looly
* @since 3.2.3
*/
public class InternalMailUtil {
/**
* 将多个字符串邮件地址转为{@link InternetAddress}列表<br>
* 单个字符串地址可以是多个地址合并的字符串
*
* @param addrStrs 地址数组
* @param charset 编码(主要用于中文用户名的编码)
* @return 地址数组
* @since 4.0.3
*/
public static InternetAddress[] parseAddressFromStrs(String[] addrStrs, Charset charset) {
final List<InternetAddress> resultList = new ArrayList<>(addrStrs.length);
InternetAddress[] addrs;
for (String addrStr : addrStrs) {
addrs = parseAddress(addrStr, charset);
if (ArrayUtil.isNotEmpty(addrs)) {
Collections.addAll(resultList, addrs);
}
}
return resultList.toArray(new InternetAddress[0]);
}
/**
* 解析第一个地址
*
* @param address 地址字符串
* @param charset 编码,{@code null}表示使用系统属性定义的编码或系统编码
* @return 地址列表
*/
public static InternetAddress parseFirstAddress(String address, Charset charset) {
final InternetAddress[] internetAddresses = parseAddress(address, charset);
if (ArrayUtil.isEmpty(internetAddresses)) {
try {
return new InternetAddress(address);
} catch (AddressException e) {
throw new MailException(e);
}
}
return internetAddresses[0];
}
/**
* 将一个地址字符串解析为多个地址<br>
* 地址间使用" "、","、";"分隔
*
* @param address 地址字符串
* @param charset 编码,{@code null}表示使用系统属性定义的编码或系统编码
* @return 地址列表
*/
public static InternetAddress[] parseAddress(String address, Charset charset) {
InternetAddress[] addresses;
try {
addresses = InternetAddress.parse(address);
} catch (AddressException e) {
throw new MailException(e);
}
//编码用户名
if (ArrayUtil.isNotEmpty(addresses)) {
final String charsetStr = null == charset ? null : charset.name();
for (InternetAddress internetAddress : addresses) {
try {
internetAddress.setPersonal(internetAddress.getPersonal(), charsetStr);
} catch (UnsupportedEncodingException e) {
throw new MailException(e);
}
}
}
return addresses;
}
/**
* 编码中文字符<br>
* 编码失败返回原字符串
*
* @param text 被编码的文本
* @param charset 编码
* @return 编码后的结果
*/
public static String encodeText(String text, Charset charset) {
try {
return MimeUtility.encodeText(text, charset.name(), null);
} catch (UnsupportedEncodingException e) {
// ignore
}
return text;
}
}

View File

@ -1,483 +0,0 @@
package org.dromara.common.mail.utils;
import cn.hutool.core.builder.Builder;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import jakarta.activation.DataHandler;
import jakarta.activation.DataSource;
import jakarta.activation.FileDataSource;
import jakarta.activation.FileTypeMap;
import jakarta.mail.*;
import jakarta.mail.internet.MimeBodyPart;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.MimeMultipart;
import jakarta.mail.internet.MimeUtility;
import jakarta.mail.util.ByteArrayDataSource;
import java.io.*;
import java.nio.charset.Charset;
import java.util.Date;
/**
* 邮件发送客户端
*
* @author looly
* @since 3.2.0
*/
public class Mail implements Builder<MimeMessage> {
@Serial
private static final long serialVersionUID = 1L;
/**
* 邮箱帐户信息以及一些客户端配置信息
*/
private final MailAccount mailAccount;
/**
* 收件人列表
*/
private String[] tos;
/**
* 抄送人列表carbon copy
*/
private String[] ccs;
/**
* 密送人列表blind carbon copy
*/
private String[] bccs;
/**
* 回复地址(reply-to)
*/
private String[] reply;
/**
* 标题
*/
private String title;
/**
* 内容
*/
private String content;
/**
* 是否为HTML
*/
private boolean isHtml;
/**
* 正文、附件和图片的混合部分
*/
private final Multipart multipart = new MimeMultipart();
/**
* 是否使用全局会话默认为false
*/
private boolean useGlobalSession = false;
/**
* debug输出位置可以自定义debug日志
*/
private PrintStream debugOutput;
/**
* 创建邮件客户端
*
* @param mailAccount 邮件帐号
* @return Mail
*/
public static Mail create(MailAccount mailAccount) {
return new Mail(mailAccount);
}
/**
* 创建邮件客户端,使用全局邮件帐户
*
* @return Mail
*/
public static Mail create() {
return new Mail();
}
// --------------------------------------------------------------- Constructor start
/**
* 构造,使用全局邮件帐户
*/
public Mail() {
this(GlobalMailAccount.INSTANCE.getAccount());
}
/**
* 构造
*
* @param mailAccount 邮件帐户如果为null使用默认配置文件的全局邮件配置
*/
public Mail(MailAccount mailAccount) {
mailAccount = (null != mailAccount) ? mailAccount : GlobalMailAccount.INSTANCE.getAccount();
this.mailAccount = mailAccount.defaultIfEmpty();
}
// --------------------------------------------------------------- Constructor end
// --------------------------------------------------------------- Getters and Setters start
/**
* 设置收件人
*
* @param tos 收件人列表
* @return this
* @see #setTos(String...)
*/
public Mail to(String... tos) {
return setTos(tos);
}
/**
* 设置多个收件人
*
* @param tos 收件人列表
* @return this
*/
public Mail setTos(String... tos) {
this.tos = tos;
return this;
}
/**
* 设置多个抄送人carbon copy
*
* @param ccs 抄送人列表
* @return this
* @since 4.0.3
*/
public Mail setCcs(String... ccs) {
this.ccs = ccs;
return this;
}
/**
* 设置多个密送人blind carbon copy
*
* @param bccs 密送人列表
* @return this
* @since 4.0.3
*/
public Mail setBccs(String... bccs) {
this.bccs = bccs;
return this;
}
/**
* 设置多个回复地址(reply-to)
*
* @param reply 回复地址(reply-to)列表
* @return this
* @since 4.6.0
*/
public Mail setReply(String... reply) {
this.reply = reply;
return this;
}
/**
* 设置标题
*
* @param title 标题
* @return this
*/
public Mail setTitle(String title) {
this.title = title;
return this;
}
/**
* 设置正文<br>
* 正文可以是普通文本也可以是HTML默认普通文本可以通过调用{@link #setHtml(boolean)} 设置是否为HTML
*
* @param content 正文
* @return this
*/
public Mail setContent(String content) {
this.content = content;
return this;
}
/**
* 设置是否是HTML
*
* @param isHtml 是否为HTML
* @return this
*/
public Mail setHtml(boolean isHtml) {
this.isHtml = isHtml;
return this;
}
/**
* 设置正文
*
* @param content 正文内容
* @param isHtml 是否为HTML
* @return this
*/
public Mail setContent(String content, boolean isHtml) {
setContent(content);
return setHtml(isHtml);
}
/**
* 设置文件类型附件文件可以是图片文件此时自动设置cid正文中引用图片默认cid为文件名
*
* @param files 附件文件列表
* @return this
*/
public Mail setFiles(File... files) {
if (ArrayUtil.isEmpty(files)) {
return this;
}
final DataSource[] attachments = new DataSource[files.length];
for (int i = 0; i < files.length; i++) {
attachments[i] = new FileDataSource(files[i]);
}
return setAttachments(attachments);
}
/**
* 增加附件或图片,附件使用{@link DataSource} 形式表示,可以使用{@link FileDataSource}包装文件表示文件附件
*
* @param attachments 附件列表
* @return this
* @since 4.0.9
*/
public Mail setAttachments(DataSource... attachments) {
if (ArrayUtil.isNotEmpty(attachments)) {
final Charset charset = this.mailAccount.getCharset();
MimeBodyPart bodyPart;
String nameEncoded;
try {
for (DataSource attachment : attachments) {
bodyPart = new MimeBodyPart();
bodyPart.setDataHandler(new DataHandler(attachment));
nameEncoded = attachment.getName();
if (this.mailAccount.isEncodefilename()) {
nameEncoded = InternalMailUtil.encodeText(nameEncoded, charset);
}
// 普通附件文件名
bodyPart.setFileName(nameEncoded);
if (StrUtil.startWith(attachment.getContentType(), "image/")) {
// 图片附件,用于正文中引用图片
bodyPart.setContentID(nameEncoded);
}
this.multipart.addBodyPart(bodyPart);
}
} catch (MessagingException e) {
throw new MailException(e);
}
}
return this;
}
/**
* 增加图片,图片的键对应到邮件模板中的占位字符串,图片类型默认为"image/jpeg"
*
* @param cid 图片与占位符占位符格式为cid:${cid}
* @param imageStream 图片文件
* @return this
* @since 4.6.3
*/
public Mail addImage(String cid, InputStream imageStream) {
return addImage(cid, imageStream, null);
}
/**
* 增加图片,图片的键对应到邮件模板中的占位字符串
*
* @param cid 图片与占位符占位符格式为cid:${cid}
* @param imageStream 图片流,不关闭
* @param contentType 图片类型null赋值默认的"image/jpeg"
* @return this
* @since 4.6.3
*/
public Mail addImage(String cid, InputStream imageStream, String contentType) {
ByteArrayDataSource imgSource;
try {
imgSource = new ByteArrayDataSource(imageStream, ObjectUtil.defaultIfNull(contentType, "image/jpeg"));
} catch (IOException e) {
throw new IORuntimeException(e);
}
imgSource.setName(cid);
return setAttachments(imgSource);
}
/**
* 增加图片,图片的键对应到邮件模板中的占位字符串
*
* @param cid 图片与占位符占位符格式为cid:${cid}
* @param imageFile 图片文件
* @return this
* @since 4.6.3
*/
public Mail addImage(String cid, File imageFile) {
InputStream in = null;
try {
in = FileUtil.getInputStream(imageFile);
return addImage(cid, in, FileTypeMap.getDefaultFileTypeMap().getContentType(imageFile));
} finally {
IoUtil.close(in);
}
}
/**
* 设置字符集编码
*
* @param charset 字符集编码
* @return this
* @see MailAccount#setCharset(Charset)
*/
public Mail setCharset(Charset charset) {
this.mailAccount.setCharset(charset);
return this;
}
/**
* 设置是否使用全局会话默认为true
*
* @param isUseGlobalSession 是否使用全局会话默认为true
* @return this
* @since 4.0.2
*/
public Mail setUseGlobalSession(boolean isUseGlobalSession) {
this.useGlobalSession = isUseGlobalSession;
return this;
}
/**
* 设置debug输出位置可以自定义debug日志
*
* @param debugOutput debug输出位置
* @return this
* @since 5.5.6
*/
public Mail setDebugOutput(PrintStream debugOutput) {
this.debugOutput = debugOutput;
return this;
}
// --------------------------------------------------------------- Getters and Setters end
@Override
public MimeMessage build() {
try {
return buildMsg();
} catch (MessagingException e) {
throw new MailException(e);
}
}
/**
* 发送
*
* @return message-id
* @throws MailException 邮件发送异常
*/
public String send() throws MailException {
try {
return doSend();
} catch (MessagingException e) {
if (e instanceof SendFailedException) {
// 当地址无效时,显示更加详细的无效地址信息
final Address[] invalidAddresses = ((SendFailedException) e).getInvalidAddresses();
final String msg = StrUtil.format("Invalid Addresses: {}", ArrayUtil.toString(invalidAddresses));
throw new MailException(msg, e);
}
throw new MailException(e);
}
}
// --------------------------------------------------------------- Private method start
/**
* 执行发送
*
* @return message-id
* @throws MessagingException 发送异常
*/
private String doSend() throws MessagingException {
final MimeMessage mimeMessage = buildMsg();
Transport.send(mimeMessage);
return mimeMessage.getMessageID();
}
/**
* 构建消息
*
* @return {@link MimeMessage}消息
* @throws MessagingException 消息异常
*/
private MimeMessage buildMsg() throws MessagingException {
final Charset charset = this.mailAccount.getCharset();
final MimeMessage msg = new MimeMessage(getSession());
// 发件人
final String from = this.mailAccount.getFrom();
if (StrUtil.isEmpty(from)) {
// 用户未提供发送方则从Session中自动获取
msg.setFrom();
} else {
msg.setFrom(InternalMailUtil.parseFirstAddress(from, charset));
}
// 标题
msg.setSubject(this.title, (null == charset) ? null : charset.name());
// 发送时间
msg.setSentDate(new Date());
// 内容和附件
msg.setContent(buildContent(charset));
// 收件人
msg.setRecipients(MimeMessage.RecipientType.TO, InternalMailUtil.parseAddressFromStrs(this.tos, charset));
// 抄送人
if (ArrayUtil.isNotEmpty(this.ccs)) {
msg.setRecipients(MimeMessage.RecipientType.CC, InternalMailUtil.parseAddressFromStrs(this.ccs, charset));
}
// 密送人
if (ArrayUtil.isNotEmpty(this.bccs)) {
msg.setRecipients(MimeMessage.RecipientType.BCC, InternalMailUtil.parseAddressFromStrs(this.bccs, charset));
}
// 回复地址(reply-to)
if (ArrayUtil.isNotEmpty(this.reply)) {
msg.setReplyTo(InternalMailUtil.parseAddressFromStrs(this.reply, charset));
}
return msg;
}
/**
* 构建邮件信息主体
*
* @param charset 编码,{@code null}则使用{@link MimeUtility#getDefaultJavaCharset()}
* @return 邮件信息主体
* @throws MessagingException 消息异常
*/
private Multipart buildContent(Charset charset) throws MessagingException {
final String charsetStr = null != charset ? charset.name() : MimeUtility.getDefaultJavaCharset();
// 正文
final MimeBodyPart body = new MimeBodyPart();
body.setContent(content, StrUtil.format("text/{}; charset={}", isHtml ? "html" : "plain", charsetStr));
this.multipart.addBodyPart(body);
return this.multipart;
}
/**
* 获取默认邮件会话<br>
* 如果为全局单例的会话,则全局只允许一个邮件帐号,否则每次发送邮件会新建一个新的会话
*
* @return 邮件会话 {@link Session}
*/
private Session getSession() {
final Session session = MailUtils.getSession(this.mailAccount, this.useGlobalSession);
if (null != this.debugOutput) {
session.setDebugOut(debugOutput);
}
return session;
}
// --------------------------------------------------------------- Private method end
}

View File

@ -1,659 +0,0 @@
package org.dromara.common.mail.utils;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.setting.Setting;
import java.io.Serial;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* 邮件账户对象
*
* @author Luxiaolei
*/
public class MailAccount implements Serializable {
@Serial
private static final long serialVersionUID = -6937313421815719204L;
private static final String MAIL_PROTOCOL = "mail.transport.protocol";
private static final String SMTP_HOST = "mail.smtp.host";
private static final String SMTP_PORT = "mail.smtp.port";
private static final String SMTP_AUTH = "mail.smtp.auth";
private static final String SMTP_TIMEOUT = "mail.smtp.timeout";
private static final String SMTP_CONNECTION_TIMEOUT = "mail.smtp.connectiontimeout";
private static final String SMTP_WRITE_TIMEOUT = "mail.smtp.writetimeout";
// SSL
private static final String STARTTLS_ENABLE = "mail.smtp.starttls.enable";
private static final String SSL_ENABLE = "mail.smtp.ssl.enable";
private static final String SSL_PROTOCOLS = "mail.smtp.ssl.protocols";
private static final String SOCKET_FACTORY = "mail.smtp.socketFactory.class";
private static final String SOCKET_FACTORY_FALLBACK = "mail.smtp.socketFactory.fallback";
private static final String SOCKET_FACTORY_PORT = "smtp.socketFactory.port";
// System Properties
private static final String SPLIT_LONG_PARAMS = "mail.mime.splitlongparameters";
//private static final String ENCODE_FILE_NAME = "mail.mime.encodefilename";
//private static final String CHARSET = "mail.mime.charset";
// 其他
private static final String MAIL_DEBUG = "mail.debug";
public static final String[] MAIL_SETTING_PATHS = new String[]{"config/mail.setting", "config/mailAccount.setting", "mail.setting"};
/**
* SMTP服务器域名
*/
private String host;
/**
* SMTP服务端口
*/
private Integer port;
/**
* 是否需要用户名密码验证
*/
private Boolean auth;
/**
* 用户名
*/
private String user;
/**
* 密码
*/
private String pass;
/**
* 发送方遵循RFC-822标准
*/
private String from;
/**
* 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
*/
private boolean debug;
/**
* 编码用于编码邮件正文和发送人、收件人等中文
*/
private Charset charset = CharsetUtil.CHARSET_UTF_8;
/**
* 对于超长参数是否切分为多份默认为false国内邮箱附件不支持切分的附件名
*/
private boolean splitlongparameters = false;
/**
* 对于文件名是否使用{@link #charset}编码,默认为 {@code true}
*/
private boolean encodefilename = true;
/**
* 使用 STARTTLS安全连接STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接TLS或SSL 而不是使用一个单独的加密通信端口。
*/
private boolean starttlsEnable = false;
/**
* 使用 SSL安全连接
*/
private Boolean sslEnable;
/**
* SSL协议多个协议用空格分隔
*/
private String sslProtocols;
/**
* 指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字
*/
private String socketFactoryClass = "javax.net.ssl.SSLSocketFactory";
/**
* 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
*/
private boolean socketFactoryFallback;
/**
* 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
*/
private int socketFactoryPort = 465;
/**
* SMTP超时时长单位毫秒缺省值不超时
*/
private long timeout;
/**
* Socket连接超时值单位毫秒缺省值不超时
*/
private long connectionTimeout;
/**
* Socket写出超时值单位毫秒缺省值不超时
*/
private long writeTimeout;
/**
* 自定义的其他属性,此自定义属性会覆盖默认属性
*/
private final Map<String, Object> customProperty = new HashMap<>();
// -------------------------------------------------------------- Constructor start
/**
* 构造,所有参数需自行定义或保持默认值
*/
public MailAccount() {
}
/**
* 构造
*
* @param settingPath 配置文件路径
*/
public MailAccount(String settingPath) {
this(new Setting(settingPath));
}
/**
* 构造
*
* @param setting 配置文件
*/
public MailAccount(Setting setting) {
setting.toBean(this);
}
// -------------------------------------------------------------- Constructor end
/**
* 获得SMTP服务器域名
*
* @return SMTP服务器域名
*/
public String getHost() {
return host;
}
/**
* 设置SMTP服务器域名
*
* @param host SMTP服务器域名
* @return this
*/
public MailAccount setHost(String host) {
this.host = host;
return this;
}
/**
* 获得SMTP服务端口
*
* @return SMTP服务端口
*/
public Integer getPort() {
return port;
}
/**
* 设置SMTP服务端口
*
* @param port SMTP服务端口
* @return this
*/
public MailAccount setPort(Integer port) {
this.port = port;
return this;
}
/**
* 是否需要用户名密码验证
*
* @return 是否需要用户名密码验证
*/
public Boolean isAuth() {
return auth;
}
/**
* 设置是否需要用户名密码验证
*
* @param isAuth 是否需要用户名密码验证
* @return this
*/
public MailAccount setAuth(boolean isAuth) {
this.auth = isAuth;
return this;
}
/**
* 获取用户名
*
* @return 用户名
*/
public String getUser() {
return user;
}
/**
* 设置用户名
*
* @param user 用户名
* @return this
*/
public MailAccount setUser(String user) {
this.user = user;
return this;
}
/**
* 获取密码
*
* @return 密码
*/
public String getPass() {
return pass;
}
/**
* 设置密码
*
* @param pass 密码
* @return this
*/
public MailAccount setPass(String pass) {
this.pass = pass;
return this;
}
/**
* 获取发送方遵循RFC-822标准
*
* @return 发送方遵循RFC-822标准
*/
public String getFrom() {
return from;
}
/**
* 设置发送方遵循RFC-822标准<br>
* 发件人可以是以下形式:
*
* <pre>
* 1. user@xxx.xx
* 2. name &lt;user@xxx.xx&gt;
* </pre>
*
* @param from 发送方遵循RFC-822标准
* @return this
*/
public MailAccount setFrom(String from) {
this.from = from;
return this;
}
/**
* 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
*
* @return 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
* @since 4.0.2
*/
public boolean isDebug() {
return debug;
}
/**
* 设置是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
*
* @param debug 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
* @return this
* @since 4.0.2
*/
public MailAccount setDebug(boolean debug) {
this.debug = debug;
return this;
}
/**
* 获取字符集编码
*
* @return 编码,可能为{@code null}
*/
public Charset getCharset() {
return charset;
}
/**
* 设置字符集编码,此选项不会修改全局配置,若修改全局配置,请设置此项为{@code null}并设置:
* <pre>
* System.setProperty("mail.mime.charset", charset);
* </pre>
*
* @param charset 字符集编码,{@code null} 则表示使用全局设置的默认编码全局编码为mail.mime.charset系统属性
* @return this
*/
public MailAccount setCharset(Charset charset) {
this.charset = charset;
return this;
}
/**
* 对于超长参数是否切分为多份默认为false国内邮箱附件不支持切分的附件名
*
* @return 对于超长参数是否切分为多份
*/
public boolean isSplitlongparameters() {
return splitlongparameters;
}
/**
* 设置对于超长参数是否切分为多份默认为false国内邮箱附件不支持切分的附件名<br>
* 注意此项为全局设置,此项会调用
* <pre>
* System.setProperty("mail.mime.splitlongparameters", true)
* </pre>
*
* @param splitlongparameters 对于超长参数是否切分为多份
*/
public void setSplitlongparameters(boolean splitlongparameters) {
this.splitlongparameters = splitlongparameters;
}
/**
* 对于文件名是否使用{@link #charset}编码,默认为 {@code true}
*
* @return 对于文件名是否使用{@link #charset}编码,默认为 {@code true}
* @since 5.7.16
*/
public boolean isEncodefilename() {
return encodefilename;
}
/**
* 设置对于文件名是否使用{@link #charset}编码,此选项不会修改全局配置<br>
* 如果此选项设置为{@code false},则是否编码取决于两个系统属性:
* <ul>
* <li>mail.mime.encodefilename 是否编码附件文件名</li>
* <li>mail.mime.charset 编码文件名的编码</li>
* </ul>
*
* @param encodefilename 对于文件名是否使用{@link #charset}编码
* @since 5.7.16
*/
public void setEncodefilename(boolean encodefilename) {
this.encodefilename = encodefilename;
}
/**
* 是否使用 STARTTLS安全连接STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接TLS或SSL 而不是使用一个单独的加密通信端口。
*
* @return 是否使用 STARTTLS安全连接
*/
public boolean isStarttlsEnable() {
return this.starttlsEnable;
}
/**
* 设置是否使用STARTTLS安全连接STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接TLS或SSL 而不是使用一个单独的加密通信端口。
*
* @param startttlsEnable 是否使用STARTTLS安全连接
* @return this
*/
public MailAccount setStarttlsEnable(boolean startttlsEnable) {
this.starttlsEnable = startttlsEnable;
return this;
}
/**
* 是否使用 SSL安全连接
*
* @return 是否使用 SSL安全连接
*/
public Boolean isSslEnable() {
return this.sslEnable;
}
/**
* 设置是否使用SSL安全连接
*
* @param sslEnable 是否使用SSL安全连接
* @return this
*/
public MailAccount setSslEnable(Boolean sslEnable) {
this.sslEnable = sslEnable;
return this;
}
/**
* 获取SSL协议多个协议用空格分隔
*
* @return SSL协议多个协议用空格分隔
* @since 5.5.7
*/
public String getSslProtocols() {
return sslProtocols;
}
/**
* 设置SSL协议多个协议用空格分隔
*
* @param sslProtocols SSL协议多个协议用空格分隔
* @since 5.5.7
*/
public void setSslProtocols(String sslProtocols) {
this.sslProtocols = sslProtocols;
}
/**
* 获取指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字
*
* @return 指定实现javax.net.SocketFactory接口的类的名称, 这个类将被用于创建SMTP的套接字
*/
public String getSocketFactoryClass() {
return socketFactoryClass;
}
/**
* 设置指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字
*
* @param socketFactoryClass 指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字
* @return this
*/
public MailAccount setSocketFactoryClass(String socketFactoryClass) {
this.socketFactoryClass = socketFactoryClass;
return this;
}
/**
* 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
*
* @return 如果设置为true, 未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
*/
public boolean isSocketFactoryFallback() {
return socketFactoryFallback;
}
/**
* 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
*
* @param socketFactoryFallback 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
* @return this
*/
public MailAccount setSocketFactoryFallback(boolean socketFactoryFallback) {
this.socketFactoryFallback = socketFactoryFallback;
return this;
}
/**
* 获取指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
*
* @return 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
*/
public int getSocketFactoryPort() {
return socketFactoryPort;
}
/**
* 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
*
* @param socketFactoryPort 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
* @return this
*/
public MailAccount setSocketFactoryPort(int socketFactoryPort) {
this.socketFactoryPort = socketFactoryPort;
return this;
}
/**
* 设置SMTP超时时长单位毫秒缺省值不超时
*
* @param timeout SMTP超时时长单位毫秒缺省值不超时
* @return this
* @since 4.1.17
*/
public MailAccount setTimeout(long timeout) {
this.timeout = timeout;
return this;
}
/**
* 设置Socket连接超时值单位毫秒缺省值不超时
*
* @param connectionTimeout Socket连接超时值单位毫秒缺省值不超时
* @return this
* @since 4.1.17
*/
public MailAccount setConnectionTimeout(long connectionTimeout) {
this.connectionTimeout = connectionTimeout;
return this;
}
/**
* 设置Socket写出超时值单位毫秒缺省值不超时
*
* @param writeTimeout Socket写出超时值单位毫秒缺省值不超时
* @return this
* @since 5.8.3
*/
public MailAccount setWriteTimeout(long writeTimeout) {
this.writeTimeout = writeTimeout;
return this;
}
/**
* 获取自定义属性列表
*
* @return 自定义参数列表
* @since 5.6.4
*/
public Map<String, Object> getCustomProperty() {
return customProperty;
}
/**
* 设置自定义属性如mail.smtp.ssl.socketFactory
*
* @param key 属性名,空白被忽略
* @param value 属性值, null被忽略
* @return this
* @since 5.6.4
*/
public MailAccount setCustomProperty(String key, Object value) {
if (StrUtil.isNotBlank(key) && ObjectUtil.isNotNull(value)) {
this.customProperty.put(key, value);
}
return this;
}
/**
* 获得SMTP相关信息
*
* @return {@link Properties}
*/
public Properties getSmtpProps() {
//全局系统参数
System.setProperty(SPLIT_LONG_PARAMS, String.valueOf(this.splitlongparameters));
final Properties p = new Properties();
p.put(MAIL_PROTOCOL, "smtp");
p.put(SMTP_HOST, this.host);
p.put(SMTP_PORT, String.valueOf(this.port));
p.put(SMTP_AUTH, String.valueOf(this.auth));
if (this.timeout > 0) {
p.put(SMTP_TIMEOUT, String.valueOf(this.timeout));
}
if (this.connectionTimeout > 0) {
p.put(SMTP_CONNECTION_TIMEOUT, String.valueOf(this.connectionTimeout));
}
// issue#2355
if (this.writeTimeout > 0) {
p.put(SMTP_WRITE_TIMEOUT, String.valueOf(this.writeTimeout));
}
p.put(MAIL_DEBUG, String.valueOf(this.debug));
if (this.starttlsEnable) {
//STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接TLS或SSL 而不是使用一个单独的加密通信端口。
p.put(STARTTLS_ENABLE, "true");
if (null == this.sslEnable) {
//为了兼容旧版本当用户没有此项配置时按照starttlsEnable开启状态时对待
this.sslEnable = true;
}
}
// SSL
if (null != this.sslEnable && this.sslEnable) {
p.put(SSL_ENABLE, "true");
p.put(SOCKET_FACTORY, socketFactoryClass);
p.put(SOCKET_FACTORY_FALLBACK, String.valueOf(this.socketFactoryFallback));
p.put(SOCKET_FACTORY_PORT, String.valueOf(this.socketFactoryPort));
// issue#IZN95@Gitee在Linux下需自定义SSL协议版本
if (StrUtil.isNotBlank(this.sslProtocols)) {
p.put(SSL_PROTOCOLS, this.sslProtocols);
}
}
// 补充自定义属性,允许自定属性覆盖已经设置的值
p.putAll(this.customProperty);
return p;
}
/**
* 如果某些值为null使用默认值
*
* @return this
*/
public MailAccount defaultIfEmpty() {
// 去掉发件人的姓名部分
final String fromAddress = InternalMailUtil.parseFirstAddress(this.from, this.charset).getAddress();
if (StrUtil.isBlank(this.host)) {
// 如果SMTP地址为空默认使用smtp.<发件人邮箱后缀>
this.host = StrUtil.format("smtp.{}", StrUtil.subSuf(fromAddress, fromAddress.indexOf('@') + 1));
}
if (StrUtil.isBlank(user)) {
// 如果用户名为空默认为发件人issue#I4FYVY@Gitee
//this.user = StrUtil.subPre(fromAddress, fromAddress.indexOf('@'));
this.user = fromAddress;
}
if (null == this.auth) {
// 如果密码非空白,则使用认证模式
this.auth = (false == StrUtil.isBlank(this.pass));
}
if (null == this.port) {
// 端口在SSL状态下默认与socketFactoryPort一致非SSL状态下默认为25
this.port = (null != this.sslEnable && this.sslEnable) ? this.socketFactoryPort : 25;
}
if (null == this.charset) {
// 默认UTF-8编码
this.charset = CharsetUtil.CHARSET_UTF_8;
}
return this;
}
@Override
public String toString() {
return "MailAccount [host=" + host + ", port=" + port + ", auth=" + auth + ", user=" + user + ", pass=" + (StrUtil.isEmpty(this.pass) ? "" : "******") + ", from=" + from + ", startttlsEnable="
+ starttlsEnable + ", socketFactoryClass=" + socketFactoryClass + ", socketFactoryFallback=" + socketFactoryFallback + ", socketFactoryPort=" + socketFactoryPort + "]";
}
}

View File

@ -1,40 +0,0 @@
package org.dromara.common.mail.utils;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import java.io.Serial;
/**
* 邮件异常
*
* @author xiaoleilu
*/
public class MailException extends RuntimeException {
@Serial
private static final long serialVersionUID = 8247610319171014183L;
public MailException(Throwable e) {
super(ExceptionUtil.getMessage(e), e);
}
public MailException(String message) {
super(message);
}
public MailException(String messageTemplate, Object... params) {
super(StrUtil.format(messageTemplate, params));
}
public MailException(String message, Throwable throwable) {
super(message, throwable);
}
public MailException(String message, Throwable throwable, boolean enableSuppression, boolean writableStackTrace) {
super(message, throwable, enableSuppression, writableStackTrace);
}
public MailException(Throwable throwable, String messageTemplate, Object... params) {
super(StrUtil.format(messageTemplate, params), throwable);
}
}

View File

@ -5,6 +5,9 @@ import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.mail.JakartaMail;
import cn.hutool.extra.mail.JakartaUserPassAuthenticator;
import cn.hutool.extra.mail.MailAccount;
import jakarta.mail.Authenticator;
import jakarta.mail.Session;
import lombok.AccessLevel;
@ -17,7 +20,7 @@ import java.io.InputStream;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* 邮件工具类
@ -385,7 +388,7 @@ public class MailUtils {
public static Session getSession(MailAccount mailAccount, boolean isSingleton) {
Authenticator authenticator = null;
if (mailAccount.isAuth()) {
authenticator = new UserPassAuthenticator(mailAccount.getUser(), mailAccount.getPass());
authenticator = new JakartaUserPassAuthenticator(mailAccount.getUser(), mailAccount.getPass());
}
return isSingleton ? Session.getDefaultInstance(mailAccount.getSmtpProps(), authenticator) //
@ -412,7 +415,7 @@ public class MailUtils {
*/
private static String send(MailAccount mailAccount, boolean useGlobalSession, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content,
Map<String, InputStream> imageMap, boolean isHtml, File... files) {
final Mail mail = Mail.create(mailAccount).setUseGlobalSession(useGlobalSession);
final JakartaMail mail = JakartaMail.create(mailAccount).setUseGlobalSession(useGlobalSession);
// 可选抄送人
if (CollUtil.isNotEmpty(ccs)) {
@ -431,7 +434,7 @@ public class MailUtils {
// 图片
if (MapUtil.isNotEmpty(imageMap)) {
for (Map.Entry<String, InputStream> entry : imageMap.entrySet()) {
for (Entry<String, InputStream> entry : imageMap.entrySet()) {
mail.addImage(entry.getKey(), entry.getValue());
// 关闭流
IoUtil.close(entry.getValue());
@ -463,5 +466,4 @@ public class MailUtils {
return result;
}
// ------------------------------------------------------------------------------------------------------------------------ Private method end
}

View File

@ -1,33 +0,0 @@
package org.dromara.common.mail.utils;
import jakarta.mail.Authenticator;
import jakarta.mail.PasswordAuthentication;
/**
* 用户名密码验证器
*
* @author looly
* @since 3.1.2
*/
public class UserPassAuthenticator extends Authenticator {
private final String user;
private final String pass;
/**
* 构造
*
* @param user 用户名
* @param pass 密码
*/
public UserPassAuthenticator(String user, String pass) {
this.user = user;
this.pass = pass;
}
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(this.user, this.pass);
}
}

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

@ -3,9 +3,10 @@ package org.dromara.common.mybatis.annotation;
import java.lang.annotation.*;
/**
* 数据权限
*
* 数据权限注解,用于标记数据权限的占位符关键字和替换值
* <p>
* 一个注解只能对应一个模板
* </p>
*
* @author Lion Li
* @version 3.5.0
@ -16,13 +17,24 @@ import java.lang.annotation.*;
public @interface DataColumn {
/**
* 占位符关键字
* 数据权限模板的占位符关键字,默认为 "deptName"
*
* @return 占位符关键字数组
*/
String[] key() default "deptName";
/**
* 占位符替换值
* 数据权限模板的占位符替换值,默认为 "dept_id"
*
* @return 占位符替换值数组
*/
String[] value() default "dept_id";
/**
* 权限标识符 用于通过菜单权限标识符来获取数据权限
* 拥有此标识符的角色 将不会拼接此角色的数据过滤sql
*
* @return 权限标识符
*/
String permission() default "";
}

View File

@ -3,7 +3,7 @@ package org.dromara.common.mybatis.annotation;
import java.lang.annotation.*;
/**
* 数据权限组
* 数据权限组注解,用于标记数据权限配置数组
*
* @author Lion Li
* @version 3.5.0
@ -13,6 +13,18 @@ import java.lang.annotation.*;
@Documented
public @interface DataPermission {
/**
* 数据权限配置数组,用于指定数据权限的占位符关键字和替换值
*
* @return 数据权限配置数组
*/
DataColumn[] value();
/**
* 权限拼接标识符(用于指定连接语句的sql符号)
* 如不填 默认 select 用 OR 其他语句用 AND
* 内容 OR 或者 AND
*/
String joinStr() default "";
}

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

@ -17,7 +17,6 @@ import java.util.Map;
*
* @author Lion Li
*/
@Data
public class BaseEntity implements Serializable {

View File

@ -12,14 +12,13 @@ import com.baomidou.mybatisplus.extension.toolkit.Db;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StreamUtils;
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 接口, 实现 自定义扩展
@ -34,78 +33,113 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
Log log = LogFactory.getLog(BaseMapperPlus.class);
/**
* 获取当前实例对象关联的泛型类型 V 的 Class 对象
*
* @return 返回当前实例对象关联的泛型类型 V 的 Class 对象
*/
default Class<V> currentVoClass() {
return (Class<V>) GenericTypeUtils.resolveTypeArguments(this.getClass(), BaseMapperPlus.class)[1];
}
/**
* 获取当前实例对象关联的泛型类型 T 的 Class 对象
*
* @return 返回当前实例对象关联的泛型类型 T 的 Class 对象
*/
default Class<T> currentModelClass() {
return (Class<T>) GenericTypeUtils.resolveTypeArguments(this.getClass(), BaseMapperPlus.class)[0];
}
/**
* 使用默认的查询条件查询并返回结果列表
*
* @return 返回查询结果的列表
*/
default List<T> selectList() {
return this.selectList(new QueryWrapper<>());
}
/**
* 批量插入
* 批量插入实体对象集合
*
* @param entityList 实体对象集合
* @return 插入操作是否成功的布尔值
*/
default boolean insertBatch(Collection<T> entityList) {
Db.saveBatch(entityList);
// 临时解决 新版本 mp 插入状态判断错误问题
return true;
return Db.saveBatch(entityList);
}
/**
* 批量更新
* 批量根据ID更新实体对象集合
*
* @param entityList 实体对象集合
* @return 更新操作是否成功的布尔值
*/
default boolean updateBatchById(Collection<T> entityList) {
Db.updateBatchById(entityList);
// 临时解决 新版本 mp 插入状态判断错误问题
return true;
return Db.updateBatchById(entityList);
}
/**
* 批量插入或更新
* 批量插入或更新实体对象集合
*
* @param entityList 实体对象集合
* @return 插入或更新操作是否成功的布尔值
*/
default boolean insertOrUpdateBatch(Collection<T> entityList) {
Db.saveOrUpdateBatch(entityList);
// 临时解决 新版本 mp 插入状态判断错误问题
return true;
return Db.saveOrUpdateBatch(entityList);
}
/**
* 批量插入(包含限制条数)
* 批量插入实体对象集合并指定批处理大小
*
* @param entityList 实体对象集合
* @param batchSize 批处理大小
* @return 插入操作是否成功的布尔值
*/
default boolean insertBatch(Collection<T> entityList, int batchSize) {
Db.saveBatch(entityList, batchSize);
// 临时解决 新版本 mp 插入状态判断错误问题
return true;
return Db.saveBatch(entityList, batchSize);
}
/**
* 批量更新(包含限制条数)
* 批量根据ID更新实体对象集合并指定批处理大小
*
* @param entityList 实体对象集合
* @param batchSize 批处理大小
* @return 更新操作是否成功的布尔值
*/
default boolean updateBatchById(Collection<T> entityList, int batchSize) {
Db.updateBatchById(entityList, batchSize);
// 临时解决 新版本 mp 插入状态判断错误问题
return true;
return Db.updateBatchById(entityList, batchSize);
}
/**
* 批量插入或更新(包含限制条数)
* 批量插入或更新实体对象集合并指定批处理大小
*
* @param entityList 实体对象集合
* @param batchSize 批处理大小
* @return 插入或更新操作是否成功的布尔值
*/
default boolean insertOrUpdateBatch(Collection<T> entityList, int batchSize) {
Db.saveOrUpdateBatch(entityList, batchSize);
// 临时解决 新版本 mp 插入状态判断错误问题
return true;
return Db.saveOrUpdateBatch(entityList, batchSize);
}
/**
* 根据ID查询单个VO对象
*
* @param id 主键ID
* @return 查询到的单个VO对象
*/
default V selectVoById(Serializable id) {
return selectVoById(id, this.currentVoClass());
}
/**
* 根据 ID 查询
* 根据ID查询单个VO对象并将其转换为指定的VO类
*
* @param id 主键ID
* @param voClass 要转换的VO类的Class对象
* @param <C> VO类的类型
* @return 查询到的单个VO对象经过转换为指定的VO类后返回
*/
default <C> C selectVoById(Serializable id, Class<C> voClass) {
T obj = this.selectById(id);
@ -115,27 +149,49 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
return MapstructUtils.convert(obj, voClass);
}
default List<V> selectVoBatchIds(Collection<? extends Serializable> idList) {
return selectVoBatchIds(idList, this.currentVoClass());
/**
* 根据ID集合批量查询VO对象列表
*
* @param idList 主键ID集合
* @return 查询到的VO对象列表
*/
default List<V> selectVoByIds(Collection<? extends Serializable> idList) {
return selectVoByIds(idList, this.currentVoClass());
}
/**
* 查询(根据ID 批量查询
* 根据ID集合批量查询实体对象列表并将其转换为指定的VO对象列表
*
* @param idList 主键ID集合
* @param voClass 要转换的VO类的Class对象
* @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();
}
return MapstructUtils.convert(list, voClass);
}
/**
* 根据查询条件Map查询VO对象列表
*
* @param map 查询条件Map
* @return 查询到的VO对象列表
*/
default List<V> selectVoByMap(Map<String, Object> map) {
return selectVoByMap(map, this.currentVoClass());
}
/**
* 查询(根据 columnMap 条件)
* 根据查询条件Map查询实体对象列表并将其转换为指定的VO对象列表
*
* @param map 查询条件Map
* @param voClass 要转换的VO类的Class对象
* @param <C> VO类的类型
* @return 查询到的VO对象列表经过转换为指定的VO类后返回
*/
default <C> List<C> selectVoByMap(Map<String, Object> map, Class<C> voClass) {
List<T> list = this.selectByMap(map);
@ -145,23 +201,47 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
return MapstructUtils.convert(list, voClass);
}
/**
* 根据条件查询单个VO对象
*
* @param wrapper 查询条件Wrapper
* @return 查询到的单个VO对象
*/
default V selectVoOne(Wrapper<T> wrapper) {
return selectVoOne(wrapper, this.currentVoClass());
}
/**
* 根据条件查询单个VO对象并根据需要决定是否抛出异常
*
* @param wrapper 查询条件Wrapper
* @param throwEx 是否抛出异常的标志
* @return 查询到的单个VO对象
*/
default V selectVoOne(Wrapper<T> wrapper, boolean throwEx) {
return selectVoOne(wrapper, this.currentVoClass(), throwEx);
}
/**
* 根据 entity 条件查询一条记录
* 根据条件查询单个VO对象并指定返回的VO对象的类型
*
* @param wrapper 查询条件Wrapper
* @param voClass 返回的VO对象的Class对象
* @param <C> 返回的VO对象的类型
* @return 查询到的单个VO对象经过类型转换为指定的VO类后返回
*/
default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
return selectVoOne(wrapper, voClass, true);
}
/**
* 根据 entity 条件查询一条记录
* 根据条件查询单个实体对象并将其转换为指定的VO对象
*
* @param wrapper 查询条件Wrapper
* @param voClass 要转换的VO类的Class对象
* @param throwEx 是否抛出异常的标志
* @param <C> VO类的类型
* @return 查询到的单个VO对象经过转换为指定的VO类后返回
*/
default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass, boolean throwEx) {
T obj = this.selectOne(wrapper, throwEx);
@ -171,16 +251,32 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
return MapstructUtils.convert(obj, voClass);
}
/**
* 查询所有VO对象列表
*
* @return 查询到的VO对象列表
*/
default List<V> selectVoList() {
return selectVoList(new QueryWrapper<>(), this.currentVoClass());
}
/**
* 根据条件查询VO对象列表
*
* @param wrapper 查询条件Wrapper
* @return 查询到的VO对象列表
*/
default List<V> selectVoList(Wrapper<T> wrapper) {
return selectVoList(wrapper, this.currentVoClass());
}
/**
* 根据 entity 条件查询全部记录
* 根据条件查询实体对象列表并将其转换为指定的VO对象列表
*
* @param wrapper 查询条件Wrapper
* @param voClass 要转换的VO类的Class对象
* @param <C> VO类的类型
* @return 查询到的VO对象列表经过转换为指定的VO类后返回
*/
default <C> List<C> selectVoList(Wrapper<T> wrapper, Class<C> voClass) {
List<T> list = this.selectList(wrapper);
@ -190,15 +286,31 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
return MapstructUtils.convert(list, voClass);
}
/**
* 根据条件分页查询VO对象列表
*
* @param page 分页信息
* @param wrapper 查询条件Wrapper
* @return 查询到的VO对象分页列表
*/
default <P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper) {
return selectVoPage(page, wrapper, this.currentVoClass());
}
/**
* 分页查询VO
* 根据条件分页查询实体对象列表并将其转换为指定的VO对象分页列表
*
* @param page 分页信息
* @param wrapper 查询条件Wrapper
* @param voClass 要转换的VO类的Class对象
* @param <C> VO类的类型
* @param <P> VO对象分页列表的类型
* @return 查询到的VO对象分页列表经过转换为指定的VO类后返回
*/
default <C, P extends IPage<C>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper, Class<C> voClass) {
// 根据条件分页查询实体对象列表
List<T> list = this.selectList(page, wrapper);
// 创建一个新的VO对象分页列表并设置分页信息
IPage<C> voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
if (CollUtil.isEmpty(list)) {
return (P) voPage;
@ -207,8 +319,16 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
return (P) voPage;
}
/**
* 根据条件查询符合条件的对象,并将其转换为指定类型的对象列表
*
* @param wrapper 查询条件Wrapper
* @param mapper 转换函数,用于将查询到的对象转换为指定类型的对象
* @param <C> 要转换的对象的类型
* @return 查询到的符合条件的对象列表,经过转换为指定类型的对象后返回
*/
default <C> List<C> selectObjs(Wrapper<T> wrapper, Function<? super Object, C> mapper) {
return this.selectObjs(wrapper).stream().filter(Objects::nonNull).map(mapper).collect(Collectors.toList());
return StreamUtils.toList(this.selectObjs(wrapper), mapper);
}
}

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