# 文档在线预览 **Repository Path**: marszhangzhixing/file-preview-spring-boot-starter ## Basic Information - **Project Name**: 文档在线预览 - **Description**: 一个文档在线预览的中间件,可通过简单的配置即可集成到springboot中,支持word、excel、ppt、pdf、ofd、图片、视频、音频、markdown、代码、网页、epub图书文档、Xmind脑图、压缩文件、bpmn业务流程、cmmn案例管理、dmn决策、3D模型等格式文件的在线预览 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 48 - **Created**: 2024-11-12 - **Last Updated**: 2024-11-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # file-preview-spring-boot-starter [](https://jitpack.io/#wb04307201/file-preview-spring-boot-starter) [](https://gitee.com/wb04307201/file-preview-spring-boot-starter) [](https://gitee.com/wb04307201/file-preview-spring-boot-starter) [](https://github.com/wb04307201/file-preview-spring-boot-starter) [](https://github.com/wb04307201/file-preview-spring-boot-starter)    ## 简介 > 一个文档在线预览的中间件 > 可通过简单的配置即可集成到springboot中 > 支持word、excel、ppt、pdf、ofd、图片、视频、音频、markdown、代码、网页、epub图书文档、Xmind脑图、压缩文件、bpmn业务流程、cmmn案例管理、dmn决策、3D模型等格式文件的在线预览 ## 支持的文件类型以及相关的的开源项目 | 文件类型 | 预览组件 | 预览示例 | |----------------|----------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| | word/excel/ppt | [jodconverter](https://github.com/sbraconnier/jodconverter/) | | | word/excel/ppt | [Spire.Office](https://www.e-iceblue.com/) | | | word/excel/ppt | [onlyoffice](https://www.onlyoffice.com/zh/) | | | word/excel/ppt | [libreoffice online](https://zh-cn.libreoffice.org/download/libreoffice-online/) | | | word/excel/ppt | [Collabora Online](https://www.collaboraoffice.com/) | | | pdf | [PDF.js](https://mozilla.github.io/pdf.js/) | | | audio音频 | [audio.js](http://kolber.github.io/audiojs/) | | | video视频 | [videojs](https://videojs.com/) | | | markdonw | [vditor](https://github.com/Vanessa219/vditor) | | | 代码 | [CodeMirror](https://codemirror.net/) | | | epub电子书 | [epub.js](https://github.com/futurepress/epub.js) | | | xmid脑图 | [xmind-embed-viewer](https://github.com/xmindltd/xmind-embed-viewer) | | | 网页 | 直接渲染 | | | 压缩文件 | [Apache Commons Compress](https://commons.apache.org/proper/commons-compress/) | | | bpmn | [bpmn.io](https://bpmn.io/) | | | cmmn | [bpmn.io](https://bpmn.io/) | | | dmn | [bpmn.io](https://bpmn.io/) | | | ofd | [ofd.js](https://gitee.com/Donal/ofd.js) | | | 3D模型 | [Online 3D Viewer](https://gitee.com/Donal/ofd.js) | | ## 代码示例 1. 使用[文档在线预览](https://gitee.com/wb04307201/file-preview-spring-boot-starter)、[多平台文件存储](https://gitee.com/wb04307201/file-storage-spring-boot-starter)、[实体SQL工具](https://gitee.com/wb04307201/sql-util)实现的[文件预览Demo](https://gitee.com/wb04307201/file-preview-demo) 2. 使用[文档在线预览](https://gitee.com/wb04307201/file-preview-spring-boot-starter)、[多平台文件存储](https://gitee.com/wb04307201/file-storage-spring-boot-starter)、[实体SQL工具](https://gitee.com/wb04307201/sql-util)实现的[文件预览VUE Demo](https://gitee.com/wb04307201/file-preview-vue) ## 快速开始 ### 引入依赖 增加 JitPack 仓库 ```xml jitpack.io https://jitpack.io ``` 1.1.7版本后groupId更换为com.github.wb04307201 1.2.0版本后升级到jdk17 SpringBoot3+ 继续使用jdk 8请查看jdk8分支 ```xml com.github.wb04307201 file-preview-spring-boot-starter 1.2.10 ``` ### 在启动类上加上`@EnableFilePreview`注解 ```java @EnableFilePreview @SpringBootApplication public class FilePreviewDemoApplication { public static void main(String[] args) { SpringApplication.run(FilePreviewDemoApplication.class, args); } } ``` ### 添加相关配置 ```yaml file: preview: enableWeb: true # 默认为true,加载内置页面 enableRest: true # 默认为true, 加载内置接口 ``` #### 配置word,excel,ppt文件的预览方式,支持5种模式 #### jodconverter > 安装[libroffice](https://zh-cn.libreoffice.org/)并添加配置 > 详细配置内容请查看[jodconverter](https://github.com/sbraconnier/jodconverter/) ```yaml file: preview: # 使用jodconverter模式,可省略该配置 officeConverter: jod jodconverter: local: enabled: true # libreOffice根目录 officeHome: C:\Program Files\LibreOffice # 任务执行的超时时间 taskExecutionTimeout: 86400000 # 任务队列的超时时间 taskQueueTimeout: 86400000 # 端口(线程) portNumbers: [ 2001,2002,2003 ] # 一个进程的超时时间 processTimeout: 86400000 ``` #### spire.office > 项目中使用的spire.office为免费版本,转换office文件存在一定限制 > 如要使用收费版本,请排除免费版本的依赖并添加正式版本 > [Spire.Office](https://www.e-iceblue.com/) ```yaml file: preview: # 使用spire.office模式 officeConverter: spire ``` #### onlyoffice > 使用[onlyoffice](https://www.onlyoffice.com/zh/)将不对office文件进行转换 > 直接预览word,excel,ppt,文本类型的文件 > 可以通过docker快速安装onlyoffice,命令如下 ```bash docker run --name onlyoffice -i -t -d -p 80:80 -e JWT_SECRET=my_jwt_secret onlyoffice/documentserver #如果需要关闭使用JWT docker run --name onlyoffice -i -t -d -p 80:80 -e JWT_ENABLED=false onlyoffice/documentserver ``` 可参考官方文档 [Installing ONLYOFFICE Docs Community Edition for Docker on a local server](https://helpcenter.onlyoffice.com/installation/docs-community-install-docker.aspx) [Configuring JWT for ONLYOFFICE Docs](https://helpcenter.onlyoffice.com/installation/docs-configure-jwt.aspx) > 容器启动成功后,打开http://127.0.0.1/可以看到欢迎页面 > 如果需要使用onlyoffice自带的测试页面,可以找页面中如下部分,并分别在终端中执行1,2的命令,然后点击按钮3 >  >  > docker版本的onlyoffice安装成功后,在项目中添加配置信息 ```yaml file: preview: # 使用onlyoffice模式 officeConverter: only online: preview: onlyoffice: domain: http://ip:port #OnlyOffice服务所在域 download: http://ip:port/file/preview/download #当前服务的文件下载接口,用于onlyoffice从当前服务下载文件 callback: http://ip:port/file/preview/onlyoffice/callback #当前服务的回写文件服务,用于onlyoffice回写文件到当前服务 secret: my_jwt_secret #如果启用JWT,需要在这里配置约定好的secret ``` #### LibreOffice Online > 使用[libreoffice online](https://zh-cn.libreoffice.org/download/libreoffice-online/)将不对office文件进行转换 > 直接预览word,excel,ppt,文本类型的文件 > 可以通过docker快速安装libreoffice online,命令如下 ```bash #安装并启动docker版本lool docker run --name lool -e "username=admin" -e "password=123456" -e "extra_params=--o:ssl.enable=false --o:storage.filesystem[@allow]=true" -v D:/lool:/srv/data:Z -p 9980:9980 -d libreoffice/online:master # extra_params=--o:ssl.enable=false 关闭ssl # --o:storage.filesystem[@allow]=true 允许读取本地文件 ``` > 容器启动成功后,打开http://127.0.0.1:9980/loleaflet/dist/admin/admin.html可以看到控制台界面 >  > docker版本的lool安装成功后,在项目中添加配置信息 ```yaml file: preview: # 使用LibreOffice Online模式 officeConverter: lool libreoffice: domain: http://ip:port #libreoffice online服务所在地址 download: D:\lool #libreoffice online预览文件存储位置,需要和上面docker创建的容器映射到本地的目录一致 ``` #### Collabora Online > 使用[Collabora Online](https://www.collaboraoffice.com/)将不对office文件进行转换 > 直接预览word,excel,ppt,文本类型的文件 > 可以通过docker快速安装Collabora Online,命令如下 ```bash docker run -t -d --name code -e "username=admin" -e "password=123456" -e "aliasgroup1=http://10.133.61.38:8090" -e "extra_params=--o:ssl.enable=false" -p 9980:9980 collabora/code # extra_params=--o:ssl.enable=false 关闭ssl # aliasgroup1=http://10.133.61.38:8090 配置允许wopi访问地址 ``` > 容器启动成功后,打开http://127.0.0.1:9980/browser/dist/admin/admin.html可以看到控制台界面 >  > docker版本的cool安装成功后,在项目中添加配置信息 ```yaml file: preview: # 使用Collabora Online模式 officeConverter: cool collabora: domain: http://ip:port #Collabora Online服务所在地址 storageDomain: http://ip:port #当前服务的域名,用于Collabora Online从当前服务下载文件 ``` ### 访问内置界面使用文件上传 上传的文件可通过http://ip:端口/file/preview/list进行查看 注意:如配置了context-path需要在地址中对应添加  ## 高级 ### 如何通过配置和实现文件预览记录接口方法将数据持久化到数据库中 ```yaml file: preview: file-preview-record: cn.wubo.file.preview.demo.H2FilePriviewRecordImpl ``` ```java @Component public class H2FilePriviewRecordImpl implements IFilePreviewRecord { static { MutilConnectionPool.init("main", "jdbc:h2:file:./data/demo;AUTO_SERVER=TRUE", "sa", ""); } @Override public FilePreviewInfo save(FilePreviewInfo filePreviewInfo) { FilePreviewRecord filePreviewRecord = FilePreviewRecord.trans(filePreviewInfo); if (!StringUtils.hasLength(filePreviewRecord.getId())) { filePreviewRecord.setId(UUID.randomUUID().toString()); MutilConnectionPool.run("main", conn -> ModelSqlUtils.insertSql(filePreviewRecord).executeUpdate(conn)); } else MutilConnectionPool.run("main", conn -> ModelSqlUtils.updateSql(filePreviewRecord).executeUpdate(conn)); return filePreviewRecord.getFilePreviewInfo(); } @Override public List list(FilePreviewInfo filePreviewInfo) { FilePreviewRecord filePreviewRecord = FilePreviewRecord.trans(filePreviewInfo); return MutilConnectionPool.run("main", conn -> ModelSqlUtils.selectSql(filePreviewRecord).executeQuery(conn)).stream().map(FilePreviewRecord::getFilePreviewInfo).collect(Collectors.toList()); } @Override public FilePreviewInfo findById(String s) { FilePreviewInfo query = new FilePreviewInfo(); query.setId(s); List list = list(query); return list.isEmpty() ? null : list.get(0); } @Override public Boolean delete(FilePreviewInfo filePreviewInfo) { FilePreviewRecord filePreviewRecord = FilePreviewRecord.trans(filePreviewInfo); return MutilConnectionPool.run("main", conn -> ModelSqlUtils.deleteSql(filePreviewRecord).executeUpdate(conn)) > 0; } @Override public void init() { if (Boolean.FALSE.equals(MutilConnectionPool.run("main", conn -> new SQL() { }.isTableExists(conn)))) MutilConnectionPool.run("main", conn -> new SQL() { }.create().parse().createTable(conn)); } } ``` ### 如何通过配置和实现文件存储记录接口方法将文件持久化到其他平台中 ```yaml file: preview: file-storage: cn.wubo.file.preview.demo.MinIOFileStorageImpl ``` ```java @Component public class MinIOFileStorageImpl implements IFileStorage { @Resource FileStorageService fileStorageService; @Override public FilePreviewInfo save(byte[] bytes, String fileName) { FileInfo fileInfo = fileStorageService.save(new MultipartFileStorage(fileName, bytes).setAlias("minio-1").setPath("preview")); FilePreviewInfo filePreviewInfo = new FilePreviewInfo(); filePreviewInfo.setFileName(fileInfo.getOriginalFilename()); filePreviewInfo.setFilePath(fileInfo.getId()); return filePreviewInfo; } @Override public Boolean delete(FilePreviewInfo filePreviewInfo) { return fileStorageService.delete(filePreviewInfo.getFilePath()); } @Override public byte[] getBytes(FilePreviewInfo filePreviewInfo) { MultipartFileStorage file = fileStorageService.download(filePreviewInfo.getFilePath()); return file.getBytes(); } @Override public void init() { } } ``` *注意: 文件存储这部分使用了[file-storage-spring-boot-starter](https://gitee.com/wb04307201/file-storage-spring-boot-starter)* ### 如何通过内置Rest接口实现自定义页面 ```html 预览文件记录 文件名 原始文件名 文件位置 查询 重置 点击上传,或将文件拖拽到此处 ``` ### 如何通过注入FileStorageService实现自定义Rest接口和自定义页面 ```java @Controller public class Demo2Controller { @Autowired FilePreviewService filePreviewService; /** * 处理demo2请求 * * @param model 模型对象 * @param filePreviewInfo 文件预览信息 * @return 返回demo2页面 */ @PostMapping(value = "/demo2") public String demo2(Model model, FilePreviewInfo filePreviewInfo) { // 获取文件存储记录列表 model.addAttribute("list", filePreviewService.list(filePreviewInfo)); // 设置查询参数 model.addAttribute("query", filePreviewInfo); return "demo2"; } /** * 上传文件 * * @param file 上传的文件 * @param model 模型对象 * @return 返回上传结果页面 */ @PostMapping(value = "/upload") public String upload(MultipartFile file, Model model) { try { filePreviewService.covert(file.getInputStream(), file.getOriginalFilename()); FilePreviewInfo filePreviewInfo = new FilePreviewInfo(); model.addAttribute("list", filePreviewService.list(filePreviewInfo)); model.addAttribute("query", filePreviewInfo); } catch (IOException e) { throw new RuntimeException(e); } return "demo2"; } /** * 获取文件列表 * * @param model 模型对象 * @return 返回页面名称 */ @GetMapping(value = "/demo2") public String demo2(Model model) { FilePreviewInfo filePreviewInfo = new FilePreviewInfo(); List list = filePreviewService.list(filePreviewInfo); model.addAttribute("list", list); model.addAttribute("query", filePreviewInfo); return "demo2"; } /** * 删除文件 * * @param req 请求对象 * @param model 模型对象 * @return 返回页面名称 */ @GetMapping(value = "/delete") public String delete(HttpServletRequest req, Model model) { String id = req.getParameter("id"); filePreviewService.delete(id); FilePreviewInfo filePreviewInfo = new FilePreviewInfo(); List list = filePreviewService.list(filePreviewInfo); model.addAttribute("list", list); model.addAttribute("query", filePreviewInfo); return "demo2"; } /** * 下载文件 * * @param req 请求对象 * @param resp 响应对象 */ @GetMapping(value = "/download") public void download(HttpServletRequest req, HttpServletResponse resp) { String id = req.getParameter("id"); FilePreviewInfo filePreviewInfo = filePreviewService.findById(id); byte[] bytes = filePreviewService.getBytes(filePreviewInfo); resp.reset(); try { resp.setContentType(FileUtils.getMimeType(filePreviewInfo.getFileName())); try (OutputStream os = resp.getOutputStream()) { resp.addHeader("Content-Disposition", "attachment;filename=" + new String(Objects.requireNonNull(filePreviewInfo.getOriginalFilename()).getBytes(), StandardCharsets.ISO_8859_1)); IoUtils.writeToStream(bytes, os); } } catch (IOException e) { throw new RuntimeException(e); } } } ``` ```html 上传文件 文件上传 支持word,excel,ppt,pdf,图片,视频,音频,markdown等格式文件的在线预览 提交 文件名 原始文件名 文件位置 查询 # 文件名 原始文件名 文件位置 删除 预览 下载 <#if list?? && (list?size > 0)> <#list list as row> ${row_index + 1} ${row.originalFilename!'-'} ${row.fileName!'-'} ${row.filePath!'-'} @删除 @预览 @下载 #list> #if> ``` ### 如何自定义文件预览方式 比如在实际使用minio作为对象存储,并想直接使用minio的url播放上传的视频 可通过继承IRenderPage并实现support和render方法的方式自定义页面渲染的方式 ```java @Service public class MinIORenderPage implements IRenderPage { @Override public Boolean support(FilePreviewService filePreviewService, FilePreviewInfo filePreviewInfo) { if ("life goes on.mp4".equals(filePreviewInfo.getOriginalFilename())) { return Boolean.TRUE; } return Boolean.FALSE; } @Override public ServerResponse render(FilePreviewService filePreviewService, FilePreviewInfo filePreviewInfo) { try { return ServerResponse.permanentRedirect(new URI("http://127.0.0.1:9000/testfilestorage/temp/preview/50236952-e8c5-4e67-afe9-ff39e3eac8ca.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=MJC7FWTK56VPHS6SUZQL%2F20231106%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20231106T004949Z&X-Amz-Expires=604800&X-Amz-Security-Token=eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NLZXkiOiJNSkM3RldUSzU2VlBIUzZTVVpRTCIsImV4cCI6MTY5OTI3NDk3NiwicGFyZW50IjoiUk9PVFVTRVIifQ.DHAPkWUuPpy7-EVcQOh9VN6FOIbtsZiIX5THR3n7ds72zRpn9EY23BdCqf1wBYwjOel9a8IHF3qi-6z0PAAC0g&X-Amz-SignedHeaders=host&versionId=null&X-Amz-Signature=acf50009a980667d5084236bcb4993a42a57b2419b0ec1955f5eef8f2e7f982c")).build(); } catch (URISyntaxException e) { throw new RuntimeException(e); } } } ```