Compare commits

...

209 Commits

Author SHA1 Message Date
YunaiV
15ba083de2 删除更新日志,统一合并到 https://doc.iocoder.cn 开发手册 2022-03-10 13:06:26 +08:00
YunaiV
9a9dbf0e97 移除 Security 无用的 secret 配置项 2022-03-10 00:39:43 +08:00
芋道源码
3c3919545a !91 修复正常租户登陆后退出切换到过期租户时造成的tenant.ignore-urls配置失效的问题,比如无法获取验证码图片造成无法登录。
Merge pull request !91 from 清溪先生/master
2022-03-09 10:27:15 +00:00
Awen
2980c6e3eb 修复正常租户登陆后退出切换到过期租户时造成的tenant.ignore-urls配置失效的问题,比如无法获取验证码图片等造成无法登录。 2022-03-09 14:55:54 +08:00
YunaiV
09cb5b6433 Merge remote-tracking branch 'origin/master' 2022-03-09 08:26:25 +08:00
芋道源码
90390dfdfa Merge pull request #103 from HFwas/hfwas
fix://修复导入数据报错
2022-03-09 08:23:48 +08:00
芋道源码
d1b6534886 !90 fix #I4WXMQ 请求地址url错误
Merge pull request !90 from xingyu/master
2022-03-08 15:37:54 +00:00
xingyu4j
972b386d93 URL错误 2022-03-08 22:01:48 +08:00
YunaiV
fc4b677b00 增加一些多租户相关的注释,更加清晰一些~ 2022-03-08 10:09:27 +08:00
YunaiV
5c03b97775 增加一些多租户相关的注释,更加清晰一些~ 2022-03-08 00:23:53 +08:00
HFwas
500f0a72ce fix://修复导入数据报错 2022-03-07 23:59:21 +08:00
芋道源码
73e30a4f37 !87 fix #I4W3DK fix #I4VUR0
Merge pull request !87 from 感觉/N/A
2022-03-06 15:03:10 +00:00
YunaiV
120cbc9123 修改版本号为 1.6.0,准备 Flowable 工作流发版! 2022-03-06 15:04:53 +08:00
YunaiV
e5b711409c 修改版本号为 1.6.0,准备 Flowable 工作流发版! 2022-03-06 09:46:24 +08:00
芋道源码
69b93ca75a !88 工作流新增 Flowable 实现
Merge pull request !88 from 芋道源码/feature/flowable
2022-03-05 09:02:45 +00:00
YunaiV
6489047a7d Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/flowable 2022-03-05 17:01:31 +08:00
感觉
1c6a77806b fix #I4W3DK fix #I4VUR0 2022-03-04 01:00:06 +00:00
YunaiV
cd919daf64 调整 yudao-module-system 的枚举包 2022-03-04 00:19:19 +08:00
YunaiV
d2636a7787 调整 DataScopeEnum 到 yudao-module-system-api 包下,合理~ 2022-03-03 13:12:52 +08:00
芋道源码
f3e0ca27d9 !85 修改vue-element-admin错误链接
Merge pull request !85 from 感觉/N/A
2022-03-01 05:52:43 +00:00
感觉
e2a9b2d3e5 修改错误链接 2022-03-01 05:42:47 +00:00
YunaiV
3201288036 review flowable 的代码实现,测试通过 2022-02-28 00:58:11 +08:00
YunaiV
b845d62e8b Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/flowable
 Conflicts:
	sql/ruoyi-vue-pro.sql
2022-02-27 23:56:23 +08:00
YunaiV
bf37095259 v.1.5.1 发布,优化多租户功能,支持自动创建用户、角色等信息 2022-02-27 16:31:56 +08:00
YunaiV
5d90760c39 v.1.5.1 发布,优化多租户功能,支持自动创建用户、角色等信息 2022-02-27 16:28:43 +08:00
YunaiV
882660a3a7 Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/flowable 2022-02-27 13:47:34 +08:00
YunaiV
e90fc607f0 v.1.5.1 发布,优化多租户功能,支持自动创建用户、角色等信息 2022-02-27 13:45:21 +08:00
YunaiV
ec8b356ba6 v.1.5.1 发布,优化多租户功能,支持自动创建用户、角色等信息 2022-02-27 13:40:10 +08:00
YunaiV
c61811a622 【修复】角色的数据范围为仅本人时,登陆后获取权限列表报错的问题 2022-02-27 12:24:21 +08:00
芋道源码
a49b1431e5 Merge pull request #91 from zzc7211/master
[Github Action]修复项目CI脚本构建失败问题
2022-02-27 03:04:23 +08:00
芋道源码
2af0e40fe7 !84 租户优化
Merge pull request !84 from 芋道源码/feature/tenant_op
2022-02-26 18:44:39 +00:00
YunaiV
f63d4e20b9 同步最新版本的 SQL 脚本 2022-02-27 02:43:25 +08:00
YunaiV
2505d61b08 Swagger 增加 tenant-id 头 2022-02-27 02:40:24 +08:00
YunaiV
fc509837a1 Merge branch 'master' of https://github.com/YunaiV/ruoyi-vue-pro into feature/tenant_op
 Conflicts:
	yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
2022-02-27 02:13:06 +08:00
YunaiV
66d6825657 * 【升级】spring-boot from 2.5.9 to 2.5.10
* 【升级】mybatis-plus from 3.4.3.4 to 3.5.1
2022-02-27 01:48:13 +08:00
YunaiV
c58eb12896 修复所有单元测试 2022-02-27 01:38:28 +08:00
YunaiV
81d89ba350 增加租户、租户套餐的单元测试 2022-02-27 00:15:13 +08:00
zhouzichun
0cbf35f7f0 Revert "core-js: ^3.21.1"
This reverts commit 4a86bd23d8.
2022-02-26 17:40:15 +08:00
zhouzichun
4a86bd23d8 core-js: ^3.21.1 2022-02-26 17:10:17 +08:00
zhouzichun
41cdc951e1 [Github Action]修复项目CI脚本构建失败问题
1.更改构建包管理工具为yarn
2.由于缓存的需要添加yarn.lock文件
3.删除没有用到的依赖javax.xml.bind.Element

更新core-js版本至最新后,前后端都能编译成功
2022-02-26 17:07:27 +08:00
YunaiV
66ebb71b8a Redis 最低版本 5.0.0 检测,解决搭建环境过程中无法理解 XREADGROUP 指令的报错 2022-02-26 00:41:27 +08:00
YunaiV
c64bb81cae 解决 spring.sql.init.schema-locations 不自动初始化,通过自定义的 SqlInitializationTestConfiguration 实现 2022-02-26 00:03:41 +08:00
芋道源码
e52d7d33be Merge pull request #82 from leosanqing/optimize-baseMapper
修改 baseMapper selectCount int -> long
2022-02-25 13:51:50 +08:00
芋道源码
19cb2b69f1 !83 更新core-js版本至最新,解决yudao-ui-admin启动时报错问题
Merge pull request !83 from tmjAccount/master
2022-02-24 16:08:26 +00:00
佟明键
80cdfbf36e 1. 更新core-js版本至最新,解决yudao-ui-admin启动时报错问题 2022-02-25 00:01:03 +08:00
YunaiV
10ba70e107 错误码存在重复的问题 2022-02-24 01:14:39 +08:00
YunaiV
75928525ca 1. 增加【默认】的系统租户的概念,禁止修改与删除等操作
2. 修复定时任务在刷新本地缓存时,会过滤租户的问题
3. 调整短信的回调地址,并进行租户的白名单
2022-02-24 00:53:28 +08:00
YunaiV
fa62ace6af 【修复】修复不支持根部门的问题 2022-02-23 22:36:45 +08:00
YunaiV
95bb9744c1 新建角色的时候,不允许创建 ADMIN 标识的角色 2022-02-23 19:28:59 +08:00
YunaiV
848fcdf329 租户创建人数的限制 2022-02-23 19:08:45 +08:00
YunaiV
d10b4595a2 租户修改角色的权限时,增加租户套餐的过滤,避免越权! 2022-02-23 13:19:08 +08:00
YunaiV
e4be51b14a 1. 新建租户、修改租户、修改租户套餐时,自动修改角色的权限
2. 租户的本地缓存,提升访问性能
3. 精简本地缓存的实现逻辑
2022-02-23 00:38:49 +08:00
YunaiV
4d53944771 【新增】新增 @TenantIgnore 注解,标记指定方法,忽略多租户的自动过滤,适合实现跨租户的逻辑 2022-02-22 19:53:40 +08:00
芋道源码
124576005a update README.md. 2022-02-21 04:53:00 +00:00
芋道源码
7c42632a50 update README.md. 2022-02-21 04:52:25 +00:00
芋道源码
40e52c3856 update README.md. 2022-02-21 04:52:00 +00:00
芋道源码
feeaac729c update README.md. 2022-02-21 04:50:45 +00:00
YunaiV
2598c033a9 【新增】【优化】新建租户时,自动创建对应的管理员账号、角色等基础信息 2022-02-20 23:59:23 +08:00
YunaiV
6b6d676a6b 【新增】租户套餐的管理,可配置每个租户的可使用的功能 2022-02-20 12:24:47 +08:00
YunaiV
79311ecc71 * 【新增】后端 yudao.tenant.enable 配置项,前端 VUE_APP_TENANT_ENABLE 配置项,用于开关租户功能
* 【优化】调整默认所有表开启多租户的特性,可通过 `yudao.tenant.ignore-tables` 配置项进行忽略,替代原本默认不开启的策略
* 【新增】通过 `yudao.tenant.ignore-urls` 配置忽略多租户的请求,例如说 ,例如说短信回调、支付回调等 Open API
2022-02-20 00:33:12 +08:00
YunaiV
27c30279a1 增加严肃声明:现在、未来都不会有商业版本! 2022-02-19 12:35:28 +08:00
jason
d8d81e835f 合并 master 分支, 修改导入流程bug 2022-02-18 12:03:45 +08:00
leosanqing
72d18b056b 修改 baseMapper selectCount int -> long
Mybatis Plus 在3.4 版本之后将 selectCount 从Integer 改为Long
2022-02-18 11:27:42 +08:00
jason
1f08a2725e 工作流 Flowable 分配leader 审批脚本 数据权限问题 2022-02-18 09:35:59 +08:00
jason
e01acfb18e 工作流 Flowable 任务自定义 Script 脚本 相关实现 2022-02-18 09:35:57 +08:00
jason
41e4283f99 工作流 Flowable 转办任务的实现 2022-02-18 09:35:54 +08:00
jason
c1884c3196 工作流 Flowable 通过任务,拒绝任务 实现 2022-02-18 09:35:49 +08:00
jason
d30bf0601c 工作流 Flowable 取消流程实例实现 2022-02-18 09:35:48 +08:00
jason
075dd83b5f 工作流 Flowable 流程实例, 用户任务相关实现 2022-02-18 09:35:47 +08:00
jason
d6775a5619 工作流 Flowable 发起流程, 用户任务相关实现 2022-02-18 09:35:44 +08:00
jason
073d860a78 工作流 Flowable 发起流程 相关实现 2022-02-18 09:35:40 +08:00
jason
c761f5258a 工作流 Flowable 流程实例 相关实现 2022-02-18 09:35:35 +08:00
jason
d64555697f 工作流 Flowable 流程模型, 流程定义 优化 2022-02-18 09:35:33 +08:00
jason
b1d6baaad8 工作流 Flowable 发布流程, 删除模型 的实现 2022-02-18 09:35:26 +08:00
jason
d6a6a01252 工作流 Flowable 发布流程的部分实现 2022-02-18 09:35:23 +08:00
jason
a207412e8c 工作流 Flowable 流程模型接口 部分实现 2022-02-18 09:34:58 +08:00
jason
9c452ee612 工作流 Flowable 流程模型 接口 2022-02-18 09:34:49 +08:00
YunaiV
167baed952 1.5.0 版本准备发布,修改更新日志 2022-02-17 21:09:11 +08:00
YunaiV
8dc40224cc Merge remote-tracking branch 'origin/master' 2022-02-17 20:59:31 +08:00
YunaiV
252b218c42 修复在线用户分页错误 2022-02-17 20:55:59 +08:00
芋道源码
9be08aae63 !80 bug-fix(用户管理)
Merge pull request !80 from fengdan/fengdan-feature-usermanage:add-fix
2022-02-17 12:51:55 +00:00
YunaiV
121fd0652d 1.5.0 版本准备发布,同步最新的 SQL 2022-02-17 20:18:40 +08:00
YunaiV
9882142a46 修复 yudao-module-system-impl 的单元测试 2022-02-17 19:52:11 +08:00
YunaiV
822f4e8192 v3.8.2 修复分页组件请求两次问题(I4SQOR) 2022-02-17 19:23:50 +08:00
YunaiV
4efb6c0847 v3.8.2 修复自定义组件file-upload无法显示第一个文件,列表显示的文件比实际文件少一个的问题 2022-02-17 19:19:29 +08:00
YunaiV
8eba07c736 v3.8.2 update .gitignore. 2022-02-17 19:18:23 +08:00
YunaiV
9a9f7058ae v3.8.2 fix css class name 2022-02-17 19:17:32 +08:00
YunaiV
bcceac5df2 v3.8.1 预览组件支持多图显示 2022-02-17 19:10:16 +08:00
YunaiV
38614abe76 v3.8.1 代码生成新增Java类型Boolean 2022-02-17 19:08:11 +08:00
YunaiV
f56450c6b7 v3.8.1 修复登录失效后多次请求提示多次弹窗问题 2022-02-17 19:05:06 +08:00
YunaiV
f7268e7ce4 v3.8.1 新增使用Gzip解压缩静态文件地址 2022-02-17 18:34:59 +08:00
YunaiV
fe552aedcd v3.8.1 集成compression-webpack-plugin插件实现打包Gzip压缩 2022-02-17 18:33:39 +08:00
YunaiV
a3e8ee2b41 v3.8.1 新增图片预览组件 2022-02-17 18:19:52 +08:00
YunaiV
7d367c367c v3.8.1 修复打包后字体图标偶现的乱码问题 2022-02-17 18:18:42 +08:00
YunaiV
9724a522e9 v3.8.0 代码生成预览支持复制内容 2022-02-17 18:13:27 +08:00
YunaiV
ca4290204c v3.8.0 自定义文字复制剪贴指令 2022-02-17 15:32:36 +08:00
YunaiV
fff6fedcfa v3.8.0 升级clipboard到最新版本2.0.8 2022-02-17 15:31:24 +08:00
YunaiV
1bebd5ef8e v3.8.0 升级js-cookie到最新版本3.0.1 2022-02-17 15:26:22 +08:00
YunaiV
8e0415a8fe v3.8.0 新增tab对象简化页签操作 2022-02-17 15:21:46 +08:00
YunaiV
5036971f55 v3.8.0 升级axios到最新版本0.24.0 2022-02-17 15:09:05 +08:00
fengdan
d3c5906cfa fix(用户管理): 新增用户数据校验问题
新增用户:用户账号参数校验

Closes https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues/I4U1XU
2022-02-17 14:38:59 +08:00
YunaiV
cfd8cd57cf v3.8.0 新增认证对象简化权限验证 2022-02-17 14:33:52 +08:00
YunaiV
fad3a030e9 v3.8.0 修复五级以上菜单404问题 2022-02-17 14:31:09 +08:00
YunaiV
02a0ab6d6d v3.8.0 新增通用方法简化下载使用 2022-02-17 14:28:43 +08:00
YunaiV
fee6d00ecf v3.8.0 升级dart-sass到版本1.32.13、升级file-saver到最新版本2.0.5、升级sass-loader到最新版本10.1.1 2022-02-17 13:44:57 +08:00
YunaiV
b72aa7b268 v3.8.0 新增通用方法简化模态/缓存使用 2022-02-17 13:16:26 +08:00
YunaiV
38ac5270d6 v3.8.0 新增通用方法简化模态/缓存使用 2022-02-17 13:11:21 +08:00
YunaiV
433056d5ea v3.8.0 Cron表达式生成器关闭时销毁,避免再次打开时存在上一次修改的数据 2022-02-17 11:44:22 +08:00
YunaiV
250c56f90c v3.8.0 禁用DictTag中el-tag渐变动画 2022-02-17 11:41:04 +08:00
YunaiV
c0380aaf6a v3.7.0 复多图组件验证失败被删除问题 2022-02-17 11:35:52 +08:00
YunaiV
eda8418797 v3.7.0 优化提示 2022-02-17 11:34:58 +08:00
YunaiV
69dba93ae5 v3.7.0 页签新增关闭左侧 2022-02-17 11:32:32 +08:00
YunaiV
3d09088029 v3.7.0 页签右键按钮添加图标 2022-02-17 11:31:25 +08:00
YunaiV
1b2dc570de v3.7.0 菜单&部门新增展开/折叠功能 2022-02-17 11:30:31 +08:00
YunaiV
f564137f05 v3.7.0 新增暗色菜单风格主题 2022-02-17 09:48:39 +08:00
YunaiV
63900da8c2 v3.7.0 修复保存配置主题颜色失效问题 2022-02-17 09:39:37 +08:00
YunaiV
170c0dbcfc v3.7.0 自定义弹层溢出滚动样式 2022-02-17 09:37:37 +08:00
YunaiV
9626b5e971 v3.7.0 定时任务支持在线生成cron表达式 2022-02-17 09:34:55 +08:00
YunaiV
e1e749d8a4 v3.7.0 验证码默认20s超时(请求) 2022-02-17 09:23:08 +08:00
YunaiV
5856c93035 v3.7.0 修复带utc日期格式 yyyy-MM-dd'T'HH:mm:ss.SSS 在safari浏览器中无法正确格式化的问题 2022-02-17 09:21:32 +08:00
YunaiV
f749b9db06 v3.7.0 自定义可拖动弹窗高度指令 2022-02-17 09:20:22 +08:00
YunaiV
5e959d310a v3.7.0 自定义可拖动弹窗宽度指令 2022-02-17 09:19:13 +08:00
YunaiV
c414e0eb62 v3.7.0 删掉此处代码,使右边栏动画生效。现在是没有动画的 2022-02-17 01:39:14 +08:00
YunaiV
2ce2287146 v3.7.0 更新到【默认首页使用keep-alive缓存】后报错 2022-02-17 01:36:32 +08:00
YunaiV
9edf88b37a v3.7.0 默认首页使用keep-alive缓存 2022-02-17 01:34:38 +08:00
YunaiV
7e31efcfe2 v3.7.0 防止错误token导致的解析异常 2022-02-17 01:30:08 +08:00
YunaiV
ab420e4120 v3.7.0 跳转路由高亮相对应的菜单栏 2022-02-17 01:28:52 +08:00
YunaiV
7b7f285034 v3.7.0 升级element-ui到最新版本2.15.3 2022-02-17 01:13:48 +08:00
YunaiV
46056a16b4 v3.7.0 密码框新增显示切换密码图标 2022-02-17 00:58:31 +08:00
YunaiV
f34f28f576 v3.7.0 导入用户样式调整 2022-02-17 00:56:50 +08:00
YunaiV
f1f602c131 v3.7.0 顶部菜单样式调整 2022-02-17 00:53:44 +08:00
YunaiV
f736e0a1c4 v3.7.0 更多操作按钮添加权限控制 2022-02-17 00:53:03 +08:00
YunaiV
969f9d0327 v3.7.0 富文本新增上传文件大小限制 2022-02-17 00:50:22 +08:00
YunaiV
2579e8549e v3.7.0 顶部菜单排除隐藏的默认路由 2022-02-17 00:49:46 +08:00
YunaiV
ba74c587b7 v3.7.0 code=401时request方法没有返回Promise.reject 修复 2022-02-17 00:48:24 +08:00
YunaiV
fc3aa2047c v3.7.0 定时任务新增更多操作 2022-02-17 00:46:18 +08:00
YunaiV
ec378d75de v3.7.0 参数管理支持配置验证码开关 2022-02-17 00:20:08 +08:00
YunaiV
e03a1a8bb3 v3.7.0 图片上传 - 多图时无法删除相应图片修复 2022-02-16 22:10:15 +08:00
YunaiV
78fe38d687 v3.6.0 完成~ 2022-02-16 22:05:13 +08:00
YunaiV
a6b70491e3 v3.6.0 富文本默认上传返回url类型 2022-02-16 22:02:21 +08:00
YunaiV
8b31b65ac1 v3.6.0 自定义弹窗拖拽指令 2022-02-16 22:01:37 +08:00
YunaiV
bc400cf646 v3.6.0 ImageUpload组件支持多图片上传 2022-02-16 21:41:08 +08:00
YunaiV
4f5964f287 v3.6.0 文件上传组件添加数量限制属性 2022-02-16 21:38:04 +08:00
YunaiV
3cb7aba37e v3.6.0 富文本编辑组件添加类型属性 2022-02-16 21:35:07 +08:00
YunaiV
c844c7ef19 v3.6.0 FileUpload组件支持多文件上传 2022-02-16 21:34:17 +08:00
YunaiV
b267b2c347 v3.6.0 增加字典标签样式回显 2022-02-16 19:42:21 +08:00
YunaiV
08a35704e9 v3.6.0 增加字典标签样式回显 2022-02-16 13:27:17 +08:00
YunaiV
986d1328e0 v3.6.0 增加字典标签样式回显 2022-02-16 01:31:14 +08:00
YunaiV
4a8129bffa v3.6.0 封装iframe组件 2022-02-15 22:24:48 +08:00
YunaiV
833ac54a2f v3.6.0 分页组件新增pagerCount属性 2022-02-15 22:11:58 +08:00
YunaiV
7bf9a85263 v3.5.0 系统布局配置支持动态标题开关 2022-02-15 22:08:40 +08:00
YunaiV
7df42db2e2 v3.5.0 富文本工具栏配置视频 2022-02-15 21:53:12 +08:00
YunaiV
e3c31c353a v3.5.0 修复关闭confirm提示框控制台报错问题 2022-02-15 21:51:24 +08:00
YunaiV
c06a96b768 v3.5.0 新增IE浏览器版本过低提示页面 2022-02-15 21:32:14 +08:00
YunaiV
e7d0024eb0 v3.5.0 导出按钮点击之后添加遮罩 2022-02-15 21:28:11 +08:00
YunaiV
d44d4da428 v3.5.0 update ruoyi-ui/src/assets/styles/element-ui.scss. 2022-02-15 20:58:26 +08:00
YunaiV
471175b406 v3.5.0 修复开启TopNav后,左侧打开外链问题 2022-02-15 20:50:28 +08:00
YunaiV
62cc1206f7 v3.5.0 主题颜色保存配置 2022-02-15 20:47:15 +08:00
YunaiV
a7a98d153c v3.5.0 过滤BindingResult对象,防止异常 2022-02-15 20:43:53 +08:00
YunaiV
41c6aa9147 v3.5.0 兼容顶部栏一级菜单内部跳转 2022-02-15 20:42:41 +08:00
YunaiV
63b7ee096a v3.5.0 固定顶部导航栏&窗口大小改变实时更新栏数 2022-02-15 20:40:59 +08:00
YunaiV
cff4391f2d v3.5.0 布局设置支持保存&重置配置 2022-02-15 20:39:21 +08:00
YunaiV
a7feb9279f v3.5.0 富文本编辑器支持自定义上传地址 2022-02-15 20:32:31 +08:00
YunaiV
4bf5b04d54 v3.5.0 新增菜单导航显示风格TopNav(false为左侧导航菜单,true为顶部导航菜单) 2022-02-15 20:27:53 +08:00
YunaiV
c5fad966d2 v3.5.0 页签新增关闭右侧 2022-02-15 20:11:10 +08:00
YunaiV
1cc2e09185 v3.5.0 富文本编辑器自定义上传地址 2022-02-15 20:08:20 +08:00
YunaiV
1467ab6530 v3.5.0 修改主题后mini类型按钮无效问题 2022-02-15 20:06:50 +08:00
YunaiV
3ac2b9973c v3.5.0 fix:RepeatedlyRequestWrapper.ServletInputStream 实现available方法 2022-02-15 20:03:34 +08:00
YunaiV
053007ef9e v3.5.0 显隐列初始默认隐藏列 2022-02-15 20:00:34 +08:00
YunaiV
33d8dbef45 v3.5.0 update ruoyi-ui/src/views/system/user/profile/userAvatar.vue. 修改头像,截图支持取消 2022-02-15 19:59:01 +08:00
YunaiV
30f160446e v3.5.0 删除多余的代码 2022-02-15 19:53:44 +08:00
YunaiV
4f770b24a4 v3.5.0 富文本编辑组件支持只读 2022-02-15 19:50:07 +08:00
YunaiV
bc41aa70d1 v3.4.0 修复四级菜单无法显示问题 2022-02-15 19:44:15 +08:00
YunaiV
f540c8a37c v3.4.0 用户显隐列添加不同key防止被复用 2022-02-15 19:31:33 +08:00
YunaiV
20e34e35a3 v3.4.0 表格右侧工具栏组件支持显隐列 2022-02-15 19:28:04 +08:00
YunaiV
cfcc2c6762 v3.4.0 修复IE11浏览器报错问题 2022-02-15 19:08:39 +08:00
YunaiV
fe0886d122 v3.4.0 Update copyright 2022-02-15 19:01:26 +08:00
YunaiV
de4df784ea v3.4.0 操作按钮组调整为朴素按钮样式 2022-02-15 18:59:22 +08:00
芋道源码
b1d42becc3 !79 修复 根据角色id查询用户id的bug
Merge pull request !79 from zyk492322922/master
2022-02-15 10:38:35 +00:00
zyk492322922
878a0ef638 ## 修复 根据角色查询用户id的bug 2022-02-15 08:49:01 +00:00
YunaiV
c0bebb7755 Merge branch 'master' of https://github.com/YunaiV/ruoyi-vue-pro 2022-02-15 12:50:31 +08:00
YunaiV
32ccb8bd84 优化 README 说明 2022-02-15 12:50:05 +08:00
YunaiV
4c169cbc58 【新增】兼容 Node 16 版本,通过升级 BPMN-JS 相关库 2022-02-12 17:55:28 +08:00
YunaiV
0d1a8c627b Merge remote-tracking branch 'origin/master' 2022-02-12 00:28:27 +08:00
YunaiV
5dcf763f08 修复缺少 user_type 导致的单元测试错误 2022-02-12 00:28:21 +08:00
芋道源码
fcbb99941c Merge pull request #67 from zzc7211/master
[build]github Action CI
2022-02-11 13:24:23 +08:00
sam
efcff7333e fix typo: 上窜 -> 上传 2022-02-11 09:16:04 +08:00
YunaiV
128ee67925 更新日志,更新~ 2022-02-11 01:18:30 +08:00
芋道源码
28cb66b971 !77 修复部门更新后本地缓存不更新问题
Merge pull request !77 from C_VS/master
2022-02-10 17:17:27 +00:00
芋道源码
bfe7cf01eb !78 fix: 修复 Maven 构建一些提示
Merge pull request !78 from 滑头一条咸鱼/lc
2022-02-10 17:09:20 +00:00
Jelly
5e43efc555 fix:修复 Maven 构建一些提示 2022-02-10 11:16:17 +08:00
chenyongsheng712
67df00264a 修复部门更新后本地缓存不更新问题
https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues/I4T22A
2022-02-10 09:48:07 +08:00
YunaiV
424ddb23e4 * 【修复】自定义 DefaultStreamMessageListenerContainerX 实现,解决 Redisson Stream 读取不到数据返回 null 导致 NPE 问题 2022-02-10 08:20:55 +08:00
YunaiV
2cef84bdc8 * 【修复】自定义 DefaultStreamMessageListenerContainerX 实现,解决 Redisson Stream 读取不到数据返回 null 导致 NPE 问题 2022-02-10 08:20:06 +08:00
YunaiV
152453106a 完善更新日志 2022-02-10 00:08:26 +08:00
YunaiV
55d8977108 Merge branch 'master' of https://github.com/YunaiV/ruoyi-vue-pro 2022-02-10 00:08:04 +08:00
芋道源码
3f19760678 Merge pull request #73 from leosanqing/optimize-json-utils
使用 lombok 优化JsonUtils
2022-02-10 00:07:12 +08:00
芋道源码
cd42846bec !73 工作流程发起以及审批异常,NotEmpty校验Long类型异常
Merge pull request !73 from zhuqi7/master
2022-02-09 16:03:23 +00:00
leosanqing
855bb214be 使用 lombok 优化工具类
1。添加 @UtilityClass, 作用私有空参构造函数
2。使用 @SneakyThrows 替代手动转换为 RuntimeException
2022-02-09 09:36:15 +08:00
zhuqi
f0395c450f 校验Long类型NotEmpty改为NotNull 2022-02-09 08:57:05 +08:00
芋道源码
5151112912 Merge pull request #68 from crimson-gao/fix/avatar
fix: 修复 avatar 配置
2022-02-09 08:53:20 +08:00
zhuqi
37ec560687 流程发起异常,忽略id 2022-02-09 08:53:11 +08:00
YunaiV
f966fae060 【修复】Knife4j 接口文档 404 的问题,原因是 spring.mvc.static-path-pattern 配置项,影响了基础路径 2022-02-09 00:25:27 +08:00
leosanqing
fd4adf2cea 使用 lombok 优化工具类
1。添加 @UtilityClass, 作用私有空参构造函数
2。使用 @SneakyThrows 替代手动转换为 RuntimeException
2022-02-08 19:25:34 +08:00
crimson
2bc45ad467 fix: 修复 avatar 配置 2022-02-08 01:03:15 +08:00
zhouzichun
c671c23e87 [build]github Action CI
添加Action CI
2022-02-07 19:57:59 +08:00
YunaiV
d5c35c23dc 初始化 1.5.0-snapshot 版本 2022-02-07 16:12:37 +08:00
芋道源码
09d0634694 !72 数据权限-部门及以下权限问题
Merge pull request !72 from @C/master
2022-02-07 08:03:00 +00:00
cks
1044e1ac72 修改角色中部门及以下权限问题 2022-02-07 16:00:07 +08:00
611 changed files with 29575 additions and 6033 deletions

30
.github/workflows/maven.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Java CI with Maven
on:
push:
branches: [ master ]
# pull_request:
# branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
java: [ '8', '11', '17' ]
steps:
- uses: actions/checkout@v2
- name: Set up JDK ${{ matrix.Java }}
uses: actions/setup-java@v2
with:
java-version: ${{ matrix.java }}
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: mvn -B package --file pom.xml -Dmaven.test.skip=true

51
.github/workflows/yudao-ui-admin.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
name: yudao-ui-admin CI
# 在master分支发生push事件时触发。
on:
push:
branches: [ master ]
# pull_request:
# branches: [ master ]
env: # 设置环境变量
TZ: Asia/Shanghai # 时区(设置时区可使页面中的`最近更新时间`使用时区时间)
WORK_DIR: yudao-ui-admin #工作目录
defaults:
run:
shell: bash
working-directory: yudao-ui-admin
jobs:
build: # 自定义名称
runs-on: ubuntu-latest # 运行在虚拟机环境ubuntu-latest
strategy:
matrix:
node-version: [14.x, 16.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- name: Checkout # 步骤1
uses: actions/checkout@v2 # 使用的动作。格式userName/repoName。作用检出仓库获取源码。 官方actions库https://github.com/actions
- name: Install pnpm
uses: pnpm/action-setup@v2.0.1
with:
version: 6.15.1
- name: Set node version to ${{ matrix.node_version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node_version }}
cache: "yarn"
cache-dependency-path: yudao-ui-admin/yarn.lock
- name: Install deps
run: node --version && yarn --version && yarn install
- name: Build
run: yarn build:prod
# 查看 workflow 的文档来获取更多信息
# @see https://github.com/crazy-max/ghaction-github-pages

2
.gitignore vendored
View File

@@ -43,3 +43,5 @@ nbdist/
!*/build/*.html
!*/build/*.xml
### JRebel ###
rebel.xml

View File

@@ -1,4 +1,11 @@
## 平台简介
**严肃声明:现在、未来都不会有商业版本,所有功能全部开源!**
**拒绝虚假开源,售卖商业版,程序员不骗程序员!!**
**「我喜欢写代码,乐此不疲」**
**「我喜欢做开源,以此为乐」**
## 🐯 平台简介
**芋道**,一套**全部开源**的**企业级**的快速开发平台,毫无保留给个人及企业免费使用。
@@ -8,12 +15,19 @@
* 后端采用 Spring Boot、MySQL + MyBatis Plus、Redis + Redisson。
* 权限认证使用 Spring Security & Token & Redis支持多终端、多种用户的认证系统。
* 支持加载动态权限菜单,按钮级别权限控制,本地缓存提升性能。
* 支持 SaaS 多租户系统,可自定义每个租户的权限,提供透明化的多租户底层封装。
* 工作流使用 Activiti ,支持动态表单、在线设计流程、多种任务分配方式。
* 高效率开发,使用代码生成器可以一键生成前后端代码 + 单元测试 + Swagger 接口文档 + Validator 参数校验。
* 集成微信小程序、微信公众号、企业微信、钉钉等三方登陆,集成支付宝、微信等支付与退款。
* 集成阿里云、腾讯云、云片等短信渠道,集成阿里云、腾讯云、七牛云等云存储服务。
## 在线体验
| 项目名 | 说明 | 传说门 |
| ---- |------------------------| ---- |
| `ruoyi-vue-pro` | Spring Boot 多模块 | **[Gitee](https://gitee.com/zhijiantianya/ruoyi-vue-pro)**     [Github](https://github.com/YunaiV/ruoyi-vue-pro) |
| `ruoyi-vue-cloud` | Spring Cloud 微服务 | **[Gitee](https://gitee.com/zhijiantianya/ruoyi-vue-cloud)**     [Github](https://github.com/YunaiV/onemall) |
| `Spring-Boot-Labs` | Spring Boot & Cloud 入门 | **[Gitee](https://gitee.com/zhijiantianya/SpringBoot-Labs)**     [Github](https://github.com/YunaiV/SpringBoot-Labs) |
## 🐶 在线体验
演示地址:<http://dashboard.yudao.iocoder.cn>
* 账号密码admin/admin123
@@ -23,7 +37,7 @@
> 未来会补充文档和视频,方便胖友冲冲冲!
## 内置功能
## 🐼 内置功能
分成多种内置功能:
* 系统功能
@@ -51,6 +65,7 @@
| | 部门管理 | 配置系统组织机构(公司、部门、小组),树结构展现支持数据权限 |
| | 岗位管理 | 配置系统用户所属担任职务 |
| 🚀 | 租户管理 | 配置系统租户,支持 SaaS 场景下的多租户功能 |
| 🚀 | 租户套餐 | 配置租户套餐,自定每个租户的菜单、操作、按钮的权限 |
| | 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 |
| 🚀 | 短信管理 | 短信渠道、短息模板、短信日志,对接阿里云、云片等主流短信平台 |
| 🚀 | 操作日志 | 系统正常操作日志记录和查询,集成 Swagger 生成日志内容 |
@@ -121,7 +136,7 @@ ps核心功能已经实现正在对接微信小程序中...
| 🚀 | 数据库文档 | 基于 Screw 自动生成数据库文档,支持导出 Word、HTML、MD 格式 |
| | 表单构建 | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件 |
## 技术栈
## 🐨 技术栈
| 项目 | 说明 |
|-----------------------|--------------------|
@@ -141,16 +156,16 @@ ps核心功能已经实现正在对接微信小程序中...
| 框架 | 说明 | 版本 | 学习指南 |
| --- | --- |----------| --- |
| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.5.9 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.5.10 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
| [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 | |
| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.8 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.4.3.4 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.1 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
| [Redis](https://redis.io/) | key-value 数据库 | 5.0 | |
| [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.16.8 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) |
| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.3.15 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |
| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.5.4 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.0 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |
| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.3.16 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |
| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.5.5 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.2 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |
| [Activiti](https://github.com/Activiti/Activiti) | 工作流引擎 | 7.1.0.M6 | [文档](TODO) |
| [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) |
| [Knife4j](https://gitee.com/xiaoym/knife4j) | Swagger 增强 UI 实现 | 3.0.2 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) |
@@ -168,16 +183,17 @@ ps核心功能已经实现正在对接微信小程序中...
| 框架 | 说明 | 版本 |
| --- | --- | --- |
| [Vue](https://cn.vuejs.org/index.html) | JavaScript 框架 | 2.6.12 |
| [Vue Element Admin](https://ant.design/docs/react/introduce-cn) | 后台前端解决方案 | - |
| [Vue Element Admin](https://panjiachen.github.io/vue-element-admin-site/zh/) | 后台前端解决方案 | - |
## 演示图
## 🐷 演示图
### 系统功能
| 模块 | biu | biu | biu |
| --- | --- | --- | --- |
| 登录 & 首页 | ![登录](https://static.iocoder.cn/images/ruoyi-vue-pro/登录.jpg) | ![首页](https://static.iocoder.cn/images/ruoyi-vue-pro/首页.jpg) | ![个人中心](https://static.iocoder.cn/images/ruoyi-vue-pro/个人中心.jpg) |
| 用户 & 租户 | ![用户管理](https://static.iocoder.cn/images/ruoyi-vue-pro/用户管理.jpg) | ![在线用户](https://static.iocoder.cn/images/ruoyi-vue-pro/在线用户.jpg) | ![用户管理](https://static.iocoder.cn/images/ruoyi-vue-pro/租户管理.jpg) |
| 模块 | biu | biu | biu |
| --- | --- |------------------------------------------------------------------| --- |
| 登录 & 首页 | ![登录](https://static.iocoder.cn/images/ruoyi-vue-pro/登录.jpg) | ![首页](https://static.iocoder.cn/images/ruoyi-vue-pro/首页.jpg) | ![个人中心](https://static.iocoder.cn/images/ruoyi-vue-pro/个人中心.jpg) |
| 用户 | ![用户管理](https://static.iocoder.cn/images/ruoyi-vue-pro/用户管理.jpg) | ![在线用户](https://static.iocoder.cn/images/ruoyi-vue-pro/在线用户.jpg) | - |
| 租户 & 套餐 | ![租户管理](https://static.iocoder.cn/images/ruoyi-vue-pro/租户管理.jpg) | ![租户套餐](https://static.iocoder.cn/images/ruoyi-vue-pro/租户套餐.png) | - |
| 部门 & 岗位 | ![部门管理](https://static.iocoder.cn/images/ruoyi-vue-pro/部门管理.jpg) | ![岗位管理](https://static.iocoder.cn/images/ruoyi-vue-pro/岗位管理.jpg) | - |
| 菜单 & 角色 | ![菜单管理](https://static.iocoder.cn/images/ruoyi-vue-pro/菜单管理.jpg) | ![角色管理](https://static.iocoder.cn/images/ruoyi-vue-pro/角色管理.jpg) | - |
| 审计日志 | ![操作日志](https://static.iocoder.cn/images/ruoyi-vue-pro/操作日志.jpg) | ![登录日志](https://static.iocoder.cn/images/ruoyi-vue-pro/登录日志.jpg) | - |

View File

@@ -1,4 +1,4 @@
config.stopBubbling = true
lombok.tostring.callsuper=true
lombok.equalsandhashcode.callsuper=true
lombok.tostring.callsuper=CALL
lombok.equalsandhashcode.callsuper=CALL
lombok.accessors.chain=true

View File

@@ -21,12 +21,12 @@
<module>yudao-module-pay</module>
</modules>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>芋道项目基础脚手架</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<properties>
<revision>1.4.0-snapshot</revision>
<revision>1.6.0-snapshot</revision>
<!-- Maven 相关 -->
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>

1517
sql/bpm-flowable.sql Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -9,14 +9,14 @@
<version>${revision}</version>
<packaging>pom</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>基础 bom 文件,管理整个项目的依赖版本</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<properties>
<revision>1.4.0-snapshot</revision>
<revision>1.6.0-snapshot</revision>
<!-- 统一依赖管理 -->
<spring.boot.version>2.5.9</spring.boot.version>
<spring.boot.version>2.5.10</spring.boot.version>
<!-- Web 相关 -->
<knife4j.version>3.0.2</knife4j.version>
<swagger-annotations.version>1.5.22</swagger-annotations.version>
@@ -399,6 +399,11 @@
<version>${revision}</version>
</dependency>
<!-- 工作流相关 flowable -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-flowable</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-basic</artifactId>

View File

@@ -35,12 +35,13 @@
<module>yudao-spring-boot-starter-biz-social</module>
<module>yudao-spring-boot-starter-biz-tenant</module>
<module>yudao-spring-boot-starter-biz-data-permission</module>
<module>yudao-spring-boot-starter-flowable</module>
</modules>
<artifactId>yudao-framework</artifactId>
<description>
该包是技术组件,每个子包,代表一个组件。每个组件包括两部分:
1. core 包:是该组件的核心
1. core 包:是该组件的核心
2. config 包:是该组件基于 Spring 的配置
技术组件,也分成两类:

View File

@@ -11,7 +11,7 @@
<artifactId>yudao-common</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>定义基础 pojo 类、枚举、工具类等等</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
@@ -89,11 +89,6 @@
<artifactId>mapstruct-processor</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>

View File

@@ -0,0 +1,20 @@
package cn.iocoder.yudao.framework.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 文档地址
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum DocumentEnum {
REDIS_INSTALL("https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues/I4VCSJ", "Redis 安装文档");
private final String url;
private final String memo;
}

View File

@@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.common.enums;
/**
* Web 过滤器顺序的枚举类,保证过滤器按照符合我们的预期
*
* 考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 util 包下
* 考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 util 包下
*
* @author 芋道源码
*/
@@ -29,6 +29,8 @@ public interface WebFilterOrderEnum {
int ACTIVITI_FILTER = -98; // 需要保证在 Spring Security 过滤后面
int FLOWABLE_FILTER = -98; // 需要保证在 Spring Security 过滤后面
int DEMO_FILTER = Integer.MAX_VALUE;
}

View File

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.common.util.collection;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.google.common.collect.ImmutableMap;
import java.util.*;
import java.util.function.BinaryOperator;
@@ -125,6 +126,15 @@ public class CollectionUtils {
return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toSet())));
}
public static <T, K> Map<K, T> convertImmutableMap(Collection<T> from, Function<T, K> keyFunc) {
if (CollUtil.isEmpty(from)) {
return Collections.emptyMap();
}
ImmutableMap.Builder<K, T> builder = ImmutableMap.builder();
from.forEach(item -> builder.put(keyFunc.apply(item), item));
return builder.build();
}
public static boolean containsAny(Collection<?> source, Collection<?> candidates) {
return org.springframework.util.CollectionUtils.containsAny(source, candidates);
}
@@ -140,6 +150,15 @@ public class CollectionUtils {
return from.stream().filter(predicate).findFirst().orElse(null);
}
public static <T, V extends Comparable<? super V>> V getMaxValue(List<T> from, Function<T, V> valueFunc) {
if (CollUtil.isEmpty(from)) {
return null;
}
assert from.size() > 0; // 断言,避免告警
T t = from.stream().max(Comparator.comparing(valueFunc)).get();
return valueFunc.apply(t);
}
public static <T> void addIfNotNull(Collection<T> coll, T item) {
if (item == null) {
return;
@@ -147,4 +166,7 @@ public class CollectionUtils {
coll.add(item);
}
public static <T> Collection<T> singleton(T deptId) {
return deptId == null ? Collections.emptyList() : Collections.singleton(deptId);
}
}

View File

@@ -2,11 +2,13 @@ package cn.iocoder.yudao.framework.common.util.json;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.ArrayList;
@@ -17,6 +19,8 @@ import java.util.List;
*
* @author 芋道源码
*/
@UtilityClass
@Slf4j
public class JsonUtils {
private static ObjectMapper objectMapper = new ObjectMapper();
@@ -36,29 +40,26 @@ public class JsonUtils {
JsonUtils.objectMapper = objectMapper;
}
@SneakyThrows
public static String toJsonString(Object object) {
try {
return objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
return objectMapper.writeValueAsString(object);
}
@SneakyThrows
public static byte[] toJsonByte(Object object) {
try {
return objectMapper.writeValueAsBytes(object);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
return objectMapper.writeValueAsBytes(object);
}
public static <T> T parseObject(String text, Class<T> clazz) {
if (StrUtil.isEmpty(text)) {
return null;
}
try {
return objectMapper.readValue(text, clazz);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
@@ -67,9 +68,11 @@ public class JsonUtils {
if (ArrayUtil.isEmpty(bytes)) {
return null;
}
try {
return objectMapper.readValue(bytes, clazz);
} catch (IOException e) {
log.error("json parse err,json:{}", bytes, e);
throw new RuntimeException(e);
}
}
@@ -78,6 +81,7 @@ public class JsonUtils {
try {
return objectMapper.readValue(text, typeReference);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
@@ -86,9 +90,11 @@ public class JsonUtils {
if (StrUtil.isEmpty(text)) {
return new ArrayList<>();
}
try {
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
@@ -98,6 +104,7 @@ public class JsonUtils {
try {
return objectMapper.readTree(text);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
@@ -106,6 +113,7 @@ public class JsonUtils {
try {
return objectMapper.readTree(text);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}

View File

@@ -1,8 +1,13 @@
package cn.iocoder.yudao.framework.common.util.validation;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.util.StringUtils;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import java.util.Set;
import java.util.regex.Pattern;
/**
@@ -34,4 +39,11 @@ public class ValidationUtils {
&& PATTERN_XML_NCNAME.matcher(str).matches();
}
public static void validate(Validator validator, Object reqVO, Class<?>... groups) {
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(reqVO, groups);
if (CollUtil.isNotEmpty(constraintViolations)) {
throw new ConstraintViolationException(constraintViolations);
}
}
}

View File

@@ -11,7 +11,7 @@
<artifactId>yudao-spring-boot-starter-activiti</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>Activiti 拓展</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>

View File

@@ -13,7 +13,6 @@ import org.activiti.bpmn.model.Process;
import org.activiti.engine.impl.identity.Authentication;
import org.activiti.engine.impl.util.io.BytesStreamSource;
import javax.xml.bind.Element;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

View File

@@ -11,7 +11,7 @@
<artifactId>yudao-spring-boot-starter-biz-data-permission</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>数据权限</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>

View File

@@ -13,10 +13,12 @@ import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.NullValue;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
@@ -51,6 +53,8 @@ public class DeptDataPermissionRule implements DataPermissionRule {
private static final String DEPT_COLUMN_NAME = "dept_id";
private static final String USER_COLUMN_NAME = "user_id";
static final Expression EXPRESSION_NULL = new NullValue();
private final DeptDataPermissionFrameworkService deptDataPermissionService;
/**
@@ -110,10 +114,12 @@ public class DeptDataPermissionRule implements DataPermissionRule {
Expression deptExpression = this.buildDeptExpression(tableName,tableAlias, deptDataPermission.getDeptIds());
Expression userExpression = this.buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId());
if (deptExpression == null && userExpression == null) {
log.error("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]",
// TODO 芋艿:获得不到条件的时候,暂时不抛出异常,而是不返回数据
log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]",
JsonUtils.toJsonString(loginUser), tableName, tableAlias, JsonUtils.toJsonString(deptDataPermission));
throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 构建的条件为空",
loginUser.getId(), tableName, tableAlias.getName()));
// throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 构建的条件为空",
// loginUser.getId(), tableName, tableAlias.getName()));
return EXPRESSION_NULL;
}
if (deptExpression == null) {
return userExpression;

View File

@@ -18,6 +18,7 @@ import org.mockito.MockedStatic;
import java.util.Map;
import static cn.iocoder.yudao.framework.datapermission.core.dept.rule.DeptDataPermissionRule.EXPRESSION_NULL;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
import static org.junit.jupiter.api.Assertions.*;
@@ -137,10 +138,9 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission);
// 调用
NullPointerException exception = assertThrows(NullPointerException.class,
() -> rule.getExpression(tableName, tableAlias));
Expression expression = rule.getExpression(tableName, tableAlias);
// 断言
assertEquals("LoginUser(1) Table(t_user/u) 构建的条件为空", exception.getMessage());
assertSame(EXPRESSION_NULL, expression);
}
}

View File

@@ -11,7 +11,7 @@
<artifactId>yudao-spring-boot-starter-biz-dict</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>字典类型、数据</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>

View File

@@ -11,7 +11,7 @@
<artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>操作日志</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>

View File

@@ -6,13 +6,13 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.framework.operatelog.core.dto.OperateLogCreateReqDTO;
import cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum;
import cn.iocoder.yudao.framework.operatelog.core.service.OperateLogFrameworkService;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import com.google.common.collect.Maps;
import io.swagger.annotations.Api;
@@ -23,6 +23,7 @@ import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;
@@ -356,7 +357,8 @@ public class OperateLogAspect {
// obj
return object instanceof MultipartFile
|| object instanceof HttpServletRequest
|| object instanceof HttpServletResponse;
|| object instanceof HttpServletResponse
|| object instanceof BindingResult;
}
}

View File

@@ -10,7 +10,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-spring-boot-starter-biz-pay</artifactId>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>支付拓展,接入国内多个支付渠道
1. 支付宝,基于官方 SDK 接入
2. 微信支付,基于 weixin-java-pay 接入

View File

@@ -27,6 +27,7 @@ public interface PayClientConfig {
*/
Set<ConstraintViolation<PayClientConfig>> verifyParam(Validator validator);
// TODO @aquan貌似抽象一个 validation group 就好了!
/**
* 参数校验
*

View File

@@ -11,7 +11,7 @@
<artifactId>yudao-spring-boot-starter-biz-sms</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>短信拓展,支持阿里云、云片</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>

View File

@@ -11,7 +11,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-spring-boot-starter-biz-social</artifactId>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<dependencies>
<dependency>

View File

@@ -11,7 +11,7 @@
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>多租户</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>

View File

@@ -14,21 +14,28 @@ import java.util.Set;
@Data
public class TenantProperties {
// /**
// * 租户是否开启
// */
// private static final Boolean ENABLE_DEFAULT = true;
//
// /**
// * 是否开启
// */
// private Boolean enable = ENABLE_DEFAULT;
/**
* 需要多租户的表
*
* 由于多租户并不作为 yudao 项目的重点功能,更多是扩展性的功能,所以采用正向配置需要多租户的表。
* 如果需要,你可以改成 ignoreTables 来取消部分不需要的表
* 租户是否开启
*/
private Set<String> tables;
private static final Boolean ENABLE_DEFAULT = true;
/**
* 是否开启
*/
private Boolean enable = ENABLE_DEFAULT;
/**
* 需要忽略多租户的请求
*
* 默认情况下,每个请求需要带上 tenant-id 的请求头。但是,部分请求是无需带上的,例如说短信回调、支付回调等 Open API
*/
private Set<String> ignoreUrls;
/**
* 需要忽略多租户的表
*
* 即默认所有表都开启多租户的功能,所以记得添加对应的 tenant_id 字段哟
*/
private Set<String> ignoreTables;
}

View File

@@ -0,0 +1,106 @@
package cn.iocoder.yudao.framework.tenant.config;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnoreAspect;
import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJobHandlerDecorator;
import cn.iocoder.yudao.framework.tenant.core.mq.TenantRedisMessageInterceptor;
import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter;
import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
import cn.iocoder.yudao.framework.tenant.core.web.TenantContextWebFilter;
import cn.iocoder.yudao.framework.web.config.WebProperties;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) // 允许使用 yudao.tenant.enable=false 禁用多租户
@EnableConfigurationProperties(TenantProperties.class)
public class YudaoTenantAutoConfiguration {
// ========== AOP ==========
@Bean
public TenantIgnoreAspect tenantIgnoreAspect() {
return new TenantIgnoreAspect();
}
// ========== DB ==========
@Bean
public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties properties,
MybatisPlusInterceptor interceptor) {
TenantLineInnerInterceptor inner = new TenantLineInnerInterceptor(new TenantDatabaseInterceptor(properties));
// 添加到 interceptor 中
// 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定
MyBatisUtils.addInterceptor(interceptor, inner, 0);
return inner;
}
// ========== WEB ==========
@Bean
public FilterRegistrationBean<TenantContextWebFilter> tenantContextWebFilter() {
FilterRegistrationBean<TenantContextWebFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new TenantContextWebFilter());
registrationBean.setOrder(WebFilterOrderEnum.TENANT_CONTEXT_FILTER);
return registrationBean;
}
// ========== Security ==========
@Bean
public FilterRegistrationBean<TenantSecurityWebFilter> tenantSecurityWebFilter(TenantProperties tenantProperties,
WebProperties webProperties,
GlobalExceptionHandler globalExceptionHandler,
TenantFrameworkService tenantFrameworkService) {
FilterRegistrationBean<TenantSecurityWebFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new TenantSecurityWebFilter(tenantProperties, webProperties,
globalExceptionHandler, tenantFrameworkService));
registrationBean.setOrder(WebFilterOrderEnum.TENANT_SECURITY_FILTER);
return registrationBean;
}
// ========== MQ ==========
@Bean
public TenantRedisMessageInterceptor tenantRedisMessageInterceptor() {
return new TenantRedisMessageInterceptor();
}
// ========== Job ==========
@Bean
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
public BeanPostProcessor jobHandlerBeanPostProcessor(TenantFrameworkService tenantFrameworkService) {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (!(bean instanceof JobHandler)) {
return bean;
}
// 有 TenantJob 注解的情况下,才会进行处理
if (!AnnotationUtil.hasAnnotation(bean.getClass(), TenantJob.class)) {
return bean;
}
// 使用 TenantJobHandlerDecorator 装饰
return new TenantJobHandlerDecorator(tenantFrameworkService, (JobHandler) bean);
}
};
}
}

View File

@@ -1,30 +0,0 @@
package cn.iocoder.yudao.framework.tenant.config;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 多租户针对 DB 的自动配置
*
* @author 芋道源码
*/
@Configuration
@EnableConfigurationProperties(TenantProperties.class)
public class YudaoTenantDatabaseAutoConfiguration {
@Bean
public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties properties,
MybatisPlusInterceptor interceptor) {
TenantLineInnerInterceptor inner = new TenantLineInnerInterceptor(new TenantDatabaseInterceptor(properties));
// 添加到 interceptor 中
// 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定
MyBatisUtils.addInterceptor(interceptor, inner, 0);
return inner;
}
}

View File

@@ -1,43 +0,0 @@
package cn.iocoder.yudao.framework.tenant.config;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJobHandlerDecorator;
import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 多租户针对 Job 的自动配置
*
* @author 芋道源码
*/
@Configuration
public class YudaoTenantJobAutoConfiguration {
@Bean
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
public BeanPostProcessor jobHandlerBeanPostProcessor(TenantFrameworkService tenantFrameworkService) {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (!(bean instanceof JobHandler)) {
return bean;
}
// 有 TenantJob 注解的情况下,才会进行处理
if (!AnnotationUtil.hasAnnotation(bean.getClass(), TenantJob.class)) {
return bean;
}
// 使用 TenantJobHandlerDecorator 装饰
return new TenantJobHandlerDecorator(tenantFrameworkService, (JobHandler) bean);
}
};
}
}

View File

@@ -1,20 +0,0 @@
package cn.iocoder.yudao.framework.tenant.config;
import cn.iocoder.yudao.framework.tenant.core.mq.TenantRedisMessageInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 多租户针对 MQ 的自动配置
*
* @author 芋道源码
*/
@Configuration
public class YudaoTenantMQAutoConfiguration {
@Bean
public TenantRedisMessageInterceptor tenantRedisMessageInterceptor() {
return new TenantRedisMessageInterceptor();
}
}

View File

@@ -1,25 +0,0 @@
package cn.iocoder.yudao.framework.tenant.config;
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 多租户针对 Web 的自动配置
*
* @author 芋道源码
*/
@Configuration
public class YudaoTenantSecurityAutoConfiguration {
@Bean
public FilterRegistrationBean<TenantSecurityWebFilter> tenantSecurityWebFilter() {
FilterRegistrationBean<TenantSecurityWebFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new TenantSecurityWebFilter());
registrationBean.setOrder(WebFilterOrderEnum.TENANT_SECURITY_FILTER);
return registrationBean;
}
}

View File

@@ -1,25 +0,0 @@
package cn.iocoder.yudao.framework.tenant.config;
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
import cn.iocoder.yudao.framework.tenant.core.web.TenantContextWebFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 多租户针对 Web 的自动配置
*
* @author 芋道源码
*/
@Configuration
public class YudaoTenantWebAutoConfiguration {
@Bean
public FilterRegistrationBean<TenantContextWebFilter> tenantContextWebFilter() {
FilterRegistrationBean<TenantContextWebFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new TenantContextWebFilter());
registrationBean.setOrder(WebFilterOrderEnum.TENANT_CONTEXT_FILTER);
return registrationBean;
}
}

View File

@@ -0,0 +1,18 @@
package cn.iocoder.yudao.framework.tenant.core.aop;
import java.lang.annotation.*;
/**
* 忽略租户,标记指定方法不进行租户的自动过滤
*
* 注意,只有 DB 的场景会过滤,其它场景暂时不过滤:
* 1、Redis 场景:因为是基于 Key 实现多租户的能力,所以忽略没有意义,不像 DB 是一个 column 实现的
* 2、MQ 场景:有点难以抉择,目前可以通过 Consumer 手动在消费的方法上,添加 @TenantIgnore 进行忽略
*
* @author 芋道源码
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface TenantIgnore {
}

View File

@@ -0,0 +1,32 @@
package cn.iocoder.yudao.framework.tenant.core.aop;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
/**
* 忽略多租户的 Aspect基于 {@link TenantIgnore} 注解实现,用于一些全局的逻辑。
* 例如说,一个定时任务,读取所有数据,进行处理。
* 又例如说,读取所有数据,进行缓存。
*
* @author 芋道源码
*/
@Aspect
@Slf4j
public class TenantIgnoreAspect {
@Around("@annotation(tenantIgnore)")
public Object around(ProceedingJoinPoint joinPoint, TenantIgnore tenantIgnore) throws Throwable {
Boolean oldIgnore = TenantContextHolder.isIgnore();
try {
TenantContextHolder.setIgnore(true);
// 执行逻辑
return joinPoint.proceed();
} finally {
TenantContextHolder.setIgnore(oldIgnore);
}
}
}

View File

@@ -9,12 +9,15 @@ import com.alibaba.ttl.TransmittableThreadLocal;
*/
public class TenantContextHolder {
/**
* 当前租户编号
*/
private static final ThreadLocal<Long> TENANT_ID = new TransmittableThreadLocal<>();
/**
* 租户编号 - 空
* 是否忽略租户
*/
private static final Long TENANT_ID_NULL = 0L;
private static final ThreadLocal<Boolean> IGNORE = new TransmittableThreadLocal<>();
/**
* 获得租户编号。
@@ -33,26 +36,31 @@ public class TenantContextHolder {
public static Long getRequiredTenantId() {
Long tenantId = getTenantId();
if (tenantId == null) {
throw new NullPointerException("TenantContextHolder 不存在租户编号");
throw new NullPointerException("TenantContextHolder 不存在租户编号"); // TODO 芋艿:增加文档链接
}
return tenantId;
}
/**
* 在一些前端场景下,可能无法请求带上租户。例如说,<img /> 方式获取图片等
* 此时,暂时的解决方案,是在该接口的 Controller 方法上,调用该方法
* TODO 芋艿:思考有没更合适的方案,目标是去掉该方法
*/
public static void setNullTenantId() {
TENANT_ID.set(TENANT_ID_NULL);
}
public static void setTenantId(Long tenantId) {
TENANT_ID.set(tenantId);
}
public static void setIgnore(Boolean ignore) {
IGNORE.set(ignore);
}
/**
* 当前是否忽略租户
*
* @return 是否忽略
*/
public static boolean isIgnore() {
return Boolean.TRUE.equals(IGNORE.get());
}
public static void clear() {
TENANT_ID.remove();
IGNORE.remove();
}
}

View File

@@ -3,12 +3,10 @@ package cn.iocoder.yudao.framework.tenant.core.db;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.tenant.config.TenantProperties;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import lombok.AllArgsConstructor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.LongValue;
/**
* 基于 MyBatis Plus 多租户的功能,实现 DB 层面的多租户的功能
@@ -22,18 +20,13 @@ public class TenantDatabaseInterceptor implements TenantLineHandler {
@Override
public Expression getTenantId() {
return new StringValue(TenantContextHolder.getRequiredTenantId().toString());
return new LongValue( TenantContextHolder.getRequiredTenantId());
}
@Override
public boolean ignoreTable(String tableName) {
// 如果实体类继承 TenantBaseDO 类,则是多租户表,不进行忽略
TableInfo tableInfo = TableInfoHelper.getTableInfo(tableName);
if (tableInfo != null && TenantBaseDO.class.isAssignableFrom(tableInfo.getEntityType())) {
return false;
}
// 不包含,说明要过滤
return !CollUtil.contains(properties.getTables(), tableName);
return TenantContextHolder.isIgnore() // 情况一,全局忽略多租户
|| CollUtil.contains(properties.getIgnoreTables(), tableName); // 情况二,忽略多租户的表
}
}

View File

@@ -1,14 +0,0 @@
package cn.iocoder.yudao.framework.tenant.core.job;
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker;
/**
* 多租户 JobHandlerInvoker 拓展实现类
*
* @author 芋道源码
*/
public class TenantJobHandlerInvoker extends JobHandlerInvoker {
}

View File

@@ -9,12 +9,12 @@ import java.time.Duration;
/**
* 多租户拓展的 RedisKeyDefine 实现类
*
* 由于 Redis 不同于 MySQL 有 column 字段,所以无法通过类似 WHERE tenant_id = ? 的方式过滤
* 由于 Redis 不同于 MySQL 有 column 字段,无法通过类似 WHERE tenant_id = ? 的方式过滤
* 所以需要通过在 Redis Key 上增加后缀的方式,进行租户之间的隔离。具体的步骤是:
* 1. 假设 Redis Key 是 user:%d示例是 user:1对应到多租户的 Redis Key 是 user:%d:%d
* 2. 在 Redis DAO 中,需要使用 {@link #formatKey(Object...)} 方法,进行 Redis Key 的格式化
*
* 注意,大多数情况下,并不用使用 TenantRedisKeyDefine 实现。主要的使用场景,是 Redis Key 可能存在冲突的情况。
* 注意,大多数情况下,并不用使用 TenantRedisKeyDefine 实现。主要的使用场景,是 Redis Key 可能存在冲突的情况。
* 例如说,租户 1 和 2 都有一个手机号作为 Key则他们会存在冲突的问题
*
* @author 芋道源码

View File

@@ -1,13 +1,19 @@
package cn.iocoder.yudao.framework.tenant.core.security;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.tenant.config.TenantProperties;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
import cn.iocoder.yudao.framework.web.config.WebProperties;
import cn.iocoder.yudao.framework.web.core.filter.ApiRequestFilter;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.util.AntPathMatcher;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
@@ -18,34 +24,92 @@ import java.util.Objects;
/**
* 多租户 Security Web 过滤器
* 校验用户访问的租户,是否是其所在的租户,避免越权问题
* 1. 如果是登陆的用户,校验是否有权限访问该租户,避免越权问题
* 2. 如果请求未带租户的编号,检查是否是忽略的 URL否则也不允许访问。
* 3. 校验租户是合法,例如说被禁用、到期
*
* 校验用户访问的租户,是否是其所在的租户,
*
* @author 芋道源码
*/
@Slf4j
public class TenantSecurityWebFilter extends OncePerRequestFilter {
public class TenantSecurityWebFilter extends ApiRequestFilter {
private final TenantProperties tenantProperties;
private final AntPathMatcher pathMatcher;
private final GlobalExceptionHandler globalExceptionHandler;
private final TenantFrameworkService tenantFrameworkService;
public TenantSecurityWebFilter(TenantProperties tenantProperties,
WebProperties webProperties,
GlobalExceptionHandler globalExceptionHandler,
TenantFrameworkService tenantFrameworkService) {
super(webProperties);
this.tenantProperties = tenantProperties;
this.pathMatcher = new AntPathMatcher();
this.globalExceptionHandler = globalExceptionHandler;
this.tenantFrameworkService = tenantFrameworkService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
Long tenantId = TenantContextHolder.getTenantId();
// 1. 登陆的用户,校验是否有权限访问该租户,避免越权问题。
LoginUser user = SecurityFrameworkUtils.getLoginUser();
assert user != null; // shouldNotFilter 已经校验
// 校验租户是否匹配。
if (!Objects.equals(user.getTenantId(), TenantContextHolder.getTenantId())) {
log.error("[doFilterInternal][租户({}) User({}/{}) 越权访问租户({}) URL({}/{})]",
user.getTenantId(), user.getId(), user.getUserType(),
TenantContextHolder.getTenantId(), request.getRequestURI(), request.getMethod());
ServletUtils.writeJSON(response, CommonResult.error(GlobalErrorCodeConstants.FORBIDDEN.getCode(),
"您无权访问租户的数据"));
return;
if (user != null) {
// 如果获取不到租户编号,则尝试使用登陆用户的租户编号
if (tenantId == null) {
tenantId = user.getTenantId();
TenantContextHolder.setTenantId(tenantId);
// 如果传递了租户编号,则进行比对租户编号,避免越权问题
} else if (!Objects.equals(user.getTenantId(), TenantContextHolder.getTenantId())) {
log.error("[doFilterInternal][租户({}) User({}/{}) 越权访问租户({}) URL({}/{})]",
user.getTenantId(), user.getId(), user.getUserType(),
TenantContextHolder.getTenantId(), request.getRequestURI(), request.getMethod());
ServletUtils.writeJSON(response, CommonResult.error(GlobalErrorCodeConstants.FORBIDDEN.getCode(),
"您无权访问该租户的数据"));
return;
}
}
//检查是否是忽略的 URL, 如果是则允许访问
if (!isIgnoreUrl(request)) {
// 2. 如果请求未带租户的编号,不允许访问。
if (tenantId == null) {
log.error("[doFilterInternal][URL({}/{}) 未传递租户编号]", request.getRequestURI(), request.getMethod());
ServletUtils.writeJSON(response, CommonResult.error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
"租户的请求未传递,请进行排查"));
return;
}
// 3. 校验租户是合法,例如说被禁用、到期
try {
tenantFrameworkService.validTenant(tenantId);
} catch (Throwable ex) {
CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex);
ServletUtils.writeJSON(response, result);
return;
}
}
// 继续过滤
chain.doFilter(request, response);
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return SecurityFrameworkUtils.getLoginUser() == null;
private boolean isIgnoreUrl(HttpServletRequest request) {
// 快速匹配,保证性能
if (CollUtil.contains(tenantProperties.getIgnoreUrls(), request.getRequestURI())) {
return true;
}
// 逐个 Ant 路径匹配
for (String url : tenantProperties.getIgnoreUrls()) {
if (pathMatcher.match(url, request.getRequestURI())) {
return true;
}
}
return false;
}
}

View File

@@ -16,4 +16,11 @@ public interface TenantFrameworkService {
*/
List<Long> getTenantIds();
/**
* 校验租户是否合法
*
* @param id 租户编号
*/
void validTenant(Long id);
}

View File

@@ -0,0 +1,36 @@
package cn.iocoder.yudao.framework.tenant.core.util;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
/**
* 多租户 Util
*
* @author 芋道源码
*/
public class TenantUtils {
/**
* 使用指定租户,执行对应的逻辑
*
* 注意,如果当前是忽略租户的情况下,会被强制设置成不忽略租户
* 当然,执行完成后,还是会恢复回去
*
* @param tenantId 租户编号
* @param runnable 逻辑
*/
public static void execute(Long tenantId, Runnable runnable) {
Long oldTenantId = TenantContextHolder.getTenantId();
Boolean oldIgnore = TenantContextHolder.isIgnore();
try {
TenantContextHolder.setTenantId(tenantId);
TenantContextHolder.setIgnore(false);
// 执行逻辑
runnable.run();
} finally {
TenantContextHolder.setTenantId(oldTenantId);
TenantContextHolder.setIgnore(oldIgnore);
}
}
}

View File

@@ -1,6 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.yudao.framework.tenant.config.YudaoTenantDatabaseAutoConfiguration,\
cn.iocoder.yudao.framework.tenant.config.YudaoTenantWebAutoConfiguration,\
cn.iocoder.yudao.framework.tenant.config.YudaoTenantJobAutoConfiguration,\
cn.iocoder.yudao.framework.tenant.config.YudaoTenantMQAutoConfiguration,\
cn.iocoder.yudao.framework.tenant.config.YudaoTenantSecurityAutoConfiguration
cn.iocoder.yudao.framework.tenant.config.YudaoTenantAutoConfiguration

View File

@@ -11,7 +11,7 @@
<artifactId>yudao-spring-boot-starter-biz-weixin</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>微信拓展
1. 基于 weixin-java-mp 库,对接微信公众号平台。目前主要解决微信公众号的支付场景。
</description>

View File

@@ -11,7 +11,7 @@
<artifactId>yudao-spring-boot-starter-config</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>配置中心,基于 Apollo 魔改实现</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>

View File

@@ -11,7 +11,7 @@
<artifactId>yudao-spring-boot-starter-excel</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>Excel 拓展</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>

View File

@@ -12,7 +12,7 @@
<artifactId>yudao-spring-boot-starter-extension</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>扩展点组件</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>yudao-framework</artifactId>
<groupId>cn.iocoder.boot</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-spring-boot-starter-flowable</artifactId>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-common</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-security</artifactId>
</dependency>
<!-- flowable 工作流相关 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-basic</artifactId>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,22 @@
package cn.iocoder.yudao.framework.flowable.config;
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
import cn.iocoder.yudao.framework.flowable.core.web.FlowableWebFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class YudaoFlowableConfiguration {
/**
* 配置 flowable Web 过滤器
*/
@Bean
public FilterRegistrationBean<FlowableWebFilter> flowableWebFilter() {
FilterRegistrationBean<FlowableWebFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new FlowableWebFilter());
registrationBean.setOrder(WebFilterOrderEnum.FLOWABLE_FILTER);
return registrationBean;
}
}

View File

@@ -0,0 +1 @@
package cn.iocoder.yudao.framework.flowable.core;

View File

@@ -0,0 +1,62 @@
package cn.iocoder.yudao.framework.flowable.core.util;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.common.engine.impl.identity.Authentication;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class FlowableUtils {
public static void setAuthenticatedUserId(Long userId) {
Authentication.setAuthenticatedUserId(String.valueOf(userId));
}
public static void clearAuthenticatedUserId() {
Authentication.setAuthenticatedUserId(null);
}
/**
* 获得 BPMN 流程中,指定的元素们
*
* @param model
* @param clazz 指定元素。例如说,{@link org.flowable.bpmn.model.UserTask}、{@link org.flowable.bpmn.model.Gateway} 等等
* @return 元素们
*/
public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) {
List<T> result = new ArrayList<>();
model.getProcesses().forEach(process -> {
process.getFlowElements().forEach(flowElement -> {
if (flowElement.getClass().isAssignableFrom(clazz)) {
result.add((T) flowElement);
}
});
});
return result;
}
/**
* 比较 两个bpmnModel 是否相同
* @param oldModel 老的bpmn model
* @param newModel 新的bpmn model
*/
public static boolean equals(BpmnModel oldModel, BpmnModel newModel) {
// 由于 BpmnModel 未提供 equals 方法,所以只能转成字节数组,进行比较
return Arrays.equals(getBpmnBytes(oldModel), getBpmnBytes(newModel));
}
/**
* 把 bpmnModel 转换成 byte[]
* @param model bpmnModel
*/
public static byte[] getBpmnBytes(BpmnModel model) {
if (model == null) {
return new byte[0];
}
BpmnXMLConverter converter = new BpmnXMLConverter();
return converter.convertToXML(model);
}
}

View File

@@ -0,0 +1,35 @@
package cn.iocoder.yudao.framework.flowable.core.web;
import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* flowable Web 过滤器,将 userId 设置到 {@link org.flowable.common.engine.impl.identity.Authentication} 中
*
* @author jason
*/
public class FlowableWebFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
try {
// 设置工作流的用户
Long userId = SecurityFrameworkUtils.getLoginUserId();
if (userId != null) {
FlowableUtils.setAuthenticatedUserId(userId);
}
// 过滤
chain.doFilter(request, response);
} finally {
// 清理
FlowableUtils.clearAuthenticatedUserId();
}
}
}

View File

@@ -0,0 +1 @@
package cn.iocoder.yudao.framework.flowable;

View File

@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.yudao.framework.flowable.config.YudaoFlowableConfiguration

View File

@@ -11,7 +11,7 @@
<artifactId>yudao-spring-boot-starter-job</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>任务拓展
1. 定时任务,基于 Quartz 拓展
2. 异步任务,基于 Spring Async 拓展

View File

@@ -11,7 +11,7 @@
<artifactId>yudao-spring-boot-starter-monitor</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>服务监控,提供链路追踪、日志服务、指标收集等等功能</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>

View File

@@ -11,7 +11,7 @@
<artifactId>yudao-spring-boot-starter-mq</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>消息队列,基于 Redis Pub/Sub 实现广播消费,基于 Stream 实现集群消费</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>

View File

@@ -1,6 +1,9 @@
package cn.iocoder.yudao.framework.mq.config;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.system.SystemUtil;
import cn.iocoder.yudao.framework.common.enums.DocumentEnum;
import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;
import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
@@ -10,17 +13,21 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.connection.stream.Consumer;
import org.springframework.data.redis.connection.stream.ObjectRecord;
import org.springframework.data.redis.connection.stream.ReadOffset;
import org.springframework.data.redis.connection.stream.StreamOffset;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.stream.DefaultStreamMessageListenerContainerX;
import org.springframework.data.redis.stream.StreamMessageListenerContainer;
import java.util.List;
import java.util.Properties;
/**
* 消息队列配置类
@@ -72,6 +79,7 @@ public class YudaoMQAutoConfiguration {
public StreamMessageListenerContainer<String, ObjectRecord<String, String>> redisStreamMessageListenerContainer(
RedisMQTemplate redisMQTemplate, List<AbstractStreamMessageListener<?>> listeners) {
RedisTemplate<String, ?> redisTemplate = redisMQTemplate.getRedisTemplate();
checkRedisVersion(redisTemplate);
// 第一步,创建 StreamMessageListenerContainer 容器
// 创建 options 配置
StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, ObjectRecord<String, String>> containerOptions =
@@ -81,11 +89,12 @@ public class YudaoMQAutoConfiguration {
.build();
// 创建 container 对象
StreamMessageListenerContainer<String, ObjectRecord<String, String>> container =
StreamMessageListenerContainer.create(redisTemplate.getRequiredConnectionFactory(), containerOptions);
// StreamMessageListenerContainer.create(redisTemplate.getRequiredConnectionFactory(), containerOptions);
DefaultStreamMessageListenerContainerX.create(redisMQTemplate.getRedisTemplate().getRequiredConnectionFactory(), containerOptions);
// 第二步,注册监听器,消费对应的 Stream 主题
String consumerName = buildConsumerName();
listeners.forEach(listener -> {
listeners.parallelStream().forEach(listener -> {
// 创建 listener 对应的消费者分组
try {
redisTemplate.opsForStream().createGroup(listener.getStreamKey(), listener.getGroup());
@@ -116,4 +125,19 @@ public class YudaoMQAutoConfiguration {
return String.format("%s@%d", SystemUtil.getHostInfo().getAddress(), SystemUtil.getCurrentPID());
}
/**
* 校验 Redis 版本号,是否满足最低的版本号要求!
*/
private static void checkRedisVersion(RedisTemplate<String, ?> redisTemplate) {
// 获得 Redis 版本
Properties info = redisTemplate.execute((RedisCallback<Properties>) RedisServerCommands::info);
String version = MapUtil.getStr(info, "redis_version");
// 校验最低版本必须大于等于 5.0.0
int majorVersion = Integer.parseInt(StrUtil.subBefore(version, '.', false));
if (majorVersion < 5) {
throw new IllegalStateException(StrUtil.format("您当前的 Redis 版本为 {},小于最低要求的 5.0.0 版本!" +
"请参考 {} 文档进行安装。", version, DocumentEnum.REDIS_INSTALL.getUrl()));
}
}
}

View File

@@ -0,0 +1,62 @@
package org.springframework.data.redis.stream;
import cn.hutool.core.util.ReflectUtil;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.stream.ByteRecord;
import org.springframework.data.redis.connection.stream.ReadOffset;
import org.springframework.data.redis.connection.stream.Record;
import org.springframework.util.Assert;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
/**
* 拓展 DefaultStreamMessageListenerContainer 实现,解决 Spring Data Redis + Redisson 结合使用时Redisson 在 Stream 获得不到数据时,返回 null 而不是空 List导致 NPE 异常。
* 对应 issuehttps://github.com/spring-projects/spring-data-redis/issues/2147 和 https://github.com/redisson/redisson/issues/4006
* 目前看下来 Spring Data Redis 不肯加 null 判断Redisson 暂时也没改返回 null 到空 List 的打算,所以暂时只能自己改,哽咽!
*
* @author 芋道源码
*/
public class DefaultStreamMessageListenerContainerX<K, V extends Record<K, ?>> extends DefaultStreamMessageListenerContainer<K, V> {
/**
* 参考 {@link StreamMessageListenerContainer#create(RedisConnectionFactory, StreamMessageListenerContainerOptions)} 的实现
*/
public static <K, V extends Record<K, ?>> StreamMessageListenerContainer<K, V> create(RedisConnectionFactory connectionFactory, StreamMessageListenerContainer.StreamMessageListenerContainerOptions<K, V> options) {
Assert.notNull(connectionFactory, "RedisConnectionFactory must not be null!");
Assert.notNull(options, "StreamMessageListenerContainerOptions must not be null!");
return new DefaultStreamMessageListenerContainerX<>(connectionFactory, options);
}
public DefaultStreamMessageListenerContainerX(RedisConnectionFactory connectionFactory, StreamMessageListenerContainerOptions<K, V> containerOptions) {
super(connectionFactory, containerOptions);
}
/**
* 参考 {@link DefaultStreamMessageListenerContainer#register(StreamReadRequest, StreamListener)} 的实现
*/
@Override
public Subscription register(StreamReadRequest<K> streamRequest, StreamListener<K, V> listener) {
return this.doRegisterX(getReadTaskX(streamRequest, listener));
}
@SuppressWarnings("unchecked")
private StreamPollTask<K, V> getReadTaskX(StreamReadRequest<K> streamRequest, StreamListener<K, V> listener) {
StreamPollTask<K, V> task = ReflectUtil.invoke(this, "getReadTask", streamRequest, listener);
// 修改 readFunction 方法
Function<ReadOffset, List<ByteRecord>> readFunction = (Function<ReadOffset, List<ByteRecord>>) ReflectUtil.getFieldValue(task, "readFunction");
ReflectUtil.setFieldValue(task, "readFunction", (Function<ReadOffset, List<ByteRecord>>) readOffset -> {
List<ByteRecord> records = readFunction.apply(readOffset);
//【重点】保证 records 不是空,避免 NPE 的问题!!!
return records != null ? records : Collections.emptyList();
});
return task;
}
private Subscription doRegisterX(Task task) {
return ReflectUtil.invoke(this, "doRegister", task);
}
}

View File

@@ -11,7 +11,7 @@
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>数据库连接池、多数据源、事务、MyBatis 拓展</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>

View File

@@ -43,12 +43,16 @@ public interface BaseMapperX<T> extends BaseMapper<T> {
return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2));
}
default Integer selectCount(String field, Object value) {
return selectCount(new QueryWrapper<T>().eq(field, value)).intValue();
default Long selectCount() {
return selectCount(new QueryWrapper<T>());
}
default Integer selectCount(SFunction<T, ?> field, Object value) {
return selectCount(new LambdaQueryWrapper<T>().eq(field, value)).intValue();
default Long selectCount(String field, Object value) {
return selectCount(new QueryWrapper<T>().eq(field, value));
}
default Long selectCount(SFunction<T, ?> field, Object value) {
return selectCount(new LambdaQueryWrapper<T>().eq(field, value));
}
default List<T> selectList() {

View File

@@ -11,7 +11,7 @@
<artifactId>yudao-spring-boot-starter-protection</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>服务保证,提供分布式锁、幂等、限流、熔断等等功能</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>

View File

@@ -11,7 +11,7 @@
<artifactId>yudao-spring-boot-starter-redis</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>Redis 封装拓展</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>

View File

@@ -11,7 +11,7 @@
<artifactId>yudao-spring-boot-starter-security</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>用户的认证、权限的校验</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>

View File

@@ -4,7 +4,6 @@ import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.time.Duration;
@@ -24,11 +23,6 @@ public class SecurityProperties {
*/
@NotNull(message = "Token 过期时间不能为空")
private Duration tokenTimeout;
/**
* Token 秘钥
*/
@NotEmpty(message = "Token 秘钥不能为空")
private String tokenSecret;
/**
* Session 过期时间
*

View File

@@ -66,7 +66,7 @@ public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
* 注意,在线上环境下,一定要关闭该功能!!!
*
* @param request 请求
* @param token 模拟的 token格式为 {@link SecurityProperties#getTokenSecret()} + 用户编号
* @param token 模拟的 token格式为 {@link SecurityProperties#getMockSecret()} + 用户编号
* @return 模拟的 LoginUser
*/
private LoginUser mockLoginUser(HttpServletRequest request, String token) {

View File

@@ -11,7 +11,7 @@
<artifactId>yudao-spring-boot-starter-test</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>测试组件,用于单元测试、集成测试</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>

View File

@@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.system.test;
package cn.iocoder.yudao.framework.test.config;
import com.github.fppt.jedismock.RedisServer;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
@@ -9,6 +9,11 @@ import org.springframework.context.annotation.Lazy;
import java.io.IOException;
/**
* Redis 测试 Configuration主要实现内嵌 Redis 的启动
*
* @author 芋道源码
*/
@Configuration(proxyBeanMethods = false)
@Lazy(false) // 禁止延迟加载
@EnableConfigurationProperties(RedisProperties.class)
@@ -20,7 +25,7 @@ public class RedisTestConfiguration {
@Bean
public RedisServer redisServer(RedisProperties properties) throws IOException {
RedisServer redisServer = new RedisServer(properties.getPort());
// TODO 芋艿一次执行多个单元测试时貌似创建多个 spring 容器导致不进行 stop这样就导致端口被占用无法启动
// 一次执行多个单元测试时貌似创建多个 spring 容器导致不进行 stop这样就导致端口被占用无法启动
try {
redisServer.start();
} catch (Exception ignore) {}

View File

@@ -0,0 +1,52 @@
package cn.iocoder.yudao.framework.test.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import javax.sql.DataSource;
/**
* SQL 初始化的测试 Configuration
*
* 为什么不使用 org.springframework.boot.autoconfigure.sql.init.DataSourceInitializationConfiguration 呢?
* 因为我们在单元测试会使用 spring.main.lazy-initialization 为 true开启延迟加载。此时会导致 DataSourceInitializationConfiguration 初始化
* 不过呢,当前类的实现代码,基本是复制 DataSourceInitializationConfiguration 的哈!
*
* @author 芋道源码
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(AbstractScriptDatabaseInitializer.class)
@ConditionalOnSingleCandidate(DataSource.class)
@ConditionalOnClass(name = "org.springframework.jdbc.datasource.init.DatabasePopulator")
@Lazy(value = false) // 禁止延迟加载
@EnableConfigurationProperties(SqlInitializationProperties.class)
public class SqlInitializationTestConfiguration {
@Bean
public DataSourceScriptDatabaseInitializer dataSourceScriptDatabaseInitializer(DataSource dataSource,
SqlInitializationProperties initializationProperties) {
DatabaseInitializationSettings settings = createFrom(initializationProperties);
return new DataSourceScriptDatabaseInitializer(dataSource, settings);
}
static DatabaseInitializationSettings createFrom(SqlInitializationProperties properties) {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(properties.getSchemaLocations());
settings.setDataLocations(properties.getDataLocations());
settings.setContinueOnError(properties.isContinueOnError());
settings.setSeparator(properties.getSeparator());
settings.setEncoding(properties.getEncoding());
settings.setMode(properties.getMode());
return settings;
}
}

View File

@@ -11,8 +11,8 @@
<artifactId>yudao-spring-boot-starter-web</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<description>用户的认证、权限的校验</description>
<name>${project.artifactId}</name>
<description>Web 框架全局异常、API 日志等</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<dependencies>

View File

@@ -8,15 +8,11 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ExampleBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.builders.RequestParameterBuilder;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
@@ -24,7 +20,6 @@ import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import static springfox.documentation.builders.RequestHandlerSelectors.basePackage;
@@ -37,8 +32,8 @@ import static springfox.documentation.builders.RequestHandlerSelectors.basePacka
@EnableSwagger2
@EnableKnife4j
@ConditionalOnClass({Docket.class, ApiInfoBuilder.class})
@ConditionalOnProperty(prefix = "yudao.swagger", value = "enable", matchIfMissing = true)
// 允许使用 swagger.enable=false 禁用 Swagger
@ConditionalOnProperty(prefix = "yudao.swagger", value = "enable", matchIfMissing = true)
@EnableConfigurationProperties(SwaggerProperties.class)
public class YudaoSwaggerAutoConfiguration {
@@ -62,9 +57,12 @@ public class YudaoSwaggerAutoConfiguration {
.paths(PathSelectors.any())
.build()
.securitySchemes(securitySchemes())
.globalRequestParameters(globalRequestParameters())
.securityContexts(securityContexts());
}
// ========== apiInfo ==========
/**
* API 摘要信息
*/
@@ -77,6 +75,8 @@ public class YudaoSwaggerAutoConfiguration {
.build();
}
// ========== securitySchemes ==========
/**
* 安全模式,这里配置通过请求头 Authorization 传递 token 参数
*/
@@ -105,4 +105,12 @@ public class YudaoSwaggerAutoConfiguration {
return new AuthorizationScope[]{new AuthorizationScope("global", "accessEverything")};
}
// ========== globalRequestParameters ==========
private static List<RequestParameter> globalRequestParameters() {
RequestParameterBuilder tenantParameter = new RequestParameterBuilder().name("tenant-id").description("租户编号")
.in(ParameterType.HEADER).example(new ExampleBuilder().value(1L).build());
return Collections.singletonList(tenantParameter.build());
}
}

View File

@@ -0,0 +1,27 @@
package cn.iocoder.yudao.framework.web.core.filter;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.web.config.WebProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.http.HttpServletRequest;
/**
* 过滤 /admin-api、/app-api 等 API 请求的过滤器
*
* @author 芋道源码
*/
@RequiredArgsConstructor
public abstract class ApiRequestFilter extends OncePerRequestFilter {
protected final WebProperties webProperties;
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
// 只过滤 API 请求的地址
return !StrUtil.startWithAny(request.getRequestURI(), webProperties.getAdminApi().getPrefix(),
webProperties.getAppApi().getPrefix());
}
}

View File

@@ -46,17 +46,22 @@ public class CacheRequestBodyWrapper extends HttpServletRequestWrapper {
@Override
public boolean isFinished() {
return true;
return false;
}
@Override
public boolean isReady() {
return true;
return false;
}
@Override
public void setReadListener(ReadListener readListener) {}
@Override
public int available() {
return body.length;
}
};
}

View File

@@ -17,7 +17,7 @@
<artifactId>yudao-module-bpm</artifactId>
<packaging>pom</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>
bpm 包下业务流程管理Business Process Management我们放工作流的功能。
例如说:流程定义、表单配置、审核中心(我的申请、我的待办、我的已办)等等

View File

@@ -11,7 +11,7 @@
<artifactId>yudao-module-bpm-api</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>
bpm 模块 API暴露给其它模块调用
</description>

View File

@@ -43,8 +43,8 @@ public interface ErrorCodeConstants {
ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1009004002, "流程取消失败,该流程不是你发起的");
// ========== 流程任务 1-009-005-000 ==========
ErrorCode TASK_COMPLETE_FAIL_NOT_EXISTS = new ErrorCode(1009004000, "审批任务失败,原因:该任务不处于未审批");
ErrorCode TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1009004001, "审批任务失败,原因:该任务的审批人不是你");
ErrorCode TASK_COMPLETE_FAIL_NOT_EXISTS = new ErrorCode(1009005000, "审批任务失败,原因:该任务不处于未审批");
ErrorCode TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1009005001, "审批任务失败,原因:该任务的审批人不是你");
// ========== 流程任务分配规则 1-009-006-000 ==========
ErrorCode TASK_ASSIGN_RULE_EXISTS = new ErrorCode(1009006000, "流程({}) 的任务({}) 已经存在分配规则");
@@ -55,7 +55,7 @@ public interface ErrorCodeConstants {
// ========== 动态表单模块 1-009-010-000 ==========
ErrorCode FORM_NOT_EXISTS = new ErrorCode(1009010000, "动态表单不存在");
ErrorCode FORM_FIELD_REPEAT = new ErrorCode(1009010000, "表单项({}) 和 ({}) 使用了相同的字段名({})");
ErrorCode FORM_FIELD_REPEAT = new ErrorCode(1009010001, "表单项({}) 和 ({}) 使用了相同的字段名({})");
// ========== 用户组模块 1-009-011-000 ==========
ErrorCode USER_GROUP_NOT_EXISTS = new ErrorCode(1009011000, "用户组不存在");

View File

@@ -10,7 +10,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-bpm-base</artifactId>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>
bpm-base 模块,实现公用的工作流的逻辑,提供给 bpm-activiti 和 bpm-flowable 复用
</description>
@@ -32,6 +32,10 @@
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-data-permission</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>

View File

@@ -88,4 +88,12 @@ public interface BpmFormService {
*/
PageResult<BpmFormDO> getFormPage(BpmFormPageReqVO pageReqVO);
/**
* 校验流程表单已配置
*
* @param configStr configStr 字段
* @return 流程表单
*/
BpmFormDO checkFormConfig(String configStr);
}

View File

@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.bpm.service.definition;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormCreateReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormUpdateReqVO;
@@ -8,18 +9,20 @@ import cn.iocoder.yudao.module.bpm.convert.definition.BpmFormConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmFormMapper;
import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmFormFieldRespDTO;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
/**
* 动态表单 Service 实现类
@@ -87,6 +90,29 @@ public class BpmFormServiceImpl implements BpmFormService {
return formMapper.selectPage(pageReqVO);
}
@Override
public BpmFormDO checkFormConfig(String configStr) {
BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(configStr, BpmModelMetaInfoRespDTO.class);
if (metaInfo == null || metaInfo.getFormType() == null) {
throw exception(MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG);
}
// 校验表单存在
if (Objects.equals(metaInfo.getFormType(), BpmModelFormTypeEnum.NORMAL.getType())) {
BpmFormDO form = getForm(metaInfo.getFormId());
if (form == null) {
throw exception(FORM_NOT_EXISTS);
}
return form;
}
return null;
}
private void checkKeyNCName(String key) {
if (!ValidationUtils.isXmlNCName(key)) {
throw exception(MODEL_KEY_VALID);
}
}
/**
* 校验 Field避免 field 重复
*

View File

@@ -5,7 +5,7 @@ import lombok.Data;
/**
* BPM 流程 MetaInfo Response DTO
* 主要用于 {@link org.activiti.engine.repository.Model#setMetaInfo(String)} 的存储
* 主要用于 { Model#setMetaInfo(String)} 的存储
*
* @author 芋道源码
*/

View File

@@ -21,7 +21,7 @@ public class BpmMessageSendWhenTaskCreatedReqDTO {
*/
@NotEmpty(message = "流程实例的名字不能为空")
private String processInstanceName;
@NotEmpty(message = "发起人的用户编号")
@NotNull(message = "发起人的用户编号")
private Long startUserId;
@NotEmpty(message = "发起人的昵称")
private String startUserNickname;

View File

@@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.bpm.test;
import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration;
import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;
import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
@@ -21,7 +21,6 @@ import org.springframework.test.context.jdbc.Sql;
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbUnitTest.Application.class)
@ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件
@Sql(scripts = "/sql/create_tables.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) // 每个单元测试结束前,创建表
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB
public class BaseDbUnitTest {
@@ -31,7 +30,7 @@ public class BaseDbUnitTest {
DataSourceAutoConfiguration.class, // Spring DB 自动配置类
DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类
DruidDataSourceAutoConfigure.class, // Druid 自动配置类
SqlInitializationAutoConfiguration.class,
SqlInitializationTestConfiguration.class, // SQL 初始化
// MyBatis 配置类
YudaoMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类
MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类

View File

@@ -16,6 +16,9 @@ spring:
druid:
async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
initial-size: 1 # 单元测试,配置为 1提升启动速度
sql:
init:
schema-locations: classpath:/sql/create_tables.sql
mybatis:
lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试

View File

@@ -1,3 +1,2 @@
-- bpm 开头的 DB
DELETE FROM "bpm_form";
DELETE FROM "bpm_user_group";

View File

@@ -1,4 +1,3 @@
-- bpm 开头的 DB
CREATE TABLE IF NOT EXISTS "bpm_user_group" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(63) NOT NULL,
@@ -11,7 +10,7 @@ CREATE TABLE IF NOT EXISTS "bpm_user_group" (
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '用户组';
) COMMENT '用户组';
CREATE TABLE IF NOT EXISTS "bpm_form" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,

View File

@@ -9,8 +9,9 @@
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-bpm-impl-activiti</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<name>${project.artifactId}</name>
<description>
bpm-activiti 模块,基于 Activiti 7 实现工作流
</description>

View File

@@ -8,7 +8,7 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
/**
* 流程实例 Api 实现类
* Activiti 流程实例 Api 实现类
*
* @author 芋道源码
*/

View File

@@ -93,5 +93,4 @@ public class BpmModelController {
bpmModelService.updateModelState(reqVO.getId(), reqVO.getState());
return success(true);
}
}

View File

@@ -1,10 +1,10 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleCreateReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleUpdateReqVO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
@@ -32,7 +32,7 @@ public class BpmTaskAssignRuleController {
@ApiOperation(value = "获得任务分配规则列表")
@ApiImplicitParams({
@ApiImplicitParam(name = "modelId", value = "模型编号", example = "1024", dataTypeClass = String.class),
@ApiImplicitParam(name = "processDefinitionId", value = "刘晨定义的编号", example = "2048", dataTypeClass = String.class)
@ApiImplicitParam(name = "processDefinitionId", value = "流程定义的编号", example = "2048", dataTypeClass = String.class)
})
@PreAuthorize("@ss.hasPermission('bpm:task-assign-rule:query')")
public CommonResult<List<BpmTaskAssignRuleRespVO>> getTaskAssignRuleList(

View File

@@ -37,7 +37,7 @@ public class BpmTaskController {
@GetMapping("done-page")
@ApiOperation("获取 Done 已办任务分页")
@PreAuthorize("@ss.hasPermission('bpm:task:query')")
public CommonResult<PageResult<BpmTaskDonePageItemRespVO>> getTodoTaskPage(@Valid BpmTaskDonePageReqVO pageVO) {
public CommonResult<PageResult<BpmTaskDonePageItemRespVO>> getDoneTaskPage(@Valid BpmTaskDonePageReqVO pageVO) {
return success(taskService.getDoneTaskPage(getLoginUserId(), pageVO));
}

View File

@@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.bpm.convert.definition;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmProcessDefinitionCreateReqDTO;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmProcessDefinitionCreateReqDTO;
import org.activiti.engine.impl.persistence.entity.SuspensionState;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;

View File

@@ -1,11 +1,11 @@
package cn.iocoder.yudao.module.bpm.convert.definition;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmProcessDefinitionCreateReqDTO;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import org.activiti.engine.impl.persistence.entity.SuspensionState;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;

View File

@@ -73,7 +73,8 @@ public interface BpmTaskConvert {
@Mappings({
@Mapping(source = "id", target = "taskId"),
@Mapping(source = "assignee", target = "assigneeUserId"),
@Mapping(source = "createdDate", target = "createTime")
@Mapping(source = "createdDate", target = "createTime"),
@Mapping(target = "id", ignore = true)
})
BpmTaskExtDO convert(org.activiti.api.task.model.Task bean);
@@ -104,6 +105,7 @@ public interface BpmTaskConvert {
BpmTaskRespVO convert3(HistoricTaskInstance bean);
BpmTaskRespVO.User convert3(AdminUserRespDTO bean);
@Mapping(target = "id", ignore = true)
void copyTo(BpmTaskExtDO from, @MappingTarget BpmTaskDonePageItemRespVO to);
@Mappings({

View File

@@ -44,7 +44,7 @@ public class BpmActivityBehaviorFactory extends DefaultActivityBehaviorFactory {
@Override
public UserTaskActivityBehavior createUserTaskActivityBehavior(UserTask userTask) {
BpmUserTaskActivitiBehavior userTaskActivityBehavior = new BpmUserTaskActivitiBehavior(userTask);
BpmUserTaskActivityBehavior userTaskActivityBehavior = new BpmUserTaskActivityBehavior(userTask);
userTaskActivityBehavior.setBpmTaskRuleService(bpmTaskRuleService);
userTaskActivityBehavior.setPermissionApi(permissionApi);
userTaskActivityBehavior.setDeptApi(deptApi);

View File

@@ -44,7 +44,7 @@ import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.TASK_CREATE_F
* @author 芋道源码
*/
@Slf4j
public class BpmUserTaskActivitiBehavior extends UserTaskActivityBehavior {
public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
@Setter
private BpmTaskAssignRuleService bpmTaskRuleService;
@@ -64,7 +64,7 @@ public class BpmUserTaskActivitiBehavior extends UserTaskActivityBehavior {
*/
private Map<Long, BpmTaskAssignScript> scriptMap = Collections.emptyMap();
public BpmUserTaskActivitiBehavior(UserTask userTask) {
public BpmUserTaskActivityBehavior(UserTask userTask) {
super(userTask);
}

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