Swagger2 JSON入参使用Map、JSONObject等非实体类接收时的处理

Swagger2 JSON入参使用Map、JSONObject等非实体类接收时的处理,基本就是扩展swagger插件通过注解动态生成实体类。
以下提供3种实现,可以按需选择:

一. ApiGlobalModel

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

使用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

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

  • 12
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值