很多时候,Swagger定义的标准并不能满足我们实际的需求,比如拿分组后的接口来说,有适合我们希望我们的接口能够排序,假如我们当前有一个注册的需求实现,那么他的接口可能是这样的:
1.获取验证码 -> 2.校验用户名是否有效 -> 3.注册验证 -> 4.登录
如果我们没有排序的情况下,上面的接口对于开发人员来说可能是杂乱无章的,对于初级的接口对接人员来说,排序更能让开发者把当前的需求清晰明了的用代码来实现掉,为此,接口文档的作用也能最大化.
那么,在swagger的标准中,那些允许我们自定义扩展,在Springfox中我们又如何来实现我们的自定义扩展呢?
先来看Swagger定义的几个标准属性,可参考官方文档
swagger | string |
Required. Specifies the Swagger Specification version being used. It can be used by the Swagger UI and other clients to interpret the API listing. The value MUST be "2.0" . |
---|---|---|
info | Info Object | Required. Provides metadata about the API. The metadata can be used by the clients if needed. |
host | string |
The host (name or ip) serving the API. This MUST be the host only and does not include the scheme nor sub-paths. It MAY include a port. If the host is not included, the host serving the documentation is to be used (including the port). The host does not support path templating. |
basePath | string |
The base path on which the API is served, which is relative to the host . If it is not included, the API is served directly under the host . The value MUST start with a leading slash (/ ). The basePath does not support path templating. |
schemes | [string ] |
The transfer protocol of the API. Values MUST be from the list: "http" , "https" , "ws" , "wss" . If the schemes is not included, the default scheme to be used is the one used to access the Swagger definition itself. |
consumes | [string ] |
A list of MIME types the APIs can consume. This is global to all APIs but can be overridden on specific API calls. Value MUST be as described under Mime Types. |
produces | [string ] |
A list of MIME types the APIs can produce. This is global to all APIs but can be overridden on specific API calls. Value MUST be as described under Mime Types. |
paths | Paths Object | Required. The available paths and operations for the API. |
definitions | Definitions Object | An object to hold data types produced and consumed by operations. |
parameters | Parameters Definitions Object | An object to hold parameters that can be used across operations. This property does not define global parameters for all operations. |
responses | Responses Definitions Object | An object to hold responses that can be used across operations. This property does not define global responses for all operations. |
securityDefinitions | Security Definitions Object | Security scheme definitions that can be used across the specification. |
security | [Security Requirement Object] | A declaration of which security schemes are applied for the API as a whole. The list of values describes alternative security schemes that can be used (that is, there is a logical OR between the security requirements). Individual operations can override this definition. |
tags | [Tag Object] | A list of tags used by the specification with additional metadata. The order of the tags can be used to reflect on their order by the parsing tools. Not all tags that are used by the Operation Objectmust be declared. The tags that are not declared may be organized randomly or based on the tools’ logic. Each tag name in the list MUST be unique. |
externalDocs | External Documentation Object | Additional external documentation. |
在上面定义的标准字段中,最后一个是扩展对象,我们在Java对象中io.swagger.models.Swagger
中可以看到他的定义
public class Swagger {
protected String swagger = "2.0";
protected Info info;
protected String host;
protected String basePath;
protected List tags;
protected List schemes;
protected List consumes;
protected List produces;
protected List security;
protected Map paths;
protected Map securityDefinitions;
protected Map definitions;
protected Map parameters;
protected Map responses;
protected ExternalDocs externalDocs;
//扩展属性
protected Map vendorExtensions;
//setter and getter
}
所以从上面的接口定义来看,我们最终可以来一一查看Swagger支持哪些对象进行自定义扩展属性
在Swagger的根对象中,我们看到他是有vendorExtensions
扩展属性的支持的,是一个散列的数据结构类型,这意味着我们可以在Swagger的根对象中添加多个扩展属性
扩展属性名称的规则必须是以x-
来开头,所有的都是这个规则
Field Pattern | Type | Description |
---|---|---|
^x- | Any | Allows extensions to the Swagger Schema. The field name MUST begin with x- , for example, x-internal-id . The value can be null , a primitive, an array or an object. See Vendor Extensions for further details. |
看到此处,以前在swagger-bootstrap-ui的1.8.5版本中添加的扩展属性都是不规范的,因为没有应用swagger的标准规则,在后期的版本中要重写该规则实现
所以,我们在Swagger根路径扩展的规则最终生成的JSON格式可能是这样:
{
"swagger": "2.0",
"info": {
"description": "swagger-bootstrap-ui-demo RESTful APIs",
"version": "1.0",
"title": "swagger-bootstrap-ui很棒~~~!!!",
"termsOfService": "http://www.group.com/",
"contact": {
"name": "[email protected]"
}
},
"host": "127.0.0.1:8999",
"basePath": "/",
"tags": [
{
"name": "1.8.2版本",
"description": "Api 182 Controller"
}
],
"paths": {
"/2/api/new187/postRequest": {
"post": {
"tags": [
"api-1871-controller"
],
"summary": "版本2-post请求参数Hidden属性是否生效",
"operationId": "postRequestUsingPOST_1",
"consumes": [
"application/json"
],
"produces": [
"*/*"
],
"parameters": [
{
"in": "body",
"name": "model187",
"description": "model187",
"required": true,
"schema": {
"originalRef": "Model187",
"$ref": "#/definitions/Model187"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"originalRef": "Rest«Model187»",
"$ref": "#/definitions/Rest«Model187»"
}
},
"201": {
"description": "Created"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Not Found"
}
},
"security": [
{
"BearerToken": [
"global"
]
},
{
"BearerToken1": [
"global"
]
}
],
"deprecated": false
}
}
},
"securityDefinitions": {
"BearerToken": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
}
},
"definitions": {
"AInfoVo": {
"type": "object",
"required": [
"aId",
"bList"
],
"properties": {
"aId": {
"type": "string",
"description": "A记录主键"
},
"bList": {
"type": "object",
"description": "B信息Map, key为BInfoVo的主键pkId",
"additionalProperties": {
"originalRef": "BInfoVo",
"$ref": "#/definitions/BInfoVo"
}
}
},
"title": "AInfoVo",
"description": "A信息"
},
"ActInteger": {
"type": "object",
"properties": {
"doub1": {
"type": "number",
"format": "double",
"description": "double类型属性"
},
"float1": {
"type": "number",
"format": "float",
"description": "float类型属性"
},
"name": {
"type": "string"
},
"number": {
"type": "integer",
"format": "int64",
"description": "Long类型"
},
"price": {
"type": "number",
"description": "BigDecimal类型属性"
},
"sort": {
"type": "integer",
"format": "int32",
"description": "int类型"
}
},
"title": "ActInteger"
},
"Actor": {
"type": "object",
"properties": {
"address": {
"type": "string"
},
"deepOne": {
"originalRef": "DeepOne",
"$ref": "#/definitions/DeepOne"
},
"recipt": {
"originalRef": "Recipt",
"$ref": "#/definitions/Recipt"
},
"sort": {
"type": "integer",
"format": "int32"
}
},
"title": "Actor"
}
},
"x-description":"Swagger扩展属性之一Description"
}
x-description
属性就是我们扩展的标准的扩展属性,我们在Java代码中也可以这么来实现:
Swagger swagger = mapper.mapDocumentation(documentation);
swagger.setVendorExtension("x-description","Swagger扩展属性之一Description");
同上面的规则,path中定义的属性,我们同样可以扩展我们的规则,此时我们的需求,根据接口来排序就可以通过path增加扩展属性来实现,比如我们给path添加一个扩展属性x-order
path的JSON结构就可能如下:
"/2/api/new187/postRequest": {
"post": {
"tags": [
"api-1871-controller"
],
"summary": "版本2-post请求参数Hidden属性是否生效",
"operationId": "postRequestUsingPOST_1",
"consumes": [
"application/json"
],
"produces": [
"*/*"
],
"parameters": [
{
"in": "body",
"name": "model187",
"description": "model187",
"required": true,
"schema": {
"originalRef": "Model187",
"$ref": "#/definitions/Model187"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"originalRef": "Rest«Model187»",
"$ref": "#/definitions/Rest«Model187»"
}
},
"201": {
"description": "Created"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Not Found"
}
},
"security": [
{
"BearerToken": [
"global"
]
},
{
"BearerToken1": [
"global"
]
}
],
"deprecated": false,
"x-order":"1"
}
}
swagger在Open API的规范文档中声明的是任意对象都可以有对象扩展,只要符合扩展规则即可(即扩展属性名称以x-
开头)
我们知道了扩展规则的定义,那么在Springfox中我们如何自定义实现呢?
在前面的文章中,我们介绍了springfox使用了Spring Plugin系统来增强整个框架的可扩展性,我想此时的你应该能明白了,如果要扩展path接口的属性,那么,其实我们只需要找到Springfox提供的Plugin接口,然后增加一个Plugin的接口实现,把我们的扩展属性增加进去即可
接下来,我们实现上面path接口的自定义x-order
的属性扩展实现.
我们先来看Springfox将自己的Documentation对象转换为Swagger标准对象的代码
@Override
public Swagger mapDocumentation(Documentation from) {
if ( from == null ) {
return null;
}
Swagger swagger = new Swagger();
swagger.setVendorExtensions( vendorExtensionsMapper.mapExtensions( from.getVendorExtensions() ) );
swagger.setSchemes( mapSchemes( from.getSchemes() ) );
//path转换
swagger.setPaths( mapApiListings( from.getApiListings() ) );
swagger.setHost( from.getHost() );
swagger.setDefinitions( modelMapper.modelsFromApiListings( from.getApiListings() ) );
swagger.setSecurityDefinitions( securityMapper.toSecuritySchemeDefinitions( from.getResourceListing() ) );
ApiInfo info = fromResourceListingInfo( from );
if ( info != null ) {
swagger.setInfo( mapApiInfo( info ) );
}
swagger.setBasePath( from.getBasePath() );
swagger.setTags( tagSetToTagList( from.getTags() ) );
List list2 = from.getConsumes();
if ( list2 != null ) {
swagger.setConsumes( new ArrayList( list2 ) );
}
else {
swagger.setConsumes( null );
}
List list3 = from.getProduces();
if ( list3 != null ) {
swagger.setProduces( new ArrayList( list3 ) );
}
else {
swagger.setProduces( null );
}
return swagger;
}
既然我们的目标是path,那么来看mapApiListings
方法
protected Map mapApiListings(Multimap apiListings) {
Map paths = newTreeMap();
for (ApiListing each : apiListings.values()) {
for (ApiDescription api : each.getApis()) {
paths.put(api.getPath(), mapOperations(api, Optional.fromNullable(paths.get(api.getPath()))));
}
}
return paths;
}
程序的逻辑是:
继续来看mapOperations
方法
private Path mapOperations(ApiDescription api, Optional 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;
}
循环遍历所有的Operation,此处为什么有循环,因为我们同一个接口允许存在不同的请求方式(GET | POST | PUT | DELETE…),但是请求体可以相同 |
@Override
protected io.swagger.models.Operation mapOperation(Operation from) {
if ( from == null ) {
return null;
}
io.swagger.models.Operation operation = new io.swagger.models.Operation();
operation.setSecurity( mapAuthorizations( from.getSecurityReferences() ) );
operation.setVendorExtensions( vendorExtensionsMapper.mapExtensions( from.getVendorExtensions() ) );
看到mapOperation
方法时,我们终于看到了setVendorExtensions的操作,即赋值扩展属性
那么来看扩展属性是如何来实现的
@Mapper
public class VendorExtensionsMapper {
public Map mapExtensions(List from) {
Map extensions = newTreeMap();
Iterable listExtensions = from(from)
.filter(ListVendorExtension.class);
for (ListVendorExtension each : listExtensions) {
extensions.put(each.getName(), each.getValue());
}
Iterable
通过VendorExtensionsMapper
中的扩展属性方法,我们看到,springfox目前做了限制,因为VendorExtension
是接口,我们可以有我们自己的定义实现,但是springfox最终会对扩展接口进行filter过滤,从代码中我们看到,springfox目前只允许三种扩展实现
分别是:
既然springfox给我们提供了默认的三种扩展实现,那么针对上面的我们需要扩展path的扩展属性,我们使用StringVendorExtension:string
这种类型即可
针对path的operation操作,我们在前面也介绍过,springfox最终使用的是OperationBuilderPlugin
接口
那么我们只需要写一个OperationBuilderPlugin
Plugin接口的实现即可
@Component
@Order(Ordered.HIGHEST_PRECEDENCE+100)
public class OperationPositionBulderPlugin implements OperationBuilderPlugin {
@Override
public void apply(OperationContext context) {
context.operationBuilder().extensions(Lists.newArrayList(new StringVendorExtension("x-order","1")));
}
@Override
public boolean supports(DocumentationType delimiter) {
return true;
}
}
目前假设我们给所有的接口都赋予扩展属性x-order
,默认值为”1”
此时,我们来看/v2/api-docs接口响应的JSON示例
上图中,我们其实可以看到我们的自定义扩展属性实现了
然后我们在结合我们自定义的swagger-bootstrap-ui前端UI渲染程序,把order字段作为path接口的排序字段,在页面进行排序显示,这样我们就实现了我们的接口自定义排序规则了
有可能细心的朋友会问,我们给的order值都是1,如果给与开发者在代码中给接口自定义的值呢
此时,有两种方式
@ApiOperationSort
进行获取@ApiOperation
中的postion属性进行二次利用此时,我们的接口代码可能会如下:
@PostMapping("/createOr33der")
@ApiOperation(value = "创建订单",position = 2)
public Rest createOrdetr(@RequestBody Order order,HttpSession httpSession){
Rest r=new Rest<>();
r.setData(order);
return r;
}
@PostMapping("/createOrder")
@ApiOperationSort(3)
@ApiOperation(value = "hash测试",nickname = "test")
public Rest createOrder(@RequestBody Order order){
Rest r=new Rest<>();
r.setData(order);
return r;
}
两种方式,取第一种即可
这时,我们更改一下上面我们的Plugin接口实现
@Override
public void apply(OperationContext context) {
int position=Integer.MAX_VALUE;
//首先查找ApiOperation注解
Optional api=context.findAnnotation(ApiOperation.class);
if (api.isPresent()){
//判断postion是否有值
int posit=api.get().position();
if (posit!=0){
position=posit;
}else{
Optional apiOperationSortOptional=context.findAnnotation(ApiOperationSort.class);
if (apiOperationSortOptional.isPresent()){
position=apiOperationSortOptional.get().value();
}
}
}else{
Optional apiOperationSortOptional=context.findAnnotation(ApiOperationSort.class);
if (apiOperationSortOptional.isPresent()){
position=apiOperationSortOptional.get().value();
}
}
context.operationBuilder().extensions(Lists.newArrayList(new StringVendorExtension("x-order",String.valueOf(position))));
}
此时,我们再来看JSON效果
此时,我们看到,我们的自定义属性已经出现了,而且是根据开发者自定义的实现,此时我们的目的也达到了.