各微服务&网关引入依赖
com.github.xiaoymin knife4j-openapi2-spring-boot-starter 4.0.0
import com.ideaaedi.springcloud.jd.commonspring.config.Knife4jConfig;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;/*** api 基础信息配置。更多配置信息项见{@link Knife4jConfig}** @author JustryDeng
* @since 2021.0.1.D*/
@Data
@Component
public class Knife4jApiInfoProperties {/*** 要扫描api的base包*/@Value("${api-info.base-package:com}")private String basePackage;/*** 是否启用登录认证*/@Value("${api-info.enable-security:true}")private boolean enableSecurity;/*** 文档标题*/@Value("${api-info.title:}")private String title;/*** 文档描述*/@Value("${api-info.description:api info}")private String description;/*** 文档版本*/@Value("${api-info.version:1.0.0}")private String version;/*** 联系人姓名*/@Value("${api-info.contact-name:JustryDeng}")private String contactName;/*** 联系人网址*/@Value("${api-info.contact-url:https://gitee.com/JustryDeng/projects}")private String contactUrl;/*** 联系人邮箱*/@Value("${api-info.contact-email:13548417409@163.com}")private String contactEmail;
}
import com.ideaaedi.springcloud.jd.commonds.constant.BaseConstant;
import com.ideaaedi.springcloud.jd.commonspring.config.properties.Knife4jApiInfoProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;import java.util.ArrayList;
import java.util.List;/*** knife4j配置类** @author JustryDeng
* @since 2021.0.1.D*/
@Slf4j
@Configuration
public class Knife4jConfig implements WebMvcConfigurer {/** 文档相关资源的链接(需保证这些资源不需要鉴权即可访问) */public static String[] RESOURCE_URLS = new String[]{"/webjars/**", "/swagger**", "/v2/api-docs", "/doc.html"};@Value("${spring.application.name:}")private String applicationName;@Beanpublic Docket docket(Knife4jApiInfoProperties knife4jApiInfoProperties) {String apiBasePackage = knife4jApiInfoProperties.getBasePackage();Docket docket = new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo(knife4jApiInfoProperties)).select().apis(RequestHandlerSelectors.basePackage(apiBasePackage)).paths(PathSelectors.any()).build();if (knife4jApiInfoProperties.isEnableSecurity()) {docket.securitySchemes(securitySchemes()).securityContexts(securityContexts());}return docket;}@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");}private ApiInfo apiInfo(Knife4jApiInfoProperties knife4jApiInfoProperties) {return new ApiInfoBuilder().title(knife4jApiInfoProperties.getTitle()).description(knife4jApiInfoProperties.getDescription()).termsOfServiceUrl(StringUtils.isBlank(applicationName) ? "" : "/" + applicationName).contact(new Contact(knife4jApiInfoProperties.getContactName(), knife4jApiInfoProperties.getContactUrl(), knife4jApiInfoProperties.getContactEmail())).version(knife4jApiInfoProperties.getVersion()).build();}private List securitySchemes() {// 设置请求头信息List result = new ArrayList<>();// 第一个参数,自定义即可。 如:BaseConstant.JWT_TOKEN_KEY=Auth-Token,然后在代码里request.getHeader(BaseConstant.JWT_TOKEN_KEY)取值ApiKey apiKey = new ApiKey(BaseConstant.JWT_TOKEN_KEY, "Authorization", "header");result.add(apiKey);return result;}private List securityContexts() {// 设置需要登录认证的路径List result = new ArrayList<>();result.add(getContextByPath("/*/.*"));return result;}private SecurityContext getContextByPath(String pathRegex) {return SecurityContext.builder().securityReferences(defaultAuth()).forPaths(PathSelectors.regex(pathRegex)).build();}private List defaultAuth() {List result = new ArrayList<>();AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];authorizationScopes[0] = authorizationScope;result.add(new SecurityReference("Authorization", authorizationScopes));return result;}}
对于管控了权限的应用,应放行以下资源
# 需要放行的资源已经定义进上面编写的Knife4jConfig中
public static String[] RESOURCE_URLS = new String[]{"/webjars/**", "/swagger**", "/v2/api-docs", "/doc.html"};
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import springfox.documentation.swagger.web.UiConfiguration;
import springfox.documentation.swagger.web.UiConfigurationBuilder;import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;/*** 网关knife4j配置** @author JustryDeng
* @since 2021.0.1.D*/
@RestController
public class Knife4jGatewayConfig {private final SecurityConfiguration securityConfiguration;private final UiConfiguration uiConfiguration;private final SwaggerResourceAdapter swaggerResourceAdapter;public Knife4jGatewayConfig(@Autowired(required = false) SecurityConfiguration securityConfiguration,@Autowired(required = false) UiConfiguration uiConfiguration,SwaggerResourceAdapter swaggerResourceAdapter) {this.securityConfiguration = securityConfiguration;this.uiConfiguration = uiConfiguration;this.swaggerResourceAdapter = swaggerResourceAdapter;}/*** 安全配置*/@GetMapping("/swagger-resources/configuration/security")public Mono> securityConfiguration() {return Mono.just(new ResponseEntity<>(Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));}/*** ui配置*/@GetMapping("/swagger-resources/configuration/ui")public Mono> uiConfiguration() {return Mono.just(new ResponseEntity<>(Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));}/*** 资源配置,自动路由到微服务中的各个服务的api-docs信息*/@GetMapping("/swagger-resources")public Mono>> swaggerResources() {return Mono.just(new ResponseEntity<>(swaggerResourceAdapter.get(), HttpStatus.OK));}/*** favicon.ico*/@GetMapping("/favicon.ico")public Mono> favicon() {return Mono.just(new ResponseEntity<>(null, HttpStatus.OK));}/*** swagger资源适配器** @author JustryDeng
* @since 2021.0.1.D*/@Slf4j@Componentpublic static class SwaggerResourceAdapter implements SwaggerResourcesProvider {/*** spring-cloud-gateway是否开启了根据服务发现自动为服务创建router*/@Value("${spring.cloud.gateway.discovery.locator.enabled:false}")private boolean autoCreateRouter;@Value("${spring.application.name:}")private String applicationName;@Resourceprivate RouteLocator routeLocator;@Resourceprivate GatewayProperties gatewayProperties;/*** 根据当前所有的微服务路由信息,创建对应的SwaggerResource*/@Overridepublic List get() {List finalResources;Set routes = new LinkedHashSet<>(16);// 获取所有路由的idrouteLocator.getRoutes().subscribe(route -> {String routeId = route.getId();routeId = routeId.replace("ReactiveCompositeDiscoveryClient_", "");routes.add(routeId);});// 没有开启自动创建路由,那么走配置文件中配置的路由if (!autoCreateRouter) {finalResources = new ArrayList<>(16);gatewayProperties.getRoutes().stream()// 过滤出配置文件中定义的路由.filter(routeDefinition -> routes.contains(routeDefinition.getId())).forEach(route -> {route.getPredicates().stream()// 过滤出设置有Path Predicate的路由.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))// 根据路径拼接成api-docs路径,生成SwaggerResource.forEach(predicateDefinition -> finalResources.add(swaggerResource(route.getId(),predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0").replace("**", "v2/api-docs"))));});} else {finalResources = routes.stream().map(routeId -> swaggerResource(routeId, routeId + "/v2/api-docs")).collect(Collectors.toList());}List resources = new ArrayList<>(finalResources);// resources过滤掉网关的SwaggerResource, 我们一般也不会在网关中编写业务controllerif (StringUtils.isNotBlank(applicationName)) {resources = resources.stream().filter(x -> !applicationName.equalsIgnoreCase(x.getName())).collect(Collectors.toList());}// 排序resources.sort(Comparator.comparing(x -> x.getName().length()));return resources;}/*** 创建swagger资源** @param name* swagger资源名(注:一般对应 {路由id})* @param location* swagger资源路径(注:一般对应 {路由id}/v2/api-docs)* @return swager资源*/private SwaggerResource swaggerResource(String name, String location) {log.info("name:{},location:{}", name, location);SwaggerResource swaggerResource = new SwaggerResource();swaggerResource.setName(name);swaggerResource.setLocation(location);swaggerResource.setSwaggerVersion("2.0");return swaggerResource;}}
}
启动微服务后,访问{网关}/doc.html
完成验证