From bf51837991746c5576d7baffcc2296fe8ca91586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E6=B3=BD=E5=A8=81?= <958142070@qq.com> Date: Wed, 14 Aug 2024 21:12:23 +0800 Subject: [PATCH] =?UTF-8?q?refactor(api-doc/web):=20=E9=87=8D=E6=9E=84doc?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E6=9E=9A=E4=B8=BE=E5=B1=95=E7=A4=BA=E5=A4=84?= =?UTF-8?q?=E7=90=86,=E5=A4=84=E7=90=86doc=20=E6=96=87=E6=A1=A3=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E5=93=8D=E5=BA=94=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SpringDocAutoConfiguration.java | 90 +----------- .../apidoc/handler/GenericEnumHandler.java | 102 ++++++++++++++ .../starter/apidoc/util/DocUtils.java | 132 ++++++++++++++++++ .../starter/apidoc/util/EnumTypeUtils.java | 73 ---------- .../GlobalResponseAutoConfiguration.java | 11 ++ .../handler/DocGenericResponseHandler.java | 54 +++++++ .../top/continew/starter/web/model/R.java | 12 +- 7 files changed, 309 insertions(+), 165 deletions(-) create mode 100644 continew-starter-api-doc/src/main/java/top/continew/starter/apidoc/handler/GenericEnumHandler.java create mode 100644 continew-starter-api-doc/src/main/java/top/continew/starter/apidoc/util/DocUtils.java delete mode 100644 continew-starter-api-doc/src/main/java/top/continew/starter/apidoc/util/EnumTypeUtils.java create mode 100644 continew-starter-web/src/main/java/top/continew/starter/web/handler/DocGenericResponseHandler.java diff --git a/continew-starter-api-doc/src/main/java/top/continew/starter/apidoc/autoconfigure/SpringDocAutoConfiguration.java b/continew-starter-api-doc/src/main/java/top/continew/starter/apidoc/autoconfigure/SpringDocAutoConfiguration.java index cfc97ef..bebdb50 100644 --- a/continew-starter-api-doc/src/main/java/top/continew/starter/apidoc/autoconfigure/SpringDocAutoConfiguration.java +++ b/continew-starter-api-doc/src/main/java/top/continew/starter/apidoc/autoconfigure/SpringDocAutoConfiguration.java @@ -48,15 +48,13 @@ 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.GenericEnumHandler; 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.*; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; /** * API 文档自动配置 @@ -158,94 +156,14 @@ public OpenAPIService openApiBuilder(Optional openAPI, /** * 自定义参数配置(针对 BaseEnum 展示枚举值和描述) * + * @return {@link GenericEnumHandler } * @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 + "" + this - .getDescMap(parameterType) + ""); - 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() + "" + this - .getDescMap(rawClass) + ""); - return schema; - }; + public GenericEnumHandler customParameterCustomizer() { + return new GenericEnumHandler(); } - /** - * 封装 Schema 配置 - * - * @param schema Schema - * @param enumClass 枚举类型 - * @since 2.4.0 - */ - private void configureSchema(Schema schema, Class enumClass) { - BaseEnum[] enums = (BaseEnum[])enumClass.getEnumConstants(); - // 设置枚举可用值 - List 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 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() { diff --git a/continew-starter-api-doc/src/main/java/top/continew/starter/apidoc/handler/GenericEnumHandler.java b/continew-starter-api-doc/src/main/java/top/continew/starter/apidoc/handler/GenericEnumHandler.java new file mode 100644 index 0000000..87ee0a8 --- /dev/null +++ b/continew-starter-api-doc/src/main/java/top/continew/starter/apidoc/handler/GenericEnumHandler.java @@ -0,0 +1,102 @@ +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 类型的枚举参数和属性进行处理 + * + * @Author echo + * @date 2024/08/12 + */ +public class GenericEnumHandler 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 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 + "" + DocUtils.getDescMap(enumClass) + ""; + } + + /** + * 解析原始类 + * + * @param type 类型 + * @return 原始类的 Class 对象 + */ + private Class resolveRawClass(Type type) { + if (type instanceof SimpleType) { + return ((SimpleType) type).getRawClass(); + } else if (type instanceof CollectionType) { + return ((CollectionType) type).getContentType().getRawClass(); + } else { + return Object.class; + } + } +} diff --git a/continew-starter-api-doc/src/main/java/top/continew/starter/apidoc/util/DocUtils.java b/continew-starter-api-doc/src/main/java/top/continew/starter/apidoc/util/DocUtils.java new file mode 100644 index 0000000..1961b7c --- /dev/null +++ b/continew-starter-api-doc/src/main/java/top/continew/starter/apidoc/util/DocUtils.java @@ -0,0 +1,132 @@ +package top.continew.starter.apidoc.util; + +import cn.hutool.core.collection.CollUtil; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.springframework.web.bind.annotation.RestController; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 接口文档工具类 + * + * @Author echo + * @date 2024/07/31 + */ +public class DocUtils { + + private DocUtils() { + } + + /** + * 获取枚举值类型 + * + * @param enumClass 枚举类型 + * @return 枚举值类型 + */ + public static String getEnumValueTypeAsString(Class enumClass) { + // 获取枚举类实现的所有接口 + Type[] interfaces = enumClass.getGenericInterfaces(); + // 定义枚举值类型的映射 + Map, 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 getDescMap(Class enumClass) { + BaseEnum[] enums = (BaseEnum[]) enumClass.getEnumConstants(); + return Arrays.stream(enums) + .collect(Collectors.toMap(BaseEnum::getValue, BaseEnum::getDescription, (a, b) -> a, LinkedHashMap::new)); + } + + /** + * 将collection转化为Set集合,但是两者的泛型不同
+ * {@code Collection ------> Set } + * + * @param collection 需要转化的集合 + * @param function collection中的泛型转化为set泛型的lambda表达式 + * @param collection中的泛型 + * @param Set中的泛型 + * @return 转化后的Set + */ + public static Set toSet(Collection collection, Function function) { + if (CollUtil.isEmpty(collection) || function == null) { + return CollUtil.newHashSet(); + } + return collection + .stream() + .map(function) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } +} \ No newline at end of file diff --git a/continew-starter-api-doc/src/main/java/top/continew/starter/apidoc/util/EnumTypeUtils.java b/continew-starter-api-doc/src/main/java/top/continew/starter/apidoc/util/EnumTypeUtils.java deleted file mode 100644 index 5b6e04f..0000000 --- a/continew-starter-api-doc/src/main/java/top/continew/starter/apidoc/util/EnumTypeUtils.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - *

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

- * http://www.gnu.org/licenses/lgpl.html - *

- * 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"; - } -} diff --git a/continew-starter-web/src/main/java/top/continew/starter/web/autoconfigure/response/GlobalResponseAutoConfiguration.java b/continew-starter-web/src/main/java/top/continew/starter/web/autoconfigure/response/GlobalResponseAutoConfiguration.java index c04fb94..33da3db 100644 --- a/continew-starter-web/src/main/java/top/continew/starter/web/autoconfigure/response/GlobalResponseAutoConfiguration.java +++ b/continew-starter-web/src/main/java/top/continew/starter/web/autoconfigure/response/GlobalResponseAutoConfiguration.java @@ -37,6 +37,7 @@ import top.continew.starter.core.util.GeneralPropertySourceFactory; import java.util.Locale; +import top.continew.starter.web.handler.DocGenericResponseHandler; /** * 全局响应自动配置 @@ -143,6 +144,16 @@ public MessageSource messageSource() { return messageSource; } + /** + * SpringDoc 通用响应处理 - 仅处理 doc 文档响应格式 + * + * @return {@link DocGenericResponseHandler } + */ + @Bean + public DocGenericResponseHandler genericResponseHandler() { + return new DocGenericResponseHandler(); + } + @PostConstruct public void postConstruct() { log.debug("[ContiNew Starter] - Auto Configuration 'Web-Global Response' completed initialization."); diff --git a/continew-starter-web/src/main/java/top/continew/starter/web/handler/DocGenericResponseHandler.java b/continew-starter-web/src/main/java/top/continew/starter/web/handler/DocGenericResponseHandler.java new file mode 100644 index 0000000..43b7520 --- /dev/null +++ b/continew-starter-web/src/main/java/top/continew/starter/web/handler/DocGenericResponseHandler.java @@ -0,0 +1,54 @@ +package top.continew.starter.web.handler; + + + +import org.apache.commons.lang3.reflect.TypeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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 通用响应处理程序 --仅处理 doc 文档响应格式 + *

+ 全局添加响应格式 {@link R} + * + * @Author echo + * @date 2024/08/12 + */ +public class DocGenericResponseHandler implements ReturnTypeParser { + private static final Logger log = LoggerFactory.getLogger(DocGenericResponseHandler.class); + + private static final Class 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 则直接返回 + if (returnType.getTypeName().contains("top.continew.starter.web.model.R")) { + return returnType; + } + // 如果是void类型,则返回R + if (returnType == void.class || returnType == Void.class) { + return TypeUtils.parameterize(R_TYPE, Void.class); + } + // 返回R + return TypeUtils.parameterize(R_TYPE, returnType); + } +} diff --git a/continew-starter-web/src/main/java/top/continew/starter/web/model/R.java b/continew-starter-web/src/main/java/top/continew/starter/web/model/R.java index 30b64b5..befcdc1 100644 --- a/continew-starter-web/src/main/java/top/continew/starter/web/model/R.java +++ b/continew-starter-web/src/main/java/top/continew/starter/web/model/R.java @@ -32,7 +32,7 @@ * @since 1.0.0 */ @Schema(description = "响应信息") -public class R implements Response { +public class R implements Response { private static final GlobalResponseProperties PROPERTIES = SpringUtil.getBean(GlobalResponseProperties.class); private static final String DEFAULT_SUCCESS_CODE = PROPERTIES.getDefaultSuccessCode(); @@ -68,7 +68,7 @@ public class R implements Response { * 响应数据 */ @Schema(description = "响应数据") - private Object data = Collections.emptyMap(); + private T data; public R() { } @@ -78,7 +78,7 @@ public R(String code, String msg) { this.setMsg(msg); } - public R(String code, String msg, Object data) { + public R(String code, String msg, T data) { this(code, msg); this.data = data; } @@ -97,7 +97,7 @@ public ResponseStatus getStatus() { @Override public void setPayload(Object payload) { - this.data = payload; + this.data = (T)payload; } @Override @@ -123,11 +123,11 @@ public void setMsg(String msg) { this.msg = msg; } - public Object getData() { + public T getData() { return data; } - public void setData(Object data) { + public void setData(T data) { this.data = data; }