1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/twofloor-uniapp-admin

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
FILE_PREVIEW.md 26 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 16.03.2025 18:15 e185f4e
title: "Кросс-платформенная (uni-app) система онлайн-просмотра файлов"
author: silianpan
date: 2019-10-23
output: word_document

Кросс-платформенная (uni-app) система онлайн-просмотра файлов

Праздник программиста прошел, желаю всем коллегам здоровья, успехов во всех делах и отсутствия ошибок.

1. Введение

Ранее я писал статью о загрузке каталоговых файлов: uni-app системные каталоговые файлы загрузка (не только картинки и видео) решение, сегодня мы решаем проблему просмотра файлов.

  uni-app — это фреймворк для создания всех типов фронтенд-приложений с использованием Vue.js. Разработчики могут создать одну базу кода и запустить её на iOS, Android, H5, а также различных мини-приложениях (WeChat / Alipay / Baidu / Toutiao / QQ / DingTalk). При работе над бизнес-системами, неизбежно возникают требования к онлайн-просмотру файлов. Здесь подразумеваются различные типы файлов, такие как PDF, Word, Excel, PPT, изображения и т.д. Онлайн-просмотр не включает скачивание файла и последующий просмотр через локальные программы или браузеры, а осуществляется непосредственно через поток файлов. Данное решение направлено на решение проблемы онлайн-просмотра, а также на преодоление всех возникающих проблем при разработке в uni-app.  Если вы заметили недочеты или знаете лучшие решения, буду рад получить ваши предложения и советы. Вы можете связаться со мной в конце статьи.> Для просмотра PDF-файлов первым делом приходит на ум pdf.js. Мы начнем с этого проекта.

Открытый проект uniapp-admin

2. Просмотр PDF

Открытый адрес и пример использования pdf.js GitHub Онлайн-демо

2.1 Метод использования №1

  • Распакуйте, скопируйте директории build и web в папку проекта hybrid->html, используйте uni-app метод web-view
  • Создание Vue компонента file-preview.vue
    • 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>
```* Эффект
  * Для H5
    Отображается корректно
    ![](http://silianpan.cn/wp-content/uploads/2019/10/b4f6ea827000afc84939d0fcc0a309c3.png)
  * Для Android
    Изображение отображается размытым, а также **китайский текст отображается неполностью**, причём размытость вызвана симулятором; но проблема с отображением китайского текста реальная, при этом в процессе отладки возникают два предупреждения. Второе предупреждение связано с тем, что **pdf.js по умолчанию не отображает электронные подписи (цифровые подписи)**, много информации по этому поводу просмотрено, но решение так и не найдено. Есть ли кто-то, кто столкнулся с этой проблемой и решил её?
    <img src="http://silianpan.cn/wp-content/uploads/2019/10/13bd0bc4eca43f65e77c1cfe641056db.png" width="360" align=center />
    ![](http://silianpan.cn/wp-content/uploads/2019/10/e8da9ee0c8fa754d592b13f9bd9e45e0.png)
  * Для iOS
    Возникает **проблема с кросс-доменной политикой (cross-domain)**, а также при отладке невозможно получить доступ к файлам pdf.js для международной локализации
    <img src="http://silianpan.cn/wp-content/uploads/2019/10/32cc3e59ea14d7261c11a8b92a0d0478.png" width="360" align=center />
    ![](http://silianpan.cn/wp-content/uploads/2019/10/05d5c60aaed1873b2a278e91b2fce94d.png)* Решение
Основная причина всех этих проблем заключается в том, что файл **viewer.html** был помещён на клиентскую сторону, что привело к потере ресурсов при загрузке. Чтобы решить эту проблему, было предложено поместить этот файл на серверную часть на основе Spring.

#### 2.2 Метод решения №2

* В коде серверной части на основе **Spring MVC** папки **build** и **web** из плагина должны быть перемещены в папку **webapp** (создайте новую папку pdf), аналогично для проектов на основе Spring Boot — в директорию статических ресурсов.
<img src="http://silianpan.cn/wp-content/uploads/2019/10/1984ff0a6031134fe90b0d7f7d1ba0a8.png" width="400" align=center />* Конфигурирование доступа к **статическим файлам** в XML-файле.

![](http://silianpan.cn/wp-content/uploads/2019/10/9a58d71e06aff7864d103abe4753e233.png)

* Изменение компонента **file-preview.vue** фронтенда, где **viewerUrl** — это путь к базовой конфигурации `globalConfig.baseUrl`, которая является **базовым адресом** проксирующего сервера. Например, **Vue proxyTable** или **Nginx прокси**.
```js
viewerUrl: globalConfig.baseUrl + '/pdf/web/viewer.html'
  • После изменения эффекты:
    • Для iOS
    * Для Android Размытие вызвано симулятором, но тестирование на реальном устройстве прошло успешно

Три. Сервисы работы с файлами

3.1 Метод 1: Конфигурирование TomcatКонфигурирование файла server.xml, находящегося в директории config Tomcat, добавлением следующих данных между тегами <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">
    <!-- имя — это адрес для доступа к проекту, этот конфигурационный файл будет доступен по адресу http://localhost:8080, appBase — это путь к директории webapps внутри Tomcat -->
    <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 Метод 2: Получение файлов с сервера через код

Пример кода:

Чтение файлов из директории и преобразование их в поток бинарных данных
В компоненте file-preview.vue фронтенда fileUrl указывает на /api/attachment

Основной код:

InputStream ios = new FileInputStream(sourceFile);
OutputStream 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();
``````java
@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).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. Офисные файлы (Word, Excel, PPT) для просмотра

> Принцип:
> Установка **OpenOffice**-сервера, конвертация файлов в PDF и использование pdf.js для просмотра.

#### 4.1 Установка OpenOffice сервера

* Скачивание [Apache_OpenOffice](https://sourceforge.net/projects/openofficeorg.mirror/files/)
  
* Распаковка```bash
tar xzvf 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 доступен с любого удаленного адреса
/opt/openoffice4/program/soffice "-accept=socket,host=0.0.0.0,port=8100;urp;" -headless -nofirststartwizard &

4.2 Интеграция Java

  • Добавление jar-пакетов в pom.xml
<!-- 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, более ранние версии не подходят, а также версия 2.2.2 отсутствует в центральном репозитории Maven. Ссылка на скачивание: https://sourceforge.net/projects/jodconverter/files/

Отдельная установка

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);
``````java
/* Use openOffice for converting office files to PDF format, then view 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)) {
    /* The filePath in the database is specified without file extension since jodConverter requires it for identification, rename the file on the server with the extension */
    // File docFileWithExt = new File(filePath + "." + ext.toLowerCase()); // file with extension
    // docFile.renameTo(docFileWithExt);
    /* Name of the file after conversion */
    String filePath = sourceFile.getPath();
    File pdfFile;
    if (filePath.contains(".")) {
        pdfFile = new File(filePath.substring(0, filePath.lastIndexOf(".")) + ".pdf");
    } else {
        pdfFile = new File(filePath + ".pdf");
    }
}

/* Check whether the file that will be converted exists / if (sourceFile.exists()) { / Check whether the file has already been converted previously. If yes, display it immediately / if (!pdfFile.exists()) { OpenOfficeConnection connection; / Establish a connection with OpenOffice */ try { connection = new SocketOpenOfficeConnection(openofficeHost, openofficePort); connection.connect(); } catch (java.net.ConnectException e) { log.warn("OpenOffice not connected, trying to reconnect...");

        // Start the OpenOffice service
        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 { // При возникновении исключения соединение не закрывается автоматически, программа виснет
    try {
        if (connection != null) {
            connection.disconnect();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

if (sourceFile == null || !sourceFile.exists()) {
    //filePath = pdfFile.getPath(); // Файл уже был конвертирован ранее
    sourceFile = pdfFile;
    response.setContentType("application/pdf");
} else {
    log.info("Запрошенный для просмотра документ отсутствует на сервере!!!");
    // Если файл отсутствует, завершаем выполнение
    return;
}
}### 5. Изображение предварительного просмотра

#### 5.1 Очередность файловых потоков серверной части
``````java
/* Preview image */
if ("PNG".equals(ext) || "JPEG".equals(ext) || "JPG".equals(ext)) {
    response.setContentType("image/jpeg");
}
/* Preview file in BMP format */
if ("BMP".equals(ext)) {
    response.setContentType("image/bmp");
}
/* Preview file in GIF format */
if ("GIF".equals(ext)) {
    response.setContentType("image/gif");
}
```#### 5.2 Предварительный просмотр на клиентской стороне

> Используется интерфейс [uni.previewImage](https://uniapp.dcloud.io/api/media/image) библиотеки uni-app
> **fileUrl**: адрес доступа к файловому потоку

```js
// Предварительный просмотр изображения
uni.previewImage({
  urls: [fileUrl],
  longPressActions: {
    itemList: ['Отправить другу', 'Сохранить изображение', 'Добавить в закладки'],
    success: function(data) {
      console.log('Выбрано ' + (data.tapIndex + 1) + '-ое действие, ' + (data.index + 1) + '-ое изображение');
    },
    fail: function(err) {
      console.log(err.errMsg);
    }
  }
})
```

### Приложение: полный код файлового потока

```java
@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);
``````java
        attachmentVO.setUrl(url);
        attachmentVO.setName(name);
        File sourceFile = getDictionaryFile(attachmentVO, request);
        if (null == sourceFile) {
            //throw new HttpResponseException(300, "Файл отсутствует!");
            return;
        }
```

```markdown
/**
 * Определение типа файла
 */
/* Получение расширения имени файла */
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");
}
```

```markdown
/* Используйте OpenOffice для конвертации офисных файлов в 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 успешно переподключен!!!");
            }
        }
    }```markdown
//                            filePath = pdfFile.getPath(); # путь после конвертации файла
                        sourceFile = pdfFile
                        response.setContentType("application/pdf")
                    } catch (OpenOfficeException e) {
                        e.printStackTrace() # не удалось прочитать конвертированный файл
                        log.info("Не удалось прочитать конвертированный файл!!!")
                        return
                    } finally { # при возникновении исключения соединение не будет автоматически закрыто, программа будет висеть
                        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
            }
        }
``````markdown
## Шесть. Открытый проект

[Открытый проект uniapp-admin](https://github.com/silianpan/uniapp-admin)

<br>
<center style="font-size:20px">Поддержите автора</center>
<center><img src="http://silianpan.cn/wp-content/uploads/2019/10/b9c369443b192642f975be9020b3234e.png" width="240" align=center /></center>
<br>
<center><img src="http://silianpan.cn/wp-content/themes/yusi1.0/img/weixin.gif" width="240" align=center /></center>
```

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/twofloor-uniapp-admin.git
git@api.gitlife.ru:oschina-mirror/twofloor-uniapp-admin.git
oschina-mirror
twofloor-uniapp-admin
twofloor-uniapp-admin
master