Apache Tika 从入门到生产:一套 API 解析 PDF/Word/Excel/PPT/图片上千种格式(含 Spring Boot 封装与 OCR)

做企业开发的同学,大概率都碰到过这类需求:

  • 做知识库 / RAG 系统:要把用户上传的 PDF、Word、PPT、甚至扫描件全部扒成纯文本,切块丢向量库;
  • 做全文检索:文件服务器上几十万份文档,要统一建索引,能按关键词搜到内容;
  • 做内容审核 / 合规审计:员工上传的附件里有没有敏感词?合同里有没有不合规条款?
  • 做元数据归档:一份合同的作者是谁、哪天创建的、最后谁改的、多少页?

这类需求的共同点是:格式杂、数量大、写解析代码非常痛苦——

  • 写 Word 用 POI,.doc 和 .docx 还不是一套 API;
  • 写 PDF 用 PDFBox,加密 PDF 还得绕一圈;
  • 写 Excel 又得换 Fesod/POI;碰到 HTML、RTF、压缩包、图片 OCR?
  • 每多一种格式,就得多学一套库、多写一套分支,维护起来就是噩梦。

今天的主角 Apache Tika 就是来解决这个痛点的。它把上千种文件格式的解析能力封装成一套统一的 API,你不用管底层是 POI 还是 PDFBox,丢文件进去 → 出来就是 文本内容 + 元数据 + MIME 类型。

配合上一篇 Spring Boot + Apache Fesod 生产级 Excel 组件,你的项目就能形成完整的 “Excel 精细化读写 + 全格式文档内容解析” 闭环。


0)Apache Tika 是什么 & 为什么选它

0.1 一句话定位

Apache Tika 是 Apache 基金会旗下的开源内容分析工具包,能够自动检测上千种文件格式,并从中统一提取文本内容、元数据、语言类型。

你可以把 Tika 理解成文档解析界的 Facade 模式——底层仍然使用 POI、PDFBox、HtmlParser、Tesseract 这些成熟解析器,但上层给你一套 10 分钟就能上手的统一门面。

0.2 Tika 能解析什么?(1000+ 格式部分代表)

类别支持的格式
办公文档Word(.doc/.docx)、Excel(.xls/.xlsx,配合 Fesod/POI)、PowerPoint(.ppt/.pptx)、Visio、Outlook MSG、OpenDocument(ODT/ODS/ODP)、RTF
PDF标准 PDF、加密 PDF(需密码)、带表单 PDF、PDF/A 归档版
网页与标记HTML、XHTML、XML、JSON、YAML、Markdown
纯文本.txt / .log / .csv(含自动编码识别)
压缩包ZIP / TAR / GZIP / RAR / 7z(可递归解包提取内容)
邮件.eml / .mbox / Outlook PST
图像 & OCRJPEG / PNG / TIFF / BMP(能取 EXIF;配合 Tesseract 可 OCR 图片文字)
多媒体元数据MP3 / FLAC(ID3 标签)、MP4 / MOV(音视频元信息)

0.3 核心三大能力

能力说明
🔍 MIME 自动检测不看后缀名,根据文件二进制 Magic Number + 内容特征判断真实类型,防改后缀名绕过
📝 内容提取输出纯文本(也可输出 XHTML 结构化结果,保留标题、表格层次)
🏷️ 元数据提取作者、标题、创建/修改时间、页数、分辨率、EXIF、Creator Tool 等几十种属性

额外可选能力:🌏 语言检测(中英文混排自动识别)、🖼️ OCR(Tesseract 集成)、🔗 递归解析(ZIP 里的 Word 直接扒出来)。

0.4 和”挨个用解析器”对比

维度手写”if PDF, if Word, if Excel”Apache Tika
支持格式加一种格式加一套代码,维护爆炸一套 API 通吃 1000+
MIME 检测fileName.endsWith(".pdf") → 改后缀立刻翻车Magic Number + 内容特征,真实类型 99%+ 准确
学习成本POI、PDFBox、jsoup 每一套都要查文档3 个核心类 + 2 个方法,10 分钟上手
升级兼容每个解析器自己升版本,依赖冲突家常便饭Tika 团队已经帮你把依赖对齐、避过坑了
企业 RAG/知识库场景一个月搭不完 + 后续持续踩坑引入依赖 → 写几十行 Service → 上线 🚀

一句话结论:只要你的系统需要解析 2 种以上的文档格式,Tika 就是 ROI 最高的选择。


1)环境准备:3 种引入方式按需选

Tika 的发布体系非常清晰,按场景选就行。

1.1 版本怎么挑?(截至 2026 Q3)

系列最新版本JDK 要求推荐场景
2.9.x LTS2.9.4JDK 8+生产环境首选,稳定、生态完善
3.3.x3.3.2JDK 11+需要新特性,不追求极端稳定
4.0.04.0.0JDK 17+尝鲜用,2026/8 刚发 GA

✅ 本文示例统一用最稳的 2.9.4 + JDK 17,代码在 3.x 系列也完全兼容(只换版本号即可)。

1.2 方式一:极简依赖(核心 + 标准解析器,99% 项目用这个 ✅)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<properties>
<tika.version>2.9.4</tika.version>
</properties>

<dependencies>
<!-- Tika 核心门面:Tika 类、检测 API、基础解析接口 -->
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>${tika.version}</version>
</dependency>

<!-- 标准解析器集合(pom 类型,会传递引入 PDFBox/POI/HtmlParser 等) -->
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-parsers-standard-package</artifactId>
<version>${tika.version}</version>
<type>pom</type>
</dependency>

<!-- 可选:语言检测(中英文等自动识别) -->
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-langdetect-optimaize</artifactId>
<version>${tika.version}</version>
</dependency>

<!-- 可选:SLF4J 日志,用你项目里现成的 -->
</dependencies>

⚠️ 依赖冲突提示:如果你的项目已经有 POI / PDFBox,建议升级到 Tika 同版本(可以去 Maven 看 tika-parsers-standard-package 的 parent POM 里锁定的版本号),避免 NoSuchMethodError。

1.3 方式二:Tika Server(微服务 / 非 Java 项目用)

如果你的主语言不是 Java(Python/Go/Node.js),或者要做”解析中心”供多个微服务调用,推荐直接用 Tika 提供的 REST 服务:

1
2
3
4
5
6
7
8
9
10
11
# 下载(版本号对应上面)
wget https://dlcdn.apache.org/tika/2.9.4/tika-server-standard-2.9.4.jar

# 启动(默认端口 9998)
java -Xmx2g -jar tika-server-standard-2.9.4.jar --port=9998

# 测试:上传一个文件拿内容
curl -T /path/to/report.pdf http://localhost:9998/tika

# 只拿元数据
curl -T /path/to/report.pdf http://localhost:9998/meta

Docker 部署(生产推荐,含 Tesseract OCR 语言包):

1
2
docker run -d -p 9998:9998 --name tika-server \
apache/tika:2.9.4.0-full

1.4 方式三:命令行 / GUI 工具(排错 & 手工解析)

Tika 还提供了一个开箱即用的 tika-app JAR,本地人肉调试神器:

1
2
3
4
5
6
7
8
9
10
11
# 纯文本导出
java -jar tika-app-2.9.4.jar -t report.pdf > report.txt

# 元数据导出
java -jar tika-app-2.9.4.jar -m report.pdf

# 导出结构化 XHTML(保留标题、段落、表格结构)
java -jar tika-app-2.9.4.jar -x report.docx > report.xhtml

# 启动图形界面:拖拽文件进窗口即可查看
java -jar tika-app-2.9.4.jar -g

2)核心 API 速览:2 层用法,按需取用

Tika 设计了两层 API,简单场景 1 行搞定,复杂场景下到底层完全可控:

2.1 Facade 层(new Tika()):10 行以内搞定一切,首推 🌟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 线程安全!全项目就一个单例即可,别每次 new
Tika tika = new Tika();

// 1) 检测 MIME 类型(不读内容也行,但建议传 InputStream 更准)
String mime = tika.detect(new File("contract.pdf")); // → application/pdf

// 2) 一把梭:拿纯文本内容
String text = tika.parseToString(new File("contract.pdf"));

// 3) 拿元数据(Metadata 是个 Map-like 对象)
Metadata metadata = new Metadata();
try (InputStream in = new FileInputStream("contract.pdf")) {
String text2 = tika.parseToString(in, metadata);
String author = metadata.get("dc:creator"); // 作者
String pages = metadata.get("xmpTPg:NPages"); // 页数
}

2.2 Parser 层(AutoDetectParser):精细控制(大文件 / OCR / 加密 PDF)

Facade 层底层就是走的 Parser 层,当你需要控制字符限制、密码、OCR 参数、解析器白名单时就直接用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
AutoDetectParser parser = new AutoDetectParser();        // 自动检测格式 → 选对应解析器
BodyContentHandler handler = new BodyContentHandler(-1); // -1 = 无字符数上限(默认是 10w 字符截断,注意!)
Metadata metadata = new Metadata();
ParseContext context = new ParseContext();

// 示例:加密 PDF 传密码
// metadata.set(PDFParser.PASSWORD, "my-password");

// 示例:限制内存中字符数(防超大文件 OOM)
// BodyContentHandler handler = new BodyContentHandler(5_000_000);

try (InputStream in = TikaInputStream.get(new File("big.docx"))) {
parser.parse(in, handler, metadata, context);
String content = handler.toString();
// metadata.get(...) 照常取
}

⚠️ 巨坑提示:BodyContentHandler 默认构造器有 100,000 字符上限,超过就直接截断!大文档务必传 -1 或你想要的上限。这是新手最高频翻车点。

下面所有实战,两个 API 都会给示例。


3)实战一:一键提取各种格式的纯文本(PDF / Word / Excel / PPT / HTML)

3.1 封装一个通用 extractText 方法(生产项目里写成工具类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Slf4j
public final class TikaUtil {

// Tika 单例:线程安全,全局复用
private static final Tika TIKA = new Tika();

private TikaUtil() {}

/**
* 通用文本提取
* @param file 任意格式文件
* @return 纯文本内容(如果是扫描件会是空,需走 OCR 章节)
*/
public static String extractText(File file) {
try {
return TIKA.parseToString(file);
} catch (IOException | TikaException e) {
log.error("提取文本失败: {}", file.getName(), e);
throw new RuntimeException("文档解析失败", e);
}
}

/**
* 重载:InputStream
*/
public static String extractText(InputStream in, String fileName) {
try {
Metadata meta = new Metadata();
meta.set(Metadata.RESOURCE_NAME_KEY, fileName); // 给 Tika 个文件名提示,检测更准
return TIKA.parseToString(in, meta);
} catch (IOException | TikaException e) {
log.error("提取文本失败: {}", fileName, e);
throw new RuntimeException("文档解析失败", e);
}
}
}

3.2 一行用起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Demo1_BasicFormats {
public static void main(String[] args) {
Stream.of(
"合同.pdf",
"需求说明.docx",
"年度预算.xlsx",
"产品路演.pptx",
"公司简介.html",
"README.md"
).forEach(name -> {
File f = new File("/docs/" + name);
String text = TikaUtil.extractText(f);
System.out.println("===== " + name + " 前 200 字 =====");
System.out.println(text.substring(0, Math.min(200, text.length())));
});
}
}

对 Excel 特别说明:Tika 拿出来的是所有 Sheet 拼起来的纯文本,适合内容索引;如果你需要按行、按列、带公式的精细化读写,配合上一篇的 Apache Fesod,两者各司其职。

3.3 进阶:拿结构化 XHTML(保留标题/表格/段落)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 拿结构化 XHTML:标题会是 <h1><h2>,表格会是 <table>,方便后续切块做 RAG
*/
public static String extractXhtml(File file) {
AutoDetectParser parser = new AutoDetectParser();
// 注意:不是 BodyContentHandler,是 ToXMLContentHandler(或 XHTMLContentHandler)
ToXMLContentHandler handler = new ToXMLContentHandler();
Metadata metadata = new Metadata();
try (InputStream in = TikaInputStream.get(file)) {
parser.parse(in, handler, metadata, new ParseContext());
return handler.toString();
} catch (SAXException | TikaException | IOException e) {
throw new RuntimeException("结构化解析失败", e);
}
}

产出的 XHTML 交给 jsoup 或者直接正则切 </p>|<table>|</h\d>,做 RAG 的文档分块体验会好很多(不会把半句话切开)。


4)实战二:提取元数据(作者、页数、EXIF、创建时间……)

元数据在合规审计、文档管理系统里特别常用。Tika 把所有属性都放进 Metadata 这个 Map 里。

4.1 通用元数据提取 + 常用 Key 速查表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@Data
@Builder
public class DocMetaInfo {
private String mime;
private String title;
private String author;
private String creatorTool; // Word 2021 / WPS / Pages 等
private LocalDateTime createdTime;
private LocalDateTime modifiedTime;
private Integer pageCount; // PDF/PPT/Word
private Long wordCount; // 部分文档才有
private Map<String, String> raw; // 原始 KV,方便排查
}

public class DocMetaExtractor {

private static final DateTimeFormatter FMT = DateTimeFormatter.ISO_DATE_TIME;

public static DocMetaInfo extract(File file) {
Metadata meta = new Metadata();
// 只拿元数据不拿文本,ContentHandler 给空的即可
try (InputStream in = TikaInputStream.get(file)) {
new AutoDetectParser().parse(in, new DefaultHandler(), meta, new ParseContext());
} catch (Exception e) {
throw new RuntimeException("元数据提取失败", e);
}

DocMetaInfo.DocMetaInfoBuilder b = DocMetaInfo.builder()
.mime(meta.get(Metadata.CONTENT_TYPE))
.title(meta.get(Metadata.TITLE))
.author(meta.get("dc:creator")) // 作者
.creatorTool(meta.get("xmp:CreatorTool")) // 制作工具
.pageCount(parseIntOrNull(meta.get("xmpTPg:NPages")))
.wordCount(parseLongOrNull(meta.get("meta:word-count")))
.raw(toMap(meta));

// 时间标准化(各种格式 Tika 一般输出 ISO)
String created = meta.get("dcterms:created");
String modified = meta.get("dcterms:modified");
if (created != null) b.createdTime(safeParse(created));
if (modified != null) b.modifiedTime(safeParse(modified));
return b.build();
}

private static Map<String, String> toMap(Metadata meta) {
Map<String, String> m = new LinkedHashMap<>();
for (String name : meta.names()) m.put(name, meta.get(name));
return m;
}

private static LocalDateTime safeParse(String s) {
try { return LocalDateTime.parse(s.replace("Z", ""), FMT); }
catch (Exception ignore) { return null; }
}
private static Integer parseIntOrNull(String s) {
try { return s == null ? null : Integer.parseInt(s); }
catch (Exception ignore) { return null; }
}
private static Long parseLongOrNull(String s) {
try { return s == null ? null : Long.parseLong(s); }
catch (Exception ignore) { return null; }
}
}

4.2 常用元数据 Key 对照表(查资料省半天)

含义Key示例值
MIME 类型Content-Typeapplication/pdf
标题dc:title / title2026 Q3 采购合同
作者dc:creator / meta:author张三
最后修改者dc:contributor / meta:last-author李四
制作工具xmp:CreatorToolMicrosoft® Word 2021
创建时间dcterms:created2026-08-01T03:12:45Z
修改时间dcterms:modified2026-09-15T08:05:10Z
页数(PDF/PPT)xmpTPg:NPages23
字数meta:word-count15320
PDF 是否加密pdf:encryptedfalse
图片 EXIF 拍摄时间exif:DateTimeOriginal2026-05-01T14:30:00
图片宽高tiff:ImageWidth / tiff:ImageLength3840 / 2160
语言dc:language / Content-Languagezh-CN

💡 小技巧:调试时直接把 metadata.names() 全打出来,因为不同格式的 Key 名不完全一致。PDF 一般 pdf: 前缀,Office 有 meta: / extended-properties:,EXIF 是 exif:。


5)实战三:MIME 类型检测(防改后缀绕过安全规则)

只靠 fileName.endsWith(".pdf") 判断格式是系统常见漏洞——把 evil.exe 改成 contract.pdf 上传就能混过检查。Tika 用文件头 Magic Number + 内容启发式判断,更靠谱。

5.1 一行检测,支持 3 种入参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MimeDetectDemo {
private static final Tika TIKA = new Tika();

public static void main(String[] args) throws IOException {
// 1) 最快:只看文件头 Magic Number
String mime1 = TIKA.detect(new File("/tmp/看起来像PDF其实是EXE.pdf"));
System.out.println(mime1); // → application/x-dosexec (不会被骗成 PDF)

// 2) 更准:配合文件名提示(有些格式 Magic Number 重合,比如 doc/ppt/xls)
Metadata meta = new Metadata();
meta.set(Metadata.RESOURCE_NAME_KEY, "报表.xlsx");
try (InputStream in = new FileInputStream("file")) {
String mime2 = TIKA.detect(in, meta);
}

// 3) 直接用 byte[](适合 MultipartFile 拿字节)
byte[] bytes = Files.readAllBytes(Path.of("a"));
String mime3 = TIKA.detect(bytes);

// 典型值对照
// .docx → application/vnd.openxmlformats-officedocument.wordprocessingml.document
// .xlsx → application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
// .pdf → application/pdf
// .png → image/png
// .zip → application/zip
}
}

5.2 在 Spring MVC 上传里直接用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@PostMapping("/upload")
public R<String> upload(@RequestParam("file") MultipartFile file) throws IOException {
String realMime = new Tika().detect(file.getBytes(), file.getOriginalFilename());

// 只允许白名单格式
Set<String> allow = Set.of(
"application/pdf",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
);
if (!allow.contains(realMime)) {
return R.fail("文件类型非法,真实类型: " + realMime);
}

// ... 后续处理
return R.ok("ok");
}

这就把”改后缀名绕过”这条路堵死了,合规审查加分项 ✅。


6)实战四:OCR 识别扫描件 / 图片文字(Tesseract 集成)

现实中很多 PDF 本质是一堆扫描图片(比如合同扫描件、发票、票据),Tika 正常解析出来是空文本。这时候就需要 Tesseract OCR 引擎出马。

6.1 先装 Tesseract(Tika 只是调用它,不自带)

OS命令
macOSbrew install tesseract tesseract-lang(tesseract-lang 含中文包 chi_sim)
Ubuntu/Debiansudo apt install tesseract-ocr tesseract-ocr-chi-sim
RHEL/CentOSsudo yum install tesseract tesseract-langpack-chi-sim(先加 EPEL 源)
Windows去 UB-Mannheim 镜像 下载安装包,勾选”中文简体”

验证是否装对:

1
2
tesseract --version        # 应该能看到版本号
tesseract --list-langs # 要能看到 chi_sim(中文简体)、eng(英文)

6.2 Java 端一行开启 OCR(Tika 2.x 自动发现 Tesseract)

Tika 会自动去 PATH 里找 tesseract 可执行文件,找到就会对图片/扫描 PDF 自动走 OCR。所以只要环境装好了,代码完全不用改 —— 还是之前那套 parseToString!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class OcrDemo {
public static void main(String[] args) throws Exception {
Tika tika = new Tika();

// 1) 普通图片 OCR
String text1 = tika.parseToString(new File("发票扫描件.jpg"));
System.out.println(text1);

// 2) 扫描版 PDF(每页都是图片,Tika 会拆出来每页 OCR 再拼)
String text2 = tika.parseToString(new File("合同扫描件.pdf"));

// 3) 如果 PATH 里找不到 tesseract,或者想指定语言,手动在 ParseContext 里配
TesseractOCRConfig ocrConfig = new TesseractOCRConfig();
ocrConfig.setTesseractPath("/opt/homebrew/bin"); // 指定安装路径
ocrConfig.setLanguage("chi_sim+eng"); // 中文简体 + 英文混排
ParseContext ctx = new ParseContext();
ctx.set(TesseractOCRConfig.class, ocrConfig);

AutoDetectParser parser = new AutoDetectParser();
BodyContentHandler handler = new BodyContentHandler(-1);
try (InputStream in = TikaInputStream.get(new File("中文合同扫描.pdf"))) {
parser.parse(in, handler, new Metadata(), ctx);
System.out.println(handler.toString());
}
}
}

6.3 OCR 常见坑

坑解决
OCR 出来全是乱码没装中文语言包,tesseract --list-langs 看有没有 chi_sim
识别特别慢1) 图片先压缩到 150~200 DPI;2) 调 ocrConfig.setPageSegMode(6)(假设一页一个均匀文本块)
表格识别错位OCR 对表格天然弱,建议配合 PaddleOCR / TableTransformer 做专门表格提取,Tika OCR 适合正文
Tesseract 不是最新macOS brew 的版本经常落后,生产环境推荐用官方 Docker apache/tika:full,已经配好

7)实战五:Spring Boot 封装”文档解析服务”(直接搬到项目里)

前面的工具类 + 自动检测 + OCR,封装成一个 Spring Service,以后任何模块要解析文件就 @Autowired DocumentParserService 一行。

7.1 配置类:Tika Bean + 可选 OCR 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Configuration
public class TikaAutoConfig {

/**
* Tika 单例(线程安全,全局共享)
*/
@Bean
public Tika tika() {
return new Tika();
}

/**
* 如果你的项目里 OCR 需求频繁,定制一下 OCR 参数
*/
@Bean
@ConditionalOnProperty(name = "tika.ocr.enabled", havingValue = "true")
public TesseractOCRConfig ocrConfig(
@Value("${tika.ocr.tesseract-path:}") String tesseractPath,
@Value("${tika.ocr.language:chi_sim+eng}") String language) {
TesseractOCRConfig cfg = new TesseractOCRConfig();
if (StrUtil.isNotBlank(tesseractPath)) cfg.setTesseractPath(tesseractPath);
cfg.setLanguage(language);
return cfg;
}
}

7.2 业务 Service:解析 + 入库 + 建索引一把梭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
@Slf4j
@Service
@RequiredArgsConstructor
public class DocumentParserService {

private final Tika tika;
private final ObjectProvider<TesseractOCRConfig> ocrConfigProvider; // 可能没装 OCR
private final DocIndexMapper docIndexMapper; // 你的 ES / MySQL Mapper
private final ApplicationEventPublisher publisher; // 解析完成后发事件

/**
* 允许的 MIME 白名单
*/
private static final Set<String> ALLOW_MIME = Set.of(
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.ms-powerpoint",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"text/plain", "text/html", "text/markdown"
);

/**
* 对外入口:上传文件 → 校验 MIME → 解析 → 落库 → 发事件
*/
@Async("docParseExecutor")
public void parseAndSave(Long docId, MultipartFile file) throws IOException {
// 1) MIME 检测(白名单)
String realMime = tika.detect(file.getBytes(), file.getOriginalFilename());
if (!ALLOW_MIME.contains(realMime)) {
throw new IllegalArgumentException("不支持的文件类型: " + realMime);
}

// 2) 解析内容 + 元数据
ParseContext ctx = new ParseContext();
ocrConfigProvider.ifAvailable(cfg -> ctx.set(TesseractOCRConfig.class, cfg));

AutoDetectParser parser = new AutoDetectParser();
BodyContentHandler handler = new BodyContentHandler(-1);
Metadata meta = new Metadata();
meta.set(Metadata.RESOURCE_NAME_KEY, file.getOriginalFilename());

try (InputStream in = TikaInputStream.get(file.getBytes())) {
parser.parse(in, handler, meta, ctx);
} catch (SAXException | TikaException e) {
throw new RuntimeException("文档解析失败", e);
}

String content = handler.toString();
DocMetaInfo docMeta = DocMetaExtractor.extractFromMeta(meta); // 复用第 4 章的工具

if (StrUtil.isBlank(content) && realMime.startsWith("image/")) {
log.warn("文档为空内容,可能是扫描件: docId={}", docId);
}

// 3) 落库 / 发事件(ES 索引、RAG 切块 都在监听器里做,解耦)
docIndexMapper.updateParsed(docId, realMime, content.length(), docMeta,
// 内容很长建议分库存储或存对象存储
StrUtil.maxLength(content, 50_000));

publisher.publishEvent(new DocParsedEvent(docId, content, realMime, docMeta));
log.info("✅ 文档解析完成 docId={}, mime={}, 字符数={}", docId, realMime, content.length());
}
}

7.3 application.yml 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
tika:
ocr:
enabled: true # 本地没装 tesseract 就设 false
language: chi_sim+eng # 中文简体+英文混排
# tesseract-path: /opt/homebrew/bin # 不在 PATH 里时手动指定

spring:
task:
execution:
pool:
# 解析是 CPU 密集型,不要太大
thread-name-prefix: doc-parse-
core-size: 4
max-size: 8
queue-capacity: 200

8)生产环境避坑清单 ⚠️(踩一次调 3 天)

坑现象解决方案
BodyContentHandler 默认截断 10w 字符长文档后半段内容丢失一定用 new BodyContentHandler(-1),不要用默认构造器
Tika 实例反复 new性能差、GC 频繁、解析器重复初始化全局单例 Bean,Tika 是线程安全的
POI / PDFBox 版本冲突NoSuchMethodError / ClassNotFoundException引入 tika-parsers-standard-package 的 pom,把自己项目里老的 poi-ooxml / pdfbox 升到 Tika 对齐的版本,或直接 exclusions 排除
超大文件 OOM200MB+ PDF 解析到内存爆炸1) 用 TikaInputStream 包装 File(不走 byte[]);2) 设置 BodyContentHandler 字符上限;3) 超过阈值的文件单独丢 Tika Server 集群处理
加密 PDF 直接抛异常EncryptedDocumentException先 metadata.set(PDFParser.PASSWORD, pwd) 再 parse;也可以在 catch 里友好提示”请先解密”
Excel 解析”丢数据”公式、图表、批注没出来Tika 对 Excel 是”全量文本抓取”,不负责公式计算和格式;精细化业务还是用 Fesod
中文 OCR 全问号/乱码扫描件出来一堆 ???1) 没装 chi_sim 语言包;2) setLanguage("chi_sim+eng")
Zip 炸弹 / 超大压缩包recursive 解析时解压出几十 GB 塞满磁盘配置 ZipParser 的 maxExtractSize、maxMemoryBytes,或干脆禁用递归解压
Tika Server 连接超时大文件 / OCR 跑几十秒客户端把 readTimeout 调到 60s~300s;服务端 -Xmx 至少 2g

9)总结 & 学习资源

9.1 能力全景回顾

本篇讲了 Tika 的 5 个核心实战能力 + 1 套 Spring 生产封装:

场景核心代码入口章节
任意格式 → 纯文本Tika.parseToString()实战一
任意格式 → 结构化 XHTMLToXMLContentHandler实战一 3.3
元数据提取(作者/页数/时间…)Metadata + 常用 Key 表实战二
MIME 真实类型检测(防改后缀)Tika.detect()实战三
扫描件 / 图片 OCRTesseract 安装 + TesseractOCRConfig实战四
Spring Boot 通用服务DocumentParserService + 异步 + 事件实战五

9.2 和 Fesod 的分工配合(划重点)

很多同学纠结:Tika 能解析 Excel,那还要 Fesod 吗? 答案是 两者都要,分工不同:

维度Apache TikaApache Fesod
定位全格式内容索引(1000+ 种格式一把梭)Excel 精细化读写(只专注电子表格)
Excel 输出纯文本拼接、不带公式结果按行按列、批注、合并单元格、样式全保留
写 Excel❌ 不支持✅ 链式 API + 模板填充 + 多 Sheet
大文件批量导入导出❌ 不适合(没有分批入库监听器)✅ 流式读 + 分批写 + 错误报告
适用场景知识库、全文搜索、合规审计、RAG 文档解析业务系统的”用户导入/报表导出”

配合起来就是:

  1. 用户上传一份 Excel → Fesod 做精细化导入(校验字段、分批入库、错误报告);
  2. 同时丢给 Tika 做全文索引(用户在搜索框输入关键词,能搜到这份 Excel 里的内容)。

9.3 官方学习资源

资源地址
Tika 官网https://tika.apache.org/
官方文档(Getting Started)https://tika.apache.org/2.9.4/gettingstarted.html
GitHub 源码 & Exampleshttps://github.com/apache/tika
解析器支持矩阵(最全格式清单)https://tika.apache.org/2.9.4/formats.html
Tika Server Dockerapache/tika(官方镜像,full 标签带 OCR)

掌握本篇内容,以后再碰到**”把这堆各种格式的文档都给我解析了建索引”** 这种需求,再也不用熬夜写 if-else 了 🚀。下一篇打算讲 “Tika + Milvus + Spring AI 搭一套可落地的企业 RAG 知识库”,解析、切块、向量化、检索、问答全链路打通,欢迎关注~


Apache Tika 从入门到生产:一套 API 解析 PDF/Word/Excel/PPT/图片上千种格式(含 Spring Boot 封装与 OCR)
https://www.pcboy.com.cn/2026/09/30/Apache-Tika-从入门到生产:一套API解析千种格式/
作者
chituer
发布于
2026年9月30日
许可协议