mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2025-01-31 17:40:05 +08:00
Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/mail-1.6.1
This commit is contained in:
commit
c9c5a818ec
55
README.md
55
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 单点登录
|
||||||
* 支持加载动态权限菜单,按钮级别权限控制,本地缓存提升性能
|
* 支持加载动态权限菜单,按钮级别权限控制,本地缓存提升性能
|
||||||
@ -39,11 +37,11 @@
|
|||||||
* 集成报表设计器,支持数据报表、图形报表、打印设计等
|
* 集成报表设计器,支持数据报表、图形报表、打印设计等
|
||||||
|
|
||||||
| 项目名 | 说明 | 传送门 |
|
| 项目名 | 说明 | 传送门 |
|
||||||
|--------------------|------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
|
|----------------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `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 修改,**重构优化**后端的代码,**美化**前端的界面。
|
||||||
>
|
>
|
||||||
@ -98,6 +97,7 @@
|
|||||||
| | 通知公告 | 系统通知公告信息发布维护 |
|
| | 通知公告 | 系统通知公告信息发布维护 |
|
||||||
| 🚀 | 敏感词 | 配置系统敏感词,支持标签分组 |
|
| 🚀 | 敏感词 | 配置系统敏感词,支持标签分组 |
|
||||||
| 🚀 | 应用管理 | 管理 SSO 单点登录的应用,支持多种 OAuth2 授权方式 |
|
| 🚀 | 应用管理 | 管理 SSO 单点登录的应用,支持多种 OAuth2 授权方式 |
|
||||||
|
| 🚀 | 地区管理 | 展示省份、城市、区镇等城市信息,支持 IP 对应城市 |
|
||||||
|
|
||||||
### 工作流程
|
### 工作流程
|
||||||
|
|
||||||
@ -153,6 +153,21 @@ ps:核心功能已经实现,正在对接微信小程序中...
|
|||||||
| 🚀 | 报表设计器 | 支持数据报表、图形报表、打印设计等 |
|
| 🚀 | 报表设计器 | 支持数据报表、图形报表、打印设计等 |
|
||||||
| 🚀 | 大屏设计器 | 建设中... 拖拽式实现可视化数据大屏 |
|
| 🚀 | 大屏设计器 | 建设中... 拖拽式实现可视化数据大屏 |
|
||||||
|
|
||||||
|
### 微信公众号
|
||||||
|
|
||||||
|
| | 功能 | 描述 |
|
||||||
|
|-----|--------|-------------------------------|
|
||||||
|
| 🚀 | 账号管理 | 配置接入的微信公众号,可支持多个公众号 |
|
||||||
|
| 🚀 | 数据统计 | 统计公众号的用户增减、累计用户、消息概况、接口分析等数据 |
|
||||||
|
| 🚀 | 粉丝管理 | 查看已关注、取关的粉丝列表,可对粉丝进行同步、打标签等操作 |
|
||||||
|
| 🚀 | 消息管理 | 查看粉丝发送的消息列表,可主动回复粉丝消息 |
|
||||||
|
| 🚀 | 自动回复 | 自动回复粉丝发送的消息,支持关注回复、消息回复、关键字回复 |
|
||||||
|
| 🚀 | 标签管理 | 对公众号的标签进行创建、查询、修改、删除等操作 |
|
||||||
|
| 🚀 | 菜单管理 | 自定义公众号的菜单,也可以从公众号同步菜单 |
|
||||||
|
| 🚀 | 素材管理 | 管理公众号的图片、语音、视频等素材,支持在线播放语音、视频 |
|
||||||
|
| 🚀 | 图文草稿箱 | 新增常用的图文素材到草稿箱,可发布到公众号 |
|
||||||
|
| 🚀 | 图文发表记录 | 查看已发布成功的图文素材,支持删除操作 |
|
||||||
|
|
||||||
### 商城系统
|
### 商城系统
|
||||||
|
|
||||||
建设中...
|
建设中...
|
||||||
@ -170,7 +185,7 @@ ps:核心功能已经实现,正在对接微信小程序中...
|
|||||||
## 🐨 技术栈
|
## 🐨 技术栈
|
||||||
|
|
||||||
| 项目 | 说明 |
|
| 项目 | 说明 |
|
||||||
|-------------------------|-----------------------|
|
|------------------------------|--------------------|
|
||||||
| `yudao-dependencies` | Maven 依赖版本管理 |
|
| `yudao-dependencies` | Maven 依赖版本管理 |
|
||||||
| `yudao-framework` | Java 框架拓展 |
|
| `yudao-framework` | Java 框架拓展 |
|
||||||
| `yudao-server` | 管理后台 + 用户 APP 的服务端 |
|
| `yudao-server` | 管理后台 + 用户 APP 的服务端 |
|
||||||
@ -181,30 +196,32 @@ ps:核心功能已经实现,正在对接微信小程序中...
|
|||||||
| `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) |
|
||||||
@ -223,17 +240,17 @@ ps:核心功能已经实现,正在对接微信小程序中...
|
|||||||
| 框架 | 说明 | 版本 |
|
| 框架 | 说明 | 版本 |
|
||||||
|----------------------------------------------------------------------|:------------:|:------:|
|
|----------------------------------------------------------------------|:------------:|:------:|
|
||||||
| [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
|
||||||
@ -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
Loading…
Reference in New Issue
Block a user