本系列文章介绍从0开始搭建一个基于分布式的医疗挂号系统。本次四篇文章完成了
医院设置微服务模块
的后端接口,为了方便开发,对接口的返回结果
、全局异常
、全局日志
进行了统一处理。 同时,为了方便进行访问测试,还整合了Swagger2工具
,这些通用的模块中,除了全局日志
被放在医院设置微服务模块的配置资源中,其余都统一被抽取在common模块中。具体实现可参考下面文章:
- 医院设置微服务 | 模块搭建
- 医院设置微服务 | 接口开发
- 通用模块 | 整合Swagger2
- 通用模块 | 统一返回结果、统一异常处理、统一日志处理
作者:Hudie
微信公众号/CSDN博客:编程一只蝶
项目已开源至gitee:https://gitee.com/guo-qianliang/yygh_parent
项目已开源至github:https://github.com/Guoqianliang/yygh_parent
一、统一返回结果
在实际开发中,是一个后端团队一起开发,每个人做不同的模块,开发不同的接口,最终进行调用进而显示。因此可以把所有返回结果做一个统一的约定。让所有的接口都返回相同的数据格式,这样利于前端的显示与解析
。
上图这一操作需要通过统一返回结果类
和统一返回结果状态信息类
来实现。
1.统一返回结果类
/**
* 全局统一返回结果类
*/
@Data
@ApiModel(value = "全局统一返回结果")
public class Result {
@ApiModelProperty(value = "返回码")
private Integer code;
@ApiModelProperty(value = "返回消息")
private String message;
@ApiModelProperty(value = "返回数据")
private T data;
public Result() {
}
protected static Result build(T data) {
Result result = new Result();
if (data != null) {
result.setData(data);
}
return result;
}
public static Result build(T body, ResultCodeEnum resultCodeEnum) {
Result result = build(body);
result.setCode(resultCodeEnum.getCode());
result.setMessage(resultCodeEnum.getMessage());
return result;
}
public static Result build(Integer code, String message) {
Result result = build(null);
result.setCode(code);
result.setMessage(message);
return result;
}
public static Result ok() {
return Result.ok(null);
}
/**
* 操作成功
* @param data
* @param
* @return
*/
public static Result ok(T data) {
Result result = build(data);
return build(data, ResultCodeEnum.SUCCESS);
}
public static Result fail() {
return Result.fail(null);
}
/**
* 操作失败
* @param data
* @param
* @return
*/
public static Result fail(T data) {
Result result = build(data);
return build(data, ResultCodeEnum.FAIL);
}
public Result message(String msg) {
this.setMessage(msg);
return this;
}
public Result code(Integer code) {
this.setCode(code);
return this;
}
public boolean isOk() {
if (this.getCode().intValue() == ResultCodeEnum.SUCCESS.getCode().intValue()) {
return true;
}
return false;
}
}
2.统一返回状态信息类
/**
* 统一返回结果状态信息类
*/
@Getter
public enum ResultCodeEnum {
SUCCESS(200, "成功"),
FAIL(201, "失败"),
PARAM_ERROR(202, "参数不正确"),
SERVICE_ERROR(203, "服务异常"),
DATA_ERROR(204, "数据异常"),
DATA_UPDATE_ERROR(205, "数据版本异常"),
LOGIN_AUTH(208, "未登陆"),
PERMISSION(209, "没有权限"),
CODE_ERROR(210, "验证码错误"),
// LOGIN_MOBLE_ERROR(211, "账号不正确"),
LOGIN_DISABLED_ERROR(212, "改用户已被禁用"),
REGISTER_MOBLE_ERROR(213, "手机号已被使用"),
LOGIN_AURH(214, "需要登录"),
LOGIN_ACL(215, "没有权限"),
URL_ENCODE_ERROR(216, "URL编码失败"),
ILLEGAL_CALLBACK_REQUEST_ERROR(217, "非法回调请求"),
FETCH_ACCESSTOKEN_FAILD(218, "获取accessToken失败"),
FETCH_USERINFO_ERROR(219, "获取用户信息失败"),
//LOGIN_ERROR( 23005, "登录失败"),
PAY_RUN(220, "支付中"),
CANCEL_ORDER_FAIL(225, "取消订单失败"),
CANCEL_ORDER_NO(225, "不能取消预约"),
HOSCODE_EXIST(230, "医院编号已经存在"),
NUMBER_NO(240, "可预约号不足"),
TIME_NO(250, "当前时间不可以预约"),
SIGN_ERROR(300, "签名错误"),
HOSPITAL_OPEN(310, "医院未开通,暂时不能访问"),
HOSPITAL_LOCK(320, "医院被锁定,暂时不能访问"),
;
private Integer code;
private String message;
private ResultCodeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
二、统一异常处理
spring boot 默认情况下会将异常映射到 /error 进行异常处理,这样的提示十分不友好,下面使用自定义异常处理,可以提供更友好的展示。
1.自定义异常类
/**
* 自定义全局异常类
*
*/
@Data
@ApiModel(value = "自定义全局异常类")
public class YyghException extends RuntimeException {
@ApiModelProperty(value = "异常状态码")
private Integer code;
/**
* 通过状态码和错误消息创建异常对象
* @param message
* @param code
*/
public YyghException(String message, Integer code) {
super(message);
this.code = code;
}
/**
* 接收枚举类型对象
* @param resultCodeEnum
*/
public YyghException(ResultCodeEnum resultCodeEnum) {
super(resultCodeEnum.getMessage());
this.code = resultCodeEnum.getCode();
}
@Override
public String toString() {
return "YyghException{" +
"code=" + code +
", message=" + this.getMessage() +
'}';
}
}
2.全局异常处理
下面代码中两个方法的
@ExceptionHandler
注解,分别传入系统异常Exception类
和自定义异常YyghException类
,当出现系统异常时会运行系统的Exception方法,当出现自定义异常时会运行YyghException方法。
/**
* @Description: 统一异常处理类
* @author Guoqianliang
* @date 20:56 - 2021/4/7
*/
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 全局异常处理
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public Result error(Exception e) {
e.printStackTrace();
return Result.fail();
}
/**
* 自定义异常处理
* @param e
* @return
*/
@ExceptionHandler(YyghException.class)
@ResponseBody
public Result error(YyghException e) {
e.printStackTrace();
return Result.fail();
}
}
使用自定义异常时,不会自动调用,需要手动抛出异常,举例如下:
三、统一日志处理
日志记录器(Logger)的行为是分等级的,常用的4个级别如下:
DEBUG < INFO < WARN < ERROR
级别越高,打印的信息越多。默认情况下,springboot从控制台打印出来的日志级别只有INFO及以上级别,通过:logging.level.root=debug
可以修改日志级别。
下面给出一个日志模块,通过此模板可以将日志持久化到本地文件:
logback
INFO
${CONSOLE_LOG_PATTERN}
UTF-8
${log.path}/log_info.log
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
UTF-8
${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log
100MB
15
INFO
ACCEPT
DENY
${log.path}/log_warn.log
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
UTF-8
${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log
100MB
15
warn
ACCEPT
DENY
${log.path}/log_error.log
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
UTF-8
${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log
100MB
15
ERROR
ACCEPT
DENY