Compare commits

..

27 Commits

Author SHA1 Message Date
1c86c632dd release: v1.4.0 2024-02-14 21:43:14 +08:00
58dc51f66c chore: 补充 CosId 相关依赖版本锁定 2024-02-13 22:32:02 +08:00
f67f278784 chore(message/sms): 精简部分依赖 2024-02-13 22:31:20 +08:00
c9311df093 refactor(data/mybatis-plus): 重构 ID 生成器配置,支持默认、CosId、自定义 2024-02-12 23:19:23 +08:00
9ebcd14878 feat(security/crypto): 新增 DES、PBEWithMD5AndDES 对称加密算法 2024-02-09 17:45:34 +08:00
74a1166b5f perf(security/crypto): 获取加密算法增加缓存 2024-02-09 17:44:59 +08:00
b604f2fc7e perf(security/crypto): 获取加密参数列表增加缓存 2024-02-09 11:22:35 +08:00
12c3d64066 feat(security): 新增 continew-starter-security-all 模块,统一引入加密、脱敏、密码编码器能力 2024-02-09 10:18:35 +08:00
111e732967 refactor(security/mask): 支持自定义脱敏策略 2024-02-09 10:13:55 +08:00
88f82d1c0a refactor(security/crypto): 支持 MyBatis 查询参数加密 2024-02-09 09:55:52 +08:00
5ccdd9e5da feat(security/crypto): 新增安全模块-加密,支持 MyBatis ORM 框架字段加密 2024-02-08 23:13:01 +08:00
ea71cf573b refactor: 根据 Sonar 建议调整,StrUtil => CharSequenceUtil
工具层调整以减少 Sonar 建议,应用层则可忽略,怎么用方便怎么来
2024-02-07 17:48:32 +08:00
00bba33517 refactor(auth): 调整 Redisson 模块为可选依赖 2024-02-07 17:41:11 +08:00
7b795194d3 feat(security/mask): 新增安全模块-脱敏,支持 JSON 数据脱敏 2024-02-07 17:38:31 +08:00
00798bdb4c chore(cache/redisson): 优化协议前缀变量命名 2024-02-07 10:54:33 +08:00
c33a6709f5 refactor(auth/satoken): JWT 配置支持启用/关闭 2024-02-05 23:39:44 +08:00
2afb0b625f refactor(log): 优化复数属性命名 include -> includes 2024-02-05 23:33:04 +08:00
669ea85658 refactor(log): Log 注解新增 include、exclude 属性,用于扩展或减少日志包含信息
处理类上 Log 注解的日志包含信息 -> 处理方法上 Log 注解的日志包含信息
2024-02-05 23:27:10 +08:00
18b9d1ba79 refactor(log): 兼容小写 user-agent 情况 2024-02-05 23:18:58 +08:00
c963978980 chore(extension/crud): 替换过期枚举 2024-02-04 23:17:28 +08:00
2aea8ba831 refactor(log): 默认启用日志 2024-02-04 23:16:06 +08:00
1ba1596f4e fix(auth/satoken): 修复 SaInterceptor Bean 获取方式错误 2024-02-04 23:13:42 +08:00
3184faaa27 refactor(captcha/graphic): 新增图形验证码服务接口,并调整验证码默认启用 2024-02-04 23:12:25 +08:00
egeong
2b0fb9aff9 fix(message/mail): 修复发送邮件收件人不为空判断错误
* 修复发送邮件问题
2024-02-04 07:32:42 +00:00
24f99754d0 fix(extension/crud): 修复删除后置处理方法访问修饰符使用错误 2024-02-03 19:15:39 +08:00
ab76665aab chore: 升级依赖
1.Spring Boot 3.1.7 => 3.1.8
2.Dynamic Datasource 4.2.0 => 4.3.0
3.JetCache 2.7.4 => 2.7.5
4.Redisson 3.25.2 => 3.26.0
5.SMS4J 3.0.4 => 3.1.1
6.X File Storage 2.0.0 => 2.1.0
7.AWS S3 1.12.626 => 1.12.651
8.Crane4j 2.4.0 => 2.5.0
9.Knife4j 4.4.0 => 4.5.0
10.Hutool 5.8.24 => 5.8.25
11.IP2Region 3.1.6 => 3.1.7
12.flatten-maven-plugin 1.5.0 => 1.6.0
13.spotless-maven-plugin 2.40.0 => 2.43.0
2024-02-03 18:17:36 +08:00
6db8990560 chore: 更新版本号为 1.4.0-SNAPSHOT 2024-02-03 13:05:37 +08:00
75 changed files with 2229 additions and 234 deletions

View File

@@ -1,3 +1,46 @@
## [v1.4.0](https://github.com/Charles7c/continew-starter/compare/v1.3.0...v1.4.0) (2024-02-14)
### ✨ 新特性
- 【captcha/graphic】新增图形验证码服务接口并调整验证码默认启用 ([3184faa](https://github.com/Charles7c/continew-starter/commit/3184faaa27111845867d2210f0db16381d53d800))
- 【log/httptrace-pro】Log 注解新增 include、exclude 属性,用于扩展或减少日志包含信息 ([669ea85](https://github.com/Charles7c/continew-starter/commit/669ea85658c89c631518def8f84d4f5d60059ad7)) ([2afb0b6](https://github.com/Charles7c/continew-starter/commit/2afb0b625fc936364c6dacacc735ce421c5cb37c))
- 【security/mask】新增安全模块-脱敏,支持 JSON 数据脱敏 ([7b79519](https://github.com/Charles7c/continew-starter/commit/7b795194d3db979c239ab30d78fdb61d95f06896)) ([111e732](https://github.com/Charles7c/continew-starter/commit/111e7329673778c475c4ff4aa5ba6eef9f43f506))
- 【security/crypto】新增安全模块-加密,支持 MyBatis ORM 框架字段加密 ([5ccdd9e](https://github.com/Charles7c/continew-starter/commit/5ccdd9e5da2a81d6a1f69bdf3f0e4eb1475b68a0)) ([88f82d1](https://github.com/Charles7c/continew-starter/commit/88f82d1c0aa5abf8f094564f4b84ae84efd80946)) ([b604f2f](https://github.com/Charles7c/continew-starter/commit/b604f2fc7eb938a52338ee41cf1823af374a14da)) ([74a1166](https://github.com/Charles7c/continew-starter/commit/74a1166b5f250c2ba8aab027d98bc11e59860c01)) ([9ebcd14](https://github.com/Charles7c/continew-starter/commit/9ebcd14878b499039a70380b0773b00b9f8dc111))
- 【security/all】新增 continew-starter-security-all 模块,统一引入加密、脱敏、密码编码器能力 ([12c3d64](https://github.com/Charles7c/continew-starter/commit/12c3d640668298439ef0b610f5b36848e1f91b1a))
### 💎 功能优化
- 【log/httptrace-pro】默认启用日志 ([2aea8ba](https://github.com/Charles7c/continew-starter/commit/2aea8ba8318dded142a274221af7de2b62d4ced9))
- 【log/httptrace-pro】兼容小写 user-agent 情况 ([18b9d1b](https://github.com/Charles7c/continew-starter/commit/18b9d1ba799ce96d8831b7243508b2517ff5c5c7))
- 【auth/satoken】JWT 配置支持启用/关闭 ([c33a670](https://github.com/Charles7c/continew-starter/commit/c33a6709f50c2240cc9826c4ee2e83d88db5fb07))
- 【cache/redisson】优化协议前缀变量命名 ([00798bd](https://github.com/Charles7c/continew-starter/commit/00798bdb4c82c8ec8b3cf1110a0afaaa94ad2b27))
- 【auth】调整 Redisson 模块为可选依赖 ([00bba33](https://github.com/Charles7c/continew-starter/commit/00bba33517c15936ec2f40a8a7f3213d25a223aa))
- 【data/mybatis-plus】重构 ID 生成器配置支持默认、CosId、自定义 ([c9311df](https://github.com/Charles7c/continew-starter/commit/c9311df093d4524b272535640333c413a2eda86f)) ([58dc51f](https://github.com/Charles7c/continew-starter/commit/58dc51f66c3a77f7f1621557cdd065243b6ae5a9))
- 【message/sms】精简部分依赖 ([f67f278](https://github.com/Charles7c/continew-starter/commit/f67f278784002de553c923f399d585e35f0a6356))
- 根据 Sonar 建议调整StrUtil => CharSequenceUtil ([ea71cf5](https://github.com/Charles7c/continew-starter/commit/ea71cf573b7b6452b9315c67967f29e25468a04a))
### 🐛 问题修复
- 【extension/crud】修复删除后置处理方法访问修饰符使用错误 ([24f9975](https://github.com/Charles7c/continew-starter/commit/24f99754d041e113f07eb43570d6a49c4ff24008))
- 【message/mail】修复发送邮件收件人不为空判断错误 ([Gitee PR#12](https://gitee.com/Charles7c/continew-starter/pulls/12))
- 【auth/satoken】修复 SaInterceptor Bean 获取方式错误 ([1ba1596](https://github.com/Charles7c/continew-starter/commit/1ba1596f4e4b31d82e174e981711e45a1df67ee7))
### 📦 依赖升级
- 【dependencies】Spring Boot 3.1.7 => 3.1.8 ([ab76665](https://github.com/Charles7c/continew-starter/commit/ab76665aab8cf06e508f039dbce13959e171d0c8))
- 【dependencies】Dynamic Datasource 4.2.0 => 4.3.0 ([ab76665](https://github.com/Charles7c/continew-starter/commit/ab76665aab8cf06e508f039dbce13959e171d0c8))
- 【dependencies】JetCache 2.7.4 => 2.7.5 ([ab76665](https://github.com/Charles7c/continew-starter/commit/ab76665aab8cf06e508f039dbce13959e171d0c8))
- 【dependencies】Redisson 3.25.2 => 3.26.0 ([ab76665](https://github.com/Charles7c/continew-starter/commit/ab76665aab8cf06e508f039dbce13959e171d0c8))
- 【dependencies】SMS4J 3.0.4 => 3.1.1 ([ab76665](https://github.com/Charles7c/continew-starter/commit/ab76665aab8cf06e508f039dbce13959e171d0c8))
- 【dependencies】X File Storage 2.0.0 => 2.1.0 ([ab76665](https://github.com/Charles7c/continew-starter/commit/ab76665aab8cf06e508f039dbce13959e171d0c8))
- 【dependencies】Amazon S3 1.12.626 => 1.12.651 ([ab76665](https://github.com/Charles7c/continew-starter/commit/ab76665aab8cf06e508f039dbce13959e171d0c8))
- 【dependencies】Crane4j 2.4.0 => 2.5.0 ([ab76665](https://github.com/Charles7c/continew-starter/commit/ab76665aab8cf06e508f039dbce13959e171d0c8)) ([c963978](https://github.com/Charles7c/continew-starter/commit/c96397898027140c243b034dc3d23bd3d60695e7))
- 【dependencies】Knife4j 4.4.0 => 4.5.0 ([ab76665](https://github.com/Charles7c/continew-starter/commit/ab76665aab8cf06e508f039dbce13959e171d0c8))
- 【dependencies】Hutool 5.8.24 => 5.8.25 ([ab76665](https://github.com/Charles7c/continew-starter/commit/ab76665aab8cf06e508f039dbce13959e171d0c8))
- 【dependencies】ip2region 3.1.6 => 3.1.7 ([ab76665](https://github.com/Charles7c/continew-starter/commit/ab76665aab8cf06e508f039dbce13959e171d0c8))
- 【dependencies】flatten-maven-plugin 1.5.0 => 1.6.0 ([ab76665](https://github.com/Charles7c/continew-starter/commit/ab76665aab8cf06e508f039dbce13959e171d0c8))
- 【dependencies】spotless-maven-plugin 2.40.0 => 2.43.0 ([ab76665](https://github.com/Charles7c/continew-starter/commit/ab76665aab8cf06e508f039dbce13959e171d0c8))
## [v1.3.0](https://github.com/Charles7c/continew-starter/compare/v1.2.0...v1.3.0) (2024-02-03)
### ✨ 新特性

114
README.md
View File

@@ -7,7 +7,7 @@
<img src="https://img.shields.io/maven-central/v/top.charles7c.continew/continew-starter.svg?label=Maven%20Central&logo=sonatype&logoColor=FFF" alt="Release" />
</a>
<a href="https://github.com/Charles7c/continew-starter" target="_blank">
<img src="https://img.shields.io/badge/RELEASE-v1.3.0-%23ff3f59.svg" alt="Release" />
<img src="https://img.shields.io/badge/RELEASE-v1.4.0-%23ff3f59.svg" alt="Release" />
</a>
<a href="https://app.codacy.com/gh/Charles7c/continew-starter/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade" target="_blank">
<img src="https://app.codacy.com/project/badge/Grade/90ed633957a9410aa8745f0654827c01" alt="Codacy Badge" />
@@ -16,7 +16,7 @@
<img src="https://sonarcloud.io/api/project_badges/measure?project=Charles7c_continew-starter&metric=alert_status" alt="Sonar Status" />
</a>
<a href="https://spring.io/projects/spring-boot" target="_blank">
<img src="https://img.shields.io/badge/Spring Boot-3.1.7-%236CB52D.svg?logo=Spring-Boot" alt="Spring Boot" />
<img src="https://img.shields.io/badge/Spring Boot-3.1.8-%236CB52D.svg?logo=Spring-Boot" alt="Spring Boot" />
</a>
<a href="https://github.com/Charles7c/continew-starter" target="_blank">
<img src="https://img.shields.io/badge/Open JDK-17-%236CB52D.svg?logo=OpenJDK&logoColor=FFF" alt="Open JDK" />
@@ -164,25 +164,97 @@ continew-starter.web:
## 模块结构
| 模块名称 | 模块说明 | 依赖版本 |
| ---------------------------------- | --------------------------------------------------- | ------------------------------------------------------------ |
| continew-starter-core | 核心模块:包含线程池等自动配置 | <a href="https://spring.io/projects/spring-boot" target="_blank">Spring Boot</a>3.1.7<br /><a href="https://www.hutool.cn/" target="_blank">Hutool</a>5.8.24<br />mica-ip2region3.1.6 |
| continew-starter-json-jackson | JSON 模块Jackson 自动配置 | Jackson2.15.3 |
| continew-starter-api-doc | API 文档模块Knife4j 自动配置 | <a href="https://doc.xiaominfo.com/" target="_blank">Knife4j</a>4.4.0 |
| continew-starter-security | 安全模块密码编码器、数据库字段加密、JSON 脱敏等 | |
| continew-starter-web | Web 模块:跨域、全局异常、错误处理等自动配置 | <a href="https://undertow.io/" target="_blank">Undertow</a>2.3.10.Final<br />TLog1.5.1 |
| continew-starter-log-httptrace-pro | 日志模块Spring Boot Actuator HttpTrace 重置增强版 | |
| continew-starter-storage-local | 存储模块:本地存储 | |
| continew-starter-file-excel | 文件处理模块Excel 相关配置 | <a href="https://easyexcel.opensource.alibaba.com/" target="_blank">Easy Excel</a>3.3.4 |
| continew-starter-captcha-graphic | 验证码模块:图形验证码 | Easy Captcha1.6.2 |
| continew-starter-captcha-behavior | 验证码模块:行为验证码 | AJ-Captcha1.3.0 |
| continew-starter-cache-redisson | 缓存模块Redisson 自动配置 | <a href="https://github.com/redisson/redisson/wiki/Redisson%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D" target="_blank">Redisson</a>3.25.2 |
| continew-starter-data-mybatis-plus | 数据访问模块MyBatis Plus 自动配置 | <a href="https://baomidou.com/" target="_blank">MyBatis Plus</a>3.5.5<br /><a href="https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611" target="_blank">dynamic-datasource-spring-boot-starter</a>4.2.0<br /><a href="https://github.com/p6spy/p6spy" target="_blank">P6Spy</a>3.9.1 |
| continew-starter-auth-satoken | 认证模块SaToken 自动配置 | <a href="https://sa-token.dev33.cn/" target="_blank">Sa-Token</a>1.37.0 |
| continew-starter-auth-justauth | 认证模块JustAuth 自动配置 | <a href="https://justauth.cn/" target="_blank">Just Auth</a>1.16.6 |
| continew-starter-messaging-mail | 消息模块:邮件 | Jakarta Mail1.1.0 |
| continew-starter-messaging-sms | 消息模块:短信 | <a href="https://sms4j.com/" target="_blank">SMS4J</a>3.0.4 |
| continew-starter-extension-crud | 扩展模块BaseController 自定义 CRUD API 封装 | |
### 核心模块
| 模块名称 | 模块说明 | 依赖版本 |
| --------------------- | ------------------------------------ | ------------------------------------------------------------ |
| continew-starter-core | 核心模块:包含线程池、项目等自动配置 | <a href="https://spring.io/projects/spring-boot" target="_blank">Spring Boot</a>3.1.8<br /><a href="https://www.hutool.cn/" target="_blank">Hutool</a>5.8.25<br />mica-ip2region3.1.7 |
### JSON模块
| 模块名称 | 模块说明 | 依赖版本 |
| ----------------------------- | -------------------- | --------------- |
| continew-starter-json-jackson | Jackson 序列化等配置 | Jackson2.15.3 |
### 接口文档
| 模块名称 | 模块说明 | 依赖版本 |
| ------------------------ | ---------------- | ------------------------------------------------------------ |
| continew-starter-api-doc | Knife4j 自动配置 | <a href="https://doc.xiaominfo.com/" target="_blank">Knife4j</a>4.5.0 |
### 安全模块
| 模块名称 | 模块说明 | 依赖版本 |
| ---------------------------------- | ----------------- | -------- |
| continew-starter-security-password | 密码编码器 | |
| continew-starter-security-mask | JSON 脱敏 | |
| continew-starter-security-crypto | 数据库字段加/解密 | |
| continew-starter-security-all | | |
### Web模块
| 模块名称 | 模块说明 | 依赖版本 |
| -------------------- | ---------------------------------- | ------------------------------------------------------------ |
| continew-starter-web | 跨域、全局异常、错误处理等自动配置 | <a href="https://undertow.io/" target="_blank">Undertow</a>2.3.10.Final<br />TLog1.5.1 |
### 日志模块
| 模块名称 | 模块说明 | 依赖版本 |
| ---------------------------------- | ----------------------------------------- | -------- |
| continew-starter-log-httptrace-pro | Spring Boot Actuator HttpTrace 重置增强版 | |
### 存储模块
| 模块名称 | 模块说明 | 依赖版本 |
| ------------------------------ | -------- | -------- |
| continew-starter-storage-local | 本地存储 | |
### 文件处理模块
| 模块名称 | 模块说明 | 依赖版本 |
| --------------------------- | -------------- | ------------------------------------------------------------ |
| continew-starter-file-excel | Excel 相关配置 | <a href="https://easyexcel.opensource.alibaba.com/" target="_blank">Easy Excel</a>3.3.3 |
### 验证码模块
| 模块名称 | 模块说明 | 依赖版本 |
| --------------------------------- | ---------- | ------------------- |
| continew-starter-captcha-graphic | 图形验证码 | Easy Captcha1.6.2 |
| continew-starter-captcha-behavior | 行为验证码 | AJ-Captcha1.3.0 |
### 缓存模块
| 模块名称 | 模块说明 | 依赖版本 |
| ---------------------------------- | --------------------- | ------------------------------------------------------------ |
| continew-starter-cache-redisson | Redisson 自动配置 | <a href="https://github.com/redisson/redisson/wiki/Redisson%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D" target="_blank">Redisson</a>3.26.0 |
| continew-starter-cache-springcache | Spring Cache 自动配置 | |
| continew-starter-cache-jetcache | JetCache 自动配置 | |
### 数据访问模块
| 模块名称 | 模块说明 | 依赖版本 |
| ---------------------------------- | --------------------- | ------------------------------------------------------------ |
| continew-starter-data-mybatis-plus | MyBatis Plus 自动配置 | <a href="https://baomidou.com/" target="_blank">MyBatis Plus</a>3.5.5<br /><a href="https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611" target="_blank">dynamic-datasource-spring-boot-starter</a>4.3.0<br /><a href="https://github.com/p6spy/p6spy" target="_blank">P6Spy</a>3.9.1 |
### 认证模块
| 模块名称 | 模块说明 | 依赖版本 |
| ------------------------------ | ----------------- | ------------------------------------------------------------ |
| continew-starter-auth-satoken | SaToken 自动配置 | <a href="https://sa-token.dev33.cn/" target="_blank">Sa-Token</a>1.37.0 |
| continew-starter-auth-justauth | JustAuth 自动配置 | <a href="https://justauth.cn/" target="_blank">Just Auth</a>1.16.6 |
### 消息模块
| 模块名称 | 模块说明 | 依赖版本 |
| ------------------------------- | -------- | ------------------------------------------------------------ |
| continew-starter-messaging-mail | 邮件 | Jakarta Mail1.1.0 |
| continew-starter-messaging-sms | 短信 | <a href="https://sms4j.com/" target="_blank">SMS4J</a>3.1.1 |
### 扩展模块
| 模块名称 | 模块说明 | 依赖版本 |
| ------------------------------- | --------------------------------------------- | -------- |
| continew-starter-extension-crud | 扩展模块BaseController 自定义 CRUD API 封装 | |
## 贡献代码

View File

@@ -23,5 +23,12 @@
<groupId>com.xkcoding.justauth</groupId>
<artifactId>justauth-spring-boot-starter</artifactId>
</dependency>
<!-- 缓存模块 - Redisson -->
<dependency>
<groupId>top.charles7c.continew</groupId>
<artifactId>continew-starter-cache-redisson</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -25,6 +25,13 @@
<artifactId>sa-token-jwt</artifactId>
</dependency>
<!-- 缓存模块 - Redisson -->
<dependency>
<groupId>top.charles7c.continew</groupId>
<artifactId>continew-starter-cache-redisson</artifactId>
<optional>true</optional>
</dependency>
<!-- Web 模块 -->
<dependency>
<groupId>top.charles7c.continew</groupId>

View File

@@ -21,6 +21,7 @@ import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.extra.spring.SpringUtil;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -58,7 +59,7 @@ public class SaTokenAutoConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(saInterceptor()).addPathPatterns(StringConstants.PATH_PATTERN);
registry.addInterceptor(SpringUtil.getBean(SaInterceptor.class)).addPathPatterns(StringConstants.PATH_PATTERN);
}
/**
@@ -86,6 +87,7 @@ public class SaTokenAutoConfiguration implements WebMvcConfigurer {
*/
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "sa-token.extension", name = "enableJwt", havingValue = "true")
public StpLogic stpLogic() {
return new StpLogicJwtForSimple();
}

View File

@@ -34,6 +34,11 @@ public class SaTokenExtensionProperties {
*/
private boolean enabled = false;
/**
* 启用 JWT
*/
private boolean enableJwt = false;
/**
* 持久层配置
*/
@@ -54,6 +59,14 @@ public class SaTokenExtensionProperties {
this.enabled = enabled;
}
public boolean isEnableJwt() {
return enableJwt;
}
public void setEnableJwt(boolean enableJwt) {
this.enableJwt = enableJwt;
}
public SaTokenDaoProperties getDao() {
return dao;
}

View File

@@ -47,8 +47,6 @@ public class SaTokenDaoConfiguration {
* 自定义持久层实现-默认(内存)
*/
@ConditionalOnMissingBean(SaTokenDao.class)
@ConditionalOnClass(RedisClient.class)
@AutoConfigureBefore(RedissonAutoConfiguration.class)
@ConditionalOnProperty(name = "sa-token.extension.dao.type", havingValue = "default", matchIfMissing = true)
public static class Default {
static {

View File

@@ -19,10 +19,10 @@
</modules>
<dependencies>
<!-- 缓存模块 - Redisson -->
<!-- 核心模块 -->
<dependency>
<groupId>top.charles7c.continew</groupId>
<artifactId>continew-starter-cache-redisson</artifactId>
<artifactId>continew-starter-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -18,7 +18,7 @@ package top.charles7c.continew.starter.cache.redisson.autoconfigure;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.redisson.codec.JsonJacksonCodec;
import org.redisson.config.ClusterServersConfig;
@@ -51,10 +51,11 @@ import java.util.List;
public class RedissonAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(RedissonAutoConfiguration.class);
private final RedissonProperties properties;
private final RedisProperties redisProperties;
private final ObjectMapper objectMapper;
private static final String REDIS_PROTOCOL_PREFIX = "redis://";
private static final String REDISS_PROTOCOL_PREFIX = "rediss://";
public RedissonAutoConfiguration(RedissonProperties properties,
RedisProperties redisProperties,
@@ -68,11 +69,13 @@ public class RedissonAutoConfiguration {
public RedissonAutoConfigurationCustomizer redissonAutoConfigurationCustomizer() {
return config -> {
RedissonProperties.Mode mode = properties.getMode();
String protocol = redisProperties.getSsl().isEnabled() ? "rediss://" : "redis://";
String protocolPrefix = redisProperties.getSsl().isEnabled()
? REDISS_PROTOCOL_PREFIX
: REDIS_PROTOCOL_PREFIX;
switch (mode) {
case CLUSTER -> this.buildClusterModeConfig(config, protocol);
case SENTINEL -> this.buildSentinelModeConfig(config, protocol);
default -> this.buildSingleModeConfig(config, protocol);
case CLUSTER -> this.buildClusterModeConfig(config, protocolPrefix);
case SENTINEL -> this.buildSentinelModeConfig(config, protocolPrefix);
default -> this.buildSingleModeConfig(config, protocolPrefix);
}
// Jackson 处理
config.setCodec(new JsonJacksonCodec(objectMapper));
@@ -83,10 +86,10 @@ public class RedissonAutoConfiguration {
/**
* 构建集群模式配置
*
* @param config 配置
* @param protocol 协议
* @param config 配置
* @param protocolPrefix 协议前缀
*/
private void buildClusterModeConfig(Config config, String protocol) {
private void buildClusterModeConfig(Config config, String protocolPrefix) {
ClusterServersConfig clusterServersConfig = config.useClusterServers();
ClusterServersConfig customClusterServersConfig = properties.getClusterServersConfig();
if (null != customClusterServersConfig) {
@@ -96,9 +99,9 @@ public class RedissonAutoConfiguration {
// 下方配置如果为空,则使用 Redis 的配置
if (CollUtil.isEmpty(clusterServersConfig.getNodeAddresses())) {
List<String> nodeList = redisProperties.getCluster().getNodes();
nodeList.stream().map(node -> protocol + node).forEach(clusterServersConfig::addNodeAddress);
nodeList.stream().map(node -> protocolPrefix + node).forEach(clusterServersConfig::addNodeAddress);
}
if (StrUtil.isBlank(clusterServersConfig.getPassword())) {
if (CharSequenceUtil.isBlank(clusterServersConfig.getPassword())) {
clusterServersConfig.setPassword(redisProperties.getPassword());
}
}
@@ -106,10 +109,10 @@ public class RedissonAutoConfiguration {
/**
* 构建哨兵模式配置
*
* @param config 配置
* @param protocol 协议
* @param config 配置
* @param protocolPrefix 协议前缀
*/
private void buildSentinelModeConfig(Config config, String protocol) {
private void buildSentinelModeConfig(Config config, String protocolPrefix) {
SentinelServersConfig sentinelServersConfig = config.useSentinelServers();
SentinelServersConfig customSentinelServersConfig = properties.getSentinelServersConfig();
if (null != customSentinelServersConfig) {
@@ -119,12 +122,12 @@ public class RedissonAutoConfiguration {
// 下方配置如果为空,则使用 Redis 的配置
if (CollUtil.isEmpty(sentinelServersConfig.getSentinelAddresses())) {
List<String> nodeList = redisProperties.getSentinel().getNodes();
nodeList.stream().map(node -> protocol + node).forEach(sentinelServersConfig::addSentinelAddress);
nodeList.stream().map(node -> protocolPrefix + node).forEach(sentinelServersConfig::addSentinelAddress);
}
if (StrUtil.isBlank(sentinelServersConfig.getPassword())) {
if (CharSequenceUtil.isBlank(sentinelServersConfig.getPassword())) {
sentinelServersConfig.setPassword(redisProperties.getPassword());
}
if (StrUtil.isBlank(sentinelServersConfig.getMasterName())) {
if (CharSequenceUtil.isBlank(sentinelServersConfig.getMasterName())) {
sentinelServersConfig.setMasterName(redisProperties.getSentinel().getMaster());
}
}
@@ -132,10 +135,10 @@ public class RedissonAutoConfiguration {
/**
* 构建单机模式配置
*
* @param config 配置
* @param protocol 协议
* @param config 配置
* @param protocolPrefix 协议前缀
*/
private void buildSingleModeConfig(Config config, String protocol) {
private void buildSingleModeConfig(Config config, String protocolPrefix) {
SingleServerConfig singleServerConfig = config.useSingleServer();
SingleServerConfig customSingleServerConfig = properties.getSingleServerConfig();
if (null != customSingleServerConfig) {
@@ -143,12 +146,12 @@ public class RedissonAutoConfiguration {
}
// 下方配置如果为空,则使用 Redis 的配置
singleServerConfig.setDatabase(redisProperties.getDatabase());
if (StrUtil.isBlank(singleServerConfig.getPassword())) {
if (CharSequenceUtil.isBlank(singleServerConfig.getPassword())) {
singleServerConfig.setPassword(redisProperties.getPassword());
}
if (StrUtil.isBlank(singleServerConfig.getAddress())) {
singleServerConfig.setAddress(protocol + redisProperties.getHost() + StringConstants.COLON + redisProperties
.getPort());
if (CharSequenceUtil.isBlank(singleServerConfig.getAddress())) {
singleServerConfig.setAddress(protocolPrefix + redisProperties
.getHost() + StringConstants.COLON + redisProperties.getPort());
}
}
}

View File

@@ -17,7 +17,7 @@
package top.charles7c.continew.starter.cache.springcache.autoconfigure;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@@ -92,7 +92,7 @@ public class SpringCacheAutoConfiguration implements CachingConfigurer {
@Override
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
String key = StrUtil.toUnderlineCase(method.getName()).toUpperCase();
String key = CharSequenceUtil.toUnderlineCase(method.getName()).toUpperCase();
Map<String, Object> paramMap = MapUtil.newHashMap(params.length);
for (int i = 0; i < params.length; i++) {
paramMap.put(String.valueOf(i), params[i]);

View File

@@ -18,7 +18,7 @@ package top.charles7c.continew.starter.captcha.behavior.autoconfigure;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.anji.captcha.model.common.Const;
import com.anji.captcha.service.CaptchaService;
import com.anji.captcha.service.impl.CaptchaServiceFactory;
@@ -81,8 +81,8 @@ public class BehaviorCaptchaAutoConfiguration {
config.put(Const.CAPTCHA_FONT_TYPE, properties.getFontType());
config.put(Const.CAPTCHA_TYPE, properties.getType().getCodeValue());
config.put(Const.CAPTCHA_INTERFERENCE_OPTIONS, properties.getInterferenceOptions());
config.put(Const.ORIGINAL_PATH_JIGSAW, StrUtil.emptyIfNull(properties.getJigsawBaseMapPath()));
config.put(Const.ORIGINAL_PATH_PIC_CLICK, StrUtil.emptyIfNull(properties.getPicClickBaseMapPath()));
config.put(Const.ORIGINAL_PATH_JIGSAW, CharSequenceUtil.emptyIfNull(properties.getJigsawBaseMapPath()));
config.put(Const.ORIGINAL_PATH_PIC_CLICK, CharSequenceUtil.emptyIfNull(properties.getPicClickBaseMapPath()));
config.put(Const.CAPTCHA_SLIP_OFFSET, properties.getSlipOffset());
config.put(Const.CAPTCHA_AES_STATUS, String.valueOf(properties.getEnableAes()));
config.put(Const.CAPTCHA_WATER_FONT, properties.getWaterFont());
@@ -98,8 +98,8 @@ public class BehaviorCaptchaAutoConfiguration {
config.put(Const.CAPTCHA_FONT_SIZE, properties.getFontSize());
config.put(Const.CAPTCHA_FONT_STYLE, properties.getFontStyle());
config.put(Const.CAPTCHA_WORD_COUNT, 4);
if (StrUtil.startWith(properties.getJigsawBaseMapPath(), "classpath:") || StrUtil.startWith(properties
.getPicClickBaseMapPath(), "classpath:")) {
if (CharSequenceUtil.startWith(properties.getJigsawBaseMapPath(), "classpath:") || CharSequenceUtil
.startWith(properties.getPicClickBaseMapPath(), "classpath:")) {
// 自定义 resources 目录下初始化底图
config.put(Const.CAPTCHA_INIT_ORIGINAL, true);
initializeBaseMap(properties.getJigsawBaseMapPath(), properties.getPicClickBaseMapPath());

View File

@@ -16,9 +16,6 @@
package top.charles7c.continew.starter.captcha.graphic.autoconfigure;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.wf.captcha.base.Captcha;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -27,10 +24,9 @@ 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 top.charles7c.continew.starter.captcha.graphic.core.GraphicCaptchaService;
import top.charles7c.continew.starter.core.constant.PropertiesConstants;
import java.awt.*;
/**
* 图形验证码自动配置
*
@@ -39,24 +35,18 @@ import java.awt.*;
*/
@AutoConfiguration
@EnableConfigurationProperties(GraphicCaptchaProperties.class)
@ConditionalOnProperty(prefix = PropertiesConstants.CAPTCHA_GRAPHIC, name = PropertiesConstants.ENABLED, havingValue = "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);
/**
* 验证码配置
* 验证码服务接口配置
*/
@Bean
@ConditionalOnMissingBean
public Captcha captcha(GraphicCaptchaProperties properties) {
Captcha captcha = ReflectUtil.newInstance(properties.getType().getCaptchaImpl(), properties
.getWidth(), properties.getHeight());
captcha.setLen(properties.getLength());
if (StrUtil.isNotBlank(properties.getFontName())) {
captcha.setFont(new Font(properties.getFontName(), Font.PLAIN, properties.getFontSize()));
}
return captcha;
public GraphicCaptchaService graphicCaptchaService(GraphicCaptchaProperties properties) {
return new GraphicCaptchaService(properties);
}
@PostConstruct

View File

@@ -32,7 +32,7 @@ public class GraphicCaptchaProperties {
/**
* 是否启用图形验证码
*/
private boolean enabled = false;
private boolean enabled = true;
/**
* 类型

View File

@@ -0,0 +1,54 @@
/*
* 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.charles7c.continew.starter.captcha.graphic.core;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.wf.captcha.base.Captcha;
import top.charles7c.continew.starter.captcha.graphic.autoconfigure.GraphicCaptchaProperties;
import java.awt.*;
/**
* 图形验证码服务接口
*
* @author Charles7c
* @since 1.4.0
*/
public class GraphicCaptchaService {
private final GraphicCaptchaProperties properties;
public GraphicCaptchaService(GraphicCaptchaProperties properties) {
this.properties = properties;
}
/**
* 获取验证码实例
*
* @return 验证码实例
*/
public Captcha getCaptcha() {
Captcha captcha = ReflectUtil.newInstance(properties.getType().getCaptchaImpl(), properties
.getWidth(), properties.getHeight());
captcha.setLen(properties.getLength());
if (CharSequenceUtil.isNotBlank(properties.getFontName())) {
captcha.setFont(new Font(properties.getFontName(), Font.PLAIN, properties.getFontSize()));
}
return captcha;
}
}

View File

@@ -52,12 +52,17 @@ public class PropertiesConstants {
/**
* 安全配置
*/
public static final String SECURITY = CONTINEW_STARTER + ".security";
public static final String SECURITY = CONTINEW_STARTER + StringConstants.DOT + "security";
/**
* 密码编解码配置
*/
public static final String PASSWORD = SECURITY + ".password";
public static final String PASSWORD = SECURITY + StringConstants.DOT + "password";
/**
* 加/解密配置
*/
public static final String CRYPTO = SECURITY + StringConstants.DOT + "crypto";
/**
* Web 配置

View File

@@ -104,6 +104,11 @@ public class StringConstants {
*/
public static final char C_AT = CharPool.AT;
/**
* 字符常量:星号 {@code '*'}
*/
public static final char C_ASTERISK = '*';
/**
* 字符串常量:制表符 {@code "\t"}
*/

View File

@@ -16,7 +16,7 @@
package top.charles7c.continew.starter.core.util;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
@@ -43,7 +43,7 @@ public class GeneralPropertySourceFactory extends DefaultPropertySourceFactory {
EncodedResource encodedResource) throws IOException {
Resource resource = encodedResource.getResource();
String resourceName = resource.getFilename();
if (StrUtil.isNotBlank(resourceName) && StrUtil.endWithAny(resourceName, ".yml", ".yaml")) {
if (CharSequenceUtil.isNotBlank(resourceName) && CharSequenceUtil.endWithAny(resourceName, ".yml", ".yaml")) {
return new YamlPropertySourceLoader().load(resourceName, resource).get(0);
}
return super.createPropertySource(name, encodedResource);

View File

@@ -18,7 +18,7 @@ package top.charles7c.continew.starter.core.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.HtmlUtil;
import net.dreamlu.mica.ip2region.core.Ip2regionSearcher;
@@ -52,7 +52,7 @@ public class IpUtils {
IpInfo ipInfo = ip2regionSearcher.memorySearch(ip);
if (null != ipInfo) {
Set<String> regionSet = CollUtil.newLinkedHashSet(ipInfo.getAddress(), ipInfo.getIsp());
regionSet.removeIf(StrUtil::isBlank);
regionSet.removeIf(CharSequenceUtil::isBlank);
return String.join(StringConstants.SPACE, regionSet);
}
return null;

View File

@@ -17,7 +17,7 @@
package top.charles7c.continew.starter.core.util.db;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import cn.hutool.db.meta.Column;
@@ -61,7 +61,7 @@ public class MetaUtils {
String querySql = "SHOW TABLE STATUS";
List<Entity> tableEntityList;
Db db = Db.use(dataSource);
if (StrUtil.isNotBlank(tableName)) {
if (CharSequenceUtil.isNotBlank(tableName)) {
tableEntityList = db.query(String.format("%s WHERE NAME = ?", querySql), tableName);
} else {
tableEntityList = db.query(querySql);

View File

@@ -16,7 +16,7 @@
package top.charles7c.continew.starter.core.util.validate;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import top.charles7c.continew.starter.core.constant.StringConstants;
import top.charles7c.continew.starter.core.exception.BusinessException;
@@ -45,7 +45,7 @@ public class CheckUtils extends Validator {
* @param fieldValue 字段值
*/
public static void throwIfNotExists(Object obj, String entityName, String fieldName, Object fieldValue) {
String message = String.format("%s 为 [%s] 的 %s 记录已不存在", fieldName, fieldValue, StrUtil
String message = String.format("%s 为 [%s] 的 %s 记录已不存在", fieldName, fieldValue, CharSequenceUtil
.replace(entityName, "DO", StringConstants.EMPTY));
throwIfNull(obj, message, EXCEPTION_TYPE);
}
@@ -58,7 +58,7 @@ public class CheckUtils extends Validator {
* @param params 参数值
*/
public static void throwIfNull(Object obj, String template, Object... params) {
throwIfNull(obj, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIfNull(obj, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -69,7 +69,7 @@ public class CheckUtils extends Validator {
* @param params 参数值
*/
public static void throwIfNotNull(Object obj, String template, Object... params) {
throwIfNotNull(obj, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIfNotNull(obj, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -93,7 +93,7 @@ public class CheckUtils extends Validator {
* @param params 参数值
*/
public static void throwIfEmpty(Object obj, String template, Object... params) {
throwIfEmpty(obj, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIfEmpty(obj, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -104,7 +104,7 @@ public class CheckUtils extends Validator {
* @param params 参数值
*/
public static void throwIfNotEmpty(Object obj, String template, Object... params) {
throwIfNotEmpty(obj, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIfNotEmpty(obj, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -115,7 +115,7 @@ public class CheckUtils extends Validator {
* @param params 参数值
*/
public static void throwIfBlank(CharSequence str, String template, Object... params) {
throwIfBlank(str, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIfBlank(str, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -126,7 +126,7 @@ public class CheckUtils extends Validator {
* @param params 参数值
*/
public static void throwIfNotBlank(CharSequence str, String template, Object... params) {
throwIfNotBlank(str, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIfNotBlank(str, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -138,7 +138,7 @@ public class CheckUtils extends Validator {
* @param params 参数值
*/
public static void throwIfEqual(Object obj1, Object obj2, String template, Object... params) {
throwIfEqual(obj1, obj2, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIfEqual(obj1, obj2, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -150,7 +150,7 @@ public class CheckUtils extends Validator {
* @param params 参数值
*/
public static void throwIfNotEqual(Object obj1, Object obj2, String template, Object... params) {
throwIfNotEqual(obj1, obj2, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIfNotEqual(obj1, obj2, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -162,7 +162,7 @@ public class CheckUtils extends Validator {
* @param params 参数值
*/
public static void throwIfEqualIgnoreCase(CharSequence str1, CharSequence str2, String template, Object... params) {
throwIfEqualIgnoreCase(str1, str2, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIfEqualIgnoreCase(str1, str2, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -177,7 +177,7 @@ public class CheckUtils extends Validator {
CharSequence str2,
String template,
Object... params) {
throwIfNotEqualIgnoreCase(str1, str2, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIfNotEqualIgnoreCase(str1, str2, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -188,7 +188,7 @@ public class CheckUtils extends Validator {
* @param params 参数值
*/
public static void throwIf(boolean condition, String template, Object... params) {
throwIf(condition, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIf(condition, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -199,6 +199,6 @@ public class CheckUtils extends Validator {
* @param params 参数值
*/
public static void throwIf(BooleanSupplier conditionSupplier, String template, Object... params) {
throwIf(conditionSupplier, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIf(conditionSupplier, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
}

View File

@@ -16,7 +16,7 @@
package top.charles7c.continew.starter.core.util.validate;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import top.charles7c.continew.starter.core.exception.BadRequestException;
import java.util.function.BooleanSupplier;
@@ -43,7 +43,7 @@ public class ValidationUtils extends Validator {
* @param params 参数值
*/
public static void throwIfNull(Object obj, String template, Object... params) {
throwIfNull(obj, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIfNull(obj, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -54,7 +54,7 @@ public class ValidationUtils extends Validator {
* @param params 参数值
*/
public static void throwIfNotNull(Object obj, String template, Object... params) {
throwIfNotNull(obj, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIfNotNull(obj, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -65,7 +65,7 @@ public class ValidationUtils extends Validator {
* @param params 参数值
*/
public static void throwIfEmpty(Object obj, String template, Object... params) {
throwIfEmpty(obj, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIfEmpty(obj, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -76,7 +76,7 @@ public class ValidationUtils extends Validator {
* @param params 参数值
*/
public static void throwIfNotEmpty(Object obj, String template, Object... params) {
throwIfNotEmpty(obj, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIfNotEmpty(obj, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -87,7 +87,7 @@ public class ValidationUtils extends Validator {
* @param params 参数值
*/
public static void throwIfBlank(CharSequence str, String template, Object... params) {
throwIfBlank(str, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIfBlank(str, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -98,7 +98,7 @@ public class ValidationUtils extends Validator {
* @param params 参数值
*/
public static void throwIfNotBlank(CharSequence str, String template, Object... params) {
throwIfNotBlank(str, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIfNotBlank(str, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -110,7 +110,7 @@ public class ValidationUtils extends Validator {
* @param params 参数值
*/
public static void throwIfEqual(Object obj1, Object obj2, String template, Object... params) {
throwIfEqual(obj1, obj2, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIfEqual(obj1, obj2, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -122,7 +122,7 @@ public class ValidationUtils extends Validator {
* @param params 参数值
*/
public static void throwIfNotEqual(Object obj1, Object obj2, String template, Object... params) {
throwIfNotEqual(obj1, obj2, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIfNotEqual(obj1, obj2, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -134,7 +134,7 @@ public class ValidationUtils extends Validator {
* @param params 参数值
*/
public static void throwIfEqualIgnoreCase(CharSequence str1, CharSequence str2, String template, Object... params) {
throwIfEqualIgnoreCase(str1, str2, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIfEqualIgnoreCase(str1, str2, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -149,7 +149,7 @@ public class ValidationUtils extends Validator {
CharSequence str2,
String template,
Object... params) {
throwIfNotEqualIgnoreCase(str1, str2, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIfNotEqualIgnoreCase(str1, str2, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -160,7 +160,7 @@ public class ValidationUtils extends Validator {
* @param params 参数值
*/
public static void throwIf(boolean condition, String template, Object... params) {
throwIf(condition, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIf(condition, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
/**
@@ -171,6 +171,6 @@ public class ValidationUtils extends Validator {
* @param params 参数值
*/
public static void throwIf(BooleanSupplier conditionSupplier, String template, Object... params) {
throwIf(conditionSupplier, StrUtil.format(template, params), EXCEPTION_TYPE);
throwIf(conditionSupplier, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
}
}

View File

@@ -16,9 +16,9 @@
package top.charles7c.continew.starter.core.util.validate;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -90,7 +90,7 @@ public class Validator {
protected static void throwIfBlank(CharSequence str,
String message,
Class<? extends RuntimeException> exceptionType) {
throwIf(StrUtil.isBlank(str), message, exceptionType);
throwIf(CharSequenceUtil.isBlank(str), message, exceptionType);
}
/**
@@ -103,7 +103,7 @@ public class Validator {
protected static void throwIfNotBlank(CharSequence str,
String message,
Class<? extends RuntimeException> exceptionType) {
throwIf(StrUtil.isNotBlank(str), message, exceptionType);
throwIf(CharSequenceUtil.isNotBlank(str), message, exceptionType);
}
/**
@@ -148,7 +148,7 @@ public class Validator {
CharSequence str2,
String message,
Class<? extends RuntimeException> exceptionType) {
throwIf(StrUtil.equalsIgnoreCase(str1, str2), message, exceptionType);
throwIf(CharSequenceUtil.equalsIgnoreCase(str1, str2), message, exceptionType);
}
/**
@@ -163,7 +163,7 @@ public class Validator {
CharSequence str2,
String message,
Class<? extends RuntimeException> exceptionType) {
throwIf(!StrUtil.equalsIgnoreCase(str1, str2), message, exceptionType);
throwIf(!CharSequenceUtil.equalsIgnoreCase(str1, str2), message, exceptionType);
}
/**

View File

@@ -31,10 +31,11 @@
<artifactId>p6spy</artifactId>
</dependency>
<!-- CosId通用、灵活、高性能的分布式 ID 生成器) -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<groupId>me.ahoo.cosid</groupId>
<artifactId>cosid-spring-boot-starter</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -18,6 +18,8 @@ package top.charles7c.continew.starter.data.mybatis.plus.autoconfigure;
import com.baomidou.mybatisplus.annotation.DbType;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import top.charles7c.continew.starter.data.mybatis.plus.autoconfigure.idgenerator.MyBatisPlusIdGeneratorProperties;
/**
* MyBatis Plus 扩展配置属性
@@ -41,6 +43,12 @@ public class MyBatisPlusExtensionProperties {
*/
private String mapperPackage;
/**
* ID 生成器
*/
@NestedConfigurationProperty
private MyBatisPlusIdGeneratorProperties idGenerator;
/**
* 数据权限插件配置
*/
@@ -144,6 +152,14 @@ public class MyBatisPlusExtensionProperties {
this.mapperPackage = mapperPackage;
}
public MyBatisPlusIdGeneratorProperties getIdGenerator() {
return idGenerator;
}
public void setIdGenerator(MyBatisPlusIdGeneratorProperties idGenerator) {
this.idGenerator = idGenerator;
}
public DataPermissionProperties getDataPermission() {
return dataPermission;
}

View File

@@ -16,10 +16,7 @@
package top.charles7c.continew.starter.data.mybatis.plus.autoconfigure;
import cn.hutool.core.net.NetUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
@@ -34,10 +31,13 @@ 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.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import top.charles7c.continew.starter.core.constant.PropertiesConstants;
import top.charles7c.continew.starter.core.util.GeneralPropertySourceFactory;
import top.charles7c.continew.starter.data.mybatis.plus.autoconfigure.idgenerator.MyBatisPlusIdGeneratorConfiguration;
import top.charles7c.continew.starter.data.mybatis.plus.datapermission.DataPermissionFilter;
import top.charles7c.continew.starter.data.mybatis.plus.datapermission.DataPermissionHandlerImpl;
@@ -81,6 +81,15 @@ public class MybatisPlusAutoConfiguration {
return interceptor;
}
/**
* ID 生成器配置
*/
@Configuration
@Import({MyBatisPlusIdGeneratorConfiguration.Default.class, MyBatisPlusIdGeneratorConfiguration.CosId.class,
MyBatisPlusIdGeneratorConfiguration.Custom.class})
protected static class MyBatisPlusIdGeneratorAutoConfiguration {
}
/**
* 数据权限处理器
*/
@@ -91,18 +100,6 @@ public class MybatisPlusAutoConfiguration {
return new DataPermissionHandlerImpl(dataPermissionFilter);
}
/**
* ID 生成器配置仅在主键类型idType配置为 ASSIGN_ID 或 ASSIGN_UUID 时有效)
* <p>
* 使用网卡信息绑定雪花生成器,防止集群雪花 ID 重复
* </p>
*/
@Bean
@ConditionalOnMissingBean
public IdentifierGenerator identifierGenerator() {
return new DefaultIdentifierGenerator(NetUtil.getLocalhost());
}
/**
* 分页插件配置(<a href="https://baomidou.com/pages/97710a/#paginationinnerinterceptor">PaginationInnerInterceptor</a>
*/

View File

@@ -0,0 +1,42 @@
/*
* 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.charles7c.continew.starter.data.mybatis.plus.autoconfigure.idgenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import me.ahoo.cosid.snowflake.SnowflakeId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
/**
* MyBatis Plus ID 生成器 - CosId
*
* @author Charles7c
* @since 1.4.0
*/
public class MyBatisPlusCosIdIdentifierGenerator implements IdentifierGenerator {
@Qualifier("__share__SnowflakeId")
@Lazy
@Autowired
private SnowflakeId snowflakeId;
@Override
public Number nextId(Object entity) {
return snowflakeId.generate();
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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.charles7c.continew.starter.data.mybatis.plus.autoconfigure.idgenerator;
import cn.hutool.core.net.NetUtil;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import me.ahoo.cosid.IdGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.core.ResolvableType;
/**
* MyBatis ID 生成器配置
*
* @author Charles7c
* @since 1.4.0
*/
public class MyBatisPlusIdGeneratorConfiguration {
private static final Logger log = LoggerFactory.getLogger(MyBatisPlusIdGeneratorConfiguration.class);
private MyBatisPlusIdGeneratorConfiguration() {
}
/**
* 自定义 ID 生成器-默认(雪花算法,使用网卡信息绑定雪花生成器,防止集群雪花 ID 重复)
*/
@ConditionalOnMissingBean(IdentifierGenerator.class)
@ConditionalOnProperty(name = "mybatis-plus.extension.id-generator.type", havingValue = "default", matchIfMissing = true)
public static class Default {
static {
log.debug("[ContiNew Starter] - Auto Configuration 'MyBatis Plus-IdGenerator-Default' completed initialization.");
}
@Bean
public IdentifierGenerator identifierGenerator() {
return new DefaultIdentifierGenerator(NetUtil.getLocalhost());
}
}
/**
* 自定义 ID 生成器-CosId
*/
@ConditionalOnMissingBean(IdentifierGenerator.class)
@ConditionalOnClass(IdGenerator.class)
@ConditionalOnProperty(name = "mybatis-plus.extension.id-generator.type", havingValue = "cosid")
public static class CosId {
static {
log.debug("[ContiNew Starter] - Auto Configuration 'MyBatis Plus-IdGenerator-CosId' completed initialization.");
}
@Bean
public IdentifierGenerator identifierGenerator() {
return new MyBatisPlusCosIdIdentifierGenerator();
}
}
/**
* 自定义 ID 生成器
*/
@ConditionalOnProperty(name = "mybatis-plus.extension.id-generator.type", havingValue = "custom")
public static class Custom {
@Bean
@ConditionalOnMissingBean
public IdentifierGenerator identifierGenerator() {
if (log.isErrorEnabled()) {
log.error("Consider defining a bean of type '{}' in your configuration.", ResolvableType
.forClass(IdentifierGenerator.class));
}
throw new NoSuchBeanDefinitionException(IdentifierGenerator.class);
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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.charles7c.continew.starter.data.mybatis.plus.autoconfigure.idgenerator;
import top.charles7c.continew.starter.data.mybatis.plus.enums.MyBatisPlusIdGeneratorType;
/**
* MyBatis ID 生成器配置属性
*
* @author Charles7c
* @since 1.4.0
*/
public class MyBatisPlusIdGeneratorProperties {
/**
* ID 生成器类型
*/
private MyBatisPlusIdGeneratorType type = MyBatisPlusIdGeneratorType.DEFAULT;
public MyBatisPlusIdGeneratorType getType() {
return type;
}
public void setType(MyBatisPlusIdGeneratorType type) {
this.type = type;
}
}

View File

@@ -16,18 +16,9 @@
package top.charles7c.continew.starter.data.mybatis.plus.datapermission;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Set;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.charles7c.continew.starter.core.constant.StringConstants;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Function;
import net.sf.jsqlparser.expression.LongValue;
@@ -42,6 +33,13 @@ import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
import net.sf.jsqlparser.statement.select.SubSelect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.charles7c.continew.starter.core.constant.StringConstants;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Set;
/**
* 数据权限处理器实现类
@@ -69,7 +67,7 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
for (Method method : methodArr) {
DataPermission dataPermission = method.getAnnotation(DataPermission.class);
String name = method.getName();
if (null == dataPermission || !StrUtil.equalsAny(methodName, name, name + "_COUNT")) {
if (null == dataPermission || !CharSequenceUtil.equalsAny(methodName, name, name + "_COUNT")) {
continue;
}
if (dataPermissionFilter.isFilter()) {

View File

@@ -0,0 +1,41 @@
/*
* 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.charles7c.continew.starter.data.mybatis.plus.enums;
/**
* MyBatis ID 生成器类型枚举
*
* @author Charles7c
* @since 1.4.0
*/
public enum MyBatisPlusIdGeneratorType {
/**
* 默认
*/
DEFAULT,
/**
* CosId
*/
COSID,
/**
* 自定义
*/
CUSTOM
}

View File

@@ -21,7 +21,7 @@ import org.slf4j.LoggerFactory;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import top.charles7c.continew.starter.core.exception.BadRequestException;
import top.charles7c.continew.starter.core.util.ReflectUtils;
@@ -117,7 +117,7 @@ public class QueryWrapperHelper {
// 没有 @Query 注解,默认等值查询
Query queryAnnotation = field.getAnnotation(Query.class);
if (null == queryAnnotation) {
return Collections.singletonList(q -> q.eq(StrUtil.toUnderlineCase(fieldName), fieldValue));
return Collections.singletonList(q -> q.eq(CharSequenceUtil.toUnderlineCase(fieldName), fieldValue));
}
// 解析单列查询
QueryType queryType = queryAnnotation.type();
@@ -125,7 +125,7 @@ public class QueryWrapperHelper {
final int columnLength = ArrayUtil.length(columns);
List<Consumer<QueryWrapper<R>>> consumers = new ArrayList<>(columnLength);
if (columnLength <= 1) {
String columnName = columnLength == 1 ? columns[0] : StrUtil.toUnderlineCase(fieldName);
String columnName = columnLength == 1 ? columns[0] : CharSequenceUtil.toUnderlineCase(fieldName);
parse(queryType, columnName, fieldValue, consumers);
return consumers;
}

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.1.7</version>
<version>3.1.8</version>
<relativePath/>
</parent>
@@ -53,30 +53,31 @@
<properties>
<!-- 项目版本号 -->
<revision>1.3.0</revision>
<revision>1.4.0</revision>
<sa-token.version>1.37.0</sa-token.version>
<just-auth.version>1.16.6</just-auth.version>
<mybatis-plus.version>3.5.5</mybatis-plus.version>
<dynamic-datasource.version>4.2.0</dynamic-datasource.version>
<dynamic-datasource.version>4.3.0</dynamic-datasource.version>
<p6spy.version>3.9.1</p6spy.version>
<jetcache.version>2.7.4</jetcache.version>
<redisson.version>3.25.2</redisson.version>
<sms4j.version>3.0.4</sms4j.version>
<jetcache.version>2.7.5</jetcache.version>
<redisson.version>3.26.0</redisson.version>
<cosid.version>2.6.5</cosid.version>
<sms4j.version>3.1.1</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.3</easy-excel.version>
<nashorn.version>15.4</nashorn.version>
<x-file-storage.version>2.0.0</x-file-storage.version>
<aws-s3.version>1.12.626</aws-s3.version>
<crane4j.version>2.4.0</crane4j.version>
<knife4j.version>4.4.0</knife4j.version>
<x-file-storage.version>2.1.0</x-file-storage.version>
<aws-s3.version>1.12.651</aws-s3.version>
<crane4j.version>2.5.0</crane4j.version>
<knife4j.version>4.5.0</knife4j.version>
<tlog.version>1.5.1</tlog.version>
<ttl.version>2.14.4</ttl.version>
<ip2region.version>3.1.6</ip2region.version>
<hutool.version>5.8.24</hutool.version>
<ip2region.version>3.1.7</ip2region.version>
<hutool.version>5.8.25</hutool.version>
<!-- Maven Plugin Versions -->
<flatten.version>1.5.0</flatten.version>
<spotless.version>2.40.0</spotless.version>
<flatten.version>1.6.0</flatten.version>
<spotless.version>2.43.0</spotless.version>
<sonar.version>3.9.1.2184</sonar.version>
</properties>
@@ -120,6 +121,11 @@
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-core</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- Dynamic Datasource基于 Spring Boot 的快速集成多数据源的启动器) -->
<dependency>
@@ -169,6 +175,23 @@
<version>${redisson.version}</version>
</dependency>
<!-- CosId通用、灵活、高性能的分布式 ID 生成器) -->
<dependency>
<groupId>me.ahoo.cosid</groupId>
<artifactId>cosid-spring-boot-starter</artifactId>
<version>${cosid.version}</version>
</dependency>
<dependency>
<groupId>me.ahoo.cosid</groupId>
<artifactId>cosid-spring-redis</artifactId>
<version>${cosid.version}</version>
</dependency>
<dependency>
<groupId>me.ahoo.cosid</groupId>
<artifactId>cosid-jdbc</artifactId>
<version>${cosid.version}</version>
</dependency>
<!-- SMS4J短信聚合框架轻松集成多家短信服务解决接入多个短信 SDK 的繁琐流程) -->
<dependency>
<groupId>org.dromara.sms4j</groupId>
@@ -382,6 +405,27 @@
<version>${revision}</version>
</dependency>
<!-- 安全模块 - 统一模块 -->
<dependency>
<groupId>top.charles7c.continew</groupId>
<artifactId>continew-starter-security-all</artifactId>
<version>${revision}</version>
</dependency>
<!-- 安全模块 - 加密 -->
<dependency>
<groupId>top.charles7c.continew</groupId>
<artifactId>continew-starter-security-crypto</artifactId>
<version>${revision}</version>
</dependency>
<!-- 安全模块 - 脱敏 -->
<dependency>
<groupId>top.charles7c.continew</groupId>
<artifactId>continew-starter-security-mask</artifactId>
<version>${revision}</version>
</dependency>
<!-- 安全模块 - 密码编码器 -->
<dependency>
<groupId>top.charles7c.continew</groupId>

View File

@@ -18,7 +18,7 @@ package top.charles7c.continew.starter.extension.crud.controller;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
@@ -184,7 +184,8 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
private void checkPermission(Api api) {
CrudRequestMapping crudRequestMapping = this.getClass().getDeclaredAnnotation(CrudRequestMapping.class);
String path = crudRequestMapping.value();
String permissionPrefix = String.join(StringConstants.COLON, StrUtil.splitTrim(path, StringConstants.SLASH));
String permissionPrefix = String.join(StringConstants.COLON, CharSequenceUtil
.splitTrim(path, StringConstants.SLASH));
StpUtil.checkPermission(String.format("%s:%s", permissionPrefix, api.name().toLowerCase()));
}
}

View File

@@ -17,7 +17,7 @@
package top.charles7c.continew.starter.extension.crud.model.query;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -77,7 +77,7 @@ public class PageQuery extends SortQuery {
for (Sort.Order order : pageSort) {
OrderItem orderItem = new OrderItem();
orderItem.setAsc(order.isAscending());
orderItem.setColumn(StrUtil.toUnderlineCase(order.getProperty()));
orderItem.setColumn(CharSequenceUtil.toUnderlineCase(order.getProperty()));
mybatisPage.addOrder(orderItem);
}
}

View File

@@ -17,7 +17,7 @@
package top.charles7c.continew.starter.extension.crud.model.query;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.data.domain.Sort;
import top.charles7c.continew.starter.core.constant.StringConstants;
@@ -56,10 +56,10 @@ public class SortQuery implements Serializable {
}
List<Sort.Order> orders = new ArrayList<>(sort.length);
if (StrUtil.contains(sort[0], StringConstants.COMMA)) {
if (CharSequenceUtil.contains(sort[0], StringConstants.COMMA)) {
// e.g "sort=createTime,desc&sort=name,asc"
for (String s : sort) {
List<String> sortList = StrUtil.splitTrim(s, StringConstants.COMMA);
List<String> sortList = CharSequenceUtil.splitTrim(s, StringConstants.COMMA);
Sort.Order order = new Sort.Order(Sort.Direction.valueOf(sortList.get(1).toUpperCase()), sortList
.get(0));
orders.add(order);

View File

@@ -34,6 +34,6 @@ public interface CommonUserService {
* @param id ID
* @return 昵称
*/
@ContainerMethod(namespace = ContainerPool.USER_NICKNAME, type = MappingType.NONE)
@ContainerMethod(namespace = ContainerPool.USER_NICKNAME, type = MappingType.ORDER_OF_KEYS)
String getNicknameById(Long id);
}

View File

@@ -25,7 +25,7 @@ import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -107,15 +107,15 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseDO,
// 构建树
return TreeUtils.build(list, treeNodeConfig, (node, tree) -> {
// 转换器
tree.setId(ReflectUtil.invoke(node, StrUtil.genGetter(treeField.value())));
tree.setParentId(ReflectUtil.invoke(node, StrUtil.genGetter(treeField.parentIdKey())));
tree.setName(ReflectUtil.invoke(node, StrUtil.genGetter(treeField.nameKey())));
tree.setWeight(ReflectUtil.invoke(node, StrUtil.genGetter(treeField.weightKey())));
tree.setId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.value())));
tree.setParentId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.parentIdKey())));
tree.setName(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.nameKey())));
tree.setWeight(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.weightKey())));
if (!isSimple) {
List<Field> fieldList = ReflectUtils.getNonStaticFields(listClass);
fieldList.removeIf(f -> StrUtil.containsAnyIgnoreCase(f.getName(), treeField.value(), treeField
fieldList.removeIf(f -> CharSequenceUtil.containsAnyIgnoreCase(f.getName(), treeField.value(), treeField
.parentIdKey(), treeField.nameKey(), treeField.weightKey(), treeField.childrenKey()));
fieldList.forEach(f -> tree.putExtra(f.getName(), ReflectUtil.invoke(node, StrUtil.genGetter(f
fieldList.forEach(f -> tree.putExtra(f.getName(), ReflectUtil.invoke(node, CharSequenceUtil.genGetter(f
.getName()))));
}
});
@@ -224,7 +224,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseDO,
String checkProperty;
// 携带表别名则获取 . 后面的字段名
if (property.contains(StringConstants.DOT)) {
checkProperty = CollUtil.getLast(StrUtil.split(property, StringConstants.DOT));
checkProperty = CollUtil.getLast(CharSequenceUtil.split(property, StringConstants.DOT));
} else {
checkProperty = property;
}
@@ -232,7 +232,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseDO,
.filter(field -> checkProperty.equals(field.getName()))
.findFirst();
ValidationUtils.throwIf(optional.isEmpty(), "无效的排序字段 [{}]", property);
queryWrapper.orderBy(true, order.isAscending(), StrUtil.toUnderlineCase(property));
queryWrapper.orderBy(true, order.isAscending(), CharSequenceUtil.toUnderlineCase(property));
}
}
}
@@ -267,6 +267,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseDO,
* @param req 创建信息
*/
protected void beforeAdd(C req) {
/* 新增前置处理 */
}
/**
@@ -276,6 +277,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseDO,
* @param id ID
*/
protected void beforeUpdate(C req, Long id) {
/* 修改前置处理 */
}
/**
@@ -284,6 +286,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseDO,
* @param ids ID 列表
*/
protected void beforeDelete(List<Long> ids) {
/* 删除前置处理 */
}
/**
@@ -293,6 +296,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseDO,
* @param entity 实体信息
*/
protected void afterAdd(C req, T entity) {
/* 新增后置处理 */
}
/**
@@ -302,6 +306,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseDO,
* @param entity 实体信息
*/
protected void afterUpdate(C req, T entity) {
/* 修改后置处理 */
}
/**
@@ -309,7 +314,8 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseDO,
*
* @param ids ID 列表
*/
private void afterDelete(List<Long> ids) {
protected void afterDelete(List<Long> ids) {
/* 删除后置处理 */
}
/**

View File

@@ -16,6 +16,8 @@
package top.charles7c.continew.starter.log.common.annotation;
import top.charles7c.continew.starter.log.common.enums.Include;
import java.lang.annotation.*;
/**
@@ -46,6 +48,16 @@ public @interface Log {
*/
String module() default "";
/**
* 包含信息(在全局配置基础上扩展包含信息)
*/
Include[] includes() default {};
/**
* 排除信息(在全局配置基础上减少包含信息)
*/
Include[] excludes() default {};
/**
* 是否忽略日志记录(用于接口方法或类上)
*/

View File

@@ -16,9 +16,8 @@
package top.charles7c.continew.starter.log.common.model;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import org.springframework.http.HttpHeaders;
import top.charles7c.continew.starter.core.util.ExceptionUtils;
import top.charles7c.continew.starter.core.util.IpUtils;
import top.charles7c.continew.starter.log.common.enums.Include;
import top.charles7c.continew.starter.web.util.ServletUtils;
@@ -91,8 +90,16 @@ public class LogRequest {
this.param = request.getParam();
}
this.address = (includes.contains(Include.IP_ADDRESS)) ? IpUtils.getAddress(this.ip) : null;
String userAgentString = ExceptionUtils.exToNull(() -> this.headers.get(HttpHeaders.USER_AGENT));
if (StrUtil.isNotBlank(userAgentString)) {
if (null == this.headers) {
return;
}
String userAgentString = this.headers.entrySet()
.stream()
.filter(h -> HttpHeaders.USER_AGENT.equalsIgnoreCase(h.getKey()))
.map(Map.Entry::getValue)
.findFirst()
.orElse(null);
if (CharSequenceUtil.isNotBlank(userAgentString)) {
this.browser = (includes.contains(Include.BROWSER)) ? ServletUtils.getBrowser(userAgentString) : null;
this.os = (includes.contains(Include.OS)) ? ServletUtils.getOs(userAgentString) : null;
}

View File

@@ -30,6 +30,6 @@ import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@ConditionalOnProperty(prefix = PropertiesConstants.LOG, name = PropertiesConstants.ENABLED, havingValue = "true")
@ConditionalOnProperty(prefix = PropertiesConstants.LOG, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
public @interface ConditionalOnEnabledLog {
}

View File

@@ -35,7 +35,7 @@ public class LogProperties {
/**
* 是否启用日志
*/
private boolean enabled = false;
private boolean enabled = true;
/**
* 是否打印日志,开启后可打印访问日志(类似于 Nginx access log
@@ -45,7 +45,7 @@ public class LogProperties {
/**
* 包含信息
*/
private Set<Include> include = new HashSet<>(Include.defaultIncludes());
private Set<Include> includes = new HashSet<>(Include.defaultIncludes());
public boolean isEnabled() {
return enabled;
@@ -63,11 +63,11 @@ public class LogProperties {
isPrint = print;
}
public Set<Include> getInclude() {
return include;
public Set<Include> getIncludes() {
return includes;
}
public void setInclude(Set<Include> include) {
this.include = include;
public void setIncludes(Set<Include> includes) {
this.includes = includes;
}
}

View File

@@ -105,7 +105,7 @@ public class LogFilter extends OncePerRequestFilter implements Ordered {
* @return truefalse
*/
private boolean isRequestWrapper(HttpServletRequest request) {
Set<Include> includeSet = logProperties.getInclude();
Set<Include> includeSet = logProperties.getIncludes();
return !(request instanceof ContentCachingRequestWrapper) && (includeSet
.contains(Include.REQUEST_BODY) || includeSet.contains(Include.REQUEST_PARAM));
}
@@ -117,7 +117,7 @@ public class LogFilter extends OncePerRequestFilter implements Ordered {
* @return truefalse
*/
private boolean isResponseWrapper(HttpServletResponse response) {
Set<Include> includeSet = logProperties.getInclude();
Set<Include> includeSet = logProperties.getIncludes();
return !(response instanceof ContentCachingResponseWrapper) && (includeSet
.contains(Include.RESPONSE_BODY) || includeSet.contains(Include.RESPONSE_PARAM));
}

View File

@@ -16,7 +16,7 @@
package top.charles7c.continew.starter.log.httptracepro.handler;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.ttl.TransmittableThreadLocal;
import io.swagger.v3.oas.annotations.Hidden;
@@ -83,18 +83,20 @@ public class LogInterceptor implements HandlerInterceptor {
return;
}
timestampTtl.remove();
Set<Include> includeSet = logProperties.getInclude();
try {
HandlerMethod handlerMethod = (HandlerMethod)handler;
Log methodLog = handlerMethod.getMethodAnnotation(Log.class);
Log classLog = handlerMethod.getBeanType().getDeclaredAnnotation(Log.class);
Set<Include> includeSet = this.getIncludes(methodLog, classLog);
LogRecord finishedLogRecord = startedLogRecord.finish(new RecordableServletHttpResponse(response, response
.getStatus()), includeSet);
HandlerMethod handlerMethod = (HandlerMethod)handler;
// 记录日志描述
if (includeSet.contains(Include.DESCRIPTION)) {
this.logDescription(finishedLogRecord, handlerMethod);
this.logDescription(finishedLogRecord, methodLog, handlerMethod);
}
// 记录所属模块
if (includeSet.contains(Include.MODULE)) {
this.logModule(finishedLogRecord, handlerMethod);
this.logModule(finishedLogRecord, methodLog, classLog, handlerMethod);
}
if (Boolean.TRUE.equals(logProperties.getIsPrint())) {
LogResponse logResponse = finishedLogRecord.getResponse();
@@ -107,23 +109,58 @@ public class LogInterceptor implements HandlerInterceptor {
}
}
/**
* 获取日志包含信息
*
* @param methodLog 方法级 Log 注解
* @param classLog 类级 Log 注解
* @return 日志包含信息
*/
private Set<Include> getIncludes(Log methodLog, Log classLog) {
Set<Include> includeSet = logProperties.getIncludes();
if (null != classLog) {
this.processInclude(includeSet, classLog);
}
if (null != methodLog) {
this.processInclude(includeSet, methodLog);
}
return includeSet;
}
/**
* 处理日志包含信息
*
* @param includes 日志包含信息
* @param logAnnotation Log 注解
*/
private void processInclude(Set<Include> includes, Log logAnnotation) {
Include[] includeArr = logAnnotation.includes();
if (includeArr.length > 0) {
includes.addAll(Set.of(includeArr));
}
Include[] excludeArr = logAnnotation.excludes();
if (excludeArr.length > 0) {
includes.removeAll(Set.of(excludeArr));
}
}
/**
* 记录描述
*
* @param logRecord 日志信息
* @param methodLog 方法级 Log 注解
* @param handlerMethod 处理器方法
*/
private void logDescription(LogRecord logRecord, HandlerMethod handlerMethod) {
private void logDescription(LogRecord logRecord, Log methodLog, HandlerMethod handlerMethod) {
// 例如:@Log("新增部门") -> 新增部门
Log methodLog = handlerMethod.getMethodAnnotation(Log.class);
if (null != methodLog && StrUtil.isNotBlank(methodLog.value())) {
if (null != methodLog && CharSequenceUtil.isNotBlank(methodLog.value())) {
logRecord.setDescription(methodLog.value());
return;
}
// 例如:@Operation(summary="新增部门") -> 新增部门
Operation methodOperation = handlerMethod.getMethodAnnotation(Operation.class);
if (null != methodOperation) {
logRecord.setDescription(StrUtil.blankToDefault(methodOperation.summary(), "请在该接口方法上指定日志描述"));
logRecord.setDescription(CharSequenceUtil.blankToDefault(methodOperation.summary(), "请在该接口方法上指定日志描述"));
}
}
@@ -131,17 +168,17 @@ public class LogInterceptor implements HandlerInterceptor {
* 记录模块
*
* @param logRecord 日志信息
* @param methodLog 方法级 Log 注解
* @param classLog 类级 Log 注解
* @param handlerMethod 处理器方法
*/
private void logModule(LogRecord logRecord, HandlerMethod handlerMethod) {
private void logModule(LogRecord logRecord, Log methodLog, Log classLog, HandlerMethod handlerMethod) {
// 例如:@Log(module = "部门管理") -> 部门管理
Log methodLog = handlerMethod.getMethodAnnotation(Log.class);
if (null != methodLog && StrUtil.isNotBlank(methodLog.module())) {
if (null != methodLog && CharSequenceUtil.isNotBlank(methodLog.module())) {
logRecord.setModule(methodLog.module());
return;
}
Log classLog = handlerMethod.getBeanType().getDeclaredAnnotation(Log.class);
if (null != classLog && StrUtil.isNotBlank(classLog.module())) {
if (null != classLog && CharSequenceUtil.isNotBlank(classLog.module())) {
logRecord.setModule(classLog.module());
return;
}
@@ -149,7 +186,7 @@ public class LogInterceptor implements HandlerInterceptor {
Tag classTag = handlerMethod.getBeanType().getDeclaredAnnotation(Tag.class);
if (null != classTag) {
String name = classTag.name();
logRecord.setModule(StrUtil.blankToDefault(name, "请在该接口类上指定所属模块"));
logRecord.setModule(CharSequenceUtil.blankToDefault(name, "请在该接口类上指定所属模块"));
}
}

View File

@@ -16,6 +16,7 @@
package top.charles7c.continew.starter.log.httptracepro.handler;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import cn.hutool.json.JSONUtil;
@@ -53,7 +54,7 @@ public final class RecordableServletHttpRequest implements RecordableHttpRequest
@Override
public URI getUrl() {
String queryString = request.getQueryString();
if (StrUtil.isBlank(queryString)) {
if (CharSequenceUtil.isBlank(queryString)) {
return URI.create(request.getRequestURL().toString());
}
try {
@@ -89,7 +90,7 @@ public final class RecordableServletHttpRequest implements RecordableHttpRequest
@Override
public Map<String, Object> getParam() {
String body = this.getBody();
return StrUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body)
return CharSequenceUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body)
? JSONUtil.toBean(body, Map.class)
: Collections.unmodifiableMap(request.getParameterMap());
}

View File

@@ -16,6 +16,7 @@
package top.charles7c.continew.starter.log.httptracepro.handler;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import jakarta.servlet.http.HttpServletResponse;
@@ -67,6 +68,6 @@ public final class RecordableServletHttpResponse implements RecordableHttpRespon
@Override
public Map<String, Object> getParam() {
String body = this.getBody();
return StrUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body) ? JSONUtil.toBean(body, Map.class) : null;
return CharSequenceUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body) ? JSONUtil.toBean(body, Map.class) : null;
}
}

View File

@@ -19,7 +19,7 @@ package top.charles7c.continew.starter.messaging.mail.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.extra.spring.SpringUtil;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
@@ -156,7 +156,7 @@ public class MailUtils {
String content,
boolean isHtml,
File... files) throws MessagingException {
Assert.isTrue(CollUtil.isEmpty(tos), "请至少指定一名收件人");
Assert.isFalse(CollUtil.isEmpty(tos), "请至少指定一名收件人");
MimeMessage mimeMessage = MAIL_SENDER.createMimeMessage();
MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, true, StandardCharsets.UTF_8
.displayName());
@@ -192,14 +192,14 @@ public class MailUtils {
* @return 联系人列表
*/
private static List<String> splitAddress(String addresses) {
if (StrUtil.isBlank(addresses)) {
if (CharSequenceUtil.isBlank(addresses)) {
return new ArrayList<>(0);
}
List<String> result;
if (StrUtil.contains(addresses, StringConstants.COMMA)) {
result = StrUtil.splitTrim(addresses, StringConstants.COMMA);
} else if (StrUtil.contains(addresses, StringConstants.SEMICOLON)) {
result = StrUtil.splitTrim(addresses, StringConstants.SEMICOLON);
if (CharSequenceUtil.contains(addresses, StringConstants.COMMA)) {
result = CharSequenceUtil.splitTrim(addresses, StringConstants.COMMA);
} else if (CharSequenceUtil.contains(addresses, StringConstants.SEMICOLON)) {
result = CharSequenceUtil.splitTrim(addresses, StringConstants.SEMICOLON);
} else {
result = CollUtil.newArrayList(addresses);
}

View File

@@ -18,10 +18,5 @@
<groupId>org.dromara.sms4j</groupId>
<artifactId>sms4j-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
</dependency>
</dependencies>
</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.charles7c.continew</groupId>
<artifactId>continew-starter-security</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-starter-security-all</artifactId>
<description>ContiNew Starter 安全模块 - 统一模块</description>
<dependencies>
<!-- 安全模块 - 加密 -->
<dependency>
<groupId>top.charles7c.continew</groupId>
<artifactId>continew-starter-security-crypto</artifactId>
</dependency>
<!-- 安全模块 - 脱敏 -->
<dependency>
<groupId>top.charles7c.continew</groupId>
<artifactId>continew-starter-security-mask</artifactId>
</dependency>
<!-- 安全模块 - 密码编码器 -->
<dependency>
<groupId>top.charles7c.continew</groupId>
<artifactId>continew-starter-security-password</artifactId>
</dependency>
</dependencies>
</project>

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.charles7c.continew</groupId>
<artifactId>continew-starter-security</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-starter-security-crypto</artifactId>
<description>ContiNew Starter 安全模块 - 加密</description>
<dependencies>
<!-- Hutool 加密解密模块(封装 JDK 中加密解密算法) -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
</dependency>
<!-- MyBatis PlusMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,54 @@
/*
* 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.charles7c.continew.starter.security.crypto.annotation;
import top.charles7c.continew.starter.security.crypto.encryptor.IEncryptor;
import top.charles7c.continew.starter.security.crypto.enums.Algorithm;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 字段加/解密注解
*
* @author Charles7c
* @since 1.4.0
*/
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldEncrypt {
/**
* 加密/解密算法
*/
Algorithm value() default Algorithm.AES;
/**
* 加密/解密处理器
* <p>
* 优先级高于加密/解密算法
* </p>
*/
Class<? extends IEncryptor> encryptor() default IEncryptor.class;
/**
* 对称加密算法密钥
*/
String password() default "";
}

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.charles7c.continew.starter.security.crypto.autoconfigure;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 top.charles7c.continew.starter.core.constant.PropertiesConstants;
import top.charles7c.continew.starter.security.crypto.core.MyBatisDecryptInterceptor;
import top.charles7c.continew.starter.security.crypto.core.MyBatisEncryptInterceptor;
/**
* 加/解密自动配置
*
* @author Charles7c
* @since 1.4.0
*/
@AutoConfiguration
@EnableConfigurationProperties(CryptoProperties.class)
@ConditionalOnProperty(prefix = PropertiesConstants.CRYPTO, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
public class CryptoAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(CryptoAutoConfiguration.class);
private final CryptoProperties properties;
public CryptoAutoConfiguration(CryptoProperties properties) {
this.properties = properties;
}
/**
* MyBatis 加密拦截器配置
*/
@Bean
@ConditionalOnMissingBean(MyBatisEncryptInterceptor.class)
public MyBatisEncryptInterceptor myBatisEncryptInterceptor() {
return new MyBatisEncryptInterceptor(properties);
}
/**
* MyBatis 解密拦截器配置
*/
@Bean
@ConditionalOnMissingBean(MyBatisDecryptInterceptor.class)
public MyBatisDecryptInterceptor myBatisDecryptInterceptor() {
return new MyBatisDecryptInterceptor(properties);
}
@PostConstruct
public void postConstruct() {
log.debug("[ContiNew Starter] - Auto Configuration 'Security-Crypto' completed initialization.");
}
}

View File

@@ -0,0 +1,82 @@
/*
* 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.charles7c.continew.starter.security.crypto.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
import top.charles7c.continew.starter.core.constant.PropertiesConstants;
/**
* 加/解密配置属性
*
* @author Charles7c
* @since 1.4.0
*/
@ConfigurationProperties(PropertiesConstants.CRYPTO)
public class CryptoProperties {
/**
* 是否启用加/解密配置
*/
private boolean enabled = true;
/**
* 对称加密算法密钥
*/
private String password;
/**
* 非对称加密算法公钥
*/
private String publicKey;
/**
* 非对称加密算法私钥
*/
private String privateKey;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPublicKey() {
return publicKey;
}
public void setPublicKey(String publicKey) {
this.publicKey = publicKey;
}
public String getPrivateKey() {
return privateKey;
}
public void setPrivateKey(String privateKey) {
this.privateKey = privateKey;
}
}

View File

@@ -0,0 +1,146 @@
/*
* 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.charles7c.continew.starter.security.crypto.core;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.plugin.*;
import top.charles7c.continew.starter.core.constant.StringConstants;
import top.charles7c.continew.starter.core.exception.BusinessException;
import top.charles7c.continew.starter.security.crypto.annotation.FieldEncrypt;
import top.charles7c.continew.starter.security.crypto.encryptor.IEncryptor;
import top.charles7c.continew.starter.security.crypto.enums.Algorithm;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
/**
* 字段解密拦截器
*
* @author Charles7c
* @since 1.4.0
*/
public abstract class AbstractMyBatisInterceptor implements Interceptor {
private static final Map<String, Map<String, FieldEncrypt>> ENCRYPT_PARAM_CACHE = new ConcurrentHashMap<>();
/**
* 获取加密参数
*
* @param mappedStatementId 映射语句 ID
* @return 加密参数
*/
public Map<String, FieldEncrypt> getEncryptParams(String mappedStatementId) {
return ENCRYPT_PARAM_CACHE.computeIfAbsent(mappedStatementId, this::getEncryptParamsNoCached);
}
/**
* 获取参数名称
*
* @param parameter 参数
* @return 参数名称
*/
public String getParameterName(Parameter parameter) {
Param param = parameter.getAnnotation(Param.class);
return null != param ? param.value() : parameter.getName();
}
/**
* 获取所有字符串类型、需要加/解密的、有值字段
*
* @param obj 对象
* @return 字段列表
*/
public List<Field> getEncryptFields(Object obj) {
if (null == obj) {
return Collections.emptyList();
}
return Arrays.stream(ReflectUtil.getFields(obj.getClass()))
.filter(field -> String.class.equals(field.getType()))
.filter(field -> null != field.getAnnotation(FieldEncrypt.class))
.filter(field -> null != ReflectUtil.getFieldValue(obj, field))
.toList();
}
/**
* 获取字段加/解密处理器
*
* @param fieldEncrypt 字段加密注解
* @return 加/解密处理器
*/
public IEncryptor getEncryptor(FieldEncrypt fieldEncrypt) {
Class<? extends IEncryptor> encryptorClass = fieldEncrypt.encryptor();
// 使用预定义加/解密处理器
if (encryptorClass == IEncryptor.class) {
Algorithm algorithm = fieldEncrypt.value();
return ReflectUtil.newInstance(algorithm.getEncryptor());
}
// 使用自定义加/解密处理器
return SpringUtil.getBean(encryptorClass);
}
/**
* 获取参数列表(无缓存)
*
* @param mappedStatementId 映射语句 ID
* @return 参数列表
*/
private Map<String, FieldEncrypt> getEncryptParamsNoCached(String mappedStatementId) {
try {
String className = CharSequenceUtil.subBefore(mappedStatementId, StringConstants.DOT, true);
String wrapperMethodName = CharSequenceUtil.subAfter(mappedStatementId, StringConstants.DOT, true);
String methodName = Stream.of("_mpCount", "_COUNT")
.filter(wrapperMethodName::endsWith)
.findFirst()
.map(suffix -> wrapperMethodName.substring(0, wrapperMethodName.length() - suffix.length()))
.orElse(wrapperMethodName);
// 获取真实方法
Optional<Method> methodOptional = Arrays.stream(ReflectUtil.getMethods(Class
.forName(className), m -> Objects.equals(m.getName(), methodName))).findFirst();
if (methodOptional.isEmpty()) {
return Collections.emptyMap();
}
// 获取方法中的加密参数
Map<String, FieldEncrypt> map = MapUtil.newHashMap();
Parameter[] parameterArr = methodOptional.get().getParameters();
for (int i = 0; i < parameterArr.length; i++) {
Parameter parameter = parameterArr[i];
String parameterName = this.getParameterName(parameter);
FieldEncrypt fieldEncrypt = parameter.getAnnotation(FieldEncrypt.class);
if (null != fieldEncrypt) {
map.put(parameterName, fieldEncrypt);
if (String.class.equals(parameter.getType())) {
map.put("param" + (i + 1), fieldEncrypt);
}
} else if (parameterName.startsWith(Constants.ENTITY)) {
map.put(parameterName, null);
}
}
return map;
} catch (ClassNotFoundException e) {
throw new BusinessException(e.getMessage());
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.charles7c.continew.starter.security.crypto.core;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.type.SimpleTypeRegistry;
import top.charles7c.continew.starter.security.crypto.annotation.FieldEncrypt;
import top.charles7c.continew.starter.security.crypto.autoconfigure.CryptoProperties;
import top.charles7c.continew.starter.security.crypto.encryptor.IEncryptor;
import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.List;
/**
* 字段解密拦截器
*
* @author Charles7c
* @since 1.4.0
*/
@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
public class MyBatisDecryptInterceptor extends AbstractMyBatisInterceptor {
private CryptoProperties properties;
public MyBatisDecryptInterceptor(CryptoProperties properties) {
this.properties = properties;
}
public MyBatisDecryptInterceptor() {
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object obj = invocation.proceed();
if (null == obj || !(invocation.getTarget() instanceof ResultSetHandler)) {
return obj;
}
List<?> resultList = (List<?>)obj;
for (Object result : resultList) {
// String、Integer、Long 等简单类型对象无需处理
if (SimpleTypeRegistry.isSimpleType(result.getClass())) {
continue;
}
// 获取所有字符串类型、需要解密的、有值字段
List<Field> fieldList = super.getEncryptFields(result);
// 解密处理
for (Field field : fieldList) {
IEncryptor encryptor = super.getEncryptor(field.getAnnotation(FieldEncrypt.class));
Object fieldValue = ReflectUtil.getFieldValue(result, field);
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置
String password = ObjectUtil.defaultIfBlank(field.getAnnotation(FieldEncrypt.class)
.password(), properties.getPassword());
String ciphertext = encryptor.decrypt(fieldValue.toString(), password, properties.getPrivateKey());
ReflectUtil.setFieldValue(result, field, ciphertext);
}
}
return resultList;
}
}

View File

@@ -0,0 +1,151 @@
/*
* 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.charles7c.continew.starter.security.crypto.core;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.SimpleTypeRegistry;
import top.charles7c.continew.starter.security.crypto.annotation.FieldEncrypt;
import top.charles7c.continew.starter.security.crypto.autoconfigure.CryptoProperties;
import top.charles7c.continew.starter.security.crypto.encryptor.IEncryptor;
import java.lang.reflect.Field;
import java.util.*;
/**
* 字段加密拦截器
*
* @author Charles7c
* @since 1.4.0
*/
@Intercepts({@Signature(method = "update", type = Executor.class, args = {MappedStatement.class, Object.class}),
@Signature(method = "query", type = Executor.class, args = {MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class, CacheKey.class, BoundSql.class}),
@Signature(method = "query", type = Executor.class, args = {MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class})})
public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor {
private CryptoProperties properties;
public MyBatisEncryptInterceptor(CryptoProperties properties) {
this.properties = properties;
}
public MyBatisEncryptInterceptor() {
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement mappedStatement = (MappedStatement)args[0];
Object parameter = args[1];
if (!this.isEncryptRequired(parameter, mappedStatement.getSqlCommandType())) {
return invocation.proceed();
}
// 使用 @Param 注解的场景
if (parameter instanceof HashMap parameterMap) {
this.encryptMap(parameterMap, mappedStatement);
} else {
this.doEncrypt(this.getEncryptFields(parameter), parameter);
}
return invocation.proceed();
}
/**
* 是否需要加密处理
*
* @param parameter 参数
* @param sqlCommandType SQL 类型
* @return truefalse
*/
private boolean isEncryptRequired(Object parameter, SqlCommandType sqlCommandType) {
if (ObjectUtil.isEmpty(parameter)) {
return false;
}
if (!(SqlCommandType.UPDATE == sqlCommandType || SqlCommandType.INSERT == sqlCommandType || SqlCommandType.SELECT == sqlCommandType)) {
return false;
}
return !SimpleTypeRegistry.isSimpleType(parameter.getClass());
}
/**
* 加密 Map 类型数据(使用 @Param 注解的场景)
*
* @param parameterMap 参数
* @param mappedStatement 映射语句
* @throws Exception /
*/
private void encryptMap(HashMap<String, Object> parameterMap, MappedStatement mappedStatement) throws Exception {
Map<String, FieldEncrypt> encryptParamMap = super.getEncryptParams(mappedStatement.getId());
for (Map.Entry<String, FieldEncrypt> encryptParamEntry : encryptParamMap.entrySet()) {
String parameterName = encryptParamEntry.getKey();
if (parameterName.startsWith(Constants.ENTITY)) {
// 兼容 MyBatis Plus 封装的 update 相关方法updateById、update
Object entity = parameterMap.getOrDefault(parameterName, null);
this.doEncrypt(this.getEncryptFields(entity), entity);
} else {
FieldEncrypt fieldEncrypt = encryptParamEntry.getValue();
parameterMap.put(parameterName, this.doEncrypt(parameterMap.get(parameterName), fieldEncrypt));
}
}
}
/**
* 处理加密
*
* @param parameterValue 参数值
* @param fieldEncrypt 字段加密注解
* @throws Exception /
*/
private Object doEncrypt(Object parameterValue, FieldEncrypt fieldEncrypt) throws Exception {
if (null == parameterValue) {
return null;
}
IEncryptor encryptor = super.getEncryptor(fieldEncrypt);
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置
String password = ObjectUtil.defaultIfBlank(fieldEncrypt.password(), properties.getPassword());
return encryptor.encrypt(parameterValue.toString(), password, properties.getPublicKey());
}
/**
* 处理加密
*
* @param fieldList 加密字段列表
* @param entity 实体
* @throws Exception /
*/
private void doEncrypt(List<Field> fieldList, Object entity) throws Exception {
for (Field field : fieldList) {
IEncryptor encryptor = super.getEncryptor(field.getAnnotation(FieldEncrypt.class));
Object fieldValue = ReflectUtil.getFieldValue(entity, field);
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置
String password = ObjectUtil.defaultIfBlank(field.getAnnotation(FieldEncrypt.class).password(), properties
.getPassword());
String ciphertext = encryptor.encrypt(fieldValue.toString(), password, properties.getPublicKey());
ReflectUtil.setFieldValue(entity, field, ciphertext);
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.charles7c.continew.starter.security.crypto.encryptor;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import top.charles7c.continew.starter.core.constant.StringConstants;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 对称加/解密处理器
*
* @author Charles7c
* @since 1.4.0
*/
public abstract class AbstractSymmetricCryptoEncryptor implements IEncryptor {
private static final Map<String, SymmetricCrypto> CACHE = new ConcurrentHashMap<>();
@Override
public String encrypt(String plaintext, String password, String publicKey) throws Exception {
if (StrUtil.isBlank(plaintext)) {
return plaintext;
}
return this.getCrypto(password).encryptHex(plaintext);
}
@Override
public String decrypt(String ciphertext, String password, String privateKey) throws Exception {
if (StrUtil.isBlank(ciphertext)) {
return ciphertext;
}
return this.getCrypto(password).decryptStr(ciphertext);
}
/**
* 获取对称加密算法
*
* @param password 密钥
* @return 对称加密算法
*/
protected SymmetricCrypto getCrypto(String password) {
SymmetricAlgorithm algorithm = this.getAlgorithm();
String key = algorithm + StringConstants.UNDERLINE + password;
if (CACHE.containsKey(key)) {
return CACHE.get(key);
}
SymmetricCrypto symmetricCrypto = new SymmetricCrypto(algorithm, password.getBytes(StandardCharsets.UTF_8));
CACHE.put(key, symmetricCrypto);
return symmetricCrypto;
}
/**
* 获取对称加密算法类型
*
* @return 对称加密算法类型
*/
protected abstract SymmetricAlgorithm getAlgorithm();
}

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.charles7c.continew.starter.security.crypto.encryptor;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
/**
* AESAdvanced Encryption Standard 加/解密处理器
* <p>
* 美国国家标准与技术研究院(NIST)采纳的对称加密算法标准提供128位、192位和256位三种密钥长度以高效和安全性著称。
* </p>
*
* @author Charles7c
* @since 1.4.0
*/
public class AesEncryptor extends AbstractSymmetricCryptoEncryptor {
@Override
protected SymmetricAlgorithm getAlgorithm() {
return SymmetricAlgorithm.AES;
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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.charles7c.continew.starter.security.crypto.encryptor;
import cn.hutool.core.codec.Base64;
/**
* Base64 加/解密处理器
* <p>
* 一种用于编码二进制数据到文本格式的算法,常用于邮件附件、网页传输等场合,但它不是一种加密算法,只提供数据的编码和解码,不保证数据的安全性。
* </p>
*
* @author Charles7c
* @since 1.4.0
*/
public class Base64Encryptor implements IEncryptor {
@Override
public String encrypt(String plaintext, String password, String publicKey) throws Exception {
return Base64.encode(plaintext);
}
@Override
public String decrypt(String ciphertext, String password, String privateKey) throws Exception {
return Base64.decodeStr(ciphertext);
}
}

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.charles7c.continew.starter.security.crypto.encryptor;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
/**
* DESData Encryption Standard 加/解密处理器
* <p>
* 一种对称加密算法使用相同的密钥进行加密和解密。DES 使用 56 位密钥(实际上有 64 位,但有 8 位用于奇偶校验)和一系列置换和替换操作来加密数据。
* </p>
*
* @author Charles7c
* @since 1.4.0
*/
public class DesEncryptor extends AbstractSymmetricCryptoEncryptor {
@Override
protected SymmetricAlgorithm getAlgorithm() {
return SymmetricAlgorithm.DES;
}
}

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.charles7c.continew.starter.security.crypto.encryptor;
/**
* 加/解密接口
*
* @author Charles7c
* @since 1.4.0
*/
public interface IEncryptor {
/**
* 加密
*
* @param plaintext 明文
* @param password 对称加密算法密钥
* @param publicKey 非对称加密算法公钥
* @return 加密后的文本
* @throws Exception /
*/
String encrypt(String plaintext, String password, String publicKey) throws Exception;
/**
* 解密
*
* @param ciphertext 密文
* @param password 对称加密算法密钥
* @param privateKey 非对称加密算法私钥
* @return 解密后的文本
* @throws Exception /
*/
String decrypt(String ciphertext, String password, String privateKey) throws Exception;
}

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.charles7c.continew.starter.security.crypto.encryptor;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
/**
* PBEWithMD5AndDESPassword Based Encryption With MD5 And DES 加/解密处理器
* <p>
* 混合加密算法,结合了 MD5 散列算法和 DESData Encryption Standard加密算法
* </p>
*
* @author Charles7c
* @since 1.4.0
*/
public class PbeWithMd5AndDesEncryptor extends AbstractSymmetricCryptoEncryptor {
@Override
protected SymmetricAlgorithm getAlgorithm() {
return SymmetricAlgorithm.PBEWithMD5AndDES;
}
}

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.charles7c.continew.starter.security.crypto.encryptor;
import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
/**
* RSA 加/解密处理器
* <p>
* 非对称加密算法由罗纳德·李维斯特Ron Rivest、阿迪·沙米尔Adi Shamir和伦纳德·阿德曼Leonard Adleman于1977年提出安全性基于大数因子分解问题的困难性。
* </p>
*
* @author Charles7c
* @since 1.4.0
*/
public class RsaEncryptor implements IEncryptor {
@Override
public String encrypt(String plaintext, String password, String publicKey) throws Exception {
return Base64.encode(SecureUtil.rsa(null, publicKey).encrypt(plaintext, KeyType.PublicKey));
}
@Override
public String decrypt(String ciphertext, String password, String privateKey) throws Exception {
return new String(SecureUtil.rsa(privateKey, null).decrypt(Base64.decode(ciphertext), KeyType.PrivateKey));
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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.charles7c.continew.starter.security.crypto.enums;
import top.charles7c.continew.starter.security.crypto.encryptor.*;
/**
* 加密/解密算法枚举
*
* @author Charles7c
* @since 1.4.0
*/
public enum Algorithm {
/**
* AES
*/
AES(AesEncryptor.class),
/**
* DES
*/
DES(DesEncryptor.class),
/**
* PBEWithMD5AndDES
*/
PBEWithMD5AndDES(PbeWithMd5AndDesEncryptor.class),
/**
* RSA
*/
RSA(RsaEncryptor.class),
/**
* Base64
*/
BASE64(Base64Encryptor.class),;
/**
* 加密/解密处理器
*/
private final Class<? extends IEncryptor> encryptor;
Algorithm(Class<? extends IEncryptor> encryptor) {
this.encryptor = encryptor;
}
public Class<? extends IEncryptor> getEncryptor() {
return encryptor;
}
}

View File

@@ -0,0 +1 @@
top.charles7c.continew.starter.security.crypto.autoconfigure.CryptoAutoConfiguration

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.charles7c.continew</groupId>
<artifactId>continew-starter-security</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-starter-security-mask</artifactId>
<description>ContiNew Starter 安全模块 - 脱敏</description>
</project>

View File

@@ -0,0 +1,76 @@
/*
* 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.charles7c.continew.starter.security.mask.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import top.charles7c.continew.starter.core.constant.StringConstants;
import top.charles7c.continew.starter.security.mask.core.JsonMaskSerializer;
import top.charles7c.continew.starter.security.mask.enums.MaskType;
import top.charles7c.continew.starter.security.mask.strategy.IMaskStrategy;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* JSON 脱敏注解
*
* @author Charles7c
* @since 1.4.0
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = JsonMaskSerializer.class)
public @interface JsonMask {
/**
* 脱敏类型
*/
MaskType value() default MaskType.CUSTOM;
/**
* 脱敏策略
* <p>
* 优先级高于脱敏类型
* </p>
*/
Class<? extends IMaskStrategy> strategy() default IMaskStrategy.class;
/**
* 左侧保留位数
* <p>
* 仅在脱敏类型为 {@code DesensitizedType.CUSTOM } 时使用
* </p>
*/
int left() default 0;
/**
* 右侧保留位数
* <p>
* 仅在脱敏类型为 {@code DesensitizedType.CUSTOM } 时使用
* </p>
*/
int right() default 0;
/**
* 脱敏符号(默认:*
*/
char character() default StringConstants.C_ASTERISK;
}

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.charles7c.continew.starter.security.mask.core;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import top.charles7c.continew.starter.core.constant.StringConstants;
import top.charles7c.continew.starter.security.mask.annotation.JsonMask;
import top.charles7c.continew.starter.security.mask.strategy.IMaskStrategy;
import java.io.IOException;
import java.util.Objects;
/**
* JSON 脱敏序列化器
*
* @author Charles7c
* @since 1.4.0
*/
public class JsonMaskSerializer extends JsonSerializer<String> implements ContextualSerializer {
private JsonMask jsonMask;
public JsonMaskSerializer(JsonMask jsonMask) {
this.jsonMask = jsonMask;
}
public JsonMaskSerializer() {
}
@Override
public void serialize(String str,
JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException {
if (CharSequenceUtil.isBlank(str)) {
jsonGenerator.writeString(StringConstants.EMPTY);
return;
}
// 使用自定义脱敏策略
Class<? extends IMaskStrategy> strategyClass = jsonMask.strategy();
IMaskStrategy maskStrategy = strategyClass != IMaskStrategy.class
? SpringUtil.getBean(strategyClass)
: jsonMask.value();
jsonGenerator.writeString(maskStrategy.mask(str, jsonMask.character(), jsonMask.left(), jsonMask.right()));
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider,
BeanProperty beanProperty) throws JsonMappingException {
if (null == beanProperty) {
return serializerProvider.findNullValueSerializer(null);
}
if (!Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
}
JsonMask jsonMaskAnnotation = ObjectUtil.defaultIfNull(beanProperty.getAnnotation(JsonMask.class), beanProperty
.getContextAnnotation(JsonMask.class));
if (null == jsonMaskAnnotation) {
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
}
return new JsonMaskSerializer(jsonMaskAnnotation);
}
}

View File

@@ -0,0 +1,220 @@
/*
* 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.charles7c.continew.starter.security.mask.enums;
import cn.hutool.core.text.CharSequenceUtil;
import top.charles7c.continew.starter.core.constant.StringConstants;
import top.charles7c.continew.starter.security.mask.strategy.IMaskStrategy;
/**
* 脱敏类型
*
* @author Charles7c
* @since 1.4.0
*/
public enum MaskType implements IMaskStrategy {
/**
* 自定义脱敏
*/
CUSTOM {
@Override
public String mask(String str, char character, int left, int right) {
return CharSequenceUtil.replace(str, left, str.length() - right, character);
}
},
/**
* 手机号码
* <p>保留前 3 位和后 4 位例如135****2210</p>
*/
MOBILE_PHONE {
@Override
public String mask(String str, char character, int left, int right) {
return CharSequenceUtil.replace(str, 3, str.length() - 4, character);
}
},
/**
* 固定电话
* <p>
* 保留前 4 位和后 2 位
* </p>
*/
FIXED_PHONE {
@Override
public String mask(String str, char character, int left, int right) {
return CharSequenceUtil.replace(str, 4, str.length() - 2, character);
}
},
/**
* 电子邮箱
*
* <p>
* 邮箱前缀仅保留第 1 个字母,@ 符号及后面的地址不脱敏例如d**@126.com
* </p>
*/
EMAIL {
@Override
public String mask(String str, char character, int left, int right) {
int index = str.indexOf(StringConstants.AT);
if (index <= 1) {
return str;
}
return CharSequenceUtil.replace(str, 1, index, character);
}
},
/**
* 身份证号
* <p>
* 保留前 1 位和后 2 位
* </p>
*/
ID_CARD {
@Override
public String mask(String str, char character, int left, int right) {
return CharSequenceUtil.replace(str, 1, str.length() - 2, character);
}
},
/**
* 银行卡
* <p>
* 由于银行卡号长度不定,所以只保留前 4 位,后面保留的位数根据卡号决定展示 1-4 位
* <ul>
* <li>1234 2222 3333 4444 6789 9 => 1234 **** **** **** **** 9</li>
* <li>1234 2222 3333 4444 6789 91 => 1234 **** **** **** **** 91</li>
* <li>1234 2222 3333 4444 678 => 1234 **** **** **** 678</li>
* <li>1234 2222 3333 4444 6789 => 1234 **** **** **** 6789</li>
* </ul>
* </p>
*/
BANK_CARD {
@Override
public String mask(String str, char character, int left, int right) {
String cleanStr = CharSequenceUtil.cleanBlank(str);
if (cleanStr.length() < 9) {
return cleanStr;
}
final int length = cleanStr.length();
final int endLength = length % 4 == 0 ? 4 : length % 4;
final int midLength = length - 4 - endLength;
final StringBuilder buffer = new StringBuilder();
buffer.append(cleanStr, 0, 4);
for (int i = 0; i < midLength; ++i) {
if (i % 4 == 0) {
buffer.append(StringConstants.SPACE);
}
buffer.append(character);
}
buffer.append(StringConstants.SPACE).append(cleanStr, length - endLength, length);
return buffer.toString();
}
},
/**
* 中国大陆车牌(包含普通车辆、新能源车辆)
* <p>
* 例如苏D40000 => 苏D4***0
* </p>
*/
CAR_LICENSE {
@Override
public String mask(String str, char character, int left, int right) {
// 普通车牌
int length = str.length();
if (length == 7) {
return CharSequenceUtil.replace(str, 3, 6, character);
}
// 新能源车牌
if (length == 8) {
return CharSequenceUtil.replace(str, 3, 7, character);
}
return str;
}
},
/**
* 中文名
* <p>
* 只保留第 1 个汉字,例如:李**
* </p>
*/
CHINESE_NAME {
@Override
public String mask(String str, char character, int left, int right) {
return CharSequenceUtil.replace(str, 1, str.length(), character);
}
},
/**
* 密码
* <p>
* 密码的全部字符都使用脱敏符号代替,例如:******
* </p>
*/
PASSWORD {
@Override
public String mask(String str, char character, int left, int right) {
return CharSequenceUtil.repeat(character, str.length());
}
},
/**
* 地址
* <p>
* 只显示到地区,不显示详细地址,例如:北京市海淀区****
* </p>
*/
ADDRESS {
@Override
public String mask(String str, char character, int left, int right) {
int length = str.length();
return CharSequenceUtil.replace(str, length - 8, length, character);
}
},
/**
* IPv4 地址
* <p>
* 例如192.0.2.1 => 192.*.*.*
* </p>
*/
IPV4 {
@Override
public String mask(String str, char character, int left, int right) {
return CharSequenceUtil.subBefore(str, StringConstants.DOT, false) + String
.format(".%s.%s.%s", character, character, character);
}
},
/**
* IPv6 地址
* <p>
* 例如2001:0db8:86a3:08d3:1319:8a2e:0370:7344 => 2001:*:*:*:*:*:*:*
* </p>
*/
IPV6 {
@Override
public String mask(String str, char character, int left, int right) {
return CharSequenceUtil.subBefore(str, StringConstants.COLON, false) + String
.format(":%s:%s:%s:%s:%s:%s:%s", character, character, character, character, character, character, character);
}
},;
}

View File

@@ -0,0 +1,37 @@
/*
* 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.charles7c.continew.starter.security.mask.strategy;
/**
* 脱敏策略
*
* @author Charles7c
* @since 1.4.0
*/
public interface IMaskStrategy {
/**
* 数据脱敏
*
* @param str 原始字符串
* @param character 脱敏符号
* @param left 左侧保留位数
* @param right 右侧保留位数
* @return 脱敏后的数据
*/
String mask(String str, char character, int left, int right);
}

View File

@@ -17,7 +17,7 @@
package top.charles7c.continew.starter.security.password.autoconfigure;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -80,7 +80,8 @@ public class PasswordEncoderAutoConfiguration {
if (CollUtil.isNotEmpty(passwordEncoderList)) {
passwordEncoderList.forEach(passwordEncoder -> {
String simpleName = passwordEncoder.getClass().getSimpleName();
encoders.put(StrUtil.removeSuffix(simpleName, "PasswordEncoder").toLowerCase(), passwordEncoder);
encoders.put(CharSequenceUtil.removeSuffix(simpleName, "PasswordEncoder")
.toLowerCase(), passwordEncoder);
});
}
String encodingId = properties.getEncodingId();

View File

@@ -14,7 +14,10 @@
<description>ContiNew Starter 安全模块</description>
<modules>
<module>continew-starter-security-all</module>
<module>continew-starter-security-password</module>
<module>continew-starter-security-mask</module>
<module>continew-starter-security-crypto</module>
</modules>
<dependencies>

View File

@@ -16,7 +16,7 @@
package top.charles7c.continew.starter.storage.local.autoconfigure;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -57,10 +57,10 @@ public class LocalStorageAutoConfiguration implements WebMvcConfigurer {
LocalStorageProperties.LocalStorageMapping mapping = mappingEntry.getValue();
String pathPattern = mapping.getPathPattern();
String location = mapping.getLocation();
if (StrUtil.isBlank(location)) {
if (CharSequenceUtil.isBlank(location)) {
throw new IllegalArgumentException(String.format("Path pattern [%s] location is null.", pathPattern));
}
registry.addResourceHandler(StrUtil.appendIfMissing(pathPattern, StringConstants.PATH_PATTERN))
registry.addResourceHandler(CharSequenceUtil.appendIfMissing(pathPattern, StringConstants.PATH_PATTERN))
.addResourceLocations(!location.startsWith("file:")
? String.format("file:%s", this.format(location))
: this.format(location))

View File

@@ -18,7 +18,7 @@ package top.charles7c.continew.starter.web.autoconfigure.exception;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
@@ -99,7 +99,8 @@ public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public R<Void> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e,
HttpServletRequest request) {
String errorMsg = StrUtil.format("参数名:[{}],期望参数类型:[{}]", e.getName(), e.getParameter().getParameterType());
String errorMsg = CharSequenceUtil.format("参数名:[{}],期望参数类型:[{}]", e.getName(), e.getParameter()
.getParameterType());
log.warn("请求地址 [{}],参数转换失败,{}。", request.getRequestURI(), errorMsg, e);
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
}
@@ -110,7 +111,7 @@ public class GlobalExceptionHandler {
@ExceptionHandler(MaxUploadSizeExceededException.class)
public R<Void> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e, HttpServletRequest request) {
log.warn("请求地址 [{}],上传文件失败,文件大小超过限制。", request.getRequestURI(), e);
String sizeLimit = StrUtil.subBetween(e.getMessage(), "The maximum size ", " for");
String sizeLimit = CharSequenceUtil.subBetween(e.getMessage(), "The maximum size ", " for");
String errorMsg = String.format("请上传小于 %sMB 的文件", NumberUtil.parseLong(sizeLimit) / 1024 / 1024);
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
}

View File

@@ -16,7 +16,7 @@
package top.charles7c.continew.starter.web.autoconfigure.trace;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.yomahub.tlog.context.TLogContext;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
@@ -53,7 +53,7 @@ public class TLogServletFilter implements Filter {
TLogWebCommon.loadInstance().preHandle(httpServletRequest);
// 把 traceId 放入 response 的 header为了方便有些人有这样的需求从前端拿整条链路的 traceId
String headerName = traceProperties.getHeaderName();
if (StrUtil.isNotBlank(headerName)) {
if (CharSequenceUtil.isNotBlank(headerName)) {
httpServletResponse.addHeader(headerName, TLogContext.getTraceId());
}
chain.doFilter(request, response);

View File

@@ -17,7 +17,7 @@
package top.charles7c.continew.starter.web.util;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.extra.spring.SpringUtil;
import jakarta.servlet.ServletContext;
import org.springframework.context.ApplicationContext;
@@ -55,7 +55,7 @@ public class SpringWebUtils {
.getFieldValue(resourceHandlerMapping, "handlerMap");
// 移除之前注册的映射
for (Map.Entry<String, String> entry : handlerMap.entrySet()) {
String pathPattern = StrUtil.appendIfMissing(entry.getKey(), StringConstants.PATH_PATTERN);
String pathPattern = CharSequenceUtil.appendIfMissing(entry.getKey(), StringConstants.PATH_PATTERN);
oldHandlerMap.remove(pathPattern);
}
}
@@ -80,10 +80,10 @@ public class SpringWebUtils {
final ResourceHandlerRegistry resourceHandlerRegistry = new ResourceHandlerRegistry(applicationContext, servletContext, contentNegotiationManager, urlPathHelper);
for (Map.Entry<String, String> entry : handlerMap.entrySet()) {
// 移除之前注册的映射
String pathPattern = StrUtil.appendIfMissing(entry.getKey(), StringConstants.PATH_PATTERN);
String pathPattern = CharSequenceUtil.appendIfMissing(entry.getKey(), StringConstants.PATH_PATTERN);
oldHandlerMap.remove(pathPattern);
// 重新注册映射
String resourceLocations = StrUtil.appendIfMissing(entry.getValue(), StringConstants.SLASH);
String resourceLocations = CharSequenceUtil.appendIfMissing(entry.getValue(), StringConstants.SLASH);
resourceHandlerRegistry.addResourceHandler(pathPattern).addResourceLocations("file:" + resourceLocations);
}
final Map<String, ?> additionalUrlMap = ReflectUtil