Skip to content

ExceptionHandler 异常处理

RuoYi-Plus 提供的全局异常处理机制,统一处理应用中的各种异常情况。

📋 异常体系

异常类型

java
// 基础业务异常
public class ServiceException extends RuntimeException {

    private Integer code;
    private String message;
    private String detailMessage;

    public ServiceException() {}

    public ServiceException(String message) {
        this.message = message;
    }

    public ServiceException(String message, Integer code) {
        this.message = message;
        this.code = code;
    }

    public ServiceException(String message, Throwable cause) {
        super(message, cause);
        this.message = message;
    }

    // getter/setter...
}

// 用户异常
public class UserException extends ServiceException {

    public UserException(String message) {
        super(message);
    }

    public UserException(String message, Integer code) {
        super(message, code);
    }
}

// 认证异常
public class AuthException extends ServiceException {

    public AuthException(String message) {
        super(message);
    }

    public AuthException(String message, Integer code) {
        super(message, code);
    }
}

// 权限异常
public class PermissionException extends ServiceException {

    public PermissionException(String message) {
        super(message);
    }

    public PermissionException(String message, Integer code) {
        super(message, code);
    }
}

异常码定义

java
// 异常码常量
public interface ErrorCode {

    // 系统异常 (1000-1999)
    int SYSTEM_ERROR = 1000;
    int SYSTEM_BUSY = 1001;
    int SYSTEM_TIMEOUT = 1002;

    // 参数异常 (2000-2999)
    int PARAM_ERROR = 2000;
    int PARAM_MISSING = 2001;
    int PARAM_INVALID = 2002;

    // 认证异常 (3000-3999)
    int AUTH_FAILED = 3000;
    int TOKEN_INVALID = 3001;
    int TOKEN_EXPIRED = 3002;

    // 权限异常 (4000-4999)
    int PERMISSION_DENIED = 4000;
    int ACCESS_FORBIDDEN = 4001;

    // 业务异常 (5000-9999)
    int USER_NOT_FOUND = 5000;
    int USER_EXISTS = 5001;
    int PASSWORD_ERROR = 5002;
    int ACCOUNT_DISABLED = 5003;
}

// 异常信息枚举
public enum ErrorCodeEnum {

    SUCCESS(200, "操作成功"),
    SYSTEM_ERROR(ErrorCode.SYSTEM_ERROR, "系统异常"),
    PARAM_ERROR(ErrorCode.PARAM_ERROR, "参数错误"),
    AUTH_FAILED(ErrorCode.AUTH_FAILED, "认证失败"),
    PERMISSION_DENIED(ErrorCode.PERMISSION_DENIED, "权限不足"),
    USER_NOT_FOUND(ErrorCode.USER_NOT_FOUND, "用户不存在");

    private final Integer code;
    private final String message;

    ErrorCodeEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    // getter...
}

🎯 全局异常处理器

核心处理器

java
/**
 * 全局异常处理器
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 业务异常
     */
    @ExceptionHandler(ServiceException.class)
    public R<Void> handleServiceException(ServiceException e, HttpServletRequest request) {
        Integer code = e.getCode();
        if (ObjectUtil.isNull(code)) {
            code = HttpStatus.INTERNAL_SERVER_ERROR.value();
        }

        log.error("请求地址'{}',发生业务异常.", request.getRequestURI(), e);
        return R.fail(code, e.getMessage());
    }

    /**
     * 参数验证异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public R<Void> handleValidException(MethodArgumentNotValidException e) {
        log.error("参数验证异常", e);
        String message = e.getBindingResult().getFieldError().getDefaultMessage();
        return R.fail(ErrorCode.PARAM_ERROR, message);
    }

    /**
     * 参数绑定异常
     */
    @ExceptionHandler(BindException.class)
    public R<Void> handleBindException(BindException e) {
        log.error("参数绑定异常", e);
        String message = e.getBindingResult().getFieldError().getDefaultMessage();
        return R.fail(ErrorCode.PARAM_ERROR, message);
    }

    /**
     * 参数类型不匹配异常
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public R<Void> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
        log.error("参数类型不匹配异常", e);
        String message = "参数类型不匹配: " + e.getName();
        return R.fail(ErrorCode.PARAM_ERROR, message);
    }

    /**
     * 请求方式不支持
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public R<Void> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
                                                        HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
        return R.fail(HttpStatus.METHOD_NOT_ALLOWED.value(), e.getMessage());
    }

    /**
     * 权限异常
     */
    @ExceptionHandler(NotPermissionException.class)
    public R<Void> handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());
        return R.fail(ErrorCode.PERMISSION_DENIED, "没有访问权限,请联系管理员授权");
    }

    /**
     * 认证异常
     */
    @ExceptionHandler(NotLoginException.class)
    public R<Void> handleNotLoginException(NotLoginException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage());
        return R.fail(ErrorCode.AUTH_FAILED, "认证失败,无法访问系统资源");
    }

    /**
     * 数据库异常
     */
    @ExceptionHandler(SQLException.class)
    public R<Void> handleSQLException(SQLException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生数据库异常.", requestURI, e);
        return R.fail("数据库操作异常,请联系管理员");
    }

    /**
     * 空指针异常
     */
    @ExceptionHandler(NullPointerException.class)
    public R<Void> handleNullPointerException(NullPointerException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生空指针异常.", requestURI, e);
        return R.fail("空指针异常");
    }

    /**
     * 系统异常
     */
    @ExceptionHandler(Exception.class)
    public R<Void> handleException(Exception e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生系统异常.", requestURI, e);
        return R.fail("系统异常,请联系管理员");
    }

    /**
     * 自定义验证异常
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public R<Void> constraintViolationException(ConstraintViolationException e) {
        log.error("参数验证异常", e);
        String message = e.getConstraintViolations().stream()
            .map(ConstraintViolation::getMessage)
            .collect(Collectors.joining(", "));
        return R.fail(ErrorCode.PARAM_ERROR, message);
    }

    /**
     * 演示模式异常
     */
    @ExceptionHandler(DemoModeException.class)
    public R<Void> handleDemoModeException(DemoModeException e) {
        return R.fail("演示模式,不允许操作");
    }
}

异步异常处理

java
/**
 * 异步异常处理器
 */
@Component
@Slf4j
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

    @Override
    public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
        log.error("异步任务执行异常 - Method: {}, Exception: {}",
            method.getName(), throwable.getMessage(), throwable);

        // 记录异常信息到数据库
        recordAsyncException(method, throwable, objects);

        // 发送异常通知
        notifyException(method, throwable);
    }

    private void recordAsyncException(Method method, Throwable throwable, Object... params) {
        try {
            AsyncExceptionLog log = new AsyncExceptionLog();
            log.setMethodName(method.getName());
            log.setClassName(method.getDeclaringClass().getName());
            log.setExceptionMessage(throwable.getMessage());
            log.setExceptionType(throwable.getClass().getName());
            log.setParameters(JSON.toJSONString(params));
            log.setStackTrace(ExceptionUtil.stacktraceToString(throwable));
            log.setCreateTime(new Date());

            // 保存到数据库
            SpringUtils.getBean(IAsyncExceptionLogService.class).save(log);
        } catch (Exception e) {
            log.error("记录异步异常失败", e);
        }
    }

    private void notifyException(Method method, Throwable throwable) {
        // 发送邮件通知
        // 发送短信通知
        // 推送到监控系统
    }
}

/**
 * 异步配置
 */
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(200);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("async-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncExceptionHandler();
    }
}

🔧 异常使用

业务层异常

java
@Service
public class UserServiceImpl implements IUserService {

    /**
     * 新增用户
     */
    public Long insertUser(UserBo bo) {
        SysUser user = BeanUtil.toBean(bo, SysUser.class);

        // 验证用户名唯一性
        if (!checkUserNameUnique(user)) {
            throw new ServiceException("用户名已存在");
        }

        // 验证邮箱唯一性
        if (StringUtils.isNotEmpty(user.getEmail()) && !checkEmailUnique(user)) {
            throw new ServiceException("邮箱地址已存在");
        }

        // 验证手机号唯一性
        if (StringUtils.isNotEmpty(user.getPhonenumber()) && !checkPhoneUnique(user)) {
            throw new ServiceException("手机号码已存在");
        }

        try {
            return baseMapper.insertUser(user);
        } catch (Exception e) {
            log.error("新增用户失败", e);
            throw new ServiceException("新增用户失败", e);
        }
    }

    /**
     * 修改用户
     */
    public Boolean updateUser(UserBo bo) {
        SysUser user = BeanUtil.toBean(bo, SysUser.class);

        // 验证用户是否存在
        SysUser existUser = baseMapper.selectById(user.getUserId());
        if (ObjectUtil.isNull(existUser)) {
            throw new UserException("用户不存在", ErrorCode.USER_NOT_FOUND);
        }

        // 验证用户状态
        if (!"0".equals(existUser.getStatus())) {
            throw new UserException("账户已被禁用", ErrorCode.ACCOUNT_DISABLED);
        }

        // 系统管理员不能被修改
        if (existUser.isAdmin()) {
            throw new PermissionException("不允许修改系统管理员", ErrorCode.PERMISSION_DENIED);
        }

        return baseMapper.updateById(user) > 0;
    }

    /**
     * 删除用户
     */
    public Boolean deleteUser(Long userId) {
        // 验证用户是否存在
        SysUser user = baseMapper.selectById(userId);
        if (ObjectUtil.isNull(user)) {
            throw new UserException("用户不存在", ErrorCode.USER_NOT_FOUND);
        }

        // 系统管理员不能被删除
        if (user.isAdmin()) {
            throw new PermissionException("不允许删除系统管理员", ErrorCode.PERMISSION_DENIED);
        }

        // 检查用户是否有关联数据
        if (hasRelatedData(userId)) {
            throw new ServiceException("用户存在关联数据,不能删除");
        }

        return baseMapper.deleteById(userId) > 0;
    }

    /**
     * 重置密码
     */
    public Boolean resetPassword(String userName, String password) {
        SysUser user = baseMapper.selectUserByUserName(userName);
        if (ObjectUtil.isNull(user)) {
            throw new UserException("用户不存在", ErrorCode.USER_NOT_FOUND);
        }

        if (!"0".equals(user.getStatus())) {
            throw new UserException("账户已被禁用,无法重置密码", ErrorCode.ACCOUNT_DISABLED);
        }

        // 验证密码复杂度
        if (!PasswordUtils.isValidPassword(password)) {
            throw new ServiceException("密码不符合复杂度要求");
        }

        user.setPassword(SecurityUtils.encryptPassword(password));
        user.setUpdateTime(new Date());

        return baseMapper.updateById(user) > 0;
    }

    private boolean hasRelatedData(Long userId) {
        // 检查是否有关联的数据
        return false;
    }
}

Controller层异常

java
@RestController
@RequestMapping("/system/user")
@Validated
public class SysUserController extends BaseController {

    @Resource
    private IUserService userService;

    /**
     * 获取用户详细信息
     */
    @SaCheckPermission("system:user:query")
    @GetMapping("/{userId}")
    public R<UserVo> getInfo(@PathVariable("userId") @NotNull(message = "用户ID不能为空") Long userId) {
        try {
            UserVo user = userService.selectUserById(userId);
            return R.ok(user);
        } catch (UserException e) {
            return R.fail(e.getCode(), e.getMessage());
        } catch (Exception e) {
            log.error("获取用户信息失败", e);
            return R.fail("获取用户信息失败");
        }
    }

    /**
     * 新增用户
     */
    @SaCheckPermission("system:user:add")
    @Log(title = "用户管理", operType = DictOperType.INSERT)
    @RepeatSubmit()
    @PostMapping
    public R<Void> add(@Validated(AddGroup.class) @RequestBody UserBo user) {
        if (!userService.checkUserNameUnique(user)) {
            return R.fail("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
        }
        if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
            return R.fail("新增用户'" + user.getUserName() + "'失败,手机号码已存在");
        }
        if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
            return R.fail("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
        }

        user.setCreateBy(getUsername());
        user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
        return toAjax(userService.insertUser(user));
    }

    /**
     * 批量删除用户
     */
    @SaCheckPermission("system:user:remove")
    @Log(title = "用户管理", operType = DictOperType.DELETE)
    @DeleteMapping("/{userIds}")
    public R<Void> remove(@PathVariable Long[] userIds) {
        if (ArrayUtil.contains(userIds, getUserId())) {
            return R.fail("当前用户不能删除");
        }
        return toAjax(userService.deleteUserByIds(userIds));
    }
}

🚨 异常监控

异常统计

java
/**
 * 异常统计服务
 */
@Service
@Slf4j
public class ExceptionStatisticsService {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    private static final String EXCEPTION_COUNT_KEY = "exception:count:";
    private static final String EXCEPTION_DETAIL_KEY = "exception:detail:";

    /**
     * 记录异常
     */
    public void recordException(String requestUri, Exception exception) {
        String today = DateUtil.today();
        String exceptionType = exception.getClass().getSimpleName();

        // 统计今日异常总数
        String totalKey = EXCEPTION_COUNT_KEY + "total:" + today;
        redisTemplate.opsForValue().increment(totalKey);
        redisTemplate.expire(totalKey, Duration.ofDays(7));

        // 统计今日各类型异常数量
        String typeKey = EXCEPTION_COUNT_KEY + "type:" + exceptionType + ":" + today;
        redisTemplate.opsForValue().increment(typeKey);
        redisTemplate.expire(typeKey, Duration.ofDays(7));

        // 统计今日各接口异常数量
        String uriKey = EXCEPTION_COUNT_KEY + "uri:" + requestUri + ":" + today;
        redisTemplate.opsForValue().increment(uriKey);
        redisTemplate.expire(uriKey, Duration.ofDays(7));

        // 记录异常详情
        recordExceptionDetail(requestUri, exception);
    }

    private void recordExceptionDetail(String requestUri, Exception exception) {
        try {
            ExceptionDetail detail = new ExceptionDetail();
            detail.setRequestUri(requestUri);
            detail.setExceptionType(exception.getClass().getName());
            detail.setExceptionMessage(exception.getMessage());
            detail.setStackTrace(ExceptionUtil.stacktraceToString(exception));
            detail.setOccurTime(new Date());

            String detailKey = EXCEPTION_DETAIL_KEY + System.currentTimeMillis();
            redisTemplate.opsForValue().set(detailKey, detail, Duration.ofDays(1));
        } catch (Exception e) {
            log.error("记录异常详情失败", e);
        }
    }

    /**
     * 获取异常统计
     */
    public ExceptionStatistics getStatistics(String date) {
        ExceptionStatistics statistics = new ExceptionStatistics();

        // 获取总异常数
        String totalKey = EXCEPTION_COUNT_KEY + "total:" + date;
        Object totalCount = redisTemplate.opsForValue().get(totalKey);
        statistics.setTotalCount(totalCount != null ? (Integer) totalCount : 0);

        // 获取各类型异常统计
        Set<String> typeKeys = redisTemplate.keys(EXCEPTION_COUNT_KEY + "type:*:" + date);
        Map<String, Integer> typeStatistics = new HashMap<>();
        if (CollUtil.isNotEmpty(typeKeys)) {
            for (String key : typeKeys) {
                String type = key.split(":")[2];
                Object count = redisTemplate.opsForValue().get(key);
                typeStatistics.put(type, count != null ? (Integer) count : 0);
            }
        }
        statistics.setTypeStatistics(typeStatistics);

        return statistics;
    }
}

/**
 * 异常详情
 */
@Data
public class ExceptionDetail {
    private String requestUri;
    private String exceptionType;
    private String exceptionMessage;
    private String stackTrace;
    private Date occurTime;
}

/**
 * 异常统计
 */
@Data
public class ExceptionStatistics {
    private Integer totalCount;
    private Map<String, Integer> typeStatistics;
    private Map<String, Integer> uriStatistics;
}

异常告警

java
/**
 * 异常告警服务
 */
@Service
@Slf4j
public class ExceptionAlertService {

    @Resource
    private IMailService mailService;

    @Resource
    private ISmsService smsService;

    @Value("${alert.exception.threshold:10}")
    private Integer exceptionThreshold;

    @Value("${alert.exception.email}")
    private String alertEmail;

    @Value("${alert.exception.phone}")
    private String alertPhone;

    /**
     * 检查是否需要告警
     */
    @Scheduled(fixedRate = 300000) // 每5分钟检查一次
    public void checkExceptionAlert() {
        String today = DateUtil.today();
        String currentHour = DateUtil.format(new Date(), "yyyy-MM-dd:HH");

        // 检查最近一小时的异常数量
        String hourKey = EXCEPTION_COUNT_KEY + "hour:" + currentHour;
        Object hourCount = redisTemplate.opsForValue().get(hourKey);
        Integer count = hourCount != null ? (Integer) hourCount : 0;

        if (count >= exceptionThreshold) {
            sendAlert(count, currentHour);
        }
    }

    private void sendAlert(Integer count, String timeRange) {
        String subject = "系统异常告警";
        String content = String.format("时间范围: %s\n异常数量: %d\n已超过告警阈值: %d",
            timeRange, count, exceptionThreshold);

        // 发送邮件告警
        if (StringUtils.isNotBlank(alertEmail)) {
            try {
                mailService.sendSimpleMail(alertEmail, subject, content);
            } catch (Exception e) {
                log.error("发送异常告警邮件失败", e);
            }
        }

        // 发送短信告警
        if (StringUtils.isNotBlank(alertPhone)) {
            try {
                smsService.sendAlert(alertPhone, content);
            } catch (Exception e) {
                log.error("发送异常告警短信失败", e);
            }
        }
    }
}

ExceptionHandler 为 RuoYi-Plus 提供了完整的异常处理机制,通过统一的异常处理、详细的异常记录和实时的异常监控,确保系统的稳定性和可维护性。