使用自定义类解决swagger2统一返回类型和处理json文档显示
首先,需要考虑的是,swagger会不会很申请,能在没有任何json数据的情况下,读取到数据的key,从而生成文档。把想要被swagger识别到的类型直接使用ResultBean,T为pojo类型,即可被swagger扫描到类型,生成T类型的文档信息。这里只是要了一下boolean,自定义类型也是一样的操作,到此已经能处理很多问题了,但是还是不能处理json的文档。上面的代码当然只是简单的统一了返
文章背景
最近,作者在做web开发的时候,使用swagger解决前后端沟通的问题,由于作者的轻微的强迫症,一直想统一后端springboot的返回值类型,但是存在的问题如下所示:
1. 统一返回类型;
2. swagger使用反射来创建文档,但是只能读取静态的类型数据,统一类型后,缺少对于data字段的描述;
3. 不能生成json数据的文档;
声明:本文档原创,且不涉及swagger的基础使用,这个需要折腾swagger,有门槛基础,本文参考了互联网先驱的文档,参考文档在底部。
实验
实验环境
springboot=2.3.7
spring-boot-starter-aop=2.6.2
swagger-spring-boot-starter=1.9.0.RELEASE
解决方案
问题一
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "ResultBean", description = "json返回结果类")
public class ResultBean
{
@ApiModelProperty(value = "状态码", example = "2")
private int code;
@ApiModelProperty("返回消息")
private String msg;
@ApiModelProperty("返回数据")
private Object data;
}
实验结果:
问题二
上面的代码当然只是简单的统一了返回类型,其中data字段不能显示更加细节的信息,于是需要使用泛型。
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "ResultBean", description = "json返回结果类")
public class ResultBean<V>
{
@ApiModelProperty(value = "状态码", example = "2")
private int code;
@ApiModelProperty("返回消息")
private String msg;
@ApiModelProperty("返回数据")
private V data;
}
把想要被swagger识别到的类型直接使用ResultBean,T为pojo类型,即可被swagger扫描到类型,生成T类型的文档信息。
实验结果:
这里只是要了一下boolean,自定义类型也是一样的操作,到此已经能处理很多问题了,但是还是不能处理json的文档。
首先,需要考虑的是,swagger会不会很申请,能在没有任何json数据的情况下,读取到数据的key,从而生成文档。答案是不能!!!swagger没这么神奇,既然不能自动,就只能手动来写文档了。
问题三
该实现我主要参考了两篇文章,主要参考的参考文献1(链接在文章底部),但是文章1有点不是很好用,于是我改了一下。
直接上代码,代码很多:
ApiResponseArray.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiResponseArray {
String name() default "";
String description() default "";
String type() default "array";
ApiResponseObject[] items();
String point() default "";
}
ApiResponseBodyReader.java
import cn.edu.swu.blockchain.entity.Value2;
import com.fasterxml.classmate.ResolvedType;
import com.google.common.base.Optional;
import io.swagger.annotations.ApiModel;
import io.swagger.models.ModelImpl;
import io.swagger.models.properties.*;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import springfox.documentation.builders.ResponseMessageBuilder;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ResponseMessage;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.OperationBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;
import java.util.*;
/**
* 注册一个OperationBuilderPlugin
* @author lsp
*/
@Component
public class ApiResponseBodyReader implements OperationBuilderPlugin {
/**
* 是否支持此类DocumentationType
* @param delimiter 当前文档类型
* @return 如果支持当前类型,则返回true
*/
@Override
public boolean supports(DocumentationType delimiter) {
return true;
}
/**
* 如果有需要,可对operationContext执行操作
* @param operationContext 方法上下文
*/
@Override
public void apply(OperationContext operationContext) {
boolean responseFields = apiResponseFields(operationContext);
if (!responseFields)
apiResponseObjectHandle(operationContext);
}
/**
* 处理ApiResponseFields注解
* @param operationContext
*/
private boolean apiResponseFields(OperationContext operationContext){
Optional<ApiResponseFields> fieldsOptional = operationContext.findAnnotation(ApiResponseFields.class);
Optional<ApiResponseObject> objectOptional = operationContext.findAnnotation(ApiResponseObject.class);
Optional<ApiResponseArray> arrayOptionalOptional = operationContext.findAnnotation(ApiResponseArray.class);
if(fieldsOptional.isPresent() && !isVoid(operationContext)){
ApiResponseFields responseFields = fieldsOptional.get();
String model_name =responseFields.modelName();
if("".equals(model_name)){
model_name = getModelName(operationContext);
}
String uuid = model_name + "-" + UUID();
String[] fields = responseFields.fields();
Value2<String, String[]> value2 = new Value2<>(model_name, fields);
ModelCache.specified_cache.put(uuid,value2);
if (arrayOptionalOptional.isPresent() && !StringUtils.isEmpty(arrayOptionalOptional.get().point())) {
ApiResponseArray apiResponseArray = arrayOptionalOptional.get();
ModelCache.point_properties_cache.put(uuid, new Value2<>(apiResponseArray.point(), createArrayProperty(apiResponseArray)));
}else if (objectOptional.isPresent() && !StringUtils.isEmpty(objectOptional.get().point())) {
ApiResponseObject apiResponseObject = objectOptional.get();
ModelCache.point_properties_cache.put(uuid, new Value2<>(apiResponseObject.point(), createObjectProperty(apiResponseObject)));
}
addResponseMessage(operationContext,uuid);
return true;
}
return false;
}
/**
* 处理ApiResponseObject注解
* @param operationContext
* @return 若此方法上有ApiResponseObject注解,则返回true,否则返回false
*/
private boolean apiResponseObjectHandle(OperationContext operationContext){
Optional<ApiResponseObject> optional = operationContext.findAnnotation(ApiResponseObject.class);
if(optional.isPresent()){
ApiResponseObject apiResponseObject = optional.get();
ModelImpl model = createModel(apiResponseObject);
String model_name = null;
if(isVoid(operationContext)){
model_name = "Map";
}else{
model_name = getModelName(operationContext);
}
String uuid = model_name + "-" + UUID();
model.setTitle(uuid);
ModelCache.extra_cache.put(uuid,model);
addResponseMessage(operationContext,uuid);
return true;
}
return false;
}
private boolean isVoid(OperationContext operationContext){
ResolvedType type = operationContext.getReturnType();
Class<?> aClass = type.getErasedType();
return aClass == void.class;
}
/**
* 获取返回值信息的名字
* @param operationContext
* @return
*/
private String getModelName(OperationContext operationContext){
ResolvedType type = operationContext.getReturnType();
Class<?> aClass = type.getErasedType();
ApiModel apiModel = aClass.getAnnotation(ApiModel.class);
String model_name = null;
if(apiModel != null){
model_name = apiModel.value();
}
if(model_name==null || "".equals(model_name)){
model_name = aClass.getSimpleName();
}
return model_name;
}
/**
* 为operationContext添加状态为200的返ResponseMessage
* @param operationContext
* @param typeName
*/
private void addResponseMessage(OperationContext operationContext,String typeName){
ResponseMessage responseMessage = new ResponseMessageBuilder()
.code(200).responseModel(new ModelRef(typeName))
.build();
HashSet<ResponseMessage> responseMessages = new HashSet<>();
responseMessages.add(responseMessage);
operationContext.operationBuilder().responseMessages(responseMessages);
}
/**
* 生成UUID
* @return
*/
private String UUID(){
return UUID.randomUUID().toString();
}
private Map<String, Property> extraProperties(ApiResponseObject apiResponseObject) {
HashMap<String, Property> res = new HashMap<>();
ApiResponseProperty[] properties = apiResponseObject.properties();
for(ApiResponseProperty apiResponseProperty : properties){
String name = apiResponseProperty.name();
String description = apiResponseProperty.description();
String type = apiResponseProperty.type();
Property property = null;
if("string".equalsIgnoreCase(type)){
property = new StringProperty();
}else if("int".equalsIgnoreCase(type)){
property = new IntegerProperty();
}else if("date".equalsIgnoreCase(type)){
property = new DateProperty();
}else if("uuid".equalsIgnoreCase(type)){
property = new UUIDProperty();
}else{
throw new RuntimeException("未支持的类型");
}
property.setDescription(description);
res.put(name,property);
}
return res;
}
/**
* 根据apiResponseObject注解生成一个ModelImpl
* @param apiResponseObject
* @return
*/
private ModelImpl createModel(ApiResponseObject apiResponseObject){
ModelImpl result = new ModelImpl();
//apiResponseObject的类型指定是object
result.setType("object");
result.setDescription(apiResponseObject.description());
result.setProperties(extraProperties(apiResponseObject));
return result;
}
private ObjectProperty createObjectProperty(ApiResponseObject apiResponseObject) {
ObjectProperty objectProperty = new ObjectProperty();
objectProperty.setType(StringUtils.isEmpty(apiResponseObject.type())?"object":apiResponseObject.type());
objectProperty.setName(apiResponseObject.name());
objectProperty.setDescription(apiResponseObject.description());
objectProperty.properties(extraProperties(apiResponseObject));
return objectProperty;
}
private ArrayProperty createArrayProperty(ApiResponseArray apiResponseArray) {
ArrayProperty arrayProperty = new ArrayProperty();
arrayProperty.setType(StringUtils.isEmpty(apiResponseArray.type())?"array":apiResponseArray.type());
arrayProperty.setDescription(apiResponseArray.description());
arrayProperty.setName(apiResponseArray.name());
ApiResponseObject items = apiResponseArray.items()[0];
ObjectProperty objectProperty = createObjectProperty(items);
return arrayProperty.items(objectProperty);
}
}
ApiResponseFields.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiResponseFields {
String modelName() default "";
String[] fields();
}
ApiResponseObject.java
public @interface ApiResponseProperty {
String name();
String description() default "";
String type();
}
ModelCache.java
import io.swagger.models.Model;
import io.swagger.models.properties.Property;
import java.util.HashMap;
import java.util.Map;
public class ModelCache {
static Map<String, Model> extra_cache = new HashMap<>();
static Map<String, Value2<String,String[]>> specified_cache = new HashMap<>();
static Map<String, Value2<String, Property>> point_properties_cache = new HashMap<>();
}
SwaggerAop.java
import io.swagger.models.Model;
import io.swagger.models.ModelImpl;
import io.swagger.models.Swagger;
import io.swagger.models.properties.Property;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.Map;
@Aspect
@Component
public class SwaggerAop {
@Pointcut(value = "execution(public * springfox.documentation.swagger2.mappers.ServiceModelToSwagger2MapperImpl.mapDocumentation(..))")
public void point(){
}
@Around("point()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Swagger swagger = (Swagger) proceedingJoinPoint.proceed();
Map<String, Model> returnValue = swagger.getDefinitions() ;
returnValue.putAll(ModelCache.extra_cache);
ModelCache.specified_cache.entrySet().forEach(e->{
String key = e.getKey();
String model_name = e.getValue().v1;
String[] fields = e.getValue().v2;
Model model = returnValue.get(model_name);
if(model == null){
throw new RuntimeException("不存在的类型"+model_name);
}
Map<String, Property> properties = model.getProperties();
ModelImpl newModel = new ModelImpl();
newModel.setDescription(model.getDescription());
newModel.setTitle(key);
for(String field : fields){
Property property = properties.get(field);
if(property == null){
throw new RuntimeException("不存在的属性"+field);
}
newModel.property(field,property);
}
if (ModelCache.point_properties_cache.size() > 0) {
Value2<String, Property> stringPropertyValue2 = ModelCache.point_properties_cache.remove(key);
newModel.property(stringPropertyValue2.v1, stringPropertyValue2.v2);
}
returnValue.put(key,newModel);
});
return swagger;
}
}
Value2.java
/**
* 存放多个实例的便捷类
*/
public class Value2<T,V> {
public final T v1;
public final V v2;
public Value2(T v1, V v2) {
this.v1 = v1;
this.v2 = v2;
}
}
实验结果:
使用接口的时候,需要保证point的数据一定要和fields的泛型的数据一致,实验结果如下:
成功生成了json的信息。还有一个叫ApiResponseArray,用来处理[]的。
参考文献
[1] https://blog.csdn.net/lzhcoder/article/details/108348430
[2] https://blog.csdn.net/pingzi_kendy/article/details/83577443
更多推荐
所有评论(0)