天天看點

文檔線上預覽新版(一)通過将檔案轉成圖檔實作線上預覽功能

作者:知北遊zzz

一、前言

如果不想網頁上的文章被複制(沒錯,說的就是某點),如果想實作文檔不需要下載下傳下來就能線上預覽檢視(常見于文檔付費下載下傳網站、郵箱附件預覽),該怎麼做?常見的做法就是将他們轉化成圖檔。

以下代碼分别提供基于aspose、pdfbox、spire來實作來實作txt、word、pdf、ppt、word等檔案轉圖檔的需求。

1、aspose

Aspose 是一家緻力于.Net ,Java,SharePoint,JasperReports和SSRS元件的提供商,數十個國家的數千機構都有用過aspose元件,建立、編輯、轉換或渲染 Office、OpenOffice、PDF、圖像、ZIP、CAD、XPS、EPS、PSD 和更多檔案格式。注意aspose是商用元件,未經授權導出檔案裡面都是是水印(尊重版權,遠離破解版)。

需要在項目的pom檔案裡添加如下依賴

<dependency>
            <groupId>com.aspose</groupId>
            <artifactId>aspose-words</artifactId>
            <version>23.1</version>
        </dependency>
        <dependency>
            <groupId>com.aspose</groupId>
            <artifactId>aspose-pdf</artifactId>
            <version>23.1</version>
        </dependency>
        <dependency>
            <groupId>com.aspose</groupId>
            <artifactId>aspose-cells</artifactId>
            <version>23.1</version>
        </dependency>
        <dependency>
            <groupId>com.aspose</groupId>
            <artifactId>aspose-slides</artifactId>
            <version>23.1</version>
        </dependency>           

2 、poi + pdfbox

因為aspose和spire雖然好用,但是都是是商用元件,是以這裡也提供使用開源庫操作的方式的方式。

POI是Apache軟體基金會用Java編寫的免費開源的跨平台的 Java API,Apache POI提供API給Java程式對Microsoft Office格式檔案讀和寫的功能。

Apache PDFBox是一個開源Java庫,支援PDF文檔的開發和轉換。 使用此庫,您可以開發用于建立,轉換和操作PDF文檔的Java程式。

需要在項目的pom檔案裡添加如下依賴

<dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>pdfbox</artifactId>
            <version>2.0.4</version>
        </dependency>
		<dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-scratchpad</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-excelant</artifactId>
            <version>5.2.0</version>
        </dependency>           

3 spire

spire一款專業的Office程式設計元件,涵蓋了對Word、Excel、PPT、PDF等檔案的讀寫、編輯、檢視功能。spire提供免費版本,但是存在隻能導出前3頁以及隻能導出前500行的限制,隻要達到其一就會觸發限制。需要超出前3頁以及隻能導出前500行的限制的這需要購買付費版(尊重版權,遠離破解版)。這裡使用免費版進行示範。

spire在添加pom之前還得先添加maven倉庫來源

<repository>
            <id>com.e-iceblue</id>
            <name>e-iceblue</name>
            <url>https://repo.e-iceblue.cn/repository/maven-public/</url>
        </repository>
           

接着在項目的pom檔案裡添加如下依賴

免費版:

<dependency>
            <groupId>e-iceblue</groupId>
            <artifactId>spire.office.free</artifactId>
            <version>5.3.1</version>
        </dependency>           

付費版版:

<dependency>
            <groupId>e-iceblue</groupId>
            <artifactId>spire.office</artifactId>
            <version>5.3.1</version>
        </dependency>           

二、将檔案轉換成圖檔,并生成到本地

1、将word檔案轉成圖檔

(1)使用aspose

public static void wordToImage(String wordPath, String imagePath) throws Exception {
        Document doc = new Document(wordPath);
        File file = new File(wordPath);
        String filename = file.getName();
        String pathPre = imagePath + File.separator + filename.substring(0, filename.lastIndexOf("."));
        for (int i = 0; i < doc.getPageCount(); i++) {
            Document extractedPage = doc.extractPages(i, 1);
            String path = pathPre + (i + 1) + ".png";
            extractedPage.save(path, SaveFormat.PNG);
        }
    }           

驗證結果:

文檔線上預覽新版(一)通過将檔案轉成圖檔實作線上預覽功能

(2)使用pdfbox

word轉圖檔沒找到特别好的免費方案,隻能先轉pdf,再轉圖檔。。。

public void wordToImage(String wordPath, String imagePath) throws Exception {
        imagePath = FileUtil.getNewFileFullPath(wordPath, imagePath, "png");
        try(FileInputStream fileInputStream = new FileInputStream(wordPath);
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()){
            XWPFDocument document = new XWPFDocument(fileInputStream);
            PdfOptions pdfOptions = PdfOptions.create();
            PdfConverter.getInstance().convert(document, byteArrayOutputStream, pdfOptions);
            document.close();
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            PDDocument doc = PDDocument.load(byteArrayInputStream);
            PDFRenderer renderer = new PDFRenderer(doc);
            for (int i = 0; i < doc.getNumberOfPages(); i++) {
                BufferedImage image = renderer.renderImageWithDPI(i, 144); // Windows native DPI
                String pathname = imagePath + (i + 1) + ".png";
                ImageIO.write(image, "PNG", new File(pathname));
            }
            doc.close();
        }
    }           

驗證結果:

文檔線上預覽新版(一)通過将檔案轉成圖檔實作線上預覽功能

(3)使用spire

public void wordToImage(String wordPath, String imagePath) throws Exception {
        File file = new File(wordPath);
        String filename = file.getName();
        String pathPre = imagePath + File.separator + filename.substring(0, filename.lastIndexOf("."));
        //加載Word文檔
        Document document = new Document();
        document.loadFromFile(wordPath);

        //将Word文檔轉換為圖檔
        BufferedImage[] images = document.saveToImages(0, document.getPageCount()-1, ImageType.Bitmap);

        //儲存圖檔
        for (int i = 0; i < images.length; i++) {
            String pathname = pathPre + (i + 1) + ".png";
            ImageIO.write(images[i], "PNG", new File(pathname));
        }
    }           

驗證結果:

因為使用的是免費版,是以隻能生成前三頁。。。有超過三頁需求的可以選擇付費版本。

文檔線上預覽新版(一)通過将檔案轉成圖檔實作線上預覽功能

2、将txt檔案轉成圖檔(同word檔案轉成圖檔)

(1)使用aspose

public static void txtToImage(String txtPath, String imagePath) throws Exception {
        wordToImage(txtPath, imagePath);
    }           

驗證:

public static void main(String[] args) throws Exception {
        FileConvertUtil.wordToImage("D:\\書籍\\電子書\\其它\\《山海經》異獸圖.doc", "D:\\test\\word");
    }           

驗證結果:

文檔線上預覽新版(一)通過将檔案轉成圖檔實作線上預覽功能

3、将pdf檔案轉圖檔

(1)使用aspose

public static void pdfToImage(String pdfPath, String imagePath) throws Exception {
        File file = new File(pdfPath);
        String filename = file.getName();
        String pathPre = imagePath + File.separator + filename.substring(0, filename.lastIndexOf("."));
        PDDocument doc = PDDocument.load(file);
        PDFRenderer renderer = new PDFRenderer(doc);
        for (int i = 0; i < doc.getNumberOfPages(); i++) {
            BufferedImage image = renderer.renderImageWithDPI(i, 144); // Windows native DPI
            String pathname = pathPre + (i + 1) + ".png";
            ImageIO.write(image, "PNG", new File(pathname));
        }
        doc.close();
    }           

驗證:

public static void main(String[] args) throws Exception {
        FileConvertUtil.pdfToImage("D:\\書籍\\電子書\\其它\\自然哲學的數學原理.pdf", "D:\\test\\pdf");
    }           

驗證結果:

文檔線上預覽新版(一)通過将檔案轉成圖檔實作線上預覽功能

(2)使用pdfbox

public void pdfToImage(String pdfPath, String imagePath) throws Exception {
        String pathPre = FileUtil.getNewMultiFileFullPathPre(pdfPath, imagePath);
        PDDocument doc = PDDocument.load(new File(pdfPath));
        PDFRenderer renderer = new PDFRenderer(doc);
        for (int i = 0; i < doc.getNumberOfPages(); i++) {
            BufferedImage image = renderer.renderImageWithDPI(i, 144); // Windows native DPI
            String pathname = pathPre + (i + 1) + ".png";
            ImageIO.write(image, "PNG", new File(pathname));
        }
        doc.close();
    }           

驗證結果:

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-q7MYkt4t-1688054753134)(D:\文章\預覽\預覽1轉圖檔\pic\pdf轉圖檔pdfbox.png)]

(3)使用spire

public void pdfToImage(String pdfPath, String imagePath) throws Exception {
        PdfDocument pdf = new PdfDocument();
        pdf.loadFromFile(pdfPath);
        File file = new File(pdfPath);
        String filename = file.getName();
        String pathPre = imagePath + File.separator + filename.substring(0, filename.lastIndexOf("."));
        for (int i = 0; i < pdf.getPages().getCount(); i++) {
            BufferedImage image = pdf.saveAsImage(i);
            String pathname = pathPre + (i + 1) + ".png";
            ImageIO.write(image, "png", new File(pathname));
        }
    }           

驗證結果:

因為使用的是免費版,是以隻有前三頁是正常的。。。有超過三頁需求的可以選擇付費版本。

文檔線上預覽新版(一)通過将檔案轉成圖檔實作線上預覽功能

4、将ppt檔案轉圖檔

(1)使用aspose

public void pptToImage(String pptPath, String imagePath) throws Exception {
        File file = new File(pptPath);
        String filename = file.getName();
        String pathPre = imagePath + File.separator + filename.substring(0, filename.lastIndexOf("."));
        Presentation presentation = new Presentation(pptPath);
        for (int i = 0; i < presentation.getSlides().size(); i++) {
            ISlide slide = presentation.getSlides().get_Item(i);
            BufferedImage image = slide.getThumbnail(1f, 1f);
            String path = pathPre + (i + 1) + ".png";
            ImageIO.write(image, "png", new File(path));
        }
    }           

驗證結果:

文檔線上預覽新版(一)通過将檔案轉成圖檔實作線上預覽功能

(2)使用pdfbox

public void pptToImage(String pptPath, String imagePath) throws Exception {
        File file = new File(pptPath);
        String filename = file.getName().substring(0, file.getName().lastIndexOf("."));
        List<BufferedImage> images			 = pptToBufferedImages(pptPath);
        String dicPath = imagePath + File.separator + filename;
        File dic = new File(dicPath);
        if (!dic.exists()) {
            dic.mkdir();
        }
        for (int i = 0; i < images.size(); i++) {
            BufferedImage image = images.get(i);
            String path = dicPath+ File.separator + filename + (i + 1) + ".png";
            ImageIO.write(image, "png", new File(path));
        }
    }           

驗證結果:

文檔線上預覽新版(一)通過将檔案轉成圖檔實作線上預覽功能

(3)使用spire

驗證結果:

免費版ppt轉圖檔生成前10頁,有進步。。。有超過10頁需求的可以選擇付費版本。

文檔線上預覽新版(一)通過将檔案轉成圖檔實作線上預覽功能

三、利用多線程提升檔案寫入本地的效率

在将牛頓大大的長達669頁的巨作《自然哲學的數學原理》時發現執行時間較長,執行花了140,281ms。但其實這種IO密集型的操作是通過使用多線程的方式來提升效率的,于是針對這點,我又寫了一版多線程的版本。

同步執行導出 自然哲學的數學原理.pdf 耗時:

文檔線上預覽新版(一)通過将檔案轉成圖檔實作線上預覽功能

優化後的代碼如下:

public static void pdfToImageAsync(String pdfPath, String imagePath) throws Exception {
        long old = System.currentTimeMillis();
        File file = new File(pdfPath);
        PDDocument doc = PDDocument.load(file);
        PDFRenderer renderer = new PDFRenderer(doc);
        int pageCount = doc.getNumberOfPages();
        int numCores = Runtime.getRuntime().availableProcessors();
        ExecutorService executorService = Executors.newFixedThreadPool(numCores);
        for (int i = 0; i < pageCount; i++) {
            int finalI = i;
            executorService.submit(() -> {
                try {
                    BufferedImage image = renderer.renderImageWithDPI(finalI, 144); // Windows native DPI
                    String filename = file.getName();
                    filename = filename.substring(0, filename.lastIndexOf("."));
                    String pathname = imagePath + File.separator + filename + (finalI + 1) + ".png";
                    ImageIO.write(image, "PNG", new File(pathname));
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            });
        }
        executorService.shutdown();
        executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
        doc.close();
        long now = System.currentTimeMillis();
        System.out.println("pdfToImage 多線程 轉換完成..用時:" + (now - old) + "ms");
    }           

多線程執行導出 自然哲學的數學原理.pdf 耗時如下:

文檔線上預覽新版(一)通過将檔案轉成圖檔實作線上預覽功能

從上圖可以看到本次執行隻花了24045ms,隻花了原先差不多六分之一的時間,極大地提升了執行效率。除了pdf,word、txt轉圖檔也可以做這樣的多線程改造:

//将word轉成圖檔(多線程)
    public static void wordToImageAsync(String wordPath, String imagePath) throws Exception {
        Document doc = new Document(wordPath);
        File file = new File(wordPath);
        String filename = file.getName();
        String pathPre = imagePath + File.separator + filename.substring(0, filename.lastIndexOf("."));
        int numCores = Runtime.getRuntime().availableProcessors();
        ExecutorService executorService = Executors.newFixedThreadPool(numCores);
        for (int i = 0; i < doc.getPageCount(); i++) {
            int finalI = i;
            executorService.submit(() -> {
                try {
                    Document extractedPage = doc.extractPages(finalI, 1);
                    String path = pathPre + (finalI + 1) + ".png";
                    extractedPage.save(path, SaveFormat.PNG);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            });
        }
    }
    
    //将txt轉成圖檔(多線程)
    public static void txtToImageAsync(String txtPath, String imagePath) throws Exception {
        wordToImageAsync(txtPath, imagePath);
    }           

四、将檔案轉換成圖檔流

有的時候我們轉成圖檔後并不需要在本地生成圖檔,而是需要将圖檔傳回或者上傳到圖檔伺服器,這時候就需要将轉換後的圖檔轉成流傳回以友善進行傳輸,代碼示例如下:

1、将word檔案轉成圖檔流

(1)使用aspose

public static List<byte[]> wordToImageStream(String wordPath) throws Exception {
    Document doc = new Document(wordPath);
    List<byte[]> list = new ArrayList<>();
    for (int i = 0; i < doc.getPageCount(); i++) {
        try(ByteArrayOutputStream outputStream = new ByteArrayOutputStream()){
            Document extractedPage = doc.extractPages(i, 1);
            extractedPage.save(outputStream, SaveFormat.*PNG*);
            list.add(outputStream.toByteArray());
        }
    }
    return list;
}           

(2)使用pdfbox

public List<byte[]> wordToImageStream(String wordPath) throws Exception {
        List<BufferedImage> images = new ArrayList<>();
        try(FileInputStream fileInputStream = new FileInputStream(wordPath);
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()){
            XWPFDocument document = new XWPFDocument(fileInputStream);
            PdfOptions pdfOptions = PdfOptions.create();
            PdfConverter.getInstance().convert(document, byteArrayOutputStream, pdfOptions);
            document.close();
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            PDDocument doc = PDDocument.load(byteArrayInputStream);
            PDFRenderer renderer = new PDFRenderer(doc);
            for (int i = 0; i < doc.getNumberOfPages(); i++) {
                BufferedImage image = renderer.renderImageWithDPI(i, 144); // Windows native DPI
                images.add(image);
            }
            doc.close();
        }
        return images.stream().map(image-> {
            try {
                return FileUtil.imageToByte(image);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }).collect(Collectors.toList());
    }           

(3)使用spire

public List<byte[]> wordToImageStream(String wordPath) throws Exception {
        Document document = new Document();
        document.loadFromFile(wordPath);
        BufferedImage[] bufferedImages = document.saveToImages(ImageType.Bitmap);
        return FileUtil.toByteArrays(bufferedImages);
    }           

2、将txt檔案轉成圖檔流

(1)使用aspose

public static List<byte[]> txtToImageStream(String txtPath) throws Exception {
    return *wordToImagetream*(txtPath);
}           

3、将pdf轉成圖檔流

(1)使用aspose

public static List<byte[]> pdfToImageStream(String pdfPath) throws Exception {
    File file = new File(pdfPath);
    PDDocument doc = PDDocument.*load*(file);
    PDFRenderer renderer = new PDFRenderer(doc);
    List<byte[]> list = new ArrayList<>();
    for (int i = 0; i < doc.getNumberOfPages(); i++) {
        try(ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            BufferedImage image = renderer.renderImageWithDPI(i, 144); // Windows native DPI
            ImageIO.*write*(image, "PNG", outputStream);
            list.add(outputStream.toByteArray());
        }
    }
    doc.close();
    return list;
}           

(2)使用pdfbox

public List<byte[]> pdfToImageStream(String pdfPath) throws Exception {
        File file = new File(pdfPath);
        PDDocument doc = PDDocument.load(file);
        PDFRenderer renderer = new PDFRenderer(doc);
        List<byte[]> list = new ArrayList<>();
        for (int i = 0; i < doc.getNumberOfPages(); i++) {
            try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
                BufferedImage image = renderer.renderImageWithDPI(i, 144); // Windows native DPI
                ImageIO.write(image, "PNG", outputStream);
                list.add(outputStream.toByteArray());
            }
        }
        doc.close();
        return list;
    }           

(3)使用spire

public List<byte[]> pdfToImageStream(String pdfPath) throws Exception {
        PdfDocument pdf = new PdfDocument();
        pdf.loadFromFile(pdfPath);
        File file = new File(pdfPath);
        String filename = file.getName();
        List<byte[]> list = new ArrayList<>();
        for (int i = 0; i < pdf.getPages().getCount(); i++) {
            BufferedImage image = pdf.saveAsImage(i);
            list.add(FileUtil.imageToByte(image));
        }
        return list;
    }           

4、将ppt檔案轉圖檔流

(1)使用aspose

public List<byte[]> pptToImageStream(String pptPath) throws IOException {
        List<byte[]> list = new ArrayList<>();
        Presentation presentation = new Presentation(pptPath);
        for (int i = 0; i < presentation.getSlides().size(); i++) {
            ISlide slide = presentation.getSlides().get_Item(i);
            BufferedImage image = slide.getThumbnail(1f, 1f);
            byte[] bytes = FileUtil.imageToByte(image);
            list.add(bytes);
        }
        return list;
    }           

(2)使用pdfbox

public List<byte[]> pptToImageStream(String pptPath) throws IOException {
        List<BufferedImage> images = pptToBufferedImages(pptPath);
        if(CollectionUtils.isEmpty(images)){
            return null;
        }
        return images.stream().map(image-> {
            try {
                return FileUtil.imageToByte(image);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }).collect(Collectors.toList());
    }           

(3)使用spire

public List<byte[]> pptToImageStream(String pptPath) throws Exception {
        List<byte[]> list = new ArrayList<>();
        Presentation presentation = new Presentation();
        presentation.loadFromFile(pptPath);
        for (int i = 0; i < presentation.getSlides().getCount(); i++) {
            BufferedImage image = presentation.getSlides().get(i).saveAsImage();
            list.add(FileUtil.imageToByte(image));
        }
        return list;
    }           

總結

将檔案轉成圖檔實作預覽的這種方式的優點是:

1、圖檔線上預覽控件比較多,也比較成熟,前端起來比較友善

2、文檔轉成圖檔後能有效減少文檔内容被複制的情況

3、浏覽器也天然支援

這種方式的缺點是:

1、文檔往往都不隻一頁,所有同城的做法将文檔的每一頁都生成一張圖檔,是以前後端都需要考慮處理多張圖檔的問題

2、如果圖檔都以base64的格式傳回給前端,會造成傳回體過大的問題,如果傳回有加日志還會存在日志體較長,增加日志伺服器的問題。

3、因為base64的格式直接傳回傳回體過長,好一點的做法現将圖檔上傳到圖檔伺服器,隻傳回圖檔的url,這樣解決了圖檔傳回體過長的問題,但要先将多張圖檔先上傳到圖檔伺服器,這樣會不可避免的拖慢接口的傳回速度,尤其是在文檔頁數較多的時候,同時也會增加圖檔伺服器的壓力。

解決多圖檔展示問題的解決方案:

應該如何解決多圖檔展示問題呢,其實很簡單,可以參考開源元件kkfileview解決多圖檔展示問題的(既然都參考了為什麼不直接拿來用[奸笑])的做法,即将生成的多張圖檔全都放到一個html頁面裡,用html保持樣式并實作多張圖檔展示,再将html傳回。

​ kkfileview展示效果如下:

文檔線上預覽新版(一)通過将檔案轉成圖檔實作線上預覽功能

下圖是kkfileview傳回的html代碼,從html代碼我們可以看到kkfileview其實是将檔案(txt檔案除外)每頁的内容都轉成了圖檔,然後将這些圖檔都嵌入到一個html裡,再傳回給使用者一個html頁面。

文檔線上預覽新版(一)通過将檔案轉成圖檔實作線上預覽功能

繼續閱讀