Compare commits

...

121 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
218 changed files with 10045 additions and 2315 deletions
+26 -6
View File
@@ -1,11 +1,11 @@
<p align="center"> <p align="center">
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png"> <img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
</p> </p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.9.0</h1> <h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.9.2</h1>
<h4 align="center">基于SpringBoot+Vue前后端分离的Java快速开发框架</h4> <h4 align="center">基于SpringBoot+Vue前后端分离的Java快速开发框架</h4>
<p align="center"> <p align="center">
<a href="https://gitee.com/y_project/RuoYi-Vue/stargazers"><img src="https://gitee.com/y_project/RuoYi-Vue/badge/star.svg?theme=dark"></a> <a href="https://gitee.com/y_project/RuoYi-Vue/stargazers"><img src="https://gitee.com/y_project/RuoYi-Vue/badge/star.svg?theme=dark"></a>
<a href="https://gitee.com/y_project/RuoYi-Vue"><img src="https://img.shields.io/badge/RuoYi-v3.9.0-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> <a href="https://gitee.com/y_project/RuoYi-Vue/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
</p> </p>
@@ -18,11 +18,31 @@
* 权限认证使用Jwt,支持多终端认证系统。 * 权限认证使用Jwt,支持多终端认证系统。
* 支持加载动态权限菜单,多方式轻松权限控制。 * 支持加载动态权限菜单,多方式轻松权限控制。
* 高效率开发,使用代码生成器可以一键生成前后端代码。 * 高效率开发,使用代码生成器可以一键生成前后端代码。
* 提供了技术栈([Vue3](https://v3.cn.vuejs.org) [Element Plus](https://element-plus.org/zh-CN) [Vite](https://cn.vitejs.dev))版本[RuoYi-Vue3](https://gitcode.com/yangzongzhuan/RuoYi-Vue3),保持同步更新。
* 提供了单应用版本[RuoYi-Vue-fast](https://gitcode.com/yangzongzhuan/RuoYi-Vue-fast)Oracle版本[RuoYi-Vue-Oracle](https://gitcode.com/yangzongzhuan/RuoYi-Vue-Oracle),保持同步更新。
* 不分离版本,请移步[RuoYi](https://gitee.com/y_project/RuoYi),微服务版本,请移步[RuoYi-Cloud](https://gitee.com/y_project/RuoYi-Cloud)
* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)&nbsp;&nbsp; * 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)&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) |
## 内置功能 ## 内置功能
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。 1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
@@ -92,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群](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群: [![加入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) 点击按钮入群。
+40 -82
View File
@@ -6,59 +6,37 @@
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>3.9.0</version> <version>3.9.2</version>
<name>ruoyi</name> <name>ruoyi</name>
<url>http://www.ruoyi.vip</url> <url>http://www.ruoyi.vip</url>
<description>若依管理系统</description> <description>若依管理系统</description>
<properties> <properties>
<ruoyi.version>3.9.0</ruoyi.version> <ruoyi.version>3.9.2</ruoyi.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version> <java.version>17</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version> <spring-boot.version>4.0.3</spring-boot.version>
<spring-boot.version>2.5.15</spring-boot.version> <mybatis-spring-boot.version>4.0.1</mybatis-spring-boot.version>
<druid.version>1.2.23</druid.version> <druid.version>1.2.28</druid.version>
<bitwalker.version>1.21</bitwalker.version> <yauaa.version>8.1.0</yauaa.version>
<swagger.version>3.0.0</swagger.version>
<kaptcha.version>2.3.3</kaptcha.version> <kaptcha.version>2.3.3</kaptcha.version>
<pagehelper.boot.version>1.4.7</pagehelper.boot.version> <pagehelper.boot.version>2.1.1</pagehelper.boot.version>
<fastjson.version>2.0.57</fastjson.version> <fastjson.version>2.0.61</fastjson.version>
<oshi.version>6.8.1</oshi.version> <oshi.version>6.10.0</oshi.version>
<commons.io.version>2.19.0</commons.io.version> <commons.io.version>2.21.0</commons.io.version>
<poi.version>4.1.2</poi.version> <poi.version>4.1.2</poi.version>
<velocity.version>2.3</velocity.version> <velocity.version>2.3</velocity.version>
<jwt.version>0.9.1</jwt.version> <jwt.version>0.9.1</jwt.version>
<!-- override dependency version --> <jaxb-api.version>2.3.1</jaxb-api.version>
<tomcat.version>9.0.105</tomcat.version> <springdoc.version>3.0.2</springdoc.version>
<logback.version>1.2.13</logback.version>
<spring-security.version>5.7.12</spring-security.version>
<spring-framework.version>5.3.39</spring-framework.version>
</properties> </properties>
<!-- 依赖声明 --> <!-- 依赖声明 -->
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>
<!-- 覆盖SpringFramework的依赖配置-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>${spring-framework.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 覆盖SpringSecurity的依赖配置-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-bom</artifactId>
<version>${spring-security.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- SpringBoot的依赖配置--> <!-- SpringBoot的依赖配置-->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@@ -68,50 +46,18 @@
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<!-- 覆盖logback的依赖配置-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- 覆盖tomcat的依赖配置-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
<version>${tomcat.version}</version>
</dependency>
<!-- 阿里数据库连接池 --> <!-- 阿里数据库连接池 -->
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId> <artifactId>druid-spring-boot-4-starter</artifactId>
<version>${druid.version}</version> <version>${druid.version}</version>
</dependency> </dependency>
<!-- 解析客户端操作系统、浏览器等 --> <!-- 解析客户端操作系统、浏览器等 -->
<dependency> <dependency>
<groupId>eu.bitwalker</groupId> <groupId>nl.basjes.parse.useragent</groupId>
<artifactId>UserAgentUtils</artifactId> <artifactId>yauaa</artifactId>
<version>${bitwalker.version}</version> <version>${yauaa.version}</version>
</dependency> </dependency>
<!-- pagehelper 分页插件 --> <!-- pagehelper 分页插件 -->
@@ -121,6 +67,18 @@
<version>${pagehelper.boot.version}</version> <version>${pagehelper.boot.version}</version>
</dependency> </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> <dependency>
<groupId>com.github.oshi</groupId> <groupId>com.github.oshi</groupId>
@@ -128,17 +86,11 @@
<version>${oshi.version}</version> <version>${oshi.version}</version>
</dependency> </dependency>
<!-- Swagger3依赖 --> <!-- spring-doc -->
<dependency> <dependency>
<groupId>io.springfox</groupId> <groupId>org.springdoc</groupId>
<artifactId>springfox-boot-starter</artifactId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${swagger.version}</version> <version>${springdoc.version}</version>
<exclusions>
<exclusion>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<!-- io常用工具类 --> <!-- io常用工具类 -->
@@ -236,13 +188,19 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version> <version>3.13.0</version>
<configuration> <configuration>
<parameters>true</parameters>
<source>${java.version}</source> <source>${java.version}</source>
<target>${java.version}</target> <target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding> <encoding>${project.build.sourceEncoding}</encoding>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
</plugins> </plugins>
</build> </build>
+7 -15
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>3.9.0</version> <version>3.9.2</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging> <packaging>jar</packaging>
@@ -24,23 +24,16 @@
<optional>true</optional> <!-- 表示依赖不会传递 --> <optional>true</optional> <!-- 表示依赖不会传递 -->
</dependency> </dependency>
<!-- swagger3--> <!-- spring-doc -->
<dependency> <dependency>
<groupId>io.springfox</groupId> <groupId>org.springdoc</groupId>
<artifactId>springfox-boot-starter</artifactId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<!-- 防止进入swagger页面报类型转换错误,排除3.0.0中的引用,手动增加1.6.2版本 -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
<version>1.6.2</version>
</dependency> </dependency>
<!-- Mysql驱动包 --> <!-- Mysql驱动包 -->
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>com.mysql</groupId>
<artifactId>mysql-connector-java</artifactId> <artifactId>mysql-connector-j</artifactId>
</dependency> </dependency>
<!-- 核心模块--> <!-- 核心模块-->
@@ -68,9 +61,8 @@
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.15</version>
<configuration> <configuration>
<fork>true</fork> <!-- 如果没有该配置,devtools不会生效 --> <addResources>true</addResources>
</configuration> </configuration>
<executions> <executions>
<execution> <execution>
@@ -2,7 +2,7 @@ package com.ruoyi;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; 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.awt.image.BufferedImage;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.annotation.Resource; import jakarta.annotation.Resource;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.FastByteArrayOutputStream; import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@@ -2,8 +2,8 @@ package com.ruoyi.web.controller.common;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; 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.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUploadUtils; import com.ruoyi.common.utils.file.FileUploadUtils;
@@ -35,7 +34,7 @@ public class CommonController
@Autowired @Autowired
private ServerConfig serverConfig; 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()); originalFilenames.add(file.getOriginalFilename());
} }
AjaxResult ajax = AjaxResult.success(); AjaxResult ajax = AjaxResult.success();
ajax.put("urls", StringUtils.join(urls, FILE_DELIMETER)); ajax.put("urls", StringUtils.join(urls, FILE_DELIMITER));
ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMETER)); ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMITER));
ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMETER)); ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMITER));
ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMETER)); ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMITER));
return ajax; return ajax;
} }
catch (Exception e) catch (Exception e)
@@ -148,7 +147,7 @@ public class CommonController
// 本地资源路径 // 本地资源路径
String localPath = RuoYiConfig.getProfile(); String localPath = RuoYiConfig.getProfile();
// 数据库资源地址 // 数据库资源地址
String downloadPath = localPath + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX); String downloadPath = localPath + FileUtils.stripPrefix(resource);
// 下载名称 // 下载名称
String downloadName = StringUtils.substringAfterLast(downloadPath, "/"); String downloadName = StringUtils.substringAfterLast(downloadPath, "/");
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
@@ -45,6 +45,7 @@ public class CacheController
caches.add(new SysCache(CacheConstants.PWD_ERR_CNT_KEY, "密码错误次数")); caches.add(new SysCache(CacheConstants.PWD_ERR_CNT_KEY, "密码错误次数"));
} }
@SuppressWarnings("deprecation")
@PreAuthorize("@ss.hasPermi('monitor:cache:list')") @PreAuthorize("@ss.hasPermi('monitor:cache:list')")
@GetMapping() @GetMapping()
public AjaxResult getInfo() throws Exception public AjaxResult getInfo() throws Exception
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.monitor; package com.ruoyi.web.controller.monitor;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.monitor; package com.ruoyi.web.controller.monitor;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.system; package com.ruoyi.web.controller.system;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@@ -1,6 +1,7 @@
package com.ruoyi.web.controller.system; package com.ruoyi.web.controller.system;
import java.util.List; import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
@@ -110,6 +111,20 @@ public class SysDeptController extends BaseController
return toAjax(deptService.updateDept(dept)); 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.ArrayList;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.system; package com.ruoyi.web.controller.system;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@@ -1,10 +1,17 @@
package com.ruoyi.web.controller.system; package com.ruoyi.web.controller.system;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired; 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.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.config.RuoYiConfig; 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.common.utils.StringUtils;
import com.ruoyi.system.service.ISysUserService;
/** /**
* 首页 * 首页
@@ -18,6 +25,9 @@ public class SysIndexController
@Autowired @Autowired
private RuoYiConfig ruoyiConfig; private RuoYiConfig ruoyiConfig;
@Autowired
private ISysUserService userService;
/** /**
* 访问首页,提示语 * 访问首页,提示语
*/ */
@@ -26,4 +36,29 @@ public class SysIndexController
{ {
return StringUtils.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion()); 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("解锁成功");
}
} }
@@ -87,6 +87,7 @@ public class SysLoginController
ajax.put("user", user); ajax.put("user", user);
ajax.put("roles", roles); ajax.put("roles", roles);
ajax.put("permissions", permissions); ajax.put("permissions", permissions);
ajax.put("pwdChrtype", getSysAccountChrtype());
ajax.put("isDefaultModifyPwd", initPasswordIsModify(user.getPwdUpdateDate())); ajax.put("isDefaultModifyPwd", initPasswordIsModify(user.getPwdUpdateDate()));
ajax.put("isPasswordExpired", passwordIsExpiration(user.getPwdUpdateDate())); ajax.put("isPasswordExpired", passwordIsExpiration(user.getPwdUpdateDate()));
return ajax; return ajax;
@@ -105,6 +106,12 @@ public class SysLoginController
return AjaxResult.success(menuService.buildMenus(menus)); return AjaxResult.success(menuService.buildMenus(menus));
} }
// 获取用户密码自定义配置规则
public String getSysAccountChrtype()
{
return Convert.toStr(configService.selectConfigByKey("sys.account.chrtype"), "0");
}
// 检查初始密码是否提醒修改 // 检查初始密码是否提醒修改
public boolean initPasswordIsModify(Date pwdUpdateDate) public boolean initPasswordIsModify(Date pwdUpdateDate)
{ {
@@ -1,6 +1,7 @@
package com.ruoyi.web.controller.system; package com.ruoyi.web.controller.system;
import java.util.List; import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@@ -93,6 +94,10 @@ public class SysMenuController extends BaseController
{ {
return error("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); return error("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
} }
else if (!menuService.checkRouteConfigUnique(menu))
{
return error("新增菜单'" + menu.getMenuName() + "'失败,路由名称或地址已存在");
}
menu.setCreateBy(getUsername()); menu.setCreateBy(getUsername());
return toAjax(menuService.insertMenu(menu)); return toAjax(menuService.insertMenu(menu));
} }
@@ -117,10 +122,28 @@ public class SysMenuController extends BaseController
{ {
return error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己"); return error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己");
} }
else if (!menuService.checkRouteConfigUnique(menu))
{
return error("修改菜单'" + menu.getMenuName() + "'失败,路由名称或地址已存在");
}
menu.setUpdateBy(getUsername()); menu.setUpdateBy(getUsername());
return toAjax(menuService.updateMenu(menu)); 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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.SysNotice; import com.ruoyi.system.domain.SysNotice;
import com.ruoyi.system.service.ISysNoticeReadService;
import com.ruoyi.system.service.ISysNoticeService; import com.ruoyi.system.service.ISysNoticeService;
/** /**
@@ -32,6 +35,9 @@ public class SysNoticeController extends BaseController
@Autowired @Autowired
private ISysNoticeService noticeService; 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}") @GetMapping(value = "/{noticeId}")
public AjaxResult getInfo(@PathVariable Long noticeId) public AjaxResult getInfo(@PathVariable Long noticeId)
{ {
@@ -78,6 +83,59 @@ public class SysNoticeController extends BaseController
return toAjax(noticeService.updateNotice(notice)); 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}") @DeleteMapping("/{noticeIds}")
public AjaxResult remove(@PathVariable Long[] noticeIds) public AjaxResult remove(@PathVariable Long[] noticeIds)
{ {
noticeReadService.deleteByNoticeIds(noticeIds);
return toAjax(noticeService.deleteNoticeByIds(noticeIds)); return toAjax(noticeService.deleteNoticeByIds(noticeIds));
} }
} }
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.system; package com.ruoyi.web.controller.system;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@@ -21,6 +21,7 @@ import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUploadUtils; import com.ruoyi.common.utils.file.FileUploadUtils;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.common.utils.file.MimeTypeUtils; import com.ruoyi.common.utils.file.MimeTypeUtils;
import com.ruoyi.framework.web.service.TokenService; import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.service.ISysUserService; import com.ruoyi.system.service.ISysUserService;
@@ -94,8 +95,9 @@ public class SysProfileController extends BaseController
String oldPassword = params.get("oldPassword"); String oldPassword = params.get("oldPassword");
String newPassword = params.get("newPassword"); String newPassword = params.get("newPassword");
LoginUser loginUser = getLoginUser(); LoginUser loginUser = getLoginUser();
String userName = loginUser.getUsername(); Long userId = loginUser.getUserId();
String password = loginUser.getPassword(); SysUser user = userService.selectUserById(userId);
String password = user.getPassword();
if (!SecurityUtils.matchesPassword(oldPassword, password)) if (!SecurityUtils.matchesPassword(oldPassword, password))
{ {
return error("修改密码失败,旧密码错误"); return error("修改密码失败,旧密码错误");
@@ -105,7 +107,7 @@ public class SysProfileController extends BaseController
return error("新密码不能与旧密码相同"); return error("新密码不能与旧密码相同");
} }
newPassword = SecurityUtils.encryptPassword(newPassword); newPassword = SecurityUtils.encryptPassword(newPassword);
if (userService.resetUserPwd(userName, newPassword) > 0) if (userService.resetUserPwd(userId, newPassword) > 0)
{ {
// 更新缓存用户密码&密码最后更新时间 // 更新缓存用户密码&密码最后更新时间
loginUser.getUser().setPwdUpdateDate(DateUtils.getNowDate()); loginUser.getUser().setPwdUpdateDate(DateUtils.getNowDate());
@@ -126,9 +128,14 @@ public class SysProfileController extends BaseController
if (!file.isEmpty()) if (!file.isEmpty())
{ {
LoginUser loginUser = getLoginUser(); LoginUser loginUser = getLoginUser();
String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION); String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION, true);
if (userService.updateUserAvatar(loginUser.getUsername(), avatar)) 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(); AjaxResult ajax = AjaxResult.success();
ajax.put("imgUrl", avatar); ajax.put("imgUrl", avatar);
// 更新缓存用户头像 // 更新缓存用户头像
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.system; package com.ruoyi.web.controller.system;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@@ -19,10 +19,8 @@ import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.service.SysPermissionService; import com.ruoyi.framework.web.service.SysPermissionService;
import com.ruoyi.framework.web.service.TokenService; import com.ruoyi.framework.web.service.TokenService;
@@ -128,14 +126,8 @@ public class SysRoleController extends BaseController
if (roleService.updateRole(role) > 0) if (roleService.updateRole(role) > 0)
{ {
// 更新缓存用户权限 // 刷新所有持有该角色的在线用户权限
LoginUser loginUser = getLoginUser(); tokenService.refreshPermissionByRoleId(role.getRoleId(), permissionService);
if (StringUtils.isNotNull(loginUser.getUser()) && !loginUser.getUser().isAdmin())
{
loginUser.setUser(userService.selectUserByUserName(loginUser.getUser().getUserName()));
loginUser.setPermissions(permissionService.getMenuPermission(loginUser.getUser()));
tokenService.setLoginUser(loginUser);
}
return success(); return success();
} }
return error("修改角色'" + role.getRoleName() + "'失败,请联系管理员"); return error("修改角色'" + role.getRoleName() + "'失败,请联系管理员");
@@ -2,7 +2,7 @@ package com.ruoyi.web.controller.system;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
@@ -111,7 +111,7 @@ public class SysUserController extends BaseController
ajax.put("roleIds", sysUser.getRoles().stream().map(SysRole::getRoleId).collect(Collectors.toList())); ajax.put("roleIds", sysUser.getRoles().stream().map(SysRole::getRoleId).collect(Collectors.toList()));
} }
List<SysRole> roles = roleService.selectRoleAll(); List<SysRole> roles = roleService.selectRoleAll();
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()));
ajax.put("posts", postService.selectPostAll()); ajax.put("posts", postService.selectPostAll());
return ajax; return ajax;
} }
@@ -226,7 +226,7 @@ public class SysUserController extends BaseController
SysUser user = userService.selectUserById(userId); SysUser user = userService.selectUserById(userId);
List<SysRole> roles = roleService.selectRolesByUserId(userId); List<SysRole> roles = roleService.selectRolesByUserId(userId);
ajax.put("user", user); 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; return ajax;
} }
@@ -15,19 +15,16 @@ import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import io.swagger.annotations.Api; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.annotations.ApiImplicitParam; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.annotations.ApiImplicitParams; import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
/** /**
* swagger 用户测试方法 * swagger 用户测试方法
* *
* @author ruoyi * @author ruoyi
*/ */
@Api("用户信息管理") @Tag(name = "用户信息管理")
@RestController @RestController
@RequestMapping("/test/user") @RequestMapping("/test/user")
public class TestController extends BaseController public class TestController extends BaseController
@@ -38,7 +35,7 @@ public class TestController extends BaseController
users.put(2, new UserEntity(2, "ry", "admin123", "15666666666")); users.put(2, new UserEntity(2, "ry", "admin123", "15666666666"));
} }
@ApiOperation("获取用户列表") @Operation(summary = "获取用户列表")
@GetMapping("/list") @GetMapping("/list")
public R<List<UserEntity>> userList() public R<List<UserEntity>> userList()
{ {
@@ -46,10 +43,10 @@ public class TestController extends BaseController
return R.ok(userList); return R.ok(userList);
} }
@ApiOperation("获取用户详细") @Operation(summary = "获取用户详细")
@ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class)
@GetMapping("/{userId}") @GetMapping("/{userId}")
public R<UserEntity> getUser(@PathVariable Integer userId) public R<UserEntity> getUser(@PathVariable(name = "userId")
Integer userId)
{ {
if (!users.isEmpty() && users.containsKey(userId)) if (!users.isEmpty() && users.containsKey(userId))
{ {
@@ -61,13 +58,7 @@ public class TestController extends BaseController
} }
} }
@ApiOperation("新增用户") @Operation(summary = "新增用户")
@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)
})
@PostMapping("/save") @PostMapping("/save")
public R<String> save(UserEntity user) public R<String> save(UserEntity user)
{ {
@@ -79,9 +70,10 @@ public class TestController extends BaseController
return R.ok(); return R.ok();
} }
@ApiOperation("更新用户") @Operation(summary = "更新用户")
@PutMapping("/update") @PutMapping("/update")
public R<String> update(@RequestBody UserEntity user) public R<String> update(@RequestBody
UserEntity user)
{ {
if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId())) if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId()))
{ {
@@ -96,10 +88,10 @@ public class TestController extends BaseController
return R.ok(); return R.ok();
} }
@ApiOperation("删除用户信息") @Operation(summary = "删除用户信息")
@ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class)
@DeleteMapping("/{userId}") @DeleteMapping("/{userId}")
public R<String> delete(@PathVariable Integer userId) public R<String> delete(@PathVariable(name = "userId")
Integer userId)
{ {
if (!users.isEmpty() && users.containsKey(userId)) if (!users.isEmpty() && users.containsKey(userId))
{ {
@@ -113,19 +105,19 @@ public class TestController extends BaseController
} }
} }
@ApiModel(value = "UserEntity", description = "用户实体") @Schema(description = "用户实体")
class UserEntity class UserEntity
{ {
@ApiModelProperty("用户ID") @Schema(title = "用户ID")
private Integer userId; private Integer userId;
@ApiModelProperty("用户名称") @Schema(title = "用户名称")
private String username; private String username;
@ApiModelProperty("用户密码") @Schema(title = "用户密码")
private String password; private String password;
@ApiModelProperty("用户手机") @Schema(title = "用户手机")
private String mobile; private String mobile;
public UserEntity() public UserEntity()
@@ -1,26 +1,15 @@
package com.ruoyi.web.core.config; 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.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.config.RuoYiConfig;
import io.swagger.annotations.ApiOperation; import io.swagger.v3.oas.models.Components;
import io.swagger.models.auth.In; import io.swagger.v3.oas.models.OpenAPI;
import springfox.documentation.builders.ApiInfoBuilder; import io.swagger.v3.oas.models.info.Contact;
import springfox.documentation.builders.PathSelectors; import io.swagger.v3.oas.models.info.Info;
import springfox.documentation.builders.RequestHandlerSelectors; import io.swagger.v3.oas.models.security.SecurityRequirement;
import springfox.documentation.service.ApiInfo; import io.swagger.v3.oas.models.security.SecurityScheme;
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;
/** /**
* Swagger2的接口配置 * Swagger2的接口配置
@@ -34,92 +23,42 @@ public class SwaggerConfig
@Autowired @Autowired
private RuoYiConfig ruoyiConfig; private RuoYiConfig ruoyiConfig;
/** 是否开启swagger */
@Value("${swagger.enabled}")
private boolean enabled;
/** 设置请求的统一前缀 */
@Value("${swagger.pathMapping}")
private String pathMapping;
/** /**
* 创建API * 自定义的 OpenAPI 对象
*/ */
@Bean @Bean
public Docket createRestApi() public OpenAPI customOpenApi()
{ {
return new Docket(DocumentationType.OAS_30) return new OpenAPI().components(new Components()
// 是否启用Swagger // 设置认证的请求头
.enable(enabled) .addSecuritySchemes("apikey", securityScheme()))
// 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息) .addSecurityItem(new SecurityRequirement().addList("apikey"))
.apiInfo(apiInfo()) .info(getApiInfo());
// 设置哪些接口暴露给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);
} }
/** @Bean
* 安全模式,这里指定token通过Authorization头请求头传递 public SecurityScheme securityScheme()
*/
private List<SecurityScheme> securitySchemes()
{ {
List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>(); return new SecurityScheme()
apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue())); .type(SecurityScheme.Type.APIKEY)
return apiKeyList; .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 Info()
return new ApiInfoBuilder() // 设置标题
// 设置标题 .title("标题:若依管理系统_接口文档")
.title("标题:若依管理系统_接口文档") // 描述
// 描述 .description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")
.description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...") // 作者信息
// 作者信息 .contact(new Contact().name(ruoyiConfig.getName()))
.contact(new Contact(ruoyiConfig.getName(), null, null)) // 版本
// 版本 .version("版本号:" + ruoyiConfig.getVersion());
.version("版本号:" + ruoyiConfig.getVersion())
.build();
} }
} }
+48 -30
View File
@@ -3,9 +3,9 @@ ruoyi:
# 名称 # 名称
name: RuoYi name: RuoYi
# 版本 # 版本
version: 3.9.0 version: 3.9.2
# 版权年份 # 版权年份
copyrightYear: 2025 copyrightYear: 2026
# 文件路径 示例( Windows配置D:/ruoyi/uploadPathLinux配置 /home/ruoyi/uploadPath # 文件路径 示例( Windows配置D:/ruoyi/uploadPathLinux配置 /home/ruoyi/uploadPath
profile: D:/ruoyi/uploadPath profile: D:/ruoyi/uploadPath
# 获取ip地址开关 # 获取ip地址开关
@@ -60,33 +60,37 @@ spring:
max-file-size: 10MB max-file-size: 10MB
# 设置总上传的文件大小 # 设置总上传的文件大小
max-request-size: 20MB max-request-size: 20MB
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
# 服务模块 # 服务模块
devtools: devtools:
restart: restart:
# 热部署开关 # 热部署开关
enabled: true enabled: true
# redis 配置 data:
redis: # redis 配置
# 地址 redis:
host: localhost # 地址
# 端口,默认为6379 host: localhost
port: 6379 # 端口,默认为6379
# 数据库索引 port: 6379
database: 0 # 数据库索引
# 密码 database: 0
password: # 密码
# 连接超时时间 password:
timeout: 10s # 连接超时时间
lettuce: timeout: 10s
pool: lettuce:
# 连接池中的最小空闲连接 pool:
min-idle: 0 # 连接池中的最小空闲连接
# 连接池中的最大空闲连接 min-idle: 0
max-idle: 8 # 连接池中的最大空闲连接
# 连接池的最大数据库连接数 max-idle: 8
max-active: 8 # 连接池的最大数据库连接数
# #连接池最大阻塞等待时间(使用负值表示没有限制) max-active: 8
max-wait: -1ms # #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# token配置 # token配置
token: token:
@@ -112,12 +116,26 @@ pagehelper:
supportMethodsArguments: true supportMethodsArguments: true
params: count=countSql params: count=countSql
# Swagger配置 # Springdoc配置
swagger: springdoc:
# 是否开启swagger api-docs:
enabled: true path: /v3/api-docs
# 请求前缀 swagger-ui:
pathMapping: /dev-api 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: xss:
+11 -12
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>3.9.0</version> <version>3.9.2</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -55,7 +55,7 @@
<!-- JSON工具类 --> <!-- JSON工具类 -->
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
</dependency> </dependency>
@@ -77,12 +77,6 @@
<artifactId>poi-ooxml</artifactId> <artifactId>poi-ooxml</artifactId>
</dependency> </dependency>
<!-- yml解析器 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<!-- Token生成与解析--> <!-- Token生成与解析-->
<dependency> <dependency>
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
@@ -101,6 +95,11 @@
<artifactId>spring-boot-starter-data-redis</artifactId> <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- pool 对象池 --> <!-- pool 对象池 -->
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
@@ -109,14 +108,14 @@
<!-- 解析客户端操作系统、浏览器等 --> <!-- 解析客户端操作系统、浏览器等 -->
<dependency> <dependency>
<groupId>eu.bitwalker</groupId> <groupId>nl.basjes.parse.useragent</groupId>
<artifactId>UserAgentUtils</artifactId> <artifactId>yauaa</artifactId>
</dependency> </dependency>
<!-- servlet包 --> <!-- servlet包 -->
<dependency> <dependency>
<groupId>javax.servlet</groupId> <groupId>jakarta.servlet</groupId>
<artifactId>javax.servlet-api</artifactId> <artifactId>jakarta.servlet-api</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -16,15 +16,25 @@ import java.lang.annotation.Target;
@Documented @Documented
public @interface DataScope public @interface DataScope
{ {
/**
* 用户表的别名
*/
public String userAlias() default "";
/** /**
* 部门表的别名 * 部门表的别名
*/ */
public String deptAlias() default ""; public String deptAlias() default "";
/** /**
* 用户表的别 * 用户字段
*/ */
public String userAlias() default ""; public String userField() default "user_id";
/**
* 部门字段名
*/
public String deptField() default "dept_id";
/** /**
* 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@ss获取,多个权限用逗号分隔开来 * 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@ss获取,多个权限用逗号分隔开来
@@ -56,6 +56,7 @@ public @interface Excel
/** /**
* BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN * BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN
*/ */
@SuppressWarnings("deprecation")
public int roundingMode() default BigDecimal.ROUND_HALF_EVEN; public int roundingMode() default BigDecimal.ROUND_HALF_EVEN;
/** /**
@@ -5,7 +5,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import tools.jackson.databind.annotation.JsonSerialize;
import com.ruoyi.common.config.serializer.SensitiveJsonSerializer; import com.ruoyi.common.config.serializer.SensitiveJsonSerializer;
import com.ruoyi.common.enums.DesensitizedType; import com.ruoyi.common.enums.DesensitizedType;
@@ -15,7 +15,7 @@ import com.ruoyi.common.enums.DesensitizedType;
* @author ruoyi * @author ruoyi
*/ */
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD) @Target({ ElementType.FIELD, ElementType.METHOD })
@JacksonAnnotationsInside @JacksonAnnotationsInside
@JsonSerialize(using = SensitiveJsonSerializer.class) @JsonSerialize(using = SensitiveJsonSerializer.class)
public @interface Sensitive public @interface Sensitive
@@ -1,13 +1,13 @@
package com.ruoyi.common.config.serializer; package com.ruoyi.common.config.serializer;
import java.io.IOException;
import java.util.Objects; import java.util.Objects;
import com.fasterxml.jackson.core.JsonGenerator; import tools.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.BeanProperty; import tools.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonMappingException; import tools.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonSerializer; import tools.jackson.databind.DatabindException;
import com.fasterxml.jackson.databind.SerializerProvider; import tools.jackson.databind.SerializationContext;
import com.fasterxml.jackson.databind.ser.ContextualSerializer; import tools.jackson.databind.ValueSerializer;
import tools.jackson.databind.ser.std.StdSerializer;
import com.ruoyi.common.annotation.Sensitive; import com.ruoyi.common.annotation.Sensitive;
import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.enums.DesensitizedType; import com.ruoyi.common.enums.DesensitizedType;
@@ -18,14 +18,26 @@ import com.ruoyi.common.utils.SecurityUtils;
* *
* @author ruoyi * @author ruoyi
*/ */
public class SensitiveJsonSerializer extends JsonSerializer<String> implements ContextualSerializer public class SensitiveJsonSerializer extends StdSerializer<String>
{ {
private DesensitizedType desensitizedType; private final DesensitizedType desensitizedType;
public SensitiveJsonSerializer()
{
super(String.class);
this.desensitizedType = null;
}
public SensitiveJsonSerializer(DesensitizedType desensitizedType)
{
super(String.class);
this.desensitizedType = desensitizedType;
}
@Override @Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException public void serialize(String value, JsonGenerator gen, SerializationContext ctxt) throws JacksonException
{ {
if (desensitization()) if (desensitizedType != null && desensitization())
{ {
gen.writeString(desensitizedType.desensitizer().apply(value)); gen.writeString(desensitizedType.desensitizer().apply(value));
} }
@@ -36,16 +48,14 @@ public class SensitiveJsonSerializer extends JsonSerializer<String> implements C
} }
@Override @Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) public ValueSerializer<?> createContextual(SerializationContext ctxt, BeanProperty property) throws DatabindException
throws JsonMappingException
{ {
Sensitive annotation = property.getAnnotation(Sensitive.class); Sensitive annotation = property.getAnnotation(Sensitive.class);
if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass()))
{ {
this.desensitizedType = annotation.desensitizedType(); return new SensitiveJsonSerializer(annotation.desensitizedType());
return this;
} }
return prov.findValueSerializer(property.getType(), property); return ctxt.findValueSerializer(property.getType());
} }
/** /**
@@ -83,12 +83,12 @@ public class Constants
/** /**
* 角色权限分隔符 * 角色权限分隔符
*/ */
public static final String ROLE_DELIMETER = ","; public static final String ROLE_DELIMITER = ",";
/** /**
* 权限标识分隔符 * 权限标识分隔符
*/ */
public static final String PERMISSION_DELIMETER = ","; public static final String PERMISSION_DELIMITER = ",";
/** /**
* 验证码有效期(分钟) * 验证码有效期(分钟)
@@ -158,7 +158,7 @@ public class Constants
/** /**
* 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全) * 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全)
*/ */
public static final String[] JSON_WHITELIST_STR = { "org.springframework", "com.ruoyi" }; public static final String[] JSON_WHITELIST_STR = { "com.ruoyi" };
/** /**
* 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
@@ -170,4 +170,35 @@ public class Constants
*/ */
public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
"org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config", "com.ruoyi.generator" }; "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 PARENT_MENU_NAME = "parentMenuName";
/** 生成详情页开关 */
public static final String GEN_VIEW = "genView";
/** 数据库字符串类型 */ /** 数据库字符串类型 */
public static final String[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2" }; public static final String[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2" };
@@ -2,10 +2,10 @@ package com.ruoyi.common.core.domain.entity;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.validation.constraints.Email; import jakarta.validation.constraints.Email;
import javax.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import javax.validation.constraints.Size; import jakarta.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.core.domain.BaseEntity;
@@ -1,7 +1,7 @@
package com.ruoyi.common.core.domain.entity; package com.ruoyi.common.core.domain.entity;
import javax.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import javax.validation.constraints.Size; import jakarta.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel;
@@ -1,8 +1,8 @@
package com.ruoyi.common.core.domain.entity; package com.ruoyi.common.core.domain.entity;
import javax.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
import javax.validation.constraints.Size; import jakarta.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel;
@@ -2,9 +2,9 @@ package com.ruoyi.common.core.domain.entity;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import javax.validation.constraints.Size; import jakarta.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.core.domain.BaseEntity;
@@ -1,9 +1,9 @@
package com.ruoyi.common.core.domain.entity; package com.ruoyi.common.core.domain.entity;
import java.util.Set; import java.util.Set;
import javax.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import javax.validation.constraints.Size; import jakarta.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel;
@@ -2,14 +2,17 @@ package com.ruoyi.common.core.domain.entity;
import java.util.Date; import java.util.Date;
import java.util.List; 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.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; 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;
import com.ruoyi.common.annotation.Excel.ColumnType; import com.ruoyi.common.annotation.Excel.ColumnType;
import com.ruoyi.common.annotation.Excel.Type; import com.ruoyi.common.annotation.Excel.Type;
import com.ruoyi.common.annotation.Excels; import com.ruoyi.common.annotation.Excels;
import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.xss.Xss; import com.ruoyi.common.xss.Xss;
/** /**
@@ -67,6 +70,7 @@ public class SysUser extends BaseEntity
private String loginIp; 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) @Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT)
private Date loginDate; private Date loginDate;
@@ -114,12 +118,7 @@ public class SysUser extends BaseEntity
public boolean isAdmin() public boolean isAdmin()
{ {
return isAdmin(this.userId); return SecurityUtils.isAdmin(this.userId);
}
public static boolean isAdmin(Long userId)
{
return userId != null && 1L == userId;
} }
public Long getDeptId() public Long getDeptId()
@@ -200,6 +199,7 @@ public class SysUser extends BaseEntity
this.avatar = avatar; this.avatar = avatar;
} }
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
public String getPassword() public String getPassword()
{ {
return password; return password;
@@ -1,11 +1,15 @@
package com.ruoyi.common.core.domain.model; 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.alibaba.fastjson2.annotation.JSONField;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Set;
/** /**
* 登录用户身份权限 * 登录用户身份权限
@@ -259,8 +263,16 @@ public class LoginUser implements UserDetails
} }
@Override @Override
@JSONField(serialize = false)
public Collection<? extends GrantedAuthority> getAuthorities() 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());
} }
} }
@@ -2,7 +2,7 @@ package com.ruoyi.common.enums;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.springframework.lang.Nullable; import org.jspecify.annotations.Nullable;
/** /**
* 请求方式 * 请求方式
@@ -3,7 +3,7 @@ package com.ruoyi.common.exception.file;
import java.util.Arrays; import java.util.Arrays;
/** /**
* 文件上传异常类 * 文件上传无效扩展名异常类
* *
* @author ruoyi * @author ruoyi
*/ */
@@ -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; package com.ruoyi.common.filter;
import java.io.IOException; import java.io.IOException;
import javax.servlet.Filter; import jakarta.servlet.Filter;
import javax.servlet.FilterChain; import jakarta.servlet.FilterChain;
import javax.servlet.FilterConfig; import jakarta.servlet.FilterConfig;
import javax.servlet.ServletException; import jakarta.servlet.ServletException;
import javax.servlet.ServletRequest; import jakarta.servlet.ServletRequest;
import javax.servlet.ServletResponse; import jakarta.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
@@ -4,11 +4,11 @@ import java.io.BufferedReader;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import javax.servlet.ReadListener; import jakarta.servlet.ReadListener;
import javax.servlet.ServletInputStream; import jakarta.servlet.ServletInputStream;
import javax.servlet.ServletResponse; import jakarta.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper; import jakarta.servlet.http.HttpServletRequestWrapper;
import com.ruoyi.common.utils.http.HttpHelper; import com.ruoyi.common.utils.http.HttpHelper;
import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.Constants;
@@ -3,14 +3,14 @@ package com.ruoyi.common.filter;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.servlet.Filter; import jakarta.servlet.Filter;
import javax.servlet.FilterChain; import jakarta.servlet.FilterChain;
import javax.servlet.FilterConfig; import jakarta.servlet.FilterConfig;
import javax.servlet.ServletException; import jakarta.servlet.ServletException;
import javax.servlet.ServletRequest; import jakarta.servlet.ServletRequest;
import javax.servlet.ServletResponse; import jakarta.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.enums.HttpMethod; import com.ruoyi.common.enums.HttpMethod;
@@ -2,10 +2,10 @@ package com.ruoyi.common.filter;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import javax.servlet.ReadListener; import jakarta.servlet.ReadListener;
import javax.servlet.ServletInputStream; import jakarta.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper; import jakarta.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@@ -16,6 +16,7 @@ import org.apache.commons.lang3.time.DateFormatUtils;
* *
* @author ruoyi * @author ruoyi
*/ */
@SuppressWarnings("deprecation")
public class DateUtils extends org.apache.commons.lang3.time.DateUtils public class DateUtils extends org.apache.commons.lang3.time.DateUtils
{ {
public static String YYYY = "yyyy"; public static String YYYY = "yyyy";
@@ -1,7 +1,9 @@
package com.ruoyi.common.utils; package com.ruoyi.common.utils;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONArray;
import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.core.domain.entity.SysDictData; import com.ruoyi.common.core.domain.entity.SysDictData;
@@ -89,37 +91,25 @@ public class DictUtils
*/ */
public static String getDictLabel(String dictType, String dictValue, String separator) public static String getDictLabel(String dictType, String dictValue, String separator)
{ {
StringBuilder propertyString = new StringBuilder();
List<SysDictData> datas = getDictCache(dictType); List<SysDictData> datas = getDictCache(dictType);
if (StringUtils.isNull(datas)) if (StringUtils.isNull(datas) || StringUtils.isEmpty(dictValue))
{ {
return StringUtils.EMPTY; return StringUtils.EMPTY;
} }
if (StringUtils.containsAny(separator, dictValue)) Map<String, String> dictMap = datas.stream().collect(HashMap::new, (map, dict) -> map.put(dict.getDictValue(), dict.getDictLabel()), Map::putAll);
if (!StringUtils.contains(dictValue, separator))
{ {
for (SysDictData dict : datas) return dictMap.getOrDefault(dictValue, StringUtils.EMPTY);
}
StringBuilder labelBuilder = new StringBuilder();
for (String seperatedValue : dictValue.split(separator))
{
if (dictMap.containsKey(seperatedValue))
{ {
for (String value : dictValue.split(separator)) labelBuilder.append(dictMap.get(seperatedValue)).append(separator);
{
if (value.equals(dict.getDictValue()))
{
propertyString.append(dict.getDictLabel()).append(separator);
break;
}
}
} }
} }
else return StringUtils.removeEnd(labelBuilder.toString(), separator);
{
for (SysDictData dict : datas)
{
if (dictValue.equals(dict.getDictValue()))
{
return dict.getDictLabel();
}
}
}
return StringUtils.stripEnd(propertyString.toString(), separator);
} }
/** /**
@@ -132,37 +122,25 @@ public class DictUtils
*/ */
public static String getDictValue(String dictType, String dictLabel, String separator) public static String getDictValue(String dictType, String dictLabel, String separator)
{ {
StringBuilder propertyString = new StringBuilder();
List<SysDictData> datas = getDictCache(dictType); List<SysDictData> datas = getDictCache(dictType);
if (StringUtils.isNull(datas)) if (StringUtils.isNull(datas) || StringUtils.isEmpty(dictLabel))
{ {
return StringUtils.EMPTY; return StringUtils.EMPTY;
} }
if (StringUtils.containsAny(separator, dictLabel)) Map<String, String> dictMap = datas.stream().collect(HashMap::new, (map, dict) -> map.put(dict.getDictLabel(), dict.getDictValue()), Map::putAll);
if (!StringUtils.contains(dictLabel, separator))
{ {
for (SysDictData dict : datas) return dictMap.getOrDefault(dictLabel, StringUtils.EMPTY);
}
StringBuilder valueBuilder = new StringBuilder();
for (String seperatedValue : dictLabel.split(separator))
{
if (dictMap.containsKey(seperatedValue))
{ {
for (String label : dictLabel.split(separator)) valueBuilder.append(dictMap.get(seperatedValue)).append(separator);
{
if (label.equals(dict.getDictLabel()))
{
propertyString.append(dict.getDictValue()).append(separator);
break;
}
}
} }
} }
else return StringUtils.removeEnd(valueBuilder.toString(), separator);
{
for (SysDictData dict : datas)
{
if (dictLabel.equals(dict.getDictLabel()))
{
return dict.getDictValue();
}
}
}
return StringUtils.stripEnd(propertyString.toString(), separator);
} }
/** /**
@@ -114,6 +114,16 @@ public class SecurityUtils
return passwordEncoder.matches(rawPassword, encodedPassword); return passwordEncoder.matches(rawPassword, encodedPassword);
} }
/**
* 是否为管理员
*
* @return 结果
*/
public static boolean isAdmin()
{
return isAdmin(getUserId());
}
/** /**
* 是否为管理员 * 是否为管理员
* *
@@ -7,10 +7,10 @@ import java.net.URLEncoder;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.servlet.ServletRequest; import jakarta.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletRequestAttributes;
@@ -6,6 +6,7 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.commons.lang3.Strings;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.text.StrFormatter; import com.ruoyi.common.core.text.StrFormatter;
@@ -15,6 +16,7 @@ import com.ruoyi.common.core.text.StrFormatter;
* *
* @author ruoyi * @author ruoyi
*/ */
@SuppressWarnings("deprecation")
public class StringUtils extends org.apache.commons.lang3.StringUtils public class StringUtils extends org.apache.commons.lang3.StringUtils
{ {
/** 空字符串 */ /** 空字符串 */
@@ -432,12 +434,24 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
return list; return list;
} }
/**
* 检查子字符串是否存在
*
* @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 * 判断给定的collection列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value
* *
* @param collection 给定的集合 * @param collection 给定的集合
* @param array 给定的数组 * @param array 给定的数组
* @return boolean 结果 * @return 结果
*/ */
public static boolean containsAny(Collection<String> collection, String... array) public static boolean containsAny(Collection<String> collection, String... array)
{ {
@@ -458,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);
}
/** /**
* 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写 * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写
* *
@@ -481,6 +507,163 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
return false; 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);
}
/** /**
* 驼峰转下划线命名 * 驼峰转下划线命名
*/ */
@@ -552,6 +735,22 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
return false; 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 * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld
* *
@@ -1,9 +1,9 @@
package com.ruoyi.common.utils.bean; package com.ruoyi.common.utils.bean;
import java.util.Set; import java.util.Set;
import javax.validation.ConstraintViolation; import jakarta.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException; import jakarta.validation.ConstraintViolationException;
import javax.validation.Validator; import jakarta.validation.Validator;
/** /**
* bean对象属性验证 * bean对象属性验证
@@ -13,6 +13,7 @@ import com.ruoyi.common.exception.file.FileSizeLimitExceededException;
import com.ruoyi.common.exception.file.InvalidExtensionException; import com.ruoyi.common.exception.file.InvalidExtensionException;
import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.common.utils.uuid.Seq; import com.ruoyi.common.utils.uuid.Seq;
/** /**
@@ -102,15 +103,35 @@ public class FileUploadUtils
throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
InvalidExtensionException InvalidExtensionException
{ {
int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length(); return upload(baseDir, file, allowedExtension, false);
if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) }
/**
* 文件上传
*
* @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); throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
} }
assertAllowed(file, allowedExtension); assertAllowed(file, allowedExtension);
String fileName = extractFilename(file); String fileName = useCustomNaming ? uuidFilename(file) : extractFilename(file);
String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath(); String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
file.transferTo(Paths.get(absPath)); file.transferTo(Paths.get(absPath));
@@ -118,12 +139,19 @@ public class FileUploadUtils
} }
/** /**
* 编码文件名 * 编码文件名(日期格式目录 + 原文件名 + 序列值 + 后缀)
*/ */
public static final String extractFilename(MultipartFile file) public static final String extractFilename(MultipartFile file)
{ {
return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
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 public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException
@@ -9,15 +9,16 @@ import java.io.OutputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils; 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); 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.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import javax.servlet.ServletRequest; import jakarta.servlet.ServletRequest;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -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); private static final Logger log = LoggerFactory.getLogger(AddressUtils.class);
// IP地址查询 // 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"; public static final String UNKNOWN = "XX XX";
@@ -2,7 +2,7 @@ package com.ruoyi.common.utils.ip;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
@@ -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;
}
}
@@ -23,7 +23,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.RegExUtils; import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.commons.lang3.reflect.FieldUtils;
@@ -106,6 +106,11 @@ public class ExcelUtil<T>
*/ */
public Map<String, String> sysDictMap = new HashMap<String, String>(); public Map<String, String> sysDictMap = new HashMap<String, String>();
/**
* 单元格样式缓存
*/
private Map<String, CellStyle> cellStyleCache = new HashMap<String, CellStyle>();
/** /**
* Excel sheet最大行数,默认65536 * Excel sheet最大行数,默认65536
*/ */
@@ -174,23 +179,18 @@ public class ExcelUtil<T>
/** /**
* 对象的子列表方法 * 对象的子列表方法
*/ */
private Method subMethod; private Map<String, Method> subMethods;
/** /**
* 对象的子列表属性 * 对象的子列表属性
*/ */
private List<Field> subFields; private Map<String, List<Field>> subFieldsMap;
/** /**
* 统计列表 * 统计列表
*/ */
private Map<Integer, Double> statistics = new HashMap<Integer, Double>(); private Map<Integer, Double> statistics = new HashMap<Integer, Double>();
/**
* 数字格式
*/
private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00");
/** /**
* 实体对象 * 实体对象
*/ */
@@ -257,7 +257,10 @@ public class ExcelUtil<T>
int titleLastCol = this.fields.size() - 1; int titleLastCol = this.fields.size() - 1;
if (isSubList()) if (isSubList())
{ {
titleLastCol = titleLastCol + subFields.size() - 1; for (List<Field> currentSubFields : subFieldsMap.values())
{
titleLastCol = titleLastCol + currentSubFields.size() - 1;
}
} }
Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0); Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0);
titleRow.setHeightInPoints(30); titleRow.setHeightInPoints(30);
@@ -277,16 +280,17 @@ public class ExcelUtil<T>
{ {
Row subRow = sheet.createRow(rownum); Row subRow = sheet.createRow(rownum);
int column = 0; int column = 0;
int subFieldSize = subFields != null ? subFields.size() : 0;
for (Object[] objects : fields) for (Object[] objects : fields)
{ {
Field field = (Field) objects[0]; Field field = (Field) objects[0];
Excel attr = (Excel) objects[1]; Excel attr = (Excel) objects[1];
CellStyle cellStyle = styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()));
if (Collection.class.isAssignableFrom(field.getType())) if (Collection.class.isAssignableFrom(field.getType()))
{ {
Cell cell = subRow.createCell(column); Cell cell = subRow.createCell(column);
cell.setCellValue(attr.name()); cell.setCellValue(attr.name());
cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); cell.setCellStyle(cellStyle);
int subFieldSize = subFieldsMap != null ? subFieldsMap.get(field.getName()).size() : 0;
if (subFieldSize > 1) if (subFieldSize > 1)
{ {
CellRangeAddress cellAddress = new CellRangeAddress(rownum, rownum, column, column + subFieldSize - 1); CellRangeAddress cellAddress = new CellRangeAddress(rownum, rownum, column, column + subFieldSize - 1);
@@ -298,7 +302,7 @@ public class ExcelUtil<T>
{ {
Cell cell = subRow.createCell(column++); Cell cell = subRow.createCell(column++);
cell.setCellValue(attr.name()); cell.setCellValue(attr.name());
cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); cell.setCellStyle(cellStyle);
} }
} }
rownum++; rownum++;
@@ -379,7 +383,11 @@ public class ExcelUtil<T>
Map<String, Integer> cellMap = new HashMap<String, Integer>(); Map<String, Integer> cellMap = new HashMap<String, Integer>();
// 获取表头 // 获取表头
Row heard = sheet.getRow(titleNum); Row heard = sheet.getRow(titleNum);
for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) if (heard == null)
{
throw new UtilException("文件标题行为空,请检查Excel文件格式");
}
for (int i = 0; i < heard.getLastCellNum(); i++)
{ {
Cell cell = heard.getCell(i); Cell cell = heard.getCell(i);
if (StringUtils.isNotNull(cell)) if (StringUtils.isNotNull(cell))
@@ -387,10 +395,6 @@ public class ExcelUtil<T>
String value = this.getCellValue(heard, i).toString(); String value = this.getCellValue(heard, i).toString();
cellMap.put(value, i); cellMap.put(value, i);
} }
else
{
cellMap.put(null, i);
}
} }
// 有数据时才处理 得到类的所有field. // 有数据时才处理 得到类的所有field.
List<Object[]> fields = this.getFields(); List<Object[]> fields = this.getFields();
@@ -419,7 +423,7 @@ public class ExcelUtil<T>
Object val = this.getCellValue(row, entry.getKey()); Object val = this.getCellValue(row, entry.getKey());
// 如果不存在实例则新建. // 如果不存在实例则新建.
entity = (entity == null ? clazz.newInstance() : entity); entity = (entity == null ? clazz.getDeclaredConstructor().newInstance() : entity);
// 从map中得到对应列的field. // 从map中得到对应列的field.
Field field = (Field) entry.getValue()[0]; Field field = (Field) entry.getValue()[0];
Excel attr = (Excel) entry.getValue()[1]; Excel attr = (Excel) entry.getValue()[1];
@@ -581,6 +585,117 @@ public class ExcelUtil<T>
exportExcel(response); exportExcel(response);
} }
/**
* 多 Sheet 导出 —— 将多个不同类型的数据集合写入同一 Excel,直接输出到 HttpServletResponse
*
* @param response HTTP 响应
* @param sheets Sheet 描述列表
*/
public static void exportMultiSheet(HttpServletResponse response, List<ExcelSheet<?>> sheets)
{
if (sheets == null || sheets.isEmpty())
{
return;
}
SXSSFWorkbook wb = buildWorkbook(sheets);
try
{
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
wb.write(response.getOutputStream());
}
catch (Exception e)
{
log.error("多Sheet导出Excel异常{}", e.getMessage());
}
finally
{
IOUtils.closeQuietly(wb);
}
}
/**
* 多 Sheet 导出 —— 将多个不同类型的数据集合写入同一 Excel,生成文件并返回下载地址
*
* @param sheets Sheet 描述列表
* @return AjaxResult(含文件下载地址)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static AjaxResult exportMultiSheet(List<ExcelSheet<?>> sheets)
{
if (sheets == null || sheets.isEmpty())
{
return AjaxResult.error("导出数据不能为空");
}
SXSSFWorkbook wb = buildWorkbook(sheets);
OutputStream out = null;
try
{
ExcelUtil firstUtil = new ExcelUtil(sheets.get(0).getClazz());
String filename = firstUtil.encodingFilename(sheets.get(0).getSheetName());
out = new FileOutputStream(firstUtil.getAbsoluteFile(filename));
wb.write(out);
return AjaxResult.success(filename);
}
catch (Exception e)
{
log.error("多Sheet导出Excel异常{}", e.getMessage());
throw new UtilException("导出Excel失败,请联系网站管理员!");
}
finally
{
IOUtils.closeQuietly(wb);
IOUtils.closeQuietly(out);
}
}
/**
* 构建多 Sheet Workbook —— 创建 SXSSFWorkbook 并将所有 Sheet 数据写入
*
* @param sheets Sheet 描述列表
* @return 已写入所有 Sheet 数据的 SXSSFWorkbook
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private static SXSSFWorkbook buildWorkbook(List<ExcelSheet<?>> sheets)
{
SXSSFWorkbook wb = new SXSSFWorkbook(500);
for (ExcelSheet<?> excelSheet : sheets)
{
ExcelUtil util = new ExcelUtil(excelSheet.getClazz());
util.initWithWorkbook(wb, excelSheet.getList(), excelSheet.getSheetName(), excelSheet.getTitle());
util.writeSheet();
}
return wb;
}
/**
* 使用外部传入的 Workbook 初始化(多 Sheet 导出专用)
* 与 init() 的区别:不新建 Workbook,而是在已有 wb 上追加新 Sheet
*
* @param wb 已有工作簿
* @param list 数据集合
* @param sheetName Sheet 名称
* @param title 大标题(可为空)
*/
public void initWithWorkbook(SXSSFWorkbook wb, List<T> list, String sheetName, String title)
{
if (list == null)
{
list = new ArrayList<T>();
}
this.list = list;
this.sheetName = sheetName;
this.title = title != null ? title : "";
this.type = Type.EXPORT;
this.rownum = 0;
this.wb = wb;
this.sheet = wb.createSheet(sheetName);
createExcelField();
this.styles = createStyles(wb);
createTitle();
createSubHead();
}
/** /**
* 对list数据源将其里面的数据导入到excel表单 * 对list数据源将其里面的数据导入到excel表单
* *
@@ -702,7 +817,8 @@ public class ExcelUtil<T>
Excel excel = (Excel) os[1]; Excel excel = (Excel) os[1];
if (Collection.class.isAssignableFrom(field.getType())) if (Collection.class.isAssignableFrom(field.getType()))
{ {
for (Field subField : subFields) List<Field> currentSubFields = subFieldsMap.get(field.getName());
for (Field subField : currentSubFields)
{ {
Excel subExcel = subField.getAnnotation(Excel.class); Excel subExcel = subField.getAnnotation(Excel.class);
this.createHeadCell(subExcel, row, column++); this.createHeadCell(subExcel, row, column++);
@@ -715,7 +831,7 @@ public class ExcelUtil<T>
} }
if (Type.EXPORT.equals(type)) if (Type.EXPORT.equals(type))
{ {
fillExcelData(index, row); fillExcelData(index);
addStatisticsRow(); addStatisticsRow();
} }
} }
@@ -725,10 +841,9 @@ public class ExcelUtil<T>
* 填充excel数据 * 填充excel数据
* *
* @param index 序号 * @param index 序号
* @param row 单元格行
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void fillExcelData(int index, Row row) public void fillExcelData(int index)
{ {
int startNo = index * sheetSize; int startNo = index * sheetSize;
int endNo = Math.min(startNo + sheetSize, list.size()); int endNo = Math.min(startNo + sheetSize, list.size());
@@ -736,7 +851,7 @@ public class ExcelUtil<T>
for (int i = startNo; i < endNo; i++) for (int i = startNo; i < endNo; i++)
{ {
row = sheet.createRow(currentRowNum); Row row = sheet.createRow(currentRowNum);
T vo = (T) list.get(i); T vo = (T) list.get(i);
int column = 0; int column = 0;
int maxSubListSize = getCurrentMaxSubListSize(vo); int maxSubListSize = getCurrentMaxSubListSize(vo);
@@ -749,6 +864,7 @@ public class ExcelUtil<T>
try try
{ {
Collection<?> subList = (Collection<?>) getTargetValue(vo, field, excel); Collection<?> subList = (Collection<?>) getTargetValue(vo, field, excel);
List<Field> currentSubFields = subFieldsMap.get(field.getName());
if (subList != null && !subList.isEmpty()) if (subList != null && !subList.isEmpty())
{ {
int subIndex = 0; int subIndex = 0;
@@ -761,15 +877,15 @@ public class ExcelUtil<T>
} }
int subColumn = column; int subColumn = column;
for (Field subField : subFields) for (Field subField : currentSubFields)
{ {
Excel subExcel = subField.getAnnotation(Excel.class); Excel subExcel = subField.getAnnotation(Excel.class);
addCell(subExcel, subRow, (T) subVo, subField, subColumn++); addCell(subExcel, subRow, (T) subVo, subField, subColumn++);
} }
subIndex++; subIndex++;
} }
column += subFields.size();
} }
column += currentSubFields.size();
} }
catch (Exception e) catch (Exception e)
{ {
@@ -861,6 +977,7 @@ public class ExcelUtil<T>
style = wb.createCellStyle(); style = wb.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER); style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setDataFormat(dataFormat.getFormat("######0.00"));
Font totalFont = wb.createFont(); Font totalFont = wb.createFont();
totalFont.setFontName("Arial"); totalFont.setFontName("Arial");
totalFont.setFontHeightInPoints((short) 10); totalFont.setFontHeightInPoints((short) 10);
@@ -1123,6 +1240,7 @@ public class ExcelUtil<T>
/** /**
* 添加单元格 * 添加单元格
*/ */
@SuppressWarnings("deprecation")
public Cell addCell(Excel attr, Row row, T vo, Field field, int column) public Cell addCell(Excel attr, Row row, T vo, Field field, int column)
{ {
Cell cell = null; Cell cell = null;
@@ -1135,7 +1253,7 @@ public class ExcelUtil<T>
{ {
// 创建cell // 创建cell
cell = row.createCell(column); cell = row.createCell(column);
if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge()) if (isSubListValue(vo) && getListCellValue(vo) > 1 && attr.needMerge())
{ {
if (subMergedLastRowNum >= subMergedFirstRowNum) if (subMergedLastRowNum >= subMergedFirstRowNum)
{ {
@@ -1152,7 +1270,7 @@ public class ExcelUtil<T>
String dictType = attr.dictType(); String dictType = attr.dictType();
if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value)) if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value))
{ {
cell.getCellStyle().setDataFormat(this.wb.getCreationHelper().createDataFormat().getFormat(dateFormat)); cell.setCellStyle(createCellStyle(cell.getCellStyle(), dateFormat));
cell.setCellValue(parseDateToStr(dateFormat, value)); cell.setCellValue(parseDateToStr(dateFormat, value));
} }
else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value)) else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value))
@@ -1191,6 +1309,28 @@ public class ExcelUtil<T>
return cell; return cell;
} }
/**
* 使用自定义格式,同时避免样式污染
*
* @param cellStyle 从此样式复制
* @param format 格式匹配的字符串
* @return 格式化后CellStyle对象
*/
private CellStyle createCellStyle(CellStyle cellStyle, String format)
{
String key = cellStyle.getIndex() + "|" + format;
CellStyle cached = cellStyleCache.get(key);
if (cached != null)
{
return cached;
}
CellStyle style = wb.createCellStyle();
style.cloneStyleFrom(cellStyle);
style.setDataFormat(wb.getCreationHelper().createDataFormat().getFormat(format));
cellStyleCache.put(key, style);
return style;
}
/** /**
* 设置 POI XSSFSheet 单元格提示或选择框 * 设置 POI XSSFSheet 单元格提示或选择框
* *
@@ -1242,18 +1382,36 @@ public class ExcelUtil<T>
public void setXSSFValidationWithHidden(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, int firstCol, int endCol) public void setXSSFValidationWithHidden(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, int firstCol, int endCol)
{ {
String hideSheetName = "combo_" + firstCol + "_" + endCol; String hideSheetName = "combo_" + firstCol + "_" + endCol;
Sheet hideSheet = wb.createSheet(hideSheetName); // 用于存储 下拉菜单数据 Sheet hideSheet = null;
for (int i = 0; i < textlist.length; i++) String hideSheetDataName = hideSheetName + "_data";
Name name = wb.getName(hideSheetDataName);
if (name != null)
{ {
hideSheet.createRow(i).createCell(0).setCellValue(textlist[i]); // 名称已存在,尝试从名称的引用中找到sheet名称
String refersToFormula = name.getRefersToFormula();
if (StringUtils.isNotEmpty(refersToFormula) && refersToFormula.contains("!"))
{
String sheetNameFromFormula = refersToFormula.substring(0, refersToFormula.indexOf("!"));
hideSheet = wb.getSheet(sheetNameFromFormula);
}
} }
// 创建名称,可被其他单元格引用
Name name = wb.createName(); if (hideSheet == null)
name.setNameName(hideSheetName + "_data"); {
name.setRefersToFormula(hideSheetName + "!$A$1:$A$" + textlist.length); hideSheet = wb.createSheet(hideSheetName); // 用于存储 下拉菜单数据
for (int i = 0; i < textlist.length; i++)
{
hideSheet.createRow(i).createCell(0).setCellValue(textlist[i]);
}
// 创建名称,可被其他单元格引用
name = wb.createName();
name.setNameName(hideSheetDataName);
name.setRefersToFormula(hideSheetName + "!$A$1:$A$" + textlist.length);
}
DataValidationHelper helper = sheet.getDataValidationHelper(); DataValidationHelper helper = sheet.getDataValidationHelper();
// 加载下拉列表内容 // 加载下拉列表内容
DataValidationConstraint constraint = helper.createFormulaListConstraint(hideSheetName + "_data"); DataValidationConstraint constraint = helper.createFormulaListConstraint(hideSheetDataName);
// 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列 // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列
CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);
// 数据有效性对象 // 数据有效性对象
@@ -1391,7 +1549,7 @@ public class ExcelUtil<T>
{ {
try try
{ {
Object instance = excel.handler().newInstance(); Object instance = excel.handler().getDeclaredConstructor().newInstance();
Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class, Cell.class, Workbook.class }); Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class, Cell.class, Workbook.class });
value = formatMethod.invoke(instance, value, excel.args(), cell, this.wb); value = formatMethod.invoke(instance, value, excel.args(), cell, this.wb);
} }
@@ -1442,7 +1600,7 @@ public class ExcelUtil<T>
{ {
cell = row.createCell(key); cell = row.createCell(key);
cell.setCellStyle(styles.get("total")); cell.setCellStyle(styles.get("total"));
cell.setCellValue(DOUBLE_FORMAT.format(statistics.get(key))); cell.setCellValue(statistics.get(key));
} }
statistics.clear(); statistics.clear();
} }
@@ -1541,6 +1699,8 @@ public class ExcelUtil<T>
{ {
List<Object[]> fields = new ArrayList<Object[]>(); List<Object[]> fields = new ArrayList<Object[]>();
List<Field> tempFields = new ArrayList<>(); List<Field> tempFields = new ArrayList<>();
subFieldsMap = new HashMap<>();
subMethods = new HashMap<>();
tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
if (StringUtils.isNotEmpty(includeFields)) if (StringUtils.isNotEmpty(includeFields))
@@ -1588,10 +1748,11 @@ public class ExcelUtil<T>
} }
if (Collection.class.isAssignableFrom(field.getType())) if (Collection.class.isAssignableFrom(field.getType()))
{ {
subMethod = getSubMethod(field.getName(), clazz); String fieldName = field.getName();
subMethods.put(fieldName, getSubMethod(fieldName, clazz));
ParameterizedType pt = (ParameterizedType) field.getGenericType(); ParameterizedType pt = (ParameterizedType) field.getGenericType();
Class<?> subClass = (Class<?>) pt.getActualTypeArguments()[0]; Class<?> subClass = (Class<?>) pt.getActualTypeArguments()[0];
this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class); subFieldsMap.put(fieldName, FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class));
} }
} }
@@ -1660,7 +1821,8 @@ public class ExcelUtil<T>
{ {
this.sheet = wb.createSheet(); this.sheet = wb.createSheet();
this.createTitle(); this.createTitle();
wb.setSheetName(index, sheetName + index); int actualIndex = wb.getSheetIndex(this.sheet);
wb.setSheetName(actualIndex, sheetName + index);
} }
} }
@@ -1843,7 +2005,7 @@ public class ExcelUtil<T>
*/ */
public boolean isSubList() public boolean isSubList()
{ {
return StringUtils.isNotNull(subFields) && subFields.size() > 0; return !StringUtils.isEmpty(subFieldsMap);
} }
/** /**
@@ -1851,24 +2013,32 @@ public class ExcelUtil<T>
*/ */
public boolean isSubListValue(T vo) public boolean isSubListValue(T vo)
{ {
return StringUtils.isNotNull(subFields) && subFields.size() > 0 && StringUtils.isNotNull(getListCellValue(vo)) && getListCellValue(vo).size() > 0; return !StringUtils.isEmpty(subFieldsMap) && getListCellValue(vo) > 0;
} }
/** /**
* 获取集合的值 * 获取集合的值
*/ */
public Collection<?> getListCellValue(Object obj) public int getListCellValue(Object obj)
{ {
Object value; Collection<?> value;
int max = 0;
try try
{ {
value = subMethod.invoke(obj, new Object[] {}); for (String s : subMethods.keySet())
{
value = (Collection<?>) subMethods.get(s).invoke(obj);
if (value.size() > max)
{
max = value.size();
}
}
} }
catch (Exception e) catch (Exception e)
{ {
return new ArrayList<Object>(); return 0;
} }
return (Collection<?>) value; return max;
} }
/** /**
@@ -7,13 +7,13 @@ import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Date; import java.util.Date;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.apache.poi.ss.usermodel.DateUtil; import org.apache.poi.ss.usermodel.DateUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
/** /**
* 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
@@ -310,6 +310,7 @@ public class ReflectUtils
/** /**
* 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
*/ */
@SuppressWarnings("deprecation")
public static void makeAccessible(Method method) public static void makeAccessible(Method method)
{ {
if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
@@ -322,6 +323,7 @@ public class ReflectUtils
/** /**
* 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
*/ */
@SuppressWarnings("deprecation")
public static void makeAccessible(Field field) public static void makeAccessible(Field field)
{ {
if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers())
@@ -13,7 +13,7 @@ public class SqlUtil
/** /**
* 定义常用的 sql关键字 * 定义常用的 sql关键字
*/ */
public static String SQL_REGEX = "\u000B|and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()"; 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()";
/** /**
* 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)
@@ -58,12 +58,13 @@ public class SqlUtil
{ {
return; return;
} }
String normalizedValue = value.replaceAll("\\p{Z}|\\s", "");
String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|"); String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|");
for (String sqlKeyword : sqlKeywords) for (String sqlKeyword : sqlKeywords)
{ {
if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) if (StringUtils.indexOfIgnoreCase(normalizedValue, sqlKeyword) > -1)
{ {
throw new UtilException("参数存在SQL注入风险"); throw new UtilException("请求参数包含敏感关键词'" + sqlKeyword + "',可能存在安全风险");
} }
} }
} }
@@ -1,7 +1,7 @@
package com.ruoyi.common.xss; package com.ruoyi.common.xss;
import javax.validation.Constraint; import jakarta.validation.Constraint;
import javax.validation.Payload; import jakarta.validation.Payload;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@@ -1,8 +1,8 @@
package com.ruoyi.common.xss; package com.ruoyi.common.xss;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import javax.validation.ConstraintValidator; import jakarta.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext; import jakarta.validation.ConstraintValidatorContext;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
+4 -4
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>3.9.0</version> <version>3.9.2</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -20,19 +20,19 @@
<!-- SpringBoot Web容器 --> <!-- SpringBoot Web容器 -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-webmvc</artifactId>
</dependency> </dependency>
<!-- SpringBoot 拦截器 --> <!-- SpringBoot 拦截器 -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId> <artifactId>spring-boot-starter-aspectj</artifactId>
</dependency> </dependency>
<!-- 阿里数据库连接池 --> <!-- 阿里数据库连接池 -->
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId> <artifactId>druid-spring-boot-4-starter</artifactId>
</dependency> </dependency>
<!-- 验证码 --> <!-- 验证码 -->
@@ -7,6 +7,7 @@ import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.ruoyi.common.annotation.DataScope; import com.ruoyi.common.annotation.DataScope;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysRole;
@@ -26,31 +27,6 @@ import com.ruoyi.framework.security.context.PermissionContextHolder;
@Component @Component
public class DataScopeAspect 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";
/** /**
* 数据权限过滤关键字 * 数据权限过滤关键字
*/ */
@@ -74,7 +50,7 @@ public class DataScopeAspect
if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
{ {
String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext()); 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,13 +64,13 @@ public class DataScopeAspect
* @param userAlias 用户别名 * @param userAlias 用户别名
* @param permission 权限字符 * @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(); StringBuilder sqlString = new StringBuilder();
List<String> conditions = new ArrayList<String>(); List<String> conditions = new ArrayList<String>();
List<String> scopeCustomIds = new ArrayList<String>(); List<String> scopeCustomIds = new ArrayList<String>();
user.getRoles().forEach(role -> { user.getRoles().forEach(role -> {
if (DATA_SCOPE_CUSTOM.equals(role.getDataScope()) && StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL) && StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) 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())); scopeCustomIds.add(Convert.toStr(role.getRoleId()));
} }
@@ -107,46 +83,46 @@ public class DataScopeAspect
{ {
continue; continue;
} }
if (!StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) if (StringUtils.isNotEmpty(permission) && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
{ {
continue; continue;
} }
if (DATA_SCOPE_ALL.equals(dataScope)) if (Constants.Dept.DATA_SCOPE_ALL.equals(dataScope))
{ {
sqlString = new StringBuilder(); sqlString = new StringBuilder();
conditions.add(dataScope); conditions.add(dataScope);
break; break;
} }
else if (DATA_SCOPE_CUSTOM.equals(dataScope)) else if (Constants.Dept.DATA_SCOPE_CUSTOM.equals(dataScope))
{ {
if (scopeCustomIds.size() > 1) if (scopeCustomIds.size() > 1)
{ {
// 多个自定数据权限使用in查询,避免多次拼接。 // 多个自定数据权限使用in查询,避免多次拼接。
sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id in ({}) ) ", deptAlias, String.join(",", scopeCustomIds))); sqlString.append(StringUtils.format(" OR {}.{} IN ( SELECT dept_id FROM sys_role_dept WHERE role_id in ({}) ) ", deptAlias, deptField, String.join(",", scopeCustomIds)));
} }
else else
{ {
sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, role.getRoleId())); 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)) if (StringUtils.isNotBlank(userAlias))
{ {
sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId())); sqlString.append(StringUtils.format(" OR {}.{} = {} ", userAlias, userField, user.getUserId()));
} }
else else
{ {
// 数据权限为仅本人且没有userAlias别名不查询任何数据 // 数据权限为仅本人且没有userAlias别名不查询任何数据
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); sqlString.append(StringUtils.format(" OR {}.{} = 0 ", deptAlias, deptField));
} }
} }
conditions.add(dataScope); conditions.add(dataScope);
@@ -155,7 +131,7 @@ public class DataScopeAspect
// 角色都不包含传递过来的权限字符,这个时候sqlString也会为空,所以要限制一下,不查询任何数据 // 角色都不包含传递过来的权限字符,这个时候sqlString也会为空,所以要限制一下,不查询任何数据
if (StringUtils.isEmpty(conditions)) if (StringUtils.isEmpty(conditions))
{ {
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); sqlString.append(StringUtils.format(" OR {}.{} = 0 ", deptAlias, deptField));
} }
if (StringUtils.isNotBlank(sqlString.toString())) if (StringUtils.isNotBlank(sqlString.toString()))
@@ -2,8 +2,8 @@ package com.ruoyi.framework.aspectj;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterReturning;
@@ -50,6 +50,9 @@ public class LogAspect
/** 计算操作消耗时间 */ /** 计算操作消耗时间 */
private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time"); private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");
/** 参数最大长度限制 */
private static final int PARAM_MAX_LENGTH = 2000;
/** /**
* 处理请求前执行 * 处理请求前执行
*/ */
@@ -172,16 +175,16 @@ public class LogAspect
*/ */
private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception
{ {
Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
String requestMethod = operLog.getRequestMethod(); String requestMethod = operLog.getRequestMethod();
Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
if (StringUtils.isEmpty(paramsMap) && StringUtils.equalsAny(requestMethod, HttpMethod.PUT.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name())) if (StringUtils.isEmpty(paramsMap) && StringUtils.equalsAny(requestMethod, HttpMethod.PUT.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name()))
{ {
String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames); String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
operLog.setOperParam(StringUtils.substring(params, 0, 2000)); operLog.setOperParam(params);
} }
else else
{ {
operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000)); operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, PARAM_MAX_LENGTH));
} }
} }
@@ -190,7 +193,7 @@ public class LogAspect
*/ */
private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames)
{ {
String params = ""; StringBuilder params = new StringBuilder();
if (paramsArray != null && paramsArray.length > 0) if (paramsArray != null && paramsArray.length > 0)
{ {
for (Object o : paramsArray) for (Object o : paramsArray)
@@ -200,15 +203,20 @@ public class LogAspect
try try
{ {
String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames)); String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames));
params += jsonObj.toString() + " "; params.append(jsonObj).append(" ");
if (params.length() >= PARAM_MAX_LENGTH)
{
return StringUtils.substring(params.toString(), 0, PARAM_MAX_LENGTH);
}
} }
catch (Exception e) catch (Exception e)
{ {
log.error("请求参数拼装异常 msg:{}, 参数:{}", e.getMessage(), paramsArray, e);
} }
} }
} }
} }
return params.trim(); return params.toString();
} }
/** /**
@@ -1,9 +1,6 @@
package com.ruoyi.framework.config; package com.ruoyi.framework.config;
import java.util.TimeZone;
import org.mybatis.spring.annotation.MapperScan; 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.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.EnableAspectJAutoProxy;
@@ -19,12 +16,4 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy;
@MapperScan("com.ruoyi.**.mapper") @MapperScan("com.ruoyi.**.mapper")
public class ApplicationConfig 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.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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 javax.sql.DataSource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties; 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.Configuration;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import com.alibaba.druid.spring.boot4.autoconfigure.DruidDataSourceBuilder;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties; import com.alibaba.druid.spring.boot4.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.util.Utils; import com.alibaba.druid.util.Utils;
import com.ruoyi.common.enums.DataSourceType; import com.ruoyi.common.enums.DataSourceType;
import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.config.properties.DruidProperties; import com.ruoyi.framework.config.properties.DruidProperties;
import com.ruoyi.framework.datasource.DynamicDataSource; 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 配置多数据源 * druid 配置多数据源
@@ -96,7 +96,7 @@ public class DruidConfig
Filter filter = new Filter() Filter filter = new Filter()
{ {
@Override @Override
public void init(javax.servlet.FilterConfig filterConfig) throws ServletException public void init(jakarta.servlet.FilterConfig filterConfig) throws ServletException
{ {
} }
@Override @Override
@@ -2,12 +2,14 @@ package com.ruoyi.framework.config;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.servlet.DispatcherType; import jakarta.servlet.DispatcherType;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.filter.RefererFilter;
import com.ruoyi.common.filter.RepeatableFilter; import com.ruoyi.common.filter.RepeatableFilter;
import com.ruoyi.common.filter.XssFilter; import com.ruoyi.common.filter.XssFilter;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
@@ -26,6 +28,9 @@ public class FilterConfig
@Value("${xss.urlPatterns}") @Value("${xss.urlPatterns}")
private String urlPatterns; private String urlPatterns;
@Value("${referer.allowed-domains}")
private String allowedDomains;
@SuppressWarnings({ "rawtypes", "unchecked" }) @SuppressWarnings({ "rawtypes", "unchecked" })
@Bean @Bean
@ConditionalOnProperty(value = "xss.enabled", havingValue = "true") @ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
@@ -43,6 +48,23 @@ public class FilterConfig
return registration; 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" }) @SuppressWarnings({ "rawtypes", "unchecked" })
@Bean @Bean
public FilterRegistrationBean someFilterRegistration() public FilterRegistrationBean someFilterRegistration()
@@ -14,6 +14,7 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
* *
* @author ruoyi * @author ruoyi
*/ */
@SuppressWarnings("deprecation")
@Configuration @Configuration
@EnableCaching @EnableCaching
public class RedisConfig extends CachingConfigurerSupport public class RedisConfig extends CachingConfigurerSupport
@@ -5,12 +5,10 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy; 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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@@ -30,12 +28,6 @@ import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;
@Configuration @Configuration
public class SecurityConfig public class SecurityConfig
{ {
/**
* 自定义用户认证逻辑
*/
@Autowired
private UserDetailsService userDetailsService;
/** /**
* 认证失败处理类 * 认证失败处理类
*/ */
@@ -66,17 +58,14 @@ public class SecurityConfig
@Autowired @Autowired
private PermitAllUrlProperties permitAllUrl; private PermitAllUrlProperties permitAllUrl;
/** /**
* 身份验证实现 * 身份验证实现
*/ */
@Bean @Bean
public AuthenticationManager authenticationManager() public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception
{ {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); return authenticationConfiguration.getAuthenticationManager();
daoAuthenticationProvider.setUserDetailsService(userDetailsService); }
daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
return new ProviderManager(daoAuthenticationProvider);
}
/** /**
* anyRequest | 匹配所有请求路径 * anyRequest | 匹配所有请求路径
@@ -109,12 +98,12 @@ public class SecurityConfig
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 注解标记允许匿名访问的url // 注解标记允许匿名访问的url
.authorizeHttpRequests((requests) -> { .authorizeHttpRequests((requests) -> {
permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll()); permitAllUrl.getUrls().forEach(url -> requests.requestMatchers(url).permitAll());
// 对于登录login 注册register 验证码captchaImage 允许匿名访问 // 对于登录login 注册register 验证码captchaImage 允许匿名访问
requests.antMatchers("/login", "/register", "/captchaImage").permitAll() requests.requestMatchers("/login", "/register", "/captchaImage").permitAll()
// 静态资源,可匿名访问 // 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .requestMatchers(HttpMethod.GET, "/", "/*.html", "/**.html", "/**.css", "/**.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() .requestMatchers("/swagger-ui.html", "/v3/api-docs/**", "/swagger-ui/**", "/druid/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证 // 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated(); .anyRequest().authenticated();
}) })
@@ -1,6 +1,6 @@
package com.ruoyi.framework.config; package com.ruoyi.framework.config;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.ServletUtils;
@@ -49,8 +49,8 @@ public class ThreadPoolConfig
protected ScheduledExecutorService scheduledExecutorService() protected ScheduledExecutorService scheduledExecutorService()
{ {
return new ScheduledThreadPoolExecutor(corePoolSize, return new ScheduledThreadPoolExecutor(corePoolSize,
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(), BasicThreadFactory.builder().namingPattern("schedule-pool-%d").daemon(true).build(),
new ThreadPoolExecutor.CallerRunsPolicy()) new ThreadPoolExecutor.CallerRunsPolicy())
{ {
@Override @Override
protected void afterExecute(Runnable r, Throwable t) protected void afterExecute(Runnable r, Throwable t)
@@ -34,10 +34,11 @@ public class PermitAllUrlProperties implements InitializingBean, ApplicationCont
public String ASTERISK = "*"; public String ASTERISK = "*";
@SuppressWarnings("deprecation")
@Override @Override
public void afterPropertiesSet() public void afterPropertiesSet()
{ {
RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class); RequestMappingHandlerMapping mapping = applicationContext.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods(); Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
map.keySet().forEach(info -> { map.keySet().forEach(info -> {
@@ -45,12 +46,12 @@ public class PermitAllUrlProperties implements InitializingBean, ApplicationCont
// 获取方法上边的注解 替代path variable 为 * // 获取方法上边的注解 替代path variable 为 *
Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class); Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);
Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns()) Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPathPatternsCondition().getPatternValues())
.forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
// 获取类上边的注解, 替代path variable 为 * // 获取类上边的注解, 替代path variable 为 *
Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class); Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);
Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns()) Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPathPatternsCondition().getPatternValues())
.forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
}); });
} }
@@ -1,8 +1,8 @@
package com.ruoyi.framework.interceptor; package com.ruoyi.framework.interceptor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
@@ -3,7 +3,7 @@ package com.ruoyi.framework.interceptor.impl;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -3,7 +3,7 @@ package com.ruoyi.framework.manager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
/** /**
* 确保应用退出时能关闭后台线程 * 确保应用退出时能关闭后台线程
@@ -7,6 +7,7 @@ import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.LogUtils; import com.ruoyi.common.utils.LogUtils;
import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.http.UserAgentUtils;
import com.ruoyi.common.utils.ip.AddressUtils; import com.ruoyi.common.utils.ip.AddressUtils;
import com.ruoyi.common.utils.ip.IpUtils; import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.common.utils.spring.SpringUtils;
@@ -14,7 +15,6 @@ import com.ruoyi.system.domain.SysLogininfor;
import com.ruoyi.system.domain.SysOperLog; import com.ruoyi.system.domain.SysOperLog;
import com.ruoyi.system.service.ISysLogininforService; import com.ruoyi.system.service.ISysLogininforService;
import com.ruoyi.system.service.ISysOperLogService; import com.ruoyi.system.service.ISysOperLogService;
import eu.bitwalker.useragentutils.UserAgent;
/** /**
* 异步工厂(产生任务用) * 异步工厂(产生任务用)
@@ -37,7 +37,7 @@ public class AsyncFactory
public static TimerTask recordLogininfor(final String username, final String status, final String message, public static TimerTask recordLogininfor(final String username, final String status, final String message,
final Object... args) final Object... args)
{ {
final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); final String userAgent = ServletUtils.getRequest().getHeader("User-Agent");
final String ip = IpUtils.getIpAddr(); final String ip = IpUtils.getIpAddr();
return new TimerTask() return new TimerTask()
{ {
@@ -54,9 +54,9 @@ public class AsyncFactory
// 打印信息到日志 // 打印信息到日志
sys_user_logger.info(s.toString(), args); sys_user_logger.info(s.toString(), args);
// 获取客户端操作系统 // 获取客户端操作系统
String os = userAgent.getOperatingSystem().getName(); String os = UserAgentUtils.getOperatingSystem(userAgent);
// 获取客户端浏览器 // 获取客户端浏览器
String browser = userAgent.getBrowser().getName(); String browser = UserAgentUtils.getBrowser(userAgent);
// 封装对象 // 封装对象
SysLogininfor logininfor = new SysLogininfor(); SysLogininfor logininfor = new SysLogininfor();
logininfor.setUserName(username); logininfor.setUserName(username);
@@ -1,10 +1,10 @@
package com.ruoyi.framework.security.filter; package com.ruoyi.framework.security.filter;
import java.io.IOException; import java.io.IOException;
import javax.servlet.FilterChain; import jakarta.servlet.FilterChain;
import javax.servlet.ServletException; import jakarta.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
@@ -2,8 +2,8 @@ package com.ruoyi.framework.security.handle;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -1,9 +1,9 @@
package com.ruoyi.framework.security.handle; package com.ruoyi.framework.security.handle;
import java.io.IOException; import java.io.IOException;
import javax.servlet.ServletException; import jakarta.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
@@ -1,6 +1,6 @@
package com.ruoyi.framework.web.exception; package com.ruoyi.framework.web.exception;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AccessDeniedException;
@@ -53,7 +53,7 @@ public class PermissionService
/** /**
* 验证用户是否具有以下任意一个权限 * 验证用户是否具有以下任意一个权限
* *
* @param permissions 以 PERMISSION_DELIMETER 为分隔符的权限列表 * @param permissions 以 PERMISSION_DELIMITER 为分隔符的权限列表
* @return 用户是否具有以下任意一个权限 * @return 用户是否具有以下任意一个权限
*/ */
public boolean hasAnyPermi(String permissions) public boolean hasAnyPermi(String permissions)
@@ -69,7 +69,7 @@ public class PermissionService
} }
PermissionContextHolder.setContext(permissions); PermissionContextHolder.setContext(permissions);
Set<String> authorities = loginUser.getPermissions(); Set<String> authorities = loginUser.getPermissions();
for (String permission : permissions.split(Constants.PERMISSION_DELIMETER)) for (String permission : permissions.split(Constants.PERMISSION_DELIMITER))
{ {
if (permission != null && hasPermissions(authorities, permission)) if (permission != null && hasPermissions(authorities, permission))
{ {
@@ -121,7 +121,7 @@ public class PermissionService
/** /**
* 验证用户是否具有以下任意一个角色 * 验证用户是否具有以下任意一个角色
* *
* @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表 * @param roles 以 ROLE_DELIMITER 为分隔符的角色列表
* @return 用户是否具有以下任意一个角色 * @return 用户是否具有以下任意一个角色
*/ */
public boolean hasAnyRoles(String roles) public boolean hasAnyRoles(String roles)
@@ -135,7 +135,7 @@ public class PermissionService
{ {
return false; return false;
} }
for (String role : roles.split(Constants.ROLE_DELIMETER)) for (String role : roles.split(Constants.ROLE_DELIMITER))
{ {
if (hasRole(role)) if (hasRole(role))
{ {
@@ -1,6 +1,6 @@
package com.ruoyi.framework.web.service; package com.ruoyi.framework.web.service;
import javax.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
@@ -10,7 +10,6 @@ import org.springframework.stereotype.Component;
import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
@@ -172,10 +171,6 @@ public class SysLoginService
*/ */
public void recordLoginInfo(Long userId) public void recordLoginInfo(Long userId)
{ {
SysUser sysUser = new SysUser(); userService.updateLoginInfo(userId, IpUtils.getIpAddr(), DateUtils.getNowDate());
sysUser.setUserId(userId);
sysUser.setLoginIp(IpUtils.getIpAddr());
sysUser.setLoginDate(DateUtils.getNowDate());
userService.updateUserProfile(sysUser);
} }
} }
@@ -6,6 +6,7 @@ import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
@@ -39,7 +40,7 @@ public class SysPermissionService
// 管理员拥有所有权限 // 管理员拥有所有权限
if (user.isAdmin()) if (user.isAdmin())
{ {
roles.add("admin"); roles.add(Constants.SUPER_ADMIN);
} }
else else
{ {
@@ -60,7 +61,7 @@ public class SysPermissionService
// 管理员拥有所有权限 // 管理员拥有所有权限
if (user.isAdmin()) if (user.isAdmin())
{ {
perms.add("*:*:*"); perms.add(Constants.ALL_PERMISSION);
} }
else else
{ {
@@ -1,9 +1,9 @@
package com.ruoyi.framework.web.service; package com.ruoyi.framework.web.service;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -15,13 +15,14 @@ import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.http.UserAgentUtils;
import com.ruoyi.common.utils.ip.AddressUtils; import com.ruoyi.common.utils.ip.AddressUtils;
import com.ruoyi.common.utils.ip.IpUtils; import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.common.utils.uuid.IdUtils; import com.ruoyi.common.utils.uuid.IdUtils;
import eu.bitwalker.useragentutils.UserAgent;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureAlgorithm;
import jakarta.servlet.http.HttpServletRequest;
/** /**
* token验证处理 * token验证处理
@@ -161,12 +162,12 @@ public class TokenService
*/ */
public void setUserAgent(LoginUser loginUser) public void setUserAgent(LoginUser loginUser)
{ {
UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); String userAgent = ServletUtils.getRequest().getHeader("User-Agent");
String ip = IpUtils.getIpAddr(); String ip = IpUtils.getIpAddr();
loginUser.setIpaddr(ip); loginUser.setIpaddr(ip);
loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip)); loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
loginUser.setBrowser(userAgent.getBrowser().getName()); loginUser.setBrowser(UserAgentUtils.getBrowser(userAgent));
loginUser.setOs(userAgent.getOperatingSystem().getName()); loginUser.setOs(UserAgentUtils.getOperatingSystem(userAgent));
} }
/** /**
@@ -229,4 +230,41 @@ public class TokenService
{ {
return CacheConstants.LOGIN_TOKEN_KEY + uuid; return CacheConstants.LOGIN_TOKEN_KEY + uuid;
} }
/**
* 角色权限变更后,刷新所有持有该角色的在线用户权限
*
* @param roleId 变更的角色ID
* @param permissionService 权限服务
*/
public void refreshPermissionByRoleId(Long roleId, SysPermissionService permissionService)
{
// 扫描所有在线 token
String pattern = CacheConstants.LOGIN_TOKEN_KEY + "*";
Collection<String> keys = redisCache.keys(pattern);
if (keys == null || keys.isEmpty())
{
return;
}
for (String key : keys)
{
LoginUser loginUser = redisCache.getCacheObject(key);
if (loginUser == null || loginUser.getUser() == null || loginUser.getUser().isAdmin())
{
// 管理员拥有所有权限,跳过
continue;
}
// 判断该用户是否拥有此角色
boolean hasRole = loginUser.getUser().getRoles() != null
&& loginUser.getUser().getRoles().stream().anyMatch(r -> roleId.equals(r.getRoleId()));
if (!hasRole)
{
continue;
}
// 刷新权限缓存
loginUser.setPermissions(permissionService.getMenuPermission(loginUser.getUser()));
refreshToken(loginUser);
log.info("角色[{}]权限变更,已刷新在线用户[{}]的权限缓存", roleId, loginUser.getUsername());
}
}
} }
+2 -2
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>3.9.0</version> <version>3.9.2</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -32,7 +32,7 @@
<!-- 阿里数据库连接池 --> <!-- 阿里数据库连接池 -->
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId> <artifactId>druid-spring-boot-4-starter</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -5,7 +5,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
@@ -17,6 +17,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.alibaba.druid.DbType; import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.SQLUtils;
@@ -112,12 +113,12 @@ public class GenController extends BaseController
@PreAuthorize("@ss.hasPermi('tool:gen:import')") @PreAuthorize("@ss.hasPermi('tool:gen:import')")
@Log(title = "代码生成", businessType = BusinessType.IMPORT) @Log(title = "代码生成", businessType = BusinessType.IMPORT)
@PostMapping("/importTable") @PostMapping("/importTable")
public AjaxResult importTableSave(String tables) public AjaxResult importTableSave(@RequestParam("tables") String tables, @RequestParam("tplWebType") String tplWebType)
{ {
String[] tableNames = Convert.toStrArray(tables); String[] tableNames = Convert.toStrArray(tables);
// 查询表信息 // 查询表信息
List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames); List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames);
genTableService.importGenTable(tableList, SecurityUtils.getUsername()); genTableService.importGenTable(tableList, tplWebType, SecurityUtils.getUsername());
return success(); return success();
} }
@@ -127,7 +128,7 @@ public class GenController extends BaseController
@PreAuthorize("@ss.hasRole('admin')") @PreAuthorize("@ss.hasRole('admin')")
@Log(title = "创建表", businessType = BusinessType.OTHER) @Log(title = "创建表", businessType = BusinessType.OTHER)
@PostMapping("/createTable") @PostMapping("/createTable")
public AjaxResult createTableSave(String sql) public AjaxResult createTableSave(@RequestParam("sql") String sql, @RequestParam("tplWebType") String tplWebType)
{ {
try try
{ {
@@ -148,7 +149,7 @@ public class GenController extends BaseController
} }
List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames.toArray(new String[tableNames.size()])); List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames.toArray(new String[tableNames.size()]));
String operName = SecurityUtils.getUsername(); String operName = SecurityUtils.getUsername();
genTableService.importGenTable(tableList, operName); genTableService.importGenTable(tableList, tplWebType, operName);
return AjaxResult.success(); return AjaxResult.success();
} }
catch (Exception e) catch (Exception e)
@@ -1,8 +1,8 @@
package com.ruoyi.generator.domain; package com.ruoyi.generator.domain;
import java.util.List; import java.util.List;
import javax.validation.Valid; import jakarta.validation.Valid;
import javax.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import com.ruoyi.common.constant.GenConstants; import com.ruoyi.common.constant.GenConstants;
import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.core.domain.BaseEntity;
@@ -41,7 +41,7 @@ public class GenTable extends BaseEntity
/** 使用的模板(crud单表操作 tree树表操作 sub主子表操作) */ /** 使用的模板(crud单表操作 tree树表操作 sub主子表操作) */
private String tplCategory; private String tplCategory;
/** 前端类型(element-ui模版 element-plus模版) */ /** 前端类型(element-ui模版 element-plus模版 element-plus-typescript模版 */
private String tplWebType; private String tplWebType;
/** 生成包路径 */ /** 生成包路径 */
@@ -64,6 +64,9 @@ public class GenTable extends BaseEntity
@NotBlank(message = "作者不能为空") @NotBlank(message = "作者不能为空")
private String functionAuthor; private String functionAuthor;
/** 表单布局(单列 双列 三列) */
private Integer formColNum;
/** 生成代码方式(0zip压缩包 1自定义路径) */ /** 生成代码方式(0zip压缩包 1自定义路径) */
private String genType; private String genType;
@@ -98,6 +101,9 @@ public class GenTable extends BaseEntity
/** 上级菜单名称字段 */ /** 上级菜单名称字段 */
private String parentMenuName; private String parentMenuName;
/** 是否生成详情页 */
private boolean isView;
public Long getTableId() public Long getTableId()
{ {
return tableId; return tableId;
@@ -228,6 +234,16 @@ public class GenTable extends BaseEntity
this.functionAuthor = functionAuthor; this.functionAuthor = functionAuthor;
} }
public Integer getFormColNum()
{
return formColNum;
}
public void setFormColNum(Integer formColNum)
{
this.formColNum = formColNum;
}
public String getGenType() public String getGenType()
{ {
return genType; return genType;
@@ -338,6 +354,16 @@ public class GenTable extends BaseEntity
this.parentMenuName = parentMenuName; this.parentMenuName = parentMenuName;
} }
public boolean isView()
{
return isView;
}
public void setView(boolean isView)
{
this.isView = isView;
}
public boolean isSub() public boolean isSub()
{ {
return isSub(this.tplCategory); return isSub(this.tplCategory);
@@ -1,6 +1,6 @@
package com.ruoyi.generator.domain; package com.ruoyi.generator.domain;
import javax.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
@@ -4,6 +4,8 @@ import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -168,13 +170,14 @@ public class GenTableServiceImpl implements IGenTableService
*/ */
@Override @Override
@Transactional @Transactional
public void importGenTable(List<GenTable> tableList, String operName) public void importGenTable(List<GenTable> tableList, String tplWebType, String operName)
{ {
try try
{ {
for (GenTable table : tableList) for (GenTable table : tableList)
{ {
String tableName = table.getTableName(); String tableName = table.getTableName();
table.setTplWebType(tplWebType);
GenUtils.initTable(table, operName); GenUtils.initTable(table, operName);
int row = genTableMapper.insertGenTable(table); int row = genTableMapper.insertGenTable(table);
if (row > 0) if (row > 0)
@@ -216,7 +219,7 @@ public class GenTableServiceImpl implements IGenTableService
VelocityContext context = VelocityUtils.prepareContext(table); VelocityContext context = VelocityUtils.prepareContext(table);
// 获取模板列表 // 获取模板列表
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); List<String> templates = VelocityUtils.getTemplateList(table);
for (String template : templates) for (String template : templates)
{ {
// 渲染模板 // 渲染模板
@@ -237,11 +240,7 @@ public class GenTableServiceImpl implements IGenTableService
@Override @Override
public byte[] downloadCode(String tableName) public byte[] downloadCode(String tableName)
{ {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); return downloadCode(new String[] { tableName });
ZipOutputStream zip = new ZipOutputStream(outputStream);
generatorCode(tableName, zip);
IOUtils.closeQuietly(zip);
return outputStream.toByteArray();
} }
/** /**
@@ -264,10 +263,10 @@ public class GenTableServiceImpl implements IGenTableService
VelocityContext context = VelocityUtils.prepareContext(table); VelocityContext context = VelocityUtils.prepareContext(table);
// 获取模板列表 // 获取模板列表
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); List<String> templates = VelocityUtils.getTemplateList(table);
for (String template : templates) for (String template : templates)
{ {
if (!StringUtils.containsAny(template, "sql.vm", "api.js.vm", "index.vue.vm", "index-tree.vue.vm")) if (!StringUtils.containsAny(template, "sql.vm", "api.js.vm", "api.ts.vm", "type.ts.vm", "index.ts.vm", "index.vue.vm", "index-tree.vue.vm", "view.vue.vm"))
{ {
// 渲染模板 // 渲染模板
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
@@ -352,9 +351,14 @@ public class GenTableServiceImpl implements IGenTableService
{ {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(outputStream); ZipOutputStream zip = new ZipOutputStream(outputStream);
Map<String, StringBuffer> typeFiles = new HashMap<>();
for (String tableName : tableNames) for (String tableName : tableNames)
{ {
generatorCode(tableName, zip); generatorCode(tableName, zip, typeFiles);
}
for (Map.Entry<String, StringBuffer> entry : typeFiles.entrySet())
{
writeToZip(zip, entry.getKey(), entry.getValue().toString());
} }
IOUtils.closeQuietly(zip); IOUtils.closeQuietly(zip);
return outputStream.toByteArray(); return outputStream.toByteArray();
@@ -363,7 +367,7 @@ public class GenTableServiceImpl implements IGenTableService
/** /**
* 查询表信息并生成代码 * 查询表信息并生成代码
*/ */
private void generatorCode(String tableName, ZipOutputStream zip) private void generatorCode(String tableName, ZipOutputStream zip, Map<String, StringBuffer> typeFiles)
{ {
// 查询表信息 // 查询表信息
GenTable table = genTableMapper.selectGenTableByName(tableName); GenTable table = genTableMapper.selectGenTableByName(tableName);
@@ -377,29 +381,56 @@ public class GenTableServiceImpl implements IGenTableService
VelocityContext context = VelocityUtils.prepareContext(table); VelocityContext context = VelocityUtils.prepareContext(table);
// 获取模板列表 // 获取模板列表
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); List<String> templates = VelocityUtils.getTemplateList(table);
for (String template : templates) for (String template : templates)
{ {
// 渲染模板 // 渲染模板
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
Template tpl = Velocity.getTemplate(template, Constants.UTF8); Template tpl = Velocity.getTemplate(template, Constants.UTF8);
tpl.merge(context, sw); tpl.merge(context, sw);
try String fileName = VelocityUtils.getFileName(template, table);
// index-bak.ts 模版,追加内容
if (fileName.contains("index-bak.ts"))
{ {
// 添加到zip if (!typeFiles.containsKey(fileName))
zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table))); {
IOUtils.write(sw.toString(), zip, Constants.UTF8); typeFiles.put(fileName, new StringBuffer(sw.toString()));
IOUtils.closeQuietly(sw); }
zip.flush(); else
zip.closeEntry(); {
Arrays.stream(sw.toString().split("\n")).filter(line -> line.startsWith("export * from")).forEach(line -> typeFiles.get(fileName).append("\n").append(line));
}
} }
catch (IOException e) else
{ {
log.error("渲染模板失败,表名:" + table.getTableName(), e); // 其他文件正常添加
writeToZip(zip, fileName, sw.toString());
} }
} }
} }
/**
* 将字符串内容写入ZIP输出流
*
* @param zip ZIP输出流
* @param fileName ZIP条目名称(即文件名)
* @param content 要写入的内容
*/
private void writeToZip(ZipOutputStream zip, String fileName, String content)
{
try
{
zip.putNextEntry(new ZipEntry(fileName));
IOUtils.write(content, zip, Constants.UTF8);
zip.flush();
zip.closeEntry();
}
catch (IOException e)
{
log.error("写入ZIP文件失败,文件名: " + fileName, e);
}
}
/** /**
* 修改保存参数校验 * 修改保存参数校验
* *
@@ -503,12 +534,14 @@ public class GenTableServiceImpl implements IGenTableService
String treeName = paramsObj.getString(GenConstants.TREE_NAME); String treeName = paramsObj.getString(GenConstants.TREE_NAME);
Long parentMenuId = paramsObj.getLongValue(GenConstants.PARENT_MENU_ID); Long parentMenuId = paramsObj.getLongValue(GenConstants.PARENT_MENU_ID);
String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME); String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME);
boolean isView = paramsObj.getBooleanValue(GenConstants.GEN_VIEW);
genTable.setTreeCode(treeCode); genTable.setTreeCode(treeCode);
genTable.setTreeParentCode(treeParentCode); genTable.setTreeParentCode(treeParentCode);
genTable.setTreeName(treeName); genTable.setTreeName(treeName);
genTable.setParentMenuId(parentMenuId); genTable.setParentMenuId(parentMenuId);
genTable.setParentMenuName(parentMenuName); genTable.setParentMenuName(parentMenuName);
genTable.setView(isView);
} }
} }
@@ -78,9 +78,10 @@ public interface IGenTableService
* 导入表结构 * 导入表结构
* *
* @param tableList 导入表列表 * @param tableList 导入表列表
* @param tplWebType 前端类型
* @param operName 操作人员 * @param operName 操作人员
*/ */
public void importGenTable(List<GenTable> tableList, String operName); public void importGenTable(List<GenTable> tableList, String tplWebType, String operName);
/** /**
* 预览代码 * 预览代码
@@ -29,6 +29,12 @@ public class VelocityUtils
/** 默认上级菜单,系统工具 */ /** 默认上级菜单,系统工具 */
private static final String DEFAULT_PARENT_MENU_ID = "3"; private static final String DEFAULT_PARENT_MENU_ID = "3";
/** Vue3 Element Plus 模版 */
private static final String ELEMENT_PLUS = "element-plus";
/** Vue3 Element Plus TypeScript 模版 */
private static final String ELEMENT_PLUS_TYPESSRIPT = "element-plus-typescript";
/** /**
* 设置模板变量信息 * 设置模板变量信息
* *
@@ -54,6 +60,7 @@ public class VelocityUtils
velocityContext.put("basePackage", getPackagePrefix(packageName)); velocityContext.put("basePackage", getPackagePrefix(packageName));
velocityContext.put("packageName", packageName); velocityContext.put("packageName", packageName);
velocityContext.put("author", genTable.getFunctionAuthor()); velocityContext.put("author", genTable.getFunctionAuthor());
velocityContext.put("colSpan", getColSpan(genTable.getFormColNum()));
velocityContext.put("datetime", DateUtils.getDate()); velocityContext.put("datetime", DateUtils.getDate());
velocityContext.put("pkColumn", genTable.getPkColumn()); velocityContext.put("pkColumn", genTable.getPkColumn());
velocityContext.put("importList", getImportList(genTable)); velocityContext.put("importList", getImportList(genTable));
@@ -61,6 +68,7 @@ public class VelocityUtils
velocityContext.put("columns", genTable.getColumns()); velocityContext.put("columns", genTable.getColumns());
velocityContext.put("table", genTable); velocityContext.put("table", genTable);
velocityContext.put("dicts", getDicts(genTable)); velocityContext.put("dicts", getDicts(genTable));
setExtensionsContext(velocityContext, genTable.getOptions());
setMenuVelocityContext(velocityContext, genTable); setMenuVelocityContext(velocityContext, genTable);
if (GenConstants.TPL_TREE.equals(tplCategory)) if (GenConstants.TPL_TREE.equals(tplCategory))
{ {
@@ -73,6 +81,13 @@ public class VelocityUtils
return velocityContext; return velocityContext;
} }
public static void setExtensionsContext(VelocityContext context, String options)
{
JSONObject paramsObj = JSONObject.parseObject(options);
boolean genView = genView(paramsObj);
context.put("genView", genView);
}
public static void setMenuVelocityContext(VelocityContext context, GenTable genTable) public static void setMenuVelocityContext(VelocityContext context, GenTable genTable)
{ {
String options = genTable.getOptions(); String options = genTable.getOptions();
@@ -127,13 +142,23 @@ public class VelocityUtils
* @param tplWebType 前端类型 * @param tplWebType 前端类型
* @return 模板列表 * @return 模板列表
*/ */
public static List<String> getTemplateList(String tplCategory, String tplWebType) public static List<String> getTemplateList(GenTable table)
{ {
String tplWebType = table.getTplWebType();
String tplCategory = table.getTplCategory();
JSONObject paramsObj = JSONObject.parseObject(table.getOptions());
boolean isView = genView(paramsObj);
String useWebType = "vm/vue"; String useWebType = "vm/vue";
if ("element-plus".equals(tplWebType)) String apiTemplate = "vm/js/api.js.vm";
if (StringUtils.equals(ELEMENT_PLUS, tplWebType))
{ {
useWebType = "vm/vue/v3"; useWebType = "vm/vue/v3";
} }
else if (StringUtils.equals(ELEMENT_PLUS_TYPESSRIPT, tplWebType))
{
useWebType = "vm/vue/v3ts";
apiTemplate = "vm/ts/api.ts.vm";
}
List<String> templates = new ArrayList<String>(); List<String> templates = new ArrayList<String>();
templates.add("vm/java/domain.java.vm"); templates.add("vm/java/domain.java.vm");
templates.add("vm/java/mapper.java.vm"); templates.add("vm/java/mapper.java.vm");
@@ -142,7 +167,12 @@ public class VelocityUtils
templates.add("vm/java/controller.java.vm"); templates.add("vm/java/controller.java.vm");
templates.add("vm/xml/mapper.xml.vm"); templates.add("vm/xml/mapper.xml.vm");
templates.add("vm/sql/sql.vm"); templates.add("vm/sql/sql.vm");
templates.add("vm/js/api.js.vm"); templates.add(apiTemplate);
if (StringUtils.equals(ELEMENT_PLUS_TYPESSRIPT, tplWebType))
{
templates.add("vm/ts/type.ts.vm");
templates.add("vm/ts/index.ts.vm");
}
if (GenConstants.TPL_CRUD.equals(tplCategory)) if (GenConstants.TPL_CRUD.equals(tplCategory))
{ {
templates.add(useWebType + "/index.vue.vm"); templates.add(useWebType + "/index.vue.vm");
@@ -156,6 +186,10 @@ public class VelocityUtils
templates.add(useWebType + "/index.vue.vm"); templates.add(useWebType + "/index.vue.vm");
templates.add("vm/java/sub-domain.java.vm"); templates.add("vm/java/sub-domain.java.vm");
} }
if (isView)
{
templates.add(useWebType + "/view.vue.vm");
}
return templates; return templates;
} }
@@ -215,6 +249,18 @@ public class VelocityUtils
{ {
fileName = StringUtils.format("{}/api/{}/{}.js", vuePath, moduleName, businessName); fileName = StringUtils.format("{}/api/{}/{}.js", vuePath, moduleName, businessName);
} }
else if (template.contains("api.ts.vm"))
{
fileName = StringUtils.format("{}/api/{}/{}.ts", vuePath, moduleName, businessName);
}
else if (template.contains("type.ts.vm"))
{
fileName = StringUtils.format("{}/types/api/{}/{}.ts", vuePath, moduleName, businessName);
}
else if (template.contains("index.ts.vm"))
{
fileName = StringUtils.format("{}/types/api/index-bak.ts", vuePath);
}
else if (template.contains("index.vue.vm")) else if (template.contains("index.vue.vm"))
{ {
fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
@@ -223,6 +269,10 @@ public class VelocityUtils
{ {
fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
} }
else if (template.contains("view.vue.vm"))
{
fileName = StringUtils.format("{}/views/{}/{}/view.vue", vuePath, moduleName, businessName);
}
return fileName; return fileName;
} }
@@ -364,6 +414,21 @@ public class VelocityUtils
return StringUtils.EMPTY; return StringUtils.EMPTY;
} }
/**
* 扩展功能/生成详情页
*
* @param paramsObj 生成其他选项
* @return 是否生成详细页
*/
public static boolean genView(JSONObject paramsObj)
{
if (StringUtils.isNotNull(paramsObj) && paramsObj.containsKey(GenConstants.GEN_VIEW))
{
return paramsObj.getBoolean(GenConstants.GEN_VIEW);
}
return false;
}
/** /**
* 获取树名称 * 获取树名称
* *
@@ -405,4 +470,23 @@ public class VelocityUtils
} }
return num; return num;
} }
/**
* 获取表单 el-col span
*
* @param formColNum 表单布局方式(1单列 2双列 3三列)
* @return span 数值字符串
*/
public static String getColSpan(int formColNum)
{
if (formColNum == 2)
{
return "12";
}
else if (formColNum == 3)
{
return "8";
}
return "24";
}
} }
@@ -94,6 +94,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<set> <set>
<if test="columnComment != null">column_comment = #{columnComment},</if> <if test="columnComment != null">column_comment = #{columnComment},</if>
<if test="javaType != null">java_type = #{javaType},</if> <if test="javaType != null">java_type = #{javaType},</if>
<if test="columnType != null">column_type = #{columnType},</if>
<if test="javaField != null">java_field = #{javaField},</if> <if test="javaField != null">java_field = #{javaField},</if>
<if test="isInsert != null">is_insert = #{isInsert},</if> <if test="isInsert != null">is_insert = #{isInsert},</if>
<if test="isEdit != null">is_edit = #{isEdit},</if> <if test="isEdit != null">is_edit = #{isEdit},</if>
@@ -18,6 +18,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="businessName" column="business_name" /> <result property="businessName" column="business_name" />
<result property="functionName" column="function_name" /> <result property="functionName" column="function_name" />
<result property="functionAuthor" column="function_author" /> <result property="functionAuthor" column="function_author" />
<result property="formColNum" column="form_col_num" />
<result property="genType" column="gen_type" /> <result property="genType" column="gen_type" />
<result property="genPath" column="gen_path" /> <result property="genPath" column="gen_path" />
<result property="options" column="options" /> <result property="options" column="options" />
@@ -55,7 +56,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap> </resultMap>
<sql id="selectGenTableVo"> <sql id="selectGenTableVo">
select table_id, table_name, table_comment, sub_table_name, sub_table_fk_name, class_name, tpl_category, tpl_web_type, package_name, module_name, business_name, function_name, function_author, gen_type, gen_path, options, create_by, create_time, update_by, update_time, remark from gen_table select table_id, table_name, table_comment, sub_table_name, sub_table_fk_name, class_name, tpl_category, tpl_web_type, package_name, module_name, business_name, function_name, function_author, form_col_num, gen_type, gen_path, options, create_by, create_time, update_by, update_time, remark from gen_table
</sql> </sql>
<select id="selectGenTableList" parameterType="GenTable" resultMap="GenTableResult"> <select id="selectGenTableList" parameterType="GenTable" resultMap="GenTableResult">
@@ -112,7 +113,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select> </select>
<select id="selectGenTableById" parameterType="Long" resultMap="GenTableResult"> <select id="selectGenTableById" parameterType="Long" resultMap="GenTableResult">
SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.tpl_web_type, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark, SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.tpl_web_type, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.form_col_num, t.gen_type, t.gen_path, t.options, t.remark,
c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
FROM gen_table t FROM gen_table t
LEFT JOIN gen_table_column c ON t.table_id = c.table_id LEFT JOIN gen_table_column c ON t.table_id = c.table_id
@@ -120,7 +121,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select> </select>
<select id="selectGenTableByName" parameterType="String" resultMap="GenTableResult"> <select id="selectGenTableByName" parameterType="String" resultMap="GenTableResult">
SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.tpl_web_type, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark, SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.tpl_web_type, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.form_col_num, t.gen_type, t.gen_path, t.options, t.remark,
c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
FROM gen_table t FROM gen_table t
LEFT JOIN gen_table_column c ON t.table_id = c.table_id LEFT JOIN gen_table_column c ON t.table_id = c.table_id
@@ -128,7 +129,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select> </select>
<select id="selectGenTableAll" parameterType="String" resultMap="GenTableResult"> <select id="selectGenTableAll" parameterType="String" resultMap="GenTableResult">
SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.tpl_web_type, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.options, t.remark, SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.tpl_web_type, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.form_col_num, t.options, t.remark,
c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
FROM gen_table t FROM gen_table t
LEFT JOIN gen_table_column c ON t.table_id = c.table_id LEFT JOIN gen_table_column c ON t.table_id = c.table_id
@@ -147,6 +148,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="businessName != null and businessName != ''">business_name,</if> <if test="businessName != null and businessName != ''">business_name,</if>
<if test="functionName != null and functionName != ''">function_name,</if> <if test="functionName != null and functionName != ''">function_name,</if>
<if test="functionAuthor != null and functionAuthor != ''">function_author,</if> <if test="functionAuthor != null and functionAuthor != ''">function_author,</if>
<if test="formColNum != null">form_col_num,</if>
<if test="genType != null and genType != ''">gen_type,</if> <if test="genType != null and genType != ''">gen_type,</if>
<if test="genPath != null and genPath != ''">gen_path,</if> <if test="genPath != null and genPath != ''">gen_path,</if>
<if test="remark != null and remark != ''">remark,</if> <if test="remark != null and remark != ''">remark,</if>
@@ -163,6 +165,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="businessName != null and businessName != ''">#{businessName},</if> <if test="businessName != null and businessName != ''">#{businessName},</if>
<if test="functionName != null and functionName != ''">#{functionName},</if> <if test="functionName != null and functionName != ''">#{functionName},</if>
<if test="functionAuthor != null and functionAuthor != ''">#{functionAuthor},</if> <if test="functionAuthor != null and functionAuthor != ''">#{functionAuthor},</if>
<if test="formColNum != null">#{formColNum},</if>
<if test="genType != null and genType != ''">#{genType},</if> <if test="genType != null and genType != ''">#{genType},</if>
<if test="genPath != null and genPath != ''">#{genPath},</if> <if test="genPath != null and genPath != ''">#{genPath},</if>
<if test="remark != null and remark != ''">#{remark},</if> <if test="remark != null and remark != ''">#{remark},</if>
@@ -184,6 +187,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="subTableFkName != null">sub_table_fk_name = #{subTableFkName},</if> <if test="subTableFkName != null">sub_table_fk_name = #{subTableFkName},</if>
<if test="className != null and className != ''">class_name = #{className},</if> <if test="className != null and className != ''">class_name = #{className},</if>
<if test="functionAuthor != null and functionAuthor != ''">function_author = #{functionAuthor},</if> <if test="functionAuthor != null and functionAuthor != ''">function_author = #{functionAuthor},</if>
<if test="formColNum != null">form_col_num = #{formColNum},</if>
<if test="genType != null and genType != ''">gen_type = #{genType},</if> <if test="genType != null and genType != ''">gen_type = #{genType},</if>
<if test="genPath != null and genPath != ''">gen_path = #{genPath},</if> <if test="genPath != null and genPath != ''">gen_path = #{genPath},</if>
<if test="tplCategory != null and tplCategory != ''">tpl_category = #{tplCategory},</if> <if test="tplCategory != null and tplCategory != ''">tpl_category = #{tplCategory},</if>
@@ -1,7 +1,7 @@
package ${packageName}.controller; package ${packageName}.controller;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@@ -0,0 +1,51 @@
import request from '@/utils/request'
import type { AjaxResult, TableDataInfo, ${BusinessName}QueryParams, ${ClassName} } from '@/types'
// 查询${functionName}列表
#if($table.tree)
export function list${BusinessName}(query?: ${BusinessName}QueryParams): Promise<AjaxResult<${ClassName}[]>> {
#else
export function list${BusinessName}(query: ${BusinessName}QueryParams): Promise<TableDataInfo<${ClassName}[]>> {
#end
return request({
url: '/${moduleName}/${businessName}/list',
method: 'get',
params: query
})
}
// 查询${functionName}详细
export function get${BusinessName}(${pkColumn.javaField}: number): Promise<AjaxResult<${ClassName}>> {
return request({
url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
method: 'get'
})
}
// 新增${functionName}
export function add${BusinessName}(data: ${ClassName}): Promise<AjaxResult> {
return request({
url: '/${moduleName}/${businessName}',
method: 'post',
data: data
})
}
// 修改${functionName}
export function update${BusinessName}(data: ${ClassName}): Promise<AjaxResult> {
return request({
url: '/${moduleName}/${businessName}',
method: 'put',
data: data
})
}
// 删除${functionName}
export function del${BusinessName}(${pkColumn.javaField}: number | number[]): Promise<AjaxResult> {
return request({
url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
method: 'delete'
})
}
@@ -0,0 +1,9 @@
/**
* API 类型统一导出
*/
....
// 防止覆盖,需手动追加下面代码到index.ts文件中,追加好后此文件可删除
// ${moduleName} 模块
export * from "./${moduleName}/${businessName}";
@@ -0,0 +1,51 @@
import type { PageDomain, BaseEntity } from "../common";
/** ${functionName}配置分页查询参数 */
export interface ${BusinessName}QueryParams extends PageDomain {
#foreach($column in $columns)
#if($column.query)
#set($type = "string")
#if($column.javaType == "Long" || $column.javaType == "Integer")
#set($type = "number")
#elseif($column.javaType == "Boolean")
#set($type = "boolean")
#end
/** ${column.columnComment} */
${column.javaField}?: ${type};
#end
#end
}
/** ${functionName}配置信息 */
export interface ${ClassName} extends BaseEntity {
#foreach($column in $columns)
#set($type = "string")
#if($column.javaType == "Long" || $column.javaType == "Integer")
#set($type = "number")
#elseif($column.javaType == "Boolean")
#set($type = "boolean")
#end
/** ${column.columnComment} */
${column.javaField}?: ${type};
#end
#if($table.sub)
/** $table.subTable.functionName信息 */
${subclassName}List?: ${subClassName}[];
#end
}
#if($table.sub)
/** ${subTable.functionName}配置信息 */
export interface ${subClassName} extends BaseEntity {
#foreach ($column in $subTable.columns)
#set($type = "string")
#if($column.javaType == "Long" || $column.javaType == "Integer")
#set($type = "number")
#elseif($column.javaType == "Boolean")
#set($type = "boolean")
#end
/** ${column.columnComment} */
${column.javaField}?: ${type};
#end
}
#end
@@ -139,6 +139,15 @@
#end #end
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope"> <template slot-scope="scope">
#if($genView)
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleViewData(scope.row)"
v-hasPermi="['${permissionPrefix}:query']"
>详情</el-button>
#end
<el-button <el-button
size="mini" size="mini"
type="text" type="text"
@@ -164,9 +173,21 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
#if($genView)
<!-- ${functionName}详情抽屉 -->
<${businessName}-view-drawer ref="${businessName}ViewRef" />
#end
<!-- 添加或修改${functionName}对话框 --> <!-- 添加或修改${functionName}对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body> #if($table.formColNum == 2)
<el-form ref="form" :model="form" :rules="rules" label-width="80px"> #set($dialogWidth = "800px")
#elseif($table.formColNum == 3)
#set($dialogWidth = "1100px")
#else
#set($dialogWidth = "500px")
#end
<el-dialog :title="title" :visible.sync="open" width="${dialogWidth}" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-row>
#foreach($column in $columns) #foreach($column in $columns)
#set($field=$column.javaField) #set($field=$column.javaField)
#if($column.insert && !$column.pk) #if($column.insert && !$column.pk)
@@ -179,100 +200,127 @@
#end #end
#set($dictType=$column.dictType) #set($dictType=$column.dictType)
#if("" != $treeParentCode && $column.javaField == $treeParentCode) #if("" != $treeParentCode && $column.javaField == $treeParentCode)
<el-form-item label="${comment}" prop="${treeParentCode}"> <el-col :span="${colSpan}">
<treeselect v-model="form.${treeParentCode}" :options="${businessName}Options" :normalizer="normalizer" placeholder="请选择${comment}" /> <el-form-item label="${comment}" prop="${treeParentCode}">
</el-form-item> <treeselect v-model="form.${treeParentCode}" :options="${businessName}Options" :normalizer="normalizer" placeholder="请选择${comment}" />
</el-form-item>
</el-col>
#elseif($column.htmlType == "input") #elseif($column.htmlType == "input")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-input v-model="form.${field}" placeholder="请输入${comment}" /> <el-form-item label="${comment}" prop="${field}">
</el-form-item> <el-input v-model="form.${field}" placeholder="请输入${comment}" />
</el-form-item>
</el-col>
#elseif($column.htmlType == "imageUpload") #elseif($column.htmlType == "imageUpload")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<image-upload v-model="form.${field}"/> <el-form-item label="${comment}" prop="${field}">
</el-form-item> <image-upload v-model="form.${field}"/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "fileUpload") #elseif($column.htmlType == "fileUpload")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<file-upload v-model="form.${field}"/> <el-form-item label="${comment}" prop="${field}">
</el-form-item> <file-upload v-model="form.${field}"/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "editor") #elseif($column.htmlType == "editor")
<el-form-item label="${comment}"> <el-col :span="24">
<editor v-model="form.${field}" :min-height="192"/> <el-form-item label="${comment}">
</el-form-item> <editor v-model="form.${field}" :min-height="192"/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "select" && "" != $dictType) #elseif($column.htmlType == "select" && "" != $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-select v-model="form.${field}" placeholder="请选择${comment}"> <el-form-item label="${comment}" prop="${field}">
<el-option <el-select v-model="form.${field}" placeholder="请选择${comment}">
v-for="dict in dict.type.${dictType}" <el-option
:key="dict.value" v-for="dict in dict.type.${dictType}"
:label="dict.label" :key="dict.value"
:label="dict.label"
#if($column.javaType == "Integer" || $column.javaType == "Long") #if($column.javaType == "Integer" || $column.javaType == "Long")
:value="parseInt(dict.value)" :value="parseInt(dict.value)"
#else #else
:value="dict.value" :value="dict.value"
#end #end
></el-option> ></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col>
#elseif($column.htmlType == "select" && $dictType) #elseif($column.htmlType == "select" && $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-select v-model="form.${field}" placeholder="请选择${comment}"> <el-form-item label="${comment}" prop="${field}">
<el-option label="请选择字典生成" value="" /> <el-select v-model="form.${field}" placeholder="请选择${comment}">
</el-select> <el-option label="请选择字典生成" value="" />
</el-form-item> </el-select>
</el-form-item>
</el-col>
#elseif($column.htmlType == "checkbox" && "" != $dictType) #elseif($column.htmlType == "checkbox" && "" != $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-checkbox-group v-model="form.${field}"> <el-form-item label="${comment}" prop="${field}">
<el-checkbox <el-checkbox-group v-model="form.${field}">
v-for="dict in dict.type.${dictType}" <el-checkbox
:key="dict.value" v-for="dict in dict.type.${dictType}"
:label="dict.value"> :key="dict.value"
{{dict.label}} :label="dict.value">
</el-checkbox> {{dict.label}}
</el-checkbox-group> </el-checkbox>
</el-form-item> </el-checkbox-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "checkbox" && $dictType) #elseif($column.htmlType == "checkbox" && $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-checkbox-group v-model="form.${field}"> <el-form-item label="${comment}" prop="${field}">
<el-checkbox>请选择字典生成</el-checkbox> <el-checkbox-group v-model="form.${field}">
</el-checkbox-group> <el-checkbox>请选择字典生成</el-checkbox>
</el-form-item> </el-checkbox-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "radio" && "" != $dictType) #elseif($column.htmlType == "radio" && "" != $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-radio-group v-model="form.${field}"> <el-form-item label="${comment}" prop="${field}">
<el-radio <el-radio-group v-model="form.${field}">
v-for="dict in dict.type.${dictType}" <el-radio
:key="dict.value" v-for="dict in dict.type.${dictType}"
:key="dict.value"
#if($column.javaType == "Integer" || $column.javaType == "Long") #if($column.javaType == "Integer" || $column.javaType == "Long")
:label="parseInt(dict.value)" :label="parseInt(dict.value)"
#else #else
:label="dict.value" :label="dict.value"
#end #end
>{{dict.label}}</el-radio> >{{dict.label}}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col>
#elseif($column.htmlType == "radio" && $dictType) #elseif($column.htmlType == "radio" && $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-radio-group v-model="form.${field}"> <el-form-item label="${comment}" prop="${field}">
<el-radio label="1">请选择字典生成</el-radio> <el-radio-group v-model="form.${field}">
</el-radio-group> <el-radio label="1">请选择字典生成</el-radio>
</el-form-item> </el-radio-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "datetime") #elseif($column.htmlType == "datetime")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-date-picker clearable <el-form-item label="${comment}" prop="${field}">
v-model="form.${field}" <el-date-picker clearable
type="date" v-model="form.${field}"
value-format="yyyy-MM-dd" type="date"
placeholder="选择${comment}"> value-format="yyyy-MM-dd"
</el-date-picker> placeholder="选择${comment}">
</el-form-item> </el-date-picker>
</el-form-item>
</el-col>
#elseif($column.htmlType == "textarea") #elseif($column.htmlType == "textarea")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="24">
<el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" /> <el-form-item label="${comment}" prop="${field}">
</el-form-item> <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-col>
#end #end
#end #end
#end #end
#end #end
</el-row>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button> <el-button type="primary" @click="submitForm">确 定</el-button>
@@ -284,6 +332,9 @@
<script> <script>
import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}" import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}"
#if($genView)
import ${BusinessName}ViewDrawer from "./view"
#end
import Treeselect from "@riophae/vue-treeselect" import Treeselect from "@riophae/vue-treeselect"
import "@riophae/vue-treeselect/dist/vue-treeselect.css" import "@riophae/vue-treeselect/dist/vue-treeselect.css"
@@ -293,6 +344,9 @@ export default {
dicts: [${dicts}], dicts: [${dicts}],
#end #end
components: { components: {
#if($genView)
${BusinessName}ViewDrawer,
#end
Treeselect Treeselect
}, },
data() { data() {
@@ -448,6 +502,12 @@ export default {
this.refreshTable = true this.refreshTable = true
}) })
}, },
#if($genView)
/** 详情按钮操作 */
handleViewData(row) {
this.#[[$]]#refs["${businessName}ViewRef"].open(row.${pkColumn.javaField})
},
#end
/** 修改按钮操作 */ /** 修改按钮操作 */
handleUpdate(row) { handleUpdate(row) {
this.reset() this.reset()

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