知识库-向量化功能-EXCEL文件向量化

知识库-向量化功能-EXCEL文件向量化

一、功能概述

基于Alibaba EasyExcel 实现Excel文件全量解析,适配 .xls/.xlsx 双格式,核心流程为:解析Excel单元格数据 → 按行合并生成结构化文本块 → 文本向量化 → 批量存储至Elasticsearch向量库。 ✅ 核心特性:

  1. 自动跳过空单元格,过滤无效数据;
  2. 保留完整元数据(工作表名、行号、列号),支持检索后溯源;
  3. 按行聚合文本,保证单行列数据的语义关联性;
  4. 批量向量化+批量写入ES,兼顾效率与内存安全。

二、技术依赖(Maven)

xml 复制代码
<!-- Excel文件解析核心依赖(适配.xls/.xlsx,无POI版本冲突) -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.3.2</version>
</dependency>

✅ 说明:EasyExcel 3.3.2 已内置适配的POI依赖,无需额外引入POI包,彻底解决传统POI解析Excel的内存溢出、版本冲突问题。

三、核心数据模型定义

3.1 单元格数据实体(解析层)

封装Excel单元格原始数据+完整元数据,用于解析结果暂存与数据流转

java 复制代码
import lombok.Data;

/**
 * Excel单元格数据实体
 * 存储单个单元格的内容+全量元数据,支持检索溯源
 */
@Data
public class ExcelCellData {
    /** 工作表名称(如:Sheet1、用户信息表) */
    private String sheetName;
    /** 行号(从1开始,与Excel视觉行号一致) */
    private Integer rowNum;
    /** 列号(从1开始,与Excel视觉列号一致) */
    private Integer colNum;
    /** 单元格有效值(已去空、去首尾空格) */
    private String cellValue;
}

3.2 文本块实体(分片层)

工作表+行聚合后的结构化文本块,作为向量化的入参载体

java 复制代码
import lombok.Data;
import java.util.Map;

/**
 * Excel行级文本块实体
 * 一行所有单元格数据聚合后的结构化文本,用于向量化
 */
@Data
public class TextChunk {
    /** 行级聚合文本内容(结构化格式,提升向量化准确性) */
    private String content;
    /** 元数据(工作表名、行号、文件类型,用于ES存储/检索溯源) */
    private Map<String, Object> metadata;
}

3.3 向量块实体(向量化层)

封装「文本+元数据+向量」的完整数据,作为写入ES的前置载体

java 复制代码
import lombok.Data;
import java.util.Map;

/**
 * 向量块实体
 * 文本块+向量数据的组合体,用于批量写入ES
 */
@Data
public class VectorChunk {
    /** 行级聚合文本内容 */
    private String content;
    /** 元数据(继承自TextChunk) */
    private Map<String, Object> metadata;
    /** 文本对应的向量数据(768维度,匹配bce-embedding-base_v1模型) */
    private float[] vector;
}

四、核心实现代码(完整四层流程)

第一层:Excel文件解析工具类(通用适配)

封装通用解析方法,支持「指定表头行数/默认无表头」双模式,自动适配所有Excel格式,无内存溢出风险

java 复制代码
import com.alibaba.excel.EasyExcel;
import java.io.File;
import java.util.List;

/**
 * Excel通用解析工具类
 * ✅ 适配.xls/.xlsx | ✅ 支持表头配置 | ✅ 自动过滤空单元格 | ✅ 无内存溢出
 */
public class ExcelParserUtil {

    /**
     * 通用Excel解析方法(推荐)
     * @param excelFile 待解析的Excel文件
     * @param headRowNumber 表头行数【核心】:有表头填1,纯数据无表头填0
     * @return 所有有效单元格数据列表(已过滤空值)
     */
    public static List<ExcelCellData> parseExcel(File excelFile, int headRowNumber) {
        ExcelAnalysisListener listener = new ExcelAnalysisListener();
        // EasyExcel 3.x 标准写法,一行代码完成全表读取
        EasyExcel.read(excelFile)
                .registerReadListener(listener) // 注册监听器接收解析数据
                .headRowNumber(headRowNumber)   // 指定表头行数,避免表头混入数据
                .doReadAll();                    // 读取Excel中所有工作表
        
        return listener.getCellDataList();
    }

    /**
     * 重载方法:默认无表头(headRowNumber=0),简化无表头Excel调用
     */
    public static List<ExcelCellData> parseExcel(File excelFile) {
        return parseExcel(excelFile, 0);
    }
}

第二层:Excel解析监听器(核心回调)

EasyExcel专用监听器,逐行读取Excel数据、过滤空单元格、封装单元格实体,是解析效率与数据质量的核心保障

java 复制代码
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Excel解析监听器(EasyExcel核心回调类)
 * ✅ 逐行解析 | ✅ 过滤空单元格 | ✅ 封装ExcelCellData | ✅ 记录工作表元数据
 */
public class ExcelAnalysisListener extends AnalysisEventListener<Map<String, Object>> {
    // 存储所有有效单元格数据
    private final List<ExcelCellData> cellDataList = new ArrayList<>();
    // 当前解析的工作表名称
    private String currentSheetName;

    /**
     * 核心回调:逐行解析Excel数据(每行触发一次)
     */
    @Override
    public void invoke(Map<String, Object> rowDataMap, AnalysisContext context) {
        // 1. 获取元数据:工作表名、当前行号(+1 匹配Excel视觉行号)
        currentSheetName = context.readSheetHolder().getSheetName();
        int rowNum = context.readRowHolder().getRowIndex() + 1;

        // 2. 遍历当前行所有单元格,过滤空值并封装实体
        int colNum = 1; // 列号从1开始计数,符合使用习惯
        for (Map.Entry<String, Object> entry : rowDataMap.entrySet()) {
            Object cellValueObj = entry.getValue();
            // 跳过空单元格,避免无效数据占用内存
            if (cellValueObj == null || cellValueObj.toString().trim().isEmpty()) {
                colNum++;
                continue;
            }

            // 3. 封装单元格数据+全量元数据
            String cellValue = cellValueObj.toString().trim();
            ExcelCellData cellData = new ExcelCellData();
            cellData.setSheetName(currentSheetName);
            cellData.setRowNum(rowNum);
            cellData.setColNum(colNum);
            cellData.setCellValue(cellValue);
            
            cellDataList.add(cellData);
            colNum++;
        }
    }

    /**
     * 解析完成回调:所有工作表解析完毕后触发
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        System.out.printf("✅ Excel解析完成 | 工作表:%s | 提取有效单元格数:%d%n", 
                currentSheetName, cellDataList.size());
    }

    // 获取最终解析结果
    public List<ExcelCellData> getCellDataList() {
        return cellDataList;
    }
}

第三层:文本分块工具类(行级聚合)

将分散的单元格数据,按「工作表+行号」分组聚合为结构化文本块,保证单行数据的语义完整性,适配向量化需求

java 复制代码
import java.util.*;
import java.util.stream.Collectors;

/**
 * Excel文本分块工具类
 * ✅ 按工作表+行分组 | ✅ 结构化拼接文本 | ✅ 封装元数据 | ✅ 生成TextChunk
 */
@Component
public class ExcelTextSplitter {

    /**
     * 核心方法:将单元格数据 → 按行聚合为文本块
     * @param cellDataList 解析后的单元格数据列表
     * @return 行级结构化文本块列表
     */
    public List<TextChunk> splitByRow(List<ExcelCellData> cellDataList) {
        // 双层Map:key1=工作表名,key2=行号 → value=该行拼接文本
        Map<String, Map<Integer, StringBuilder>> rowTextMap = new HashMap<>(16);
        List<TextChunk> textChunks = new ArrayList<>();

        // 1. 按「工作表+行号」分组,拼接单行所有单元格数据
        cellDataList.forEach(cellData -> {
            String sheetName = cellData.getSheetName();
            Integer rowNum = cellData.getRowNum();
            // 不存在则初始化层级Map
            rowTextMap.computeIfAbsent(sheetName, k -> new HashMap<>(32));
            StringBuilder rowText = rowTextMap.get(sheetName)
                    .computeIfAbsent(rowNum, k -> new StringBuilder());
            // 结构化拼接:列X:值; → 保证文本可读性与向量化准确性
            rowText.append("列").append(cellData.getColNum())
                    .append(":").append(cellData.getCellValue()).append("; ");
        });

        // 2. 生成TextChunk,封装聚合文本+元数据
        rowTextMap.forEach((sheetName, rowMap) -> {
            rowMap.forEach((rowNum, rowText) -> {
                // 拼接最终行文本(带工作表+行号标识)
                String chunkText = String.format("工作表[%s] 行[%d] 内容: %s",
                        sheetName, rowNum, rowText);
                
                TextChunk chunk = new TextChunk();
                chunk.setContent(chunkText);
                // 封装元数据:用于ES存储、检索溯源、结果过滤
                chunk.setMetadata(Map.of(
                        "sheetName", sheetName,
                        "rowNum", rowNum.toString(),
                        "fileType", "excel",
                        "dataSource", "excel-row"
                ));
                textChunks.add(chunk);
            });
        });
        return textChunks;
    }
}

第四层:向量化+ES存储服务(核心业务)

整合「文本向量化」与「批量写入ES」能力,实现Excel数据向量化的最终落地,适配Spring AI向量库规范

java 复制代码
import lombok.RequiredArgsConstructor;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.IntStream;

/**
 * Excel向量化核心服务
 * ✅ 文本块批量向量化 | ✅ 向量数据封装 | ✅ 批量写入Elasticsearch
 */
@Service
@RequiredArgsConstructor // 构造器注入依赖,避免空指针
public class ExcelVectorStoreService {
    // Spring AI 嵌入模型(注入bce-embedding-base_v1)
    private final EmbeddingModel embeddingModel;
    // Spring AI 向量库(已配置ES实现,直接调用)
    private final VectorStore vectorStore;

    /**
     * 文本块批量向量化:调用嵌入模型生成向量数据
     */
    public List<VectorChunk> generateVector(List<TextChunk> textChunks) {
        // 提取所有文本内容,批量向量化(效率远高于单条调用)
        List<String> texts = textChunks.stream()
                .map(TextChunk::getContent)
                .collect(Collectors.toList());
        List<float[]> vectorList = embeddingModel.embed(texts);

        // 文本块 + 元数据 + 向量 组合封装
        return IntStream.range(0, textChunks.size())
                .mapToObj(i -> {
                    TextChunk textChunk = textChunks.get(i);
                    VectorChunk vectorChunk = new VectorChunk();
                    vectorChunk.setContent(textChunk.getContent());
                    vectorChunk.setMetadata(textChunk.getMetadata());
                    vectorChunk.setVector(vectorList.get(i));
                    return vectorChunk;
                }).collect(Collectors.toList());
    }

    /**
     * 向量数据批量写入ES:适配Spring AI VectorStore规范,自动处理向量存储
     */
    public void saveToElasticsearch(List<VectorChunk> vectorChunks) {
        List<Document> esDocList = new ArrayList<>(vectorChunks.size());
        vectorChunks.forEach(vectorChunk -> {
            // 生成分片唯一ID(UUID去横线,避免ES索引ID特殊字符问题)
            String chunkId = UUID.randomUUID().toString().replace("-", "");
            // 封装为Spring AI标准Document,直接写入ES
            Document document = new Document(chunkId, vectorChunk.getContent(), vectorChunk.getMetadata());
            esDocList.add(document);
        });
        // 批量写入ES,适配配置的bulk-size,提升写入效率
        vectorStore.add(esDocList);
        System.out.printf("✅ Excel向量数据写入完成 | 共写入分片数:%d%n", esDocList.size());
    }
}

五、完整调用示例(一站式流程)

整合「解析→分块→向量化→存储」全流程,提供标准调用入口,可直接嵌入业务代码(如文件上传、定时任务)

java 复制代码
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.io.File;
import java.util.List;

/**
 * Excel向量化一站式调用服务
 * 对外提供统一入口,屏蔽底层四层实现细节
 */
@Service
@RequiredArgsConstructor
public class ExcelVectorizationService {
    private final ExcelTextSplitter excelTextSplitter;
    private final ExcelVectorStoreService excelVectorStoreService;

    /**
     * Excel文件向量化一站式入口
     * @param excelFilePath Excel文件绝对路径
     * @param headRowNumber 表头行数(有表头=1,无表头=0)
     */
    public void vectorizeExcel(String excelFilePath, int headRowNumber) {
        try {
            File excelFile = new File(excelFilePath);
            // 1. 解析Excel:单元格数据
            List<ExcelCellData> cellDataList = ExcelParserUtil.parseExcel(excelFile, headRowNumber);
            // 2. 文本分块:按行聚合为TextChunk
            List<TextChunk> textChunks = excelTextSplitter.splitByRow(cellDataList);
            // 3. 向量化:生成向量数据
            List<VectorChunk> vectorChunks = excelVectorStoreService.generateVector(textChunks);
            // 4. 写入ES:批量存储向量+文本+元数据
            excelVectorStoreService.saveToElasticsearch(vectorChunks);
        } catch (Exception e) {
            throw new RuntimeException("❌ Excel文件向量化失败:" + e.getMessage(), e);
        }
    }

    /**
     * 重载方法:默认无表头(headRowNumber=0),简化调用
     */
    public void vectorizeExcel(String excelFilePath) {
        vectorizeExcel(excelFilePath, 0);
    }
}

// ========== 调用示例 ==========
// @Autowired
// private ExcelVectorizationService excelVectorizationService;
// 
// // 无表头Excel调用
// excelVectorizationService.vectorizeExcel("D:/数据报表.xlsx");
// // 有表头Excel调用
// excelVectorizationService.vectorizeExcel("D:/用户信息表.xlsx", 1);

六、核心设计亮点 & 优化说明

✅ 关键设计优势(解决传统Excel处理痛点)

  1. 内存安全:基于EasyExcel流式解析,无需一次性加载全量Excel数据,支持GB级超大Excel解析,无OOM风险;
  2. 数据纯净:自动过滤空单元格、首尾空格,避免无效数据参与向量化,提升检索准确率;
  3. 语义完整:按「行」聚合单元格数据,保证单行业务数据的关联性(如一行是一条用户信息,完整聚合不拆分);
  4. 溯源能力:全程保留工作表名、行号、列号元数据,检索时可精准定位数据在Excel中的位置;
  5. 效率最优 :批量向量化+批量写入ES,匹配Spring AI配置的bulk-size,大幅减少模型调用与ES请求次数。

✅ 核心参数说明(可按需调整)

参数名 取值建议 作用说明
headRowNumber 0/1 必配!0=无表头,1=有表头,避免表头数据混入业务数据
向量维度 768 固定匹配lrs33/bce-embedding-base_v1模型,与ES配置一致
批量写入大小 50 复用application.yml中bulk-size配置,无需单独调整
列号/行号计数 从1开始 与Excel视觉行号/列号一致,符合业务使用习惯

七、注意事项 & 最佳实践

⚠️ 开发/部署注意事项

  1. 格式支持 :完美适配 .xls(Excel 97-2003)、.xlsx(Excel 2007+),不支持WPS专属格式(.et);
  2. 表头处理有表头的Excel必须传headRowNumber=1,否则表头会被当作业务数据解析;
  3. 数据类型兼容:Excel中数字、日期、布尔值会自动转为字符串,统一处理无类型异常;
  4. 大文件优化:解析10万行+超大Excel时,建议异步执行(结合线程池),避免阻塞主线程;
  5. 权限校验 :解析文件前需校验文件读取权限,避免AccessDeniedException
  6. ES索引匹配 :确保ES索引的text-field-name/vector-field-name与配置一致,否则存储失败。

✅ 最佳实践建议

  1. 统一调用入口 :使用ExcelVectorizationService一站式调用,屏蔽底层细节,便于维护;
  2. 异步处理 :大批量Excel文件向量化时,结合@Async实现异步执行,提升系统吞吐量;
  3. 日志增强:在关键步骤增加日志(文件路径、解析行数、写入分片数、耗时),便于问题排查;
  4. 容错机制:对损坏的Excel文件增加重试逻辑,或返回友好提示,避免服务崩溃;
  5. 数据过滤:可扩展「行过滤」逻辑(如跳过指定行、过滤指定内容),适配个性化业务需求。

八、扩展能力规划

  1. 按工作表过滤:支持指定解析单个/多个工作表,无需解析全表;
  2. 自定义分块策略:支持「按列分组」「按固定行数分块」,适配不同Excel数据结构;
  3. 数据校验:增加单元格数据格式校验(如手机号、邮箱、日期),提升数据质量;
  4. 进度监控:增加解析/向量化进度回调,支持前端展示处理进度;
  5. 结果去重:基于Excel文件MD5去重,避免重复向量化写入ES。
相关推荐
Elasticsearch2 小时前
使用 Elasticsearch 中的结构化输出创建可靠的 agents
elasticsearch
不思念一个荒废的名字2 小时前
【黑马JavaWeb+AI知识梳理】Web后端开发07 - Maven高级
后端·maven
czlczl200209252 小时前
Spring Cache 全景指南
java·后端·spring
undsky2 小时前
【RuoYi-SpringBoot3-Pro】:MyBatis-Plus 集成
spring boot·后端·mybatis
法拉第第2 小时前
CAS乐观锁
后端
高松燈2 小时前
K8s学习文档(1) -- 集群搭建(手把手教学)
后端
czlczl200209252 小时前
SpringBoot自定义Redis
spring boot·redis·后端
踏浪无痕2 小时前
Java 17 升级避坑:如何安全处理反射访问限制
后端·面试·架构
Go高并发架构_王工2 小时前
Redis命令执行原理与源码分析:深入理解内部机制
数据库·redis·后端