---
name: error-handling-rules
description: "Spring Boot 全局异常集中处理与拦截规范。适用于所有控制器层、服务层异常捕获与自定义异常响应的编写。"
globs: ["src/main/java/**/*.java", "**/exception/*.java", "**/handler/*.java"]
alwaysApply: false
updated: 2026-05-22
---

# Spring Boot 全局异常集中处理与拦截规范

> [!IMPORTANT]
> 容错设计与日志可追踪能力是衡量生产级后端系统的核心维度。AI 助手在设计 Spring Boot 接口报错流程时，必须杜绝裸吞异常与粗暴打印，建立全系统 `@RestControllerAdvice` 全局集中拦截、安全脱敏及具备完整 TraceId 追溯的容错架构。

## 1. 适用场景

当您在控制器编写业务控制器（Controllers）、设计业务逻辑层自定义 Runtime 异常类，或开发 API 异常集中拦截器（Exception Handlers）时生效。

---

## 2. 操作规则

### 2.1 全局集中捕获 (Centralized Exception Advice)
- 业务控制器（Controller）和业务逻辑服务（Service）中**禁止使用冗长的大 `try-catch` 包裹**。
- 所有业务层异常应默认直接向上抛出自定义 Runtime 异常（如继承自 `BusinessException` 的派生类），交由 Spring 统一提供的 **`@RestControllerAdvice`** 在外层进行无侵入的统一截获与 JSON 响应组装。

### 2.2 异常分类与 HTTP 状态码对齐 (Exception Leveling)
- 必须针对常见错误定义清晰、职责明确的异常子类，并与 HTTP 状态码对齐：
  - `IllegalArgumentException` / `MethodArgumentNotValidException` (参数非法) -> HTTP 400
  - `UnauthorizedException` (未登录/认证失败) -> HTTP 401
  - `ForbiddenException` (无权操作/越权) -> HTTP 403
  - `ResourceNotFoundException` (资源不存在) -> HTTP 404
  - `BusinessException` (常规业务流程不满足) -> HTTP 422，或按语义映射到更具体的 4xx 状态码

### 2.3 敏感堆栈信息脱敏 (Data Masking for Security)
- 针对系统崩溃类未知异常（如 `SQLException`、`NullPointerException`、`RedisConnectionException`），全局处理器必须统一捕获并返回 `HTTP 500`。
- 对外输出的 JSON 响应中文案必须严格**安全脱敏**（如统一为“系统繁忙，请稍后再试”），绝对禁止将具体的 SQL 堆栈、Java 类路径、数据库字段名等抛给前端，规避重大安全审查漏洞。
- 必须使用 `slf4j` 日志组件在本地或日志服务器中，使用 `log.error("context message", e)` 将原始的长堆栈调用轨迹进行完整存档，便于安全排查。

---

## 3. 禁止事项

- 绝对禁止在代码中裸写 `try { ... } catch (Exception e) {}` 把未知故障完全闷死在内存中。如果捕获了异常，必须重新抛出或使用 logger 记录。
- 严禁在 `@ExceptionHandler` 中将未脱敏的底层数据库连接异常明文直接转成字符串作为 `message` 返回给客户端。
- 严禁将失败场景包装成 `HTTP 200` 再依赖自定义业务 Code 表达错误；失败响应必须与 `api-design-rules` 保持一致，使用标准 4xx/5xx HTTP 状态码。

---

## 4. 验证方式

- 运行单元测试模拟触发各类异常分支，校验返回的 HTTP 状态码和 JSON 结构。

---

## 5. 代码对比示例

### ❌ 错误示例（Controller 充斥 try-catch、直接吞异常、向前端裸露 SQL 堆栈引发安全隐患）

```java
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
    @Autowired
    private UserService userService;

    // 违反规则 2.1：充斥冗余的 try-catch，难以维护
    // 违反规则 2.3：直接向客户端暴露底层 SQL 崩溃信息，HTTP 状态码没有合理对齐
    @GetMapping("/{id}")
    public ResponseEntity<Object> getUserDetail(@PathVariable Long id) {
        try {
            User user = userService.findById(id);
            return ResponseEntity.ok(user);
        } catch (UserNotFoundException e) {
            // 违反 HTTP 状态码规则，404 写成 200
            return ResponseEntity.ok(new ErrorResult("user_not_found")); 
        } catch (Exception e) {
            // 严重安全漏洞：把数据库报错甚至 SQL 语法错泄露给前端！
            return ResponseEntity.status(500).body(new ErrorResult("Database crash: " + e.getMessage()));
        }
    }
}
```

###  正确方向（抛出语义化异常、全局拦截器自动脱敏、堆栈完整归档）

```java
// 1. 控制器层：职责单一，完全不用写 try-catch，业务出错直接抛出异常冒泡
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public User getUserDetail(@PathVariable Long id) {
        if (id == null || id <= 0) {
            // 遵循规则 2.2：抛出参数非法异常
            throw new InvalidParameterException("用户 ID 不合法");
        }
        return userService.findById(id); // 内部查不到时会自动抛出 ResourceNotFoundException
    }
}

// 2. 全局异常集中处理拦截器：截获漏网之鱼，脱敏输出并安全存档
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    // 遵循规则 2.2：处理已知业务自定义异常，并返回对齐的 HTTP 状态码
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
        log.warn("Business execution blocked: {}", e.getMessage());
        return ResponseEntity
            .status(HttpStatus.UNPROCESSABLE_ENTITY)
            .body(new ErrorResponse(e.getErrorCode(), e.getMessage()));
    }

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException e) {
        return ResponseEntity
            .status(HttpStatus.NOT_FOUND)
            .body(new ErrorResponse("RESOURCE_NOT_FOUND", e.getMessage()));
    }

    // 遵循规则 2.3：彻底截获系统崩溃类未知异常（如 SQL 崩溃、NullPointer）
    @ExceptionHandler(Throwable.class)
    public ResponseEntity<ErrorResponse> handleUnexpectedThrowable(Throwable e) {
        // 遵循规则 2.3：使用 slf4j 打印完整调用栈日志，便于本地/ELK 排查
        log.error("Internal system panic encountered: ", e);
        
        // 遵循规则 2.3：脱敏返回，杜绝将敏感的 SQL 或报错路径暴露给终端用户
        return ResponseEntity
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(new ErrorResponse(
                "SYSTEM_INNER_ERROR", 
                "服务器繁忙，请稍候再试或联系技术客服。"
            ));
    }
}
```
