mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-11-13 16:57:12 +08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7571c05f9c | |||
| 4c4f98a86e | |||
|
|
bf51837991 | ||
| 22ebdfeb9f | |||
| b0a2a8c927 | |||
| ea6b316296 |
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,8 +1,14 @@
|
|||||||
## [v2.5.2](https://github.com/continew-org/continew-starter/compare/v2.5.1...v2.5.2) (2024-08-13)
|
## [v2.5.2](https://github.com/continew-org/continew-starter/compare/v2.5.1...v2.5.2) (2024-08-14)
|
||||||
|
|
||||||
|
### 💎 功能优化
|
||||||
|
|
||||||
|
- 【api-doc】重构接口文档枚举展示处理 ([bf51837](https://github.com/continew-org/continew-starter/commit/bf51837991746c5576d7baffcc2296fe8ca91586)) ([4c4f98a](https://github.com/continew-org/continew-starter/commit/4c4f98a86e6d10b2b52814406bd0dc3d248c8486))
|
||||||
|
- 【web】针对最新响应风格增加全局响应格式 ([bf51837](https://github.com/continew-org/continew-starter/commit/bf51837991746c5576d7baffcc2296fe8ca91586)) ([4c4f98a](https://github.com/continew-org/continew-starter/commit/4c4f98a86e6d10b2b52814406bd0dc3d248c8486))
|
||||||
|
|
||||||
### 🐛 问题修复
|
### 🐛 问题修复
|
||||||
|
|
||||||
- 【extension/crud】重构排序字段处理,预防 SQL 注入问题 ([c31fa75](https://github.com/continew-org/continew-starter/commit/c31fa753b6d9753d72a2e28bf3184981ed848ac2))
|
- 【extension/crud】重构排序字段处理,预防 SQL 注入问题 ([c31fa75](https://github.com/continew-org/continew-starter/commit/c31fa753b6d9753d72a2e28bf3184981ed848ac2)) ([22ebdfe](https://github.com/continew-org/continew-starter/commit/22ebdfeb9f30a8832825424a05343acfaf31643b))
|
||||||
|
- 【security/crypto】修复 updateById 修改未正确加密的问题 ([b0a2a8c](https://github.com/continew-org/continew-starter/commit/b0a2a8c927171795e3ed89a820caf829f76c80ee))
|
||||||
|
|
||||||
## [v2.5.1](https://github.com/continew-org/continew-starter/compare/v2.5.0...v2.5.1) (2024-08-12)
|
## [v2.5.1](https://github.com/continew-org/continew-starter/compare/v2.5.0...v2.5.1) (2024-08-12)
|
||||||
|
|
||||||
|
|||||||
@@ -17,16 +17,11 @@
|
|||||||
package top.continew.starter.apidoc.autoconfigure;
|
package top.continew.starter.apidoc.autoconfigure;
|
||||||
|
|
||||||
import cn.hutool.core.map.MapUtil;
|
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.Components;
|
||||||
import io.swagger.v3.oas.models.OpenAPI;
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
import io.swagger.v3.oas.models.info.Contact;
|
import io.swagger.v3.oas.models.info.Contact;
|
||||||
import io.swagger.v3.oas.models.info.Info;
|
import io.swagger.v3.oas.models.info.Info;
|
||||||
import io.swagger.v3.oas.models.info.License;
|
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.SecurityRequirement;
|
||||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
@@ -48,15 +43,13 @@ import org.springframework.http.CacheControl;
|
|||||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
import top.continew.starter.apidoc.handler.BaseEnumParameterHandler;
|
||||||
import top.continew.starter.apidoc.handler.OpenApiHandler;
|
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.autoconfigure.project.ProjectProperties;
|
||||||
import top.continew.starter.core.enums.BaseEnum;
|
|
||||||
import top.continew.starter.core.util.GeneralPropertySourceFactory;
|
import top.continew.starter.core.util.GeneralPropertySourceFactory;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API 文档自动配置
|
* API 文档自动配置
|
||||||
@@ -156,95 +149,14 @@ public class SpringDocAutoConfiguration implements WebMvcConfigurer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义参数配置(针对 BaseEnum 展示枚举值和描述)
|
* 自定义 BaseEnum 枚举参数配置(针对实现了 BaseEnum 的枚举,优化其枚举值和描述展示)
|
||||||
*
|
*
|
||||||
|
* @return {@link BaseEnumParameterHandler }
|
||||||
* @since 2.4.0
|
* @since 2.4.0
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public ParameterCustomizer customParameterCustomizer() {
|
public BaseEnumParameterHandler customParameterCustomizer() {
|
||||||
return (parameterModel, methodParameter) -> {
|
return new BaseEnumParameterHandler();
|
||||||
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
|
@PostConstruct
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.core.converter.AnnotatedType;
|
||||||
|
import io.swagger.v3.oas.models.media.Schema;
|
||||||
|
import io.swagger.v3.oas.models.parameters.Parameter;
|
||||||
|
import org.springdoc.core.customizers.ParameterCustomizer;
|
||||||
|
import org.springdoc.core.customizers.PropertyCustomizer;
|
||||||
|
import org.springframework.core.MethodParameter;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import top.continew.starter.apidoc.util.DocUtils;
|
||||||
|
import top.continew.starter.core.enums.BaseEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义 BaseEnum 枚举参数处理器
|
||||||
|
* <p>
|
||||||
|
* 针对实现了 BaseEnum 的枚举,优化其枚举值和描述展示
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author echo
|
||||||
|
* @since 2.5.2
|
||||||
|
*/
|
||||||
|
public class BaseEnumParameterHandler implements ParameterCustomizer, PropertyCustomizer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Parameter customize(Parameter parameterModel, MethodParameter 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;
|
||||||
|
}
|
||||||
|
// 自定义枚举描述并封装参数配置
|
||||||
|
configureSchema(parameterModel.getSchema(), parameterType);
|
||||||
|
parameterModel.setDescription(appendEnumDescription(description, parameterType));
|
||||||
|
return parameterModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Schema customize(Schema schema, AnnotatedType type) {
|
||||||
|
Class<?> rawClass = resolveRawClass(type.getType());
|
||||||
|
// 判断是否为 BaseEnum 的子类型
|
||||||
|
if (!ClassUtil.isAssignable(BaseEnum.class, rawClass)) {
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
// 自定义参数描述并封装参数配置
|
||||||
|
configureSchema(schema, rawClass);
|
||||||
|
schema.setDescription(appendEnumDescription(schema.getDescription(), rawClass));
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 封装 Schema 配置
|
||||||
|
*
|
||||||
|
* @param schema Schema
|
||||||
|
* @param enumClass 枚举类型
|
||||||
|
*/
|
||||||
|
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 = DocUtils.getEnumValueTypeAsString(enumClass);
|
||||||
|
schema.setType(enumValueType);
|
||||||
|
schema.setFormat(DocUtils.resolveFormat(enumValueType));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追加枚举描述
|
||||||
|
*
|
||||||
|
* @param originalDescription 原始描述
|
||||||
|
* @param enumClass 枚举类型
|
||||||
|
* @return 追加后的描述字符串
|
||||||
|
*/
|
||||||
|
private String appendEnumDescription(String originalDescription, Class<?> enumClass) {
|
||||||
|
return originalDescription + "<span style='color:red'>" + DocUtils.getDescMap(enumClass) + "</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析原始类
|
||||||
|
*
|
||||||
|
* @param type 类型
|
||||||
|
* @return 原始类的 Class 对象
|
||||||
|
*/
|
||||||
|
private Class<?> resolveRawClass(Type type) {
|
||||||
|
if (type instanceof SimpleType simpleType) {
|
||||||
|
return simpleType.getRawClass();
|
||||||
|
} else if (type instanceof CollectionType collectionType) {
|
||||||
|
return collectionType.getContentType().getRawClass();
|
||||||
|
} else {
|
||||||
|
return Object.class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* 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 java.lang.reflect.ParameterizedType;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import top.continew.starter.core.enums.BaseEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接口文档工具类
|
||||||
|
*
|
||||||
|
* @author echo
|
||||||
|
* @since 2.5.2
|
||||||
|
*/
|
||||||
|
public class DocUtils {
|
||||||
|
|
||||||
|
private DocUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取枚举值类型
|
||||||
|
*
|
||||||
|
* @param enumClass 枚举类型
|
||||||
|
* @return 枚举值类型
|
||||||
|
*/
|
||||||
|
public static String getEnumValueTypeAsString(Class<?> enumClass) {
|
||||||
|
// 获取枚举类实现的所有接口
|
||||||
|
Type[] interfaces = enumClass.getGenericInterfaces();
|
||||||
|
// 定义枚举值类型的映射
|
||||||
|
Map<Class<?>, String> typeMap = Map
|
||||||
|
.of(Integer.class, "integer", Long.class, "long", Double.class, "number", String.class, "string");
|
||||||
|
// 遍历所有接口
|
||||||
|
for (Type type : interfaces) {
|
||||||
|
// 检查接口是否为参数化类型并且原始类型为 BaseEnum
|
||||||
|
if (type instanceof ParameterizedType parameterizedType && parameterizedType
|
||||||
|
.getRawType() == BaseEnum.class) {
|
||||||
|
Type actualType = parameterizedType.getActualTypeArguments()[0];
|
||||||
|
// 检查实际类型参数是否为类类型,并返回对应的字符串类型
|
||||||
|
if (actualType instanceof Class<?> actualClass) {
|
||||||
|
return typeMap.getOrDefault(actualClass, "string");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 默认返回 "string" 类型
|
||||||
|
return "string";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析枚举值的格式
|
||||||
|
*
|
||||||
|
* @param enumValueType 枚举值类型
|
||||||
|
* @return String 格式化类型
|
||||||
|
*/
|
||||||
|
public static String resolveFormat(String enumValueType) {
|
||||||
|
return switch (enumValueType) {
|
||||||
|
case "integer" -> "int32";
|
||||||
|
case "long" -> "int64";
|
||||||
|
case "number" -> "double";
|
||||||
|
default -> enumValueType;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 具有 RestController 注释,既检查是否继承了BaseController
|
||||||
|
*
|
||||||
|
* @param clazz clazz
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public static boolean hasRestControllerAnnotation(Class<?> clazz) {
|
||||||
|
// 如果注释包含 RestController 注解,则返回 true
|
||||||
|
if (clazz.isAnnotationPresent(RestController.class)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 递归检查父类
|
||||||
|
Class<?> superClass = clazz.getSuperclass();
|
||||||
|
// 循环检查父类
|
||||||
|
while (superClass != null && !superClass.equals(Object.class)) {
|
||||||
|
// 如果父类包含 RestController 注解,则返回 true
|
||||||
|
if (hasRestControllerAnnotation(superClass)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 递归检查接口
|
||||||
|
superClass = superClass.getSuperclass();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取枚举描述 Map
|
||||||
|
*
|
||||||
|
* @param enumClass 枚举类型
|
||||||
|
* @return 枚举描述 Map
|
||||||
|
*/
|
||||||
|
public static 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
|
||||||
* <p>
|
|
||||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* <p>
|
|
||||||
* http://www.gnu.org/licenses/lgpl.html
|
|
||||||
* <p>
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package top.continew.starter.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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,17 +16,24 @@
|
|||||||
|
|
||||||
package top.continew.starter.data.core.util;
|
package top.continew.starter.data.core.util;
|
||||||
|
|
||||||
import java.util.Objects;
|
import cn.hutool.core.text.CharSequenceUtil;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SQL 注入验证工具类
|
* SQL 注入验证工具类
|
||||||
*
|
*
|
||||||
* @author hubin
|
* @author hubin(<a href="https://github.com/baomidou/mybatis-plus">MyBatis Plus</a>)
|
||||||
|
* @author zhoujf(<a href="https://github.com/jeecgboot/JeecgBoot">JeecgBoot</a>)
|
||||||
|
* @author Charles7c
|
||||||
* @since 2.5.2
|
* @since 2.5.2
|
||||||
*/
|
*/
|
||||||
public class SqlInjectionUtils {
|
public class SqlInjectionUtils {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(SqlInjectionUtils.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SQL语法检查正则:符合两个关键字(有先后顺序)才算匹配
|
* SQL语法检查正则:符合两个关键字(有先后顺序)才算匹配
|
||||||
*/
|
*/
|
||||||
@@ -39,6 +46,23 @@ public class SqlInjectionUtils {
|
|||||||
private static final Pattern SQL_COMMENT_PATTERN = Pattern
|
private static final Pattern SQL_COMMENT_PATTERN = Pattern
|
||||||
.compile("'.*(or|union|--|#|/\\*|;)", Pattern.CASE_INSENSITIVE);
|
.compile("'.*(or|union|--|#|/\\*|;)", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL 语法关键字
|
||||||
|
*/
|
||||||
|
private static final String SQL_SYNTAX_KEYWORD = "and |exec |peformance_schema|information_schema|extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+|--";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL 函数检查正则
|
||||||
|
*/
|
||||||
|
private static final String[] SQL_FUNCTION_PATTERN = new String[] {"chr\\s*\\(", "mid\\s*\\(", " char\\s*\\(",
|
||||||
|
"sleep\\s*\\(", "user\\s*\\(", "show\\s+tables", "user[\\s]*\\([\\s]*\\)", "show\\s+databases",
|
||||||
|
"sleep\\(\\d*\\)", "sleep\\(.*\\)",};
|
||||||
|
|
||||||
|
private static final String MESSAGE_TEMPLATE = "SQL 注入检查: 检查值=>{}<=存在 SQL 注入关键字, 关键字=>{}<=";
|
||||||
|
|
||||||
|
private SqlInjectionUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查参数是否存在 SQL 注入
|
* 检查参数是否存在 SQL 注入
|
||||||
*
|
*
|
||||||
@@ -46,8 +70,59 @@ public class SqlInjectionUtils {
|
|||||||
* @return true:非法;false:合法
|
* @return true:非法;false:合法
|
||||||
*/
|
*/
|
||||||
public static boolean check(String value) {
|
public static boolean check(String value) {
|
||||||
Objects.requireNonNull(value);
|
return check(value, null);
|
||||||
// 处理是否包含 SQL 注释字符 || 检查是否包含 SQ L注入敏感字符
|
}
|
||||||
return SQL_COMMENT_PATTERN.matcher(value).find() || SQL_SYNTAX_PATTERN.matcher(value).find();
|
|
||||||
|
/**
|
||||||
|
* 检查参数是否存在 SQL 注入
|
||||||
|
*
|
||||||
|
* @param value 检查参数
|
||||||
|
* @param customKeyword 自定义关键字
|
||||||
|
* @return true:非法;false:合法
|
||||||
|
*/
|
||||||
|
public static boolean check(String value, String customKeyword) {
|
||||||
|
if (CharSequenceUtil.isBlank(value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 处理是否包含 SQL 注释字符 || 检查是否包含 SQL 注入敏感字符
|
||||||
|
if (SQL_COMMENT_PATTERN.matcher(value).find() || SQL_SYNTAX_PATTERN.matcher(value).find()) {
|
||||||
|
log.warn("SQL 注入检查: 检查值=>{}<=存在 SQL 注释字符或 SQL 注入敏感字符", value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 转换成小写再进行比较
|
||||||
|
value = value.toLowerCase().trim();
|
||||||
|
// 检查是否包含 SQL 语法关键字
|
||||||
|
if (checkKeyword(value, SQL_SYNTAX_KEYWORD.split("\\|"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 检查是否包含自定义关键字
|
||||||
|
if (CharSequenceUtil.isNotBlank(customKeyword) && checkKeyword(value, customKeyword.split("\\|"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 检查是否包含 SQL 注入敏感字符
|
||||||
|
for (String pattern : SQL_FUNCTION_PATTERN) {
|
||||||
|
if (Pattern.matches(".*" + pattern + ".*", value)) {
|
||||||
|
log.warn(MESSAGE_TEMPLATE, value, pattern);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查参数是否存在关键字
|
||||||
|
*
|
||||||
|
* @param value 检查参数
|
||||||
|
* @param keywords 关键字列表
|
||||||
|
* @return true:非法;false:合法
|
||||||
|
*/
|
||||||
|
private static boolean checkKeyword(String value, String[] keywords) {
|
||||||
|
for (String keyword : keywords) {
|
||||||
|
if (value.contains(keyword)) {
|
||||||
|
log.warn(MESSAGE_TEMPLATE, value, keyword);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package top.continew.starter.extension.crud.model.query;
|
package top.continew.starter.extension.crud.model.query;
|
||||||
|
|
||||||
import cn.hutool.core.net.URLDecoder;
|
|
||||||
import cn.hutool.core.text.CharSequenceUtil;
|
import cn.hutool.core.text.CharSequenceUtil;
|
||||||
import cn.hutool.core.util.ArrayUtil;
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
@@ -27,7 +26,6 @@ import top.continew.starter.data.core.util.SqlInjectionUtils;
|
|||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -60,7 +58,7 @@ public class SortQuery implements Serializable {
|
|||||||
}
|
}
|
||||||
ValidationUtils.throwIf(sort.length < 2, "排序条件非法");
|
ValidationUtils.throwIf(sort.length < 2, "排序条件非法");
|
||||||
List<Sort.Order> orders = new ArrayList<>(sort.length);
|
List<Sort.Order> orders = new ArrayList<>(sort.length);
|
||||||
if (CharSequenceUtil.contains(sort[0], URLDecoder.decode(StringConstants.COMMA, StandardCharsets.UTF_8))) {
|
if (CharSequenceUtil.contains(sort[0], StringConstants.COMMA)) {
|
||||||
// e.g "sort=createTime,desc&sort=name,asc"
|
// e.g "sort=createTime,desc&sort=name,asc"
|
||||||
for (String s : sort) {
|
for (String s : sort) {
|
||||||
List<String> sortList = CharSequenceUtil.splitTrim(s, StringConstants.COMMA);
|
List<String> sortList = CharSequenceUtil.splitTrim(s, StringConstants.COMMA);
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import cn.crane4j.core.support.OperateTemplate;
|
|||||||
import cn.hutool.core.bean.BeanUtil;
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
import cn.hutool.core.bean.copier.CopyOptions;
|
import cn.hutool.core.bean.copier.CopyOptions;
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.lang.Opt;
|
|
||||||
import cn.hutool.core.lang.tree.Tree;
|
import cn.hutool.core.lang.tree.Tree;
|
||||||
import cn.hutool.core.lang.tree.TreeNodeConfig;
|
import cn.hutool.core.lang.tree.TreeNodeConfig;
|
||||||
import cn.hutool.core.text.CharSequenceUtil;
|
import cn.hutool.core.text.CharSequenceUtil;
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ import cn.hutool.core.util.ReflectUtil;
|
|||||||
import cn.hutool.extra.spring.SpringUtil;
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
import org.apache.ibatis.plugin.*;
|
import org.apache.ibatis.mapping.MappedStatement;
|
||||||
|
import org.apache.ibatis.mapping.SqlCommandType;
|
||||||
|
import org.apache.ibatis.plugin.Interceptor;
|
||||||
import top.continew.starter.core.constant.StringConstants;
|
import top.continew.starter.core.constant.StringConstants;
|
||||||
import top.continew.starter.core.exception.BusinessException;
|
import top.continew.starter.core.exception.BusinessException;
|
||||||
import top.continew.starter.security.crypto.annotation.FieldEncrypt;
|
import top.continew.starter.security.crypto.annotation.FieldEncrypt;
|
||||||
@@ -46,39 +48,6 @@ public abstract class AbstractMyBatisInterceptor implements Interceptor {
|
|||||||
|
|
||||||
private static final Map<String, Map<String, FieldEncrypt>> ENCRYPT_PARAM_CACHE = new ConcurrentHashMap<>();
|
private static final Map<String, Map<String, FieldEncrypt>> ENCRYPT_PARAM_CACHE = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取加密参数
|
|
||||||
*
|
|
||||||
* @param mappedStatementId 映射语句 ID
|
|
||||||
* @return 加密参数
|
|
||||||
*/
|
|
||||||
public Map<String, FieldEncrypt> getEncryptParams(String mappedStatementId) {
|
|
||||||
return getEncryptParams(mappedStatementId, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取加密参数
|
|
||||||
*
|
|
||||||
* @param mappedStatementId 映射语句 ID
|
|
||||||
* @param parameterIndex 参数索引
|
|
||||||
* @return 加密参数
|
|
||||||
*/
|
|
||||||
public Map<String, FieldEncrypt> getEncryptParams(String mappedStatementId, Integer parameterIndex) {
|
|
||||||
return ENCRYPT_PARAM_CACHE
|
|
||||||
.computeIfAbsent(mappedStatementId, key -> getEncryptParamsNoCached(mappedStatementId, parameterIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取参数名称
|
|
||||||
*
|
|
||||||
* @param parameter 参数
|
|
||||||
* @return 参数名称
|
|
||||||
*/
|
|
||||||
public String getParameterName(Parameter parameter) {
|
|
||||||
Param param = parameter.getAnnotation(Param.class);
|
|
||||||
return null != param ? param.value() : parameter.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有字符串类型、需要加/解密的、有值字段
|
* 获取所有字符串类型、需要加/解密的、有值字段
|
||||||
*
|
*
|
||||||
@@ -114,13 +83,67 @@ public abstract class AbstractMyBatisInterceptor implements Interceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取参数列表(无缓存)
|
* 获取加密参数
|
||||||
|
*
|
||||||
|
* @param mappedStatement 映射语句
|
||||||
|
* @return 加密参数
|
||||||
|
*/
|
||||||
|
public Map<String, FieldEncrypt> getEncryptParams(MappedStatement mappedStatement) {
|
||||||
|
return getEncryptParams(mappedStatement, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取加密参数
|
||||||
|
*
|
||||||
|
* @param mappedStatement 映射语句
|
||||||
|
* @param parameterCount 参数数量
|
||||||
|
* @return 加密参数
|
||||||
|
*/
|
||||||
|
public Map<String, FieldEncrypt> getEncryptParams(MappedStatement mappedStatement, Integer parameterCount) {
|
||||||
|
String mappedStatementId = mappedStatement.getId();
|
||||||
|
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
|
||||||
|
if (SqlCommandType.UPDATE != sqlCommandType) {
|
||||||
|
return ENCRYPT_PARAM_CACHE.computeIfAbsent(mappedStatementId, key -> this
|
||||||
|
.getEncryptParams(mappedStatementId, parameterCount));
|
||||||
|
} else {
|
||||||
|
return this.getEncryptParams(mappedStatementId, parameterCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取参数名称
|
||||||
|
*
|
||||||
|
* @param parameter 参数
|
||||||
|
* @return 参数名称
|
||||||
|
*/
|
||||||
|
public String getParameterName(Parameter parameter) {
|
||||||
|
Param param = parameter.getAnnotation(Param.class);
|
||||||
|
return null != param ? param.value() : parameter.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取加密参数列表
|
||||||
*
|
*
|
||||||
* @param mappedStatementId 映射语句 ID
|
* @param mappedStatementId 映射语句 ID
|
||||||
* @param parameterIndex 参数数量
|
* @param parameterCount 参数数量
|
||||||
* @return 参数列表
|
* @return 加密参数列表
|
||||||
*/
|
*/
|
||||||
private Map<String, FieldEncrypt> getEncryptParamsNoCached(String mappedStatementId, Integer parameterIndex) {
|
private Map<String, FieldEncrypt> getEncryptParams(String mappedStatementId, Integer parameterCount) {
|
||||||
|
Method method = this.getMethod(mappedStatementId, parameterCount);
|
||||||
|
if (method == null) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
return this.getEncryptParams(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取映射方法
|
||||||
|
*
|
||||||
|
* @param mappedStatementId 映射语句 ID
|
||||||
|
* @param parameterCount 参数数量
|
||||||
|
* @return 映射方法
|
||||||
|
*/
|
||||||
|
private Method getMethod(String mappedStatementId, Integer parameterCount) {
|
||||||
try {
|
try {
|
||||||
String className = CharSequenceUtil.subBefore(mappedStatementId, StringConstants.DOT, true);
|
String className = CharSequenceUtil.subBefore(mappedStatementId, StringConstants.DOT, true);
|
||||||
String wrapperMethodName = CharSequenceUtil.subAfter(mappedStatementId, StringConstants.DOT, true);
|
String wrapperMethodName = CharSequenceUtil.subAfter(mappedStatementId, StringConstants.DOT, true);
|
||||||
@@ -131,17 +154,27 @@ public abstract class AbstractMyBatisInterceptor implements Interceptor {
|
|||||||
.orElse(wrapperMethodName);
|
.orElse(wrapperMethodName);
|
||||||
// 获取真实方法
|
// 获取真实方法
|
||||||
Optional<Method> methodOptional = Arrays.stream(ReflectUtil.getMethods(Class.forName(className), m -> {
|
Optional<Method> methodOptional = Arrays.stream(ReflectUtil.getMethods(Class.forName(className), m -> {
|
||||||
if (Objects.nonNull(parameterIndex)) {
|
if (parameterCount != null) {
|
||||||
return Objects.equals(m.getName(), methodName) && m.getParameterCount() == parameterIndex;
|
return Objects.equals(m.getName(), methodName) && m.getParameterCount() == parameterCount;
|
||||||
}
|
}
|
||||||
return Objects.equals(m.getName(), methodName);
|
return Objects.equals(m.getName(), methodName);
|
||||||
})).findFirst();
|
})).findFirst();
|
||||||
if (methodOptional.isEmpty()) {
|
return methodOptional.orElse(null);
|
||||||
return Collections.emptyMap();
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new BusinessException(e.getMessage());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取加密参数列表
|
||||||
|
*
|
||||||
|
* @param method 方法
|
||||||
|
* @return 加密参数列表
|
||||||
|
*/
|
||||||
|
private Map<String, FieldEncrypt> getEncryptParams(Method method) {
|
||||||
// 获取方法中的加密参数
|
// 获取方法中的加密参数
|
||||||
Map<String, FieldEncrypt> map = MapUtil.newHashMap();
|
Map<String, FieldEncrypt> map = MapUtil.newHashMap();
|
||||||
Parameter[] parameterArr = methodOptional.get().getParameters();
|
Parameter[] parameterArr = method.getParameters();
|
||||||
for (int i = 0; i < parameterArr.length; i++) {
|
for (int i = 0; i < parameterArr.length; i++) {
|
||||||
Parameter parameter = parameterArr[i];
|
Parameter parameter = parameterArr[i];
|
||||||
String parameterName = this.getParameterName(parameter);
|
String parameterName = this.getParameterName(parameter);
|
||||||
@@ -158,8 +191,5 @@ public abstract class AbstractMyBatisInterceptor implements Interceptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
throw new BusinessException(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,8 +109,10 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor {
|
|||||||
* @throws Exception /
|
* @throws Exception /
|
||||||
*/
|
*/
|
||||||
private void encryptMap(HashMap<String, Object> parameterMap, MappedStatement mappedStatement) throws Exception {
|
private void encryptMap(HashMap<String, Object> parameterMap, MappedStatement mappedStatement) throws Exception {
|
||||||
Map<String, FieldEncrypt> encryptParamMap = super.getEncryptParams(mappedStatement.getId(), parameterMap
|
Map<String, FieldEncrypt> encryptParamMap = super.getEncryptParams(mappedStatement);
|
||||||
.isEmpty() ? null : parameterMap.size() / 2);
|
if (encryptParamMap.isEmpty() && !parameterMap.isEmpty()) {
|
||||||
|
encryptParamMap = super.getEncryptParams(mappedStatement, parameterMap.size() / 2);
|
||||||
|
}
|
||||||
for (Map.Entry<String, FieldEncrypt> encryptParamEntry : encryptParamMap.entrySet()) {
|
for (Map.Entry<String, FieldEncrypt> encryptParamEntry : encryptParamMap.entrySet()) {
|
||||||
String parameterName = encryptParamEntry.getKey();
|
String parameterName = encryptParamEntry.getKey();
|
||||||
if (parameterName.startsWith(Constants.ENTITY)) {
|
if (parameterName.startsWith(Constants.ENTITY)) {
|
||||||
|
|||||||
@@ -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.web.autoconfigure.response;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.reflect.TypeUtils;
|
||||||
|
import org.springdoc.core.parsers.ReturnTypeParser;
|
||||||
|
import org.springframework.core.MethodParameter;
|
||||||
|
import top.continew.starter.apidoc.util.DocUtils;
|
||||||
|
import top.continew.starter.web.model.R;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SpringDoc 全局响应处理器
|
||||||
|
* <p>
|
||||||
|
* 接口文档全局添加响应格式 {@link R}
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author echo
|
||||||
|
* @since 2.5.2
|
||||||
|
*/
|
||||||
|
public class ApiDocGlobalResponseHandler implements ReturnTypeParser {
|
||||||
|
|
||||||
|
private static final Class<R> R_TYPE = R.class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取返回类型
|
||||||
|
*
|
||||||
|
* @param methodParameter 方法参数
|
||||||
|
* @return {@link Type }
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Type getReturnType(MethodParameter methodParameter) {
|
||||||
|
// 获取返回类型
|
||||||
|
Type returnType = ReturnTypeParser.super.getReturnType(methodParameter);
|
||||||
|
// 判断是否具有 RestController 注解
|
||||||
|
if (!DocUtils.hasRestControllerAnnotation(methodParameter.getContainingClass())) {
|
||||||
|
return returnType;
|
||||||
|
}
|
||||||
|
// 如果为 R<T> 则直接返回
|
||||||
|
if (returnType.getTypeName().contains("top.continew.starter.web.model.R")) {
|
||||||
|
return returnType;
|
||||||
|
}
|
||||||
|
// 如果是 void类型,则返回 R<Void>
|
||||||
|
if (returnType == void.class || returnType == Void.class) {
|
||||||
|
return TypeUtils.parameterize(R_TYPE, Void.class);
|
||||||
|
}
|
||||||
|
// 返回 R<T>
|
||||||
|
return TypeUtils.parameterize(R_TYPE, returnType);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -143,6 +143,17 @@ public class GlobalResponseAutoConfiguration {
|
|||||||
return messageSource;
|
return messageSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SpringDoc 全局响应处理器
|
||||||
|
*
|
||||||
|
* @return {@link ApiDocGlobalResponseHandler }
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public ApiDocGlobalResponseHandler apiDocGlobalResponseHandler() {
|
||||||
|
return new ApiDocGlobalResponseHandler();
|
||||||
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void postConstruct() {
|
public void postConstruct() {
|
||||||
log.debug("[ContiNew Starter] - Auto Configuration 'Web-Global Response' completed initialization.");
|
log.debug("[ContiNew Starter] - Auto Configuration 'Web-Global Response' completed initialization.");
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ import com.feiniaojin.gracefulresponse.data.ResponseStatus;
|
|||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import top.continew.starter.web.autoconfigure.response.GlobalResponseProperties;
|
import top.continew.starter.web.autoconfigure.response.GlobalResponseProperties;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 响应信息
|
* 响应信息
|
||||||
*
|
*
|
||||||
@@ -32,7 +30,7 @@ import java.util.Collections;
|
|||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
@Schema(description = "响应信息")
|
@Schema(description = "响应信息")
|
||||||
public class R implements Response {
|
public class R<T> implements Response {
|
||||||
|
|
||||||
private static final GlobalResponseProperties PROPERTIES = SpringUtil.getBean(GlobalResponseProperties.class);
|
private static final GlobalResponseProperties PROPERTIES = SpringUtil.getBean(GlobalResponseProperties.class);
|
||||||
private static final String DEFAULT_SUCCESS_CODE = PROPERTIES.getDefaultSuccessCode();
|
private static final String DEFAULT_SUCCESS_CODE = PROPERTIES.getDefaultSuccessCode();
|
||||||
@@ -49,7 +47,7 @@ public class R implements Response {
|
|||||||
/**
|
/**
|
||||||
* 状态信息
|
* 状态信息
|
||||||
*/
|
*/
|
||||||
@Schema(description = "状态信息", example = "操作成功")
|
@Schema(description = "状态信息", example = "ok")
|
||||||
private String msg;
|
private String msg;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -68,7 +66,7 @@ public class R implements Response {
|
|||||||
* 响应数据
|
* 响应数据
|
||||||
*/
|
*/
|
||||||
@Schema(description = "响应数据")
|
@Schema(description = "响应数据")
|
||||||
private Object data = Collections.emptyMap();
|
private T data;
|
||||||
|
|
||||||
public R() {
|
public R() {
|
||||||
}
|
}
|
||||||
@@ -78,7 +76,7 @@ public class R implements Response {
|
|||||||
this.setMsg(msg);
|
this.setMsg(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public R(String code, String msg, Object data) {
|
public R(String code, String msg, T data) {
|
||||||
this(code, msg);
|
this(code, msg);
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
@@ -97,7 +95,7 @@ public class R implements Response {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPayload(Object payload) {
|
public void setPayload(Object payload) {
|
||||||
this.data = payload;
|
this.data = (T)payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -123,11 +121,11 @@ public class R implements Response {
|
|||||||
this.msg = msg;
|
this.msg = msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getData() {
|
public T getData() {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setData(Object data) {
|
public void setData(T data) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user