From 000018826ade94a53fc9fa296b7c6a5c33ad4f46 Mon Sep 17 00:00:00 2001 From: jerrywei <2073825933@qq.com> Date: Tue, 10 Sep 2024 02:18:12 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E3=80=90=E4=BC=98=E5=8C=96=E3=80=91SWAGGER?= =?UTF-8?q?:=20=E4=BF=AE=E5=A4=8Dswagger=E6=96=87=E6=A1=A3A=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E5=BC=95=E7=94=A8B=E5=B1=9E=E6=80=A7=E6=97=B6=20A?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E4=B8=AD=E5=AE=9A=E4=B9=89B=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E4=B8=8A=E7=9A=84=20@Schema=20=E6=B3=A8=E8=A7=A3?= =?UTF-8?q?=E4=B8=8D=E7=94=9F=E6=95=88=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/YudaoSwaggerAutoConfiguration.java | 9 ++ .../SchemaPropertyFixModelConverter.java | 96 +++++++++++++++++++ .../src/main/resources/application.yaml | 1 + 3 files changed, 106 insertions(+) create mode 100644 yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/core/converter/SchemaPropertyFixModelConverter.java diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java index a131d1f2d8..5b7456961f 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.framework.swagger.config; +import cn.iocoder.yudao.framework.swagger.core.converter.SchemaPropertyFixModelConverter; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Contact; @@ -100,6 +101,14 @@ public class YudaoSwaggerAutoConfiguration { propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider); } + /** + * 修复swagger文档A属性引用B属性时 A属性中定义B字段上的 @Schema 注解不生效问题 + */ + @Bean + public SchemaPropertyFixModelConverter schemaPropertyFixModelConverter(){ + return new SchemaPropertyFixModelConverter(); + } + // ========== 分组 OpenAPI 配置 ========== /** diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/core/converter/SchemaPropertyFixModelConverter.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/core/converter/SchemaPropertyFixModelConverter.java new file mode 100644 index 0000000000..ea7ba4fa81 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/core/converter/SchemaPropertyFixModelConverter.java @@ -0,0 +1,96 @@ +package cn.iocoder.yudao.framework.swagger.core.converter; + +import cn.hutool.core.util.StrUtil; +import io.swagger.v3.core.converter.AnnotatedType; +import io.swagger.v3.core.converter.ModelConverter; +import io.swagger.v3.core.converter.ModelConverterContext; +import io.swagger.v3.core.util.RefUtils; +import io.swagger.v3.oas.models.media.Schema; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Optional; + +/** + *

+ * swagger3的规范? + * 如果属性中的字段是引用另一个类时, 那么底层会被包装成只有$ref的Schema, 导致属性上的@Schema注解信息直接丢失了 + * 那么就会导致前端不显示属性上的注解信息了, 这里手动将信息和$ref字段放在一起, 保证前端可以正常显现引用属性上@Schema注解的信息 + * 且 springdoc.api-docs.version 需配置 3.1.0 版本, 3.0.1会在序列化时判断$ref有值则直接过滤除了$ref字段以外的信息 + *

+ * + * @author jerryskyr + */ +public class SchemaPropertyFixModelConverter implements ModelConverter { + + + + /** + * Instantiates a new Polymorphic model converter. + * + */ + public SchemaPropertyFixModelConverter() { + } + + @Override + @SuppressWarnings("rawtypes") + public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterator chain) { + Schema resolvedSchema = (chain.hasNext()) ? chain.next().resolve(type, context, chain) : null; + if (resolvedSchema == null || resolvedSchema.get$ref() == null) { + return resolvedSchema; + } + return supplementarySchema(type, resolvedSchema, context.getDefinedModels().values()); + } + + /** + * 补充 schema 信息. + * + * @param type the type + * @param schema the schema + * @param schemas the schemas + * @return the schema + */ + @SuppressWarnings("rawtypes") + private Schema supplementarySchema(AnnotatedType type, Schema schema, Collection schemas) { + // 拿到真实的类型定义 + Optional schemaDefine = schemas.stream() + .filter(s -> schema.get$ref().equals(RefUtils.constructRef(s.getName()))) + .findAny(); + if (!schemaDefine.isPresent()) { + return schema; + } + + // 拿到真实定义下的类型下的属性定义 + Map properties = schemaDefine.get().getProperties(); + if (properties == null) { + return schema; + } + + for (Object propertie : properties.values()) { + if (propertie instanceof Schema) { + // 拿到属性的真实定义 + Schema propertieSchema = (Schema) propertie; + + String propertieRef = propertieSchema.get$ref(); + // 如果是引用类型, 则将真实定义的属性, 赋值到引用定义中 + if (propertieRef != null) { + schemas.stream() + .filter(s -> propertieRef.equals(RefUtils.constructRef(s.getName()))) + .findAny() + .ifPresent(propertieSchemaDefine -> { + if (StrUtil.isEmpty(propertieSchema.getTitle())) { + propertieSchema.setTitle(propertieSchemaDefine.getTitle()); + } + if (StrUtil.isEmpty(propertieSchema.getDescription())) { + propertieSchema.setDescription(propertieSchemaDefine.getDescription()); + } + }); + } + + } + } + + return schema; + } +} diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index baf68657e0..0d38db8fbc 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -40,6 +40,7 @@ server: springdoc: api-docs: + version: OPENAPI_3_1 enabled: true path: /v3/api-docs swagger-ui: From 16328e64c8f4d19001674903a2c29c614cd58925 Mon Sep 17 00:00:00 2001 From: jerrywei <2073825933@qq.com> Date: Wed, 11 Sep 2024 23:35:26 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E3=80=90=E4=BC=98=E5=8C=96=E3=80=91SWAGGER?= =?UTF-8?q?:=20=E4=BF=AE=E5=A4=8Dswagger=E6=96=87=E6=A1=A3A=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E5=BC=95=E7=94=A8B=E5=B1=9E=E6=80=A7=E6=97=B6=20A?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E4=B8=AD=E5=AE=9A=E4=B9=89B=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E4=B8=8A=E7=9A=84=20@Schema=20=E6=B3=A8=E8=A7=A3?= =?UTF-8?q?=E4=B8=8D=E7=94=9F=E6=95=88=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/YudaoSwaggerAutoConfiguration.java | 5 +- .../SchemaPropertyFixModelConverter.java | 94 +++++++++++++++---- 2 files changed, 80 insertions(+), 19 deletions(-) diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java index 5b7456961f..994ce93555 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java @@ -15,6 +15,7 @@ import org.springdoc.core.*; import org.springdoc.core.customizers.OpenApiBuilderCustomizer; import org.springdoc.core.customizers.ServerBaseUrlCustomizer; import org.springdoc.core.providers.JavadocProvider; +import org.springdoc.core.providers.ObjectMapperProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -105,8 +106,8 @@ public class YudaoSwaggerAutoConfiguration { * 修复swagger文档A属性引用B属性时 A属性中定义B字段上的 @Schema 注解不生效问题 */ @Bean - public SchemaPropertyFixModelConverter schemaPropertyFixModelConverter(){ - return new SchemaPropertyFixModelConverter(); + public SchemaPropertyFixModelConverter schemaPropertyFixModelConverter(ObjectMapperProvider springDocObjectMapper){ + return new SchemaPropertyFixModelConverter(springDocObjectMapper); } // ========== 分组 OpenAPI 配置 ========== diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/core/converter/SchemaPropertyFixModelConverter.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/core/converter/SchemaPropertyFixModelConverter.java index ea7ba4fa81..18410adb65 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/core/converter/SchemaPropertyFixModelConverter.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/core/converter/SchemaPropertyFixModelConverter.java @@ -1,16 +1,22 @@ package cn.iocoder.yudao.framework.swagger.core.converter; import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; import io.swagger.v3.core.converter.AnnotatedType; import io.swagger.v3.core.converter.ModelConverter; import io.swagger.v3.core.converter.ModelConverterContext; +import io.swagger.v3.core.util.AnnotationsUtils; import io.swagger.v3.core.util.RefUtils; import io.swagger.v3.oas.models.media.Schema; +import org.apache.commons.lang3.StringUtils; +import org.springdoc.core.providers.ObjectMapperProvider; -import java.util.Collection; -import java.util.Iterator; -import java.util.Map; -import java.util.Optional; +import java.lang.annotation.Annotation; +import java.util.*; /** *

@@ -24,13 +30,18 @@ import java.util.Optional; */ public class SchemaPropertyFixModelConverter implements ModelConverter { + /** + * The Spring doc object mapper. + */ + private final ObjectMapperProvider springDocObjectMapper; /** * Instantiates a new Polymorphic model converter. * */ - public SchemaPropertyFixModelConverter() { + public SchemaPropertyFixModelConverter(ObjectMapperProvider springDocObjectMapper) { + this.springDocObjectMapper = springDocObjectMapper; } @Override @@ -51,7 +62,7 @@ public class SchemaPropertyFixModelConverter implements ModelConverter { * @param schemas the schemas * @return the schema */ - @SuppressWarnings("rawtypes") + @SuppressWarnings("all") private Schema supplementarySchema(AnnotatedType type, Schema schema, Collection schemas) { // 拿到真实的类型定义 Optional schemaDefine = schemas.stream() @@ -67,6 +78,56 @@ public class SchemaPropertyFixModelConverter implements ModelConverter { return schema; } + // 获取类型 + ObjectMapper _mapper = springDocObjectMapper.jsonMapper(); + final JavaType javaType; + if (type.getType() instanceof JavaType) { + javaType = (JavaType) type.getType(); + } else { + javaType = _mapper.constructType(type.getType()); + } + + // 解析类属性描述 + final BeanDescription beanDesc; + { + BeanDescription recurBeanDesc = _mapper.getSerializationConfig().introspect(javaType); + + HashSet visited = new HashSet<>(); + JsonSerialize jsonSerialize = recurBeanDesc.getClassAnnotations().get(JsonSerialize.class); + while (jsonSerialize != null && !Void.class.equals(jsonSerialize.as())) { + String asName = jsonSerialize.as().getName(); + if (visited.contains(asName)) break; + visited.add(asName); + + recurBeanDesc = _mapper.getSerializationConfig().introspect( + _mapper.constructType(jsonSerialize.as()) + ); + jsonSerialize = recurBeanDesc.getClassAnnotations().get(JsonSerialize.class); + } + beanDesc = recurBeanDesc; + } + + // 解析属性上的注解 + List propertiesDefine = beanDesc.findProperties(); + Map defineMap = new HashMap<>(); + for (BeanPropertyDefinition propDef : propertiesDefine) { + List annotationList = new ArrayList<>(); + for (Annotation a : propDef.getPrimaryMember().annotations()) { + annotationList.add(a); + } + Annotation[] annotations = annotationList.toArray(new Annotation[annotationList.size()]); + io.swagger.v3.oas.annotations.media.Schema ctxSchema = AnnotationsUtils.getSchemaAnnotation(annotations); + + String propSchemaName = propDef.getName(); + if (AnnotationsUtils.hasSchemaAnnotation(ctxSchema)) { + if (!StringUtils.isBlank(ctxSchema.name())) { + propSchemaName = ctxSchema.name(); + } + } + defineMap.put(propSchemaName, ctxSchema); + } + + // 重新补充属性描述 for (Object propertie : properties.values()) { if (propertie instanceof Schema) { // 拿到属性的真实定义 @@ -75,17 +136,16 @@ public class SchemaPropertyFixModelConverter implements ModelConverter { String propertieRef = propertieSchema.get$ref(); // 如果是引用类型, 则将真实定义的属性, 赋值到引用定义中 if (propertieRef != null) { - schemas.stream() - .filter(s -> propertieRef.equals(RefUtils.constructRef(s.getName()))) - .findAny() - .ifPresent(propertieSchemaDefine -> { - if (StrUtil.isEmpty(propertieSchema.getTitle())) { - propertieSchema.setTitle(propertieSchemaDefine.getTitle()); - } - if (StrUtil.isEmpty(propertieSchema.getDescription())) { - propertieSchema.setDescription(propertieSchemaDefine.getDescription()); - } - }); + io.swagger.v3.oas.annotations.media.Schema propertieSchemaDefine = defineMap.get(propertieSchema.getName()); + + if (AnnotationsUtils.hasSchemaAnnotation(propertieSchemaDefine)) { + if (!StrUtil.isBlank(propertieSchemaDefine.title())) { + propertieSchema.setTitle(propertieSchemaDefine.title()); + } + if (!StrUtil.isBlank(propertieSchemaDefine.description())) { + propertieSchema.setDescription(propertieSchemaDefine.description()); + } + } } }