SpringMVC控制层的请求和应答
本文介绍Spring MVC中控制层接收请求参数,和返回应答数据的规范。
请求
控制层暴露的接口必须按照以上定义设置请求方法。不允许直接使用RequestMapping
,原则上每个接口只允许支持一种请求方法 (例外的情况:可能第三方回调不规范的情况下会有特殊要求)。如果接口用于第三方回调,应按照第三方回调的要求开发。
所有参数应该定义为参数本身的类型,不允许一切参数都使用 String
类型接收。通过对 Spring 的扩展,可以优雅的处理对 Boolean
、LocalDate
、LocalDateTime
的类型转换。
不允许使用 Long
类型或 String
类型替代日期或日期时间类型。
@Schema(description = "用户信息")
@Data
public class UserAddDto {
/**
* 用户名
*/
@NotBlank(message = "用户名不能为空")
@Schema(description = "用户名", required = true)
private String username;
/**
* 密码
*/
@NotBlank(message = "密码不能为空")
@Schema(description = "密码", required = true)
private String password;
}
对于 POST、PUT、PATCH 接口,应答使用 requestBody
方式接收参数,也即需要要求前端提交请求的Content-Type
类型为 application/json
。后端通过 @RequestBody
注解解析参数。
UserController#add
@PostMapping("/add")
@Operation(summary = "新增用户信息")
public ResponseBean<String> add(@Valid @RequestBody UserAddDto addDto) {
this.userService.insert(addDto);
return ResponseBean.success();
}
@Valid
注解用户数据验证,请参考《接口参数验证》章节。
原则上一个请求对应一个DTO(如果接口功能类似,且参数相同,可以共用),不允许一个大的DTO接收很多方法的参数。DTO可以通过继承来避免重复代码。如下
@Schema(description = "用户信息")
@Data
public class UserUpdateDto extends UserAddDto {
/**
* 用户ID
*/
@NotBlank(message = "用户ID不能为空")
@Schema(description = "用户ID", required = true)
private String userId;
}
应答
应答格式
- 成功(非分页)
- 失败(非分页)
- 成功(分页)
- 失败(分页)
{
"success": true,
"status": 200,
"data": {
"xxx": "xxx"
}
}
{
"success": false,
"status": 401,
"errorCode": "AUTH_ERR_401",
"message": "用户未登录,请登录后重试"
}
{
"success": true,
"status": 200,
"page": 1,
"pageSize": 15,
"total": 38,
"totalPage": 3,
"data": [
{
"xxx": "xxx"
}
]
}
{
"success": false,
"status": 401,
"errorCode": "AUTH_ERR_401",
"message": "用户未登录,请登录后重试"
}
应答结果应采用泛型封装,全系统通用。
- BaseResponseBean.java
- ResponseBean.java
- PageResponseBean.java
@Schema(name = "应答信息")
@NoArgsConstructor
@Data
public abstract class BaseResponseBean {
/**
* 业务处理是否成功
*/
@Schema(title = "业务处理是否成功", description = "业务处理是否成功", example = "true")
private boolean success;
/**
* 真实的Http状态码,默认200
*
* <p>如果是业务异常,应该为200,因为一般情况下业务异常是当处理业务时因为某些条件不满足或者数据验证未通过等情况,这时候返回的应是给用户
* 友好的提示信息,待用户进一步修改后重新提交
* </p>
*
* <p>针对404、401、403以及其他的非业务异常,一般建议返回对应的真实的状态码</p>
*
* <p>此字段为一个参考字段,非必要,核心字段应该为 {@link #errorCode}</p>
*/
@Schema(title = "真实的Http状态码", description = "真实的Http状态码", example = "200")
private int status = 200;
/**
* 错误编码
*/
@Schema(description = "错误编码", example = "ERR_1001")
private String errorCode;
/**
* 消息(成功消息或失败消息)
*/
@Schema(description = "消息", example = "Token失效,请重新登录")
private String message;
/**
* 错误详情
*/
@Schema(description = "错误详情")
private Object errorDetails;
protected BaseResponseBean(boolean success, int status) {
this.success = success;
this.status = status;
}
protected BaseResponseBean(String errorCode, String message) {
this.errorCode = errorCode;
this.message = message;
this.success = false;
}
}
@Schema(description = "应答数据")
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Data
public class ResponseBean<T> extends BaseResponseBean<Object> {
/**
* 应答数据
*/
@Schema(description = "应答数据")
private T data;
public ResponseBean(T data) {
super(true, HttpStatus.OK.value());
this.data = data;
}
public ResponseBean(boolean success, int status) {
super(success, status);
}
public ResponseBean(boolean success, int status, T data) {
super(success, status);
this.data = data;
}
public ResponseBean(String errorCode, String message) {
super(errorCode, message);
}
public ResponseBean(String errorCode, String message, T data) {
super(errorCode, message);
this.data = data;
}
public static <T> ResponseBean<T> success() {
return new ResponseBean<>(true, HttpStatus.OK.value());
}
public static <T> ResponseBean<T> success(T data) {
return new ResponseBean<>(data);
}
public static <T> ResponseBean<T> failure() {
return new ResponseBean<>(false, HttpStatus.INTERNAL_SERVER_ERROR.value());
}
public static <T> ResponseBean<T> failure(String message) {
ResponseBean<T> responseBean = failure();
responseBean.setMessage(message);
return responseBean;
}
public static <T> ResponseBean<T> failure(String errorCode, String message) {
ResponseBean<T> responseBean = failure(message);
responseBean.setErrorCode(errorCode);
return responseBean;
}
}
@Schema(title = "分页应答数据")
@EqualsAndHashCode(callSuper = true)
@Data
public class PageResponseBean<T> extends BaseResponseBean<Object> implements PageInfo {
/**
* 应答数据
*/
@Schema(description = "应答数据")
private List<T> data;
/**
* 总记录数
*/
@Schema(description = "总记录数")
private Long total;
/**
* 当前页码
*/
@Schema(description = "当前页码")
private Long page;
/**
* 每页记录数
*/
@Schema(description = "每页记录数")
private Long pageSize;
/**
* 总页数
*/
@Schema(description = "总页数")
private Long totalPage;
public PageResponseBean() {
}
public PageResponseBean(boolean success, int status) {
super(success, status);
}
public PageResponseBean(String errorCode, String message) {
super(errorCode, message);
}
public static <T> PageResponseBean<T> success() {
return new PageResponseBean<>(true, HttpStatus.OK.value());
}
public static <T> PageResponseBean<T> success(Long page, Long pageSize, Long total, List<T> data) {
PageResponseBean<T> responseBean = success();
responseBean.setPage(page);
responseBean.setPageSize(pageSize);
responseBean.setTotal(total);
responseBean.setData(data);
responseBean.setTotalPage((total + pageSize - 1) / pageSize);
return responseBean;
}
public static <T> PageResponseBean<T> failure() {
return new PageResponseBean<>(false, HttpStatus.INTERNAL_SERVER_ERROR.value());
}
public static <T> PageResponseBean<T> failure(String message) {
PageResponseBean<T> responseBean = failure();
responseBean.setMessage(message);
return responseBean;
}
public static <T> PageResponseBean<T> failure(String errorCode, String message) {
PageResponseBean<T> responseBean = failure(message);
responseBean.setErrorCode(errorCode);
return responseBean;
}
/**
* 返回成功状态,返回分页信息
*
* @param page 分页
* @param <T> 数据类型
* @return 响应数据包
*/
public static <T> PageResponseBean<T> success(IPage<T> page) {
PageResponseBean<T> responseBean = createPageInfo(page);
responseBean.setSuccess(true);
return responseBean;
}
/**
* 返回成功状态,返回分页信息
*
* @param page 分页
* @param extension 扩展数据
* @param <T> 数据类型
* @return 响应数据包
*/
public static <T> PageResponseBean<T> success(IPage<T> page, Object extension) {
PageResponseBean<T> responseBean = createPageInfo(page);
responseBean.setExtension(extension);
responseBean.setSuccess(true);
return responseBean;
}
public static <T> PageResponseBean<T> failure(ErrorInfo errorInfo) {
return new PageResponseBean<>(errorInfo);
}
/**
* 创建PageResponseBean
*
* @param page 分页数据
* @param <T> 输出数据类型
* @return 响应数据
*/
private static <T> PageResponseBean<T> createPageInfo(IPage<T> page) {
//List<R>
PageResponseBean<T> responseBean = new PageResponseBean<>();
responseBean.setData(page.getRecords());
responseBean.setPage(page.getCurrent());
responseBean.setPageSize(page.getSize());
responseBean.setTotal(page.getTotal());
responseBean.setTotalPage(page.getPages());
return responseBean;
}
}
以上代码可在开源项目butterfly 中获取。在使用时必须指定泛型类型。如果没有返回值,则设置泛型类型为:Void
。
应答数据的返回应遵循以下原则:
- 应答数据应返回Vo,不允许直接将Model、Dto、Bo等实体类返回前端。
- 不允许在service层返回 ResponseBean
示例
- UserService.java
- UserController.java
public class UserService {
/**
* 通过ID获取用户信息详情
*
* @param userId 主键
* @return 实例对象
*/
UserVo getById(String userId);
/**
* 分页查询用户信息
*
* @param query 查询条件和分页参数
* @return 分页信息及数据列表
*/
Page<UserListVo> findByPage(UserListQuery query)
}
@RestController
@RequestMapping("/api/user")
public class UserController {
private final UserService userService;
public UserController(UserService userService){
this.userService = userService;
}
@GetMapping("/get/{userId}")
public ResponseBean<UserVo> getById(@PathVariable String userId) {
UserVo userVo = this.userService.getById(userId);
return ResponseBean.success(userVo);
}
@GetMapping("/findByPage")
public PageResponseBean<UserListVo> findByPage(@Valid UserListQuery query) {
Page<UserListVo> pageList = this.userService.findByPage(query);
return PageResponseBean.success(pageList);
}
}