Java实现在线编辑预览office文档详解
作者:爱吃牛肉的大老虎
1 在线编辑
1.1 PageOffice简介
PageOffice是一款在线的office编辑软件,帮助Web应用系统或Web网站实现用户在线编辑Word、Excel、PowerPoint文档。可以完美实现在线公文流转,领导批阅,盖章。可以给文件添加水印,在线安全预览防止用户下载和复制文件等
1.2 前端项目
由于pageoffice浏览器是ie内核,vue3不兼容ie。所以需要把页面放在后端
1.2.1 配置
在 vue.config.js 中配置代理
devServer: { proxy: { '/api': { target: 'http://localhost:8081/samples-springboot-back', //"/api"对应后端项目"http://localhost:8081/samples-springboot-back"地址 ws: true, changeOrigin: true, // 允许跨域 pathRewrite: { '^/api': '' // 标识替换,使用 '/api' 代替真实的接口地址 } } } }
1.2.2 页面部分
在index.html页面引用后端项目(samples-springboot-back)根目录下的pageoffice.js
<script type="text/javascript" src="http://localhost:8081/samples-springboot-back/pageoffice.js"></script>
在index.vue页面添加一个按钮,调用POBrowser.openWindowModeless请求后端。http://localhost:8081/springboot-pageoffice-demo/SimpleWord/Word2 是后端打开文件的controller
POBrowser.openWindowModeless('http://localhost:8081/springboot-pageoffice-demo/SimpleWord/Word2', 'width=1150px;height=900px;');
在Word.vue页面created中通过axios请求后台获取pageoffice控件(注意:后台返回string字符串,前端需要使用v-html解析)
这里给后台发请求的是axios,如果需要添加token可以在main.js中配置拦截器给请求添加token
Word.vue页面,可以直接复制后修改url
<template> <div class="Word"> <div style="height: 800px; width: auto" v-html="poHtmlCode" /> </div> </template> <script> const axios = require("axios"); export default { name: "Word", data() { return { poHtmlCode: "", }; }, created: function () { axios .post("/api/SimpleWord/Word") .then((response) => { this.poHtmlCode = response.data; }) .catch(function (err) { console.log(err); }); }, methods: { //控件中的一些常用方法都在这里调用,比如保存,打印等等 /** * Save()方法是/api/SimpleWord/Word这个后台controller中PageOfficeCtrl控件通过poCtrl.addCustomToolButton定义的方法,除了保存还有另存到本地、打印等功能。 */ Save() { document.getElementById("PageOfficeCtrl1").WebSave(); } }, mounted: function () { // 将PageOffice控件中的方法通过mounted挂载到window对象上,只有挂载后才能被vue组件识别 window.Save = this.Save; }, }; </script>
1.3 后端项目
1.3.1 pom.xml
<dependency> <groupId>com.zhuozhengsoft</groupId> <artifactId>pageoffice</artifactId> <version>5.4.0.3</version> </dependency>
1.3.2 添加配置
在启动类中配置servlet bean,poSysPath 是在 properites 中配置的磁盘路径(注意:pageoffice的poserver.zz等这些请求不要拦截,get和post请求都放出来)
@Bean public ServletRegistrationBean pageofficeRegistrationBean() { com.zhuozhengsoft.pageoffice.poserver.Server poserver = new com.zhuozhengsoft.pageoffice.poserver.Server(); poserver.setSysPath(poSysPath);//设置PageOffice注册成功后,license.lic文件存放的目录 ServletRegistrationBean srb = new ServletRegistrationBean(poserver); srb.addUrlMappings("/poserver.zz"); srb.addUrlMappings("/posetup.exe"); srb.addUrlMappings("/pageoffice.js"); srb.addUrlMappings("/jquery.min.js"); srb.addUrlMappings("/pobstyle.css"); srb.addUrlMappings("/sealsetup.exe"); return srb; }
1.3.3 controller
打开文件的controller(webopen第一个参数是当前文件的磁盘路径,磁盘路径必须反向双斜杠)。
setServerPage和setSaveFilePage中的api是前端代理,前后端分离项目必须配置代理
@RestController @RequestMapping(value = "/SimpleWord") public class SimpleWordController { @RequestMapping(value="/Word") public String showWord(HttpServletRequest request) { PageOfficeCtrl poCtrl = new PageOfficeCtrl(request); poCtrl.setServerPage("/api/poserver.zz");//设置服务页面 poCtrl.addCustomToolButton("保存", "Save", 1); poCtrl.setSaveFilePage("/api/SimpleWord/save");//设置保存方法的url //打开word poCtrl.webOpen("D:\\doc\\test.docx", OpenModeType.docNormalEdit, "张三"); return poCtrl.getHtmlCode("PageOfficeCtrl1"); } @RequestMapping("save") public void save(HttpServletRequest request, HttpServletResponse response) { FileSaver fs = new FileSaver(request, response); fs.saveToFile("D:\\doc\\" + fs.getFileName()); fs.close(); } }
2 在线预览
2.1 引言
最近遇到了文件预览的需求,但一搜索发现,这还不是一个简单的功能。于是又去查询了很多资料,调研了一些方案,也踩了好多坑。最后总结方案如下:
花钱解决(使用市面上现有的文件预览服务)
微软,google,阿里云 IMM,XDOC,Office Web 365,wps开放平台
前端方案
pptx的预览方案,pdf的预览方案,docx的预览方案,xlsx(excel)的预览方案
服务端方案
openOffice,kkFileView,onlyOffice
2.2 市面上现有的文件预览服务
2.2.1 微软
docx,pptx,xlsx可以说是office三件套,那自然得看一下微软官方提供的文件预览服务。使用方法特别简单,只需要将文件链接,拼接到参数后面即可。
记得encodeURL
https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(url)}
对于docx,pptx,xlsx都有较好的支持,pdf不行。
还有一个坑点是:这个服务是否稳定,有什么限制,是否收费,都查不到一个定论。在office官方网站上甚至找不到介绍这个东西的地方。
目前只能找到一个Q&A:
微软官方人员回答表示:
翻译翻译,就是:几乎永久使用,没有收费计划,不会存储预览的文件数据,限制文件10MB,建议用于 查看互联网上公开的文件。
但经过某些用户测试发现,使用了微软的文件预览服务,然后删除了文件地址,仍然可访问,但过一段时间才会失效。
2.2.2 Google Drive查看器
接入简单,同 Office Web Viewer,只需要把 src 改为https://drive.google.com/viewer?url=${encodeURIComponent(url)}
即可。
限制25MB,支持以下格式:
测试效果,支持docx,pptx,xlsx,pdf预览,但pptx预览的效果不如微软,没有动画效果,样式有小部分会错乱。
2.2.3 阿里云 IMM
付费使用
2.2.4 XDOC 文档预览
说了一些大厂的,在介绍一些其他的,需要自行分辨
2.2.5 Office Web 365
需要注意的是,虽然名字很像office
,但我们看网页的Copyright
可以发现,其实是一个西安的公司,不是微软,但毕竟也提供了文件预览的服务
2.2.6 WPS开放平台
官方地址:solution.wps.cn
付费使用,价格如下:
2.3 前端处理方案
2.3.1 pptx的预览方案
先查一下有没有现成的轮子,目前 pptx 的开源预览方案能找到的只有这个:github.com/g21589/PPTX…[6] 。但已经六七年没有更新,也没有维护,笔者使用的时候发现有很多兼容性问题。
简单来说就是,没有。
对于这种情况,我们可以自行解析,主要步骤如下:
- 查询pptx的国际标准
- 解析pptx文件
- 渲染成html或者canvas进行展示
我们先去找一下pptx的国际标准
先解释下什么是officeopenxml:
Office OpenXML,也称为OpenXML或OOXML,是一种基于XML的办公文档格式,包括文字处理文档、电子表格、演示文稿以及图表、图表、形状和其他图形材料。该规范由微软开发,并于2006年被ECMA国际采用为ECMA-376。第二个版本于2008年12月发布,第三个版本于2011年6月发布。该规范已被ISO和IEC采用为ISO/IEC 29500。
虽然Microsoft继续支持较旧的二进制格式(.doc、.xls和.ppt),但OOXML现在是所有Microsoft Office文档(.docx、.xlsx和.pptx)的默认格式。
由此可见,Office OpenXML由微软开发,目前已经是国际标准。
接下来我们看一下pptx里面有哪些内容,具体可以看pptx的官方标准:officeopenxml-pptx[8]
PresentationML或.pptx文件是一个zip文件,其中包含许多“部分”(通常是UTF-8或UTF-16编码)或XML文件。该包还可能包含其他媒体文件,例如图像。该结构根据 OOXML 标准 ECMA-376 第 2 部分中概述的开放打包约定进行组织。
根据国际标准,我们知道,pptx文件本质就是一个zip文件,其中包含许多部分:
部件的数量和类型将根据演示文稿中的内容而有所不同,但始终会有一个 [Content_Types].xml、一个或多个关系 (.rels) 部件和一个演示文稿部件(演示文稿.xml),它位于 ppt 文件夹中,用于Microsoft Powerpoint 文件。通常,还将至少有一个幻灯片部件,以及一张母版幻灯片和一张版式幻灯片,从中形成幻灯片。
那么js如何读取zip呢?
找到一个工具: www.npmjs.com
于是我们可以开始尝试解析pptx了。
import JSZip from 'jszip' // 加载pptx数据 const zip = await JSZip.loadAsync(pptxData)
解析[Content_Types].xml
每个pptx必然会有一个 [Content_Types].xml。此文件包含包中部件的所有内容类型的列表。每个部件及其类型都必须列在 [Content_Types].xml 中。通过它里面的内容,可以解析其他的文件数据
const filesInfo = await getContentTypes(zip) async function getContentTypes(zip: JSZip) { const ContentTypesJson = await readXmlFile(zip, '[Content_Types].xml') const subObj = ContentTypesJson['Types']['Override'] const slidesLocArray = [] const slideLayoutsLocArray = [] for (let i = 0; i < subObj.length; i++) { switch (subObj[i]['attrs']['ContentType']) { case 'application/vnd.openxmlformats-officedocument.presentationml.slide+xml': slidesLocArray.push(subObj[i]['attrs']['PartName'].substr(1)) break case 'application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml': slideLayoutsLocArray.push(subObj[i]['attrs']['PartName'].substr(1)) break default: } } return { slides: slidesLocArray, slideLayouts: slideLayoutsLocArray, } }
解析演示文稿
先获取ppt目录下的presentation.xml演示文稿的大小
由于演示文稿是xml格式,要真正的读取内容需要执行 readXmlFile
const slideSize = await getSlideSize(zip) async function getSlideSize(zip: JSZip) { const content = await readXmlFile(zip, 'ppt/presentation.xml') const sldSzAttrs = content['p:presentation']['p:sldSz']['attrs'] return { width: (parseInt(sldSzAttrs['cx']) * 96) / 914400, height: (parseInt(sldSzAttrs['cy']) * 96) / 914400, } }
加载主题
根据 officeopenxml的标准解释
每个包都包含一个关系部件,用于定义其他部件之间的关系以及与包外部资源的关系。这样可以将关系与内容分开,并且可以轻松地更改关系,而无需更改引用目标的源。
除了包的关系部分之外,作为一个或多个关系源的每个部件都有自己的关系部分。每个这样的关系部件都可以在部件的_rels子文件夹中找到,并通过在部件名称后附加“.rels”来命名。
其中主题的相关信息就在ppt/_rels/presentation.xml.rels中
async function loadTheme(zip: JSZip) { const preResContent = await readXmlFile( zip, 'ppt/_rels/presentation.xml.rels', ) const relationshipArray = preResContent['Relationships']['Relationship'] let themeURI if (relationshipArray.constructor === Array) { for (let i = 0; i < relationshipArray.length; i++) { if ( relationshipArray[i]['attrs']['Type'] === 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme' ) { themeURI = relationshipArray[i]['attrs']['Target'] break } } } else if ( relationshipArray['attrs']['Type'] === 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme' ) { themeURI = relationshipArray['attrs']['Target'] } if (themeURI === undefined) { throw Error("Can't open theme file.") } return readXmlFile(zip, 'ppt/' + themeURI) }
2.3.2 pdf的预览方案
2.3.2.1 iframe和embed
pdf 比较特别,一般的浏览器默认支持预览pdf。因此,我们可以使用浏览器的能力:<iframe src="viewFileUrl" />
但这样就完全依赖浏览器,对PDF的展示,交互,是否支持全看浏览器的能力,且不同的浏览器展示和交互往往不同,如果需要统一的话,最好还是尝试其他方案。
embed的解析方式也是一样,这里不举例子了
2.3.2.2 pdfjs
由mozilla出品,就是我们常见的MDN的老大。而且目前 火狐浏览器 使用的 PDF 预览就是采用这个,我们可以用火狐浏览器打开pdf文件,查看浏览器使用的js就能发现
需要注意的是,最新版 pdf.js 限制了 node 版本,需要大于等于18
如果你项目node版本小于这个情况,可能会无法使用。
具体使用情况如下:
import * as pdfjs from 'pdfjs-dist' import * as pdfjsWorker from 'pdfjs-dist/build/pdf.work.entry' interface Viewport { width: number height: number viewBox: Array<number> } interface RenderContext { canvasContext: CanvasRenderingContext2D | null transform: Array<number> viewport: Viewport } interface PDFPageProxy { pageNumber: number getViewport: () => Viewport render: (options: RenderContext) => void } interface PDFDocumentProxy { numPages: number getPage: (x: number) => Promise<PDFPageProxy> } class PdfPreview { private pdfDoc: PDFDocumentProxy | undefined pageNumber: number total: number dom: HTMLElement pdf: string | ArrayBuffer constructor(pdf: string | ArrayBuffer, dom: HTMLElement | undefined) { this.pageNumber = 1 this.total = 0 this.pdfDoc = undefined this.pdf = pdf this.dom = dom ? dom : document.body } private getPdfPage = (number: number) => { return new Promise((resolve, reject) => { if (this.pdfDoc) { this.pdfDoc.getPage(number).then((page: PDFPageProxy) => { const viewport = page.getViewport() const canvas = document.createElement('canvas') this.dom.appendChild(canvas) const context = canvas.getContext('2d') const [_, __, width, height] = viewport.viewBox canvas.width = width canvas.height = height viewport.width = width viewport.height = height canvas.style.width = Math.floor(viewport.width) + 'px' canvas.style.height = Math.floor(viewport.height) + 'px' const renderContext = { canvasContext: context, viewport: viewport, transform: [1, 0, 0, -1, 0, viewport.height], } page.render(renderContext) resolve({ success: true, data: page }) }) } else { reject({ success: false, data: null, message: 'pdfDoc is undefined' }) } }) } pdfPreview = () => { window.pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker window.pdfjsLib .getDocument(this.pdf) .promise.then(async (doc: PDFDocumentProxy) => { this.pdfDoc = doc this.total = doc.numPages for (let i = 1; i <= this.total; i++) { await this.getPdfPage(i) } }) } prevPage = () => { if (this.pageNumber > 1) { this.pageNumber -= 1 } else { this.pageNumber = 1 } this.getPdfPage(this.pageNumber) } nextPage = () => { if (this.pageNumber < this.total) { this.pageNumber += 1 } else { this.pageNumber = this.total } this.getPdfPage(this.pageNumber) } } const createReader = (file: File): Promise<string | ArrayBuffer | null> => { return new Promise((resolve, reject) => { const reader = new FileReader() reader.readAsDataURL(file) reader.onload = () => { resolve(reader.result) } reader.onerror = (error) => { reject(error) } reader.onabort = (abort) => { reject(abort) } }) } export const renderPdf = async ( file: File, dom?: HTMLElement, ): Promise<void> => { try { if (typeof window !== 'undefined') { const pdf = await createReader(file) if (pdf) { const PDF = new PdfPreview(pdf, dom) PDF.pdfPreview() } } } catch (error) { console.log('renderPdf', error) } }
2.3.3 docx的预览方案
我们可以去查看docx的国际标准,去解析文件格式,渲染成html和canvas,不过比较好的是,已经有人这么做了,还开源了
使用方法如下:
import { renderAsync } from 'docx-preview' interface DocxOptions { bodyContainer?: HTMLElement | null styleContainer?: HTMLElement buffer: Blob docxOptions?: Partial<Record<string, string | boolean>> } export const renderDocx = (options: DocxOptions): Promise<void> | undefined => { if (typeof window !== 'undefined') { const { bodyContainer, styleContainer, buffer, docxOptions = {} } = options const defaultOptions = { className: 'docx', ignoreLastRenderedPageBreak: false, } const configuration = Object.assign({}, defaultOptions, docxOptions) if (bodyContainer) { return renderAsync(buffer, bodyContainer, styleContainer, configuration) } else { const contain = document.createElement('div') document.body.appendChild(contain) return renderAsync(buffer, contain, styleContainer, configuration) } } }
2.3.4 前端预览方案总结
我们对以上找到的优秀的解决方案,进行改进和总结,并封装成一个web components组件:preview组件
为什么是web components组件?
因为它跟框架无关,可以在任何框架中使用,且使用起来跟原生的div标签一样方便。并编写使用文档: preview组件文档, 文档支持交互体验。
目前docx,pdf,xlsx预览基本可以了,都是最好的方案。pptx预览效果不太好,因为需要自行解析。
2.4 服务端预览方案
2.4.1 openOffice
由于浏览器不能直接打开 docx,pptx,xlsx 等格式文件,但可以直接打开pdf和图片,因此,我们可以换一个思路,用服务端去转换下文件的格式,转换成浏览器能识别的格式,然后再让浏览器打开,这不就OK了吗,甚至不需要前端处理了。
我们可以借助openOffice的能力,先介绍一下openOffice:
Apache OpenOffice是领先的开源办公软件套件,用于文字处理,电子表格,演示文稿,图形,数据库等。它有多种语言版本,适用于所有常用计算机。它以国际开放标准格式存储您的所有数据,还可以从其他常见的办公软件包中读取和写入文件。它可以出于任何目的完全免费下载和使用。
官网如下:www.openoffice.org
完整示例如下:
package org.example; import org.artofsolving.jodconverter.OfficeDocumentConverter; import org.artofsolving.jodconverter.office.DefaultOfficeManagerConfiguration; import org.artofsolving.jodconverter.office.OfficeManager; import java.io.File; public class OfficeUtil { private static OfficeManager officeManager; private static int port[] = {8100}; /** * start openOffice service. */ public static void startService() { DefaultOfficeManagerConfiguration configuration = new DefaultOfficeManagerConfiguration(); try { System.out.println("准备启动office转换服务...."); configuration.setOfficeHome("这里的路径一般为C:\\Program Files (x86)\\OpenOffice 4的bin目录"); configuration.setPortNumbers(port); // 设置转换端口,默认为8100 configuration.setTaskExecutionTimeout(1000 * 60 * 30L);// 设置任务执行超时为30分钟 configuration.setTaskQueueTimeout(1000 * 60 * 60 * 24L);// 设置任务队列超时为24小时 officeManager = configuration.buildOfficeManager(); officeManager.start(); // 启动服务 System.out.println("office转换服务启动成功!"); } catch (Exception e) { System.out.println("office转换服务启动失败!详细信息:" + e); } } /** * stop openOffice service. */ public static void stopService() { System.out.println("准备关闭office转换服务...."); if (officeManager != null) { officeManager.stop(); } System.out.println("office转换服务关闭成功!"); } public static void convertToPDF(String inputFile, String outputFile) { startService(); System.out.println("进行文档转换转换:" + inputFile + " --> " + outputFile); OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager); converter.convert(new File(inputFile), new File(outputFile)); stopService(); } public static void main(String[] args) { convertToPDF("/Users/koolearn/Desktop/asdf.docx", "/Users/koolearn/Desktop/adsf.pdf"); } }
2.4.2 kkFileView
支持的文件预览格式非常丰富图片
安装下libreoffice :
kkFileView明确要求的额外依赖 libreoffice,否则无法启动
启动项目
找到主文件,主函数mian,即可执行
2.4.3 onlyOffice
官网地址:https://www.onlyoffice.com/zh
开发者版本和社区版免费,企业版付费:www.onlyoffice.com/zh/docs-ent
预览的文件种类没有kkFileView多,但对office三件套有很好的支持,甚至支持多人编辑。
以上就是Java实现在线编辑预览office文档详解的详细内容,更多关于Java在线预览office的资料请关注脚本之家其它相关文章!