EmailParseService.java 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930
  1. package com.simuwang.daq.service;
  2. import cn.hutool.core.bean.BeanUtil;
  3. import cn.hutool.core.collection.CollUtil;
  4. import cn.hutool.core.collection.CollectionUtil;
  5. import cn.hutool.core.collection.ListUtil;
  6. import cn.hutool.core.date.DateUtil;
  7. import cn.hutool.core.exceptions.ExceptionUtil;
  8. import cn.hutool.core.map.MapUtil;
  9. import cn.hutool.core.util.StrUtil;
  10. import com.simuwang.base.common.conts.*;
  11. import com.simuwang.base.common.enums.ReportParserFileType;
  12. import com.simuwang.base.common.enums.ReportType;
  13. import com.simuwang.base.common.util.EmailUtil;
  14. import com.simuwang.base.common.util.ExcelUtil;
  15. import com.simuwang.base.common.util.FileUtil;
  16. import com.simuwang.base.config.DaqProperties;
  17. import com.simuwang.base.config.EmailRuleConfig;
  18. import com.simuwang.base.mapper.*;
  19. import com.simuwang.base.pojo.dos.*;
  20. import com.simuwang.base.pojo.dto.EmailContentInfoDTO;
  21. import com.simuwang.base.pojo.dto.EmailFundNavDTO;
  22. import com.simuwang.base.pojo.dto.MailboxInfoDTO;
  23. import com.simuwang.base.pojo.dto.report.ReportData;
  24. import com.simuwang.base.pojo.dto.report.ReportParserParams;
  25. import com.simuwang.base.pojo.valuation.CmValuationTableAttribute;
  26. import com.simuwang.daq.components.report.parser.ReportParser;
  27. import com.simuwang.daq.components.report.parser.ReportParserFactory;
  28. import com.simuwang.daq.components.report.writer.ReportWriter;
  29. import com.simuwang.daq.components.report.writer.ReportWriterFactory;
  30. import jakarta.mail.*;
  31. import jakarta.mail.internet.MimeMessage;
  32. import jakarta.mail.internet.MimeMultipart;
  33. import jakarta.mail.search.ComparisonTerm;
  34. import jakarta.mail.search.ReceivedDateTerm;
  35. import jakarta.mail.search.SearchTerm;
  36. import org.slf4j.Logger;
  37. import org.slf4j.LoggerFactory;
  38. import org.springframework.beans.factory.annotation.Autowired;
  39. import org.springframework.beans.factory.annotation.Value;
  40. import org.springframework.stereotype.Service;
  41. import org.springframework.util.StopWatch;
  42. import java.io.File;
  43. import java.math.BigDecimal;
  44. import java.util.*;
  45. import java.util.regex.Matcher;
  46. import java.util.regex.Pattern;
  47. import java.util.stream.Collectors;
  48. /**
  49. * @author mozuwen
  50. * @date 2024-09-04
  51. * @description 邮件解析服务
  52. */
  53. @Service
  54. public class EmailParseService {
  55. public static final int stepSize = 10000;
  56. private static final Logger log = LoggerFactory.getLogger(EmailParseService.class);
  57. private final EmailTypeRuleMapper emailTypeRuleMapper;
  58. private final EmailRuleConfig emailRuleConfig;
  59. private final EmailFieldMappingMapper emailFieldMapper;
  60. private final EmailParserFactory emailParserFactory;
  61. private final EmailParseInfoMapper emailParseInfoMapper;
  62. private final EmailFileInfoMapper emailFileInfoMapper;
  63. private final EmailFundNavMapper emailFundNavMapper;
  64. private final EmailFundAssetMapper emailFundAssetMapper;
  65. private final AssetMapper assetMapper;
  66. private final NavMapper navMapper;
  67. private final FundService fundService;
  68. private final FundAliasMapper fundAliasMapper;
  69. private final ValuationTableMapper valuationTableMapper;
  70. private final ValuationTableAttributeMapper valuationTableAttributeMapper;
  71. private final FundPositionDetailMapper fundPositionDetailMapper;
  72. @Value("${email.file.path}")
  73. private String path;
  74. @Autowired
  75. private DaqProperties properties;
  76. /* 报告解析和入库的方法 */
  77. @Autowired
  78. private ReportParserFactory reportParserFactory;
  79. @Autowired
  80. private ReportWriterFactory reportWriterFactory;
  81. public EmailParseService(EmailTypeRuleMapper emailTypeRuleMapper, EmailRuleConfig emailRuleConfig,
  82. EmailFieldMappingMapper emailFieldMapper, EmailParserFactory emailParserFactory,
  83. EmailParseInfoMapper emailParseInfoMapper, EmailFileInfoMapper emailFileInfoMapper,
  84. EmailFundNavMapper emailFundNavMapper, EmailFundAssetMapper emailFundAssetMapper,
  85. AssetMapper assetMapper, NavMapper navMapper, FundService fundService,
  86. FundAliasMapper fundAliasMapper,
  87. ValuationTableMapper valuationTableMapper, ValuationTableAttributeMapper valuationTableAttributeMapper,
  88. FundPositionDetailMapper fundPositionDetailMapper) {
  89. this.emailTypeRuleMapper = emailTypeRuleMapper;
  90. this.emailRuleConfig = emailRuleConfig;
  91. this.emailFieldMapper = emailFieldMapper;
  92. this.emailParserFactory = emailParserFactory;
  93. this.emailParseInfoMapper = emailParseInfoMapper;
  94. this.emailFileInfoMapper = emailFileInfoMapper;
  95. this.emailFundNavMapper = emailFundNavMapper;
  96. this.emailFundAssetMapper = emailFundAssetMapper;
  97. this.assetMapper = assetMapper;
  98. this.navMapper = navMapper;
  99. this.fundService = fundService;
  100. this.fundAliasMapper = fundAliasMapper;
  101. this.valuationTableMapper = valuationTableMapper;
  102. this.valuationTableAttributeMapper = valuationTableAttributeMapper;
  103. this.fundPositionDetailMapper = fundPositionDetailMapper;
  104. }
  105. /**
  106. * 解析指定邮箱指定时间范围内的邮件
  107. *
  108. * @param mailboxInfoDTO 邮箱配置信息
  109. * @param startDate 邮件起始日期(yyyy-MM-dd HH:mm:ss)
  110. * @param endDate 邮件截止日期(yyyy-MM-dd HH:mm:ss, 为null,将解析邮件日期小于等于startDate的当天邮件)
  111. */
  112. public void parseEmail(MailboxInfoDTO mailboxInfoDTO, Date startDate, Date endDate) {
  113. log.info("开始邮件解析 -> 邮箱信息:{},开始时间:{},结束时间:{}", mailboxInfoDTO, DateUtil.format(startDate, DateConst.YYYY_MM_DD_HH_MM_SS), DateUtil.format(endDate, DateConst.YYYY_MM_DD_HH_MM_SS));
  114. // 邮件类型配置
  115. Map<Integer, List<String>> emailTypeMap = getEmailType();
  116. // 邮件字段识别映射表
  117. Map<String, List<String>> emailFieldMap = getEmailFieldMapping();
  118. Map<String, List<EmailContentInfoDTO>> emailContentMap;
  119. try {
  120. emailContentMap = realEmail(mailboxInfoDTO, emailTypeMap, startDate, endDate);
  121. } catch (Exception e) {
  122. log.info("采集邮件失败 -> 邮箱配置信息:{},堆栈信息:{}", mailboxInfoDTO, ExceptionUtil.stacktraceToString(e));
  123. return;
  124. }
  125. if (MapUtil.isEmpty(emailContentMap)) {
  126. log.info("未采集到邮件 -> 邮箱配置信息:{},开始时间:{},结束时间:{}", mailboxInfoDTO, DateUtil.format(startDate, DateConst.YYYY_MM_DD_HH_MM_SS), DateUtil.format(endDate, DateConst.YYYY_MM_DD_HH_MM_SS));
  127. return;
  128. }
  129. for (Map.Entry<String, List<EmailContentInfoDTO>> emailEntry : emailContentMap.entrySet()) {
  130. List<EmailContentInfoDTO> emailContentInfoDTOList = emailEntry.getValue();
  131. if (CollUtil.isEmpty(emailContentInfoDTOList)) {
  132. log.warn("未采集到正文或附件");
  133. continue;
  134. }
  135. log.info("开始解析邮件数据 -> 邮件主题:{},邮件日期:{}", emailContentInfoDTOList.get(0).getEmailTitle(), emailContentInfoDTOList.get(0).getEmailDate());
  136. List<EmailFundNavDTO> emailFundNavDTOList = CollUtil.newArrayList();
  137. Map<EmailContentInfoDTO, List<EmailFundNavDTO>> fileNameNavMap = MapUtil.newHashMap();
  138. for (EmailContentInfoDTO emailContentInfoDTO : emailContentInfoDTOList) {
  139. try {
  140. List<EmailFundNavDTO> fundNavDTOList = parseEmail(emailContentInfoDTO, emailFieldMap);
  141. fileNameNavMap.put(emailContentInfoDTO, fundNavDTOList);
  142. emailFundNavDTOList.addAll(fundNavDTOList);
  143. } catch (Exception e) {
  144. log.error("堆栈信息:{}", ExceptionUtil.stacktraceToString(e));
  145. }
  146. }
  147. // 保存相关信息 -> 邮件信息表,邮件文件表,邮件净值表,邮件规模表,基金净值表
  148. saveRelatedTable(mailboxInfoDTO.getAccount(), emailContentInfoDTOList, fileNameNavMap);
  149. log.info("结束邮件解析 -> 邮箱信息:{},开始时间:{},结束时间:{}", mailboxInfoDTO, DateUtil.format(startDate, DateConst.YYYY_MM_DD_HH_MM_SS), DateUtil.format(endDate, DateConst.YYYY_MM_DD_HH_MM_SS));
  150. }
  151. }
  152. public List<EmailFundNavDTO> parseEmail(EmailContentInfoDTO emailContentInfoDTO, Map<String, List<String>> emailFieldMap) {
  153. Integer emailType = emailContentInfoDTO.getEmailType();
  154. AbstractEmailParser emailParser = emailParserFactory.getInstance(emailType);
  155. return emailParser.parse(emailContentInfoDTO, emailFieldMap);
  156. }
  157. public void saveRelatedTable(String emailAddress, List<EmailContentInfoDTO> emailContentInfoDTOList, Map<EmailContentInfoDTO, List<EmailFundNavDTO>> fileNameNavMap) {
  158. String emailTitle = CollUtil.isNotEmpty(emailContentInfoDTOList) ? emailContentInfoDTOList.get(0).getEmailTitle() : null;
  159. String emailDate = CollUtil.isNotEmpty(emailContentInfoDTOList) ? emailContentInfoDTOList.get(0).getEmailDate() : null;
  160. Integer emailType = CollUtil.isNotEmpty(emailContentInfoDTOList) ? emailContentInfoDTOList.get(0).getEmailType() : null;
  161. Integer emailId = CollUtil.isNotEmpty(emailContentInfoDTOList) ? emailContentInfoDTOList.get(0).getEmailId() : null;
  162. String senderEmail = CollUtil.isNotEmpty(emailContentInfoDTOList) ? emailContentInfoDTOList.get(0).getSenderEmail() : null;
  163. Date parseDate = new Date();
  164. int emailParseStatus = EmailParseStatusConst.SUCCESS;
  165. EmailParseInfoDO emailParseInfoDO = buildEmailParseInfo(emailId, emailAddress, senderEmail, emailDate, emailTitle, emailType, emailParseStatus, parseDate);
  166. emailId = saveEmailParseInfo(emailParseInfoDO);
  167. // python 报告解析接口结果
  168. List<ReportData> dataList = ListUtil.list(false);
  169. for (Map.Entry<EmailContentInfoDTO, List<EmailFundNavDTO>> fileNameNavEntry : fileNameNavMap.entrySet()) {
  170. // 保存邮件文件表
  171. EmailContentInfoDTO emailContentInfoDTO = fileNameNavEntry.getKey();
  172. String fileName = emailContentInfoDTO.getFileName();
  173. if (Objects.equals(EmailTypeConst.REPORT_EMAIL_TYPE, emailType) && fileName.endsWith(".html")) {
  174. continue;
  175. }
  176. Integer fileId = saveEmailFileInfo(emailId, emailContentInfoDTO.getFileId(), fileName, emailContentInfoDTO.getFilePath(), parseDate);
  177. List<EmailFundNavDTO> fundNavDTOList = fileNameNavEntry.getValue();
  178. if (CollUtil.isNotEmpty(fundNavDTOList)) {
  179. // 过滤出解析成功的数据
  180. fundNavDTOList = fundNavDTOList.stream().filter(e -> e != null && StrUtil.isBlank(e.getFailReason())).toList();
  181. }
  182. if (CollUtil.isEmpty(fundNavDTOList) && !Objects.equals(EmailTypeConst.REPORT_EMAIL_TYPE, emailType)) {
  183. continue;
  184. }
  185. if (Objects.equals(EmailTypeConst.REPORT_EMAIL_TYPE, emailType)) {
  186. // 解析结果(可以从python获取或者自行解析)并保存报告
  187. ReportData data = this.parseReportAndHandleResult(fileId, emailContentInfoDTO);
  188. if (data != null) {
  189. dataList.add(data);
  190. }
  191. }
  192. for (EmailFundNavDTO fundNavDTO : fundNavDTOList) {
  193. // 设置净值数据的解析状态
  194. setNavParseStatus(fundNavDTO, emailTitle);
  195. }
  196. // 保存净值表和规模表
  197. saveNavAndAssetNet(fileId, fundNavDTOList, parseDate);
  198. saveValuationInfo(fileId, fundNavDTOList);
  199. }
  200. // 更新邮件解析结果 -> 当【净值日期】和【备案编码/基金名称】能正常解读,即识别为【成功】
  201. long successNavCount = fileNameNavMap.values().stream().flatMap(List::stream).filter(e -> e != null && StrUtil.isBlank(e.getFailReason())).count();
  202. emailParseStatus = successNavCount >= 1 ? EmailParseStatusConst.SUCCESS : EmailParseStatusConst.FAIL;
  203. // 报告邮件有一条成功就表示整体成功
  204. if (Objects.equals(EmailTypeConst.REPORT_EMAIL_TYPE, emailType) && CollUtil.isNotEmpty(dataList)) {
  205. long count = dataList.size();
  206. emailParseStatus = count >= 1 ? EmailParseStatusConst.SUCCESS : EmailParseStatusConst.FAIL;
  207. }
  208. String failReason = null;
  209. if (emailParseStatus == EmailParseStatusConst.FAIL) {
  210. // 邮件解析失败时 -> 保存失败原因
  211. int hasPdfFile = emailContentInfoDTOList.stream().map(EmailContentInfoDTO::getFilePath).anyMatch(ExcelUtil::isPdf) ? 1 : 0;
  212. List<EmailFundNavDTO> navDTOList = fileNameNavMap.values().stream().flatMap(List::stream).toList();
  213. failReason = hasPdfFile == 1 && CollUtil.isEmpty(navDTOList) ? "无法从pdf文件中获取到数据" : navDTOList.stream().map(EmailFundNavDTO::getFailReason).distinct().collect(Collectors.joining("/"));
  214. }
  215. emailParseInfoMapper.updateParseStatus(emailId, emailParseStatus, failReason);
  216. }
  217. private void saveValuationInfo(Integer fileId, List<EmailFundNavDTO> fundNavDTOList) {
  218. if (CollUtil.isEmpty(fundNavDTOList)) {
  219. return;
  220. }
  221. valuationTableMapper.unValid(fileId);
  222. for (EmailFundNavDTO fundNavDTO : fundNavDTOList) {
  223. ValuationTableDO valuationTableDO = fundNavDTO.getValuationTableDO();
  224. if (valuationTableDO == null) {
  225. continue;
  226. }
  227. List<String> fundIdList = fundNavDTO.getFundIdList();
  228. valuationTableDO.setFileId(fileId);
  229. List<FundPositionDetailDO> fundPositionDetailDOList = fundNavDTO.getFundPositionDetailDOList();
  230. List<CmValuationTableAttribute> valuationTableAttributeList = fundNavDTO.getValuationTableAttributeList();
  231. if (CollUtil.isEmpty(fundIdList)) {
  232. valuationTableMapper.insert(valuationTableDO);
  233. int valuationId = valuationTableDO.getId();
  234. saveValuationTableAttribute(valuationId, valuationTableAttributeList);
  235. fundPositionDetailDOList.forEach(e -> e.setValuationId(valuationId));
  236. // 不匹配基金的情况下 -> 不写fund_position_detail
  237. // saveFundPositionDetail(fundPositionDetailDOList, null, fundNavDTO.getPriceDate());
  238. continue;
  239. }
  240. for (String fundId : fundIdList) {
  241. valuationTableDO.setFundId(fundId);
  242. valuationTableMapper.insert(valuationTableDO);
  243. int valuationId = valuationTableDO.getId();
  244. fundPositionDetailDOList.forEach(e -> {
  245. e.setFundId(fundId);
  246. e.setValuationId(valuationId);
  247. });
  248. saveValuationTableAttribute(valuationId, valuationTableAttributeList);
  249. saveFundPositionDetail(fundPositionDetailDOList, fundId, fundNavDTO.getPriceDate());
  250. }
  251. }
  252. }
  253. public void saveFundPositionDetail(List<FundPositionDetailDO> fundPositionDetails, String fundId, String valuationDate) {
  254. int subBegin = 0;
  255. int subEnd = stepSize;
  256. int insertNum = 0;
  257. int hasStock = 0;
  258. int hasBond = 0;
  259. int hasFuture = 0;
  260. if (CollectionUtil.isNotEmpty(fundPositionDetails)) {
  261. //插入持仓数据 cm_fund_position_detail(记录数较多,得分批)
  262. // 先删除原先的数据 然后写入数据
  263. fundPositionDetailMapper.deleteUnUsed(fundId, valuationDate);
  264. // 将最终结果写入 cm_fund_position_detail
  265. while (subBegin < fundPositionDetails.size()) {
  266. List<FundPositionDetailDO> segment = fundPositionDetails.subList(subBegin, Math.min(subEnd, fundPositionDetails.size()));
  267. for (FundPositionDetailDO fundPositionDetail : segment) {
  268. //实收信托、实收资本对应的编码为107
  269. if (StrUtil.isNotBlank(fundPositionDetail.getSecuritiesName()) && "实收信托".equals(fundPositionDetail.getSecuritiesName().trim())) {
  270. fundPositionDetail.setSubjectCode("107");
  271. fundPositionDetail.setSecuritiesCode("107");
  272. }
  273. //设置创建者id,在拿取最近修改人时有用
  274. fundPositionDetail.setCreatorId(0);
  275. if (fundPositionDetail.getLevel() != null && fundPositionDetail.getLevel() >= 4) {
  276. Integer secType = fundPositionDetail.getSecType();
  277. Integer subType = fundPositionDetail.getSubType();
  278. if (secType != null && subType != null) {
  279. if (secType == HoldingType.Stock.getId() && subType == HoldingType.Stock.getId()) {
  280. hasStock = 1;
  281. }
  282. // 正逆回购不属于债券
  283. String subjectCode = fundPositionDetail.getSubjectCode();
  284. String[] SALE_CODES = {"2202", "1202"};
  285. if (secType == HoldingType.Bond.getId() && !StrUtil.containsAny(subjectCode, SALE_CODES)) {
  286. hasBond = 1;
  287. }
  288. if (secType == HoldingType.Future.getId() || secType == HoldingType.Option.getId()) {
  289. hasFuture = 1;
  290. }
  291. }
  292. }
  293. }
  294. insertNum += fundPositionDetailMapper.insertMulti(segment);
  295. subBegin = subEnd;
  296. subEnd += stepSize;
  297. }
  298. }
  299. // 更新 cm_user_valuation_table
  300. if (insertNum > 0) {
  301. valuationTableMapper.updateUpdateAnalyzed(fundId, valuationDate, hasStock, hasBond, hasFuture);
  302. }
  303. }
  304. private void saveValuationTableAttribute(Integer valuationId, List<CmValuationTableAttribute> valuationTableAttributes) {
  305. if (valuationId == null || CollUtil.isEmpty(valuationTableAttributes)) {
  306. return;
  307. }
  308. List<ValuationTableAttributeDO> valuationTableAttributeDOList = buildValuationTableAttributeDO(valuationId, valuationTableAttributes);
  309. valuationTableAttributeMapper.deleteByValuationId(valuationId);
  310. if (CollUtil.isNotEmpty(valuationTableAttributeDOList)) {
  311. valuationTableAttributeMapper.batchInsert(valuationTableAttributeDOList);
  312. }
  313. }
  314. private List<ValuationTableAttributeDO> buildValuationTableAttributeDO(Integer valuationId, List<CmValuationTableAttribute> valuationTableAttributes) {
  315. if (CollUtil.isEmpty(valuationTableAttributes)) {
  316. return CollUtil.newArrayList();
  317. }
  318. return valuationTableAttributes.stream().map(e -> {
  319. ValuationTableAttributeDO tableAttributeDO = new ValuationTableAttributeDO();
  320. tableAttributeDO.setValuationId(valuationId);
  321. tableAttributeDO.setSubjectCode(e.getOriginalSubjectCode());
  322. tableAttributeDO.setSubjectName(e.getSubjectName());
  323. tableAttributeDO.setCurrency(e.getCurrency());
  324. tableAttributeDO.setExchangeRate(e.getExchangeRate());
  325. tableAttributeDO.setSecuritiesAmount(e.getSecuritiesAmount());
  326. tableAttributeDO.setUnitCost(e.getUnitCost());
  327. tableAttributeDO.setOriCurrencyCost(e.getOCurrencyCost());
  328. tableAttributeDO.setNetCost(e.getNetCost());
  329. tableAttributeDO.setNetCostRatio(e.getNetCostRatio());
  330. tableAttributeDO.setMarketPrice(e.getMarketPrice());
  331. tableAttributeDO.setOriCurrencyMarketValue(e.getOCurrencyMarketValue());
  332. tableAttributeDO.setMarketValue(e.getMarketValue());
  333. tableAttributeDO.setMarketValueRatio(e.getMarketValueRatio());
  334. tableAttributeDO.setOriCurrencyIncrement(e.getOCurrencyMarketValue());
  335. tableAttributeDO.setIncrement(e.getIncrement());
  336. tableAttributeDO.setHaltInfo(e.getHaltInfo());
  337. tableAttributeDO.setRightsInterestsInfo(e.getRightsInterestsInfo());
  338. tableAttributeDO.setIsvalid(1);
  339. tableAttributeDO.setCreatorId(0);
  340. tableAttributeDO.setUpdaterId(0);
  341. tableAttributeDO.setCreateTime(new Date());
  342. tableAttributeDO.setUpdateTime(new Date());
  343. return tableAttributeDO;
  344. }).collect(Collectors.toList());
  345. }
  346. private ReportData parseReportAndHandleResult(int fileId, EmailContentInfoDTO emailContentInfoDTO) {
  347. String fileName = emailContentInfoDTO.getFileName();
  348. Integer emailType = emailContentInfoDTO.getEmailType();
  349. if (!Objects.equals(EmailTypeConst.REPORT_EMAIL_TYPE, emailType) || StrUtil.isBlank(fileName)) {
  350. return null;
  351. }
  352. Pattern pattern = Pattern.compile("S(?:[A-Z]{0}[0-9]{5}|[A-Z][0-9]{4}|[A-Z]{2}[0-9]{3}|[A-Z]{3}[0-9]{2})");
  353. Matcher matcher = pattern.matcher(fileName);
  354. String registerNumber = null;
  355. if (matcher.find()) {
  356. registerNumber = matcher.group();
  357. }
  358. // 类型识别---先识别季度报告,没有季度再识别年度报告,最后识别月报
  359. ReportType reportType = ReportType.MONTHLY;
  360. if (StrUtil.containsAny(fileName, ReportType.QUARTERLY.getPatterns())) {
  361. reportType = ReportType.QUARTERLY;
  362. } else if (StrUtil.containsAny(fileName, ReportType.ANNUALLY.getPatterns())) {
  363. reportType = ReportType.ANNUALLY;
  364. }
  365. // 解析器--如果开启python解析则直接调用python接口,否则根据文件后缀获取对应解析器
  366. ReportParserFileType fileType;
  367. if (Objects.equals(Boolean.TRUE, this.properties.getEnablePyParser())) {
  368. fileType = ReportParserFileType.PYTHON;
  369. } else {
  370. String fileSuffix = StrUtil.subAfter(fileName, ".", true);
  371. fileType = ReportParserFileType.getBySuffix(fileSuffix);
  372. }
  373. // 解析报告
  374. ReportParserParams params = null;
  375. ReportData reportData = null;
  376. StopWatch parserWatch = new StopWatch();
  377. parserWatch.start();
  378. try {
  379. params = ReportParserParams.builder().fileId(fileId).filename(fileName)
  380. .filepath(emailContentInfoDTO.getFilePath()).registerNumber(registerNumber).build();
  381. ReportParser<ReportData> instance = this.reportParserFactory.getInstance(reportType, fileType);
  382. reportData = instance.parse(params);
  383. } catch (Exception e) {
  384. log.error("报告{}解析失败\n{}", params, ExceptionUtil.stacktraceToString(e));
  385. } finally {
  386. parserWatch.stop();
  387. if (log.isInfoEnabled()) {
  388. log.info("报告{}解析结果为{},耗时{}ms", params, reportData, parserWatch.getTotalTimeMillis());
  389. }
  390. }
  391. // 保存报告解析结果
  392. if (reportData != null) {
  393. StopWatch writeWatch = new StopWatch();
  394. writeWatch.start();
  395. try {
  396. ReportWriter<ReportData> instance = this.reportWriterFactory.getInstance(reportType);
  397. instance.write(reportData);
  398. } catch (Exception e) {
  399. log.error("报告{}结果保存失败\n{}", params, ExceptionUtil.stacktraceToString(e));
  400. } finally {
  401. writeWatch.stop();
  402. if (log.isInfoEnabled()) {
  403. log.info("报告{}解析结果保存完成,耗时{}ms", params, writeWatch.getTotalTimeMillis());
  404. }
  405. }
  406. }
  407. return reportData;
  408. }
  409. private void saveNavAndAssetNet(Integer fileId, List<EmailFundNavDTO> fundNavDTOList, Date parseDate) {
  410. if (CollUtil.isEmpty(fundNavDTOList)) {
  411. return;
  412. }
  413. // 净值数据
  414. List<EmailFundNavDO> emailFundNavDOList = fundNavDTOList.stream()
  415. .map(e -> buildEmailFundNavDo(fileId, e, parseDate)).filter(CollUtil::isNotEmpty).flatMap(List::stream).collect(Collectors.toList());
  416. if (CollUtil.isNotEmpty(emailFundNavDOList)) {
  417. // 先删除文件id下的净值数据(考虑到重新解析的需求,如果是首次解析,那么file_id下不存在净值数据)
  418. emailFundNavMapper.deleteByFileId(fileId);
  419. emailFundNavMapper.batchInsert(emailFundNavDOList);
  420. List<NavDO> navDOList = emailFundNavDOList.stream().filter(e -> StrUtil.isNotBlank(e.getFundId()))
  421. .map(e -> BeanUtil.copyProperties(e, NavDO.class)).collect(Collectors.toList());
  422. saveNavDo(navDOList);
  423. }
  424. // 保存规模数据
  425. List<EmailFundAssetDO> emailFundAssetDOList = fundNavDTOList.stream()
  426. .map(e -> buildEmailFundAssetDo(fileId, e, parseDate)).filter(CollUtil::isNotEmpty).flatMap(List::stream).collect(Collectors.toList());
  427. if (CollUtil.isNotEmpty(emailFundAssetDOList)) {
  428. // 先删除file_id下的规模数据(考虑到重新解析的需求,如果是首次解析,那么file_id下不存在规模数据)
  429. emailFundAssetMapper.deleteByFileId(fileId);
  430. emailFundAssetMapper.batchInsert(emailFundAssetDOList);
  431. List<AssetDO> assetDOList = emailFundAssetDOList.stream().filter(e -> StrUtil.isNotBlank(e.getFundId()))
  432. .map(e -> BeanUtil.copyProperties(e, AssetDO.class)).collect(Collectors.toList());
  433. saveAssetDo(assetDOList);
  434. }
  435. }
  436. public void saveNavDo(List<NavDO> navDOList) {
  437. if (CollUtil.isEmpty(navDOList)) {
  438. return;
  439. }
  440. Map<String, List<NavDO>> fundIdNavMap = navDOList.stream().collect(Collectors.groupingBy(NavDO::getFundId));
  441. for (Map.Entry<String, List<NavDO>> entry : fundIdNavMap.entrySet()) {
  442. List<NavDO> navDOS = entry.getValue();
  443. List<String> priceDateList = navDOS.stream().map(NavDO::getPriceDate).map(e -> DateUtil.format(e, DateConst.YYYY_MM_DD)).collect(Collectors.toList());
  444. List<String> dateList = navMapper.queryFundNavByDate(entry.getKey(), priceDateList);
  445. List<NavDO> updateNavDoList = navDOS.stream().filter(e -> dateList.contains(DateUtil.format(e.getPriceDate(), DateConst.YYYY_MM_DD))).collect(Collectors.toList());
  446. List<NavDO> insertNavDoList = navDOS.stream().filter(e -> !dateList.contains(DateUtil.format(e.getPriceDate(), DateConst.YYYY_MM_DD))).collect(Collectors.toList());
  447. if (CollUtil.isNotEmpty(insertNavDoList)) {
  448. Map<Date, List<NavDO>> priceDateNavDoListMap = insertNavDoList.stream().collect(Collectors.groupingBy(NavDO::getPriceDate));
  449. boolean hasDuplicationDateData = priceDateNavDoListMap.values().stream().map(List::size).anyMatch(e -> e > 1);
  450. if (!hasDuplicationDateData) {
  451. navMapper.batchInsert(insertNavDoList);
  452. }
  453. // 要插入的数据中存在相同日期的数据 -> 只能一条一条的插入数据了
  454. insertNavDoList.forEach(e -> saveNavDo(ListUtil.toList(e)));
  455. }
  456. if (CollUtil.isNotEmpty(updateNavDoList)) {
  457. navMapper.batchUpdate(updateNavDoList);
  458. }
  459. }
  460. }
  461. public void saveAssetDo(List<AssetDO> assetDOList) {
  462. if (CollUtil.isEmpty(assetDOList)) {
  463. return;
  464. }
  465. Map<String, List<AssetDO>> fundIdNavMap = assetDOList.stream().collect(Collectors.groupingBy(AssetDO::getFundId));
  466. for (Map.Entry<String, List<AssetDO>> entry : fundIdNavMap.entrySet()) {
  467. List<AssetDO> assetDOS = entry.getValue();
  468. List<String> priceDateList = assetDOS.stream().map(AssetDO::getPriceDate).map(e -> DateUtil.format(e, DateConst.YYYY_MM_DD)).collect(Collectors.toList());
  469. List<String> dateList = assetMapper.queryFundNavByDate(entry.getKey(), priceDateList);
  470. List<AssetDO> updateAssetDoList = assetDOS.stream().filter(e -> dateList.contains(DateUtil.format(e.getPriceDate(), DateConst.YYYY_MM_DD))).collect(Collectors.toList());
  471. List<AssetDO> insertAssetDoList = assetDOS.stream().filter(e -> !dateList.contains(DateUtil.format(e.getPriceDate(), DateConst.YYYY_MM_DD))).collect(Collectors.toList());
  472. if (CollUtil.isNotEmpty(insertAssetDoList)) {
  473. Map<Date, List<AssetDO>> priceDateAssetDoListMap = insertAssetDoList.stream().collect(Collectors.groupingBy(AssetDO::getPriceDate));
  474. boolean hasDuplicationDateData = priceDateAssetDoListMap.values().stream().map(List::size).anyMatch(e -> e > 1);
  475. if (!hasDuplicationDateData) {
  476. assetMapper.batchInsert(insertAssetDoList);
  477. }
  478. // 要插入的数据中存在相同日期的数据 -> 只能一条一条的插入数据了
  479. insertAssetDoList.forEach(e -> saveAssetDo(ListUtil.toList(e)));
  480. }
  481. if (CollUtil.isNotEmpty(updateAssetDoList)) {
  482. assetMapper.batchUpdate(updateAssetDoList);
  483. }
  484. }
  485. }
  486. private List<EmailFundAssetDO> buildEmailFundAssetDo(Integer fileId, EmailFundNavDTO fundNavDTO, Date parseDate) {
  487. List<EmailFundAssetDO> fundAssetDOList = CollUtil.newArrayList();
  488. BigDecimal assetNet = StrUtil.isNotBlank(fundNavDTO.getAssetNet()) ? new BigDecimal(fundNavDTO.getAssetNet()) : null;
  489. BigDecimal assetShare = StrUtil.isNotBlank(fundNavDTO.getAssetShare()) ? new BigDecimal(fundNavDTO.getAssetShare()) : null;
  490. if (assetNet == null) {
  491. return fundAssetDOList;
  492. }
  493. Integer isStored = fundNavDTO.getParseStatus() != null
  494. && (fundNavDTO.getParseStatus().equals(NavParseStatusConst.ASSET_NET_NEGATIVE) || fundNavDTO.getParseStatus().equals(NavParseStatusConst.SUCCESS)) ? 1 : 0;
  495. Date priceDate = DateUtil.parse(fundNavDTO.getPriceDate(), DateConst.YYYY_MM_DD);
  496. if (CollUtil.isNotEmpty(fundNavDTO.getFundIdList())) {
  497. for (String fundId : fundNavDTO.getFundIdList()) {
  498. EmailFundAssetDO emailFundAssetDO = new EmailFundAssetDO();
  499. emailFundAssetDO.setFileId(fileId);
  500. emailFundAssetDO.setPriceDate(priceDate);
  501. emailFundAssetDO.setFundId(fundId);
  502. emailFundAssetDO.setFundName(fundNavDTO.getFundName());
  503. emailFundAssetDO.setRegisterNumber(fundNavDTO.getRegisterNumber());
  504. emailFundAssetDO.setAssetNet(assetNet);
  505. emailFundAssetDO.setAssetShare(assetShare);
  506. emailFundAssetDO.setIsStored(isStored);
  507. emailFundAssetDO.setExceptionStatus(fundNavDTO.getParseStatus());
  508. emailFundAssetDO.setIsvalid(1);
  509. emailFundAssetDO.setCreatorId(0);
  510. emailFundAssetDO.setCreateTime(parseDate);
  511. emailFundAssetDO.setUpdaterId(0);
  512. emailFundAssetDO.setUpdateTime(parseDate);
  513. fundAssetDOList.add(emailFundAssetDO);
  514. }
  515. } else {
  516. EmailFundAssetDO emailFundAssetDO = new EmailFundAssetDO();
  517. emailFundAssetDO.setFileId(fileId);
  518. emailFundAssetDO.setPriceDate(priceDate);
  519. emailFundAssetDO.setFundName(fundNavDTO.getFundName());
  520. emailFundAssetDO.setRegisterNumber(fundNavDTO.getRegisterNumber());
  521. emailFundAssetDO.setAssetNet(assetNet);
  522. emailFundAssetDO.setAssetShare(assetShare);
  523. emailFundAssetDO.setIsStored(isStored);
  524. emailFundAssetDO.setExceptionStatus(fundNavDTO.getParseStatus());
  525. emailFundAssetDO.setIsvalid(1);
  526. emailFundAssetDO.setCreatorId(0);
  527. emailFundAssetDO.setCreateTime(parseDate);
  528. emailFundAssetDO.setUpdaterId(0);
  529. emailFundAssetDO.setUpdateTime(parseDate);
  530. fundAssetDOList.add(emailFundAssetDO);
  531. }
  532. return fundAssetDOList;
  533. }
  534. private List<EmailFundNavDO> buildEmailFundNavDo(Integer fileId, EmailFundNavDTO fundNavDTO, Date parseDate) {
  535. List<EmailFundNavDO> fundNavDOList = CollUtil.newArrayList();
  536. Date priceDate = DateUtil.parse(fundNavDTO.getPriceDate(), DateConst.YYYY_MM_DD);
  537. BigDecimal nav = StrUtil.isNotBlank(fundNavDTO.getNav()) ? new BigDecimal(fundNavDTO.getNav()) : null;
  538. BigDecimal cumulativeNavWithdrawal = StrUtil.isNotBlank(fundNavDTO.getCumulativeNavWithdrawal()) ? new BigDecimal(fundNavDTO.getCumulativeNavWithdrawal()) : null;
  539. Integer isStored = fundNavDTO.getParseStatus() != null && !fundNavDTO.getParseStatus().equals(NavParseStatusConst.NAV_DEFICIENCY)
  540. && !fundNavDTO.getParseStatus().equals(NavParseStatusConst.NOT_MATCH) ? 1 : 0;
  541. if (CollUtil.isNotEmpty(fundNavDTO.getFundIdList())) {
  542. for (String fundId : fundNavDTO.getFundIdList()) {
  543. EmailFundNavDO emailFundNavDO = new EmailFundNavDO();
  544. emailFundNavDO.setFileId(fileId);
  545. emailFundNavDO.setIsStored(isStored);
  546. emailFundNavDO.setPriceDate(priceDate);
  547. emailFundNavDO.setNav(nav);
  548. emailFundNavDO.setFundId(fundId);
  549. emailFundNavDO.setCumulativeNavWithdrawal(cumulativeNavWithdrawal);
  550. emailFundNavDO.setFundName(fundNavDTO.getFundName());
  551. emailFundNavDO.setRegisterNumber(fundNavDTO.getRegisterNumber());
  552. emailFundNavDO.setExceptionStatus(fundNavDTO.getParseStatus());
  553. emailFundNavDO.setTemplateId(fundNavDTO.getTemplateId());
  554. emailFundNavDO.setIsvalid(1);
  555. emailFundNavDO.setCreatorId(0);
  556. emailFundNavDO.setCreateTime(parseDate);
  557. emailFundNavDO.setUpdaterId(0);
  558. emailFundNavDO.setUpdateTime(parseDate);
  559. fundNavDOList.add(emailFundNavDO);
  560. }
  561. } else {
  562. EmailFundNavDO emailFundNavDO = new EmailFundNavDO();
  563. emailFundNavDO.setFileId(fileId);
  564. emailFundNavDO.setPriceDate(priceDate);
  565. emailFundNavDO.setNav(nav);
  566. emailFundNavDO.setCumulativeNavWithdrawal(cumulativeNavWithdrawal);
  567. emailFundNavDO.setFundName(fundNavDTO.getFundName());
  568. emailFundNavDO.setRegisterNumber(fundNavDTO.getRegisterNumber());
  569. emailFundNavDO.setExceptionStatus(fundNavDTO.getParseStatus());
  570. emailFundNavDO.setTemplateId(fundNavDTO.getTemplateId());
  571. emailFundNavDO.setIsStored(isStored);
  572. emailFundNavDO.setIsvalid(1);
  573. emailFundNavDO.setCreatorId(0);
  574. emailFundNavDO.setCreateTime(parseDate);
  575. emailFundNavDO.setUpdaterId(0);
  576. emailFundNavDO.setUpdateTime(parseDate);
  577. fundNavDOList.add(emailFundNavDO);
  578. }
  579. return fundNavDOList;
  580. }
  581. private Integer saveEmailFileInfo(Integer emailId, Integer fileId, String fileName, String filePath, Date parseDate) {
  582. EmailFileInfoDO emailFileInfoDO = buildEmailFileInfoDO(emailId, fileId, fileName, filePath, parseDate);
  583. if (emailFileInfoDO.getId() != null) {
  584. emailFileInfoMapper.updateTimeById(fileId, parseDate);
  585. return emailFileInfoDO.getId();
  586. }
  587. emailFileInfoMapper.insert(emailFileInfoDO);
  588. return emailFileInfoDO.getId();
  589. }
  590. private EmailFileInfoDO buildEmailFileInfoDO(Integer emailId, Integer fileId, String fileName, String filePath, Date parseDate) {
  591. EmailFileInfoDO emailFileInfoDO = new EmailFileInfoDO();
  592. emailFileInfoDO.setId(fileId);
  593. emailFileInfoDO.setEmailId(emailId);
  594. emailFileInfoDO.setFileName(fileName);
  595. emailFileInfoDO.setFilePath(filePath);
  596. emailFileInfoDO.setIsvalid(1);
  597. emailFileInfoDO.setCreatorId(0);
  598. emailFileInfoDO.setCreateTime(parseDate);
  599. emailFileInfoDO.setUpdaterId(0);
  600. emailFileInfoDO.setUpdateTime(parseDate);
  601. return emailFileInfoDO;
  602. }
  603. private void setNavParseStatus(EmailFundNavDTO fundNavDTO, String emailTitle) {
  604. // 1.单位净值或累计净值缺失
  605. if (StrUtil.isBlank(fundNavDTO.getNav()) || StrUtil.isBlank(fundNavDTO.getCumulativeNavWithdrawal())) {
  606. fundNavDTO.setParseStatus(NavParseStatusConst.NAV_DEFICIENCY);
  607. return;
  608. }
  609. // 考虑单独规模文件时 -> 无单位净值和累计净值
  610. // 2.单位净值或累计净值不大于0
  611. if (!emailTitle.contains("规模")) {
  612. if (StrUtil.isBlank(fundNavDTO.getNav()) || StrUtil.isBlank(fundNavDTO.getCumulativeNavWithdrawal())
  613. || (fundNavDTO.getNav().compareTo("0") <= 0 || fundNavDTO.getCumulativeNavWithdrawal().compareTo("0") <= 0)) {
  614. fundNavDTO.setParseStatus(NavParseStatusConst.NAV_NEGATIVE);
  615. return;
  616. }
  617. }
  618. // 3.资产净值不大于0
  619. if (StrUtil.isNotBlank(fundNavDTO.getAssetNet()) && fundNavDTO.getAssetNet().compareTo("0") <= 0) {
  620. fundNavDTO.setParseStatus(NavParseStatusConst.ASSET_NET_NEGATIVE);
  621. return;
  622. }
  623. // 4.匹配基金(考虑到解析估值表时已经匹配上基金的情况)
  624. List<String> fundIdList = fundNavDTO.getFundIdList();
  625. if (CollUtil.isEmpty(fundIdList)) {
  626. fundIdList = fundService.getFundIdByNamesAndCode(fundNavDTO.getFundName(), fundNavDTO.getRegisterNumber());
  627. if (CollUtil.isEmpty(fundIdList)) {
  628. fundNavDTO.setParseStatus(NavParseStatusConst.NOT_MATCH);
  629. }
  630. }
  631. fundNavDTO.setFundIdList(fundIdList);
  632. // 写入别名管理表fund_alias
  633. saveFundAlias(fundNavDTO.getFundName(), fundNavDTO.getRegisterNumber(), fundIdList);
  634. if (CollUtil.isEmpty(fundIdList)) {
  635. return;
  636. }
  637. fundNavDTO.setParseStatus(NavParseStatusConst.SUCCESS);
  638. }
  639. private void saveFundAlias(String fundName, String registerNumber, List<String> fundIdList) {
  640. // 未识别到基金名称和备案编码的数据不写入别名管理
  641. if (StrUtil.isBlank(fundName) && StrUtil.isBlank(registerNumber)) {
  642. return;
  643. }
  644. List<FundAliasDO> fundAliasDOList = CollUtil.newArrayList();
  645. if (StrUtil.isNotBlank(fundName) && StrUtil.isNotBlank(registerNumber)) {
  646. fundAliasDOList = fundAliasMapper.queryFundIdByNameAndRegisterNumber(fundName, registerNumber);
  647. }
  648. if (StrUtil.isBlank(fundName) && StrUtil.isNotBlank(registerNumber)) {
  649. fundAliasDOList = fundAliasMapper.queryFundIdByRegisterNumber(registerNumber);
  650. }
  651. if (StrUtil.isNotBlank(fundName) && StrUtil.isBlank(registerNumber)) {
  652. fundAliasDOList = fundAliasMapper.queryFundIdByName(fundName);
  653. }
  654. // 未匹配基金且已写入别名表
  655. if (CollUtil.isEmpty(fundIdList) && CollUtil.isNotEmpty(fundAliasDOList)) {
  656. return;
  657. }
  658. // 未匹配基金且未写入别名表
  659. if (CollUtil.isEmpty(fundIdList) && CollUtil.isEmpty(fundAliasDOList)) {
  660. fundAliasMapper.batchInsert(ListUtil.toList(buildFundAliasDO(fundName, registerNumber, null)));
  661. log.info("写入别名表 -> 基金名称:{},备案编码:{},基金id:{}", fundName, registerNumber, fundIdList);
  662. return;
  663. }
  664. // 匹配上基金 -> 需要判断此时基金id是否已经在别名管理表
  665. if (CollUtil.isNotEmpty(fundAliasDOList)) {
  666. List<String> collect = fundAliasDOList.stream().filter(Objects::nonNull).map(FundAliasDO::getTargetFundId).filter(Objects::nonNull).distinct().toList();
  667. fundIdList = fundIdList.stream().filter(e -> !collect.contains(e)).toList();
  668. }
  669. List<FundAliasDO> fundAliasDOS = CollUtil.isNotEmpty(fundIdList) ? fundIdList.stream().map(e -> buildFundAliasDO(fundName, registerNumber, e)).toList() : null;
  670. if (CollUtil.isNotEmpty(fundAliasDOS)) {
  671. fundAliasMapper.batchInsert(fundAliasDOS);
  672. log.info("写入别名表 -> 基金名称:{},备案编码:{},基金id:{}", fundName, registerNumber, fundIdList);
  673. }
  674. }
  675. public FundAliasDO buildFundAliasDO(String fundName, String registerNumber, String fundId) {
  676. FundAliasDO fundAliasDO = new FundAliasDO();
  677. fundAliasDO.setTargetFundId(fundId);
  678. fundAliasDO.setSourceFundName(fundName);
  679. fundAliasDO.setSourceRegisterNumber(registerNumber);
  680. fundAliasDO.setIsvalid(1);
  681. fundAliasDO.setCreatorId(0);
  682. fundAliasDO.setCreateTime(new Date());
  683. fundAliasDO.setUpdateTime(new Date());
  684. fundAliasDO.setUpdaterId(0);
  685. return fundAliasDO;
  686. }
  687. private Integer saveEmailParseInfo(EmailParseInfoDO emailParseInfoDO) {
  688. if (emailParseInfoDO == null) {
  689. return null;
  690. }
  691. // 重新邮件功能 -> 修改解析时间和更新时间
  692. if (emailParseInfoDO.getId() != null) {
  693. emailParseInfoMapper.updateParseTime(emailParseInfoDO.getId(), emailParseInfoDO.getParseDate());
  694. return emailParseInfoDO.getId();
  695. }
  696. emailParseInfoMapper.insert(emailParseInfoDO);
  697. return emailParseInfoDO.getId();
  698. }
  699. private EmailParseInfoDO buildEmailParseInfo(Integer emailId, String emailAddress, String senderEmail, String emailDate,
  700. String emailTitle, Integer emailType, Integer parseStatus, Date parseDate) {
  701. EmailParseInfoDO emailParseInfoDO = new EmailParseInfoDO();
  702. emailParseInfoDO.setId(emailId);
  703. emailParseInfoDO.setSenderEmail(senderEmail);
  704. emailParseInfoDO.setEmail(emailAddress);
  705. emailParseInfoDO.setEmailDate(DateUtil.parse(emailDate, DateConst.YYYY_MM_DD_HH_MM_SS));
  706. emailParseInfoDO.setParseDate(parseDate);
  707. emailParseInfoDO.setEmailTitle(emailTitle);
  708. emailParseInfoDO.setEmailType(emailType);
  709. emailParseInfoDO.setParseStatus(parseStatus);
  710. emailParseInfoDO.setIsvalid(1);
  711. emailParseInfoDO.setCreatorId(0);
  712. emailParseInfoDO.setCreateTime(parseDate);
  713. emailParseInfoDO.setUpdaterId(0);
  714. emailParseInfoDO.setUpdateTime(parseDate);
  715. return emailParseInfoDO;
  716. }
  717. public Map<String, List<String>> getEmailFieldMapping() {
  718. List<EmailFieldMappingDO> emailFieldMappingDOList = emailFieldMapper.getEmailFieldMapping(1);
  719. return emailFieldMappingDOList.stream()
  720. .collect(Collectors.toMap(EmailFieldMappingDO::getCode, v -> Arrays.stream(v.getName().split(",")).toList()));
  721. }
  722. public Map<Integer, List<String>> getEmailType() {
  723. Map<Integer, List<String>> emailTypeMap = MapUtil.newHashMap(3, true);
  724. EmailTypeRuleDO emailTypeRuleDO = emailTypeRuleMapper.getEmailTypeRule();
  725. String nav = emailTypeRuleDO != null && StrUtil.isNotBlank(emailTypeRuleDO.getNav()) ? emailTypeRuleDO.getNav() : emailRuleConfig.getNav();
  726. String valuation = emailTypeRuleDO != null && StrUtil.isNotBlank(emailTypeRuleDO.getValuation()) ? emailTypeRuleDO.getValuation() : emailRuleConfig.getValuation();
  727. String report = emailTypeRuleDO != null && StrUtil.isNotBlank(emailTypeRuleDO.getReport()) ? emailTypeRuleDO.getReport() : emailRuleConfig.getReport();
  728. emailTypeMap.put(EmailTypeConst.VALUATION_EMAIL_TYPE, Arrays.stream(valuation.split(",")).toList());
  729. emailTypeMap.put(EmailTypeConst.REPORT_EMAIL_TYPE, Arrays.stream(report.split(",")).toList());
  730. emailTypeMap.put(EmailTypeConst.NAV_EMAIL_TYPE, Arrays.stream(nav.split(",")).toList());
  731. return emailTypeMap;
  732. }
  733. /**
  734. * 读取邮件
  735. *
  736. * @param mailboxInfoDTO 邮箱配置信息
  737. * @param emailTypeMap 邮件类型识别规则映射表
  738. * @param startDate 邮件起始日期
  739. * @param endDate 邮件截止日期(为null,将解析邮件日期小于等于startDate的当天邮件)
  740. * @return 读取到的邮件信息
  741. * @throws Exception 异常信息
  742. */
  743. private Map<String, List<EmailContentInfoDTO>> realEmail(MailboxInfoDTO mailboxInfoDTO, Map<Integer, List<String>> emailTypeMap, Date startDate, Date endDate) throws Exception {
  744. Store store = EmailUtil.getStoreNew(mailboxInfoDTO);
  745. if (store == null) {
  746. return MapUtil.newHashMap();
  747. }
  748. // 默认读取收件箱的邮件
  749. Folder folder = store.getFolder("INBOX");
  750. folder.open(Folder.READ_ONLY);
  751. Message[] messages = getEmailMessage(folder, mailboxInfoDTO.getProtocol(), startDate);
  752. if (messages == null || messages.length == 0) {
  753. log.info("获取不到邮件 -> 邮箱信息:{},开始时间:{},结束时间:{}", mailboxInfoDTO, startDate, endDate);
  754. return MapUtil.newHashMap();
  755. }
  756. Map<String, List<EmailContentInfoDTO>> emailMessageMap = MapUtil.newHashMap();
  757. for (Message message1 : messages) {
  758. MimeMessage message = (MimeMessage) message1;
  759. List<EmailContentInfoDTO> emailContentInfoDTOList = CollUtil.newArrayList();
  760. String uuidKey = UUID.randomUUID().toString().replaceAll("-", "");
  761. Integer emailType;
  762. String senderEmail;
  763. try {
  764. Date emailDate = message.getSentDate();
  765. boolean isNotParseConditionSatisfied = emailDate == null || (endDate != null && emailDate.compareTo(endDate) > 0) || (startDate != null && emailDate.compareTo(startDate) < 0);
  766. if (isNotParseConditionSatisfied) {
  767. continue;
  768. }
  769. senderEmail = getSenderEmail(message.getFrom());
  770. emailType = EmailUtil.getEmailTypeBySubject(message.getSubject(), emailTypeMap);
  771. String emailDateStr = DateUtil.format(emailDate, DateConst.YYYY_MM_DD_HH_MM_SS);
  772. if (emailType == null) {
  773. log.info("邮件不满足解析条件 -> 邮件主题:{},邮件日期:{}", message.getSubject(), emailDateStr);
  774. continue;
  775. }
  776. log.info("邮件采集成功 -> 邮件主题:{},邮件日期:{}", message.getSubject(), emailDateStr);
  777. Object content = message.getContent();
  778. // 1.邮件为MIME多部分消息体:可能既有邮件又有正文
  779. if (content instanceof MimeMultipart) {
  780. emailContentInfoDTOList = EmailUtil.collectMimeMultipart(message, mailboxInfoDTO.getAccount(), path);
  781. }
  782. // 2.邮件只有正文
  783. if (content instanceof String) {
  784. EmailContentInfoDTO emailContentInfoDTO = new EmailContentInfoDTO();
  785. emailContentInfoDTO.setEmailContent(content.toString());
  786. emailContentInfoDTO.setEmailDate(emailDateStr);
  787. emailContentInfoDTO.setEmailTitle(message.getSubject());
  788. String fileName = message.getSubject() + DateUtil.format(emailDate, DateConst.YYYYMMDDHHMMSS24);
  789. String filePath = path + mailboxInfoDTO.getAccount() + "/" + DateUtil.format(emailDate, DateConst.YYYY_MM_DD) + "/" + fileName + ".html";
  790. File saveFile = new File(filePath);
  791. saveFile.setReadable(true);
  792. if (!saveFile.exists()) {
  793. if (!saveFile.getParentFile().exists()) {
  794. saveFile.getParentFile().mkdirs();
  795. saveFile.getParentFile().setExecutable(true);
  796. }
  797. }
  798. FileUtil.writeFile(filePath, content.toString());
  799. emailContentInfoDTO.setFilePath(filePath);
  800. emailContentInfoDTO.setFileName(fileName);
  801. emailContentInfoDTOList.add(emailContentInfoDTO);
  802. }
  803. if (CollUtil.isNotEmpty(emailContentInfoDTOList)) {
  804. // 估值表或定期报告邮件不展示正文html文件
  805. if (emailType.equals(EmailTypeConst.VALUATION_EMAIL_TYPE) || emailType.equals(EmailTypeConst.REPORT_EMAIL_TYPE)) {
  806. emailContentInfoDTOList = emailContentInfoDTOList.stream().filter(e -> !ExcelUtil.isHTML(e.getFilePath())).toList();
  807. }
  808. emailContentInfoDTOList.forEach(e -> {
  809. e.setEmailType(emailType);
  810. e.setSenderEmail(senderEmail);
  811. });
  812. emailMessageMap.put(uuidKey, emailContentInfoDTOList);
  813. }
  814. } catch (Exception e) {
  815. log.error("获取邮箱的邮件报错,堆栈信息:{}", ExceptionUtil.stacktraceToString(e));
  816. }
  817. }
  818. folder.close(false);
  819. store.close();
  820. return emailMessageMap;
  821. }
  822. private String getSenderEmail(Address[] senderAddress) {
  823. if (senderAddress == null || senderAddress.length == 0) {
  824. return null;
  825. }
  826. // 此时的address是含有编码(MIME编码方式)后的文本和实际的邮件地址
  827. String address = senderAddress[0].toString();
  828. // 正则表达式匹配邮件地址
  829. Pattern pattern = Pattern.compile("<(\\S+)>");
  830. Matcher matcher = pattern.matcher(address);
  831. if (matcher.find()) {
  832. return matcher.group(1);
  833. }
  834. return null;
  835. }
  836. private Message[] getEmailMessage(Folder folder, String protocol, Date startDate) {
  837. try {
  838. if (protocol.contains("imap")) {
  839. // 获取邮件日期大于等于startDate的邮件(搜索条件只支持按天)
  840. SearchTerm startDateTerm = new ReceivedDateTerm(ComparisonTerm.GE, startDate);
  841. return folder.search(startDateTerm);
  842. } else {
  843. return folder.getMessages();
  844. }
  845. } catch (MessagingException e) {
  846. throw new RuntimeException(e);
  847. }
  848. }
  849. private static class PythonData {
  850. private Integer fileId;
  851. private Integer status;
  852. private String msg;
  853. private String register_number;
  854. public Integer getFileId() {
  855. return fileId;
  856. }
  857. public void setFileId(Integer fileId) {
  858. this.fileId = fileId;
  859. }
  860. public Integer getStatus() {
  861. return status;
  862. }
  863. public void setStatus(Integer status) {
  864. this.status = status;
  865. }
  866. public String getMsg() {
  867. return msg;
  868. }
  869. public void setMsg(String msg) {
  870. this.msg = msg;
  871. }
  872. public String getRegister_number() {
  873. return register_number;
  874. }
  875. public void setRegister_number(String register_number) {
  876. this.register_number = register_number;
  877. }
  878. }
  879. }