mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-11-12 05:01:28 +08:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b9f106572a | |||
| be3a121c09 | |||
| 6e76269bb6 | |||
| 0b41f2d10c | |||
| 9ec2e6b981 | |||
| 31c3162563 | |||
| 8c6b94ba76 | |||
| 5b0f89a88f | |||
| bd07f9b41f | |||
| 702dcca701 | |||
| bed954ca94 | |||
|
|
ebc73a94e8 | ||
| 1479c8d939 | |||
| 730df52797 | |||
| 32935fa4fa | |||
| b27fbd41eb | |||
| 8bacd87d25 | |||
|
|
c1ebc4621c | ||
| a0388b5dc8 | |||
| e7566d284b | |||
| c17668c2d1 | |||
| 65cfe91770 | |||
| dca715709f | |||
| a110bd9789 | |||
| b0f5506424 | |||
| 6809600858 | |||
| d31d8d209a |
70
CHANGELOG.md
70
CHANGELOG.md
@@ -1,3 +1,73 @@
|
||||
## [v2.5.0](https://github.com/continew-org/continew-starter/compare/v2.4.0...v2.5.0) (2024-08-07)
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
- 【web】重构全局响应处理方案 ([0b41f2d](https://github.com/continew-org/continew-starter/commit/0b41f2d10c5dfb309585b36bde7008cdff6c912a))
|
||||
- 【web】FileUploadUtils 新增下载重载方法 ([be3a121](https://github.com/continew-org/continew-starter/commit/be3a121c0901e7c31bd2d30ac3befa74197c8c35))
|
||||
|
||||
### 🐛 问题修复
|
||||
|
||||
- 【log】仅支持获取 JSON 结构响应体 ([6e76269](https://github.com/continew-org/continew-starter/commit/6e76269bb6f335fa655fab5a5bee85824c1ca9e3))
|
||||
|
||||
### 💎 功能优化
|
||||
|
||||
- 【web】BaseEnumConverterAutoConfiguration => WebMvcAutoConfiguration ([9ec2e6b](https://github.com/continew-org/continew-starter/commit/9ec2e6b98137ed54fbba25d4e895d28cc39e7d93))
|
||||
- 【log】log-httptrace-pro => log-interceptor ([31c3162](https://github.com/continew-org/continew-starter/commit/31c3162563589b71f7bc09797d39abb0177bee27))
|
||||
|
||||
## [v2.4.0](https://github.com/continew-org/continew-starter/compare/v2.3.0...v2.4.0) (2024-07-31)
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
- 【json/jackson】新增枚举接口序列化及反序列化配置 ([32935fa](https://github.com/continew-org/continew-starter/commit/32935fa4fafad0a405153f408b2c16c2389b3aa3))
|
||||
- 【api-doc】增加对 BaseEnum 枚举接口的详细展示 (Gitee#28) ([ebc73a9](https://github.com/continew-org/continew-starter/commit/ebc73a94e8824aa3233492ab4c013b5c70a71ee8))
|
||||
- 【web】新增 BaseEnum 枚举接口参数转换器 ([bed954c](https://github.com/continew-org/continew-starter/commit/bed954ca94b8e2edd897c25d06475da079d0720a))
|
||||
- 【web】SpringWebUtils 新增 match 路径匹配方法 ([702dcca](https://github.com/continew-org/continew-starter/commit/702dcca7012a4ac3d779396f12ef9eeb8371f7cb))
|
||||
|
||||
### 🐛 问题修复
|
||||
|
||||
- EasyExcel 4.0.1 => 3.3.4,暂时回退版本,解决版本冲突问题 ([1479c8d](https://github.com/continew-org/continew-starter/commit/1479c8d9396e92decfbaefd2d71145d2953674ba))
|
||||
|
||||
### 💎 功能优化
|
||||
|
||||
- 代码编译增加 -parameters 参数 ([c1ebc46](https://github.com/continew-org/continew-starter/commit/c1ebc4621c7c44334cf172708cb061f0fecb5a05))
|
||||
- 【data/mybatis-plus】移动枚举接口到 core 模块,和 MP IEnum 枚举接口解耦 ([b27fbd4](https://github.com/continew-org/continew-starter/commit/b27fbd41eb9ec6020f3403a5e3beaef6e2a8fb62))
|
||||
- 【extension/crud】移动 ExcelBaseEnumConverter 到 excel 模块 ([730df52](https://github.com/continew-org/continew-starter/commit/730df527970718aa1008d03a35b53064b20ef1ce))
|
||||
- 【log】新增 excludePatterns 放行路由配置 ([bd07f9b](https://github.com/continew-org/continew-starter/commit/bd07f9b41f5eabe380c91c18877886fa97e1bc20))
|
||||
|
||||
## [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.7(TaskExecutor => 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)
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
111
README.md
111
README.md
@@ -13,7 +13,7 @@
|
||||
<img src="https://sonarcloud.io/api/project_badges/measure?project=Charles7c_continew-starter&metric=alert_status" alt="Sonar Status" />
|
||||
</a>
|
||||
<a href="https://spring.io/projects/spring-boot" target="_blank">
|
||||
<img src="https://img.shields.io/badge/Spring Boot-3.1.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 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" />
|
||||
@@ -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 |
|
||||
| Gitee(码云) | https://gitee.com/continew/continew-starter |
|
||||
|
||||
## 像数1,2,3一样容易
|
||||
|
||||
@@ -140,96 +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-ip2region:3.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-ip2region:3.2.6 |
|
||||
|
||||
### JSON模块
|
||||
|
||||
| 模块名称 | 模块说明 | 依赖版本 |
|
||||
| ----------------------------- | -------------------- | --------------- |
|
||||
| continew-starter-json-jackson | Jackson 序列化等配置 | Jackson:2.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-mask | JSON 脱敏 | |
|
||||
| continew-starter-security-crypto | 数据库字段加/解密 | |
|
||||
| continew-starter-security-limiter | 限流器 | |
|
||||
| 模块名称 | 模块说明 |
|
||||
| ---------------------------------- | ----------------- |
|
||||
| continew-starter-security-password | 密码编码器 |
|
||||
| continew-starter-security-mask | JSON 脱敏 |
|
||||
| continew-starter-security-crypto | 数据库字段加/解密 |
|
||||
| continew-starter-security-limiter | 限流器 |
|
||||
|
||||
### Web模块
|
||||
|
||||
| 模块名称 | 模块说明 | 依赖版本 |
|
||||
| -------------------- | ---------------------------------- | ------------------------------------------------------------ |
|
||||
| continew-starter-web | 跨域、全局异常、错误处理等自动配置 | <a href="https://undertow.io/" target="_blank">Undertow</a>:2.3.10.Final<br />TLog:1.5.1 |
|
||||
| 模块名称 | 模块说明 |
|
||||
| -------------------- | ---------------------------------- |
|
||||
| continew-starter-web | 跨域、全局异常、错误处理等自动配置 |
|
||||
|
||||
### 日志模块
|
||||
|
||||
| 模块名称 | 模块说明 | 依赖版本 |
|
||||
| ---------------------------------- | ----------------------------------------- | -------- |
|
||||
| continew-starter-log-core | 日志核心模块 | |
|
||||
| continew-starter-log-httptrace-pro | Spring Boot Actuator HttpTrace 重置增强版 | |
|
||||
| 模块名称 | 模块说明 |
|
||||
|----------------------------------| ----------------------------------------- |
|
||||
| continew-starter-log-core | 日志核心模块 |
|
||||
| continew-starter-log-interceptor | 拦截器版(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 Captcha:1.6.2 |
|
||||
| continew-starter-captcha-behavior | 行为验证码 | AJ-Captcha:1.3.0 |
|
||||
| 模块名称 | 模块说明 |
|
||||
| --------------------------------- | ---------- |
|
||||
| continew-starter-captcha-graphic | 图形验证码 |
|
||||
| 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-springcache | Spring Cache 自动配置 | |
|
||||
| continew-starter-cache-jetcache | JetCache 自动配置 | |
|
||||
| 模块名称 | 模块说明 |
|
||||
| ---------------------------------- | --------------------- |
|
||||
| continew-starter-cache-redisson | Redisson 自动配置 |
|
||||
| continew-starter-cache-springcache | Spring Cache 自动配置 |
|
||||
| continew-starter-cache-jetcache | JetCache 自动配置 |
|
||||
|
||||
### 数据访问模块
|
||||
|
||||
| 模块名称 | 模块说明 | 依赖版本 |
|
||||
|------------------------------------|-------------------| ----------------------------------------------------------- |
|
||||
| continew-starter-data-core | 数据访问核心模块 | |
|
||||
| continew-starter-data-mybatis-plus | MyBatis Plus 自动配置 | <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-flex | MyBatis Flex 自动配置 | |
|
||||
| 模块名称 | 模块说明 |
|
||||
| ---------------------------------- | --------------------- |
|
||||
| continew-starter-data-core | 数据访问核心模块 |
|
||||
| continew-starter-data-mybatis-plus | MyBatis Plus 自动配置 |
|
||||
| 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-justauth | JustAuth 自动配置 | <a href="https://justauth.cn/" target="_blank">Just Auth</a>:1.16.6 |
|
||||
| 模块名称 | 模块说明 |
|
||||
| ------------------------------ | ----------------- |
|
||||
| continew-starter-auth-satoken | SaToken 自动配置 |
|
||||
| continew-starter-auth-justauth | JustAuth 自动配置 |
|
||||
|
||||
### 消息模块
|
||||
|
||||
| 模块名称 | 模块说明 | 依赖版本 |
|
||||
|--------------------------------------|-----------| ------------------------------------------------------------ |
|
||||
| continew-starter-messaging-mail | 邮件 | Jakarta Mail:1.1.0 |
|
||||
| continew-starter-messaging-websocket | WebSocket | |
|
||||
| 模块名称 | 模块说明 |
|
||||
| ------------------------------------ | --------- |
|
||||
| continew-starter-messaging-mail | 邮件 |
|
||||
| continew-starter-messaging-websocket | WebSocket |
|
||||
|
||||
### 扩展模块
|
||||
|
||||
| 模块名称 | 模块说明 | 依赖版本 |
|
||||
| ------------------------------- | --------------------------------------------- | -------- |
|
||||
| continew-starter-extension-crud | 扩展模块:BaseController 自定义 CRUD API 封装 | |
|
||||
| 模块名称 | 模块说明 |
|
||||
| ------------------------------- | --------------------------------------------- |
|
||||
| continew-starter-extension-crud | 扩展模块:BaseController 自定义 CRUD API 封装 |
|
||||
|
||||
## 贡献代码
|
||||
|
||||
|
||||
@@ -17,17 +17,28 @@
|
||||
package top.continew.starter.apidoc.autoconfigure;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.fasterxml.jackson.databind.type.CollectionType;
|
||||
import com.fasterxml.jackson.databind.type.SimpleType;
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import io.swagger.v3.oas.models.media.Schema;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
|
||||
import org.springdoc.core.configuration.SpringDocConfiguration;
|
||||
import org.springdoc.core.customizers.*;
|
||||
import org.springdoc.core.properties.SpringDocConfigProperties;
|
||||
import org.springdoc.core.providers.JavadocProvider;
|
||||
import org.springdoc.core.service.OpenAPIService;
|
||||
import org.springdoc.core.service.SecurityService;
|
||||
import org.springdoc.core.utils.PropertyResolverUtils;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
@@ -37,12 +48,15 @@ import org.springframework.http.CacheControl;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import top.continew.starter.apidoc.handler.OpenApiHandler;
|
||||
import top.continew.starter.apidoc.util.EnumTypeUtils;
|
||||
import top.continew.starter.core.autoconfigure.project.ProjectProperties;
|
||||
import top.continew.starter.core.enums.BaseEnum;
|
||||
import top.continew.starter.core.util.GeneralPropertySourceFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* API 文档自动配置
|
||||
@@ -51,7 +65,7 @@ import java.util.concurrent.TimeUnit;
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@EnableWebMvc
|
||||
@AutoConfiguration
|
||||
@AutoConfiguration(before = SpringDocConfiguration.class)
|
||||
@EnableConfigurationProperties(SpringDocExtensionProperties.class)
|
||||
@PropertySource(value = "classpath:default-api-doc.yml", factory = GeneralPropertySourceFactory.class)
|
||||
public class SpringDocAutoConfiguration implements WebMvcConfigurer {
|
||||
@@ -127,6 +141,112 @@ public class SpringDocAutoConfiguration implements WebMvcConfigurer {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义 OpenApi 处理器
|
||||
*/
|
||||
@Bean
|
||||
public OpenAPIService openApiBuilder(Optional<OpenAPI> openAPI,
|
||||
SecurityService securityParser,
|
||||
SpringDocConfigProperties springDocConfigProperties,
|
||||
PropertyResolverUtils propertyResolverUtils,
|
||||
Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomisers,
|
||||
Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomisers,
|
||||
Optional<JavadocProvider> javadocProvider) {
|
||||
return new OpenApiHandler(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomisers, serverBaseUrlCustomisers, javadocProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义参数配置(针对 BaseEnum 展示枚举值和描述)
|
||||
*
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@Bean
|
||||
public ParameterCustomizer customParameterCustomizer() {
|
||||
return (parameterModel, methodParameter) -> {
|
||||
Class<?> parameterType = methodParameter.getParameterType();
|
||||
// 判断是否为 BaseEnum 的子类型
|
||||
if (!ClassUtil.isAssignable(BaseEnum.class, parameterType)) {
|
||||
return parameterModel;
|
||||
}
|
||||
String description = parameterModel.getDescription();
|
||||
if (StrUtil.contains(description, "color:red")) {
|
||||
return parameterModel;
|
||||
}
|
||||
// 封装参数配置
|
||||
this.configureSchema(parameterModel.getSchema(), parameterType);
|
||||
// 自定义枚举描述
|
||||
parameterModel.setDescription(description + "<span style='color:red'>" + this
|
||||
.getDescMap(parameterType) + "</span>");
|
||||
return parameterModel;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义参数配置(针对 BaseEnum 展示枚举值和描述)
|
||||
*
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@Bean
|
||||
public PropertyCustomizer customPropertyCustomizer() {
|
||||
return (schema, type) -> {
|
||||
Class<?> rawClass;
|
||||
// 获取原始类的类型
|
||||
if (type.getType() instanceof SimpleType) {
|
||||
rawClass = ((SimpleType)type.getType()).getRawClass();
|
||||
} else if (type.getType() instanceof CollectionType) {
|
||||
rawClass = ((CollectionType)type.getType()).getContentType().getRawClass();
|
||||
} else {
|
||||
rawClass = Object.class;
|
||||
}
|
||||
// 判断是否为 BaseEnum 的子类型
|
||||
if (!ClassUtil.isAssignable(BaseEnum.class, rawClass)) {
|
||||
return schema;
|
||||
}
|
||||
// 封装参数配置
|
||||
this.configureSchema(schema, rawClass);
|
||||
// 自定义参数描述
|
||||
schema.setDescription(schema.getDescription() + "<span style='color:red'>" + this
|
||||
.getDescMap(rawClass) + "</span>");
|
||||
return schema;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装 Schema 配置
|
||||
*
|
||||
* @param schema Schema
|
||||
* @param enumClass 枚举类型
|
||||
* @since 2.4.0
|
||||
*/
|
||||
private void configureSchema(Schema schema, Class<?> enumClass) {
|
||||
BaseEnum[] enums = (BaseEnum[])enumClass.getEnumConstants();
|
||||
// 设置枚举可用值
|
||||
List<String> valueList = Arrays.stream(enums).map(e -> e.getValue().toString()).toList();
|
||||
schema.setEnum(valueList);
|
||||
// 设置枚举值类型和格式
|
||||
String enumValueType = EnumTypeUtils.getEnumValueTypeAsString(enumClass);
|
||||
schema.setType(enumValueType);
|
||||
switch (enumValueType) {
|
||||
case "integer" -> schema.setFormat("int32");
|
||||
case "long" -> schema.setFormat("int64");
|
||||
case "number" -> schema.setFormat("double");
|
||||
default -> schema.setFormat(enumValueType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取枚举描述 Map
|
||||
*
|
||||
* @param enumClass 枚举类型
|
||||
* @return 枚举描述 Map
|
||||
* @since 2.4.0
|
||||
*/
|
||||
private Map<Object, String> getDescMap(Class<?> enumClass) {
|
||||
BaseEnum[] enums = (BaseEnum[])enumClass.getEnumConstants();
|
||||
return Arrays.stream(enums)
|
||||
.collect(Collectors.toMap(BaseEnum::getValue, BaseEnum::getDescription, (a, b) -> a, LinkedHashMap::new));
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'ApiDoc' completed initialization.");
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.apidoc.handler;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import io.swagger.v3.core.jackson.TypeNameResolver;
|
||||
import io.swagger.v3.core.util.AnnotationsUtils;
|
||||
import io.swagger.v3.oas.annotations.tags.Tags;
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import io.swagger.v3.oas.models.Paths;
|
||||
import io.swagger.v3.oas.models.tags.Tag;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
|
||||
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
|
||||
import org.springdoc.core.properties.SpringDocConfigProperties;
|
||||
import org.springdoc.core.providers.JavadocProvider;
|
||||
import org.springdoc.core.service.OpenAPIService;
|
||||
import org.springdoc.core.service.SecurityService;
|
||||
import org.springdoc.core.utils.PropertyResolverUtils;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 自定义 OpenApi 处理器(对源码功能进行修改,增强使用)
|
||||
*
|
||||
* @author echo
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@SuppressWarnings("all")
|
||||
public class OpenApiHandler extends OpenAPIService {
|
||||
|
||||
/**
|
||||
* The Basic error controller.
|
||||
*/
|
||||
private static Class<?> basicErrorController;
|
||||
|
||||
/**
|
||||
* The Security parser.
|
||||
*/
|
||||
private final SecurityService securityParser;
|
||||
|
||||
/**
|
||||
* The Mappings map.
|
||||
*/
|
||||
private final Map<String, Object> mappingsMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The Springdoc tags.
|
||||
*/
|
||||
private final Map<HandlerMethod, Tag> springdocTags = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The Open api builder customisers.
|
||||
*/
|
||||
private final Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomisers;
|
||||
|
||||
/**
|
||||
* The server base URL customisers.
|
||||
*/
|
||||
private final Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers;
|
||||
|
||||
/**
|
||||
* The Spring doc config properties.
|
||||
*/
|
||||
private final SpringDocConfigProperties springDocConfigProperties;
|
||||
|
||||
/**
|
||||
* The Cached open api map.
|
||||
*/
|
||||
private final Map<String, OpenAPI> cachedOpenAPI = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The Property resolver utils.
|
||||
*/
|
||||
private final PropertyResolverUtils propertyResolverUtils;
|
||||
|
||||
/**
|
||||
* The javadoc provider.
|
||||
*/
|
||||
private final Optional<JavadocProvider> javadocProvider;
|
||||
|
||||
/**
|
||||
* The Context.
|
||||
*/
|
||||
private ApplicationContext context;
|
||||
|
||||
/**
|
||||
* The Open api.
|
||||
*/
|
||||
private OpenAPI openAPI;
|
||||
|
||||
/**
|
||||
* The Is servers present.
|
||||
*/
|
||||
private boolean isServersPresent;
|
||||
|
||||
/**
|
||||
* The Server base url.
|
||||
*/
|
||||
private String serverBaseUrl;
|
||||
|
||||
/**
|
||||
* Instantiates a new Open api builder.
|
||||
*
|
||||
* @param openAPI the open api
|
||||
* @param securityParser the security parser
|
||||
* @param springDocConfigProperties the spring doc config properties
|
||||
* @param propertyResolverUtils the property resolver utils
|
||||
* @param openApiBuilderCustomizers the open api builder customisers
|
||||
* @param serverBaseUrlCustomizers the server base url customizers
|
||||
* @param javadocProvider the javadoc provider
|
||||
*/
|
||||
public OpenApiHandler(Optional<OpenAPI> openAPI,
|
||||
SecurityService securityParser,
|
||||
SpringDocConfigProperties springDocConfigProperties,
|
||||
PropertyResolverUtils propertyResolverUtils,
|
||||
Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomizers,
|
||||
Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers,
|
||||
Optional<JavadocProvider> javadocProvider) {
|
||||
super(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider);
|
||||
if (openAPI.isPresent()) {
|
||||
this.openAPI = openAPI.get();
|
||||
if (this.openAPI.getComponents() == null)
|
||||
this.openAPI.setComponents(new Components());
|
||||
if (this.openAPI.getPaths() == null)
|
||||
this.openAPI.setPaths(new Paths());
|
||||
if (!CollectionUtils.isEmpty(this.openAPI.getServers()))
|
||||
this.isServersPresent = true;
|
||||
}
|
||||
this.propertyResolverUtils = propertyResolverUtils;
|
||||
this.securityParser = securityParser;
|
||||
this.springDocConfigProperties = springDocConfigProperties;
|
||||
this.openApiBuilderCustomisers = openApiBuilderCustomizers;
|
||||
this.serverBaseUrlCustomizers = serverBaseUrlCustomizers;
|
||||
this.javadocProvider = javadocProvider;
|
||||
if (springDocConfigProperties.isUseFqn())
|
||||
TypeNameResolver.std.setUseFqn(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Operation buildTags(HandlerMethod handlerMethod, Operation operation, OpenAPI openAPI, Locale locale) {
|
||||
|
||||
Set<Tag> tags = new HashSet<>();
|
||||
Set<String> tagsStr = new HashSet<>();
|
||||
|
||||
buildTagsFromMethod(handlerMethod.getMethod(), tags, tagsStr, locale);
|
||||
buildTagsFromClass(handlerMethod.getBeanType(), tags, tagsStr, locale);
|
||||
|
||||
if (!CollectionUtils.isEmpty(tagsStr))
|
||||
tagsStr = tagsStr.stream()
|
||||
.map(str -> propertyResolverUtils.resolve(str, locale))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (springdocTags.containsKey(handlerMethod)) {
|
||||
Tag tag = springdocTags.get(handlerMethod);
|
||||
tagsStr.add(tag.getName());
|
||||
if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
|
||||
openAPI.addTagsItem(tag);
|
||||
}
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(tagsStr)) {
|
||||
if (CollectionUtils.isEmpty(operation.getTags()))
|
||||
operation.setTags(new ArrayList<>(tagsStr));
|
||||
else {
|
||||
Set<String> operationTagsSet = new HashSet<>(operation.getTags());
|
||||
operationTagsSet.addAll(tagsStr);
|
||||
operation.getTags().clear();
|
||||
operation.getTags().addAll(operationTagsSet);
|
||||
}
|
||||
}
|
||||
|
||||
if (isAutoTagClasses(operation)) {
|
||||
|
||||
if (javadocProvider.isPresent()) {
|
||||
String description = javadocProvider.get().getClassJavadoc(handlerMethod.getBeanType());
|
||||
if (StringUtils.isNotBlank(description)) {
|
||||
Tag tag = new Tag();
|
||||
|
||||
// 自定义部分 修改使用java注释当tag名
|
||||
List<String> list = IoUtil.readLines(new StringReader(description), new ArrayList<>());
|
||||
// tag.setName(tagAutoName);
|
||||
tag.setName(list.get(0));
|
||||
operation.addTagsItem(list.get(0));
|
||||
|
||||
tag.setDescription(description);
|
||||
if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
|
||||
openAPI.addTagsItem(tag);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String tagAutoName = splitCamelCase(handlerMethod.getBeanType().getSimpleName());
|
||||
operation.addTagsItem(tagAutoName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(tags)) {
|
||||
// Existing tags
|
||||
List<Tag> openApiTags = openAPI.getTags();
|
||||
if (!CollectionUtils.isEmpty(openApiTags))
|
||||
tags.addAll(openApiTags);
|
||||
openAPI.setTags(new ArrayList<>(tags));
|
||||
}
|
||||
|
||||
// Handle SecurityRequirement at operation level
|
||||
io.swagger.v3.oas.annotations.security.SecurityRequirement[] securityRequirements = securityParser
|
||||
.getSecurityRequirements(handlerMethod);
|
||||
if (securityRequirements != null) {
|
||||
if (securityRequirements.length == 0)
|
||||
operation.setSecurity(Collections.emptyList());
|
||||
else
|
||||
securityParser.buildSecurityRequirement(securityRequirements, operation);
|
||||
}
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
private void buildTagsFromMethod(Method method, Set<Tag> tags, Set<String> tagsStr, Locale locale) {
|
||||
// method tags
|
||||
Set<Tags> tagsSet = AnnotatedElementUtils.findAllMergedAnnotations(method, Tags.class);
|
||||
Set<io.swagger.v3.oas.annotations.tags.Tag> methodTags = tagsSet.stream()
|
||||
.flatMap(x -> Stream.of(x.value()))
|
||||
.collect(Collectors.toSet());
|
||||
methodTags.addAll(AnnotatedElementUtils
|
||||
.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class));
|
||||
if (!CollectionUtils.isEmpty(methodTags)) {
|
||||
tagsStr.addAll(toSet(methodTags, tag -> propertyResolverUtils.resolve(tag.name(), locale)));
|
||||
List<io.swagger.v3.oas.annotations.tags.Tag> allTags = new ArrayList<>(methodTags);
|
||||
addTags(allTags, tags, locale);
|
||||
}
|
||||
}
|
||||
|
||||
private void addTags(List<io.swagger.v3.oas.annotations.tags.Tag> sourceTags, Set<Tag> tags, Locale locale) {
|
||||
Optional<Set<Tag>> optionalTagSet = AnnotationsUtils.getTags(sourceTags
|
||||
.toArray(new io.swagger.v3.oas.annotations.tags.Tag[0]), true);
|
||||
optionalTagSet.ifPresent(tagsSet -> {
|
||||
tagsSet.forEach(tag -> {
|
||||
tag.name(propertyResolverUtils.resolve(tag.getName(), locale));
|
||||
tag.description(propertyResolverUtils.resolve(tag.getDescription(), locale));
|
||||
if (tags.stream().noneMatch(t -> t.getName().equals(tag.getName())))
|
||||
tags.add(tag);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 将collection转化为Set集合,但是两者的泛型不同<br>
|
||||
* <B>{@code Collection<E> ------> Set<T> } </B>
|
||||
*
|
||||
* @param collection 需要转化的集合
|
||||
* @param function collection中的泛型转化为set泛型的lambda表达式
|
||||
* @param <E> collection中的泛型
|
||||
* @param <T> Set中的泛型
|
||||
* @return 转化后的Set
|
||||
*/
|
||||
public static <E, T> Set<T> toSet(Collection<E> collection, Function<E, T> function) {
|
||||
if (CollUtil.isEmpty(collection) || function == null) {
|
||||
return CollUtil.newHashSet();
|
||||
}
|
||||
return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.apidoc.util;
|
||||
|
||||
import top.continew.starter.core.enums.BaseEnum;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* 枚举类型工具
|
||||
*
|
||||
* @author echo
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class EnumTypeUtils {
|
||||
|
||||
private EnumTypeUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取枚举值类型
|
||||
*
|
||||
* @param enumClass 枚举类型
|
||||
* @return 枚举值类型
|
||||
*/
|
||||
public static String getEnumValueTypeAsString(Class<?> enumClass) {
|
||||
try {
|
||||
// 获取枚举类实现的所有接口
|
||||
Type[] interfaces = enumClass.getGenericInterfaces();
|
||||
// 遍历所有接口
|
||||
for (Type type : interfaces) {
|
||||
// 检查接口是否为参数化类型
|
||||
if (type instanceof ParameterizedType parameterizedType) {
|
||||
// 检查接口的原始类型是否为 BaseEnum
|
||||
if (parameterizedType.getRawType() != BaseEnum.class) {
|
||||
continue;
|
||||
}
|
||||
Type actualType = parameterizedType.getActualTypeArguments()[0];
|
||||
// 检查实际类型参数是否为类类型
|
||||
if (actualType instanceof Class<?> actualClass) {
|
||||
if (actualClass == Integer.class) {
|
||||
return "integer";
|
||||
} else if (actualClass == Long.class) {
|
||||
return "long";
|
||||
} else if (actualClass == Double.class) {
|
||||
return "number";
|
||||
} else if (actualClass == String.class) {
|
||||
return "string";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// ignored
|
||||
}
|
||||
return "string";
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,6 @@ import top.continew.starter.core.util.GeneralPropertySourceFactory;
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ComponentScan("top.continew.starter.auth.satoken.exception")
|
||||
@EnableConfigurationProperties(SaTokenExtensionProperties.class)
|
||||
@ConditionalOnProperty(prefix = "sa-token.extension", name = PropertiesConstants.ENABLED, havingValue = "true")
|
||||
@PropertySource(value = "classpath:default-auth-satoken.yml", factory = GeneralPropertySourceFactory.class)
|
||||
|
||||
@@ -1,72 +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.auth.satoken.exception;
|
||||
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.exception.NotPermissionException;
|
||||
import cn.dev33.satoken.exception.NotRoleException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import top.continew.starter.web.model.R;
|
||||
|
||||
/**
|
||||
* 全局 SaToken 异常处理器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalSaTokenExceptionHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalSaTokenExceptionHandler.class);
|
||||
|
||||
/**
|
||||
* 认证异常-登录认证
|
||||
*/
|
||||
@ExceptionHandler(NotLoginException.class)
|
||||
public R<Void> handleNotLoginException(NotLoginException e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],认证失败,无法访问系统资源。", request.getRequestURI(), e);
|
||||
String errorMsg = switch (e.getType()) {
|
||||
case NotLoginException.KICK_OUT -> "您已被踢下线。";
|
||||
case NotLoginException.BE_REPLACED_MESSAGE -> "您已被顶下线。";
|
||||
default -> "您的登录状态已过期,请重新登录。";
|
||||
};
|
||||
return R.fail(HttpStatus.UNAUTHORIZED.value(), errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证异常-权限认证
|
||||
*/
|
||||
@ExceptionHandler(NotPermissionException.class)
|
||||
public R<Void> handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],权限码校验失败。", request.getRequestURI(), e);
|
||||
return R.fail(HttpStatus.FORBIDDEN.value(), "没有访问权限,请联系管理员授权");
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证异常-角色认证
|
||||
*/
|
||||
@ExceptionHandler(NotRoleException.class)
|
||||
public R<Void> handleNotRoleException(NotRoleException e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],角色权限校验失败。", request.getRequestURI(), e);
|
||||
return R.fail(HttpStatus.FORBIDDEN.value(), "没有访问权限,请联系管理员授权");
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,8 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.task.TaskExecutorCustomizer;
|
||||
import org.springframework.boot.task.TaskSchedulerCustomizer;
|
||||
import org.springframework.boot.task.ThreadPoolTaskExecutorCustomizer;
|
||||
import org.springframework.boot.task.ThreadPoolTaskSchedulerCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
@@ -53,7 +53,7 @@ public class ThreadPoolAutoConfiguration {
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "spring.task.execution.extension", name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
public TaskExecutorCustomizer taskExecutorCustomizer(ThreadPoolExtensionProperties properties) {
|
||||
public ThreadPoolTaskExecutorCustomizer threadPoolTaskExecutorCustomizer(ThreadPoolExtensionProperties properties) {
|
||||
return executor -> {
|
||||
// 核心(最小)线程数
|
||||
executor.setCorePoolSize(corePoolSize);
|
||||
@@ -74,7 +74,7 @@ public class ThreadPoolAutoConfiguration {
|
||||
@ConditionalOnProperty(prefix = "spring.task.scheduling.extension", name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
public static class TaskSchedulerConfiguration {
|
||||
@Bean
|
||||
public TaskSchedulerCustomizer taskSchedulerCustomizer(ThreadPoolExtensionProperties properties) {
|
||||
public ThreadPoolTaskSchedulerCustomizer threadPoolTaskSchedulerCustomizer(ThreadPoolExtensionProperties properties) {
|
||||
return executor -> {
|
||||
executor.setRejectedExecutionHandler(properties.getScheduling()
|
||||
.getRejectedPolicy()
|
||||
|
||||
@@ -64,6 +64,11 @@ public class PropertiesConstants {
|
||||
*/
|
||||
public static final String WEB_CORS = WEB + StringConstants.DOT + "cors";
|
||||
|
||||
/**
|
||||
* 响应配置
|
||||
*/
|
||||
public static final String WEB_RESPONSE = WEB + StringConstants.DOT + "response";
|
||||
|
||||
/**
|
||||
* 链路配置
|
||||
*/
|
||||
@@ -74,11 +79,6 @@ public class PropertiesConstants {
|
||||
*/
|
||||
public static final String WEB_XSS = WEB + StringConstants.DOT + "xss";
|
||||
|
||||
/**
|
||||
* 国际化配置
|
||||
*/
|
||||
public static final String WEB_I18N = WEB + StringConstants.DOT + "i18n";
|
||||
|
||||
/**
|
||||
* 日志配置
|
||||
*/
|
||||
|
||||
@@ -14,9 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.data.mybatis.plus.base;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
package top.continew.starter.core.enums;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@@ -27,7 +25,14 @@ import java.io.Serializable;
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface IBaseEnum<T extends Serializable> extends IEnum<T> {
|
||||
public interface BaseEnum<T extends Serializable> {
|
||||
|
||||
/**
|
||||
* 枚举值
|
||||
*
|
||||
* @return 枚举值
|
||||
*/
|
||||
T getValue();
|
||||
|
||||
/**
|
||||
* 枚举描述
|
||||
@@ -1,43 +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.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;
|
||||
}
|
||||
}
|
||||
@@ -1,66 +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.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;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +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.exception;
|
||||
|
||||
/**
|
||||
* 接口返回码与消息 所有业务异常都要继承该接口
|
||||
*
|
||||
* @author Jasmine
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public interface ResultInfoInterface {
|
||||
|
||||
/**
|
||||
* 获取编码
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
int getCode();
|
||||
|
||||
/**
|
||||
* 国际化消息key
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
default String getMessageKey() {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认消息 若从国际化文件里没有获取到值,就取默认值
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getDefaultMessage();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,14 +16,9 @@
|
||||
|
||||
package top.continew.starter.core.util.validate;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import top.continew.starter.core.exception.BadRequestException;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
/**
|
||||
@@ -178,21 +173,4 @@ public class ValidationUtils extends Validator {
|
||||
public static void throwIf(BooleanSupplier conditionSupplier, String template, Object... params) {
|
||||
throwIf(conditionSupplier, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* JSR 303 校验
|
||||
*
|
||||
* @param obj 被校验对象
|
||||
* @param groups 分组
|
||||
*/
|
||||
public static void validate(Object obj, Class<?>... groups) {
|
||||
jakarta.validation.Validator validator = SpringUtil.getBean(jakarta.validation.Validator.class);
|
||||
Set<ConstraintViolation<Object>> violations = validator.validate(obj, groups);
|
||||
if (CollUtil.isEmpty(violations)) {
|
||||
return;
|
||||
}
|
||||
throw ReflectUtil.newInstance(EXCEPTION_TYPE, violations.stream()
|
||||
.map(ConstraintViolation::getMessage)
|
||||
.findFirst());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,13 @@ package top.continew.starter.core.util.validate;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
/**
|
||||
@@ -32,6 +36,8 @@ import java.util.function.BooleanSupplier;
|
||||
*/
|
||||
public class Validator {
|
||||
private static final Logger log = LoggerFactory.getLogger(Validator.class);
|
||||
private static final jakarta.validation.Validator VALIDATOR = SpringUtil
|
||||
.getBean(jakarta.validation.Validator.class);
|
||||
|
||||
protected Validator() {
|
||||
}
|
||||
@@ -195,4 +201,18 @@ public class Validator {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
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.AsyncAutoConfiguration
|
||||
@@ -17,6 +17,7 @@
|
||||
package top.continew.starter.data.mybatis.plus.autoconfigure;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusPropertiesCustomizer;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
|
||||
@@ -40,6 +41,7 @@ import top.continew.starter.core.util.GeneralPropertySourceFactory;
|
||||
import top.continew.starter.data.mybatis.plus.autoconfigure.idgenerator.MyBatisPlusIdGeneratorConfiguration;
|
||||
import top.continew.starter.data.mybatis.plus.datapermission.DataPermissionFilter;
|
||||
import top.continew.starter.data.mybatis.plus.datapermission.DataPermissionHandlerImpl;
|
||||
import top.continew.starter.data.mybatis.plus.handler.MybatisBaseEnumTypeHandler;
|
||||
|
||||
/**
|
||||
* MyBatis Plus 自动配置
|
||||
@@ -57,6 +59,16 @@ public class MybatisPlusAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MybatisPlusAutoConfiguration.class);
|
||||
|
||||
/**
|
||||
* MyBatis Plus 配置
|
||||
*
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusPropertiesCustomizer mybatisPlusPropertiesCustomizer() {
|
||||
return properties -> properties.getConfiguration().setDefaultEnumTypeHandler(MybatisBaseEnumTypeHandler.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* MyBatis Plus 插件配置
|
||||
*/
|
||||
@@ -89,8 +101,7 @@ public class MybatisPlusAutoConfiguration {
|
||||
@Configuration
|
||||
@Import({MyBatisPlusIdGeneratorConfiguration.Default.class, MyBatisPlusIdGeneratorConfiguration.CosId.class,
|
||||
MyBatisPlusIdGeneratorConfiguration.Custom.class})
|
||||
protected static class MyBatisPlusIdGeneratorAutoConfiguration {
|
||||
}
|
||||
protected static class MyBatisPlusIdGeneratorAutoConfiguration {}
|
||||
|
||||
/**
|
||||
* 数据权限处理器
|
||||
|
||||
@@ -30,9 +30,7 @@ import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
|
||||
import net.sf.jsqlparser.expression.operators.relational.InExpression;
|
||||
import net.sf.jsqlparser.schema.Column;
|
||||
import net.sf.jsqlparser.schema.Table;
|
||||
import net.sf.jsqlparser.statement.select.PlainSelect;
|
||||
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
|
||||
import net.sf.jsqlparser.statement.select.SubSelect;
|
||||
import net.sf.jsqlparser.statement.select.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
@@ -124,9 +122,9 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
|
||||
private Expression buildDeptAndChildExpression(DataPermission dataPermission,
|
||||
DataPermissionCurrentUser currentUser,
|
||||
Expression expression) {
|
||||
SubSelect subSelect = new SubSelect();
|
||||
ParenthesedSelect subSelect = new ParenthesedSelect();
|
||||
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()));
|
||||
EqualsTo equalsTo = new EqualsTo();
|
||||
equalsTo.setLeftExpression(new Column(dataPermission.id()));
|
||||
@@ -135,7 +133,7 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
|
||||
function.setName("find_in_set");
|
||||
function.setParameters(new ExpressionList(new LongValue(currentUser.getDeptId()), new Column("ancestors")));
|
||||
select.setWhere(new OrExpression(equalsTo, function));
|
||||
subSelect.setSelectBody(select);
|
||||
subSelect.setSelect(select);
|
||||
// 构建父查询
|
||||
InExpression inExpression = new InExpression();
|
||||
inExpression.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.deptId()));
|
||||
@@ -201,15 +199,15 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
|
||||
private Expression buildCustomExpression(DataPermission dataPermission,
|
||||
DataPermissionCurrentUser.CurrentUserRole role,
|
||||
Expression expression) {
|
||||
SubSelect subSelect = new SubSelect();
|
||||
ParenthesedSelect subSelect = new ParenthesedSelect();
|
||||
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()));
|
||||
EqualsTo equalsTo = new EqualsTo();
|
||||
equalsTo.setLeftExpression(new Column(dataPermission.roleId()));
|
||||
equalsTo.setRightExpression(new LongValue(role.getRoleId()));
|
||||
select.setWhere(equalsTo);
|
||||
subSelect.setSelectBody(select);
|
||||
subSelect.setSelect(select);
|
||||
// 构建父查询
|
||||
InExpression inExpression = new InExpression();
|
||||
inExpression.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.deptId()));
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* 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.data.mybatis.plus.handler;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||
import org.apache.ibatis.reflection.DefaultReflectorFactory;
|
||||
import org.apache.ibatis.reflection.MetaClass;
|
||||
import org.apache.ibatis.reflection.ReflectorFactory;
|
||||
import org.apache.ibatis.reflection.invoker.Invoker;
|
||||
import org.apache.ibatis.type.BaseTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
import top.continew.starter.core.enums.BaseEnum;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 自定义枚举属性转换器
|
||||
*
|
||||
* @author hubin
|
||||
* @author Charles7c
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class MybatisBaseEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
|
||||
|
||||
private static final Map<String, String> TABLE_METHOD_OF_ENUM_TYPES = new ConcurrentHashMap<>();
|
||||
private static final ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();
|
||||
private final Class<E> enumClassType;
|
||||
private final Class<?> propertyType;
|
||||
private final Invoker getInvoker;
|
||||
|
||||
public MybatisBaseEnumTypeHandler(Class<E> enumClassType) {
|
||||
if (enumClassType == null) {
|
||||
throw new IllegalArgumentException("Type argument cannot be null");
|
||||
}
|
||||
this.enumClassType = enumClassType;
|
||||
MetaClass metaClass = MetaClass.forClass(enumClassType, REFLECTOR_FACTORY);
|
||||
String name = "value";
|
||||
if (!BaseEnum.class.isAssignableFrom(enumClassType) && !IEnum.class.isAssignableFrom(enumClassType)) {
|
||||
name = findEnumValueFieldName(this.enumClassType).orElseThrow(() -> new IllegalArgumentException(String
|
||||
.format("Could not find @EnumValue in Class: %s.", this.enumClassType.getName())));
|
||||
}
|
||||
this.propertyType = ReflectionKit.resolvePrimitiveIfNecessary(metaClass.getGetterType(name));
|
||||
this.getInvoker = metaClass.getGetInvoker(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找标记标记EnumValue字段
|
||||
*
|
||||
* @param clazz class
|
||||
* @return EnumValue字段
|
||||
*/
|
||||
public static Optional<String> findEnumValueFieldName(Class<?> clazz) {
|
||||
if (clazz != null && clazz.isEnum()) {
|
||||
String className = clazz.getName();
|
||||
return Optional.ofNullable(CollectionUtils.computeIfAbsent(TABLE_METHOD_OF_ENUM_TYPES, className, key -> {
|
||||
Optional<Field> fieldOptional = findEnumValueAnnotationField(clazz);
|
||||
return fieldOptional.map(Field::getName).orElse(null);
|
||||
}));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Optional<Field> findEnumValueAnnotationField(Class<?> clazz) {
|
||||
return Arrays.stream(clazz.getDeclaredFields())
|
||||
.filter(field -> field.isAnnotationPresent(EnumValue.class))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为MP枚举处理
|
||||
*
|
||||
* @param clazz class
|
||||
* @return 是否为MP枚举处理
|
||||
*/
|
||||
public static boolean isMpEnums(Class<?> clazz) {
|
||||
return clazz != null && clazz.isEnum() && (BaseEnum.class.isAssignableFrom(clazz) || IEnum.class
|
||||
.isAssignableFrom(clazz) || findEnumValueFieldName(clazz).isPresent());
|
||||
}
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
@Override
|
||||
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
|
||||
if (jdbcType == null) {
|
||||
ps.setObject(i, this.getValue(parameter));
|
||||
} else {
|
||||
// see r3589
|
||||
ps.setObject(i, this.getValue(parameter), jdbcType.TYPE_CODE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||
Object value = rs.getObject(columnName, this.propertyType);
|
||||
if (null == value || rs.wasNull()) {
|
||||
return null;
|
||||
}
|
||||
return this.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||
Object value = rs.getObject(columnIndex, this.propertyType);
|
||||
if (null == value || rs.wasNull()) {
|
||||
return null;
|
||||
}
|
||||
return this.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||
Object value = cs.getObject(columnIndex, this.propertyType);
|
||||
if (null == value || cs.wasNull()) {
|
||||
return null;
|
||||
}
|
||||
return this.valueOf(value);
|
||||
}
|
||||
|
||||
private E valueOf(Object value) {
|
||||
E[] es = this.enumClassType.getEnumConstants();
|
||||
return Arrays.stream(es).filter(e -> equalsValue(value, getValue(e))).findAny().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 值比较
|
||||
*
|
||||
* @param sourceValue 数据库字段值
|
||||
* @param targetValue 当前枚举属性值
|
||||
* @return 是否匹配
|
||||
*/
|
||||
private boolean equalsValue(Object sourceValue, Object targetValue) {
|
||||
String sValue = StringUtils.toStringTrim(sourceValue);
|
||||
String tValue = StringUtils.toStringTrim(targetValue);
|
||||
if (sourceValue instanceof Number && targetValue instanceof Number && new BigDecimal(sValue)
|
||||
.compareTo(new BigDecimal(tValue)) == 0) {
|
||||
return true;
|
||||
}
|
||||
return Objects.equals(sValue, tValue);
|
||||
}
|
||||
|
||||
private Object getValue(Object object) {
|
||||
try {
|
||||
return this.getInvoker.invoke(object, new Object[0]);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw ExceptionUtils.mpe(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
protected final List<Field> entityFields = ReflectUtils.getNonStaticFields(this.entityClass);
|
||||
private List<Field> entityFields;
|
||||
|
||||
@Override
|
||||
public T getById(Serializable id) {
|
||||
return this.getById(id, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前实体类型字段
|
||||
*
|
||||
* @return 当前实体类型字段列表
|
||||
*/
|
||||
public List<Field> getEntityFields() {
|
||||
if (this.entityFields == null) {
|
||||
this.entityFields = ReflectUtils.getNonStaticFields(this.getEntityClass());
|
||||
}
|
||||
return this.entityFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 ID 查询
|
||||
*
|
||||
@@ -53,7 +65,7 @@ public class ServiceImpl<M extends BaseMapper<T>, T> extends com.baomidou.mybati
|
||||
protected T getById(Serializable id, boolean isCheckExists) {
|
||||
T entity = baseMapper.selectById(id);
|
||||
if (isCheckExists) {
|
||||
CheckUtils.throwIfNotExists(entity, ClassUtil.getClassName(entityClass, true), "ID", id);
|
||||
CheckUtils.throwIfNotExists(entity, ClassUtil.getClassName(this.getEntityClass(), true), "ID", id);
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>3.1.11</version>
|
||||
<version>3.2.7</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
@@ -43,46 +43,64 @@
|
||||
|
||||
<properties>
|
||||
<!-- 项目版本号 -->
|
||||
<revision>2.2.0</revision>
|
||||
<revision>2.5.0</revision>
|
||||
<snail-job.version>1.1.0</snail-job.version>
|
||||
<sa-token.version>1.38.0</sa-token.version>
|
||||
<just-auth.version>1.16.6</just-auth.version>
|
||||
<mybatis-plus.version>3.5.5</mybatis-plus.version>
|
||||
<mybatis-flex.version>1.8.9</mybatis-flex.version>
|
||||
<dynamic-datasource.version>4.3.0</dynamic-datasource.version>
|
||||
<mybatis-plus.version>3.5.7</mybatis-plus.version>
|
||||
<mybatis-flex.version>1.9.3</mybatis-flex.version>
|
||||
<dynamic-datasource.version>4.3.1</dynamic-datasource.version>
|
||||
<p6spy.version>3.9.1</p6spy.version>
|
||||
<jetcache.version>2.7.5</jetcache.version>
|
||||
<redisson.version>3.30.0</redisson.version>
|
||||
<cosid.version>2.6.8</cosid.version>
|
||||
<jetcache.version>2.7.6</jetcache.version>
|
||||
<redisson.version>3.32.0</redisson.version>
|
||||
<cosid.version>2.9.1</cosid.version>
|
||||
<sms4j.version>3.2.1</sms4j.version>
|
||||
<aj-captcha.version>1.3.0</aj-captcha.version>
|
||||
<easy-captcha.version>1.6.2</easy-captcha.version>
|
||||
<easy-excel.version>3.3.4</easy-excel.version>
|
||||
<nashorn.version>15.4</nashorn.version>
|
||||
<x-file-storage.version>2.1.0</x-file-storage.version>
|
||||
<aws-s3.version>1.12.720</aws-s3.version>
|
||||
<crane4j.version>2.8.0</crane4j.version>
|
||||
<x-file-storage.version>2.2.0</x-file-storage.version>
|
||||
<aws-s3.version>1.12.761</aws-s3.version>
|
||||
<graceful-response.version>4.0.1-boot3</graceful-response.version>
|
||||
<crane4j.version>2.9.0</crane4j.version>
|
||||
<knife4j.version>4.5.0</knife4j.version>
|
||||
<tlog.version>1.5.2</tlog.version>
|
||||
<snakeyaml.version>2.2</snakeyaml.version>
|
||||
<okhttp.version>4.12.0</okhttp.version>
|
||||
<ttl.version>2.14.5</ttl.version>
|
||||
<ip2region.version>3.1.11</ip2region.version>
|
||||
<hutool.version>5.8.27</hutool.version>
|
||||
<ip2region.version>3.2.6</ip2region.version>
|
||||
<hutool.version>5.8.29</hutool.version>
|
||||
<!-- Maven Plugin Versions -->
|
||||
<flatten.version>1.6.0</flatten.version>
|
||||
<spotless.version>2.43.0</spotless.version>
|
||||
<sonar.version>3.9.1.2184</sonar.version>
|
||||
<sonar.version>3.11.0.3922</sonar.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<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 权限认证框架,让鉴权变得简单、优雅) -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot3-starter</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 整合 JWT -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
@@ -152,14 +170,12 @@
|
||||
<artifactId>jetcache-autoconfigure</artifactId>
|
||||
<version>${jetcache.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JetCache 注解 -->
|
||||
<dependency>
|
||||
<groupId>com.alicp.jetcache</groupId>
|
||||
<artifactId>jetcache-anno</artifactId>
|
||||
<version>${jetcache.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JetCache Redisson 适配 -->
|
||||
<dependency>
|
||||
<groupId>com.alicp.jetcache</groupId>
|
||||
@@ -246,6 +262,13 @@
|
||||
<version>${aws-s3.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Graceful Response(一个Spring Boot技术栈下的优雅响应处理组件,可以帮助开发者完成响应数据封装、异常处理、错误码填充等过程,提高开发效率,提高代码质量) -->
|
||||
<dependency>
|
||||
<groupId>com.feiniaojin</groupId>
|
||||
<artifactId>graceful-response</artifactId>
|
||||
<version>${graceful-response.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Crane4j(一个基于注解的,用于完成一切 “根据 A 的 key 值拿到 B,再把 B 的属性映射到 A” 这类需求的字段填充框架) -->
|
||||
<dependency>
|
||||
<groupId>cn.crane4j</groupId>
|
||||
@@ -431,10 +454,10 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 日志模块 - HttpTracePro(Spring Boot Actuator HttpTrace 定制增强版) -->
|
||||
<!-- 日志模块 - 拦截器版(Spring Boot Actuator HttpTrace 增强版) -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-log-httptrace-pro</artifactId>
|
||||
<artifactId>continew-starter-log-interceptor</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.extension.crud.model.resp;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* ID 响应信息
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public class BaseIdResp<T extends Serializable> implements Serializable {
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@Schema(description = "ID", example = "1")
|
||||
private T id;
|
||||
|
||||
public T getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(T id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
BaseIdResp(final T id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public static <T extends Serializable> BaseIdRespBuilder<T> builder() {
|
||||
return new BaseIdRespBuilder();
|
||||
}
|
||||
|
||||
public static class BaseIdRespBuilder<T extends Serializable> {
|
||||
private T id;
|
||||
|
||||
BaseIdRespBuilder() {
|
||||
}
|
||||
|
||||
public BaseIdRespBuilder<T> id(final T id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BaseIdResp<T> build() {
|
||||
return new BaseIdResp(this.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package top.continew.starter.extension.crud.controller;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.lang.tree.Tree;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import com.feiniaojin.gracefulresponse.api.ExcludeFromGracefulResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
@@ -29,13 +30,13 @@ import org.springframework.web.bind.annotation.*;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
|
||||
import top.continew.starter.extension.crud.enums.Api;
|
||||
import top.continew.starter.extension.crud.model.query.PageQuery;
|
||||
import top.continew.starter.extension.crud.model.query.SortQuery;
|
||||
import top.continew.starter.extension.crud.model.req.BaseReq;
|
||||
import top.continew.starter.extension.crud.model.resp.BaseIdResp;
|
||||
import top.continew.starter.extension.crud.model.resp.PageResp;
|
||||
import top.continew.starter.extension.crud.service.BaseService;
|
||||
import top.continew.starter.extension.crud.util.ValidateGroup;
|
||||
import top.continew.starter.extension.crud.model.query.PageQuery;
|
||||
import top.continew.starter.extension.crud.model.resp.PageResp;
|
||||
import top.continew.starter.web.model.R;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -65,9 +66,9 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
@Operation(summary = "分页查询列表", description = "分页查询列表")
|
||||
@ResponseBody
|
||||
@GetMapping
|
||||
public R<PageResp<L>> page(Q query, @Validated PageQuery pageQuery) {
|
||||
public PageResp<L> page(Q query, @Validated PageQuery pageQuery) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return R.ok(baseService.page(query, pageQuery));
|
||||
return baseService.page(query, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,9 +81,9 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
@Operation(summary = "查询树列表", description = "查询树列表")
|
||||
@ResponseBody
|
||||
@GetMapping("/tree")
|
||||
public R<List<Tree<Long>>> tree(Q query, SortQuery sortQuery) {
|
||||
public List<Tree<Long>> tree(Q query, SortQuery sortQuery) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return R.ok(baseService.tree(query, sortQuery, false));
|
||||
return baseService.tree(query, sortQuery, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,9 +96,9 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
@Operation(summary = "查询列表", description = "查询列表")
|
||||
@ResponseBody
|
||||
@GetMapping("/list")
|
||||
public R<List<L>> list(Q query, SortQuery sortQuery) {
|
||||
public List<L> list(Q query, SortQuery sortQuery) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return R.ok(baseService.list(query, sortQuery));
|
||||
return baseService.list(query, sortQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,9 +111,9 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
|
||||
@ResponseBody
|
||||
@GetMapping("/{id}")
|
||||
public R<D> get(@PathVariable Long id) {
|
||||
public D get(@PathVariable("id") Long id) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return R.ok(baseService.get(id));
|
||||
return baseService.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,9 +125,9 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
@Operation(summary = "新增数据", description = "新增数据")
|
||||
@ResponseBody
|
||||
@PostMapping
|
||||
public R<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody C req) {
|
||||
public BaseIdResp<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody C req) {
|
||||
this.checkPermission(Api.ADD);
|
||||
return R.ok("新增成功", baseService.add(req));
|
||||
return BaseIdResp.<Long>builder().id(baseService.add(req)).build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,32 +135,28 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
*
|
||||
* @param req 修改信息
|
||||
* @param id ID
|
||||
* @return /
|
||||
*/
|
||||
@Operation(summary = "修改数据", description = "修改数据")
|
||||
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
|
||||
@ResponseBody
|
||||
@PutMapping("/{id}")
|
||||
public R<Void> update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody C req, @PathVariable Long id) {
|
||||
public void update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody C req, @PathVariable("id") Long id) {
|
||||
this.checkPermission(Api.UPDATE);
|
||||
baseService.update(req, id);
|
||||
return R.ok("修改成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*
|
||||
* @param ids ID 列表
|
||||
* @return /
|
||||
*/
|
||||
@Operation(summary = "删除数据", description = "删除数据")
|
||||
@Parameter(name = "ids", description = "ID 列表", example = "1,2", in = ParameterIn.PATH)
|
||||
@ResponseBody
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> delete(@PathVariable List<Long> ids) {
|
||||
public void delete(@PathVariable("ids") List<Long> ids) {
|
||||
this.checkPermission(Api.DELETE);
|
||||
baseService.delete(ids);
|
||||
return R.ok("删除成功");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,6 +166,7 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
* @param sortQuery 排序查询条件
|
||||
* @param response 响应对象
|
||||
*/
|
||||
@ExcludeFromGracefulResponse
|
||||
@Operation(summary = "导出数据", description = "导出数据")
|
||||
@GetMapping("/export")
|
||||
public void export(Q query, SortQuery sortQuery, HttpServletResponse response) {
|
||||
|
||||
@@ -19,6 +19,7 @@ package top.continew.starter.extension.crud.controller;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.lang.tree.Tree;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import com.feiniaojin.gracefulresponse.api.ExcludeFromGracefulResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
@@ -31,11 +32,11 @@ import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
|
||||
import top.continew.starter.extension.crud.enums.Api;
|
||||
import top.continew.starter.extension.crud.model.query.SortQuery;
|
||||
import top.continew.starter.extension.crud.model.req.BaseReq;
|
||||
import top.continew.starter.extension.crud.model.resp.BaseIdResp;
|
||||
import top.continew.starter.extension.crud.util.ValidateGroup;
|
||||
import top.continew.starter.extension.crud.model.query.PageQuery;
|
||||
import top.continew.starter.extension.crud.model.resp.PageResp;
|
||||
import top.continew.starter.extension.crud.service.BaseService;
|
||||
import top.continew.starter.web.model.R;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -65,9 +66,9 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
@Operation(summary = "分页查询列表", description = "分页查询列表")
|
||||
@ResponseBody
|
||||
@GetMapping
|
||||
public R<PageResp<L>> page(Q query, @Validated PageQuery pageQuery) {
|
||||
public PageResp<L> page(Q query, @Validated PageQuery pageQuery) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return R.ok(baseService.page(query, pageQuery));
|
||||
return baseService.page(query, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,9 +81,9 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
@Operation(summary = "查询树列表", description = "查询树列表")
|
||||
@ResponseBody
|
||||
@GetMapping("/tree")
|
||||
public R<List<Tree<Long>>> tree(Q query, SortQuery sortQuery) {
|
||||
public List<Tree<Long>> tree(Q query, SortQuery sortQuery) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return R.ok(baseService.tree(query, sortQuery, false));
|
||||
return baseService.tree(query, sortQuery, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,9 +96,9 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
@Operation(summary = "查询列表", description = "查询列表")
|
||||
@ResponseBody
|
||||
@GetMapping("/list")
|
||||
public R<List<L>> list(Q query, SortQuery sortQuery) {
|
||||
public List<L> list(Q query, SortQuery sortQuery) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return R.ok(baseService.list(query, sortQuery));
|
||||
return baseService.list(query, sortQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,9 +111,9 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
|
||||
@ResponseBody
|
||||
@GetMapping("/{id}")
|
||||
public R<D> get(@PathVariable Long id) {
|
||||
public D get(@PathVariable("id") Long id) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return R.ok(baseService.get(id));
|
||||
return baseService.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,9 +125,9 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
@Operation(summary = "新增数据", description = "新增数据")
|
||||
@ResponseBody
|
||||
@PostMapping
|
||||
public R<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody C req) {
|
||||
public BaseIdResp<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody C req) {
|
||||
this.checkPermission(Api.ADD);
|
||||
return R.ok("新增成功", baseService.add(req));
|
||||
return BaseIdResp.<Long>builder().id(baseService.add(req)).build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,32 +135,28 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
*
|
||||
* @param req 修改信息
|
||||
* @param id ID
|
||||
* @return /
|
||||
*/
|
||||
@Operation(summary = "修改数据", description = "修改数据")
|
||||
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
|
||||
@ResponseBody
|
||||
@PutMapping("/{id}")
|
||||
public R<Void> update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody C req, @PathVariable Long id) {
|
||||
public void update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody C req, @PathVariable("id") Long id) {
|
||||
this.checkPermission(Api.UPDATE);
|
||||
baseService.update(req, id);
|
||||
return R.ok("修改成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*
|
||||
* @param ids ID 列表
|
||||
* @return /
|
||||
*/
|
||||
@Operation(summary = "删除数据", description = "删除数据")
|
||||
@Parameter(name = "ids", description = "ID 列表", example = "1,2", in = ParameterIn.PATH)
|
||||
@ResponseBody
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> delete(@PathVariable List<Long> ids) {
|
||||
public void delete(@PathVariable("ids") List<Long> ids) {
|
||||
this.checkPermission(Api.DELETE);
|
||||
baseService.delete(ids);
|
||||
return R.ok("删除成功");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,6 +166,7 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
* @param sortQuery 排序查询条件
|
||||
* @param response 响应对象
|
||||
*/
|
||||
@ExcludeFromGracefulResponse
|
||||
@Operation(summary = "导出数据", description = "导出数据")
|
||||
@GetMapping("/export")
|
||||
public void export(Q query, SortQuery sortQuery, HttpServletResponse response) {
|
||||
|
||||
@@ -1,91 +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.extension.crud.converter;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import com.alibaba.excel.converters.Converter;
|
||||
import com.alibaba.excel.enums.CellDataTypeEnum;
|
||||
import com.alibaba.excel.metadata.GlobalConfiguration;
|
||||
import com.alibaba.excel.metadata.data.ReadCellData;
|
||||
import com.alibaba.excel.metadata.data.WriteCellData;
|
||||
import com.alibaba.excel.metadata.property.ExcelContentProperty;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.data.mybatis.plus.base.IBaseEnum;
|
||||
|
||||
/**
|
||||
* Easy Excel 枚举接口转换器
|
||||
*
|
||||
* @see IBaseEnum
|
||||
* @author Charles7c
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class ExcelBaseEnumConverter implements Converter<IBaseEnum<Integer>> {
|
||||
|
||||
@Override
|
||||
public Class<IBaseEnum> supportJavaTypeKey() {
|
||||
return IBaseEnum.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CellDataTypeEnum supportExcelTypeKey() {
|
||||
return CellDataTypeEnum.STRING;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 Java 数据(读取 Excel)
|
||||
*/
|
||||
@Override
|
||||
public IBaseEnum convertToJavaData(ReadCellData<?> cellData,
|
||||
ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
return this.getEnum(IBaseEnum.class, Convert.toStr(cellData.getData()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 Excel 数据(写入 Excel)
|
||||
*/
|
||||
@Override
|
||||
public WriteCellData<String> convertToExcelData(IBaseEnum<Integer> value,
|
||||
ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
if (null == value) {
|
||||
return new WriteCellData<>(StringConstants.EMPTY);
|
||||
}
|
||||
return new WriteCellData<>(value.getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 value 获取枚举对象,获取不到时为 {@code null}
|
||||
*
|
||||
* @param enumType 枚举类型
|
||||
* @param description 描述
|
||||
* @return 对应枚举 ,获取不到时为 {@code null}
|
||||
*/
|
||||
private IBaseEnum<Integer> getEnum(Class<?> enumType, String description) {
|
||||
Object[] enumConstants = enumType.getEnumConstants();
|
||||
for (Object enumConstant : enumConstants) {
|
||||
if (ClassUtil.isAssignable(IBaseEnum.class, enumType)) {
|
||||
IBaseEnum<Integer> baseEnum = (IBaseEnum<Integer>)enumConstant;
|
||||
if (baseEnum.getDescription().equals(description)) {
|
||||
return baseEnum;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
private final Class<?>[] typeArgumentCache = ClassUtils.getTypeArguments(this.getClass());
|
||||
protected final Class<L> listClass = this.currentListClass();
|
||||
protected final Class<D> detailClass = this.currentDetailClass();
|
||||
protected final Class<Q> queryClass = this.currentQueryClass();
|
||||
private final List<Field> queryFields = ReflectUtils.getNonStaticFields(this.queryClass);
|
||||
private Class<L> listClass;
|
||||
private Class<D> detailClass;
|
||||
private Class<Q> queryClass;
|
||||
private List<Field> queryFields;
|
||||
|
||||
@Override
|
||||
public PageResp<L> page(Q query, PageQuery pageQuery) {
|
||||
QueryWrapper<T> queryWrapper = this.buildQueryWrapper(query);
|
||||
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);
|
||||
return pageResp;
|
||||
}
|
||||
@@ -91,7 +90,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
|
||||
}
|
||||
// 如果构建简单树结构,则不包含基本树结构之外的扩展字段
|
||||
TreeNodeConfig treeNodeConfig = TreeUtils.DEFAULT_CONFIG;
|
||||
TreeField treeField = listClass.getDeclaredAnnotation(TreeField.class);
|
||||
TreeField treeField = this.getListClass().getDeclaredAnnotation(TreeField.class);
|
||||
if (!isSimple) {
|
||||
// 根据 @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.setWeight(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.weightKey())));
|
||||
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
|
||||
.parentIdKey(), treeField.nameKey(), treeField.weightKey(), treeField.childrenKey()));
|
||||
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
|
||||
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);
|
||||
return list;
|
||||
}
|
||||
@@ -123,7 +122,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
|
||||
@Override
|
||||
public D get(Long id) {
|
||||
T entity = super.getById(id, false);
|
||||
D detail = BeanUtil.toBean(entity, detailClass);
|
||||
D detail = BeanUtil.toBean(entity, this.getDetailClass());
|
||||
this.fill(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) {
|
||||
QueryWrapper<T> queryWrapper = this.buildQueryWrapper(query);
|
||||
this.sort(queryWrapper, sortQuery);
|
||||
DictField dictField = entityClass.getDeclaredAnnotation(DictField.class);
|
||||
DictField dictField = super.getEntityClass().getDeclaredAnnotation(DictField.class);
|
||||
CheckUtils.throwIfNull(dictField, "请添加并配置 @DictField 字典结构信息");
|
||||
// 指定查询字典字段
|
||||
queryWrapper.select(dictField.labelKey(), dictField.valueKey());
|
||||
List<T> entityList = baseMapper.selectList(queryWrapper);
|
||||
// 解析映射
|
||||
Map<String, String> fieldMapping = MapUtil.newHashMap(2);
|
||||
fieldMapping.put(dictField.labelKey(), "label");
|
||||
fieldMapping.put(dictField.valueKey(), "value");
|
||||
fieldMapping.put(CharSequenceUtil.toCamelCase(dictField.labelKey()), "label");
|
||||
fieldMapping.put(CharSequenceUtil.toCamelCase(dictField.valueKey()), "value");
|
||||
return BeanUtil.copyToList(entityList, LabelValueResp.class, CopyOptions.create()
|
||||
.setFieldMapping(fieldMapping));
|
||||
}
|
||||
@@ -149,7 +148,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long add(C req) {
|
||||
this.beforeAdd(req);
|
||||
T entity = BeanUtil.copyProperties(req, entityClass);
|
||||
T entity = BeanUtil.copyProperties(req, super.getEntityClass());
|
||||
baseMapper.insert(entity);
|
||||
this.afterAdd(req, entity);
|
||||
return entity.getId();
|
||||
@@ -169,15 +168,63 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(List<Long> ids) {
|
||||
this.beforeDelete(ids);
|
||||
baseMapper.deleteBatchIds(ids);
|
||||
baseMapper.deleteByIds(ids);
|
||||
this.afterDelete(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
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);
|
||||
List<T> entityList = baseMapper.selectList(queryWrapper);
|
||||
if (entityClass == targetClass) {
|
||||
if (super.getEntityClass() == targetClass) {
|
||||
return (List<E>)entityList;
|
||||
}
|
||||
return BeanUtil.copyToList(entityList, targetClass);
|
||||
@@ -217,7 +264,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
|
||||
} else {
|
||||
checkProperty = property;
|
||||
}
|
||||
Optional<Field> optional = entityFields.stream()
|
||||
Optional<Field> optional = super.getEntityFields().stream()
|
||||
.filter(field -> checkProperty.equals(field.getName()))
|
||||
.findFirst();
|
||||
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) {
|
||||
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) {
|
||||
/* 删除后置处理 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前列表信息类型
|
||||
*
|
||||
* @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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.extension.crud.converter;
|
||||
package top.continew.starter.file.excel.converter;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
@@ -25,20 +25,20 @@ import com.alibaba.excel.metadata.data.ReadCellData;
|
||||
import com.alibaba.excel.metadata.data.WriteCellData;
|
||||
import com.alibaba.excel.metadata.property.ExcelContentProperty;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.data.mybatis.flex.base.IBaseEnum;
|
||||
import top.continew.starter.core.enums.BaseEnum;
|
||||
|
||||
/**
|
||||
* Easy Excel 枚举接口转换器
|
||||
*
|
||||
* @see IBaseEnum
|
||||
* @see BaseEnum
|
||||
* @author Charles7c
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class ExcelBaseEnumConverter implements Converter<IBaseEnum<Integer>> {
|
||||
public class ExcelBaseEnumConverter implements Converter<BaseEnum<Integer>> {
|
||||
|
||||
@Override
|
||||
public Class<IBaseEnum> supportJavaTypeKey() {
|
||||
return IBaseEnum.class;
|
||||
public Class<BaseEnum> supportJavaTypeKey() {
|
||||
return BaseEnum.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -50,17 +50,17 @@ public class ExcelBaseEnumConverter implements Converter<IBaseEnum<Integer>> {
|
||||
* 转换为 Java 数据(读取 Excel)
|
||||
*/
|
||||
@Override
|
||||
public IBaseEnum convertToJavaData(ReadCellData<?> cellData,
|
||||
ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
return this.getEnum(IBaseEnum.class, Convert.toStr(cellData.getData()));
|
||||
public BaseEnum convertToJavaData(ReadCellData<?> cellData,
|
||||
ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
return this.getEnum(BaseEnum.class, Convert.toStr(cellData.getData()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 Excel 数据(写入 Excel)
|
||||
*/
|
||||
@Override
|
||||
public WriteCellData<String> convertToExcelData(IBaseEnum<Integer> value,
|
||||
public WriteCellData<String> convertToExcelData(BaseEnum<Integer> value,
|
||||
ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
if (null == value) {
|
||||
@@ -76,11 +76,11 @@ public class ExcelBaseEnumConverter implements Converter<IBaseEnum<Integer>> {
|
||||
* @param description 描述
|
||||
* @return 对应枚举 ,获取不到时为 {@code null}
|
||||
*/
|
||||
private IBaseEnum<Integer> getEnum(Class<?> enumType, String description) {
|
||||
private BaseEnum<Integer> getEnum(Class<?> enumType, String description) {
|
||||
Object[] enumConstants = enumType.getEnumConstants();
|
||||
for (Object enumConstant : enumConstants) {
|
||||
if (ClassUtil.isAssignable(IBaseEnum.class, enumType)) {
|
||||
IBaseEnum<Integer> baseEnum = (IBaseEnum<Integer>)enumConstant;
|
||||
if (ClassUtil.isAssignable(BaseEnum.class, enumType)) {
|
||||
BaseEnum<Integer> baseEnum = (BaseEnum<Integer>)enumConstant;
|
||||
if (baseEnum.getDescription().equals(description)) {
|
||||
return baseEnum;
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
package top.continew.starter.json.jackson.autoconfigure;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
@@ -30,8 +31,12 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import top.continew.starter.core.enums.BaseEnum;
|
||||
import top.continew.starter.core.util.GeneralPropertySourceFactory;
|
||||
import top.continew.starter.json.jackson.serializer.BaseEnumDeserializer;
|
||||
import top.continew.starter.json.jackson.serializer.BaseEnumSerializer;
|
||||
import top.continew.starter.json.jackson.serializer.BigNumberSerializer;
|
||||
import top.continew.starter.json.jackson.serializer.SimpleDeserializersWrapper;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.time.LocalDate;
|
||||
@@ -54,26 +59,53 @@ public class JacksonAutoConfiguration {
|
||||
@Bean
|
||||
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
|
||||
return builder -> {
|
||||
// 针对大数值的序列化处理
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
javaTimeModule.addSerializer(Long.class, BigNumberSerializer.SERIALIZER_INSTANCE);
|
||||
javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.SERIALIZER_INSTANCE);
|
||||
javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.SERIALIZER_INSTANCE);
|
||||
// 针对时间类型:LocalDateTime 的序列化和反序列化处理
|
||||
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN);
|
||||
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter));
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter));
|
||||
// 针对时间类型:LocalDate 的序列化和反序列化处理
|
||||
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN);
|
||||
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(dateFormatter));
|
||||
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(dateFormatter));
|
||||
// 针对时间类型:LocalTime 的序列化和反序列化处理
|
||||
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN);
|
||||
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(timeFormatter));
|
||||
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(timeFormatter));
|
||||
JavaTimeModule javaTimeModule = this.timeModule();
|
||||
SimpleModule simpleModule = this.simpleModule();
|
||||
builder.timeZone(TimeZone.getDefault());
|
||||
builder.modules(javaTimeModule);
|
||||
builder.modules(javaTimeModule, simpleModule);
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Jackson' completed initialization.");
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期时间序列化及反序列化配置
|
||||
*
|
||||
* @return JavaTimeModule /
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private JavaTimeModule timeModule() {
|
||||
// 针对大数值的序列化处理
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
javaTimeModule.addSerializer(Long.class, BigNumberSerializer.SERIALIZER_INSTANCE);
|
||||
javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.SERIALIZER_INSTANCE);
|
||||
javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.SERIALIZER_INSTANCE);
|
||||
// 针对时间类型:LocalDateTime 的序列化和反序列化处理
|
||||
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN);
|
||||
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter));
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter));
|
||||
// 针对时间类型:LocalDate 的序列化和反序列化处理
|
||||
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN);
|
||||
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(dateFormatter));
|
||||
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(dateFormatter));
|
||||
// 针对时间类型:LocalTime 的序列化和反序列化处理
|
||||
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN);
|
||||
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(timeFormatter));
|
||||
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(timeFormatter));
|
||||
return javaTimeModule;
|
||||
}
|
||||
|
||||
/**
|
||||
* 枚举序列化及反序列化配置
|
||||
*
|
||||
* @return SimpleModule /
|
||||
* @since 2.4.0
|
||||
*/
|
||||
private SimpleModule simpleModule() {
|
||||
SimpleModule simpleModule = new SimpleModule();
|
||||
simpleModule.addSerializer(BaseEnum.class, BaseEnumSerializer.SERIALIZER_INSTANCE);
|
||||
SimpleDeserializersWrapper deserializers = new SimpleDeserializersWrapper();
|
||||
deserializers.addDeserializer(BaseEnum.class, BaseEnumDeserializer.SERIALIZER_INSTANCE);
|
||||
simpleModule.setDeserializers(deserializers);
|
||||
return simpleModule;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.json.jackson.serializer;
|
||||
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
|
||||
import top.continew.starter.core.enums.BaseEnum;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* 枚举接口 BaseEnum 反序列化器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @see BaseEnum
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@JacksonStdImpl
|
||||
public class BaseEnumDeserializer extends JsonDeserializer<BaseEnum> {
|
||||
|
||||
/**
|
||||
* 静态实例
|
||||
*/
|
||||
public static final BaseEnumDeserializer SERIALIZER_INSTANCE = new BaseEnumDeserializer();
|
||||
|
||||
@Override
|
||||
public BaseEnum deserialize(JsonParser jsonParser,
|
||||
DeserializationContext deserializationContext) throws IOException {
|
||||
Class<?> targetClass = jsonParser.getCurrentValue().getClass();
|
||||
String fieldName = jsonParser.getCurrentName();
|
||||
String value = jsonParser.getText();
|
||||
return this.getEnum(targetClass, value, fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过某字段对应值获取枚举实例,获取不到时为 {@code null}
|
||||
*
|
||||
* @param targetClass 目标类型
|
||||
* @param value 字段值
|
||||
* @param fieldName 字段名
|
||||
* @return 对应枚举实例 ,获取不到时为 {@code null}
|
||||
*/
|
||||
private BaseEnum getEnum(Class<?> targetClass, String value, String fieldName) {
|
||||
Field field = ReflectUtil.getField(targetClass, fieldName);
|
||||
Class<?> fieldTypeClass = field.getType();
|
||||
Object[] enumConstants = fieldTypeClass.getEnumConstants();
|
||||
for (Object enumConstant : enumConstants) {
|
||||
if (ClassUtil.isAssignable(BaseEnum.class, fieldTypeClass)) {
|
||||
BaseEnum baseEnum = (BaseEnum)enumConstant;
|
||||
if (baseEnum.getValue().equals(Integer.valueOf(value))) {
|
||||
return baseEnum;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.json.jackson.serializer;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
|
||||
import top.continew.starter.core.enums.BaseEnum;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 枚举接口 BaseEnum 序列化器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @see BaseEnum
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@JacksonStdImpl
|
||||
public class BaseEnumSerializer extends JsonSerializer<BaseEnum> {
|
||||
|
||||
/**
|
||||
* 静态实例
|
||||
*/
|
||||
public static final BaseEnumSerializer SERIALIZER_INSTANCE = new BaseEnumSerializer();
|
||||
|
||||
@Override
|
||||
public void serialize(BaseEnum value, JsonGenerator generator, SerializerProvider serializers) throws IOException {
|
||||
generator.writeObject(value.getValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.json.jackson.serializer;
|
||||
|
||||
import com.fasterxml.jackson.databind.BeanDescription;
|
||||
import com.fasterxml.jackson.databind.DeserializationConfig;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.module.SimpleDeserializers;
|
||||
import com.fasterxml.jackson.databind.type.ClassKey;
|
||||
|
||||
/**
|
||||
* 反序列化器包装类(重写 Jackson 反序列化枚举方法,参阅:FasterXML/jackson-databind#2842)
|
||||
*
|
||||
* <p>
|
||||
* 默认处理:<br>
|
||||
* 1. Jackson 会先查找指定枚举类型对应的反序列化器(例如:GenderEnum 枚举类型,则是找 GenderEnum 枚举类型的对应反序列化器);<br>
|
||||
* 2. 如果找不到则开始查找 Enum 类型(所有枚举父类)的反序列化器;<br>
|
||||
* 3. 如果都找不到则会采用默认的枚举反序列化器(它仅能根据枚举类型的 name、ordinal 来进行反序列化)。
|
||||
* </p>
|
||||
* <p>
|
||||
* 重写增强后:<br>
|
||||
* 1. 同默认 1;<br>
|
||||
* 2. 同默认 2;<br>
|
||||
* 3. 如果也找不到 Enum 类型(所有枚举父类)的反序列化器,开始查找指定枚举类型的接口的反序列化器(例如:GenderEnum 枚举类型,则是找它的接口 BaseEnum 的反序列化器);<br>
|
||||
* 4. 同默认 3。
|
||||
* </p>
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class SimpleDeserializersWrapper extends SimpleDeserializers {
|
||||
|
||||
@Override
|
||||
public JsonDeserializer<?> findEnumDeserializer(Class<?> type,
|
||||
DeserializationConfig config,
|
||||
BeanDescription beanDesc) throws JsonMappingException {
|
||||
JsonDeserializer<?> deser = super.findEnumDeserializer(type, config, beanDesc);
|
||||
if (null != deser) {
|
||||
return deser;
|
||||
}
|
||||
// 重写增强:开始查找指定枚举类型的接口的反序列化器(例如:GenderEnum 枚举类型,则是找它的接口 BaseEnum 的反序列化器)
|
||||
for (Class<?> typeInterface : type.getInterfaces()) {
|
||||
deser = this._classMappings.get(new ClassKey(typeInterface));
|
||||
if (null != deser) {
|
||||
return deser;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
top.continew.starter.log.httptracepro.autoconfigure.LogAutoConfiguration
|
||||
@@ -9,8 +9,8 @@
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-log-httptrace-pro</artifactId>
|
||||
<description>ContiNew Starter 日志模块 - HttpTracePro(Spring Boot Actuator HttpTrace 重置增强版)</description>
|
||||
<artifactId>continew-starter-log-interceptor</artifactId>
|
||||
<description>ContiNew Starter 日志模块 - 拦截器版(Spring Boot Actuator HttpTrace 增强版)</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Swagger 注解 -->
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.httptracepro.autoconfigure;
|
||||
package top.continew.starter.log.interceptor.autoconfigure;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.httptracepro.autoconfigure;
|
||||
package top.continew.starter.log.interceptor.autoconfigure;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
@@ -28,8 +28,8 @@ import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import top.continew.starter.log.core.dao.LogDao;
|
||||
import top.continew.starter.log.core.dao.impl.LogDaoDefaultImpl;
|
||||
import top.continew.starter.log.httptracepro.handler.LogFilter;
|
||||
import top.continew.starter.log.httptracepro.handler.LogInterceptor;
|
||||
import top.continew.starter.log.interceptor.handler.LogFilter;
|
||||
import top.continew.starter.log.interceptor.handler.LogInterceptor;
|
||||
|
||||
/**
|
||||
* 日志自动配置
|
||||
@@ -75,6 +75,6 @@ public class LogAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Log-HttpTracePro' completed initialization.");
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Log' completed initialization.");
|
||||
}
|
||||
}
|
||||
@@ -14,13 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.httptracepro.autoconfigure;
|
||||
package top.continew.starter.log.interceptor.autoconfigure;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.log.core.enums.Include;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -47,6 +49,11 @@ public class LogProperties {
|
||||
*/
|
||||
private Set<Include> includes = new HashSet<>(Include.defaultIncludes());
|
||||
|
||||
/**
|
||||
* 放行路由
|
||||
*/
|
||||
private List<String> excludePatterns = new ArrayList<>();
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
@@ -70,4 +77,12 @@ public class LogProperties {
|
||||
public void setIncludes(Set<Include> includes) {
|
||||
this.includes = includes;
|
||||
}
|
||||
|
||||
public List<String> getExcludePatterns() {
|
||||
return excludePatterns;
|
||||
}
|
||||
|
||||
public void setExcludePatterns(List<String> excludePatterns) {
|
||||
this.excludePatterns = excludePatterns;
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.httptracepro.handler;
|
||||
package top.continew.starter.log.interceptor.handler;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
@@ -27,7 +29,8 @@ import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
import top.continew.starter.log.core.enums.Include;
|
||||
import top.continew.starter.log.httptracepro.autoconfigure.LogProperties;
|
||||
import top.continew.starter.log.interceptor.autoconfigure.LogProperties;
|
||||
import top.continew.starter.web.util.SpringWebUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
@@ -63,26 +66,48 @@ public class LogFilter extends OncePerRequestFilter implements Ordered {
|
||||
protected void doFilterInternal(@NonNull HttpServletRequest request,
|
||||
@NonNull HttpServletResponse response,
|
||||
@NonNull FilterChain filterChain) throws ServletException, IOException {
|
||||
if (!isRequestValid(request)) {
|
||||
if (!this.isFilter(request)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
// 包装输入流,可重复读取
|
||||
if (isRequestWrapper(request)) {
|
||||
if (this.isRequestWrapper(request)) {
|
||||
request = new ContentCachingRequestWrapper(request);
|
||||
}
|
||||
// 包装输出流,可重复读取
|
||||
boolean isResponseWrapper = isResponseWrapper(response);
|
||||
boolean isResponseWrapper = this.isResponseWrapper(response);
|
||||
if (isResponseWrapper) {
|
||||
response = new ContentCachingResponseWrapper(response);
|
||||
}
|
||||
filterChain.doFilter(request, response);
|
||||
// 更新响应(不操作这一步,会导致接口响应空白)
|
||||
if (isResponseWrapper) {
|
||||
updateResponse(response);
|
||||
this.updateResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否过滤请求
|
||||
*
|
||||
* @param request 请求对象
|
||||
* @return 是否过滤请求
|
||||
*/
|
||||
private boolean isFilter(HttpServletRequest request) {
|
||||
if (!isRequestValid(request)) {
|
||||
return false;
|
||||
}
|
||||
// 不拦截 /error
|
||||
ServerProperties serverProperties = SpringUtil.getBean(ServerProperties.class);
|
||||
if (request.getRequestURI().equals(serverProperties.getError().getPath())) {
|
||||
return false;
|
||||
}
|
||||
// 放行
|
||||
boolean isMatch = logProperties.getExcludePatterns()
|
||||
.stream()
|
||||
.anyMatch(pattern -> SpringWebUtils.match(pattern, request.getRequestURI()));
|
||||
return !isMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求是否有效
|
||||
*
|
||||
@@ -14,10 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.httptracepro.handler;
|
||||
package top.continew.starter.log.interceptor.handler;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -26,7 +25,6 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
@@ -35,7 +33,7 @@ import top.continew.starter.log.core.dao.LogDao;
|
||||
import top.continew.starter.log.core.enums.Include;
|
||||
import top.continew.starter.log.core.model.LogRecord;
|
||||
import top.continew.starter.log.core.model.LogResponse;
|
||||
import top.continew.starter.log.httptracepro.autoconfigure.LogProperties;
|
||||
import top.continew.starter.log.interceptor.autoconfigure.LogProperties;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.util.Set;
|
||||
@@ -63,7 +61,7 @@ public class LogInterceptor implements HandlerInterceptor {
|
||||
@NonNull HttpServletResponse response,
|
||||
@NonNull Object handler) {
|
||||
Clock timestamp = Clock.systemUTC();
|
||||
if (this.isRequestRecord(handler, request)) {
|
||||
if (this.isRequestRecord(handler)) {
|
||||
if (Boolean.TRUE.equals(logProperties.getIsPrint())) {
|
||||
log.info("[{}] {}", request.getMethod(), request.getRequestURI());
|
||||
}
|
||||
@@ -194,18 +192,12 @@ public class LogInterceptor implements HandlerInterceptor {
|
||||
* 是否要记录日志
|
||||
*
|
||||
* @param handler 处理器
|
||||
* @param request 请求对象
|
||||
* @return true:需要记录;false:不需要记录
|
||||
*/
|
||||
private boolean isRequestRecord(Object handler, HttpServletRequest request) {
|
||||
private boolean isRequestRecord(Object handler) {
|
||||
if (!(handler instanceof HandlerMethod handlerMethod)) {
|
||||
return false;
|
||||
}
|
||||
// 不拦截 /error
|
||||
ServerProperties serverProperties = SpringUtil.getBean(ServerProperties.class);
|
||||
if (request.getRequestURI().equals(serverProperties.getError().getPath())) {
|
||||
return false;
|
||||
}
|
||||
// 如果接口被隐藏,不记录日志
|
||||
Operation methodOperation = handlerMethod.getMethodAnnotation(Operation.class);
|
||||
if (null != methodOperation && methodOperation.hidden()) {
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.httptracepro.handler;
|
||||
package top.continew.starter.log.interceptor.handler;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.httptracepro.handler;
|
||||
package top.continew.starter.log.interceptor.handler;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@@ -22,7 +22,6 @@ import cn.hutool.json.JSONUtil;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.log.core.model.RecordableHttpResponse;
|
||||
import top.continew.starter.web.util.ServletUtils;
|
||||
|
||||
@@ -60,9 +59,10 @@ public final class RecordableServletHttpResponse implements RecordableHttpRespon
|
||||
ContentCachingResponseWrapper wrapper = WebUtils
|
||||
.getNativeResponse(response, ContentCachingResponseWrapper.class);
|
||||
if (null != wrapper) {
|
||||
return StrUtil.utf8Str(wrapper.getContentAsByteArray());
|
||||
String body = StrUtil.utf8Str(wrapper.getContentAsByteArray());
|
||||
return JSONUtil.isTypeJSON(body) ? body : null;
|
||||
}
|
||||
return StringConstants.EMPTY;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -0,0 +1 @@
|
||||
top.continew.starter.log.interceptor.autoconfigure.LogAutoConfiguration
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
<modules>
|
||||
<module>continew-starter-log-core</module>
|
||||
<module>continew-starter-log-httptrace-pro</module>
|
||||
<module>continew-starter-log-interceptor</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -36,7 +36,7 @@ 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 top.continew.starter.web.util.SpringWebUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
@@ -171,7 +171,7 @@ public class RateLimiterAspect {
|
||||
}
|
||||
// 获取后缀
|
||||
String suffix = switch (rateLimiter.type()) {
|
||||
case IP -> JakartaServletUtil.getClientIP(ServletUtils.getRequest());
|
||||
case IP -> JakartaServletUtil.getClientIP(SpringWebUtils.getRequest());
|
||||
case CLUSTER -> redissonClient.getId();
|
||||
default -> StringConstants.EMPTY;
|
||||
};
|
||||
|
||||
@@ -44,6 +44,12 @@
|
||||
<artifactId>tlog-web-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Graceful Response(一个Spring Boot技术栈下的优雅响应处理组件,可以帮助开发者完成响应数据封装、异常处理、错误码填充等过程,提高开发效率,提高代码质量) -->
|
||||
<dependency>
|
||||
<groupId>com.feiniaojin</groupId>
|
||||
<artifactId>graceful-response</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- API 文档模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
package top.continew.starter.web.annotation;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
import top.continew.starter.web.autoconfigure.exception.GlobalExceptionHandlerAutoConfiguration;
|
||||
import top.continew.starter.web.autoconfigure.response.GlobalResponseAutoConfiguration;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 全局异常、错误处理器启用注解
|
||||
* 全局响应启用注解
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.2.0
|
||||
@@ -30,6 +30,7 @@ import java.lang.annotation.*;
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Import({GlobalExceptionHandlerAutoConfiguration.class})
|
||||
public @interface EnableGlobalExceptionHandler {
|
||||
@Inherited
|
||||
@Import({GlobalResponseAutoConfiguration.class})
|
||||
public @interface EnableGlobalResponse {
|
||||
}
|
||||
@@ -1,94 +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.web.autoconfigure.exception;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
|
||||
import org.springframework.boot.web.servlet.error.ErrorAttributes;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import top.continew.starter.web.model.R;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 全局错误处理器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@RestController
|
||||
public class GlobalErrorHandler extends BasicErrorController {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalErrorHandler.class);
|
||||
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
public GlobalErrorHandler(ErrorAttributes errorAttributes,
|
||||
ServerProperties serverProperties,
|
||||
List<ErrorViewResolver> errorViewResolvers) {
|
||||
super(errorAttributes, serverProperties.getError(), errorViewResolvers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
|
||||
Map<String, Object> errorAttributeMap = super.getErrorAttributes(request, super.getErrorAttributeOptions(request, MediaType.TEXT_HTML));
|
||||
String path = (String)errorAttributeMap.get("path");
|
||||
HttpStatus status = super.getStatus(request);
|
||||
R<Object> result = R.fail(status.value(), (String)errorAttributeMap.get("error"));
|
||||
result.setData(path);
|
||||
try {
|
||||
response.setStatus(HttpStatus.OK.value());
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
objectMapper.writeValue(response.getWriter(), result);
|
||||
} catch (IOException e) {
|
||||
log.error("请求地址 [{}],默认错误处理时发生 IO 异常。", path, e);
|
||||
}
|
||||
if (log.isErrorEnabled()) {
|
||||
log.error("请求地址 [{}],发生错误,错误信息:{}。", path, JSONUtil.toJsonStr(errorAttributeMap));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
|
||||
Map<String, Object> errorAttributeMap = super.getErrorAttributes(request, super.getErrorAttributeOptions(request, MediaType.ALL));
|
||||
String path = (String)errorAttributeMap.get("path");
|
||||
HttpStatus status = super.getStatus(request);
|
||||
R<Object> result = R.fail(status.value(), (String)errorAttributeMap.get("error"));
|
||||
result.setData(path);
|
||||
if (log.isErrorEnabled()) {
|
||||
log.error("请求地址 [{}],发生错误,错误信息:{}。", path, JSONUtil.toJsonStr(errorAttributeMap));
|
||||
}
|
||||
return new ResponseEntity<>(BeanUtil.beanToMap(result), HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
@@ -1,199 +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.web.autoconfigure.exception;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.support.DefaultMessageSourceResolvable;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
import org.springframework.web.multipart.MultipartException;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.exception.BadRequestException;
|
||||
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.util.MessageSourceUtils;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
private static final String PARAM_FAILED = "请求地址 [{}],参数验证失败。";
|
||||
@Resource
|
||||
private I18nProperties i18nProperties;
|
||||
|
||||
/**
|
||||
* 拦截自定义验证异常-错误请求
|
||||
*/
|
||||
@ExceptionHandler(BadRequestException.class)
|
||||
public R<Void> handleBadRequestException(BadRequestException e, HttpServletRequest request) {
|
||||
log.warn("请求地址 [{}],自定义验证失败。", request.getRequestURI(), e);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截校验异常-违反约束异常
|
||||
*/
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
public R<Void> constraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
|
||||
log.warn(PARAM_FAILED, request.getRequestURI(), e);
|
||||
String errorMsg = CollUtil.join(e
|
||||
.getConstraintViolations(), StringConstants.CHINESE_COMMA, ConstraintViolation::getMessage);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截校验异常-绑定异常
|
||||
*/
|
||||
@ExceptionHandler(BindException.class)
|
||||
public R<Void> handleBindException(BindException e, HttpServletRequest request) {
|
||||
log.warn(PARAM_FAILED, request.getRequestURI(), e);
|
||||
String errorMsg = CollUtil.join(e
|
||||
.getAllErrors(), StringConstants.CHINESE_COMMA, DefaultMessageSourceResolvable::getDefaultMessage);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截校验异常-方法参数无效异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public R<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e,
|
||||
HttpServletRequest request) {
|
||||
log.warn(PARAM_FAILED, request.getRequestURI(), e);
|
||||
String errorMsg = CollUtil.join(e
|
||||
.getAllErrors(), StringConstants.CHINESE_COMMA, DefaultMessageSourceResolvable::getDefaultMessage);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截校验异常-方法参数类型不匹配异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||
public R<Void> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e,
|
||||
HttpServletRequest request) {
|
||||
String errorMsg = CharSequenceUtil.format("参数名:[{}],期望参数类型:[{}]", e.getName(), e.getParameter()
|
||||
.getParameterType());
|
||||
log.warn("请求地址 [{}],参数转换失败,{}。", request.getRequestURI(), errorMsg, e);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截文件上传异常-超过上传大小限制
|
||||
*/
|
||||
@ExceptionHandler(MultipartException.class)
|
||||
public R<Void> handleRequestTooBigException(MultipartException e, HttpServletRequest request) {
|
||||
String msg = e.getMessage();
|
||||
R<Void> defaultFail = R.fail(HttpStatus.BAD_REQUEST.value(), msg);
|
||||
if (CharSequenceUtil.isBlank(msg)) {
|
||||
return defaultFail;
|
||||
}
|
||||
|
||||
String sizeLimit;
|
||||
Throwable cause = e.getCause();
|
||||
if (null != cause) {
|
||||
msg = msg.concat(cause.getMessage().toLowerCase());
|
||||
}
|
||||
if (msg.contains("size") && msg.contains("exceed")) {
|
||||
sizeLimit = CharSequenceUtil.subBetween(msg, "maximum (", ")");
|
||||
} else if (msg.contains("larger than")) {
|
||||
sizeLimit = CharSequenceUtil.subAfter(msg, "larger than ", true);
|
||||
} else {
|
||||
return defaultFail;
|
||||
}
|
||||
|
||||
String errorMsg = "请上传小于 %sMB 的文件".formatted(NumberUtil.parseLong(sizeLimit) / 1024 / 1024);
|
||||
log.warn("请求地址 [{}],上传文件失败,文件大小超过限制。", request.getRequestURI(), e);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截校验异常-请求方式不支持异常
|
||||
*/
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
public R<Void> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
|
||||
HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],不支持 [{}] 请求。", request.getRequestURI(), e.getMethod());
|
||||
return R.fail(HttpStatus.METHOD_NOT_ALLOWED.value(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截业务异常
|
||||
*/
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public R<Void> handleServiceException(BusinessException e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],发生业务异常。", request.getRequestURI(), e);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截未知的运行时异常
|
||||
*/
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public R<Void> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],发生系统异常。", request.getRequestURI(), e);
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截未知的系统异常
|
||||
*/
|
||||
@ExceptionHandler(Throwable.class)
|
||||
public R<Void> handleException(Throwable e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],发生未知异常。", request.getRequestURI(), e);
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.web.autoconfigure.exception;
|
||||
|
||||
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.LoggerFactory;
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory;
|
||||
import top.continew.starter.web.autoconfigure.i18n.I18nProperties;
|
||||
|
||||
/**
|
||||
* 全局异常处理器自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnMissingBean(BasicErrorController.class)
|
||||
@Import({GlobalExceptionHandler.class, GlobalErrorHandler.class})
|
||||
@EnableConfigurationProperties(I18nProperties.class)
|
||||
public class GlobalExceptionHandlerAutoConfiguration {
|
||||
|
||||
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
|
||||
public void postConstruct() {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Web-Global Exception Handler' completed initialization.");
|
||||
}
|
||||
}
|
||||
@@ -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.web.autoconfigure.mvc;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import top.continew.starter.core.enums.BaseEnum;
|
||||
import top.continew.starter.core.util.validate.ValidationUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* BaseEnum 参数转换器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class BaseEnumConverter<T extends BaseEnum> implements Converter<String, T> {
|
||||
|
||||
private final Map<String, T> enumMap = new HashMap<>();
|
||||
|
||||
public BaseEnumConverter(Class<T> enumType) {
|
||||
T[] enums = enumType.getEnumConstants();
|
||||
for (T e : enums) {
|
||||
enumMap.put(String.valueOf(e.getValue()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T convert(String source) {
|
||||
T t = enumMap.get(source);
|
||||
ValidationUtils.throwIfNull(t, "枚举值非法:{}", source);
|
||||
return t;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.mvc;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.ConverterFactory;
|
||||
import top.continew.starter.core.enums.BaseEnum;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* BaseEnum 参数转换器工厂
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class BaseEnumConverterFactory implements ConverterFactory<String, BaseEnum> {
|
||||
|
||||
private static final Map<Class, Converter> CONVERTER_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public <T extends BaseEnum> Converter<String, T> getConverter(Class<T> targetType) {
|
||||
return CONVERTER_CACHE.computeIfAbsent(targetType, key -> new BaseEnumConverter<>(targetType));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.mvc;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Web MVC 自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@EnableWebMvc
|
||||
@AutoConfiguration
|
||||
public class WebMvcAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(WebMvcAutoConfiguration.class);
|
||||
private final MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;
|
||||
|
||||
public WebMvcAutoConfiguration(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
|
||||
this.mappingJackson2HttpMessageConverter = mappingJackson2HttpMessageConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解决 Jackson2ObjectMapperBuilderCustomizer 配置不生效的问题
|
||||
* <p>
|
||||
* MappingJackson2HttpMessageConverter 对象在程序启动时创建了多个,移除多余的,保证只有一个
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
converters.removeIf(MappingJackson2HttpMessageConverter.class::isInstance);
|
||||
if (Objects.isNull(mappingJackson2HttpMessageConverter)) {
|
||||
converters.add(0, new MappingJackson2HttpMessageConverter());
|
||||
} else {
|
||||
converters.add(0, mappingJackson2HttpMessageConverter);
|
||||
}
|
||||
// 自定义 converters 时,需要手动在最前面添加 ByteArrayHttpMessageConverter
|
||||
// 否则 Spring Doc OpenAPI 的 /*/api-docs/**(例如:/v3/api-docs/default)接口响应内容会变为 Base64 编码后的内容,最终导致接口文档解析失败
|
||||
// 详情请参阅:https://github.com/springdoc/springdoc-openapi/issues/2143
|
||||
converters.add(0, new ByteArrayHttpMessageConverter());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
registry.addConverterFactory(new BaseEnumConverterFactory());
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Web MVC' completed initialization.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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.response;
|
||||
|
||||
import com.feiniaojin.gracefulresponse.ExceptionAliasRegister;
|
||||
import com.feiniaojin.gracefulresponse.advice.*;
|
||||
import com.feiniaojin.gracefulresponse.api.ResponseFactory;
|
||||
import com.feiniaojin.gracefulresponse.api.ResponseStatusFactory;
|
||||
import com.feiniaojin.gracefulresponse.defaults.DefaultResponseFactory;
|
||||
import com.feiniaojin.gracefulresponse.defaults.DefaultResponseStatusFactoryImpl;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.context.support.ResourceBundleMessageSource;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.core.util.GeneralPropertySourceFactory;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 全局响应自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(GlobalResponseProperties.class)
|
||||
@PropertySource(value = "classpath:default-web.yml", factory = GeneralPropertySourceFactory.class)
|
||||
public class GlobalResponseAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalResponseAutoConfiguration.class);
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GrGlobalExceptionAdvice globalExceptionAdvice() {
|
||||
return new GrGlobalExceptionAdvice();
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局校验异常处理
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GrValidationExceptionAdvice validationExceptionAdvice() {
|
||||
return new GrValidationExceptionAdvice();
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局响应体处理(非 void)
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GrNotVoidResponseBodyAdvice notVoidResponseBodyAdvice() {
|
||||
return new GrNotVoidResponseBodyAdvice();
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局响应体处理(void)
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GrVoidResponseBodyAdvice voidResponseBodyAdvice() {
|
||||
return new GrVoidResponseBodyAdvice();
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应工厂
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ResponseFactory responseBeanFactory() {
|
||||
return new DefaultResponseFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应状态工厂
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ResponseStatusFactory responseStatusFactory() {
|
||||
return new DefaultResponseStatusFactoryImpl();
|
||||
}
|
||||
|
||||
/**
|
||||
* 异常别名注册
|
||||
*/
|
||||
@Bean
|
||||
public ExceptionAliasRegister exceptionAliasRegister() {
|
||||
return new ExceptionAliasRegister();
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应支持
|
||||
*/
|
||||
@Bean
|
||||
public AdviceSupport adviceSupport() {
|
||||
return new AdviceSupport();
|
||||
}
|
||||
|
||||
/**
|
||||
* 国际化支持
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.WEB_RESPONSE, name = "i18n", havingValue = "true")
|
||||
public GrI18nAdvice i18nAdvice() {
|
||||
return new GrI18nAdvice();
|
||||
}
|
||||
|
||||
/**
|
||||
* 国际化配置
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.WEB_RESPONSE, name = "i18n", havingValue = "true")
|
||||
public MessageSource messageSource() {
|
||||
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
|
||||
messageSource.setBasenames("i18n", "i18n/empty-messages");
|
||||
messageSource.setDefaultEncoding("UTF-8");
|
||||
messageSource.setDefaultLocale(Locale.CHINA);
|
||||
return messageSource;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Web-Global Response' completed initialization.");
|
||||
}
|
||||
}
|
||||
@@ -14,30 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.web.autoconfigure.i18n;
|
||||
package top.continew.starter.web.autoconfigure.response;
|
||||
|
||||
import com.feiniaojin.gracefulresponse.GracefulResponseProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
|
||||
/**
|
||||
* 国际化配置属性
|
||||
* 全局响应配置属性
|
||||
*
|
||||
* @author Jasmine
|
||||
* @since 2.2.0
|
||||
* @author Charles7c
|
||||
* @since 2.5.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;
|
||||
}
|
||||
@ConfigurationProperties(PropertiesConstants.WEB_RESPONSE)
|
||||
public class GlobalResponseProperties extends GracefulResponseProperties {
|
||||
}
|
||||
@@ -16,12 +16,14 @@
|
||||
|
||||
package top.continew.starter.web.model;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.feiniaojin.gracefulresponse.data.Response;
|
||||
import com.feiniaojin.gracefulresponse.data.ResponseStatus;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import top.continew.starter.web.autoconfigure.response.GlobalResponseProperties;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* 响应信息
|
||||
@@ -30,13 +32,25 @@ import java.io.Serializable;
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Schema(description = "响应信息")
|
||||
public class R<T> implements Serializable {
|
||||
public class R implements Response {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final GlobalResponseProperties PROPERTIES = SpringUtil.getBean(GlobalResponseProperties.class);
|
||||
private static final String DEFAULT_SUCCESS_CODE = PROPERTIES.getDefaultSuccessCode();
|
||||
private static final String DEFAULT_SUCCESS_MSG = PROPERTIES.getDefaultSuccessMsg();
|
||||
private static final String DEFAULT_ERROR_CODE = PROPERTIES.getDefaultErrorCode();
|
||||
private static final String DEFAULT_ERROR_MSG = PROPERTIES.getDefaultErrorMsg();
|
||||
|
||||
private static final int SUCCESS_CODE = HttpStatus.OK.value();
|
||||
private static final int FAIL_CODE = HttpStatus.INTERNAL_SERVER_ERROR.value();
|
||||
/**
|
||||
* 状态码
|
||||
*/
|
||||
@Schema(description = "状态码", example = "1")
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 状态信息
|
||||
*/
|
||||
@Schema(description = "状态信息", example = "操作成功")
|
||||
private String msg;
|
||||
|
||||
/**
|
||||
* 是否成功
|
||||
@@ -45,153 +59,60 @@ public class R<T> implements Serializable {
|
||||
private boolean success;
|
||||
|
||||
/**
|
||||
* 业务状态码
|
||||
* 时间戳
|
||||
*/
|
||||
@Schema(description = "业务状态码", example = "200")
|
||||
private int code;
|
||||
|
||||
/**
|
||||
* 业务状态信息
|
||||
*/
|
||||
@Schema(description = "业务状态信息", example = "操作成功")
|
||||
private String msg;
|
||||
@Schema(description = "时间戳", example = "1691453288000")
|
||||
private final Long timestamp = System.currentTimeMillis();
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
*/
|
||||
@Schema(description = "响应数据")
|
||||
private T data;
|
||||
private Object data = Collections.emptyMap();
|
||||
|
||||
/**
|
||||
* 时间戳
|
||||
*/
|
||||
@Schema(description = "时间戳", example = "1691453288")
|
||||
private long timestamp = DateUtil.currentSeconds();
|
||||
|
||||
private R() {
|
||||
public R() {
|
||||
}
|
||||
|
||||
private R(boolean success, int code, String msg, T data) {
|
||||
this.success = success;
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
public R(String code, String msg) {
|
||||
this.setCode(code);
|
||||
this.setMsg(msg);
|
||||
}
|
||||
|
||||
public R(String code, String msg, Object data) {
|
||||
this(code, msg);
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作成功
|
||||
*
|
||||
* @param <T> 响应数据类型
|
||||
* @return R /
|
||||
*/
|
||||
public static <T> R<T> ok() {
|
||||
return new R<>(true, SUCCESS_CODE, "操作成功", null);
|
||||
@Override
|
||||
public void setStatus(ResponseStatus status) {
|
||||
this.setCode(status.getCode());
|
||||
this.setMsg(status.getMsg());
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作成功
|
||||
*
|
||||
* @param data 响应数据
|
||||
* @param <T> 响应数据类型
|
||||
* @return R /
|
||||
*/
|
||||
public static <T> R<T> ok(T data) {
|
||||
return new R<>(true, SUCCESS_CODE, "操作成功", data);
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public ResponseStatus getStatus() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作成功
|
||||
*
|
||||
* @param msg 业务状态信息
|
||||
* @param <T> 响应数据类型
|
||||
* @return R /
|
||||
*/
|
||||
public static <T> R<T> ok(String msg) {
|
||||
return new R<>(true, SUCCESS_CODE, msg, null);
|
||||
@Override
|
||||
public void setPayload(Object payload) {
|
||||
this.data = payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作成功
|
||||
*
|
||||
* @param msg 业务状态信息
|
||||
* @param data 响应数据
|
||||
* @param <T> 响应数据类型
|
||||
* @return R /
|
||||
*/
|
||||
public static <T> R<T> ok(String msg, T data) {
|
||||
return new R<>(true, SUCCESS_CODE, msg, data);
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public Object getPayload() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作失败
|
||||
*
|
||||
* @param <T> 响应数据类型
|
||||
* @return R /
|
||||
*/
|
||||
public static <T> R<T> fail() {
|
||||
return new R<>(false, FAIL_CODE, "操作失败", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作失败
|
||||
*
|
||||
* @param msg 业务状态信息
|
||||
* @param <T> 响应数据类型
|
||||
* @return R /
|
||||
*/
|
||||
public static <T> R<T> fail(String msg) {
|
||||
return new R<>(false, FAIL_CODE, msg, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作失败
|
||||
*
|
||||
* @param data 响应数据
|
||||
* @param <T> 响应数据类型
|
||||
* @return R /
|
||||
*/
|
||||
public static <T> R<T> fail(T data) {
|
||||
return new R<>(false, FAIL_CODE, "操作失败", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作失败
|
||||
*
|
||||
* @param msg 业务状态信息
|
||||
* @param data 响应数据
|
||||
* @param <T> 响应数据类型
|
||||
* @return R /
|
||||
*/
|
||||
public static <T> R<T> fail(String msg, T data) {
|
||||
return new R<>(false, FAIL_CODE, msg, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作失败
|
||||
*
|
||||
* @param code 业务状态码
|
||||
* @param msg 业务状态信息
|
||||
* @param <T> 响应数据类型
|
||||
* @return R /
|
||||
*/
|
||||
public static <T> R<T> fail(int code, String msg) {
|
||||
return new R<>(false, code, msg, null);
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(int code) {
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
this.success = DEFAULT_SUCCESS_CODE.equals(code);
|
||||
}
|
||||
|
||||
public String getMsg() {
|
||||
@@ -202,19 +123,73 @@ public class R<T> implements Serializable {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
public Object getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(T data) {
|
||||
public void setData(Object data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public Long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
/**
|
||||
* 操作成功
|
||||
*
|
||||
* @return R /
|
||||
*/
|
||||
public static R ok() {
|
||||
return new R(DEFAULT_SUCCESS_CODE, DEFAULT_SUCCESS_MSG);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作成功
|
||||
*
|
||||
* @param data 响应数据
|
||||
* @return R /
|
||||
*/
|
||||
public static R ok(Object data) {
|
||||
return new R(DEFAULT_SUCCESS_CODE, DEFAULT_SUCCESS_MSG, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作成功
|
||||
*
|
||||
* @param msg 业务状态信息
|
||||
* @param data 响应数据
|
||||
* @return R /
|
||||
*/
|
||||
public static R ok(String msg, Object data) {
|
||||
return new R(DEFAULT_SUCCESS_CODE, msg, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作失败
|
||||
*
|
||||
* @return R /
|
||||
*/
|
||||
public static R fail() {
|
||||
return new R(DEFAULT_ERROR_CODE, DEFAULT_ERROR_MSG);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作失败
|
||||
*
|
||||
* @param code 业务状态码
|
||||
* @param msg 业务状态信息
|
||||
* @return R /
|
||||
*/
|
||||
public static R fail(String code, String msg) {
|
||||
return new R(code, msg);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.io.file.FileNameUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -29,8 +29,8 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
@@ -83,27 +83,29 @@ public class FileUploadUtils {
|
||||
/**
|
||||
* 下载
|
||||
*
|
||||
* @param request 请求对象
|
||||
* @param response 响应对象
|
||||
* @param file 文件
|
||||
* @param autoDelete 下载后自动删除
|
||||
* @param response 响应对象
|
||||
* @param file 文件
|
||||
*/
|
||||
public static void download(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
File file,
|
||||
boolean autoDelete) {
|
||||
response.setCharacterEncoding(request.getCharacterEncoding());
|
||||
public static void download(HttpServletResponse response, File file) throws IOException {
|
||||
download(response, new FileInputStream(file), file.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载
|
||||
*
|
||||
* @param response 响应对象
|
||||
* @param inputStream 文件流
|
||||
* @param fileName 文件名
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public static void download(HttpServletResponse response,
|
||||
InputStream inputStream,
|
||||
String fileName) throws IOException {
|
||||
byte[] bytes = IoUtil.readBytes(inputStream);
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
|
||||
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
||||
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getName());
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
IoUtil.copy(fis, response.getOutputStream());
|
||||
response.flushBuffer();
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
} finally {
|
||||
if (autoDelete) {
|
||||
file.deleteOnExit();
|
||||
}
|
||||
}
|
||||
response.setContentLength(bytes.length);
|
||||
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + URLUtil.encode(fileName));
|
||||
IoUtil.write(response.getOutputStream(), true, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +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.web.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 messageSource = SpringUtil.getBean(MessageSource.class);
|
||||
|
||||
private static final Object[] emptyArray = new Object[] {};
|
||||
|
||||
public static String getMessage(String key) {
|
||||
return getMessage(key, emptyArray);
|
||||
}
|
||||
|
||||
public static String getMessage(String key, String defaultMessage) {
|
||||
return getMessage(key, defaultMessage, emptyArray);
|
||||
}
|
||||
|
||||
public static String getMessage(String msgKey, Object... args) {
|
||||
return getMessage(msgKey, msgKey, args);
|
||||
}
|
||||
|
||||
public static String getMessage(String msgKey, String defaultMessage, Object... args) {
|
||||
try {
|
||||
return messageSource.getMessage(msgKey, args, LocaleContextHolder.getLocale());
|
||||
} catch (Exception e) {
|
||||
return defaultMessage;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,8 +21,6 @@ import cn.hutool.http.useragent.UserAgent;
|
||||
import cn.hutool.http.useragent.UserAgentUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
|
||||
import java.util.*;
|
||||
@@ -38,24 +36,6 @@ public class ServletUtils {
|
||||
private ServletUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求对象
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public static HttpServletRequest getRequest() {
|
||||
return getServletRequestAttributes().getRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取响应对象
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public static HttpServletResponse getResponse() {
|
||||
return getServletRequestAttributes().getResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取浏览器及其版本信息
|
||||
*
|
||||
@@ -118,8 +98,4 @@ public class ServletUtils {
|
||||
}
|
||||
return headerMap;
|
||||
}
|
||||
|
||||
private static ServletRequestAttributes getServletRequestAttributes() {
|
||||
return (ServletRequestAttributes)Objects.requireNonNull(RequestContextHolder.getRequestAttributes());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,15 +20,23 @@ import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.http.server.PathContainer;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
|
||||
import org.springframework.web.util.UrlPathHelper;
|
||||
import org.springframework.web.util.pattern.PathPattern;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Spring Web 工具类
|
||||
@@ -41,6 +49,38 @@ public class SpringWebUtils {
|
||||
private SpringWebUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求对象
|
||||
*
|
||||
* @return 请求对象
|
||||
*/
|
||||
public static HttpServletRequest getRequest() {
|
||||
return getServletRequestAttributes().getRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取响应对象
|
||||
*
|
||||
* @return 响应对象
|
||||
*/
|
||||
public static HttpServletResponse getResponse() {
|
||||
return getServletRequestAttributes().getResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径是否匹配
|
||||
*
|
||||
* @param pattern 匹配模式
|
||||
* @param path 路径
|
||||
* @return 是否匹配
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public static boolean match(String pattern, String path) {
|
||||
PathPattern pathPattern = PathPatternParser.defaultInstance.parse(pattern);
|
||||
PathContainer pathContainer = PathContainer.parsePath(path);
|
||||
return pathPattern.matches(pathContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消注册静态资源映射
|
||||
*
|
||||
@@ -91,4 +131,8 @@ public class SpringWebUtils {
|
||||
.getUrlMap();
|
||||
ReflectUtil.<Void>invoke(resourceHandlerMapping, "registerHandlers", additionalUrlMap);
|
||||
}
|
||||
|
||||
private static ServletRequestAttributes getServletRequestAttributes() {
|
||||
return (ServletRequestAttributes)Objects.requireNonNull(RequestContextHolder.getRequestAttributes());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
top.continew.starter.web.autoconfigure.mvc.WebMvcAutoConfiguration
|
||||
top.continew.starter.web.autoconfigure.cors.CorsAutoConfiguration
|
||||
top.continew.starter.web.autoconfigure.trace.TraceAutoConfiguration
|
||||
top.continew.starter.web.autoconfigure.xss.XssAutoConfiguration
|
||||
top.continew.starter.web.autoconfigure.xss.XssAutoConfiguration
|
||||
|
||||
23
continew-starter-web/src/main/resources/default-web.yml
Normal file
23
continew-starter-web/src/main/resources/default-web.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
--- ### 响应配置
|
||||
continew-starter.web.response:
|
||||
# 是否开启国际化(默认:false)
|
||||
i18n: false
|
||||
# 响应类全名(配置后 response-style 将不再生效)
|
||||
response-class-full-name: top.continew.starter.web.model.R
|
||||
# 自定义成功响应码(默认:0)
|
||||
default-success-code: 0
|
||||
# 自定义成功提示(默认:ok)
|
||||
default-success-msg: ok
|
||||
# 自定义失败响应码(默认:1)
|
||||
default-error-code: 1
|
||||
# 自定义失败提示(默认:error)
|
||||
default-error-msg: error
|
||||
# 是否打印异常日志(默认:false)
|
||||
print-exception-in-global-advice: true
|
||||
# 是否将原生异常错误信息填充到状态信息中(默认:false)
|
||||
origin-exception-using-detail-message: true
|
||||
# 例外包路径(支持数字, * 和 ** 通配符匹配),该包路径下的 Controller 将被忽略处理
|
||||
exclude-packages:
|
||||
- io.swagger.**
|
||||
- org.springdoc.**
|
||||
- org.springframework.boot.actuate.*
|
||||
8
pom.xml
8
pom.xml
@@ -76,6 +76,14 @@
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- 编译插件 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<compilerArgument>-parameters</compilerArgument>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- 代码格式化插件 -->
|
||||
<plugin>
|
||||
<groupId>com.diffplug.spotless</groupId>
|
||||
|
||||
Reference in New Issue
Block a user