Commit 315a024ccc465597a9e6aa57a3fec6e2339c4338

Authored by YunaiV
1 parent e6dcbde8

feat(mail): 增强邮件模板参数解析和内容格式化功能

yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateServiceImpl.java
... ... @@ -131,52 +131,47 @@ public class MailTemplateServiceImpl implements MailTemplateService {
131 131  
132 132 @Override
133 133 public String formatMailTemplateContent(String content, Map<String, Object> params) {
134   - // 先替换模板变量
  134 + // 1. 先替换模板变量
135 135 String formattedContent = StrUtil.format(content, params);
136 136  
137   - // 反转义HTML特殊字符
  137 + // 关联 Pull Request:https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1461 讨论
  138 + // 2.1 反转义HTML特殊字符
138 139 formattedContent = unescapeHtml(formattedContent);
139   -
140   - // 处理代码块(确保<pre><code>标签格式正确)
  140 + // 2.2 处理代码块(确保<pre><code>标签格式正确)
141 141 formattedContent = formatHtmlCodeBlocks(formattedContent);
142   -
143   - // 将最外层的pre标签替换为div标签
  142 + // 2.3 将最外层的 pre 标签替换为 div 标签
144 143 formattedContent = replaceOuterPreWithDiv(formattedContent);
145   -
146 144 return formattedContent;
147 145 }
148 146  
149 147 private String replaceOuterPreWithDiv(String content) {
150   - if (content == null) {
151   - return null;
  148 + if (StrUtil.isEmpty(content)) {
  149 + return content;
152 150 }
153   -
154   - // 使用正则表达式匹配所有的<pre>标签,包括嵌套的<code>标签
  151 + // 使用正则表达式匹配所有的 <pre> 标签,包括嵌套的 <code> 标签
155 152 String regex = "(?s)<pre[^>]*>(.*?)</pre>";
156 153 Pattern pattern = Pattern.compile(regex);
157 154 Matcher matcher = pattern.matcher(content);
158   -
159   - StringBuffer sb = new StringBuffer();
  155 + StringBuilder sb = new StringBuilder();
160 156 while (matcher.find()) {
161   - // 提取<pre>标签内的内容
  157 + // 提取 <pre> 标签内的内容
162 158 String innerContent = matcher.group(1);
163   - // 返回div标签包裹的内容
  159 + // 返回 div 标签包裹的内容
164 160 matcher.appendReplacement(sb, "<div>" + innerContent + "</div>");
165 161 }
166 162 matcher.appendTail(sb);
167   -
168 163 return sb.toString();
169 164 }
170 165  
171 166 /**
172   - * 反转义HTML特殊字符
  167 + * 反转义 HTML 特殊字符
173 168 *
174 169 * @param input 输入字符串
175 170 * @return 反转义后的字符串
176 171 */
177 172 private String unescapeHtml(String input) {
178   - if (input == null) {
179   - return null;
  173 + if (StrUtil.isEmpty(input)) {
  174 + return input;
180 175 }
181 176 return input
182 177 .replace("&amp;", "&")
... ... @@ -188,27 +183,24 @@ public class MailTemplateServiceImpl implements MailTemplateService {
188 183 }
189 184  
190 185 /**
191   - * 格式化HTML中的代码块
  186 + * 格式化 HTML 中的代码块
192 187 *
193 188 * @param content 邮件内容
194 189 * @return 格式化后的邮件内容
195 190 */
196 191 private String formatHtmlCodeBlocks(String content) {
197   - // 匹配<pre><code>标签的代码块
  192 + // 匹配 <pre><code> 标签的代码块
198 193 Pattern codeBlockPattern = Pattern.compile("<pre\\s*.*?><code\\s*.*?>(.*?)</code></pre>", Pattern.DOTALL);
199 194 Matcher matcher = codeBlockPattern.matcher(content);
200   -
201   - StringBuffer sb = new StringBuffer();
  195 + StringBuilder sb = new StringBuilder();
202 196 while (matcher.find()) {
203 197 // 获取代码块内容
204 198 String codeBlock = matcher.group(1);
205   -
206 199 // 为代码块添加样式
207 200 String replacement = "<pre style=\"background-color: #f5f5f5; padding: 10px; border-radius: 5px; overflow-x: auto;\"><code>" + codeBlock + "</code></pre>";
208 201 matcher.appendReplacement(sb, replacement);
209 202 }
210 203 matcher.appendTail(sb);
211   -
212 204 return sb.toString();
213 205 }
214 206  
... ... @@ -216,6 +208,7 @@ public class MailTemplateServiceImpl implements MailTemplateService {
216 208 public long getMailTemplateCountByAccountId(Long accountId) {
217 209 return mailTemplateMapper.selectCountByAccountId(accountId);
218 210 }
  211 +
219 212 /**
220 213 * 解析标题和内容中的参数
221 214 */
... ... @@ -223,7 +216,6 @@ public class MailTemplateServiceImpl implements MailTemplateService {
223 216 public List<String> parseTemplateTitleAndContentParams(String title, String content) {
224 217 List<String> titleParams = ReUtil.findAllGroup1(PATTERN_PARAMS, title);
225 218 List<String> contentParams = ReUtil.findAllGroup1(PATTERN_PARAMS, content);
226   -
227 219 // 合并参数并去重
228 220 List<String> allParams = new ArrayList<>(titleParams);
229 221 for (String param : contentParams) {
... ...
yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateServiceImplTest.java
... ... @@ -221,7 +221,7 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
221 221  
222 222 // 调用,并断言结果
223 223 String result = mailTemplateService.formatMailTemplateContent(content, params);
224   - // 断言pre标签被替换为div标签
  224 + // 断言 pre 标签被替换为 div 标签
225 225 assertTrue(result.contains("<div><code>public class Test {"));
226 226 assertTrue(result.contains("System.out.println(\"Hello 测试\""));
227 227 assertTrue(result.contains("</code></div>"));
... ... @@ -233,10 +233,10 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
233 233 Map<String, Object> params = new HashMap<>();
234 234 params.put("content", "测试内容");
235 235  
236   - // 测试pre标签替换为div标签
  236 + // 测试 pre 标签替换为 div 标签
237 237 String content = "<pre><code>{content}</code></pre>";
238 238 String result = mailTemplateService.formatMailTemplateContent(content, params);
239   - // 断言结果中包含div标签,而不包含pre标签
  239 + // 断言结果中包含 div 标签,而不包含 pre 标签
240 240 assertTrue(result.contains("<div><code>测试内容</code></div>"));
241 241 }
242 242  
... ... @@ -247,7 +247,7 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
247 247 params.put("username", "testuser");
248 248 params.put("company", "测试公司");
249 249  
250   - // 测试完整的HTML邮件模板
  250 + // 测试完整的 HTML 邮件模板
251 251 String content = "<!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n <meta charset=\"UTF-8\">\n <title>Title</title>\n </head>\n <body>\n <div>\n <includetail>\n <div>\n <div class=\"open_email\" style=\"margin: 8px; \">\n <div>\n <br>\n <span class=\"genEmailContent\">\n <div id=\"cTMail-Wrap\" style=\"word-break: break-all;box-sizing:border-box;text-align:left;min-width:320px; max-width:660px; border:1px solid #f6f6f6; background-color:#f7f8fa; margin:auto; padding:20px 0 30px; font-family:'helvetica neue',PingFangSC-Light,arial,'hiragino sans gb','microsoft yahei ui','microsoft yahei',simsun,sans-serif\">\n <div class=\"main-content\">\n <table style=\"width:100%;font-weight:300;margin-bottom:10px;border-collapse:collapse\">\n <tbody>\n <tr style=\"font-weight:300\">\n <td style=\"width:3%;max-width:30px;\"></td>\n <td style=\"max-width:600px;\">\n <div id=\"cTMail-logo\" style=\"width:92px; height:36px;\">\n <a href=\"\">\n <img border=\"0\" src=\"左上角图片logo\" style=\"width:120px; height:36px;display:block\">\n </a>\n </div>\n <div style=\"color: #C2C5C9;width: 260px;float: right;font-size: 12px;\">此邮件由系统发出,请勿直接回复或转发他人</div>\n <p style=\"height:2px;background-color: #00a4ff;border: 0;font-size:0;padding:0;width:100%;margin-top:20px;\"></p>\n <div id=\"cTMail-inner\" style=\"background-color:#fff; padding:23px 0 20px;box-shadow: 0px 1px 1px 0px rgba(122, 55, 55, 0.2);text-align:left;\">\n <table style=\"width:100%;font-weight:300;margin-bottom:10px;border-collapse:collapse;\">\n <tbody>\n <tr style=\"font-weight:300\">\n <td style=\"width:3.2%;max-width:30px;\"></td>\n <td style=\"max-width:480px;\">\n <h1 id=\"cTMail-title\" style=\"font-size: 20px; line-height: 36px; margin: 0px 0px 22px;\">\n 尊敬的 {username},\n </h1>\n <dl style=\"font-size: 14px; color: #595E65; line-height: 18px;\">\n <dd style=\"margin: 0px 0px 6px; padding: 0px; font-size: 14px; line-height: 22px;\">\n <p id=\"cTMail-sender\" style=\"font-size: 14px; line-height: 26px; word-wrap: break-word; word-break: break-all; margin-top: 32px;\">\n 内容<br>\n 内容<br>\n 内容123<br><br>\n\n 如果您在使用过程中遇到任何问题或者有任何建议,都可以随时联系我们的客户团队,我们将竭诚为您服务。\n\n\n\n\n <br>\n <br>\n {company}<br>\n 地址:xxxxx<br>\n 邮箱:lambc77@163.com\n </p>\n </dd>\n </dl>\n <hr style=\"border: 0.1px solid #e5e5e5;\"/>\n <dl style=\"font-size: 14px; color: #595E65; line-height: 18px;\">\n <div style=\"color: #93979B;\">\n <strong>声明:本邮件含有保密信息,仅限于收件人所用。禁止任何人未经发件人许可,以任何形式(包括但不限于部分的泄露、复制或散发)不当的使用本邮件中的信息。如果您错收了本邮件,请您立即电话或邮件通知发件人并删除本邮件,谢谢!\n </strong><br>\n </div>\n </dl>\n </td>\n <td style=\"width:3.2%;max-width:30px;\"></td>\n </tr>\n </tbody>\n </table>\n </div>\n </td>\n <td style=\"width:3%;max-width:30px;\"></td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n </span>\n </div>\n </div>\n </div>\n </includetail>\n </div>\n </body>\n <script>\n\n </script>\n </html>";
252 252  
253 253 // 调用,并断言成功处理
... ... @@ -255,7 +255,7 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
255 255 // 断言结果中包含替换后的变量
256 256 assertTrue(result.contains("尊敬的 testuser"));
257 257 assertTrue(result.contains("测试公司"));
258   - // 断言结果是有效的HTML
  258 + // 断言结果是有效的 HTML
259 259 assertTrue(result.startsWith("<!DOCTYPE html>"));
260 260 }
261 261  
... ... @@ -287,7 +287,7 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
287 287 params.put("param1", "value1");
288 288 params.put("param2", "value2");
289 289  
290   - // 测试多个pre标签的情况
  290 + // 测试多个 pre 标签的情况
291 291 String content = "<pre><code>First code block: {param1}</code></pre>\n" +
292 292 "<p>Some text between code blocks</p>\n" +
293 293 "<pre><code>Second code block: {param2}</code></pre>";
... ... @@ -302,7 +302,7 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
302 302 // 准备参数
303 303 Map<String, Object> params = new HashMap<>();
304 304  
305   - // 简化测试,只测试基本的HTML特殊字符
  305 + // 简化测试,只测试基本的 HTML 特殊字符
306 306 String content = "&lt;div&gt;测试 &amp; 特殊字符&lt;/div&gt;";
307 307 String result = mailTemplateService.formatMailTemplateContent(content, params);
308 308 // 断言特殊字符被正确反转义
... ... @@ -327,7 +327,7 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
327 327  
328 328 @Test
329 329 public void testDifferenceWithHtmlContent() {
330   - // 准备包含HTML格式的模板内容
  330 + // 准备包含 HTML 格式的模板内容
331 331 String content = "<div style='font-family: Arial, sans-serif; color: #333;'>" +
332 332 "<h1>Welcome, {username}!</h1>" +
333 333 "<p>Your account has been created successfully.</p>" +
... ... @@ -347,28 +347,28 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
347 347 params.put("role", "admin");
348 348 params.put("activationLink", "https://example.com/activate?code=12345");
349 349  
350   - // 1. 使用parseTemplateContentParams:只提取参数名称,忽略了HTML格式
  350 + // 1. 使用 parseTemplateContentParams:只提取参数名称,忽略了 HTML 格式
351 351 List<String> parsedParams = mailTemplateService.parseTemplateContentParams(content);
352 352 System.out.println("parseTemplateContentParams结果:" + parsedParams);
353 353  
354   - // 断言:只提取了纯参数名称,没有HTML格式
  354 + // 断言:只提取了纯参数名称,没有 HTML 格式
355 355 assertEquals(6, parsedParams.size());
356 356 // 检查所有参数类型
357   - assertTrue(parsedParams.stream().filter("username"::equals).count() == 3);
358   - assertTrue(parsedParams.stream().filter("email"::equals).count() == 1);
359   - assertTrue(parsedParams.stream().filter("role"::equals).count() == 1);
360   - assertTrue(parsedParams.stream().filter("activationLink"::equals).count() == 1);
361   - // 断言:没有包含任何HTML标签
  357 + assertEquals(3, parsedParams.stream().filter("username"::equals).count());
  358 + assertEquals(1, parsedParams.stream().filter("email"::equals).count());
  359 + assertEquals(1, parsedParams.stream().filter("role"::equals).count());
  360 + assertEquals(1, parsedParams.stream().filter("activationLink"::equals).count());
  361 + // 断言:没有包含任何 HTML 标签
362 362 for (String param : parsedParams) {
363 363 assertFalse(param.contains("<"));
364 364 assertFalse(param.contains(">"));
365 365 }
366 366  
367   - // 2. 使用formatMailTemplateContent:处理HTML格式,生成最终内容
  367 + // 2. 使用 formatMailTemplateContent:处理 HTML 格式,生成最终内容
368 368 String formattedContent = mailTemplateService.formatMailTemplateContent(content, params);
369 369 System.out.println("formatMailTemplateContent结果:" + formattedContent);
370 370  
371   - // 断言:HTML格式被保留并处理
  371 + // 断言:HTML 格式被保留并处理
372 372 assertTrue(formattedContent.contains("<div style='font-family: Arial, sans-serif; color: #333;'>"));
373 373 assertTrue(formattedContent.contains("<h1>Welcome, testuser!</h1>"));
374 374 assertTrue(formattedContent.contains("<a href='https://example.com/activate?code=12345'>here</a>"));
... ...