SpringCloud整合Knife4j实现接口文档
创始人
2024-05-20 00:13:58
0

SpringBoot可以通过整合knife4j来实现在线接口文档功能,但在微服务环境下,每个服务的接口文档访问地址都不相同,访问起来十分麻烦,因此我们可以在gateway成对各个微服务的接口文档进行整合,实现访问网关即可任意切换查看各个微服务的接口文档。

一、微服务整合knife4j接口文档

1. 引入依赖

org.springframework.bootspring-boot-starter-parent2.6.11 
org.springframework.bootspring-boot-startercom.github.xiaoyminknife4j-spring-boot-starter3.0.3org.springframework.bootspring-boot-starter-webtruecom.alibaba.cloudspring-cloud-starter-alibaba-nacos-discoverycom.alibaba.cloudspring-cloud-starter-alibaba-nacos-configorg.springframework.cloudspring-cloud-starter-loadbalancer

2. 编写配置类

@ConfigurationProperties(prefix = "swagger.doc")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SwaggerProperties {private boolean enable = true;/*** 作者*/private String author;/*** 标题*/private String title;/*** 项目描述*/private String description;/*** 官网地址*/private String url;/*** 邮箱地址*/private String email;
}

3. 编写配置类配置knife4j

@ConditionalOnClass(EnableSwagger2.class)
@Configuration
@EnableSwagger2
@EnableKnife4j
@Import(BeanValidatorPluginsConfiguration.class)
@EnableConfigurationProperties(SwaggerProperties.class)
public class Knife4jConfiguration implements WebMvcConfigurer {@Autowiredprivate SwaggerProperties swaggerProperties;@Autowiredprivate ApplicationInfo applicationInfo;@Beanpublic static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {return new BeanPostProcessor() {@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {//spring boot 2.6以上版本使用 PATH_PATTERN_PARSER,swagger并没有兼容,所以替换为 ANT_PATH_MATCHERif(bean instanceof WebMvcProperties){WebMvcProperties pathmatch = (WebMvcProperties)bean;pathmatch.getPathmatch().setMatchingStrategy(WebMvcProperties.MatchingStrategy.ANT_PATH_MATCHER);}if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {customizeSpringfoxHandlerMappings(getHandlerMappings(bean));}return bean;}private  void customizeSpringfoxHandlerMappings(List mappings) {List copy = mappings.stream().filter(mapping -> mapping.getPatternParser() == null).collect(Collectors.toList());mappings.clear();mappings.addAll(copy);}@SuppressWarnings("unchecked")private List getHandlerMappings(Object bean) {try {Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");field.setAccessible(true);return (List) field.get(bean);} catch (IllegalArgumentException | IllegalAccessException e) {throw new IllegalStateException(e);}}};}@Beanpublic Docket createRestApi() {Docket docket = new Docket(DocumentationType.OAS_30).pathMapping("/").groupName(SpringUtil.getApplicationName())// 定义是否开启swagger,false为关闭,可以通过变量控制.enable(true)// 将api的元信息设置为包含在json ResourceListing响应中。.apiInfo(apiInfo())// 选择哪些接口作为swagger的doc发布.select()//指定某个路径才能生成swagger.paths(PathSelectors.any()).paths(s -> !PathSelectors.regex("/error/*").test(s)).build()//是否启用.enable(swaggerProperties.isEnable())// 支持的通讯协议集合.protocols(new HashSet<>(Arrays.asList("https", "http")));return docket;}/*** API 页面上半部分展示信息*/private ApiInfo apiInfo() {return new ApiInfoBuilder().title(swaggerProperties.getTitle()).description(swaggerProperties.getDescription()).contact(new Contact(swaggerProperties.getAuthor(), swaggerProperties.getUrl(), swaggerProperties.getEmail())).version(applicationInfo.getVersion()).build();}
}

4. 编写ResponseBodyAdvice拦截swagger接口的请求

编写ResponseBodyAdvice拦截swagger接口的请求,当通过网关访问swagger接口文档时需要拼接微服务访问前缀,否则网关的web页面访问会404

@Slf4j
@RestControllerAdvice
@ConditionalOnClass({ResponseBodyAdvice.class, EnableSwagger2.class})
public class SwaggerResponseBodyAdvice implements ResponseBodyAdvice {@Autowiredprivate ApplicationInfo applicationInfo;@Overridepublic boolean supports(MethodParameter methodParameter, Class converterType) {if(!(RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes)){return false;}ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();HttpServletRequest request = requestAttributes.getRequest();String requestURI = request.getRequestURI();//拦截 /v3/api-docs 并且参数带有prefix的(网关请求有配置带上prefix参数)return requestURI.equals(StrUtil.replace(applicationInfo.getWebContextPath() + "/v3/api-docs","//","/")) &&StrUtil.isNotBlank(request.getParameter("prefix"));}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {//获取请求的前缀ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();String prefix = StrUtil.addPrefixIfNot(requestAttributes.getRequest().getParameter("prefix"),"/");//重新生成一个paths,拼接上参数带的前缀Paths newPaths = new Paths();OpenAPI openAPI = JSONUtil.parseObj(((Json) body).value()).toBean(OpenAPI.class);openAPI.getPaths().forEach((path, pathObj)->{newPaths.put(prefix+path,pathObj);});openAPI.setPaths(newPaths);return JSONUtil.toJsonStr(openAPI);}
}

二、网关整合各微服务接口文档

1. 引入依赖

org.springframework.bootspring-boot-starter-parent2.6.11
org.springframework.bootspring-boot-startercom.github.xiaoyminknife4j-spring-boot-starter3.0.3com.alibaba.cloudspring-cloud-starter-alibaba-nacos-discoverycom.alibaba.cloudspring-cloud-starter-alibaba-nacos-configorg.springframework.cloudspring-cloud-starter-gatewayorg.springframework.cloudspring-cloud-starter-loadbalancer

2. 编写配置类配置Knife4j

SwaggerResourceConfig

@Primary
@Configuration
public class SwaggerResourceConfig implements SwaggerResourcesProvider {@Autowiredprivate RouteLocator routeLocator;// 网关应用名称@Value("${spring.application.name}")private String applicationName;//接口地址private static final String API_URI = "/v3/api-docs";@Overridepublic List get() {//接口资源列表List resources = new ArrayList<>();//服务名称列表List routeHosts = new ArrayList<>();// 获取所有可用的微服务routeLocator.getRoutes().filter(route -> route.getUri().getHost() != null).filter(route -> !applicationName.equals(route.getUri().getHost())).subscribe(route -> {routeHosts.add(route.getUri().getHost());});// 去重,多负载服务只添加一次Set existsServer = new HashSet<>();routeHosts.forEach(host -> {// 拼接url 拼接前缀String url = "/" + host + API_URI+"?prefix="+host+"&group="+host;//不存在则添加if (!existsServer.contains(url)) {existsServer.add(url);SwaggerResource swaggerResource = new SwaggerResource();swaggerResource.setUrl(url);swaggerResource.setName(host);resources.add(swaggerResource);}});return resources;}
}

SwaggerHeaderFilter

@Configuration
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {private static final String HEADER_NAME = "X-Forwarded-Prefix";private static final String URI = "/v3/api-docs";@Overridepublic GatewayFilter apply(Object config) {return (exchange, chain) -> {ServerHttpRequest request = exchange.getRequest();String path = request.getURI().getPath();if(StringUtils.endsWithIgnoreCase(path, URI)) {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);}else {return chain.filter(exchange);}};}
}

SwaggerHandler

@RestController
@RequestMapping("/swagger-resources")
public class SwaggerHandler {@Autowired(required = false)private SecurityConfiguration securityConfiguration;@Autowired(required = false)private UiConfiguration uiConfiguration;private final SwaggerResourcesProvider swaggerResources;@Autowiredpublic SwaggerHandler(SwaggerResourcesProvider swaggerResources) {this.swaggerResources = swaggerResources;}@GetMapping("/configuration/security")public Mono> securityConfiguration() {return Mono.just(new ResponseEntity<>(Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));}@GetMapping ("/configuration/ui")public Mono> uiConfiguration() {return Mono.just(new ResponseEntity<>(Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));}@GetMapping()public Mono swaggerResources() {return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));}}

3. 为每个微服务自动配置一个以微服务名开头的路由配置

整合knife4j后通过网关访问某个微服务接口时前缀是以服务名开头的,因此我们需要配置路由规则,否则knife4j的接口访问会404。

SpringCloud Gateway 其实已经帮我们实现了此功能,只需要在boostrap.properties内配置即可

# enabled:默认为false,设置为true表明spring cloud gateway开启服务发现和路由的功能,网关自动根据注册中心的服务名为每个服务创建一个router,将以服务名开头的请求路径转发到对应的服务
spring.cloud.gateway.discovery.locator.enabled=true
# lowerCaseServiceId:启动 locator.enabled=true 自动路由时,路由的路径默认会使用大写ID,若想要使用小写ID,可将lowerCaseServiceId设置为true
spring.cloud.gateway.discovery.locator.lower-case-service-id=true

以下配置是通过配置类的方式实现的,如果配置文件配置方式能够生效可以忽略(本项目内实现了写自定义的配置导致配置文件方式失效,所以才通过配置类方式实现)

@Slf4j
@Configuration
@EnableScheduling
public class ApplicationRouteConfiguration  {@Autowiredprivate ApplicationEventPublisher applicationEventPublisher;@Autowiredprivate RouteDefinitionWriter routeDefinitionWriter;@Autowiredprivate ReactiveDiscoveryClient discoveryClient;@Autowiredprivate DiscoveryLocatorProperties discoveryLocatorProperties;/*** 用于存放已经加载的路由配置* */private ConcurrentHashSet routeSet = new ConcurrentHashSet<>(16);@PostConstructpublic void postRegisterRoute(){registerRoute();}/*** 每5秒刷新下路由配置,保证新注册的微服务也能够被配置*/@Scheduled(cron = "0/5 * * * * ?")public void refreshRoute(){registerRoute();}public void registerRoute(){//由于网关配置了动态路由刷新,和springcloud提供的配置 spring.cloud.gateway.discovery.locator.enabled=true 冲突//所以我们需要手动注册路由规则DiscoveryLocatorProperties properties = BeanUtil.copyProperties(discoveryLocatorProperties, DiscoveryLocatorProperties.class);properties.setEnabled(true);//通过DiscoveryClientRouteDefinitionLocator生成规则DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator = new DiscoveryClientRouteDefinitionLocator(discoveryClient,properties);//注册规则Flux routeDefinitions = discoveryClientRouteDefinitionLocator.getRouteDefinitions();AtomicInteger addCount = new AtomicInteger(0);//去重循环添加routeDefinitions.filter( routeDefinition -> !routeSet.contains(routeDefinition.getPredicates().get(0).getArgs().get("pattern"))).subscribe( routeDefinition -> {//放入set,防止重复添加routeSet.add(routeDefinition.getPredicates().get(0).getArgs().get("pattern"));routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();addCount.incrementAndGet();log.info("新增路由规则=>{}",routeDefinition);});if(addCount.get()>0){applicationEventPublisher.publishEvent(routeDefinitionWriter);}}
}

相关内容

热门资讯

指南针作文 指南针作文(精选60篇)  在我们平凡的日常里,大家都尝试过写作文吧,通过作文可以把我们那些零零散散...
心理素质的作文 关于心理素质的作文(通用49篇)  无论是在学校还是在社会中,大家总少不了接触作文吧,作文是从内部言...
改写《石壕吏》作文800字 改写《石壕吏》作文800字(精选21篇)  在平平淡淡的日常中,许多人都写过作文吧,作文要求篇章结构...
颜色的争执作文 颜色的争执作文一天,颜色们要挑选颜色之王。红色先发话了:“在红黄蓝橙绿紫里面,我最鲜艳,请大家投我的...
举手投足之间作文600字 举手投足之间作文600字  在平日的学习、工作和生活里,大家对作文都不陌生吧,作文是从内部言语向外部...
初中范文带标题的作文优选29... 初中范文带标题的作文 第一篇在我的印象里,母亲是再*凡不过的,她是在不美,而且习惯沉默,像一粒最微小...
风筝与线的作文 关于风筝与线的作文  篇一:关于风筝与线的作文  你放过风筝吗?  是的,在刮大风的时候,风筝才飞得...
不知好歹的狼作文 不知好歹的狼作文星期一的上午,天气晴朗。小象和爸爸、妈妈在森林里玩,他看见一个乱七八糟的狼窝。他想:...
描写我爱秋天的优秀作文 描写我爱秋天的优秀作文(精选10篇)  在日复一日的学习、工作或生活中,大家最不陌生的就是作文了吧,...
贪吃鬼作文 贪吃鬼作文  无论在学习、工作或是生活中,大家都跟作文打过交道吧,写作文可以锻炼我们的独处习惯,让自...
作文 台风来了 作文 台风来了作文 台风来了1  今天上午,“韦森特”台风袭击了中山。  可怕的台风刮过来,伞都吹翻...
丑小鸭历险记作文600字 丑小鸭历险记作文600字  在平平淡淡的日常中,大家总少不了接触作文吧,根据写作命题的特点,作文可以...
可爱的蝴蝶花作文 可爱的蝴蝶花作文 蝴蝶花是人们喜爱的一种花朵,它虽然终身只开一朵花,可它的奇异的外形吸引了很多人。·...
网作文 网作文如今的'网多了,人们编织网,利用网,却时时也被“网”困惑,下面就让我来介绍吧!“网”是我们常见...
做友善感恩的人作文优选24篇 做友善感恩的人作文 第一篇诚实守信*是我国的传统美德,它阐述了两种品质一是诚信二是善,这两种品质缺一...
沙家浜游记 沙家浜游记沙家浜游记1沙家浜游记正文: 沙家浜游记 上海市嘉定 嘉定区封浜中心校 一(1)班 张...
云朵作文400字 云朵作文400字(通用57篇)  在生活、工作和学习中,大家都不可避免地会接触到作文吧,作文是通过文...
月亮和星星作文100字 月亮和星星作文100字月亮和星星作文100字晚上月亮妈妈在银河里划船划呀划,划呀划许是累了停泊在银河...
初中毕业作文 关于初中毕业作文3篇  在日常生活或是工作学习中,大家都经常接触到作文吧,作文根据写作时限的不同可以...
我战胜了胆怯作文 我战胜了胆怯作文(精选5篇)  在日常的学习、工作、生活中,许多人都写过作文吧,作文是从内部言语向外...