独立开发一个 AI 模拟面试产品的完整复盘:从产品想法到技术选型,从踩坑到解决方案。React 19 + Spring Boot + DeepSeek 大模型的实战经验分享。

1 起因

        今年看到身边不少朋友在准备面试,大家最大的痛点不是"不知道考什么"——网上八股文资料一搜一大把——而是"面试的时候说不出来"

        我就想,能不能做一个产品,让用户跟 AI 模拟面试,像跟真人聊天一样?

        说干就干。前后花了大概两个月的业余时间,从 0 到 1 做了出来。

2 产品定位

        一句话:AI 模拟面试 + 题库 + 评估报告

        核心功能:

                - 用户选方向和难度 → AI 面试官逐题提问 → 用户对话式作答 → AI 实时打分和反馈 → 面试结束生成报告
                - 500 道面试题,覆盖 Java、前端、Python、Go、AI 五大方向
                - 题库页面支持 AI 解答+多轮追问

3 技术选型与理由

3.1 前端:React 19 + Vite 8 + Ant Design 6

        选 React 而不是 Vue,主要是个人更熟练。Ant Design 6 的组件库非常全,表单、表格、标签、抽屉、弹窗等开箱即用,一个人开发效率最重要。

        Vite 8 启动快、HMR 快,开发体验好。

        状态管理用 Zustand——比 Redux 轻量太多了。结合 `persist` 中间件,用户登录状态自动存 localStorage,刷新页面不丢失。

3.2 后端:Spring Boot 3.2 + JDK 17 + MyBatis-Plus

        稳,没别的理由。Spring Boot 生态成熟,出了问题搜一下基本都能解决。MyBatis-Plus 省了很多 CRUD 代码。

        认证选了 Sa-Token——比 Spring Security 轻量很多,配置也简单。Bearer Token 模式,7 天有效期,前端 Axios 拦截器自动带 token。

3.3 数据库:PostgreSQL + Redis

        PostgreSQL 的 JSONB 类型非常好用。面试会话里的"题目ID列表"和报告里的"评分维度"都用 JSONB 存储,不用建关联表,读写也方便。

        Redis 用来存面试进度(当前第几题),因为这个值变更频繁,放在 Redis 里比频繁 UPDATE 数据库好。

3.4 AI:DeepSeek

        选 DeepSeek 的原因:性价比高,中文能力强,API 兼容 OpenAI 格式,切换成本低。

4 踩坑记录

4.1 坑 1:SSE 流式传输的跨层转发

        最大的坑。

        我要实现的效果是 AI 回答逐字输出(像 ChatGPT),但架构是 前端 → Spring Boot → DeepSeek API,有两层网络。

        DeepSeek 返回的是 SSE 流,Spring Boot 要接收这个流,再实时转发给前端。

        一开始用 WebClient(Spring WebFlux),各种问题——Flux 和传统 MVC 混用容易出错。

        最后的方案是 OkHttp EventSource 接收 + Spring SseEmitter 转发。OkHttp 每收到一个 token,就通过 SseEmitter 推给前端。前端用 `fetch` 的 `ReadableStream` 接收,每收到一段就更新 React 状态。

        关键代码量其实不大,但调通这条链路花了不少时间。

4.2 坑 2:PostgreSQL 的 `user` 保留字

        建表的时候用了 `user` 做表名,结果 SQL 报错。因为 `user` 是 PostgreSQL 的保留字。(一开始做技术方案使用MySQL数据库,后面换成PostgreSQL)

        解决方案:表名加双引号 `"user"`。MyBatis-Plus 的 `@TableName` 注解需要写成:

java
@TableName("\"user\"")

注意这里是转义的双引号,不是单引号。

        【我这项目开发中,将user表改成了mb_user】

4.3 坑 3:JSONB 写入的类型问题

        往 PostgreSQL 的 JSONB 列写数据时报错:

column "question_ids" is of type jsonb but expression is of type character varying

        原因:JDBC 默认把 Java 的 String 当作 `varchar` 传给 PostgreSQL,但 JSONB 列需要的是 `jsonb` 类型。

        解决方案:在 JDBC URL 里加 `?stringtype=unspecified`,让 PostgreSQL 自己推断类型。

4.4 坑 4:面试切题的延迟

        最初的实现:用户回答 → AI 生成反馈(5-15秒)→ 前端拿到 [DONE] → 请求下一题 → 渲染。

        两次等待叠加,体验很差。

        优化方案:并行预取。后端在 AI 开始生成反馈之前就递增 Redis 索引,前端在 AI 流开始 200ms 后就预取下一题。等 AI 说完,下一题已经在内存里了,直接切换——零延迟。

4.5 坑 5:AI 评分提取

        AI 的回复是自由文本,怎么从里面提取分数?

        方案:在 System Prompt 里要求 AI 用固定格式嵌入评分——`【评分:X/10】`,后端用正则提取。

        但 AI 有时候会用全角冒号 `:`,有时候用半角冒号 `:`,甚至偶尔格式不完全一致。所以正则要写得宽松一点:

java
Pattern.compile("【评分[::]\\s*(\\d+)/10】")

        兼容全角和半角冒号,中间允许空格。

4.6 坑 6:报告生成时间不确定

        面试报告需要 AI 读完整个面试记录再生成结构化 JSON,这个过程要 10-30 秒。

        前端怎么处理?我用了轮询模式:每 3 秒请求一次报告接口,最多轮询 10 次。接口返回有数据就渲染,返回空就继续等。

        有人会说为什么不用 WebSocket?因为这只是一个"等一次结果"的场景,轮询实现最简单,也完全够用。工程中不需要每个地方都上最"高级"的方案。

5 数据库设计

        一共 7 张表,够精简:

作用
mb_user 用户账号、额度、VIP 状态
mb_question 500 道面试题
mb_interview_session 面试会话(方向、难度、题目ID列表)
mb_interview_message 面试对话消息(用户回答+AI反馈+评分)
mb_interview_report  AI 评估报告(维度评分、总结、建议)
mb_payment_plan 套餐定义
mb_payment_order 订单记录

        最得意的设计是 `question_ids` 用 JSONB 数组 `[42, 17, 88, 5, 63]` 存储,面试过程中不需要关联表,直接按索引取题目 ID,简单高效。

6 一些数据

        开发下来的统计:

                - 前端代码:约 15 个组件/页面
                - 后端代码:4 个模块(auth/interview/question/payment)
                - 数据库:7 张表,9 个索引
                - 题库:500 题,手动 + AI 辅助编写
                - AI 调用场景:面试提问反馈(流式)、报告生成(同步)、题库问答(流式)

7 总结

        一个人做全栈 AI 产品,最大的感受是:

                - 技术选型要务实:一个人开发,稳定和效率比"高级"重要。Spring Boot + React + Ant Design 这套组合虽然不够 fancy,但开发快、坑少。

                - AI 应用的核心不是 AI:调 API 很简单,真正花时间的是产品设计、交互体验和工程细节。SSE 流式传输、状态管理、异常处理这些才是大头。

                - 先做出来,再做好:一开始先把核心流程跑通(面试→回答→报告),再一点点优化(并行预取、Markdown 渲染、题库 AI 助手)。

        如果你也在做 AI 相关的项目,希望这篇踩坑记录能帮你少走一些弯路。

欢迎评论区交流,有技术问题也可以随时问。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐