# 题目更新接口文档 > 对应接口:`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,支持两种格式:
① 自增数字ID(如 `378`)
② UUID格式的 questionId(如 `e6bb2566-90a8-4651-b553-083a1d695b3c`) | ### 1.2 认证要求 需要在请求头中携带 JWT Token: ``` Authorization: Bearer ``` 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\ | 否 | 知识点标签 | - | | `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\ | 否 | 切片ID列表 | - | | `source_chunks_used` | Integer | 否 | 使用的源切片数量 | - | | `page_numbers` | Array\ | 否 | 页码列表 | - | --- ## 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()` | "题目内容不能为空" | | 存在性检查 | `getById(id) == null` | "题目不存在" | | 更新影响行数 | `updateById 返回 false` | "题目可能已被删除或归档" | ### 6.4 逻辑删除 - 已逻辑删除的记录(`is_deleted = 1`)仍可通过 `getById` 查到 - 但 `updateById` 由于 MyBatis-Plus 自动追加 `WHERE is_deleted = 0` 条件,将返回 `false` - 此时接口会抛出 `IllegalStateException`,返回 400 错误 ### 6.5 JSON 序列化 - 后台使用 `JacksonTypeHandler` 将 `content` 字段序列化为 JSON 存入数据库 - 使用独立的 `ObjectMapper` 实例(非 Spring 管理的全局配置) - 避免传入 `NaN`、`Infinity` 等非法 JSON 值,否则可能触发 `JsonProcessingException` --- ## 7. curl 测试命令 ### 7.1 获取 Token ```bash curl -X POST "http://localhost:8080/api/auth/login" \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123456"}' ``` ### 7.2 正常更新(数字ID) ```bash curl -X PUT "http://localhost:8080/api/question/378" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "question_type": "multiple_choice", "difficulty": 4, "score": 5.00, "content": { "stem": "更新后的题干", "data": { "options": [ {"label": "A", "text": "选项A"}, {"label": "B", "text": "选项B"} ] }, "answer": "A" } }' ``` ### 7.3 使用 UUID 更新 ```bash curl -X PUT "http://localhost:8080/api/question/e6bb2566-90a8-4651-b553-083a1d695b3c" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{"difficulty": 3}' ``` ### 7.4 score 超出范围(测试防御性校验) ```bash curl -X PUT "http://localhost:8080/api/question/378" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{"score": 1000}' # 期望响应: {"code": 500, "message": "更新题目失败: 分值超出范围,允许范围: -999.99 ~ 999.99"} ``` ### 7.5 无效题型(测试防御性校验) ```bash curl -X PUT "http://localhost:8080/api/question/378" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{"question_type": "invalid_type"}' # 期望响应: {"code": 500, "message": "更新题目失败: 无效的题目类型: invalid_type"} ``` ### 7.6 不存在的题目 ```bash curl -X PUT "http://localhost:8080/api/question/99999" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{"difficulty": 3}' # 期望响应: {"code": 404, "message": "题目不存在: 99999"} ```