Compare commits
8 Commits
master
...
master-jdk
Author | SHA1 | Date |
---|---|---|
|
d7567e669c | |
|
4ea26c7e81 | |
|
d963f8910e | |
|
b2d288d584 | |
|
4dd63b77cf | |
|
cb2e9e044f | |
|
f638f90afc | |
|
86801517d1 |
|
@ -51,4 +51,3 @@ rebel.xml
|
|||
application-my.yaml
|
||||
|
||||
/yudao-ui-app/unpackage/
|
||||
**/.DS_Store
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 21 KiB |
71
README.md
71
README.md
|
@ -1,7 +1,7 @@
|
|||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/Spring%20Boot-2.7.18-blue.svg" alt="Downloads">
|
||||
<img src="https://img.shields.io/badge/Spring%20Boot-3.4.1-blue.svg" alt="Downloads">
|
||||
<img src="https://img.shields.io/badge/Vue-3.2-blue.svg" alt="Downloads">
|
||||
<img src="https://img.shields.io/github/license/YunaiV/ruoyi-vue-pro"/>
|
||||
<img src="https://img.shields.io/github/license/YunaiV/ruoyi-vue-pro" alt="Downloads" />
|
||||
</p>
|
||||
|
||||
**严肃声明:现在、未来都不会有商业版本,所有代码全部开源!!**
|
||||
|
@ -149,45 +149,22 @@
|
|||
|
||||
### 工作流程
|
||||
|
||||

|
||||
| | 功能 | 描述 |
|
||||
|----|-------|-----------------------------------------|
|
||||
| 🚀 | 流程模型 | 配置工作流的流程模型,支持 BPMN 和仿钉钉/飞书设计器 |
|
||||
| 🚀 | 流程表单 | 拖动表单元素生成相应的工作流表单,覆盖 Element UI 所有的表单组件 |
|
||||
| 🚀 | 用户分组 | 自定义用户分组,可用于工作流的审批分组 |
|
||||
| 🚀 | 我的流程 | 查看我发起的工作流程,支持新建、取消流程等操作,高亮流程图、审批时间线 |
|
||||
| 🚀 | 待办任务 | 查看自己【未】审批的工作任务,支持通过、不通过、转派、委派、退回、加减签等操作 |
|
||||
| 🚀 | 已办任务 | 查看自己【已】审批的工作任务,支持流程预测,展示未来审批人信息 |
|
||||
| 🚀 | OA 请假 | 作为业务自定义接入工作流的使用示例,只需创建请求对应的工作流程,即可进行审批 |
|
||||
|
||||
基于 Flowable 构建,可支持信创(国产)数据库,满足中国特色流程操作:
|
||||

|
||||
|
||||
| BPMN 设计器 | 钉钉/飞书设计器 |
|
||||
|------------------------------|--------------------------------|
|
||||
|  |  |
|
||||
|
||||
> 历经头部企业生产验证,工作流引擎须标配仿钉钉/飞书 + BPMN 双设计器!!!
|
||||
>
|
||||
> 前者支持轻量配置简单流程,后者实现复杂场景深度编排
|
||||
|
||||
| 功能列表 | 功能描述 | 是否完成 |
|
||||
|------------|-------------------------------------------------------------------------------------|------|
|
||||
| SIMPLE 设计器 | 仿钉钉/飞书设计器,支持拖拽搭建表单流程,10 分钟快速完成审批流程配置 | ✅ |
|
||||
| BPMN 设计器 | 基于 BPMN 标准开发,适配复杂业务场景,满足多层级审批及流程自动化需求 | ✅ |
|
||||
| 会签 | 同一个审批节点设置多个人(如 A、B、C 三人,三人会同时收到待办任务),需全部同意之后,审批才可到下一审批节点 | ✅ |
|
||||
| 或签 | 同一个审批节点设置多个人,任意一个人处理后,就能进入下一个节点 | ✅ |
|
||||
| 依次审批 | (顺序会签)同一个审批节点设置多个人(如 A、B、C 三人),三人按顺序依次收到待办,即 A 先审批,A 提交后 B 才能审批,需全部同意之后,审批才可到下一审批节点 | ✅ |
|
||||
| 抄送 | 将审批结果通知给抄送人,同一个审批默认排重,不重复抄送给同一人 | ✅ |
|
||||
| 驳回 | (退回)将审批重置发送给某节点,重新审批。可驳回至发起人、上一节点、任意节点 | ✅ |
|
||||
| 转办 | A 转给其 B 审批,B 审批后,进入下一节点 | ✅ |
|
||||
| 委派 | A 转给其 B 审批,B 审批后,转给 A,A 继续审批后进入下一节点 | ✅ |
|
||||
| 加签 | 允许当前审批人根据需要,自行增加当前节点的审批人,支持向前、向后加签 | ✅ |
|
||||
| 减签 | (取消加签)在当前审批人操作之前,减少审批人 | ✅ |
|
||||
| 撤销 | (取消流程)流程发起人,可以对流程进行撤销处理 | ✅ |
|
||||
| 终止 | 系统管理员,在任意节点终止流程实例 | ✅ |
|
||||
| 表单权限 | 支持拖拉拽配置表单,每个审批节点可配置只读、编辑、隐藏权限 | ✅ |
|
||||
| 超时审批 | 配置超时审批时间,超时后自动触发审批通过、不通过、驳回等操作 | ✅ |
|
||||
| 自动提醒 | 配置提醒时间,到达时间后自动触发短信、邮箱、站内信等通知提醒,支持自定义重复提醒频次 | ✅ |
|
||||
| 父子流程 | 主流程设置子流程节点,子流程节点会自动触发子流程。子流程结束后,主流程才会执行(继续往下下执行),支持同步子流程、异步子流程 | ✅ |
|
||||
| 条件分支 | (排它分支)用于在流程中实现决策,即根据条件选择一个分支执行 | ✅ |
|
||||
| 并行分支 | 允许将流程分成多条分支,不进行条件判断,所有分支都会执行 | ✅ |
|
||||
| 包容分支 | (条件分支 + 并行分支的结合体)允许基于条件选择多条分支执行,但如果没有任何一个分支满足条件,则可以选择默认分支 | ✅ |
|
||||
| 路由分支 | 根据条件选择一个分支执行(重定向到指定配置节点),也可以选择默认分支执行(继续往下执行) | ✅ |
|
||||
| 触发节点 | 执行到该节点,触发 HTTP 请求、HTTP 回调、更新数据、删除数据等 | ✅ |
|
||||
| 延迟节点 | 执行到该节点,审批等待一段时间再执行,支持固定时长、固定日期等 | ✅ |
|
||||
| 拓展设置 | 流程前置/后置通知,节点(任务)前置、后置通知,流程报表,自动审批去重,自定流程编号、标题、摘要,流程报表等 | ✅ |
|
||||
|
||||
### 支付系统
|
||||
|
||||
| | 功能 | 描述 |
|
||||
|
@ -308,26 +285,26 @@
|
|||
|
||||
| 框架 | 说明 | 版本 | 学习指南 |
|
||||
|---------------------------------------------------------------------------------------------|------------------|----------------|----------------------------------------------------------------|
|
||||
| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.7.18 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
|
||||
| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 3.4.1 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
|
||||
| [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 / 8.0+ | |
|
||||
| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.23 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
|
||||
| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.7 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
|
||||
| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.6.1 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
|
||||
| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 4.3.1 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
|
||||
| [Redis](https://redis.io/) | key-value 数据库 | 5.0 / 6.0 /7.0 | |
|
||||
| [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.32.0 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) |
|
||||
| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.3.24 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |
|
||||
| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.7.11 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
|
||||
| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.5 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |
|
||||
| [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 6.8.0 | [文档](https://doc.iocoder.cn/bpm/) |
|
||||
| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 6.1.10 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |
|
||||
| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 6.3.1 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
|
||||
| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 8.0.1 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |
|
||||
| [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 7.0.0 | [文档](https://doc.iocoder.cn/bpm/) |
|
||||
| [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) |
|
||||
| [Springdoc](https://springdoc.org/) | Swagger 文档 | 1.7.0 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) |
|
||||
| [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 8.12.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) |
|
||||
| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 2.7.10 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) |
|
||||
| [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.13.5 | |
|
||||
| [Springdoc](https://springdoc.org/) | Swagger 文档 | 2.3.0 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) |
|
||||
| [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 9.0.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) |
|
||||
| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 3.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) |
|
||||
| [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.17.1 | |
|
||||
| [MapStruct](https://mapstruct.org/) | Java Bean 转换 | 1.6.3 | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao) |
|
||||
| [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码 | 1.18.34 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) |
|
||||
| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.8.2 | - |
|
||||
| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 4.8.0 | - |
|
||||
| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.10.1 | - |
|
||||
| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 5.7.0 | - |
|
||||
|
||||
## 🐷 演示图
|
||||
|
||||
|
|
16
pom.xml
16
pom.xml
|
@ -23,9 +23,8 @@
|
|||
<!-- <module>yudao-module-mall</module>-->
|
||||
<!-- <module>yudao-module-crm</module>-->
|
||||
<!-- <module>yudao-module-erp</module>-->
|
||||
<!-- <module>yudao-module-iot</module>-->
|
||||
<!-- AI 大模型的开启,请参考 https://doc.iocoder.cn/ai/build/ 文档,对 JDK 版本要要求! -->
|
||||
<!-- <module>yudao-module-ai</module>-->
|
||||
<!-- <module>yudao-module-iot</module>-->
|
||||
</modules>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
|
@ -33,17 +32,17 @@
|
|||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<properties>
|
||||
<revision>2.6.0-jdk8-SNAPSHOT</revision>
|
||||
<revision>2.4.1-SNAPSHOT</revision>
|
||||
<!-- Maven 相关 -->
|
||||
<java.version>1.8</java.version>
|
||||
<java.version>17</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<maven-surefire-plugin.version>3.2.2</maven-surefire-plugin.version>
|
||||
<maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
|
||||
<maven-compiler-plugin.version>3.13.0</maven-compiler-plugin.version>
|
||||
<flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
|
||||
<!-- 看看咋放到 bom 里 -->
|
||||
<lombok.version>1.18.36</lombok.version>
|
||||
<spring.boot.version>2.7.18</spring.boot.version>
|
||||
<spring.boot.version>3.4.1</spring.boot.version>
|
||||
<mapstruct.version>1.6.3</mapstruct.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
@ -94,6 +93,11 @@
|
|||
<version>${mapstruct.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
<!-- 编译参数写在 arg 内,解决 Spring Boot 3.2 的 Parameter Name Discovery 问题 -->
|
||||
<debug>false</debug>
|
||||
<compilerArgs>
|
||||
<arg>-parameters</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
|
|
|
@ -15,7 +15,7 @@ services:
|
|||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-123456}
|
||||
volumes:
|
||||
- mysql:/var/lib/mysql/
|
||||
- ../../sql/mysql/ruoyi-vue-pro.sql:/docker-entrypoint-initdb.d/ruoyi-vue-pro.sql:ro
|
||||
- ./sql/mysql/ruoyi-vue-pro.sql:/docker-entrypoint-initdb.d/ruoyi-vue-pro.sql:ro
|
||||
|
||||
redis:
|
||||
container_name: yudao-redis
|
||||
|
@ -50,7 +50,7 @@ services:
|
|||
--spring.datasource.dynamic.datasource.slave.url=${SLAVE_DATASOURCE_URL:-jdbc:mysql://yudao-mysql:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true}
|
||||
--spring.datasource.dynamic.datasource.slave.username=${SLAVE_DATASOURCE_USERNAME:-root}
|
||||
--spring.datasource.dynamic.datasource.slave.password=${SLAVE_DATASOURCE_PASSWORD:-123456}
|
||||
--spring.redis.host=${REDIS_HOST:-yudao-redis}
|
||||
--spring.data.redis.host=${REDIS_HOST:-yudao-redis}
|
||||
depends_on:
|
||||
- mysql
|
||||
- redis
|
||||
|
|
|
@ -679,7 +679,6 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
|
|||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (86, 0, '病假', '1', 'bpm_oa_leave_type', 0, 'primary', '', NULL, '1', '2021-09-21 22:35:28', '1', '2022-02-16 10:00:41', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (87, 1, '事假', '2', 'bpm_oa_leave_type', 0, 'info', '', NULL, '1', '2021-09-21 22:36:11', '1', '2022-02-16 10:00:49', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (88, 2, '婚假', '3', 'bpm_oa_leave_type', 0, 'warning', '', NULL, '1', '2021-09-21 22:36:38', '1', '2022-02-16 10:00:53', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (112, 0, '微信 Wap 网站支付', 'wx_wap', 'pay_channel_code', 0, 'success', '', '微信 Wap 网站支付', '1', '2023-07-19 20:08:06', '1', '2023-07-19 20:09:08', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (113, 1, '微信公众号支付', 'wx_pub', 'pay_channel_code', 0, 'success', '', '微信公众号支付', '1', '2021-12-03 10:40:24', '1', '2023-07-19 20:08:47', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (114, 2, '微信小程序支付', 'wx_lite', 'pay_channel_code', 0, 'success', '', '微信小程序支付', '1', '2021-12-03 10:41:06', '1', '2023-07-19 20:08:50', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (115, 3, '微信 App 支付', 'wx_app', 'pay_channel_code', 0, 'success', '', '微信 App 支付', '1', '2021-12-03 10:41:20', '1', '2023-07-19 20:08:56', '0');
|
||||
|
|
|
@ -826,7 +826,6 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
|
|||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (86, 0, '病假', '1', 'bpm_oa_leave_type', 0, 'primary', '', NULL, '1', '2021-09-21 22:35:28', '1', '2022-02-16 10:00:41', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (87, 1, '事假', '2', 'bpm_oa_leave_type', 0, 'info', '', NULL, '1', '2021-09-21 22:36:11', '1', '2022-02-16 10:00:49', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (88, 2, '婚假', '3', 'bpm_oa_leave_type', 0, 'warning', '', NULL, '1', '2021-09-21 22:36:38', '1', '2022-02-16 10:00:53', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (112, 0, '微信 Wap 网站支付', 'wx_wap', 'pay_channel_code', 0, 'success', '', '微信 Wap 网站支付', '1', '2023-07-19 20:08:06', '1', '2023-07-19 20:09:08', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (113, 1, '微信公众号支付', 'wx_pub', 'pay_channel_code', 0, 'success', '', '微信公众号支付', '1', '2021-12-03 10:40:24', '1', '2023-07-19 20:08:47', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (114, 2, '微信小程序支付', 'wx_lite', 'pay_channel_code', 0, 'success', '', '微信小程序支付', '1', '2021-12-03 10:41:06', '1', '2023-07-19 20:08:50', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (115, 3, '微信 App 支付', 'wx_app', 'pay_channel_code', 0, 'success', '', '微信 App 支付', '1', '2021-12-03 10:41:20', '1', '2023-07-19 20:08:56', '0');
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -826,7 +826,6 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
|
|||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (86, 0, '病假', '1', 'bpm_oa_leave_type', 0, 'primary', '', NULL, '1', '2021-09-21 22:35:28', '1', '2022-02-16 10:00:41', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (87, 1, '事假', '2', 'bpm_oa_leave_type', 0, 'info', '', NULL, '1', '2021-09-21 22:36:11', '1', '2022-02-16 10:00:49', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (88, 2, '婚假', '3', 'bpm_oa_leave_type', 0, 'warning', '', NULL, '1', '2021-09-21 22:36:38', '1', '2022-02-16 10:00:53', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (112, 0, '微信 Wap 网站支付', 'wx_wap', 'pay_channel_code', 0, 'success', '', '微信 Wap 网站支付', '1', '2023-07-19 20:08:06', '1', '2023-07-19 20:09:08', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (113, 1, '微信公众号支付', 'wx_pub', 'pay_channel_code', 0, 'success', '', '微信公众号支付', '1', '2021-12-03 10:40:24', '1', '2023-07-19 20:08:47', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (114, 2, '微信小程序支付', 'wx_lite', 'pay_channel_code', 0, 'success', '', '微信小程序支付', '1', '2021-12-03 10:41:06', '1', '2023-07-19 20:08:50', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (115, 3, '微信 App 支付', 'wx_app', 'pay_channel_code', 0, 'success', '', '微信 App 支付', '1', '2021-12-03 10:41:20', '1', '2023-07-19 20:08:56', '0');
|
||||
|
|
|
@ -778,7 +778,6 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
|
|||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (86, 0, '病假', '1', 'bpm_oa_leave_type', 0, 'primary', '', NULL, '1', to_date('2021-09-21 22:35:28', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2022-02-16 10:00:41', 'SYYYY-MM-DD HH24:MI:SS'), '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (87, 1, '事假', '2', 'bpm_oa_leave_type', 0, 'info', '', NULL, '1', to_date('2021-09-21 22:36:11', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2022-02-16 10:00:49', 'SYYYY-MM-DD HH24:MI:SS'), '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (88, 2, '婚假', '3', 'bpm_oa_leave_type', 0, 'warning', '', NULL, '1', to_date('2021-09-21 22:36:38', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2022-02-16 10:00:53', 'SYYYY-MM-DD HH24:MI:SS'), '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (112, 0, '微信 Wap 网站支付', 'wx_wap', 'pay_channel_code', 0, 'success', '', '微信 Wap 网站支付', '1', '2023-07-19 20:08:06', '1', '2023-07-19 20:09:08', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (113, 1, '微信公众号支付', 'wx_pub', 'pay_channel_code', 0, 'success', '', '微信公众号支付', '1', to_date('2021-12-03 10:40:24', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2023-07-19 20:08:47', 'SYYYY-MM-DD HH24:MI:SS'), '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (114, 2, '微信小程序支付', 'wx_lite', 'pay_channel_code', 0, 'success', '', '微信小程序支付', '1', to_date('2021-12-03 10:41:06', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2023-07-19 20:08:50', 'SYYYY-MM-DD HH24:MI:SS'), '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (115, 3, '微信 App 支付', 'wx_app', 'pay_channel_code', 0, 'success', '', '微信 App 支付', '1', to_date('2021-12-03 10:41:20', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2023-07-19 20:08:56', 'SYYYY-MM-DD HH24:MI:SS'), '0');
|
||||
|
|
|
@ -826,7 +826,6 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
|
|||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (86, 0, '病假', '1', 'bpm_oa_leave_type', 0, 'primary', '', NULL, '1', '2021-09-21 22:35:28', '1', '2022-02-16 10:00:41', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (87, 1, '事假', '2', 'bpm_oa_leave_type', 0, 'info', '', NULL, '1', '2021-09-21 22:36:11', '1', '2022-02-16 10:00:49', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (88, 2, '婚假', '3', 'bpm_oa_leave_type', 0, 'warning', '', NULL, '1', '2021-09-21 22:36:38', '1', '2022-02-16 10:00:53', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (112, 0, '微信 Wap 网站支付', 'wx_wap', 'pay_channel_code', 0, 'success', '', '微信 Wap 网站支付', '1', '2023-07-19 20:08:06', '1', '2023-07-19 20:09:08', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (113, 1, '微信公众号支付', 'wx_pub', 'pay_channel_code', 0, 'success', '', '微信公众号支付', '1', '2021-12-03 10:40:24', '1', '2023-07-19 20:08:47', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (114, 2, '微信小程序支付', 'wx_lite', 'pay_channel_code', 0, 'success', '', '微信小程序支付', '1', '2021-12-03 10:41:06', '1', '2023-07-19 20:08:50', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (115, 3, '微信 App 支付', 'wx_app', 'pay_channel_code', 0, 'success', '', '微信 App 支付', '1', '2021-12-03 10:41:20', '1', '2023-07-19 20:08:56', '0');
|
||||
|
|
|
@ -2224,8 +2224,6 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
|
|||
GO
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (88, 2, N'婚假', N'3', N'bpm_oa_leave_type', 0, N'warning', N'', NULL, N'1', N'2021-09-21 22:36:38', N'1', N'2022-02-16 10:00:53', N'0')
|
||||
GO
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (112, 0, N'微信 Wap 网站支付', N'wx_wap', N'pay_channel_code', 0, N'success', N'', N'微信 Wap 网站支付', N'1', N'2023-07-19 20:08:06', N'1', N'2023-07-19 20:09:08', N'0');
|
||||
GO
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (113, 1, N'微信公众号支付', N'wx_pub', N'pay_channel_code', 0, N'success', N'', N'微信公众号支付', N'1', N'2021-12-03 10:40:24', N'1', N'2023-07-19 20:08:47', N'0')
|
||||
GO
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (114, 2, N'微信小程序支付', N'wx_lite', N'pay_channel_code', 0, N'success', N'', N'微信小程序支付', N'1', N'2021-12-03 10:41:06', N'1', N'2023-07-19 20:08:50', N'0')
|
||||
|
|
|
@ -14,20 +14,17 @@
|
|||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<properties>
|
||||
<revision>2.6.0-jdk8-SNAPSHOT</revision>
|
||||
<revision>2.4.1-SNAPSHOT</revision>
|
||||
<flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
|
||||
<!-- 统一依赖管理 -->
|
||||
<spring.framework.version>5.3.39</spring.framework.version>
|
||||
<spring.security.version>5.8.14</spring.security.version>
|
||||
<spring.boot.version>2.7.18</spring.boot.version>
|
||||
<spring.boot.version>3.4.1</spring.boot.version>
|
||||
<!-- Web 相关 -->
|
||||
<springdoc.version>1.8.0</springdoc.version>
|
||||
<knife4j.version>4.5.0</knife4j.version>
|
||||
<servlet.versoin>2.5</servlet.versoin>
|
||||
<springdoc.version>2.7.0</springdoc.version>
|
||||
<knife4j.version>4.6.0</knife4j.version>
|
||||
<!-- DB 相关 -->
|
||||
<druid.version>1.2.24</druid.version>
|
||||
<mybatis.version>3.5.19</mybatis.version>
|
||||
<mybatis-plus.version>3.5.10.1</mybatis-plus.version>
|
||||
<mybatis.version>3.5.17</mybatis.version>
|
||||
<mybatis-plus.version>3.5.9</mybatis-plus.version>
|
||||
<dynamic-datasource.version>4.3.1</dynamic-datasource.version>
|
||||
<mybatis-plus-join.version>1.4.13</mybatis-plus-join.version>
|
||||
<easy-trans.version>3.0.6</easy-trans.version>
|
||||
|
@ -35,51 +32,46 @@
|
|||
<dm8.jdbc.version>8.1.3.140</dm8.jdbc.version>
|
||||
<kingbase.jdbc.version>8.6.0</kingbase.jdbc.version>
|
||||
<opengauss.jdbc.version>5.1.0</opengauss.jdbc.version>
|
||||
<taos.version>3.3.3</taos.version>
|
||||
<!-- 消息队列 -->
|
||||
<rocketmq-spring.version>2.3.2</rocketmq-spring.version>
|
||||
<rocketmq-spring.version>2.3.1</rocketmq-spring.version>
|
||||
<!-- 服务保障相关 -->
|
||||
<lock4j.version>2.2.7</lock4j.version>
|
||||
<!-- 监控相关 -->
|
||||
<skywalking.version>8.12.0</skywalking.version>
|
||||
<spring-boot-admin.version>2.7.15</spring-boot-admin.version>
|
||||
<skywalking.version>9.0.0</skywalking.version>
|
||||
<spring-boot-admin.version>3.4.1</spring-boot-admin.version>
|
||||
<opentracing.version>0.33.0</opentracing.version>
|
||||
<!-- Test 测试相关 -->
|
||||
<podam.version>7.2.11.RELEASE</podam.version> <!-- Spring Boot 2.X 最多使用 7.2.11 版本 -->
|
||||
<podam.version>8.0.2.RELEASE</podam.version>
|
||||
<jedis-mock.version>1.1.8</jedis-mock.version>
|
||||
<mockito-inline.version>4.11.0</mockito-inline.version>
|
||||
<mockito-inline.version>5.2.0</mockito-inline.version>
|
||||
<!-- Bpm 工作流相关 -->
|
||||
<flowable.version>6.8.0</flowable.version>
|
||||
<flowable.version>7.0.1</flowable.version>
|
||||
<!-- 工具类相关 -->
|
||||
<anji-plus-captcha.version>1.4.0</anji-plus-captcha.version>
|
||||
<captcha-plus.version>2.0.3</captcha-plus.version>
|
||||
<jsoup.version>1.18.3</jsoup.version>
|
||||
<lombok.version>1.18.36</lombok.version>
|
||||
<mapstruct.version>1.6.3</mapstruct.version>
|
||||
<hutool.version>5.8.35</hutool.version>
|
||||
<easyexcel.version>4.0.3</easyexcel.version>
|
||||
<velocity.version>2.4</velocity.version> <!-- JDK8 不能从 2.4 升级到 2.4.1,会报包不存在!!!! -->
|
||||
<hutool-5.version>5.8.35</hutool-5.version>
|
||||
<hutool-6.version>6.0.0-M19</hutool-6.version>
|
||||
<easyexcel.verion>4.0.3</easyexcel.verion>
|
||||
<velocity.version>2.4.1</velocity.version>
|
||||
<fastjson.version>1.2.83</fastjson.version>
|
||||
<guava.version>33.4.8-jre</guava.version>
|
||||
<guava.version>33.4.0-jre</guava.version>
|
||||
<transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>
|
||||
<commons-net.version>3.11.1</commons-net.version>
|
||||
<jsch.version>0.1.55</jsch.version>
|
||||
<tika-core.version>2.9.3</tika-core.version> <!-- JDK8 不能从 2.9.3 升级到 3.X,会报 JDK8 不支持 -->
|
||||
<tika-core.version>2.9.2</tika-core.version>
|
||||
<ip2region.version>2.7.0</ip2region.version>
|
||||
<bizlog-sdk.version>3.0.6</bizlog-sdk.version>
|
||||
<netty.version>4.1.116.Final</netty.version>
|
||||
<mqtt.version>1.2.5</mqtt.version>
|
||||
<pf4j-spring.version>0.9.0</pf4j-spring.version>
|
||||
<vertx.version>4.5.13</vertx.version>
|
||||
<!-- 三方云服务相关 -->
|
||||
<commons-io.version>2.17.0</commons-io.version>
|
||||
<commons-compress.version>1.27.1</commons-compress.version>
|
||||
<awssdk.version>2.30.14</awssdk.version>
|
||||
<justauth.version>1.16.7</justauth.version>
|
||||
<justauth-starter.version>1.4.0</justauth-starter.version>
|
||||
<jimureport.version>1.9.4</jimureport.version>
|
||||
<weixin-java.version>4.7.5.B</weixin-java.version>
|
||||
<!-- 专属于 JDK8 安全漏洞升级 -->
|
||||
<logback.version>1.2.13</logback.version> <!-- 无法使用 1.3.X 版本,启动会报错 -->
|
||||
<aws-java-sdk-s3.version>1.12.777</aws-java-sdk-s3.version>
|
||||
<justauth.version>2.0.5</justauth.version>
|
||||
<jimureport.version>1.8.1</jimureport.version>
|
||||
<weixin-java.version>4.6.0</weixin-java.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
|
@ -92,20 +84,6 @@
|
|||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-framework-bom</artifactId> <!-- JDK8 版本独有:保证 Spring Framework 尽量高 -->
|
||||
<version>${spring.framework.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-bom</artifactId> <!-- JDK8 版本独有:保证 Spring Security 尽量高 -->
|
||||
<version>${spring.security.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
|
@ -170,13 +148,13 @@
|
|||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
|
||||
<groupId>com.github.xingfudeshi</groupId> <!-- TODO 芋艿:https://github.com/xiaoymin/knife4j/issues/874 -->
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
<version>${knife4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-ui</artifactId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
|
||||
<version>${springdoc.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
@ -189,23 +167,22 @@
|
|||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
<artifactId>druid-spring-boot-3-starter</artifactId>
|
||||
<version>${druid.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- 注意:必须声明,避免 flowable 和 mybatis-plus 引入的 mybatis 版本不一致!!! -->
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis</artifactId>
|
||||
<version>${mybatis.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-jsqlparser-4.9</artifactId>
|
||||
<artifactId>mybatis-plus-jsqlparser</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -215,7 +192,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 -->
|
||||
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId> <!-- 多数据源 -->
|
||||
<version>${dynamic-datasource.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -265,18 +242,8 @@
|
|||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.redisson</groupId>
|
||||
<!-- 使用 redisson-spring-data-27 替代,解决 Tuple NoClassDefFoundError 报错 -->
|
||||
<artifactId>redisson-spring-data-34</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-data-27</artifactId>
|
||||
<version>${redisson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.dameng</groupId>
|
||||
|
@ -296,12 +263,6 @@
|
|||
<version>${kingbase.jdbc.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.taosdata.jdbc</groupId>
|
||||
<artifactId>taos-jdbcdriver</artifactId>
|
||||
<version>${taos.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Job 定时任务相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
|
@ -315,6 +276,7 @@
|
|||
<artifactId>yudao-spring-boot-starter-mq</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.rocketmq</groupId>
|
||||
<artifactId>rocketmq-spring-boot-starter</artifactId>
|
||||
|
@ -498,13 +460,18 @@
|
|||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
<version>${hutool-5.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.dromara.hutool</groupId>
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
<version>${hutool-6.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>easyexcel</artifactId>
|
||||
<version>${easyexcel.version}</version>
|
||||
<version>${easyexcel.verion}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
|
@ -559,9 +526,9 @@
|
|||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.anji-plus</groupId>
|
||||
<artifactId>captcha-spring-boot-starter</artifactId> <!-- 验证码,一般用于登录使用 -->
|
||||
<version>${anji-plus-captcha.version}</version>
|
||||
<groupId>com.xingyuv</groupId>
|
||||
<artifactId>spring-boot-starter-captcha-plus</artifactId>
|
||||
<version>${captcha-plus.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
@ -578,22 +545,16 @@
|
|||
|
||||
<!-- 三方云服务相关 -->
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3</artifactId>
|
||||
<version>${awssdk.version}</version>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>aws-java-sdk-s3</artifactId>
|
||||
<version>${aws-java-sdk-s3.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>me.zhyd.oauth</groupId>
|
||||
<artifactId>JustAuth</artifactId> <!-- 社交登陆(例如说,个人微信、企业微信等等) -->
|
||||
<groupId>com.xingyuv</groupId>
|
||||
<artifactId>spring-boot-starter-justauth</artifactId> <!-- 社交登陆(例如说,个人微信、企业微信等等) -->
|
||||
<version>${justauth.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xkcoding.justauth</groupId>
|
||||
<artifactId>justauth-spring-boot-starter</artifactId>
|
||||
<version>${justauth-starter.version}</version>
|
||||
<exclusions>
|
||||
<!-- 移除,避免和项目里的 hutool-all 冲突 -->
|
||||
<exclusion>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
|
@ -620,69 +581,22 @@
|
|||
<!-- 积木报表-->
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.jimureport</groupId>
|
||||
<artifactId>jimureport-spring-boot-starter</artifactId>
|
||||
<version>${jimureport.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.jimureport</groupId>
|
||||
<artifactId>jimubi-spring-boot-starter</artifactId>
|
||||
<artifactId>jimureport-spring-boot3-starter-fastjson2</artifactId>
|
||||
<version>${jimureport.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.github.jsqlparser</groupId>
|
||||
<artifactId>jsqlparser</artifactId>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- PF4J -->
|
||||
<dependency>
|
||||
<groupId>org.pf4j</groupId>
|
||||
<artifactId>pf4j-spring</artifactId>
|
||||
<version>${pf4j-spring.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Vert.x -->
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-core</artifactId>
|
||||
<version>${vertx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-web</artifactId>
|
||||
<version>${vertx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-mqtt</artifactId>
|
||||
<version>${vertx.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- MQTT -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.paho</groupId>
|
||||
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
|
||||
<version>${mqtt.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 专属于 JDK8 安全漏洞升级 -->
|
||||
<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>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-ui</artifactId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,主要是 PageParam 使用到 -->
|
||||
</dependency>
|
||||
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* 针对 infra 模块的 api 包
|
||||
*/
|
||||
package cn.iocoder.yudao.framework.common.biz.infra;
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* 特殊:用于 framework 下,starter 需要调用 biz 业务模块的接口定义!
|
||||
*/
|
||||
package cn.iocoder.yudao.framework.common.biz;
|
|
@ -1,22 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.common.biz.system.dict;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.biz.system.dict.dto.DictDataRespDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 字典数据 API 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface DictDataCommonApi {
|
||||
|
||||
/**
|
||||
* 获得指定字典类型的字典数据列表
|
||||
*
|
||||
* @param dictType 字典类型
|
||||
* @return 字典数据列表
|
||||
*/
|
||||
List<DictDataRespDTO> getDictDataList(String dictType);
|
||||
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.common.biz.system.logger;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.biz.system.logger.dto.OperateLogCreateReqDTO;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
/**
|
||||
* 操作日志 API 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface OperateLogCommonApi {
|
||||
|
||||
/**
|
||||
* 创建操作日志
|
||||
*
|
||||
* @param createReqDTO 请求
|
||||
*/
|
||||
void createOperateLog(@Valid OperateLogCreateReqDTO createReqDTO);
|
||||
|
||||
/**
|
||||
* 【异步】创建操作日志
|
||||
*
|
||||
* @param createReqDTO 请求
|
||||
*/
|
||||
@Async
|
||||
default void createOperateLogAsync(OperateLogCreateReqDTO createReqDTO) {
|
||||
createOperateLog(createReqDTO);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* 针对 system 模块的 api 包
|
||||
*/
|
||||
package cn.iocoder.yudao.framework.common.biz.system;
|
|
@ -1,17 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.common.enums;
|
||||
|
||||
/**
|
||||
* RPC 相关的枚举
|
||||
*
|
||||
* 虽然放在 yudao-spring-boot-starter-rpc 会相对合适,但是每个 API 模块需要使用到,所以暂时只好放在此处
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class RpcConstants {
|
||||
|
||||
/**
|
||||
* RPC API 的前缀
|
||||
*/
|
||||
public static final String RPC_API_PREFIX = "/rpc-api";
|
||||
|
||||
}
|
|
@ -3,9 +3,9 @@ package cn.iocoder.yudao.framework.common.pojo;
|
|||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Schema(description="分页参数")
|
||||
|
|
|
@ -11,7 +11,6 @@ import java.util.function.*;
|
|||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static cn.hutool.core.convert.Convert.toCollection;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
/**
|
||||
|
@ -336,17 +335,4 @@ public class CollectionUtils {
|
|||
return list.stream().filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 LinkedHashSet
|
||||
*
|
||||
* @param <T> 元素类型
|
||||
* @param elementType 集合中元素类型
|
||||
* @param value 被转换的值
|
||||
* @return {@link LinkedHashSet}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> LinkedHashSet<T> toLinkedHashSet(Class<T> elementType, Object value) {
|
||||
return (LinkedHashSet<T>) toCollection(LinkedHashSet.class, elementType, value);
|
||||
}
|
||||
|
||||
}
|
|
@ -8,16 +8,12 @@ import cn.hutool.core.util.StrUtil;
|
|||
import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
|
||||
|
||||
import java.time.*;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.hutool.core.date.DatePattern.UTC_MS_WITH_XXX_OFFSET_PATTERN;
|
||||
import static cn.hutool.core.date.DatePattern.createFormatter;
|
||||
|
||||
/**
|
||||
* 时间工具类,用于 {@link java.time.LocalDateTime}
|
||||
*
|
||||
|
@ -30,8 +26,6 @@ public class LocalDateTimeUtils {
|
|||
*/
|
||||
public static LocalDateTime EMPTY = buildTime(1970, 1, 1);
|
||||
|
||||
public static DateTimeFormatter UTC_MS_WITH_XXX_OFFSET_FORMATTER = createFormatter(UTC_MS_WITH_XXX_OFFSET_PATTERN);
|
||||
|
||||
/**
|
||||
* 解析时间
|
||||
*
|
||||
|
|
|
@ -7,16 +7,13 @@ import cn.hutool.core.util.ReflectUtil;
|
|||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
@ -26,17 +23,6 @@ import java.util.Map;
|
|||
*/
|
||||
public class HttpUtils {
|
||||
|
||||
/**
|
||||
* 编码 URL 参数
|
||||
*
|
||||
* @param value 参数
|
||||
* @return 编码后的参数
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static String encodeUtf8(String value) {
|
||||
return URLEncoder.encode(value, StandardCharsets.UTF_8.name());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static String replaceUrlQuery(String url, String key, String value) {
|
||||
UrlBuilder builder = UrlBuilder.of(url, Charset.defaultCharset());
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
package cn.iocoder.yudao.framework.common.util.io;
|
||||
|
||||
import cn.hutool.core.io.FileTypeUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.file.FileNameUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
|
@ -58,4 +63,22 @@ public class FileUtils {
|
|||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成文件路径
|
||||
*
|
||||
* @param content 文件内容
|
||||
* @param originalName 原始文件名
|
||||
* @return path,唯一不可重复
|
||||
*/
|
||||
public static String generatePath(byte[] content, String originalName) {
|
||||
String sha256Hex = DigestUtil.sha256Hex(content);
|
||||
// 情况一:如果存在 name,则优先使用 name 的后缀
|
||||
if (StrUtil.isNotBlank(originalName)) {
|
||||
String extName = FileNameUtil.extName(originalName);
|
||||
return StrUtil.isBlank(extName) ? sha256Hex : sha256Hex + "." + extName;
|
||||
}
|
||||
// 情况二:基于 content 计算
|
||||
return sha256Hex + '.' + FileTypeUtil.getType(new ByteArrayInputStream(content));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
package cn.iocoder.yudao.framework.common.util.number;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数字的工具类,补全 {@link cn.hutool.core.util.NumberUtil} 的功能
|
||||
|
@ -22,18 +20,6 @@ public class NumberUtils {
|
|||
return StrUtil.isNotEmpty(str) ? Integer.valueOf(str) : null;
|
||||
}
|
||||
|
||||
public static boolean isAllNumber(List<String> values) {
|
||||
if (CollUtil.isEmpty(values)) {
|
||||
return false;
|
||||
}
|
||||
for (String value : values) {
|
||||
if (!NumberUtil.isNumber(value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过经纬度获取地球上两点之间的距离
|
||||
*
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
package cn.iocoder.yudao.framework.common.util.servlet;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import cn.hutool.extra.servlet.JakartaServletUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
@ -32,22 +29,7 @@ public class ServletUtils {
|
|||
@SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码
|
||||
public static void writeJSON(HttpServletResponse response, Object object) {
|
||||
String content = JsonUtils.toJsonString(object);
|
||||
ServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回附件
|
||||
*
|
||||
* @param response 响应
|
||||
* @param filename 文件名
|
||||
* @param content 附件内容
|
||||
*/
|
||||
public static void writeAttachment(HttpServletResponse response, String filename, byte[] content) throws IOException {
|
||||
// 设置 header 和 contentType
|
||||
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
|
||||
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
||||
// 输出附件
|
||||
IoUtil.write(response.getOutputStream(), false, content);
|
||||
JakartaServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -85,7 +67,7 @@ public class ServletUtils {
|
|||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
return ServletUtil.getClientIP(request);
|
||||
return JakartaServletUtil.getClientIP(request);
|
||||
}
|
||||
|
||||
public static boolean isJsonRequest(ServletRequest request) {
|
||||
|
@ -95,7 +77,7 @@ public class ServletUtils {
|
|||
public static String getBody(HttpServletRequest request) {
|
||||
// 只有在 json 请求在读取,因为只有 CacheRequestBodyFilter 才会进行缓存,支持重复读取
|
||||
if (isJsonRequest(request)) {
|
||||
return ServletUtil.getBody(request);
|
||||
return JakartaServletUtil.getBody(request);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -103,21 +85,17 @@ public class ServletUtils {
|
|||
public static byte[] getBodyBytes(HttpServletRequest request) {
|
||||
// 只有在 json 请求在读取,因为只有 CacheRequestBodyFilter 才会进行缓存,支持重复读取
|
||||
if (isJsonRequest(request)) {
|
||||
return ServletUtil.getBodyBytes(request);
|
||||
return JakartaServletUtil.getBodyBytes(request);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getClientIP(HttpServletRequest request) {
|
||||
return ServletUtil.getClientIP(request);
|
||||
return JakartaServletUtil.getClientIP(request);
|
||||
}
|
||||
|
||||
public static Map<String, String> getParamMap(HttpServletRequest request) {
|
||||
return ServletUtil.getParamMap(request);
|
||||
}
|
||||
|
||||
public static Map<String, String> getHeaderMap(HttpServletRequest request) {
|
||||
return ServletUtil.getHeaderMap(request);
|
||||
return JakartaServletUtil.getParamMap(request);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -97,26 +97,12 @@ public class SpringExpressionUtils {
|
|||
* @return 执行界面
|
||||
*/
|
||||
public static Object parseExpression(String expressionString) {
|
||||
return parseExpression(expressionString, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Bean 工厂,解析 EL 表达式的结果
|
||||
*
|
||||
* @param expressionString EL 表达式
|
||||
* @param variables 变量
|
||||
* @return 执行界面
|
||||
*/
|
||||
public static Object parseExpression(String expressionString, Map<String, Object> variables) {
|
||||
if (StrUtil.isBlank(expressionString)) {
|
||||
return null;
|
||||
}
|
||||
Expression expression = EXPRESSION_PARSER.parseExpression(expressionString);
|
||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||
context.setBeanResolver(new BeanFactoryResolver(SpringUtil.getApplicationContext()));
|
||||
if (MapUtil.isNotEmpty(variables)) {
|
||||
context.setVariables(variables);
|
||||
}
|
||||
return expression.getValue(context);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ package cn.iocoder.yudao.framework.common.util.string;
|
|||
import cn.hutool.core.text.StrPool;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
@ -78,30 +77,4 @@ public class StrUtils {
|
|||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接方法的参数
|
||||
*
|
||||
* 特殊:排除一些无法序列化的参数,如 ServletRequest、ServletResponse、MultipartFile
|
||||
*
|
||||
* @param joinPoint 连接点
|
||||
* @return 拼接后的参数
|
||||
*/
|
||||
public static String joinMethodArgs(JoinPoint joinPoint) {
|
||||
Object[] args = joinPoint.getArgs();
|
||||
if (ArrayUtil.isEmpty(args)) {
|
||||
return "";
|
||||
}
|
||||
return ArrayUtil.join(args, ",", item -> {
|
||||
if (item == null) {
|
||||
return "";
|
||||
}
|
||||
// 讨论可见:https://t.zsxq.com/XUJVk、https://t.zsxq.com/MnKcL
|
||||
String clazzName = item.getClass().getName();
|
||||
if (StrUtil.startWithAny(clazzName, "javax.servlet", "jakarta.servlet", "org.springframework.web")) {
|
||||
return "";
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@ import cn.hutool.core.collection.CollUtil;
|
|||
import cn.hutool.core.lang.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import jakarta.validation.Validation;
|
||||
import jakarta.validation.Validator;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package cn.iocoder.yudao.framework.common.validation;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import jakarta.validation.Constraint;
|
||||
import jakarta.validation.Payload;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target({
|
||||
|
|
|
@ -2,9 +2,9 @@ package cn.iocoder.yudao.framework.common.validation;
|
|||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package cn.iocoder.yudao.framework.common.validation;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.common.validation;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import jakarta.validation.Constraint;
|
||||
import jakarta.validation.Payload;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target({
|
||||
|
|
|
@ -3,8 +3,8 @@ package cn.iocoder.yudao.framework.common.validation;
|
|||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
|
||||
public class MobileValidator implements ConstraintValidator<Mobile, String> {
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.common.validation;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import jakarta.validation.Constraint;
|
||||
import jakarta.validation.Payload;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target({
|
||||
|
|
|
@ -3,8 +3,8 @@ package cn.iocoder.yudao.framework.common.validation;
|
|||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.PhoneUtil;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
|
||||
public class TelephoneValidator implements ConstraintValidator<Telephone, String> {
|
||||
|
||||
|
|
|
@ -34,6 +34,13 @@
|
|||
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 业务组件 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-system-api</artifactId> <!-- 需要使用它,进行数据权限的获取 -->
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package cn.iocoder.yudao.framework.datapermission.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi;
|
||||
import cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRule;
|
||||
import cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRuleCustomizer;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
|
@ -18,11 +18,11 @@ import java.util.List;
|
|||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass(LoginUser.class)
|
||||
@ConditionalOnBean(value = {PermissionCommonApi.class, DeptDataPermissionRuleCustomizer.class})
|
||||
@ConditionalOnBean(value = {PermissionApi.class, DeptDataPermissionRuleCustomizer.class})
|
||||
public class YudaoDeptDataPermissionAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public DeptDataPermissionRule deptDataPermissionRule(PermissionCommonApi permissionApi,
|
||||
public DeptDataPermissionRule deptDataPermissionRule(PermissionApi permissionApi,
|
||||
List<DeptDataPermissionRuleCustomizer> customizers) {
|
||||
// 创建 DeptDataPermissionRule 对象
|
||||
DeptDataPermissionRule rule = new DeptDataPermissionRule(permissionApi);
|
||||
|
|
|
@ -12,8 +12,6 @@ import net.sf.jsqlparser.schema.Table;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.skipPermissionCheck;
|
||||
|
||||
/**
|
||||
* 基于 {@link DataPermissionRule} 的数据权限处理器
|
||||
*
|
||||
|
@ -29,11 +27,6 @@ public class DataPermissionRuleHandler implements MultiDataPermissionHandler {
|
|||
|
||||
@Override
|
||||
public Expression getSqlSegment(Table table, Expression where, String mappedStatementId) {
|
||||
// 特殊:跨租户访问
|
||||
if (skipPermissionCheck()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获得 Mapper 对应的数据权限的规则
|
||||
List<DataPermissionRule> rules = ruleFactory.getDataPermissionRule(mappedStatementId);
|
||||
if (CollUtil.isEmpty(rules)) {
|
||||
|
|
|
@ -3,8 +3,6 @@ package cn.iocoder.yudao.framework.datapermission.core.rule.dept;
|
|||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi;
|
||||
import cn.iocoder.yudao.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO;
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
|
@ -13,13 +11,12 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
|||
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
|
||||
import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.expression.Alias;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.LongValue;
|
||||
import net.sf.jsqlparser.expression.NullValue;
|
||||
import net.sf.jsqlparser.expression.*;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
|
||||
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
|
||||
|
@ -62,7 +59,7 @@ public class DeptDataPermissionRule implements DataPermissionRule {
|
|||
|
||||
static final Expression EXPRESSION_NULL = new NullValue();
|
||||
|
||||
private final PermissionCommonApi permissionApi;
|
||||
private final PermissionApi permissionApi;
|
||||
|
||||
/**
|
||||
* 基于部门的表字段配置
|
||||
|
|
|
@ -32,12 +32,13 @@ public class DataPermissionUtils {
|
|||
* @param runnable 逻辑
|
||||
*/
|
||||
public static void executeIgnore(Runnable runnable) {
|
||||
addDisableDataPermission();
|
||||
DataPermission dataPermission = getDisableDataPermissionDisable();
|
||||
DataPermissionContextHolder.add(dataPermission);
|
||||
try {
|
||||
// 执行 runnable
|
||||
runnable.run();
|
||||
} finally {
|
||||
removeDataPermission();
|
||||
DataPermissionContextHolder.remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,25 +50,14 @@ public class DataPermissionUtils {
|
|||
*/
|
||||
@SneakyThrows
|
||||
public static <T> T executeIgnore(Callable<T> callable) {
|
||||
addDisableDataPermission();
|
||||
DataPermission dataPermission = getDisableDataPermissionDisable();
|
||||
DataPermissionContextHolder.add(dataPermission);
|
||||
try {
|
||||
// 执行 callable
|
||||
return callable.call();
|
||||
} finally {
|
||||
removeDataPermission();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加忽略数据权限
|
||||
*/
|
||||
public static void addDisableDataPermission(){
|
||||
DataPermission dataPermission = getDisableDataPermissionDisable();
|
||||
DataPermissionContextHolder.add(dataPermission);
|
||||
}
|
||||
|
||||
public static void removeDataPermission(){
|
||||
DataPermissionContextHolder.remove();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,13 +2,13 @@ package cn.iocoder.yudao.framework.datapermission.core.rule.dept;
|
|||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi;
|
||||
import cn.iocoder.yudao.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO;
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
|
||||
import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
|
||||
import net.sf.jsqlparser.expression.Alias;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
@ -39,7 +39,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
|
|||
private DeptDataPermissionRule rule;
|
||||
|
||||
@Mock
|
||||
private PermissionCommonApi permissionApi;
|
||||
private PermissionApi permissionApi;
|
||||
|
||||
@BeforeEach
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
|
@ -4,7 +4,6 @@ import lombok.Data;
|
|||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
@ -31,14 +30,7 @@ public class TenantProperties {
|
|||
*
|
||||
* 默认情况下,每个请求需要带上 tenant-id 的请求头。但是,部分请求是无需带上的,例如说短信回调、支付回调等 Open API!
|
||||
*/
|
||||
private Set<String> ignoreUrls = new HashSet<>();
|
||||
|
||||
/**
|
||||
* 需要忽略跨(切换)租户访问的请求
|
||||
*
|
||||
* 原因是:某些接口,访问的是个人信息,在跨租户是获取不到的!
|
||||
*/
|
||||
private Set<String> ignoreVisitUrls = Collections.emptySet();
|
||||
private Set<String> ignoreUrls = Collections.emptySet();
|
||||
|
||||
/**
|
||||
* 需要忽略多租户的表
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
package cn.iocoder.yudao.framework.tenant.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.biz.system.tenant.TenantCommonApi;
|
||||
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
||||
import cn.iocoder.yudao.framework.redis.config.YudaoCacheProperties;
|
||||
import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkService;
|
||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnoreAspect;
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor;
|
||||
import cn.iocoder.yudao.framework.tenant.core.job.TenantJobAspect;
|
||||
|
@ -17,9 +14,9 @@ import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter;
|
|||
import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
|
||||
import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkServiceImpl;
|
||||
import cn.iocoder.yudao.framework.tenant.core.web.TenantContextWebFilter;
|
||||
import cn.iocoder.yudao.framework.tenant.core.web.TenantVisitContextInterceptor;
|
||||
import cn.iocoder.yudao.framework.web.config.WebProperties;
|
||||
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
|
||||
import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
|
@ -27,7 +24,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.data.redis.cache.BatchStrategies;
|
||||
|
@ -36,29 +32,16 @@ import org.springframework.data.redis.cache.RedisCacheManager;
|
|||
import org.springframework.data.redis.cache.RedisCacheWriter;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.util.pattern.PathPattern;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
@AutoConfiguration
|
||||
@ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) // 允许使用 yudao.tenant.enable=false 禁用多租户
|
||||
@EnableConfigurationProperties(TenantProperties.class)
|
||||
public class YudaoTenantAutoConfiguration {
|
||||
|
||||
@Resource
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Bean
|
||||
public TenantFrameworkService tenantFrameworkService(TenantCommonApi tenantApi) {
|
||||
public TenantFrameworkService tenantFrameworkService(TenantApi tenantApi) {
|
||||
return new TenantFrameworkServiceImpl(tenantApi);
|
||||
}
|
||||
|
||||
|
@ -84,60 +67,13 @@ public class YudaoTenantAutoConfiguration {
|
|||
// ========== WEB ==========
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean<TenantContextWebFilter> tenantContextWebFilter(TenantProperties tenantProperties) {
|
||||
public FilterRegistrationBean<TenantContextWebFilter> tenantContextWebFilter() {
|
||||
FilterRegistrationBean<TenantContextWebFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(new TenantContextWebFilter());
|
||||
registrationBean.setOrder(WebFilterOrderEnum.TENANT_CONTEXT_FILTER);
|
||||
addIgnoreUrls(tenantProperties);
|
||||
return registrationBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果 Controller 接口上,有 {@link TenantIgnore} 注解,那么添加到忽略的 URL 中
|
||||
*
|
||||
* @param tenantProperties 租户配置
|
||||
*/
|
||||
private void addIgnoreUrls(TenantProperties tenantProperties) {
|
||||
// 获得接口对应的 HandlerMethod 集合
|
||||
RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping)
|
||||
applicationContext.getBean("requestMappingHandlerMapping");
|
||||
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
|
||||
// 获得有 @TenantIgnore 注解的接口
|
||||
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethodMap.entrySet()) {
|
||||
HandlerMethod handlerMethod = entry.getValue();
|
||||
if (!handlerMethod.hasMethodAnnotation(TenantIgnore.class)) {
|
||||
continue;
|
||||
}
|
||||
// 添加到忽略的 URL 中
|
||||
if (entry.getKey().getPatternsCondition() != null) {
|
||||
tenantProperties.getIgnoreUrls().addAll(entry.getKey().getPatternsCondition().getPatterns());
|
||||
}
|
||||
if (entry.getKey().getPathPatternsCondition() != null) {
|
||||
tenantProperties.getIgnoreUrls().addAll(
|
||||
convertList(entry.getKey().getPathPatternsCondition().getPatterns(), PathPattern::getPatternString));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TenantVisitContextInterceptor tenantVisitContextInterceptor(TenantProperties tenantProperties,
|
||||
SecurityFrameworkService securityFrameworkService) {
|
||||
return new TenantVisitContextInterceptor(tenantProperties, securityFrameworkService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebMvcConfigurer tenantWebMvcConfigurer(TenantProperties tenantProperties,
|
||||
TenantVisitContextInterceptor tenantVisitContextInterceptor) {
|
||||
return new WebMvcConfigurer() {
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(tenantVisitContextInterceptor)
|
||||
.excludePathPatterns(tenantProperties.getIgnoreVisitUrls().toArray(new String[0]));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ========== Security ==========
|
||||
|
||||
@Bean
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package cn.iocoder.yudao.framework.tenant.core.aop;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.config.TenantProperties;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
|
@ -11,22 +9,10 @@ import java.lang.annotation.*;
|
|||
* 1、Redis 场景:因为是基于 Key 实现多租户的能力,所以忽略没有意义,不像 DB 是一个 column 实现的
|
||||
* 2、MQ 场景:有点难以抉择,目前可以通过 Consumer 手动在消费的方法上,添加 @TenantIgnore 进行忽略
|
||||
*
|
||||
* 特殊:
|
||||
* 1、如果添加到 Controller 类上,则该 URL 自动添加到 {@link TenantProperties#getIgnoreUrls()} 中
|
||||
* 2、如果添加到 DO 实体类上,则它对应的表名“相当于”自动添加到 {@link TenantProperties#getIgnoreTables()} 中
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface TenantIgnore {
|
||||
|
||||
/**
|
||||
* 是否开启忽略租户,默认为 true 开启
|
||||
*
|
||||
* 支持 Spring EL 表达式,如果返回 true 则满足条件,进行租户的忽略
|
||||
*/
|
||||
String enable() default "true";
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package cn.iocoder.yudao.framework.tenant.core.aop;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -25,12 +24,7 @@ public class TenantIgnoreAspect {
|
|||
public Object around(ProceedingJoinPoint joinPoint, TenantIgnore tenantIgnore) throws Throwable {
|
||||
Boolean oldIgnore = TenantContextHolder.isIgnore();
|
||||
try {
|
||||
// 计算条件,满足的情况下,才进行忽略
|
||||
Object enable = SpringExpressionUtils.parseExpression(tenantIgnore.enable());
|
||||
if (Boolean.TRUE.equals(enable)) {
|
||||
TenantContextHolder.setIgnore(true);
|
||||
}
|
||||
|
||||
// 执行逻辑
|
||||
return joinPoint.proceed();
|
||||
} finally {
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
package cn.iocoder.yudao.framework.tenant.core.db;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.tenant.config.TenantProperties;
|
||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.SqlParserUtils;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.LongValue;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 基于 MyBatis Plus 多租户的功能,实现 DB 层面的多租户的功能
|
||||
|
@ -20,21 +18,16 @@ import java.util.Map;
|
|||
*/
|
||||
public class TenantDatabaseInterceptor implements TenantLineHandler {
|
||||
|
||||
/**
|
||||
* 忽略的表
|
||||
*
|
||||
* KEY:表名
|
||||
* VALUE:是否忽略
|
||||
*/
|
||||
private final Map<String, Boolean> ignoreTables = new HashMap<>();
|
||||
private final Set<String> ignoreTables = new HashSet<>();
|
||||
|
||||
public TenantDatabaseInterceptor(TenantProperties properties) {
|
||||
// 不同 DB 下,大小写的习惯不同,所以需要都添加进去
|
||||
properties.getIgnoreTables().forEach(table -> {
|
||||
addIgnoreTable(table, true);
|
||||
ignoreTables.add(table.toLowerCase());
|
||||
ignoreTables.add(table.toUpperCase());
|
||||
});
|
||||
// 在 OracleKeyGenerator 中,生成主键时,会查询这个表,查询这个表后,会自动拼接 TENANT_ID 导致报错
|
||||
addIgnoreTable("DUAL", true);
|
||||
ignoreTables.add("DUAL");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -44,40 +37,8 @@ public class TenantDatabaseInterceptor implements TenantLineHandler {
|
|||
|
||||
@Override
|
||||
public boolean ignoreTable(String tableName) {
|
||||
// 情况一,全局忽略多租户
|
||||
if (TenantContextHolder.isIgnore()) {
|
||||
return true;
|
||||
}
|
||||
// 情况二,忽略多租户的表
|
||||
tableName = SqlParserUtils.removeWrapperSymbol(tableName);
|
||||
Boolean ignore = ignoreTables.get(tableName.toLowerCase());
|
||||
if (ignore == null) {
|
||||
ignore = computeIgnoreTable(tableName);
|
||||
synchronized (ignoreTables) {
|
||||
addIgnoreTable(tableName, ignore);
|
||||
}
|
||||
}
|
||||
return ignore;
|
||||
}
|
||||
|
||||
private void addIgnoreTable(String tableName, boolean ignore) {
|
||||
ignoreTables.put(tableName.toLowerCase(), ignore);
|
||||
ignoreTables.put(tableName.toUpperCase(), ignore);
|
||||
}
|
||||
|
||||
private boolean computeIgnoreTable(String tableName) {
|
||||
// 找不到的表,说明不是 yudao 项目里的,不进行拦截(忽略租户)
|
||||
TableInfo tableInfo = TableInfoHelper.getTableInfo(tableName);
|
||||
if (tableInfo == null) {
|
||||
return true;
|
||||
}
|
||||
// 如果继承了 TenantBaseDO 基类,显然不忽略租户
|
||||
if (TenantBaseDO.class.isAssignableFrom(tableInfo.getEntityType())) {
|
||||
return false;
|
||||
}
|
||||
// 如果添加了 @TenantIgnore 注解,显然也不忽略租户
|
||||
TenantIgnore tenantIgnore = tableInfo.getEntityType().getAnnotation(TenantIgnore.class);
|
||||
return tenantIgnore != null;
|
||||
return TenantContextHolder.isIgnore() // 情况一,全局忽略多租户
|
||||
|| CollUtil.contains(ignoreTables, SqlParserUtils.removeWrapperSymbol(tableName)); // 情况二,忽略多租户的表
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,10 +15,10 @@ import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
|
||||
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 java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package cn.iocoder.yudao.framework.tenant.core.service;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.biz.system.tenant.TenantCommonApi;
|
||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||
import cn.iocoder.yudao.framework.common.util.cache.CacheUtils;
|
||||
import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
@ -21,7 +21,7 @@ public class TenantFrameworkServiceImpl implements TenantFrameworkService {
|
|||
|
||||
private static final ServiceException SERVICE_EXCEPTION_NULL = new ServiceException();
|
||||
|
||||
private final TenantCommonApi tenantApi;
|
||||
private final TenantApi tenantApi;
|
||||
|
||||
/**
|
||||
* 针对 {@link #getTenantIds()} 的缓存
|
||||
|
|
|
@ -45,7 +45,6 @@ public class TenantUtils {
|
|||
*
|
||||
* @param tenantId 租户编号
|
||||
* @param callable 逻辑
|
||||
* @return 结果
|
||||
*/
|
||||
public static <V> V execute(Long tenantId, Callable<V> callable) {
|
||||
Long oldTenantId = TenantContextHolder.getTenantId();
|
||||
|
@ -79,25 +78,6 @@ public class TenantUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略租户,执行对应的逻辑
|
||||
*
|
||||
* @param callable 逻辑
|
||||
* @return 结果
|
||||
*/
|
||||
public static <V> V executeIgnore(Callable<V> callable) {
|
||||
Boolean oldIgnore = TenantContextHolder.isIgnore();
|
||||
try {
|
||||
TenantContextHolder.setIgnore(true);
|
||||
// 执行逻辑
|
||||
return callable.call();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
TenantContextHolder.setIgnore(oldIgnore);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将多租户编号,添加到 header 中
|
||||
*
|
||||
|
|
|
@ -4,10 +4,10 @@ import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
|||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.tenant.core.web;
|
||||
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkService;
|
||||
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import cn.iocoder.yudao.framework.tenant.config.TenantProperties;
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class TenantVisitContextInterceptor implements HandlerInterceptor {
|
||||
|
||||
private static final String PERMISSION = "system:tenant:visit";
|
||||
|
||||
private final TenantProperties tenantProperties;
|
||||
|
||||
private final SecurityFrameworkService securityFrameworkService;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
// 如果和当前租户编号一致,则直接跳过
|
||||
Long visitTenantId = WebFrameworkUtils.getVisitTenantId(request);
|
||||
if (visitTenantId == null) {
|
||||
return true;
|
||||
}
|
||||
if (ObjUtil.equal(visitTenantId, TenantContextHolder.getTenantId())) {
|
||||
return true;
|
||||
}
|
||||
// 必须是登录用户
|
||||
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
|
||||
if (loginUser == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 校验用户是否可切换租户
|
||||
if (!securityFrameworkService.hasAnyPermissions(PERMISSION)) {
|
||||
throw exception0(GlobalErrorCodeConstants.FORBIDDEN.getCode(), "您无权切换租户");
|
||||
}
|
||||
|
||||
// 【重点】切换租户编号
|
||||
loginUser.setVisitTenantId(visitTenantId);
|
||||
TenantContextHolder.setTenantId(visitTenantId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
// 【重点】清理切换,换回原租户编号
|
||||
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
|
||||
if (loginUser != null && loginUser.getTenantId() != null) {
|
||||
TenantContextHolder.setTenantId(loginUser.getTenantId());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,6 +16,11 @@
|
|||
|
||||
package org.springframework.messaging.handler.invocation;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
|
@ -27,11 +32,6 @@ import org.springframework.messaging.Message;
|
|||
import org.springframework.messaging.handler.HandlerMethod;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
|
||||
|
||||
/**
|
||||
|
@ -50,10 +50,12 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
|||
|
||||
private static final Object[] EMPTY_ARGS = new Object[0];
|
||||
|
||||
|
||||
private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
|
||||
|
||||
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
|
||||
|
||||
|
||||
/**
|
||||
* Create an instance from a {@code HandlerMethod}.
|
||||
*/
|
||||
|
@ -81,6 +83,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
|||
super(bean, methodName, parameterTypes);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers} to use for resolving method argument values.
|
||||
*/
|
||||
|
@ -91,12 +94,13 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
|||
/**
|
||||
* Set the ParameterNameDiscoverer for resolving parameter names when needed
|
||||
* (e.g. default request attribute name).
|
||||
* <p>Default is a {@link DefaultParameterNameDiscoverer}.
|
||||
* <p>Default is a {@link org.springframework.core.DefaultParameterNameDiscoverer}.
|
||||
*/
|
||||
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
|
||||
this.parameterNameDiscoverer = parameterNameDiscoverer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invoke the method after resolving its argument values in the context of the given message.
|
||||
* <p>Argument values are commonly resolved through
|
||||
|
@ -200,20 +204,21 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
|||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
assertTargetBean(getBridgedMethod(), getBean(), args);
|
||||
String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
|
||||
String text = (ex.getMessage() == null || ex.getCause() instanceof NullPointerException) ?
|
||||
"Illegal argument": ex.getMessage();
|
||||
throw new IllegalStateException(formatInvokeError(text, args), ex);
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
// Unwrap for HandlerExceptionResolvers ...
|
||||
Throwable targetException = ex.getTargetException();
|
||||
if (targetException instanceof RuntimeException) {
|
||||
throw (RuntimeException) targetException;
|
||||
if (targetException instanceof RuntimeException runtimeException) {
|
||||
throw runtimeException;
|
||||
}
|
||||
else if (targetException instanceof Error) {
|
||||
throw (Error) targetException;
|
||||
else if (targetException instanceof Error error) {
|
||||
throw error;
|
||||
}
|
||||
else if (targetException instanceof Exception) {
|
||||
throw (Exception) targetException;
|
||||
else if (targetException instanceof Exception exception) {
|
||||
throw exception;
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
|
||||
|
@ -225,7 +230,8 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
|||
return new AsyncResultMethodParameter(returnValue);
|
||||
}
|
||||
|
||||
private class AsyncResultMethodParameter extends HandlerMethodParameter {
|
||||
|
||||
private class AsyncResultMethodParameter extends AnnotatedMethodParameter {
|
||||
|
||||
@Nullable
|
||||
private final Object returnValue;
|
||||
|
|
|
@ -27,6 +27,13 @@
|
|||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 业务组件 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-system-api</artifactId> <!-- 需要使用它,进行 Dict 的查询 -->
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.dict.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.biz.system.dict.DictDataCommonApi;
|
||||
import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
|
||||
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
|
@ -10,7 +10,7 @@ public class YudaoDictAutoConfiguration {
|
|||
|
||||
@Bean
|
||||
@SuppressWarnings("InstantiationOfUtilityClass")
|
||||
public DictFrameworkUtils dictUtils(DictDataCommonApi dictDataApi) {
|
||||
public DictFrameworkUtils dictUtils(DictDataApi dictDataApi) {
|
||||
DictFrameworkUtils.init(dictDataApi);
|
||||
return new DictFrameworkUtils();
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package cn.iocoder.yudao.framework.dict.core;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.biz.system.dict.DictDataCommonApi;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.framework.common.util.cache.CacheUtils;
|
||||
import cn.iocoder.yudao.framework.common.biz.system.dict.dto.DictDataRespDTO;
|
||||
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
|
||||
import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import lombok.SneakyThrows;
|
||||
|
@ -11,9 +12,6 @@ import lombok.extern.slf4j.Slf4j;
|
|||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
/**
|
||||
* 字典工具类
|
||||
|
@ -23,57 +21,76 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
|
|||
@Slf4j
|
||||
public class DictFrameworkUtils {
|
||||
|
||||
private static DictDataCommonApi dictDataApi;
|
||||
private static DictDataApi dictDataApi;
|
||||
|
||||
private static final DictDataRespDTO DICT_DATA_NULL = new DictDataRespDTO();
|
||||
|
||||
// TODO @puhui999:GET_DICT_DATA_CACHE、GET_DICT_DATA_LIST_CACHE、PARSE_DICT_DATA_CACHE 这 3 个缓存是有点重叠,可以思考下,有没可能减少 1 个。微信讨论好私聊,再具体改哈
|
||||
/**
|
||||
* 针对 dictType 的字段数据缓存
|
||||
* 针对 {@link #getDictDataLabel(String, String)} 的缓存
|
||||
*/
|
||||
private static final LoadingCache<String, List<DictDataRespDTO>> GET_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache(
|
||||
private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> GET_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache(
|
||||
Duration.ofMinutes(1L), // 过期时间 1 分钟
|
||||
new CacheLoader<String, List<DictDataRespDTO>>() {
|
||||
new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() {
|
||||
|
||||
@Override
|
||||
public List<DictDataRespDTO> load(String dictType) {
|
||||
return dictDataApi.getDictDataList(dictType);
|
||||
public DictDataRespDTO load(KeyValue<String, String> key) {
|
||||
return ObjectUtil.defaultIfNull(dictDataApi.getDictData(key.getKey(), key.getValue()), DICT_DATA_NULL);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
public static void init(DictDataCommonApi dictDataApi) {
|
||||
/**
|
||||
* 针对 {@link #getDictDataLabelList(String)} 的缓存
|
||||
*/
|
||||
private static final LoadingCache<String, List<String>> GET_DICT_DATA_LIST_CACHE = CacheUtils.buildAsyncReloadingCache(
|
||||
Duration.ofMinutes(1L), // 过期时间 1 分钟
|
||||
new CacheLoader<String, List<String>>() {
|
||||
|
||||
@Override
|
||||
public List<String> load(String dictType) {
|
||||
return dictDataApi.getDictDataLabelList(dictType);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* 针对 {@link #parseDictDataValue(String, String)} 的缓存
|
||||
*/
|
||||
private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> PARSE_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache(
|
||||
Duration.ofMinutes(1L), // 过期时间 1 分钟
|
||||
new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() {
|
||||
|
||||
@Override
|
||||
public DictDataRespDTO load(KeyValue<String, String> key) {
|
||||
return ObjectUtil.defaultIfNull(dictDataApi.parseDictData(key.getKey(), key.getValue()), DICT_DATA_NULL);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
public static void init(DictDataApi dictDataApi) {
|
||||
DictFrameworkUtils.dictDataApi = dictDataApi;
|
||||
log.info("[init][初始化 DictFrameworkUtils 成功]");
|
||||
}
|
||||
|
||||
public static void clearCache() {
|
||||
GET_DICT_DATA_CACHE.invalidateAll();
|
||||
@SneakyThrows
|
||||
public static String getDictDataLabel(String dictType, Integer value) {
|
||||
return GET_DICT_DATA_CACHE.get(new KeyValue<>(dictType, String.valueOf(value))).getLabel();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String parseDictDataLabel(String dictType, Integer value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return parseDictDataLabel(dictType, String.valueOf(value));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String parseDictDataLabel(String dictType, String value) {
|
||||
List<DictDataRespDTO> dictDatas = GET_DICT_DATA_CACHE.get(dictType);
|
||||
DictDataRespDTO dictData = CollUtil.findOne(dictDatas, data -> Objects.equals(data.getValue(), value));
|
||||
return dictData != null ? dictData.getLabel(): null;
|
||||
public static String getDictDataLabel(String dictType, String value) {
|
||||
return GET_DICT_DATA_CACHE.get(new KeyValue<>(dictType, value)).getLabel();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static List<String> getDictDataLabelList(String dictType) {
|
||||
List<DictDataRespDTO> dictDatas = GET_DICT_DATA_CACHE.get(dictType);
|
||||
return convertList(dictDatas, DictDataRespDTO::getLabel);
|
||||
return GET_DICT_DATA_LIST_CACHE.get(dictType);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String parseDictDataValue(String dictType, String label) {
|
||||
List<DictDataRespDTO> dictDatas = GET_DICT_DATA_CACHE.get(dictType);
|
||||
DictDataRespDTO dictData = CollUtil.findOne(dictDatas, data -> Objects.equals(data.getLabel(), label));
|
||||
return dictData!= null ? dictData.getValue(): null;
|
||||
return PARSE_DICT_DATA_CACHE.get(new KeyValue<>(dictType, label)).getValue();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ public class DictConvert implements Converter<Object> {
|
|||
// 使用字典格式化
|
||||
String type = getType(contentProperty);
|
||||
String value = String.valueOf(object);
|
||||
String label = DictFrameworkUtils.parseDictDataLabel(type, value);
|
||||
String label = DictFrameworkUtils.getDictDataLabel(type, value);
|
||||
if (label == null) {
|
||||
log.error("[convertToExcelData][type({}) 转换不了 label({})]", type, value);
|
||||
return new WriteCellData<>("");
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
package cn.iocoder.yudao.framework.excel.core.util;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
|
||||
import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler;
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.alibaba.excel.converters.longconverter.LongStringConverter;
|
||||
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -39,7 +40,7 @@ public class ExcelUtils {
|
|||
.registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度
|
||||
.sheet(sheetName).doWrite(data);
|
||||
// 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了
|
||||
response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename));
|
||||
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8.name()));
|
||||
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
package cn.iocoder.yudao.framework.dict.core.util;
|
||||
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.iocoder.yudao.framework.common.biz.system.dict.DictDataCommonApi;
|
||||
import cn.iocoder.yudao.framework.common.biz.system.dict.dto.DictDataRespDTO;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
|
||||
import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
|
@ -22,40 +19,33 @@ import static org.mockito.Mockito.when;
|
|||
public class DictFrameworkUtilsTest extends BaseMockitoUnitTest {
|
||||
|
||||
@Mock
|
||||
private DictDataCommonApi dictDataApi;
|
||||
private DictDataApi dictDataApi;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
DictFrameworkUtils.init(dictDataApi);
|
||||
DictFrameworkUtils.clearCache();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseDictDataLabel() {
|
||||
public void testGetDictDataLabel() {
|
||||
// mock 数据
|
||||
List<DictDataRespDTO> dictDatas = ListUtil.of(
|
||||
randomPojo(DictDataRespDTO.class, o -> o.setDictType("animal").setValue("cat").setLabel("猫")),
|
||||
randomPojo(DictDataRespDTO.class, o -> o.setDictType("animal").setValue("dog").setLabel("狗"))
|
||||
);
|
||||
DictDataRespDTO dataRespDTO = randomPojo(DictDataRespDTO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
// mock 方法
|
||||
when(dictDataApi.getDictDataList(eq("animal"))).thenReturn(dictDatas);
|
||||
when(dictDataApi.getDictData(dataRespDTO.getDictType(), dataRespDTO.getValue())).thenReturn(dataRespDTO);
|
||||
|
||||
// 断言返回值
|
||||
assertEquals("狗", DictFrameworkUtils.parseDictDataLabel("animal", "dog"));
|
||||
assertEquals(dataRespDTO.getLabel(), DictFrameworkUtils.getDictDataLabel(dataRespDTO.getDictType(), dataRespDTO.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseDictDataValue() {
|
||||
// mock 数据
|
||||
List<DictDataRespDTO> dictDatas = ListUtil.of(
|
||||
randomPojo(DictDataRespDTO.class, o -> o.setDictType("animal").setValue("cat").setLabel("猫")),
|
||||
randomPojo(DictDataRespDTO.class, o -> o.setDictType("animal").setValue("dog").setLabel("狗"))
|
||||
);
|
||||
DictDataRespDTO resp = randomPojo(DictDataRespDTO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
// mock 方法
|
||||
when(dictDataApi.getDictDataList(eq("animal"))).thenReturn(dictDatas);
|
||||
when(dictDataApi.parseDictData(resp.getDictType(), resp.getLabel())).thenReturn(resp);
|
||||
|
||||
// 断言返回值
|
||||
assertEquals("dog", DictFrameworkUtils.parseDictDataValue("animal", "狗"));
|
||||
assertEquals(resp.getValue(), DictFrameworkUtils.parseDictDataValue(resp.getDictType(), resp.getLabel()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,9 +13,8 @@ import org.quartz.PersistJobDataAfterExecution;
|
|||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.scheduling.quartz.QuartzJobBean;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import jakarta.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
import static cn.hutool.core.exceptions.ExceptionUtil.getRootCauseMessage;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.quartz.core.service;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,10 +3,10 @@ package cn.iocoder.yudao.framework.tracer.core.filter;
|
|||
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,7 +6,6 @@ import cn.hutool.system.SystemUtil;
|
|||
import cn.iocoder.yudao.framework.common.enums.DocumentEnum;
|
||||
import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
|
||||
import cn.iocoder.yudao.framework.mq.redis.core.job.RedisPendingMessageResendJob;
|
||||
import cn.iocoder.yudao.framework.mq.redis.core.job.RedisStreamMessageCleanupJob;
|
||||
import cn.iocoder.yudao.framework.mq.redis.core.pubsub.AbstractRedisChannelMessageListener;
|
||||
import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener;
|
||||
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
|
||||
|
@ -74,17 +73,6 @@ public class YudaoRedisMQConsumerAutoConfiguration {
|
|||
return new RedisPendingMessageResendJob(listeners, redisTemplate, groupName, redissonClient);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Redis Stream 消息清理任务
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnBean(AbstractRedisStreamMessageListener.class)
|
||||
public RedisStreamMessageCleanupJob redisStreamMessageCleanupJob(List<AbstractRedisStreamMessageListener<?>> listeners,
|
||||
RedisMQTemplate redisTemplate,
|
||||
RedissonClient redissonClient) {
|
||||
return new RedisStreamMessageCleanupJob(listeners, redisTemplate, redissonClient);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Redis Stream 集群消费的容器
|
||||
*
|
||||
|
|
|
@ -23,13 +23,13 @@ import java.util.Objects;
|
|||
@AllArgsConstructor
|
||||
public class RedisPendingMessageResendJob {
|
||||
|
||||
private static final String LOCK_KEY = "redis:stream:pending-message-resend:lock";
|
||||
private static final String LOCK_KEY = "redis:pending:msg:lock";
|
||||
|
||||
/**
|
||||
* 消息超时时间,默认 5 分钟
|
||||
*
|
||||
* 1. 超时的消息才会被重新投递
|
||||
* 2. 由于定时任务 1 分钟一次,消息超时后不会被立即重投,极端情况下消息 5 分钟过期后,再等 1 分钟才会被扫瞄到
|
||||
* 2. 由于定时任务 1 分钟一次,消息超时后不会被立即重投,极端情况下消息5分钟过期后,再等 1 分钟才会被扫瞄到
|
||||
*/
|
||||
private static final int EXPIRE_TIME = 5 * 60;
|
||||
|
||||
|
@ -39,7 +39,7 @@ public class RedisPendingMessageResendJob {
|
|||
private final RedissonClient redissonClient;
|
||||
|
||||
/**
|
||||
* 一分钟执行一次,这里选择每分钟的 35 秒执行,是为了避免整点任务过多的问题
|
||||
* 一分钟执行一次,这里选择每分钟的35秒执行,是为了避免整点任务过多的问题
|
||||
*/
|
||||
@Scheduled(cron = "35 * * * * ?")
|
||||
public void messageResend() {
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.mq.redis.core.job;
|
||||
|
||||
import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
|
||||
import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.data.redis.core.StreamOperations;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Redis Stream 消息清理任务
|
||||
* 用于定期清理已消费的消息,防止内存占用过大
|
||||
*
|
||||
* @see <a href="https://www.cnblogs.com/nanxiang/p/16179519.html">记一次 redis stream 数据类型内存不释放问题</a>
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class RedisStreamMessageCleanupJob {
|
||||
|
||||
private static final String LOCK_KEY = "redis:stream:message-cleanup:lock";
|
||||
|
||||
/**
|
||||
* 保留的消息数量,默认保留最近 10000 条消息
|
||||
*/
|
||||
private static final long MAX_COUNT = 10000;
|
||||
|
||||
private final List<AbstractRedisStreamMessageListener<?>> listeners;
|
||||
private final RedisMQTemplate redisTemplate;
|
||||
private final RedissonClient redissonClient;
|
||||
|
||||
/**
|
||||
* 每小时执行一次清理任务
|
||||
*/
|
||||
@Scheduled(cron = "0 0 * * * ?")
|
||||
public void cleanup() {
|
||||
RLock lock = redissonClient.getLock(LOCK_KEY);
|
||||
// 尝试加锁
|
||||
if (lock.tryLock()) {
|
||||
try {
|
||||
execute();
|
||||
} catch (Exception ex) {
|
||||
log.error("[cleanup][执行异常]", ex);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行清理逻辑
|
||||
*/
|
||||
private void execute() {
|
||||
StreamOperations<String, Object, Object> ops = redisTemplate.getRedisTemplate().opsForStream();
|
||||
listeners.forEach(listener -> {
|
||||
try {
|
||||
// 使用 XTRIM 命令清理消息,只保留最近的 MAX_LEN 条消息
|
||||
Long trimCount = ops.trim(listener.getStreamKey(), MAX_COUNT, true);
|
||||
if (trimCount != null && trimCount > 0) {
|
||||
log.info("[execute][Stream({}) 清理消息数量({})]", listener.getStreamKey(), trimCount);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.error("[execute][Stream({}) 清理异常]", listener.getStreamKey(), ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -63,27 +63,28 @@
|
|||
<artifactId>opengauss-jdbc</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.taosdata.jdbc</groupId>
|
||||
<artifactId>taos-jdbcdriver</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
<artifactId>druid-spring-boot-3-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-jsqlparser-4.9</artifactId>
|
||||
<artifactId>mybatis-plus-jsqlparser</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 -->
|
||||
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId> <!-- 多数据源 -->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-undertow</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.datasource.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.datasource.core.filter.DruidAdRemoveFilter;
|
||||
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
|
||||
import com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
|
|
|
@ -3,10 +3,10 @@ package cn.iocoder.yudao.framework.datasource.core.filter;
|
|||
import com.alibaba.druid.util.Utils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
|
|
|
@ -53,14 +53,4 @@ public abstract class BaseDO implements Serializable, TransPojo {
|
|||
@TableLogic
|
||||
private Boolean deleted;
|
||||
|
||||
/**
|
||||
* 把 creator、createTime、updateTime、updater 都清空,避免前端直接传递 creator 之类的字段,直接就被更新了
|
||||
*/
|
||||
public void clean(){
|
||||
this.creator = null;
|
||||
this.createTime = null;
|
||||
this.updater = null;
|
||||
this.updateTime = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -92,36 +92,10 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
|
|||
|
||||
default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2,
|
||||
SFunction<T, ?> field3, Object value3) {
|
||||
return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2).eq(field3, value3));
|
||||
return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2)
|
||||
.eq(field3, value3));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取满足条件的第 1 条记录
|
||||
*
|
||||
* 目的:解决并发场景下,插入多条记录后,使用 selectOne 会报错的问题
|
||||
*
|
||||
* @param field 字段名
|
||||
* @param value 字段值
|
||||
* @return 实体
|
||||
*/
|
||||
default T selectFirstOne(SFunction<T, ?> field, Object value) {
|
||||
// 如果明确使用 MySQL 等场景,可以考虑使用 LIMIT 1 进行优化
|
||||
List<T> list = selectList(new LambdaQueryWrapper<T>().eq(field, value));
|
||||
return CollUtil.getFirst(list);
|
||||
}
|
||||
|
||||
default T selectFirstOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2) {
|
||||
List<T> list = selectList(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2));
|
||||
return CollUtil.getFirst(list);
|
||||
}
|
||||
|
||||
default T selectFirstOne(SFunction<T,?> field1, Object value1, SFunction<T,?> field2, Object value2,
|
||||
SFunction<T,?> field3, Object value3) {
|
||||
List<T> list = selectList(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2).eq(field3, value3));
|
||||
return CollUtil.getFirst(list);
|
||||
}
|
||||
|
||||
|
||||
default Long selectCount() {
|
||||
return selectCount(new QueryWrapper<>());
|
||||
}
|
||||
|
@ -215,11 +189,4 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
|
|||
return delete(new LambdaQueryWrapper<T>().eq(field, value));
|
||||
}
|
||||
|
||||
default int deleteBatch(SFunction<T, ?> field, Collection<?> values) {
|
||||
if (CollUtil.isEmpty(values)) {
|
||||
return 0;
|
||||
}
|
||||
return delete(new LambdaQueryWrapper<T>().in(field, values));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import cn.hutool.core.util.ArrayUtil;
|
|||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
||||
import com.github.yulichang.toolkit.MPJWrappers;
|
||||
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
|
@ -14,93 +15,93 @@ import java.util.function.Consumer;
|
|||
* 拓展 MyBatis Plus Join QueryWrapper 类,主要增加如下功能:
|
||||
* <p>
|
||||
* 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。
|
||||
* 2. SFunction<S, ?> column + <S> 泛型:支持任意类字段(主表、子表、三表),推荐写法, 让编译器自动推断 S 类型
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
*/
|
||||
public class MPJLambdaWrapperX<T> extends MPJLambdaWrapper<T> {
|
||||
|
||||
public <S> MPJLambdaWrapperX<T> likeIfPresent(SFunction<S, ?> column, String val) {
|
||||
public MPJLambdaWrapperX<T> likeIfPresent(SFunction<T, ?> column, String val) {
|
||||
MPJWrappers.lambdaJoin().like(column, val);
|
||||
if (StringUtils.hasText(val)) {
|
||||
return (MPJLambdaWrapperX<T>) super.like(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public <S> MPJLambdaWrapperX<T> inIfPresent(SFunction<S, ?> column, Collection<?> values) {
|
||||
public MPJLambdaWrapperX<T> inIfPresent(SFunction<T, ?> column, Collection<?> values) {
|
||||
if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) {
|
||||
return (MPJLambdaWrapperX<T>) super.in(column, values);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public <S> MPJLambdaWrapperX<T> inIfPresent(SFunction<S, ?> column, Object... values) {
|
||||
public MPJLambdaWrapperX<T> inIfPresent(SFunction<T, ?> column, Object... values) {
|
||||
if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) {
|
||||
return (MPJLambdaWrapperX<T>) super.in(column, values);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public <S> MPJLambdaWrapperX<T> eqIfPresent(SFunction<S, ?> column, Object val) {
|
||||
public MPJLambdaWrapperX<T> eqIfPresent(SFunction<T, ?> column, Object val) {
|
||||
if (ObjectUtil.isNotEmpty(val)) {
|
||||
return (MPJLambdaWrapperX<T>) super.eq(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public <S> MPJLambdaWrapperX<T> neIfPresent(SFunction<S, ?> column, Object val) {
|
||||
public MPJLambdaWrapperX<T> neIfPresent(SFunction<T, ?> column, Object val) {
|
||||
if (ObjectUtil.isNotEmpty(val)) {
|
||||
return (MPJLambdaWrapperX<T>) super.ne(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public <S> MPJLambdaWrapperX<T> gtIfPresent(SFunction<S, ?> column, Object val) {
|
||||
public MPJLambdaWrapperX<T> gtIfPresent(SFunction<T, ?> column, Object val) {
|
||||
if (val != null) {
|
||||
return (MPJLambdaWrapperX<T>) super.gt(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public <S> MPJLambdaWrapperX<T> geIfPresent(SFunction<S, ?> column, Object val) {
|
||||
public MPJLambdaWrapperX<T> geIfPresent(SFunction<T, ?> column, Object val) {
|
||||
if (val != null) {
|
||||
return (MPJLambdaWrapperX<T>) super.ge(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public <S> MPJLambdaWrapperX<T> ltIfPresent(SFunction<S, ?> column, Object val) {
|
||||
public MPJLambdaWrapperX<T> ltIfPresent(SFunction<T, ?> column, Object val) {
|
||||
if (val != null) {
|
||||
return (MPJLambdaWrapperX<T>) super.lt(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public <S> MPJLambdaWrapperX<T> leIfPresent(SFunction<S, ?> column, Object val) {
|
||||
public MPJLambdaWrapperX<T> leIfPresent(SFunction<T, ?> column, Object val) {
|
||||
if (val != null) {
|
||||
return (MPJLambdaWrapperX<T>) super.le(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public <S> MPJLambdaWrapperX<T> betweenIfPresent(SFunction<S, ?> column, Object[] values) {
|
||||
Object val1 = ArrayUtils.get(values, 0);
|
||||
Object val2 = ArrayUtils.get(values, 1);
|
||||
return betweenIfPresent(column, val1, val2);
|
||||
}
|
||||
|
||||
public <S> MPJLambdaWrapperX<T> betweenIfPresent(SFunction<S, ?> column, Object val1, Object val2) {
|
||||
public MPJLambdaWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object val1, Object val2) {
|
||||
if (val1 != null && val2 != null) {
|
||||
return (MPJLambdaWrapperX<T>) super.between(column, val1, val2);
|
||||
}
|
||||
if (val1 != null) {
|
||||
return (MPJLambdaWrapperX<T>) super.ge(column, val1);
|
||||
return (MPJLambdaWrapperX<T>) ge(column, val1);
|
||||
}
|
||||
if (val2 != null) {
|
||||
return (MPJLambdaWrapperX<T>) super.le(column, val2);
|
||||
return (MPJLambdaWrapperX<T>) le(column, val2);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public MPJLambdaWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object[] values) {
|
||||
Object val1 = ArrayUtils.get(values, 0);
|
||||
Object val2 = ArrayUtils.get(values, 1);
|
||||
return betweenIfPresent(column, val1, val2);
|
||||
}
|
||||
|
||||
// ========== 重写父类方法,方便链式调用 ==========
|
||||
|
||||
|
@ -309,41 +310,4 @@ public class MPJLambdaWrapperX<T> extends MPJLambdaWrapper<T> {
|
|||
return this;
|
||||
}
|
||||
|
||||
// ========== 关键重写:使 leftJoin 返回当前类型 this ==========
|
||||
@Override
|
||||
public <A, B> MPJLambdaWrapperX<T> leftJoin(Class<A> clazz, SFunction<A, ?> left, SFunction<B, ?> right) {
|
||||
super.leftJoin(clazz, left, right);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A, B> MPJLambdaWrapperX<T> rightJoin(Class<A> clazz, SFunction<A, ?> left, SFunction<B, ?> right) {
|
||||
super.rightJoin(clazz, left, right);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A, B> MPJLambdaWrapperX<T> innerJoin(Class<A> clazz, SFunction<A, ?> left, SFunction<B, ?> right) {
|
||||
super.innerJoin(clazz, left, right);
|
||||
return this;
|
||||
}
|
||||
|
||||
// ========== 添加扩展 Join 支持 ext 函数式参数 ==========
|
||||
public <A, B> MPJLambdaWrapperX<T> leftJoin(Class<A> clazz, SFunction<A, ?> left, SFunction<B, ?> right, Consumer<MPJLambdaWrapperX<T>> ext) {
|
||||
super.leftJoin(clazz, left, right);
|
||||
if (ext != null) ext.accept(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
public <A, B> MPJLambdaWrapperX<T> rightJoin(Class<A> clazz, SFunction<A, ?> left, SFunction<B, ?> right, Consumer<MPJLambdaWrapperX<T>> ext) {
|
||||
super.rightJoin(clazz, left, right);
|
||||
if (ext != null) ext.accept(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
public <A, B> MPJLambdaWrapperX<T> innerJoin(Class<A> clazz, SFunction<A, ?> left, SFunction<B, ?> right, Consumer<MPJLambdaWrapperX<T>> ext) {
|
||||
super.innerJoin(clazz, left, right);
|
||||
if (ext != null) ext.accept(this);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import java.sql.ResultSet;
|
|||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* 字段字段的 TypeHandler 实现类,基于 {@link AES} 实现
|
||||
* 字段字段的 TypeHandler 实现类,基于 {@link cn.hutool.crypto.symmetric.AES} 实现
|
||||
* 可通过 jasypt.encryptor.password 配置项,设置密钥
|
||||
*
|
||||
* @author 芋道源码
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.mybatis.core.type;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
import org.apache.ibatis.type.MappedJdbcTypes;
|
||||
import org.apache.ibatis.type.MappedTypes;
|
||||
import org.apache.ibatis.type.TypeHandler;
|
||||
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Set<Long> 的类型转换器实现类,对应数据库的 varchar 类型
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@MappedJdbcTypes(JdbcType.VARCHAR)
|
||||
@MappedTypes(List.class)
|
||||
public class LongSetTypeHandler implements TypeHandler<Set<Long>> {
|
||||
|
||||
private static final String COMMA = ",";
|
||||
|
||||
@Override
|
||||
public void setParameter(PreparedStatement ps, int i, Set<Long> strings, JdbcType jdbcType) throws SQLException {
|
||||
// 设置占位符
|
||||
ps.setString(i, CollUtil.join(strings, COMMA));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Long> getResult(ResultSet rs, String columnName) throws SQLException {
|
||||
String value = rs.getString(columnName);
|
||||
return getResult(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Long> getResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||
String value = rs.getString(columnIndex);
|
||||
return getResult(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Long> getResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||
String value = cs.getString(columnIndex);
|
||||
return getResult(value);
|
||||
}
|
||||
|
||||
private Set<Long> getResult(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return StrUtils.splitToLongSet(value, COMMA);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.idempotent.core.keyresolver.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
|
||||
import cn.iocoder.yudao.framework.idempotent.core.annotation.Idempotent;
|
||||
import cn.iocoder.yudao.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
|
@ -18,7 +18,7 @@ public class DefaultIdempotentKeyResolver implements IdempotentKeyResolver {
|
|||
@Override
|
||||
public String resolver(JoinPoint joinPoint, Idempotent idempotent) {
|
||||
String methodName = joinPoint.getSignature().toString();
|
||||
String argsStr = StrUtils.joinMethodArgs(joinPoint);
|
||||
String argsStr = StrUtil.join(",", joinPoint.getArgs());
|
||||
return SecureUtil.md5(methodName + argsStr);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.idempotent.core.annotation.Idempotent;
|
|||
import cn.iocoder.yudao.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
|
@ -21,7 +21,8 @@ import java.lang.reflect.Method;
|
|||
*/
|
||||
public class ExpressionIdempotentKeyResolver implements IdempotentKeyResolver {
|
||||
|
||||
private final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
|
||||
private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
|
||||
|
||||
private final ExpressionParser expressionParser = new SpelExpressionParser();
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.idempotent.core.keyresolver.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
|
||||
import cn.iocoder.yudao.framework.idempotent.core.annotation.Idempotent;
|
||||
import cn.iocoder.yudao.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
|
||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||
|
@ -19,7 +19,7 @@ public class UserIdempotentKeyResolver implements IdempotentKeyResolver {
|
|||
@Override
|
||||
public String resolver(JoinPoint joinPoint, Idempotent idempotent) {
|
||||
String methodName = joinPoint.getSignature().toString();
|
||||
String argsStr = StrUtils.joinMethodArgs(joinPoint);
|
||||
String argsStr = StrUtil.join(",", joinPoint.getArgs());
|
||||
Long userId = WebFrameworkUtils.getLoginUserId();
|
||||
Integer userType = WebFrameworkUtils.getLoginUserType();
|
||||
return SecureUtil.md5(methodName + argsStr + userId + userType);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
|
||||
import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter;
|
||||
import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
|
@ -19,7 +19,7 @@ public class ClientIpRateLimiterKeyResolver implements RateLimiterKeyResolver {
|
|||
@Override
|
||||
public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) {
|
||||
String methodName = joinPoint.getSignature().toString();
|
||||
String argsStr = StrUtils.joinMethodArgs(joinPoint);
|
||||
String argsStr = StrUtil.join(",", joinPoint.getArgs());
|
||||
String clientIp = ServletUtils.getClientIP();
|
||||
return SecureUtil.md5(methodName + argsStr + clientIp);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
|
||||
import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter;
|
||||
import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
|
@ -18,7 +18,7 @@ public class DefaultRateLimiterKeyResolver implements RateLimiterKeyResolver {
|
|||
@Override
|
||||
public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) {
|
||||
String methodName = joinPoint.getSignature().toString();
|
||||
String argsStr = StrUtils.joinMethodArgs(joinPoint);
|
||||
String argsStr = StrUtil.join(",", joinPoint.getArgs());
|
||||
return SecureUtil.md5(methodName + argsStr);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
|
||||
import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter;
|
||||
import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
|
@ -19,7 +19,7 @@ public class ServerNodeRateLimiterKeyResolver implements RateLimiterKeyResolver
|
|||
@Override
|
||||
public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) {
|
||||
String methodName = joinPoint.getSignature().toString();
|
||||
String argsStr = StrUtils.joinMethodArgs(joinPoint);
|
||||
String argsStr = StrUtil.join(",", joinPoint.getArgs());
|
||||
String serverNode = String.format("%s@%d", SystemUtil.getHostInfo().getAddress(), SystemUtil.getCurrentPID());
|
||||
return SecureUtil.md5(methodName + argsStr + serverNode);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
|
||||
import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter;
|
||||
import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver;
|
||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||
|
@ -19,7 +19,7 @@ public class UserRateLimiterKeyResolver implements RateLimiterKeyResolver {
|
|||
@Override
|
||||
public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) {
|
||||
String methodName = joinPoint.getSignature().toString();
|
||||
String argsStr = StrUtils.joinMethodArgs(joinPoint);
|
||||
String argsStr = StrUtil.join(",", joinPoint.getArgs());
|
||||
Long userId = WebFrameworkUtils.getLoginUserId();
|
||||
Integer userType = WebFrameworkUtils.getLoginUserType();
|
||||
return SecureUtil.md5(methodName + argsStr + userId + userType);
|
||||
|
|
|
@ -44,7 +44,6 @@ public class RateLimiterRedisDAO {
|
|||
RateLimiterConfig config = rateLimiter.getConfig();
|
||||
if (config == null) {
|
||||
rateLimiter.trySetRate(RateType.OVERALL, count, rateInterval, RateIntervalUnit.SECONDS);
|
||||
rateLimiter.expire(rateInterval, TimeUnit.SECONDS); // 原因参见 https://t.zsxq.com/lcR0W
|
||||
return rateLimiter;
|
||||
}
|
||||
// 2. 如果存在,并且配置相同,则直接返回
|
||||
|
@ -55,7 +54,6 @@ public class RateLimiterRedisDAO {
|
|||
}
|
||||
// 3. 如果存在,并且配置不同,则进行新建
|
||||
rateLimiter.setRate(RateType.OVERALL, count, rateInterval, RateIntervalUnit.SECONDS);
|
||||
rateLimiter.expire(rateInterval, TimeUnit.SECONDS); // 原因参见 https://t.zsxq.com/lcR0W
|
||||
return rateLimiter;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,22 +2,20 @@ package cn.iocoder.yudao.framework.signature.core.aop;
|
|||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.BooleanUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import cn.iocoder.yudao.framework.signature.core.annotation.ApiSignature;
|
||||
import cn.iocoder.yudao.framework.signature.core.redis.ApiSignatureRedisDAO;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.SortedMap;
|
||||
|
@ -71,17 +69,13 @@ public class ApiSignatureAspect {
|
|||
|
||||
// 3. 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 )
|
||||
String nonce = request.getHeader(signature.nonce());
|
||||
if (BooleanUtil.isFalse(signatureRedisDAO.setNonce(appId, nonce, signature.timeout() * 2, signature.timeUnit()))) {
|
||||
String timestamp = request.getHeader(signature.timestamp());
|
||||
log.info("[verifySignature][appId({}) timestamp({}) nonce({}) sign({}) 存在重复请求]", appId, timestamp, nonce, clientSignature);
|
||||
throw new ServiceException(GlobalErrorCodeConstants.REPEATED_REQUESTS.getCode(), "存在重复请求");
|
||||
}
|
||||
signatureRedisDAO.setNonce(appId, nonce, signature.timeout() * 2, signature.timeUnit());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验请求头加签参数
|
||||
* <p>
|
||||
*
|
||||
* 1. appId 是否为空
|
||||
* 2. timestamp 是否为空,请求是否已经超时,默认 10 分钟
|
||||
* 3. nonce 是否为空,随机数是否 10 位以上,是否在规定时间内已经访问过了
|
||||
|
@ -124,7 +118,7 @@ public class ApiSignatureAspect {
|
|||
|
||||
/**
|
||||
* 构建签名字符串
|
||||
* <p>
|
||||
*
|
||||
* 格式为 = 请求参数 + 请求体 + 请求头 + 密钥
|
||||
*
|
||||
* @param signature signature
|
||||
|
|
|
@ -17,7 +17,7 @@ public class ApiSignatureRedisDAO {
|
|||
|
||||
/**
|
||||
* 验签随机数
|
||||
* <p>
|
||||
*
|
||||
* KEY 格式:signature_nonce:%s // 参数为 随机数
|
||||
* VALUE 格式:String
|
||||
* 过期时间:不固定
|
||||
|
@ -26,7 +26,7 @@ public class ApiSignatureRedisDAO {
|
|||
|
||||
/**
|
||||
* 签名密钥
|
||||
* <p>
|
||||
*
|
||||
* HASH 结构
|
||||
* KEY 格式:%s // 参数为 appid
|
||||
* VALUE 格式:String
|
||||
|
@ -40,8 +40,8 @@ public class ApiSignatureRedisDAO {
|
|||
return stringRedisTemplate.opsForValue().get(formatNonceKey(appId, nonce));
|
||||
}
|
||||
|
||||
public Boolean setNonce(String appId, String nonce, int time, TimeUnit timeUnit) {
|
||||
return stringRedisTemplate.opsForValue().setIfAbsent(formatNonceKey(appId, nonce), "", time, timeUnit);
|
||||
public void setNonce(String appId, String nonce, int time, TimeUnit timeUnit) {
|
||||
stringRedisTemplate.opsForValue().set(formatNonceKey(appId, nonce), "", time, timeUnit);
|
||||
}
|
||||
|
||||
private static String formatNonceKey(String appId, String nonce) {
|
||||
|
|
|
@ -6,13 +6,13 @@ import cn.hutool.crypto.digest.DigestUtil;
|
|||
import cn.iocoder.yudao.framework.signature.core.annotation.ApiSignature;
|
||||
import cn.iocoder.yudao.framework.signature.core.aop.ApiSignatureAspect;
|
||||
import cn.iocoder.yudao.framework.signature.core.redis.ApiSignatureRedisDAO;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
|
@ -63,12 +63,13 @@ public class ApiSignatureTest {
|
|||
when(request.getReader()).thenReturn(new BufferedReader(new StringReader("test")));
|
||||
// mock 方法
|
||||
when(signatureRedisDAO.getAppSecret(eq(appId))).thenReturn(appSecret);
|
||||
when(signatureRedisDAO.setNonce(eq(appId), eq(nonce), eq(120), eq(TimeUnit.SECONDS))).thenReturn(true);
|
||||
|
||||
// 调用
|
||||
boolean result = apiSignatureAspect.verifySignature(apiSignature, request);
|
||||
// 断言结果
|
||||
assertTrue(result);
|
||||
// 断言调用
|
||||
verify(signatureRedisDAO).setNonce(eq(appId), eq(nonce), eq(120), eq(TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
}
|
|
@ -26,10 +26,6 @@
|
|||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-data-27</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
|
|
@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.redis.config;
|
|||
import cn.hutool.core.util.ReflectUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.redisson.spring.starter.RedissonAutoConfiguration;
|
||||
import org.redisson.spring.starter.RedissonAutoConfigurationV2;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
|
@ -13,7 +13,7 @@ import org.springframework.data.redis.serializer.RedisSerializer;
|
|||
/**
|
||||
* Redis 配置类
|
||||
*/
|
||||
@AutoConfiguration(before = RedissonAutoConfiguration.class) // 目的:使用自己定义的 RedisTemplate Bean
|
||||
@AutoConfiguration(before = RedissonAutoConfigurationV2.class) // 目的:使用自己定义的 RedisTemplate Bean
|
||||
public class YudaoRedisAutoConfiguration {
|
||||
|
||||
/**
|
||||
|
|
|
@ -59,6 +59,13 @@
|
|||
<groupId>io.github.mouzt</groupId>
|
||||
<artifactId>bizlog-sdk</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 业务组件 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-system-api</artifactId> <!-- 需要使用它,进行 Token 的校验 -->
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
package cn.iocoder.yudao.framework.operatelog.core.service;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.biz.system.logger.OperateLogCommonApi;
|
||||
import cn.iocoder.yudao.framework.common.biz.system.logger.dto.OperateLogCreateReqDTO;
|
||||
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
|
||||
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
|
||||
import com.mzt.logapi.beans.LogRecord;
|
||||
import com.mzt.logapi.service.ILogRecordService;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 操作日志 ILogRecordService 实现类
|
||||
*
|
||||
* 基于 {@link OperateLogCommonApi} 实现,记录操作日志
|
||||
* 基于 {@link OperateLogApi} 实现,记录操作日志
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
|
@ -25,7 +25,7 @@ import java.util.List;
|
|||
public class LogRecordServiceImpl implements ILogRecordService {
|
||||
|
||||
@Resource
|
||||
private OperateLogCommonApi operateLogApi;
|
||||
private OperateLogApi operateLogApi;
|
||||
|
||||
@Override
|
||||
public void record(LogRecord logRecord) {
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
package cn.iocoder.yudao.framework.security.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.web.config.WebProperties;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 自定义的 URL 的安全配置
|
||||
* 目的:每个 Maven Module 可以自定义规则!
|
||||
|
|
|
@ -4,8 +4,8 @@ import lombok.Data;
|
|||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package cn.iocoder.yudao.framework.security.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
|
||||
import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi;
|
||||
import cn.iocoder.yudao.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy;
|
||||
import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
|
||||
import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl;
|
||||
|
@ -9,7 +7,9 @@ import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPoint
|
|||
import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkService;
|
||||
import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkServiceImpl;
|
||||
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
|
||||
import javax.annotation.Resource;
|
||||
import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
|
||||
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
|
||||
|
@ -69,12 +69,12 @@ public class YudaoSecurityAutoConfiguration {
|
|||
*/
|
||||
@Bean
|
||||
public TokenAuthenticationFilter authenticationTokenFilter(GlobalExceptionHandler globalExceptionHandler,
|
||||
OAuth2TokenCommonApi oauth2TokenApi) {
|
||||
OAuth2TokenApi oauth2TokenApi) {
|
||||
return new TokenAuthenticationFilter(securityProperties, globalExceptionHandler, oauth2TokenApi);
|
||||
}
|
||||
|
||||
@Bean("ss") // 使用 Spring Security 的缩写,方便使用
|
||||
public SecurityFrameworkService securityFrameworkService(PermissionCommonApi permissionApi) {
|
||||
public SecurityFrameworkService securityFrameworkService(PermissionApi permissionApi) {
|
||||
return new SecurityFrameworkServiceImpl(permissionApi);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@ import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter
|
|||
import cn.iocoder.yudao.framework.web.config.WebProperties;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
@ -28,9 +30,6 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
|||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.util.pattern.PathPattern;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.annotation.security.PermitAll;
|
||||
import javax.servlet.DispatcherType;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -143,9 +142,7 @@ public class YudaoWebSecurityConfigurerAdapter {
|
|||
// ②:每个项目的自定义规则
|
||||
.authorizeHttpRequests(c -> authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(c)))
|
||||
// ③:兜底规则,必须认证
|
||||
.authorizeHttpRequests(c -> c
|
||||
.dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll() // WebFlux 异步请求,无需认证,目的:SSE 场景
|
||||
.anyRequest().authenticated());
|
||||
.authorizeHttpRequests(c -> c.anyRequest().authenticated());
|
||||
|
||||
// 添加 Token Filter
|
||||
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
|
|
|
@ -56,10 +56,6 @@ public class LoginUser {
|
|||
*/
|
||||
@JsonIgnore
|
||||
private Map<String, Object> context;
|
||||
/**
|
||||
* 访问的租户编号
|
||||
*/
|
||||
private Long visitTenantId;
|
||||
|
||||
public void setContext(String key, Object value) {
|
||||
if (context == null) {
|
||||
|
|
|
@ -2,8 +2,6 @@ package cn.iocoder.yudao.framework.security.core.filter;
|
|||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
|
||||
import cn.iocoder.yudao.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
|
||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
|
@ -12,14 +10,16 @@ import cn.iocoder.yudao.framework.security.core.LoginUser;
|
|||
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
|
||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||
import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
|
||||
import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
|
@ -35,7 +35,7 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
|||
|
||||
private final GlobalExceptionHandler globalExceptionHandler;
|
||||
|
||||
private final OAuth2TokenCommonApi oauth2TokenApi;
|
||||
private final OAuth2TokenApi oauth2TokenApi;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("NullableProblems")
|
||||
|
|
|
@ -8,16 +8,14 @@ import lombok.extern.slf4j.Slf4j;
|
|||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||
import org.springframework.security.web.access.ExceptionTranslationFilter;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
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 java.io.IOException;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.FORBIDDEN;
|
||||
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED;
|
||||
|
||||
/**
|
||||
* 访问一个需要认证的 URL 资源,已经认证(登录)但是没有权限的情况下,返回 {@link GlobalErrorCodeConstants#FORBIDDEN} 错误码。
|
||||
|
|
|
@ -8,9 +8,9 @@ import org.springframework.security.core.AuthenticationException;
|
|||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.security.web.access.ExceptionTranslationFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED;
|
||||
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
package cn.iocoder.yudao.framework.security.core.service;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.skipPermissionCheck;
|
||||
|
||||
/**
|
||||
* 默认的 {@link SecurityFrameworkService} 实现类
|
||||
|
@ -19,7 +18,7 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti
|
|||
@AllArgsConstructor
|
||||
public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
|
||||
|
||||
private final PermissionCommonApi permissionApi;
|
||||
private final PermissionApi permissionApi;
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(String permission) {
|
||||
|
@ -28,12 +27,6 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
|
|||
|
||||
@Override
|
||||
public boolean hasAnyPermissions(String... permissions) {
|
||||
// 特殊:跨租户访问
|
||||
if (skipPermissionCheck()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 权限校验
|
||||
Long userId = getLoginUserId();
|
||||
if (userId == null) {
|
||||
return false;
|
||||
|
@ -48,12 +41,6 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
|
|||
|
||||
@Override
|
||||
public boolean hasAnyRoles(String... roles) {
|
||||
// 特殊:跨租户访问
|
||||
if (skipPermissionCheck()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 权限校验
|
||||
Long userId = getLoginUserId();
|
||||
if (userId == null) {
|
||||
return false;
|
||||
|
@ -68,12 +55,6 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
|
|||
|
||||
@Override
|
||||
public boolean hasAnyScopes(String... scope) {
|
||||
// 特殊:跨租户访问
|
||||
if (skipPermissionCheck()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 权限校验
|
||||
LoginUser user = SecurityFrameworkUtils.getLoginUser();
|
||||
if (user == null) {
|
||||
return false;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package cn.iocoder.yudao.framework.security.core.util;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||
|
@ -13,7 +12,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
|||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
|
@ -138,21 +137,4 @@ public class SecurityFrameworkUtils {
|
|||
return authenticationToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否条件跳过权限校验,包括数据权限、功能权限
|
||||
*
|
||||
* @return 是否跳过
|
||||
*/
|
||||
public static boolean skipPermissionCheck() {
|
||||
LoginUser loginUser = getLoginUser();
|
||||
if (loginUser == null) {
|
||||
return false;
|
||||
}
|
||||
if (loginUser.getVisitTenantId() == null) {
|
||||
return false;
|
||||
}
|
||||
// 重点:跨租户访问时,无法进行权限校验
|
||||
return ObjUtil.notEqual(loginUser.getVisitTenantId(), loginUser.getTenantId());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue