本文共 10293 字,大约阅读时间需要 34 分钟。
本文主要的内容是:
1、springboot整合Swagger2,如需要看这部分内容可以直接跳到对应章节 2、讨论swagger的使用规范,以及一些最佳实践。 认真看完,你会有收获的swagger版本是:2.9.2(不同版本UI界面有可能不同)
swagger2和1,因为2的版本可能对比1升级比较大,所以叫2,其实还是swagger
先准备基础的知识。
传参一般使用两种方式
键值对
传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"}
只用@Api、@ApiOperation、@ApiParam、@ApiModelProperty就够了
在如下地方使用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 ResponseDTOgetUser(// 当有更长的内容补充时,使用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在任何情况都不可省略
再次强调,required任何情况都不要省略,省略后不直观,参数再长也不过一行
// 单一类型,要用 @RequestParam(required)@ApiOperation("获取用户信息")@GetMapping("/user/get")public ResponseDTOgetUser( @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
@ApiModelProperty(example = "ID", required = true)
// 类上不用注解// 没个字段都必须用 @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 ListprodInfoList; @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 ListprodInfoList; @Data public static class ProdInfo { @ApiModelProperty("商品ID") private String prodId; @ApiModelProperty("商品单价") private Long unitPriceInCent; } }
上面的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
我们知道使用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等众多特性
其实是可以共存多个UI的,实际测试可行。注意有时候会发现其中一个UI能刷出来另外一个不行,可以稍微等一等再试,或者调换下一下两个UI的顺序再试下。总之我遇到过 “其中一个UI可以另外一个不行” 的情况,但是最终发现 “两个UI可以并存”
微服务部署的时候确实比较麻烦,接口很分散,详细可以搜下教程,本文不发散讨论
时不时会出现NumberFormatException异常,这是Swagger的bug,似乎满足这些条件就会出现异常
// 具体的原因是,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; } ...}
没办法解决
说的是这个问题
@ApiOperation("新增订单")@PostMapping("/order/add")public ResponseDTOaddOrder( @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 的两个字段都被标注为必填,但是界面上却显示非必填
演示了springboot结合swagger2的
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)标注:@Configuration 和 @EnableSwagger22)产生一个 Docket 的Bean
@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/