Spring Boot 整合 Apache Tika 实现文件类型检测与内容提取

在实际开发中,我们经常需要处理各类文件(如PDF、Word、Excel、图片等),核心需求包括识别文件真实类型(避免后缀名欺骗)和提取文件内容/元数据(如文档正文、创建时间、作者)。Apache Tika作为Apache基金会的开源项目,能高效解决这些问题,且无需手动编写不同格式的解析逻辑。

前言

在实际开发中,我们经常需要处理各类文件(如PDF、Word、Excel、图片等),核心需求包括识别文件真实类型(避免后缀名欺骗)和提取文件内容/元数据(如文档正文、创建时间、作者)。Apache Tika作为Apache基金会的开源项目,能高效解决这些问题,且无需手动编写不同格式的解析逻辑。

核心概念

在整合前,先明确Tika3个核心组件,理解其工作原理:

  • Detector(检测器):负责识别文件的真实类型,支持通过文件头、字节流、扩展名等多维度检测,避免 “后缀名篡改” 导致的类型误判。
  • Parser(解析器):根据Detector识别的文件类型,调用对应的解析器提取文件内容(如文本)和元数据(如文件大小、修改时间),Tika内置了PDF、Office、XML等格式的解析器。
  • Metadata(元数据):存储文件的结构化信息,分为内置元数据(如Metadata.CONTENT_ENCODINGMetadata.CONTENT_TYPE)和自定义元数据(如文档作者、版本号)。

案例

依赖添加

Tika依赖较多解析库(如POI、PDFBox),可能与项目中已有的依赖冲突(如POI版本不一致),手动排除即可。

<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>
配置 Tika Bean

为避免重复创建Tika实例(提升性能),支持自定义配置(如超时时间、解析器优先级):

@Configuration
public class TikaSelfConfig {

    /**
     * 全局Tika实例(用于文件类型检测)
     */
    @Bean
    public Tika tika() throws TikaException, IOException, SAXException {
        // 自定义Tika配置:设置文件类型检测超时时间(5秒)
        TikaConfig config = new TikaConfig();
        return new org.apache.tika.Tika(config) {
            @Override
            public String detect(java.io.InputStream stream, Metadata metadata) {
                try {
                    // 超时控制:避免解析超大文件阻塞
                    return super.detect(new TimeoutInputStream(stream, 5000), metadata);
                } catch (IOException e) {
                    throw new RuntimeException("文件类型检测超时(超过5秒)", e);
                }
            }
        };
    }

    /**
     * 自动检测解析器(用于内容提取)
     */
    @Bean
    public Parser autoDetectParser() {
        // AutoDetectParser会根据文件类型自动选择解析器
        return new AutoDetectParser();
    }

    // 注册自定义解析器(在TikaConfig中添加)
    @Bean
    public Parser customParser() {
        return new CustomCsvParser();
    }
}


public class TimeoutInputStream extends InputStream {
    private final InputStream delegate;
    private final long timeoutMillis;
    private long lastReadTime;

    public TimeoutInputStream(InputStream delegate, long timeoutMillis) {
        this.delegate = delegate;
        this.timeoutMillis = timeoutMillis;
        this.lastReadTime = System.currentTimeMillis();
    }

    @Override
    public int read() throws IOException {
        checkTimeout();
        int data = delegate.read();
        if (data != -1) {
            lastReadTime = System.currentTimeMillis();
        }
        return data;
    }

    private void checkTimeout() throws IOException {
        long elapsed = System.currentTimeMillis() - lastReadTime;
        if (elapsed > timeoutMillis) {
            throw new IOException("Stream read timeout (elapsed: " + elapsed + "ms)");
        }
    }

    // 重写其他read方法(read(byte[]), read(byte[], int, int)),逻辑类似
}
功能 1:文件类型检测
@Service
public class TikaFileDetectService {

    private final Tika tika;

    // 注入全局Tika Bean
    public TikaFileDetectService(Tika tika) {
        this.tika = tika;
    }

    /**
     * 1. 基于MultipartFile(文件流)检测真实类型(推荐)
     * @param file 上传的文件
     * @return 真实MIME类型(如image/jpeg、application/pdf)
     */
    public String detectFileByStream(MultipartFile file) throws IOException {
        if (file.isEmpty()) {
            throw new IllegalArgumentException("文件不能为空");
        }

        // 元数据:可添加文件名辅助检测(非必需,但能提升准确率)
        Metadata metadata = new Metadata();
        metadata.add(TikaCoreProperties.RESOURCE_NAME_KEY, file.getOriginalFilename());

        // 通过文件流检测(Tika会读取文件头字节,不依赖后缀名)
        try (InputStream inputStream = file.getInputStream()) {
            return tika.detect(inputStream, metadata);
        }
    }

    /**
     * 2. 基于字节数组检测(适用于小文件/内存中的文件)
     * @param bytes 文件字节数组
     * @param fileName 文件名(辅助检测)
     * @return 真实MIME类型
     */
    public String detectFileByBytes(byte[] bytes, String fileName) {
        if (bytes == null || bytes.length == 0) {
            throw new IllegalArgumentException("字节数组不能为空");
        }

        return tika.detect(bytes, Metadata.TIKA_MIME_FILE);
    }

    /**
     * 3. 基于扩展名检测(仅作辅助,准确率低)
     * @param fileName 文件名(如test.pdf)
     * @return 推测的MIME类型
     */
    public String detectFileByExtension(String fileName) {
        return tika.detect(fileName);
    }
}
功能 2:文件内容与元数据提取
@Service
public class TikaContentExtractService {

    private final Parser autoDetectParser;

    // 注入自动检测解析器
    public TikaContentExtractService(Parser autoDetectParser) {
        this.autoDetectParser = autoDetectParser;
    }

    /**
     * 提取文件的文本内容(支持PDF、Word、Excel等)
     * @param file 上传的文件
     * @return 提取的纯文本
     */
    public String extractText(MultipartFile file) throws Exception {
        if (file.isEmpty()) {
            throw new IllegalArgumentException("文件不能为空");
        }

        // 1. 内容处理器:BodyContentHandler用于接收文本内容,设置容量(避免大文件OOM)
        ContentHandler contentHandler = new BodyContentHandler(10 * 1024 * 1024); // 10MB上限

        // 2. 元数据:存储文件的结构化信息
        Metadata metadata = new Metadata();
        metadata.add(TikaCoreProperties.RESOURCE_NAME_KEY, file.getOriginalFilename());

        // 3. 解析上下文:用于传递解析器所需的额外信息(如密码,适用于加密文件)
        ParseContext parseContext = new ParseContext();
        parseContext.set(Parser.class, autoDetectParser); // 绑定当前解析器

        // 4. 解析文件并提取内容
        try (InputStream inputStream = file.getInputStream()) {
            autoDetectParser.parse(inputStream, contentHandler, metadata, parseContext);
            return contentHandler.toString();
        }
    }

    /**
     * 提取文件的元数据(如创建时间、作者、文件大小)
     * @param file 上传的文件
     * @return 元数据键值对(格式化输出)
     */
    public String extractMetadata(MultipartFile file) throws Exception {
        Metadata metadata = new Metadata();
        ParseContext parseContext = new ParseContext();
        parseContext.set(Parser.class, autoDetectParser);

        try (InputStream inputStream = file.getInputStream()) {
            autoDetectParser.parse(inputStream, new BodyContentHandler(), metadata, parseContext);
        }

        // 格式化元数据输出(遍历所有元数据键)
        StringBuilder metadataStr = new StringBuilder();
        for (String name : metadata.names()) {
            metadataStr.append(name).append(": ").append(metadata.get(name)).append("\n");
        }
        return metadataStr.toString();
    }


    /**
     * 提取加密文件的文本内容(支持PDF、Word、Excel等)
     * @param file 上传的文件
     * @return 提取的纯文本
     * 1. 对旧版 Office 文档(.doc、.xls),使用 POI 的 EncryptionInfo 和 Decryptor 直接解密
     * 2. 对新版 Office 文档(.docx、.xlsx),仍使用 Tika 的 PasswordProvider
     */
    public String extractEncryptedText(MultipartFile file, String password) throws Exception {
        ContentHandler handler = new BodyContentHandler();
        Metadata metadata = new Metadata();
        metadata.add(TikaCoreProperties.RESOURCE_NAME_KEY, file.getOriginalFilename());
        metadata.set("password", password);

        AutoDetectParser autoDetectParser = new AutoDetectParser();
        ParseContext context = new ParseContext();
        context.set(PasswordProvider.class,metadata1 -> password);
        try (InputStream inputStream = file.getInputStream()) {
            autoDetectParser.parse(inputStream, handler, metadata, context);
            return handler.toString();
        }
    }
}
功能 3:自定义解析器
// 自定义解析器:处理.csv格式文件(示例,Tika已内置CSV解析器,此处仅演示扩展)
public class CustomCsvParser extends AbstractParser {

    // 声明支持的MIME类型
    @Override
    public Set<MediaType> getSupportedTypes(ParseContext context) {
        return Collections.singleton(MediaType.parse("text/csv"));
    }

    // 核心解析逻辑
    @Override
    public void parse(InputStream stream, ContentHandler handler, Metadata metadata, ParseContext context)
            throws IOException, SAXException {
        // 1. 设置CSV文件的元数据
        metadata.add("file_format", "CSV");
        metadata.add("delimiter", ",");

        // 2. 读取CSV内容并写入ContentHandler
        String csvContent = readInputStreamAsString(stream);
        handler.characters(csvContent.toCharArray(), 0, csvContent.length());
    }

    private String readInputStreamAsString(InputStream inputStream) throws IOException {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        int nRead;
        byte[] data = new byte[1024];
        while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
            buffer.write(data, 0, nRead);
        }
        buffer.flush();
        return new String(buffer.toByteArray(), StandardCharsets.UTF_8);
    }
}
    测试代码

    图片

    @Test
        public void tikaFile() throws Exception {
            File fakeImageFile = new File("D:\\文档\\自动巡检脚本部署说明.docx");
            MultipartFile mockFile = new MockMultipartFile("file",new FileInputStream(fakeImageFile));
            System.out.println(tikaContentExtractService.extractText(mockFile));
            System.out.println(tikaContentExtractService.extractMetadata(mockFile));
    //        System.out.println(tikaContentExtractService.extractEncryptedText(mockFile,"123456"));
        }

    AI大模型学习福利

    作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

    一、全套AGI大模型学习路线

    AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

    因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

    二、640套AI大模型报告合集

    这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

    因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

    三、AI大模型经典PDF籍

    随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。


    因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

    四、AI大模型商业化落地方案

    因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

    作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量

    评论
    成就一亿技术人!
    拼手气红包6.0元
    还能输入1000个字符
     
    红包 添加红包
    表情包 插入表情
     条评论被折叠 查看
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值