全局异常处理
项目开发中异常通常分为两种:
预期异常(程序员手动抛出)
运行时异常
如果在业务层或持久层遇到异常,这些异常会向上抛出,如果直到抛到前端都没人处理的话会引起前端崩溃,用户体验极差,最好的办法是在控制层通过try-catch捕获异常,但一个一个捕获又太麻烦,因此就引入了全局异常处理器
全局异常处理器在项目中:
BaseException:
- BaseException 基础异常,业务中需要手动抛出异常,则需要抛出此异常
package com.zzyl.exception;
import com.zzyl.enums.BasicEnum;
import lombok.Getter;
import lombok.Setter;
/**
* BaseException
* @author itheima
**/
@Getter
@Setter
public class BaseException extends RuntimeException {
private BasicEnum basicEnum;
public BaseException(BasicEnum basicEnum) {
this.basicEnum = basicEnum;
}
}
BaseException继承运行时异常,将BasicEnum以构造器方法的形式依赖于BaseException
BasicEnum:
- BaseException中的参数是一个枚举,在BasicEnum中定义业务所涉及到的异常说明
package com.zzyl.enums;
import com.zzyl.base.IBasicEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 基础枚举
*
* @author itcast
*/
@Getter
@AllArgsConstructor
public enum BasicEnum implements IBasicEnum {
SUCCEED(200, "操作成功"),
SECURITY_ACCESSDENIED_FAIL(401, "权限不足!"),
LOGIN_FAIL(401, "用户登录失败"),
LOGIN_LOSE_EFFICACY(401, "登录状态失效,请重新登录"),
SYSYTEM_FAIL(500, "系统运行异常"),
//权限相关异常:1400-1499
DEPT_DEPTH_UPPER_LIMIT(1400, "部门最多4级"),
PARENT_DEPT_DISABLE(1401, "父级部门为禁用状态,不允许启用"),
DEPT_NULL_EXCEPTION(1402, "部门不能为空"),
POSITION_DISTRIBUTED(1403, "职位已分配,不允许禁用"),
MENU_NAME_DUPLICATE_EXCEPTION(1404, "菜单路由重复"),
MENU_PATH_DUPLICATE_EXCEPTION(1405, "菜单路由重复"),
RESOURCE_DEPTH_UPPER_LIMIT(1406, "菜单最多3级"),
USER_ROLE_AND_MENU_EMPTY(1407, "请为用户分配角色和菜单"),
PARENT_MENU_DISABLE(1408, "父级菜单为禁用状态,不允许启用"),
MENU_DISTRIBUTED(1409, "菜单已分配,不允许禁用"),
BUTTON_DISTRIBUTED(1410, "按钮已分配,不允许禁用"),
ROLE_DISTRIBUTED(1411, "该角色已分配用户,不能删除"),
USER_LOCATED_BOTTOMED_DEPT(1412, "请选择最底层部门"),
DEPT_BINDING_USER(1413, "部门已绑定用户,不允许禁用"),
USER_EMPTY_EXCEPTION(1414, "用户不存在,不能启用或禁用"),
ORIGINAL_PASSWORD_ERROR(1415, "原密码不正确,请重新输入"),
ORIGINAL_CANNOT_EQUAL_NEW(1416, "新密码和旧密码不能一致"),
GET_OPENID_ERROR(1417, "小程序登录,获取openId失败"),
GET_PHONE_ERROR(1418, "小程序登录,获取手机号失败"),
GET_TOKEN_ERROR(1419, "小程序登录,获取token失败"),
//业务相关异常:1500-1599
WEBSOCKET_PUSH_MSG_ERROR(1500, "websocket推送消息失败"),
CLOSE_BALANCE_ERROR(1501, "关闭余额账户失败"),
MONTH_BILL_DUPLICATE_EXCEPTION(1502, "该老人的月度账单已生成,不可重复生成"),
MONTH_OUT_CHECKIN_TERM(1503, "该月不在费用期限内"),
RETREAT_TERM_LACK_MONTH_BILL(1504, "退住日期内缺少月度账单,请前往财务管理模块手动生成"),
ELDER_ALREADY_CHECKIN(1505, "该老人已入住,请重新输入"),
CHECKIN_TERM_SHOULD_CONTAIN_COST_TERM(1506, "费用期限应该在入住期限内"),
DEVICE_NAME_EXIST(1507, "设备名称已存在,请重新输入"),
LOCATION_BINDING_PRODUCT(1508, "该老人/位置已绑定该产品,请重新选择"),
IOT_REGISTER_DEVICE_ERROR(1509, "物联网接口 - 注册设备,调用失败"),
IOT_QUERY_PRODUCT_ERROR(1510, "物联网接口 - 查询产品,调用失败"),
IOT_QUERY_DEVICE_ERROR(1511, "物联网接口 - 查询产品,调用失败"),
IOT_QUERY_DEVICE_PROPERTY_STATUS_ERROR(1512, "物联网接口 - 查询设备的物模型运行状态,调用失败"),
IOT_QUERY_THING_MODEL_PUBLISHED_ERROR(1513, "物联网接口 - 查询物模型数据,调用失败"),
DEVICE_NOT_EXIST(1514, "该设备不存在,无法修改"),
IOT_BATCH_UPDATE_DEVICE_ERROR(1515, "物联网接口 - 批量修改设备名称,调用失败"),
RE_SELECT_PRODUCT(1516, "该老人/位置已绑定该产品,请重新选择"),
IOT_DELETE_DEVICE_ERROR(1517, "物联网接口 - 删除设备,调用失败"),
IOT_LIST_PRODUCT_ERROR(1518, "物联网接口 - 查看所有产品列表,调用失败"),
IOT_OPEN_DOOR_ERROR(1519, "开门失败"),
IOT_QUERY_DEVICE_SERVICE_DATA_ERROR(1520, "物联网接口 - 调用查询指定设备的服务调用记录接口,调用失败"),
ELDER_NOT_EXIST(1521, "老人不存在"),
MEMBER_ALREADY_BINDING_ELDER(1522, "已绑定过此家人"),
CANNOT_PLACE_ORDER_DUE_ELDER_ALREADY_RETREATED(1523, "已退住,不可下单"),
ORDER_CLOSED(1524, "订单已关闭"),
CANNOT_RESERVATION_DUE_ELDER_ALREADY_RETREATED(1525, "已退住,不可预约"),
RESERVATION_CANCEL_COUNT_UPPER_LIMIT(1526, "今天取消次数已达上限,不可进行预约"),
TIME_ALREADY_RESERVATED_BY_PHONE(1527, "此手机号已预约该时间"),
RETREAT_SHOULD_IN_COST_TERM(1528, "请在费用期限内发起退住申请"),
UPLOAD_FILE_EMPTY(1529, "上传图片不能为空"),
DONE_ORDER_CANNOT_REFUND(1530, "已执行的订单不可退款"),
BED_INSERT_FAIL(1531,"床位新增失败");
/**
* 编码
*/
public final int code;
/**
* 信息
*/
public final String msg;
}
GlobalExceptionHandle:
- GlobalExceptionHandle 全局异常处理器,全局异常处理器会处理传入的异常并返回给前端一个友好的说明哪一部分出错了
package com.zzyl.exception;
import cn.hutool.core.util.ObjectUtil;
import com.zzyl.base.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 处理自定义异常BaseException。
* 返回自定义异常中的错误代码和错误消息。
*
* @param exception 自定义异常
* @return 响应数据,包含错误代码和错误消息
*/
@ExceptionHandler(BaseException.class)
public ResponseResult<Object> handleBaseException(BaseException exception) {
exception.printStackTrace();
if (ObjectUtil.isNotEmpty(exception.getBasicEnum())) {
log.error("自定义异常处理:{}", exception.getBasicEnum().getMsg());
}
return ResponseResult.error(exception.getBasicEnum());
}
/**
* 处理其他未知异常。
* 返回HTTP响应状态码500,包含错误代码和异常堆栈信息。
*
* @param exception 未知异常
* @return 响应数据,包含错误代码和异常堆栈信息
*/
@ExceptionHandler(Exception.class)
public ResponseResult<Object> handleUnknownException(Exception exception) {
exception.printStackTrace();
if (ObjectUtil.isNotEmpty(exception.getCause())) {
log.error("其他未知异常:{}", exception.getMessage());
}
return ResponseResult.error(500,exception.getMessage());
}
}
@RestControllerAdvice
这个注解可以捕获整个Controller中重复编写异常处理逻辑
通过@ExceptionHandler注解定义对异常的不同处理方法
返回的异常会自动转换为JSON/XMl格式,因为包含@ResponseBody
处理异常时:
先处理BaseException中有的异常:,将打印回的信息返回给前端
package com.zzyl.exception;
import com.zzyl.enums.BasicEnum;
import lombok.Getter;
import lombok.Setter;
/**
* BaseException
* @author itheima
**/
@Getter
@Setter
public class BaseException extends RuntimeException {
private BasicEnum basicEnum;
public BaseException(BasicEnum basicEnum) {
this.basicEnum = basicEnum;
}
}
BaseException中没有的话,打印这个未知异常
举例:
/**
* 创建床位
* @param bedDto
*/
@Override
public void createBed(BedDto bedDto) {
Bed bed = BeanUtil.copyProperties(bedDto, Bed.class);
bed.setCreateTime(LocalDateTime.now());
bed.setUpdateTime(LocalDateTime.now());
bed.setBedStatus(0);
bed.setCreateBy(123456L);
bedMapper.insertSelective(bed);
}
注意:
正常情况下运行时异常可以不主动捕获抛出,因为全局异常处理器可以捕获这些异常,不过我们在需要手动传递给前端信息要手动捕获抛出异常.