异常处理设计
1. 概述
ZsAdmin 采用统一的异常处理机制,确保系统在发生异常时能够返回标准化的响应,提高系统的可用性和可维护性。本文档将详细介绍后端异常处理的设计与实现。
2. 异常处理架构
2.1 核心组件
- ZsExceptionHandler:全局异常处理器
- ZsException:业务异常类
- Result:统一响应结果
- ErrorCode:错误码枚举
2.2 架构图
mermaid
graph TD
A[客户端] --> B[Controller]
B --> C{业务逻辑}
C --> |正常| D[返回 Result.ok]
C --> |异常| E[抛出异常]
E --> |业务异常| F[ZsException]
E --> |系统异常| G[RuntimeException]
E --> |其他异常| H[Exception]
F --> I[ZsExceptionHandler]
G --> I
H --> I
I --> J[返回 Result.error]
J --> K[客户端]3. 核心实现
3.1 错误码
java
public interface ErrorCodeConstants {
ErrorCode CAPTCHA_ERROR = new ErrorCode(10001, "验证码错误");
ErrorCode USER_NOT_EXIST = new ErrorCode(10002, "用户不存在");
ErrorCode USER_PASSWORD_ERROR = new ErrorCode(10003, "用户密码错误");
ErrorCode USER_NOT_LOGIN = new ErrorCode(10004, "用户未登录");
ErrorCode USER_NOT_PERMISSION = new ErrorCode(10005, "用户没有权限");
ErrorCode USER_NOT_ROLE = new ErrorCode(10006, "用户没有角色");
ErrorCode USER_NOT_ENABLE = new ErrorCode(10007, "用户未启用");
ErrorCode USER_LOGIN_ERROR = new ErrorCode(10008, "用户登录失败");
ErrorCode USER_LOGOUT_ERROR = new ErrorCode(10009, "用户登出失败");
ErrorCode USER_LOGIN_NAME_EXIST = new ErrorCode(10010, "用户登录名已存在");
ErrorCode TENANT_NOT_EXIST = new ErrorCode(10011, "租户不存在");
ErrorCode TENANT_NOT_ENABLE = new ErrorCode(10012, "租户未启用");
ErrorCode TENANT_PACKAGE_NOT_EXIST = new ErrorCode(10013, "租户套餐不存在");
ErrorCode TENANT_PACKAGE_NOT_ENABLE = new ErrorCode(10014, "租户套餐未启用");
ErrorCode TENANT_NAME_EXIST = new ErrorCode(10015, "租户名称已存在");
ErrorCode TENANT_SYSTEM_NOT_DELETE = new ErrorCode(10016, "系统租户,不能删除");
ErrorCode TENANT_ADMIN_ROLE_NOT_EXIST = new ErrorCode(10017, "未找到租户管理员角色");
}3.2 业务异常类
java
public class ZsException extends RuntimeException {
private Integer code;
private String msg;
public ZsException(Integer code, String msg, Exception e) {
super(e);
this.code = code;
this.msg = msg;
}
public ZsException(String msg) {
this.msg = msg;
}
public ZsException(@NotNull HttpEnum httpEnum, Exception e) {
super(e);
this.code = httpEnum.getCode();
this.msg = httpEnum.getMsg();
}
public ZsException(HttpEnum httpEnum) {
this.code = httpEnum.getCode();
this.msg = httpEnum.getMsg();
}
public ZsException(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public ZsException(HttpEnum httpEnum, String msg) {
this.code = httpEnum.getCode();
this.msg = msg;
}
public ZsException(ErrorCode errorCode) {
this.code = errorCode.getCode();
this.msg = errorCode.getMsg();
}
}3.3 全局异常处理器
java
@RestControllerAdvice
public class ZsExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(ZsExceptionHandler.class);
/**
* 自定义业务异常
*
* @return Result
*/
@ExceptionHandler(ZsException.class)
public Result<?> handleZsException(@NotNull ZsException e) {
logger.error(e.getMessage(), e);
return new Result<>().error(e.getCode(), e.getMsg(), e.getMessage());
}
@ExceptionHandler(MyBatisSystemException.class)
public Result<?> handleMyBatisException(@NotNull MyBatisSystemException ex) {
// 记录MyBatis-Plus的SQL异常信息到日志
logger.error("MyBatis-Plus SQL Exception occurred", ex);
logger.error(ex.getMessage(), ex);
return new Result<>().error();
}
/**
* 参数异常
*
* @param e a
* @return Result
*/
@ExceptionHandler(BindException.class)
public Result<?> handleBindException(@NotNull BindException e) {
logger.error(e.getMessage(), e);
String message = e.getAllErrors().get(0).getDefaultMessage();
return new Result<>().error(HttpEnum.VALIDATE_ERROR, message);
}
/**
* 拦截未知的运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public Result<?> handleRuntimeException(@NotNull RuntimeException e, @NotNull HttpServletRequest request) {
logger.error(e.getMessage(), e);
String requestUri = request.getRequestURI();
logger.error(requestUri);
return new Result<>().error(e.getMessage());
}
@ExceptionHandler(Exception.class)
public Result<?> handleException(@NotNull Exception ex) {
// 获取错误状态码
logger.error(ex.getMessage(), ex);
if (ex instanceof NoResourceFoundException){
return new Result<>().error(HttpStatus.NOT_FOUND);
} else if (ex instanceof BadRequestException) {
return new Result<>().error( HttpStatus.BAD_REQUEST);
} else if(ex instanceof HttpRequestMethodNotSupportedException){
return new Result<>().error(HttpStatus.METHOD_NOT_ALLOWED);
} else if (ex instanceof MethodArgumentNotValidException) {
return new Result<>().error(HttpStatus.BAD_REQUEST);
} else if (ex instanceof AccessDeniedException) {
return new Result<>().error(HttpStatus.FORBIDDEN);
}
else {
// 默认处理其他未预料的异常
return new Result<>().error();
}
}
@ExceptionHandler(AccessDeniedException.class)
public void accessDeniedException(@NotNull AccessDeniedException e) throws AccessDeniedException {
throw e;
}
}3.4 统一响应结果
java
@Data
@Schema(description = "统一返回结果")
public class Result<T> implements Serializable {
@Schema(description = "状态码")
private Integer code;
@Schema(description = "返回信息")
private String msg;
@Schema(description = "返回数据")
private T data;
@Schema(description = "时间戳")
private long timestamp = System.currentTimeMillis();
public Result() {
}
public Result(@NotNull HttpEnum httpEnum) {
this.code = httpEnum.getCode();
this.msg = httpEnum.getMsg();
}
public Result(@NotNull HttpEnum httpEnum, String msg) {
this.code = httpEnum.getCode();
this.msg = msg;
}
public Result(String msg) {
this.code = HttpEnum.INTERNAL_SERVER_ERROR.getCode();
this.msg = msg;
this.data = null;
}
public Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
this.data = null;
}
public Result(@NotNull HttpEnum httpEnum, @Nullable T data) {
this.code = httpEnum.getCode();
this.msg = httpEnum.getMsg();
this.data = data;
}
public Result(Integer code, String msg, @Nullable T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
@NotNull
public Result<T> ok() {
return new Result<>(HttpEnum.OK);
}
@NotNull
public Result<T> ok(T data) {
return new Result<>(HttpEnum.OK, data);
}
@NotNull
public Result<T> ok(@NotNull HttpEnum httpEnum) {
return new Result<>(httpEnum);
}
@NotNull
public Result<T> ok(Integer code, String msg, T data) {
return new Result<>(code, msg, data);
}
@NotNull
public Result<T> error() {
return new Result<>( HttpEnum.INTERNAL_SERVER_ERROR);
}
@NotNull
public Result<T> error(T data) {
return new Result<>(HttpEnum.INTERNAL_SERVER_ERROR, data);
}
@NotNull
public Result<T> error(@NotNull HttpStatus httpStatus) {
return new Result<>(httpStatus.value(), httpStatus.getReasonPhrase());
}
@NotNull
public Result<T> error(String msg) {
return new Result<>(msg);
}
@NotNull
public Result<T> error(@NotNull HttpEnum httpEnum) {
return new Result<>(httpEnum);
}
@NotNull
public Result<T> error(Integer code, String msg) {
return new Result<>(code, msg);
}
@NotNull
public Result<T> error(@NotNull HttpEnum httpEnum, String msg) {
return new Result<>(httpEnum, msg);
}
@NotNull
public Result<T> error(Integer code, String msg, T data) {
return new Result<>(code, msg, data);
}
}4. 异常处理流程
- 业务层:当业务逻辑出现异常时,抛出
ZsException并指定错误码 - 控制器层:接收请求,调用业务层方法,不处理异常
- 全局异常处理器:捕获所有异常,根据异常类型返回标准化响应
- 客户端:接收统一格式的响应,根据状态码进行处理