应用效果:
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 {@Autowiredprivate ResourceLoader resourceLoader;@Beanpublic 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 {@Autowiredprivate Tika tika;/*** 解析文件,提取文件中的文本内容** @param file 文件* @return 文件中的文本内容* @throws IOException IO异常* @throws TikaException Tika异常*/@Overridepublic String parseFile(File file) throws TikaException, IOException {return tika.parseToString(file);}/*** 解析文件,提取文件流中的文本内容** @param fileStream 文件流* @return 文件中的文本内容* @throws IOException IO异常* @throws TikaException Tika异常*/@Overridepublic 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();
};<BasePreventReClickButtonclass="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
/*** 从文件或文件流中提取文本内容*/@Overridepublic 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
@Autowiredprivate FileService fileService; /*** 从文件或文件流中提取文本内容*/@Overridepublic 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);}}