Compare commits

..

42 Commits

Author SHA1 Message Date
679cae1760 release: v2.7.1 2024-09-28 22:58:15 +08:00
46d2212f53 fix: 修复 Maven 配置错误及依赖升级问题 2024-09-28 22:57:53 +08:00
af295d6938 release: v2.7.0 2024-09-28 22:22:38 +08:00
802dcb5735 chore: 升级依赖
Spring Boot 3.2.7 => 3.2.10
MyBatis Plus 3.5.7 => 3.5.8
Redisson 3.35.0 => 3.36.0
CosID 2.9.6 => 2.9.8
SMS4J 3.2.1 => 3.3.3
X File Storage 2.2.0 => 2.2.1
2024-09-28 22:11:59 +08:00
cd69b2adb6 chore: 优化部分依赖传递范围 2024-09-28 21:32:31 +08:00
f8437918de feat(extension/tenant): 新增多租户数据源级隔离支持 2024-09-28 21:31:58 +08:00
f85839559a chore: 统一部分命名风格 2024-09-26 23:34:13 +08:00
7666d56019 feat(extension/datapermission): 新增数据权限模块 2024-09-26 23:27:43 +08:00
04e0b4b1cc revert(data/mp): 还原批量新增和更新方法,方便直接判断成功和失败场景 2024-09-24 22:25:56 +08:00
be4dec5a30 fix(log/interceptor): 修复全局配置和局部配置包含请求、响应体冲突 2024-09-24 22:14:50 +08:00
3535ac64f7 refactor(file/excel): 导出方法增加排除字段参数 2024-09-24 22:07:52 +08:00
fire
ca1b92cde3 fix(log): 修复 continew-starter.log.exclude-patterns 配置不生效的问题 2024-09-20 02:10:08 +00:00
1a97a1b709 feat(extension/tenant): 新增 continew-starter-extension-tenant 多租户模块(暂时仅支持行级隔离) 2024-09-11 23:42:30 +08:00
0c334dadcc chore(data): 移除 QueryIgnore 的无用属性 2024-09-11 22:27:46 +08:00
46773df9dd chore: 优化代码格式 2024-09-11 22:24:46 +08:00
1fc80cda9e chore: 优化部分代码写法 2024-09-11 21:23:50 +08:00
0cede6bf9f chore: 完善 ConditionalOnProperty 配置 2024-09-11 21:22:19 +08:00
5d1601e6eb build(data/mp): BaseMapper 移除批量插入和更新方法 (MP 3.5.7 已提供) 2024-09-11 21:18:52 +08:00
06d3a6ca41 build(data/mp): 移除多数据源依赖,如需使用可手动引入 2024-09-10 22:45:17 +08:00
08ef09c9b5 feat(data/mp): 新增乐观锁插件启用配置(默认关闭) 2024-09-10 22:12:56 +08:00
f3f57b8a5e docs: 更新 README 文档中的模块结构内容 2024-09-07 12:15:18 +08:00
2388d32363 release: v2.6.0 2024-09-06 20:39:50 +08:00
9fdbfdf8bb fix: 修复单参查询加密失效的问题 2024-09-06 20:29:22 +08:00
e9b81f9466 fix: 重构并修复更新场景加密失效的问题(仍需处理 MP 和单参查询加密问题) 2024-09-06 00:03:35 +08:00
427143db69 chore: 升级依赖
SnailJob 1.1.0 => 1.1.2
SaToken 1.38.0 => 1.39.0
MyBatis Flex 1.9.3 => 1.9.7
Redisson 3.32.0 => 3.35.0
CosID 2.9.1 => 2.9.6
SMS4J 3.2.1 => 3.3.2
XFileStorage 2.2.0 => 2.2.1
aws-java-sdk-s3 1.12.761 => 1.12.771
snakeyaml 2.2 => 2.3
Hutool 5.8.32 => 5.8.29
2024-09-05 20:08:22 +08:00
2208dbd028 chore: 升级依赖 graceful-response 4.0.1-boot3 => 5.0.0-boot3 2024-09-05 19:54:19 +08:00
9e5f33b2c9 revert(data): 还原 SQL 函数接口 2024-09-04 20:21:07 +08:00
260f484af9 chore(web): 链路追踪配置属性响应头名称 => 链路 ID 名称 2024-09-04 20:12:44 +08:00
梓陌
4caf0a0db6 fix(log): 修复接口耗时统计始终为0ms问题 2024-09-03 06:19:54 +00:00
16b6e9b466 feat(log): 不记录日志也支持开启打印访问日志 2024-08-30 20:40:30 +08:00
eac5c1fa79 fix(log): 修复日志全局 includes 配置会被局部修改的问题 2024-08-30 06:54:00 +00:00
httpsjt
52fc9d086d 日志includes引用传递修改问题。 2024-08-30 14:45:50 +08:00
5e0eea2ed8 refactor(data): mybatis-plus => mp,mybatis-flex => mf 2024-08-29 19:47:56 +08:00
784a56fd4f refactor(web): 提升接口文档响应类型优化扩展性 2024-08-29 19:42:44 +08:00
d3fa00d14c fix(data): 移除 DataPermission 注解的 value 属性 2024-08-28 22:09:53 +08:00
e55eb17d64 feat(web): 新增 isMatch 路径是否匹配方法
match => isMatch
2024-08-19 22:40:53 +08:00
7571c05f9c release: v2.5.2 2024-08-14 22:19:37 +08:00
4c4f98a86e chore(api-doc): 优化部分代码 2024-08-14 22:14:02 +08:00
吴泽威
bf51837991 refactor(api-doc/web): 重构doc文档枚举展示处理,处理doc 文档全局响应格式 2024-08-14 21:12:23 +08:00
22ebdfeb9f fix(data): 完善 SQL 注入验证工具类 2024-08-14 18:42:32 +08:00
b0a2a8c927 fix(security/crypto): 修复 updateById 修改未正确加密的问题 2024-08-13 23:59:49 +08:00
ea6b316296 refactor(curd): 重构排序字段处理,预防 SQL 注入问题 2024-08-13 20:36:10 +08:00
147 changed files with 2649 additions and 1025 deletions

View File

@@ -1,8 +1,80 @@
## [v2.5.2](https://github.com/continew-org/continew-starter/compare/v2.5.1...v2.5.2) (2024-08-13)
## [v2.7.0](https://github.com/continew-org/continew-starter/compare/v2.6.0...v2.7.0) (2024-09-28)
### ✨ 新特性
- 【data/mp】新增乐观锁插件启用配置默认关闭 ([08ef09c](https://github.com/continew-org/continew-starter/commit/08ef09c9b594dca75b39e36add38998826b234bf))
- 【extension/tenant】新增 continew-starter-extension-tenant 多租户模块 ([1a97a1b](https://github.com/continew-org/continew-starter/commit/1a97a1b709ee0c04300fd39758506fd479da0713)) ([f843791](https://github.com/continew-org/continew-starter/commit/f8437918de342ee45d15df30c20de5e8d3b18379))
- 【extension/datapermission】新增数据权限模块原 data/mp 中数据权限移除) ([7666d56](https://github.com/continew-org/continew-starter/commit/7666d56019bb309dca004d43b0717f6bb0e56c8f))
### 💎 功能优化
- 【data/mp】移除多数据源依赖如需使用可手动引入 ([06d3a6c](https://github.com/continew-org/continew-starter/commit/06d3a6ca412b0bdeba9c0e460db6a0b05215b6b3))
- 完善 ConditionalOnProperty 配置 ([0cede6b](https://github.com/continew-org/continew-starter/commit/0cede6bf9fc89e0c5009e9721b5cea2cf73b890c))
- 优化部分代码写法 ([1fc80cd](https://github.com/continew-org/continew-starter/commit/1fc80cda9eb5b377b30d834692dff58d8f93053b))
- 优化代码格式 ([46773df](https://github.com/continew-org/continew-starter/commit/46773df9dd2dc473459d58fc17f650d3da260545))
- 【data/mp】移除 QueryIgnore 的无用属性 ([0c334da](https://github.com/continew-org/continew-starter/commit/0c334dadcce9d74301dbcc3c336dc28ffc4cf62e))
- 【file/excel】导出方法增加排除字段参数 ([3535ac6](https://github.com/continew-org/continew-starter/commit/3535ac64f79c7c3d8e03d8ed2a996ebdfab1ff92))
- 统一部分命名风格 ([f858395](https://github.com/continew-org/continew-starter/commit/f85839559ad7002dffbe3c5999a75e801ef9c4d1))
- 优化部分依赖传递范围 ([cd69b2a](https://github.com/continew-org/continew-starter/commit/cd69b2adb67cf17b12619f06b8e81492cbb41c26))
### 🐛 问题修复
-extension/crud】重构排序字段处理预防 SQL 注入问题 ([c31fa75](https://github.com/continew-org/continew-starter/commit/c31fa753b6d9753d72a2e28bf3184981ed848ac2))
-log/interceptor】修复 continew-starter.log.exclude-patterns 配置不生效的问题 ([ca1b92c](https://github.com/continew-org/continew-starter/commit/ca1b92cde3cf8f9d9ee0b7420f5b13f200e80781))
- 【log/interceptor】修复全局配置和局部配置包含请求、响应体冲突 ([be4dec5](https://github.com/continew-org/continew-starter/commit/be4dec5a3039625e62d346dbb148206b602af6aa))
### 📦 依赖升级
- Spring Boot 3.2.7 => 3.2.10 ([802dcb5](https://github.com/continew-org/continew-starter/commit/802dcb5735562e911e3a51741cfcf17dbe59a89e))
- MyBatis Plus 3.5.7 => 3.5.8
- Redisson 3.35.0 => 3.36.0
- CosID 2.9.6 => 2.9.8
- SMS4J 3.2.1 => 3.3.3
- X File Storage 2.2.0 => 2.2.1
## [v2.6.0](https://github.com/continew-org/continew-starter/compare/v2.5.2...v2.6.0) (2024-09-06)
### ✨ 新特性
- 【web】新增 isMatch 路径是否匹配方法 ([e55eb17](https://github.com/continew-org/continew-starter/commit/e55eb17d64d7b42c6e64b18a645cda9558f08d58))
- 【log】不记录日志也支持开启打印访问日志 ([16b6e9b](https://github.com/continew-org/continew-starter/commit/16b6e9b466578d935ab9a8a85a5eac4d09025dee))
### 💎 功能优化
- 【data】移除 DataPermission 注解的 value 属性 ([d3fa00d](https://github.com/continew-org/continew-starter/commit/d3fa00d14ce95ab1bedaebbce8304e29df5da8fd))
- 【data】mybatis-plus => mpmybatis-flex => mf ([5e0eea2](https://github.com/continew-org/continew-starter/commit/5e0eea2ed801b526da9f65b8cb2942b3b7b050ef))
- 【web】提升接口文档响应类型优化扩展性 ([784a56f](https://github.com/continew-org/continew-starter/commit/784a56fd4f85ae170b8a56dd0a64a33a7bff98bc))
- 【web】链路追踪配置属性响应头名称 => 链路 ID 名称 ([260f484](https://github.com/continew-org/continew-starter/commit/260f484af98d112927edec4316c0375e628dd3ba))
- 【log】优化接口耗时相关时间类型使用 ([4caf0a0](https://github.com/continew-org/continew-starter/commit/4caf0a0db69779a5ea409ec7e01c4044817afc94))
### 🐛 问题修复
- 【log】修复日志全局 includes 配置会被局部修改的问题 ([eac5c1f](https://github.com/continew-org/continew-starter/commit/eac5c1fa79f7f91217b0525a07e0f1314f8efe24))
- 【data】还原 SQL 函数接口 ([9e5f33b](https://github.com/continew-org/continew-starter/commit/9e5f33b2c9b804741f9b77eab5b67ab3c4a8b220))
- 【crypto】修复由于升级 mybatis plus 引发的更新场景加密失效问题 ([e9b81f9](https://github.com/continew-org/continew-starter/commit/e9b81f946603e2f9e44b50471102b2551b9d50ac)) ([9fdbfdf](https://github.com/continew-org/continew-starter/commit/9fdbfdf8bb6719d3d044c71a2a8ceab85ccef8f1))
### 📦 依赖升级
- Graceful Response 4.0.1-boot3 => 5.0.0-boot3 ([2208dbd](https://github.com/continew-org/continew-starter/commit/2208dbd0282964aab76b02e811d6689ba69d75fd))
- Snail Job 1.1.0 => 1.1.2
- Sa Token 1.38.0 => 1.39.0
- MyBatis Flex 1.9.3 => 1.9.7
- Redisson 3.32.0 => 3.35.0
- Cos ID 2.9.1 => 2.9.6
- Hutool 5.8.29 => 5.8.32
- aws-java-sdk-s3 1.12.761 => 1.12.771
- snakeyaml 2.2 => 2.3
## [v2.5.2](https://github.com/continew-org/continew-starter/compare/v2.5.1...v2.5.2) (2024-08-14)
### 💎 功能优化
- 【api-doc】重构接口文档枚举展示处理 ([bf51837](https://github.com/continew-org/continew-starter/commit/bf51837991746c5576d7baffcc2296fe8ca91586)) ([4c4f98a](https://github.com/continew-org/continew-starter/commit/4c4f98a86e6d10b2b52814406bd0dc3d248c8486))
- 【web】针对最新响应风格增加全局响应格式 ([bf51837](https://github.com/continew-org/continew-starter/commit/bf51837991746c5576d7baffcc2296fe8ca91586)) ([4c4f98a](https://github.com/continew-org/continew-starter/commit/4c4f98a86e6d10b2b52814406bd0dc3d248c8486))
### 🐛 问题修复
- 【extension/crud】重构排序字段处理预防 SQL 注入问题 ([c31fa75](https://github.com/continew-org/continew-starter/commit/c31fa753b6d9753d72a2e28bf3184981ed848ac2)) ([22ebdfe](https://github.com/continew-org/continew-starter/commit/22ebdfeb9f30a8832825424a05343acfaf31643b))
- 【security/crypto】修复 updateById 修改未正确加密的问题 ([b0a2a8c](https://github.com/continew-org/continew-starter/commit/b0a2a8c927171795e3ed89a820caf829f76c80ee))
## [v2.5.1](https://github.com/continew-org/continew-starter/compare/v2.5.0...v2.5.1) (2024-08-12)

147
README.md
View File

@@ -72,6 +72,11 @@ ContiNew Starter 就是将脚手架项目中的通用基础配置进行了封装
第一种方式:如您使用的是 Spring Boot Parent 的方式,则替换 Spring Boot Parent 为 ContiNew Starter
> 最新稳定版latest-version
<a href="https://central.sonatype.com/search?q=continew-starter" target="_blank" rel="noopener" style="display: inline-block;">
<img src="https://img.shields.io/maven-central/v/top.continew/continew-starter.svg?label=Maven%20Central&logo=sonatype&logoColor=FFF" alt="Release" />
</a>
```xml
<parent>
<groupId>top.continew</groupId>
@@ -137,100 +142,54 @@ continew-starter.web:
## 模块结构
### 核心模块
| 模块名称 | 模块说明 | 依赖版本 |
| --------------------- | ------------------------------------ |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| continew-starter-core | 核心模块:包含线程池、项目等自动配置 | <a href="https://spring.io/projects/spring-boot" target="_blank">Spring Boot</a>3.1.11<br /><a href="https://www.hutool.cn/" target="_blank">Hutool</a>5.8.29<br />mica-ip2region3.2.6 |
### JSON模块
| 模块名称 | 模块说明 |
| ----------------------------- | -------------------- |
| continew-starter-json-jackson | Jackson 序列化等配置 |
### 接口文档
| 模块名称 | 模块说明 |
| ------------------------ | ---------------- |
| continew-starter-api-doc | Knife4j 自动配置 |
### 安全模块
| 模块名称 | 模块说明 |
| ---------------------------------- | ----------------- |
| continew-starter-security-password | 密码编码器 |
| continew-starter-security-mask | JSON 脱敏 |
| continew-starter-security-crypto | 数据库字段加/解密 |
| continew-starter-security-limiter | 限流器 |
### Web模块
| 模块名称 | 模块说明 |
| -------------------- | ---------------------------------- |
| continew-starter-web | 跨域、全局异常、错误处理等自动配置 |
### 日志模块
| 模块名称 | 模块说明 |
|----------------------------------| ----------------------------------------- |
| continew-starter-log-core | 日志核心模块 |
| continew-starter-log-interceptor | 拦截器版Spring Boot Actuator HttpTrace 增强版) |
### 存储模块
| 模块名称 | 模块说明 |
| ------------------------------ | -------- |
| continew-starter-storage-local | 本地存储 |
### 文件处理模块
| 模块名称 | 模块说明 |
| --------------------------- | -------------- |
| continew-starter-file-excel | Excel 相关配置 |
### 验证码模块
| 模块名称 | 模块说明 |
| --------------------------------- | ---------- |
| continew-starter-captcha-graphic | 图形验证码 |
| continew-starter-captcha-behavior | 行为验证码 |
### 缓存模块
| 模块名称 | 模块说明 |
| ---------------------------------- | --------------------- |
| continew-starter-cache-redisson | Redisson 自动配置 |
| continew-starter-cache-springcache | Spring Cache 自动配置 |
| continew-starter-cache-jetcache | JetCache 自动配置 |
### 数据访问模块
| 模块名称 | 模块说明 |
| ---------------------------------- | --------------------- |
| continew-starter-data-core | 数据访问核心模块 |
| continew-starter-data-mybatis-plus | MyBatis Plus 自动配置 |
| continew-starter-data-mybatis-flex | MyBatis Flex 自动配置 |
### 认证模块
| 模块名称 | 模块说明 |
| ------------------------------ | ----------------- |
| continew-starter-auth-satoken | SaToken 自动配置 |
| continew-starter-auth-justauth | JustAuth 自动配置 |
### 消息模块
| 模块名称 | 模块说明 |
| ------------------------------------ | --------- |
| continew-starter-messaging-mail | 邮件 |
| continew-starter-messaging-websocket | WebSocket |
### 扩展模块
| 模块名称 | 模块说明 |
| ------------------------------- | --------------------------------------------- |
| continew-starter-extension-crud | 扩展模块BaseController 自定义 CRUD API 封装 |
```
continew-starter
├─ continew-starter-core核心模块包含线程池等自动配置
├─ continew-starter-jsonJSON 模块)
│ └─ continew-starter-json-jackson
├─ continew-starter-api-doc接口文档模块Spring Doc + Knife4j
├─ continew-starter-webWeb 开发模块:包含跨域、全局异常+响应、链路追踪等自动配置)
├─ continew-starter-auth认证模块
│ ├─ continew-starter-auth-satoken国产轻量认证鉴权
│ └─ continew-starter-auth-justauth第三方登录
├─ continew-starter-data数据访问模块
│ ├─ continew-starter-data-core通用模块
│ ├─ continew-starter-data-mpMyBatis Plus
│ └─ continew-starter-data-mfMyBatis Flex
├─ continew-starter-cache缓存模块
│ ├─ continew-starter-cache-redissonRedisson
│ ├─ continew-starter-cache-jetcacheJetCache 多级缓存)
│ └─ continew-starter-cache-springcacheSpring 缓存)
├─ continew-starter-security安全模块
│ ├─ continew-starter-security-crypto加密字段加解密
│ ├─ continew-starter-security-mask脱敏JSON 数据脱敏)
│ ├─ continew-starter-security-limiter限流
│ └─ continew-starter-security-password密码编码器
├─ continew-starter-captcha验证码模块
│ ├─ continew-starter-captcha-graphic静态验证码
│ └─ continew-starter-captcha-behavior动态验证码
├─ continew-starter-messaging消息模块
│ ├─ continew-starter-messaging-mail邮件
│ └─ continew-starter-messaging-websocketWebSocket
├─ continew-starter-log日志模块
│ ├─ continew-starter-log-core通用模块
│ └─ continew-starter-log-interceptor拦截器版Spring Boot Actuator HttpTrace 增强版))
├─ continew-starter-file文件处理模块
│ └─ continew-starter-file-excelEasy Excel
├─ continew-starter-storage存储模块
│ └─ continew-starter-storage-local本地存储
└─ continew-starter-extension扩展模块
├─ continew-starter-extension-datapermission数据权限模块
│ ├─ continew-starter-extension-datapermission-core通用模块
│ └─ continew-starter-extension-datapermission-mpMyBatis Plus
├─ continew-starter-extension-tenant多租户模块
│ ├─ continew-starter-extension-tenant-core通用模块
└─ continew-starter-extension-tenant-mpMyBatis Plus
└─ continew-starter-extension-crudCRUD 模块)
├─ continew-starter-extension-crud-core通用模块
├─ continew-starter-extension-crud-mpMyBatis Plus
└─ continew-starter-extension-crud-mfMyBatis Flex
```
## 贡献代码

View File

@@ -17,23 +17,20 @@
package top.continew.starter.apidoc.autoconfigure;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.SimpleType;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springdoc.core.configuration.SpringDocConfiguration;
import org.springdoc.core.customizers.*;
import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
import org.springdoc.core.properties.SpringDocConfigProperties;
import org.springdoc.core.providers.JavadocProvider;
import org.springdoc.core.service.OpenAPIService;
@@ -48,15 +45,15 @@ import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import top.continew.starter.apidoc.handler.BaseEnumParameterHandler;
import top.continew.starter.apidoc.handler.OpenApiHandler;
import top.continew.starter.apidoc.util.EnumTypeUtils;
import top.continew.starter.core.autoconfigure.project.ProjectProperties;
import top.continew.starter.core.enums.BaseEnum;
import top.continew.starter.core.util.GeneralPropertySourceFactory;
import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* API 文档自动配置
@@ -156,95 +153,14 @@ public class SpringDocAutoConfiguration implements WebMvcConfigurer {
}
/**
* 自定义参数配置(针对 BaseEnum 展示枚举值和描述)
* 自定义 BaseEnum 枚举参数配置(针对实现了 BaseEnum 的枚举,优化其枚举值和描述展示
*
* @return {@link BaseEnumParameterHandler }
* @since 2.4.0
*/
@Bean
public ParameterCustomizer customParameterCustomizer() {
return (parameterModel, methodParameter) -> {
Class<?> parameterType = methodParameter.getParameterType();
// 判断是否为 BaseEnum 的子类型
if (!ClassUtil.isAssignable(BaseEnum.class, parameterType)) {
return parameterModel;
}
String description = parameterModel.getDescription();
if (StrUtil.contains(description, "color:red")) {
return parameterModel;
}
// 封装参数配置
this.configureSchema(parameterModel.getSchema(), parameterType);
// 自定义枚举描述
parameterModel.setDescription(description + "<span style='color:red'>" + this
.getDescMap(parameterType) + "</span>");
return parameterModel;
};
}
/**
* 自定义参数配置(针对 BaseEnum 展示枚举值和描述)
*
* @since 2.4.0
*/
@Bean
public PropertyCustomizer customPropertyCustomizer() {
return (schema, type) -> {
Class<?> rawClass;
// 获取原始类的类型
if (type.getType() instanceof SimpleType) {
rawClass = ((SimpleType)type.getType()).getRawClass();
} else if (type.getType() instanceof CollectionType) {
rawClass = ((CollectionType)type.getType()).getContentType().getRawClass();
} else {
rawClass = Object.class;
}
// 判断是否为 BaseEnum 的子类型
if (!ClassUtil.isAssignable(BaseEnum.class, rawClass)) {
return schema;
}
// 封装参数配置
this.configureSchema(schema, rawClass);
// 自定义参数描述
schema.setDescription(schema.getDescription() + "<span style='color:red'>" + this
.getDescMap(rawClass) + "</span>");
return schema;
};
}
/**
* 封装 Schema 配置
*
* @param schema Schema
* @param enumClass 枚举类型
* @since 2.4.0
*/
private void configureSchema(Schema schema, Class<?> enumClass) {
BaseEnum[] enums = (BaseEnum[])enumClass.getEnumConstants();
// 设置枚举可用值
List<String> valueList = Arrays.stream(enums).map(e -> e.getValue().toString()).toList();
schema.setEnum(valueList);
// 设置枚举值类型和格式
String enumValueType = EnumTypeUtils.getEnumValueTypeAsString(enumClass);
schema.setType(enumValueType);
switch (enumValueType) {
case "integer" -> schema.setFormat("int32");
case "long" -> schema.setFormat("int64");
case "number" -> schema.setFormat("double");
default -> schema.setFormat(enumValueType);
}
}
/**
* 获取枚举描述 Map
*
* @param enumClass 枚举类型
* @return 枚举描述 Map
* @since 2.4.0
*/
private Map<Object, String> getDescMap(Class<?> enumClass) {
BaseEnum[] enums = (BaseEnum[])enumClass.getEnumConstants();
return Arrays.stream(enums)
.collect(Collectors.toMap(BaseEnum::getValue, BaseEnum::getDescription, (a, b) -> a, LinkedHashMap::new));
public BaseEnumParameterHandler customParameterCustomizer() {
return new BaseEnumParameterHandler();
}
@PostConstruct

View File

@@ -0,0 +1,118 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.apidoc.handler;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.SimpleType;
import io.swagger.v3.core.converter.AnnotatedType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
import org.springdoc.core.customizers.ParameterCustomizer;
import org.springdoc.core.customizers.PropertyCustomizer;
import org.springframework.core.MethodParameter;
import top.continew.starter.apidoc.util.DocUtils;
import top.continew.starter.core.enums.BaseEnum;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
/**
* 自定义 BaseEnum 枚举参数处理器
* <p>
* 针对实现了 BaseEnum 的枚举,优化其枚举值和描述展示
* </p>
*
* @author echo
* @since 2.5.2
*/
public class BaseEnumParameterHandler implements ParameterCustomizer, PropertyCustomizer {
@Override
public Parameter customize(Parameter parameterModel, MethodParameter methodParameter) {
Class<?> parameterType = methodParameter.getParameterType();
// 判断是否为 BaseEnum 的子类型
if (!ClassUtil.isAssignable(BaseEnum.class, parameterType)) {
return parameterModel;
}
String description = parameterModel.getDescription();
if (StrUtil.contains(description, "color:red")) {
return parameterModel;
}
// 自定义枚举描述并封装参数配置
configureSchema(parameterModel.getSchema(), parameterType);
parameterModel.setDescription(appendEnumDescription(description, parameterType));
return parameterModel;
}
@Override
public Schema customize(Schema schema, AnnotatedType type) {
Class<?> rawClass = resolveRawClass(type.getType());
// 判断是否为 BaseEnum 的子类型
if (!ClassUtil.isAssignable(BaseEnum.class, rawClass)) {
return schema;
}
// 自定义参数描述并封装参数配置
configureSchema(schema, rawClass);
schema.setDescription(appendEnumDescription(schema.getDescription(), rawClass));
return schema;
}
/**
* 封装 Schema 配置
*
* @param schema Schema
* @param enumClass 枚举类型
*/
private void configureSchema(Schema schema, Class<?> enumClass) {
BaseEnum[] enums = (BaseEnum[])enumClass.getEnumConstants();
List<String> valueList = Arrays.stream(enums).map(e -> e.getValue().toString()).toList();
schema.setEnum(valueList);
String enumValueType = DocUtils.getEnumValueTypeAsString(enumClass);
schema.setType(enumValueType);
schema.setFormat(DocUtils.resolveFormat(enumValueType));
}
/**
* 追加枚举描述
*
* @param originalDescription 原始描述
* @param enumClass 枚举类型
* @return 追加后的描述字符串
*/
private String appendEnumDescription(String originalDescription, Class<?> enumClass) {
return originalDescription + "<span style='color:red'>" + DocUtils.getDescMap(enumClass) + "</span>";
}
/**
* 解析原始类
*
* @param type 类型
* @return 原始类的 Class 对象
*/
private Class<?> resolveRawClass(Type type) {
if (type instanceof SimpleType simpleType) {
return simpleType.getRawClass();
} else if (type instanceof CollectionType collectionType) {
return collectionType.getContentType().getRawClass();
} else {
return Object.class;
}
}
}

View File

@@ -146,12 +146,15 @@ public class OpenApiHandler extends OpenAPIService {
super(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider);
if (openAPI.isPresent()) {
this.openAPI = openAPI.get();
if (this.openAPI.getComponents() == null)
if (this.openAPI.getComponents() == null) {
this.openAPI.setComponents(new Components());
if (this.openAPI.getPaths() == null)
}
if (this.openAPI.getPaths() == null) {
this.openAPI.setPaths(new Paths());
if (!CollectionUtils.isEmpty(this.openAPI.getServers()))
}
if (!CollectionUtils.isEmpty(this.openAPI.getServers())) {
this.isServersPresent = true;
}
}
this.propertyResolverUtils = propertyResolverUtils;
this.securityParser = securityParser;
@@ -159,8 +162,9 @@ public class OpenApiHandler extends OpenAPIService {
this.openApiBuilderCustomisers = openApiBuilderCustomizers;
this.serverBaseUrlCustomizers = serverBaseUrlCustomizers;
this.javadocProvider = javadocProvider;
if (springDocConfigProperties.isUseFqn())
if (springDocConfigProperties.isUseFqn()) {
TypeNameResolver.std.setUseFqn(true);
}
}
@Override
@@ -172,10 +176,11 @@ public class OpenApiHandler extends OpenAPIService {
buildTagsFromMethod(handlerMethod.getMethod(), tags, tagsStr, locale);
buildTagsFromClass(handlerMethod.getBeanType(), tags, tagsStr, locale);
if (!CollectionUtils.isEmpty(tagsStr))
if (!CollectionUtils.isEmpty(tagsStr)) {
tagsStr = tagsStr.stream()
.map(str -> propertyResolverUtils.resolve(str, locale))
.collect(Collectors.toSet());
}
if (springdocTags.containsKey(handlerMethod)) {
Tag tag = springdocTags.get(handlerMethod);
@@ -186,9 +191,9 @@ public class OpenApiHandler extends OpenAPIService {
}
if (!CollectionUtils.isEmpty(tagsStr)) {
if (CollectionUtils.isEmpty(operation.getTags()))
if (CollectionUtils.isEmpty(operation.getTags())) {
operation.setTags(new ArrayList<>(tagsStr));
else {
} else {
Set<String> operationTagsSet = new HashSet<>(operation.getTags());
operationTagsSet.addAll(tagsStr);
operation.getTags().clear();
@@ -223,8 +228,9 @@ public class OpenApiHandler extends OpenAPIService {
if (!CollectionUtils.isEmpty(tags)) {
// Existing tags
List<Tag> openApiTags = openAPI.getTags();
if (!CollectionUtils.isEmpty(openApiTags))
if (!CollectionUtils.isEmpty(openApiTags)) {
tags.addAll(openApiTags);
}
openAPI.setTags(new ArrayList<>(tags));
}
@@ -232,10 +238,11 @@ public class OpenApiHandler extends OpenAPIService {
io.swagger.v3.oas.annotations.security.SecurityRequirement[] securityRequirements = securityParser
.getSecurityRequirements(handlerMethod);
if (securityRequirements != null) {
if (securityRequirements.length == 0)
if (securityRequirements.length == 0) {
operation.setSecurity(Collections.emptyList());
else
} else {
securityParser.buildSecurityRequirement(securityRequirements, operation);
}
}
return operation;
@@ -263,8 +270,9 @@ public class OpenApiHandler extends OpenAPIService {
tagsSet.forEach(tag -> {
tag.name(propertyResolverUtils.resolve(tag.getName(), locale));
tag.description(propertyResolverUtils.resolve(tag.getDescription(), locale));
if (tags.stream().noneMatch(t -> t.getName().equals(tag.getName())))
if (tags.stream().noneMatch(t -> t.getName().equals(tag.getName()))) {
tags.add(tag);
}
});
});
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.apidoc.util;
import org.springframework.web.bind.annotation.RestController;
import top.continew.starter.core.enums.BaseEnum;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 接口文档工具类
*
* @author echo
* @since 2.5.2
*/
public class DocUtils {
private DocUtils() {
}
/**
* 获取枚举值类型
*
* @param enumClass 枚举类型
* @return 枚举值类型
*/
public static String getEnumValueTypeAsString(Class<?> enumClass) {
// 获取枚举类实现的所有接口
Type[] interfaces = enumClass.getGenericInterfaces();
// 定义枚举值类型的映射
Map<Class<?>, String> typeMap = Map
.of(Integer.class, "integer", Long.class, "long", Double.class, "number", String.class, "string");
// 遍历所有接口
for (Type type : interfaces) {
// 检查接口是否为参数化类型并且原始类型为 BaseEnum
if (type instanceof ParameterizedType parameterizedType && parameterizedType
.getRawType() == BaseEnum.class) {
Type actualType = parameterizedType.getActualTypeArguments()[0];
// 检查实际类型参数是否为类类型,并返回对应的字符串类型
if (actualType instanceof Class<?> actualClass) {
return typeMap.getOrDefault(actualClass, "string");
}
}
}
// 默认返回 "string" 类型
return "string";
}
/**
* 解析枚举值的格式
*
* @param enumValueType 枚举值类型
* @return String 格式化类型
*/
public static String resolveFormat(String enumValueType) {
return switch (enumValueType) {
case "integer" -> "int32";
case "long" -> "int64";
case "number" -> "double";
default -> enumValueType;
};
}
/**
* 具有 RestController 注释既检查是否继承了BaseController
*
* @param clazz clazz
* @return boolean
*/
public static boolean hasRestControllerAnnotation(Class<?> clazz) {
// 如果注释包含 RestController 注解,则返回 true
if (clazz.isAnnotationPresent(RestController.class)) {
return true;
}
// 递归检查父类
Class<?> superClass = clazz.getSuperclass();
// 循环检查父类
while (superClass != null && !superClass.equals(Object.class)) {
// 如果父类包含 RestController 注解,则返回 true
if (hasRestControllerAnnotation(superClass)) {
return true;
}
// 递归检查接口
superClass = superClass.getSuperclass();
}
return false;
}
/**
* 获取枚举描述 Map
*
* @param enumClass 枚举类型
* @return 枚举描述 Map
*/
public static Map<Object, String> getDescMap(Class<?> enumClass) {
BaseEnum[] enums = (BaseEnum[])enumClass.getEnumConstants();
return Arrays.stream(enums)
.collect(Collectors.toMap(BaseEnum::getValue, BaseEnum::getDescription, (a, b) -> a, LinkedHashMap::new));
}
}

View File

@@ -1,73 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.apidoc.util;
import top.continew.starter.core.enums.BaseEnum;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
* 枚举类型工具
*
* @author echo
* @since 2.4.0
*/
public class EnumTypeUtils {
private EnumTypeUtils() {
}
/**
* 获取枚举值类型
*
* @param enumClass 枚举类型
* @return 枚举值类型
*/
public static String getEnumValueTypeAsString(Class<?> enumClass) {
try {
// 获取枚举类实现的所有接口
Type[] interfaces = enumClass.getGenericInterfaces();
// 遍历所有接口
for (Type type : interfaces) {
// 检查接口是否为参数化类型
if (type instanceof ParameterizedType parameterizedType) {
// 检查接口的原始类型是否为 BaseEnum
if (parameterizedType.getRawType() != BaseEnum.class) {
continue;
}
Type actualType = parameterizedType.getActualTypeArguments()[0];
// 检查实际类型参数是否为类类型
if (actualType instanceof Class<?> actualClass) {
if (actualClass == Integer.class) {
return "integer";
} else if (actualClass == Long.class) {
return "long";
} else if (actualClass == Double.class) {
return "number";
} else if (actualClass == String.class) {
return "string";
}
}
}
}
} catch (Exception ignored) {
// ignored
}
return "string";
}
}

View File

@@ -25,7 +25,7 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import top.continew.starter.auth.justauth.core.JustAuthStateCacheRedisImpl;
import top.continew.starter.auth.justauth.core.AuthStateCacheRedisDefaultImpl;
import top.continew.starter.core.constant.PropertiesConstants;
/**
@@ -35,19 +35,19 @@ import top.continew.starter.core.constant.PropertiesConstants;
* @since 1.0.0
*/
@AutoConfiguration(before = com.xkcoding.justauth.autoconfigure.JustAuthAutoConfiguration.class)
@ConditionalOnProperty(prefix = "justauth", name = PropertiesConstants.ENABLED, matchIfMissing = true)
@ConditionalOnProperty(prefix = "justauth", name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
public class JustAuthAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(JustAuthAutoConfiguration.class);
/**
* 自定义 State 缓存实现
* State 缓存 Redis 实现(默认)
*/
@Bean
@ConditionalOnClass(RedisClient.class)
@ConditionalOnProperty(prefix = "justauth.cache", name = "type", havingValue = "redis")
public AuthStateCache authStateCache() {
JustAuthStateCacheRedisImpl impl = new JustAuthStateCacheRedisImpl();
AuthStateCacheRedisDefaultImpl impl = new AuthStateCacheRedisDefaultImpl();
log.debug("[ContiNew Starter] - Auto Configuration 'JustAuth-AuthStateCache-Redis' completed initialization.");
return impl;
}

View File

@@ -22,12 +22,12 @@ import top.continew.starter.cache.redisson.util.RedisUtils;
import java.time.Duration;
/**
* JustAuth State 缓存 Redis 实现
* 默认 State 缓存 Redis 实现
*
* @author Charles7c
* @since 1.0.0
*/
public class JustAuthStateCacheRedisImpl implements AuthStateCache {
public class AuthStateCacheRedisDefaultImpl implements AuthStateCache {
private static final String KEY_PREFIX = "SOCIAL_AUTH_STATE";

View File

@@ -29,7 +29,10 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import top.continew.starter.auth.satoken.autoconfigure.dao.SaTokenDaoConfiguration;
@@ -78,8 +81,7 @@ public class SaTokenAutoConfiguration implements WebMvcConfigurer {
@Configuration
@Import({SaTokenDaoConfiguration.Default.class, SaTokenDaoConfiguration.Redis.class,
SaTokenDaoConfiguration.Custom.class})
protected static class SaTokenDaoAutoConfiguration {
}
protected static class SaTokenDaoAutoConfiguration {}
/**
* 整合 JWT简单模式

View File

@@ -60,7 +60,7 @@ public class SaTokenDaoConfiguration {
}
/**
* 自定义持久层实现-Redis
* 自定义持久层实现-Redis(默认)
*/
@ConditionalOnMissingBean(SaTokenDao.class)
@ConditionalOnClass(RedisClient.class)
@@ -73,7 +73,7 @@ public class SaTokenDaoConfiguration {
@Bean
public SaTokenDao saTokenDao() {
return new SaTokenDaoRedisImpl();
return new SaTokenDaoRedisDefaultImpl();
}
}

View File

@@ -26,12 +26,12 @@ import java.util.Collection;
import java.util.List;
/**
* Sa-Token 持久层 Redis 实现参考Sa-Token/sa-token-plugin/sa-token-dao-redisx/SaTokenDaoOfRedis.java
* 默认 Sa-Token 持久层 Redis 实现参考Sa-Token/sa-token-plugin/sa-token-dao-redisx/SaTokenDaoOfRedis.java
*
* @author Charles7c
* @since 1.0.0
*/
public class SaTokenDaoRedisImpl implements SaTokenDao {
public class SaTokenDaoRedisDefaultImpl implements SaTokenDao {
@Override
public String get(String key) {

View File

@@ -46,7 +46,7 @@ import java.util.List;
* @since 1.0.0
*/
@AutoConfiguration
@ConditionalOnProperty(prefix = "spring.data.redisson", name = PropertiesConstants.ENABLED, matchIfMissing = true)
@ConditionalOnProperty(prefix = "spring.data.redisson", name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(RedissonProperties.class)
public class RedissonAutoConfiguration {

View File

@@ -49,7 +49,7 @@ import java.util.Properties;
*/
@AutoConfiguration
@EnableConfigurationProperties(BehaviorCaptchaProperties.class)
@ConditionalOnProperty(prefix = PropertiesConstants.CAPTCHA_BEHAVIOR, name = PropertiesConstants.ENABLED, matchIfMissing = true)
@ConditionalOnProperty(prefix = PropertiesConstants.CAPTCHA_BEHAVIOR, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
public class BehaviorCaptchaAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(BehaviorCaptchaAutoConfiguration.class);

View File

@@ -35,7 +35,7 @@ import top.continew.starter.core.constant.PropertiesConstants;
*/
@AutoConfiguration
@EnableConfigurationProperties(GraphicCaptchaProperties.class)
@ConditionalOnProperty(prefix = PropertiesConstants.CAPTCHA_GRAPHIC, name = PropertiesConstants.ENABLED, matchIfMissing = true)
@ConditionalOnProperty(prefix = PropertiesConstants.CAPTCHA_GRAPHIC, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
public class GraphicCaptchaAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(GraphicCaptchaAutoConfiguration.class);

View File

@@ -16,8 +16,8 @@
package top.continew.starter.captcha.graphic.core;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil;
import com.wf.captcha.base.Captcha;
import top.continew.starter.captcha.graphic.autoconfigure.GraphicCaptchaProperties;

View File

@@ -31,5 +31,4 @@ import org.springframework.context.annotation.Import;
@ComponentScan("cn.hutool.extra.spring")
@Import(cn.hutool.extra.spring.SpringUtil.class)
@EnableConfigurationProperties(ProjectProperties.class)
public class ProjectAutoConfiguration {
}
public class ProjectAutoConfiguration {}

View File

@@ -42,7 +42,7 @@ import java.util.concurrent.Executor;
@Lazy
@AutoConfiguration
@EnableAsync(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.task.execution.extension", name = PropertiesConstants.ENABLED, matchIfMissing = true)
@ConditionalOnProperty(prefix = "spring.task.execution.extension", name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
public class AsyncAutoConfiguration implements AsyncConfigurer {
private static final Logger log = LoggerFactory.getLogger(AsyncAutoConfiguration.class);

View File

@@ -52,7 +52,7 @@ public class ThreadPoolAutoConfiguration {
* 异步任务线程池配置
*/
@Bean
@ConditionalOnProperty(prefix = "spring.task.execution.extension", name = PropertiesConstants.ENABLED, matchIfMissing = true)
@ConditionalOnProperty(prefix = "spring.task.execution.extension", name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
public ThreadPoolTaskExecutorCustomizer threadPoolTaskExecutorCustomizer(ThreadPoolExtensionProperties properties) {
return executor -> {
// 核心(最小)线程数
@@ -71,7 +71,7 @@ public class ThreadPoolAutoConfiguration {
* 定时任务线程池配置
*/
@EnableScheduling
@ConditionalOnProperty(prefix = "spring.task.scheduling.extension", name = PropertiesConstants.ENABLED, matchIfMissing = true)
@ConditionalOnProperty(prefix = "spring.task.scheduling.extension", name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
public static class TaskSchedulerConfiguration {
@Bean
public ThreadPoolTaskSchedulerCustomizer threadPoolTaskSchedulerCustomizer(ThreadPoolExtensionProperties properties) {

View File

@@ -119,6 +119,16 @@ public class PropertiesConstants {
*/
public static final String MESSAGING_WEBSOCKET = MESSAGING + StringConstants.DOT + "websocket";
/**
* 数据权限配置
*/
public static final String DATA_PERMISSION = CONTINEW_STARTER + StringConstants.DOT + "data-permission";
/**
* 多租户配置
*/
public static final String TENANT = CONTINEW_STARTER + StringConstants.DOT + "tenant";
private PropertiesConstants() {
}
}

View File

@@ -120,8 +120,7 @@ public class StringConstants {
public static final String DOT = StrPool.DOT;
/**
* 字符串常量:双点 {@code ".."} <br>
* 用途:作为指向上级文件夹的路径,如:{@code "../path"}
* 字符串常量:双点 {@code ".."} <br> 用途:作为指向上级文件夹的路径,如:{@code "../path"}
*/
public static final String DOUBLE_DOT = StrPool.DOUBLE_DOT;
@@ -136,8 +135,7 @@ public class StringConstants {
public static final String BACKSLASH = StrPool.BACKSLASH;
/**
* 字符串常量:回车符 {@code "\r"} <br>
* 解释:该字符常用于表示 Linux 系统和 MacOS 系统下的文本换行
* 字符串常量:回车符 {@code "\r"} <br> 解释:该字符常用于表示 Linux 系统和 MacOS 系统下的文本换行
*/
public static final String CR = StrPool.CR;
@@ -147,8 +145,7 @@ public class StringConstants {
public static final String LF = StrPool.LF;
/**
* 字符串常量Windows 换行 {@code "\r\n"} <br>
* 解释:该字符串常用于表示 Windows 系统下的文本换行
* 字符串常量Windows 换行 {@code "\r\n"} <br> 解释:该字符串常用于表示 Windows 系统下的文本换行
*/
public static final String CRLF = StrPool.CRLF;
@@ -272,6 +269,11 @@ public class StringConstants {
*/
public static final String ROUND_BRACKET_END = ")";
/**
* 等号(=
*/
public static final String EQUALS = "=";
/**
* 路径模式
*/

View File

@@ -27,12 +27,4 @@ import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface QueryIgnore {
/**
* 获取是否忽略查询解析
*
* @return 是否忽略查询解析
*/
boolean value() default true;
}
public @interface QueryIgnore {}

View File

@@ -18,6 +18,8 @@ package top.continew.starter.data.core.enums;
import top.continew.starter.data.core.function.ISqlFunction;
import java.io.Serializable;
/**
* 数据库类型枚举
*
@@ -31,8 +33,8 @@ public enum DatabaseType implements ISqlFunction {
*/
MYSQL("MySQL") {
@Override
public String findInSet() {
return "find_in_set({0}, {1}) <> 0";
public String findInSet(Serializable value, String set) {
return "find_in_set('%s', %s) <> 0".formatted(value, set);
}
},
@@ -41,8 +43,8 @@ public enum DatabaseType implements ISqlFunction {
*/
POSTGRE_SQL("PostgreSQL") {
@Override
public String findInSet() {
return "(select position(',{0},' in ','||{1}||',')) <> 0";
public String findInSet(Serializable value, String set) {
return "(select position(',%s,' in ','||%s||',')) <> 0".formatted(value, set);
}
},;

View File

@@ -16,6 +16,8 @@
package top.continew.starter.data.core.function;
import java.io.Serializable;
/**
* SQL 函数接口
*
@@ -27,7 +29,9 @@ public interface ISqlFunction {
/**
* find_in_set 函数
*
* @param value 值
* @param set 集合
* @return 函数实现
*/
String findInSet();
String findInSet(Serializable value, String set);
}

View File

@@ -16,17 +16,24 @@
package top.continew.starter.data.core.util;
import java.util.Objects;
import cn.hutool.core.text.CharSequenceUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.regex.Pattern;
/**
* SQL 注入验证工具类
*
* @author hubin
* @author hubin<a href="https://github.com/baomidou/mybatis-plus">MyBatis Plus</a>
* @author zhoujf<a href="https://github.com/jeecgboot/JeecgBoot">JeecgBoot</a>
* @author Charles7c
* @since 2.5.2
*/
public class SqlInjectionUtils {
private static final Logger log = LoggerFactory.getLogger(SqlInjectionUtils.class);
/**
* SQL语法检查正则符合两个关键字有先后顺序才算匹配
*/
@@ -39,6 +46,23 @@ public class SqlInjectionUtils {
private static final Pattern SQL_COMMENT_PATTERN = Pattern
.compile("'.*(or|union|--|#|/\\*|;)", Pattern.CASE_INSENSITIVE);
/**
* SQL 语法关键字
*/
private static final String SQL_SYNTAX_KEYWORD = "and |exec |peformance_schema|information_schema|extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+|--";
/**
* SQL 函数检查正则
*/
private static final String[] SQL_FUNCTION_PATTERN = new String[] {"chr\\s*\\(", "mid\\s*\\(", " char\\s*\\(",
"sleep\\s*\\(", "user\\s*\\(", "show\\s+tables", "user[\\s]*\\([\\s]*\\)", "show\\s+databases",
"sleep\\(\\d*\\)", "sleep\\(.*\\)",};
private static final String MESSAGE_TEMPLATE = "SQL 注入检查: 检查值=>{}<=存在 SQL 注入关键字, 关键字=>{}<=";
private SqlInjectionUtils() {
}
/**
* 检查参数是否存在 SQL 注入
*
@@ -46,8 +70,59 @@ public class SqlInjectionUtils {
* @return true非法false合法
*/
public static boolean check(String value) {
Objects.requireNonNull(value);
// 处理是否包含 SQL 注释字符 || 检查是否包含 SQ L注入敏感字符
return SQL_COMMENT_PATTERN.matcher(value).find() || SQL_SYNTAX_PATTERN.matcher(value).find();
return check(value, null);
}
/**
* 检查参数是否存在 SQL 注入
*
* @param value 检查参数
* @param customKeyword 自定义关键字
* @return true非法false合法
*/
public static boolean check(String value, String customKeyword) {
if (CharSequenceUtil.isBlank(value)) {
return false;
}
// 处理是否包含 SQL 注释字符 || 检查是否包含 SQL 注入敏感字符
if (SQL_COMMENT_PATTERN.matcher(value).find() || SQL_SYNTAX_PATTERN.matcher(value).find()) {
log.warn("SQL 注入检查: 检查值=>{}<=存在 SQL 注释字符或 SQL 注入敏感字符", value);
return true;
}
// 转换成小写再进行比较
value = value.toLowerCase().trim();
// 检查是否包含 SQL 语法关键字
if (checkKeyword(value, SQL_SYNTAX_KEYWORD.split("\\|"))) {
return true;
}
// 检查是否包含自定义关键字
if (CharSequenceUtil.isNotBlank(customKeyword) && checkKeyword(value, customKeyword.split("\\|"))) {
return true;
}
// 检查是否包含 SQL 注入敏感字符
for (String pattern : SQL_FUNCTION_PATTERN) {
if (Pattern.matches(".*" + pattern + ".*", value)) {
log.warn(MESSAGE_TEMPLATE, value, pattern);
return true;
}
}
return false;
}
/**
* 检查参数是否存在关键字
*
* @param value 检查参数
* @param keywords 关键字列表
* @return true非法false合法
*/
private static boolean checkKeyword(String value, String[] keywords) {
for (String keyword : keywords) {
if (value.contains(keyword)) {
log.warn(MESSAGE_TEMPLATE, value, keyword);
return true;
}
}
return false;
}
}

View File

@@ -9,7 +9,7 @@
<version>${revision}</version>
</parent>
<artifactId>continew-starter-data-mybatis-flex</artifactId>
<artifactId>continew-starter-data-mf</artifactId>
<description>ContiNew Starter 数据访问模块 - MyBatis Flex</description>
<dependencies>

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.flex.autoconfigure;
package top.continew.starter.data.mf.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import top.continew.starter.core.constant.PropertiesConstants;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.flex.autoconfigure;
package top.continew.starter.data.mf.autoconfigure;
import com.mybatisflex.core.dialect.DbType;
import org.springframework.boot.context.properties.ConfigurationProperties;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.flex.autoconfigure;
package top.continew.starter.data.mf.autoconfigure;
import com.mybatisflex.core.dialect.DbType;
import com.mybatisflex.core.dialect.DialectFactory;
@@ -30,8 +30,8 @@ import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.core.util.GeneralPropertySourceFactory;
import top.continew.starter.data.mybatis.flex.datapermission.DataPermissionDialect;
import top.continew.starter.data.mybatis.flex.datapermission.DataPermissionFilter;
import top.continew.starter.data.mf.datapermission.DataPermissionDialect;
import top.continew.starter.data.mf.datapermission.DataPermissionFilter;
/**
* MyBatis Flex 自动配置

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.flex.base;
package top.continew.starter.data.mf.base;
import cn.hutool.core.util.ClassUtil;
import com.mybatisflex.core.query.QueryWrapper;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.flex.base;
package top.continew.starter.data.mf.base;
import java.io.Serializable;

View File

@@ -14,9 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.flex.datapermission;
import org.springframework.core.annotation.AliasFor;
package top.continew.starter.data.mf.datapermission;
import java.lang.annotation.*;
@@ -31,16 +29,9 @@ import java.lang.annotation.*;
@Documented
public @interface DataPermission {
/**
* Alias for the {@link #tableAlias()} attribute.
*/
@AliasFor("tableAlias")
String value() default "";
/**
* 表别名
*/
@AliasFor("value")
String tableAlias() default "";
/**

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.flex.datapermission;
package top.continew.starter.data.mf.datapermission;
import org.aspectj.lang.annotation.*;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.flex.datapermission;
package top.continew.starter.data.mf.datapermission;
import java.util.Set;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.flex.datapermission;
package top.continew.starter.data.mf.datapermission;
import cn.hutool.core.util.StrUtil;
import com.mybatisflex.core.dialect.impl.CommonsDialectImpl;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.flex.datapermission;
package top.continew.starter.data.mf.datapermission;
/**
* 数据权限过滤器接口

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.flex.datapermission;
package top.continew.starter.data.mf.datapermission;
/**
* 数据权限枚举

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.flex.service;
package top.continew.starter.data.mf.service;
/**
* 通用业务接口
@@ -23,5 +23,4 @@ package top.continew.starter.data.mybatis.flex.service;
* @author hellokaton
* @since 1.2.0
*/
public interface IService<T> extends com.mybatisflex.core.service.IService<T> {
}
public interface IService<T> extends com.mybatisflex.core.service.IService<T> {}

View File

@@ -14,11 +14,11 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.flex.service.impl;
package top.continew.starter.data.mf.service.impl;
import top.continew.starter.core.util.ClassUtils;
import top.continew.starter.data.mybatis.flex.base.BaseMapper;
import top.continew.starter.data.mybatis.flex.service.IService;
import top.continew.starter.data.mf.base.BaseMapper;
import top.continew.starter.data.mf.service.IService;
/**
* 通用业务实现类

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.flex.util;
package top.continew.starter.data.mf.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
@@ -143,7 +143,7 @@ public class QueryWrapperHelper {
}
// 设置了 @QueryIgnore 注解直接忽略
QueryIgnore queryIgnoreAnnotation = field.getAnnotation(QueryIgnore.class);
if (null != queryIgnoreAnnotation && queryIgnoreAnnotation.value()) {
if (null != queryIgnoreAnnotation) {
return Collections.emptyList();
}
// 建议数据库表列建议采用下划线连接法命名程序变量建议采用驼峰法命名

View File

@@ -0,0 +1 @@
top.continew.starter.data.mf.autoconfigure.MybatisFlexAutoConfiguration

View File

@@ -9,7 +9,7 @@
<version>${revision}</version>
</parent>
<artifactId>continew-starter-data-mybatis-plus</artifactId>
<artifactId>continew-starter-data-mp</artifactId>
<description>ContiNew Starter 数据访问模块 - MyBatis Plus</description>
<dependencies>
@@ -19,12 +19,6 @@
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<!-- Dynamic Datasource基于 Spring Boot 的快速集成多数据源的启动器) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
</dependency>
<!-- P6SpySQL 性能分析组件) -->
<dependency>
<groupId>p6spy</groupId>

View File

@@ -14,12 +14,12 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.plus.autoconfigure;
package top.continew.starter.data.mp.autoconfigure;
import com.baomidou.mybatisplus.annotation.DbType;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import top.continew.starter.data.mybatis.plus.autoconfigure.idgenerator.MyBatisPlusIdGeneratorProperties;
import top.continew.starter.data.mp.autoconfigure.idgenerator.MyBatisPlusIdGeneratorProperties;
/**
* MyBatis Plus 扩展配置属性
@@ -49,40 +49,21 @@ public class MyBatisPlusExtensionProperties {
@NestedConfigurationProperty
private MyBatisPlusIdGeneratorProperties idGenerator;
/**
* 数据权限插件配置
*/
private DataPermissionProperties dataPermission;
/**
* 分页插件配置
*/
private PaginationProperties pagination;
/**
* 启用乐观锁插件
*/
private boolean optimisticLockerEnabled = false;
/**
* 启用防全表更新与删除插件
*/
private boolean blockAttackPluginEnabled = true;
/**
* 数据权限插件配置属性
*/
public static class DataPermissionProperties {
/**
* 是否启用数据权限插件
*/
private boolean enabled = false;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
/**
* 分页插件配置属性
*/
@@ -165,14 +146,6 @@ public class MyBatisPlusExtensionProperties {
this.idGenerator = idGenerator;
}
public DataPermissionProperties getDataPermission() {
return dataPermission;
}
public void setDataPermission(DataPermissionProperties dataPermission) {
this.dataPermission = dataPermission;
}
public PaginationProperties getPagination() {
return pagination;
}
@@ -181,6 +154,14 @@ public class MyBatisPlusExtensionProperties {
this.pagination = pagination;
}
public boolean isOptimisticLockerEnabled() {
return optimisticLockerEnabled;
}
public void setOptimisticLockerEnabled(boolean optimisticLockerEnabled) {
this.optimisticLockerEnabled = optimisticLockerEnabled;
}
public boolean isBlockAttackPluginEnabled() {
return blockAttackPluginEnabled;
}

View File

@@ -14,15 +14,12 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.plus.autoconfigure;
package top.continew.starter.data.mp.autoconfigure;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusPropertiesCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.*;
import jakarta.annotation.PostConstruct;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
@@ -38,10 +35,10 @@ import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.core.util.GeneralPropertySourceFactory;
import top.continew.starter.data.mybatis.plus.autoconfigure.idgenerator.MyBatisPlusIdGeneratorConfiguration;
import top.continew.starter.data.mybatis.plus.datapermission.DataPermissionFilter;
import top.continew.starter.data.mybatis.plus.datapermission.DataPermissionHandlerImpl;
import top.continew.starter.data.mybatis.plus.handler.MybatisBaseEnumTypeHandler;
import top.continew.starter.data.mp.autoconfigure.idgenerator.MyBatisPlusIdGeneratorConfiguration;
import top.continew.starter.data.mp.handler.MybatisBaseEnumTypeHandler;
import java.util.Map;
/**
* MyBatis Plus 自动配置
@@ -76,18 +73,20 @@ public class MybatisPlusAutoConfiguration {
@ConditionalOnMissingBean
public MybatisPlusInterceptor mybatisPlusInterceptor(MyBatisPlusExtensionProperties properties) {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 数据权限插件
MyBatisPlusExtensionProperties.DataPermissionProperties dataPermissionProperties = properties
.getDataPermission();
if (null != dataPermissionProperties && dataPermissionProperties.isEnabled()) {
interceptor.addInnerInterceptor(new DataPermissionInterceptor(SpringUtil
.getBean(DataPermissionHandler.class)));
// 其他拦截器
Map<String, InnerInterceptor> innerInterceptors = SpringUtil.getBeansOfType(InnerInterceptor.class);
if (!innerInterceptors.isEmpty()) {
innerInterceptors.values().forEach(interceptor::addInnerInterceptor);
}
// 分页插件
MyBatisPlusExtensionProperties.PaginationProperties paginationProperties = properties.getPagination();
if (null != paginationProperties && paginationProperties.isEnabled()) {
interceptor.addInnerInterceptor(this.paginationInnerInterceptor(paginationProperties));
}
// 乐观锁插件
if (properties.isOptimisticLockerEnabled()) {
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
}
// 防全表更新与删除插件
if (properties.isBlockAttackPluginEnabled()) {
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
@@ -103,16 +102,6 @@ public class MybatisPlusAutoConfiguration {
MyBatisPlusIdGeneratorConfiguration.Custom.class})
protected static class MyBatisPlusIdGeneratorAutoConfiguration {}
/**
* 数据权限处理器
*/
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledDataPermission
public DataPermissionHandler dataPermissionHandler(DataPermissionFilter dataPermissionFilter) {
return new DataPermissionHandlerImpl(dataPermissionFilter);
}
/**
* 分页插件配置<a href="https://baomidou.com/pages/97710a/#paginationinnerinterceptor">PaginationInnerInterceptor</a>
*/

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.plus.autoconfigure.idgenerator;
package top.continew.starter.data.mp.autoconfigure.idgenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import me.ahoo.cosid.snowflake.SnowflakeId;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.plus.autoconfigure.idgenerator;
package top.continew.starter.data.mp.autoconfigure.idgenerator;
import cn.hutool.core.net.NetUtil;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;

View File

@@ -14,9 +14,9 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.plus.autoconfigure.idgenerator;
package top.continew.starter.data.mp.autoconfigure.idgenerator;
import top.continew.starter.data.mybatis.plus.enums.MyBatisPlusIdGeneratorType;
import top.continew.starter.data.mp.enums.MyBatisPlusIdGeneratorType;
/**
* MyBatis ID 生成器配置属性

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.plus.base;
package top.continew.starter.data.mp.base;
import cn.hutool.core.util.ClassUtil;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.plus.enums;
package top.continew.starter.data.mp.enums;
/**
* MyBatis ID 生成器类型枚举

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.plus.handler;
package top.continew.starter.data.mp.handler;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.baomidou.mybatisplus.annotation.IEnum;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.plus.service;
package top.continew.starter.data.mp.service;
/**
* 通用业务接口
@@ -23,5 +23,4 @@ package top.continew.starter.data.mybatis.plus.service;
* @author Charles7c
* @since 1.2.0
*/
public interface IService<T> extends com.baomidou.mybatisplus.extension.service.IService<T> {
}
public interface IService<T> extends com.baomidou.mybatisplus.extension.service.IService<T> {}

View File

@@ -14,13 +14,13 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.plus.service.impl;
package top.continew.starter.data.mp.service.impl;
import cn.hutool.core.util.ClassUtil;
import top.continew.starter.core.util.ReflectUtils;
import top.continew.starter.core.util.validate.CheckUtils;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mybatis.plus.service.IService;
import top.continew.starter.data.mp.base.BaseMapper;
import top.continew.starter.data.mp.service.IService;
import java.io.Serializable;
import java.lang.reflect.Field;

View File

@@ -14,15 +14,15 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.plus.util;
package top.continew.starter.data.mp.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Sort;
import top.continew.starter.core.exception.BadRequestException;
import top.continew.starter.core.util.ReflectUtils;
@@ -33,7 +33,10 @@ import top.continew.starter.data.core.enums.QueryType;
import top.continew.starter.data.core.util.SqlInjectionUtils;
import java.lang.reflect.Field;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
/**
@@ -134,7 +137,7 @@ public class QueryWrapperHelper {
}
// 设置了 @QueryIgnore 注解直接忽略
QueryIgnore queryIgnoreAnnotation = field.getAnnotation(QueryIgnore.class);
if (null != queryIgnoreAnnotation && queryIgnoreAnnotation.value()) {
if (null != queryIgnoreAnnotation) {
return Collections.emptyList();
}
// 建议数据库表列建议采用下划线连接法命名程序变量建议采用驼峰法命名

View File

@@ -0,0 +1 @@
top.continew.starter.data.mp.autoconfigure.MybatisPlusAutoConfiguration

View File

@@ -1 +0,0 @@
top.continew.starter.data.mybatis.flex.autoconfigure.MybatisFlexAutoConfiguration

View File

@@ -1 +0,0 @@
top.continew.starter.data.mybatis.plus.autoconfigure.MybatisPlusAutoConfiguration

View File

@@ -15,8 +15,8 @@
<modules>
<module>continew-starter-data-core</module>
<module>continew-starter-data-mybatis-plus</module>
<module>continew-starter-data-mybatis-flex</module>
<module>continew-starter-data-mp</module>
<module>continew-starter-data-mf</module>
</modules>
<dependencies>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.7</version>
<version>3.2.10</version>
<relativePath/>
</parent>
@@ -43,33 +43,33 @@
<properties>
<!-- 项目版本号 -->
<revision>2.5.2</revision>
<snail-job.version>1.1.0</snail-job.version>
<sa-token.version>1.38.0</sa-token.version>
<revision>2.7.1</revision>
<snail-job.version>1.1.2</snail-job.version>
<sa-token.version>1.39.0</sa-token.version>
<just-auth.version>1.16.6</just-auth.version>
<mybatis-plus.version>3.5.7</mybatis-plus.version>
<mybatis-flex.version>1.9.3</mybatis-flex.version>
<mybatis-plus.version>3.5.8</mybatis-plus.version>
<mybatis-flex.version>1.9.7</mybatis-flex.version>
<dynamic-datasource.version>4.3.1</dynamic-datasource.version>
<p6spy.version>3.9.1</p6spy.version>
<jetcache.version>2.7.6</jetcache.version>
<redisson.version>3.32.0</redisson.version>
<cosid.version>2.9.1</cosid.version>
<sms4j.version>3.2.1</sms4j.version>
<redisson.version>3.36.0</redisson.version>
<cosid.version>2.9.8</cosid.version>
<sms4j.version>3.3.3</sms4j.version>
<aj-captcha.version>1.3.0</aj-captcha.version>
<easy-captcha.version>1.6.2</easy-captcha.version>
<easy-excel.version>3.3.4</easy-excel.version>
<nashorn.version>15.4</nashorn.version>
<x-file-storage.version>2.2.0</x-file-storage.version>
<aws-s3.version>1.12.761</aws-s3.version>
<graceful-response.version>4.0.1-boot3</graceful-response.version>
<x-file-storage.version>2.2.1</x-file-storage.version>
<aws-s3.version>1.12.771</aws-s3.version>
<graceful-response.version>5.0.0-boot3</graceful-response.version>
<crane4j.version>2.9.0</crane4j.version>
<knife4j.version>4.5.0</knife4j.version>
<tlog.version>1.5.2</tlog.version>
<snakeyaml.version>2.2</snakeyaml.version>
<snakeyaml.version>2.3</snakeyaml.version>
<okhttp.version>4.12.0</okhttp.version>
<ttl.version>2.14.5</ttl.version>
<ip2region.version>3.2.6</ip2region.version>
<hutool.version>5.8.29</hutool.version>
<hutool.version>5.8.32</hutool.version>
<!-- Maven Plugin Versions -->
<flatten.version>1.6.0</flatten.version>
<spotless.version>2.43.0</spotless.version>
@@ -137,6 +137,11 @@
<artifactId>mybatis-plus-core</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- MyBatis FlexMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
<dependency>
@@ -341,14 +346,12 @@
<artifactId>continew-starter-extension-crud-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - CRUD - MyBatis Plus ORM 模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-crud-mp</artifactId>
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - CRUD - MyBatis Flex ORM 模块 -->
<dependency>
<groupId>top.continew</groupId>
@@ -356,6 +359,32 @@
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - 数据权限 - 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-datapermission-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - 数据权限 - MyBatis Plus ORM 模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-datapermission-mp</artifactId>
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - 多租户 - 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-tenant-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - 多租户 - MyBatis Plus ORM 模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-tenant-mp</artifactId>
<version>${revision}</version>
</dependency>
<!-- 认证模块 - JustAuth -->
<dependency>
<groupId>top.continew</groupId>
@@ -373,14 +402,14 @@
<!-- 数据访问模块 - MyBatis Plus -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-data-mybatis-plus</artifactId>
<artifactId>continew-starter-data-mp</artifactId>
<version>${revision}</version>
</dependency>
<!-- 数据访问模块 - MyBatis Flex -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-data-mybatis-flex</artifactId>
<artifactId>continew-starter-data-mf</artifactId>
<version>${revision}</version>
</dependency>

View File

@@ -53,5 +53,11 @@
<groupId>top.continew</groupId>
<artifactId>continew-starter-file-excel</artifactId>
</dependency>
<!-- API 文档模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-api-doc</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -31,5 +31,4 @@ import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({CrudRestControllerAutoConfiguration.class})
public @interface EnableCrudRestController {
}
public @interface EnableCrudRestController {}

View File

@@ -16,7 +16,6 @@
package top.continew.starter.extension.crud.model.query;
import cn.hutool.core.net.URLDecoder;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ArrayUtil;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -27,7 +26,6 @@ import top.continew.starter.data.core.util.SqlInjectionUtils;
import java.io.Serial;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@@ -60,7 +58,7 @@ public class SortQuery implements Serializable {
}
ValidationUtils.throwIf(sort.length < 2, "排序条件非法");
List<Sort.Order> orders = new ArrayList<>(sort.length);
if (CharSequenceUtil.contains(sort[0], URLDecoder.decode(StringConstants.COMMA, StandardCharsets.UTF_8))) {
if (CharSequenceUtil.contains(sort[0], StringConstants.COMMA)) {
// e.g "sort=createTime,desc&sort=name,asc"
for (String s : sort) {
List<String> sortList = CharSequenceUtil.splitTrim(s, StringConstants.COMMA);

View File

@@ -33,13 +33,11 @@ public interface ValidateGroup extends Default {
/**
* 分组校验-创建
*/
interface Add extends Crud {
}
interface Add extends Crud {}
/**
* 分组校验-修改
*/
interface Update extends Crud {
}
interface Update extends Crud {}
}
}

View File

@@ -22,7 +22,7 @@
<!-- 数据访问模块 - MyBatis Flex -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-data-mybatis-flex</artifactId>
<artifactId>continew-starter-data-mf</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -18,8 +18,8 @@ package top.continew.starter.extension.crud.service;
import cn.hutool.core.lang.tree.Tree;
import jakarta.servlet.http.HttpServletResponse;
import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.continew.starter.extension.crud.model.resp.PageResp;

View File

@@ -20,7 +20,6 @@ import cn.crane4j.core.support.OperateTemplate;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.text.CharSequenceUtil;
@@ -34,16 +33,16 @@ import org.springframework.transaction.annotation.Transactional;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.ReflectUtils;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.data.mybatis.flex.base.BaseMapper;
import top.continew.starter.data.mybatis.flex.util.QueryWrapperHelper;
import top.continew.starter.data.mybatis.flex.service.impl.ServiceImpl;
import top.continew.starter.data.mf.base.BaseMapper;
import top.continew.starter.data.mf.service.impl.ServiceImpl;
import top.continew.starter.data.mf.util.QueryWrapperHelper;
import top.continew.starter.extension.crud.annotation.TreeField;
import top.continew.starter.extension.crud.model.entity.BaseIdDO;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.extension.crud.service.BaseService;
import top.continew.starter.extension.crud.util.TreeUtils;
import top.continew.starter.extension.crud.model.entity.BaseIdDO;
import top.continew.starter.file.excel.util.ExcelUtils;
import java.lang.reflect.Field;

View File

@@ -22,7 +22,7 @@
<!-- 数据访问模块 - MyBatis Plus -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-data-mybatis-plus</artifactId>
<artifactId>continew-starter-data-mp</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -30,13 +30,13 @@ import org.springframework.web.bind.annotation.*;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.enums.Api;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.req.BaseReq;
import top.continew.starter.extension.crud.model.resp.BaseIdResp;
import top.continew.starter.extension.crud.util.ValidateGroup;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.extension.crud.service.BaseService;
import top.continew.starter.extension.crud.util.ValidateGroup;
import java.util.List;

View File

@@ -37,9 +37,9 @@ import top.continew.starter.core.util.ClassUtils;
import top.continew.starter.core.util.ReflectUtils;
import top.continew.starter.core.util.validate.CheckUtils;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mybatis.plus.service.impl.ServiceImpl;
import top.continew.starter.data.mybatis.plus.util.QueryWrapperHelper;
import top.continew.starter.data.mp.base.BaseMapper;
import top.continew.starter.data.mp.service.impl.ServiceImpl;
import top.continew.starter.data.mp.util.QueryWrapperHelper;
import top.continew.starter.extension.crud.annotation.DictField;
import top.continew.starter.extension.crud.annotation.TreeField;
import top.continew.starter.extension.crud.model.entity.BaseIdDO;

View File

@@ -0,0 +1,14 @@
<?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>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-datapermission</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-starter-extension-datapermission-core</artifactId>
<description>ContiNew Starter 扩展模块 - 数据权限 - 核心模块</description>
</project>

View File

@@ -14,9 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.plus.datapermission;
import org.springframework.core.annotation.AliasFor;
package top.continew.starter.extension.datapermission.annotation;
import java.lang.annotation.*;
@@ -31,16 +29,9 @@ import java.lang.annotation.*;
@Documented
public @interface DataPermission {
/**
* Alias for the {@link #tableAlias()} attribute.
*/
@AliasFor("tableAlias")
String value() default "";
/**
* 表别名
*/
@AliasFor("value")
String tableAlias() default "";
/**

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.datapermission.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
import top.continew.starter.core.constant.PropertiesConstants;
/**
* 数据权限配置属性
*
* @author Charles7c
* @since 2.7.0
*/
@ConfigurationProperties(PropertiesConstants.DATA_PERMISSION)
public class DataPermissionProperties {
/**
* 是否启用多租户
*/
private boolean enabled = true;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}

View File

@@ -14,15 +14,17 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.plus.datapermission;
package top.continew.starter.extension.datapermission.filter;
import top.continew.starter.extension.datapermission.model.UserContext;
/**
* 数据权限过滤器接口
* 数据权限用户上下文提供者
*
* @author Charles7c
* @since 1.1.0
*/
public interface DataPermissionFilter {
public interface DataPermissionUserContextProvider {
/**
* 是否过滤
@@ -32,9 +34,9 @@ public interface DataPermissionFilter {
boolean isFilter();
/**
* 获取当前用户信息
* 获取用户上下文
*
* @return 当前用户信息
* @return 用户上下文
*/
DataPermissionCurrentUser getCurrentUser();
UserContext getUserContext();
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.datapermission.model;
import top.continew.starter.extension.datapermission.enums.DataScope;
/**
* 角色上下文
*
* @author Charles7c
* @since 1.1.0
*/
public class RoleContext {
/**
* 角色 ID
*/
private String roleId;
/**
* 数据权限
*/
private DataScope dataScope;
public RoleContext() {
}
public RoleContext(String roleId, DataScope dataScope) {
this.roleId = roleId;
this.dataScope = dataScope;
}
public String getRoleId() {
return roleId;
}
public void setRoleId(String roleId) {
this.roleId = roleId;
}
public DataScope getDataScope() {
return dataScope;
}
public void setDataScope(DataScope dataScope) {
this.dataScope = dataScope;
}
}

View File

@@ -14,17 +14,17 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.plus.datapermission;
package top.continew.starter.extension.datapermission.model;
import java.util.Set;
/**
* 当前用户信息
* 用户上下文
*
* @author Charles7c
* @since 1.1.0
*/
public class DataPermissionCurrentUser {
public class UserContext {
/**
* 用户 ID
@@ -34,53 +34,13 @@ public class DataPermissionCurrentUser {
/**
* 角色列表
*/
private Set<CurrentUserRole> roles;
private Set<RoleContext> roles;
/**
* 部门 ID
*/
private String deptId;
/**
* 当前用户角色信息
*/
public static class CurrentUserRole {
/**
* 角色 ID
*/
private String roleId;
/**
* 数据权限
*/
private DataScope dataScope;
public CurrentUserRole() {
}
public CurrentUserRole(String roleId, DataScope dataScope) {
this.roleId = roleId;
this.dataScope = dataScope;
}
public String getRoleId() {
return roleId;
}
public void setRoleId(String roleId) {
this.roleId = roleId;
}
public DataScope getDataScope() {
return dataScope;
}
public void setDataScope(DataScope dataScope) {
this.dataScope = dataScope;
}
}
public String getUserId() {
return userId;
}
@@ -89,11 +49,11 @@ public class DataPermissionCurrentUser {
this.userId = userId;
}
public Set<CurrentUserRole> getRoles() {
public Set<RoleContext> getRoles() {
return roles;
}
public void setRoles(Set<CurrentUserRole> roles) {
public void setRoles(Set<RoleContext> roles) {
this.roles = roles;
}

View File

@@ -0,0 +1,28 @@
<?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>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-datapermission</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-starter-extension-datapermission-mp</artifactId>
<description>ContiNew Starter 扩展模块 - 数据权限 - MyBatis Plus ORM 模块</description>
<dependencies>
<!-- MyBatis PlusMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
</dependency>
<!-- 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-datapermission-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,84 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.datapermission.autoconfigure;
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.ResolvableType;
import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.extension.datapermission.filter.DataPermissionUserContextProvider;
import top.continew.starter.extension.datapermission.handler.DefaultDataPermissionHandler;
/**
* 数据权限自动配置
*
* @author Charles7c
* @since 2.7.0
*/
@AutoConfiguration
@EnableConfigurationProperties(DataPermissionProperties.class)
@ConditionalOnProperty(prefix = PropertiesConstants.DATA_PERMISSION, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
public class DataPermissionAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(DataPermissionAutoConfiguration.class);
private DataPermissionAutoConfiguration() {
}
/**
* 数据权限拦截器
*/
@Bean
@ConditionalOnMissingBean
public DataPermissionInterceptor dataPermissionInterceptor(DataPermissionHandler dataPermissionHandler) {
return new DataPermissionInterceptor(dataPermissionHandler);
}
/**
* 数据权限处理器
*/
@Bean
@ConditionalOnMissingBean
public DataPermissionHandler dataPermissionHandler(DataPermissionUserContextProvider dataPermissionUserContextProvider) {
return new DefaultDataPermissionHandler(dataPermissionUserContextProvider);
}
/**
* 数据权限用户上下文提供者
*/
@Bean
@ConditionalOnMissingBean
public DataPermissionUserContextProvider dataPermissionUserContextProvider() {
if (log.isErrorEnabled()) {
log.error("Consider defining a bean of type '{}' in your configuration.", ResolvableType
.forClass(DataPermissionUserContextProvider.class));
}
throw new NoSuchBeanDefinitionException(DataPermissionUserContextProvider.class);
}
static {
log.debug("[ContiNew Starter] - Auto Configuration 'DataPermission' completed initialization.");
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.plus.datapermission;
package top.continew.starter.extension.datapermission.handler;
import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
@@ -22,37 +22,44 @@ import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Function;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.statement.select.ParenthesedSelect;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.SelectItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.extension.datapermission.annotation.DataPermission;
import top.continew.starter.extension.datapermission.enums.DataScope;
import top.continew.starter.extension.datapermission.filter.DataPermissionUserContextProvider;
import top.continew.starter.extension.datapermission.model.RoleContext;
import top.continew.starter.extension.datapermission.model.UserContext;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Set;
/**
* 数据权限处理器实现类
* 默认数据权限处理器
*
* @author <a href="https://gitee.com/baomidou/mybatis-plus/issues/I37I90">DataPermissionInterceptor 如何使用</a>
* @author Charles7c
* @since 1.1.0
*/
public class DataPermissionHandlerImpl implements DataPermissionHandler {
public class DefaultDataPermissionHandler implements DataPermissionHandler {
private static final Logger log = LoggerFactory.getLogger(DataPermissionHandlerImpl.class);
private final DataPermissionFilter dataPermissionFilter;
private static final Logger log = LoggerFactory.getLogger(DefaultDataPermissionHandler.class);
private final DataPermissionUserContextProvider dataPermissionUserContextProvider;
public DataPermissionHandlerImpl(DataPermissionFilter dataPermissionFilter) {
this.dataPermissionFilter = dataPermissionFilter;
public DefaultDataPermissionHandler(DataPermissionUserContextProvider dataPermissionUserContextProvider) {
this.dataPermissionUserContextProvider = dataPermissionUserContextProvider;
}
@Override
@@ -68,7 +75,7 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
if (null == dataPermission || !CharSequenceUtil.equalsAny(methodName, name, name + "_COUNT")) {
continue;
}
if (dataPermissionFilter.isFilter()) {
if (dataPermissionUserContextProvider.isFilter()) {
return buildDataScopeFilter(dataPermission, where);
}
}
@@ -87,23 +94,23 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
*/
private Expression buildDataScopeFilter(DataPermission dataPermission, Expression where) {
Expression expression = null;
DataPermissionCurrentUser currentUser = dataPermissionFilter.getCurrentUser();
Set<DataPermissionCurrentUser.CurrentUserRole> roles = currentUser.getRoles();
for (DataPermissionCurrentUser.CurrentUserRole role : roles) {
DataScope dataScope = role.getDataScope();
UserContext userContext = dataPermissionUserContextProvider.getUserContext();
Set<RoleContext> roles = userContext.getRoles();
for (RoleContext roleContext : roles) {
DataScope dataScope = roleContext.getDataScope();
if (DataScope.ALL.equals(dataScope)) {
return where;
}
switch (dataScope) {
case DEPT_AND_CHILD -> expression = this
.buildDeptAndChildExpression(dataPermission, currentUser, expression);
case DEPT -> expression = this.buildDeptExpression(dataPermission, currentUser, expression);
case SELF -> expression = this.buildSelfExpression(dataPermission, currentUser, expression);
case CUSTOM -> expression = this.buildCustomExpression(dataPermission, role, expression);
.buildDeptAndChildExpression(dataPermission, userContext, expression);
case DEPT -> expression = this.buildDeptExpression(dataPermission, userContext, expression);
case SELF -> expression = this.buildSelfExpression(dataPermission, userContext, expression);
case CUSTOM -> expression = this.buildCustomExpression(dataPermission, roleContext, expression);
default -> throw new IllegalArgumentException("暂不支持 [%s] 数据权限".formatted(dataScope));
}
}
return null != where ? new AndExpression(where, new Parenthesis(expression)) : expression;
return null != where ? new AndExpression(where, new ParenthesedExpressionList<>(expression)) : expression;
}
/**
@@ -115,12 +122,12 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
* </p>
*
* @param dataPermission 数据权限
* @param currentUser 当前用户
* @param userContext 用户上下文
* @param expression 处理前的表达式
* @return 处理完后的表达式
*/
private Expression buildDeptAndChildExpression(DataPermission dataPermission,
DataPermissionCurrentUser currentUser,
UserContext userContext,
Expression expression) {
ParenthesedSelect subSelect = new ParenthesedSelect();
PlainSelect select = new PlainSelect();
@@ -128,10 +135,10 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
select.setFromItem(new Table(dataPermission.deptTableAlias()));
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(new Column(dataPermission.id()));
equalsTo.setRightExpression(new LongValue(currentUser.getDeptId()));
equalsTo.setRightExpression(new LongValue(userContext.getDeptId()));
Function function = new Function();
function.setName("find_in_set");
function.setParameters(new ExpressionList(new LongValue(currentUser.getDeptId()), new Column("ancestors")));
function.setParameters(new ExpressionList<>(new LongValue(userContext.getDeptId()), new Column("ancestors")));
select.setWhere(new OrExpression(equalsTo, function));
subSelect.setSelect(select);
// 构建父查询
@@ -149,16 +156,16 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
* </p>
*
* @param dataPermission 数据权限
* @param currentUser 当前用户
* @param userContext 用户上下文
* @param expression 处理前的表达式
* @return 处理完后的表达式
*/
private Expression buildDeptExpression(DataPermission dataPermission,
DataPermissionCurrentUser currentUser,
UserContext userContext,
Expression expression) {
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.deptId()));
equalsTo.setRightExpression(new LongValue(currentUser.getDeptId()));
equalsTo.setRightExpression(new LongValue(userContext.getDeptId()));
return null != expression ? new OrExpression(expression, equalsTo) : equalsTo;
}
@@ -170,16 +177,16 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
* </p>
*
* @param dataPermission 数据权限
* @param currentUser 当前用户
* @param userContext 用户上下文
* @param expression 处理前的表达式
* @return 处理完后的表达式
*/
private Expression buildSelfExpression(DataPermission dataPermission,
DataPermissionCurrentUser currentUser,
UserContext userContext,
Expression expression) {
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.userId()));
equalsTo.setRightExpression(new LongValue(currentUser.getUserId()));
equalsTo.setRightExpression(new LongValue(userContext.getUserId()));
return null != expression ? new OrExpression(expression, equalsTo) : equalsTo;
}
@@ -192,12 +199,12 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
* </p>
*
* @param dataPermission 数据权限
* @param role 当前用户角色
* @param roleContext 角色上下文
* @param expression 处理前的表达式
* @return 处理完后的表达式
*/
private Expression buildCustomExpression(DataPermission dataPermission,
DataPermissionCurrentUser.CurrentUserRole role,
RoleContext roleContext,
Expression expression) {
ParenthesedSelect subSelect = new ParenthesedSelect();
PlainSelect select = new PlainSelect();
@@ -205,7 +212,7 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
select.setFromItem(new Table(dataPermission.roleDeptTableAlias()));
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(new Column(dataPermission.roleId()));
equalsTo.setRightExpression(new LongValue(role.getRoleId()));
equalsTo.setRightExpression(new LongValue(roleContext.getRoleId()));
select.setWhere(equalsTo);
subSelect.setSelect(select);
// 构建父查询

View File

@@ -0,0 +1,20 @@
<?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>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-starter-extension-datapermission</artifactId>
<packaging>pom</packaging>
<description>ContiNew Starter 扩展模块 - 数据权限</description>
<modules>
<module>continew-starter-extension-datapermission-core</module>
<module>continew-starter-extension-datapermission-mp</module>
</modules>
</project>

View File

@@ -0,0 +1,34 @@
<?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>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-tenant</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-starter-extension-tenant-core</artifactId>
<description>ContiNew Starter 扩展模块 - 多租户 - 核心模块</description>
<dependencies>
<!-- TTL线程间传递 ThreadLocal异步执行时上下文传递的解决方案 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -14,22 +14,18 @@
* limitations under the License.
*/
package top.continew.starter.data.mybatis.plus.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import top.continew.starter.core.constant.PropertiesConstants;
package top.continew.starter.extension.tenant.annotation;
import java.lang.annotation.*;
/**
* 是否启用数据权限注解
* 多租户数据源级隔离忽略注解
*
* @author Charles7c
* @since 1.1.0
* @since 2.7.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ConditionalOnProperty(prefix = "mybatis-plus.extension.data-permission", name = PropertiesConstants.ENABLED, havingValue = "true")
public @interface ConditionalOnEnabledDataPermission {
}
public @interface TenantDataSourceIgnore {
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.tenant.autoconfigure;
import cn.hutool.core.convert.Convert;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import top.continew.starter.extension.tenant.context.TenantContext;
import top.continew.starter.extension.tenant.context.TenantContextHolder;
/**
* 租户拦截器
*
* @author Charles7c
* @since 2.7.0
*/
public class TenantInterceptor implements HandlerInterceptor {
private final TenantProperties tenantProperties;
public TenantInterceptor(TenantProperties tenantProperties) {
this.tenantProperties = tenantProperties;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String tenantId = request.getHeader(tenantProperties.getTenantIdHeader());
TenantContext tenantContext = new TenantContext();
tenantContext.setTenantId(Convert.toLong(tenantId));
TenantContextHolder.setContext(tenantContext);
return true;
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.tenant.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
import java.util.List;
/**
* 租户配置属性
*
* @author Charles7c
* @since 2.7.0
*/
@ConfigurationProperties(PropertiesConstants.TENANT)
public class TenantProperties {
/**
* 是否启用多租户
*/
private boolean enabled = true;
/**
* 租户隔离级别
*/
private TenantIsolationLevel isolationLevel = TenantIsolationLevel.LINE;
/**
* 租户 ID 列名
*/
private String tenantIdColumn = "tenant_id";
/**
* 请求头中租户 ID 键名
*/
private String tenantIdHeader = "X-Tenant-Id";
/**
* 超级租户 ID
*/
private Long superTenantId = 1L;
/**
* 忽略表(忽略拼接多租户条件)
*/
private List<String> ignoreTables;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public TenantIsolationLevel getIsolationLevel() {
return isolationLevel;
}
public void setIsolationLevel(TenantIsolationLevel isolationLevel) {
this.isolationLevel = isolationLevel;
}
public String getTenantIdColumn() {
return tenantIdColumn;
}
public void setTenantIdColumn(String tenantIdColumn) {
this.tenantIdColumn = tenantIdColumn;
}
public String getTenantIdHeader() {
return tenantIdHeader;
}
public void setTenantIdHeader(String tenantIdHeader) {
this.tenantIdHeader = tenantIdHeader;
}
public Long getSuperTenantId() {
return superTenantId;
}
public void setSuperTenantId(Long superTenantId) {
this.superTenantId = superTenantId;
}
public List<String> getIgnoreTables() {
return ignoreTables;
}
public void setIgnoreTables(List<String> ignoreTables) {
this.ignoreTables = ignoreTables;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.tenant.autoconfigure;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import top.continew.starter.core.constant.PropertiesConstants;
/**
* 多租户 Web MVC 自动配置
*
* @author Charles7c
* @since 2.7.0
*/
@AutoConfiguration
@ConditionalOnWebApplication
@ConditionalOnProperty(prefix = PropertiesConstants.TENANT, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
public class TenantWebMvcAutoConfiguration implements WebMvcConfigurer {
private final TenantProperties tenantProperties;
public TenantWebMvcAutoConfiguration(TenantProperties tenantProperties) {
this.tenantProperties = tenantProperties;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TenantInterceptor(tenantProperties));
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.tenant.config;
/**
* 租户数据源配置
*
* @author Charles7c
* @since 2.7.0
*/
public class TenantDataSource {
/**
* 连接池名称
*/
private String poolName;
/**
* 驱动类名
*/
private String driverClassName;
/**
* 数据库连接地址
*/
private String url;
/**
* 数据库用户名
*/
private String username;
/**
* 数据库密码
*/
private String password;
public String getPoolName() {
return poolName;
}
public void setPoolName(String poolName) {
this.poolName = poolName;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.tenant.config;
/**
* 租户数据源提供者
*
* @author Charles7c
* @since 2.7.0
*/
public interface TenantDataSourceProvider {
/**
* 根据租户 ID 获取数据源配置
*
* @param tenantId 租户 ID
* @return 数据源配置
*/
TenantDataSource getByTenantId(String tenantId);
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.tenant.context;
/**
* 租户上下文
*
* @author Charles7c
* @since 2.7.0
*/
public class TenantContext {
/**
* 租户 ID
*/
private Long tenantId;
public Long getTenantId() {
return tenantId;
}
public void setTenantId(Long tenantId) {
this.tenantId = tenantId;
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.tenant.context;
import com.alibaba.ttl.TransmittableThreadLocal;
import java.util.Optional;
/**
* 租户上下文 Holder
*
* @author Charles7c
* @since 2.7.0
*/
public class TenantContextHolder {
private static final TransmittableThreadLocal<TenantContext> CONTEXT_HOLDER = new TransmittableThreadLocal<>();
private TenantContextHolder() {
}
/**
* 设置上下文
*
* @param context 上下文
*/
public static void setContext(TenantContext context) {
CONTEXT_HOLDER.set(context);
}
/**
* 获取上下文
*
* @return 上下文
*/
public static TenantContext getContext() {
return CONTEXT_HOLDER.get();
}
/**
* 清除上下文
*/
public static void clearContext() {
CONTEXT_HOLDER.remove();
}
/**
* 获取租户 ID
*
* @return 租户 ID
*/
public static Long getTenantId() {
return Optional.ofNullable(getContext()).map(TenantContext::getTenantId).orElse(null);
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.tenant.enums;
/**
* 租户隔离级别
*
* @author Charles7c
* @since 2.7.0
*/
public enum TenantIsolationLevel {
/**
* 行级
*/
LINE,
/**
* 数据源级
*/
DATASOURCE
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.tenant.handler;
import top.continew.starter.extension.tenant.config.TenantDataSource;
import javax.sql.DataSource;
/**
* 租户数据源级隔离处理器
*
* @author Charles7c
* @since 2.7.0
*/
public interface TenantDataSourceHandler {
/**
* 切换数据源
*
* @param dataSourceName 数据源名称
*/
void changeDataSource(String dataSourceName);
/**
* 是否存在指定数据源
*
* @param dataSourceName 数据源名称
* @return 是否存在指定数据源
*/
boolean containsDataSource(String dataSourceName);
/**
* 创建数据源
*
* @param tenantDataSource 数据源配置
* @return 数据源
*/
DataSource createDataSource(TenantDataSource tenantDataSource);
/**
* 移除数据源
*
* @param dataSourceName 数据源名称
*/
void removeDataSource(String dataSourceName);
}

View File

@@ -0,0 +1,35 @@
<?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>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-tenant</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-starter-extension-tenant-mp</artifactId>
<description>ContiNew Starter 扩展模块 - 多租户 - MyBatis Plus ORM 模块</description>
<dependencies>
<!-- MyBatis PlusMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
</dependency>
<!-- Dynamic Datasource基于 Spring Boot 的快速集成多数据源的启动器) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<optional>true</optional>
</dependency>
<!-- 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-tenant-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,133 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.tenant.autoconfigure;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.ResolvableType;
import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.extension.tenant.config.TenantDataSourceProvider;
import top.continew.starter.extension.tenant.handler.*;
/**
* 多租户自动配置
*
* @author Charles7c
* @since 2.7.0
*/
@AutoConfiguration
@EnableConfigurationProperties(TenantProperties.class)
@ConditionalOnProperty(prefix = PropertiesConstants.TENANT, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
public class TenantAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(TenantAutoConfiguration.class);
private TenantAutoConfiguration() {
}
/**
* 租户隔离级别:行级
*/
@AutoConfiguration
@ConditionalOnProperty(name = PropertiesConstants.TENANT + ".isolation-level", havingValue = "line", matchIfMissing = true)
public static class Line {
static {
log.debug("[ContiNew Starter] - Auto Configuration 'Tenant-Line' completed initialization.");
}
/**
* 租户行级隔离拦截器
*/
@Bean
@ConditionalOnMissingBean
public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantLineHandler tenantLineHandler) {
return new TenantLineInnerInterceptor(tenantLineHandler);
}
/**
* 租户行级隔离处理器(默认)
*/
@Bean
@ConditionalOnMissingBean
public TenantLineHandler tenantLineHandler(TenantProperties properties) {
return new DefaultTenantLineHandler(properties);
}
}
/**
* 租户隔离级别:数据源级
*/
@AutoConfiguration
@ConditionalOnProperty(name = PropertiesConstants.TENANT + ".isolation-level", havingValue = "datasource")
public static class DataSource {
static {
log.debug("[ContiNew Starter] - Auto Configuration 'Tenant-DataSource' completed initialization.");
}
/**
* 租户数据源级隔离通知
*/
@Bean
@ConditionalOnMissingBean
public TenantDataSourceAdvisor tenantDataSourceAdvisor(TenantDataSourceInterceptor tenantDataSourceInterceptor) {
return new TenantDataSourceAdvisor(tenantDataSourceInterceptor);
}
/**
* 租户数据源级隔离拦截器
*/
@Bean
@ConditionalOnMissingBean
public TenantDataSourceInterceptor tenantDataSourceInterceptor(TenantDataSourceHandler tenantDataSourceHandler) {
return new TenantDataSourceInterceptor(tenantDataSourceHandler);
}
/**
* 租户数据源级隔离处理器(默认)
*/
@Bean
@ConditionalOnMissingBean
public TenantDataSourceHandler tenantDataSourceHandler(TenantDataSourceProvider tenantDataSourceProvider,
DynamicRoutingDataSource dynamicRoutingDataSource,
DefaultDataSourceCreator dataSourceCreator) {
return new DefaultTenantDataSourceHandler(tenantDataSourceProvider, dynamicRoutingDataSource, dataSourceCreator);
}
/**
* 多租户数据源提供者
*/
@Bean
@ConditionalOnMissingBean
public TenantDataSourceProvider tenantDataSourceProvider() {
if (log.isErrorEnabled()) {
log.error("Consider defining a bean of type '{}' in your configuration.", ResolvableType
.forClass(TenantDataSourceProvider.class));
}
throw new NoSuchBeanDefinitionException(TenantDataSourceProvider.class);
}
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.tenant.handler;
import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.continew.starter.extension.tenant.config.TenantDataSource;
import top.continew.starter.extension.tenant.config.TenantDataSourceProvider;
import javax.sql.DataSource;
/**
* 默认租户数据源级隔离处理器
*
* @author Charles7c
* @since 2.7.0
*/
public class DefaultTenantDataSourceHandler implements TenantDataSourceHandler {
private static final Logger log = LoggerFactory.getLogger(DefaultTenantDataSourceHandler.class);
private final DynamicRoutingDataSource dynamicRoutingDataSource;
private final DefaultDataSourceCreator dataSourceCreator;
private final TenantDataSourceProvider tenantDataSourceProvider;
public DefaultTenantDataSourceHandler(TenantDataSourceProvider tenantDataSourceProvider,
DynamicRoutingDataSource dynamicRoutingDataSource,
DefaultDataSourceCreator dataSourceCreator) {
this.tenantDataSourceProvider = tenantDataSourceProvider;
this.dynamicRoutingDataSource = dynamicRoutingDataSource;
this.dataSourceCreator = dataSourceCreator;
}
@Override
public void changeDataSource(String dataSourceName) {
if (!this.containsDataSource(dataSourceName)) {
TenantDataSource tenantDataSource = tenantDataSourceProvider.getByTenantId(dataSourceName);
if (null == tenantDataSource) {
throw new IllegalArgumentException("Data source [%s] configuration not found"
.formatted(dataSourceName));
}
DataSource datasource = this.createDataSource(tenantDataSource);
dynamicRoutingDataSource.addDataSource(dataSourceName, datasource);
log.info("Load data source: {}", dataSourceName);
}
DynamicDataSourceContextHolder.push(dataSourceName);
log.info("Change data source: {}", dataSourceName);
}
@Override
public boolean containsDataSource(String dataSourceName) {
return CharSequenceUtil.isNotBlank(dataSourceName) && dynamicRoutingDataSource.getDataSources()
.containsKey(dataSourceName);
}
@Override
public DataSource createDataSource(TenantDataSource tenantDataSource) {
DataSourceProperty dataSourceProperty = new DataSourceProperty();
dataSourceProperty.setPoolName(tenantDataSource.getPoolName());
dataSourceProperty.setDriverClassName(tenantDataSource.getDriverClassName());
dataSourceProperty.setUrl(tenantDataSource.getUrl());
dataSourceProperty.setUsername(tenantDataSource.getUsername());
dataSourceProperty.setPassword(tenantDataSource.getPassword());
return dataSourceCreator.createDataSource(dataSourceProperty);
}
@Override
public void removeDataSource(String dataSourceName) {
dynamicRoutingDataSource.removeDataSource(dataSourceName);
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.tenant.handler;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import top.continew.starter.extension.tenant.autoconfigure.TenantProperties;
import top.continew.starter.extension.tenant.context.TenantContextHolder;
/**
* 默认租户行级隔离处理器
*
* @author Charles7c
* @since 2.7.0
*/
public class DefaultTenantLineHandler implements TenantLineHandler {
private final TenantProperties tenantProperties;
public DefaultTenantLineHandler(TenantProperties tenantProperties) {
this.tenantProperties = tenantProperties;
}
@Override
public Expression getTenantId() {
Long tenantId = TenantContextHolder.getTenantId();
if (null != tenantId) {
return new LongValue(tenantId);
}
return null;
}
@Override
public String getTenantIdColumn() {
return tenantProperties.getTenantIdColumn();
}
@Override
public boolean ignoreTable(String tableName) {
Long tenantId = TenantContextHolder.getTenantId();
if (null != tenantId && tenantId.equals(tenantProperties.getSuperTenantId())) {
return true;
}
return CollUtil.contains(tenantProperties.getIgnoreTables(), tableName);
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.tenant.handler;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Pointcut;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
/**
* 租户数据源级隔离通知
*
* @author Charles7c
* @since 2.7.0
*/
public class TenantDataSourceAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
private final Advice advice;
private final Pointcut pointcut;
public TenantDataSourceAdvisor(TenantDataSourceInterceptor interceptor) {
this.advice = interceptor;
this.pointcut = buildPointcut();
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public Advice getAdvice() {
return this.advice;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (this.advice instanceof BeanFactoryAware beanFactoryAware) {
beanFactoryAware.setBeanFactory(beanFactory);
}
}
/**
* 构建切点
*
* @return 切点
*/
private Pointcut buildPointcut() {
AspectJExpressionPointcut cut = new AspectJExpressionPointcut();
cut.setExpression("!@annotation(top.continew.starter.extension.tenant.annotation.TenantDataSourceIgnore)");
return new ComposablePointcut((Pointcut)cut);
}
}

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