一、背景
由于项目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