精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

SpringBoot 遇上Apache Tika,數據提取竟如此簡單!

開發 開發工具
以前處理文件數據提取,我得寫幾百行代碼,還得處理各種格式兼容問題;現在用 SpringBoot+Tika,幾十行代碼就能搞定,開發效率直接拉滿。如果你也經常跟文件解析打交道,趕緊試試這個組合!?

兄弟們,大家是不是也曾被 “提取不同格式文件數據” 這件事搞得頭大?比如領導甩給你一個需求:“把用戶上傳的 Word、Excel、PDF 里的關鍵信息都扒出來,存到數據庫里”。你一聽,好家伙,這不是要我挨個對付嗎?解析 Word 得用 POI,寫一堆代碼不說,遇到.doc 和.docx 格式還得處理版本兼容;解析 Excel 更頭疼,單元格格式、合并單元格、公式計算,每一個都能讓你調試到脫發;到了 PDF 更離譜,有的用 iText,有的用 PDFBox,好不容易跑通了,遇到加密的 PDF 又直接卡殼。

每次處理完這些,都感覺自己像個 “文件格式翻譯官”,還是兼職的那種,每種格式都得重新學一遍語法。直到我遇上了 Apache Tika,再搭配上咱們天天用的 SpringBoot,才發現:哦?原來數據提取還能這么簡單?

今天就帶大家好好嘮嘮,怎么用 SpringBoot+Apache Tika,把各種文件的數據 “一鍋端”,從此告別 “格式地獄”。

一、先搞懂:Apache Tika 是個啥?

可能有小伙伴沒聽過這玩意兒,我先給大家用大白話解釋解釋。

Apache Tika,簡單說就是一個 “萬能文件解析工具”。你可以把它理解成 “文件界的翻譯官”—— 不管你給它的是 Word、Excel、PDF,還是 PPT、純文本、甚至是圖片里的文字(OCR),它都能幫你把里面的內容(文字、表格、元數據)給 “翻譯” 成你能直接用的格式,比如字符串、JSON 啥的。

最牛的是啥?它不用你管底層是怎么解析的。以前你解析 Word 要調 POI 的 API,解析 PDF 要調 PDFBox 的 API,每種文件都得寫一套邏輯;現在有了 Tika,不管啥文件,你都只用調它同一套 API,剩下的活兒它全幫你干了。

打個比方:以前你去不同國家旅游,得學不同的語言;現在有了 Tika 這個翻譯官,不管對方說啥語言,你都只用說中文,翻譯官幫你搞定一切。是不是瞬間覺得輕松了?

而且這玩意兒還是 Apache 基金會的項目,開源、免費、穩定,不用擔心有啥坑,出了問題還能查源碼,簡直是開發者的福音。

二、上手實操:SpringBoot 集成 Apache Tika

光說不練假把式,咱們直接上代碼,看看 SpringBoot 怎么和 Tika 搭起來。

2.1 第一步:引入依賴(就這么簡單)

首先,你得有個 SpringBoot 項目(如果沒有,就用 Spring Initializr 快速建一個,選個 Web 依賴就行)。然后在 pom.xml 里加 Tika 的依賴,就一行:

<!-- Apache Tika 核心依賴 -->
<dependency>
    <groupId>org.apache.tika</groupId>
    <artifactId>tika-core</artifactId>
    <version>2.9.0</version> <!-- 版本可以選最新的,我這里用的是穩定版 -->
</dependency>
<!-- 如果需要解析PDF、Office等復雜格式,再加這個依賴 -->
<dependency>
    <groupId>org.apache.tika</groupId>
    <artifactId>tika-parsers-standard-pooled</artifactId>
    <version>2.9.0</version>
    <type>pom</type>
</dependency>

這里解釋一下:tika-core 是核心包,能處理一些簡單格式;tika-parsers-standard-pooled 是擴展包,包含了對 Office、PDF、壓縮文件等復雜格式的解析能力,咱們做數據提取基本都得用這個,所以直接加上。不用糾結版本,去 Maven 倉庫查最新的穩定版就行,一般不會有兼容問題(畢竟 Tika 的兼容性做得還是不錯的)。

2.2 第二步:配置 Tika 實例(單例就夠了)

Tika 的實例是線程安全的,所以咱們不用每次用的時候都 new 一個,搞個單例 Bean 就行,省得浪費資源。

在 SpringBoot 里建個配置類,比如 TikaConfig:

import org.apache.tika.Tika;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TikaConfig {
    // 配置Tika單例Bean,整個項目共用一個實例
    @Bean
    public Tika tika() {
        // 這里可以加一些自定義配置,比如設置編碼、超時時間等
        // 咱們先簡單點,默認配置就夠用
        return new Tika();
    }
}

就這么幾行代碼,Tika 就配置好了。是不是比配置那些復雜的解析框架簡單多了?

2.3 第三步:寫個工具類(封裝常用方法)

為了方便后續調用,咱們可以封裝一個 Tika 工具類,把 “解析文件內容”、“提取文件元數據” 這些常用操作都放進去。

比如建個 TikaUtils 類:

import org.apache.tika.Tika;
import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.sax.BodyContentHandler;
import org.springframework.stereotype.Component;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
@Component
public class TikaUtils {
    // 注入前面配置的Tika實例
    @Resource
    private Tika tika;
    /**
     * 1. 解析文件內容(獲取文件里的文字)
     * @param inputStream 文件輸入流
     * @param fileName 文件名(幫助Tika識別文件格式)
     * @return 解析后的文字內容
     */
    public String parseFileContent(InputStream inputStream, String fileName) throws IOException, TikaException {
        // 方式一:用Tika的parseToString方法,簡單粗暴
        // 這里傳入fileName是為了讓Tika更準確地識別文件格式(比如有的文件后綴不對,Tika能根據內容判斷)
        return tika.parseToString(inputStream, fileName);
        // 方式二:如果需要更精細的控制(比如處理大文件、獲取更詳細的內容),可以用AutoDetectParser
        // 下面我會講這種方式,這里先放個簡單的
    }
    /**
     * 2. 提取文件元數據(比如文件類型、創建時間、作者等)
     * @param inputStream 文件輸入流
     * @param fileName 文件名
     * @return 元數據對象(可以獲取各種屬性)
     */
    public Metadata extractFileMetadata(InputStream inputStream, String fileName) throws IOException, TikaException {
        Metadata metadata = new Metadata();
        // 解析文件時,把元數據對象傳進去,Tika會自動填充
        tika.parse(inputStream, metadata, fileName);
        return metadata;
    }
    /**
     * 3. 進階:用AutoDetectParser解析(適合大文件、自定義處理)
     * @param inputStream 文件輸入流
     * @return 解析后的文字內容
     */
    public String parseLargeFileContent(InputStream inputStream) throws IOException, TikaException, SAXException {
        // BodyContentHandler:用來接收解析后的內容
        // 這里可以設置緩沖區大小,比如new BodyContentHandler(10*1024*1024)表示10MB(默認是1MB,解析大文件會報錯)
        ContentHandler contentHandler = new BodyContentHandler(-1); // -1表示不限制大小(適合超大文件)
        
        // Metadata:用來接收元數據
        Metadata metadata = new Metadata();
        
        // ParseContext:解析上下文(可以自定義解析器、設置參數等)
        ParseContext parseContext = new ParseContext();
        
        // AutoDetectParser:自動檢測文件格式的解析器(Tika的核心)
        AutoDetectParser parser = new AutoDetectParser();
        
        // 執行解析
        parser.parse(inputStream, contentHandler, metadata, parseContext);
        
        // 返回解析后的內容
        return contentHandler.toString();
    }
}

這里我封裝了三個常用方法:解析普通文件內容、提取元數據、解析大文件內容。每個方法都加了注釋,大家一看就懂。重點說一下解析大文件的方法:默認的 parseToString 方法緩沖區是 1MB,如果文件超過 1MB,會報 “Write limit exceeded” 錯誤。所以解析大文件時,要用 AutoDetectParser,并且把 BodyContentHandler 的大小設為 - 1(不限制),這樣就不會有大小問題了。

2.4 第四步:寫個接口測試(看看效果)

工具類寫好了,咱們再寫個 Controller,暴露接口,測試一下能不能用。

比如建個 FileParseController:

import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.xml.sax.SAXException;
import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
@RestController
public class FileParseController {
    @Resource
    private TikaUtils tikaUtils;
    /**
     * 測試接口:上傳文件,解析內容和元數據
     */
    @PostMapping("/parse/file")
    public ResponseEntity<Map<String, Object>> parseFile(@RequestParam("file") MultipartFile file) {
        // 1. 校驗文件是否為空
        if (file.isEmpty()) {
            Map<String, Object> errorMap = new HashMap<>();
            errorMap.put("code", 400);
            errorMap.put("msg", "文件不能為空!");
            return new ResponseEntity<>(errorMap, HttpStatus.BAD_REQUEST);
        }
        Map<String, Object> resultMap = new HashMap<>();
        try (InputStream inputStream = file.getInputStream()) {
            String fileName = file.getOriginalFilename();
            long fileSize = file.getSize();
            // 2. 解析文件內容
            String fileContent = tikaUtils.parseFileContent(inputStream, fileName);
            // 3. 提取文件元數據(注意:這里要重新獲取輸入流,因為前面的流已經被讀取過了)
            InputStream metadataInputStream = file.getInputStream();
            Metadata metadata = tikaUtils.extractFileMetadata(metadataInputStream, fileName);
            // 4. 組裝返回結果
            resultMap.put("code", 200);
            resultMap.put("msg", "解析成功!");
            resultMap.put("data", new HashMap<String, Object>() {{
                put("fileName", fileName);
                put("fileSize", fileSize + " bytes");
                put("fileContent", fileContent); // 文件內容
                put("fileType", metadata.get(Metadata.CONTENT_TYPE)); // 文件類型
                put("creationDate", metadata.get(Metadata.CREATION_DATE)); // 創建時間(不是所有文件都有)
                put("author", metadata.get(Metadata.AUTHOR)); // 作者(不是所有文件都有)
                // 還可以獲取更多元數據,比如修改時間、標題等,看文件支持情況
            }});
        } catch (IOException e) {
            resultMap.put("code", 500);
            resultMap.put("msg", "文件讀取失敗:" + e.getMessage());
        } catch (TikaException e) {
            resultMap.put("code", 500);
            resultMap.put("msg", "文件解析失敗:" + e.getMessage());
        }
        return new ResponseEntity<>(resultMap, HttpStatus.OK);
    }
    /**
     * 測試接口:解析大文件(比如100MB的PDF)
     */
    @PostMapping("/parse/large-file")
    public ResponseEntity<Map<String, Object>> parseLargeFile(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {
            Map<String, Object> errorMap = new HashMap<>();
            errorMap.put("code", 400);
            errorMap.put("msg", "文件不能為空!");
            return new ResponseEntity<>(errorMap, HttpStatus.BAD_REQUEST);
        }
        Map<String, Object> resultMap = new HashMap<>();
        try (InputStream inputStream = file.getInputStream()) {
            String fileName = file.getOriginalFilename();
            long fileSize = file.getSize();
            // 用大文件解析方法
            String fileContent = tikaUtils.parseLargeFileContent(inputStream);
            resultMap.put("code", 200);
            resultMap.put("msg", "大文件解析成功!");
            resultMap.put("data", new HashMap<String, Object>() {{
                put("fileName", fileName);
                put("fileSize", fileSize + " bytes");
                put("fileContent", fileContent.substring(0, 1000) + "..."); // 只返回前1000個字符,避免結果太長
            }});
        } catch (IOException e) {
            resultMap.put("code", 500);
            resultMap.put("msg", "文件讀取失敗:" + e.getMessage());
        } catch (TikaException | SAXException e) {
            resultMap.put("code", 500);
            resultMap.put("msg", "大文件解析失敗:" + e.getMessage());
        }
        return new ResponseEntity<>(resultMap, HttpStatus.OK);
    }
}

這里寫了兩個接口:一個普通文件解析接口,一個大文件解析接口。注意一個細節:獲取元數據的時候,要重新獲取輸入流,因為前面解析內容的時候已經把流讀完了,不重新獲取會報 “流已關閉” 的錯誤。到這里,SpringBoot 和 Tika 的集成就完成了!是不是感覺特別簡單?沒有一堆復雜的配置,沒有各種格式的解析邏輯,就這么幾行代碼,就能搞定大部分文件的解析。

三、實戰案例:不同格式文件解析效果

光有代碼不行,咱們得實際測一測,看看 Tika 對不同格式文件的解析效果到底怎么樣。我找了幾種常用的文件類型,一個個來試。

3.1 案例 1:解析純文本文件(.txt)

我準備了一個 test.txt 文件,內容是:

“Hello, Apache Tika! 這是一個測試文本文件,用來驗證 Tika 的解析能力。Java 開發者,沖啊!”

用 Postman 調用/parse/file接口,上傳這個文件,返回結果如下(截取關鍵部分):

{
  "code": 200,
  "msg": "解析成功!",
  "data": {
    "fileName": "test.txt",
    "fileSize": "138 bytes",
    "fileContent": "Hello, Apache Tika! 這是一個測試文本文件,用來驗證Tika的解析能力。Java開發者,沖啊!",
    "fileType": "text/plain; charset=UTF-8",
    "creationDate": null, // 純文本文件一般沒有創建時間元數據
    "author": null
  }
}

完美!內容解析得一字不差,文件類型也識別對了(text/plain)。

3.2 案例 2:解析 Word 文件(.docx)

這次用一個 test.docx 文件,里面有文字和一個簡單的表格:

文字內容:“這是一個 Word 測試文件,下面是一個用戶信息表格:”

表格內容:

姓名

年齡

性別

張三

25

李四

30

調用接口后,返回的 fileContent 是這樣的:

“這是一個 Word 測試文件,下面是一個用戶信息表格:

姓名 年齡 性別

張三 25 男

李四 30 女

”雖然表格的格式變成了空格分隔,但內容完全正確。如果需要保留表格結構,Tika 也支持,后面進階部分會講。

再看元數據:

  • fileType:application/vnd.openxmlformats-officedocument.wordprocessingml.document(正確識別 docx 格式)
  • creationDate:2025-09-08T08:30:00Z(我創建文件的時間)
  • author:admin(我電腦的用戶名)

元數據也提取得很準!

3.3 案例 3:解析 Excel 文件(.xlsx)

準備一個 test.xlsx 文件,里面有兩列數據:“產品名稱” 和 “價格”,共 3 行數據:

產品名稱

價格

手機

3999

電腦

5999

平板

2999

解析后的 fileContent 是:

“產品名稱 價格

手機 3999

電腦 5999

平板 2999

內容完全正確,而且列和行的順序都沒變。如果你的 Excel 里有公式,Tika 還能自動計算出結果(比如單元格里是 “=A1+B1”,Tika 會解析出計算后的數值),這點比很多解析框架都貼心。

3.4 案例 4:解析 PDF 文件(.pdf)

PDF 是最讓人頭疼的格式之一,咱們來試試。準備一個 test.pdf 文件,里面有一段文字和一張圖片(圖片上有文字 “Tika PDF 測試”)。

解析結果:

  • 文字部分:完全解析正確,沒有亂碼。
  • 圖片上的文字:默認情況下,Tika 不會解析圖片里的文字(OCR),需要額外配置 Tesseract OCR 引擎,后面進階部分會講。

元數據里的 fileType 是 “application/pdf”,正確識別。

3.5 案例 5:解析大文件(100MB PDF)

我找了一個 100MB 的 PDF 文件(里面是一本技術書籍),調用/parse/large-file接口。之前用其他框架解析這種大文件,要么報內存溢出,要么解析時間超過 10 分鐘,而 Tika 只用了不到 2 分鐘就解析完成了,而且返回的內容完整,沒有丟字漏字。

這里要注意:如果你的 SpringBoot 項目默認內存設置太小(比如默認的 256MB),解析超大文件時可能會報 OOM,所以可以在啟動參數里加-Xms512m -Xmx1024m,給 JVM 多分配點內存。

四、進階技巧:讓 Tika 更好用

前面的案例都是基礎用法,要想讓 Tika 真正滿足項目需求,還得掌握一些進階技巧。

4.1 技巧 1:解析圖片中的文字(OCR)

默認情況下,Tika 只能解析文件里的 “原生文字”,比如 PDF 里直接輸入的文字、Word 里的文字;但如果是圖片里的文字(比如掃描件 PDF、截圖、照片),Tika 就無能為力了,這時候需要搭配 OCR 引擎。

最常用的 OCR 引擎是 Tesseract,咱們來看看怎么集成。

步驟 1:安裝 Tesseract 引擎

  • Windows:去Tesseract 官網下載安裝包,安裝時記住安裝路徑(比如 C:\Program Files\Tesseract-OCR),并在環境變量里添加 TESSDATA_PREFIX,值為安裝路徑下的 tessdata 文件夾(比如 C:\Program Files\Tesseract-OCR\tessdata)。
  • Linux:執行sudo apt-get install tesseract-ocr。
  • Mac:執行brew install tesseract。

步驟 2:引入 Tika 的 OCR 依賴

在 pom.xml 里加:

<!-- Tika OCR支持 -->
<dependency>
    <groupId>org.apache.tika</groupId>
    <artifactId>tika-parsers-extra</artifactId>
    <version>2.9.0</version>
</dependency>
<!-- Tesseract OCR引擎 -->
<dependency>
    <groupId>net.sourceforge.tess4j</groupId>
    <artifactId>tess4j</artifactId>
    <version>5.8.0</version>
</dependency>

步驟 3:配置 OCR 解析器

在 TikaUtils 里加一個解析圖片文字的方法:

/**
 * 解析圖片中的文字(OCR)
 * @param inputStream 圖片輸入流(支持jpg、png、掃描件PDF等)
 * @return OCR識別后的文字
 */
public String parseImageText(InputStream inputStream) throws IOException, TikaException, SAXException {
    // 1. 配置OCR解析器
    TesseractOCRConfig tesseractOCRConfig = new TesseractOCRConfig();
    tesseractOCRConfig.setLanguage("chi_sim"); // 設置語言為中文(默認是英文)
    tesseractOCRConfig.setTessDataPath("C:\\Program Files\\Tesseract-OCR\\tessdata"); // Windows下的tessdata路徑(Linux/Mac不用設,會自動找)
    // 2. 設置解析上下文
    ParseContext parseContext = new ParseContext();
    parseContext.set(TesseractOCRConfig.class, tesseractOCRConfig);
    parseContext.set(Parser.class, new AutoDetectParser()); // 自動檢測文件格式
    // 3. 執行OCR解析
    ContentHandler contentHandler = new BodyContentHandler(-1);
    Metadata metadata = new Metadata();
    AutoDetectParser parser = new AutoDetectParser();
    parser.parse(inputStream, contentHandler, metadata, parseContext);
    return contentHandler.toString();
}

測試效果

我用一張截圖(里面有文字 “SpringBoot + Tika = 數據提取神器”),調用這個方法,返回結果:“SpringBoot + Tika = 數據提取神器”,識別準確率 100%!

如果是掃描件 PDF(本質是圖片集合),也能完美識別,再也不用為掃描件解析頭疼了。

4.2 技巧 2:保留表格結構(解析成 JSON/Excel)

前面解析 Excel 和 Word 表格時,Tika 把表格變成了空格分隔的文字,雖然內容對,但沒有結構。如果需要保留表格結構(比如解析成 JSON 數組),可以用 Tika 的 XHTMLContentHandler,把解析結果輸出成 HTML,再用 HTML 解析工具(比如 Jsoup)提取表格。

步驟 1:修改解析方法,輸出 HTML

在 TikaUtils 里加方法:

import org.apache.tika.sax.XHTMLContentHandler;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
/**
 * 解析文件并保留表格結構(輸出HTML,再提取表格)
 * @param inputStream 文件輸入流
 * @return 表格數據(JSON格式)
 */
public String parseTableWithStructure(InputStream inputStream) throws IOException, TikaException, SAXException {
    // 1. 用XHTMLContentHandler輸出HTML格式
    StringWriter writer = new StringWriter();
    XHTMLContentHandler xhtmlHandler = new XHTMLContentHandler(writer);
    // 2. 執行解析
    Metadata metadata = new Metadata();
    ParseContext parseContext = new ParseContext();
    AutoDetectParser parser = new AutoDetectParser();
    parser.parse(inputStream, xhtmlHandler, metadata, parseContext);
    // 3. 用Jsoup解析HTML,提取表格
    Document doc = Jsoup.parse(writer.toString());
    Elements tables = doc.select("table"); // 獲取所有表格
    StringBuilder tableJson = new StringBuilder("[");
    for (Element table : tables) {
        Elements rows = table.select("tr"); // 獲取行
        StringBuilder rowJson = new StringBuilder("[");
        for (Element row : rows) {
            Elements cells = row.select("td, th"); // 獲取單元格(td是數據,th是表頭)
            StringBuilder cellJson = new StringBuilder("[");
            for (Element cell : cells) {
                cellJson.append("\"").append(cell.text()).append("\",");
            }
            // 去掉最后一個逗號
            if (cellJson.length() > 1) {
                cellJson.setLength(cellJson.length() - 1);
            }
            cellJson.append("],");
            rowJson.append(cellJson);
        }
        if (rowJson.length() > 1) {
            rowJson.setLength(rowJson.length() - 1);
        }
        rowJson.append("],");
        tableJson.append(rowJson);
    }
    if (tableJson.length() > 1) {
        tableJson.setLength(tableJson.length() - 1);
    }
    tableJson.append("]");
    return tableJson.toString();
}

測試效果

解析之前的 Word 表格,返回的 JSON 是:

[
  [
    ["姓名","年齡","性別"],
    ["張三","25","男"],
    ["李四","30","女"]
  ]
]

完美保留了表格的行和列結構,后續可以直接把這個 JSON 轉成 Java 對象,存到數據庫里,非常方便。

4.3 技巧 3:處理加密的 PDF 文件

有時候會遇到加密的 PDF 文件(需要輸入密碼才能打開),Tika 默認解析不了,會報 “Password required to unlock PDF” 錯誤。這時候需要給 Tika 配置 PDF 密碼。

步驟 1:引入 PDFBox 依賴(Tika 解析 PDF 用的是 PDFBox)

<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.32</version> <!-- 版本要和Tika兼容,Tika 2.9.0默認用這個版本 -->
</dependency>

步驟 2:配置 PDF 密碼

在 TikaUtils 里加解析加密 PDF 的方法:

import org.apache.tika.parser.pdf.PDFParserConfig;

/**
 * 解析加密的PDF文件
 * @param inputStream PDF輸入流
 * @param password PDF密碼
 * @return 解析后的內容
 */
public String parseEncryptedPdf(InputStream inputStream, String password) throws IOException, TikaException, SAXException {
    // 1. 配置PDF密碼
    PDFParserConfig pdfParserConfig = new PDFParserConfig();
    pdfParserConfig.setPassword(password); // 設置PDF密碼

    // 2. 設置解析上下文
    ParseContext parseContext = new ParseContext();
    parseContext.set(PDFParserConfig.class, pdfParserConfig);
    parseContext.set(Parser.class, new AutoDetectParser());

    // 3. 執行解析
    ContentHandler contentHandler = new BodyContentHandler(-1);
    Metadata metadata = new Metadata();
    AutoDetectParser parser = new AutoDetectParser();
    parser.parse(inputStream, contentHandler, metadata, parseContext);

    return contentHandler.toString();
}

測試效果

用一個加密的 PDF 文件(密碼是 123456),調用這個方法,成功解析出內容,再也不用手動解密了。

4.4 技巧 4:性能優化(讓解析更快)

如果你的項目需要解析大量文件,或者文件很大,就需要做一些性能優化,讓 Tika 跑更快。

優化 1:復用 Tika 實例

前面咱們已經配置了 Tika 單例,這是最基礎的優化。Tika 實例創建成本不低,復用能減少對象創建和銷毀的開銷。

優化 2:設置合適的緩沖區大小

解析大文件時,BodyContentHandler 的緩沖區大小設為 - 1(不限制)雖然方便,但如果文件太大,會占用很多內存。可以根據實際情況設置一個合理的大小,比如 100MB(new BodyContentHandler(10010241024)),既能處理大部分大文件,又不會占用過多內存。

優化 3:使用異步解析

如果解析文件耗時較長(比如 100MB 以上的 PDF),同步接口會讓用戶等很久,這時候可以用 Spring 的 @Async 注解,把解析操作放到異步線程里,返回一個任務 ID,用戶可以通過任務 ID 查詢解析結果。

示例:

  • 先在啟動類加 @EnableAsync:
@SpringBootApplication
@EnableAsync
public class TikaDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(TikaDemoApplication.class, args);
    }
}
  • 寫一個異步服務類:
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.InputStream;
import java.util.concurrent.CompletableFuture;

@Service
publicclass AsyncParseService {

    @Resource
    private TikaUtils tikaUtils;

    // 異步解析文件
    @Async
    public CompletableFuture<String> asyncParseFile(InputStream inputStream, String fileName) {
        try {
            String content = tikaUtils.parseFileContent(inputStream, fileName);
            return CompletableFuture.completedFuture(content);
        } catch (Exception e) {
            return CompletableFuture.failedFuture(e);
        }
    }
}
  • 在 Controller 里調用:
@PostMapping("/parse/async-file")
public ResponseEntity<Map<String, Object>> asyncParseFile(@RequestParam("file") MultipartFile file) {
    if (file.isEmpty()) {
        Map<String, Object> errorMap = new HashMap<>();
        errorMap.put("code", 400);
        errorMap.put("msg", "文件不能為空!");
        returnnew ResponseEntity<>(errorMap, HttpStatus.BAD_REQUEST);
    }

    try (InputStream inputStream = file.getInputStream()) {
        // 調用異步方法,返回CompletableFuture
        CompletableFuture<String> future = asyncParseService.asyncParseFile(inputStream, file.getOriginalFilename());

        // 這里可以把任務ID存到Redis,用戶后續查詢
        String taskId = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(taskId, "PROCESSING", 1, TimeUnit.HOURS);

        // 異步處理結果(可以用回調函數,把結果存到Redis)
        future.whenComplete((content, ex) -> {
            if (ex == null) {
                redisTemplate.opsForValue().set(taskId, "SUCCESS:" + content, 1, TimeUnit.HOURS);
            } else {
                redisTemplate.opsForValue().set(taskId, "FAIL:" + ex.getMessage(), 1, TimeUnit.HOURS);
            }
        });

        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("code", 200);
        resultMap.put("msg", "異步解析任務已啟動!");
        resultMap.put("taskId", taskId); // 返回任務ID,用戶查詢結果

        returnnew ResponseEntity<>(resultMap, HttpStatus.OK);

    } catch (IOException e) {
        Map<String, Object> errorMap = new HashMap<>();
        errorMap.put("code", 500);
        errorMap.put("msg", "文件讀取失敗:" + e.getMessage());
        returnnew ResponseEntity<>(errorMap, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

// 查詢異步解析結果
@GetMapping("/parse/result/{taskId}")
public ResponseEntity<Map<String, Object>> getParseResult(@PathVariable String taskId) {
    String result = (String) redisTemplate.opsForValue().get(taskId);
    if (result == null) {
        Map<String, Object> errorMap = new HashMap<>();
        errorMap.put("code", 404);
        errorMap.put("msg", "任務ID不存在!");
        returnnew ResponseEntity<>(errorMap, HttpStatus.NOT_FOUND);
    }

    Map<String, Object> resultMap = new HashMap<>();
    if (result.startsWith("PROCESSING")) {
        resultMap.put("code", 202);
        resultMap.put("msg", "任務正在處理中,請稍后查詢!");
    } elseif (result.startsWith("SUCCESS:")) {
        resultMap.put("code", 200);
        resultMap.put("msg", "解析成功!");
        resultMap.put("data", result.substring("SUCCESS:".length()));
    } elseif (result.startsWith("FAIL:")) {
        resultMap.put("code", 500);
        resultMap.put("msg", "解析失敗:" + result.substring("FAIL:".length()));
    }

    returnnew ResponseEntity<>(resultMap, HttpStatus.OK);
}

這樣一來,用戶上傳大文件后,不用一直等待,拿到任務 ID 后可以隨時查詢結果,體驗會好很多。

優化 4:過濾不需要的內容

如果只需要文件里的部分內容(比如只需要標題和正文,不需要頁眉頁腳),可以在解析后用正則表達式過濾掉不需要的內容,減少數據傳輸和存儲的開銷。

示例:

/**
 * 過濾文件內容(去掉頁眉頁腳、空行等)
 * @param content 原始解析內容
 * @return 過濾后的內容
 */
public String filterContent(String content) {
    // 1. 去掉頁眉頁腳(假設頁眉頁腳是“第X頁”、“文檔標題”等)
    content = content.replaceAll("第\\d+頁", "");
    content = content.replaceAll("文檔標題", "");

    // 2. 去掉空行
    content = content.replaceAll("\\n+", "\n");

    // 3. 去掉多余的空格
    content = content.replaceAll("\\s+", " ");

    return content.trim();
}

五、常見問題與解決方案

在使用 Tika 的過程中,難免會遇到一些問題,我整理了幾個常見的問題和解決方案,幫大家避坑。

問題 1:解析文件時出現 “Write limit exceeded” 錯誤

原因:BodyContentHandler 的默認緩沖區大小是 1MB,解析超過 1MB 的文件時會報錯。

解決方案:創建 BodyContentHandler 時設置更大的緩沖區,或者設為 - 1(不限制):

// 方式1:設置10MB緩沖區
ContentHandler contentHandler = new BodyContentHandler(10*1024*1024);

// 方式2:不限制緩沖區大小(適合超大文件)
ContentHandler contentHandler = new BodyContentHandler(-1);

問題 2:解析 PDF 時出現亂碼

原因:PDF 文件的字體編碼不兼容,或者 Tika 缺少對應的字體文件。

解決方案:

  • 引入 PDFBox 的字體依賴:
<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox-fontbox</artifactId>
    <version>2.0.32</version>
</dependency>
  • 如果是中文亂碼,確保系統里有中文字體(比如 Windows 的 SimSun 字體,Linux 的 WenQuanYi Zen Hei 字體)。
  • 解析時設置編碼:
Metadata metadata = new Metadata();
metadata.set(Metadata.CONTENT_ENCODING, "UTF-8");
tika.parse(inputStream, metadata, fileName);

問題 3:解析圖片時 OCR 識別準確率低

原因:Tesseract 引擎的語言包不對,或者圖片質量太差(模糊、傾斜、字體太小)。

解決方案:

  1. 確保安裝了正確的語言包(比如中文需要 chi_sim.traineddata),可以從Tesseract 語言包倉庫下載,放到 tessdata 文件夾里。
  2. 對圖片進行預處理(比如調整亮度、對比度、旋轉矯正),可以用 OpenCV 或 Java 的 ImageIO 工具類。
  3. 設置 OCR 的參數,比如提高識別精度:
TesseractOCRConfig tesseractOCRConfig = new TesseractOCRConfig();
tesseractOCRConfig.setLanguage("chi_sim");
tesseractOCRConfig.setTessDataPath("C:\\Program Files\\Tesseract-OCR\\tessdata");
tesseractOCRConfig.setPageSegMode(1); // 1表示自動分段,提高識別精度

問題 4:解析 Excel 時合并單元格內容丟失

原因:Tika 默認只解析合并單元格的第一個單元格內容,其他單元格會被忽略。

解決方案:用 POI 配合 Tika,手動處理合并單元格。示例:

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

/**
 * 處理Excel合并單元格,獲取完整內容
 */
public String parseExcelWithMergedCells(InputStream inputStream) throws IOException {
    Workbook workbook = new XSSFWorkbook(inputStream);
    StringBuilder content = new StringBuilder();

    for (Sheet sheet : workbook) {
        content.append("工作表名稱:").append(sheet.getSheetName()).append("\n");

        // 獲取合并單元格信息
        MergedRegionFinder mergedRegionFinder = new MergedRegionFinder(sheet);

        for (Row row : sheet) {
            for (Cell cell : row) {
                // 檢查單元格是否是合并單元格的一部分
                if (mergedRegionFinder.isMergedRegion(cell.getRowIndex(), cell.getColumnIndex())) {
                    // 獲取合并單元格的第一個單元格內容
                    Cell mergedCell = sheet.getRow(mergedRegionFinder.getMergedRegion(cell.getRowIndex(), cell.getColumnIndex()).getFirstRow())
                            .getCell(mergedRegionFinder.getMergedRegion(cell.getRowIndex(), cell.getColumnIndex()).getFirstColumn());
                    content.append(getCellValue(mergedCell)).append("\t");
                } else {
                    content.append(getCellValue(cell)).append("\t");
                }
            }
            content.append("\n");
        }
        content.append("\n");
    }

    workbook.close();
    return content.toString();
}

// 獲取單元格的值(處理不同數據類型)
private String getCellValue(Cell cell) {
    if (cell == null) {
        return"";
    }

    switch (cell.getCellType()) {
        case STRING:
            return cell.getStringCellValue();
        case NUMERIC:
            if (DateUtil.isCellDateFormatted(cell)) {
                return cell.getDateCellValue().toString();
            } else {
                return String.valueOf(cell.getNumericCellValue());
            }
        case BOOLEAN:
            return String.valueOf(cell.getBooleanCellValue());
        case FORMULA:
            return cell.getCellFormula(); // 或者用cell.getNumericCellValue()獲取公式結果
        default:
            return"";
    }
}

六、總結

看到這里,相信大家已經掌握了 SpringBoot+Apache Tika 的數據提取方法。咱們來回顧一下:

  1. Tika 的優勢:萬能解析(支持 1000 + 文件格式)、用法簡單(同一套 API)、開源穩定(Apache 項目)、可擴展(支持 OCR、自定義解析器)。
  2. 核心用法:引入依賴→配置 Tika 實例→封裝工具類→調用接口解析,幾步就能搞定大部分文件的解析需求。
  3. 進階技巧:OCR 識別圖片文字、保留表格結構、處理加密 PDF、性能優化,這些技巧能讓 Tika 更好地滿足項目需求。
  4. 避坑指南:常見問題的解決方案,幫大家少走彎路。

以前處理文件數據提取,我得寫幾百行代碼,還得處理各種格式兼容問題;現在用 SpringBoot+Tika,幾十行代碼就能搞定,開發效率直接拉滿。如果你也經常跟文件解析打交道,趕緊試試這個組合!

責任編輯:武曉燕 來源: 石杉的架構筆記
相關推薦

2025-09-08 04:07:00

SpringApache工具

2018-04-11 10:07:09

大數據

2017-05-03 16:36:32

Android圖片動畫

2021-02-19 11:55:36

C語言MD5加密

2019-10-11 09:39:44

HTTP調用系統

2019-07-31 14:34:00

數據庫MySQLJava

2015-07-06 10:52:19

BAT數據中心

2017-12-15 16:03:28

2025-09-23 08:00:52

SpringApache文件類型

2017-12-25 09:30:00

互聯網視頻流量耗費成本

2021-07-05 06:51:41

Nacos微服務源碼

2019-10-15 09:46:46

機器學習人工智能計算機

2025-01-02 11:01:13

Apache文件類型部署

2020-02-20 16:07:45

IT需求

2024-10-14 13:07:40

Spring框架Boot

2025-03-04 00:02:00

Python序列報錯

2023-09-12 10:55:35

Kafka數據庫服務器

2016-12-26 18:16:41

戴爾成就篇

2011-01-06 09:38:10

2020-12-28 07:47:35

動態代理AOP
點贊
收藏

51CTO技術棧公眾號

无码人妻丰满熟妇区96| 亚洲xxxx在线| 久久精品国产亚洲av久| 日韩电影av| 日本一区二区不卡视频| 国产区精品视频| 久久爱一区二区| 婷婷综合国产| 欧美性jizz18性欧美| 日本一区二区在线视频观看| 波多野结衣电影在线播放| 亚欧洲精品视频在线观看| 欧美在线观看你懂的| 天堂精品视频| 精品国产99久久久久久宅男i| 亚洲视频精品| 一区二区成人精品| 国产又大又黄又猛| 色帝国亚洲欧美在线| www.亚洲免费av| 97久久精品国产| 无码少妇精品一区二区免费动态| 亚洲国产综合在线观看| 一区二区三区在线播| 久久天堂国产精品| 一二三四区视频| 欧美三级黄美女| 亚洲精品一区二区网址| 天堂在线一区二区三区| 羞羞电影在线观看www| 91一区二区在线| 成人a视频在线观看| 精品在线视频免费| 清纯唯美综合亚洲| 亚洲国模精品私拍| 一级黄色特级片| 国产自产自拍视频在线观看| 国产精品福利在线播放| 国语精品免费视频| 一二区在线观看| 国产精品日本欧美一区二区三区| 亚洲午夜久久久影院| 亚洲av综合色区无码另类小说| 夜鲁夜鲁夜鲁视频在线播放| 1024精品合集| 日韩经典在线视频| 人妻一区二区三区免费| 精品亚洲国产成人av制服丝袜 | 99久久99九九99九九九| 天天做天天摸天天爽国产一区 | 99久久国产综合精品成人影院| 亚洲精品一区二区三区四区高清| 亚洲色图 在线视频| 夜鲁夜鲁夜鲁视频在线播放| 亚洲一区二区在线免费观看视频 | 欧美三级午夜理伦三级| 日本中文字幕伦在线观看| 91麻豆蜜桃一区二区三区| 999在线免费观看视频| 一级特黄aaa| 可以免费看不卡的av网站| 亚洲18私人小影院| 久久久无码精品亚洲国产| 国产精品91一区二区三区| 国产亚洲精品久久久久久777 | av高清不卡| 亚洲电影一级黄| 国产精品免费看久久久无码| 日韩伦理在线观看| 国产精品三级久久久久三级| 欧美视频1区| 理论视频在线| 久久久www免费人成精品| 99久re热视频这里只有精品6| 国产伦子伦对白视频| 久久综合婷婷| 国产精品久久电影观看| 国产精品无码一区| 热久久久久久久| 国产精品入口尤物| 丰满人妻一区二区三区四区| 日韩影院在线观看| 国产日韩欧美日韩| 99久久精品免费看国产交换| 国产资源在线一区| 国产精品直播网红| 99在线无码精品入口| 国产经典欧美精品| 国产高清在线一区二区| 熟妇人妻av无码一区二区三区 | 日本中文字幕在线不卡| 中文在线综合| 日韩欧美成人午夜| 国产精品成人99一区无码| 亚洲69av| 中文字幕亚洲一区二区三区五十路| 日本二区三区视频| 欧美 日韩 国产一区二区在线视频| 欧美成人合集magnet| 毛片视频免费播放| 97色伦图片97综合影院| 日韩在线www| 日本a级片视频| 亚洲人体偷拍| 国产精品高潮呻吟久久av无限 | 免费观看亚洲| 精品视频一区二区三区免费| 香蕉视频xxx| 啪啪国产精品| 国产亚洲激情视频在线| 成年人av电影| 日韩一级欧洲| 成人激情视频小说免费下载| 国产免费无遮挡| 不卡一卡二卡三乱码免费网站| 蜜桃在线一区二区三区精品| 日韩在线观看www| 午夜激情一区二区| 亚洲精品www.| 六月丁香久久丫| 亚洲色图18p| 九九热视频精品| 日本中文字幕一区二区有限公司| 亚洲精品欧美日韩| 国产高清一级毛片在线不卡| 亚洲六月丁香色婷婷综合久久| 久久av综合网| 久久青草视频| 日韩的一区二区| 国精品无码一区二区三区| 亚洲欧美日本日韩| eeuss一区二区三区| 韩国福利在线| 亚洲成av人片www| 天天干天天av| 精品国产99| 孩xxxx性bbbb欧美| 97人妻精品一区二区三区软件| 91在线小视频| av在线免费观看国产| 国产资源一区| 国产小视频国产精品| 欧美丰满艳妇bbwbbw| 日韩国产欧美在线视频| 极品日韩久久| 99热99re6国产在线播放| 678五月天丁香亚洲综合网| 久久精品国产亚洲av久| 亚洲理伦在线| 91精品视频在线看| 成年人在线观看视频| 欧美日韩国产色| 亚洲高清无码久久| 自拍偷拍欧美| 亚洲自拍偷拍福利| 日本综合在线| 欧美午夜一区二区三区免费大片| av网站有哪些| 亚洲作爱视频| 91亚洲国产成人精品一区二三| 国产嫩草一区二区三区在线观看 | 亚洲 欧美 自拍偷拍| 尤物视频一区二区| 日韩av影视大全| 久久要要av| 成人免费网站在线| 午夜av在线免费观看| 日韩免费电影一区| 日韩欧美国产亚洲| 91美女在线视频| 欧美伦理视频在线观看| 成人综合一区| 91中文字幕在线| 免费污视频在线| 国产视频精品久久久| 免费一级a毛片| 综合婷婷亚洲小说| 香蕉视频污视频| 鲁大师成人一区二区三区| 日韩精品资源| 国产一区一区| 97在线视频一区| a√资源在线| 日韩三级在线观看| 一本一道无码中文字幕精品热| 久久久久久久性| 午夜一区二区视频| 亚洲精品日韩久久| 一本久久a久久精品vr综合| 综合成人在线| 国产成人久久久精品一区| 欧美成人三区| 日韩av影院在线观看| 在线观看中文字幕码| 亚洲精品成人精品456| 狠狠人妻久久久久久综合蜜桃| 日韩不卡一区二区| 成人一级生活片| 精品视频亚洲| 国产欧美韩日| 亚洲tv在线| 欧美在线国产精品| av毛片在线看| 国产一区av在线| 手机av免费在线观看| 欧美日韩极品在线观看一区| 久草国产精品视频| 亚洲人成7777| 性欧美精品男男| 成人动漫视频在线| 毛片毛片毛片毛| 久久一区精品| 国产 日韩 欧美在线| 国产麻豆免费视频| 中文字幕一区在线观看| 免费观看一区二区三区| 欧美a级理论片| 精品视频在线观看一区| 日韩精品午夜| 欧美日韩免费精品| 国产精品调教视频| 91色琪琪电影亚洲精品久久| free欧美| 91精品国产沙发| 人妖欧美1区| 日韩一区二区三区xxxx| 国产视频网址在线| 精品一区二区三区电影| 亚洲xxx在线| 欧美一级免费大片| 91亚洲国产成人精品一区| 日韩欧美亚洲综合| 日韩免费视频网站| 亚洲一区二区黄色| 国产盗摄x88av| 亚洲欧洲日韩av| 一本色道久久88| 中文一区二区完整视频在线观看| jizz日本免费| 91视视频在线观看入口直接观看www| 国产大学生av| 国产成人丝袜美腿| 人妻巨大乳一二三区| 国产一区二区三区四| 性欧美在线视频| 国内精品久久久久影院色| 天天干天天综合| 毛片av中文字幕一区二区| 四季av一区二区| 青青草97国产精品免费观看| 成人免费无码av| 日精品一区二区三区| 免费激情视频在线观看| 久久美女性网| 麻豆一区二区三区视频| 日韩高清国产一区在线| 久久九九国产视频| 蜜桃精品视频在线观看| 国产成年人视频网站| 精品一区二区三区在线观看 | 亚洲无线视频| 欧美精品卡一卡二| 国产精品亚洲欧美| 久久综合久久色| 麻豆成人免费电影| 亚洲天堂av一区二区三区| 国产精品一区二区三区99| 精品人妻无码中文字幕18禁| 不卡av在线免费观看| 大又大又粗又硬又爽少妇毛片| 国产日韩三级在线| 久久高清内射无套| 黄色精品一区二区| 国产精品传媒在线观看| 7777精品伊人久久久大香线蕉超级流畅| 91亚洲国产成人精品一区| 欧美大片日本大片免费观看| 天天舔天天干天天操| 伊人精品在线观看| 岛国中文字幕在线| 欧美激情高清视频| 久久久人成影片一区二区三区在哪下载| 国产精品久久在线观看| 日韩一区二区三区色| 久久综合九色综合久99| 99久久99久久精品国产片果冰| 免费网站在线观看视频| 日本中文字幕一区二区有限公司| 日韩精品aaa| 99视频有精品| 国产男女猛烈无遮挡在线喷水| 亚洲电影在线播放| 91国偷自产中文字幕久久| 精品国产凹凸成av人导航| 成人在线视频成人| 久久久女女女女999久久| 国产韩日精品| 国产欧美日韩在线播放| 日韩一区三区| 久久久久久人妻一区二区三区| 免费人成精品欧美精品| 亚洲成年人在线观看| 国产精品素人视频| 国产区在线观看视频| 日韩一区二区三免费高清| 韩国免费在线视频| 久久久久久网址| 免费一区二区三区四区| 久久av二区| 欧美成人国产| 激情五月俺来也| 久久影院午夜论| 久久久久久国产精品视频| 欧美日韩在线免费视频| 青青草视频在线观看| 欧美激情精品久久久久久大尺度| 欧美videos粗暴| 欧美精品亚洲精品| 亚洲高清久久| 亚洲911精品成人18网站| 国产精品成人一区二区三区夜夜夜| 黄色在线免费观看| 亚洲国产三级网| 韩国日本一区| 99电影在线观看| 91精品国偷自产在线电影| 国产一级特黄a大片免费| 91在线视频18| 日韩av无码中文字幕| 欧美一三区三区四区免费在线看| 91精品国产91久久久久游泳池| 茄子视频成人在线| 天天躁日日躁狠狠躁欧美巨大小说 | 欧美日韩成人综合| 飘雪影院手机免费高清版在线观看| 欧美激情乱人伦一区| 国产一区二区三区| 裸体裸乳免费看| 精品无人码麻豆乱码1区2区 | 精品国产午夜| 国产男女激情视频| 久久久美女艺术照精彩视频福利播放| 日本熟伦人妇xxxx| 亚洲国产91色在线| 交100部在线观看| 精品国产免费人成电影在线观...| 午夜精品久久久久99热蜜桃导演| 亚洲日本黄色片| 亚洲激情av在线| 国产a级免费视频| 欧美激情2020午夜免费观看| 天堂va欧美ⅴa亚洲va一国产| 米仓穗香在线观看| 成人一道本在线| 国产成人无码精品久久久久| 亚洲精品国产福利| 伊人久久综合一区二区| 欧美理论一区二区| 日本视频在线一区| 懂色av粉嫩av浪潮av| 51午夜精品国产| 午夜伦理在线视频| 精品国产_亚洲人成在线| 国产精品久久久一区二区| 四虎影成人精品a片| 欧美日韩免费观看一区二区三区| 欧美成人性生活视频| 97久草视频| 99国产精品| 国产123在线| 欧美年轻男男videosbes| av超碰免费在线| 精品产品国产在线不卡| 日韩国产在线一| www.色小姐com| 亚洲精品久久久久久久久| 经典三级一区二区| 综合久久国产| 成年人网站91| 国产偷人爽久久久久久老妇app| 久久人人爽人人爽人人片亚洲| 网站一区二区| 国产日韩一区二区在线观看| 国产精品电影院| 色噜噜在线播放| 国产精品嫩草影院久久久| 午夜国产精品视频| 欧美做受xxxxxⅹ性视频| 欧美一区二区三区影视| sm性调教片在线观看| 亚洲国产一区二区三区在线播 | 黄色网页网址在线免费| 国产日韩三区| 美女诱惑一区二区| 日本污视频在线观看| 亚洲天堂av在线免费| 亚洲免费一区三区| 看欧美ab黄色大片视频免费| 亚洲一区在线观看视频| 天天影视久久综合|