JobBriefingExecutor.java 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. package net.yyc.quartz.executor;
  2. import cn.hutool.core.util.StrUtil;
  3. import lombok.RequiredArgsConstructor;
  4. import lombok.extern.slf4j.Slf4j;
  5. import net.yyc.quartz.entity.JobBriefing;
  6. import net.yyc.quartz.service.EmailService;
  7. import net.yyc.quartz.service.JobBriefingService;
  8. import org.springframework.stereotype.Component;
  9. import java.time.format.DateTimeFormatter;
  10. /**
  11. * 每日简报执行器
  12. * 每天早上9点执行,生成昨日任务执行简报并发送邮件
  13. */
  14. @Slf4j
  15. @Component("JobBriefingExecutor")
  16. @RequiredArgsConstructor
  17. public class JobBriefingExecutor {
  18. private final JobBriefingService jobBriefingService;
  19. private final EmailService emailService;
  20. private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
  21. /**
  22. * 生成并发送每日简报
  23. *
  24. * @return 执行结果(成功返回"success",失败返回异常信息)
  25. */
  26. public String sendDailyBriefing() {
  27. try {
  28. log.info("[简报] 开始生成每日简报...");
  29. // 生成昨日简报
  30. JobBriefing briefing = jobBriefingService.generateYesterdayBriefing();
  31. // 构建邮件内容
  32. String content = buildEmailContent(briefing);
  33. String subject = String.format("【定时任务简报】%s", briefing.getReportDate().format(DATE_FORMATTER));
  34. // 发送邮件
  35. boolean success = emailService.sendBriefing(content, subject);
  36. if (success) {
  37. log.info("[简报] 每日简报发送成功 | 日期={} | 任务数={} | 错过执行={}",
  38. briefing.getReportDate(),
  39. briefing.getJobStats().size(),
  40. briefing.getMissedJobs().size());
  41. return "success";
  42. } else {
  43. log.warn("[简报] 每日简报发送失败");
  44. return "send failed";
  45. }
  46. } catch (Exception e) {
  47. log.error("[简报] 生成每日简报异常", e);
  48. throw new RuntimeException("生成每日简报异常: " + e.getMessage(), e);
  49. }
  50. }
  51. private String buildEmailContent(JobBriefing briefing) {
  52. StringBuilder sb = new StringBuilder();
  53. sb.append("<html><body style='font-family: Arial, sans-serif; font-size: 14px;'>");
  54. // 标题
  55. sb.append("<h2 style='color: #333; margin-bottom: 20px;'>定时任务每日简报</h2>");
  56. sb.append(String.format("<p><b>报告日期:</b>%s</p>",
  57. briefing.getReportDate().format(DATE_FORMATTER)));
  58. // 错过执行告警
  59. if (!briefing.getMissedJobs().isEmpty()) {
  60. sb.append("<div style='background-color: #fff3cd; border: 1px solid #ffc107; padding: 15px; margin-bottom: 20px; border-radius: 5px;'>");
  61. sb.append("<h3 style='color: #856404; margin-top: 0;'>⚠️ 错过执行</h3>");
  62. sb.append("<table style='border-collapse: collapse; width: 100%;'>");
  63. sb.append("<tr style='background-color: #fff8e1;'>");
  64. sb.append("<th style='padding: 8px; border: 1px solid #ffc107; text-align: left;'>任务名称</th>");
  65. sb.append("<th style='padding: 8px; border: 1px solid #ffc107; text-align: left;'>任务组</th>");
  66. sb.append("<th style='padding: 8px; border: 1px solid #ffc107; text-align: left;'>下次执行时间</th>");
  67. sb.append("<th style='padding: 8px; border: 1px solid #ffc107; text-align: left;'>说明</th>");
  68. sb.append("</tr>");
  69. for (JobBriefing.MissedJobInfo job : briefing.getMissedJobs()) {
  70. sb.append("<tr>");
  71. sb.append(String.format("<td style='padding: 8px; border: 1px solid #ffc107;'>%s</td>",
  72. escapeHtml(job.getJobName())));
  73. sb.append(String.format("<td style='padding: 8px; border: 1px solid #ffc107;'>%s</td>",
  74. escapeHtml(job.getJobGroup())));
  75. sb.append(String.format("<td style='padding: 8px; border: 1px solid #ffc107;'>%s</td>",
  76. escapeHtml(job.getNextExecuteTime())));
  77. sb.append(String.format("<td style='padding: 8px; border: 1px solid #ffc107; color: #856404;'>%s</td>",
  78. escapeHtml(job.getRemark())));
  79. sb.append("</tr>");
  80. }
  81. sb.append("</table>");
  82. sb.append("</div>");
  83. }
  84. // 任务统计列表
  85. sb.append("<h3 style='color: #666; border-bottom: 1px solid #ddd; padding-bottom: 5px;'>任务执行情况</h3>");
  86. sb.append("<table style='border-collapse: collapse; width: 100%; font-size: 12px;'>");
  87. sb.append("<tr style='background-color: #f5f5f5;'>");
  88. sb.append("<th style='padding: 8px; border: 1px solid #ddd; text-align: left;'>任务名称</th>");
  89. sb.append("<th style='padding: 8px; border: 1px solid #ddd; text-align: left;'>任务组</th>");
  90. sb.append("<th style='padding: 8px; border: 1px solid #ddd; text-align: center;'>执行次数</th>");
  91. sb.append("<th style='padding: 8px; border: 1px solid #ddd; text-align: center;'>成功</th>");
  92. sb.append("<th style='padding: 8px; border: 1px solid #ddd; text-align: center;'>失败</th>");
  93. sb.append("<th style='padding: 8px; border: 1px solid #ddd; text-align: center;'>成功率</th>");
  94. sb.append("<th style='padding: 8px; border: 1px solid #ddd; text-align: center;'>平均耗时(ms)</th>");
  95. sb.append("<th style='padding: 8px; border: 1px solid #ddd; text-align: center;'>最后执行</th>");
  96. sb.append("<th style='padding: 8px; border: 1px solid #ddd; text-align: center;'>状态</th>");
  97. sb.append("<th style='padding: 8px; border: 1px solid #ddd; text-align: left;'>异常信息</th>");
  98. sb.append("</tr>");
  99. for (JobBriefing.JobStatInfo job : briefing.getJobStats()) {
  100. // 根据状态设置行背景色
  101. String rowBgColor = "";
  102. if (job.getFailCount() > 0) {
  103. rowBgColor = "background-color: #ffebee;"; // 红色背景(失败)
  104. } else if (job.getSuccessRate() < 90 && job.getExecCount() > 0) {
  105. rowBgColor = "background-color: #fff3e0;"; // 橙色背景(成功率低)
  106. } else if (job.getExecCount() == 0) {
  107. rowBgColor = "background-color: #e3f2fd;"; // 蓝色背景(无执行)
  108. }
  109. sb.append(String.format("<tr style='%s'>", rowBgColor));
  110. sb.append(String.format("<td style='padding: 8px; border: 1px solid #ddd;'><b>%s</b></td>",
  111. escapeHtml(job.getJobName())));
  112. sb.append(String.format("<td style='padding: 8px; border: 1px solid #ddd;'>%s</td>",
  113. escapeHtml(job.getJobGroup())));
  114. sb.append(String.format("<td style='padding: 8px; border: 1px solid #ddd; text-align: center;'>%d</td>",
  115. job.getExecCount()));
  116. sb.append(String.format("<td style='padding: 8px; border: 1px solid #ddd; text-align: center; color: green;'>%d</td>",
  117. job.getSuccessCount()));
  118. sb.append(String.format("<td style='padding: 8px; border: 1px solid #ddd; text-align: center; %s'>%d</td>",
  119. job.getFailCount() > 0 ? "color: red; font-weight: bold;" : "",
  120. job.getFailCount()));
  121. sb.append(String.format("<td style='padding: 8px; border: 1px solid #ddd; text-align: center; %s'>%.2f%%</td>",
  122. job.getSuccessRate() < 90 ? "color: orange; font-weight: bold;" : "",
  123. job.getSuccessRate()));
  124. sb.append(String.format("<td style='padding: 8px; border: 1px solid #ddd; text-align: center;'>%d</td>",
  125. job.getAvgExecuteTime()));
  126. sb.append(String.format("<td style='padding: 8px; border: 1px solid #ddd;'>%s</td>",
  127. job.getLastExecuteTime() != null ? job.getLastExecuteTime().substring(0, Math.min(19, job.getLastExecuteTime().length())) : ""));
  128. sb.append(String.format("<td style='padding: 8px; border: 1px solid #ddd; text-align: center; %s'>%s</td>",
  129. "失败".equals(job.getLastExecStatus()) ? "color: red; font-weight: bold;" : "color: green;",
  130. job.getLastExecStatus() != null ? job.getLastExecStatus() : "-"));
  131. sb.append(String.format("<td style='padding: 8px; border: 1px solid #ddd; font-size: 11px; color: #666;'>%s</td>",
  132. escapeHtml(job.getExceptionInfo())));
  133. sb.append("</tr>");
  134. }
  135. sb.append("</table>");
  136. // 页脚
  137. sb.append(String.format("<p style='color: #999; font-size: 12px; margin-top: 20px;'>生成时间:%s</p>",
  138. briefing.getGeneratedAt()));
  139. sb.append("</body></html>");
  140. return sb.toString();
  141. }
  142. private String escapeHtml(String text) {
  143. if (StrUtil.isBlank(text) || "null".equals(text)) {
  144. return "";
  145. }
  146. return text.replace("&", "&amp;")
  147. .replace("<", "&lt;")
  148. .replace(">", "&gt;")
  149. .replace("\"", "&quot;")
  150. .replace("'", "&#39;");
  151. }
  152. }