# 题库PLUS 服务端 C/S 架构数学题库管理系统 — 服务端。Python + FastAPI + SQLite,以 docx 原生格式存储题目。 ## 快速部署 ```bash git clone ssh://git@xn--48s96u.top:10022/nonvioce/tiku.git /opt/tiku-server cd /opt/tiku-server/tiku-server bash install.sh # 安装依赖、构建知识点 nohup ./tiku-server serve --restart > tiku.log 2>&1 & # 后台启动(崩溃/更新自动重启) ``` 部署后打开 `http://<服务器IP>:8765`,Token 首次启动自动生成(`./tiku-server token` 查看)。 ## 目录结构 ``` tiku-server/ ├── app/ │ ├── config.py # YAML 配置加载(路径、端口、Token) │ ├── database.py # SQLite 连接、建表、CRUD、FTS5 │ ├── models.py # Pydantic 请求/响应模型 │ ├── main.py # FastAPI 入口 + CLI 入口 │ ├── routers/ # API 路由层(仅参数校验和 HTTP 返回) │ │ ├── auth.py # Token + HMAC 签名认证 │ │ ├── problems.py # 题目 CRUD、搜索、渲染、标注 │ │ ├── import_.py # 整卷 docx 上传拆分导入 │ │ ├── export_.py # 按章节/试题篮导出 docx │ │ ├── knowledge.py # 知识点树浏览和搜索 │ │ ├── carts.py # 试题篮 CRUD │ │ └── admin.py # 备份、索引重建、知识点构建、Git 更新 │ └── services/ # 业务逻辑层 │ ├── knowledge.py # 5层知识点树解析(Markdown → 编码映射) │ ├── docx_parser.py # 整卷 docx 拆分 + 单题 docx 生成 │ ├── docx_builder.py # 多题 docx 合并导出 │ ├── indexer.py # 增量索引(mtime+checksum 检测变更) │ ├── renderer.py # pandoc docx→HTML+MathJax 渲染管线 │ ├── backup.py # 本地备份 + WebDAV 远程推送 │ └── search.py # FTS5 全文搜索封装 ├── data/ # 运行时数据(不提交 Git) │ ├── problems/ # 单题 docx(.docx) │ ├── bank.db # SQLite 数据库 │ ├── papers/ # 导出试卷 │ ├── backups/ # 自动备份(保留 30 个) │ └── uploads/ # 上传临时文件 ├── src/ │ ├── 知识点树.md # 5层 Markdown 知识树(1811 叶) │ └── knowledge_map.json # 编码→路径映射(knowledge build 生成) ├── web/ # Flutter Web 构建产物(可选) ├── config.yaml # 配置文件 ├── install.sh # 安装脚本 ├── tiku-server # CLI 入口 └── requirements.txt # Python 依赖 ``` ## CLI 命令 | 命令 | 说明 | |------|------| | `./tiku-server serve` | 启动 FastAPI 服务 | | `./tiku-server serve --restart` | 启动服务(崩溃/git-update 后自动重启) | | `./tiku-server knowledge build` | 解析 `src/知识点树.md` 生成编码映射 | | `./tiku-server rebuild-index` | 手动增量扫描 problems/ 目录 | | `./tiku-server stats` | 终端显示题库统计 | | `./tiku-server backup` | 创建本地备份快照 | | `./tiku-server backup --remote` | 备份并推送到 WebDAV | | `./tiku-server token` | 显示 API Token | | `./tiku-server token regenerate` | 重新生成 Token | ## API 参考 ### 认证 所有需要认证的端点必须携带: - `Authorization: Bearer ` - `X-Timestamp: `(5 分钟内有效) - `X-Signature: HMAC-SHA256(token, METHOD+PATH+timestamp)`(hex 编码) | 方法 | 路径 | 认证 | 说明 | |------|------|------|------| | GET | `/api/health` | 否 | 健康检查 | | POST | `/api/auth/verify` | 否 | 验证 Token | ### 题库查询 | 方法 | 路径 | 参数 | 说明 | |------|------|------|------| | GET | `/api/stats` | — | 仪表盘统计(总数、按题型/类型/章节分布) | | GET | `/api/problems` | `page`, `page_size`, `chapter`, `question_type`, `import_type`, `annotated` | 题目列表(分页+筛选) | | GET | `/api/problems/{id}` | — | 题目详情(含渲染 HTML) | | GET | `/api/problems/{id}/render` | `include_answer` | 题目 HTML 渲染片段(JSON) | | GET | `/api/problems/{id}/view` | `token`, `include_answer` | 题目完整 HTML 页面(IFrame 用,Token 在 URL 中) | | GET | `/api/problems/next-unannotated` | `current_id`, `direction` | 连续标注导航 | | GET | `/api/search` | `q`, `page`, `page_size` | 全文搜索(FTS5 trigram) | ### 元数据编辑 | 方法 | 路径 | 说明 | |------|------|------| | PUT | `/api/problems/{id}/metadata` | 更新元数据(难度、题型、类型、评语) | | PUT | `/api/problems/{id}/knowledge` | 更新知识点编码 | ### 导入导出 | 方法 | 路径 | 说明 | |------|------|------| | POST | `/api/import` | 上传整卷 docx(multipart: `file` + `import_type`) | | GET | `/api/import/{task_id}/status` | 轮询导入进度 | | POST | `/api/export/chapter` | 按章节+类型导出(JSON body: `chapter`, `import_type`, `title`) | | POST | `/api/export/cart/{id}` | 按试题篮导出 | | GET | `/api/exports/{filename}` | 下载导出 docx 文件 | ### 知识点 | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/knowledge/tree` | 完整知识树(5层嵌套 JSON) | | GET | `/api/knowledge/search` | 搜索知识点(`q` 参数) | ### 试题篮 | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/carts` | 篮子列表(含题目数量) | | POST | `/api/carts` | 创建篮子(JSON: `name`) | | DELETE | `/api/carts/{id}` | 删除篮子 | | GET | `/api/carts/{id}` | 篮子详情(含题目列表) | | POST | `/api/carts/{id}/items` | 添加题目(JSON: `problem_id`) | | DELETE | `/api/carts/{id}/items/{pid}` | 移除题目 | ### 管理 | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/admin/backup-config` | 查看 WebDAV 配置 | | PUT | `/api/admin/backup-config` | 设置 WebDAV(JSON: `url`, `username`, `password`) | | POST | `/api/admin/backup/trigger` | 增量备份(bank.db + 变更的 docx) | | POST | `/api/admin/full-backup` | 全量备份(bank.db + 全部 docx) | | GET | `/api/admin/backup/history` | 备份历史 | | POST | `/api/admin/clear-all` | 清空题库(删除所有题目) | | POST | `/api/admin/rebuild-index` | 增量重建索引 | | POST | `/api/admin/knowledge-build` | 重建知识点编码 | | POST | `/api/admin/git-update` | `git pull` + 自动重启 | ## 备份策略 - **增量备份**:导入/编辑元数据时自动备份 `bank.db` + 被修改的 docx - **全量备份**:`POST /api/admin/full-backup`,备份全部 docx - **WebDAV**:配置后每次本地备份自动推送到远程(坚果云/NextCloud) - **保留 30 个快照**,自动清理旧备份 ## 导入队列 导入使用单线程队列处理,避免并发写入 SQLite 导致数据库损坏。`/api/health` 返回 `queue_depth` 可查看队列长度。 ## 单题 docx 格式 ``` [题目正文段落(含 OMML 公式、图片、选项)] 【答案】X ← 分割线 【分析】解题思路... (可选) 【详解】详细步骤... (可选) 【点睛】... (可选) ``` 自定义属性(`custom.xml`,Word 不可见): - `question_type`:单选题 / 多选题 / 填空题 / 解答题 - `difficulty`:0~1 浮点数,越大越容易 - `knowledge_codes`:JSON 数组,如 `["01.01.03.02.01"]` - `import_type`:精选题 / 经典题 / 高考题 - `comment`:评语 - `source`:来源文件名 ## 数据库结构 ```sql problems (id, file_path, mtime, checksum, chapters, difficulty, question_type, import_type, knowledge_codes, comment, source, ...) problems_fts (problem_id, body) — FTS5 trigram 全文索引 knowledge_points (code, path, leaf_name, chapter_code, chapter_name) carts (id, name, created_at) cart_items (cart_id, problem_id, added_at) — 联合主键 import_tasks (id, status, total, completed, message, result) ``` ## 域名转换 中文域名 `数学.top` 的 punycode 编码为 `xn--48s96u.top`,在 Git remote 等处使用: ``` ssh://git@xn--48s96u.top:10022/nonvioce/tiku.git ```