二哥项目资料

✅求职派应用架构设计

正文 11,810 字 #02 原文
本文目录 · 24 节

星球:Java进阶之路&二哥编程星球 | 专栏:求职派 OpenClaw 架构 | 作者:沉默王二 | 发布:2026-06-30

✅求职派应用架构设计

✅求职派应用架构设计

本篇记录的是求职派早期围绕“岗位信息采集展示”的应用架构演进,可用于理解职位数据、任务池和后台业务。技术派阅读: https://paicoding.com/jobclaw-architecture-decomposition

求职派的核心能力是岗位信息采集。

图片.png

用户提交数据源——可能是一段文本、一个网页链接、一份 Excel 文件,甚至一张截图——系统调大模型把里面的岗位信息提取出来,清洗、校验、入库。

01、集成 AI 能力

求职派本质上是一个 Spring Web 应用,去掉 AI 能力也能跑。

但,如果没有AI,谁学啊,是吧,哈哈

AI 在求职派里的定位是数据采集引擎。

用户提交需要采集的目标数据源,通过系统内部维护的系统提示词,调用大模型提取岗位信息,然后把结构化的结果存入数据库。

图片.png

对外是“提交任务 → 得到结果”,对内是“构造 Prompt → 调大模型 → 解析返回”。

基于 Spring AI 接入大模型,是整个架构的第一趴。后面所有的能力——多模态、多轮对话、工具调用——都是在这个基础上一层层往上添加的。

02、多模态支持

采集的数据源形式很多。有纯文本、有 HTML 网页、有 CSV 和 Excel 文件,还有图片格式(比如用户截了一张招聘信息的图)。

纯文本调文本模型没问题,图片就需要视觉模型了。

图片.png

Spring AI 本身支持多模态输入,可以把图片作为二进制数据和文字提示一起发给大模型。系统检测到输入是图片格式时,会构建一个包含图片数据和提取指令的复合消息,发给支持视觉能力的模型处理。

并不是所有模型都能处理图片,有些模型是文本和视觉分开的。所以系统在检测到输入是图片时,会自动切换到支持视觉能力的模型。

03、多轮对话

采集任务的信息要素比较多时(一份 Excel 可能有几十/几千条岗位信息),大模型不一定能一次把所有的数据完整返回。

要知道,大模型本身是无状态的,要它记住之前的对话内容,就得把之前的对话上下文一起发过去。求职派基于 Spring AI 的聊天记忆机制实现了这个能力:每轮对话的消息存在内存里,下一轮自动带上历史消息。

图片.png

具体的处理逻辑是:大模型返回结果后,系统检查返回内容是否完整(比如 JSON 数组有没有闭合)。如果不完整,自动发一条“你之前返回的结果不完整,继续返回剩余的内容”,带上历史上下文让大模型接着生成。

中间提取到的完整 JSON 对象会先暂存在本轮采集结果里。即使后面某一轮出错,系统也会保留已经提取出来的对象,并在这次采集结束时统一落到草稿库。

04、使用 Function Calling

免费模型的能力有局限。

比如给它一个 HTTP 链接,让它抓取对应网页的内容来解析——这事它就做不了,因为大模型本身没有访问网络的能力。

解决办法是 Function Calling。

图片.png

我们给大模型注册了一个网页抓取工具:输入一个 URL,用爬虫抓取网页内容,解析出表格数据返回给大模型。大模型判断用户提交的是链接时,就自己调这个工具拿到网页内容,再从中提取岗位信息。

工具注册走的是 Spring AI 的标准方式。大模型在调用时能看到所有已注册工具的描述,自己判断要不要调用。比如用户提交的是一段纯文本,大模型就不会调爬虫工具,直接从文本里提取信息。提交的是链接,大模型就先调爬虫拿到网页内容,再做提取。

除了爬虫工具,系统还支持通过 MCP 接入外部工具服务器。比如后来加了一个基于浏览器自动化的链接解析工具,专门处理 JavaScript 动态渲染的页面——爬虫只能拿到空白 HTML,浏览器工具能拿到渲染后的完整内容。

05、结构化输出

大模型返回的内容是无结构的,那怎么让大模型按照我们定义的格式返回呢?

Spring AI 提供了一套结构化输出方案。

我们定义好岗位信息的数据结构(公司名称、公司类型、招聘岗位、薪资范围等 20 多个字段),每个字段加上描述和示例。系统自动把这个结构转成 JSON Schema,在调大模型时附加到 Prompt 里,告诉大模型“请按这个格式返回”。

图片.png

大模型返回 JSON 文本后,系统解析成 Java 对象列表。配合上一步的多轮对话,即使大模型分多次返回,中间也能实时提取出已经完整的 JSON 对象,不用等全部返回完才开始解析。

这一步的关键在于字段描述要足够具体。

比如“公司类型”这个字段,描述里列出了“国企、事业单位、学校、银行、私企”等选项,大模型就会从这些选项里选,而不是自由发挥。描述写得越精确,大模型返回的结构化数据质量就越高。

06、大模型切换

用户可以在管理后台配置不同的模型供应商和对应的 API Key,系统按配置实例化对应的模型。

模型配置分两层:一层是早期采集链路里的模型枚举,比如智谱、讯飞星火、DeepSeek、OpenAI、阿里百炼;另一层是后台统一的 provider#modelName 配置,可以把不同供应商和模型挂到同一套模型路由里。

后续这块还会继续升级到 IM 命令层:用户输入 /model 时,系统展示当前已经配置过的所有模型;输入 /model xxx 时,切换当前会话或当前用户偏好的模型。这样模型切换不只停留在后台配置页,也能在对话里直接完成。

图片.png

模型路由在采集场景里有这样一个用途:文本类数据用便宜的模型就够了,但图片类数据需要视觉能力更强的模型。

系统会根据输入类型(文本还是图片)自动选择对应的模型,不需要用户手动切换。

07、任务池调度

用户提交的采集任务先存到数据库,状态标记为“待处理”,页面立刻返回“已提交”。

后台有一个定时调度器,每 5 分钟触发一次。触发后会先抢占单线程执行锁,拿到锁之后按创建时间顺序拉取待处理任务执行,直到队列里暂时没有待处理任务。

任务在数据库里有完整的状态流转:初始化 → 处理中 → 成功/失败。执行失败的任务可以重置状态然后重新排队。

为什么要加这把锁?

如果是免费模型,会有并发限制,同时跑多个请求容易被限流。我们的做法是,给调度入口加一把原子锁,保证同一时间只有一个调度器在处理采集队列。

图片.png

普通任务池链路主要负责“按类型分发输入 → 调模型采集 → 保存草稿 → 记录任务结果”。它适合后台异步批处理,核心目标是把用户提交的数据源稳定地转成岗位草稿。

如果从 Agent 链路发起任务,执行过程会走一个 4 步工作流(基于 LangGraph4J):

  1. 任务分类:自动识别输入格式(文本、HTML、链接、CSV、Excel、图片)

  2. 数据采集:根据格式调用对应的处理方式提取岗位信息

  3. 数据清洗:自动规范化公司类型、招聘类型、目标人群等字段,比如“25届”统一转成“2025年”,秋招自动补全毕业年份,公司类型按字典映射到标准分类

  4. 数据发布:把清洗后的草稿数据正式发布到岗位信息主表

每一步都有条件判断:格式识别失败就终止,采集结果为空就终止,清洗后没有合格数据也终止。

这条 Agent 链路会通过 SSE 向前端推送每一步的执行状态,管理后台能实时看到当前在哪一步、处理结果是什么。

08、完整架构一览

图片.png

最底层是任务池和调度器,往上是大模型调用层(模型路由 + 多模型供应商),再往上是能力层(多模态、多轮对话、工具调用、结构化输出)。业务层分成两条执行路径:普通任务池链路负责异步采集并保存草稿;Agent 工作流链路负责分类 → 采集 → 清洗 → 发布。

本篇记录的是求职派早期围绕“岗位信息采集展示”的应用架构演进,可用于理解职位数据、任务池和后台业务。技术派阅读: https://paicoding.com/jobclaw-architecture-decomposition

求职派的核心能力是岗位信息采集。

图片.png

用户提交数据源——可能是一段文本、一个网页链接、一份 Excel 文件,甚至一张截图——系统调大模型把里面的岗位信息提取出来,清洗、校验、入库。

01、集成 AI 能力

求职派本质上是一个 Spring Web 应用,去掉 AI 能力也能跑。

但,如果没有AI,谁学啊,是吧,哈哈

AI 在求职派里的定位是数据采集引擎。

用户提交需要采集的目标数据源,通过系统内部维护的系统提示词,调用大模型提取岗位信息,然后把结构化的结果存入数据库。

图片.png

对外是“提交任务 → 得到结果”,对内是“构造 Prompt → 调大模型 → 解析返回”。

基于 Spring AI 接入大模型,是整个架构的第一趴。后面所有的能力——多模态、多轮对话、工具调用——都是在这个基础上一层层往上添加的。

02、多模态支持

采集的数据源形式很多。有纯文本、有 HTML 网页、有 CSV 和 Excel 文件,还有图片格式(比如用户截了一张招聘信息的图)。

纯文本调文本模型没问题,图片就需要视觉模型了。

图片.png

Spring AI 本身支持多模态输入,可以把图片作为二进制数据和文字提示一起发给大模型。系统检测到输入是图片格式时,会构建一个包含图片数据和提取指令的复合消息,发给支持视觉能力的模型处理。

并不是所有模型都能处理图片,有些模型是文本和视觉分开的。所以系统在检测到输入是图片时,会自动切换到支持视觉能力的模型。

03、多轮对话

采集任务的信息要素比较多时(一份 Excel 可能有几十/几千条岗位信息),大模型不一定能一次把所有的数据完整返回。

要知道,大模型本身是无状态的,要它记住之前的对话内容,就得把之前的对话上下文一起发过去。求职派基于 Spring AI 的聊天记忆机制实现了这个能力:每轮对话的消息存在内存里,下一轮自动带上历史消息。

图片.png

具体的处理逻辑是:大模型返回结果后,系统检查返回内容是否完整(比如 JSON 数组有没有闭合)。如果不完整,自动发一条“你之前返回的结果不完整,继续返回剩余的内容”,带上历史上下文让大模型接着生成。

中间提取到的完整 JSON 对象会先暂存在本轮采集结果里。即使后面某一轮出错,系统也会保留已经提取出来的对象,并在这次采集结束时统一落到草稿库。

04、使用 Function Calling

免费模型的能力有局限。

比如给它一个 HTTP 链接,让它抓取对应网页的内容来解析——这事它就做不了,因为大模型本身没有访问网络的能力。

解决办法是 Function Calling。

图片.png

我们给大模型注册了一个网页抓取工具:输入一个 URL,用爬虫抓取网页内容,解析出表格数据返回给大模型。大模型判断用户提交的是链接时,就自己调这个工具拿到网页内容,再从中提取岗位信息。

工具注册走的是 Spring AI 的标准方式。大模型在调用时能看到所有已注册工具的描述,自己判断要不要调用。比如用户提交的是一段纯文本,大模型就不会调爬虫工具,直接从文本里提取信息。提交的是链接,大模型就先调爬虫拿到网页内容,再做提取。

除了爬虫工具,系统还支持通过 MCP 接入外部工具服务器。比如后来加了一个基于浏览器自动化的链接解析工具,专门处理 JavaScript 动态渲染的页面——爬虫只能拿到空白 HTML,浏览器工具能拿到渲染后的完整内容。

05、结构化输出

大模型返回的内容是无结构的,那怎么让大模型按照我们定义的格式返回呢?

Spring AI 提供了一套结构化输出方案。

我们定义好岗位信息的数据结构(公司名称、公司类型、招聘岗位、薪资范围等 20 多个字段),每个字段加上描述和示例。系统自动把这个结构转成 JSON Schema,在调大模型时附加到 Prompt 里,告诉大模型“请按这个格式返回”。

图片.png

大模型返回 JSON 文本后,系统解析成 Java 对象列表。配合上一步的多轮对话,即使大模型分多次返回,中间也能实时提取出已经完整的 JSON 对象,不用等全部返回完才开始解析。

这一步的关键在于字段描述要足够具体。

比如“公司类型”这个字段,描述里列出了“国企、事业单位、学校、银行、私企”等选项,大模型就会从这些选项里选,而不是自由发挥。描述写得越精确,大模型返回的结构化数据质量就越高。

06、大模型切换

用户可以在管理后台配置不同的模型供应商和对应的 API Key,系统按配置实例化对应的模型。

模型配置分两层:一层是早期采集链路里的模型枚举,比如智谱、讯飞星火、DeepSeek、OpenAI、阿里百炼;另一层是后台统一的 provider#modelName 配置,可以把不同供应商和模型挂到同一套模型路由里。

后续这块还会继续升级到 IM 命令层:用户输入 /model 时,系统展示当前已经配置过的所有模型;输入 /model xxx 时,切换当前会话或当前用户偏好的模型。这样模型切换不只停留在后台配置页,也能在对话里直接完成。

图片.png

模型路由在采集场景里有这样一个用途:文本类数据用便宜的模型就够了,但图片类数据需要视觉能力更强的模型。

系统会根据输入类型(文本还是图片)自动选择对应的模型,不需要用户手动切换。

07、任务池调度

用户提交的采集任务先存到数据库,状态标记为“待处理”,页面立刻返回“已提交”。

后台有一个定时调度器,每 5 分钟触发一次。触发后会先抢占单线程执行锁,拿到锁之后按创建时间顺序拉取待处理任务执行,直到队列里暂时没有待处理任务。

任务在数据库里有完整的状态流转:初始化 → 处理中 → 成功/失败。执行失败的任务可以重置状态然后重新排队。

为什么要加这把锁?

如果是免费模型,会有并发限制,同时跑多个请求容易被限流。我们的做法是,给调度入口加一把原子锁,保证同一时间只有一个调度器在处理采集队列。

图片.png

普通任务池链路主要负责“按类型分发输入 → 调模型采集 → 保存草稿 → 记录任务结果”。它适合后台异步批处理,核心目标是把用户提交的数据源稳定地转成岗位草稿。

如果从 Agent 链路发起任务,执行过程会走一个 4 步工作流(基于 LangGraph4J):

  1. 任务分类:自动识别输入格式(文本、HTML、链接、CSV、Excel、图片)

  2. 数据采集:根据格式调用对应的处理方式提取岗位信息

  3. 数据清洗:自动规范化公司类型、招聘类型、目标人群等字段,比如“25届”统一转成“2025年”,秋招自动补全毕业年份,公司类型按字典映射到标准分类

  4. 数据发布:把清洗后的草稿数据正式发布到岗位信息主表

每一步都有条件判断:格式识别失败就终止,采集结果为空就终止,清洗后没有合格数据也终止。

这条 Agent 链路会通过 SSE 向前端推送每一步的执行状态,管理后台能实时看到当前在哪一步、处理结果是什么。

08、完整架构一览

图片.png

最底层是任务池和调度器,往上是大模型调用层(模型路由 + 多模型供应商),再往上是能力层(多模态、多轮对话、工具调用、结构化输出)。业务层分成两条执行路径:普通任务池链路负责异步采集并保存草稿;Agent 工作流链路负责分类 → 采集 → 清洗 → 发布。

本篇记录的是求职派早期围绕“岗位信息采集展示”的应用架构演进,可用于理解职位数据、任务池和后台业务。技术派阅读: https://paicoding.com/jobclaw-architecture-decomposition

求职派的核心能力是岗位信息采集。

图片.png

用户提交数据源——可能是一段文本、一个网页链接、一份 Excel 文件,甚至一张截图——系统调大模型把里面的岗位信息提取出来,清洗、校验、入库。

01、集成 AI 能力

求职派本质上是一个 Spring Web 应用,去掉 AI 能力也能跑。

但,如果没有AI,谁学啊,是吧,哈哈

AI 在求职派里的定位是数据采集引擎。

用户提交需要采集的目标数据源,通过系统内部维护的系统提示词,调用大模型提取岗位信息,然后把结构化的结果存入数据库。

图片.png

对外是“提交任务 → 得到结果”,对内是“构造 Prompt → 调大模型 → 解析返回”。

基于 Spring AI 接入大模型,是整个架构的第一趴。后面所有的能力——多模态、多轮对话、工具调用——都是在这个基础上一层层往上添加的。

02、多模态支持

采集的数据源形式很多。有纯文本、有 HTML 网页、有 CSV 和 Excel 文件,还有图片格式(比如用户截了一张招聘信息的图)。

纯文本调文本模型没问题,图片就需要视觉模型了。

图片.png

Spring AI 本身支持多模态输入,可以把图片作为二进制数据和文字提示一起发给大模型。系统检测到输入是图片格式时,会构建一个包含图片数据和提取指令的复合消息,发给支持视觉能力的模型处理。

并不是所有模型都能处理图片,有些模型是文本和视觉分开的。所以系统在检测到输入是图片时,会自动切换到支持视觉能力的模型。

03、多轮对话

采集任务的信息要素比较多时(一份 Excel 可能有几十/几千条岗位信息),大模型不一定能一次把所有的数据完整返回。

要知道,大模型本身是无状态的,要它记住之前的对话内容,就得把之前的对话上下文一起发过去。求职派基于 Spring AI 的聊天记忆机制实现了这个能力:每轮对话的消息存在内存里,下一轮自动带上历史消息。

图片.png

具体的处理逻辑是:大模型返回结果后,系统检查返回内容是否完整(比如 JSON 数组有没有闭合)。如果不完整,自动发一条“你之前返回的结果不完整,继续返回剩余的内容”,带上历史上下文让大模型接着生成。

中间提取到的完整 JSON 对象会先暂存在本轮采集结果里。即使后面某一轮出错,系统也会保留已经提取出来的对象,并在这次采集结束时统一落到草稿库。

04、使用 Function Calling

免费模型的能力有局限。

比如给它一个 HTTP 链接,让它抓取对应网页的内容来解析——这事它就做不了,因为大模型本身没有访问网络的能力。

解决办法是 Function Calling。

图片.png

我们给大模型注册了一个网页抓取工具:输入一个 URL,用爬虫抓取网页内容,解析出表格数据返回给大模型。大模型判断用户提交的是链接时,就自己调这个工具拿到网页内容,再从中提取岗位信息。

工具注册走的是 Spring AI 的标准方式。大模型在调用时能看到所有已注册工具的描述,自己判断要不要调用。比如用户提交的是一段纯文本,大模型就不会调爬虫工具,直接从文本里提取信息。提交的是链接,大模型就先调爬虫拿到网页内容,再做提取。

除了爬虫工具,系统还支持通过 MCP 接入外部工具服务器。比如后来加了一个基于浏览器自动化的链接解析工具,专门处理 JavaScript 动态渲染的页面——爬虫只能拿到空白 HTML,浏览器工具能拿到渲染后的完整内容。

05、结构化输出

大模型返回的内容是无结构的,那怎么让大模型按照我们定义的格式返回呢?

Spring AI 提供了一套结构化输出方案。

我们定义好岗位信息的数据结构(公司名称、公司类型、招聘岗位、薪资范围等 20 多个字段),每个字段加上描述和示例。系统自动把这个结构转成 JSON Schema,在调大模型时附加到 Prompt 里,告诉大模型“请按这个格式返回”。

图片.png

大模型返回 JSON 文本后,系统解析成 Java 对象列表。配合上一步的多轮对话,即使大模型分多次返回,中间也能实时提取出已经完整的 JSON 对象,不用等全部返回完才开始解析。

这一步的关键在于字段描述要足够具体。

比如“公司类型”这个字段,描述里列出了“国企、事业单位、学校、银行、私企”等选项,大模型就会从这些选项里选,而不是自由发挥。描述写得越精确,大模型返回的结构化数据质量就越高。

06、大模型切换

用户可以在管理后台配置不同的模型供应商和对应的 API Key,系统按配置实例化对应的模型。

模型配置分两层:一层是早期采集链路里的模型枚举,比如智谱、讯飞星火、DeepSeek、OpenAI、阿里百炼;另一层是后台统一的 provider#modelName 配置,可以把不同供应商和模型挂到同一套模型路由里。

后续这块还会继续升级到 IM 命令层:用户输入 /model 时,系统展示当前已经配置过的所有模型;输入 /model xxx 时,切换当前会话或当前用户偏好的模型。这样模型切换不只停留在后台配置页,也能在对话里直接完成。

图片.png

模型路由在采集场景里有这样一个用途:文本类数据用便宜的模型就够了,但图片类数据需要视觉能力更强的模型。

系统会根据输入类型(文本还是图片)自动选择对应的模型,不需要用户手动切换。

07、任务池调度

用户提交的采集任务先存到数据库,状态标记为“待处理”,页面立刻返回“已提交”。

后台有一个定时调度器,每 5 分钟触发一次。触发后会先抢占单线程执行锁,拿到锁之后按创建时间顺序拉取待处理任务执行,直到队列里暂时没有待处理任务。

任务在数据库里有完整的状态流转:初始化 → 处理中 → 成功/失败。执行失败的任务可以重置状态然后重新排队。

为什么要加这把锁?

如果是免费模型,会有并发限制,同时跑多个请求容易被限流。我们的做法是,给调度入口加一把原子锁,保证同一时间只有一个调度器在处理采集队列。

图片.png

普通任务池链路主要负责“按类型分发输入 → 调模型采集 → 保存草稿 → 记录任务结果”。它适合后台异步批处理,核心目标是把用户提交的数据源稳定地转成岗位草稿。

如果从 Agent 链路发起任务,执行过程会走一个 4 步工作流(基于 LangGraph4J):

  1. 任务分类:自动识别输入格式(文本、HTML、链接、CSV、Excel、图片)

  2. 数据采集:根据格式调用对应的处理方式提取岗位信息

  3. 数据清洗:自动规范化公司类型、招聘类型、目标人群等字段,比如“25届”统一转成“2025年”,秋招自动补全毕业年份,公司类型按字典映射到标准分类

  4. 数据发布:把清洗后的草稿数据正式发布到岗位信息主表

每一步都有条件判断:格式识别失败就终止,采集结果为空就终止,清洗后没有合格数据也终止。

这条 Agent 链路会通过 SSE 向前端推送每一步的执行状态,管理后台能实时看到当前在哪一步、处理结果是什么。

08、完整架构一览

图片.png

最底层是任务池和调度器,往上是大模型调用层(模型路由 + 多模型供应商),再往上是能力层(多模态、多轮对话、工具调用、结构化输出)。业务层分成两条执行路径:普通任务池链路负责异步采集并保存草稿;Agent 工作流链路负责分类 → 采集 → 清洗 → 发布。