Spring Cloud集成Knife4j管理接口文档

共 21206字,需浏览 43分钟

 ·

2023-06-24 09:44


 Knife4j前身是swagger-bootstrap-ui,取名knife4j是希望她能像一把匕首一样小巧,轻量,并且功能强悍,更名也是希望把她做成一个为Swagger接口文档服务的通用性解决方案,不仅仅只是专注于前端Ui前端.虽然目前还只是在前端,但以后功能肯定不止于此。


2.0版本主要是使用Vue+Ant Design对前端Ui进行重写,该版本是真正的前后端分离版本,同时依赖于Vue的技术生态,以后会有更多有趣的功能实现,全方位满足开发者的需要。




Knife4j简介




核心功能


该UI增强包主要包括两大核心功能:文档说明在线调试


  • 文档说明

    :根据Swagger的规范说明,详细列出接口文档的说明,包括接口地址、类型、请求示例、请求参数、响应示例、响应参数、响应码等信息,使用

    Knife4j

    能能根据该文档说明,对该接口的使用情况一目了然。




  • 在线调试

    :提供在线接口联调的强大功能,自动解析当前接口参数,同时包含表单验证,调用参数可返回接口响应内容、headers、Curl请求命令实例、响应时间、响应状态码等信息,帮助开发者在线调试,而不必通过其他测试工具测试接口是否正确,简介、强大。





UI增强



Knife4j

在满足以上功能的同时,还提供了文档的增强功能,这些功能是官方

swagger-ui

所没有的,每一个增强的功能都是贴合实际,考虑到开发者的实际开发需要,是必不可少的功能,主要包括:



  • 个性化配置

    :通过个性化ui配置项,可自定义UI的相关显示信息



  • 离线文档:根据标准规范,生成的在线

    markdown

    离线文档,开发者可以进行拷贝生成

    markdown

    接口文档,通过其他第三方

    markdown

    转换工具转换成

    html



    pdf

    ,这样也可以放弃

    swagger2markdown

    组件





  • 接口排序

    :自1.8.5后,ui支持了接口排序功能,例如一个注册功能主要包含了多个步骤,可以根据


    Knife4j

    提供的接口排序规则实现接口的排序,step化接口操作,方便其他开发者进行接口对接





UI特点






  • markdown

    形式展示文档,将文档的请求地址、类型、请求参数、示例、响应参数分层次依次展示,接口文档一目了然,方便开发者对接





  • 在线调试栏除了自动解析参数外,针对必填项着颜色区分,同时支持tab键快速输入上下切换.调试时可自定义Content-Type请求头类型





  • 个性化配置项,支持接口地址、接口description属性、UI增强等个性化配置功能





  • 接口排序,支持分组及接口的排序功能





  • 支持

    markdown

    文档离线文档导出,也可在线查看离线文档





  • 调试信息全局缓存,页面刷新后依然存在,方便开发者调试





  • 以更人性化的treetable组件展示Swagger Models功能





  • 响应内容可全屏查看,针对响应内容很多的情况下,全屏查看,方便调试、复制





  • 文档以多tab方式可显示多个接口文档





  • 请求参数栏请求类型、是否必填着颜色区分





  • 主页中粗略统计接口不同类型数量





  • 支持接口在线搜索功能





  • 左右菜单和内容页可自由拖动宽度





  • 支持自定义全局参数功能,主页包括header及query两种类型





  • i18n国际化支持,目前支持:中文简体、中文繁体、英文





  • JSR-303 annotations 注解的支持



Knife4j集成

对于Spring Cloud微服务集成,有2种不同的配置:网关微服务




Spring Cloud Gateway网关


我们的网关做了文档聚合的作用,也就是将所有微服务文档聚合到网关提供文档统一入口,当然你也可以单独做一个“文档聚合”服务😀



第一步:pom引入相关jar包





<dependency>



<groupId>com.github.xiaoymingroupId>


<artifactId>knife4j-spring-boot-starterartifactId>


<version>${knife4j.version}version>



dependency>





${knife4j.version}取最新的版本,我们使用的是2.0.2


需要注意的是knife4j需要依赖lombok,如果没有使用的话请增加下面的配置





<dependency>



<groupId>org.projectlombokgroupId>


<artifactId>lombokartifactId>


<version>1.18.4version>


<scope>providedscope>



dependency>






第二步:


文档聚合业务编码


在我们使用Spring Boot等单体架构集成swagger项目时,是通过对包路径进行业务分组,然后在前端进行不同模块的展示,而在微服务架构下,我们的一个服务就类似于原来我们写的一个业务分组

springfox-swagger

提供的分组接口是

swagger-resource

,返回的是分组接口名称、地址等信息





在Spring Cloud微服务架构下,我们需要重写该接口,主要是通过网关的注册中心动态发现所有的微服务文档,代码如下:




@Slf4j



@Component




@Primary




@AllArgsConstructor



public class SwaggerResourceConfig implements SwaggerResourcesProvider {







private final RouteLocator routeLocator;


private final GatewayProperties gatewayProperties;












@Override


public List get() {


List resources = new ArrayList<>();


List 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) {


log.info("name:{},location:{}",name,location);


SwaggerResource swaggerResource = new SwaggerResource();


swaggerResource.setName(name);


swaggerResource.setLocation(location);


swaggerResource.setSwaggerVersion("2.0");


return swaggerResource;


}


}



上面代码为Knife4j官方提供,由于我们是使用了
k8s

APIServer+ETCD
做的服务注册中心并且支持了
ConfigMap
动态配置,必须做如下代码改造下:





/**




* @author James Tang




* @date Created in 2020/3/18 19:48




*/



@Slf4j


@Component


@Primary


@AllArgsConstructor


public class SwaggerResourceConfig implements SwaggerResourcesProvider {








// private final RouteLocator routeLocator;




// private final GatewayProperties gatewayProperties;



private final InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository;







private final String API_DOCS_ROUTE_ID_POSTFIX = "apidocs";







@Override


public List<SwaggerResource> get() {


List<SwaggerResource> resources = new ArrayList<>();



// List routes = new ArrayList<>();




// routeLocator.getRoutes().subscribe(route -> {




// log.info(route.toString());




// routes.add(route.getId());




// });








Set<String> serviceGroups = Sets.newHashSet();


inMemoryRouteDefinitionRepository.getRouteDefinitions().subscribe(routeDefinition -> {


log.info(routeDefinition.toString());


if (routeDefinition.getId().endsWith(API_DOCS_ROUTE_ID_POSTFIX)) {


String[] tmpSplits = routeDefinition.getId().split("_");


if (tmpSplits.length > 1) {


String groupName = tmpSplits[1];


if (!serviceGroups.contains(groupName)) {


routeDefinition.getPredicates().stream()


.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName())).findFirst().ifPresent(predicateDefinition -> {


String routePath = predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0");


String groupPath = routePath.substring(1).split("/")[0];


resources.add(swaggerResource(groupName, String.format("/%s/v2/api-docs?group=%s", groupPath, groupName)));


});


serviceGroups.add(groupName);


}


}


}


});








// 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) {


log.info("name:{},location:{}", name, location);


SwaggerResource swaggerResource = new SwaggerResource();


swaggerResource.setName(name);


swaggerResource.setLocation(location);


swaggerResource.setSwaggerVersion("2.0");


return swaggerResource;


}


}



文档聚合相关接口:





@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> securityConfiguration() {


return Mono.just(new ResponseEntity<>(


Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));


}







@GetMapping("/swagger-resources/configuration/ui")


public Mono> uiConfiguration() {


return Mono.just(new ResponseEntity<>(


Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));


}







@GetMapping("/swagger-resources")


public Mono swaggerResources() {


return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));


}


}







微服务接口


下面我们看下消息服务做了什么配置?



第一步:pom引入相关jar包





<dependency>



<groupId>org.projectlombokgroupId>


<artifactId>lombokartifactId>


<version>1.18.4version>


<scope>providedscope>



dependency>










<dependency>



<groupId>io.springfoxgroupId>


<artifactId>springfox-swagger2artifactId>


<version>2.8.0version>



dependency>





<dependency>



<groupId>io.springfoxgroupId>


<artifactId>springfox-swagger-uiartifactId>


<version>2.8.0version>



dependency>





<dependency>



<groupId>io.springfoxgroupId>


<artifactId>springfox-bean-validatorsartifactId>


<version>2.8.0version>



dependency>










<dependency>



<groupId>com.github.xiaoymingroupId>


<artifactId>knife4j-micro-spring-boot-starterartifactId>


<version>${knife4j.version}version>



dependency>




${knife4j.version}取最新的版本,我们使用的是2.0.2





knife4j-micro-spring-boot-starter在服务内不提供文档入口,如果需要在消息服务直接能显示文档,请替换为knife4j-spring-boot-starter




第二步


:增加@EnableKnife4j注解




/**


* @author James


* @date 19/4/2


*/


@Configuration


@EnableKnife4j


@EnableSwagger2


@Import(BeanValidatorPluginsConfiguration.class)


public class SwaggerConfig {







...







}


好了,大功告成!


bf9e3c689bcb18a2d29eb5d8fe27b0cd.webp



注意点


在集成Spring Cloud Gateway网关的时候,会出现没有basePath的情况(即定义的例如/user、/order等微服务的前缀),这个情况在使用zuul网关的时候不会出现此问题。因此,在Gateway网关需要添加一个Filter实体Bean,代码如下:





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


};


}


}


然后在配置文件指定这个filter




spring:


application:


name: service-doc


cloud:


gateway:


routes:


- id: service-user


uri: lb://service-user


predicates:


- Path=/user/**



# - Header=Cookie,Set-Cookie



filters:


- SwaggerHeaderFilter


- StripPrefix=1


- id: service-order


uri: lb://service-order


predicates:


- Path=/order/**


filters:


- SwaggerHeaderFilter //指定filter


- StripPrefix=1


这个“注意点”是官方提供的,我们并没有遇到这个问题,也没加这个配置,写在这里希望遇到此类问题的人可以快速集成


其实Swagger2文档其实不止一种UI效果,在没有接触Knife4j之前,大家一般会使用springfox的几个库,下面是使用springfox-swagger-ui的效果图,其实还是挺不错的



11fdd046aa6b8396b8ef9c5058493460.webp


当然Knife4jspringfox-swagger-ui并不排他,如果都喜欢的话可以一起使用



Springfox-Swagger



关于
SpringfoxSwagger
详细使用,这里不过多叙述,可自行通过下面地址查阅





  • GitHub


    https://gith
    ub.com/springfox/springfox






  • 文档

    :http://springfox.io




下面我们重点讲解SpringfoxSwagger提供的与
K
ni
fe4j
有关的2个接口,
K
ni
fe4j
包会根据下面2个接口来动态生成文档




  • 分组接口:
    /swagg
    er-resources





  • 详情实例接口:
    /v2/api-docs




Swagger分组



Swagger的分组接口是用后端配置不同的扫描包,将后端的接口,按配置的扫描包基础属性响应给前端,看看分组接口响应的json内容:




[


{


"name": "分组接口",


"url": "/v2/api-docs?group=分组接口",


"swaggerVersion": "2.0",


"location": "/v2/api-docs?group=分组接口"


},


{


"name": "默认接口",


"url": "/v2/api-docs?group=默认接口",


"swaggerVersion": "2.0",


"location": "/v2/api-docs?group=默认接口"


}


]




在Springfox-Swagger有些较低的版本中,并没有location属性,高版本会有该属性



c2e929621eeaf2ba07446cfe58ea24d8.webp



分组的后端Java配置代码如下:




@Bean(value = "defaultApi")


public Docket defaultApi() {


ParameterBuilder parameterBuilder=new ParameterBuilder();


List parameters= Lists.newArrayList();


parameterBuilder.name("token").description("token令牌").modelRef(new ModelRef("String"))


.parameterType("header").defaultValue("abc")


.required(true).build();


parameters.add(parameterBuilder.build());







return new Docket(DocumentationType.SWAGGER_2)


.apiInfo(apiInfo())


.groupName("默认接口")


.select()


.apis(RequestHandlerSelectors.basePackage("com.swagger.bootstrap.ui.demo.controller"))


.paths(PathSelectors.any())


.build().globalOperationParameters(parameters)


.securityContexts(Lists.newArrayList(securityContext(),securityContext1())).securitySchemes(Lists.newArrayList(apiKey(),apiKey1()));


}


@Bean(value = "groupRestApi")


public Docket groupRestApi() {


return new Docket(DocumentationType.SWAGGER_2)


.apiInfo(groupApiInfo())


.groupName("分组接口")


.select()


.apis(RequestHandlerSelectors.basePackage("com.swagger.bootstrap.ui.demo.group"))


.paths(PathSelectors.any())


.build().securityContexts(Lists.newArrayList(securityContext(),securityContext1())).securitySchemes(Lists.newArrayList(apiKey(),apiKey1()));


}



详情实例接口


详情实例接口是根据分组名称动态获取该组下配置的basePackage所有的接口描述信息,如下图所示:


d3ebbcfe1fb5c4302ef0d3cf57772407.webp


理解这2个接口,就理解
Knife4j
是怎么回事了😀
大家最近安好,中国加油💪
浏览 28
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报