1. 项目背景与核心价值
去年在金融行业做智能文档处理系统时,我遇到了一个典型的技术痛点:客户上传的文档格式五花八门(PDF、Word、Excel、PPT、扫描图片等),而传统方案要为每种格式单独开发解析逻辑。这不仅导致代码臃肿,更让维护变成噩梦——每当新增格式支持时,整个服务都得重新部署。这个项目就是基于Spring AI构建的解决方案,通过工厂模式+模板方法的组合拳,实现了文档向量化处理的优雅解耦。
这个架构最核心的价值在于:
- 扩展性:新增文档格式只需实现特定解析逻辑,不影响主流程
- 一致性:所有格式最终输出标准化向量,下游应用无需适配差异
- 性能可控:利用模板方法规范预处理、解析、后处理的执行流程
- 资源隔离:不同格式处理器在独立线程池运行,避免相互阻塞
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 架构设计解析
2.1 核心模式组合
工厂模式在这里扮演格式路由器的角色。我们定义DocumentVectorizerFactory接口,其getVectorizer()方法根据文件扩展名返回对应的处理器实例。实际开发中我推荐使用枚举实现工厂,比传统的if-else更优雅:
java复制public enum VectorizerFactory {
INSTANCE;
public DocumentVectorizer getVectorizer(String ext) {
return switch(ext.toLowerCase()) {
case "pdf" -> new PdfVectorizer();
case "docx" -> new WordVectorizer();
case "xlsx" -> new ExcelVectorizer();
default -> throw new UnsupportedFormatException(ext);
};
}
}
模板方法模式则通过抽象类AbstractDocumentVectorizer规范处理流程。这是我经过多个项目验证的最佳实践模板:
java复制public abstract class AbstractDocumentVectorizer implements DocumentVectorizer {
// 模板方法设为final防止子类修改流程
public final float[] vectorize(File doc) {
preProcess(doc); // 预处理(水印去除等)
String text = extractText(doc); // 抽象方法
return postProcess(text); // 向量化
}
protected abstract String extractText(File doc);
// 钩子方法(可选)
protected void preProcess(File doc) {
// 默认空实现
}
}
2.2 微服务化设计要点
在Spring Cloud架构下,我特别建议将向量化服务设计成独立微服务。关键配置包括:
- 异步处理:使用
@Async注解配合自定义线程池
java复制@Bean(name = "vectorizationPool")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("Vectorization-");
executor.initialize();
return executor;
}
- 服务降级:通过Resilience4j实现熔断
yaml复制resilience4j.circuitbreaker:
instances:
vectorizationCB:
failureRateThreshold: 50
waitDurationInOpenState: 5000
ringBufferSizeInClosedState: 10
- 性能监控:利用Micrometer暴露指标
java复制@Timed(value = "vectorization.time", description = "Time taken to vectorize")
public float[] vectorizeDocument(File file) {
//...
}
3. Spring AI集成实战
3.1 向量化实现方案
Spring AI目前主要支持以下嵌入模型:
- OpenAIEmbeddingClient
- AzureOpenAiEmbeddingClient
- OllamaEmbeddingClient
我的实测对比显示,对于中文文档,Azure的text-embedding-3-large模型效果最佳。配置示例:
java复制@Bean
public EmbeddingClient embeddingClient() {
AzureOpenAiEmbeddingModel model = new AzureOpenAiEmbeddingModel(
"text-embedding-3-large",
"your-api-key",
"https://{resource-name}.openai.azure.com",
"deployment-name"
);
return new AzureOpenAiEmbeddingClient(model);
}
3.2 文档分块策略
大文档必须分块处理,我总结的最佳实践是:
- 按Markdown标题拆分(保留层级关系)
- 滑动窗口法(窗口512token,重叠128token)
- 表格特殊处理(转为Markdown格式)
实现代码片段:
java复制public List<TextSegment> splitDocument(String text) {
List<TextSegment> segments = new ArrayList<>();
// 标题分割逻辑
Pattern pattern = Pattern.compile("^#+ .+", Pattern.MULTILINE);
//...具体实现
return segments;
}
4. 性能优化技巧
4.1 缓存设计
高频访问的文档建议缓存向量结果,我的方案是:
java复制@Cacheable(value = "documentVectors",
key = "#file.hashCode()",
unless = "#result == null")
public float[] getCachedVector(File file) {
return vectorize(file);
}
4.2 并行处理
对于复合文档(如含图片的PDF),采用ForkJoinPool实现并行解析:
java复制public class PdfImageProcessor extends RecursiveTask<List<String>> {
private final List<Image> images;
@Override
protected List<String> compute() {
// 实现图像文本提取的并行处理
}
}
5. 生产环境踩坑记录
-
内存泄漏:PDFBox解析器需要手动关闭PDDocument对象
必须用try-with-resources确保资源释放:
java复制try (PDDocument doc = PDDocument.load(file)) { // 解析逻辑 } -
编码问题:旧版Excel文件需指定GBK编码
java复制new ExcelExtractor().setEncoding("GBK"); -
性能陷阱:Apache POI的SAX模式比DOM模式内存占用低90%
-
向量维度:不同模型输出维度不同,建议在存储时添加维度标记
sql复制ALTER TABLE document_vectors ADD COLUMN embedding_dim INT NOT NULL;
6. 扩展设计建议
- 插件化架构:结合Spring Plugin框架实现动态加载
java复制public interface VectorizerPlugin extends Plugin<String> {
boolean supports(String extension);
float[] vectorize(File file);
}
- 质量评估:添加向量相似度校验
java复制public boolean checkQuality(float[] v1, float[] v2) {
return cosineSimilarity(v1, v2) > 0.85;
}
- 混合模型:关键段落用大模型,常规内容用小模型
这个架构已在银行、保险行业多个项目落地,最高支持日均50万份文档的处理。核心在于通过设计模式将变化点隔离,这也是我在复杂系统设计中始终坚持的原则——让修改局限在最小范围内。
