跨平臺(uni-app)文件在線預覽解決方案

1024剛過,也祝願各位碼友在今後生活中,身體健康,事事順心,再無Bug。

一、前言

之前寫過一篇文章關於上傳目錄文件:uni-app系統目錄文件上傳(非只圖片和視頻)解決方案,這次來解決文件預覽問題。

  uni-app 是一個使用 Vue.js 開發所有前端應用的框架,開發者編寫一套代碼,可發佈到iOS、Android、H5、以及各種小程序(微信/支付寶/百度/頭條/QQ/釘釘)等多個平臺。在做業務系統時,不可避免會遇到文件在線預覽的需求。這裏的文件包括PDF、Word、Excel、PPT、圖片等。而在線預覽不是下載後再用本地軟件或瀏覽器打開預覽,而是直接通過文件流的形式查看。本方案主要解決在線預覽問題,以及在uni-app開發過程中遇到一系列問題

  如果有欠缺的地方,或者有更好的方案,還望各位碼友多提意見,多多交流,文章最後可以加我。

文件預覽,首先會想到pdf預覽,前端做pdf預覽,首先也會想到pdf.js,那我們就從pdf.js說起。

二、PDF預覽

pdf.js開源地址和在線例子
Github
Online Demo

2.1 使用方法一

<img src="https://user-gold-cdn.xitu.io...;h=288&f=png&s=32729" width="240" align=center />

<img src="https://user-gold-cdn.xitu.io...;h=262&f=png&s=17092" width="240" align=center />

  • 新建vue組件file-preview.vue

    • viewerUrl:前端本地viewer.html頁面地址
    • fileUrl:文件流訪問地址,參考《三、文件流服務
<template>
    <view>
        <web-view :src="allUrl"></web-view>
    </view>
</template>

<script>
    import globalConfig from '@/config'
    export default {
        data() {
            return {
                viewerUrl: '/hybrid/html/web/viewer.html',
                // viewerUrl: globalConfig.baseUrl + '/pdf/web/viewer.html',
                allUrl: ''
            }
        },
        onLoad(options) {
            let fileUrl = encodeURIComponent(
                globalConfig.baseUrl + '/api/attachment?name=' + options.name + '&url=' + options.url)
            this.allUrl = this.viewerUrl + '?file=' + fileUrl
        }
    }
</script>
  • 效果

    • h5端

顯示正常

  • Android端

顯示模糊,並且中文顯示不全,其中模糊問題是模擬器原因;但是中文顯示問題是真,調試出現兩個警告。第二個警告pdf.js默認不顯示電子簽章(數字簽名)問題,查了很多資料也沒解決,各位碼友有遇到過並且解決了嗎?
<img src="https://user-gold-cdn.xitu.io...;h=812&f=png&s=253098" width="360" align=center />

  • iOS端

出現跨域問題,並且調試出現無法訪問pdf.js國際化文件
<img src="https://user-gold-cdn.xitu.io...;h=452&f=png&s=95345" width="360" align=center />

  • 解決

基於Android和iOS預覽出現的各種問題,最根本原因是viewer.html文件放到前端導致加載資源文件丟失問題。針對這個問題,我就在想能不能直接放在spring後端作爲靜態資源訪問文件呢?於是有了下面的方法。

2.2 使用方法二

  • 在基於spring mvc的後端代碼中,將插件包的build和web文件夾放到webapp下面(新建pdf文件夾),spring boot架構的後端項目同理,放到靜態資源目錄

<img src="https://user-gold-cdn.xitu.io...;h=650&f=png&s=69156" width="400" align=center />

  • 在xml文件中配置靜態文件訪問

  • 修改前端組件file-preview.vue中的viewerUrl,其中globalConfig.baseUrl爲代理後端地址的baseUrl。如Vue中proxyTablenginx代理
viewerUrl: globalConfig.baseUrl + '/pdf/web/viewer.html'
  • 修改後效果

    • iOS端

<img src="https://user-gold-cdn.xitu.io...;h=698&f=png&s=184270" width="360" align=center />

  • Android端

模糊是模擬器原因,在真機上測試通過
<img src="https://user-gold-cdn.xitu.io...;h=634&f=png&s=140599" width="360" align=center />

三、文件流服務

3.1 方法一:tomcat配置

配置tomcat的config目錄下的server.xml,在最後的<server></server>中間添加如下:

port=8090 文件訪問服務端口
docBase="/root/" 文件存儲目錄
服務器上文件會存儲到/root/fileData/目錄下
文件訪問地址爲:http://ip地址:8090/fileData/...
<Service name="fileData">  
    <!--分配8089端口 -->  
    <!-- <Connector port="8090" protocol="HTTP/1.1" connectionTimeout="20000" URIEncoding="GBK" redirectPort="8443" /> -->
    <Connector port="8090" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
    <Engine name="fileData" defaultHost="localhost">  
    <!--name爲項目訪問地址 此配置的訪問爲http://localhost:8080 appBase配置tomcat下wabapps下的路徑-->     
    <Host name="localhost" appBase="webapps"  
        unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false">  
        <!--資源地址-->  
        <Context path="" docBase="/root/" debug="0" reloadable="false"/>  
    </Host>  
    </Engine>
</Service>

3.2 方法二:寫代碼獲取服務器文件進行轉換

直接上代碼

讀取目錄文件,轉換爲二進制流
前端組件file-preview.vue中fileUrl/api/attachment

核心代碼

ios = new FileInputStream(sourceFile);
os = response.getOutputStream();
int read = 0;
byte[] buffer = new byte[1024 * 1024];
while ((read = ios.read(buffer)) != -1) {
    os.write(buffer, 0, read);
}
os.flush();

完整代碼

@RequestMapping(value = "/api/attachment", method = RequestMethod.GET)
    public void getFileBytes(@RequestParam("name") String name, @RequestParam("url") String url, HttpServletRequest request, HttpServletResponse response) {
        response.reset();
        response.setContentType("application/octet-stream");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-Disposition", "attachment;filename=" + name);

        AttachmentVO attachmentVO = new AttachmentVO();
        FileInputStream ios = null;
        OutputStream os = null;
        try {
            name = CharsetUtils.toUTF_8(name);
            url = CharsetUtils.toUTF_8(url);

            attachmentVO.setUrl(url);
            attachmentVO.setName(name);
            File sourceFile = getDictionaryFile(attachmentVO, request);
            if (null == sourceFile) {
//                throw new HttpResponseException(300, "附件不存在!");
                return;
            }

            /**
             * 判斷文件類型
             */
            /* 獲得文件名後綴 */
            String ext = "";
            if (!"".equals(url) && url.contains(".")) {
                ext = url.substring(url.lastIndexOf(".") + 1, url.length()).toUpperCase();
            }
            /* 根據文件類型不同進行預覽 */
            /* 預覽pdf */
            if ("PDF".equals(ext)) {
                response.setContentType("application/pdf");
            }

            /**
             * 將文件寫入輸出流,顯示在界面上,實現預覽效果
             */
            ios = new FileInputStream(sourceFile);
            os = response.getOutputStream();
            int read = 0;
            byte[] buffer = new byte[1024 * 1024];
            while ((read = ios.read(buffer)) != -1) {
                os.write(buffer, 0, read);
            }
            os.flush();
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if (null != ios) {
                    ios.close();
                }
                if (null != os) {
                    os.close();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }

四、office文件(Word、Excel、PPT)預覽

原理:
搭建OpenOffice服務,將文件轉換爲pdf,在使用pdf.js預覽

4.1 搭建openOffice服務

tar xzvfm Apache_OpenOffice_xxx.tar.gz
cd zh-CN/RPMS
rpm -ivh *rpm
  • 運行
# 127.0.0.1只能本機使用該服務
/opt/openoffice4/program/soffice "-accept=socket,host=127.0.0.1,port=8100;urp;" -headless -nofirststartwizard &
# 0.0.0.0遠程ip能使用
/opt/openoffice4/program/soffice "-accept=socket,host=0.0.0.0,port=8100;urp;" -headless -nofirststartwizard &

4.2 集成java

  • 在pom.xml添加jar包
<!-- openoffice start -->
<dependency>
    <groupId>org.openoffice</groupId>
    <artifactId>juh</artifactId>
    <version>4.1.2</version>
</dependency>
<dependency>
    <groupId>org.openoffice</groupId>
    <artifactId>jurt</artifactId>
    <version>4.1.2</version>
</dependency>
<dependency>
    <groupId>org.openoffice</groupId>
    <artifactId>ridl</artifactId>
    <version>4.1.2</version>
</dependency>
<dependency>
    <groupId>org.openoffice</groupId>
    <artifactId>unoil</artifactId>
    <version>4.1.2</version>
</dependency>
<dependency>
    <groupId>com.artofsolving</groupId>
    <artifactId>jodconverter</artifactId>
    <version>2.2.2</version>
</dependency>
<!-- openoffice end -->

<span style="color:red">注意</span>:jodconverter需要單獨下載2.2.2版本,之前的版本都不行,而且maven中央倉庫沒有2.2.2版本。然後再單獨導入。下載地址:https://sourceforge.net/proje...

單獨導入

mvn install:install-file -Dfile="jodconverter-2.2.2.jar" -DgroupId=com.artofsolving -DartifactId=jodconverter -Dversion=2.2.2 -Dpackaging=jar
  • 轉換代碼

核心代碼

connection = new SocketOpenOfficeConnection(openofficeHost, openofficePort);
connection.connect();
DocumentConverter converter = new StreamOpenOfficeDocumentConverter(connection);
converter.convert(sourceFile, pdfFile);

完整代碼

/* 利用openOffice將office文件轉換爲pdf格式, 然後預覽doc, docx, xls, xlsx, ppt, pptx */
if ("DOC".equals(ext) || "DOCX".equals(ext) || "XLS".equals(ext) || "XLSX".equals(ext) || "PPT".equals(ext) || "PPTX".equals(ext)) {
    /* filePath在數據庫中是不帶文件後綴的, 由於jodConverter必須要識別後綴,所以將服務器中的文件重命名爲帶後綴的文件 */
    // File docFileWithExt = new File(filePath + "." + ext.toLowerCase()); //帶後綴的文件
    // docFile.renameTo(docFileWithExt);
    /* 轉換之後的文件名 */
    String filePath = sourceFile.getPath();
    File pdfFile;
    if (filePath.contains(".")) {
        pdfFile = new File(filePath.substring(0, filePath.lastIndexOf(".")) + ".pdf");
    } else {
        pdfFile = new File(filePath + ".pdf");
    }

    /* 判斷即將要轉換的文件是否真實存在 */
    if (sourceFile.exists()) {
        /* 判斷該文件是否已經被轉換過,若已經轉換則直接預覽 */
        if (!pdfFile.exists()) {
            OpenOfficeConnection connection;
            /* 打開OpenOffice連接 */
            try {
                connection = new SocketOpenOfficeConnection(openofficeHost, openofficePort);
                connection.connect();
            } catch (java.net.ConnectException e) {
                log.warn("openOffice未連接,正在重新連接...");

                // 啓動OpenOffice的服務
                String command = openofficeInstallPath + "program/soffice -headless -accept=\"socket,host=127.0.0.1,port=8100;urp;\" -nofirststartwizard";
                Runtime.getRuntime().exec(command);

                Thread.sleep(1000);

                connection = new SocketOpenOfficeConnection(8100);
                connection.connect();

                log.warn("openOffice重新連接成功!!!");
            }

            try {
                // DocumentConverter converter = new OpenOfficeDocumentConverter(connection);
                DocumentConverter converter = new StreamOpenOfficeDocumentConverter(connection);
                converter.convert(sourceFile, pdfFile);
                connection.disconnect();

//                            filePath = pdfFile.getPath(); // 文件轉換之後的路徑
                sourceFile = pdfFile;
                response.setContentType("application/pdf");
            } catch (OpenOfficeException e) {
                e.printStackTrace(); // 讀取轉換文件失敗
                log.info("讀取轉換文件失敗!!!");
                return;
            } finally { // 發生exception時, connection不會自動切斷, 程序會一直掛着
                try {
                    if (connection != null) {
                        connection.disconnect();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } else {
//                        filePath = pdfFile.getPath(); // 文件已經轉換過
            sourceFile = pdfFile;
            response.setContentType("application/pdf");
        }
    } else {
        log.info("需要預覽的文檔在服務器中不存在!!!");
        // 文件不存在,直接返回
        return;
    }
}

五、圖片預覽

5.1 後端文件流

/* 預覽圖片 */
if ("PNG".equals(ext) || "JPEG".equals(ext) || "JPG".equals(ext)) {
    response.setContentType("image/jpeg");
}
/* 預覽BMP格式的文件 */
if ("BMP".equals(ext)) {
    response.setContentType("image/bmp");
}
/* 預覽GIF格式的文件 */
if ("GIF".equals(ext)) {
    response.setContentType("image/gif");
}

5.2 前端預覽

採用uni-app的uni.previewImage接口
fileUrl:爲文件流訪問地址
// 預覽圖片
uni.previewImage({
  urls: [fileUrl],
  longPressActions: {
    itemList: ['發送給朋友', '保存圖片', '收藏'],
    success: function(data) {
      console.log('選中了第' + (data.tapIndex + 1) + '個按鈕,第' + (data.index + 1) + '張圖片');
    },
    fail: function(err) {
      console.log(err.errMsg);
    }
  }
})

附:完整文件流代碼

@RequestMapping(value = "/api/attachment", method = RequestMethod.GET)
public void getFileBytes(@RequestParam("name") String name, @RequestParam("url") String url, HttpServletRequest request, HttpServletResponse response) {
    response.reset();
    // 解決IFrame拒絕的問題,無效
//        response.setHeader("X-Frame-Options", "SAMEORIGIN");
    response.setContentType("application/octet-stream");
    response.setCharacterEncoding("utf-8");
    response.setHeader("Content-Disposition", "attachment;filename=" + name);

    AttachmentVO attachmentVO = new AttachmentVO();
    FileInputStream ios = null;
    OutputStream os = null;
    try {
        name = CharsetUtils.toUTF_8(name);
        url = CharsetUtils.toUTF_8(url);

        attachmentVO.setUrl(url);
        attachmentVO.setName(name);
        File sourceFile = getDictionaryFile(attachmentVO, request);
        if (null == sourceFile) {
//                throw new HttpResponseException(300, "附件不存在!");
            return;
        }

        /**
          * 判斷文件類型
          */
        /* 獲得文件名後綴 */
        String ext = "";
        if (!"".equals(url) && url.contains(".")) {
            ext = url.substring(url.lastIndexOf(".") + 1, url.length()).toUpperCase();
        }
        /* 根據文件類型不同進行預覽 */
        /* 預覽圖片 */
        if ("PNG".equals(ext) || "JPEG".equals(ext) || "JPG".equals(ext)) {
            response.setContentType("image/jpeg");
        }
        /* 預覽BMP格式的文件 */
        if ("BMP".equals(ext)) {
            response.setContentType("image/bmp");
        }
        /* 預覽GIF格式的文件 */
        if ("GIF".equals(ext)) {
            response.setContentType("image/gif");
        }
        /* 預覽pdf */
        if ("PDF".equals(ext)) {
            response.setContentType("application/pdf");
        }

        /* 利用openOffice將office文件轉換爲pdf格式, 然後預覽doc, docx, xls, xlsx, ppt, pptx */
        if ("DOC".equals(ext) || "DOCX".equals(ext) || "XLS".equals(ext) || "XLSX".equals(ext) || "PPT".equals(ext) || "PPTX".equals(ext)) {
            /* filePath在數據庫中是不帶文件後綴的, 由於jodConverter必須要識別後綴,所以將服務器中的文件重命名爲帶後綴的文件 */
            // File docFileWithExt = new File(filePath + "." + ext.toLowerCase()); //帶後綴的文件
            // docFile.renameTo(docFileWithExt);
            /* 轉換之後的文件名 */
            String filePath = sourceFile.getPath();
            File pdfFile;
            if (filePath.contains(".")) {
                pdfFile = new File(filePath.substring(0, filePath.lastIndexOf(".")) + ".pdf");
            } else {
                pdfFile = new File(filePath + ".pdf");
            }

            /* 判斷即將要轉換的文件是否真實存在 */
            if (sourceFile.exists()) {
                /* 判斷該文件是否已經被轉換過,若已經轉換則直接預覽 */
                if (!pdfFile.exists()) {
                    OpenOfficeConnection connection;
                    /* 打開OpenOffice連接 */
                    try {
                        connection = new SocketOpenOfficeConnection(openofficeHost, openofficePort);
                        connection.connect();
                    } catch (java.net.ConnectException e) {
                        log.warn("openOffice未連接,正在重新連接...");

                        // 啓動OpenOffice的服務
                        String command = openofficeInstallPath + "program/soffice -headless -accept=\"socket,host=127.0.0.1,port=8100;urp;\" -nofirststartwizard";
                        Runtime.getRuntime().exec(command);

                        Thread.sleep(1000);

                        connection = new SocketOpenOfficeConnection(8100);
                        connection.connect();

                        log.warn("openOffice重新連接成功!!!");
                    }

                    try {
                        // DocumentConverter converter = new OpenOfficeDocumentConverter(connection);
                        DocumentConverter converter = new StreamOpenOfficeDocumentConverter(connection);
                        converter.convert(sourceFile, pdfFile);
                        connection.disconnect();

//                            filePath = pdfFile.getPath(); // 文件轉換之後的路徑
                        sourceFile = pdfFile;
                        response.setContentType("application/pdf");
                    } catch (OpenOfficeException e) {
                        e.printStackTrace(); // 讀取轉換文件失敗
                        log.info("讀取轉換文件失敗!!!");
                        return;
                    } finally { // 發生exception時, connection不會自動切斷, 程序會一直掛着
                        try {
                            if (connection != null) {
                                connection.disconnect();
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                } else {
//                        filePath = pdfFile.getPath(); // 文件已經轉換過
                    sourceFile = pdfFile;
                    response.setContentType("application/pdf");
                }
            } else {
                log.info("需要預覽的文檔在服務器中不存在!!!");
                // 文件不存在,直接返回
                return;
            }
        }

        /**
          * 將文件寫入輸出流,顯示在界面上,實現預覽效果
          */
        ios = new FileInputStream(sourceFile);
        os = response.getOutputStream();
        int read = 0;
        byte[] buffer = new byte[1024 * 1024];
        while ((read = ios.read(buffer)) != -1) {
            os.write(buffer, 0, read);
        }
        os.flush();
    } catch (Exception e) {
        e.printStackTrace();
        try {
            if (null != ios) {
                ios.close();
            }
            if (null != os) {
                os.close();
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}



<center>贊助作者</center>
<center><img src="https://user-gold-cdn.xitu.io...;h=1080&f=png&s=342366" width="240" align=center /><center>

<center><img src="https://user-gold-cdn.xitu.io...;h=299&f=gif&s=20783" width="240" align=center /><center>

轉載請註明:溜爸 » 跨平臺(uni-app)文件在線預覽解決方案

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章