Docker 部署onlyoffice的詳細(xì)過(guò)程
以下是全面優(yōu)化后的 OnlyOffice 部署、Token 生成、前端測(cè)試及文件轉(zhuǎn)換方案,解決潛在問(wèn)題、提升穩(wěn)定性和易用性:
一、Docker 部署優(yōu)化(穩(wěn)定性+可維護(hù)性)
sudo docker run -itd \ -p 1230:80 \ --name onlyoffice \ --restart=always \ # 容器異常自動(dòng)重啟 --privileged=true \ # 解決文件權(quán)限問(wèn)題 -e JWT_SECRET=VI71S3cGtX75g96HgFW52785zQhblz1KwMc1Jzk \ -e JWT_ENABLED=true \ -e TZ=Asia/Shanghai \ # 同步時(shí)區(qū),避免日志時(shí)間錯(cuò)亂 -v /data/middleware/onlyoffice/DocumentServer/logs:/var/log/onlyoffice \ -v /data/middleware/onlyoffice/DocumentServer/data:/var/www/onlyoffice/Data \ -v /data/middleware/onlyoffice/DocumentServer/lib:/var/lib/onlyoffice \ -v /data/middleware/onlyoffice/DocumentServer/rabbitmq:/var/lib/rabbitmq \ -v /data/middleware/onlyoffice/DocumentServer/redis:/var/lib/redis \ -v /data/middleware/onlyoffice/DocumentServer/db:/var/lib/postgresql \ onlyoffice/documentserver:7.3
優(yōu)化點(diǎn):
- 新增
--restart=always確保服務(wù)高可用 - 增加
--privileged=true解決宿主機(jī)與容器文件權(quán)限不匹配問(wèn)題 - 配置
TZ=Asia/Shanghai同步時(shí)區(qū),日志時(shí)間更易排查 - 格式化命令換行,提升可讀性
二、Token 生成工具類(lèi)優(yōu)化(健壯性+易用性)
package org.example;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Objects;
/**
* OnlyOffice JWT Token 生成工具(優(yōu)化版)
* 支持預(yù)覽/編輯/轉(zhuǎn)換三種模式,新增參數(shù)校驗(yàn)、異常處理、可配置化
*/
public class OnlyOfficeTokenGenerator {
// 可配置常量(集中管理,便于修改)
private static final String JWT_SECRET = "VI71S3cGtX75g96HgFW52785zQhblz1KwMc1Jzk";
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final String ALGORITHM = "HS256";
private static final String TYPE = "JWT";
private static final int EXPIRATION_SECONDS = 3600; // 1小時(shí)有效期
private static final String DEFAULT_LANG = "zh-CN"; // 默認(rèn)中文
// 單例模式(避免重復(fù)創(chuàng)建對(duì)象)
private static final OnlyOfficeTokenGenerator INSTANCE = new OnlyOfficeTokenGenerator();
private OnlyOfficeTokenGenerator() {}
public static OnlyOfficeTokenGenerator getInstance() { return INSTANCE; }
public static void main(String[] args) {
try {
// 配置參數(shù)(替換為實(shí)際值)
String docUrl = "http://101.42.40.19:9000/xingyue-knowledge/工作需要1.0.docx";
String docTitle = "工作需要1.0.docx";
String fileType = "docx";
String callbackUrl = "https://xingyue.asia/test/callback";
// 生成 Token
String viewToken = INSTANCE.generateViewToken(docUrl, docTitle, fileType, "word");
System.out.println("預(yù)覽 Token:\n" + viewToken + "\n");
String editToken = INSTANCE.generateEditToken(docUrl, docTitle, fileType, "word", callbackUrl, "測(cè)試用戶(hù)", "user123");
System.out.println("編輯 Token:\n" + editToken + "\n");
String convertToken = INSTANCE.generateConvertToken(docUrl, fileType, "pdf", "convert_"+System.currentTimeMillis(), docTitle);
System.out.println("轉(zhuǎn)換 Token:\n" + convertToken);
} catch (Exception e) {
System.err.println("Token 生成失?。? + e.getMessage());
e.printStackTrace();
}
}
/**
* 預(yù)覽模式 Token(僅查看,無(wú)編輯權(quán)限)
*/
public String generateViewToken(String docUrl, String docTitle, String fileType, String documentType) throws Exception {
validateRequiredParams(
new StringParam(docUrl, "文檔URL"),
new StringParam(docTitle, "文檔標(biāo)題"),
new StringParam(fileType, "文件類(lèi)型"),
new StringParam(documentType, "文檔類(lèi)別")
);
return generateJWT(createDocumentPayload("view", docUrl, docTitle, fileType, documentType, null, null, null));
}
/**
* 編輯模式 Token(支持修改+回調(diào))
*/
public String generateEditToken(String docUrl, String docTitle, String fileType, String documentType,
String callbackUrl, String userName, String userId) throws Exception {
validateRequiredParams(
new StringParam(docUrl, "文檔URL"),
new StringParam(docTitle, "文檔標(biāo)題"),
new StringParam(fileType, "文件類(lèi)型"),
new StringParam(documentType, "文檔類(lèi)別"),
new StringParam(callbackUrl, "回調(diào)地址"),
new StringParam(userName, "用戶(hù)名"),
new StringParam(userId, "用戶(hù)ID")
);
return generateJWT(createDocumentPayload("edit", docUrl, docTitle, fileType, documentType, callbackUrl, userName, userId));
}
/**
* 文件轉(zhuǎn)換 Token(支持 docx→pdf 等格式轉(zhuǎn)換)
*/
public String generateConvertToken(String docUrl, String fileType, String outputType, String convertKey, String title) throws Exception {
validateRequiredParams(
new StringParam(docUrl, "文檔URL"),
new StringParam(fileType, "源文件類(lèi)型"),
new StringParam(outputType, "目標(biāo)文件類(lèi)型"),
new StringParam(convertKey, "轉(zhuǎn)換標(biāo)識(shí)Key"),
new StringParam(title, "文件標(biāo)題")
);
return generateJWT(createConvertPayload(docUrl, fileType, outputType, convertKey, title));
}
/**
* 通用 JWT 生成邏輯(優(yōu)化 Base64 編碼、簽名算法穩(wěn)定性)
*/
private String generateJWT(Map<String, Object> payload) throws Exception {
// 構(gòu)建 Header
Map<String, String> header = new HashMap<>(2);
header.put("alg", ALGORITHM);
header.put("typ", TYPE);
// JSON 序列化(避免空值導(dǎo)致的解析異常)
String headerJson = MAPPER.writeValueAsString(header);
String payloadJson = MAPPER.writeValueAsString(payload);
// Base64URL 編碼(嚴(yán)格遵循 JWT 標(biāo)準(zhǔn))
String headerBase64 = base64UrlEncode(headerJson.getBytes(StandardCharsets.UTF_8));
String payloadBase64 = base64UrlEncode(payloadJson.getBytes(StandardCharsets.UTF_8));
// 生成簽名(避免密鑰為空)
if (JWT_SECRET == null || JWT_SECRET.trim().isEmpty()) {
throw new IllegalArgumentException("JWT_SECRET 不能為空");
}
String signature = hmacSha256(headerBase64 + ".", JWT_SECRET);
return headerBase64 + "." + payloadBase64 + "." + signature;
}
/**
* 構(gòu)建文檔操作 Payload(預(yù)覽/編輯)
*/
private Map<String, Object> createDocumentPayload(String mode, String docUrl, String docTitle, String fileType,
String documentType, String callbackUrl, String userName, String userId) {
Map<String, Object> payload = new HashMap<>();
payload.put("document", createDocumentInfo(docUrl, docTitle, fileType));
payload.put("editorConfig", createEditorConfig(mode, callbackUrl, userName, userId));
payload.put("documentType", documentType);
// 設(shè)置過(guò)期時(shí)間(避免 Token 永久有效)
long currentTime = System.currentTimeMillis() / 1000;
payload.put("iat", currentTime);
payload.put("exp", currentTime + EXPIRATION_SECONDS);
return payload;
}
/**
* 構(gòu)建文件轉(zhuǎn)換 Payload
*/
private Map<String, Object> createConvertPayload(String docUrl, String fileType, String outputType, String convertKey, String title) {
Map<String, Object> payload = new HashMap<>();
payload.put("async", false); // 同步轉(zhuǎn)換(立即返回結(jié)果)
payload.put("filetype", fileType);
payload.put("key", convertKey);
payload.put("outputtype", outputType);
payload.put("title", title);
payload.put("url", docUrl);
// 設(shè)置過(guò)期時(shí)間
long currentTime = System.currentTimeMillis() / 1000;
payload.put("iat", currentTime);
payload.put("exp", currentTime + EXPIRATION_SECONDS);
return payload;
}
/**
* 構(gòu)建文檔基礎(chǔ)信息(優(yōu)化文檔 Key 生成邏輯)
*/
private Map<String, Object> createDocumentInfo(String docUrl, String docTitle, String fileType) {
Map<String, Object> document = new HashMap<>();
// 文檔 Key:基于 URL + 時(shí)間戳生成(避免同一文檔多次打開(kāi)沖突)
String docKey = Base64.getUrlEncoder().withoutPadding().encodeToString((docUrl + System.currentTimeMillis()).getBytes());
document.put("fileType", fileType);
document.put("key", docKey);
document.put("title", docTitle);
document.put("url", docUrl);
return document;
}
/**
* 構(gòu)建編輯器配置
*/
private Map<String, Object> createEditorConfig(String mode, String callbackUrl, String userName, String userId) {
Map<String, Object> editorConfig = new HashMap<>();
editorConfig.put("mode", mode);
editorConfig.put("lang", DEFAULT_LANG); // 默認(rèn)中文
// 編輯模式額外配置
if ("edit".equals(mode)) {
editorConfig.put("callbackUrl", callbackUrl);
editorConfig.put("canDownload", true); // 允許下載文檔
editorConfig.put("canPrint", true); // 允許打印文檔
// 用戶(hù)信息
Map<String, Object> user = new HashMap<>();
user.put("name", userName);
user.put("id", userId);
editorConfig.put("user", user);
}
return editorConfig;
}
/**
* 基礎(chǔ)工具類(lèi):Base64URL 編碼(遵循 JWT 標(biāo)準(zhǔn),去除 = 填充)
*/
private String base64UrlEncode(byte[] data) {
return Base64.getUrlEncoder().withoutPadding().encodeToString(data);
}
/**
* 基礎(chǔ)工具類(lèi):HMAC-SHA256 簽名
*/
private String hmacSha256(String data, String secret) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
mac.init(secretKeySpec);
byte[] signatureBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
return base64UrlEncode(signatureBytes);
}
/**
* 參數(shù)校驗(yàn)工具(支持批量校驗(yàn),清晰提示缺失參數(shù))
*/
private static class StringParam {
private final String value;
private final String name;
public StringParam(String value, String name) {
this.value = value;
this.name = name;
}
public boolean isInvalid() { return value == null || value.trim().isEmpty(); }
public String getName() { return name; }
}
private void validateRequiredParams(StringParam... params) {
StringBuilder errorMsg = new StringBuilder();
for (StringParam param : params) {
if (param.isInvalid()) {
errorMsg.append(param.getName()).append("、");
}
}
if (errorMsg.length() > 0) {
throw new IllegalArgumentException("必填參數(shù)缺失:" + errorMsg.substring(0, errorMsg.length() - 1));
}
}
}優(yōu)化點(diǎn):
- 采用單例模式,減少對(duì)象創(chuàng)建開(kāi)銷(xiāo)
- 新增集中式配置常量,便于維護(hù)
- 優(yōu)化參數(shù)校驗(yàn)邏輯,錯(cuò)誤提示更清晰
- 改進(jìn)文檔 Key 生成規(guī)則,避免沖突
- 編輯模式新增下載/打印權(quán)限配置
- 完善異常處理,便于問(wèn)題排查
- 嚴(yán)格遵循 JWT 標(biāo)準(zhǔn),提升兼容性
三、前端測(cè)試頁(yè)優(yōu)化(兼容性+用戶(hù)體驗(yàn))
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OnlyOffice 文檔編輯測(cè)試</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body, html { height: 100%; overflow: hidden; }
#placeholder { height: 100vh; width: 100vw; }
/* 加載提示樣式 */
.loading {
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
font-size: 18px; color: #333; z-index: 9999;
}
</style>
</head>
<body>
<div class="loading">文檔加載中...</div>
<div id="placeholder"></div>
<!-- 優(yōu)先加載本地 OnlyOffice API(避免跨域/網(wǎng)絡(luò)問(wèn)題) -->
<script type="text/javascript" src="http://localhost:1230/web-apps/apps/api/documents/api.js"></script>
<script type="text/javascript">
// 配置參數(shù)(替換為實(shí)際 Token 和文檔地址)
const config = {
token: "替換為生成的編輯 Token",
document: {
fileType: "docx",
title: "工作需要1.0.docx",
url: "http://101.42.40.19:9000/xingyue-knowledge/工作需要1.0.docx"
},
documentType: "word",
editorConfig: {
callbackUrl: "https://xingyue.asia/test/callback",
lang: "zh-CN",
user: { name: "測(cè)試用戶(hù)", id: "user123" },
canDownload: true,
canPrint: true
},
width: "100%",
height: "100%",
// 錯(cuò)誤處理回調(diào)(提升用戶(hù)體驗(yàn))
events: {
onError: function(error) {
console.error("OnlyOffice 錯(cuò)誤:", error);
alert(`文檔加載失敗:${error.message}\n請(qǐng)檢查 Token 有效性和文檔地址是否可訪問(wèn)`);
},
onReady: function() {
document.querySelector(".loading").style.display = "none"; // 隱藏加載提示
}
}
};
// 確保 API 加載完成后初始化
if (window.DocsAPI) {
initEditor();
} else {
// 降級(jí)處理:API 加載失敗時(shí)重試
setTimeout(initEditor, 1000);
}
function initEditor() {
try {
window.docEditor = new DocsAPI.DocEditor("placeholder", config);
} catch (e) {
console.error("編輯器初始化失?。?, e);
alert("編輯器初始化失敗,請(qǐng)刷新頁(yè)面重試");
}
}
</script>
</body>
</html>優(yōu)化點(diǎn):
- 加載本地 API 腳本,避免跨域和網(wǎng)絡(luò)依賴(lài)
- 新增加載提示,提升用戶(hù)體驗(yàn)
- 增加錯(cuò)誤處理回調(diào),便于排查問(wèn)題
- 優(yōu)化樣式適配,支持全屏顯示
- 增加 API 加載重試機(jī)制,提升穩(wěn)定性
四、文件轉(zhuǎn)換接口優(yōu)化(安全性+可讀性)
1. 轉(zhuǎn)換請(qǐng)求(推薦用 POSTMAN/ curl 測(cè)試)
請(qǐng)求地址:http://localhost:1230/converter(注意:OnlyOffice 轉(zhuǎn)換接口默認(rèn) 80 端口,對(duì)應(yīng)部署的 1230 端口)
請(qǐng)求方法:POST
請(qǐng)求頭:
Content-Type: application/json
請(qǐng)求體:
{
"async": false,
"filetype": "docx",
"key": "convert_1762394085",
"outputtype": "pdf",
"title": "工作需要1.0.pdf",
"url": "http://101.42.40.19:9000/xingyue-knowledge/工作需要1.0.docx",
"token": "替換為生成的轉(zhuǎn)換 Token"
}到此這篇關(guān)于Docker 部署onlyoffice的文章就介紹到這了,更多相關(guān)Docker 部署onlyoffice內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
docker swarm 集群創(chuàng)建過(guò)程
通過(guò)docker swarm 工具將一臺(tái)或者多臺(tái)安裝了docker的服務(wù)器組成一個(gè)完整的集群,該集群中的node節(jié)點(diǎn)可以通過(guò)Leader節(jié)點(diǎn)管理,這篇文章主要介紹了docker swarm 集群創(chuàng)建,需要的朋友可以參考下2024-03-03
寫(xiě)給前端的nginx配置指南基于docker所有配置秒級(jí)運(yùn)行(最新講解)
這篇文章主要介紹了寫(xiě)給前端的nginx配置指南基于docker所有配置秒級(jí)運(yùn)行,通過(guò)?docker?高效學(xué)習(xí)?nginx?配置,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06
Windows的docker刪除容器后WSL2磁盤(pán)空間不釋放的問(wèn)題的解決方法
很多同學(xué)拉取鏡像使用一段時(shí)間后發(fā)現(xiàn) C 盤(pán)快滿(mǎn)了,把之前用過(guò)的鏡像和容器刪除,發(fā)現(xiàn) WSL 掛載目錄的虛擬磁盤(pán)大小沒(méi)有變化,非常的奇怪,所以本文介紹了Windows的docker刪除容器后WSL2磁盤(pán)空間不釋放的問(wèn)題的解決方法,需要的朋友可以參考下2024-12-12
解決Mac Docker x509證書(shū)的問(wèn)題
這篇文章主要介紹了解決Mac Docker x509證書(shū)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-03-03
在IntelliJ IDEA中使用Docker的開(kāi)發(fā)指南
這篇文章主要給大家介紹了在IntelliJ IDEA中使用Docker開(kāi)發(fā)指南,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)我們的使用Docker有一定的幫助,需要的朋友可以參考下2023-09-09
淺談docker Dockerfile 指令 VOLUME 介紹
本篇文章主要介紹了淺談docker Dockerfile 指令 VOLUME 介紹 ,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02
使用Docker快速搭建Airflow+MySQL詳細(xì)步驟
本文詳細(xì)介紹了如何使用Docker和Docker Compose安裝并配置Apache Airflow 2.9.3,步驟包括創(chuàng)建項(xiàng)目目錄、編寫(xiě)docker-compose.yml文件、創(chuàng)建所需目錄結(jié)構(gòu)、啟動(dòng)和管理Docker容器,以及如何訪問(wèn)和配置Airflow Web UI,感興趣的朋友跟隨小編一起看看吧2024-09-09
docker harbor私有倉(cāng)庫(kù)登錄報(bào)錯(cuò)的問(wèn)題解決
本文主要介紹了docker harbor私有倉(cāng)庫(kù)登錄報(bào)錯(cuò),通過(guò)報(bào)錯(cuò)信息可知,Docker無(wú)法驗(yàn)證Harbor私有倉(cāng)庫(kù)所使用的SSL證書(shū),下面就來(lái)介紹一下問(wèn)題解決,感興趣的可以了解一下2025-08-08
利用OpenVSwitch在多臺(tái)主機(jī)上部署Docker的教程
這篇文章主要介紹了利用OpenVSwitch在多臺(tái)主機(jī)上部署Docker的教程,包括配置多個(gè)容器的IP地址等內(nèi)容,需要的朋友可以參考下2015-03-03

