Files
teaching-design/docs/superpowers/specs/2026-06-15-bun-sqlite-backend-design.md

176 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 教学设计生成器后端Bun + SQLite设计方案
## 1. 建设目标
在现有 Vue 3 + TypeScript + Vite 教学设计生成器基础上,新增一个 Bun 后端服务,使用 SQLite 持久化保存多本「教学设计整本」(课程名称、教师姓名、全部教案及顺序)。用户可以创建、打开、重命名、删除多本整本,并在编辑时自动保存到服务器。同时新增「输入主题生成教案」功能,调用 Deepseek API 生成符合现有模板结构的 Markdown 教案,解析后作为新课时加入当前整本。
前端原有的解析、编辑、打印、ZIP 导出逻辑保持不变,仅将持久化层从浏览器 `localStorage` 切换为服务器 API。
## 2. 现状与变更范围
当前系统(见 [2026-06-15-printable-teaching-design-generator-design.md](2026-06-15-printable-teaching-design-generator-design.md))是纯前端应用:
- `useTeachingBook` 管理单一 `TeachingBook`(封面 + 教案数组 + 选中页 + 排序)。
- `bookStorage.ts` 将该 `TeachingBook` 序列化存入 `localStorage`,刷新后通过 `RestoreDraftDialog` 提示恢复。
- 上传、解析、编辑、打印、导出 ZIP 均在浏览器本地完成。
本次变更:
- 新增 Bun + Hono + `bun:sqlite` 后端,提供整本的增删改查 API 和 AI 生成 API。
- 移除 `bookStorage.ts``RestoreDraftDialog.vue` 及相关 localStorage 逻辑。
- 新增整本列表页作为应用入口,新增「生成教案」对话框。
- `useTeachingBook` 改为基于 `bookId` 从服务器加载/保存数据。
- 教案的解析(`markdownParser`)、生成(`markdownWriter`)、打印(`PrintBook`、ZIP 导出(`zipExporter`等服务保持不变AI 生成的 Markdown 同样通过 `parseTeachingDesign` 解析,复用既有容错与警告机制。
`data/` 目录下的语料教案由用户通过现有上传功能手动导入到某个整本中,服务器不直接读取或预置 `data/` 内容。
## 3. 核心产品决策
- 不引入用户账号体系,所有人访问同一个后端,看到同一份整本列表。
- 整本以「列表 -> 选择/新建 -> 工作区」的方式使用:进入应用先看到整本列表,选择或新建后进入现有工作区,编辑期间自动保存到该整本。
- 删除整本是显式操作(列表页确认),与工作区内的「清空当前整本内容」(清空教案列表但保留整本记录)是两个不同操作。
- 「生成教案」是工作区工具栏的一个按钮:输入主题 -> 调用 Deepseek -> 解析为新教案 -> 加入当前整本课次列表末尾,用户可继续手动编辑。
- 生成失败、保存失败、列表加载失败均以非破坏性提示展示,不清除已有的整本内容或编辑状态。
## 4. 技术方案
技术栈:
- 前端Vue 3、TypeScript、Vite不变
- 后端Bun 运行时 + Hono 路由框架 + `bun:sqlite`
- AI 生成Deepseek Chat Completion API密钥通过服务器端环境变量 `DEEPSEEK_API_KEY``.env`,不提交到仓库)配置
- 数据传输JSON over HTTP前端通过 `fetch` 调用 `/api/*`
开发环境Vite dev server 通过代理将 `/api` 转发到本地 Bun 服务(端口 3001。生产环境`bun run server/index.ts` 同时提供 API 与 `vite build` 产出的静态文件。
## 5. 数据库设计
SQLite 数据库文件位于 `data/teaching-books.db`(加入 `.gitignore`)。
```sql
CREATE TABLE IF NOT EXISTS books (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
data TEXT NOT NULL, -- JSON 序列化的 TeachingBookschemaVersion、cover、designs、selectedId、updatedAt
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
```
- `data` 字段直接复用前端 `TeachingBook` 的 JSON 结构,不做字段级拆表,避免引入与前端模型重复维护的风险。
- `name` 是整本在列表页展示的名称,与 `TeachingBook.cover.courseName` 独立(前者用于管理,后者用于打印封面)。
- `updated_at` 在每次 `PUT /api/books/:id` 时更新,用于列表页排序和展示。
## 6. 后端 API
所有响应为 JSON出错时返回 `{ "error": "<message>" }` 及合适的 4xx/5xx 状态码。
| 方法 | 路径 | 请求体 | 响应 | 说明 |
|---|---|---|---|---|
| GET | `/api/books` | - | `{ id, name, updatedAt, lessonCount }[]` | 按 `updated_at` 倒序列出整本 |
| POST | `/api/books` | `{ name }` | `{ id, name, updatedAt, data: TeachingBook }` | 创建整本,`data``createEmptyBook()` |
| GET | `/api/books/:id` | - | `{ id, name, updatedAt, data: TeachingBook }` | 加载整本;不存在返回 404 |
| PUT | `/api/books/:id` | `{ data: TeachingBook }` | `{ id, name, updatedAt }` | 覆盖保存 `data`,更新 `updated_at` |
| PATCH | `/api/books/:id` | `{ name }` | `{ id, name, updatedAt }` | 重命名整本 |
| DELETE | `/api/books/:id` | - | `{ ok: true }` | 删除整本 |
| POST | `/api/generate` | `{ topic }` | `{ filename, markdown }` | 调用 Deepseek 生成教案 Markdown |
`POST /api/generate` 实现要点:
- 服务器构造提示词,要求 Deepseek 按照现有教案模板结构(一级标题、基本信息表、`## 教学过程` 五列表、`## 板书设计``## 教学成效与反思`)输出 Markdown主题信息来自请求中的 `topic`
- `filename` 由主题派生(例如 `<topic>.md`),用于 `parseTeachingDesign(filename, markdown)` 和后续 ZIP 导出时的文件名与现有课时文件名重复时沿用现有「keep」策略由前端导入逻辑处理。
- Deepseek 请求失败(网络错误、超时、鉴权失败、限流)统一返回 `502``error` 信息可展示给用户。
## 7. 前端变更
### 7.1 `src/services/booksApi.ts`(新增)
封装上述 API 的 `fetch` 调用,返回类型与后端响应一致,统一抛出带 `message` 的错误供调用方捕获。
### 7.2 `src/components/BookListPage.vue`(新增)
应用入口页面:
- 加载并展示整本列表(名称、更新时间、课时数)。
- 「新建整本」:输入名称 -> `POST /api/books` -> 直接进入该整本的工作区。
- 每行提供「打开」「重命名」「删除」(删除需二次确认)。
- 列表加载失败时显示错误提示和重试按钮。
### 7.3 `useTeachingBook` 重写
- 接收 `bookId`,初始化时 `GET /api/books/:id` 加载 `TeachingBook`
- 响应式变化后按 300ms 防抖调用 `PUT /api/books/:id` 保存整本 `data``saveStatus` 含义不变(`idle | saving | saved | error`)。
- 移除 `restore``pendingDuplicateFiles` 中与本地草稿恢复相关的逻辑;导入重名提示对话框(`ImportConflictDialog`)保留。
- `clearBook()` 语义不变:清空当前整本的 `designs`,仍保存到同一个整本记录。
- 新增 `generateLesson(topic)`:调用 `POST /api/generate`,成功后 `parseTeachingDesign` 解析并 `push``designs`,选中新教案;失败时设置错误提示,不影响现有数据。
### 7.4 `src/components/GenerateLessonDialog.vue`(新增)
- 输入主题文本框 + 确认/取消。
- 提交后显示加载状态Deepseek 调用失败在对话框内显示错误并允许重试,不关闭对话框。
### 7.5 `WorkspaceToolbar.vue`
- 新增「生成教案」按钮,打开 `GenerateLessonDialog`
- 新增「返回列表」按钮,回到 `BookListPage`(不删除当前整本,依赖已有自动保存)。
### 7.6 `App.vue`
- 用本地 `ref``BookListPage` 与现有工作区(`WorkspaceToolbar` + `LessonSidebar` + `A4Workspace` + `PrintBook`)之间切换,不引入路由库。
- 进入工作区时把选中的 `bookId` 传给 `useTeachingBook`
### 7.7 移除内容
- `src/services/bookStorage.ts``bookStorage.test.ts`
- `src/components/RestoreDraftDialog.vue`
- `useTeachingBook` 中与 localStorage 相关的逻辑及对应测试用例(替换为针对 `booksApi` 的 mock 测试)
## 8. 开发与构建流程
- `vite.config.ts` 增加开发代理:`/api` -> `http://localhost:3001`
- `package.json` 新增依赖 `hono`,新增脚本:
- `"server": "bun run server/index.ts"`(生产/手动启动)
- `"server:dev": "bun --watch run server/index.ts"`(开发时热重载)
- `"test:server": "bun test server"`
- 开发时需要同时运行 `npm run dev`Vite`npm run server:dev`Bun API两者为独立进程。
- 生产构建:`vite build` 产出 `dist/``server/index.ts` 中 Hono 应用对未匹配 `/api/*` 的请求回退为静态文件服务(`dist/`),单进程 `bun run server/index.ts` 即可部署。
- `DEEPSEEK_API_KEY` 通过项目根目录 `.env`Bun 自动加载,加入 `.gitignore`)配置;缺失时 `/api/generate` 返回 `500` 并提示未配置。
## 9. 错误处理与状态反馈
- 后端:所有路由捕获异常,返回 `{ error }` 及对应状态码400 参数错误、404 整本不存在、500 数据库/配置错误、502 Deepseek 调用失败)。
- 前端列表页:加载/创建/重命名/删除失败时显示错误提示,不影响已渲染的列表。
- 前端工作区:
- 加载整本失败404/网络错误)显示错误并提供返回列表入口。
- 自动保存失败时 `saveStatus = 'error'`,工具栏显示提示,内存中的编辑内容保留,下次变更会重试保存。
- 生成教案失败在对话框内提示,不影响已有课次。
## 10. 测试策略
### 10.1 前端Vitest不变的运行方式
- `useTeachingBook.test.ts`mock `booksApi`,覆盖加载、自动保存防抖、`generateLesson` 成功/失败、`clearBook`
- `BookListPage.test.ts`(新增):覆盖列表渲染、创建、重命名、删除、错误提示。
- `GenerateLessonDialog.test.ts`(新增):覆盖提交、加载状态、错误显示与重试。
- `WorkspaceToolbar.test.ts`:补充「生成教案」「返回列表」按钮的事件触发断言。
- 既有解析器、写出器、ZIP 导出、打印组件测试不变。
### 10.2 后端(`bun:test`
- `server/db.test.ts`:使用 `:memory:` SQLite验证表初始化与基本读写。
- `server/routes/books.test.ts`:覆盖列表、创建、获取(含 404、保存、重命名、删除的完整 CRUD 行为。
- `server/routes/generate.test.ts`mock `fetch` 模拟 Deepseek 响应覆盖成功解析、Deepseek 错误502、缺失 API Key500
### 10.3 手动验证
- 启动 `server:dev``dev`,在浏览器中:创建整本 -> 上传 `data/Web` 教案 -> 编辑并确认自动保存(刷新后内容仍存在)-> 使用「生成教案」输入主题并检查生成的课次结构 -> 返回列表确认整本出现并显示正确的更新时间和课时数 -> 删除整本并确认从列表移除。
## 11. 验收标准
- 应用启动后先显示整本列表,可创建、打开、重命名、删除整本。
- 进入某个整本后原有上传、编辑、拖拽排序、打印、ZIP 导出功能行为不变。
- 编辑内容在 300ms 防抖后通过 API 保存到 SQLite刷新页面后从服务器恢复不再依赖 `localStorage`
- 工具栏「生成教案」可输入主题,调用 Deepseek 生成 Markdown 并作为新课时加入当前整本,解析结果应用既有警告机制。
- 后端、前端的新增/修改测试均通过(`npm test``bun test server`)。
- 生产构建后单一 `bun run server/index.ts` 进程即可同时提供 API 与前端静态资源。