# 用户反馈系统 API

**版本**: v1.0.3  
**基础路径**: `/api/v1`  
**模块标识**: `platform_feedback`  
**状态**: 🟢 已实现  
**创建日期**: 2025-12-08  
**更新日期**: 2026-01-07

---

## 概述

用户反馈系统 API 提供反馈收集、查询和管理功能。所有接口遵循 FFOA 统一 API 规范。

### 接口统计

| 分类 | 接口数 | 说明 |
|------|--------|------|
| 用户端接口 | 3 | 提交反馈、查看我的反馈 |
| 管理端接口 | 6 | 反馈列表、详情、状态管理、更新、删除 |
| 统计接口 | 1 | 反馈统计（已实现） |

**总计**: 10 个 API 端点

### 通用约定

| 约定 | 说明 |
|------|------|
| **基础路径** | `/api/v1` |
| **时间格式** | UTC ISO 8601（如 `2025-12-08T10:30:00.123Z`） |
| **认证方式** | Bearer Token（`Authorization: Bearer <token>`） |
| **请求头** | `Content-Type: application/json` |
| **响应格式** | 统一 `{ success, data, message?, timestamp, path }` 结构 |

### 多区域支持

反馈记录自动继承提交用户的 `metadata.region` 字段。区域管理员只能查看和管理本区域的反馈。

| 角色 | 数据范围 |
|------|----------|
| 普通用户 | 仅自己的反馈 |
| 区域管理员 (`FEEDBACK_ADMIN_CN`) | 本区域所有反馈 |
| 全局管理员 (`ADMINISTRATOR`, `FEEDBACK_ADMIN`) | 所有区域反馈 |

### 相关文档

- [PRD 产品需求文档](./01-prd.md) - 完整的功能规格和业务规则
- [ARCHITECTURE 架构设计](./03-architecture.md) - 系统架构和技术设计
- [后端规范入口](../../../.agents/skills/backend-main/references/backend-standards.md) - 通用 API 规范

---

## 认证与权限

### 认证方式

所有接口（除公开接口外）需要在请求头中携带 JWT Token：

```http
Authorization: Bearer <access_token>
```

### 权限矩阵

| 权限代码 | 说明 | 适用接口 |
|---------|------|---------|
| `feedback:read` | 查看所有反馈（管理员） | GET /feedbacks, GET /feedbacks/:id |
| `feedback:update` | 更新反馈（管理员） | PATCH /feedbacks/:id, PATCH /feedbacks/:id/status |
| `feedback:delete` | 删除反馈（管理员） | DELETE /feedbacks/:id |

> 注意：用户端接口（`/feedbacks/my/*`）仅需登录即可访问，不需要额外权限。

### 权限装饰器

```typescript
@RequirePermissions('feedback:read')    // 查看反馈
@RequirePermissions('feedback:update')  // 更新反馈
@RequirePermissions('feedback:delete')  // 删除反馈
```

---

## 公共类型定义

### 统一响应格式

#### 成功响应

```typescript
interface ApiResponse<T> {
  success: true;
  data: T;
  message?: string;
  timestamp: string;       // ISO 8601 格式
  path: string;            // 请求路径
}
```

#### 分页响应

```typescript
interface PaginatedResponse<T> {
  success: true;
  data: {
    items: T[];
    total: number;
    page: number;
    limit: number;
    totalPages: number;
    hasNext: boolean;
    hasPrev: boolean;
  };
  message?: string;
  timestamp: string;
  path: string;
}
```

#### 错误响应

```typescript
interface ApiError {
  success: false;
  error: {
    code: string;          // 机器可读错误码
    message: string;       // 人类可读消息
    details?: any;         // 详细信息
    field?: string;        // 字段级错误
    errors?: FieldError[]; // 多字段验证错误
  };
  timestamp: string;
  path: string;
  method: string;
  statusCode: number;
}
```

### 枚举类型

```typescript
// 反馈类型
enum FeedbackType {
  BUG = 'BUG',                    // 问题反馈
  FEATURE = 'FEATURE',            // 功能建议
  IMPROVEMENT = 'IMPROVEMENT',    // 改进建议
  OTHER = 'OTHER'                 // 其他
}

// 反馈状态
enum FeedbackStatus {
  PENDING = 'PENDING',            // 待处理
  IN_PROGRESS = 'IN_PROGRESS',    // 处理中
  RESOLVED = 'RESOLVED',          // 已解决
  CLOSED = 'CLOSED'               // 已关闭
}

// 优先级
enum FeedbackPriority {
  LOW = 'LOW',                    // 低
  MEDIUM = 'MEDIUM',              // 中
  HIGH = 'HIGH',                  // 高
  URGENT = 'URGENT'               // 紧急
}

// 区域
enum Region {
  CN = 'CN',                      // 中国
  US = 'US',                      // 美国
  EU = 'EU',                      // 欧洲
  APAC = 'APAC'                   // 亚太
}
```

### 通用查询参数

```typescript
interface CommonQueryParams {
  // 分页
  page?: number;           // 页码，从 1 开始，默认 1
  pageSize?: number;       // 每页数量，默认 20，最大 100
  
  // 排序
  sortBy?: string;         // 排序字段，默认 'createdAt'
  sortOrder?: 'asc' | 'desc';  // 排序方向，默认 'desc'
  
  // 通用筛选
  keyword?: string;        // 模糊搜索（标题、内容）
}
```

---

## API 列表

### 1️⃣ 用户端接口 (3 个)

| 方法 | 路径 | 说明 | 权限 |
|------|------|------|------|
| POST | `/feedbacks` | 提交反馈 | 已登录 |
| GET | `/feedbacks/my` | 获取我的反馈列表 | 已登录 |
| GET | `/feedbacks/my/:id` | 获取我的反馈详情 | 已登录 |

### 2️⃣ 管理端接口 (6 个)

| 方法 | 路径 | 说明 | 权限 |
|------|------|------|------|
| GET | `/feedbacks` | 获取所有反馈列表 | `feedback:read` |
| GET | `/feedbacks/:id` | 获取反馈详情 | `feedback:read` |
| PATCH | `/feedbacks/:id/status` | 更新反馈状态 | `feedback:update` |
| PATCH | `/feedbacks/:id` | 更新反馈信息 | `feedback:update` |
| DELETE | `/feedbacks/:id` | 删除反馈（软删除） | `feedback:delete` |
| POST | `/feedbacks/batch-status` | 批量更新状态 | `feedback:update` |

### 3️⃣ 统计接口 (1 个)

| 方法 | 路径 | 说明 | 权限 |
|------|------|------|------|
| GET | `/feedbacks/stats` | 获取反馈统计 | `feedback:read` |

---

## 接口详情

### 1. 用户端接口

#### POST /feedbacks

提交新的反馈。

**权限**: 已登录

**请求体**:

```typescript
interface CreateFeedbackDto {
  type: FeedbackType;        // 必填，反馈类型
  title: string;             // 必填，标题（1-200字符）
  content: string;           // 必填，详细内容（1-5000字符）
  attachments?: string[];    // 可选，附件 URL 数组（最多5个）
  pageUrl?: string;          // 可选，当前页面 URL（自动获取）
  userAgent?: string;        // 可选，浏览器信息（自动获取）
}
```

**示例请求**:

```json
{
  "type": "BUG",
  "title": "登录页面无法加载",
  "content": "点击登录按钮后，页面一直显示加载中，无法完成登录操作。\n\n复现步骤：\n1. 打开登录页面\n2. 输入用户名和密码\n3. 点击登录按钮\n4. 页面一直转圈",
  "attachments": [
    "https://storage.example.com/feedbacks/screenshot1.png"
  ],
  "pageUrl": "/login",
  "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)..."
}
```

**成功响应** (201):

```json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "type": "BUG",
    "title": "登录页面无法加载",
    "content": "点击登录按钮后...",
    "attachments": ["https://storage.example.com/feedbacks/screenshot1.png"],
    "status": "PENDING",
    "priority": null,
    "adminReply": null,
    "createdAt": "2025-12-08T10:30:00.000Z",
    "updatedAt": "2025-12-08T10:30:00.000Z"
  },
  "message": "反馈提交成功",
  "timestamp": "2025-12-08T10:30:00.000Z",
  "path": "/api/v1/feedbacks"
}
```

**错误响应**:

| 错误码 | HTTP | 说明 |
|--------|------|------|
| `VALIDATION_ERROR` | 400 | 请求参数验证失败 |
| `FEEDBACK_ATTACHMENTS_EXCEED_LIMIT` | 400 | 附件数量超过 5 个 |

**验证错误示例**:

```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "请求参数验证失败",
    "errors": [
      {
        "field": "type",
        "message": "反馈类型必须是 BUG/FEATURE/IMPROVEMENT/OTHER 之一",
        "value": "INVALID",
        "constraint": "isEnum"
      }
    ]
  },
  "timestamp": "2025-12-08T10:30:00.000Z",
  "path": "/api/v1/feedbacks",
  "method": "POST",
  "statusCode": 400
}
```

**业务规则**:
- `type`、`title`、`content` 为必填字段
- `title` 长度限制 1-200 字符
- `content` 长度限制 1-5000 字符
- `attachments` 最多 5 个，且需为合法 URL
- 反馈自动继承用户的 `metadata.region` 字段
- 初始状态为 `PENDING`

---

#### GET /feedbacks/my

获取当前用户提交的反馈列表。

**权限**: 已登录

**Query 参数**:

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `page` | number | 否 | 页码，默认 1 |
| `pageSize` | number | 否 | 每页数量，默认 20 |
| `status` | string | 否 | 状态筛选：PENDING/IN_PROGRESS/RESOLVED/CLOSED |
| `type` | string | 否 | 类型筛选：BUG/FEATURE/IMPROVEMENT/OTHER |
| `sortBy` | string | 否 | 排序字段，默认 createdAt |
| `sortOrder` | string | 否 | 排序方向：asc/desc，默认 desc |

**成功响应** (200):

```json
{
  "success": true,
  "data": {
    "items": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "type": "BUG",
        "title": "登录页面无法加载",
        "status": "IN_PROGRESS",
        "priority": "HIGH",
        "adminReply": "我们已经定位到问题，正在修复中。",
        "createdAt": "2025-12-08T10:30:00.000Z",
        "updatedAt": "2025-12-08T11:00:00.000Z"
      },
      {
        "id": "550e8400-e29b-41d4-a716-446655440001",
        "type": "FEATURE",
        "title": "希望增加暗色模式",
        "status": "PENDING",
        "priority": null,
        "adminReply": null,
        "createdAt": "2025-12-07T09:00:00.000Z",
        "updatedAt": "2025-12-07T09:00:00.000Z"
      }
    ],
    "total": 15,
    "page": 1,
    "limit": 20,
    "totalPages": 1,
    "hasNext": false,
    "hasPrev": false
  },
  "timestamp": "2025-12-08T12:00:00.000Z",
  "path": "/api/v1/feedbacks/my"
}
```

**业务规则**:
- 只返回当前用户自己提交的反馈
- 不返回 `adminNote`、`assigneeId` 等管理员专用字段
- 软删除的反馈不返回

---

#### GET /feedbacks/my/:id

获取当前用户提交的单条反馈详情。

**权限**: 已登录

**路径参数**:

| 参数 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | 反馈 ID |

**成功响应** (200):

```json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "type": "BUG",
    "title": "登录页面无法加载",
    "content": "点击登录按钮后，页面一直显示加载中...",
    "attachments": ["https://storage.example.com/feedbacks/screenshot1.png"],
    "status": "IN_PROGRESS",
    "priority": "HIGH",
    "adminReply": "我们已经定位到问题，正在修复中。预计今天内解决。",
    "createdAt": "2025-12-08T10:30:00.000Z",
    "updatedAt": "2025-12-08T11:00:00.000Z"
  },
  "timestamp": "2025-12-08T12:00:00.000Z",
  "path": "/api/v1/feedbacks/my/550e8400-e29b-41d4-a716-446655440000"
}
```

**错误响应**:

| 错误码 | HTTP | 说明 |
|--------|------|------|
| `FEEDBACK_NOT_FOUND` | 404 | 反馈不存在 |
| `FEEDBACK_ACCESS_DENIED` | 403 | 无权查看该反馈（不是自己的） |

---

### 2. 管理端接口

#### GET /feedbacks

获取所有反馈列表（管理员）。

**权限**: `feedback:read`

**Query 参数**:

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `page` | number | 否 | 页码，默认 1 |
| `pageSize` | number | 否 | 每页数量，默认 20，最大 100 |
| `keyword` | string | 否 | 搜索关键词（匹配标题、内容） |
| `status` | string | 否 | 状态筛选 |
| `type` | string | 否 | 类型筛选 |
| `priority` | string | 否 | 优先级筛选 |
| `region` | string | 否 | 区域筛选（全局管理员可用） |
| `userId` | string | 否 | 提交用户 ID 筛选 |
| `assigneeId` | string | 否 | 处理人 ID 筛选 |
| `startDate` | string | 否 | 开始日期（ISO 8601） |
| `endDate` | string | 否 | 结束日期（ISO 8601） |
| `sortBy` | string | 否 | 排序字段，默认 createdAt |
| `sortOrder` | string | 否 | 排序方向：asc/desc，默认 desc |

**成功响应** (200):

```json
{
  "success": true,
  "data": {
    "items": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "type": "BUG",
        "title": "登录页面无法加载",
        "content": "点击登录按钮后...",
        "attachments": ["https://..."],
        "pageUrl": "/login",
        "userAgent": "Mozilla/5.0...",
        "status": "IN_PROGRESS",
        "priority": "HIGH",
        "adminNote": "需要后端配合排查",
        "adminReply": "我们已经定位到问题，正在修复中。",
        "assignee": {
          "id": "user-admin-1",
          "displayName": "管理员小张"
        },
        "resolvedAt": null,
        "user": {
          "id": "user-1",
          "displayName": "张三",
          "email": "zhangsan@example.com"
        },
        "region": "CN",
        "createdAt": "2025-12-08T10:30:00.000Z",
        "updatedAt": "2025-12-08T11:00:00.000Z"
      }
    ],
    "total": 156,
    "page": 1,
    "limit": 20,
    "totalPages": 8,
    "hasNext": true,
    "hasPrev": false
  },
  "timestamp": "2025-12-08T12:00:00.000Z",
  "path": "/api/v1/feedbacks"
}
```

**业务规则**:
- 区域管理员只能看到本区域的反馈
- 全局管理员可以看到所有区域的反馈
- 软删除的反馈不返回

---

#### GET /feedbacks/:id

获取反馈详情（管理员）。

**权限**: `feedback:read`

**路径参数**:

| 参数 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | 反馈 ID |

**成功响应** (200):

```json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "type": "BUG",
    "title": "登录页面无法加载",
    "content": "点击登录按钮后，页面一直显示加载中，无法完成登录操作。\n\n复现步骤：\n1. 打开登录页面\n2. 输入用户名和密码\n3. 点击登录按钮\n4. 页面一直转圈",
    "attachments": [
      "https://storage.example.com/feedbacks/screenshot1.png"
    ],
    "pageUrl": "/login",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36...",
    "status": "IN_PROGRESS",
    "priority": "HIGH",
    "adminNote": "需要后端配合排查，疑似 API 超时问题",
    "adminReply": "我们已经定位到问题，正在修复中。预计今天内解决。",
    "assignee": {
      "id": "user-admin-1",
      "displayName": "管理员小张",
      "email": "admin@example.com"
    },
    "resolvedAt": null,
    "user": {
      "id": "user-1",
      "displayName": "张三",
      "email": "zhangsan@example.com",
      "department": {
        "id": "dept-1",
        "name": "技术部"
      }
    },
    "region": "CN",
    "createdAt": "2025-12-08T10:30:00.000Z",
    "updatedAt": "2025-12-08T11:00:00.000Z"
  },
  "timestamp": "2025-12-08T12:00:00.000Z",
  "path": "/api/v1/feedbacks/550e8400-e29b-41d4-a716-446655440000"
}
```

**错误响应**:

| 错误码 | HTTP | 说明 |
|--------|------|------|
| `FEEDBACK_NOT_FOUND` | 404 | 反馈不存在 |
| `FEEDBACK_ACCESS_DENIED` | 403 | 无权查看该反馈（区域不匹配） |

---

#### PATCH /feedbacks/:id/status

更新反馈状态。

**权限**: `feedback:update`

**路径参数**:

| 参数 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | 反馈 ID |

**请求体**:

```typescript
interface UpdateStatusDto {
  status: FeedbackStatus;    // 必填，新状态
}
```

**示例请求**:

```json
{
  "status": "IN_PROGRESS"
}
```

**成功响应** (200):

```json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "IN_PROGRESS",
    "updatedAt": "2025-12-08T12:00:00.000Z"
  },
  "message": "状态更新成功",
  "timestamp": "2025-12-08T12:00:00.000Z",
  "path": "/api/v1/feedbacks/550e8400-e29b-41d4-a716-446655440000/status"
}
```

**错误响应**:

| 错误码 | HTTP | 说明 |
|--------|------|------|
| `FEEDBACK_NOT_FOUND` | 404 | 反馈不存在 |
| `FEEDBACK_INVALID_STATUS_TRANSITION` | 400 | 无效的状态流转 |
| `FEEDBACK_ACCESS_DENIED` | 403 | 无权操作该反馈 |

**业务规则**:
- 状态变更必须符合状态流转规则
- 变更为 `RESOLVED` 时自动设置 `resolvedAt`
- 从 `RESOLVED` 变更为其他状态时清空 `resolvedAt`

**状态流转规则**:

| 当前状态 | 可变更为 |
|----------|----------|
| PENDING | IN_PROGRESS, CLOSED |
| IN_PROGRESS | RESOLVED, CLOSED, PENDING |
| RESOLVED | CLOSED, IN_PROGRESS |
| CLOSED | PENDING |

---

#### PATCH /feedbacks/:id

更新反馈信息（备注、回复、优先级、指派人）。

**权限**: `feedback:update`

**路径参数**:

| 参数 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | 反馈 ID |

**请求体**:

```typescript
interface UpdateFeedbackDto {
  priority?: FeedbackPriority;   // 可选，优先级
  adminNote?: string;            // 可选，管理员内部备注
  adminReply?: string;           // 可选，管理员回复（用户可见）
  assigneeId?: string;           // 可选，指派处理人 ID
}
```

**示例请求**:

```json
{
  "priority": "HIGH",
  "adminNote": "需要后端配合排查，疑似 API 超时问题",
  "adminReply": "我们已经定位到问题，正在修复中。预计今天内解决。",
  "assigneeId": "user-admin-1"
}
```

**成功响应** (200):

```json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "priority": "HIGH",
    "adminNote": "需要后端配合排查，疑似 API 超时问题",
    "adminReply": "我们已经定位到问题，正在修复中。预计今天内解决。",
    "assignee": {
      "id": "user-admin-1",
      "displayName": "管理员小张"
    },
    "updatedAt": "2025-12-08T12:00:00.000Z"
  },
  "message": "更新成功",
  "timestamp": "2025-12-08T12:00:00.000Z",
  "path": "/api/v1/feedbacks/550e8400-e29b-41d4-a716-446655440000"
}
```

**错误响应**:

| 错误码 | HTTP | 说明 |
|--------|------|------|
| `FEEDBACK_NOT_FOUND` | 404 | 反馈不存在 |
| `FEEDBACK_ACCESS_DENIED` | 403 | 无权操作该反馈 |
| `USER_NOT_FOUND` | 404 | 指派的用户不存在 |

---

#### DELETE /feedbacks/:id

删除反馈（软删除）。

**权限**: `feedback:delete`

**路径参数**:

| 参数 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | 反馈 ID |

**成功响应** (200):

```json
{
  "success": true,
  "data": null,
  "message": "删除成功",
  "timestamp": "2025-12-08T12:00:00.000Z",
  "path": "/api/v1/feedbacks/550e8400-e29b-41d4-a716-446655440000"
}
```

**错误响应**:

| 错误码 | HTTP | 说明 |
|--------|------|------|
| `FEEDBACK_NOT_FOUND` | 404 | 反馈不存在 |
| `FEEDBACK_ACCESS_DENIED` | 403 | 无权删除该反馈 |

**业务规则**:
- 执行软删除，设置 `deletedAt` 字段
- 已删除的反馈不会出现在列表中
- 物理删除需要通过其他管理手段

---

#### POST /feedbacks/batch-status

批量更新反馈状态。

**权限**: `feedback:update`

**请求体**:

```typescript
interface BatchUpdateStatusDto {
  ids: string[];               // 必填，反馈 ID 数组（最多 100 个）
  status: FeedbackStatus;      // 必填，目标状态
}
```

**示例请求**:

```json
{
  "ids": [
    "550e8400-e29b-41d4-a716-446655440000",
    "550e8400-e29b-41d4-a716-446655440001",
    "550e8400-e29b-41d4-a716-446655440002"
  ],
  "status": "CLOSED"
}
```

**成功响应** (200):

```json
{
  "success": true,
  "data": {
    "total": 3,
    "updated": 2,
    "failed": 1,
    "failures": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440002",
        "reason": "FEEDBACK_INVALID_STATUS_TRANSITION"
      }
    ]
  },
  "message": "批量更新完成",
  "timestamp": "2025-12-08T12:00:00.000Z",
  "path": "/api/v1/feedbacks/batch-status"
}
```

**业务规则**:
- 最多同时处理 100 条记录
- 部分失败不影响其他记录的更新
- 返回详细的失败原因

---

### 3. 统计接口

#### GET /feedbacks/stats

获取反馈统计数据。

**权限**: `feedback:read`

**Query 参数**:

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `region` | string | 否 | 区域筛选（全局管理员可用） |
| `startDate` | string | 否 | 开始日期 |
| `endDate` | string | 否 | 结束日期 |

**成功响应** (200):

```json
{
  "success": true,
  "data": {
    "total": 156,
    "byStatus": {
      "PENDING": 42,
      "IN_PROGRESS": 28,
      "RESOLVED": 76,
      "CLOSED": 10
    },
    "byType": {
      "BUG": 68,
      "FEATURE": 52,
      "IMPROVEMENT": 28,
      "OTHER": 8
    },
    "byPriority": {
      "LOW": 20,
      "MEDIUM": 45,
      "HIGH": 30,
      "URGENT": 5,
      "UNSET": 56
    },
    "byRegion": {
      "CN": 120,
      "US": 20,
      "EU": 10,
      "APAC": 6
    },
    "avgResolutionTime": 28.5,
    "todayNew": 5,
    "thisWeekNew": 23,
    "thisMonthNew": 89
  },
  "timestamp": "2025-12-08T12:00:00.000Z",
  "path": "/api/v1/feedbacks/stats"
}
```

**业务规则**:
- 区域管理员只能看到本区域的统计
- `avgResolutionTime` 单位为小时
- `byRegion` 只有全局管理员可见
- `byPriority` 中的 `UNSET` 表示 `priority` 为空（非枚举值）

---

## 错误码汇总

### 错误码命名规范

- **命名空间**：`FEEDBACK_` 前缀（`platform_feedback` 模块专属）
- **格式**：`FEEDBACK_{RESOURCE}_{ERROR_TYPE}`，全大写下划线分隔

### 业务错误码列表

| 错误码 | HTTP | 场景 | 说明 |
|--------|------|------|------|
| `FEEDBACK_NOT_FOUND` | 404 | 查看/更新/删除 | 反馈不存在或已删除 |
| `FEEDBACK_ACCESS_DENIED` | 403 | 查看/更新/删除 | 无权操作该反馈（区域不匹配或非本人） |
| `FEEDBACK_INVALID_STATUS_TRANSITION` | 400 | 状态变更 | 无效的状态流转（如 PENDING → RESOLVED） |
| `FEEDBACK_ATTACHMENTS_EXCEED_LIMIT` | 400 | 创建反馈 | 附件数量超过限制（最多 5 个） |
| `FEEDBACK_BATCH_LIMIT_EXCEEDED` | 400 | 批量操作 | 批量操作数量超过限制（最多 100 个） |

### 错误响应示例

**404 - 反馈不存在**

```json
{
  "success": false,
  "error": {
    "code": "FEEDBACK_NOT_FOUND",
    "message": "反馈不存在",
    "details": "反馈 ID '550e8400-e29b-41d4-a716-446655440000' 不存在或已删除"
  },
  "timestamp": "2025-12-08T10:30:00.000Z",
  "path": "/api/v1/feedbacks/550e8400-e29b-41d4-a716-446655440000",
  "method": "GET",
  "statusCode": 404
}
```

**403 - 无权访问**

```json
{
  "success": false,
  "error": {
    "code": "FEEDBACK_ACCESS_DENIED",
    "message": "无权访问该反馈",
    "details": "该反馈属于其他区域，您无权查看"
  },
  "timestamp": "2025-12-08T10:30:00.000Z",
  "path": "/api/v1/feedbacks/550e8400-e29b-41d4-a716-446655440000",
  "method": "GET",
  "statusCode": 403
}
```

**400 - 无效状态变更**

```json
{
  "success": false,
  "error": {
    "code": "FEEDBACK_INVALID_STATUS_TRANSITION",
    "message": "无效的状态变更",
    "details": "不允许从 PENDING 直接变更为 RESOLVED，请先变更为 IN_PROGRESS"
  },
  "timestamp": "2025-12-08T10:30:00.000Z",
  "path": "/api/v1/feedbacks/550e8400-e29b-41d4-a716-446655440000/status",
  "method": "PATCH",
  "statusCode": 400
}
```

**400 - 验证错误**

```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "请求参数验证失败",
    "errors": [
      {
        "field": "title",
        "message": "标题不能为空",
        "constraint": "isNotEmpty"
      },
      {
        "field": "content",
        "message": "内容长度必须在 1-5000 字符之间",
        "value": "",
        "constraint": "length"
      }
    ]
  },
  "timestamp": "2025-12-08T10:30:00.000Z",
  "path": "/api/v1/feedbacks",
  "method": "POST",
  "statusCode": 400
}

---

## 数据模型

### Feedback

```typescript
interface Feedback {
  id: string;                     // UUID
  type: FeedbackType;             // 反馈类型
  title: string;                  // 标题
  content: string;                // 详细内容
  attachments: string[];          // 附件 URL 数组
  
  pageUrl?: string;               // 提交时的页面 URL
  userAgent?: string;             // 浏览器信息
  
  status: FeedbackStatus;         // 处理状态
  priority?: FeedbackPriority;    // 优先级
  
  adminNote?: string;             // 管理员内部备注
  adminReply?: string;            // 管理员回复
  assigneeId?: string;            // 处理人 ID
  assignee?: UserBrief;           // 处理人信息
  resolvedAt?: string;            // 解决时间
  
  userId: string;                 // 提交用户 ID
  user?: UserBrief;               // 提交用户信息
  region: string;                 // 区域
  
  createdAt: string;              // 创建时间
  updatedAt: string;              // 更新时间
}

interface UserBrief {
  id: string;
  displayName: string;
  email?: string;
  department?: {
    id: string;
    name: string;
  };
}
```

---

## 调用示例

### 前端 React 示例

```typescript
import apiClient from '@/lib/api-client';

// 提交反馈
async function submitFeedback(data: CreateFeedbackDto) {
  try {
    const feedback = await apiClient.post('/feedbacks', {
      ...data,
      pageUrl: window.location.pathname,
      userAgent: navigator.userAgent
    });
    toast.success('反馈提交成功');
    return feedback;
  } catch (error) {
    if (error instanceof ApiClientError) {
      toast.error(error.message);
    }
    throw error;
  }
}

// 获取我的反馈列表
async function getMyFeedbacks(params?: { status?: string; page?: number }) {
  return apiClient.get('/feedbacks/my', { params });
}

// 管理员更新状态
async function updateFeedbackStatus(id: string, status: FeedbackStatus) {
  return apiClient.patch(`/feedbacks/${id}/status`, { status });
}
```

### cURL 示例

```bash
# 提交反馈
curl -X POST http://localhost:3001/api/v1/feedbacks \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "BUG",
    "title": "登录页面无法加载",
    "content": "点击登录按钮后页面一直转圈"
  }'

# 获取反馈列表（管理员）
curl http://localhost:3001/api/v1/feedbacks?status=PENDING&page=1&pageSize=20 \
  -H "Authorization: Bearer <admin_token>"

# 更新反馈状态
curl -X PATCH http://localhost:3001/api/v1/feedbacks/<id>/status \
  -H "Authorization: Bearer <admin_token>" \
  -H "Content-Type: application/json" \
  -d '{"status": "IN_PROGRESS"}'
```

---

**最后更新**: 2026-01-06  
**版本**: v1.0.2
