mirror of
https://github.com/handsomezhuzhu/QQuiz.git
synced 2026-02-20 20:10:14 +00:00
## 新功能 - 实现管理后台API配置管理(OpenAI/Anthropic/Qwen) - API配置保存到数据库,实时生效无需重启 - API密钥遮罩显示(前10位+后4位) - 完整endpoint URL自动显示 ## 后端改进 - 新增 config_service.py 用于加载数据库配置 - LLMService 支持动态配置注入,回退到环境变量 - 更新 exam.py 和 question.py 使用数据库配置 - 扩展 schemas.py 支持所有API配置字段 ## 前端改进 - 重写 AdminSettings.jsx 增强UI体验 - API密钥显示/隐藏切换 - 当前使用的提供商可视化标识 - 移除"需要重启"的误导性提示 ## 项目结构重组 - 移动所有脚本到 scripts/ 目录 - 移动所有文档到 docs/ 目录 - 清理 Python 缓存文件 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
400 lines
12 KiB
Markdown
400 lines
12 KiB
Markdown
# QQuiz 项目结构
|
||
|
||
## 📁 完整目录结构
|
||
|
||
```
|
||
QQuiz/
|
||
├── backend/ # FastAPI 后端
|
||
│ ├── alembic/ # 数据库迁移
|
||
│ │ ├── versions/ # 迁移脚本
|
||
│ │ ├── env.py # Alembic 环境配置
|
||
│ │ └── script.py.mako # 迁移脚本模板
|
||
│ ├── routers/ # API 路由
|
||
│ │ ├── __init__.py # 路由包初始化
|
||
│ │ ├── auth.py # 认证路由(登录/注册)
|
||
│ │ ├── admin.py # 管理员路由
|
||
│ │ ├── exam.py # 题库路由(创建/追加/查询)⭐
|
||
│ │ ├── question.py # 题目路由(刷题/答题)
|
||
│ │ └── mistake.py # 错题本路由
|
||
│ ├── services/ # 业务逻辑层
|
||
│ │ ├── __init__.py # 服务包初始化
|
||
│ │ ├── auth_service.py # 认证服务(JWT/权限)
|
||
│ │ ├── llm_service.py # AI 服务(解析/评分)⭐
|
||
│ │ └── document_parser.py # 文档解析服务
|
||
│ ├── models.py # SQLAlchemy 数据模型 ⭐
|
||
│ ├── schemas.py # Pydantic 请求/响应模型
|
||
│ ├── database.py # 数据库配置
|
||
│ ├── utils.py # 工具函数(Hash/密码)
|
||
│ ├── main.py # FastAPI 应用入口
|
||
│ ├── requirements.txt # Python 依赖
|
||
│ ├── alembic.ini # Alembic 配置
|
||
│ └── Dockerfile # 后端 Docker 镜像
|
||
│
|
||
├── frontend/ # React 前端
|
||
│ ├── src/
|
||
│ │ ├── api/
|
||
│ │ │ └── client.js # API 客户端(Axios)⭐
|
||
│ │ ├── components/
|
||
│ │ │ ├── Layout.jsx # 主布局(导航栏)
|
||
│ │ │ └── ProtectedRoute.jsx # 路由保护
|
||
│ │ ├── context/
|
||
│ │ │ └── AuthContext.jsx # 认证上下文
|
||
│ │ ├── pages/
|
||
│ │ │ ├── Login.jsx # 登录页
|
||
│ │ │ ├── Register.jsx # 注册页
|
||
│ │ │ ├── Dashboard.jsx # 仪表盘
|
||
│ │ │ ├── ExamList.jsx # 题库列表 ⭐
|
||
│ │ │ ├── ExamDetail.jsx # 题库详情(追加上传)⭐
|
||
│ │ │ ├── QuizPlayer.jsx # 刷题核心页面 ⭐
|
||
│ │ │ ├── MistakeList.jsx # 错题本
|
||
│ │ │ └── AdminSettings.jsx # 系统设置
|
||
│ │ ├── utils/
|
||
│ │ │ └── helpers.js # 工具函数
|
||
│ │ ├── App.jsx # 应用根组件
|
||
│ │ ├── index.jsx # 应用入口
|
||
│ │ └── index.css # 全局样式
|
||
│ ├── public/
|
||
│ │ └── index.html # HTML 模板
|
||
│ ├── package.json # Node 依赖
|
||
│ ├── vite.config.js # Vite 配置
|
||
│ ├── tailwind.config.js # Tailwind CSS 配置
|
||
│ ├── postcss.config.js # PostCSS 配置
|
||
│ └── Dockerfile # 前端 Docker 镜像
|
||
│
|
||
├── docker-compose.yml # Docker 编排配置 ⭐
|
||
├── .env.example # 环境变量模板
|
||
├── .gitignore # Git 忽略文件
|
||
├── README.md # 项目说明
|
||
├── DEPLOYMENT.md # 部署指南
|
||
├── PROJECT_STRUCTURE.md # 项目结构(本文件)
|
||
└── run_local.sh # 本地运行脚本
|
||
|
||
⭐ 表示核心文件
|
||
```
|
||
|
||
---
|
||
|
||
## 🔑 核心文件说明
|
||
|
||
### 后端核心
|
||
|
||
#### `models.py` - 数据模型
|
||
定义了 5 个核心数据表:
|
||
- **User**: 用户表(用户名、密码、管理员标识)
|
||
- **SystemConfig**: 系统配置(KV 存储)
|
||
- **Exam**: 题库表(标题、状态、进度、题目数)
|
||
- **Question**: 题目表(内容、类型、选项、答案、**content_hash**)
|
||
- **UserMistake**: 错题本(用户 ID、题目 ID)
|
||
|
||
**关键设计:**
|
||
- `content_hash`: MD5 哈希,用于题目去重
|
||
- `current_index`: 记录刷题进度
|
||
- `status`: Enum 管理题库状态(pending/processing/ready/failed)
|
||
|
||
#### `exam.py` - 题库路由
|
||
实现了最核心的业务逻辑:
|
||
- `POST /create`: 创建题库并上传第一份文档
|
||
- `POST /{exam_id}/append`: 追加文档到现有题库 ⭐
|
||
- `GET /`: 获取题库列表
|
||
- `GET /{exam_id}`: 获取题库详情
|
||
- `PUT /{exam_id}/progress`: 更新刷题进度
|
||
|
||
**去重逻辑:**
|
||
```python
|
||
# 1. 解析文档获取题目
|
||
questions_data = await llm_service.parse_document(content)
|
||
|
||
# 2. 计算每道题的 Hash
|
||
for q in questions_data:
|
||
q["content_hash"] = calculate_content_hash(q["content"])
|
||
|
||
# 3. 仅在当前 exam_id 范围内查询去重
|
||
existing_hashes = await db.execute(
|
||
select(Question.content_hash).where(Question.exam_id == exam_id)
|
||
)
|
||
|
||
# 4. 仅插入 Hash 不存在的题目
|
||
for q in questions_data:
|
||
if q["content_hash"] not in existing_hashes:
|
||
db.add(Question(**q))
|
||
```
|
||
|
||
#### `llm_service.py` - AI 服务
|
||
提供两个核心功能:
|
||
1. `parse_document()`: 调用 LLM 解析文档,提取题目
|
||
2. `grade_short_answer()`: AI 评分简答题
|
||
|
||
支持 3 个 AI 提供商:
|
||
- OpenAI (GPT-4o-mini)
|
||
- Anthropic (Claude-3-haiku)
|
||
- Qwen (通义千问)
|
||
|
||
---
|
||
|
||
### 前端核心
|
||
|
||
#### `client.js` - API 客户端
|
||
封装了所有后端 API:
|
||
- `authAPI`: 登录、注册、用户信息
|
||
- `examAPI`: 题库 CRUD、追加文档
|
||
- `questionAPI`: 获取题目、答题
|
||
- `mistakeAPI`: 错题本管理
|
||
- `adminAPI`: 系统配置
|
||
|
||
**特性:**
|
||
- 自动添加 JWT Token
|
||
- 统一错误处理和 Toast 提示
|
||
- 401 自动跳转登录
|
||
|
||
#### `ExamDetail.jsx` - 题库详情
|
||
最复杂的前端页面,包含:
|
||
- **追加上传**: 上传新文档并去重
|
||
- **状态轮询**: 每 3 秒轮询一次状态
|
||
- **智能按钮**:
|
||
- 处理中时禁用「添加文档」
|
||
- 就绪后显示「开始/继续刷题」
|
||
- **进度展示**: 题目数、完成度、进度条
|
||
|
||
**状态轮询实现:**
|
||
```javascript
|
||
useEffect(() => {
|
||
const interval = setInterval(() => {
|
||
pollExamStatus() // 轮询状态
|
||
}, 3000)
|
||
|
||
return () => clearInterval(interval)
|
||
}, [examId])
|
||
|
||
const pollExamStatus = async () => {
|
||
const newExam = await examAPI.getDetail(examId)
|
||
|
||
// 检测状态变化
|
||
if (exam?.status === 'processing' && newExam.status === 'ready') {
|
||
toast.success('文档解析完成!')
|
||
await loadExamDetail() // 重新加载数据
|
||
}
|
||
|
||
setExam(newExam)
|
||
}
|
||
```
|
||
|
||
#### `QuizPlayer.jsx` - 刷题核心
|
||
实现完整的刷题流程:
|
||
1. 基于 `current_index` 加载当前题目
|
||
2. 根据题型显示不同的答题界面
|
||
3. 提交答案并检查(简答题调用 AI 评分)
|
||
4. 答错自动加入错题本
|
||
5. 点击下一题自动更新进度
|
||
|
||
**断点续做实现:**
|
||
```javascript
|
||
// 始终基于 exam.current_index 加载题目
|
||
const loadCurrentQuestion = async () => {
|
||
const question = await questionAPI.getCurrentQuestion(examId)
|
||
// 后端会根据 current_index 返回对应题目
|
||
}
|
||
|
||
// 下一题时更新进度
|
||
const handleNext = async () => {
|
||
const newIndex = exam.current_index + 1
|
||
await examAPI.updateProgress(examId, newIndex)
|
||
await loadCurrentQuestion()
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔄 核心业务流程
|
||
|
||
### 1. 创建题库流程
|
||
|
||
```
|
||
用户上传文档
|
||
↓
|
||
后端创建 Exam (status=pending)
|
||
↓
|
||
后台任务开始解析
|
||
↓
|
||
更新状态为 processing
|
||
↓
|
||
调用 document_parser 解析文件
|
||
↓
|
||
调用 llm_service 提取题目
|
||
↓
|
||
计算 content_hash 并去重
|
||
↓
|
||
插入新题目到数据库
|
||
↓
|
||
更新 total_questions 和 status=ready
|
||
↓
|
||
前端轮询检测到状态变化
|
||
↓
|
||
自动刷新显示新题目
|
||
```
|
||
|
||
### 2. 追加文档流程
|
||
|
||
```
|
||
用户点击「添加题目文档」
|
||
↓
|
||
上传新文档
|
||
↓
|
||
后端检查 Exam 是否在处理中
|
||
↓
|
||
更新状态为 processing
|
||
↓
|
||
后台任务解析新文档
|
||
↓
|
||
提取题目并计算 Hash
|
||
↓
|
||
仅在当前 exam_id 范围内查重
|
||
↓
|
||
插入不重复的题目
|
||
↓
|
||
更新 total_questions
|
||
↓
|
||
更新状态为 ready
|
||
↓
|
||
前端轮询检测并刷新
|
||
```
|
||
|
||
### 3. 刷题流程
|
||
|
||
```
|
||
用户点击「开始刷题」
|
||
↓
|
||
基于 current_index 加载题目
|
||
↓
|
||
用户选择/输入答案
|
||
↓
|
||
提交答案到后端
|
||
↓
|
||
后端检查答案
|
||
├─ 选择题:字符串比对
|
||
├─ 多选题:排序后比对
|
||
├─ 判断题:字符串比对
|
||
└─ 简答题:调用 AI 评分
|
||
↓
|
||
答错自动加入错题本
|
||
↓
|
||
返回结果和解析
|
||
↓
|
||
用户点击「下一题」
|
||
↓
|
||
更新 current_index += 1
|
||
↓
|
||
加载下一题
|
||
```
|
||
|
||
---
|
||
|
||
## 🗄️ 数据库设计
|
||
|
||
### 关键索引
|
||
|
||
```sql
|
||
-- Exam 表
|
||
CREATE INDEX ix_exams_user_status ON exams(user_id, status);
|
||
|
||
-- Question 表
|
||
CREATE INDEX ix_questions_exam_hash ON questions(exam_id, content_hash);
|
||
CREATE INDEX ix_questions_content_hash ON questions(content_hash);
|
||
|
||
-- UserMistake 表
|
||
CREATE UNIQUE INDEX ix_user_mistakes_unique ON user_mistakes(user_id, question_id);
|
||
```
|
||
|
||
### 关键约束
|
||
|
||
- `Question.content_hash`: 用于去重,同一 exam_id 下不允许重复
|
||
- `UserMistake`: user_id + question_id 唯一约束,防止重复添加
|
||
- 级联删除:删除 Exam 时自动删除所有关联的 Question 和 UserMistake
|
||
|
||
---
|
||
|
||
## 🎨 技术栈
|
||
|
||
### 后端
|
||
- **FastAPI**: 现代化 Python Web 框架
|
||
- **SQLAlchemy 2.0**: 异步 ORM
|
||
- **Alembic**: 数据库迁移
|
||
- **Pydantic**: 数据验证
|
||
- **JWT**: 无状态认证
|
||
- **OpenAI/Anthropic/Qwen**: AI 解析和评分
|
||
|
||
### 前端
|
||
- **React 18**: UI 框架
|
||
- **Vite**: 构建工具(比 CRA 更快)
|
||
- **Tailwind CSS**: 原子化 CSS
|
||
- **Axios**: HTTP 客户端
|
||
- **React Router**: 路由管理
|
||
- **React Hot Toast**: 消息提示
|
||
|
||
### 部署
|
||
- **Docker + Docker Compose**: 容器化部署
|
||
- **PostgreSQL 15**: 关系型数据库
|
||
- **Nginx** (可选): 反向代理
|
||
|
||
---
|
||
|
||
## 📊 API 接口汇总
|
||
|
||
### 认证相关
|
||
- `POST /api/auth/register`: 用户注册
|
||
- `POST /api/auth/login`: 用户登录
|
||
- `GET /api/auth/me`: 获取当前用户信息
|
||
- `POST /api/auth/change-password`: 修改密码
|
||
|
||
### 题库相关
|
||
- `POST /api/exams/create`: 创建题库
|
||
- `POST /api/exams/{exam_id}/append`: 追加文档 ⭐
|
||
- `GET /api/exams/`: 获取题库列表
|
||
- `GET /api/exams/{exam_id}`: 获取题库详情
|
||
- `DELETE /api/exams/{exam_id}`: 删除题库
|
||
- `PUT /api/exams/{exam_id}/progress`: 更新进度
|
||
|
||
### 题目相关
|
||
- `GET /api/questions/exam/{exam_id}/questions`: 获取题库所有题目
|
||
- `GET /api/questions/exam/{exam_id}/current`: 获取当前题目
|
||
- `GET /api/questions/{question_id}`: 获取题目详情
|
||
- `POST /api/questions/check`: 检查答案
|
||
|
||
### 错题本相关
|
||
- `GET /api/mistakes/`: 获取错题列表
|
||
- `POST /api/mistakes/add`: 添加错题
|
||
- `DELETE /api/mistakes/{mistake_id}`: 移除错题
|
||
- `DELETE /api/mistakes/question/{question_id}`: 按题目 ID 移除
|
||
|
||
### 管理员相关
|
||
- `GET /api/admin/config`: 获取系统配置
|
||
- `PUT /api/admin/config`: 更新系统配置
|
||
|
||
---
|
||
|
||
## 🔒 安全特性
|
||
|
||
1. **密码加密**: bcrypt 哈希
|
||
2. **JWT 认证**: 无状态 Token
|
||
3. **权限控制**: 管理员/普通用户
|
||
4. **CORS 保护**: 可配置允许的来源
|
||
5. **文件类型验证**: 仅允许特定格式
|
||
6. **文件大小限制**: 可配置最大上传大小
|
||
7. **速率限制**: 每日上传次数限制
|
||
|
||
---
|
||
|
||
## 🎯 核心创新点
|
||
|
||
1. **智能去重**: 基于 content_hash 的高效去重算法
|
||
2. **追加上传**: 支持向现有题库添加新文档
|
||
3. **异步处理**: 后台任务处理文档解析,不阻塞用户
|
||
4. **状态轮询**: 前端实时显示处理状态
|
||
5. **断点续做**: 基于 current_index 的进度管理
|
||
6. **AI 评分**: 简答题智能评分和反馈
|
||
7. **自动错题本**: 答错自动收集,支持手动管理
|
||
8. **多 AI 支持**: 灵活切换 AI 提供商
|
||
|
||
---
|
||
|
||
这就是 QQuiz 的完整架构!🎉
|