【AI全栈后端12-04】Spring Boot 用结构化输出自动解析简历:让模型按你的 POJO 输出
本文是「Spring Boot AI 全栈后端」系列第 04 篇。第 03 篇解决了「贵的模型被便宜问题白烧」的成本问题这一篇换个方向模型明明读懂了后端却还是用不了它返回的东西。示例基于 Spring AI 2.0 / Boot 4.1全部已通过测试。一个让后端很憋屈的场景V哥 带学员做招聘系统实训时总会先让他们去看一眼 HR 的日常工作收到一封简历PDF、Word、甚至邮件正文人工把姓名、手机号、学历、工作年限、技能关键词、最近职位一个一个敲进表单再点保存。一份简历平均两三分钟一天几十份眼睛看花了就串行、漏字段录进去的脏数据后面还要返工清洗。你肯定第一时间想到让 AI 来读。于是你按第 02 篇的方式接了个对话接口把简历文本丢给模型问它「请提取姓名、手机号、学历、工作年限、技能」。模型回了一大段这位候选人叫张伟联系电话是 13800138000邮箱 zhangweiexample.com。 他本科学历有 6 年左右的 Java 后端开发经验比较熟悉的技术包括 Java、Spring Boot 和 MySQL…… 最近一份工作是某电商公司的高级后端工程师。读得准不准准。但这段东西后端根本没法直接入库——它是一段自然语言不是一条记录。于是现实里通常出现三种土办法每一种都埋雷写正则 / 字符串截取从文本里抠「姓名」后面的内容。模型今天写「姓名张伟」明天写「这位候选人叫张伟」正则直接失效在提示词里喊「请返回 JSON」模型确实返回 JSON 了但字段名一会儿name、一会儿姓名yearsOfExperience有时是数字 6、有时是字符串 “6 年”反序列化照样炸让模型输出后再调一次模型「请把上面的内容转成 JSON」多一轮调用多一份钱多一次出错机会。问题到这里就清楚了不是模型不会读简历是它的输出没有「形状」约束下游没法稳定消费。V哥 做后端二十多年这条规律反复被验证凡是靠「约定」而不是靠「约束」的接口早晚要在脏数据上栽跟头。我们要解决的就是「让模型的输出必须长成我定义的样子」这件事。解决思路给它一份 JSON Schema让它照着填结构化输出Structured Output的思路很朴素你先把想要的字段定义成一个 Java 类POJO框架拿着这个类自动生成一份 JSON Schema把 schema 塞进提示词里告诉模型「你必须按这个格式输出」拿到模型回复后再自动反序列化成 POJO。对比一下就明白它好在哪做法形状约束字段类型维护成本提示词里喊「返回 JSON」靠模型自觉靠运气数字可能变字符串每次改字段都要改提示词 改解析手写正则无全是字符串模型换个说法就崩结构化输出schema 约束框架强制按 POJO 类型保证只改 POJO其它全自动化Spring AI 里这件事只需要一个.entity(ResumeFields.class)。下面按「定义字段 → 服务调用 → 脏数据防线 → 接口暴露」四步落地。第一步把字段定义成 POJO注释写给模型看先定义简历要抽哪些字段。这里最关键的一行不是字段本身而是JsonPropertyDescription——它不是给程序员看的注释是写给模型看的字段说明书会被 Spring AI 的JsonSchemaGenerator读走变成 JSON Schema 里的description。publicrecordResumeFields(JsonPropertyDescription(候选人姓名纯中文人名不要带称谓)Stringname,JsonPropertyDescription(手机号11 位数字没有就返回空字符串)Stringphone,JsonPropertyDescription(邮箱地址没有就返回空字符串)Stringemail,JsonPropertyDescription(最高学历取值只能是大专/本科/硕士/博士/其他)Stringeducation,JsonPropertyDescription(工作年限整数不足一年按 0)IntegeryearsOfExperience,JsonPropertyDescription(技能关键词列表最多 10 个每个不超过 8 个字)ListStringskills,JsonPropertyDescription(最近一段工作经历的职位名称)StringlatestTitle,JsonPropertyDescription(一句话总结候选人不超过 40 字)Stringsummary){/** 关键字段缺失时打人工复核标记别让脏数据直接落库。 */publicbooleanneedsReview(){returnisBlank(name)||isBlank(latestTitle)||yearsOfExperiencenull||yearsOfExperience0;}privatestaticbooleanisBlank(Strings){returnsnull||s.trim().isEmpty();}}写这份 POJO 有三条经验是 V哥 在项目里一条条试出来的取值要收敛学历不要写「学历」写「取值只能是大专/本科/硕士/博士/其他」。模型的自由度给得越大它给你的花样越多缺失要约定找不到就「字符串填空串、数字填 0、列表填空数组」不要让模型自己编——编出来的手机号比空值危险一百倍列表要给边界「最多 10 个每个不超过 8 个字」否则技能列表能被它写成 30 个同义词。本篇的测试里专门断言了 schema 的生成结果converter.getFormat()里既包含字段yearsOfExperience也包含我写的中文说明「工作年限」——说明这份说明书确实送到了模型面前。第二步服务里一行 entity()三步活全包了有了 POJO服务层异常简单ServicepublicclassResumeParserService{privatestaticfinalStringINSTRUCTION 你是招聘系统的简历解析助手。请从下面的简历文本中抽取字段严格按 JSON Schema 输出。 规则 1. 只输出 JSON不要任何解释、不要 Markdown 代码块 2. 文本里找不到的字段字符串填空串、数字填 0、列表填空数组不要自己编造 3. 工作年限按「截止到今天」折算成整数。 简历文本 {resume} ;privatefinalChatClientchatClient;publicResumeParserService(ChatModelchatModel){this.chatClientChatClient.create(chatModel);}publicResumeFieldsparse(StringresumeText){try{returnchatClient.prompt().user(u-u.text(INSTRUCTION).param(resume,resumeText)).call().entity(ResumeFields.class);}catch(Exceptionex){thrownewResumeParseException(模型输出无法解析为简历字段请转人工复核,ex);}}}别看只有一行.entity(ResumeFields.class)它背后替你干了三步生成 schema拿着ResumeFields生成 JSON Schema就是BeanOutputConverter干的事注入提示词把 schema 和一段格式指令拼到你的提示词后面。这段指令是框架内置的大意是「只提供符合 RFC8259 的 JSON 响应、不要解释、不要 Markdown 代码块输出必须遵循下面的 JSON Schema」反序列化模型返回文本后自动剥掉可能的 Markdown 围栏、转成ResumeFields对象。想自己掌控这三步比如想把 schema 打进日志排查、或是在流式场景里手动收尾可以手动用BeanOutputConverterBeanOutputConverterResumeFieldsconverternewBeanOutputConverter(ResumeFields.class);Stringformatconverter.getFormat();// 拿到带 schema 的格式指令可注入任意提示词ResumeFieldsfieldsconverter.convert(jsonText);// 手动反序列化日常用entity()需要定制提示词或排查 schema 时用BeanOutputConverter——两者是同一套机制的两层封装不是两套东西。第三步别信模型留两道防线结构化输出把「大概率对」变成了「基本可信」但 AI 应用必须有兜底这一节是本篇最该抄走的部分。防线一输出不干净也能救回来。模型经常把 JSON 用json代码块包起来甚至带点「好的以下是结果」的前缀。Spring AI 内置了输出清理器本篇测试专门模拟了这种情况——桩模型返回被 Markdown 围栏包裹的 JSONentity()照样解析出正确字段。所以别自己写正则去剥围栏框架已经替你干了。防线二字段缺失要打复核标记不要直接落库。模型说「简历里没写手机号」时正确做法不是把空值塞进数据库而是标记这条数据需要人工看一眼publicbooleanneedsReview(){returnisBlank(name)||isBlank(latestTitle)||yearsOfExperiencenull||yearsOfExperience0;}防线三彻底解析不了要有明确出口。模型偶尔会抽风返回「抱歉我无法完成这个请求」。这时候不能让异常变成一堆堆栈甩给用户包一层业务异常再在全局异常处理器里转成能直接展示的响应/** 模型输出不合 schema不是服务挂了而是这条数据需要转人工返回 422。 */ExceptionHandler(ResumeParseException.class)publicResponseEntityMapString,StringhandleParseFailure(ResumeParseExceptionex){returnResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(Map.of(error,ex.getMessage(),review,true));}注意这里返回的是422不可处理的实体而不是 500服务没挂是这条数据不合格。前端拿到review true直接把这条简历推进「人工复核」队列用户体验和反悔成本都最小。第四步接口暴露控制器不碰模型RestControllerRequestMapping(/api/resume)publicclassResumeParseController{privatefinalResumeParserServiceparserService;publicResumeParseController(ResumeParserServiceparserService){this.parserServiceparserService;}PostMapping(/parse)publicResponseEntityResumeParseResultparse(ValidRequestBodyResumeParseRequestrequest){ResumeFieldsfieldsparserService.parse(request.resumeText());returnResponseEntity.ok(newResumeParseResult(fields,fields.needsReview()));}}ResumeParseResult只是把「结构化结果 是否要复核」打包出去publicrecordResumeParseResult(ResumeFieldsdata,booleanreview){}控制器从头到尾没出现任何模型 API——模型在哪、用的哪档、schema 长什么样它一概不知。这就是第 03 篇说的「业务只认抽象」在结构化场景里的延续。怎么验证离线也能跑通这套逻辑外部 LLM 要密钥要联网但要验证的是「schema 生成对不对、反序列化稳不稳、兜底逻辑好不好使」用一个桩模型返回固定 JSON 就够了重点验证这几件事测试验证什么parse_returnsStructuredFields发真实 HTTP断言姓名/电话/学历/年限/技能/职位逐字段正确reviewfalseschemaIsGeneratedFromFieldDescriptionsgetFormat()含「JSON Schema」、字段名和中文说明证明说明书真的送到了模型markdownWrappedJson_isStillParsed桩返回被 Markdown 围栏包裹的 JSON照样解析成功manualConverterMode_producesSameFields手动BeanOutputConverter.convert()与entity()结果一致missingKeyFields_markedForManualReview关键字段为空 →needsReview()为 trueunparsableOutput_throwsResumeParseException模型返回非 JSON → 抛业务异常→ 422blankResumeText_returnsBadRequest简历文本为空 → 400 校验拦截全程不需要任何真实密钥也不依赖网络。落地要点schema 别贪多一次抽 20 个字段模型的注意力被摊薄每个字段的准确率都会掉。真要抽 20 个拆成两次调用一次抽「基本信息」、一次抽「项目经历」不确定就给「未知」与其让模型猜不如约定一个明确的兜底值下游逻辑才好判断schema 本身吃 token字段越多、说明越长每次调用都要多付这笔钱。说明写到「够约束」就停别写小作文长简历先截断超过模型上下文的简历要先切块或抽关键段落别整本丢进去否则又贵又慢合规要前置简历是个人敏感信息调用外部模型前确认对方的数据留存策略必要时走私有化部署或脱敏姓名、手机号先打码再解析。V哥 给企业做方案时这条永远是评审会上第一个要过的关。最后一句别再拿正则去抠模型说的话——把字段定义成 POJO让 schema 逼着模型按你的形状输出再留一道「解析不了就转人工」的出口AI 才算真正接进了你的业务系统而不是停在演示里。下一篇05V哥 带你解决更尴尬的一种情况模型答不上实时数据让它自己调你的 Java 方法去查订单和库存。

相关新闻

数据库课程设计:用户登录系统从建表到前后端联调的完整落地指南

数据库课程设计:用户登录系统从建表到前后端联调的完整落地指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 2:54:16 阅读更多 →
Claude Code与Pi agent:从安装到迁移的全方位对比

Claude Code与Pi agent:从安装到迁移的全方位对比

最近编程群里问得最多的问题,已经不再是“你怎么还在用手敲代码”,而是“你还在用 Claude Code 吗?”紧接着就会有人补一句:“我换成 Pi agent 了,回不去了。”说实话,我第一次看到这种说法时也有点意外&am…

2026/10/1 2:54:16 阅读更多 →
基于Python的智能客服系统开发:从理论到实践的全面指南

基于Python的智能客服系统开发:从理论到实践的全面指南

所谓智能客服, 这篇文章对其背后的技术实现路径以及系统的优化方法进行了全面的解析, 并且开篇就先讨论了有关智能客服所构成的技术生态圈, 同时也剖析了它所具备的核心价值所在。它凭借着非常丰富的人工智能生态库(这里比如NLTK, spaCy等), 加上它的开发…

2026/10/1 2:54:16 阅读更多 →

最新新闻

VMware+CentOS7虚拟机安装与Xshell远程连接配置指南

VMware+CentOS7虚拟机安装与Xshell远程连接配置指南

本地想练 Linux、跑个服务、学运维命令,手边只有一台 Windows 电脑,最省事的做法就是装一台 CentOS7 虚拟机,再用 Xshell 从宿主机连上去敲命令。这套组合我前后折腾过十几台机器,从早期的 VMware 12 到现在的 Workstation 17&…

2026/10/1 4:14:53 阅读更多 →
AI皮肤检测API全链路实战:文件上传、异步任务与结果读取

AI皮肤检测API全链路实战:文件上传、异步任务与结果读取

皮肤检测类 API 这几年在美妆、医美、健康管理这几个圈子里被问得越来越多。我最早接触这类接口是在一个护肤品牌的小程序项目里,当时的需求很朴素:用户拍一张正脸照,后台返回肤质、毛孔、皱纹、色斑这些维度的评分。听起来简单,真…

2026/10/1 4:14:53 阅读更多 →
nps 内网穿透实战:npc 配置、隧道类型与排障指南

nps 内网穿透实战:npc 配置、隧道类型与排障指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 4:14:53 阅读更多 →
全模态AI与算力经济:技术落地的延迟、成本与决策闭环

全模态AI与算力经济:技术落地的延迟、成本与决策闭环

1. 这份“每日早报”不是新闻简报,而是一张技术演进的实时坐标图你点开手机里那个叫“每日早报”的推送,第一反应可能是——又一份信息过载的碎片合集。但如果你把标题里的两个短语拆开看:“AI全模态”和“全球央行同步加息”,会发…

2026/10/1 4:14:53 阅读更多 →
PDI CE 9.4.0.0-343 安装与生产级部署指南

PDI CE 9.4.0.0-343 安装与生产级部署指南

简介:本资源为Pentaho Data Integration(Kettle)社区版9.4.0正式发行包,面向ETL开发工程师、数据集成初学者及BI项目实施人员,提供开箱即用的数据抽取、转换与加载工具链,适用于数据库迁移、日志清洗、报表…

2026/10/1 4:14:53 阅读更多 →
基于Simulink的风光储微电网下垂控制与并离网切换仿真解析

基于Simulink的风光储微电网下垂控制与并离网切换仿真解析

这几年我一直在和微电网仿真打交道,尤其围绕风光储微电网的下垂控制与并离网切换做了不少模型迭代。很多人一上来就问“并离网切换到底怎么实现”“下垂系数怎么给”,其实这些问题靠一套搭得规整的 MATLAB/Simulink 模型就能回答大半。今天我就把这套模型…

2026/10/1 4:13:53 阅读更多 →

日新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/30 13:14:22 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/30 18:13:06 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/9/30 13:14:49 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →