从对话界面到知识库检索,再到桌面端打包,一个本地 AI 平台看似只是把若干成熟组件组合起来,真正落地时却会遇到架构边界、原生模块兼容、密钥存储和流式中断等一连串工程问题。下面结合一次完整开发经历,梳理这些问题的处理方式及其背后的取舍。
一个人、一台机器、一套代码,走完产品全流程的复盘。
一个私人 AI 助手:对话、知识库 RAG、自定义助手人设,Web 模式和桌面端(Electron)双交付。技术栈是 Next.js 14 全栈 + Electron + SQLite + pnpm Monorepo。
从零到能打包交付,踩了不少坑,也沉淀了一些方法论。
Monorepo + 单向依赖 + 服务层,让改动有边界。改数据库不影响前端,换供应商不影响 UI。没有分层,一个人维护全栈就是噩梦。
Electron + better-sqlite3,差点让我放弃桌面端。最终方案是 fork 真实 Node 子进程,原生模块跑在和编译时一致的运行时里。遇到 dlopen 报错,先查 NODE_MODULE_VERSION。
API Key 明文落库 = 把钥匙放在门口。加密落盘 + 永不返回明文 + 本地服务令牌守卫,三层防护缺一不可。用户把 Key 交给你,你要对得起这份信任。
用户点停止,已生成的内容不能丢。abort 后写回已生成内容、消息置 stopped,是体验细节,也是工程素养。
几百篇文档的知识库,SQLite + sqlite-vec 完全够。归一化向量后 L2 等价余弦,一个文件搞定。不要为了「技术先进」引入运维负担。
{ success, data } / { success, error: { code, message } } + 领域错误码,前端零猜测。前后端类型同源(Zod schema),改一处全链路生效。
慢、贵、不稳定。100% mock 外部调用,测试验证的是你的代码逻辑,不是模型质量。依赖注入让 mock 变得简单。
超长文件 = 认知过载 = 改不动。设个数字红线,用脚本强制,逼自己拆。维护成本在写的时候就付了,后面都是收益。
没人评审,CI 就是 reviewer。typecheck + lint + 行数 + 覆盖率,全绿才提交。不是为了好看,是为了改代码不害怕。
架构文档、接口文档、模块设计,边写代码边更新。代码会忘,文档不会。三个月后回来,文档是你和过去自己的对话。
| 坑 | 症状 | 解法 |
|---|---|---|
| Electron fork 子进程 ABI 不匹配 | 窗口 30s 不出现 | 打包内置真实 Node,fork 指定 execPath |
| fork 无 error | 子进程崩溃无日志 | 加 error/exit ,转发 stdout/stderr |
| Smart App Control 拦截未签名 exe | 打包产物无法启动 | 用官方签名 electron.exe 作为宿主加载 asar |
| SSE 半包解析失败 | 流式内容错乱 | 逐字符状态机 parser,不依赖完整行 |
| 密钥明文出参 | 安全漏洞 | 仓储层分脱敏视图和含密文行,服务层才可取密文 |
| 数据库写入非单一事务 | 崩溃后数据不一致 | 四步写库包进一个 db.transaction |
| 未子进程端口回收 | 重启端口被占 | will-quit 先 SIGTERM 再 SIGKILL |
一个人做全栈产品,最大的挑战不是技术,是取舍:
不要追求完美,追求能用、好维护、能迭代。
这个项目还能继续:
但核心闭环已经跑通:配置供应商 → 创建助手 → 对话 → 知识库 RAG → 桌面打包。
从 0 到 1 做一个产品,技术只是其中一部分。架构决策、工程规范、测试体系、文档习惯,这些「非功能」的东西,决定了项目能不能活下去、能不能长大。
希望这个系列能给你一些启发。如果你也在做自己的 AI 应用,记住:先让它跑起来,再让它跑得好。