OnlyOffice 私有化部署与 Spring Boot 整合实战教程
在 Windows 11 环境下通过 Docker 私有化部署 OnlyOffice Document Server,并结合 Spring Boot 实现文档预览与编辑功能。流程包括镜像拉取、容器参数配置、Java 后端接口开发、前端 SDK 集成及常见问题排查,重点解决跨域访问、回调保存及网络连通性问题,提供可直接运行的代码示例与测试步骤。

在 Windows 11 环境下通过 Docker 私有化部署 OnlyOffice Document Server,并结合 Spring Boot 实现文档预览与编辑功能。流程包括镜像拉取、容器参数配置、Java 后端接口开发、前端 SDK 集成及常见问题排查,重点解决跨域访问、回调保存及网络连通性问题,提供可直接运行的代码示例与测试步骤。

docker -v 验证)打开 Win11 的「终端」(管理员模式),执行以下命令拉取官方最新镜像:
# 拉取 OnlyOffice Document Server 最新版镜像
docker pull onlyoffice/documentserver:latest
docker images,能看到 onlyoffice/documentserver 镜像即成功。为了避免容器重启后数据丢失,创建本地目录映射容器内数据目录:
# 新建本地目录(建议放在非系统盘,比如 D 盘)
mkdir D:\onlyoffice\data
mkdir D:\onlyoffice\logs
mkdir D:\onlyoffice\plugins
mkdir D:\onlyoffice\fonts
核心命令:关闭 JWT 验证(忽略 token),并映射端口和目录:
docker run -itd --name onlyoffice \
-p 9999:80 \
-v D:\onlyoffice\data:/var/www/onlyoffice/Data \
-v D:\onlyoffice\logs:/var/log/onlyoffice \
-v D:\onlyoffice\plugins:/var/www/onlyoffice/documentserver/sdkjs-plugins \
-v D:\onlyoffice\fonts:/usr/share/fonts \
-e JWT_SECRET=mysecret \
-e JWT_ENABLED=false \
--restart=always \
onlyoffice/documentserver:latest
参数说明:
-p 9999:80:将容器 80 端口映射到主机 9999 端口(访问用)-v:目录挂载(数据/日志/插件/字体持久化)JWT_ENABLED=false:关键!关闭 Token 验证(忽略 JWT)--restart=always:Docker 重启后自动启动容器--name onlyoffice:给容器命名,方便管理
docker logs onlyoffice 查看报错(常见问题:端口被占用,换 -p 9998:80 重试)。http://localhost:9999/healthcheck,返回 {"status":"true"} 即服务正常。浏览器访问验证:打开 http://localhost:9999,若看到 OnlyOffice 欢迎页(显示 Document Server is running),则部署成功!
查看容器状态:执行 docker ps,若 onlyoffice 状态为 Up 则运行中;
OnlyOffice 启动需要下载依赖,Win11 可能出现启动超时:
docker exec -it onlyoffice bash 进入容器;退出容器:exit
手动执行初始化脚本:
supervisorctl restart all
Java 后端提供「文档预览/编辑」接口,返回 OnlyOffice 所需的配置参数(文档地址、回调地址等),前端通过 OnlyOffice SDK 加载编辑器。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>onlyoffice-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- Spring Web 核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 跨域支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- JSON 工具 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
server:
port: 48083 # Java 服务端口
# OnlyOffice 配置
onlyOffice:
server:
url: http://localhost:9999 # 本地 Docker 部署的 OnlyOffice 地址
document:
storage: D:/onlyoffice/documents/ # 本地文档存储目录(需提前创建)
创建 DocumentUtils.java,处理文档路径、URL 生成:
package com.example.onlyofficedemo.utils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.File;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@Component
public class OnlyOfficeUtils {
@Value("${onlyoffice.document.storage}")
private String documentStoragePath;
@Value("${onlyoffice.server.url}")
private String onlyOfficeServerUrl;
@Value("${server.port}")
private String serverPort;
// 初始化文档存储目录
public void initStorageDir() {
File dir = new File(documentStoragePath);
if (!dir.exists()) {
dir.mkdirs();
}
}
// 获取文档访问 URL(供 OnlyOffice 访问)
public String getDocumentUrl(String fileName) {
// 本地 Java 服务的 IP + 端口(Win11 需替换为实际 IP,不能用 localhost,否则容器访问不到)
String host = "http://" + getLocalIp() + ":" + serverPort;
return host + "/document/get?fileName=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8);
}
// 获取回调 URL(OnlyOffice 保存文档时回调)
public String getCallbackUrl(String fileName) {
String host = "http://" + getLocalIp() + ":" + serverPort;
return host + "/document/callback?fileName=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8);
}
// 获取 OnlyOffice 编辑器配置 URL
public String getEditorUrl() {
return onlyOfficeServerUrl + "/web-apps/apps/api/documents/api.js";
}
// 获取本地 IP(关键:容器需访问主机 IP,不能用 localhost)
private String getLocalIp() {
try {
java.net.InetAddress addr = java.net.InetAddress.getLocalHost();
return addr.getHostAddress();
} catch (Exception e) {
return "127.0.0.1";
}
}
// 获取文档本地路径
public String getDocumentLocalPath(String fileName) {
return documentStoragePath + File.separator + fileName;
}
}
创建 OnlyOfficeOffController.java,提供「文档获取、编辑配置、回调保存」接口:
package com.vintechhk.module.document.controller.app.document;
import com.alibaba.fastjson.JSONObject;
import com.vintechhk.module.document.util.OnlyOfficeUtils;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.UUID;
/**
* 用户端 - 协作文档 Controller
*/
@Tag(name = "用户端 - 在线文档")
@RestController
@RequestMapping("/doc/only-office")
@Validated
@Slf4j
@CrossOrigin(origins = "*") // 允许跨域(测试用,生产需限制)
public class OnlyOfficeOffController {
@Autowired
private OnlyOfficeUtils onlyOfficeUtils;
// 初始化:创建测试文档
@GetMapping("/init")
public String initTestDocument() {
log.info("-----------------------初始化---init-------1-----------");
onlyOfficeUtils.initStorageDir();
// 创建一个测试 Word 文档(若没有则新建)
String testFileName = "test-" + UUID.randomUUID() + ".docx";
String localPath = onlyOfficeUtils.getDocumentLocalPath(testFileName);
File testFile = new File(localPath);
if (!testFile.exists()) {
try {
testFile.createNewFile();
// 写入测试内容
FileOutputStream fos = new FileOutputStream(testFile);
fos.write("OnlyOffice Java 对接测试文档".getBytes());
fos.close();
} catch (Exception e) {
return "初始化失败:" + e.getMessage();
}
}
log.info("-----------------------初始化---init------2------------");
return "测试文档创建成功!文件名:" + testFileName + ",访问编辑页:http://localhost:48083/document/edit?fileName=" + testFileName;
}
// 获取文档(供 OnlyOffice 访问)
@GetMapping("/get")
public ResponseEntity<FileSystemResource> getDocument(@RequestParam String fileName) {
String localPath = onlyOfficeUtils.getDocumentLocalPath(fileName);
File file = new File(localPath);
if (!file.exists()) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
HttpHeaders headers = new HttpHeaders();
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Content-Disposition", "attachment; filename=" + fileName);
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
return ResponseEntity.ok().headers(headers).contentLength(file.length()).contentType(MediaType.APPLICATION_OCTET_STREAM).body(new FileSystemResource(file));
}
// 获取 OnlyOffice 编辑配置
@GetMapping("/edit")
public JSONObject getEditConfig(@RequestParam String fileName) {
JSONObject config = new JSONObject();
// 基础配置
config.put("documentType", "text"); // 文档类型:text(Word)/spreadsheet(Excel)/presentation(PowerPoint)
config.put("editorUrl", onlyOfficeUtils.getEditorUrl());
// 文档配置
JSONObject document = new JSONObject();
document.put("title", fileName);
document.put("url", onlyOfficeUtils.getDocumentUrl(fileName)); // 文档访问 URL
document.put("fileType", fileName.substring(fileName.lastIndexOf(".") + 1)); // 文件后缀
document.put("key", UUID.randomUUID().toString()); // 唯一标识(避免缓存)
config.put("document", document);
// 编辑器配置
JSONObject editorConfig = new JSONObject();
editorConfig.put("callbackUrl", onlyOfficeUtils.getCallbackUrl(fileName)); // 回调地址
editorConfig.put("lang", "zh-CN"); // 中文
config.put("editorConfig", editorConfig);
return config;
}
// OnlyOffice 回调保存文档
@PostMapping("/callback")
public String callback(HttpServletRequest request, @RequestParam String fileName) {
try {
// 解析回调参数
JSONObject json = JSONObject.parseObject(request.getInputStream(), "UTF-8".getClass());
String status = json.getString("status"); // status=2 表示文档编辑完成,需要保存
if ("2".equals(status)) {
String downloadUrl = json.getJSONObject("url").getString("url");
// 下载 OnlyOffice 编辑后的文档
URL url = new URL(downloadUrl);
URLConnection conn = url.openConnection();
InputStream is = conn.getInputStream();
// 覆盖本地文档
String localPath = onlyOfficeUtils.getDocumentLocalPath(fileName);
FileOutputStream fos = new FileOutputStream(localPath);
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
fos.close();
is.close();
}
// 返回 OnlyOffice 要求的格式(必须返回 {"error":0})
return "{\"error\":0}";
} catch (Exception e) {
return "{\"error\":1,\"message\":\"" + e.getMessage() + "\"}";
}
}
}
创建 OnlyOfficeDemoApplication.java:
package com.example.onlyofficedemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class OnlyOfficeDemoApplication {
public static void main(String[] args) {
SpringApplication.run(OnlyOfficeDemoApplication.class, args);
}
}
在 resources/static 目录下创建 edit.html(Spring Boot 静态资源目录):
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>OnlyOffice 编辑测试</title>
<style>
#editor { width: 100%; height: 800px; }
</style>
</head>
<body>
<div id="editor"></div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
// 获取 URL 参数
function getUrlParam(name) {
let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
let r = window.location.search.substr(1).match(reg);
if (r != null) return unescape(r[2]);
return null;
}
let fileName = getUrlParam("fileName");
// 请求 Java 后端获取编辑配置
$.get("/document/edit?fileName=" + fileName, function(data) {
// 加载 OnlyOffice 编辑器
let script = document.createElement('script');
script.src = data.editorUrl;
script.onload = function() {
new DocsAPI.DocEditor("editor", {
document: data.document,
documentType: data.documentType,
editorConfig: data.editorConfig,
width: "100%",
height: "100%"
});
};
document.body.appendChild(script);
});
</script>
</body>
</html>
docker start onlyoffice);OnlyOfficeDemoApplication.java);D:/onlyoffice/documents/ 目录(文档存储用)。浏览器访问:http://localhost:48083/document/init
返回类似:测试文档创建成功!文件名:test-xxx.docx,访问编辑页:http://localhost:48083/document/edit?fileName=test-xxx.docx
复制返回的编辑页 URL 到浏览器打开,即可看到 OnlyOffice 编辑器加载完成,显示测试文档:
http://localhost:48083/document/callback 接口,将编辑后的内容保存到本地文档。打开 D:/onlyoffice/documents/ 下的测试文档,查看内容是否已更新,确认保存成功。
localhost,容器内无法访问主机的 localhost;DocumentUtils.java 中 getLocalIp() 需返回 Win11 实际 IP(如 192.168.1.100),而非 127.0.0.1。docker exec -it onlyoffice bash 进入容器,运行 apt update && apt install -y wget,再重启容器。<meta charset="UTF-8">。JWT_ENABLED=false),映射端口和目录;至此,从 OnlyOffice 私有化部署到 Java 对接测试的全流程已完成,可基于此扩展更多功能(如文档权限控制、多格式支持、历史版本等)。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online