Springboot+Swagger生成接口文档---自定义返回Model(兼容Map、Object)

一、背景

由于项目controller封装的通用返回类不规范,是用继承HashMap实现的,没有使用泛型,导致swagger生成接口文档无法讲返回model自动生成,十分影响和前端对接,@ApiResponses又不支持泛型,于是需要多写点代码兼容.

R类代码片段如下

public class R extends HashMap<String, Object> {
	private static final long serialVersionUID = 1L;
	public static final int CODE_OK = 0;
	public static final int CODE_ERROR_NEG1 = -1;
	public static final int CODE_ERROR_1 = 1;
	public static final int CODE_ERROR_500 = 500;
	public static final int CODE_WARN = -2;
	public static final String CODE = "code";
	public static final String MSG = "msg";
	public static final String DATA = "data";
	public static final String PAGE = "page";
	public static final String SUCCESS = "success";

	
	public R() {
		put(CODE, CODE_OK);
		put(MSG, "success");
		put(SUCCESS, true);
	}
	
	public R put(String key, Object value) {
		super.put(key, value);
		return this;
	}
}

二、实现

前置类、几个注解

@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiReturnJson {
	String key();  //对象名称
    ApiReturnJsonPro[] value(); //对象属性值
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiReturnJsonPro {
 
    String key();  //key
 
    Class<?> dataType() default String.class;
 
    String description() default "";

	/*
	Page类是自定义的一个分页数据通用返回类,
	*/
    //List Set Page
    String responseContainer() default "";
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiReturnJsonR {

    Class<?> dataType() default String.class;
 
    String description() default "";

    //List Set Page
    String responseContainer() default "";
}

1、首先需要实现OperationModelsProviderPlugin接口,将我们期望的model添加进documentContext

@Slf4j
@Conditional(DevCondition.class)
@Configuration
@Order(-999)
public class CustomOperationModelsProviderPlugin implements OperationModelsProviderPlugin {

    @Resource
    private TypeResolver typeResolver;

    private final static String basePackage = "com.xxx.devops.swagger.bean.";

    @Override
    public void apply(RequestMappingContext context) {
        if (context.getReturnType().isInstanceOf(R.class)) {
            //生成R类
            Optional<ApiReturnJsonR> optional1 = context.findAnnotation(ApiReturnJsonR.class);
            try {
                if (optional1.isPresent()) {
                    ApiReturnJsonR apiReturnJson = optional1.get();
                    String name = "R" + "_" + apiReturnJson.dataType().getSimpleName()+"_"+context.getName(); // model名称
                    ResolvedType rt = typeResolver.resolve(createRefModelR(apiReturnJson,name));
                    // 像documentContext的Models中添加我们新生成的Class
                    context.getDocumentationContext().getAdditionalModels().add(rt);
                    context.operationModelsBuilder().addReturn(rt).build();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            //根据参数上的ApiJsonObject注解中的参数动态生成Class
            Optional<ApiReturnJson> optional2 = context.findAnnotation(ApiReturnJson.class);
            ApiReturnJsonPro[] properties = null;
            String name = null;
            try {
                if (optional2.isPresent()) {
                    ApiReturnJson apiReturnJson = optional2.get();
                    properties = apiReturnJson.value();
                    name = optional2.get().key()+"_"+context.getName(); // model名称
                    ResolvedType rt = typeResolver.resolve(createRefModel(properties, name));
                    // 像documentContext的Models中添加我们新生成的Class
                    context.getDocumentationContext().getAdditionalModels().add(rt);
                    context.operationModelsBuilder().addReturn(rt).build();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }

    private Class createRefModelR(ApiReturnJsonR jsonR, String name) {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass(basePackage + name);
        try {
            ctClass.addField(createField("code",Integer.class,"","返回码",ctClass));
            ctClass.addField(createField("msg",String.class,"","返回提示",ctClass));
            ctClass.addField(createField("success",Boolean.class,"","是否成功",ctClass));
            ctClass.addField(createField("data",jsonR.dataType(),jsonR.responseContainer(),jsonR.description(),ctClass));
            return ctClass.toClass();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 根据propertys中的值动态生成含有Swagger注解的javaBeen
     */
    private Class createRefModel(ApiReturnJsonPro[] propertys, String name) {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass(basePackage + name);
        try {
            for (ApiReturnJsonPro property : propertys) {
                ctClass.addField(createField(property.key(),property.dataType(),property.responseContainer(),property.description(),ctClass));
            }
            return ctClass.toClass();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 创建类的属性
     * @param key
     * @param dataType
     * @param responseContainer
     * @param description
     * @param ctClass
     * @return
     * @throws Exception
     */
    private CtField createField(String key,Class<?> dataType,String responseContainer,
                                String description,
                                CtClass ctClass) throws Exception {
        CtClass fieldClass = getFieldType(dataType, responseContainer);
        CtField ctField = new CtField(fieldClass, key, ctClass);
        if(StringUtils.isNotBlank(responseContainer)){
            getGenericSignature(ctField,dataType,responseContainer);
        }
        ctField.setModifiers(Modifier.PUBLIC);
        ConstPool constPool = ctClass.getClassFile().getConstPool();
        AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        Annotation ann = new Annotation("io.swagger.annotations.ApiModelProperty", constPool);
        ann.addMemberValue("value", new StringMemberValue(description, constPool));
     /*   if(ctField.getType().subclassOf(ClassPool.getDefault().get(String.class.getName()))){
            ann.addMemberValue("example", new StringMemberValue(example, ClassPool.getDefault().get(String.class.getName()).getClassFile().getConstPool()));
        }
        if(ctField.getType().subclassOf(ClassPool.getDefault().get(Integer.class.getName()))){
            ann.addMemberValue("example", new IntegerMemberValue(Integer.parseInt(example), ClassPool.getDefault().get(Integer.class.getName()).getClassFile().getConstPool()));
        }
        if(ctField.getType().subclassOf(ClassPool.getDefault().get(Boolean.class.getName()))){
            ann.addMemberValue("example", new BooleanMemberValue(Boolean.parseBoolean(example), ClassPool.getDefault().get(Boolean.class.getName()).getClassFile().getConstPool()));
        }*/
        attr.addAnnotation(ann);
        ctField.getFieldInfo().addAttribute(attr);

        return ctField;
    }

    /**
     * 生成返回对象的属性class
     * @param classObj
     * @param responseContainer
     * @return
     * @throws Exception
     */
    private CtClass getFieldType(Class<?> classObj,String responseContainer) throws Exception {
        ClassPool classPool = ClassPool.getDefault();
        if ("List".compareToIgnoreCase(responseContainer) == 0) {
            return classPool.get(List.class.getCanonicalName());
        } else if ("Set".compareToIgnoreCase(responseContainer) == 0) {
            return classPool.get(Set.class.getCanonicalName());
        } else if ("Page".compareToIgnoreCase(responseContainer) == 0) {
            return classPool.get(Page.class.getCanonicalName());
        }
        return classPool.get(classObj.getName());
    }

    /**
     *  javasist对CtField的泛型类型添加泛型的类声明
     * @param ctField
     * @param relatedClass
     * @param responseContainer
     * @return
     * @throws BadBytecode
     */
    private CtField getGenericSignature(CtField ctField, Class<?> relatedClass,String responseContainer) throws BadBytecode {
        String fieldSignature = "";
        if ("List".compareToIgnoreCase(responseContainer) == 0) {
            fieldSignature = "L" + List.class.getCanonicalName().replace(".", "/") + "<L" + relatedClass.getCanonicalName().replace(".", "/") + ";>;";
        } else if ("Set".compareToIgnoreCase(responseContainer) == 0) {
            fieldSignature = "L" + Set.class.getCanonicalName().replace(".", "/") + "<L" + relatedClass.getCanonicalName().replace(".", "/") + ";>;";
        } else if ("Page".compareToIgnoreCase(responseContainer) == 0) {
            fieldSignature = "L" + Page.class.getCanonicalName().replace(".", "/") + "<L" + relatedClass.getCanonicalName().replace(".", "/") + ";>;";
        }else {
            return ctField;
        }
        ctField.setGenericSignature(SignatureAttribute.toClassSignature(fieldSignature).encode());
        return ctField;
    }

}

2、实现OperationBuilderPlugin接口,将之前生成的Model进行替换

@Configuration
@Component
public class CustomOperationBuilderPlugin implements OperationBuilderPlugin {
    @Override
    public void apply(OperationContext operationContext) {
        if(operationContext.getReturnType().isInstanceOf(R.class)) {
            //根据参数上的ApiJsonObject注解中的参数动态生成Class
            Optional<ApiReturnJsonR> optional1 = operationContext.findAnnotation(ApiReturnJsonR.class);
            Optional<ApiReturnJson> optional2 = operationContext.findAnnotation(ApiReturnJson.class);
            try {
                String name = "";
                if(optional1.isPresent()){
                    ApiReturnJsonR apiReturnJson = optional1.get();
                    name = "R" + "_" + apiReturnJson.dataType().getSimpleName() + "_" + operationContext.getName();
                }
                if(optional2.isPresent()){
                    ApiReturnJson apiReturnJson = optional2.get();
                    name = apiReturnJson.key() + "_" + operationContext.getName();
                }
                if(StringUtils.isNotBlank(name)){
                    Set<ResponseMessage> set = new HashSet<>();
                    ModelRef mr = new ModelRef(name);
                    set.add(new ResponseMessage(200,"返回json用例说明",mr,null,null));
                    operationContext.operationBuilder().responseMessages(set);
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }
}

三、Controller中使用

@ApiReturnJsonR
该注解是专门针对我的R类写的逻辑,定制化

	@ApiOperation("接口描述")
    @ApiReturnJsonR(responseContainer="List", dataType = SelectVo.class)
    @GetMapping("/listWebArea")
    public R listWebArea() {
        List<SelectVo> result = new ArrayList<>();
       	.....
        return R.data(result);
    }

@ApiReturnJson
@ApiReturnJsonPro
兼容了个通用写法

	@ApiOperation("接口描述")
    @ApiReturnJson(key="listWebArea",value = {
            @ApiReturnJsonPro(key = "code", dataType = Integer.class),
            @ApiReturnJsonPro(key = "msg", dataType = String.class),
            @ApiReturnJsonPro(key = "data", dataType = SelectVo.class, responseContainer = "List"),
    })
    @GetMapping("/listWebArea")
    public R listWebArea() {
        List<SelectVo> result = new ArrayList<>();
        return R.data(result);
    }

效果

在这里插入图片描述
在这里插入图片描述

参考文章

https://cloud.tencent.com/developer/article/1700641
https://my.oschina.net/internetafei/blog/3196604
https://blog.csdn.net/DearShaJing/article/details/104675889
https://blog.csdn.net/hellopeng1/article/details/82227942

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值