文档内容:
- 整合gateway网关
- 使用
Nacos
进行配置管理- 使用
Nacos
进行服务注册- 整合
springDoc
自动生成API文档
springboot升级为3.x时,swagger的迁移会出现一些问题,目前springboot3.x将 包javax下的所有内容都迁移到了jakarta下,比如HttpServletRequest, 而swagger还是使用的包javax, 导致出现不兼容的问题,因此可以使用springdoc来替代以前的swagger包。
注意:本次使用的SpringDoc版本为
2.X
对应的OpenAPI 3,如果使用1.x
对应OpenAPI 2版本springboot版本需要降级,两个版本所对应的依赖不同。官网文档:Springdoc-OpenAPI v2.0.4
代码地址:study-servies · java学习 - 码云 - 开源中国 (gitee.com)
- JDK 17+
- mysql 8.x
- Nacos 2.2.0+
- SpringBoot 3.x
- SpringCloud 2022.0.0
- SpringCloud Alibaba 2022.0.0.0-RC1
- SpringDoc-OpenApi v2.0.4
- knife4j 4.1.0
此处使用的
2.0.4
版本,可以在[maven][https://mvnrepository.com/]仓库查询最新版本,springboot3.x只能使用2.x以上版本,springboot2.x版本使用1.x版本,注意只需要加这个webmvc的依赖就可以了。
<dependency>
<groupId>org.springdocgroupId>
<artifactId>springdoc-openapi-starter-webmvc-uiartifactId>
<version>2.0.4version>
dependency>
下面只需要修改一下你对应的扫描包路径
springdoc.packages-to-scan: - com.hippo.liteflow.controller
,其他的可以不动,这里的springdoc.swagger-ui.path:
可以设置成自己喜欢的路径,最综接口文档访问地址就是https:host:ip/context-path/设置的路径
。
# swagger configuration
springdoc:
packages-to-scan: ##需要扫描的包,可以配置多个
- com.hippo.database.controller
paths-to-exclude: ##配置不包含在swagger文档中的api
- /api/test/**
- /api/mockito/data
swagger-ui:
enabled: true #开启/禁止swagger,prod可以设置为false
path: /swagger-ui.html #swagger页面
api-docs:
enabled: true #开启/禁止api-docs, prod可以设置为false
path: /v3/api-docs #api的json文档
version: openapi_3_0
use-management-port: false
### 设置为true时, management也需要设置
# use-management-port: true
# show-actuator: true
这里可以根据自定义设置,基本与之前其他版本的相同。
package com.hippo.database.config;
import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.media.StringSchema;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
/**
* @ClassName SpringDocConfiguration
* @Description TODO springdoc配置
* @Author tangxl
* @create 2023-03-28 11:32
**/
@Configuration
public class SpringDocConfiguration {
@Bean
public OpenAPI openAPI() {
return new OpenAPI()
.info(apiInfo())
.externalDocs(new ExternalDocumentation() // 外部文档
.description("SpringDoc Wiki Documentation")
.url("https://springdoc.org/v2"));
}
private Info apiInfo() {
return new Info()
.title("数据库操作")
.description("对不同数据操作进行操作")
.version("1.0.0")
.contact(new Contact()
.name("mongodb官方文档")
.url("https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#reference")
.email("[email protected]")
)
.license(new License()
.name("Apache 2.0")
.url("http://www.apache.org/licenses/LICENSE-2.0.txt")
);
}
}
新建CorsConfig.class
package com.hippo.database.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @ClassName CorsConfig
* @Description TODO 跨域配置
* @Author tangxl
* @create 2023-03-30 11:23
**/
@Configuration
public class CorsConfig implements WebMvcConfigurer {
/**
* 跨域配置
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// 允许跨域访问的路径
registry.addMapping("/**")
// 允许跨域访问的源
.allowedOriginPatterns("*")
// 允许请求方法
.allowedMethods("GET", "HEAD", "POST","PUT", "DELETE", "OPTIONS")
// 是否允许证书 不再默认开启
.allowCredentials(true)
// 允许前端获取的响应头
.maxAge(3600)
// 允许前端获取的响应头
.allowedHeaders("*");
}
}
package com.hippo.database.controller.springdoc;
import com.hippo.database.controller.springdoc.model.BpmnXmlReq;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
* @ClassName Demo2Controller
* @Description TODO springDoc演示例子2
* @Author tangxl
* @create 2023-03-28 18:37
**/
@RestController
@RequestMapping("body")
@Tag(name = "body参数")
public class Demo2Controller {
@Operation(summary = "普通body请求")
@PostMapping("/body")
public ResponseEntity<BpmnXmlReq> body(@RequestBody BpmnXmlReq fileResp){
return ResponseEntity.ok(fileResp);
}
@Operation(summary = "普通body请求+Param+Header+Path")
@Parameters({
@Parameter(name = "id",description = "文件id",in = ParameterIn.PATH),
@Parameter(name = "token",description = "请求token",required = true,in = ParameterIn.HEADER),
@Parameter(name = "name",description = "文件名称",required = true,in=ParameterIn.QUERY)
})
@PostMapping("/bodyParamHeaderPath/{id}")
public ResponseEntity<BpmnXmlReq> bodyParamHeaderPath(@PathVariable("id") String id, @RequestHeader("token") String token, @RequestParam("name")String name, @RequestBody BpmnXmlReq fileResp){
fileResp.setName(fileResp.getName()+",receiveName:"+name+",token:"+token+",pathID:"+id);
return ResponseEntity.ok(fileResp);
}
}
package com.hippo.database.controller.springdoc.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* @ClassName BpmnXmlReq
* @Description TODO 演示请求参数类
* @Author tangxl
* @create 2023-03-28 09:36
**/
@Data
// Schema 注解设置这个类的描述
@Schema(description = "bpmn xml 请求参数")
public class BpmnXmlReq {
// Schema 注解设置每个属性的描述和示例
@Schema(description = "名称",example = "请假流程名称")
private String name;
@Schema(description = "bpmn文件的内容, 字符串格式", example = "")
private String xml;
@Schema(description = "流程部署名称", example = "请假流程")
private String deployName;
}
默认访问地址:
https:host:ip/context-path/swagger-ui/index.html
配置路径:
https:host:ip/context-path/自己配置的地址
,上面配置的是swagger-ui.html
注意:
/context-path
是上下文地址,没有配置需要去掉
gateway网关使用的是
webflux
,这里需要引入springdoc-openapi-starter-webflux-ui
依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
<version>4.0.0version>
dependency>
<dependency>
<groupId>org.springdocgroupId>
<artifactId>springdoc-openapi-starter-webflux-uiartifactId>
<version>2.0.4version>
dependency>
这里在基础路由的基础上添加了一个转发路由
openai
,注意各服务的api文档json地址不要修改
,上面例子中写的默认api文档json地址,如果修改了需要修改转发路由的拦截filters中的地址。
spring:
cloud:
gateway:
# 全局跨域配置
globalcors:
add-to-simple-url-handler-mapping: true
cors-configurations:
'[/**]':
# 允许跨域的域名,可以用*表示允许任何域名使用
allowed-origin-patterns: '*'
#允许请求中携带的头信息
allowed-headers: '*'
# 允许请求的方法
allowed-methods: '*'
# 允许携带cookie
allow-credentials: true
# 允许暴露的头信息
exposed-headers: Content-Disposition,Content-Type,Cache-Control
# 预检请求的有效期
max-age: 36000
discovery:
locator:
# 是否可以通过其他服务的serviceId来转发到具体的服务实例。默认为false
# 为true,自动创建路由,路由访问方式:http://Gateway_HOST:Gateway_PORT/大写的serviceId/**,其中微服务应用名默认大写访问
# 让gateway通过服务发现组件找到其他的微服务,如果是true则自动,false才可以配置routes属性生效
enabled: true
# 服务名默认必须大写,否则会抛404错误,如果服务名要用小写,可在属性配置文件中添加spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true配置解决
lower-case-service-id: true
# 路由配置
routes:
# 资源路由:转发各个服务的 /v3/api-docs 请求
- id: openapi
uri: lb://${spring.application.name}
predicates:
- Path=/v3/api-docs/**
filters:
- RewritePath=/v3/api-docs/(?>.*), /$\{path}/v3/api-docs
# 路由id,自定义,只要唯一即可
- id: database-server
# 路由的目的地址,lb就是负载均衡,后面跟服务名称
uri: lb://database
# 路由断言,也就是判断请求是否符合路由规则的调价
predicates:
- Path=/database/**
#default-filters: #全局用于配置所有路由共享过滤器
# - StripPrefix=1 #去掉- Path=/auth 前缀
# - PreserveHostHeader #发送原主机头
filters:
- SpringDocHeaderFilter
- StripPrefix=1
# 路由id,自定义,只要唯一即可
- id: liteflow-server
# 路由的目的地址,lb就是负载均衡,后面跟服务名称
uri: lb://liteflow
# 路由断言,也就是判断请求是否符合路由规则的调价
predicates:
- Path=/liteflow/**
#default-filters: #全局用于配置所有路由共享过滤器
# - StripPrefix=1 #去掉- Path=/auth 前缀
# - PreserveHostHeader #发送原主机头
filters:
- RewritePath=/liteflow/(?>.*), /$\{path}
default-filters:
- AddRequestHeader=X-Request-red, green
# 添加名为origin的请求头,值为gateway
- AddRequestHeader=origin, gateway
# swagger configuration
springdoc:
swagger-ui:
# 禁止默认路径
disable-swagger-default-url: true
webjars:
# 设置为空,不要前缀,不配置这个就需要多加一层路径`/webjars`->`http:host:网关ip/webjars/swagger-ui/index.html`
prefix:
新建SpringDocConfiguration.class和InstancesChangeEventListener.class
这里可以采用的有两种方法,这里是从nacos服务中心动态获取所有服务来进行配置分组,你也手动添加配置分组
package com.hippo.gateway.config;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName SpringDocConfiguration
* @Description TODO swagger配置服务:读取gateway的路由配置,截取服务前缀
* @Author tangxl
* @create 2023-03-27 18:38
**/
@Configuration
public class SpringDocConfiguration {
@Value(value = "${server.port:9000}")
private int port;
@Bean
public OpenAPI springShopOpenApi() {
final String loginToken = "BearerAuth";
return new OpenAPI().info(new Info().title("API")
.description("gateway网关")
.version("v1.0.0")).externalDocs(new ExternalDocumentation()
.description("gateway网关")
.url("http://127.0.0.1:" + port))
.components(new Components().addSecuritySchemes(loginToken, new SecurityScheme()
.type(SecurityScheme.Type.HTTP).scheme("Bearer").bearerFormat("JWT")
.in(SecurityScheme.In.HEADER)
.name(loginToken)))
.addSecurityItem(new SecurityRequirement().addList(loginToken));
}
@Bean
public GroupedOpenApi service2Api() {
String[] paths = {"/_default/**"};
return GroupedOpenApi.builder().group("_default")
.pathsToMatch(paths)
.build();
}
}
package com.hippo.gateway.listener;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.client.naming.event.InstancesChangeEvent;
import com.alibaba.nacos.common.notify.Event;
import com.alibaba.nacos.common.notify.NotifyCenter;
import com.alibaba.nacos.common.notify.listener.Subscriber;
import com.alibaba.nacos.common.utils.CollectionUtils;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springdoc.core.properties.AbstractSwaggerUiConfigProperties;
import org.springdoc.core.properties.SwaggerUiConfigProperties;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.route.CachingRouteLocator;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.stereotype.Component;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* @ClassName InstancesChangeEventListener
* @Description TODO 动态刷新Swagger下拉
* @Author tangxl
* @create 2023-03-30 10:20
**/
@Component
public class InstancesChangeEventListener extends Subscriber<InstancesChangeEvent> implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(InstancesChangeEventListener.class);
@Autowired
private RouteLocator routeLocator;
@Autowired
private SwaggerUiConfigProperties swaggerUiConfigProperties;
@Value("${spring.application.name}")
private String applicationName;
private Set<AbstractSwaggerUiConfigProperties.SwaggerUrl> urls;
@PostConstruct
private void post() {
NotifyCenter.registerSubscriber(this);
}
/**
* Event callback.
*
* @param event {@link Event}
*/
@Override
public void onEvent(InstancesChangeEvent event) {
logger.info("接收到 InstancesChangeEvent 订阅事件:{}", JSON.toJSONString(event));
CachingRouteLocator cachingRouteLocator = (CachingRouteLocator) routeLocator;
cachingRouteLocator.refresh();
List<Route> routeList = routeLocator.getRoutes().collectList().block();
System.out.println(routeList);
LinkedHashSet<AbstractSwaggerUiConfigProperties.SwaggerUrl> set = new LinkedHashSet<>();
if (CollectionUtils.isNotEmpty(this.urls)) {
set.addAll(this.urls);
}
for (Route route : routeList) {
if (route.getMetadata().size() > 0) {
// ReactiveCompositeDiscoveryClient_sky-user
String id = route.getId();
String name = id.replaceAll("ReactiveCompositeDiscoveryClient_", "");
if (applicationName.equals(name)) {
continue;
}
set.add(new AbstractSwaggerUiConfigProperties.SwaggerUrl(name, "/"+name+"/v3/api-docs", ""));
}
}
if (CollectionUtils.isEmpty(set)) {
set.add(new AbstractSwaggerUiConfigProperties.SwaggerUrl("_default", "/v3/api-docs/_default", ""));
}
swaggerUiConfigProperties.setUrls(set);
}
/**
* Type of this subscriber's subscription.
*
* @return Class which extends {@link Event}
*/
@Override
public Class<? extends com.alibaba.nacos.common.notify.Event> subscribeType() {
return InstancesChangeEvent.class;
}
@Override
public void afterPropertiesSet() throws Exception {
this.urls = this.swaggerUiConfigProperties.getUrls();
}
}
默认访问地址:
http:host:网关ip/webjars/swagger-ui/index.html
这里去掉了
/webjar
路径:http:host:网关ip/swagger-ui/index.html
这个界面不太好看可以使用knife4j
增强,目前knife4j v4.x版本也已经集成了springdoc,支持springboot3.x版本
官网地址:升级到v4.0.0版本 | Knife4j (xiaominfo.com)
官方案例:knife4j-openapi3-gateway · 萧明/swagger-bootstrap-ui-demo - 码云 - 开源中国 (gitee.com)
添加
xml
依赖,去掉原来添加的springdoc-openapi-starter-webmvc-ui
,注意版本不要冲突<dependency> <groupId>com.github.xiaoymingroupId> <artifactId>knife4j-openapi3-jakarta-spring-boot-starterartifactId> <version>4.1.0version> dependency>
添加
yaml
配置文件# knife4j的增强配置,不需要增强可以不配 knife4j: enable: true setting: language: zh_cn
添加依赖,原springDoc依赖不要去掉
<dependency> <groupId>com.github.xiaoymingroupId> <artifactId>knife4j-gateway-spring-boot-starterartifactId> <version>4.1.0version> dependency>
添加
yaml
配置文件# knife4j的增强配置,不需要增强可以不配 knife4j: # 聚合swagger文档 gateway: enable: true routes: - name: database url: /database/v3/api-docs?group=default service-name: user-service order: 2 - name: liteflow url: /liteflow/v2/api-docs?group=default service-name: order-service order: 3
默认访问地址:
http:host:网关ip/doc.html
目前这里使用最新的网关还有一点问题,单服务访问没有问题,等待后期网关增强swagger解决了更新。