# 错误处理

## 规则（Rules）

# 错误处理 - 规范

## 后端错误对象结构
```javascript
{
  code: 10001,        // 错误码
  message: '错误描述',  // 用户可读的错误描述
  detail: '详细信息'    // 调试用（不返回给前端）
}
```

## 错误码范围

| 范围 | 说明 |
|:-----|:-----|
| 0 | 成功 |
| 10000–19999 | 通用错误（参数错误、认证失败） |
| 20000–29999 | 业务逻辑错误 |
| 30000–39999 | 第三方服务错误 |
| 50000–59999 | 系统级错误 |

## 日志级别

| 级别 | 触发场景 |
|:-----|:---------|
| `debug` | 开发环境调试信息 |
| `info` | 请求入口/出口、状态变更 |
| `warn` | 参数异常、降级处理 |
| `error` | 业务异常、第三方调用失败 |
| `fatal` | 数据库连接失败、内存溢出 |

## 日志内容要求
```javascript
// ✅ 好的日志
logger.info('用户登录成功', { userId: 123, ip: '192.168.1.1' });
logger.error('数据库查询失败', { sql: 'SELECT ...', error: err.message });

// ❌ 差的日志
logger.info('成功');
```

## 禁止行为
- ❌ 禁止吞掉异常（空的 catch 块）
- ❌ 禁止将敏感信息（密码、token）写入日志
- ❌ 禁止在前端暴露后端错误栈
- ❌ 禁止用 `console.log` 替代日志框架

## 方法（Methods）

# 错误处理 - 方法

## 前置条件
- [ ] 已确定项目使用的日志框架
- [ ] 已定义错误码表

## 流程概览
```
定义错误类 → 抛出错误 → 全局捕获 → 格式化响应 → 记录日志
```

## 后端错误处理

### 1. 自定义错误类
```javascript
class AppError extends Error {
  constructor(code, message, detail = '') {
    super(message);
    this.code = code;
    this.detail = detail;
    this.name = 'AppError';
  }
}

// 使用
throw new AppError(10001, '用户不存在', `userId: ${id}`);
```

### 2. 全局错误处理中间件
```javascript
app.use(async (err, req, res, next) => {
  if (err instanceof AppError) {
    return res.json({ code: err.code, msg: err.message, data: null });
  }
  // 未知错误：记录日志，返回 500
  logger.error('未捕获错误', err);
  return res.status(500).json({ code: 50000, msg: '服务器内部错误', data: null });
});
```

### 3. 异步错误包装
```javascript
const asyncHandler = (fn) => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next);

app.get('/api/users', asyncHandler(async (req, res) => {
  const users = await userService.list();
  res.json({ code: 0, msg: '成功', data: users });
}));
```

## 前端错误处理

### 1. HTTP 错误拦截
```javascript
http.interceptors.response.use(
  (response) => {
    const { code, msg } = response.data;
    if (code !== 0) {
      message.error(msg);
      return Promise.reject(new Error(msg));
    }
    return response.data;
  },
  (error) => {
    if (error.response) {
      switch (error.response.status) {
        case 401: // 跳转登录
        case 403: // 无权限提示
        case 500: // 服务器错误提示
      }
    }
    return Promise.reject(error);
  }
);
```

### 2. React 错误边界
```jsx
class ErrorBoundary extends React.Component {
  state = { hasError: false, error: null };
  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }
  componentDidCatch(error, info) {
    logger.error('组件渲染错误', error, info);
  }
  render() {
    if (this.state.hasError) {
      return <ErrorFallback error={this.state.error} />;
    }
    return this.props.children;
  }
}
```

## 技巧（Tips）

# 错误处理 - 技巧

## 1. 区分业务错误和系统错误
业务错误（如参数校验失败）返回正常 HTTP 200，通过 `code !== 0` 表示；系统错误（如 DB 连接失败）返回 HTTP 500。

## 2. 日志脱敏
```javascript
function maskSensitive(data) {
  if (data.password) data.password = '***';
  if (data.phone) data.phone = data.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
  return data;
}
```

## 3. API 错误提示要有用
```javascript
// ❌ 没有用的提示
"参数错误"

// ✅ 有用的提示
"参数错误：用户名为必填项，长度 2-20 个字符"
```

## 4. 错误码维护建议
- 在项目中维护一份错误码表文档
- 新增错误码时先查表，避免重复
- 错误码一旦发布不做删除（标记为废弃）
