背景
在开发 Restful
服务的过程中,大家或多或少都会碰到类似的问题,比如:接口如何文档化、怎样自动生成 Client
。我们在开发 DMS
的过程,也碰到了类似的问题,并且积累了一些经验,借此跟大家分享,希望抛砖引玉。
概述
首先整体说下总的技术方案就是 Spring Boot
+ Springfox
+ Swagger Codegen
,其中 Spring Boot
+ Springfox
主要解决了接口如何文档化问题;Swagger Codegen
则主要解决了如何自动生成 Client
的问题。下面就详细介绍这套方案的实现细节。
具体方案
接口文档化
Spring Boot
+Springfox
Springfox
是为基于 Spring
构建的接口自动生成文档的工具,它的原理就是根据 Spring
接口层的注解生成符合 Swagger
规范的接口描述文件,然后通过内嵌的 Swagger UI
解析该描述文件并渲染出来。
对于这个这个方案,知道的人比较多,内网也有很多介绍该方案的文章,附录里我罗列了些,在此我就不具体阐述,下面我只介绍下我们使用的一些经验。
-
@Api
的tags
属性请使用英文,该属性值在Codegen
的时候会作为模块名。 -
创建多版本
Api
的方法@Configuration @EnableSwagger2 @ComponentScan(basePackages = {"com.tmall.pegasus.dms.controller"}) public class SwaggerConfiguration { @Bean public Docket v20DocumentationPlugin() { return new VersionedDocket("2.0"); } @Bean public Docket v10DocumentationPlugin() { return new VersionedDocket("1.0"); } class VersionedDocket extends Docket { public VersionedDocket(String version) { super(DocumentationType.SWAGGER_2); super.groupName(version) .select() .apis(RequestHandlerSelectors.any()) .paths(regex(API_BASE_PATH + "/.*")) .build() .apiInfo(getApiInfo(version)) .pathProvider(new BasePathAwareRelativePathProvider(API_BASE_PATH)) // 这里记得设置 protocols,不然 Codegen 默认生成的 basePath 是 https 协议 .protocols(Sets.newHashSet("http")) .directModelSubstitute(LocalDate.class, String.class) .genericModelSubstitutes(ResponseEntity.class); } private ApiInfo getApiInfo(String version) { return new ApiInfo("Test Api", "Test Api Documentation", "1.0", "urn:tos", new Contact("xiaoming", "", "xiaoming@qq.com"), "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList()); } } @Bean UiConfiguration uiConfig() { return UiConfigurationBuilder.builder() .deepLinking(true) .displayOperationId(false) .defaultModelsExpandDepth(1) .defaultModelExpandDepth(1) .defaultModelRendering(ModelRendering.EXAMPLE) .displayRequestDuration(false) .docExpansion(DocExpansion.NONE) .filter(false) .maxDisplayedTags(null) .operationsSorter(OperationsSorter.ALPHA) .showExtensions(false) .tagsSorter(TagsSorter.ALPHA) .supportedSubmitMethods(UiConfiguration.Constants.DEFAULT_SUBMIT_METHODS) .validatorUrl(null) .build(); } class BasePathAwareRelativePathProvider extends AbstractPathProvider { private String basePath; public BasePathAwareRelativePathProvider(String basePath) { this.basePath = basePath; } @Override protected String applicationPath() { return basePath; } @Override protected String getDocumentationPath() { return "/"; } @Override public String getOperationPath(String operationPath) { UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromPath("/"); return Paths.removeAdjacentForwardSlashes( uriComponentsBuilder.path(operationPath.replaceFirst(basePath, "")).build().toString()); } } }
-
在
Controller
上统一加上接口的前缀,尤其接口有多个版本的时候,该方式会非常方便。@RequestMapping(path = "/api/v1") public class DataController { } @RequestMapping(path = "/api/v2") public class DataControllerV2 { }
-
方法注解记得设置
nickName
,设置该属性的好处是在Codegen
的时候会用该属性值作为对应的接口方法名。@ApiOperation(value = "新建数据", nickname = "createContent")
自动生成 Client
Swagger Codegen
, 官方 Usage Doc
在让业务方使用我们接口的时候,自然而然的我们需要提供对应的 Client
,如果一个个手动封装接口,会带来很多的重复工作,维护起来也很麻烦,于是我们想寻求一些自动生成的方案。后来发现 Swagger
官方已经帮我们想好了,对应的就是 Swagger Codegen
,它的原理也不复杂,就是基于 Swagger
的接口描述文件生成 Client
代码,当然它也支持生成 Server
端的项目骨架。
下面,我就详细说下我们的使用经验。
-
多模块项目生成
Client
针对多模块项目,我们会期望
Client
的pom
可以自定义,因为client
会依赖common
的一些Model
类,另外会依赖Parent Pom
,所以不能通过codegen
生成。这个可以通过.swagger-codegen-ignore
实现,该文件你可以理解为类似.gitignore
的文件,在codegen
的时候会忽略生成该文件下的文件列表。.swagger-codegen-ignore
要放到client
目录下,配置的路径都是相对Client
目录而言,你可以把所有不想生成的文件都列在里面,下面就是一个具体示例:build.sh build.sbt build.gradle gradle.properties gradlew gradlew.bat pom.xml README.md settings.gradle .gitignore gradle .swagger-codegen/VERSION docs/** git_push.sh .travis.yml src/main/AndroidManifest.xml
-
推荐通过
maven
插件配置生成Client
。对应的插件配置示例如下:
<plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> <executions> <execution> <id>clean-additional-generated-files</id> <phase>generate-sources</phase> <goals> <goal>clean</goal> </goals> <configuration> <excludeDefaultDirectories>true</excludeDefaultDirectories> <filesets> <fileset> <directory>${project.basedir}/src/main/java</directory> <!--<directory>${project.basedir}/src/test</directory>--> </fileset> </filesets> </configuration> </execution> </executions> </plugin> <plugin> <groupId>io.swagger</groupId> <artifactId>swagger-codegen-maven-plugin</artifactId> <version>2.3.1</version> <executions> <execution> <goals> <goal>generate</goal> </goals> <configuration> <templateDirectory>${project.basedir}/src/main/resources/template</templateDirectory> <inputSpec>${project.basedir}/src/main/resources/api.json</inputSpec> <!--<inputSpec>http://localhost:7001/v2/api-docs?group=1.0</inputSpec>--> <language>java</language> <output>${project.basedir}</output> <importMappings> <importMapping>ContentInfo=com.tmall.pegasus.dms.common.result.ContentInfo</importMapping> <importMapping>DataContent=com.tmall.pegasus.dms.common.params.DataContent</importMapping> <importMapping>DeliveryInfo=com.tmall.pegasus.dms.common.result.DeliveryInfo</importMapping> <importMapping>FieldInfo=com.tmall.pegasus.dms.common.result.FieldInfo</importMapping> <importMapping>ResourceInfo=com.tmall.pegasus.dms.common.result.ResourceInfo</importMapping> </importMappings> <invokerPackage>com.tmall.pegasus.dms.client</invokerPackage> <apiPackage>com.tmall.pegasus.dms.client.api</apiPackage> <modelPackage>com.tmall.pegasus.dms.common.result</modelPackage> <library>feign</library> <configOptions> <sourceFolder>src/main/java</sourceFolder> <ignoreFileOverride>${project.basedir}/.swagger-codegen-ignore</ignoreFileOverride> </configOptions> </configuration> </execution> </executions> </plugin>
第一个插件是
maven-clean-plugin
,它帮助我们clean
掉上次生成的代码,第二个就是maven-swagger-codegen
插件,可以看出里面的配置非常丰富,各个配置的含义都可以在 官方文档 找到,有几个配置我觉得会常用到,下面简单介绍下。-
importMappings 当你不想用
Swagger Codegen
自己生成的Model
类,而是想用自己Common
包里的Model
时,你可以用该配置实现,配置项就是一个个的映射关系,在Codegen
的时候会将Model
引用替换成你设置的值。 - apiPackage 就是你接口的包名
-
library
Codegen
默认支持多种http client
,你可以通过该参数指定。 -
ignoreFileOverride 就是
.swagger-codegen-ignore
对应的路径。 -
templateDirectory 你如果想自定义生成
Client
代码,可以通过自定义Generator
实现,你如果只是想对系统生成的Client
做微小调整,可以通过修改系统自带的的Template
实现。方法就是把Swaggen Codegen
的模板文件拷贝一份到你的src/main/resources
里,直接改里面的文件,同时记得配置templateDirectory
的路径,再重新生成即可。
-
importMappings 当你不想用
- 将生成文件均放到
.gitignore
中。
这样的好处是可以避免大量无意义的提交其他
====
本文更多介绍的是这套方案中我们关注的点,并没有一步步的介绍开发步骤,因为这方面的资料谷歌上有很多,大家可以自行查阅。如果阅读中有不理解或疑惑的点,欢迎钉钉 @澶渊 、@乱我。
另外说点自己开发的一些感想,在开发的过程中,应尽可能的让自己变的 “懒” 一些,要对 “重复” 保持足够的敏感,每当感觉有重复代码出现时,就要考虑这里是否可以复用,是否可以通过 AOP
实现,是否可以自动生成,总的来说就是用更少的代码做更多的事,最后谢谢阅读本文。