Bootstrap

Swagger2文档生成 扩展支持 Map JSONObject 传参

本功能在 Swagger 2.7.0   和   2.9.2 版本 测试通过 ,保证可正常使用,

有问题联系[email protected]

关于Swagger2本身不支持Map/JSONObject传参 生成文档,在网上找了很多,大多是自定义注解,借助第三方插件动态生成类,或者重写Swagger2的多数文件,显得很复杂,而且要看很多代码,跟踪才能了解并修改为自己的,而且还时好时坏,但是这些资料又给我带来了新的灵感。

大体思路,

1、新建一个静态Map,存储自己新建的model变量(不需要创建实体类)

2、自定义注解,实现配置参数信息

3、继承Swagger的部分builder类,根据注解 创建自己的 model 塞入 静态Map

4、添加Swagger Aop切面,将静态Map 合并到 Swagger的model中返回

下面是实现代码 

import com.cloud.utils.util.baseenum.DataType;

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

/**
 * @author: fengyuan
 * @date 2021-6-3 17:23:28
 * @Description TODO Swagger2配置
 */
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PropFy2 {

    String name(); // key

    String eg() default "";// 示例

    DataType type() default DataType.String; // 支持string、int、double

    String desc() default "";// 参数描述

    boolean required() default false; // 是否必传

}
import com.cloud.utils.util.baseenum.DataType;

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

/**
 * @author: fengyuan
 * @date 2021-6-3 17:23:28
 * @Description TODO Swagger2配置
 */
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PropFy {

    String name(); // key

    String eg() default "";// 示例

    DataType type() default DataType.String; // 支持string、int、double

    String desc() default "";// 参数描述

    boolean required() default false; // 是否必传

    PropFy2[] map() default {};

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

/**
 * @author: fengyuan
 * @date 2021-6-3 17:23:28
 * @Description TODO Swagger2配置
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ResObjFY {

    PropFy[] value() default {}; //请求属性值

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

/**
 * @author: fengyuan
 * @date 2021-6-3 17:23:28
 * @Description TODO Swagger2配置
 */
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReqObjFy {

    PropFy[] value(); //对象属性值

}
//随便找个地方
public static Map<String, Model> modelMap = new HashMap<>();
//此为创建Model的方法,深度支持2级
import com.cloud.utils.util.baseenum.DataType;
import com.cloud.utils.utilabstract.BasePubFun;
import io.swagger.models.ModelImpl;
import io.swagger.models.properties.*;
import org.apache.commons.lang3.StringUtils;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author: fengyuan
 * @date 2021-6-3 15:17:49
 * @Description TODO Swagger2配置公共方法
 */
public class SwaggerFun {

    public static String createModelRst(String name, PropFy[] pros) {

        createModel(name, pros);

        Map<String, Property> proMap = new LinkedHashMap<>();

        Property code = new StringProperty();
        code.setName("code");
        code.setDescription("状态'10000'成功,其他失败");
        code.setExample((Object) "10000");
        proMap.put("code", code);

        Property msg = new StringProperty();
        msg.setName("msg");
        msg.setDescription("提示");
        proMap.put("msg", msg);

        Property msgext = new StringProperty();
        msgext.setName("msgext");
        msgext.setDescription("详情");
        proMap.put("msgext", msgext);

        Property result = new RefProperty(name);
        result.setName("result");
        result.setDescription("结果集");
        proMap.put("result", result);

        Property success = new BooleanProperty();
        success.setName("success");
        success.setDescription("状态 true成功 false失败");
        proMap.put("success", success);

        //创建model
        String rstName = BasePubFun.format("RstData<{}>", name);
        ModelImpl model = new ModelImpl();
        model.setName(rstName);
        model.setProperties(proMap);
        model.setType("object");
        SwaggerFy.modelMap.put(rstName, model);
        return model.getName();
    }

    public static void createModel(String name, PropFy[] pros) {

        Map<String, Property> proMap = new LinkedHashMap<>();
        for (PropFy pro : pros) {
            Property property = null;
            try {
                if (DataType.String.equals(pro.type())) {
                    property = new StringProperty();
                    property.setExample(StringUtils.isBlank(pro.eg()) ? "" : (Object) pro.eg());
                } else if (DataType.Long.equals(pro.type())) {
                    property = new LongProperty();
                    property.setExample(StringUtils.isBlank(pro.eg()) ? 0L : Long.valueOf(pro.eg()));
                } else if (DataType.Integer.equals(pro.type())) {
                    property = new IntegerProperty();
                    property.setExample(StringUtils.isBlank(pro.eg()) ? 0 : Integer.valueOf(pro.eg()));
                } else if (DataType.Date.equals(pro.type())) {
                    property = new DateProperty();
                    property.setExample(StringUtils.isBlank(pro.eg()) ? "" : Boolean.valueOf(pro.eg()));
                } else if (DataType.Boolean.equals(pro.type())) {
                    property = new BooleanProperty();
                    property.setExample(StringUtils.isBlank(pro.eg()) ? "" : Boolean.valueOf(pro.eg()));
                } else if (DataType.Map.equals(pro.type())) {
                    String tempName = name + "_" + pro.name();
                    createModel(tempName, pro.map());
                    property = new RefProperty(tempName);
                } else {
                    property = new StringProperty();
                    property.setExample((Object) pro.eg());
                }
            } catch (Exception e) {
            }
            property.setDescription(pro.desc());
            property.setRequired(pro.required());
            property.setName(pro.name());
            proMap.put(pro.name(), property);
        }
        ModelImpl model = new ModelImpl();
        model.setName(name);
        model.setProperties(proMap);
        model.setType("object");
        SwaggerFy.modelMap.put(name, model);
    }

    private static void createModel(String name, PropFy2[] pros) {
        Map<String, Property> proMap = new LinkedHashMap<>();
        for (PropFy2 pro : pros) {
            Property property = null;
            if (DataType.String.equals(pro.type())) {
                property = new StringProperty();
                property.setExample(StringUtils.isBlank(pro.eg()) ? "" : (Object) pro.eg());
            } else if (DataType.Long.equals(pro.type())) {
                property = new LongProperty();
                property.setExample(StringUtils.isBlank(pro.eg()) ? 0L : Long.valueOf(pro.eg()));
            } else if (DataType.Integer.equals(pro.type())) {
                property = new IntegerProperty();
                property.setExample(StringUtils.isBlank(pro.eg()) ? 0 : Integer.valueOf(pro.eg()));
            } else if (DataType.Date.equals(pro.type())) {
                property = new DateProperty();
                property.setExample(StringUtils.isBlank(pro.eg()) ? "" : Boolean.valueOf(pro.eg()));
            } else if (DataType.Boolean.equals(pro.type())) {
                property = new BooleanProperty();
                property.setExample(!StringUtils.isBlank(pro.eg()) && Boolean.valueOf(pro.eg()));
            }
            property.setDescription(pro.desc());
            property.setRequired(pro.required());
            property.setName(pro.name());
            proMap.put(pro.name(), property);
        }
        ModelImpl model = new ModelImpl();
        model.setName(name);
        model.setProperties(proMap);
        model.setType("object");
        SwaggerFy.modelMap.put(name, model);
    }

}
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Optional;
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: fengyuan
 * @date 2021-6-3 15:17:49
 * @Description TODO Swagger2配置
 */
@Component
@Order // plugin加载顺序,默认是最后加载
public class ParamReqPlugin implements ParameterBuilderPlugin {

    @Override
    public void apply(ParameterContext parameterContext) {
        ResolvedMethodParameter methodParameter = parameterContext.resolvedMethodParameter();
        // 判断是否需要修改对象ModelRef,这里我判断的是Map类型和JSONObject类型需要重新修改ModelRef对象
        if (!methodParameter.getParameterType().canCreateSubtype(Map.class) && methodParameter.getParameterType().canCreateSubtype(JSONObject.class)) {
            return;
        }
        Optional<ReqObjFy> optional = methodParameter.findAnnotation(ReqObjFy.class);
        if (!optional.isPresent()) {
            return;
        }
        String name = parameterContext.getOperationContext().requestMappingPattern().replace("/", "_").substring(1);
        SwaggerFun.createModel(name, optional.get().value());
        parameterContext.parameterBuilder().parameterType("body").modelRef(new ModelRef(name)).name(name);
    }

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

}
import com.cloud.utils.dto.RstData;
import com.fasterxml.classmate.ResolvedType;
import com.google.common.base.Optional;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import springfox.documentation.builders.ResponseMessageBuilder;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ResponseMessage;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.OperationBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;

import java.util.HashSet;

/**
 * @author: fengyuan
 * @date 2021-6-3 15:17:49
 * @Description TODO Swagger2配置
 */
@Component
@Order // plugin加载顺序,默认是最后加载
public class ParamResPlugin implements OperationBuilderPlugin {

    @Override
    public void apply(OperationContext operationContext) {
        ResolvedType returnType = operationContext.getReturnType();
        // 判断是否需要修改对象ModelRef,这里我判断的是RstData类型需要重新修改ModelRef对象
        if (!returnType.canCreateSubtype(RstData.class)) {
            return;
        }
        Optional<ResObjFY> optional = operationContext.findAnnotation(ResObjFY.class);
        if (!optional.isPresent()) {
            return;
        }
        String name = operationContext.requestMappingPattern().replace("/", "_").substring(1) + "_rst";
        String rstName = SwaggerFun.createModelRst(name, optional.get().value());
        ResponseMessage responseMessage = new ResponseMessageBuilder()
                .code(200).responseModel(new ModelRef(rstName))
                .build();
        HashSet<ResponseMessage> responseMessages = new HashSet<>();
        responseMessages.add(responseMessage);
        operationContext.operationBuilder().responseMessages(responseMessages);
    }

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

import io.swagger.models.Swagger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * @author: fengyuan
 * @date 2021-6-3 17:23:28
 * @Description TODO Swagger2配置
 */
@Aspect
@Component
public class SwaggerAop {

    @Pointcut(value = "execution(public * springfox.documentation.swagger2.mappers.ServiceModelToSwagger2MapperImpl.mapDocumentation(..))")
    public void point() {
    }

    @Around("point()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Swagger swagger = (Swagger) proceedingJoinPoint.proceed();
        swagger.getDefinitions().putAll(SwaggerFy.modelMap);
        return swagger;
    }

}
@PostMapping(value = "demo")
@ApiOperation("请求参数配置方式,支持二级")
@ResObjFY({
        @PropFy(name = "id", eg = "123", desc = "主键", type = DataType.Long, required = true),
        @PropFy(name = "row", desc = "具体数据", type = DataType.Map, map = {
                @PropFy2(name = "idCard", eg = "610126198911111111", desc = "身份证号", type = DataType.String, required = true),
                @PropFy2(name = "name", eg = "张三", desc = "姓名", required = true),
                @PropFy2(name = "sex", eg = "1", desc = "性别 1男 2女", type = DataType.Integer),
        })
})
public RstData demo(
        @ReqObjFy({
                @PropFy(name = "id", eg = "123", desc = "主键", type = DataType.Long, required = true),
                @PropFy(name = "row", desc = "具体数据", type = DataType.Map, map = {
                        @PropFy2(name = "idCard", eg = "610126198911111111", desc = "身份证号", type = DataType.String, required = true),
                        @PropFy2(name = "name", eg = "张三", desc = "姓名", required = true),
                        @PropFy2(name = "sex", eg = "1", desc = "性别 1男 2女", type = DataType.Integer),
                })
        })
        @RequestBody Map<String, Object> params) {
    return RstData.success(params);
}

效果图 

2.7.0

2.9.2

下面为Swagger扩展Ui的依赖及效果图
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>swagger-bootstrap-ui</artifactId>
    <version>1.9.6</version>
</dependency>

;