# 题目更新接口文档

> 对应接口：`PUT /api/question/{id}`  
> 功能说明：更新题目信息，支持部分字段更新（只传需要修改的字段即可）  
> 接口版本：V1.0  
> 更新日期：2026-06-08

---

## 目录

- [1. 接口定义](#1-接口定义)
- [2. 请求参数](#2-请求参数)
- [3. 请求示例](#3-请求示例)
- [4. 响应说明](#4-响应说明)
- [5. 错误码说明](#5-错误码说明)
- [6. 边界情况说明](#6-边界情况说明)
- [7. curl 测试命令](#7-curl-测试命令)

---

## 1. 接口定义

| 项目 | 内容 |
|------|------|
| **请求方法** | `PUT` |
| **接口路径** | `/api/question/{id}` |
| **Content-Type** | `application/json` |
| **认证方式** | Bearer Token (JWT) |
| **基础URL** | `http://localhost:8080`（开发环境） |
| **完整URL示例** | `PUT http://localhost:8080/api/question/378` |

### 1.1 路径参数说明

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `id` | String | **是** | 题目ID，支持两种格式：<br>① 自增数字ID（如 `378`）<br>② UUID格式的 questionId（如 `e6bb2566-90a8-4651-b553-083a1d695b3c`） |

### 1.2 认证要求

需要在请求头中携带 JWT Token：

```
Authorization: Bearer <token>
```

Token 通过 `/api/auth/login` 接口登录获取。

---

## 2. 请求参数

所有请求字段均为**可选**（部分更新），只传需要修改的字段即可。

### 2.1 请求体结构

```json
{
  "question_type": "single_choice",
  "difficulty": 3,
  "content": {
    "stem": "题目标题",
    "data": {},
    "answer": "A"
  },
  "tags": ["数学", "代数"],
  "score": 5.00,
  "status": "pending",
  "reviewer_comment": "审核意见",
  "document_id": "uuid-string",
  "document_name": "文件名.doc",
  "knowledge_base_path": "public/dept_1",
  "source_context": "原文上下文内容",
  "chunk_ids": ["chunk_001", "chunk_002"],
  "source_chunks_used": 3,
  "page_numbers": [12, 15]
}
```

### 2.2 字段详情

| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|------|------|------|------|---------|
| `question_type` | String | 否 | 题目类型 | 必须是以下之一：`single_choice`、`multiple_choice`、`true_false`、`fill_blank`、`subjective` |
| `difficulty` | Integer | 否 | 难度等级 | 1-5 |
| `content` | Object | 否 | 题目内容（JSON对象） | 不能为空的JSON对象 |
| `content.stem` | String | 视题型 | 题干 | 主观题必填 |
| `content.data` | Object | 视题型 | 额外数据（如选择题的选项列表） | - |
| `content.answer` | String | 视题型 | 答案 | 选择题为选项字母，判断题 "T"/"F" |
| `tags` | Array\<String\> | 否 | 知识点标签 | - |
| `score` | BigDecimal | 否 | 分值 | 范围：`-999.99` ~ `999.99`（数据库 decimal(5,2) 约束） |
| `status` | String | 否 | 审核状态 | `pending`、`approved`、`rejected` |
| `reviewer_comment` | String | 否 | 审核评论 | - |
| `document_id` | String | 否 | 关联文档ID | - |
| `document_name` | String | 否 | 文档名称 | - |
| `knowledge_base_path` | String | 否 | 知识库路径 | 最大500字符 |
| `source_context` | String | 否 | 原文上下文 | - |
| `chunk_ids` | Array\<String\> | 否 | 切片ID列表 | - |
| `source_chunks_used` | Integer | 否 | 使用的源切片数量 | - |
| `page_numbers` | Array\<Integer\> | 否 | 页码列表 | - |

---

## 3. 请求示例

### 3.1 修改题型和难度

```json
// PUT /api/question/378
{
  "question_type": "multiple_choice",
  "difficulty": 4
}
```

### 3.2 修改题目内容和分值

```json
// PUT /api/question/378
{
  "content": {
    "stem": "以下哪项是正确的？",
    "data": {
      "options": [
        { "label": "A", "text": "选项A" },
        { "label": "B", "text": "选项B" },
        { "label": "C", "text": "选项C" }
      ]
    },
    "answer": "B"
  },
  "score": 10.00
}
```

### 3.3 审核通过题目

```json
// PUT /api/question/378
{
  "status": "approved",
  "reviewer_comment": "内容正确，通过审核"
}
```

### 3.4 使用 UUID 更新

```json
// PUT /api/question/e6bb2566-90a8-4651-b553-083a1d695b3c
{
  "difficulty": 5,
  "score": 8.00
}
```

---

## 4. 响应说明

### 4.1 通用响应格式

```json
{
  "code": 200,
  "message": "操作成功",
  "data": {},
  "timestamp": "2026-06-08 10:00:00",
  "success": true
}
```

### 4.2 更新成功响应（code=200）

```json
{
  "code": 200,
  "message": "题目更新成功",
  "data": {
    "id": 378,
    "questionId": "41a890f2-c9b9-466f-b9f6-e0bd237329b7",
    "questionType": "multiple_choice",
    "difficulty": 4,
    "tags": null,
    "score": 5.00,
    "version": "1.0",
    "documentId": null,
    "excludeStems": null,
    "knowledgeBasePath": null,
    "sourceContext": null,
    "chunkIds": null,
    "sourceChunksUsed": 0,
    "examCount": 0,
    "correctCount": 0,
    "lastExamTime": null,
    "pageNumbers": null,
    "content": {
      "stem": "以下哪项是正确的？",
      "data": {
        "options": [
          { "label": "A", "text": "选项A" },
          { "label": "B", "text": "选项B" }
        ]
      },
      "answer": "A"
    },
    "status": "pending",
    "reviewerComment": null,
    "createdAt": "2026-06-08 09:00:00",
    "updatedAt": "2026-06-08 10:00:00",
    "isDeleted": 0
  },
  "timestamp": "2026-06-08 10:00:00",
  "success": true
}
```

### 4.3 data 字段说明

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | Long | 自增主键ID |
| `questionId` | String | 题目UUID（全局唯一标识） |
| `questionType` | String | 题目类型 |
| `difficulty` | Integer | 难度等级 1-5 |
| `tags` | JsonNode | 知识点标签（JSON数组或null） |
| `score` | BigDecimal | 分值 |
| `version` | String | 版本号（默认"1.0"） |
| `documentId` | String | 关联文档ID |
| `excludeStems` | String | 题干内容（AI去重用） |
| `knowledgeBasePath` | String | 知识库路径 |
| `sourceContext` | String | 原文上下文 |
| `chunkIds` | JsonNode | 切片ID列表 |
| `sourceChunksUsed` | Integer | 使用切片数 |
| `examCount` | Integer | 出题次数 |
| `correctCount` | Integer | 正确次数 |
| `lastExamTime` | DateTime | 最后作答时间 |
| `pageNumbers` | JsonNode | 页码列表 |
| `content` | JsonNode | 题目内容（JSON对象） |
| `status` | String | 审核状态 |
| `reviewerComment` | String | 审核评论 |
| `createdAt` | DateTime | 创建时间 |
| `updatedAt` | DateTime | 最后更新时间 |
| `isDeleted` | Integer | 逻辑删除标志（0=正常，1=已删除） |

---

## 5. 错误码说明

### 5.1 业务错误码

| HTTP状态码 | code | message | 触发场景 |
|-----------|------|---------|---------|
| **200** | 200 | 题目更新成功 | 更新成功 |
| **404** | 404 | 题目不存在: {id} | 指定ID的题目不存在 |
| **400** | 400 | 分值超出范围，允许范围: -999.99 ~ 999.99 | score 超出 decimal(5,2) 精度范围 |
| **400** | 400 | 知识库路径过长，最大允许500字符 | knowledge_base_path 超过500字符 |
| **400** | 400 | 无效的题目类型: {type} | question_type 不在枚举值内 |
| **400** | 400 | 题目内容不能为空 | content 传了空对象 `{}` |
| **400** | 400 | 题目可能已被删除或归档 | 记录已被逻辑删除，updateById 影响行数为0 |
| **400** | 400 | 题目不存在 | getById 返回 null |

### 5.2 异常错误码

| HTTP状态码 | code | message | 触发场景 |
|-----------|------|---------|---------|
| **500** | 500 | 更新题目失败: {异常信息} | 数据库异常、JSON序列化异常等（Controller 兜底catch） |
| **401** | 401 | 用户未登录 | 未携带 Token 或 Token 无效 |
| **403** | 403 | 无权限访问 | 当前用户无操作权限 |

---

## 6. 边界情况说明

### 6.1 ID 格式自动识别

接口自动识别传入的 `{id}` 格式：

- **纯数字**（如 `378`）：按自增主键 `id` 查询
- **非数字**（如 `e6bb2566-...`）：按 `question_id`（UUID）查询
- **非法格式**：抛出 `NumberFormatException`，返回 400

### 6.2 部分更新机制

接口支持**部分字段更新**（PATCH 语义），请求体中只需包含要修改的字段：

- 未传的字段保持原值不变
- 传了 `null` 的字段会被设为 `null`
- `content` 为完整替换，非合并更新

### 6.3 防御性校验

Service 层对以下输入进行校验：

| 校验项 | 拦截条件 | 错误信息 |
|--------|---------|---------|
| score 范围 | `score > 999.99 \|\| score < -999.99` | "分值超出范围，允许范围: -999.99 ~ 999.99" |
| 路径长度 | `knowledge_base_path.length > 500` | "知识库路径过长，最大允许500字符" |
| 题型枚举 | `question_type ∉ validTypes` | "无效的题目类型: {type}" |
| 内容非空 | `content != null && content.isEmpty()` | "题目内容不能为空" |
| 存在性检查 | `