diff --git a/weather-service/pom.xml b/weather-service/pom.xml index a1f237d..33e9704 100644 --- a/weather-service/pom.xml +++ b/weather-service/pom.xml @@ -71,6 +71,18 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-mail</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.retry</groupId> + <artifactId>spring-retry</artifactId> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjweaver</artifactId> + </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> diff --git a/weather-service/src/main/java/com/htfp/weather/WeatherServiceApplication.java b/weather-service/src/main/java/com/htfp/weather/WeatherServiceApplication.java index 90797fe..14b9058 100644 --- a/weather-service/src/main/java/com/htfp/weather/WeatherServiceApplication.java +++ b/weather-service/src/main/java/com/htfp/weather/WeatherServiceApplication.java @@ -2,11 +2,13 @@ package com.htfp.weather; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.retry.annotation.EnableRetry; import org.springframework.scheduling.annotation.EnableScheduling; import java.util.TimeZone; -@EnableScheduling +@EnableRetry // 开启重试机制 +@EnableScheduling // 开启定时任务 @SpringBootApplication public class WeatherServiceApplication { diff --git a/weather-service/src/main/java/com/htfp/weather/schedule/ErrorReport.java b/weather-service/src/main/java/com/htfp/weather/schedule/ErrorReport.java new file mode 100644 index 0000000..cbdb573 --- /dev/null +++ b/weather-service/src/main/java/com/htfp/weather/schedule/ErrorReport.java @@ -0,0 +1,14 @@ +package com.htfp.weather.schedule; + +import lombok.Data; + +/** + * @Author : shiyi + * @Date : 2024/6/13 15:32 + * @Description : 异常报告内容 + */ +@Data +public class ErrorReport { + String time; + String message; +} diff --git a/weather-service/src/main/java/com/htfp/weather/schedule/ErrorReportService.java b/weather-service/src/main/java/com/htfp/weather/schedule/ErrorReportService.java new file mode 100644 index 0000000..08f1212 --- /dev/null +++ b/weather-service/src/main/java/com/htfp/weather/schedule/ErrorReportService.java @@ -0,0 +1,66 @@ +package com.htfp.weather.schedule; + +import com.htfp.weather.utils.HttpClientUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.retry.annotation.Backoff; +import org.springframework.retry.annotation.Recover; +import org.springframework.retry.annotation.Retryable; +import org.springframework.stereotype.Service; + +/** + * @Author : shiyi + * @Date : 2024/6/13 10:57 + * @Description : 邮件发送服务 + */ +@Service +@Slf4j +public class ErrorReportService { + @Autowired + private JavaMailSender mailSender; + + @Value("${mail.send.from}") + public String sender;// 发送者 + // 发送简单文本文件 + + @Retryable(value = {ReportException.class}, maxAttempts = 4, backoff = @Backoff(delay = 2000, multiplier = 2)) + public void sendSimpleEmail(final Mail mail) { + try { + SimpleMailMessage message = new SimpleMailMessage(); + message.setFrom(sender); + message.setTo(mail.getTos()); + message.setSubject(mail.getSubject()); + message.setText(mail.getContent()); + mailSender.send(message); + log.info("[error report] 异常邮件发送成功,{} --> {}, 主题:{}", sender, mail.getTos(), mail.getSubject()); + } catch (Exception e) { + log.error("[error report] 异常邮件发送失败, 重试中: {}", e.getMessage()); + throw new ReportException(); + } + } + + @Retryable(value = {ReportException.class}, maxAttempts = 4, backoff = @Backoff(delay = 2000, multiplier = 2)) + public void reportToRemote(final ErrorReport report) { + try { + // TODO 2024/6/13: 发送到指定服务器上 + // HttpClientUtils.sendPost(); + log.info("[error report] 异常报告发送成功:{}", report.message); + } catch (Exception e) { + log.error("[error report] 邮件发送失败, 重试中: {}", e.getMessage()); + throw new ReportException(); + } + } + + @Recover + public void recover(ReportException e) { + // recover 方法的返回值要和重试方法的返回值一致 + log.error("[error report]:异常通知发送重试到达最大次数,发送失败"); + } + + public class ReportException extends RuntimeException { + } +} + diff --git a/weather-service/src/main/java/com/htfp/weather/schedule/GridDataProcessor.java b/weather-service/src/main/java/com/htfp/weather/schedule/GridDataProcessor.java index 67d89dc..7494d52 100644 --- a/weather-service/src/main/java/com/htfp/weather/schedule/GridDataProcessor.java +++ b/weather-service/src/main/java/com/htfp/weather/schedule/GridDataProcessor.java @@ -4,9 +4,11 @@ import com.htfp.weather.download.FileInfo; import com.htfp.weather.download.GfsDataConfig; import com.htfp.weather.download.GfsDownloader; import com.htfp.weather.griddata.operation.GfsDataImport; +import com.htfp.weather.griddata.operation.GfsDataImport.ImportResult; import com.htfp.weather.info.Constant; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.DependsOn; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -19,6 +21,10 @@ import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.stream.Collectors; /** @@ -36,7 +42,14 @@ public class GridDataProcessor { GfsDownloader gfsDownloader; @Resource GfsDataConfig gfsDataConfig; + @Resource + ErrorReportService errorReportService; + + @Value("${mail.send.to}") + private String[] mailReceiver; + private final ExecutorService executorService = Executors.newFixedThreadPool(3); + boolean test = false; @Scheduled(cron = "0 0 0,8,12,18 * * ?") public void dailyDataProcess() throws Exception { if (download()) { @@ -45,48 +58,55 @@ public class GridDataProcessor { clearExpiredData(); } - private boolean importToTableStore() throws Exception { - try { - OffsetDateTime refTime = gfsDownloader.getRefTime(); - log.info("[schedule-start] 开始将数据导入数据库, 起报时间: {}", refTime); - List<GfsDataImport.ImportResult> importResults = gfsDataImport.importData(refTime); - List<GfsDataImport.ImportResult> failedList = importResults.stream().filter(result -> !result.isSuccess()).collect(Collectors.toList()); - if (!failedList.isEmpty()) { - log.error("[schedule-end] 气象数据导入数据库失败, 文件列表: {} ", failedList); - emailReport(); - return false; - } - log.info("[schedule-end] 气象数据导入数据库成功"); - return true; - } catch (Exception e) { - log.error("[schedule-end] 气象数据导入数据库失败", e); - return false; - } - } - - private boolean download() { + public boolean download() { + gfsDownloader.iniTimeSetting(); try { log.info("[schedule-start] 开始下载气象数据"); - gfsDownloader.iniTimeSetting(); List<FileInfo> fileInfoList = gfsDownloader.getFilesInfo(); + // if (test){ + // throw new RuntimeException("测试异常"); + // } List<FileInfo> finishedList = gfsDownloader.downloadAll(fileInfoList); List<FileInfo> failedList = finishedList.stream().filter(fileInfo -> !fileInfo.isDownloadSuccess()).collect(Collectors.toList()); if (!failedList.isEmpty()) { log.error("[schedule] 下载失败文件列表: {}", failedList); - emailReport(); + downloadFailedReport(gfsDownloader.getRefTimeStr(), finishedList, failedList); return false; } log.info("[schedule-end] 下载气象数据成功"); return true; } catch (RuntimeException e) { log.error("[schedule-end] 下载气象数据失败", e); + downloadFailedReport(gfsDownloader.getRefTimeStr(), e); return false; } } - private void emailReport() { + public boolean importToTableStore(){ + try { + OffsetDateTime refTime = gfsDownloader.getRefTime(); + log.info("[schedule-start] 开始将数据导入数据库, 起报时间: {}", refTime); + // if (test){ + // throw new RuntimeException("测试异常"); + // } + List<ImportResult> importResults = gfsDataImport.importData(refTime); + List<ImportResult> finishedList = importResults.stream().filter(result -> result.isSuccess()).collect(Collectors.toList()); + List<ImportResult> failedList = importResults.stream().filter(result -> !result.isSuccess()).collect(Collectors.toList()); + if (!failedList.isEmpty()) { + log.error("[schedule-end] 气象数据导入数据库失败, 文件列表: {} ", failedList); + importFailedReport(gfsDownloader.getRefTimeStr(), finishedList, failedList); + return false; + } + log.info("[schedule-end] 气象数据导入数据库成功"); + return true; + } catch (Exception e) { + log.error("[schedule-end] 气象数据导入数据库失败", e); + importFailedReport(gfsDownloader.getRefTimeStr(),e); + return false; + } } + public void clearExpiredData() { log.info("[schedule-start] 开始清理过期数据"); File dataDir = new File(gfsDataConfig.getSaveRoot()); @@ -108,4 +128,108 @@ public class GridDataProcessor { } } + private void downloadFailedReport(String refTime, List<FileInfo> finishedList, List<FileInfo> failedList) { + // 邮件 + Mail mail = new Mail(); + mail.setSubject("【航天飞鹏-气象服务】数据下载失败通知"); + mail.setTos(mailReceiver); + StringBuilder sb = new StringBuilder(); + sb.append("数据下载失败通知\n 气象数据下载失败,请检查下载配置。\n"); + sb.append("起报时间: ").append(refTime).append("\n"); + sb.append("下载失败文件列表: \n"); + sb.append(failedList).append("\n"); + sb.append("下载成功文件列表: \n"); + sb.append(finishedList).append("\n"); + mail.setContent(sb.toString()); + Future<?> emailFuture = executorService.submit(() -> errorReportService.sendSimpleEmail(mail)); + + // post + ErrorReport errorReport = new ErrorReport(); + errorReport.setTime(OffsetDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)); + errorReport.setMessage("【航天飞鹏-气象服务】数据下载失败通知, 起报时间:" + refTime); + Future<?> httpFuture = executorService.submit(() -> errorReportService.reportToRemote(errorReport)); + + try { + emailFuture.get(); + httpFuture.get(); + } catch (Exception ex) { + log.error("[schedule] 异常报告过程错误: {}", ex.getMessage()); + } + } + + private void downloadFailedReport(String refTime, Throwable e) { + Mail mail = new Mail(); + mail.setSubject("【航天飞鹏-气象服务】数据下载失败通知"); + mail.setTos(mailReceiver); + String sb = "数据下载失败通知\n 气象数据下载失败,请检查下载配置。\n" + + "起报时间: " + refTime + "。\n"; + + mail.setContent(sb + e); + Future<?> emailFuture = executorService.submit(() -> errorReportService.sendSimpleEmail(mail)); + + // post + ErrorReport errorReport = new ErrorReport(); + errorReport.setTime(OffsetDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)); + errorReport.setMessage("【航天飞鹏-气象服务】数据下载失败通知, 起报时间:" + refTime); + Future<?> httpFuture = executorService.submit(() -> errorReportService.reportToRemote(errorReport)); + + try { + emailFuture.get(); + httpFuture.get(); + } catch (Exception ex) { + log.error("[schedule] 异常报告过程错误: {}", ex.getMessage()); + } + } + + private void importFailedReport(String refTime, List<ImportResult> finishedList, List<ImportResult> failedList) { + Mail mail = new Mail(); + mail.setSubject("【航天飞鹏-气象服务】数据入库失败通知"); + mail.setTos(mailReceiver); + StringBuilder sb = new StringBuilder(); + sb.append("数据下载失败通知\n 数据入库下载失败,请检查。\n"); + sb.append("起报时间: ").append(refTime).append("\n"); + sb.append("失败文件列表: \n"); + sb.append(failedList).append("\n"); + sb.append("成功文件列表: \n"); + sb.append(finishedList).append("\n"); + mail.setContent(sb.toString()); + Future<?> emailFuture = executorService.submit(() -> errorReportService.sendSimpleEmail(mail)); + + // post + ErrorReport errorReport = new ErrorReport(); + errorReport.setTime(OffsetDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)); + errorReport.setMessage("【航天飞鹏-气象服务】数据入库失败通知, 起报时间:" + refTime); + Future<?> httpFuture = executorService.submit(() -> errorReportService.reportToRemote(errorReport)); + + try { + emailFuture.get(); + httpFuture.get(); + } catch (Exception ex) { + log.error("[schedule] 异常报告过程错误: {}", ex.getMessage()); + } + } + + private void importFailedReport(String refTime, Throwable e) { + Mail mail = new Mail(); + mail.setSubject("【航天飞鹏-气象服务】数据下载失败通知"); + mail.setTos(mailReceiver); + String sb = "数据下载失败通知\n 气象数据下载失败,请检查下载配置。\n" + + "起报时间: " + refTime + "。\n"; + mail.setContent(sb + e); + Future<?> emailFuture = executorService.submit(() -> errorReportService.sendSimpleEmail(mail)); + + // post + ErrorReport errorReport = new ErrorReport(); + errorReport.setTime(OffsetDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)); + errorReport.setMessage("【航天飞鹏-气象服务】数据入库失败通知, 起报时间:" + refTime); + Future<?> httpFuture = executorService.submit(() -> errorReportService.reportToRemote(errorReport)); + + try { + emailFuture.get(); + httpFuture.get(); + } catch (Exception ex) { + log.error("[schedule] 异常报告过程错误: {}", ex.getMessage()); + } + } + } diff --git a/weather-service/src/main/java/com/htfp/weather/schedule/Mail.java b/weather-service/src/main/java/com/htfp/weather/schedule/Mail.java new file mode 100644 index 0000000..6b5a17c --- /dev/null +++ b/weather-service/src/main/java/com/htfp/weather/schedule/Mail.java @@ -0,0 +1,24 @@ +package com.htfp.weather.schedule; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import java.io.Serializable; + +/** + * @Author : shiyi + * @Date : 2024/6/13 14:08 + * @Description : 邮件 + */ +@Data +@ToString +public class Mail { + // 邮件主题 + private String subject; + // 邮件内容 + private String content; + // 收件邮箱列表 + private String[] tos; +} diff --git a/weather-service/src/main/resources/application-weather.yml b/weather-service/src/main/resources/application-weather.yml index d73b258..e409048 100644 --- a/weather-service/src/main/resources/application-weather.yml +++ b/weather-service/src/main/resources/application-weather.yml @@ -9,4 +9,5 @@ tablestore: endpoint: https://gfs-test.cn-hangzhou.ots.aliyuncs.com accessId: LTAI5tDphvXLfwKJEmVQqrVz accessKey: ksioVP1S36PI6plkIIRN4A2xLB94uc - instanceName: gfs-test \ No newline at end of file + instanceName: gfs-test + diff --git a/weather-service/src/main/resources/application.yml b/weather-service/src/main/resources/application.yml index cd4c264..727fc6d 100644 --- a/weather-service/src/main/resources/application.yml +++ b/weather-service/src/main/resources/application.yml @@ -6,7 +6,18 @@ spring: mvc: servlet: load-on-startup: 1 # 关闭 dispatcherServlet懒加载 - thymeleaf: cache: false - encoding: UTF-8 \ No newline at end of file + encoding: UTF-8 + mail: + host: smtp.exmail.qq.com + username: shiyi@htsdfp.com + password: weatherHTFP123 + properties.mail.smtp: + auth: true + enable: true + required: true +mail: + send: + from: shiyi@htsdfp.com + to: shiyi@htsdfp.com \ No newline at end of file diff --git a/weather-service/src/test/java/com/htfp/weather/schedule/GridDataProcessorTest.java b/weather-service/src/test/java/com/htfp/weather/schedule/GridDataProcessorTest.java new file mode 100644 index 0000000..51d438c --- /dev/null +++ b/weather-service/src/test/java/com/htfp/weather/schedule/GridDataProcessorTest.java @@ -0,0 +1,29 @@ +package com.htfp.weather.schedule; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import javax.annotation.Resource; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @Author : shiyi + * @Date : 2024/6/13 16:38 + * @Description : + */ +@SpringBootTest +class GridDataProcessorTest { + + @Resource + GridDataProcessor gridDataProcessor; + @Test + void importToTableStore() { + + } + + @Test + void download() { + gridDataProcessor.download(); + } +} \ No newline at end of file diff --git a/weather-service/src/test/resources/application.yml b/weather-service/src/test/resources/application.yml index cd4c264..727fc6d 100644 --- a/weather-service/src/test/resources/application.yml +++ b/weather-service/src/test/resources/application.yml @@ -6,7 +6,18 @@ spring: mvc: servlet: load-on-startup: 1 # 关闭 dispatcherServlet懒加载 - thymeleaf: cache: false - encoding: UTF-8 \ No newline at end of file + encoding: UTF-8 + mail: + host: smtp.exmail.qq.com + username: shiyi@htsdfp.com + password: weatherHTFP123 + properties.mail.smtp: + auth: true + enable: true + required: true +mail: + send: + from: shiyi@htsdfp.com + to: shiyi@htsdfp.com \ No newline at end of file