Spring Boot + Apache Tika 从文件或文件流中提取文本内容

发布于:2025-09-08 ⋅ 阅读:(20) ⋅ 点赞:(0)

应用效果:

1、安装 Apache Tika 依赖

pom.xml

    <!-- Apache Tika 从文件中提取结构化文本和元数据 -->
    <dependency>
      <groupId>org.apache.tika</groupId>
      <artifactId>tika-core</artifactId>
      <version>2.9.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.tika</groupId>
      <artifactId>tika-parsers-standard-package</artifactId>
      <version>2.9.2</version>
    </dependency>

2、配置

新建配置文件:src/main/resources/tika-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<properties>
    <encodingDetectors>
        <encodingDetector class="org.apache.tika.parser.html.HtmlEncodingDetector">
            <params>
                <param name="markLimit" type="int">64000</param>
            </params>
        </encodingDetector>
        <encodingDetector class="org.apache.tika.parser.txt.UniversalEncodingDetector">
            <params>
                <param name="markLimit" type="int">64001</param>
            </params>
        </encodingDetector>
        <encodingDetector class="org.apache.tika.parser.txt.Icu4jEncodingDetector">
            <params>
                <param name="markLimit" type="int">64002</param>
            </params>
        </encodingDetector>
    </encodingDetectors>
</properties>

新建配置类:src/main/java/com/weiyu/config/TikaConfiguration.java

package com.weiyu.config;

import org.apache.tika.Tika;
import org.apache.tika.config.TikaConfig;
import org.apache.tika.detect.Detector;
import org.apache.tika.exception.TikaException;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.Parser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.io.InputStream;
/**
 * Tika 配置
 */
@Configuration
public class TikaConfiguration {

    @Autowired
    private ResourceLoader resourceLoader;

    @Bean
    public Tika tika() throws IOException, TikaException, SAXException {
        Resource resource = resourceLoader.getResource("classpath:tika-config.xml");
        InputStream inputStream = resource.getInputStream();
        TikaConfig config = new TikaConfig(inputStream);
        Detector detector = config.getDetector();
        Parser parser = new AutoDetectParser(config);
        return new Tika(detector, parser);
    }
}

扩展原有文件工具类(增加提取文本内容的方法) 或 新建文件服务类

工具类:src/main/java/com/weiyu/utils/FileUtils.java

package com.weiyu.utils;

import org.apache.tika.Tika;
import org.apache.tika.exception.TikaException;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

/**
 * 文件工具
 */
public class FileUtils {

    private static final Tika tika = new Tika();

    /**
     * 获取路径分隔符
     * <p>如 windows:D:\\MyCode\\file\\example.txt,返回 \\; example.txt,返回 \u0000(空字符)
     * <p>如 windows:D:/MyCode/file/example.txt,返回 /; example.txt,返回 \u0000(空字符)
     * <p>如 Linux:/MyCode/file/example.txt,返回 /; example.txt,返回 \u0000(空字符)
     * @param filePathName 文件路径名称 或 文件名称
     * @return 分隔符,如 /、\、\u0000(空字符)
     */
    public static int getPathSplitChar(String filePathName) {
        if (filePathName == null || filePathName.isEmpty()) return '\u0000';
        int splitChar;
        // Linux 目录,以 / 开头
        if (filePathName.charAt(0) == '/') {
            splitChar = '/';
        }
        // windows 目录
        else {
            // windows 目录路径使用正斜杠(/)作为路径分隔符
            if (filePathName.contains("/")) {
                splitChar = '/';
            }
            // windows 目录路径使用反斜杠(\)作为路径分隔符
            else if (filePathName.contains("\\")){
                splitChar = '\\';
            } else {
                splitChar = '\u0000';
            }
        }
        return splitChar;
    }

    /**
     * 获取路径分隔字符串
     * <p>如 windows:D:\\MyCode\\file\\example.txt,返回 \\; example.txt,返回 \u0000(空字符)
     * <p>如 windows:D:/MyCode/file/example.txt,返回 /; example.txt,返回 \u0000(空字符)
     * <p>如 Linux:/MyCode/file/example.txt,返回 /; example.txt,返回 \u0000(空字符)
     * @param filePathName 文件路径名称 或 文件名称
     * @return 分隔字符串,如 /、\、\u0000(空字符)
     */
    public static String getPathSplitString(String filePathName) {
        int splitChar = getPathSplitChar(filePathName);
        switch (splitChar) {
            case 47 -> {
                return "/";
            }
            case 92 -> {
                return "\\";
            }
            default -> {
                return "";
            }
        }
    }

    /**
     * 获取文件名
     * <p>如 windows:D:\\MyCode\\file\\example.txt,返回 example.txt; example.txt,返回 example.txt
     * <p>如 windows:D:/MyCode/file/example.txt,返回 example.txt; example.txt,返回 example.txt
     * <p>如 Linux:/MyCode/file/example.txt,返回 example.txt; example.txt,返回 example.txt
     * @param filePathName 文件路径名称 或 文件名称
     * @return 文件名(无路径、有后缀),如:example.txt
     */
    public static String getFileName(String filePathName) {
        // 获取路径分隔符
        int ch = getPathSplitChar(filePathName);
        // 查找最后一个 ch 的位置
        int lastDotIndex = filePathName.lastIndexOf(ch);
        // 如果没有 ch,直接返回原文件名
        if (lastDotIndex == -1) {
            return filePathName;
        }
        return filePathName.substring(lastDotIndex + 1);
    }

    /**
     * 移除文件名后缀
     * <p>如 windows:D:\\MyCode\\file\\example.txt,返回 D:\\MyCode\\file\\example; example.txt,返回 example
     * <p>如 windows:D:/MyCode/file/example.txt,返回 D:/MyCode/file/example; example.txt,返回 example
     * <p>如 Linux:/MyCode/file/example.txt,返回 /MyCode/file/example; example.txt,返回 example
     * @param filePathName 文件路径名称 或 文件名称
     * @return 文件名(有路径、无后缀)
     */
    public static String getFileNameContainPathWithoutExtension(String filePathName) {
        // 查找最后一个 . 的位置
        int lastDotIndex = filePathName.lastIndexOf('.');
        // 如果没有后缀,直接返回原文件名
        if (lastDotIndex == -1) {
            return filePathName;
        }
        return filePathName.substring(0, lastDotIndex);
    }

    /**
     * 移除文件名路径和后缀
     * <p>如 windows:D:\\MyCode\\file\\example.txt 或 example.txt,返回 example
     * <p>如 windows:D:/MyCode/file/example.txt 或 example.txt,返回 example
     * <p>如 Linux:/MyCode/file/example.txt 或 example.txt,返回 example
     * @param filePathName 文件路径名称 或 文件名称
     * @return 文件名(无路径、无后缀),如:example
     */
    public static String getFileNameWithoutPathAndExtension(String filePathName) {
        // 获取路径分隔符
        int ch = getPathSplitChar(filePathName);
        // 查找最后一个 ch 的位置
        int lastDotIndex = filePathName.lastIndexOf(ch);
        // 如果没有ch,直接返回原文件名
        if (lastDotIndex != -1) {
            filePathName = filePathName.substring(lastDotIndex + 1);
        }

        // 查找最后一个 . 的位置
        lastDotIndex = filePathName.lastIndexOf('.');
        filePathName = filePathName.substring(0, lastDotIndex);

        return filePathName;
    }

    /**
     * 获取文件名的扩展名(后缀)
     * <p>如 windows:D:\\MyCode\\file\\example.txt 或 example.txt,返回 txt
     * <p>如 windows:D:/MyCode/file/example.txt 或 example.txt,返回 txt
     * <p>如 Linux:/MyCode/file/example.txt 或 example.txt,返回 txt
     * @param filePathName 文件路径名称 或 文件名称
     * @return 扩展名(后缀),不带.(如:txt)
     */
    public static String getExtension(String filePathName) {
        // 处理 null 异常
        if (filePathName == null) return null;
        // 获取文件名中最右边的.
        int index = filePathName.lastIndexOf(".");
        // 没有.
        if (index == -1) return "";
        // 获取文件名的扩展名(后缀)
        return filePathName.substring(index + 1);
    }

    /**
     * 是否目录
     * <p>如 windows:D:\\MyCode\\file\\example.txt,返回 true; example.txt,返回 false
     * <p>如 windows:D:/MyCode/file/example.txt,返回 true; example.txt,返回 false
     * <p>如 Linux:/MyCode/file/example.txt,返回 true; example.txt,返回 false
     * @param filePathName 文件路径名称 或 文件名称
     * @return true,包含文件路径;false,不包含文件路径
     */
    public static Boolean hasDirectory(String filePathName) {
        // 获取路径分隔符
        int ch = getPathSplitChar(filePathName);
        // 查找最后一个 ch 的位置
        int lastDotIndex = filePathName.lastIndexOf(ch);
        // 如果没有 ch,不包含文件路径
        return lastDotIndex != -1;
    }

    /**
     * 安全目录,windows目录以 \ 或 / 结尾,linux目录以 / 结尾
     * @param directoryPath 目录路径
     * @return 返回安全目录,如: 或 D:/MyCode/file/QualityFile/ 或 /MyCode/file/QualityFile/ 或 D:\\MyCode\\file\\QualityFile\\
     */
    public static String safeDirectory(String directoryPath) {
        // 获取目录路径最后一位字符
        String lastChar = directoryPath.substring(directoryPath.length() - 1);
        // 目录路径最后没有目录分割符 \ 或 /(windows) 或 /(linux)
        if (!lastChar.equals("\\") && !lastChar.equals("/")) {
            // linux 目录路径以 / 开头
            if (directoryPath.charAt(0) == '/') {
                directoryPath = directoryPath + "/";
            }
            // windows 目录路径
            else {
                // windows 目录路径使用正斜杠(/)作为路径分隔符
                if (directoryPath.contains("/")) {
                    directoryPath = directoryPath + "/";
                }
                // windows 目录路径使用反斜杠(\)作为路径分隔符
                else {
                    directoryPath = directoryPath + "\\";
                }
            }
        }
        return  directoryPath;
    }

    /**
     * 拼接目录路径
     * @param mainDirectoryPath 主目录路径
     *                          如 windows:D:/MyCode/file 或 D:\\MyCode\\file,Linux:/MyCode/file
     *                          如 windows:D:/MyCode/file/ 或 D:\\MyCode\\file\\,Linux:/MyCode/file/
     * @param subDirectoryPathAndFileName 子目录路径 或 文件名 或 子目录路径及文件名
     *                                    如 windows:QualityFile/ 或  QualityFile\\,Linux:QualityFile/
     *                                    如 windows:QualityFile/example.txt 或 QualityFile\\example.txt,Linux:QualityFile/example.txt
     *                                    如 windows:/example.txt 或 \\example.txt,Linux:/example.txt
     * @return 目录路径
     * 如 windows:D:/MyCode/file/QualityFile/ 或 D:\\MyCode\\file\\QualityFile\\,Linux:/MyCode/file/QualityFile/
     * 如 windows:D:/MyCode/file/QualityFile/example.txt 或 D:\\MyCode\\file\\QualityFile\\example.txt,Linux:/MyCode/file/QualityFile/example.txt
     * 如 windows:D:/MyCode/file/example.txt 或 D:\\MyCode\\file\\example.txt,Linux:/MyCode/file/example.txt
     */
    public static String joinDirectoryPath(String mainDirectoryPath, String subDirectoryPathAndFileName) {
        String filePathName;
        // 安全检查主目录路径,Linux:以 / 开头,windows:第二位字符是 :
        if (!isMainDirectoryPath(mainDirectoryPath)) {
            throw new RuntimeException("非法目录!");
        }

        filePathName = mainDirectoryPath;
        // 获取路径分隔符 / 或 \
        String pathSplitString = FileUtils.getPathSplitString(filePathName);
        // 确保最后是路径分隔符 / 或 \
        if (!filePathName.endsWith(pathSplitString)) {
            filePathName = filePathName + pathSplitString;
        }
        // 拼接子目录路径 或 文件名 或 子目录路径及文件名
        if (subDirectoryPathAndFileName != null && !subDirectoryPathAndFileName.isEmpty()) {
            // 拼接子目录路径 或 文件名 或 子目录路径及文件名 以 路径分隔符 / 或 \ 开头
            if (subDirectoryPathAndFileName.startsWith(pathSplitString)) {
                // 先删除子目录路径 或 文件名 或 子目录路径及文件名 开头的路径分隔符 / 或 \,再拼接
                filePathName = filePathName + subDirectoryPathAndFileName.substring(1);
            } else {
                // 直接拼接
                filePathName = filePathName + subDirectoryPathAndFileName;
            }
        }
        return filePathName;
    }

    /**
     * 是否是主目录,即 Linux:以 / 开头,windows:第二位字符是 :
     * @param mainDirectoryPath 主目录路径
     *                          如 windows:D:/MyCode/file 或 D:\\MyCode\\file,Linux:/MyCode/file
     *                          如 windows:D:/MyCode/file/ 或 D:\\MyCode\\file\\,Linux:/MyCode/file/
     * @return true 或 false
     */
    public static boolean isMainDirectoryPath(String mainDirectoryPath) {
        // 安全检查主目录路径,Linux:以 / 开头,windows:第二位字符是 :
        return mainDirectoryPath != null &&
               !mainDirectoryPath.isEmpty() &&
               (mainDirectoryPath.startsWith("/") || (mainDirectoryPath.length() > 2 && mainDirectoryPath.charAt(1) == ':'));
    }

    /**
     * 解析文件,提取文件中的文本内容
     *
     * @param file 文件
     * @return 文件中的文本内容
     * @throws IOException IO异常
     * @throws TikaException Tika异常
     */
    public static String parseFile(File file) throws IOException, TikaException {
        return tika.parseToString(file);
    }

    /**
     * 解析文件,提取文件流中的文本内容
     *
     * @param fileStream 文件流
     * @return 文件中的文本内容
     * @throws IOException IO异常
     * @throws TikaException Tika异常
     */
    public static String parseFile(InputStream fileStream) throws IOException, TikaException {
        return tika.parseToString(fileStream);
    }
}

服务类:

src/main/java/com/weiyu/service/FileService.java

src/main/java/com/weiyu/service/impl/FileServiceImpl.java

package com.weiyu.service;

import org.apache.tika.exception.TikaException;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

/**
 * 文件 Service 接口
 */
public interface FileService {
    /**
     * 解析文件,提取文件中的文本内容
     *
     * @param file 文件
     * @return 文件中的文本内容
     * @throws IOException IO异常
     * @throws TikaException Tika异常
     */
    String parseFile(File file) throws TikaException, IOException;

    /**
     * 解析文件,提取文件流中的文本内容
     *
     * @param fileStream 文件流
     * @return 文件中的文本内容
     * @throws IOException IO异常
     * @throws TikaException Tika异常
     */
    String parseFile(InputStream fileStream) throws TikaException, IOException;
}

src/main/java/com/weiyu/service/impl/FileServiceImpl.java

package com.weiyu.service.impl;

import com.weiyu.service.FileService;
import org.apache.tika.Tika;
import org.apache.tika.exception.TikaException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

/**
 * 文件 Service 接口实现
 */
@Service
public class FileServiceImpl implements FileService {

    @Autowired
    private Tika tika;

    /**
     * 解析文件,提取文件中的文本内容
     *
     * @param file 文件
     * @return 文件中的文本内容
     * @throws IOException IO异常
     * @throws TikaException Tika异常
     */
    @Override
    public String parseFile(File file) throws TikaException, IOException {
        return tika.parseToString(file);
    }

    /**
     * 解析文件,提取文件流中的文本内容
     *
     * @param fileStream 文件流
     * @return 文件中的文本内容
     * @throws IOException IO异常
     * @throws TikaException Tika异常
     */
    @Override
    public String parseFile(InputStream fileStream) throws TikaException, IOException {
        return tika.parseToString(fileStream);
    }
}

3、前后端联合应用

3.1、前端 Vue3

API:qualityFile.ts

/**
 * 从文件或文件流中提取文本内容
 * @param fileNo 文件编号(可能包含非安全字符,如:4.2 2人员v∕V/v+DW=dw,其中空格、全角斜杠∕、半角斜杠/、加号+、非ASCII字符​​(如中文、日文等),这些字符为非安全字符,在URL中都会被编码传输)
 * @returns 文本内容
 */
export const qualityFileParseService = (fileNo: string) => {
  return request.get("/resources/qualityFile/parse", {
    params: {
      fileNo: fileNo
    }
  });
};

UI:QualityFile.vue

// 提取
const onParseClick = async (fileNo: string) => {
  const result = await qualityFileParseService(fileNo);
  store.currentParseFileText = result.data;
  // 显示质量体系文件文本提取内容模态框
  showContentDialogRef.value?.showDialog();
};

          <BasePreventReClickButton
            class="table-btn"
            type="primary"
            size="default"
            text
            :loading="false"
            :disabled="scope.row.isNullContent"
            @click="onParseClick(scope.row.fileNo)"
            >提取</BasePreventReClickButton
          >

    <!-- 显示质量体系文件文本提取内容模态框 -->
    <BaseShowContentDialog ref="showContentDialogRef" title="提取的文本内容" :content="store.currentParseFileText" />

3.2、后端 Spring Boot

Controller:QualityFileController.java

    /**
     * 从文件或文件流中提取文本内容
     *
     * @param fileNo 文件编号(可能包含非安全字符,如:4.2 2人员v∕V/v+DW=dw,其中空格、全角斜杠∕、半角斜杠/、加号+、中文为非安全字符)
     * @return 文本内容
     * @apiNote 本接口使用防抖机制,3s 内重复请求会被忽略
     */
    @GetMapping("/parse")
    @Debounce(key = "/resources/qualityFile/parse", value = 3000)
    public Result<String> parse(String fileNo) throws TikaException, IOException {
        log.info("【质量体系文件】,提取文件中的文本内容,/resources/qualityFile/parse,fileNo = {}", fileNo);
        String fileContentStr = qualityFileService.parse(fileNo);
        return Result.success(fileContentStr);
    }

Service:QualityFileService.java

    /**
     * 从文件或文件流中提取文本内容
     */
    String parse(String fileNo) throws TikaException, IOException;

Service 实现(使用文件工具类实现):QualityFileServiceImpl.java

    /**
     * 从文件或文件流中提取文本内容
     */
    @Override
    public String parse(String fileNo) throws TikaException, IOException {
        // 获取文件数据
        FileData fileData = qualityFileMapper.selectFileData(fileNo);
        // 从文件名中获取路径分隔符
        String pathSplitString = FileUtils.getPathSplitString(fileData.getFileName());
        // 有路径分隔符,提取本地磁盘中文件的文本内容
        if (!pathSplitString.isEmpty()) {
            // 获取文件绝对路径
            String filePathName = FileUtils.joinDirectoryPath(mainDirectoryPath, fileData.getFileName());
            // 提取本地磁盘中文件的文本内容
            return FileUtils.parseFile(new File(filePathName));
        }
        // 无路径分隔符,提取数据库中文件流的文本内容
        else {
            InputStream fileStream = new ByteArrayInputStream(fileData.getFileContent());
            // 提取数据库中文件流的文本内容
            return FileUtils.parseFile(fileStream);
        }
    }

Service 实现(使用文件服务类实现):WorkInstructionServiceImpl.java

    @Autowired
    private FileService fileService;    

    /**
     * 从文件或文件流中提取文本内容
     */
    @Override
    public String parse(String fileNo) throws TikaException, IOException {
        // 获取文件数据
        FileData fileData = qualityFileMapper.selectFileData(fileNo);
        // 从文件名中获取路径分隔符
        String pathSplitString = FileUtils.getPathSplitString(fileData.getFileName());
        // 有路径分隔符,提取本地磁盘中文件的文本内容
        if (!pathSplitString.isEmpty()) {
            // 获取文件绝对路径
            String filePathName = FileUtils.joinDirectoryPath(mainDirectoryPath, fileData.getFileName());
            // 提取本地磁盘中文件的文本内容
            return fileService.parseFile(new File(filePathName));
        }
        // 无路径分隔符,提取数据库中文件流的文本内容
        else {
            InputStream fileStream = new ByteArrayInputStream(fileData.getFileContent());
            // 提取数据库中文件流的文本内容
            return fileService.parseFile(fileStream);
        }
    }


网站公告

今日签到

点亮在社区的每一天
去签到