335 Commits

Author SHA1 Message Date
99e66bf11c 若依 3.8.8 2024-06-30 08:02:22 +08:00
a96d4bf2ed 菜单管理新增路由名称 2024-06-29 19:08:09 +08:00
8264b8fb31 删除多余的依赖 2024-06-27 11:08:31 +08:00
4ec32367fd 升级core-js到最新版本3.37.1 2024-06-27 10:22:55 +08:00
9e8aa14348 优化查表特殊字符使用反斜杠进行转义 2024-06-27 10:22:38 +08:00
10f68b97af 升级spring-security到安全版本,防止漏洞风险 2024-06-26 17:43:14 +08:00
8eff83e2b4 优化代码 2024-06-26 17:40:01 +08:00
7b064d84bb 升级druid到最新版本1.2.23 2024-06-25 12:29:13 +08:00
88560a7aa5 升级oshi到最新版本6.6.1 2024-06-25 12:28:50 +08:00
e14f40670a 优化代码 2024-06-25 12:27:21 +08:00
5b98495067 cron生成的表达式hour优化 2024-06-25 12:02:23 +08:00
259dc67728 优化数据权限代码 2024-06-05 12:30:43 +08:00
bc7a607033 Excel注解新增属性comboReadDict 2024-06-02 19:29:11 +08:00
161cd2b1ea 优化代码生成主子表关联查询方式 2024-06-02 19:28:40 +08:00
7480fb4020 优化导入Excel时设置dictType属性重复查缓存问题 2024-05-30 13:35:43 +08:00
906c3a68b8 添加新群号:151450850 2024-05-29 14:48:56 +08:00
084bab3494 update sql 2024-05-29 14:48:40 +08:00
cc0efa3330 优化代码 2024-05-29 14:48:23 +08:00
f46b1bbebd 限制用户操作数据权限范围 2024-05-29 14:48:03 +08:00
e5f30b1a19 升级spring-framework到安全版本,防止漏洞风险 2024-04-11 16:43:48 +08:00
1140a6c333 新增数据脱敏过滤注解 2024-04-08 13:16:27 +08:00
86ca404dbf 设置表格头单元格文本形式 2024-03-22 16:44:54 +08:00
11320b2e13 Excel注解ColumnType类型新增文本 2024-03-22 16:23:19 +08:00
905c08fb2c 升级oshi到最新版本6.5.0 2024-03-19 16:38:37 +08:00
9386645150 定义Locale默认国际化配置 2024-03-19 16:38:03 +08:00
bf3e2115e3 update vue.config.js 2024-03-18 14:28:28 +08:00
61eb54e4a1 更新compressionPlugin到6.1.2以兼容node18+ 2024-03-18 14:11:26 +08:00
d93e2b9df0 定时任务白名单配置范围缩小 2024-03-11 11:07:29 +08:00
50339c6f73 update copyright 2024 2024-03-11 10:47:55 +08:00
b83f2ff60b 添加新群号:138988063 2024-03-11 10:47:40 +08:00
66128f140f joblog order by 2024-03-11 09:42:15 +08:00
8c990ae9fc 用户密码新增非法字符验证 2024-03-01 21:53:57 +08:00
8836d31d77 升级oshi到最新版本6.4.13 2024-03-01 14:33:56 +08:00
2f624ab5f4 代码生成新增创建表结构功能 2024-03-01 14:33:09 +08:00
80f96b4915 升级oshi到最新版本6.4.11 2024-01-25 11:41:57 +08:00
7e9d050432 update http user-agent 2024-01-25 11:41:20 +08:00
649cfe8652 优化匹配方式 2024-01-25 11:34:25 +08:00
e9ae7ae5f3 !825 update: 修改退出处理类的日志记录和返回内容
Merge pull request !825 from 致远/master
2024-01-05 05:01:24 +00:00
3cc6fb5535 update: 修改退出处理类的日志记录和返回内容 2024-01-04 21:11:13 +08:00
a7bfd3b2d6 !822 删除未生效代码
Merge pull request !822 from mrzxc/fixbug/unuseCodeDelete
2024-01-02 02:20:43 +00:00
08d0326718 fix: delete unuse code 2023-12-25 10:22:05 +08:00
3f4ac65a31 remove packages 2023-12-13 11:51:17 +08:00
94d5c174aa 添加新群号:161281055 2023-12-13 11:47:35 +08:00
e719ac8cff !817 密码输入错误时,登录日志重复
Merge pull request !817 from 也曾为你像超人/N/A
2023-12-13 03:46:20 +00:00
a9bcfc66c3 密码输入错误时,登录日志重复
Signed-off-by: 也曾为你像超人 <1553592282@qq.com>
2023-12-10 14:18:35 +00:00
36b900cef8 若依 3.8.7 2023-12-08 09:03:30 +08:00
ac9302e2a2 升级element-ui到最新版本2.15.14 2023-12-07 11:08:03 +08:00
0f7e3a744e 删除无用的代码 2023-12-07 11:07:30 +08:00
45656b271a 升级oshi到最新版本6.4.8 2023-12-05 11:28:42 +08:00
323e3b7371 升级pagehelper到最新版1.4.7 2023-12-05 11:28:18 +08:00
bfbaa9e7b5 升级druid到最新版本1.2.20 2023-12-05 11:28:05 +08:00
2253a146b3 update fastjson2 2023-12-05 10:48:22 +08:00
2070a9252a 操作日志记录部门名称 2023-12-05 10:47:39 +08:00
e231d78469 修复代码生成导入后必填项与数据库不匹配问题 2023-12-05 10:45:54 +08:00
f74454b61a 删除无用的实例演示开关配置 2023-12-05 10:44:50 +08:00
d71ee5dba1 显隐列组件支持复选框弹出类型 2023-12-01 11:20:12 +08:00
78b1ac4a60 代码生成支持选择前端模板类型 2023-11-30 09:38:07 +08:00
966a17123f 优化代码 2023-11-30 09:37:36 +08:00
42bb8f6445 优化头像上传参数新增文件名称 2023-11-29 12:41:04 +08:00
72e4cd9fb3 优化字典标签支持自定义分隔符 2023-11-29 12:40:47 +08:00
1525bd8b54 优化下载zip方法新增遮罩层 2023-11-29 12:40:01 +08:00
b8e2eeaaf8 优化缓存监控图表支持跟随屏幕大小自适应调整 2023-11-29 12:39:22 +08:00
cbcfabee2a 优化代码 2023-11-29 12:38:45 +08:00
e6d0599b25 优化个人中心/基本资料修改时数据显示问题 2023-11-28 12:36:30 +08:00
b224cebab7 防止高频率定时任务不执行问题 2023-11-28 12:35:04 +08:00
f880dee7a4 !804 update ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserPostMapper.java.
Merge pull request !804 from 刚刚好/N/A
2023-11-28 04:07:51 +00:00
f16875c9af !799 update ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java.
Merge pull request !799 from 张利/N/A
2023-11-28 04:04:36 +00:00
a90355eb5e !791 优化白名单页面放行逻辑
Merge pull request !791 from 也曾为你像超人/N/A
2023-11-28 03:54:05 +00:00
386f32a3b7 update ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserPostMapper.java.
提交错别字

Signed-off-by: 刚刚好 <380862139@qq.com>
2023-11-12 02:38:46 +00:00
4ca30f08d6 修改权限字符匹配方式 2023-11-10 15:46:27 +08:00
73f881c7d3 修复五级路由缓存无效问题 2023-11-10 15:31:30 +08:00
b357aedaa3 修复内链iframe没有传递参数问题(I8DUOJ) 2023-11-10 11:13:16 +08:00
8cf8c8acd0 修复外链带端口出现的异常(I86J4B) 2023-11-07 11:38:19 +08:00
fbab383bd7 update ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java.
此处新密码加密了两次,多余的操作,且会导致新生成的数据库密码与缓存中的密码不同,如果修改的不对还请讲解回复下,谢谢。

Signed-off-by: 张利 <zhangli_wei555@163.com>
2023-11-02 02:57:04 +00:00
d8255edf84 新增编程式判断资源访问权限 2023-11-01 16:02:53 +08:00
eff42d8b0f !797 修复字典表详情页面搜索bug
Merge pull request !797 from 也曾为你像超人/N/A
2023-11-01 01:57:49 +00:00
1f753e3d84 修复字典表详情页面搜索bug
Signed-off-by: 也曾为你像超人 <1553592282@qq.com>
2023-10-30 03:50:19 +00:00
72d4069537 优化数字金额大写转换精度丢失问题(I81IJA) 2023-10-27 12:25:54 +08:00
76205588f0 update ruoyi-ui/src/permission.js.
Signed-off-by: 也曾为你像超人 <1553592282@qq.com>
2023-10-24 07:45:03 +00:00
7b4ba0146b 升级fastjson到最新版2.0.41 2023-10-21 14:44:02 +08:00
3963e86537 升级oshi到最新版本6.4.6 2023-10-21 14:34:05 +08:00
7098acc968 登录不做数据重复提交验证 2023-10-21 14:31:12 +08:00
079ac841f3 添加新群号:174951577 2023-10-09 21:27:00 +08:00
0434b4ca7a 去掉多余的参数 2023-10-09 21:26:40 +08:00
8873dc9b64 富文本Editor组件检验图片格式 2023-10-02 12:45:27 +08:00
078a3aad5a 修复HeaderSearch组件跳转query参数丢失问题 2023-09-28 22:24:25 +08:00
207a9ce855 操作日志列表新增IP地址查询 2023-09-27 15:21:59 +08:00
9ced1e9766 全局数据存储用户编号 2023-09-27 15:21:37 +08:00
1926840204 优化菜单管理类型为按钮状态可选 2023-09-18 15:04:34 +08:00
006d46ad07 修复自定义字典样式不生效的问题(I81F03) 2023-09-14 16:55:07 +08:00
f5a1b0c550 删除无用的传参 2023-09-01 09:37:16 +08:00
4a78fe116d 优化TopNav菜单没有图标svg不显示 2023-08-31 10:18:25 +08:00
3e95dd21f2 !772 修改未登录访问需要登录的资源,在登录后重定向丢失请求参数问题
Merge pull request !772 from who's hu/pr
2023-08-31 02:17:32 +00:00
491b0f3db8 修复字典缓存删除方法参数错误问题(I7UDIR) 2023-08-23 14:54:20 +08:00
16d8b71e21 update ruoyi-ui/src/permission.js.
由于重定向url存在 http://xxx.xx.xxx/{id}?param={a}&name={b} 的场景, 当未登录访问时, 通过改js封装登录后重定向参数, 会丢失?后的query params
如:
访问 http://localhost:1024/core/doc/doc?id=1683734914907807745&version=31
期望 http://localhost:1024/login?redirect=%2Fcore%2Fdoc%2Fdoc%3Fid%3D1683734914907807745%26version%3D31
实际通过 to.fullPath 封装后 获得 http://localhost:1024/login?redirect=%2Fcore%2Fdoc%2Fdoc%3Fid%3D1683734914907807745&version=31

登录成功跳转到重定向参数url后, 导致version参数丢失.
需要对 to.fullPath 进行一次编码, 以保证重定向前 to.fullPath 的完整性.
通过 ${encodeURIComponent(to.fullPath)} 获得 http://localhost:1024/login?redirect=%2Fcore%2Fdoc%2Fdoc%3Fid%3D1683734914907807745%26version%3D31 完整url



Signed-off-by: who's hu <hup_dev@outlook.com>
2023-08-22 09:25:19 +00:00
90260ce2f9 修复Excels导入时无法获取到dictType字典值问题(I7M4PW) 2023-08-21 15:52:30 +08:00
d58942c506 防重复提交数据大小限制(I7KZDA) 2023-08-21 11:57:14 +08:00
6a742e1d1b Excel导入数据临时文件无法删除问题(I7KIXX) 2023-08-19 15:43:57 +08:00
5b61aea064 修复树模板父级编码变量错误(I7JZ0L) 2023-08-19 14:34:30 +08:00
45ef542687 升级fastjson到最新版2.0.39 2023-08-15 12:17:27 +08:00
4ac7a1aa1f 升级commons.io到最新版本2.13.0 2023-08-15 11:31:38 +08:00
c5e4459bb8 优化代码 2023-08-15 11:30:49 +08:00
8f67bf416b 升级oshi到最新版本6.4.4 2023-08-14 19:11:46 +08:00
ab99a72b65 优化代码 2023-08-14 19:11:13 +08:00
7c9423657e Excel自定义数据处理器增加单元格/工作簿对象 2023-08-14 17:42:44 +08:00
128b186b8e 优化定时任务状态页面显示 2023-08-14 17:42:24 +08:00
68ac40eda9 update maven-plugin 2023-08-14 17:41:52 +08:00
5557433235 添加新群号:143961921 2023-07-28 11:12:09 +08:00
2517e9dddb 优化登录提示信息(I6ADCR) 2023-07-24 15:16:52 +08:00
a0595711ca 优化页签在Firefox浏览器被遮挡的问题 2023-07-06 22:09:16 +08:00
1ffb6379f7 排序属性orderBy参数限制长度 2023-07-06 22:09:02 +08:00
4d5c204b9a 优化代码 2023-07-06 22:08:47 +08:00
8ee740ef49 update sql 2023-07-06 22:07:00 +08:00
6a811d9824 若依 3.8.6 2023-06-30 08:43:54 +08:00
1c9c076280 升级oshi到最新版本6.4.3 2023-06-29 08:50:27 +08:00
918f94d8da 升级fastjson到最新版2.0.34 2023-06-29 08:38:33 +08:00
5db610d16f optimized code 2023-06-28 21:31:25 +08:00
cc6f983ee3 升级spring-boot到最新版本2.5.15 2023-06-24 14:49:03 +08:00
afe2852bbb update banner.txt 2023-06-24 14:48:54 +08:00
9c7d302b94 升级element-ui到最新版本2.15.13 2023-06-24 10:57:40 +08:00
9e66ada9c1 优化代码 2023-06-24 10:57:05 +08:00
a63eec3be4 !714 修改侧边栏的平台标题内容与process.env.VUE_APP_TITLE保持同步
Merge pull request !714 from Yakov/N/A
2023-06-24 02:16:00 +00:00
51990695f5 !729 update ruoyi-admin/src/main/resources/application.yml.
Merge pull request !729 from WhiskyZulu/N/A
2023-06-24 02:15:34 +00:00
a7b8f2ee90 !722 update ruoyi-admin/src/main/resources/banner.txt.
Merge pull request !722 from 万河/N/A
2023-06-24 02:13:44 +00:00
67ba621db6 update ruoyi-admin/src/main/resources/application.yml.
注释不太对,“数组计算”改为“数字计算”

Signed-off-by: WhiskyZulu <a913681304@qq.com>
2023-06-05 01:44:12 +00:00
05feef34c7 update ruoyi-admin/src/main/resources/banner.txt.
线条填歪了,看着难受

Signed-off-by: 万河 <12894283+science-01@user.noreply.gitee.com>
2023-05-18 08:53:14 +00:00
be0b36f6b9 update ruoyi-ui/src/layout/components/Sidebar/Logo.vue.
修改了第38行的/* title: '若依后台管理系统',  */ 为/* title: process.env.VUE_APP_TITLE, */,使得侧边栏的平台标题内容可以和vue.config.js里面的process.env.VUE_APP_TITLE保持同步。

Signed-off-by: yangfanao <2364917935@qq.com>
2023-04-25 09:35:36 +00:00
69bbccbd76 添加新群号:136919097 2023-04-23 15:46:53 +08:00
1eb7b3a03f !713 缓存列表:多次清除操作,提示不变的问题
Merge pull request !713 from 刘立伟/master
2023-04-23 06:57:17 +00:00
4661edf7f0 !712 修复路由跳转被阻止时vue-router内部产生报错信息问题
Merge pull request !712 from 爱吃猫的鱼/master
2023-04-23 06:55:46 +00:00
8485605145 !710 修复代码生成表字段注释不全问题
Merge pull request !710 from zouhuu/dev
2023-04-23 06:54:34 +00:00
a4fe88ca61 !707 恢复翻页/切换路由滚动功能
Merge pull request !707 from 也曾为你像超人/master
2023-04-23 06:53:45 +00:00
af15a3b274 !704 Vue的DictTag组件,当value没有匹配的值时,展示value
Merge pull request !704 from Aurora/master
2023-04-23 06:53:11 +00:00
571393c32c 缓存列表:多次清除操作,提示不变的问题; 2023-04-20 15:18:17 +08:00
eff06c110f 修复路由跳转被阻止时vue-router内部产生报错信息问题 2023-04-20 15:02:38 +08:00
6a18e06339 去除element滚动条 2023-04-17 18:52:46 +08:00
f04ca57f7a update ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml.
修复生成列字段注释显示不全问题

Signed-off-by: zouhuu <zouhugz@163.com>
2023-04-17 08:08:44 +00:00
b4f2a4f7dd 恢复翻页/切换路由滚动功能 2023-04-15 17:01:18 +08:00
de0a43285f update pom.xml.
去除多余代码

Signed-off-by: zouhuu <zouhugz@163.com>
2023-04-14 07:31:50 +00:00
4952ac0a3d 修改DictTag组件,当value没有匹配的值时,展示value 2023-04-12 15:14:09 +08:00
6ad345331d 修复开启TopNav后一级菜单路由参数设置无效问题(I6T1DK) 2023-04-11 16:51:55 +08:00
5a634a4ecd 修复导入用户时无法更新存在用户数据的问题 2023-04-10 18:03:34 +08:00
f5b865a2e1 优化用户导入更新时需获取用户编号问题 2023-04-10 17:58:03 +08:00
f7595e4998 !700 newInstance() 已弃用,使用clazz.getDeclaredConstructor().newInstance()
Merge pull request !700 from Nymph2333/N/A
2023-04-10 09:32:01 +00:00
64e71302e4 !699 修改注释中不存在的参数 set
Merge pull request !699 from bell/N/A
2023-04-10 09:26:01 +00:00
2e99c68ed0 !695 下拉图标选择组件优化:1.已选择图标高亮回显 2.滚动条采用el-scrollbar
Merge pull request !695 from 绿色心情/icon-select
2023-04-10 09:09:43 +00:00
af0e0a110e newInstance() 已弃用,使用clazz.getDeclaredConstructor().newInstance()
This method propagates any exception thrown by the nullary constructor, including a checked exception. Use of this method effectively bypasses the compile-time exception checking that would otherwise be performed by the compiler. The Constructor.newInstance method avoids this problem by wrapping any exception thrown by the constructor in a (checked) InvocationTargetException.
The call
 clazz.newInstance()
can be replaced by
 clazz.getDeclaredConstructor().newInstance()
The latter sequence of calls is inferred to be able to throw the additional exception types InvocationTargetException and NoSuchMethodException. Both of these exception types are subclasses of ReflectiveOperationException.

Signed-off-by: Nymph2333 <498092988@qq.com>
2023-04-10 06:27:40 +00:00
bef86e041f 修改注释中不存在的参数 set
Signed-off-by: bell <bellaconly@qq.com>
2023-04-10 03:20:19 +00:00
1067567f1c 下拉图标选择组件优化:1.已选择图标高亮回显 2.滚动条采用el-scrollbar 2023-04-09 13:20:59 +08:00
e
0a670fdfd7 将el-scrollbar移动到main-container下,避免鼠标移出时无法隐藏的问题 2023-04-08 04:47:34 +08:00
a33090c90e 添加新群号:101046199 2023-04-05 17:52:27 +08:00
5061558e94 优化固定头部页签滚动条被隐藏的问题 2023-04-05 17:50:32 +08:00
e7f088552f !686 导出Excel,提高导出效率
Merge pull request !686 from wzy1024/wzy1024
2023-04-05 09:36:45 +00:00
5c4682e060 !683 修复tab栏“关闭其他”异常的问题
Merge pull request !683 from 也曾为你像超人/N/A
2023-04-05 09:35:24 +00:00
5d5ebbec1a !682 解决表字段comment过长问题
Merge pull request !682 from baozhigang/column-comment
2023-04-05 09:35:04 +00:00
23544bab5e !681 移除vue-multiselect样式
Merge pull request !681 from Jimi/master
2023-04-05 09:34:17 +00:00
c5ef0336a4 !676 优化选择图标组件
Merge pull request !676 from 也曾为你像超人/master
2023-04-05 09:30:43 +00:00
a907f8485c 导出Excel,@Excel注解使用dictType属性时,如果有大量的字典数据,就会有大量的查询redis(打开、关闭),导致特别慢。于是使用map存储字典数据,相同的key就不需要再次去查询redis,大大提高了导出效率。 2023-04-04 11:58:26 +08:00
66200c4203 修复tab栏”关闭其他“异常的问题
Signed-off-by: 也曾为你像超人 <1553592282@qq.com>
2023-04-01 03:17:47 +00:00
5a25212509 解决表字段comment过长问题 2023-03-30 20:10:22 +08:00
95742bf5bd style:移除vue-multiselect样式(项目中并未安装vue-multiselect plugin) 2023-03-30 14:58:06 +08:00
4eea8cdbb0 优化选择图标组件 2023-03-18 10:59:05 +08:00
cfce89be7d 升级fastjson到最新版2.0.25 2023-03-18 10:30:34 +08:00
ce7e12ec1d delete build style 2023-03-18 09:31:26 +08:00
4f02f3c6f7 支持自定义隐藏属性列过滤子对象(I6GKPE) 2023-03-17 14:13:39 +08:00
5ca9bd6876 !673 $tab.closePage后存在非首页页签时不应该跳转首页
Merge pull request !673 from Giovanni/master
2023-03-17 06:11:24 +00:00
020a2d4670 !671 优化弹窗后导航栏偏移的问题
Merge pull request !671 from 也曾为你像超人/master
2023-03-17 06:08:47 +00:00
635d621b7b !670 修复页面切换时布局错乱的问题
Merge pull request !670 from 也曾为你像超人/N/A
2023-03-17 05:53:51 +00:00
4cbd56cbd7 !669 用户多角色,数据权限切面处理时可能出现权限抬升的情况。
Merge pull request !669 from 0慕容雪0/master
2023-03-17 05:50:26 +00:00
dcb9cb3d13 优化弹窗后导航栏偏移的问题 2023-03-11 14:42:02 +08:00
628bc94a9a update ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java.
Signed-off-by: 0慕容雪0 <ytu.mxh@163.com>
2023-03-11 04:31:55 +00:00
38ddefe2e6 修复页面切换时布局错乱的问题
Signed-off-by: 也曾为你像超人 <1553592282@qq.com>
2023-03-11 02:19:57 +00:00
7a090bda1e 关闭当前tab页应跳转最右侧tab页而非首页 2023-03-10 18:04:56 +08:00
4e8dd706d5 update ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java.
DataScopeAspect,数据权限切面处理类中,用户多角色情况下,若所有角色都不包含传递过来的权限字符,这个时候sqlString也会为空,会导致用户拥有全部数据权限,所以要限制一下, 可以根据conditions集合是否为空,来判断循环时所有角色是否都是在判断权限字符时continue了。
复现方法: 在使用@DataScope注解时permission定义了值,这个值所有角色不包含。

Signed-off-by: 0慕容雪0 <ytu.mxh@163.com>
2023-03-10 08:22:35 +00:00
641e550d7f 优化修改密码日志存储明文问题(I6ESO9) 2023-03-05 12:06:27 +08:00
81a01a1d9d 优化文件下载出现的异常(I6DLNU) 2023-02-28 13:33:12 +08:00
6523fe59a2 日志管理使用索引提升查询性能 2023-02-23 10:01:16 +08:00
90970eb9fe 修复isMatchedIp的参数判断产生空指针的问题 2023-02-22 10:29:28 +08:00
3402b69556 移除apache/commons-fileupload依赖 2023-02-21 18:06:28 +08:00
2c5e3e429f 升级druid到最新版本1.2.16 2023-02-21 18:05:22 +08:00
96ba768f50 优化代码 2023-02-21 18:02:00 +08:00
1268637e58 支持登录IP黑名单限制 2023-02-21 09:00:44 +08:00
61caa7966b 日志注解支持排除指定的请求参数 2023-02-20 16:25:40 +08:00
a5f95eddab 新增监控页面图标显示 2023-02-17 08:55:22 +08:00
ade70583e9 操作日志新增消耗时间属性 2023-02-16 10:22:39 +08:00
5676cf9ad4 修复匿名注解Anonymous空指针问题(I683DT) 2023-02-06 11:20:12 +08:00
c3d0cd5f8c update copyright 2023 2023-02-04 22:26:02 +08:00
eb96afee64 连接池Druid支持新的配置connectTimeout和socketTimeout 2023-02-04 22:25:49 +08:00
5873da87ae 屏蔽定时任务bean违规的字符 2023-02-04 22:25:33 +08:00
4f1933e2e4 !656 tagsView右键选择框,只存在首页时,不应该存在关闭左侧选项
Merge pull request !656 from Giovanni/master
2023-02-04 14:21:28 +00:00
9926f73cd0 tagsView右选框,首页不应该存在关闭左侧选项 2023-02-01 15:58:40 +08:00
492919d4af 升级element-ui到最新版本2.15.12 2023-01-19 12:05:01 +08:00
a7ff50e695 升级fastjson到最新版2.0.23 2023-01-19 12:04:11 +08:00
71e7e1d6dd 字符未使用下划线不进行驼峰式处理 2023-01-19 12:02:48 +08:00
5073f95ccd 添加新群号:108482800 2023-01-11 12:55:29 +08:00
c3a727b2fd 若依 3.8.5 2023-01-01 09:09:25 +08:00
19eaad0129 v3最新版本不需要render-after-expand 2022-12-23 16:08:23 +08:00
565cbb8c7a 升级pagehelper到最新版1.4.6 2022-12-13 19:42:39 +08:00
b2cf949956 修改参数键名时移除前缓存配置 2022-12-13 19:40:48 +08:00
3fec133c69 升级oshi到最新版本6.4.0 2022-12-13 19:39:36 +08:00
c9de6fcd82 优化代码 2022-12-13 19:39:19 +08:00
22ee2c2e94 删除fuse无效选项maxPatternLength 2022-12-08 10:21:48 +08:00
f11db02ff8 修复代码生成图片/文件/单选时选择必填无法校验问题(I64IO2) 2022-12-08 10:19:38 +08:00
d5f5c5d066 升级fastjson到最新版2.0.20 2022-12-08 10:13:39 +08:00
4536906b21 修复Vue3树形下拉不能默认选中(I64ESN) 2022-12-07 20:24:08 +08:00
9748e10339 升级druid到最新版本1.2.15 2022-12-07 11:41:44 +08:00
b08a6ce3f6 升级kaptcha到最新版2.3.3 2022-12-07 10:45:50 +08:00
3039b745a9 升级echarts到最新版本5.4.0 2022-12-07 10:12:15 +08:00
39298d803c 添加新群号:170801498 2022-12-03 12:50:26 +08:00
09bb3e15c6 定时任务违规的字符 2022-12-03 12:48:51 +08:00
3b8a68c4cf 升级oshi到最新版本6.3.2 2022-12-03 12:43:37 +08:00
9aae863ce3 优化弹窗内容过多展示不全问题(I645RU) 2022-12-03 12:42:50 +08:00
963247df43 !627 update ruoyi-ui/src/plugins/download.js.
Merge pull request !627 from Zeno/N/A
2022-12-01 08:21:29 +00:00
7d874e31cf update ruoyi-ui/src/plugins/download.js.
修复文件名包含特殊字符(+、-、*...)的文件无法下载问题

Signed-off-by: Zeno <15270656234@163.com>
2022-11-28 10:29:38 +00:00
1bb6342bcb 修复Log注解GET请求记录不到参数问题 2022-11-22 10:41:40 +08:00
27acbe5b73 修复某些特性的环境生成代码变乱码TXT文件问题 2022-11-22 09:23:17 +08:00
6474a17100 消除Vue3控制台出现的警告信息 2022-11-21 19:10:25 +08:00
142f6ad6a0 兼容Excel下拉框内容过多无法显示的问题(I5XB6I) 2022-11-21 12:20:36 +08:00
b4bdd4f306 开启TopNav没有子菜单隐藏侧边栏 2022-11-17 14:27:30 +08:00
74ba681fee 修复回显数据字典数组异常问题(I60UYQ) 2022-11-15 14:11:52 +08:00
8f2b3ac465 !611 修复调度日志点击多次数据不变化的问题
Merge pull request !611 from 也曾为你像超人/N/A
2022-11-15 06:09:02 +00:00
7eee3b9f02 升级druid到最新版本1.2.14 2022-11-14 11:17:04 +08:00
27e34c2f0a 忽略不必要的属性数据返回 2022-11-12 11:26:48 +08:00
836180fe0e 优化导出对象的子列表为空会出现[]问题 2022-11-11 11:31:27 +08:00
06fbda5324 修复sheet超出最大行数异常问题 2022-11-07 11:20:02 +08:00
bf313b17da !612 根据调度编号获取详细信息参数名改正
Merge pull request !612 from Rain/N/A
2022-10-31 05:51:48 +00:00
a8b9485a29 根据调度编号获取详细信息参数名改正
Signed-off-by: Rain <938448486@qq.com>
2022-10-31 05:51:08 +00:00
101e15d83f 修复调度日志点击多次数据不变化的问题
Signed-off-by: 也曾为你像超人 <1553592282@qq.com>
2022-10-31 05:45:21 +00:00
ebb9f15a75 新增返回警告消息提示 2022-10-30 12:02:06 +08:00
cd137bd9fc 升级fastjson到最新版2.0.16 2022-10-30 09:58:12 +08:00
07bde5f88a !610 pagehelper-boot更新1.4.5
Merge pull request !610 from abbfun/N/A
2022-10-30 01:57:27 +00:00
6fffa02acf pagehelper-boot更新1.4.5
Signed-off-by: abbfun <819589789@qq.com>
2022-10-29 14:45:55 +00:00
dc48f9858b 修复table中更多按钮切换主题色未生效修复问题 2022-10-28 20:59:42 +08:00
a6b2ac5dcd 升级oshi到最新版本6.3.0 2022-10-28 19:57:35 +08:00
c0685b7f7f !608 编辑头像时生成为透明png图片
Merge pull request !608 from BlossomWave/N/A
2022-10-28 11:48:22 +00:00
fb2d616c57 !609 优化代码
Merge pull request !609 from lihy2021/N/A
2022-10-28 11:44:27 +00:00
6ff6853082 !606 重置时取消部门选中
Merge pull request !606 from 也曾为你像超人/N/A
2022-10-28 11:42:26 +00:00
f8014ae969 !602 去除某些svg图标的fill="#bfbfbf"属性,避免菜单激活无法修改其填充颜色
Merge pull request !602 from 清溪先生/master
2022-10-28 11:39:13 +00:00
1de2b7a57e 优化代码 2022-10-27 01:25:39 +00:00
f01aa37394 update ruoyi-ui/src/views/system/user/profile/userAvatar.vue.
默认修改头像时如果上传的图片为png透明图片,生成的头像透明部分会变成黑色,修改了生成头像为png格式。可正常显示图片透明部分。

Signed-off-by: BlossomWave <316975215@qq.com>
2022-10-24 08:25:33 +00:00
4517dea98d 重置时取消部门选中
Signed-off-by: 也曾为你、像超人 <1553592282@qq.com>
2022-10-21 23:24:35 +00:00
e21396870f 修正菜单状态注释信息 2022-10-21 11:56:15 +08:00
b67f6a0fec !604 修复小屏幕上修改头像界面布局错位的问题
Merge pull request !604 from 也曾为你、像超人/master
2022-10-21 03:50:16 +00:00
42d8104505 !603 update ruoyi-ui/src/views/system/user/index.vue.
Merge pull request !603 from kknd97/N/A
2022-10-21 03:46:49 +00:00
f40a0eab23 !601 swagger-ui静态资源缓存
Merge pull request !601 from abbfun/N/A
2022-10-21 03:41:22 +00:00
b6153d1aef 修正选择按钮宽度 2022-10-21 11:21:59 +08:00
a2c585daa4 !605 升级fastjson到最新版2.0.15
Merge pull request !605 from Rain/N/A
2022-10-21 03:11:58 +00:00
5a60bf0b0a 升级fastjson到最新版2.0.15
Signed-off-by: Rain <938448486@qq.com>
2022-10-21 03:01:54 +00:00
9dcddc7876 修复小屏幕上修改头像界面布局错位的问题 2022-10-20 19:18:10 +08:00
b970185536 update ruoyi-ui/src/views/system/user/index.vue.
handleUpdate(row)方法中:this.form = response.data;语句
会导致【this.form.postIds = response.postIds;】和【this.form.roleIds = response.roleIds;】失效。
导致用户编辑对话框中,角色和部门多选框无法正常修改。
建议使用以下语句修改:
this.$set(this.form, "postIds", response.postIds);
this.$set(this.form, "roleIds", response.roleIds);

Signed-off-by: kknd97 <liujingwei@ln.chinamobile.com>
2022-10-20 06:27:02 +00:00
a64a029323 去除某些svg图标的fill="#bfbfbf"属性,避免菜单激活无法修改其填充颜色。
Signed-off-by: 清溪先生 <usfree2021@163.com>
2022-10-19 22:11:14 +08:00
78f4d1c85b swagger-ui静态资源缓存
Signed-off-by: abbfun <819589789@qq.com>
2022-10-19 09:07:35 +00:00
bf83fe568b 修复主题颜色在Drawer组件不会加载问题(I5VCF0) 2022-10-19 10:54:01 +08:00
6505432bf4 修复文件上传组件格式验证问题(I5V32H) 2022-10-12 19:33:58 +08:00
a32a931d24 升级core-js到最新版本3.25.3 2022-10-10 09:22:47 +08:00
4d72fb4289 R isError and isSuccess static 2022-10-10 09:22:37 +08:00
186c04d90a !595 解决导出时包含空子列表数据异常的问题
Merge pull request !595 from 也曾为你、像超人/N/A
2022-10-10 01:20:41 +00:00
c542b7ac32 解决导出时包含空子列表数据异常的问题
Signed-off-by: 也曾为你、像超人 <1553592282@qq.com>
2022-09-30 23:06:10 +00:00
3607e008a3 优化限流打印日志KEY(I5SQ09) 2022-09-28 19:32:55 +08:00
5b8edbf381 修复代码生成勾选属性无效问题 2022-09-28 19:31:52 +08:00
03f7bc0f3f 导入更新用户数据前校验数据权限 2022-09-28 19:26:16 +08:00
e95a1245c5 若依 3.8.4 2022-09-26 08:29:03 +08:00
d7441edae7 升级core-js到最新版本3.25.2 2022-09-22 11:13:49 +08:00
5053361a7f 升级fastjson到最新版2.0.14 2022-09-22 11:13:32 +08:00
acbdd6365a !581 优化日志操作中重置按钮时重复查询的问题
Merge pull request !581 from 也曾为你、像超人/N/A
2022-09-19 06:41:36 +00:00
e781f88eca 优化日志操作中重置按钮时重复查询的问题
Signed-off-by: 也曾为你、像超人 <1553592282@qq.com>
2022-09-19 06:30:47 +00:00
86d16d070d 通用下载方法新增config配置选项(I5PNXE) 2022-09-19 13:09:21 +08:00
854b405d89 修改用户登录账号重复验证 2022-09-18 11:26:03 +08:00
c3faa1a925 升级element-ui到最新版本2.15.10 2022-09-15 08:48:43 +08:00
37d6cc8146 优化代码生成同步后值NULL问题(I5OJDW) 2022-09-13 08:23:30 +08:00
2293822dc2 优化没有权限的用户编辑部门缺少数据(I5OF3O) 2022-09-12 17:22:23 +08:00
f5bec70911 R add isError and isSuccess method 2022-09-12 17:22:07 +08:00
87c53a049b !574 修复 issue#I5Q2ZO
Merge pull request !574 from 捏造的信仰/fix-I5Q2ZO
2022-09-09 01:45:12 +00:00
5bcd2825b6 !568 style重复赋值
Merge pull request !568 from coverme/N/A
2022-09-09 01:25:22 +00:00
036144b9ea 插入 SysOperLog 时,限制 operUrl 属性的长度
Signed-off-by: 捏造的信仰 <yiding.he@gmail.com>
2022-09-07 03:00:51 +00:00
42d602b7a8 添加新群号:160110482 2022-08-28 20:09:25 +08:00
d4b800036c style重复赋值
Signed-off-by: 胡亚飞 <huyafeily@aliyun.com>
2022-08-26 16:26:09 +00:00
2f380f0c41 !564 修复执行任务时,若方法入口在任务的父类,则无法执行的问题
Merge pull request !564 from 捏造的信仰/fix-issue-I5NNXI
2022-08-26 00:42:03 +00:00
39efed1710 修复多文件上传报错出现的异常问题 2022-08-24 11:18:52 +08:00
9a895763d5 优化页面内嵌iframe切换tab不刷新数据 2022-08-23 20:59:26 +08:00
7347cbaedf 优化页面内嵌iframe切换tab不刷新数据 2022-08-23 17:29:09 +08:00
a78b5b7b2a 修复执行任务时,若方法入口在任务的父类,则无法执行的问题
实际项目开发中,可能会为所有定时任务类建一个共同的父类,任务的执行入口在父类定义,以便管理。此时使用 `getDeclaredMethod()` 是无法从子类找到要执行的方法的,而是要换用 `getMethod()` 方法。

Signed-off-by: 捏造的信仰 <yiding.he@gmail.com>
2022-08-23 05:36:29 +00:00
99d1760b98 优化代码 2022-08-22 15:27:58 +08:00
4d71cfa90a !562 修复菜单管理已知问题
Merge pull request !562 from 稚屿/N/A
2022-08-22 06:20:18 +00:00
e02f692359 修复菜单管理已知问题
问题描述:在菜单管理下,类型为菜单或者按钮的条目下点击修改按钮。
情况1,如果是类型为菜单,第一次点击修改按钮正常,则第二次点击另一个条目后面的修改按钮时报错!
情况2,如果是类型为按钮,第一次点击修改时正常,当点击取消按钮关闭弹窗时,浏览器报错!

报错代码:this.resetForm("form")

修复方案:添加遗漏的表单 prop 属性

Signed-off-by: 稚屿 <1491182878@qq.com>
2022-08-22 06:19:08 +00:00
77ec8d1c9a 修复代码生成权限父编号错误(I5NATP) 2022-08-22 12:21:07 +08:00
66ce21ec29 升级fastjson到最新版2.0.12 2022-08-22 12:04:01 +08:00
27e0937235 支持多权限字符匹配角色数据权限 2022-08-22 12:03:51 +08:00
aadb7a41cb 优化Context信息,防止泄漏问题 2022-08-22 10:24:20 +08:00
9b3767a954 优化多角色数据权限匹配规则 2022-08-21 22:53:57 +08:00
851dc54b49 修复图片预览组件src属性为null值控制台报错问题(I5KBAS) 2022-08-15 12:07:24 +08:00
cf2c4e02c6 字典管理操作类型新增其他 2022-08-14 09:40:36 +08:00
88b5715eae 升级oshi到最新版本6.2.2 2022-08-12 12:17:48 +08:00
5b05d4a123 个人中心修改密码去除多余的user传递 2022-08-12 12:17:28 +08:00
893ef39401 防止用户个人信息修改部门 2022-08-12 12:16:30 +08:00
890ad682d7 优化修改资料头像被覆盖的问题(I5LK04) 2022-08-11 13:22:55 +08:00
b9f45057b8 操作日志记录支持排除敏感属性字段 2022-08-10 18:01:02 +08:00
89008c28df 升级fastjson到最新版2.0.11 2022-08-10 14:03:35 +08:00
af7d0a3409 优化导出对象的子列表判断条件 2022-08-10 13:59:57 +08:00
1cd2eef899 优化excel/scale属性导出单元格数值类型 2022-08-09 08:02:24 +08:00
8c7b93ec4f !547 fix: 账户解锁接口去掉多余的 /
Merge pull request !547 from iacker/N/A
2022-08-08 23:44:57 +00:00
3f7bf545b5 fix: 账户解锁接口去掉多余的 / 2022-08-08 13:55:08 +00:00
cbedec7ca6 登录日志新增解锁账户功能 2022-08-08 09:23:52 +08:00
fb1bac2114 新增删除Hash中的某条数据 2022-08-07 19:31:04 +08:00
d0f399a66a 优化xss过滤后格式出现的异常 2022-08-07 19:30:54 +08:00
e73dbd470a Excel注解支持导出对象的子列表方法 2022-08-07 18:33:04 +08:00
e0cd5381e2 数据逻辑删除不进行唯一验证 2022-08-03 16:48:55 +08:00
aee5d417ed 支持配置密码最大错误次数/锁定时间 2022-07-30 14:01:38 +08:00
250c5ba226 优化任务过期不执行调度 2022-07-29 20:28:59 +08:00
b91c848962 !538 优化表格上右侧工具条(搜索按钮显隐&右侧样式凸出)
Merge pull request !538 from abbfun/N/A
2022-07-29 12:28:38 +00:00
d4475d0e8d 优化表格上右侧工具条(搜索按钮显隐&右侧样式凸出)
无搜索条件时可通过search隐藏搜索按钮,工具条组右侧样式超出5px(相对于底部表格),其父节点gutter代码生成默认10,此处也默认10,使工具组样式左右一致
2022-07-27 09:05:31 +00:00
aff54ab5fe 自定义数据权限不排除重复 2022-07-26 20:02:17 +08:00
06177addf5 添加新群号:104748341 2022-07-25 18:45:51 +08:00
9f3b91fe57 !535 所有的覆写方法,必须加@Override注解
Merge pull request !535 from 天才的逻辑/master
2022-07-25 10:32:48 +00:00
80d25863db !536 防止主键字段名与'row'或'ids'一致导致报错的问题。
Merge pull request !536 from 张政/N/A
2022-07-25 10:32:42 +00:00
cf8a7f8678 防止主键字段名与'row'或'ids'一致导致报错的问题。 2022-07-23 08:29:52 +00:00
86fc709b03 升级pagehelper到最新版1.4.3 2022-07-22 14:54:38 +08:00
b8b58cb202 所有的覆写方法,必须加@Override注解 2022-07-22 10:57:00 +08:00
96970ff951 所有的覆写方法,必须加@Override注解 2022-07-22 10:35:31 +08:00
7cc9d17424 支持自定义隐藏Excel属性列 2022-07-21 13:52:05 +08:00
bc8b5f1079 优化布局设置使用el-drawer抽屉显示 2022-07-21 08:50:48 +08:00
5ec5e1a65d 优化字典数据使用store存取 2022-07-20 19:38:16 +08:00
1f0e742710 Excel注解支持backgroundColor属性设置背景色 2022-07-20 09:39:58 +08:00
28b9fbb4d2 优化多个相同角色数据导致权限SQL重复问题 2022-07-19 15:55:03 +08:00
2c79dc906e 升级oshi到最新版本6.2.1 2022-07-14 19:49:32 +08:00
1852017ecc 修改验证码开关变量名 2022-07-14 19:49:00 +08:00
424d11896a !531 去除生成验证码多余的逻辑判断
Merge pull request !531 from 稚屿/N/A
2022-07-14 09:28:46 +00:00
edefee46b2 去除生成验证码多余的逻辑判断 2022-07-13 01:00:44 +00:00
c7c3da2038 升级fastjson到最新版2.0.9 2022-07-12 18:07:05 +08:00
eb9f3d3772 修改验证码开关变量名 2022-07-12 18:04:49 +08:00
1a61790407 !523 修改错误的变量名
Merge pull request !523 from SG/N/A
2022-07-12 10:02:50 +00:00
fe98fba5b5 !527 调增数组格式声明,random随机转换修正
Merge pull request !527 from 靖少毅/jsy20220705
2022-07-12 10:01:56 +00:00
4eb8809a8a 调增数组格式声明,random随机转换修正 2022-07-05 16:43:55 +08:00
0a893d196e update 优化魔法值 2022-07-05 02:06:31 +00:00
SG
ec45cf04af 修改错误的变量名 2022-07-01 13:56:44 +00:00
252 changed files with 5183 additions and 2742 deletions

View File

@ -1,11 +1,11 @@
<p align="center"> <p align="center">
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png"> <img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
</p> </p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.8.3</h1> <h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.8.8</h1>
<h4 align="center">基于SpringBoot+Vue前后端分离的Java快速开发框架</h4> <h4 align="center">基于SpringBoot+Vue前后端分离的Java快速开发框架</h4>
<p align="center"> <p align="center">
<a href="https://gitee.com/y_project/RuoYi-Vue/stargazers"><img src="https://gitee.com/y_project/RuoYi-Vue/badge/star.svg?theme=dark"></a> <a href="https://gitee.com/y_project/RuoYi-Vue/stargazers"><img src="https://gitee.com/y_project/RuoYi-Vue/badge/star.svg?theme=dark"></a>
<a href="https://gitee.com/y_project/RuoYi-Vue"><img src="https://img.shields.io/badge/RuoYi-v3.8.3-brightgreen.svg"></a> <a href="https://gitee.com/y_project/RuoYi-Vue"><img src="https://img.shields.io/badge/RuoYi-v3.8.8-brightgreen.svg"></a>
<a href="https://gitee.com/y_project/RuoYi-Vue/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a> <a href="https://gitee.com/y_project/RuoYi-Vue/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
</p> </p>
@ -21,7 +21,6 @@
* 提供了技术栈([Vue3](https://v3.cn.vuejs.org) [Element Plus](https://element-plus.org/zh-CN) [Vite](https://cn.vitejs.dev))版本[RuoYi-Vue3](https://github.com/yangzongzhuan/RuoYi-Vue3),保持同步更新。 * 提供了技术栈([Vue3](https://v3.cn.vuejs.org) [Element Plus](https://element-plus.org/zh-CN) [Vite](https://cn.vitejs.dev))版本[RuoYi-Vue3](https://github.com/yangzongzhuan/RuoYi-Vue3),保持同步更新。
* 提供了单应用版本[RuoYi-Vue-fast](https://github.com/yangzongzhuan/RuoYi-Vue-fast)Oracle版本[RuoYi-Vue-Oracle](https://github.com/yangzongzhuan/RuoYi-Vue-Oracle),保持同步更新。 * 提供了单应用版本[RuoYi-Vue-fast](https://github.com/yangzongzhuan/RuoYi-Vue-fast)Oracle版本[RuoYi-Vue-Oracle](https://github.com/yangzongzhuan/RuoYi-Vue-Oracle),保持同步更新。
* 不分离版本,请移步[RuoYi](https://gitee.com/y_project/RuoYi),微服务版本,请移步[RuoYi-Cloud](https://gitee.com/y_project/RuoYi-Cloud) * 不分离版本,请移步[RuoYi](https://gitee.com/y_project/RuoYi),微服务版本,请移步[RuoYi-Cloud](https://gitee.com/y_project/RuoYi-Cloud)
* 特别鸣谢:[element](https://github.com/ElemeFE/element)[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)[eladmin-web](https://github.com/elunez/eladmin-web)。
* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)&nbsp;&nbsp; * 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)&nbsp;&nbsp;
* 阿里云优惠券:[点我领取](https://www.aliyun.com/minisite/goods?userCode=brki8iof&share_source=copy_link),腾讯云优惠券:[点我领取](https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console)&nbsp;&nbsp; * 阿里云优惠券:[点我领取](https://www.aliyun.com/minisite/goods?userCode=brki8iof&share_source=copy_link),腾讯云优惠券:[点我领取](https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console)&nbsp;&nbsp;
@ -94,4 +93,4 @@
## 若依前后端分离交流群 ## 若依前后端分离交流群
QQ群 [![加入QQ群](https://img.shields.io/badge/已满-937441-blue.svg)](https://jq.qq.com/?_wv=1027&k=5bVB1og) [![加入QQ群](https://img.shields.io/badge/已满-887144332-blue.svg)](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [![加入QQ群](https://img.shields.io/badge/已满-180251782-blue.svg)](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [![加入QQ群](https://img.shields.io/badge/已满-104180207-blue.svg)](https://jq.qq.com/?_wv=1027&k=51G72yr) [![加入QQ群](https://img.shields.io/badge/已满-186866453-blue.svg)](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [![加入QQ群](https://img.shields.io/badge/已满-201396349-blue.svg)](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [![加入QQ群](https://img.shields.io/badge/已满-101456076-blue.svg)](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [![加入QQ群](https://img.shields.io/badge/已满-101539465-blue.svg)](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [![加入QQ群](https://img.shields.io/badge/已满-264312783-blue.svg)](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) [![加入QQ群](https://img.shields.io/badge/已满-167385320-blue.svg)](https://jq.qq.com/?_wv=1027&k=SWCtLnMz) [![加入QQ群](https://img.shields.io/badge/139821253-blue.svg)](https://jq.qq.com/?_wv=1027&k=njiWDmkj) 点击按钮入群。 QQ群 [![加入QQ群](https://img.shields.io/badge/已满-937441-blue.svg)](https://jq.qq.com/?_wv=1027&k=5bVB1og) [![加入QQ群](https://img.shields.io/badge/已满-887144332-blue.svg)](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [![加入QQ群](https://img.shields.io/badge/已满-180251782-blue.svg)](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [![加入QQ群](https://img.shields.io/badge/已满-104180207-blue.svg)](https://jq.qq.com/?_wv=1027&k=51G72yr) [![加入QQ群](https://img.shields.io/badge/已满-186866453-blue.svg)](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [![加入QQ群](https://img.shields.io/badge/已满-201396349-blue.svg)](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [![加入QQ群](https://img.shields.io/badge/已满-101456076-blue.svg)](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [![加入QQ群](https://img.shields.io/badge/已满-101539465-blue.svg)](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [![加入QQ群](https://img.shields.io/badge/已满-264312783-blue.svg)](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) [![加入QQ群](https://img.shields.io/badge/已满-167385320-blue.svg)](https://jq.qq.com/?_wv=1027&k=SWCtLnMz) [![加入QQ群](https://img.shields.io/badge/已满-104748341-blue.svg)](https://jq.qq.com/?_wv=1027&k=96Dkdq0k) [![加入QQ群](https://img.shields.io/badge/已满-160110482-blue.svg)](https://jq.qq.com/?_wv=1027&k=0fsNiYZt) [![加入QQ群](https://img.shields.io/badge/已满-170801498-blue.svg)](https://jq.qq.com/?_wv=1027&k=7xw4xUG1) [![加入QQ群](https://img.shields.io/badge/已满-108482800-blue.svg)](https://jq.qq.com/?_wv=1027&k=eCx8eyoJ) [![加入QQ群](https://img.shields.io/badge/已满-101046199-blue.svg)](https://jq.qq.com/?_wv=1027&k=SpyH2875) [![加入QQ群](https://img.shields.io/badge/已满-136919097-blue.svg)](https://jq.qq.com/?_wv=1027&k=tKEt51dz) [![加入QQ群](https://img.shields.io/badge/已满-143961921-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0vBbSb0ztbBgVtn3kJS-Q4HUNYwip89G&authKey=8irq5PhutrZmWIvsUsklBxhj57l%2F1nOZqjzigkXZVoZE451GG4JHPOqW7AW6cf0T&noverify=0&group_code=143961921) [![加入QQ群](https://img.shields.io/badge/已满-174951577-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=ZFAPAbp09S2ltvwrJzp7wGlbopsc0rwi&authKey=HB2cxpxP2yspk%2Bo3WKTBfktRCccVkU26cgi5B16u0KcAYrVu7sBaE7XSEqmMdFQp&noverify=0&group_code=174951577) [![加入QQ群](https://img.shields.io/badge/已满-161281055-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Fn2aF5IHpwsy8j6VlalNJK6qbwFLFHat&authKey=uyIT%2B97x2AXj3odyXpsSpVaPMC%2Bidw0LxG5MAtEqlrcBcWJUA%2FeS43rsF1Tg7IRJ&noverify=0&group_code=161281055) [![加入QQ群](https://img.shields.io/badge/已满-138988063-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=XIzkm_mV2xTsUtFxo63bmicYoDBA6Ifm&authKey=dDW%2F4qsmw3x9govoZY9w%2FoWAoC4wbHqGal%2BbqLzoS6VBarU8EBptIgPKN%2FviyC8j&noverify=0&group_code=138988063) [![加入QQ群](https://img.shields.io/badge/151450850-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=DkugnCg68PevlycJSKSwjhFqfIgrWWwR&authKey=pR1Pa5lPIeGF%2FFtIk6d%2FGB5qFi0EdvyErtpQXULzo03zbhopBHLWcuqdpwY241R%2F&noverify=0&group_code=151450850) 点击按钮入群。

71
pom.xml
View File

@ -6,43 +6,60 @@
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>3.8.3</version> <version>3.8.8</version>
<name>ruoyi</name> <name>ruoyi</name>
<url>http://www.ruoyi.vip</url> <url>http://www.ruoyi.vip</url>
<description>若依管理系统</description> <description>若依管理系统</description>
<properties> <properties>
<ruoyi.version>3.8.3</ruoyi.version> <ruoyi.version>3.8.8</ruoyi.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version> <java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version> <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<druid.version>1.2.11</druid.version> <spring-framework.version>5.3.33</spring-framework.version>
<spring-security.version>5.7.12</spring-security.version>
<druid.version>1.2.23</druid.version>
<bitwalker.version>1.21</bitwalker.version> <bitwalker.version>1.21</bitwalker.version>
<swagger.version>3.0.0</swagger.version> <swagger.version>3.0.0</swagger.version>
<kaptcha.version>2.3.2</kaptcha.version> <kaptcha.version>2.3.3</kaptcha.version>
<mybatis-spring-boot.version>2.2.2</mybatis-spring-boot.version> <pagehelper.boot.version>1.4.7</pagehelper.boot.version>
<pagehelper.boot.version>1.4.1</pagehelper.boot.version> <fastjson.version>2.0.43</fastjson.version>
<fastjson.version>2.0.8</fastjson.version> <oshi.version>6.6.1</oshi.version>
<oshi.version>6.1.6</oshi.version> <commons.io.version>2.13.0</commons.io.version>
<commons.io.version>2.11.0</commons.io.version>
<commons.fileupload.version>1.4</commons.fileupload.version>
<commons.collections.version>3.2.2</commons.collections.version>
<poi.version>4.1.2</poi.version> <poi.version>4.1.2</poi.version>
<velocity.version>2.3</velocity.version> <velocity.version>2.3</velocity.version>
<jwt.version>0.9.1</jwt.version> <jwt.version>0.9.1</jwt.version>
</properties> </properties>
<!-- 依赖声明 --> <!-- 依赖声明 -->
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>
<!-- SpringFramework的依赖配置-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>${spring-framework.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- SpringSecurity的依赖配置-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-bom</artifactId>
<version>${spring-security.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- SpringBoot的依赖配置--> <!-- SpringBoot的依赖配置-->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId> <artifactId>spring-boot-dependencies</artifactId>
<version>2.5.14</version> <version>2.5.15</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
@ -61,13 +78,6 @@
<version>${bitwalker.version}</version> <version>${bitwalker.version}</version>
</dependency> </dependency>
<!-- SpringBoot集成mybatis框架 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot.version}</version>
</dependency>
<!-- pagehelper 分页插件 --> <!-- pagehelper 分页插件 -->
<dependency> <dependency>
<groupId>com.github.pagehelper</groupId> <groupId>com.github.pagehelper</groupId>
@ -102,13 +112,6 @@
<version>${commons.io.version}</version> <version>${commons.io.version}</version>
</dependency> </dependency>
<!-- 文件上传工具类 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${commons.fileupload.version}</version>
</dependency>
<!-- excel工具 --> <!-- excel工具 -->
<dependency> <dependency>
<groupId>org.apache.poi</groupId> <groupId>org.apache.poi</groupId>
@ -123,13 +126,6 @@
<version>${velocity.version}</version> <version>${velocity.version}</version>
</dependency> </dependency>
<!-- collections工具类 -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>${commons.collections.version}</version>
</dependency>
<!-- 阿里JSON解析器 --> <!-- 阿里JSON解析器 -->
<dependency> <dependency>
<groupId>com.alibaba.fastjson2</groupId> <groupId>com.alibaba.fastjson2</groupId>
@ -146,7 +142,7 @@
<!-- 验证码 --> <!-- 验证码 -->
<dependency> <dependency>
<groupId>com.github.penggle</groupId> <groupId>pro.fessional</groupId>
<artifactId>kaptcha</artifactId> <artifactId>kaptcha</artifactId>
<version>${kaptcha.version}</version> <version>${kaptcha.version}</version>
</dependency> </dependency>
@ -199,11 +195,6 @@
</modules> </modules>
<packaging>pom</packaging> <packaging>pom</packaging>
<dependencies>
</dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>3.8.3</version> <version>3.8.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging> <packaging>jar</packaging>
@ -68,7 +68,7 @@
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.1.RELEASE</version> <version>2.5.15</version>
<configuration> <configuration>
<fork>true</fork> <!-- 如果没有该配置devtools不会生效 --> <fork>true</fork> <!-- 如果没有该配置devtools不会生效 -->
</configuration> </configuration>

View File

@ -46,9 +46,9 @@ public class CaptchaController
public AjaxResult getCode(HttpServletResponse response) throws IOException public AjaxResult getCode(HttpServletResponse response) throws IOException
{ {
AjaxResult ajax = AjaxResult.success(); AjaxResult ajax = AjaxResult.success();
boolean captchaOnOff = configService.selectCaptchaOnOff(); boolean captchaEnabled = configService.selectCaptchaEnabled();
ajax.put("captchaOnOff", captchaOnOff); ajax.put("captchaEnabled", captchaEnabled);
if (!captchaOnOff) if (!captchaEnabled)
{ {
return ajax; return ajax;
} }

View File

@ -7,6 +7,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.TreeSet;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
@ -41,6 +42,7 @@ public class CacheController
caches.add(new SysCache(CacheConstants.CAPTCHA_CODE_KEY, "验证码")); caches.add(new SysCache(CacheConstants.CAPTCHA_CODE_KEY, "验证码"));
caches.add(new SysCache(CacheConstants.REPEAT_SUBMIT_KEY, "防重提交")); caches.add(new SysCache(CacheConstants.REPEAT_SUBMIT_KEY, "防重提交"));
caches.add(new SysCache(CacheConstants.RATE_LIMIT_KEY, "限流处理")); caches.add(new SysCache(CacheConstants.RATE_LIMIT_KEY, "限流处理"));
caches.add(new SysCache(CacheConstants.PWD_ERR_CNT_KEY, "密码错误次数"));
} }
@PreAuthorize("@ss.hasPermi('monitor:cache:list')") @PreAuthorize("@ss.hasPermi('monitor:cache:list')")
@ -78,8 +80,8 @@ public class CacheController
@GetMapping("/getKeys/{cacheName}") @GetMapping("/getKeys/{cacheName}")
public AjaxResult getCacheKeys(@PathVariable String cacheName) public AjaxResult getCacheKeys(@PathVariable String cacheName)
{ {
Set<String> cacheKyes = redisTemplate.keys(cacheName + "*"); Set<String> cacheKeys = redisTemplate.keys(cacheName + "*");
return AjaxResult.success(cacheKyes); return AjaxResult.success(new TreeSet<>(cacheKeys));
} }
@PreAuthorize("@ss.hasPermi('monitor:cache:list')") @PreAuthorize("@ss.hasPermi('monitor:cache:list')")

View File

@ -16,6 +16,7 @@ import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.service.SysPasswordService;
import com.ruoyi.system.domain.SysLogininfor; import com.ruoyi.system.domain.SysLogininfor;
import com.ruoyi.system.service.ISysLogininforService; import com.ruoyi.system.service.ISysLogininforService;
@ -31,6 +32,9 @@ public class SysLogininforController extends BaseController
@Autowired @Autowired
private ISysLogininforService logininforService; private ISysLogininforService logininforService;
@Autowired
private SysPasswordService passwordService;
@PreAuthorize("@ss.hasPermi('monitor:logininfor:list')") @PreAuthorize("@ss.hasPermi('monitor:logininfor:list')")
@GetMapping("/list") @GetMapping("/list")
public TableDataInfo list(SysLogininfor logininfor) public TableDataInfo list(SysLogininfor logininfor)
@ -64,6 +68,15 @@ public class SysLogininforController extends BaseController
public AjaxResult clean() public AjaxResult clean()
{ {
logininforService.cleanLogininfor(); logininforService.cleanLogininfor();
return AjaxResult.success(); return success();
}
@PreAuthorize("@ss.hasPermi('monitor:logininfor:unlock')")
@Log(title = "账户解锁", businessType = BusinessType.OTHER)
@GetMapping("/unlock/{userName}")
public AjaxResult unlock(@PathVariable("userName") String userName)
{
passwordService.clearLoginRecordCache(userName);
return success();
} }
} }

View File

@ -64,6 +64,6 @@ public class SysOperlogController extends BaseController
public AjaxResult clean() public AjaxResult clean()
{ {
operLogService.cleanOperLog(); operLogService.cleanOperLog();
return AjaxResult.success(); return success();
} }
} }

View File

@ -49,24 +49,15 @@ public class SysUserOnlineController extends BaseController
LoginUser user = redisCache.getCacheObject(key); LoginUser user = redisCache.getCacheObject(key);
if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName))
{ {
if (StringUtils.equals(ipaddr, user.getIpaddr()) && StringUtils.equals(userName, user.getUsername())) userOnlineList.add(userOnlineService.selectOnlineByInfo(ipaddr, userName, user));
{
userOnlineList.add(userOnlineService.selectOnlineByInfo(ipaddr, userName, user));
}
} }
else if (StringUtils.isNotEmpty(ipaddr)) else if (StringUtils.isNotEmpty(ipaddr))
{ {
if (StringUtils.equals(ipaddr, user.getIpaddr())) userOnlineList.add(userOnlineService.selectOnlineByIpaddr(ipaddr, user));
{
userOnlineList.add(userOnlineService.selectOnlineByIpaddr(ipaddr, user));
}
} }
else if (StringUtils.isNotEmpty(userName) && StringUtils.isNotNull(user.getUser())) else if (StringUtils.isNotEmpty(userName) && StringUtils.isNotNull(user.getUser()))
{ {
if (StringUtils.equals(userName, user.getUsername())) userOnlineList.add(userOnlineService.selectOnlineByUserName(userName, user));
{
userOnlineList.add(userOnlineService.selectOnlineByUserName(userName, user));
}
} }
else else
{ {
@ -87,6 +78,6 @@ public class SysUserOnlineController extends BaseController
public AjaxResult forceLogout(@PathVariable String tokenId) public AjaxResult forceLogout(@PathVariable String tokenId)
{ {
redisCache.deleteObject(CacheConstants.LOGIN_TOKEN_KEY + tokenId); redisCache.deleteObject(CacheConstants.LOGIN_TOKEN_KEY + tokenId);
return AjaxResult.success(); return success();
} }
} }

View File

@ -14,7 +14,6 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
@ -64,7 +63,7 @@ public class SysConfigController extends BaseController
@GetMapping(value = "/{configId}") @GetMapping(value = "/{configId}")
public AjaxResult getInfo(@PathVariable Long configId) public AjaxResult getInfo(@PathVariable Long configId)
{ {
return AjaxResult.success(configService.selectConfigById(configId)); return success(configService.selectConfigById(configId));
} }
/** /**
@ -73,7 +72,7 @@ public class SysConfigController extends BaseController
@GetMapping(value = "/configKey/{configKey}") @GetMapping(value = "/configKey/{configKey}")
public AjaxResult getConfigKey(@PathVariable String configKey) public AjaxResult getConfigKey(@PathVariable String configKey)
{ {
return AjaxResult.success(configService.selectConfigByKey(configKey)); return success(configService.selectConfigByKey(configKey));
} }
/** /**
@ -84,9 +83,9 @@ public class SysConfigController extends BaseController
@PostMapping @PostMapping
public AjaxResult add(@Validated @RequestBody SysConfig config) public AjaxResult add(@Validated @RequestBody SysConfig config)
{ {
if (UserConstants.NOT_UNIQUE.equals(configService.checkConfigKeyUnique(config))) if (!configService.checkConfigKeyUnique(config))
{ {
return AjaxResult.error("新增参数'" + config.getConfigName() + "'失败,参数键名已存在"); return error("新增参数'" + config.getConfigName() + "'失败,参数键名已存在");
} }
config.setCreateBy(getUsername()); config.setCreateBy(getUsername());
return toAjax(configService.insertConfig(config)); return toAjax(configService.insertConfig(config));
@ -100,9 +99,9 @@ public class SysConfigController extends BaseController
@PutMapping @PutMapping
public AjaxResult edit(@Validated @RequestBody SysConfig config) public AjaxResult edit(@Validated @RequestBody SysConfig config)
{ {
if (UserConstants.NOT_UNIQUE.equals(configService.checkConfigKeyUnique(config))) if (!configService.checkConfigKeyUnique(config))
{ {
return AjaxResult.error("修改参数'" + config.getConfigName() + "'失败,参数键名已存在"); return error("修改参数'" + config.getConfigName() + "'失败,参数键名已存在");
} }
config.setUpdateBy(getUsername()); config.setUpdateBy(getUsername());
return toAjax(configService.updateConfig(config)); return toAjax(configService.updateConfig(config));
@ -129,6 +128,6 @@ public class SysConfigController extends BaseController
public AjaxResult refreshCache() public AjaxResult refreshCache()
{ {
configService.resetConfigCache(); configService.resetConfigCache();
return AjaxResult.success(); return success();
} }
} }

View File

@ -1,6 +1,5 @@
package com.ruoyi.web.controller.system; package com.ruoyi.web.controller.system;
import java.util.Iterator;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -43,7 +42,7 @@ public class SysDeptController extends BaseController
public AjaxResult list(SysDept dept) public AjaxResult list(SysDept dept)
{ {
List<SysDept> depts = deptService.selectDeptList(dept); List<SysDept> depts = deptService.selectDeptList(dept);
return AjaxResult.success(depts); return success(depts);
} }
/** /**
@ -54,17 +53,8 @@ public class SysDeptController extends BaseController
public AjaxResult excludeChild(@PathVariable(value = "deptId", required = false) Long deptId) public AjaxResult excludeChild(@PathVariable(value = "deptId", required = false) Long deptId)
{ {
List<SysDept> depts = deptService.selectDeptList(new SysDept()); List<SysDept> depts = deptService.selectDeptList(new SysDept());
Iterator<SysDept> it = depts.iterator(); depts.removeIf(d -> d.getDeptId().intValue() == deptId || ArrayUtils.contains(StringUtils.split(d.getAncestors(), ","), deptId + ""));
while (it.hasNext()) return success(depts);
{
SysDept d = (SysDept) it.next();
if (d.getDeptId().intValue() == deptId
|| ArrayUtils.contains(StringUtils.split(d.getAncestors(), ","), deptId + ""))
{
it.remove();
}
}
return AjaxResult.success(depts);
} }
/** /**
@ -75,30 +65,7 @@ public class SysDeptController extends BaseController
public AjaxResult getInfo(@PathVariable Long deptId) public AjaxResult getInfo(@PathVariable Long deptId)
{ {
deptService.checkDeptDataScope(deptId); deptService.checkDeptDataScope(deptId);
return AjaxResult.success(deptService.selectDeptById(deptId)); return success(deptService.selectDeptById(deptId));
}
/**
* 获取部门下拉树列表
*/
@GetMapping("/treeselect")
public AjaxResult treeselect(SysDept dept)
{
List<SysDept> depts = deptService.selectDeptList(dept);
return AjaxResult.success(deptService.buildDeptTreeSelect(depts));
}
/**
* 加载对应角色部门列表树
*/
@GetMapping(value = "/roleDeptTreeselect/{roleId}")
public AjaxResult roleDeptTreeselect(@PathVariable("roleId") Long roleId)
{
List<SysDept> depts = deptService.selectDeptList(new SysDept());
AjaxResult ajax = AjaxResult.success();
ajax.put("checkedKeys", deptService.selectDeptListByRoleId(roleId));
ajax.put("depts", deptService.buildDeptTreeSelect(depts));
return ajax;
} }
/** /**
@ -109,9 +76,9 @@ public class SysDeptController extends BaseController
@PostMapping @PostMapping
public AjaxResult add(@Validated @RequestBody SysDept dept) public AjaxResult add(@Validated @RequestBody SysDept dept)
{ {
if (UserConstants.NOT_UNIQUE.equals(deptService.checkDeptNameUnique(dept))) if (!deptService.checkDeptNameUnique(dept))
{ {
return AjaxResult.error("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在"); return error("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在");
} }
dept.setCreateBy(getUsername()); dept.setCreateBy(getUsername());
return toAjax(deptService.insertDept(dept)); return toAjax(deptService.insertDept(dept));
@ -127,17 +94,17 @@ public class SysDeptController extends BaseController
{ {
Long deptId = dept.getDeptId(); Long deptId = dept.getDeptId();
deptService.checkDeptDataScope(deptId); deptService.checkDeptDataScope(deptId);
if (UserConstants.NOT_UNIQUE.equals(deptService.checkDeptNameUnique(dept))) if (!deptService.checkDeptNameUnique(dept))
{ {
return AjaxResult.error("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在"); return error("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在");
} }
else if (dept.getParentId().equals(deptId)) else if (dept.getParentId().equals(deptId))
{ {
return AjaxResult.error("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己"); return error("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己");
} }
else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus()) && deptService.selectNormalChildrenDeptById(deptId) > 0) else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus()) && deptService.selectNormalChildrenDeptById(deptId) > 0)
{ {
return AjaxResult.error("该部门包含未停用的子部门!"); return error("该部门包含未停用的子部门!");
} }
dept.setUpdateBy(getUsername()); dept.setUpdateBy(getUsername());
return toAjax(deptService.updateDept(dept)); return toAjax(deptService.updateDept(dept));
@ -153,11 +120,11 @@ public class SysDeptController extends BaseController
{ {
if (deptService.hasChildByDeptId(deptId)) if (deptService.hasChildByDeptId(deptId))
{ {
return AjaxResult.error("存在下级部门,不允许删除"); return warn("存在下级部门,不允许删除");
} }
if (deptService.checkDeptExistUser(deptId)) if (deptService.checkDeptExistUser(deptId))
{ {
return AjaxResult.error("部门存在用户,不允许删除"); return warn("部门存在用户,不允许删除");
} }
deptService.checkDeptDataScope(deptId); deptService.checkDeptDataScope(deptId);
return toAjax(deptService.deleteDeptById(deptId)); return toAjax(deptService.deleteDeptById(deptId));

View File

@ -66,7 +66,7 @@ public class SysDictDataController extends BaseController
@GetMapping(value = "/{dictCode}") @GetMapping(value = "/{dictCode}")
public AjaxResult getInfo(@PathVariable Long dictCode) public AjaxResult getInfo(@PathVariable Long dictCode)
{ {
return AjaxResult.success(dictDataService.selectDictDataById(dictCode)); return success(dictDataService.selectDictDataById(dictCode));
} }
/** /**
@ -80,7 +80,7 @@ public class SysDictDataController extends BaseController
{ {
data = new ArrayList<SysDictData>(); data = new ArrayList<SysDictData>();
} }
return AjaxResult.success(data); return success(data);
} }
/** /**

View File

@ -14,7 +14,6 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysDictType; import com.ruoyi.common.core.domain.entity.SysDictType;
@ -61,7 +60,7 @@ public class SysDictTypeController extends BaseController
@GetMapping(value = "/{dictId}") @GetMapping(value = "/{dictId}")
public AjaxResult getInfo(@PathVariable Long dictId) public AjaxResult getInfo(@PathVariable Long dictId)
{ {
return AjaxResult.success(dictTypeService.selectDictTypeById(dictId)); return success(dictTypeService.selectDictTypeById(dictId));
} }
/** /**
@ -72,9 +71,9 @@ public class SysDictTypeController extends BaseController
@PostMapping @PostMapping
public AjaxResult add(@Validated @RequestBody SysDictType dict) public AjaxResult add(@Validated @RequestBody SysDictType dict)
{ {
if (UserConstants.NOT_UNIQUE.equals(dictTypeService.checkDictTypeUnique(dict))) if (!dictTypeService.checkDictTypeUnique(dict))
{ {
return AjaxResult.error("新增字典'" + dict.getDictName() + "'失败,字典类型已存在"); return error("新增字典'" + dict.getDictName() + "'失败,字典类型已存在");
} }
dict.setCreateBy(getUsername()); dict.setCreateBy(getUsername());
return toAjax(dictTypeService.insertDictType(dict)); return toAjax(dictTypeService.insertDictType(dict));
@ -88,9 +87,9 @@ public class SysDictTypeController extends BaseController
@PutMapping @PutMapping
public AjaxResult edit(@Validated @RequestBody SysDictType dict) public AjaxResult edit(@Validated @RequestBody SysDictType dict)
{ {
if (UserConstants.NOT_UNIQUE.equals(dictTypeService.checkDictTypeUnique(dict))) if (!dictTypeService.checkDictTypeUnique(dict))
{ {
return AjaxResult.error("修改字典'" + dict.getDictName() + "'失败,字典类型已存在"); return error("修改字典'" + dict.getDictName() + "'失败,字典类型已存在");
} }
dict.setUpdateBy(getUsername()); dict.setUpdateBy(getUsername());
return toAjax(dictTypeService.updateDictType(dict)); return toAjax(dictTypeService.updateDictType(dict));
@ -117,7 +116,7 @@ public class SysDictTypeController extends BaseController
public AjaxResult refreshCache() public AjaxResult refreshCache()
{ {
dictTypeService.resetDictCache(); dictTypeService.resetDictCache();
return AjaxResult.success(); return success();
} }
/** /**
@ -127,6 +126,6 @@ public class SysDictTypeController extends BaseController
public AjaxResult optionselect() public AjaxResult optionselect()
{ {
List<SysDictType> dictTypes = dictTypeService.selectDictTypeAll(); List<SysDictType> dictTypes = dictTypeService.selectDictTypeAll();
return AjaxResult.success(dictTypes); return success(dictTypes);
} }
} }

View File

@ -41,7 +41,7 @@ public class SysMenuController extends BaseController
public AjaxResult list(SysMenu menu) public AjaxResult list(SysMenu menu)
{ {
List<SysMenu> menus = menuService.selectMenuList(menu, getUserId()); List<SysMenu> menus = menuService.selectMenuList(menu, getUserId());
return AjaxResult.success(menus); return success(menus);
} }
/** /**
@ -51,7 +51,7 @@ public class SysMenuController extends BaseController
@GetMapping(value = "/{menuId}") @GetMapping(value = "/{menuId}")
public AjaxResult getInfo(@PathVariable Long menuId) public AjaxResult getInfo(@PathVariable Long menuId)
{ {
return AjaxResult.success(menuService.selectMenuById(menuId)); return success(menuService.selectMenuById(menuId));
} }
/** /**
@ -61,7 +61,7 @@ public class SysMenuController extends BaseController
public AjaxResult treeselect(SysMenu menu) public AjaxResult treeselect(SysMenu menu)
{ {
List<SysMenu> menus = menuService.selectMenuList(menu, getUserId()); List<SysMenu> menus = menuService.selectMenuList(menu, getUserId());
return AjaxResult.success(menuService.buildMenuTreeSelect(menus)); return success(menuService.buildMenuTreeSelect(menus));
} }
/** /**
@ -85,13 +85,13 @@ public class SysMenuController extends BaseController
@PostMapping @PostMapping
public AjaxResult add(@Validated @RequestBody SysMenu menu) public AjaxResult add(@Validated @RequestBody SysMenu menu)
{ {
if (UserConstants.NOT_UNIQUE.equals(menuService.checkMenuNameUnique(menu))) if (!menuService.checkMenuNameUnique(menu))
{ {
return AjaxResult.error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); return error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
} }
else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath()))
{ {
return AjaxResult.error("新增菜单'" + menu.getMenuName() + "'失败地址必须以http(s)://开头"); return error("新增菜单'" + menu.getMenuName() + "'失败地址必须以http(s)://开头");
} }
menu.setCreateBy(getUsername()); menu.setCreateBy(getUsername());
return toAjax(menuService.insertMenu(menu)); return toAjax(menuService.insertMenu(menu));
@ -105,17 +105,17 @@ public class SysMenuController extends BaseController
@PutMapping @PutMapping
public AjaxResult edit(@Validated @RequestBody SysMenu menu) public AjaxResult edit(@Validated @RequestBody SysMenu menu)
{ {
if (UserConstants.NOT_UNIQUE.equals(menuService.checkMenuNameUnique(menu))) if (!menuService.checkMenuNameUnique(menu))
{ {
return AjaxResult.error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); return error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
} }
else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath()))
{ {
return AjaxResult.error("修改菜单'" + menu.getMenuName() + "'失败地址必须以http(s)://开头"); return error("修改菜单'" + menu.getMenuName() + "'失败地址必须以http(s)://开头");
} }
else if (menu.getMenuId().equals(menu.getParentId())) else if (menu.getMenuId().equals(menu.getParentId()))
{ {
return AjaxResult.error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己"); return error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己");
} }
menu.setUpdateBy(getUsername()); menu.setUpdateBy(getUsername());
return toAjax(menuService.updateMenu(menu)); return toAjax(menuService.updateMenu(menu));
@ -131,11 +131,11 @@ public class SysMenuController extends BaseController
{ {
if (menuService.hasChildByMenuId(menuId)) if (menuService.hasChildByMenuId(menuId))
{ {
return AjaxResult.error("存在子菜单,不允许删除"); return warn("存在子菜单,不允许删除");
} }
if (menuService.checkMenuExistRole(menuId)) if (menuService.checkMenuExistRole(menuId))
{ {
return AjaxResult.error("菜单已分配,不允许删除"); return warn("菜单已分配,不允许删除");
} }
return toAjax(menuService.deleteMenuById(menuId)); return toAjax(menuService.deleteMenuById(menuId));
} }

View File

@ -51,7 +51,7 @@ public class SysNoticeController extends BaseController
@GetMapping(value = "/{noticeId}") @GetMapping(value = "/{noticeId}")
public AjaxResult getInfo(@PathVariable Long noticeId) public AjaxResult getInfo(@PathVariable Long noticeId)
{ {
return AjaxResult.success(noticeService.selectNoticeById(noticeId)); return success(noticeService.selectNoticeById(noticeId));
} }
/** /**

View File

@ -14,7 +14,6 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
@ -64,7 +63,7 @@ public class SysPostController extends BaseController
@GetMapping(value = "/{postId}") @GetMapping(value = "/{postId}")
public AjaxResult getInfo(@PathVariable Long postId) public AjaxResult getInfo(@PathVariable Long postId)
{ {
return AjaxResult.success(postService.selectPostById(postId)); return success(postService.selectPostById(postId));
} }
/** /**
@ -75,13 +74,13 @@ public class SysPostController extends BaseController
@PostMapping @PostMapping
public AjaxResult add(@Validated @RequestBody SysPost post) public AjaxResult add(@Validated @RequestBody SysPost post)
{ {
if (UserConstants.NOT_UNIQUE.equals(postService.checkPostNameUnique(post))) if (!postService.checkPostNameUnique(post))
{ {
return AjaxResult.error("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在"); return error("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在");
} }
else if (UserConstants.NOT_UNIQUE.equals(postService.checkPostCodeUnique(post))) else if (!postService.checkPostCodeUnique(post))
{ {
return AjaxResult.error("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在"); return error("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在");
} }
post.setCreateBy(getUsername()); post.setCreateBy(getUsername());
return toAjax(postService.insertPost(post)); return toAjax(postService.insertPost(post));
@ -95,13 +94,13 @@ public class SysPostController extends BaseController
@PutMapping @PutMapping
public AjaxResult edit(@Validated @RequestBody SysPost post) public AjaxResult edit(@Validated @RequestBody SysPost post)
{ {
if (UserConstants.NOT_UNIQUE.equals(postService.checkPostNameUnique(post))) if (!postService.checkPostNameUnique(post))
{ {
return AjaxResult.error("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在"); return error("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在");
} }
else if (UserConstants.NOT_UNIQUE.equals(postService.checkPostCodeUnique(post))) else if (!postService.checkPostCodeUnique(post))
{ {
return AjaxResult.error("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在"); return error("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在");
} }
post.setUpdateBy(getUsername()); post.setUpdateBy(getUsername());
return toAjax(postService.updatePost(post)); return toAjax(postService.updatePost(post));
@ -125,6 +124,6 @@ public class SysPostController extends BaseController
public AjaxResult optionselect() public AjaxResult optionselect()
{ {
List<SysPost> posts = postService.selectPostAll(); List<SysPost> posts = postService.selectPostAll();
return AjaxResult.success(posts); return success(posts);
} }
} }

View File

@ -11,7 +11,6 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
@ -61,31 +60,26 @@ public class SysProfileController extends BaseController
public AjaxResult updateProfile(@RequestBody SysUser user) public AjaxResult updateProfile(@RequestBody SysUser user)
{ {
LoginUser loginUser = getLoginUser(); LoginUser loginUser = getLoginUser();
SysUser sysUser = loginUser.getUser(); SysUser currentUser = loginUser.getUser();
user.setUserName(sysUser.getUserName()); currentUser.setNickName(user.getNickName());
if (StringUtils.isNotEmpty(user.getPhonenumber()) currentUser.setEmail(user.getEmail());
&& UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) currentUser.setPhonenumber(user.getPhonenumber());
currentUser.setSex(user.getSex());
if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(currentUser))
{ {
return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); return error("修改用户'" + loginUser.getUsername() + "'失败,手机号码已存在");
} }
if (StringUtils.isNotEmpty(user.getEmail()) if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(currentUser))
&& UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user)))
{ {
return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); return error("修改用户'" + loginUser.getUsername() + "'失败,邮箱账号已存在");
} }
user.setUserId(sysUser.getUserId()); if (userService.updateUserProfile(currentUser) > 0)
user.setPassword(null);
if (userService.updateUserProfile(user) > 0)
{ {
// 更新缓存用户信息 // 更新缓存用户信息
sysUser.setNickName(user.getNickName());
sysUser.setPhonenumber(user.getPhonenumber());
sysUser.setEmail(user.getEmail());
sysUser.setSex(user.getSex());
tokenService.setLoginUser(loginUser); tokenService.setLoginUser(loginUser);
return AjaxResult.success(); return success();
} }
return AjaxResult.error("修改个人信息异常,请联系管理员"); return error("修改个人信息异常,请联系管理员");
} }
/** /**
@ -100,20 +94,21 @@ public class SysProfileController extends BaseController
String password = loginUser.getPassword(); String password = loginUser.getPassword();
if (!SecurityUtils.matchesPassword(oldPassword, password)) if (!SecurityUtils.matchesPassword(oldPassword, password))
{ {
return AjaxResult.error("修改密码失败,旧密码错误"); return error("修改密码失败,旧密码错误");
} }
if (SecurityUtils.matchesPassword(newPassword, password)) if (SecurityUtils.matchesPassword(newPassword, password))
{ {
return AjaxResult.error("新密码不能与旧密码相同"); return error("新密码不能与旧密码相同");
} }
if (userService.resetUserPwd(userName, SecurityUtils.encryptPassword(newPassword)) > 0) newPassword = SecurityUtils.encryptPassword(newPassword);
if (userService.resetUserPwd(userName, newPassword) > 0)
{ {
// 更新缓存用户密码 // 更新缓存用户密码
loginUser.getUser().setPassword(SecurityUtils.encryptPassword(newPassword)); loginUser.getUser().setPassword(newPassword);
tokenService.setLoginUser(loginUser); tokenService.setLoginUser(loginUser);
return AjaxResult.success(); return success();
} }
return AjaxResult.error("修改密码异常,请联系管理员"); return error("修改密码异常,请联系管理员");
} }
/** /**
@ -137,6 +132,6 @@ public class SysProfileController extends BaseController
return ajax; return ajax;
} }
} }
return AjaxResult.error("上传图片异常,请联系管理员"); return error("上传图片异常,请联系管理员");
} }
} }

View File

@ -14,9 +14,9 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.domain.model.LoginUser;
@ -27,6 +27,7 @@ import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.service.SysPermissionService; import com.ruoyi.framework.web.service.SysPermissionService;
import com.ruoyi.framework.web.service.TokenService; import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.domain.SysUserRole; import com.ruoyi.system.domain.SysUserRole;
import com.ruoyi.system.service.ISysDeptService;
import com.ruoyi.system.service.ISysRoleService; import com.ruoyi.system.service.ISysRoleService;
import com.ruoyi.system.service.ISysUserService; import com.ruoyi.system.service.ISysUserService;
@ -44,13 +45,16 @@ public class SysRoleController extends BaseController
@Autowired @Autowired
private TokenService tokenService; private TokenService tokenService;
@Autowired @Autowired
private SysPermissionService permissionService; private SysPermissionService permissionService;
@Autowired @Autowired
private ISysUserService userService; private ISysUserService userService;
@Autowired
private ISysDeptService deptService;
@PreAuthorize("@ss.hasPermi('system:role:list')") @PreAuthorize("@ss.hasPermi('system:role:list')")
@GetMapping("/list") @GetMapping("/list")
public TableDataInfo list(SysRole role) public TableDataInfo list(SysRole role)
@ -78,7 +82,7 @@ public class SysRoleController extends BaseController
public AjaxResult getInfo(@PathVariable Long roleId) public AjaxResult getInfo(@PathVariable Long roleId)
{ {
roleService.checkRoleDataScope(roleId); roleService.checkRoleDataScope(roleId);
return AjaxResult.success(roleService.selectRoleById(roleId)); return success(roleService.selectRoleById(roleId));
} }
/** /**
@ -89,13 +93,13 @@ public class SysRoleController extends BaseController
@PostMapping @PostMapping
public AjaxResult add(@Validated @RequestBody SysRole role) public AjaxResult add(@Validated @RequestBody SysRole role)
{ {
if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role))) if (!roleService.checkRoleNameUnique(role))
{ {
return AjaxResult.error("新增角色'" + role.getRoleName() + "'失败,角色名称已存在"); return error("新增角色'" + role.getRoleName() + "'失败,角色名称已存在");
} }
else if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role))) else if (!roleService.checkRoleKeyUnique(role))
{ {
return AjaxResult.error("新增角色'" + role.getRoleName() + "'失败,角色权限已存在"); return error("新增角色'" + role.getRoleName() + "'失败,角色权限已存在");
} }
role.setCreateBy(getUsername()); role.setCreateBy(getUsername());
return toAjax(roleService.insertRole(role)); return toAjax(roleService.insertRole(role));
@ -112,13 +116,13 @@ public class SysRoleController extends BaseController
{ {
roleService.checkRoleAllowed(role); roleService.checkRoleAllowed(role);
roleService.checkRoleDataScope(role.getRoleId()); roleService.checkRoleDataScope(role.getRoleId());
if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role))) if (!roleService.checkRoleNameUnique(role))
{ {
return AjaxResult.error("修改角色'" + role.getRoleName() + "'失败,角色名称已存在"); return error("修改角色'" + role.getRoleName() + "'失败,角色名称已存在");
} }
else if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role))) else if (!roleService.checkRoleKeyUnique(role))
{ {
return AjaxResult.error("修改角色'" + role.getRoleName() + "'失败,角色权限已存在"); return error("修改角色'" + role.getRoleName() + "'失败,角色权限已存在");
} }
role.setUpdateBy(getUsername()); role.setUpdateBy(getUsername());
@ -132,9 +136,9 @@ public class SysRoleController extends BaseController
loginUser.setUser(userService.selectUserByUserName(loginUser.getUser().getUserName())); loginUser.setUser(userService.selectUserByUserName(loginUser.getUser().getUserName()));
tokenService.setLoginUser(loginUser); tokenService.setLoginUser(loginUser);
} }
return AjaxResult.success(); return success();
} }
return AjaxResult.error("修改角色'" + role.getRoleName() + "'失败,请联系管理员"); return error("修改角色'" + role.getRoleName() + "'失败,请联系管理员");
} }
/** /**
@ -182,7 +186,7 @@ public class SysRoleController extends BaseController
@GetMapping("/optionselect") @GetMapping("/optionselect")
public AjaxResult optionselect() public AjaxResult optionselect()
{ {
return AjaxResult.success(roleService.selectRoleAll()); return success(roleService.selectRoleAll());
} }
/** /**
@ -242,4 +246,17 @@ public class SysRoleController extends BaseController
roleService.checkRoleDataScope(roleId); roleService.checkRoleDataScope(roleId);
return toAjax(roleService.insertAuthUsers(roleId, userIds)); return toAjax(roleService.insertAuthUsers(roleId, userIds));
} }
/**
* 获取对应角色部门树列表
*/
@PreAuthorize("@ss.hasPermi('system:role:query')")
@GetMapping(value = "/deptTree/{roleId}")
public AjaxResult deptTree(@PathVariable("roleId") Long roleId)
{
AjaxResult ajax = AjaxResult.success();
ajax.put("checkedKeys", deptService.selectDeptListByRoleId(roleId));
ajax.put("depts", deptService.selectDeptTreeList(new SysDept()));
return ajax;
}
} }

View File

@ -17,9 +17,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
@ -27,6 +27,7 @@ import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.system.service.ISysDeptService;
import com.ruoyi.system.service.ISysPostService; import com.ruoyi.system.service.ISysPostService;
import com.ruoyi.system.service.ISysRoleService; import com.ruoyi.system.service.ISysRoleService;
import com.ruoyi.system.service.ISysUserService; import com.ruoyi.system.service.ISysUserService;
@ -46,6 +47,9 @@ public class SysUserController extends BaseController
@Autowired @Autowired
private ISysRoleService roleService; private ISysRoleService roleService;
@Autowired
private ISysDeptService deptService;
@Autowired @Autowired
private ISysPostService postService; private ISysPostService postService;
@ -80,7 +84,7 @@ public class SysUserController extends BaseController
List<SysUser> userList = util.importExcel(file.getInputStream()); List<SysUser> userList = util.importExcel(file.getInputStream());
String operName = getUsername(); String operName = getUsername();
String message = userService.importUser(userList, updateSupport, operName); String message = userService.importUser(userList, updateSupport, operName);
return AjaxResult.success(message); return success(message);
} }
@PostMapping("/importTemplate") @PostMapping("/importTemplate")
@ -120,19 +124,19 @@ public class SysUserController extends BaseController
@PostMapping @PostMapping
public AjaxResult add(@Validated @RequestBody SysUser user) public AjaxResult add(@Validated @RequestBody SysUser user)
{ {
if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(user.getUserName()))) deptService.checkDeptDataScope(user.getDeptId());
roleService.checkRoleDataScope(user.getRoleIds());
if (!userService.checkUserNameUnique(user))
{ {
return AjaxResult.error("新增用户'" + user.getUserName() + "'失败,登录账号已存在"); return error("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
} }
else if (StringUtils.isNotEmpty(user.getPhonenumber()) else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user))
&& UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user)))
{ {
return AjaxResult.error("新增用户'" + user.getUserName() + "'失败,手机号码已存在"); return error("新增用户'" + user.getUserName() + "'失败,手机号码已存在");
} }
else if (StringUtils.isNotEmpty(user.getEmail()) else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user))
&& UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user)))
{ {
return AjaxResult.error("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在"); return error("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
} }
user.setCreateBy(getUsername()); user.setCreateBy(getUsername());
user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
@ -149,15 +153,19 @@ public class SysUserController extends BaseController
{ {
userService.checkUserAllowed(user); userService.checkUserAllowed(user);
userService.checkUserDataScope(user.getUserId()); userService.checkUserDataScope(user.getUserId());
if (StringUtils.isNotEmpty(user.getPhonenumber()) deptService.checkDeptDataScope(user.getDeptId());
&& UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) roleService.checkRoleDataScope(user.getRoleIds());
if (!userService.checkUserNameUnique(user))
{ {
return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); return error("修改用户'" + user.getUserName() + "'失败,登录账号已存在");
} }
else if (StringUtils.isNotEmpty(user.getEmail()) else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user))
&& UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user)))
{ {
return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); return error("修改用户'" + user.getUserName() + "'失败,手机号码已存在");
}
else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user))
{
return error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
} }
user.setUpdateBy(getUsername()); user.setUpdateBy(getUsername());
return toAjax(userService.updateUser(user)); return toAjax(userService.updateUser(user));
@ -231,7 +239,18 @@ public class SysUserController extends BaseController
public AjaxResult insertAuthRole(Long userId, Long[] roleIds) public AjaxResult insertAuthRole(Long userId, Long[] roleIds)
{ {
userService.checkUserDataScope(userId); userService.checkUserDataScope(userId);
roleService.checkRoleDataScope(roleIds);
userService.insertUserAuth(userId, roleIds); userService.insertUserAuth(userId, roleIds);
return success(); return success();
} }
/**
* 获取部门树列表
*/
@PreAuthorize("@ss.hasPermi('system:user:list')")
@GetMapping("/deptTree")
public AjaxResult deptTree(SysDept dept)
{
return success(deptService.selectDeptTreeList(dept));
}
} }

View File

@ -1,24 +0,0 @@
package com.ruoyi.web.controller.tool;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.ruoyi.common.core.controller.BaseController;
/**
* swagger 接口
*
* @author ruoyi
*/
@Controller
@RequestMapping("/tool/swagger")
public class SwaggerController extends BaseController
{
@PreAuthorize("@ss.hasPermi('tool:swagger:view')")
@GetMapping()
public String index()
{
return redirect("/swagger-ui.html");
}
}

View File

@ -1 +1 @@
restart.include.json=/com.alibaba.fastjson.*.jar restart.include.json=/com.alibaba.fastjson2.*.jar

View File

@ -24,6 +24,10 @@ spring:
maxActive: 20 maxActive: 20
# 配置获取连接等待超时的时间 # 配置获取连接等待超时的时间
maxWait: 60000 maxWait: 60000
# 配置连接超时时间
connectTimeout: 30000
# 配置网络超时时间
socketTimeout: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000 timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒 # 配置一个连接在池中最小生存的时间,单位是毫秒

View File

@ -3,16 +3,14 @@ ruoyi:
# 名称 # 名称
name: RuoYi name: RuoYi
# 版本 # 版本
version: 3.8.3 version: 3.8.8
# 版权年份 # 版权年份
copyrightYear: 2022 copyrightYear: 2024
# 实例演示开关
demoEnabled: true
# 文件路径 示例( Windows配置D:/ruoyi/uploadPathLinux配置 /home/ruoyi/uploadPath # 文件路径 示例( Windows配置D:/ruoyi/uploadPathLinux配置 /home/ruoyi/uploadPath
profile: D:/ruoyi/uploadPath profile: D:/ruoyi/uploadPath
# 获取ip地址开关 # 获取ip地址开关
addressEnabled: false addressEnabled: false
# 验证码类型 math 数计算 char 字符验证 # 验证码类型 math 数计算 char 字符验证
captchaType: math captchaType: math
# 开发环境配置 # 开发环境配置
@ -39,21 +37,29 @@ logging:
com.ruoyi: debug com.ruoyi: debug
org.springframework: warn org.springframework: warn
# 用户配置
user:
password:
# 密码最大错误次数
maxRetryCount: 5
# 密码锁定时间默认10分钟
lockTime: 10
# Spring配置 # Spring配置
spring: spring:
# 资源信息 # 资源信息
messages: messages:
# 国际化资源文件路径 # 国际化资源文件路径
basename: i18n/messages basename: i18n/messages
profiles: profiles:
active: druid active: druid
# 文件上传 # 文件上传
servlet: servlet:
multipart: multipart:
# 单个文件大小 # 单个文件大小
max-file-size: 10MB max-file-size: 10MB
# 设置总上传的文件大小 # 设置总上传的文件大小
max-request-size: 20MB max-request-size: 20MB
# 服务模块 # 服务模块
devtools: devtools:
restart: restart:
@ -68,7 +74,7 @@ spring:
# 数据库索引 # 数据库索引
database: 0 database: 0
# 密码 # 密码
password: password:
# 连接超时时间 # 连接超时时间
timeout: 10s timeout: 10s
lettuce: lettuce:
@ -84,27 +90,27 @@ spring:
# token配置 # token配置
token: token:
# 令牌自定义标识 # 令牌自定义标识
header: Authorization header: Authorization
# 令牌密钥 # 令牌密钥
secret: abcdefghijklmnopqrstuvwxyz secret: abcdefghijklmnopqrstuvwxyz
# 令牌有效期默认30分钟 # 令牌有效期默认30分钟
expireTime: 30 expireTime: 30
# MyBatis配置 # MyBatis配置
mybatis: mybatis:
# 搜索指定包别名 # 搜索指定包别名
typeAliasesPackage: com.ruoyi.**.domain typeAliasesPackage: com.ruoyi.**.domain
# 配置mapper的扫描找到所有的mapper.xml映射文件 # 配置mapper的扫描找到所有的mapper.xml映射文件
mapperLocations: classpath*:mapper/**/*Mapper.xml mapperLocations: classpath*:mapper/**/*Mapper.xml
# 加载全局的配置文件 # 加载全局的配置文件
configLocation: classpath:mybatis/mybatis-config.xml configLocation: classpath:mybatis/mybatis-config.xml
# PageHelper分页插件 # PageHelper分页插件
pagehelper: pagehelper:
helperDialect: mysql helperDialect: mysql
supportMethodsArguments: true supportMethodsArguments: true
params: count=countSql params: count=countSql
# Swagger配置 # Swagger配置
swagger: swagger:
@ -114,7 +120,7 @@ swagger:
pathMapping: /dev-api pathMapping: /dev-api
# 防止XSS攻击 # 防止XSS攻击
xss: xss:
# 过滤开关 # 过滤开关
enabled: true enabled: true
# 排除链接(多个用逗号分隔) # 排除链接(多个用逗号分隔)

View File

@ -5,10 +5,11 @@ user.jcaptcha.expire=验证码已失效
user.not.exists=用户不存在/密码错误 user.not.exists=用户不存在/密码错误
user.password.not.match=用户不存在/密码错误 user.password.not.match=用户不存在/密码错误
user.password.retry.limit.count=密码输入错误{0}次 user.password.retry.limit.count=密码输入错误{0}次
user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定10分钟 user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
user.password.delete=对不起,您的账号已被删除 user.password.delete=对不起,您的账号已被删除
user.blocked=用户已封禁,请联系管理员 user.blocked=用户已封禁,请联系管理员
role.blocked=角色已封禁,请联系管理员 role.blocked=角色已封禁,请联系管理员
login.blocked=很遗憾访问IP已被列入系统黑名单
user.logout.success=退出成功 user.logout.success=退出成功
length.not.valid=长度必须在{min}到{max}个字符之间 length.not.valid=长度必须在{min}到{max}个字符之间

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>3.8.3</version> <version>3.8.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@ -58,7 +58,7 @@
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
</dependency> </dependency>
<!-- 阿里JSON解析器 --> <!-- 阿里JSON解析器 -->
<dependency> <dependency>
<groupId>com.alibaba.fastjson2</groupId> <groupId>com.alibaba.fastjson2</groupId>
@ -71,12 +71,6 @@
<artifactId>commons-io</artifactId> <artifactId>commons-io</artifactId>
</dependency> </dependency>
<!-- 文件上传工具类 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
<!-- excel工具 --> <!-- excel工具 -->
<dependency> <dependency>
<groupId>org.apache.poi</groupId> <groupId>org.apache.poi</groupId>

View File

@ -25,4 +25,9 @@ public @interface DataScope
* 用户表的别名 * 用户表的别名
*/ */
public String userAlias() default ""; public String userAlias() default "";
/**
* 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@ss获取多个权限用逗号分隔开来
*/
public String permission() default "";
} }

View File

@ -59,12 +59,12 @@ public @interface Excel
public int roundingMode() default BigDecimal.ROUND_HALF_EVEN; public int roundingMode() default BigDecimal.ROUND_HALF_EVEN;
/** /**
* 导出时在excel中每个列的高度 单位为字符 * 导出时在excel中每个列的高度
*/ */
public double height() default 14; public double height() default 14;
/** /**
* 导出时在excel中每个列的宽 单位为字符 * 导出时在excel中每个列的宽
*/ */
public double width() default 16; public double width() default 16;
@ -88,6 +88,16 @@ public @interface Excel
*/ */
public String[] combo() default {}; public String[] combo() default {};
/**
* 是否从字典读数据到combo,默认不读取,如读取需要设置dictType注解.
*/
public boolean comboReadDict() default false;
/**
* 是否需要纵向合并单元格,应对需求:含有list集合单元格)
*/
public boolean needMerge() default false;
/** /**
* 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写. * 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写.
*/ */
@ -104,12 +114,27 @@ public @interface Excel
public boolean isStatistics() default false; public boolean isStatistics() default false;
/** /**
* 导出类型0数字 1字符串 * 导出类型0数字 1字符串 2图片
*/ */
public ColumnType cellType() default ColumnType.STRING; public ColumnType cellType() default ColumnType.STRING;
/** /**
* 导出字体颜色 * 导出列头背景颜色
*/
public IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT;
/**
* 导出列头字体颜色
*/
public IndexedColors headerColor() default IndexedColors.WHITE;
/**
* 导出单元格背景颜色
*/
public IndexedColors backgroundColor() default IndexedColors.WHITE;
/**
* 导出单元格字体颜色
*/ */
public IndexedColors color() default IndexedColors.BLACK; public IndexedColors color() default IndexedColors.BLACK;
@ -128,22 +153,6 @@ public @interface Excel
*/ */
public String[] args() default {}; public String[] args() default {};
public enum Align
{
AUTO(0), LEFT(1), CENTER(2), RIGHT(3);
private final int value;
Align(int value)
{
this.value = value;
}
public int value()
{
return this.value;
}
}
/** /**
* 字段类型0导出导入1仅导出2仅导入 * 字段类型0导出导入1仅导出2仅导入
*/ */
@ -167,7 +176,7 @@ public @interface Excel
public enum ColumnType public enum ColumnType
{ {
NUMERIC(0), STRING(1), IMAGE(2); NUMERIC(0), STRING(1), IMAGE(2), TEXT(3);
private final int value; private final int value;
ColumnType(int value) ColumnType(int value)

View File

@ -20,7 +20,7 @@ import com.ruoyi.common.enums.OperatorType;
public @interface Log public @interface Log
{ {
/** /**
* 模块 * 模块
*/ */
public String title() default ""; public String title() default "";
@ -43,4 +43,9 @@ public @interface Log
* 是否保存响应的参数 * 是否保存响应的参数
*/ */
public boolean isSaveResponseData() default true; public boolean isSaveResponseData() default true;
/**
* 排除指定的请求参数
*/
public String[] excludeParamNames() default {};
} }

View File

@ -0,0 +1,24 @@
package com.ruoyi.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.ruoyi.common.config.serializer.SensitiveJsonSerializer;
import com.ruoyi.common.enums.DesensitizedType;
/**
* 数据脱敏注解
*
* @author ruoyi
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveJsonSerializer.class)
public @interface Sensitive
{
DesensitizedType desensitizedType();
}

View File

@ -21,9 +21,6 @@ public class RuoYiConfig
/** 版权年份 */ /** 版权年份 */
private String copyrightYear; private String copyrightYear;
/** 实例演示开关 */
private boolean demoEnabled;
/** 上传路径 */ /** 上传路径 */
private static String profile; private static String profile;
@ -63,16 +60,6 @@ public class RuoYiConfig
this.copyrightYear = copyrightYear; this.copyrightYear = copyrightYear;
} }
public boolean isDemoEnabled()
{
return demoEnabled;
}
public void setDemoEnabled(boolean demoEnabled)
{
this.demoEnabled = demoEnabled;
}
public static String getProfile() public static String getProfile()
{ {
return profile; return profile;

View File

@ -0,0 +1,67 @@
package com.ruoyi.common.config.serializer;
import java.io.IOException;
import java.util.Objects;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.ruoyi.common.annotation.Sensitive;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.enums.DesensitizedType;
import com.ruoyi.common.utils.SecurityUtils;
/**
* 数据脱敏序列化过滤
*
* @author ruoyi
*/
public class SensitiveJsonSerializer extends JsonSerializer<String> implements ContextualSerializer
{
private DesensitizedType desensitizedType;
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException
{
if (desensitization())
{
gen.writeString(desensitizedType.desensitizer().apply(value));
}
else
{
gen.writeString(value);
}
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException
{
Sensitive annotation = property.getAnnotation(Sensitive.class);
if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass()))
{
this.desensitizedType = annotation.desensitizedType();
return this;
}
return prov.findValueSerializer(property.getType(), property);
}
/**
* 是否需要脱敏处理
*/
private boolean desensitization()
{
try
{
LoginUser securityUser = SecurityUtils.getLoginUser();
// 管理员不脱敏
return !securityUser.getUser().isAdmin();
}
catch (Exception e)
{
return true;
}
}
}

View File

@ -36,4 +36,9 @@ public class CacheConstants
* 限流 redis key * 限流 redis key
*/ */
public static final String RATE_LIMIT_KEY = "rate_limit:"; public static final String RATE_LIMIT_KEY = "rate_limit:";
/**
* 登录账户密码错误次数 redis key
*/
public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
} }

View File

@ -1,5 +1,6 @@
package com.ruoyi.common.constant; package com.ruoyi.common.constant;
import java.util.Locale;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
/** /**
@ -19,6 +20,16 @@ public class Constants
*/ */
public static final String GBK = "GBK"; public static final String GBK = "GBK";
/**
* 系统语言
*/
public static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE;
/**
* www主域
*/
public static final String WWW = "www.";
/** /**
* http请求 * http请求
*/ */
@ -58,7 +69,27 @@ public class Constants
* 登录失败 * 登录失败
*/ */
public static final String LOGIN_FAIL = "Error"; public static final String LOGIN_FAIL = "Error";
/**
* 所有权限标识
*/
public static final String ALL_PERMISSION = "*:*:*";
/**
* 管理员角色权限标识
*/
public static final String SUPER_ADMIN = "admin";
/**
* 角色权限分隔符
*/
public static final String ROLE_DELIMETER = ",";
/**
* 权限标识分隔符
*/
public static final String PERMISSION_DELIMETER = ",";
/** /**
* 验证码有效期(分钟) * 验证码有效期(分钟)
*/ */
@ -124,14 +155,19 @@ public class Constants
*/ */
public static final String LOOKUP_LDAPS = "ldaps:"; public static final String LOOKUP_LDAPS = "ldaps:";
/**
* 自动识别json对象白名单配置仅允许解析的包名范围越小越安全
*/
public static final String[] JSON_WHITELIST_STR = { "org.springframework", "com.ruoyi" };
/** /**
* 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
*/ */
public static final String[] JOB_WHITELIST_STR = { "com.ruoyi" }; public static final String[] JOB_WHITELIST_STR = { "com.ruoyi.quartz.task" };
/** /**
* 定时任务违规的字符 * 定时任务违规的字符
*/ */
public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
"org.springframework", "org.apache", "com.ruoyi.common.utils.file" }; "org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config", "com.ruoyi.generator" };
} }

View File

@ -86,4 +86,9 @@ public class HttpStatus
* 接口未实现 * 接口未实现
*/ */
public static final int NOT_IMPLEMENTED = 501; public static final int NOT_IMPLEMENTED = 501;
/**
* 系统警告消息
*/
public static final int WARN = 601;
} }

View File

@ -60,9 +60,9 @@ public class UserConstants
/** InnerLink组件标识 */ /** InnerLink组件标识 */
public final static String INNER_LINK = "InnerLink"; public final static String INNER_LINK = "InnerLink";
/** 校验返回结果码 */ /** 校验是否唯一的返回标识 */
public final static String UNIQUE = "0"; public final static boolean UNIQUE = true;
public final static String NOT_UNIQUE = "1"; public final static boolean NOT_UNIQUE = false;
/** /**
* 用户名长度限制 * 用户名长度限制

View File

@ -113,6 +113,14 @@ public class BaseController
{ {
return AjaxResult.success(message); return AjaxResult.success(message);
} }
/**
* 返回成功消息
*/
public AjaxResult success(Object data)
{
return AjaxResult.success(data);
}
/** /**
* 返回失败消息 * 返回失败消息
@ -122,6 +130,14 @@ public class BaseController
return AjaxResult.error(message); return AjaxResult.error(message);
} }
/**
* 返回警告消息
*/
public AjaxResult warn(String message)
{
return AjaxResult.warn(message);
}
/** /**
* 响应返回结果 * 响应返回结果
* *

View File

@ -1,6 +1,7 @@
package com.ruoyi.common.core.domain; package com.ruoyi.common.core.domain;
import java.util.HashMap; import java.util.HashMap;
import java.util.Objects;
import com.ruoyi.common.constant.HttpStatus; import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
@ -101,10 +102,33 @@ public class AjaxResult extends HashMap<String, Object>
return new AjaxResult(HttpStatus.SUCCESS, msg, data); return new AjaxResult(HttpStatus.SUCCESS, msg, data);
} }
/**
* 返回警告消息
*
* @param msg 返回内容
* @return 警告消息
*/
public static AjaxResult warn(String msg)
{
return AjaxResult.warn(msg, null);
}
/**
* 返回警告消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 警告消息
*/
public static AjaxResult warn(String msg, Object data)
{
return new AjaxResult(HttpStatus.WARN, msg, data);
}
/** /**
* 返回错误消息 * 返回错误消息
* *
* @return * @return 错误消息
*/ */
public static AjaxResult error() public static AjaxResult error()
{ {
@ -115,7 +139,7 @@ public class AjaxResult extends HashMap<String, Object>
* 返回错误消息 * 返回错误消息
* *
* @param msg 返回内容 * @param msg 返回内容
* @return 警告消息 * @return 错误消息
*/ */
public static AjaxResult error(String msg) public static AjaxResult error(String msg)
{ {
@ -127,7 +151,7 @@ public class AjaxResult extends HashMap<String, Object>
* *
* @param msg 返回内容 * @param msg 返回内容
* @param data 数据对象 * @param data 数据对象
* @return 警告消息 * @return 错误消息
*/ */
public static AjaxResult error(String msg, Object data) public static AjaxResult error(String msg, Object data)
{ {
@ -139,13 +163,43 @@ public class AjaxResult extends HashMap<String, Object>
* *
* @param code 状态码 * @param code 状态码
* @param msg 返回内容 * @param msg 返回内容
* @return 警告消息 * @return 错误消息
*/ */
public static AjaxResult error(int code, String msg) public static AjaxResult error(int code, String msg)
{ {
return new AjaxResult(code, msg, null); return new AjaxResult(code, msg, null);
} }
/**
* 是否为成功消息
*
* @return 结果
*/
public boolean isSuccess()
{
return Objects.equals(HttpStatus.SUCCESS, this.get(CODE_TAG));
}
/**
* 是否为警告消息
*
* @return 结果
*/
public boolean isWarn()
{
return Objects.equals(HttpStatus.WARN, this.get(CODE_TAG));
}
/**
* 是否为错误消息
*
* @return 结果
*/
public boolean isError()
{
return Objects.equals(HttpStatus.ERROR, this.get(CODE_TAG));
}
/** /**
* 方便链式调用 * 方便链式调用
* *

View File

@ -5,6 +5,8 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
/** /**
* Entity基类 * Entity基类
@ -16,6 +18,7 @@ public class BaseEntity implements Serializable
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** 搜索值 */ /** 搜索值 */
@JsonIgnore
private String searchValue; private String searchValue;
/** 创建者 */ /** 创建者 */
@ -36,6 +39,7 @@ public class BaseEntity implements Serializable
private String remark; private String remark;
/** 请求参数 */ /** 请求参数 */
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private Map<String, Object> params; private Map<String, Object> params;
public String getSearchValue() public String getSearchValue()

View File

@ -102,4 +102,14 @@ public class R<T> implements Serializable
{ {
this.data = data; this.data = data;
} }
public static <T> Boolean isError(R<T> ret)
{
return !isSuccess(ret);
}
public static <T> Boolean isSuccess(R<T> ret)
{
return R.SUCCESS == ret.getCode();
}
} }

View File

@ -131,7 +131,7 @@ public class SysDictData extends BaseEntity
public boolean getDefault() public boolean getDefault()
{ {
return UserConstants.YES.equals(this.isDefault) ? true : false; return UserConstants.YES.equals(this.isDefault);
} }
public String getIsDefault() public String getIsDefault()

View File

@ -42,6 +42,9 @@ public class SysMenu extends BaseEntity
/** 路由参数 */ /** 路由参数 */
private String query; private String query;
/** 路由名称默认和路由地址相同的驼峰格式注意因为vue3版本的router会删除名称相同路由为避免名字的冲突特殊情况可以自定义 */
private String routeName;
/** 是否为外链0是 1否 */ /** 是否为外链0是 1否 */
private String isFrame; private String isFrame;
@ -53,8 +56,8 @@ public class SysMenu extends BaseEntity
/** 显示状态0显示 1隐藏 */ /** 显示状态0显示 1隐藏 */
private String visible; private String visible;
/** 菜单状态0显示 1隐藏 */ /** 菜单状态0正常 1停用 */
private String status; private String status;
/** 权限字符串 */ /** 权限字符串 */
@ -151,6 +154,16 @@ public class SysMenu extends BaseEntity
this.query = query; this.query = query;
} }
public String getRouteName()
{
return routeName;
}
public void setRouteName(String routeName)
{
this.routeName = routeName;
}
public String getIsFrame() public String getIsFrame()
{ {
return isFrame; return isFrame;
@ -232,7 +245,7 @@ public class SysMenu extends BaseEntity
{ {
this.children = children; this.children = children;
} }
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@ -242,6 +255,8 @@ public class SysMenu extends BaseEntity
.append("orderNum", getOrderNum()) .append("orderNum", getOrderNum())
.append("path", getPath()) .append("path", getPath())
.append("component", getComponent()) .append("component", getComponent())
.append("query", getQuery())
.append("routeName", getRouteName())
.append("isFrame", getIsFrame()) .append("isFrame", getIsFrame())
.append("IsCache", getIsCache()) .append("IsCache", getIsCache())
.append("menuType", getMenuType()) .append("menuType", getMenuType())

View File

@ -1,6 +1,8 @@
package com.ruoyi.common.core.domain.entity; package com.ruoyi.common.core.domain.entity;
import java.util.Set;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
@ -31,7 +33,7 @@ public class SysRole extends BaseEntity
/** 角色排序 */ /** 角色排序 */
@Excel(name = "角色排序") @Excel(name = "角色排序")
private String roleSort; private Integer roleSort;
/** 数据范围1所有数据权限2自定义数据权限3本部门数据权限4本部门及以下数据权限5仅本人数据权限 */ /** 数据范围1所有数据权限2自定义数据权限3本部门数据权限4本部门及以下数据权限5仅本人数据权限 */
@Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限") @Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限")
@ -59,6 +61,9 @@ public class SysRole extends BaseEntity
/** 部门组(数据权限) */ /** 部门组(数据权限) */
private Long[] deptIds; private Long[] deptIds;
/** 角色菜单权限 */
private Set<String> permissions;
public SysRole() public SysRole()
{ {
@ -113,13 +118,13 @@ public class SysRole extends BaseEntity
this.roleKey = roleKey; this.roleKey = roleKey;
} }
@NotBlank(message = "显示顺序不能为空") @NotNull(message = "显示顺序不能为空")
public String getRoleSort() public Integer getRoleSort()
{ {
return roleSort; return roleSort;
} }
public void setRoleSort(String roleSort) public void setRoleSort(Integer roleSort)
{ {
this.roleSort = roleSort; this.roleSort = roleSort;
} }
@ -203,7 +208,17 @@ public class SysRole extends BaseEntity
{ {
this.deptIds = deptIds; this.deptIds = deptIds;
} }
public Set<String> getPermissions()
{
return permissions;
}
public void setPermissions(Set<String> permissions)
{
this.permissions = permissions;
}
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)

View File

@ -22,7 +22,7 @@ public class SysUser extends BaseEntity
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** 用户ID */ /** 用户ID */
@Excel(name = "用户序号", cellType = ColumnType.NUMERIC, prompt = "用户编号") @Excel(name = "用户序号", type = Type.EXPORT, cellType = ColumnType.NUMERIC, prompt = "用户编号")
private Long userId; private Long userId;
/** 部门ID */ /** 部门ID */
@ -42,7 +42,7 @@ public class SysUser extends BaseEntity
private String email; private String email;
/** 手机号码 */ /** 手机号码 */
@Excel(name = "手机号码") @Excel(name = "手机号码", cellType = ColumnType.TEXT)
private String phonenumber; private String phonenumber;
/** 用户性别 */ /** 用户性别 */

View File

@ -1,11 +1,11 @@
package com.ruoyi.common.core.domain.model; package com.ruoyi.common.core.domain.model;
import java.util.Collection;
import java.util.Set;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.alibaba.fastjson2.annotation.JSONField; import com.alibaba.fastjson2.annotation.JSONField;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Set;
/** /**
* 登录用户身份权限 * 登录用户身份权限
@ -71,6 +71,24 @@ public class LoginUser implements UserDetails
*/ */
private SysUser user; private SysUser user;
public LoginUser()
{
}
public LoginUser(SysUser user, Set<String> permissions)
{
this.user = user;
this.permissions = permissions;
}
public LoginUser(Long userId, Long deptId, SysUser user, Set<String> permissions)
{
this.userId = userId;
this.deptId = deptId;
this.user = user;
this.permissions = permissions;
}
public Long getUserId() public Long getUserId()
{ {
return userId; return userId;
@ -101,24 +119,6 @@ public class LoginUser implements UserDetails
this.token = token; this.token = token;
} }
public LoginUser()
{
}
public LoginUser(SysUser user, Set<String> permissions)
{
this.user = user;
this.permissions = permissions;
}
public LoginUser(Long userId, Long deptId, SysUser user, Set<String> permissions)
{
this.userId = userId;
this.deptId = deptId;
this.user = user;
this.permissions = permissions;
}
@JSONField(serialize = false) @JSONField(serialize = false)
@Override @Override
public String getPassword() public String getPassword()

View File

@ -74,6 +74,28 @@ public class RedisCache
return redisTemplate.expire(key, timeout, unit); return redisTemplate.expire(key, timeout, unit);
} }
/**
* 获取有效时间
*
* @param key Redis键
* @return 有效时间
*/
public long getExpire(final String key)
{
return redisTemplate.getExpire(key);
}
/**
* 判断 key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public Boolean hasKey(String key)
{
return redisTemplate.hasKey(key);
}
/** /**
* 获得缓存的基本对象。 * 获得缓存的基本对象。
* *
@ -102,9 +124,9 @@ public class RedisCache
* @param collection 多个对象 * @param collection 多个对象
* @return * @return
*/ */
public long deleteObject(final Collection collection) public boolean deleteObject(final Collection collection)
{ {
return redisTemplate.delete(collection); return redisTemplate.delete(collection) > 0;
} }
/** /**
@ -209,18 +231,6 @@ public class RedisCache
return opsForHash.get(key, hKey); return opsForHash.get(key, hKey);
} }
/**
* 删除Hash中的数据
*
* @param key
* @param hKey
*/
public void delCacheMapValue(final String key, final String hKey)
{
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.delete(key, hKey);
}
/** /**
* 获取多个Hash中的数据 * 获取多个Hash中的数据
* *
@ -233,6 +243,18 @@ public class RedisCache
return redisTemplate.opsForHash().multiGet(key, hKeys); return redisTemplate.opsForHash().multiGet(key, hKeys);
} }
/**
* 删除Hash中的某条数据
*
* @param key Redis键
* @param hKey Hash键
* @return 是否成功
*/
public boolean deleteCacheMapValue(final String key, final String hKey)
{
return redisTemplate.opsForHash().delete(key, hKey) > 0;
}
/** /**
* 获得缓存的基本对象列表 * 获得缓存的基本对象列表
* *

View File

@ -2,6 +2,7 @@ package com.ruoyi.common.core.text;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
import java.math.RoundingMode;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.text.NumberFormat; import java.text.NumberFormat;
@ -364,6 +365,10 @@ public class Convert
*/ */
public static String[] toStrArray(String str) public static String[] toStrArray(String str)
{ {
if (StringUtils.isEmpty(str))
{
return new String[] {};
}
return toStrArray(",", str); return toStrArray(",", str);
} }
@ -713,7 +718,7 @@ public class Convert
} }
if (value instanceof Double) if (value instanceof Double)
{ {
return new BigDecimal((Double) value); return BigDecimal.valueOf((Double) value);
} }
if (value instanceof Integer) if (value instanceof Integer)
{ {
@ -894,7 +899,7 @@ public class Convert
*/ */
public static String toSBC(String input, Set<Character> notConvertSet) public static String toSBC(String input, Set<Character> notConvertSet)
{ {
char c[] = input.toCharArray(); char[] c = input.toCharArray();
for (int i = 0; i < c.length; i++) for (int i = 0; i < c.length; i++)
{ {
if (null != notConvertSet && notConvertSet.contains(c[i])) if (null != notConvertSet && notConvertSet.contains(c[i]))
@ -936,7 +941,7 @@ public class Convert
*/ */
public static String toDBC(String text, Set<Character> notConvertSet) public static String toDBC(String text, Set<Character> notConvertSet)
{ {
char c[] = text.toCharArray(); char[] c = text.toCharArray();
for (int i = 0; i < c.length; i++) for (int i = 0; i < c.length; i++)
{ {
if (null != notConvertSet && notConvertSet.contains(c[i])) if (null != notConvertSet && notConvertSet.contains(c[i]))
@ -977,7 +982,12 @@ public class Convert
String s = ""; String s = "";
for (int i = 0; i < fraction.length; i++) for (int i = 0; i < fraction.length; i++)
{ {
s += (digit[(int) (Math.floor(n * 10 * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", ""); // 优化double计算精度丢失问题
BigDecimal nNum = new BigDecimal(n);
BigDecimal decimal = new BigDecimal(10);
BigDecimal scale = nNum.multiply(decimal).setScale(2, RoundingMode.HALF_EVEN);
double d = scale.doubleValue();
s += (digit[(int) (Math.floor(d * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", "");
} }
if (s.length() < 1) if (s.length() < 1)
{ {

View File

@ -0,0 +1,59 @@
package com.ruoyi.common.enums;
import java.util.function.Function;
import com.ruoyi.common.utils.DesensitizedUtil;
/**
* 脱敏类型
*
* @author ruoyi
*/
public enum DesensitizedType
{
/**
* 姓名第2位星号替换
*/
USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")),
/**
* 密码,全部字符都用*代替
*/
PASSWORD(DesensitizedUtil::password),
/**
* 身份证中间10位星号替换
*/
ID_CARD(s -> s.replaceAll("(\\d{4})\\d{10}(\\d{4})", "$1** **** ****$2")),
/**
* 手机号中间4位星号替换
*/
PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),
/**
* 电子邮箱,仅显示第一个字母和@后面的地址显示,其他星号替换
*/
EMAIL(s -> s.replaceAll("(^.)[^@]*(@.*$)", "$1****$2")),
/**
* 银行卡号保留最后4位其他星号替换
*/
BANK_CARD(s -> s.replaceAll("\\d{15}(\\d{3})", "**** **** **** **** $1")),
/**
* 车牌号码,包含普通车辆、新能源车辆
*/
CAR_LICENSE(DesensitizedUtil::carLicense);
private final Function<String, String> desensitizer;
DesensitizedType(Function<String, String> desensitizer)
{
this.desensitizer = desensitizer;
}
public Function<String, String> desensitizer()
{
return desensitizer;
}
}

View File

@ -7,7 +7,6 @@ package com.ruoyi.common.exception;
*/ */
public class GlobalException extends RuntimeException public class GlobalException extends RuntimeException
{ {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** /**
@ -45,6 +44,7 @@ public class GlobalException extends RuntimeException
return this; return this;
} }
@Override
public String getMessage() public String getMessage()
{ {
return message; return message;

View File

@ -49,6 +49,7 @@ public final class ServiceException extends RuntimeException
return detailMessage; return detailMessage;
} }
@Override
public String getMessage() public String getMessage()
{ {
return message; return message;

View File

@ -0,0 +1,61 @@
package com.ruoyi.common.exception.file;
import java.io.PrintStream;
import java.io.PrintWriter;
/**
* 文件上传异常类
*
* @author ruoyi
*/
public class FileUploadException extends Exception
{
private static final long serialVersionUID = 1L;
private final Throwable cause;
public FileUploadException()
{
this(null, null);
}
public FileUploadException(final String msg)
{
this(msg, null);
}
public FileUploadException(String msg, Throwable cause)
{
super(msg);
this.cause = cause;
}
@Override
public void printStackTrace(PrintStream stream)
{
super.printStackTrace(stream);
if (cause != null)
{
stream.println("Caused by:");
cause.printStackTrace(stream);
}
}
@Override
public void printStackTrace(PrintWriter writer)
{
super.printStackTrace(writer);
if (cause != null)
{
writer.println("Caused by:");
cause.printStackTrace(writer);
}
}
@Override
public Throwable getCause()
{
return cause;
}
}

View File

@ -1,7 +1,6 @@
package com.ruoyi.common.exception.file; package com.ruoyi.common.exception.file;
import java.util.Arrays; import java.util.Arrays;
import org.apache.commons.fileupload.FileUploadException;
/** /**
* 文件上传 误异常类 * 文件上传 误异常类

View File

@ -0,0 +1,16 @@
package com.ruoyi.common.exception.user;
/**
* 黑名单IP异常类
*
* @author ruoyi
*/
public class BlackListException extends UserException
{
private static final long serialVersionUID = 1L;
public BlackListException()
{
super("login.blocked", null);
}
}

View File

@ -0,0 +1,16 @@
package com.ruoyi.common.exception.user;
/**
* 用户不存在异常类
*
* @author ruoyi
*/
public class UserNotExistsException extends UserException
{
private static final long serialVersionUID = 1L;
public UserNotExistsException()
{
super("user.not.exists", null);
}
}

View File

@ -0,0 +1,16 @@
package com.ruoyi.common.exception.user;
/**
* 用户错误最大次数异常类
*
* @author ruoyi
*/
public class UserPasswordRetryLimitExceedException extends UserException
{
private static final long serialVersionUID = 1L;
public UserPasswordRetryLimitExceedException(int retryLimitCount, int lockTime)
{
super("user.password.retry.limit.exceed", new Object[] { retryLimitCount, lockTime });
}
}

View File

@ -0,0 +1,24 @@
package com.ruoyi.common.filter;
import com.alibaba.fastjson2.filter.SimplePropertyPreFilter;
/**
* 排除JSON敏感属性
*
* @author ruoyi
*/
public class PropertyPreExcludeFilter extends SimplePropertyPreFilter
{
public PropertyPreExcludeFilter()
{
}
public PropertyPreExcludeFilter addExcludes(String... filters)
{
for (int i = 0; i < filters.length; i++)
{
this.getExcludes().add(filters[i]);
}
return this;
}
}

View File

@ -10,6 +10,7 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletRequestWrapper;
import com.ruoyi.common.utils.http.HttpHelper; import com.ruoyi.common.utils.http.HttpHelper;
import com.ruoyi.common.constant.Constants;
/** /**
* 构建可重复读取inputStream的request * 构建可重复读取inputStream的request
@ -23,10 +24,10 @@ public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper
public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException
{ {
super(request); super(request);
request.setCharacterEncoding("UTF-8"); request.setCharacterEncoding(Constants.UTF8);
response.setCharacterEncoding("UTF-8"); response.setCharacterEncoding(Constants.UTF8);
body = HttpHelper.getBodyString(request).getBytes("UTF-8"); body = HttpHelper.getBodyString(request).getBytes(Constants.UTF8);
} }
@Override @Override

View File

@ -12,6 +12,7 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.enums.HttpMethod;
/** /**
* 防止XSS攻击的过滤器 * 防止XSS攻击的过滤器
@ -31,10 +32,10 @@ public class XssFilter implements Filter
String tempExcludes = filterConfig.getInitParameter("excludes"); String tempExcludes = filterConfig.getInitParameter("excludes");
if (StringUtils.isNotEmpty(tempExcludes)) if (StringUtils.isNotEmpty(tempExcludes))
{ {
String[] url = tempExcludes.split(","); String[] urls = tempExcludes.split(",");
for (int i = 0; url != null && i < url.length; i++) for (String url : urls)
{ {
excludes.add(url[i]); excludes.add(url);
} }
} }
} }
@ -59,7 +60,7 @@ public class XssFilter implements Filter
String url = request.getServletPath(); String url = request.getServletPath();
String method = request.getMethod(); String method = request.getMethod();
// GET DELETE 不过滤 // GET DELETE 不过滤
if (method == null || method.matches("GET") || method.matches("DELETE")) if (method == null || HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method))
{ {
return true; return true;
} }

View File

@ -34,13 +34,13 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper
if (values != null) if (values != null)
{ {
int length = values.length; int length = values.length;
String[] escapseValues = new String[length]; String[] escapesValues = new String[length];
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
// 防xss攻击和过滤前后空格 // 防xss攻击和过滤前后空格
escapseValues[i] = EscapeUtil.clean(values[i]).trim(); escapesValues[i] = EscapeUtil.clean(values[i]).trim();
} }
return escapseValues; return escapesValues;
} }
return super.getParameterValues(name); return super.getParameterValues(name);
} }

View File

@ -145,16 +145,20 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
} }
/** /**
* 计算两个时间差 * 计算时间差
*
* @param endDate 最后时间
* @param startTime 开始时间
* @return 时间差(天/小时/分钟)
*/ */
public static String getDatePoor(Date endDate, Date nowDate) public static String timeDistance(Date endDate, Date startTime)
{ {
long nd = 1000 * 24 * 60 * 60; long nd = 1000 * 24 * 60 * 60;
long nh = 1000 * 60 * 60; long nh = 1000 * 60 * 60;
long nm = 1000 * 60; long nm = 1000 * 60;
// long ns = 1000; // long ns = 1000;
// 获得两个时间的毫秒时间差异 // 获得两个时间的毫秒时间差异
long diff = endDate.getTime() - nowDate.getTime(); long diff = endDate.getTime() - startTime.getTime();
// 计算差多少天 // 计算差多少天
long day = diff / nd; long day = diff / nd;
// 计算差多少小时 // 计算差多少小时

View File

@ -0,0 +1,49 @@
package com.ruoyi.common.utils;
/**
* 脱敏工具类
*
* @author ruoyi
*/
public class DesensitizedUtil
{
/**
* 密码的全部字符都用*代替,比如:******
*
* @param password 密码
* @return 脱敏后的密码
*/
public static String password(String password)
{
if (StringUtils.isBlank(password))
{
return StringUtils.EMPTY;
}
return StringUtils.repeat('*', password.length());
}
/**
* 车牌中间用*代替,如果是错误的车牌,不处理
*
* @param carLicense 完整的车牌号
* @return 脱敏后的车牌
*/
public static String carLicense(String carLicense)
{
if (StringUtils.isBlank(carLicense))
{
return StringUtils.EMPTY;
}
// 普通车牌
if (carLicense.length() == 7)
{
carLicense = StringUtils.hide(carLicense, 3, 6);
}
else if (carLicense.length() == 8)
{
// 新能源车牌
carLicense = StringUtils.hide(carLicense, 3, 7);
}
return carLicense;
}
}

View File

@ -56,6 +56,10 @@ public class DictUtils
*/ */
public static String getDictLabel(String dictType, String dictValue) public static String getDictLabel(String dictType, String dictValue)
{ {
if (StringUtils.isEmpty(dictValue))
{
return StringUtils.EMPTY;
}
return getDictLabel(dictType, dictValue, SEPARATOR); return getDictLabel(dictType, dictValue, SEPARATOR);
} }
@ -68,6 +72,10 @@ public class DictUtils
*/ */
public static String getDictValue(String dictType, String dictLabel) public static String getDictValue(String dictType, String dictLabel)
{ {
if (StringUtils.isEmpty(dictLabel))
{
return StringUtils.EMPTY;
}
return getDictValue(dictType, dictLabel, SEPARATOR); return getDictValue(dictType, dictLabel, SEPARATOR);
} }
@ -83,31 +91,31 @@ public class DictUtils
{ {
StringBuilder propertyString = new StringBuilder(); StringBuilder propertyString = new StringBuilder();
List<SysDictData> datas = getDictCache(dictType); List<SysDictData> datas = getDictCache(dictType);
if (StringUtils.isNull(datas))
if (StringUtils.isNotNull(datas))
{ {
if (StringUtils.containsAny(separator, dictValue)) return StringUtils.EMPTY;
}
if (StringUtils.containsAny(separator, dictValue))
{
for (SysDictData dict : datas)
{ {
for (SysDictData dict : datas) for (String value : dictValue.split(separator))
{ {
for (String value : dictValue.split(separator)) if (value.equals(dict.getDictValue()))
{ {
if (value.equals(dict.getDictValue())) propertyString.append(dict.getDictLabel()).append(separator);
{ break;
propertyString.append(dict.getDictLabel()).append(separator);
break;
}
} }
} }
} }
else }
else
{
for (SysDictData dict : datas)
{ {
for (SysDictData dict : datas) if (dictValue.equals(dict.getDictValue()))
{ {
if (dictValue.equals(dict.getDictValue())) return dict.getDictLabel();
{
return dict.getDictLabel();
}
} }
} }
} }
@ -126,8 +134,11 @@ public class DictUtils
{ {
StringBuilder propertyString = new StringBuilder(); StringBuilder propertyString = new StringBuilder();
List<SysDictData> datas = getDictCache(dictType); List<SysDictData> datas = getDictCache(dictType);
if (StringUtils.isNull(datas))
if (StringUtils.containsAny(separator, dictLabel) && StringUtils.isNotEmpty(datas)) {
return StringUtils.EMPTY;
}
if (StringUtils.containsAny(separator, dictLabel))
{ {
for (SysDictData dict : datas) for (SysDictData dict : datas)
{ {
@ -154,6 +165,48 @@ public class DictUtils
return StringUtils.stripEnd(propertyString.toString(), separator); return StringUtils.stripEnd(propertyString.toString(), separator);
} }
/**
* 根据字典类型获取字典所有值
*
* @param dictType 字典类型
* @return 字典值
*/
public static String getDictValues(String dictType)
{
StringBuilder propertyString = new StringBuilder();
List<SysDictData> datas = getDictCache(dictType);
if (StringUtils.isNull(datas))
{
return StringUtils.EMPTY;
}
for (SysDictData dict : datas)
{
propertyString.append(dict.getDictValue()).append(SEPARATOR);
}
return StringUtils.stripEnd(propertyString.toString(), SEPARATOR);
}
/**
* 根据字典类型获取字典所有标签
*
* @param dictType 字典类型
* @return 字典值
*/
public static String getDictLabels(String dictType)
{
StringBuilder propertyString = new StringBuilder();
List<SysDictData> datas = getDictCache(dictType);
if (StringUtils.isNull(datas))
{
return StringUtils.EMPTY;
}
for (SysDictData dict : datas)
{
propertyString.append(dict.getDictLabel()).append(SEPARATOR);
}
return StringUtils.stripEnd(propertyString.toString(), SEPARATOR);
}
/** /**
* 删除指定字典缓存 * 删除指定字典缓存
* *

View File

@ -1,9 +1,15 @@
package com.ruoyi.common.utils; package com.ruoyi.common.utils;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.util.PatternMatchUtils;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.HttpStatus; import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
@ -14,6 +20,7 @@ import com.ruoyi.common.exception.ServiceException;
*/ */
public class SecurityUtils public class SecurityUtils
{ {
/** /**
* 用户ID * 用户ID
**/ **/
@ -43,7 +50,7 @@ public class SecurityUtils
throw new ServiceException("获取部门ID异常", HttpStatus.UNAUTHORIZED); throw new ServiceException("获取部门ID异常", HttpStatus.UNAUTHORIZED);
} }
} }
/** /**
* 获取用户账户 * 获取用户账户
**/ **/
@ -117,4 +124,55 @@ public class SecurityUtils
{ {
return userId != null && 1L == userId; return userId != null && 1L == userId;
} }
/**
* 验证用户是否具备某权限
*
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public static boolean hasPermi(String permission)
{
return hasPermi(getLoginUser().getPermissions(), permission);
}
/**
* 判断是否包含权限
*
* @param authorities 权限列表
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public static boolean hasPermi(Collection<String> authorities, String permission)
{
return authorities.stream().filter(StringUtils::hasText)
.anyMatch(x -> Constants.ALL_PERMISSION.equals(x) || PatternMatchUtils.simpleMatch(x, permission));
}
/**
* 验证用户是否拥有某个角色
*
* @param role 角色标识
* @return 用户是否具备某角色
*/
public static boolean hasRole(String role)
{
List<SysRole> roleList = getLoginUser().getUser().getRoles();
Collection<String> roles = roleList.stream().map(SysRole::getRoleKey).collect(Collectors.toSet());
return hasRole(roles, role);
}
/**
* 判断是否包含角色
*
* @param roles 角色列表
* @param role 角色
* @return 用户是否具备某角色权限
*/
public static boolean hasRole(Collection<String> roles, String role)
{
return roles.stream().filter(StringUtils::hasText)
.anyMatch(x -> Constants.SUPER_ADMIN.equals(x) || PatternMatchUtils.simpleMatch(x, role));
}
} }

View File

@ -4,6 +4,10 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
@ -68,6 +72,34 @@ public class ServletUtils
return Convert.toBool(getRequest().getParameter(name), defaultValue); return Convert.toBool(getRequest().getParameter(name), defaultValue);
} }
/**
* 获得所有请求参数
*
* @param request 请求对象{@link ServletRequest}
* @return Map
*/
public static Map<String, String[]> getParams(ServletRequest request)
{
final Map<String, String[]> map = request.getParameterMap();
return Collections.unmodifiableMap(map);
}
/**
* 获得所有请求参数
*
* @param request 请求对象{@link ServletRequest}
* @return Map
*/
public static Map<String, String> getParamMap(ServletRequest request)
{
Map<String, String> params = new HashMap<>();
for (Map.Entry<String, String[]> entry : getParams(request).entrySet())
{
params.put(entry.getKey(), StringUtils.join(entry.getValue(), ","));
}
return params;
}
/** /**
* 获取request * 获取request
*/ */

View File

@ -23,6 +23,9 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/** 下划线 */ /** 下划线 */
private static final char SEPARATOR = '_'; private static final char SEPARATOR = '_';
/** 星号 */
private static final char ASTERISK = '*';
/** /**
* 获取参数不为空值 * 获取参数不为空值
* *
@ -163,6 +166,49 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
return (str == null ? "" : str.trim()); return (str == null ? "" : str.trim());
} }
/**
* 替换指定字符串的指定区间内字符为"*"
*
* @param str 字符串
* @param startInclude 开始位置(包含)
* @param endExclude 结束位置(不包含)
* @return 替换后的字符串
*/
public static String hide(CharSequence str, int startInclude, int endExclude)
{
if (isEmpty(str))
{
return NULLSTR;
}
final int strLength = str.length();
if (startInclude > strLength)
{
return NULLSTR;
}
if (endExclude > strLength)
{
endExclude = strLength;
}
if (startInclude > endExclude)
{
// 如果起始位置大于结束位置,不替换
return NULLSTR;
}
final char[] chars = new char[strLength];
for (int i = 0; i < strLength; i++)
{
if (i >= startInclude && i < endExclude)
{
chars[i] = ASTERISK;
}
else
{
chars[i] = str.charAt(i);
}
}
return new String(chars);
}
/** /**
* 截取字符串 * 截取字符串
* *
@ -240,6 +286,30 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
return str.substring(start, end); return str.substring(start, end);
} }
/**
* 判断是否为空,并且不是空白字符
*
* @param str 要判断的value
* @return 结果
*/
public static boolean hasText(String str)
{
return (str != null && !str.isEmpty() && containsText(str));
}
private static boolean containsText(CharSequence str)
{
int strLen = str.length();
for (int i = 0; i < strLen; i++)
{
if (!Character.isWhitespace(str.charAt(i)))
{
return true;
}
}
return false;
}
/** /**
* 格式化文本, {} 表示占位符<br> * 格式化文本, {} 表示占位符<br>
* 此方法只是简单将占位符 {} 按照顺序替换为参数<br> * 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
@ -324,6 +394,32 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
return list; return list;
} }
/**
* 判断给定的collection列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value
*
* @param collection 给定的集合
* @param array 给定的数组
* @return boolean 结果
*/
public static boolean containsAny(Collection<String> collection, String... array)
{
if (isEmpty(collection) || isEmpty(array))
{
return false;
}
else
{
for (String str : array)
{
if (collection.contains(str))
{
return true;
}
}
return false;
}
}
/** /**
* 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写 * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写
* *
@ -455,7 +551,8 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
} }
/** /**
* 驼峰式命名法 例如user_name->userName * 驼峰式命名法
* 例如user_name->userName
*/ */
public static String toCamelCase(String s) public static String toCamelCase(String s)
{ {
@ -463,6 +560,10 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
{ {
return null; return null;
} }
if (s.indexOf(SEPARATOR) == -1)
{
return s;
}
s = s.toLowerCase(); s = s.toLowerCase();
StringBuilder sb = new StringBuilder(s.length()); StringBuilder sb = new StringBuilder(s.length());
boolean upperCase = false; boolean upperCase = false;

View File

@ -25,7 +25,7 @@ public class FileUploadUtils
/** /**
* 默认大小 50M * 默认大小 50M
*/ */
public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024; public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024L;
/** /**
* 默认的文件名最大长度 100 * 默认的文件名最大长度 100

View File

@ -116,8 +116,7 @@ public class FileUtils
// 路径为文件且不为空则进行删除 // 路径为文件且不为空则进行删除
if (file.isFile() && file.exists()) if (file.isFile() && file.exists())
{ {
file.delete(); flag = file.delete();
flag = true;
} }
return flag; return flag;
} }
@ -289,5 +288,4 @@ public class FileUtils
String baseName = FilenameUtils.getBaseName(fileName); String baseName = FilenameUtils.getBaseName(fileName);
return baseName; return baseName;
} }
} }

View File

@ -387,7 +387,7 @@ public final class HTMLFilter
{ {
paramValue = processParamProtocol(paramValue); paramValue = processParamProtocol(paramValue);
} }
params.append(' ').append(paramName).append("=\\\"").append(paramValue).append("\""); params.append(' ').append(paramName).append("=\\\"").append(paramValue).append("\\\"");
} }
} }

View File

@ -74,7 +74,7 @@ public class HttpUtils
URLConnection connection = realUrl.openConnection(); URLConnection connection = realUrl.openConnection();
connection.setRequestProperty("accept", "*/*"); connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive"); connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); connection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
connection.connect(); connection.connect();
in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType)); in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType));
String line; String line;
@ -136,7 +136,7 @@ public class HttpUtils
URLConnection conn = realUrl.openConnection(); URLConnection conn = realUrl.openConnection();
conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
conn.setRequestProperty("Accept-Charset", "utf-8"); conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setRequestProperty("contentType", "utf-8"); conn.setRequestProperty("contentType", "utf-8");
conn.setDoOutput(true); conn.setDoOutput(true);
@ -202,7 +202,7 @@ public class HttpUtils
HttpsURLConnection conn = (HttpsURLConnection) console.openConnection(); HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
conn.setRequestProperty("Accept-Charset", "utf-8"); conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setRequestProperty("contentType", "utf-8"); conn.setRequestProperty("contentType", "utf-8");
conn.setDoOutput(true); conn.setDoOutput(true);

View File

@ -3,6 +3,7 @@ package com.ruoyi.common.utils.ip;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
/** /**
@ -12,6 +13,23 @@ import com.ruoyi.common.utils.StringUtils;
*/ */
public class IpUtils public class IpUtils
{ {
public final static String REGX_0_255 = "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)";
// 匹配 ip
public final static String REGX_IP = "((" + REGX_0_255 + "\\.){3}" + REGX_0_255 + ")";
public final static String REGX_IP_WILDCARD = "(((\\*\\.){3}\\*)|(" + REGX_0_255 + "(\\.\\*){3})|(" + REGX_0_255 + "\\." + REGX_0_255 + ")(\\.\\*){2}" + "|((" + REGX_0_255 + "\\.){3}\\*))";
// 匹配网段
public final static String REGX_IP_SEG = "(" + REGX_IP + "\\-" + REGX_IP + ")";
/**
* 获取客户端IP
*
* @return IP地址
*/
public static String getIpAddr()
{
return getIpAddr(ServletUtils.getRequest());
}
/** /**
* 获取客户端IP * 获取客户端IP
* *
@ -248,7 +266,7 @@ public class IpUtils
} }
} }
} }
return ip; return StringUtils.substring(ip, 0, 255);
} }
/** /**
@ -261,4 +279,104 @@ public class IpUtils
{ {
return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
} }
/**
* 是否为IP
*/
public static boolean isIP(String ip)
{
return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP);
}
/**
* 是否为IP或 *为间隔的通配符地址
*/
public static boolean isIpWildCard(String ip)
{
return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP_WILDCARD);
}
/**
* 检测参数是否在ip通配符里
*/
public static boolean ipIsInWildCardNoCheck(String ipWildCard, String ip)
{
String[] s1 = ipWildCard.split("\\.");
String[] s2 = ip.split("\\.");
boolean isMatchedSeg = true;
for (int i = 0; i < s1.length && !s1[i].equals("*"); i++)
{
if (!s1[i].equals(s2[i]))
{
isMatchedSeg = false;
break;
}
}
return isMatchedSeg;
}
/**
* 是否为特定格式如:“10.10.10.1-10.10.10.99”的ip段字符串
*/
public static boolean isIPSegment(String ipSeg)
{
return StringUtils.isNotBlank(ipSeg) && ipSeg.matches(REGX_IP_SEG);
}
/**
* 判断ip是否在指定网段中
*/
public static boolean ipIsInNetNoCheck(String iparea, String ip)
{
int idx = iparea.indexOf('-');
String[] sips = iparea.substring(0, idx).split("\\.");
String[] sipe = iparea.substring(idx + 1).split("\\.");
String[] sipt = ip.split("\\.");
long ips = 0L, ipe = 0L, ipt = 0L;
for (int i = 0; i < 4; ++i)
{
ips = ips << 8 | Integer.parseInt(sips[i]);
ipe = ipe << 8 | Integer.parseInt(sipe[i]);
ipt = ipt << 8 | Integer.parseInt(sipt[i]);
}
if (ips > ipe)
{
long t = ips;
ips = ipe;
ipe = t;
}
return ips <= ipt && ipt <= ipe;
}
/**
* 校验ip是否符合过滤串规则
*
* @param filter 过滤IP列表,支持后缀'*'通配,支持网段如:`10.10.10.1-10.10.10.99`
* @param ip 校验IP地址
* @return boolean 结果
*/
public static boolean isMatchedIp(String filter, String ip)
{
if (StringUtils.isEmpty(filter) || StringUtils.isEmpty(ip))
{
return false;
}
String[] ips = filter.split(";");
for (String iStr : ips)
{
if (isIP(iStr) && iStr.equals(ip))
{
return true;
}
else if (isIpWildCard(iStr) && ipIsInWildCardNoCheck(iStr, ip))
{
return true;
}
else if (isIPSegment(iStr) && ipIsInNetNoCheck(iStr, ip))
{
return true;
}
}
return false;
}
} }

View File

@ -1,5 +1,8 @@
package com.ruoyi.common.utils.poi; package com.ruoyi.common.utils.poi;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Workbook;
/** /**
* Excel数据格式处理适配器 * Excel数据格式处理适配器
* *
@ -12,8 +15,10 @@ public interface ExcelHandlerAdapter
* *
* @param value 单元格数据值 * @param value 单元格数据值
* @param args excel注解args参数组 * @param args excel注解args参数组
* @param cell 单元格对象
* @param wb 工作簿对象
* *
* @return 处理后的值 * @return 处理后的值
*/ */
Object format(Object value, String[] args); Object format(Object value, String[] args, Cell cell, Workbook wb);
} }

View File

@ -7,12 +7,14 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
@ -22,7 +24,9 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.RegExUtils; import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.poi.hssf.usermodel.HSSFClientAnchor; import org.apache.poi.hssf.usermodel.HSSFClientAnchor;
import org.apache.poi.hssf.usermodel.HSSFPicture; import org.apache.poi.hssf.usermodel.HSSFPicture;
import org.apache.poi.hssf.usermodel.HSSFPictureData; import org.apache.poi.hssf.usermodel.HSSFPictureData;
@ -35,6 +39,7 @@ import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.ClientAnchor; import org.apache.poi.ss.usermodel.ClientAnchor;
import org.apache.poi.ss.usermodel.DataFormat;
import org.apache.poi.ss.usermodel.DataValidation; import org.apache.poi.ss.usermodel.DataValidation;
import org.apache.poi.ss.usermodel.DataValidationConstraint; import org.apache.poi.ss.usermodel.DataValidationConstraint;
import org.apache.poi.ss.usermodel.DataValidationHelper; import org.apache.poi.ss.usermodel.DataValidationHelper;
@ -44,6 +49,7 @@ import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Font; import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors; import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Name;
import org.apache.poi.ss.usermodel.PictureData; import org.apache.poi.ss.usermodel.PictureData;
import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Sheet;
@ -93,6 +99,11 @@ public class ExcelUtil<T>
public static final String[] FORMULA_STR = { "=", "-", "+", "@" }; public static final String[] FORMULA_STR = { "=", "-", "+", "@" };
/**
* 用于dictType属性数据存储避免重复查缓存
*/
public Map<String, String> sysDictMap = new HashMap<String, String>();
/** /**
* Excel sheet最大行数默认65536 * Excel sheet最大行数默认65536
*/ */
@ -148,6 +159,26 @@ public class ExcelUtil<T>
*/ */
private short maxHeight; private short maxHeight;
/**
* 合并后最后行数
*/
private int subMergedLastRowNum = 0;
/**
* 合并后开始行数
*/
private int subMergedFirstRowNum = 1;
/**
* 对象的子列表方法
*/
private Method subMethod;
/**
* 对象的子列表属性
*/
private List<Field> subFields;
/** /**
* 统计列表 * 统计列表
*/ */
@ -163,11 +194,27 @@ public class ExcelUtil<T>
*/ */
public Class<T> clazz; public Class<T> clazz;
/**
* 需要排除列属性
*/
public String[] excludeFields;
public ExcelUtil(Class<T> clazz) public ExcelUtil(Class<T> clazz)
{ {
this.clazz = clazz; this.clazz = clazz;
} }
/**
* 隐藏Excel中列属性
*
* @param fields 列属性名 示例[单个"name"/多个"id","name"]
* @throws Exception
*/
public void hideColumn(String... fields)
{
this.excludeFields = fields;
}
public void init(List<T> list, String sheetName, String title, Type type) public void init(List<T> list, String sheetName, String title, Type type)
{ {
if (list == null) if (list == null)
@ -181,6 +228,7 @@ public class ExcelUtil<T>
createExcelField(); createExcelField();
createWorkbook(); createWorkbook();
createTitle(); createTitle();
createSubHead();
} }
/** /**
@ -190,13 +238,48 @@ public class ExcelUtil<T>
{ {
if (StringUtils.isNotEmpty(title)) if (StringUtils.isNotEmpty(title))
{ {
subMergedFirstRowNum++;
subMergedLastRowNum++;
int titleLastCol = this.fields.size() - 1;
if (isSubList())
{
titleLastCol = titleLastCol + subFields.size() - 1;
}
Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0); Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0);
titleRow.setHeightInPoints(30); titleRow.setHeightInPoints(30);
Cell titleCell = titleRow.createCell(0); Cell titleCell = titleRow.createCell(0);
titleCell.setCellStyle(styles.get("title")); titleCell.setCellStyle(styles.get("title"));
titleCell.setCellValue(title); titleCell.setCellValue(title);
sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), titleRow.getRowNum(), sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), titleRow.getRowNum(), titleLastCol));
this.fields.size() - 1)); }
}
/**
* 创建对象的子列表名称
*/
public void createSubHead()
{
if (isSubList())
{
subMergedFirstRowNum++;
subMergedLastRowNum++;
Row subRow = sheet.createRow(rownum);
int excelNum = 0;
for (Object[] objects : fields)
{
Excel attr = (Excel) objects[1];
Cell headCell1 = subRow.createCell(excelNum);
headCell1.setCellValue(attr.name());
headCell1.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor())));
excelNum++;
}
int headFirstRow = excelNum - 1;
int headLastRow = headFirstRow + subFields.size() - 1;
if (headLastRow > headFirstRow)
{
sheet.addMergedRegion(new CellRangeAddress(rownum, rownum, headFirstRow, headLastRow));
}
rownum++;
} }
} }
@ -206,9 +289,23 @@ public class ExcelUtil<T>
* @param is 输入流 * @param is 输入流
* @return 转换后集合 * @return 转换后集合
*/ */
public List<T> importExcel(InputStream is) throws Exception public List<T> importExcel(InputStream is)
{ {
return importExcel(is, 0); List<T> list = null;
try
{
list = importExcel(is, 0);
}
catch (Exception e)
{
log.error("导入Excel异常{}", e.getMessage());
throw new UtilException(e.getMessage());
}
finally
{
IOUtils.closeQuietly(is);
}
return list;
} }
/** /**
@ -254,7 +351,6 @@ public class ExcelUtil<T>
} }
// 获取最后一个非空行的行下标比如总行数为n则返回的为n-1 // 获取最后一个非空行的行下标比如总行数为n则返回的为n-1
int rows = sheet.getLastRowNum(); int rows = sheet.getLastRowNum();
if (rows > 0) if (rows > 0)
{ {
// 定义一个map用于存放excel列的序号和field. // 定义一个map用于存放excel列的序号和field.
@ -369,17 +465,22 @@ public class ExcelUtil<T>
{ {
propertyName = field.getName() + "." + attr.targetAttr(); propertyName = field.getName() + "." + attr.targetAttr();
} }
else if (StringUtils.isNotEmpty(attr.readConverterExp())) if (StringUtils.isNotEmpty(attr.readConverterExp()))
{ {
val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator()); val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());
} }
else if (StringUtils.isNotEmpty(attr.dictType())) else if (StringUtils.isNotEmpty(attr.dictType()))
{ {
val = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator()); if (!sysDictMap.containsKey(attr.dictType() + val))
{
String dictValue = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator());
sysDictMap.put(attr.dictType() + val, dictValue);
}
val = sysDictMap.get(attr.dictType() + val);
} }
else if (!attr.handler().equals(ExcelHandlerAdapter.class)) else if (!attr.handler().equals(ExcelHandlerAdapter.class))
{ {
val = dataFormatHandlerAdapter(val, attr); val = dataFormatHandlerAdapter(val, attr, null);
} }
else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures)) else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures))
{ {
@ -576,8 +677,20 @@ public class ExcelUtil<T>
// 写入各个字段的列头名称 // 写入各个字段的列头名称
for (Object[] os : fields) for (Object[] os : fields)
{ {
Field field = (Field) os[0];
Excel excel = (Excel) os[1]; Excel excel = (Excel) os[1];
this.createCell(excel, row, column++); if (Collection.class.isAssignableFrom(field.getType()))
{
for (Field subField : subFields)
{
Excel subExcel = subField.getAnnotation(Excel.class);
this.createHeadCell(subExcel, row, column++);
}
}
else
{
this.createHeadCell(excel, row, column++);
}
} }
if (Type.EXPORT.equals(type)) if (Type.EXPORT.equals(type))
{ {
@ -593,21 +706,67 @@ public class ExcelUtil<T>
* @param index 序号 * @param index 序号
* @param row 单元格行 * @param row 单元格行
*/ */
@SuppressWarnings("unchecked")
public void fillExcelData(int index, Row row) public void fillExcelData(int index, Row row)
{ {
int startNo = index * sheetSize; int startNo = index * sheetSize;
int endNo = Math.min(startNo + sheetSize, list.size()); int endNo = Math.min(startNo + sheetSize, list.size());
int rowNo = (1 + rownum) - startNo;
for (int i = startNo; i < endNo; i++) for (int i = startNo; i < endNo; i++)
{ {
row = sheet.createRow(i + 1 + rownum - startNo); rowNo = isSubList() ? (i > 1 ? rowNo + 1 : rowNo + i) : i + 1 + rownum - startNo;
row = sheet.createRow(rowNo);
// 得到导出对象. // 得到导出对象.
T vo = (T) list.get(i); T vo = (T) list.get(i);
Collection<?> subList = null;
if (isSubList())
{
if (isSubListValue(vo))
{
subList = getListCellValue(vo);
subMergedLastRowNum = subMergedLastRowNum + subList.size();
}
else
{
subMergedFirstRowNum++;
subMergedLastRowNum++;
}
}
int column = 0; int column = 0;
for (Object[] os : fields) for (Object[] os : fields)
{ {
Field field = (Field) os[0]; Field field = (Field) os[0];
Excel excel = (Excel) os[1]; Excel excel = (Excel) os[1];
this.addCell(excel, row, vo, field, column++); if (Collection.class.isAssignableFrom(field.getType()) && StringUtils.isNotNull(subList))
{
boolean subFirst = false;
for (Object obj : subList)
{
if (subFirst)
{
rowNo++;
row = sheet.createRow(rowNo);
}
List<Field> subFields = FieldUtils.getFieldsListWithAnnotation(obj.getClass(), Excel.class);
int subIndex = 0;
for (Field subField : subFields)
{
if (subField.isAnnotationPresent(Excel.class))
{
subField.setAccessible(true);
Excel attr = subField.getAnnotation(Excel.class);
this.addCell(attr, row, (T) obj, subField, column + subIndex);
}
subIndex++;
}
subFirst = true;
}
this.subMergedFirstRowNum = this.subMergedFirstRowNum + subList.size();
}
else
{
this.addCell(excel, row, vo, field, column++);
}
} }
} }
} }
@ -630,6 +789,8 @@ public class ExcelUtil<T>
titleFont.setFontHeightInPoints((short) 16); titleFont.setFontHeightInPoints((short) 16);
titleFont.setBold(true); titleFont.setBold(true);
style.setFont(titleFont); style.setFont(titleFont);
DataFormat dataFormat = wb.createDataFormat();
style.setDataFormat(dataFormat.getFormat("@"));
styles.put("title", style); styles.put("title", style);
style = wb.createCellStyle(); style = wb.createCellStyle();
@ -649,20 +810,6 @@ public class ExcelUtil<T>
style.setFont(dataFont); style.setFont(dataFont);
styles.put("data", style); styles.put("data", style);
style = wb.createCellStyle();
style.cloneStyleFrom(styles.get("data"));
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
Font headerFont = wb.createFont();
headerFont.setFontName("Arial");
headerFont.setFontHeightInPoints((short) 10);
headerFont.setBold(true);
headerFont.setColor(IndexedColors.WHITE.getIndex());
style.setFont(headerFont);
styles.put("header", style);
style = wb.createCellStyle(); style = wb.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER); style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER);
@ -672,60 +819,140 @@ public class ExcelUtil<T>
style.setFont(totalFont); style.setFont(totalFont);
styles.put("total", style); styles.put("total", style);
styles.putAll(annotationStyles(wb)); styles.putAll(annotationHeaderStyles(wb, styles));
styles.putAll(annotationDataStyles(wb));
return styles; return styles;
} }
/** /**
* 根据Excel注解创建表格样式 * 根据Excel注解创建表格样式
* *
* @param wb 工作薄对象 * @param wb 工作薄对象
* @return 自定义样式列表 * @return 自定义样式列表
*/ */
private Map<String, CellStyle> annotationStyles(Workbook wb) private Map<String, CellStyle> annotationHeaderStyles(Workbook wb, Map<String, CellStyle> styles)
{
Map<String, CellStyle> headerStyles = new HashMap<String, CellStyle>();
for (Object[] os : fields)
{
Excel excel = (Excel) os[1];
String key = StringUtils.format("header_{}_{}", excel.headerColor(), excel.headerBackgroundColor());
if (!headerStyles.containsKey(key))
{
CellStyle style = wb.createCellStyle();
style.cloneStyleFrom(styles.get("data"));
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setFillForegroundColor(excel.headerBackgroundColor().index);
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
Font headerFont = wb.createFont();
headerFont.setFontName("Arial");
headerFont.setFontHeightInPoints((short) 10);
headerFont.setBold(true);
headerFont.setColor(excel.headerColor().index);
style.setFont(headerFont);
// 设置表格头单元格文本形式
DataFormat dataFormat = wb.createDataFormat();
style.setDataFormat(dataFormat.getFormat("@"));
headerStyles.put(key, style);
}
}
return headerStyles;
}
/**
* 根据Excel注解创建表格列样式
*
* @param wb 工作薄对象
* @return 自定义样式列表
*/
private Map<String, CellStyle> annotationDataStyles(Workbook wb)
{ {
Map<String, CellStyle> styles = new HashMap<String, CellStyle>(); Map<String, CellStyle> styles = new HashMap<String, CellStyle>();
for (Object[] os : fields) for (Object[] os : fields)
{ {
Field field = (Field) os[0];
Excel excel = (Excel) os[1]; Excel excel = (Excel) os[1];
String key = "data_" + excel.align() + "_" + excel.color(); if (Collection.class.isAssignableFrom(field.getType()))
if (!styles.containsKey(key))
{ {
CellStyle style = wb.createCellStyle(); ParameterizedType pt = (ParameterizedType) field.getGenericType();
style = wb.createCellStyle(); Class<?> subClass = (Class<?>) pt.getActualTypeArguments()[0];
style.setAlignment(excel.align()); List<Field> subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class);
style.setVerticalAlignment(VerticalAlignment.CENTER); for (Field subField : subFields)
style.setBorderRight(BorderStyle.THIN); {
style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); Excel subExcel = subField.getAnnotation(Excel.class);
style.setBorderLeft(BorderStyle.THIN); annotationDataStyles(styles, subField, subExcel);
style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); }
style.setBorderTop(BorderStyle.THIN); }
style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); else
style.setBorderBottom(BorderStyle.THIN); {
style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); annotationDataStyles(styles, field, excel);
Font dataFont = wb.createFont();
dataFont.setFontName("Arial");
dataFont.setFontHeightInPoints((short) 10);
dataFont.setColor(excel.color().index);
style.setFont(dataFont);
styles.put(key, style);
} }
} }
return styles; return styles;
} }
/**
* 根据Excel注解创建表格列样式
*
* @param styles 自定义样式列表
* @param field 属性列信息
* @param excel 注解信息
*/
public void annotationDataStyles(Map<String, CellStyle> styles, Field field, Excel excel)
{
String key = StringUtils.format("data_{}_{}_{}_{}", excel.align(), excel.color(), excel.backgroundColor(), excel.cellType());
if (!styles.containsKey(key))
{
CellStyle style = wb.createCellStyle();
style.setAlignment(excel.align());
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setBorderRight(BorderStyle.THIN);
style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderLeft(BorderStyle.THIN);
style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderTop(BorderStyle.THIN);
style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderBottom(BorderStyle.THIN);
style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
style.setFillForegroundColor(excel.backgroundColor().getIndex());
Font dataFont = wb.createFont();
dataFont.setFontName("Arial");
dataFont.setFontHeightInPoints((short) 10);
dataFont.setColor(excel.color().index);
style.setFont(dataFont);
if (ColumnType.TEXT == excel.cellType())
{
DataFormat dataFormat = wb.createDataFormat();
style.setDataFormat(dataFormat.getFormat("@"));
}
styles.put(key, style);
}
}
/** /**
* 创建单元格 * 创建单元格
*/ */
public Cell createCell(Excel attr, Row row, int column) public Cell createHeadCell(Excel attr, Row row, int column)
{ {
// 创建列 // 创建列
Cell cell = row.createCell(column); Cell cell = row.createCell(column);
// 写入列信息 // 写入列信息
cell.setCellValue(attr.name()); cell.setCellValue(attr.name());
setDataValidation(attr, row, column); setDataValidation(attr, row, column);
cell.setCellStyle(styles.get("header")); cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor())));
if (isSubList())
{
// 填充默认样式,防止合并单元格样式失效
sheet.setDefaultColumnStyle(column, styles.get(StringUtils.format("data_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType())));
if (attr.needMerge())
{
sheet.addMergedRegion(new CellRangeAddress(rownum - 1, rownum, column, column));
}
}
return cell; return cell;
} }
@ -738,7 +965,7 @@ public class ExcelUtil<T>
*/ */
public void setCellVo(Object value, Excel attr, Cell cell) public void setCellVo(Object value, Excel attr, Cell cell)
{ {
if (ColumnType.STRING == attr.cellType()) if (ColumnType.STRING == attr.cellType() || ColumnType.TEXT == attr.cellType())
{ {
String cellValue = Convert.toStr(value); String cellValue = Convert.toStr(value);
// 对于任何以表达式触发字符 =-+@开头的单元格直接使用tab字符作为前缀防止CSV注入。 // 对于任何以表达式触发字符 =-+@开头的单元格直接使用tab字符作为前缀防止CSV注入。
@ -746,6 +973,10 @@ public class ExcelUtil<T>
{ {
cellValue = RegExUtils.replaceFirst(cellValue, FORMULA_REGEX_STR, "\t$0"); cellValue = RegExUtils.replaceFirst(cellValue, FORMULA_REGEX_STR, "\t$0");
} }
if (value instanceof Collection && StringUtils.equals("[]", cellValue))
{
cellValue = StringUtils.EMPTY;
}
cell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue + attr.suffix()); cell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue + attr.suffix());
} }
else if (ColumnType.NUMERIC == attr.cellType()) else if (ColumnType.NUMERIC == attr.cellType())
@ -811,10 +1042,29 @@ public class ExcelUtil<T>
// 设置列宽 // 设置列宽
sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256)); sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256));
} }
if (StringUtils.isNotEmpty(attr.prompt()) || attr.combo().length > 0) if (StringUtils.isNotEmpty(attr.prompt()) || attr.combo().length > 0 || attr.comboReadDict())
{ {
// 提示信息或只能选择不能输入的列内容. String[] comboArray = attr.combo();
setPromptOrValidation(sheet, attr.combo(), attr.prompt(), 1, 100, column, column); if (attr.comboReadDict())
{
if (!sysDictMap.containsKey("combo_" + attr.dictType()))
{
String labels = DictUtils.getDictLabels(attr.dictType());
sysDictMap.put("combo_" + attr.dictType(), labels);
}
String val = sysDictMap.get("combo_" + attr.dictType());
comboArray = StringUtils.split(val, DictUtils.SEPARATOR);
}
if (comboArray.length > 15 || StringUtils.join(comboArray).length() > 255)
{
// 如果下拉数大于15或字符串长度大于255则使用一个新sheet存储避免生成的模板下拉值获取不到
setXSSFValidationWithHidden(sheet, comboArray, attr.prompt(), 1, 100, column, column);
}
else
{
// 提示信息或只能选择不能输入的列内容.
setPromptOrValidation(sheet, comboArray, attr.prompt(), 1, 100, column, column);
}
} }
} }
@ -833,7 +1083,12 @@ public class ExcelUtil<T>
{ {
// 创建cell // 创建cell
cell = row.createCell(column); cell = row.createCell(column);
cell.setCellStyle(styles.get("data_" + attr.align() + "_" + attr.color())); if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge())
{
CellRangeAddress cellAddress = new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column);
sheet.addMergedRegion(cellAddress);
}
cell.setCellStyle(styles.get(StringUtils.format("data_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType())));
// 用于读取对象中的属性 // 用于读取对象中的属性
Object value = getTargetValue(vo, field, attr); Object value = getTargetValue(vo, field, attr);
@ -851,15 +1106,20 @@ public class ExcelUtil<T>
} }
else if (StringUtils.isNotEmpty(dictType) && StringUtils.isNotNull(value)) else if (StringUtils.isNotEmpty(dictType) && StringUtils.isNotNull(value))
{ {
cell.setCellValue(convertDictByExp(Convert.toStr(value), dictType, separator)); if (!sysDictMap.containsKey(dictType + value))
{
String lable = convertDictByExp(Convert.toStr(value), dictType, separator);
sysDictMap.put(dictType + value, lable);
}
cell.setCellValue(sysDictMap.get(dictType + value));
} }
else if (value instanceof BigDecimal && -1 != attr.scale()) else if (value instanceof BigDecimal && -1 != attr.scale())
{ {
cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).toString()); cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).doubleValue());
} }
else if (!attr.handler().equals(ExcelHandlerAdapter.class)) else if (!attr.handler().equals(ExcelHandlerAdapter.class))
{ {
cell.setCellValue(dataFormatHandlerAdapter(value, attr)); cell.setCellValue(dataFormatHandlerAdapter(value, attr, cell));
} }
else else
{ {
@ -913,6 +1173,58 @@ public class ExcelUtil<T>
sheet.addValidationData(dataValidation); sheet.addValidationData(dataValidation);
} }
/**
* 设置某些列的值只能输入预制的数据,显示下拉框(兼容超出一定数量的下拉框).
*
* @param sheet 要设置的sheet.
* @param textlist 下拉框显示的内容
* @param promptContent 提示内容
* @param firstRow 开始行
* @param endRow 结束行
* @param firstCol 开始列
* @param endCol 结束列
*/
public void setXSSFValidationWithHidden(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, int firstCol, int endCol)
{
String hideSheetName = "combo_" + firstCol + "_" + endCol;
Sheet hideSheet = wb.createSheet(hideSheetName); // 用于存储 下拉菜单数据
for (int i = 0; i < textlist.length; i++)
{
hideSheet.createRow(i).createCell(0).setCellValue(textlist[i]);
}
// 创建名称,可被其他单元格引用
Name name = wb.createName();
name.setNameName(hideSheetName + "_data");
name.setRefersToFormula(hideSheetName + "!$A$1:$A$" + textlist.length);
DataValidationHelper helper = sheet.getDataValidationHelper();
// 加载下拉列表内容
DataValidationConstraint constraint = helper.createFormulaListConstraint(hideSheetName + "_data");
// 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列
CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);
// 数据有效性对象
DataValidation dataValidation = helper.createValidation(constraint, regions);
if (StringUtils.isNotEmpty(promptContent))
{
// 如果设置了提示信息则鼠标放上去提示
dataValidation.createPromptBox("", promptContent);
dataValidation.setShowPromptBox(true);
}
// 处理Excel兼容性问题
if (dataValidation instanceof XSSFDataValidation)
{
dataValidation.setSuppressDropDownArrow(true);
dataValidation.setShowErrorBox(true);
}
else
{
dataValidation.setSuppressDropDownArrow(false);
}
sheet.addValidationData(dataValidation);
// 设置hiddenSheet隐藏
wb.setSheetHidden(wb.getSheetIndex(hideSheet), true);
}
/** /**
* 解析导出值 0=男,1=女,2=未知 * 解析导出值 0=男,1=女,2=未知
* *
@ -928,7 +1240,7 @@ public class ExcelUtil<T>
for (String item : convertSource) for (String item : convertSource)
{ {
String[] itemArray = item.split("="); String[] itemArray = item.split("=");
if (StringUtils.containsAny(separator, propertyValue)) if (StringUtils.containsAny(propertyValue, separator))
{ {
for (String value : propertyValue.split(separator)) for (String value : propertyValue.split(separator))
{ {
@ -965,7 +1277,7 @@ public class ExcelUtil<T>
for (String item : convertSource) for (String item : convertSource)
{ {
String[] itemArray = item.split("="); String[] itemArray = item.split("=");
if (StringUtils.containsAny(separator, propertyValue)) if (StringUtils.containsAny(propertyValue, separator))
{ {
for (String value : propertyValue.split(separator)) for (String value : propertyValue.split(separator))
{ {
@ -1020,13 +1332,13 @@ public class ExcelUtil<T>
* @param excel 数据注解 * @param excel 数据注解
* @return * @return
*/ */
public String dataFormatHandlerAdapter(Object value, Excel excel) public String dataFormatHandlerAdapter(Object value, Excel excel, Cell cell)
{ {
try try
{ {
Object instance = excel.handler().newInstance(); Object instance = excel.handler().newInstance();
Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class }); Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class, Cell.class, Workbook.class });
value = formatMethod.invoke(instance, value, excel.args()); value = formatMethod.invoke(instance, value, excel.args(), cell, this.wb);
} }
catch (Exception e) catch (Exception e)
{ {
@ -1086,7 +1398,7 @@ public class ExcelUtil<T>
*/ */
public String encodingFilename(String filename) public String encodingFilename(String filename)
{ {
filename = UUID.randomUUID().toString() + "_" + filename + ".xlsx"; filename = UUID.randomUUID() + "_" + filename + ".xlsx";
return filename; return filename;
} }
@ -1178,29 +1490,40 @@ public class ExcelUtil<T>
tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
for (Field field : tempFields) for (Field field : tempFields)
{ {
// 单注解 if (!ArrayUtils.contains(this.excludeFields, field.getName()))
if (field.isAnnotationPresent(Excel.class))
{ {
Excel attr = field.getAnnotation(Excel.class); // 单注解
if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) if (field.isAnnotationPresent(Excel.class))
{
field.setAccessible(true);
fields.add(new Object[] { field, attr });
}
}
// 多注解
if (field.isAnnotationPresent(Excels.class))
{
Excels attrs = field.getAnnotation(Excels.class);
Excel[] excels = attrs.value();
for (Excel attr : excels)
{ {
Excel attr = field.getAnnotation(Excel.class);
if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
{ {
field.setAccessible(true); field.setAccessible(true);
fields.add(new Object[] { field, attr }); fields.add(new Object[] { field, attr });
} }
if (Collection.class.isAssignableFrom(field.getType()))
{
subMethod = getSubMethod(field.getName(), clazz);
ParameterizedType pt = (ParameterizedType) field.getGenericType();
Class<?> subClass = (Class<?>) pt.getActualTypeArguments()[0];
this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class);
}
}
// 多注解
if (field.isAnnotationPresent(Excels.class))
{
Excels attrs = field.getAnnotation(Excels.class);
Excel[] excels = attrs.value();
for (Excel attr : excels)
{
if (!ArrayUtils.contains(this.excludeFields, field.getName() + "." + attr.targetAttr())
&& (attr != null && (attr.type() == Type.ALL || attr.type() == type)))
{
field.setAccessible(true);
fields.add(new Object[] { field, attr });
}
}
} }
} }
} }
@ -1353,7 +1676,7 @@ public class ExcelUtil<T>
HSSFPicture pic = (HSSFPicture) shape; HSSFPicture pic = (HSSFPicture) shape;
int pictureIndex = pic.getPictureIndex() - 1; int pictureIndex = pic.getPictureIndex() - 1;
HSSFPictureData picData = pictures.get(pictureIndex); HSSFPictureData picData = pictures.get(pictureIndex);
String picIndex = String.valueOf(anchor.getRow1()) + "_" + String.valueOf(anchor.getCol1()); String picIndex = anchor.getRow1() + "_" + anchor.getCol1();
sheetIndexPicMap.put(picIndex, picData); sheetIndexPicMap.put(picIndex, picData);
} }
} }
@ -1429,4 +1752,61 @@ public class ExcelUtil<T>
} }
return str; return str;
} }
/**
* 是否有对象的子列表
*/
public boolean isSubList()
{
return StringUtils.isNotNull(subFields) && subFields.size() > 0;
}
/**
* 是否有对象的子列表,集合不为空
*/
public boolean isSubListValue(T vo)
{
return StringUtils.isNotNull(subFields) && subFields.size() > 0 && StringUtils.isNotNull(getListCellValue(vo)) && getListCellValue(vo).size() > 0;
}
/**
* 获取集合的值
*/
public Collection<?> getListCellValue(Object obj)
{
Object value;
try
{
value = subMethod.invoke(obj, new Object[] {});
}
catch (Exception e)
{
return new ArrayList<Object>();
}
return (Collection<?>) value;
}
/**
* 获取对象的子列表方法
*
* @param name 名称
* @param pojoClass 类对象
* @return 子列表方法
*/
public Method getSubMethod(String name, Class<?> pojoClass)
{
StringBuffer getMethodName = new StringBuffer("get");
getMethodName.append(name.substring(0, 1).toUpperCase());
getMethodName.append(name.substring(1));
Method method = null;
try
{
method = pojoClass.getMethod(getMethodName.toString(), new Class[] {});
}
catch (Exception e)
{
log.error("获取对象异常{}", e.getMessage());
}
return method;
}
} }

View File

@ -13,13 +13,18 @@ public class SqlUtil
/** /**
* 定义常用的 sql关键字 * 定义常用的 sql关键字
*/ */
public static String SQL_REGEX = "select |insert |delete |update |drop |count |exec |chr |mid |master |truncate |char |and |declare "; public static String SQL_REGEX = "and |extractvalue|updatexml|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |+|user()";
/** /**
* 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)
*/ */
public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+";
/**
* 限制orderBy最大长度
*/
private static final int ORDER_BY_MAX_LENGTH = 500;
/** /**
* 检查字符,防止注入绕过 * 检查字符,防止注入绕过
*/ */
@ -29,6 +34,10 @@ public class SqlUtil
{ {
throw new UtilException("参数不符合规范,不能进行查询"); throw new UtilException("参数不符合规范,不能进行查询");
} }
if (StringUtils.length(value) > ORDER_BY_MAX_LENGTH)
{
throw new UtilException("参数已超过最大限制,不能进行查询");
}
return value; return value;
} }

View File

@ -22,7 +22,7 @@ public class Seq
private static AtomicInteger uploadSeq = new AtomicInteger(1); private static AtomicInteger uploadSeq = new AtomicInteger(1);
// 机器标识 // 机器标识
private static String machineCode = "A"; private static final String machineCode = "A";
/** /**
* 获取通用序列号 * 获取通用序列号

View File

@ -66,7 +66,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
} }
/** /**
* 获取类型 4伪随机生成的UUID 的静态工厂。 使用加密的本地线程伪随机数生成器生成该 UUID。 * 获取类型 4伪随机生成的UUID 的静态工厂。
* *
* @return 随机生成的 {@code UUID} * @return 随机生成的 {@code UUID}
*/ */

View File

@ -27,8 +27,13 @@ public class XssValidator implements ConstraintValidator<Xss, String>
public static boolean containsHtml(String value) public static boolean containsHtml(String value)
{ {
StringBuilder sHtml = new StringBuilder();
Pattern pattern = Pattern.compile(HTML_PATTERN); Pattern pattern = Pattern.compile(HTML_PATTERN);
Matcher matcher = pattern.matcher(value); Matcher matcher = pattern.matcher(value);
return matcher.matches(); while (matcher.find())
{
sHtml.append(matcher.group());
}
return pattern.matcher(sHtml).matches();
} }
} }

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>3.8.3</version> <version>3.8.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@ -37,11 +37,11 @@
<!-- 验证码 --> <!-- 验证码 -->
<dependency> <dependency>
<groupId>com.github.penggle</groupId> <groupId>pro.fessional</groupId>
<artifactId>kaptcha</artifactId> <artifactId>kaptcha</artifactId>
<exclusions> <exclusions>
<exclusion> <exclusion>
<artifactId>javax.servlet-api</artifactId> <artifactId>servlet-api</artifactId>
<groupId>javax.servlet</groupId> <groupId>javax.servlet</groupId>
</exclusion> </exclusion>
</exclusions> </exclusions>

View File

@ -1,5 +1,7 @@
package com.ruoyi.framework.aspectj; package com.ruoyi.framework.aspectj;
import java.util.ArrayList;
import java.util.List;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Before;
@ -9,8 +11,10 @@ import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.security.context.PermissionContextHolder;
/** /**
* 数据过滤处理 * 数据过滤处理
@ -68,8 +72,9 @@ public class DataScopeAspect
// 如果是超级管理员,则不过滤数据 // 如果是超级管理员,则不过滤数据
if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
{ {
String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext());
dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
controllerDataScope.userAlias()); controllerDataScope.userAlias(), permission);
} }
} }
} }
@ -79,25 +84,50 @@ public class DataScopeAspect
* *
* @param joinPoint 切点 * @param joinPoint 切点
* @param user 用户 * @param user 用户
* @param userAlias 别名 * @param deptAlias 部门别名
* @param userAlias 用户别名
* @param permission 权限字符
*/ */
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias) public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission)
{ {
StringBuilder sqlString = new StringBuilder(); StringBuilder sqlString = new StringBuilder();
List<String> conditions = new ArrayList<String>();
List<String> scopeCustomIds = new ArrayList<String>();
user.getRoles().forEach(role -> {
if (DATA_SCOPE_CUSTOM.equals(role.getDataScope()) && StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
{
scopeCustomIds.add(Convert.toStr(role.getRoleId()));
}
});
for (SysRole role : user.getRoles()) for (SysRole role : user.getRoles())
{ {
String dataScope = role.getDataScope(); String dataScope = role.getDataScope();
if (conditions.contains(dataScope))
{
continue;
}
if (!StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
{
continue;
}
if (DATA_SCOPE_ALL.equals(dataScope)) if (DATA_SCOPE_ALL.equals(dataScope))
{ {
sqlString = new StringBuilder(); sqlString = new StringBuilder();
conditions.add(dataScope);
break; break;
} }
else if (DATA_SCOPE_CUSTOM.equals(dataScope)) else if (DATA_SCOPE_CUSTOM.equals(dataScope))
{ {
sqlString.append(StringUtils.format( if (scopeCustomIds.size() > 1)
" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, {
role.getRoleId())); // 多个自定数据权限使用in查询避免多次拼接。
sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id in ({}) ) ", deptAlias, String.join(",", scopeCustomIds)));
}
else
{
sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, role.getRoleId()));
}
} }
else if (DATA_SCOPE_DEPT.equals(dataScope)) else if (DATA_SCOPE_DEPT.equals(dataScope))
{ {
@ -105,9 +135,7 @@ public class DataScopeAspect
} }
else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
{ {
sqlString.append(StringUtils.format( sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", deptAlias, user.getDeptId(), user.getDeptId()));
" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
deptAlias, user.getDeptId(), user.getDeptId()));
} }
else if (DATA_SCOPE_SELF.equals(dataScope)) else if (DATA_SCOPE_SELF.equals(dataScope))
{ {
@ -121,6 +149,13 @@ public class DataScopeAspect
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
} }
} }
conditions.add(dataScope);
}
// 角色都不包含传递过来的权限字符这个时候sqlString也会为空所以要限制一下,不查询任何数据
if (StringUtils.isEmpty(conditions))
{
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
} }
if (StringUtils.isNotBlank(sqlString.toString())) if (StringUtils.isNotBlank(sqlString.toString()))

View File

@ -4,25 +4,29 @@ import java.util.Collection;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.enums.BusinessStatus; import com.ruoyi.common.enums.BusinessStatus;
import com.ruoyi.common.enums.HttpMethod; import com.ruoyi.common.enums.HttpMethod;
import com.ruoyi.common.filter.PropertyPreExcludeFilter;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.IpUtils; import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.manager.AsyncManager; import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory; import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.system.domain.SysOperLog; import com.ruoyi.system.domain.SysOperLog;
@ -38,6 +42,21 @@ public class LogAspect
{ {
private static final Logger log = LoggerFactory.getLogger(LogAspect.class); private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
/** 排除敏感属性字段 */
public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };
/** 计算操作消耗时间 */
private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");
/**
* 处理请求前执行
*/
@Before(value = "@annotation(controllerLog)")
public void boBefore(JoinPoint joinPoint, Log controllerLog)
{
TIME_THREADLOCAL.set(System.currentTimeMillis());
}
/** /**
* 处理完请求后执行 * 处理完请求后执行
* *
@ -72,12 +91,17 @@ public class LogAspect
SysOperLog operLog = new SysOperLog(); SysOperLog operLog = new SysOperLog();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址 // 请求的地址
String ip = IpUtils.getIpAddr(ServletUtils.getRequest()); String ip = IpUtils.getIpAddr();
operLog.setOperIp(ip); operLog.setOperIp(ip);
operLog.setOperUrl(ServletUtils.getRequest().getRequestURI()); operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
if (loginUser != null) if (loginUser != null)
{ {
operLog.setOperName(loginUser.getUsername()); operLog.setOperName(loginUser.getUsername());
SysUser currentUser = loginUser.getUser();
if (StringUtils.isNotNull(currentUser) && StringUtils.isNotNull(currentUser.getDept()))
{
operLog.setDeptName(currentUser.getDept().getDeptName());
}
} }
if (e != null) if (e != null)
@ -93,16 +117,21 @@ public class LogAspect
operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数 // 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
// 设置消耗时间
operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
// 保存数据库 // 保存数据库
AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
} }
catch (Exception exp) catch (Exception exp)
{ {
// 记录本地异常日志 // 记录本地异常日志
log.error("==前置通知异常==");
log.error("异常信息:{}", exp.getMessage()); log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace(); exp.printStackTrace();
} }
finally
{
TIME_THREADLOCAL.remove();
}
} }
/** /**
@ -124,7 +153,7 @@ public class LogAspect
if (log.isSaveRequestData()) if (log.isSaveRequestData())
{ {
// 获取参数的信息,传入到数据库中。 // 获取参数的信息,传入到数据库中。
setRequestValue(joinPoint, operLog); setRequestValue(joinPoint, operLog, log.excludeParamNames());
} }
// 是否需要保存response参数和值 // 是否需要保存response参数和值
if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult))
@ -139,25 +168,26 @@ public class LogAspect
* @param operLog 操作日志 * @param operLog 操作日志
* @throws Exception 异常 * @throws Exception 异常
*/ */
private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception
{ {
Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
String requestMethod = operLog.getRequestMethod(); String requestMethod = operLog.getRequestMethod();
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) if (StringUtils.isEmpty(paramsMap)
&& (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)))
{ {
String params = argsArrayToString(joinPoint.getArgs()); String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
operLog.setOperParam(StringUtils.substring(params, 0, 2000)); operLog.setOperParam(StringUtils.substring(params, 0, 2000));
} }
else else
{ {
Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000));
operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
} }
} }
/** /**
* 参数拼装 * 参数拼装
*/ */
private String argsArrayToString(Object[] paramsArray) private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames)
{ {
String params = ""; String params = "";
if (paramsArray != null && paramsArray.length > 0) if (paramsArray != null && paramsArray.length > 0)
@ -168,7 +198,7 @@ public class LogAspect
{ {
try try
{ {
Object jsonObj = JSON.toJSON(o); String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames));
params += jsonObj.toString() + " "; params += jsonObj.toString() + " ";
} }
catch (Exception e) catch (Exception e)
@ -180,6 +210,14 @@ public class LogAspect
return params.trim(); return params.trim();
} }
/**
* 忽略敏感属性
*/
public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames)
{
return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames));
}
/** /**
* 判断是否需要过滤的对象。 * 判断是否需要过滤的对象。
* *

View File

@ -16,7 +16,6 @@ import org.springframework.stereotype.Component;
import com.ruoyi.common.annotation.RateLimiter; import com.ruoyi.common.annotation.RateLimiter;
import com.ruoyi.common.enums.LimitType; import com.ruoyi.common.enums.LimitType;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.IpUtils; import com.ruoyi.common.utils.ip.IpUtils;
@ -50,7 +49,6 @@ public class RateLimiterAspect
@Before("@annotation(rateLimiter)") @Before("@annotation(rateLimiter)")
public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable
{ {
String key = rateLimiter.key();
int time = rateLimiter.time(); int time = rateLimiter.time();
int count = rateLimiter.count(); int count = rateLimiter.count();
@ -63,7 +61,7 @@ public class RateLimiterAspect
{ {
throw new ServiceException("访问过于频繁,请稍候再试"); throw new ServiceException("访问过于频繁,请稍候再试");
} }
log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), key); log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), combineKey);
} }
catch (ServiceException e) catch (ServiceException e)
{ {
@ -80,7 +78,7 @@ public class RateLimiterAspect
StringBuffer stringBuffer = new StringBuffer(rateLimiter.key()); StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());
if (rateLimiter.limitType() == LimitType.IP) if (rateLimiter.limitType() == LimitType.IP)
{ {
stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest())).append("-"); stringBuffer.append(IpUtils.getIpAddr()).append("-");
} }
MethodSignature signature = (MethodSignature) point.getSignature(); MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod(); Method method = signature.getMethod();

View File

@ -6,6 +6,8 @@ import org.springframework.data.redis.serializer.SerializationException;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader; import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter; import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.filter.Filter;
import com.ruoyi.common.constant.Constants;
/** /**
* Redis使用FastJson序列化 * Redis使用FastJson序列化
@ -16,6 +18,8 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{ {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(Constants.JSON_WHITELIST_STR);
private Class<T> clazz; private Class<T> clazz;
public FastJson2JsonRedisSerializer(Class<T> clazz) public FastJson2JsonRedisSerializer(Class<T> clazz)
@ -43,6 +47,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
} }
String str = new String(bytes, DEFAULT_CHARSET); String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType); return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER);
} }
} }

View File

@ -0,0 +1,43 @@
package com.ruoyi.framework.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import com.ruoyi.common.constant.Constants;
/**
* 资源文件配置加载
*
* @author ruoyi
*/
@Configuration
public class I18nConfig implements WebMvcConfigurer
{
@Bean
public LocaleResolver localeResolver()
{
SessionLocaleResolver slr = new SessionLocaleResolver();
// 默认语言
slr.setDefaultLocale(Constants.DEFAULT_LOCALE);
return slr;
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor()
{
LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
// 参数名
lci.setParamName("lang");
return lci;
}
@Override
public void addInterceptors(InterceptorRegistry registry)
{
registry.addInterceptor(localeChangeInterceptor());
}
}

View File

@ -5,7 +5,7 @@ import com.google.code.kaptcha.text.impl.DefaultTextCreator;
/** /**
* 验证码文本生成器 * 验证码文本生成器
* *
* @author ruoyi * @author ruoyi
*/ */
public class KaptchaTextCreator extends DefaultTextCreator public class KaptchaTextCreator extends DefaultTextCreator
@ -20,7 +20,7 @@ public class KaptchaTextCreator extends DefaultTextCreator
int x = random.nextInt(10); int x = random.nextInt(10);
int y = random.nextInt(10); int y = random.nextInt(10);
StringBuilder suChinese = new StringBuilder(); StringBuilder suChinese = new StringBuilder();
int randomoperands = (int) Math.round(Math.random() * 2); int randomoperands = random.nextInt(3);
if (randomoperands == 0) if (randomoperands == 0)
{ {
result = x * y; result = x * y;
@ -30,7 +30,7 @@ public class KaptchaTextCreator extends DefaultTextCreator
} }
else if (randomoperands == 1) else if (randomoperands == 1)
{ {
if (!(x == 0) && y % x == 0) if ((x != 0) && y % x == 0)
{ {
result = y / x; result = y / x;
suChinese.append(CNUMBERS[y]); suChinese.append(CNUMBERS[y]);
@ -45,7 +45,7 @@ public class KaptchaTextCreator extends DefaultTextCreator
suChinese.append(CNUMBERS[y]); suChinese.append(CNUMBERS[y]);
} }
} }
else if (randomoperands == 2) else
{ {
if (x >= y) if (x >= y)
{ {
@ -62,13 +62,6 @@ public class KaptchaTextCreator extends DefaultTextCreator
suChinese.append(CNUMBERS[x]); suChinese.append(CNUMBERS[x]);
} }
} }
else
{
result = x + y;
suChinese.append(CNUMBERS[x]);
suChinese.append("+");
suChinese.append(CNUMBERS[y]);
}
suChinese.append("=?@" + result); suChinese.append("=?@" + result);
return suChinese.toString(); return suChinese.toString();
} }

View File

@ -1,8 +1,10 @@
package com.ruoyi.framework.config; package com.ruoyi.framework.config;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter; import org.springframework.web.filter.CorsFilter;
@ -33,7 +35,8 @@ public class ResourcesConfig implements WebMvcConfigurer
/** swagger配置 */ /** swagger配置 */
registry.addResourceHandler("/swagger-ui/**") registry.addResourceHandler("/swagger-ui/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/"); .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
.setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic());;
} }
/** /**

View File

@ -2,16 +2,17 @@ package com.ruoyi.framework.config;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.filter.CorsFilter; import org.springframework.web.filter.CorsFilter;
@ -25,8 +26,9 @@ import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;
* *
* @author ruoyi * @author ruoyi
*/ */
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) @EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter @Configuration
public class SecurityConfig
{ {
/** /**
* 自定义用户认证逻辑 * 自定义用户认证逻辑
@ -65,16 +67,15 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
private PermitAllUrlProperties permitAllUrl; private PermitAllUrlProperties permitAllUrl;
/** /**
* 解决 无法直接注入 AuthenticationManager * 身份验证实现
*
* @return
* @throws Exception
*/ */
@Bean @Bean
@Override public AuthenticationManager authenticationManager()
public AuthenticationManager authenticationManagerBean() throws Exception
{ {
return super.authenticationManagerBean(); DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
return new ProviderManager(daoAuthenticationProvider);
} }
/** /**
@ -92,38 +93,39 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
* rememberMe | 允许通过remember-me登录的用户访问 * rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问 * authenticated | 用户登录后可访问
*/ */
@Override @Bean
protected void configure(HttpSecurity httpSecurity) throws Exception protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception
{ {
// 注解标记允许匿名访问的url return httpSecurity
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests(); // CSRF禁用因为不使用session
permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll()); .csrf(csrf -> csrf.disable())
// 禁用HTTP响应标头
httpSecurity .headers((headersCustomizer) -> {
// CSRF禁用因为不使用session headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());
.csrf().disable() })
// 认证失败处理类 // 认证失败处理类
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
// 基于token所以不需要session // 基于token所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 过滤请求 // 注解标记允许匿名访问的url
.authorizeRequests() .authorizeHttpRequests((requests) -> {
permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
// 对于登录login 注册register 验证码captchaImage 允许匿名访问 // 对于登录login 注册register 验证码captchaImage 允许匿名访问
.antMatchers("/login", "/register", "/captchaImage").anonymous() requests.antMatchers("/login", "/register", "/captchaImage").permitAll()
// 静态资源,可匿名访问 // 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证 // 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated() .anyRequest().authenticated();
.and() })
.headers().frameOptions().disable(); // 添加Logout filter
// 添加Logout filter .logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler); // 添加JWT filter
// 添加JWT filter .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // 添加CORS filter
// 添加CORS filter .addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class); .addFilterBefore(corsFilter, LogoutFilter.class)
httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class); .build();
} }
/** /**
@ -134,13 +136,4 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
{ {
return new BCryptPasswordEncoder(); return new BCryptPasswordEncoder();
} }
/**
* 身份认证接口
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
} }

View File

@ -24,6 +24,12 @@ public class DruidProperties
@Value("${spring.datasource.druid.maxWait}") @Value("${spring.datasource.druid.maxWait}")
private int maxWait; private int maxWait;
@Value("${spring.datasource.druid.connectTimeout}")
private int connectTimeout;
@Value("${spring.datasource.druid.socketTimeout}")
private int socketTimeout;
@Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}") @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
private int timeBetweenEvictionRunsMillis; private int timeBetweenEvictionRunsMillis;
@ -54,6 +60,12 @@ public class DruidProperties
/** 配置获取连接等待超时的时间 */ /** 配置获取连接等待超时的时间 */
datasource.setMaxWait(maxWait); datasource.setMaxWait(maxWait);
/** 配置驱动连接超时时间,检测数据库建立连接的超时时间,单位是毫秒 */
datasource.setConnectTimeout(connectTimeout);
/** 配置网络超时时间,等待数据库操作完成的网络超时时间,单位是毫秒 */
datasource.setSocketTimeout(socketTimeout);
/** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */ /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);

View File

@ -3,6 +3,7 @@ package com.ruoyi.framework.config.properties;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.apache.commons.lang3.RegExUtils; import org.apache.commons.lang3.RegExUtils;
@ -44,12 +45,12 @@ public class PermitAllUrlProperties implements InitializingBean, ApplicationCont
// 获取方法上边的注解 替代path variable 为 * // 获取方法上边的注解 替代path variable 为 *
Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class); Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);
Optional.ofNullable(method).ifPresent(anonymous -> info.getPatternsCondition().getPatterns() Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns())
.forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
// 获取类上边的注解, 替代path variable 为 * // 获取类上边的注解, 替代path variable 为 *
Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class); Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);
Optional.ofNullable(controller).ifPresent(anonymous -> info.getPatternsCondition().getPatterns() Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns())
.forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
}); });
} }

View File

@ -14,7 +14,7 @@ public class DynamicDataSourceContextHolder
/** /**
* 使用ThreadLocal维护变量ThreadLocal为每个使用该变量的线程提供独立的变量副本 * 使用ThreadLocal维护变量ThreadLocal为每个使用该变量的线程提供独立的变量副本
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/ */
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

View File

@ -47,8 +47,9 @@ public abstract class RepeatSubmitInterceptor implements HandlerInterceptor
/** /**
* 验证是否重复提交由子类实现具体的防重复提交的规则 * 验证是否重复提交由子类实现具体的防重复提交的规则
* *
* @param request * @param request 请求信息
* @return * @param annotation 防重复注解参数
* @return 结果
* @throws Exception * @throws Exception
*/ */
public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation); public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation);

View File

@ -38,7 +38,7 @@ public class AsyncFactory
final Object... args) final Object... args)
{ {
final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
final String ip = IpUtils.getIpAddr(ServletUtils.getRequest()); final String ip = IpUtils.getIpAddr();
return new TimerTask() return new TimerTask()
{ {
@Override @Override

View File

@ -0,0 +1,28 @@
package com.ruoyi.framework.security.context;
import org.springframework.security.core.Authentication;
/**
* 身份验证信息
*
* @author ruoyi
*/
public class AuthenticationContextHolder
{
private static final ThreadLocal<Authentication> contextHolder = new ThreadLocal<>();
public static Authentication getContext()
{
return contextHolder.get();
}
public static void setContext(Authentication context)
{
contextHolder.set(context);
}
public static void clearContext()
{
contextHolder.remove();
}
}

View File

@ -0,0 +1,27 @@
package com.ruoyi.framework.security.context;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import com.ruoyi.common.core.text.Convert;
/**
* 权限信息
*
* @author ruoyi
*/
public class PermissionContextHolder
{
private static final String PERMISSION_CONTEXT_ATTRIBUTES = "PERMISSION_CONTEXT";
public static void setContext(String permission)
{
RequestContextHolder.currentRequestAttributes().setAttribute(PERMISSION_CONTEXT_ATTRIBUTES, permission,
RequestAttributes.SCOPE_REQUEST);
}
public static String getContext()
{
return Convert.toStr(RequestContextHolder.currentRequestAttributes().getAttribute(PERMISSION_CONTEXT_ATTRIBUTES,
RequestAttributes.SCOPE_REQUEST));
}
}

View File

@ -10,9 +10,9 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.manager.AsyncManager; import com.ruoyi.framework.manager.AsyncManager;
@ -46,8 +46,8 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
// 删除用户缓存记录 // 删除用户缓存记录
tokenService.delLoginUser(loginUser.getToken()); tokenService.delLoginUser(loginUser.getToken());
// 记录用户退出日志 // 记录用户退出日志
AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, "退出成功")); AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, MessageUtils.message("user.logout.success")));
} }
ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(HttpStatus.SUCCESS, "退出成功"))); ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success(MessageUtils.message("user.logout.success"))));
} }
} }

View File

@ -117,7 +117,7 @@ public class Jvm
*/ */
public String getRunTime() public String getRunTime()
{ {
return DateUtils.getDatePoor(DateUtils.getNowDate(), DateUtils.getServerStartDate()); return DateUtils.timeDistance(DateUtils.getNowDate(), DateUtils.getServerStartDate());
} }
/** /**

View File

@ -7,13 +7,17 @@ import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindException; import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import com.ruoyi.common.constant.HttpStatus; import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.exception.DemoModeException; import com.ruoyi.common.exception.DemoModeException;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.html.EscapeUtil;
/** /**
* 全局异常处理器 * 全局异常处理器
@ -59,6 +63,33 @@ public class GlobalExceptionHandler
return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage()); return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage());
} }
/**
* 请求路径中缺少必需的路径变量
*/
@ExceptionHandler(MissingPathVariableException.class)
public AjaxResult handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e);
return AjaxResult.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName()));
}
/**
* 请求参数类型不匹配
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public AjaxResult handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
String value = Convert.toStr(e.getValue());
if (StringUtils.isNotEmpty(value))
{
value = EscapeUtil.clean(value);
}
log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e);
return AjaxResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), value));
}
/** /**
* 拦截未知的运行时异常 * 拦截未知的运行时异常
*/ */

View File

@ -3,10 +3,12 @@ package com.ruoyi.framework.web.service;
import java.util.Set; import java.util.Set;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.security.context.PermissionContextHolder;
/** /**
* RuoYi首创 自定义权限实现ss取自SpringSecurity首字母 * RuoYi首创 自定义权限实现ss取自SpringSecurity首字母
@ -16,16 +18,6 @@ import com.ruoyi.common.utils.StringUtils;
@Service("ss") @Service("ss")
public class PermissionService public class PermissionService
{ {
/** 所有权限标识 */
private static final String ALL_PERMISSION = "*:*:*";
/** 管理员角色权限标识 */
private static final String SUPER_ADMIN = "admin";
private static final String ROLE_DELIMETER = ",";
private static final String PERMISSION_DELIMETER = ",";
/** /**
* 验证用户是否具备某权限 * 验证用户是否具备某权限
* *
@ -43,6 +35,7 @@ public class PermissionService
{ {
return false; return false;
} }
PermissionContextHolder.setContext(permission);
return hasPermissions(loginUser.getPermissions(), permission); return hasPermissions(loginUser.getPermissions(), permission);
} }
@ -60,7 +53,7 @@ public class PermissionService
/** /**
* 验证用户是否具有以下任意一个权限 * 验证用户是否具有以下任意一个权限
* *
* @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表 * @param permissions 以 PERMISSION_DELIMETER 为分隔符的权限列表
* @return 用户是否具有以下任意一个权限 * @return 用户是否具有以下任意一个权限
*/ */
public boolean hasAnyPermi(String permissions) public boolean hasAnyPermi(String permissions)
@ -74,8 +67,9 @@ public class PermissionService
{ {
return false; return false;
} }
PermissionContextHolder.setContext(permissions);
Set<String> authorities = loginUser.getPermissions(); Set<String> authorities = loginUser.getPermissions();
for (String permission : permissions.split(PERMISSION_DELIMETER)) for (String permission : permissions.split(Constants.PERMISSION_DELIMETER))
{ {
if (permission != null && hasPermissions(authorities, permission)) if (permission != null && hasPermissions(authorities, permission))
{ {
@ -105,7 +99,7 @@ public class PermissionService
for (SysRole sysRole : loginUser.getUser().getRoles()) for (SysRole sysRole : loginUser.getUser().getRoles())
{ {
String roleKey = sysRole.getRoleKey(); String roleKey = sysRole.getRoleKey();
if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) if (Constants.SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role)))
{ {
return true; return true;
} }
@ -141,7 +135,7 @@ public class PermissionService
{ {
return false; return false;
} }
for (String role : roles.split(ROLE_DELIMETER)) for (String role : roles.split(Constants.ROLE_DELIMETER))
{ {
if (hasRole(role)) if (hasRole(role))
{ {
@ -160,6 +154,6 @@ public class PermissionService
*/ */
private boolean hasPermissions(Set<String> permissions, String permission) private boolean hasPermissions(Set<String> permissions, String permission)
{ {
return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission)); return permissions.contains(Constants.ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
} }
} }

View File

@ -9,20 +9,23 @@ import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.user.BlackListException;
import com.ruoyi.common.exception.user.CaptchaException; import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.CaptchaExpireException; import com.ruoyi.common.exception.user.CaptchaExpireException;
import com.ruoyi.common.exception.user.UserNotExistsException;
import com.ruoyi.common.exception.user.UserPasswordNotMatchException; import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MessageUtils; import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.IpUtils; import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.framework.manager.AsyncManager; import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory; import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.framework.security.context.AuthenticationContextHolder;
import com.ruoyi.system.service.ISysConfigService; import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysUserService; import com.ruoyi.system.service.ISysUserService;
@ -60,19 +63,18 @@ public class SysLoginService
*/ */
public String login(String username, String password, String code, String uuid) public String login(String username, String password, String code, String uuid)
{ {
boolean captchaOnOff = configService.selectCaptchaOnOff(); // 验证码校验
// 验证码开关 validateCaptcha(username, code, uuid);
if (captchaOnOff) // 登录前置校验
{ loginPreCheck(username, password);
validateCaptcha(username, code, uuid);
}
// 用户验证 // 用户验证
Authentication authentication = null; Authentication authentication = null;
try try
{ {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
AuthenticationContextHolder.setContext(authenticationToken);
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager authentication = authenticationManager.authenticate(authenticationToken);
.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} }
catch (Exception e) catch (Exception e)
{ {
@ -87,6 +89,10 @@ public class SysLoginService
throw new ServiceException(e.getMessage()); throw new ServiceException(e.getMessage());
} }
} }
finally
{
AuthenticationContextHolder.clearContext();
}
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
LoginUser loginUser = (LoginUser) authentication.getPrincipal(); LoginUser loginUser = (LoginUser) authentication.getPrincipal();
recordLoginInfo(loginUser.getUserId()); recordLoginInfo(loginUser.getUserId());
@ -104,18 +110,58 @@ public class SysLoginService
*/ */
public void validateCaptcha(String username, String code, String uuid) public void validateCaptcha(String username, String code, String uuid)
{ {
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, ""); boolean captchaEnabled = configService.selectCaptchaEnabled();
String captcha = redisCache.getCacheObject(verifyKey); if (captchaEnabled)
redisCache.deleteObject(verifyKey);
if (captcha == null)
{ {
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"))); String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
throw new CaptchaExpireException(); String captcha = redisCache.getCacheObject(verifyKey);
if (captcha == null)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
throw new CaptchaExpireException();
}
redisCache.deleteObject(verifyKey);
if (!code.equalsIgnoreCase(captcha))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
throw new CaptchaException();
}
} }
if (!code.equalsIgnoreCase(captcha)) }
/**
* 登录前置校验
* @param username 用户名
* @param password 用户密码
*/
public void loginPreCheck(String username, String password)
{
// 用户名或密码为空 错误
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password))
{ {
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
throw new CaptchaException(); throw new UserNotExistsException();
}
// 密码如果不在指定范围内 错误
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
}
// 用户名不在指定范围内 错误
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
}
// IP黑名单校验
String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked")));
throw new BlackListException();
} }
} }
@ -128,7 +174,7 @@ public class SysLoginService
{ {
SysUser sysUser = new SysUser(); SysUser sysUser = new SysUser();
sysUser.setUserId(userId); sysUser.setUserId(userId);
sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest())); sysUser.setLoginIp(IpUtils.getIpAddr());
sysUser.setLoginDate(DateUtils.getNowDate()); sysUser.setLoginDate(DateUtils.getNowDate());
userService.updateUserProfile(sysUser); userService.updateUserProfile(sysUser);
} }

View File

@ -0,0 +1,86 @@
package com.ruoyi.framework.web.service;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
import com.ruoyi.common.exception.user.UserPasswordRetryLimitExceedException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.security.context.AuthenticationContextHolder;
/**
* 登录密码方法
*
* @author ruoyi
*/
@Component
public class SysPasswordService
{
@Autowired
private RedisCache redisCache;
@Value(value = "${user.password.maxRetryCount}")
private int maxRetryCount;
@Value(value = "${user.password.lockTime}")
private int lockTime;
/**
* 登录账户密码错误次数缓存键名
*
* @param username 用户名
* @return 缓存键key
*/
private String getCacheKey(String username)
{
return CacheConstants.PWD_ERR_CNT_KEY + username;
}
public void validate(SysUser user)
{
Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext();
String username = usernamePasswordAuthenticationToken.getName();
String password = usernamePasswordAuthenticationToken.getCredentials().toString();
Integer retryCount = redisCache.getCacheObject(getCacheKey(username));
if (retryCount == null)
{
retryCount = 0;
}
if (retryCount >= Integer.valueOf(maxRetryCount).intValue())
{
throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime);
}
if (!matches(user, password))
{
retryCount = retryCount + 1;
redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES);
throw new UserPasswordNotMatchException();
}
else
{
clearLoginRecordCache(username);
}
}
public boolean matches(SysUser user, String rawPassword)
{
return SecurityUtils.matchesPassword(rawPassword, user.getPassword());
}
public void clearLoginRecordCache(String loginName)
{
if (redisCache.hasKey(getCacheKey(loginName)))
{
redisCache.deleteObject(getCacheKey(loginName));
}
}
}

View File

@ -1,9 +1,12 @@
package com.ruoyi.framework.web.service; package com.ruoyi.framework.web.service;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.system.service.ISysMenuService; import com.ruoyi.system.service.ISysMenuService;
import com.ruoyi.system.service.ISysRoleService; import com.ruoyi.system.service.ISysRoleService;
@ -59,7 +62,21 @@ public class SysPermissionService
} }
else else
{ {
perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId())); List<SysRole> roles = user.getRoles();
if (!CollectionUtils.isEmpty(roles))
{
// 多角色设置permissions属性以便数据权限匹配权限
for (SysRole role : roles)
{
Set<String> rolePerms = menuService.selectMenuPermsByRoleId(role.getRoleId());
role.setPermissions(rolePerms);
perms.addAll(rolePerms);
}
}
else
{
perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));
}
} }
return perms; return perms;
} }

View File

@ -41,10 +41,12 @@ public class SysRegisterService
public String register(RegisterBody registerBody) public String register(RegisterBody registerBody)
{ {
String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword(); String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword();
SysUser sysUser = new SysUser();
sysUser.setUserName(username);
boolean captchaOnOff = configService.selectCaptchaOnOff();
// 验证码开关 // 验证码开关
if (captchaOnOff) boolean captchaEnabled = configService.selectCaptchaEnabled();
if (captchaEnabled)
{ {
validateCaptcha(username, registerBody.getCode(), registerBody.getUuid()); validateCaptcha(username, registerBody.getCode(), registerBody.getUuid());
} }
@ -67,16 +69,14 @@ public class SysRegisterService
{ {
msg = "密码长度必须在5到20个字符之间"; msg = "密码长度必须在5到20个字符之间";
} }
else if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(username))) else if (!userService.checkUserNameUnique(sysUser))
{ {
msg = "保存用户'" + username + "'失败,注册账号已存在"; msg = "保存用户'" + username + "'失败,注册账号已存在";
} }
else else
{ {
SysUser sysUser = new SysUser();
sysUser.setUserName(username);
sysUser.setNickName(username); sysUser.setNickName(username);
sysUser.setPassword(SecurityUtils.encryptPassword(registerBody.getPassword())); sysUser.setPassword(SecurityUtils.encryptPassword(password));
boolean regFlag = userService.registerUser(sysUser); boolean regFlag = userService.registerUser(sysUser);
if (!regFlag) if (!regFlag)
{ {
@ -84,8 +84,7 @@ public class SysRegisterService
} }
else else
{ {
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.REGISTER, AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.register.success")));
MessageUtils.message("user.register.success")));
} }
} }
return msg; return msg;

View File

@ -4,6 +4,8 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -29,6 +31,8 @@ import io.jsonwebtoken.SignatureAlgorithm;
@Component @Component
public class TokenService public class TokenService
{ {
private static final Logger log = LoggerFactory.getLogger(TokenService.class);
// 令牌自定义标识 // 令牌自定义标识
@Value("${token.header}") @Value("${token.header}")
private String header; private String header;
@ -72,6 +76,7 @@ public class TokenService
} }
catch (Exception e) catch (Exception e)
{ {
log.error("获取用户信息异常'{}'", e.getMessage());
} }
} }
return null; return null;
@ -156,7 +161,7 @@ public class TokenService
public void setUserAgent(LoginUser loginUser) public void setUserAgent(LoginUser loginUser)
{ {
UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = IpUtils.getIpAddr(ServletUtils.getRequest()); String ip = IpUtils.getIpAddr();
loginUser.setIpaddr(ip); loginUser.setIpaddr(ip);
loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip)); loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
loginUser.setBrowser(userAgent.getBrowser().getName()); loginUser.setBrowser(userAgent.getBrowser().getName());

View File

@ -11,6 +11,7 @@ import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.enums.UserStatus; import com.ruoyi.common.enums.UserStatus;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysUserService; import com.ruoyi.system.service.ISysUserService;
@ -26,6 +27,9 @@ public class UserDetailsServiceImpl implements UserDetailsService
@Autowired @Autowired
private ISysUserService userService; private ISysUserService userService;
@Autowired
private SysPasswordService passwordService;
@Autowired @Autowired
private SysPermissionService permissionService; private SysPermissionService permissionService;
@ -37,19 +41,21 @@ public class UserDetailsServiceImpl implements UserDetailsService
if (StringUtils.isNull(user)) if (StringUtils.isNull(user))
{ {
log.info("登录用户:{} 不存在.", username); log.info("登录用户:{} 不存在.", username);
throw new ServiceException("登录用户:" + username + " 不存在"); throw new ServiceException(MessageUtils.message("user.not.exists"));
} }
else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
{ {
log.info("登录用户:{} 已被删除.", username); log.info("登录用户:{} 已被删除.", username);
throw new ServiceException("对不起,您的账号:" + username + " 已被删除"); throw new ServiceException(MessageUtils.message("user.password.delete"));
} }
else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
{ {
log.info("登录用户:{} 已被停用.", username); log.info("登录用户:{} 已被停用.", username);
throw new ServiceException("对不起,您的账号:" + username + " 已停用"); throw new ServiceException(MessageUtils.message("user.blocked"));
} }
passwordService.validate(user);
return createLoginUser(user); return createLoginUser(user);
} }

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