nex_math/README.md

350 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 数学学习系统(前后端分离版)
把原单文件静态页 `math_learning_dashboard_v3.html` 重构为可运行的前后端分离系统,
前端采用 Ant Design,包含用户体系与标准 RBAC。
页面结构、题库与学习流程在此基础上重设计:知识点以“初等/高等数学知识图谱”
为全局地基,章节与题目通过关系表挂到图谱节点,不再使用文本标签匹配。
学习状态从浏览器 localStorage 改为 SQLite 持久化;教材目录以
《盖尔范德中学生数学思维丛书》为基础,并支持大模型配置与按章节生成题目。
## 技术栈
- 后端:FastAPI + SQLAlchemy + SQLite
- 前端:React + TypeScript + Vite + Ant Design
- 公式渲染:KaTeX(支持 `$...$` / `$$...$$` 与 Markdown 插图)
- 认证:JWT(HS256)+ PBKDF2 密码散列
- RBAC:user / role / user_role / permission / role_permission
- 部署:支持本机直接运行或 `docker compose`
## 目录结构
```text
nex_math/
├── backend/ # FastAPI 应用
│ ├── main.py # 入口(CORS、路由注册)
│ ├── database.py # SQLite 连接与默认库路径
│ ├── models/ # ORM:教材 / 章节 / 知识点 / 题目 / 记录 / 错题 / 模型配置
│ ├── schemas/ # Pydantic 请求响应模型
│ ├── routers/ # overview / practice / data / analytics / archive / catalog / question-bank / llm
│ ├── services/ # llm_client / question_generator / ai_teacher / question_engine / mastery_engine / recommendation
│ ├── seed/ # 目录与题库种子数据
│ └── dbtool.py # 数据库备份 / 恢复 / 内容快照(python -m dbtool)
├── frontend/ # Vite + React 前端
│ └── src/
│ ├── pages/ # 学习端 9 个页面 + 管理端页面 + 登录 / 电子书阅读器
│ ├── components/ # SplitView / Pane(分屏工作台)、Paper(答题卡·题块·解析)、
│ │ # PaperReview(整卷回看)、useApi、ui.tsx 等
│ ├── api/ # API 客户端
│ ├── theme.ts # 设计令牌:标签色 / 状态色 / 掌握度色
│ ├── i18n.ts # 中英双语文案(新增键必须 zh、en 同时补)
│ └── types/
├── data/ # math.db(本机库,被 gitignore)
│ ├── ebooks/ # 教材电子书(随仓库提交)
│ ├── backups/ # db.sh backup 生成的时间戳备份(gitignore)
│ └── dump/ # catalog.sql:内容快照,提交进仓库=远端存了一份库
└── scripts/ # init.sh(建环境)/ start.sh(起服务)/ db.sh(备份与恢复)
```
## 界面与导航
- 左侧菜单栏支持**展开 / 收拢**两种模式:底部“收拢菜单”按钮或顶栏折叠按钮
一键切换,偏好写入 localStorage 下次进入自动恢复;收拢态只显示图标,
悬停出现文字提示,功能分组以分隔线保留。
- 窄屏(<768px)自动切换为覆盖式抽屉导航,顶栏汉堡按钮开合,不再挤压内容区。
- 顶栏统一展示“分区 · 页面说明”、角色标识与头像菜单(设置 / 退出登录),
与侧栏底部用户卡片入口一致。
- 全站样式与控件收敛到三层:**theme.ts(设计令牌)→ ui.tsx(Page / Panel /
Toolbar / DataTable / RowActions / MetricGrid / CountTag / EmptyState 等)
→ 页面**,新页面只组合统一控件,不各写一套尺寸与颜色。
- **题库管理 / 练习记录 / 错题本 / 章节测试采用「清单 + 试卷」分屏工作台**
(`components/SplitView.tsx` + `Pane`):一栏是可搜索的清单,另一栏按真实试卷版式
排题目;两栏各自独立滚动,清单不会把试卷顶走,整页不再是一条长滚动条。
- 题库管理:左「教材→章节」树 + 搜索/难度/来源/使用状态筛选,右栏题目预览卷;
侧栏首行显示题库总数,并提示还有多少题没挂到章节(计数来自 `/api/question-bank/summary`)。
- 练习记录:左练习清单(得分进度条 + 错题数徽标),右该次练习的完整试卷回看,
顶部答题卡按题号标色(正确 / 错误 / 未答),点题号跳题,可切「全部 / 只看错题」与「展开全部解析」。
- 错题本:左错题清单(按知识点筛选、搜索题干),右错题精讲,逐题对照
我的作答 / 正确答案 / 解析,底部上一题、下一题翻页。
- 章节测试:试卷在左,答题卡与本次小结在右,答题中随时点题号跳题、查看已答未答;
交卷后同一套布局直接变成判卷结果。
- 中缝可拖拽调节清单栏宽度(248–560px),偏好写入 localStorage
(`nex_math.split.<bank|records|errors|test>.aside`);窄屏(<992px)自动改为上下堆叠。
- 章节测试按章节组卷,前置条件是**教材有章节目录、章节下挂了题目**。缺任何一步都只给
引导空态,不会变成红色报错,也不会写出 0 题的记录:
- 教材还没有章节 → 提示请管理员在「教材管理」中添加教材、整理章节目录;
- 选了还没挂题的章节 → 组出的是空卷,试卷位置显示「请管理员在题库管理里把题目关联到
本章」,答题卡提示本章暂无题目,提交按钮禁用;
- 章节题量不足 5 题时,组卷条件那行挂黄色徽标提示实际题量。
## 快速开始(本机)
要求:
- Python 3.10+。macOS 自带的 `python3` 往往是 3.9,`init.sh` 会自动改找
`python3.13 / 3.12 / 3.11 / 3.10`;最省事的做法是先装
[uv](https://docs.astral.sh/uv/)(脚本会在 `PATH`、`~/.local/bin`、`~/.cargo/bin` 里找它,
并用它拉一个 CPython 3.12),或 `brew install python@3.12`。
- Node.js 20.19+ / 22.12+(Vite 8 的要求;本机 `node -v` 低于 20 时脚本会给出提示)。
```bash
./scripts/init.sh # 创建虚拟环境、安装依赖、写入种子数据
./scripts/start.sh # 同时启动后端 :8000 与前端 :5173
```
打开 http://localhost:5173。
- `init.sh` 可重复执行:虚拟环境已存在就复用,已有 `data/math.db` 就不重写种子数据。
- 依赖装不上时会自动改用国内镜像重试(pip → 清华源,npm → npmmirror);
也可以显式指定:`PIP_INDEX_URL=… NPM_REGISTRY=… ./scripts/init.sh`;
只想准备后端环境用 `./scripts/init.sh --no-web`。
- `start.sh` 启动前会检查 8000 / 5173 是否被占用并直接告诉你是哪个进程,
换端口:`BACKEND_PORT=8010 FRONTEND_PORT=5180 ./scripts/start.sh`。
默认账号:
| 账号 | 密码 | 角色 | 说明 |
| --- | --- | --- | --- |
| admin | admin123 | 管理员 | 用户、模型、教材课程、题库管理 |
| student | student123 | 普通用户 | 学习总览、练习、错题、分析等 |
首次登录后请通过左侧栏底部用户卡片的“个人设置”修改默认密码,
并在生产环境设置 JWT_SECRET(环境变量)。管理员可在“模型配置”维护多通道模型。
管理员是纯管理账号:不创建学习档案,学习总览/知识地图等学习功能不展示,
学习类接口对管理员返回无权限。
## 快速开始(Docker)
```bash
docker compose up --build
```
前端 http://localhost:5173,后端文档 http://localhost:8000/docs。
数据库会以卷形式挂载在宿主机的 `./data/math.db`。
## 热加载
- 前端:Vite 开发服务器自带 HMR,保存代码页面即时更新;
- 后端:`./scripts/start.sh` 与 `docker compose` 均以
`uvicorn --reload` 启动,Python 代码变更自动重载。
## 功能对照
| 静态版模块 | 新系统位置 | 说明 |
| --- | --- | --- |
| 学习总览 | `/` 学习总览 | 指标卡 + 掌握度 + 推荐计划 |
| 知识地图 | 知识地图 | 知识点掌握度;点击卡片查看定义、掌握度、相关知识点,以及讲了它的教材章节与视频课程(可一键进入本章测试) |
| 章节测试 | 章节测试 | 选书→选章→按掌握度动态抽取 5 题,可“换一套”;唯一的组卷与练习入口。试卷 + 右侧答题卡分屏,需要教材已有章节目录 |
| 错题本 | 错题本 | 左清单 + 右错题精讲分屏:逐题对照我的作答、正确答案与解析,可按知识点筛选 |
| 练习记录 | 练习记录 | 左清单 + 右整卷回看:答题卡跳题、只看错题、展开全部解析 |
| 今日任务 | 今日任务 | 推荐今日章节 → 做章节测试 → 对章节提交掌握反馈 |
| 学习分析 | 学习分析 | 正确率、错题、诊断排序 |
| 导出 / 导入档案 | 个人设置 → 学习档案 | `/api/archive/export`、`/api/archive/import` |
| 教材 / 课程 | 教材 / 课程 | 盖尔范德丛书书目(ISBN)+ 在线课程链接 |
| 教材详情 | 点击教材卡片 | 查看章节目录、每章知识点、题目数量并直接开始本章测试 |
| 题库管理 | 题库管理 | 左章节树 + 筛选、右题目预览卷的分屏;编辑删除题目,按教材章节调用模型生成 |
| 模型配置 | 模型配置 | 多通道模型配置、通道命名、默认通道与连通测试 |
| 用户管理 | 用户管理(管理员) | 创建用户、启停账号、切换角色、重置密码 |
| 个人设置 | 左侧栏底部用户卡片 → 个人设置(主内容区整页) | 修改昵称/密码,导出与导入学习档案 |
| 教材课程管理 | 教材课程管理(管理员) | 教材/课程维护;章节可新增/编辑/删除,也可按教材 AI 自动生成目录,章节可直接跳转到对应题库 |
| 题库生成 | 题库管理 | 先选教材,再选章节与数量,按章节调用大模型生成新题 |
学习数据(掌握度、练习记录、错题、每日完成)按用户隔离,同一账号的数据互相独立。
练习提交时会保存每题题目快照与作答(错误选项、正确答案、解析),因此题库后续
删题也不影响错题本与练习记录的逐题回看;所有明细均归属当前用户,用户之间不可见。
## RBAC 权限
| 权限码 | 说明 | 管理员 | 普通用户 |
| --- | --- | --- | --- |
| learning:use | 学习总览/练习/错题/分析/档案 | ✘ | ✔ |
| users:manage | 用户管理 | ✔ | ✘ |
| llm:manage | 模型配置 | ✔ | ✘ |
| catalog:manage | 教材/课程管理 | ✔ | ✘ |
| question-bank:manage | 题库管理与生成 | ✔ | ✘ |
| knowledge:manage | 知识图谱管理 | ✔ | ✘ |
除登录、健康检查外,所有 `/api/*` 接口都要求 `Authorization: Bearer <token>`;
管理接口在后端通过权限依赖二次校验(`backend/dependencies.py`)。
知识点状态由掌握度自动计算:`≥85 已掌握`、`75–84 基本掌握`、`60–74 待加强`、`<60 重点`。
## 教材目录
当前教材基线为《盖尔范德中学生数学思维丛书》(中国科学技术大学出版社):
| 分册 | ISBN |
| --- | --- |
| 函数和图像(主教材,已上传 PDF 电子书) | 978-7-312-05005-3 |
| 代数 | 978-7-312-04893-7 |
| 三角函数(已上传 PDF) | 978-7-312-04695-7 |
| 坐标方法 | 978-7-312-05006-0 |
| 几何 | 978-7-312-05779-3 |
**有正式目录的书,章节由种子预置**:`seed/catalog.py` 里 `OFFICIAL_*_CHAPTERS`
是核对过原书的正式目录,按 ISBN 绑到 `CATALOG[*].chapters`,`python -m seed`
会幂等地补齐章节,并按 `KNOWLEDGE_CHAPTER_MAP` /
`DEFAULT_CHAPTER_KNOWLEDGE` 把演示题与知识点挂上去(只补空,不覆盖人工改动)。
目前已落地:函数和图像 8 章、三角函数 10 章、坐标方法 7 章、几何 4 章。
**注意**:补章节只发生在初始化(`python -m seed`)时;后端每次启动只对齐
表结构,不会再补数据,所以把自己准备好的 `math.db` 放回去后重启即原样生效。
**没有正式目录的书不由种子编造**(代数、普林斯顿微积分读本、柯西-施瓦茨大师课)。
管理员在「教材 / 课程」打开某本教材的章节面板后,有三种方式:
1. 「添加章节」手动逐条录入名称与内容提示;
2. 「AI 整理章节」:把书的目录原文(版权页/目录页文字,或在别处查到的正式目录)粘进
「目录参考数据」,任务在后台解析目录并为每章挂上知识图谱节点;模型只做整理,
不新增、不补造素材里没有的章节;
3. 纯接口方式 `POST /api/textbooks/{id}/chapters/generate`:先按 ISBN 查 Open Library /
Google Books 的正式目录,再让模型按正式目录还原,查不到且模型没把握时直接拒绝写入。
确认无误的正式目录也可以回填到 `backend/seed/catalog.py` 的
`OFFICIAL_*_CHAPTERS` / `CATALOG[*].chapters`,再执行
`cd backend && .venv/bin/python -m seed`(幂等:只补缺,不覆盖界面上的修改,也不清空学习记录),
这样换机器或库丢了都能一并重建。
> 种子题库的 18 道演示题会按知识点自动挂到《函数和图像》的章节下(当前:引言 3 题、
> 第6章 幂函数 15 题),所以「章节测试」开箱就能组卷;管理员手工改过的章节归属不会被覆盖。
## 模型配置与题目生成
1. 打开“模型配置”→“新增通道”,选择 OpenAI / DeepSeek / 阿里千问(百炼)/
OpenAI 兼容;通道名称建议按“服务商 · 模型”填写(例如“阿里千问 · qwen-plus”),
系统会随服务商选择自动带出默认 Base URL 与模型名。
2. 填写 API Key 并点击该通道的“测试”;Key 仅保存在本地 `data/math.db`,
回显只显示末四位。可新增多个通道并用“设为默认”指定生成题目时使用的通道。
3. 打开“题库管理”,选择教材、章节、数量、难度、该章节知识点(可多选)与
生成使用的模型通道(默认取“默认通道”),
点击“开始生成”。返回结果会做 JSON 结构校验(4 个选项、唯一答案、非空题干)后
入库并挂到所选教材章节下,可在该章“章节测试”中直接使用;题库页支持筛选与删除。
大模型调用统一走 OpenAI Chat Completions 协议(`services/llm_client.py`),
不需要额外 SDK;生成的题若带新知识点,首次作答会自动建立该知识点并参与掌握度计算。
生成提示词要求模型把数学公式写成 LaTeX(`$...$` 或 `$$...$$`)、插图写成
`![图注](图片地址)`;题库、练习、错题本与练习记录中的题目内容均由前端 KaTeX
渲染公式并展示图片。
### 教材章节目录的整理与题库跳转
在「教材 / 课程」中打开某本教材的章节面板后:
1. 「添加章节」逐个录入名称与内容提示;
2. 面板右上角的「AI 整理章节」:选模型通道,把该书目录原文粘进「目录参考数据」,
点「开始整理」。任务在后台执行并显示进度,模型只整理素材里真实出现的目录条目
(不新增、不凭印象补造),同时为每章从知识图谱挑选知识点;与已有章节同名的条目自动跳过,
入库后仍可编辑或删除;
3. 每个章节行都有「题库」入口,点击后跳转到题库管理并自动筛选该书与章节,
可以在那里直接按章节生成新题,或把已有的未挂章节题目编辑到该章下。
题库管理每道题会标注“使用状态”(已使用次数 / 未使用)。使用过的题目和被
章节挂载的题目不允许删除,只能编辑;题目编辑会保留历史答题快照。
### AI 出题配图机制(自动生成,不使用图库/上传)
生成新题弹窗中有一个“需要配图(AI 根据题目自动生成图像)”开关:
- 不勾选:题目正常生成,不携带图像;
- 勾选后:提示词要求模型为这道题生成与题目语义一致的原创 SVG(函数图/几何示意图,
含坐标轴与关键标注),模型把它放在 JSON 的 `image_svg` 字段;
- 后端把每张 SVG 独立保存为 `/api/question-images/generated_*.svg` 并自动写入题干,
因此每道题都有自己独立的图,互不共享。
### 知识点与章节、多知识点
- 每个章节可维护多个“本章知识点”(教材课程管理 → 章节 → 编辑);
- 生成新题先选教材和章节后,知识点下拉自动展示该章节的知识点,可多选;
- 题目支持多个知识点:列表、练习记录、错题、掌握度与推荐都会按全部知识点统计;
- 练习提交时如果出现未见过的新知识点,会自动为用户建立并参与掌握度计算。
## 主要 API
所有接口前缀 `/api`,交互文档见 http://localhost:8000/docs。
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/overview` | 学习总览指标与推荐计划 |
| GET | `/knowledge` | 知识点掌握度 |
| GET | `/knowledge/detail/{id}` | 知识点详情:定义、相关知识点、关联章节与视频资源 |
| POST/DELETE | `/knowledge/nodes/{id}/resources`、`/knowledge/resources/{id}` | 知识点关联视频/课程(管理员) |
| GET | `/practice/chapter/{id}` | 章节测试组卷(不含答案) |
| POST | `/practice/submit` | 提交答案并判分、更新掌握度与错题 |
| GET/POST | `/daily`、`/daily/complete` | 今日完成状态 |
| GET | `/errors`、`/records`、`/records/{id}`、`/analytics` | 错题(含逐题详情)、记录、记录详情、分析 |
| GET/POST | `/archive/export`、`/archive/import` | 学习档案导出 / 恢复 |
| GET | `/textbooks` | 教材/课程目录(含章节,登录可用) |
| POST/PUT/DELETE | `/textbooks` 与 `/textbooks/{id}` | 教材/课程增删改(管理员) |
| POST/PUT/DELETE | `/textbooks/.../chapters` | 章节管理(管理员) |
| POST | `/textbooks/{id}/chapters/generate` | 按教材 AI 生成章节(管理员) |
| GET | `/question-bank/summary` | 题库侧栏计数:总数 / 未挂章节 / AI 生成 / 已使用(管理员) |
| GET/POST/PUT/DELETE | `/question-bank/questions`、`…/generate` | 题库管理:列表/生成/编辑/删除(管理员),已使用题禁止删除 |
| GET | `/llm/settings` | 模型通道列表与服务商预设(管理员) |
| POST/PUT/DELETE | `/llm/channels` 与 `/llm/channels/{id}` | 模型通道增删改(管理员) |
| PUT | `/llm/channels/{id}/default` | 设置默认通道(管理员) |
| POST | `/llm/channels/{id}/test`、`/llm/test` | 通道连通测试(管理员) |
| PUT | `/auth/profile` | 修改自己的用户昵称 |
| POST | `/auth/login`、`/auth/change-password` | 登录、修改自己的密码 |
| GET | `/auth/me` | 当前用户与角色 |
| GET/POST/PUT | `/users`、`/users/{id}`、`/users/roles` | 用户管理(管理员) |
## 配置
后端启动时(`backend/database.py` 导入阶段)会读取 `.env`,优先级为
**进程环境变量 > 仓库根 `.env` > `backend/.env` > 代码默认值**;
容器里 `docker-compose.yml` 注入的 `DATABASE_URL` 因此依然优先。
本机已有一份可用的 `.env`,换机器时复制 `.env.example` 按需修改即可。
- `DATABASE_URL`:留空默认使用 `<项目根>/data/math.db`;本机 `.env` 里写的是
绝对路径,直接就能看到当前实际连的库
- `CORS_ORIGINS`:允许访问的前端地址
- `OPENAI_API_KEY`:可选。只在初始化(`python -m seed`)时用来创建
“OpenAI · gpt-4o-mini”默认通道;正常使用的模型通道请在「模型配置」里维护,
`ai_teacher.py` 的逐题讲解仍为本地规则实现
- `JWT_SECRET`:JWT 签名密钥,生产环境必须设置(改后所有人需要重新登录)
**换库**:停掉后端 → 覆盖 `data/math.db` → 重启。启动只会建表 / 补列,
不会写入任何业务数据,覆盖回来的库保持原样。
## 重置演示数据
```bash
cd backend
.venv/bin/python -m seed --force
```
会清空全部用户的学习数据与题库(含 AI 生成题)并重新写入种子数据;
用户、角色、教材目录与模型配置保留。演示账号的学习历史(练习记录、错题本)
现在包含逐题明细,可直接展开查看每题作答。
`--force` 执行前会自动把当前库备份到 `data/backups/`(打印路径),误操作可用
`./scripts/db.sh restore` 回退。
## 数据备份与恢复
`data/*.db` 在 `.gitignore` 里,仓库只有代码和电子书,**数据库文件从来没有进过远端**:
库一旦被重置,章节、题目这些内容就跟着没了。为此有两层保险:
```bash
./scripts/db.sh path # 数据库文件位置
./scripts/db.sh backup 升级前 # 时间戳备份(用 SQLite backup API,服务在写也安全)
./scripts/db.sh list # 本机备份列表(默认保留最近 20 份)
./scripts/db.sh restore [文件|latest] # 回滚;覆盖前先把当前库再备份一份
./scripts/db.sh dump # 导出 data/dump/catalog.sql(内容快照,建议随改动提交)
./scripts/db.sh load # 把 data/dump/catalog.sql 灌回当前库
./scripts/db.sh dump data/dump/full.sql --full # 整库快照(含账号/答题记录)提交进仓库
./scripts/db.sh load data/dump/full.sql # 新机器上整库还原
```
* `dump` 只导**内容**:教材、章节、知识点与关联、课程、题库。
这份文本提交进 git,远端仓库里就始终有一份可恢复的数据库快照。
* 默认 `dump` 不含账号与学习数据。希望远端也能整库还原时,显式给输出路径,把全量快照
提交进仓库:`./scripts/db.sh dump data/dump/full.sql --full`(含密码散列、答题记录、
错题本;**不含 `llm_settings`,API Key 不会进 git**)。不想让个人数据上远端就删掉这份文件。
不带输出路径的 `dump --full` 只写本机 `data/backups/math.full.sql`。
* 新机器上的恢复顺序:`./scripts/init.sh`(建库 + 种子)→ `./scripts/db.sh load`(内容)
或 `./scripts/db.sh load data/dump/full.sql`(整库)→ 重启后端。
* 想确认远端有没有库快照:`git log --oneline -- data/dump` 有记录才说明推上去了。
* 电子书与库分离:`data/ebooks/` 随仓库提交,库只存文件名。
若库丢了,初始化时的 `seed/ebooks.py` 会按「大小 + SHA-256」认领磁盘上的
已知电子书(`KNOWN_EBOOKS`),重建教材条目并把文件挪回该书目录,不必重新上传。
(这一步只在 `python -m seed` 时执行,后端启动不再改动库与电子书文件。)