From 1759f01d4e37043563f318325e343c9d5bce42bf Mon Sep 17 00:00:00 2001 From: Spencer Chang Date: Wed, 15 Jul 2026 23:26:13 +0800 Subject: [PATCH] fix: respect explicit spreadsheet preview type --- .../java/cn/keking/model/FileAttribute.java | 12 +++++ .../cn/keking/service/FileHandlerService.java | 43 +++++++++++++++--- .../service/impl/OfficeFilePreviewImpl.java | 41 ++++++++++++----- .../service/FileHandlerServiceTests.java | 28 ++++++++++++ .../impl/OfficeFilePreviewImplTests.java | 45 +++++++++++++++++++ 5 files changed, 154 insertions(+), 15 deletions(-) create mode 100644 server/src/test/java/cn/keking/service/FileHandlerServiceTests.java create mode 100644 server/src/test/java/cn/keking/service/impl/OfficeFilePreviewImplTests.java diff --git a/server/src/main/java/cn/keking/model/FileAttribute.java b/server/src/main/java/cn/keking/model/FileAttribute.java index 04ed17492..894ab946b 100644 --- a/server/src/main/java/cn/keking/model/FileAttribute.java +++ b/server/src/main/java/cn/keking/model/FileAttribute.java @@ -17,6 +17,7 @@ public class FileAttribute { private String filePassword; private boolean usePasswordCache; private String officePreviewType = ConfigConstants.getOfficePreviewType(); + private boolean officePreviewTypeSpecified; private String tifPreviewType; private Boolean skipDownLoad = false; private Boolean forceUpdatedCache = false; @@ -49,6 +50,7 @@ public FileAttribute(FileType type, String suffix, String name, String url, Stri this.name = name; this.url = url; this.officePreviewType = officePreviewType; + this.officePreviewTypeSpecified = true; } public boolean isCompressFile() { @@ -81,6 +83,16 @@ public String getOfficePreviewType() { public void setOfficePreviewType(String officePreviewType) { this.officePreviewType = officePreviewType; + this.officePreviewTypeSpecified = true; + } + + /** + * 判断 Office 预览类型是否由请求或调用方显式指定。 + * + * @return {@code true} 表示显式指定,{@code false} 表示使用系统默认配置 + */ + public boolean isOfficePreviewTypeSpecified() { + return officePreviewTypeSpecified; } public FileType getType() { diff --git a/server/src/main/java/cn/keking/service/FileHandlerService.java b/server/src/main/java/cn/keking/service/FileHandlerService.java index 101ae2c7f..d395cb5d0 100644 --- a/server/src/main/java/cn/keking/service/FileHandlerService.java +++ b/server/src/main/java/cn/keking/service/FileHandlerService.java @@ -271,7 +271,12 @@ public FileAttribute getFileAttribute(String url, HttpServletRequest req) { // 处理逻辑:抛出异常、记录日志、返回错误等 throw new IllegalArgumentException("文件名超过系统限制"); } - boolean isHtmlView = suffix.equalsIgnoreCase("xls") || suffix.equalsIgnoreCase("xlsx") || suffix.equalsIgnoreCase("csv") || suffix.equalsIgnoreCase("xlsm") || suffix.equalsIgnoreCase("xlt") || suffix.equalsIgnoreCase("xltm") || suffix.equalsIgnoreCase("et") || suffix.equalsIgnoreCase("ett") || suffix.equalsIgnoreCase("xlam"); + String officePreviewType = req == null ? null : req.getParameter("officePreviewType"); + if (StringUtils.hasText(officePreviewType)) { + attribute.setOfficePreviewType(officePreviewType); + } + boolean isHtmlView = isHtmlPreview(suffix, attribute.getOfficePreviewType(), + attribute.isOfficePreviewTypeSpecified()); String cacheFilePrefixName = null; try { cacheFilePrefixName = originFileName.substring(0, originFileName.lastIndexOf(".")) + suffix + "."; //这里统一文件名处理 下面更具类型 各自添加后缀 @@ -293,12 +298,8 @@ public FileAttribute getFileAttribute(String url, HttpServletRequest req) { attribute.setSuffix(suffix); attribute.setUrl(url); if (req != null) { - String officePreviewType = req.getParameter("officePreviewType"); String forceUpdatedCache = req.getParameter("forceUpdatedCache"); String usePasswordCache = req.getParameter("usePasswordCache"); - if (StringUtils.hasText(officePreviewType)) { - attribute.setOfficePreviewType(officePreviewType); - } if (StringUtils.hasText(compressFileKey)) { attribute.setCompressFile(isCompressFile); attribute.setCompressFileKey(compressFileKey); @@ -327,6 +328,38 @@ public FileAttribute getFileAttribute(String url, HttpServletRequest req) { return attribute; } + /** + * 判断电子表格是否需要生成 HTML 预览缓存。 + * 未显式指定预览类型时保留原有行为;显式指定时仅 {@code html} 模式生成 HTML。 + * + * @param suffix 文件后缀 + * @param officePreviewType Office 预览类型 + * @param previewTypeSpecified 是否由请求或调用方显式指定预览类型 + * @return 是否生成 HTML 预览缓存 + */ + static boolean isHtmlPreview(String suffix, String officePreviewType, boolean previewTypeSpecified) { + return isSpreadsheet(suffix) + && (!previewTypeSpecified || "html".equalsIgnoreCase(officePreviewType)); + } + + /** + * 判断文件后缀是否属于支持 HTML 预览的电子表格类型。 + * + * @param suffix 文件后缀 + * @return 是否为电子表格类型 + */ + private static boolean isSpreadsheet(String suffix) { + return suffix.equalsIgnoreCase("xls") + || suffix.equalsIgnoreCase("xlsx") + || suffix.equalsIgnoreCase("csv") + || suffix.equalsIgnoreCase("xlsm") + || suffix.equalsIgnoreCase("xlt") + || suffix.equalsIgnoreCase("xltm") + || suffix.equalsIgnoreCase("et") + || suffix.equalsIgnoreCase("ett") + || suffix.equalsIgnoreCase("xlam"); + } + /** * 获取缓存的文件名 * diff --git a/server/src/main/java/cn/keking/service/impl/OfficeFilePreviewImpl.java b/server/src/main/java/cn/keking/service/impl/OfficeFilePreviewImpl.java index 00051a444..f68e5cb92 100644 --- a/server/src/main/java/cn/keking/service/impl/OfficeFilePreviewImpl.java +++ b/server/src/main/java/cn/keking/service/impl/OfficeFilePreviewImpl.java @@ -72,16 +72,14 @@ public String filePreviewHandle(String url, Model model, FileAttribute fileAttri return convertStatusResult; } - if (!officePreviewType.equalsIgnoreCase("html")) { - if (ConfigConstants.getOfficeTypeWeb().equalsIgnoreCase("web")) { - if (suffix.equalsIgnoreCase("xlsx")) { - model.addAttribute("pdfUrl", KkFileUtils.htmlEscape(url)); //特殊符号处理 - return XLSX_FILE_PREVIEW_PAGE; - } - if (suffix.equalsIgnoreCase("csv")) { - model.addAttribute("csvUrl", KkFileUtils.htmlEscape(url)); - return CSV_FILE_PREVIEW_PAGE; - } + if (shouldUseWebPreview(fileAttribute)) { + if (suffix.equalsIgnoreCase("xlsx")) { + model.addAttribute("pdfUrl", KkFileUtils.htmlEscape(url)); //特殊符号处理 + return XLSX_FILE_PREVIEW_PAGE; + } + if (suffix.equalsIgnoreCase("csv")) { + model.addAttribute("csvUrl", KkFileUtils.htmlEscape(url)); + return CSV_FILE_PREVIEW_PAGE; } } @@ -136,6 +134,29 @@ public String filePreviewHandle(String url, Model model, FileAttribute fileAttri isHtmlView, userToken, filePassword); } + /** + * 判断 xlsx 或 csv 文件是否应直接使用前端 Web 预览。 + * 未显式指定预览类型时保留系统原有 Web 预览行为;显式指定时仅文件类型同名模式 + * ({@code xlsx} 或 {@code csv})使用 Web 预览,避免 {@code pdf} 请求被错误路由。 + * + * @param fileAttribute 文件属性 + * @return 是否使用 Web 预览 + */ + static boolean shouldUseWebPreview(FileAttribute fileAttribute) { + if (!ConfigConstants.getOfficeTypeWeb().equalsIgnoreCase("web")) { + return false; + } + String suffix = fileAttribute.getSuffix(); + boolean webPreviewFile = suffix.equalsIgnoreCase("xlsx") || suffix.equalsIgnoreCase("csv"); + if (!webPreviewFile || "html".equalsIgnoreCase(fileAttribute.getOfficePreviewType())) { + return false; + } + if (!fileAttribute.isOfficePreviewTypeSpecified()) { + return true; + } + return suffix.equalsIgnoreCase(fileAttribute.getOfficePreviewType()); + } + /** * 启动异步Office转换 */ diff --git a/server/src/test/java/cn/keking/service/FileHandlerServiceTests.java b/server/src/test/java/cn/keking/service/FileHandlerServiceTests.java new file mode 100644 index 000000000..cf49fb4ce --- /dev/null +++ b/server/src/test/java/cn/keking/service/FileHandlerServiceTests.java @@ -0,0 +1,28 @@ +package cn.keking.service; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class FileHandlerServiceTests { + + @Test + void pdfPreviewDoesNotCreateHtmlForXlsx() { + assertThat(FileHandlerService.isHtmlPreview("xlsx", "pdf", true)).isFalse(); + } + + @Test + void htmlPreviewCreatesHtmlForXlsx() { + assertThat(FileHandlerService.isHtmlPreview("xlsx", "html", true)).isTrue(); + } + + @Test + void configuredPreviewTypeKeepsLegacySpreadsheetCacheTarget() { + assertThat(FileHandlerService.isHtmlPreview("xlsx", "pdf", false)).isTrue(); + } + + @Test + void htmlPreviewDoesNotChangeNonSpreadsheetOutput() { + assertThat(FileHandlerService.isHtmlPreview("docx", "html", true)).isFalse(); + } +} diff --git a/server/src/test/java/cn/keking/service/impl/OfficeFilePreviewImplTests.java b/server/src/test/java/cn/keking/service/impl/OfficeFilePreviewImplTests.java new file mode 100644 index 000000000..65c7caa9c --- /dev/null +++ b/server/src/test/java/cn/keking/service/impl/OfficeFilePreviewImplTests.java @@ -0,0 +1,45 @@ +package cn.keking.service.impl; + +import cn.keking.config.ConfigConstants; +import cn.keking.model.FileAttribute; +import cn.keking.model.FileType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class OfficeFilePreviewImplTests { + + @BeforeEach + void setUp() { + ConfigConstants.setOfficeTypeWebValue("web"); + } + + @Test + void usesWebPreviewWhenPreviewTypeWasNotExplicitlySpecified() { + FileAttribute attribute = new FileAttribute(FileType.OFFICE, "xlsx", "report.xlsx", "http://localhost/report.xlsx"); + + assertThat(OfficeFilePreviewImpl.shouldUseWebPreview(attribute)).isTrue(); + } + + @Test + void explicitPdfDoesNotUseWebPreview() { + FileAttribute attribute = new FileAttribute(FileType.OFFICE, "xlsx", "report.xlsx", "http://localhost/report.xlsx", "pdf"); + + assertThat(OfficeFilePreviewImpl.shouldUseWebPreview(attribute)).isFalse(); + } + + @Test + void explicitXlsxUsesWebPreview() { + FileAttribute attribute = new FileAttribute(FileType.OFFICE, "xlsx", "report.xlsx", "http://localhost/report.xlsx", "xlsx"); + + assertThat(OfficeFilePreviewImpl.shouldUseWebPreview(attribute)).isTrue(); + } + + @Test + void explicitHtmlDoesNotUseWebPreview() { + FileAttribute attribute = new FileAttribute(FileType.OFFICE, "xlsx", "report.xlsx", "http://localhost/report.xlsx", "html"); + + assertThat(OfficeFilePreviewImpl.shouldUseWebPreview(attribute)).isFalse(); + } +}