前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用 Swagger 的扩展组件Plugin 机制自定义API文档的生成

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

作者头像
一个会写诗的程序员
发布2021-12-16 11:20:35
1.4K0
发布2021-12-16 11:20:35
举报

简史

让我们先理一下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

代码语言:javascript
复制
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

For Gradle

代码语言:javascript
复制
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方法中我们去手动扫描我们自定义的注解,然后加上相关实现的逻辑即可。

代码示例:

代码语言:javascript
复制
/**
 * 针对传值的参数自定义注解
 * @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

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

代码语言:javascript
复制
@Api(value = "UserController", description = "用户相关api")

@ApiOperation

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

代码语言:javascript
复制
@ApiOperation(value = "查找用户", notes = "查找用户", httpMethod = "GET", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)

@ApiImplicitParams

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

@ApiImplicitParam

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

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

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

@ApiResponses

用于表示一组响应

@ApiResponse

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

代码语言:javascript
复制
@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注解进行描述的时候)

代码语言:javascript
复制
@ApiModel(value = "用户实体类")

@ApiModelProperty

描述一个model的属性

代码语言:javascript
复制
@ApiModelProperty(value = "登录用户")
@ApiIgnore //使用这个注解忽略这个接口

参考资料

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021/8/27 上,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简史
  • SpringFox
  • Getting Started
    • For new projects
    • Swagger的可扩展组件
    • Swagger 常用注解
      • @Api
        • @ApiOperation
          • @ApiImplicitParams
            • @ApiImplicitParam
              • @ApiResponses
                • @ApiResponse
                  • @ApiModel
                    • @ApiModelProperty
                    • 参考资料
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档