← 返回项目列表
核心项目
学习记录(慢慢学习 OS)
为管理自己的学习而做的全栈应用 — 产品思维 × AI 集成 × 前端审美
Vue 3TypeScriptExpressSupabaseOpenAIThree.js
核心指标
14
功能页面
50+
API 端点
11
核心数据表
MVP4
当前迭代
功能导览
← 拖动滚动查看全部截图 →
技术架构
前端 Vue 3 Composition API + TypeScript + Vue Router + Vite,后端 Express.js (Node.js),数据库 Supabase PostgreSQL 和本地 JSON 文件双模式,AI 服务单独调用 OpenAI API(支持 responses 和 chat 两种模式,含本地兜底)。
前端层
- •Vue 3 + TypeScript + Vite
- •14 个视图组件 · 全局 AppShell + TopNav
- •api.ts 缓存层 (去重 / 后台刷新 / 预取)
- •prefetch.ts 路由预取 · Three.js 智慧树 3D 场景
后端层
- •Express.js · 中间件链: logRequest → slowRequestAlert → json → static
- •controller → service → repository 三层架构
- •aiService.js: OpenAI 集成 + 本地兜底 + 30min 缓存
- •baseRepository.js: Supabase REST API + 本地 JSON 回退
- •cacheService.js: 内存 Map + 标签索引 · 幂等性 / 上传 / 保存缓存
数据层
- •Supabase PostgreSQL (11 张表 · 外键约束 · CHECK 约束 · 30+ 索引)
- •⇄ 自动回退: data/mvp-db.json (5min 内存缓存)
- •JWT 认证 · bcryptjs 密码哈希 · user_id 全表隔离
核心亮点
故事 1:从 JSONB 到结构化表 — 踩了数据一致性的坑
现象:项目早期把用户的所有状态(知识点、错题、计划、复习)全部塞进一个 payload jsonb 字段。页面越多,写入冲突越频繁——A 操作改了知识库,B 操作改了计划,后写入的覆盖前写入的,数据悄悄丢失。
排查过程:我注意到用户反馈「刚存的知识点不见了」,反复重现后发现是并发写入同一个 jsonb 字段导致的竞态覆盖。排查 Supabase 日志确认了时间窗口:两个 PATCH 请求间隔不到 200ms,第二个请求拿到的还是旧数据。
根因:单表单字段承载了太多职责。jsonb 聚合模式在小数据量下看不出问题,一旦操作变多,缺乏行级锁和字段级隔离,并发写入必然冲突。
解决方案:全面重构为结构化业务表:learning_inputs、knowledge_nodes、mistakes、study_plans、plan_tasks、review_schedules 等 11 张独立表。每条数据独立一行,用 user_id 做全表隔离,外键关联保持引用完整性。旧系统数据不迁移,干净利落地切。
故事 2:AI 不可靠但系统不能崩 — 全线兜底设计
现象:OpenAI API 时不时超时(45s 限制)、返回非 JSON 格式、或者干脆连不上。用户上传了内容点了「AI 分析」,等了半分钟报错,体验极差。
排查过程:梳理了所有 AI 调用路径:内容丰富 (enrich)、学习建议 (suggest)、知识问答 (chat)、文件识别 (recognize)。每条路径都需要独立处理超时、非法 JSON、网络不可达三种异常。
根因:对外部 API 的依赖没有降级方案。在「AI 不可用」和「系统不可用」之间缺少隔离层。
解决方案:在每个 AI 服务函数里内置 fallback 结果:enrich → 本地分词拆要点 + 默认学习建议;suggest → 统计错题数量、列出最近知识点,生成文本建议;chat → 返回「AI 暂不可用」提示;recognize → 内置 .docx ZIP 解析器(node:zlib),PDF/图片未实现时提示手动补文本。还加了 30 分钟结果缓存和幂等性去重,避免重复扣费。
故事 3:前端缓存层的进化 — 从 N+1 请求到智能去重
现象:页面切换时,同一个 API(比如 /api/knowledge/nodes)被多个组件重复请求。首页加载时需要串行请求看板、计划、复习三个接口,慢的时候要等 3 秒以上。
排查过程:用 Chrome DevTools Network 面板发现大量冗余 GET 请求。同一个 URL 在 2 秒内被请求了 4 次——组件挂载、路由切换、导航栏预取、用户操作各触发一次。
根因:没有前端缓存层,也没有请求去重。每个组件独立发请求,互不知道对方已经拿到了数据。
解决方案:自建了 api.ts 缓存层:内存缓存(Map 存储,按 endpoint 区分 TTL)、请求去重(inFlight Map,同一 key 的并发请求只发一次)、后台刷新(缓存命中时静默发起新请求更新缓存,stale-while-revalidate)、路由预取(prefetch.ts 利用 requestIdleCallback 在空闲时预取下一个路由的组件和 API)。最终首页加载从 3s+ 降到 300ms 以内(缓存命中时)。
故事 4:双模式存储的自动回退 — Supabase 挂了也不怕
现象:Supabase 偶尔响应慢(被慢请求告警抓到过 /api/focus/summary 耗时 3.2s),或者网络抖动时连接失败。早期版本直接抛错,用户刷不出数据。
排查过程:在 baseRepository.js 的每个 CRUD 方法里加了 try-catch。Supabase 请求失败时记录 warn 日志,自动切换到本地 JSON 文件。同时加了 5 分钟的内存缓存避免重复读盘。
根因:单点依赖外部服务,没有本地容灾方案。
解决方案:实现了 Supabase-first + 本地回退策略:读写优先走 Supabase(PostgREST);失败自动回退到 data/mvp-db.json;本地 DB 有 5 分钟内存缓存减少磁盘 IO;云端列表查询有差异化 TTL 缓存(知识点 60s、复习 30s);通过 DISABLE_SUPABASE=true 环境变量可以完全切到纯本地模式。系统在「无 Supabase + 无 OpenAI」的极简环境下也能完整运行。
故事 5:BOM 字符引发的隐形 Bug
现象:Windows 上用 VS Code 编辑 server.js 和 learningService.js 后,服务器启动报语法错误——第一行的 import 语句不识别。
排查过程:肉眼看不到任何问题。用 hex editor 查看文件头部,发现 EF BB BF(UTF-8 BOM)。Node.js 的 ES Module 解析器把 BOM 当作代码的一部分,导致 import 关键字被破坏。
根因:Windows 的某些编辑器默认在 UTF-8 文件前加 BOM 标记。ES Module 对 BOM 的处理比 CommonJS 更严格。
解决方案:全局替换为 UTF-8 without BOM 编码。在 README 里加了语法检查命令 npm run check,用 scripts/check-syntax.js 自动扫描所有 .js/.ts/.vue 文件的 import/export 语法。后续在 Git 上配置了 .gitattributes 强制 LF 换行。
数据模型
11 张核心业务表,支持多态流转(素材五态、错题三态、计划多状态)。通过 Supabase PostgreSQL 实现关系建模,配合 JSONB 字段存储灵活结构。
| 数据表 | 核心字段 | 说明 |
|---|---|---|
| learning_inputs | input_type, title, raw_text, status | 上传素材,五态流转 |
| knowledge_nodes | title, summary, tags, privacy, quality_status | 树形知识结构,公开/私密切换 |
| mistakes | question, answer, reason, solution, check_points, status | 错题三态:open→reviewing→mastered |
| study_plans | date_key, title, status | 按日期组织学习计划 |
| plan_tasks | title, item_type, status, due_date | 计划任务,支持打卡 |
| review_schedules | prompt, status, due_date, interval_days | 间隔重复调度 |
| focus_sessions | target_minutes, actual_seconds, earned_coins | 专注会话 + 金币结算 |
| coin_transactions | amount, transaction_type, reason | 金币收支流水 |
| shop_items | item_key, title, item_type, price | 杂货铺商品 |
设计特色
•独立设计规范 design.md,明确反对千篇一律的 SaaS 模板风格
•配色禁用紫色/靛蓝/蓝紫渐变,强制暖色调(棕色、米色、绿色、暗红)
•布局拒绝 Hero + 三卡片,必须不对称、不对齐
•文案口语化、有温度,每句不超过 15 字 — 如「今天别装忙」「长歪了也行」
•背景 SVG 噪点纹理,卡片微微旋转(-0.7deg / 0.45deg),按钮偏移阴影
•全站使用 Iconify (Phosphor 图标集),禁用 Emoji 做功能图标
个人角色
职责:独立负责从需求分析、架构设计、前后端开发、测试联调到部署上线的完整流程。
技术决策:技术栈选型、数据库 Schema 设计、API 规范制定、代码 Review 标准。
团队协作:产品需求来自真实使用场景,持续迭代优化用户体验。