CLAUDE.md 14 KB

题库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/<sha256-8>.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 (<sha256-8>.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 → 集合与常用逻辑用语/集合/集合的基本运算/并集/并集的概念及运算

数据库结构

CREATE TABLE problems (
    id TEXT PRIMARY KEY,              -- document.xml SHA256 前8位
    file_path TEXT NOT NULL UNIQUE,   -- problems/<id>.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/<timestamp>/,保留 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 备份、性能、文档