手头有几个rest服务的项目,写api文档是个力气活,更新api的时候文档经常更新不及时,被QA和使用者抱怨的厉害。一开始改成了markdown,起码可以放在git里方便维护,后来发现连markdown维护也麻烦。后来又尝试了yapi,对api使用者倒是很友好,一个一个api的网上加实在受不了。
花了点时间试用了一下Swagger,完了再配上yapi每分钟一次从dev分支的部署环境同步swagger文档,代码提交触发流水线自动部署,1分钟以后,其他人就能通过yapi查看和试用了。
马克一下,趟了几个小坑。主要是很多服务的输出都有类似的结构,但是,有一部分又不一样的情况,如分页的结果。文档主要是给用户看的,总不能给人家整个{"code": 0, "message": "haha", "result": {}}这样,让人家自己猜result里面都有哪些属性吧。
最后在官方实例中找到了解。
http://springfox.github.io/springfox/docs/current/#springfox-spring-mvc-and-spring-boot
pom.xml
最开始用了swagger2版本是2.8,结果不知道咋回事,浏览器死活js报错。各种查证都不行,最后,,改了2.5,莫名奇妙而好了。
<properties>
<spring-boot-version>2.0.0.RELEASE</spring-boot-version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot-version}</version>
<exclusions>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>${spring-boot-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>${spring-boot-version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.5.0</version>
</dependency>
</dependencies>
增加两个Config类
Swagger2Config.java
import com.fasterxml.classmate.TypeResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.ResponseEntity;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.WildcardType;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import static springfox.documentation.schema.AlternateTypeRules.newRule;
@Configuration
@EnableSwagger2
public class Swagger2Config extends WebMvcConfigurationSupport {
@Autowired
private TypeResolver typeResolver;
@Bean
public Docket productApi() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.xxx.xxx"))
.paths(PathSelectors.any())
.build()
.directModelSubstitute(org.joda.time.LocalDate.class, java.sql.Date.class) //不需要在文档上显示细节的复杂类型
.directModelSubstitute(org.joda.time.DateTime.class, java.util.Date.class)
.directModelSubstitute(java.time.ZonedDateTime.class, java.util.Date.class)
.directModelSubstitute(JsonNode.class, Object.class)
.apiInfo(metaData())
.alternateTypeRules( //自定义规则,如果遇到DeferredResult,则把泛型类转成json
newRule(typeResolver.resolve(DeferredResult.class,
typeResolver.resolve(ResponseEntity.class, WildcardType.class)),
typeResolver.resolve(WildcardType.class)))
;
}
private ApiInfo metaData() {
return new ApiInfoBuilder()
.title("REST API")
.description("\"REST API for Online Store\"")
.version("1.0.0")
.license("Apache License Version 2.0")
.licenseUrl("https://www.apache.org/licenses/LICENSE-2.0\"")
.contact(new Contact("myname", "", "mail@mail"))
.build();
}
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
SpringSecConfig.java
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class SpringSecConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests().antMatchers("/","/swagger-resources").permitAll();
httpSecurity.csrf().disable();
httpSecurity.headers().frameOptions().disable();
}
}
MyController.java
@RestController
@RequestMapping("/service/v1")
@Api(value="Controller",tags={"接口"})
public class MyController {
@ApiOperation(value="获得某个详细信息")
@ApiImplicitParams(
{
@ApiImplicitParam(name="mnftCode", value="厂商", paramType="query"),
@ApiImplicitParam(name="badgeNmb", value="编号", paramType="query")
}
)
@RequestMapping(value = "/meters/{mnftCode}/{badgeNmb}", method = RequestMethod.GET)
public DeferredResult<ResponseEntity<BaseResponse<MeterVo>>> queryOneGasMeter(@PathVariable("mnftCode") String mnftCode,
@PathVariable("badgeNmb") String badgeNmb){
DeferredResult<ResponseEntity<BaseResponse<MeterVo>>> result = new DeferredResult();
if (mnftCode != null && badgeNmb != null){
try {
MeterVo meterVo = xxService.queryOneMeter(mnftCode,badgeNmb);
result.setResult(BaseResponse.generateOKResponseEntity(meterVo));
}catch (MyException e){
result.setErrorResult(BaseResponse.generateBadResponseEntity(e.getErrorCode(), e.getMessage(), null));
}
}else {
result.setErrorResult(BaseResponse.generateBadResponseEntity("error", null));
}
return result;
}
}
BaseResponse.java
import io.swagger.annotations.ApiModelProperty;
import org.springframework.http.ResponseEntity;
public class BaseResponse<T> {
@ApiModelProperty(value = "返回码:正确0, 警告2,错误为自定义码")
private int responseCode;
@ApiModelProperty(value = "返回消息")
private String responseMsg;
@ApiModelProperty(value = "返回具体内容")
private T result;
public BaseResponse(int responseCode, String responseMsg, T result) {
this.responseCode = responseCode;
this.responseMsg = responseMsg;
this.result = result;
}
//这两个方法写在这里纯粹为了省地儿
public static ResponseEntity generateOKResponseEntity(Object object) {
return ResponseEntity.ok().body(new BaseResponse<>(ResponseCode.CODE_SUCCESS, "Success", object));
}
public static ResponseEntity generateBadResponseEntity(String message, Object object) {
return ResponseEntity.badRequest().body(
new BaseResponse<>(ResponseCode.CODE_ERROR, message, object));
}
public static ResponseEntity generateBadResponseEntity(int code, String message, Object object) {
return ResponseEntity.badRequest().body(
new BaseResponse<>(code, message, object));
}
public int getResponseCode() {
return responseCode;
}
public void setResponseCode(int responseCode) {
this.responseCode = responseCode;
}
public String getResponseMsg() {
return responseMsg;
}
public void setResponseMsg(String responseMsg) {
this.responseMsg = responseMsg;
}
public T getResult() {
return result;
}
}
MeterVo的类定义上,也需要标注@ApiModelProperty注释
如此定义完毕,在swagger页面上,对此服务的出参,大概就会是下面这个样子。也比较适合统一有分页格式的输出。
{
"responseCode": 0,
"respponseMessage": "string",
"result": {
"mnftCode": "string",
"badgeNmb": "string"
}
}