Compare commits

...

429 Commits

Author SHA1 Message Date
RuoYi 7da12b0c07 用户密码支持自定义配置规则 2026-04-17 13:10:02 +08:00
RuoYi dbe9834e4c 角色权限变更后刷新所有持有该角色的在线用户权限 2026-04-16 16:33:50 +08:00
RuoYi 69aaabd09d 优化代码生成同步操作column_type没更新问题 2026-04-16 14:24:07 +08:00
RuoYi 557efc4d44 优化白名单支持对通配符路径匹配 2026-04-16 13:42:57 +08:00
RuoYi 58f3c43c50 修复脱敏不生效问题(IIPBZR) 2026-04-15 13:03:51 +08:00
RuoYi d454d9729e 通知公告新增阅读用户列表 2026-04-14 16:11:36 +08:00
RuoYi 0728a04b1b 通知公告新增阅读用户列表 2026-04-14 15:39:49 +08:00
RuoYi ba33fe9e03 通知公告新增详细显示 2026-04-14 14:35:23 +08:00
RuoYi 60e542d809 新增标签页样式chrome风格 2026-04-13 00:41:03 +08:00
RuoYi 290f7b4cd9 新增代码生成详情页功能 2026-04-12 09:57:04 +08:00
RuoYi 4b9800eb54 自动导入配置 2026-04-10 10:59:14 +08:00
RuoYi 02907f967e 代码生成修改拖拽时显示手指样式 2026-04-10 10:58:52 +08:00
RuoYi 157228e1d0 用户列表新增抽屉效果详细信息 2026-04-09 12:47:23 +08:00
RuoYi df4261f4d3 优化样式 2026-04-03 22:33:20 +08:00
RuoYi abffc253c0 数据权限注解支持自定义字段名 2026-04-03 15:38:16 +08:00
RuoYi cf25c229a9 新增Excel导入组件ExcelImportDialog 2026-04-03 08:53:41 +08:00
RuoYi ea3410e170 update tree-panel ref 2026-04-01 21:48:51 +08:00
RuoYi 7979124798 新增树分割组件TreePanel 2026-04-01 21:34:10 +08:00
RuoYi 3b42c7c633 支持表格列显隐状态记忆 2026-03-31 19:22:43 +08:00
RuoYi 26d7bb25f0 支持多sheet导出 2026-03-31 16:31:18 +08:00
RuoYi 6775ed78d1 代码生成支持表单布局选项 2026-03-30 16:44:47 +08:00
RuoYi b3a2572410 update sqlkeyword 2026-03-30 16:35:58 +08:00
RuoYi 0e2d75c23c 若依 3.9.2 2026-03-26 08:23:30 +08:00
RuoYi 2a26aa1356 优化页签全屏显示 2026-03-26 00:52:04 +08:00
RuoYi 3fa6a4b5df 优化快速点击页签刷新出现404问题 2026-03-25 21:21:58 +08:00
RuoYi 3c2186cf73 修复typescript版多表代码生成报错问题 2026-03-25 12:32:24 +08:00
RuoYi 7282667d41 菜单管理列表新增类型显示 2026-03-24 15:33:27 +08:00
RuoYi 4b1fad5f18 优化菜单主题风格 2026-03-24 10:30:32 +08:00
RuoYi 58bc7043a0 升级axios到最新版本0.30.3 2026-03-23 11:42:53 +08:00
RuoYi 677400b933 优化页签显示 2026-03-23 11:42:30 +08:00
RuoYi 959a2ffd67 添加持久化标签页开关功能 2026-03-22 23:29:23 +08:00
RuoYi ec0ca11cc6 添加持久化标签页开关功能 2026-03-22 21:03:58 +08:00
RuoYi 0f734eda88 菜单搜索支持文本高亮&数量提示 2026-03-22 16:54:12 +08:00
RuoYi d3831bf455 优化tag全屏为页签内区域 2026-03-22 15:01:19 +08:00
RuoYi 9c6ae1dd60 页签左右滚动效果兼容所有环境不依赖behavior 2026-03-22 14:44:55 +08:00
RuoYi 20dc6b3925 优化页签功能&支持全屏按钮操作 2026-03-22 13:58:14 +08:00
RuoYi fa224503e4 保存排序添加编辑权限 2026-03-21 19:08:52 +08:00
RuoYi b869c9622a 优化点击任务名称查看详细 2026-03-21 14:28:19 +08:00
RuoYi f7665b9ec5 字典类型列表新增抽屉效果详细信息 2026-03-21 13:37:46 +08:00
RuoYi b508e05b0c 菜单管理支持批量保存排序 2026-03-21 12:41:51 +08:00
RuoYi 01fd7be61a 部门管理支持批量保存排序 2026-03-21 11:39:53 +08:00
RuoYi 16a78c8113 新增锁定屏幕功能 2026-03-20 21:51:17 +08:00
RuoYi 01780ea59c 新增锁定屏幕功能 2026-03-20 20:36:07 +08:00
RuoYi 9f0b31ebbd 优化定时任务详情页展示&补充执行时间字段 2026-03-20 16:37:08 +08:00
RuoYi fde9db31e4 操作日志详细页面优化 2026-03-20 13:04:05 +08:00
RuoYi 5b52281fe1 首页新增通知公告消息提醒 2026-03-20 10:36:10 +08:00
RuoYi f80143eccb 优化RightToolbar搜索栏切换动画 2026-03-19 19:41:08 +08:00
RuoYi 7816597b86 TypeScript前端代码生成模板同步到最新 2026-03-12 12:24:35 +08:00
RuoYi a6f142db96 remove test code 2026-03-10 22:01:28 +08:00
RuoYi 51d11c39c8 update README.md 2026-03-10 16:30:44 +08:00
RuoYi c5677434cf 项目升级到 Spring Boot 4 2026-03-10 16:20:30 +08:00
RuoYi 1d294464a7 升级fastjson到最新版2.0.61 2026-03-09 16:43:20 +08:00
RuoYi 140a7fb66f 升级oshi到最新版本6.10.0 2026-03-09 16:42:47 +08:00
RuoYi 0d85aa50a7 优化Excel自定义格式样式重复创建问题 2026-03-09 15:01:42 +08:00
RuoYi ed916656dd 删除无用的注解 2026-03-01 11:20:16 +08:00
若依 85cb89f1b6 !1148 修复IP查询失败的问题
update address url
2026-02-26 02:03:11 +00:00
chniccs 8aef19ba49 update ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java.
修复此Issuse https://gitee.com/y_project/RuoYi-Vue/issues/IDR6LP  经验证发现使用http协议时会出现IP地址查询失败的情况,替换为https后正常


Signed-off-by: chniccs <chniccs@163.com>
2026-02-25 01:37:13 +00:00
若依 83bc1a331a !1146 update ruoyi-ui/src/views/system/dict/index.vue.
Merge pull request !1146 from NewYoung208/master
2026-02-11 07:33:44 +00:00
NewYoung208 b9c7731cdb update ruoyi-ui/src/views/system/dict/index.vue.
优化代码

Signed-off-by: NewYoung208 <niuyang208@163.com>
2026-02-11 07:07:57 +00:00
RuoYi 850b86e0de 优化字典类型属性提醒说明 2026-02-05 12:45:43 +08:00
RuoYi 8a1cf9ed55 update README.md 2026-01-28 14:27:46 +08:00
RuoYi 600e06904b update README.md 2026-01-28 13:59:34 +08:00
RuoYi 6281609d9e 添加新群号:127358632 2026-01-28 13:49:51 +08:00
RuoYi 2cf825dd3d 优化代码 2026-01-28 13:42:42 +08:00
RuoYi 245baa705b 添加菜单路由地址和名称的校验规则 2026-01-09 11:13:58 +08:00
RuoYi cfe076ebd0 优化防重提交间隔时间可自定义 2026-01-08 13:39:16 +08:00
RuoYi 1f2f11f80f 修复Excel自定义格式样式污染问题 2026-01-06 13:53:11 +08:00
RuoYi 12fb035b2e copyright 2026 2026-01-04 15:27:17 +08:00
RuoYi 98a8545ca0 将isAdmin方法统一到SecurityUtils 2026-01-04 15:26:52 +08:00
若依 fdb1853a34 !1129 将isAdmin方法统一到SecurityUtils
Merge pull request !1129 from MicyToy/enhance/unified_is_admin
2026-01-04 07:09:51 +00:00
Tiany 8c6b4a96b7 将isAdmin方法统一到SecurityUtils 2025-12-22 16:13:10 +08:00
若依 5e83011d56 !1127 fix: 修正 UserAgent 解析逻辑,正确设置浏览器和操作系统字段
Merge pull request !1127 from yeleiyun/fix-useragent-bug
2025-12-21 02:45:34 +00:00
yeleiyun b12dab2d2e fix: 修正 UserAgent 解析逻辑,正确设置浏览器和操作系统字段 2025-12-20 22:26:20 +08:00
RuoYi 7b75f9ac0b 优化topbar顶部菜单样式 2025-12-18 13:59:15 +08:00
RuoYi 4615293be9 若依 3.9.1 2025-12-18 09:04:16 +08:00
RuoYi ba5cf9de6e 默认固定头部 2025-12-18 09:03:05 +08:00
RuoYi 6de392bac2 优化字典组件值宽松匹配 2025-12-16 16:40:55 +08:00
RuoYi 49f62e565a 菜单导航设置支持纯顶部 2025-12-16 11:40:06 +08:00
RuoYi 5579b57bef 升级druid到最新版本1.2.27 2025-12-11 14:30:27 +08:00
RuoYi 88609b3b24 升级oshi到最新版本6.9.1 2025-12-11 14:30:06 +08:00
RuoYi 03f3f47397 升级commons.io到最新版本2.21.0 2025-12-11 14:29:45 +08:00
RuoYi b5400d962b 升级fastjson到最新版2.0.60 2025-12-11 14:29:20 +08:00
RuoYi e7ef3241c5 update sqlkeyword 2025-12-11 14:29:03 +08:00
RuoYi eb6878e18f 使用yauaa代替bitwalker 2025-12-09 14:30:03 +08:00
RuoYi 8499225192 优化用户密码字段序列化配置 2025-12-05 14:59:14 +08:00
RuoYi 4a5e45d160 优化数据权限控制逻辑,放开permission限制 2025-12-04 17:31:57 +08:00
RuoYi 188e50ff1c 支持Excel导出对象的多个子列表 2025-12-04 16:32:30 +08:00
RuoYi bd66cc7260 优化表单构建关闭页签销毁复制插件 2025-12-04 13:15:20 +08:00
RuoYi 866b47000c 忽略用户密码字段的JSON序列化 2025-12-03 14:38:14 +08:00
RuoYi f38f8b2c3e 升级tomcat到最新版本9.0.112 2025-12-03 11:39:56 +08:00
RuoYi faa86ac946 优化代码 2025-12-03 11:39:27 +08:00
RuoYi ad280e824c 优化生成代码下载的zip文件名 2025-12-03 10:26:27 +08:00
RuoYi 6e1aa42ebe 网页标题设置新增SET_TITLE方法 2025-12-02 19:30:16 +08:00
RuoYi 315901041f 支持Excel导出对象的多个子列表 2025-12-02 19:13:04 +08:00
RuoYi 91263711d4 登录/注册页面底部版权信息修改为读取配置 2025-12-02 15:28:44 +08:00
RuoYi 9372d3401f 修复v3时间控件between选择后清空报错问题 2025-12-02 14:56:34 +08:00
RuoYi 0eaa090f4b 修复表单构建移除所有控件后切换路由回来空白问题 2025-12-02 13:07:37 +08:00
RuoYi a5adee3c5f 修复comboReadDict属性下多个sheet出现的报错(ICWQ8E) 2025-11-13 11:35:04 +08:00
RuoYi 075e96466f 添加新群号:174569686 2025-10-05 20:10:10 +08:00
RuoYi 41496b6d8a 升级spring-security到安全版本 2025-09-05 09:18:13 +08:00
RuoYi 4a401984c1 升级fastjson到最新版2.0.58 2025-09-05 09:16:51 +08:00
RuoYi e5faee66c8 修复固定头部时出现的导航栏偏移问题(ICV9OH) 2025-09-04 19:58:16 +08:00
RuoYi 7558c176eb 支持防盗链功能 2025-09-02 11:30:54 +08:00
RuoYi 4a5b0e6079 升级oshi到最新版本6.8.3 2025-08-28 13:33:23 +08:00
RuoYi 08637e31e5 优化代码 2025-08-28 13:32:57 +08:00
RuoYi 512b157801 优化代码 2025-08-27 15:34:24 +08:00
若依 5e8efaa94a !1082 修复每次登录把部门id更新为null
Merge pull request !1082 from afterglow/master
2025-08-27 06:48:16 +00:00
RuoYi 5f11fed41b 用户导入添加验证提示 2025-08-23 11:13:51 +08:00
RuoYi b60b5de750 优化布局设置显示 2025-08-23 11:12:42 +08:00
lcs 41ff3843e6 修复每次登录把部门id更新为null 2025-08-22 16:01:01 +08:00
RuoYi 6a2e8a35e9 修复用户归属部门无法修改为空问题 2025-08-21 14:47:48 +08:00
RuoYi 769165575f columns default value 2025-08-09 16:11:36 +08:00
RuoYi 18c8d4ec9c 显示列信息支持对象格式 2025-08-09 13:21:54 +08:00
RuoYi 191fd29301 自动识别json对象白名单配置范围缩小 2025-08-09 10:57:26 +08:00
RuoYi 47510fe2de 升级tomcat到最新版本9.0.108 2025-08-07 15:24:27 +08:00
RuoYi 725c7dcea2 添加新群号:191164766 2025-06-20 11:39:20 +08:00
RuoYi 158ccaebe0 优化定时任务包名白名单匹配方式 2025-06-20 11:33:25 +08:00
RuoYi 7b9060af26 优化Excel统计行数值的单元格样式显示 2025-06-19 14:47:49 +08:00
RuoYi 1a2f20e859 升级oshi到最新版本6.8.2 2025-06-18 13:51:41 +08:00
RuoYi 09faecb5d3 升级tomcat到最新版本9.0.106 2025-06-18 13:40:59 +08:00
RuoYi d46e62a21a 用户头像更换后移除旧头像文件 2025-06-06 14:58:01 +08:00
RuoYi fa88922637 若依 3.9.0 2025-05-28 09:04:45 +08:00
RuoYi 65159934ab 注册账号设置默认密码最后更新时间 2025-05-26 10:57:49 +08:00
RuoYi 1642bba612 升级fastjson到最新版2.0.57 2025-05-26 08:59:40 +08:00
RuoYi a7a61fee8d update vue.config.js 2025-05-24 14:31:02 +08:00
RuoYi db6d5d34e6 添加底部版权信息及开关 2025-05-24 14:24:23 +08:00
RuoYi 9ceca3a68e 添加页签图标显示开关功能 2025-05-23 14:56:38 +08:00
RuoYi cf2579612c update pwdUpdateDate 2025-05-23 10:44:51 +08:00
RuoYi c0355a0f5a 账号密码支持自定义更新周期 2025-05-23 09:04:50 +08:00
RuoYi 8ff013552a 初始密码支持自定义修改策略 2025-05-22 23:03:30 +08:00
RuoYi 673249d373 升级tomcat到最新版本9.0.105 2025-05-15 10:54:38 +08:00
RuoYi fe3a92a812 升级oshi到最新版本6.8.1 2025-05-15 09:05:21 +08:00
RuoYi 67b6a0e11b 升级commons.io到最新版本2.19.0 2025-05-15 09:04:36 +08:00
RuoYi bc70351e34 delete vue-meta 2025-05-15 08:52:28 +08:00
RuoYi fe0c1fcb5b delete eslint 2025-05-15 08:13:34 +08:00
RuoYi 9f39dfd0c1 优化导航栏显示昵称&设置 2025-05-09 13:45:39 +08:00
RuoYi 131abe876d 菜单搜索支持键盘选择&悬浮主题背景 2025-05-07 13:22:43 +08:00
RuoYi 46708ceee4 图片上传组件新增disabled属性 2025-05-06 19:13:32 +08:00
RuoYi ecd201550f add columnName Drag 2025-05-06 14:52:36 +08:00
若依 ff3f3f2631 !1013 修复图片上传组件在同一页面中被多次引用时,仅有第一个组件拖拽功能生效的问题
Merge pull request !1013 from 稚屿/N/A
2025-05-06 05:03:41 +00:00
若依 d3cc8f0fb7 !1012 修复文件上传组件在同一页面中被多次引用时,仅有第一个组件拖拽功能生效的问题
Merge pull request !1012 from 稚屿/N/A
2025-05-06 05:03:36 +00:00
稚屿 6cafa3373e 修复图片上传组件在同一页面中被多次引用时,仅有第一个组件拖拽功能生效的问题
Signed-off-by: 稚屿 <1491182878@qq.com>
2025-05-06 04:53:32 +00:00
稚屿 42fbf09dde 修复图片上传组件在同一页面中被多次引用时,仅有第一个组件拖拽功能生效的问题
Signed-off-by: 稚屿 <1491182878@qq.com>
2025-05-06 04:48:46 +00:00
RuoYi 88b0f5bcb2 update icon 2025-05-05 11:20:27 +08:00
RuoYi e852fdb687 上传组件新增拖动排序属性 2025-04-30 10:28:59 +08:00
RuoYi baf2f6f46b 优化Excel匹配数值型.0结尾 2025-04-28 11:24:24 +08:00
RuoYi e19f1abfeb update editor index 2025-04-27 14:02:36 +08:00
RuoYi 38ed092de7 remove all semicolons 2025-04-27 10:05:51 +08:00
RuoYi 27a037ed3d Excel导入导出支持多图片 2025-04-25 10:09:21 +08:00
RuoYi 87173cbe75 富文本复制粘贴图片上传至url 2025-04-24 14:23:29 +08:00
RuoYi 29a5b6da53 update vue.config.js 2025-04-24 11:08:10 +08:00
RuoYi b1d2139559 update package.json 2025-04-24 11:07:54 +08:00
RuoYi 43d78c2cf5 优化低版本node无法启动的问题 2025-04-22 12:05:43 +08:00
RuoYi 8f4eb24bf2 优化代码 2025-04-22 12:03:31 +08:00
RuoYi a9f9133e31 显隐列组件支持全选/全不选 2025-04-21 15:21:51 +08:00
RuoYi 09810ccf1d 优化菜单搜索查询页 2025-04-21 13:22:00 +08:00
RuoYi 0d9fb8b5c0 支持文件&图片组件自定义地址&参数 2025-04-18 12:55:58 +08:00
RuoYi c6b0efcdc2 优化角色禁用不允许分配 2025-04-17 15:08:10 +08:00
RuoYi 84fef1f675 update status name 2025-04-17 15:07:38 +08:00
RuoYi 11fed08b56 添加新群号:287842588 2025-04-01 19:15:21 +08:00
RuoYi f83b6fbfa2 remove dev runjs 2025-03-18 15:49:01 +08:00
若依 eef81e6ca9 !997 登录页和注册页表头使用VUE_APP_TITLE配置值
Merge pull request !997 from myifengs/master
2025-03-18 07:48:23 +00:00
Chingfeng Li 5a03a754e8 登录页肯注册页表头使用VUE_APP_TITLE配置值 2025-03-18 14:53:46 +08:00
RuoYi 245dea7215 升级tomcat到最新版本9.0.102 2025-03-14 16:09:22 +08:00
RuoYi 51632f8e60 优化代码 2025-03-14 16:09:01 +08:00
RuoYi 525ebf92d2 菜单管理新增路由名称 2025-03-06 11:02:21 +08:00
RuoYi d3b23a831e 优化代码 2025-03-04 20:03:11 +08:00
若依 89ab3bd058 !990 优化服务监控和缓存监控页面,页边距保持一致
Merge pull request !990 from NewYoung208/master
2025-03-04 11:18:04 +00:00
若依 9e16beb48f !989 update ruoyi-common/src/main/java/com/ruoyi/common/core/text/Convert.java.
Merge pull request !989 from 程子/N/A
2025-03-04 11:16:45 +00:00
NewYoung208 8d5ecc7ff4 优化服务监控和缓存监控页面,页边距保持一致 2025-03-04 16:58:16 +08:00
程子 6e314dd3e8 update ruoyi-common/src/main/java/com/ruoyi/common/core/text/Convert.java.
添加String转换Boolean值时对(是、否)的支持

Signed-off-by: 程子 <395030787@qq.com>
2025-03-03 08:04:15 +00:00
RuoYi 193c256e71 优化顶部菜单搜索栏为多层级显示(IBESXH) 2025-03-03 12:07:38 +08:00
RuoYi 4df52a6b40 优化导出Excel日期格式双击离开后与设定的格式不一致问题 2025-03-01 15:21:22 +08:00
RuoYi 079b7eeecf 优化代码 2025-03-01 15:17:01 +08:00
RuoYi ba24010709 pagination更换成flex布局 2025-03-01 15:07:43 +08:00
RuoYi bd257f85e6 优化前端处理路由函数代码 2025-03-01 15:07:21 +08:00
RuoYi 40c7ca34a8 优化前端树结构性能问题 2025-03-01 14:53:39 +08:00
RuoYi 1ef73d7360 修复代码生成主子表校验必填失效问题 2025-02-28 21:52:56 +08:00
RuoYi bd233fd62f 代码生成列表支持按时间排序 2025-02-28 19:38:34 +08:00
RuoYi fabddc518a 文件上传组件新增类型 2025-02-28 19:36:25 +08:00
RuoYi ca61b6c68d 优化空指针异常时无法获取错误信息问题 2025-02-28 19:35:13 +08:00
RuoYi 51e5cf2a09 升级tomcat到最新版本9.0.100 2025-02-28 13:00:01 +08:00
RuoYi 00acc37916 文件上传组件新增disabled属性 2025-02-28 12:59:41 +08:00
RuoYi 511ff0f125 优化代码 2025-02-28 12:58:03 +08:00
RuoYi bf46e38c29 添加新群号 2025-01-15 15:07:57 +08:00
RuoYi 698a5198d9 copyright 2025 2025-01-07 10:43:54 +08:00
RuoYi 5e6c917ab0 若依 3.8.9 2024-12-30 08:49:55 +08:00
RuoYi 9a51563144 代码生成新增配置是否允许文件覆盖到本地 2024-12-25 15:48:16 +08:00
RuoYi 3b2704c181 优化导入带标题文件关闭清理 2024-12-25 15:47:32 +08:00
RuoYi 7232217061 update sqlkeyword 2024-12-25 00:05:16 +08:00
RuoYi 25fd29c5ea 优化特殊字符密码修改失败问题 2024-12-17 14:27:18 +08:00
RuoYi 2d6a6a162f 优化TopNav内链菜单点击没有高亮(IB8WHJ) 2024-12-17 11:56:51 +08:00
RuoYi 164c62743f 优化菜单管理切换Mini布局错乱问题 2024-12-17 11:24:10 +08:00
RuoYi 4ee169b0c8 update README 2024-12-13 20:07:48 +08:00
RuoYi d487ffc92f 用户管理过滤掉已禁用部门(IB5H7F) 2024-12-11 11:20:09 +08:00
RuoYi 5a1e7bae2c 修改主题样式本地读取 2024-12-07 17:06:50 +08:00
RuoYi 1810f30491 白名单支持对通配符路径匹配 2024-12-04 08:51:17 +08:00
RuoYi 6efceac460 Excel注解支持wrapText是否允许内容换行 2024-12-03 08:58:44 +08:00
RuoYi 77a6350460 修复导出子列表对象只能在最后的问题 2024-12-02 20:36:53 +08:00
若依 58a21ff9d7 !961 修复默认关闭Tags-Views时,内链页面打不开
Merge pull request !961 from Lyb刘同学/master
2024-11-27 09:28:06 +00:00
liuyuanbo 7f507f5dfa 修复默认关闭Tags-Views时,内链页面打不开 2024-11-27 17:24:53 +08:00
若依 a1a45ef7ac !958 修复TopNav无法正确获取active的问题
Merge pull request !958 from Lyb刘同学/N/A
2024-11-27 00:55:53 +00:00
Lyb刘同学 b343308a97 修复TopNav无法正确获取active的问题
Signed-off-by: Lyb刘同学 <1553592282@qq.com>
2024-11-26 09:24:21 +00:00
RuoYi 0bf7457eb7 优化代码 2024-11-25 22:27:10 +08:00
RuoYi 0f77f524d0 面板兼容移动端显示 2024-11-25 15:39:49 +08:00
RuoYi 747d816be2 参数键值更换为多行文本 2024-11-25 12:15:21 +08:00
RuoYi 262d9e1ff0 菜单面包屑导航支持多层级显示 2024-11-22 20:44:39 +08:00
RuoYi ab37956874 分栏参数微调 2024-11-22 14:45:58 +08:00
RuoYi 86ab3bf600 用户管理支持分栏拖动 2024-11-22 12:19:56 +08:00
RuoYi f76908912e 用户头像http(s)链接支持 2024-11-20 10:42:41 +08:00
RuoYi 8df4c72ad1 update .env.staging 2024-11-20 10:41:20 +08:00
RuoYi 6bdcbabc09 update pom.xml 2024-11-08 16:31:54 +08:00
RuoYi 58fca720a9 升级pom依赖到安全版本 2024-11-08 16:24:14 +08:00
RuoYi e4ccbc6601 支持自定义显示Excel属性列 2024-11-07 22:15:27 +08:00
RuoYi 430e6d4dea 升级spring-framework到安全版本 2024-11-07 22:14:51 +08:00
RuoYi a0e6295693 升级oshi到最新版本6.6.5 2024-11-05 16:23:52 +08:00
RuoYi 52ba823328 优化无用户编号不校验数据权限 2024-11-05 16:23:42 +08:00
RuoYi 91ae9a164c 校检文件名是否包含特殊字符 2024-11-05 12:49:40 +08:00
RuoYi d3326987a4 优化身份证脱敏正则 2024-10-21 16:19:17 +08:00
若依 4de087b1ad !937 update ruoyi-ui/src/components/ImageUpload/index.vue.
Merge pull request !937 from AZP/N/A
2024-10-21 08:05:26 +00:00
AZP 5b959b32d7 update ruoyi-ui/src/components/ImageUpload/index.vue.
【fix】修复后台前端上传图片如果图片路径已经携带域名就无需增加前缀域名

Signed-off-by: AZP <2198774759@qq.com>
2024-10-21 03:39:18 +00:00
RuoYi 4358621473 优化权限更新后同步缓存 2024-10-21 10:24:45 +08:00
RuoYi adb8d51932 操作日志记录DELETE请求参数(IAMV6F) 2024-10-17 12:42:40 +08:00
RuoYi 08a5deb285 升级fastjson到最新版2.0.53 2024-10-17 12:42:24 +08:00
RuoYi dc9f3ee722 升级quill到最新版本2.0.2 2024-10-15 16:18:02 +08:00
RuoYi 78bb30bb5f 修复码生成上级菜单显示问题(I9CTIJ) 2024-09-27 16:15:17 +08:00
RuoYi 5fad997d38 修复角色禁用权限不失效问题(IAA8ZX) 2024-09-21 11:28:52 +08:00
RuoYi 22a795d041 优化代码 2024-09-08 10:29:41 +08:00
RuoYi 8a0a3a03fe 升级oshi到最新版本6.6.3 2024-08-30 21:46:03 +08:00
RuoYi ad86486285 update sqlkeyword 2024-08-30 21:45:16 +08:00
RuoYi 3ef6000794 修改时间范围日期格式 2024-07-08 16:45:36 +08:00
RuoYi f812e99a0d remove sub resultType 2024-07-08 16:38:34 +08:00
RuoYi 2feae7619f avatar add headers 2024-07-02 16:08:30 +08:00
RuoYi 212e3b4977 升级axios到最新版本0.28.1 2024-07-02 12:58:28 +08:00
RuoYi 99e66bf11c 若依 3.8.8 2024-06-30 08:02:22 +08:00
RuoYi a96d4bf2ed 菜单管理新增路由名称 2024-06-29 19:08:09 +08:00
RuoYi 8264b8fb31 删除多余的依赖 2024-06-27 11:08:31 +08:00
RuoYi 4ec32367fd 升级core-js到最新版本3.37.1 2024-06-27 10:22:55 +08:00
RuoYi 9e8aa14348 优化查表特殊字符使用反斜杠进行转义 2024-06-27 10:22:38 +08:00
RuoYi 10f68b97af 升级spring-security到安全版本,防止漏洞风险 2024-06-26 17:43:14 +08:00
RuoYi 8eff83e2b4 优化代码 2024-06-26 17:40:01 +08:00
RuoYi 7b064d84bb 升级druid到最新版本1.2.23 2024-06-25 12:29:13 +08:00
RuoYi 88560a7aa5 升级oshi到最新版本6.6.1 2024-06-25 12:28:50 +08:00
RuoYi e14f40670a 优化代码 2024-06-25 12:27:21 +08:00
RuoYi 5b98495067 cron生成的表达式hour优化 2024-06-25 12:02:23 +08:00
RuoYi 259dc67728 优化数据权限代码 2024-06-05 12:30:43 +08:00
RuoYi bc7a607033 Excel注解新增属性comboReadDict 2024-06-02 19:29:11 +08:00
RuoYi 161cd2b1ea 优化代码生成主子表关联查询方式 2024-06-02 19:28:40 +08:00
RuoYi 7480fb4020 优化导入Excel时设置dictType属性重复查缓存问题 2024-05-30 13:35:43 +08:00
RuoYi 906c3a68b8 添加新群号:151450850 2024-05-29 14:48:56 +08:00
RuoYi 084bab3494 update sql 2024-05-29 14:48:40 +08:00
RuoYi cc0efa3330 优化代码 2024-05-29 14:48:23 +08:00
RuoYi f46b1bbebd 限制用户操作数据权限范围 2024-05-29 14:48:03 +08:00
RuoYi e5f30b1a19 升级spring-framework到安全版本,防止漏洞风险 2024-04-11 16:43:48 +08:00
RuoYi 1140a6c333 新增数据脱敏过滤注解 2024-04-08 13:16:27 +08:00
RuoYi 86ca404dbf 设置表格头单元格文本形式 2024-03-22 16:44:54 +08:00
RuoYi 11320b2e13 Excel注解ColumnType类型新增文本 2024-03-22 16:23:19 +08:00
RuoYi 905c08fb2c 升级oshi到最新版本6.5.0 2024-03-19 16:38:37 +08:00
RuoYi 9386645150 定义Locale默认国际化配置 2024-03-19 16:38:03 +08:00
RuoYi bf3e2115e3 update vue.config.js 2024-03-18 14:28:28 +08:00
RuoYi 61eb54e4a1 更新compressionPlugin到6.1.2以兼容node18+ 2024-03-18 14:11:26 +08:00
RuoYi d93e2b9df0 定时任务白名单配置范围缩小 2024-03-11 11:07:29 +08:00
RuoYi 50339c6f73 update copyright 2024 2024-03-11 10:47:55 +08:00
RuoYi b83f2ff60b 添加新群号:138988063 2024-03-11 10:47:40 +08:00
RuoYi 66128f140f joblog order by 2024-03-11 09:42:15 +08:00
RuoYi 8c990ae9fc 用户密码新增非法字符验证 2024-03-01 21:53:57 +08:00
RuoYi 8836d31d77 升级oshi到最新版本6.4.13 2024-03-01 14:33:56 +08:00
RuoYi 2f624ab5f4 代码生成新增创建表结构功能 2024-03-01 14:33:09 +08:00
RuoYi 80f96b4915 升级oshi到最新版本6.4.11 2024-01-25 11:41:57 +08:00
RuoYi 7e9d050432 update http user-agent 2024-01-25 11:41:20 +08:00
RuoYi 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
oddfar 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
zhaoxc5 08d0326718 fix: delete unuse code 2023-12-25 10:22:05 +08:00
RuoYi 3f4ac65a31 remove packages 2023-12-13 11:51:17 +08:00
RuoYi 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
RuoYi 36b900cef8 若依 3.8.7 2023-12-08 09:03:30 +08:00
RuoYi ac9302e2a2 升级element-ui到最新版本2.15.14 2023-12-07 11:08:03 +08:00
RuoYi 0f7e3a744e 删除无用的代码 2023-12-07 11:07:30 +08:00
RuoYi 45656b271a 升级oshi到最新版本6.4.8 2023-12-05 11:28:42 +08:00
RuoYi 323e3b7371 升级pagehelper到最新版1.4.7 2023-12-05 11:28:18 +08:00
RuoYi bfbaa9e7b5 升级druid到最新版本1.2.20 2023-12-05 11:28:05 +08:00
RuoYi 2253a146b3 update fastjson2 2023-12-05 10:48:22 +08:00
RuoYi 2070a9252a 操作日志记录部门名称 2023-12-05 10:47:39 +08:00
RuoYi e231d78469 修复代码生成导入后必填项与数据库不匹配问题 2023-12-05 10:45:54 +08:00
RuoYi f74454b61a 删除无用的实例演示开关配置 2023-12-05 10:44:50 +08:00
RuoYi d71ee5dba1 显隐列组件支持复选框弹出类型 2023-12-01 11:20:12 +08:00
RuoYi 78b1ac4a60 代码生成支持选择前端模板类型 2023-11-30 09:38:07 +08:00
RuoYi 966a17123f 优化代码 2023-11-30 09:37:36 +08:00
RuoYi 42bb8f6445 优化头像上传参数新增文件名称 2023-11-29 12:41:04 +08:00
RuoYi 72e4cd9fb3 优化字典标签支持自定义分隔符 2023-11-29 12:40:47 +08:00
RuoYi 1525bd8b54 优化下载zip方法新增遮罩层 2023-11-29 12:40:01 +08:00
RuoYi b8e2eeaaf8 优化缓存监控图表支持跟随屏幕大小自适应调整 2023-11-29 12:39:22 +08:00
RuoYi cbcfabee2a 优化代码 2023-11-29 12:38:45 +08:00
RuoYi e6d0599b25 优化个人中心/基本资料修改时数据显示问题 2023-11-28 12:36:30 +08:00
RuoYi 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
RuoYi 4ca30f08d6 修改权限字符匹配方式 2023-11-10 15:46:27 +08:00
RuoYi 73f881c7d3 修复五级路由缓存无效问题 2023-11-10 15:31:30 +08:00
RuoYi b357aedaa3 修复内链iframe没有传递参数问题(I8DUOJ) 2023-11-10 11:13:16 +08:00
RuoYi 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
RuoYi 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
RuoYi 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
RuoYi 7b4ba0146b 升级fastjson到最新版2.0.41 2023-10-21 14:44:02 +08:00
RuoYi 3963e86537 升级oshi到最新版本6.4.6 2023-10-21 14:34:05 +08:00
RuoYi 7098acc968 登录不做数据重复提交验证 2023-10-21 14:31:12 +08:00
RuoYi 079ac841f3 添加新群号:174951577 2023-10-09 21:27:00 +08:00
RuoYi 0434b4ca7a 去掉多余的参数 2023-10-09 21:26:40 +08:00
RuoYi 8873dc9b64 富文本Editor组件检验图片格式 2023-10-02 12:45:27 +08:00
RuoYi 078a3aad5a 修复HeaderSearch组件跳转query参数丢失问题 2023-09-28 22:24:25 +08:00
RuoYi 207a9ce855 操作日志列表新增IP地址查询 2023-09-27 15:21:59 +08:00
RuoYi 9ced1e9766 全局数据存储用户编号 2023-09-27 15:21:37 +08:00
RuoYi 1926840204 优化菜单管理类型为按钮状态可选 2023-09-18 15:04:34 +08:00
RuoYi 006d46ad07 修复自定义字典样式不生效的问题(I81F03) 2023-09-14 16:55:07 +08:00
RuoYi f5a1b0c550 删除无用的传参 2023-09-01 09:37:16 +08:00
RuoYi 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
RuoYi 491b0f3db8 修复字典缓存删除方法参数错误问题(I7UDIR) 2023-08-23 14:54:20 +08:00
who's hu 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
RuoYi 90260ce2f9 修复Excels导入时无法获取到dictType字典值问题(I7M4PW) 2023-08-21 15:52:30 +08:00
RuoYi d58942c506 防重复提交数据大小限制(I7KZDA) 2023-08-21 11:57:14 +08:00
RuoYi 6a742e1d1b Excel导入数据临时文件无法删除问题(I7KIXX) 2023-08-19 15:43:57 +08:00
RuoYi 5b61aea064 修复树模板父级编码变量错误(I7JZ0L) 2023-08-19 14:34:30 +08:00
RuoYi 45ef542687 升级fastjson到最新版2.0.39 2023-08-15 12:17:27 +08:00
RuoYi 4ac7a1aa1f 升级commons.io到最新版本2.13.0 2023-08-15 11:31:38 +08:00
RuoYi c5e4459bb8 优化代码 2023-08-15 11:30:49 +08:00
RuoYi 8f67bf416b 升级oshi到最新版本6.4.4 2023-08-14 19:11:46 +08:00
RuoYi ab99a72b65 优化代码 2023-08-14 19:11:13 +08:00
RuoYi 7c9423657e Excel自定义数据处理器增加单元格/工作簿对象 2023-08-14 17:42:44 +08:00
RuoYi 128b186b8e 优化定时任务状态页面显示 2023-08-14 17:42:24 +08:00
RuoYi 68ac40eda9 update maven-plugin 2023-08-14 17:41:52 +08:00
RuoYi 5557433235 添加新群号:143961921 2023-07-28 11:12:09 +08:00
RuoYi 2517e9dddb 优化登录提示信息(I6ADCR) 2023-07-24 15:16:52 +08:00
RuoYi a0595711ca 优化页签在Firefox浏览器被遮挡的问题 2023-07-06 22:09:16 +08:00
RuoYi 1ffb6379f7 排序属性orderBy参数限制长度 2023-07-06 22:09:02 +08:00
RuoYi 4d5c204b9a 优化代码 2023-07-06 22:08:47 +08:00
RuoYi 8ee740ef49 update sql 2023-07-06 22:07:00 +08:00
RuoYi 6a811d9824 若依 3.8.6 2023-06-30 08:43:54 +08:00
RuoYi 1c9c076280 升级oshi到最新版本6.4.3 2023-06-29 08:50:27 +08:00
RuoYi 918f94d8da 升级fastjson到最新版2.0.34 2023-06-29 08:38:33 +08:00
RuoYi 5db610d16f optimized code 2023-06-28 21:31:25 +08:00
RuoYi cc6f983ee3 升级spring-boot到最新版本2.5.15 2023-06-24 14:49:03 +08:00
RuoYi afe2852bbb update banner.txt 2023-06-24 14:48:54 +08:00
RuoYi 9c7d302b94 升级element-ui到最新版本2.15.13 2023-06-24 10:57:40 +08:00
RuoYi 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
WhiskyZulu 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
yangfanao 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
RuoYi 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
loren-li eff06c110f 修复路由跳转被阻止时vue-router内部产生报错信息问题 2023-04-20 15:02:38 +08:00
刘元博 6a18e06339 去除element滚动条 2023-04-17 18:52:46 +08:00
zouhuu 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
zouhuu 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
RuoYi 6ad345331d 修复开启TopNav后一级菜单路由参数设置无效问题(I6T1DK) 2023-04-11 16:51:55 +08:00
RuoYi 5a634a4ecd 修复导入用户时无法更新存在用户数据的问题 2023-04-10 18:03:34 +08:00
RuoYi 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
Nymph2333 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
bell 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
RuoYi a33090c90e 添加新群号:101046199 2023-04-05 17:52:27 +08:00
RuoYi 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
wzy1024 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
“baozhigang” 5a25212509 解决表字段comment过长问题 2023-03-30 20:10:22 +08:00
Jimi 95742bf5bd style:移除vue-multiselect样式(项目中并未安装vue-multiselect plugin) 2023-03-30 14:58:06 +08:00
刘元博 4eea8cdbb0 优化选择图标组件 2023-03-18 10:59:05 +08:00
RuoYi cfce89be7d 升级fastjson到最新版2.0.25 2023-03-18 10:30:34 +08:00
RuoYi ce7e12ec1d delete build style 2023-03-18 09:31:26 +08:00
RuoYi 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
0慕容雪0 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
Giovanni 7a090bda1e 关闭当前tab页应跳转最右侧tab页而非首页 2023-03-10 18:04:56 +08:00
0慕容雪0 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
RuoYi 641e550d7f 优化修改密码日志存储明文问题(I6ESO9) 2023-03-05 12:06:27 +08:00
RuoYi 81a01a1d9d 优化文件下载出现的异常(I6DLNU) 2023-02-28 13:33:12 +08:00
RuoYi 6523fe59a2 日志管理使用索引提升查询性能 2023-02-23 10:01:16 +08:00
RuoYi 90970eb9fe 修复isMatchedIp的参数判断产生空指针的问题 2023-02-22 10:29:28 +08:00
RuoYi 3402b69556 移除apache/commons-fileupload依赖 2023-02-21 18:06:28 +08:00
RuoYi 2c5e3e429f 升级druid到最新版本1.2.16 2023-02-21 18:05:22 +08:00
RuoYi 96ba768f50 优化代码 2023-02-21 18:02:00 +08:00
RuoYi 1268637e58 支持登录IP黑名单限制 2023-02-21 09:00:44 +08:00
RuoYi 61caa7966b 日志注解支持排除指定的请求参数 2023-02-20 16:25:40 +08:00
RuoYi a5f95eddab 新增监控页面图标显示 2023-02-17 08:55:22 +08:00
RuoYi ade70583e9 操作日志新增消耗时间属性 2023-02-16 10:22:39 +08:00
RuoYi 5676cf9ad4 修复匿名注解Anonymous空指针问题(I683DT) 2023-02-06 11:20:12 +08:00
RuoYi c3d0cd5f8c update copyright 2023 2023-02-04 22:26:02 +08:00
RuoYi eb96afee64 连接池Druid支持新的配置connectTimeout和socketTimeout 2023-02-04 22:25:49 +08:00
RuoYi 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
Giovanni 9926f73cd0 tagsView右选框,首页不应该存在关闭左侧选项 2023-02-01 15:58:40 +08:00
RuoYi 492919d4af 升级element-ui到最新版本2.15.12 2023-01-19 12:05:01 +08:00
RuoYi a7ff50e695 升级fastjson到最新版2.0.23 2023-01-19 12:04:11 +08:00
RuoYi 71e7e1d6dd 字符未使用下划线不进行驼峰式处理 2023-01-19 12:02:48 +08:00
RuoYi 5073f95ccd 添加新群号:108482800 2023-01-11 12:55:29 +08:00
351 changed files with 17205 additions and 7810 deletions
+26 -8
View File
@@ -1,11 +1,11 @@
<p align="center">
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.8.5</h1>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.9.2</h1>
<h4 align="center">基于SpringBoot+Vue前后端分离的Java快速开发框架</h4>
<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"><img src="https://img.shields.io/badge/RuoYi-v3.8.5-brightgreen.svg"></a>
<a href="https://gitee.com/y_project/RuoYi-Vue"><img src="https://img.shields.io/badge/RuoYi-v3.9.2-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>
</p>
@@ -18,12 +18,30 @@
* 权限认证使用Jwt,支持多终端认证系统。
* 支持加载动态权限菜单,多方式轻松权限控制。
* 高效率开发,使用代码生成器可以一键生成前后端代码。
* 提供了技术栈([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](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;
* 阿里云优惠券:[点我领取](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;
# 版本分支
RuoYi-Vue 后端项目提供 Spring Boot 2.x / 3.x / 4.x 多版本分支的并行维护。
| 名称 | 说明 | 地址 |
| :---------------- | :------------------------ | :------------------------------------------------------ |
| master 默认分支 | Spring Boot 4.x (JDK 17+) | https://gitee.com/y_project/RuoYi-Vue |
| springboot3 分支 | Spring Boot 3.x (JDK 17+) | https://gitee.com/y_project/RuoYi-Vue/tree/springboot3 |
| springboot2 分支 | Spring Boot 2.x (JDK 8+) | https://gitee.com/y_project/RuoYi-Vue/tree/springboot2 |
RuoYi-Vue 前端项目提供 Vue 2.x / 3.x / JavaScript TypeScript 版本均可混用搭配
| 项目名称 | **RuoYi-Vue** | **RuoYi-Vue3** | **RuoYi-Vue3-TypeScript** |
| :--- | :--- | :--- | :--- |
| **前端框架** | Vue 2 | Vue 3 | Vue 3 |
| **脚本语言** | JavaScript | JavaScript | TypeScript |
| **构建工具** | Vue CLI | Vite | Vite |
| **UI 组件库** | Element UI | Element Plus | Element Plus |
| **状态管理** | Vuex | Pinia | Pinia |
| **路由管理** | Vue Router 3 | Vue Router 4 | Vue Router 4 |
| **核心特点** | 1. 技术栈经典稳定<br>2. 社区资料丰富<br>3. 当前维护重心已转移 | 1. 现代前端技术栈<br>2. 开发体验与性能更优<br>3. 官方主推的活跃版本 | 1. 类型加持,减少沟通成本<br>2. 开发时有提示,效率更高<br>3. 多人协作企业级开发项目 |
| **仓库地址** | [RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue) | [RuoYi-Vue3](https://gitcode.com/yangzongzhuan/RuoYi-Vue3) | [RuoYi-Vue3-TypeScript](https://gitcode.com/yangzongzhuan/RuoYi-Vue3/tree/typescript) |
## 内置功能
@@ -94,4 +112,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/已满-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群: [![加入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) [![加入QQ群](https://img.shields.io/badge/已满-224622315-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=F58bgRa-Dp-rsQJThiJqIYv8t4-lWfXh&authKey=UmUs4CVG5OPA1whvsa4uSespOvyd8%2FAr9olEGaWAfdLmfKQk%2FVBp2YU3u2xXXt76&noverify=0&group_code=224622315) [![加入QQ群](https://img.shields.io/badge/已满-287842588-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Nxb2EQ5qozWa218Wbs7zgBnjLSNk_tVT&authKey=obBKXj6SBKgrFTJZx0AqQnIYbNOvBB2kmgwWvGhzxR67RoRr84%2Bus5OadzMcdJl5&noverify=0&group_code=287842588) [![加入QQ群](https://img.shields.io/badge/已满-187944233-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=numtK1M_I4eVd2Gvg8qtbuL8JgX42qNh&authKey=giV9XWMaFZTY%2FqPlmWbkB9g3fi0Ev5CwEtT9Tgei0oUlFFCQLDp4ozWRiVIzubIm&noverify=0&group_code=187944233) [![加入QQ群](https://img.shields.io/badge/已满-228578329-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=G6r5KGCaa3pqdbUSXNIgYloyb8e0_L0D&authKey=4w8tF1eGW7%2FedWn%2FHAypQksdrML%2BDHolQSx7094Agm7Luakj9EbfPnSTxSi2T1LQ&noverify=0&group_code=228578329) [![加入QQ群](https://img.shields.io/badge/已满-191164766-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=GsOo-OLz53J8y_9TPoO6XXSGNRTgbFxA&authKey=R7Uy%2Feq%2BZsoKNqHvRKhiXpypW7DAogoWapOawUGHokJSBIBIre2%2FoiAZeZBSLuBc&noverify=0&group_code=191164766) [![加入QQ群](https://img.shields.io/badge/已满-174569686-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=PmYavuzsOthVqfdAPbo4uAeIbu7Ttjgc&authKey=p52l8%2FXa4PS1JcEmS3VccKSwOPJUZ1ZfQ69MEKzbrooNUljRtlKjvsXf04bxNp3G&noverify=0&group_code=174569686) [![加入QQ群](https://img.shields.io/badge/127358632-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=M9y5NjAl44lAL_Vh2crmEehZU_PMU6KS&authKey=ZSDz8hEREWSaPuxQV3gEwqGIaGjfRNnkB4rJjf0IvXhrSUGSGwQFmBA%2Boe8HFxyl&noverify=0&group_code=127358632) 点击按钮入群。
Binary file not shown.
+42 -49
View File
@@ -6,33 +6,33 @@
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId>
<version>3.8.5</version>
<version>3.9.2</version>
<name>ruoyi</name>
<url>http://www.ruoyi.vip</url>
<description>若依管理系统</description>
<properties>
<ruoyi.version>3.8.5</ruoyi.version>
<ruoyi.version>3.9.2</ruoyi.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<druid.version>1.2.15</druid.version>
<bitwalker.version>1.21</bitwalker.version>
<swagger.version>3.0.0</swagger.version>
<java.version>17</java.version>
<spring-boot.version>4.0.3</spring-boot.version>
<mybatis-spring-boot.version>4.0.1</mybatis-spring-boot.version>
<druid.version>1.2.28</druid.version>
<yauaa.version>8.1.0</yauaa.version>
<kaptcha.version>2.3.3</kaptcha.version>
<pagehelper.boot.version>1.4.6</pagehelper.boot.version>
<fastjson.version>2.0.20</fastjson.version>
<oshi.version>6.4.0</oshi.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>
<pagehelper.boot.version>2.1.1</pagehelper.boot.version>
<fastjson.version>2.0.61</fastjson.version>
<oshi.version>6.10.0</oshi.version>
<commons.io.version>2.21.0</commons.io.version>
<poi.version>4.1.2</poi.version>
<velocity.version>2.3</velocity.version>
<jwt.version>0.9.1</jwt.version>
<jaxb-api.version>2.3.1</jaxb-api.version>
<springdoc.version>3.0.2</springdoc.version>
</properties>
<!-- 依赖声明 -->
<dependencyManagement>
<dependencies>
@@ -41,7 +41,7 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.5.14</version>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -49,15 +49,15 @@
<!-- 阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<artifactId>druid-spring-boot-4-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- 解析客户端操作系统、浏览器等 -->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>${bitwalker.version}</version>
<groupId>nl.basjes.parse.useragent</groupId>
<artifactId>yauaa</artifactId>
<version>${yauaa.version}</version>
</dependency>
<!-- pagehelper 分页插件 -->
@@ -67,6 +67,18 @@
<version>${pagehelper.boot.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot.version}</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>${jaxb-api.version}</version>
</dependency>
<!-- 获取系统信息 -->
<dependency>
<groupId>com.github.oshi</groupId>
@@ -74,17 +86,11 @@
<version>${oshi.version}</version>
</dependency>
<!-- Swagger3依赖 -->
<!-- spring-doc -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${swagger.version}</version>
<exclusions>
<exclusion>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
</exclusion>
</exclusions>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<!-- io常用工具类 -->
@@ -94,13 +100,6 @@
<version>${commons.io.version}</version>
</dependency>
<!-- 文件上传工具类 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${commons.fileupload.version}</version>
</dependency>
<!-- excel工具 -->
<dependency>
<groupId>org.apache.poi</groupId>
@@ -115,13 +114,6 @@
<version>${velocity.version}</version>
</dependency>
<!-- collections工具类 -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>${commons.collections.version}</version>
</dependency>
<!-- 阿里JSON解析器 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
@@ -191,23 +183,24 @@
</modules>
<packaging>pom</packaging>
<dependencies>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<version>3.13.0</version>
<configuration>
<parameters>true</parameters>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
</plugins>
</build>
+7 -15
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.8.5</version>
<version>3.9.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
@@ -24,23 +24,16 @@
<optional>true</optional> <!-- 表示依赖不会传递 -->
</dependency>
<!-- swagger3-->
<!-- spring-doc -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
</dependency>
<!-- 防止进入swagger页面报类型转换错误,排除3.0.0中的引用,手动增加1.6.2版本 -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
<version>1.6.2</version>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<!-- Mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- 核心模块-->
@@ -68,9 +61,8 @@
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.1.RELEASE</version>
<configuration>
<fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
<addResources>true</addResources>
</configuration>
<executions>
<execution>
@@ -2,7 +2,7 @@ package com.ruoyi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
/**
* 启动程序
@@ -3,9 +3,9 @@ package com.ruoyi.web.controller.common;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import jakarta.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.web.bind.annotation.GetMapping;
@@ -2,8 +2,8 @@ package com.ruoyi.web.controller.common;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -14,7 +14,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUploadUtils;
@@ -35,7 +34,7 @@ public class CommonController
@Autowired
private ServerConfig serverConfig;
private static final String FILE_DELIMETER = ",";
private static final String FILE_DELIMITER = ",";
/**
* 通用下载请求
@@ -120,10 +119,10 @@ public class CommonController
originalFilenames.add(file.getOriginalFilename());
}
AjaxResult ajax = AjaxResult.success();
ajax.put("urls", StringUtils.join(urls, FILE_DELIMETER));
ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMETER));
ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMETER));
ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMETER));
ajax.put("urls", StringUtils.join(urls, FILE_DELIMITER));
ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMITER));
ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMITER));
ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMITER));
return ajax;
}
catch (Exception e)
@@ -148,7 +147,7 @@ public class CommonController
// 本地资源路径
String localPath = RuoYiConfig.getProfile();
// 数据库资源地址
String downloadPath = localPath + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX);
String downloadPath = localPath + FileUtils.stripPrefix(resource);
// 下载名称
String downloadName = StringUtils.substringAfterLast(downloadPath, "/");
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
@@ -7,6 +7,7 @@ import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
@@ -44,6 +45,7 @@ public class CacheController
caches.add(new SysCache(CacheConstants.PWD_ERR_CNT_KEY, "密码错误次数"));
}
@SuppressWarnings("deprecation")
@PreAuthorize("@ss.hasPermi('monitor:cache:list')")
@GetMapping()
public AjaxResult getInfo() throws Exception
@@ -80,7 +82,7 @@ public class CacheController
public AjaxResult getCacheKeys(@PathVariable String cacheName)
{
Set<String> cacheKeys = redisTemplate.keys(cacheName + "*");
return AjaxResult.success(cacheKeys);
return AjaxResult.success(new TreeSet<>(cacheKeys));
}
@PreAuthorize("@ss.hasPermi('monitor:cache:list')")
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.monitor;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.monitor;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -49,24 +49,15 @@ public class SysUserOnlineController extends BaseController
LoginUser user = redisCache.getCacheObject(key);
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))
{
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()))
{
if (StringUtils.equals(userName, user.getUsername()))
{
userOnlineList.add(userOnlineService.selectOnlineByUserName(userName, user));
}
userOnlineList.add(userOnlineService.selectOnlineByUserName(userName, user));
}
else
{
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.system;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
@@ -14,7 +14,6 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
@@ -84,7 +83,7 @@ public class SysConfigController extends BaseController
@PostMapping
public AjaxResult add(@Validated @RequestBody SysConfig config)
{
if (UserConstants.NOT_UNIQUE.equals(configService.checkConfigKeyUnique(config)))
if (!configService.checkConfigKeyUnique(config))
{
return error("新增参数'" + config.getConfigName() + "'失败,参数键名已存在");
}
@@ -100,7 +99,7 @@ public class SysConfigController extends BaseController
@PutMapping
public AjaxResult edit(@Validated @RequestBody SysConfig config)
{
if (UserConstants.NOT_UNIQUE.equals(configService.checkConfigKeyUnique(config)))
if (!configService.checkConfigKeyUnique(config))
{
return error("修改参数'" + config.getConfigName() + "'失败,参数键名已存在");
}
@@ -1,6 +1,7 @@
package com.ruoyi.web.controller.system;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -76,7 +77,7 @@ public class SysDeptController extends BaseController
@PostMapping
public AjaxResult add(@Validated @RequestBody SysDept dept)
{
if (UserConstants.NOT_UNIQUE.equals(deptService.checkDeptNameUnique(dept)))
if (!deptService.checkDeptNameUnique(dept))
{
return error("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在");
}
@@ -94,7 +95,7 @@ public class SysDeptController extends BaseController
{
Long deptId = dept.getDeptId();
deptService.checkDeptDataScope(deptId);
if (UserConstants.NOT_UNIQUE.equals(deptService.checkDeptNameUnique(dept)))
if (!deptService.checkDeptNameUnique(dept))
{
return error("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在");
}
@@ -110,6 +111,20 @@ public class SysDeptController extends BaseController
return toAjax(deptService.updateDept(dept));
}
/**
* 保存部门排序
*/
@PreAuthorize("@ss.hasPermi('system:dept:edit')")
@Log(title = "保存部门排序", businessType = BusinessType.UPDATE)
@PutMapping("/updateSort")
public AjaxResult updateSort(@RequestBody Map<String, String> params)
{
String[] deptIds = params.get("deptIds").split(",");
String[] orderNums = params.get("orderNums").split(",");
deptService.updateDeptSort(deptIds, orderNums);
return success();
}
/**
* 删除部门
*/
@@ -2,7 +2,7 @@ package com.ruoyi.web.controller.system;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.system;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
@@ -14,7 +14,6 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysDictType;
@@ -72,7 +71,7 @@ public class SysDictTypeController extends BaseController
@PostMapping
public AjaxResult add(@Validated @RequestBody SysDictType dict)
{
if (UserConstants.NOT_UNIQUE.equals(dictTypeService.checkDictTypeUnique(dict)))
if (!dictTypeService.checkDictTypeUnique(dict))
{
return error("新增字典'" + dict.getDictName() + "'失败,字典类型已存在");
}
@@ -88,7 +87,7 @@ public class SysDictTypeController extends BaseController
@PutMapping
public AjaxResult edit(@Validated @RequestBody SysDictType dict)
{
if (UserConstants.NOT_UNIQUE.equals(dictTypeService.checkDictTypeUnique(dict)))
if (!dictTypeService.checkDictTypeUnique(dict))
{
return error("修改字典'" + dict.getDictName() + "'失败,字典类型已存在");
}
@@ -1,10 +1,17 @@
package com.ruoyi.web.controller.system;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysUserService;
/**
* 首页
@@ -18,6 +25,9 @@ public class SysIndexController
@Autowired
private RuoYiConfig ruoyiConfig;
@Autowired
private ISysUserService userService;
/**
* 访问首页,提示语
*/
@@ -26,4 +36,29 @@ public class SysIndexController
{
return StringUtils.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion());
}
/**
* 解锁屏幕
*/
@PostMapping("/unlockscreen")
public AjaxResult unlockScreen(@RequestBody Map<String, String> body)
{
String password = body.get("password");
if (StringUtils.isEmpty(password))
{
return AjaxResult.error("密码不能为空");
}
String username = SecurityUtils.getUsername();
SysUser user = userService.selectUserByUserName(username);
if (user == null)
{
return AjaxResult.error("服务器超时,请重新登录");
}
if (!SecurityUtils.matchesPassword(password, user.getPassword()))
{
return AjaxResult.error("密码错误,请重新输入");
}
return AjaxResult.success("解锁成功");
}
}
@@ -1,5 +1,6 @@
package com.ruoyi.web.controller.system;
import java.util.Date;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
@@ -12,9 +13,15 @@ import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysMenu;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginBody;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.web.service.SysLoginService;
import com.ruoyi.framework.web.service.SysPermissionService;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysMenuService;
/**
@@ -34,6 +41,12 @@ public class SysLoginController
@Autowired
private SysPermissionService permissionService;
@Autowired
private TokenService tokenService;
@Autowired
private ISysConfigService configService;
/**
* 登录方法
*
@@ -59,15 +72,24 @@ public class SysLoginController
@GetMapping("getInfo")
public AjaxResult getInfo()
{
SysUser user = SecurityUtils.getLoginUser().getUser();
LoginUser loginUser = SecurityUtils.getLoginUser();
SysUser user = loginUser.getUser();
// 角色集合
Set<String> roles = permissionService.getRolePermission(user);
// 权限集合
Set<String> permissions = permissionService.getMenuPermission(user);
if (!loginUser.getPermissions().equals(permissions))
{
loginUser.setPermissions(permissions);
tokenService.refreshToken(loginUser);
}
AjaxResult ajax = AjaxResult.success();
ajax.put("user", user);
ajax.put("roles", roles);
ajax.put("permissions", permissions);
ajax.put("pwdChrtype", getSysAccountChrtype());
ajax.put("isDefaultModifyPwd", initPasswordIsModify(user.getPwdUpdateDate()));
ajax.put("isPasswordExpired", passwordIsExpiration(user.getPwdUpdateDate()));
return ajax;
}
@@ -83,4 +105,34 @@ public class SysLoginController
List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
return AjaxResult.success(menuService.buildMenus(menus));
}
// 获取用户密码自定义配置规则
public String getSysAccountChrtype()
{
return Convert.toStr(configService.selectConfigByKey("sys.account.chrtype"), "0");
}
// 检查初始密码是否提醒修改
public boolean initPasswordIsModify(Date pwdUpdateDate)
{
Integer initPasswordModify = Convert.toInt(configService.selectConfigByKey("sys.account.initPasswordModify"));
return initPasswordModify != null && initPasswordModify == 1 && pwdUpdateDate == null;
}
// 检查密码是否过期
public boolean passwordIsExpiration(Date pwdUpdateDate)
{
Integer passwordValidateDays = Convert.toInt(configService.selectConfigByKey("sys.account.passwordValidateDays"));
if (passwordValidateDays != null && passwordValidateDays > 0)
{
if (StringUtils.isNull(pwdUpdateDate))
{
// 如果从未修改过初始密码,直接提醒过期
return true;
}
Date nowDate = DateUtils.getNowDate();
return DateUtils.differentDaysByMillisecond(nowDate, pwdUpdateDate) > passwordValidateDays;
}
return false;
}
}
@@ -1,6 +1,7 @@
package com.ruoyi.web.controller.system;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
@@ -85,7 +86,7 @@ public class SysMenuController extends BaseController
@PostMapping
public AjaxResult add(@Validated @RequestBody SysMenu menu)
{
if (UserConstants.NOT_UNIQUE.equals(menuService.checkMenuNameUnique(menu)))
if (!menuService.checkMenuNameUnique(menu))
{
return error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
}
@@ -93,6 +94,10 @@ public class SysMenuController extends BaseController
{
return error("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
}
else if (!menuService.checkRouteConfigUnique(menu))
{
return error("新增菜单'" + menu.getMenuName() + "'失败,路由名称或地址已存在");
}
menu.setCreateBy(getUsername());
return toAjax(menuService.insertMenu(menu));
}
@@ -105,7 +110,7 @@ public class SysMenuController extends BaseController
@PutMapping
public AjaxResult edit(@Validated @RequestBody SysMenu menu)
{
if (UserConstants.NOT_UNIQUE.equals(menuService.checkMenuNameUnique(menu)))
if (!menuService.checkMenuNameUnique(menu))
{
return error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
}
@@ -117,10 +122,28 @@ public class SysMenuController extends BaseController
{
return error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己");
}
else if (!menuService.checkRouteConfigUnique(menu))
{
return error("修改菜单'" + menu.getMenuName() + "'失败,路由名称或地址已存在");
}
menu.setUpdateBy(getUsername());
return toAjax(menuService.updateMenu(menu));
}
/**
* 保存菜单排序
*/
@PreAuthorize("@ss.hasPermi('system:menu:edit')")
@Log(title = "保存菜单排序", businessType = BusinessType.UPDATE)
@PutMapping("/updateSort")
public AjaxResult updateSort(@RequestBody Map<String, String> params)
{
String[] menuIds = params.get("menuIds").split(",");
String[] orderNums = params.get("orderNums").split(",");
menuService.updateMenuSort(menuIds, orderNums);
return success();
}
/**
* 删除菜单
*/
@@ -11,13 +11,16 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.SysNotice;
import com.ruoyi.system.service.ISysNoticeReadService;
import com.ruoyi.system.service.ISysNoticeService;
/**
@@ -32,6 +35,9 @@ public class SysNoticeController extends BaseController
@Autowired
private ISysNoticeService noticeService;
@Autowired
private ISysNoticeReadService noticeReadService;
/**
* 获取通知公告列表
*/
@@ -47,7 +53,6 @@ public class SysNoticeController extends BaseController
/**
* 根据通知公告编号获取详细信息
*/
@PreAuthorize("@ss.hasPermi('system:notice:query')")
@GetMapping(value = "/{noticeId}")
public AjaxResult getInfo(@PathVariable Long noticeId)
{
@@ -78,6 +83,59 @@ public class SysNoticeController extends BaseController
return toAjax(noticeService.updateNotice(notice));
}
/**
* 首页顶部公告列表(返回全部正常公告,带当前用户已读标记,最多5条)
*/
@GetMapping("/listTop")
@ResponseBody
public AjaxResult listTop()
{
Long userId = getUserId();
List<SysNotice> list = noticeReadService.selectNoticeListWithReadStatus(userId, 5);
long unreadCount = list.stream().filter(n -> !n.getIsRead()).count();
AjaxResult result = AjaxResult.success(list);
result.put("unreadCount", unreadCount);
return result;
}
/**
* 标记公告已读
*/
@PostMapping("/markRead")
@ResponseBody
public AjaxResult markRead(Long noticeId)
{
Long userId = getUserId();
noticeReadService.markRead(noticeId, userId);
return success();
}
/**
* 批量标记已读
*/
@PostMapping("/markReadAll")
@ResponseBody
public AjaxResult markReadAll(String ids)
{
Long userId = getUserId();
Long[] noticeIds = Convert.toLongArray(ids);
noticeReadService.markReadBatch(userId, noticeIds);
return success();
}
/**
* 已读用户列表数据
*/
@PreAuthorize("@ss.hasPermi('system:notice:list')")
@GetMapping("/readUsers/list")
@ResponseBody
public TableDataInfo readUsersList(Long noticeId, String searchValue)
{
startPage();
List<?> list = noticeReadService.selectReadUsersByNoticeId(noticeId, searchValue);
return getDataTable(list);
}
/**
* 删除通知公告
*/
@@ -86,6 +144,7 @@ public class SysNoticeController extends BaseController
@DeleteMapping("/{noticeIds}")
public AjaxResult remove(@PathVariable Long[] noticeIds)
{
noticeReadService.deleteByNoticeIds(noticeIds);
return toAjax(noticeService.deleteNoticeByIds(noticeIds));
}
}
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.system;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
@@ -14,7 +14,6 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
@@ -75,11 +74,11 @@ public class SysPostController extends BaseController
@PostMapping
public AjaxResult add(@Validated @RequestBody SysPost post)
{
if (UserConstants.NOT_UNIQUE.equals(postService.checkPostNameUnique(post)))
if (!postService.checkPostNameUnique(post))
{
return error("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在");
}
else if (UserConstants.NOT_UNIQUE.equals(postService.checkPostCodeUnique(post)))
else if (!postService.checkPostCodeUnique(post))
{
return error("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在");
}
@@ -95,11 +94,11 @@ public class SysPostController extends BaseController
@PutMapping
public AjaxResult edit(@Validated @RequestBody SysPost post)
{
if (UserConstants.NOT_UNIQUE.equals(postService.checkPostNameUnique(post)))
if (!postService.checkPostNameUnique(post))
{
return error("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在");
}
else if (UserConstants.NOT_UNIQUE.equals(postService.checkPostCodeUnique(post)))
else if (!postService.checkPostCodeUnique(post))
{
return error("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在");
}
@@ -1,5 +1,6 @@
package com.ruoyi.web.controller.system;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@@ -11,15 +12,16 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUploadUtils;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.common.utils.file.MimeTypeUtils;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.service.ISysUserService;
@@ -61,29 +63,22 @@ public class SysProfileController extends BaseController
public AjaxResult updateProfile(@RequestBody SysUser user)
{
LoginUser loginUser = getLoginUser();
SysUser sysUser = loginUser.getUser();
user.setUserName(sysUser.getUserName());
if (StringUtils.isNotEmpty(user.getPhonenumber())
&& UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user)))
SysUser currentUser = loginUser.getUser();
currentUser.setNickName(user.getNickName());
currentUser.setEmail(user.getEmail());
currentUser.setPhonenumber(user.getPhonenumber());
currentUser.setSex(user.getSex());
if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(currentUser))
{
return error("修改用户'" + user.getUserName() + "'失败,手机号码已存在");
return error("修改用户'" + loginUser.getUsername() + "'失败,手机号码已存在");
}
if (StringUtils.isNotEmpty(user.getEmail())
&& UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user)))
if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(currentUser))
{
return error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
return error("修改用户'" + loginUser.getUsername() + "'失败,邮箱账号已存在");
}
user.setUserId(sysUser.getUserId());
user.setPassword(null);
user.setAvatar(null);
user.setDeptId(null);
if (userService.updateUserProfile(user) > 0)
if (userService.updateUserProfile(currentUser) > 0)
{
// 更新缓存用户信息
sysUser.setNickName(user.getNickName());
sysUser.setPhonenumber(user.getPhonenumber());
sysUser.setEmail(user.getEmail());
sysUser.setSex(user.getSex());
tokenService.setLoginUser(loginUser);
return success();
}
@@ -95,11 +90,14 @@ public class SysProfileController extends BaseController
*/
@Log(title = "个人信息", businessType = BusinessType.UPDATE)
@PutMapping("/updatePwd")
public AjaxResult updatePwd(String oldPassword, String newPassword)
public AjaxResult updatePwd(@RequestBody Map<String, String> params)
{
String oldPassword = params.get("oldPassword");
String newPassword = params.get("newPassword");
LoginUser loginUser = getLoginUser();
String userName = loginUser.getUsername();
String password = loginUser.getPassword();
Long userId = loginUser.getUserId();
SysUser user = userService.selectUserById(userId);
String password = user.getPassword();
if (!SecurityUtils.matchesPassword(oldPassword, password))
{
return error("修改密码失败,旧密码错误");
@@ -108,10 +106,12 @@ public class SysProfileController extends BaseController
{
return error("新密码不能与旧密码相同");
}
if (userService.resetUserPwd(userName, SecurityUtils.encryptPassword(newPassword)) > 0)
newPassword = SecurityUtils.encryptPassword(newPassword);
if (userService.resetUserPwd(userId, newPassword) > 0)
{
// 更新缓存用户密码
loginUser.getUser().setPassword(SecurityUtils.encryptPassword(newPassword));
// 更新缓存用户密码&密码最后更新时间
loginUser.getUser().setPwdUpdateDate(DateUtils.getNowDate());
loginUser.getUser().setPassword(newPassword);
tokenService.setLoginUser(loginUser);
return success();
}
@@ -128,9 +128,14 @@ public class SysProfileController extends BaseController
if (!file.isEmpty())
{
LoginUser loginUser = getLoginUser();
String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION);
if (userService.updateUserAvatar(loginUser.getUsername(), avatar))
String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION, true);
if (userService.updateUserAvatar(loginUser.getUserId(), avatar))
{
String oldAvatar = loginUser.getUser().getAvatar();
if (StringUtils.isNotEmpty(oldAvatar))
{
FileUtils.deleteFile(RuoYiConfig.getProfile() + FileUtils.stripPrefix(oldAvatar));
}
AjaxResult ajax = AjaxResult.success();
ajax.put("imgUrl", avatar);
// 更新缓存用户头像
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.system;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
@@ -14,16 +14,13 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.controller.BaseController;
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.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.service.SysPermissionService;
import com.ruoyi.framework.web.service.TokenService;
@@ -94,11 +91,11 @@ public class SysRoleController extends BaseController
@PostMapping
public AjaxResult add(@Validated @RequestBody SysRole role)
{
if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role)))
if (!roleService.checkRoleNameUnique(role))
{
return error("新增角色'" + role.getRoleName() + "'失败,角色名称已存在");
}
else if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role)))
else if (!roleService.checkRoleKeyUnique(role))
{
return error("新增角色'" + role.getRoleName() + "'失败,角色权限已存在");
}
@@ -117,11 +114,11 @@ public class SysRoleController extends BaseController
{
roleService.checkRoleAllowed(role);
roleService.checkRoleDataScope(role.getRoleId());
if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role)))
if (!roleService.checkRoleNameUnique(role))
{
return error("修改角色'" + role.getRoleName() + "'失败,角色名称已存在");
}
else if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role)))
else if (!roleService.checkRoleKeyUnique(role))
{
return error("修改角色'" + role.getRoleName() + "'失败,角色权限已存在");
}
@@ -129,14 +126,8 @@ public class SysRoleController extends BaseController
if (roleService.updateRole(role) > 0)
{
// 更新缓存用户权限
LoginUser loginUser = getLoginUser();
if (StringUtils.isNotNull(loginUser.getUser()) && !loginUser.getUser().isAdmin())
{
loginUser.setPermissions(permissionService.getMenuPermission(loginUser.getUser()));
loginUser.setUser(userService.selectUserByUserName(loginUser.getUser().getUserName()));
tokenService.setLoginUser(loginUser);
}
// 刷新所有持有该角色的在线用户权限
tokenService.refreshPermissionByRoleId(role.getRoleId(), permissionService);
return success();
}
return error("修改角色'" + role.getRoleName() + "'失败,请联系管理员");
@@ -2,7 +2,7 @@ package com.ruoyi.web.controller.system;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -17,7 +17,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysDept;
@@ -102,18 +101,18 @@ public class SysUserController extends BaseController
@GetMapping(value = { "/", "/{userId}" })
public AjaxResult getInfo(@PathVariable(value = "userId", required = false) Long userId)
{
userService.checkUserDataScope(userId);
AjaxResult ajax = AjaxResult.success();
List<SysRole> roles = roleService.selectRoleAll();
ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
ajax.put("posts", postService.selectPostAll());
if (StringUtils.isNotNull(userId))
{
userService.checkUserDataScope(userId);
SysUser sysUser = userService.selectUserById(userId);
ajax.put(AjaxResult.DATA_TAG, sysUser);
ajax.put("postIds", postService.selectPostListByUserId(userId));
ajax.put("roleIds", sysUser.getRoles().stream().map(SysRole::getRoleId).collect(Collectors.toList()));
}
List<SysRole> roles = roleService.selectRoleAll();
ajax.put("roles", SecurityUtils.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
ajax.put("posts", postService.selectPostAll());
return ajax;
}
@@ -125,17 +124,17 @@ public class SysUserController extends BaseController
@PostMapping
public AjaxResult add(@Validated @RequestBody SysUser user)
{
if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(user)))
deptService.checkDeptDataScope(user.getDeptId());
roleService.checkRoleDataScope(user.getRoleIds());
if (!userService.checkUserNameUnique(user))
{
return error("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
}
else if (StringUtils.isNotEmpty(user.getPhonenumber())
&& UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user)))
else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user))
{
return error("新增用户'" + user.getUserName() + "'失败,手机号码已存在");
}
else if (StringUtils.isNotEmpty(user.getEmail())
&& UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user)))
else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user))
{
return error("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
}
@@ -154,17 +153,17 @@ public class SysUserController extends BaseController
{
userService.checkUserAllowed(user);
userService.checkUserDataScope(user.getUserId());
if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(user)))
deptService.checkDeptDataScope(user.getDeptId());
roleService.checkRoleDataScope(user.getRoleIds());
if (!userService.checkUserNameUnique(user))
{
return error("修改用户'" + user.getUserName() + "'失败,登录账号已存在");
}
else if (StringUtils.isNotEmpty(user.getPhonenumber())
&& UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user)))
else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user))
{
return error("修改用户'" + user.getUserName() + "'失败,手机号码已存在");
}
else if (StringUtils.isNotEmpty(user.getEmail())
&& UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user)))
else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user))
{
return error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
}
@@ -227,7 +226,7 @@ public class SysUserController extends BaseController
SysUser user = userService.selectUserById(userId);
List<SysRole> roles = roleService.selectRolesByUserId(userId);
ajax.put("user", user);
ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
ajax.put("roles", SecurityUtils.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
return ajax;
}
@@ -240,6 +239,7 @@ public class SysUserController extends BaseController
public AjaxResult insertAuthRole(Long userId, Long[] roleIds)
{
userService.checkUserDataScope(userId);
roleService.checkRoleDataScope(roleIds);
userService.insertUserAuth(userId, roleIds);
return success();
}
@@ -15,19 +15,16 @@ import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.StringUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* swagger 用户测试方法
*
*
* @author ruoyi
*/
@Api("用户信息管理")
@Tag(name = "用户信息管理")
@RestController
@RequestMapping("/test/user")
public class TestController extends BaseController
@@ -37,19 +34,19 @@ public class TestController extends BaseController
users.put(1, new UserEntity(1, "admin", "admin123", "15888888888"));
users.put(2, new UserEntity(2, "ry", "admin123", "15666666666"));
}
@ApiOperation("获取用户列表")
@Operation(summary = "获取用户列表")
@GetMapping("/list")
public R<List<UserEntity>> userList()
{
List<UserEntity> userList = new ArrayList<UserEntity>(users.values());
return R.ok(userList);
}
@ApiOperation("获取用户详细")
@ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class)
@Operation(summary = "获取用户详细")
@GetMapping("/{userId}")
public R<UserEntity> getUser(@PathVariable Integer userId)
public R<UserEntity> getUser(@PathVariable(name = "userId")
Integer userId)
{
if (!users.isEmpty() && users.containsKey(userId))
{
@@ -60,14 +57,8 @@ public class TestController extends BaseController
return R.fail("用户不存在");
}
}
@ApiOperation("新增用户")
@ApiImplicitParams({
@ApiImplicitParam(name = "userId", value = "用户id", dataType = "Integer", dataTypeClass = Integer.class),
@ApiImplicitParam(name = "username", value = "用户名称", dataType = "String", dataTypeClass = String.class),
@ApiImplicitParam(name = "password", value = "用户密码", dataType = "String", dataTypeClass = String.class),
@ApiImplicitParam(name = "mobile", value = "用户手机", dataType = "String", dataTypeClass = String.class)
})
@Operation(summary = "新增用户")
@PostMapping("/save")
public R<String> save(UserEntity user)
{
@@ -78,10 +69,11 @@ public class TestController extends BaseController
users.put(user.getUserId(), user);
return R.ok();
}
@ApiOperation("更新用户")
@Operation(summary = "更新用户")
@PutMapping("/update")
public R<String> update(@RequestBody UserEntity user)
public R<String> update(@RequestBody
UserEntity user)
{
if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId()))
{
@@ -95,11 +87,11 @@ public class TestController extends BaseController
users.put(user.getUserId(), user);
return R.ok();
}
@ApiOperation("删除用户信息")
@ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class)
@Operation(summary = "删除用户信息")
@DeleteMapping("/{userId}")
public R<String> delete(@PathVariable Integer userId)
public R<String> delete(@PathVariable(name = "userId")
Integer userId)
{
if (!users.isEmpty() && users.containsKey(userId))
{
@@ -113,26 +105,26 @@ public class TestController extends BaseController
}
}
@ApiModel(value = "UserEntity", description = "用户实体")
@Schema(description = "用户实体")
class UserEntity
{
@ApiModelProperty("用户ID")
@Schema(title = "用户ID")
private Integer userId;
@ApiModelProperty("用户名称")
@Schema(title = "用户名称")
private String username;
@ApiModelProperty("用户密码")
@Schema(title = "用户密码")
private String password;
@ApiModelProperty("用户手机")
@Schema(title = "用户手机")
private String mobile;
public UserEntity()
{
}
public UserEntity(Integer userId, String username, String password, String mobile)
{
this.userId = userId;
@@ -140,42 +132,42 @@ class UserEntity
this.password = password;
this.mobile = mobile;
}
public Integer getUserId()
{
return userId;
}
public void setUserId(Integer userId)
{
this.userId = userId;
}
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
public String getMobile()
{
return mobile;
}
public void setMobile(String mobile)
{
this.mobile = mobile;
@@ -1,26 +1,15 @@
package com.ruoyi.web.core.config;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.ruoyi.common.config.RuoYiConfig;
import io.swagger.annotations.ApiOperation;
import io.swagger.models.auth.In;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
/**
* Swagger2的接口配置
@@ -33,93 +22,43 @@ public class SwaggerConfig
/** 系统基础配置 */
@Autowired
private RuoYiConfig ruoyiConfig;
/** 是否开启swagger */
@Value("${swagger.enabled}")
private boolean enabled;
/** 设置请求的统一前缀 */
@Value("${swagger.pathMapping}")
private String pathMapping;
/**
* 创建API
* 自定义的 OpenAPI 对象
*/
@Bean
public Docket createRestApi()
public OpenAPI customOpenApi()
{
return new Docket(DocumentationType.OAS_30)
// 是否启用Swagger
.enable(enabled)
// 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
.apiInfo(apiInfo())
// 设置哪些接口暴露给Swagger展示
.select()
// 扫描所有有注解的api,用这种方式更灵活
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
// 扫描指定包中的swagger注解
// .apis(RequestHandlerSelectors.basePackage("com.ruoyi.project.tool.swagger"))
// 扫描所有 .apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
/* 设置安全模式,swagger可以设置访问token */
.securitySchemes(securitySchemes())
.securityContexts(securityContexts())
.pathMapping(pathMapping);
return new OpenAPI().components(new Components()
// 设置认证的请求头
.addSecuritySchemes("apikey", securityScheme()))
.addSecurityItem(new SecurityRequirement().addList("apikey"))
.info(getApiInfo());
}
/**
* 安全模式,这里指定token通过Authorization头请求头传递
*/
private List<SecurityScheme> securitySchemes()
@Bean
public SecurityScheme securityScheme()
{
List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));
return apiKeyList;
return new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.name("Authorization")
.in(SecurityScheme.In.HEADER)
.scheme("Bearer");
}
/**
* 安全上下文
*/
private List<SecurityContext> securityContexts()
{
List<SecurityContext> securityContexts = new ArrayList<>();
securityContexts.add(
SecurityContext.builder()
.securityReferences(defaultAuth())
.operationSelector(o -> o.requestMappingPattern().matches("/.*"))
.build());
return securityContexts;
}
/**
* 默认的安全上引用
*/
private List<SecurityReference> defaultAuth()
{
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
List<SecurityReference> securityReferences = new ArrayList<>();
securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
return securityReferences;
}
/**
* 添加摘要信息
*/
private ApiInfo apiInfo()
public Info getApiInfo()
{
// 用ApiInfoBuilder进行定制
return new ApiInfoBuilder()
// 设置标题
.title("标题:若依管理系统_接口文档")
// 描述
.description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")
// 作者信息
.contact(new Contact(ruoyiConfig.getName(), null, null))
// 版本
.version("版本号:" + ruoyiConfig.getVersion())
.build();
return new Info()
// 设置标题
.title("标题:若依管理系统_接口文档")
// 描述
.description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")
// 作者信息
.contact(new Contact().name(ruoyiConfig.getName()))
// 版本
.version("版本号:" + ruoyiConfig.getVersion());
}
}
@@ -1 +1 @@
restart.include.json=/com.alibaba.fastjson.*.jar
restart.include.json=/com.alibaba.fastjson2.*.jar
@@ -24,6 +24,10 @@ spring:
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置连接超时时间
connectTimeout: 30000
# 配置网络超时时间
socketTimeout: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
+71 -55
View File
@@ -3,16 +3,14 @@ ruoyi:
# 名称
name: RuoYi
# 版本
version: 3.8.5
version: 3.9.2
# 版权年份
copyrightYear: 2023
# 实例演示开关
demoEnabled: true
copyrightYear: 2026
# 文件路径 示例( Windows配置D:/ruoyi/uploadPathLinux配置 /home/ruoyi/uploadPath
profile: D:/ruoyi/uploadPath
# 获取ip地址开关
addressEnabled: false
# 验证码类型 math 数计算 char 字符验证
# 验证码类型 math 数计算 char 字符验证
captchaType: math
# 开发环境配置
@@ -53,76 +51,94 @@ spring:
messages:
# 国际化资源文件路径
basename: i18n/messages
profiles:
profiles:
active: druid
# 文件上传
servlet:
multipart:
# 单个文件大小
max-file-size: 10MB
# 设置总上传的文件大小
max-request-size: 20MB
multipart:
# 单个文件大小
max-file-size: 10MB
# 设置总上传的文件大小
max-request-size: 20MB
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
# 服务模块
devtools:
restart:
# 热部署开关
enabled: true
# redis 配置
redis:
# 地址
host: localhost
# 端口,默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
data:
# redis 配置
redis:
# 地址
host: localhost
# 端口,默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# token配置
token:
# 令牌自定义标识
header: Authorization
# 令牌密钥
secret: abcdefghijklmnopqrstuvwxyz
# 令牌有效期(默认30分钟)
expireTime: 30
# 令牌自定义标识
header: Authorization
# 令牌密钥
secret: abcdefghijklmnopqrstuvwxyz
# 令牌有效期(默认30分钟)
expireTime: 30
# MyBatis配置
mybatis:
# 搜索指定包别名
typeAliasesPackage: com.ruoyi.**.domain
# 配置mapper的扫描,找到所有的mapper.xml映射文件
mapperLocations: classpath*:mapper/**/*Mapper.xml
# 加载全局的配置文件
configLocation: classpath:mybatis/mybatis-config.xml
# 搜索指定包别名
typeAliasesPackage: com.ruoyi.**.domain
# 配置mapper的扫描,找到所有的mapper.xml映射文件
mapperLocations: classpath*:mapper/**/*Mapper.xml
# 加载全局的配置文件
configLocation: classpath:mybatis/mybatis-config.xml
# PageHelper分页插件
pagehelper:
pagehelper:
helperDialect: mysql
supportMethodsArguments: true
params: count=countSql
params: count=countSql
# Swagger配置
swagger:
# 是否开启swagger
enabled: true
# 请求前缀
pathMapping: /dev-api
# Springdoc配置
springdoc:
api-docs:
path: /v3/api-docs
swagger-ui:
enabled: true
path: /swagger-ui.html
tags-sorter: alpha
group-configs:
- group: 'default'
display-name: '测试模块'
paths-to-match: '/**'
packages-to-scan: com.ruoyi.web.controller.tool
# 防盗链配置
referer:
# 防盗链开关
enabled: false
# 允许的域名列表
allowed-domains: localhost,127.0.0.1,ruoyi.vip,www.ruoyi.vip
# 防止XSS攻击
xss:
xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
@@ -9,6 +9,7 @@ user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分
user.password.delete=对不起,您的账号已被删除
user.blocked=用户已封禁,请联系管理员
role.blocked=角色已封禁,请联系管理员
login.blocked=很遗憾,访问IP已被列入系统黑名单
user.logout.success=退出成功
length.not.valid=长度必须在{min}到{max}个字符之间
+11 -25
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.8.5</version>
<version>3.9.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -55,17 +55,10 @@
<!-- JSON工具类 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- 动态数据源 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<!-- 阿里JSON解析器 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
@@ -78,24 +71,12 @@
<artifactId>commons-io</artifactId>
</dependency>
<!-- 文件上传工具类 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
<!-- excel工具 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
</dependency>
<!-- yml解析器 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<!-- Token生成与解析-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
@@ -114,6 +95,11 @@
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- pool 对象池 -->
<dependency>
<groupId>org.apache.commons</groupId>
@@ -122,14 +108,14 @@
<!-- 解析客户端操作系统、浏览器等 -->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<groupId>nl.basjes.parse.useragent</groupId>
<artifactId>yauaa</artifactId>
</dependency>
<!-- servlet包 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
</dependency>
</dependencies>
@@ -16,15 +16,25 @@ import java.lang.annotation.Target;
@Documented
public @interface DataScope
{
/**
* 用户表的别名
*/
public String userAlias() default "";
/**
* 部门表的别名
*/
public String deptAlias() default "";
/**
* 用户表的别
* 用户字段
*/
public String userAlias() default "";
public String userField() default "user_id";
/**
* 部门字段名
*/
public String deptField() default "dept_id";
/**
* 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@ss获取,多个权限用逗号分隔开来
@@ -56,15 +56,16 @@ public @interface Excel
/**
* BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN
*/
@SuppressWarnings("deprecation")
public int roundingMode() default BigDecimal.ROUND_HALF_EVEN;
/**
* 导出时在excel中每个列的高度 单位为字符
* 导出时在excel中每个列的高度
*/
public double height() default 14;
/**
* 导出时在excel中每个列的宽 单位为字符
* 导出时在excel中每个列的宽
*/
public double width() default 16;
@@ -83,11 +84,21 @@ public @interface Excel
*/
public String prompt() default "";
/**
* 是否允许内容换行
*/
public boolean wrapText() default false;
/**
* 设置只能选择不能输入的列内容.
*/
public String[] combo() default {};
/**
* 是否从字典读数据到combo,默认不读取,如读取需要设置dictType注解.
*/
public boolean comboReadDict() default false;
/**
* 是否需要纵向合并单元格,应对需求:含有list集合单元格)
*/
@@ -114,7 +125,7 @@ public @interface Excel
public ColumnType cellType() default ColumnType.STRING;
/**
* 导出列头背景色
* 导出列头背景
*/
public IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT;
@@ -124,7 +135,7 @@ public @interface Excel
public IndexedColors headerColor() default IndexedColors.WHITE;
/**
* 导出单元格背景色
* 导出单元格背景
*/
public IndexedColors backgroundColor() default IndexedColors.WHITE;
@@ -171,7 +182,7 @@ public @interface Excel
public enum ColumnType
{
NUMERIC(0), STRING(1), IMAGE(2);
NUMERIC(0), STRING(1), IMAGE(2), TEXT(3);
private final int value;
ColumnType(int value)
@@ -20,7 +20,7 @@ import com.ruoyi.common.enums.OperatorType;
public @interface Log
{
/**
* 模块
* 模块
*/
public String title() default "";
@@ -43,4 +43,9 @@ public @interface Log
* 是否保存响应的参数
*/
public boolean isSaveResponseData() default true;
/**
* 排除指定的请求参数
*/
public String[] excludeParamNames() default {};
}
@@ -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 tools.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, ElementType.METHOD })
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveJsonSerializer.class)
public @interface Sensitive
{
DesensitizedType desensitizedType();
}
@@ -21,9 +21,6 @@ public class RuoYiConfig
/** 版权年份 */
private String copyrightYear;
/** 实例演示开关 */
private boolean demoEnabled;
/** 上传路径 */
private static String profile;
@@ -63,16 +60,6 @@ public class RuoYiConfig
this.copyrightYear = copyrightYear;
}
public boolean isDemoEnabled()
{
return demoEnabled;
}
public void setDemoEnabled(boolean demoEnabled)
{
this.demoEnabled = demoEnabled;
}
public static String getProfile()
{
return profile;
@@ -0,0 +1,77 @@
package com.ruoyi.common.config.serializer;
import java.util.Objects;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.BeanProperty;
import tools.jackson.databind.DatabindException;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ValueSerializer;
import tools.jackson.databind.ser.std.StdSerializer;
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 StdSerializer<String>
{
private final DesensitizedType desensitizedType;
public SensitiveJsonSerializer()
{
super(String.class);
this.desensitizedType = null;
}
public SensitiveJsonSerializer(DesensitizedType desensitizedType)
{
super(String.class);
this.desensitizedType = desensitizedType;
}
@Override
public void serialize(String value, JsonGenerator gen, SerializationContext ctxt) throws JacksonException
{
if (desensitizedType != null && desensitization())
{
gen.writeString(desensitizedType.desensitizer().apply(value));
}
else
{
gen.writeString(value);
}
}
@Override
public ValueSerializer<?> createContextual(SerializationContext ctxt, BeanProperty property) throws DatabindException
{
Sensitive annotation = property.getAnnotation(Sensitive.class);
if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass()))
{
return new SensitiveJsonSerializer(annotation.desensitizedType());
}
return ctxt.findValueSerializer(property.getType());
}
/**
* 是否需要脱敏处理
*/
private boolean desensitization()
{
try
{
LoginUser securityUser = SecurityUtils.getLoginUser();
// 管理员不脱敏
return !securityUser.getUser().isAdmin();
}
catch (Exception e)
{
return true;
}
}
}
@@ -1,5 +1,6 @@
package com.ruoyi.common.constant;
import java.util.Locale;
import io.jsonwebtoken.Claims;
/**
@@ -19,6 +20,11 @@ public class Constants
*/
public static final String GBK = "GBK";
/**
* 系统语言
*/
public static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE;
/**
* www主域
*/
@@ -63,7 +69,27 @@ public class Constants
* 登录失败
*/
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_DELIMITER = ",";
/**
* 权限标识分隔符
*/
public static final String PERMISSION_DELIMITER = ",";
/**
* 验证码有效期(分钟)
*/
@@ -129,14 +155,50 @@ public class Constants
*/
public static final String LOOKUP_LDAPS = "ldaps:";
/**
* 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全)
*/
public static final String[] JSON_WHITELIST_STR = { "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",
"org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config" };
"org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config", "com.ruoyi.generator" };
/**
* 部门相关常量
*/
public static class Dept
{
/**
* 全部数据权限
*/
public static final String DATA_SCOPE_ALL = "1";
/**
* 自定数据权限
*/
public static final String DATA_SCOPE_CUSTOM = "2";
/**
* 部门数据权限
*/
public static final String DATA_SCOPE_DEPT = "3";
/**
* 部门及以下数据权限
*/
public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
/**
* 仅本人数据权限
*/
public static final String DATA_SCOPE_SELF = "5";
}
}
@@ -31,6 +31,9 @@ public class GenConstants
/** 上级菜单名称字段 */
public static final String PARENT_MENU_NAME = "parentMenuName";
/** 生成详情页开关 */
public static final String GEN_VIEW = "genView";
/** 数据库字符串类型 */
public static final String[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2" };
@@ -21,6 +21,9 @@ public class UserConstants
/** 用户封禁状态 */
public static final String USER_DISABLE = "1";
/** 角色正常状态 */
public static final String ROLE_NORMAL = "0";
/** 角色封禁状态 */
public static final String ROLE_DISABLE = "1";
@@ -60,9 +63,9 @@ public class UserConstants
/** InnerLink组件标识 */
public final static String INNER_LINK = "InnerLink";
/** 校验返回结果码 */
public final static String UNIQUE = "0";
public final static String NOT_UNIQUE = "1";
/** 校验是否唯一的返回标识 */
public final static boolean UNIQUE = true;
public final static boolean NOT_UNIQUE = false;
/**
* 用户名长度限制
@@ -1,6 +1,7 @@
package com.ruoyi.common.core.domain;
import java.util.HashMap;
import java.util.Objects;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.utils.StringUtils;
@@ -169,6 +170,36 @@ public class AjaxResult extends HashMap<String, Object>
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));
}
/**
* 方便链式调用
*
@@ -4,8 +4,10 @@ import java.io.Serializable;
import java.util.List;
import java.util.stream.Collectors;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.domain.entity.SysMenu;
import com.ruoyi.common.utils.StringUtils;
/**
* Treeselect树结构实体类
@@ -22,6 +24,9 @@ public class TreeSelect implements Serializable
/** 节点名称 */
private String label;
/** 节点禁用 */
private boolean disabled = false;
/** 子节点 */
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private List<TreeSelect> children;
@@ -35,6 +40,7 @@ public class TreeSelect implements Serializable
{
this.id = dept.getDeptId();
this.label = dept.getDeptName();
this.disabled = StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus());
this.children = dept.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
}
@@ -65,6 +71,16 @@ public class TreeSelect implements Serializable
this.label = label;
}
public boolean isDisabled()
{
return disabled;
}
public void setDisabled(boolean disabled)
{
this.disabled = disabled;
}
public List<TreeSelect> getChildren()
{
return children;
@@ -2,10 +2,10 @@ package com.ruoyi.common.core.domain.entity;
import java.util.ArrayList;
import java.util.List;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.core.domain.BaseEntity;
@@ -1,7 +1,7 @@
package com.ruoyi.common.core.domain.entity;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
@@ -1,8 +1,8 @@
package com.ruoyi.common.core.domain.entity;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
@@ -2,9 +2,9 @@ package com.ruoyi.common.core.domain.entity;
import java.util.ArrayList;
import java.util.List;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.core.domain.BaseEntity;
@@ -42,6 +42,9 @@ public class SysMenu extends BaseEntity
/** 路由参数 */
private String query;
/** 路由名称,默认和路由地址相同的驼峰格式(注意:因为vue3版本的router会删除名称相同路由,为避免名字的冲突,特殊情况可以自定义) */
private String routeName;
/** 是否为外链(0是 1否) */
private String isFrame;
@@ -53,7 +56,7 @@ public class SysMenu extends BaseEntity
/** 显示状态(0显示 1隐藏) */
private String visible;
/** 菜单状态(0正常 1停用) */
private String status;
@@ -151,6 +154,16 @@ public class SysMenu extends BaseEntity
this.query = query;
}
public String getRouteName()
{
return routeName;
}
public void setRouteName(String routeName)
{
this.routeName = routeName;
}
public String getIsFrame()
{
return isFrame;
@@ -232,7 +245,7 @@ public class SysMenu extends BaseEntity
{
this.children = children;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@@ -242,6 +255,8 @@ public class SysMenu extends BaseEntity
.append("orderNum", getOrderNum())
.append("path", getPath())
.append("component", getComponent())
.append("query", getQuery())
.append("routeName", getRouteName())
.append("isFrame", getIsFrame())
.append("IsCache", getIsCache())
.append("menuType", getMenuType())
@@ -1,9 +1,9 @@
package com.ruoyi.common.core.domain.entity;
import java.util.Set;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
@@ -2,14 +2,17 @@ package com.ruoyi.common.core.domain.entity;
import java.util.Date;
import java.util.List;
import javax.validation.constraints.*;
import jakarta.validation.constraints.*;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.annotation.Excel.ColumnType;
import com.ruoyi.common.annotation.Excel.Type;
import com.ruoyi.common.annotation.Excels;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.xss.Xss;
/**
@@ -22,7 +25,7 @@ public class SysUser extends BaseEntity
private static final long serialVersionUID = 1L;
/** 用户ID */
@Excel(name = "用户序号", cellType = ColumnType.NUMERIC, prompt = "用户编号")
@Excel(name = "用户序号", type = Type.EXPORT, cellType = ColumnType.NUMERIC, prompt = "用户编号")
private Long userId;
/** 部门ID */
@@ -42,7 +45,7 @@ public class SysUser extends BaseEntity
private String email;
/** 手机号码 */
@Excel(name = "手机号码")
@Excel(name = "手机号码", cellType = ColumnType.TEXT)
private String phonenumber;
/** 用户性别 */
@@ -55,8 +58,8 @@ public class SysUser extends BaseEntity
/** 密码 */
private String password;
/** 号状态(0正常 1停用) */
@Excel(name = "号状态", readConverterExp = "0=正常,1=停用")
/** 号状态(0正常 1停用) */
@Excel(name = "号状态", readConverterExp = "0=正常,1=停用")
private String status;
/** 删除标志(0代表存在 2代表删除) */
@@ -67,9 +70,13 @@ public class SysUser extends BaseEntity
private String loginIp;
/** 最后登录时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT)
private Date loginDate;
/** 密码最后更新时间 */
private Date pwdUpdateDate;
/** 部门对象 */
@Excels({
@Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT),
@@ -111,12 +118,7 @@ public class SysUser extends BaseEntity
public boolean isAdmin()
{
return isAdmin(this.userId);
}
public static boolean isAdmin(Long userId)
{
return userId != null && 1L == userId;
return SecurityUtils.isAdmin(this.userId);
}
public Long getDeptId()
@@ -197,6 +199,7 @@ public class SysUser extends BaseEntity
this.avatar = avatar;
}
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
public String getPassword()
{
return password;
@@ -247,6 +250,16 @@ public class SysUser extends BaseEntity
this.loginDate = loginDate;
}
public Date getPwdUpdateDate()
{
return pwdUpdateDate;
}
public void setPwdUpdateDate(Date pwdUpdateDate)
{
this.pwdUpdateDate = pwdUpdateDate;
}
public SysDept getDept()
{
return dept;
@@ -313,6 +326,7 @@ public class SysUser extends BaseEntity
.append("delFlag", getDelFlag())
.append("loginIp", getLoginIp())
.append("loginDate", getLoginDate())
.append("pwdUpdateDate", getPwdUpdateDate())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
@@ -1,8 +1,12 @@
package com.ruoyi.common.core.domain.model;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.alibaba.fastjson2.annotation.JSONField;
import com.ruoyi.common.core.domain.entity.SysUser;
@@ -71,6 +75,24 @@ public class LoginUser implements UserDetails
*/
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()
{
return userId;
@@ -101,24 +123,6 @@ public class LoginUser implements UserDetails
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)
@Override
public String getPassword()
@@ -259,8 +263,16 @@ public class LoginUser implements UserDetails
}
@Override
@JSONField(serialize = false)
public Collection<? extends GrantedAuthority> getAuthorities()
{
return null;
if (permissions == null || permissions.isEmpty())
{
return Collections.emptyList();
}
return permissions.stream()
.filter(Objects::nonNull)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
}
@@ -37,7 +37,7 @@ public class TableDataInfo implements Serializable
* @param list 列表数据
* @param total 总记录数
*/
public TableDataInfo(List<?> list, int total)
public TableDataInfo(List<?> list, long total)
{
this.rows = list;
this.total = total;
@@ -2,12 +2,12 @@ package com.ruoyi.common.core.text;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.text.NumberFormat;
import java.util.Set;
import com.ruoyi.common.utils.StringUtils;
import org.apache.commons.lang3.ArrayUtils;
/**
* 类型转换器
@@ -364,6 +364,10 @@ public class Convert
*/
public static String[] toStrArray(String str)
{
if (StringUtils.isEmpty(str))
{
return new String[] {};
}
return toStrArray(",", str);
}
@@ -536,7 +540,7 @@ public class Convert
/**
* 转换为boolean<br>
* String支持的值为:true、false、yes、ok、no1,0 如果给定的值为空,或者转换失败,返回默认值<br>
* String支持的值为:true、false、yes、ok、no、1、0、是、否, 如果给定的值为空,或者转换失败,返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
@@ -565,10 +569,12 @@ public class Convert
case "yes":
case "ok":
case "1":
case "":
return true;
case "false":
case "no":
case "0":
case "":
return false;
default:
return defaultValue;
@@ -791,14 +797,23 @@ public class Convert
{
return (String) obj;
}
else if (obj instanceof byte[])
else if (obj instanceof byte[] || obj instanceof Byte[])
{
return str((byte[]) obj, charset);
}
else if (obj instanceof Byte[])
{
byte[] bytes = ArrayUtils.toPrimitive((Byte[]) obj);
return str(bytes, charset);
if (obj instanceof byte[])
{
return str((byte[]) obj, charset);
}
else
{
Byte[] bytes = (Byte[]) obj;
int length = bytes.length;
byte[] dest = new byte[length];
for (int i = 0; i < length; i++)
{
dest[i] = bytes[i];
}
return str(dest, charset);
}
}
else if (obj instanceof ByteBuffer)
{
@@ -954,9 +969,7 @@ public class Convert
c[i] = (char) (c[i] - 65248);
}
}
String returnString = new String(c);
return returnString;
return new String(c);
}
/**
@@ -977,7 +990,12 @@ public class Convert
String s = "";
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)
{
@@ -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{3}[Xx]|\\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;
}
}
@@ -2,7 +2,7 @@ package com.ruoyi.common.enums;
import java.util.HashMap;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
/**
* 请求方式
@@ -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;
}
}
@@ -1,10 +1,9 @@
package com.ruoyi.common.exception.file;
import java.util.Arrays;
import org.apache.commons.fileupload.FileUploadException;
/**
* 文件上传 异常类
* 文件上传无效扩展名异常类
*
* @author ruoyi
*/
@@ -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);
}
}
@@ -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);
}
}
@@ -0,0 +1,77 @@
package com.ruoyi.common.filter;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* 防盗链过滤器
*
* @author ruoyi
*/
public class RefererFilter implements Filter
{
/**
* 允许的域名列表
*/
public List<String> allowedDomains;
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
String domains = filterConfig.getInitParameter("allowedDomains");
this.allowedDomains = Arrays.asList(domains.split(","));
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
String referer = req.getHeader("Referer");
// 如果Referer为空拒绝访问
if (referer == null || referer.isEmpty())
{
resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Access denied: Referer header is required");
return;
}
// 检查Referer是否在允许的域名列表中
boolean allowed = false;
for (String domain : allowedDomains)
{
if (referer.contains(domain))
{
allowed = true;
break;
}
}
// 根据检查结果决定是否放行
if (allowed)
{
chain.doFilter(request, response);
}
else
{
resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Access denied: Referer '" + referer + "' is not allowed");
}
}
@Override
public void destroy()
{
}
}
@@ -1,13 +1,13 @@
package com.ruoyi.common.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import com.ruoyi.common.utils.StringUtils;
@@ -4,11 +4,11 @@ import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import com.ruoyi.common.utils.http.HttpHelper;
import com.ruoyi.common.constant.Constants;
@@ -3,14 +3,14 @@ package com.ruoyi.common.filter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.enums.HttpMethod;
@@ -32,10 +32,10 @@ public class XssFilter implements Filter
String tempExcludes = filterConfig.getInitParameter("excludes");
if (StringUtils.isNotEmpty(tempExcludes))
{
String[] url = tempExcludes.split(",");
for (int i = 0; url != null && i < url.length; i++)
String[] urls = tempExcludes.split(",");
for (String url : urls)
{
excludes.add(url[i]);
excludes.add(url);
}
}
}
@@ -2,10 +2,10 @@ package com.ruoyi.common.filter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@@ -108,7 +108,6 @@ public class Arith
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
BigDecimal one = BigDecimal.ONE;
return b.divide(one, scale, RoundingMode.HALF_UP).doubleValue();
return b.divide(BigDecimal.ONE, scale, RoundingMode.HALF_UP).doubleValue();
}
}
@@ -16,6 +16,7 @@ import org.apache.commons.lang3.time.DateFormatUtils;
*
* @author ruoyi
*/
@SuppressWarnings("deprecation")
public class DateUtils extends org.apache.commons.lang3.time.DateUtils
{
public static String YYYY = "yyyy";
@@ -145,16 +146,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 nh = 1000 * 60 * 60;
long nm = 1000 * 60;
// long ns = 1000;
// 获得两个时间的毫秒时间差异
long diff = endDate.getTime() - nowDate.getTime();
long diff = endDate.getTime() - startTime.getTime();
// 计算差多少天
long day = diff / nd;
// 计算差多少小时
@@ -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;
}
}
@@ -1,7 +1,9 @@
package com.ruoyi.common.utils;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.alibaba.fastjson2.JSONArray;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.core.domain.entity.SysDictData;
@@ -56,6 +58,10 @@ public class DictUtils
*/
public static String getDictLabel(String dictType, String dictValue)
{
if (StringUtils.isEmpty(dictValue))
{
return StringUtils.EMPTY;
}
return getDictLabel(dictType, dictValue, SEPARATOR);
}
@@ -68,6 +74,10 @@ public class DictUtils
*/
public static String getDictValue(String dictType, String dictLabel)
{
if (StringUtils.isEmpty(dictLabel))
{
return StringUtils.EMPTY;
}
return getDictValue(dictType, dictLabel, SEPARATOR);
}
@@ -81,37 +91,25 @@ public class DictUtils
*/
public static String getDictLabel(String dictType, String dictValue, String separator)
{
StringBuilder propertyString = new StringBuilder();
List<SysDictData> datas = getDictCache(dictType);
if (StringUtils.isNotNull(datas))
if (StringUtils.isNull(datas) || StringUtils.isEmpty(dictValue))
{
if (StringUtils.containsAny(separator, dictValue))
return StringUtils.EMPTY;
}
Map<String, String> dictMap = datas.stream().collect(HashMap::new, (map, dict) -> map.put(dict.getDictValue(), dict.getDictLabel()), Map::putAll);
if (!StringUtils.contains(dictValue, separator))
{
return dictMap.getOrDefault(dictValue, StringUtils.EMPTY);
}
StringBuilder labelBuilder = new StringBuilder();
for (String seperatedValue : dictValue.split(separator))
{
if (dictMap.containsKey(seperatedValue))
{
for (SysDictData dict : datas)
{
for (String value : dictValue.split(separator))
{
if (value.equals(dict.getDictValue()))
{
propertyString.append(dict.getDictLabel()).append(separator);
break;
}
}
}
}
else
{
for (SysDictData dict : datas)
{
if (dictValue.equals(dict.getDictValue()))
{
return dict.getDictLabel();
}
}
labelBuilder.append(dictMap.get(seperatedValue)).append(separator);
}
}
return StringUtils.stripEnd(propertyString.toString(), separator);
return StringUtils.removeEnd(labelBuilder.toString(), separator);
}
/**
@@ -123,35 +121,68 @@ public class DictUtils
* @return 字典值
*/
public static String getDictValue(String dictType, String dictLabel, String separator)
{
List<SysDictData> datas = getDictCache(dictType);
if (StringUtils.isNull(datas) || StringUtils.isEmpty(dictLabel))
{
return StringUtils.EMPTY;
}
Map<String, String> dictMap = datas.stream().collect(HashMap::new, (map, dict) -> map.put(dict.getDictLabel(), dict.getDictValue()), Map::putAll);
if (!StringUtils.contains(dictLabel, separator))
{
return dictMap.getOrDefault(dictLabel, StringUtils.EMPTY);
}
StringBuilder valueBuilder = new StringBuilder();
for (String seperatedValue : dictLabel.split(separator))
{
if (dictMap.containsKey(seperatedValue))
{
valueBuilder.append(dictMap.get(seperatedValue)).append(separator);
}
}
return StringUtils.removeEnd(valueBuilder.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);
}
if (StringUtils.containsAny(separator, dictLabel) && StringUtils.isNotEmpty(datas))
/**
* 根据字典类型获取字典所有标签
*
* @param dictType 字典类型
* @return 字典值
*/
public static String getDictLabels(String dictType)
{
StringBuilder propertyString = new StringBuilder();
List<SysDictData> datas = getDictCache(dictType);
if (StringUtils.isNull(datas))
{
for (SysDictData dict : datas)
{
for (String label : dictLabel.split(separator))
{
if (label.equals(dict.getDictLabel()))
{
propertyString.append(dict.getDictValue()).append(separator);
break;
}
}
}
return StringUtils.EMPTY;
}
else
for (SysDictData dict : datas)
{
for (SysDictData dict : datas)
{
if (dictLabel.equals(dict.getDictLabel()))
{
return dict.getDictValue();
}
}
propertyString.append(dict.getDictLabel()).append(SEPARATOR);
}
return StringUtils.stripEnd(propertyString.toString(), separator);
return StringUtils.stripEnd(propertyString.toString(), SEPARATOR);
}
/**
@@ -1,9 +1,15 @@
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.context.SecurityContextHolder;
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.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.exception.ServiceException;
@@ -14,6 +20,7 @@ import com.ruoyi.common.exception.ServiceException;
*/
public class SecurityUtils
{
/**
* 用户ID
**/
@@ -43,7 +50,7 @@ public class SecurityUtils
throw new ServiceException("获取部门ID异常", HttpStatus.UNAUTHORIZED);
}
}
/**
* 获取用户账户
**/
@@ -107,6 +114,16 @@ public class SecurityUtils
return passwordEncoder.matches(rawPassword, encodedPassword);
}
/**
* 是否为管理员
*
* @return 结果
*/
public static boolean isAdmin()
{
return isAdmin(getUserId());
}
/**
* 是否为管理员
*
@@ -117,4 +134,55 @@ public class SecurityUtils
{
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));
}
}
@@ -7,10 +7,10 @@ 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.HttpServletResponse;
import javax.servlet.http.HttpSession;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@@ -6,6 +6,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.Strings;
import org.springframework.util.AntPathMatcher;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.text.StrFormatter;
@@ -15,6 +16,7 @@ import com.ruoyi.common.core.text.StrFormatter;
*
* @author ruoyi
*/
@SuppressWarnings("deprecation")
public class StringUtils extends org.apache.commons.lang3.StringUtils
{
/** 空字符串 */
@@ -23,6 +25,9 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/** 下划线 */
private static final char SEPARATOR = '_';
/** 星号 */
private static final char ASTERISK = '*';
/**
* 获取参数不为空值
*
@@ -163,6 +168,49 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
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 +288,56 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
return str.substring(start, end);
}
/**
* 在字符串中查找第一个出现的 `open` 和最后一个出现的 `close` 之间的子字符串
*
* @param str 要截取的字符串
* @param open 起始字符串
* @param close 结束字符串
* @return 截取结果
*/
public static String substringBetweenLast(final String str, final String open, final String close)
{
if (isEmpty(str) || isEmpty(open) || isEmpty(close))
{
return NULLSTR;
}
final int start = str.indexOf(open);
if (start != INDEX_NOT_FOUND)
{
final int end = str.lastIndexOf(close);
if (end != INDEX_NOT_FOUND)
{
return str.substring(start + open.length(), end);
}
}
return NULLSTR;
}
/**
* 判断是否为空并且不是空白字符
*
* @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>
@@ -285,6 +383,18 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
return new HashSet<String>(str2List(str, sep, true, false));
}
/**
* 字符串转list
*
* @param str 字符串
* @param sep 分隔符
* @return list集合
*/
public static final List<String> str2List(String str, String sep)
{
return str2List(str, sep, true, false);
}
/**
* 字符串转list
*
@@ -325,11 +435,23 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
}
/**
* 判断给定的set列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value
* 检查子字符串是否存在
*
* @param set 给定的集合
* @param seq 检查的字符串
* @param searchSeq 查找的字符串
* @return 结果
*/
public static boolean contains(final CharSequence seq, final CharSequence searchSeq)
{
return Strings.CS.contains(seq, searchSeq);
}
/**
* 判断给定的collection列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value
*
* @param collection 给定的集合
* @param array 给定的数组
* @return boolean 结果
* @return 结果
*/
public static boolean containsAny(Collection<String> collection, String... array)
{
@@ -350,6 +472,18 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
}
}
/**
* 判断是否包含给定数组中的任意一个
*
* @param cs 要判断的字符串
* @param searchCharSequences 要判断的数组
* @return 结果
*/
public static boolean containsAny(final CharSequence cs, final CharSequence... searchCharSequences)
{
return Strings.CS.containsAny(cs, searchCharSequences);
}
/**
* 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写
*
@@ -373,6 +507,163 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
return false;
}
/**
* 检查是否包含要搜索的字符串忽略大小写
*
* @param str 要检查的字符串
* @param searchStr 要查找的字符串
* @return 如果包含要搜索的字符串忽略大小写则返回true如果不包含或返回false
*/
public static boolean containsIgnoreCase(final CharSequence str, final CharSequence searchStr)
{
return Strings.CI.contains(str, searchStr);
}
/**
* 检查字符串是否以任意前缀开始
*
* @param sequence 要检查的字符串
* @param searchStrings 区分大小写的字符串前缀数组
* @return 结果
*/
public static boolean startsWithAny(final CharSequence sequence, final CharSequence... searchStrings)
{
return Strings.CS.startsWithAny(sequence, searchStrings);
}
/**
* 不区分大小写地检查一个字符串是否以指定前缀开头
*
* @param str 待检查的字符串
* @param 要查找的前缀
* @return 结果
*/
public static boolean startsWithIgnoreCase(final CharSequence str, final CharSequence prefix)
{
return Strings.CI.startsWith(str, prefix);
}
/**
* 比较两个字符串是否相同
*
* @param cs1 第一个字符串
* @param cs2 第二个字符串
* @return 如果给定对象与字符串相等则返回 true否则返回 false
*/
public static boolean equals(final CharSequence cs1, final CharSequence cs2)
{
return Strings.CS.equals(cs1, cs2);
}
/**
* 替换字符串中所有匹配的字符
*
* @param text 要搜索和替换的文本
* @param searchString 要搜索的字符串
* @param replacement 用于替换的字符串
* @return 处理完所有替换后的文本
*/
public static String replace(final String text, final String searchString, final String replacement)
{
return Strings.CS.replace(text, searchString, replacement);
}
/**
* 仅当子字符串位于源字符串末尾时才将其移除否则返回源字符串
* @param str 要搜索的源字符串
* @param remove 要搜索并移除的字符串
* @return 如果找到并移除了字符串则返回移除后的子字符串
*/
public static String removeEnd(final String str, final String remove)
{
return Strings.CS.removeEnd(str, remove);
}
/**
* 查找字符串首次出现位置的索引
*
* @param seq 要检查的字符串
* @param searchSeq 要查找的字符串
* @return 返回指定字符在字符串中第一次出现处的索引如果此字符串中没有这样的字符则返回 -1
*/
public static int indexOf(final CharSequence seq, final CharSequence searchSeq)
{
return Strings.CS.indexOf(seq, searchSeq);
}
/**
* 检查字符串是否以指定的后缀结尾
*
* @param str 要检查的字符
* @param suffix 要检查的后缀
* @return 若参数与该字符串末尾相符 true;否则 false
*/
public static boolean endsWith(final CharSequence str, final CharSequence suffix)
{
return Strings.CS.endsWith(str, suffix);
}
/**
* 将给定的字符串与数组进行比较
*
* @param string 要比较的字符串
* @param searchStrings 字符串数组
* @return 如果字符串等于区分大小写{@code searchStrings}中的任意其他元素则返回true如果{@code searchStrings}为null或不包含匹配项则返回false
*/
public static boolean equalsAny(final CharSequence string, final CharSequence... searchStrings)
{
return Strings.CS.equalsAny(string, searchStrings);
}
/**
* 检查一个字符串是否以任意提供的区分大小写的后缀结尾
*
* @param sequence 要检查的字符串
* @param searchStrings 要查找的区分大小写的字符串数组
* @return 如果输入参数{@code sequence}为null且未提供任何{@code searchStrings}或者输入{@code sequence}以任意提供的区分大小写的{@code searchStrings}结尾则返回{@code true}
*/
public static boolean endsWithAny(final CharSequence sequence, final CharSequence... searchStrings)
{
return Strings.CS.endsWithAny(sequence, searchStrings);
}
/**
* 不区分大小写地检查字符序列是否以指定的后缀结尾
*
* @param str 要检查的字符序列
* @param suffix 要查找的后缀
* @return 如果字符序列以该后缀结尾不区分大小写或两者均为{@code null}则返回{@code true}
*/
public static boolean endsWithIgnoreCase(final CharSequence str, final CharSequence suffix)
{
return Strings.CI.endsWith(str, suffix);
}
/**
* 指定范围内查找字符串,忽略大小写
*
* @param str 要检查的字符串
* @param searchStr 要查找的字符串
* @return 搜索字符串的第一个索引如果未找到匹配项则返回 -1
*/
public static int indexOfIgnoreCase(final CharSequence str, final CharSequence searchStr)
{
return Strings.CI.indexOf(str, searchStr);
}
/**
* Compares given {@code string} to a CharSequences vararg of {@code searchStrings},
* returning {@code true} if the {@code string} is equal to any of the {@code searchStrings}, ignoring case.
*
* @param string to compare, may be {@code null}.
* @param searchStrings a vararg of strings, may be {@code null}.
* @return {@code true} if the string is equal (case-insensitive) to any other element of {@code searchStrings};
*/
public static boolean equalsAnyIgnoreCase(final CharSequence string, final CharSequence... searchStrings)
{
return Strings.CI.equalsAny(string, searchStrings);
}
/**
* 驼峰转下划线命名
*/
@@ -444,6 +735,22 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
return false;
}
/**
* 删除最后一个字符串
*
* @param str 输入字符串
* @param spit 以什么类型结尾的
* @return 截取后的字符串
*/
public static String lastStringDel(String str, String spit)
{
if (!StringUtils.isEmpty(str) && str.endsWith(spit))
{
return str.subSequence(0, str.length() - 1).toString();
}
return str;
}
/**
* 将下划线大写方式命名的字符串转换为驼峰式如果转换前的下划线大写方式命名的字符串为空则返回空字符串 例如HELLO_WORLD->HelloWorld
*
@@ -481,7 +788,8 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
}
/**
* 驼峰式命名法 例如user_name->userName
* 驼峰式命名法
* 例如user_name->userName
*/
public static String toCamelCase(String s)
{
@@ -489,6 +797,10 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
{
return null;
}
if (s.indexOf(SEPARATOR) == -1)
{
return s;
}
s = s.toLowerCase();
StringBuilder sb = new StringBuilder(s.length());
boolean upperCase = false;
@@ -1,9 +1,9 @@
package com.ruoyi.common.utils.bean;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Validator;
/**
* bean对象属性验证
@@ -13,11 +13,12 @@ import com.ruoyi.common.exception.file.FileSizeLimitExceededException;
import com.ruoyi.common.exception.file.InvalidExtensionException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.common.utils.uuid.Seq;
/**
* 文件上传工具类
*
*
* @author ruoyi
*/
public class FileUploadUtils
@@ -25,7 +26,7 @@ public class FileUploadUtils
/**
* 默认大小 50M
*/
public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;
public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024L;
/**
* 默认的文件名最大长度 100
@@ -102,15 +103,35 @@ public class FileUploadUtils
throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
InvalidExtensionException
{
int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length();
if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
return upload(baseDir, file, allowedExtension, false);
}
/**
* 文件上传
*
* @param baseDir 相对应用的基目录
* @param file 上传的文件
* @param useCustomNaming 系统自定义文件名
* @param allowedExtension 上传文件类型
* @return 返回上传成功的文件名
* @throws FileSizeLimitExceededException 如果超出最大大小
* @throws FileNameLengthLimitExceededException 文件名太长
* @throws IOException 比如读写文件出错时
* @throws InvalidExtensionException 文件校验异常
*/
public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension, boolean useCustomNaming)
throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
InvalidExtensionException
{
int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length();
if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
{
throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
}
assertAllowed(file, allowedExtension);
String fileName = extractFilename(file);
String fileName = useCustomNaming ? uuidFilename(file) : extractFilename(file);
String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
file.transferTo(Paths.get(absPath));
@@ -118,12 +139,19 @@ public class FileUploadUtils
}
/**
* 编码文件名
* 编码文件名(日期格式目录 + 原文件名 + 序列值 + 后缀)
*/
public static final String extractFilename(MultipartFile file)
{
return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
}
/**
* 编编码文件名(日期格式目录 + UUID + 后缀)
*/
public static final String uuidFilename(MultipartFile file)
{
return StringUtils.format("{}/{}.{}", DateUtils.datePath(), IdUtils.fastSimpleUUID(), getExtension(file));
}
public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException
@@ -216,7 +244,7 @@ public class FileUploadUtils
/**
* 获取文件名的后缀
*
*
* @param file 表单文件
* @return 后缀名
*/
@@ -9,15 +9,16 @@ import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import org.apache.commons.io.FilenameUtils;
/**
* 文件处理工具类
@@ -103,6 +104,17 @@ public class FileUtils
return FileUploadUtils.getPathFileName(uploadDir, pathName);
}
/**
* 移除路径中的请求前缀片段
*
* @param filePath 文件路径
* @return 移除后的文件路径
*/
public static String stripPrefix(String filePath)
{
return StringUtils.substringAfter(filePath, Constants.RESOURCE_PREFIX);
}
/**
* 删除文件
*
@@ -5,7 +5,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import javax.servlet.ServletRequest;
import jakarta.servlet.ServletRequest;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -21,6 +21,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.StringUtils;
import org.springframework.http.MediaType;
/**
* 通用http发送方法
@@ -74,7 +75,7 @@ public class HttpUtils
URLConnection connection = realUrl.openConnection();
connection.setRequestProperty("accept", "*/*");
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();
in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType));
String line;
@@ -125,6 +126,19 @@ public class HttpUtils
* @return 所代表远程资源的响应结果
*/
public static String sendPost(String url, String param)
{
return sendPost(url, param, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
}
/**
* 向指定 URL 发送POST方法的请求
*
* @param url 发送请求的 URL
* @param param 请求参数
* @param contentType 内容类型
* @return 所代表远程资源的响应结果
*/
public static String sendPost(String url, String param, String contentType)
{
PrintWriter out = null;
BufferedReader in = null;
@@ -136,9 +150,9 @@ public class HttpUtils
URLConnection conn = realUrl.openConnection();
conn.setRequestProperty("accept", "*/*");
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("contentType", "utf-8");
conn.setRequestProperty("Content-Type", contentType);
conn.setDoOutput(true);
conn.setDoInput(true);
out = new PrintWriter(conn.getOutputStream());
@@ -190,6 +204,11 @@ public class HttpUtils
}
public static String sendSSLPost(String url, String param)
{
return sendSSLPost(url, param, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
}
public static String sendSSLPost(String url, String param, String contentType)
{
StringBuilder result = new StringBuilder();
String urlNameString = url + "?" + param;
@@ -202,9 +221,9 @@ public class HttpUtils
HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
conn.setRequestProperty("accept", "*/*");
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("contentType", "utf-8");
conn.setRequestProperty("Content-Type", contentType);
conn.setDoOutput(true);
conn.setDoInput(true);
@@ -0,0 +1,254 @@
package com.ruoyi.common.utils.http;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.ruoyi.common.utils.StringUtils;
import nl.basjes.parse.useragent.UserAgent;
import nl.basjes.parse.useragent.UserAgentAnalyzer;
/**
* UserAgent解析工具类
*
* @author ruoyi
*/
public class UserAgentUtils
{
public static final String UNKNOWN = "";
// 浏览器正则表达式模式
private static final Pattern CHROME_PATTERN = Pattern.compile("Chrome/(\\d+)(?:\\.\\d+)*");
private static final Pattern FIREFOX_PATTERN = Pattern.compile("Firefox/(\\d+)(?:\\.\\d+)*");
private static final Pattern EDGE_PATTERN = Pattern.compile("Edg(?:e)?/(\\d+)(?:\\.\\d+)*");
private static final Pattern SAFARI_PATTERN = Pattern.compile("Version/(\\d+)(?:\\.\\d+)*");
private static final Pattern OPERA_PATTERN = Pattern.compile("Opera/(\\d+)(?:\\.\\d+)*");
private static final Pattern IE_PATTERN = Pattern.compile("(?:MSIE |Trident/.*rv:)(\\d+)(?:\\.\\d+)*");
private static final Pattern SAMSUNG_PATTERN = Pattern.compile("SamsungBrowser/(\\d+)(?:\\.\\d+)*");
private static final Pattern UC_PATTERN = Pattern.compile("UCBrowser/(\\d+)(?:\\.\\d+)*");
private static final Pattern QQ_PATTERN = Pattern.compile("QQBrowser/(\\d+)(?:\\.\\d+)*");
private static final Pattern WECHAT_PATTERN = Pattern.compile("MicroMessenger/(\\d+)(?:\\.\\d+)*");
private static final Pattern BAIDU_PATTERN = Pattern.compile("baidubrowser/(\\d+)(?:\\.\\d+)*");
// 操作系统正则表达式模式
private static final Pattern WINDOWS_PATTERN = Pattern.compile("Windows NT (\\d+\\.\\d+)");
private static final Pattern MACOS_PATTERN = Pattern.compile("Mac OS X (\\d+[_\\d]*)");
private static final Pattern ANDROID_PATTERN = Pattern.compile("Android (\\d+)(?:\\.\\d+)*");
private static final Pattern IOS_PATTERN = Pattern.compile("OS[\\s_](\\d+)(?:_\\d+)*");
private static final Pattern LINUX_PATTERN = Pattern.compile("Linux");
private static final Pattern CHROMEOS_PATTERN = Pattern.compile("CrOS");
private static final UserAgentAnalyzer userAgentAnalyzer = UserAgentAnalyzer
.newBuilder().hideMatcherLoadStats()
.withCache(5000)
.showMinimalVersion()
.withField(UserAgent.AGENT_NAME_VERSION)
.withField(UserAgent.OPERATING_SYSTEM_NAME_VERSION)
.build();
/**
* 获取客户端浏览器
*/
public static String getBrowser(String userAgent)
{
UserAgent.ImmutableUserAgent iua = userAgentAnalyzer.parse(userAgent);
String agentNameVersion = iua.get(UserAgent.AGENT_NAME_VERSION).getValue();
if (StringUtils.isBlank(agentNameVersion) || agentNameVersion.contains("??"))
{
return formatBrowser(userAgent);
}
return agentNameVersion;
}
/**
* 获取客户端操作系统
*/
public static String getOperatingSystem(String userAgent)
{
UserAgent.ImmutableUserAgent iua = userAgentAnalyzer.parse(userAgent);
String operatingSystemNameVersion = iua.get(UserAgent.OPERATING_SYSTEM_NAME_VERSION).getValue();
if (StringUtils.isBlank(operatingSystemNameVersion) || operatingSystemNameVersion.contains("??"))
{
return formatOperatingSystem(userAgent);
}
return operatingSystemNameVersion;
}
/**
* 全面浏览器检测
*/
private static String formatBrowser(String browser)
{
// Chrome系列浏览器
Matcher chromeMatcher = CHROME_PATTERN.matcher(browser);
if (chromeMatcher.find() && (browser.contains("Chrome") || browser.contains("CriOS")))
{
return "Chrome" + chromeMatcher.group(1);
}
// Firefox
Matcher firefoxMatcher = FIREFOX_PATTERN.matcher(browser);
if (firefoxMatcher.find())
{
return "Firefox" + firefoxMatcher.group(1);
}
// Edge浏览器
Matcher edgeMatcher = EDGE_PATTERN.matcher(browser);
if (edgeMatcher.find())
{
return "Edge" + edgeMatcher.group(1);
}
// Safari浏览器需排除Chrome
Matcher safariMatcher = SAFARI_PATTERN.matcher(browser);
if (safariMatcher.find() && !browser.contains("Chrome"))
{
return "Safari" + safariMatcher.group(1);
}
// 微信内置浏览器
Matcher wechatMatcher = WECHAT_PATTERN.matcher(browser);
if (wechatMatcher.find())
{
return "WeChat" + wechatMatcher.group(1);
}
// UC浏览器
Matcher ucMatcher = UC_PATTERN.matcher(browser);
if (ucMatcher.find())
{
return "UC Browser" + ucMatcher.group(1);
}
// QQ浏览器
Matcher qqMatcher = QQ_PATTERN.matcher(browser);
if (qqMatcher.find())
{
return "QQ Browser" + qqMatcher.group(1);
}
// 百度浏览器
Matcher baiduMatcher = BAIDU_PATTERN.matcher(browser);
if (baiduMatcher.find())
{
return "Baidu Browser" + baiduMatcher.group(1);
}
// Samsung浏览器
Matcher samsungMatcher = SAMSUNG_PATTERN.matcher(browser);
if (samsungMatcher.find())
{
return "Samsung Browser" + samsungMatcher.group(1);
}
// Opera浏览器
Matcher operaMatcher = OPERA_PATTERN.matcher(browser);
if (operaMatcher.find())
{
return "Opera" + operaMatcher.group(1);
}
// IE浏览器
Matcher ieMatcher = IE_PATTERN.matcher(browser);
if (ieMatcher.find())
{
return "Internet Explorer" + ieMatcher.group(1);
}
return UNKNOWN;
}
/**
* 检测操作系统
*/
private static String formatOperatingSystem(String operatingSystem)
{
// Windows系统
Matcher windowsMatcher = WINDOWS_PATTERN.matcher(operatingSystem);
if (windowsMatcher.find())
{
return "Windows" + getWindowsVersionDisplay(windowsMatcher.group(1));
}
// macOS系统
Matcher macMatcher = MACOS_PATTERN.matcher(operatingSystem);
if (macMatcher.find())
{
String version = macMatcher.group(1).replace("_", ".");
return "macOS" + extractMajorVersion(version);
}
// Android系统
Matcher androidMatcher = ANDROID_PATTERN.matcher(operatingSystem);
if (androidMatcher.find())
{
return "Android" + extractMajorVersion(androidMatcher.group(1));
}
// iOS系统
Matcher iosMatcher = IOS_PATTERN.matcher(operatingSystem);
if (iosMatcher.find() && (operatingSystem.contains("iPhone") || operatingSystem.contains("iPad")))
{
return "iOS" + extractMajorVersion(iosMatcher.group(1));
}
// Linux系统
if (LINUX_PATTERN.matcher(operatingSystem).find() && !operatingSystem.contains("Android"))
{
return "Linux";
}
// Chrome OS
if (CHROMEOS_PATTERN.matcher(operatingSystem).find())
{
return "Chrome OS";
}
return UNKNOWN;
}
/**
* 提取优化的主版本号
*/
private static String extractMajorVersion(String fullVersion)
{
if (StringUtils.isEmpty(fullVersion))
{
return StringUtils.EMPTY;
}
try
{
// 清理版本号中的非数字字符
String cleanVersion = fullVersion.replaceAll("[^0-9.]", "");
String[] parts = cleanVersion.split("\\.");
if (parts.length > 0)
{
String firstPart = parts[0];
if (firstPart.matches("\\d+"))
{
int version = Integer.parseInt(firstPart);
// 处理三位数版本号如142 -> 14
if (version >= 100)
{
return String.valueOf(version / 10);
}
return firstPart;
}
}
}
catch (NumberFormatException e)
{
// 版本号解析失败返回原始值
}
return fullVersion;
}
/**
* Windows版本号显示优化
*/
private static String getWindowsVersionDisplay(String version)
{
switch (version)
{
case "10.0":
return "10";
case "6.3":
return "8.1";
case "6.2":
return "8";
case "6.1":
return "7";
case "6.0":
return "Vista";
case "5.1":
return "XP";
case "5.0":
return "2000";
default:
return extractMajorVersion(version);
}
}
}
@@ -19,7 +19,7 @@ public class AddressUtils
private static final Logger log = LoggerFactory.getLogger(AddressUtils.class);
// IP地址查询
public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp";
public static final String IP_URL = "https://whois.pconline.com.cn/ipJson.jsp";
// 未知地址
public static final String UNKNOWN = "XX XX";
@@ -2,7 +2,8 @@ package com.ruoyi.common.utils.ip;
import java.net.InetAddress;
import java.net.UnknownHostException;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
/**
@@ -12,6 +13,23 @@ import com.ruoyi.common.utils.StringUtils;
*/
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
*
@@ -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);
}
/**
* 是否为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;
}
}
@@ -1,5 +1,8 @@
package com.ruoyi.common.utils.poi;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Workbook;
/**
* Excel数据格式处理适配器
*
@@ -12,8 +15,10 @@ public interface ExcelHandlerAdapter
*
* @param value 单元格数据值
* @param args excel注解args参数组
* @param cell 单元格对象
* @param wb 工作簿对象
*
* @return 处理后的值
*/
Object format(Object value, String[] args);
Object format(Object value, String[] args, Cell cell, Workbook wb);
}
@@ -0,0 +1,85 @@
package com.ruoyi.common.utils.poi;
import java.util.ArrayList;
import java.util.List;
/**
* Sheet 导出时的数据信息
*
* 使用示例
* <pre>
* List<ExcelSheet<?>> sheets = new ArrayList<>();
* sheets.add(new ExcelSheet<>("参数数据", configList, Config.class, "参数信息"));
* sheets.add(new ExcelSheet<>("岗位数据", postList, Post.class, "岗位信息"));
* return ExcelUtil.exportMultiSheet(sheets);
* </pre>
*
* @author ruoyi
*/
public class ExcelSheet<T>
{
/** Sheet 名称 */
private String sheetName;
/** 导出数据集合 */
private List<T> list;
/** 数据对应的实体 Class */
private Class<T> clazz;
/** Sheet 顶部大标题(可为空) */
private String title;
public ExcelSheet(String sheetName, List<T> list, Class<T> clazz)
{
this(sheetName, list, clazz, "");
}
public ExcelSheet(String sheetName, List<T> list, Class<T> clazz, String title)
{
this.sheetName = sheetName;
this.list = list != null ? list : new ArrayList<>();
this.clazz = clazz;
this.title = title != null ? title : "";
}
public String getSheetName()
{
return sheetName;
}
public List<T> getList()
{
return list;
}
public Class<T> getClazz()
{
return clazz;
}
public String getTitle()
{
return title;
}
public void setSheetName(String sheetName)
{
this.sheetName = sheetName;
}
public void setList(List<T> list)
{
this.list = list;
}
public void setClazz(Class<T> clazz)
{
this.clazz = clazz;
}
public void setTitle(String title)
{
this.title = title;
}
}
File diff suppressed because it is too large Load Diff
@@ -7,13 +7,13 @@ import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Date;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.poi.ss.usermodel.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
/**
* 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
@@ -310,6 +310,7 @@ public class ReflectUtils
/**
* 改变private/protected的方法为public尽量不调用实际改动的语句避免JDK的SecurityManager抱怨
*/
@SuppressWarnings("deprecation")
public static void makeAccessible(Method method)
{
if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
@@ -322,6 +323,7 @@ public class ReflectUtils
/**
* 改变private/protected的成员变量为public尽量不调用实际改动的语句避免JDK的SecurityManager抱怨
*/
@SuppressWarnings("deprecation")
public static void makeAccessible(Field field)
{
if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers())
@@ -1,5 +1,6 @@
package com.ruoyi.common.utils.spring;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
@@ -120,7 +121,12 @@ public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationC
@SuppressWarnings("unchecked")
public static <T> T getAopProxy(T invoker)
{
return (T) AopContext.currentProxy();
Object proxy = AopContext.currentProxy();
if (((Advised) proxy).getTargetSource().getTargetClass() == invoker.getClass())
{
return (T) proxy;
}
return invoker;
}
/**
@@ -13,13 +13,18 @@ public class SqlUtil
/**
* 定义常用的 sql关键字
*/
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_REGEX = "\u000B|%0A|and |extractvalue|updatexml|sleep|information_schema|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()";
/**
* 仅支持字母数字下划线空格逗号小数点支持多个字段排序
*/
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("参数不符合规范,不能进行查询");
}
if (StringUtils.length(value) > ORDER_BY_MAX_LENGTH)
{
throw new UtilException("参数已超过最大限制,不能进行查询");
}
return value;
}
@@ -49,12 +58,13 @@ public class SqlUtil
{
return;
}
String normalizedValue = value.replaceAll("\\p{Z}|\\s", "");
String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|");
for (String sqlKeyword : sqlKeywords)
{
if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1)
if (StringUtils.indexOfIgnoreCase(normalizedValue, sqlKeyword) > -1)
{
throw new UtilException("参数存在SQL注入风险");
throw new UtilException("请求参数包含敏感关键词'" + sqlKeyword + "',可能存在安全风险");
}
}
}
@@ -22,7 +22,7 @@ public class Seq
private static AtomicInteger uploadSeq = new AtomicInteger(1);
// 机器标识
private static String machineCode = "A";
private static final String machineCode = "A";
/**
* 获取通用序列号
@@ -66,7 +66,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
}
/**
* 获取类型 4伪随机生成的UUID 的静态工厂 使用加密的本地线程伪随机数生成器生成该 UUID
* 获取类型 4伪随机生成的UUID 的静态工厂
*
* @return 随机生成的 {@code UUID}
*/
@@ -1,7 +1,7 @@
package com.ruoyi.common.xss;
import javax.validation.Constraint;
import javax.validation.Payload;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -1,8 +1,8 @@
package com.ruoyi.common.xss;
import com.ruoyi.common.utils.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -27,8 +27,13 @@ public class XssValidator implements ConstraintValidator<Xss, String>
public static boolean containsHtml(String value)
{
StringBuilder sHtml = new StringBuilder();
Pattern pattern = Pattern.compile(HTML_PATTERN);
Matcher matcher = pattern.matcher(value);
return matcher.matches();
while (matcher.find())
{
sHtml.append(matcher.group());
}
return pattern.matcher(sHtml).matches();
}
}
+5 -5
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.8.5</version>
<version>3.9.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -20,19 +20,19 @@
<!-- SpringBoot Web容器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<!-- SpringBoot 拦截器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<artifactId>spring-boot-starter-aspectj</artifactId>
</dependency>
<!-- 阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<artifactId>druid-spring-boot-4-starter</artifactId>
</dependency>
<!-- 验证码 -->
@@ -41,7 +41,7 @@
<artifactId>kaptcha</artifactId>
<exclusions>
<exclusion>
<artifactId>javax.servlet-api</artifactId>
<artifactId>servlet-api</artifactId>
<groupId>javax.servlet</groupId>
</exclusion>
</exclusions>
@@ -7,6 +7,8 @@ import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import com.ruoyi.common.annotation.DataScope;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
@@ -25,31 +27,6 @@ import com.ruoyi.framework.security.context.PermissionContextHolder;
@Component
public class DataScopeAspect
{
/**
* 全部数据权限
*/
public static final String DATA_SCOPE_ALL = "1";
/**
* 自定数据权限
*/
public static final String DATA_SCOPE_CUSTOM = "2";
/**
* 部门数据权限
*/
public static final String DATA_SCOPE_DEPT = "3";
/**
* 部门及以下数据权限
*/
public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
/**
* 仅本人数据权限
*/
public static final String DATA_SCOPE_SELF = "5";
/**
* 数据权限过滤关键字
*/
@@ -73,8 +50,7 @@ public class DataScopeAspect
if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
{
String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext());
dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
controllerDataScope.userAlias(), permission);
dataScopeFilter(joinPoint, currentUser, controllerDataScope.userAlias(), controllerDataScope.deptAlias(), controllerDataScope.userField(), controllerDataScope.deptField(), permission);
}
}
}
@@ -88,59 +64,76 @@ public class DataScopeAspect
* @param userAlias 用户别名
* @param permission 权限字符
*/
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission)
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String userAlias, String deptAlias, String userField, String deptField, String permission)
{
StringBuilder sqlString = new StringBuilder();
List<String> conditions = new ArrayList<String>();
List<String> scopeCustomIds = new ArrayList<String>();
user.getRoles().forEach(role -> {
if (Constants.Dept.DATA_SCOPE_CUSTOM.equals(role.getDataScope()) && StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL) && (StringUtils.isEmpty(permission) || StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))))
{
scopeCustomIds.add(Convert.toStr(role.getRoleId()));
}
});
for (SysRole role : user.getRoles())
{
String dataScope = role.getDataScope();
if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope))
if (conditions.contains(dataScope) || StringUtils.equals(role.getStatus(), UserConstants.ROLE_DISABLE))
{
continue;
}
if (StringUtils.isNotEmpty(permission) && StringUtils.isNotEmpty(role.getPermissions())
&& !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
if (StringUtils.isNotEmpty(permission) && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
{
continue;
}
if (DATA_SCOPE_ALL.equals(dataScope))
if (Constants.Dept.DATA_SCOPE_ALL.equals(dataScope))
{
sqlString = new StringBuilder();
conditions.add(dataScope);
break;
}
else if (DATA_SCOPE_CUSTOM.equals(dataScope))
else if (Constants.Dept.DATA_SCOPE_CUSTOM.equals(dataScope))
{
sqlString.append(StringUtils.format(
" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
role.getRoleId()));
if (scopeCustomIds.size() > 1)
{
// 多个自定数据权限使用in查询避免多次拼接
sqlString.append(StringUtils.format(" OR {}.{} IN ( SELECT dept_id FROM sys_role_dept WHERE role_id in ({}) ) ", deptAlias, deptField, String.join(",", scopeCustomIds)));
}
else
{
sqlString.append(StringUtils.format(" OR {}.{} IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, deptField, role.getRoleId()));
}
}
else if (DATA_SCOPE_DEPT.equals(dataScope))
else if (Constants.Dept.DATA_SCOPE_DEPT.equals(dataScope))
{
sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
sqlString.append(StringUtils.format(" OR {}.{} = {} ", deptAlias, deptField, user.getDeptId()));
}
else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
else if (Constants.Dept.DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
{
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()));
sqlString.append(StringUtils.format(" OR {}.{} IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", deptAlias, deptField, user.getDeptId(), user.getDeptId()));
}
else if (DATA_SCOPE_SELF.equals(dataScope))
else if (Constants.Dept.DATA_SCOPE_SELF.equals(dataScope))
{
if (StringUtils.isNotBlank(userAlias))
{
sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
sqlString.append(StringUtils.format(" OR {}.{} = {} ", userAlias, userField, user.getUserId()));
}
else
{
// 数据权限为仅本人且没有userAlias别名不查询任何数据
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
sqlString.append(StringUtils.format(" OR {}.{} = 0 ", deptAlias, deptField));
}
}
conditions.add(dataScope);
}
// 角色都不包含传递过来的权限字符这个时候sqlString也会为空所以要限制一下,不查询任何数据
if (StringUtils.isEmpty(conditions))
{
sqlString.append(StringUtils.format(" OR {}.{} = 0 ", deptAlias, deptField));
}
if (StringUtils.isNotBlank(sqlString.toString()))
{
Object params = joinPoint.getArgs()[0];
@@ -2,23 +2,29 @@ package com.ruoyi.framework.aspectj;
import java.util.Collection;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import com.alibaba.fastjson2.JSON;
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.text.Convert;
import com.ruoyi.common.enums.BusinessStatus;
import com.ruoyi.common.enums.HttpMethod;
import com.ruoyi.common.filter.PropertyPreExcludeFilter;
import com.ruoyi.common.utils.ExceptionUtil;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
@@ -41,6 +47,21 @@ public class LogAspect
/** 排除敏感属性字段 */
public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };
/** 计算操作消耗时间 */
private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");
/** 参数最大长度限制 */
private static final int PARAM_MAX_LENGTH = 2000;
/**
* 处理请求前执行
*/
@Before(value = "@annotation(controllerLog)")
public void doBefore(JoinPoint joinPoint, Log controllerLog)
{
TIME_THREADLOCAL.set(System.currentTimeMillis());
}
/**
* 处理完请求后执行
*
@@ -75,18 +96,23 @@ public class LogAspect
SysOperLog operLog = new SysOperLog();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
String ip = IpUtils.getIpAddr();
operLog.setOperIp(ip);
operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
if (loginUser != null)
{
operLog.setOperName(loginUser.getUsername());
SysUser currentUser = loginUser.getUser();
if (StringUtils.isNotNull(currentUser) && StringUtils.isNotNull(currentUser.getDept()))
{
operLog.setDeptName(currentUser.getDept().getDeptName());
}
}
if (e != null)
{
operLog.setStatus(BusinessStatus.FAIL.ordinal());
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
operLog.setErrorMsg(StringUtils.substring(Convert.toStr(e.getMessage(), ExceptionUtil.getExceptionMessage(e)), 0, 2000));
}
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
@@ -96,6 +122,8 @@ public class LogAspect
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
// 设置消耗时间
operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
// 保存数据库
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
}
@@ -105,6 +133,10 @@ public class LogAspect
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
finally
{
TIME_THREADLOCAL.remove();
}
}
/**
@@ -126,7 +158,7 @@ public class LogAspect
if (log.isSaveRequestData())
{
// 获取参数的信息传入到数据库中
setRequestValue(joinPoint, operLog);
setRequestValue(joinPoint, operLog, log.excludeParamNames());
}
// 是否需要保存response参数和值
if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult))
@@ -141,27 +173,27 @@ public class LogAspect
* @param operLog 操作日志
* @throws Exception 异常
*/
private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception
private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception
{
String requestMethod = operLog.getRequestMethod();
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))
Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
if (StringUtils.isEmpty(paramsMap) && StringUtils.equalsAny(requestMethod, HttpMethod.PUT.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name()))
{
String params = argsArrayToString(joinPoint.getArgs());
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
operLog.setOperParam(params);
}
else
{
Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter()), 0, 2000));
operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, PARAM_MAX_LENGTH));
}
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray)
private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames)
{
String params = "";
StringBuilder params = new StringBuilder();
if (paramsArray != null && paramsArray.length > 0)
{
for (Object o : paramsArray)
@@ -170,24 +202,29 @@ public class LogAspect
{
try
{
String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter());
params += jsonObj.toString() + " ";
String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames));
params.append(jsonObj).append(" ");
if (params.length() >= PARAM_MAX_LENGTH)
{
return StringUtils.substring(params.toString(), 0, PARAM_MAX_LENGTH);
}
}
catch (Exception e)
{
log.error("请求参数拼装异常 msg:{}, 参数:{}", e.getMessage(), paramsArray, e);
}
}
}
}
return params.trim();
return params.toString();
}
/**
* 忽略敏感属性
*/
public PropertyPreExcludeFilter excludePropertyPreFilter()
public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames)
{
return new PropertyPreExcludeFilter().addExcludes(EXCLUDE_PROPERTIES);
return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames));
}
/**
@@ -16,7 +16,6 @@ import org.springframework.stereotype.Component;
import com.ruoyi.common.annotation.RateLimiter;
import com.ruoyi.common.enums.LimitType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.IpUtils;
@@ -79,7 +78,7 @@ public class RateLimiterAspect
StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());
if (rateLimiter.limitType() == LimitType.IP)
{
stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest())).append("-");
stringBuffer.append(IpUtils.getIpAddr()).append("-");
}
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
@@ -1,9 +1,6 @@
package com.ruoyi.framework.config;
import java.util.TimeZone;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@@ -19,12 +16,4 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy;
@MapperScan("com.ruoyi.**.mapper")
public class ApplicationConfig
{
/**
* 时区配置
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization()
{
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault());
}
}
@@ -3,11 +3,6 @@ package com.ruoyi.framework.config;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.sql.DataSource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -16,13 +11,18 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.spring.boot4.autoconfigure.DruidDataSourceBuilder;
import com.alibaba.druid.spring.boot4.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.util.Utils;
import com.ruoyi.common.enums.DataSourceType;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.config.properties.DruidProperties;
import com.ruoyi.framework.datasource.DynamicDataSource;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
/**
* druid 配置多数据源
@@ -96,7 +96,7 @@ public class DruidConfig
Filter filter = new Filter()
{
@Override
public void init(javax.servlet.FilterConfig filterConfig) throws ServletException
public void init(jakarta.servlet.FilterConfig filterConfig) throws ServletException
{
}
@Override
@@ -6,6 +6,8 @@ import org.springframework.data.redis.serializer.SerializationException;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.filter.Filter;
import com.ruoyi.common.constant.Constants;
/**
* Redis使用FastJson序列化
@@ -16,6 +18,8 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{
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;
public FastJson2JsonRedisSerializer(Class<T> clazz)
@@ -43,6 +47,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
}
String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER);
}
}
@@ -2,12 +2,14 @@ package com.ruoyi.framework.config;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.DispatcherType;
import jakarta.servlet.DispatcherType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.filter.RefererFilter;
import com.ruoyi.common.filter.RepeatableFilter;
import com.ruoyi.common.filter.XssFilter;
import com.ruoyi.common.utils.StringUtils;
@@ -26,6 +28,9 @@ public class FilterConfig
@Value("${xss.urlPatterns}")
private String urlPatterns;
@Value("${referer.allowed-domains}")
private String allowedDomains;
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
@ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
@@ -43,6 +48,23 @@ public class FilterConfig
return registration;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
@ConditionalOnProperty(value = "referer.enabled", havingValue = "true")
public FilterRegistrationBean refererFilterRegistration()
{
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new RefererFilter());
registration.addUrlPatterns(Constants.RESOURCE_PREFIX + "/*");
registration.setName("refererFilter");
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
Map<String, String> initParameters = new HashMap<String, String>();
initParameters.put("allowedDomains", allowedDomains);
registration.setInitParameters(initParameters);
return registration;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
public FilterRegistrationBean someFilterRegistration()
@@ -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());
}
}
@@ -14,6 +14,7 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
*
* @author ruoyi
*/
@SuppressWarnings("deprecation")
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
@@ -36,7 +36,7 @@ public class ResourcesConfig implements WebMvcConfigurer
/** swagger配置 */
registry.addResourceHandler("/swagger-ui/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
.setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic());;
.setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic());
}
/**
@@ -55,7 +55,6 @@ public class ResourcesConfig implements WebMvcConfigurer
public CorsFilter corsFilter()
{
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 设置访问源地址
config.addAllowedOriginPattern("*");
// 设置访问源请求头
@@ -2,16 +2,15 @@ package com.ruoyi.framework.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
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.logout.LogoutFilter;
import org.springframework.web.filter.CorsFilter;
@@ -25,15 +24,10 @@ import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;
*
* @author ruoyi
*/
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Configuration
public class SecurityConfig
{
/**
* 自定义用户认证逻辑
*/
@Autowired
private UserDetailsService userDetailsService;
/**
* 认证失败处理类
*/
@@ -64,18 +58,14 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
@Autowired
private PermitAllUrlProperties permitAllUrl;
/**
* 解决 无法直接注入 AuthenticationManager
*
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception
{
return super.authenticationManagerBean();
}
/**
* 身份验证实现
*/
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception
{
return authenticationConfiguration.getAuthenticationManager();
}
/**
* anyRequest | 匹配所有请求路径
@@ -92,40 +82,39 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*/
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception
@Bean
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception
{
// 注解标记允许匿名访问的url
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());
httpSecurity
// CSRF禁用因为不使用session
.csrf().disable()
// 禁用HTTP响应标头
.headers().cacheControl().disable().and()
// 认证失败处理类
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// 基于token所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
return httpSecurity
// CSRF禁用因为不使用session
.csrf(csrf -> csrf.disable())
// 禁用HTTP响应标头
.headers((headersCustomizer) -> {
headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());
})
// 认证失败处理类
.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
// 基于token所以不需要session
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 注解标记允许匿名访问的url
.authorizeHttpRequests((requests) -> {
permitAllUrl.getUrls().forEach(url -> requests.requestMatchers(url).permitAll());
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
.antMatchers("/login", "/register", "/captchaImage").permitAll()
// 静态资源可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
// 添加Logout filter
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
// 添加JWT filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 添加CORS filter
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
requests.requestMatchers("/login", "/register", "/captchaImage").permitAll()
// 静态资源可匿名访问
.requestMatchers(HttpMethod.GET, "/", "/*.html", "/**.html", "/**.css", "/**.js", "/profile/**").permitAll()
.requestMatchers("/swagger-ui.html", "/v3/api-docs/**", "/swagger-ui/**", "/druid/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
})
// 添加Logout filter
.logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
// 添加JWT filter
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
// 添加CORS filter
.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
.addFilterBefore(corsFilter, LogoutFilter.class)
.build();
}
/**
@@ -136,13 +125,4 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
{
return new BCryptPasswordEncoder();
}
/**
* 身份认证接口
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
}
@@ -1,6 +1,6 @@
package com.ruoyi.framework.config;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import com.ruoyi.common.utils.ServletUtils;
@@ -49,8 +49,8 @@ public class ThreadPoolConfig
protected ScheduledExecutorService scheduledExecutorService()
{
return new ScheduledThreadPoolExecutor(corePoolSize,
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
new ThreadPoolExecutor.CallerRunsPolicy())
BasicThreadFactory.builder().namingPattern("schedule-pool-%d").daemon(true).build(),
new ThreadPoolExecutor.CallerRunsPolicy())
{
@Override
protected void afterExecute(Runnable r, Throwable t)

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