一、版本适配说明(SpringAI 1.1.x)
SpringAI 1.1.x 对结构化解析API进行核心重构,废弃旧版 BeanOutputParser,统一采用 StructuredOutputParser 作为唯一标准解析方案。本文档所有代码完全适配1.1.x正式版,无过期API、无兼容Bug,可直接用于生产与面试。
核心版本变更点
-
移除废弃解析类,统一使用泛型结构化解析器
-
修复旧版空指针、格式适配异常问题
-
适配新版链式Prompt、ChatClient调用规范
-
支持自动JSON Schema生成、严格/宽松双解析模式
必备Maven核心依赖
<!-- SpringAI1.1.x核心依赖 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-core</artifactId>
<version>1.1.0</version>
</dependency>
<!-- 结构化解析专属依赖(1.1.x必选) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-structure-output-parser</artifactId>
<version>1.1.0</version>
</dependency>
<!-- PDF/Word文档解析依赖 -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.32</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
二、核心目标与核心概念
2.1 实现目标
基于SpringAI1.1.x新版解析API,解决大模型自由文本输出混乱问题,实现合同/发票/简历三类业务文档的自动字段抽取、结构化格式化、异常降级兜底、合规数据入库,打造可直接上线的智能解析服务。
2.2 核心概念(白话释义)
-
StructuredOutputParser结构化解析器:约束大模型输出格式的核心工具,强制模型按自定义实体字段输出,过滤无效废话。
-
实体驱动Schema:无需手写JSON模板,通过Java实体类自动生成校验规则,简洁易维护。
-
双解析模式:1.1.x专属特性,生产严格校验、测试宽松兼容。
-
重试+纠错+校验三层防护:解决模型漏字段、输出脏数据、瞬时异常问题。
-
Fallback降级兜底:解析失败不抛异常、不崩服务,返回标准空结构保障服务稳定。
三、整体业务流程
文档上传 → 文件合规校验 → 文本抽取清洗 → 超长文本截断防超限 → AI结构化解析(可重试) → 字段缺失二次纠错 → 数据清洗+业务校验 → 结构化入库 → 临时文件资源释放 → 全局异常降级兜底
四、三大业务场景定义
统一适配三类高频办公文档解析,包含正常输出示例与异常降级兜底结构。
4.1 合同文档解析
抽取字段:合同编号、甲方名称、乙方名称、合同金额、签订日期、有效期
正常输出
{
"contractNo": "HT20260804001",
"partyA": "XX科技有限公司",
"partyB": "XX商贸有限公司",
"amount": "58000.00",
"signDate": "2026-08-04",
"validPeriod": "1年"
}
降级兜底输出
{
"contractNo": "",
"partyA": "",
"partyB": "",
"amount": "",
"signDate": "",
"validPeriod": ""
}
4.2 发票文档解析
抽取字段:发票代码、发票号码、开票日期、价税合计、销售方名称、购买方名称
正常输出
{
"invoiceCode": "110026100111",
"invoiceNo": "89654210",
"invoiceDate": "2026-07-20",
"totalAmount": "1280.50",
"sellerName": "XX电子科技公司",
"buyerName": "XX信息技术有限公司"
}
降级兜底输出
{
"invoiceCode": "",
"invoiceNo": "",
"invoiceDate": "",
"totalAmount": "",
"sellerName": "",
"buyerName": ""
}
4.3 简历文档解析
抽取字段:姓名、手机号、邮箱、学历、工作年限、求职意向
正常输出
{
"userName": "张三",
"phone": "13800138000",
"email": "zhangsan@163.com",
"education": "本科",
"workYear": "3年",
"jobIntention": "Java开发工程师"
}
降级兜底输出
{
"userName": "",
"phone": "",
"email": "",
"education": "",
"workYear": "",
"jobIntention": ""
}
五、结构化解析实体类(1.1.x纯原生)
1.1.x无需AI专属注解,纯Lombok实体类自动生成JSON Schema,适配新版解析器。
5.1 合同解析实体
import lombok.Data;
@Data
public class ContractParseDTO {
// 合同编号
private String contractNo;
// 甲方名称
private String partyA;
// 乙方名称
private String partyB;
// 合同金额
private String amount;
// 签订日期
private String signDate;
// 有效期
private String validPeriod;
}
5.2 发票解析实体
import lombok.Data;
@Data
public class InvoiceParseDTO {
// 发票代码
private String invoiceCode;
// 发票号码
private String invoiceNo;
// 开票日期
private String invoiceDate;
// 价税合计
private String totalAmount;
// 销售方
private String sellerName;
// 购买方
private String buyerName;
}
5.3 简历解析实体
import lombok.Data;
@Data
public class ResumeParseDTO {
// 姓名
private String userName;
// 手机号
private String phone;
// 邮箱
private String email;
// 学历
private String education;
// 工作年限
private String workYear;
// 求职意向
private String jobIntention;
}
六、生产级通用工具类
6.1 降级兜底工具类(Fallback)
统一各类文档解析异常兜底逻辑,返回标准空结构,保障服务高可用。
import com.alibaba.fastjson2.JSONObject;
public class ParserFallbackUtil {
public static JSONObject getFallbackData(String docType) {
JSONObject fallback = new JSONObject();
switch (docType) {
case "contract":
fallback.put("contractNo", "");
fallback.put("partyA", "");
fallback.put("partyB", "");
fallback.put("amount", "");
fallback.put("signDate", "");
fallback.put("validPeriod", "");
break;
case "invoice":
fallback.put("invoiceCode", "");
fallback.put("invoiceNo", "");
fallback.put("invoiceDate", "");
fallback.put("totalAmount", "");
fallback.put("sellerName", "");
fallback.put("buyerName", "");
break;
case "resume":
fallback.put("userName", "");
fallback.put("phone", "");
fallback.put("email", "");
fallback.put("education", "");
fallback.put("workYear", "");
fallback.put("jobIntention", "");
break;
default:
return new JSONObject();
}
return fallback;
}
}
6.2 数据清洗工具类
清洗模型输出的空格、换行、特殊符号、无效备注,规整入库数据。
import org.springframework.util.StringUtils;
public class ParseDataCleanUtil {
// 通用字段清洗
public static String cleanField(String field) {
if (!StringUtils.hasText(field)) {
return "";
}
field = field.trim().replaceAll("[\r\n\t]", "");
field = field.replaceAll("【.*?】", "").replaceAll("#|//.*", "");
return field;
}
// 日期字段统一规整
public static String cleanDate(String date) {
String clean = cleanField(date);
if (clean.length() > 10) {
return clean.substring(0, 10);
}
return clean;
}
}
6.3 字段合规校验工具类
第二层数据拦截,校验手机号、邮箱、日期、金额格式,杜绝脏数据入库。
import org.springframework.util.StringUtils;
import java.util.regex.Pattern;
public class ParseFieldVerifyUtil {
private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
private static final Pattern EMAIL_PATTERN = Pattern.compile("^\\w+@\\w+\\.\\w+$");
private static final Pattern DATE_PATTERN = Pattern.compile("^\\d{4}-\\d{2}-\\d{2}$");
private static final Pattern AMOUNT_PATTERN = Pattern.compile("^\\d+(\\.\\d{1,2})?$");
public static boolean verifyDate(String date) {
if (!StringUtils.hasText(date)) return false;
return DATE_PATTERN.matcher(date).matches();
}
public static boolean verifyAmount(String amount) {
if (!StringUtils.hasText(amount)) return false;
return AMOUNT_PATTERN.matcher(amount).matches();
}
public static boolean verifyPhone(String phone) {
if (!StringUtils.hasText(phone)) return false;
return PHONE_PATTERN.matcher(phone).matches();
}
public static boolean verifyEmail(String email) {
if (!StringUtils.hasText(email)) return false;
return EMAIL_PATTERN.matcher(email).matches();
}
}
6.4 长文本截断工具类(防Token超限)
读取配置参数,自动截断超长文档文本,解决大模型上下文溢出报错问题。
import org.springframework.stereotype.Component;
@Component
public class DocumentTextLimitUtil {
private final ParseProperties parseProperties;
public DocumentTextLimitUtil(ParseProperties parseProperties) {
this.parseProperties = parseProperties;
}
public String limitText(String content) {
if (content == null) return "";
int maxLen = parseProperties.getMaxContentLength();
if (content.length() > maxLen) {
return content.substring(0, maxLen);
}
return content;
}
}
6.5 后置统一处理工具类(清洗+校验闭环)
解析完成后统一执行数据规整与字段校验,实现入库最后一道防护。
public class ParsePostHandler {
public static <T> T postHandle(T entity, boolean autoClean, boolean strictCheck) {
if (entity == null) return null;
// 自动数据清洗
if (autoClean) cleanAllField(entity);
// 严格字段校验
if (strictCheck) verifyAllField(entity);
return entity;
}
// 全字符串字段清洗
private static <T> void cleanAllField(T entity) {
try {
java.lang.reflect.Field[] fields = entity.getClass().getDeclaredFields();
for (java.lang.reflect.Field field : fields) {
field.setAccessible(true);
Object val = field.get(entity);
if (val instanceof String) {
field.set(entity, ParseDataCleanUtil.cleanField((String) val));
}
}
} catch (Exception ignored) {}
}
// 业务字段合规校验
private static <T> void verifyAllField(T entity) {
if (entity instanceof InvoiceParseDTO invoice) {
if (!ParseFieldVerifyUtil.verifyAmount(invoice.getTotalAmount())) invoice.setTotalAmount("");
if (!ParseFieldVerifyUtil.verifyDate(invoice.getInvoiceDate())) invoice.setInvoiceDate("");
}
if (entity instanceof ResumeParseDTO resume) {
if (!ParseFieldVerifyUtil.verifyPhone(resume.getPhone())) resume.setPhone("");
if (!ParseFieldVerifyUtil.verifyEmail(resume.getEmail())) resume.setEmail("");
}
}
}
6.6 文档文件解析工具类(PDF/Word文本抽取)
支持文件格式、大小、空内容校验,自动抽取并清洗文档纯文本。
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
@Component
public class DocumentFileParseUtil {
private static final String[] SUPPORT_SUFFIX = {"pdf", "docx"};
private static final long MAX_FILE_SIZE = 20 * 1024 * 1024;
public String extractFileText(File file) throws IOException {
if (!file.exists() || !file.isFile() || file.length() > MAX_FILE_SIZE) return "";
String suffix = getFileSuffix(file.getName());
if (!isSupportSuffix(suffix)) return "";
String rawText = switch (suffix) {
case "pdf" -> parsePdf(file);
case "docx" -> parseDocx(file);
default -> "";
};
if (!StringUtils.hasText(rawText)) return "";
return cleanFileText(rawText);
}
private String parsePdf(File file) throws IOException {
try (PDDocument document = PDDocument.load(file)) {
return new PDFTextStripper().getText(document);
}
}
private String parseDocx(File file) throws IOException {
try (FileInputStream fis = new FileInputStream(file);
XWPFDocument document = new XWPFDocument(fis)) {
StringBuilder textSb = new StringBuilder();
List<XWPFParagraph> paragraphList = document.getParagraphs();
for (XWPFParagraph paragraph : paragraphList) {
String paraText = paragraph.getText().trim();
if (StringUtils.hasText(paraText)) textSb.append(paraText).append(" ");
}
return textSb.toString();
}
}
private String getFileSuffix(String fileName) {
return fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
}
private boolean isSupportSuffix(String suffix) {
for (String s : SUPPORT_SUFFIX) if (s.equals(suffix)) return true;
return false;
}
private String cleanFileText(String text) {
return text.replaceAll("\\s+", " ")
.replaceAll("[^\\u4e00-\\u9fa5a-zA-Z0-9\\u3002\\uff1b\\uff0c\\uff1f\\uff08\\uff09\\s]", "")
.trim();
}
}
七、核心解析业务类(1.1.x最终整合版)
7.1 通用重试解析核心类
整合配置热更新、严格/宽松双模式、文本截断、日志监控、可配置重试,完全消除硬编码。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.parser.StructuredOutputParser;
import org.springframework.stereotype.Component;
@Component
public class NativeDocumentParser {
private static final Logger log = LoggerFactory.getLogger(NativeDocumentParser.class);
private final ChatClient chatClient;
private final ParseProperties parseProperties;
private final DocumentTextLimitUtil textLimitUtil;
public NativeDocumentParser(ChatClient chatClient,
ParseProperties parseProperties,
DocumentTextLimitUtil textLimitUtil) {
this.chatClient = chatClient;
this.parseProperties = parseProperties;
this.textLimitUtil = textLimitUtil;
}
public <T> T parseWithRetry(String content, Class<T> clazz) {
String safeContent = textLimitUtil.limitText(content);
int retryTimes = parseProperties.getRetryTimes();
for (int i = 0; i < retryTimes; i++) {
try {
// 动态切换严格/宽松解析模式
StructuredOutputParser<T> parser = parseProperties.isStrictCheck()
? StructuredOutputParser.fromType(clazz).strict()
: StructuredOutputParser.fromType(clazz).lenient();
log.info("【结构化解析】当前{}模式", parseProperties.isStrictCheck() ? "生产严格" : "测试宽松");
String promptText = """
你是专业的文档解析助手,严格根据文档内容抽取字段。
禁止输出多余解释、禁止输出markdown、无数据填空字符串。
文档内容:{content}
输出格式要求:{format}
""";
PromptTemplate promptTemplate = new PromptTemplate(promptText);
promptTemplate.add("content", safeContent);
promptTemplate.add("format", parser.getFormat());
String result = chatClient.prompt(promptTemplate.render()).call().content();
T parseResult = parser.parse(result);
log.info("【解析成功】重试次数:{},文本长度:{}", i, safeContent.length());
return parseResult;
} catch (Exception e) {
log.error("【解析失败】第{}次重试异常:{}", i + 1, e.getMessage());
try { Thread.sleep(300); } catch (InterruptedException ignored) {}
}
}
log.error("【最终解析失败】已用尽全部重试次数");
return null;
}
}
7.2 二次纠错增强解析类
针对首次解析字段缺失问题,实现AI二次补全,联动后置清洗校验闭环。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.parser.StructuredOutputParser;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@Component
public class RetryCorrectParser {
private static final Logger log = LoggerFactory.getLogger(RetryCorrectParser.class);
private final ChatClient chatClient;
private final NativeDocumentParser nativeParser;
private final ParseProperties parseProperties;
public RetryCorrectParser(ChatClient chatClient,
NativeDocumentParser nativeParser,
ParseProperties parseProperties) {
this.chatClient = chatClient;
this.nativeParser = nativeParser;
this.parseProperties = parseProperties;
}
public <T> T parseAndCorrect(String docContent, Class<T> clazz) {
T firstParseResult = nativeParser.parseWithRetry(docContent, clazz);
// 字段完整直接后置处理返回
if (!checkFieldLack(firstParseResult)) {
return ParsePostHandler.postHandle(firstParseResult,
parseProperties.isAutoClean(),
parseProperties.isStrictCheck());
}
log.info("【字段缺失】触发AI二次纠错补全");
String correctPrompt = """
本次为二次纠错解析,原始文档部分字段缺失。
请仔细通读文档,精准补全首次解析遗漏的关键字段,
严格遵循指定JSON格式,不新增、不篡改已有正确数据,无数据填空。
文档内容:{content}
输出格式:{format}
""";
try {
StructuredOutputParser<T> parser = StructuredOutputParser.fromType(clazz);
PromptTemplate template = new PromptTemplate(correctPrompt);
template.add("content", docContent);
template.add("format", parser.getFormat());
String correctResult = chatClient.prompt(template.render()).call().content();
T finalResult = parser.parse(correctResult);
return ParsePostHandler.postHandle(finalResult,
parseProperties.isAutoClean(),
parseProperties.isStrictCheck());
} catch (Exception e) {
log.error("【二次纠错失败】:{}", e.getMessage());
return ParsePostHandler.postHandle(firstParseResult,
parseProperties.isAutoClean(),
parseProperties.isStrictCheck());
}
}
// 反射判断实体是否存在空字段
private <T> boolean checkFieldLack(T entity) {
if (entity == null) return true;
try {
java.lang.reflect.Field[] fields = entity.getClass().getDeclaredFields();
for (java.lang.reflect.Field field : fields) {
field.setAccessible(true);
Object value = field.get(entity);
if (!StringUtils.hasText(String.valueOf(value))) return true;
}
} catch (Exception e) {
return false;
}
return false;
}
}
八、项目配置与全局组件
8.1 AI全局配置类(统一ChatClient)
统一模型参数、温度、最大Token,全局单例ChatClient,规范生产调用。
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringAiConfig {
@Value("${spring.ai.openai.api-key}")
private String apiKey;
@Value("${spring.ai.openai.base-url}")
private String baseUrl;
// 文档解析固定0温度,杜绝随机输出
private final double temperature = 0.0;
private final int maxTokens = 2048;
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
return builder.defaultOptions(options -> options
.temperature(temperature)
.maxTokens(maxTokens)
).build();
}
}
8.2 自定义配置绑定类(消除硬编码)
对接YAML配置,支持多环境动态调参、功能开关热更新。
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "parse")
public class ParseProperties {
// 解析重试次数
private int retryTimes = 2;
// 是否开启严格Schema+字段校验
private boolean strictCheck = true;
// 是否开启自动数据清洗
private boolean autoClean = true;
// 单文档最大解析字符长度
private int maxContentLength = 8000;
// getter & setter
public int getRetryTimes() { return retryTimes; }
public void setRetryTimes(int retryTimes) { this.retryTimes = retryTimes; }
public boolean isStrictCheck() { return strictCheck; }
public void setStrictCheck(boolean strictCheck) { this.strictCheck = strictCheck; }
public boolean isAutoClean() { return autoClean; }
public void setAutoClean(boolean autoClean) { this.autoClean = autoClean; }
public int getMaxContentLength() { return maxContentLength; }
public void setMaxContentLength(int maxContentLength) { this.maxContentLength = maxContentLength; }
}
8.3 全局异常处理器
统一拦截所有解析异常,自动降级兜底,服务无报错、无崩溃。
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalParseExceptionHandler {
@ExceptionHandler(Exception.class)
public Object handleParseException() {
return ParserFallbackUtil.getFallbackData("");
}
}
8.4 数据入库服务类
通用结构化数据入库逻辑,适配三类文档解析数据存储。
import com.alibaba.fastjson2.JSONObject;
import org.springframework.stereotype.Service;
@Service
public class DocumentDataSaveService {
public void saveStructuredData(String docType, Object structuredData) {
DocumentParserRecord record = new DocumentParserRecord();
record.setDocType(docType);
record.setStructuredJson(JSONObject.toJSONString(structuredData));
record.setParseTime(System.currentTimeMillis());
// 实际项目注入Mapper执行入库
}
}
// 文档解析记录实体
class DocumentParserRecord {
private String docType;
private String structuredJson;
private long parseTime;
// getter/setter
public String getDocType() { return docType; }
public void setDocType(String docType) { this.docType = docType; }
public String getStructuredJson() { return structuredJson; }
public void setStructuredJson(String structuredJson) { this.structuredJson = structuredJson; }
public long getParseTime() { return parseTime; }
public void setParseTime(long parseTime) { this.parseTime = parseTime; }
}
8.5 YAML生产配置文件
spring:
ai:
openai:
api-key: sk-xxx
base-url: https://xxx/v1
parse:
retry-times: 2
strict-check: true
auto-clean: true
max-content-length: 8000
九、最终业务接口(全链路闭环)
整合所有生产能力,修复临时文件磁盘泄漏,实现完整解析链路,可直接上线部署。
import com.alibaba.fastjson2.JSONObject;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
@RestController
public class FullDocumentParseController {
private final DocumentFileParseUtil fileParseUtil;
private final RetryCorrectParser correctParser;
private final DocumentDataSaveService dataSaveService;
private final ParserFallbackUtil fallbackUtil;
public FullDocumentParseController(DocumentFileParseUtil fileParseUtil,
RetryCorrectParser correctParser,
DocumentDataSaveService dataSaveService) {
this.fileParseUtil = fileParseUtil;
this.correctParser = correctParser;
this.dataSaveService = dataSaveService;
}
@PostMapping("/parse/file")
public Object parseFile(@RequestParam MultipartFile file,
@RequestParam String docType) {
File tempFile = null;
try {
tempFile = File.createTempFile("parse_", "_temp");
file.transferTo(tempFile);
String docText = fileParseUtil.extractFileText(tempFile);
if (!org.springframework.util.StringUtils.hasText(docText)) {
return fallbackUtil.getFallbackData(docType);
}
Object parseResult = switch (docType) {
case "contract" -> correctParser.parseAndCorrect(docText, ContractParseDTO.class);
case "invoice" -> correctParser.parseAndCorrect(docText, InvoiceParseDTO.class);
case "resume" -> correctParser.parseAndCorrect(docText, ResumeParseDTO.class);
default -> null;
};
if (parseResult == null) return fallbackUtil.getFallbackData(docType);
dataSaveService.saveStructuredData(docType, parseResult);
return parseResult;
} catch (IOException e) {
return fallbackUtil.getFallbackData(docType);
} finally {
// 强制释放临时文件,彻底解决磁盘泄漏
if (tempFile != null && tempFile.exists()) tempFile.delete();
}
}
}
十、踩坑总结(1.1.x专属)
-
版本兼容坑:1.1.x废弃BeanOutputParser,使用旧版API直接启动报错,必须替换为StructuredOutputParser。
-
Schema校验坑:新版校验更严格,字段类型不匹配、缺失会解析失败,必须搭配清洗校验工具。
-
Token超限坑:长文档直接解析会触发模型报错,必须前置文本截断。
-
资源泄漏坑:临时文件异常时无法自动删除,必须通过finally强制释放。
-
硬编码维护坑:重试次数、功能开关必须配置化,适配多环境部署。
十一、高频面试核心题(1.1.x专属)
11.1 SpringAI1.1.x结构化解析核心API变更?
彻底废弃1.0.x的BeanOutputParser,统一使用StructuredOutputParser作为唯一官方解析器,支持自动Schema生成、严格/宽松双模式,修复旧版空指针、映射异常问题。
11.2 严格模式与宽松模式的区别与生产选型?
严格模式(生产用):Schema不匹配、字段缺失、类型错误直接校验拦截,保障数据强一致性;宽松模式(测试用):容忍轻微格式错误和字段缺失,方便本地调试。
11.3 为什么需要文本截断防护?
大模型存在上下文Token上限,超长文档直接传入会导致解析失败、接口报错,生产环境必须前置长度截断,保障调用稳定性。
11.4 项目四层生产防护体系是什么?
模型Schema格式约束 → 接口重试容错 → AI二次纠错补漏 → 业务数据清洗+字段合规校验,层层拦截异常与脏数据。
11.5 临时文件为什么必须finally删除?
接口出现异常时,常规删除代码不会执行,会导致服务器残留大量临时文件,造成磁盘泄漏、服务宕机,finally可保障无论成败均释放资源。
11.6 配置化相比硬编码的生产优势?
支持多环境差异化配置、线上动态调参、无需重启服务修改功能开关,提升项目可运维性与迭代效率。
十二、全文总结与完整生产链路
本文档基于SpringAI1.1.x最新API,摒弃所有过期写法,搭建了一套可直接上线、高可用、高容错的文档结构化解析系统。以实体驱动Schema解析为核心,搭配重试、纠错、清洗、校验、降级、资源释放全链路防护,彻底解决大模型输出随机性问题。
最终完整生产链路:文件合规校验 → 文档文本抽取 → 超长文本截断 → 结构化解析(严格模式) → 重试容错 → AI二次纠错 → 数据自动清洗 → 业务字段校验 → 日志记录 → 结构化入库 → 临时文件释放 → 全局异常降级兜底