面壁 OfferPilot — 开源 AI 简历智能分析与模拟面试平台 面壁 OfferPilot一个求职者为自己做的 AI 面试工具从需求到实现从自用到开源一、故事从一次求职开始1.1 求职者的困境两周前我开始准备新一轮的求职。作为 Java 后端开发者每次改简历的时候都会想我这简历到底行不行跟这个岗位匹配吗试着把简历发给朋友看朋友说还行但说不出具体哪里行、哪里不行。刷八股文又觉得没有针对性——你根本不知道面试官会从哪个角度问。想找人 Mock 面试时间凑不上最后不了了之。我相信这不是我一个人的问题。每个求职者都经历过这种状态你花了很多时间准备但不确定这些准备是不是对的。1.2 灵光一现2024 年大语言模型的能力已经足够成熟了。我试着把简历和 JD 扔给 ChatGPT让它帮我分析匹配度。结果出乎意料的好——它指出了几个我完全没注意到的短板。但 ChatGPT 是通用对话工具不是专门为面试准备的。每次都要复制粘贴简历、调整 Prompt用起来很别扭。为什么不做个专门的产品呢上传简历 → 自动解析 → 选目标岗位 → 生成匹配报告 → 模拟面试。把整个流程串起来做成一个完整的工具。给自己用。1.3 面壁名字的由来项目起名的时候想了很久。面壁取自面壁思过。面壁是达摩祖师的典故——面壁九年潜心修炼。放在求职的场景里就是面对 AI反思自己的不足查漏补缺。汉语里面壁本身就带有一丝苦修的意味恰好契合准备面试的过程枯燥、反复、但值得。英文名OfferPilot则直白一些——Pilot 是领航员目标是帮你拿到 Offer。1.4 从自用到开源项目最开始就是给自己写的。没有规划开源没有想过去 GitHub 上拿 Star。就是单纯地觉得我需要一个工具市面上没有现成的那我写一个。写着写着发现这个工具确实能帮到我。那它应该也能帮到其他人。于是决定认真做前后端分离、部署上线、修 bug、优化体验。等真正稳定了才决定开源。开源是结果不是目的。二、这个项目能做什么2.1 简历解析从 PDF 到结构化数据用户上传 PDF 简历系统自动解析出技术栈、项目经历、工作经历、教育背景以结构化卡片展示。产品层面解析结果一眼看清自己的简历信息。支持多简历管理可以随时切换默认简历方便管理不同版本的简历。技术层面解析采用三阶段管线——PDFBox 提取文本 → Tess4j OCR 降级 → DeepSeek AI 结构化。PDFBox 处理绝大多数 PDF当文本提取过短疑似扫描件时自动降级到 OCRAI 负责从非结构化文本中提取结构化字段。调用时使用不含 RAG 的 ChatClient避免知识库内容污染简历解析结果。2.2 评估报告AI 分析匹配度选定简历和目标职位后AI 综合简历和 JD 生成匹配度评估报告。产品层面报告包含综合匹配分数、技术栈对比分析哪些匹配、哪些缺失、建议补什么、亮点提炼和短板提醒。报告异步生成生成完成后可在报告列表查看。技术层面报告使用专用的 Prompt 模板综合简历文本和 JD 描述调用 DeepSeek 输出结构化 JSON。生成过程在独立线程池中异步执行避免阻塞主请求。事务机制确保报告状态与内容一致写入。2.3 模拟面试三面制 AI 陪练三面制每轮 10 题技术一面考察基础能力技术二面深入底层原理技术三面考察架构设计。产品层面AI 逐题实时反馈 评分SSE 流式输出像打字一样逐字显示回答。过程中断后 1 小时内可以继续支持跳过和提前结束结束后生成三轮评分汇总。技术层面面试会话使用状态机管理IDLE → IN_PROGRESS → COMPLETED / EXPIRED每次答题前后端通过 SSE 长连接通信后端使用虚拟线程处理流式 AI 调用避免阻塞 Tomcat 线程池。对话历史持久化到 PostgreSQL支持中断恢复。2.4 知识库把笔记带进面试上传技术文档面试时 AI 自动检索相关内容增强回答。产品层面支持 Markdown 和 TXT 格式上传后 AI 自动完成分片、向量化、建索引无需手动干预。面试答题时系统自动匹配知识库中与当前问题最相关的内容注入上下文让回答更有针对性。技术层面上传后经 TokenTextSplitter 分片500 字符/片50 字符重叠BGE 本地 ONNX 模型嵌入为 512 维向量存入 pgvector HNSW 索引。检索时先向量检索 top-K再经 BGE 交叉编码器重排序确保精度。检索严格按 user_id 过滤数据隔离。2.5 API Key 配置自备 Key 不限量用户可以在平台配置自己的 DeepSeek API Key配置后不再受平台免费额度限制。产品层面配置页面提供 API Key 输入框配置后实时生效支持随时更换或删除。用量统计页面展示今日 Token 消耗、月度汇总和趋势图用量透明可见。技术层面API Key 使用 AES-256-GCM 加密后存入数据库每次加密生成随机 IV相同明文每次密文不同。解密仅在内存中临时进行用完即弃不写日志。调用时通过 ApiKeyRoutingAdvisor 在 Advisor 链末端拦截请求判断是否使用用户自备 Key 直连 DeepSeek API。2.6 如果你是开发者这个项目本身也是一个学习 AI Agent 开发的完整案例Advisor Chain 管道设计、RAG 全链路、SSE 流式架构、对话记忆管理、API Key 多租户路由。每一个模块都可以独立拆出来学习和复用。三、技术选型每一层都是权衡3.1 技术栈全景层次选型为何选它后端框架Spring Boot 4.1.1最新稳定版虚拟线程原生支持AI 框架Spring AI 2.0.1Spring 生态原生集成Advisor 机制灵活运行环境JDK 21虚拟线程正式 GA适合 SSE 长连接场景数据库PostgreSQL 16 pgvector结构化数据 向量检索一体化LLMDeepSeek性价比高OpenAI 兼容接入成本低文件存储MinIOS3 兼容Docker 一键部署社区版免费认证Sa-Token JWT双 Token 机制开箱即用前端Vue 3 TypeScript Vite主流技术栈Element Plus 生态成熟向量嵌入BGE bge-small-zh-v1.5 (ONNX)本地运行零 API 费用数据不出服务器向量重排BGE bge-reranker-base (ONNX)交叉编码器先检索后重排兼顾效率与精度3.2 几个关键决策背后的思考简历解析为什么不用商业 API市面上有成熟的文档解析 API——上传 PDF返回结构化 JSON非常方便。但有两个问题一是按调用次数收费量大了成本不低二是依赖第三方服务一旦对方接口变更或下线整个功能就废了。我的方案是三阶段管线PDFBox 提取文本 → Tess4j OCR 降级 → DeepSeek AI 结构化。PDFBox 处理绝大多数 PDF当文本提取过短疑似扫描件时自动降级到 OCRAI 负责从非结构化文本中提取技术栈、项目经历等结构化字段。成本可控不依赖单一服务。解析管线的大致实现// 第一阶段PDFBox 提取文本StringtextextractTextWithPdfBox(inputStream);// 第二阶段文本过短则 OCR 降级if(text.length()MIN_TEXT_LENGTH){textextractTextWithOcr(inputStream);}// 第三阶段AI 结构化解析StringpromptbuildResumeParsePrompt(text);ChatResponseresponsechatClientNoRag.prompt(prompt).call().chatResponse();ResumeParseResultresultobjectMapper.readValue(response.getResult().getOutput().getText(),ResumeParseResult.class);注意这里用的是chatClientNoRag不含 RAG 的 ChatClient避免知识库内容污染简历解析结果。SSE 还是 WebSocket模拟面试的场景是用户答题 → AI 流式生成 → 前端实时展示。这是一个单向数据流服务器推给客户端不需要客户端持续推送。WebSocket 是全双工协议引入它意味着需要处理心跳、重连、连接池管理——针对这个场景这些都是不必要的复杂度。SSE 天然适合服务器推送基于 HTTP 协议浏览器原生支持配合 Nginx 只需要一行proxy_buffering off。为什么选 Spring AI 而不是 LangChain4j两个都是 Java 生态的 AI 框架选 Spring AI 的原因很直接项目本身基于 Spring BootSpring AI 的 Advisor 机制与业务需求高度吻合。我要的不是一个简单的调 API的封装而是一个可编排的调用管道——安全校验、额度控制、Prompt 优化、RAG 检索、日志记录、Key 路由每个环节独立可以插拔、可以排序。Spring AI 的 Advisor 接口就是这个模式而 Spring 生态对拦截器链这种模式有天然的优势。为什么选 DeepSeek选 DeepSeek 的原因很务实性价比。国外大模型的 API 价格对个人项目来说仍然偏高——每月跑几百次测试、几十轮面试对话Token 消耗量不小。DeepSeek 的定价大约是 GPT-4 的十分之一而且提供 OpenAI 兼容接口接入成本极低。如果未来需要换模型只需改一行 API 地址和 Key。另外这个项目的核心是 AI Agent 的调用链路设计而不是某个特定模型的能力。模型只是一个引擎随时可以换。选择 DeepSeek 意味着可以用更低的成本跑通全链路把预算花在更有价值的地方——比如多测几轮 Advisor 链的编排。为什么不是微服务这是一个一定会被问到的问题。Nacos、Redis、MySQL 读写分离、API 网关、分布式事务——这些技术栈在今天的后端项目中非常成熟对于需要高可用、高并发的业务场景来说不可或缺。但面壁从一开始就没有走这条路原因很简单这不是本次开源的重点。这个项目的核心价值在于 AI Agent 的调用链路设计——Advisor Chain 的编排、RAG 的全链路实现、SSE 流式架构、对话记忆管理。这些是真正值得关注和学习的部分。引入微服务架构意味着要处理服务发现、配置中心、分布式会话、服务间调用等额外复杂度而这些与 AI Agent 的核心逻辑没有直接关系。少依赖基础设施也是在控制成本。当前架构下Spring Boot 单体 PostgreSQL MinIO三台 Docker 容器就能跑完整套系统。一台 2C4G 服务器月租不到一百块个人开发者完全负担得起。当然如果未来有更高的性能和稳定性要求引入更完备的基础设施是完全合理的——这个项目本身的设计足够模块化Advisor 链、RAG 服务、面试状态机等模块按领域划分边界清晰迁移到微服务架构并不困难。如果有人想在此基础上做性能优化或二次开发欢迎 fork 改造。如果有人只是想借鉴产品需求或产品设计思路也完全没问题。技术选型没有绝对的对错只有合不合适的场景。这个阶段我把精力放在 AI Agent 的核心技术上希望让更多人能轻松部署和上手学习而不是被基础设施的门槛挡在门外。四、核心实现把设计落地4.1 Advisor ChainAI 调用的 7 层管道这是整个 AI 调用链的核心抽象。借鉴了 AOP 拦截器链的思想将一次 AI 调用拆解为 7 个独立环节按顺序执行SafeValidAdvisor(0) → 安全校验过滤非法输入 TokenUsageAdvisor(0) → 前置校验额度 后置累计用量 ReReadingAdvisor(1) → Prompt 重读优化RE² 技术 MessageChatMemoryAdvisor(2) → 对话记忆自动注入 RetrievalAugmentationAdvisor(3) → 自动 RAG 检索 增强 MyLogAdvisor(4) → 请求耗时日志 ApiKeyRoutingAdvisor(5) → 用户 Key / 平台 Key 路由每个 Advisor 只做一件事。比如TokenUsageAdvisor不关心 Prompt 内容只负责校验额度ApiKeyRoutingAdvisor不关心回答质量只负责判断走哪个 Key。在代码中所有的 Advisor 在AiCoreConfig中组装BeanpublicChatClientchatClient(ChatClient.Builderbuilder,OptionalRetrievalAugmentationAdvisorragAdvisor,MessageChatMemoryAdvisormemoryAdvisor,UserTokenUsageServicetokenUsageService,ObjectMapperobjectMapper){ListAdvisoradvisorsnewArrayList();advisors.add(newSafeValidAdvisor());advisors.add(newTokenUsageAdvisor(tokenUsageService));advisors.add(newReReadingAdvisor());advisors.add(memoryAdvisor);ragAdvisor.ifPresent(advisors::add);// RAG 可选无 VectorStore 时不注入advisors.add(newMyLogAdvisor());advisors.add(newApiKeyRoutingAdvisor(objectMapper));returnbuilder.defaultAdvisors(advisors.toArray(newAdvisor[0])).build();}注意ragAdvisor是Optional的意味着如果 pgvector 不可用RAG 环节自动跳过整个链路仍然正常工作。这种设计保证了系统在基础设施不完整时也能降级运行。ApiKeyRoutingAdvisor是链路中比较特殊的一个——它会在调用链的末端拦截请求判断用户是否配置了自己的 API KeyOverridepublicChatClientResponseadviseCall(ChatClientRequestrequest,CallAdvisorChainchain){StringapiKeyextractApiKey(request);if(apiKey!null){// 用户自备 Key跳过 ChatModel直连 DeepSeek APIreturncallDeepSeekDirectly(request,apiKey);}// 使用平台默认 Key透传给 ChatModelreturnchain.nextCall(request);}这种设计让免费用户和使用自备 Key 的高级用户走不同的路径但上层的调用方不需要关心这个差异——Advisor 链对调用方是完全透明的。4.2 双 ChatClient为什么需要两个ChatClient是 Spring AI 的核心入口所有的 AI 调用都通过它。但不同的场景需要不同的 Advisor 组合。面试场景需要完整的 Advisor 链包括 RAG知识库检索增强。但简历解析如果也走 RAG知识库的内容会污染结构化提取的结果——比如用户上传了一份 Java 技术文档结果简历解析时 AI 把文档内容也当作参考信息提取出来的技术栈就偏了。解决方案是创建两个 ChatClient BeanBeanChatClientchatClient(ChatClient.Builderbuilder,MessageChatMemoryAdvisormemoryAdvisor,OptionalRetrievalAugmentationAdvisorragAdvisor,UserTokenUsageServicetokenUsageService,ObjectMapperobjectMapper){ListAdvisoradvisorsnewArrayList();advisors.add(newSafeValidAdvisor());advisors.add(newTokenUsageAdvisor(tokenUsageService));advisors.add(newReReadingAdvisor());advisors.add(memoryAdvisor);ragAdvisor.ifPresent(advisors::add);// 含 RAG无 VectorStore 时自动跳过advisors.add(newMyLogAdvisor());advisors.add(newApiKeyRoutingAdvisor(objectMapper));returnbuilder.defaultAdvisors(advisors.toArray(newAdvisor[0])).build();}BeanChatClientchatClientNoRag(ChatClient.Builderbuilder,UserTokenUsageServicetokenUsageService,ObjectMapperobjectMapper){returnbuilder.defaultAdvisors(newSafeValidAdvisor(),newTokenUsageAdvisor(tokenUsageService),newReReadingAdvisor(),newMyLogAdvisor(),newApiKeyRoutingAdvisor(objectMapper))// 不含 RAG.build();}4.3 虚拟线程 SSE面试实时反馈的架构设计模拟面试的逐题反馈场景本质上是长连接 流式推送。每个用户的每次答题后端都需要组装 Prompt当前轮次、历史对话、RAG 检索结果调用 DeepSeek API流式返回逐字推送到前端如果使用 Tomcat 线程池处理这些请求一个 SSE 连接就占用一个线程连接数一多线程池就满了。JDK 21 的虚拟线程正是为了解决这个问题——虚拟线程在 IO 阻塞时会自动挂起并释放载体线程非常适合 SSE 这种等待 IO 的场景。Bean(name sseTaskExecutor, destroyMethod close) public Executor sseTaskExecutor() { return Executors.newThreadPerTaskExecutor( Thread.ofVirtual().name(sse-).factory()); }每个 SSE 连接持有一个独立的虚拟线程不会阻塞 Tomcat 线程池。客户端断开时通过onCancel和onDispose回调及时清理资源防止连接泄漏。SSE 端点的实现大致如下PostMapping(/{id}/answer)publicSseEmitteranswer(PathVariableLongid,RequestBodyAnswerRequestrequest){// 校验会话状态InterviewSessionsessioninterviewService.validateSession(id);// 创建 SSE 发射器超时 30 分钟SseEmitteremitternewSseEmitter(1800000L);sseTaskExecutor.execute(()-{try{// 流式调用 AI逐字推送到前端chatClient.prompt().user(request.getAnswer()).advisors(a-a.param(user_id,session.getUserId())).stream().chatResponse().doOnNext(response-{Stringcontentresponse.getResult().getOutput().getText();emitter.send(SseEmitter.event().data(content));}).blockLast();emitter.complete();}catch(Exceptione){emitter.completeWithError(e);}});// 客户端断开时清理emitter.onCompletion(()-cleanup(session));emitter.onTimeout(()-cleanup(session));returnemitter;}4.4 面试状态机中断恢复的实现模拟面试不是一次性对话——用户可能中途离开、网络中断、或者想先退出去准备一下再回来。这就要求面试会话必须能暂停和恢复。面试会话使用状态机管理核心状态流转IDLE → IN_PROGRESS → COMPLETED ↑ ↓ └── EXPIRED超过 1 小时未操作每次答题前检查会话是否过期if(session.getExpiredAt().isBefore(LocalDateTime.now())){session.setStatus(EXPIRED);sessionRepository.updateById(session);// 持久化过期状态thrownewBusinessException(面试已过期请重新开始);}1 小时窗口的设计每轮面试约 20-30 分钟1 小时足够完成一轮。过长则失去安全性意义——如果用户离开座位太久会话应该自动失效而不是一直等着。4.5 RAG 知识库从文档到检索的全链路知识库的功能听起来简单——上传文档面试时自动检索——但背后的链路并不短。从原始文档到可检索的向量索引再到面试时的实时检索和重排中间经过多个环节文档上传 → TokenTextSplitter 分片500 字符/片50 字符重叠 → BGE 本地 ONNX 模型嵌入512 维向量 → pgvector HNSW 索引余弦距离 → 面试时自动检索 → BGE 交叉编码器重排序 → 结果注入 Prompt 上下文为什么用本地嵌入模型最开始考虑过调用第三方嵌入 API但每次文档上传都要网络请求延迟高还可能产生额外费用。BGE 的 ONNX 量化版只有 23MB在 2C4G 服务器上推理延迟约 50-100ms足够了。重排的作用向量检索bi-encoder速度快但精度有限top-K 结果中可能混入不相关的内容。交叉编码器cross-encoder逐对计算 query 与 doc 的相关性精度更高。先检索后重排两阶段兼顾效率与精度。检索时通过user_id过滤确保数据隔离publicListDocumentsearch(Stringquery,LonguserId,inttopK){if(vectorStorenull){returnList.of();// 无 VectorStore 时降级}FilterExpressionBuilderbuildernewFilterExpressionBuilder();Filter.Expressionfilterbuilder.eq(user_id,userId.toString()).build();SearchRequestsearchRequestSearchRequest.builder().query(query).topK(topK0?topK:5).similarityThreshold(0.5).filterExpression(filter).build();returnvectorStore.similaritySearch(searchRequest);}每个用户只能检索到自己知识库中的文档过滤条件由 Spring AI 的RetrievalAugmentationAdvisor自动注入无需在每个查询中手动拼接 SQL。4.6 API Key 加密AES-256-GCM 的实现用户配置的 DeepSeek API Key 相当于用户的资产——泄漏了可能被他人盗用产生费用。因此 API Key 不能明文存储必须在入库前加密且解密只在内存中临时进行用完即弃。采用 AES-256-GCM 加密后存入数据库publicStringencrypt(StringplainText){byte[]ivnewbyte[12];// GCM 推荐 12 字节 IVsecureRandom.nextBytes(iv);cipher.init(Cipher.ENCRYPT_MODE,secretKey,newGCMParameterSpec(128,iv));byte[]cipherTextcipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));returnBase64.getEncoder().encodeToString(concat(iv,cipherText));}选择 GCM 模式的原因提供认证加密Authenticated Encryption同时保证机密性和完整性。每次加密生成随机 IV相同明文每次密文不同。加密密钥通过环境变量注入不写死在代码中。这里踩过一个坑SecureRandom.getInstanceStrong()在低熵 Docker 容器上可能阻塞导致应用启动挂起。后来改用new SecureRandom()解决。五、部署与 CI/CD5.1 生产环境架构服务器配置火山引擎 2C4G宝塔面板管理。Nginx反向代理 ├── / → 前端 SPA 静态文件 └── /api/ → 代理到后端 :8080SSE 需 proxy_buffering off Spring BootJava 21 端口 8080prod 环境 Docker 容器 ├── PostgreSQL 16 pgvector └── MinIO 对象存储5.2 CI/CD 自动化GitHub Actions 每次 push 自动执行push / PR → 启动 PostgreSQL pgvector 容器 → JDK 21 环境准备 → Maven 编译打包 → 运行全部单元测试 → 构建结果通知CI 配置中有几个细节需要处理数据库连接CI 中 PostgreSQL 使用默认端口 5432通过SPRING_DATASOURCE_URL环境变量覆盖默认配置。ONNX 模型下载CI 网络环境不一定能访问 HuggingFace测试类通过MockitoBean模拟嵌入模型避免下载 40MB 的模型文件。DeepSeek API Key测试类中覆盖为 dummy 值不依赖 GitHub Secrets。SpringBootTest(properties{spring.ai.deepseek.api-keydummy-test-key,spring.ai.rag.reranker.model-urifile:/nonexistent/reranker/model.onnx,spring.ai.rag.reranker.tokenizer-urifile:/nonexistent/reranker/tokenizer.json})classOfferPilotApplicationTests{MockitoBeanprivateEmbeddingModelembeddingModel;// 避免下载 ONNX 模型TestvoidcontextLoads(){// 验证 Spring 上下文能正常加载}}MockitoBean是 Spring Boot 3.4 引入的注解相比旧版的MockBean它更轻量且不会触发原 Bean 的初始化过程。六、并发安全与线上踩坑6.1 Token 用量统计的竞态问题user_token_usage表按(user_id, usage_date)唯一约束记录每日用量。两个并发请求同时插入同一天的记录会导致唯一约束冲突。解决方式用INSERT ... ON CONFLICT DO UPDATE替代先查后改一步到位INSERTINTOuser_token_usage(user_id,usage_date,prompt_tokens,completion_tokens)VALUES(?,?,?,?)ONCONFLICT(user_id,usage_date)DOUPDATESETprompt_tokensuser_token_usage.prompt_tokensEXCLUDED.prompt_tokens,completion_tokensuser_token_usage.completion_tokensEXCLUDED.completion_tokens;6.2 SSE 连接泄漏流式场景中客户端可能在不经意间断开连接浏览器关闭、网络中断。如果不及时释放资源每个断开的连接都会留下一个僵尸线程。在ApiKeyRoutingAdvisor中通过Flux.create()的onCancel/onDispose回调清理 HTTP 连接emitter.onCancel(()-{httpRequest.abort();// 客户端断开时关闭 HTTP 连接});emitter.onDispose(()-{inputStream.close();// 订阅取消时清理资源});6.3 SecureRandom 阻塞第一次部署到服务器时应用启动后卡住了五分钟没有任何日志输出。排查后发现是SecureRandom.getInstanceStrong()在低熵 Docker 容器中阻塞等待熵池填充。改成new SecureRandom()后问题解决。这个坑在开发环境不会出现开发机有鼠标、键盘等输入设备提供熵源但生产服务器的 Docker 容器中一定会遇到。七、项目价值与产品价值7.1 产品价值帮求职者解决实际问题面壁不是什么颠覆性的产品它解决的是一个很具体的问题求职者不知道自己的简历和面试准备到底够不够。它做的事情很简单简历不够好 → AI 告诉你哪里不够不知道面什么 → AI 根据你的简历和 JD 出题没人 Mock 面试 → AI 陪练逐题实时反馈它不是替代面试官而是帮你在真正面试前多一次照镜子的机会。7.2 项目价值一个可学习的 AI Agent 实践案例从技术角度看这个项目覆盖了 AI Agent 开发的几个核心领域AI 调用管道设计Advisor Chain 模式可插拔、可编排RAG 完整实现从文档分片到向量检索到重排全链路闭环流式架构SSE 虚拟线程适合 IO 密集型实时推送场景安全设计AES-256-GCM 加密、数据隔离、双 Token 认证生产部署从 CI/CD 到上线运维全流程实践如果你正在学习 AI Agent 开发这个项目可以作为一个能跑起来的参考案例来读。7.3 一些遗憾目前只支持 DeepSeek后续可以接入更多模型面试题目不能自定义用户无法上传自己的题库不支持批量上传简历没有 HTTPS后续配域名后再上当然这些遗憾也正是项目继续迭代的方向。如果你有好的想法或建议欢迎在 GitHub 上提 Issue 或 PR。但回过头来看这个项目从最初的一个想法到能跑起来的工具再到部署上线、开源分享已经超出了我最初的预期。它证明了一个普通开发者用业余时间借助现有的 AI 能力和开源生态完全可以做出一个有价值的产品。八、关于作者我是eyki技术开发者。这个项目从我个人求职面试的需求出发经历了需求分析、技术选型、编码实现、上线部署到最终开源的全过程。如果你也在准备面试或者对这个项目感兴趣欢迎在 GitHub 上交流GitHubhttps://github.com/OneyTo7/offer-pilot在线体验http://14.103.58.208如果觉得有用欢迎 Star 支持也欢迎提交 Issue 和 PR。九、截图预览 首页 / 登录注册登录页面简洁大气的 Landing 页 简历管理上传简历后自动解析结构化展示技术栈、项目经历 目标职位为简历设定目标公司和职位输入 JD 描述 评估报告AI 一键生成匹配度评估技术栈对比一目了然️ 模拟面试三面制 AI 面试SSE 流式实时反馈 AI 大模型配置支持自配 API Key用量透明展示 知识库上传文档AI 自动索引面试时检索增强开源地址GitHub:https://github.com/OneyTo7/offer-pilot如果觉得有用欢迎 Star ⭐ 支持关于作者平台微信加好友进群欢迎交流技术问题、面试经验、项目合作许可证MIT License — 可自由使用、修改、商用。