背景
在开发 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 的路径,再重新生成即可。
- 将生成文件均放到
.gitignore 中。
这样的好处是可以避免大量无意义的提交其他
====
本文更多介绍的是这套方案中我们关注的点,并没有一步步的介绍开发步骤,因为这方面的资料谷歌上有很多,大家可以自行查阅。如果阅读中有不理解或疑惑的点,欢迎钉钉 @澶渊 、@乱我。
另外说点自己开发的一些感想,在开发的过程中,应尽可能的让自己变的 “懒” 一些,要对 “重复” 保持足够的敏感,每当感觉有重复代码出现时,就要考虑这里是否可以复用,是否可以通过 AOP 实现,是否可以自动生成,总的来说就是用更少的代码做更多的事,最后谢谢阅读本文。