从一个问题开始
去年有个朋友问我:“你做了一个 AI 聊天网站?”
我说不是。
他又问:“那你是接了个大模型的 API,套了个壳?”
我说也不是。
他第三次问的时候,我意识到一个问题——市面上几乎所有的”AI 项目”给人的印象就是 Chat 套壳。
但面面通不是。
准确说,它最开始是一个AI 模拟面试工具。但做着做着发现,面试完了要改简历,改完简历要准备论文——干脆把这几件事串成一个完整的产品。
于是它变成了:
- AI 模拟面试(多轮问答 + 5 维评分 + 编程考核)
- 简历优化(上传 → AI 分析 → 岗位定向改写 → Word 导出)
- 论文润色与降重(中英文润色 + AI 降重 + 查重改写)
- 文献知识库(上传文献 → 检索 → 引用标注 → 导出剥离)
- 智能题库 + 错题本 + 配额管理
不是聊天壳子,是一套真实 SaaS 产品的复杂度。
但老实说,“功能多”本身没什么稀奇。真正让我花时间写这一组文章的,是里面有几个很容易被低估的工程问题:
- AI 调用不能散在各个 Service 里,不然一加供应商、一加配额,七八个地方都要改。
- 面试不是普通聊天,AI 可能问问题、发笔试邀请、结束面试、生成评分,前端和后端都要接住这些状态。
- 论文引用不能靠 AI 自觉,必须有知识库边界、证据分类和引用标记。
- Word 导出不是替换文本,DOCX 里一堆 run、表格、文本框,稍微粗暴一点模板就崩。
如果画成地图,大概就是下面这四个坑位:

所以这篇先做总览,后面几篇再拆具体模块。
长什么样
先看整体架构:
用户(Web / 小程序) ↓Vue 3 前端(19 个页面,12 个共享 composables) ↓Spring Boot REST API(39 个 Service,7 大业务模块) ↓AI 网关层(多供应商路由 / 用户自有 Key / 配额) ↓MySQL + IndexedDB(服务端 / 客户端双层存储) ↓DeepSeek / 通义千问 / 自定义端点
功能上每个模块能做的事情:
| 模块 | 具体能力 |
|---|---|
| AI 模拟面试 | 多轮技术问答、5 维度评分、编程笔试环节、SSE 流式对话、面试报告 |
| 智能题库 | 分类浏览、随机刷题、即时判分、错题本自动收录 |
| 简历优化 | DOCX 解析、AI 简历评分、岗位定向优化、格式保留导出 Word |
| 论文润色 | 中英文可选、段落级润色、本地格式检查、差异对比 |
| 论文降重 | AI 降重 + 查重改写 + 交互式 Diff 预览 |
| 文献知识库 | PDF/DOCX 上传、语义搜索、证据分类、引用芯片渲染导出 |
| 用户配置 | 自定义 API Key、模型切换 Flash/Pro、每日配额、管理员后台 |
技术选型
选型上没有用太多花哨的东西,主打一个实用优先。
后端是 Spring Boot 3 + MyBatis-Plus + MySQL。原因很朴素:文档多、生态成熟、排坑成本低。一个人做项目时,技术新颖不一定是优点,出问题能不能快速查到答案才是。
前端是 Vue 3 + TypeScript + Vite,状态用 Pinia,路由用 Vue Router。论文知识库的数据放在浏览器本地,所以用了 Dexie 包 IndexedDB,再用 MiniSearch 做客户端全文检索。
流式输出靠 SSE。面试对话、论文润色、简历深度分析这些场景,都需要边生成边展示;等 AI 全部返回再刷新页面,体验会很硬。
还有几块比较特殊:
- pdf.js + mammoth:负责 PDF/DOCX 文本解析。
- CodeMirror6:负责面试里的在线编程编辑器。
- Piston:负责隔离执行代码题。
- 阿里云文档智能:辅助做简历字段识别。
这里面没有哪个技术是为了”显得高级”才选的。基本都是被需求推着走:要本地知识库,就得碰 IndexedDB;要流式体验,就得接 SSE;要保留 Word 格式,就得下到 OOXML 那一层。
项目的组织方式
后端:按业务模块分
39 个 Service 分布在 7 个包里,每个包各自内聚:
service/├── ai/gateway/ ← AI 网关(多供应商 + 适配器模式)├── interview/ ← 面试(流程 + prompt + 对话 + 解析)├── paper/ ← 论文(润色 + 降重 + 证据分类)├── resume/ ← 简历(分析 + 模板导出)├── document/ ← 文档(DOCX 格式保留导出)├── coding/ ← 编程(题目 + Piston 执行)├── exam/question/ ← 题库├── user/ ← 用户 + 配额└── auth/ ← 认证每个模块的 Service 内部还拆了多个职责组件。比如面试模块拆成了 4 个文件:
InterviewService → 主流程InterviewPromptBuilder → prompt 构建InterviewTranscriptManager → 对话管理InterviewReportParser → 报告 JSON 解析这样拆的好处是单个文件不会超过 400 行,每个类的职责一眼能看懂。
前端:composable 复用
前端 19 个页面共享了 12 个 composables:
// composables 目录useInterviewStream.ts // SSE 面试流处理useModelToggle.ts // 模型切换分段控制器usePaperKbPanel.ts // 知识库面板逻辑usePaperStore.ts // 论文页面状态usePaperExport.ts // 论文导出useCitationChips.ts // 引用芯片渲染useStreamPolish.ts // 流式润色useQuota.ts // 配额useResponsive.ts // 响应式useScrollReveal.ts // 滚动动画useWarnToast.ts // 警告提示useTabIndicator.ts // Tab 指示器useModelToggle 在三个页面(面试 / 简历报告 / 简历上传)被复用,usePaperKbPanel 在三个论文工具页面共享同一套知识库面板逻辑。一个 composable 里改了 bug,三个页面同时受益。
个人觉得这是前端架构里最值得借鉴的做法:不要急着写新页面,先看看已有的 composable 能不能复用。
开发时间线
整个项目从初始化到现在大概 158 个 commit,几个关键节点:
初期:搭建 Spring Boot + uni-app 骨架,做完基础的题库和面试对话
中期:加了完整的简历优化 + DOCX 导出,然后做了 Web 端 Vue 3 重构
后期:论文模块(润色 + 降重)和文献知识库引用系统
近期:AiGateway 网关层重构(7 个 Service 统一接入多模型),前端 composable 提取,管理员后台
整个过程中最花时间的不是写新功能,而是重构。面试服务拆成 4 个文件花了 3 天,AiGateway 网关层的迁移断断续续搞了一周。但做完之后扩展新功能明显快了——后面加论文润色服务时,只需要调 AiGateway 就好了,不用再写一遍 API 调用代码。
对我自己来说的价值
做这个项目最大的收获不是技术上的,而是产品闭环的体感:
- 面试 + 简历 + 论文 三个看起来不相关的场景,通过”用户求职/学术”这个主线串起来之后,自然产生了联动需求(简历分析完直接关联面试方向、论文润色时自动引用已上传文献)
- 单模块功能和完整产品链路之间的差距很大——搭好了一个 AI 面试,不代表用户就愿意用,还需要配额、历史记录、导出、权限控制这些东西
- 重构是必要的,但要有节奏。“用的时候再重构”比”先重构再上线”更安全
这个系列会写什么
这组文章不会按功能菜单一项项介绍,那样太像产品手册了。我更想记录几个开发时真正卡过我的点:
- AiGateway 网关层:7 个 Service 怎么统一接入多模型、用户 Key 和配额。
- AI 面试引擎:为什么我用 Prompt 当协议,而不是把面试流程全部写死在代码里。
- 论文知识库引用:怎么让 AI 只能引用用户上传过的文献,而不是张口编论文。
- DOCX 格式保留导出:AI 改写完简历后,怎么尽量不把 Word 模板弄崩。
面面通的完整代码在 GitHub 上。下一篇先讲 AiGateway:那次重构花了一周,但后面加新 AI 功能明显舒服多了。
部分信息可能已经过时





