杜龙少(sdvdxl)

RESTFul API 错误的设计

Word count: 1,115 / Reading time: 5 min
2017/05/13 Share

不管是WEB还是移动端如果需要调用Http接口那么不可避免要处理各种错误包括客户端参数完整性校验类型校验系统错误等如果一个设计规范的接口错误返回值不但可以规范调用方统一处理方式给出更合理的提示而且后端也有着接口返回规范的作用这里讨论的其实并不单纯是返回的错误设计也包括业务上的错误设计

接口返回设计

接口返回需要有http status错误说明并且要提供一个完整的错误列表可以通过简单的错误说明或者错误代码查阅到详细的错误原因简要错误说明可以只包含简短的文字描述可以包含错误代码如果说哪种方式更为合理我更倾向于错误代码+简要错误说明如果只有简要错误说明这个错误描述可能有些描述不合理如果后端改了说明那么对应的错误映射也要更改调用方也要跟着修改如果只有错误代码调用方难以一眼看出错误原因需要查阅错误码这个比较啰嗦如果同时提供错误码和简要错误说明调用方可以通过简要说明快速定位问题如果要处理错误逻辑根据错误码表映射即可这样即使描述改变了但是错误码是永久不变的不会影响调用方的处理逻辑返回的结构如下

1
2
3
4
5
6
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8

{
"msg": "错误说明"
}

服务端错误设计和处理

根据RESTful接口的http status对于错误可以分为以下几大类常用

  1. 权限错误 401
  2. 参数错误 400
  3. 重复冲突 409
  4. 找不到资源 404
  5. 服务器错误 500

对于以上5中分类除了第5种系统服务器错误外其余四种我们可以分别设计一个错误异常类型至于第5中服务器错误为什么不需要因为我们需要根据上面四种错误是需要单独判断的如果不在上面的类型中直接返回500即可对于上面的错误在返回或者抛出的时候应该具体到原因比如 参数错误要写明哪里错误是手机号不合法还是邮箱不合法还是哪个参数类型不对这样便于调用方定位问题

Java为例我们可以定义以下异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class AuthExcepption extends RuntimeException {
protected int code;
public AuthExcepption(int code, String msg) {
this.code = code;
super(msg);
}

public int getCode() {
return code;
}
}

// 对于其他三个也是这个格式
// IllegalArgumentException
// DuplicatedException
// NotFoundException

在做异常统一处理的时候可以判断极少的类型即可下面是 spring mvc 的统一异常处理方式

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
class Body {
private int code;
private String msg;
// setter and getter

public Body(int code, String msg) {
this.code = code;
this.msg = msg;
}
}

@ExceptionHandler(value = {AuthExcepption.class})
public ResponseEntity<Object> handleAuthExcepption(AuthExcepption ex, WebRequest request) {
// 记录日志
return new ResponseEntity(new Body(ex.getCode(),ex.getMessage()),HttpStatus.UNAUTHORIZED);
}

@ExceptionHandler(value = {IllegalArgumentException.class})
public ResponseEntity<Object> handleIllegalArgumentException(IllegalArgumentException ex, WebRequest request) {
// 记录日志
return new ResponseEntity(new Body(ex.getCode(),ex.getMessage()), HttpStatus.BAD_REQUEST);
}

@ExceptionHandler(value = {DuplicatedException.class})
public ResponseEntity<Object> handleIllegalArgumentException(DuplicatedException ex, WebRequest request) {
// 记录日志
return new ResponseEntity(new Body(ex.getCode(),ex.getMessage()), HttpStatus.CONFLICT);
}

@ExceptionHandler(value = {NotFoundException.class})
public ResponseEntity<Object> handleIllegalArgumentException(NotFoundException ex, WebRequest request) {
// 记录日志
return new ResponseEntity(new Body(ex.getCode(),ex.getMessage()),HttpStatus.NOT_FOUND);
}

在业务层那么需要这么处理抛出一种异常附带具体的错误码和简要消息

1
2
3
4
5
public void addUser(User user) {
if (!isValid(user.getPhone())) {
throw new IllegalArgumentException(10001,"手机"+user.getPhone()+" 不合法");
}
}

在上面的业务处理示例中我们采用了传递错误码的形式另一种处理方式是定义一个该错误码对应的异常比如

1
2
3
4
5
public class PhoneIllegalException extends IllegalArgumentException{
public PhoneIllegalException (String msg) {
super(10001,msg);
}
}

然后业务层就可以直接抛出此异常了

1
2
3
4
5
public void addUser(User user) {
if (!isValid(user.getPhone())) {
throw new PhoneIllegalException("手机"+user.getPhone()+" 不合法");
}
}

CATALOG
  1. 1. 接口返回设计
  2. 2. 服务端错误设计和处理