SpringBoot 中接口规范
本文主要记录,我在学习使用 SpringBoot 开发接口的一些技巧
一、接口请求参数方式
目前主流的请求参数有两种:form-urlencoded 和 application/json(大佬推荐),两种不同的方式 JAVA 端读取参数的方式略有不同
1. form-urlencoded
Content-Type:application/x-www-form-urlencoded
参数类型,主要是通过:键值对传递,例如:username: 'zZ爱吃菜'
@RequestParam("key")
public void register(@RequestParam String username) {
// 推荐使用日志模式输入内容
log.info("username={}", username);
}
// 接受参数的方式
public void register(@RequestParam String username) {
// 推荐使用日志模式输入内容
log.info("username={}", username);
}
// 参数重命名, 同上
public void register(@RequestParam("username") String userName) {
// 推荐使用日志模式输入内容
log.info("username={}", userName);
}
// 直接将参数复制给一个对象,前提参数名与对象属性名对应
public void register(User user) {
// 推荐使用日志模式输入内容
log.info("username={}", user.getUsername());
}
2. application/json
Content-Type:application/json
参数形式: json 形式
{
"username": "zZ 爱吃菜",
"password": "123123"
}
java 接收,需要增加 @RequestBody 注解,来接收
// 如果是 Post 方式 Content-Type: application/json,则使用@RequestBody 方式接收参数
public void register(@RequestBody User user) {
// 推荐使用日志模式输入内容
log.info("username={}", user.toString());
}
二、接口返回
在制作接口通常都是放回 JSON 数据给前端
SpringBoot 有专门的注解返回 JSON
以前:@Controller + @ResponseBody
现在:@RestController 即可
1. 常规返回标准化接口数据
通常的标准化接口数据格式如下
// 正确返回值
{
"status": 1,
"msg": "***操作成功",
"data": {
"key": "value",
...
}
}
// 专属错误返回值
{
"status": 2,
"msg": "用户未登录,请先登录"
}
// 错误返回值(通用)
{
"status": -1,
"msg": "系统错误"
}
2.实现标准版接口返回数据
在项目下,创建 vo 包(view object缩写),创建 ResponseVo.java 类
构建通用的 接口返回内容模块,另外需要跟 接口返回状态 status 对应的枚举匹配
package com.imooc.mall.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.imooc.mall.enums.ResponseEnum;
import lombok.Data;
@Data
//@JsonSerialize
// 让 json 接口返回的 null 隐藏掉
@JsonInclude(value = JsonInclude.Include.NON_NULL)
public class ResponseVo<T> {
private Integer status;
private String msg;
// data 数据不确定,所以使用 泛型
private T data;
public ResponseVo(Integer status, String msg) {
this.status = status;
this.msg = msg;
}
// 指定msg 成功返回结果
public static <T> ResponseVo<T> success(String msg) {
return new ResponseVo(ResponseEnum.SUCCESS.getCode(), msg);
}
// 默认成功返回结果
public static <T> ResponseVo<T> success() {
return new ResponseVo(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getDesc());
}
// 错误内容返回
public static <T> ResponseVo<T> error(ResponseEnum responseEnum) {
return new ResponseVo(responseEnum.getCode(), responseEnum.getDesc());
}
}
接口状态枚举累 ResponseEnum
注意:枚举状态中的状态码,没必要跟我一致,我这里也是根据课程内容写的,具体开发阶段还是需要自己跟前端开发工程师去沟通。因为接口是给前端交互的,而不是满足自己的。
package com.imooc.mall.enums;
import lombok.Getter;
@Getter
public enum ResponseEnum {
ERROR(-1, "服务端错误"),
SUCCESS(0, "成功"),
PASSWORD_ERROR(1, "密码错误"),
USER_EXIST(2, "用户 已存在"),
NEED_LOGIN(10, "用户未登录,请先登录"),
;
Integer code;
String desc;
ResponseEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
}
最近简单在 Controller 中调用一下
// 实现接口返回 JSON 格式
@RestController
// 定义 Controller 请求路径
@RequestMapping("/user")
// 日志打印注解,开发阶段很需要
@Slf4j
public class UserController {
@PostMapping(value = "/register")
public ResponseVo register(@RequestBody User user) {
// 推荐使用日志模式输入内容
log.info("username={}", user.toString());
//return ResponseVo.success();
return ResponseVo.error(NEED_LOGIN);
}
}
三、表单验证
一般接口都会接收参数,那么我们需要怎么校验没个参数是否接收到呢?
答:为每个接口增加一个表单类,用来控制每个参数是否为必须参数
以用户注册为例
1. 创建一个表单类
通常表单类,放在 form 包下: src/main/java/工程名/form/
注意:如下注解对应不同字段即不同参数的要求。
@NotBlank // 用户 String 判断空格 @NotEmpty // 用于集合 @NotNull // 参数不能为 null , 可以为空字符串
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class UserForm {
// @NotBlank 用户 String 判断空格
// @NotEmpty // 用于集合
// @NotNull
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "用户密码不能为空")
private String password;
@NotBlank(message = "用户邮箱不能为空")
private String email;
}
2. Controller 校验
重点:@Valid + BindingResult 实现对 UserForm 接口参数表单内容的校验
UserForm 来接收 接口发来的参数(@RequestBody 中的参数),@Valid 注解帮助我们获取到 UserForm 参数是否符合要求,通过 BindingResult 输出是否有错误
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Autowired
private IUserService userService;
@PostMapping(value = "/register")
public ResponseVo register(@Valid @RequestBody UserForm userForm,
BindingResult bindingResult) {
// 表单校验
if (bindingResult.hasErrors()) {
log.error("注册提交的参数有误, {} {}",
bindingResult.getFieldError().getField(),
bindingResult.getFieldError().getDefaultMessage());
return ResponseVo.error(PARAM_ERROR, bindingResult);
}
User user = new User();
BeanUtils.copyProperties(userForm, user);
return userService.register(user);
}
}
方法 | 描述 |
---|---|
bindingResult.getFieldError().getField() | 获取不符合要求的字段 |
bindingResult.getFieldError().getDefaultMessage() | 输入字段描述的错误信息, 对应 @NotBlank(message = "用户名不能为空") |
3. 最后重载一下 ResponseVo 的 error 方法,
实现自动对 bindingResult 输出
public static <T> ResponseVo<T> error(ResponseEnum responseEnum, BindingResult bindingResult) {
return new ResponseVo(responseEnum.getCode(), responseEnum.getDesc() + ":" + Objects.requireNonNull(bindingResult.getFieldError()).getField() + " " + bindingResult.getFieldError().getDefaultMessage());
}
4.最终错误参数接口输出结果
{
"status": 4,
"msg": "邮箱已存在"
}
总结
不论接口参数多少,都可以统一在接口参数表单类里进行设置 判断条件(通过注解)并设置默认错误信息,然后即可以统一使用 @Valid + BindingResult + ResponseVo 输出错误结果,提高代码的简洁度和可读性,降低代码的耦合度。
四、运行时错误,统一输出 JSON
在请求结果执行的过程中如果返现 throw new RuntimeException(“****”); 不可控的错误时,该如何友好的让前端获取到呢?这就需要查看一下如真的有异常输出,前端获取的 json 格式是怎么样的。
1. 模拟一个运行时错误
加入我们在 Service 层增加一个抛出 RuntimeException 的方法,并被调用。
// 模拟一个异常操作,然后输出错误
private void testError() {
throw new RuntimeException("意外错误");
}
@Override
public ResponseVo register(User user) {
// 测试异常
testError();
/// 下面时注册逻辑;
}
接口返回的结果
{
"timestamp": "2020-04-21T14:51:21.840+0000",
"status": 500,
"error": "Internal Server Error",
"message": "意外错误",
"path": "/user/register"
}
看看吧,接口返回的内容 跟 我们与前端沟通的格式不同,前端同事会崩溃的~
2.通过设置异常处理类统一返回结果
创建一个 exception 包,并创建 RuntimeExceptionHandle 类 专门处理这些异常错误,并统一返回 json 格式
我的理解,这是通过拦截异常错误,获取错误内容,自己结合 ResponseVo 返回我们约定的 json 格式
@ControllerAdvice
public class RuntimeExceptionHandle {
// 通过捕获异常,将运行时异常的输出与我们通用 API 返回 JSON 格式统一起来
@ExceptionHandler(RuntimeException.class)
@ResponseBody // 用于返回 json 数据
//@ResponseStatus(HttpStatus.ACCEPTED) // 控制返回状态码
public ResponseVo handle(RuntimeException e) {
return ResponseVo.error(ResponseEnum.ERROR, e.getMessage());
}
}
返回的 json 数据
{
"status": -1,
"msg": "服务端错误:意外错误"
}
五、Session 和 Cookie
在 Controller 中增加 HttpSession session 参数即可获得,session 对象,通过 session.setAttribute(key, value) session.getAttribute(key) 方式 设置session 和 读取。
session 和 cookie 的关系
- session 存储在服务器,session.getId() 同 下面 cookie 对应
- cookie 存储在客户端,内容为:JSESSIONID=0E245BE27CB3AE565CA68F004879ED9D
- cookie 存储注意 跨域问题,即 domain, 比如 localhost 和 127.0.0.1 发同一个请求, JSESSIONID 是不同的
未完待续
- session + cookie
- 接口统一校验 等
最后说两句
其实已经开发了很多年了,不过都是其他技术栈(PHP+iOS+Flutter),这次在使用 SpringBoot 开发过程中,发现通过相对规范的方式开发系统,其实不会增加代码量和工作量,而且可以使项目更容易理解与维护。