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);}}
}

相关内容

热门资讯

常用商务英语口语   商务英语是以适应职场生活的语言要求为目的,内容涉及到商务活动的方方面面。下面是小编收集的常用商务...
六年级上册英语第一单元练习题   一、根据要求写单词。  1.dry(反义词)__________________  2.writ...
复活节英文怎么说 复活节英文怎么说?复活节的英语翻译是什么?复活节:Easter;"Easter,anniversar...
2008年北京奥运会主题曲 2008年北京奥运会(第29届夏季奥林匹克运动会),2008年8月8日到2008年8月24日在中华人...
英语道歉信 英语道歉信15篇  在日常生活中,道歉信的使用频率越来越高,通过道歉信,我们可以更好地解释事情发生的...
六年级英语专题训练(连词成句... 六年级英语专题训练(连词成句30题)  1. have,playhouse,many,I,toy,i...
上班迟到情况说明英语   每个人都或多或少的迟到过那么几次,因为各种原因,可能生病,可能因为交通堵车,可能是因为天气冷,有...
小学英语教学论文 小学英语教学论文范文  引导语:英语教育一直都是每个家长所器重的,那么有关小学英语教学论文要怎么写呢...
英语口语学习必看的方法技巧 英语口语学习必看的方法技巧如何才能说流利的英语? 说外语时,我们主要应做到四件事:理解、回答、提问、...
四级英语作文选:Birth ... 四级英语作文范文选:Birth controlSince the Chinese Governmen...
金融专业英语面试自我介绍 金融专业英语面试自我介绍3篇  金融专业的学生面试时,面试官要求用英语做自我介绍该怎么说。下面是小编...
我的李老师走了四年级英语日记... 我的李老师走了四年级英语日记带翻译  我上了五个学期的小学却换了六任老师,李老师是带我们班最长的语文...
小学三年级英语日记带翻译捡玉... 小学三年级英语日记带翻译捡玉米  今天,我和妈妈去外婆家,外婆家有刚剥的`玉米棒上带有玉米籽,好大的...
七年级英语优秀教学设计 七年级英语优秀教学设计  作为一位兢兢业业的人民教师,常常要写一份优秀的教学设计,教学设计是把教学原...
我的英语老师作文 我的英语老师作文(通用21篇)  在日常生活或是工作学习中,大家都有写作文的经历,对作文很是熟悉吧,...
英语老师教学经验总结 英语老师教学经验总结(通用19篇)  总结是指社会团体、企业单位和个人对某一阶段的学习、工作或其完成...
初一英语暑假作业答案 初一英语暑假作业答案  英语练习一(基础训练)第一题1.D2.H3.E4.F5.I6.A7.J8.C...
大学生的英语演讲稿 大学生的英语演讲稿范文(精选10篇)  使用正确的写作思路书写演讲稿会更加事半功倍。在现实社会中,越...
VOA美国之音英语学习网址 VOA美国之音英语学习推荐网址 美国之音网站已经成为语言学习最重要的资源站点,在互联网上还有若干网站...
商务英语期末试卷 Part I Term Translation (20%)Section A: Translate ...