Spring Boot
目标
在 Spring Boot 项目中使用 swagger 进行接口测试
开发环境
名称 | 版本 |
---|---|
操作系统 | Windows 10 X64 |
JDK | JDK1.8(jdk-8u151-windows-x64) |
IntelliJ IDEA | IntelliJ IDEA 2018.3 |
Maven | Maven 3.6.0 |
swagger2 | 2.7.0 |
swagger-ui | 2.7.0 |
说明
本文代码基于 7.Spring Boot 中使用 Redis 数据库 的基础上进行修改
test-invoice-web
pom.xml
<properties>
<swagger.version>2.7.0</swagger.version>
<swagger-ui.version>2.7.0</swagger-ui.version>
</properties>
<dependencies>
<!--swagger start-->
<!--springfox是通过注解的形式自动生成API文档,利用它,可以很方便的书写restful API,swagger主要用于展示springfox生成的API文档-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger-ui.version}</version>
</dependency>
<!--swagger end-->
</dependencies>
application.yml
# 是否显示swagger-ui
swagger.show: true
Swagger 配置类-SwaggerConfig
package com.test.invoice.config;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyName;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.configuration.ObjectMapperConfigured;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import javax.annotation.PostConstruct;
import java.lang.annotation.Annotation;
/**
* Swagger 配置类
*
* @author:
* @date: 2020-01-17 16:26
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig extends WebMvcConfigurerAdapter {
@Value("${swagger.show}")
private boolean enableSwagger;
@Autowired
private ApplicationContext applicationContext;
@PostConstruct
public void setObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
objectMapper.registerModule(module);
JacksonAnnotationIntrospector jacksonAnnotationIntrospector= new JacksonAnnotationIntrospector();
objectMapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
@Override
public boolean isAnnotationBundle(Annotation ann) {
if (ann.annotationType() == JSONField.class) {
return true;
}
return super.isAnnotationBundle(ann);
}
@Override
public PropertyName findNameForSerialization(Annotated a) {
PropertyName nameForSerialization = super.findNameForSerialization(a);
if (nameForSerialization == null || nameForSerialization == PropertyName.USE_DEFAULT) {
JSONField jsonField = _findAnnotation(a, JSONField.class);
if (jsonField != null) {
return PropertyName.construct(jsonField.name());
}
}
return nameForSerialization;
}
@Override
public PropertyName findNameForDeserialization(Annotated a) {
PropertyName nameForDeserialization = super.findNameForDeserialization(a);
if (nameForDeserialization == null || nameForDeserialization == PropertyName.USE_DEFAULT) {
JSONField jsonField = _findAnnotation(a, JSONField.class);
if (jsonField != null) {
return PropertyName.construct(jsonField.name());
}
}
return nameForDeserialization;
}
});
ObjectMapperConfigured objectMapperConfigured = new ObjectMapperConfigured(applicationContext, objectMapper);
applicationContext.publishEvent(objectMapperConfigured);
}
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).groupName("framework").enable(enableSwagger)
.genericModelSubstitutes(DeferredResult.class).useDefaultResponseMessages(false)
.forCodeGeneration(false).pathMapping("/").apiInfo(apiInfo()).select()
.apis(RequestHandlerSelectors.basePackage("com.test.invoice.control")).build();
//com.test.invoice.controller
//package com.foreign.payment.web.controller
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("应用框架").description("平台接口文档").termsOfServiceUrl("")
.contact("测试项目").version("1.0").build();
}
}
MVC 配置类-WebMVCConfig
package com.test.invoice.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* Created by on 2019/4/9.
* 是因为swagger-ui.html 是在springfox-swagger-ui.jar里的,因为修改了路径Spring Boot不会自动把/swagger-ui.html这个路径映射到对应的目录META-INF/resources/下面。
*所以我们修改springboot配置类,为swagger建立新的静态文件路径映射就可以了
*原文:https://blog.csdn.net/weixin_39168678/article/details/80563402
*/
@Configuration
public class WebMVCConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
测试类-TRbtTestController
package com.test.invoice.controller;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.test.invoice.data.TRbtTestData;
import com.test.invoice.data.TRbtTestDataParam;
import com.test.invoice.enums.ResponseCode;
import com.test.invoice.model.TRbtTestEntity;
import com.test.invoice.model.TRbtUserEntity;
import com.test.invoice.model.base.SuperEntity;
import com.test.invoice.service.consumer.TRbtTestConsumer;
import com.test.invoice.util.*;
import com.test.invoice.vo.LoginRequestVO;
import com.test.invoice.vo.ResponseVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.POST;
import java.net.URLEncoder;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 消费者,通过 Feign-api 调用
*
* @author:
* @version:
* @date: 2019-08-12 14:49
*/
@RestController
@Api(description = "测试框架系统控制类")
@RequestMapping("/Inv/Api")
@Slf4j
public class TRbtTestController {
/**
* ClassToJson
* @param data TRbtTestData
* @param convertType 1:使用 GSON 转换,2:使用 alibaba.fastjson 转换
* @return JsonString
*/
@PostMapping("/Test/ClassToJson")
@ApiOperation(value = "类转 Json",httpMethod = "POST",response = ResponseVO.class,notes="类转 Json")
public ResponseVO ClassToJson(@RequestBody TRbtTestData data,@RequestParam(value="convertType",required = true) String convertType)
{
ResponseVO responseVO = new ResponseVO<String>();
if (convertType.equals("1")){
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.create();
responseVO.setData(gson.toJson(data));
}else if (convertType.equals("2")){
responseVO.setData(JSON.toJSONString(data));
}
responseVO.setCode(ResponseCode.OK.value());
return responseVO;
}
/**
* WebClientTest
* @return String
*/
@PostMapping("/Test/PinYin")
@ApiImplicitParams({@ApiImplicitParam(paramType = "header", name = "Authorization", value = "凭证", required = true, dataType = "string"),})
public ResponseVO PinYin(){
ResponseVO responseVO = new ResponseVO<String>();
CharacterPinYinConvert convert = new CharacterPinYinConvert();
responseVO.setData(convert.toPinYin("罗马")+"======"+convert.toPinYins("一加一的和等于二"));
return responseVO;
}
}
测试
浏览器访问 http://localhost:8080//swagger-ui.html
测试-ClassToJson
测试-PinYin
Spring Boot3-OpenAPI 3 与 Swagger
整合
- 导入场景
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.1.0</version>
</dependency>
- 配置
# /api-docs endpoint custom path 默认 /v3/api-docs
springdoc.api-docs.path=/api-docs
# swagger 相关配置在 springdoc.swagger-ui
# swagger-ui custom path
springdoc.swagger-ui.path=/swagger-ui.html
springdoc.show-actuator=true
使用
1.常用注解
注解 | 标注位置 | 作用 |
---|---|---|
@Tag | controller 类 | 标识 controller 作用 |
@Parameter | 参数 | 标识参数作用 |
@Parameters | 参数 | 参数多重说明 |
@Schema | model 层的 JavaBean | 描述模型作用及每个属性 |
@Operation | 方法 | 描述方法作用 |
@ApiResponse | 方法 | 描述响应状态码等 |
2.Docket配置
如果有多个 Docket,配置如下
@Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("springshop-public")
.pathsToMatch("/public/**")
.build();
}
@Bean
public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder()
.group("springshop-admin")
.pathsToMatch("/admin/**")
.addMethodFilter(method -> method.isAnnotationPresent(Admin.class))
.build();
}
如果只有一个 Docket,可以配置如下
springdoc.packagesToScan=package1, package2
springdoc.pathsToMatch=/v1, /api/balance/**
3.OpenAPI 配置
@Bean
public OpenAPI springShopOpenAPI() {
return new OpenAPI()
.info(new Info().title("SpringShop API")
.description("Spring shop sample application")
.version("v0.0.1")
.license(new License().name("Apache 2.0").url("http://springdoc.org")))
.externalDocs(new ExternalDocumentation()
.description("SpringShop Wiki Documentation")
.url("https://springshop.wiki.github.org/docs"));
}
Springfox 迁移
1.注解变化
原注解 | 现注解 | 作用 |
---|---|---|
@Api | @Tag | 描述 Controller |
@ApiIgnore | @Parameter(hidden = true) @Operation(hidden = true) @Hidden |
描述忽略操作 |
@ApiImplicitParam | @Parameter | 描述参数 |
@ApiImplicitParams | @Parameters | 描述参数 |
@ApiModel | @Schema | 描述对象 |
@ApiModelProperty(hidden = true) | @Schema(accessMode = READ_ONLY) | 描述对象属性 |
@ApiModelProperty | @Schema | 描述对象属性 |
@ApiOperation(value = “foo”, notes = “bar”) | @Operation(summary = “foo”, description = “bar”) | 描述方法 |
@ApiParam | @Parameter | 描述参数 |
@ApiResponse(code = 404, message = “foo”) | @ApiResponse(responseCode = “404”, description = “foo”) | 描述响应 |
2.Docket 配置
(1)以前写法
@Bean
public Docket publicApi() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("org.github.springshop.web.public"))
.paths(PathSelectors.regex("/public.*"))
.build()
.groupName("springshop-public")
.apiInfo(apiInfo());
}
@Bean
public Docket adminApi() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("org.github.springshop.web.admin"))
.paths(PathSelectors.regex("/admin.*"))
.apis(RequestHandlerSelectors.withMethodAnnotation(Admin.class))
.build()
.groupName("springshop-admin")
.apiInfo(apiInfo());
}
(2)新写法
@Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("springshop-public")
.pathsToMatch("/public/**")
.build();
}
@Bean
public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder()
.group("springshop-admin")
.pathsToMatch("/admin/**")
.addOpenApiMethodFilter(method -> method.isAnnotationPresent(Admin.class))
.build();
}
(3)添加 OpenAPI 组件
@Bean
public OpenAPI springShopOpenAPI() {
return new OpenAPI()
.info(new Info().title("SpringShop API")
.description("Spring shop sample application")
.version("v0.0.1")
.license(new License().name("Apache 2.0").url("http://springdoc.org")))
.externalDocs(new ExternalDocumentation()
.description("SpringShop Wiki Documentation")
.url("https://springshop.wiki.github.org/docs"));
}