| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- package net.yyc.quartz.executor;
- import cn.hutool.core.util.StrUtil;
- import lombok.RequiredArgsConstructor;
- import lombok.extern.slf4j.Slf4j;
- import net.yyc.quartz.entity.JobBriefing;
- import net.yyc.quartz.service.EmailService;
- import net.yyc.quartz.service.JobBriefingService;
- import org.springframework.stereotype.Component;
- import java.time.format.DateTimeFormatter;
- /**
- * 每日简报执行器
- * 每天早上9点执行,生成昨日任务执行简报并发送邮件
- */
- @Slf4j
- @Component("JobBriefingExecutor")
- @RequiredArgsConstructor
- public class JobBriefingExecutor {
- private final JobBriefingService jobBriefingService;
- private final EmailService emailService;
- private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
- /**
- * 生成并发送每日简报
- *
- * @return 执行结果(成功返回"success",失败返回异常信息)
- */
- public String sendDailyBriefing() {
- try {
- log.info("[简报] 开始生成每日简报...");
- // 生成昨日简报
- JobBriefing briefing = jobBriefingService.generateYesterdayBriefing();
- // 构建邮件内容
- String content = buildEmailContent(briefing);
- String subject = String.format("【定时任务简报】%s", briefing.getReportDate().format(DATE_FORMATTER));
- // 发送邮件
- boolean success = emailService.sendBriefing(content, subject);
- if (success) {
- log.info("[简报] 每日简报发送成功 | 日期={} | 任务数={} | 错过执行={}",
- briefing.getReportDate(),
- briefing.getJobStats().size(),
- briefing.getMissedJobs().size());
- return "success";
- } else {
- log.warn("[简报] 每日简报发送失败");
- return "send failed";
- }
- } catch (Exception e) {
- log.error("[简报] 生成每日简报异常", e);
- throw new RuntimeException("生成每日简报异常: " + e.getMessage(), e);
- }
- }
- private String buildEmailContent(JobBriefing briefing) {
- StringBuilder sb = new StringBuilder();
- sb.append("<html><body style='font-family: Arial, sans-serif; font-size: 14px;'>");
- // 标题
- sb.append("<h2 style='color: #333; margin-bottom: 20px;'>定时任务每日简报</h2>");
- sb.append(String.format("<p><b>报告日期:</b>%s</p>",
- briefing.getReportDate().format(DATE_FORMATTER)));
- // 错过执行告警
- if (!briefing.getMissedJobs().isEmpty()) {
- sb.append("<div style='background-color: #fff3cd; border: 1px solid #ffc107; padding: 15px; margin-bottom: 20px; border-radius: 5px;'>");
- sb.append("<h3 style='color: #856404; margin-top: 0;'>⚠️ 错过执行</h3>");
- sb.append("<table style='border-collapse: collapse; width: 100%;'>");
- sb.append("<tr style='background-color: #fff8e1;'>");
- sb.append("<th style='padding: 8px; border: 1px solid #ffc107; text-align: left;'>任务名称</th>");
- sb.append("<th style='padding: 8px; border: 1px solid #ffc107; text-align: left;'>任务组</th>");
- sb.append("<th style='padding: 8px; border: 1px solid #ffc107; text-align: left;'>下次执行时间</th>");
- sb.append("<th style='padding: 8px; border: 1px solid #ffc107; text-align: left;'>说明</th>");
- sb.append("</tr>");
- for (JobBriefing.MissedJobInfo job : briefing.getMissedJobs()) {
- sb.append("<tr>");
- sb.append(String.format("<td style='padding: 8px; border: 1px solid #ffc107;'>%s</td>",
- escapeHtml(job.getJobName())));
- sb.append(String.format("<td style='padding: 8px; border: 1px solid #ffc107;'>%s</td>",
- escapeHtml(job.getJobGroup())));
- sb.append(String.format("<td style='padding: 8px; border: 1px solid #ffc107;'>%s</td>",
- escapeHtml(job.getNextExecuteTime())));
- sb.append(String.format("<td style='padding: 8px; border: 1px solid #ffc107; color: #856404;'>%s</td>",
- escapeHtml(job.getRemark())));
- sb.append("</tr>");
- }
- sb.append("</table>");
- sb.append("</div>");
- }
- // 任务统计列表
- sb.append("<h3 style='color: #666; border-bottom: 1px solid #ddd; padding-bottom: 5px;'>任务执行情况</h3>");
- sb.append("<table style='border-collapse: collapse; width: 100%; font-size: 12px;'>");
- sb.append("<tr style='background-color: #f5f5f5;'>");
- sb.append("<th style='padding: 8px; border: 1px solid #ddd; text-align: left;'>任务名称</th>");
- sb.append("<th style='padding: 8px; border: 1px solid #ddd; text-align: left;'>任务组</th>");
- sb.append("<th style='padding: 8px; border: 1px solid #ddd; text-align: center;'>执行次数</th>");
- sb.append("<th style='padding: 8px; border: 1px solid #ddd; text-align: center;'>成功</th>");
- sb.append("<th style='padding: 8px; border: 1px solid #ddd; text-align: center;'>失败</th>");
- sb.append("<th style='padding: 8px; border: 1px solid #ddd; text-align: center;'>成功率</th>");
- sb.append("<th style='padding: 8px; border: 1px solid #ddd; text-align: center;'>平均耗时(ms)</th>");
- sb.append("<th style='padding: 8px; border: 1px solid #ddd; text-align: center;'>最后执行</th>");
- sb.append("<th style='padding: 8px; border: 1px solid #ddd; text-align: center;'>状态</th>");
- sb.append("<th style='padding: 8px; border: 1px solid #ddd; text-align: left;'>异常信息</th>");
- sb.append("</tr>");
- for (JobBriefing.JobStatInfo job : briefing.getJobStats()) {
- // 根据状态设置行背景色
- String rowBgColor = "";
- if (job.getFailCount() > 0) {
- rowBgColor = "background-color: #ffebee;"; // 红色背景(失败)
- } else if (job.getSuccessRate() < 90 && job.getExecCount() > 0) {
- rowBgColor = "background-color: #fff3e0;"; // 橙色背景(成功率低)
- } else if (job.getExecCount() == 0) {
- rowBgColor = "background-color: #e3f2fd;"; // 蓝色背景(无执行)
- }
- sb.append(String.format("<tr style='%s'>", rowBgColor));
- sb.append(String.format("<td style='padding: 8px; border: 1px solid #ddd;'><b>%s</b></td>",
- escapeHtml(job.getJobName())));
- sb.append(String.format("<td style='padding: 8px; border: 1px solid #ddd;'>%s</td>",
- escapeHtml(job.getJobGroup())));
- sb.append(String.format("<td style='padding: 8px; border: 1px solid #ddd; text-align: center;'>%d</td>",
- job.getExecCount()));
- sb.append(String.format("<td style='padding: 8px; border: 1px solid #ddd; text-align: center; color: green;'>%d</td>",
- job.getSuccessCount()));
- sb.append(String.format("<td style='padding: 8px; border: 1px solid #ddd; text-align: center; %s'>%d</td>",
- job.getFailCount() > 0 ? "color: red; font-weight: bold;" : "",
- job.getFailCount()));
- sb.append(String.format("<td style='padding: 8px; border: 1px solid #ddd; text-align: center; %s'>%.2f%%</td>",
- job.getSuccessRate() < 90 ? "color: orange; font-weight: bold;" : "",
- job.getSuccessRate()));
- sb.append(String.format("<td style='padding: 8px; border: 1px solid #ddd; text-align: center;'>%d</td>",
- job.getAvgExecuteTime()));
- sb.append(String.format("<td style='padding: 8px; border: 1px solid #ddd;'>%s</td>",
- job.getLastExecuteTime() != null ? job.getLastExecuteTime().substring(0, Math.min(19, job.getLastExecuteTime().length())) : ""));
- sb.append(String.format("<td style='padding: 8px; border: 1px solid #ddd; text-align: center; %s'>%s</td>",
- "失败".equals(job.getLastExecStatus()) ? "color: red; font-weight: bold;" : "color: green;",
- job.getLastExecStatus() != null ? job.getLastExecStatus() : "-"));
- sb.append(String.format("<td style='padding: 8px; border: 1px solid #ddd; font-size: 11px; color: #666;'>%s</td>",
- escapeHtml(job.getExceptionInfo())));
- sb.append("</tr>");
- }
- sb.append("</table>");
- // 页脚
- sb.append(String.format("<p style='color: #999; font-size: 12px; margin-top: 20px;'>生成时间:%s</p>",
- briefing.getGeneratedAt()));
- sb.append("</body></html>");
- return sb.toString();
- }
- private String escapeHtml(String text) {
- if (StrUtil.isBlank(text) || "null".equals(text)) {
- return "";
- }
- return text.replace("&", "&")
- .replace("<", "<")
- .replace(">", ">")
- .replace("\"", """)
- .replace("'", "'");
- }
- }
|