# 题库PLUS — C/S 架构数学题库管理系统 ## 项目定位 独立的高中数学题库管理系统,C/S 架构。服务端 Python + FastAPI,客户端 Flutter 跨平台(Web + Android)。 核心区别:题目以 **docx 原生格式** 存储(每题一个 docx),图片和公式天然内嵌,不再经过 Markdown 中间格式。 ## 参考项目 两个现有题库软件位于同级目录: - `题库/` — Python CLI + Web 版(FastAPI + SQLite + pandoc + fzf),Markdown 存储 - `题库C++/` — Qt6 桌面版,C++17,直接兼容 `题库/` 的数据格式 ## 全局设计决策 | # | 决策 | 选择 | |---|------|------| | 1 | 全新开发 vs 改造现有 | **全新开发**(C/S 架构需要重做) | | 2 | 服务端技术栈 | **Python + FastAPI** | | 3 | 题目存储格式 | **每题一个独立 docx** | | 4 | docx 内题干/答案区分 | **`【答案】` 标记行分隔**,元数据存自定义属性 | | 5 | 客户端形态 | **Flutter 跨平台**(Web + Android) | | 6 | 数据库 | **SQLite + 文件即真相** | | 7 | 用户模型 | **单用户**,客户端首次启动输入服务器地址 | | 8 | API 协议 | **REST + JSON + 二进制流**(multipart 上传) | | 9 | 知识点编码 | **完整沿用** 现有 5 层编码体系(1811 叶子) | | 10 | 导入流程 | **服务端拆分**(整卷 docx → N 个单题 docx)+ 目录监控 | | 11 | 全文搜索 | **提取纯文本建 FTS5 索引** | | 12 | 客户端题目渲染 | **混合方案** — Flutter 原生 UI + WebView(pandoc docx→HTML+MathML) | | 13 | 导出组卷 | **python-docx 拼接**,不经过 pandoc | | 14 | 图片处理 | **完全内嵌**在 docx 中,不提取 | | 15 | 元数据存储 | **docx 自定义属性**(Custom Document Properties) | | 16 | 数据迁移 | **无需迁移**,用户提供原始 Word 文件重新导入 | | 17 | 增量索引 | **document.xml SHA256**(忽略 Word 自动元数据变更) | | 18 | API 认证 | **简单 Token**(服务端启动生成,客户端首次输入) | | 19 | 试题篮 | **服务端多篮命名** | | 20 | 知识点标注交互 | **树+搜索混合** + 逐题标注入口 | | 21 | 部署方式 | **裸机安装脚本**(install.sh) | | 22 | 服务端目录结构 | **数据和代码分离**,`data/` 独立 | | 23 | 单题 docx 格式 | `【答案】` 前=题干,后=答案区;`【难度】``【知识点】` 提取后从正文删除 | | 24 | pandoc 渲染管线 | **pandoc docx→HTML + python-docx 微调**(base64 图片、清理 artifact) | | 25 | 知识点树管理 | **保留 `知识点树.md`** 源文件 + CLI `knowledge build` | | 26 | 学生版/教师版导出 | **标记驱动一次遍历**(遇 `【答案】` 跳过生成学生版) | | 27 | fzf 角色 | **仅服务端 CLI 管理**,教师日常走客户端 | | 28 | 本地备份 | **DB + docx 一起备份**,保留 30 个快照 | | 29 | 远程备份 | **WebDAV 客户端**(推送到 NAS/NextCloud) | | 30 | Flutter 状态管理 | **Riverpod** | | 31 | 文件传输 | **multipart 上传 + 二进制流下载** | | 32 | 抬头信息 | **全部丢弃** | | 33 | 题目 ID | **document.xml SHA256 前 8 位** | | 34 | 导入拆分 | **python-docx 段落遍历**(题号正则 + 题型标题双驱动) | | 35 | 服务端代码结构 | **按功能域分包**(routers/ + services/) | | 36 | CLI 命令 | `serve` / `knowledge build` / `rebuild-index` / `stats` / `backup` / `token` | | 37 | 客户端平台 | **Web + Android** | | 38 | 导航结构 | **4 标签底部导航**(题库/搜索/试题篮/设置)| | 39 | 首次配置 | 配置页输入服务器地址 + token,Web 版免配置 | | 40 | 导入进度 | **异步 + 轮询**(POST → task_id → 轮询 status) | | 41 | 分页 | **传统分页**(page + page_size) | | 42 | 错误处理 | **统一 JSON + 全局异常中间件** | | 43 | 客户端部署 | **FastAPI serve Flutter Web** + **APK 下载页** | | 44 | 服务端端口 | **8765**(避开常用端口) | | 45 | pandoc 安装 | **install.sh 检测 + 提示** | | 46 | 解答题小题 | **一整题不拆** | | 47 | 文件名 | `problems/.docx` | | 48 | Web 部署 | **install.sh 检测 Flutter SDK 自动构建** | | 49 | Flutter 项目结构 | **按功能域分包**(pages/models/services/providers/widgets) | | 50 | 缺失元数据 | **部分默认 + 报告警告** | | 51 | 开发阶段 | **1.服务端核心 → 2.Flutter Web → 3.Android → 4.打磨** | ## 单题 docx 格式约定 ``` [题目正文段落,可能含公式(OMML)、图片、选项] > A.... B.... C.... D.... (选择题选项,blockquote 缩进或普通段落) 【答案】X ← 分割线:以上是题干,以下是答案区 【分析】解题思路... (可选) 【详解】详细步骤... (可选) 【点睛】... (可选) ``` **自定义属性**(python-docx Custom Document Properties,Word 不可见): - `question_type`:单选题/多选题/填空题/解答题 - `difficulty`:0-1 浮点数,越大越容易 - `knowledge_codes`:JSON 数组字符串,如 `'["01.01.03.02.01"]'` - `import_type`:精选题/经典题/高考题 - `comment`:评语字符串(高考题自动从文件名生成) 导入时 `【难度】` 和 `【知识点】` 文本从正文提取后**删除**(存入自定义属性),`【分析】``【详解】``【点睛】` 保留在正文。 ## 服务端项目结构 ``` tiku-server/ ├── app/ │ ├── __init__.py │ ├── config.py # 配置加载 (config.yaml) │ ├── database.py # DB 连接 + CRUD │ ├── models.py # Pydantic schemas │ ├── main.py # FastAPI 入口 + 生命周期 │ ├── routers/ │ │ ├── auth.py # token 验证 │ │ ├── problems.py # 题目 CRUD + 搜索 │ │ ├── import_.py # docx 导入拆分 │ │ ├── export_.py # 导出组卷 │ │ ├── knowledge.py # 知识点树 API │ │ ├── carts.py # 试题篮 │ │ └── admin.py # 管理端点 │ └── services/ │ ├── docx_parser.py # docx 拆分 + 元数据提取 │ ├── docx_builder.py # docx 合并 + 排版 │ ├── indexer.py # 增量索引 + FTS │ ├── renderer.py # pandoc docx→HTML 渲染 │ ├── knowledge.py # 知识点树解析编码 │ ├── backup.py # 备份 + WebDAV │ └── search.py # FTS5 搜索封装 ├── data/ # 运行时数据(可单独备份) │ ├── problems/ # 单题 docx (.docx) │ ├── bank.db # SQLite 数据库 │ ├── papers/ # 导出试卷 │ └── backups/ # 自动备份(保留 30 个) ├── src/ │ └── 知识点树.md # 知识点层级树(输入源) ├── web/ # Flutter Web 构建产物 ├── config.yaml ├── tiku-server # CLI 入口脚本 ├── install.sh └── requirements.txt ``` ## 知识点编码体系 5 层 Markdown 层级(`#` → `##` → `###` → `####` → `-`),共 1811 叶子。 编码:每层两位数字,各层独立,用 `.` 连接。 示例:`01.01.03.02.01` → 集合与常用逻辑用语/集合/集合的基本运算/并集/并集的概念及运算 ## 数据库结构 ```sql CREATE TABLE problems ( id TEXT PRIMARY KEY, -- document.xml SHA256 前8位 file_path TEXT NOT NULL UNIQUE, -- problems/.docx mtime REAL NOT NULL, checksum TEXT NOT NULL, -- document.xml SHA256 chapters TEXT, -- JSON 数组 difficulty REAL, -- 0-1,越大越容易 question_type TEXT, -- 单选题|多选题|填空题|解答题 import_type TEXT, -- 经典题|高考题|精选题 knowledge_codes TEXT, -- JSON 数组 comment TEXT, source TEXT, -- 来源文件名 created_at TEXT, updated_at TEXT ); CREATE VIRTUAL TABLE problems_fts USING fts5( problem_id UNINDEXED, body, -- docx 纯文本提取 tokenize='trigram' ); CREATE TABLE knowledge_points ( code TEXT PRIMARY KEY, path TEXT NOT NULL, leaf_name TEXT NOT NULL, chapter_code TEXT, chapter_name TEXT ); CREATE TABLE carts ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, created_at TEXT ); CREATE TABLE cart_items ( cart_id INTEGER REFERENCES carts(id), problem_id TEXT REFERENCES problems(id), added_at TEXT, PRIMARY KEY (cart_id, problem_id) ); CREATE TABLE import_tasks ( id TEXT PRIMARY KEY, status TEXT, -- pending|processing|done|error total INTEGER, completed INTEGER, message TEXT, result TEXT, -- JSON 结果 created_at TEXT ); ``` ## API 清单 ### 认证 - `GET /api/health` — 健康检查(无需 auth) - `POST /api/auth/verify` — 验证 token ### 题库查询 - `GET /api/stats` — 仪表盘统计 - `GET /api/problems?page=&page_size=&chapter=&type=&question_type=&annotated=` — 题目列表 - `GET /api/problems/{id}` — 题目元数据 - `GET /api/problems/{id}/render` — 题目 HTML 渲染(WebView 用) - `GET /api/problems/{id}/image` — 图片内嵌访问 - `GET /api/problems/next-unannotated?current_id=&direction=next|prev` — 连续标注导航 - `GET /api/search?q=&page=&page_size=` — 全文搜索 ### 导入导出 - `POST /api/import` — 上传整卷 docx(multipart) - `GET /api/import/{task_id}/status` — 轮询导入进度 - `POST /api/export/chapter` — 按章节+类型导出 - `POST /api/export/cart/{cart_id}` — 导出试题篮 - `GET /api/exports/{filename}` — 下载 docx ### 元数据编辑 - `PUT /api/problems/{id}/metadata` — 更新元数据 - `PUT /api/problems/{id}/knowledge` — 更新知识点编码 ### 知识点 - `GET /api/knowledge/tree` — 完整知识树 - `GET /api/knowledge/search?q=` — 搜索知识点 ### 试题篮 - `GET /api/carts` — 篮子列表 - `POST /api/carts` — 创建篮子 - `DELETE /api/carts/{id}` — 删除篮子 - `GET /api/carts/{id}` — 篮子详情 - `POST /api/carts/{id}/items` — 添加题目 - `DELETE /api/carts/{id}/items/{pid}` — 移除题目 ### 管理 - `GET /api/admin/backup-config` — 查看 WebDAV 配置 - `PUT /api/admin/backup-config` — 设置 WebDAV 配置 - `POST /api/admin/backup/trigger` — 手动触发备份 - `GET /api/admin/backup/history` — 备份历史 ## CLI 管理命令 ``` tiku-server serve — 启动 FastAPI 服务 tiku-server knowledge build — 重建知识点编码 tiku-server rebuild-index — 手动增量索引 tiku-server stats — 终端统计 tiku-server backup — 手动备份 tiku-server backup --remote — 推送到 WebDAV tiku-server token — 显示/重新生成 token ``` ## 导入拆分算法 1. python-docx 遍历段落,跳过抬头部分 2. 遇到 `**X、题型名**` 段落(含关键词:单选题/多选题/填空题/解答题)→ 设置 `current_type` 3. 遇到 `^\d+[..]` 开头段落 → 新题开始,完成上一题 4. 遇到 `【答案】` → 标记答案区起点(同一段落之后的归答案区) 5. 按答案区切分该题,提取 `【难度】` `【知识点】` 并删除,保留 `【分析】` `【详解】` `【点睛】` 6. 题干部分 + 答案区分别写入新的单题 docx 7. 元数据写入自定义属性 8. 知识点名称查 `knowledge_map.json` 转编码,不匹配的跳过并警告 ## 导出合并算法 1. 获取选中题目的 docx 文件列表 2. python-docx 打开每个单题 docx 3. 遍历段落:在学生版中遇到 `【答案】` 就跳过该段及之后所有内容;教师版保留全部 4. 每题前加 `N.` 标记 → cleanup 识别 → Word 自动编号 5. 统一排版:宋体五号 (10.5pt) 6. 输出 `papers/<标题>_<时间戳>_学生版.docx` / `_教师版.docx` ## Flutter 客户端结构 ``` lib/ ├── main.dart ├── app.dart # MaterialApp + GoRouter + 主题 ├── models/ # Problem, KnowledgeNode, Cart, ... ├── services/ # API 调用封装 ├── providers/ # Riverpod providers ├── pages/ │ ├── home/ # 题库首页(三栏) │ ├── search/ # 搜索页 │ ├── problem_detail/ # 题目详情 │ ├── carts/ # 试题篮列表 + 详情 │ ├── export/ # 导出操作 │ └── settings/ # 设置 + 首次配置 └── widgets/ # 共享组件 ├── knowledge_tree.dart # 知识点树 ├── problem_card.dart # 题目卡片 └── webview_render.dart # WebView 题目渲染 ``` ## 备份策略 - **本地**:变更操作前自动备份 `bank.db` + 被修改 docx → `data/backups//`,保留 30 个 - **远程**:配置 WebDAV 地址后,每次操作后自动推送备份 → NAS/NextCloud - **手动**:`tiku-server backup [--remote]` ## 开发阶段 1. **服务端核心** — 改造现有 Python 版为 C/S 后端(导入/搜索/导出/知识点/试题篮/认证/CLI/install.sh) 2. **Flutter Web** — 所有页面 + WebView 集成 3. **Flutter Android** — 移动端适配 + APK 分发 4. **打磨** — WebDAV 备份、性能、文档