mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-11-12 06:57:10 +08:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c01ba23463 | |||
|
|
c0aa86327a | ||
| ad298930db | |||
| c08b57cb48 | |||
| a4823dcb0b | |||
|
|
eb7dfd4ed7 | ||
| eda35e59bc | |||
| 6b95083c63 | |||
| 265d90fa4c | |||
| f83a901626 | |||
| 4f9e1ba108 | |||
| 73e2b169f7 | |||
|
|
918a0abfda | ||
| 4a6b4624c2 | |||
| 2fdd5b6fd3 | |||
| d5a74b42e8 | |||
|
|
abc005e690 | ||
|
|
9c6182e028 | ||
| b543b2f94d | |||
| fa2cdf4f80 | |||
| adaf475835 |
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -16,7 +16,7 @@ body:
|
||||
required: true
|
||||
- label: 查阅过 [使用指南](https://continew.top/starter/guide/introduction.html) 和 [常见问题](https://continew.top/starter/faq.html) ,仍无解决方法
|
||||
required: true
|
||||
- label: 根据报错信息(自行翻译英文)百度或 Google 后,仍无法解决
|
||||
- label: 查看过控制台是否有报错,如果有报错,下拉控制台到最下查找过 Caused 提示(如果有 Caused 关键字,一般其后都有直观提示,请自行翻译英文),并百度或 Google 后,仍无法解决
|
||||
required: true
|
||||
- label: 尝试 [最新版本](https://central.sonatype.com/artifact/top.continew/continew-starter/versions)(还可以尝试本地编译安装 dev 分支的最新快照版本),仍有相同问题
|
||||
required: true
|
||||
|
||||
43
CHANGELOG.md
43
CHANGELOG.md
@@ -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)
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
- 【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))
|
||||
- 【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))
|
||||
@@ -24,6 +51,19 @@
|
||||
- 【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))
|
||||
|
||||
### 📦 依赖升级
|
||||
|
||||
- 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)
|
||||
|
||||
### ✨ 新特性
|
||||
@@ -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))
|
||||
|
||||
- 替换 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))
|
||||
- 【log】访问日志过滤资源路径 (Gitee#47@dom-w) ([a6a44cd](https://github.com/continew-org/continew-starter/commit/a6a44cd46131d41f8626fe67f6ad9e4d70f1d46c))
|
||||
|
||||
12
README.md
12
README.md
@@ -153,6 +153,10 @@ continew-starter
|
||||
│ └─ continew-starter-json-jackson
|
||||
├─ continew-starter-api-doc(接口文档模块:Spring Doc + Knife4j)
|
||||
├─ continew-starter-web(Web 开发模块:包含跨域、全局异常+响应、链路追踪等自动配置)
|
||||
├─ continew-starter-cache(缓存模块)
|
||||
│ ├─ continew-starter-cache-redisson(Redisson)
|
||||
│ ├─ continew-starter-cache-jetcache(JetCache 多级缓存)
|
||||
│ └─ continew-starter-cache-springcache(Spring 缓存)
|
||||
├─ continew-starter-auth(认证模块)
|
||||
│ ├─ continew-starter-auth-satoken(国产轻量认证鉴权)
|
||||
│ └─ continew-starter-auth-justauth(第三方登录)
|
||||
@@ -160,10 +164,6 @@ continew-starter
|
||||
│ ├─ continew-starter-data-core(通用模块)
|
||||
│ ├─ continew-starter-data-mp(MyBatis Plus)
|
||||
│ └─ continew-starter-data-mf(MyBatis Flex)
|
||||
├─ continew-starter-cache(缓存模块)
|
||||
│ ├─ continew-starter-cache-redisson(Redisson)
|
||||
│ ├─ continew-starter-cache-jetcache(JetCache 多级缓存)
|
||||
│ └─ continew-starter-cache-springcache(Spring 缓存)
|
||||
├─ continew-starter-security(安全模块)
|
||||
│ ├─ continew-starter-security-crypto(加密:字段加解密)
|
||||
│ ├─ continew-starter-security-xss(XSS 过滤)
|
||||
@@ -186,6 +186,10 @@ continew-starter
|
||||
│ └─ continew-starter-file-excel(Easy Excel)
|
||||
├─ continew-starter-storage(存储模块)
|
||||
│ └─ continew-starter-storage-local(本地存储)
|
||||
├─ continew-starter-license(License 模块)
|
||||
│ ├─ continew-starter-license-core(核心模块)
|
||||
│ ├─ continew-starter-license-generator(License 生成器)
|
||||
│ └─ continew-starter-license-verifier(License 校验器)
|
||||
└─ continew-starter-extension(扩展模块)
|
||||
├─ continew-starter-extension-datapermission(数据权限模块)
|
||||
│ ├─ continew-starter-extension-datapermission-core(通用模块)
|
||||
|
||||
@@ -10,19 +10,22 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-api-doc</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter API 文档模块</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Knife4j(前身是 swagger-bootstrap-ui,集 Swagger2 和 OpenAPI3 为一体的增强解决方案) -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Knife4j(前身是 swagger-bootstrap-ui,集 Swagger2 和 OpenAPI3 为一体的增强解决方案) -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -66,6 +66,7 @@ import java.util.concurrent.TimeUnit;
|
||||
@EnableConfigurationProperties(SpringDocExtensionProperties.class)
|
||||
@PropertySource(value = "classpath:default-api-doc.yml", factory = GeneralPropertySourceFactory.class)
|
||||
public class SpringDocAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SpringDocAutoConfiguration.class);
|
||||
|
||||
@Override
|
||||
|
||||
@@ -10,9 +10,19 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-auth-justauth</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 认证模块 - JustAuth</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 缓存模块 - Redisson -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-cache-redisson</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Just Auth(开箱即用的整合第三方登录的开源组件,脱离繁琐的第三方登录 SDK,让登录变得 So easy!) -->
|
||||
<dependency>
|
||||
<groupId>me.zhyd.oauth</groupId>
|
||||
@@ -23,12 +33,5 @@
|
||||
<groupId>com.xkcoding.justauth</groupId>
|
||||
<artifactId>justauth-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 缓存模块 - Redisson -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-cache-redisson</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -10,15 +10,12 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-auth-satoken</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 认证模块 - SaToken</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Web 模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 缓存模块 - Redisson -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
@@ -30,12 +27,6 @@
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot3-starter</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 整合 JWT -->
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
<artifactId>continew-starter-auth</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 认证模块</description>
|
||||
|
||||
<modules>
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
<artifactId>continew-starter-bom</artifactId>
|
||||
<version>${revision}</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter BOM</description>
|
||||
|
||||
<properties>
|
||||
<revision>2.12.0</revision>
|
||||
<revision>2.12.2</revision>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -44,6 +46,25 @@
|
||||
<version>${revision}</version>
|
||||
</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 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
@@ -76,25 +97,6 @@
|
||||
<version>${revision}</version>
|
||||
</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>
|
||||
<groupId>top.continew</groupId>
|
||||
@@ -117,6 +119,7 @@
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-security-xss</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- 安全模块 - 敏感词 -->
|
||||
<dependency>
|
||||
|
||||
@@ -10,9 +10,18 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-cache-jetcache</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 缓存模块 - JetCache</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 缓存模块 - Redisson -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-cache-redisson</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JetCache(基于 Java 的缓存系统封装,提供统一的 API 和注解来简化缓存的使用。提供了比 SpringCache 更加强大的注解,可以原生的支持 TTL、两级缓存、分布式自动刷新,还提供了 Cache 接口用于手工缓存操作) -->
|
||||
<dependency>
|
||||
<groupId>com.alicp.jetcache</groupId>
|
||||
@@ -41,11 +50,5 @@
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 缓存模块 - Redisson -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-cache-redisson</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -10,6 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-cache-redisson</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 缓存模块 - Redisson</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,15 @@ public class RedisUtils {
|
||||
private RedisUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Redisson 客户端实例
|
||||
*
|
||||
* @return Redisson 客户端实例
|
||||
*/
|
||||
public static RedissonClient getClient() {
|
||||
return CLIENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置缓存
|
||||
*
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-cache-springcache</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 缓存模块 - Spring Cache</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
<artifactId>continew-starter-cache</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 缓存模块</description>
|
||||
|
||||
<modules>
|
||||
|
||||
@@ -10,19 +10,22 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-captcha-behavior</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 验证码模块 - 行为验证码</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- AJ-Captcha(行为验证码,包含滑动拼图、文字点选两种方式,UI支持弹出和嵌入两种方式) -->
|
||||
<dependency>
|
||||
<groupId>com.anji-plus</groupId>
|
||||
<artifactId>captcha</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 缓存模块 - Redisson -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-cache-redisson</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- AJ-Captcha(行为验证码,包含滑动拼图、文字点选两种方式,UI支持弹出和嵌入两种方式) -->
|
||||
<dependency>
|
||||
<groupId>com.anji-plus</groupId>
|
||||
<artifactId>captcha</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -10,6 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-captcha-graphic</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 验证码模块 - 图形验证码</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
<artifactId>continew-starter-captcha</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 验证码模块</description>
|
||||
|
||||
<modules>
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 核心模块</description>
|
||||
|
||||
<dependencies>
|
||||
@@ -29,6 +32,24 @@
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</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 -->
|
||||
<dependency>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 异常工具类
|
||||
@@ -44,7 +45,7 @@ public class ExceptionUtils {
|
||||
* @param throwable 异常
|
||||
*/
|
||||
public static void printException(Runnable runnable, Throwable throwable) {
|
||||
if (null == throwable && runnable instanceof Future<?> future) {
|
||||
if (throwable == null && runnable instanceof Future<?> future) {
|
||||
try {
|
||||
if (future.isDone()) {
|
||||
future.get();
|
||||
@@ -107,6 +108,25 @@ public class ExceptionUtils {
|
||||
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 /
|
||||
*/
|
||||
T get() throws Exception;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* 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.DateUtil;
|
||||
@@ -43,6 +43,7 @@ import java.time.LocalDateTime;
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class FileUploadUtils {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(FileUploadUtils.class);
|
||||
|
||||
private FileUploadUtils() {
|
||||
@@ -50,7 +50,7 @@ public class IpUtils {
|
||||
}
|
||||
Ip2regionSearcher ip2regionSearcher = SpringUtil.getBean(Ip2regionSearcher.class);
|
||||
IpInfo ipInfo = ip2regionSearcher.memorySearch(ip);
|
||||
if (null == ipInfo) {
|
||||
if (ipInfo == null) {
|
||||
return null;
|
||||
}
|
||||
Set<String> regionSet = CollUtil.newLinkedHashSet(ipInfo.getCountry(), ipInfo.getRegion(), ipInfo
|
||||
|
||||
@@ -14,13 +14,14 @@
|
||||
* 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.text.CharSequenceUtil;
|
||||
import cn.hutool.extra.servlet.JakartaServletUtil;
|
||||
import cn.hutool.http.useragent.UserAgent;
|
||||
import cn.hutool.http.useragent.UserAgentUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
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.util.UriUtils;
|
||||
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.URISyntaxException;
|
||||
@@ -55,7 +57,7 @@ public class ServletUtils extends JakartaServletUtil {
|
||||
* @return 浏览器及其版本信息
|
||||
*/
|
||||
public static String getBrowser(HttpServletRequest request) {
|
||||
if (null == request) {
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
return getBrowser(request.getHeader("User-Agent"));
|
||||
@@ -68,8 +70,17 @@ public class ServletUtils extends JakartaServletUtil {
|
||||
* @return 浏览器及其版本信息
|
||||
*/
|
||||
public static String getBrowser(String userAgentString) {
|
||||
UserAgent userAgent = UserAgentUtil.parse(userAgentString);
|
||||
return userAgent.getBrowser().getName() + StringConstants.SPACE + userAgent.getVersion();
|
||||
try {
|
||||
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 操作系统
|
||||
*/
|
||||
public static String getOs(HttpServletRequest request) {
|
||||
if (null == request) {
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
return getOs(request.getHeader("User-Agent"));
|
||||
@@ -92,8 +103,15 @@ public class ServletUtils extends JakartaServletUtil {
|
||||
* @return 操作系统
|
||||
*/
|
||||
public static String getOs(String userAgentString) {
|
||||
UserAgent userAgent = UserAgentUtil.parse(userAgentString);
|
||||
return userAgent.getOs().getName();
|
||||
try {
|
||||
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();
|
||||
if (request instanceof RepeatReadRequestWrapper wrapper && !wrapper.isMultipartContent(request)) {
|
||||
String body = JakartaServletUtil.getBody(request);
|
||||
return JSONUtils.isTypeJSON(body) ? body : null;
|
||||
return JSONUtil.isTypeJSON(body) ? body : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -201,8 +219,8 @@ public class ServletUtils extends JakartaServletUtil {
|
||||
*/
|
||||
public static Map<String, Object> getRequestParams() {
|
||||
String body = getRequestBody();
|
||||
return CharSequenceUtil.isNotBlank(body) && JSONUtils.isTypeJSON(body)
|
||||
? JSONUtils.toBean(body, Map.class)
|
||||
return CharSequenceUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body)
|
||||
? JSONUtil.toBean(body, Map.class)
|
||||
: Collections.unmodifiableMap(JakartaServletUtil.getParamMap(Objects.requireNonNull(getRequest())));
|
||||
}
|
||||
|
||||
@@ -246,7 +264,7 @@ public class ServletUtils extends JakartaServletUtil {
|
||||
HttpServletResponse response = getResponse();
|
||||
if (response instanceof RepeatReadResponseWrapper wrapper && !wrapper.isStreamingResponse()) {
|
||||
String body = wrapper.getResponseContent();
|
||||
return JSONUtils.isTypeJSON(body) ? body : null;
|
||||
return JSONUtil.isTypeJSON(body) ? body : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -259,9 +277,7 @@ public class ServletUtils extends JakartaServletUtil {
|
||||
*/
|
||||
public static Map<String, Object> getResponseParams() {
|
||||
String body = getResponseBody();
|
||||
return CharSequenceUtil.isNotBlank(body) && JSONUtils.isTypeJSON(body)
|
||||
? JSONUtils.toBean(body, Map.class)
|
||||
: null;
|
||||
return CharSequenceUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body) ? JSONUtil.toBean(body, Map.class) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -14,7 +14,7 @@
|
||||
* 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.util.ReflectUtil;
|
||||
@@ -48,7 +48,7 @@ public class Validator {
|
||||
* @param exceptionType 异常类型
|
||||
*/
|
||||
protected static void throwIfNull(Object obj, String message, Class<? extends RuntimeException> exceptionType) {
|
||||
throwIf(null == obj, message, exceptionType);
|
||||
throwIf(obj == null, message, exceptionType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.web.util;
|
||||
package top.continew.starter.core.wrapper;
|
||||
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.WriteListener;
|
||||
@@ -10,6 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-data-core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 数据访问模块 - 核心模块</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -57,7 +57,7 @@ public class MetaUtils {
|
||||
*/
|
||||
public static DatabaseType getDatabaseTypeOrDefault(DataSource dataSource, DatabaseType defaultValue) {
|
||||
DatabaseType databaseType = getDatabaseType(dataSource);
|
||||
return null == databaseType ? defaultValue : databaseType;
|
||||
return databaseType == null ? defaultValue : databaseType;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,12 +10,16 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-data-mf</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 数据访问模块 - MyBatis Flex</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 数据访问模块 - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-data-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis Plus(MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
|
||||
@@ -29,12 +33,6 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据访问模块 - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-data-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- CosId(通用、灵活、高性能的分布式 ID 生成器) -->
|
||||
<dependency>
|
||||
<groupId>me.ahoo.cosid</groupId>
|
||||
|
||||
@@ -43,7 +43,7 @@ public class DataPermissionDialect extends CommonsDialectImpl {
|
||||
return super.buildSelectSql(queryWrapper);
|
||||
}
|
||||
DataPermission dataPermission = DataPermissionAspect.currentDataPermission();
|
||||
if (null == dataPermission) {
|
||||
if (dataPermission == null) {
|
||||
return super.buildSelectSql(queryWrapper);
|
||||
}
|
||||
DataPermissionCurrentUser currentUser = dataPermissionFilter.getCurrentUser();
|
||||
|
||||
@@ -65,7 +65,7 @@ public class QueryWrapperHelper {
|
||||
public static <Q> QueryWrapper build(Q query) {
|
||||
QueryWrapper queryWrapper = QueryWrapper.create();
|
||||
// 没有查询条件,直接返回
|
||||
if (null == query) {
|
||||
if (query == null) {
|
||||
return queryWrapper;
|
||||
}
|
||||
// 获取查询条件中所有的字段
|
||||
@@ -85,7 +85,7 @@ public class QueryWrapperHelper {
|
||||
public static <Q> QueryWrapper build(Q query, Sort sort) {
|
||||
QueryWrapper queryWrapper = QueryWrapper.create();
|
||||
// 没有查询条件,直接返回
|
||||
if (null == query) {
|
||||
if (query == null) {
|
||||
return queryWrapper;
|
||||
}
|
||||
// 设置排序条件
|
||||
@@ -112,7 +112,7 @@ public class QueryWrapperHelper {
|
||||
*/
|
||||
public static <Q> QueryWrapper build(Q query, List<Field> fields, QueryWrapper queryWrapper) {
|
||||
// 没有查询条件,直接返回
|
||||
if (null == query) {
|
||||
if (query == null) {
|
||||
return queryWrapper;
|
||||
}
|
||||
// 解析并拼接查询条件
|
||||
@@ -150,7 +150,7 @@ public class QueryWrapperHelper {
|
||||
String fieldName = ReflectUtil.getFieldName(field);
|
||||
// 没有 @Query 注解,默认等值查询
|
||||
Query queryAnnotation = AnnotationUtil.getAnnotation(field, Query.class);
|
||||
if (null == queryAnnotation) {
|
||||
if (queryAnnotation == null) {
|
||||
return Collections.singletonList(q -> q.eq(CharSequenceUtil.toUnderlineCase(fieldName), fieldValue));
|
||||
}
|
||||
// 解析单列查询
|
||||
|
||||
@@ -10,9 +10,18 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-data-mp</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 数据访问模块 - MyBatis Plus</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 数据访问模块 - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-data-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis Plus(MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
@@ -25,12 +34,6 @@
|
||||
<artifactId>p6spy</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据访问模块 - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-data-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- CosId(通用、灵活、高性能的分布式 ID 生成器) -->
|
||||
<dependency>
|
||||
<groupId>me.ahoo.cosid</groupId>
|
||||
|
||||
@@ -121,7 +121,7 @@ public class MybatisBaseEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandl
|
||||
@Override
|
||||
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||
Object value = rs.getObject(columnName, this.propertyType);
|
||||
if (null == value || rs.wasNull()) {
|
||||
if (value == null || rs.wasNull()) {
|
||||
return null;
|
||||
}
|
||||
return this.valueOf(value);
|
||||
@@ -130,7 +130,7 @@ public class MybatisBaseEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandl
|
||||
@Override
|
||||
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||
Object value = rs.getObject(columnIndex, this.propertyType);
|
||||
if (null == value || rs.wasNull()) {
|
||||
if (value == null || rs.wasNull()) {
|
||||
return null;
|
||||
}
|
||||
return this.valueOf(value);
|
||||
@@ -139,7 +139,7 @@ public class MybatisBaseEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandl
|
||||
@Override
|
||||
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||
Object value = cs.getObject(columnIndex, this.propertyType);
|
||||
if (null == value || cs.wasNull()) {
|
||||
if (value == null || cs.wasNull()) {
|
||||
return null;
|
||||
}
|
||||
return this.valueOf(value);
|
||||
|
||||
@@ -97,7 +97,7 @@ public class QueryWrapperHelper {
|
||||
public static <Q, R> QueryWrapper<R> build(Q query, Sort sort) {
|
||||
QueryWrapper<R> queryWrapper = new QueryWrapper<>();
|
||||
// 没有查询条件,直接返回
|
||||
if (null == query) {
|
||||
if (query == null) {
|
||||
return queryWrapper;
|
||||
}
|
||||
// 设置排序条件
|
||||
@@ -125,7 +125,7 @@ public class QueryWrapperHelper {
|
||||
*/
|
||||
public static <Q, R> QueryWrapper<R> build(Q query, List<Field> fields, QueryWrapper<R> queryWrapper) {
|
||||
// 没有查询条件,直接返回
|
||||
if (null == query) {
|
||||
if (query == null) {
|
||||
return queryWrapper;
|
||||
}
|
||||
// 解析并拼接查询条件
|
||||
@@ -161,7 +161,7 @@ public class QueryWrapperHelper {
|
||||
String fieldName = ReflectUtil.getFieldName(field);
|
||||
// 没有 @Query 注解,默认等值查询
|
||||
Query queryAnnotation = AnnotationUtil.getAnnotation(field, Query.class);
|
||||
if (null == queryAnnotation) {
|
||||
if (queryAnnotation == null) {
|
||||
return Collections.singletonList(q -> q.eq(CharSequenceUtil.toUnderlineCase(fieldName), fieldValue));
|
||||
}
|
||||
// 解析单列查询
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
<artifactId>continew-starter-data</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 数据访问模块</description>
|
||||
|
||||
<modules>
|
||||
|
||||
@@ -8,11 +8,13 @@
|
||||
<artifactId>continew-starter-dependencies</artifactId>
|
||||
<version>${revision}</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 依赖模块</description>
|
||||
|
||||
<properties>
|
||||
<!-- 项目版本号 -->
|
||||
<revision>2.12.0</revision>
|
||||
<revision>2.12.2</revision>
|
||||
<spring-boot.version>3.3.11</spring-boot.version>
|
||||
<spring-cloud.version>2023.0.5</spring-cloud.version>
|
||||
<redisson.version>3.46.0</redisson.version>
|
||||
|
||||
@@ -10,18 +10,16 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-extension-crud-core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 扩展模块 - CRUD(增删改查) - 核心模块</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Crane4j(基于注解的,用于完成一切 “根据 A 的 key 值拿到 B,再把 B 的属性映射到 A” 这类需求的字段填充框架) -->
|
||||
<!-- API 文档模块 -->
|
||||
<dependency>
|
||||
<groupId>cn.crane4j</groupId>
|
||||
<artifactId>crane4j-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-commons</artifactId>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-api-doc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 模块 -->
|
||||
@@ -42,10 +40,10 @@
|
||||
<artifactId>continew-starter-file-excel</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- API 文档模块 -->
|
||||
<!-- Crane4j(基于注解的,用于完成一切 “根据 A 的 key 值拿到 B,再把 B 的属性映射到 A” 这类需求的字段填充框架) -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-api-doc</artifactId>
|
||||
<groupId>cn.crane4j</groupId>
|
||||
<artifactId>crane4j-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -42,7 +42,7 @@ public class CrudRequestMappingHandlerMapping extends RequestMappingHandlerMappi
|
||||
@Override
|
||||
protected RequestMappingInfo getMappingForMethod(@NonNull Method method, @NonNull Class<?> handlerType) {
|
||||
RequestMappingInfo requestMappingInfo = super.getMappingForMethod(method, handlerType);
|
||||
if (null == requestMappingInfo) {
|
||||
if (requestMappingInfo == null) {
|
||||
return null;
|
||||
}
|
||||
// 如果没有声明 @CrudRequestMapping 注解,直接返回
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-extension-crud-mf</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 扩展模块 - CRUD(增删改查) - MyBatis Flex ORM 模块</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -56,7 +56,7 @@ public class PageResp<L> extends BasePageResp<L> {
|
||||
* @return 分页信息
|
||||
*/
|
||||
public static <T, L> PageResp<L> build(Page<T> page, Class<L> targetClass) {
|
||||
if (null == page) {
|
||||
if (page == null) {
|
||||
return empty();
|
||||
}
|
||||
return new PageResp<>(BeanUtil.copyToList(page.getRecords(), targetClass), page.getTotalRow());
|
||||
@@ -70,7 +70,7 @@ public class PageResp<L> extends BasePageResp<L> {
|
||||
* @return 分页信息
|
||||
*/
|
||||
public static <L> PageResp<L> build(Page<L> page) {
|
||||
if (null == page) {
|
||||
if (page == null) {
|
||||
return empty();
|
||||
}
|
||||
return new PageResp<>(page.getRecords(), page.getTotalRow());
|
||||
|
||||
@@ -220,7 +220,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
|
||||
* @param obj 待填充信息
|
||||
*/
|
||||
protected void fill(Object obj) {
|
||||
if (null == obj) {
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
OperateTemplate operateTemplate = SpringUtil.getBean(OperateTemplate.class);
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-extension-crud-mp</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 扩展模块 - CRUD(增删改查) - MyBatis Plus ORM 模块</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -56,7 +56,7 @@ public class PageResp<L> extends BasePageResp<L> {
|
||||
* @return 分页信息
|
||||
*/
|
||||
public static <T, L> PageResp<L> build(IPage<T> page, Class<L> targetClass) {
|
||||
if (null == page) {
|
||||
if (page == null) {
|
||||
return empty();
|
||||
}
|
||||
return new PageResp<>(BeanUtil.copyToList(page.getRecords(), targetClass), page.getTotal());
|
||||
@@ -70,7 +70,7 @@ public class PageResp<L> extends BasePageResp<L> {
|
||||
* @return 分页信息
|
||||
*/
|
||||
public static <L> PageResp<L> build(IPage<L> page) {
|
||||
if (null == page) {
|
||||
if (page == null) {
|
||||
return empty();
|
||||
}
|
||||
return new PageResp<>(page.getRecords(), page.getTotal());
|
||||
|
||||
@@ -311,7 +311,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
|
||||
* @param obj 待填充信息
|
||||
*/
|
||||
protected void fill(Object obj) {
|
||||
if (null == obj) {
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
OperateTemplate operateTemplate = SpringUtil.getBean(OperateTemplate.class);
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
<artifactId>continew-starter-extension-crud</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 扩展模块 - CRUD(增删改查)</description>
|
||||
|
||||
<modules>
|
||||
|
||||
@@ -10,5 +10,8 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-extension-datapermission-core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 扩展模块 - 数据权限 - 核心模块</description>
|
||||
</project>
|
||||
@@ -10,6 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-extension-datapermission-mp</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 扩展模块 - 数据权限 - MyBatis Plus ORM 模块</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -36,11 +36,7 @@ import net.sf.jsqlparser.expression.LongValue;
|
||||
import net.sf.jsqlparser.expression.StringValue;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
|
||||
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
|
||||
import net.sf.jsqlparser.expression.operators.relational.InExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
|
||||
import net.sf.jsqlparser.expression.operators.relational.*;
|
||||
import net.sf.jsqlparser.schema.Column;
|
||||
import net.sf.jsqlparser.schema.Table;
|
||||
import net.sf.jsqlparser.statement.select.ParenthesedSelect;
|
||||
@@ -82,7 +78,7 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
|
||||
for (Method method : methodArr) {
|
||||
DataPermission dataPermission = method.getAnnotation(DataPermission.class);
|
||||
String name = method.getName();
|
||||
if (null == dataPermission || !CharSequenceUtil.equalsAny(methodName, name, name + "_COUNT")) {
|
||||
if (dataPermission == null || !CharSequenceUtil.equalsAny(methodName, name, name + "_COUNT")) {
|
||||
continue;
|
||||
}
|
||||
if (dataPermissionUserContextProvider.isFilter()) {
|
||||
@@ -153,8 +149,8 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
|
||||
if (DatabaseType.MYSQL.getDatabase().equalsIgnoreCase(databaseType.getDatabase())) {
|
||||
Function findInSetFunction = new Function();
|
||||
findInSetFunction.setName("find_in_set");
|
||||
findInSetFunction.setParameters(new ExpressionList<>(new LongValue(userContext
|
||||
.getDeptId()), new StringValue(new Column("ancestors") + ",")));
|
||||
findInSetFunction.setParameters(new ExpressionList(new LongValue(userContext
|
||||
.getDeptId()), new Column("ancestors")));
|
||||
inSetExpression = findInSetFunction;
|
||||
} else if (DatabaseType.POSTGRE_SQL.getDatabase().equalsIgnoreCase(databaseType.getDatabase())) {
|
||||
// 构建 concat 函数
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
<artifactId>continew-starter-extension-datapermission</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 扩展模块 - 数据权限</description>
|
||||
|
||||
<modules>
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-extension-tenant-core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 扩展模块 - 多租户 - 核心模块</description>
|
||||
|
||||
<dependencies>
|
||||
@@ -18,17 +21,5 @@
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>transmittable-thread-local</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -10,6 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-extension-tenant-mp</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 扩展模块 - 多租户 - MyBatis Plus ORM 模块</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -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.line.DefaultTenantLineHandler;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* 租户自动配置
|
||||
*
|
||||
@@ -72,8 +74,8 @@ public class TenantAutoConfiguration {
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantLineHandler tenantLineHandler(TenantProperties properties) {
|
||||
return new DefaultTenantLineHandler(properties);
|
||||
public TenantLineHandler tenantLineHandler() {
|
||||
return new DefaultTenantLineHandler(tenantProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,7 +101,7 @@ public class TenantAutoConfiguration {
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantDataSourceHandler tenantDataSourceHandler(javax.sql.DataSource dataSource,
|
||||
public TenantDataSourceHandler tenantDataSourceHandler(DataSource dataSource,
|
||||
DefaultDataSourceCreator dataSourceCreator) {
|
||||
return new DefaultTenantDataSourceHandler((DynamicRoutingDataSource)dataSource, dataSourceCreator);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
<artifactId>continew-starter-extension-tenant</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 扩展模块 - 多租户</description>
|
||||
|
||||
<modules>
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
<artifactId>continew-starter-extension</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 扩展模块</description>
|
||||
|
||||
<modules>
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-file-excel</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 文件处理模块 - Excel</description>
|
||||
|
||||
<dependencies>
|
||||
@@ -18,11 +21,5 @@
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>easyexcel</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Jakarta(原 Javax Servlet) -->
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -61,7 +61,7 @@ public class ExcelBaseEnumConverter implements Converter<BaseEnum<?>> {
|
||||
public WriteCellData<String> convertToExcelData(BaseEnum<?> value,
|
||||
ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
if (null == value) {
|
||||
if (value == null) {
|
||||
return new WriteCellData<>(StringConstants.EMPTY);
|
||||
}
|
||||
return new WriteCellData<>(value.getDescription());
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
<artifactId>continew-starter-file</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 文件处理模块</description>
|
||||
|
||||
<modules>
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-idempotent</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 幂等模块</description>
|
||||
|
||||
<dependencies>
|
||||
@@ -18,5 +21,11 @@
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-cache-redisson</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Hutool 加密解密模块(封装 JDK 中加密解密算法) -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -18,7 +18,6 @@ package top.continew.starter.idempotent.aop;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
@@ -98,7 +97,7 @@ public class IdempotentAspect {
|
||||
String key = idempotent.key();
|
||||
if (CharSequenceUtil.isNotBlank(key)) {
|
||||
Object eval = ExpressionUtils.eval(key, target, method, args);
|
||||
if (ObjectUtil.isNull(eval)) {
|
||||
if (eval == null) {
|
||||
throw new IdempotentException("幂等 Key 解析错误");
|
||||
}
|
||||
key = Convert.toStr(eval);
|
||||
|
||||
@@ -19,16 +19,15 @@ package top.continew.starter.idempotent.autoconfigure;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import top.continew.starter.cache.redisson.autoconfigure.RedissonAutoConfiguration;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.idempotent.aop.IdempotentAspect;
|
||||
import top.continew.starter.idempotent.generator.DefaultIdempotentNameGenerator;
|
||||
import top.continew.starter.idempotent.generator.IdempotentNameGenerator;
|
||||
|
||||
/**
|
||||
@@ -60,11 +59,7 @@ public class IdempotentAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public IdempotentNameGenerator idempotentNameGenerator() {
|
||||
if (log.isErrorEnabled()) {
|
||||
log.error("Consider defining a bean of type '{}' in your configuration.", ResolvableType
|
||||
.forClass(IdempotentNameGenerator.class));
|
||||
}
|
||||
throw new NoSuchBeanDefinitionException(IdempotentNameGenerator.class);
|
||||
return new DefaultIdempotentNameGenerator();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-json-jackson</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter JSON 模块 - Jackson</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -16,28 +16,6 @@
|
||||
|
||||
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.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -45,24 +23,60 @@ import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
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 自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author Jasmine
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(JacksonExtensionProperties.class)
|
||||
@PropertySource(value = "classpath:default-json-jackson.yml", factory = GeneralPropertySourceFactory.class)
|
||||
public class JacksonAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(JacksonAutoConfiguration.class);
|
||||
private final JacksonExtensionProperties properties;
|
||||
|
||||
public JacksonAutoConfiguration(JacksonExtensionProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
|
||||
return builder -> {
|
||||
JavaTimeModule javaTimeModule = this.timeModule();
|
||||
SimpleModule simpleModule = this.simpleModule();
|
||||
JavaTimeModule javaTimeModule = this.javaTimeModule();
|
||||
SimpleModule baseEnumModule = this.baseEnumModule();
|
||||
SimpleModule bigNumberModule = this.bigNumberModule();
|
||||
|
||||
builder.timeZone(TimeZone.getDefault());
|
||||
builder.modules(javaTimeModule, simpleModule);
|
||||
builder.modules(javaTimeModule, baseEnumModule, bigNumberModule);
|
||||
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
|
||||
*/
|
||||
private JavaTimeModule timeModule() {
|
||||
// 针对大数值的序列化处理
|
||||
private JavaTimeModule 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 的序列化和反序列化处理
|
||||
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN);
|
||||
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter));
|
||||
@@ -100,7 +110,7 @@ public class JacksonAutoConfiguration {
|
||||
* @return SimpleModule /
|
||||
* @since 2.4.0
|
||||
*/
|
||||
private SimpleModule simpleModule() {
|
||||
private SimpleModule baseEnumModule() {
|
||||
SimpleModule simpleModule = new SimpleModule();
|
||||
simpleModule.addSerializer(BaseEnum.class, BaseEnumSerializer.SERIALIZER_INSTANCE);
|
||||
SimpleDeserializersWrapper deserializers = new SimpleDeserializersWrapper();
|
||||
@@ -108,4 +118,28 @@ public class JacksonAutoConfiguration {
|
||||
simpleModule.setDeserializers(deserializers);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
* JS:Number.MIN_SAFE_INTEGER:-9007199254740991L <br />
|
||||
* JS:Number.MAX_SAFE_INTEGER:9007199254740991L
|
||||
* </p>
|
||||
*/
|
||||
FLEXIBLE,
|
||||
|
||||
/**
|
||||
* 统一转为 {@link String} 类型
|
||||
*/
|
||||
TO_STRING,
|
||||
|
||||
/**
|
||||
* 不操作(不建议)
|
||||
* <p>
|
||||
* 注意:超过 JS 范围的数值会损失精度,例如:8014753905961037835 会被转为 8014753905961038000
|
||||
* </p>
|
||||
*/
|
||||
NO_OPERATE,
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package top.continew.starter.json.jackson.util;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
@@ -61,7 +60,7 @@ public class JSONUtils {
|
||||
* @return {@link String }
|
||||
*/
|
||||
public static String toJsonStr(Object object) {
|
||||
if (ObjectUtil.isNull(object)) {
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -21,3 +21,5 @@ spring:
|
||||
deserialization:
|
||||
# 允许反序列化不存在的属性
|
||||
fail-on-unknown-properties: false
|
||||
# 大数值序列化模式
|
||||
big-number-serialize-mode: FLEXIBLE
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
<artifactId>continew-starter-json</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter JSON 模块</description>
|
||||
|
||||
<modules>
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-license-core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter License 模块 - 核心模块</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-license-generator</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter License 模块 - 生成器</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-license-verifier</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter License 模块 - 校验器</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
<artifactId>continew-starter-license</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter License模块</description>
|
||||
|
||||
<modules>
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-log-aop</artifactId>
|
||||
<description>ContiNew Starter 日志模块 - 基于 AOP 实现</description>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 日志模块 - 基于 AOP 实现</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 日志模块 - 核心模块 -->
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-log-core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 日志模块 - 核心模块</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -25,8 +25,8 @@ import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import top.continew.starter.web.util.RepeatReadRequestWrapper;
|
||||
import top.continew.starter.web.util.RepeatReadResponseWrapper;
|
||||
import top.continew.starter.core.wrapper.RepeatReadRequestWrapper;
|
||||
import top.continew.starter.core.wrapper.RepeatReadResponseWrapper;
|
||||
import top.continew.starter.log.model.LogProperties;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -31,7 +31,7 @@ import top.continew.starter.log.model.AccessLogContext;
|
||||
import top.continew.starter.log.model.AccessLogProperties;
|
||||
import top.continew.starter.log.model.LogRecord;
|
||||
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.time.Duration;
|
||||
@@ -70,7 +70,7 @@ public abstract class AbstractLogHandler implements LogHandler {
|
||||
return false;
|
||||
}
|
||||
Log classLog = AnnotationUtil.getAnnotation(targetClass, Log.class);
|
||||
return null == classLog || !classLog.ignore();
|
||||
return classLog == null || !classLog.ignore();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
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.List;
|
||||
|
||||
@@ -20,7 +20,7 @@ import cn.hutool.core.text.CharSequenceUtil;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import top.continew.starter.core.util.ExceptionUtils;
|
||||
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 java.net.URI;
|
||||
@@ -93,7 +93,7 @@ public class LogRequest {
|
||||
this.address = (includes.contains(Include.IP_ADDRESS))
|
||||
? ExceptionUtils.exToNull(() -> IpUtils.getIpv4Address(this.ip))
|
||||
: null;
|
||||
if (null == this.headers) {
|
||||
if (this.headers == null) {
|
||||
return;
|
||||
}
|
||||
String userAgentString = this.headers.entrySet()
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
package top.continew.starter.log.model;
|
||||
|
||||
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.Set;
|
||||
|
||||
@@ -18,12 +18,11 @@ package top.continew.starter.log.util;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import top.continew.starter.json.jackson.util.JSONUtils;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import top.continew.starter.log.model.AccessLogProperties;
|
||||
import top.continew.starter.log.model.LogProperties;
|
||||
import top.continew.starter.web.util.ServletUtils;
|
||||
import top.continew.starter.web.util.SpringWebUtils;
|
||||
import top.continew.starter.core.util.ServletUtils;
|
||||
import top.continew.starter.core.util.SpringWebUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -79,7 +78,7 @@ public class AccessLogUtils {
|
||||
params = processTruncateLongParams(params, properties.getLongParamThreshold(), properties
|
||||
.getLongParamMaxLength(), properties.getLongParamSuffix());
|
||||
}
|
||||
return JSONUtils.toJsonStr(params);
|
||||
return JSONUtil.toJsonStr(params);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,13 +191,12 @@ public class AccessLogUtils {
|
||||
*/
|
||||
private static Object getAccessLogReqParam() {
|
||||
String body = ServletUtils.getRequestBody();
|
||||
if (CharSequenceUtil.isNotBlank(body) && JSONUtils.isTypeJSON(body)) {
|
||||
if (CharSequenceUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body)) {
|
||||
try {
|
||||
JsonNode jsonNode = JSONUtils.getObjectMapper().readTree(body);
|
||||
if (jsonNode.isArray()) {
|
||||
return JSONUtils.toBean(body, List.class);
|
||||
if (JSONUtil.isTypeJSONArray(body)) {
|
||||
return JSONUtil.toBean(body, List.class);
|
||||
} else {
|
||||
return JSONUtils.toBean(body, Map.class);
|
||||
return JSONUtil.toBean(body, Map.class);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-log-interceptor</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 日志模块 - 基于拦截器实现(Spring Boot Actuator HttpTrace 增强版)</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -76,7 +76,7 @@ public class LogInterceptor implements HandlerInterceptor {
|
||||
Instant endTime = Instant.now();
|
||||
logHandler.accessLogFinish(AccessLogContext.builder().endTime(endTime).build());
|
||||
LogRecord.Started startedLogRecord = logTtl.get();
|
||||
if (null == startedLogRecord) {
|
||||
if (startedLogRecord == null) {
|
||||
return;
|
||||
}
|
||||
// 结束日志记录
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
<artifactId>continew-starter-log</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 日志模块</description>
|
||||
|
||||
<modules>
|
||||
@@ -20,10 +22,10 @@
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
<!-- Web 模块 -->
|
||||
<!-- 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-web</artifactId>
|
||||
<artifactId>continew-starter-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -10,6 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-messaging-mail</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 消息模块 - 邮件</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-messaging-websocket</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 消息模块 - WebSocket</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -18,6 +18,9 @@ package top.continew.starter.messaging.websocket.dao;
|
||||
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* WebSocket 会话 DAO
|
||||
*
|
||||
@@ -48,4 +51,20 @@ public interface WebSocketSessionDao {
|
||||
* @return 会话信息
|
||||
*/
|
||||
WebSocketSession get(String key);
|
||||
|
||||
/**
|
||||
* 获取所有会话
|
||||
*
|
||||
* @return 所有会话
|
||||
* @since 2.12.1
|
||||
*/
|
||||
Collection<WebSocketSession> listAll();
|
||||
|
||||
/**
|
||||
* 获取所有会话 ID
|
||||
*
|
||||
* @return 所有会话 ID
|
||||
* @since 2.12.1
|
||||
*/
|
||||
Set<String> listAllSessionIds();
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ package top.continew.starter.messaging.websocket.dao;
|
||||
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
@@ -45,4 +47,14 @@ public class WebSocketSessionDaoDefaultImpl implements WebSocketSessionDao {
|
||||
public WebSocketSession get(String key) {
|
||||
return SESSION_MAP.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<WebSocketSession> listAll() {
|
||||
return SESSION_MAP.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> listAllSessionIds() {
|
||||
return SESSION_MAP.keySet();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package top.continew.starter.messaging.websocket.util;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -25,6 +26,8 @@ import org.springframework.web.socket.WebSocketSession;
|
||||
import top.continew.starter.messaging.websocket.dao.WebSocketSessionDao;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* WebSocket 工具类
|
||||
@@ -62,6 +65,28 @@ public class WebSocketUtils {
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
<artifactId>continew-starter-messaging</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 消息模块</description>
|
||||
|
||||
<modules>
|
||||
|
||||
@@ -8,13 +8,16 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-ratelimiter</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 限流模块</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Web 模块 -->
|
||||
<!-- 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-web</artifactId>
|
||||
<artifactId>continew-starter-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 缓存模块 - Redisson -->
|
||||
|
||||
@@ -18,7 +18,6 @@ package top.continew.starter.ratelimiter.aop;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
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.enums.LimitType;
|
||||
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.time.Duration;
|
||||
@@ -162,7 +161,7 @@ public class RateLimiterAspect {
|
||||
String key = rateLimiter.key();
|
||||
if (CharSequenceUtil.isNotBlank(key)) {
|
||||
Object eval = ExpressionUtils.eval(key, target, method, args);
|
||||
if (ObjectUtil.isNull(eval)) {
|
||||
if (eval == null) {
|
||||
throw new RateLimiterException("限流 Key 解析错误");
|
||||
}
|
||||
key = Convert.toStr(eval);
|
||||
|
||||
@@ -24,7 +24,6 @@ import java.lang.reflect.Method;
|
||||
* @author Charles7c
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RateLimiterNameGenerator {
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-security-crypto</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 安全模块 - 加密</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -52,7 +52,7 @@ public abstract class AbstractMyBatisInterceptor {
|
||||
* @return 字段列表
|
||||
*/
|
||||
protected List<Field> getEncryptFields(Object obj) {
|
||||
if (null == obj) {
|
||||
if (obj == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return this.getEncryptFields(obj.getClass());
|
||||
@@ -98,7 +98,7 @@ public abstract class AbstractMyBatisInterceptor {
|
||||
String mappedStatementId = mappedStatement.getId();
|
||||
return ENCRYPT_PARAM_CACHE.computeIfAbsent(mappedStatementId, key -> {
|
||||
Method method = this.getMethod(mappedStatementId);
|
||||
if (null == method) {
|
||||
if (method == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<String, FieldEncrypt> encryptMap = new HashMap<>();
|
||||
@@ -106,7 +106,7 @@ public abstract class AbstractMyBatisInterceptor {
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
Parameter parameter = parameters[i];
|
||||
FieldEncrypt fieldEncrypt = parameter.getAnnotation(FieldEncrypt.class);
|
||||
if (null == fieldEncrypt) {
|
||||
if (fieldEncrypt == null) {
|
||||
continue;
|
||||
}
|
||||
String parameterName = this.getParameterName(parameter);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package top.continew.starter.security.crypto.core;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
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.Signature;
|
||||
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.autoconfigure.CryptoProperties;
|
||||
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})})
|
||||
public class MyBatisDecryptInterceptor extends AbstractMyBatisInterceptor implements Interceptor {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MyBatisDecryptInterceptor.class);
|
||||
private CryptoProperties properties;
|
||||
|
||||
public MyBatisDecryptInterceptor(CryptoProperties properties) {
|
||||
@@ -53,31 +57,73 @@ public class MyBatisDecryptInterceptor extends AbstractMyBatisInterceptor implem
|
||||
@Override
|
||||
public Object intercept(Invocation invocation) throws Throwable {
|
||||
Object obj = invocation.proceed();
|
||||
if (null == obj || !(invocation.getTarget() instanceof ResultSetHandler)) {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
// 确保目标是 ResultSetHandler
|
||||
if (!(invocation.getTarget() instanceof ResultSetHandler)) {
|
||||
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) {
|
||||
// String、Integer、Long 等简单类型对象无需处理
|
||||
if (SimpleTypeRegistry.isSimpleType(result.getClass())) {
|
||||
decryptObject(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密单个对象结果
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
// 获取所有字符串类型、需要解密的、有值字段
|
||||
List<Field> fieldList = super.getEncryptFields(result);
|
||||
// 解密处理
|
||||
for (Field field : fieldList) {
|
||||
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 password = ObjectUtil.defaultIfBlank(field.getAnnotation(FieldEncrypt.class).password(), properties
|
||||
.getPassword());
|
||||
try {
|
||||
String ciphertext = encryptor.decrypt(fieldValue.toString(), password, properties.getPrivateKey());
|
||||
ReflectUtil.setFieldValue(result, field, ciphertext);
|
||||
} catch (Exception e) {
|
||||
// 解密失败时保留原值,避免影响正常业务流程
|
||||
log.warn("解密失败,请检查加密配置", e);
|
||||
}
|
||||
}
|
||||
return resultList;
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,9 @@ import org.apache.ibatis.mapping.BoundSql;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.session.ResultHandler;
|
||||
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.exception.BaseException;
|
||||
import top.continew.starter.security.crypto.annotation.FieldEncrypt;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoProperties;
|
||||
import top.continew.starter.security.crypto.encryptor.IEncryptor;
|
||||
@@ -50,6 +51,7 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implements InnerInterceptor {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MyBatisEncryptInterceptor.class);
|
||||
private static final Pattern PARAM_PAIRS_PATTERN = Pattern
|
||||
.compile("#\\{ew\\.paramNameValuePairs\\.(" + Constants.WRAPPER_PARAM + "\\d+)\\}");
|
||||
private final CryptoProperties properties;
|
||||
@@ -65,7 +67,7 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implem
|
||||
RowBounds rowBounds,
|
||||
ResultHandler resultHandler,
|
||||
BoundSql boundSql) {
|
||||
if (null == parameterObject) {
|
||||
if (parameterObject == null) {
|
||||
return;
|
||||
}
|
||||
if (parameterObject instanceof Map parameterMap) {
|
||||
@@ -75,7 +77,7 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implem
|
||||
|
||||
@Override
|
||||
public void beforeUpdate(Executor executor, MappedStatement mappedStatement, Object parameterObject) {
|
||||
if (null == parameterObject) {
|
||||
if (parameterObject == null) {
|
||||
return;
|
||||
}
|
||||
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 注解的场景)
|
||||
*
|
||||
@@ -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 等参数)
|
||||
*
|
||||
@@ -156,7 +185,7 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implem
|
||||
propMap.put(elPart[0], elPart[1]);
|
||||
});
|
||||
// 获取加密字段
|
||||
Class<?> entityClass = mappedStatement.getParameterMap().getType();
|
||||
Class<?> entityClass = this.getEntityClass(updateWrapper, mappedStatement);
|
||||
List<Field> encryptFieldList = super.getEncryptFields(entityClass);
|
||||
for (Field field : encryptFieldList) {
|
||||
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 字段加密注解
|
||||
*/
|
||||
private Object doEncrypt(Object parameterValue, FieldEncrypt fieldEncrypt) {
|
||||
if (null == parameterValue) {
|
||||
if (parameterValue == null) {
|
||||
return null;
|
||||
}
|
||||
IEncryptor encryptor = super.getEncryptor(fieldEncrypt);
|
||||
@@ -217,7 +220,26 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implem
|
||||
try {
|
||||
return encryptor.encrypt(parameterValue.toString(), password, properties.getPublicKey());
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,5 +10,8 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-security-mask</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 安全模块 - 脱敏</description>
|
||||
</project>
|
||||
@@ -68,7 +68,7 @@ public class JsonMaskSerializer extends JsonSerializer<String> implements Contex
|
||||
@Override
|
||||
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider,
|
||||
BeanProperty beanProperty) throws JsonMappingException {
|
||||
if (null == beanProperty) {
|
||||
if (beanProperty == null) {
|
||||
return serializerProvider.findNullValueSerializer(null);
|
||||
}
|
||||
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
|
||||
.getContextAnnotation(JsonMask.class));
|
||||
if (null == jsonMaskAnnotation) {
|
||||
if (jsonMaskAnnotation == null) {
|
||||
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
|
||||
}
|
||||
return new JsonMaskSerializer(jsonMaskAnnotation);
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-security-password</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 安全模块 - 密码编码器</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user