1. 添加pom依赖
1.1. 根pom下
添加统一的版本管理
<properties>
<knife4j.version>3.0.3</knife4j.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-micro-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
1.2. gateway的pom
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
1.3. 其他服务的pom
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-micro-spring-boot-starter</artifactId>
</dependency>
与gateway的区别就在于这个包少了ui依赖knife4j-spring-ui
。
2. 配置gateway服务
2.1. 配置swagger核心配置 docket
package com.holland.gateway.swagger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfiguration {
@Value("${spring.application.name}")
private String name;
@Bean
public Docket defaultApi2() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(groupApiInfo(name))
//分组名称 想要网关被记录到swagger就不要开分组
// .groupName("2.X版本")
.select()
//这里指定Controller扫描包路径
.apis(RequestHandlerSelectors.basePackage("com.holland." + name + ".controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo groupApiInfo(String name) {
return new ApiInfoBuilder()
.title("后端接口文档-" + name)
.description("<div style='font-size:14px;color:red;'>description</div>")
.termsOfServiceUrl("N")
.contact(new Contact("HollanZang", "https://juejin.cn/user/352263461681214", "zhn.pop@gmail.com"))
.version("1.0")
.build();
}
}
2.2. 配置swagger接口
package com.holland.gateway.swagger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.*;
import java.util.Optional;
@RestController
public class SwaggerHandler {
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
private UiConfiguration uiConfiguration;
private final SwaggerResourcesProvider swaggerResources;
@Autowired
public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
this.swaggerResources = swaggerResources;
}
@GetMapping("/swagger-resources/configuration/security")
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("/swagger-resources/configuration/ui")
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("/swagger-resources")
public Mono<ResponseEntity<?>> swaggerResources() {
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
}
2.3. 配置过滤器
2.3.1 官方demo做法
package com.xiaominfo.swagger.service.doc.config;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
/**
* @author fsl
* @description: SwaggerHeaderFilter
* @date 2019-06-0310:47
*/
@Component
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
private static final String HEADER_NAME = "X-Forwarded-Prefix";
private static final String URI = "/v2/api-docs";
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
if (!StringUtils.endsWithIgnoreCase(path,URI )) {
return chain.filter(exchange);
}
String basePath = path.substring(0, path.lastIndexOf(URI));
ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
return chain.filter(newExchange);
};
}
}
2.3.2 我的项目配置
package com.holland.gateway.swagger;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
public class SwaggerRouteFilter {
private static final String HEADER_NAME = "X-Forwarded-Prefix";
private static final String URI = "/v2/api-docs";
public static WebFilter getWebFilter() {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
if (!StringUtils.endsWithIgnoreCase(path, URI)) {
return chain.filter(exchange);
}
String basePath = path.substring(0, path.lastIndexOf(URI));
ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
return chain.filter(newExchange);
};
}
}
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.addFilterAfter(SwaggerRouteFilter.getWebFilter(), SecurityWebFiltersOrder.FIRST)
.addFilterAfter(tokenFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
.addFilterBefore(logFilter(), SecurityWebFiltersOrder.LAST)
.csrf(ServerHttpSecurity.CsrfSpec::disable);
return http.build();
}
2.4. 重点:获取其他服务的信息,才能集成其他服务的swagger
2.4.1. 从配置文件里面获取的服务信息
配置文件比如说如下,那么我们就能集成filesystem
的swagger信息
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: filesystem
uri: http://localhost:8763
predicates:
- Path=/filesystem/**
filters:
- StripPrefix=1
Config写法
package com.holland.gateway.swagger;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
@Component
@Primary
public class SwaggerResourceConfig implements SwaggerResourcesProvider {
/**
* 此写法只能获取配置文件里面的路由规则
*/
@Resource
private RouteLocator routeLocator;
@Resource
private GatewayProperties gatewayProperties;
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
List<String> routes = new ArrayList<>();
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId())).forEach(route -> {
route.getPredicates().stream()
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
.forEach(predicateDefinition -> resources.add(swaggerResource(route.getId(),
predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
.replace("**", "v2/api-docs"))));
});
return resources;
}
private SwaggerResource swaggerResource(String name, String location) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion("2.0");
return swaggerResource;
}
}
2.4.2. 通过eureka获取的服务信息
只用修改public List<SwaggerResource> get()
方法
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
discoveryClient.getServices()
.stream()
//排除掉不需要swagger的模块和自身模块
.filter(s -> !"eureka".equals(s) && !"admin".equals(s) && !"gateway".equals(s))
.forEach(appName -> resources.add(swaggerResource(appName, appName + "/swagger/v2/api-docs")));
return resources;
}
2.4.3. 将gateway自身集成到swagger
只需要改动一点点代码
List<SwaggerResource> resources = new ArrayList<>() {{
add(swaggerResource("gateway", "/v2/api-docs"));
}};
2.5. 注意事项
访问/doc.html
时会调用api/swagger-resources
。底层会调用SwaggerResourceConfig.get()
方法。
所以如果这里报错了,务必要考虑情况:
- api
/swagger-resources
获取的数组不能为空;即,必须要获得其他服务的信息。 - 至少其他服务得有
knife4j-micro-spring-boot-starter
依赖,才能保证调用接口/v2/api-docs
不会404。
另外,务必要注意url前缀的过滤情况,也可能是/v2/api-docs
抛404的原因。
3. 配置其他服务
3.1. 配置swagger核心配置 docket
package com.holland.gateway.swagger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfiguration {
@Value("${spring.application.name}")
private String name;
@Bean
public Docket defaultApi2() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(groupApiInfo(name))
.select()
//这里指定Controller扫描包路径
.apis(RequestHandlerSelectors.basePackage("com.holland." + name + ".controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo groupApiInfo(String name) {
return new ApiInfoBuilder()
.title("后端接口文档-" + name)
.description("<div style='font-size:14px;color:red;'>description</div>")
.termsOfServiceUrl("N")
.contact(new Contact("HollanZang", "https://juejin.cn/user/352263461681214", "zhn.pop@gmail.com"))
.version("1.0")
.build();
}
}
3.2. 配置swagger转发接口
这里需要添加转发接口的原因是:
/doc.html
页面里面调试其他服务的接口时,访问路径不正确。但是没有找到比较官方的解决办法,所以采用这种方式。
如果有好的实现方式希望可以评论留言交流~
package com.holland.filesystem.swagger;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import springfox.documentation.spring.web.json.Json;
import springfox.documentation.swagger2.web.Swagger2ControllerWebFlux;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
/**
* swagger转发控制器
* 为knife4j配置url前缀
*/
@Controller
public class SwaggerForwardController {
@Resource
private Swagger2ControllerWebFlux swagger2ControllerWebFlux;
@GetMapping("/swagger/v2/api-docs")
public ResponseEntity<?> forward(@RequestParam(value = "group", required = false) String swaggerGroup,
ServerHttpRequest request) {
final ResponseEntity<Json> documentation = swagger2ControllerWebFlux.getDocumentation(swaggerGroup, request);
final Map<String, Object> map = JSON.parseObject(documentation.getBody().value(), Map.class);
map.computeIfPresent("basePath", (k, pathsObj) -> "/filesystem");
map.computeIfPresent("paths", (k, pathsObj) -> {
final Map<String, JSONObject> paths = ((JSONObject) pathsObj).toJavaObject(Map.class);
final Map<String, JSONObject> res = new HashMap<>(paths.size());
paths.forEach((k1, v) -> res.put("/filesystem" + k1, v));
return res;
});
return ResponseEntity.ok(map);
}
}
4. 访问接口文档
大功告成!