主要技术:Spring Boot、Spring AI、PostgreSQL、Redis
项目目录
项目简介
InterviewGuide 是开源 AI 面试辅助平台,围绕简历分析、模拟面试和知识库问答组织求职练习。用户上传简历后,可以查看评分、优势与修改建议;进入模拟面试后记录主问题、追问和作答,再查看逐题评价与报告。平台还支持把自己的知识资料整理成题库,用已启用题目开展专项练习。
后端基于 Java、Spring Boot 与 Spring AI,前端使用 React、TypeScript。PostgreSQL 保存简历、题库和面试记录,pgvector 提供知识检索,Redis Streams 承接分析、出题和评估等耗时任务,把上传、等待与报告回看连接起来。
项目亮点
- 队列与数据库共同跟踪分析任务。 简历分析使用消费组领取消息,先补领长期未确认消息,再读取新消息;数据库记录任务状态,定时巡检卡住的任务并有限补投,上传请求无需等待模型完成。
- 旧消费者的结果有写入校验。 简历任务以状态和随机
attemptId领取执行权,阶段心跳检查当前代次;保存分析结果时再在事务内锁行复核,阻止任务重新领取后旧消费者迟到覆盖。 - 模型结果按统一结构交接。 简历评分和建议由
Java record、BeanOutputConverter定义字段,统一调用器提供Schema校验与有限重试,供报告展示和保存复用。 - 知识库出题与正式练习分开。 根据检索片段生成带答案、关键点、评分标准和追问的草稿;练习只从已启用题目中抽取,先核对主问题与追问容量,再保留父子问题关系组卷,交卷后复用统一评估服务。
技术架构
frontend/ 提供简历、知识库和面试界面,app/ 中的业务模块处理上传、任务提交、作答与报告读取。资料解析和知识检索为模型调用准备正文或参考片段;公共结构化调用器把模型输出转换为可交给业务模块保存的对象。
耗时处理通过 Redis Streams 交给对应消费者,结果落入 PostgreSQL 后由页面查询。简历分析另有执行代次和超时恢复;知识库出题则在保存前核对任务标识,再将生成草稿交给题库维护,启用后进入抽题与面试评估。
| 代码位置 | 职责 |
|---|---|
frontend/、app/src/main/java/interview/guide/modules/ |
前端交互与简历、知识库、面试业务模块 |
app/src/main/java/interview/guide/common/async/、app/src/main/java/interview/guide/infrastructure/redis/ |
公共消费模板、消息补领与确认 |
app/src/main/java/interview/guide/modules/resume/ |
简历解析、分析代次、恢复与报告保存 |
app/src/main/java/interview/guide/modules/knowledgebase/ |
知识检索、题目草稿、容量校验与抽题 |
app/src/main/java/interview/guide/common/ai/、app/src/main/java/interview/guide/modules/interview/ |
结构化模型调用、作答合并与评估报告 |
如何写进简历
简历参谋 · 候选人简历分析助手 | 人力资源服务业场景示例
项目描述:面向招聘顾问的候选人简历分析工具,解析简历并结合岗位要求输出结构化评分、优势与修改建议,支持分析进度查询和原文依据回查。
技术栈:Java、Spring Boot、Spring AI、PostgreSQL、Redis
主要工作:
- 异步分析与超时任务恢复:基于
Redis Streams消费组承接耗时分析,补领未确认消息并通过数据库条件更新领取任务;结合阶段心跳、超时巡检与有限补投恢复卡住任务,将简历上传与模型评估解耦。 - 执行代次与迟到结果隔离:以
attemptId标识本轮执行权,进度与完成状态均按代次条件更新,保存分析结果时通过PostgreSQL事务行锁再次复核;防止任务重领后旧消费者覆盖新结果。 - 结构化评价与岗位证据关联:基于
Spring AI、Java record与BeanOutputConverter约束评分及建议结构,统一执行Schema校验与有限重试;将岗位要求、简历段落标识接入评价结果,使顾问能够回查匹配依据。
面试搭子 · 技术岗位模拟面试陪练 | 教育培训业场景示例
项目描述:面向技术培训机构的模拟面试陪练,从课程知识库生成题目与评分要点,按方向和难度组织主问、追问,并根据学员作答生成逐题评价与补练建议。
技术栈:Java、Spring Boot、Spring AI、PostgreSQL、pgvector、Redis
主要工作:
- 课程知识检索与结构化出题:基于
pgvector检索课程资料,通过Spring AI生成主问题、参考答案、评分要点和追问;结合章节范围组织上下文,按规范化题干去重并保存草稿,供教师审核后启用。 - 生成任务隔离与短事务落库:将耗时检索和模型调用放在事务外执行,生成前核对任务标识,保存时以短事务复核当前任务并替换题目;拦截已被新任务替代的迟到结果,避免长事务占锁和旧题覆盖。
- 主问追问关联与受限抽题:按方向、难度及知识库筛选启用题目,检查主问题数量与追问池容量,再按父子关系抽取题目;结合课程知识点覆盖组织训练,容量不足时返回缺口,避免追问错配或静默缺题。
- 异步评估与分项训练反馈:通过
Redis Streams异步处理作答评估,按题目索引合并回答,将参考答案和评分要点交给结构化评价服务;按知识点汇总逐题失分依据与补练建议,形成可回看的训练报告。
面试题与回答
3 题用 Redis 作消息队列时,如何处理宕机导致的消息丢失?
展开收起
核心回答
InterviewGuide用Java和Spring实现简历分析等异步任务,Redis Stream负责投递,数据库保存正文、结果与任务状态。以简历分析为例,先保存PENDING任务,再发送包含resumeId和retryCount的消息;消费者先领取数据库任务,执行分析、保存结果并更新完成状态,最后确认消息。恢复有两层:Stream 层主动补领未确认消息,数据库层扫描长期卡住的任务并补投。 前者解决消息交给某个消费者后无人继续处理的问题,后者为消息丢失、裁剪或业务状态停滞提供恢复依据。它允许重投,依靠业务状态约束重复执行,并不承诺 Redis 永不丢消息。
未确认消息怎样重新被消费
消费者启动时准备消费组,每个实例使用独立消费者名称。循环先调用
autoClaim(),按游标补领空闲超过 5 分钟的待处理消息,每批最多 10 条;本轮没有补领结果时,再阻塞读取该组尚未投递的新消息。由此,原消费者宕机后,恢复并不依赖它以原名称重新上线。具体顺序见补领与新消息读取。补领成功只表示获得消息,还要通过数据库的
PENDING→PROCESSING条件更新领取业务任务。已完成或无法领取的任务会确认消息并跳过。若旧任务仍停在PROCESSING,补领消息不会直接使它重新执行,需要下一层数据库恢复将过期状态重置后补投。PEL 状态和业务任务状态是两套记录,不能混为一条状态机。业务失败、重试与 ACK 的顺序
公共消费者的正常路径是:领取任务、执行业务、更新完成状态、
ACK。异常路径会检查消息里的retryCount:小于 3 时重新投递,达到上限后标记失败,再确认旧消息。这里最多是首次执行加 3 次消息重试,业务抛出的异常才会进入这条路径。简历分析重投前先条件重置为
PENDING,新消息写入增加后的重试次数;重投入队失败时,把仍待处理的任务标为FAILED。格式错误消息会直接确认丢弃,ACK 失败则记录日志,后续仍可能再次被补领。见消费确认与异常处理及简历分析重投。当前超过重试上限的处理是数据库
FAILED终态与手动重试,没有独立死信 Stream。若增加死信能力,可以保存原消息、业务标识、异常原因和重试历史,完成持久转存后再确认原消息。另一方面,分析服务会把部分模型调用异常转换为错误报告返回,队列可能仍按正常结果保存并完成;消息成功处理与模型分析结果有效也要分别判断。队列记录丢了,数据库怎样兜底
上传流程先提交简历记录,再发送消息;发送失败会保留文件与记录,并把待处理任务标记失败,供用户重试。这是分步写入,未使用覆盖数据库和
Redis的原子事务或事务发件箱。数据库恢复调度器默认每分钟分别扫描过期的
PENDING和PROCESSING:前者默认超过 10 分钟尝试补投;后者默认 15 分钟无进展时条件重置,再补投。恢复有次数上限,避免无限循环,具体见卡住任务恢复调度。阈值需要覆盖排队和实际业务耗时,否则会误判仍在执行的任务。消息发送使用近似
MAXLEN=1000裁剪,未确认消息也可能先被裁掉;补领发现这类 ID 时只记录警告日志,无法恢复已裁剪的消息体。因此数据库任务和已保存的简历正文很关键,但仍需要核对恢复是否成功、失败任务是否有人处理。要进一步提高可靠性,应结合部署环境配置持久化与复制、按积压调整保留策略,并验证“入队前宕机、领取后宕机、结果保存后 ACK 前宕机”三个窗口,而不能只测试正常消费。租约失效后为什么还需要 Fencing Token,如何防止旧 Worker 写回?
展开收起
核心回答
InterviewGuide的简历分析任务通过数据库中的状态和随机attemptId识别当前执行者。消费者原子地把任务从PENDING改成PROCESSING,并记录本次尝试;超时恢复或重试会清除旧尝试,新消费者重新领取时生成新的标识。心跳、完成、失败和重试重置均检查当前尝试,分析结果保存也在数据库事务中验证所有权。这里采用的是当前尝试的等值校验,能阻止失去执行权的旧消费者写入分析结果或改动新尝试的状态。
attemptId是随机UUID,并不是面向外部资源的单调递增Fencing Token;它也无法撤销已经发给模型的分析请求。怎样领取与恢复任务
AnalyzeStreamConsumer.tryMarkProcessing()生成UUID,调用tryMarkAnalyzeProcessing()。只有数据库中仍为PENDING的任务才能更新成功,受影响行数为1时才继续分析;重复消息或其他消费者不会仅凭消息到达就取得业务执行权。消费者领取、条件领取。心跳通过
PROCESSING + analyzeAttemptId条件更新进展时间,更新失败就抛出执行权失效异常。当前简历分析链在下载解析完成、模型分析返回后报告心跳,并非持续运行独立心跳线程。长调用可能超过无进展阈值,因此阈值需要考虑调用和重试的总耗时。业务阶段与心跳、恢复配置说明。恢复调度器扫描无进展任务,以状态和时间条件将过期的
PROCESSING重置为PENDING、清除旧attemptId,再补投消息。旧消费者仍可能继续计算,但它持有的旧标识已经无法匹配当前数据库状态。恢复调度、恢复条件更新。为什么保存前再检查仍然有用
仅靠模型返回后的心跳仍不够:消费者可能刚通过心跳检查,就再次暂停。
saveAnalysisIfOwned()会在事务内通过findByIdForUpdate()锁住简历行,重新检查状态和attemptId,通过后才保存分析结果。这次校验与结果落库受同一个事务保护,恢复调度器不能在二者之间替换任务所有权。锁行查询、事务内验证与结果保存。完成、失败以及重试重置也带尝试条件。旧消费者失权后,它的重试重置更新不到当前任务,就不会重新投递旧尝试;旧的完成或失败回调也不会覆盖新尝试。条件状态更新、重试处理。
分析结果保存与随后标记
COMPLETED是两个独立事务,不能把这条链描述为“结果、终态和消息确认一次原子提交”。它保护的是相应数据库写回;模型可能被重复调用,端到端也不具备外部副作用只发生一次的保证。消费执行顺序。怎样验证旧结果确实被隔离
可以让尝试 A 停在模型调用中,触发恢复并让 B 取得新
attemptId,再释放 A,检查 A 的心跳与结果写回被拒绝;还要覆盖“A 心跳成功后暂停、B 接管、A 再保存”的时序,以验证最终事务检查。分别核对分析记录、任务状态和外部模型调用次数,才能区分旧结果隔离与重复计算。简历评分结果如何结构化输出?
展开收起
核心回答
InterviewGuide以 Javarecord描述简历分析结果,用BeanOutputConverter生成格式要求,再通过统一的StructuredOutputInvoker调用与解析模型响应。结果包括总分、五项评分、摘要、优点及建议,最终保存为分析记录。这一实现解决了结构交接和重试复用,但仍要单独看分值约束与失败语义:当前 DTO 没有表达评分区间,服务外层还可能把失败转换为零分报告,因此结构能保存不等于分析有效。
DTO 和 Prompt 怎样配合?
内部 DTO 定义
overallScore、scoreDetail、summary、strengths、suggestions;建议包含类别、优先级、问题和推荐修改。服务把outputConverter.getFormat()追加到系统提示词,再将简历正文填入用户模板。当前分析方法接收的是简历正文,没有显式目标岗位参数。DTO 与格式构造。系统模板要求项目、技能、内容、结构、表达五项贡献分分别为
40/20/15/15/10,合计 100。对外响应类的部分注释仍是旧的分项上限,不能将注释直接当成当前执行规则;这些标准需要统一维护。实际评分模板、响应字段注释。转换失败怎样重试?
统一调用器默认开启 Schema 校验,通过
call.entity(outputConverter, spec -> spec.validateSchema())获取对象。关闭校验的兼容分支才采用文本转换,并尝试修复字符串中的未转义引号;这不是任意 JSON 错误都能修好的通用修复器。两条解析分支。外层调用器按配置重试,后续尝试可增加严格 JSON 要求和上一轮错误,默认外层最多尝试两次。Schema 校验和 SDK 自身可能还有内部调用,所以两次不能直接当成全部 HTTP 请求的上限。重试封装、默认配置。
为什么还需要业务校验和独立失败状态?
评分 DTO 的分数字段只有
int,没有最小值、最大值或分项合计约束;转换后也没有重新计算总分。生成的对象即使通过现有结构校验,仍可能分值越界或总分不一致。完善时应让分项标准进入可执行约束,再检查总分与评分依据。此外,
ResumeGradingService外层捕获异常后返回一份零分错误报告。只要当前消费者仍拥有执行权,它就可以按普通分析结果保存,并随后完成任务。因此不能把任务COMPLETED或一条零分分析记录直接解释为评价成功。错误报告回退、结果保存入口。执行代次校验能阻止旧消费者写入,却不会判断报告内容有效。进一步完善时,可把分析调用失败、报告校验失败与正常评分分开,避免用户将系统错误理解为自己的简历得了零分;再用缺字段、越界分数、总分冲突和服务不可用样例分别验证。