Ai面试项目:voice interview模块

interview-guide项目 voice interview模块的设计与接口实现

VoiceInterview 语音面试模块设计与实现

这篇笔记记录我在 interview-guide 项目中对 VoiceInterview 模块的实现。核心目标是让语音面试具备“实时交互 + 可恢复会话 + 可追踪评估”的完整体验。

模块能力概览

  • 实时语音对话:基于 WebSocket + 千问3语音模型(ASR/TTS/LLM 统一 API Key)。
  • 流式体验优化:句子级并发 TTS,边生成边合成边播放,首包延迟约 200ms。
  • 服务端 VAD:自动断句,提供实时字幕(含中间结果)。
  • 回声防护:支持手动提交机制,避免 AI 播报被误录入。
  • 会话连续性:支持暂停/恢复与多轮上下文记忆,超时可自动暂停。
  • 监控埋点:通过 Micrometer 采集 TTS/ASR 延迟、会话时长等指标。

状态转换

关键接口设计

POST /api/voice-interview/sessions 创建语音面试会话

Controller 入口:

VoiceInterviewController.createSession(@Valid @RequestBody CreateSessionRequest request)

核心调用链:

voiceInterviewService.createSession(request);

实现要点:

  • 兜底 skillId(未传则使用默认技能)。
  • 兜底 llmProvider(空值走默认 provider)。
  • 组装 VoiceInterviewSessionEntity(阶段开关、难度、简历 ID、JD 文本、计划时长等)。
  • 默认 userId = "default"
  • 设置初始阶段(intro/tech/project/hr 中第一个启用阶段)。
  • 持久化到数据库 voice_interview_sessions,并写入 Redis(带 TTL)。
  • 返回 SessionResponseDTO(会话 ID、状态、阶段、配置等)。

GET /api/voice-interview/sessions/{sessionId} 根据会话 ID 获取详情

Controller 调用:

voiceInterviewService.getSessionDTO(sessionId);

实现要点:

  • 先查 Redis 缓存,未命中再查数据库。
  • 查到后组装 SessionResponseDTO
  • 查不到返回统一错误:Session not found: {sessionId}

POST /api/voice-interview/sessions/{sessionId}/end 结束会话并触发异步评估

Controller 调用:

voiceInterviewService.endSession(sessionId.toString());

结束与评估逻辑:

session.setEndTime(now);
session.setCurrentPhase(COMPLETED);
session.setStatus(COMPLETED);
session.setEvaluateStatus(PENDING);
sessionRepository.save(session);
voiceEvaluateStreamProducer.sendEvaluateTask(sessionId);
redisService.streamAdd(streamKey(), buildMessage(payload), AsyncTaskStreamConstants.STREAM_MAX_LEN);

说明:

  • 接口立即返回 Result.success(),不阻塞等待评估结果。
  • 前端通过 GET /api/voice-interview/sessions/{sessionId}/evaluation 轮询评估状态。

PUT /api/voice-interview/sessions/{sessionId}/pause 暂停会话

核心调用:

voiceInterviewService.pauseSession(sessionId.toString(), reason);

实现要点:

  • IN_PROGRESS 状态允许暂停。
  • 更新状态为 PAUSED,记录原因并刷新 updatedAt
  • 同步持久化数据库与 Redis 缓存。

PUT /api/voice-interview/sessions/{sessionId}/resume 恢复会话

核心调用:

voiceInterviewService.resumeSession(sessionId.toString());

实现要点:

  • PAUSED 状态允许恢复。
  • 恢复后状态改为 IN_PROGRESS,不重置题目进度与阶段。
  • 保存数据库并同步 Redis,返回最新 SessionResponseDTO

GET /api/voice-interview/sessions 获取会话列表(按 userId/status 可过滤)

调用链:

voiceInterviewService.getAllSessions(userId, status);
sessionRepository.findByUserIdAndStatusOrderByUpdatedAtDesc(userId, statusEnum);

返回:

  • Result<List<SessionMetaDTO>>

DELETE /api/voice-interview/sessions/{sessionId} 删除语音面试会话

调用链:

voiceInterviewService.deleteSession(sessionId);

实现要点:

  • 校验会话存在性。
  • 删除会话与关联数据(消息/评估等)。
  • 清理 Redis 缓存。

GET /api/voice-interview/sessions/{sessionId}/messages 获取对话历史

调用链:

voiceInterviewService.getConversationHistoryDTO(sessionId);

返回:

  • Result<List<VoiceInterviewMessageDTO>>

GET /api/voice-interview/sessions/{sessionId}/evaluation 获取异步评估状态与结果

实现要点:

  • 先校验会话存在(不存在抛 VOICE_SESSION_NOT_FOUND)。
  • 读取 evaluateStatusevaluateError
  • 若状态为 COMPLETED,再读取评估详情:
evaluationService.getEvaluation(sessionId);
  • 返回 VoiceEvaluationStatusDTO(包含状态,完成时附评估结果)。

POST /api/voice-interview/sessions/{sessionId}/evaluation 手动触发异步评估

处理逻辑:

voiceInterviewService.getSession(sessionId);
evaluationService.getEvaluation(sessionId);
voiceInterviewService.triggerEvaluation(sessionId);

规则:

  • 若已 COMPLETED:直接返回已有评估结果。
  • 若为 PENDING/PROCESSING:返回当前状态,不重复触发。
  • 其他可触发状态:入队评估任务,立即返回 PENDING,前端继续轮询。

小结

VoiceInterview 模块的关键不是“把语音跑通”,而是把实时链路和会话生命周期稳定地串起来。对我来说,只有在创建、暂停、恢复、结束、评估这一整条链都能可靠协同时,语音面试才是真正可持续迭代的产品能力。