# D8D Starter 错误处理规范

## 版本信息
| 版本 | 日期 | 描述 | 作者 |
|------|------|------|------|
| 1.0 | 2025-09-15 | 初始错误处理规范 | Sarah (PO) |

## 1. 错误处理原则

### 1.1 核心原则
- **一致性**: 所有错误响应格式统一
- **安全性**: 不泄露敏感信息到客户端
- **可操作性**: 错误信息应指导用户或开发者
- **可追溯性**: 错误应包含足够上下文用于调试

### 1.2 错误分类

#### 按严重程度
- 🔴 **致命错误**: 系统无法继续运行
- 🟡 **业务错误**: 用户操作失败，可恢复
- 🔵 **客户端错误**: 用户输入或配置问题
- ⚪ **信息性错误**: 警告或提示信息

#### 按来源
- **验证错误**: 输入数据验证失败
- **业务逻辑错误**: 业务规则违反
- **系统错误**: 基础设施或第三方服务故障
- **网络错误**: 连接超时或中断

## 2. 错误响应格式

### 2.1 标准错误响应
```typescript
// 成功响应
{
  "success": true,
  "data": { /* 业务数据 */ },
  "message": "操作成功"
}

// 错误响应
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "输入数据验证失败",
    "details": [
      {
        "field": "email",
        "message": "邮箱格式不正确"
      }
    ],
    "timestamp": "2025-09-15T10:30:00Z",
    "requestId": "req_1234567890"
  }
}
```

### 2.2 HTTP状态码映射

| 错误类型 | HTTP状态码 | 错误代码 | 描述 |
|----------|------------|----------|------|
| 验证错误 | 400 | `VALIDATION_ERROR` | 输入数据验证失败 |
| 未授权 | 401 | `UNAUTHORIZED` | 需要认证 |
| 权限不足 | 403 | `FORBIDDEN` | 权限不足 |
| 资源不存在 | 404 | `NOT_FOUND` | 资源未找到 |
| 业务错误 | 409 | `BUSINESS_ERROR` | 业务规则冲突 |
| 系统错误 | 500 | `INTERNAL_ERROR` | 服务器内部错误 |
| 服务不可用 | 503 | `SERVICE_UNAVAILABLE` | 服务暂时不可用 |

## 3. 错误代码规范

### 3.1 错误代码命名约定
- 使用 `SCREAMING_SNAKE_CASE`
- 前缀表示错误类别
- 后缀表示具体错误

### 3.2 标准错误代码

#### 认证错误 (AUTH_*)
```typescript
export const AUTH_ERRORS = {
  UNAUTHORIZED: 'AUTH_UNAUTHORIZED',
  INVALID_CREDENTIALS: 'AUTH_INVALID_CREDENTIALS',
  TOKEN_EXPIRED: 'AUTH_TOKEN_EXPIRED',
  TOKEN_INVALID: 'AUTH_TOKEN_INVALID',
  PERMISSION_DENIED: 'AUTH_PERMISSION_DENIED'
} as const;
```

#### 验证错误 (VALIDATION_*)
```typescript
export const VALIDATION_ERRORS = {
  REQUIRED_FIELD: 'VALIDATION_REQUIRED_FIELD',
  INVALID_EMAIL: 'VALIDATION_INVALID_EMAIL',
  INVALID_PASSWORD: 'VALIDATION_INVALID_PASSWORD',
  UNIQUE_CONSTRAINT: 'VALIDATION_UNIQUE_CONSTRAINT',
  OUT_OF_RANGE: 'VALIDATION_OUT_OF_RANGE'
} as const;
```

#### 业务错误 (BUSINESS_*)
```typescript
export const BUSINESS_ERRORS = {
  USER_NOT_FOUND: 'BUSINESS_USER_NOT_FOUND',
  INSUFFICIENT_BALANCE: 'BUSINESS_INSUFFICIENT_BALANCE',
  OPERATION_NOT_ALLOWED: 'BUSINESS_OPERATION_NOT_ALLOWED',
  RESOURCE_CONFLICT: 'BUSINESS_RESOURCE_CONFLICT'
} as const;
```

#### 系统错误 (SYSTEM_*)
```typescript
export const SYSTEM_ERRORS = {
  DATABASE_ERROR: 'SYSTEM_DATABASE_ERROR',
  NETWORK_ERROR: 'SYSTEM_NETWORK_ERROR',
  EXTERNAL_SERVICE_ERROR: 'SYSTEM_EXTERNAL_SERVICE_ERROR',
  CONFIGURATION_ERROR: 'SYSTEM_CONFIGURATION_ERROR'
} as const;
```

## 4. 实现指南

### 4.1 后端错误处理

#### 错误类定义
```typescript
// src/shared/errors/AppError.ts
export class AppError extends Error {
  public readonly code: string;
  public readonly statusCode: number;
  public readonly details?: any;
  public readonly timestamp: Date;

  constructor(
    code: string,
    message: string,
    statusCode: number = 500,
    details?: any
  ) {
    super(message);
    this.code = code;
    this.statusCode = statusCode;
    this.details = details;
    this.timestamp = new Date();

    // 保持正确的堆栈跟踪
    Error.captureStackTrace(this, this.constructor);
  }

  toJSON() {
    return {
      code: this.code,
      message: this.message,
      details: this.details,
      timestamp: this.timestamp.toISOString()
    };
  }
}

// 特定错误类
export class ValidationError extends AppError {
  constructor(message: string, details?: any) {
    super('VALIDATION_ERROR', message, 400, details);
  }
}

export class NotFoundError extends AppError {
  constructor(resource: string, id?: string | number) {
    const message = id
      ? `${resource} with ID ${id} not found`
      : `${resource} not found`;
    super('NOT_FOUND', message, 404);
  }
}
```

#### 错误处理中间件
```typescript
// src/server/middleware/errorHandler.ts
import { Context } from 'hono';
import { AppError } from '../../shared/errors/AppError';

export async function errorHandler(err: Error, c: Context) {
  // 记录错误
  console.error('Error:', {
    message: err.message,
    stack: err.stack,
    url: c.req.url,
    method: c.req.method,
    timestamp: new Date().toISOString()
  });

  // AppError 实例
  if (err instanceof AppError) {
    return c.json(
      {
        success: false,
        error: {
          code: err.code,
          message: err.message,
          details: err.details,
          timestamp: err.timestamp.toISOString(),
          requestId: c.get('requestId')
        }
      },
      err.statusCode
    );
  }

  // 未知错误（生产环境隐藏细节）
  const isProduction = process.env.NODE_ENV === 'production';

  return c.json(
    {
      success: false,
      error: {
        code: 'INTERNAL_ERROR',
        message: isProduction ? 'Internal server error' : err.message,
        timestamp: new Date().toISOString(),
        requestId: c.get('requestId')
      }
    },
    500
  );
}
```

#### 使用示例
```typescript
// 在服务中使用
export class UserService {
  async getUserById(id: number) {
    const user = await User.findOne({ where: { id } });

    if (!user) {
      throw new NotFoundError('User', id);
    }

    return user;
  }

  async createUser(data: CreateUserDto) {
    // 验证输入
    const errors = validateUserData(data);
    if (errors.length > 0) {
      throw new ValidationError('Invalid user data', errors);
    }

    // 检查唯一性
    const existingUser = await User.findOne({
      where: [{ email: data.email }, { username: data.username }]
    });

    if (existingUser) {
      throw new BusinessError('USER_ALREADY_EXISTS', 'User with this email or username already exists', 409);
    }

    // 创建用户
    try {
      return await User.create(data).save();
    } catch (error) {
      throw new DatabaseError('Failed to create user', error);
    }
  }
}
```

### 4.2 前端错误处理

#### 错误处理钩子
```typescript
// src/client/hooks/useErrorHandler.ts
import { toast } from 'sonner';

export function useErrorHandler() {
  const handleError = (error: unknown) => {
    console.error('Application error:', error);

    if (error instanceof Error) {
      // 显示用户友好的错误消息
      toast.error(getUserFriendlyMessage(error));
    } else {
      toast.error('发生未知错误，请重试');
    }
  };

  const getUserFriendlyMessage = (error: Error): string => {
    const message = error.message.toLowerCase();

    if (message.includes('network')) {
      return '网络连接失败，请检查网络设置';
    }

    if (message.includes('validation')) {
      return '输入数据验证失败，请检查表单';
    }

    if (message.includes('permission') || message.includes('auth')) {
      return '权限不足，请重新登录';
    }

    return error.message || '操作失败，请重试';
  };

  return { handleError, getUserFriendlyMessage };
}
```

#### API客户端错误处理
```typescript
// src/client/api.ts
import { toast } from 'sonner';

const axiosFetch = async (url: RequestInfo | URL, init?: RequestInit) => {
  try {
    const response = await axios.request({
      url: url.toString(),
      method: init?.method || 'GET',
      headers: init?.headers,
      data: init?.body,
    });

    // 处理错误响应
    if (response.status >= 400) {
      const errorData = response.data?.error || {};
      throw new ApiError(
        errorData.message || '请求失败',
        response.status,
        errorData.code,
        errorData.details
      );
    }

    return createResponse(response);
  } catch (error) {
    if (axios.isAxiosError(error)) {
      const errorData = error.response?.data?.error || {};
      throw new ApiError(
        errorData.message || error.message,
        error.response?.status || 500,
        errorData.code,
        errorData.details
      );
    }
    throw error;
  }
};

class ApiError extends Error {
  constructor(
    message: string,
    public status: number,
    public code?: string,
    public details?: any
  ) {
    super(message);
    this.name = 'ApiError';
  }
}
```

## 5. 日志和监控

### 5.1 错误日志格式
```typescript
// 错误日志示例
{
  "level": "error",
  "timestamp": "2025-09-15T10:30:00.000Z",
  "message": "User creation failed",
  "code": "VALIDATION_ERROR",
  "stack": "Error: Invalid email format...",
  "context": {
    "userId": 123,
    "email": "invalid-email",
    "requestId": "req_1234567890",
    "url": "/api/v1/users",
    "method": "POST"
  }
}
```

### 5.2 监控配置
```typescript
// 错误监控集成
import * as Sentry from '@sentry/node';
import * as Tracing from '@sentry/tracing';

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV,
  tracesSampleRate: 1.0,
});

// 在错误处理中间件中
if (process.env.NODE_ENV === 'production') {
  Sentry.captureException(err, {
    extra: {
      url: c.req.url,
      method: c.req.method,
      requestId: c.get('requestId')
    }
  });
}
```

## 6. 测试错误场景

### 6.1 错误测试示例
```typescript
// 测试验证错误
describe('UserService - Error Handling', () => {
  it('should throw ValidationError for invalid email', async () => {
    const invalidData = { email: 'invalid', password: '123' };

    await expect(userService.createUser(invalidData))
      .rejects
      .toThrow(ValidationError);

    await expect(userService.createUser(invalidData))
      .rejects
      .toMatchObject({
        code: 'VALIDATION_ERROR',
        statusCode: 400
      });
  });

  it('should throw NotFoundError for non-existent user', async () => {
    await expect(userService.getUserById(999))
      .rejects
      .toThrow(NotFoundError);
  });
});

// 测试错误响应格式
describe('Error Handler Middleware', () => {
  it('should return formatted error response', async () => {
    const server = createTestServer();

    const response = await server.inject({
      method: 'GET',
      url: '/api/v1/users/999'
    });

    expect(response.statusCode).toBe(404);
    expect(response.json()).toEqual({
      success: false,
      error: {
        code: 'NOT_FOUND',
        message: 'User with ID 999 not found',
        timestamp: expect.any(String),
        requestId: expect.any(String)
      }
    });
  });
});
```

## 7. 最佳实践清单

### 7.1 错误处理检查清单
- [ ] 所有错误都有明确的错误代码
- [ ] 错误消息对用户友好且可操作
- [ ] 生产环境不泄露敏感信息
- [ ] 错误包含足够的调试上下文
- [ ] 错误响应格式统一
- [ ] 适当的HTTP状态码
- [ ] 错误日志记录完整
- [ ] 前端错误处理用户友好
- [ ] 测试覆盖错误场景

### 7.2 安全注意事项
- ❌ 不要返回堆栈跟踪到客户端
- ❌ 不要泄露数据库错误细节
- ❌ 不要暴露敏感配置信息
- ✅ 使用通用的错误消息
- ✅ 记录详细错误到服务器日志
- ✅ 使用请求ID关联错误

---

**最后更新**: 2025-09-15
**维护者**: 开发团队
**文档状态**: 正式版