From c2c555d5ac602d7ea08fe3e8116c602333994798 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Mon, 15 Jun 2026 02:10:23 -0600 Subject: [PATCH] docs: add design spec for Bun + SQLite backend with book management and AI generation Co-Authored-By: Claude Sonnet 4.6 --- .../2026-06-15-bun-sqlite-backend-design.md | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-15-bun-sqlite-backend-design.md diff --git a/docs/superpowers/specs/2026-06-15-bun-sqlite-backend-design.md b/docs/superpowers/specs/2026-06-15-bun-sqlite-backend-design.md new file mode 100644 index 0000000..5a54d66 --- /dev/null +++ b/docs/superpowers/specs/2026-06-15-bun-sqlite-backend-design.md @@ -0,0 +1,175 @@ +# 教学设计生成器后端(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 序列化的 TeachingBook(schemaVersion、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": "" }` 及合适的 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` 由主题派生(例如 `.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 Key(500)。 + +### 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 与前端静态资源。