新增物联:完成Java 21与IoT融合架构及安全门禁基线归档

This commit is contained in:
wangzhibiao committed 2026-10-13 21:17:39 +08:00
1 parent 9a62ac8536
commit 373921447f
468 files changed
+36812 -4676

No files matched your search

+4
View File
@@ -20,6 +20,10 @@ node_modules/
.npm-cache/
*.class
**/*.class
*.jar
**/*.jar
*.dmp
**/*.dmp
# Local authorized Kingbase JDBC vendor binary; provision via scripts/bootstrap/
/RuoYi-Vue/lib/kingbase8-8.6.0.jar
+4 -4
View File
@@ -15,8 +15,8 @@ build_backend:
tags:
- railway-deploy
rules:
- if: '$CI_COMMIT_BRANCH'
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == "feat/cicd-pipeline"'
- when: never
script:
- echo "=== 开始编译后端 RuoYi-Vue ==="
- export JAVA_HOME=/data2/wzb/tools/jdk8
@@ -39,8 +39,8 @@ build_frontend:
tags:
- railway-deploy
rules:
- if: '$CI_COMMIT_BRANCH'
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == "feat/cicd-pipeline"'
- when: never
script:
- echo "=== 开始编译前端 build_ui ==="
- export NODE_OPTIONS="--openssl-legacy-provider --dns-result-order=ipv4first"
+153
View File
@@ -0,0 +1,153 @@
# SYFJGYD学员管理系统(融合演示版)— Clean Demo Candidate 运行与演示说明书
## 一、系统概况
本工程为 **SYFJGYD学员管理系统** 的隔离融合演示候选版本(Clean Demo Candidate),集成了 **Java 21 + Spring Boot 4** 架构升级、培训管理业务(学员、班级、住宿全周期)与智能门锁 IoT 协同授权中枢。
### 核心演示原则
- **数据源隔离**:严格运行于独立研发 QA 数据库(`railwaysystem_fusion_qa_20261013`),采用最小权限应用账号连接,与共享试用库(`training`)及生产环境完全隔离。
- **仿真受控运行**:IoT 底层采用仿真受控模式,物理 MQTT 硬件与实体门锁处于安全隔离状态,防止误触发真实硬件动作。
- **真实完整业务闭环**:前端 UI、后端业务逻辑、Kingbase 事务状态流转均为 100% 真实执行。
---
## 二、系统访问与前置准备
### 1. 访问地址
- **前端 Web 控制台**:`http://127.0.0.1:8088`
- **默认演示账号**:`admin` / `admin123`
- **后端 API 服务**:`http://127.0.0.1:8096`(由前端通过 `/prod-api` 自动反向代理)
### 2. 演示前服务状态核验
在启动系统前,请确保研发数据库 SSH 隧道处于连通状态:
- **隧道端口**:`127.0.0.1:14321`
- **若隧道未连通**,请在终端执行:
```bash
ssh -N -L 127.0.0.1:14321:192.168.139.218:4321 -o ServerAliveInterval=15 wzbmatebook
```
- **安全凭据文件**:系统启动依赖受限目录下的环境变量配置(`~/.railwaysystem-qa.env`),密码由启动脚本安全注入,无需人工输入。
---
## 三、一键启停操作指南
系统提供了平滑、健壮的一键管理脚本,支持自动端口清理与就绪健康探测:
### 1. 启动系统
在工程根目录下执行:
```bash
./start_demo.sh
```
**启动脚本自动完成:**
1. 校验环境变量与凭据完整性;
2. 探测数据库 SSH 隧道连通性(未连通时给出明确操作指引并安全终止);
3. 自动释放 8096 与 8088 端口历史旧进程;
4. 启动 Spring Boot 4 后端,轮询 `/captchaImage` 验证就绪(约 4~6 秒);
5. 启动前端反向代理服务并验证 HTTP 200 就绪;
6. 打印就绪提示与访问入口。
### 2. 停止系统
在演示结束或需要重置时执行:
```bash
./stop_demo.sh
```
**停止脚本自动完成:**
1. 优雅停止前端反向代理进程(释放端口 8088);
2. 优雅停止后端 Spring Boot 进程(释放端口 8096);
3. 清理 Headless Chrome 测试残留进程;
4. 确认所有演示端口已完全释放。
---
## 四、建议的五分钟核心业务演示路线
建议按照培训学员入宿、门锁授权、调宿换锁、退宿撤权的标准业务生命周期进行演示:
```
[1. 管理员登录]
↓
[2. 住宿管理:查询预分配学员] ──> 点击【入住】办理 ──> 状态流转为【在住】
↓
[3. 智能门锁:授权管理] ──> 查看自动生成的人脸授权意图 (待下发/待回执)
↓
[4. 仿真回执确认] ──> 受控驱动网关与门锁回执 ──> 页面刷新为【已生效/下发成功】
↓
[5. 住宿管理:办理调房] ──> 选择新宿舍 (致远楼 102) 提交 ──> 触发新门锁建权与旧门锁撤权
↓
[6. 住宿管理:办理退宿] ──> 点击【退宿】确认 ──> 状态更新为【已退宿】,授权彻底清除
↓
[7. 履历与流水核查] ──> 查看【住宿履历】对话框 & 【命令记录】指令流水
```
### 步骤详解:
1. **登录系统**:
- 打开浏览器访问 `http://127.0.0.1:8088`,输入账号 `admin`,密码 `admin123`,点击登录,进入系统仪表盘首页。
2. **查询并办理入住**:
- 点击左侧菜单【培训管理】->【住宿安排】(或访问 `/train/accommodation`);
- 在学号输入框输入演示学员学号(例如 `S_DEMO_20261013`),点击【搜索】;
- 找到处于“预分配”状态的学员记录,点击操作列的【入住】按钮;
- 在弹出的确认对话框中点击【确定】;
- 页面提示“入住办理成功”,学员住宿状态实时刷新为绿色的【在住】标签。
3. **核查门锁授权意图**:
- 点击左侧菜单【门禁管理】->【授权管理】(或访问 `/iot/auth`);
- 看到系统已根据学员的人脸特征与所分配的宿舍房间(致远楼 101),自动建立门锁授权意图记录。
4. **演示仿真回执确认生效**:
- 在仿真环境下,系统调用受控 Mock 回执端点驱动状态;
- 在授权管理页面点击【搜索】刷新,授权同步状态变更为【已生效】(或【下发成功】),展示端到端联动效果。
5. **办理学员调房**:
- 返回【住宿安排】页面,点击学员右侧的【调房】按钮;
- 在弹出的【办理学员调房】对话框中,目标宿舍选择“致远楼 102”,输入床位号“2号床”,点击【确认调房】;
- 系统执行调房事务:原宿舍记录流转为“已退宿”,新宿舍记录建立并流转为“在住”;
- 后台自动向旧门锁下发撤权指令,向新门锁下发建权指令。
6. **办理退宿**:
- 在【住宿安排】页面中,点击学员所在行的【退宿】按钮,在确认框中点击【确定】;
- 提示“退宿办理成功”,住宿状态更新为红色的【已退宿】标签。
7. **查看历史履历与指令流水**:
- 点击该行操作列的【住宿履历】按钮,弹出【学员历史住宿记录】对话框,清晰展示从预分配、调房退宿到最终退宿的完整时间轴;
- 点击左侧菜单【门禁管理】->【命令记录】(或访问 `/iot/command`),查看全流程自动生成的授权下发(0011)与清除授权(0012)指令流水,证明全链路具备完整审计溯源能力。
---
## 五、真实业务流程 vs 仿真(Mock)环节说明
为了确保演示的真实性与合规性,系统严格区分业务逻辑与硬件模拟:
| 业务环节 | 实现方式 | 状态说明 |
| :--- | :--- | :--- |
| **管理员鉴权与 RBAC 权限** | 真实数据库与 Spring Security | 100% 真实执行 |
| **学员基本信息与班级档案** | 真实 Kingbase QA 数据库表 | 100% 真实执行 |
| **住宿全生命周期状态机** | 真实业务 Service 与事务 | 100% 真实执行(预分配/在住/退宿) |
| **住宿调房多阶段事务** | 真实业务 Service | 100% 真实执行(旧记退宿 + 新建在住) |
| **门锁授权意图关联生成** | 真实 IoT 业务桥接器 | 100% 真实执行(自动解析人脸与房间锁映射) |
| **指令流水与审计日志** | 真实 Kingbase `iot_command` 表 | 100% 真实执行,全链路持久化 |
| **前端交互与响应** | 真实 Vue SPA 页面与 Element UI | 100% 真实执行,无静态伪造 |
| **MQTT 指令双端回执** | Mock 仿真驱动(`iot.mqtt.mock=true`) | 仿真模拟网关与门锁硬件回执,隔绝物理设备 |
| **人脸识别特征提取** | 仿真服务 | 仿真特征向量映射,未连接物理人脸终端 |
| **实体门锁物理开锁/状态** | 物理隔离(未接入) | **BLOCKED / HARDWARE_PENDING** |
---
## 六、常见问题排查与快速恢复
### 1. 启动脚本提示 `[BLOCKED] 数据库 SSH 隧道未连接`
- **原因**:本地 14321 端口无法连接到 Kingbase 研发服务器。
- **排查方法**:
1. 检查 SSH 隧道进程:`ps aux | grep 14321`;
2. 重新建立隧道:`ssh -N -L 127.0.0.1:14321:192.168.139.218:4321 wzbmatebook`;
3. 验证端口连通:`nc -z 127.0.0.1 14321`。
### 2. 页面访问提示 502 Bad Gateway
- **原因**:后端 Spring Boot 正在启动中或由于数据库超时启动失败。
- **排查方法**:
1. 查看后端日志:`tail -n 50 backend.log`;
2. 检查是否有 Druid 数据源连接超时异常;
3. 确认后执行 `./stop_demo.sh && ./start_demo.sh` 重启。
### 3. 演示过程中需要重置测试数据
- **方法**:
可直接在工程根目录下执行演示数据预置脚本:
```bash
java -cp RuoYi-Vue-master/ruoyi-admin/target/test-classes:RuoYi-Vue-master/ruoyi-admin/target/ruoyi-admin.jar com.ruoyi.train.PrepareDemoData
```
该命令将自动重置学员 `S_DEMO_20261013` 为初始的“预分配”住宿状态,并清理门锁历史旧授权,无需重启系统即可开始下一轮完整演示。
+30 -24
View File
@@ -1,11 +1,11 @@
<p align="center">
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.9.1</h1>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.9.2</h1>
<h4 align="center">基于SpringBoot+Vue前后端分离的Java快速开发框架</h4>
<p align="center">
<a href="https://gitee.com/y_project/RuoYi-Vue/stargazers"><img src="https://gitee.com/y_project/RuoYi-Vue/badge/star.svg?theme=dark"></a>
<a href="https://gitee.com/y_project/RuoYi-Vue"><img src="https://img.shields.io/badge/RuoYi-v3.9.1-brightgreen.svg"></a>
<a href="https://gitee.com/y_project/RuoYi-Vue"><img src="https://img.shields.io/badge/RuoYi-v3.9.2-brightgreen.svg"></a>
<a href="https://gitee.com/y_project/RuoYi-Vue/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
</p>
@@ -13,15 +13,36 @@
若依是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
* 前端采用Vue、Element UI。
* 前端采用Vue、Element(提供 Vue2/Vue3/TS 多版本)。
* 后端采用Spring Boot、Spring Security、Redis & Jwt。
* 权限认证使用Jwt,支持多终端认证系统。
* 支持加载动态权限菜单,多方式轻松权限控制。
* 高效率开发,使用代码生成器可以一键生成前后端代码。
* 提供了技术栈(Vue3 Element Plus Vite)的 [RuoYi-Vue3](https://gitcode.com/yangzongzhuan/RuoYi-Vue3)版本,以及技术栈(TypeScript)的 [RuoYi-Vue3-TypeScript](https://gitcode.com/yangzongzhuan/RuoYi-Vue3/tree/typescript)版本,两者保持同步更新。
* 提供了适配 Spring Boot 3 的版本分支 [RuoYi-Vue (springboot3)](https://gitee.com/y_project/RuoYi-Vue/tree/springboot3),以及特定需求的 单应用版本 [RuoYi-Vue-fast](https://gitcode.com/yangzongzhuan/RuoYi-Vue-fast) 与 Oracle数据库版本 [RuoYi-Vue-Oracle](https://gitcode.com/yangzongzhuan/RuoYi-Vue-Oracle),均保持同步更新。
* 基于Jwt的Token认证,支持多终端(Web、App、小程序)无缝接入。
* 支持加载动态权限菜单,多方式轻松权限控制,细粒度到按钮级别。
* 内置强大代码生成器,一键生成前后端代码(java、vue、xml、sql),支持 CRUD 下载。
* 阿里云折扣场:[点我进入](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-Vue2** | **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-Vue2](https://gitcode.com/yangzongzhuan/RuoYi-Vue2) | [RuoYi-Vue3](https://gitcode.com/yangzongzhuan/RuoYi-Vue3) | [RuoYi-Vue3-TypeScript](https://gitcode.com/yangzongzhuan/RuoYi-Vue3/tree/typescript) |
## 内置功能
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
@@ -43,21 +64,6 @@
17. 在线构建器:拖动表单元素生成相应的HTML代码。
18. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。
# 版本对比
RuoYi-Vue 前端项目的三个主要演进版本,方便你直观对比其技术栈差异(并行开发维护)。
| 项目名称 | **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) |
## 在线体验
- admin/admin123
@@ -106,4 +112,4 @@ RuoYi-Vue 前端项目的三个主要演进版本,方便你直观对比其技
## 若依前后端分离交流群
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) 点击按钮入群。
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) [![加入QQ群](https://img.shields.io/badge/113071109-blue.svg)](http://qm.qq.com/cgi-bin/qmLine truncated
+69 -160
View File
@@ -6,61 +6,40 @@
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId>
<version>3.9.1</version>
<version>3.9.2</version>
<name>ruoyi</name>
<url>http://www.ruoyi.vip</url>
<description>若依管理系统</description>
<properties>
<ruoyi.version>3.9.1</ruoyi.version>
<ruoyi.version>3.9.2</ruoyi.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<spring-boot.version>2.5.15</spring-boot.version>
<druid.version>1.2.27</druid.version>
<yauaa.version>7.32.0</yauaa.version>
<swagger.version>3.0.0</swagger.version>
<java.version>17</java.version>
<spring-boot.version>4.1.0</spring-boot.version>
<mybatis-spring-boot.version>4.1.0</mybatis-spring-boot.version>
<druid.version>1.2.28</druid.version>
<yauaa.version>8.2.0</yauaa.version>
<kaptcha.version>2.3.3</kaptcha.version>
<pagehelper.boot.version>1.4.7</pagehelper.boot.version>
<fastjson.version>2.0.60</fastjson.version>
<oshi.version>6.9.1</oshi.version>
<commons.io.version>2.21.0</commons.io.version>
<poi.version>4.1.2</poi.version>
<pagehelper.boot.version>4.1.1</pagehelper.boot.version>
<fastjson.version>2.0.64</fastjson.version>
<oshi.version>7.4.4</oshi.version>
<commons.io.version>2.22.0</commons.io.version>
<poi.version>5.5.1</poi.version>
<velocity.version>2.3</velocity.version>
<jwt.version>0.9.1</jwt.version>
<!-- override dependency version -->
<tomcat.version>9.0.112</tomcat.version>
<logback.version>1.2.13</logback.version>
<spring-security.version>5.7.14</spring-security.version>
<spring-framework.version>5.3.39</spring-framework.version>
<jaxb-api.version>2.3.1</jaxb-api.version>
<springdoc.version>3.1.0</springdoc.version>
<kingbase.version>9.0.0</kingbase.version>
<paho.version>1.2.5</paho.version>
</properties>
<!-- 依赖声明 -->
<dependencyManagement>
<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>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
@@ -69,42 +48,10 @@
<scope>import</scope>
</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>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<artifactId>druid-spring-boot-4-starter</artifactId>
<version>${druid.version}</version>
</dependency>
@@ -122,6 +69,18 @@
<version>${pagehelper.boot.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot.version}</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>${jaxb-api.version}</version>
</dependency>
<!-- 获取系统信息 -->
<dependency>
<groupId>com.github.oshi</groupId>
@@ -129,17 +88,11 @@
<version>${oshi.version}</version>
</dependency>
<!-- Swagger3依赖 -->
<!-- spring-doc -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${swagger.version}</version>
<exclusions>
<exclusion>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
</exclusion>
</exclusions>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<!-- io常用工具类 -->
@@ -177,31 +130,6 @@
<version>${jwt.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.15.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.15.0</version>
</dependency>
<dependency>
<groupId>com.kingbase</groupId>
<artifactId>kingbase8</artifactId>
<version>8.6.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>2.2.13.RELEASE</version>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- 验证码 -->
<dependency>
<groupId>pro.fessional</groupId>
@@ -209,6 +137,22 @@
<version>${kaptcha.version}</version>
</dependency>
<!-- KingbaseES(人大金仓)驱动:官方未提供公网 Maven 仓库,驱动包直接放置在
ruoyi-admin/lib 目录,由 ruoyi-admin 模块的 kingbase profile 以 system scope 引入。
若 jar 文件名或版本变更,请同步修改 kingbase.version 属性及 ruoyi-admin 中的 systemPath。 -->
<dependency>
<groupId>cn.com.kingbase</groupId>
<artifactId>kingbase8</artifactId>
<version>${kingbase.version}</version>
</dependency>
<!-- Eclipse Paho MQTT客户端 -->
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>${paho.version}</version>
</dependency>
<!-- 定时任务-->
<dependency>
<groupId>com.ruoyi</groupId>
@@ -237,6 +181,19 @@
<version>${ruoyi.version}</version>
</dependency>
<!-- IOT设备接入-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-iot</artifactId>
<version>${ruoyi.version}</version>
</dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-train</artifactId>
<version>${ruoyi.version}</version>
</dependency>
<!-- 通用工具-->
<dependency>
<groupId>com.ruoyi</groupId>
@@ -244,13 +201,6 @@
<version>${ruoyi.version}</version>
</dependency>
<!-- 业务模块-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-train</artifactId>
<version>${ruoyi.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
@@ -261,6 +211,7 @@
<module>ruoyi-quartz</module>
<module>ruoyi-generator</module>
<module>ruoyi-common</module>
<module>ruoyi-iot</module>
<module>ruoyi-train</module>
</modules>
<packaging>pom</packaging>
@@ -270,62 +221,20 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<version>3.13.0</version>
<configuration>
<parameters>true</parameters>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<!-- 输出目录 -->
<outputDirectory>D:\maven_repository\com\kingbase\kingbase8\8.6.0</outputDirectory>
<!-- 包含运行时依赖 -->
<includeScope>runtime</includeScope>
<!-- 包含编译时依赖 -->
<includeScope>compile</includeScope>
<!-- 可选:包含指定的 groupId -->
<includeGroupIds>com.kingbase</includeGroupIds>
<!-- 可选:包含指定的 artifactId -->
<includeArtifactIds>kingbase8</includeArtifactIds>
</configuration>
</execution>
</executions>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.yml</include>
<include>**/*.vm</include>
</includes>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.yml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
<repositories>
+77 -51
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.9.1</version>
<version>3.9.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
@@ -24,29 +24,10 @@
<optional>true</optional> <!-- 表示依赖不会传递 -->
</dependency>
<!-- swagger3-->
<!-- spring-doc -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
</dependency>
<!-- 防止进入swagger页面报类型转换错误,排除3.0.0中的引用,手动增加1.6.2版本 -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>com.kingbase</groupId>
<artifactId>kingbase8</artifactId>
<version>8.6.0</version>
</dependency>
<!-- Mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<!-- 核心模块-->
@@ -67,10 +48,23 @@
<artifactId>ruoyi-generator</artifactId>
</dependency>
<!-- 业务模块-->
<!-- IOT设备接入-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-iot</artifactId>
<version>3.9.2</version>
</dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-train</artifactId>
<version>3.9.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
@@ -80,16 +74,73 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- 默认使用 KingbaseES,切换到 MySQL 时使用:mvn -P mysql -->
<profiles>
<profile>
<id>mysql</id>
<dependencies>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
</dependencies>
</profile>
<!-- KingbaseES(人大金仓):驱动包置于 ruoyi-admin/lib 目录,直接按文件路径引入 -->
<profile>
<id>kingbase</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>cn.com.kingbase</groupId>
<artifactId>kingbase8</artifactId>
<scope>system</scope>
<systemPath>${project.basedir}/lib/kingbase8-${kingbase.version}.jar</systemPath>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<!-- 打 war 包时把外部驱动一起放进 WEB-INF/lib -->
<webResources>
<resource>
<directory>${project.basedir}/lib</directory>
<targetPath>WEB-INF/lib</targetPath>
<filtering>false</filtering>
<includes>
<include>kingbase8-${kingbase.version}.jar</include>
</includes>
</resource>
</webResources>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.15</version>
<configuration>
<fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
<addResources>true</addResources>
<!-- KingbaseES 等 system scope 驱动需要打进可执行 jar -->
<includeSystemScope>true</includeSystemScope>
</configuration>
<executions>
<execution>
@@ -108,31 +159,6 @@
<warName>${project.artifactId}</warName>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<!-- 输出目录 -->
<outputDirectory>D:\maven_repository\com\kingbase\kingbase8\8.6.0</outputDirectory>
<!-- 包含运行时依赖 -->
<includeScope>runtime</includeScope>
<!-- 包含编译时依赖 -->
<includeScope>compile</includeScope>
<!-- 可选:包含指定的 groupId -->
<includeGroupIds>com.kingbase</includeGroupIds>
<!-- 可选:包含指定的 artifactId -->
<includeArtifactIds>kingbase8</includeArtifactIds>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<finalName>${project.artifactId}</finalName>
</build>
@@ -2,7 +2,7 @@ package com.ruoyi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
/**
* 启动程序
@@ -3,9 +3,9 @@ package com.ruoyi.web.controller.common;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import jakarta.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.web.bind.annotation.GetMapping;
@@ -2,8 +2,8 @@ package com.ruoyi.web.controller.common;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -45,6 +45,7 @@ public class CacheController
caches.add(new SysCache(CacheConstants.PWD_ERR_CNT_KEY, "密码错误次数"));
}
@SuppressWarnings("deprecation")
@PreAuthorize("@ss.hasPermi('monitor:cache:list')")
@GetMapping()
public AjaxResult getInfo() throws Exception
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.monitor;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.monitor;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.system;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
@@ -1,6 +1,7 @@
package com.ruoyi.web.controller.system;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -110,6 +111,20 @@ public class SysDeptController extends BaseController
return toAjax(deptService.updateDept(dept));
}
/**
* 保存部门排序
*/
@PreAuthorize("@ss.hasPermi('system:dept:edit')")
@Log(title = "保存部门排序", businessType = BusinessType.UPDATE)
@PutMapping("/updateSort")
public AjaxResult updateSort(@RequestBody Map<String, String> params)
{
String[] deptIds = params.get("deptIds").split(",");
String[] orderNums = params.get("orderNums").split(",");
deptService.updateDeptSort(deptIds, orderNums);
return success();
}
/**
* 删除部门
*/
@@ -2,7 +2,7 @@ package com.ruoyi.web.controller.system;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.system;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
@@ -1,10 +1,17 @@
package com.ruoyi.web.controller.system;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysUserService;
/**
* 首页
@@ -18,6 +25,9 @@ public class SysIndexController
@Autowired
private RuoYiConfig ruoyiConfig;
@Autowired
private ISysUserService userService;
/**
* 访问首页,提示语
*/
@@ -26,4 +36,29 @@ public class SysIndexController
{
return StringUtils.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion());
}
/**
* 解锁屏幕
*/
@PostMapping("/unlockscreen")
public AjaxResult unlockScreen(@RequestBody Map<String, String> body)
{
String password = body.get("password");
if (StringUtils.isEmpty(password))
{
return AjaxResult.error("密码不能为空");
}
String username = SecurityUtils.getUsername();
SysUser user = userService.selectUserByUserName(username);
if (user == null)
{
return AjaxResult.error("服务器超时,请重新登录");
}
if (!SecurityUtils.matchesPassword(password, user.getPassword()))
{
return AjaxResult.error("密码错误,请重新输入");
}
return AjaxResult.success("解锁成功");
}
}
@@ -1,6 +1,7 @@
package com.ruoyi.web.controller.system;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
@@ -129,6 +130,20 @@ public class SysMenuController extends BaseController
return toAjax(menuService.updateMenu(menu));
}
/**
* 保存菜单排序
*/
@PreAuthorize("@ss.hasPermi('system:menu:edit')")
@Log(title = "保存菜单排序", businessType = BusinessType.UPDATE)
@PutMapping("/updateSort")
public AjaxResult updateSort(@RequestBody Map<String, String> params)
{
String[] menuIds = params.get("menuIds").split(",");
String[] orderNums = params.get("orderNums").split(",");
menuService.updateMenuSort(menuIds, orderNums);
return success();
}
/**
* 删除菜单
*/
@@ -11,13 +11,16 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.SysNotice;
import com.ruoyi.system.service.ISysNoticeReadService;
import com.ruoyi.system.service.ISysNoticeService;
/**
@@ -32,6 +35,9 @@ public class SysNoticeController extends BaseController
@Autowired
private ISysNoticeService noticeService;
@Autowired
private ISysNoticeReadService noticeReadService;
/**
* 获取通知公告列表
*/
@@ -47,7 +53,6 @@ public class SysNoticeController extends BaseController
/**
* 根据通知公告编号获取详细信息
*/
@PreAuthorize("@ss.hasPermi('system:notice:query')")
@GetMapping(value = "/{noticeId}")
public AjaxResult getInfo(@PathVariable Long noticeId)
{
@@ -78,6 +83,59 @@ public class SysNoticeController extends BaseController
return toAjax(noticeService.updateNotice(notice));
}
/**
* 首页顶部公告列表(返回全部正常公告,带当前用户已读标记,最多5条)
*/
@GetMapping("/listTop")
@ResponseBody
public AjaxResult listTop()
{
Long userId = getUserId();
List<SysNotice> list = noticeReadService.selectNoticeListWithReadStatus(userId, 5);
long unreadCount = list.stream().filter(n -> !n.getIsRead()).count();
AjaxResult result = AjaxResult.success(list);
result.put("unreadCount", unreadCount);
return result;
}
/**
* 标记公告已读
*/
@PostMapping("/markRead")
@ResponseBody
public AjaxResult markRead(Long noticeId)
{
Long userId = getUserId();
noticeReadService.markRead(noticeId, userId);
return success();
}
/**
* 批量标记已读
*/
@PostMapping("/markReadAll")
@ResponseBody
public AjaxResult markReadAll(String ids)
{
Long userId = getUserId();
Long[] noticeIds = Convert.toLongArray(ids);
noticeReadService.markReadBatch(userId, noticeIds);
return success();
}
/**
* 已读用户列表数据
*/
@PreAuthorize("@ss.hasPermi('system:notice:list')")
@GetMapping("/readUsers/list")
@ResponseBody
public TableDataInfo readUsersList(Long noticeId, String searchValue)
{
startPage();
List<?> list = noticeReadService.selectReadUsersByNoticeId(noticeId, searchValue);
return getDataTable(list);
}
/**
* 删除通知公告
*/
@@ -86,6 +144,7 @@ public class SysNoticeController extends BaseController
@DeleteMapping("/{noticeIds}")
public AjaxResult remove(@PathVariable Long[] noticeIds)
{
noticeReadService.deleteByNoticeIds(noticeIds);
return toAjax(noticeService.deleteNoticeByIds(noticeIds));
}
}
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.system;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.system;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
@@ -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.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.service.SysPermissionService;
import com.ruoyi.framework.web.service.TokenService;
@@ -128,14 +126,8 @@ public class SysRoleController extends BaseController
if (roleService.updateRole(role) > 0)
{
// 更新缓存用户权限
LoginUser loginUser = getLoginUser();
if (StringUtils.isNotNull(loginUser.getUser()) && !loginUser.getUser().isAdmin())
{
loginUser.setUser(userService.selectUserByUserName(loginUser.getUser().getUserName()));
loginUser.setPermissions(permissionService.getMenuPermission(loginUser.getUser()));
tokenService.setLoginUser(loginUser);
}
// 刷新所有持有该角色的在线用户权限
tokenService.refreshPermissionByRoleId(role.getRoleId(), permissionService);
return success();
}
return error("修改角色'" + role.getRoleName() + "'失败,请联系管理员");
@@ -2,7 +2,7 @@ package com.ruoyi.web.controller.system;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -0,0 +1,103 @@
package com.ruoyi.web.controller.tool;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.access.prepost.PreAuthorize;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.iot.domain.MqttMessageDTO;
import com.ruoyi.iot.lock.protocol.YdlinkFrameDTO;
import com.ruoyi.iot.service.IMqttService;
/**
* 独立 QA 测试环境专属:Mock MQTT 消息检查与设备回执驱动 Controller
* 仅在配置 train.iot.mock-mode=true 时加载,且必须经过管理员权限校验,禁止生产泄露与匿名调用
*/
@RestController
@RequestMapping("/qa/mock")
@ConditionalOnProperty(name = "iot.mqtt.mock", havingValue = "true")
@PreAuthorize("@ss.hasRole('admin') or @ss.hasPermi('iot:command:edit')")
public class QaMockMqttApiController
{
@Autowired
private IMqttService mqttService;
/**
* 获取所有拦截到的下行指令
*/
@GetMapping("/published")
public AjaxResult getPublishedMessages()
{
return AjaxResult.success(mqttService.getMockPublishedMessages());
}
/**
* 清空已拦截的下行指令
*/
@PostMapping("/clear")
public AjaxResult clearPublishedMessages()
{
mqttService.getMockPublishedMessages().clear();
return AjaxResult.success("已清空");
}
/**
* 模拟注入网关与门锁回执报文
*
* @param body 参数字典包含:
* groupCode: 分组编号 (如 GRP_FUSION_TEST)
* commandNo: 命令流水号 seq
* deviceSn: 门锁SN (如 LOCK-ZY-101)
* op: 操作类型 (add / delete)
* code: 结果码 (0=门锁成功, 10=网关已接收, 20=门锁执行失败)
* msg: 说明消息
*/
@PostMapping("/send-ack")
public AjaxResult sendAck(@RequestBody Map<String, Object> body)
{
String groupCode = (String) body.getOrDefault("groupCode", "GRP_FUSION_TEST");
String commandNo = (String) body.get("commandNo");
String deviceSn = (String) body.getOrDefault("deviceSn", "LOCK-ZY-101");
String op = (String) body.getOrDefault("op", "add");
String code = String.valueOf(body.getOrDefault("code", "0"));
String msg = (String) body.getOrDefault("msg", "mock execution ok");
if (commandNo == null || commandNo.isEmpty())
{
return AjaxResult.error("commandNo (seq) 不能为空");
}
String topic = "ydlink/" + groupCode + "/thing/data/batch_report";
YdlinkFrameDTO frame = new YdlinkFrameDTO();
frame.setDevId(groupCode);
frame.setSeq(commandNo);
frame.setOp(op);
frame.setAt(String.valueOf(System.currentTimeMillis() / 1000L));
List<Object> records = new ArrayList<>();
records.add(Arrays.asList(op, deviceSn, code, "person_mock", msg));
frame.setParams(records);
String payload = JSON.toJSONString(frame);
mqttService.mockReceive(topic, payload);
return AjaxResult.success("Mock 回执注入成功", Map.of(
"topic", topic,
"seq", commandNo,
"code", code,
"payload", payload
));
}
}
@@ -15,19 +15,16 @@ import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.StringUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* swagger 用户测试方法
*
* @author ruoyi
*/
@Api("用户信息管理")
@Tag(name = "用户信息管理")
@RestController
@RequestMapping("/test/user")
public class TestController extends BaseController
@@ -38,7 +35,7 @@ public class TestController extends BaseController
users.put(2, new UserEntity(2, "ry", "admin123", "15666666666"));
}
@ApiOperation("获取用户列表")
@Operation(summary = "获取用户列表")
@GetMapping("/list")
public R<List<UserEntity>> userList()
{
@@ -46,10 +43,10 @@ public class TestController extends BaseController
return R.ok(userList);
}
@ApiOperation("获取用户详细")
@ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class)
@Operation(summary = "获取用户详细")
@GetMapping("/{userId}")
public R<UserEntity> getUser(@PathVariable Integer userId)
public R<UserEntity> getUser(@PathVariable(name = "userId")
Integer userId)
{
if (!users.isEmpty() && users.containsKey(userId))
{
@@ -61,13 +58,7 @@ public class TestController extends BaseController
}
}
@ApiOperation("新增用户")
@ApiImplicitParams({
@ApiImplicitParam(name = "userId", value = "用户id", dataType = "Integer", dataTypeClass = Integer.class),
@ApiImplicitParam(name = "username", value = "用户名称", dataType = "String", dataTypeClass = String.class),
@ApiImplicitParam(name = "password", value = "用户密码", dataType = "String", dataTypeClass = String.class),
@ApiImplicitParam(name = "mobile", value = "用户手机", dataType = "String", dataTypeClass = String.class)
})
@Operation(summary = "新增用户")
@PostMapping("/save")
public R<String> save(UserEntity user)
{
@@ -79,9 +70,10 @@ public class TestController extends BaseController
return R.ok();
}
@ApiOperation("更新用户")
@Operation(summary = "更新用户")
@PutMapping("/update")
public R<String> update(@RequestBody UserEntity user)
public R<String> update(@RequestBody
UserEntity user)
{
if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId()))
{
@@ -96,10 +88,10 @@ public class TestController extends BaseController
return R.ok();
}
@ApiOperation("删除用户信息")
@ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class)
@Operation(summary = "删除用户信息")
@DeleteMapping("/{userId}")
public R<String> delete(@PathVariable Integer userId)
public R<String> delete(@PathVariable(name = "userId")
Integer userId)
{
if (!users.isEmpty() && users.containsKey(userId))
{
@@ -113,19 +105,19 @@ public class TestController extends BaseController
}
}
@ApiModel(value = "UserEntity", description = "用户实体")
@Schema(description = "用户实体")
class UserEntity
{
@ApiModelProperty("用户ID")
@Schema(title = "用户ID")
private Integer userId;
@ApiModelProperty("用户名称")
@Schema(title = "用户名称")
private String username;
@ApiModelProperty("用户密码")
@Schema(title = "用户密码")
private String password;
@ApiModelProperty("用户手机")
@Schema(title = "用户手机")
private String mobile;
public UserEntity()
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.train;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.train;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.train;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.train;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
@@ -2,7 +2,7 @@ package com.ruoyi.web.controller.train;
import java.util.List;
import java.util.regex.Pattern;
import javax.validation.Valid;
import jakarta.validation.Valid;
import com.ruoyi.common.utils.StringUtils;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.train;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
@@ -1,7 +1,7 @@
package com.ruoyi.web.controller.train;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
@@ -1,26 +1,15 @@
package com.ruoyi.web.core.config;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.ruoyi.common.config.RuoYiConfig;
import io.swagger.annotations.ApiOperation;
import io.swagger.models.auth.In;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
/**
* Swagger2的接口配置
@@ -34,92 +23,42 @@ public class SwaggerConfig
@Autowired
private RuoYiConfig ruoyiConfig;
/** 是否开启swagger */
@Value("${swagger.enabled}")
private boolean enabled;
/** 设置请求的统一前缀 */
@Value("${swagger.pathMapping}")
private String pathMapping;
/**
* 创建API
* 自定义的 OpenAPI 对象
*/
@Bean
public Docket createRestApi()
public OpenAPI customOpenApi()
{
return new Docket(DocumentationType.OAS_30)
// 是否启用Swagger
.enable(enabled)
// 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
.apiInfo(apiInfo())
// 设置哪些接口暴露给Swagger展示
.select()
// 扫描所有有注解的api,用这种方式更灵活
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
// 扫描指定包中的swagger注解
// .apis(RequestHandlerSelectors.basePackage("com.ruoyi.project.tool.swagger"))
// 扫描所有 .apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
/* 设置安全模式,swagger可以设置访问token */
.securitySchemes(securitySchemes())
.securityContexts(securityContexts())
.pathMapping(pathMapping);
return new OpenAPI().components(new Components()
// 设置认证的请求头
.addSecuritySchemes("apikey", securityScheme()))
.addSecurityItem(new SecurityRequirement().addList("apikey"))
.info(getApiInfo());
}
/**
* 安全模式,这里指定token通过Authorization头请求头传递
*/
private List<SecurityScheme> securitySchemes()
@Bean
public SecurityScheme securityScheme()
{
List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));
return apiKeyList;
}
/**
* 安全上下文
*/
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;
return new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.name("Authorization")
.in(SecurityScheme.In.HEADER)
.scheme("Bearer");
}
/**
* 添加摘要信息
*/
private ApiInfo apiInfo()
public Info getApiInfo()
{
// 用ApiInfoBuilder进行定制
return new ApiInfoBuilder()
// 设置标题
.title("标题:若依管理系统_接口文档")
// 描述
.description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")
// 作者信息
.contact(new Contact(ruoyiConfig.getName(), null, null))
// 版本
.version("版本号:" + ruoyiConfig.getVersion())
.build();
return new Info()
// 设置标题
.title("标题:若依管理系统_接口文档")
// 描述
.description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")
// 作者信息
.contact(new Contact().name(ruoyiConfig.getName()))
// 版本
.version("版本号:" + ruoyiConfig.getVersion());
}
}
@@ -1,15 +1,5 @@
package com.ruoyi.web.service.train;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.exception.ServiceException;
@@ -19,14 +9,15 @@ import com.ruoyi.system.service.ISysRoleService;
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.train.domain.TrainStudent;
import com.ruoyi.train.domain.TrainTeacher;
import com.ruoyi.train.domain.vo.BindableUserVO;
import com.ruoyi.train.domain.vo.StudentAdminVO;
import com.ruoyi.train.domain.vo.StudentDetailVO;
import com.ruoyi.train.domain.vo.StudentItemVO;
import com.ruoyi.train.domain.vo.TeacherAdminVO;
import com.ruoyi.train.domain.vo.*;
import com.ruoyi.train.mapper.TrainStudentMapper;
import com.ruoyi.train.mapper.TrainTeacherMapper;
import com.ruoyi.train.service.ITrainingIdentityService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.stream.Collectors;
/**
* 管理员端业务身份账号绑定编排服务。
@@ -2,41 +2,26 @@
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
#driverClassName: com.mysql.cj.jdbc.Driver
# KingbaseES 使用 com.kingbase8.Driver;切回 MySQL 使用 com.mysql.cj.jdbc.Driver
driverClassName: com.kingbase8.Driver
druid:
# 主库数据源
master:
#url: jdbc:mysql://192.168.43.110:3306/gy_gb?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
# username: root
# password: rootdev
# 192.168.125.228
#url: jdbc:mysql://192.168.125.228:3306/judge?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
#url: jdbc:mysql://127.0.0.1:3306/s_builder?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
#url: jdbc:mysql://120.53.244.187:3306/sict_builder?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
#url: jdbc:mysql://83.102.67.232:3306/judge?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
#url: jdbc:mysql://127.0.0.1:3306/s_builder_kj?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
# Environment driven production-safe database config (defaults to shared trial training database):
url: ${SPRING_DATASOURCE_DRUID_MASTER_URL:jdbc:kingbase8://192.168.139.218:4321/training?currentSchema=public,sys_catalog&useUnicode=true&characterEncoding=utf8}
username: ${SPRING_DATASOURCE_DRUID_MASTER_USERNAME:kingbase}
password: ${SPRING_DATASOURCE_DRUID_MASTER_PASSWORD:sict123456}
connectionInitSqls: ["SET search_path TO public, sys_catalog"]
# prod env kingbase:
#url: jdbc:kingbase8://localhost:54321/graph?currentSchema=public&useUnicode=true&characterEncoding=utf8
#username: graph
#password: psw.db7898
#password: Psw.s10@7898
#driver-class-name: com.kingbase8.Driver
#password: Ldtc@123123!
url: ${SPRING_DATASOURCE_DRUID_MASTER_URL:jdbc:kingbase8://${DB_HOST:127.0.0.1}:${DB_PORT:54321}/${DB_NAME:railwaysystem_fusion}?useUnicode=true&characterEncoding=utf8}
username: ${SPRING_DATASOURCE_DRUID_MASTER_USERNAME:${QA_DB_USER:fusion_app}}
password: ${SPRING_DATASOURCE_DRUID_MASTER_PASSWORD:${FUSION_QA_APP_PASSWORD:}}
# 多模式透明路由与系统视图隔离
connectionInitSqls:
- set search_path to iotlocks, public, pg_catalog, sys_catalog
# 从库数据源
# slave:
# # 从数据源开关/默认关闭
# enabled: false
# url: bolt://192.168.125.228:7687/
# username: neo4j
# password: Sipsys12345,
slave:
# 从数据源开关/默认关闭
enabled: false
url:
username:
password:
connectionInitSqls:
- set search_path to public, pg_catalog, sys_catalog
# 初始连接数
initialSize: 5
# 最小连接池数量
@@ -55,10 +40,10 @@ spring:
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
# 配置检测连接是否有效(SELECT 1 为 MySQL 与 KingbaseES 通用写法)
validationQuery: SELECT 1
testWhileIdle: true
testOnBorrow: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
@@ -78,5 +63,7 @@ spring:
slow-sql-millis: 1000
merge-sql: true
wall:
# 若 Druid 无法自动识别 KingbaseES,取消下一行注释显式指定数据库类型
# db-type: kingbase
config:
multi-statement-allow: true
@@ -3,7 +3,7 @@ ruoyi:
# 名称
name: RuoYi
# 版本
version: 3.9.1
version: 3.9.2
# 版权年份
copyrightYear: 2026
# 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
@@ -16,7 +16,7 @@ ruoyi:
# 开发环境配置
server:
# 服务器的HTTP端口,默认为8080
port: 8090
port: 8096
servlet:
# 应用的访问路径
context-path: /
@@ -60,44 +60,47 @@ spring:
max-file-size: 10MB
# 设置总上传的文件大小
max-request-size: 20MB
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
# 服务模块
devtools:
restart:
# 热部署开关
enabled: true
# redis 配置
redis:
# 地址
host: localhost
# 端口,默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
data:
# redis 配置
redis:
# 地址
host: localhost
# 端口,默认为6379
port: 6379
# 数据库索引 (独立 QA 测试隔离使用 14 库)
database: 14
# 密码
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 学员身份证安全配置只接受外部注入;仓库内不得保存实际密钥。
# 培训与门禁业务配置
train:
# QA 环境业务桥接引擎配置:显式使用自主 iot 门锁授权桥接
lock:
auth-engine: ${TRAIN_LOCK_AUTH_ENGINE:iot}
student:
identity:
encryption-key: ${TRAIN_STUDENT_IDENTITY_ENCRYPTION_KEY:}
hash-key: ${TRAIN_STUDENT_IDENTITY_HASH_KEY:}
mock:
device:
# 模拟设备打卡接口开关 (生产环境与公共配置必须默认关闭,仅允许在测试环境显式开启)
enabled: ${TRAIN_MOCK_DEVICE_ENABLED:false}
# token配置
token:
@@ -119,16 +122,24 @@ mybatis:
# PageHelper分页插件
pagehelper:
helperDialect: mysql
# 方言由 PageHelper 根据数据源自动识别(已内置 MySQL 与 KingbaseES)
# 如需强制指定,MySQL 填写 mysql,KingbaseES 填写 kingbase
supportMethodsArguments: true
params: count=countSql
# Swagger配置
swagger:
# 是否开启swagger
enabled: true
# 请求前缀
pathMapping: /dev-api
# Springdoc配置
springdoc:
api-docs:
path: /v3/api-docs
swagger-ui:
enabled: true
path: /swagger-ui.html
tags-sorter: alpha
group-configs:
- group: 'default'
display-name: '测试模块'
paths-to-match: '/**'
packages-to-scan: com.ruoyi.web.controller.tool
# 防盗链配置
referer:
@@ -146,3 +157,65 @@ xss:
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
# IOT设备接入(Mock 模式,拦截外部真实硬件)
iot:
# 人脸特征提取服务 (Mock 拦截,绝不向外部真实接口发请求)
face:
endpoint: mock://face-feature-extract
mock: true
timeout: 3000
# 第三方开放接口:关闭时接口返回 404;启用后调用方需带请求头 X-API-Key
openapi:
enabled: true
api-key: qa-fusion-test-api-key
path-prefix: /openapi/iot
mqtt:
# 彻底禁用真实 MQTT 连接,启用 Mock 替身模式
enabled: false
mock: true
# 真实硬件下行控制写入总开关(硬性安全门禁,默认为 false,严禁向真实硬件发控制指令)
real-device-write-enabled: false
# 真实设备 SN 白名单(归档配置默认值为空列表,联调时由环境配置或控制台注入)
device-whitelist: []
# 历史指令防重放保护(禁止向真实硬件重放系统启动前遗留的历史待发送指令)
replay-protection-enabled: true
# 定时补偿任务开关
retry-task-enabled: true
broker-url: ${IOT_MQTT_BROKER_URL:tcp://127.0.0.1:1883}
# 客户端ID,同一服务器下必须唯一;为空时自动生成
client-id:
username: ${IOT_MQTT_USERNAME:admin}
password: ${IOT_MQTT_PASSWORD:}
# false 时服务端保留离线消息,需配合固定 client-id
clean-session: true
connection-timeout: 30
keep-alive-interval: 60
# 断线自动重连
automatic-reconnect: true
max-reconnect-delay: 30000
# 连接健康检查周期(毫秒),0 表示不做定时重连
health-check-interval: 30000
# 发布消息等待服务端确认时间(毫秒),0 表示不等待
publish-timeout: 3000
# 消息持久化目录,为空使用内存
persistence-directory:
default-qos: 1
default-retained: false
# 只订阅上行主题(下行由平台发布,不消费自己发出的指令)
topics:
- topic: ydlink/+/thing/onoff_line # 网关上下线
qos: 1
- topic: ydlink/+/thing/property/set_response # 属性设置响应
qos: 1
- topic: ydlink/+/thing/action/execute_response # 远程开关锁响应
qos: 1
- topic: ydlink/+/thing/data/batch_report # 授权下发结果
qos: 1
- topic: ydlink/+/thing/event/trigger # 开门记录上报
qos: 1
# 遗嘱消息:无法区分主动断开与异常掉线,门禁场景建议关闭(离线由 GatewayOfflineCheckTask 判定)
# will:
# topic: ydlink/status
# payload: offline
# qos: 1
# retained: true
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日志存放路径 -->
<property name="log.path" value="./logs" />
<property name="log.path" value="C:/Users/zfjia/work/projects/ruoyi/logs" />
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
@@ -262,9 +262,9 @@ public class RbacHttpAndAuthorizationSemanticsTest
{
// 模拟 Spring Security 权限不足异常
AccessDeniedException ex = new AccessDeniedException("Access is denied");
javax.servlet.http.HttpServletRequest request = (javax.servlet.http.HttpServletRequest) Proxy.newProxyInstance(
jakarta.servlet.http.HttpServletRequest request = (jakarta.servlet.http.HttpServletRequest) Proxy.newProxyInstance(
getClass().getClassLoader(),
new Class<?>[]{javax.servlet.http.HttpServletRequest.class},
new Class<?>[]{jakarta.servlet.http.HttpServletRequest.class},
(proxy, method, args) -> {
if ("getRequestURI".equals(method.getName())) return "/train/me/teacher";
return null;
+21 -4
View File
@@ -5,8 +5,20 @@
<parent>
<artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.9.1</version>
<version>3.9.2</version>
</parent>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>9</source>
<target>9</target>
</configuration>
</plugin>
</plugins>
</build>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-common</artifactId>
@@ -55,7 +67,7 @@
<!-- JSON工具类 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
@@ -95,6 +107,11 @@
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- pool 对象池 -->
<dependency>
<groupId>org.apache.commons</groupId>
@@ -109,8 +126,8 @@
<!-- servlet包 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
</dependency>
</dependencies>
@@ -16,15 +16,25 @@ import java.lang.annotation.Target;
@Documented
public @interface DataScope
{
/**
* 用户表的别名
*/
public String userAlias() default "";
/**
* 部门表的别名
*/
public String deptAlias() default "";
/**
* 用户表的别名
* 用户字段名
*/
public String userAlias() default "";
public String userField() default "user_id";
/**
* 部门字段名
*/
public String deptField() default "dept_id";
/**
* 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@ss获取,多个权限用逗号分隔开来
@@ -56,6 +56,7 @@ public @interface Excel
/**
* BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN
*/
@SuppressWarnings("deprecation")
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.Target;
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.enums.DesensitizedType;
@@ -15,7 +15,7 @@ import com.ruoyi.common.enums.DesensitizedType;
* @author ruoyi
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Target({ ElementType.FIELD, ElementType.METHOD })
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveJsonSerializer.class)
public @interface Sensitive
@@ -1,13 +1,13 @@
package com.ruoyi.common.config.serializer;
import java.io.IOException;
import java.util.Objects;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.BeanProperty;
import tools.jackson.databind.DatabindException;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ValueSerializer;
import tools.jackson.databind.ser.std.StdSerializer;
import com.ruoyi.common.annotation.Sensitive;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.enums.DesensitizedType;
@@ -18,14 +18,26 @@ import com.ruoyi.common.utils.SecurityUtils;
*
* @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
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));
}
@@ -36,16 +48,14 @@ public class SensitiveJsonSerializer extends JsonSerializer<String> implements C
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException
public ValueSerializer<?> createContextual(SerializationContext ctxt, BeanProperty property) throws DatabindException
{
Sensitive annotation = property.getAnnotation(Sensitive.class);
if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass()))
{
this.desensitizedType = annotation.desensitizedType();
return this;
return new SensitiveJsonSerializer(annotation.desensitizedType());
}
return prov.findValueSerializer(property.getType(), property);
return ctxt.findValueSerializer(property.getType());
}
/**
@@ -170,4 +170,35 @@ public class Constants
*/
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" };
/**
* 部门相关常量
*/
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,8 +31,11 @@ public class GenConstants
/** 上级菜单名称字段 */
public static final String PARENT_MENU_NAME = "parentMenuName";
/** 生成详情页开关 */
public static final String GEN_VIEW = "genView";
/** 数据库字符串类型 */
public static final String[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2", "character varying" };
public static final String[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2" };
/** 数据库文本类型 */
public static final String[] COLUMNTYPE_TEXT = { "tinytext", "text", "mediumtext", "longtext" };
@@ -42,7 +45,7 @@ public class GenConstants
/** 数据库数字类型 */
public static final String[] COLUMNTYPE_NUMBER = { "tinyint", "smallint", "mediumint", "int", "number", "integer",
"bit", "bigint", "float", "double", "decimal", "numeric" };
"bit", "bigint", "float", "double", "decimal" };
/** 页面不需要编辑字段 */
public static final String[] COLUMNNAME_NOT_EDIT = { "id", "create_by", "create_time", "del_flag" };
@@ -2,10 +2,10 @@ package com.ruoyi.common.core.domain.entity;
import java.util.ArrayList;
import java.util.List;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.core.domain.BaseEntity;
@@ -1,7 +1,7 @@
package com.ruoyi.common.core.domain.entity;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
@@ -1,8 +1,8 @@
package com.ruoyi.common.core.domain.entity;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
@@ -2,9 +2,9 @@ package com.ruoyi.common.core.domain.entity;
import java.util.ArrayList;
import java.util.List;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.core.domain.BaseEntity;
@@ -1,9 +1,9 @@
package com.ruoyi.common.core.domain.entity;
import java.util.Set;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
@@ -2,9 +2,10 @@ package com.ruoyi.common.core.domain.entity;
import java.util.Date;
import java.util.List;
import javax.validation.constraints.*;
import jakarta.validation.constraints.*;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.annotation.Excel.ColumnType;
@@ -69,6 +70,7 @@ public class SysUser extends BaseEntity
private String loginIp;
/** 最后登录时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT)
private Date loginDate;
@@ -1,11 +1,15 @@
package com.ruoyi.common.core.domain.model;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.alibaba.fastjson2.annotation.JSONField;
import com.ruoyi.common.core.domain.entity.SysUser;
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
@JSONField(serialize = false)
public Collection<? extends GrantedAuthority> getAuthorities()
{
return null;
if (permissions == null || permissions.isEmpty())
{
return Collections.emptyList();
}
return permissions.stream()
.filter(Objects::nonNull)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
}
@@ -1,5 +1,6 @@
package com.ruoyi.common.core.redis;
import java.time.Duration;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
@@ -46,7 +47,7 @@ public class RedisCache
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
redisTemplate.opsForValue().set(key, value, Duration.of(timeout, timeUnit.toChronoUnit()));
}
/**
@@ -71,7 +72,7 @@ public class RedisCache
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
return redisTemplate.expire(key, Duration.of(timeout, unit.toChronoUnit()));
}
/**
@@ -2,7 +2,7 @@ package com.ruoyi.common.enums;
import java.util.HashMap;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
/**
* 请求方式
@@ -3,14 +3,14 @@ package com.ruoyi.common.filter;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* 防盗链过滤器
@@ -1,13 +1,13 @@
package com.ruoyi.common.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import com.ruoyi.common.utils.StringUtils;
@@ -4,11 +4,11 @@ import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import com.ruoyi.common.utils.http.HttpHelper;
import com.ruoyi.common.constant.Constants;
@@ -3,14 +3,14 @@ package com.ruoyi.common.filter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.enums.HttpMethod;
@@ -2,10 +2,10 @@ package com.ruoyi.common.filter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@@ -16,6 +16,7 @@ import org.apache.commons.lang3.time.DateFormatUtils;
*
* @author ruoyi
*/
@SuppressWarnings("deprecation")
public class DateUtils extends org.apache.commons.lang3.time.DateUtils
{
public static String YYYY = "yyyy";
@@ -0,0 +1,77 @@
package com.ruoyi.common.utils;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.util.Locale;
import javax.sql.DataSource;
/**
* 数据库方言工具
* <p>
* 统一的 databaseId 识别入口,供 MyBatis 方言适配与代码生成模板使用。
*
* @author ruoyi
*/
public class DbDialectUtils
{
/** MySQL 方言标识 */
public static final String MYSQL = "mysql";
/** KingbaseES(人大金仓)方言标识 */
public static final String KINGBASE = "kingbase";
private static final String URL_KEY_MYSQL = "jdbc:mysql";
private static final String URL_KEY_MARIADB = "jdbc:mariadb";
private static final String URL_KEY_KINGBASE = "jdbc:kingbase";
private DbDialectUtils()
{
}
/**
* 获取数据源对应的方言标识
*
* @param dataSource 数据源
* @return databaseId,无法识别时返回 {@code null}
*/
public static String getDatabaseId(DataSource dataSource)
{
if (dataSource == null)
{
return null;
}
try (Connection connection = dataSource.getConnection())
{
DatabaseMetaData metaData = connection.getMetaData();
return resolveDatabaseId(metaData.getURL(), metaData.getDatabaseProductName());
}
catch (Exception e)
{
return null;
}
}
/**
* 根据 JDBC 地址与数据库产品名称识别方言
*
* @param url JDBC 连接地址
* @param productName 数据库产品名称或驱动类名
* @return databaseId,未识别时返回 {@code null}
*/
public static String resolveDatabaseId(String url, String productName)
{
String lowerUrl = StringUtils.isNotEmpty(url) ? url.toLowerCase(Locale.ROOT) : "";
String lowerProduct = StringUtils.isNotEmpty(productName) ? productName.toLowerCase(Locale.ROOT) : "";
if (lowerUrl.startsWith(URL_KEY_KINGBASE) || lowerProduct.contains(KINGBASE))
{
return KINGBASE;
}
if (lowerUrl.startsWith(URL_KEY_MYSQL) || lowerUrl.startsWith(URL_KEY_MARIADB) || lowerProduct.contains(MYSQL))
{
return MYSQL;
}
return null;
}
}
@@ -7,10 +7,10 @@ import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@@ -6,6 +6,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.Strings;
import org.springframework.util.AntPathMatcher;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.text.StrFormatter;
@@ -15,6 +16,7 @@ import com.ruoyi.common.core.text.StrFormatter;
*
* @author ruoyi
*/
@SuppressWarnings("deprecation")
public class StringUtils extends org.apache.commons.lang3.StringUtils
{
/** 空字符串 */
@@ -432,12 +434,24 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
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
*
* @param collection 给定的集合
* @param array 给定的数组
* @return boolean 结果
* @return 结果
*/
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;
}
/**
* 检查是否包含要搜索的字符串,忽略大小写
*
* @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;
}
/**
* 删除最后一个字符串
*
* @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
*
@@ -1,9 +1,9 @@
package com.ruoyi.common.utils.bean;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Validator;
/**
* bean对象属性验证
@@ -9,8 +9,8 @@ import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
@@ -30,7 +30,7 @@ public class MimeTypeUtils
// 图片
"bmp", "gif", "jpg", "jpeg", "png",
// word excel powerpoint
"doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
"doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt",
// 压缩文件
"rar", "zip", "gz", "bz2",
// 视频格式
@@ -5,7 +5,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import javax.servlet.ServletRequest;
import jakarta.servlet.ServletRequest;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -2,7 +2,7 @@ package com.ruoyi.common.utils.ip;
import java.net.InetAddress;
import java.net.UnknownHostException;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
@@ -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.UUID;
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.RegExUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
@@ -106,6 +106,11 @@ public class ExcelUtil<T>
*/
public Map<String, String> sysDictMap = new HashMap<String, String>();
/**
* 单元格样式缓存
*/
private Map<String, CellStyle> cellStyleCache = new HashMap<String, CellStyle>();
/**
* Excel sheet最大行数,默认65536
*/
@@ -418,7 +423,7 @@ public class ExcelUtil<T>
Object val = this.getCellValue(row, entry.getKey());
// 如果不存在实例则新建.
entity = (entity == null ? clazz.newInstance() : entity);
entity = (entity == null ? clazz.getDeclaredConstructor().newInstance() : entity);
// 从map中得到对应列的field.
Field field = (Field) entry.getValue()[0];
Excel attr = (Excel) entry.getValue()[1];
@@ -580,6 +585,117 @@ public class ExcelUtil<T>
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表单
*
@@ -894,13 +1010,13 @@ public class ExcelUtil<T>
style.cloneStyleFrom(styles.get("data"));
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setFillForegroundColor(excel.headerBackgroundColor().index);
style.setFillForegroundColor(excel.headerBackgroundColor().getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
Font headerFont = wb.createFont();
headerFont.setFontName("Arial");
headerFont.setFontHeightInPoints((short) 10);
headerFont.setBold(true);
headerFont.setColor(excel.headerColor().index);
headerFont.setColor(excel.headerColor().getIndex());
style.setFont(headerFont);
// 设置表格头单元格文本形式
DataFormat dataFormat = wb.createDataFormat();
@@ -972,7 +1088,7 @@ public class ExcelUtil<T>
Font dataFont = wb.createFont();
dataFont.setFontName("Arial");
dataFont.setFontHeightInPoints((short) 10);
dataFont.setColor(excel.color().index);
dataFont.setColor(excel.color().getIndex());
style.setFont(dataFont);
if (ColumnType.TEXT == excel.cellType())
{
@@ -1124,6 +1240,7 @@ public class ExcelUtil<T>
/**
* 添加单元格
*/
@SuppressWarnings("deprecation")
public Cell addCell(Excel attr, Row row, T vo, Field field, int column)
{
Cell cell = null;
@@ -1201,9 +1318,16 @@ public class ExcelUtil<T>
*/
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;
}
@@ -1425,7 +1549,7 @@ public class ExcelUtil<T>
{
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 });
value = formatMethod.invoke(instance, value, excel.args(), cell, this.wb);
}
@@ -7,13 +7,13 @@ import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Date;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.poi.ss.usermodel.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
/**
* 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
@@ -310,6 +310,7 @@ public class ReflectUtils
/**
* 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
*/
@SuppressWarnings("deprecation")
public static void makeAccessible(Method method)
{
if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
@@ -322,6 +323,7 @@ public class ReflectUtils
/**
* 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
*/
@SuppressWarnings("deprecation")
public static void makeAccessible(Field field)
{
if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers())
@@ -13,7 +13,7 @@ public class SqlUtil
/**
* 定义常用的 sql关键字
*/
public static String SQL_REGEX = "\u000B|and |extractvalue|updatexml|sleep|information_schema|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()";
public static String SQL_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;
}
String normalizedValue = value.replaceAll("\\p{Z}|\\s", "");
String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|");
for (String sqlKeyword : sqlKeywords)
{
if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1)
if (StringUtils.indexOfIgnoreCase(normalizedValue, sqlKeyword) > -1)
{
throw new UtilException("参数存在SQL注入风险");
throw new UtilException("请求参数包含敏感关键词'" + sqlKeyword + "',可能存在安全风险");
}
}
}
@@ -1,7 +1,7 @@
package com.ruoyi.common.xss;
import javax.validation.Constraint;
import javax.validation.Payload;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -1,8 +1,8 @@
package com.ruoyi.common.xss;
import com.ruoyi.common.utils.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+4 -4
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.9.1</version>
<version>3.9.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -20,19 +20,19 @@
<!-- SpringBoot Web容器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<!-- SpringBoot 拦截器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<artifactId>spring-boot-starter-aspectj</artifactId>
</dependency>
<!-- 阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<artifactId>druid-spring-boot-4-starter</artifactId>
</dependency>
<!-- 验证码 -->
@@ -7,6 +7,7 @@ import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import com.ruoyi.common.annotation.DataScope;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.domain.entity.SysRole;
@@ -26,31 +27,6 @@ import com.ruoyi.framework.security.context.PermissionContextHolder;
@Component
public class DataScopeAspect
{
/**
* 全部数据权限
*/
public static final String DATA_SCOPE_ALL = "1";
/**
* 自定数据权限
*/
public static final String DATA_SCOPE_CUSTOM = "2";
/**
* 部门数据权限
*/
public static final String DATA_SCOPE_DEPT = "3";
/**
* 部门及以下数据权限
*/
public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
/**
* 仅本人数据权限
*/
public static final String DATA_SCOPE_SELF = "5";
/**
* 数据权限过滤关键字
*/
@@ -74,7 +50,7 @@ public class DataScopeAspect
if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
{
String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext());
dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), controllerDataScope.userAlias(), permission);
dataScopeFilter(joinPoint, currentUser, controllerDataScope.userAlias(), controllerDataScope.deptAlias(), controllerDataScope.userField(), controllerDataScope.deptField(), permission);
}
}
}
@@ -88,13 +64,13 @@ public class DataScopeAspect
* @param userAlias 用户别名
* @param permission 权限字符
*/
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission)
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String userAlias, String deptAlias, String userField, String deptField, String permission)
{
StringBuilder sqlString = new StringBuilder();
List<String> conditions = new ArrayList<String>();
List<String> scopeCustomIds = new ArrayList<String>();
user.getRoles().forEach(role -> {
if (DATA_SCOPE_CUSTOM.equals(role.getDataScope()) && StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL) && (StringUtils.isEmpty(permission) || 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()));
}
@@ -111,42 +87,42 @@ public class DataScopeAspect
{
continue;
}
if (DATA_SCOPE_ALL.equals(dataScope))
if (Constants.Dept.DATA_SCOPE_ALL.equals(dataScope))
{
sqlString = new StringBuilder();
conditions.add(dataScope);
break;
}
else if (DATA_SCOPE_CUSTOM.equals(dataScope))
else if (Constants.Dept.DATA_SCOPE_CUSTOM.equals(dataScope))
{
if (scopeCustomIds.size() > 1)
{
// 多个自定数据权限使用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
{
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 concat(',', ancestors, ',') like concat('%,', {}, ',%') )", deptAlias, deptField, user.getDeptId(), user.getDeptId()));
}
else if (DATA_SCOPE_SELF.equals(dataScope))
else if (Constants.Dept.DATA_SCOPE_SELF.equals(dataScope))
{
if (StringUtils.isNotBlank(userAlias))
{
sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
sqlString.append(StringUtils.format(" OR {}.{} = {} ", userAlias, userField, user.getUserId()));
}
else
{
// 数据权限为仅本人且没有userAlias别名不查询任何数据
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
sqlString.append(StringUtils.format(" OR {}.{} = 0 ", deptAlias, deptField));
}
}
conditions.add(dataScope);
@@ -155,7 +131,7 @@ public class DataScopeAspect
// 角色都不包含传递过来的权限字符,这个时候sqlString也会为空,所以要限制一下,不查询任何数据
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()))
@@ -2,8 +2,8 @@ package com.ruoyi.framework.aspectj;
import java.util.Collection;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
@@ -1,9 +1,6 @@
package com.ruoyi.framework.config;
import java.util.TimeZone;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@@ -19,12 +16,4 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy;
@MapperScan("com.ruoyi.**.mapper")
public class ApplicationConfig
{
/**
* 时区配置
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization()
{
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault());
}
}
@@ -3,11 +3,6 @@ package com.ruoyi.framework.config;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.sql.DataSource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -16,13 +11,18 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.spring.boot4.autoconfigure.DruidDataSourceBuilder;
import com.alibaba.druid.spring.boot4.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.util.Utils;
import com.ruoyi.common.enums.DataSourceType;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.config.properties.DruidProperties;
import com.ruoyi.framework.datasource.DynamicDataSource;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
/**
* druid 配置多数据源
@@ -96,7 +96,7 @@ public class DruidConfig
Filter filter = new Filter()
{
@Override
public void init(javax.servlet.FilterConfig filterConfig) throws ServletException
public void init(jakarta.servlet.FilterConfig filterConfig) throws ServletException
{
}
@Override
@@ -2,7 +2,7 @@ package com.ruoyi.framework.config;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.DispatcherType;
import jakarta.servlet.DispatcherType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
@@ -7,6 +7,7 @@ import java.util.HashSet;
import java.util.List;
import javax.sql.DataSource;
import org.apache.ibatis.io.VFS;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
@@ -114,7 +115,7 @@ public class MyBatisConfig
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception
public SqlSessionFactory sqlSessionFactory(DataSource dataSource, DatabaseIdProvider databaseIdProvider) throws Exception
{
String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
String mapperLocations = env.getProperty("mybatis.mapperLocations");
@@ -124,6 +125,8 @@ public class MyBatisConfig
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
// 多数据库方言适配:MySQL / KingbaseES
sessionFactory.setDatabaseIdProvider(databaseIdProvider);
sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
@@ -14,6 +14,7 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
*
* @author ruoyi
*/
@SuppressWarnings("deprecation")
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
@@ -5,12 +5,10 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@@ -30,12 +28,6 @@ import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;
@Configuration
public class SecurityConfig
{
/**
* 自定义用户认证逻辑
*/
@Autowired
private UserDetailsService userDetailsService;
/**
* 认证失败处理类
*/
@@ -66,17 +58,14 @@ public class SecurityConfig
@Autowired
private PermitAllUrlProperties permitAllUrl;
/**
* 身份验证实现
*/
@Bean
public AuthenticationManager authenticationManager()
{
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
return new ProviderManager(daoAuthenticationProvider);
}
/**
* 身份验证实现
*/
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception
{
return authenticationConfiguration.getAuthenticationManager();
}
/**
* anyRequest | 匹配所有请求路径
@@ -109,12 +98,12 @@ public class SecurityConfig
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 注解标记允许匿名访问的url
.authorizeHttpRequests((requests) -> {
permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
permitAllUrl.getUrls().forEach(url -> requests.requestMatchers(url).permitAll());
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
requests.antMatchers("/login", "/register", "/captchaImage").permitAll()
requests.requestMatchers("/login", "/register", "/captchaImage").permitAll()
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
.requestMatchers(HttpMethod.GET, "/", "/*.html", "/**.html", "/**.css", "/**.js", "/profile/**").permitAll()
.requestMatchers("/swagger-ui.html", "/v3/api-docs/**", "/swagger-ui/**", "/druid/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
})
@@ -1,6 +1,6 @@
package com.ruoyi.framework.config;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import com.ruoyi.common.utils.ServletUtils;
@@ -49,8 +49,8 @@ public class ThreadPoolConfig
protected ScheduledExecutorService scheduledExecutorService()
{
return new ScheduledThreadPoolExecutor(corePoolSize,
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
new ThreadPoolExecutor.CallerRunsPolicy())
BasicThreadFactory.builder().namingPattern("schedule-pool-%d").daemon(true).build(),
new ThreadPoolExecutor.CallerRunsPolicy())
{
@Override
protected void afterExecute(Runnable r, Throwable t)
@@ -34,10 +34,11 @@ public class PermitAllUrlProperties implements InitializingBean, ApplicationCont
public String ASTERISK = "*";
@SuppressWarnings("deprecation")
@Override
public void afterPropertiesSet()
{
RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
RequestMappingHandlerMapping mapping = applicationContext.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
map.keySet().forEach(info -> {
@@ -45,12 +46,12 @@ public class PermitAllUrlProperties implements InitializingBean, ApplicationCont
// 获取方法上边的注解 替代path variable 为 *
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))));
// 获取类上边的注解, 替代path variable 为 *
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))));
});
}
@@ -0,0 +1,44 @@
package com.ruoyi.framework.datasource;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import com.ruoyi.common.utils.DbDialectUtils;
/**
* MyBatis 数据库方言(databaseId)识别器
* <p>
* Mapper 中的 &lt;select databaseId="kingbase"&gt;、&lt;choose&gt;&lt;when test="_databaseId == 'kingbase'"&gt;
* 依赖该标识选择对应数据库的 SQL,因此返回值必须稳定且可预测:这里以 JDBC 地址与产品名称为准,
* 同时支持 MySQL 与 KingbaseES。
*
* @author ruoyi
*/
@Component
public class RuoYiDatabaseIdProvider implements DatabaseIdProvider
{
@Autowired
private Environment env;
@Override
public String getDatabaseId(DataSource dataSource)
{
String databaseId = DbDialectUtils.getDatabaseId(dataSource);
if (databaseId != null)
{
return databaseId;
}
// 数据库不可用时退化为读取配置的地址,保证服务仍可启动
return DbDialectUtils.resolveDatabaseId(env.getProperty("spring.datasource.druid.master.url"),
env.getProperty("spring.datasource.druid.master.driver-class-name", ""));
}
@Override
public void setProperties(Properties properties)
{
// 不需要外部参数
}
}
@@ -1,8 +1,8 @@
package com.ruoyi.framework.interceptor;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
@@ -3,7 +3,7 @@ package com.ruoyi.framework.interceptor.impl;
import java.util.HashMap;
import java.util.Map;
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.Value;
import org.springframework.stereotype.Component;
@@ -3,7 +3,7 @@ package com.ruoyi.framework.manager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
import jakarta.annotation.PreDestroy;
/**
* 确保应用退出时能关闭后台线程
@@ -1,10 +1,10 @@
package com.ruoyi.framework.security.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -2,8 +2,8 @@ package com.ruoyi.framework.security.handle;
import java.io.IOException;
import java.io.Serializable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
@@ -1,9 +1,9 @@
package com.ruoyi.framework.security.handle;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
@@ -1,6 +1,6 @@
package com.ruoyi.framework.web.exception;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
@@ -1,6 +1,6 @@
package com.ruoyi.framework.web.service;
import javax.annotation.Resource;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
@@ -1,9 +1,9 @@
package com.ruoyi.framework.web.service;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -22,6 +22,7 @@ import com.ruoyi.common.utils.uuid.IdUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import jakarta.servlet.http.HttpServletRequest;
/**
* token验证处理
@@ -229,4 +230,41 @@ public class TokenService
{
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>
<artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.9.1</version>
<version>3.9.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -32,7 +32,7 @@
<!-- 阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<artifactId>druid-spring-boot-4-starter</artifactId>
</dependency>
</dependencies>
@@ -5,7 +5,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils;
@@ -112,12 +113,12 @@ public class GenController extends BaseController
@PreAuthorize("@ss.hasPermi('tool:gen:import')")
@Log(title = "代码生成", businessType = BusinessType.IMPORT)
@PostMapping("/importTable")
public AjaxResult importTableSave(String tables)
public AjaxResult importTableSave(@RequestParam("tables") String tables, @RequestParam("tplWebType") String tplWebType)
{
String[] tableNames = Convert.toStrArray(tables);
// 查询表信息
List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames);
genTableService.importGenTable(tableList, SecurityUtils.getUsername());
genTableService.importGenTable(tableList, tplWebType, SecurityUtils.getUsername());
return success();
}
@@ -127,7 +128,7 @@ public class GenController extends BaseController
@PreAuthorize("@ss.hasRole('admin')")
@Log(title = "创建表", businessType = BusinessType.OTHER)
@PostMapping("/createTable")
public AjaxResult createTableSave(String sql)
public AjaxResult createTableSave(@RequestParam("sql") String sql, @RequestParam("tplWebType") String tplWebType)
{
try
{
@@ -148,7 +149,7 @@ public class GenController extends BaseController
}
List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames.toArray(new String[tableNames.size()]));
String operName = SecurityUtils.getUsername();
genTableService.importGenTable(tableList, operName);
genTableService.importGenTable(tableList, tplWebType, operName);
return AjaxResult.success();
}
catch (Exception e)
@@ -1,8 +1,8 @@
package com.ruoyi.generator.domain;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import org.apache.commons.lang3.ArrayUtils;
import com.ruoyi.common.constant.GenConstants;
import com.ruoyi.common.core.domain.BaseEntity;
@@ -41,7 +41,7 @@ public class GenTable extends BaseEntity
/** 使用的模板(crud单表操作 tree树表操作 sub主子表操作) */
private String tplCategory;
/** 前端类型(element-ui模版 element-plus模版) */
/** 前端类型(element-ui模版 element-plus模版 element-plus-typescript模版) */
private String tplWebType;
/** 生成包路径 */
@@ -64,6 +64,9 @@ public class GenTable extends BaseEntity
@NotBlank(message = "作者不能为空")
private String functionAuthor;
/** 表单布局(单列 双列 三列) */
private Integer formColNum;
/** 生成代码方式(0zip压缩包 1自定义路径) */
private String genType;
@@ -98,6 +101,9 @@ public class GenTable extends BaseEntity
/** 上级菜单名称字段 */
private String parentMenuName;
/** 是否生成详情页 */
private boolean isView;
public Long getTableId()
{
return tableId;
@@ -228,6 +234,16 @@ public class GenTable extends BaseEntity
this.functionAuthor = functionAuthor;
}
public Integer getFormColNum()
{
return formColNum;
}
public void setFormColNum(Integer formColNum)
{
this.formColNum = formColNum;
}
public String getGenType()
{
return genType;
@@ -338,6 +354,16 @@ public class GenTable extends BaseEntity
this.parentMenuName = parentMenuName;
}
public boolean isView()
{
return isView;
}
public void setView(boolean isView)
{
this.isView = isView;
}
public boolean isSub()
{
return isSub(this.tplCategory);
@@ -1,6 +1,6 @@
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.utils.StringUtils;
@@ -4,20 +4,15 @@ import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.sql.DataSource;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.velocity.Template;
@@ -53,9 +48,6 @@ public class GenTableServiceImpl implements IGenTableService
{
private static final Logger log = LoggerFactory.getLogger(GenTableServiceImpl.class);
@Autowired
private DataSource dataSource;
@Autowired
private GenTableMapper genTableMapper;
@@ -178,19 +170,20 @@ public class GenTableServiceImpl implements IGenTableService
*/
@Override
@Transactional
public void importGenTable(List<GenTable> tableList, String operName)
public void importGenTable(List<GenTable> tableList, String tplWebType, String operName)
{
try
{
for (GenTable table : tableList)
{
String tableName = table.getTableName();
table.setTplWebType(tplWebType);
GenUtils.initTable(table, operName);
int row = genTableMapper.insertGenTable(table);
if (row > 0)
{
// 保存列信息
List<GenTableColumn> genTableColumns = selectDbTableColumnsByName(tableName);
List<GenTableColumn> genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName);
for (GenTableColumn column : genTableColumns)
{
GenUtils.initColumnField(column, table);
@@ -226,7 +219,7 @@ public class GenTableServiceImpl implements IGenTableService
VelocityContext context = VelocityUtils.prepareContext(table);
// 获取模板列表
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType());
List<String> templates = VelocityUtils.getTemplateList(table);
for (String template : templates)
{
// 渲染模板
@@ -247,11 +240,7 @@ public class GenTableServiceImpl implements IGenTableService
@Override
public byte[] downloadCode(String tableName)
{
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(outputStream);
generatorCode(tableName, zip);
IOUtils.closeQuietly(zip);
return outputStream.toByteArray();
return downloadCode(new String[] { tableName });
}
/**
@@ -274,10 +263,10 @@ public class GenTableServiceImpl implements IGenTableService
VelocityContext context = VelocityUtils.prepareContext(table);
// 获取模板列表
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType());
List<String> templates = VelocityUtils.getTemplateList(table);
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();
@@ -309,7 +298,7 @@ public class GenTableServiceImpl implements IGenTableService
List<GenTableColumn> tableColumns = table.getColumns();
Map<String, GenTableColumn> tableColumnMap = tableColumns.stream().collect(Collectors.toMap(GenTableColumn::getColumnName, Function.identity()));
List<GenTableColumn> dbTableColumns = selectDbTableColumnsByName(tableName);
List<GenTableColumn> dbTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName);
if (StringUtils.isEmpty(dbTableColumns))
{
throw new ServiceException("同步数据失败,原表结构不存在");
@@ -362,9 +351,14 @@ public class GenTableServiceImpl implements IGenTableService
{
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(outputStream);
Map<String, StringBuffer> typeFiles = new HashMap<>();
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);
return outputStream.toByteArray();
@@ -373,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);
@@ -387,29 +381,56 @@ public class GenTableServiceImpl implements IGenTableService
VelocityContext context = VelocityUtils.prepareContext(table);
// 获取模板列表
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType());
List<String> templates = VelocityUtils.getTemplateList(table);
for (String template : templates)
{
// 渲染模板
StringWriter sw = new StringWriter();
Template tpl = Velocity.getTemplate(template, Constants.UTF8);
tpl.merge(context, sw);
try
String fileName = VelocityUtils.getFileName(template, table);
// index-bak.ts 模版,追加内容
if (fileName.contains("index-bak.ts"))
{
// 添加到zip
zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table)));
IOUtils.write(sw.toString(), zip, Constants.UTF8);
IOUtils.closeQuietly(sw);
zip.flush();
zip.closeEntry();
if (!typeFiles.containsKey(fileName))
{
typeFiles.put(fileName, new StringBuffer(sw.toString()));
}
else
{
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);
}
}
/**
* 修改保存参数校验
*
@@ -513,12 +534,14 @@ public class GenTableServiceImpl implements IGenTableService
String treeName = paramsObj.getString(GenConstants.TREE_NAME);
Long parentMenuId = paramsObj.getLongValue(GenConstants.PARENT_MENU_ID);
String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME);
boolean isView = paramsObj.getBooleanValue(GenConstants.GEN_VIEW);
genTable.setTreeCode(treeCode);
genTable.setTreeParentCode(treeParentCode);
genTable.setTreeName(treeName);
genTable.setParentMenuId(parentMenuId);
genTable.setParentMenuName(parentMenuName);
genTable.setView(isView);
}
}
@@ -538,54 +561,4 @@ public class GenTableServiceImpl implements IGenTableService
}
return genPath + File.separator + VelocityUtils.getFileName(template, table);
}
/**
* 查询表列信息并补充自增列元数据
*/
private List<GenTableColumn> selectDbTableColumnsByName(String tableName)
{
List<GenTableColumn> dbTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName);
fillColumnIncrementByJdbc(tableName, dbTableColumns);
return dbTableColumns;
}
/**
* 通过 JDBC DatabaseMetaData 补充自增/Identity列元数据,兼顾跨数据库方言
*/
private void fillColumnIncrementByJdbc(String tableName, List<GenTableColumn> columns)
{
if (StringUtils.isEmpty(columns))
{
return;
}
try (Connection conn = dataSource.getConnection())
{
DatabaseMetaData meta = conn.getMetaData();
String catalog = conn.getCatalog();
String schema = conn.getSchema();
Set<String> autoCols = new HashSet<>();
try (ResultSet rs = meta.getColumns(catalog, schema, tableName, null))
{
while (rs.next())
{
if ("YES".equalsIgnoreCase(rs.getString("IS_AUTOINCREMENT")))
{
autoCols.add(rs.getString("COLUMN_NAME"));
}
}
}
for (GenTableColumn column : columns)
{
if (autoCols.contains(column.getColumnName()))
{
column.setIsIncrement("1");
}
}
}
catch (SQLException e)
{
log.error("通过 JDBC DatabaseMetaData 读取自增列失败,已中止 Generator 元数据初始化", e);
throw new ServiceException("读取数据库自增列元数据失败,请检查数据库连接与元数据权限");
}
}
}
Loaded 100 of 468 files, more files were not shown because too many files have changed in this diff. Show more