From 41bc55bc6ab5e70a12736ae174a248b5d37ec5bd Mon Sep 17 00:00:00 2001
From: linwei <872216696@qq.com>
Date: Fri, 10 Jul 2026 15:14:33 +0800
Subject: [PATCH] feat(resource): 集成OnlyOffice文档编辑功能
---
drone-ops/drone-resource/src/main/java/org/sxkj/resource/controller/AttachController.java | 165 +++++++++++++++++++++++++++++++++++++++++++-----------
1 files changed, 131 insertions(+), 34 deletions(-)
diff --git a/drone-ops/drone-resource/src/main/java/org/sxkj/resource/controller/AttachController.java b/drone-ops/drone-resource/src/main/java/org/sxkj/resource/controller/AttachController.java
index 4854705..7eefe34 100644
--- a/drone-ops/drone-resource/src/main/java/org/sxkj/resource/controller/AttachController.java
+++ b/drone-ops/drone-resource/src/main/java/org/sxkj/resource/controller/AttachController.java
@@ -547,40 +547,63 @@
return baseUrl.toString() + "/attach/onlyoffice-download?attachId=" + attachId;
}
- /**
- * OnlyOffice代理下载接口,用于代理下载远程文件
- * <p>
- * 当OnlyOffice Document Server无法直接访问远程文件URL时,
- * 通过本接口代理下载文件并流式返回给Document Server。
- * </p>
- *
- * @param request HTTP请求对象
- * @param response HTTP响应对象
- * @param attachId 附件ID,用于从缓存中查找对应的远程文件URL
- * @throws IOException 当下载或写入文件流时发生I/O异常
- */
@GetMapping("/onlyoffice-download")
@ApiOperation(value = "OnlyOffice代理下载", notes = "代理下载远程文件供OnlyOffice Document Server访问")
public void onlyofficeDownload(
- HttpServletRequest request,
- HttpServletResponse response,
- @ApiParam(value = "附件ID", required = true) @RequestParam Long attachId) throws IOException {
+ HttpServletRequest request,
+ HttpServletResponse response,
+ @ApiParam(value = "附件ID", required = true) @RequestParam Long attachId) throws IOException {
- // 1. 从缓存中获取远程文件URL
- String remoteUrl = REMOTE_URL_CACHE.get(attachId);
- if (remoteUrl == null || remoteUrl.isEmpty()) {
- response.sendError(HttpServletResponse.SC_NOT_FOUND, "未找到对应的远程文件URL");
+ // 处理 OPTIONS 预检请求
+ if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
+ response.setHeader("Access-Control-Allow-Origin", "*");
+ response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
+ response.setHeader("Access-Control-Allow-Headers", "*");
+ response.setStatus(HttpServletResponse.SC_OK);
return;
}
- // 2. 根据附件ID查询附件信息,获取文件名
- Attach attach = attachService.getOne(Wrappers.lambdaQuery(Attach.class).eq(Attach::getId, attachId));
- String fileName = (attach != null && attach.getName() != null) ? attach.getName() : "document.docx";
+ log.info("ONLYOFFICE 下载请求,attachId: {}", attachId);
- // 3. 从远程URL下载文件
+ // 1. 根据附件ID查询附件信息
+ Attach attach = attachService.getOne(Wrappers.lambdaQuery(Attach.class).eq(Attach::getId, attachId));
+ if (attach == null) {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND, "附件不存在,attachId: " + attachId);
+ return;
+ }
+
+ // 2. 优先从缓存中获取远程文件URL
+ String remoteUrl = REMOTE_URL_CACHE.get(attachId);
+ if (remoteUrl == null || remoteUrl.isEmpty()) {
+ remoteUrl = attach.getLink();
+ log.info("从数据库获取远程URL,attachId: {}, link: {}", attachId, remoteUrl);
+ }
+
+ // 3. 验证远程URL
+ if (remoteUrl == null || remoteUrl.isEmpty()) {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND, "附件缺少远程文件URL,attachId: " + attachId);
+ return;
+ }
+
+ // 4. 获取纯文件名(不含路径)
+ String fileName = attach.getName();
+ if (fileName == null || fileName.isEmpty()) {
+ fileName = "document.docx";
+ }
+ // 移除路径部分
+ if (fileName.contains("/")) {
+ fileName = fileName.substring(fileName.lastIndexOf("/") + 1);
+ }
+ if (fileName.contains("\\")) {
+ fileName = fileName.substring(fileName.lastIndexOf("\\") + 1);
+ }
+ log.info("下载文件名: {}", fileName);
+
+ // 5. 下载远程文件
HttpURLConnection connection = null;
InputStream inputStream = null;
OutputStream outputStream = null;
+
try {
URL url = new URL(remoteUrl);
connection = (HttpURLConnection) url.openConnection();
@@ -588,42 +611,78 @@
connection.setConnectTimeout(CONNECT_TIMEOUT);
connection.setReadTimeout(READ_TIMEOUT);
- // 4. 检查响应状态码
+ // 复制请求头(如果需要)
+ String userAgent = request.getHeader("User-Agent");
+ if (userAgent != null) {
+ connection.setRequestProperty("User-Agent", userAgent);
+ }
+
int responseCode = connection.getResponseCode();
if (responseCode != HttpURLConnection.HTTP_OK) {
+ log.error("远程文件下载失败,状态码: {}, url: {}", responseCode, remoteUrl);
response.sendError(HttpServletResponse.SC_BAD_GATEWAY, "远程文件下载失败,状态码: " + responseCode);
return;
}
- // 5. 获取远程文件的Content-Type
+ // 6. 设置响应头
String contentType = connection.getContentType();
if (contentType == null || contentType.isEmpty()) {
- contentType = "application/octet-stream";
+ contentType = getContentTypeByFileName(fileName);
+ }
+ response.setContentType(contentType);
+
+ // 设置 Content-Length
+ int contentLength = connection.getContentLength();
+ if (contentLength > 0) {
+ response.setContentLength(contentLength);
}
- // 6. 设置响应头
- response.setContentType(contentType);
- response.setHeader("Content-Disposition", "attachment; filename=\""
- + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()) + "\"");
+ // 7. 正确设置 Content-Disposition
+ String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name())
+ .replaceAll("\\+", "%20");
- // 7. 流式传输文件内容
+ // ONLYOFFICE 需要 inline 方式访问
+ response.setHeader("Content-Disposition",
+ "inline; filename=\"" + encodedFileName + "\"; " +
+ "filename*=UTF-8''" + encodedFileName);
+
+ // CORS 头
+ response.setHeader("Access-Control-Allow-Origin", "*");
+ response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
+ response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
+
+ // 缓存控制
+ response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+ response.setHeader("Pragma", "no-cache");
+ response.setHeader("Expires", "0");
+
+ // 8. 流式传输
inputStream = connection.getInputStream();
outputStream = response.getOutputStream();
byte[] buffer = new byte[8192];
int bytesRead;
+ long totalBytes = 0;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
+ totalBytes += bytesRead;
}
outputStream.flush();
+
+ log.info("下载完成,文件大小: {} bytes", totalBytes);
+
} catch (Exception e) {
- log.error("代理下载远程文件失败,attachId: {}, remoteUrl: {}, 错误: {}", attachId, remoteUrl, e.getMessage(), e);
+ log.error("代理下载远程文件失败,attachId: {}, remoteUrl: {}, 错误: {}",
+ attachId, remoteUrl, e.getMessage(), e);
if (!response.isCommitted()) {
- response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "代理下载远程文件失败: " + e.getMessage());
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ "代理下载远程文件失败: " + e.getMessage());
}
} finally {
- // 8. 关闭资源
if (inputStream != null) {
try { inputStream.close(); } catch (IOException ignored) {}
+ }
+ if (outputStream != null) {
+ try { outputStream.close(); } catch (IOException ignored) {}
}
if (connection != null) {
connection.disconnect();
@@ -631,4 +690,42 @@
}
}
+ /**
+ * 根据文件名获取 Content-Type
+ */
+ private String getContentTypeByFileName(String fileName) {
+ if (fileName == null) return "application/octet-stream";
+
+ String extension = "";
+ int lastDot = fileName.lastIndexOf(".");
+ if (lastDot > 0) {
+ extension = fileName.substring(lastDot + 1).toLowerCase();
+ }
+
+ switch (extension) {
+ case "docx":
+ return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
+ case "doc":
+ return "application/msword";
+ case "xlsx":
+ return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
+ case "xls":
+ return "application/vnd.ms-excel";
+ case "pptx":
+ return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
+ case "ppt":
+ return "application/vnd.ms-powerpoint";
+ case "pdf":
+ return "application/pdf";
+ case "txt":
+ return "text/plain";
+ case "csv":
+ return "text/csv";
+ case "rtf":
+ return "application/rtf";
+ default:
+ return "application/octet-stream";
+ }
+ }
+
}
--
Gitblit v1.9.3