Spring boot集成swagger
前面一片文章在介绍时有提到过swagger,这篇文章就重点介绍一下Spring boot集成swagger的方法与代码实践。
引入依赖
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-boot-starter</artifactId> <version>3.0.0</version> </dependency>
如截图所示,springfox-boot-starter依赖提供了swagger与swagger-ui以及一些必要非必要的依赖。
yml文件
spring: # 新增该配置处理程序启动swagger路径查找报错 mvc: path-match: matching-strategy: ant_path_matcher server: port: 8080 swagger: enable: true application-name: ${spring.application.name} application-version: 1.0 application-description: ***平台接口文档 try-host: http://localhost:${server.port}
Swagger配置文件
@Data @Component @ConfigurationProperties("swagger") public class SwaggerProperties { /** * 是否开启swagger,生产环境一般关闭,所以这里定义一个变量 */ private Boolean enable; /** * 项目应用名 */ private String applicationName; /** * 项目版本信息 */ private String applicationVersion; /** * 项目描述信息 */ private String applicationDescription; /** * 接口调试地址 */ private String tryHost; }
swagger配置
@EnableOpenApi @Configuration public class SwaggerConfig implements WebMvcConfigurer { private final SwaggerProperties swaggerProperties; private final RepeatSubmitInterceptor repeatSubmitInterceptor; /** * 首页地址 */ @Value("${shiro.user.indexUrl}") private String indexUrl; public SwaggerConfig(SwaggerProperties swaggerProperties, RepeatSubmitInterceptor repeatSubmitInterceptor) { this.swaggerProperties = swaggerProperties; this.repeatSubmitInterceptor = repeatSubmitInterceptor; } @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("forward:" + indexUrl); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { //swagger配置 registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/"); } @Bean public Docket createRestApi() { return new Docket(DocumentationType.OAS_30).pathMapping("/") // 定义是否开启swagger,false为关闭,可以通过变量控制 .enable(swaggerProperties.getEnable()) // 将api的元信息设置为包含在json ResourceListing响应中。 .apiInfo(apiInfo()) // 接口调试地址 .host(swaggerProperties.getTryHost()) // 选择哪些接口作为swagger的doc发布 .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) .build() .globalRequestParameters( singletonList(new springfox.documentation.builders.RequestParameterBuilder() .name("Authentication") .description("token") .in(ParameterType.HEADER) .required(true) .query(q -> q.model(m -> m.scalarModel(ScalarType.STRING))) .build())) // 支持的通讯协议集合 .protocols(newHashSet("https", "http")); } /** * API 页面上半部分展示信息 */ private ApiInfo apiInfo() { return new ApiInfoBuilder().title(swaggerProperties.getApplicationName() + " Api Doc") .description(swaggerProperties.getApplicationDescription()) .version("Application Version: " + swaggerProperties.getApplicationVersion() + ", Spring Boot Version: " + SpringBootVersion.getVersion()) .build(); } @SafeVarargs private <T> Set<T> newHashSet(T... ts) { if (ts.length > 0) { return new LinkedHashSet<>(Arrays.asList(ts)); } return Collections.emptySet(); } /** * 通用拦截器排除swagger设置,所有拦截器都会自动加swagger相关的资源排除信息 */ @SuppressWarnings("unchecked") @Override public void addInterceptors(@NonNull InterceptorRegistry registry) { try { registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); Field registrationsField = FieldUtils.getField(InterceptorRegistry.class, "registrations", true); List<InterceptorRegistration> registrations = (List<InterceptorRegistration>) ReflectionUtils.getField(registrationsField, registry); if (registrations != null) { for (InterceptorRegistration interceptorRegistration : registrations) { interceptorRegistration .excludePathPatterns("/**/swagger**/**") .excludePathPatterns("/**/webjars/**") .excludePathPatterns("/**/v3/**") .excludePathPatterns("/**/doc.html"); } } } catch (Exception e) { e.printStackTrace(); } } }
启动类上加上注解@EnableSwagger2
swagger核心的注解就@ApiModel、@ApiModelProperty、@Api、@ApiOperation,@ApiParam() 、@ApiIgnore()这几个,下面详细介绍如何使用
Domain层
@Data @ApiModel(value = "property", description = "模型属性") //apiModel说明该domain的整体数据类型/内容 @TableName(value = "t_property", autoResultMap = true) //autoResultMap = true主要针对数据库内json/jsonb格式数据做映射关系,防止相应数据查询时返回null,此处不配置默认false. //也可以再xml中自定义resultMap public class Property implements Serializable { /** * 主键 */ @TableId @JsonFormat(shape = JsonFormat.Shape.STRING) //pgsql数据库使用的内置主键策略,类型为long,但前后端交互过程会出现精度丢失, //故(反)序列化是转换成string private Long id; /** * 属性key */ @ApiModelProperty(value = "属性key", name = "key", example = "key1", required = true) //ApiModelProperty主要作用于参数上 // value为字段的中文含义 name为对应key(可忽略) // example为该字段的value示例 required标识该字段必传 @NotNull(message = "属性KEY不能为空") //个人习惯针对api创建不同的domain,故针对必传字段使用@NotNull配合controller层的@Validated注解进行判空。 //还有一种方案是 在业务层进行非空判断。 根据个人开发情况而定吧 private String key; /** * 数据类型 */ @ApiModelProperty(value = "数据类型", name = "dataType", example = "enum", required = true) @NotNull(message = "属性数据类型不能为空") private String dataType; /** * 属性名称 */ @ApiModelProperty(value = "数据类型", name = "dataType", example = "name1", required = true) @NotNull(message = "属性名称不能为空") private String name; /** * 数据描述 */ @TableField(typeHandler = FastjsonTypeHandler.class) //针对数据库内json/josnb格式数据需要进行类型处理,这里直接使用的第三方提供的handler,也可以自定义handler private JSONObject dataInfo; /** * 标签 */ @TableField(typeHandler = FastjsonTypeHandler.class) private JSONArray tag; /** * 创建时间 */ @ApiModelProperty(hidden = true) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") //针对时间类型个人偏好java8新增的LocalDateTime,涉及国际化时间问题换成java8的ZonedDateTime即可,ZonedDateTime+ZoneId处理时区。 private LocalDateTime createTime; /** * 创建人 */ @ApiModelProperty(hidden = true) //hidden表示该字段不在文档中展示 private String createBy; /** * 更新人 */ @ApiModelProperty(hidden = true) private String updateBy; /** * 更新时间 */ @ApiModelProperty(hidden = true) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private LocalDateTime updateTime; /** * 单位 */ @NotNull(message = "属性单位不能为空") private String unit; /**状态 1-启用 0-禁用**/ @ApiModelProperty(value = "属性状态(1-启用 0-禁用)", name = "status", example = "1") private Integer status; /**读写类型 1-只读 2-只写 3-读写**/ private Integer rwType; /** * 操作人 **/ @TableField(exist = false) //该注解表示在数据库不存在,不会进行持久化,但可以参与序列化,相似的注解还有@Transient private String username; private static final long serialVersionUID = 1L; }
controller层
@Api(value="模型管理") //介绍整个controler整体的业务范围 @RestController @RequestMapping("/module/detail") public class ModuleDetailController { @Resource ModuleService moduleService; /** * 模型新增 * @param module 实体 * @return com.hd.uws.result.Res<?> * @author Jackpot * @date 2022/5/27 09:15 */ @ApiOperation(value = "新增模型")
//@ApiIgnore() 用于类或者方法上,可以不被swagger显示在页面上
//介绍接口的用途 @PostMapping("add") //postMapping表示该接口为post请求 public Res<?> add(@Validated @RequestBody Module module) { //@Validated结合domain内的判空、数据格式化做校验 @RequestBody表示该接口为application/json格式,请求参数放在body内。 //RequestBody 接收的是请求体里面的数据;而RequestParam接收的是key-value 里面的参数 return Boolean.TRUE.equals(moduleService.add(module)) ? Res.ok() : Res.code(ResCode.OPERATION_FAILED); } }
启动程序,最终效果,地址格式也很简单http://ip:port/swagger-ui/index.html。
注:如果程序中有使用shiro等权限控制,需要对swagger-UI及api-docs路径进行anon
当然如果你觉得swagger页面看起来不友好,也可以配合着apifox使用,数据源URL就是swagger UI页面上的**/v3/api-docs
下面是apifox导入swagger api的效果。图1为所有的接口及数据模型(DTO/VO/ENUM等),图2为接口的详细信息,可以直接输入参数进行模拟请求(但目前还没发现可进行压力测试的入口,如果想要进行压测,可以使用jmeter或者其他第三方(个人推荐locust))