Compare commits

...

21 Commits

Author SHA1 Message Date
c01ba23463 release: v2.12.2 2025-06-13 23:35:07 +08:00
jasmine
c0aa86327a fix(extension/datapermission): 修复构建本部门及以下数据权限表达式问题。 2025-06-11 07:34:43 +00:00
ad298930db release: v2.12.1 2025-06-09 21:43:40 +08:00
c08b57cb48 feat(cache/redisson): 新增 RedisQueueUtils 队列工具类 2025-06-09 19:29:05 +08:00
a4823dcb0b refactor(security/crypto): 优化字段加解密相关代码 2025-06-09 19:28:45 +08:00
liquor
eb7dfd4ed7 fix(core): 修复 application/x-www-form-urlencoded 请求体数据无法在 Controller 层获取的问题 2025-06-09 03:54:49 +00:00
eda35e59bc refactor: 优化部分代码格式 2025-06-01 11:10:53 +08:00
6b95083c63 feat(idempotent): 新增默认幂等名称生成器 2025-06-01 11:10:16 +08:00
265d90fa4c style: 调整代码风格 null == xx => xx == null(更符合大众风格) 2025-06-01 11:09:12 +08:00
f83a901626 refactor: 移除 web-core,融合 web-core 和 core 模块
1.移除 web-core,融合 web-core 和 core 模块
2.调整部分依赖顺序
2025-05-22 21:49:20 +08:00
4f9e1ba108 chore: 优化 Bug Issue 模板 2025-05-22 21:37:26 +08:00
73e2b169f7 refactor(json/jackson): 优化部分代码 2025-05-22 21:21:48 +08:00
jasmine
918a0abfda feat(json/jackson): Jackson 大数值序列化增加多模式支持 2025-05-22 03:24:53 +00:00
4a6b4624c2 feat(core): ExceptionUtils 新增 exToThrow 方法 2025-05-21 22:18:57 +08:00
2fdd5b6fd3 chore: 优化部分代码格式和注释 2025-05-21 22:18:10 +08:00
d5a74b42e8 Merge branch 'dev' into 2.12.x 2025-05-21 22:00:00 +08:00
beginner
abc005e690 fix(web): 添加Servlet工具类对getOs和getBrowser方法中User-Agent为空或解析失败时的非空判断 2025-05-21 01:01:38 +00:00
liquor
9c6182e028 refactor(web): 拆分 web 模块 2025-05-20 14:58:38 +00:00
b543b2f94d feat(messaging/websocket): 新增批量发送消息方法 2025-05-18 20:50:32 +08:00
fa2cdf4f80 feat(messaging/websocket): 新增发送消息给所有客户端方法 2025-05-18 20:42:24 +08:00
adaf475835 docs: 补充缺失文档 2025-05-17 18:32:04 +08:00
114 changed files with 1250 additions and 506 deletions

View File

@@ -16,7 +16,7 @@ body:
required: true required: true
- label: 查阅过 [使用指南](https://continew.top/starter/guide/introduction.html) 和 [常见问题](https://continew.top/starter/faq.html) ,仍无解决方法 - label: 查阅过 [使用指南](https://continew.top/starter/guide/introduction.html) 和 [常见问题](https://continew.top/starter/faq.html) ,仍无解决方法
required: true required: true
- label: 根据报错信息(自行翻译英文)百度或 Google 后,仍无法解决 - label: 查看过控制台是否有报错,如果有报错,下拉控制台到最下查找过 Caused 提示(如果有 Caused 关键字,一般其后都有直观提示,请自行翻译英文),并百度或 Google 后,仍无法解决
required: true required: true
- label: 尝试 [最新版本](https://central.sonatype.com/artifact/top.continew/continew-starter/versions)(还可以尝试本地编译安装 dev 分支的最新快照版本),仍有相同问题 - label: 尝试 [最新版本](https://central.sonatype.com/artifact/top.continew/continew-starter/versions)(还可以尝试本地编译安装 dev 分支的最新快照版本),仍有相同问题
required: true required: true

View File

@@ -1,9 +1,36 @@
## [v2.12.2](https://github.com/continew-org/continew-starter/compare/v2.12.1...v2.12.2) (2025-06-13)
### 🐛 问题修复
- 【extension/datapermission】修复构建本部门及以下数据权限表达式问题。 (Gitee#65@httpsjt) ([c0aa863](https://github.com/continew-org/continew-starter/commit/c0aa86327acac94b55e2f7c4fa193da4e38af986))
## [v2.12.1](https://github.com/continew-org/continew-starter/compare/v2.12.0...v2.12.1) (2025-06-09)
### ✨ 新特性
- 【messaging/websocket】新增发送消息给所有客户端方法 ([fa2cdf4](https://github.com/continew-org/continew-starter/commit/fa2cdf4f80bf4ca8656b658657398908894dfa40))
- 【messaging/websocket】新增批量发送消息方法 ([b543b2f](https://github.com/continew-org/continew-starter/commit/b543b2f94d09658a276e3a77d3091e1ec32360f9))
- 【core】ExceptionUtils 新增 exToThrow 方法 ([4a6b462](https://github.com/continew-org/continew-starter/commit/4a6b4624c2ed769bba6c50efd90592f7719247e5))
- 【json/jackson】Jackson 大数值序列化增加多模式支持 (Gitee#63@httpsjt) ([918a0ab](https://github.com/continew-org/continew-starter/commit/918a0abfda61bda8199256e4d4ecd5e20564569e)) ([73e2b16](https://github.com/continew-org/continew-starter/commit/73e2b169f7bc4a02140f963fd7b90037be8ff2b8))
- 【idempotent】新增默认幂等名称生成器 ([6b95083](https://github.com/continew-org/continew-starter/commit/6b95083c63de6a8eb7a7d08e6d537ec7afdb32f8))
- 【cache/redisson】新增 RedisQueueUtils 队列工具类 ([c08b57c](https://github.com/continew-org/continew-starter/commit/c08b57cb489e29b44ae99ec5bd725b72ec9a83a3))
### 💎 功能优化
- 调整代码风格 null == xx => xx == null更符合大众风格 ([265d90f](https://github.com/continew-org/continew-starter/commit/265d90fa4ca0db8ed2bada22bd2881d364efde6e))
- 调整 Web 工具类到 core 模块 ([f83a901](https://github.com/continew-org/continew-starter/commit/f83a90162623208d3be75b03450d7ca29780c2b9))
- 【security/crypto】优化字段加解密相关代码 ([a4823dc](https://github.com/continew-org/continew-starter/commit/a4823dcb0bf211e26ccb8816928b5332b2bfe216))
### 🐛 问题修复
- 【web】添加 Servlet 工具类对 getOs 和 getBrowser 方法中User-Agent 为空或解析失败时的非空判断 (Gitee#61@beginner_b) ([abc005e](https://github.com/continew-org/continew-starter/commit/abc005e69022e7e08a580cd8027a5a3fb73ba929))
- 【core】修复 application/x-www-form-urlencoded 请求体数据无法在 Controller 层获取的问题 (Gitee#65@httpsjt) ([eb7dfd4](https://github.com/continew-org/continew-starter/commit/eb7dfd4ed706ed6b72364e316c4278364a4d4af4))
## [v2.12.0](https://github.com/continew-org/continew-starter/compare/v2.11.0...v2.12.0) (2025-05-17) ## [v2.12.0](https://github.com/continew-org/continew-starter/compare/v2.11.0...v2.12.0) (2025-05-17)
### ✨ 新特性 ### ✨ 新特性
- 【security/password】添加密码编码器相关常量类 ([1b7f541](https://github.com/continew-org/continew-starter/commit/1b7f541e7d133cd431a9ca097bdac46ea85073be)) - 【security/password】添加密码编码器相关常量类 ([1b7f541](https://github.com/continew-org/continew-starter/commit/1b7f541e7d133cd431a9ca097bdac46ea85073be))
- 【license】新增 License 模块 (Gitee#51@aiming317、dom-w、httpsjt) ([da4c815](https://github.com/continew-org/continew-starter/commit/da4c8154bf6ddae7c0d0c6719efcc36537ed5983)) ([1ce5c02](https://github.com/continew-org/continew-starter/commit/1ce5c023cf8fe78849fba9fe0f7c0fcfac09c491)) ([7d97026](https://github.com/continew-org/continew-starter/commit/7d97026480b244319fa322c854a5e2d2552cc786)) ([06f5a0f](https://github.com/continew-org/continew-starter/commit/06f5a0f34680c93efe525b8102d24622b8b20893)) ([5e9a3f3](https://github.com/continew-org/continew-starter/commit/5e9a3f3e93ab55a6bc09828e124705e23543f72e)) - 【license】新增 License 模块 (Gitee#51@aiming317、dom-w、httpsjt) ([da4c815](https://github.com/continew-org/continew-starter/commit/da4c8154bf6ddae7c0d0c6719efcc36537ed5983)) ([1ce5c02](https://github.com/continew-org/continew-starter/commit/1ce5c023cf8fe78849fba9fe0f7c0fcfac09c491)) ([7d97026](https://github.com/continew-org/continew-starter/commit/7d97026480b244319fa322c854a5e2d2552cc786)) ([06f5a0f](https://github.com/continew-org/continew-starter/commit/06f5a0f34680c93efe525b8102d24622b8b20893)) ([5e9a3f3](https://github.com/continew-org/continew-starter/commit/5e9a3f3e93ab55a6bc09828e124705e23543f72e))
- 【core】新增 JSON 格式字符串校验器 ([cf5ef36](https://github.com/continew-org/continew-starter/commit/cf5ef36af5179550e9c8cb75332497813488aee3)) - 【core】新增 JSON 格式字符串校验器 ([cf5ef36](https://github.com/continew-org/continew-starter/commit/cf5ef36af5179550e9c8cb75332497813488aee3))
- 【extension/crud】PageQuery 和 SortQuery 完善带参构造方法 ([70f8fc0](https://github.com/continew-org/continew-starter/commit/70f8fc01c0cd5636316705f9f9c425cda3f1d736)) - 【extension/crud】PageQuery 和 SortQuery 完善带参构造方法 ([70f8fc0](https://github.com/continew-org/continew-starter/commit/70f8fc01c0cd5636316705f9f9c425cda3f1d736))
@@ -24,6 +51,19 @@
- 【web】修复默认 Response 类 msg 传递污染的问题 ([3bbd04a](https://github.com/continew-org/continew-starter/commit/3bbd04add2946a9619e8edbd94cb9bbb23c688a8)) - 【web】修复默认 Response 类 msg 传递污染的问题 ([3bbd04a](https://github.com/continew-org/continew-starter/commit/3bbd04add2946a9619e8edbd94cb9bbb23c688a8))
- 【web】修复 /file/ 注册资源映射时被解析为 /file//** 的问题 ([35e2cdc](https://github.com/continew-org/continew-starter/commit/35e2cdc3bf2c2137f86e72612b3572be148b5823)) - 【web】修复 /file/ 注册资源映射时被解析为 /file//** 的问题 ([35e2cdc](https://github.com/continew-org/continew-starter/commit/35e2cdc3bf2c2137f86e72612b3572be148b5823))
### 📦 依赖升级
- spring-boot 3.3.9 => 3.3.11 ([62334d8](https://github.com/continew-org/continew-starter/commit/62334d882c174fd2de5fdeb081407356984d26bd))
- redisson 3.45.0 => 3.46.0
- jetcache 2.2.7 => 2.7.8
- cosid 2.11.0 => 2.12.3
- sa-token 1.41.0 => 1.42.0
- mybatis-flex 1.10.8 => 1.10.9
- aws-s3 1.12.782 => 1.12.783
- s3 2.30.35 => 2.31.35
- s3-crt 0.36.1 => 0.38.1
- hutool 5.8.36 => 5.8.37
## [v2.11.0](https://github.com/continew-org/continew-starter/compare/v2.10.0...v2.11.0) (2025-04-13) ## [v2.11.0](https://github.com/continew-org/continew-starter/compare/v2.10.0...v2.11.0) (2025-04-13)
### ✨ 新特性 ### ✨ 新特性
@@ -35,7 +75,6 @@
### 💎 功能优化 ### 💎 功能优化
- 【dependencies】采取 bom 方式来管理 JetCache 依赖 (Gitee#44@jiang4yu) ([e2d8f45](https://github.com/continew-org/continew-starter/commit/e2d8f45206a55e333c26a48c501efbb82c89beea)) ([f662b74](https://github.com/continew-org/continew-starter/commit/f662b740610da3e1ff4c0fadf2e5b2a188b06d73)) ([3e0dd83](https://github.com/continew-org/continew-starter/commit/3e0dd83e2664e57d61c37e4ea7afa618c322b984)) - 【dependencies】采取 bom 方式来管理 JetCache 依赖 (Gitee#44@jiang4yu) ([e2d8f45](https://github.com/continew-org/continew-starter/commit/e2d8f45206a55e333c26a48c501efbb82c89beea)) ([f662b74](https://github.com/continew-org/continew-starter/commit/f662b740610da3e1ff4c0fadf2e5b2a188b06d73)) ([3e0dd83](https://github.com/continew-org/continew-starter/commit/3e0dd83e2664e57d61c37e4ea7afa618c322b984))
- 替换 aspectjweaver 依赖为 Spring Boot Starter AOP ([ae2b898](https://github.com/continew-org/continew-starter/commit/ae2b898e57ca8e418289a2974c92447ec191e15f)) - 替换 aspectjweaver 依赖为 Spring Boot Starter AOP ([ae2b898](https://github.com/continew-org/continew-starter/commit/ae2b898e57ca8e418289a2974c92447ec191e15f))
- 【dependencies】调整 sa-token 版本锁定为 bom 方式PR by iang4yu ([e242568](https://github.com/continew-org/continew-starter/commit/e24256818d716c4c2bbc50d6e7bd0df394bbbd4f)) - 【dependencies】调整 sa-token 版本锁定为 bom 方式PR by iang4yu ([e242568](https://github.com/continew-org/continew-starter/commit/e24256818d716c4c2bbc50d6e7bd0df394bbbd4f))
- 【log】访问日志过滤资源路径 (Gitee#47@dom-w) ([a6a44cd](https://github.com/continew-org/continew-starter/commit/a6a44cd46131d41f8626fe67f6ad9e4d70f1d46c)) - 【log】访问日志过滤资源路径 (Gitee#47@dom-w) ([a6a44cd](https://github.com/continew-org/continew-starter/commit/a6a44cd46131d41f8626fe67f6ad9e4d70f1d46c))

View File

@@ -153,6 +153,10 @@ continew-starter
│ └─ continew-starter-json-jackson │ └─ continew-starter-json-jackson
├─ continew-starter-api-doc接口文档模块Spring Doc + Knife4j ├─ continew-starter-api-doc接口文档模块Spring Doc + Knife4j
├─ continew-starter-webWeb 开发模块:包含跨域、全局异常+响应、链路追踪等自动配置) ├─ continew-starter-webWeb 开发模块:包含跨域、全局异常+响应、链路追踪等自动配置)
├─ continew-starter-cache缓存模块
│ ├─ continew-starter-cache-redissonRedisson
│ ├─ continew-starter-cache-jetcacheJetCache 多级缓存)
│ └─ continew-starter-cache-springcacheSpring 缓存)
├─ continew-starter-auth认证模块 ├─ continew-starter-auth认证模块
│ ├─ continew-starter-auth-satoken国产轻量认证鉴权 │ ├─ continew-starter-auth-satoken国产轻量认证鉴权
│ └─ continew-starter-auth-justauth第三方登录 │ └─ continew-starter-auth-justauth第三方登录
@@ -160,10 +164,6 @@ continew-starter
│ ├─ continew-starter-data-core通用模块 │ ├─ continew-starter-data-core通用模块
│ ├─ continew-starter-data-mpMyBatis Plus │ ├─ continew-starter-data-mpMyBatis Plus
│ └─ continew-starter-data-mfMyBatis Flex │ └─ continew-starter-data-mfMyBatis Flex
├─ continew-starter-cache缓存模块
│ ├─ continew-starter-cache-redissonRedisson
│ ├─ continew-starter-cache-jetcacheJetCache 多级缓存)
│ └─ continew-starter-cache-springcacheSpring 缓存)
├─ continew-starter-security安全模块 ├─ continew-starter-security安全模块
│ ├─ continew-starter-security-crypto加密字段加解密 │ ├─ continew-starter-security-crypto加密字段加解密
│ ├─ continew-starter-security-xssXSS 过滤) │ ├─ continew-starter-security-xssXSS 过滤)
@@ -186,6 +186,10 @@ continew-starter
│ └─ continew-starter-file-excelEasy Excel │ └─ continew-starter-file-excelEasy Excel
├─ continew-starter-storage存储模块 ├─ continew-starter-storage存储模块
│ └─ continew-starter-storage-local本地存储 │ └─ continew-starter-storage-local本地存储
├─ continew-starter-licenseLicense 模块)
│ ├─ continew-starter-license-core核心模块
│ ├─ continew-starter-license-generatorLicense 生成器)
│ └─ continew-starter-license-verifierLicense 校验器)
└─ continew-starter-extension扩展模块 └─ continew-starter-extension扩展模块
├─ continew-starter-extension-datapermission数据权限模块 ├─ continew-starter-extension-datapermission数据权限模块
│ ├─ continew-starter-extension-datapermission-core通用模块 │ ├─ continew-starter-extension-datapermission-core通用模块

View File

@@ -10,19 +10,22 @@
</parent> </parent>
<artifactId>continew-starter-api-doc</artifactId> <artifactId>continew-starter-api-doc</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter API 文档模块</description> <description>ContiNew Starter API 文档模块</description>
<dependencies> <dependencies>
<!-- Knife4j前身是 swagger-bootstrap-ui集 Swagger2 和 OpenAPI3 为一体的增强解决方案) -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
</dependency>
<!-- 核心模块 --> <!-- 核心模块 -->
<dependency> <dependency>
<groupId>top.continew</groupId> <groupId>top.continew</groupId>
<artifactId>continew-starter-core</artifactId> <artifactId>continew-starter-core</artifactId>
</dependency> </dependency>
<!-- Knife4j前身是 swagger-bootstrap-ui集 Swagger2 和 OpenAPI3 为一体的增强解决方案) -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -66,6 +66,7 @@ import java.util.concurrent.TimeUnit;
@EnableConfigurationProperties(SpringDocExtensionProperties.class) @EnableConfigurationProperties(SpringDocExtensionProperties.class)
@PropertySource(value = "classpath:default-api-doc.yml", factory = GeneralPropertySourceFactory.class) @PropertySource(value = "classpath:default-api-doc.yml", factory = GeneralPropertySourceFactory.class)
public class SpringDocAutoConfiguration implements WebMvcConfigurer { public class SpringDocAutoConfiguration implements WebMvcConfigurer {
private static final Logger log = LoggerFactory.getLogger(SpringDocAutoConfiguration.class); private static final Logger log = LoggerFactory.getLogger(SpringDocAutoConfiguration.class);
@Override @Override

View File

@@ -10,9 +10,19 @@
</parent> </parent>
<artifactId>continew-starter-auth-justauth</artifactId> <artifactId>continew-starter-auth-justauth</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 认证模块 - JustAuth</description> <description>ContiNew Starter 认证模块 - JustAuth</description>
<dependencies> <dependencies>
<!-- 缓存模块 - Redisson -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-redisson</artifactId>
<optional>true</optional>
</dependency>
<!-- Just Auth开箱即用的整合第三方登录的开源组件脱离繁琐的第三方登录 SDK让登录变得 So easy! --> <!-- Just Auth开箱即用的整合第三方登录的开源组件脱离繁琐的第三方登录 SDK让登录变得 So easy! -->
<dependency> <dependency>
<groupId>me.zhyd.oauth</groupId> <groupId>me.zhyd.oauth</groupId>
@@ -23,12 +33,5 @@
<groupId>com.xkcoding.justauth</groupId> <groupId>com.xkcoding.justauth</groupId>
<artifactId>justauth-spring-boot-starter</artifactId> <artifactId>justauth-spring-boot-starter</artifactId>
</dependency> </dependency>
<!-- 缓存模块 - Redisson -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-redisson</artifactId>
<optional>true</optional>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -10,15 +10,12 @@
</parent> </parent>
<artifactId>continew-starter-auth-satoken</artifactId> <artifactId>continew-starter-auth-satoken</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 认证模块 - SaToken</description> <description>ContiNew Starter 认证模块 - SaToken</description>
<dependencies> <dependencies>
<!-- Web 模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-web</artifactId>
</dependency>
<!-- 缓存模块 - Redisson --> <!-- 缓存模块 - Redisson -->
<dependency> <dependency>
<groupId>top.continew</groupId> <groupId>top.continew</groupId>
@@ -30,12 +27,6 @@
<dependency> <dependency>
<groupId>cn.dev33</groupId> <groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId> <artifactId>sa-token-spring-boot3-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<!-- Sa-Token 整合 JWT --> <!-- Sa-Token 整合 JWT -->

View File

@@ -11,6 +11,8 @@
<artifactId>continew-starter-auth</artifactId> <artifactId>continew-starter-auth</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 认证模块</description> <description>ContiNew Starter 认证模块</description>
<modules> <modules>

View File

@@ -8,10 +8,12 @@
<artifactId>continew-starter-bom</artifactId> <artifactId>continew-starter-bom</artifactId>
<version>${revision}</version> <version>${revision}</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter BOM</description> <description>ContiNew Starter BOM</description>
<properties> <properties>
<revision>2.12.0</revision> <revision>2.12.2</revision>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
@@ -44,6 +46,25 @@
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<!-- 缓存模块 - Redisson -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-redisson</artifactId>
<version>${revision}</version>
</dependency>
<!-- 缓存模块 - Spring Cache -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-springcache</artifactId>
<version>${revision}</version>
</dependency>
<!-- 缓存模块 - JetCache -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-jetcache</artifactId>
<version>${revision}</version>
</dependency>
<!-- 认证模块 - SaToken --> <!-- 认证模块 - SaToken -->
<dependency> <dependency>
<groupId>top.continew</groupId> <groupId>top.continew</groupId>
@@ -76,25 +97,6 @@
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<!-- 缓存模块 - Redisson -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-redisson</artifactId>
<version>${revision}</version>
</dependency>
<!-- 缓存模块 - Spring Cache -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-springcache</artifactId>
<version>${revision}</version>
</dependency>
<!-- 缓存模块 - JetCache -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-jetcache</artifactId>
<version>${revision}</version>
</dependency>
<!-- 安全模块 - 密码编码器 --> <!-- 安全模块 - 密码编码器 -->
<dependency> <dependency>
<groupId>top.continew</groupId> <groupId>top.continew</groupId>
@@ -117,6 +119,7 @@
<dependency> <dependency>
<groupId>top.continew</groupId> <groupId>top.continew</groupId>
<artifactId>continew-starter-security-xss</artifactId> <artifactId>continew-starter-security-xss</artifactId>
<version>${revision}</version>
</dependency> </dependency>
<!-- 安全模块 - 敏感词 --> <!-- 安全模块 - 敏感词 -->
<dependency> <dependency>

View File

@@ -10,9 +10,18 @@
</parent> </parent>
<artifactId>continew-starter-cache-jetcache</artifactId> <artifactId>continew-starter-cache-jetcache</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 缓存模块 - JetCache</description> <description>ContiNew Starter 缓存模块 - JetCache</description>
<dependencies> <dependencies>
<!-- 缓存模块 - Redisson -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-redisson</artifactId>
</dependency>
<!-- JetCache基于 Java 的缓存系统封装,提供统一的 API 和注解来简化缓存的使用。提供了比 SpringCache 更加强大的注解,可以原生的支持 TTL、两级缓存、分布式自动刷新还提供了 Cache 接口用于手工缓存操作) --> <!-- JetCache基于 Java 的缓存系统封装,提供统一的 API 和注解来简化缓存的使用。提供了比 SpringCache 更加强大的注解,可以原生的支持 TTL、两级缓存、分布式自动刷新还提供了 Cache 接口用于手工缓存操作) -->
<dependency> <dependency>
<groupId>com.alicp.jetcache</groupId> <groupId>com.alicp.jetcache</groupId>
@@ -41,11 +50,5 @@
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId> <artifactId>commons-pool2</artifactId>
</dependency> </dependency>
<!-- 缓存模块 - Redisson -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-redisson</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -10,6 +10,9 @@
</parent> </parent>
<artifactId>continew-starter-cache-redisson</artifactId> <artifactId>continew-starter-cache-redisson</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 缓存模块 - Redisson</description> <description>ContiNew Starter 缓存模块 - Redisson</description>
<dependencies> <dependencies>

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.continew.starter.cache.redisson.util;
import cn.hutool.extra.spring.SpringUtil;
import org.redisson.api.*;
import java.util.concurrent.TimeUnit;
/**
* Redis 队列工具类
*
* @author Charles7c
* @since 2.12.1
*/
public class RedisQueueUtils {
private static final RedissonClient CLIENT = SpringUtil.getBean(RedissonClient.class);
private RedisQueueUtils() {
}
/**
* 获取 Redisson 客户端实例
*
* @return Redisson 客户端实例
*/
public static RedissonClient getClient() {
return CLIENT;
}
/**
* 获取阻塞队列实例
*
* @param key 键
* @return 阻塞队列实例
*/
public static <T> RBlockingQueue<T> getBlockingQueue(String key) {
return CLIENT.getBlockingQueue(key);
}
/**
* 添加元素到阻塞队列尾部
* <p>
* 如果队列已满,则返回 false
* </p>
*
* @param key 键
* @param value 值
* @return true添加成功false添加失败
*/
public static <T> boolean addBlockingQueueData(String key, T value) {
RBlockingQueue<T> queue = getBlockingQueue(key);
return queue.offer(value);
}
/**
* 获取并移除阻塞队列头部元素
* <p>
* 如果队列为空,则返回 null
* </p>
*
* @param key 键
* @return 队列头部元素,如果队列为空,则返回 null
*/
public static <T> T getBlockingQueueData(String key) {
RBlockingQueue<T> queue = getBlockingQueue(key);
return queue.poll();
}
/**
* 删除阻塞队列中的指定元素
*
* @param key 键
* @param value 值
* @return true删除成功false删除失败
*/
public static <T> boolean removeBlockingQueueData(String key, T value) {
RBlockingQueue<T> queue = getBlockingQueue(key);
return queue.remove(value);
}
/**
* 获取延迟队列实例
*
* @param key 键
* @return 延迟队列实例
*/
public static <T> RDelayedQueue<T> getDelayedQueue(String key) {
RBlockingQueue<T> blockingQueue = getBlockingQueue(key);
return CLIENT.getDelayedQueue(blockingQueue);
}
/**
* 添加元素到延迟队列
*
* @param key 键
* @param value 值
* @param delay 延迟时间
* @param unit 时间单位
*/
public static <T> void addDelayedQueueData(String key, T value, long delay, TimeUnit unit) {
RDelayedQueue<T> delayedQueue = getDelayedQueue(key);
delayedQueue.offer(value, delay, unit);
}
/**
* 获取并移除延迟队列头部元素
* <p>
* 如果队列为空,则返回 null
* </p>
*
* @param key 键
* @return 队列头部元素,如果队列为空,则返回 null
*/
public static <T> T getDelayedQueueData(String key) {
RDelayedQueue<T> delayedQueue = getDelayedQueue(key);
return delayedQueue.poll();
}
/**
* 移除延迟队列中的指定元素
*
* @param key 键
* @param value 值
* @return true移除成功false移除失败
*/
public static <T> boolean removeDelayedQueueData(String key, T value) {
RDelayedQueue<T> delayedQueue = getDelayedQueue(key);
return delayedQueue.remove(value);
}
}

View File

@@ -41,6 +41,15 @@ public class RedisUtils {
private RedisUtils() { private RedisUtils() {
} }
/**
* 获取 Redisson 客户端实例
*
* @return Redisson 客户端实例
*/
public static RedissonClient getClient() {
return CLIENT;
}
/** /**
* 设置缓存 * 设置缓存
* *

View File

@@ -10,6 +10,9 @@
</parent> </parent>
<artifactId>continew-starter-cache-springcache</artifactId> <artifactId>continew-starter-cache-springcache</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 缓存模块 - Spring Cache</description> <description>ContiNew Starter 缓存模块 - Spring Cache</description>
<dependencies> <dependencies>

View File

@@ -11,6 +11,8 @@
<artifactId>continew-starter-cache</artifactId> <artifactId>continew-starter-cache</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 缓存模块</description> <description>ContiNew Starter 缓存模块</description>
<modules> <modules>

View File

@@ -10,19 +10,22 @@
</parent> </parent>
<artifactId>continew-starter-captcha-behavior</artifactId> <artifactId>continew-starter-captcha-behavior</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 验证码模块 - 行为验证码</description> <description>ContiNew Starter 验证码模块 - 行为验证码</description>
<dependencies> <dependencies>
<!-- AJ-Captcha行为验证码包含滑动拼图、文字点选两种方式UI支持弹出和嵌入两种方式 -->
<dependency>
<groupId>com.anji-plus</groupId>
<artifactId>captcha</artifactId>
</dependency>
<!-- 缓存模块 - Redisson --> <!-- 缓存模块 - Redisson -->
<dependency> <dependency>
<groupId>top.continew</groupId> <groupId>top.continew</groupId>
<artifactId>continew-starter-cache-redisson</artifactId> <artifactId>continew-starter-cache-redisson</artifactId>
</dependency> </dependency>
<!-- AJ-Captcha行为验证码包含滑动拼图、文字点选两种方式UI支持弹出和嵌入两种方式 -->
<dependency>
<groupId>com.anji-plus</groupId>
<artifactId>captcha</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -10,6 +10,9 @@
</parent> </parent>
<artifactId>continew-starter-captcha-graphic</artifactId> <artifactId>continew-starter-captcha-graphic</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 验证码模块 - 图形验证码</description> <description>ContiNew Starter 验证码模块 - 图形验证码</description>
<dependencies> <dependencies>

View File

@@ -11,6 +11,8 @@
<artifactId>continew-starter-captcha</artifactId> <artifactId>continew-starter-captcha</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 验证码模块</description> <description>ContiNew Starter 验证码模块</description>
<modules> <modules>

View File

@@ -10,6 +10,9 @@
</parent> </parent>
<artifactId>continew-starter-core</artifactId> <artifactId>continew-starter-core</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 核心模块</description> <description>ContiNew Starter 核心模块</description>
<dependencies> <dependencies>
@@ -29,6 +32,24 @@
<artifactId>spring-boot-starter-aop</artifactId> <artifactId>spring-boot-starter-aop</artifactId>
</dependency> </dependency>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<!-- Spring MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<!-- Jakarta Servlet原 Javax Servlet -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
</dependency>
<!-- Hibernate Validator --> <!-- Hibernate Validator -->
<dependency> <dependency>
<groupId>org.hibernate.validator</groupId> <groupId>org.hibernate.validator</groupId>

View File

@@ -24,6 +24,7 @@ import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function;
/** /**
* 异常工具类 * 异常工具类
@@ -44,7 +45,7 @@ public class ExceptionUtils {
* @param throwable 异常 * @param throwable 异常
*/ */
public static void printException(Runnable runnable, Throwable throwable) { public static void printException(Runnable runnable, Throwable throwable) {
if (null == throwable && runnable instanceof Future<?> future) { if (throwable == null && runnable instanceof Future<?> future) {
try { try {
if (future.isDone()) { if (future.isDone()) {
future.get(); future.get();
@@ -107,6 +108,25 @@ public class ExceptionUtils {
return exToDefault(exSupplier, defaultValue, null); return exToDefault(exSupplier, defaultValue, null);
} }
/**
* 如果有异常,抛出自定义异常
*
* @param exSupplier 可能会出现异常的方法执行
* @param exceptionMapper 异常转换函数
* @param <T> 返回值类型
* @param <E> 自定义异常类型
* @return 执行结果
* @throws E 自定义异常
*/
public static <T, E extends Exception> T exToThrow(ExSupplier<T> exSupplier,
Function<Exception, E> exceptionMapper) throws E {
try {
return exSupplier.get();
} catch (Exception e) {
throw exceptionMapper.apply(e);
}
}
/** /**
* 如果有异常,执行异常处理,返回默认值 * 如果有异常,执行异常处理,返回默认值
* *
@@ -140,5 +160,6 @@ public class ExceptionUtils {
* @throws Exception / * @throws Exception /
*/ */
T get() throws Exception; T get() throws Exception;
} }
} }

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.web.util; package top.continew.starter.core.util;
import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
@@ -43,6 +43,7 @@ import java.time.LocalDateTime;
* @since 1.0.0 * @since 1.0.0
*/ */
public class FileUploadUtils { public class FileUploadUtils {
private static final Logger log = LoggerFactory.getLogger(FileUploadUtils.class); private static final Logger log = LoggerFactory.getLogger(FileUploadUtils.class);
private FileUploadUtils() { private FileUploadUtils() {

View File

@@ -50,7 +50,7 @@ public class IpUtils {
} }
Ip2regionSearcher ip2regionSearcher = SpringUtil.getBean(Ip2regionSearcher.class); Ip2regionSearcher ip2regionSearcher = SpringUtil.getBean(Ip2regionSearcher.class);
IpInfo ipInfo = ip2regionSearcher.memorySearch(ip); IpInfo ipInfo = ip2regionSearcher.memorySearch(ip);
if (null == ipInfo) { if (ipInfo == null) {
return null; return null;
} }
Set<String> regionSet = CollUtil.newLinkedHashSet(ipInfo.getCountry(), ipInfo.getRegion(), ipInfo Set<String> regionSet = CollUtil.newLinkedHashSet(ipInfo.getCountry(), ipInfo.getRegion(), ipInfo

View File

@@ -14,13 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.web.util; package top.continew.starter.core.util;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.extra.servlet.JakartaServletUtil; import cn.hutool.extra.servlet.JakartaServletUtil;
import cn.hutool.http.useragent.UserAgent; import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil; import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.json.JSONUtil;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
@@ -29,7 +30,8 @@ import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.util.UriUtils; import org.springframework.web.util.UriUtils;
import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.json.jackson.util.JSONUtils; import top.continew.starter.core.wrapper.RepeatReadRequestWrapper;
import top.continew.starter.core.wrapper.RepeatReadResponseWrapper;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
@@ -55,7 +57,7 @@ public class ServletUtils extends JakartaServletUtil {
* @return 浏览器及其版本信息 * @return 浏览器及其版本信息
*/ */
public static String getBrowser(HttpServletRequest request) { public static String getBrowser(HttpServletRequest request) {
if (null == request) { if (request == null) {
return null; return null;
} }
return getBrowser(request.getHeader("User-Agent")); return getBrowser(request.getHeader("User-Agent"));
@@ -68,8 +70,17 @@ public class ServletUtils extends JakartaServletUtil {
* @return 浏览器及其版本信息 * @return 浏览器及其版本信息
*/ */
public static String getBrowser(String userAgentString) { public static String getBrowser(String userAgentString) {
UserAgent userAgent = UserAgentUtil.parse(userAgentString); try {
return userAgent.getBrowser().getName() + StringConstants.SPACE + userAgent.getVersion(); UserAgent userAgent = UserAgentUtil.parse(userAgentString);
if (userAgent == null || userAgent.getBrowser() == null) {
return null;
}
String browserName = userAgent.getBrowser().getName();
String version = userAgent.getVersion();
return CharSequenceUtil.isBlank(version) ? browserName : browserName + StringConstants.SPACE + version;
} catch (Exception e) {
return null;
}
} }
/** /**
@@ -79,7 +90,7 @@ public class ServletUtils extends JakartaServletUtil {
* @return 操作系统 * @return 操作系统
*/ */
public static String getOs(HttpServletRequest request) { public static String getOs(HttpServletRequest request) {
if (null == request) { if (request == null) {
return null; return null;
} }
return getOs(request.getHeader("User-Agent")); return getOs(request.getHeader("User-Agent"));
@@ -92,8 +103,15 @@ public class ServletUtils extends JakartaServletUtil {
* @return 操作系统 * @return 操作系统
*/ */
public static String getOs(String userAgentString) { public static String getOs(String userAgentString) {
UserAgent userAgent = UserAgentUtil.parse(userAgentString); try {
return userAgent.getOs().getName(); UserAgent userAgent = UserAgentUtil.parse(userAgentString);
if (userAgent == null || userAgent.getOs() == null) {
return null;
}
return userAgent.getOs().getName();
} catch (Exception e) {
return null;
}
} }
/** /**
@@ -188,7 +206,7 @@ public class ServletUtils extends JakartaServletUtil {
HttpServletRequest request = getRequest(); HttpServletRequest request = getRequest();
if (request instanceof RepeatReadRequestWrapper wrapper && !wrapper.isMultipartContent(request)) { if (request instanceof RepeatReadRequestWrapper wrapper && !wrapper.isMultipartContent(request)) {
String body = JakartaServletUtil.getBody(request); String body = JakartaServletUtil.getBody(request);
return JSONUtils.isTypeJSON(body) ? body : null; return JSONUtil.isTypeJSON(body) ? body : null;
} }
return null; return null;
} }
@@ -201,8 +219,8 @@ public class ServletUtils extends JakartaServletUtil {
*/ */
public static Map<String, Object> getRequestParams() { public static Map<String, Object> getRequestParams() {
String body = getRequestBody(); String body = getRequestBody();
return CharSequenceUtil.isNotBlank(body) && JSONUtils.isTypeJSON(body) return CharSequenceUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body)
? JSONUtils.toBean(body, Map.class) ? JSONUtil.toBean(body, Map.class)
: Collections.unmodifiableMap(JakartaServletUtil.getParamMap(Objects.requireNonNull(getRequest()))); : Collections.unmodifiableMap(JakartaServletUtil.getParamMap(Objects.requireNonNull(getRequest())));
} }
@@ -246,7 +264,7 @@ public class ServletUtils extends JakartaServletUtil {
HttpServletResponse response = getResponse(); HttpServletResponse response = getResponse();
if (response instanceof RepeatReadResponseWrapper wrapper && !wrapper.isStreamingResponse()) { if (response instanceof RepeatReadResponseWrapper wrapper && !wrapper.isStreamingResponse()) {
String body = wrapper.getResponseContent(); String body = wrapper.getResponseContent();
return JSONUtils.isTypeJSON(body) ? body : null; return JSONUtil.isTypeJSON(body) ? body : null;
} }
return null; return null;
} }
@@ -259,9 +277,7 @@ public class ServletUtils extends JakartaServletUtil {
*/ */
public static Map<String, Object> getResponseParams() { public static Map<String, Object> getResponseParams() {
String body = getResponseBody(); String body = getResponseBody();
return CharSequenceUtil.isNotBlank(body) && JSONUtils.isTypeJSON(body) return CharSequenceUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body) ? JSONUtil.toBean(body, Map.class) : null;
? JSONUtils.toBean(body, Map.class)
: null;
} }
/** /**

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.web.util; package top.continew.starter.core.util;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ReflectUtil;

View File

@@ -48,7 +48,7 @@ public class Validator {
* @param exceptionType 异常类型 * @param exceptionType 异常类型
*/ */
protected static void throwIfNull(Object obj, String message, Class<? extends RuntimeException> exceptionType) { protected static void throwIfNull(Object obj, String message, Class<? extends RuntimeException> exceptionType) {
throwIf(null == obj, message, exceptionType); throwIf(obj == null, message, exceptionType);
} }
/** /**

View File

@@ -0,0 +1,231 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.core.wrapper;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.springframework.http.MediaType;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.util.StreamUtils;
import top.continew.starter.core.constant.StringConstants;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* 可重复读取请求体的包装器 支持文件流直接透传,非文件流可重复读取
* <p>
* 虽然这里可以多次读取流里面的数据, 但是建议还是调用getContentAsString()/getCachedContent() 方法, 已经把内容缓存在内存中了。
* </p>
*
* @author Jasmine
* @since 2.12.1
*/
public class RepeatReadRequestWrapper extends HttpServletRequestWrapper {
/**
* 缓存内容
*/
private final FastByteArrayOutputStream cachedContent;
/*** 用于缓存输入流 */
private ContentCachingInputStream contentCachingInputStream;
/**
* 字符编码
*/
private final String characterEncoding;
// private BufferedReader reader;
/**
* Constructs a request object wrapping the given request.
*
* @param request the {@link HttpServletRequest} to be wrapped.
* @throws IllegalArgumentException if the request is null
*/
public RepeatReadRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
this.characterEncoding = request.getCharacterEncoding() != null
? request.getCharacterEncoding()
: StandardCharsets.UTF_8.name();
int contentLength = super.getRequest().getContentLength();
cachedContent = (contentLength > 0)
? new FastByteArrayOutputStream(contentLength)
: new FastByteArrayOutputStream();
// 判断是否为文件上传请求
if (!isMultipartContent(request)) {
if (isFormRequest()) {
writeRequestParametersToCachedContent();
} else {
StreamUtils.copy(request.getInputStream(), cachedContent);
}
contentCachingInputStream = new ContentCachingInputStream(cachedContent.toByteArray());
}
}
@Override
public ServletInputStream getInputStream() throws IOException {
// 如果是文件上传,直接返回原始输入流
if (isMultipartContent(super.getRequest())) {
return super.getRequest().getInputStream();
}
synchronized (this) {
contentCachingInputStream.reset();
return contentCachingInputStream;
}
}
@Override
public BufferedReader getReader() throws IOException {
// 如果是文件上传直接返回原始Reader
if (isMultipartContent(super.getRequest())) {
return super.getRequest().getReader();
}
// BufferedReader不支持多次reset()(除非手动调用 mark() 并控制其生命周期最安全的方式是每次调用getReader()时基于缓存内容重新创建一个新的BufferedReader实例。
synchronized (this) {
return new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding()));
}
}
private void writeRequestParametersToCachedContent() {
try {
if (this.cachedContent.size() == 0) {
Map<String, String[]> form = super.getParameterMap();
for (Iterator<String> nameIterator = form.keySet().iterator(); nameIterator.hasNext();) {
String name = nameIterator.next();
List<String> values = Arrays.asList(form.get(name));
for (Iterator<String> valueIterator = values.iterator(); valueIterator.hasNext();) {
String value = valueIterator.next();
this.cachedContent.write(URLEncoder.encode(name, characterEncoding).getBytes());
if (value != null) {
this.cachedContent.write(StringConstants.EQUALS.getBytes());
this.cachedContent.write(URLEncoder.encode(value, characterEncoding).getBytes());
if (valueIterator.hasNext()) {
this.cachedContent.write(StringConstants.AMP.getBytes());
}
}
}
if (nameIterator.hasNext()) {
this.cachedContent.write(StringConstants.AMP.getBytes());
}
}
}
} catch (IOException ex) {
throw new IllegalStateException("Failed to write request parameters to cached content", ex);
}
}
@Override
public String getCharacterEncoding() {
return this.characterEncoding;
}
public String getContentAsString() {
return this.cachedContent.toString(Charset.forName(getCharacterEncoding()));
}
public FastByteArrayOutputStream getCachedContent() {
return cachedContent;
}
/**
* 判断当前请求是否为 multipart/form-data 类型的文件上传请求。 该类型一般用于表单上传文件的场景,例如 enctype="multipart/form-data"。
*
* @param request 当前 HTTP 请求对象
* @return true 表示为 multipart 文件上传请求;否则为 false
*/
public boolean isMultipartContent(ServletRequest request) {
String contentType = request.getContentType();
return contentType != null && contentType.toLowerCase().startsWith("multipart/");
}
private boolean isFormRequest() {
String contentType = getContentType();
return (contentType != null && contentType.contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE));
}
/**
* Body 缓存的ServletInputStream实现 DefaultServerRequestBuilder.BodyInputStream
*/
private static class ContentCachingInputStream extends ServletInputStream {
private final InputStream delegate;
public ContentCachingInputStream(byte[] body) {
this.delegate = new ByteArrayInputStream(body);
}
public boolean isFinished() {
return false;
}
public boolean isReady() {
return true;
}
public void setReadListener(ReadListener readListener) {
throw new UnsupportedOperationException();
}
public int read() throws IOException {
return this.delegate.read();
}
public int read(byte[] b, int off, int len) throws IOException {
return this.delegate.read(b, off, len);
}
public int read(byte[] b) throws IOException {
return this.delegate.read(b);
}
public long skip(long n) throws IOException {
return this.delegate.skip(n);
}
public int available() throws IOException {
return this.delegate.available();
}
public void close() throws IOException {
this.delegate.close();
}
public synchronized void mark(int readlimit) {
this.delegate.mark(readlimit);
}
public synchronized void reset() throws IOException {
this.delegate.reset();
}
public boolean markSupported() {
return this.delegate.markSupported();
}
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.web.util; package top.continew.starter.core.wrapper;
import jakarta.servlet.ServletOutputStream; import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.WriteListener; import jakarta.servlet.WriteListener;

View File

@@ -10,6 +10,9 @@
</parent> </parent>
<artifactId>continew-starter-data-core</artifactId> <artifactId>continew-starter-data-core</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 数据访问模块 - 核心模块</description> <description>ContiNew Starter 数据访问模块 - 核心模块</description>
<dependencies> <dependencies>

View File

@@ -57,7 +57,7 @@ public class MetaUtils {
*/ */
public static DatabaseType getDatabaseTypeOrDefault(DataSource dataSource, DatabaseType defaultValue) { public static DatabaseType getDatabaseTypeOrDefault(DataSource dataSource, DatabaseType defaultValue) {
DatabaseType databaseType = getDatabaseType(dataSource); DatabaseType databaseType = getDatabaseType(dataSource);
return null == databaseType ? defaultValue : databaseType; return databaseType == null ? defaultValue : databaseType;
} }
/** /**

View File

@@ -10,12 +10,16 @@
</parent> </parent>
<artifactId>continew-starter-data-mf</artifactId> <artifactId>continew-starter-data-mf</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 数据访问模块 - MyBatis Flex</description> <description>ContiNew Starter 数据访问模块 - MyBatis Flex</description>
<dependencies> <dependencies>
<!-- 数据访问模块 - 核心模块 -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>top.continew</groupId>
<artifactId>spring-boot-starter-aop</artifactId> <artifactId>continew-starter-data-core</artifactId>
</dependency> </dependency>
<!-- MyBatis PlusMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) --> <!-- MyBatis PlusMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
@@ -29,12 +33,6 @@
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<!-- 数据访问模块 - 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-data-core</artifactId>
</dependency>
<!-- CosId通用、灵活、高性能的分布式 ID 生成器) --> <!-- CosId通用、灵活、高性能的分布式 ID 生成器) -->
<dependency> <dependency>
<groupId>me.ahoo.cosid</groupId> <groupId>me.ahoo.cosid</groupId>

View File

@@ -43,7 +43,7 @@ public class DataPermissionDialect extends CommonsDialectImpl {
return super.buildSelectSql(queryWrapper); return super.buildSelectSql(queryWrapper);
} }
DataPermission dataPermission = DataPermissionAspect.currentDataPermission(); DataPermission dataPermission = DataPermissionAspect.currentDataPermission();
if (null == dataPermission) { if (dataPermission == null) {
return super.buildSelectSql(queryWrapper); return super.buildSelectSql(queryWrapper);
} }
DataPermissionCurrentUser currentUser = dataPermissionFilter.getCurrentUser(); DataPermissionCurrentUser currentUser = dataPermissionFilter.getCurrentUser();

View File

@@ -65,7 +65,7 @@ public class QueryWrapperHelper {
public static <Q> QueryWrapper build(Q query) { public static <Q> QueryWrapper build(Q query) {
QueryWrapper queryWrapper = QueryWrapper.create(); QueryWrapper queryWrapper = QueryWrapper.create();
// 没有查询条件,直接返回 // 没有查询条件,直接返回
if (null == query) { if (query == null) {
return queryWrapper; return queryWrapper;
} }
// 获取查询条件中所有的字段 // 获取查询条件中所有的字段
@@ -85,7 +85,7 @@ public class QueryWrapperHelper {
public static <Q> QueryWrapper build(Q query, Sort sort) { public static <Q> QueryWrapper build(Q query, Sort sort) {
QueryWrapper queryWrapper = QueryWrapper.create(); QueryWrapper queryWrapper = QueryWrapper.create();
// 没有查询条件,直接返回 // 没有查询条件,直接返回
if (null == query) { if (query == null) {
return queryWrapper; return queryWrapper;
} }
// 设置排序条件 // 设置排序条件
@@ -112,7 +112,7 @@ public class QueryWrapperHelper {
*/ */
public static <Q> QueryWrapper build(Q query, List<Field> fields, QueryWrapper queryWrapper) { public static <Q> QueryWrapper build(Q query, List<Field> fields, QueryWrapper queryWrapper) {
// 没有查询条件,直接返回 // 没有查询条件,直接返回
if (null == query) { if (query == null) {
return queryWrapper; return queryWrapper;
} }
// 解析并拼接查询条件 // 解析并拼接查询条件
@@ -150,7 +150,7 @@ public class QueryWrapperHelper {
String fieldName = ReflectUtil.getFieldName(field); String fieldName = ReflectUtil.getFieldName(field);
// 没有 @Query 注解,默认等值查询 // 没有 @Query 注解,默认等值查询
Query queryAnnotation = AnnotationUtil.getAnnotation(field, Query.class); Query queryAnnotation = AnnotationUtil.getAnnotation(field, Query.class);
if (null == queryAnnotation) { if (queryAnnotation == null) {
return Collections.singletonList(q -> q.eq(CharSequenceUtil.toUnderlineCase(fieldName), fieldValue)); return Collections.singletonList(q -> q.eq(CharSequenceUtil.toUnderlineCase(fieldName), fieldValue));
} }
// 解析单列查询 // 解析单列查询

View File

@@ -10,9 +10,18 @@
</parent> </parent>
<artifactId>continew-starter-data-mp</artifactId> <artifactId>continew-starter-data-mp</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 数据访问模块 - MyBatis Plus</description> <description>ContiNew Starter 数据访问模块 - MyBatis Plus</description>
<dependencies> <dependencies>
<!-- 数据访问模块 - 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-data-core</artifactId>
</dependency>
<!-- MyBatis PlusMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) --> <!-- MyBatis PlusMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
@@ -25,12 +34,6 @@
<artifactId>p6spy</artifactId> <artifactId>p6spy</artifactId>
</dependency> </dependency>
<!-- 数据访问模块 - 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-data-core</artifactId>
</dependency>
<!-- CosId通用、灵活、高性能的分布式 ID 生成器) --> <!-- CosId通用、灵活、高性能的分布式 ID 生成器) -->
<dependency> <dependency>
<groupId>me.ahoo.cosid</groupId> <groupId>me.ahoo.cosid</groupId>

View File

@@ -121,7 +121,7 @@ public class MybatisBaseEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandl
@Override @Override
public E getNullableResult(ResultSet rs, String columnName) throws SQLException { public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
Object value = rs.getObject(columnName, this.propertyType); Object value = rs.getObject(columnName, this.propertyType);
if (null == value || rs.wasNull()) { if (value == null || rs.wasNull()) {
return null; return null;
} }
return this.valueOf(value); return this.valueOf(value);
@@ -130,7 +130,7 @@ public class MybatisBaseEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandl
@Override @Override
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException { public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
Object value = rs.getObject(columnIndex, this.propertyType); Object value = rs.getObject(columnIndex, this.propertyType);
if (null == value || rs.wasNull()) { if (value == null || rs.wasNull()) {
return null; return null;
} }
return this.valueOf(value); return this.valueOf(value);
@@ -139,7 +139,7 @@ public class MybatisBaseEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandl
@Override @Override
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
Object value = cs.getObject(columnIndex, this.propertyType); Object value = cs.getObject(columnIndex, this.propertyType);
if (null == value || cs.wasNull()) { if (value == null || cs.wasNull()) {
return null; return null;
} }
return this.valueOf(value); return this.valueOf(value);

View File

@@ -97,7 +97,7 @@ public class QueryWrapperHelper {
public static <Q, R> QueryWrapper<R> build(Q query, Sort sort) { public static <Q, R> QueryWrapper<R> build(Q query, Sort sort) {
QueryWrapper<R> queryWrapper = new QueryWrapper<>(); QueryWrapper<R> queryWrapper = new QueryWrapper<>();
// 没有查询条件,直接返回 // 没有查询条件,直接返回
if (null == query) { if (query == null) {
return queryWrapper; return queryWrapper;
} }
// 设置排序条件 // 设置排序条件
@@ -125,7 +125,7 @@ public class QueryWrapperHelper {
*/ */
public static <Q, R> QueryWrapper<R> build(Q query, List<Field> fields, QueryWrapper<R> queryWrapper) { public static <Q, R> QueryWrapper<R> build(Q query, List<Field> fields, QueryWrapper<R> queryWrapper) {
// 没有查询条件,直接返回 // 没有查询条件,直接返回
if (null == query) { if (query == null) {
return queryWrapper; return queryWrapper;
} }
// 解析并拼接查询条件 // 解析并拼接查询条件
@@ -161,7 +161,7 @@ public class QueryWrapperHelper {
String fieldName = ReflectUtil.getFieldName(field); String fieldName = ReflectUtil.getFieldName(field);
// 没有 @Query 注解,默认等值查询 // 没有 @Query 注解,默认等值查询
Query queryAnnotation = AnnotationUtil.getAnnotation(field, Query.class); Query queryAnnotation = AnnotationUtil.getAnnotation(field, Query.class);
if (null == queryAnnotation) { if (queryAnnotation == null) {
return Collections.singletonList(q -> q.eq(CharSequenceUtil.toUnderlineCase(fieldName), fieldValue)); return Collections.singletonList(q -> q.eq(CharSequenceUtil.toUnderlineCase(fieldName), fieldValue));
} }
// 解析单列查询 // 解析单列查询

View File

@@ -11,6 +11,8 @@
<artifactId>continew-starter-data</artifactId> <artifactId>continew-starter-data</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 数据访问模块</description> <description>ContiNew Starter 数据访问模块</description>
<modules> <modules>

View File

@@ -8,11 +8,13 @@
<artifactId>continew-starter-dependencies</artifactId> <artifactId>continew-starter-dependencies</artifactId>
<version>${revision}</version> <version>${revision}</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 依赖模块</description> <description>ContiNew Starter 依赖模块</description>
<properties> <properties>
<!-- 项目版本号 --> <!-- 项目版本号 -->
<revision>2.12.0</revision> <revision>2.12.2</revision>
<spring-boot.version>3.3.11</spring-boot.version> <spring-boot.version>3.3.11</spring-boot.version>
<spring-cloud.version>2023.0.5</spring-cloud.version> <spring-cloud.version>2023.0.5</spring-cloud.version>
<redisson.version>3.46.0</redisson.version> <redisson.version>3.46.0</redisson.version>

View File

@@ -10,18 +10,16 @@
</parent> </parent>
<artifactId>continew-starter-extension-crud-core</artifactId> <artifactId>continew-starter-extension-crud-core</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 扩展模块 - CRUD增删改查 - 核心模块</description> <description>ContiNew Starter 扩展模块 - CRUD增删改查 - 核心模块</description>
<dependencies> <dependencies>
<!-- Crane4j基于注解的用于完成一切 “根据 A 的 key 值拿到 B再把 B 的属性映射到 A” 这类需求的字段填充框架) --> <!-- API 文档模块 -->
<dependency> <dependency>
<groupId>cn.crane4j</groupId> <groupId>top.continew</groupId>
<artifactId>crane4j-spring-boot-starter</artifactId> <artifactId>continew-starter-api-doc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency> </dependency>
<!-- Web 模块 --> <!-- Web 模块 -->
@@ -42,10 +40,10 @@
<artifactId>continew-starter-file-excel</artifactId> <artifactId>continew-starter-file-excel</artifactId>
</dependency> </dependency>
<!-- API 文档模块 --> <!-- Crane4j基于注解的用于完成一切 “根据 A 的 key 值拿到 B再把 B 的属性映射到 A” 这类需求的字段填充框架) -->
<dependency> <dependency>
<groupId>top.continew</groupId> <groupId>cn.crane4j</groupId>
<artifactId>continew-starter-api-doc</artifactId> <artifactId>crane4j-spring-boot-starter</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -42,7 +42,7 @@ public class CrudRequestMappingHandlerMapping extends RequestMappingHandlerMappi
@Override @Override
protected RequestMappingInfo getMappingForMethod(@NonNull Method method, @NonNull Class<?> handlerType) { protected RequestMappingInfo getMappingForMethod(@NonNull Method method, @NonNull Class<?> handlerType) {
RequestMappingInfo requestMappingInfo = super.getMappingForMethod(method, handlerType); RequestMappingInfo requestMappingInfo = super.getMappingForMethod(method, handlerType);
if (null == requestMappingInfo) { if (requestMappingInfo == null) {
return null; return null;
} }
// 如果没有声明 @CrudRequestMapping 注解,直接返回 // 如果没有声明 @CrudRequestMapping 注解,直接返回

View File

@@ -10,6 +10,9 @@
</parent> </parent>
<artifactId>continew-starter-extension-crud-mf</artifactId> <artifactId>continew-starter-extension-crud-mf</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 扩展模块 - CRUD增删改查 - MyBatis Flex ORM 模块</description> <description>ContiNew Starter 扩展模块 - CRUD增删改查 - MyBatis Flex ORM 模块</description>
<dependencies> <dependencies>

View File

@@ -56,7 +56,7 @@ public class PageResp<L> extends BasePageResp<L> {
* @return 分页信息 * @return 分页信息
*/ */
public static <T, L> PageResp<L> build(Page<T> page, Class<L> targetClass) { public static <T, L> PageResp<L> build(Page<T> page, Class<L> targetClass) {
if (null == page) { if (page == null) {
return empty(); return empty();
} }
return new PageResp<>(BeanUtil.copyToList(page.getRecords(), targetClass), page.getTotalRow()); return new PageResp<>(BeanUtil.copyToList(page.getRecords(), targetClass), page.getTotalRow());
@@ -70,7 +70,7 @@ public class PageResp<L> extends BasePageResp<L> {
* @return 分页信息 * @return 分页信息
*/ */
public static <L> PageResp<L> build(Page<L> page) { public static <L> PageResp<L> build(Page<L> page) {
if (null == page) { if (page == null) {
return empty(); return empty();
} }
return new PageResp<>(page.getRecords(), page.getTotalRow()); return new PageResp<>(page.getRecords(), page.getTotalRow());

View File

@@ -220,7 +220,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
* @param obj 待填充信息 * @param obj 待填充信息
*/ */
protected void fill(Object obj) { protected void fill(Object obj) {
if (null == obj) { if (obj == null) {
return; return;
} }
OperateTemplate operateTemplate = SpringUtil.getBean(OperateTemplate.class); OperateTemplate operateTemplate = SpringUtil.getBean(OperateTemplate.class);

View File

@@ -10,6 +10,9 @@
</parent> </parent>
<artifactId>continew-starter-extension-crud-mp</artifactId> <artifactId>continew-starter-extension-crud-mp</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 扩展模块 - CRUD增删改查 - MyBatis Plus ORM 模块</description> <description>ContiNew Starter 扩展模块 - CRUD增删改查 - MyBatis Plus ORM 模块</description>
<dependencies> <dependencies>

View File

@@ -56,7 +56,7 @@ public class PageResp<L> extends BasePageResp<L> {
* @return 分页信息 * @return 分页信息
*/ */
public static <T, L> PageResp<L> build(IPage<T> page, Class<L> targetClass) { public static <T, L> PageResp<L> build(IPage<T> page, Class<L> targetClass) {
if (null == page) { if (page == null) {
return empty(); return empty();
} }
return new PageResp<>(BeanUtil.copyToList(page.getRecords(), targetClass), page.getTotal()); return new PageResp<>(BeanUtil.copyToList(page.getRecords(), targetClass), page.getTotal());
@@ -70,7 +70,7 @@ public class PageResp<L> extends BasePageResp<L> {
* @return 分页信息 * @return 分页信息
*/ */
public static <L> PageResp<L> build(IPage<L> page) { public static <L> PageResp<L> build(IPage<L> page) {
if (null == page) { if (page == null) {
return empty(); return empty();
} }
return new PageResp<>(page.getRecords(), page.getTotal()); return new PageResp<>(page.getRecords(), page.getTotal());

View File

@@ -311,7 +311,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
* @param obj 待填充信息 * @param obj 待填充信息
*/ */
protected void fill(Object obj) { protected void fill(Object obj) {
if (null == obj) { if (obj == null) {
return; return;
} }
OperateTemplate operateTemplate = SpringUtil.getBean(OperateTemplate.class); OperateTemplate operateTemplate = SpringUtil.getBean(OperateTemplate.class);

View File

@@ -11,6 +11,8 @@
<artifactId>continew-starter-extension-crud</artifactId> <artifactId>continew-starter-extension-crud</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 扩展模块 - CRUD增删改查</description> <description>ContiNew Starter 扩展模块 - CRUD增删改查</description>
<modules> <modules>

View File

@@ -10,5 +10,8 @@
</parent> </parent>
<artifactId>continew-starter-extension-datapermission-core</artifactId> <artifactId>continew-starter-extension-datapermission-core</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 扩展模块 - 数据权限 - 核心模块</description> <description>ContiNew Starter 扩展模块 - 数据权限 - 核心模块</description>
</project> </project>

View File

@@ -10,6 +10,9 @@
</parent> </parent>
<artifactId>continew-starter-extension-datapermission-mp</artifactId> <artifactId>continew-starter-extension-datapermission-mp</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 扩展模块 - 数据权限 - MyBatis Plus ORM 模块</description> <description>ContiNew Starter 扩展模块 - 数据权限 - MyBatis Plus ORM 模块</description>
<dependencies> <dependencies>

View File

@@ -36,11 +36,7 @@ import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression; import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression; import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo; import net.sf.jsqlparser.expression.operators.relational.*;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.ParenthesedSelect; import net.sf.jsqlparser.statement.select.ParenthesedSelect;
@@ -82,7 +78,7 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
for (Method method : methodArr) { for (Method method : methodArr) {
DataPermission dataPermission = method.getAnnotation(DataPermission.class); DataPermission dataPermission = method.getAnnotation(DataPermission.class);
String name = method.getName(); String name = method.getName();
if (null == dataPermission || !CharSequenceUtil.equalsAny(methodName, name, name + "_COUNT")) { if (dataPermission == null || !CharSequenceUtil.equalsAny(methodName, name, name + "_COUNT")) {
continue; continue;
} }
if (dataPermissionUserContextProvider.isFilter()) { if (dataPermissionUserContextProvider.isFilter()) {
@@ -153,8 +149,8 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
if (DatabaseType.MYSQL.getDatabase().equalsIgnoreCase(databaseType.getDatabase())) { if (DatabaseType.MYSQL.getDatabase().equalsIgnoreCase(databaseType.getDatabase())) {
Function findInSetFunction = new Function(); Function findInSetFunction = new Function();
findInSetFunction.setName("find_in_set"); findInSetFunction.setName("find_in_set");
findInSetFunction.setParameters(new ExpressionList<>(new LongValue(userContext findInSetFunction.setParameters(new ExpressionList(new LongValue(userContext
.getDeptId()), new StringValue(new Column("ancestors") + ","))); .getDeptId()), new Column("ancestors")));
inSetExpression = findInSetFunction; inSetExpression = findInSetFunction;
} else if (DatabaseType.POSTGRE_SQL.getDatabase().equalsIgnoreCase(databaseType.getDatabase())) { } else if (DatabaseType.POSTGRE_SQL.getDatabase().equalsIgnoreCase(databaseType.getDatabase())) {
// 构建 concat 函数 // 构建 concat 函数

View File

@@ -11,6 +11,8 @@
<artifactId>continew-starter-extension-datapermission</artifactId> <artifactId>continew-starter-extension-datapermission</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 扩展模块 - 数据权限</description> <description>ContiNew Starter 扩展模块 - 数据权限</description>
<modules> <modules>

View File

@@ -10,6 +10,9 @@
</parent> </parent>
<artifactId>continew-starter-extension-tenant-core</artifactId> <artifactId>continew-starter-extension-tenant-core</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 扩展模块 - 多租户 - 核心模块</description> <description>ContiNew Starter 扩展模块 - 多租户 - 核心模块</description>
<dependencies> <dependencies>
@@ -18,17 +21,5 @@
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId> <artifactId>transmittable-thread-local</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<optional>true</optional>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -10,6 +10,9 @@
</parent> </parent>
<artifactId>continew-starter-extension-tenant-mp</artifactId> <artifactId>continew-starter-extension-tenant-mp</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 扩展模块 - 多租户 - MyBatis Plus ORM 模块</description> <description>ContiNew Starter 扩展模块 - 多租户 - MyBatis Plus ORM 模块</description>
<dependencies> <dependencies>

View File

@@ -40,6 +40,8 @@ import top.continew.starter.extension.tenant.handler.datasource.TenantDataSource
import top.continew.starter.extension.tenant.handler.datasource.TenantDataSourceInterceptor; import top.continew.starter.extension.tenant.handler.datasource.TenantDataSourceInterceptor;
import top.continew.starter.extension.tenant.handler.line.DefaultTenantLineHandler; import top.continew.starter.extension.tenant.handler.line.DefaultTenantLineHandler;
import javax.sql.DataSource;
/** /**
* 租户自动配置 * 租户自动配置
* *
@@ -72,8 +74,8 @@ public class TenantAutoConfiguration {
*/ */
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public TenantLineHandler tenantLineHandler(TenantProperties properties) { public TenantLineHandler tenantLineHandler() {
return new DefaultTenantLineHandler(properties); return new DefaultTenantLineHandler(tenantProperties);
} }
/** /**
@@ -99,7 +101,7 @@ public class TenantAutoConfiguration {
*/ */
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public TenantDataSourceHandler tenantDataSourceHandler(javax.sql.DataSource dataSource, public TenantDataSourceHandler tenantDataSourceHandler(DataSource dataSource,
DefaultDataSourceCreator dataSourceCreator) { DefaultDataSourceCreator dataSourceCreator) {
return new DefaultTenantDataSourceHandler((DynamicRoutingDataSource)dataSource, dataSourceCreator); return new DefaultTenantDataSourceHandler((DynamicRoutingDataSource)dataSource, dataSourceCreator);
} }

View File

@@ -11,6 +11,8 @@
<artifactId>continew-starter-extension-tenant</artifactId> <artifactId>continew-starter-extension-tenant</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 扩展模块 - 多租户</description> <description>ContiNew Starter 扩展模块 - 多租户</description>
<modules> <modules>

View File

@@ -11,6 +11,8 @@
<artifactId>continew-starter-extension</artifactId> <artifactId>continew-starter-extension</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 扩展模块</description> <description>ContiNew Starter 扩展模块</description>
<modules> <modules>

View File

@@ -10,6 +10,9 @@
</parent> </parent>
<artifactId>continew-starter-file-excel</artifactId> <artifactId>continew-starter-file-excel</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 文件处理模块 - Excel</description> <description>ContiNew Starter 文件处理模块 - Excel</description>
<dependencies> <dependencies>
@@ -18,11 +21,5 @@
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId> <artifactId>easyexcel</artifactId>
</dependency> </dependency>
<!-- Jakarta原 Javax Servlet -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -61,7 +61,7 @@ public class ExcelBaseEnumConverter implements Converter<BaseEnum<?>> {
public WriteCellData<String> convertToExcelData(BaseEnum<?> value, public WriteCellData<String> convertToExcelData(BaseEnum<?> value,
ExcelContentProperty contentProperty, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) { GlobalConfiguration globalConfiguration) {
if (null == value) { if (value == null) {
return new WriteCellData<>(StringConstants.EMPTY); return new WriteCellData<>(StringConstants.EMPTY);
} }
return new WriteCellData<>(value.getDescription()); return new WriteCellData<>(value.getDescription());

View File

@@ -11,6 +11,8 @@
<artifactId>continew-starter-file</artifactId> <artifactId>continew-starter-file</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 文件处理模块</description> <description>ContiNew Starter 文件处理模块</description>
<modules> <modules>

View File

@@ -10,6 +10,9 @@
</parent> </parent>
<artifactId>continew-starter-idempotent</artifactId> <artifactId>continew-starter-idempotent</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 幂等模块</description> <description>ContiNew Starter 幂等模块</description>
<dependencies> <dependencies>
@@ -18,5 +21,11 @@
<groupId>top.continew</groupId> <groupId>top.continew</groupId>
<artifactId>continew-starter-cache-redisson</artifactId> <artifactId>continew-starter-cache-redisson</artifactId>
</dependency> </dependency>
<!-- Hutool 加密解密模块(封装 JDK 中加密解密算法) -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -18,7 +18,6 @@ package top.continew.starter.idempotent.aop;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
@@ -98,7 +97,7 @@ public class IdempotentAspect {
String key = idempotent.key(); String key = idempotent.key();
if (CharSequenceUtil.isNotBlank(key)) { if (CharSequenceUtil.isNotBlank(key)) {
Object eval = ExpressionUtils.eval(key, target, method, args); Object eval = ExpressionUtils.eval(key, target, method, args);
if (ObjectUtil.isNull(eval)) { if (eval == null) {
throw new IdempotentException("幂等 Key 解析错误"); throw new IdempotentException("幂等 Key 解析错误");
} }
key = Convert.toStr(eval); key = Convert.toStr(eval);

View File

@@ -19,16 +19,15 @@ package top.continew.starter.idempotent.autoconfigure;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.core.ResolvableType;
import top.continew.starter.cache.redisson.autoconfigure.RedissonAutoConfiguration; import top.continew.starter.cache.redisson.autoconfigure.RedissonAutoConfiguration;
import top.continew.starter.core.constant.PropertiesConstants; import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.idempotent.aop.IdempotentAspect; import top.continew.starter.idempotent.aop.IdempotentAspect;
import top.continew.starter.idempotent.generator.DefaultIdempotentNameGenerator;
import top.continew.starter.idempotent.generator.IdempotentNameGenerator; import top.continew.starter.idempotent.generator.IdempotentNameGenerator;
/** /**
@@ -60,11 +59,7 @@ public class IdempotentAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public IdempotentNameGenerator idempotentNameGenerator() { public IdempotentNameGenerator idempotentNameGenerator() {
if (log.isErrorEnabled()) { return new DefaultIdempotentNameGenerator();
log.error("Consider defining a bean of type '{}' in your configuration.", ResolvableType
.forClass(IdempotentNameGenerator.class));
}
throw new NoSuchBeanDefinitionException(IdempotentNameGenerator.class);
} }
@PostConstruct @PostConstruct

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.idempotent.generator;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONUtil;
import top.continew.starter.core.constant.StringConstants;
import java.lang.reflect.Method;
/**
* 默认幂等名称生成器
*
* @author Charles7c
* @since 2.12.1
*/
public class DefaultIdempotentNameGenerator implements IdempotentNameGenerator {
@Override
public String generate(Object target, Method method, Object... args) {
StringBuilder nameSb = new StringBuilder();
// 添加类名
nameSb.append(ClassUtil.getClassName(target, false));
nameSb.append(StringConstants.COLON);
// 添加方法名
nameSb.append(method.getName());
// 添加参数信息的哈希值(如果有参数)
if (args != null && args.length > 0) {
nameSb.append(StringConstants.COLON);
// 使用JSONUtil序列化参数然后计算哈希值以确保唯一性
String argsJson = JSONUtil.toJsonStr(args);
nameSb.append(DigestUtil.md5Hex(argsJson));
}
return nameSb.toString();
}
}

View File

@@ -10,6 +10,9 @@
</parent> </parent>
<artifactId>continew-starter-json-jackson</artifactId> <artifactId>continew-starter-json-jackson</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter JSON 模块 - Jackson</description> <description>ContiNew Starter JSON 模块 - Jackson</description>
<dependencies> <dependencies>

View File

@@ -16,28 +16,6 @@
package top.continew.starter.json.jackson.autoconfigure; package top.continew.starter.json.jackson.autoconfigure;
import cn.hutool.core.date.DatePattern;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import top.continew.starter.core.enums.BaseEnum;
import top.continew.starter.core.util.GeneralPropertySourceFactory;
import top.continew.starter.json.jackson.serializer.BaseEnumDeserializer;
import top.continew.starter.json.jackson.serializer.BaseEnumSerializer;
import top.continew.starter.json.jackson.serializer.BigNumberSerializer;
import top.continew.starter.json.jackson.serializer.SimpleDeserializersWrapper;
import java.math.BigInteger; import java.math.BigInteger;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -45,24 +23,60 @@ import java.time.LocalTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.TimeZone; import java.util.TimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import cn.hutool.core.date.DatePattern;
import top.continew.starter.core.enums.BaseEnum;
import top.continew.starter.core.util.GeneralPropertySourceFactory;
import top.continew.starter.json.jackson.serializer.BaseEnumDeserializer;
import top.continew.starter.json.jackson.serializer.BaseEnumSerializer;
import top.continew.starter.json.jackson.serializer.BigNumberSerializer;
import top.continew.starter.json.jackson.serializer.SimpleDeserializersWrapper;
/** /**
* Jackson 自动配置 * Jackson 自动配置
* *
* @author Charles7c * @author Charles7c
* @author Jasmine
* @since 1.0.0 * @since 1.0.0
*/ */
@AutoConfiguration @AutoConfiguration
@EnableConfigurationProperties(JacksonExtensionProperties.class)
@PropertySource(value = "classpath:default-json-jackson.yml", factory = GeneralPropertySourceFactory.class) @PropertySource(value = "classpath:default-json-jackson.yml", factory = GeneralPropertySourceFactory.class)
public class JacksonAutoConfiguration { public class JacksonAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(JacksonAutoConfiguration.class); private static final Logger log = LoggerFactory.getLogger(JacksonAutoConfiguration.class);
private final JacksonExtensionProperties properties;
public JacksonAutoConfiguration(JacksonExtensionProperties properties) {
this.properties = properties;
}
@Bean @Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> { return builder -> {
JavaTimeModule javaTimeModule = this.timeModule(); JavaTimeModule javaTimeModule = this.javaTimeModule();
SimpleModule simpleModule = this.simpleModule(); SimpleModule baseEnumModule = this.baseEnumModule();
SimpleModule bigNumberModule = this.bigNumberModule();
builder.timeZone(TimeZone.getDefault()); builder.timeZone(TimeZone.getDefault());
builder.modules(javaTimeModule, simpleModule); builder.modules(javaTimeModule, baseEnumModule, bigNumberModule);
log.debug("[ContiNew Starter] - Auto Configuration 'Jackson' completed initialization."); log.debug("[ContiNew Starter] - Auto Configuration 'Jackson' completed initialization.");
}; };
} }
@@ -70,15 +84,11 @@ public class JacksonAutoConfiguration {
/** /**
* 日期时间序列化及反序列化配置 * 日期时间序列化及反序列化配置
* *
* @return JavaTimeModule / * @return {@link JavaTimeModule}
* @since 1.0.0 * @since 1.0.0
*/ */
private JavaTimeModule timeModule() { private JavaTimeModule javaTimeModule() {
// 针对大数值的序列化处理
JavaTimeModule javaTimeModule = new JavaTimeModule(); JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(Long.class, BigNumberSerializer.SERIALIZER_INSTANCE);
javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.SERIALIZER_INSTANCE);
javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.SERIALIZER_INSTANCE);
// 针对时间类型LocalDateTime 的序列化和反序列化处理 // 针对时间类型LocalDateTime 的序列化和反序列化处理
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN); DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN);
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter)); javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter));
@@ -100,7 +110,7 @@ public class JacksonAutoConfiguration {
* @return SimpleModule / * @return SimpleModule /
* @since 2.4.0 * @since 2.4.0
*/ */
private SimpleModule simpleModule() { private SimpleModule baseEnumModule() {
SimpleModule simpleModule = new SimpleModule(); SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(BaseEnum.class, BaseEnumSerializer.SERIALIZER_INSTANCE); simpleModule.addSerializer(BaseEnum.class, BaseEnumSerializer.SERIALIZER_INSTANCE);
SimpleDeserializersWrapper deserializers = new SimpleDeserializersWrapper(); SimpleDeserializersWrapper deserializers = new SimpleDeserializersWrapper();
@@ -108,4 +118,28 @@ public class JacksonAutoConfiguration {
simpleModule.setDeserializers(deserializers); simpleModule.setDeserializers(deserializers);
return simpleModule; return simpleModule;
} }
/**
* 大数值序列化及反序列化配置
*
* @return SimpleModule /
* @since 2.12.1
*/
private SimpleModule bigNumberModule() {
SimpleModule bigNumberModule = new SimpleModule();
switch (properties.getBigNumberSerializeMode()) {
case FLEXIBLE -> {
bigNumberModule.addSerializer(Long.class, BigNumberSerializer.SERIALIZER_INSTANCE);
bigNumberModule.addSerializer(Long.TYPE, BigNumberSerializer.SERIALIZER_INSTANCE);
bigNumberModule.addSerializer(BigInteger.class, BigNumberSerializer.SERIALIZER_INSTANCE);
}
case TO_STRING -> {
bigNumberModule.addSerializer(Long.class, ToStringSerializer.instance);
bigNumberModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
bigNumberModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
}
default -> log.warn("[ContiNew Starter] - Jackson 大数值序列化模式NO_OPERATE超过 JS 范围的数值会损失精度");
}
return bigNumberModule;
}
} }

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.json.jackson.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
import top.continew.starter.json.jackson.enums.BigNumberSerializeMode;
/**
* Jackson 扩展配置属性
*
* @author Jasmine
* @author Charles7c
* @since 2.12.1
*/
@ConfigurationProperties("spring.jackson")
public class JacksonExtensionProperties {
/**
* 大数值序列化模式
*/
private BigNumberSerializeMode bigNumberSerializeMode;
public BigNumberSerializeMode getBigNumberSerializeMode() {
return bigNumberSerializeMode;
}
public void setBigNumberSerializeMode(BigNumberSerializeMode bigNumberSerializeMode) {
this.bigNumberSerializeMode = bigNumberSerializeMode;
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.json.jackson.enums;
/**
* 大数值序列化模式
*
* @author Jasmine
* @author Charles7c
* @since 2.12.1
*/
public enum BigNumberSerializeMode {
/**
* 超过 JS 范围的数值转为 {@link String} 类型,否则保持原类型
* <p>
* JSNumber.MIN_SAFE_INTEGER-9007199254740991L <br />
* JSNumber.MAX_SAFE_INTEGER9007199254740991L
* </p>
*/
FLEXIBLE,
/**
* 统一转为 {@link String} 类型
*/
TO_STRING,
/**
* 不操作(不建议)
* <p>
* 注意:超过 JS 范围的数值会损失精度例如8014753905961037835 会被转为 8014753905961038000
* </p>
*/
NO_OPERATE,
}

View File

@@ -16,7 +16,6 @@
package top.continew.starter.json.jackson.util; package top.continew.starter.json.jackson.util;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
@@ -61,7 +60,7 @@ public class JSONUtils {
* @return {@link String } * @return {@link String }
*/ */
public static String toJsonStr(Object object) { public static String toJsonStr(Object object) {
if (ObjectUtil.isNull(object)) { if (object == null) {
return null; return null;
} }
try { try {

View File

@@ -21,3 +21,5 @@ spring:
deserialization: deserialization:
# 允许反序列化不存在的属性 # 允许反序列化不存在的属性
fail-on-unknown-properties: false fail-on-unknown-properties: false
# 大数值序列化模式
big-number-serialize-mode: FLEXIBLE

View File

@@ -11,6 +11,8 @@
<artifactId>continew-starter-json</artifactId> <artifactId>continew-starter-json</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter JSON 模块</description> <description>ContiNew Starter JSON 模块</description>
<modules> <modules>

View File

@@ -9,6 +9,9 @@
</parent> </parent>
<artifactId>continew-starter-license-core</artifactId> <artifactId>continew-starter-license-core</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter License 模块 - 核心模块</description> <description>ContiNew Starter License 模块 - 核心模块</description>
<dependencies> <dependencies>

View File

@@ -10,6 +10,9 @@
</parent> </parent>
<artifactId>continew-starter-license-generator</artifactId> <artifactId>continew-starter-license-generator</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter License 模块 - 生成器</description> <description>ContiNew Starter License 模块 - 生成器</description>
<dependencies> <dependencies>

View File

@@ -10,6 +10,9 @@
</parent> </parent>
<artifactId>continew-starter-license-verifier</artifactId> <artifactId>continew-starter-license-verifier</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter License 模块 - 校验器</description> <description>ContiNew Starter License 模块 - 校验器</description>
<dependencies> <dependencies>

View File

@@ -11,6 +11,8 @@
<artifactId>continew-starter-license</artifactId> <artifactId>continew-starter-license</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter License模块</description> <description>ContiNew Starter License模块</description>
<modules> <modules>

View File

@@ -9,8 +9,10 @@
</parent> </parent>
<artifactId>continew-starter-log-aop</artifactId> <artifactId>continew-starter-log-aop</artifactId>
<description>ContiNew Starter 日志模块 - 基于 AOP 实现</description> <packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 日志模块 - 基于 AOP 实现</description>
<dependencies> <dependencies>
<!-- 日志模块 - 核心模块 --> <!-- 日志模块 - 核心模块 -->

View File

@@ -10,6 +10,9 @@
</parent> </parent>
<artifactId>continew-starter-log-core</artifactId> <artifactId>continew-starter-log-core</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 日志模块 - 核心模块</description> <description>ContiNew Starter 日志模块 - 核心模块</description>
<dependencies> <dependencies>

View File

@@ -25,8 +25,8 @@ import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import top.continew.starter.web.util.RepeatReadRequestWrapper; import top.continew.starter.core.wrapper.RepeatReadRequestWrapper;
import top.continew.starter.web.util.RepeatReadResponseWrapper; import top.continew.starter.core.wrapper.RepeatReadResponseWrapper;
import top.continew.starter.log.model.LogProperties; import top.continew.starter.log.model.LogProperties;
import java.io.IOException; import java.io.IOException;

View File

@@ -31,7 +31,7 @@ import top.continew.starter.log.model.AccessLogContext;
import top.continew.starter.log.model.AccessLogProperties; import top.continew.starter.log.model.AccessLogProperties;
import top.continew.starter.log.model.LogRecord; import top.continew.starter.log.model.LogRecord;
import top.continew.starter.log.util.AccessLogUtils; import top.continew.starter.log.util.AccessLogUtils;
import top.continew.starter.web.util.ServletUtils; import top.continew.starter.core.util.ServletUtils;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.time.Duration; import java.time.Duration;
@@ -70,7 +70,7 @@ public abstract class AbstractLogHandler implements LogHandler {
return false; return false;
} }
Log classLog = AnnotationUtil.getAnnotation(targetClass, Log.class); Log classLog = AnnotationUtil.getAnnotation(targetClass, Log.class);
return null == classLog || !classLog.ignore(); return classLog == null || !classLog.ignore();
} }
@Override @Override

View File

@@ -20,7 +20,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.boot.context.properties.NestedConfigurationProperty;
import top.continew.starter.core.constant.PropertiesConstants; import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.log.enums.Include; import top.continew.starter.log.enums.Include;
import top.continew.starter.web.util.SpringWebUtils; import top.continew.starter.core.util.SpringWebUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

View File

@@ -20,7 +20,7 @@ import cn.hutool.core.text.CharSequenceUtil;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import top.continew.starter.core.util.ExceptionUtils; import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.core.util.IpUtils; import top.continew.starter.core.util.IpUtils;
import top.continew.starter.web.util.ServletUtils; import top.continew.starter.core.util.ServletUtils;
import top.continew.starter.log.enums.Include; import top.continew.starter.log.enums.Include;
import java.net.URI; import java.net.URI;
@@ -93,7 +93,7 @@ public class LogRequest {
this.address = (includes.contains(Include.IP_ADDRESS)) this.address = (includes.contains(Include.IP_ADDRESS))
? ExceptionUtils.exToNull(() -> IpUtils.getIpv4Address(this.ip)) ? ExceptionUtils.exToNull(() -> IpUtils.getIpv4Address(this.ip))
: null; : null;
if (null == this.headers) { if (this.headers == null) {
return; return;
} }
String userAgentString = this.headers.entrySet() String userAgentString = this.headers.entrySet()

View File

@@ -17,7 +17,7 @@
package top.continew.starter.log.model; package top.continew.starter.log.model;
import top.continew.starter.log.enums.Include; import top.continew.starter.log.enums.Include;
import top.continew.starter.web.util.ServletUtils; import top.continew.starter.core.util.ServletUtils;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;

View File

@@ -18,12 +18,11 @@ package top.continew.starter.log.util;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.databind.JsonNode; import cn.hutool.json.JSONUtil;
import top.continew.starter.json.jackson.util.JSONUtils;
import top.continew.starter.log.model.AccessLogProperties; import top.continew.starter.log.model.AccessLogProperties;
import top.continew.starter.log.model.LogProperties; import top.continew.starter.log.model.LogProperties;
import top.continew.starter.web.util.ServletUtils; import top.continew.starter.core.util.ServletUtils;
import top.continew.starter.web.util.SpringWebUtils; import top.continew.starter.core.util.SpringWebUtils;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -79,7 +78,7 @@ public class AccessLogUtils {
params = processTruncateLongParams(params, properties.getLongParamThreshold(), properties params = processTruncateLongParams(params, properties.getLongParamThreshold(), properties
.getLongParamMaxLength(), properties.getLongParamSuffix()); .getLongParamMaxLength(), properties.getLongParamSuffix());
} }
return JSONUtils.toJsonStr(params); return JSONUtil.toJsonStr(params);
} }
/** /**
@@ -192,13 +191,12 @@ public class AccessLogUtils {
*/ */
private static Object getAccessLogReqParam() { private static Object getAccessLogReqParam() {
String body = ServletUtils.getRequestBody(); String body = ServletUtils.getRequestBody();
if (CharSequenceUtil.isNotBlank(body) && JSONUtils.isTypeJSON(body)) { if (CharSequenceUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body)) {
try { try {
JsonNode jsonNode = JSONUtils.getObjectMapper().readTree(body); if (JSONUtil.isTypeJSONArray(body)) {
if (jsonNode.isArray()) { return JSONUtil.toBean(body, List.class);
return JSONUtils.toBean(body, List.class);
} else { } else {
return JSONUtils.toBean(body, Map.class); return JSONUtil.toBean(body, Map.class);
} }
} catch (Exception e) { } catch (Exception e) {
return null; return null;

View File

@@ -10,6 +10,9 @@
</parent> </parent>
<artifactId>continew-starter-log-interceptor</artifactId> <artifactId>continew-starter-log-interceptor</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 日志模块 - 基于拦截器实现Spring Boot Actuator HttpTrace 增强版)</description> <description>ContiNew Starter 日志模块 - 基于拦截器实现Spring Boot Actuator HttpTrace 增强版)</description>
<dependencies> <dependencies>

View File

@@ -76,7 +76,7 @@ public class LogInterceptor implements HandlerInterceptor {
Instant endTime = Instant.now(); Instant endTime = Instant.now();
logHandler.accessLogFinish(AccessLogContext.builder().endTime(endTime).build()); logHandler.accessLogFinish(AccessLogContext.builder().endTime(endTime).build());
LogRecord.Started startedLogRecord = logTtl.get(); LogRecord.Started startedLogRecord = logTtl.get();
if (null == startedLogRecord) { if (startedLogRecord == null) {
return; return;
} }
// 结束日志记录 // 结束日志记录

View File

@@ -11,6 +11,8 @@
<artifactId>continew-starter-log</artifactId> <artifactId>continew-starter-log</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 日志模块</description> <description>ContiNew Starter 日志模块</description>
<modules> <modules>
@@ -20,10 +22,10 @@
</modules> </modules>
<dependencies> <dependencies>
<!-- Web 模块 --> <!-- 核心模块 -->
<dependency> <dependency>
<groupId>top.continew</groupId> <groupId>top.continew</groupId>
<artifactId>continew-starter-web</artifactId> <artifactId>continew-starter-core</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -10,6 +10,9 @@
</parent> </parent>
<artifactId>continew-starter-messaging-mail</artifactId> <artifactId>continew-starter-messaging-mail</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 消息模块 - 邮件</description> <description>ContiNew Starter 消息模块 - 邮件</description>
<dependencies> <dependencies>

View File

@@ -10,6 +10,9 @@
</parent> </parent>
<artifactId>continew-starter-messaging-websocket</artifactId> <artifactId>continew-starter-messaging-websocket</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 消息模块 - WebSocket</description> <description>ContiNew Starter 消息模块 - WebSocket</description>
<dependencies> <dependencies>

View File

@@ -18,6 +18,9 @@ package top.continew.starter.messaging.websocket.dao;
import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.WebSocketSession;
import java.util.Collection;
import java.util.Set;
/** /**
* WebSocket 会话 DAO * WebSocket 会话 DAO
* *
@@ -48,4 +51,20 @@ public interface WebSocketSessionDao {
* @return 会话信息 * @return 会话信息
*/ */
WebSocketSession get(String key); WebSocketSession get(String key);
/**
* 获取所有会话
*
* @return 所有会话
* @since 2.12.1
*/
Collection<WebSocketSession> listAll();
/**
* 获取所有会话 ID
*
* @return 所有会话 ID
* @since 2.12.1
*/
Set<String> listAllSessionIds();
} }

View File

@@ -18,7 +18,9 @@ package top.continew.starter.messaging.websocket.dao;
import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.WebSocketSession;
import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
/** /**
@@ -45,4 +47,14 @@ public class WebSocketSessionDaoDefaultImpl implements WebSocketSessionDao {
public WebSocketSession get(String key) { public WebSocketSession get(String key) {
return SESSION_MAP.get(key); return SESSION_MAP.get(key);
} }
@Override
public Collection<WebSocketSession> listAll() {
return SESSION_MAP.values();
}
@Override
public Set<String> listAllSessionIds() {
return SESSION_MAP.keySet();
}
} }

View File

@@ -16,6 +16,7 @@
package top.continew.starter.messaging.websocket.util; package top.continew.starter.messaging.websocket.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -25,6 +26,8 @@ import org.springframework.web.socket.WebSocketSession;
import top.continew.starter.messaging.websocket.dao.WebSocketSessionDao; import top.continew.starter.messaging.websocket.dao.WebSocketSessionDao;
import java.io.IOException; import java.io.IOException;
import java.util.Collection;
import java.util.List;
/** /**
* WebSocket 工具类 * WebSocket 工具类
@@ -62,6 +65,28 @@ public class WebSocketUtils {
sendMessage(session, new TextMessage(message)); sendMessage(session, new TextMessage(message));
} }
/**
* 批量发送消息
*
* @param clientIds 客户端 ID 列表
* @param message 消息内容
* @since 2.12.1
*/
public static void sendMessage(List<String> clientIds, String message) {
Collection<String> sessionIds = CollUtil.intersection(SESSION_DAO.listAllSessionIds(), clientIds);
sessionIds.parallelStream().forEach(sessionId -> sendMessage(sessionId, message));
}
/**
* 发送消息给所有客户端
*
* @param message 消息内容
* @since 2.12.1
*/
public static void sendMessage(String message) {
SESSION_DAO.listAll().forEach(session -> sendMessage(session, message));
}
/** /**
* 发送消息 * 发送消息
* *

View File

@@ -11,6 +11,8 @@
<artifactId>continew-starter-messaging</artifactId> <artifactId>continew-starter-messaging</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 消息模块</description> <description>ContiNew Starter 消息模块</description>
<modules> <modules>

View File

@@ -8,13 +8,16 @@
</parent> </parent>
<artifactId>continew-starter-ratelimiter</artifactId> <artifactId>continew-starter-ratelimiter</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 限流模块</description> <description>ContiNew Starter 限流模块</description>
<dependencies> <dependencies>
<!-- Web 模块 --> <!-- 核心模块 -->
<dependency> <dependency>
<groupId>top.continew</groupId> <groupId>top.continew</groupId>
<artifactId>continew-starter-web</artifactId> <artifactId>continew-starter-core</artifactId>
</dependency> </dependency>
<!-- 缓存模块 - Redisson --> <!-- 缓存模块 - Redisson -->

View File

@@ -18,7 +18,6 @@ package top.continew.starter.ratelimiter.aop;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Around;
@@ -35,7 +34,7 @@ import top.continew.starter.ratelimiter.autoconfigure.RateLimiterProperties;
import top.continew.starter.ratelimiter.generator.RateLimiterNameGenerator; import top.continew.starter.ratelimiter.generator.RateLimiterNameGenerator;
import top.continew.starter.ratelimiter.enums.LimitType; import top.continew.starter.ratelimiter.enums.LimitType;
import top.continew.starter.ratelimiter.exception.RateLimiterException; import top.continew.starter.ratelimiter.exception.RateLimiterException;
import top.continew.starter.web.util.ServletUtils; import top.continew.starter.core.util.ServletUtils;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.time.Duration; import java.time.Duration;
@@ -162,7 +161,7 @@ public class RateLimiterAspect {
String key = rateLimiter.key(); String key = rateLimiter.key();
if (CharSequenceUtil.isNotBlank(key)) { if (CharSequenceUtil.isNotBlank(key)) {
Object eval = ExpressionUtils.eval(key, target, method, args); Object eval = ExpressionUtils.eval(key, target, method, args);
if (ObjectUtil.isNull(eval)) { if (eval == null) {
throw new RateLimiterException("限流 Key 解析错误"); throw new RateLimiterException("限流 Key 解析错误");
} }
key = Convert.toStr(eval); key = Convert.toStr(eval);

View File

@@ -24,7 +24,6 @@ import java.lang.reflect.Method;
* @author Charles7c * @author Charles7c
* @since 2.2.0 * @since 2.2.0
*/ */
@FunctionalInterface
public interface RateLimiterNameGenerator { public interface RateLimiterNameGenerator {
/** /**

View File

@@ -10,6 +10,9 @@
</parent> </parent>
<artifactId>continew-starter-security-crypto</artifactId> <artifactId>continew-starter-security-crypto</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 安全模块 - 加密</description> <description>ContiNew Starter 安全模块 - 加密</description>
<dependencies> <dependencies>

View File

@@ -52,7 +52,7 @@ public abstract class AbstractMyBatisInterceptor {
* @return 字段列表 * @return 字段列表
*/ */
protected List<Field> getEncryptFields(Object obj) { protected List<Field> getEncryptFields(Object obj) {
if (null == obj) { if (obj == null) {
return Collections.emptyList(); return Collections.emptyList();
} }
return this.getEncryptFields(obj.getClass()); return this.getEncryptFields(obj.getClass());
@@ -98,7 +98,7 @@ public abstract class AbstractMyBatisInterceptor {
String mappedStatementId = mappedStatement.getId(); String mappedStatementId = mappedStatement.getId();
return ENCRYPT_PARAM_CACHE.computeIfAbsent(mappedStatementId, key -> { return ENCRYPT_PARAM_CACHE.computeIfAbsent(mappedStatementId, key -> {
Method method = this.getMethod(mappedStatementId); Method method = this.getMethod(mappedStatementId);
if (null == method) { if (method == null) {
return Collections.emptyMap(); return Collections.emptyMap();
} }
Map<String, FieldEncrypt> encryptMap = new HashMap<>(); Map<String, FieldEncrypt> encryptMap = new HashMap<>();
@@ -106,7 +106,7 @@ public abstract class AbstractMyBatisInterceptor {
for (int i = 0; i < parameters.length; i++) { for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i]; Parameter parameter = parameters[i];
FieldEncrypt fieldEncrypt = parameter.getAnnotation(FieldEncrypt.class); FieldEncrypt fieldEncrypt = parameter.getAnnotation(FieldEncrypt.class);
if (null == fieldEncrypt) { if (fieldEncrypt == null) {
continue; continue;
} }
String parameterName = this.getParameterName(parameter); String parameterName = this.getParameterName(parameter);

View File

@@ -16,6 +16,7 @@
package top.continew.starter.security.crypto.core; package top.continew.starter.security.crypto.core;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ReflectUtil;
import org.apache.ibatis.executor.resultset.ResultSetHandler; import org.apache.ibatis.executor.resultset.ResultSetHandler;
@@ -24,6 +25,8 @@ import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.type.SimpleTypeRegistry; import org.apache.ibatis.type.SimpleTypeRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.continew.starter.security.crypto.annotation.FieldEncrypt; import top.continew.starter.security.crypto.annotation.FieldEncrypt;
import top.continew.starter.security.crypto.autoconfigure.CryptoProperties; import top.continew.starter.security.crypto.autoconfigure.CryptoProperties;
import top.continew.starter.security.crypto.encryptor.IEncryptor; import top.continew.starter.security.crypto.encryptor.IEncryptor;
@@ -41,6 +44,7 @@ import java.util.List;
@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})}) @Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
public class MyBatisDecryptInterceptor extends AbstractMyBatisInterceptor implements Interceptor { public class MyBatisDecryptInterceptor extends AbstractMyBatisInterceptor implements Interceptor {
private static final Logger log = LoggerFactory.getLogger(MyBatisDecryptInterceptor.class);
private CryptoProperties properties; private CryptoProperties properties;
public MyBatisDecryptInterceptor(CryptoProperties properties) { public MyBatisDecryptInterceptor(CryptoProperties properties) {
@@ -53,31 +57,73 @@ public class MyBatisDecryptInterceptor extends AbstractMyBatisInterceptor implem
@Override @Override
public Object intercept(Invocation invocation) throws Throwable { public Object intercept(Invocation invocation) throws Throwable {
Object obj = invocation.proceed(); Object obj = invocation.proceed();
if (null == obj || !(invocation.getTarget() instanceof ResultSetHandler)) { if (obj == null) {
return null;
}
// 确保目标是 ResultSetHandler
if (!(invocation.getTarget() instanceof ResultSetHandler)) {
return obj; return obj;
} }
List<?> resultList = (List<?>)obj; // 处理查询结果
if (obj instanceof List<?> resultList) {
// 处理列表结果
this.decryptList(resultList);
} else {
// 处理单个对象结果
this.decryptObject(obj);
}
return obj;
}
/**
* 解密列表结果
*
* @param resultList 结果列表
*/
private void decryptList(List<?> resultList) {
if (CollUtil.isEmpty(resultList)) {
return;
}
for (Object result : resultList) { for (Object result : resultList) {
// String、Integer、Long 等简单类型对象无需处理 decryptObject(result);
if (SimpleTypeRegistry.isSimpleType(result.getClass())) { }
}
/**
* 解密单个对象结果
*
* @param result 结果对象
*/
private void decryptObject(Object result) {
if (result == null) {
return;
}
// String、Integer、Long 等简单类型对象无需处理
if (SimpleTypeRegistry.isSimpleType(result.getClass())) {
return;
}
// 获取所有字符串类型、需要解密的、有值字段
List<Field> fieldList = super.getEncryptFields(result);
if (fieldList.isEmpty()) {
return;
}
// 解密处理
for (Field field : fieldList) {
IEncryptor encryptor = super.getEncryptor(field.getAnnotation(FieldEncrypt.class));
Object fieldValue = ReflectUtil.getFieldValue(result, field);
if (fieldValue == null) {
continue; continue;
} }
// 获取所有字符串类型、需要解密的、有值字段 // 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置
List<Field> fieldList = super.getEncryptFields(result); String password = ObjectUtil.defaultIfBlank(field.getAnnotation(FieldEncrypt.class).password(), properties
// 解密处理 .getPassword());
for (Field field : fieldList) { try {
IEncryptor encryptor = super.getEncryptor(field.getAnnotation(FieldEncrypt.class));
Object fieldValue = ReflectUtil.getFieldValue(result, field);
if (null == fieldValue) {
continue;
}
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置
String password = ObjectUtil.defaultIfBlank(field.getAnnotation(FieldEncrypt.class)
.password(), properties.getPassword());
String ciphertext = encryptor.decrypt(fieldValue.toString(), password, properties.getPrivateKey()); String ciphertext = encryptor.decrypt(fieldValue.toString(), password, properties.getPrivateKey());
ReflectUtil.setFieldValue(result, field, ciphertext); ReflectUtil.setFieldValue(result, field, ciphertext);
} catch (Exception e) {
// 解密失败时保留原值,避免影响正常业务流程
log.warn("解密失败,请检查加密配置", e);
} }
} }
return resultList;
} }
} }

View File

@@ -28,8 +28,9 @@ import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.exception.BaseException;
import top.continew.starter.security.crypto.annotation.FieldEncrypt; import top.continew.starter.security.crypto.annotation.FieldEncrypt;
import top.continew.starter.security.crypto.autoconfigure.CryptoProperties; import top.continew.starter.security.crypto.autoconfigure.CryptoProperties;
import top.continew.starter.security.crypto.encryptor.IEncryptor; import top.continew.starter.security.crypto.encryptor.IEncryptor;
@@ -50,6 +51,7 @@ import java.util.regex.Pattern;
*/ */
public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implements InnerInterceptor { public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implements InnerInterceptor {
private static final Logger log = LoggerFactory.getLogger(MyBatisEncryptInterceptor.class);
private static final Pattern PARAM_PAIRS_PATTERN = Pattern private static final Pattern PARAM_PAIRS_PATTERN = Pattern
.compile("#\\{ew\\.paramNameValuePairs\\.(" + Constants.WRAPPER_PARAM + "\\d+)\\}"); .compile("#\\{ew\\.paramNameValuePairs\\.(" + Constants.WRAPPER_PARAM + "\\d+)\\}");
private final CryptoProperties properties; private final CryptoProperties properties;
@@ -65,7 +67,7 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implem
RowBounds rowBounds, RowBounds rowBounds,
ResultHandler resultHandler, ResultHandler resultHandler,
BoundSql boundSql) { BoundSql boundSql) {
if (null == parameterObject) { if (parameterObject == null) {
return; return;
} }
if (parameterObject instanceof Map parameterMap) { if (parameterObject instanceof Map parameterMap) {
@@ -75,7 +77,7 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implem
@Override @Override
public void beforeUpdate(Executor executor, MappedStatement mappedStatement, Object parameterObject) { public void beforeUpdate(Executor executor, MappedStatement mappedStatement, Object parameterObject) {
if (null == parameterObject) { if (parameterObject == null) {
return; return;
} }
if (parameterObject instanceof Map parameterMap) { if (parameterObject instanceof Map parameterMap) {
@@ -87,6 +89,60 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implem
} }
} }
/**
* 加密查询参数(针对 Map 类型参数)
*
* @param parameterMap 参数
* @param mappedStatement 映射语句
*/
private void encryptQueryParameter(Map<String, Object> parameterMap, MappedStatement mappedStatement) {
Map<String, FieldEncrypt> encryptParameterMap = super.getEncryptParameters(mappedStatement);
for (Map.Entry<String, Object> parameterEntrySet : parameterMap.entrySet()) {
String parameterName = parameterEntrySet.getKey();
Object parameterValue = parameterEntrySet.getValue();
if (parameterValue == null || ClassUtil.isBasicType(parameterValue
.getClass()) || parameterValue instanceof AbstractWrapper) {
continue;
}
if (parameterValue instanceof String str) {
FieldEncrypt fieldEncrypt = encryptParameterMap.get(parameterName);
if (fieldEncrypt != null) {
parameterMap.put(parameterName, this.doEncrypt(str, fieldEncrypt));
}
} else {
// 实体参数
this.encryptEntity(super.getEncryptFields(parameterValue), parameterValue);
}
}
}
/**
* 处理实体加密
*
* @param fieldList 加密字段列表
* @param entity 实体
*/
private void encryptEntity(List<Field> fieldList, Object entity) {
for (Field field : fieldList) {
IEncryptor encryptor = super.getEncryptor(field.getAnnotation(FieldEncrypt.class));
Object fieldValue = ReflectUtil.getFieldValue(entity, field);
if (fieldValue == null) {
continue;
}
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置
String password = ObjectUtil.defaultIfBlank(field.getAnnotation(FieldEncrypt.class).password(), properties
.getPassword());
String ciphertext = fieldValue.toString();
try {
ciphertext = encryptor.encrypt(fieldValue.toString(), password, properties.getPublicKey());
} catch (Exception e) {
// 加密失败时保留原值,避免影响正常业务流程
log.warn("加密失败,请检查加密配置", e);
}
ReflectUtil.setFieldValue(entity, field, ciphertext);
}
}
/** /**
* 加密 Map 类型数据(使用 @Param 注解的场景) * 加密 Map 类型数据(使用 @Param 注解的场景)
* *
@@ -105,33 +161,6 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implem
} }
} }
/**
* 加密查询参数(针对 Map 类型参数)
*
* @param parameterMap 参数
* @param mappedStatement 映射语句
*/
private void encryptQueryParameter(Map<String, Object> parameterMap, MappedStatement mappedStatement) {
Map<String, FieldEncrypt> encryptParameterMap = super.getEncryptParameters(mappedStatement);
for (Map.Entry<String, Object> parameterEntrySet : parameterMap.entrySet()) {
String parameterName = parameterEntrySet.getKey();
Object parameterValue = parameterEntrySet.getValue();
if (null == parameterValue || ClassUtil.isBasicType(parameterValue
.getClass()) || parameterValue instanceof AbstractWrapper) {
continue;
}
if (parameterValue instanceof String str) {
FieldEncrypt fieldEncrypt = encryptParameterMap.get(parameterName);
if (fieldEncrypt != null) {
parameterMap.put(parameterName, this.doEncrypt(str, fieldEncrypt));
}
} else {
// 实体参数
this.encryptEntity(super.getEncryptFields(parameterValue), parameterValue);
}
}
}
/** /**
* 处理 UpdateWrapper 类型参数加密(针对 MP 的 UpdateWrapper、LambdaUpdateWrapper 等参数) * 处理 UpdateWrapper 类型参数加密(针对 MP 的 UpdateWrapper、LambdaUpdateWrapper 等参数)
* *
@@ -156,7 +185,7 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implem
propMap.put(elPart[0], elPart[1]); propMap.put(elPart[0], elPart[1]);
}); });
// 获取加密字段 // 获取加密字段
Class<?> entityClass = mappedStatement.getParameterMap().getType(); Class<?> entityClass = this.getEntityClass(updateWrapper, mappedStatement);
List<Field> encryptFieldList = super.getEncryptFields(entityClass); List<Field> encryptFieldList = super.getEncryptFields(entityClass);
for (Field field : encryptFieldList) { for (Field field : encryptFieldList) {
FieldEncrypt fieldEncrypt = field.getAnnotation(FieldEncrypt.class); FieldEncrypt fieldEncrypt = field.getAnnotation(FieldEncrypt.class);
@@ -175,32 +204,6 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implem
} }
} }
/**
* 处理实体加密
*
* @param fieldList 加密字段列表
* @param entity 实体
*/
private void encryptEntity(List<Field> fieldList, Object entity) {
for (Field field : fieldList) {
IEncryptor encryptor = super.getEncryptor(field.getAnnotation(FieldEncrypt.class));
Object fieldValue = ReflectUtil.getFieldValue(entity, field);
if (null == fieldValue) {
continue;
}
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置
String password = ObjectUtil.defaultIfBlank(field.getAnnotation(FieldEncrypt.class).password(), properties
.getPassword());
String ciphertext;
try {
ciphertext = encryptor.encrypt(fieldValue.toString(), password, properties.getPublicKey());
} catch (Exception e) {
throw new BaseException(e);
}
ReflectUtil.setFieldValue(entity, field, ciphertext);
}
}
/** /**
* 处理加密 * 处理加密
* *
@@ -208,7 +211,7 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implem
* @param fieldEncrypt 字段加密注解 * @param fieldEncrypt 字段加密注解
*/ */
private Object doEncrypt(Object parameterValue, FieldEncrypt fieldEncrypt) { private Object doEncrypt(Object parameterValue, FieldEncrypt fieldEncrypt) {
if (null == parameterValue) { if (parameterValue == null) {
return null; return null;
} }
IEncryptor encryptor = super.getEncryptor(fieldEncrypt); IEncryptor encryptor = super.getEncryptor(fieldEncrypt);
@@ -217,7 +220,26 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implem
try { try {
return encryptor.encrypt(parameterValue.toString(), password, properties.getPublicKey()); return encryptor.encrypt(parameterValue.toString(), password, properties.getPublicKey());
} catch (Exception e) { } catch (Exception e) {
throw new BaseException(e); // 加密失败时保留原值,避免影响正常业务流程
log.warn("加密失败,请检查加密配置", e);
} }
return parameterValue;
}
/**
* 获取实体类
*
* @param wrapper 查询或更新包装器
* @param mappedStatement 映射语句
* @return 实体类
*/
private Class<?> getEntityClass(AbstractWrapper wrapper, MappedStatement mappedStatement) {
// 尝试从 Wrapper 中获取实体类
Class<?> entityClass = wrapper.getEntityClass();
if (entityClass != null) {
return entityClass;
}
// 从映射语句中获取实体类
return mappedStatement.getParameterMap().getType();
} }
} }

View File

@@ -10,5 +10,8 @@
</parent> </parent>
<artifactId>continew-starter-security-mask</artifactId> <artifactId>continew-starter-security-mask</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 安全模块 - 脱敏</description> <description>ContiNew Starter 安全模块 - 脱敏</description>
</project> </project>

View File

@@ -68,7 +68,7 @@ public class JsonMaskSerializer extends JsonSerializer<String> implements Contex
@Override @Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, public JsonSerializer<?> createContextual(SerializerProvider serializerProvider,
BeanProperty beanProperty) throws JsonMappingException { BeanProperty beanProperty) throws JsonMappingException {
if (null == beanProperty) { if (beanProperty == null) {
return serializerProvider.findNullValueSerializer(null); return serializerProvider.findNullValueSerializer(null);
} }
if (!Objects.equals(beanProperty.getType().getRawClass(), String.class)) { if (!Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
@@ -76,7 +76,7 @@ public class JsonMaskSerializer extends JsonSerializer<String> implements Contex
} }
JsonMask jsonMaskAnnotation = ObjectUtil.defaultIfNull(beanProperty.getAnnotation(JsonMask.class), beanProperty JsonMask jsonMaskAnnotation = ObjectUtil.defaultIfNull(beanProperty.getAnnotation(JsonMask.class), beanProperty
.getContextAnnotation(JsonMask.class)); .getContextAnnotation(JsonMask.class));
if (null == jsonMaskAnnotation) { if (jsonMaskAnnotation == null) {
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty); return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
} }
return new JsonMaskSerializer(jsonMaskAnnotation); return new JsonMaskSerializer(jsonMaskAnnotation);

View File

@@ -10,6 +10,9 @@
</parent> </parent>
<artifactId>continew-starter-security-password</artifactId> <artifactId>continew-starter-security-password</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 安全模块 - 密码编码器</description> <description>ContiNew Starter 安全模块 - 密码编码器</description>
<dependencies> <dependencies>

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