背景
在原有文档的基础上, 丰富或更改文档的信息 , 例如在@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()));
}
}
}
网友评论