4 Комити 9ba89bf06c ... 7c4417a0c7

Аутор SHA1 Порука Датум
  wangzaijun 7c4417a0c7 fix:启动时不重刷任务 пре 5 дана
  wangzaijun 6db73de17e feat:邮件采集支持其他文件夹 пре 5 дана
  wangzaijun 24ce5962cd fix:excel пре 5 дана
  wangzaijun 4c5ddc2396 fix:现在不支持excel表格文件解析 пре 6 дана

+ 1 - 1
mo-daq/src/main/java/com/smppw/modaq/application/api/ParseApi.java

@@ -28,7 +28,7 @@ public class ParseApi {
         } else {
             preDate = DateUtil.parseDateTime(startDateTime);
         }
-        this.service.parseEmail(preDate, now);
+        this.service.parseEmail(preDate, now, null, null);
         return ResponseEntity.ok("success");
     }
 

+ 5 - 2
mo-daq/src/main/java/com/smppw/modaq/application/service/EmailParseApiService.java

@@ -3,6 +3,7 @@ package com.smppw.modaq.application.service;
 import com.smppw.modaq.domain.dto.MailboxInfoDTO;
 
 import java.util.Date;
+import java.util.List;
 
 /**
  * @author mozuwen
@@ -11,7 +12,7 @@ import java.util.Date;
  */
 public interface EmailParseApiService {
 
-    void parseEmail(Date startDate, Date endDate);
+    void parseEmail(Date startDate, Date endDate, List<String> folderNames, List<Integer> emailTypes);
 
     /**
      * 解析指定邮箱指定时间范围内的邮件
@@ -19,8 +20,10 @@ public interface EmailParseApiService {
      * @param mailboxInfoDTO 邮箱配置信息
      * @param startDate      邮件起始日期(yyyy-MM-dd HH:mm:ss)
      * @param endDate        邮件截止日期(yyyy-MM-dd HH:mm:ss, 为null,将解析邮件日期小于等于startDate的当天邮件)
+     * @param folderNames    文件夹
+     * @param emailTypes     支持的解析类型
      */
-    void parseEmail(MailboxInfoDTO mailboxInfoDTO, Date startDate, Date endDate);
+    void parseEmail(MailboxInfoDTO mailboxInfoDTO, Date startDate, Date endDate, List<String> folderNames, List<Integer> emailTypes);
 
     /**
      * 重新解析指定邮件

+ 4 - 4
mo-daq/src/main/java/com/smppw/modaq/application/service/EmailParseApiServiceImpl.java

@@ -60,7 +60,7 @@ public class EmailParseApiServiceImpl implements EmailParseApiService {
     }
 
     @Override
-    public void parseEmail(Date startDate, Date endDate) {
+    public void parseEmail(Date startDate, Date endDate, List<String> folderNames, List<Integer> emailTypes) {
         List<MailboxInfoDO> mailboxList = this.mailboxInfoMapper.listMailboxInfo();
         for (MailboxInfoDO mailbox : mailboxList) {
             MailboxInfoDTO paramDTO = new MailboxInfoDTO();
@@ -69,12 +69,12 @@ public class EmailParseApiServiceImpl implements EmailParseApiService {
             paramDTO.setPort(mailbox.getPort());
             paramDTO.setHost(mailbox.getServer());
             paramDTO.setProtocol(mailbox.getProtocol());
-            this.parseEmail(paramDTO, startDate, endDate);
+            this.parseEmail(paramDTO, startDate, endDate, folderNames, emailTypes);
         }
     }
 
     @Override
-    public void parseEmail(MailboxInfoDTO mailboxInfoDTO, Date startDate, Date endDate) {
+    public void parseEmail(MailboxInfoDTO mailboxInfoDTO, Date startDate, Date endDate, List<String> folderNames, List<Integer> emailTypes) {
 //        Integer userId = null;
 //        try{
 //            if(UserUtils.getPrincipal() == null){
@@ -98,7 +98,7 @@ public class EmailParseApiServiceImpl implements EmailParseApiService {
 ////                      endEmailTask(emailTaskInfoDO.getId(),2);
 //                }
 //        );
-        emailParseService.parseEmail(mailboxInfoDTO, startDate, endDate);
+        emailParseService.parseEmail(mailboxInfoDTO, startDate, endDate, folderNames, emailTypes);
     }
 
 //    private void endEmailTask(Integer id,Integer taskStatus) {

+ 44 - 8
mo-daq/src/main/java/com/smppw/modaq/application/task/ParseSchedulerTask.java

@@ -1,11 +1,12 @@
 package com.smppw.modaq.application.task;
 
+import cn.hutool.core.collection.ListUtil;
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.exceptions.ExceptionUtil;
 import com.smppw.modaq.application.service.EmailParseApiService;
+import com.smppw.modaq.common.conts.EmailTypeConst;
 import com.smppw.modaq.domain.entity.TaskRecordDO;
 import com.smppw.modaq.domain.service.TaskRecordService;
-import jakarta.annotation.PostConstruct;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.scheduling.annotation.EnableScheduling;
@@ -27,17 +28,18 @@ public class ParseSchedulerTask {
         this.taskRecordService = taskRecordService;
     }
 
-    @PostConstruct
-    public void executeOnStartup() {
-        this.run();
-    }
+//    @PostConstruct
+//    public void executeOnStartup() {
+//        this.letter();
+////        this.report();
+//    }
 
     /**
      * 定时任务每2小时执行一次
      */
     @Scheduled(cron = "0 0 */1 * * ?")
-    public void run() {
-        String taskKye = "mo_email_parser_task";
+    public void letter() {
+        String taskKye = "mo_email_parser_letter_task";
         TaskRecordDO task = this.taskRecordService.getTaskRecord(taskKye);
         if (task == null) {
             return;
@@ -47,7 +49,9 @@ public class ParseSchedulerTask {
         try {
             // 尽可能往前找3分钟覆盖可能遗漏的邮件
             Date startTime = DateUtil.offsetMinute(task.getStartTime(), -3);
-            this.emailParseApiService.parseEmail(startTime, now);
+            // 确认单从 INBOX 默认文件夹获取邮件
+            this.emailParseApiService.parseEmail(startTime, now,
+                    null, ListUtil.of(EmailTypeConst.REPORT_LETTER_EMAIL_TYPE));
             task.setStatus(1);
         } catch (Exception e) {
             task.setStatus(2);
@@ -61,4 +65,36 @@ public class ParseSchedulerTask {
             }
         }
     }
+
+    /**
+     * 定时任务每1小时执行一次
+     */
+    @Scheduled(cron = "0 30 */2 * * ?")
+    public void report() {
+        String taskKye = "mo_email_parser_report_task";
+        TaskRecordDO task = this.taskRecordService.getTaskRecord(taskKye);
+        if (task == null) {
+            return;
+        }
+        long start = System.currentTimeMillis();
+        Date now = new Date();
+        try {
+            // 尽可能往前找3分钟覆盖可能遗漏的邮件
+            Date startTime = DateUtil.offsetMinute(task.getStartTime(), -3);
+            // 定期报告从 我的文件夹.报告公告 文件夹获取邮件
+            this.emailParseApiService.parseEmail(startTime, now,
+                    ListUtil.of("INBOX", "其他文件夹/报告公告"), EmailTypeConst.REPORT_EMAIL_TYPES);
+            task.setStatus(1);
+        } catch (Exception e) {
+            task.setStatus(2);
+            task.setErrMsg(ExceptionUtil.stacktraceToString(e));
+            this.logger.error("定期报告解析任务{} 执行错误:{}", taskKye, ExceptionUtil.stacktraceToString(e));
+        } finally {
+            task.setEndTime(now);
+            this.taskRecordService.updateStatus(task);
+            if (this.logger.isInfoEnabled()) {
+                this.logger.info("定期报告解析任务{} 执行完成,耗时:{}ms", taskKye, System.currentTimeMillis() - start);
+            }
+        }
+    }
 }

+ 2 - 0
mo-daq/src/main/java/com/smppw/modaq/common/conts/EmailTypeConst.java

@@ -40,4 +40,6 @@ public class EmailTypeConst {
      * 所有支持解析的类型
      */
     public final static List<Integer> SUPPORT_EMAIL_TYPES = ListUtil.of(REPORT_EMAIL_TYPE, REPORT_LETTER_EMAIL_TYPE, REPORT_OTHER_TYPE, REPORT_WEEKLY_TYPE);
+
+    public final static List<Integer> REPORT_EMAIL_TYPES = ListUtil.of(REPORT_EMAIL_TYPE, REPORT_OTHER_TYPE, REPORT_WEEKLY_TYPE);
 }

+ 85 - 20
mo-daq/src/main/java/com/smppw/modaq/domain/service/EmailParseService.java

@@ -92,17 +92,21 @@ public class EmailParseService {
      * @param mailboxInfoDTO 邮箱配置信息
      * @param startDate      邮件起始日期(yyyy-MM-dd HH:mm:ss)
      * @param endDate        邮件截止日期(yyyy-MM-dd HH:mm:ss, 为null,将解析邮件日期小于等于startDate的当天邮件)
+     * @param emailTypes     当前任务支持的邮件类型,默认支持确认单
      */
-    public void parseEmail(MailboxInfoDTO mailboxInfoDTO, Date startDate, Date endDate) {
+    public void parseEmail(MailboxInfoDTO mailboxInfoDTO,
+                           Date startDate, Date endDate,
+                           List<String> folderNames, List<Integer> emailTypes) {
+        if (CollUtil.isEmpty(emailTypes)) {
+            emailTypes = ListUtil.of(EmailTypeConst.REPORT_LETTER_EMAIL_TYPE);
+        }
         log.info("开始邮件解析 -> 邮箱信息:{},开始时间:{},结束时间:{}", mailboxInfoDTO, DateUtil.format(startDate,
                 DateConst.YYYY_MM_DD_HH_MM_SS), DateUtil.format(endDate, DateConst.YYYY_MM_DD_HH_MM_SS));
         // 邮件类型配置
         Map<Integer, List<String>> emailTypeMap = getEmailType();
-        // 邮件字段识别映射表
-//        Map<String, List<String>> emailFieldMap = getEmailFieldMapping();
         Map<String, List<EmailContentInfoDTO>> emailContentMap;
         try {
-            emailContentMap = realEmail(mailboxInfoDTO, emailTypeMap, startDate, endDate);
+            emailContentMap = realEmail(mailboxInfoDTO, emailTypeMap, startDate, endDate, folderNames);
         } catch (Exception e) {
             log.error("采集邮件失败 -> 邮箱配置信息:{},堆栈信息:{}", mailboxInfoDTO, ExceptionUtil.stacktraceToString(e));
             return;
@@ -120,9 +124,7 @@ public class EmailParseService {
             }
             log.info("开始解析邮件数据 -> 邮件主题:{},邮件日期:{}", emailContentInfoDTOList.get(0).getEmailTitle(), emailContentInfoDTOList.get(0).getEmailDate());
             Map<EmailContentInfoDTO, List<EmailZipFileDTO>> emailZipFileMap = MapUtil.newHashMap();
-            Iterator<EmailContentInfoDTO> iterator = emailContentInfoDTOList.iterator();
-            while (iterator.hasNext()) {
-                EmailContentInfoDTO emailContentInfoDTO = iterator.next();
+            for (EmailContentInfoDTO emailContentInfoDTO : emailContentInfoDTOList) {
                 // 正文不用解压附件
                 if (emailContentInfoDTO.getFileName() != null && emailContentInfoDTO.getFileName().endsWith(".html")) {
                     emailZipFileMap.put(emailContentInfoDTO, ListUtil.empty());
@@ -138,11 +140,37 @@ public class EmailParseService {
                     fail.setParseStatus(EmailParseStatusConst.FAIL);
                     fail.setEmailKey(emailEntry.getKey());
                     this.emailParseInfoMapper.insert(fail);
-                    iterator.remove();
                 } catch (Exception e) {
                     log.error("堆栈信息:{}", ExceptionUtil.stacktraceToString(e));
                 }
             }
+
+            Iterator<Map.Entry<EmailContentInfoDTO, List<EmailZipFileDTO>>> entryIterator = emailZipFileMap.entrySet().iterator();
+            while (entryIterator.hasNext()) {
+                Map.Entry<EmailContentInfoDTO, List<EmailZipFileDTO>> entry = entryIterator.next();
+                EmailContentInfoDTO key = entry.getKey();
+                List<EmailZipFileDTO> dtos = entry.getValue();
+
+                List<Integer> types = ListUtil.list(false);
+                types.add(key.getEmailType());
+                if (CollUtil.isNotEmpty(dtos)) {
+                    List<Integer> list = dtos.stream().map(EmailZipFileDTO::getEmailType).distinct().toList();
+                    types.addAll(list);
+                }
+
+                boolean flag = false;
+                for (Integer type : types) {
+                    if (emailTypes.contains(type)) {
+                        flag = true;
+                        break;
+                    }
+                }
+                if (!flag) {
+                    log.warn("当前邮件{} 的类型{} 不在支持的任务类型{} 中,不用执行解析逻辑。", key, types, emailTypes);
+                    entryIterator.remove();
+                }
+            }
+
             // 保存相关信息 -> 邮件信息表,邮件文件表,邮件净值表,邮件规模表,基金净值表
             saveRelatedTable(emailEntry.getKey(), mailboxInfoDTO.getAccount(), emailZipFileMap);
             log.info("结束邮件解析 -> 邮箱信息:{},开始时间:{},结束时间:{}", mailboxInfoDTO,
@@ -485,17 +513,55 @@ public class EmailParseService {
      * @throws Exception 异常信息
      */
     private Map<String, List<EmailContentInfoDTO>> realEmail(MailboxInfoDTO mailboxInfoDTO,
-                                                             Map<Integer, List<String>> emailTypeMap, Date startDate, Date endDate) throws Exception {
+                                                             Map<Integer, List<String>> emailTypeMap,
+                                                             Date startDate, Date endDate,
+                                                             List<String> folderNames) throws Exception {
+        if (CollUtil.isEmpty(folderNames)) {
+            folderNames.add("INBOX");
+        }
         Store store = EmailUtil.getStoreNew(mailboxInfoDTO);
         if (store == null) {
-            return MapUtil.newHashMap();
+            return MapUtil.newHashMap(4);
+        }
+
+        Map<String, List<EmailContentInfoDTO>> result = MapUtil.newHashMap(128);
+        try {
+            if (log.isInfoEnabled()) {
+                Folder[] list = store.getDefaultFolder().list("*");
+                List<String> names = Arrays.stream(list).map(Folder::getFullName).toList();
+                log.info("获取所有邮箱文件夹:{}", names);
+            }
+
+            for (String folderName : folderNames) {
+                try {
+                    Map<String, List<EmailContentInfoDTO>> temp = this.getFolderEmail(mailboxInfoDTO, emailTypeMap,
+                            startDate, endDate, store, folderName);
+                    if (MapUtil.isNotEmpty(temp)) {
+                        result.putAll(temp);
+                    }
+                } catch (Exception e) {
+                    log.warn("文件夹{} 邮件获取失败:{}", folderName, ExceptionUtil.stacktraceToString(e));
+                }
+            }
+        } catch (Exception e) {
+            log.error("邮件获取失败:{}", ExceptionUtil.stacktraceToString(e));
+        } finally {
+            store.close();
         }
+
+        return result;
+    }
+
+    private Map<String, List<EmailContentInfoDTO>> getFolderEmail(MailboxInfoDTO mailboxInfoDTO,
+                                                                  Map<Integer, List<String>> emailTypeMap,
+                                                                  Date startDate, Date endDate,
+                                                                  Store store, String folderName) throws MessagingException {
         // 默认读取收件箱的邮件
-        Folder folder = store.getFolder("INBOX");
+        Folder folder = store.getFolder(folderName);
         folder.open(Folder.READ_ONLY);
         Message[] messages = getEmailMessage(folder, mailboxInfoDTO.getProtocol(), startDate);
         if (messages == null || messages.length == 0) {
-            log.warn("获取不到邮件 -> 邮箱信息:{},开始时间:{},结束时间:{}", mailboxInfoDTO, startDate, endDate);
+            log.warn("{} 获取不到邮件 -> 邮箱信息:{},开始时间:{},结束时间:{}", folderName, mailboxInfoDTO, startDate, endDate);
             return MapUtil.newHashMap();
         }
         Map<String, List<EmailContentInfoDTO>> emailMessageMap = MapUtil.newHashMap();
@@ -511,23 +577,23 @@ public class EmailParseService {
                 Date emailDate = message.getSentDate();
                 String emailDateStr = DateUtil.format(emailDate, DateConst.YYYY_MM_DD_HH_MM_SS);
                 if (log.isInfoEnabled()) {
-                    log.info("邮件{} 数据获取中,邮件时间:{}", emailTitle, emailDateStr);
+                    log.info("{} 邮件{} 数据获取中,邮件时间:{}", folderName, emailTitle, emailDateStr);
                 }
 
                 boolean isNotParseConditionSatisfied = emailDate == null
                         || (endDate != null && emailDate.compareTo(endDate) > 0)
                         || (startDate != null && emailDate.compareTo(startDate) < 0);
                 if (isNotParseConditionSatisfied) {
-                    log.warn("邮件{} 没有日期{} 或者 邮件日期不在区间内【{} ~ {}】", emailTitle, emailDate, startDate, endDate);
+                    log.warn("{} 邮件{} 没有日期{} 或者 邮件日期不在区间内【{} ~ {}】", folderName, emailTitle, emailDate, startDate, endDate);
                     continue;
                 }
                 senderEmail = getSenderEmail(message);
                 emailType = EmailUtil.getEmailTypeBySubject(emailTitle, emailTypeMap);
                 if (emailType == null) {
-                    log.warn("邮件不满足解析条件 -> 邮件主题:{},邮件日期:{}", emailTitle, emailDateStr);
+                    log.warn("{} 邮件不满足解析条件 -> 邮件主题:{},邮件日期:{}", folderName,  emailTitle, emailDateStr);
                     continue;
                 }
-                log.info("邮件{} 基本信息获取完成,开始下载附件!邮件日期:{}", emailTitle, emailDateStr);
+                log.info("{} 邮件{} 基本信息获取完成,开始下载附件!邮件日期:{}", folderName, emailTitle, emailDateStr);
                 Object content = message.getContent();
 
                 if (content instanceof Multipart multipart) {
@@ -535,7 +601,7 @@ public class EmailParseService {
                 } else if (content instanceof Part part) {
                     this.rePart(mailboxInfoDTO.getAccount(), emailTitle, emailDate, part, emailContentInfoDTOList);
                 } else {
-                    log.warn("不支持的邮件数据 {}", emailTitle);
+                    log.warn("{} 不支持的邮件数据 {}", folderName, emailTitle);
                 }
 
                 if (CollUtil.isNotEmpty(emailContentInfoDTOList)) {
@@ -546,15 +612,14 @@ public class EmailParseService {
                     emailMessageMap.put(uuidKey, emailContentInfoDTOList);
                 }
                 if (log.isInfoEnabled() && emailTitle != null) {
-                    log.info("邮件{} 下载完成,总计耗时{} ms,文件内容如下\n {}",
+                    log.info("{} 邮件{} 下载完成,总计耗时{} ms,文件内容如下\n {}", folderName,
                             emailTitle, System.currentTimeMillis() - start, emailContentInfoDTOList);
                 }
             } catch (Exception e) {
-                log.error("获取邮箱的邮件{} 报错,堆栈信息:{}", emailTitle, ExceptionUtil.stacktraceToString(e));
+                log.error("{} 获取邮箱的邮件{} 报错,堆栈信息:{}", folderName, emailTitle, ExceptionUtil.stacktraceToString(e));
             }
         }
         folder.close(false);
-        store.close();
         return emailMessageMap;
     }
 

+ 12 - 5
mo-daq/src/test/java/com/smppw/modaq/MoDaqApplicationTests.java

@@ -1,15 +1,18 @@
 package com.smppw.modaq;
 
+import cn.hutool.core.collection.ListUtil;
 import cn.hutool.core.date.DateUtil;
 import com.smppw.modaq.application.service.EmailParseApiService;
 import com.smppw.modaq.common.conts.DateConst;
-import com.smppw.modaq.domain.service.EmailParseService;
+import com.smppw.modaq.common.conts.EmailTypeConst;
 import com.smppw.modaq.domain.dto.MailboxInfoDTO;
+import com.smppw.modaq.domain.service.EmailParseService;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 
 import java.util.Date;
+import java.util.List;
 
 @SpringBootTest(classes = MoDaqApplication.class)
 public class MoDaqApplicationTests {
@@ -26,7 +29,7 @@ public class MoDaqApplicationTests {
         Date startDate = DateUtil.parse("2024-10-10 15:00:00", DateConst.YYYY_MM_DD_HH_MM_SS);
         Date endDate = DateUtil.parse("2024-10-10 16:40:00", DateConst.YYYY_MM_DD_HH_MM_SS);
         try {
-            emailParseService.parseEmail(emailInfoDTO, startDate, endDate);
+            emailParseService.parseEmail(emailInfoDTO, startDate, endDate, null, null);
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
@@ -35,10 +38,14 @@ public class MoDaqApplicationTests {
     @Test
     public void reportTest() {
         MailboxInfoDTO emailInfoDTO = this.buildMailbox("**@simuwang.com", "**");
-        Date startDate = DateUtil.parse("2025-04-24 08:40:00", DateConst.YYYY_MM_DD_HH_MM_SS);
-        Date endDate = DateUtil.parse("2025-04-24 19:42:05", DateConst.YYYY_MM_DD_HH_MM_SS);
+        Date startDate = DateUtil.parse("2025-04-29 17:00:00", DateConst.YYYY_MM_DD_HH_MM_SS);
+        Date endDate = DateUtil.parse("2025-04-29 19:42:05", DateConst.YYYY_MM_DD_HH_MM_SS);
         try {
-            emailParseService.parseEmail(emailInfoDTO, startDate, endDate);
+            List<String> folderNames = ListUtil.list(false);
+//            folderNames.add("其他文件夹/报告公告");
+            folderNames.add("INBOX");
+            emailParseService.parseEmail(emailInfoDTO, startDate, endDate,
+                    folderNames, EmailTypeConst.REPORT_EMAIL_TYPES);
         } catch (Exception e) {
             throw new RuntimeException(e);
         }