美文网首页
使用 Swagger 的扩展组件Plugin 机制自定义API文

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

作者: 光剑书架上的书 | 来源:发表于2021-08-27 01:04 被阅读0次

    简史

    让我们先理一下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/109259315
    https://blog.csdn.net/wsh900221/article/details/80508548

    相关文章

      网友评论

          本文标题:使用 Swagger 的扩展组件Plugin 机制自定义API文

          本文链接:https://www.haomeiwen.com/subject/tivpiltx.html