Compare commits

..

21 Commits

Author SHA1 Message Date
8c6b94ba76 release: v2.4.0 2024-07-31 22:03:11 +08:00
5b0f89a88f refactor: 优化部分代码 2024-07-31 21:57:36 +08:00
bd07f9b41f refactor(log): 新增 excludePatterns 放行路由配置 2024-07-31 21:11:45 +08:00
702dcca701 feat(web): SpringWebUtils 新增 match 方法
ServletUtils 部分方法调整到 SpringWebUtils
2024-07-31 21:10:06 +08:00
bed954ca94 feat(web): 新增 BaseEnum 枚举接口参数转换器 2024-07-31 20:43:04 +08:00
吴泽威
ebc73a94e8 feat(api-doc): 增加对 BaseEnum 枚举接口的详细展示 2024-07-31 02:14:02 +00:00
1479c8d939 revert: EasyExcel 4.0.1 => 3.3.4
暂时回退版本,解决版本冲突问题
2024-07-30 21:23:34 +08:00
730df52797 refactor(file/excel): 移动 ExcelBaseEnumConverter 到 excel 模块 2024-07-30 21:22:32 +08:00
32935fa4fa feat(json/jackson): 新增枚举接口序列化及反序列化配置 2024-07-30 21:11:23 +08:00
b27fbd41eb refactor(data/mybatis-plus): 移动枚举接口到 core 模块,和 MP IEnum 枚举接口解耦 2024-07-30 20:58:26 +08:00
8bacd87d25 docs: 更新项目源码链接 2024-07-24 21:15:58 +08:00
吴泽威
c1ebc4621c build: 代码编译增加 -parameters 参数 2024-07-19 03:49:06 +00:00
a0388b5dc8 release: v2.3.0 2024-07-18 23:43:16 +08:00
e7566d284b fix(web): 修复文件上传异常单位显示错误 2024-07-18 23:27:59 +08:00
c17668c2d1 fix(extension/crud): 修复 Name for argument of type [java.lang.Long] not specified, and parameter name information not available via reflection. 错误 2024-07-18 21:18:27 +08:00
65cfe91770 fix(extension/crud): 修复 DictField 映射错误
Closes #IADTTC
2024-07-18 21:13:59 +08:00
dca715709f refactor(extension/crud): 调整 BaseService 相关泛型类型加载为懒加载 2024-07-16 22:57:35 +08:00
a110bd9789 chore: 升级依赖
SpringBoot 3.1.11 => 3.2.7
SnailJob 1.1.0-beta1 => 1.1.0
MyBatisPlus 3.5.5 => 3.5.7
MyBatisFlex 1.8.9 => 1.9.3
dynamic-datasource 4.3.0 => 4.3.1
JetCache 2.7.5 => 2.7.6
Redisson 3.30.0 => 3.32.0
CosID 2.6.8 => 2.9.1
EasyExcel 3.3.4 => 4.0.1
XFileStorage 2.1.0 => 2.2.0
Crane4j 2.8.0 => 2.9.0
Hutool 5.8.27 => 5.8.29
AWS S3 1.12.720 => 1.12.761
IP2Region 3.1.11 => 3.2.6
2024-07-16 22:37:46 +08:00
b0f5506424 refactor(core): 优化 JSR 303 校验方法 2024-07-03 23:23:06 +08:00
6809600858 feat(core): 新增 JSR 303 校验器自动配置(从 web 模块迁移)
1.从 web 模块移动 JSR 303 校验器自动配置到 core 模块
2.从 web 模块移动 MessageSourceUtils 到 core 模块
2024-07-03 23:22:07 +08:00
d31d8d209a chore: 新增 Snail Job 依赖版本 2024-07-02 22:22:19 +08:00
40 changed files with 1589 additions and 418 deletions

View File

@@ -1,3 +1,57 @@
## [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.7TaskExecutor => ThreadPoolTaskExecutor
- MyBatisPlus 3.5.5 => 3.5.7(数据权限处理器调整)
- MyBatisFlex 1.8.9 => 1.9.3
- dynamic-datasource 4.3.0 => 4.3.1
- JetCache 2.7.5 => 2.7.6
- Redisson 3.30.0 => 3.32.0
- CosID 2.6.8 => 2.9.1
- EasyExcel 3.3.4 => 4.0.1
- XFileStorage 2.1.0 => 2.2.0
- Crane4j 2.8.0 => 2.9.0
- Hutool 5.8.27 => 5.8.29
- AWS S3 1.12.720 => 1.12.761
- IP2Region 3.1.11 => 3.2.6
## [v2.2.0](https://github.com/continew-org/continew-starter/compare/v2.1.1...v2.2.0) (2024-06-30)
### ✨ 新特性

109
README.md
View File

@@ -13,7 +13,7 @@
<img src="https://sonarcloud.io/api/project_badges/measure?project=Charles7c_continew-starter&metric=alert_status" alt="Sonar Status" />
</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" />
@@ -61,9 +61,10 @@ ContiNew Starter 就是将脚手架项目中的通用基础配置进行了封装
## 项目源码
| 开源平台 | 源码地址 |
| :------------ | :-------------------------------------------- |
| GitHub | https://github.com/continew-org/continew-starter |
| :------------ | :----------------------------------------------- |
| Gitee码云 | https://gitee.com/continew/continew-starter |
| GitCode | https://gitcode.com/continew/continew-starter |
| GitHub | https://github.com/continew-org/continew-starter |
## 像数123一样容易
@@ -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-ip2region3.1.7 |
| continew-starter-core | 核心模块:包含线程池、项目等自动配置 | <a href="https://spring.io/projects/spring-boot" target="_blank">Spring Boot</a>3.1.11<br /><a href="https://www.hutool.cn/" target="_blank">Hutool</a>5.8.29<br />mica-ip2region3.2.6 |
### JSON模块
| 模块名称 | 模块说明 | 依赖版本 |
| ----------------------------- | -------------------- | --------------- |
| continew-starter-json-jackson | Jackson 序列化等配置 | Jackson2.15.3 |
| 模块名称 | 模块说明 |
| ----------------------------- | -------------------- |
| continew-starter-json-jackson | Jackson 序列化等配置 |
### 接口文档
| 模块名称 | 模块说明 | 依赖版本 |
| ------------------------ | ---------------- | ------------------------------------------------------------ |
| continew-starter-api-doc | Knife4j 自动配置 | <a href="https://doc.xiaominfo.com/" target="_blank">Knife4j</a>4.5.0 |
| 模块名称 | 模块说明 |
| ------------------------ | ---------------- |
| continew-starter-api-doc | Knife4j 自动配置 |
### 安全模块
| 模块名称 | 模块说明 | 依赖版本 |
|------------------------------------|-----------| -------- |
| continew-starter-security-password | 密码编码器 | |
| continew-starter-security-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 />TLog1.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-httptrace-pro | Spring Boot Actuator HttpTrace 重置增强版 |
### 存储模块
| 模块名称 | 模块说明 | 依赖版本 |
| ------------------------------ | -------- | -------- |
| continew-starter-storage-local | 本地存储 | |
| 模块名称 | 模块说明 |
| ------------------------------ | -------- |
| continew-starter-storage-local | 本地存储 |
### 文件处理模块
| 模块名称 | 模块说明 | 依赖版本 |
| --------------------------- | -------------- |------------------------------------------------------------------------------------------|
| continew-starter-file-excel | Excel 相关配置 | <a href="https://easyexcel.opensource.alibaba.com/" target="_blank">Easy Excel</a>3.3.4 |
| 模块名称 | 模块说明 |
| --------------------------- | -------------- |
| continew-starter-file-excel | Excel 相关配置 |
### 验证码模块
| 模块名称 | 模块说明 | 依赖版本 |
| --------------------------------- | ---------- | ------------------- |
| continew-starter-captcha-graphic | 图形验证码 | Easy Captcha1.6.2 |
| continew-starter-captcha-behavior | 行为验证码 | AJ-Captcha1.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 Mail1.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 封装 |
## 贡献代码

View File

@@ -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.");

View File

@@ -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());
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.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";
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.core.autoconfigure;
import jakarta.annotation.PostConstruct;
import jakarta.validation.Validator;
import org.hibernate.validator.HibernateValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import java.util.Properties;
/**
* JSR 303 校验器自动配置
*
* @author Charles7c
* @since 2.3.0
*/
@AutoConfiguration
public class ValidatorAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(ValidatorAutoConfiguration.class);
/**
* Validator 失败立即返回模式配置
*
* <p>
* 默认情况下会校验完所有字段,然后才抛出异常。
* </p>
*/
@Bean
public Validator validator(MessageSource messageSource) {
try (LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean()) {
// 国际化
factoryBean.setValidationMessageSource(messageSource);
factoryBean.setProviderClass(HibernateValidator.class);
Properties properties = new Properties();
properties.setProperty("hibernate.validator.fail_fast", "true");
factoryBean.setValidationProperties(properties);
factoryBean.afterPropertiesSet();
return factoryBean.getValidator();
}
}
@PostConstruct
public void postConstruct() {
log.debug("[ContiNew Starter] - Auto Configuration 'Validator' completed initialization.");
}
}

View File

@@ -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()

View File

@@ -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();
/**
* 枚举描述

View File

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

View File

@@ -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());
}
}

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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 {}
/**
* 数据权限处理器

View File

@@ -30,9 +30,7 @@ import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.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()));

View File

@@ -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);
}
}
}

View File

@@ -36,13 +36,25 @@ import java.util.List;
*/
public class ServiceImpl<M extends BaseMapper<T>, T> extends com.baomidou.mybatisplus.extension.service.impl.ServiceImpl<M, T> implements IService<T> {
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;
}

View File

@@ -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,63 @@
<properties>
<!-- 项目版本号 -->
<revision>2.2.0</revision>
<revision>2.4.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>
<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 +169,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>

View File

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

View File

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

View File

@@ -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;
}
}

View File

@@ -68,17 +68,16 @@ import java.util.*;
*/
public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdDO, L, D, Q, C> extends ServiceImpl<M, T> implements BaseService<L, D, Q, C> {
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];
}
}

View File

@@ -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,
public BaseEnum convertToJavaData(ReadCellData<?> cellData,
ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return this.getEnum(IBaseEnum.class, Convert.toStr(cellData.getData()));
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;
}

View File

@@ -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,6 +59,21 @@ public class JacksonAutoConfiguration {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> {
JavaTimeModule javaTimeModule = this.timeModule();
SimpleModule simpleModule = this.simpleModule();
builder.timeZone(TimeZone.getDefault());
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);
@@ -71,9 +91,21 @@ public class JacksonAutoConfiguration {
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN);
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(timeFormatter));
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(timeFormatter));
builder.timeZone(TimeZone.getDefault());
builder.modules(javaTimeModule);
log.debug("[ContiNew Starter] - Auto Configuration 'Jackson' completed initialization.");
};
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;
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.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;
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -20,7 +20,9 @@ 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;
}
}

View File

@@ -16,10 +16,12 @@
package top.continew.starter.log.httptracepro.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;
@@ -28,6 +30,7 @@ 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.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;
}
/**
* 请求是否有效
*

View File

@@ -17,7 +17,6 @@
package top.continew.starter.log.httptracepro.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;
@@ -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()) {

View File

@@ -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;
};

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.web.autoconfigure.converter;
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;
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.web.autoconfigure.converter;
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.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* BaseEnum 参数转换自动配置
*
* @author Charles7c
* @since 2.4.0
*/
@EnableWebMvc
@AutoConfiguration
public class BaseEnumConverterAutoConfiguration implements WebMvcConfigurer {
private static final Logger log = LoggerFactory.getLogger(BaseEnumConverterAutoConfiguration.class);
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(new BaseEnumConverterFactory());
}
@PostConstruct
public void postConstruct() {
log.debug("[ContiNew Starter] - Auto Configuration 'Web-BaseEnum Converter' completed initialization.");
}
}

View File

@@ -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.converter;
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));
}
}

View File

@@ -42,7 +42,7 @@ 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;
import top.continew.starter.core.util.MessageSourceUtils;
/**
* 全局异常处理器
@@ -130,14 +130,14 @@ public class GlobalExceptionHandler {
msg = msg.concat(cause.getMessage().toLowerCase());
}
if (msg.contains("size") && msg.contains("exceed")) {
sizeLimit = CharSequenceUtil.subBetween(msg, "maximum (", ")");
sizeLimit = CharSequenceUtil.subBetween(msg, "the maximum size ", " for");
} else if (msg.contains("larger than")) {
sizeLimit = CharSequenceUtil.subAfter(msg, "larger than ", true);
} else {
return defaultFail;
}
String errorMsg = "请上传小于 %sMB 的文件".formatted(NumberUtil.parseLong(sizeLimit) / 1024 / 1024);
String errorMsg = "请上传小于 %sKB 的文件".formatted(NumberUtil.parseLong(sizeLimit) / 1024);
log.warn("请求地址 [{}],上传文件失败,文件大小超过限制。", request.getRequestURI(), e);
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
}

View File

@@ -17,20 +17,13 @@
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;
/**
@@ -47,25 +40,6 @@ 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.");

View File

@@ -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;
}
}
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -1,3 +1,4 @@
top.continew.starter.web.autoconfigure.converter.BaseEnumConverterAutoConfiguration
top.continew.starter.web.autoconfigure.cors.CorsAutoConfiguration
top.continew.starter.web.autoconfigure.trace.TraceAutoConfiguration
top.continew.starter.web.autoconfigure.xss.XssAutoConfiguration

View File

@@ -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>