build(doc):升级springdoc (#631)
* build(doc):升级springdoc * Update pom.xml * Update SpringDocCustomizerConfiguration.java * 增加单独的类处理 * Update ResponseWrapperConverter.java * 增加普通类型的处理 * Update ResponseWrapperConverter.java * Update ResponseWrapperConverter.java * Update SpringDocCustomizerConfiguration.java * 增加泛型转换 * Update ResponseWrapperConverter.java * Update ResponseWrapperConverter.java * Update pom.xml * openai参数配置化 --------- Co-authored-by: 老周 <zh.sqy@qq.com>
This commit is contained in:
parent
3ae7e668c2
commit
c57dbda68a
|
|
@ -74,7 +74,13 @@
|
|||
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-webflux-core</artifactId>
|
||||
<artifactId>springdoc-openapi-starter-webflux-api</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,251 @@
|
|||
package org.jetlinks.community.configure.doc;
|
||||
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
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.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import io.swagger.v3.oas.models.SpecVersion;
|
||||
import io.swagger.v3.oas.models.media.MediaType;
|
||||
import io.swagger.v3.oas.models.media.Schema;
|
||||
import io.swagger.v3.oas.models.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.models.responses.ApiResponses;
|
||||
import org.apache.commons.lang3.ClassUtils;
|
||||
import org.hswebframework.web.api.crud.entity.EntityFactory;
|
||||
import org.hswebframework.web.crud.web.ResponseMessage;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springdoc.core.converters.ResponseSupportConverter;
|
||||
import org.springdoc.core.customizers.GlobalOperationComponentsCustomizer;
|
||||
import org.springdoc.core.providers.ObjectMapperProvider;
|
||||
import org.springdoc.core.utils.SpringDocAnnotationsUtils;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
public class ResponseWrapperConverter extends ResponseSupportConverter implements GlobalOperationComponentsCustomizer, Ordered {
|
||||
|
||||
private final EntityFactory entityFactory;
|
||||
|
||||
private final ObjectMapperProvider provider;
|
||||
|
||||
public ResponseWrapperConverter(EntityFactory entityFactory, ObjectMapperProvider springDocObjectMapper) {
|
||||
super(springDocObjectMapper);
|
||||
this.entityFactory = entityFactory;
|
||||
this.provider = springDocObjectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Schema<?> resolve(AnnotatedType _type, ModelConverterContext context, Iterator<ModelConverter> chain) {
|
||||
JavaType javaType = provider.jsonMapper().constructType(_type.getType());
|
||||
if (javaType != null) {
|
||||
_type.type(provider
|
||||
.jsonMapper()
|
||||
.constructType(getRealType(ResolvableType.forType(_type.getType())).getType()));
|
||||
}
|
||||
return super.resolve(_type, context, chain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Operation customize(Operation operation, Components components, HandlerMethod handlerMethod) {
|
||||
MethodParameter parameter = handlerMethod.getReturnType();
|
||||
ApiResponses responses = operation.getResponses();
|
||||
|
||||
if (responses != null) {
|
||||
// 只展示2xx的响应
|
||||
responses
|
||||
.keySet()
|
||||
.stream()
|
||||
.filter(code -> !code.startsWith("2"))
|
||||
.collect(Collectors.toSet())
|
||||
.forEach(operation.getResponses()::remove);
|
||||
|
||||
// 原始返回类型
|
||||
ResolvableType originType = ResolvableType.forMethodParameter(
|
||||
handlerMethod.getReturnType(),
|
||||
ResolvableType.forType(handlerMethod.getBeanType()));
|
||||
|
||||
for (Map.Entry<String, ApiResponse> entry : responses.entrySet()) {
|
||||
if (entry.getValue().getContent() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Map.Entry<String, MediaType> typeEntry :
|
||||
entry.getValue().getContent().entrySet()) {
|
||||
|
||||
Schema<?> schema = typeEntry.getValue().getSchema();
|
||||
if (schema != null) {
|
||||
ResolvableType type = resolveWrappedType(originType);
|
||||
org.springframework.http.MediaType mediaType = org.springframework.http.MediaType.parseMediaType(typeEntry.getKey());
|
||||
// 流式响应,不包装.
|
||||
if (org.springframework.http.MediaType.TEXT_EVENT_STREAM.includes(mediaType)
|
||||
|| org.springframework.http.MediaType.APPLICATION_NDJSON.includes(mediaType)) {
|
||||
type = type.getGeneric(0);
|
||||
}
|
||||
if (originType.equalsType(type)) {
|
||||
continue;
|
||||
}
|
||||
Type _type = provider
|
||||
.jsonMapper()
|
||||
.getTypeFactory()
|
||||
.constructType(type.getType());
|
||||
|
||||
Schema<?> schema_ = SpringDocAnnotationsUtils
|
||||
.extractSchema(components,
|
||||
_type,
|
||||
null,
|
||||
parameter.getParameterAnnotations(),
|
||||
SpecVersion.V31
|
||||
);
|
||||
|
||||
typeEntry.getValue().schema(schema_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
|
||||
public Type resolveWrappedType(Type type) {
|
||||
return resolveWrappedType(ResolvableType.forType(type)).getType();
|
||||
}
|
||||
|
||||
public ResolvableType resolveWrappedType(ResolvableType type) {
|
||||
Class<?> typeClass = type.toClass();
|
||||
|
||||
@SuppressWarnings("all")
|
||||
Class<ResponseMessage> msgType = entityFactory.getInstanceType(ResponseMessage.class);
|
||||
|
||||
// 处理响应式类型
|
||||
if (Publisher.class.isAssignableFrom(typeClass)) {
|
||||
ResolvableType actualType = type.getGeneric(0);
|
||||
// 如果已经是ResponseEntity或者ResponseMessage
|
||||
if (ResponseEntity.class.isAssignableFrom(actualType.toClass()) ||
|
||||
ResponseMessage.class.isAssignableFrom(actualType.toClass())) {
|
||||
ResolvableType real = getRealType(actualType.getGeneric(0));
|
||||
return ResolvableType
|
||||
.forClassWithGenerics(actualType.toClass(), real);
|
||||
}
|
||||
ResolvableType realType = getRealType(actualType);
|
||||
// flux 返回List
|
||||
if (Flux.class.isAssignableFrom(type.toClass())) {
|
||||
return ResolvableType
|
||||
.forClassWithGenerics(
|
||||
msgType,
|
||||
ResolvableType.forClassWithGenerics(List.class, realType)
|
||||
);
|
||||
}
|
||||
|
||||
return ResolvableType
|
||||
.forClassWithGenerics(
|
||||
msgType,
|
||||
realType
|
||||
);
|
||||
}
|
||||
|
||||
// 处理ResponseEntity 或者 ResponseMessage
|
||||
if (ResponseEntity.class.isAssignableFrom(typeClass)
|
||||
|| ResponseMessage.class.isAssignableFrom(typeClass)) {
|
||||
ResolvableType realType = getRealType(type.getGeneric(0));
|
||||
return ResolvableType
|
||||
.forClassWithGenerics(type.toClass(), realType);
|
||||
}
|
||||
|
||||
// 其他类型,直接获取真实类型并包装到ResponseMessage中
|
||||
ResolvableType realType = getRealType(type);
|
||||
|
||||
return ResolvableType.forClassWithGenerics(msgType, realType);
|
||||
}
|
||||
|
||||
private ResolvableType getRealType(ResolvableType type) {
|
||||
if (isIgnoreWrapped(type.getType())) {
|
||||
return type;
|
||||
}
|
||||
|
||||
Class<?> typeClazz = type.toClass();
|
||||
// Iterable
|
||||
if (Iterable.class.isAssignableFrom(typeClazz)) {
|
||||
ResolvableType _type = ResolvableType
|
||||
.forType(typeClazz)
|
||||
.as(Iterable.class);
|
||||
return ResolvableType.forClassWithGenerics(
|
||||
Iterable.class, getRealType(_type.getGeneric(0))
|
||||
);
|
||||
}
|
||||
// Map<?,?>
|
||||
if (Map.class.isAssignableFrom(typeClazz)) {
|
||||
ResolvableType _type = ResolvableType
|
||||
.forType(typeClazz)
|
||||
.as(Map.class);
|
||||
return ResolvableType.forClassWithGenerics(
|
||||
Map.class,
|
||||
_type.getGeneric(0),
|
||||
getRealType(_type.getGeneric(1))
|
||||
);
|
||||
}
|
||||
if (typeClazz != Object.class) {
|
||||
Class<?> t = entityFactory.getInstanceType(typeClazz);
|
||||
if (t == null) {
|
||||
return type;
|
||||
}
|
||||
ResolvableType resolved = ResolvableType.forClass(t);
|
||||
ResolvableType[] generics = resolved.getGenerics();
|
||||
ResolvableType[] typeGenerics = type.getGenerics();
|
||||
// 泛型
|
||||
// todo 不同长度的匹配?
|
||||
if (generics.length > 0 && generics.length == typeGenerics.length) {
|
||||
return ResolvableType.forClassWithGenerics(
|
||||
t,
|
||||
Arrays.stream(typeGenerics)
|
||||
.map(this::getRealType)
|
||||
.toArray(ResolvableType[]::new)
|
||||
);
|
||||
}
|
||||
|
||||
return ResolvableType.forClass(t);
|
||||
}
|
||||
|
||||
// 如果不是Class类型,则直接返回原类型
|
||||
return type;
|
||||
}
|
||||
|
||||
private boolean isIgnoreWrapped(Type type) {
|
||||
if (type == null) {
|
||||
return true;
|
||||
}
|
||||
if (type instanceof Class<?> clazz) {
|
||||
return clazz == Object.class
|
||||
|| ClassUtils.isPrimitiveOrWrapper(clazz)
|
||||
|| CharSequence.class.isAssignableFrom(clazz)
|
||||
|| Enum.class.isAssignableFrom(clazz);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Operation customize(Operation operation, HandlerMethod handlerMethod) {
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,69 +1,21 @@
|
|||
package org.jetlinks.community.configure.doc;
|
||||
|
||||
import org.hswebframework.web.api.crud.entity.EntityFactory;
|
||||
import org.hswebframework.web.crud.web.ResponseMessage;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springdoc.core.ReturnTypeParser;
|
||||
import org.springdoc.webflux.core.SpringDocWebFluxConfiguration;
|
||||
import org.springdoc.core.converters.ResponseSupportConverter;
|
||||
import org.springdoc.core.providers.ObjectMapperProvider;
|
||||
import org.springdoc.webflux.core.configuration.SpringDocWebFluxConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@AutoConfigureBefore(SpringDocWebFluxConfiguration.class)
|
||||
public class SpringDocCustomizerConfiguration {
|
||||
|
||||
@Bean
|
||||
public ReturnTypeParser operationCustomizer(EntityFactory factory) {
|
||||
|
||||
return new ReturnTypeParser() {
|
||||
@Override
|
||||
public Type getReturnType(MethodParameter methodParameter) {
|
||||
Type type = ReturnTypeParser.super.getReturnType(methodParameter);
|
||||
|
||||
if (type instanceof ParameterizedType) {
|
||||
ParameterizedType parameterizedType = ((ParameterizedType) type);
|
||||
Type rawType = parameterizedType.getRawType();
|
||||
if (rawType instanceof Class && Publisher.class.isAssignableFrom(((Class<?>) rawType))) {
|
||||
Type actualType = parameterizedType.getActualTypeArguments()[0];
|
||||
|
||||
if (actualType instanceof ParameterizedType) {
|
||||
actualType = ((ParameterizedType) actualType).getRawType();
|
||||
}
|
||||
if (actualType == ResponseEntity.class || actualType == ResponseMessage.class) {
|
||||
return type;
|
||||
}
|
||||
boolean returnList = Flux.class.isAssignableFrom(((Class<?>) rawType));
|
||||
|
||||
//统一返回ResponseMessage
|
||||
return ResolvableType
|
||||
.forClassWithGenerics(
|
||||
Mono.class,
|
||||
ResolvableType.forClassWithGenerics(
|
||||
factory.getInstanceType(ResponseMessage.class),
|
||||
returnList ?
|
||||
ResolvableType.forClassWithGenerics(
|
||||
List.class,
|
||||
ResolvableType.forType(parameterizedType.getActualTypeArguments()[0])
|
||||
) :
|
||||
ResolvableType.forType(parameterizedType.getActualTypeArguments()[0])
|
||||
))
|
||||
.getType();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
};
|
||||
public ResponseSupportConverter responseSupportConverter(EntityFactory entityFactory,
|
||||
ObjectMapperProvider springDocObjectMapper) {
|
||||
return new ResponseWrapperConverter(entityFactory, springDocObjectMapper);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -298,6 +298,12 @@
|
|||
<groupId>org.hswebframework.web</groupId>
|
||||
<artifactId>hsweb-access-logging-aop</artifactId>
|
||||
<version>${hsweb.framework.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
|
@ -307,13 +313,13 @@
|
|||
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-webflux-ui</artifactId>
|
||||
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-springdoc-ui</artifactId>
|
||||
<version>2.0.8</version>
|
||||
<artifactId>knife4j-openapi3-ui</artifactId>
|
||||
<version>4.4.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
|
|
|||
|
|
@ -226,7 +226,20 @@ management:
|
|||
metrics:
|
||||
export:
|
||||
enabled: false
|
||||
# knife4j的增强配置,不需要增强可以不配
|
||||
knife4j:
|
||||
enable: true
|
||||
setting:
|
||||
language: zh_cn
|
||||
springdoc:
|
||||
openapi:
|
||||
info:
|
||||
title: "jetlinks"
|
||||
description: "jetlinks平台API"
|
||||
version: "2.10"
|
||||
api-docs:
|
||||
enabled: true
|
||||
path: /v3/api-docs
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
# packages-to-scan: org.jetlinks
|
||||
|
|
|
|||
23
pom.xml
23
pom.xml
|
|
@ -52,11 +52,11 @@
|
|||
<log4j.version>2.24.3</log4j.version>
|
||||
<slf4j.version>2.0.16</slf4j.version>
|
||||
<logback.version>1.5.12</logback.version>
|
||||
<springdoc.version>1.8.0</springdoc.version>
|
||||
<springdoc.version>2.8.6</springdoc.version>
|
||||
<jackson.version>2.17.1</jackson.version>
|
||||
<gson.version>2.11.0</gson.version>
|
||||
<opentelemetry.version>1.39.0</opentelemetry.version>
|
||||
<swagger.version>2.2.22</swagger.version>
|
||||
<swagger.version>2.2.29</swagger.version>
|
||||
<jna.version>5.12.1</jna.version>
|
||||
<aliyun.sdk.core>4.5.2</aliyun.sdk.core>
|
||||
<jsonata.version>2.4.1</jsonata.version>
|
||||
|
|
@ -275,6 +275,19 @@
|
|||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<compilerArgs>
|
||||
<arg>-parameters</arg>
|
||||
</compilerArgs>
|
||||
<source>${project.build.jdk}</source>
|
||||
<target>${project.build.jdk}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
|
@ -362,19 +375,19 @@
|
|||
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-common</artifactId>
|
||||
<artifactId>springdoc-openapi-starter-common</artifactId>
|
||||
<version>${springdoc.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-webflux-core</artifactId>
|
||||
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
|
||||
<version>${springdoc.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-webflux-ui</artifactId>
|
||||
<artifactId>springdoc-openapi-starter-webflux-api</artifactId>
|
||||
<version>${springdoc.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue