博客
关于我
springboot+Swagger2最佳实践和使用规范
阅读量:402 次
发布时间:2019-03-05

本文共 10293 字,大约阅读时间需要 34 分钟。

springboot+Swagger2最佳实践和使用规范

1. 前言

本文主要的内容是:

1、springboot整合Swagger2,如需要看这部分内容可以直接跳到对应章节
2、讨论swagger的使用规范,以及一些最佳实践。
认真看完,你会有收获的

swagger版本是:2.9.2(不同版本UI界面有可能不同)

swagger2和1,因为2的版本可能对比1升级比较大,所以叫2,其实还是swagger

2. 使用规范

2.1 准备

先准备基础的知识。

传参一般使用两种方式

  • 键值对

  • 传JSON

键值对准确说是 “名值对”,即Content-Type是x-www-form-urlencoded或者form-data的传参方式。

传JSON 的 Content-Type是application/json。注意区分 “传JSON字符串”,两者在请求body的内容不一样

// 传JSON字符串,body内容如下jsonStr={   "name":"Stone"}// 传JSON,如下,没有key的{   "name":"Stone"}

2.2 Swagger使用规范

只用@Api、@ApiOperation、@ApiParam、@ApiModelProperty就够了

  • 禁止不指定请求方法,要指定用GET/POST…,用@RequestMapping不指定的时候会产生大量的垃圾接口
    在这里插入图片描述

在如下地方使用swagger的注解要注意

  • 控制器

    必须注解 @Api(tags = "..."),必须有tags

    @RestController@Api(tags = "用户模块")public class UserController {     ...}

    不想暴露控制器里所有方法,用hiden属性,要隐藏单个接口,在@ApiOperation里使用hidden

  • 方法

    用 @ApiOperation,用value说明接口用途,notes用于更详细的说明。value必出现,notes可选,没有notes时value要省略以保证简洁

    // 要省略value,保证简洁,别 @ApiOperation(value = "获取用户信息")@ApiOperation("获取用户信息")@GetMapping("/user/get")public ResponseDTO
    getUser(// 当有更长的内容补充时,使用notes@ApiOperation(value = "获取用户列表", notes = "如果需要详细的补充描述,在这里写,在value里会让标题很长")@GetMapping("/user/list")public ResponseDTO
    > listUser(
  • 方法入参

    • @ApiParam(value = “…”, required = true|false)

      任何控制器参数必须有这个注解(除HttpServletRequest…外)

      value和required任何情况都不能少(不论required是true是false)

    • @RequestParam(required = true|false)

      若使用该注解,required在任何情况都不可省略

      • 如果是单一类型,使用该注解(非单一类型例如类里有多个字段组成,单一类型就是仅一个类型)
      • 如果是类的类型,无论传键值对还是传JSON,都不能加上(否则swagger不能展类里的多字段)

    再次强调,required任何情况都不要省略,省略后不直观,参数再长也不过一行

    // 单一类型,要用 @RequestParam(required)@ApiOperation("获取用户信息")@GetMapping("/user/get")public ResponseDTO
    getUser( @ApiParam(value = "用户ID", required = true) @RequestParam(required = true) Integer id)// 入参是一个类,禁止用 @RequestParam(required),这个例子是传键值对的@ApiOperation("新增评论(键值对传参方式)")@PostMapping("/comment/operate")public ResponseDTO
    operateComment( @ApiParam(value = "评论信息", required = true) @Valid CommentOperReqDTO commentOperReqDTO)// 入参是个类,禁止用 @RequestParam(required),这个例子是传JSON的@ApiOperation("新增订单")@PostMapping("/order/add")public ResponseDTO
    addOrder( @ApiParam(value = "订单信息", required = true) @Valid @RequestBody OrderReqDTO orderReqDTO)
  • 方法入参DTO

    • 只有类的类型才需要标注

    • DTO类上不需要任何注解,DTO所有字段都需要 @ApiModelProperty

      • 必须指定value和required,任何情况不得省略required,指定required是希望前端同学知道是否必填
      • 某个字段,比如ID,在新增时用不上可不填但是在修改时必填,这种情况让required=false,并且在value中指出什么情况下必填什么时候可选
      • 内嵌其他类的字段也必须使用注解
      • 不要用example,例如 @ApiModelProperty(example = "ID", required = true)
      • 因为入参需要校验参数,有可能还会带jsr303的注解
    // 类上不用注解// 没个字段都必须用 @ApiModelProperty// 内嵌的类 contactInfo 和集合 prodInfoList 也必须用 @ApiModelProperty// @ApiModelProperty 必须包含 value 和 required// 内嵌的类用静态内部类@Datapublic class OrderReq2DTO {         @NotNull    @ApiModelProperty(value = "商品总价", required = true)    private Long totalPriceInCent;    @ApiModelProperty(value = "备注(如果总价大于1万,必须备注)", required = false)    private String memo;    @NotNull    @Valid    @ApiModelProperty(value = "联系信息", required = true)    private ContactInfo contactInfo;        @NotEmpty    @Valid    @ApiModelProperty(value = "商品信息列表", required = true)    private List
    prodInfoList; @Data public static class ContactInfo { @NotBlank @ApiModelProperty(value = "邮寄地址", required = true) private String address; @NotBlank @ApiModelProperty(value = "手机", required = true) private String mobile; } @Data public static class ProdInfo { @ApiModelProperty(value = "商品ID", required = true) @NotBlank private String prodId; @ApiModelProperty(value = "商品单价", required = true) @NotNull @Min(0) private Long unitPriceInCent; }}
  • 方法出参DTO

    • 没DTO的无需标记

    • 有DTO的 ,在DTO里用 @ApiModelProperty("…"),禁止用required属性

    • 由于响应参数无需校验参数,所以不带jsr303注解

    // 类上不需要标记  // 每个字段都必须标记,包括嵌入字段,如这里的 prodInfoList  // 用 @ApiModelProperty("..."),不要用required  @Data  public class OrderRespDTO {           @ApiModelProperty("生成的订单编号")      private String orderNo;        @ApiModelProperty("订单总价")      private Long priceInCent;        @ApiModelProperty("备忘(用户无备忘的时候为空)")      private String memo;        @ApiModelProperty("产品信息列表")      private List
    prodInfoList; @Data public static class ProdInfo { @ApiModelProperty("商品ID") private String prodId; @ApiModelProperty("商品单价") private Long unitPriceInCent; } }

2.3 补充其他

2.3.1 DTO使用规范
  • 不是每个方法都需要将入参封装成DTO类
  • 传JSON必须使用DTO类
  • 传键值对,参数比较少的,不要用DTO类,直接写在控制器的方法里列出
  • 传键值对,如果字段比较多,允许使用DTO类,但不建议,直接平铺到控制器方法更加直观
  • 返回值要封装统一的DTO,如ResponseDTO,一般有code、message、reqTime、data字段
  • 控制器的方法返回值,即丢在ResponseDTO里的data,如果有返回值,建议data使用DTO装起来,例如将UserDTO放在data里
  • 如果控制器的方法返回值ResponseDTO里的data比较简单,也允许使用单一类型
  • DTO分为reqDTO和respDTO,分别对应入参和出参,一般是一对的,不建议DTO继承,禁止控制器不同方法复用同一个DTO禁止DTO里存在没用的字段
  • DTO有符合结构的,建议使用共有的静态内部类,写在同一个文件里比较好找
  • DTO使用lombok,以方便阅读
2.3.2 为什么会有上面的规范

上面的Swagger使用规范是根据实际情形制定出来的。首先使用繁琐将不利于推广,大浪淘沙后,就留下了上述少量的注解。

  • required=…在可以省略的情况下,即使顶着 “IDEA 提示 redundant” 还要坚持 “禁止省略”,为什么?

    为了风格统一,为了直观。否则还需要在脑里反应一会,才知道其默认值。尤其是 @RequestParam默认是true,但是@ApiParam的required默认false,容易搞混

  • 有些地方一定要用 @RequestParam,有些一定不要用,为什么?

    因为 Swagger有bug,对于单一类型的字段,不写会导致它认为是传JSON,但是对于DTO类型的入参,写上之后展不开DTO里面的字段值

  • 要求返回值DTO不要用 @ApiModelProperty的required属性,为什么?

    因为没有意义,不增加开发者的负担。又不是入参,必不必填不重要。如果说你用required表示字段会不会出现,这用法不好。写在DTO里的字段,即使它的值是null,在JSON格式化之后也是定会出现的;如果你说用required表示这个值会不会是null,建议真别这么增加后端工作量,代码一改就变还要维护这个属性值

  • 要求入参DTO不要用example,即 @ApiModelProperty(example="…", required=…),为什么?

    • example的属性,本意是参数示例值,这跟属性的设计不一致

    • 在DTO,名叫A,里面有个List的字段,B是一个类,如果在此字段使用 @ApiModelProperty(example="…", required=…),将不能展开B类里的字段

    • 使用后,提交接口,对于Integer类型的,也会显示成字串,提交失败

    • 会抛出错误,例如遇到下面的用法时(抛出错误不影响使用,但是会在控制台打印出来)

      // 这个明明是数字类型,example却写了字串,会导致AbstractSerializableParameter#getExample()// 抛出格式化错误的异常@ApiModelProperty(example = "售价", required = true)private Integer priceInCent;

Example

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3. Swagger其他

3.1 如何更换UI

我们知道使用Swagger需要引入两个GAV,其中UI的那个是可以替换的。新UI不仅仅是UI上的改变,还有导出md等重要功能。

官方的是io.springfox:springfox-swagger-ui,可以换成别的,注意不要同时存在多个UI,例如

com.github.xiaoymin
swagger-bootstrap-ui
1.9.6

com.github.caspar-chen
swagger-ui-layer
1.1.3

推荐使用bootstrap-ui,这个UI美观、紧凑、还支持全局按照请求路径搜索,导出md等众多特性

3.2 如何共存多套UI

其实是可以共存多个UI的,实际测试可行。注意有时候会发现其中一个UI能刷出来另外一个不行,可以稍微等一等再试,或者调换下一下两个UI的顺序再试下。总之我遇到过 “其中一个UI可以另外一个不行” 的情况,但是最终发现 “两个UI可以并存”

3.3 如何支持微服务

微服务部署的时候确实比较麻烦,接口很分散,详细可以搜下教程,本文不发散讨论

3.4 NumberFormatException 问题

时不时会出现NumberFormatException异常,这是Swagger的bug,似乎满足这些条件就会出现异常

  • 高版本,据说低版本的不会(详细没研究具体版本)
  • 必须是请求参数,响应参数不会
  • 键值对传参方式,传JSON时不会
  • @ApiModelProperty 中没有设置example或者设置成空串
  • @ApiModelProperty 修饰的是数字类型(如Integer、Long等等)
// 具体的原因是,AbstractSerializableParameter#getExample// 发生在 return Long.valueOf(example); 这行// 可以发现,这个方法应该是Swagger页面要展示Example Value,必须获取example的值,当是数字类型的时候,空串被转换时抛出异常// 同时可以看到,即使发生异常,也无关紧要,第一这是展示示例@JsonProperty("x-example")public Object getExample() {       if (example == null) {           return null;    }    try {           if (BaseIntegerProperty.TYPE.equals(type)) {               return Long.valueOf(example);        } else if (DecimalProperty.TYPE.equals(type)) {               return Double.valueOf(example);        } else if (BooleanProperty.TYPE.equals(type)) {               if ("true".equalsIgnoreCase(example) || "false".equalsIgnoreCase(defaultValue)) {                   return Boolean.valueOf(example);            }        }    } catch (NumberFormatException e) {           LOGGER.warn(String.format("Illegal DefaultValue %s for parameter type %s", defaultValue, type), e);    }    return example;}

解决方法

换JAR包,详细见下面的搭建过程已经排除了这个问题了

需要注意的是,2.8.0版本的swagger,不会出现这个问题(经过实际测试了),2.8.0的代码也是`if (example == null)` 这么判断,为什么就不会出现这个问题呢? 暂时不知道为什么@JsonProperty("x-example")public Object getExample() {       if (example == null) {           return null;    }    ...}

3.5 必填参数显示不准确的问题

没办法解决

说的是这个问题

@ApiOperation("新增订单")@PostMapping("/order/add")public ResponseDTO
addOrder( @ApiParam(value = "订单信息", required = true) @Valid @RequestBody OrderReqDTO orderReqDTO)@Datapublic class OrderReqDTO { @NotEmpty @Valid @ApiModelProperty(value = "商品信息列表", required = true) private List
prodInfoList; @NotNull @ApiModelProperty(value = "商品总价", required = true) private Long totalPriceInCent; @ApiModelProperty(value = "备注", required = false) private String memo; @Data public static class ProdInfo { @ApiModelProperty(value = "商品ID", required = true) @NotBlank private String prodId; @ApiModelProperty(value = "商品单价", required = true) @NotNull @Min(0) private Long unitPriceInCent; }}

实际上可以看到 prodInfoList 里的 ProdInfo 的两个字段都被标注为必填,但是界面上却显示非必填

在这里插入图片描述

最初我以为是bootstrap-ui的问题,后来看官方UI也是这样子。
在这里插入图片描述

4. Springboot+Swagger2搭建步骤

演示了springboot结合swagger2的

  1. pom.xml 中添加(尽量用新版本)
io.springfox
springfox-swagger2
2.9.2
io.swagger
swagger-models
io.swagger
swagger-annotations
io.swagger
swagger-models
1.6.0
io.swagger
swagger-annotations
1.6.0
io.springfox
springfox-swagger-ui
2.9.2
  1. 写一个 Swagger2Config 配置类(详细见后面的附录)
1)标注:@Configuration 和 @EnableSwagger22)产生一个 Docket 的Bean
  1. 启动springboot并访问:http://{host}:{port}/swagger-ui.html(使用的ui不同则不同)
  2. 附录:Swagger2Config 类代码,需要修改basePackage,扫描注解产生文档
@Configuration@EnableSwagger2public class Swagger2Config {       @Bean    public Docket docket() {           // basePackage 需要扫描注解生成文档的路径        return new Docket(DocumentationType.SWAGGER_2)                .apiInfo(apiInfo())                .select()                .apis(RequestHandlerSelectors.basePackage("com.wyf.test.swagger2springboot"))                .paths(PathSelectors.any())                .build();    }    private ApiInfo apiInfo() {           return new ApiInfoBuilder()                .title("Swagger demo")                .description("这是展示Swagger怎么用的例子")                .version("1.0").build();    }}

转载地址:http://lgbzz.baihongyu.com/

你可能感兴趣的文章
MYSQL之union和order by分析([Err] 1221 - Incorrect usage of UNION and ORDER BY)
查看>>
Mysql之主从复制
查看>>