388 lines
11 KiB
Markdown
388 lines
11 KiB
Markdown
# 题目更新接口文档
|
||
|
||
> 对应接口:`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()` | "题目内容不能为空" |
|
||
| 存在性检查 | `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 <token>" \
|
||
-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 <token>" \
|
||
-d '{"difficulty": 3}'
|
||
```
|
||
|
||
### 7.4 score 超出范围(测试防御性校验)
|
||
|
||
```bash
|
||
curl -X PUT "http://localhost:8080/api/question/378" \
|
||
-H "Content-Type: application/json" \
|
||
-H "Authorization: Bearer <token>" \
|
||
-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 <token>" \
|
||
-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 <token>" \
|
||
-d '{"difficulty": 3}'
|
||
# 期望响应: {"code": 404, "message": "题目不存在: 99999"}
|
||
```
|