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(""); // 标题 sb.append("

定时任务每日简报

"); sb.append(String.format("

报告日期:%s

", briefing.getReportDate().format(DATE_FORMATTER))); // 错过执行告警 if (!briefing.getMissedJobs().isEmpty()) { sb.append("
"); sb.append("

⚠️ 错过执行

"); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); for (JobBriefing.MissedJobInfo job : briefing.getMissedJobs()) { sb.append(""); sb.append(String.format("", escapeHtml(job.getJobName()))); sb.append(String.format("", escapeHtml(job.getJobGroup()))); sb.append(String.format("", escapeHtml(job.getNextExecuteTime()))); sb.append(String.format("", escapeHtml(job.getRemark()))); sb.append(""); } sb.append("
任务名称任务组下次执行时间说明
%s%s%s%s
"); sb.append("
"); } // 任务统计列表 sb.append("

任务执行情况

"); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); 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("", rowBgColor)); sb.append(String.format("", escapeHtml(job.getJobName()))); sb.append(String.format("", escapeHtml(job.getJobGroup()))); sb.append(String.format("", job.getExecCount())); sb.append(String.format("", job.getSuccessCount())); sb.append(String.format("", job.getFailCount() > 0 ? "color: red; font-weight: bold;" : "", job.getFailCount())); sb.append(String.format("", job.getSuccessRate() < 90 ? "color: orange; font-weight: bold;" : "", job.getSuccessRate())); sb.append(String.format("", job.getAvgExecuteTime())); sb.append(String.format("", job.getLastExecuteTime() != null ? job.getLastExecuteTime().substring(0, Math.min(19, job.getLastExecuteTime().length())) : "")); sb.append(String.format("", "失败".equals(job.getLastExecStatus()) ? "color: red; font-weight: bold;" : "color: green;", job.getLastExecStatus() != null ? job.getLastExecStatus() : "-")); sb.append(String.format("", escapeHtml(job.getExceptionInfo()))); sb.append(""); } sb.append("
任务名称任务组执行次数成功失败成功率平均耗时(ms)最后执行状态异常信息
%s%s%d%d%d%.2f%%%d%s%s%s
"); // 页脚 sb.append(String.format("

生成时间:%s

", briefing.getGeneratedAt())); sb.append(""); return sb.toString(); } private String escapeHtml(String text) { if (StrUtil.isBlank(text) || "null".equals(text)) { return ""; } return text.replace("&", "&") .replace("<", "<") .replace(">", ">") .replace("\"", """) .replace("'", "'"); } }