☰
面试会话状态和题目状态机,为什么需要分开看
2026/10/27 2:04:26 网站建设 项目流程

本文基于「码上面试平台」当前代码整理,重点是区分整场会话的生命周期和题目流程的游标状态。

一场面试,为什么会有两套状态?

阅读这条状态链路时,很容易把两个问题混在一起:题目 flow 已经COMPLETED,是否意味着会话记录也已FINISHED?或者会话可以恢复,是否意味着 Redis 里一定有当前题游标?

这类现象不一定意味着某个枚举写错了。先要确认代码里管理的是不是同一个对象。

项目里至少有两套状态:

状态对象表达什么主要字段
InterviewSessionStatus整场面试的生命周期会话是否在上传简历、可开始、进行中、结束或被放弃
InterviewFlowState当前题目流程走到哪里当前题号、题目索引、追问次数、流程阶段

会话主记录保存在 MongoDB 的interview_session集合。题目 flow 是面试运行态,通过缓存服务保存在 Redis。它们的存储位置和更新时机都不同。

会话状态回答“这场面试还能不能继续”

会话状态目前包含:

DRAFT -> RESUME_UPLOADING -> READY -> IN_PROGRESS -> FINISHED \______________________________________________-> ABANDONED

代码并不把它当作可任意改写的显示标签。InterviewSessionServiceImpl创建会话时设为DRAFT;上传简历前变为RESUME_UPLOADING;成功提题后写成READY;首次取题或答题后,READY才转为IN_PROGRESS;正式收尾会设置FINISHED和结束时间。

canResume()当前只把READY和IN_PROGRESS视为可继续状态。DRAFT、RESUME_UPLOADING属于活动状态的一部分,但不代表用户可以直接进入答题。创建新会话时,服务还会先把该用户已有的活动会话标记为ABANDONED。这是一项产品行为,意味着“再开一场”会影响之前尚未结束的会话,前端交互和恢复策略要和它保持一致。

Flow 状态回答“当前题应该发生什么”

题目 flow 有自己的状态:

INIT -> ASKING | COMPLETED ASKING -> EVALUATING | FOLLOW_UP | COMPLETED EVALUATING -> ASKING | FOLLOW_UP | COMPLETED FOLLOW_UP -> EVALUATING | ASKING | COMPLETED COMPLETED -> (terminal)

实际合法转换由InterviewFlowStateMachine的映射表约束。状态机负责检查转换是否合法,并委托题目缓存服务读写 Redis 状态、推进题号;当推进后的题号越界时,再将 flow 标记为COMPLETED。

InterviewFlowState除了 status,还保存currentIndex、currentQuestionNumber、totalQuestions和追问计数。追问题号可能是1-F1这样的形式,所以“下一题”不能简单理解成currentIndex + 1。追问仍然属于当前主问题的子流程。

两套状态在代码里何时更新?

简历提取成功后,InterviewSessionFacade把会话状态设为READY;提取服务则把题目放入缓存并初始化 flow。开始答题时,会话状态变成IN_PROGRESS,flow 仍然按当前题目的进度更新。

最后一道题答完后,答题流水线可以先把题目 flow 标记成COMPLETED并返回结束响应。正式收尾走另一条路径:InterviewRecordServiceImpl.saveInterviewRecordFromRedis在 finalize 锁下保存记录;如果会话还没结束,会把主记录改为FINISHED,然后再保存一次报告记录。

因此,看到 flow 是COMPLETED,只能说明题目流程走到了末尾;它本身不等于报告已经收口,也不等于 MongoDB 会话主记录已经更新为FINISHED。反过来,主记录状态也不能单独告诉我们 Redis flow 里当前题号和追问游标是什么。

阅读和排障时我会先确认四件事

  1. 先确认状态属于哪个对象。session.status和flow.status的同名值也不能混为一谈。
  2. 再确认是谁写状态。会话服务写 Mongo 主记录,状态机委托缓存服务推进 Redis flow,报告服务负责正式 finalize。
  3. 恢复时不要只看一个缓存 key。Redis flow 缺失时,代码还有从快照、答题轮次和题目材料重建的路径。
  4. 关注非法状态如何处理。InterviewFlowStatus.from遇到空值或未知字符串会回退到INIT。这提高了读取时的容错,但也可能掩盖存储数据异常;如果线上依赖这个回退,最好保证日志和恢复诊断能暴露原始值。

我的复盘:状态机应该描述事实,而不是页面文案

一开始我也容易把所有“进行中/已完成”收进一个 session status。读完调用链后,更清楚地看到这两个状态的职责不同:一个服务于会话管理和恢复入口,一个服务于题目推进和并发控制。

拆开之后,代码会多几处状态同步点。收益是每个状态能表达更准确的事实;代价是 finalize、恢复和异常补偿都要考虑两边是否暂时不一致。排障时与其问“为什么状态不对”,不如先问“哪个状态对象、在哪个步骤、由谁更新”。

源码入口

  • 会话状态枚举:admin/src/main/java/com/hewei/hzyjy/xunzhi/interview/service/model/InterviewSessionStatus.java
  • 会话状态更新:admin/src/main/java/com/hewei/hzyjy/xunzhi/interview/service/impl/InterviewSessionServiceImpl.java
  • 题目流程状态机:admin/src/main/java/com/hewei/hzyjy/xunzhi/interview/application/flow/InterviewFlowStateMachine.java
  • Redis flow 读写:admin/src/main/java/com/hewei/hzyjy/xunzhi/interview/service/impl/InterviewQuestionCacheServiceImpl.java
  • 正式结束与记录收口:admin/src/main/java/com/hewei/hzyjy/xunzhi/interview/flow/report/InterviewRecordServiceImpl.java

发布标签

JavaSpring Boot状态机RedisMongoDB源码分析

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询