Compare commits

..

26 Commits
2.1.x ... 2.3.x

Author SHA1 Message Date
8bacd87d25 docs: 更新项目源码链接 2024-07-24 21:15:58 +08:00
吴泽威
c1ebc4621c build: 代码编译增加 -parameters 参数 2024-07-19 03:49:06 +00:00
a0388b5dc8 release: v2.3.0 2024-07-18 23:43:16 +08:00
e7566d284b fix(web): 修复文件上传异常单位显示错误 2024-07-18 23:27:59 +08:00
c17668c2d1 fix(extension/crud): 修复 Name for argument of type [java.lang.Long] not specified, and parameter name information not available via reflection. 错误 2024-07-18 21:18:27 +08:00
65cfe91770 fix(extension/crud): 修复 DictField 映射错误
Closes #IADTTC
2024-07-18 21:13:59 +08:00
dca715709f refactor(extension/crud): 调整 BaseService 相关泛型类型加载为懒加载 2024-07-16 22:57:35 +08:00
a110bd9789 chore: 升级依赖
SpringBoot 3.1.11 => 3.2.7
SnailJob 1.1.0-beta1 => 1.1.0
MyBatisPlus 3.5.5 => 3.5.7
MyBatisFlex 1.8.9 => 1.9.3
dynamic-datasource 4.3.0 => 4.3.1
JetCache 2.7.5 => 2.7.6
Redisson 3.30.0 => 3.32.0
CosID 2.6.8 => 2.9.1
EasyExcel 3.3.4 => 4.0.1
XFileStorage 2.1.0 => 2.2.0
Crane4j 2.8.0 => 2.9.0
Hutool 5.8.27 => 5.8.29
AWS S3 1.12.720 => 1.12.761
IP2Region 3.1.11 => 3.2.6
2024-07-16 22:37:46 +08:00
b0f5506424 refactor(core): 优化 JSR 303 校验方法 2024-07-03 23:23:06 +08:00
6809600858 feat(core): 新增 JSR 303 校验器自动配置(从 web 模块迁移)
1.从 web 模块移动 JSR 303 校验器自动配置到 core 模块
2.从 web 模块移动 MessageSourceUtils 到 core 模块
2024-07-03 23:22:07 +08:00
d31d8d209a chore: 新增 Snail Job 依赖版本 2024-07-02 22:22:19 +08:00
4f3ee18bef release: v2.2.0 2024-06-30 22:24:42 +08:00
13788d6f57 build(security/limiter): 移除无用依赖 2024-06-30 12:26:44 +08:00
635b664d5f chore(captcha/behavior): 默认启用行为验证码自动配置 2024-06-30 12:25:14 +08:00
3e4b6ab3a9 chore(messaging/mail): 优化邮件配置服务命名 2024-06-30 12:06:44 +08:00
7bc25b2f8b fix(security/limiter): 修复默认限流器名称生成器错误 2024-06-30 11:10:38 +08:00
51c47751f4 refactor(security/limiter): 重构限流器,支持自定义限流器名称生成器 2024-06-28 23:34:51 +08:00
13b3f24845 feat(core): 新增表达式解析工具类 2024-06-28 23:03:56 +08:00
6b90880c21 chore: 优化属性前缀命名 2024-06-28 21:42:53 +08:00
0ad7b18521 refactor(core): 重构线程池自动配置 2024-06-26 22:51:09 +08:00
82574cd5ce fix(api-doc): 修复接口文档配置错误 2024-06-25 22:07:49 +08:00
491721e887 chore: 优化部分文件格式 2024-06-25 21:32:15 +08:00
de056aa0c4 refactor(core): 重构线程池自动配置 2024-06-25 21:31:42 +08:00
KAI
a89765f49e feat(security/limiter): 新增限流器 2024-06-25 01:01:43 +00:00
3e9a15295a feat(core): 新增 JSR 303 校验方法 2024-06-24 22:09:09 +08:00
jasmine
ce08f28a61 feat: 新增国际化及全局异常码配置 2024-06-24 13:44:03 +00:00
68 changed files with 1855 additions and 459 deletions

View File

@@ -1,3 +1,57 @@
## [v2.3.0](https://github.com/continew-org/continew-starter/compare/v2.2.0...v2.3.0) (2024-07-18)
### ✨ 新特性
- 【core】新增 JSR 303 校验器自动配置(从 web 模块迁移) ([6809600](https://github.com/continew-org/continew-starter/commit/6809600858ed597567f78581187f6d88a2ea899e))
- 新增 Snail Job 依赖版本 ([d31d8d2](https://github.com/continew-org/continew-starter/commit/d31d8d209a66884d046763bb8497b2c58cf88506))
### 🐛 问题修复
- 【extension/crud】修复 DictField 映射错误 ([65cfe91](https://github.com/continew-org/continew-starter/commit/65cfe917709320edd9db2ae55390afe64077e3d3))
- 【extension/crud】修复 Name for argument of type [java.lang.Long] not specified, and parameter name information not available via reflection. 错误 ([c17668c](https://github.com/continew-org/continew-starter/commit/c17668c2d1a9440dd0260fd7d8b2a28f104bbce6))
- 【web】修复文件上传异常单位显示错误 ([e7566d2](https://github.com/continew-org/continew-starter/commit/e7566d284b53b47577ade59c0b7e9262f9b43758))
### 💎 功能优化
- 【core】优化 JSR 303 校验方法 ([b0f5506](https://github.com/continew-org/continew-starter/commit/b0f55064242615717789b3d62880e482ea72a23a))
- 【extension/crud】调整 BaseService 相关泛型类型加载为懒加载 ([dca7157](https://github.com/continew-org/continew-starter/commit/dca715709faa9fbd61194ea4177c91475b768694))
### 📦 依赖升级
- SpringBoot 3.1.11 => 3.2.7TaskExecutor => ThreadPoolTaskExecutor
- MyBatisPlus 3.5.5 => 3.5.7(数据权限处理器调整)
- MyBatisFlex 1.8.9 => 1.9.3
- dynamic-datasource 4.3.0 => 4.3.1
- JetCache 2.7.5 => 2.7.6
- Redisson 3.30.0 => 3.32.0
- CosID 2.6.8 => 2.9.1
- EasyExcel 3.3.4 => 4.0.1
- XFileStorage 2.1.0 => 2.2.0
- Crane4j 2.8.0 => 2.9.0
- Hutool 5.8.27 => 5.8.29
- AWS S3 1.12.720 => 1.12.761
- IP2Region 3.1.11 => 3.2.6
## [v2.2.0](https://github.com/continew-org/continew-starter/compare/v2.1.1...v2.2.0) (2024-06-30)
### ✨ 新特性
- 新增国际化及全局异常码配置 (Gitee#25) ([ce08f28](https://github.com/continew-org/continew-starter/commit/ce08f28a618bbeb2c501defe71f9018972a4828b))
- 【core】新增 JSR 303 校验方法 ([3e9a152](https://github.com/continew-org/continew-starter/commit/3e9a15295a79901cf1c5fa603d6a7407e7e2a2ec))
- 【security/limiter】新增限流器 ([a89765f](https://github.com/continew-org/continew-starter/commit/a89765f49ef9d3b1ce4b3a420507b43792ed69a1)) ([51c4775](https://github.com/continew-org/continew-starter/commit/51c47751f4ef92bb111619ee9ceb7c3ce4e2dba4)) ([7bc25b2](https://github.com/continew-org/continew-starter/commit/7bc25b2f8bdb74ad295c54ab82cdae88f6264096)) ([13788d6](https://github.com/continew-org/continew-starter/commit/13788d6f5796e87900dd83ece955cd921ffc3946))
- 【core】新增表达式 SPEL 解析工具类 ([13b3f24](https://github.com/continew-org/continew-starter/commit/13b3f2484555b69dd25b280806f98d98d53f75fe))
### 🐛 问题修复
- 【api-doc】修复接口文档配置错误 ([82574cd](https://github.com/continew-org/continew-starter/commit/82574cd5cee923d6dfe447414c0a2453defc8790))
### 💎 功能优化
- 【core】重构线程池自动配置 ([de056aa](https://github.com/continew-org/continew-starter/commit/de056aa0c42621f5d5cf7690f2a42f54ffa1cd7e)) ([0ad7b18](https://github.com/continew-org/continew-starter/commit/0ad7b185212da31d8b6afdab6dd9cd8f72f83acb))
- 优化属性前缀命名 ([6b90880](https://github.com/continew-org/continew-starter/commit/6b90880c21d4fd7e603397692cf88a98f30194a0))
- 【captcha/behavior】默认启用行为验证码自动配置 ([635b664](https://github.com/continew-org/continew-starter/commit/635b664d5f92e5d01cadef4c868753eb41279c7d))
- 【messaging/mail】优化邮件配置服务命名 ([3e4b6ab](https://github.com/continew-org/continew-starter/commit/3e4b6ab3a9590639e1fa606b0d52b29e83ecb890))
## [v2.1.1](https://github.com/continew-org/continew-starter/compare/v2.1.0...v2.1.1) (2024-06-23) ## [v2.1.1](https://github.com/continew-org/continew-starter/compare/v2.1.0...v2.1.1) (2024-06-23)
### ✨ 新特性 ### ✨ 新特性

110
README.md
View File

@@ -13,7 +13,7 @@
<img src="https://sonarcloud.io/api/project_badges/measure?project=Charles7c_continew-starter&metric=alert_status" alt="Sonar Status" /> <img src="https://sonarcloud.io/api/project_badges/measure?project=Charles7c_continew-starter&metric=alert_status" alt="Sonar Status" />
</a> </a>
<a href="https://spring.io/projects/spring-boot" target="_blank"> <a href="https://spring.io/projects/spring-boot" target="_blank">
<img src="https://img.shields.io/badge/Spring Boot-3.1.11-%236CB52D.svg?logo=Spring-Boot" alt="Spring Boot" /> <img src="https://img.shields.io/badge/Spring Boot-3.2.7-%236CB52D.svg?logo=Spring-Boot" alt="Spring Boot" />
</a> </a>
<a href="https://github.com/continew-org/continew-starter" target="_blank"> <a href="https://github.com/continew-org/continew-starter" target="_blank">
<img src="https://img.shields.io/badge/Open JDK-17-%236CB52D.svg?logo=OpenJDK&logoColor=FFF" alt="Open JDK" /> <img src="https://img.shields.io/badge/Open JDK-17-%236CB52D.svg?logo=OpenJDK&logoColor=FFF" alt="Open JDK" />
@@ -60,10 +60,11 @@ ContiNew Starter 就是将脚手架项目中的通用基础配置进行了封装
## 项目源码 ## 项目源码
| 开源平台 | 源码地址 | | 开源平台 | 源码地址 |
| :------------ | :-------------------------------------------- | | :------------ | :----------------------------------------------- |
| Gitee码云 | https://gitee.com/continew/continew-starter |
| GitCode | https://gitcode.com/continew/continew-starter |
| GitHub | https://github.com/continew-org/continew-starter | | GitHub | https://github.com/continew-org/continew-starter |
| Gitee码云 | https://gitee.com/continew/continew-starter |
## 像数123一样容易 ## 像数123一样容易
@@ -140,95 +141,96 @@ continew-starter.web:
| 模块名称 | 模块说明 | 依赖版本 | | 模块名称 | 模块说明 | 依赖版本 |
| --------------------- | ------------------------------------ |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | --------------------- | ------------------------------------ |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| continew-starter-core | 核心模块:包含线程池、项目等自动配置 | <a href="https://spring.io/projects/spring-boot" target="_blank">Spring Boot</a>3.1.11<br /><a href="https://www.hutool.cn/" target="_blank">Hutool</a>5.8.25<br />mica-ip2region3.1.7 | | continew-starter-core | 核心模块:包含线程池、项目等自动配置 | <a href="https://spring.io/projects/spring-boot" target="_blank">Spring Boot</a>3.1.11<br /><a href="https://www.hutool.cn/" target="_blank">Hutool</a>5.8.29<br />mica-ip2region3.2.6 |
### JSON模块 ### JSON模块
| 模块名称 | 模块说明 | 依赖版本 | | 模块名称 | 模块说明 |
| ----------------------------- | -------------------- | --------------- | | ----------------------------- | -------------------- |
| continew-starter-json-jackson | Jackson 序列化等配置 | Jackson2.15.3 | | continew-starter-json-jackson | Jackson 序列化等配置 |
### 接口文档 ### 接口文档
| 模块名称 | 模块说明 | 依赖版本 | | 模块名称 | 模块说明 |
| ------------------------ | ---------------- | ------------------------------------------------------------ | | ------------------------ | ---------------- |
| continew-starter-api-doc | Knife4j 自动配置 | <a href="https://doc.xiaominfo.com/" target="_blank">Knife4j</a>4.5.0 | | continew-starter-api-doc | Knife4j 自动配置 |
### 安全模块 ### 安全模块
| 模块名称 | 模块说明 | 依赖版本 | | 模块名称 | 模块说明 |
| ---------------------------------- | ----------------- | -------- | | ---------------------------------- | ----------------- |
| continew-starter-security-password | 密码编码器 | | | continew-starter-security-password | 密码编码器 |
| continew-starter-security-mask | JSON 脱敏 | | | continew-starter-security-mask | JSON 脱敏 |
| continew-starter-security-crypto | 数据库字段加/解密 | | | continew-starter-security-crypto | 数据库字段加/解密 |
| continew-starter-security-limiter | 限流器 |
### Web模块 ### Web模块
| 模块名称 | 模块说明 | 依赖版本 | | 模块名称 | 模块说明 |
| -------------------- | ---------------------------------- | ------------------------------------------------------------ | | -------------------- | ---------------------------------- |
| continew-starter-web | 跨域、全局异常、错误处理等自动配置 | <a href="https://undertow.io/" target="_blank">Undertow</a>2.3.10.Final<br />TLog1.5.1 | | continew-starter-web | 跨域、全局异常、错误处理等自动配置 |
### 日志模块 ### 日志模块
| 模块名称 | 模块说明 | 依赖版本 | | 模块名称 | 模块说明 |
| ---------------------------------- | ----------------------------------------- | -------- | | ---------------------------------- | ----------------------------------------- |
| continew-starter-log-core | 日志核心模块 | | | continew-starter-log-core | 日志核心模块 |
| continew-starter-log-httptrace-pro | Spring Boot Actuator HttpTrace 重置增强版 | | | continew-starter-log-httptrace-pro | Spring Boot Actuator HttpTrace 重置增强版 |
### 存储模块 ### 存储模块
| 模块名称 | 模块说明 | 依赖版本 | | 模块名称 | 模块说明 |
| ------------------------------ | -------- | -------- | | ------------------------------ | -------- |
| continew-starter-storage-local | 本地存储 | | | continew-starter-storage-local | 本地存储 |
### 文件处理模块 ### 文件处理模块
| 模块名称 | 模块说明 | 依赖版本 | | 模块名称 | 模块说明 |
| --------------------------- | -------------- |------------------------------------------------------------------------------------------| | --------------------------- | -------------- |
| continew-starter-file-excel | Excel 相关配置 | <a href="https://easyexcel.opensource.alibaba.com/" target="_blank">Easy Excel</a>3.3.4 | | continew-starter-file-excel | Excel 相关配置 |
### 验证码模块 ### 验证码模块
| 模块名称 | 模块说明 | 依赖版本 | | 模块名称 | 模块说明 |
| --------------------------------- | ---------- | ------------------- | | --------------------------------- | ---------- |
| continew-starter-captcha-graphic | 图形验证码 | Easy Captcha1.6.2 | | continew-starter-captcha-graphic | 图形验证码 |
| continew-starter-captcha-behavior | 行为验证码 | AJ-Captcha1.3.0 | | continew-starter-captcha-behavior | 行为验证码 |
### 缓存模块 ### 缓存模块
| 模块名称 | 模块说明 | 依赖版本 | | 模块名称 | 模块说明 |
| ---------------------------------- | --------------------- |--------------------------------------------------------------------------------------------------------------------------------------| | ---------------------------------- | --------------------- |
| continew-starter-cache-redisson | Redisson 自动配置 | <a href="https://github.com/redisson/redisson/wiki/Redisson%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D" target="_blank">Redisson</a>3.30.0 | | continew-starter-cache-redisson | Redisson 自动配置 |
| continew-starter-cache-springcache | Spring Cache 自动配置 | | | continew-starter-cache-springcache | Spring Cache 自动配置 |
| continew-starter-cache-jetcache | JetCache 自动配置 | | | continew-starter-cache-jetcache | JetCache 自动配置 |
### 数据访问模块 ### 数据访问模块
| 模块名称 | 模块说明 | 依赖版本 | | 模块名称 | 模块说明 |
|------------------------------------|-------------------| ----------------------------------------------------------- | | ---------------------------------- | --------------------- |
| continew-starter-data-core | 数据访问核心模块 | | | continew-starter-data-core | 数据访问核心模块 |
| continew-starter-data-mybatis-plus | MyBatis Plus 自动配置 | <a href="https://baomidou.com/" target="_blank">MyBatis Plus</a>3.5.5<br /><a href="https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611" target="_blank">dynamic-datasource-spring-boot-starter</a>4.3.0<br /><a href="https://github.com/p6spy/p6spy" target="_blank">P6Spy</a>3.9.1 | | continew-starter-data-mybatis-plus | MyBatis Plus 自动配置 |
| continew-starter-data-mybatis-flex | MyBatis Flex 自动配置 | | | continew-starter-data-mybatis-flex | MyBatis Flex 自动配置 |
### 认证模块 ### 认证模块
| 模块名称 | 模块说明 | 依赖版本 | | 模块名称 | 模块说明 |
| ------------------------------ | ----------------- |--------------------------------------------------------------------------| | ------------------------------ | ----------------- |
| continew-starter-auth-satoken | SaToken 自动配置 | <a href="https://sa-token.dev33.cn/" target="_blank">Sa-Token</a>1.38.0 | | continew-starter-auth-satoken | SaToken 自动配置 |
| continew-starter-auth-justauth | JustAuth 自动配置 | <a href="https://justauth.cn/" target="_blank">Just Auth</a>1.16.6 | | continew-starter-auth-justauth | JustAuth 自动配置 |
### 消息模块 ### 消息模块
| 模块名称 | 模块说明 | 依赖版本 | | 模块名称 | 模块说明 |
|--------------------------------------|-----------| ------------------------------------------------------------ | | ------------------------------------ | --------- |
| continew-starter-messaging-mail | 邮件 | Jakarta Mail1.1.0 | | continew-starter-messaging-mail | 邮件 |
| continew-starter-messaging-websocket | WebSocket | | | continew-starter-messaging-websocket | WebSocket |
### 扩展模块 ### 扩展模块
| 模块名称 | 模块说明 | 依赖版本 | | 模块名称 | 模块说明 |
| ------------------------------- | --------------------------------------------- | -------- | | ------------------------------- | --------------------------------------------- |
| continew-starter-extension-crud | 扩展模块BaseController 自定义 CRUD API 封装 | | | continew-starter-extension-crud | 扩展模块BaseController 自定义 CRUD API 封装 |
## 贡献代码 ## 贡献代码

View File

@@ -30,7 +30,6 @@ import org.slf4j.LoggerFactory;
import org.springdoc.core.customizers.GlobalOpenApiCustomizer; import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource; import org.springframework.context.annotation.PropertySource;
@@ -39,7 +38,6 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import top.continew.starter.core.autoconfigure.project.ProjectProperties; import top.continew.starter.core.autoconfigure.project.ProjectProperties;
import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.core.util.GeneralPropertySourceFactory; import top.continew.starter.core.util.GeneralPropertySourceFactory;
import java.util.List; import java.util.List;
@@ -54,7 +52,6 @@ import java.util.concurrent.TimeUnit;
*/ */
@EnableWebMvc @EnableWebMvc
@AutoConfiguration @AutoConfiguration
@ConditionalOnProperty(prefix = PropertiesConstants.SPRINGDOC_SWAGGER_UI, name = PropertiesConstants.ENABLED, matchIfMissing = true)
@EnableConfigurationProperties(SpringDocExtensionProperties.class) @EnableConfigurationProperties(SpringDocExtensionProperties.class)
@PropertySource(value = "classpath:default-api-doc.yml", factory = GeneralPropertySourceFactory.class) @PropertySource(value = "classpath:default-api-doc.yml", factory = GeneralPropertySourceFactory.class)
public class SpringDocAutoConfiguration implements WebMvcConfigurer { public class SpringDocAutoConfiguration implements WebMvcConfigurer {

View File

@@ -19,7 +19,6 @@ package top.continew.starter.apidoc.autoconfigure;
import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.Components;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.boot.context.properties.NestedConfigurationProperty;
import top.continew.starter.core.constant.PropertiesConstants;
/** /**
* API 文档扩展配置属性 * API 文档扩展配置属性
@@ -27,7 +26,7 @@ import top.continew.starter.core.constant.PropertiesConstants;
* @author Charles7c * @author Charles7c
* @since 1.0.1 * @since 1.0.1
*/ */
@ConfigurationProperties(prefix = PropertiesConstants.SPRINGDOC) @ConfigurationProperties("springdoc")
public class SpringDocExtensionProperties { public class SpringDocExtensionProperties {
/** /**

View File

@@ -1,6 +1,7 @@
--- ### 接口文档配置 --- ### 接口文档配置
springdoc: springdoc:
swagger-ui: swagger-ui:
enabled: true
path: /swagger-ui.html path: /swagger-ui.html
tags-sorter: alpha tags-sorter: alpha
operations-sorter: alpha operations-sorter: alpha

View File

@@ -26,7 +26,7 @@ import top.continew.starter.auth.satoken.autoconfigure.dao.SaTokenDaoProperties;
* @author Charles7c * @author Charles7c
* @since 1.0.0 * @since 1.0.0
*/ */
@ConfigurationProperties(prefix = "sa-token.extension") @ConfigurationProperties("sa-token.extension")
public class SaTokenExtensionProperties { public class SaTokenExtensionProperties {
/** /**

View File

@@ -28,7 +28,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Charles7c * @author Charles7c
* @since 1.0.0 * @since 1.0.0
*/ */
@ConfigurationProperties(prefix = "spring.data.redisson") @ConfigurationProperties("spring.data.redisson")
public class RedissonProperties { public class RedissonProperties {
/** /**

View File

@@ -16,6 +16,7 @@
package top.continew.starter.cache.redisson.util; package top.continew.starter.cache.redisson.util;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
import org.redisson.api.*; import org.redisson.api.*;
import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.constant.StringConstants;
@@ -213,6 +214,6 @@ public class RedisUtils {
* @return 键 * @return 键
*/ */
public static String formatKey(String... subKeys) { public static String formatKey(String... subKeys) {
return String.join(StringConstants.COLON, subKeys); return String.join(StringConstants.COLON, ArrayUtil.removeBlank(subKeys));
} }
} }

View File

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

View File

@@ -36,7 +36,7 @@ public class BehaviorCaptchaProperties {
/** /**
* 是否启用行为验证码 * 是否启用行为验证码
*/ */
private boolean enabled = false; private boolean enabled = true;
/** /**
* 是否开启 AES 坐标加密默认true * 是否开启 AES 坐标加密默认true

View File

@@ -23,6 +23,13 @@
<artifactId>spring-boot-configuration-processor</artifactId> <artifactId>spring-boot-configuration-processor</artifactId>
</dependency> </dependency>
<!-- Hibernate Validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<optional>true</optional>
</dependency>
<!-- 第三方封装 Ip2region离线 IP 数据管理框架和定位库支持亿级别的数据段10 微秒级别的查询性能,提供了许多主流编程语言的 xdb 数据管理引擎的实现) --> <!-- 第三方封装 Ip2region离线 IP 数据管理框架和定位库支持亿级别的数据段10 微秒级别的查询性能,提供了许多主流编程语言的 xdb 数据管理引擎的实现) -->
<dependency> <dependency>
<groupId>net.dreamlu</groupId> <groupId>net.dreamlu</groupId>

View File

@@ -0,0 +1,67 @@
/*
* 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.autoconfigure;
import jakarta.annotation.PostConstruct;
import jakarta.validation.Validator;
import org.hibernate.validator.HibernateValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import java.util.Properties;
/**
* JSR 303 校验器自动配置
*
* @author Charles7c
* @since 2.3.0
*/
@AutoConfiguration
public class ValidatorAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(ValidatorAutoConfiguration.class);
/**
* Validator 失败立即返回模式配置
*
* <p>
* 默认情况下会校验完所有字段,然后才抛出异常。
* </p>
*/
@Bean
public Validator validator(MessageSource messageSource) {
try (LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean()) {
// 国际化
factoryBean.setValidationMessageSource(messageSource);
factoryBean.setProviderClass(HibernateValidator.class);
Properties properties = new Properties();
properties.setProperty("hibernate.validator.fail_fast", "true");
factoryBean.setValidationProperties(properties);
factoryBean.afterPropertiesSet();
return factoryBean.getValidator();
}
}
@PostConstruct
public void postConstruct() {
log.debug("[ContiNew Starter] - Auto Configuration 'Validator' completed initialization.");
}
}

View File

@@ -24,7 +24,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Charles7c * @author Charles7c
* @since 1.0.0 * @since 1.0.0
*/ */
@ConfigurationProperties(prefix = "project") @ConfigurationProperties("project")
public class ProjectProperties { public class ProjectProperties {
/** /**

View File

@@ -17,6 +17,7 @@
package top.continew.starter.core.autoconfigure.threadpool; package top.continew.starter.core.autoconfigure.threadpool;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
@@ -25,40 +26,39 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import top.continew.starter.core.constant.PropertiesConstants; import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.core.exception.BaseException; import top.continew.starter.core.exception.BaseException;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
/** /**
* 异步任务自动配置 * 异步任务自动配置
* *
* @author Charles7c * @author Charles7c
* @author Lion Li<a href="https://gitee.com/dromara/RuoYi-Vue-Plus">RuoYi-Vue-Plus</a>
* @since 1.0.0 * @since 1.0.0
*/ */
@Lazy @Lazy
@AutoConfiguration @AutoConfiguration
@EnableAsync(proxyTargetClass = true) @EnableAsync(proxyTargetClass = true)
@ConditionalOnProperty(prefix = PropertiesConstants.THREAD_POOL, name = PropertiesConstants.ENABLED, havingValue = "true") @ConditionalOnProperty(prefix = "spring.task.execution.extension", name = PropertiesConstants.ENABLED, matchIfMissing = true)
public class AsyncAutoConfiguration implements AsyncConfigurer { public class AsyncAutoConfiguration implements AsyncConfigurer {
private static final Logger log = LoggerFactory.getLogger(AsyncAutoConfiguration.class); private static final Logger log = LoggerFactory.getLogger(AsyncAutoConfiguration.class);
private final ScheduledExecutorService scheduledExecutorService; private final ThreadPoolTaskExecutor threadPoolTaskExecutor;
public AsyncAutoConfiguration(ScheduledExecutorService scheduledExecutorService) { public AsyncAutoConfiguration(ThreadPoolTaskExecutor threadPoolTaskExecutor) {
this.scheduledExecutorService = scheduledExecutorService; this.threadPoolTaskExecutor = threadPoolTaskExecutor;
} }
/** /**
* 异步任务 @Async 执行时,使用 Java 内置线程池 * 异步任务线程池配置
*/ */
@Override @Override
public Executor getAsyncExecutor() { public Executor getAsyncExecutor() {
log.debug("[ContiNew Starter] - Auto Configuration 'AsyncConfigurer' completed initialization."); return threadPoolTaskExecutor;
return scheduledExecutorService;
} }
/** /**
@@ -79,4 +79,9 @@ public class AsyncAutoConfiguration implements AsyncConfigurer {
throw new BaseException(sb.toString()); throw new BaseException(sb.toString());
}; };
} }
@PostConstruct
public void postConstruct() {
log.debug("[ContiNew Starter] - Auto Configuration 'AsyncConfigurer' completed initialization.");
}
} }

View File

@@ -16,148 +16,71 @@
package top.continew.starter.core.autoconfigure.threadpool; package top.continew.starter.core.autoconfigure.threadpool;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ObjectUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.task.ThreadPoolTaskExecutorCustomizer;
import org.springframework.boot.task.ThreadPoolTaskSchedulerCustomizer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.annotation.EnableScheduling;
import top.continew.starter.core.constant.PropertiesConstants; import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.core.util.ExceptionUtils;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/** /**
* 线程池自动配置 * 线程池自动配置
* *
* @author Charles7c * @author Charles7c
* @author Lion Li<a href="https://gitee.com/dromara/RuoYi-Vue-Plus">RuoYi-Vue-Plus</a>
* @since 1.0.0 * @since 1.0.0
*/ */
@Lazy @Lazy
@AutoConfiguration @AutoConfiguration
@ConditionalOnProperty(prefix = PropertiesConstants.THREAD_POOL, name = PropertiesConstants.ENABLED, havingValue = "true") @EnableConfigurationProperties(ThreadPoolExtensionProperties.class)
@EnableConfigurationProperties(ThreadPoolProperties.class)
public class ThreadPoolAutoConfiguration { public class ThreadPoolAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(ThreadPoolAutoConfiguration.class); private static final Logger log = LoggerFactory.getLogger(ThreadPoolAutoConfiguration.class);
/** @Value("${spring.task.execution.pool.core-size:#{T(java.lang.Runtime).getRuntime().availableProcessors() + 1}}")
* 核心(最小)线程数 = CPU 核心数 + 1 private int corePoolSize;
*/
private final int corePoolSize = Runtime.getRuntime().availableProcessors() + 1; @Value("${spring.task.execution.pool.max-size:#{T(java.lang.Runtime).getRuntime().availableProcessors() * 2}}")
private int maxPoolSize;
/** /**
* Spring 内置线程池ThreadPoolTaskExecutor * 异步任务线程池配置
*/ */
@Bean @Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor(ThreadPoolProperties properties) { @ConditionalOnProperty(prefix = "spring.task.execution.extension", name = PropertiesConstants.ENABLED, matchIfMissing = true)
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); public ThreadPoolTaskExecutorCustomizer threadPoolTaskExecutorCustomizer(ThreadPoolExtensionProperties properties) {
executor.setThreadNamePrefix("thread-pool"); return executor -> {
// 核心(最小)线程数 // 核心(最小)线程数
executor.setCorePoolSize(ObjectUtil.defaultIfNull(properties.getCorePoolSize(), corePoolSize)); executor.setCorePoolSize(corePoolSize);
// 最大线程数 // 最大线程数
executor.setMaxPoolSize(ObjectUtil.defaultIfNull(properties.getMaxPoolSize(), corePoolSize * 2)); executor.setMaxPoolSize(maxPoolSize);
// 队列容量 // 当线程池的任务缓存队列已满并且线程池中的线程数已达到 maxPoolSize 时采取的任务拒绝策略
executor.setQueueCapacity(properties.getQueueCapacity()); executor.setRejectedExecutionHandler(properties.getExecution()
// 活跃时间 .getRejectedPolicy()
executor.setKeepAliveSeconds(properties.getKeepAliveSeconds()); .getRejectedExecutionHandler());
// 配置当池内线程数已达到上限的时候,该如何处理新任务:不在新线程中执行任务,而是由调用者所在的线程来执行 log.debug("[ContiNew Starter] - Auto Configuration 'TaskExecutor' completed initialization.");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 关闭线程池是否等待任务完成
executor.setWaitForTasksToCompleteOnShutdown(properties.isWaitForTasksToCompleteOnShutdown());
// 执行器在关闭时阻塞的最长毫秒数,以等待剩余任务完成执行
executor.setAwaitTerminationMillis(properties.getAwaitTerminationMillis());
log.debug("[ContiNew Starter] - Auto Configuration 'ThreadPoolTaskExecutor' completed initialization.");
return executor;
}
/**
* Java 内置线程池ScheduledExecutorService适用于执行周期性或定时任务
*/
@Bean
@ConditionalOnMissingBean
public ScheduledExecutorService scheduledExecutorService(ThreadPoolProperties properties) {
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(ObjectUtil.defaultIfNull(properties
.getCorePoolSize(), corePoolSize), ThreadUtil
.newNamedThreadFactory("schedule-pool-%d", true), new ThreadPoolExecutor.CallerRunsPolicy()) {
@Override
protected void afterExecute(Runnable runnable, Throwable throwable) {
super.afterExecute(runnable, throwable);
ExceptionUtils.printException(runnable, throwable);
}
}; };
// 应用关闭时,关闭线程池
SpringApplication.getShutdownHandlers().add(() -> this.shutdown(executor, properties));
log.debug("[ContiNew Starter] - Auto Configuration 'ScheduledExecutorService' completed initialization.");
return executor;
} }
/** /**
* 根据相应的配置设置关闭 ExecutorService * 定时任务线程池配置
*
* @see org.springframework.scheduling.concurrent.ExecutorConfigurationSupport#shutdown()
* @since 2.0.0
*/ */
public void shutdown(ExecutorService executor, ThreadPoolProperties properties) { @EnableScheduling
log.debug("[ContiNew Starter] - Shutting down ScheduledExecutorService start."); @ConditionalOnProperty(prefix = "spring.task.scheduling.extension", name = PropertiesConstants.ENABLED, matchIfMissing = true)
if (executor != null) { public static class TaskSchedulerConfiguration {
if (properties.isWaitForTasksToCompleteOnShutdown()) { @Bean
executor.shutdown(); public ThreadPoolTaskSchedulerCustomizer threadPoolTaskSchedulerCustomizer(ThreadPoolExtensionProperties properties) {
} else { return executor -> {
for (Runnable remainingTask : executor.shutdownNow()) { executor.setRejectedExecutionHandler(properties.getScheduling()
cancelRemainingTask(remainingTask); .getRejectedPolicy()
} .getRejectedExecutionHandler());
} log.debug("[ContiNew Starter] - Auto Configuration 'TaskScheduler' completed initialization.");
awaitTerminationIfNecessary(executor, properties); };
log.debug("[ContiNew Starter] - Shutting down ScheduledExecutorService complete.");
}
}
/**
* Cancel the given remaining task which never commenced execution,
* as returned from {@link ExecutorService#shutdownNow()}.
*
* @param task the task to cancel (typically a {@link RunnableFuture})
* @see RunnableFuture#cancel(boolean)
* @since 2.0.0
*/
protected void cancelRemainingTask(Runnable task) {
if (task instanceof Future<?> future) {
future.cancel(true);
}
}
/**
* Wait for the executor to terminate, according to the value of the properties
*
* @since 2.0.0
*/
private void awaitTerminationIfNecessary(ExecutorService executor, ThreadPoolProperties properties) {
if (properties.getAwaitTerminationMillis() > 0) {
try {
if (!executor.awaitTermination(properties.getAwaitTerminationMillis(), TimeUnit.MILLISECONDS)) {
if (log.isWarnEnabled()) {
log.warn("[ContiNew Starter] - Timed out while waiting for executor 'ScheduledExecutorService' to terminate.");
}
}
} catch (InterruptedException ex) {
if (log.isWarnEnabled()) {
log.warn("[ContiNew Starter] - Interrupted while waiting for executor 'ScheduledExecutorService' to terminate");
}
Thread.currentThread().interrupt();
}
} }
} }
} }

View File

@@ -0,0 +1,76 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.core.autoconfigure.threadpool;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 线程池拒绝策略
*
* @author Charles7c
* @since 2.2.0
*/
public enum ThreadPoolExecutorRejectedPolicy {
/**
* ThreadPoolTaskExecutor 默认的拒绝策略,不执行新任务,直接抛出 RejectedExecutionException 异常
*/
ABORT {
@Override
public RejectedExecutionHandler getRejectedExecutionHandler() {
return new ThreadPoolExecutor.AbortPolicy();
}
},
/**
* 提交的任务在执行被拒绝时,会由提交任务的线程去执行
*/
CALLER_RUNS {
@Override
public RejectedExecutionHandler getRejectedExecutionHandler() {
return new ThreadPoolExecutor.CallerRunsPolicy();
}
},
/**
* 不执行新任务,也不抛出异常
*/
DISCARD {
@Override
public RejectedExecutionHandler getRejectedExecutionHandler() {
return new ThreadPoolExecutor.DiscardPolicy();
}
},
/**
* 拒绝新任务,但是会抛弃队列中最老的任务,然后尝试再次提交新任务
*/
DISCARD_OLDEST {
@Override
public RejectedExecutionHandler getRejectedExecutionHandler() {
return new ThreadPoolExecutor.DiscardOldestPolicy();
}
};
/**
* 获取拒绝处理器
*
* @return 拒绝处理器
*/
public abstract RejectedExecutionHandler getRejectedExecutionHandler();
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.core.autoconfigure.threadpool;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 线程池扩展配置属性
*
* @author Charles7c
* @since 1.0.0
*/
@ConfigurationProperties("spring.task")
public class ThreadPoolExtensionProperties {
/**
* 异步任务扩展配置属性
*/
private ExecutorExtensionProperties execution = new ExecutorExtensionProperties();
/**
* 调度任务扩展配置属性
*/
private SchedulerExtensionProperties scheduling = new SchedulerExtensionProperties();
/**
* 异步任务扩展配置属性
*/
public static class ExecutorExtensionProperties {
/**
* 拒绝策略
*/
private ThreadPoolExecutorRejectedPolicy rejectedPolicy = ThreadPoolExecutorRejectedPolicy.CALLER_RUNS;
public ThreadPoolExecutorRejectedPolicy getRejectedPolicy() {
return rejectedPolicy;
}
public void setRejectedPolicy(ThreadPoolExecutorRejectedPolicy rejectedPolicy) {
this.rejectedPolicy = rejectedPolicy;
}
}
/**
* 调度任务扩展配置属性
*/
public static class SchedulerExtensionProperties {
/**
* 拒绝策略
*/
private ThreadPoolExecutorRejectedPolicy rejectedPolicy = ThreadPoolExecutorRejectedPolicy.CALLER_RUNS;
public ThreadPoolExecutorRejectedPolicy getRejectedPolicy() {
return rejectedPolicy;
}
public void setRejectedPolicy(ThreadPoolExecutorRejectedPolicy rejectedPolicy) {
this.rejectedPolicy = rejectedPolicy;
}
}
public ExecutorExtensionProperties getExecution() {
return execution;
}
public void setExecution(ExecutorExtensionProperties execution) {
this.execution = execution;
}
public SchedulerExtensionProperties getScheduling() {
return scheduling;
}
public void setScheduling(SchedulerExtensionProperties scheduling) {
this.scheduling = scheduling;
}
}

View File

@@ -1,122 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.core.autoconfigure.threadpool;
import org.springframework.boot.context.properties.ConfigurationProperties;
import top.continew.starter.core.constant.PropertiesConstants;
/**
* 线程池配置属性
*
* @author Charles7c
* @author Lion Li<a href="https://gitee.com/dromara/RuoYi-Vue-Plus">RuoYi-Vue-Plus</a>
* @since 1.0.0
*/
@ConfigurationProperties(PropertiesConstants.THREAD_POOL)
public class ThreadPoolProperties {
/**
* 是否启用线程池配置
*/
private boolean enabled = false;
/**
* 核心/最小线程数默认CPU 核心数 + 1
*/
private Integer corePoolSize;
/**
* 最大线程数(默认:(CPU 核心数 + 1) * 2
*/
private Integer maxPoolSize;
/**
* 队列容量
*/
private int queueCapacity = 128;
/**
* 活跃时间(单位:秒)
*/
private int keepAliveSeconds = 300;
/**
* 关闭线程池是否等待任务完成
*/
private boolean waitForTasksToCompleteOnShutdown = false;
/**
* 执行器在关闭时阻塞的最长毫秒数,以等待剩余任务完成执行
*/
private long awaitTerminationMillis = 0;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public Integer getCorePoolSize() {
return corePoolSize;
}
public void setCorePoolSize(Integer corePoolSize) {
this.corePoolSize = corePoolSize;
}
public Integer getMaxPoolSize() {
return maxPoolSize;
}
public void setMaxPoolSize(Integer maxPoolSize) {
this.maxPoolSize = maxPoolSize;
}
public int getQueueCapacity() {
return queueCapacity;
}
public void setQueueCapacity(int queueCapacity) {
this.queueCapacity = queueCapacity;
}
public int getKeepAliveSeconds() {
return keepAliveSeconds;
}
public void setKeepAliveSeconds(int keepAliveSeconds) {
this.keepAliveSeconds = keepAliveSeconds;
}
public boolean isWaitForTasksToCompleteOnShutdown() {
return waitForTasksToCompleteOnShutdown;
}
public void setWaitForTasksToCompleteOnShutdown(boolean waitForTasksToCompleteOnShutdown) {
this.waitForTasksToCompleteOnShutdown = waitForTasksToCompleteOnShutdown;
}
public long getAwaitTerminationMillis() {
return awaitTerminationMillis;
}
public void setAwaitTerminationMillis(long awaitTerminationMillis) {
this.awaitTerminationMillis = awaitTerminationMillis;
}
}

View File

@@ -34,21 +34,6 @@ public class PropertiesConstants {
*/ */
public static final String ENABLED = "enabled"; public static final String ENABLED = "enabled";
/**
* 线程池配置
*/
public static final String THREAD_POOL = CONTINEW_STARTER + StringConstants.DOT + "thread-pool";
/**
* Spring Doc 配置
*/
public static final String SPRINGDOC = "springdoc";
/**
* Spring Doc Swagger UI 配置
*/
public static final String SPRINGDOC_SWAGGER_UI = SPRINGDOC + StringConstants.DOT + "swagger-ui";
/** /**
* 安全配置 * 安全配置
*/ */
@@ -57,12 +42,17 @@ public class PropertiesConstants {
/** /**
* 密码编解码配置 * 密码编解码配置
*/ */
public static final String PASSWORD = SECURITY + StringConstants.DOT + "password"; public static final String SECURITY_PASSWORD = SECURITY + StringConstants.DOT + "password";
/** /**
* 加/解密配置 * 加/解密配置
*/ */
public static final String CRYPTO = SECURITY + StringConstants.DOT + "crypto"; public static final String SECURITY_CRYPTO = SECURITY + StringConstants.DOT + "crypto";
/**
* 限流器配置
*/
public static final String SECURITY_LIMITER = SECURITY + StringConstants.DOT + "limiter";
/** /**
* Web 配置 * Web 配置
@@ -72,17 +62,22 @@ public class PropertiesConstants {
/** /**
* 跨域配置 * 跨域配置
*/ */
public static final String CORS = WEB + StringConstants.DOT + "cors"; public static final String WEB_CORS = WEB + StringConstants.DOT + "cors";
/** /**
* 链路配置 * 链路配置
*/ */
public static final String TRACE = WEB + StringConstants.DOT + "trace"; public static final String WEB_TRACE = WEB + StringConstants.DOT + "trace";
/** /**
* XSS 配置 * XSS 配置
*/ */
public static final String XSS = WEB + StringConstants.DOT + "xss"; public static final String WEB_XSS = WEB + StringConstants.DOT + "xss";
/**
* 国际化配置
*/
public static final String WEB_I18N = WEB + StringConstants.DOT + "i18n";
/** /**
* 日志配置 * 日志配置

View File

@@ -262,6 +262,16 @@ public class StringConstants {
*/ */
public static final String CHINESE_COMMA = ""; public static final String CHINESE_COMMA = "";
/**
* 圆括号(左) {@code "("}
*/
public static final String ROUND_BRACKET_START = "(";
/**
* 圆括号(右) {@code ")"}
*/
public static final String ROUND_BRACKET_END = ")";
/** /**
* 路径模式 * 路径模式
*/ */

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.core.exception;
/**
* 统一错误码异常
*
* @author Jasmine
* @since 2.2.0
*/
public class GlobalException extends Exception {
private ResultInfoInterface resultInfo;
public GlobalException() {
}
public GlobalException(ResultInfoInterface resultInfo) {
this.resultInfo = resultInfo;
}
public ResultInfoInterface getResultInfo() {
return this.resultInfo;
}
public void setResultInfo(ResultInfoInterface resultInfo) {
this.resultInfo = resultInfo;
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.core.exception;
/**
* 接口返回码 所有业务异常都要继承该接口
*
* @author Jasmine
* @since 2.2.0
*/
public enum GlobalResultInfoEnum implements ResultInfoInterface {
/**
* 操作成功
*/
SUCCESS(200, "操作成功"),
/**
* 操作失败
*/
FAILED(500, "操作失败");
private int code;
private String messageKey;
private String defaultMessage;
GlobalResultInfoEnum(int code, String defaultMessage) {
this.code = code;
this.defaultMessage = defaultMessage;
}
GlobalResultInfoEnum(int code, String messageKey, String defaultMessage) {
this.code = code;
this.messageKey = messageKey;
this.defaultMessage = defaultMessage;
}
@Override
public int getCode() {
return this.code;
}
@Override
public String getMessageKey() {
return this.messageKey;
}
@Override
public String getDefaultMessage() {
return this.defaultMessage;
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.core.exception;
/**
* 接口返回码与消息 所有业务异常都要继承该接口
*
* @author Jasmine
* @since 2.2.0
*/
public interface ResultInfoInterface {
/**
* 获取编码
*
* @return String
*/
int getCode();
/**
* 国际化消息key
*
* @return
*/
default String getMessageKey() {
return "";
}
/**
* 获取默认消息 若从国际化文件里没有获取到值,就取默认值
*
* @return String
*/
String getDefaultMessage();
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.core.util;
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
/**
* 国际化工具类
*
* @author Jasmine
* @since 2.2.0
*/
public class MessageSourceUtils {
private static final MessageSource MESSAGE_SOURCE = SpringUtil.getBean(MessageSource.class);
private static final Object[] EMPTY_ARGS = {};
private MessageSourceUtils() {
}
/**
* 根据消息编码获取
*
* @param code 消息编码
* @return 国际化后的消息
*/
public static String getMessage(String code) {
return getMessage(code, EMPTY_ARGS);
}
/**
* 根据消息编码获取
*
* @param code 消息编码
* @param args 参数
* @return 国际化后的消息
*/
public static String getMessage(String code, Object... args) {
return getMessage(code, code, args);
}
/**
* 根据消息编码获取
*
* @param code 消息编码
* @param defaultMessage 默认消息
* @return 国际化后的消息
*/
public static String getMessage(String code, String defaultMessage) {
return getMessage(code, defaultMessage, EMPTY_ARGS);
}
/**
* 根据消息编码获取
*
* @param code 消息编码
* @param defaultMessage 默认消息
* @param args 参数
* @return 国际化后的消息
*/
public static String getMessage(String code, String defaultMessage, Object... args) {
try {
return MESSAGE_SOURCE.getMessage(code, args, LocaleContextHolder.getLocale());
} catch (Exception e) {
return defaultMessage;
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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.util.expression;
import java.lang.reflect.Method;
import java.util.function.Function;
/**
* 表达式解析器
*
* @author Charles7c
* @since 2.2.0
*/
public class ExpressionEvaluator implements Function<Object, Object> {
private final Function<Object, Object> evaluator;
public ExpressionEvaluator(String script, Method defineMethod) {
this.evaluator = new SpelEvaluator(script, defineMethod);
}
@Override
public Object apply(Object rootObject) {
return evaluator.apply(rootObject);
}
Function<Object, Object> getEvaluator() {
return evaluator;
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.util.expression;
import java.lang.reflect.Method;
/**
* 表达式上下文
*
* @author Charles7c
* @since 2.2.0
*/
public class ExpressionInvokeContext {
/**
* 目标方法
*/
private Method method;
/**
* 方法参数
*/
private Object[] args;
/**
* 目标对象
*/
private Object target;
public ExpressionInvokeContext(Method method, Object[] args, Object target) {
this.method = method;
this.args = args;
this.target = target;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public void setArgs(Object[] args) {
this.args = args;
}
public Object[] getArgs() {
return args;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.core.util.expression;
import cn.hutool.core.text.CharSequenceUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
/**
* 表达式解析工具类
*
* @author Charles7c
* @since 2.2.0
*/
public class ExpressionUtils {
private static final Logger log = LoggerFactory.getLogger(ExpressionUtils.class);
private ExpressionUtils() {
}
/**
* 解析
*
* @param script 表达式
* @param target 目标对象
* @param method 目标方法
* @param args 方法参数
* @return 解析结果
*/
public static Object eval(String script, Object target, Method method, Object... args) {
try {
if (CharSequenceUtil.isBlank(script)) {
return null;
}
ExpressionEvaluator expressionEvaluator = new ExpressionEvaluator(script, method);
ExpressionInvokeContext invokeContext = new ExpressionInvokeContext(method, args, target);
return expressionEvaluator.apply(invokeContext);
} catch (Exception e) {
log.error("Error occurs when eval script \"{}\" in {} : {}", script, method, e.getMessage(), e);
return null;
}
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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.util.expression;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.lang.reflect.Method;
import java.util.function.Function;
/**
* Spring EL 表达式解析器
*
* @author Charles7c
* @since 2.2.0
*/
public class SpelEvaluator implements Function<Object, Object> {
private static final ExpressionParser PARSER;
private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER;
static {
PARSER = new SpelExpressionParser();
PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
}
private final Expression expression;
private String[] parameterNames;
public SpelEvaluator(String script, Method defineMethod) {
expression = PARSER.parseExpression(script);
if (defineMethod.getParameterCount() > 0) {
parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(defineMethod);
}
}
@Override
public Object apply(Object rootObject) {
EvaluationContext context = new StandardEvaluationContext(rootObject);
ExpressionInvokeContext invokeContext = (ExpressionInvokeContext)rootObject;
if (parameterNames != null) {
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i], invokeContext.getArgs()[i]);
}
}
return expression.getValue(context);
}
}

View File

@@ -19,9 +19,13 @@ package top.continew.starter.core.util.validate;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.spring.SpringUtil;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.Set;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
/** /**
@@ -32,6 +36,8 @@ import java.util.function.BooleanSupplier;
*/ */
public class Validator { public class Validator {
private static final Logger log = LoggerFactory.getLogger(Validator.class); private static final Logger log = LoggerFactory.getLogger(Validator.class);
private static final jakarta.validation.Validator VALIDATOR = SpringUtil
.getBean(jakarta.validation.Validator.class);
protected Validator() { protected Validator() {
} }
@@ -195,4 +201,18 @@ public class Validator {
throw ReflectUtil.newInstance(exceptionType, message); throw ReflectUtil.newInstance(exceptionType, message);
} }
} }
/**
* JSR 303 校验
*
* @param obj 被校验对象
* @param groups 分组
* @since 2.3.0
*/
public static void validate(Object obj, Class<?>... groups) {
Set<ConstraintViolation<Object>> violations = VALIDATOR.validate(obj, groups);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}
} }

View File

@@ -1,3 +1,4 @@
top.continew.starter.core.autoconfigure.project.ProjectAutoConfiguration top.continew.starter.core.autoconfigure.project.ProjectAutoConfiguration
top.continew.starter.core.autoconfigure.ValidatorAutoConfiguration
top.continew.starter.core.autoconfigure.threadpool.ThreadPoolAutoConfiguration top.continew.starter.core.autoconfigure.threadpool.ThreadPoolAutoConfiguration
top.continew.starter.core.autoconfigure.threadpool.AsyncAutoConfiguration top.continew.starter.core.autoconfigure.threadpool.AsyncAutoConfiguration

View File

@@ -25,7 +25,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Charles7c * @author Charles7c
* @since 1.0.0 * @since 1.0.0
*/ */
@ConfigurationProperties(prefix = "mybatis-flex.extension") @ConfigurationProperties("mybatis-flex.extension")
public class MyBatisFlexExtensionProperties { public class MyBatisFlexExtensionProperties {
/** /**

View File

@@ -27,7 +27,7 @@ import top.continew.starter.data.mybatis.plus.autoconfigure.idgenerator.MyBatisP
* @author Charles7c * @author Charles7c
* @since 1.0.0 * @since 1.0.0
*/ */
@ConfigurationProperties(prefix = "mybatis-plus.extension") @ConfigurationProperties("mybatis-plus.extension")
public class MyBatisPlusExtensionProperties { public class MyBatisPlusExtensionProperties {
/** /**

View File

@@ -30,9 +30,7 @@ import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression; import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
import net.sf.jsqlparser.statement.select.SubSelect;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.constant.StringConstants;
@@ -124,9 +122,9 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
private Expression buildDeptAndChildExpression(DataPermission dataPermission, private Expression buildDeptAndChildExpression(DataPermission dataPermission,
DataPermissionCurrentUser currentUser, DataPermissionCurrentUser currentUser,
Expression expression) { Expression expression) {
SubSelect subSelect = new SubSelect(); ParenthesedSelect subSelect = new ParenthesedSelect();
PlainSelect select = new PlainSelect(); PlainSelect select = new PlainSelect();
select.setSelectItems(Collections.singletonList(new SelectExpressionItem(new Column(dataPermission.id())))); select.setSelectItems(Collections.singletonList(new SelectItem<>(new Column(dataPermission.id()))));
select.setFromItem(new Table(dataPermission.deptTableAlias())); select.setFromItem(new Table(dataPermission.deptTableAlias()));
EqualsTo equalsTo = new EqualsTo(); EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(new Column(dataPermission.id())); equalsTo.setLeftExpression(new Column(dataPermission.id()));
@@ -135,7 +133,7 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
function.setName("find_in_set"); function.setName("find_in_set");
function.setParameters(new ExpressionList(new LongValue(currentUser.getDeptId()), new Column("ancestors"))); function.setParameters(new ExpressionList(new LongValue(currentUser.getDeptId()), new Column("ancestors")));
select.setWhere(new OrExpression(equalsTo, function)); select.setWhere(new OrExpression(equalsTo, function));
subSelect.setSelectBody(select); subSelect.setSelect(select);
// 构建父查询 // 构建父查询
InExpression inExpression = new InExpression(); InExpression inExpression = new InExpression();
inExpression.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.deptId())); inExpression.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.deptId()));
@@ -201,15 +199,15 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
private Expression buildCustomExpression(DataPermission dataPermission, private Expression buildCustomExpression(DataPermission dataPermission,
DataPermissionCurrentUser.CurrentUserRole role, DataPermissionCurrentUser.CurrentUserRole role,
Expression expression) { Expression expression) {
SubSelect subSelect = new SubSelect(); ParenthesedSelect subSelect = new ParenthesedSelect();
PlainSelect select = new PlainSelect(); PlainSelect select = new PlainSelect();
select.setSelectItems(Collections.singletonList(new SelectExpressionItem(new Column(dataPermission.deptId())))); select.setSelectItems(Collections.singletonList(new SelectItem<>(new Column(dataPermission.deptId()))));
select.setFromItem(new Table(dataPermission.roleDeptTableAlias())); select.setFromItem(new Table(dataPermission.roleDeptTableAlias()));
EqualsTo equalsTo = new EqualsTo(); EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(new Column(dataPermission.roleId())); equalsTo.setLeftExpression(new Column(dataPermission.roleId()));
equalsTo.setRightExpression(new LongValue(role.getRoleId())); equalsTo.setRightExpression(new LongValue(role.getRoleId()));
select.setWhere(equalsTo); select.setWhere(equalsTo);
subSelect.setSelectBody(select); subSelect.setSelect(select);
// 构建父查询 // 构建父查询
InExpression inExpression = new InExpression(); InExpression inExpression = new InExpression();
inExpression.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.deptId())); inExpression.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.deptId()));

View File

@@ -36,13 +36,25 @@ import java.util.List;
*/ */
public class ServiceImpl<M extends BaseMapper<T>, T> extends com.baomidou.mybatisplus.extension.service.impl.ServiceImpl<M, T> implements IService<T> { public class ServiceImpl<M extends BaseMapper<T>, T> extends com.baomidou.mybatisplus.extension.service.impl.ServiceImpl<M, T> implements IService<T> {
protected final List<Field> entityFields = ReflectUtils.getNonStaticFields(this.entityClass); private List<Field> entityFields;
@Override @Override
public T getById(Serializable id) { public T getById(Serializable id) {
return this.getById(id, true); return this.getById(id, true);
} }
/**
* 获取当前实体类型字段
*
* @return 当前实体类型字段列表
*/
public List<Field> getEntityFields() {
if (this.entityFields == null) {
this.entityFields = ReflectUtils.getNonStaticFields(this.getEntityClass());
}
return this.entityFields;
}
/** /**
* 根据 ID 查询 * 根据 ID 查询
* *
@@ -53,7 +65,7 @@ public class ServiceImpl<M extends BaseMapper<T>, T> extends com.baomidou.mybati
protected T getById(Serializable id, boolean isCheckExists) { protected T getById(Serializable id, boolean isCheckExists) {
T entity = baseMapper.selectById(id); T entity = baseMapper.selectById(id);
if (isCheckExists) { if (isCheckExists) {
CheckUtils.throwIfNotExists(entity, ClassUtil.getClassName(entityClass, true), "ID", id); CheckUtils.throwIfNotExists(entity, ClassUtil.getClassName(this.getEntityClass(), true), "ID", id);
} }
return entity; return entity;
} }

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId> <artifactId>spring-boot-dependencies</artifactId>
<version>3.1.11</version> <version>3.2.7</version>
<relativePath/> <relativePath/>
</parent> </parent>
@@ -43,46 +43,64 @@
<properties> <properties>
<!-- 项目版本号 --> <!-- 项目版本号 -->
<revision>2.1.1</revision> <revision>2.3.0</revision>
<snail-job.version>1.1.0</snail-job.version>
<sa-token.version>1.38.0</sa-token.version> <sa-token.version>1.38.0</sa-token.version>
<just-auth.version>1.16.6</just-auth.version> <just-auth.version>1.16.6</just-auth.version>
<mybatis-plus.version>3.5.5</mybatis-plus.version> <mybatis-plus.version>3.5.7</mybatis-plus.version>
<mybatis-flex.version>1.8.9</mybatis-flex.version> <mybatis-flex.version>1.9.3</mybatis-flex.version>
<dynamic-datasource.version>4.3.0</dynamic-datasource.version> <dynamic-datasource.version>4.3.1</dynamic-datasource.version>
<p6spy.version>3.9.1</p6spy.version> <p6spy.version>3.9.1</p6spy.version>
<jetcache.version>2.7.5</jetcache.version> <jetcache.version>2.7.6</jetcache.version>
<redisson.version>3.30.0</redisson.version> <redisson.version>3.32.0</redisson.version>
<cosid.version>2.6.8</cosid.version> <cosid.version>2.9.1</cosid.version>
<sms4j.version>3.2.1</sms4j.version> <sms4j.version>3.2.1</sms4j.version>
<aj-captcha.version>1.3.0</aj-captcha.version> <aj-captcha.version>1.3.0</aj-captcha.version>
<easy-captcha.version>1.6.2</easy-captcha.version> <easy-captcha.version>1.6.2</easy-captcha.version>
<easy-excel.version>3.3.4</easy-excel.version> <easy-excel.version>4.0.1</easy-excel.version>
<nashorn.version>15.4</nashorn.version> <nashorn.version>15.4</nashorn.version>
<x-file-storage.version>2.1.0</x-file-storage.version> <x-file-storage.version>2.2.0</x-file-storage.version>
<aws-s3.version>1.12.720</aws-s3.version> <aws-s3.version>1.12.761</aws-s3.version>
<crane4j.version>2.8.0</crane4j.version> <crane4j.version>2.9.0</crane4j.version>
<knife4j.version>4.5.0</knife4j.version> <knife4j.version>4.5.0</knife4j.version>
<tlog.version>1.5.2</tlog.version> <tlog.version>1.5.2</tlog.version>
<snakeyaml.version>2.2</snakeyaml.version> <snakeyaml.version>2.2</snakeyaml.version>
<okhttp.version>4.12.0</okhttp.version> <okhttp.version>4.12.0</okhttp.version>
<ttl.version>2.14.5</ttl.version> <ttl.version>2.14.5</ttl.version>
<ip2region.version>3.1.11</ip2region.version> <ip2region.version>3.2.6</ip2region.version>
<hutool.version>5.8.27</hutool.version> <hutool.version>5.8.29</hutool.version>
<!-- Maven Plugin Versions --> <!-- Maven Plugin Versions -->
<flatten.version>1.6.0</flatten.version> <flatten.version>1.6.0</flatten.version>
<spotless.version>2.43.0</spotless.version> <spotless.version>2.43.0</spotless.version>
<sonar.version>3.9.1.2184</sonar.version> <sonar.version>3.11.0.3922</sonar.version>
<maven-compiler-plugin.verison>3.11.0</maven-compiler-plugin.verison>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>
<!-- SnailJob灵活可靠和快速的分布式任务重试和分布式任务调度平台 -->
<dependency>
<groupId>com.aizuda</groupId>
<artifactId>snail-job-client-starter</artifactId>
<version>${snail-job.version}</version>
</dependency>
<dependency>
<groupId>com.aizuda</groupId>
<artifactId>snail-job-client-retry-core</artifactId>
<version>${snail-job.version}</version>
</dependency>
<dependency>
<groupId>com.aizuda</groupId>
<artifactId>snail-job-client-job-core</artifactId>
<version>${snail-job.version}</version>
</dependency>
<!-- Sa-Token轻量级 Java 权限认证框架,让鉴权变得简单、优雅) --> <!-- Sa-Token轻量级 Java 权限认证框架,让鉴权变得简单、优雅) -->
<dependency> <dependency>
<groupId>cn.dev33</groupId> <groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId> <artifactId>sa-token-spring-boot3-starter</artifactId>
<version>${sa-token.version}</version> <version>${sa-token.version}</version>
</dependency> </dependency>
<!-- Sa-Token 整合 JWT --> <!-- Sa-Token 整合 JWT -->
<dependency> <dependency>
<groupId>cn.dev33</groupId> <groupId>cn.dev33</groupId>
@@ -152,14 +170,12 @@
<artifactId>jetcache-autoconfigure</artifactId> <artifactId>jetcache-autoconfigure</artifactId>
<version>${jetcache.version}</version> <version>${jetcache.version}</version>
</dependency> </dependency>
<!-- JetCache 注解 --> <!-- JetCache 注解 -->
<dependency> <dependency>
<groupId>com.alicp.jetcache</groupId> <groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-anno</artifactId> <artifactId>jetcache-anno</artifactId>
<version>${jetcache.version}</version> <version>${jetcache.version}</version>
</dependency> </dependency>
<!-- JetCache Redisson 适配 --> <!-- JetCache Redisson 适配 -->
<dependency> <dependency>
<groupId>com.alicp.jetcache</groupId> <groupId>com.alicp.jetcache</groupId>
@@ -473,6 +489,13 @@
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<!-- 安全模块 - 限流 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-security-limiter</artifactId>
<version>${revision}</version>
</dependency>
<!-- API 文档模块 --> <!-- API 文档模块 -->
<dependency> <dependency>
<groupId>top.continew</groupId> <groupId>top.continew</groupId>
@@ -632,6 +655,12 @@
</plugins> </plugins>
<pluginManagement> <pluginManagement>
<plugins> <plugins>
<!-- 编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.verison}</version>
</plugin>
<!-- 统一版本号插件 --> <!-- 统一版本号插件 -->
<plugin> <plugin>
<groupId>org.codehaus.mojo</groupId> <groupId>org.codehaus.mojo</groupId>

View File

@@ -110,7 +110,7 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@ResponseBody @ResponseBody
@GetMapping("/{id}") @GetMapping("/{id}")
public R<D> get(@PathVariable Long id) { public R<D> get(@PathVariable("id") Long id) {
this.checkPermission(Api.LIST); this.checkPermission(Api.LIST);
return R.ok(baseService.get(id)); return R.ok(baseService.get(id));
} }
@@ -140,7 +140,7 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@ResponseBody @ResponseBody
@PutMapping("/{id}") @PutMapping("/{id}")
public R<Void> update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody C req, @PathVariable Long id) { public R<Void> update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody C req, @PathVariable("id") Long id) {
this.checkPermission(Api.UPDATE); this.checkPermission(Api.UPDATE);
baseService.update(req, id); baseService.update(req, id);
return R.ok("修改成功"); return R.ok("修改成功");
@@ -156,7 +156,7 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
@Parameter(name = "ids", description = "ID 列表", example = "1,2", in = ParameterIn.PATH) @Parameter(name = "ids", description = "ID 列表", example = "1,2", in = ParameterIn.PATH)
@ResponseBody @ResponseBody
@DeleteMapping("/{ids}") @DeleteMapping("/{ids}")
public R<Void> delete(@PathVariable List<Long> ids) { public R<Void> delete(@PathVariable("ids") List<Long> ids) {
this.checkPermission(Api.DELETE); this.checkPermission(Api.DELETE);
baseService.delete(ids); baseService.delete(ids);
return R.ok("删除成功"); return R.ok("删除成功");

View File

@@ -110,7 +110,7 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@ResponseBody @ResponseBody
@GetMapping("/{id}") @GetMapping("/{id}")
public R<D> get(@PathVariable Long id) { public R<D> get(@PathVariable("id") Long id) {
this.checkPermission(Api.LIST); this.checkPermission(Api.LIST);
return R.ok(baseService.get(id)); return R.ok(baseService.get(id));
} }
@@ -140,7 +140,7 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@ResponseBody @ResponseBody
@PutMapping("/{id}") @PutMapping("/{id}")
public R<Void> update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody C req, @PathVariable Long id) { public R<Void> update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody C req, @PathVariable("id") Long id) {
this.checkPermission(Api.UPDATE); this.checkPermission(Api.UPDATE);
baseService.update(req, id); baseService.update(req, id);
return R.ok("修改成功"); return R.ok("修改成功");
@@ -156,7 +156,7 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
@Parameter(name = "ids", description = "ID 列表", example = "1,2", in = ParameterIn.PATH) @Parameter(name = "ids", description = "ID 列表", example = "1,2", in = ParameterIn.PATH)
@ResponseBody @ResponseBody
@DeleteMapping("/{ids}") @DeleteMapping("/{ids}")
public R<Void> delete(@PathVariable List<Long> ids) { public R<Void> delete(@PathVariable("ids") List<Long> ids) {
this.checkPermission(Api.DELETE); this.checkPermission(Api.DELETE);
baseService.delete(ids); baseService.delete(ids);
return R.ok("删除成功"); return R.ok("删除成功");

View File

@@ -68,17 +68,16 @@ import java.util.*;
*/ */
public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdDO, L, D, Q, C> extends ServiceImpl<M, T> implements BaseService<L, D, Q, C> { public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdDO, L, D, Q, C> extends ServiceImpl<M, T> implements BaseService<L, D, Q, C> {
private final Class<?>[] typeArgumentCache = ClassUtils.getTypeArguments(this.getClass()); private Class<L> listClass;
protected final Class<L> listClass = this.currentListClass(); private Class<D> detailClass;
protected final Class<D> detailClass = this.currentDetailClass(); private Class<Q> queryClass;
protected final Class<Q> queryClass = this.currentQueryClass(); private List<Field> queryFields;
private final List<Field> queryFields = ReflectUtils.getNonStaticFields(this.queryClass);
@Override @Override
public PageResp<L> page(Q query, PageQuery pageQuery) { public PageResp<L> page(Q query, PageQuery pageQuery) {
QueryWrapper<T> queryWrapper = this.buildQueryWrapper(query); QueryWrapper<T> queryWrapper = this.buildQueryWrapper(query);
IPage<T> page = baseMapper.selectPage(pageQuery.toPage(), queryWrapper); IPage<T> page = baseMapper.selectPage(pageQuery.toPage(), queryWrapper);
PageResp<L> pageResp = PageResp.build(page, listClass); PageResp<L> pageResp = PageResp.build(page, this.getListClass());
pageResp.getList().forEach(this::fill); pageResp.getList().forEach(this::fill);
return pageResp; return pageResp;
} }
@@ -91,7 +90,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
} }
// 如果构建简单树结构,则不包含基本树结构之外的扩展字段 // 如果构建简单树结构,则不包含基本树结构之外的扩展字段
TreeNodeConfig treeNodeConfig = TreeUtils.DEFAULT_CONFIG; TreeNodeConfig treeNodeConfig = TreeUtils.DEFAULT_CONFIG;
TreeField treeField = listClass.getDeclaredAnnotation(TreeField.class); TreeField treeField = this.getListClass().getDeclaredAnnotation(TreeField.class);
if (!isSimple) { if (!isSimple) {
// 根据 @TreeField 配置生成树结构配置 // 根据 @TreeField 配置生成树结构配置
treeNodeConfig = TreeUtils.genTreeNodeConfig(treeField); treeNodeConfig = TreeUtils.genTreeNodeConfig(treeField);
@@ -104,7 +103,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
tree.setName(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.nameKey()))); tree.setName(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.nameKey())));
tree.setWeight(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.weightKey()))); tree.setWeight(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.weightKey())));
if (!isSimple) { if (!isSimple) {
List<Field> fieldList = ReflectUtils.getNonStaticFields(listClass); List<Field> fieldList = ReflectUtils.getNonStaticFields(this.getListClass());
fieldList.removeIf(f -> CharSequenceUtil.equalsAnyIgnoreCase(f.getName(), treeField.value(), treeField fieldList.removeIf(f -> CharSequenceUtil.equalsAnyIgnoreCase(f.getName(), treeField.value(), treeField
.parentIdKey(), treeField.nameKey(), treeField.weightKey(), treeField.childrenKey())); .parentIdKey(), treeField.nameKey(), treeField.weightKey(), treeField.childrenKey()));
fieldList.forEach(f -> tree.putExtra(f.getName(), ReflectUtil.invoke(node, CharSequenceUtil.genGetter(f fieldList.forEach(f -> tree.putExtra(f.getName(), ReflectUtil.invoke(node, CharSequenceUtil.genGetter(f
@@ -115,7 +114,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
@Override @Override
public List<L> list(Q query, SortQuery sortQuery) { public List<L> list(Q query, SortQuery sortQuery) {
List<L> list = this.list(query, sortQuery, listClass); List<L> list = this.list(query, sortQuery, this.getListClass());
list.forEach(this::fill); list.forEach(this::fill);
return list; return list;
} }
@@ -123,7 +122,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
@Override @Override
public D get(Long id) { public D get(Long id) {
T entity = super.getById(id, false); T entity = super.getById(id, false);
D detail = BeanUtil.toBean(entity, detailClass); D detail = BeanUtil.toBean(entity, this.getDetailClass());
this.fill(detail); this.fill(detail);
return detail; return detail;
} }
@@ -132,15 +131,15 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
public List<LabelValueResp> listDict(Q query, SortQuery sortQuery) { public List<LabelValueResp> listDict(Q query, SortQuery sortQuery) {
QueryWrapper<T> queryWrapper = this.buildQueryWrapper(query); QueryWrapper<T> queryWrapper = this.buildQueryWrapper(query);
this.sort(queryWrapper, sortQuery); this.sort(queryWrapper, sortQuery);
DictField dictField = entityClass.getDeclaredAnnotation(DictField.class); DictField dictField = super.getEntityClass().getDeclaredAnnotation(DictField.class);
CheckUtils.throwIfNull(dictField, "请添加并配置 @DictField 字典结构信息"); CheckUtils.throwIfNull(dictField, "请添加并配置 @DictField 字典结构信息");
// 指定查询字典字段 // 指定查询字典字段
queryWrapper.select(dictField.labelKey(), dictField.valueKey()); queryWrapper.select(dictField.labelKey(), dictField.valueKey());
List<T> entityList = baseMapper.selectList(queryWrapper); List<T> entityList = baseMapper.selectList(queryWrapper);
// 解析映射 // 解析映射
Map<String, String> fieldMapping = MapUtil.newHashMap(2); Map<String, String> fieldMapping = MapUtil.newHashMap(2);
fieldMapping.put(dictField.labelKey(), "label"); fieldMapping.put(CharSequenceUtil.toCamelCase(dictField.labelKey()), "label");
fieldMapping.put(dictField.valueKey(), "value"); fieldMapping.put(CharSequenceUtil.toCamelCase(dictField.valueKey()), "value");
return BeanUtil.copyToList(entityList, LabelValueResp.class, CopyOptions.create() return BeanUtil.copyToList(entityList, LabelValueResp.class, CopyOptions.create()
.setFieldMapping(fieldMapping)); .setFieldMapping(fieldMapping));
} }
@@ -149,7 +148,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public Long add(C req) { public Long add(C req) {
this.beforeAdd(req); this.beforeAdd(req);
T entity = BeanUtil.copyProperties(req, entityClass); T entity = BeanUtil.copyProperties(req, super.getEntityClass());
baseMapper.insert(entity); baseMapper.insert(entity);
this.afterAdd(req, entity); this.afterAdd(req, entity);
return entity.getId(); return entity.getId();
@@ -169,15 +168,63 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void delete(List<Long> ids) { public void delete(List<Long> ids) {
this.beforeDelete(ids); this.beforeDelete(ids);
baseMapper.deleteBatchIds(ids); baseMapper.deleteByIds(ids);
this.afterDelete(ids); this.afterDelete(ids);
} }
@Override @Override
public void export(Q query, SortQuery sortQuery, HttpServletResponse response) { public void export(Q query, SortQuery sortQuery, HttpServletResponse response) {
List<D> list = this.list(query, sortQuery, detailClass); List<D> list = this.list(query, sortQuery, this.getDetailClass());
list.forEach(this::fill); list.forEach(this::fill);
ExcelUtils.export(list, "导出数据", detailClass, response); ExcelUtils.export(list, "导出数据", this.getDetailClass(), response);
}
/**
* 获取当前列表信息类型
*
* @return 当前列表信息类型
*/
public Class<L> getListClass() {
if (this.listClass == null) {
this.listClass = (Class<L>)ClassUtils.getTypeArguments(this.getClass())[2];
}
return this.listClass;
}
/**
* 获取当前详情信息类型
*
* @return 当前详情信息类型
*/
public Class<D> getDetailClass() {
if (this.detailClass == null) {
this.detailClass = (Class<D>)ClassUtils.getTypeArguments(this.getClass())[3];
}
return this.detailClass;
}
/**
* 获取当前查询条件类型
*
* @return 当前查询条件类型
*/
public Class<Q> getQueryClass() {
if (this.queryClass == null) {
this.queryClass = (Class<Q>)ClassUtils.getTypeArguments(this.getClass())[4];
}
return this.queryClass;
}
/**
* 获取当前查询条件类型字段
*
* @return 当前查询条件类型字段列表
*/
public List<Field> getQueryFields() {
if (this.queryFields == null) {
this.queryFields = ReflectUtils.getNonStaticFields(this.getQueryClass());
}
return queryFields;
} }
/** /**
@@ -193,7 +240,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
// 设置排序 // 设置排序
this.sort(queryWrapper, sortQuery); this.sort(queryWrapper, sortQuery);
List<T> entityList = baseMapper.selectList(queryWrapper); List<T> entityList = baseMapper.selectList(queryWrapper);
if (entityClass == targetClass) { if (super.getEntityClass() == targetClass) {
return (List<E>)entityList; return (List<E>)entityList;
} }
return BeanUtil.copyToList(entityList, targetClass); return BeanUtil.copyToList(entityList, targetClass);
@@ -217,7 +264,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
} else { } else {
checkProperty = property; checkProperty = property;
} }
Optional<Field> optional = entityFields.stream() Optional<Field> optional = super.getEntityFields().stream()
.filter(field -> checkProperty.equals(field.getName())) .filter(field -> checkProperty.equals(field.getName()))
.findFirst(); .findFirst();
ValidationUtils.throwIf(optional.isEmpty(), "无效的排序字段 [{}]", property); ValidationUtils.throwIf(optional.isEmpty(), "无效的排序字段 [{}]", property);
@@ -248,7 +295,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
protected QueryWrapper<T> buildQueryWrapper(Q query) { protected QueryWrapper<T> buildQueryWrapper(Q query) {
QueryWrapper<T> queryWrapper = new QueryWrapper<>(); QueryWrapper<T> queryWrapper = new QueryWrapper<>();
// 解析并拼接查询条件 // 解析并拼接查询条件
return QueryWrapperHelper.build(query, queryFields, queryWrapper); return QueryWrapperHelper.build(query, this.getQueryFields(), queryWrapper);
} }
/** /**
@@ -307,31 +354,4 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
protected void afterDelete(List<Long> ids) { protected void afterDelete(List<Long> ids) {
/* 删除后置处理 */ /* 删除后置处理 */
} }
/**
* 获取当前列表信息类型
*
* @return 当前列表信息类型
*/
protected Class<L> currentListClass() {
return (Class<L>)this.typeArgumentCache[2];
}
/**
* 获取当前详情信息类型
*
* @return 当前详情信息类型
*/
protected Class<D> currentDetailClass() {
return (Class<D>)this.typeArgumentCache[3];
}
/**
* 获取当前查询条件类型
*
* @return 当前查询条件类型
*/
protected Class<Q> currentQueryClass() {
return (Class<Q>)this.typeArgumentCache[4];
}
} }

View File

@@ -20,12 +20,12 @@ import org.springframework.mail.javamail.JavaMailSenderImpl;
import top.continew.starter.core.util.validate.ValidationUtils; import top.continew.starter.core.util.validate.ValidationUtils;
/** /**
* 邮件配置服务 * 邮件配置
* *
* @author Charles7c * @author Charles7c
* @since 2.1.0 * @since 2.1.0
*/ */
public interface MailConfigService { public interface MailConfigurer {
/** /**
* 获取邮件配置 * 获取邮件配置

View File

@@ -27,7 +27,7 @@ import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.mail.javamail.MimeMessageHelper;
import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.ExceptionUtils; import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.messaging.mail.core.MailConfigService; import top.continew.starter.messaging.mail.core.MailConfigurer;
import java.io.File; import java.io.File;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@@ -217,10 +217,9 @@ public class MailUtils {
*/ */
public static JavaMailSenderImpl getMailSender() { public static JavaMailSenderImpl getMailSender() {
JavaMailSenderImpl mailSender = SpringUtil.getBean(JavaMailSenderImpl.class); JavaMailSenderImpl mailSender = SpringUtil.getBean(JavaMailSenderImpl.class);
MailConfigService mailConfigService = ExceptionUtils.exToNull(() -> SpringUtil MailConfigurer mailConfigurer = ExceptionUtils.exToNull(() -> SpringUtil.getBean(MailConfigurer.class));
.getBean(MailConfigService.class)); if (mailConfigurer != null && mailConfigurer.getMailConfig() != null) {
if (mailConfigService != null && mailConfigService.getMailConfig() != null) { mailConfigurer.apply(mailConfigurer.getMailConfig(), mailSender);
mailConfigService.apply(mailConfigService.getMailConfig(), mailSender);
} }
return mailSender; return mailSender;
} }

View File

@@ -36,7 +36,7 @@ import top.continew.starter.security.crypto.core.MyBatisEncryptInterceptor;
*/ */
@AutoConfiguration @AutoConfiguration
@EnableConfigurationProperties(CryptoProperties.class) @EnableConfigurationProperties(CryptoProperties.class)
@ConditionalOnProperty(prefix = PropertiesConstants.CRYPTO, name = PropertiesConstants.ENABLED, matchIfMissing = true) @ConditionalOnProperty(prefix = PropertiesConstants.SECURITY_CRYPTO, name = PropertiesConstants.ENABLED, matchIfMissing = true)
public class CryptoAutoConfiguration { public class CryptoAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(CryptoAutoConfiguration.class); private static final Logger log = LoggerFactory.getLogger(CryptoAutoConfiguration.class);

View File

@@ -25,7 +25,7 @@ import top.continew.starter.core.constant.PropertiesConstants;
* @author Charles7c * @author Charles7c
* @since 1.4.0 * @since 1.4.0
*/ */
@ConfigurationProperties(PropertiesConstants.CRYPTO) @ConfigurationProperties(PropertiesConstants.SECURITY_CRYPTO)
public class CryptoProperties { public class CryptoProperties {
/** /**

View File

@@ -65,7 +65,7 @@ public abstract class AbstractMyBatisInterceptor implements Interceptor {
*/ */
public Map<String, FieldEncrypt> getEncryptParams(String mappedStatementId, Integer parameterIndex) { public Map<String, FieldEncrypt> getEncryptParams(String mappedStatementId, Integer parameterIndex) {
return ENCRYPT_PARAM_CACHE return ENCRYPT_PARAM_CACHE
.computeIfAbsent(mappedStatementId, m -> getEncryptParamsNoCached(mappedStatementId, parameterIndex)); .computeIfAbsent(mappedStatementId, key -> getEncryptParamsNoCached(mappedStatementId, parameterIndex));
} }
/** /**

View File

@@ -0,0 +1,31 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.continew</groupId>
<artifactId>continew-starter-security</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-starter-security-limiter</artifactId>
<description>ContiNew Starter 安全模块 - 限流</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Web 模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-web</artifactId>
</dependency>
<!-- 缓存模块 - Redisson -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-redisson</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,69 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.security.limiter.annotation;
import org.redisson.api.RateIntervalUnit;
import top.continew.starter.security.limiter.enums.LimitType;
import java.lang.annotation.*;
/**
* 限流注解
*
* @author KAI
* @since 2.2.0
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimiter {
/**
* 类型
*/
LimitType type() default LimitType.DEFAULT;
/**
* 名称
*/
String name() default "";
/**
* 键(支持 Spring EL 表达式)
*/
String key() default "";
/**
* 速率(指定时间间隔产生的令牌数)
*/
int rate() default Integer.MAX_VALUE;
/**
* 速率间隔(时间间隔)
*/
int interval() default 0;
/**
* 速率间隔时间单位(默认:毫秒)
*/
RateIntervalUnit unit() default RateIntervalUnit.MILLISECONDS;
/**
* 提示信息
*/
String message() default "操作过于频繁,请稍后再试";
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.security.limiter.annotation;
import java.lang.annotation.*;
/**
* 限流组注解
*
* @author KAI
* @since 2.2.0
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RateLimiters {
/**
* 限流组
*/
RateLimiter[] value();
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.security.limiter.autoconfigure;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.security.limiter.core.DefaultRateLimiterNameGenerator;
import top.continew.starter.security.limiter.core.RateLimiterNameGenerator;
/**
* 限流器自动配置
*
* @author KAI
* @author Charles7c
* @since 2.2.0
*/
@AutoConfiguration
@EnableConfigurationProperties(RateLimiterProperties.class)
@ComponentScan({"top.continew.starter.security.limiter.core"})
@ConditionalOnProperty(prefix = PropertiesConstants.SECURITY_LIMITER, name = PropertiesConstants.ENABLED, matchIfMissing = true)
public class RateLimiterAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(RateLimiterAutoConfiguration.class);
/**
* 限流器名称生成器
*/
@Bean
@ConditionalOnMissingBean
public RateLimiterNameGenerator nameGenerator() {
return new DefaultRateLimiterNameGenerator();
}
@PostConstruct
public void postConstruct() {
log.debug("[ContiNew Starter] - Auto Configuration 'Security-RateLimiter' completed initialization.");
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.security.limiter.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
import top.continew.starter.core.constant.PropertiesConstants;
/**
* 限流器配置属性
*
* @author KAI
* @since 2.2.0
*/
@ConfigurationProperties(PropertiesConstants.SECURITY_LIMITER)
public class RateLimiterProperties {
/**
* Key 前缀
*/
private String keyPrefix = "RateLimiter";
public String getKeyPrefix() {
return keyPrefix;
}
public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
}

View File

@@ -0,0 +1,107 @@
/*
* 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.security.limiter.core;
import cn.hutool.core.util.ClassUtil;
import top.continew.starter.core.constant.StringConstants;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
/**
* 默认限流器名称生成器
*
* @author Charles7c
* @since 2.2.0
*/
public class DefaultRateLimiterNameGenerator implements RateLimiterNameGenerator {
protected final ConcurrentHashMap<Method, String> nameMap = new ConcurrentHashMap<>();
@Override
public String generate(Object target, Method method, Object... args) {
return nameMap.computeIfAbsent(method, key -> {
final StringBuilder nameSb = new StringBuilder();
String className = method.getDeclaringClass().getName();
nameSb.append(ClassUtil.getShortClassName(className));
nameSb.append(StringConstants.DOT);
nameSb.append(method.getName());
nameSb.append(StringConstants.ROUND_BRACKET_START);
for (Class<?> clazz : method.getParameterTypes()) {
this.getDescriptor(nameSb, clazz);
}
nameSb.append(StringConstants.ROUND_BRACKET_END);
return nameSb.toString();
});
}
/**
* 获取指定数据类型的描述
*
* @param sb 名称字符串缓存
* @param typeClass 数据类型
*/
private void getDescriptor(final StringBuilder sb, final Class<?> typeClass) {
Class<?> clazz = typeClass;
while (true) {
if (clazz.isPrimitive()) {
sb.append(this.getPrimitiveChar(clazz));
return;
} else if (clazz.isArray()) {
sb.append(StringConstants.BRACKET_START);
clazz = clazz.getComponentType();
} else {
sb.append('L');
String name = clazz.getName();
name = ClassUtil.getShortClassName(name);
sb.append(name);
sb.append(StringConstants.SEMICOLON);
return;
}
}
}
/**
* 根据基本数据获取类型字符
*
* @param clazz 数据类型
* @return 类型字符
*/
private char getPrimitiveChar(Class<?> clazz) {
char c;
if (clazz == Integer.TYPE) {
c = 'I';
} else if (clazz == Void.TYPE) {
c = 'V';
} else if (clazz == Boolean.TYPE) {
c = 'Z';
} else if (clazz == Byte.TYPE) {
c = 'B';
} else if (clazz == Character.TYPE) {
c = 'C';
} else if (clazz == Short.TYPE) {
c = 'S';
} else if (clazz == Double.TYPE) {
c = 'D';
} else if (clazz == Float.TYPE) {
c = 'F';
} else {
c = 'J';
}
return c;
}
}

View File

@@ -0,0 +1,200 @@
/*
* 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.security.limiter.core;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.*;
import org.springframework.stereotype.Component;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.expression.ExpressionUtils;
import top.continew.starter.security.limiter.annotation.RateLimiter;
import top.continew.starter.security.limiter.annotation.RateLimiters;
import top.continew.starter.security.limiter.autoconfigure.RateLimiterProperties;
import top.continew.starter.security.limiter.enums.LimitType;
import top.continew.starter.security.limiter.exception.RateLimiterException;
import top.continew.starter.web.util.ServletUtils;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* 限流器切面
*
* @author KAI
* @author Charles7c
* @since 2.2.0
*/
@Aspect
@Component
public class RateLimiterAspect {
private static final ConcurrentHashMap<String, RRateLimiter> RATE_LIMITER_CACHE = new ConcurrentHashMap<>();
private final RateLimiterProperties properties;
private final RateLimiterNameGenerator nameGenerator;
private final RedissonClient redissonClient;
public RateLimiterAspect(RateLimiterProperties properties,
RateLimiterNameGenerator nameGenerator,
RedissonClient redissonClient) {
this.properties = properties;
this.nameGenerator = nameGenerator;
this.redissonClient = redissonClient;
}
/**
* 单个限流注解切点
*/
@Pointcut("@annotation(top.continew.starter.security.limiter.annotation.RateLimiter)")
public void rateLimiterPointCut() {
}
/**
* 多个限流注解切点
*/
@Pointcut("@annotation(top.continew.starter.security.limiter.annotation.RateLimiters)")
public void rateLimitersPointCut() {
}
/**
* 单限流场景
*
* @param joinPoint 切点
* @param rateLimiter 限流注解
* @return 目标方法的执行结果
* @throws Throwable /
*/
@Around("@annotation(rateLimiter)")
public Object aroundRateLimiter(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) throws Throwable {
if (isRateLimited(joinPoint, rateLimiter)) {
throw new RateLimiterException(rateLimiter.message());
}
return joinPoint.proceed();
}
/**
* 多限流场景
*
* @param joinPoint 切点
* @param rateLimiters 限流组注解
* @return 目标方法的执行结果
* @throws Throwable /
*/
@Around("@annotation(rateLimiters)")
public Object aroundRateLimiters(ProceedingJoinPoint joinPoint, RateLimiters rateLimiters) throws Throwable {
for (RateLimiter rateLimiter : rateLimiters.value()) {
if (isRateLimited(joinPoint, rateLimiter)) {
throw new RateLimiterException(rateLimiter.message());
}
}
return joinPoint.proceed();
}
/**
* 是否需要限流
*
* @param joinPoint 切点
* @param rateLimiter 限流注解
* @return true: 需要限流false不需要限流
*/
private boolean isRateLimited(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) {
try {
String cacheKey = this.getCacheKey(joinPoint, rateLimiter);
RRateLimiter rRateLimiter = RATE_LIMITER_CACHE.computeIfAbsent(cacheKey, key -> redissonClient
.getRateLimiter(cacheKey));
// 限流器配置
RateType rateType = rateLimiter.type() == LimitType.CLUSTER ? RateType.PER_CLIENT : RateType.OVERALL;
int rate = rateLimiter.rate();
int rateInterval = rateLimiter.interval();
RateIntervalUnit rateIntervalUnit = rateLimiter.unit();
// 判断是否需要更新限流器
if (this.isConfigurationUpdateNeeded(rRateLimiter, rateType, rate, rateInterval, rateIntervalUnit)) {
// 更新限流器
rRateLimiter.setRate(rateType, rate, rateInterval, rateIntervalUnit);
}
// 尝试获取令牌
return !rRateLimiter.tryAcquire();
} catch (Exception e) {
throw new RateLimiterException("服务器限流异常,请稍候再试", e);
}
}
/**
* 获取限流缓存 Key
*
* @param joinPoint 切点
* @param rateLimiter 限流注解
* @return 限流缓存 Key
*/
private String getCacheKey(JoinPoint joinPoint, RateLimiter rateLimiter) {
Object target = joinPoint.getTarget();
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
Method method = methodSignature.getMethod();
Object[] args = joinPoint.getArgs();
// 获取限流名称
String name = rateLimiter.name();
if (CharSequenceUtil.isBlank(name)) {
name = nameGenerator.generate(target, method, args);
}
// 解析限流 Key
String key = rateLimiter.key();
if (CharSequenceUtil.isNotBlank(key)) {
Object eval = ExpressionUtils.eval(key, target, method, args);
if (ObjectUtil.isNull(eval)) {
throw new RateLimiterException("限流 Key 解析错误");
}
key = Convert.toStr(eval);
}
// 获取后缀
String suffix = switch (rateLimiter.type()) {
case IP -> JakartaServletUtil.getClientIP(ServletUtils.getRequest());
case CLUSTER -> redissonClient.getId();
default -> StringConstants.EMPTY;
};
return RedisUtils.formatKey(properties.getKeyPrefix(), name, key, suffix);
}
/**
* 判断是否需要更新限流器配置
*
* @param rRateLimiter 限流器
* @param rateType 限流类型OVERALL全局限流PER_CLIENT单机限流
* @param rate 速率(指定时间间隔产生的令牌数)
* @param rateInterval 速率间隔
* @param rateIntervalUnit 时间单位
* @return 是否需要更新配置
*/
private boolean isConfigurationUpdateNeeded(RRateLimiter rRateLimiter,
RateType rateType,
long rate,
long rateInterval,
RateIntervalUnit rateIntervalUnit) {
RateLimiterConfig config = rRateLimiter.getConfig();
return !Objects.equals(config.getRateType(), rateType) || !Objects.equals(config.getRate(), rate) || !Objects
.equals(config.getRateInterval(), rateIntervalUnit.toMillis(rateInterval));
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.security.limiter.core;
import java.lang.reflect.Method;
/**
* 限流器名称生成器
*
* @author Charles7c
* @since 2.2.0
*/
@FunctionalInterface
public interface RateLimiterNameGenerator {
/**
* Generate a rate limiter name for the given method and its parameters.
*
* @param target the target instance
* @param method the method being called
* @param args the method parameters (with any var-args expanded)
* @return a generated rate limiter name
*/
String generate(Object target, Method method, Object... args);
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.security.limiter.enums;
/**
* 限流类型
*
* @author KAI
* @since 2.2.0
*/
public enum LimitType {
/**
* 全局限流
*/
DEFAULT,
/**
* 根据 IP 限流
*/
IP,
/**
* 根据实例限流(支持集群多实例)
*/
CLUSTER
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.security.limiter.exception;
import top.continew.starter.core.exception.BaseException;
/**
* 限流异常
*
* @author KAI
* @since 2.2.0
*/
public class RateLimiterException extends BaseException {
public RateLimiterException(String message) {
super(message);
}
public RateLimiterException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1 @@
top.continew.starter.security.limiter.autoconfigure.RateLimiterAutoConfiguration

View File

@@ -0,0 +1,7 @@
{
"top.continew.starter.security.limiter.annotation.RateLimiter@key":{
"method":{
"parameters": true
}
}
}

View File

@@ -53,7 +53,7 @@ import java.util.Map;
*/ */
@AutoConfiguration @AutoConfiguration
@EnableConfigurationProperties(PasswordEncoderProperties.class) @EnableConfigurationProperties(PasswordEncoderProperties.class)
@ConditionalOnProperty(prefix = PropertiesConstants.PASSWORD, name = PropertiesConstants.ENABLED, matchIfMissing = true) @ConditionalOnProperty(prefix = PropertiesConstants.SECURITY_PASSWORD, name = PropertiesConstants.ENABLED, matchIfMissing = true)
public class PasswordEncoderAutoConfiguration { public class PasswordEncoderAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(PasswordEncoderAutoConfiguration.class); private static final Logger log = LoggerFactory.getLogger(PasswordEncoderAutoConfiguration.class);

View File

@@ -25,7 +25,7 @@ import top.continew.starter.core.constant.PropertiesConstants;
* @author Jasmine * @author Jasmine
* @since 1.3.0 * @since 1.3.0
*/ */
@ConfigurationProperties(PropertiesConstants.PASSWORD) @ConfigurationProperties(PropertiesConstants.SECURITY_PASSWORD)
public class PasswordEncoderProperties { public class PasswordEncoderProperties {
/** /**

View File

@@ -17,6 +17,7 @@
<module>continew-starter-security-password</module> <module>continew-starter-security-password</module>
<module>continew-starter-security-mask</module> <module>continew-starter-security-mask</module>
<module>continew-starter-security-crypto</module> <module>continew-starter-security-crypto</module>
<module>continew-starter-security-limiter</module>
</modules> </modules>
<dependencies> <dependencies>

View File

@@ -40,7 +40,7 @@ import top.continew.starter.core.constant.StringConstants;
@Lazy @Lazy
@AutoConfiguration @AutoConfiguration
@ConditionalOnWebApplication @ConditionalOnWebApplication
@ConditionalOnProperty(prefix = PropertiesConstants.CORS, name = PropertiesConstants.ENABLED, havingValue = "true") @ConditionalOnProperty(prefix = PropertiesConstants.WEB_CORS, name = PropertiesConstants.ENABLED, havingValue = "true")
@EnableConfigurationProperties(CorsProperties.class) @EnableConfigurationProperties(CorsProperties.class)
public class CorsAutoConfiguration { public class CorsAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(CorsAutoConfiguration.class); private static final Logger log = LoggerFactory.getLogger(CorsAutoConfiguration.class);

View File

@@ -30,7 +30,7 @@ import java.util.List;
* @author Charles7c * @author Charles7c
* @since 1.0.0 * @since 1.0.0
*/ */
@ConfigurationProperties(PropertiesConstants.CORS) @ConfigurationProperties(PropertiesConstants.WEB_CORS)
public class CorsProperties { public class CorsProperties {
private static final List<String> ALL = Collections.singletonList(StringConstants.ASTERISK); private static final List<String> ALL = Collections.singletonList(StringConstants.ASTERISK);

View File

@@ -19,6 +19,8 @@ package top.continew.starter.web.autoconfigure.exception;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException; import jakarta.validation.ConstraintViolationException;
@@ -36,7 +38,11 @@ import org.springframework.web.multipart.MultipartException;
import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.exception.BadRequestException; import top.continew.starter.core.exception.BadRequestException;
import top.continew.starter.core.exception.BusinessException; import top.continew.starter.core.exception.BusinessException;
import top.continew.starter.core.exception.GlobalException;
import top.continew.starter.core.exception.ResultInfoInterface;
import top.continew.starter.web.autoconfigure.i18n.I18nProperties;
import top.continew.starter.web.model.R; import top.continew.starter.web.model.R;
import top.continew.starter.core.util.MessageSourceUtils;
/** /**
* 全局异常处理器 * 全局异常处理器
@@ -49,6 +55,8 @@ public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
private static final String PARAM_FAILED = "请求地址 [{}],参数验证失败。"; private static final String PARAM_FAILED = "请求地址 [{}],参数验证失败。";
@Resource
private I18nProperties i18nProperties;
/** /**
* 拦截自定义验证异常-错误请求 * 拦截自定义验证异常-错误请求
@@ -122,14 +130,14 @@ public class GlobalExceptionHandler {
msg = msg.concat(cause.getMessage().toLowerCase()); msg = msg.concat(cause.getMessage().toLowerCase());
} }
if (msg.contains("size") && msg.contains("exceed")) { if (msg.contains("size") && msg.contains("exceed")) {
sizeLimit = CharSequenceUtil.subBetween(msg, "maximum (", ")"); sizeLimit = CharSequenceUtil.subBetween(msg, "the maximum size ", " for");
} else if (msg.contains("larger than")) { } else if (msg.contains("larger than")) {
sizeLimit = CharSequenceUtil.subAfter(msg, "larger than ", true); sizeLimit = CharSequenceUtil.subAfter(msg, "larger than ", true);
} else { } else {
return defaultFail; return defaultFail;
} }
String errorMsg = "请上传小于 %sMB 的文件".formatted(NumberUtil.parseLong(sizeLimit) / 1024 / 1024); String errorMsg = "请上传小于 %sKB 的文件".formatted(NumberUtil.parseLong(sizeLimit) / 1024);
log.warn("请求地址 [{}],上传文件失败,文件大小超过限制。", request.getRequestURI(), e); log.warn("请求地址 [{}],上传文件失败,文件大小超过限制。", request.getRequestURI(), e);
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg); return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
} }
@@ -153,6 +161,23 @@ public class GlobalExceptionHandler {
return R.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()); return R.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
} }
/**
* 拦截全局应用异常
*/
@ExceptionHandler(GlobalException.class)
public R<Void> handleGlobalException(GlobalException e, HttpServletRequest request) {
log.error("请求地址 [{}],发生业务异常。", request.getRequestURI(), e);
ResultInfoInterface resultInfo = e.getResultInfo();
// 未开启,直接返回
if (!i18nProperties.getEnabled()) {
return R.fail(resultInfo.getCode(), resultInfo.getDefaultMessage());
}
// 以用户自定的messageKey优先否则枚举当messageKey
String messageKey = StrUtil.blankToDefault(resultInfo.getMessageKey(), resultInfo.toString());
String message = MessageSourceUtils.getMessage(messageKey, resultInfo.getDefaultMessage());
return R.fail(resultInfo.getCode(), message);
}
/** /**
* 拦截未知的运行时异常 * 拦截未知的运行时异常
*/ */
@@ -170,4 +195,5 @@ public class GlobalExceptionHandler {
log.error("请求地址 [{}],发生未知异常。", request.getRequestURI(), e); log.error("请求地址 [{}],发生未知异常。", request.getRequestURI(), e);
return R.fail(e.getMessage()); return R.fail(e.getMessage());
} }
} }

View File

@@ -17,19 +17,14 @@
package top.continew.starter.web.autoconfigure.exception; package top.continew.starter.web.autoconfigure.exception;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import org.hibernate.validator.HibernateValidator;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController; import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.context.annotation.Bean; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory; import top.continew.starter.web.autoconfigure.i18n.I18nProperties;
/** /**
* 全局异常处理器自动配置 * 全局异常处理器自动配置
@@ -40,29 +35,11 @@ import org.springframework.validation.beanvalidation.SpringConstraintValidatorFa
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(BasicErrorController.class) @ConditionalOnMissingBean(BasicErrorController.class)
@Import({GlobalExceptionHandler.class, GlobalErrorHandler.class}) @Import({GlobalExceptionHandler.class, GlobalErrorHandler.class})
@EnableConfigurationProperties(I18nProperties.class)
public class GlobalExceptionHandlerAutoConfiguration { public class GlobalExceptionHandlerAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandlerAutoConfiguration.class); private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandlerAutoConfiguration.class);
/**
* Validator 失败立即返回模式配置
*
* <p>
* 默认情况下会校验完所有字段,然后才抛出异常。
* </p>
*/
@Bean
public Validator validator(AutowireCapableBeanFactory beanFactory) {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true)
.constraintValidatorFactory(new SpringConstraintValidatorFactory(beanFactory))
.buildValidatorFactory();
try (validatorFactory) {
return validatorFactory.getValidator();
}
}
@PostConstruct @PostConstruct
public void postConstruct() { public void postConstruct() {
log.debug("[ContiNew Starter] - Auto Configuration 'Web-Global Exception Handler' completed initialization."); log.debug("[ContiNew Starter] - Auto Configuration 'Web-Global Exception Handler' completed initialization.");

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.web.autoconfigure.i18n;
import org.springframework.boot.context.properties.ConfigurationProperties;
import top.continew.starter.core.constant.PropertiesConstants;
/**
* 国际化配置属性
*
* @author Jasmine
* @since 2.2.0
*/
@ConfigurationProperties(PropertiesConstants.WEB_I18N)
public class I18nProperties {
/**
* 国际化开启 true-开启, false-关闭
*/
private Boolean enabled;
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
}

View File

@@ -43,7 +43,7 @@ import top.continew.starter.core.constant.PropertiesConstants;
@AutoConfiguration @AutoConfiguration
@ConditionalOnWebApplication @ConditionalOnWebApplication
@EnableConfigurationProperties(TraceProperties.class) @EnableConfigurationProperties(TraceProperties.class)
@ConditionalOnProperty(prefix = PropertiesConstants.TRACE, name = PropertiesConstants.ENABLED, havingValue = "true") @ConditionalOnProperty(prefix = PropertiesConstants.WEB_TRACE, name = PropertiesConstants.ENABLED, havingValue = "true")
public class TraceAutoConfiguration { public class TraceAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(TraceAutoConfiguration.class); private static final Logger log = LoggerFactory.getLogger(TraceAutoConfiguration.class);

View File

@@ -26,7 +26,7 @@ import top.continew.starter.core.constant.PropertiesConstants;
* @author Charles7c * @author Charles7c
* @since 1.3.0 * @since 1.3.0
*/ */
@ConfigurationProperties(PropertiesConstants.TRACE) @ConfigurationProperties(PropertiesConstants.WEB_TRACE)
public class TraceProperties { public class TraceProperties {
/** /**

View File

@@ -33,7 +33,7 @@ import top.continew.starter.core.constant.PropertiesConstants;
@AutoConfiguration @AutoConfiguration
@ConditionalOnWebApplication @ConditionalOnWebApplication
@EnableConfigurationProperties(XssProperties.class) @EnableConfigurationProperties(XssProperties.class)
@ConditionalOnProperty(prefix = PropertiesConstants.XSS, name = PropertiesConstants.ENABLED, havingValue = "true") @ConditionalOnProperty(prefix = PropertiesConstants.WEB_XSS, name = PropertiesConstants.ENABLED, havingValue = "true")
public class XssAutoConfiguration { public class XssAutoConfiguration {
/** /**

View File

@@ -29,7 +29,7 @@ import java.util.List;
* @author whhya * @author whhya
* @since 2.0.0 * @since 2.0.0
*/ */
@ConfigurationProperties(PropertiesConstants.XSS) @ConfigurationProperties(PropertiesConstants.WEB_XSS)
public class XssProperties { public class XssProperties {
/** /**

View File

@@ -76,6 +76,14 @@
<build> <build>
<plugins> <plugins>
<!-- 编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgument>-parameters</compilerArgument>
</configuration>
</plugin>
<!-- 代码格式化插件 --> <!-- 代码格式化插件 -->
<plugin> <plugin>
<groupId>com.diffplug.spotless</groupId> <groupId>com.diffplug.spotless</groupId>