使用 Swagger 的扩展组件Plugin 机制自定义API文档的生成

Posted 东海陈光剑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 Swagger 的扩展组件Plugin 机制自定义API文档的生成相关的知识,希望对你有一定的参考价值。

简史

让我们先理一下springfox与swagger的关系。

swagger是一个流行的API开发框架,这个框架以“开放API声明”(OpenAPI Specification,OAS)为基础,对整个API的开发周期都提供了相应的解决方案,是一个非常庞大的项目(包括设计、编码和测试,几乎支持所有语言)。

OAS本身是一个API规范,它用于描述一整套API接口,包括一个接口是GET还是POST请求啊,有哪些参数哪些header啊,都会被包括在这个文件中。它在设计的时候通常是YAML格式,这种格式书写起来比较方便,而在网络中传输时又会以json形式居多,因为json的通用性比较强。

由于Spring的流行,Marty Pitt编写了一个基于Spring的组件swagger-springmvc,用于将swagger集成到springmvc中来。而springfox则是从这个组件发展而来,同时springfox也是一个新的项目,本文仍然是使用其中的一个组件springfox-swagger2。

pringfox-swagger2依然是依赖OSA规范文档,也就是一个描述API的json文件,而这个组件的功能就是帮助我们自动生成这个json文件,我们会用到的另外一个组件springfox-swagger-ui就是将这个json文件解析出来,用一种更友好的方式呈现出来。

SpringFox

Github: https://github.com/springfox/springfox

Automated JSON API documentation for API's built with Spring.

Getting Started

For new projects

For Maven

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

For Gradle

implementation "io.springfox:springfox-boot-starter:<version>"

Swagger的可扩展组件

https://github.com/springfox/springfox/tree/master/springfox-spi/src/main/java/springfox/documentation/spi/service

在源码中( https://github.com/springfox/springfox ), 可以看到下图所示的一些Plugin结尾的接口文件,我们就是要在这些上面做文章的。

自定义扩展功能的话,只需要实现某个xxxPlugin的接口中的apply方法就可以。apply方法中我们去手动扫描我们自定义的注解,然后加上相关实现的逻辑即可。

代码示例:

/**
 * 针对传值的参数自定义注解
 * @author zhenghui
 * @date 2020年9月13日13:25:18
 * @desc 读取自定义的属性并动态生成model
 */
@Configuration
@Order(-19999)   //plugin加载顺序,默认是最后加载
public class SwaggerModelReader implements ParameterBuilderPlugin {

    @Autowired
    private TypeResolver typeResolver;

    static final Map<String,String> MAPS = new HashMap<>();
    static {
        MAPS.put("byte","java.lang.Byte");
        MAPS.put("short","java.lang.Short");
        MAPS.put("integer","java.lang.Integer");
        MAPS.put("long","java.lang.Long");
        MAPS.put("float","java.lang.Float");
        MAPS.put("double","java.lang.Double");
        MAPS.put("char","java.lang.Character");
        MAPS.put("string","java.lang.String");
        MAPS.put("boolean","java.lang.Boolean");
    }

    //根据用户自定义的类型拿到该类型所在的包的class位置
    static public String getTypePath(String key){
        return key==null || !MAPS.containsKey(key.toLowerCase()) ? null :  MAPS.get(key.toLowerCase());
    }


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

        //自定义的注解
        Optional<ApiIgp> apiIgp = methodParameter.findAnnotation(ApiIgp.class);
        Optional<Apicp> apicp = methodParameter.findAnnotation(Apicp.class);



        if (apiIgp.isPresent() || apicp.isPresent()) {
            Class originClass = null;
            String[] properties = null; //注解传递的参数
            Integer annoType = 0;//注解的类型
            String name = null + "Model" + 1;  //model 名称  //参数名称

            String[] noValues = null;
            String[] noValueTypes = null;
            String[] noVlaueExplains = null;
            //拿到自定义注解传递的参数
            if (apiIgp.isPresent()){
                properties = apiIgp.get().values(); //排除的
                originClass = apiIgp.get().classPath();//原始对象的class
                name = apiIgp.get().modelName() ;  //model 名称  //参数名称

                noValues = apiIgp.get().noValues();
                noValueTypes = apiIgp.get().noValueTypes();
                noVlaueExplains = apiIgp.get().noVlaueExplains();

            }else {
                properties = apicp.get().values(); //需要的
                annoType = 1;
                originClass = apicp.get().classPath();//原始对象的class
                name = apicp.get().modelName() ;//自定义类的名字
                noValues = apicp.get().noValues();
                noValueTypes = apicp.get().noValueTypes();
                noVlaueExplains = apicp.get().noVlaueExplains();
            }

            //生成一个新的类
            Class newClass = createRefModelIgp(properties, noValues, noValueTypes, noVlaueExplains, name, originClass, annoType);


            context.getDocumentationContext()
                    .getAdditionalModels()
                    .add(typeResolver.resolve(newClass));  //向documentContext的Models中添加我们新生成的Class


            context.parameterBuilder()  //修改model参数的ModelRef为我们动态生成的class
                    .parameterType("body")
                    .modelRef(new ModelRef(name))
                    .name(name);

        }


    }

    /**
     *
     * @param properties annoType=1:需要的  annoType=0:排除的
     * @param noValues
     * @param noValueTypes
     * @param noVlaueExplains
     * @param name 创建的mode的名称
     * @param origin
     * @param annoType 注解的类型
     * @return
     */
    private Class createRefModelIgp(String[] properties, String[] noValues, String[] noValueTypes, String[] noVlaueExplains, String name, Class origin, Integer annoType) {
        try {
            //获取原始实体类中所有的变量
            Field[] fields = origin.getDeclaredFields();
            //转换成List集合,方便使用stream流过滤
            List<Field> fieldList = Arrays.asList(fields);
            //把传入的参数也转换成List
            List<String> dealProperties = Arrays.asList(properties);//去掉空格并用逗号分割
            //过滤出来已经存在的
            List<Field> dealFileds = fieldList
                    .stream()
                    .filter(s ->
                            annoType==0 ? (!(dealProperties.contains(s.getName()))) //如果注解的类型是0,说明要取反
                                        : dealProperties.contains(s.getName())
                            ).collect(Collectors.toList());

            //存储不存在的变量
            List<String> noDealFileds = Arrays.asList(noValues);
            List<String> noDealFiledTypes = Arrays.asList(noValueTypes);
            List<String> noDealFiledExplains = Arrays.asList(noVlaueExplains);


            //创建一个类
            ClassPool pool = ClassPool.getDefault();
            CtClass ctClass = pool.makeClass(origin.getPackage().getName()+"."+name);

            //创建对象,并把已有的变量添加进去
            createCtFileds(dealFileds,noDealFileds,noDealFiledTypes,noDealFiledExplains,ctClass,annoType);

            //返回最终的class
            return ctClass.toClass();

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


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

    /**
     * 根据propertys中的值动态生成含有Swagger注解的javaBeen
     *
     * @param dealFileds  原始对象中已经存在的对象属性名字
     * @param noDealFileds  原始对象中不存在的对象属性名字
     * @param noDealFiledTypes 原始对象中不存在的对象属性的类型,八大基本类型例如:dounle等,还有String
     * @param noDealFiledExplains  自定义变量的参数说明
     * @param ctClass 源class
     * @throws CannotCompileException
     * @throws NotFoundException
     * @throws ClassNotFoundException
     */
    public void createCtFileds(List<Field> dealFileds, List<String> noDealFileds, List<String> noDealFiledTypes,List<String> noDealFiledExplains, CtClass ctClass, Integer annoType) {
       //添加原实体类存在的的变量
//        if(annoType==1)
        for (Field field : dealFileds) {
            CtField ctField = null;
            try {
                ctField = new CtField(ClassPool.getDefault().get(field.getType().getName()), field.getName(), ctClass);
            } catch (CannotCompileException e) {
                System.out.println("找不到了1:"+e.getMessage());
            } catch (NotFoundException e) {
                System.out.println("找不到了2:"+e.getMessage());
            }
            ctField.setModifiers(Modifier.PUBLIC);
            ApiModelProperty annotation = field.getAnnotation(ApiModelProperty.class);
            String apiModelPropertyValue = java.util.Optional.ofNullable(annotation).map(s -> s.value()).orElse("");



            if (StringUtils.isNotBlank(apiModelPropertyValue)) { //添加model属性说明
                ConstPool constPool = ctClass.getClassFile().getConstPool();

                AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
                Annotation ann = new Annotation(ApiModelProperty.class.getName(), constPool);
                ann.addMemberValue("value", new StringMemberValue(apiModelPropertyValue,constPool));
                attr.addAnnotation(ann);

                ctField.getFieldInfo().addAttribute(attr);
            }
            try {
                ctClass.addField(ctField);
            } catch (CannotCompileException e) {
                System.out.println("无法添加字段1:"+e.getMessage());
            }
        }

        //添加原实体类中不存在的的变量
         for (int i = 0; i < noDealFileds.size(); i++) {
            String valueName = noDealFileds.get(i);//变量名字
            String valueType = noDealFiledTypes.get(i);//变量的类型
            valueType=getTypePath(valueType);

            //根据变量的类型,变量的名字,变量将要在的类 创建一个变量
             CtField ctField = null;
             try {
                 ctField = new CtField(ClassPool.getDefault().get(valueType), valueName, ctClass);
             } catch (CannotCompileException e) {
                 System.out.println("找不到了3:"+e.getMessage());
             } catch (NotFoundException e) {
                 System.out.println("找不到了4:"+e.getMessage());
             }
             ctField.setModifiers(Modifier.PUBLIC);//设置权限范围是私有的,或者public等

             if(noDealFiledExplains.size()!=0){
                 //参数设置描述
                 String apiModelPropertyValue = (apiModelPropertyValue=noDealFiledExplains.get(i))==null?"无描述":apiModelPropertyValue;//参数描述

                 System.out.println(apiModelPropertyValue);

                 if (StringUtils.isNotBlank(apiModelPropertyValue)) { //添加model属性说明
                     ConstPool constPool = ctClass.getClassFile().getConstPool();
                     AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
                     Annotation ann = new Annotation(ApiModelProperty.class.getName(), constPool);
                     ann.addMemberValue("value", new StringMemberValue(apiModelPropertyValue,constPool));
                     attr.addAnnotation(ann);

                     ctField.getFieldInfo().addAttribute(attr);
                 }

             }

             //把此变量添加到类中
             try {
                 ctClass.addField(ctField);
             } catch (CannotCompileException e) {
                 System.out.println("无法添加字段2:"+e.getMessage());
             }

         }

    }
}

Swagger 常用注解

@Api

用在类上,说明该类的作用

@Api(value = "UserController", description = "用户相关api")

@ApiOperation

用在方法上,说明方法的作用

@ApiOperation(value = "查找用户", notes = "查找用户", httpMethod = "GET", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)

@ApiImplicitParams

用在方法上包含一组参数说明

@ApiImplicitParam

用在@ApiImplicitParams注解中,指定一个请求参数的各个方面
paramType:参数放在哪个地方

header–>请求参数的获取:@RequestHeader
query–>请求参数的获取:@RequestParam
path(用于restful接口)–>请求参数的获取:@PathVariable
body(不常用)
form(不常用)

name:参数名
dataType:参数类型
required:参数是否必须传
value:参数的意思
defaultValue:参数的默认值
@ApiImplicitParams({
        @ApiImplicitParam(name = "id", value = "唯一id", required = true, dataType = "Long", paramType = "path"),
})

@ApiResponses

用于表示一组响应

@ApiResponse

用在@ApiResponses中,一般用于表达一个错误的响应信息
code:数字,例如400
message:信息,例如”请求参数没填好”
response:抛出异常的类

@ApiResponses(value = {
        @ApiResponse(code = 400, message = "No Name Provided")  
})

@ApiModel

Swagger-core builds the model definitions based on the references to them throughout the API introspection.

The @ApiModel allows you to manipulate the meta data of a model from a simple description or name change to a definition of polymorphism.

描述一个Model的信息(这种一般用在post创建的时候,使用@RequestBody这样的场景,请求参数无法使用@ApiImplicitParam注解进行描述的时候)

@ApiModel(value = "用户实体类")

@ApiModelProperty

描述一个model的属性

@ApiModelProperty(value = "登录用户")
@ApiIgnore //使用这个注解忽略这个接口

参考资料

https://blog.csdn.net/qq_17623363/article/details/109259315https://blog.csdn.net/wsh900221/article/details/80508548

以上是关于使用 Swagger 的扩展组件Plugin 机制自定义API文档的生成的主要内容,如果未能解决你的问题,请参考以下文章

使用maven导出swagger2文档

利用Swagger Maven Plugin生成Rest API文档

Ranger安装部署 - 扩展组件安装

导入 svelte 组件,省略 .svelte 扩展名

手写webpack增加plugin

Swagger-Codegen 不使用供应商扩展 x-discriminator-value