位置:首页 > Java > SpringBoot集成OnlyOffice实现Word和Excel在线编辑

SpringBoot集成OnlyOffice实现Word和Excel在线编辑

时间:2026-08-21  |  作者:星际追番人  |  阅读:0
大家好,工作中经常遇到多人同时编辑同一份文档的需求,尤其是在企业微信这类协作场景里。但放到项目开发中,如何实现多人同时在线编辑 Office 文件?这件事说起来简单,做起来却有不少坑。今天要聊的 **OnlyOffice**,正是解决这个问题的利器——一套开源的在线办公套件,支持文档、表格、演示文稿的实时协同编辑。下面以最新的 SpringBoot 4.x 为例,演示如何将其集成到项目中。 --- ## 02 简介 OnlyOffice(文档服务器)本质上是一个独立的 Web 服务,主要做三件事: - 渲染 Office 编辑器界面(前端 SDK `api.js`) - 将 Office 文件转换为网页可编辑格式 - 编辑完成后回调保存结果 它支持所有主流办公文件格式:DOCX、ODT、XLSX、ODS、CSV、PPTX、ODP……甚至 PDF 和 PDF 表单也能处理。社区版和企业版都在 GitHub 上提供了 Docker 镜像,下面以社区版为例。 ![OnlyOffice 架构图](http://img.318050.com/uploads/20260803/17857355806a70299c0eef2743342860.webp) **GitHub 地址**:https://github.com/ONLYOFFICE/Docker-DocumentServer **官网地址**:https://www.onlyoffice.com/zh/office-suite --- ## 03 服务搭建 部署最简单的方式就是 Docker。直接拉取镜像运行: ```bash sudo docker run -i -t -d -p 9090:80 -v /app/onlyoffice/DocumentServer/logs:/var/log/onlyoffice -v /app/onlyoffice/DocumentServer/data:/var/www/onlyoffice/Data -v /app/onlyoffice/DocumentServer/lib:/var/lib/onlyoffice onlyoffice/documentserver ``` ![Docker 启动](http://img.318050.com/uploads/20260803/17857355806a70299c580eb205813361.webp) 注意,默认启用了 JWT 认证。启动后可以通过以下命令找到 JWT 密钥: ```bash sudo docker exec <容器ID> /var/www/onlyoffice/documentserver/npm/json -f /etc/onlyoffice/documentserver/local.json 'services.CoAuthoring.secret.session.string' ``` ![JWT 密钥](http://img.318050.com/uploads/20260803/17857355806a70299c982d6238914396.webp) 进入容器首页也能看到很多命令提示,按需使用即可。 ![服务首页](http://img.318050.com/uploads/20260803/17857355806a70299cee79c146838229.webp) --- ## 04 SpringBoot 集成 ### 4.1 Ma ven 依赖 除了 Spring Boot 的 Web 依赖,还需要引入 JWT 库(如果启用认证的话)。官方推荐使用 `nimbus-jose-jwt`: ```xml org.springframework.boot spring-boot-starter-web com.nimbusds nimbus-jose-jwt 10.2 ``` 如果关闭了 JWT 认证,可省略第二个依赖。 ### 4.2 核心常量 配置类存放各种地址和密钥: ```ja va public final class OnlyOfficeProperties { private OnlyOfficeProperties() {} public static final String DOC_SERVICE_URL = "http://10.100.xx.xx:9090"; public static final String SERVER_URL = "http://10.50.xx.xx:8080"; public static final String STORAGE_PATH = "./storage"; public static final String JWT_SECRET = "InYyL*******xTDDugbfyQI"; } ``` ### 4.3 核心代码 **文件上传与列表展示** ```ja va @GetMapping("/files") public ResponseEntity> listFiles() throws IOException { try (Stream stream = Files.list(storageDir)) { return ResponseEntity.ok(stream .filter(Files::isRegularFile) .map(p -> p.getFileName().toString()) .toList()); } } @PostMapping("/files") public ResponseEntity upload(@RequestParam("file") MultipartFile file) throws IOException { Files.copy(file.getInputStream(), storageDir.resolve(file.getOriginalFilename()), StandardCopyOption.REPLACE_EXISTING); return ResponseEntity.ok("uploaded: " + file.getOriginalFilename()); } ``` **OnlyOffice 通过下载令牌加载文档** ```ja va @GetMapping("/files/download/{token}") public ResponseEntity downloadByToken(@PathVariable String token) throws IOException { String name = downloadTokens.getOrDefault(token, token); Path file = storageDir.resolve(name).normalize(); Resource resource = new UrlResource(file.toUri()); if (!resource.exists() || !resource.isReadable()) return ResponseEntity.notFound().build(); return ResponseEntity.ok() .contentType(MediaType.parseMediaType(contentType(name))) .body(resource); } ``` **编辑配置** ```ja va @GetMapping("/settings") public ResponseEntity> settings() { return ResponseEntity.ok(Map.of("docServiceUrl", OnlyOfficeProperties.DOC_SERVICE_URL)); } @GetMapping("/config") public ResponseEntity> config( @RequestParam("name") String name, @RequestParam(value = "user", defaultValue = "user") String user) throws Exception { String token = UUID.randomUUID().toString().replace("-", ""); downloadTokens.put(token, name); Map document = new LinkedHashMap<>(); document.put("fileType", fileType(name)); document.put("key", token); document.put("title", name); document.put("url", OnlyOfficeProperties.SERVER_URL + "/api/files/download/" + token); Map editorConfig = new LinkedHashMap<>(); editorConfig.put("callbackUrl", OnlyOfficeProperties.SERVER_URL + "/api/callback"); editorConfig.put("lang", "zh-CN"); editorConfig.put("mode", "edit"); editorConfig.put("user", Map.of("id", user, "name", user)); Map cfg = new LinkedHashMap<>(); cfg.put("document", document); cfg.put("documentType", docType(name)); cfg.put("editorConfig", editorConfig); cfg.put("height", "100%"); cfg.put("width", "100%"); if (isJwtEnabled()) { cfg.put("token", sign(objectMapper.writeValueAsString(cfg))); } return ResponseEntity.ok(cfg); } ``` **回调处理** ```ja va @PostMapping("/callback") public ResponseEntity> callback(@RequestBody Map body) { try { handleCallback(body); } catch (Exception e) { e.printStackTrace(); } return ResponseEntity.ok(Map.of("error", 0)); } @SuppressWarnings("unchecked") private void handleCallback(Map body) throws IOException, InterruptedException { int status = ((Number) body.get("status")).intValue(); if (status != 2 && status != 6) return; String downloadUrl = (String) body.get("url"); if (downloadUrl == null || downloadUrl.isEmpty()) return; String filename = downloadTokens.getOrDefault( (String) body.get("key"), (String) body.get("key")); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(downloadUrl)).GET().build(); HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofFile(storageDir.resolve(filename + ".tmp"))); if (response.statusCode() == 200) { Files.move(storageDir.resolve(filename + ".tmp"), storageDir.resolve(filename), StandardCopyOption.REPLACE_EXISTING); } } ``` **辅助方法** ```ja va private String contentType(String name) { return switch (ext(name)) { case "docx" -> "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; case "xlsx" -> "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; case "pptx" -> "application/vnd.openxmlformats-officedocument.presentationml.presentation"; case "pdf" -> "application/pdf"; default -> "application/octet-stream"; }; } private String fileType(String name) { return switch (ext(name)) { case "xlsx", "xls" -> "xlsx"; case "pptx", "ppt" -> "pptx"; case "pdf" -> "pdf"; case "txt" -> "txt"; case "csv" -> "csv"; default -> "docx"; }; } private String docType(String name) { return switch (ext(name)) { case "xlsx", "xls", "csv" -> "cell"; case "pptx", "ppt" -> "slide"; default -> "word"; }; } private String ext(String name) { int dot = name.lastIndexOf('.'); return dot > 0 name.substring(dot + 1).toLowerCase() : "docx"; } private boolean isJwtEnabled() { return OnlyOfficeProperties.JWT_SECRET != null && !OnlyOfficeProperties.JWT_SECRET.isBlank(); } private String sign(String configJson) throws Exception { JWTClaimsSet claims = JWTClaimsSet.parse(configJson); SignedJWT signed = new SignedJWT( new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JWT).build(), claims); signed.sign(new MACSigner(OnlyOfficeProperties.JWT_SECRET.getBytes(StandardCharsets.UTF_8))); return signed.serialize(); } ``` ### 4.4 页面 **文件管理页 `index.html`** ```html OnlyOffice 在线编辑

OnlyOffice 在线编辑

``` **编辑页 `editor.html`** ```html 文档编辑 - OnlyOffice
``` --- ## 05 测试 启动项目后,访问首页上传文件。分别上传 Excel 和 Word 文档,文件会立刻出现在列表里。 ![上传页面](http://img.318050.com/uploads/20260803/17857355816a70299d4b20b556574661.webp) 点击“编辑”按钮,会打开一个新页面,加载 OnlyOffice 编辑器。这里展示的是 Word 和 Excel 在线编辑的效果: ![Word 编辑](http://img.318050.com/uploads/20260803/17857355816a70299d84a5a991267353.webp) ![Excel 编辑](http://img.318050.com/uploads/20260803/17857355816a70299dbb9b4813086216.webp) ![编辑完成](http://img.318050.com/uploads/20260803/17857355826a70299e09b0a732200453.webp) 编辑完成后,关闭页面再次打开,之前修改的内容会完整保留——因为回调机制已经把文件保存到存储目录了。 以上就是 SpringBoot 4.x 集成 OnlyOffice 实现文档在线编辑的完整流程。从服务搭建到后端接口,再到前端页面,每一步都可在实际项目中直接复现。如果遇到 JWT 签名问题,记得检查密钥是否一致;如果不需要认证,关掉 JWT 即可。希望这份指南能帮大家快速上手。

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多