From c4e1929ecf948c538bc06288f458903b9108839e Mon Sep 17 00:00:00 2001 From: yuhaibo Date: Tue, 8 Sep 2026 00:54:15 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=B0=B4=E5=8D=B0=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E7=BC=BA=E5=A4=B1=E6=97=B6=20xlsx=20=E9=A2=84=E8=A7=88?= =?UTF-8?q?=E7=99=BD=E9=A1=B5=20=E2=80=94=E2=80=94=20officeweb.ftl=20?= =?UTF-8?q?=E8=A1=A5=E4=B8=8A=E7=BC=BA=E7=9C=81=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 未提供外部 config/application.properties 的部署上,预览任何 xlsx 都失败: FreeMarker 在 officeweb.ftl 第 25 行抛 InvalidReferenceException,点名 watermarkTxt。DEBUG 模式把错误内联进 HTML,页面断在半截 JavaScript 里、 永远没有 ,浏览器上是白页或一直转圈,而 HTTP 状态码是 200 —— 看起来像转换超时,不像模板错。 两处叠在一起: 1. 十个 watermark* 请求属性可能整族不存在。WatermarkConfigConstants 没有 类级注解(对比 ConfigConstants 上的 @Component),不是 Spring bean, 那十个 @Value 的 setter 从来不会被调用;唯一填充它们的 ConfigRefreshComponent#loadConfig() 在配置文件不存在时直接 return。 于是十个 getter 全返回 null,AttributeSetFilter 的 setAttribute(name, null) 按 Servlet 规范等同于删除属性。 2. officeweb.ftl 是唯一没有 classic_compatible 兜底的预览模板。 commonHeader.ftl 第 1 行是 <#setting classic_compatible=true>, 所有 include 它的模板在属性缺失时把变量渲染成空串、照常出页面; officeweb.ftl 既不 include 它、自己也没有这个设置。 缺省值取 WatermarkConfigConstants.DEFAULT_*,watermarkTxt 取空串—— 空串会让下一行的 if (watermarkTxt !== '') 跳过 watermark.init(),不会给 所有预览凭空加上水印。其余九个只在水印开着时才用到,但那个 if 是 JavaScript 的、不是模板的,FreeMarker 两条分支都渲染,所以十个都要给。 新增的测试直接用 FreeMarker 渲染这个模板,不需要把服务跑起来: 数据模型里一个 watermark* 都不给(那正是缺陷发生时的情形),断言渲染得出 ;另一条把十个值全给上,断言逐个原样渲染,保证缺省值不会盖掉真实配置。 改模板之前先跑过一遍,第一条报 InvalidReferenceException、第二条通过。 Co-Authored-By: Claude Opus 5 (1M context) --- server/src/main/resources/web/officeweb.ftl | 22 ++--- .../web/OfficeWebWatermarkDefaultsTests.java | 92 +++++++++++++++++++ 2 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 server/src/test/java/cn/keking/web/OfficeWebWatermarkDefaultsTests.java diff --git a/server/src/main/resources/web/officeweb.ftl b/server/src/main/resources/web/officeweb.ftl index b1ed2227..8f6c2b77 100644 --- a/server/src/main/resources/web/officeweb.ftl +++ b/server/src/main/resources/web/officeweb.ftl @@ -22,23 +22,23 @@ * 初始化水印 */ function initWaterMark() { - let watermarkTxt = '${watermarkTxt}'; + let watermarkTxt = '${watermarkTxt!''}'; if (watermarkTxt !== '') { watermark.init({ - watermark_txt: '${watermarkTxt}', + watermark_txt: '${watermarkTxt!''}', watermark_x: 0, watermark_y: 0, watermark_rows: 0, watermark_cols: 0, - watermark_x_space: ${watermarkXSpace}, - watermark_y_space: ${watermarkYSpace}, - watermark_font: '${watermarkFont}', - watermark_fontsize: '${watermarkFontsize}', - watermark_color: '${watermarkColor}', - watermark_alpha: ${watermarkAlpha}, - watermark_width: ${watermarkWidth}, - watermark_height: ${watermarkHeight}, - watermark_angle: ${watermarkAngle}, + watermark_x_space: ${watermarkXSpace!10}, + watermark_y_space: ${watermarkYSpace!10}, + watermark_font: '${watermarkFont!'微软雅黑'}', + watermark_fontsize: '${watermarkFontsize!'18px'}', + watermark_color: '${watermarkColor!'black'}', + watermark_alpha: ${watermarkAlpha!0.2}, + watermark_width: ${watermarkWidth!240}, + watermark_height: ${watermarkHeight!80}, + watermark_angle: ${watermarkAngle!10}, }); } } diff --git a/server/src/test/java/cn/keking/web/OfficeWebWatermarkDefaultsTests.java b/server/src/test/java/cn/keking/web/OfficeWebWatermarkDefaultsTests.java new file mode 100644 index 00000000..b71b571b --- /dev/null +++ b/server/src/test/java/cn/keking/web/OfficeWebWatermarkDefaultsTests.java @@ -0,0 +1,92 @@ +package cn.keking.web; + +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateExceptionHandler; +import org.junit.jupiter.api.Test; + +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * officeweb.ftl is the only preview template that neither includes commonHeader.ftl nor + * turns on classic_compatible, so it is the only one that fails outright when the + * watermark request attributes are absent. + * + * They are absent whenever WatermarkConfigConstants has not been populated: it is not a + * Spring bean, so its @Value defaults never run, and ConfigRefreshComponent#loadConfig + * returns early when the external config/application.properties is missing. Every getter + * then returns null, and AttributeSetFilter's setAttribute(name, null) removes the + * attribute per the Servlet spec. + * + * The first test therefore renders with a model that carries no watermark* entry at all. + * Do not add them "to make it realistic" - that is the condition under test. + */ +class OfficeWebWatermarkDefaultsTests { + + @Test + void shouldRenderXlsxPreviewPageWhenWatermarkAttributesAreMissing() throws Exception { + String html = render(baseModel()); + + assertTrue(html.contains(""), "page was truncated before "); + assertTrue(html.contains("let watermarkTxt = '';"), "watermarkTxt did not fall back to an empty string"); + assertTrue(html.contains("watermark_width: 240,"), "watermarkWidth did not fall back to its declared default"); + assertTrue(html.contains("watermark_font: '\u5fae\u8f6f\u96c5\u9ed1',"), "watermarkFont did not fall back to its declared default"); + } + + @Test + void shouldKeepConfiguredWatermarkSettingsWhenAttributesArePresent() throws Exception { + Map model = baseModel(); + model.put("watermarkTxt", "kkFileView"); + model.put("watermarkXSpace", "20"); + model.put("watermarkYSpace", "30"); + model.put("watermarkFont", "Arial"); + model.put("watermarkFontsize", "24px"); + model.put("watermarkColor", "red"); + model.put("watermarkAlpha", "0.5"); + model.put("watermarkWidth", "300"); + model.put("watermarkHeight", "120"); + model.put("watermarkAngle", "45"); + + String html = render(model); + + assertTrue(html.contains("let watermarkTxt = 'kkFileView';")); + assertTrue(html.contains("watermark_x_space: 20,")); + assertTrue(html.contains("watermark_y_space: 30,")); + assertTrue(html.contains("watermark_font: 'Arial',")); + assertTrue(html.contains("watermark_fontsize: '24px',")); + assertTrue(html.contains("watermark_color: 'red',")); + assertTrue(html.contains("watermark_alpha: 0.5,")); + assertTrue(html.contains("watermark_width: 300,")); + assertTrue(html.contains("watermark_height: 120,")); + assertTrue(html.contains("watermark_angle: 45,")); + } + + /** Everything officeweb.ftl needs except the watermark attributes. */ + private Map baseModel() { + Map model = new HashMap<>(); + model.put("file", Map.of("name", "demo.xlsx")); + model.put("pdfUrl", "demo.xlsx"); + model.put("baseUrl", "http://127.0.0.1:8012/"); + model.put("kkagent", "false"); + model.put("kkkey", ""); + model.put("xlsxshowtoolbar", Boolean.TRUE); + model.put("xlsxallowEdit", Boolean.FALSE); + return model; + } + + private String render(Map model) throws Exception { + Configuration configuration = new Configuration(Configuration.VERSION_2_3_32); + configuration.setClassLoaderForTemplateLoading(getClass().getClassLoader(), "web"); + configuration.setDefaultEncoding("UTF-8"); + configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + + Template template = configuration.getTemplate("officeweb.ftl"); + StringWriter out = new StringWriter(); + template.process(model, out); + return out.toString(); + } +}