title: "Кросс-платформенная (uni-app) система онлайн-просмотра файлов"
author: silianpan
date: 2019-10-23
output: word_document
Праздник программиста прошел, желаю всем коллегам здоровья, успехов во всех делах и отсутствия ошибок.
Ранее я писал статью о загрузке каталоговых файлов: uni-app системные каталоговые файлы загрузка (не только картинки и видео) решение, сегодня мы решаем проблему просмотра файлов.
uni-app — это фреймворк для создания всех типов фронтенд-приложений с использованием Vue.js. Разработчики могут создать одну базу кода и запустить её на iOS, Android, H5, а также различных мини-приложениях (WeChat / Alipay / Baidu / Toutiao / QQ / DingTalk). При работе над бизнес-системами, неизбежно возникают требования к онлайн-просмотру файлов. Здесь подразумеваются различные типы файлов, такие как PDF, Word, Excel, PPT, изображения и т.д. Онлайн-просмотр не включает скачивание файла и последующий просмотр через локальные программы или браузеры, а осуществляется непосредственно через поток файлов. Данное решение направлено на решение проблемы онлайн-просмотра, а также на преодоление всех возникающих проблем при разработке в uni-app. Если вы заметили недочеты или знаете лучшие решения, буду рад получить ваши предложения и советы. Вы можете связаться со мной в конце статьи.> Для просмотра PDF-файлов первым делом приходит на ум pdf.js. Мы начнем с этого проекта.
Открытый адрес и пример использования pdf.js GitHub Онлайн-демо
<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="http://silianpan.cn/wp-content/uploads/2019/10/13bd0bc4eca43f65e77c1cfe641056db.png" width="360" align=center />

* Для iOS
Возникает **проблема с кросс-доменной политикой (cross-domain)**, а также при отладке невозможно получить доступ к файлам pdf.js для международной локализации
<img src="http://silianpan.cn/wp-content/uploads/2019/10/32cc3e59ea14d7261c11a8b92a0d0478.png" width="360" align=center />
* Решение
Основная причина всех этих проблем заключается в том, что файл **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-файле.

* Изменение компонента **file-preview.vue** фронтенда, где **viewerUrl** — это путь к базовой конфигурации `globalConfig.baseUrl`, которая является **базовым адресом** проксирующего сервера. Например, **Vue proxyTable** или **Nginx прокси**.
```js
viewerUrl: globalConfig.baseUrl + '/pdf/web/viewer.html'
<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>
Пример кода:
Чтение файлов из директории и преобразование их в поток бинарных данных
В компоненте 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 &
<!-- 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 )