Skip to content

fix: 修复水印配置缺失时 xlsx 预览白页 - #789

Open
yuhaibohotmail wants to merge 1 commit into
kekingcn:masterfrom
yuhaibohotmail:fix/officeweb-watermark-defaults
Open

yuhaibohotmail wants to merge 1 commit into
kekingcn:masterfrom
yuhaibohotmail:fix/officeweb-watermark-defaults

Conversation

@yuhaibohotmail

Copy link
Copy Markdown

问题

未提供外部 config/application.properties 的部署上,预览任何 xlsx 都失败(5.0.2 与当前 master 都有)。

页面从 <html> 开始正常输出、断在半截 JavaScript 里、永远没有 </html>,浏览器上表现为白页或一直转圈。
HTTP 状态码是 200,服务端日志里只有一句「响应已提交、错误页渲染不了」——它长得像转换超时,不像模板错
所以很容易一路去查 LibreOffice 和转换链路。

真正的报错是:

FreeMarker template error:
The following has evaluated to null or missing:
==> watermarkTxt  [in template "officeweb.ftl" at line 25, column 31]

原因

两处叠在一起,缺一个都不会出事:

1. 十个 watermark* 请求属性可能整族不存在。

WatermarkConfigConstants 没有任何类级注解(对比 ConfigConstants 上的 @Component),
所以它不是 Spring bean,那十个 @Value("${watermark.xxx:默认值}") 的 setter 从来不会被调用。
唯一会填充那十个静态字段的是 ConfigRefreshComponent#loadConfig(),而它在配置文件不存在时直接 return

if (!Files.exists(configPath)) {
    LOGGER.warn("配置文件不存在: {}", configFilePath);
    return;
}

此时十个 getter 全部返回 null,而 AttributeSetFilterrequest.setAttribute(name, null)
按 Servlet 规范等同于删除属性——不是「值是 null」,是「这个属性不存在」。

2. officeweb.ftl 是唯一没有 classic_compatible 兜底的预览模板。

commonHeader.ftl 第 1 行是 <#setting classic_compatible=true>,而这个设置作用于整个渲染环境、
覆盖到 include 它的那份模板后续的引用(已单独用 FreeMarker 验过)。于是 csv / markdown / pdf / picture 等
所有 include 它的模板在属性缺失时把变量渲染成空串、照常出页面;
officeweb.ftl 既不 include 它、自己也没有这个设置,所以只有 xlsx 预览会硬失败

修复

officeweb.ftl 里那十个变量补上 FreeMarker 缺省值,取的就是 WatermarkConfigConstants.DEFAULT_*
那一组(10 / 10 / 微软雅黑 / 18px / black / 0.2 / 240 / 80 / 10),不另编一套。

watermarkTxt 取空串——空串会让模板下一行的 if (watermarkTxt !== '') 跳过 watermark.init()
不会给所有预览凭空加上水印。其余九个只在水印真的开着时才用得上,但那个 ifJavaScript 的、
不是模板的,FreeMarker 两条分支都会渲染,所以十个都要给(只补 watermarkTxt 的话,页面会改为断在
下一个变量 watermarkXSpace 上,症状一模一样)。

验证

新增 server/src/test/java/cn/keking/web/OfficeWebWatermarkDefaultsTests.java,直接用 FreeMarker
渲染这个模板——不需要把服务跑起来,也不需要 LibreOffice

  • shouldRenderXlsxPreviewPageWhenWatermarkAttributesAreMissing:数据模型里一个 watermark* 都不给
    (那正是缺陷发生时的情形),断言渲染得出 </html>watermarkTxt 落到空串、其余落到声明的缺省值。
    ⚠ 判据取「有没有 </html>」而不是 HTTP 200——坏的时候响应头早就发出去了,也是 200。
  • shouldKeepConfiguredWatermarkSettingsWhenAttributesArePresent:十个值全给上,断言逐个原样渲染,
    保证缺省值不会盖掉真实配置。

改模板之前先跑过一遍:第一条报 InvalidReferenceException(第 25 行 watermarkTxt),第二条通过
——说明红的确实是被测的那个条件,不是模型缺了别的东西。改完之后:

mvn -B -pl server -Dtest=OfficeWebWatermarkDefaultsTests test
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS

没有一并改的

  • commonHeader.ftlpdf.ftl 里也是裸 ${watermark*},今天靠 classic_compatible=true 兜着,
    没跟着改——改动越小越好审。
  • 更彻底的修法是让那十个值不可能为 null:给 WatermarkConfigConstants@Component
    (让它的 @Value 缺省值真正生效),或让 getter 回落到 DEFAULT_*。那会动到配置装载的语义,
    超出这个 PR 的范围;如果维护者更倾向那条路,我可以照着改成那一版。
  • 顺带一个不影响本 PR 的不一致,供参考:server/src/main/config/application.properties
    watermark.width 默认是 180,而 WatermarkConfigConstants.DEFAULT_WATERMARK_WIDTH240
    本 PR 取的是后者(Java 侧声明的默认值)。

未提供外部 config/application.properties 的部署上,预览任何 xlsx 都失败:
FreeMarker 在 officeweb.ftl 第 25 行抛 InvalidReferenceException,点名
watermarkTxt。DEBUG 模式把错误内联进 HTML,页面断在半截 JavaScript 里、
永远没有 </html>,浏览器上是白页或一直转圈,而 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* 都不给(那正是缺陷发生时的情形),断言渲染得出
</html>;另一条把十个值全给上,断言逐个原样渲染,保证缺省值不会盖掉真实配置。
改模板之前先跑过一遍,第一条报 InvalidReferenceException、第二条通过。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@klboke klboke left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已核对当前提交 c4e1929 的模板、配置加载和请求属性链路。水印配置缺失导致 FreeMarker 渲染中断的根因成立,采用模板缺省值作为局部修复也合理。不过还有一个已复现的 locale 问题需要在合并前修复,详见行内评论。

验证情况:

  • 本地运行 OfficeWebWatermarkDefaultsTests,2 条测试通过;以 -DargLine="-Duser.language=de -Duser.country=DE" 再运行也通过,说明现有断言未覆盖默认透明度的错误输出。
  • 使用项目实际依赖中的 Spring FreeMarkerView、模拟请求/响应及 AcceptHeaderLocaleResolver 渲染当前模板,并对生成的内联脚本运行 node --check:缺少全部水印属性时,zh-CN 通过,de-DEfr-FRar-EG 失败;提供完整水印配置时均通过。
  • 在临时模板中将六个数值缺省值改为字符串后,上述四种语言的缺失配置和已有配置场景均通过 JavaScript 语法检查,已有配置值保持原样。

这次复测验证的是模板渲染和生成脚本的语法,不依赖 LibreOffice 或完整服务启动。

watermark_font: '${watermarkFont!'微软雅黑'}',
watermark_fontsize: '${watermarkFontsize!'18px'}',
watermark_color: '${watermarkColor!'black'}',
watermark_alpha: ${watermarkAlpha!0.2},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 数值缺省值需要避免 locale 格式化,否则仍会导致 xlsx 预览失败

当水印属性缺失、请求携带 Accept-Language: de-DEfr-FR 时,${watermarkAlpha!0.2} 的缺省值是 FreeMarker 数值,会被渲染为:

watermark_alpha: 0,2,

我通过 Spring FreeMarkerView 复现了该输出,node --checkSyntaxError: Unexpected number。此时响应为 200,HTML 也包含 </html>,但第一个内联脚本块无法解析,initWaterMarkisLoading 都没有初始化;后面的 loadTextAsync() 在访问 isLoading 时仍然无法继续加载。因此仅检查页面完整性不能证明这个场景已修复。ar-EG 下其他数值缺省值也会输出本地化数字,造成语法错误。

建议将这六个数值缺省值统一改成字符串,让它们与 Java 侧 DEFAULT_* 的类型一致,并直接输出合法的 JavaScript 数字文本:

watermark_x_space: ${watermarkXSpace!'10'},
watermark_y_space: ${watermarkYSpace!'10'},
watermark_alpha: ${watermarkAlpha!'0.2'},
watermark_width: ${watermarkWidth!'240'},
watermark_height: ${watermarkHeight!'80'},
watermark_angle: ${watermarkAngle!'10'},

这个调整已在临时模板中复测通过,并且不会覆盖已存在的配置。请同时补上 de_DE / ar_EG 下缺失水印属性的回归测试,检查所有数值输出或生成脚本的语法;当前缺省值测试只断言了空文本、宽度和字体,因此会漏掉这个错误。

参考:FreeMarker 数字的人类格式与计算机格式

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants