赞
踩
Swagger2 JSON入参使用Map、JSONObject等非实体类接收时的处理,基本就是扩展swagger插件通过注解动态生成实体类。
以下提供3种实现,可以按需选择:
ApiGlobalModel注解用于从一个已有的实体类中抽取接口所需的参数字段
示例:
/**
* 修改地址 - ApiGlobalModel
*
* @return R
*/
@PostMapping("2")
@ApiOperation(value = "修改地址 - ApiGlobalModel")
@ApiGlobalModel(component = GlobalVo.class, value = "addressId,telNumber")
public Resp<AddressVo> updateByApiGlobalModel(JSONObject param) {
//***************************** 读取参数并校验 *******************************/
Integer id = param.getInteger("id");
String telNumber = param.getString("telNumber");
//***************************** 处理业务并返回 *******************************/
return Resp.success(null);
}
package com.platform.po; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.io.Serializable; /** * 出参字段集合类 用于ApiGlobalModel * 不可继承 不可实例化 只是字段描述文件 * */ @Data @ApiModel public abstract class GlobalVo implements Serializable { private GlobalVo() { } @ApiModelProperty(value = "收货地址id", name = "id") private Integer addressId; @ApiModelProperty(value = "收件人电话") private Integer telNumber; @ApiModelProperty(value = "购物车id", name = "id") private Integer cartId; @ApiModelProperty("商品Id") private Integer goodsId; @ApiModelProperty("sku_id") private Integer skuId; @ApiModelProperty("数量") private Integer number; @ApiModelProperty("已选规格列表") private String properties; @ApiModelProperty(name = "id", value = "用户id") private String userId; @ApiModelProperty(name = "id", value = "订单id") private String orderId; @ApiModelProperty("用户名") private String userName; @ApiModelProperty("openId") private String openId; @ApiModelProperty("手机号码") private String mobile; @ApiModelProperty("短信验证码") private String smsCode; @ApiModelProperty("短信验证码Key") private String smsCodeKey; @ApiModelProperty("图形验证码Key") private String captchaKey; @ApiModelProperty("图形验证码") private String captchaCode; @ApiModelProperty("密码") private String password; @ApiModelProperty(value = "新密码", name = "password") private String newPassword; @ApiModelProperty("微信code") private String code; @ApiModelProperty(name = "code", value = "支付宝code") private String aliCode; }
效果:
这里使用了knife4j来美化swagger,方便查看和调试
以上,GlobalVo类用于存放接口入参字段,这是一个不可实现不可继承的实体类,仅用于字段描述
可以在这个类里定义所有需要的字段,相同的参数可以复用
描述这个实体类使用的是swagger原生注解
另一个使用场景比如有个修改订单状态的接口,虽然我们有订单实体类GoodsOrderEntity.java,但这个接口只需要订单id和订单状态两个字段,如果把整个订单实体类丢给swagger,接口文档就会显示一大堆不必要的参数,这显然不是前端需要的,这时候就可以使用ApiGlobalModel抽取仅需要的字段
/**
* 修改订单状态 - ApiGlobalModel
*
* @return R
*/
@PostMapping("3")
@ApiOperation(value = "修改订单状态 - ApiGlobalModel")
@ApiGlobalModel(component = GoodsOrderEntity.class, value = "id,orderStatus")
public Resp<Object> changeOrderStatus(JSONObject param) {
//***************************** 读取参数并校验 *******************************/
Integer id = param.getInteger("id");
Integer orderStatus = param.getInteger("orderStatus");
//***************************** 处理业务并返回 *******************************/
return Resp.success(null);
}
以上,GoodsOrderEntity是一个用swagger注解正常描述的实体类,字段非常多,我们抽离出需要的放到接口文档里。
ApiGlobalModel方式需要以下两个类的支持:ApiGlobalModel.java和ApiGlobalModelBuilder.java
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Swagger扩展注解 * 用于application/json请求 * 并使用诸如Map或JSONObject等非具体实体类接收参数时,对参数进行进一步描述 */ @Target({ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface ApiGlobalModel { /** * 字段集合容器 * * @return Global model */ Class<?> component(); /** * 分隔符 * * @return separator */ String separator() default ","; /** * 实际用到的字段 * 可以是字符串数组,也可以是一个字符串 多个字段以分隔符隔开: "id,name" * 注意这里对应的是component里的属性名,但swagger显示的字段名实际是属性注解上的name * * @return value */ String[] value() default {}; }
import com.fasterxml.classmate.TypeResolver; import com.google.common.base.Optional; import io.swagger.annotations.ApiModelProperty; import javassist.*; import javassist.bytecode.AnnotationsAttribute; import javassist.bytecode.annotation.Annotation; import javassist.bytecode.annotation.MemberValue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import springfox.documentation.schema.ModelRef; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.service.ParameterBuilderPlugin; import springfox.documentation.spi.service.contexts.ParameterContext; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * ApiJsonParameterBuilder * <p> * 将map入参匹配到swagger文档的工具类 * plugin加载顺序,默认是最后加载 * */ @Component @Order public class ApiGlobalModelBuilder implements ParameterBuilderPlugin { private static final Logger logger = LoggerFactory.getLogger(ApiGlobalModelBuilder.class); @Autowired private TypeResolver typeResolver; /** * 动态生成的Class的包路径 自由定义 */ private final static String BASE_PACKAGE = "com.platform.entity."; /** * 默认类名 */ private final static String DEFAULT_CLASS_NAME = "RequestBody"; /** * 序号 防止重名 */ private static Integer i = 0; @Override public void apply(ParameterContext context) { try { // 从方法或参数上获取指定注解的Optional Optional<ApiGlobalModel> optional = context.getOperationContext().findAnnotation(ApiGlobalModel.class); if (!optional.isPresent()) { optional = context.resolvedMethodParameter().findAnnotation(ApiGlobalModel.class); } if (optional.isPresent()) { String key = DEFAULT_CLASS_NAME + i++; ApiGlobalModel apiAnno = optional.get(); try { //类名重复将导致swagger识别不准确 主动触发异常 Class.forName(BASE_PACKAGE + key); } catch (ClassNotFoundException e) { String[] fields = apiAnno.value(); String separator = apiAnno.separator(); ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass(BASE_PACKAGE + key); ctClass.setModifiers(Modifier.ABSTRACT); //处理 javassist.NotFoundException pool.insertClassPath(new ClassClassPath(apiAnno.component())); CtClass globalCtClass = pool.getCtClass(apiAnno.component().getName()); //从globalCtClass拷贝指定字段到动态创建的类中 for (String field : merge(fields, separator)) { //若指定的字段不存在 throw NotFoundException CtField ctField = globalCtClass.getDeclaredField(field); CtField newCtField = new CtField(ctField, ctClass); handleField(newCtField); ctClass.addField(newCtField); } // 将生成的Class添加到SwaggerModels context.getDocumentationContext().getAdditionalModels() .add(typeResolver.resolve(ctClass.toClass())); // 修改Json参数的ModelRef为动态生成的class context.parameterBuilder() .parameterType("body").modelRef(new ModelRef(key)).name("body").description("body"); } } } catch (Exception e) { logger.error("@ApiGlobalModel Error", e); } } private void handleField(CtField field) { //防止private又没有getter field.setModifiers(Modifier.PUBLIC); //有name的把字段名改为name //因为JSON格式化的原因,ApiModelProperty的name属性无效 所以如果有name,直接更改字段名为name AnnotationsAttribute annos = ((AnnotationsAttribute) field.getFieldInfo().getAttribute("RuntimeVisibleAnnotations")); if (annos != null) { Annotation anno = annos.getAnnotation(ApiModelProperty.class.getTypeName()); if (anno != null) { MemberValue name = anno.getMemberValue("name"); if (name != null) { //这里返回的name会以引号包裹 String fName = name.toString().replace("\"", "").trim(); if (fName.length() > 0) { field.setName(fName); } } } } } /** * 字符串列表 分隔符 合并 * A{"a","b,c","d"} => B{"a","d","b","c"} * * @param arr arr * @return list */ private List<String> merge(String[] arr, String separator) { List<String> tmp = new ArrayList<>(); Arrays.stream(arr).forEach(s -> { if (s.contains(separator)) { tmp.addAll(Arrays.asList(s.split(separator))); } else { tmp.add(s); } }); return tmp; } @Override public boolean supports(DocumentationType delimiter) { return true; } }
使用ApiJsonModel注解不需要预先定义实体类,所有描述都在接口方法的注解上,描述具体字段的时候使用的依然是swagger原生注解ApiModelProperty
优点是不需要创建实体类,缺点是很多注解在方法上比较难看,另外字段描述不可复用
示例:
/** * 修改地址 - ApiJsonModel * * @return R */ @PostMapping("4") @ApiOperation(value = "修改地址 - ApiJsonModel") @ApiJsonModel({ @ApiModelProperty(name = "id", value = "收件人电话"), @ApiModelProperty(name = "telNumber", value = "收货地址id") }) public Resp<AddressVo> saveByApiJsonModel(JSONObject param) { //***************************** 读取参数并校验 *******************************/ Integer id = param.getInteger("id"); String telNumber = param.getString("telNumber"); //***************************** 处理业务并返回 *******************************/ return Resp.success(null); }
ApiJsonModel方式需要以下两个类的支持:ApiJsonModel.java和ApiJsonModelBuilder.java
import io.swagger.annotations.ApiModelProperty; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Swagger扩展注解 * 用于application/json请求 * 并使用诸如Map或JSONObject等非具体实体类接收参数时,对参数进行进一步描述 * */ @Target({ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface ApiJsonModel { /** * 原生Swagger注解 用于描述具体字段 * accessMode,extensions配置将无效 * * @return value */ ApiModelProperty[] value() default {}; }
import com.fasterxml.classmate.TypeResolver; import com.google.common.base.Optional; import io.swagger.annotations.ApiModelProperty; import javassist.ClassPool; import javassist.CtClass; import javassist.CtField; import javassist.Modifier; import javassist.bytecode.AnnotationsAttribute; import javassist.bytecode.ConstPool; import javassist.bytecode.annotation.Annotation; import javassist.bytecode.annotation.BooleanMemberValue; import javassist.bytecode.annotation.IntegerMemberValue; import javassist.bytecode.annotation.StringMemberValue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import springfox.documentation.schema.ModelRef; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.service.ParameterBuilderPlugin; import springfox.documentation.spi.service.contexts.ParameterContext; import java.lang.reflect.Method; import java.lang.reflect.Type; /** * ApiJsonParameterBuilder * <p> * 将map入参匹配到swagger文档的工具类 * plugin加载顺序,默认是最后加载 * */ @Component @Order public class ApiJsonModelBuilder implements ParameterBuilderPlugin { private static final Logger logger = LoggerFactory.getLogger(ApiJsonModelBuilder.class); @Autowired private TypeResolver typeResolver; /** * 动态生成的Class的包路径 自由定义 */ private final static String BASE_PACKAGE = "com.platform.entity."; /** * 默认类名 */ private final static String DEFAULT_CLASS_NAME = "RequestData"; /** * 序号 防止重名 */ private static Integer i = 0; @Override public void apply(ParameterContext context) { try { // 从方法或参数上获取指定注解的Optional Optional<ApiJsonModel> optional = context.getOperationContext().findAnnotation(ApiJsonModel.class); if (!optional.isPresent()) { optional = context.resolvedMethodParameter().findAnnotation(ApiJsonModel.class); } if (optional.isPresent()) { String key = DEFAULT_CLASS_NAME + i++; ApiJsonModel apiAnno = optional.get(); try { Class.forName(BASE_PACKAGE + key); } catch (ClassNotFoundException e) { ApiModelProperty[] properties = apiAnno.value(); // 将生成的Class添加到SwaggerModels context.getDocumentationContext().getAdditionalModels() .add(typeResolver.resolve(createRefModel(properties, key))); // 修改Json参数的ModelRef为动态生成的class context.parameterBuilder() .parameterType("body").modelRef(new ModelRef(key)).name("body").description("body"); } } } catch (Exception e) { logger.error("@ApiJsonModel Error", e); } } /** * MapReaderForApi * 根据propertys中的值动态生成含有Swagger注解的javaBeen * * @author chengz * @date 2020/10/14 */ private Class<?> createRefModel(ApiModelProperty[] propertys, String key) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass(BASE_PACKAGE + key); ctClass.setModifiers(Modifier.ABSTRACT); for (ApiModelProperty property : propertys) { ctClass.addField(createField(property, ctClass)); } return ctClass.toClass(); } /** * 根据property的值生成含有swagger apiModelProperty注解的属性 */ private CtField createField(ApiModelProperty property, CtClass ctClass) throws Exception { //此处默认字段类型为String 如果不是 swagger也是取注解的dataType 字段类型就不重要了 CtField ctField = new CtField(ClassPool.getDefault().get(String.class.getName()), property.name(), ctClass); ctField.setModifiers(Modifier.PUBLIC); ConstPool constPool = ctClass.getClassFile().getConstPool(); AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag); //不知道是否有直接把property转为对应Annotation的方法 它们本质是一样的 Annotation anno = new Annotation(ApiModelProperty.class.getTypeName(), constPool); Method[] members = ApiModelProperty.class.getDeclaredMethods(); for (Method member : members) { Object value = member.invoke(property); //使用默认值的就不重复赋值了 if (!value.equals(member.getDefaultValue())) { //由于只拷贝了String,int,boolean等返回值的属性,所以诸如accessMode,extensions这样的属性设置将无效 Type type = member.getReturnType(); if (type == String.class) { anno.addMemberValue(member.getName(), new StringMemberValue(String.valueOf(member.invoke(property)), constPool)); } else if (type == int.class) { anno.addMemberValue(member.getName(), new IntegerMemberValue((Integer) member.invoke(property), constPool)); } else if (type == boolean.class) { anno.addMemberValue(member.getName(), new BooleanMemberValue((Boolean) member.invoke(property), constPool)); } } } attr.addAnnotation(anno); ctField.getFieldInfo().addAttribute(attr); return ctField; } @Override public boolean supports(DocumentationType delimiter) { return true; } }
ApiSimpleModel注解只用于定义字段名,字段类型默认Strng,用于快速定义简单接口
示例:
/**
* 修改地址 - ApiSimpleModel
*
* @return R
*/
@PostMapping("5")
@ApiSimpleModel("id,telNumber")
@ApiOperation(value = "修改地址 - ApiSimpleModel")
public Resp<AddressVo> saveByApiSimpleModel(JSONObject param) {
//***************************** 读取参数并校验 *******************************/
Integer id = param.getInteger("id");
String telNumber = param.getString("telNumber");
//***************************** 处理业务并返回 *******************************/
return Resp.success(null);
}
ApiSimpleModel方式需要以下两个类的支持:ApiSimpleModel.java和ApiSimpleModelBuilder.java
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Swagger扩展注解 * 用于application/json请求 * 并使用诸如Map或JSONObject等非具体实体类接收参数时,对参数进行进一步描述 * 简易描述 只有参数名 * */ @Target({ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface ApiSimpleModel { /** * 分隔符 * * @return separator */ String separator() default ","; /** * 参数列表 * 可以是字符串数组,也可以是一个字符串 多个字段以分隔符隔开: "id,name" * * @return value */ String[] value() default {}; }
import com.fasterxml.classmate.TypeResolver; import com.google.common.base.Optional; import javassist.ClassPool; import javassist.CtClass; import javassist.CtField; import javassist.Modifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import springfox.documentation.schema.ModelRef; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.service.ParameterBuilderPlugin; import springfox.documentation.spi.service.contexts.ParameterContext; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * ApiJsonParameterBuilder * <p> * 将map入参匹配到swagger文档的工具类 * plugin加载顺序,默认是最后加载 * */ @Component @Order public class ApiSimpleModelBuilder implements ParameterBuilderPlugin { private static final Logger logger = LoggerFactory.getLogger(ApiSimpleModelBuilder.class); @Autowired private TypeResolver typeResolver; /** * 动态生成的Class的包路径 自由定义 */ private final static String BASE_PACKAGE = "com.platform.entity."; /** * 默认类名 */ private final static String DEFAULT_CLASS_NAME = "RequestInfo"; /** * 序号 防止重名 */ private static Integer i = 0; @Override public void apply(ParameterContext context) { try { // 从方法或参数上获取指定注解的Optional Optional<ApiSimpleModel> optional = context.getOperationContext().findAnnotation(ApiSimpleModel.class); if (!optional.isPresent()) { optional = context.resolvedMethodParameter().findAnnotation(ApiSimpleModel.class); } if (optional.isPresent()) { String key = DEFAULT_CLASS_NAME + i++; ApiSimpleModel apiAnno = optional.get(); try { //类名重复将导致swagger识别不准确 主动触发异常 Class.forName(BASE_PACKAGE + key); } catch (ClassNotFoundException e) { String[] fields = apiAnno.value(); String separator = apiAnno.separator(); List<String> fieldList = merge(fields, separator); Class<?> ctClass = createRefModel(fieldList, key); // 将生成的Class添加到SwaggerModels context.getDocumentationContext().getAdditionalModels() .add(typeResolver.resolve(ctClass)); // 修改Json参数的ModelRef为动态生成的class context.parameterBuilder() .parameterType("body").modelRef(new ModelRef(key)).name("body").description("body"); } } } catch (Exception e) { logger.error("@ApiSimpleModel Error", e); } } /** * 字符串列表 分隔符 合并 * A{"a","b,c","d"} => B{"a","d","b","c"} * * @param arr arr * @return list */ private List<String> merge(String[] arr, String separator) { List<String> tmp = new ArrayList<>(); Arrays.stream(arr).forEach(s -> { if (s.contains(separator)) { tmp.addAll(Arrays.asList(s.split(separator))); } else { tmp.add(s); } }); return tmp; } /** * MapReaderForApi * 根据propertys中的值动态生成含有Swagger注解的javaBeen * * @author chengz * @date 2020/10/14 */ private Class<?> createRefModel(List<String> fieldList, String key) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass(BASE_PACKAGE + key); ctClass.setModifiers(Modifier.ABSTRACT); for (String field : fieldList) { CtField newCtField = createField(field, ctClass); ctClass.addField(newCtField); } return ctClass.toClass(); } /** * 根据property的值生成含有swagger apiModelProperty注解的属性 */ private CtField createField(String field, CtClass ctClass) throws Exception { CtField ctField = new CtField(ClassPool.getDefault().get(String.class.getName()), field, ctClass); ctField.setModifiers(Modifier.PUBLIC); return ctField; } @Override public boolean supports(DocumentationType delimiter) { return true; } }
如果swagger文档里只显示body不显示字段的加这个controller到项目里:
import io.swagger.annotations.Api; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 如果系统最后一个加载的接口加了@ApiGlobalModel/@ApiJsonModel/@ApiSimpleModel,这个接口动态生成的参数在swagger文档里将找不到 * 出现这种情况就加上这个Controller,保证这个Controller是最后一个Controller => 保证最后一个接口没有使用以上3个注解 */ @RestController @RequestMapping("/whatever") @Api(tags = "whatever") public class ZZZController { @GetMapping("/whatever") public void whatever() { } }
参考:https://blog.csdn.net/hellopeng1/article/details/82227942
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。