
本文详解如何在 thymeleaf 邮件模板中正确渲染动态 html 内容(如带 `
- ` 的列表),避免 `th:text` 自动转义导致标签显示为纯文本,并推荐使用 `th:each` + 原生 java 列表的结构化方案,兼顾安全性与可维护性。
在使用 Thymeleaf 作为邮件模板引擎时,一个常见误区是:将包含 HTML 标签的字符串(如 "<ul><li>任务A</li><li>任务B</li></ul>")直接通过 th:text 插入模板。由于 th:text 会自动对特殊字符进行 HTML 转义(例如 < →
✅ 正确做法是避免拼接 HTML 字符串,改用 Thymeleaf 原生迭代机制处理结构化数据:
✅ 推荐方案:使用 th:each 渲染列表(安全、清晰、易测试)
将后端传入的 lateSteps 改为 List<String> 类型(而非含 HTML 的字符串),模板中用语义化方式展开:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Template for reminder to team lead</title> </head> <body> <p th:text="'Hello ' + ${firstNameTeamLead} + ' ' + ${lastNameTeamLead} + '!'"/> <p> <span th:text="${member} + ' did not:'"/> <br/> <ul> <li th:each="step : ${lateSteps}" th:text="${step}">示例步骤</li> </ul> </p> </body> </html>对应 Java 代码需同步调整变量名与类型(注意命名一致性):
立即学习“前端免费学习笔记(深入)”;
private Map<String, Object> createNotificationTlVariables(Mission mission, List<String> lateSteps) { Map<String, Object> variables = new HashMap<>(); TeamLead teamLead = mission.getTeamLead(); Member member = mission.getUMember(); variables.put("firstNameTeamLead", teamLead.getFirstName()); // ✅ 匹配模板中的变量名 variables.put("lastNameTeamLead", teamLead.getLastName()); variables.put("member", member.getFirstName()); variables.put("lateSteps", lateSteps); // ✅ 传入 List,非 HTML 字符串 return variables; }⚠️ 不推荐方案:th:utext(存在 XSS 风险)
虽然 th:utext 可直接输出未转义 HTML:
<span th:utext="${lateSteps}"/>但若 lateSteps 来自用户输入或不可信来源,将导致 HTML 注入/XSS 漏洞(如嵌入 <script> 标签)。邮件模板虽通常不执行 JS,但破坏结构、泄露信息或触发客户端解析异常的风险依然存在。因此,仅当内容绝对可信且无法重构为结构化数据时才谨慎使用。
? 其他关键注意事项
- 修复模板语法错误:原模板中存在孤立的 </span> 标签,会导致 HTML 解析异常,务必确保标签成对闭合。
- 变量名严格一致:模板中引用 ${firstNameTeamLead},而代码中误传 "firstNameTL",将导致值为空 —— 建议启用 Thymeleaf 的 spring.thymeleaf.cache=false(开发环境)并关注日志警告。
- 邮件客户端兼容性:部分邮件客户端(如 Outlook)对 CSS 和现代 HTML 支持有限,建议使用内联样式、语义化标签(<table> 布局仍较稳妥),并测试多端渲染效果。
通过将动态内容建模为 Java 集合 + Thymeleaf 迭代,不仅彻底规避了 HTML 转义问题,还提升了模板可读性、后端逻辑可测性及长期可维护性 —— 这正是模板引擎设计的初衷。











