Swagger自定义文档插件

2021-05-13  本文已影响0人  清蒸三文鱼_

背景

在原有文档的基础上, 丰富或更改文档的信息 , 例如在@ApiOperation中自动添加权限码的说明, 那么可以通过Swagger的OperationBuilderPlugin插件来实现

Maven

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.3.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
    </dependencies>

Code

测试入口和主程序

@SpringBootApplication
@RestController
@EnableSwagger2
public class AppDemo {
    public static void main(String[] args) {
        SpringApplication.run(AppDemo.class, args);
    }
    @GetMapping("swaggerTest1")
    @ApiOperation(value = "swagger测试", notes = "测试1")
    @AutoPermission
    public LocalDateTime swaggerTest1() {
        return LocalDateTime.now();
    }
}

自定义注解类

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoPermission {
    String value() default "";
    boolean auto() default true;
}

Swagger插件类

自定义的插件Bean加载, 优先级要迟于Swagger自带的插件, 否则文档中的值会被覆盖, 如被自带的notes解析类覆盖OperationNotesReader

import com.google.common.base.Optional;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.OperationBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;
import springfox.documentation.spring.web.DescriptionResolver;
import springfox.documentation.swagger.common.SwaggerPluginSupport;

@Component
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1)
public class CustomOperationPlugin implements OperationBuilderPlugin {
    private final DescriptionResolver descriptions;
    @Autowired
    public CustomOperationPlugin(DescriptionResolver descriptions) {
        this.descriptions = descriptions;
    }
    @Override
    public void apply(OperationContext context) {
        Optional<ApiOperation> apiOperationOp = context.findAnnotation(ApiOperation.class);
        if (apiOperationOp.isPresent()) {
            String notes = apiOperationOp.get().notes();
            Optional<AutoPermission> autoPermissionOp = context.findAnnotation(AutoPermission.class);
            //添加权限码到notes中
            if (autoPermissionOp.isPresent()) {
                AutoPermission autoPermission = autoPermissionOp.get();
                String code = autoPermission.auto() ? context.requestMappingPattern().toUpperCase() : autoPermission.value();
                notes = notes + " 自定义生成的权限码:" + code;
            }
            //编辑api文档信息, notes
            context.operationBuilder().notes(descriptions.resolve(notes));
        }
    }
    @Override
    public boolean supports(DocumentationType documentationType) {
        return SwaggerPluginSupport.pluginDoesApply(documentationType);
    }
}
OperationNotesReader

获取handleMethod

如果需要更复杂的操作, 如根据方法和类上的信息更改Swagger文档, 不是必须的视情况而定

    private Method getHandleMethod(OperationContext context, boolean useReflect) {
        //通过反射方式获取Method
        if (useReflect){
            Field requestContextField = ReflectionUtils.findField(OperationContext.class, "requestContext");
            requestContextField.setAccessible(true);
            RequestMappingContext requestMappingContext = (RequestMappingContext) ReflectionUtils.getField(requestContextField, context);

            Field handlerField = ReflectionUtils.findField(RequestMappingContext.class, "handler");
            handlerField.setAccessible(true);
            RequestHandler requestHandler = (RequestHandler) ReflectionUtils.getField(handlerField, requestMappingContext);
            return requestHandler.getHandlerMethod().getMethod(); 
        }
        
        //通过url查找Method
        return context.getDocumentationContext().getRequestHandlers().stream()
                .filter(v -> v.getPatternsCondition().getPatterns().contains(context.requestMappingPattern()))
                .findFirst().map(v -> v.getHandlerMethod().getMethod()).get();
    }

验证

输入文档地址 http://localhost:8080/swagger-ui.html , 由图可知权限码已自动添加到文档的描述中

其他方式的实现

实现的方案有多种(任意一种均可), 可以覆盖原有的文档相关的类, 可以加入web的拦截器; 主要是分享一下思路, 具体的流程可以去debug查看SpringFox源码

1. DocumentationCache扩展

页面的请求 (Swagger2ControllerWebMvc) 最终会落到DocumentationCache, 所以在文档缓存添加的时候, 用反射更改文档字段的信息


示例代码如下,也可以通过Spring的Bean工厂对DocumentationCache先销毁再注册:
@Component
@Primary
public class DocumentationCacheExt extends DocumentationCache {
    @Override
    public void addDocumentation(Documentation documentation) {
        ApiListing apiListing = new ArrayList<>(documentation.getApiListings().get("app-demo")).get(0);
        Operation operation = apiListing.getApis().get(0).getOperations().get(0);
        Field notesField = ReflectionUtils.findField(Operation.class, "notes");
        notesField.setAccessible(true);
        String notes = (String) ReflectionUtils.getField(notesField, operation);
        ReflectionUtils.setField(notesField, operation, notes + " 我的缓存测试ABC");
        super.addDocumentation(documentation);
    }
}

2. OperationNotesReader扩展

@Component
@Primary
public class OperationNotesReaderExt extends OperationNotesReader {
    private final DescriptionResolver descriptions;
    @Autowired
    public OperationNotesReaderExt(DescriptionResolver descriptions) {
        super(descriptions);
        this.descriptions = descriptions;
    }
    @Override
    public void apply(OperationContext context) {
        Optional<ApiOperation> methodAnnotation = context.findAnnotation(ApiOperation.class);
        if (methodAnnotation.isPresent() && StringUtils.hasText(methodAnnotation.get().notes())) {
            //do something
            context.operationBuilder().notes(descriptions.resolve(methodAnnotation.get().notes()));
        }
    }
}
上一篇 下一篇

猜你喜欢

热点阅读