写在前面
未经允许不得转载!
如果你有现在使用
Springfox
的经历的话,可能就有对 api 进行分组以及排序等需求,而
Springfox
(应当说是
Swagger
更确切)默认是根据自然排序对 api 进行排序的,比如:”/aaa1″会排在”/aaa2″前面,会排在”/bbb1″前面。
当需要自定义排序规则时怎么办?
-
那就是使用
ApiOperation
注解的
position
方法,但是很不幸的是该方法已经标记为过时了,现在保留这个方法只是为了兼容之前的版本,且高版本的已经一移除该功能了(包括后端和前端UI)。
那有其他办法实现自定义排序吗?
- 自己花时间找文档及api看看有没有类似功能
-
按照我的配置方式实现临时用的拓展解决方案(当然只是临时的 毕竟
position
已经过时了,之后的版本可能就移除了)
相关链接
临时用的拓展解决方案
第1步:在需要的位置添加
position
position
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Yahuan Jin
* @since 2019.08.24
*/
@Api(tags = "test", description = "test")
@RequestMapping("test/apiOperation")
@RestController
public class ApiOperationTestController {
@ApiOperation(value = "Test position 2", tags = {"position-test"}, position = 2)
@PostMapping(value = "aaa")
public String testPositionAaa() {
return "aaa";
}
@ApiOperation(value = "Test position 1", tags = {"position-test"}, position = 1)
@RequestMapping(value = "bbb")
public String testPositionBbb() {
return "bbb";
}
}
第2步:完成对
position
的支持
position
拓展
springfox.documentation.spring.web.readers.operation.ApiOperationReade
类。
/*
* Copyright 2015-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.google.common.base.Optional;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import springfox.documentation.OperationNameGenerator;
import springfox.documentation.builders.OperationBuilder;
import springfox.documentation.service.Operation;
import springfox.documentation.spi.service.contexts.OperationContext;
import springfox.documentation.spi.service.contexts.RequestMappingContext;
import springfox.documentation.spring.web.plugins.DocumentationPluginsManager;
import springfox.documentation.spring.web.readers.operation.OperationReader;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import static com.google.common.collect.Lists.newArrayList;
import static java.util.Arrays.asList;
/**
* @author {@link springfox.documentation.spring.web.readers.operation.ApiOperationReader
* the original author}
* @author Yahuan Jin
* @see springfox.documentation.spring.web.readers.operation.ApiOperationReader
* @since 2019.08.24
*/
@Component
public class SwaggerSupportPositionApiOperationReader implements OperationReader {
private static final Set<RequestMethod> allRequestMethods
= new LinkedHashSet<>(asList(RequestMethod.values()));
private final DocumentationPluginsManager pluginsManager;
private final OperationNameGenerator nameGenerator;
@Autowired
public SwaggerSupportPositionApiOperationReader(DocumentationPluginsManager pluginsManager,
OperationNameGenerator nameGenerator) {
this.pluginsManager = pluginsManager;
this.nameGenerator = nameGenerator;
}
@Override
public List<Operation> read(RequestMappingContext outerContext) {
List<Operation> operations = newArrayList();
Set<RequestMethod> requestMethods = outerContext.getMethodsCondition();
Set<RequestMethod> supportedMethods = supportedMethods(requestMethods);
//Setup response message list
Integer currentCount = 0;
// Get position, then support position. NOTE: not support sorted by RequestMethod.
int position = getApiOperationPosition(outerContext, 0);
for (RequestMethod httpRequestMethod : supportedMethods) {
OperationContext operationContext = new OperationContext(
new OperationBuilder(nameGenerator),
httpRequestMethod,
outerContext,
(position + currentCount));
Operation operation = pluginsManager.operation(operationContext);
if (!operation.isHidden()) {
operations.add(operation);
currentCount++;
}
}
Collections.sort(operations, outerContext.operationOrdering());
return operations;
}
private Set<RequestMethod> supportedMethods(final Set<RequestMethod> requestMethods) {
return requestMethods == null || requestMethods.isEmpty()
? allRequestMethods
: requestMethods;
}
private int getApiOperationPosition(final RequestMappingContext outerContext, final int defaultValue) {
final Optional<ApiOperation> annotation = outerContext.findAnnotation(ApiOperation.class);
return annotation.isPresent() ? annotation.get().position() : defaultValue;
}
}
第3步:实现根据
position
进行数据排序
position
拓展
springfox.documentation.swagger2.mappers.ServiceModelToSwagger2MapperImpl
类。
/*
* Copyright 2015-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.google.common.base.Optional;
import com.google.common.collect.Multimap;
import io.swagger.models.Operation;
import io.swagger.models.Path;
import io.swagger.models.Swagger;
import org.springframework.stereotype.Component;
import springfox.documentation.service.ApiDescription;
import springfox.documentation.service.ApiListing;
import springfox.documentation.service.Documentation;
import springfox.documentation.swagger2.mappers.ServiceModelToSwagger2MapperImpl;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import static springfox.documentation.builders.BuilderDefaults.nullToEmptyList;
/**
* @author {@link springfox.documentation.swagger2.mappers.ServiceModelToSwagger2MapperImpl
* the original author}
* @author Yahuan Jin
* @see springfox.documentation.swagger2.mappers.ServiceModelToSwagger2MapperImpl
* @since 2019.08.24
*/
@Component
public class SwaggerOriginalSortedServiceModelToSwagger2MapperImpl extends ServiceModelToSwagger2MapperImpl {
@Override
public Swagger mapDocumentation(Documentation from) {
final Swagger swagger = super.mapDocumentation(from);
if (Objects.isNull(swagger)) {
return null;
}
Map<String, Path> map__ = mapOriginalSortedApiListings(from.getApiListings());
if (map__ != null) {
swagger.setPaths(map__);
}
return swagger;
}
private Map<String, Path> mapOriginalSortedApiListings(Multimap<String, ApiListing> apiListings) {
Map<String, Path> paths = new LinkedHashMap<>();
for (ApiListing each : apiListings.values()) {
for (ApiDescription api : each.getApis()) {
paths.put(api.getPath(), mapOperations(api, Optional.fromNullable(paths.get(api.getPath()))));
}
}
return paths;
}
private Path mapOperations(ApiDescription api, Optional<Path> existingPath) {
Path path = existingPath.or(new Path());
for (springfox.documentation.service.Operation each : nullToEmptyList(api.getOperations())) {
Operation operation = mapOperation(each);
path.set(each.getMethod().toString().toLowerCase(), operation);
}
return path;
}
}
第4步:用拓展的两个 bean 覆盖默认的; 第5步:指定排序规则
代码合在一起,方便查看
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.google.common.collect.Ordering;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import springfox.documentation.OperationNameGenerator;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiDescription;
import springfox.documentation.service.Operation;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.spring.web.plugins.DocumentationPluginsManager;
import springfox.documentation.spring.web.readers.operation.OperationReader;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import springfox.documentation.swagger2.mappers.ServiceModelToSwagger2Mapper;
import java.util.List;
/**
* @author Yahuan Jin
* @since 2019.08.24
*/
@EnableSwagger2
@Configuration
public class Swagger2Config {
@Bean
public Docket v1ApiDocket() {
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com"))
.paths(PathSelectors.any())
.build();
// Support position
docket.apiDescriptionOrdering(new Ordering<ApiDescription>() {
@Override
public int compare(ApiDescription a, ApiDescription b) {
final List<Operation> leftList = a.getOperations();
final List<Operation> rightList = b.getOperations();
return Integer.compare(leftList.get(0).getPosition(), rightList.get(0).getPosition());
}
});
return docket;
}
@Primary
@Bean(name = "default")
public OperationReader operationReader(
DocumentationPluginsManager pluginsManager, OperationNameGenerator nameGenerator) {
return new SwaggerSupportPositionApiOperationReader(pluginsManager, nameGenerator);
}
@Primary
@Bean(name = "serviceModelToSwagger2Mapper")
public ServiceModelToSwagger2Mapper SwaggerOriginalSortedServiceModelToSwagger2MapperImpl() {
return new SwaggerOriginalSortedServiceModelToSwagger2MapperImpl();
}
}
最后查看效果
略
针对
2.7
(不包括)之后的版本需要做另外的处理
2.7
由于默认的前端UI包
springfox-swagger-ui
移除了排序功能,所以即使后端接口数据排过序了,前端照样会乱。
我的解决方案是使用第三方提供的前端UI包。比如
swagger-bootstrap-ui
knife4j
,新的 api doc 页面可以和默认的同时使用,如果不需要
springfox-swagger-ui
或者
swagger-bootstrap-ui
直接去掉依赖就行了。
http://localhost:8080/swagger-ui.htm
http://localhost:8080/doc.html
maven 的配置方式,自己补上版本号
<properties>
<!-- springfox-swagger: swagger api doc -->
<springfox-swagger.version>2.7.0</springfox-swagger.version>
<!-- knife4j: swagger ui -->
<knife4j.version>2.0.8</knife4j.version>
</properties>
<dependencies>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${springfox.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${springfox.version}</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-ui</artifactId>
<version>${knife4j.version}</version>
</dependency>
</dependencies>