Repository navigation
fix: 修复水印配置缺失时 xlsx 预览白页 - #789
Conversation
未提供外部 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
left a comment
There was a problem hiding this comment.
已核对当前提交 c4e1929 的模板、配置加载和请求属性链路。水印配置缺失导致 FreeMarker 渲染中断的根因成立,采用模板缺省值作为局部修复也合理。不过还有一个已复现的 locale 问题需要在合并前修复,详见行内评论。
验证情况:
- 本地运行
OfficeWebWatermarkDefaultsTests,2 条测试通过;以-DargLine="-Duser.language=de -Duser.country=DE"再运行也通过,说明现有断言未覆盖默认透明度的错误输出。 - 使用项目实际依赖中的 Spring
FreeMarkerView、模拟请求/响应及AcceptHeaderLocaleResolver渲染当前模板,并对生成的内联脚本运行node --check:缺少全部水印属性时,zh-CN通过,de-DE、fr-FR、ar-EG失败;提供完整水印配置时均通过。 - 在临时模板中将六个数值缺省值改为字符串后,上述四种语言的缺失配置和已有配置场景均通过 JavaScript 语法检查,已有配置值保持原样。
这次复测验证的是模板渲染和生成脚本的语法,不依赖 LibreOffice 或完整服务启动。
上一版给六个数值水印变量用的是数值缺省值(${watermarkAlpha!0.2} 等)。
FreeMarker 按请求的 locale 格式化数值:Accept-Language 为 de-DE / fr-FR 时
0.2 输出成 0,2,ar-EG 时数字变成阿拉伯-印度数字,内联脚本解析失败,
initWaterMark 与 isLoading 都没有定义,xlsx 预览照样起不来——而此时响应是 200、
页面也有 </html>,只看页面完整性看不出来。
改为字符串缺省值,与 WatermarkConfigConstants.DEFAULT_* 的类型一致,原样输出。
已配置的值本来就是字符串,不受影响。
测试改为按 zh-CN / en-US / de-DE / fr-FR / ar-EG 逐个渲染,并逐个断言全部数值
输出;已有配置那条也按 zh-CN / de-DE / ar-EG 跑。改模板前先跑:de-DE、fr-FR
红在 watermark_alpha: 0,2,ar-EG 红在 watermark_x_space: ١٠,其余五条通过;
改后 8 条全过。
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Re-reviewed head 45d7e34. The six numeric defaults are now string literals, so FreeMarker emits valid JavaScript numbers independently of the request locale. The empty watermark text keeps watermark rendering disabled when no text is configured, and supplied settings retain their original values.
The earlier locale blocker is fixed and the review thread is resolved. Validation against current master:
- All 24 related regression tests pass, including the 8 new watermark cases.
- Actual Spring FreeMarkerView rendering with AcceptHeaderLocaleResolver passes for missing and configured attributes across zh-CN, en-US, de-DE, fr-FR, and ar-EG: 10 complete responses and 20 inline scripts passing node --check.
- Linux, Windows, and macOS packaging and PR E2E are green on this head. The Actions checkout log confirms those runs used historical merge snapshot 9ec677f based on cd127fd, rather than current master. The local integration checks above use current master 5e3c013; I am synchronizing master into the PR and running fresh CI before merging.
No remaining blocking findings.
klboke
left a comment
There was a problem hiding this comment.
Reviewed the latest head 567c8b9, including synchronization with current master. The locale-sensitive defaults reported in the earlier review are fixed: all six numeric fallbacks are strings, missing watermark text disables watermark initialization, and configured settings are preserved. The original review thread is resolved.
Validation:
- All 24 related regression tests pass on the exact same file tree as this head, including the 8 watermark default/configuration cases.
- Actual Spring FreeMarkerView rendering with AcceptHeaderLocaleResolver passes for missing/configured attributes across zh-CN, en-US, de-DE, fr-FR, and ar-EG: 10 complete responses and 20 inline scripts passing node --check.
- Fresh Linux, Windows, and macOS packaging and PR E2E are all green for this head, which contains master 5e3c013 and the recent preview fixes.
No remaining blocking findings.
问题
在未提供外部
config/application.properties的部署上,预览任何 xlsx 都失败(5.0.2 与当前 master 都有)。页面从
<html>开始正常输出、断在半截 JavaScript 里、永远没有</html>,浏览器上表现为白页或一直转圈。HTTP 状态码是 200,服务端日志里只有一句「响应已提交、错误页渲染不了」——它长得像转换超时,不像模板错,
所以很容易一路去查 LibreOffice 和转换链路。
真正的报错是:
原因
两处叠在一起,缺一个都不会出事:
1. 十个
watermark*请求属性可能整族不存在。WatermarkConfigConstants没有任何类级注解(对比ConfigConstants上的@Component),所以它不是 Spring bean,那十个
@Value("${watermark.xxx:默认值}")的 setter 从来不会被调用。唯一会填充那十个静态字段的是
ConfigRefreshComponent#loadConfig(),而它在配置文件不存在时直接return:此时十个 getter 全部返回
null,而AttributeSetFilter的request.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(),不会给所有预览凭空加上水印。其余九个只在水印真的开着时才用得上,但那个
if是 JavaScript 的、不是模板的,FreeMarker 两条分支都会渲染,所以十个都要给(只补
watermarkTxt的话,页面会改为断在下一个变量
watermarkXSpace上,症状一模一样)。六个数值缺省值写成字符串(
${watermarkAlpha!'0.2'},不是${watermarkAlpha!0.2}),与 Java 侧DEFAULT_*的类型一致。数值缺省值会走 FreeMarker 按 locale 的数字格式:请求带Accept-Language: de-DE或
fr-FR时0.2输出成0,2,ar-EG时数字变成阿拉伯-印度数字,内联脚本解析失败,预览照样起不来——而此时响应是 200、页面也有
</html>(感谢 review 指出并复现)。已配置的值本来就是字符串,不受影响。验证
新增
server/src/test/java/cn/keking/web/OfficeWebWatermarkDefaultsTests.java,直接用 FreeMarker渲染这个模板——不需要把服务跑起来,也不需要 LibreOffice。模板按请求的 locale 取用,与 Spring
FreeMarkerView一致:shouldRenderXlsxPreviewPageWhenWatermarkAttributesAreMissing:数据模型里一个watermark*都不给(那正是缺陷发生时的情形),按 zh-CN / en-US / de-DE / fr-FR / ar-EG 各渲染一次,断言渲染得出
</html>、watermarkTxt落到空串,并逐个断言其余九个值的输出文本。⚠ 只查
</html>或 HTTP 200 都不够:模板报错时响应头早已发出,也是 200;数字被本地化时页面完整、但脚本解析不了。所以判据落在每个值实际输出的那一行。
shouldKeepConfiguredWatermarkSettingsWhenAttributesArePresent:十个值全给上,按 zh-CN / de-DE / ar-EG各跑一次,断言逐个原样渲染,保证缺省值不会盖掉真实配置。
改模板之前先跑过:没有缺省值时,缺属性那条报
InvalidReferenceException(第 25 行watermarkTxt);数值缺省值时,de-DE、fr-FR 红在
watermark_alpha: 0,2,ar-EG 红在watermark_x_space: ١٠,其余通过——说明红的确实是被测的那个条件。改完之后:
没有一并改的
commonHeader.ftl与pdf.ftl里也是裸${watermark*},今天靠classic_compatible=true兜着,没跟着改——改动越小越好审。
null:给WatermarkConfigConstants加@Component(让它的
@Value缺省值真正生效),或让 getter 回落到DEFAULT_*。那会动到配置装载的语义,超出这个 PR 的范围;如果维护者更倾向那条路,我可以照着改成那一版。
server/src/main/config/application.properties里watermark.width默认是 180,而WatermarkConfigConstants.DEFAULT_WATERMARK_WIDTH是 240。本 PR 取的是后者(Java 侧声明的默认值)。