Java實現(xiàn)在線編輯預覽office文檔詳解
1 在線編輯
1.1 PageOffice簡介
PageOffice是一款在線的office編輯軟件,幫助Web應用系統(tǒng)或Web網(wǎng)站實現(xiàn)用戶在線編輯Word、Excel、PowerPoint文檔??梢酝昝缹崿F(xiàn)在線公文流轉(zhuǎn),領(lǐng)導批閱,蓋章??梢越o文件添加水印,在線安全預覽防止用戶下載和復制文件等
1.2 前端項目
由于pageoffice瀏覽器是ie內(nèi)核,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頁面添加一個按鈕,調(diào)用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解析)
這里給后臺發(fā)請求的是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: {
//控件中的一些常用方法都在這里調(diào)用,比如保存,打印等等
/**
* 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第一個參數(shù)是當前文件的磁盤路徑,磁盤路徑必須反向雙斜杠)。
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 引言
最近遇到了文件預覽的需求,但一搜索發(fā)現(xiàn),這還不是一個簡單的功能。于是又去查詢了很多資料,調(diào)研了一些方案,也踩了好多坑。最后總結(jié)方案如下:
花錢解決(使用市面上現(xiàn)有的文件預覽服務)
微軟,google,阿里云 IMM,XDOC,Office Web 365,wps開放平臺
前端方案
pptx的預覽方案,pdf的預覽方案,docx的預覽方案,xlsx(excel)的預覽方案
服務端方案
openOffice,kkFileView,onlyOffice
2.2 市面上現(xiàn)有的文件預覽服務
2.2.1 微軟
docx,pptx,xlsx可以說是office三件套,那自然得看一下微軟官方提供的文件預覽服務。使用方法特別簡單,只需要將文件鏈接,拼接到參數(shù)后面即可。
記得encodeURL
https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(url)}
對于docx,pptx,xlsx都有較好的支持,pdf不行。
還有一個坑點是:這個服務是否穩(wěn)定,有什么限制,是否收費,都查不到一個定論。在office官方網(wǎng)站上甚至找不到介紹這個東西的地方。
目前只能找到一個Q&A:
微軟官方人員回答表示:

翻譯翻譯,就是:幾乎永久使用,沒有收費計劃,不會存儲預覽的文件數(shù)據(jù),限制文件10MB,建議用于 查看互聯(lián)網(wǎng)上公開的文件。
但經(jīng)過某些用戶測試發(fā)現(xiàn),使用了微軟的文件預覽服務,然后刪除了文件地址,仍然可訪問,但過一段時間才會失效。
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,但我們看網(wǎng)頁的Copyright可以發(fā)現(xiàn),其實是一個西安的公司,不是微軟,但畢竟也提供了文件預覽的服務
官網(wǎng)地址:www.officeweb365.com/

2.2.6 WPS開放平臺
官方地址:solution.wps.cn

付費使用,價格如下:

2.3 前端處理方案
2.3.1 pptx的預覽方案
先查一下有沒有現(xiàn)成的輪子,目前 pptx 的開源預覽方案能找到的只有這個:github.com/g21589/PPTX…[6] 。但已經(jīng)六七年沒有更新,也沒有維護,筆者使用的時候發(fā)現(xiàn)有很多兼容性問題。
簡單來說就是,沒有。
對于這種情況,我們可以自行解析,主要步驟如下:
- 查詢pptx的國際標準
- 解析pptx文件
- 渲染成html或者canvas進行展示
我們先去找一下pptx的國際標準
先解釋下什么是officeopenxml:
Office OpenXML,也稱為OpenXML或OOXML,是一種基于XML的辦公文檔格式,包括文字處理文檔、電子表格、演示文稿以及圖表、圖表、形狀和其他圖形材料。該規(guī)范由微軟開發(fā),并于2006年被ECMA國際采用為ECMA-376。第二個版本于2008年12月發(fā)布,第三個版本于2011年6月發(fā)布。該規(guī)范已被ISO和IEC采用為ISO/IEC 29500。
雖然Microsoft繼續(xù)支持較舊的二進制格式(.doc、.xls和.ppt),但OOXML現(xiàn)在是所有Microsoft Office文檔(.docx、.xlsx和.pptx)的默認格式。
由此可見,Office OpenXML由微軟開發(fā),目前已經(jīng)是國際標準。
接下來我們看一下pptx里面有哪些內(nèi)容,具體可以看pptx的官方標準:officeopenxml-pptx[8]
PresentationML或.pptx文件是一個zip文件,其中包含許多“部分”(通常是UTF-8或UTF-16編碼)或XML文件。該包還可能包含其他媒體文件,例如圖像。該結(jié)構(gòu)根據(jù) OOXML 標準 ECMA-376 第 2 部分中概述的開放打包約定進行組織。
根據(jù)國際標準,我們知道,pptx文件本質(zhì)就是一個zip文件,其中包含許多部分:
部件的數(shù)量和類型將根據(jù)演示文稿中的內(nèi)容而有所不同,但始終會有一個 [Content_Types].xml、一個或多個關(guān)系 (.rels) 部件和一個演示文稿部件(演示文稿.xml),它位于 ppt 文件夾中,用于Microsoft Powerpoint 文件。通常,還將至少有一個幻燈片部件,以及一張母版幻燈片和一張版式幻燈片,從中形成幻燈片。
那么js如何讀取zip呢?
找到一個工具: www.npmjs.com
于是我們可以開始嘗試解析pptx了。
import JSZip from 'jszip' // 加載pptx數(shù)據(jù) const zip = await JSZip.loadAsync(pptxData)
解析[Content_Types].xml
每個pptx必然會有一個 [Content_Types].xml。此文件包含包中部件的所有內(nèi)容類型的列表。每個部件及其類型都必須列在 [Content_Types].xml 中。通過它里面的內(nèi)容,可以解析其他的文件數(shù)據(jù)
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格式,要真正的讀取內(nèi)容需要執(zhí)行 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,
}
}
加載主題
根據(jù) officeopenxml的標準解釋
每個包都包含一個關(guān)系部件,用于定義其他部件之間的關(guān)系以及與包外部資源的關(guān)系。這樣可以將關(guān)系與內(nèi)容分開,并且可以輕松地更改關(guān)系,而無需更改引用目標的源。
除了包的關(guān)系部分之外,作為一個或多個關(guān)系源的每個部件都有自己的關(guān)系部分。每個這樣的關(guān)系部件都可以在部件的_rels子文件夾中找到,并通過在部件名稱后附加“.rels”來命名。
其中主題的相關(guān)信息就在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的展示,交互,是否支持全看瀏覽器的能力,且不同的瀏覽器展示和交互往往不同,如果需要統(tǒng)一的話,最好還是嘗試其他方案。
embed的解析方式也是一樣,這里不舉例子了
2.3.2.2 pdfjs
由mozilla出品,就是我們常見的MDN的老大。而且目前 火狐瀏覽器 使用的 PDF 預覽就是采用這個,我們可以用火狐瀏覽器打開pdf文件,查看瀏覽器使用的js就能發(fā)現(xiàn)
需要注意的是,最新版 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,不過比較好的是,已經(jīng)有人這么做了,還開源了
使用方法如下:
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 前端預覽方案總結(jié)
我們對以上找到的優(yōu)秀的解決方案,進行改進和總結(jié),并封裝成一個web components組件:preview組件
為什么是web components組件?
因為它跟框架無關(guān),可以在任何框架中使用,且使用起來跟原生的div標簽一樣方便。并編寫使用文檔: preview組件文檔, 文檔支持交互體驗。
目前docx,pdf,xlsx預覽基本可以了,都是最好的方案。pptx預覽效果不太好,因為需要自行解析。
2.4 服務端預覽方案
2.4.1 openOffice
由于瀏覽器不能直接打開 docx,pptx,xlsx 等格式文件,但可以直接打開pdf和圖片,因此,我們可以換一個思路,用服務端去轉(zhuǎn)換下文件的格式,轉(zhuǎn)換成瀏覽器能識別的格式,然后再讓瀏覽器打開,這不就OK了嗎,甚至不需要前端處理了。
我們可以借助openOffice的能力,先介紹一下openOffice:
Apache OpenOffice是領(lǐng)先的開源辦公軟件套件,用于文字處理,電子表格,演示文稿,圖形,數(shù)據(jù)庫等。它有多種語言版本,適用于所有常用計算機。它以國際開放標準格式存儲您的所有數(shù)據(jù),還可以從其他常見的辦公軟件包中讀取和寫入文件。它可以出于任何目的完全免費下載和使用。
官網(wǎng)如下: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轉(zhuǎn)換服務....");
configuration.setOfficeHome("這里的路徑一般為C:\\Program Files (x86)\\OpenOffice 4的bin目錄");
configuration.setPortNumbers(port); // 設置轉(zhuǎn)換端口,默認為8100
configuration.setTaskExecutionTimeout(1000 * 60 * 30L);// 設置任務執(zhí)行超時為30分鐘
configuration.setTaskQueueTimeout(1000 * 60 * 60 * 24L);// 設置任務隊列超時為24小時
officeManager = configuration.buildOfficeManager();
officeManager.start(); // 啟動服務
System.out.println("office轉(zhuǎn)換服務啟動成功!");
} catch (Exception e) {
System.out.println("office轉(zhuǎn)換服務啟動失敗!詳細信息:" + e);
}
}
/**
* stop openOffice service.
*/
public static void stopService() {
System.out.println("準備關(guān)閉office轉(zhuǎn)換服務....");
if (officeManager != null) {
officeManager.stop();
}
System.out.println("office轉(zhuǎn)換服務關(guān)閉成功!");
}
public static void convertToPDF(String inputFile, String outputFile) {
startService();
System.out.println("進行文檔轉(zhuǎn)換轉(zhuǎn)換:" + 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,否則無法啟動
啟動項目
找到主文件,主函數(shù)mian,即可執(zhí)行
2.4.3 onlyOffice
官網(wǎng)地址:https://www.onlyoffice.com/zh
開發(fā)者版本和社區(qū)版免費,企業(yè)版付費:www.onlyoffice.com/zh/docs-ent
預覽的文件種類沒有kkFileView多,但對office三件套有很好的支持,甚至支持多人編輯。
以上就是Java實現(xiàn)在線編輯預覽office文檔詳解的詳細內(nèi)容,更多關(guān)于Java在線預覽office的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring Cloud Config實現(xiàn)分布式配置中心
這篇文章主要介紹了Spring Cloud Config實現(xiàn)分布式配置中心,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04
MyBatisPlus中使用or()和and()遇到的問題及細節(jié)處理
這篇文章主要介紹了MyBatisPlus中使用or()和and()遇到的問題,本文通過多種寫法實例代碼相結(jié)合給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08
springboot+mybaties項目中掃描不到@mapper注解的解決方法
本文主要介紹了springboot+mybaties項目中掃描不到@mapper注解的解決方法,該報錯表明掃描不到Mapper層,具有一定的參考價值,感興趣的可以了解一下2024-05-05
java實現(xiàn)簡單的學生信息管理系統(tǒng)代碼實例
這篇文章主要介紹了java實現(xiàn)簡單的學生信息管理系統(tǒng),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-04-04
java自帶命令行工具jmap、jhat與jinfo的使用實例代碼詳解
本篇文章主要通過代碼實例對java自帶命令行工具jmap、jhat與jinfo的使用做出了詳解,需要的朋友可以參考下2017-04-04

