面壁 OfferPilot — 开源 AI 简历智能分析与模拟面试平台
2026/9/12 21:42:07 网站建设 项目流程

面壁 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,当文本提取过短(疑似扫描件)时自动降级到 OCR,AI 负责从非结构化文本中提取结构化字段。调用时使用不含 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,当文本提取过短(疑似扫描件)时自动降级到 OCR,AI 负责从非结构化文本中提取技术栈、项目经历等结构化字段。成本可控,不依赖单一服务。

解析管线的大致实现:

// 第一阶段:PDFBox 提取文本Stringtext=extractTextWithPdfBox(inputStream);// 第二阶段:文本过短则 OCR 降级if(text.length()<MIN_TEXT_LENGTH){text=extractTextWithOcr(inputStream);}// 第三阶段:AI 结构化解析Stringprompt=buildResumeParsePrompt(text);ChatResponseresponse=chatClientNoRag.prompt(prompt).call().chatResponse();ResumeParseResultresult=objectMapper.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 Boot,Spring 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 Chain:AI 调用的 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,Optional<RetrievalAugmentationAdvisor>ragAdvisor,MessageChatMemoryAdvisormemoryAdvisor,UserTokenUsageServicetokenUsageService,ObjectMapperobjectMapper){List<Advisor>advisors=newArrayList<>();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();}

注意ragAdvisorOptional的,意味着如果 pgvector 不可用,RAG 环节自动跳过,整个链路仍然正常工作。这种设计保证了系统在基础设施不完整时也能降级运行。

ApiKeyRoutingAdvisor是链路中比较特殊的一个——它会在调用链的末端拦截请求,判断用户是否配置了自己的 API Key:

@OverridepublicChatClientResponseadviseCall(ChatClientRequestrequest,CallAdvisorChainchain){StringapiKey=extractApiKey(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 Bean:

@BeanChatClientchatClient(ChatClient.Builderbuilder,MessageChatMemoryAdvisormemoryAdvisor,Optional<RetrievalAugmentationAdvisor>ragAdvisor,UserTokenUsageServicetokenUsageService,ObjectMapperobjectMapper){List<Advisor>advisors=newArrayList<>();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:面试实时反馈的架构设计

模拟面试的"逐题反馈"场景,本质上是长连接 + 流式推送。每个用户的每次答题,后端都需要:

  1. 组装 Prompt(当前轮次、历史对话、RAG 检索结果)
  2. 调用 DeepSeek API(流式返回)
  3. 逐字推送到前端

如果使用 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 线程池。客户端断开时,通过onCancelonDispose回调及时清理资源,防止连接泄漏。

SSE 端点的实现大致如下:

@PostMapping("/{id}/answer")publicSseEmitteranswer(@PathVariableLongid,@RequestBodyAnswerRequestrequest){// 校验会话状态InterviewSessionsession=interviewService.validateSession(id);// 创建 SSE 发射器,超时 30 分钟SseEmitteremitter=newSseEmitter(1800000L);sseTaskExecutor.execute(()->{try{// 流式调用 AI,逐字推送到前端chatClient.prompt().user(request.getAnswer()).advisors(a->a.param("user_id",session.getUserId())).stream().chatResponse().doOnNext(response->{Stringcontent=response.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过滤确保数据隔离:

publicList<Document>search(Stringquery,LonguserId,inttopK){if(vectorStore==null){returnList.of();// 无 VectorStore 时降级}FilterExpressionBuilderbuilder=newFilterExpressionBuilder();Filter.Expressionfilter=builder.eq("user_id",userId.toString()).build();SearchRequestsearchRequest=SearchRequest.builder().query(query).topK(topK>0?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[]iv=newbyte[12];// GCM 推荐 12 字节 IVsecureRandom.nextBytes(iv);cipher.init(Cipher.ENCRYPT_MODE,secretKey,newGCMParameterSpec(128,iv));byte[]cipherText=cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));returnBase64.getEncoder().encodeToString(concat(iv,cipherText));}

选择 GCM 模式的原因:提供认证加密(Authenticated Encryption),同时保证机密性和完整性。每次加密生成随机 IV,相同明文每次密文不同。加密密钥通过环境变量注入,不写死在代码中。

这里踩过一个坑:SecureRandom.getInstanceStrong()在低熵 Docker 容器上可能阻塞,导致应用启动挂起。后来改用new SecureRandom()解决。


五、部署与 CI/CD

5.1 生产环境架构

服务器配置:火山引擎 2C4G,宝塔面板管理。

Nginx(反向代理) ├── / → 前端 SPA 静态文件 └── /api/ → 代理到后端 :8080(SSE 需 proxy_buffering off) Spring Boot(Java 21) 端口 8080,prod 环境 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-key=dummy-test-key","spring.ai.rag.reranker.model-uri=file:/nonexistent/reranker/model.onnx","spring.ai.rag.reranker.tokenizer-uri=file:/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_tokens=user_token_usage.prompt_tokens+EXCLUDED.prompt_tokens,completion_tokens=user_token_usage.completion_tokens+EXCLUDED.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 上交流:

  • GitHub:https://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 — 可自由使用、修改、商用。

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

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

立即咨询