初始化项目代码
This commit is contained in:
1690
docs/ai/curl测试手册 (2).md
Normal file
1690
docs/ai/curl测试手册 (2).md
Normal file
File diff suppressed because it is too large
Load Diff
1707
docs/ai/curl测试手册 (3).md
Normal file
1707
docs/ai/curl测试手册 (3).md
Normal file
File diff suppressed because it is too large
Load Diff
1872
docs/ai/curl测试手册 (4).md
Normal file
1872
docs/ai/curl测试手册 (4).md
Normal file
File diff suppressed because it is too large
Load Diff
468
docs/ai/出题批题后端对接指南.md
Normal file
468
docs/ai/出题批题后端对接指南.md
Normal file
@@ -0,0 +1,468 @@
|
||||
# 出题批题接口 - 后端对接指南
|
||||
|
||||
## 一、接口概览
|
||||
|
||||
| 接口 | 方法 | 功能 | 超时建议 |
|
||||
|------|------|------|----------|
|
||||
| `/exam/generate` | POST | 生成题目 | 120秒 |
|
||||
| `/exam/grade` | POST | 批阅答案 | 60秒 |
|
||||
|
||||
---
|
||||
|
||||
## 二、出题接口
|
||||
|
||||
### 2.1 请求
|
||||
|
||||
```
|
||||
POST /exam/generate
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**请求体字段说明:**
|
||||
|
||||
| 字段 | 类型 | 必需 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `file_path` | string | ✅ | 文档路径(相对于 documents 目录) |
|
||||
| `collection` | string 或 string[] | ✅ | 向量库名称,支持数组(按优先级顺序检索,找到文件即停止) |
|
||||
| `question_types` | object | ✅ | 题型及数量 |
|
||||
| `difficulty` | int | ❌ | 难度等级 1-5,默认 3 |
|
||||
| `request_id` | string | ❌ | 请求ID,相同ID返回缓存结果(幂等性) |
|
||||
|
||||
**请求示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"file_path": "薪酬制度.docx",
|
||||
"collection": ["dept_hr", "public_kb"],
|
||||
"question_types": {
|
||||
"single_choice": 3,
|
||||
"multiple_choice": 2,
|
||||
"true_false": 2,
|
||||
"fill_blank": 2,
|
||||
"subjective": 1
|
||||
},
|
||||
"difficulty": 3,
|
||||
"request_id": "uuid-for-idempotency"
|
||||
}
|
||||
```
|
||||
|
||||
> **collection 检索逻辑说明**:
|
||||
> - 出题接口采用**按优先级顺序检索**策略
|
||||
> - 先在 `dept_hr` 中查找文件,找到则停止
|
||||
> - 未找到则继续在 `public_kb` 中查找
|
||||
> - 这与 RAG 问答接口的**并行检索+融合**策略不同
|
||||
> - 原因:出题需要特定文件的完整内容,而非广泛搜索
|
||||
|
||||
### 2.2 响应
|
||||
|
||||
**成功响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"status": "success",
|
||||
"status_code": 2011,
|
||||
"message": "出题完成",
|
||||
"data": {
|
||||
"request_id": "uuid-xxx",
|
||||
"total": 10,
|
||||
"source_chunks_used": 25,
|
||||
"questions": [
|
||||
{
|
||||
"question_type": "single_choice",
|
||||
"difficulty": 3,
|
||||
"content": {
|
||||
"stem": "题干内容",
|
||||
"data": {
|
||||
"options": [
|
||||
{"key": "A", "content": "选项A"},
|
||||
{"key": "B", "content": "选项B"},
|
||||
{"key": "C", "content": "选项C"},
|
||||
{"key": "D", "content": "选项D"}
|
||||
]
|
||||
},
|
||||
"answer": "B",
|
||||
"explanation": "答案解析"
|
||||
},
|
||||
"source_trace": {
|
||||
"document_name": "薪酬制度.docx",
|
||||
"chunks_count": 3
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**失败响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"status": "failed",
|
||||
"error_code": "FILE_NOT_FOUND",
|
||||
"message": "文件不存在"
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 返回字段说明
|
||||
|
||||
**RAG 服务返回的字段(后端需存储):**
|
||||
|
||||
| 字段 | 类型 | 说明 | 后端是否需要存储 |
|
||||
|------|------|------|------------------|
|
||||
| `question_type` | string | 题型 | ✅ 存储 |
|
||||
| `difficulty` | int | 难度 1-5 | ✅ 存储 |
|
||||
| `content` | object | 题目内容 | ✅ 存储 |
|
||||
| `content.stem` | string | 题干 | ✅ |
|
||||
| `content.data` | object | 附加数据(选项、评分标准等) | ✅ |
|
||||
| `content.answer` | any | 正确答案 | ✅ 批阅时需要 |
|
||||
| `content.explanation` | string | 答案解析 | ✅ |
|
||||
| `source_trace` | object | 来源追踪 | ✅ 存储 |
|
||||
|
||||
**后端需要自己生成的字段:**
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `question_id` | UUID,唯一标识 |
|
||||
| `score` | 满分,根据业务需求设定 |
|
||||
| `tags` | 标签 |
|
||||
| `status` | 状态(待审核/已通过/已拒绝) |
|
||||
|
||||
> **重要**:RAG 服务不返回 `score` 字段,分值由后端在入库时指定。
|
||||
|
||||
### 2.4 题型格式对照表
|
||||
|
||||
| 题型 | question_type | content.answer 格式 | content.data |
|
||||
|------|---------------|---------------------|--------------|
|
||||
| 单选题 | `single_choice` | `"B"` | `{options: [{key, content}]}` |
|
||||
| 多选题 | `multiple_choice` | `["A", "C"]` | `{options: [{key, content}]}` |
|
||||
| 判断题 | `true_false` | `"T"` 或 `"F"` | 无 |
|
||||
| 填空题 | `fill_blank` | `[["答案1"], ["答案2", "同义词"]]` | `{blank_count: 2}` |
|
||||
| 简答题 | `subjective` | `"参考范文..."` | `{scoring_points: [...]}` |
|
||||
|
||||
### 2.5 错误码
|
||||
|
||||
| 错误码 | HTTP | 说明 |
|
||||
|--------|------|------|
|
||||
| `FILE_NOT_FOUND` | 404 | 指定文件不存在 |
|
||||
| `COLLECTION_NOT_FOUND` | 404 | 指定向量库不存在 |
|
||||
| `NO_CONTENT` | 400 | 文件内容为空,无法出题 |
|
||||
| `LLM_ERROR` | 500 | LLM 调用失败 |
|
||||
| `PARSE_ERROR` | 500 | 解析失败 |
|
||||
|
||||
---
|
||||
|
||||
## 三、批阅接口
|
||||
|
||||
### 3.1 请求
|
||||
|
||||
```
|
||||
POST /exam/grade
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**请求体字段说明:**
|
||||
|
||||
| 字段 | 类型 | 必需 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `request_id` | string | ❌ | 请求ID,用于追踪 |
|
||||
| `answers` | array | ✅ | 答案列表 |
|
||||
|
||||
**answers 数组中每个对象的字段:**
|
||||
|
||||
| 字段 | 类型 | 必需 | 说明 | 来源 |
|
||||
|------|------|------|------|------|
|
||||
| `question_id` | string | ✅ | 题目ID | 后端数据库 |
|
||||
| `question_type` | string | ✅ | 题型 | 后端数据库 |
|
||||
| `question_content` | object | ✅ | 题目内容(含正确答案) | 后端数据库 |
|
||||
| `student_answer` | any | ✅ | 学生答案 | 学生提交 |
|
||||
| `max_score` | number | ✅ | 满分 | 后端数据库 |
|
||||
|
||||
**请求示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"request_id": "grade-uuid-xxx",
|
||||
"answers": [
|
||||
{
|
||||
"question_id": "q-001",
|
||||
"question_type": "single_choice",
|
||||
"question_content": {
|
||||
"stem": "根据公司规定,员工薪资由哪几部分组成?",
|
||||
"data": {
|
||||
"options": [
|
||||
{"key": "A", "content": "基本工资+奖金"},
|
||||
{"key": "B", "content": "基本工资+绩效奖金+津贴补贴"},
|
||||
{"key": "C", "content": "基本工资+加班费"},
|
||||
{"key": "D", "content": "基本工资"}
|
||||
]
|
||||
},
|
||||
"answer": "B"
|
||||
},
|
||||
"student_answer": "B",
|
||||
"max_score": 5.0
|
||||
},
|
||||
{
|
||||
"question_id": "q-002",
|
||||
"question_type": "multiple_choice",
|
||||
"question_content": {
|
||||
"stem": "以下哪些属于绩效奖金的评定因素?",
|
||||
"data": {
|
||||
"options": [
|
||||
{"key": "A", "content": "工作质量"},
|
||||
{"key": "B", "content": "工作态度"},
|
||||
{"key": "C", "content": "考勤情况"},
|
||||
{"key": "D", "content": "团队协作"}
|
||||
]
|
||||
},
|
||||
"answer": ["A", "B", "D"]
|
||||
},
|
||||
"student_answer": ["A", "B"],
|
||||
"max_score": 4.0
|
||||
},
|
||||
{
|
||||
"question_id": "q-003",
|
||||
"question_type": "true_false",
|
||||
"question_content": {
|
||||
"stem": "公司规定员工每月绩效奖金上限为工资的20%。",
|
||||
"answer": "F"
|
||||
},
|
||||
"student_answer": "T",
|
||||
"max_score": 2.0
|
||||
},
|
||||
{
|
||||
"question_id": "q-004",
|
||||
"question_type": "fill_blank",
|
||||
"question_content": {
|
||||
"stem": "员工薪资由___、___和___三部分组成。",
|
||||
"data": {"blank_count": 3},
|
||||
"answer": [["基本工资"], ["绩效奖金", "绩效"], ["津贴补贴", "补贴"]]
|
||||
},
|
||||
"student_answer": ["基本工资", "绩效奖金", "交通补贴"],
|
||||
"max_score": 6.0
|
||||
},
|
||||
{
|
||||
"question_id": "q-005",
|
||||
"question_type": "subjective",
|
||||
"question_content": {
|
||||
"stem": "请简述公司薪酬制度的核心原则。",
|
||||
"data": {
|
||||
"scoring_points": [
|
||||
{"point": "公平性", "weight": 0.3},
|
||||
{"point": "激励性", "weight": 0.3},
|
||||
{"point": "竞争力", "weight": 0.2},
|
||||
{"point": "合法性", "weight": 0.2}
|
||||
]
|
||||
},
|
||||
"answer": "公司薪酬制度遵循公平、激励、竞争、合法四大原则..."
|
||||
},
|
||||
"student_answer": "我认为公司的薪酬制度应该公平公正...",
|
||||
"max_score": 10.0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 响应
|
||||
|
||||
**成功响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"status": "success",
|
||||
"status_code": 2012,
|
||||
"message": "批阅完成",
|
||||
"data": {
|
||||
"request_id": "grade-uuid-xxx",
|
||||
"total_score": 14.0,
|
||||
"total_max_score": 27.0,
|
||||
"score_rate": 51.85,
|
||||
"results": [
|
||||
{
|
||||
"question_id": "q-001",
|
||||
"question_type": "single_choice",
|
||||
"score": 5.0,
|
||||
"max_score": 5.0,
|
||||
"is_correct": true,
|
||||
"student_answer": "B",
|
||||
"correct_answer": "B",
|
||||
"feedback": "回答正确"
|
||||
},
|
||||
{
|
||||
"question_id": "q-002",
|
||||
"question_type": "multiple_choice",
|
||||
"score": 2.0,
|
||||
"max_score": 4.0,
|
||||
"is_correct": false,
|
||||
"student_answer": ["A", "B"],
|
||||
"correct_answer": ["A", "B", "D"],
|
||||
"feedback": "少选了 D 选项,正确答案: A, B, D"
|
||||
},
|
||||
{
|
||||
"question_id": "q-003",
|
||||
"question_type": "true_false",
|
||||
"score": 0.0,
|
||||
"max_score": 2.0,
|
||||
"is_correct": false,
|
||||
"student_answer": "T",
|
||||
"correct_answer": "F",
|
||||
"feedback": "正确答案: F"
|
||||
},
|
||||
{
|
||||
"question_id": "q-004",
|
||||
"question_type": "fill_blank",
|
||||
"score": 4.0,
|
||||
"max_score": 6.0,
|
||||
"is_correct": false,
|
||||
"student_answer": ["基本工资", "绩效奖金", "交通补贴"],
|
||||
"correct_answer": [["基本工资"], ["绩效奖金", "绩效"], ["津贴补贴", "补贴"]],
|
||||
"feedback": "第1、2空正确,第3空答案应为:津贴补贴或补贴"
|
||||
},
|
||||
{
|
||||
"question_id": "q-005",
|
||||
"question_type": "subjective",
|
||||
"score": 3.0,
|
||||
"max_score": 10.0,
|
||||
"is_correct": false,
|
||||
"student_answer": "我认为公司的薪酬制度应该公平公正...",
|
||||
"correct_answer": "公司薪酬制度遵循公平、激励、竞争、合法四大原则...",
|
||||
"feedback": "得分点:提到公平性概念,但未展开说明激励性和竞争力原则"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 返回字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `total_score` | number | 总得分 |
|
||||
| `total_max_score` | number | 总满分 |
|
||||
| `score_rate` | number | 得分率(百分比) |
|
||||
| `results` | array | 每道题的批阅结果 |
|
||||
| `results[].question_id` | string | 题目ID |
|
||||
| `results[].score` | number | 该题得分 |
|
||||
| `results[].max_score` | number | 该题满分 |
|
||||
| `results[].is_correct` | boolean | 是否完全正确 |
|
||||
| `results[].feedback` | string | 批阅反馈 |
|
||||
|
||||
### 3.4 批阅规则
|
||||
|
||||
| 题型 | 批阅方式 | 得分规则 |
|
||||
|------|----------|----------|
|
||||
| 单选题 | 精确匹配 | 正确得满分,错误得 0 |
|
||||
| 多选题 | 集合比对 | 全对满分,少选得一半,错选得 0 |
|
||||
| 判断题 | 精确匹配 | 正确得满分,错误得 0 |
|
||||
| 填空题 | 多答案匹配 | 每空独立评分,支持同义词匹配 |
|
||||
| 简答题 | LLM 评分 | 根据得分点评分,不超过 `max_score` |
|
||||
|
||||
### 3.5 错误码
|
||||
|
||||
| 错误码 | HTTP | 说明 |
|
||||
|--------|------|------|
|
||||
| `INVALID_ANSWER_FORMAT` | 400 | 答案格式不正确 |
|
||||
| `GRADING_ERROR` | 500 | 批阅过程出错 |
|
||||
|
||||
---
|
||||
|
||||
## 四、完整调用流程
|
||||
|
||||
### 4.1 出题流程
|
||||
|
||||
```
|
||||
后端调用 /exam/generate
|
||||
│
|
||||
▼
|
||||
RAG 返回题目
|
||||
(question_type, difficulty, content, source_trace)
|
||||
│
|
||||
▼
|
||||
后端生成 question_id
|
||||
后端设置 score (满分)
|
||||
后端设置 status
|
||||
│
|
||||
▼
|
||||
后端存入数据库
|
||||
```
|
||||
|
||||
### 4.2 批阅流程
|
||||
|
||||
```
|
||||
学生提交答案
|
||||
│
|
||||
▼
|
||||
后端从数据库查询:
|
||||
- question_id
|
||||
- question_type
|
||||
- question_content (含正确答案)
|
||||
- score (满分)
|
||||
│
|
||||
▼
|
||||
后端组装请求调用 /exam/grade
|
||||
│
|
||||
▼
|
||||
RAG 返回批阅结果
|
||||
(score, feedback, is_correct)
|
||||
│
|
||||
▼
|
||||
后端存入成绩表
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、字段来源速查表
|
||||
|
||||
### 5.1 出题接口
|
||||
|
||||
| 字段 | 返回方 | 存储方 | 说明 |
|
||||
|------|--------|--------|------|
|
||||
| question_id | ❌ 不返回 | 后端生成 | UUID |
|
||||
| question_type | ✅ RAG | 后端存储 | 题型 |
|
||||
| difficulty | ✅ RAG | 后端存储 | 难度 1-5 |
|
||||
| content | ✅ RAG | 后端存储 | 题目内容 |
|
||||
| source_trace | ✅ RAG | 后端存储 | 来源追踪 |
|
||||
| **score** | ❌ 不返回 | **后端设定** | 满分 |
|
||||
| tags | ❌ 不返回 | 后端设定 | 标签 |
|
||||
| status | ❌ 不返回 | 后端设定 | 状态 |
|
||||
|
||||
### 5.2 批阅接口
|
||||
|
||||
| 字段 | 来源 | 说明 |
|
||||
|------|------|------|
|
||||
| question_id | 后端数据库 | 题目唯一标识 |
|
||||
| question_type | 后端数据库 | 题型 |
|
||||
| question_content | 后端数据库 | 题目内容(含正确答案) |
|
||||
| student_answer | 学生提交 | 学生作答 |
|
||||
| **max_score** | **后端数据库** | 满分(决定得分上限) |
|
||||
|
||||
---
|
||||
|
||||
## 六、常见问题
|
||||
|
||||
### Q1: 出题接口返回的题目需要审核吗?
|
||||
|
||||
建议后端设置 `status` 字段进行审核流程,审核通过后再用于组卷。
|
||||
|
||||
### Q2: 分值如何动态调整?
|
||||
|
||||
后端在入库时自由设定 `score` 字段,批阅时传入 `max_score` 即可。RAG 服务不干预分值。
|
||||
|
||||
### Q3: 填空题同义词匹配是如何实现的?
|
||||
|
||||
填空题答案格式为 `[["主答案", "同义词1", "同义词2"], ...]`,学生答案匹配任意一个即视为正确。
|
||||
|
||||
### Q4: 批阅接口超时怎么办?
|
||||
|
||||
建议:
|
||||
- 单次批阅不超过 50 道题
|
||||
- 设置 60 秒超时
|
||||
- 主观题较多时适当延长
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v1.1
|
||||
**更新时间**: 2026-05-17
|
||||
**相关文档**: [后端对接规范.md](./后端对接规范.md)
|
||||
109
docs/error.txt
Normal file
109
docs/error.txt
Normal file
@@ -0,0 +1,109 @@
|
||||
index-DxCaeH_z.js:504 POST http://47.116.16.222/api/exam/record/103edf6a-e026-4314-889f-4187f6b44147/submit 500 (Internal Server Error)
|
||||
(匿名) @ index-DxCaeH_z.js:504
|
||||
xhr @ index-DxCaeH_z.js:504
|
||||
dispatchRequest @ index-DxCaeH_z.js:506
|
||||
Promise.then
|
||||
_request @ index-DxCaeH_z.js:510
|
||||
request @ index-DxCaeH_z.js:506
|
||||
(匿名) @ index-DxCaeH_z.js:510
|
||||
(匿名) @ index-DxCaeH_z.js:502
|
||||
submit @ index-DxCaeH_z.js:659
|
||||
fn @ index-DxCaeH_z.js:707
|
||||
ft @ index-DxCaeH_z.js:707
|
||||
Bt @ index-DxCaeH_z.js:709
|
||||
createBaseVNode.onClick.fn.<computed>.fn.<computed> @ index-DxCaeH_z.js:709
|
||||
callWithErrorHandling @ index-DxCaeH_z.js:16
|
||||
callWithAsyncErrorHandling @ index-DxCaeH_z.js:16
|
||||
ee @ index-DxCaeH_z.js:22
|
||||
index-DxCaeH_z.js:707 [提交] submit(结束考试) 失败:
|
||||
yt @ index-DxCaeH_z.js:707
|
||||
ft @ index-DxCaeH_z.js:707
|
||||
await in ft
|
||||
Bt @ index-DxCaeH_z.js:709
|
||||
createBaseVNode.onClick.fn.<computed>.fn.<computed> @ index-DxCaeH_z.js:709
|
||||
callWithErrorHandling @ index-DxCaeH_z.js:16
|
||||
callWithAsyncErrorHandling @ index-DxCaeH_z.js:16
|
||||
ee @ index-DxCaeH_z.js:22
|
||||
index-DxCaeH_z.js:707 message: 系统异常
|
||||
yt @ index-DxCaeH_z.js:707
|
||||
ft @ index-DxCaeH_z.js:707
|
||||
await in ft
|
||||
Bt @ index-DxCaeH_z.js:709
|
||||
createBaseVNode.onClick.fn.<computed>.fn.<computed> @ index-DxCaeH_z.js:709
|
||||
callWithErrorHandling @ index-DxCaeH_z.js:16
|
||||
callWithAsyncErrorHandling @ index-DxCaeH_z.js:16
|
||||
ee @ index-DxCaeH_z.js:22
|
||||
index-DxCaeH_z.js:707 HTTP状态: 500
|
||||
yt @ index-DxCaeH_z.js:707
|
||||
ft @ index-DxCaeH_z.js:707
|
||||
await in ft
|
||||
Bt @ index-DxCaeH_z.js:709
|
||||
createBaseVNode.onClick.fn.<computed>.fn.<computed> @ index-DxCaeH_z.js:709
|
||||
callWithErrorHandling @ index-DxCaeH_z.js:16
|
||||
callWithAsyncErrorHandling @ index-DxCaeH_z.js:16
|
||||
ee @ index-DxCaeH_z.js:22
|
||||
index-DxCaeH_z.js:707 响应数据: {"code":500,"message":"系统异常","data":null,"timestamp":"2026-06-01 08:44:52","success":false}
|
||||
yt @ index-DxCaeH_z.js:707
|
||||
ft @ index-DxCaeH_z.js:707
|
||||
await in ft
|
||||
Bt @ index-DxCaeH_z.js:709
|
||||
createBaseVNode.onClick.fn.<computed>.fn.<computed> @ index-DxCaeH_z.js:709
|
||||
callWithErrorHandling @ index-DxCaeH_z.js:16
|
||||
callWithAsyncErrorHandling @ index-DxCaeH_z.js:16
|
||||
ee @ index-DxCaeH_z.js:22
|
||||
index-DxCaeH_z.js:707 响应头: {"connection":"keep-alive","content-type":"application/json","date":"Mon, 01 Jun 2026 08:44:52 GMT","server":"nginx/1.18.0 (Ubuntu)","transfer-encoding":"chunked","vary":"Origin, Access-Control-Request-Method, Access-Control-Request-Headers"}
|
||||
yt @ index-DxCaeH_z.js:707
|
||||
ft @ index-DxCaeH_z.js:707
|
||||
await in ft
|
||||
Bt @ index-DxCaeH_z.js:709
|
||||
createBaseVNode.onClick.fn.<computed>.fn.<computed> @ index-DxCaeH_z.js:709
|
||||
callWithErrorHandling @ index-DxCaeH_z.js:16
|
||||
callWithAsyncErrorHandling @ index-DxCaeH_z.js:16
|
||||
ee @ index-DxCaeH_z.js:22
|
||||
index-DxCaeH_z.js:707 请求URL: /api/exam/record/103edf6a-e026-4314-889f-4187f6b44147/submit
|
||||
yt @ index-DxCaeH_z.js:707
|
||||
ft @ index-DxCaeH_z.js:707
|
||||
await in ft
|
||||
Bt @ index-DxCaeH_z.js:709
|
||||
createBaseVNode.onClick.fn.<computed>.fn.<computed> @ index-DxCaeH_z.js:709
|
||||
callWithErrorHandling @ index-DxCaeH_z.js:16
|
||||
callWithAsyncErrorHandling @ index-DxCaeH_z.js:16
|
||||
ee @ index-DxCaeH_z.js:22
|
||||
index-DxCaeH_z.js:707 请求方法: post
|
||||
yt @ index-DxCaeH_z.js:707
|
||||
ft @ index-DxCaeH_z.js:707
|
||||
await in ft
|
||||
Bt @ index-DxCaeH_z.js:709
|
||||
createBaseVNode.onClick.fn.<computed>.fn.<computed> @ index-DxCaeH_z.js:709
|
||||
callWithErrorHandling @ index-DxCaeH_z.js:16
|
||||
callWithAsyncErrorHandling @ index-DxCaeH_z.js:16
|
||||
ee @ index-DxCaeH_z.js:22
|
||||
index-DxCaeH_z.js:659 [examAPI] 正式考试提交批改 - recordId: 103edf6a-e026-4314-889f-4187f6b44147 题目数: 5
|
||||
index-DxCaeH_z.js:707 [提交] gradeExamRecord(空答案) 失败:
|
||||
yt @ index-DxCaeH_z.js:707
|
||||
ft @ index-DxCaeH_z.js:707
|
||||
await in ft
|
||||
Bt @ index-DxCaeH_z.js:709
|
||||
createBaseVNode.onClick.fn.<computed>.fn.<computed> @ index-DxCaeH_z.js:709
|
||||
callWithErrorHandling @ index-DxCaeH_z.js:16
|
||||
callWithAsyncErrorHandling @ index-DxCaeH_z.js:16
|
||||
ee @ index-DxCaeH_z.js:22
|
||||
index-DxCaeH_z.js:707 message: 提交失败: Index 0 out of bounds for length 0
|
||||
yt @ index-DxCaeH_z.js:707
|
||||
ft @ index-DxCaeH_z.js:707
|
||||
await in ft
|
||||
Bt @ index-DxCaeH_z.js:709
|
||||
createBaseVNode.onClick.fn.<computed>.fn.<computed> @ index-DxCaeH_z.js:709
|
||||
callWithErrorHandling @ index-DxCaeH_z.js:16
|
||||
callWithAsyncErrorHandling @ index-DxCaeH_z.js:16
|
||||
ee @ index-DxCaeH_z.js:22
|
||||
index-DxCaeH_z.js:707 完全无响应对象, error keys:
|
||||
yt @ index-DxCaeH_z.js:707
|
||||
ft @ index-DxCaeH_z.js:707
|
||||
await in ft
|
||||
Bt @ index-DxCaeH_z.js:709
|
||||
createBaseVNode.onClick.fn.<computed>.fn.<computed> @ index-DxCaeH_z.js:709
|
||||
callWithErrorHandling @ index-DxCaeH_z.js:16
|
||||
callWithAsyncErrorHandling @ index-DxCaeH_z.js:16
|
||||
ee @ index-DxCaeH_z.js:22
|
||||
index-DxCaeH_z.js:707 [提交] 所有2种提交方式均失败
|
||||
1502
docs/sql/knowledge_management_system.sql
Normal file
1502
docs/sql/knowledge_management_system.sql
Normal file
File diff suppressed because it is too large
Load Diff
172
docs/前端图片URL使用指南.md
Normal file
172
docs/前端图片URL使用指南.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# 前端图片 URL 使用指南
|
||||
|
||||
## 概述
|
||||
后端已优化图片访问方式,现在支持通过 Nginx 静态资源直接访问图片,提高了图片加载效率。
|
||||
|
||||
## 图片数据结构
|
||||
|
||||
### 聊天消息中的图片
|
||||
```json
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"id": "abc123",
|
||||
"url": "/files/img/abc123.jpg",
|
||||
"type": "image",
|
||||
"source": "文档名.pdf",
|
||||
"page": 1,
|
||||
"description": "图片描述"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## URL 格式说明
|
||||
|
||||
### 两种 URL 格式
|
||||
后端会返回以下两种格式的 URL:
|
||||
1. **新格式(推荐):`/files/img/xxx.jpg` - 通过 Nginx 静态资源访问
|
||||
2. **旧格式(兼容)**:`/api/image/xxx/data` - 通过后端 API 代理访问
|
||||
|
||||
## 前端适配说明
|
||||
|
||||
### 无需修改的情况
|
||||
如果您的前端目前使用如下方式处理图片,则无需修改代码:
|
||||
```javascript
|
||||
// 直接使用后端返回的 URL
|
||||
<img src={image.url} alt={image.description} />
|
||||
```
|
||||
|
||||
### 推荐的处理方式
|
||||
```javascript
|
||||
// 通用图片加载器
|
||||
function loadImage(imageData) {
|
||||
const imageUrl = imageData.url;
|
||||
|
||||
// 直接使用后端返回的 URL
|
||||
const img = document.createElement('img');
|
||||
img.src = imageUrl;
|
||||
img.alt = imageData.description || '图片';
|
||||
img.onerror = function() {
|
||||
// 图片加载失败时的处理
|
||||
console.error('图片加载失败:', imageUrl);
|
||||
};
|
||||
|
||||
return img;
|
||||
}
|
||||
```
|
||||
|
||||
### 检查 URL 类型
|
||||
```javascript
|
||||
function isNginxUrl(url) {
|
||||
return url && url.startsWith('/files/');
|
||||
}
|
||||
|
||||
function isApiUrl(url) {
|
||||
return url && url.includes('/api/image/');
|
||||
}
|
||||
```
|
||||
|
||||
## SSE 流处理示例
|
||||
|
||||
### JavaScript 示例
|
||||
```javascript
|
||||
const eventSource = new EventSource('/api/chat/stream');
|
||||
|
||||
eventSource.onmessage = function(event) {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
if (data.type === 'finish') {
|
||||
// 处理图片
|
||||
if (data.images && data.images.length > 0) {
|
||||
renderImages(data.images);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function renderImages(images) {
|
||||
const container = document.getElementById('image-container');
|
||||
|
||||
images.forEach(image => {
|
||||
const img = document.createElement('img');
|
||||
img.src = image.url;
|
||||
img.alt = image.description || '图片';
|
||||
img.style.maxWidth = '100%';
|
||||
container.appendChild(img);
|
||||
|
||||
// 显示图片描述
|
||||
if (image.description) {
|
||||
const desc = document.createElement('p');
|
||||
desc.textContent = image.description;
|
||||
container.appendChild(desc);
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### React 示例
|
||||
```jsx
|
||||
function ChatImages({ images }) {
|
||||
return (
|
||||
<div className="chat-images">
|
||||
{images && images.map((image, index) => (
|
||||
<div key={index} className="image-item">
|
||||
<img
|
||||
src={image.url}
|
||||
alt={image.description}
|
||||
className="chat-image"
|
||||
/>
|
||||
{image.description && (
|
||||
<div className="image-description">
|
||||
{image.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Vue 示例
|
||||
```vue
|
||||
<template>
|
||||
<div class="chat-images">
|
||||
<div v-for="(image, index) in images" :key="index" class="image-item">
|
||||
<img
|
||||
:src="image.url"
|
||||
:alt="image.description"
|
||||
class="chat-image"
|
||||
/>
|
||||
<div v-if="image.description" class="image-description">
|
||||
{{ image.description }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ChatImages',
|
||||
props: {
|
||||
images: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## 历史记录查看
|
||||
在查看历史会话时,后端已自动将图片 URL 转换为 Nginx 格式,无需额外处理。
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **向后兼容**:后端保留了对旧 URL 格式的支持,以确保旧数据仍然可用
|
||||
2. **错误处理**:建议添加图片加载失败的错误处理逻辑
|
||||
3. **相对路径**:`/files/` 开头的 URL 是相对路径,会自动根据当前域名拼接完整地址
|
||||
|
||||
## 兼容性
|
||||
- 无需对现有前端代码进行大量修改
|
||||
- 两种格式都支持
|
||||
251
docs/前端图片渲染修复.md
Normal file
251
docs/前端图片渲染修复.md
Normal file
@@ -0,0 +1,251 @@
|
||||
# 前端图片渲染修复指南
|
||||
|
||||
## 问题分析
|
||||
|
||||
根据日志分析,**后端已完美修复**,图片数据已正确返回,但前端没有正确渲染:
|
||||
|
||||
### 后端返回的数据格式
|
||||
```javascript
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": 175,
|
||||
"role": "assistant",
|
||||
"content": "...",
|
||||
"images": [
|
||||
{
|
||||
"doc_name": "1.docx",
|
||||
"page": 1,
|
||||
"image_id": "49d2910b148d.jpg",
|
||||
"url": "/files/img/49d2910b148d.jpg"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 问题现象
|
||||
从HTML片段可以看到,页面中只有引用来源和文本内容,**没有任何 `<img>` 标签渲染出来**。
|
||||
|
||||
---
|
||||
|
||||
## 修复步骤
|
||||
|
||||
### 1. 确认前端代码位置
|
||||
|
||||
首先找到渲染聊天消息的前端组件,通常在以下位置:
|
||||
- `src/components/ChatMessage.vue`
|
||||
- `src/views/ChatView.vue`
|
||||
- 或其他类似位置
|
||||
|
||||
### 2. 修改消息渲染代码
|
||||
|
||||
在渲染消息的地方,添加对 `images` 字段的处理:
|
||||
|
||||
#### Vue 组件示例:
|
||||
```vue
|
||||
<template>
|
||||
<div class="message-content">
|
||||
<!-- 原有文本渲染 -->
|
||||
<div v-html="message.content"></div>
|
||||
|
||||
<!-- 新增:图片渲染 -->
|
||||
<div v-if="message.images && message.images.length > 0" class="message-images">
|
||||
<div v-for="(image, index) in message.images" :key="index" class="image-item">
|
||||
<img
|
||||
:src="image.url"
|
||||
:alt="image.description || '图片'"
|
||||
class="message-image"
|
||||
loading="lazy"
|
||||
@error="handleImageError($event, image)"
|
||||
/>
|
||||
<div v-if="image.doc_name" class="image-info">
|
||||
<span class="doc-name">{{ image.doc_name }}</span>
|
||||
<span v-if="image.page" class="page-num">第 {{ image.page }} 页</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 原有引用卡片 -->
|
||||
<div v-if="message.sources" class="references-card">
|
||||
<!-- ... -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ChatMessage',
|
||||
props: {
|
||||
message: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleImageError(event, image) {
|
||||
console.error('图片加载失败:', image);
|
||||
// 可选:显示占位图
|
||||
// event.target.src = '/placeholder-image.jpg';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.message-images {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.image-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.message-image {
|
||||
max-width: 100%;
|
||||
max-height: 400px;
|
||||
border-radius: 8px;
|
||||
object-fit: contain;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.message-image:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.image-info {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
### 3. 处理历史会话消息
|
||||
|
||||
确认在获取历史会话的接口调用后,正确地将数据传递给消息渲染组件:
|
||||
|
||||
```javascript
|
||||
// 示例:在 API 调用处
|
||||
async function loadSessionHistory(sessionId) {
|
||||
try {
|
||||
const response = await fetch(`/api/ai/session/${sessionId}`);
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
// 确保 messages 数据正确传递给组件
|
||||
messages.value = result.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载历史会话失败:', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 添加图片点击预览功能(可选优化)
|
||||
|
||||
```vue
|
||||
<!-- 在组件中添加图片预览模态框 -->
|
||||
<div v-if="showPreview && previewImage" class="image-preview-overlay" @click="closePreview">
|
||||
<img :src="previewImage.url" class="preview-image" alt="预览" />
|
||||
<button class="close-btn" @click.stop="closePreview">×</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 在 methods 中添加
|
||||
openPreview(image) {
|
||||
this.previewImage = image;
|
||||
this.showPreview = true;
|
||||
}
|
||||
|
||||
closePreview() {
|
||||
this.showPreview = false;
|
||||
this.previewImage = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- 修改 img 标签,添加点击事件 -->
|
||||
<img
|
||||
:src="image.url"
|
||||
:alt="image.description || '图片'"
|
||||
class="message-image"
|
||||
@click="openPreview(image)"
|
||||
@error="handleImageError($event, image)"
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 验证步骤
|
||||
|
||||
1. **检查浏览器控制台**:打开浏览器开发者工具(F12),查看 Console 标签,确认没有 JavaScript 错误
|
||||
2. **检查网络请求**:在 Network 标签中,确认图片请求(如 `/files/img/49d2910b148d.jpg`)状态码为 200
|
||||
3. **确认数据结构**:在 Network 标签中,检查 `/api/ai/session/{sessionId}` 接口的响应,确认 `images` 字段存在且格式正确
|
||||
|
||||
---
|
||||
|
||||
## 常见问题排查
|
||||
|
||||
### 问题1:images 字段为 null 或 undefined
|
||||
**原因**:可能旧版本的消息没有 images 字段
|
||||
**解决**:添加空值检查
|
||||
```vue
|
||||
<div v-if="message.images && message.images.length > 0" class="message-images">
|
||||
```
|
||||
|
||||
### 问题2:图片加载 404
|
||||
**原因**:Nginx 路径配置问题
|
||||
**解决**:确认 Nginx 配置正确映射了 `/files/img/` 路径
|
||||
|
||||
### 问题3:图片跨域
|
||||
**原因**:图片域名与前端域名不一致
|
||||
**解决**:使用后端代理接口 `/api/image/{imageId}/data` 而不是 `/files/img/` 路径
|
||||
|
||||
---
|
||||
|
||||
## 后端返回的完整数据说明
|
||||
|
||||
后端返回的 ChatMessage 中 `images` 字段的完整结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"image_id": "49d2910b148d.jpg",
|
||||
"url": "/files/img/49d2910b148d.jpg",
|
||||
"doc_name": "1.docx",
|
||||
"page": 1,
|
||||
"description": "图片描述(可选)",
|
||||
"type": "image"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
主要字段说明:
|
||||
- `url`: 图片的 Nginx 静态资源路径,直接使用即可
|
||||
- `image_id`: 图片的唯一标识
|
||||
- `doc_name`: 来源文档名称
|
||||
- `page`: 来源页码
|
||||
- `description`: 图片描述(可能为 null)
|
||||
|
||||
---
|
||||
|
||||
## 修改总结
|
||||
|
||||
| 任务 | 优先级 |
|
||||
|------|--------|
|
||||
| 在消息渲染组件中添加 `images` 字段处理 | 🔴 高 |
|
||||
| 确保正确解析和渲染图片列表 | 🔴 高 |
|
||||
| 添加图片加载错误处理 | 🟡 中 |
|
||||
| 添加图片预览功能 | 🟢 低 |
|
||||
| 优化图片样式和交互 | 🟢 低 |
|
||||
70
docs/图片URL优化更新文档.md
Normal file
70
docs/图片URL优化更新文档.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# 图片 URL 优化更新文档
|
||||
|
||||
## 概述
|
||||
本次更新优化了图片访问方式,从原来通过后端 API 代理访问改为直接通过 Nginx 静态资源访问,提高了图片加载效率。
|
||||
|
||||
## 修改内容
|
||||
|
||||
### 1. 配置文件
|
||||
- 使用配置项 `file.upload.path` 确定本地保存路径
|
||||
- 使用配置项 `file.upload.url-prefix` 确定 Nginx 访问前缀(默认为 `/files/`)
|
||||
|
||||
### 2. ImageService 接口
|
||||
- 新增 `getNginxUrlByImageId(String imageId)` 方法,用于根据图片 ID 获取 Nginx 格式的 URL
|
||||
|
||||
### 3. ImageServiceImpl 实现
|
||||
- 使用配置的 `file.upload.path` + `/img` 作为图片存储目录
|
||||
- 保存图片时返回相对路径(如 `img/xxx.jpg`)而非绝对路径
|
||||
- 修改本地读取图片逻辑,支持相对路径
|
||||
- 新增 `getNginxUrlByImageId` 方法
|
||||
- 新增 `extractFilenameFromPath` 辅助方法
|
||||
|
||||
### 4. AiChatServiceImpl 修改
|
||||
- 在保存图片后立即更新 imageMetadata 中的 url 为 Nginx 格式
|
||||
- 在 SSE 的 finish 事件中,对无 metadata 的图片也尝试生成 Nginx 格式 URL
|
||||
|
||||
### 5. ChatServiceImpl 修改
|
||||
- 查看历史记录时,将图片 URL 从 `/api/image/xxx/data` 转换为 Nginx 格式(`/files/img/xxx.jpg`)
|
||||
|
||||
## 使用示例
|
||||
|
||||
### Nginx 配置示例
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location /files/ {
|
||||
alias /path/to/upload/;
|
||||
expires 7d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 配置文件 (application-prod.yml)
|
||||
```yaml
|
||||
file:
|
||||
upload:
|
||||
path: /opt/deploy/uploads/
|
||||
url-prefix: /files/
|
||||
```
|
||||
|
||||
## 部署说明
|
||||
1. 确保 Nginx 配置正确,`/files/` 路径指向实际的上传目录
|
||||
2. 确保应用配置文件中的 `file.upload.path` 和 `file.upload.url-prefix` 设置正确
|
||||
3. 重新构建并部署应用
|
||||
|
||||
## 兼容性
|
||||
- 保留了向后兼容,如果 Nginx 格式 URL 无法获取,会回退为原 `/api/image/xxx/data` 格式
|
||||
- 旧数据仍然可以正常访问
|
||||
|
||||
## 图片 URL 格式对比
|
||||
| 旧格式 | 新格式 |
|
||||
|--------|--------|
|
||||
| /api/image/xxx/data | /files/img/xxx.jpg |
|
||||
1252
docs/完整curl测试手册.md
Normal file
1252
docs/完整curl测试手册.md
Normal file
File diff suppressed because it is too large
Load Diff
2128
docs/完整接口文档.md
Normal file
2128
docs/完整接口文档.md
Normal file
File diff suppressed because it is too large
Load Diff
473
docs/批阅接口测试.md
Normal file
473
docs/批阅接口测试.md
Normal file
@@ -0,0 +1,473 @@
|
||||
# 批阅接口测试文档
|
||||
|
||||
## 一、接口说明
|
||||
|
||||
| 接口 | 地址 | 功能 |
|
||||
| -------- | ------------------------------------------------------------- | --------------------- |
|
||||
| 后端通用接口 | POST <http://localhost:8080/api/exam/grade> | 批阅答案(日常练习/模拟考/正式考试通用) |
|
||||
| 后端正式考试接口 | POST <http://localhost:8080/api/exam/record/{recordId}/grade> | 正式考试专用批阅接口 |
|
||||
| AI端接口 | POST <http://localhost:5001/exam/grade> | AI端批阅答案 |
|
||||
|
||||
**answerType 参数说明**:后端接口支持 `answerType` 字段区分答题类型,前端传入什么值后端就使用什么值。
|
||||
|
||||
| answerType 值 | 说明 | 重复答题 |
|
||||
| ------------- | ---- | ----------- |
|
||||
| `practice` | 日常练习 | 允许(更新记录) |
|
||||
| `mock_exam` | 模拟考 | 允许(更新记录) |
|
||||
| `formal_exam` | 正式考试 | 不允许(唯一索引保护) |
|
||||
|
||||
***
|
||||
|
||||
## 二、接口路径规范
|
||||
|
||||
**注意**:所有接口路径都在 `/api` 前缀下,实际完整路径为:
|
||||
|
||||
- `http://localhost:8080/api/exam/grade`
|
||||
- `http://localhost:8080/api/exam/record/{recordId}/grade`
|
||||
|
||||
***
|
||||
|
||||
## 三、获取 Token
|
||||
|
||||
在测试前,需要先获取有效的访问令牌:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/auth/login -H "Content-Type: application/json" -d '{"username":"admin","password":"123456"}'
|
||||
```
|
||||
|
||||
**新 Token**(有效期2小时):
|
||||
|
||||
```
|
||||
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwidXNlcm5hbWUiOiJhZG1pbiIsImlhdCI6MTc4MDAzMTgyNSwiZXhwIjoxNzgwMDM5MDI1fQ.an_bqtorziJ5Yb-otGPIIg400fCM1-VLLfX7mgQYHspOgSsTczcSY70evxsMUnfGmOr_euXgXwf2VkMQoXpG5w
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## 四、后端接口测试
|
||||
|
||||
### 4.1 单选题测试(日常练习)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/exam/grade -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwidXNlcm5hbWUiOiJhZG1pbiIsImlhdCI6MTc4MDAzMTgyNSwiZXhwIjoxNzgwMDM5MDI1fQ.an_bqtorziJ5Yb-otGPIIg400fCM1-VLLfX7mgQYHspOgSsTczcSY70evxsMUnfGmOr_euXgXwf2VkMQoXpG5w" -d '{"answerType":"practice","requestId":"test_001","answers":[{"questionId":"9d05c475-b4fe-4679-a450-ee88a94b3b34","questionType":"single_choice","questionContent":{"stem":"根据文档内容,务求实效原则的核心要求聚焦于哪一根本目标?","data":{"options":[{"key":"A","content":"扩大终端数量规模"},{"key":"B","content":"提升终端信息化系统覆盖率"},{"key":"C","content":"着眼于终端功能发挥和服务水平提升"},{"key":"D","content":"统一各类终端建设外观标准"}]},"answer":"C"},"studentAnswer":"C","maxScore":2.0}]}'
|
||||
```
|
||||
|
||||
### 4.2 多选题测试(日常练习)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/exam/grade -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwidXNlcm5hbWUiOiJhZG1pbiIsImlhdCI6MTc4MDAzMTgyNSwiZXhwIjoxNzgwMDM5MDI1fQ.an_bqtorziJ5Yb-otGPIIg400fCM1-VLLfX7mgQYHspOgSsTczcSY70evxsMUnfGmOr_euXgXwf2VkMQoXpG5w" -d '{"answerType":"practice","requestId":"test_002","answers":[{"questionId":"ee652f7e-da5e-4129-8766-c113277332f4","questionType":"multiple_choice","questionContent":{"stem":"依据务求实效原则,以下哪些做法符合其核心要求?(可多选)","data":{"options":[{"key":"A","content":"科学设定终端投入标准"},{"key":"B","content":"严格执行终端投入标准"},{"key":"C","content":"优先采购高配置但非必需的智能设备"},{"key":"D","content":"坚决杜绝不必要的支出和浪费"}]},"answer":["A","B","D"]},"studentAnswer":["A","B"],"maxScore":4.0}]}'
|
||||
```
|
||||
|
||||
### 4.3 判断题测试(日常练习)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/exam/grade -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwidXNlcm5hbWUiOiJhZG1pbiIsImlhdCI6MTc4MDAzMTgyNSwiZXhwIjoxNzgwMDM5MDI1fQ.an_bqtorziJ5Yb-otGPIIg400fCM1-VLLfX7mgQYHspOgSsTczcSY70evxsMUnfGmOr_euXgXwf2VkMQoXpG5w" -d '{"answerType":"practice","requestId":"test_003","answers":[{"questionId":"76913ad8-5325-4f30-a03a-6e19e434b59e","questionType":"true_false","questionContent":{"stem":"务求实效原则允许在终端建设中适度预留冗余预算,以应对未来可能的功能升级需求。","data":{},"answer":"false"},"studentAnswer":"false","maxScore":1.0}]}'
|
||||
```
|
||||
|
||||
### 4.4 填空题测试(日常练习)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/exam/grade -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwidXNlcm5hbWUiOiJhZG1pbiIsImlhdCI6MTc4MDAzMTgyNSwiZXhwIjoxNzgwMDM5MDI1fQ.an_bqtorziJ5Yb-otGPIIg400fCM1-VLLfX7mgQYHspOgSsTczcSY70evxsMUnfGmOr_euXgXwf2VkMQoXpG5w" -d '{"answerType":"practice","requestId":"test_004","answers":[{"questionId":"7ae3daef-0345-4852-bd37-336e74498cc4","questionType":"fill_blank","questionContent":{"stem":"务求实效原则强调以___为出发点,通过科学设定和严格执行终端投入标准,实现___与___的双重提升。","data":{"blank_count":3},"answer":["终端功能发挥和服务水平提升","终端功能发挥","服务水平提升"]},"studentAnswer":["终端功能发挥和服务水平提升","终端功能发挥","服务水平"],"maxScore":2.0}]}'
|
||||
```
|
||||
|
||||
### 4.5 主观题测试(日常练习)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/exam/grade -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwidXNlcm5hbWUiOiJhZG1pbiIsImlhdCI6MTc4MDAzMTgyNSwiZXhwIjoxNzgwMDM5MDI1fQ.an_bqtorziJ5Yb-otGPIIg400fCM1-VLLfX7mgQYHspOgSsTczcSY70evxsMUnfGmOr_euXgXwf2VkMQoXpG5w" -d '{"answerType":"practice","requestId":"test_005","answers":[{"questionId":"ea6ba220-43ff-44d8-8b24-cd350eeb8b53","questionType":"subjective","questionContent":{"stem":"请结合文档内容,简述\"务求实效原则\"在终端建设管理中的具体体现,并说明其与形式主义倾向的根本区别。","data":{"answer_length_hint":"150–200字"},"answer":"务求实效原则具体体现为:以终端功能发挥和服务水平提升为唯一导向,科学设定并严格执行投入标准,杜绝一切不必要的支出和浪费。"},"studentAnswer":"务求实效原则强调终端功能发挥和服务水平提升,杜绝浪费。形式主义只注重表面,不注重实际效果。","maxScore":6.0}]}'
|
||||
```
|
||||
|
||||
### 4.6 批量测试(所有题型,包含错题,日常练习)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/exam/grade -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwidXNlcm5hbWUiOiJhZG1pbiIsImlhdCI6MTc4MDMwNDMwOCwiZXhwIjoxNzgwMzExNTA4fQ.5NLIrXfK-7MVrrmU7TZ7L_z2TXxc4OzaYIOmoFKBLOAkX9pjCBtxz915QQ1SGLekHTj5DBcgF-NhIZDSw-CPtQ" -d '{"answerType":"practice","requestId":"batch_test_001","answers":[{"questionId":"9d05c475-b4fe-4679-a450-ee88a94b3b34","questionType":"single_choice","questionContent":{"stem":"根据文档内容,务求实效原则的核心要求聚焦于哪一根本目标?","data":{"options":[{"key":"A","content":"扩大终端数量规模"},{"key":"B","content":"提升终端信息化系统覆盖率"},{"key":"C","content":"着眼于终端功能发挥和服务水平提升"},{"key":"D","content":"统一各类终端建设外观标准"}]},"answer":"C"},"studentAnswer":"","maxScore":2.0},{"questionId":"ee652f7e-da5e-4129-8766-c113277332f4","questionType":"multiple_choice","questionContent":{"stem":"依据务求实效原则,以下哪些做法符合其核心要求?(可多选)","data":{"options":[{"key":"A","content":"科学设定终端投入标准"},{"key":"B","content":"严格执行终端投入标准"},{"key":"C","content":"优先采购高配置但非必需的智能设备"},{"key":"D","content":"坚决杜绝不必要的支出和浪费"}]},"answer":["A","B","D"]},"studentAnswer":[],"maxScore":4.0},{"questionId":"76913ad8-5325-4f30-a03a-6e19e434b59e","questionType":"true_false","questionContent":{"stem":"务求实效原则允许在终端建设中适度预留冗余预算,以应对未来可能的功能升级需求。","data":{},"answer":"false"},"studentAnswer":"false","maxScore":1.0}]}'
|
||||
```
|
||||
|
||||
**错题说明**:
|
||||
|
||||
- 单选题:学生选A(正确答案是C)
|
||||
- 多选题:学生选A、B、C(正确答案是A、B、D,C选项违背务求实效原则)
|
||||
- 判断题:学生选true(正确答案是false)
|
||||
|
||||
### 4.7 模拟考测试(answerType = mock\_exam)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/exam/grade -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwidXNlcm5hbWUiOiJhZG1pbiIsImlhdCI6MTc4MDAzMTgyNSwiZXhwIjoxNzgwMDM5MDI1fQ.an_bqtorziJ5Yb-otGPIIg400fCM1-VLLfX7mgQYHspOgSsTczcSY70evxsMUnfGmOr_euXgXwf2VkMQoXpG5w" -d '{"answerType":"mock_exam","requestId":"mock_test_001","answers":[{"questionId":"9d05c475-b4fe-4679-a450-ee88a94b3b34","questionType":"single_choice","questionContent":{"stem":"根据文档内容,务求实效原则的核心要求聚焦于哪一根本目标?","data":{"options":[{"key":"A","content":"扩大终端数量规模"},{"key":"B","content":"提升终端信息化系统覆盖率"},{"key":"C","content":"着眼于终端功能发挥和服务水平提升"},{"key":"D","content":"统一各类终端建设外观标准"}]},"answer":"C"},"studentAnswer":"C","maxScore":2.0},{"questionId":"ee652f7e-da5e-4129-8766-c113277332f4","questionType":"multiple_choice","questionContent":{"stem":"依据务求实效原则,以下哪些做法符合其核心要求?(可多选)","data":{"options":[{"key":"A","content":"科学设定终端投入标准"},{"key":"B","content":"严格执行终端投入标准"},{"key":"C","content":"优先采购高配置但非必需的智能设备"},{"key":"D","content":"坚决杜绝不必要的支出和浪费"}]},"answer":["A","B","D"]},"studentAnswer":["A","B","D"],"maxScore":4.0}]}'
|
||||
```
|
||||
|
||||
### 4.8 正式考测试(通过 recordId 接口,推荐方式)
|
||||
|
||||
> **注意**:正式考试推荐使用 `/api/exam/record/{recordId}/grade` 接口提交。这种方式会关联已存在的考试记录。
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/exam/record/59759c40-ee4e-4ff5-ab03-4819a0499beb/grade -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwidXNlcm5hbWUiOiJhZG1pbiIsImlhdCI6MTc4MDAzMTgyNSwiZXhwIjoxNzgwMDM5MDI1fQ.an_bqtorziJ5Yb-otGPIIg400fCM1-VLLfX7mgQYHspOgSsTczcSY70evxsMUnfGmOr_euXgXwf2VkMQoXpG5w" -d '{"answers":[{"questionId":"9d05c475-b4fe-4679-a450-ee88a94b3b34","questionType":"single_choice","answer":"C"},{"questionId":"ee652f7e-da5e-4129-8766-c113277332f4","questionType":"multiple_choice","answer":["A","B","D"]}]}'
|
||||
```
|
||||
|
||||
### 4.9 正式考测试(通过通用接口 + recordId 参数)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/exam/grade -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwidXNlcm5hbWUiOiJhZG1pbiIsImlhdCI6MTc4MDAzMTgyNSwiZXhwIjoxNzgwMDM5MDI1fQ.an_bqtorziJ5Yb-otGPIIg400fCM1-VLLfX7mgQYHspOgSsTczcSY70evxsMUnfGmOr_euXgXwf2VkMQoXpG5w" -d '{"answerType":"formal_exam","recordId":"59759c40-ee4e-4ff5-ab03-4819a0499beb","requestId":"formal_test_001","paperId":"0ef70443-67d3-48cd-9309-f5088c092fe1","answers":[{"questionId":"9d05c475-b4fe-4679-a450-ee88a94b3b34","questionType":"single_choice","questionContent":{"stem":"根据文档内容,务求实效原则的核心要求聚焦于哪一根本目标?","data":{"options":[{"key":"A","content":"扩大终端数量规模"},{"key":"B","content":"提升终端信息化系统覆盖率"},{"key":"C","content":"着眼于终端功能发挥和服务水平提升"},{"key":"D","content":"统一各类终端建设外观标准"}]},"answer":"C"},"studentAnswer":"C","maxScore":2.0}]}'
|
||||
```
|
||||
|
||||
### 4.10 空白试卷测试 - 日常练习(answers 为空数组)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/exam/grade -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwidXNlcm5hbWUiOiJhZG1pbiIsImlhdCI6MTc4MDAzMTgyNSwiZXhwIjoxNzgwMDM5MDI1fQ.an_bqtorziJ5Yb-otGPIIg400fCM1-VLLfX7mgQYHspOgSsTczcSY70evxsMUnfGmOr_euXgXwf2VkMQoXpG5w" -d '{"answerType":"practice","requestId":"blank_test_001","answers":[]}'
|
||||
```
|
||||
|
||||
### 4.11 空白试卷测试 - 模拟考(answers 为空数组)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/exam/grade -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwidXNlcm5hbWUiOiJhZG1pbiIsImlhdCI6MTc4MDAzMTgyNSwiZXhwIjoxNzgwMDM5MDI1fQ.an_bqtorziJ5Yb-otGPIIg400fCM1-VLLfX7mgQYHspOgSsTczcSY70evxsMUnfGmOr_euXgXwf2VkMQoXpG5w" -d '{"answerType":"mock_exam","requestId":"blank_test_002","answers":[]}'
|
||||
```
|
||||
|
||||
### 4.12 空白试卷测试 - 正式考试(通过 recordId 接口)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/exam/record/4e34c752-1e0e-4207-8fdb-b0027b30521e/grade -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwidXNlcm5hbWUiOiJhZG1pbiIsImlhdCI6MTc4MDAzMTgyNSwiZXhwIjoxNzgwMDM5MDI1fQ.an_bqtorziJ5Yb-otGPIIg400fCM1-VLLfX7mgQYHspOgSsTczcSY70evxsMUnfGmOr_euXgXwf2VkMQoXpG5w" -d '{"answers":[]}'
|
||||
```
|
||||
|
||||
### 4.13 空白试卷测试 - 正式考试(通过通用接口 + recordId)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/exam/grade -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwidXNlcm5hbWUiOiJhZG1pbiIsImlhdCI6MTc4MDAzMTgyNSwiZXhwIjoxNzgwMDM5MDI1fQ.an_bqtorziJ5Yb-otGPIIg400fCM1-VLLfX7mgQYHspOgSsTczcSY70evxsMUnfGmOr_euXgXwf2VkMQoXpG5w" -d '{"answerType":"formal_exam","recordId":"4e34c752-1e0e-4207-8fdb-b0027b30521e","requestId":"blank_test_003","answers":[]}'
|
||||
```
|
||||
|
||||
### 4.14 正式考试 - 只提交一道题(通过 recordId 接口)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/exam/record/2fa88db4-bc90-4096-9970-3286639c5b5b/grade -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwidXNlcm5hbWUiOiJhZG1pbiIsImlhdCI6MTc4MDMxOTgzMiwiZXhwIjoxNzgwMzI3MDMyfQ.azBEWs7-LiicNDA_O0qdm-TPmBtKLBUeCplRPnkGl7NFUhn-JMP5-G9gboP7eHxsaIvowoY6B2TVlB4z8bDIuQ" -d '{"answers":[{"questionId":"5af6fdce-dbf2-4a57-9ef4-bef176be6fb8","questionType":"single_choice","answer":"C"}]}'
|
||||
```
|
||||
|
||||
### 4.15 正式考试 - 一道题没选(通过 recordId 接口)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/exam/record/389806c8-3b42-4b5a-adb8-23d166bb1053/grade -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwidXNlcm5hbWUiOiJhZG1pbiIsImlhdCI6MTc4MDMxOTgzMiwiZXhwIjoxNzgwMzI3MDMyfQ.azBEWs7-LiicNDA_O0qdm-TPmBtKLBUeCplRPnkGl7NFUhn-JMP5-G9gboP7eHxsaIvowoY6B2TVlB4z8bDIuQ" -d '{"answers":[{"questionId":"5af6fdce-dbf2-4a57-9ef4-bef176be6fb8","questionType":"single_choice","answer":""},{"questionId":"5e069067-a10d-4230-9564-e7503d72d738","questionType":"multiple_choice","answer":["A","B"]},{"questionId":"e6bb2566-90a8-4651-b553-083a1d695b3c","questionType":"true_false","answer":"true"}]}'
|
||||
```
|
||||
|
||||
**answerType 测试总结**:
|
||||
|
||||
| answerType 值 | 场景 | 重复提交同一题目 | 数据库行为 |
|
||||
| ------------- | ---- | -------- | ------------ |
|
||||
| `practice` | 日常练习 | 允许 | UPDATE 已有记录 |
|
||||
| `mock_exam` | 模拟考 | 允许 | UPDATE 已有记录 |
|
||||
| `formal_exam` | 正式考试 | 不允许 | 抛出异常(唯一索引保护) |
|
||||
|
||||
***
|
||||
|
||||
## 五、AI端接口测试
|
||||
|
||||
AI端接口**支持** **`explanation`** **字段**,可以直接使用包含完整题目信息的JSON。
|
||||
|
||||
### 5.1 单选题测试
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:5001/exam/grade -H "Content-Type: application/json" -d '{"answers":[{"questionId":"q1","questionType":"single_choice","questionContent":{"stem":"根据文档内容,务求实效原则的核心要求聚焦于哪一根本目标?","data":{"options":[{"key":"A","content":"扩大终端数量规模"},{"key":"B","content":"提升终端信息化系统覆盖率"},{"key":"C","content":"着眼于终端功能发挥和服务水平提升"},{"key":"D","content":"统一各类终端建设外观标准"}]},"answer":"C","explanation":"文档明确指出要着眼于终端功能发挥和服务水平提升"},"studentAnswer":"C","maxScore":2}]}'
|
||||
```
|
||||
|
||||
### 5.2 多选题测试
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:5001/exam/grade -H "Content-Type: application/json" -d '{"answers":[{"questionId":"q2","questionType":"multiple_choice","questionContent":{"stem":"依据务求实效原则,以下哪些做法符合其核心要求?(可多选)","data":{"options":[{"key":"A","content":"科学设定终端投入标准"},{"key":"B","content":"严格执行终端投入标准"},{"key":"C","content":"优先采购高配置但非必需的智能设备"},{"key":"D","content":"坚决杜绝不必要的支出和浪费"}]},"answer":["A","B","D"],"explanation":"A、B、D均符合务求实效原则"},"studentAnswer":["A","B","D"],"maxScore":4}]}'
|
||||
```
|
||||
|
||||
### 5.3 空白试卷测试(answers 为空数组)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:5001/exam/grade -H "Content-Type: application/json" -d '{"answers":[]}'
|
||||
```
|
||||
|
||||
**预期响应**:
|
||||
|
||||
- **正常情况**(Python AI 已做空值防御):
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"requestId": null,
|
||||
"results": [],
|
||||
"scoreRate": 0.0,
|
||||
"success": true,
|
||||
"totalMaxScore": 0,
|
||||
"totalScore": 0
|
||||
},
|
||||
"message": "批阅完成",
|
||||
"status": "success",
|
||||
"statusCode": 2012,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
- **异常情况**(Python AI 未做空值防御):
|
||||
```json
|
||||
{
|
||||
"error_code": "GRADE_ERROR",
|
||||
"message": "'NoneType' object has no attribute 'get'",
|
||||
"status": "failed",
|
||||
"status_code": 5021,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 填空题空答案测试
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:5001/exam/grade -H "Content-Type: application/json" -d '{"answers":[{"questionId":"q3","questionType":"fill_blank","questionContent":{"stem":"务求实效原则强调以___为出发点,通过科学设定和严格执行终端投入标准,实现___与___的双重提升。","data":{"blank_count":3},"answer":["终端功能发挥和服务水平提升","终端功能发挥","服务水平提升"]},"studentAnswer":null,"maxScore":3}]}'
|
||||
```
|
||||
|
||||
### 5.5 主观题空答案测试
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:5001/exam/grade -H "Content-Type: application/json" -d '{"answers":[{"questionId":"q4","questionType":"subjective","questionContent":{"stem":"请简述\"务求实效原则\"的核心要求。","data":{"scoring_points":[{"point":"功能发挥","weight":0.5},{"point":"服务水平","weight":0.3},{"point":"杜绝浪费","weight":0.2}]},"answer":"以终端功能发挥和服务水平提升为唯一导向"},"studentAnswer":"","maxScore":5}]}'
|
||||
```
|
||||
|
||||
### 5.6 判断题空答案测试(studentAnswer 为空字符串)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:5001/exam/grade -H "Content-Type: application/json" -d '{"answers":[{"questionId":"q5","questionType":"true_false","questionContent":{"stem":"务求实效原则允许在终端建设中适度预留冗余预算,以应对未来可能的功能升级需求。","answer":"false"},"studentAnswer":"","maxScore":2}]}'
|
||||
```
|
||||
|
||||
### 5.7 判断题空答案测试(studentAnswer 为 null)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:5001/exam/grade -H "Content-Type: application/json" -d '{"answers":[{"questionId":"q6","questionType":"true_false","questionContent":{"stem":"务求实效原则强调以终端功能发挥和服务水平提升为唯一导向。","answer":"true"},"studentAnswer":null,"maxScore":2}]}'
|
||||
```
|
||||
|
||||
### 5.8 单选题空答案测试
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:5001/exam/grade -H "Content-Type: application/json" -d '{"answers":[{"questionId":"q7","questionType":"single_choice","questionContent":{"stem":"根据文档内容,务求实效原则的核心要求聚焦于哪一根本目标?","data":{"options":[{"key":"A","content":"扩大终端数量规模"},{"key":"B","content":"提升终端信息化系统覆盖率"},{"key":"C","content":"着眼于终端功能发挥和服务水平提升"},{"key":"D","content":"统一各类终端建设外观标准"}]},"answer":"C"},"studentAnswer":null,"maxScore":2}]}'
|
||||
```
|
||||
|
||||
### 5.9 多选题空答案测试
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:5001/exam/grade -H "Content-Type: application/json" -d '{"answers":[{"questionId":"q8","questionType":"multiple_choice","questionContent":{"stem":"依据务求实效原则,以下哪些做法符合其核心要求?(可多选)","data":{"options":[{"key":"A","content":"科学设定终端投入标准"},{"key":"B","content":"严格执行终端投入标准"},{"key":"C","content":"优先采购高配置但非必需的智能设备"},{"key":"D","content":"坚决杜绝不必要的支出和浪费"}]},"answer":["A","B","D"]},"studentAnswer":[],"maxScore":4}]}'
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## 六、接口对比总结
|
||||
|
||||
| 特性 | 后端通用接口 (/api/exam/grade) | 后端正式考试接口 (/api/exam/record/{recordId}/grade) | AI端接口 (/exam/grade) |
|
||||
| --------------- | -------------------------------------- | -------------------------------------------------------- | ---------------------------------- |
|
||||
| 地址 | <http://localhost:8080/api/exam/grade> | <http://localhost:8080/api/exam/record/{recordId}/grade> | <http://localhost:5001/exam/grade> |
|
||||
| 认证 | 需要 Bearer Token | 需要 Bearer Token | 不需要认证 |
|
||||
| requestId | 必需 | 不需要 | 不需要 |
|
||||
| recordId | 可选(正式考试时使用) | 路径参数(必需) | 不支持 |
|
||||
| answerType | 前端传入,后端原样使用 | 固定为 "formal\_exam" | 不支持 |
|
||||
| questionId | 使用数据库UUID | 使用数据库UUID | 可自定义ID |
|
||||
| maxScore | 小数 (2.0) | 不需要(从数据库获取) | 整数 (2) |
|
||||
| explanation字段 | **不支持** | **不支持** | 支持 |
|
||||
| questionContent | 日常练习/模拟考时必需 | 不需要 | 支持 |
|
||||
|
||||
**recordId 参数说明**:
|
||||
|
||||
- 当在通用接口中传入 `recordId` 时,系统会尝试查找并更新对应的考试记录
|
||||
- 这种方式主要用于正式考试场景,配合 `answerType: "formal_exam"` 使用
|
||||
- 对于日常练习或模拟考,可以不传入 `recordId`
|
||||
- 推荐使用专门的 `/api/exam/record/{recordId}/grade` 接口进行正式考试
|
||||
|
||||
***
|
||||
|
||||
## 七、预期响应格式
|
||||
|
||||
### 7.1 后端通用接口响应示例(日常练习/模拟考)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "批阅成功",
|
||||
"data": {
|
||||
"requestId": "test_001",
|
||||
"results": [
|
||||
{
|
||||
"questionId": "9d05c475-b4fe-4679-a450-ee88a94b3b34",
|
||||
"correct": true,
|
||||
"feedback": "回答正确!",
|
||||
"score": 2.0,
|
||||
"maxScore": 2.0
|
||||
}
|
||||
],
|
||||
"totalScore": 2.0,
|
||||
"totalMaxScore": 2.0,
|
||||
"scoreRate": 100.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 后端正式考试接口响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "提交成功",
|
||||
"data": {
|
||||
"recordId": "59759c40-ee4e-4ff5-ab03-4819a0499beb",
|
||||
"paperId": "0ef70443-67d3-48cd-9309-f5088c092fe1",
|
||||
"userId": 1,
|
||||
"answerType": "formal_exam",
|
||||
"status": "graded",
|
||||
"totalScore": 12.0,
|
||||
"paperTitle": "测试试卷",
|
||||
"answers": [
|
||||
{
|
||||
"questionId": "9d05c475-b4fe-4679-a450-ee88a94b3b34",
|
||||
"score": 2.0,
|
||||
"isCorrect": true,
|
||||
"feedback": "回答正确!"
|
||||
}
|
||||
],
|
||||
"startTime": "2024-05-20T10:00:00",
|
||||
"submitTime": "2024-05-20T10:30:00",
|
||||
"gradedTime": "2024-05-20T10:30:05"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.3 AI端接口响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"requestId": null,
|
||||
"results": [
|
||||
{
|
||||
"correct": true,
|
||||
"feedback": "正确!",
|
||||
"maxScore": 2,
|
||||
"questionId": "9d05c475-b4fe-4679-a450-ee88a94b3b34",
|
||||
"score": 2
|
||||
}
|
||||
],
|
||||
"scoreRate": 100.0,
|
||||
"success": true,
|
||||
"totalMaxScore": 2,
|
||||
"totalScore": 2
|
||||
},
|
||||
"message": "批阅完成",
|
||||
"status": "success",
|
||||
"statusCode": 2021,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 7.4 空白试卷响应示例(日常练习/模拟考)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "批阅成功",
|
||||
"data": {
|
||||
"requestId": "blank_test_001",
|
||||
"results": [],
|
||||
"totalScore": 0.0,
|
||||
"totalMaxScore": 0.0,
|
||||
"scoreRate": 0.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.5 空白试卷响应示例(正式考试)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "提交成功",
|
||||
"data": {
|
||||
"recordId": "59759c40-ee4e-4ff5-ab03-4819a0499beb",
|
||||
"paperId": "0ef70443-67d3-48cd-9309-f5088c092fe1",
|
||||
"userId": 1,
|
||||
"answerType": "formal_exam",
|
||||
"status": "graded",
|
||||
"totalScore": 0.0,
|
||||
"paperTitle": "测试试卷",
|
||||
"answers": [],
|
||||
"startTime": "2024-05-20T10:00:00",
|
||||
"submitTime": "2024-05-20T10:30:00",
|
||||
"gradedTime": "2024-05-20T10:30:05"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## 八、测试数据来源说明
|
||||
|
||||
本测试文档中的题目数据均来源于数据库表 `question`,具体来源如下:
|
||||
|
||||
| questionId | 题目类型 | 文档来源 |
|
||||
| -------------------------------------- | ---------------- | ------------------- |
|
||||
| `9d05c475-b4fe-4679-a450-ee88a94b3b34` | single\_choice | public\_kb - 务求实效原则 |
|
||||
| `ee652f7e-da5e-4129-8766-c113277332f4` | multiple\_choice | public\_kb - 务求实效原则 |
|
||||
| `76913ad8-5325-4f30-a03a-6e19e434b59e` | true\_false | public\_kb - 务求实效原则 |
|
||||
| `7ae3daef-0345-4852-bd37-336e74498cc4` | fill\_blank | public\_kb - 务求实效原则 |
|
||||
| `ea6ba220-43ff-44d8-8b24-cd350eeb8b53` | subjective | public\_kb - 务求实效原则 |
|
||||
|
||||
数据库文件路径:[knowledge\_management\_system.sql](file:///d:/Users/19211/Desktop/demo/docs/sql/knowledge_management_system.sql)
|
||||
|
||||
### 8.1 数据库题目查询 SQL
|
||||
|
||||
如需从数据库获取更多测试题目,可使用以下 SQL 查询:
|
||||
|
||||
```sql
|
||||
-- 查询所有已审核通过的题目
|
||||
SELECT question_id, question_type, difficulty, score, content
|
||||
FROM question
|
||||
WHERE status = 'approved'
|
||||
ORDER BY question_type, id;
|
||||
|
||||
-- 查询指定类型的题目
|
||||
SELECT question_id, question_type, content
|
||||
FROM question
|
||||
WHERE status = 'approved'
|
||||
AND question_type = 'single_choice'
|
||||
LIMIT 10;
|
||||
|
||||
-- 查询指定难度的题目
|
||||
SELECT question_id, question_type, difficulty, content
|
||||
FROM question
|
||||
WHERE status = 'approved'
|
||||
AND difficulty = 3;
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## 九、重要提示
|
||||
|
||||
**后端接口 QuestionContent 字段定义**:
|
||||
|
||||
```java
|
||||
private String stem;
|
||||
private Map<String, Object> data;
|
||||
private Object answer;
|
||||
```
|
||||
|
||||
**注意**:后端接口**不支持** **`explanation`** **字段**,如果传入会导致JSON解析错误。
|
||||
|
||||
**字段命名规范**:
|
||||
|
||||
- 所有字段使用驼峰命名(camelCase),而不是下划线命名(snake\_case)
|
||||
- 例如:`answerType`、`recordId`、`questionId`、`studentAnswer`、`maxScore`
|
||||
|
||||
**使用建议**:
|
||||
|
||||
1. **所有curl命令已改为单行格式**,可以直接复制使用
|
||||
2. **Token有效期2小时**,过期后需要重新获取
|
||||
3. **测试时请确保后端服务和AI服务都已启动**
|
||||
4. **日常练习和模拟考可以重复提交**,会自动更新记录
|
||||
5. **正式考试只能提交一次**,重复提交会报错
|
||||
|
||||
**空白试卷测试说明**:
|
||||
|
||||
- 空白试卷(`answers` 为空数组)测试用于验证系统在无答案情况下的处理能力
|
||||
- 正常情况下,空白试卷应返回总分 0.0,不触发错误
|
||||
- 若 Python AI 批改服务未做空值防御,可能会出现 `'NoneType' object has no attribute 'get'` 错误(500 状态码)
|
||||
- 相关修复方案可参考:`后端问题诊断报告_考试提交AI批改服务崩溃.md`
|
||||
|
||||
**AI端空白试卷测试要点**:
|
||||
|
||||
- AI端接口(`http://localhost:5001/exam/grade`)**支持完整的题目信息**,包含 `explanation` 字段
|
||||
- AI端采用**小写驼峰命名**(如 `questionId`、`questionType`、`questionContent`、`studentAnswer`、`maxScore`)
|
||||
- 空白试卷测试重点验证 Python AI 服务的空值防御能力
|
||||
- 填空题空答案(`studentAnswer: null`)和主观题空答案(`studentAnswer: ""`)也是常见的边界测试场景
|
||||
|
||||
***
|
||||
|
||||
**文档结束**
|
||||
Reference in New Issue
Block a user