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

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

1、前言

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

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

  若是有欠缺的地方,或者有更好的方案,還望各位碼友多提意見,多多交流,文章最後能夠加我。vue

文件預覽,首先會想到pdf預覽,前端作pdf預覽,首先也會想到pdf.js,那咱們就從pdf.js提及。java

2、PDF預覽

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

2.1 使用方法一

  • 下載插件包,下載地址 nginx

  • 解壓,拷貝build和web目錄到項目hybrid->html目錄下,參考uni-app中web-view用法 git

  • 新建vue組件file-preview.vuegithub

    • viewerUrl:前端本地viewer.html頁面地址
    • fileUrl:文件流訪問地址,參考《3、文件流服務
<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>
複製代碼
  • 效果web

    • h5端 顯示正常
    • Android端 顯示模糊,而且中文顯示不全,其中模糊問題是模擬器緣由;可是中文顯示問題是真,調試出現兩個警告。第二個警告pdf.js默認不顯示電子簽章(數字簽名)問題,查了不少資料也沒解決,各位碼友有遇到過而且解決了嗎?
    • iOS端 出現跨域問題,而且調試出現沒法訪問pdf.js國際化文件
  • 解決 基於Android和iOS預覽出現的各類問題,最根本緣由是viewer.html文件放到前端致使加載資源文件丟失問題。針對這個問題,我就在想能不能直接放在spring後端做爲靜態資源訪問文件呢?因而有了下面的方法。

2.2 使用方法二

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

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

  • 修改前端組件file-preview.vue中的viewerUrl,其中globalConfig.baseUrl爲代理後端地址的baseUrl。如Vue中proxyTablenginx代理

viewerUrl: globalConfig.baseUrl + '/pdf/web/viewer.html'
複製代碼
  • 修改後效果
    • iOS端
    • Android端 模糊是模擬器緣由,在真機上測試經過

3、文件流服務

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();
            }
        }
    }
複製代碼

4、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 -->
複製代碼

注意jodconverter須要單獨下載2.2.2版本,以前的版本都不行,並且maven中央倉庫沒有2.2.2版本。而後再單獨導入。下載地址:sourceforge.net/projects/jo…

單獨導入

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、圖片預覽

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();
        }
    }
}
複製代碼

贊助做者

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

相關文章
相關標籤/搜索