Swagger2 关于Map参数在API文档中展示详细参数以及参数说明

SpringFox 提供给我们了一个ParameterBuilderPlugin接口,通过这个接口我们可以在SpringFox构造Map参数映射的ModelRef时使用javassist动态的生成类,并把这个map参数的modelRef对象指向我们动态生成的具体Class对象(通过自定义注解在Map参数上生成可表示JSON结构的类

 1、ApiJsonProperty注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author Administrator
 */
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiJsonProperty {
    //key
    String key();

    String example() default "";

    //支持string 和 int
    String type() default "string";

    String description() default "";
}

2、ApiJsonObject注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author Administrator
 */
@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiJsonObject {
    //对象属性值
    ApiJsonProperty[] value();

    //对象名称
    String name();
}

3、MapApiReader类

import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Optional;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.IntegerMemberValue;
import javassist.bytecode.annotation.StringMemberValue;
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.service.ResolvedMethodParameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterContext;

import java.util.Map;

/**
 * @author Administrator
 * plugin加载顺序,默认是最后加载
 */
@Component
@Order
public class MapApiReader implements ParameterBuilderPlugin {
    @Autowired
    private TypeResolver typeResolver;

    @Override
    public void apply(ParameterContext parameterContext) {
        ResolvedMethodParameter methodParameter = parameterContext.resolvedMethodParameter();

        if (methodParameter.getParameterType().canCreateSubtype(Map.class) || methodParameter.getParameterType().canCreateSubtype(String.class)) {
            //判断是否需要修改对象ModelRef,这里我判断的是Map类型和String类型需要重新修改ModelRef对象
            Optional<ApiJsonObject> optional = methodParameter.findAnnotation(ApiJsonObject.class);
            //根据参数上的ApiJsonObject注解中的参数动态生成Class
            if (optional.isPresent()) {
                String name = optional.get().name();
                //model 名称
                ApiJsonProperty[] properties = optional.get().value();

                parameterContext.getDocumentationContext().getAdditionalModels().add(typeResolver.resolve(createRefModel(properties, name)));
                //像documentContext的Models中添加我们新生成的Class
                parameterContext.parameterBuilder()
                        //修改Map参数的ModelRef为我们动态生成的class
                        .parameterType("body")
                        .modelRef(new ModelRef(name))
                        .name(name);
            }
        }

    }

    private final static String BASE_PACKAGE = "com.xx.in.swagger.model.";
    //动态生成的Class名

    /**
     * 根据propertys中的值动态生成含有Swagger注解的javaBeen
     */
    private Class createRefModel(ApiJsonProperty[] propertys, String name) {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass(BASE_PACKAGE + name);

        try {
            for (ApiJsonProperty property : propertys) {
                ctClass.addField(createField(property, ctClass));
            }
            return ctClass.toClass();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据property的值生成含有swagger apiModelProperty注解的属性
     */
    private CtField createField(ApiJsonProperty property, CtClass ctClass) throws NotFoundException, CannotCompileException {
        CtField ctField = new CtField(getFieldType(property.type()), property.key(), ctClass);
        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(property.description(), constPool));
        if (ctField.getType().subclassOf(ClassPool.getDefault().get(String.class.getName())))
            ann.addMemberValue("example", new StringMemberValue(property.example(), constPool));
        if (ctField.getType().subclassOf(ClassPool.getDefault().get(Integer.class.getName())))
            ann.addMemberValue("example", new IntegerMemberValue(Integer.parseInt(property.example()), constPool));

        attr.addAnnotation(ann);
        ctField.getFieldInfo().addAttribute(attr);

        return ctField;
    }

    private CtClass getFieldType(String type) throws NotFoundException {
        CtClass fileType = null;
        switch (type) {
            case "string":
                fileType = ClassPool.getDefault().get(String.class.getName());
                break;
            case "int":
                fileType = ClassPool.getDefault().get(Integer.class.getName());
                break;
            default:
                fileType = ClassPool.getDefault().get(String.class.getName());
                break;
        }
        return fileType;
    }

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

4、Controller控制器

    /**
     * 会员登录(通过短信或账号密码)
     */
    @ApiOperation(value = "通过短信或账号密码登录")
    @RequestMapping(value = "/signIn", method = RequestMethod.POST)
    @CrossOrigin
    public ResponseObject signIn(@ApiJsonObject(name = "param", value = {
                                         @ApiJsonProperty(key = "mobile", example = "15677888899", description = "通过短信验证码登录:手机号"),
                                         @ApiJsonProperty(key = "verifyCode", example = "123456", description = "通过短信验证码登录:短信验证码"),
                                         @ApiJsonProperty(key = "account", example = "qiaochu", description = "通过账号密码登录:登录账号"),
                                         @ApiJsonProperty(key = "password", example = "123456", description = "通过账号密码登录:登录密码"),
                                         @ApiJsonProperty(key = "captchaCode", example = "57ma", description = "通过账号密码登录:图形验证码"),
                                         @ApiJsonProperty(key = "uuid", example = "b65d461893a14f2899e1287643391981", description = "通过账号密码登录:图形验证码uuid")
                                 }) @RequestBody Map<String, Object> param) {
		// 		TODO::接收参数处理业务逻辑 
		//        String mobile = param.get("mobile") == null ? "" : param.get("mobile").toString();
		//        String verifyCode = param.get("verifyCode") == null ? "" : param.get("verifyCode").toString();
		//        String account = param.get("account") == null ? "" : param.get("account").toString();
		//        String password = param.get("password") == null ? "" : param.get("password").toString();
		//        String captchaCode = param.get("captchaCode") == null ? "" : param.get("captchaCode").toString();
		//        String uuid = param.get("uuid") == null ? "" : param.get("uuid").toString();
	}

 

注意:整个工程中ApiJsonObject种的name值不能相同,否则会报错

 如果整个工程中ApiJsonObject种的name值有多个相同,升级ApiJsonProperty注解和MapApiReader类方案如下:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author Administrator
 */
@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiJsonObject {
    //对象属性值
    ApiJsonProperty[] value();

    //对象名称
    String name();

    //对象方法
    String method();
}
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Optional;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.IntegerMemberValue;
import javassist.bytecode.annotation.StringMemberValue;
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.service.ResolvedMethodParameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterContext;

import java.util.Map;

/**
 * @author Administrator
 * plugin加载顺序,默认是最后加载
 */
@Component
@Order
public class MapApiReader implements ParameterBuilderPlugin {
    @Autowired
    private TypeResolver typeResolver;

    @Override
    public void apply(ParameterContext parameterContext) {
        ResolvedMethodParameter methodParameter = parameterContext.resolvedMethodParameter();

        if (methodParameter.getParameterType().canCreateSubtype(Map.class) || methodParameter.getParameterType().canCreateSubtype(String.class)) {
            //判断是否需要修改对象ModelRef,这里我判断的是Map类型和String类型需要重新修改ModelRef对象
            Optional<ApiJsonObject> optional = methodParameter.findAnnotation(ApiJsonObject.class);
            //根据参数上的ApiJsonObject注解中的参数动态生成Class
            if (optional.isPresent()) {
                String name = String.format("%s_%s", optional.get().name(), optional.get().method());
                //model 名称
                ApiJsonProperty[] properties = optional.get().value();
                parameterContext.getDocumentationContext().getAdditionalModels().add(typeResolver.resolve(createRefModel(properties, name)));
                //像documentContext的Models中添加我们新生成的Class
                parameterContext.parameterBuilder()
                        //修改Map参数的ModelRef为我们动态生成的class
                        .parameterType("body")
                        .modelRef(new ModelRef(name))
                        .name(optional.get().name());
            }
        }
    }

    /**
     * 动态生成的Class名
     */
    private final static String BASE_PACKAGE = "com.fuint.in.swagger.model.";
    
    /**
     * 根据propertys中的值动态生成含有Swagger注解的javaBeen
     */
    private Class createRefModel(ApiJsonProperty[] propertys, String name) {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass(BASE_PACKAGE + name);

        try {
            for (ApiJsonProperty property : propertys) {
                ctClass.addField(createField(property, ctClass));
            }
            return ctClass.toClass();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据property的值生成含有swagger apiModelProperty注解的属性
     */
    private CtField createField(ApiJsonProperty property, CtClass ctClass) throws NotFoundException, CannotCompileException {
        CtField ctField = new CtField(getFieldType(property.type()), property.key(), ctClass);
        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(property.description(), constPool));
        if (ctField.getType().subclassOf(ClassPool.getDefault().get(String.class.getName())))
            ann.addMemberValue("example", new StringMemberValue(property.example(), constPool));
        if (ctField.getType().subclassOf(ClassPool.getDefault().get(Integer.class.getName())))
            ann.addMemberValue("example", new IntegerMemberValue(Integer.parseInt(property.example()), constPool));

        attr.addAnnotation(ann);
        ctField.getFieldInfo().addAttribute(attr);

        return ctField;
    }

    private CtClass getFieldType(String type) throws NotFoundException {
        CtClass fileType = null;
        switch (type) {
            case "string":
                fileType = ClassPool.getDefault().get(String.class.getName());
                break;
            case "int":
                fileType = ClassPool.getDefault().get(Integer.class.getName());
                break;
            default:
                fileType = ClassPool.getDefault().get(String.class.getName());
                break;
        }
        return fileType;
    }

    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }
}
    /**
     * 会员登录(通过短信或账号密码)
     */
    @ApiOperation(value = "通过短信或账号密码登录")
    @RequestMapping(value = "/signIn", method = RequestMethod.POST)
    @CrossOrigin
    public ResponseObject signIn(@ApiJsonObject(method = "signIn", name = "param", value = {
                                         @ApiJsonProperty(key = "mobile", example = "15677888899", description = "通过短信验证码登录:手机号"),
                                         @ApiJsonProperty(key = "verifyCode", example = "123456", description = "通过短信验证码登录:短信验证码"),
                                         @ApiJsonProperty(key = "account", example = "qiaochu", description = "通过账号密码登录:登录账号"),
                                         @ApiJsonProperty(key = "password", example = "123456", description = "通过账号密码登录:登录密码"),
                                         @ApiJsonProperty(key = "captchaCode", example = "57ma", description = "通过账号密码登录:图形验证码"),
                                         @ApiJsonProperty(key = "uuid", example = "b65d461893a14f2899e1287643391981", description = "通过账号密码登录:图形验证码uuid")
                                 }) @RequestBody Map<String, Object> param) {
		// 		TODO::接收参数处理业务逻辑 
		//        String mobile = param.get("mobile") == null ? "" : param.get("mobile").toString();
		//        String verifyCode = param.get("verifyCode") == null ? "" : param.get("verifyCode").toString();
		//        String account = param.get("account") == null ? "" : param.get("account").toString();
		//        String password = param.get("password") == null ? "" : param.get("password").toString();
		//        String captchaCode = param.get("captchaCode") == null ? "" : param.get("captchaCode").toString();
		//        String uuid = param.get("uuid") == null ? "" : param.get("uuid").toString();
	}

注意: 整个工程中ApiJsonObject种的name_method值不能相同,否则会报错

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值