package com.simuwang.daq.service; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.exceptions.ExceptionUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import com.simuwang.base.common.conts.DateConst; import com.simuwang.base.common.conts.EmailParseStatusConst; import com.simuwang.base.common.conts.NavParseStatusConst; import com.simuwang.base.common.conts.EmailTypeConst; import com.simuwang.base.common.util.EmailUtil; import com.simuwang.base.common.util.FileUtil; import com.simuwang.base.config.EmailRuleConfig; import com.simuwang.base.mapper.*; import com.simuwang.base.pojo.dos.*; import com.simuwang.base.pojo.dto.EmailContentInfoDTO; import com.simuwang.base.pojo.dto.EmailFundNavDTO; import com.simuwang.base.pojo.dto.MailboxInfoDTO; import jakarta.mail.*; import jakarta.mail.internet.MimeMultipart; import jakarta.mail.search.ComparisonTerm; import jakarta.mail.search.ReceivedDateTerm; import jakarta.mail.search.SearchTerm; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.File; import java.math.BigDecimal; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; /** * @author mozuwen * @date 2024-09-04 * @description 邮件解析服务 */ @Service public class EmailParseService { private static final Logger log = LoggerFactory.getLogger(EmailParseService.class); @Value("${email.file.path}") private String path; private final EmailTypeRuleMapper emailTypeRuleMapper; private final EmailRuleConfig emailRuleConfig; private final EmailFieldMappingMapper emailFieldMapper; private final EmailParserFactory emailParserFactory; private final EmailParseInfoMapper emailParseInfoMapper; private final EmailFileInfoMapper emailFileInfoMapper; private final EmailFundNavMapper emailFundNavMapper; private final EmailFundAssetMapper emailFundAssetMapper; private final AssetMapper assetMapper; private final NavMapper navMapper; private final FundService fundService; private final FundAliasMapper fundAliasMapper; public EmailParseService(EmailTypeRuleMapper emailTypeRuleMapper, EmailRuleConfig emailRuleConfig, EmailFieldMappingMapper emailFieldMapper, EmailParserFactory emailParserFactory, EmailParseInfoMapper emailParseInfoMapper, EmailFileInfoMapper emailFileInfoMapper, EmailFundNavMapper emailFundNavMapper, EmailFundAssetMapper emailFundAssetMapper, AssetMapper assetMapper, NavMapper navMapper, FundService fundService, FundAliasMapper fundAliasMapper) { this.emailTypeRuleMapper = emailTypeRuleMapper; this.emailRuleConfig = emailRuleConfig; this.emailFieldMapper = emailFieldMapper; this.emailParserFactory = emailParserFactory; this.emailParseInfoMapper = emailParseInfoMapper; this.emailFileInfoMapper = emailFileInfoMapper; this.emailFundNavMapper = emailFundNavMapper; this.emailFundAssetMapper = emailFundAssetMapper; this.assetMapper = assetMapper; this.navMapper = navMapper; this.fundService = fundService; this.fundAliasMapper = fundAliasMapper; } /** * 解析指定邮箱指定时间范围内的邮件 * * @param mailboxInfoDTO 邮箱配置信息 * @param startDate 邮件起始日期(yyyy-MM-dd HH:mm:ss) * @param endDate 邮件截止日期(yyyy-MM-dd HH:mm:ss, 为null,将解析邮件日期小于等于startDate的当天邮件) */ public void parseEmail(MailboxInfoDTO mailboxInfoDTO, Date startDate, Date endDate) { // 邮件类型配置 Map> emailTypeMap = getEmailType(); // 邮件字段识别映射表 Map> emailFieldMap = getEmailFieldMapping(); Map> emailContentMap; try { emailContentMap = realEmail(mailboxInfoDTO, emailTypeMap, startDate, endDate); } catch (Exception e) { log.info("采集邮件失败 -> 邮箱配置信息:{},堆栈信息:{}", mailboxInfoDTO, ExceptionUtil.stacktraceToString(e)); return; } for (Map.Entry> emailEntry : emailContentMap.entrySet()) { List emailContentInfoDTOList = emailEntry.getValue(); if (CollUtil.isEmpty(emailContentInfoDTOList)) { log.warn("未采集到正文或附件"); continue; } log.info("开始解析邮件数据 -> 邮件主题:{},邮件日期:{}", emailContentInfoDTOList.get(0).getEmailTitle(), emailContentInfoDTOList.get(0).getEmailDate()); List emailFundNavDTOList = CollUtil.newArrayList(); Map> fileNameNavMap = MapUtil.newHashMap(); for (EmailContentInfoDTO emailContentInfoDTO : emailContentInfoDTOList) { try { List fundNavDTOList = parseEmail(emailContentInfoDTO, emailFieldMap); fileNameNavMap.put(emailContentInfoDTO, fundNavDTOList); emailFundNavDTOList.addAll(fundNavDTOList); } catch (Exception e) { log.error("堆栈信息:{}", ExceptionUtil.stacktraceToString(e)); } } // 保存相关信息 -> 邮件信息表,邮件文件表,邮件净值表,邮件规模表,基金净值表 saveRelatedTable(mailboxInfoDTO.getAccount(), emailContentInfoDTOList, fileNameNavMap); } } public List parseEmail(EmailContentInfoDTO emailContentInfoDTO, Map> emailFieldMap) { Integer emailType = emailContentInfoDTO.getEmailType(); AbstractEmailParser emailParser = emailParserFactory.getInstance(emailType); return emailParser.parse(emailContentInfoDTO, emailFieldMap); } public void saveRelatedTable(String emailAddress, List emailContentInfoDTOList, Map> fileNameNavMap) { String emailTitle = CollUtil.isNotEmpty(emailContentInfoDTOList) ? emailContentInfoDTOList.get(0).getEmailTitle() : null; String emailDate = CollUtil.isNotEmpty(emailContentInfoDTOList) ? emailContentInfoDTOList.get(0).getEmailDate() : null; Integer emailType = CollUtil.isNotEmpty(emailContentInfoDTOList) ? emailContentInfoDTOList.get(0).getEmailType() : null; Integer emailId = CollUtil.isNotEmpty(emailContentInfoDTOList) ? emailContentInfoDTOList.get(0).getEmailId() : null; String senderEmail = CollUtil.isNotEmpty(emailContentInfoDTOList) ? emailContentInfoDTOList.get(0).getSenderEmail() : null; Date parseDate = new Date(); int emailParseStatus = EmailParseStatusConst.SUCCESS; EmailParseInfoDO emailParseInfoDO = buildEmailParseInfo(emailId, emailAddress, senderEmail, emailDate, emailTitle, emailType, emailParseStatus, parseDate); emailId = saveEmailParseInfo(emailParseInfoDO); for (Map.Entry> fileNameNavEntry : fileNameNavMap.entrySet()) { // 保存邮件文件表 EmailContentInfoDTO emailContentInfoDTO = fileNameNavEntry.getKey(); Integer fileId = saveEmailFileInfo(emailId, emailContentInfoDTO.getFielId(), emailContentInfoDTO.getFileName(), emailContentInfoDTO.getFilePath(), parseDate); List fundNavDTOList = fileNameNavEntry.getValue(); if (CollUtil.isEmpty(fundNavDTOList)) { continue; } for (EmailFundNavDTO fundNavDTO : fundNavDTOList) { // 设置净值数据的解析状态 setNavParseStatus(fundNavDTO, emailTitle); } // 保存净值表和规模表 saveNavAndAssetNet(fileId, fundNavDTOList, parseDate); } // 更新邮件解析结果 -> 当【净值日期】和【备案编码/基金名称】能正常解读,即识别为【成功】 long successNavCount = fileNameNavMap.values().stream().flatMap(List::stream).filter(Objects::nonNull).count(); emailParseStatus = successNavCount >= 1 ? EmailParseStatusConst.SUCCESS : EmailParseStatusConst.FAIL; emailParseInfoMapper.updateParseStatus(emailId, emailParseStatus); } private void saveNavAndAssetNet(Integer fileId, List fundNavDTOList, Date parseDate) { if (CollUtil.isEmpty(fundNavDTOList)) { return; } // 净值数据 List emailFundNavDOList = fundNavDTOList.stream() .map(e -> buildEmailFundNavDo(fileId, e, parseDate)).filter(CollUtil::isNotEmpty).flatMap(List::stream).collect(Collectors.toList()); if (CollUtil.isNotEmpty(emailFundNavDOList)) { // 先删除文件id下的净值数据(考虑到重新解析的需求,如果是首次解析,那么fiel_id下不存在净值数据) emailFundNavMapper.deleteByFileId(fileId); emailFundNavMapper.batchInsert(emailFundNavDOList); List navDOList = emailFundNavDOList.stream().filter(e -> StrUtil.isNotBlank(e.getFundId())) .map(e -> BeanUtil.copyProperties(e, NavDO.class)).collect(Collectors.toList()); saveNavDo(navDOList); } // 保存规模数据 List emailFundAssetDOList = fundNavDTOList.stream() .map(e -> buildEmailFundAssetDo(fileId, e, parseDate)).filter(CollUtil::isNotEmpty).flatMap(List::stream).collect(Collectors.toList()); if (CollUtil.isNotEmpty(emailFundAssetDOList)) { // 先删除file_id下的规模数据(考虑到重新解析的需求,如果是首次解析,那么fiel_id下不存在规模数据) emailFundAssetMapper.deleteByFileId(fileId); emailFundAssetMapper.batchInsert(emailFundAssetDOList); List assetDOList = emailFundAssetDOList.stream().filter(e -> StrUtil.isNotBlank(e.getFundId())) .map(e -> BeanUtil.copyProperties(e, AssetDO.class)).collect(Collectors.toList()); saveAssetDo(assetDOList); } } public void saveNavDo(List navDOList) { if (CollUtil.isEmpty(navDOList)) { return; } Map> fundIdNavMap = navDOList.stream().collect(Collectors.groupingBy(NavDO::getFundId)); for (Map.Entry> entry : fundIdNavMap.entrySet()) { List navDOS = entry.getValue(); List priceDateList = navDOS.stream().map(NavDO::getPriceDate).map(e -> DateUtil.format(e, DateConst.YYYY_MM_DD)).collect(Collectors.toList()); List dateList = navMapper.queryFundNavByDate(entry.getKey(), priceDateList); List updateNavDoList = navDOS.stream().filter(e -> dateList.contains(DateUtil.format(e.getPriceDate(), DateConst.YYYY_MM_DD))).collect(Collectors.toList()); List insertNavDoList = navDOS.stream().filter(e -> !dateList.contains(DateUtil.format(e.getPriceDate(), DateConst.YYYY_MM_DD))).collect(Collectors.toList()); if (CollUtil.isNotEmpty(insertNavDoList)) { navMapper.batchInsert(insertNavDoList); } if (CollUtil.isNotEmpty(updateNavDoList)) { navMapper.batchUpdate(updateNavDoList); } } } public void saveAssetDo(List assetDOList) { if (CollUtil.isEmpty(assetDOList)) { return; } Map> fundIdNavMap = assetDOList.stream().collect(Collectors.groupingBy(AssetDO::getFundId)); for (Map.Entry> entry : fundIdNavMap.entrySet()) { List assetDOS = entry.getValue(); List priceDateList = assetDOS.stream().map(AssetDO::getPriceDate).map(e -> DateUtil.format(e, DateConst.YYYY_MM_DD)).collect(Collectors.toList()); List dateList = assetMapper.queryFundNavByDate(entry.getKey(), priceDateList); List updateAssetDoList = assetDOS.stream().filter(e -> dateList.contains(DateUtil.format(e.getPriceDate(), DateConst.YYYY_MM_DD))).collect(Collectors.toList()); List insertAssetDoList = assetDOS.stream().filter(e -> !dateList.contains(DateUtil.format(e.getPriceDate(), DateConst.YYYY_MM_DD))).collect(Collectors.toList()); if (CollUtil.isNotEmpty(insertAssetDoList)) { assetMapper.batchInsert(insertAssetDoList); } if (CollUtil.isNotEmpty(updateAssetDoList)) { assetMapper.batchUpdate(updateAssetDoList); } } } private List buildEmailFundAssetDo(Integer fileId, EmailFundNavDTO fundNavDTO, Date parseDate) { List fundAssetDOList = CollUtil.newArrayList(); BigDecimal assetNet = StrUtil.isNotBlank(fundNavDTO.getAssetNet()) ? new BigDecimal(fundNavDTO.getAssetNet()) : null; BigDecimal assetShare = StrUtil.isNotBlank(fundNavDTO.getAssetShare()) ? new BigDecimal(fundNavDTO.getAssetShare()) : null; if (assetNet == null) { return fundAssetDOList; } Integer isStored = fundNavDTO.getParseStatus() != null && (fundNavDTO.getParseStatus().equals(NavParseStatusConst.ASSET_NET_NEGATIVE) || fundNavDTO.getParseStatus().equals(NavParseStatusConst.SUCCESS)) ? 1 : 0; Date priceDate = DateUtil.parse(fundNavDTO.getPriceDate(), DateConst.YYYY_MM_DD); if (CollUtil.isNotEmpty(fundNavDTO.getFundIdList())) { for (String fundId : fundNavDTO.getFundIdList()) { EmailFundAssetDO emailFundAssetDO = new EmailFundAssetDO(); emailFundAssetDO.setFileId(fileId); emailFundAssetDO.setPriceDate(priceDate); emailFundAssetDO.setFundId(fundId); emailFundAssetDO.setFundName(fundNavDTO.getFundName()); emailFundAssetDO.setRegisterNumber(fundNavDTO.getRegisterNumber()); emailFundAssetDO.setAssetNet(assetNet); emailFundAssetDO.setAssetShare(assetShare); emailFundAssetDO.setIsStored(isStored); emailFundAssetDO.setExceptionStatus(fundNavDTO.getParseStatus()); emailFundAssetDO.setIsvalid(1); emailFundAssetDO.setCreatorId(0); emailFundAssetDO.setCreateTime(parseDate); emailFundAssetDO.setUpdaterId(0); emailFundAssetDO.setUpdateTime(parseDate); fundAssetDOList.add(emailFundAssetDO); } } else { EmailFundAssetDO emailFundAssetDO = new EmailFundAssetDO(); emailFundAssetDO.setFileId(fileId); emailFundAssetDO.setPriceDate(priceDate); emailFundAssetDO.setFundName(fundNavDTO.getFundName()); emailFundAssetDO.setRegisterNumber(fundNavDTO.getRegisterNumber()); emailFundAssetDO.setAssetNet(assetNet); emailFundAssetDO.setAssetShare(assetShare); emailFundAssetDO.setIsStored(isStored); emailFundAssetDO.setExceptionStatus(fundNavDTO.getParseStatus()); emailFundAssetDO.setIsvalid(1); emailFundAssetDO.setCreatorId(0); emailFundAssetDO.setCreateTime(parseDate); emailFundAssetDO.setUpdaterId(0); emailFundAssetDO.setUpdateTime(parseDate); fundAssetDOList.add(emailFundAssetDO); } return fundAssetDOList; } private List buildEmailFundNavDo(Integer fileId, EmailFundNavDTO fundNavDTO, Date parseDate) { List fundNavDOList = CollUtil.newArrayList(); Date priceDate = DateUtil.parse(fundNavDTO.getPriceDate(), DateConst.YYYY_MM_DD); BigDecimal nav = StrUtil.isNotBlank(fundNavDTO.getNav()) ? new BigDecimal(fundNavDTO.getNav()) : null; BigDecimal cumulativeNavWithdrawal = StrUtil.isNotBlank(fundNavDTO.getCumulativeNavWithdrawal()) ? new BigDecimal(fundNavDTO.getCumulativeNavWithdrawal()) : null; if (nav == null && cumulativeNavWithdrawal == null) { return CollUtil.newArrayList(); } Integer isStored = fundNavDTO.getParseStatus() != null && !fundNavDTO.getParseStatus().equals(NavParseStatusConst.NAV_DEFICIENCY) && !fundNavDTO.getParseStatus().equals(NavParseStatusConst.NOT_MATCH) ? 1 : 0; if (CollUtil.isNotEmpty(fundNavDTO.getFundIdList())) { for (String fundId : fundNavDTO.getFundIdList()) { EmailFundNavDO emailFundNavDO = new EmailFundNavDO(); emailFundNavDO.setFileId(fileId); emailFundNavDO.setIsStored(isStored); emailFundNavDO.setPriceDate(priceDate); emailFundNavDO.setNav(nav); emailFundNavDO.setFundId(fundId); emailFundNavDO.setCumulativeNavWithdrawal(cumulativeNavWithdrawal); emailFundNavDO.setFundName(fundNavDTO.getFundName()); emailFundNavDO.setRegisterNumber(fundNavDTO.getRegisterNumber()); emailFundNavDO.setExceptionStatus(fundNavDTO.getParseStatus()); emailFundNavDO.setIsvalid(1); emailFundNavDO.setCreatorId(0); emailFundNavDO.setCreateTime(parseDate); emailFundNavDO.setUpdaterId(0); emailFundNavDO.setUpdateTime(parseDate); fundNavDOList.add(emailFundNavDO); } } else { EmailFundNavDO emailFundNavDO = new EmailFundNavDO(); emailFundNavDO.setFileId(fileId); emailFundNavDO.setPriceDate(priceDate); emailFundNavDO.setNav(nav); emailFundNavDO.setCumulativeNavWithdrawal(cumulativeNavWithdrawal); emailFundNavDO.setFundName(fundNavDTO.getFundName()); emailFundNavDO.setRegisterNumber(fundNavDTO.getRegisterNumber()); emailFundNavDO.setExceptionStatus(fundNavDTO.getParseStatus()); emailFundNavDO.setIsStored(isStored); emailFundNavDO.setIsvalid(1); emailFundNavDO.setCreatorId(0); emailFundNavDO.setCreateTime(parseDate); emailFundNavDO.setUpdaterId(0); emailFundNavDO.setUpdateTime(parseDate); fundNavDOList.add(emailFundNavDO); } return fundNavDOList; } private Integer saveEmailFileInfo(Integer emailId, Integer fileId, String fileName, String filePath, Date parseDate) { EmailFileInfoDO emailFileInfoDO = buildEmailFileInfoDO(emailId, fileId, fileName, filePath, parseDate); if (emailFileInfoDO.getId() != null) { emailFileInfoMapper.updateTimeById(fileId, parseDate); return emailFileInfoDO.getId(); } emailFileInfoMapper.insert(emailFileInfoDO); return emailFileInfoDO.getId(); } private EmailFileInfoDO buildEmailFileInfoDO(Integer emailId, Integer fileId, String fileName, String filePath, Date parseDate) { EmailFileInfoDO emailFileInfoDO = new EmailFileInfoDO(); emailFileInfoDO.setId(fileId); emailFileInfoDO.setEmailId(emailId); emailFileInfoDO.setFileName(fileName); emailFileInfoDO.setFilePath(filePath); emailFileInfoDO.setIsvalid(1); emailFileInfoDO.setCreatorId(0); emailFileInfoDO.setCreateTime(parseDate); emailFileInfoDO.setUpdaterId(0); emailFileInfoDO.setUpdateTime(parseDate); return emailFileInfoDO; } private void setNavParseStatus(EmailFundNavDTO fundNavDTO, String emailTitle) { // 1.单位净值或累计净值缺失 if (StrUtil.isBlank(fundNavDTO.getNav()) || StrUtil.isBlank(fundNavDTO.getCumulativeNavWithdrawal())) { fundNavDTO.setParseStatus(NavParseStatusConst.NAV_DEFICIENCY); return; } // 2.匹配基金(考虑到解析估值表时已经匹配上基金的情况) if (CollUtil.isEmpty(fundNavDTO.getFundIdList())) { List fundIdList = fundService.getFundIdByNamesAndCode(fundNavDTO.getFundName(), fundNavDTO.getRegisterNumber()); if (CollUtil.isEmpty(fundIdList)) { // 判断是否写入别名管理表fund_alias saveFundAlias(fundNavDTO.getFundName(), fundNavDTO.getRegisterNumber()); fundNavDTO.setParseStatus(NavParseStatusConst.NOT_MATCH); return; } fundNavDTO.setFundIdList(fundIdList); } // 考虑单独规模文件时 -> 无单位净值和累计净值 // 3.单位净值或累计净值不大于0 if (!emailTitle.contains("规模")) { if (StrUtil.isBlank(fundNavDTO.getNav()) || StrUtil.isBlank(fundNavDTO.getCumulativeNavWithdrawal()) || (fundNavDTO.getNav().compareTo("0") <= 0 || fundNavDTO.getCumulativeNavWithdrawal().compareTo("0") <= 0)) { fundNavDTO.setParseStatus(NavParseStatusConst.NAV_NEGATIVE); return; } } // 4.资产净值不大于0 if (StrUtil.isNotBlank(fundNavDTO.getAssetNet()) && fundNavDTO.getAssetNet().compareTo("0") <= 0) { fundNavDTO.setParseStatus(NavParseStatusConst.ASSET_NET_NEGATIVE); return; } fundNavDTO.setParseStatus(NavParseStatusConst.SUCCESS); } private void saveFundAlias(String fundName, String registerNumber) { // 未识别到基金名称和备案编码的数据不写入别名管理 if (StrUtil.isBlank(fundName) && StrUtil.isBlank(registerNumber)) { return; } List fundAliasDOList = CollUtil.newArrayList(); if (StrUtil.isNotBlank(fundName) && StrUtil.isNotBlank(registerNumber)) { fundAliasDOList = fundAliasMapper.queryFundByNameAndRegisterNumber(fundName, registerNumber); } if (StrUtil.isBlank(fundName) && StrUtil.isNotBlank(registerNumber)) { fundAliasDOList = fundAliasMapper.queryFundByRegisterNumber(registerNumber); } if (StrUtil.isNotBlank(fundName) && StrUtil.isBlank(registerNumber)) { fundAliasDOList = fundAliasMapper.queryFundByName(fundName); } if (CollUtil.isNotEmpty(fundAliasDOList)) { return; } log.info("写入别名表 -> 基金名称:{},备案编码:{}", fundName, registerNumber); fundAliasMapper.insert(fundName, registerNumber); } private Integer saveEmailParseInfo(EmailParseInfoDO emailParseInfoDO) { if (emailParseInfoDO == null) { return null; } // 重新邮件功能 -> 修改解析时间和更新时间 if (emailParseInfoDO.getId() != null) { emailParseInfoMapper.updateParseTime(emailParseInfoDO.getId(), emailParseInfoDO.getParseDate()); return emailParseInfoDO.getId(); } emailParseInfoMapper.insert(emailParseInfoDO); return emailParseInfoDO.getId(); } private EmailParseInfoDO buildEmailParseInfo(Integer emailId, String emailAddress, String senderEmail, String emailDate, String emailTitle, Integer emailType, Integer parseStatus, Date parseDate) { EmailParseInfoDO emailParseInfoDO = new EmailParseInfoDO(); emailParseInfoDO.setId(emailId); emailParseInfoDO.setSenderEmail(senderEmail); emailParseInfoDO.setEmail(emailAddress); emailParseInfoDO.setEmailDate(DateUtil.parse(emailDate, DateConst.YYYY_MM_DD_HH_MM_SS)); emailParseInfoDO.setParseDate(parseDate); emailParseInfoDO.setEmailTitle(emailTitle); emailParseInfoDO.setEmailType(emailType); emailParseInfoDO.setParseStatus(parseStatus); emailParseInfoDO.setIsvalid(1); emailParseInfoDO.setCreatorId(0); emailParseInfoDO.setCreateTime(parseDate); emailParseInfoDO.setUpdaterId(0); emailParseInfoDO.setUpdateTime(parseDate); return emailParseInfoDO; } public Map> getEmailFieldMapping() { List emailFieldMappingDOList = emailFieldMapper.getEmailFieldMapping(); return emailFieldMappingDOList.stream() .collect(Collectors.toMap(EmailFieldMappingDO::getCode, v -> Arrays.stream(v.getName().split(",")).toList())); } public Map> getEmailType() { Map> emailTypeMap = MapUtil.newHashMap(3, true); EmailTypeRuleDO emailTypeRuleDO = emailTypeRuleMapper.getEmailTypeRule(); String nav = emailTypeRuleDO != null && StrUtil.isNotBlank(emailTypeRuleDO.getNav()) ? emailTypeRuleDO.getNav() : emailRuleConfig.getNav(); String valuation = emailTypeRuleDO != null && StrUtil.isNotBlank(emailTypeRuleDO.getValuation()) ? emailTypeRuleDO.getValuation() : emailRuleConfig.getValuation(); String report = emailTypeRuleDO != null && StrUtil.isNotBlank(emailTypeRuleDO.getReport()) ? emailTypeRuleDO.getReport() : emailRuleConfig.getReport(); emailTypeMap.put(EmailTypeConst.VALUATION_EMAIL_TYPE, Arrays.stream(valuation.split(",")).toList()); emailTypeMap.put(EmailTypeConst.REPORT_EMAIL_TYPE, Arrays.stream(report.split(",")).toList()); emailTypeMap.put(EmailTypeConst.NAV_EMAIL_TYPE, Arrays.stream(nav.split(",")).toList()); return emailTypeMap; } /** * 读取邮件 * * @param mailboxInfoDTO 邮箱配置信息 * @param emailTypeMap 邮件类型识别规则映射表 * @param startDate 邮件起始日期 * @param endDate 邮件截止日期(为null,将解析邮件日期小于等于startDate的当天邮件) * @return 读取到的邮件信息 * @throws Exception 异常信息 */ private Map> realEmail(MailboxInfoDTO mailboxInfoDTO, Map> emailTypeMap, Date startDate, Date endDate) throws Exception { Store store = EmailUtil.getStoreNew(mailboxInfoDTO); if (store == null) { return MapUtil.newHashMap(); } // 默认读取收件箱的邮件 Folder folder = store.getFolder("INBOX"); folder.open(Folder.READ_ONLY); Message[] messages = getEmailMessage(folder, mailboxInfoDTO.getProtocol(), startDate); if (messages == null || messages.length == 0) { log.info("获取不到邮件 -> 邮箱信息:{},开始时间:{},结束时间:{}", mailboxInfoDTO, startDate, endDate); return MapUtil.newHashMap(); } Map> emailMessageMap = MapUtil.newHashMap(); for (Message message : messages) { List emailContentInfoDTOList = CollUtil.newArrayList(); String uuidKey = UUID.randomUUID().toString().replaceAll("-", ""); Integer emailType; String senderEmail; try { Date emailDate = message.getSentDate(); boolean isNotParseConditionSatisfied = emailDate == null || (endDate != null && emailDate.compareTo(endDate) > 0) || (startDate != null && emailDate.compareTo(startDate) < 0); if (isNotParseConditionSatisfied) { continue; } senderEmail = getSenderEmail(message.getFrom()); emailType = EmailUtil.getEmailTypeBySubject(message.getSubject(), emailTypeMap); String emailDateStr = DateUtil.format(emailDate, DateConst.YYYY_MM_DD_HH_MM_SS); if (emailType == null) { log.info("邮件不满足解析条件 -> 邮件主题:{},邮件日期:{}", message.getSubject(), emailDateStr); continue; } log.info("邮件采集成功 -> 邮件主题:{},邮件日期:{}", message.getSubject(), emailDateStr); Object content = message.getContent(); // 1.邮件为MIME多部分消息体:可能既有邮件又有正文 if (content instanceof MimeMultipart) { emailContentInfoDTOList = EmailUtil.collectMimeMultipart(message, mailboxInfoDTO.getAccount(), path); } // 2.邮件只有正文 if (content instanceof String) { EmailContentInfoDTO emailContentInfoDTO = new EmailContentInfoDTO(); emailContentInfoDTO.setEmailContent(content.toString()); emailContentInfoDTO.setEmailDate(emailDateStr); String fileName = message.getSubject() + DateUtil.format(emailDate, DateConst.YYYYMMDDHHMMSS24); String filePath = path + mailboxInfoDTO.getAccount() + "/" + DateUtil.format(emailDate, DateConst.YYYY_MM_DD) + "/" + fileName + ".html"; File saveFile = new File(filePath); saveFile.setReadable(true); if (!saveFile.exists()) { if (!saveFile.getParentFile().exists()) { saveFile.getParentFile().mkdirs(); saveFile.getParentFile().setExecutable(true); } } FileUtil.writeFile(filePath, content.toString()); emailContentInfoDTO.setFilePath(filePath); emailContentInfoDTOList.add(emailContentInfoDTO); } if (CollUtil.isNotEmpty(emailContentInfoDTOList)) { emailContentInfoDTOList.forEach(e -> { e.setEmailType(emailType); e.setSenderEmail(senderEmail); }); emailMessageMap.put(uuidKey, emailContentInfoDTOList); } } catch (Exception e) { log.error("获取邮箱的邮件报错,堆栈信息:{}", ExceptionUtil.stacktraceToString(e)); } } folder.close(false); store.close(); return emailMessageMap; } private String getSenderEmail(Address[] senderAddress) { if (senderAddress == null || senderAddress.length == 0) { return null; } // 此时的address是含有编码(MIME编码方式)后的文本和实际的邮件地址 String address = senderAddress[0].toString(); // 正则表达式匹配邮件地址 Pattern pattern = Pattern.compile("<(\\S+)>"); Matcher matcher = pattern.matcher(address); if (matcher.find()) { return matcher.group(1); } return null; } private Message[] getEmailMessage(Folder folder, String protocol, Date startDate) { Message[] messages; try { if (protocol.contains("imap")) { // 获取邮件日期大于等于startDate的邮件(搜索条件只支持按天) SearchTerm startDateTerm = new ReceivedDateTerm(ComparisonTerm.GE, startDate); messages = folder.search(startDateTerm); } else { messages = folder.getMessages(); } } catch (MessagingException e) { throw new RuntimeException(e); } return messages; } }