在我们日常后端开发中,在处理数据之后,给客户端返回一个统一的、优雅的、规范的结果,能够让客户端很容易判断数据交给服务端处理的结果。

首先 , 我们需要知道的是我们通常会返回什么样的数据给前端?

  1. code : 状态码 , 除去一些常见的状态码 , 其他的一般由自己添加

    比如

    • 200 - 请求成功
    • 301 - 资源(网页等)被永久转移到其它URL
    • 404 - 请求的资源(网页等)不存在
    • 500 - 内部服务器错误
  2. data : 结果数据

  3. message : 信息

  4. description : 描述返回的信息 , description的定义似乎与message有一点冲突 , 不过可以根据情况选择是否携带

对象+工具类+枚举类

首先我们可以定义一个对象 , 简单的取个名字 , BaseResponse
在前面我们提到了常见的错误信息 , 这里可以吧常见的错误封装成枚举类, 在出现异常或者代码执行出错的时候可以直接利用枚举类来做出处理

错误枚举类ErrorCode

这里可以看到我们一般在message属性中简单的描述错误的原因 , 具体的描述可以封装到description

举个简单的例子 , 用户在注册的时候传入了用户名, 而恰好此时的用户名不符合我们定义的规范(一般情况我们会使用正则来校验用户传入的数据的合法性)

这个时候我们可以直接抛出异常, 同时通过ErrorCode来快速的返回错误内容,

同时在description属性中封装 用户名不符合规范 ! 等类似的提示信息 , 提升用户体验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public enum ErrorCode {
SUCCESS(0,"ok",""),
//HTTP状态码本身就是500,为什么500,因为你的业务里面抛异常 , 但是不应该让前端出现500,因为我们刚刚自己定义了一个业务异常,它应该返回40000
PARAMS_ERROR(40000,"请求参数错误",""),
NULL_ERROR(40001,"请求数据为空",""),
NOT_LOGIN(40100,"未登录",""),
NO_AUTH(40101,"无权限",""),
NOT_FOUND(404,"访问错误",""),
SYSTEM_ERROR(500000,"系统内部异常","")
;
final int code;
final String message;
final String description;
ErrorCode(int code, String message , String description) {
this.code = code;
this.message = message;
this.description = description;
}

public int getCode() {
return code;
}

public String getMessage() {
return message;
}

public String getDescription() {
return description;
}
}

这里的ErrorCode不止用于返回结果的处理, 同时也可以结合Exception 使用

通用返回对象BaseResponse

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Data
public class BaseResponse<T> implements Serializable {

private int code;
private T data; // controller 中的不同的方法返回值的類型不同
private String message;
private String description;
public BaseResponse(int code, T data, String message,String description) {
this.code = code;
this.data = data;
this.message = message;
}
public BaseResponse(int code, T data ,String description ) {
this(code,data,"",null);
}

public BaseResponse(ErrorCode errorCode){
this(errorCode.code,null,errorCode.message, errorCode.description);
}
}

ResultUtil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ResultUtils {
/**
*
* @param data
* @param <T>
* @return
*/
public static <T> BaseResponse<T> success(T data){
return new BaseResponse<>(0,data,"ok","");
}

/**
* 出现错误
* @param errorCode
* @return
*/
public static BaseResponse error(ErrorCode errorCode){
return new BaseResponse<>(errorCode);
}
}

值得一提的是我们在BaseResponse 定义了泛型, 这样做无疑会让我们的代码可读性变得更强 ,

但是在一些特定的情况下也会有一些限制 , 比如查看用户的详细信息, 需要返回不止一个对象,

这个时候有的小伙伴就会问了 : 你怎么这么懒, 就不能定义一个VO用来封装数据吗 ?

封装Vo的做法是很合理的, 但是也要根据具体的情况去封装 , 如果在某些情况下我们已经定义了Vo, 并且需要返回的数据可能会不断的变化, 此时如果我们再去一个个的封装Vo , 可能会造成属性的冗余 , 甚至封装过多的Vo , 同时随着我们代码的不断迭代 , 极有可能造成一出 ==屎山== , 这个时候就需要使用下面的方法来处理了。

这里来补充一下 **O 的定义以及含义 , 这个规范的全称叫做 ==分层领域模型规约==

  • DO( Data Object):与数据库表结构一一对应,通过DAO层向上传输数据源对象。
  • DTO( Data Transfer Object):数据传输对象,Service或Manager向外传输的对象。
  • BO( Business Object):业务对象。 由Service层输出的封装业务逻辑的对象。
  • AO( Application Object):应用对象。 在Web层与Service层之间抽象的复用对象模型,极为贴近展示层,复用度不高。
  • VO( View Object):显示层对象,通常是Web向模板渲染引擎层传输的对象。
  • POJO( Plain Ordinary Java Object):在本手册中, POJO专指只有setter/getter/toString的简单类,包括DO/DTO/BO/VO等。
  • TO(Transfer Object) ,数据传输对象 不同的应用程序之间传输的对象

继承Map

这种的操作比较简单 , 相当于把通用返回对象以及工具类给结合起来

比如下面的代码示例

这里只用了 Code msg 以及后端可能会返回的data

这样做的好处就是可以很方便的去封装返回的数据 , 不需要做多余的操作( VO 等)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@Data
public class Rextends HashMap<String, Object> {
private static final long serialVersionUID = 1L;

public R() {
put("code", 0);
put("msg", "success");
}

public static R error() {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");
}

public static R error(String msg) {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);
}

public static R error(int code, String msg) {
R r = new R();
r.put("code", code);
r.put("msg", msg);
return r;
}

public static R ok(String msg) {
R r = new R();
r.put("msg", msg);
return r;
}

public static R ok(Map<String, Object> map) {
R r = new R();
r.putAll(map);
return r;
}

public static R ok() {
return new R();
}

public static R error(BizCode unknowException) {
R r = new R();
r.put("code", unknowException.getCode());
r.put("msg", unknowException.getMessage());
return r;
}

public R put(String key, Object value) {
super.put(key, value);
return this;
}

public Integer getCode(){
return Integer.parseInt((String) this.get("code"));
}
}

比如下面的代码 , 我们需要返回一个帖子列表 , 需要包含基本的帖子的信息以及用户的基本信息

使用R就不需要去封装Vo , 同时我们还可以任意的添加别的数据 ( 前提是规定好了返回结果) , 比如这里就还封装了分页查询的相关数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public R getTopicList(Integer current, Integer pageSize) {
Page<Topic> pages = query().orderByDesc("score").page(new Page<>(current, pageSize));
List<Topic> topics = pages.getRecords();
List<Map<String, Object>> list = new ArrayList<>();

for (Topic topic : topics) {
HashMap<String, Object> map = new HashMap<>();
setTopicIsLiked(topic);
UserDTO userDTO = getUserOfTopic(topic);
map.put("topic", topic);
map.put("user", userDTO);
list.add(map);
}
return R.ok().put("data", list).put("total", pages.getTotal());
}

返回的数据是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
"total": 1000,
"code": 200,
"data": [{
"topic": {
"topicId": 513,
"userId": 2277,
"title": "Clothing, Shoes and Jewelry",
"imageIds": "19",
"content": "After logged i Mt each repository contains the same information. It wasn’t raining when Noah built the ark.",
"commentCount": 0,
"score": 0.0,
"likeCount": 0,
"isSelected": 0,
"createTime": "2013-04-23T04:28:06.000+00:00",
"isDelete": 0,
"isLike": null,
"updateTime": "2008-10-10T01:58:23.000+00:00"
},
"user": {
"userId": 2277,
"userName": "Kong Ziyi",
"avatarUrl": "https://www.gls6.info/Food",
"isAdmin": 0
}
}
],
"description": "",
"message": "ok"
}

相对上一种方法, 使用Map 的 子类无疑是更加的灵活 , 在我们规定好返回数据的前提下更容易去做一些拓展