mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-11-04 04:08:43 +08:00 
			
		
		
		
	Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/mail-1.6.1
This commit is contained in:
		
							
								
								
									
										135
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										135
									
								
								README.md
									
									
									
									
									
								
							@@ -1,6 +1,4 @@
 | 
				
			|||||||
**严肃声明:现在、未来都不会有商业版本,所有功能全部开源!**
 | 
					**严肃声明:现在、未来都不会有商业版本,所有代码全部开源!**
 | 
				
			||||||
 | 
					 | 
				
			||||||
**拒绝虚假开源,售卖商业版,程序员不骗程序员!!**
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
**「我喜欢写代码,乐此不疲」**  
 | 
					**「我喜欢写代码,乐此不疲」**  
 | 
				
			||||||
**「我喜欢做开源,以此为乐」**
 | 
					**「我喜欢做开源,以此为乐」**
 | 
				
			||||||
@@ -27,7 +25,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
* 管理后台的 Vue3 版本采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin) ,Vue2 版本采用 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) 
 | 
					* 管理后台的 Vue3 版本采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin) ,Vue2 版本采用 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) 
 | 
				
			||||||
* 管理后台的移动端采用 [uni-app](https://github.com/dcloudio/uni-app) 方案,一份代码多终端适配,同时支持 APP、小程序、H5!
 | 
					* 管理后台的移动端采用 [uni-app](https://github.com/dcloudio/uni-app) 方案,一份代码多终端适配,同时支持 APP、小程序、H5!
 | 
				
			||||||
* 后端采用 Spring Boot、MySQL + MyBatis Plus、Redis + Redisson
 | 
					* 后端采用 Spring Boot 多模块架构、MySQL + MyBatis Plus、Redis + Redisson
 | 
				
			||||||
* 数据库可使用 MySQL、Oracle、PostgreSQL、SQL Server、MariaDB、国产达梦 DM、TiDB 等
 | 
					* 数据库可使用 MySQL、Oracle、PostgreSQL、SQL Server、MariaDB、国产达梦 DM、TiDB 等
 | 
				
			||||||
* 权限认证使用 Spring Security & Token & Redis,支持多终端、多种用户的认证系统,支持 SSO 单点登录
 | 
					* 权限认证使用 Spring Security & Token & Redis,支持多终端、多种用户的认证系统,支持 SSO 单点登录
 | 
				
			||||||
* 支持加载动态权限菜单,按钮级别权限控制,本地缓存提升性能
 | 
					* 支持加载动态权限菜单,按钮级别权限控制,本地缓存提升性能
 | 
				
			||||||
@@ -38,12 +36,12 @@
 | 
				
			|||||||
* 集成阿里云、腾讯云等短信渠道,集成 MinIO、阿里云、腾讯云、七牛云等云存储服务
 | 
					* 集成阿里云、腾讯云等短信渠道,集成 MinIO、阿里云、腾讯云、七牛云等云存储服务
 | 
				
			||||||
* 集成报表设计器,支持数据报表、图形报表、打印设计等
 | 
					* 集成报表设计器,支持数据报表、图形报表、打印设计等
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 项目名                | 说明                     | 传送门                                                                                                                               |
 | 
					| 项目名                  | 说明                     | 传送门                                                                                                                                 |
 | 
				
			||||||
|--------------------|------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
 | 
					|----------------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
 | 
				
			||||||
| `ruoyi-vue-pro`    | Spring Boot 多模块        | **[Gitee](https://gitee.com/zhijiantianya/ruoyi-vue-pro)**     [Github](https://github.com/YunaiV/ruoyi-vue-pro)   |
 | 
					| `ruoyi-vue-pro`      | Spring Boot 多模块        | **[Gitee](https://gitee.com/zhijiantianya/ruoyi-vue-pro)**     [Github](https://github.com/YunaiV/ruoyi-vue-pro)     |
 | 
				
			||||||
| `yudao-cloud`  | Spring Cloud 微服务       | **[Gitee](https://gitee.com/zhijiantianya/yudao-cloud)**     [Github](https://github.com/YunaiV/yudao-cloud)       |
 | 
					| `yudao-cloud`        | Spring Cloud 微服务       | **[Gitee](https://gitee.com/zhijiantianya/yudao-cloud)**     [Github](https://github.com/YunaiV/yudao-cloud)         |
 | 
				
			||||||
| `Spring-Boot-Labs` | Spring Boot & Cloud 入门 | **[Gitee](https://gitee.com/zhijiantianya/SpringBoot-Labs)**     [Github](https://github.com/YunaiV/SpringBoot-Labs) |
 | 
					| `Spring-Boot-Labs`   | Spring Boot & Cloud 入门 | **[Gitee](https://gitee.com/zhijiantianya/SpringBoot-Labs)**     [Github](https://github.com/YunaiV/SpringBoot-Labs) |
 | 
				
			||||||
 | `ruoyi-vue-pro-mini` | 精简版 移除工作流 支付等模块| **[Gitee](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/mini)**  |
 | 
					 | `ruoyi-vue-pro-mini` | 精简版:移除工作流、支付等模块        | **[Gitee](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/mini)**                                                                |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 😎 开源协议
 | 
					## 😎 开源协议
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -70,6 +68,7 @@
 | 
				
			|||||||
* 会员中心
 | 
					* 会员中心
 | 
				
			||||||
* 数据报表
 | 
					* 数据报表
 | 
				
			||||||
* 商城系统
 | 
					* 商城系统
 | 
				
			||||||
 | 
					* 微信公众号
 | 
				
			||||||
 | 
					
 | 
				
			||||||
> 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。
 | 
					> 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
@@ -96,8 +95,9 @@
 | 
				
			|||||||
| ⭐️  | 登录日志  | 系统登录日志记录查询,包含登录异常               |
 | 
					| ⭐️  | 登录日志  | 系统登录日志记录查询,包含登录异常               |
 | 
				
			||||||
| 🚀  | 错误码管理 | 系统所有错误码的管理,可在线修改错误提示,无需重启服务     |
 | 
					| 🚀  | 错误码管理 | 系统所有错误码的管理,可在线修改错误提示,无需重启服务     |
 | 
				
			||||||
|     | 通知公告  | 系统通知公告信息发布维护                    |
 | 
					|     | 通知公告  | 系统通知公告信息发布维护                    |
 | 
				
			||||||
| 🚀  | 敏感词  | 配置系统敏感词,支持标签分组                  |
 | 
					| 🚀  | 敏感词   | 配置系统敏感词,支持标签分组                  |
 | 
				
			||||||
| 🚀  | 应用管理  | 管理 SSO 单点登录的应用,支持多种 OAuth2 授权方式 |
 | 
					| 🚀  | 应用管理  | 管理 SSO 单点登录的应用,支持多种 OAuth2 授权方式 |
 | 
				
			||||||
 | 
					| 🚀  | 地区管理  | 展示省份、城市、区镇等城市信息,支持 IP 对应城市      |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 工作流程
 | 
					### 工作流程
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -132,7 +132,7 @@ ps:核心功能已经实现,正在对接微信小程序中...
 | 
				
			|||||||
|     | 表单构建     | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件         |
 | 
					|     | 表单构建     | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件         |
 | 
				
			||||||
| 🚀  | 配置管理     | 对系统动态配置常用参数,支持 SpringBoot 加载                 |
 | 
					| 🚀  | 配置管理     | 对系统动态配置常用参数,支持 SpringBoot 加载                 |
 | 
				
			||||||
| ⭐️  | 定时任务     | 在线(添加、修改、删除)任务调度包含执行结果日志                     |
 | 
					| ⭐️  | 定时任务     | 在线(添加、修改、删除)任务调度包含执行结果日志                     |
 | 
				
			||||||
| 🚀  | 文件服务     | 支持将文件存储到 S3(MinIO、阿里云、腾讯云、七牛云)、本地、FTP、数据库等      | 
 | 
					| 🚀  | 文件服务     | 支持将文件存储到 S3(MinIO、阿里云、腾讯云、七牛云)、本地、FTP、数据库等   | 
 | 
				
			||||||
| 🚀  | API 日志   | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题   |
 | 
					| 🚀  | API 日志   | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题   |
 | 
				
			||||||
|     | MySQL 监控 | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈              |
 | 
					|     | MySQL 监控 | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈              |
 | 
				
			||||||
|     | Redis 监控 | 监控 Redis 数据库的使用情况,使用的 Redis Key 管理           |
 | 
					|     | Redis 监控 | 监控 Redis 数据库的使用情况,使用的 Redis Key 管理           |
 | 
				
			||||||
@@ -153,6 +153,21 @@ ps:核心功能已经实现,正在对接微信小程序中...
 | 
				
			|||||||
| 🚀  | 报表设计器     | 支持数据报表、图形报表、打印设计等       |
 | 
					| 🚀  | 报表设计器     | 支持数据报表、图形报表、打印设计等       |
 | 
				
			||||||
| 🚀  | 大屏设计器     | 建设中... 拖拽式实现可视化数据大屏          |
 | 
					| 🚀  | 大屏设计器     | 建设中... 拖拽式实现可视化数据大屏          |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 微信公众号
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					|     | 功能     | 描述                            |
 | 
				
			||||||
 | 
					|-----|--------|-------------------------------|
 | 
				
			||||||
 | 
					| 🚀  | 账号管理   | 配置接入的微信公众号,可支持多个公众号           |
 | 
				
			||||||
 | 
					| 🚀  | 数据统计   | 统计公众号的用户增减、累计用户、消息概况、接口分析等数据  |
 | 
				
			||||||
 | 
					| 🚀  | 粉丝管理   | 查看已关注、取关的粉丝列表,可对粉丝进行同步、打标签等操作 |
 | 
				
			||||||
 | 
					| 🚀  | 消息管理   | 查看粉丝发送的消息列表,可主动回复粉丝消息         |
 | 
				
			||||||
 | 
					| 🚀  | 自动回复   | 自动回复粉丝发送的消息,支持关注回复、消息回复、关键字回复 |
 | 
				
			||||||
 | 
					| 🚀  | 标签管理   | 对公众号的标签进行创建、查询、修改、删除等操作       |
 | 
				
			||||||
 | 
					| 🚀  | 菜单管理   | 自定义公众号的菜单,也可以从公众号同步菜单         |
 | 
				
			||||||
 | 
					| 🚀  | 素材管理   | 管理公众号的图片、语音、视频等素材,支持在线播放语音、视频 |
 | 
				
			||||||
 | 
					| 🚀  | 图文草稿箱  | 新增常用的图文素材到草稿箱,可发布到公众号         |
 | 
				
			||||||
 | 
					| 🚀  | 图文发表记录 | 查看已发布成功的图文素材,支持删除操作           |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 商城系统
 | 
					### 商城系统
 | 
				
			||||||
 | 
					
 | 
				
			||||||
建设中...
 | 
					建设中...
 | 
				
			||||||
@@ -169,47 +184,49 @@ ps:核心功能已经实现,正在对接微信小程序中...
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## 🐨 技术栈
 | 
					## 🐨 技术栈
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 项目                      | 说明                 |
 | 
					| 项目                           | 说明                 |
 | 
				
			||||||
|-------------------------|-----------------------|
 | 
					|------------------------------|--------------------|
 | 
				
			||||||
| `yudao-dependencies`    | Maven 依赖版本管理       |
 | 
					| `yudao-dependencies`         | Maven 依赖版本管理       |
 | 
				
			||||||
| `yudao-framework`       | Java 框架拓展          |
 | 
					| `yudao-framework`            | Java 框架拓展          |
 | 
				
			||||||
| `yudao-server`          | 管理后台 + 用户 APP 的服务端 |
 | 
					| `yudao-server`               | 管理后台 + 用户 APP 的服务端 |
 | 
				
			||||||
| `yudao-ui-admin`        | 管理后台的 Vue2 前端项目     |
 | 
					| `yudao-ui-admin`             | 管理后台的 Vue2 前端项目    |
 | 
				
			||||||
| `yudao-ui-admin-vue3`   | 管理后台的 Vue3 前端项目     |
 | 
					| `yudao-ui-admin-vue3`        | 管理后台的 Vue3 前端项目    |
 | 
				
			||||||
| `yudao-ui-admin-uniapp` | 管理后台的 uni-app 多端项目  |
 | 
					| `yudao-ui-admin-uniapp`      | 管理后台的 uni-app 多端项目 |
 | 
				
			||||||
| `yudao-ui-app`          | 用户 APP 的 UI 界面     |
 | 
					| `yudao-ui-app`               | 用户 APP 的 UI 界面     |
 | 
				
			||||||
| `yudao-module-system`   | 系统功能的 Module 模块    |
 | 
					| `yudao-module-system`        | 系统功能的 Module 模块    |
 | 
				
			||||||
| `yudao-module-member`   | 会员中心的 Module 模块    |
 | 
					| `yudao-module-member`        | 会员中心的 Module 模块    |
 | 
				
			||||||
| `yudao-module-infra`    | 基础设施的 Module 模块    |
 | 
					| `yudao-module-infra`         | 基础设施的 Module 模块    |
 | 
				
			||||||
| `yudao-module-tool`     | 研发工具的 Module 模块    |
 | 
					| `yudao-module-bpm`           | 工作流程的 Module 模块    |
 | 
				
			||||||
| `yudao-module-bpm`      | 工作流程的 Module 模块    |
 | 
					| `yudao-module-pay`           | 支付系统的 Module 模块    |
 | 
				
			||||||
| `yudao-module-pay`      | 支付系统的 Module 模块    |
 | 
					| `yudao-module-mall`          | 商城系统的 Module 模块    |
 | 
				
			||||||
 | 
					| `yudao-module-mp`            | 微信公众号的 Module 模块   |
 | 
				
			||||||
 | 
					| `yudao-module-visualization` | 大屏报表 Module 模块     |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 后端
 | 
					### 后端
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 框架                                                                                         | 说明                   | 版本          | 学习指南                                                           |
 | 
					| 框架                                                                                          | 说明               | 版本          | 学习指南                                                           |
 | 
				
			||||||
|---------------------------------------------------------------------------------------------|-----------------------|-------------|----------------------------------------------------------------|
 | 
					|---------------------------------------------------------------------------------------------|------------------|-------------|----------------------------------------------------------------|
 | 
				
			||||||
| [Spring Boot](https://spring.io/projects/spring-boot)                                       | 应用开发框架             | 2.7.6       | [文档](https://github.com/YunaiV/SpringBoot-Labs)                |
 | 
					| [Spring Boot](https://spring.io/projects/spring-boot)                                       | 应用开发框架           | 2.7.7       | [文档](https://github.com/YunaiV/SpringBoot-Labs)                |
 | 
				
			||||||
| [MySQL](https://www.mysql.com/cn/)                                                          | 数据库服务器             | 5.7 / 8.0+  |                                                                |
 | 
					| [MySQL](https://www.mysql.com/cn/)                                                          | 数据库服务器           | 5.7 / 8.0+  |                                                                |
 | 
				
			||||||
| [Druid](https://github.com/alibaba/druid)                                                   | JDBC 连接池、监控组件     | 1.2.15      | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
 | 
					| [Druid](https://github.com/alibaba/druid)                                                   | JDBC 连接池、监控组件    | 1.2.15      | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
 | 
				
			||||||
| [MyBatis Plus](https://mp.baomidou.com/)                                                    | MyBatis 增强工具包       | 3.5.2       | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao)         |
 | 
					| [MyBatis Plus](https://mp.baomidou.com/)                                                    | MyBatis 增强工具包    | 3.5.3.1     | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao)         |
 | 
				
			||||||
| [Dynamic Datasource](https://dynamic-datasource.com/)                                       | 动态数据源               | 3.6.0       | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
 | 
					| [Dynamic Datasource](https://dynamic-datasource.com/)                                       | 动态数据源            | 3.6.1       | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
 | 
				
			||||||
| [Redis](https://redis.io/)                                                                  | key-value 数据库        | 5.0 / 6.0   |                                                                |
 | 
					| [Redis](https://redis.io/)                                                                  | key-value 数据库    | 5.0 / 6.0   |                                                                |
 | 
				
			||||||
| [Redisson](https://github.com/redisson/redisson)                                            | Redis 客户端            | 3.18.0      | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao)           |
 | 
					| [Redisson](https://github.com/redisson/redisson)                                            | Redis 客户端        | 3.18.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 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.5       | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
 | 
					| [Spring Security](https://github.com/spring-projects/spring-security)                       | Spring 安全框架      | 5.7.6       | [文档](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)      |
 | 
					| [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.7.2       | [文档](https://doc.iocoder.cn/bpm/)                                                     |
 | 
					| [Flowable](https://github.com/flowable/flowable-engine)                                     | 工作流引擎            | 6.8.0       | [文档](https://doc.iocoder.cn/bpm/)                              |
 | 
				
			||||||
| [Quartz](https://github.com/quartz-scheduler)                                               | 任务调度组件             | 2.3.2       | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao)             |
 | 
					| [Quartz](https://github.com/quartz-scheduler)                                               | 任务调度组件           | 2.3.2       | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao)             |
 | 
				
			||||||
| [Knife4j](https://gitee.com/xiaoym/knife4j)                                                 | Swagger 增强 UI 实现    | 3.0.3       | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao)         |
 | 
					| [Knife4j](https://gitee.com/xiaoym/knife4j)                                                 | Swagger 增强 UI 实现 | 3.0.3       | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao)         |
 | 
				
			||||||
| [Resilience4j](https://github.com/resilience4j/resilience4j)                                | 服务保障组件             | 1.7.1       | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao)    |
 | 
					| [Resilience4j](https://github.com/resilience4j/resilience4j)                                | 服务保障组件           | 1.7.1       | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao)    |
 | 
				
			||||||
| [SkyWalking](https://skywalking.apache.org/)                                                | 分布式应用追踪系统        | 8.12.0      | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?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.9       | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?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.3      |                                                                |
 | 
					| [Jackson](https://github.com/FasterXML/jackson)                                             | JSON 工具库         | 2.13.3      |                                                                |
 | 
				
			||||||
| [MapStruct](https://mapstruct.org/)                                                         | Java Bean 转换         | 1.5.3.Final | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao)       |
 | 
					| [MapStruct](https://mapstruct.org/)                                                         | Java Bean 转换     | 1.5.3.Final | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao)       |
 | 
				
			||||||
| [Lombok](https://projectlombok.org/)                                                        | 消除冗长的 Java 代码     | 1.18.24     | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao)          |
 | 
					| [Lombok](https://projectlombok.org/)                                                        | 消除冗长的 Java 代码    | 1.18.24     | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao)          |
 | 
				
			||||||
| [JUnit](https://junit.org/junit5/)                                                          | Java 单元测试框架        | 5.8.2       | -                                                              |
 | 
					| [JUnit](https://junit.org/junit5/)                                                          | Java 单元测试框架      | 5.8.2       | -                                                              |
 | 
				
			||||||
| [Mockito](https://github.com/mockito/mockito)                                               | Java Mock 框架         | 4.8.0       | -                                                              |
 | 
					| [Mockito](https://github.com/mockito/mockito)                                               | Java Mock 框架     | 4.8.0       | -                                                              |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### [管理后台 Vue2 前端](./yudao-ui-admin)
 | 
					### [管理后台 Vue2 前端](./yudao-ui-admin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -220,22 +237,22 @@ ps:核心功能已经实现,正在对接微信小程序中...
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### [管理后台 Vue3 前端](./yudao-ui-admin-vue3)
 | 
					### [管理后台 Vue3 前端](./yudao-ui-admin-vue3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 框架                                                                  |     说明      |   版本   |
 | 
					| 框架                                                                   |      说明      |   版本   |
 | 
				
			||||||
|----------------------------------------------------------------------|:------------:|:------:|
 | 
					|----------------------------------------------------------------------|:------------:|:------:|
 | 
				
			||||||
| [Vue](https://staging-cn.vuejs.org/)                                 |   Vue 框架    | 3.2.45 |
 | 
					| [Vue](https://staging-cn.vuejs.org/)                                 |    Vue 框架    | 3.2.45 |
 | 
				
			||||||
| [Vite](https://cn.vitejs.dev//)                                      | 开发与构建工具  | 4.0.3  |
 | 
					| [Vite](https://cn.vitejs.dev//)                                      |   开发与构建工具    | 4.0.4  |
 | 
				
			||||||
| [Element Plus](https://element-plus.org/zh-CN/)                      | Element Plus | 2.2.27 |
 | 
					| [Element Plus](https://element-plus.org/zh-CN/)                      | Element Plus | 2.2.28 |
 | 
				
			||||||
| [TypeScript](https://www.typescriptlang.org/docs/)                   |  TypeScript  | 4.9.4  |
 | 
					| [TypeScript](https://www.typescriptlang.org/docs/)                   |  TypeScript  | 4.9.4  |
 | 
				
			||||||
| [pinia](https://pinia.vuejs.org/)                                    |    vuex5     | 2.0.28 |
 | 
					| [pinia](https://pinia.vuejs.org/)                                    |    vuex5     | 2.0.28 |
 | 
				
			||||||
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) |    国际化     | 9.2.2  |
 | 
					| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) |     国际化      | 9.2.2  |
 | 
				
			||||||
| [vxe-table](https://vxetable.cn/)                                    |  vue最强表单  | 4.3.7  |
 | 
					| [vxe-table](https://vxetable.cn/)                                    |   vue最强表单    | 4.3.9  |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### [管理后台 uni-app 跨端](./yudao-ui-admin-uniapp)
 | 
					### [管理后台 uni-app 跨端](./yudao-ui-admin-uniapp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 框架                                                                  | 说明               | 版本     |
 | 
					| 框架                                              | 说明                 | 版本     |
 | 
				
			||||||
|----------------------------------------------------------------------|------------------|--------|
 | 
					|-------------------------------------------------|--------------------|--------|
 | 
				
			||||||
| [uni-app](hhttps://github.com/dcloudio/uni-app)                                 | 跨平台框架           | 2.0.0 |
 | 
					| [uni-app](hhttps://github.com/dcloudio/uni-app) | 跨平台框架              | 2.0.0  |
 | 
				
			||||||
| [uni-ui](https://github.com/dcloudio/uni-ui)                                      | 基于 uni-app 的 UI 框架          | 1.4.20  |
 | 
					| [uni-ui](https://github.com/dcloudio/uni-ui)    | 基于 uni-app 的 UI 框架 | 1.4.20 |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 🐷 演示图
 | 
					## 🐷 演示图
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@
 | 
				
			|||||||
    "adminTenentId": "1",
 | 
					    "adminTenentId": "1",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    "appApi": "http://127.0.0.1:48080/app-api",
 | 
					    "appApi": "http://127.0.0.1:48080/app-api",
 | 
				
			||||||
    "appToken": "test1",
 | 
					    "appToken": "test247",
 | 
				
			||||||
    "appTenentId": "1"
 | 
					    "appTenentId": "1"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "gateway": {
 | 
					  "gateway": {
 | 
				
			||||||
@@ -15,6 +15,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    "appApi": "http://127.0.0.1:8888/app-api",
 | 
					    "appApi": "http://127.0.0.1:8888/app-api",
 | 
				
			||||||
    "appToken": "test1",
 | 
					    "appToken": "test1",
 | 
				
			||||||
    "appTenentId": "1"
 | 
					    "appTenantId": "1"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										20
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								pom.xml
									
									
									
									
									
								
							@@ -14,12 +14,13 @@
 | 
				
			|||||||
        <module>yudao-server</module>
 | 
					        <module>yudao-server</module>
 | 
				
			||||||
        <!-- 各种 module 拓展 -->
 | 
					        <!-- 各种 module 拓展 -->
 | 
				
			||||||
        <module>yudao-module-member</module>
 | 
					        <module>yudao-module-member</module>
 | 
				
			||||||
        <module>yudao-module-bpm</module>
 | 
					 | 
				
			||||||
        <module>yudao-module-system</module>
 | 
					        <module>yudao-module-system</module>
 | 
				
			||||||
        <module>yudao-module-infra</module>
 | 
					        <module>yudao-module-infra</module>
 | 
				
			||||||
        <module>yudao-module-pay</module>
 | 
					        <module>yudao-module-pay</module>
 | 
				
			||||||
        <module>yudao-module-mall</module>
 | 
					<!--        <module>yudao-module-bpm</module>-->
 | 
				
			||||||
        <module>yudao-module-visualization</module>
 | 
					<!--        <module>yudao-module-visualization</module>-->
 | 
				
			||||||
 | 
					<!--        <module>yudao-module-mp</module>-->
 | 
				
			||||||
 | 
					        <!--        <module>yudao-module-mall</module>-->
 | 
				
			||||||
        <!-- 示例项目 -->
 | 
					        <!-- 示例项目 -->
 | 
				
			||||||
        <module>yudao-example</module>
 | 
					        <module>yudao-example</module>
 | 
				
			||||||
    </modules>
 | 
					    </modules>
 | 
				
			||||||
@@ -29,15 +30,16 @@
 | 
				
			|||||||
    <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
 | 
					    <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <properties>
 | 
					    <properties>
 | 
				
			||||||
        <revision>1.6.5-snapshot</revision>
 | 
					        <revision>1.6.6-snapshot</revision>
 | 
				
			||||||
        <!-- Maven 相关 -->
 | 
					        <!-- Maven 相关 -->
 | 
				
			||||||
        <java.version>1.8</java.version>
 | 
					        <java.version>1.8</java.version>
 | 
				
			||||||
        <maven.compiler.source>${java.version}</maven.compiler.source>
 | 
					        <maven.compiler.source>${java.version}</maven.compiler.source>
 | 
				
			||||||
        <maven.compiler.target>${java.version}</maven.compiler.target>
 | 
					        <maven.compiler.target>${java.version}</maven.compiler.target>
 | 
				
			||||||
        <maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version>
 | 
					        <maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version>
 | 
				
			||||||
        <maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
 | 
					        <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
 | 
				
			||||||
        <!-- 看看咋放到 bom 里 -->
 | 
					        <!-- 看看咋放到 bom 里 -->
 | 
				
			||||||
        <lombok.version>1.18.24</lombok.version>
 | 
					        <lombok.version>1.18.24</lombok.version>
 | 
				
			||||||
 | 
					        <spring.boot.version>2.7.7</spring.boot.version>
 | 
				
			||||||
        <mapstruct.version>1.5.3.Final</mapstruct.version>
 | 
					        <mapstruct.version>1.5.3.Final</mapstruct.version>
 | 
				
			||||||
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 | 
					        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 | 
				
			||||||
    </properties>
 | 
					    </properties>
 | 
				
			||||||
@@ -64,13 +66,19 @@
 | 
				
			|||||||
                    <artifactId>maven-surefire-plugin</artifactId>
 | 
					                    <artifactId>maven-surefire-plugin</artifactId>
 | 
				
			||||||
                    <version>${maven-surefire-plugin.version}</version>
 | 
					                    <version>${maven-surefire-plugin.version}</version>
 | 
				
			||||||
                </plugin>
 | 
					                </plugin>
 | 
				
			||||||
                <!-- maven-compiler-plugin 插件,解决 Lombok + MapStruct 组合 -->
 | 
					                <!-- maven-compiler-plugin 插件,解决 spring-boot-configuration-processor + Lombok + MapStruct 组合 -->
 | 
				
			||||||
 | 
					                <!-- https://stackoverflow.com/questions/33483697/re-run-spring-boot-configuration-annotation-processor-to-update-generated-metada -->
 | 
				
			||||||
                <plugin>
 | 
					                <plugin>
 | 
				
			||||||
                    <groupId>org.apache.maven.plugins</groupId>
 | 
					                    <groupId>org.apache.maven.plugins</groupId>
 | 
				
			||||||
                    <artifactId>maven-compiler-plugin</artifactId>
 | 
					                    <artifactId>maven-compiler-plugin</artifactId>
 | 
				
			||||||
                    <version>${maven-compiler-plugin.version}</version>
 | 
					                    <version>${maven-compiler-plugin.version}</version>
 | 
				
			||||||
                    <configuration>
 | 
					                    <configuration>
 | 
				
			||||||
                        <annotationProcessorPaths>
 | 
					                        <annotationProcessorPaths>
 | 
				
			||||||
 | 
					                            <path>
 | 
				
			||||||
 | 
					                                <groupId>org.springframework.boot</groupId>
 | 
				
			||||||
 | 
					                                <artifactId>spring-boot-configuration-processor</artifactId>
 | 
				
			||||||
 | 
					                                <version>${spring.boot.version}</version>
 | 
				
			||||||
 | 
					                            </path>
 | 
				
			||||||
                            <path>
 | 
					                            <path>
 | 
				
			||||||
                                <groupId>org.projectlombok</groupId>
 | 
					                                <groupId>org.projectlombok</groupId>
 | 
				
			||||||
                                <artifactId>lombok</artifactId>
 | 
					                                <artifactId>lombok</artifactId>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1344,6 +1344,7 @@ CREATE TABLE `jimu_report_share`  (
 | 
				
			|||||||
  `last_update_time` datetime NULL DEFAULT NULL COMMENT '最后更新时间',
 | 
					  `last_update_time` datetime NULL DEFAULT NULL COMMENT '最后更新时间',
 | 
				
			||||||
  `term_of_validity` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '有效期(0:永久有效,1:1天,2:7天)',
 | 
					  `term_of_validity` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '有效期(0:永久有效,1:1天,2:7天)',
 | 
				
			||||||
  `status` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否过期(0未过期,1已过期)',
 | 
					  `status` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否过期(0未过期,1已过期)',
 | 
				
			||||||
 | 
					  `preview_lock_status` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码锁状态',
 | 
				
			||||||
  PRIMARY KEY (`id`) USING BTREE
 | 
					  PRIMARY KEY (`id`) USING BTREE
 | 
				
			||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '积木报表预览权限表' ROW_FORMAT = Dynamic;
 | 
					) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '积木报表预览权限表' ROW_FORMAT = Dynamic;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										274
									
								
								sql/mysql/optional/mp.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										274
									
								
								sql/mysql/optional/mp.sql
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -262,5 +262,6 @@ INSERT INTO `system_menu` VALUES (1266, '客户端更新', 'system:oauth2-client
 | 
				
			|||||||
INSERT INTO `system_menu` VALUES (1267, '客户端删除', 'system:oauth2-client:delete', 3, 4, 1263, '', '', '', 0, b'1', b'1', '', '2022-05-10 16:26:33', '1', '2022-05-11 00:31:33', b'0');
 | 
					INSERT INTO `system_menu` VALUES (1267, '客户端删除', 'system:oauth2-client:delete', 3, 4, 1263, '', '', '', 0, b'1', b'1', '', '2022-05-10 16:26:33', '1', '2022-05-11 00:31:33', b'0');
 | 
				
			||||||
INSERT INTO `system_menu` VALUES (1281, '可视化报表', '', 1, 12, 0, '/visualization', 'ep:histogram', NULL, 0, b'1', b'1', '1', '2022-07-10 20:22:15', '1', '2022-07-10 20:33:30', b'0');
 | 
					INSERT INTO `system_menu` VALUES (1281, '可视化报表', '', 1, 12, 0, '/visualization', 'ep:histogram', NULL, 0, b'1', b'1', '1', '2022-07-10 20:22:15', '1', '2022-07-10 20:33:30', b'0');
 | 
				
			||||||
INSERT INTO `system_menu` VALUES (1282, '积木报表', '', 2, 1, 1281, 'jimu-report', 'ep:histogram', 'visualization/jmreport/index', 0, b'1', b'1', '1', '2022-07-10 20:26:36', '1', '2022-07-28 21:17:34', b'0');
 | 
					INSERT INTO `system_menu` VALUES (1282, '积木报表', '', 2, 1, 1281, 'jimu-report', 'ep:histogram', 'visualization/jmreport/index', 0, b'1', b'1', '1', '2022-07-10 20:26:36', '1', '2022-07-28 21:17:34', b'0');
 | 
				
			||||||
 | 
					INSERT INTO `system_menu` VALUES (1283, 'webSocket连接', '', 2, 14, 2, 'webSocket', 'ep:turn-off', 'infra/webSocket/index', 0, b'1', b'1', '1', '2023-01-01 11:43:04', '1', '2023-01-01 11:43:04', b'0');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
SET FOREIGN_KEY_CHECKS = 1;
 | 
					SET FOREIGN_KEY_CHECKS = 1;
 | 
				
			||||||
 
 | 
				
			|||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,85 +0,0 @@
 | 
				
			|||||||
DROP TABLE IF EXISTS `coupon`;
 | 
					 | 
				
			||||||
CREATE TABLE `coupon`
 | 
					 | 
				
			||||||
(
 | 
					 | 
				
			||||||
    `id`                        bigint                                                         NOT NULL AUTO_INCREMENT COMMENT '用户ID',
 | 
					 | 
				
			||||||
    `type`                      varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci  NOT NULL COMMENT '优惠券类型 reward-满减 discount-折扣 random-随机',
 | 
					 | 
				
			||||||
    `name`                      varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci   NOT NULL COMMENT '优惠券名称',
 | 
					 | 
				
			||||||
    `coupon_type_id`            bigint UNSIGNED                                                         DEFAULT 0 COMMENT '优惠券类型id',
 | 
					 | 
				
			||||||
    `coupon_code`               varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci  NOT NULL COMMENT '优惠券编码',
 | 
					 | 
				
			||||||
    `member_id`                 bigint UNSIGNED                                                NOT NULL DEFAULT 0 COMMENT '领用人',
 | 
					 | 
				
			||||||
    `use_order_id`              bigint UNSIGNED                                                NOT NULL DEFAULT 0 COMMENT '优惠券使用订单id',
 | 
					 | 
				
			||||||
    `goods_type`                tinyint(1) UNSIGNED                                            NOT NULL DEFAULT 0 COMMENT '适用商品类型1-全部商品可用;2-指定商品可用;3-指定商品不可用',
 | 
					 | 
				
			||||||
    `goods_ids`                 varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '适用商品id',
 | 
					 | 
				
			||||||
    `at_least`                  decimal(10, 2) UNSIGNED                                        NOT NULL DEFAULT 0 COMMENT '最小金额',
 | 
					 | 
				
			||||||
    `money`                     decimal(10, 2) UNSIGNED                                        NOT NULL DEFAULT 0 COMMENT '面额',
 | 
					 | 
				
			||||||
    `discount`                  decimal(10, 2) UNSIGNED                                        NOT NULL DEFAULT 0 COMMENT '1 =< 折扣 <= 9.9 当type为discount时需要添加',
 | 
					 | 
				
			||||||
    `discount_limit`            decimal(10, 2) UNSIGNED                                        NOT NULL DEFAULT 0 COMMENT '最多折扣金额 当type为discount时可选择性添加',
 | 
					 | 
				
			||||||
    `whether_forbid_preference` tinyint(1) UNSIGNED                                            NOT NULL DEFAULT 0 COMMENT '优惠叠加 0-不限制 1- 优惠券仅原价购买商品时可用',
 | 
					 | 
				
			||||||
    `whether_expire_notice`     tinyint(1) UNSIGNED                                            NOT NULL DEFAULT 0 COMMENT '是否开启过期提醒0-不开启 1-开启',
 | 
					 | 
				
			||||||
    `expire_notice_fixed_term`  int(11) UNSIGNED                                               NOT NULL DEFAULT 0 COMMENT '过期前N天提醒',
 | 
					 | 
				
			||||||
    `whether_noticed`           tinyint(1) UNSIGNED                                            NOT NULL DEFAULT 0 COMMENT '是否已提醒',
 | 
					 | 
				
			||||||
    `state`                     tinyint(4) UNSIGNED                                            NOT NULL DEFAULT 0 COMMENT '优惠券状态 1已领用(未使用) 2已使用 3已过期',
 | 
					 | 
				
			||||||
    `get_type`                  tinyint(1) UNSIGNED                                            NOT NULL DEFAULT 0 COMMENT '获取方式1订单2.直接领取3.活动领取 4转赠 5分享获取',
 | 
					 | 
				
			||||||
    `fetch_time`                datetime                                                       NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '领取时间',
 | 
					 | 
				
			||||||
    `use_time`                  datetime                                                       NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '使用时间',
 | 
					 | 
				
			||||||
    `start_time`                datetime                                                       NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '可使用的开始时间',
 | 
					 | 
				
			||||||
    `end_time`                  datetime                                                       NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '有效期结束时间',
 | 
					 | 
				
			||||||
    `creator`                   varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci   NULL     DEFAULT '' COMMENT '创建者',
 | 
					 | 
				
			||||||
    `create_time`               datetime                                                       NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 | 
					 | 
				
			||||||
    `updater`                   varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci   NULL     DEFAULT '' COMMENT '更新者',
 | 
					 | 
				
			||||||
    `update_time`               datetime                                                       NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 | 
					 | 
				
			||||||
    `deleted`                   bit(1)                                                         NOT NULL DEFAULT b'0' COMMENT '是否删除',
 | 
					 | 
				
			||||||
    `tenant_id`                 bigint                                                         NOT NULL DEFAULT 0 COMMENT '租户编号',
 | 
					 | 
				
			||||||
    PRIMARY KEY (`id`) USING BTREE
 | 
					 | 
				
			||||||
) ENGINE = InnoDB
 | 
					 | 
				
			||||||
  AUTO_INCREMENT = 119
 | 
					 | 
				
			||||||
  CHARACTER SET = utf8mb4
 | 
					 | 
				
			||||||
  COLLATE = utf8mb4_unicode_ci COMMENT = '优惠券';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
DROP TABLE IF EXISTS `coupon_templete`;
 | 
					 | 
				
			||||||
CREATE TABLE `coupon_templete`
 | 
					 | 
				
			||||||
(
 | 
					 | 
				
			||||||
    `id`                        bigint                                                       NOT NULL AUTO_INCREMENT COMMENT '用户ID',
 | 
					 | 
				
			||||||
    `type`                      varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '优惠券类型 reward-满减 discount-折扣 random-随机',
 | 
					 | 
				
			||||||
    `name`                      varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '优惠券名称',
 | 
					 | 
				
			||||||
    `coupon_name_remark`        varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '名称备注',
 | 
					 | 
				
			||||||
    `image`                     varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '优惠券图片',
 | 
					 | 
				
			||||||
    `count`                     int(11)                                                      NOT NULL DEFAULT 0 COMMENT '发放数量',
 | 
					 | 
				
			||||||
    `lead_count`                int(11)                                                      NOT NULL DEFAULT 0 COMMENT '已领取数量',
 | 
					 | 
				
			||||||
    `used_count`                int(11) UNSIGNED                                             NOT NULL DEFAULT 0 COMMENT '已使用数量',
 | 
					 | 
				
			||||||
    `goods_type`                tinyint(1) UNSIGNED                                          NOT NULL DEFAULT 1 COMMENT '适用商品类型1-全部商品可用;2-指定商品可用;3-指定商品不可用',
 | 
					 | 
				
			||||||
    `product_ids`               varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '适用商品id',
 | 
					 | 
				
			||||||
    `has_use_limit`             tinyint(1) UNSIGNED                                          NOT NULL DEFAULT 0 COMMENT '使用门槛0-无门槛 1-有门槛',
 | 
					 | 
				
			||||||
    `at_least`                  decimal(10, 2)                                               NOT NULL DEFAULT 0 COMMENT '满多少元使用 0代表无限制',
 | 
					 | 
				
			||||||
    `money`                     decimal(10, 2)                                               NOT NULL DEFAULT 0 COMMENT '发放面额 当type为reward时需要添加',
 | 
					 | 
				
			||||||
    `discount`                  decimal(10, 2) UNSIGNED                                      NOT NULL DEFAULT 0 COMMENT '1 =< 折扣 <= 9.9 当type为discount时需要添加',
 | 
					 | 
				
			||||||
    `discount_limit`            decimal(10, 2)                                               NOT NULL DEFAULT 0 COMMENT '最多折扣金额 当type为discount时可选择性添加',
 | 
					 | 
				
			||||||
    `min_money`                 decimal(10, 2) UNSIGNED                                      NOT NULL DEFAULT 0 COMMENT '最低金额 当type为radom时需要添加',
 | 
					 | 
				
			||||||
    `max_money`                 decimal(10, 2) UNSIGNED                                      NOT NULL DEFAULT 0 COMMENT '最大金额 当type为radom时需要添加',
 | 
					 | 
				
			||||||
    `validity_type`             tinyint(1) UNSIGNED                                          NOT NULL DEFAULT 0 COMMENT '过期类型1-时间范围过期 2-领取之日固定日期后过期 3-领取次日固定日期后过期',
 | 
					 | 
				
			||||||
    `start_use_time`            datetime COMMENT '使用开始日期 过期类型1时必填',
 | 
					 | 
				
			||||||
    `end_use_time`              datetime COMMENT '使用结束日期 过期类型1时必填',
 | 
					 | 
				
			||||||
    `fixed_term`                int(11) UNSIGNED                                             NOT NULL DEFAULT 0 COMMENT '当validity_type为2或者3时需要添加 领取之日起或者次日N天内有效',
 | 
					 | 
				
			||||||
    `whether_limitless`         tinyint(1) UNSIGNED                                          NOT NULL DEFAULT 0 COMMENT '是否无限制0-否 1是',
 | 
					 | 
				
			||||||
    `max_fetch`                 int(11) UNSIGNED                                             NOT NULL DEFAULT 0 COMMENT '每人最大领取个数',
 | 
					 | 
				
			||||||
    `whether_expire_notice`     tinyint(1) UNSIGNED                                          NOT NULL DEFAULT 0 COMMENT '是否开启过期提醒0-不开启 1-开启',
 | 
					 | 
				
			||||||
    `expire_notice_fixed_term`  int(11) UNSIGNED                                             NOT NULL DEFAULT 0 COMMENT '过期前N天提醒',
 | 
					 | 
				
			||||||
    `whether_forbid_preference` tinyint(1) UNSIGNED                                          NOT NULL DEFAULT 0 COMMENT '优惠叠加 0-不限制 1- 优惠券仅原价购买商品时可用',
 | 
					 | 
				
			||||||
    `whether_show`              int(11) UNSIGNED                                             NOT NULL DEFAULT 0 COMMENT '是否显示',
 | 
					 | 
				
			||||||
    `discount_order_money`      decimal(10, 2) UNSIGNED                                      NOT NULL DEFAULT 0 COMMENT '订单的优惠总金额',
 | 
					 | 
				
			||||||
    `order_money`               decimal(10, 2) UNSIGNED                                      NOT NULL DEFAULT 0 COMMENT '用券总成交额',
 | 
					 | 
				
			||||||
    `whether_forbidden`         tinyint(1) UNSIGNED                                          NOT NULL DEFAULT 0 COMMENT '是否禁止发放0-否 1-是',
 | 
					 | 
				
			||||||
    `order_goods_num`           int(11) UNSIGNED                                             NOT NULL DEFAULT 0 COMMENT '使用优惠券购买的商品数量',
 | 
					 | 
				
			||||||
    `status`                    tinyint(11)                                                  NOT NULL DEFAULT 0 COMMENT '状态(1进行中2已结束-1已关闭)',
 | 
					 | 
				
			||||||
    `end_time`                  datetime COMMENT '有效日期结束时间',
 | 
					 | 
				
			||||||
    `creator`                   varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL     DEFAULT '' COMMENT '创建者',
 | 
					 | 
				
			||||||
    `create_time`               datetime                                                     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 | 
					 | 
				
			||||||
    `updater`                   varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL     DEFAULT '' COMMENT '更新者',
 | 
					 | 
				
			||||||
    `update_time`               datetime                                                     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 | 
					 | 
				
			||||||
    `deleted`                   bit(1)                                                       NOT NULL DEFAULT b'0' COMMENT '是否删除',
 | 
					 | 
				
			||||||
    `tenant_id`                 bigint                                                       NOT NULL DEFAULT 0 COMMENT '租户编号',
 | 
					 | 
				
			||||||
    PRIMARY KEY (`id`) USING BTREE
 | 
					 | 
				
			||||||
) ENGINE = InnoDB
 | 
					 | 
				
			||||||
  AUTO_INCREMENT = 119
 | 
					 | 
				
			||||||
  CHARACTER SET = utf8mb4
 | 
					 | 
				
			||||||
  COLLATE = utf8mb4_unicode_ci COMMENT = '优惠券模板';
 | 
					 | 
				
			||||||
@@ -1,315 +0,0 @@
 | 
				
			|||||||
/*
 | 
					 | 
				
			||||||
 Navicat Premium Data Transfer
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 Source Server         : 127.0.0.1 MySQL
 | 
					 | 
				
			||||||
 Source Server Type    : MySQL
 | 
					 | 
				
			||||||
 Source Server Version : 80026
 | 
					 | 
				
			||||||
 Source Host           : localhost:3306
 | 
					 | 
				
			||||||
 Source Schema         : ruoyi-vue-pro
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 Target Server Type    : MySQL
 | 
					 | 
				
			||||||
 Target Server Version : 80026
 | 
					 | 
				
			||||||
 File Encoding         : 65001
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 Date: 01/08/2022 23:01:36
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
SET NAMES utf8mb4;
 | 
					 | 
				
			||||||
SET FOREIGN_KEY_CHECKS = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
-- Table structure for market_activity
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
DROP TABLE IF EXISTS `market_activity`;
 | 
					 | 
				
			||||||
CREATE TABLE `market_activity`  (
 | 
					 | 
				
			||||||
                                    `id` bigint NOT NULL AUTO_INCREMENT COMMENT '活动编号',
 | 
					 | 
				
			||||||
                                    `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '活动标题',
 | 
					 | 
				
			||||||
                                    `activity_type` tinyint NOT NULL COMMENT '活动类型',
 | 
					 | 
				
			||||||
                                    `status` tinyint NOT NULL DEFAULT -1 COMMENT '活动状态',
 | 
					 | 
				
			||||||
                                    `start_time` datetime NOT NULL COMMENT '开始时间',
 | 
					 | 
				
			||||||
                                    `end_time` datetime NOT NULL COMMENT '结束时间',
 | 
					 | 
				
			||||||
                                    `invalid_time` datetime NULL DEFAULT NULL COMMENT '失效时间',
 | 
					 | 
				
			||||||
                                    `delete_time` datetime NULL DEFAULT NULL COMMENT '删除时间',
 | 
					 | 
				
			||||||
                                    `time_limited_discount` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '限制折扣字符串,使用 JSON 序列化成字符串存储',
 | 
					 | 
				
			||||||
                                    `full_privilege` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '限制折扣字符串,使用 JSON 序列化成字符串存储',
 | 
					 | 
				
			||||||
                                    `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
 | 
					 | 
				
			||||||
                                    `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 | 
					 | 
				
			||||||
                                    `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
 | 
					 | 
				
			||||||
                                    `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 | 
					 | 
				
			||||||
                                    `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
 | 
					 | 
				
			||||||
                                    `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
 | 
					 | 
				
			||||||
                                    PRIMARY KEY (`id`) USING BTREE
 | 
					 | 
				
			||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '促销活动';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
-- Records of market_activity
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
BEGIN;
 | 
					 | 
				
			||||||
COMMIT;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
-- Table structure for market_banner
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
DROP TABLE IF EXISTS `market_banner`;
 | 
					 | 
				
			||||||
CREATE TABLE `market_banner`  (
 | 
					 | 
				
			||||||
                                  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'Banner编号',
 | 
					 | 
				
			||||||
                                  `title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'Banner标题',
 | 
					 | 
				
			||||||
                                  `pic_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '图片URL',
 | 
					 | 
				
			||||||
                                  `status` tinyint NOT NULL DEFAULT -1 COMMENT '活动状态',
 | 
					 | 
				
			||||||
                                  `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '跳转地址',
 | 
					 | 
				
			||||||
                                  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
 | 
					 | 
				
			||||||
                                  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 | 
					 | 
				
			||||||
                                  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
 | 
					 | 
				
			||||||
                                  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 | 
					 | 
				
			||||||
                                  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
 | 
					 | 
				
			||||||
                                  `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
 | 
					 | 
				
			||||||
                                  `sort` tinyint NULL DEFAULT NULL COMMENT '排序',
 | 
					 | 
				
			||||||
                                  `memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '描述',
 | 
					 | 
				
			||||||
                                  PRIMARY KEY (`id`) USING BTREE
 | 
					 | 
				
			||||||
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Banner管理';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
-- Records of market_banner
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
BEGIN;
 | 
					 | 
				
			||||||
COMMIT;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
-- Table structure for member_address
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
DROP TABLE IF EXISTS `member_address`;
 | 
					 | 
				
			||||||
CREATE TABLE `member_address`  (
 | 
					 | 
				
			||||||
                                   `id` bigint NOT NULL AUTO_INCREMENT COMMENT '收件地址编号',
 | 
					 | 
				
			||||||
                                   `user_id` bigint NOT NULL COMMENT '用户编号',
 | 
					 | 
				
			||||||
                                   `name` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '收件人名称',
 | 
					 | 
				
			||||||
                                   `mobile` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '手机号',
 | 
					 | 
				
			||||||
                                   `area_id` bigint NOT NULL COMMENT '地区编码',
 | 
					 | 
				
			||||||
                                   `post_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮编',
 | 
					 | 
				
			||||||
                                   `detail_address` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '收件详细地址',
 | 
					 | 
				
			||||||
                                   `defaulted` bit(1) NOT NULL COMMENT '是否默认',
 | 
					 | 
				
			||||||
                                   `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
 | 
					 | 
				
			||||||
                                   `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 | 
					 | 
				
			||||||
                                   `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
 | 
					 | 
				
			||||||
                                   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 | 
					 | 
				
			||||||
                                   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
 | 
					 | 
				
			||||||
                                   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
 | 
					 | 
				
			||||||
                                   PRIMARY KEY (`id`) USING BTREE,
 | 
					 | 
				
			||||||
                                   INDEX `idx_userId`(`user_id` ASC) USING BTREE
 | 
					 | 
				
			||||||
) ENGINE = InnoDB AUTO_INCREMENT = 21 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户收件地址';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
-- Records of member_address
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
BEGIN;
 | 
					 | 
				
			||||||
INSERT INTO `member_address` (`id`, `user_id`, `name`, `mobile`, `area_id`, `post_code`, `detail_address`, `defaulted`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (21, 1, 'yunai', '15601691300', 610632, '200000', '芋道源码 233 号 666 室', b'1', '1', '2022-08-01 22:46:35', '1', '2022-08-01 22:46:35', b'0', 1);
 | 
					 | 
				
			||||||
COMMIT;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
-- Table structure for product_brand
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
DROP TABLE IF EXISTS `product_brand`;
 | 
					 | 
				
			||||||
CREATE TABLE `product_brand`  (
 | 
					 | 
				
			||||||
                                  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '品牌编号',
 | 
					 | 
				
			||||||
                                  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '品牌名称',
 | 
					 | 
				
			||||||
                                  `pic_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '品牌图片',
 | 
					 | 
				
			||||||
                                  `sort` int NULL DEFAULT 0 COMMENT '品牌排序',
 | 
					 | 
				
			||||||
                                  `description` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '品牌描述',
 | 
					 | 
				
			||||||
                                  `status` tinyint NOT NULL COMMENT '状态',
 | 
					 | 
				
			||||||
                                  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
 | 
					 | 
				
			||||||
                                  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 | 
					 | 
				
			||||||
                                  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
 | 
					 | 
				
			||||||
                                  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 | 
					 | 
				
			||||||
                                  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
 | 
					 | 
				
			||||||
                                  `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
 | 
					 | 
				
			||||||
                                  PRIMARY KEY (`id`) USING BTREE
 | 
					 | 
				
			||||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '商品品牌';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
-- Records of product_brand
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
BEGIN;
 | 
					 | 
				
			||||||
INSERT INTO `product_brand` (`id`, `name`, `pic_url`, `sort`, `description`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, '苹果', 'http://test.yudao.iocoder.cn/e3726713fa56db5717c78c011762fcc7a251db12735c3581470638b8e1fa17e2.jpeg', 0, '是上市', 0, '1', '2022-07-30 22:12:18', '1', '2022-07-30 22:13:55', b'0', 1);
 | 
					 | 
				
			||||||
COMMIT;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
-- Table structure for product_category
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
DROP TABLE IF EXISTS `product_category`;
 | 
					 | 
				
			||||||
CREATE TABLE `product_category`  (
 | 
					 | 
				
			||||||
                                     `id` bigint NOT NULL AUTO_INCREMENT COMMENT '分类编号',
 | 
					 | 
				
			||||||
                                     `parent_id` bigint NOT NULL COMMENT '父分类编号',
 | 
					 | 
				
			||||||
                                     `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类名称',
 | 
					 | 
				
			||||||
                                     `pic_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类图片',
 | 
					 | 
				
			||||||
                                     `sort` int NULL DEFAULT 0 COMMENT '分类排序',
 | 
					 | 
				
			||||||
                                     `description` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '分类描述',
 | 
					 | 
				
			||||||
                                     `status` tinyint NOT NULL COMMENT '开启状态',
 | 
					 | 
				
			||||||
                                     `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
 | 
					 | 
				
			||||||
                                     `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 | 
					 | 
				
			||||||
                                     `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
 | 
					 | 
				
			||||||
                                     `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 | 
					 | 
				
			||||||
                                     `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
 | 
					 | 
				
			||||||
                                     `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
 | 
					 | 
				
			||||||
                                     PRIMARY KEY (`id`) USING BTREE
 | 
					 | 
				
			||||||
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '商品分类';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
-- Records of product_category
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
BEGIN;
 | 
					 | 
				
			||||||
INSERT INTO `product_category` (`id`, `parent_id`, `name`, `pic_url`, `sort`, `description`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 0, '电脑办公', 'http://test.yudao.iocoder.cn/122d548e1b3cd5dec72fe8075c6977a70f9cc13541a684ab3685f1b5df42f6bd.jpeg', 1, '1234', 0, '1', '2022-07-30 16:36:35', '1', '2022-07-30 20:27:16', b'0', 1), (2, 1, '笔记本', 'http://test.yudao.iocoder.cn/72713ac7b947600a019a18786ed0e6562e8692e253dbd35110a0a85c2469bbec.jpg', 1, '<p>测试一下</p>', 0, '1', '2022-07-30 16:38:09', '1', '2022-07-30 16:38:09', b'0', 1), (3, 1, '游戏本', 'http://test.yudao.iocoder.cn/287c50dd9f5f575f57329a0c57b2095be6d1aeba83867b905fe549f54a296feb.jpg', 2, '<p>测试一下</p>', 0, '1', '2022-07-30 16:39:09', '1', '2022-07-30 20:26:59', b'0', 1), (4, 0, '手机', 'http://test.yudao.iocoder.cn/e1b63900c78dbb661b3e383960cee5cfea7e1dd2fb22cff2e317ff025faaf8b2.jpeg', 2, '<p>123</p>', 0, '1', '2022-07-30 16:40:00', '1', '2022-07-30 16:40:09', b'0', 1), (5, 4, '5G手机', 'http://test.yudao.iocoder.cn/3af6557ac7def6423f046f5b2e920b644793420b466959aaa996a2e19068bbde.jpeg', 1, '<p><br></p>', 0, '1', '2022-07-30 16:43:00', '1', '2022-07-30 16:43:00', b'0', 1), (6, 4, '游戏手机', 'http://test.yudao.iocoder.cn/964fe9ccd1710d64ede261dc36d231918a017641986c15293c367f9f66d94d05.jpeg', 2, NULL, 0, '1', '2022-07-30 16:43:44', '1', '2022-07-30 16:43:44', b'0', 1), (7, 5, '厉害的 5G 手机', 'http://test.yudao.iocoder.cn/b287122f277838e8de368769b96217918605743bc45f3a29bda3cc7359dc66e1.png', 0, '123', 0, '1', '2022-07-30 20:38:09', '1', '2022-07-30 20:38:09', b'0', 1);
 | 
					 | 
				
			||||||
COMMIT;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
-- Table structure for product_property
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
DROP TABLE IF EXISTS `product_property`;
 | 
					 | 
				
			||||||
CREATE TABLE `product_property`  (
 | 
					 | 
				
			||||||
                                     `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
 | 
					 | 
				
			||||||
                                     `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '规格名称',
 | 
					 | 
				
			||||||
                                     `status` tinyint NULL DEFAULT NULL COMMENT '状态: 0 开启 ,1 禁用',
 | 
					 | 
				
			||||||
                                     `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 | 
					 | 
				
			||||||
                                     `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 | 
					 | 
				
			||||||
                                     `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人',
 | 
					 | 
				
			||||||
                                     `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人',
 | 
					 | 
				
			||||||
                                     `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
 | 
					 | 
				
			||||||
                                     `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
 | 
					 | 
				
			||||||
                                     PRIMARY KEY (`id`) USING BTREE,
 | 
					 | 
				
			||||||
                                     INDEX `idx_name`(`name`(32) ASC) USING BTREE COMMENT '规格名称索引'
 | 
					 | 
				
			||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '规格名称';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
-- Records of product_property
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
BEGIN;
 | 
					 | 
				
			||||||
COMMIT;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
-- Table structure for product_property_value
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
DROP TABLE IF EXISTS `product_property_value`;
 | 
					 | 
				
			||||||
CREATE TABLE `product_property_value`  (
 | 
					 | 
				
			||||||
                                           `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
 | 
					 | 
				
			||||||
                                           `property_id` bigint NULL DEFAULT NULL COMMENT '规格键id',
 | 
					 | 
				
			||||||
                                           `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '规格值名字',
 | 
					 | 
				
			||||||
                                           `status` tinyint NULL DEFAULT NULL COMMENT '状态: 1 开启 ,2 禁用',
 | 
					 | 
				
			||||||
                                           `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 | 
					 | 
				
			||||||
                                           `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 | 
					 | 
				
			||||||
                                           `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人',
 | 
					 | 
				
			||||||
                                           `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人',
 | 
					 | 
				
			||||||
                                           `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
 | 
					 | 
				
			||||||
                                           `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
 | 
					 | 
				
			||||||
                                           PRIMARY KEY (`id`) USING BTREE
 | 
					 | 
				
			||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '规格值';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
-- Records of product_property_value
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
BEGIN;
 | 
					 | 
				
			||||||
COMMIT;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
-- Table structure for product_sku
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
DROP TABLE IF EXISTS `product_sku`;
 | 
					 | 
				
			||||||
CREATE TABLE `product_sku` (
 | 
					 | 
				
			||||||
                               `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
 | 
					 | 
				
			||||||
                               `spu_id` bigint NOT NULL COMMENT 'spu编号',
 | 
					 | 
				
			||||||
                               `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
 | 
					 | 
				
			||||||
                               `name` varchar(128)  DEFAULT NULL COMMENT '商品 SKU 名字',
 | 
					 | 
				
			||||||
                               `properties` varchar(128)  DEFAULT NULL COMMENT '规格值数组-json格式, [{propertId: , valueId: }, {propertId: , valueId: }]',
 | 
					 | 
				
			||||||
                               `price` int NOT NULL DEFAULT '-1' COMMENT '销售价格,单位:分',
 | 
					 | 
				
			||||||
                               `market_price` int DEFAULT NULL COMMENT '市场价',
 | 
					 | 
				
			||||||
                               `cost_price` int NOT NULL DEFAULT '-1' COMMENT '成本价,单位: 分',
 | 
					 | 
				
			||||||
                               `pic_url` varchar(128)  NOT NULL COMMENT '图片地址',
 | 
					 | 
				
			||||||
                               `stock` int DEFAULT NULL COMMENT '库存',
 | 
					 | 
				
			||||||
                               `warn_stock` int DEFAULT NULL COMMENT '预警库存',
 | 
					 | 
				
			||||||
                               `volume` double DEFAULT NULL COMMENT '商品体积',
 | 
					 | 
				
			||||||
                               `weight` double DEFAULT NULL COMMENT '商品重量',
 | 
					 | 
				
			||||||
                               `bar_code` varchar(64)  DEFAULT NULL COMMENT '条形码',
 | 
					 | 
				
			||||||
                               `status` tinyint DEFAULT NULL COMMENT '状态: 0-正常 1-禁用',
 | 
					 | 
				
			||||||
                               `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 | 
					 | 
				
			||||||
                               `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 | 
					 | 
				
			||||||
                               `creator` varchar(64) DEFAULT NULL COMMENT '创建人',
 | 
					 | 
				
			||||||
                               `updater` double(64,0) DEFAULT NULL COMMENT '更新人',
 | 
					 | 
				
			||||||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
 | 
					 | 
				
			||||||
PRIMARY KEY (`id`) USING BTREE
 | 
					 | 
				
			||||||
) ENGINE=InnoDB COMMENT='商品sku';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
-- Records of product_sku
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
BEGIN;
 | 
					 | 
				
			||||||
COMMIT;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
-- Table structure for product_spu
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
DROP TABLE IF EXISTS `product_spu`;
 | 
					 | 
				
			||||||
CREATE TABLE `product_spu` (
 | 
					 | 
				
			||||||
                               `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
 | 
					 | 
				
			||||||
                               `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
 | 
					 | 
				
			||||||
                               `brand_id` bigint DEFAULT NULL COMMENT '商品品牌编号',
 | 
					 | 
				
			||||||
                               `category_id` bigint NOT NULL COMMENT '分类id',
 | 
					 | 
				
			||||||
                               `spec_type` int NOT NULL COMMENT '规格类型:0 单规格 1 多规格',
 | 
					 | 
				
			||||||
                               `code` varchar(128)  DEFAULT NULL COMMENT '商品编码',
 | 
					 | 
				
			||||||
                               `name` varchar(128)  NOT NULL COMMENT '商品名称',
 | 
					 | 
				
			||||||
                               `sell_point` varchar(128)  DEFAULT NULL COMMENT '卖点',
 | 
					 | 
				
			||||||
                               `description` text  COMMENT '描述',
 | 
					 | 
				
			||||||
                               `pic_urls` varchar(1024)  DEFAULT '' COMMENT '商品轮播图地址\n 数组,以逗号分隔\n 最多上传15张',
 | 
					 | 
				
			||||||
                               `video_url` varchar(128)  DEFAULT NULL COMMENT '商品视频',
 | 
					 | 
				
			||||||
                               `market_price` int DEFAULT NULL COMMENT '市场价,单位使用:分',
 | 
					 | 
				
			||||||
                               `min_price` int DEFAULT NULL COMMENT '最小价格,单位使用:分',
 | 
					 | 
				
			||||||
                               `max_price` int DEFAULT NULL COMMENT '最大价格,单位使用:分',
 | 
					 | 
				
			||||||
                               `total_stock` int NOT NULL DEFAULT '0' COMMENT '总库存',
 | 
					 | 
				
			||||||
                               `show_stock` int DEFAULT '0' COMMENT '是否展示库存',
 | 
					 | 
				
			||||||
                               `sales_count` int DEFAULT '0' COMMENT '商品销量',
 | 
					 | 
				
			||||||
                               `virtual_sales_count` int DEFAULT '0' COMMENT '虚拟销量',
 | 
					 | 
				
			||||||
                               `click_count` int DEFAULT '0' COMMENT '商品点击量',
 | 
					 | 
				
			||||||
                               `status` bit(1) DEFAULT NULL COMMENT '上下架状态: 0 上架(开启) 1 下架(禁用)-1 回收',
 | 
					 | 
				
			||||||
                               `sort` int NOT NULL DEFAULT '0' COMMENT '排序字段',
 | 
					 | 
				
			||||||
                               `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 | 
					 | 
				
			||||||
                               `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 | 
					 | 
				
			||||||
                               `creator` varchar(64)  DEFAULT NULL COMMENT '创建人',
 | 
					 | 
				
			||||||
                               `updater` varchar(64)  DEFAULT NULL COMMENT '更新人',
 | 
					 | 
				
			||||||
                               `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
 | 
					 | 
				
			||||||
                               PRIMARY KEY (`id`) USING BTREE
 | 
					 | 
				
			||||||
) ENGINE=InnoDB COMMENT='商品spu';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
-- Records of product_spu
 | 
					 | 
				
			||||||
-- ----------------------------
 | 
					 | 
				
			||||||
BEGIN;
 | 
					 | 
				
			||||||
COMMIT;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
SET FOREIGN_KEY_CHECKS = 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2000, '商品中心', '', 1, 60, 0, '/product', 'merchant', NULL, 0, b'1', b'1', '', '2022-07-29 15:53:53', '1', '2022-07-30 22:26:19', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2002, '商品分类', '', 2, 2, 2000, 'category', 'dict', 'mall/product/category/index', 0, b'1', b'1', '', '2022-07-29 15:53:53', '1', '2022-07-30 22:23:37', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2003, '分类查询', 'product:category:query', 3, 1, 2002, '', '', '', 0, b'1', b'1', '', '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2004, '分类创建', 'product:category:create', 3, 2, 2002, '', '', '', 0, b'1', b'1', '', '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2005, '分类更新', 'product:category:update', 3, 3, 2002, '', '', '', 0, b'1', b'1', '', '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2006, '分类删除', 'product:category:delete', 3, 4, 2002, '', '', '', 0, b'1', b'1', '', '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2007, '分类导出', 'product:category:export', 3, 5, 2002, '', '', '', 0, b'1', b'1', '', '2022-07-29 15:53:53', '', '2022-07-30 13:52:13', b'1');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2008, '商品品牌', '', 2, 1, 2000, 'brand', 'dashboard', 'mall/product/brand/index', 0, b'1', b'1', '', '2022-07-30 13:52:44', '1', '2022-07-30 22:23:43', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2009, '品牌查询', 'product:brand:query', 3, 1, 2008, '', '', '', 0, b'1', b'1', '', '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2010, '品牌创建', 'product:brand:create', 3, 2, 2008, '', '', '', 0, b'1', b'1', '', '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2011, '品牌更新', 'product:brand:update', 3, 3, 2008, '', '', '', 0, b'1', b'1', '', '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2012, '品牌删除', 'product:brand:delete', 3, 4, 2008, '', '', '', 0, b'1', b'1', '', '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2013, '品牌导出', 'product:brand:export', 3, 5, 2008, '', '', '', 0, b'1', b'1', '', '2022-07-30 13:52:44', '', '2022-07-30 14:15:00', b'1');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2014, '商品管理', '', 2, 0, 2000, 'spu', 'link', 'mall/product/spu/index', 0, b'1', b'1', '', '2022-07-30 14:22:58', '1', '2022-07-30 22:26:35', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2015, '商品查询', 'product:spu:query', 3, 1, 2014, '', '', '', 0, b'1', b'1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2016, '商品创建', 'product:spu:create', 3, 2, 2014, '', '', '', 0, b'1', b'1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2017, '商品更新', 'product:spu:update', 3, 3, 2014, '', '', '', 0, b'1', b'1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2018, '商品删除', 'product:spu:delete', 3, 4, 2014, '', '', '', 0, b'1', b'1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2019, '规格管理', '', 2, 3, 2000, 'property', '', 'mall/product/property/index', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2020, '规格查询', 'product:property:query', 3, 1, 2019, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2021, '规格创建', 'product:property:create', 3, 2, 2019, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2022, '规格更新', 'product:property:update', 3, 3, 2019, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2023, '规格删除', 'product:property:delete', 3, 4, 2019, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2024, '规格导出', 'product:property:export', 3, 5, 2019, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2025, 'Banner管理', '', 2, 1, 2000, 'banner', '', 'mall/market/banner/index', 0, b'1', b'1', '', '2022-08-01 14:56:14', '', '2022-08-01 14:56:14', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2026, 'Banner查询', 'market:banner:query', 3, 1, 2025, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:56:14', '', '2022-08-01 14:56:14', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2027, 'Banner创建', 'market:banner:create', 3, 2, 2025, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:56:14', '', '2022-08-01 14:56:14', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2028, 'Banner更新', 'market:banner:update', 3, 3, 2025, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:56:14', '', '2022-08-01 14:56:14', b'0');
 | 
					 | 
				
			||||||
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2029, 'Banner删除', 'market:banner:delete', 3, 4, 2025, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:56:14', '', '2022-08-01 14:56:14', b'0');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@@ -1,78 +0,0 @@
 | 
				
			|||||||
/**todo cancelType 设置默认值 0?*/
 | 
					 | 
				
			||||||
DROP TABLE IF EXISTS `trade_order`;
 | 
					 | 
				
			||||||
CREATE TABLE `trade_order`
 | 
					 | 
				
			||||||
(
 | 
					 | 
				
			||||||
    `id`                      bigint          NOT NULL AUTO_INCREMENT COMMENT '用户ID',
 | 
					 | 
				
			||||||
    `sn`                      varchar(32)     NOT NULL COMMENT '订单流水号',
 | 
					 | 
				
			||||||
    `type`                    int             NOT NULL DEFAULT '0' COMMENT '订单类型:[0:普通订单 1:秒杀订单 2:拼团订单 3:砍价订单]',
 | 
					 | 
				
			||||||
    `terminal`                int             NOT NULL COMMENT '订单来源终端:[1:小程序 2:H5 3:iOS 4:安卓]',
 | 
					 | 
				
			||||||
    `user_id`                 bigint unsigned NOT NULL COMMENT '用户编号',
 | 
					 | 
				
			||||||
    `user_ip`                 varchar(30)     NOT NULL DEFAULT '' COMMENT '用户 IP',
 | 
					 | 
				
			||||||
    `user_remark`             varchar(200)             DEFAULT NULL COMMENT '用户备注',
 | 
					 | 
				
			||||||
    `status`                  int             NOT NULL DEFAULT '0' COMMENT '订单状态:[0:待付款 1:待发货 2:待收货 3:已完成 4:已关闭]',
 | 
					 | 
				
			||||||
    `product_count`           int             NOT NULL COMMENT '购买的商品数量',
 | 
					 | 
				
			||||||
    `cancel_type`             int             DEFAULT NULL COMMENT '取消类型:[10:超时未支付 20:退款关闭 30:买家取消 40:已通过货到付款交易]',
 | 
					 | 
				
			||||||
    `remark`                  varchar(200)             DEFAULT NULL COMMENT '商家备注',
 | 
					 | 
				
			||||||
    `payed`                   bit(1)          NOT NULL DEFAULT b'0' COMMENT '是否已支付:[0:未支付 1:已经支付过]',
 | 
					 | 
				
			||||||
    `pay_time`                datetime                 DEFAULT NULL COMMENT '订单支付时间',
 | 
					 | 
				
			||||||
    `finish_time`             datetime                 DEFAULT NULL COMMENT '订单完成时间',
 | 
					 | 
				
			||||||
    `cancel_time`             datetime                 DEFAULT NULL COMMENT '订单取消时间',
 | 
					 | 
				
			||||||
    `sku_original_price`      int             NOT NULL DEFAULT '0' COMMENT '商品原价(总),单位:分',
 | 
					 | 
				
			||||||
    `sku_promotion_price`     int             NOT NULL DEFAULT '0' COMMENT '商品优惠(总),单位:分',
 | 
					 | 
				
			||||||
    `order_promotion_price`   int             NOT NULL DEFAULT '0' COMMENT '订单优惠(总),单位:分',
 | 
					 | 
				
			||||||
    `delivery_price`          int             NOT NULL DEFAULT '0' COMMENT '运费金额,单位:分',
 | 
					 | 
				
			||||||
    `pay_price`               int             NOT NULL DEFAULT '0' COMMENT '应付金额(总),单位:分',
 | 
					 | 
				
			||||||
    `pay_order_id`            int             DEFAULT NULL COMMENT '支付订单编号',
 | 
					 | 
				
			||||||
    `pay_channel`             int             DEFAULT NULL COMMENT '支付成功的支付渠道',
 | 
					 | 
				
			||||||
    `delivery_type`           int             DEFAULT NULL DEFAULT '1' COMMENT '配送方式:[1:快递发货 2:自提]',
 | 
					 | 
				
			||||||
    `actual_delivery_type`    int             DEFAULT NULL DEFAULT '1' COMMENT '实际的配送方式:[1:快递发货 2:自提]',
 | 
					 | 
				
			||||||
    `delivery_template_id`     int                      DEFAULT NULL COMMENT '配置模板的编号',
 | 
					 | 
				
			||||||
    `express_no`              int                      DEFAULT NULL COMMENT '物流公司单号',
 | 
					 | 
				
			||||||
    `delivery_status`         bit(1)          NOT NULL DEFAULT b'0' COMMENT '发货状态[0:未发货 1:已发货]',
 | 
					 | 
				
			||||||
    `delivery_time`           datetime                 DEFAULT NULL COMMENT '发货时间',
 | 
					 | 
				
			||||||
    `receive_time`            datetime                 DEFAULT NULL COMMENT '收货时间',
 | 
					 | 
				
			||||||
    `receiver_name`           varchar(20)     NOT NULL COMMENT '收件人名称',
 | 
					 | 
				
			||||||
    `receiver_mobile`         varchar(20)     NOT NULL COMMENT '收件人手机',
 | 
					 | 
				
			||||||
    `receiver_area_id`        int             NOT NULL COMMENT '收件人地区编号',
 | 
					 | 
				
			||||||
    `receiver_post_code`      int                      DEFAULT NULL COMMENT '收件人邮编',
 | 
					 | 
				
			||||||
    `receiver_detail_address` varchar(255)    NOT NULL COMMENT '收件人详细地址',
 | 
					 | 
				
			||||||
    `refund_status`           int             NOT NULL DEFAULT '0' COMMENT '订单状态:[0:未退款 1:部分退款 2:全部退款]',
 | 
					 | 
				
			||||||
    `refund_price`            int             NOT NULL DEFAULT '0' COMMENT '退款金额,单位:分',
 | 
					 | 
				
			||||||
    `coupon_id`               bigint unsigned NOT NULL COMMENT '优惠劵编号',
 | 
					 | 
				
			||||||
    `creator`                 varchar(64)              DEFAULT '' COMMENT '创建者',
 | 
					 | 
				
			||||||
    `create_time`             datetime        NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 | 
					 | 
				
			||||||
    `updater`                 varchar(64)              DEFAULT '' COMMENT '更新者',
 | 
					 | 
				
			||||||
    `update_time`             datetime        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 | 
					 | 
				
			||||||
    `deleted`                 bit(1)          NOT NULL DEFAULT b'0' COMMENT '是否删除',
 | 
					 | 
				
			||||||
    PRIMARY KEY (`id`) USING BTREE
 | 
					 | 
				
			||||||
) ENGINE = InnoDB COMMENT ='交易订单表';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
DROP TABLE IF EXISTS `trade_order_item`;
 | 
					 | 
				
			||||||
CREATE TABLE `trade_order_item`
 | 
					 | 
				
			||||||
(
 | 
					 | 
				
			||||||
    `id`                    bigint          NOT NULL AUTO_INCREMENT COMMENT '用户ID',
 | 
					 | 
				
			||||||
    `user_id`               bigint unsigned NOT NULL COMMENT '用户编号',
 | 
					 | 
				
			||||||
    `order_Id`              bigint unsigned NOT NULL COMMENT '订单编号',
 | 
					 | 
				
			||||||
    `spu_id`                bigint unsigned NOT NULL COMMENT '商品 SPU 编号',
 | 
					 | 
				
			||||||
    `sku_id`                bigint unsigned NOT NULL COMMENT '商品 SKU 编号',
 | 
					 | 
				
			||||||
    `properties`            json                     DEFAULT NULL COMMENT '规格值数组,JSON 格式',
 | 
					 | 
				
			||||||
    `name`                  varchar(128)    NOT NULL DEFAULT '' COMMENT '商品名称',
 | 
					 | 
				
			||||||
    `pic_url`               varchar(200)             DEFAULT NULL COMMENT '商品图片',
 | 
					 | 
				
			||||||
    `count`                 int             NOT NULL COMMENT '购买数量',
 | 
					 | 
				
			||||||
    `commented`             bit(1)          NOT NULL DEFAULT b'0' COMMENT '是否评论:[0:未评论 1:已评论]',
 | 
					 | 
				
			||||||
    `original_price`        int             NOT NULL DEFAULT '0' COMMENT '商品原价(单),单位:分',
 | 
					 | 
				
			||||||
    `total_original_price`  int             NOT NULL DEFAULT '0' COMMENT '商品原价(总),单位:分',
 | 
					 | 
				
			||||||
    `total_promotion_price` int             NOT NULL DEFAULT '0' COMMENT '商品级优惠(总),单位:分',
 | 
					 | 
				
			||||||
    `present_price`         int             NOT NULL DEFAULT '0' COMMENT '最终购买金额(单),单位:分。',
 | 
					 | 
				
			||||||
    `total_present_price`   int             NOT NULL DEFAULT '0' COMMENT '最终购买金额(总),单位:分。',
 | 
					 | 
				
			||||||
    `total_pay_price`       int             NOT NULL DEFAULT '0' COMMENT '应付金额(总),单位:分',
 | 
					 | 
				
			||||||
    `refund_status`         int             NOT NULL DEFAULT '0' COMMENT '退款状态:[0:未申请退款 1:申请退款 2:等待退款 3:退款成功]',
 | 
					 | 
				
			||||||
    `refund_total`          int             NOT NULL DEFAULT '0' COMMENT '退款总金额,单位:分',
 | 
					 | 
				
			||||||
    `creator`               varchar(64)              DEFAULT '' COMMENT '创建者',
 | 
					 | 
				
			||||||
    `create_time`           datetime        NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 | 
					 | 
				
			||||||
    `updater`               varchar(64)              DEFAULT '' COMMENT '更新者',
 | 
					 | 
				
			||||||
    `update_time`           datetime        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 | 
					 | 
				
			||||||
    `deleted`               bit(1)          NOT NULL DEFAULT b'0' COMMENT '是否删除',
 | 
					 | 
				
			||||||
    PRIMARY KEY (`id`) USING BTREE
 | 
					 | 
				
			||||||
) ENGINE = InnoDB COMMENT ='交易订单明细表';
 | 
					 | 
				
			||||||
@@ -14,37 +14,39 @@
 | 
				
			|||||||
    <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
 | 
					    <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <properties>
 | 
					    <properties>
 | 
				
			||||||
        <revision>1.6.5-snapshot</revision>
 | 
					        <revision>1.6.6-snapshot</revision>
 | 
				
			||||||
        <!-- 统一依赖管理 -->
 | 
					        <!-- 统一依赖管理 -->
 | 
				
			||||||
        <spring.boot.version>2.7.6</spring.boot.version>
 | 
					        <spring.boot.version>2.7.7</spring.boot.version>
 | 
				
			||||||
        <!-- Web 相关 -->
 | 
					        <!-- Web 相关 -->
 | 
				
			||||||
        <knife4j.version>3.0.3</knife4j.version>
 | 
					        <knife4j.version>4.0.0</knife4j.version>
 | 
				
			||||||
        <swagger-annotations.version>1.6.8</swagger-annotations.version>
 | 
					        <swagger-annotations.version>1.6.8</swagger-annotations.version>
 | 
				
			||||||
        <servlet.versoin>2.5</servlet.versoin>
 | 
					        <servlet.versoin>2.5</servlet.versoin>
 | 
				
			||||||
        <!-- DB 相关 -->
 | 
					        <!-- DB 相关 -->
 | 
				
			||||||
        <druid.version>1.2.15</druid.version>
 | 
					        <druid.version>1.2.15</druid.version>
 | 
				
			||||||
        <mybatis-plus.version>3.5.2</mybatis-plus.version>
 | 
					        <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
 | 
				
			||||||
        <mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version>
 | 
					        <mybatis-plus-generator.version>3.5.3.1</mybatis-plus-generator.version>
 | 
				
			||||||
        <dynamic-datasource.version>3.6.0</dynamic-datasource.version>
 | 
					        <dynamic-datasource.version>3.6.1</dynamic-datasource.version>
 | 
				
			||||||
        <redisson.version>3.18.0</redisson.version>
 | 
					        <redisson.version>3.18.0</redisson.version>
 | 
				
			||||||
        <!-- 服务保障相关 -->
 | 
					        <!-- 服务保障相关 -->
 | 
				
			||||||
        <lock4j.version>2.2.3</lock4j.version>
 | 
					        <lock4j.version>2.2.3</lock4j.version>
 | 
				
			||||||
        <resilience4j.version>1.7.1</resilience4j.version>
 | 
					        <resilience4j.version>1.7.1</resilience4j.version>
 | 
				
			||||||
        <!-- 监控相关 -->
 | 
					        <!-- 监控相关 -->
 | 
				
			||||||
        <skywalking.version>8.12.0</skywalking.version>
 | 
					        <skywalking.version>8.12.0</skywalking.version>
 | 
				
			||||||
        <spring-boot-admin.version>2.7.9</spring-boot-admin.version>
 | 
					        <spring-boot-admin.version>2.7.10</spring-boot-admin.version>
 | 
				
			||||||
        <opentracing.version>0.33.0</opentracing.version>
 | 
					        <opentracing.version>0.33.0</opentracing.version>
 | 
				
			||||||
        <!-- Test 测试相关 -->
 | 
					        <!-- Test 测试相关 -->
 | 
				
			||||||
        <podam.version>7.2.11.RELEASE</podam.version>
 | 
					        <podam.version>7.2.11.RELEASE</podam.version>
 | 
				
			||||||
        <jedis-mock.version>1.0.5</jedis-mock.version>
 | 
					        <jedis-mock.version>1.0.5</jedis-mock.version>
 | 
				
			||||||
        <mockito-inline.version>4.8.0</mockito-inline.version>
 | 
					        <mockito-inline.version>4.11.0</mockito-inline.version>
 | 
				
			||||||
        <!-- Bpm 工作流相关 -->
 | 
					        <!-- Bpm 工作流相关 -->
 | 
				
			||||||
        <flowable.version>6.7.2</flowable.version>
 | 
					        <flowable.version>6.8.0</flowable.version>
 | 
				
			||||||
        <!-- 工具类相关 -->
 | 
					        <!-- 工具类相关 -->
 | 
				
			||||||
 | 
					        <captcha-plus.version>1.0.1</captcha-plus.version>
 | 
				
			||||||
 | 
					        <jsoup.version>1.15.3</jsoup.version>
 | 
				
			||||||
        <lombok.version>1.18.24</lombok.version>
 | 
					        <lombok.version>1.18.24</lombok.version>
 | 
				
			||||||
        <mapstruct.version>1.5.3.Final</mapstruct.version>
 | 
					        <mapstruct.version>1.5.3.Final</mapstruct.version>
 | 
				
			||||||
        <hutool.version>5.8.10</hutool.version>
 | 
					        <hutool.version>5.8.11</hutool.version>
 | 
				
			||||||
        <easyexcel.verion>3.1.3</easyexcel.verion>
 | 
					        <easyexcel.verion>3.1.5</easyexcel.verion>
 | 
				
			||||||
        <velocity.version>2.3</velocity.version>
 | 
					        <velocity.version>2.3</velocity.version>
 | 
				
			||||||
        <screw.version>1.0.5</screw.version>
 | 
					        <screw.version>1.0.5</screw.version>
 | 
				
			||||||
        <fastjson.version>1.2.83</fastjson.version>
 | 
					        <fastjson.version>1.2.83</fastjson.version>
 | 
				
			||||||
@@ -54,18 +56,19 @@
 | 
				
			|||||||
        <commons-net.version>3.8.0</commons-net.version>
 | 
					        <commons-net.version>3.8.0</commons-net.version>
 | 
				
			||||||
        <jsch.version>0.1.55</jsch.version>
 | 
					        <jsch.version>0.1.55</jsch.version>
 | 
				
			||||||
        <tika-core.version>2.6.0</tika-core.version>
 | 
					        <tika-core.version>2.6.0</tika-core.version>
 | 
				
			||||||
        <aj-captcha.version>1.3.0</aj-captcha.version>
 | 
					        <netty-all.version>4.1.86.Final</netty-all.version>
 | 
				
			||||||
        <netty-all.version>4.1.85.Final</netty-all.version>
 | 
					        <ip2region.version>2.6.6</ip2region.version>
 | 
				
			||||||
        <!-- 三方云服务相关 -->
 | 
					        <!-- 三方云服务相关 -->
 | 
				
			||||||
        <okio.version>3.0.0</okio.version>
 | 
					        <okio.version>3.0.0</okio.version>
 | 
				
			||||||
        <okhttp3.version>4.10.0</okhttp3.version>
 | 
					        <okhttp3.version>4.10.0</okhttp3.version>
 | 
				
			||||||
        <minio.version>8.4.6</minio.version>
 | 
					        <minio.version>8.5.1</minio.version>
 | 
				
			||||||
        <aliyun-java-sdk-core.version>4.6.3</aliyun-java-sdk-core.version>
 | 
					        <aliyun-java-sdk-core.version>4.6.3</aliyun-java-sdk-core.version>
 | 
				
			||||||
        <aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version>
 | 
					        <aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version>
 | 
				
			||||||
        <tencentcloud-sdk-java.version>3.1.637</tencentcloud-sdk-java.version>
 | 
					        <tencentcloud-sdk-java.version>3.1.676</tencentcloud-sdk-java.version>
 | 
				
			||||||
        <justauth.version>1.4.0</justauth.version>
 | 
					        <justauth.version>1.4.0</justauth.version>
 | 
				
			||||||
        <jimureport.version>1.5.6</jimureport.version>
 | 
					        <jimureport.version>1.5.6</jimureport.version>
 | 
				
			||||||
        <xercesImpl.version>2.12.2</xercesImpl.version>
 | 
					        <xercesImpl.version>2.12.2</xercesImpl.version>
 | 
				
			||||||
 | 
					        <wx-java-mp.version>4.3.0</wx-java-mp.version>
 | 
				
			||||||
    </properties>
 | 
					    </properties>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <dependencyManagement>
 | 
					    <dependencyManagement>
 | 
				
			||||||
@@ -130,11 +133,21 @@
 | 
				
			|||||||
                <artifactId>yudao-spring-boot-starter-biz-error-code</artifactId>
 | 
					                <artifactId>yudao-spring-boot-starter-biz-error-code</artifactId>
 | 
				
			||||||
                <version>${revision}</version>
 | 
					                <version>${revision}</version>
 | 
				
			||||||
            </dependency>
 | 
					            </dependency>
 | 
				
			||||||
 | 
					            <dependency>
 | 
				
			||||||
 | 
					                <groupId>cn.iocoder.boot</groupId>
 | 
				
			||||||
 | 
					                <artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
 | 
				
			||||||
 | 
					                <version>${revision}</version>
 | 
				
			||||||
 | 
					            </dependency>
 | 
				
			||||||
            <dependency>
 | 
					            <dependency>
 | 
				
			||||||
                <groupId>cn.iocoder.boot</groupId>
 | 
					                <groupId>cn.iocoder.boot</groupId>
 | 
				
			||||||
                <artifactId>yudao-spring-boot-starter-captcha</artifactId>
 | 
					                <artifactId>yudao-spring-boot-starter-captcha</artifactId>
 | 
				
			||||||
                <version>${revision}</version>
 | 
					                <version>${revision}</version>
 | 
				
			||||||
            </dependency>
 | 
					            </dependency>
 | 
				
			||||||
 | 
					            <dependency>
 | 
				
			||||||
 | 
					                <groupId>cn.iocoder.boot</groupId>
 | 
				
			||||||
 | 
					                <artifactId>yudao-spring-boot-starter-desensitize</artifactId>
 | 
				
			||||||
 | 
					                <version>${revision}</version>
 | 
				
			||||||
 | 
					            </dependency>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <!-- Spring 核心 -->
 | 
					            <!-- Spring 核心 -->
 | 
				
			||||||
            <dependency>
 | 
					            <dependency>
 | 
				
			||||||
@@ -159,7 +172,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            <dependency>
 | 
					            <dependency>
 | 
				
			||||||
                <groupId>com.github.xiaoymin</groupId>
 | 
					                <groupId>com.github.xiaoymin</groupId>
 | 
				
			||||||
                <artifactId>knife4j-spring-boot-starter</artifactId>
 | 
					                <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
 | 
				
			||||||
                <version>${knife4j.version}</version>
 | 
					                <version>${knife4j.version}</version>
 | 
				
			||||||
                <exclusions>
 | 
					                <exclusions>
 | 
				
			||||||
                    <exclusion>
 | 
					                    <exclusion>
 | 
				
			||||||
@@ -440,12 +453,6 @@
 | 
				
			|||||||
                <version>${tika-core.version}</version>
 | 
					                <version>${tika-core.version}</version>
 | 
				
			||||||
            </dependency>
 | 
					            </dependency>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <dependency>
 | 
					 | 
				
			||||||
                <groupId>com.anji-plus</groupId>
 | 
					 | 
				
			||||||
                <artifactId>spring-boot-starter-captcha</artifactId>
 | 
					 | 
				
			||||||
                <version>${aj-captcha.version}</version>
 | 
					 | 
				
			||||||
            </dependency>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <dependency>
 | 
					            <dependency>
 | 
				
			||||||
                <groupId>org.apache.velocity</groupId>
 | 
					                <groupId>org.apache.velocity</groupId>
 | 
				
			||||||
                <artifactId>velocity-engine-core</artifactId>
 | 
					                <artifactId>velocity-engine-core</artifactId>
 | 
				
			||||||
@@ -510,6 +517,24 @@
 | 
				
			|||||||
                <version>${netty-all.version}</version>
 | 
					                <version>${netty-all.version}</version>
 | 
				
			||||||
            </dependency>
 | 
					            </dependency>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <dependency>
 | 
				
			||||||
 | 
					                <groupId>com.xingyuv</groupId>
 | 
				
			||||||
 | 
					                <artifactId>spring-boot-starter-captcha-plus</artifactId>
 | 
				
			||||||
 | 
					                <version>${captcha-plus.version}</version>
 | 
				
			||||||
 | 
					            </dependency>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <dependency>
 | 
				
			||||||
 | 
					                <groupId>org.lionsoul</groupId>
 | 
				
			||||||
 | 
					                <artifactId>ip2region</artifactId>
 | 
				
			||||||
 | 
					                <version>${ip2region.version}</version>
 | 
				
			||||||
 | 
					            </dependency>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <dependency>
 | 
				
			||||||
 | 
					                <groupId>org.jsoup</groupId>
 | 
				
			||||||
 | 
					                <artifactId>jsoup</artifactId>
 | 
				
			||||||
 | 
					                <version>${jsoup.version}</version>
 | 
				
			||||||
 | 
					            </dependency>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <!-- 三方云服务相关 -->
 | 
					            <!-- 三方云服务相关 -->
 | 
				
			||||||
            <dependency>
 | 
					            <dependency>
 | 
				
			||||||
                <groupId>com.squareup.okio</groupId>
 | 
					                <groupId>com.squareup.okio</groupId>
 | 
				
			||||||
@@ -566,6 +591,12 @@
 | 
				
			|||||||
                <version>${justauth.version}</version>
 | 
					                <version>${justauth.version}</version>
 | 
				
			||||||
            </dependency>
 | 
					            </dependency>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <dependency>
 | 
				
			||||||
 | 
					                <groupId>com.github.binarywang</groupId>
 | 
				
			||||||
 | 
					                <artifactId>wx-java-mp-spring-boot-starter</artifactId>
 | 
				
			||||||
 | 
					                <version>${wx-java-mp.version}</version>
 | 
				
			||||||
 | 
					            </dependency>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <!-- 积木报表-->
 | 
					            <!-- 积木报表-->
 | 
				
			||||||
            <dependency>
 | 
					            <dependency>
 | 
				
			||||||
                <groupId>org.jeecgframework.jimureport</groupId>
 | 
					                <groupId>org.jeecgframework.jimureport</groupId>
 | 
				
			||||||
@@ -583,6 +614,12 @@
 | 
				
			|||||||
                <artifactId>xercesImpl</artifactId>
 | 
					                <artifactId>xercesImpl</artifactId>
 | 
				
			||||||
                <version>${xercesImpl.version}</version>
 | 
					                <version>${xercesImpl.version}</version>
 | 
				
			||||||
            </dependency>
 | 
					            </dependency>
 | 
				
			||||||
 | 
					            <!-- SpringBoot Websocket -->
 | 
				
			||||||
 | 
					            <dependency>
 | 
				
			||||||
 | 
					                <groupId>org.springframework.boot</groupId>
 | 
				
			||||||
 | 
					                <artifactId>spring-boot-starter-websocket</artifactId>
 | 
				
			||||||
 | 
					                <version>${spring.boot.version}</version>
 | 
				
			||||||
 | 
					            </dependency>
 | 
				
			||||||
        </dependencies>
 | 
					        </dependencies>
 | 
				
			||||||
    </dependencyManagement>
 | 
					    </dependencyManagement>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,7 +21,7 @@
 | 
				
			|||||||
        <maven.compiler.target>8</maven.compiler.target>
 | 
					        <maven.compiler.target>8</maven.compiler.target>
 | 
				
			||||||
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 | 
					        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 | 
				
			||||||
        <!-- 统一依赖管理 -->
 | 
					        <!-- 统一依赖管理 -->
 | 
				
			||||||
        <spring.boot.version>2.7.6</spring.boot.version>
 | 
					        <spring.boot.version>2.7.7</spring.boot.version>
 | 
				
			||||||
    </properties>
 | 
					    </properties>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <dependencyManagement>
 | 
					    <dependencyManagement>
 | 
				
			||||||
@@ -52,7 +52,7 @@
 | 
				
			|||||||
        <dependency>
 | 
					        <dependency>
 | 
				
			||||||
            <groupId>cn.hutool</groupId>
 | 
					            <groupId>cn.hutool</groupId>
 | 
				
			||||||
            <artifactId>hutool-all</artifactId>
 | 
					            <artifactId>hutool-all</artifactId>
 | 
				
			||||||
            <version>5.8.10</version>
 | 
					            <version>5.8.11</version>
 | 
				
			||||||
        </dependency>
 | 
					        </dependency>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <dependency>
 | 
					        <dependency>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,7 +21,7 @@
 | 
				
			|||||||
        <maven.compiler.target>8</maven.compiler.target>
 | 
					        <maven.compiler.target>8</maven.compiler.target>
 | 
				
			||||||
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 | 
					        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 | 
				
			||||||
        <!-- 统一依赖管理 -->
 | 
					        <!-- 统一依赖管理 -->
 | 
				
			||||||
        <spring.boot.version>2.7.6</spring.boot.version>
 | 
					        <spring.boot.version>2.7.7</spring.boot.version>
 | 
				
			||||||
    </properties>
 | 
					    </properties>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <dependencyManagement>
 | 
					    <dependencyManagement>
 | 
				
			||||||
@@ -52,7 +52,7 @@
 | 
				
			|||||||
        <dependency>
 | 
					        <dependency>
 | 
				
			||||||
            <groupId>cn.hutool</groupId>
 | 
					            <groupId>cn.hutool</groupId>
 | 
				
			||||||
            <artifactId>hutool-all</artifactId>
 | 
					            <artifactId>hutool-all</artifactId>
 | 
				
			||||||
            <version>5.8.10</version>
 | 
					            <version>5.8.11</version>
 | 
				
			||||||
        </dependency>
 | 
					        </dependency>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <dependency>
 | 
					        <dependency>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,9 +36,12 @@
 | 
				
			|||||||
        <module>yudao-spring-boot-starter-biz-tenant</module>
 | 
					        <module>yudao-spring-boot-starter-biz-tenant</module>
 | 
				
			||||||
        <module>yudao-spring-boot-starter-biz-data-permission</module>
 | 
					        <module>yudao-spring-boot-starter-biz-data-permission</module>
 | 
				
			||||||
        <module>yudao-spring-boot-starter-biz-error-code</module>
 | 
					        <module>yudao-spring-boot-starter-biz-error-code</module>
 | 
				
			||||||
 | 
					        <module>yudao-spring-boot-starter-biz-ip</module>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <module>yudao-spring-boot-starter-flowable</module>
 | 
					        <module>yudao-spring-boot-starter-flowable</module>
 | 
				
			||||||
        <module>yudao-spring-boot-starter-captcha</module>
 | 
					        <module>yudao-spring-boot-starter-captcha</module>
 | 
				
			||||||
 | 
					        <module>yudao-spring-boot-starter-websocket</module>
 | 
				
			||||||
 | 
					        <module>yudao-spring-boot-starter-desensitize</module>
 | 
				
			||||||
    </modules>
 | 
					    </modules>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <artifactId>yudao-framework</artifactId>
 | 
					    <artifactId>yudao-framework</artifactId>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,6 @@ public enum CommonStatusEnum implements IntArrayValuable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CommonStatusEnum::getStatus).toArray();
 | 
					    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CommonStatusEnum::getStatus).toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 状态值
 | 
					     * 状态值
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,11 +15,12 @@ import java.util.Arrays;
 | 
				
			|||||||
@Getter
 | 
					@Getter
 | 
				
			||||||
public enum TerminalEnum implements IntArrayValuable {
 | 
					public enum TerminalEnum implements IntArrayValuable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //TODO terminal 重复,请参考 '订单来源终端:[1:小程序 2:H5 3:iOS 4:安卓]'
 | 
					    WECHAT_MINI_PROGRAM(10, "微信小程序"),
 | 
				
			||||||
    MINI_PROGRAM(1, "小程序"),
 | 
					    WECHAT_WAP(11, "微信公众号"),
 | 
				
			||||||
    H5(2, "H5"),
 | 
					    H5(20, "H5 网页"),
 | 
				
			||||||
    IOS(3, "iOS"),
 | 
					    IOS(31, "苹果 App"),
 | 
				
			||||||
    ANDROID(3, "安卓"),;
 | 
					    ANDROID(32, "安卓 App"),
 | 
				
			||||||
 | 
					    ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(TerminalEnum::getTerminal).toArray();
 | 
					    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(TerminalEnum::getTerminal).toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,6 +25,8 @@ public class DateUtils {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
 | 
					    public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static final String FORMAT_HOUR_MINUTE_SECOND = "HH:mm:ss";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 将 LocalDateTime 转换成 Date
 | 
					     * 将 LocalDateTime 转换成 Date
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,11 @@ import java.time.LocalDateTime;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
public class LocalDateTimeUtils {
 | 
					public class LocalDateTimeUtils {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 空的 LocalDateTime 对象,主要用于 DB 唯一索引的默认值
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static LocalDateTime EMPTY = buildTime(1970, 1, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static LocalDateTime addTime(Duration duration) {
 | 
					    public static LocalDateTime addTime(Duration duration) {
 | 
				
			||||||
        return LocalDateTime.now().plus(duration);
 | 
					        return LocalDateTime.now().plus(duration);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,6 @@
 | 
				
			|||||||
package cn.iocoder.yudao.framework.common.util.validation;
 | 
					package cn.iocoder.yudao.framework.common.util.validation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import cn.hutool.core.collection.CollUtil;
 | 
					import cn.hutool.core.collection.CollUtil;
 | 
				
			||||||
import cn.hutool.core.util.StrUtil;
 | 
					 | 
				
			||||||
import org.springframework.util.StringUtils;
 | 
					import org.springframework.util.StringUtils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.validation.ConstraintViolation;
 | 
					import javax.validation.ConstraintViolation;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -227,7 +227,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
 | 
				
			|||||||
            // 调用
 | 
					            // 调用
 | 
				
			||||||
            Expression expression = rule.getExpression(tableName, tableAlias);
 | 
					            Expression expression = rule.getExpression(tableName, tableAlias);
 | 
				
			||||||
            // 断言
 | 
					            // 断言
 | 
				
			||||||
            assertEquals("u.dept_id IN (10, 20) OR u.id = 1", expression.toString());
 | 
					            assertEquals("(u.dept_id IN (10, 20) OR u.id = 1)", expression.toString());
 | 
				
			||||||
            assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
 | 
					            assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										58
									
								
								yudao-framework/yudao-spring-boot-starter-biz-ip/pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								yudao-framework/yudao-spring-boot-starter-biz-ip/pom.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<project xmlns="http://maven.apache.org/POM/4.0.0"
 | 
				
			||||||
 | 
					         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
				
			||||||
 | 
					         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
				
			||||||
 | 
					    <parent>
 | 
				
			||||||
 | 
					        <artifactId>yudao-framework</artifactId>
 | 
				
			||||||
 | 
					        <groupId>cn.iocoder.boot</groupId>
 | 
				
			||||||
 | 
					        <version>${revision}</version>
 | 
				
			||||||
 | 
					    </parent>
 | 
				
			||||||
 | 
					    <modelVersion>4.0.0</modelVersion>
 | 
				
			||||||
 | 
					    <artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
 | 
				
			||||||
 | 
					    <packaging>jar</packaging>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <name>${project.artifactId}</name>
 | 
				
			||||||
 | 
					    <description>IP 拓展,支持如下功能:
 | 
				
			||||||
 | 
					        1. IP 功能:查询 IP 对应的城市信息
 | 
				
			||||||
 | 
					            基于 https://gitee.com/lionsoul/ip2region 实现
 | 
				
			||||||
 | 
					        2. 城市功能:查询城市编码对应的城市信息
 | 
				
			||||||
 | 
					            基于 https://github.com/modood/Administrative-divisions-of-China 实现
 | 
				
			||||||
 | 
					    </description>
 | 
				
			||||||
 | 
					    <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <properties>
 | 
				
			||||||
 | 
					        <ip2region.version>2.6.6</ip2region.version>
 | 
				
			||||||
 | 
					    </properties>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <dependencies>
 | 
				
			||||||
 | 
					        <dependency>
 | 
				
			||||||
 | 
					            <groupId>cn.iocoder.boot</groupId>
 | 
				
			||||||
 | 
					            <artifactId>yudao-common</artifactId>
 | 
				
			||||||
 | 
					        </dependency>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <!-- IP地址检索 -->
 | 
				
			||||||
 | 
					        <dependency>
 | 
				
			||||||
 | 
					            <groupId>org.lionsoul</groupId>
 | 
				
			||||||
 | 
					            <artifactId>ip2region</artifactId>
 | 
				
			||||||
 | 
					        </dependency>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <dependency>
 | 
				
			||||||
 | 
					            <groupId>org.projectlombok</groupId>
 | 
				
			||||||
 | 
					            <artifactId>lombok</artifactId>
 | 
				
			||||||
 | 
					        </dependency>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <dependency>
 | 
				
			||||||
 | 
					            <groupId>org.slf4j</groupId>
 | 
				
			||||||
 | 
					            <artifactId>slf4j-api</artifactId>
 | 
				
			||||||
 | 
					            <scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
 | 
				
			||||||
 | 
					        </dependency>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <!-- Test 测试相关 -->
 | 
				
			||||||
 | 
					        <dependency>
 | 
				
			||||||
 | 
					            <groupId>cn.iocoder.boot</groupId>
 | 
				
			||||||
 | 
					            <artifactId>yudao-spring-boot-starter-test</artifactId>
 | 
				
			||||||
 | 
					            <scope>test</scope>
 | 
				
			||||||
 | 
					        </dependency>
 | 
				
			||||||
 | 
					    </dependencies>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</project>
 | 
				
			||||||
@@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.ip.core;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
 | 
				
			||||||
 | 
					import lombok.AllArgsConstructor;
 | 
				
			||||||
 | 
					import lombok.Data;
 | 
				
			||||||
 | 
					import lombok.NoArgsConstructor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 区域节点,包括国家、省份、城市、地区等信息
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * 数据可见 resources/area.csv 文件
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author 芋道源码
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Data
 | 
				
			||||||
 | 
					@AllArgsConstructor
 | 
				
			||||||
 | 
					@NoArgsConstructor
 | 
				
			||||||
 | 
					public class Area {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 编号 - 全球,即根目录
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static final Integer ID_GLOBAL = 0;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 编号 - 中国
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static final Integer ID_CHINA = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 编号
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private Integer id;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 名字
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private String name;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 类型
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * 枚举 {@link AreaTypeEnum}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private Integer type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 父节点
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private Area parent;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 子节点
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private List<Area> children;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.ip.core.enums;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
 | 
				
			||||||
 | 
					import lombok.AllArgsConstructor;
 | 
				
			||||||
 | 
					import lombok.Getter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 区域类型枚举
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author 芋道源码
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@AllArgsConstructor
 | 
				
			||||||
 | 
					@Getter
 | 
				
			||||||
 | 
					public enum AreaTypeEnum implements IntArrayValuable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    COUNTRY(1, "国家"),
 | 
				
			||||||
 | 
					    PROVINCE(2, "省份"),
 | 
				
			||||||
 | 
					    CITY(3, "城市"),
 | 
				
			||||||
 | 
					    DISTRICT(4, "地区"), // 县、镇、区等
 | 
				
			||||||
 | 
					    ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AreaTypeEnum::getType).toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 类型
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private final Integer type;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 名字
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private final String name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public int[] array() {
 | 
				
			||||||
 | 
					        return ARRAYS;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,119 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.ip.core.utils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.hutool.core.io.resource.ResourceUtil;
 | 
				
			||||||
 | 
					import cn.hutool.core.lang.Assert;
 | 
				
			||||||
 | 
					import cn.hutool.core.text.csv.CsvRow;
 | 
				
			||||||
 | 
					import cn.hutool.core.text.csv.CsvUtil;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.ip.core.Area;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
 | 
				
			||||||
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.HashMap;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 区域工具类
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author 芋道源码
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Slf4j
 | 
				
			||||||
 | 
					public class AreaUtils {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 初始化 SEARCHER
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @SuppressWarnings("InstantiationOfUtilityClass")
 | 
				
			||||||
 | 
					    private final static AreaUtils INSTANCE = new AreaUtils();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Area 内存缓存,提升访问速度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private static Map<Integer, Area> areas;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private AreaUtils() {
 | 
				
			||||||
 | 
					        long now = System.currentTimeMillis();
 | 
				
			||||||
 | 
					        areas = new HashMap<>();
 | 
				
			||||||
 | 
					        areas.put(Area.ID_GLOBAL, new Area(Area.ID_GLOBAL, "全球", 0,
 | 
				
			||||||
 | 
					                null, new ArrayList<>()));
 | 
				
			||||||
 | 
					        // 从 csv 中加载数据
 | 
				
			||||||
 | 
					        List<CsvRow> rows = CsvUtil.getReader().read(ResourceUtil.getUtf8Reader("area.csv")).getRows();
 | 
				
			||||||
 | 
					        rows.remove(0); // 删除 header
 | 
				
			||||||
 | 
					        for (CsvRow row : rows) {
 | 
				
			||||||
 | 
					            // 创建 Area 对象
 | 
				
			||||||
 | 
					            Area area = new Area(Integer.valueOf(row.get(0)), row.get(1), Integer.valueOf(row.get(2)),
 | 
				
			||||||
 | 
					                    null, new ArrayList<>());
 | 
				
			||||||
 | 
					            // 添加到 areas 中
 | 
				
			||||||
 | 
					            areas.put(area.getId(), area);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 构建父子关系:因为 Area 中没有 parentId 字段,所以需要重复读取
 | 
				
			||||||
 | 
					        for (CsvRow row : rows) {
 | 
				
			||||||
 | 
					            Area area = areas.get(Integer.valueOf(row.get(0))); // 自己
 | 
				
			||||||
 | 
					            Area parent = areas.get(Integer.valueOf(row.get(3))); // 父
 | 
				
			||||||
 | 
					            Assert.isTrue(area != parent, "{}:父子节点相同", area.getName());
 | 
				
			||||||
 | 
					            area.setParent(parent);
 | 
				
			||||||
 | 
					            parent.getChildren().add(area);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        log.info("启动加载 AreaUtils 成功,耗时 ({}) 毫秒", System.currentTimeMillis() - now);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 获得指定编号对应的区域
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param id 区域编号
 | 
				
			||||||
 | 
					     * @return 区域
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static Area getArea(Integer id) {
 | 
				
			||||||
 | 
					        return areas.get(id);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 格式化区域
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param id 区域编号
 | 
				
			||||||
 | 
					     * @return 格式化后的区域
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static String format(Integer id) {
 | 
				
			||||||
 | 
					        return format(id, " ");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 格式化区域
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * 例如说:
 | 
				
			||||||
 | 
					     *      1. id = “静安区”时:上海 上海市 静安区
 | 
				
			||||||
 | 
					     *      2. id = “上海市”时:上海 上海市
 | 
				
			||||||
 | 
					     *      3. id = “上海”时:上海
 | 
				
			||||||
 | 
					     *      4. id = “美国”时:美国
 | 
				
			||||||
 | 
					     * 当区域在中国时,默认不显示中国
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param id 区域编号
 | 
				
			||||||
 | 
					     * @param separator 分隔符
 | 
				
			||||||
 | 
					     * @return 格式化后的区域
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static String format(Integer id, String separator) {
 | 
				
			||||||
 | 
					        // 获得区域
 | 
				
			||||||
 | 
					        Area area = areas.get(id);
 | 
				
			||||||
 | 
					        if (area == null) {
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 格式化
 | 
				
			||||||
 | 
					        StringBuilder sb = new StringBuilder();
 | 
				
			||||||
 | 
					        for (int i = 0; i < AreaTypeEnum.values().length; i++) { // 避免死循环
 | 
				
			||||||
 | 
					            sb.insert(0, area.getName());
 | 
				
			||||||
 | 
					            // “递归”父节点
 | 
				
			||||||
 | 
					            area = area.getParent();
 | 
				
			||||||
 | 
					            if (area == null
 | 
				
			||||||
 | 
					                || ObjectUtils.equalsAny(area.getId(), Area.ID_GLOBAL, Area.ID_CHINA)) { // 跳过父节点为中国的情况
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            sb.insert(0, separator);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return sb.toString();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,87 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.ip.core.utils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.hutool.core.io.resource.ResourceUtil;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.ip.core.Area;
 | 
				
			||||||
 | 
					import lombok.SneakyThrows;
 | 
				
			||||||
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
 | 
					import org.lionsoul.ip2region.xdb.Searcher;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * IP 工具类
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * IP 数据源来自 ip2region.xdb 精简版,基于 <a href="https://gitee.com/zhijiantianya/ip2region"/> 项目
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author wanglhup
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Slf4j
 | 
				
			||||||
 | 
					public class IPUtils {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 初始化 SEARCHER
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @SuppressWarnings("InstantiationOfUtilityClass")
 | 
				
			||||||
 | 
					    private final static IPUtils INSTANCE = new IPUtils();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * IP 查询器,启动加载到内存中
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private static Searcher SEARCHER;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 私有化构造
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private IPUtils() {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            long now = System.currentTimeMillis();
 | 
				
			||||||
 | 
					            byte[] bytes = ResourceUtil.readBytes("ip2region.xdb");
 | 
				
			||||||
 | 
					            SEARCHER = Searcher.newWithBuffer(bytes);
 | 
				
			||||||
 | 
					            log.info("启动加载 IPUtils 成功,耗时 ({}) 毫秒", System.currentTimeMillis() - now);
 | 
				
			||||||
 | 
					        } catch (IOException e) {
 | 
				
			||||||
 | 
					            log.error("启动加载 IPUtils 失败", e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 查询 IP 对应的地区编号
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param ip IP 地址,格式为 127.0.0.1
 | 
				
			||||||
 | 
					     * @return 地区id
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @SneakyThrows
 | 
				
			||||||
 | 
					    public static Integer getAreaId(String ip) {
 | 
				
			||||||
 | 
					        return Integer.parseInt(SEARCHER.search(ip));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 查询 IP 对应的地区编号
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param ip IP 地址的时间戳,格式参考{@link Searcher#checkIP(String)} 的返回
 | 
				
			||||||
 | 
					     * @return 地区编号
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @SneakyThrows
 | 
				
			||||||
 | 
					    public static Integer getAreaId(long ip) {
 | 
				
			||||||
 | 
					        return Integer.parseInt(SEARCHER.search(ip));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 查询 IP 对应的地区
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param ip IP 地址,格式为 127.0.0.1
 | 
				
			||||||
 | 
					     * @return 地区
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static Area getArea(String ip) {
 | 
				
			||||||
 | 
					        return AreaUtils.getArea(getAreaId(ip));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 查询 IP 对应的地区
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param ip IP 地址的时间戳,格式参考{@link Searcher#checkIP(String)} 的返回
 | 
				
			||||||
 | 
					     * @return 地区
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static Area getArea(long ip) {
 | 
				
			||||||
 | 
					        return AreaUtils.getArea(getAreaId(ip));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * IP 拓展,支持如下功能:
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * 1. IP 功能:查询 IP 对应的城市信息
 | 
				
			||||||
 | 
					 *      基于 https://gitee.com/lionsoul/ip2region 实现
 | 
				
			||||||
 | 
					 * 2. 城市功能:查询城市编码对应的城市信息
 | 
				
			||||||
 | 
					 *      基于 https://github.com/modood/Administrative-divisions-of-China 实现
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author 芋道源码
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					package cn.iocoder.yudao.framework.ip;
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.ip.core.utils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.ip.core.Area;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
 | 
				
			||||||
 | 
					import org.junit.jupiter.api.Test;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.junit.jupiter.api.Assertions.assertEquals;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {@link AreaUtils} 的单元测试
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author 芋道源码
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class AreaUtilsTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void testGetArea() {
 | 
				
			||||||
 | 
					        // 调用:北京
 | 
				
			||||||
 | 
					        Area area = AreaUtils.getArea(110100);
 | 
				
			||||||
 | 
					        // 断言
 | 
				
			||||||
 | 
					        assertEquals(area.getId(), 110100);
 | 
				
			||||||
 | 
					        assertEquals(area.getName(), "北京市");
 | 
				
			||||||
 | 
					        assertEquals(area.getType(), AreaTypeEnum.CITY.getType());
 | 
				
			||||||
 | 
					        assertEquals(area.getParent().getId(), 110000);
 | 
				
			||||||
 | 
					        assertEquals(area.getChildren().size(), 16);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void testFormat() {
 | 
				
			||||||
 | 
					        assertEquals(AreaUtils.format(110105), "北京 北京市 朝阳区");
 | 
				
			||||||
 | 
					        assertEquals(AreaUtils.format(1), "中国");
 | 
				
			||||||
 | 
					        assertEquals(AreaUtils.format(2), "蒙古");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.ip.core.utils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.ip.core.Area;
 | 
				
			||||||
 | 
					import org.junit.jupiter.api.Test;
 | 
				
			||||||
 | 
					import org.lionsoul.ip2region.xdb.Searcher;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.junit.jupiter.api.Assertions.assertEquals;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {@link IPUtils} 的单元测试
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author wanglhup
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class IPUtilsTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void testGetAreaId_string() {
 | 
				
			||||||
 | 
					        // 120.202.4.0|120.202.4.255|420600
 | 
				
			||||||
 | 
					        Integer areaId = IPUtils.getAreaId("120.202.4.50");
 | 
				
			||||||
 | 
					        assertEquals(420600, areaId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void testGetAreaId_long() throws Exception {
 | 
				
			||||||
 | 
					        // 120.203.123.0|120.203.133.255|360900
 | 
				
			||||||
 | 
					        long ip = Searcher.checkIP("120.203.123.250");
 | 
				
			||||||
 | 
					        Integer areaId = IPUtils.getAreaId(ip);
 | 
				
			||||||
 | 
					        assertEquals(360900, areaId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void testGetArea_string() {
 | 
				
			||||||
 | 
					        // 120.202.4.0|120.202.4.255|420600
 | 
				
			||||||
 | 
					        Area area = IPUtils.getArea("120.202.4.50");
 | 
				
			||||||
 | 
					        assertEquals("襄阳市", area.getName());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void testGetArea_long() throws Exception {
 | 
				
			||||||
 | 
					        // 120.203.123.0|120.203.133.255|360900
 | 
				
			||||||
 | 
					        long ip = Searcher.checkIP("120.203.123.252");
 | 
				
			||||||
 | 
					        Area area = IPUtils.getArea(ip);
 | 
				
			||||||
 | 
					        assertEquals("宜春市", area.getName());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -52,7 +52,7 @@
 | 
				
			|||||||
        <dependency>
 | 
					        <dependency>
 | 
				
			||||||
            <groupId>com.alipay.sdk</groupId>
 | 
					            <groupId>com.alipay.sdk</groupId>
 | 
				
			||||||
            <artifactId>alipay-sdk-java</artifactId>
 | 
					            <artifactId>alipay-sdk-java</artifactId>
 | 
				
			||||||
            <version>4.35.0.ALL</version>
 | 
					            <version>4.35.32.ALL</version>
 | 
				
			||||||
            <exclusions>
 | 
					            <exclusions>
 | 
				
			||||||
                <exclusion>
 | 
					                <exclusion>
 | 
				
			||||||
                    <groupId>org.bouncycastle</groupId>
 | 
					                    <groupId>org.bouncycastle</groupId>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,26 +13,23 @@ import javax.validation.constraints.NotEmpty;
 | 
				
			|||||||
public class PayProperties {
 | 
					public class PayProperties {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 支付回调地址
 | 
					     * 回调地址
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * 实际上,对应的 PayNotifyController 的 notifyCallback 方法的 URL
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
     * 注意,支付渠道统一回调到 payNotifyUrl 地址,由支付模块统一处理;然后,自己的支付模块,在回调 PayAppDO.payNotifyUrl 地址
 | 
					     * 注意,支付渠道统一回调到 payNotifyUrl 地址,由支付模块统一处理;然后,自己的支付模块,在回调 PayAppDO.payNotifyUrl 地址
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    @NotEmpty(message = "支付回调地址不能为空")
 | 
					    @NotEmpty(message = "回调地址不能为空")
 | 
				
			||||||
    @URL(message = "支付回调地址的格式必须是 URL")
 | 
					    @URL(message = "回调地址的格式必须是 URL")
 | 
				
			||||||
    private String payNotifyUrl;
 | 
					    private String callbackUrl;
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 退款回调地址
 | 
					 | 
				
			||||||
     * 注意点,同 {@link #payNotifyUrl} 属性
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @NotEmpty(message = "退款回调地址不能为空")
 | 
					 | 
				
			||||||
    @URL(message = "退款回调地址的格式必须是 URL")
 | 
					 | 
				
			||||||
    private String refundNotifyUrl;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 支付完成的返回地址
 | 
					     * 回跳地址
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * 实际上,对应的 PayNotifyController 的 returnCallback 方法的 URL
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    @URL(message = "支付返回的地址的格式必须是 URL")
 | 
					    @URL(message = "回跳地址的格式必须是 URL")
 | 
				
			||||||
    @NotEmpty(message = "支付返回的地址不能为空")
 | 
					    @NotEmpty(message = "回跳地址不能为空")
 | 
				
			||||||
    private String payReturnUrl;
 | 
					    private String returnUrl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,9 +21,9 @@ public class PayNotifyDataDTO {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    private String body;
 | 
					    private String body;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数
 | 
					     * HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private Map<String,String> params;
 | 
					    private Map<String,String> params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -62,7 +62,7 @@ public class PayOrderUnifiedReqDTO {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    @NotNull(message = "支付金额不能为空")
 | 
					    @NotNull(message = "支付金额不能为空")
 | 
				
			||||||
    @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
 | 
					    @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
 | 
				
			||||||
    private Long amount;
 | 
					    private Integer amount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 支付过期时间
 | 
					     * 支付过期时间
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -63,7 +63,7 @@ public class PayRefundUnifiedReqDTO {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    @NotNull(message = "退款金额不能为空")
 | 
					    @NotNull(message = "退款金额不能为空")
 | 
				
			||||||
    @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
 | 
					    @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
 | 
				
			||||||
    private Long amount;
 | 
					    private Integer amount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 退款结果 notify 回调地址, 支付宝退款不需要回调地址, 微信需要
 | 
					     * 退款结果 notify 回调地址, 支付宝退款不需要回调地址, 微信需要
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -69,7 +69,7 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
 | 
				
			|||||||
        this.init();
 | 
					        this.init();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected Double calculateAmount(Long amount) {
 | 
					    protected Double calculateAmount(Integer amount) {
 | 
				
			||||||
        return amount / 100.0;
 | 
					        return amount / 100.0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,6 +26,8 @@ import com.github.binarywang.wxpay.service.WxPayService;
 | 
				
			|||||||
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
 | 
					import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.time.ZoneId;
 | 
				
			||||||
 | 
					import java.util.Date;
 | 
				
			||||||
import java.util.Objects;
 | 
					import java.util.Objects;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 | 
					import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 | 
				
			||||||
@@ -119,7 +121,7 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
 | 
				
			|||||||
                .setTotal(reqDTO
 | 
					                .setTotal(reqDTO
 | 
				
			||||||
                        .getAmount()
 | 
					                        .getAmount()
 | 
				
			||||||
                        .intValue())); // 单位分
 | 
					                        .intValue())); // 单位分
 | 
				
			||||||
        request.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX")); // v3的时间格式
 | 
					        request.setTimeExpire(DateUtil.format(Date.from(reqDTO.getExpireTime().atZone(ZoneId.systemDefault()).toInstant()), "yyyy-MM-dd'T'HH:mm:ssXXX")); // v3的时间格式
 | 
				
			||||||
        request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO)));
 | 
					        request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO)));
 | 
				
			||||||
        request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
 | 
					        request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
 | 
				
			||||||
        request.setNotifyUrl(reqDTO.getNotifyUrl());
 | 
					        request.setNotifyUrl(reqDTO.getNotifyUrl());
 | 
				
			||||||
@@ -167,7 +169,8 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
 | 
				
			|||||||
        return PayOrderNotifyRespDTO
 | 
					        return PayOrderNotifyRespDTO
 | 
				
			||||||
                .builder()
 | 
					                .builder()
 | 
				
			||||||
                .orderExtensionNo(result.getOutTradeNo())
 | 
					                .orderExtensionNo(result.getOutTradeNo())
 | 
				
			||||||
                .channelOrderNo(result.getTradeState())
 | 
					                .channelOrderNo(result.getTransactionId())
 | 
				
			||||||
 | 
					                .channelUserId(result.getPayer().getOpenid())
 | 
				
			||||||
                .successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
 | 
					                .successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
 | 
				
			||||||
                .data(data.getBody())
 | 
					                .data(data.getBody())
 | 
				
			||||||
                .build();
 | 
					                .build();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,6 +24,8 @@ import com.github.binarywang.wxpay.service.WxPayService;
 | 
				
			|||||||
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
 | 
					import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.time.ZoneId;
 | 
				
			||||||
 | 
					import java.util.Date;
 | 
				
			||||||
import java.util.Objects;
 | 
					import java.util.Objects;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 | 
					import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 | 
				
			||||||
@@ -98,7 +100,7 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
 | 
				
			|||||||
                .outTradeNo(reqDTO.getMerchantOrderId())
 | 
					                .outTradeNo(reqDTO.getMerchantOrderId())
 | 
				
			||||||
                .body(reqDTO.getBody())
 | 
					                .body(reqDTO.getBody())
 | 
				
			||||||
                .totalFee(reqDTO.getAmount().intValue()) // 单位分
 | 
					                .totalFee(reqDTO.getAmount().intValue()) // 单位分
 | 
				
			||||||
                .timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
 | 
					                .timeExpire(DateUtil.format(Date.from(reqDTO.getExpireTime().atZone(ZoneId.systemDefault()).toInstant()), "yyyy-MM-dd'T'HH:mm:ssXXX"))
 | 
				
			||||||
                .spbillCreateIp(reqDTO.getUserIp())
 | 
					                .spbillCreateIp(reqDTO.getUserIp())
 | 
				
			||||||
                .notifyUrl(reqDTO.getNotifyUrl())
 | 
					                .notifyUrl(reqDTO.getNotifyUrl())
 | 
				
			||||||
                .productId(tradeType)
 | 
					                .productId(tradeType)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
 | 
					package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import cn.hutool.core.bean.BeanUtil;
 | 
					import cn.hutool.core.bean.BeanUtil;
 | 
				
			||||||
import cn.hutool.core.date.DateUtil;
 | 
					 | 
				
			||||||
import cn.hutool.core.date.LocalDateTimeUtil;
 | 
					import cn.hutool.core.date.LocalDateTimeUtil;
 | 
				
			||||||
 | 
					import cn.hutool.core.date.TemporalAccessorUtil;
 | 
				
			||||||
import cn.hutool.core.lang.Assert;
 | 
					import cn.hutool.core.lang.Assert;
 | 
				
			||||||
import cn.hutool.core.map.MapUtil;
 | 
					import cn.hutool.core.map.MapUtil;
 | 
				
			||||||
import cn.hutool.core.util.StrUtil;
 | 
					import cn.hutool.core.util.StrUtil;
 | 
				
			||||||
@@ -26,6 +26,8 @@ import com.github.binarywang.wxpay.service.WxPayService;
 | 
				
			|||||||
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
 | 
					import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.time.LocalDateTime;
 | 
				
			||||||
 | 
					import java.time.ZoneId;
 | 
				
			||||||
import java.util.Objects;
 | 
					import java.util.Objects;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 | 
					import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 | 
				
			||||||
@@ -98,8 +100,8 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
 | 
				
			|||||||
        WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
 | 
					        WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
 | 
				
			||||||
                .outTradeNo(reqDTO.getMerchantOrderId())
 | 
					                .outTradeNo(reqDTO.getMerchantOrderId())
 | 
				
			||||||
                .body(reqDTO.getBody())
 | 
					                .body(reqDTO.getBody())
 | 
				
			||||||
                .totalFee(reqDTO.getAmount().intValue()) // 单位分
 | 
					                .totalFee(reqDTO.getAmount()) // 单位分
 | 
				
			||||||
                .timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
 | 
					                .timeExpire(formatDate(reqDTO.getExpireTime()))
 | 
				
			||||||
                .spbillCreateIp(reqDTO.getUserIp())
 | 
					                .spbillCreateIp(reqDTO.getUserIp())
 | 
				
			||||||
                .openid(getOpenid(reqDTO))
 | 
					                .openid(getOpenid(reqDTO))
 | 
				
			||||||
                .notifyUrl(reqDTO.getNotifyUrl())
 | 
					                .notifyUrl(reqDTO.getNotifyUrl())
 | 
				
			||||||
@@ -113,8 +115,8 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
 | 
				
			|||||||
        WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
 | 
					        WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
 | 
				
			||||||
        request.setOutTradeNo(reqDTO.getMerchantOrderId());
 | 
					        request.setOutTradeNo(reqDTO.getMerchantOrderId());
 | 
				
			||||||
        request.setDescription(reqDTO.getBody());
 | 
					        request.setDescription(reqDTO.getBody());
 | 
				
			||||||
        request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount().intValue())); // 单位分
 | 
					        request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount())); // 单位分
 | 
				
			||||||
        request.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"));
 | 
					        request.setTimeExpire(formatDate(reqDTO.getExpireTime()));
 | 
				
			||||||
        request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO)));
 | 
					        request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO)));
 | 
				
			||||||
        request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
 | 
					        request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
 | 
				
			||||||
        request.setNotifyUrl(reqDTO.getNotifyUrl());
 | 
					        request.setNotifyUrl(reqDTO.getNotifyUrl());
 | 
				
			||||||
@@ -194,4 +196,8 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
 | 
				
			|||||||
        throw new UnsupportedOperationException();
 | 
					        throw new UnsupportedOperationException();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static String formatDate(LocalDateTime time) {
 | 
				
			||||||
 | 
					        return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), "yyyy-MM-dd'T'HH:mm:ssXXX");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
package cn.iocoder.yudao.framework.core.client.impl;
 | 
					package cn.iocoder.yudao.framework.pay.core.client.impl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import cn.hutool.core.io.IoUtil;
 | 
					import cn.hutool.core.io.IoUtil;
 | 
				
			||||||
import cn.hutool.core.util.RandomUtil;
 | 
					import cn.hutool.core.util.RandomUtil;
 | 
				
			||||||
@@ -6,7 +6,6 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 | 
				
			|||||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 | 
					import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 | 
				
			||||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
 | 
					import cn.iocoder.yudao.framework.pay.core.client.PayClient;
 | 
				
			||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
 | 
					import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
 | 
				
			||||||
import cn.iocoder.yudao.framework.pay.core.client.impl.PayClientFactoryImpl;
 | 
					 | 
				
			||||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
 | 
					import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
 | 
				
			||||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayQrPayClient;
 | 
					import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayQrPayClient;
 | 
				
			||||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayWapPayClient;
 | 
					import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayWapPayClient;
 | 
				
			||||||
@@ -14,6 +13,7 @@ import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig;
 | 
				
			|||||||
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPubPayClient;
 | 
					import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPubPayClient;
 | 
				
			||||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
 | 
					import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
 | 
				
			||||||
import com.alipay.api.response.AlipayTradePrecreateResponse;
 | 
					import com.alipay.api.response.AlipayTradePrecreateResponse;
 | 
				
			||||||
 | 
					import org.junit.jupiter.api.Disabled;
 | 
				
			||||||
import org.junit.jupiter.api.Test;
 | 
					import org.junit.jupiter.api.Test;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.FileInputStream;
 | 
					import java.io.FileInputStream;
 | 
				
			||||||
@@ -24,7 +24,8 @@ import java.io.FileNotFoundException;
 | 
				
			|||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author 芋道源码
 | 
					 * @author 芋道源码
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class PayClientFactoryImplTest {
 | 
					@Disabled
 | 
				
			||||||
 | 
					public class PayClientFactoryImplIntegrationTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final PayClientFactoryImpl payClientFactory = new PayClientFactoryImpl();
 | 
					    private final PayClientFactoryImpl payClientFactory = new PayClientFactoryImpl();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -91,7 +92,7 @@ public class PayClientFactoryImplTest {
 | 
				
			|||||||
        PayClient client = payClientFactory.getPayClient(channelId);
 | 
					        PayClient client = payClientFactory.getPayClient(channelId);
 | 
				
			||||||
        // 发起支付
 | 
					        // 发起支付
 | 
				
			||||||
        PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
 | 
					        PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
 | 
				
			||||||
        reqDTO.setNotifyUrl("http://niubi.natapp1.cc/api/pay/order/notify/alipay-qr/1"); // TODO @tina: 这里改成你的 natapp 回调地址
 | 
					        reqDTO.setNotifyUrl("http://yunai.natapp1.cc/admin-api/pay/notify/callback/18"); // TODO @tina: 这里改成你的 natapp 回调地址
 | 
				
			||||||
        CommonResult<AlipayTradePrecreateResponse> result = (CommonResult<AlipayTradePrecreateResponse>) client.unifiedOrder(reqDTO);
 | 
					        CommonResult<AlipayTradePrecreateResponse> result = (CommonResult<AlipayTradePrecreateResponse>) client.unifiedOrder(reqDTO);
 | 
				
			||||||
        System.out.println(JsonUtils.toJsonString(result));
 | 
					        System.out.println(JsonUtils.toJsonString(result));
 | 
				
			||||||
        System.out.println(result.getData().getQrCode());
 | 
					        System.out.println(result.getData().getQrCode());
 | 
				
			||||||
@@ -121,7 +122,7 @@ public class PayClientFactoryImplTest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private static PayOrderUnifiedReqDTO buildPayOrderUnifiedReqDTO() {
 | 
					    private static PayOrderUnifiedReqDTO buildPayOrderUnifiedReqDTO() {
 | 
				
			||||||
        PayOrderUnifiedReqDTO reqDTO = new PayOrderUnifiedReqDTO();
 | 
					        PayOrderUnifiedReqDTO reqDTO = new PayOrderUnifiedReqDTO();
 | 
				
			||||||
        reqDTO.setAmount(123L);
 | 
					        reqDTO.setAmount(123);
 | 
				
			||||||
        reqDTO.setSubject("IPhone 13");
 | 
					        reqDTO.setSubject("IPhone 13");
 | 
				
			||||||
        reqDTO.setBody("biubiubiu");
 | 
					        reqDTO.setBody("biubiubiu");
 | 
				
			||||||
        reqDTO.setMerchantOrderId(String.valueOf(System.currentTimeMillis()));
 | 
					        reqDTO.setMerchantOrderId(String.valueOf(System.currentTimeMillis()));
 | 
				
			||||||
@@ -73,7 +73,7 @@ public class AlipayQrPayClientTest extends BaseMockitoUnitTest {
 | 
				
			|||||||
        Long shopOrderId = System.currentTimeMillis();
 | 
					        Long shopOrderId = System.currentTimeMillis();
 | 
				
			||||||
        PayOrderUnifiedReqDTO reqDTO=new PayOrderUnifiedReqDTO();
 | 
					        PayOrderUnifiedReqDTO reqDTO=new PayOrderUnifiedReqDTO();
 | 
				
			||||||
        reqDTO.setMerchantOrderId(String.valueOf(System.currentTimeMillis()));
 | 
					        reqDTO.setMerchantOrderId(String.valueOf(System.currentTimeMillis()));
 | 
				
			||||||
        reqDTO.setAmount(1L);
 | 
					        reqDTO.setAmount(1);
 | 
				
			||||||
        reqDTO.setBody("内容:" + shopOrderId);
 | 
					        reqDTO.setBody("内容:" + shopOrderId);
 | 
				
			||||||
        reqDTO.setSubject("标题:"+shopOrderId);
 | 
					        reqDTO.setSubject("标题:"+shopOrderId);
 | 
				
			||||||
        String notify="http://niubi.natapp1.cc/api/pay/order/notify";
 | 
					        String notify="http://niubi.natapp1.cc/api/pay/order/notify";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,8 +35,8 @@ public class AliyunSmsCodeMapping implements SmsCodeMapping {
 | 
				
			|||||||
            case "isv.OUT_OF_SERVICE": return SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_MONEY_NOT_ENOUGH;
 | 
					            case "isv.OUT_OF_SERVICE": return SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_MONEY_NOT_ENOUGH;
 | 
				
			||||||
            case "isv.MOBILE_NUMBER_ILLEGAL": return SmsFrameworkErrorCodeConstants.SMS_MOBILE_INVALID;
 | 
					            case "isv.MOBILE_NUMBER_ILLEGAL": return SmsFrameworkErrorCodeConstants.SMS_MOBILE_INVALID;
 | 
				
			||||||
            case "isv.TEMPLATE_MISSING_PARAMETERS": return SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_PARAM_ERROR;
 | 
					            case "isv.TEMPLATE_MISSING_PARAMETERS": return SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_PARAM_ERROR;
 | 
				
			||||||
 | 
					            default: return SmsFrameworkErrorCodeConstants.SMS_UNKNOWN;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return SmsFrameworkErrorCodeConstants.SMS_UNKNOWN;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
package cn.iocoder.yudao.framework.tenant.core.aop;
 | 
					package cn.iocoder.yudao.framework.tenant.core.aop;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
 | 
					import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
import org.aspectj.lang.ProceedingJoinPoint;
 | 
					import org.aspectj.lang.ProceedingJoinPoint;
 | 
				
			||||||
import org.aspectj.lang.annotation.Around;
 | 
					import org.aspectj.lang.annotation.Around;
 | 
				
			||||||
@@ -11,6 +12,8 @@ import org.aspectj.lang.annotation.Aspect;
 | 
				
			|||||||
 * 例如说,一个定时任务,读取所有数据,进行处理。
 | 
					 * 例如说,一个定时任务,读取所有数据,进行处理。
 | 
				
			||||||
 * 又例如说,读取所有数据,进行缓存。
 | 
					 * 又例如说,读取所有数据,进行缓存。
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 | 
					 * 整体逻辑的实现,和 {@link TenantUtils#executeIgnore(Runnable)} 需要保持一致
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 * @author 芋道源码
 | 
					 * @author 芋道源码
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@Aspect
 | 
					@Aspect
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,11 @@ package cn.iocoder.yudao.framework.tenant.core.util;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
 | 
					import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					import java.util.concurrent.Callable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 多租户 Util
 | 
					 * 多租户 Util
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
@@ -32,4 +37,57 @@ public class TenantUtils {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 使用指定租户,执行对应的逻辑
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * 注意,如果当前是忽略租户的情况下,会被强制设置成不忽略租户
 | 
				
			||||||
 | 
					     * 当然,执行完成后,还是会恢复回去
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param tenantId 租户编号
 | 
				
			||||||
 | 
					     * @param callable 逻辑
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static <V> V execute(Long tenantId, Callable<V> callable) {
 | 
				
			||||||
 | 
					        Long oldTenantId = TenantContextHolder.getTenantId();
 | 
				
			||||||
 | 
					        Boolean oldIgnore = TenantContextHolder.isIgnore();
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            TenantContextHolder.setTenantId(tenantId);
 | 
				
			||||||
 | 
					            TenantContextHolder.setIgnore(false);
 | 
				
			||||||
 | 
					            // 执行逻辑
 | 
				
			||||||
 | 
					            return callable.call();
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					            throw new RuntimeException(e);
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            TenantContextHolder.setTenantId(oldTenantId);
 | 
				
			||||||
 | 
					            TenantContextHolder.setIgnore(oldIgnore);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 忽略租户,执行对应的逻辑
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param runnable 逻辑
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static void executeIgnore(Runnable runnable) {
 | 
				
			||||||
 | 
					        Boolean oldIgnore = TenantContextHolder.isIgnore();
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            TenantContextHolder.setIgnore(true);
 | 
				
			||||||
 | 
					            // 执行逻辑
 | 
				
			||||||
 | 
					            runnable.run();
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            TenantContextHolder.setIgnore(oldIgnore);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 将多租户编号,添加到 header 中
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param headers HTTP 请求 headers
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static void addTenantHeader(Map<String, String> headers) {
 | 
				
			||||||
 | 
					        Long tenantId = TenantContextHolder.getTenantId();
 | 
				
			||||||
 | 
					        if (tenantId != null) {
 | 
				
			||||||
 | 
					            headers.put(HEADER_TENANT_ID, tenantId.toString());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,6 +34,7 @@
 | 
				
			|||||||
        <!-- 三方云服务相关 -->
 | 
					        <!-- 三方云服务相关 -->
 | 
				
			||||||
        <dependency>
 | 
					        <dependency>
 | 
				
			||||||
            <groupId>com.github.binarywang</groupId>
 | 
					            <groupId>com.github.binarywang</groupId>
 | 
				
			||||||
 | 
					<!--            <artifactId>weixin-java-mp</artifactId>-->
 | 
				
			||||||
            <artifactId>wx-java-mp-spring-boot-starter</artifactId>
 | 
					            <artifactId>wx-java-mp-spring-boot-starter</artifactId>
 | 
				
			||||||
            <version>4.4.0</version>
 | 
					            <version>4.4.0</version>
 | 
				
			||||||
        </dependency>
 | 
					        </dependency>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,6 +17,10 @@
 | 
				
			|||||||
    </description>
 | 
					    </description>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <dependencies>
 | 
					    <dependencies>
 | 
				
			||||||
 | 
					        <dependency>
 | 
				
			||||||
 | 
					            <groupId>com.xingyuv</groupId>
 | 
				
			||||||
 | 
					            <artifactId>spring-boot-starter-captcha-plus</artifactId>
 | 
				
			||||||
 | 
					        </dependency>
 | 
				
			||||||
        <!-- Spring 核心 -->
 | 
					        <!-- Spring 核心 -->
 | 
				
			||||||
        <dependency>
 | 
					        <dependency>
 | 
				
			||||||
            <groupId>org.springframework.boot</groupId>
 | 
					            <groupId>org.springframework.boot</groupId>
 | 
				
			||||||
@@ -29,11 +33,6 @@
 | 
				
			|||||||
            <artifactId>yudao-spring-boot-starter-redis</artifactId>
 | 
					            <artifactId>yudao-spring-boot-starter-redis</artifactId>
 | 
				
			||||||
        </dependency>
 | 
					        </dependency>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <!-- 验证码相关 -->
 | 
					 | 
				
			||||||
        <dependency>
 | 
					 | 
				
			||||||
            <groupId>com.anji-plus</groupId>
 | 
					 | 
				
			||||||
            <artifactId>spring-boot-starter-captcha</artifactId>
 | 
					 | 
				
			||||||
        </dependency>
 | 
					 | 
				
			||||||
    </dependencies>
 | 
					    </dependencies>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</project>
 | 
					</project>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.captcha.config;
 | 
				
			|||||||
import cn.hutool.core.util.ClassUtil;
 | 
					import cn.hutool.core.util.ClassUtil;
 | 
				
			||||||
import cn.iocoder.yudao.framework.captcha.core.enums.CaptchaRedisKeyConstants;
 | 
					import cn.iocoder.yudao.framework.captcha.core.enums.CaptchaRedisKeyConstants;
 | 
				
			||||||
import cn.iocoder.yudao.framework.captcha.core.service.RedisCaptchaServiceImpl;
 | 
					import cn.iocoder.yudao.framework.captcha.core.service.RedisCaptchaServiceImpl;
 | 
				
			||||||
import com.anji.captcha.service.CaptchaCacheService;
 | 
					import com.xingyuv.captcha.service.CaptchaCacheService;
 | 
				
			||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
 | 
					import org.springframework.boot.autoconfigure.AutoConfiguration;
 | 
				
			||||||
import org.springframework.context.annotation.Bean;
 | 
					import org.springframework.context.annotation.Bean;
 | 
				
			||||||
import org.springframework.data.redis.core.StringRedisTemplate;
 | 
					import org.springframework.data.redis.core.StringRedisTemplate;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,10 @@
 | 
				
			|||||||
package cn.iocoder.yudao.framework.captcha.core.enums;
 | 
					package cn.iocoder.yudao.framework.captcha.core.enums;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
 | 
					import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
 | 
				
			||||||
import com.anji.captcha.model.vo.PointVO;
 | 
					import com.xingyuv.captcha.model.vo.PointVO;
 | 
				
			||||||
import org.redisson.api.RLock;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.time.Duration;
 | 
					import java.time.Duration;
 | 
				
			||||||
import java.util.Map;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.HASH;
 | 
					 | 
				
			||||||
import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
 | 
					import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,8 @@
 | 
				
			|||||||
package cn.iocoder.yudao.framework.captcha.core.service;
 | 
					package cn.iocoder.yudao.framework.captcha.core.service;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.anji.captcha.service.CaptchaCacheService;
 | 
					import com.xingyuv.captcha.service.CaptchaCacheService;
 | 
				
			||||||
import lombok.AllArgsConstructor;
 | 
					import lombok.AllArgsConstructor;
 | 
				
			||||||
import lombok.NoArgsConstructor;
 | 
					import lombok.NoArgsConstructor;
 | 
				
			||||||
import lombok.RequiredArgsConstructor;
 | 
					 | 
				
			||||||
import org.springframework.data.redis.core.StringRedisTemplate;
 | 
					import org.springframework.data.redis.core.StringRedisTemplate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.annotation.Resource;
 | 
					import javax.annotation.Resource;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<project xmlns="http://maven.apache.org/POM/4.0.0"
 | 
				
			||||||
 | 
					         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
				
			||||||
 | 
					         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
				
			||||||
 | 
					    <modelVersion>4.0.0</modelVersion>
 | 
				
			||||||
 | 
					    <parent>
 | 
				
			||||||
 | 
					        <artifactId>yudao-framework</artifactId>
 | 
				
			||||||
 | 
					        <groupId>cn.iocoder.boot</groupId>
 | 
				
			||||||
 | 
					        <version>${revision}</version>
 | 
				
			||||||
 | 
					    </parent>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <artifactId>yudao-spring-boot-starter-desensitize</artifactId>
 | 
				
			||||||
 | 
					    <description>脱敏组件:支持 JSON 返回数据时,将邮箱、手机等字段进行脱敏</description>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <dependencies>
 | 
				
			||||||
 | 
					        <dependency>
 | 
				
			||||||
 | 
					            <groupId>cn.iocoder.boot</groupId>
 | 
				
			||||||
 | 
					            <artifactId>yudao-common</artifactId>
 | 
				
			||||||
 | 
					        </dependency>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <!-- jackson -->
 | 
				
			||||||
 | 
					        <dependency>
 | 
				
			||||||
 | 
					            <groupId>com.fasterxml.jackson.core</groupId>
 | 
				
			||||||
 | 
					            <artifactId>jackson-annotations</artifactId>
 | 
				
			||||||
 | 
					        </dependency>
 | 
				
			||||||
 | 
					        <dependency>
 | 
				
			||||||
 | 
					            <groupId>com.fasterxml.jackson.core</groupId>
 | 
				
			||||||
 | 
					            <artifactId>jackson-databind</artifactId>
 | 
				
			||||||
 | 
					        </dependency>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <!-- Test 测试相关 -->
 | 
				
			||||||
 | 
					        <dependency>
 | 
				
			||||||
 | 
					            <groupId>cn.iocoder.boot</groupId>
 | 
				
			||||||
 | 
					            <artifactId>yudao-spring-boot-starter-test</artifactId>
 | 
				
			||||||
 | 
					            <scope>test</scope>
 | 
				
			||||||
 | 
					        </dependency>
 | 
				
			||||||
 | 
					    </dependencies>
 | 
				
			||||||
 | 
					</project>
 | 
				
			||||||
@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.base.annotation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.base.serializer.StringDesensitizeSerializer;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.annotation.Documented;
 | 
				
			||||||
 | 
					import java.lang.annotation.ElementType;
 | 
				
			||||||
 | 
					import java.lang.annotation.Retention;
 | 
				
			||||||
 | 
					import java.lang.annotation.RetentionPolicy;
 | 
				
			||||||
 | 
					import java.lang.annotation.Target;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 顶级脱敏注解,自定义注解需要使用此注解
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Documented
 | 
				
			||||||
 | 
					@Target(ElementType.ANNOTATION_TYPE)
 | 
				
			||||||
 | 
					@Retention(RetentionPolicy.RUNTIME)
 | 
				
			||||||
 | 
					@JacksonAnnotationsInside // 此注解是其他所有 jackson 注解的元注解,打上了此注解的注解表明是 jackson 注解的一部分
 | 
				
			||||||
 | 
					@JsonSerialize(using = StringDesensitizeSerializer.class) // 指定序列化器
 | 
				
			||||||
 | 
					public @interface DesensitizeBy {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 脱敏处理器
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @SuppressWarnings("rawtypes")
 | 
				
			||||||
 | 
					    Class<? extends DesensitizationHandler> handler();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.base.handler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.annotation.Annotation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 脱敏处理器接口
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public interface DesensitizationHandler<T extends Annotation> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 脱敏
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param origin     原始字符串
 | 
				
			||||||
 | 
					     * @param annotation 注解信息
 | 
				
			||||||
 | 
					     * @return 脱敏后的字符串
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    String desensitize(String origin, T annotation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,92 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.base.serializer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.hutool.core.annotation.AnnotationUtil;
 | 
				
			||||||
 | 
					import cn.hutool.core.lang.Singleton;
 | 
				
			||||||
 | 
					import cn.hutool.core.util.ArrayUtil;
 | 
				
			||||||
 | 
					import cn.hutool.core.util.ReflectUtil;
 | 
				
			||||||
 | 
					import cn.hutool.core.util.StrUtil;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.core.JsonGenerator;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.databind.BeanProperty;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.databind.JsonSerializer;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.databind.SerializerProvider;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.databind.ser.ContextualSerializer;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.databind.ser.std.StdSerializer;
 | 
				
			||||||
 | 
					import lombok.Getter;
 | 
				
			||||||
 | 
					import lombok.Setter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					import java.lang.annotation.Annotation;
 | 
				
			||||||
 | 
					import java.lang.reflect.Field;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 脱敏序列化器
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * 实现 JSON 返回数据时,使用 {@link DesensitizationHandler} 对声明脱敏注解的字段,进行脱敏处理。
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@SuppressWarnings("rawtypes")
 | 
				
			||||||
 | 
					public class StringDesensitizeSerializer extends StdSerializer<String> implements ContextualSerializer {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Getter
 | 
				
			||||||
 | 
					    @Setter
 | 
				
			||||||
 | 
					    private DesensitizationHandler desensitizationHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected StringDesensitizeSerializer() {
 | 
				
			||||||
 | 
					        super(String.class);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) {
 | 
				
			||||||
 | 
					        DesensitizeBy annotation = beanProperty.getAnnotation(DesensitizeBy.class);
 | 
				
			||||||
 | 
					        if (annotation == null) {
 | 
				
			||||||
 | 
					            return this;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // 创建一个 StringDesensitizeSerializer 对象,使用 DesensitizeBy 对应的处理器
 | 
				
			||||||
 | 
					        StringDesensitizeSerializer serializer = new StringDesensitizeSerializer();
 | 
				
			||||||
 | 
					        serializer.setDesensitizationHandler(Singleton.get(annotation.handler()));
 | 
				
			||||||
 | 
					        return serializer;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    @SuppressWarnings("unchecked")
 | 
				
			||||||
 | 
					    public void serialize(String value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {
 | 
				
			||||||
 | 
					        if (StrUtil.isBlank(value)) {
 | 
				
			||||||
 | 
					            gen.writeNull();
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // 获取序列化字段
 | 
				
			||||||
 | 
					        Field field = getField(gen);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 自定义处理器
 | 
				
			||||||
 | 
					        DesensitizeBy[] annotations = AnnotationUtil.getCombinationAnnotations(field, DesensitizeBy.class);
 | 
				
			||||||
 | 
					        if (ArrayUtil.isEmpty(annotations)) {
 | 
				
			||||||
 | 
					            gen.writeString(value);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (Annotation annotation : field.getAnnotations()) {
 | 
				
			||||||
 | 
					            if (AnnotationUtil.hasAnnotation(annotation.annotationType(), DesensitizeBy.class)) {
 | 
				
			||||||
 | 
					                value = this.desensitizationHandler.desensitize(value, annotation);
 | 
				
			||||||
 | 
					                gen.writeString(value);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        gen.writeString(value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 获取字段
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param generator JsonGenerator
 | 
				
			||||||
 | 
					     * @return 字段
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private Field getField(JsonGenerator generator) {
 | 
				
			||||||
 | 
					        String currentName = generator.getOutputContext().getCurrentName();
 | 
				
			||||||
 | 
					        Object currentValue = generator.getCurrentValue();
 | 
				
			||||||
 | 
					        Class<?> currentValueClass = currentValue.getClass();
 | 
				
			||||||
 | 
					        return ReflectUtil.getField(currentValueClass, currentName);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 脱敏组件:支持 JSON 返回数据时,将邮箱、手机等字段进行脱敏
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core;
 | 
				
			||||||
@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.regex.annotation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.regex.handler.EmailDesensitizationHandler;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.annotation.Documented;
 | 
				
			||||||
 | 
					import java.lang.annotation.ElementType;
 | 
				
			||||||
 | 
					import java.lang.annotation.Retention;
 | 
				
			||||||
 | 
					import java.lang.annotation.RetentionPolicy;
 | 
				
			||||||
 | 
					import java.lang.annotation.Target;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 邮箱脱敏注解
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Documented
 | 
				
			||||||
 | 
					@Target({ElementType.FIELD})
 | 
				
			||||||
 | 
					@Retention(RetentionPolicy.RUNTIME)
 | 
				
			||||||
 | 
					@JacksonAnnotationsInside
 | 
				
			||||||
 | 
					@DesensitizeBy(handler = EmailDesensitizationHandler.class)
 | 
				
			||||||
 | 
					public @interface EmailDesensitize {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 匹配的正则表达式
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    String regex() default "(^.)[^@]*(@.*$)";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 替换规则,邮箱;
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * 比如:example@gmail.com 脱敏之后为 e****@gmail.com
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    String replacer() default "$1****$2";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.regex.annotation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.regex.handler.DefaultRegexDesensitizationHandler;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.annotation.Documented;
 | 
				
			||||||
 | 
					import java.lang.annotation.ElementType;
 | 
				
			||||||
 | 
					import java.lang.annotation.Retention;
 | 
				
			||||||
 | 
					import java.lang.annotation.RetentionPolicy;
 | 
				
			||||||
 | 
					import java.lang.annotation.Target;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 正则脱敏注解
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Documented
 | 
				
			||||||
 | 
					@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
 | 
				
			||||||
 | 
					@Retention(RetentionPolicy.RUNTIME)
 | 
				
			||||||
 | 
					@JacksonAnnotationsInside
 | 
				
			||||||
 | 
					@DesensitizeBy(handler = DefaultRegexDesensitizationHandler.class)
 | 
				
			||||||
 | 
					public @interface RegexDesensitize {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 匹配的正则表达式(默认匹配所有)
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    String regex() default "^[\\s\\S]*$";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 替换规则,会将匹配到的字符串全部替换成 replacer
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * 例如:regex=123; replacer=******
 | 
				
			||||||
 | 
					     * 原始字符串 123456789
 | 
				
			||||||
 | 
					     * 脱敏后字符串 ******456789
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    String replacer() default "******";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.regex.handler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.annotation.Annotation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 正则表达式脱敏处理器抽象类,已实现通用的方法
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public abstract class AbstractRegexDesensitizationHandler<T extends Annotation>
 | 
				
			||||||
 | 
					        implements DesensitizationHandler<T> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public String desensitize(String origin, T annotation) {
 | 
				
			||||||
 | 
					        String regex = getRegex(annotation);
 | 
				
			||||||
 | 
					        String replacer = getReplacer(annotation);
 | 
				
			||||||
 | 
					        return origin.replaceAll(regex, replacer);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 获取注解上的 regex 参数
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param annotation 注解信息
 | 
				
			||||||
 | 
					     * @return 正则表达式
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    abstract String getRegex(T annotation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 获取注解上的 replacer 参数
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param annotation 注解信息
 | 
				
			||||||
 | 
					     * @return 待替换的字符串
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    abstract String getReplacer(T annotation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.regex.handler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.regex.annotation.RegexDesensitize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {@link RegexDesensitize} 的正则脱敏处理器
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class DefaultRegexDesensitizationHandler extends AbstractRegexDesensitizationHandler<RegexDesensitize> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    String getRegex(RegexDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.regex();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    String getReplacer(RegexDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.replacer();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.regex.handler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.regex.annotation.EmailDesensitize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {@link EmailDesensitize} 的脱敏处理器
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class EmailDesensitizationHandler extends AbstractRegexDesensitizationHandler<EmailDesensitize> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    String getRegex(EmailDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.regex();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    String getReplacer(EmailDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.replacer();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.handler.BankCardDesensitization;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.annotation.Documented;
 | 
				
			||||||
 | 
					import java.lang.annotation.ElementType;
 | 
				
			||||||
 | 
					import java.lang.annotation.Retention;
 | 
				
			||||||
 | 
					import java.lang.annotation.RetentionPolicy;
 | 
				
			||||||
 | 
					import java.lang.annotation.Target;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 银行卡号
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Documented
 | 
				
			||||||
 | 
					@Target({ElementType.FIELD})
 | 
				
			||||||
 | 
					@Retention(RetentionPolicy.RUNTIME)
 | 
				
			||||||
 | 
					@JacksonAnnotationsInside
 | 
				
			||||||
 | 
					@DesensitizeBy(handler = BankCardDesensitization.class)
 | 
				
			||||||
 | 
					public @interface BankCardDesensitize {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 前缀保留长度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    int prefixKeep() default 6;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 后缀保留长度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    int suffixKeep() default 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 替换规则,银行卡号; 比如:9988002866797031 脱敏之后为 998800********31
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    String replacer() default "*";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.handler.CarLicenseDesensitization;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.annotation.Documented;
 | 
				
			||||||
 | 
					import java.lang.annotation.ElementType;
 | 
				
			||||||
 | 
					import java.lang.annotation.Retention;
 | 
				
			||||||
 | 
					import java.lang.annotation.RetentionPolicy;
 | 
				
			||||||
 | 
					import java.lang.annotation.Target;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 车牌号
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Documented
 | 
				
			||||||
 | 
					@Target({ElementType.FIELD})
 | 
				
			||||||
 | 
					@Retention(RetentionPolicy.RUNTIME)
 | 
				
			||||||
 | 
					@JacksonAnnotationsInside
 | 
				
			||||||
 | 
					@DesensitizeBy(handler = CarLicenseDesensitization.class)
 | 
				
			||||||
 | 
					public @interface CarLicenseDesensitize {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 前缀保留长度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    int prefixKeep() default 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 后缀保留长度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    int suffixKeep() default 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 替换规则,车牌号;比如:粤A66666 脱敏之后为粤A6***6
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    String replacer() default "*";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.handler.ChineseNameDesensitization;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.annotation.Documented;
 | 
				
			||||||
 | 
					import java.lang.annotation.ElementType;
 | 
				
			||||||
 | 
					import java.lang.annotation.Retention;
 | 
				
			||||||
 | 
					import java.lang.annotation.RetentionPolicy;
 | 
				
			||||||
 | 
					import java.lang.annotation.Target;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 中文名
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Documented
 | 
				
			||||||
 | 
					@Target({ElementType.FIELD})
 | 
				
			||||||
 | 
					@Retention(RetentionPolicy.RUNTIME)
 | 
				
			||||||
 | 
					@JacksonAnnotationsInside
 | 
				
			||||||
 | 
					@DesensitizeBy(handler = ChineseNameDesensitization.class)
 | 
				
			||||||
 | 
					public @interface ChineseNameDesensitize {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 前缀保留长度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    int prefixKeep() default 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 后缀保留长度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    int suffixKeep() default 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 替换规则,中文名;比如:刘子豪脱敏之后为刘**
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    String replacer() default "*";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.handler.FixedPhoneDesensitization;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.annotation.Documented;
 | 
				
			||||||
 | 
					import java.lang.annotation.ElementType;
 | 
				
			||||||
 | 
					import java.lang.annotation.Retention;
 | 
				
			||||||
 | 
					import java.lang.annotation.RetentionPolicy;
 | 
				
			||||||
 | 
					import java.lang.annotation.Target;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 固定电话
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Documented
 | 
				
			||||||
 | 
					@Target({ElementType.FIELD})
 | 
				
			||||||
 | 
					@Retention(RetentionPolicy.RUNTIME)
 | 
				
			||||||
 | 
					@JacksonAnnotationsInside
 | 
				
			||||||
 | 
					@DesensitizeBy(handler = FixedPhoneDesensitization.class)
 | 
				
			||||||
 | 
					public @interface FixedPhoneDesensitize {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 前缀保留长度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    int prefixKeep() default 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 后缀保留长度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    int suffixKeep() default 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 替换规则,固定电话;比如:01086551122 脱敏之后为 0108*****22
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    String replacer() default "*";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.handler.IdCardDesensitization;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.annotation.Documented;
 | 
				
			||||||
 | 
					import java.lang.annotation.ElementType;
 | 
				
			||||||
 | 
					import java.lang.annotation.Retention;
 | 
				
			||||||
 | 
					import java.lang.annotation.RetentionPolicy;
 | 
				
			||||||
 | 
					import java.lang.annotation.Target;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 身份证
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Documented
 | 
				
			||||||
 | 
					@Target({ElementType.FIELD})
 | 
				
			||||||
 | 
					@Retention(RetentionPolicy.RUNTIME)
 | 
				
			||||||
 | 
					@JacksonAnnotationsInside
 | 
				
			||||||
 | 
					@DesensitizeBy(handler = IdCardDesensitization.class)
 | 
				
			||||||
 | 
					public @interface IdCardDesensitize {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 前缀保留长度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    int prefixKeep() default 6;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 后缀保留长度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    int suffixKeep() default 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 替换规则,身份证号码;比如:530321199204074611 脱敏之后为 530321**********11
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    String replacer() default "*";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.handler.MobileDesensitization;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.annotation.Documented;
 | 
				
			||||||
 | 
					import java.lang.annotation.ElementType;
 | 
				
			||||||
 | 
					import java.lang.annotation.Retention;
 | 
				
			||||||
 | 
					import java.lang.annotation.RetentionPolicy;
 | 
				
			||||||
 | 
					import java.lang.annotation.Target;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 手机号
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Documented
 | 
				
			||||||
 | 
					@Target({ElementType.FIELD})
 | 
				
			||||||
 | 
					@Retention(RetentionPolicy.RUNTIME)
 | 
				
			||||||
 | 
					@JacksonAnnotationsInside
 | 
				
			||||||
 | 
					@DesensitizeBy(handler = MobileDesensitization.class)
 | 
				
			||||||
 | 
					public @interface MobileDesensitize {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 前缀保留长度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    int prefixKeep() default 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 后缀保留长度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    int suffixKeep() default 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 替换规则,手机号;比如:13248765917 脱敏之后为 132****5917
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    String replacer() default "*";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.handler.PasswordDesensitization;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.annotation.Documented;
 | 
				
			||||||
 | 
					import java.lang.annotation.ElementType;
 | 
				
			||||||
 | 
					import java.lang.annotation.Retention;
 | 
				
			||||||
 | 
					import java.lang.annotation.RetentionPolicy;
 | 
				
			||||||
 | 
					import java.lang.annotation.Target;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 密码
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Documented
 | 
				
			||||||
 | 
					@Target({ElementType.FIELD})
 | 
				
			||||||
 | 
					@Retention(RetentionPolicy.RUNTIME)
 | 
				
			||||||
 | 
					@JacksonAnnotationsInside
 | 
				
			||||||
 | 
					@DesensitizeBy(handler = PasswordDesensitization.class)
 | 
				
			||||||
 | 
					public @interface PasswordDesensitize {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 前缀保留长度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    int prefixKeep() default 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 后缀保留长度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    int suffixKeep() default 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 替换规则,密码;
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * 比如:123456 脱敏之后为 ******
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    String replacer() default "*";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.handler.DefaultDesensitizationHandler;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.annotation.Documented;
 | 
				
			||||||
 | 
					import java.lang.annotation.ElementType;
 | 
				
			||||||
 | 
					import java.lang.annotation.Retention;
 | 
				
			||||||
 | 
					import java.lang.annotation.RetentionPolicy;
 | 
				
			||||||
 | 
					import java.lang.annotation.Target;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 滑动脱敏注解
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Documented
 | 
				
			||||||
 | 
					@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
 | 
				
			||||||
 | 
					@Retention(RetentionPolicy.RUNTIME)
 | 
				
			||||||
 | 
					@JacksonAnnotationsInside
 | 
				
			||||||
 | 
					@DesensitizeBy(handler = DefaultDesensitizationHandler.class)
 | 
				
			||||||
 | 
					public @interface SliderDesensitize {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 后缀保留长度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    int suffixKeep() default 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 替换规则,会将前缀后缀保留后,全部替换成 replacer
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * 例如:prefixKeep = 1; suffixKeep = 2; replacer = "*";
 | 
				
			||||||
 | 
					     * 原始字符串  123456
 | 
				
			||||||
 | 
					     * 脱敏后     1***56
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    String replacer() default "*";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 前缀保留长度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    int prefixKeep() default 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.annotation.Annotation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 滑动脱敏处理器抽象类,已实现通用的方法
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public abstract class AbstractSliderDesensitizationHandler<T extends Annotation>
 | 
				
			||||||
 | 
					        implements DesensitizationHandler<T> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public String desensitize(String origin, T annotation) {
 | 
				
			||||||
 | 
					        int prefixKeep = getPrefixKeep(annotation);
 | 
				
			||||||
 | 
					        int suffixKeep = getSuffixKeep(annotation);
 | 
				
			||||||
 | 
					        String replacer = getReplacer(annotation);
 | 
				
			||||||
 | 
					        int length = origin.length();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 情况一:原始字符串长度小于等于保留长度,则原始字符串全部替换
 | 
				
			||||||
 | 
					        if (prefixKeep >= length || suffixKeep >= length) {
 | 
				
			||||||
 | 
					            return buildReplacerByLength(replacer, length);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 情况二:原始字符串长度小于等于前后缀保留字符串长度,则原始字符串全部替换
 | 
				
			||||||
 | 
					        if ((prefixKeep + suffixKeep) >= length) {
 | 
				
			||||||
 | 
					            return buildReplacerByLength(replacer, length);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 情况三:原始字符串长度大于前后缀保留字符串长度,则替换中间字符串
 | 
				
			||||||
 | 
					        int interval = length - prefixKeep - suffixKeep;
 | 
				
			||||||
 | 
					        return origin.substring(0, prefixKeep) +
 | 
				
			||||||
 | 
					                buildReplacerByLength(replacer, interval) +
 | 
				
			||||||
 | 
					                origin.substring(prefixKeep + interval);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 根据长度循环构建替换符
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param replacer 替换符
 | 
				
			||||||
 | 
					     * @param length   长度
 | 
				
			||||||
 | 
					     * @return 构建后的替换符
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private String buildReplacerByLength(String replacer, int length) {
 | 
				
			||||||
 | 
					        StringBuilder builder = new StringBuilder();
 | 
				
			||||||
 | 
					        for (int i = 0; i < length; i++) {
 | 
				
			||||||
 | 
					            builder.append(replacer);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return builder.toString();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 前缀保留长度
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param annotation 注解信息
 | 
				
			||||||
 | 
					     * @return 前缀保留长度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    abstract Integer getPrefixKeep(T annotation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 后缀保留长度
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param annotation 注解信息
 | 
				
			||||||
 | 
					     * @return 后缀保留长度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    abstract Integer getSuffixKeep(T annotation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 替换符
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param annotation 注解信息
 | 
				
			||||||
 | 
					     * @return 替换符
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    abstract String getReplacer(T annotation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.BankCardDesensitize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {@link BankCardDesensitize} 的脱敏处理器
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class BankCardDesensitization extends AbstractSliderDesensitizationHandler<BankCardDesensitize> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    Integer getPrefixKeep(BankCardDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.prefixKeep();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    Integer getSuffixKeep(BankCardDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.suffixKeep();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    String getReplacer(BankCardDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.replacer();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.CarLicenseDesensitize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {@link CarLicenseDesensitize} 的脱敏处理器
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class CarLicenseDesensitization extends AbstractSliderDesensitizationHandler<CarLicenseDesensitize> {
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    Integer getPrefixKeep(CarLicenseDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.prefixKeep();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    Integer getSuffixKeep(CarLicenseDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.suffixKeep();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    String getReplacer(CarLicenseDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.replacer();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.ChineseNameDesensitize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {@link ChineseNameDesensitize} 的脱敏处理器
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class ChineseNameDesensitization extends AbstractSliderDesensitizationHandler<ChineseNameDesensitize> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    Integer getPrefixKeep(ChineseNameDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.prefixKeep();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    Integer getSuffixKeep(ChineseNameDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.suffixKeep();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    String getReplacer(ChineseNameDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.replacer();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.SliderDesensitize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {@link SliderDesensitize} 的脱敏处理器
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class DefaultDesensitizationHandler extends AbstractSliderDesensitizationHandler<SliderDesensitize> {
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    Integer getPrefixKeep(SliderDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.prefixKeep();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    Integer getSuffixKeep(SliderDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.suffixKeep();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    String getReplacer(SliderDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.replacer();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.FixedPhoneDesensitize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {@link FixedPhoneDesensitize} 的脱敏处理器
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class FixedPhoneDesensitization extends AbstractSliderDesensitizationHandler<FixedPhoneDesensitize> {
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    Integer getPrefixKeep(FixedPhoneDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.prefixKeep();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    Integer getSuffixKeep(FixedPhoneDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.suffixKeep();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    String getReplacer(FixedPhoneDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.replacer();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.IdCardDesensitize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {@link IdCardDesensitize} 的脱敏处理器
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class IdCardDesensitization extends AbstractSliderDesensitizationHandler<IdCardDesensitize> {
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    Integer getPrefixKeep(IdCardDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.prefixKeep();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    Integer getSuffixKeep(IdCardDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.suffixKeep();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    String getReplacer(IdCardDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.replacer();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.MobileDesensitize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {@link MobileDesensitize} 的脱敏处理器
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class MobileDesensitization extends AbstractSliderDesensitizationHandler<MobileDesensitize> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    Integer getPrefixKeep(MobileDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.prefixKeep();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    Integer getSuffixKeep(MobileDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.suffixKeep();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    String getReplacer(MobileDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.replacer();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.PasswordDesensitize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {@link PasswordDesensitize} 的码脱敏处理器
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class PasswordDesensitization extends AbstractSliderDesensitizationHandler<PasswordDesensitize> {
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    Integer getPrefixKeep(PasswordDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.prefixKeep();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    Integer getSuffixKeep(PasswordDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.suffixKeep();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    String getReplacer(PasswordDesensitize annotation) {
 | 
				
			||||||
 | 
					        return annotation.replacer();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,98 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.regex.annotation.EmailDesensitize;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.regex.annotation.RegexDesensitize;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.annotation.Address;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.BankCardDesensitize;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.CarLicenseDesensitize;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.ChineseNameDesensitize;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.FixedPhoneDesensitize;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.IdCardDesensitize;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.PasswordDesensitize;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.MobileDesensitize;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.SliderDesensitize;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
 | 
				
			||||||
 | 
					import lombok.Data;
 | 
				
			||||||
 | 
					import org.junit.jupiter.api.Test;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.junit.jupiter.api.Assertions.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {@link DesensitizeTest} 的单元测试
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class DesensitizeTest extends BaseMockitoUnitTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void test() {
 | 
				
			||||||
 | 
					        // 准备参数
 | 
				
			||||||
 | 
					        DesensitizeDemo desensitizeDemo = new DesensitizeDemo();
 | 
				
			||||||
 | 
					        desensitizeDemo.setNickname("芋道源码");
 | 
				
			||||||
 | 
					        desensitizeDemo.setBankCard("9988002866797031");
 | 
				
			||||||
 | 
					        desensitizeDemo.setCarLicense("粤A66666");
 | 
				
			||||||
 | 
					        desensitizeDemo.setFixedPhone("01086551122");
 | 
				
			||||||
 | 
					        desensitizeDemo.setIdCard("530321199204074611");
 | 
				
			||||||
 | 
					        desensitizeDemo.setPassword("123456");
 | 
				
			||||||
 | 
					        desensitizeDemo.setPhoneNumber("13248765917");
 | 
				
			||||||
 | 
					        desensitizeDemo.setSlider1("ABCDEFG");
 | 
				
			||||||
 | 
					        desensitizeDemo.setSlider2("ABCDEFG");
 | 
				
			||||||
 | 
					        desensitizeDemo.setSlider3("ABCDEFG");
 | 
				
			||||||
 | 
					        desensitizeDemo.setEmail("1@email.com");
 | 
				
			||||||
 | 
					        desensitizeDemo.setRegex("你好,我是芋道源码");
 | 
				
			||||||
 | 
					        desensitizeDemo.setAddress("北京市海淀区上地十街10号");
 | 
				
			||||||
 | 
					        desensitizeDemo.setOrigin("芋道源码");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 调用
 | 
				
			||||||
 | 
					        DesensitizeDemo d = JsonUtils.parseObject(JsonUtils.toJsonString(desensitizeDemo), DesensitizeDemo.class);
 | 
				
			||||||
 | 
					        // 断言
 | 
				
			||||||
 | 
					        assertNotNull(d);
 | 
				
			||||||
 | 
					        assertEquals("芋***", d.getNickname());
 | 
				
			||||||
 | 
					        assertEquals("998800********31", d.getBankCard());
 | 
				
			||||||
 | 
					        assertEquals("粤A6***6", d.getCarLicense());
 | 
				
			||||||
 | 
					        assertEquals("0108*****22", d.getFixedPhone());
 | 
				
			||||||
 | 
					        assertEquals("530321**********11", d.getIdCard());
 | 
				
			||||||
 | 
					        assertEquals("******", d.getPassword());
 | 
				
			||||||
 | 
					        assertEquals("132****5917", d.getPhoneNumber());
 | 
				
			||||||
 | 
					        assertEquals("#######", d.getSlider1());
 | 
				
			||||||
 | 
					        assertEquals("ABC*EFG", d.getSlider2());
 | 
				
			||||||
 | 
					        assertEquals("*******", d.getSlider3());
 | 
				
			||||||
 | 
					        assertEquals("1****@email.com", d.getEmail());
 | 
				
			||||||
 | 
					        assertEquals("你好,我是*", d.getRegex());
 | 
				
			||||||
 | 
					        assertEquals("北京市海淀区上地十街10号*", d.getAddress());
 | 
				
			||||||
 | 
					        assertEquals("芋道源码", d.getOrigin());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Data
 | 
				
			||||||
 | 
					    public static class DesensitizeDemo {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @ChineseNameDesensitize
 | 
				
			||||||
 | 
					        private String nickname;
 | 
				
			||||||
 | 
					        @BankCardDesensitize
 | 
				
			||||||
 | 
					        private String bankCard;
 | 
				
			||||||
 | 
					        @CarLicenseDesensitize
 | 
				
			||||||
 | 
					        private String carLicense;
 | 
				
			||||||
 | 
					        @FixedPhoneDesensitize
 | 
				
			||||||
 | 
					        private String fixedPhone;
 | 
				
			||||||
 | 
					        @IdCardDesensitize
 | 
				
			||||||
 | 
					        private String idCard;
 | 
				
			||||||
 | 
					        @PasswordDesensitize
 | 
				
			||||||
 | 
					        private String password;
 | 
				
			||||||
 | 
					        @MobileDesensitize
 | 
				
			||||||
 | 
					        private String phoneNumber;
 | 
				
			||||||
 | 
					        @SliderDesensitize(prefixKeep = 6, suffixKeep = 1, replacer = "#")
 | 
				
			||||||
 | 
					        private String slider1;
 | 
				
			||||||
 | 
					        @SliderDesensitize(prefixKeep = 3, suffixKeep = 3)
 | 
				
			||||||
 | 
					        private String slider2;
 | 
				
			||||||
 | 
					        @SliderDesensitize(prefixKeep = 10)
 | 
				
			||||||
 | 
					        private String slider3;
 | 
				
			||||||
 | 
					        @EmailDesensitize
 | 
				
			||||||
 | 
					        private String email;
 | 
				
			||||||
 | 
					        @RegexDesensitize(regex = "芋道源码", replacer = "*")
 | 
				
			||||||
 | 
					        private String regex;
 | 
				
			||||||
 | 
					        @Address
 | 
				
			||||||
 | 
					        private String address;
 | 
				
			||||||
 | 
					        private String origin;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.annotation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.DesensitizeTest;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.handler.AddressHandler;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.annotation.Documented;
 | 
				
			||||||
 | 
					import java.lang.annotation.ElementType;
 | 
				
			||||||
 | 
					import java.lang.annotation.Retention;
 | 
				
			||||||
 | 
					import java.lang.annotation.RetentionPolicy;
 | 
				
			||||||
 | 
					import java.lang.annotation.Target;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 地址
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * 用于 {@link DesensitizeTest} 测试使用
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author gaibu
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Documented
 | 
				
			||||||
 | 
					@Target({ElementType.FIELD})
 | 
				
			||||||
 | 
					@Retention(RetentionPolicy.RUNTIME)
 | 
				
			||||||
 | 
					@JacksonAnnotationsInside
 | 
				
			||||||
 | 
					@DesensitizeBy(handler = AddressHandler.class)
 | 
				
			||||||
 | 
					public @interface Address {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    String replacer() default "*";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.desensitize.core.handler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.DesensitizeTest;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.desensitize.core.annotation.Address;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {@link Address} 的脱敏处理器
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * 用于 {@link DesensitizeTest} 测试使用
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class AddressHandler implements DesensitizationHandler<Address> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public String desensitize(String origin, Address annotation) {
 | 
				
			||||||
 | 
					        return origin + annotation.replacer();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -9,10 +9,11 @@ import io.minio.*;
 | 
				
			|||||||
import java.io.ByteArrayInputStream;
 | 
					import java.io.ByteArrayInputStream;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_ALIYUN;
 | 
					import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_ALIYUN;
 | 
				
			||||||
 | 
					import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_TENCENT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 基于 S3 协议的文件客户端,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务
 | 
					 * 基于 S3 协议的文件客户端,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务
 | 
				
			||||||
 *
 | 
					 * <p>
 | 
				
			||||||
 * S3 协议的客户端,采用亚马逊提供的 software.amazon.awssdk.s3 库
 | 
					 * S3 协议的客户端,采用亚马逊提供的 software.amazon.awssdk.s3 库
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author 芋道源码
 | 
					 * @author 芋道源码
 | 
				
			||||||
@@ -78,6 +79,11 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
 | 
				
			|||||||
                    .replaceAll("-internal", "")// 去除内网 Endpoint 的后缀
 | 
					                    .replaceAll("-internal", "")// 去除内网 Endpoint 的后缀
 | 
				
			||||||
                    .replaceAll("https://", "");
 | 
					                    .replaceAll("https://", "");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        // 腾讯云必须有 region,否则会报错
 | 
				
			||||||
 | 
					        if (config.getEndpoint().contains(ENDPOINT_TENCENT)) {
 | 
				
			||||||
 | 
					            return StrUtil.subAfter(config.getEndpoint(), ".cos.", false)
 | 
				
			||||||
 | 
					                    .replaceAll("." + ENDPOINT_TENCENT, ""); // 去除 Endpoint
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        return null;
 | 
					        return null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,7 @@ public class S3FileClientConfig implements FileClientConfig {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public static final String ENDPOINT_QINIU = "qiniucs.com";
 | 
					    public static final String ENDPOINT_QINIU = "qiniucs.com";
 | 
				
			||||||
    public static final String ENDPOINT_ALIYUN = "aliyuncs.com";
 | 
					    public static final String ENDPOINT_ALIYUN = "aliyuncs.com";
 | 
				
			||||||
 | 
					    public static final String ENDPOINT_TENCENT = "myqcloud.com";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 节点地址
 | 
					     * 节点地址
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,8 +8,11 @@ import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
 | 
				
			|||||||
import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;
 | 
					import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;
 | 
				
			||||||
import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
 | 
					import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
 | 
				
			||||||
import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;
 | 
					import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.mq.job.RedisPendingMessageResendJob;
 | 
				
			||||||
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
 | 
					import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
 | 
					import org.redisson.api.RedissonClient;
 | 
				
			||||||
 | 
					import org.springframework.beans.factory.annotation.Value;
 | 
				
			||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
 | 
					import org.springframework.boot.autoconfigure.AutoConfiguration;
 | 
				
			||||||
import org.springframework.context.annotation.Bean;
 | 
					import org.springframework.context.annotation.Bean;
 | 
				
			||||||
import org.springframework.data.redis.connection.RedisServerCommands;
 | 
					import org.springframework.data.redis.connection.RedisServerCommands;
 | 
				
			||||||
@@ -24,7 +27,7 @@ import org.springframework.data.redis.listener.ChannelTopic;
 | 
				
			|||||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
 | 
					import org.springframework.data.redis.listener.RedisMessageListenerContainer;
 | 
				
			||||||
import org.springframework.data.redis.stream.DefaultStreamMessageListenerContainerX;
 | 
					import org.springframework.data.redis.stream.DefaultStreamMessageListenerContainerX;
 | 
				
			||||||
import org.springframework.data.redis.stream.StreamMessageListenerContainer;
 | 
					import org.springframework.data.redis.stream.StreamMessageListenerContainer;
 | 
				
			||||||
import org.springframework.scheduling.annotation.Async;
 | 
					import org.springframework.scheduling.annotation.EnableScheduling;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Properties;
 | 
					import java.util.Properties;
 | 
				
			||||||
@@ -35,6 +38,7 @@ import java.util.Properties;
 | 
				
			|||||||
 * @author 芋道源码
 | 
					 * @author 芋道源码
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@Slf4j
 | 
					@Slf4j
 | 
				
			||||||
 | 
					@EnableScheduling // 启用定时任务,用于 RedisPendingMessageResendJob 重发消息
 | 
				
			||||||
@AutoConfiguration(after = YudaoRedisAutoConfiguration.class)
 | 
					@AutoConfiguration(after = YudaoRedisAutoConfiguration.class)
 | 
				
			||||||
public class YudaoMQAutoConfiguration {
 | 
					public class YudaoMQAutoConfiguration {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -69,9 +73,20 @@ public class YudaoMQAutoConfiguration {
 | 
				
			|||||||
        return container;
 | 
					        return container;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 创建 Redis Stream 重新消费的任务
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @Bean
 | 
				
			||||||
 | 
					    public RedisPendingMessageResendJob redisPendingMessageResendJob(List<AbstractStreamMessageListener<?>> listeners,
 | 
				
			||||||
 | 
					                                                                     RedisMQTemplate redisTemplate,
 | 
				
			||||||
 | 
					                                                                     @Value("${spring.application.name}") String groupName,
 | 
				
			||||||
 | 
					                                                                     RedissonClient redissonClient) {
 | 
				
			||||||
 | 
					        return new RedisPendingMessageResendJob(listeners, redisTemplate, groupName, redissonClient);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 创建 Redis Stream 集群消费的容器
 | 
					     * 创建 Redis Stream 集群消费的容器
 | 
				
			||||||
     *
 | 
					     * <p>
 | 
				
			||||||
     * Redis Stream 的 xreadgroup 命令:https://www.geek-book.com/src/docs/redis/redis/redis.io/commands/xreadgroup.html
 | 
					     * Redis Stream 的 xreadgroup 命令:https://www.geek-book.com/src/docs/redis/redis/redis.io/commands/xreadgroup.html
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    @Bean(initMethod = "start", destroyMethod = "stop")
 | 
					    @Bean(initMethod = "start", destroyMethod = "stop")
 | 
				
			||||||
@@ -99,7 +114,8 @@ public class YudaoMQAutoConfiguration {
 | 
				
			|||||||
            // 创建 listener 对应的消费者分组
 | 
					            // 创建 listener 对应的消费者分组
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                redisTemplate.opsForStream().createGroup(listener.getStreamKey(), listener.getGroup());
 | 
					                redisTemplate.opsForStream().createGroup(listener.getStreamKey(), listener.getGroup());
 | 
				
			||||||
            } catch (Exception ignore) {}
 | 
					            } catch (Exception ignore) {
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            // 设置 listener 对应的 redisTemplate
 | 
					            // 设置 listener 对应的 redisTemplate
 | 
				
			||||||
            listener.setRedisMQTemplate(redisMQTemplate);
 | 
					            listener.setRedisMQTemplate(redisMQTemplate);
 | 
				
			||||||
            // 创建 Consumer 对象
 | 
					            // 创建 Consumer 对象
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,80 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.mq.job;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.hutool.core.collection.CollUtil;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;
 | 
				
			||||||
 | 
					import lombok.AllArgsConstructor;
 | 
				
			||||||
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
 | 
					import org.redisson.api.RLock;
 | 
				
			||||||
 | 
					import org.redisson.api.RedissonClient;
 | 
				
			||||||
 | 
					import org.springframework.data.redis.connection.stream.Consumer;
 | 
				
			||||||
 | 
					import org.springframework.data.redis.connection.stream.MapRecord;
 | 
				
			||||||
 | 
					import org.springframework.data.redis.connection.stream.PendingMessagesSummary;
 | 
				
			||||||
 | 
					import org.springframework.data.redis.connection.stream.ReadOffset;
 | 
				
			||||||
 | 
					import org.springframework.data.redis.connection.stream.StreamOffset;
 | 
				
			||||||
 | 
					import org.springframework.data.redis.connection.stream.StreamRecords;
 | 
				
			||||||
 | 
					import org.springframework.data.redis.core.StreamOperations;
 | 
				
			||||||
 | 
					import org.springframework.scheduling.annotation.Scheduled;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 这个任务用于处理,crash 之后的消费者未消费完的消息
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Slf4j
 | 
				
			||||||
 | 
					@AllArgsConstructor
 | 
				
			||||||
 | 
					public class RedisPendingMessageResendJob {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final String LOCK_KEY = "redis:pending:msg:lock";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final List<AbstractStreamMessageListener<?>> listeners;
 | 
				
			||||||
 | 
					    private final RedisMQTemplate redisTemplate;
 | 
				
			||||||
 | 
					    private final String groupName;
 | 
				
			||||||
 | 
					    private final RedissonClient redissonClient;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 一分钟执行一次,这里选择每分钟的35秒执行,是为了避免整点任务过多的问题
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @Scheduled(cron = "35 * * * * ?")
 | 
				
			||||||
 | 
					    public void messageResend() {
 | 
				
			||||||
 | 
					        RLock lock = redissonClient.getLock(LOCK_KEY);
 | 
				
			||||||
 | 
					        // 尝试加锁
 | 
				
			||||||
 | 
					        if (lock.tryLock()) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                execute();
 | 
				
			||||||
 | 
					            } catch (Exception ex) {
 | 
				
			||||||
 | 
					                log.error("[messageResend][执行异常]", ex);
 | 
				
			||||||
 | 
					            } finally {
 | 
				
			||||||
 | 
					                lock.unlock();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void execute() {
 | 
				
			||||||
 | 
					        StreamOperations<String, Object, Object> ops = redisTemplate.getRedisTemplate().opsForStream();
 | 
				
			||||||
 | 
					        listeners.forEach(listener -> {
 | 
				
			||||||
 | 
					            PendingMessagesSummary pendingMessagesSummary = ops.pending(listener.getStreamKey(), groupName);
 | 
				
			||||||
 | 
					            // 每个消费者的 pending 队列消息数量
 | 
				
			||||||
 | 
					            Map<String, Long> pendingMessagesPerConsumer = pendingMessagesSummary.getPendingMessagesPerConsumer();
 | 
				
			||||||
 | 
					            pendingMessagesPerConsumer.forEach((consumerName, pendingMessageCount) -> {
 | 
				
			||||||
 | 
					                log.info("[processPendingMessage][消费者({}) 消息数量({})]", consumerName, pendingMessageCount);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // 从消费者的 pending 队列中读取消息
 | 
				
			||||||
 | 
					                List<MapRecord<String, Object, Object>> records = ops.read(Consumer.from(groupName, consumerName), StreamOffset.create(listener.getStreamKey(), ReadOffset.from("0")));
 | 
				
			||||||
 | 
					                if (CollUtil.isEmpty(records)) {
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                for (MapRecord<String, Object, Object> record : records) {
 | 
				
			||||||
 | 
					                    // 重新投递消息
 | 
				
			||||||
 | 
					                    redisTemplate.getRedisTemplate().opsForStream().add(StreamRecords.newRecord()
 | 
				
			||||||
 | 
					                            .ofObject(record.getValue()) // 设置内容
 | 
				
			||||||
 | 
					                            .withStreamKey(listener.getStreamKey()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // ack 消息消费完成
 | 
				
			||||||
 | 
					                    redisTemplate.getRedisTemplate().opsForStream().acknowledge(groupName, record);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -9,6 +9,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 | 
				
			|||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 | 
					import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 | 
				
			||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
 | 
					import com.baomidou.mybatisplus.core.metadata.IPage;
 | 
				
			||||||
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
 | 
					import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
 | 
				
			||||||
 | 
					import com.baomidou.mybatisplus.extension.toolkit.Db;
 | 
				
			||||||
import org.apache.ibatis.annotations.Param;
 | 
					import org.apache.ibatis.annotations.Param;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.Collection;
 | 
					import java.util.Collection;
 | 
				
			||||||
@@ -75,20 +76,35 @@ public interface BaseMapperX<T> extends BaseMapper<T> {
 | 
				
			|||||||
        return selectList(new LambdaQueryWrapper<T>().in(field, values));
 | 
					        return selectList(new LambdaQueryWrapper<T>().in(field, values));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default List<T> selectList(SFunction<T, ?> leField, SFunction<T, ?> geField, Object value) {
 | 
				
			||||||
 | 
					        return selectList(new LambdaQueryWrapper<T>().le(leField, value).ge(geField, value));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 逐条插入,适合少量数据插入,或者对性能要求不高的场景
 | 
					     * 批量插入,适合大量数据插入
 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * 如果大量,请使用 {@link com.baomidou.mybatisplus.extension.service.impl.ServiceImpl#saveBatch(Collection)} 方法
 | 
					 | 
				
			||||||
     * 使用示例,可见 RoleMenuBatchInsertMapper、UserRoleBatchInsertMapper 类
 | 
					 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param entities 实体们
 | 
					     * @param entities 实体们
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    default void insertBatch(Collection<T> entities) {
 | 
					    default void insertBatch(Collection<T> entities) {
 | 
				
			||||||
        entities.forEach(this::insert);
 | 
					        Db.saveBatch(entities);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 批量插入,适合大量数据插入
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param entities 实体们
 | 
				
			||||||
 | 
					     * @param size     插入数量 Db.saveBatch 默认为 1000
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    default void insertBatch(Collection<T> entities, int size) {
 | 
				
			||||||
 | 
					        Db.saveBatch(entities, size);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    default void updateBatch(T update) {
 | 
					    default void updateBatch(T update) {
 | 
				
			||||||
        update(update, new QueryWrapper<>());
 | 
					        update(update, new QueryWrapper<>());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default void updateBatch(Collection<T> entities, int size) {
 | 
				
			||||||
 | 
					        Db.updateBatchById(entities, size);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -129,6 +129,8 @@ public class YudaoWebSecurityConfigurerAdapter {
 | 
				
			|||||||
                .antMatchers(buildAppApi("/**")).permitAll()
 | 
					                .antMatchers(buildAppApi("/**")).permitAll()
 | 
				
			||||||
                // 1.5 验证码captcha 允许匿名访问
 | 
					                // 1.5 验证码captcha 允许匿名访问
 | 
				
			||||||
                .antMatchers("/captcha/get", "/captcha/check").permitAll()
 | 
					                .antMatchers("/captcha/get", "/captcha/check").permitAll()
 | 
				
			||||||
 | 
					                // 1.6 webSocket 允许匿名访问
 | 
				
			||||||
 | 
					                .antMatchers("/websocket/message").permitAll()
 | 
				
			||||||
                // ②:每个项目的自定义规则
 | 
					                // ②:每个项目的自定义规则
 | 
				
			||||||
                .and().authorizeRequests(registry -> // 下面,循环设置自定义规则
 | 
					                .and().authorizeRequests(registry -> // 下面,循环设置自定义规则
 | 
				
			||||||
                        authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(registry)))
 | 
					                        authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(registry)))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,6 +33,10 @@ public class AssertUtils {
 | 
				
			|||||||
    public static void assertPojoEquals(Object expected, Object actual, String... ignoreFields) {
 | 
					    public static void assertPojoEquals(Object expected, Object actual, String... ignoreFields) {
 | 
				
			||||||
        Field[] expectedFields = ReflectUtil.getFields(expected.getClass());
 | 
					        Field[] expectedFields = ReflectUtil.getFields(expected.getClass());
 | 
				
			||||||
        Arrays.stream(expectedFields).forEach(expectedField -> {
 | 
					        Arrays.stream(expectedFields).forEach(expectedField -> {
 | 
				
			||||||
 | 
					            // 忽略 jacoco 自动生成的 $jacocoData 属性的情况
 | 
				
			||||||
 | 
					            if (expectedField.isSynthetic()) {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            // 如果是忽略的属性,则不进行比对
 | 
					            // 如果是忽略的属性,则不进行比对
 | 
				
			||||||
            if (ArrayUtil.contains(ignoreFields, expectedField.getName())) {
 | 
					            if (ArrayUtil.contains(ignoreFields, expectedField.getName())) {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,6 +21,12 @@
 | 
				
			|||||||
            <artifactId>yudao-common</artifactId>
 | 
					            <artifactId>yudao-common</artifactId>
 | 
				
			||||||
        </dependency>
 | 
					        </dependency>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <dependency>
 | 
				
			||||||
 | 
					            <groupId>org.apache.commons</groupId>
 | 
				
			||||||
 | 
					            <artifactId>commons-lang3</artifactId>
 | 
				
			||||||
 | 
					            <scope>provided</scope>
 | 
				
			||||||
 | 
					        </dependency>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <!-- Web 相关 -->
 | 
					        <!-- Web 相关 -->
 | 
				
			||||||
        <dependency>
 | 
					        <dependency>
 | 
				
			||||||
            <groupId>org.springframework.boot</groupId>
 | 
					            <groupId>org.springframework.boot</groupId>
 | 
				
			||||||
@@ -35,7 +41,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        <dependency>
 | 
					        <dependency>
 | 
				
			||||||
            <groupId>com.github.xiaoymin</groupId>
 | 
					            <groupId>com.github.xiaoymin</groupId>
 | 
				
			||||||
            <artifactId>knife4j-spring-boot-starter</artifactId>
 | 
					            <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
 | 
				
			||||||
        </dependency>
 | 
					        </dependency>
 | 
				
			||||||
        <dependency>
 | 
					        <dependency>
 | 
				
			||||||
            <groupId>io.swagger</groupId>
 | 
					            <groupId>io.swagger</groupId>
 | 
				
			||||||
@@ -67,6 +73,12 @@
 | 
				
			|||||||
            <scope>provided</scope> <!-- 设置为 provided,主要是 GlobalExceptionHandler 使用 -->
 | 
					            <scope>provided</scope> <!-- 设置为 provided,主要是 GlobalExceptionHandler 使用 -->
 | 
				
			||||||
        </dependency>
 | 
					        </dependency>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <!-- xss -->
 | 
				
			||||||
 | 
					        <dependency>
 | 
				
			||||||
 | 
					            <groupId>org.jsoup</groupId>
 | 
				
			||||||
 | 
					            <artifactId>jsoup</artifactId>
 | 
				
			||||||
 | 
					        </dependency>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    </dependencies>
 | 
					    </dependencies>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</project>
 | 
					</project>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.jackson.core.databind;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.time.ZoneId;
 | 
				
			||||||
 | 
					import java.time.format.DateTimeFormatter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_HOUR_MINUTE_SECOND;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class LocalTimeJson {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static final LocalTimeSerializer SERIALIZER = new LocalTimeSerializer(DateTimeFormatter
 | 
				
			||||||
 | 
					            .ofPattern(FORMAT_HOUR_MINUTE_SECOND)
 | 
				
			||||||
 | 
					            .withZone(ZoneId.systemDefault()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static final LocalTimeDeserializer DESERIALIZABLE = new LocalTimeDeserializer(DateTimeFormatter
 | 
				
			||||||
 | 
					            .ofPattern(FORMAT_HOUR_MINUTE_SECOND)
 | 
				
			||||||
 | 
					            .withZone(ZoneId.systemDefault()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1 +1,4 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Web 框架,全局异常、API 日志等
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
package cn.iocoder.yudao.framework;
 | 
					package cn.iocoder.yudao.framework;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,15 +9,16 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
 | 
				
			|||||||
import org.springframework.context.annotation.Bean;
 | 
					import org.springframework.context.annotation.Bean;
 | 
				
			||||||
import org.springframework.http.HttpHeaders;
 | 
					import org.springframework.http.HttpHeaders;
 | 
				
			||||||
import springfox.documentation.builders.ApiInfoBuilder;
 | 
					import springfox.documentation.builders.ApiInfoBuilder;
 | 
				
			||||||
import springfox.documentation.builders.ExampleBuilder;
 | 
					import springfox.documentation.builders.ParameterBuilder;
 | 
				
			||||||
import springfox.documentation.builders.PathSelectors;
 | 
					import springfox.documentation.builders.PathSelectors;
 | 
				
			||||||
import springfox.documentation.builders.RequestParameterBuilder;
 | 
					import springfox.documentation.schema.ModelRef;
 | 
				
			||||||
import springfox.documentation.service.*;
 | 
					import springfox.documentation.service.*;
 | 
				
			||||||
import springfox.documentation.spi.DocumentationType;
 | 
					import springfox.documentation.spi.DocumentationType;
 | 
				
			||||||
import springfox.documentation.spi.service.contexts.SecurityContext;
 | 
					import springfox.documentation.spi.service.contexts.SecurityContext;
 | 
				
			||||||
import springfox.documentation.spring.web.plugins.Docket;
 | 
					import springfox.documentation.spring.web.plugins.Docket;
 | 
				
			||||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
 | 
					import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
import java.util.Collections;
 | 
					import java.util.Collections;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -30,7 +31,7 @@ import static springfox.documentation.builders.RequestHandlerSelectors.basePacka
 | 
				
			|||||||
 * @author 芋道源码
 | 
					 * @author 芋道源码
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@AutoConfiguration
 | 
					@AutoConfiguration
 | 
				
			||||||
@EnableSwagger2
 | 
					@EnableSwagger2WebMvc
 | 
				
			||||||
@EnableKnife4j
 | 
					@EnableKnife4j
 | 
				
			||||||
@ConditionalOnClass({Docket.class, ApiInfoBuilder.class})
 | 
					@ConditionalOnClass({Docket.class, ApiInfoBuilder.class})
 | 
				
			||||||
// 允许使用 swagger.enable=false 禁用 Swagger
 | 
					// 允许使用 swagger.enable=false 禁用 Swagger
 | 
				
			||||||
@@ -59,7 +60,7 @@ public class YudaoSwaggerAutoConfiguration {
 | 
				
			|||||||
                .securitySchemes(securitySchemes())
 | 
					                .securitySchemes(securitySchemes())
 | 
				
			||||||
                .securityContexts(securityContexts())
 | 
					                .securityContexts(securityContexts())
 | 
				
			||||||
                // ④ 全局参数(多租户 header)
 | 
					                // ④ 全局参数(多租户 header)
 | 
				
			||||||
                .globalRequestParameters(globalRequestParameters());
 | 
					                .globalOperationParameters(globalRequestParameters());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // ========== apiInfo ==========
 | 
					    // ========== apiInfo ==========
 | 
				
			||||||
@@ -95,7 +96,7 @@ public class YudaoSwaggerAutoConfiguration {
 | 
				
			|||||||
        return Collections.singletonList(SecurityContext.builder()
 | 
					        return Collections.singletonList(SecurityContext.builder()
 | 
				
			||||||
                .securityReferences(securityReferences())
 | 
					                .securityReferences(securityReferences())
 | 
				
			||||||
                // 通过 PathSelectors.regex("^(?!auth).*$"),排除包含 "auth" 的接口不需要使用securitySchemes
 | 
					                // 通过 PathSelectors.regex("^(?!auth).*$"),排除包含 "auth" 的接口不需要使用securitySchemes
 | 
				
			||||||
                .operationSelector(o -> o.requestMappingPattern().matches("^(?!auth).*$"))
 | 
					                .forPaths(PathSelectors.regex("^(?!auth).*$"))
 | 
				
			||||||
                .build());
 | 
					                .build());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -109,11 +110,17 @@ public class YudaoSwaggerAutoConfiguration {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // ========== globalRequestParameters ==========
 | 
					    // ========== globalRequestParameters ==========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static List<RequestParameter> globalRequestParameters() {
 | 
					    private static List<Parameter> globalRequestParameters() {
 | 
				
			||||||
        RequestParameterBuilder tenantParameter = new RequestParameterBuilder()
 | 
					        List<Parameter> tenantParameter = new ArrayList<>();
 | 
				
			||||||
                .name(HEADER_TENANT_ID).description("租户编号")
 | 
					        tenantParameter.add(new ParameterBuilder()
 | 
				
			||||||
                .in(ParameterType.HEADER).example(new ExampleBuilder().value(1L).build());
 | 
					                .name(HEADER_TENANT_ID)
 | 
				
			||||||
        return Collections.singletonList(tenantParameter.build());
 | 
					                .description("租户编号")
 | 
				
			||||||
 | 
					                .modelRef(new ModelRef("long"))
 | 
				
			||||||
 | 
					                .defaultValue("1")
 | 
				
			||||||
 | 
					                .parameterType("header")
 | 
				
			||||||
 | 
					                .required(true)
 | 
				
			||||||
 | 
					                .build());
 | 
				
			||||||
 | 
					        return tenantParameter;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 | 
				
			|||||||
import org.springframework.beans.BeansException;
 | 
					import org.springframework.beans.BeansException;
 | 
				
			||||||
import org.springframework.beans.factory.config.BeanPostProcessor;
 | 
					import org.springframework.beans.factory.config.BeanPostProcessor;
 | 
				
			||||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
 | 
					import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
 | 
				
			||||||
import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
 | 
					 | 
				
			||||||
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
 | 
					import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
@@ -20,7 +19,7 @@ public class SpringFoxHandlerProviderBeanPostProcessor implements BeanPostProces
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
 | 
					    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
 | 
				
			||||||
        if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
 | 
					        if (bean instanceof WebMvcRequestHandlerProvider) {
 | 
				
			||||||
            customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
 | 
					            customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return bean;
 | 
					        return bean;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkServic
 | 
				
			|||||||
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
 | 
					import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
 | 
				
			||||||
import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyFilter;
 | 
					import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyFilter;
 | 
				
			||||||
import cn.iocoder.yudao.framework.web.core.filter.DemoFilter;
 | 
					import cn.iocoder.yudao.framework.web.core.filter.DemoFilter;
 | 
				
			||||||
import cn.iocoder.yudao.framework.web.core.filter.XssFilter;
 | 
					 | 
				
			||||||
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
 | 
					import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
 | 
				
			||||||
import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler;
 | 
					import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler;
 | 
				
			||||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
 | 
					import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
 | 
				
			||||||
@@ -15,7 +14,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
 | 
				
			|||||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
 | 
					import org.springframework.boot.web.servlet.FilterRegistrationBean;
 | 
				
			||||||
import org.springframework.context.annotation.Bean;
 | 
					import org.springframework.context.annotation.Bean;
 | 
				
			||||||
import org.springframework.util.AntPathMatcher;
 | 
					import org.springframework.util.AntPathMatcher;
 | 
				
			||||||
import org.springframework.util.PathMatcher;
 | 
					 | 
				
			||||||
import org.springframework.web.bind.annotation.RestController;
 | 
					import org.springframework.web.bind.annotation.RestController;
 | 
				
			||||||
import org.springframework.web.cors.CorsConfiguration;
 | 
					import org.springframework.web.cors.CorsConfiguration;
 | 
				
			||||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
 | 
					import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
 | 
				
			||||||
@@ -27,7 +25,7 @@ import javax.annotation.Resource;
 | 
				
			|||||||
import javax.servlet.Filter;
 | 
					import javax.servlet.Filter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@AutoConfiguration
 | 
					@AutoConfiguration
 | 
				
			||||||
@EnableConfigurationProperties({WebProperties.class, XssProperties.class})
 | 
					@EnableConfigurationProperties(WebProperties.class)
 | 
				
			||||||
public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
 | 
					public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Resource
 | 
					    @Resource
 | 
				
			||||||
@@ -48,7 +46,7 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
 | 
				
			|||||||
     * 设置 API 前缀,仅仅匹配 controller 包下的
 | 
					     * 设置 API 前缀,仅仅匹配 controller 包下的
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param configurer 配置
 | 
					     * @param configurer 配置
 | 
				
			||||||
     * @param api API 配置
 | 
					     * @param api        API 配置
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private void configurePathMatch(PathMatchConfigurer configurer, WebProperties.Api api) {
 | 
					    private void configurePathMatch(PathMatchConfigurer configurer, WebProperties.Api api) {
 | 
				
			||||||
        AntPathMatcher antPathMatcher = new AntPathMatcher(".");
 | 
					        AntPathMatcher antPathMatcher = new AntPathMatcher(".");
 | 
				
			||||||
@@ -100,14 +98,6 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
 | 
				
			|||||||
        return createFilterBean(new CacheRequestBodyFilter(), WebFilterOrderEnum.REQUEST_BODY_CACHE_FILTER);
 | 
					        return createFilterBean(new CacheRequestBodyFilter(), WebFilterOrderEnum.REQUEST_BODY_CACHE_FILTER);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 创建 XssFilter Bean,解决 Xss 安全问题
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Bean
 | 
					 | 
				
			||||||
    public FilterRegistrationBean<XssFilter> xssFilter(XssProperties properties, PathMatcher pathMatcher) {
 | 
					 | 
				
			||||||
        return createFilterBean(new XssFilter(properties, pathMatcher), WebFilterOrderEnum.XSS_FILTER);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 创建 DemoFilter Bean,演示模式
 | 
					     * 创建 DemoFilter Bean,演示模式
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
@@ -117,7 +107,7 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
 | 
				
			|||||||
        return createFilterBean(new DemoFilter(), WebFilterOrderEnum.DEMO_FILTER);
 | 
					        return createFilterBean(new DemoFilter(), WebFilterOrderEnum.DEMO_FILTER);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static <T extends Filter> FilterRegistrationBean<T> createFilterBean(T filter, Integer order) {
 | 
					    public static <T extends Filter> FilterRegistrationBean<T> createFilterBean(T filter, Integer order) {
 | 
				
			||||||
        FilterRegistrationBean<T> bean = new FilterRegistrationBean<>(filter);
 | 
					        FilterRegistrationBean<T> bean = new FilterRegistrationBean<>(filter);
 | 
				
			||||||
        bean.setOrder(order);
 | 
					        bean.setOrder(order);
 | 
				
			||||||
        return bean;
 | 
					        return bean;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,136 +0,0 @@
 | 
				
			|||||||
package cn.iocoder.yudao.framework.web.core.filter;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import cn.hutool.core.collection.CollUtil;
 | 
					 | 
				
			||||||
import cn.hutool.core.io.IoUtil;
 | 
					 | 
				
			||||||
import cn.hutool.core.util.ArrayUtil;
 | 
					 | 
				
			||||||
import cn.hutool.core.util.ReflectUtil;
 | 
					 | 
				
			||||||
import cn.hutool.core.util.StrUtil;
 | 
					 | 
				
			||||||
import cn.hutool.http.HTMLFilter;
 | 
					 | 
				
			||||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import javax.servlet.ReadListener;
 | 
					 | 
				
			||||||
import javax.servlet.ServletInputStream;
 | 
					 | 
				
			||||||
import javax.servlet.http.HttpServletRequest;
 | 
					 | 
				
			||||||
import javax.servlet.http.HttpServletRequestWrapper;
 | 
					 | 
				
			||||||
import java.io.BufferedReader;
 | 
					 | 
				
			||||||
import java.io.ByteArrayInputStream;
 | 
					 | 
				
			||||||
import java.io.IOException;
 | 
					 | 
				
			||||||
import java.io.InputStreamReader;
 | 
					 | 
				
			||||||
import java.util.Map;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Xss 请求 Wrapper
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @author 芋道源码
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
public class XssRequestWrapper extends HttpServletRequestWrapper {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 基于线程级别的 HTMLFilter 对象,因为它线程非安全
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private static final ThreadLocal<HTMLFilter> HTML_FILTER = ThreadLocal.withInitial(() -> {
 | 
					 | 
				
			||||||
        HTMLFilter htmlFilter = new HTMLFilter();
 | 
					 | 
				
			||||||
        // 反射修改 encodeQuotes 属性为 false,避免 " 被转移成 " 字符
 | 
					 | 
				
			||||||
        ReflectUtil.setFieldValue(htmlFilter, "encodeQuotes", false);
 | 
					 | 
				
			||||||
        return htmlFilter;
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public XssRequestWrapper(HttpServletRequest request) {
 | 
					 | 
				
			||||||
        super(request);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static String filterXss(String content) {
 | 
					 | 
				
			||||||
        if (StrUtil.isEmpty(content)) {
 | 
					 | 
				
			||||||
            return content;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return HTML_FILTER.get().filter(content);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // ========== IO 流相关 ==========
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public BufferedReader getReader() throws IOException {
 | 
					 | 
				
			||||||
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public ServletInputStream getInputStream() throws IOException {
 | 
					 | 
				
			||||||
        // 如果非 json 请求,不进行 Xss 处理
 | 
					 | 
				
			||||||
        if (!ServletUtils.isJsonRequest(this)) {
 | 
					 | 
				
			||||||
            return super.getInputStream();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // 读取内容,并过滤
 | 
					 | 
				
			||||||
        String content = IoUtil.readUtf8(super.getInputStream());
 | 
					 | 
				
			||||||
        content = filterXss(content);
 | 
					 | 
				
			||||||
        final ByteArrayInputStream newInputStream = new ByteArrayInputStream(content.getBytes());
 | 
					 | 
				
			||||||
        // 返回 ServletInputStream
 | 
					 | 
				
			||||||
        return new ServletInputStream() {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            @Override
 | 
					 | 
				
			||||||
            public int read() {
 | 
					 | 
				
			||||||
                return newInputStream.read();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            @Override
 | 
					 | 
				
			||||||
            public boolean isFinished() {
 | 
					 | 
				
			||||||
                return true;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            @Override
 | 
					 | 
				
			||||||
            public boolean isReady() {
 | 
					 | 
				
			||||||
                return true;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            @Override
 | 
					 | 
				
			||||||
            public void setReadListener(ReadListener readListener) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // ========== Param 相关 ==========
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public String getParameter(String name) {
 | 
					 | 
				
			||||||
        String value = super.getParameter(name);
 | 
					 | 
				
			||||||
        return filterXss(value);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public String[] getParameterValues(String name) {
 | 
					 | 
				
			||||||
        String[] values = super.getParameterValues(name);
 | 
					 | 
				
			||||||
        if (ArrayUtil.isEmpty(values)) {
 | 
					 | 
				
			||||||
            return values;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        // 过滤处理
 | 
					 | 
				
			||||||
        for (int i = 0; i < values.length; i++) {
 | 
					 | 
				
			||||||
            values[i] = filterXss(values[i]);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return values;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public Map<String, String[]> getParameterMap() {
 | 
					 | 
				
			||||||
        Map<String, String[]> valueMap = super.getParameterMap();
 | 
					 | 
				
			||||||
        if (CollUtil.isEmpty(valueMap)) {
 | 
					 | 
				
			||||||
            return valueMap;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        // 过滤处理
 | 
					 | 
				
			||||||
        for (Map.Entry<String, String[]> entry : valueMap.entrySet()) {
 | 
					 | 
				
			||||||
            String[] values = entry.getValue();
 | 
					 | 
				
			||||||
            for (int i = 0; i < values.length; i++) {
 | 
					 | 
				
			||||||
                values[i] = filterXss(values[i]);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return valueMap;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // ========== Header 相关 ==========
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public String getHeader(String name) {
 | 
					 | 
				
			||||||
        String value = super.getHeader(name);
 | 
					 | 
				
			||||||
        return filterXss(value);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
package cn.iocoder.yudao.framework.web.config;
 | 
					package cn.iocoder.yudao.framework.xss.config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import lombok.Data;
 | 
					import lombok.Data;
 | 
				
			||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
 | 
					import org.springframework.boot.context.properties.ConfigurationProperties;
 | 
				
			||||||
@@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.xss.config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.xss.core.clean.JsoupXssCleaner;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.xss.core.filter.XssFilter;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.xss.core.json.XssStringJsonDeserializer;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.databind.ObjectMapper;
 | 
				
			||||||
 | 
					import org.springframework.boot.autoconfigure.AutoConfiguration;
 | 
				
			||||||
 | 
					import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 | 
				
			||||||
 | 
					import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 | 
				
			||||||
 | 
					import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 | 
				
			||||||
 | 
					import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
 | 
				
			||||||
 | 
					import org.springframework.boot.context.properties.EnableConfigurationProperties;
 | 
				
			||||||
 | 
					import org.springframework.boot.web.servlet.FilterRegistrationBean;
 | 
				
			||||||
 | 
					import org.springframework.context.annotation.Bean;
 | 
				
			||||||
 | 
					import org.springframework.util.PathMatcher;
 | 
				
			||||||
 | 
					import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration.createFilterBean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@AutoConfiguration
 | 
				
			||||||
 | 
					@EnableConfigurationProperties(XssProperties.class)
 | 
				
			||||||
 | 
					public class YudaoXssAutoConfiguration implements WebMvcConfigurer {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Xss 清理者
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return XssCleaner
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @Bean
 | 
				
			||||||
 | 
					    @ConditionalOnMissingBean(XssCleaner.class)
 | 
				
			||||||
 | 
					    public XssCleaner xssCleaner() {
 | 
				
			||||||
 | 
					        return new JsoupXssCleaner();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 注册 Jackson 的序列化器,用于处理 json 类型参数的 xss 过滤
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return Jackson2ObjectMapperBuilderCustomizer
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @Bean
 | 
				
			||||||
 | 
					    @ConditionalOnMissingBean(name = "xssJacksonCustomizer")
 | 
				
			||||||
 | 
					    @ConditionalOnBean(ObjectMapper.class)
 | 
				
			||||||
 | 
					    @ConditionalOnProperty(value = "yudao.xss.enable", havingValue = "true")
 | 
				
			||||||
 | 
					    public Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(XssCleaner xssCleaner) {
 | 
				
			||||||
 | 
					        // 在反序列化时进行 xss 过滤,可以替换使用 XssStringJsonSerializer,在序列化时进行处理
 | 
				
			||||||
 | 
					        return builder -> builder.deserializerByType(String.class, new XssStringJsonDeserializer(xssCleaner));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 创建 XssFilter Bean,解决 Xss 安全问题
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @Bean
 | 
				
			||||||
 | 
					    @ConditionalOnBean(XssCleaner.class)
 | 
				
			||||||
 | 
					    public FilterRegistrationBean<XssFilter> xssFilter(XssProperties properties, PathMatcher pathMatcher, XssCleaner xssCleaner) {
 | 
				
			||||||
 | 
					        return createFilterBean(new XssFilter(properties, pathMatcher, xssCleaner), WebFilterOrderEnum.XSS_FILTER);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,64 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.xss.core.clean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.jsoup.Jsoup;
 | 
				
			||||||
 | 
					import org.jsoup.nodes.Document;
 | 
				
			||||||
 | 
					import org.jsoup.safety.Safelist;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 基于 JSONP 实现 XSS 过滤字符串
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class JsoupXssCleaner implements XssCleaner {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final Safelist safelist;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 用于在 src 属性使用相对路径时,强制转换为绝对路径。 为空时不处理,值应为绝对路径的前缀(包含协议部分)
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private final String baseUri;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 无参构造,默认使用 {@link JsoupXssCleaner#buildSafelist} 方法构建一个安全列表
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public JsoupXssCleaner() {
 | 
				
			||||||
 | 
					        this.safelist = buildSafelist();
 | 
				
			||||||
 | 
					        this.baseUri = "";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 构建一个 Xss 清理的 Safelist 规则。
 | 
				
			||||||
 | 
					     * 基于 Safelist#relaxed() 的基础上:
 | 
				
			||||||
 | 
					     * 1. 扩展支持了 style 和 class 属性
 | 
				
			||||||
 | 
					     * 2. a 标签额外支持了 target 属性
 | 
				
			||||||
 | 
					     * 3. img 标签额外支持了 data 协议,便于支持 base64
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return Safelist
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private Safelist buildSafelist() {
 | 
				
			||||||
 | 
					        // 使用 jsoup 提供的默认的
 | 
				
			||||||
 | 
					        Safelist relaxedSafelist = Safelist.relaxed();
 | 
				
			||||||
 | 
					        // 富文本编辑时一些样式是使用 style 来进行实现的
 | 
				
			||||||
 | 
					        // 比如红色字体 style="color:red;", 所以需要给所有标签添加 style 属性
 | 
				
			||||||
 | 
					        // 注意:style 属性会有注入风险 <img STYLE="background-image:url(javascript:alert('XSS'))">
 | 
				
			||||||
 | 
					        relaxedSafelist.addAttributes(":all", "style", "class");
 | 
				
			||||||
 | 
					        // 保留 a 标签的 target 属性
 | 
				
			||||||
 | 
					        relaxedSafelist.addAttributes("a", "target");
 | 
				
			||||||
 | 
					        // 支持img 为base64
 | 
				
			||||||
 | 
					        relaxedSafelist.addProtocols("img", "src", "data");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 保留相对路径, 保留相对路径时,必须提供对应的 baseUri 属性,否则依然会被删除
 | 
				
			||||||
 | 
					        // WHITELIST.preserveRelativeLinks(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 移除 a 标签和 img 标签的一些协议限制,这会导致 xss 防注入失效,如 <img src=javascript:alert("xss")>
 | 
				
			||||||
 | 
					        // 虽然可以重写 WhiteList#isSafeAttribute 来处理,但是有隐患,所以暂时不支持相对路径
 | 
				
			||||||
 | 
					        // WHITELIST.removeProtocols("a", "href", "ftp", "http", "https", "mailto");
 | 
				
			||||||
 | 
					        // WHITELIST.removeProtocols("img", "src", "http", "https");
 | 
				
			||||||
 | 
					        return relaxedSafelist;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public String clean(String html) {
 | 
				
			||||||
 | 
					        return Jsoup.clean(html, baseUri, safelist, new Document.OutputSettings().prettyPrint(false));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.xss.core.clean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 对 html 文本中的有 Xss 风险的数据进行清理
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public interface XssCleaner {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 清理有 Xss 风险的文本
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param html 原 html
 | 
				
			||||||
 | 
					     * @return 清理后的 html
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    String clean(String html);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
package cn.iocoder.yudao.framework.web.core.filter;
 | 
					package cn.iocoder.yudao.framework.xss.core.filter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import cn.iocoder.yudao.framework.web.config.XssProperties;
 | 
					import cn.iocoder.yudao.framework.xss.config.XssProperties;
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
 | 
				
			||||||
import lombok.AllArgsConstructor;
 | 
					import lombok.AllArgsConstructor;
 | 
				
			||||||
import org.springframework.util.PathMatcher;
 | 
					import org.springframework.util.PathMatcher;
 | 
				
			||||||
import org.springframework.web.filter.OncePerRequestFilter;
 | 
					import org.springframework.web.filter.OncePerRequestFilter;
 | 
				
			||||||
@@ -14,8 +15,6 @@ import java.io.IOException;
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * Xss 过滤器
 | 
					 * Xss 过滤器
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * 对 Xss 不了解的胖友,可以看看 http://www.iocoder.cn/Fight/The-new-girl-asked-me-why-AJAX-requests-are-not-secure-I-did-not-answer/
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @author 芋道源码
 | 
					 * @author 芋道源码
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@AllArgsConstructor
 | 
					@AllArgsConstructor
 | 
				
			||||||
@@ -30,10 +29,12 @@ public class XssFilter extends OncePerRequestFilter {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    private final PathMatcher pathMatcher;
 | 
					    private final PathMatcher pathMatcher;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final XssCleaner xssCleaner;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
 | 
					    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
 | 
				
			||||||
            throws IOException, ServletException {
 | 
					            throws IOException, ServletException {
 | 
				
			||||||
        filterChain.doFilter(new XssRequestWrapper(request), response);
 | 
					        filterChain.doFilter(new XssRequestWrapper(request, xssCleaner), response);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
@@ -0,0 +1,92 @@
 | 
				
			|||||||
 | 
					package cn.iocoder.yudao.framework.xss.core.filter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.servlet.http.HttpServletRequest;
 | 
				
			||||||
 | 
					import javax.servlet.http.HttpServletRequestWrapper;
 | 
				
			||||||
 | 
					import java.util.LinkedHashMap;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Xss 请求 Wrapper
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author 芋道源码
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class XssRequestWrapper extends HttpServletRequestWrapper {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final XssCleaner xssCleaner;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public XssRequestWrapper(HttpServletRequest request, XssCleaner xssCleaner) {
 | 
				
			||||||
 | 
					        super(request);
 | 
				
			||||||
 | 
					        this.xssCleaner = xssCleaner;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // ============================ parameter ============================
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public Map<String, String[]> getParameterMap() {
 | 
				
			||||||
 | 
					        Map<String, String[]> map = new LinkedHashMap<>();
 | 
				
			||||||
 | 
					        Map<String, String[]> parameters = super.getParameterMap();
 | 
				
			||||||
 | 
					        for (Map.Entry<String, String[]> entry : parameters.entrySet()) {
 | 
				
			||||||
 | 
					            String[] values = entry.getValue();
 | 
				
			||||||
 | 
					            for (int i = 0; i < values.length; i++) {
 | 
				
			||||||
 | 
					                values[i] = xssCleaner.clean(values[i]);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            map.put(entry.getKey(), values);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return map;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public String[] getParameterValues(String name) {
 | 
				
			||||||
 | 
					        String[] values = super.getParameterValues(name);
 | 
				
			||||||
 | 
					        if (values == null) {
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        int count = values.length;
 | 
				
			||||||
 | 
					        String[] encodedValues = new String[count];
 | 
				
			||||||
 | 
					        for (int i = 0; i < count; i++) {
 | 
				
			||||||
 | 
					            encodedValues[i] = xssCleaner.clean(values[i]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return encodedValues;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public String getParameter(String name) {
 | 
				
			||||||
 | 
					        String value = super.getParameter(name);
 | 
				
			||||||
 | 
					        if (value == null) {
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return xssCleaner.clean(value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // ============================ attribute ============================
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public Object getAttribute(String name) {
 | 
				
			||||||
 | 
					        Object value = super.getAttribute(name);
 | 
				
			||||||
 | 
					        if (value instanceof String) {
 | 
				
			||||||
 | 
					            xssCleaner.clean((String) value);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return value;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // ============================ header ============================
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public String getHeader(String name) {
 | 
				
			||||||
 | 
					        String value = super.getHeader(name);
 | 
				
			||||||
 | 
					        if (value == null) {
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return xssCleaner.clean(value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // ============================ queryString ============================
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public String getQueryString() {
 | 
				
			||||||
 | 
					        String value = super.getQueryString();
 | 
				
			||||||
 | 
					        if (value == null) {
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return xssCleaner.clean(value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user