最近在使用swagger2作用在线文档工具,完成后发现在页面上模块和接口的顺序是混乱的。
swagger 使用的版本信息
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.8.9</version>
</dependency>
理想的顺序是,模块是开发、二次开发、测试,接口是开始、暂停、继续、结束。项目启动后,接口的显示没有和预计的一样。
经过分析发现,接口信息的数据中通过http://127.0.0.1:8080/v2/api-docs接口获取的。在返回数据中,tags对应的是目录节点,paths对应的是各个接口,通过里面的tags里的。如果要进行排序,则返回结里tags和paths都是按照指定的顺序返回的。
接下来分析源码
springfox.documentation.swagger2.mappers.ServiceModelToSwagger2MapperImpl的mapDocumentation方法:
public Swagger mapDocumentation(Documentation from) {
if (from == null) {
return null;
} else {
Swagger swagger = new Swagger();
swagger.setVendorExtensions(this.vendorExtensionsMapper.mapExtensions(from.getVendorExtensions()));
swagger.setSchemes(this.mapSchemes(from.getSchemes()));
// path属性设置
swagger.setPaths(this.mapApiListings(from.getApiListings()));
swagger.setHost(from.getHost());
swagger.setDefinitions(this.modelMapper.modelsFromApiListings(from.getApiListings()));
swagger.setSecurityDefinitions(this.securityMapper.toSecuritySchemeDefinitions(from.getResourceListing()));
ApiInfo info = this.fromResourceListingInfo(from);
if (info != null) {
swagger.setInfo(this.mapApiInfo(info));
}
swagger.setBasePath(from.getBasePath());
// tags 属性设置
swagger.setTags(this.tagSetToTagList(from.getTags()));
....
springfox.documentation.swagger2.mappers.ServiceModelToSwagger2Mapper
// paths 属性封闭实现方法
protected Map<String, Path> mapApiListings(Multimap<String, ApiListing> apiListings) {
Map<String, Path> paths = Maps.newTreeMap();
Iterator var3 = apiListings.values().iterator();
while(var3.hasNext()) {
ApiListing each = (ApiListing)var3.next();
Iterator var5 = each.getApis().iterator();
while(var5.hasNext()) {
ApiDescription api = (ApiDescription)var5.next();
paths.put(api.getPath(), this.mapOperations(api, Optional.fromNullable(paths.get(api.getPath()))));
}
}
return paths;
}
// tags 属性的封装实现方法
protected List<Tag> tagSetToTagList(Set<springfox.documentation.service.Tag> set) {
if (set == null) {
return null;
} else {
List<Tag> list = new ArrayList(set.size());
Iterator var3 = set.iterator();
while(var3.hasNext()) {
springfox.documentation.service.Tag tag = (springfox.documentation.service.Tag)var3.next();
list.add(this.mapTag(tag));
}
return list;
}
}
经过代码跟踪分析,总结如下:
1. 模块的排序是依据tag 的名称进行的,虽然@Api里保留有position属性,但是已经完全弃用。
2.接口的排序也是依据@ApiOperation里value的值进行的,position属性虽然废弃,但是仍可以进行设置取值。
解决方案为依据排序设计规则,重写相应的方法。规则如下:
1.模块的排序,使用tag名称排序,在名称前加上前缀0X-,即在名称就加上"01-"、"02-"。但为了页面展示效果,在排序后把前缀进行处理;
2.接口的排序,使用@ApiOperation的position实现。
代码实现
@Api(tags = "01-开发")
@RestController
@RequestMapping("/b")
public class Test3Contoller {
@ApiOperation(value = "开始",position = 1)
@ApiImplicitParam(name = "platformNo", value = "平台编号", required = true, dataType = "String", defaultValue = "34020000001320000001")
@GetMapping(value = "/start")
public RespData queryCatalog(String platformNo) {
return RespData.success();
}
package com.ferry.configer;
import com.google.common.base.Optional;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import io.swagger.models.Operation;
import io.swagger.models.Path;
import io.swagger.models.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import springfox.documentation.builders.BuilderDefaults;
import springfox.documentation.service.ApiDescription;
import springfox.documentation.service.ApiListing;
import springfox.documentation.swagger2.mappers.ServiceModelToSwagger2MapperImpl;
import springfox.documentation.swagger2.mappers.VendorExtensionsMapper;
import java.util.*;
@Primary //同一个接口,可能会有几种不同的实现类,而默认只会采取其中一种的情况下
@Component("ServiceModelToSwagger2Mapper")
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomModelToSwaggerMapper extends ServiceModelToSwagger2MapperImpl {
@Autowired
private VendorExtensionsMapper vendorExtensionsMapper;
protected Map<String, Path> mapApiListings(Multimap<String, ApiListing> apiListings) {
Map<String, Path> paths = Maps.newTreeMap();
Iterator var3 = apiListings.values().iterator();
while (var3.hasNext()) {
ApiListing each = (ApiListing) var3.next();
List<ApiDescription> list = each.getApis();
Iterator var5 = each.getApis().iterator();
while (var5.hasNext()) {
ApiDescription api = (ApiDescription) var5.next();
paths.put(api.getOperations().get(0).getPosition() + "-" + api.getPath(), this.mapOperations(api, Optional.fromNullable(paths.get(api.getPath()))));
}
}
Map<String, Path> paths2 = new LinkedHashMap<>();
for (String key : paths.keySet()) {
paths2.put(key.substring(key.indexOf("-") + 1), paths.get(key));
}
return paths2;
}
private Path mapOperations(ApiDescription api, Optional<Path> existingPath) {
Path path = (Path) existingPath.or(new Path());
Iterator var4 = BuilderDefaults.nullToEmptyList(api.getOperations()).iterator();
while (var4.hasNext()) {
springfox.documentation.service.Operation each = (springfox.documentation.service.Operation) var4.next();
Operation operation = this.mapOperation(each);
path.set(each.getMethod().toString().toLowerCase(), operation);
}
return path;
}
protected Operation mapOperation(springfox.documentation.service.Operation from) {
if (from == null) {
return null;
} else {
Operation operation = new Operation();
operation.setSecurity(this.mapAuthorizations(from.getSecurityReferences()));
operation.setVendorExtensions(this.vendorExtensionsMapper.mapExtensions(from.getVendorExtensions()));
operation.setDescription(from.getNotes());
operation.setOperationId(from.getPosition() + from.getUniqueId());
operation.setResponses(this.mapResponseMessages(from.getResponseMessages()));
operation.setSchemes(this.stringSetToSchemeList(from.getProtocol()));
Set<String> set = from.getTags();
if (set != null) {
Iterator<String> it= set.iterator();
Set<String> rset= new HashSet<>();
while (it.hasNext()){
String tagName= it.next();
rset.add(tagName.substring(tagName.indexOf("-")+1));
}
operation.setTags(new ArrayList(rset));
} else {
operation.setTags((List) null);
}
operation.setSummary(from.getSummary());
Set<String> set1 = from.getConsumes();
if (set1 != null) {
operation.setConsumes(new ArrayList(set1));
} else {
operation.setConsumes((List) null);
}
Set<String> set2 = from.getProduces();
if (set2 != null) {
operation.setProduces(new ArrayList(set2));
} else {
operation.setProduces((List) null);
}
operation.setParameters(this.parameterListToParameterList(from.getParameters()));
if (from.getDeprecated() != null) {
operation.setDeprecated(Boolean.parseBoolean(from.getDeprecated()));
}
return operation;
}
}
@Override
protected List<Tag> tagSetToTagList(Set<springfox.documentation.service.Tag> set) {
List<Tag> tlist = super.tagSetToTagList(set);
List<Tag> result = new LinkedList<>();
Iterator<Tag> it = tlist.iterator();
while (it.hasNext()) {
Tag tag = it.next();
Tag tt = new Tag();
tt.setName(tag.getName().substring(tag.getName().indexOf("-") + 1));
tt.setDescription(tag.getDescription());
result.add(tt);
}
return result;
}
}
重新运行的效果: