前言
在一个后台中会有很多的接口,所有接口返回统一的结果无疑是非常重要。返回结果中无非包括两个方面的信息,状态(是否成功)和数据,状态是必须的,数据不是必须的。如登录接口,返回结果中只有是否登录成功;获取列表信息的接口,则包括是否成功和成功的对应数据列表。
后端返回给前端的统一结果一般用json定义,
{
//状态码
code:200,
//与状态码对应的描述信息
msg:"成功",
//携带的数据,可为空
data:{}
}
如何定义这个结果呢?我们从设计的角度来讲,然后一步一步优化。
将结果封装成对象
将返回值封装成对象,然后在Controller中创建这个对象并返回。
@Data
public class Result{
private Integer code;
private String msg;
private Object data;
}
然后在Controller中
package com.hgt.controller;
import com.hgt.pojo.Result;
import com.hgt.pojo.ResultCode;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(path = "api")
public class HelloController {
@RequestMapping(path = "result")
public Result testResult(){
return new Result(1,"成功",null);
}
}
返回值为:{"code":1,"msg":"成功","data":null}
这种方式也能用,但是缺点也很明显:
- code与msg可随意定义,没有统一标准,出问题后无法查询
- 接口应只专注于业务,每个接口都创建一个Result对象显然不合适
解决第一个问题
我们看一下HTTP请求常见的状态码:
200 - 请求成功
301 - 资源(网页等)被永久转移到其它URL
404 - 请求的资源(网页等)不存在
500 - 内部服务器错误
分类分类描述1**信息,服务器收到请求,需要请求者继续执行操作2**成功,操作被成功接收并处理3**重定向,需要进一步的操作以完成请求4**客户端错误,请求包含语法错误或无法完成请求5**服务器错误,服务器在处理请求的过程中发生了错误
接口返回的状态其实和上面很相似,也是固定的、有限的若干个。
既然是固定的,可以使用枚举类型定义返回值状态
package com.hgt.pojo;
//返回结果的code&message
public enum ResultCode {
// 成功
SUCCESS(1, "成功"),
FAIL(500,"异常"),
// 参数无效
PARAM_IS_INVALID(1001, "无效参数"),
PARAM_IS_BLANK(1002, "参数为空"),
PARAM_IS_TYPE_ERROR(1003, "参数类型错误"),
NO_PERMISSION(1004,"无权访问"),
PARAM_MISSING(1005,"缺少参数");
private Integer code;
private String msg;
ResultCode(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return this.code;
}
public String getMsg() {
return this.msg;
}
}
然后将ResultCode与返回数据重新封装成统一返回对象
package com.hgt.pojo;
import java.io.Serializable;
/**
* 统一返回结果的封装
*/
public class Result implements Serializable {
/*状态码*/
private Integer code;
/*状态对应的信息*/
private String msg;
/*返回值中的数据,可为空*/
private Object data;
/**
* 含数据的返回结果
*
* @param resultCode 状态
* @param data 数据
*/
public Result(ResultCode resultCode, Object data) {
this.code = resultCode.getCode();
this.msg = resultCode.getMsg();
this.data = data;
}
/**
* 无数据的返回结果
*
* @param resultCode 状态
*/
public Result(ResultCode resultCode) {
this.code = resultCode.getCode();
this.msg = resultCode.getMsg();
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
@Override
public String toString() {
return "Result{" +
"code=" + code +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
}
课后作业1:类中构造方法使用ResultCode而不使用code、msg想必大家都能理解吧?如果不理解的可以留言。
控制类对应接口修改为
@RequestMapping(path = "result")
public Result testResult() {
// 无数据的返回值
// return new Result(ResultCode.SUCCESS);
// 有数据的返回值
return new Result(ResultCode.SUCCESS,"字符串代替数据");
}
无数据的返回值:
{"code":1,"msg":"成功","data":null}
有数据的返回值:
{"code":1,"msg":"成功","data":"字符串代替数据"}
这样优化之后就解决了第一个问题。
在实际开发中,返回值的状态只能由项目经理根据需要往ResultCode中添加,写接口的人员只能使用;前端人员想查看所有文档,只需要查看状态的文档就可以了,这样很优雅地就解决了混乱的问题。
细心的小伙伴可能会发现,控制器返回时,都通过new关键字创建对象,这样显示很low,也很麻烦,下面我们进一步优化返回对象。
静态方法返回结果对象
分成两步:
- 构造方法改在private
- 创建静态方法,在静态方法中调用构造方法创建对象
具体代码为:
package com.hgt.pojo;
import java.io.Serializable;
/**
* 统一返回结果的封装
*/
public class Result implements Serializable {
/*状态码*/
private Integer code;
/*状态对应的信息*/
private String msg;
/*返回值中的数据,可为空*/
private Object data;
/**
* 含数据的返回结果
*
* @param resultCode 状态
* @param data 数据
*/
private Result(ResultCode resultCode, Object data) {
this.code = resultCode.getCode();
this.msg = resultCode.getMsg();
this.data = data;
}
/**
* 无数据的返回结果
*
* @param resultCode 状态
*/
private Result(ResultCode resultCode) {
this.code = resultCode.getCode();
this.msg = resultCode.getMsg();
}
/**
* 状态成功,且无返回数据的返回值
* @return
*/
public static Result success() {
return new Result(ResultCode.SUCCESS);
}
/**
* 状态成功,且有返回数据的返回值
* @param data 数据
* @return
*/
public static Result success(Object data) {
return new Result(ResultCode.SUCCESS,data);
}
/**
* 状态失败,且无数据的返回值
* @param resultCode 返回的状态
* @return 返回值
*/
public static Result fail(ResultCode resultCode){
return new Result(resultCode);
}
/**
* 状态失败,且有数据的返回值
* @param resultCode 不同的状态
* @param data 数据
* @return 返回值
*/
public static Result fail(ResultCode resultCode,Object data) {
return new Result(resultCode,data);
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
@Override
public String toString() {
return "Result{" +
"code=" + code +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
}
控制器中的代码为:
@RequestMapping(path = "result")
public Result testResult() {
// 无数据的返回值
return Result.success("成功的数据列表");
// 有数据的返回值
// return Result.success("字符串代替数据");
}
返回的数据与上同,但静态调用的方式显然比每次都new一个对象优雅的多。
但目前还不是最优雅的。我们在每个接口中创建的Result对象其它与业务并无关系,只要为了统一的返回值才这样做的。
能否让接口只专注于业务的处理,无论接口返回值是什么对象,都在其它地方能处理成统一的返回格式的呢?这就是要解决的第二个问题。
思路是这样的:
- 增加一个自定义注解类,表示接口返回值都要统一包装
- 在控制器类或方法中使用此注解
- 定义拦截器,使用拦截器对所有请求对所有请求拦截,将所有使用@ResponseResult注解的类和方法做统一处理
- 注册拦截器,拦截所有请求
- 重新封装返回体
这一部分内容比较重要,也比较难,涉及到自定义注解、拦截器等知识点,所在放在下一节单独讲,代码同步更新。我在网上查询了一下,还没有哪个博客写的这么详细,详细到直接可以用到自己的项目当中。而且这部分功能是SpringBoot项目标配的功能,适用于所有的SpringBoot项目,期待吧!!!
项目代码:https://github.com/316620938/shop-springboot.git