EmailParseService.java 63 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176
  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.exception.ReportParseException;
  14. import com.simuwang.base.common.util.*;
  15. import com.simuwang.base.config.DaqProperties;
  16. import com.simuwang.base.config.EmailRuleConfig;
  17. import com.simuwang.base.mapper.*;
  18. import com.simuwang.base.pojo.dos.*;
  19. import com.simuwang.base.pojo.dto.EmailContentInfoDTO;
  20. import com.simuwang.base.pojo.dto.EmailFundNavDTO;
  21. import com.simuwang.base.pojo.dto.MailboxInfoDTO;
  22. import com.simuwang.base.pojo.dto.query.DataboardQuery;
  23. import com.simuwang.base.pojo.dto.report.ParseResult;
  24. import com.simuwang.base.pojo.dto.report.ReportData;
  25. import com.simuwang.base.pojo.dto.report.ReportParseStatus;
  26. import com.simuwang.base.pojo.dto.report.ReportParserParams;
  27. import com.simuwang.base.pojo.valuation.CmValuationTableAttribute;
  28. import com.simuwang.base.pojo.vo.*;
  29. import com.simuwang.daq.components.report.parser.ReportParser;
  30. import com.simuwang.daq.components.report.parser.ReportParserFactory;
  31. import com.simuwang.daq.components.report.writer.ReportWriter;
  32. import com.simuwang.daq.components.report.writer.ReportWriterFactory;
  33. import jakarta.mail.*;
  34. import jakarta.mail.internet.MimeMessage;
  35. import jakarta.mail.internet.MimeMultipart;
  36. import jakarta.mail.search.ComparisonTerm;
  37. import jakarta.mail.search.ReceivedDateTerm;
  38. import jakarta.mail.search.SearchTerm;
  39. import org.slf4j.Logger;
  40. import org.slf4j.LoggerFactory;
  41. import org.springframework.beans.factory.annotation.Autowired;
  42. import org.springframework.beans.factory.annotation.Value;
  43. import org.springframework.stereotype.Service;
  44. import org.springframework.util.StopWatch;
  45. import java.io.File;
  46. import java.math.BigDecimal;
  47. import java.util.*;
  48. import java.util.regex.Matcher;
  49. import java.util.regex.Pattern;
  50. import java.util.stream.Collectors;
  51. /**
  52. * @author mozuwen
  53. * @date 2024-09-04
  54. * @description 邮件解析服务
  55. */
  56. @Service
  57. public class EmailParseService {
  58. public static final int stepSize = 10000;
  59. private static final Logger log = LoggerFactory.getLogger(EmailParseService.class);
  60. private final EmailTypeRuleMapper emailTypeRuleMapper;
  61. private final EmailRuleConfig emailRuleConfig;
  62. private final EmailFieldMappingMapper emailFieldMapper;
  63. private final EmailParserFactory emailParserFactory;
  64. private final EmailParseInfoMapper emailParseInfoMapper;
  65. private final EmailFileInfoMapper emailFileInfoMapper;
  66. private final EmailFundNavMapper emailFundNavMapper;
  67. private final EmailFundAssetMapper emailFundAssetMapper;
  68. private final AssetMapper assetMapper;
  69. private final NavMapper navMapper;
  70. private final FundService fundService;
  71. private final FundAliasMapper fundAliasMapper;
  72. private final ValuationTableMapper valuationTableMapper;
  73. private final ValuationTableAttributeMapper valuationTableAttributeMapper;
  74. private final FundPositionDetailMapper fundPositionDetailMapper;
  75. private final DistributionMapper distributionMapper;
  76. private final CompanyInformationMapper companyInformationMapper;
  77. @Autowired
  78. private FundInfoMapper fundInfoMapper;
  79. @Value("${email.file.path}")
  80. private String path;
  81. @Autowired
  82. private DaqProperties properties;
  83. /* 报告解析和入库的方法 */
  84. @Autowired
  85. private ReportParserFactory reportParserFactory;
  86. @Autowired
  87. private ReportWriterFactory reportWriterFactory;
  88. public EmailParseService(EmailTypeRuleMapper emailTypeRuleMapper, EmailRuleConfig emailRuleConfig,
  89. EmailFieldMappingMapper emailFieldMapper, EmailParserFactory emailParserFactory,
  90. EmailParseInfoMapper emailParseInfoMapper, EmailFileInfoMapper emailFileInfoMapper,
  91. EmailFundNavMapper emailFundNavMapper, EmailFundAssetMapper emailFundAssetMapper,
  92. AssetMapper assetMapper, NavMapper navMapper, FundService fundService,
  93. FundAliasMapper fundAliasMapper,
  94. ValuationTableMapper valuationTableMapper, ValuationTableAttributeMapper valuationTableAttributeMapper,
  95. FundPositionDetailMapper fundPositionDetailMapper, DistributionMapper distributionMapper, CompanyInformationMapper companyInformationMapper) {
  96. this.emailTypeRuleMapper = emailTypeRuleMapper;
  97. this.emailRuleConfig = emailRuleConfig;
  98. this.emailFieldMapper = emailFieldMapper;
  99. this.emailParserFactory = emailParserFactory;
  100. this.emailParseInfoMapper = emailParseInfoMapper;
  101. this.emailFileInfoMapper = emailFileInfoMapper;
  102. this.emailFundNavMapper = emailFundNavMapper;
  103. this.emailFundAssetMapper = emailFundAssetMapper;
  104. this.assetMapper = assetMapper;
  105. this.navMapper = navMapper;
  106. this.fundService = fundService;
  107. this.fundAliasMapper = fundAliasMapper;
  108. this.valuationTableMapper = valuationTableMapper;
  109. this.valuationTableAttributeMapper = valuationTableAttributeMapper;
  110. this.fundPositionDetailMapper = fundPositionDetailMapper;
  111. this.distributionMapper = distributionMapper;
  112. this.companyInformationMapper = companyInformationMapper;
  113. }
  114. /**
  115. * 解析指定邮箱指定时间范围内的邮件
  116. *
  117. * @param mailboxInfoDTO 邮箱配置信息
  118. * @param startDate 邮件起始日期(yyyy-MM-dd HH:mm:ss)
  119. * @param endDate 邮件截止日期(yyyy-MM-dd HH:mm:ss, 为null,将解析邮件日期小于等于startDate的当天邮件)
  120. */
  121. public void parseEmail(MailboxInfoDTO mailboxInfoDTO, Date startDate, Date endDate) {
  122. log.info("开始邮件解析 -> 邮箱信息:{},开始时间:{},结束时间:{}", mailboxInfoDTO, DateUtil.format(startDate, DateConst.YYYY_MM_DD_HH_MM_SS), DateUtil.format(endDate, DateConst.YYYY_MM_DD_HH_MM_SS));
  123. // 邮件类型配置
  124. Map<Integer, List<String>> emailTypeMap = getEmailType();
  125. // 邮件字段识别映射表
  126. Map<String, List<String>> emailFieldMap = getEmailFieldMapping();
  127. Map<String, List<EmailContentInfoDTO>> emailContentMap;
  128. try {
  129. emailContentMap = realEmail(mailboxInfoDTO, emailTypeMap, startDate, endDate);
  130. } catch (Exception e) {
  131. log.info("采集邮件失败 -> 邮箱配置信息:{},堆栈信息:{}", mailboxInfoDTO, ExceptionUtil.stacktraceToString(e));
  132. return;
  133. }
  134. if (MapUtil.isEmpty(emailContentMap)) {
  135. log.info("未采集到邮件 -> 邮箱配置信息:{},开始时间:{},结束时间:{}", mailboxInfoDTO, DateUtil.format(startDate, DateConst.YYYY_MM_DD_HH_MM_SS), DateUtil.format(endDate, DateConst.YYYY_MM_DD_HH_MM_SS));
  136. return;
  137. }
  138. for (Map.Entry<String, List<EmailContentInfoDTO>> emailEntry : emailContentMap.entrySet()) {
  139. List<EmailContentInfoDTO> emailContentInfoDTOList = emailEntry.getValue();
  140. if (CollUtil.isEmpty(emailContentInfoDTOList)) {
  141. log.warn("未采集到正文或附件");
  142. continue;
  143. }
  144. log.info("开始解析邮件数据 -> 邮件主题:{},邮件日期:{}", emailContentInfoDTOList.get(0).getEmailTitle(), emailContentInfoDTOList.get(0).getEmailDate());
  145. List<EmailFundNavDTO> emailFundNavDTOList = CollUtil.newArrayList();
  146. Map<EmailContentInfoDTO, List<EmailFundNavDTO>> fileNameNavMap = MapUtil.newHashMap();
  147. for (EmailContentInfoDTO emailContentInfoDTO : emailContentInfoDTOList) {
  148. try {
  149. List<EmailFundNavDTO> fundNavDTOList = parseEmail(emailContentInfoDTO, emailFieldMap);
  150. fileNameNavMap.put(emailContentInfoDTO, fundNavDTOList);
  151. emailFundNavDTOList.addAll(fundNavDTOList);
  152. } catch (Exception e) {
  153. log.error("堆栈信息:{}", ExceptionUtil.stacktraceToString(e));
  154. }
  155. }
  156. try {
  157. // 保存相关信息 -> 邮件信息表,邮件文件表,邮件净值表,邮件规模表,基金净值表
  158. saveRelatedTable(mailboxInfoDTO.getAccount(), emailContentInfoDTOList, fileNameNavMap);
  159. } catch (Exception exception) {
  160. log.error("邮件信息:{},堆栈信息:{}", emailContentInfoDTOList, ExceptionUtil.stacktraceToString(exception));
  161. }
  162. log.info("结束邮件解析 -> 邮箱信息:{},开始时间:{},结束时间:{}", mailboxInfoDTO, DateUtil.format(startDate, DateConst.YYYY_MM_DD_HH_MM_SS), DateUtil.format(endDate, DateConst.YYYY_MM_DD_HH_MM_SS));
  163. }
  164. }
  165. public List<EmailFundNavDTO> parseEmail(EmailContentInfoDTO emailContentInfoDTO, Map<String, List<String>> emailFieldMap) {
  166. Integer emailType = emailContentInfoDTO.getEmailType();
  167. AbstractEmailParser emailParser = emailParserFactory.getInstance(emailType);
  168. return emailParser.parse(emailContentInfoDTO, emailFieldMap);
  169. }
  170. public void saveRelatedTable(String emailAddress, List<EmailContentInfoDTO> emailContentInfoDTOList, Map<EmailContentInfoDTO, List<EmailFundNavDTO>> fileNameNavMap) {
  171. String emailTitle = CollUtil.isNotEmpty(emailContentInfoDTOList) ? emailContentInfoDTOList.get(0).getEmailTitle() : null;
  172. String emailDate = CollUtil.isNotEmpty(emailContentInfoDTOList) ? emailContentInfoDTOList.get(0).getEmailDate() : null;
  173. Integer emailType = CollUtil.isNotEmpty(emailContentInfoDTOList) ? emailContentInfoDTOList.get(0).getEmailType() : null;
  174. Integer emailId = CollUtil.isNotEmpty(emailContentInfoDTOList) ? emailContentInfoDTOList.get(0).getEmailId() : null;
  175. String senderEmail = CollUtil.isNotEmpty(emailContentInfoDTOList) ? emailContentInfoDTOList.get(0).getSenderEmail() : null;
  176. Date parseDate = new Date();
  177. int emailParseStatus = EmailParseStatusConst.SUCCESS;
  178. EmailParseInfoDO emailParseInfoDO = buildEmailParseInfo(emailId, emailAddress, senderEmail, emailDate, emailTitle, emailType, emailParseStatus, parseDate);
  179. emailId = saveEmailParseInfo(emailParseInfoDO);
  180. // python 报告解析接口结果
  181. List<ParseResult<ReportData>> dataList = ListUtil.list(false);
  182. for (Map.Entry<EmailContentInfoDTO, List<EmailFundNavDTO>> fileNameNavEntry : fileNameNavMap.entrySet()) {
  183. // 保存邮件文件表
  184. EmailContentInfoDTO emailContentInfoDTO = fileNameNavEntry.getKey();
  185. String fileName = emailContentInfoDTO.getFileName();
  186. if (Objects.equals(EmailTypeConst.REPORT_EMAIL_TYPE, emailType) && fileName.endsWith(".html")) {
  187. continue;
  188. }
  189. Integer fileId = saveEmailFileInfo(emailId, emailContentInfoDTO.getFileId(), fileName, emailContentInfoDTO.getFilePath(), parseDate);
  190. List<EmailFundNavDTO> fundNavDTOList = fileNameNavEntry.getValue();
  191. if (CollUtil.isNotEmpty(fundNavDTOList)) {
  192. // 过滤出解析成功的数据
  193. fundNavDTOList = fundNavDTOList.stream().filter(e -> e != null && StrUtil.isBlank(e.getFailReason())).toList();
  194. }
  195. if (CollUtil.isEmpty(fundNavDTOList) && !Objects.equals(EmailTypeConst.REPORT_EMAIL_TYPE, emailType)) {
  196. continue;
  197. }
  198. if (Objects.equals(EmailTypeConst.REPORT_EMAIL_TYPE, emailType)) {
  199. // 解析结果(可以从python获取或者自行解析)并保存报告
  200. ParseResult<ReportData> parseResult = this.parseReportAndHandleResult(fileId, emailContentInfoDTO);
  201. dataList.add(parseResult);
  202. }
  203. for (EmailFundNavDTO fundNavDTO : fundNavDTOList) {
  204. // 设置净值数据的解析状态
  205. setNavParseStatus(fundNavDTO, emailTitle);
  206. }
  207. // 保存净值表和规模表
  208. saveNavAndAssetNet(fileId, fundNavDTOList, parseDate);
  209. saveValuationInfo(fileId, fundNavDTOList);
  210. }
  211. // 更新邮件解析结果 -> 当【净值日期】和【备案编码/基金名称】能正常解读,即识别为【成功】
  212. long successNavCount = fileNameNavMap.values().stream().flatMap(List::stream).filter(e -> e != null && StrUtil.isBlank(e.getFailReason())).count();
  213. emailParseStatus = successNavCount >= 1 ? EmailParseStatusConst.SUCCESS : EmailParseStatusConst.FAIL;
  214. String failReason = null;
  215. if (emailParseStatus == EmailParseStatusConst.FAIL) {
  216. // 邮件解析失败时 -> 保存失败原因
  217. int hasPdfFile = emailContentInfoDTOList.stream().map(EmailContentInfoDTO::getFilePath).anyMatch(ExcelUtil::isPdf) ? 1 : 0;
  218. List<EmailFundNavDTO> navDTOList = fileNameNavMap.values().stream().flatMap(List::stream).toList();
  219. failReason = hasPdfFile == 1 && CollUtil.isEmpty(navDTOList) ? "无法从PDF文件中获取到数据" : navDTOList.stream().map(EmailFundNavDTO::getFailReason).distinct().collect(Collectors.joining("/"));
  220. if (StrUtil.isBlank(failReason)) {
  221. failReason = "模板不支持";
  222. }
  223. }
  224. // 报告邮件有一条失败就表示整个邮件解析失败
  225. if (Objects.equals(EmailTypeConst.REPORT_EMAIL_TYPE, emailType) && CollUtil.isNotEmpty(dataList)) {
  226. long sucNum = dataList.stream().filter(e -> Objects.equals(1, e.getStatus())).count();
  227. if (sucNum > 0) {
  228. emailParseStatus = EmailParseStatusConst.SUCCESS;
  229. } else {
  230. emailParseStatus = EmailParseStatusConst.FAIL;
  231. failReason = dataList.stream().map(ParseResult::getMsg).collect(Collectors.joining("/"));
  232. }
  233. }
  234. emailParseInfoMapper.updateParseStatus(emailId, emailParseStatus, failReason);
  235. }
  236. private void saveValuationInfo(Integer fileId, List<EmailFundNavDTO> fundNavDTOList) {
  237. if (CollUtil.isEmpty(fundNavDTOList)) {
  238. return;
  239. }
  240. valuationTableMapper.unValid(fileId);
  241. for (EmailFundNavDTO fundNavDTO : fundNavDTOList) {
  242. ValuationTableDO valuationTableDO = fundNavDTO.getValuationTableDO();
  243. if (valuationTableDO == null) {
  244. continue;
  245. }
  246. List<String> fundIdList = fundNavDTO.getFundIdList();
  247. valuationTableDO.setFileId(fileId);
  248. List<FundPositionDetailDO> fundPositionDetailDOList = fundNavDTO.getFundPositionDetailDOList();
  249. List<CmValuationTableAttribute> valuationTableAttributeList = fundNavDTO.getValuationTableAttributeList();
  250. if (CollUtil.isEmpty(fundIdList)) {
  251. valuationTableMapper.insert(valuationTableDO);
  252. int valuationId = valuationTableDO.getId();
  253. saveValuationTableAttribute(valuationId, valuationTableAttributeList);
  254. fundPositionDetailDOList.forEach(e -> e.setValuationId(valuationId));
  255. // 不匹配基金的情况下 -> 不写fund_position_detail
  256. // saveFundPositionDetail(fundPositionDetailDOList, null, fundNavDTO.getPriceDate());
  257. continue;
  258. }
  259. for (String fundId : fundIdList) {
  260. valuationTableDO.setFundId(fundId);
  261. valuationTableMapper.insert(valuationTableDO);
  262. int valuationId = valuationTableDO.getId();
  263. fundPositionDetailDOList.forEach(e -> {
  264. e.setFundId(fundId);
  265. e.setValuationId(valuationId);
  266. });
  267. saveValuationTableAttribute(valuationId, valuationTableAttributeList);
  268. saveFundPositionDetail(fundPositionDetailDOList, fundId, fundNavDTO.getPriceDate());
  269. }
  270. }
  271. }
  272. public void saveFundPositionDetail(List<FundPositionDetailDO> fundPositionDetails, String fundId, String valuationDate) {
  273. int subBegin = 0;
  274. int subEnd = stepSize;
  275. int insertNum = 0;
  276. int hasStock = 0;
  277. int hasBond = 0;
  278. int hasFuture = 0;
  279. if (CollectionUtil.isNotEmpty(fundPositionDetails)) {
  280. //插入持仓数据 cm_fund_position_detail(记录数较多,得分批)
  281. // 先删除原先的数据 然后写入数据
  282. fundPositionDetailMapper.deleteUnUsed(fundId, valuationDate);
  283. // 将最终结果写入 cm_fund_position_detail
  284. while (subBegin < fundPositionDetails.size()) {
  285. List<FundPositionDetailDO> segment = fundPositionDetails.subList(subBegin, Math.min(subEnd, fundPositionDetails.size()));
  286. for (FundPositionDetailDO fundPositionDetail : segment) {
  287. //实收信托、实收资本对应的编码为107
  288. if (StrUtil.isNotBlank(fundPositionDetail.getSecuritiesName()) && "实收信托".equals(fundPositionDetail.getSecuritiesName().trim())) {
  289. fundPositionDetail.setSubjectCode("107");
  290. fundPositionDetail.setSecuritiesCode("107");
  291. }
  292. //设置创建者id,在拿取最近修改人时有用
  293. fundPositionDetail.setCreatorId(0);
  294. if (fundPositionDetail.getLevel() != null && fundPositionDetail.getLevel() >= 4) {
  295. Integer secType = fundPositionDetail.getSecType();
  296. Integer subType = fundPositionDetail.getSubType();
  297. if (secType != null && subType != null) {
  298. if (secType == HoldingType.Stock.getId() && subType == HoldingType.Stock.getId()) {
  299. hasStock = 1;
  300. }
  301. // 正逆回购不属于债券
  302. String subjectCode = fundPositionDetail.getSubjectCode();
  303. String[] SALE_CODES = {"2202", "1202"};
  304. if (secType == HoldingType.Bond.getId() && !StrUtil.containsAny(subjectCode, SALE_CODES)) {
  305. hasBond = 1;
  306. }
  307. if (secType == HoldingType.Future.getId() || secType == HoldingType.Option.getId()) {
  308. hasFuture = 1;
  309. }
  310. }
  311. }
  312. }
  313. insertNum += fundPositionDetailMapper.insertMulti(segment);
  314. subBegin = subEnd;
  315. subEnd += stepSize;
  316. }
  317. }
  318. // 更新 cm_user_valuation_table
  319. if (insertNum > 0) {
  320. valuationTableMapper.updateUpdateAnalyzed(fundId, valuationDate, hasStock, hasBond, hasFuture);
  321. }
  322. }
  323. private void saveValuationTableAttribute(Integer valuationId, List<CmValuationTableAttribute> valuationTableAttributes) {
  324. if (valuationId == null || CollUtil.isEmpty(valuationTableAttributes)) {
  325. return;
  326. }
  327. List<ValuationTableAttributeDO> valuationTableAttributeDOList = buildValuationTableAttributeDO(valuationId, valuationTableAttributes);
  328. valuationTableAttributeMapper.deleteByValuationId(valuationId);
  329. if (CollUtil.isNotEmpty(valuationTableAttributeDOList)) {
  330. valuationTableAttributeMapper.batchInsert(valuationTableAttributeDOList);
  331. }
  332. }
  333. private List<ValuationTableAttributeDO> buildValuationTableAttributeDO(Integer valuationId, List<CmValuationTableAttribute> valuationTableAttributes) {
  334. if (CollUtil.isEmpty(valuationTableAttributes)) {
  335. return CollUtil.newArrayList();
  336. }
  337. return valuationTableAttributes.stream().map(e -> {
  338. ValuationTableAttributeDO tableAttributeDO = new ValuationTableAttributeDO();
  339. tableAttributeDO.setValuationId(valuationId);
  340. tableAttributeDO.setSubjectCode(e.getOriginalSubjectCode());
  341. tableAttributeDO.setSubjectName(e.getSubjectName());
  342. tableAttributeDO.setCurrency(e.getCurrency());
  343. tableAttributeDO.setExchangeRate(e.getExchangeRate());
  344. tableAttributeDO.setSecuritiesAmount(e.getSecuritiesAmount());
  345. tableAttributeDO.setUnitCost(e.getUnitCost());
  346. tableAttributeDO.setOriCurrencyCost(e.getOCurrencyCost());
  347. tableAttributeDO.setNetCost(e.getNetCost());
  348. tableAttributeDO.setNetCostRatio(e.getNetCostRatio());
  349. tableAttributeDO.setMarketPrice(e.getMarketPrice());
  350. tableAttributeDO.setOriCurrencyMarketValue(e.getOCurrencyMarketValue());
  351. tableAttributeDO.setMarketValue(e.getMarketValue());
  352. tableAttributeDO.setMarketValueRatio(e.getMarketValueRatio());
  353. tableAttributeDO.setOriCurrencyIncrement(e.getOCurrencyMarketValue());
  354. tableAttributeDO.setIncrement(e.getIncrement());
  355. tableAttributeDO.setHaltInfo(e.getHaltInfo());
  356. tableAttributeDO.setRightsInterestsInfo(e.getRightsInterestsInfo());
  357. tableAttributeDO.setIsvalid(1);
  358. tableAttributeDO.setCreatorId(0);
  359. tableAttributeDO.setUpdaterId(0);
  360. tableAttributeDO.setCreateTime(new Date());
  361. tableAttributeDO.setUpdateTime(new Date());
  362. return tableAttributeDO;
  363. }).collect(Collectors.toList());
  364. }
  365. private ParseResult<ReportData> parseReportAndHandleResult(int fileId, EmailContentInfoDTO emailContentInfoDTO) {
  366. ParseResult<ReportData> result = new ParseResult<>();
  367. String fileName = emailContentInfoDTO.getFileName();
  368. Integer emailType = emailContentInfoDTO.getEmailType();
  369. if (!Objects.equals(EmailTypeConst.REPORT_EMAIL_TYPE, emailType) || StrUtil.isBlank(fileName)) {
  370. result.setStatus(ReportParseStatus.NOT_A_REPORT.getCode());
  371. result.setMsg(ReportParseStatus.NOT_A_REPORT.getMsg());
  372. return result;
  373. }
  374. 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})");
  375. Matcher matcher = pattern.matcher(fileName);
  376. String registerNumber = null;
  377. if (matcher.find()) {
  378. registerNumber = matcher.group();
  379. }
  380. // 类型识别---先识别季度报告,没有季度再识别年度报告,最后识别月报
  381. ReportType reportType = null;
  382. if (StrUtil.containsAny(fileName, ReportType.QUARTERLY.getPatterns())) {
  383. reportType = ReportType.QUARTERLY;
  384. } else if (StrUtil.containsAny(fileName, ReportType.ANNUALLY.getPatterns())) {
  385. reportType = ReportType.ANNUALLY;
  386. } else if (StrUtil.containsAny(fileName, ReportType.MONTHLY.getPatterns())) {
  387. reportType = ReportType.MONTHLY;
  388. }
  389. // 解析器--如果开启python解析则直接调用python接口,否则根据文件后缀获取对应解析器
  390. ReportParserFileType fileType;
  391. if (Objects.equals(Boolean.TRUE, this.properties.getEnablePyParser())) {
  392. fileType = ReportParserFileType.PYTHON;
  393. } else {
  394. String fileSuffix = StrUtil.subAfter(fileName, ".", true);
  395. fileType = ReportParserFileType.getBySuffix(fileSuffix);
  396. }
  397. // 不支持的格式
  398. if (fileType == null) {
  399. result.setStatus(ReportParseStatus.NO_SUPPORT_TEMPLATE.getCode());
  400. result.setMsg(StrUtil.format(ReportParseStatus.NO_SUPPORT_TEMPLATE.getMsg(), fileName));
  401. return result;
  402. }
  403. // 不是定期报告的判断逻辑放在不支持的格式下面
  404. if (reportType == null) {
  405. result.setStatus(ReportParseStatus.NOT_A_REPORT.getCode());
  406. result.setMsg(StrUtil.format(ReportParseStatus.NOT_A_REPORT.getMsg(), fileName));
  407. return result;
  408. }
  409. // 解析报告
  410. ReportData reportData = null;
  411. StopWatch parserWatch = new StopWatch();
  412. parserWatch.start();
  413. try {
  414. ReportParserParams params = ReportParserParams.builder().fileId(fileId).filename(fileName)
  415. .filepath(emailContentInfoDTO.getFilePath()).registerNumber(registerNumber).build();
  416. ReportParser<ReportData> instance = this.reportParserFactory.getInstance(reportType, fileType);
  417. reportData = instance.parse(params);
  418. result.setStatus(1);
  419. result.setMsg("报告解析成功");
  420. result.setData(reportData);
  421. } catch (ReportParseException e) {
  422. log.error("解析失败\n{}", e.getMsg());
  423. result.setStatus(e.getCode());
  424. result.setMsg(e.getMsg());
  425. } catch (Exception e) {
  426. log.error("解析错误\n{}", ExceptionUtil.stacktraceToString(e));
  427. result.setStatus(ReportParseStatus.PARSE_FAIL.getCode());
  428. result.setMsg(StrUtil.format(ReportParseStatus.PARSE_FAIL.getMsg(), e.getMessage()));
  429. } finally {
  430. parserWatch.stop();
  431. if (log.isInfoEnabled()) {
  432. log.info("报告{}解析结果为{},耗时{}ms", fileName, reportData, parserWatch.getTotalTimeMillis());
  433. }
  434. }
  435. // 保存报告解析结果
  436. if (reportData != null) {
  437. StopWatch writeWatch = new StopWatch();
  438. writeWatch.start();
  439. try {
  440. ReportWriter<ReportData> instance = this.reportWriterFactory.getInstance(reportType);
  441. instance.write(reportData);
  442. } catch (Exception e) {
  443. log.error("报告{}结果保存失败\n{}", fileName, ExceptionUtil.stacktraceToString(e));
  444. } finally {
  445. writeWatch.stop();
  446. if (log.isInfoEnabled()) {
  447. log.info("报告{}解析结果保存完成,耗时{}ms", fileName, writeWatch.getTotalTimeMillis());
  448. }
  449. }
  450. }
  451. return result;
  452. }
  453. private void saveNavAndAssetNet(Integer fileId, List<EmailFundNavDTO> fundNavDTOList, Date parseDate) {
  454. if (CollUtil.isEmpty(fundNavDTOList)) {
  455. return;
  456. }
  457. // 净值数据
  458. List<EmailFundNavDO> emailFundNavDOList = fundNavDTOList.stream()
  459. .map(e -> buildEmailFundNavDo(fileId, e, parseDate)).filter(CollUtil::isNotEmpty).flatMap(List::stream).collect(Collectors.toList());
  460. if (CollUtil.isNotEmpty(emailFundNavDOList)) {
  461. // 先删除文件id下的净值数据(考虑到重新解析的需求,如果是首次解析,那么file_id下不存在净值数据)
  462. emailFundNavMapper.deleteByFileId(fileId);
  463. emailFundNavMapper.batchInsert(emailFundNavDOList);
  464. List<NavDO> navDOList = emailFundNavDOList.stream().filter(e -> StrUtil.isNotBlank(e.getFundId()))
  465. .map(e -> BeanUtil.copyProperties(e, NavDO.class)).collect(Collectors.toList());
  466. saveNavDo(navDOList);
  467. }
  468. // 保存规模数据
  469. List<EmailFundAssetDO> emailFundAssetDOList = fundNavDTOList.stream()
  470. .map(e -> buildEmailFundAssetDo(fileId, e, parseDate)).filter(CollUtil::isNotEmpty).flatMap(List::stream).collect(Collectors.toList());
  471. if (CollUtil.isNotEmpty(emailFundAssetDOList)) {
  472. // 先删除file_id下的规模数据(考虑到重新解析的需求,如果是首次解析,那么file_id下不存在规模数据)
  473. emailFundAssetMapper.deleteByFileId(fileId);
  474. emailFundAssetMapper.batchInsert(emailFundAssetDOList);
  475. List<AssetDO> assetDOList = emailFundAssetDOList.stream().filter(e -> StrUtil.isNotBlank(e.getFundId()))
  476. .map(e -> BeanUtil.copyProperties(e, AssetDO.class)).collect(Collectors.toList());
  477. saveAssetDo(assetDOList);
  478. }
  479. }
  480. public void saveNavDo(List<NavDO> navDOList) {
  481. if (CollUtil.isEmpty(navDOList)) {
  482. return;
  483. }
  484. Map<String, List<NavDO>> fundIdNavMap = navDOList.stream().collect(Collectors.groupingBy(NavDO::getFundId));
  485. for (Map.Entry<String, List<NavDO>> entry : fundIdNavMap.entrySet()) {
  486. List<NavDO> navDOS = entry.getValue();
  487. List<String> priceDateList = navDOS.stream().map(NavDO::getPriceDate).map(e -> DateUtil.format(e, DateConst.YYYY_MM_DD)).collect(Collectors.toList());
  488. List<String> dateList = navMapper.queryFundNavByDate(entry.getKey(), priceDateList);
  489. List<NavDO> updateNavDoList = navDOS.stream().filter(e -> dateList.contains(DateUtil.format(e.getPriceDate(), DateConst.YYYY_MM_DD))).collect(Collectors.toList());
  490. List<NavDO> insertNavDoList = navDOS.stream().filter(e -> !dateList.contains(DateUtil.format(e.getPriceDate(), DateConst.YYYY_MM_DD))).collect(Collectors.toList());
  491. if (CollUtil.isNotEmpty(insertNavDoList)) {
  492. Map<Date, List<NavDO>> priceDateNavDoListMap = insertNavDoList.stream().collect(Collectors.groupingBy(NavDO::getPriceDate));
  493. boolean hasDuplicationDateData = priceDateNavDoListMap.values().stream().map(List::size).anyMatch(e -> e > 1);
  494. if (!hasDuplicationDateData) {
  495. navMapper.batchInsert(insertNavDoList);
  496. }
  497. // 要插入的数据中存在相同日期的数据 -> 只能一条一条的插入数据了
  498. insertNavDoList.forEach(e -> saveNavDo(ListUtil.toList(e)));
  499. }
  500. if (CollUtil.isNotEmpty(updateNavDoList)) {
  501. navMapper.batchUpdate(updateNavDoList);
  502. }
  503. }
  504. }
  505. public void saveAssetDo(List<AssetDO> assetDOList) {
  506. if (CollUtil.isEmpty(assetDOList)) {
  507. return;
  508. }
  509. Map<String, List<AssetDO>> fundIdNavMap = assetDOList.stream().collect(Collectors.groupingBy(AssetDO::getFundId));
  510. for (Map.Entry<String, List<AssetDO>> entry : fundIdNavMap.entrySet()) {
  511. List<AssetDO> assetDOS = entry.getValue();
  512. List<String> priceDateList = assetDOS.stream().map(AssetDO::getPriceDate).map(e -> DateUtil.format(e, DateConst.YYYY_MM_DD)).collect(Collectors.toList());
  513. List<String> dateList = assetMapper.queryFundNavByDate(entry.getKey(), priceDateList);
  514. List<AssetDO> updateAssetDoList = assetDOS.stream().filter(e -> dateList.contains(DateUtil.format(e.getPriceDate(), DateConst.YYYY_MM_DD))).collect(Collectors.toList());
  515. List<AssetDO> insertAssetDoList = assetDOS.stream().filter(e -> !dateList.contains(DateUtil.format(e.getPriceDate(), DateConst.YYYY_MM_DD))).collect(Collectors.toList());
  516. if (CollUtil.isNotEmpty(insertAssetDoList)) {
  517. Map<Date, List<AssetDO>> priceDateAssetDoListMap = insertAssetDoList.stream().collect(Collectors.groupingBy(AssetDO::getPriceDate));
  518. boolean hasDuplicationDateData = priceDateAssetDoListMap.values().stream().map(List::size).anyMatch(e -> e > 1);
  519. if (!hasDuplicationDateData) {
  520. assetMapper.batchInsert(insertAssetDoList);
  521. }
  522. // 要插入的数据中存在相同日期的数据 -> 只能一条一条的插入数据了
  523. insertAssetDoList.forEach(e -> saveAssetDo(ListUtil.toList(e)));
  524. }
  525. if (CollUtil.isNotEmpty(updateAssetDoList)) {
  526. assetMapper.batchUpdate(updateAssetDoList);
  527. }
  528. }
  529. }
  530. private List<EmailFundAssetDO> buildEmailFundAssetDo(Integer fileId, EmailFundNavDTO fundNavDTO, Date parseDate) {
  531. List<EmailFundAssetDO> fundAssetDOList = CollUtil.newArrayList();
  532. BigDecimal assetNet = StrUtil.isNotBlank(fundNavDTO.getAssetNet()) ? new BigDecimal(fundNavDTO.getAssetNet()) : null;
  533. BigDecimal assetShare = StrUtil.isNotBlank(fundNavDTO.getAssetShare()) ? new BigDecimal(fundNavDTO.getAssetShare()) : null;
  534. if (assetNet == null) {
  535. return fundAssetDOList;
  536. }
  537. Integer isStored = fundNavDTO.getParseStatus() != null
  538. && (fundNavDTO.getParseStatus().equals(NavParseStatusConst.ASSET_NET_NEGATIVE) || fundNavDTO.getParseStatus().equals(NavParseStatusConst.SUCCESS)) ? 1 : 0;
  539. Date priceDate = DateUtil.parse(fundNavDTO.getPriceDate(), DateConst.YYYY_MM_DD);
  540. if (CollUtil.isNotEmpty(fundNavDTO.getFundIdList())) {
  541. for (String fundId : fundNavDTO.getFundIdList()) {
  542. EmailFundAssetDO emailFundAssetDO = new EmailFundAssetDO();
  543. emailFundAssetDO.setFileId(fileId);
  544. emailFundAssetDO.setPriceDate(priceDate);
  545. emailFundAssetDO.setFundId(fundId);
  546. emailFundAssetDO.setFundName(fundNavDTO.getFundName());
  547. emailFundAssetDO.setRegisterNumber(fundNavDTO.getRegisterNumber());
  548. emailFundAssetDO.setAssetNet(assetNet);
  549. emailFundAssetDO.setAssetShare(assetShare);
  550. emailFundAssetDO.setIsStored(isStored);
  551. emailFundAssetDO.setExceptionStatus(fundNavDTO.getParseStatus());
  552. emailFundAssetDO.setIsvalid(1);
  553. emailFundAssetDO.setCreatorId(0);
  554. emailFundAssetDO.setCreateTime(parseDate);
  555. emailFundAssetDO.setUpdaterId(0);
  556. emailFundAssetDO.setUpdateTime(parseDate);
  557. fundAssetDOList.add(emailFundAssetDO);
  558. }
  559. } else {
  560. EmailFundAssetDO emailFundAssetDO = new EmailFundAssetDO();
  561. emailFundAssetDO.setFileId(fileId);
  562. emailFundAssetDO.setPriceDate(priceDate);
  563. emailFundAssetDO.setFundName(fundNavDTO.getFundName());
  564. emailFundAssetDO.setRegisterNumber(fundNavDTO.getRegisterNumber());
  565. emailFundAssetDO.setAssetNet(assetNet);
  566. emailFundAssetDO.setAssetShare(assetShare);
  567. emailFundAssetDO.setIsStored(isStored);
  568. emailFundAssetDO.setExceptionStatus(fundNavDTO.getParseStatus());
  569. emailFundAssetDO.setIsvalid(1);
  570. emailFundAssetDO.setCreatorId(0);
  571. emailFundAssetDO.setCreateTime(parseDate);
  572. emailFundAssetDO.setUpdaterId(0);
  573. emailFundAssetDO.setUpdateTime(parseDate);
  574. fundAssetDOList.add(emailFundAssetDO);
  575. }
  576. return fundAssetDOList;
  577. }
  578. private List<EmailFundNavDO> buildEmailFundNavDo(Integer fileId, EmailFundNavDTO fundNavDTO, Date parseDate) {
  579. List<EmailFundNavDO> fundNavDOList = CollUtil.newArrayList();
  580. Date priceDate = DateUtil.parse(fundNavDTO.getPriceDate(), DateConst.YYYY_MM_DD);
  581. BigDecimal nav = StrUtil.isNotBlank(fundNavDTO.getNav()) ? new BigDecimal(fundNavDTO.getNav()) : null;
  582. BigDecimal cumulativeNavWithdrawal = StrUtil.isNotBlank(fundNavDTO.getCumulativeNavWithdrawal()) ? new BigDecimal(fundNavDTO.getCumulativeNavWithdrawal()) : null;
  583. Integer isStored = fundNavDTO.getParseStatus() != null && !fundNavDTO.getParseStatus().equals(NavParseStatusConst.NAV_DEFICIENCY)
  584. && !fundNavDTO.getParseStatus().equals(NavParseStatusConst.NOT_MATCH) ? 1 : 0;
  585. if (CollUtil.isNotEmpty(fundNavDTO.getFundIdList())) {
  586. for (String fundId : fundNavDTO.getFundIdList()) {
  587. EmailFundNavDO emailFundNavDO = new EmailFundNavDO();
  588. emailFundNavDO.setFileId(fileId);
  589. emailFundNavDO.setIsStored(isStored);
  590. emailFundNavDO.setPriceDate(priceDate);
  591. emailFundNavDO.setNav(nav);
  592. emailFundNavDO.setFundId(fundId);
  593. emailFundNavDO.setCumulativeNavWithdrawal(cumulativeNavWithdrawal);
  594. emailFundNavDO.setFundName(fundNavDTO.getFundName());
  595. emailFundNavDO.setRegisterNumber(fundNavDTO.getRegisterNumber());
  596. emailFundNavDO.setExceptionStatus(fundNavDTO.getParseStatus());
  597. emailFundNavDO.setTemplateId(fundNavDTO.getTemplateId());
  598. emailFundNavDO.setIsvalid(1);
  599. emailFundNavDO.setCreatorId(0);
  600. emailFundNavDO.setCreateTime(parseDate);
  601. emailFundNavDO.setUpdaterId(0);
  602. emailFundNavDO.setUpdateTime(parseDate);
  603. fundNavDOList.add(emailFundNavDO);
  604. }
  605. } else {
  606. EmailFundNavDO emailFundNavDO = new EmailFundNavDO();
  607. emailFundNavDO.setFileId(fileId);
  608. emailFundNavDO.setPriceDate(priceDate);
  609. emailFundNavDO.setNav(nav);
  610. emailFundNavDO.setCumulativeNavWithdrawal(cumulativeNavWithdrawal);
  611. emailFundNavDO.setFundName(fundNavDTO.getFundName());
  612. emailFundNavDO.setRegisterNumber(fundNavDTO.getRegisterNumber());
  613. emailFundNavDO.setExceptionStatus(fundNavDTO.getParseStatus());
  614. emailFundNavDO.setTemplateId(fundNavDTO.getTemplateId());
  615. emailFundNavDO.setIsStored(isStored);
  616. emailFundNavDO.setIsvalid(1);
  617. emailFundNavDO.setCreatorId(0);
  618. emailFundNavDO.setCreateTime(parseDate);
  619. emailFundNavDO.setUpdaterId(0);
  620. emailFundNavDO.setUpdateTime(parseDate);
  621. fundNavDOList.add(emailFundNavDO);
  622. }
  623. return fundNavDOList;
  624. }
  625. private Integer saveEmailFileInfo(Integer emailId, Integer fileId, String fileName, String filePath, Date parseDate) {
  626. EmailFileInfoDO emailFileInfoDO = buildEmailFileInfoDO(emailId, fileId, fileName, filePath, parseDate);
  627. if (emailFileInfoDO.getId() != null) {
  628. emailFileInfoMapper.updateTimeById(fileId, parseDate);
  629. return emailFileInfoDO.getId();
  630. }
  631. emailFileInfoMapper.insert(emailFileInfoDO);
  632. return emailFileInfoDO.getId();
  633. }
  634. private EmailFileInfoDO buildEmailFileInfoDO(Integer emailId, Integer fileId, String fileName, String filePath, Date parseDate) {
  635. EmailFileInfoDO emailFileInfoDO = new EmailFileInfoDO();
  636. emailFileInfoDO.setId(fileId);
  637. emailFileInfoDO.setEmailId(emailId);
  638. emailFileInfoDO.setFileName(fileName);
  639. emailFileInfoDO.setFilePath(filePath);
  640. emailFileInfoDO.setIsvalid(1);
  641. emailFileInfoDO.setCreatorId(0);
  642. emailFileInfoDO.setCreateTime(parseDate);
  643. emailFileInfoDO.setUpdaterId(0);
  644. emailFileInfoDO.setUpdateTime(parseDate);
  645. return emailFileInfoDO;
  646. }
  647. private void setNavParseStatus(EmailFundNavDTO fundNavDTO, String emailTitle) {
  648. // 1.单位净值或累计净值缺失
  649. if (StrUtil.isBlank(fundNavDTO.getNav()) || StrUtil.isBlank(fundNavDTO.getCumulativeNavWithdrawal())) {
  650. fundNavDTO.setParseStatus(NavParseStatusConst.NAV_DEFICIENCY);
  651. return;
  652. }
  653. // 考虑单独规模文件时 -> 无单位净值和累计净值
  654. // 2.单位净值或累计净值不大于0
  655. if (!emailTitle.contains("规模")) {
  656. if (StrUtil.isBlank(fundNavDTO.getNav()) || StrUtil.isBlank(fundNavDTO.getCumulativeNavWithdrawal())
  657. || (fundNavDTO.getNav().compareTo("0") <= 0 || fundNavDTO.getCumulativeNavWithdrawal().compareTo("0") <= 0)) {
  658. fundNavDTO.setParseStatus(NavParseStatusConst.NAV_NEGATIVE);
  659. return;
  660. }
  661. }
  662. // 3.资产净值不大于0
  663. if (StrUtil.isNotBlank(fundNavDTO.getAssetNet()) && fundNavDTO.getAssetNet().compareTo("0") <= 0) {
  664. fundNavDTO.setParseStatus(NavParseStatusConst.ASSET_NET_NEGATIVE);
  665. return;
  666. }
  667. // 4.匹配基金(考虑到解析估值表时已经匹配上基金的情况)
  668. List<String> fundIdList = fundNavDTO.getFundIdList();
  669. if (CollUtil.isEmpty(fundIdList)) {
  670. fundIdList = fundService.getFundIdByNamesAndCode(fundNavDTO.getFundName(), fundNavDTO.getRegisterNumber());
  671. if (CollUtil.isEmpty(fundIdList)) {
  672. fundNavDTO.setParseStatus(NavParseStatusConst.NOT_MATCH);
  673. }
  674. }
  675. fundNavDTO.setFundIdList(fundIdList);
  676. // 写入别名管理表fund_alias
  677. saveFundAlias(fundNavDTO.getFundName(), fundNavDTO.getRegisterNumber(), fundIdList);
  678. if (CollUtil.isEmpty(fundIdList)) {
  679. return;
  680. }
  681. fundNavDTO.setParseStatus(NavParseStatusConst.SUCCESS);
  682. }
  683. private void saveFundAlias(String fundName, String registerNumber, List<String> fundIdList) {
  684. // 未识别到基金名称和备案编码的数据不写入别名管理
  685. if (StrUtil.isBlank(fundName) && StrUtil.isBlank(registerNumber)) {
  686. return;
  687. }
  688. List<FundAliasDO> fundAliasDOList = CollUtil.newArrayList();
  689. if (StrUtil.isNotBlank(fundName) && StrUtil.isNotBlank(registerNumber)) {
  690. fundAliasDOList = fundAliasMapper.queryFundIdByNameAndRegisterNumber(fundName, registerNumber);
  691. }
  692. if (StrUtil.isBlank(fundName) && StrUtil.isNotBlank(registerNumber)) {
  693. fundAliasDOList = fundAliasMapper.queryFundIdByRegisterNumber(registerNumber);
  694. }
  695. if (StrUtil.isNotBlank(fundName) && StrUtil.isBlank(registerNumber)) {
  696. fundAliasDOList = fundAliasMapper.queryFundIdByName(fundName);
  697. }
  698. // 未匹配基金且已写入别名表
  699. if (CollUtil.isEmpty(fundIdList) && CollUtil.isNotEmpty(fundAliasDOList)) {
  700. return;
  701. }
  702. // 未匹配基金且未写入别名表
  703. if (CollUtil.isEmpty(fundIdList) && CollUtil.isEmpty(fundAliasDOList)) {
  704. fundAliasMapper.batchInsert(ListUtil.toList(buildFundAliasDO(fundName, registerNumber, null)));
  705. log.info("写入别名表 -> 基金名称:{},备案编码:{},基金id:{}", fundName, registerNumber, fundIdList);
  706. return;
  707. }
  708. // 匹配上基金 -> 需要判断此时基金id是否已经在别名管理表
  709. if (CollUtil.isNotEmpty(fundAliasDOList)) {
  710. List<String> collect = fundAliasDOList.stream().filter(Objects::nonNull).map(FundAliasDO::getTargetFundId).filter(Objects::nonNull).distinct().toList();
  711. fundIdList = fundIdList.stream().filter(e -> !collect.contains(e)).toList();
  712. }
  713. List<FundAliasDO> fundAliasDOS = CollUtil.isNotEmpty(fundIdList) ? fundIdList.stream().map(e -> buildFundAliasDO(fundName, registerNumber, e)).toList() : null;
  714. if (CollUtil.isNotEmpty(fundAliasDOS)) {
  715. fundAliasMapper.batchInsert(fundAliasDOS);
  716. log.info("写入别名表 -> 基金名称:{},备案编码:{},基金id:{}", fundName, registerNumber, fundIdList);
  717. }
  718. }
  719. public FundAliasDO buildFundAliasDO(String fundName, String registerNumber, String fundId) {
  720. FundAliasDO fundAliasDO = new FundAliasDO();
  721. fundAliasDO.setTargetFundId(fundId);
  722. fundAliasDO.setSourceFundName(fundName);
  723. fundAliasDO.setSourceRegisterNumber(registerNumber);
  724. fundAliasDO.setIsvalid(1);
  725. fundAliasDO.setCreatorId(0);
  726. fundAliasDO.setCreateTime(new Date());
  727. fundAliasDO.setUpdateTime(new Date());
  728. fundAliasDO.setUpdaterId(0);
  729. return fundAliasDO;
  730. }
  731. private Integer saveEmailParseInfo(EmailParseInfoDO emailParseInfoDO) {
  732. if (emailParseInfoDO == null) {
  733. return null;
  734. }
  735. // 重新邮件功能 -> 修改解析时间和更新时间
  736. if (emailParseInfoDO.getId() != null) {
  737. emailParseInfoMapper.updateParseTime(emailParseInfoDO.getId(), emailParseInfoDO.getParseDate());
  738. return emailParseInfoDO.getId();
  739. }
  740. emailParseInfoMapper.insert(emailParseInfoDO);
  741. return emailParseInfoDO.getId();
  742. }
  743. private EmailParseInfoDO buildEmailParseInfo(Integer emailId, String emailAddress, String senderEmail, String emailDate,
  744. String emailTitle, Integer emailType, Integer parseStatus, Date parseDate) {
  745. EmailParseInfoDO emailParseInfoDO = new EmailParseInfoDO();
  746. emailParseInfoDO.setId(emailId);
  747. emailParseInfoDO.setSenderEmail(senderEmail);
  748. emailParseInfoDO.setEmail(emailAddress);
  749. emailParseInfoDO.setEmailDate(DateUtil.parse(emailDate, DateConst.YYYY_MM_DD_HH_MM_SS));
  750. emailParseInfoDO.setParseDate(parseDate);
  751. emailParseInfoDO.setEmailTitle(emailTitle);
  752. emailParseInfoDO.setEmailType(emailType);
  753. emailParseInfoDO.setParseStatus(parseStatus);
  754. emailParseInfoDO.setIsvalid(1);
  755. emailParseInfoDO.setCreatorId(0);
  756. emailParseInfoDO.setCreateTime(parseDate);
  757. emailParseInfoDO.setUpdaterId(0);
  758. emailParseInfoDO.setUpdateTime(parseDate);
  759. return emailParseInfoDO;
  760. }
  761. public Map<String, List<String>> getEmailFieldMapping() {
  762. List<EmailFieldMappingDO> emailFieldMappingDOList = emailFieldMapper.getEmailFieldMapping(1);
  763. return emailFieldMappingDOList.stream()
  764. .collect(Collectors.toMap(EmailFieldMappingDO::getCode, v -> Arrays.stream(v.getName().split(",")).toList()));
  765. }
  766. public Map<Integer, List<String>> getEmailType() {
  767. Map<Integer, List<String>> emailTypeMap = MapUtil.newHashMap(3, true);
  768. EmailTypeRuleDO emailTypeRuleDO = emailTypeRuleMapper.getEmailTypeRule();
  769. String nav = emailTypeRuleDO != null && StrUtil.isNotBlank(emailTypeRuleDO.getNav()) ? emailTypeRuleDO.getNav() : emailRuleConfig.getNav();
  770. String valuation = emailTypeRuleDO != null && StrUtil.isNotBlank(emailTypeRuleDO.getValuation()) ? emailTypeRuleDO.getValuation() : emailRuleConfig.getValuation();
  771. String report = emailTypeRuleDO != null && StrUtil.isNotBlank(emailTypeRuleDO.getReport()) ? emailTypeRuleDO.getReport() : emailRuleConfig.getReport();
  772. emailTypeMap.put(EmailTypeConst.VALUATION_EMAIL_TYPE, Arrays.stream(valuation.split(",")).toList());
  773. emailTypeMap.put(EmailTypeConst.REPORT_EMAIL_TYPE, Arrays.stream(report.split(",")).toList());
  774. emailTypeMap.put(EmailTypeConst.NAV_EMAIL_TYPE, Arrays.stream(nav.split(",")).toList());
  775. return emailTypeMap;
  776. }
  777. /**
  778. * 读取邮件
  779. *
  780. * @param mailboxInfoDTO 邮箱配置信息
  781. * @param emailTypeMap 邮件类型识别规则映射表
  782. * @param startDate 邮件起始日期
  783. * @param endDate 邮件截止日期(为null,将解析邮件日期小于等于startDate的当天邮件)
  784. * @return 读取到的邮件信息
  785. * @throws Exception 异常信息
  786. */
  787. private Map<String, List<EmailContentInfoDTO>> realEmail(MailboxInfoDTO mailboxInfoDTO, Map<Integer, List<String>> emailTypeMap, Date startDate, Date endDate) throws Exception {
  788. Store store = EmailUtil.getStoreNew(mailboxInfoDTO);
  789. if (store == null) {
  790. return MapUtil.newHashMap();
  791. }
  792. // 默认读取收件箱的邮件
  793. Folder folder = store.getFolder("INBOX");
  794. folder.open(Folder.READ_ONLY);
  795. Message[] messages = getEmailMessage(folder, mailboxInfoDTO.getProtocol(), startDate);
  796. if (messages == null || messages.length == 0) {
  797. log.info("获取不到邮件 -> 邮箱信息:{},开始时间:{},结束时间:{}", mailboxInfoDTO, startDate, endDate);
  798. return MapUtil.newHashMap();
  799. }
  800. Map<String, List<EmailContentInfoDTO>> emailMessageMap = MapUtil.newHashMap();
  801. for (Message message1 : messages) {
  802. MimeMessage message = (MimeMessage) message1;
  803. List<EmailContentInfoDTO> emailContentInfoDTOList = CollUtil.newArrayList();
  804. String uuidKey = UUID.randomUUID().toString().replaceAll("-", "");
  805. Integer emailType;
  806. String senderEmail;
  807. try {
  808. if (!folder.isOpen()) {
  809. folder.open(Folder.READ_ONLY);
  810. }
  811. Date emailDate = message.getSentDate();
  812. boolean isNotParseConditionSatisfied = emailDate == null || (endDate != null && emailDate.compareTo(endDate) > 0) || (startDate != null && emailDate.compareTo(startDate) < 0);
  813. if (isNotParseConditionSatisfied) {
  814. continue;
  815. }
  816. senderEmail = getSenderEmail(message);
  817. emailType = EmailUtil.getEmailTypeBySubject(message.getSubject(), emailTypeMap);
  818. String emailDateStr = DateUtil.format(emailDate, DateConst.YYYY_MM_DD_HH_MM_SS);
  819. if (emailType == null) {
  820. log.info("邮件不满足解析条件 -> 邮件主题:{},邮件日期:{}", message.getSubject(), emailDateStr);
  821. continue;
  822. }
  823. log.info("邮件采集成功 -> 邮件主题:{},邮件日期:{}", message.getSubject(), emailDateStr);
  824. Object content = message.getContent();
  825. // 1.邮件为MIME多部分消息体:可能既有邮件又有正文
  826. if (content instanceof MimeMultipart) {
  827. emailContentInfoDTOList = EmailUtil.collectMimeMultipart(message, mailboxInfoDTO.getAccount(), path);
  828. }
  829. // 2.邮件只有正文
  830. if (content instanceof String) {
  831. EmailContentInfoDTO emailContentInfoDTO = new EmailContentInfoDTO();
  832. emailContentInfoDTO.setEmailContent(content.toString());
  833. emailContentInfoDTO.setEmailDate(emailDateStr);
  834. emailContentInfoDTO.setEmailTitle(message.getSubject());
  835. String fileName = message.getSubject() + DateUtil.format(emailDate, DateConst.YYYYMMDDHHMMSS24);
  836. String filePath = path + mailboxInfoDTO.getAccount() + "/" + DateUtil.format(emailDate, DateConst.YYYY_MM_DD) + "/" + fileName + ".html";
  837. File saveFile = new File(filePath);
  838. saveFile.setReadable(true);
  839. if (!saveFile.exists()) {
  840. if (!saveFile.getParentFile().exists()) {
  841. saveFile.getParentFile().mkdirs();
  842. saveFile.getParentFile().setExecutable(true);
  843. }
  844. }
  845. FileUtil.writeFile(filePath, content.toString());
  846. emailContentInfoDTO.setFilePath(filePath);
  847. emailContentInfoDTO.setFileName(fileName);
  848. emailContentInfoDTOList.add(emailContentInfoDTO);
  849. }
  850. if (CollUtil.isNotEmpty(emailContentInfoDTOList)) {
  851. // 估值表或定期报告邮件不展示正文html文件
  852. if (emailType.equals(EmailTypeConst.VALUATION_EMAIL_TYPE) || emailType.equals(EmailTypeConst.REPORT_EMAIL_TYPE)) {
  853. emailContentInfoDTOList = emailContentInfoDTOList.stream().filter(e -> !ExcelUtil.isHTML(e.getFilePath())).toList();
  854. }
  855. emailContentInfoDTOList.forEach(e -> {
  856. e.setEmailType(emailType);
  857. e.setSenderEmail(senderEmail);
  858. });
  859. emailMessageMap.put(uuidKey, emailContentInfoDTOList);
  860. }
  861. } catch (Exception e) {
  862. log.error("获取邮箱的邮件报错,堆栈信息:{}", ExceptionUtil.stacktraceToString(e));
  863. }
  864. }
  865. folder.close(false);
  866. store.close();
  867. return emailMessageMap;
  868. }
  869. private String getSenderEmail(MimeMessage message) {
  870. Address[] senderAddress = null;
  871. try {
  872. senderAddress = message.getFrom();
  873. if (senderAddress == null || senderAddress.length == 0) {
  874. log.info("发件人获取失败=============================");
  875. return null;
  876. }
  877. // 此时的address是含有编码(MIME编码方式)后的文本和实际的邮件地址
  878. String address = "";
  879. for (Address from : senderAddress) {
  880. if (StringUtil.isNotEmpty(from.toString())) {
  881. address = from.toString();
  882. break;
  883. }
  884. }
  885. log.info("发件人地址:" + address + "========================senderAddress size:" + senderAddress.length);
  886. // 正则表达式匹配邮件地址
  887. Pattern pattern = Pattern.compile("<(\\S+)>");
  888. Matcher matcher = pattern.matcher(address);
  889. if (matcher.find()) {
  890. return matcher.group(1);
  891. }
  892. //说明匹配不到,直接获取sender
  893. Address sender = message.getSender();
  894. if (sender == null) {
  895. return address;
  896. }
  897. String senderEmail = sender.toString();
  898. log.info("senderEmail:" + senderEmail + "====================");
  899. if (senderEmail.contains("<") && senderEmail.contains(">") && senderEmail.indexOf("<") < senderEmail.indexOf(">")) {
  900. senderEmail = senderEmail.substring(senderEmail.indexOf("<") + 1, senderEmail.length() - 1);
  901. }
  902. return senderEmail;
  903. } catch (MessagingException e) {
  904. log.error(e.getMessage(), e);
  905. }
  906. return null;
  907. }
  908. public List<NameValueVO> searchEmailCount(DataboardQuery databoardQuery) {
  909. if (StringUtil.isNotEmpty(databoardQuery.getEndDate())) {
  910. databoardQuery.setEndDate(com.smppw.utils.DateUtil.getAroundDate(DateUtils.parse(databoardQuery.getEndDate(), DateUtils.YYYY_MM_DD), 1));
  911. }
  912. List<Map<String, Object>> dataList = emailParseInfoMapper.searchEmailDataBoard(databoardQuery);
  913. List<NameValueVO> result = new ArrayList<>();
  914. Long total = 0L;
  915. for (Map<String, Object> data : dataList) {
  916. NameValueVO vo = new NameValueVO();
  917. if (1 == ((Integer) data.get("parse_status")).intValue()) {
  918. vo.setValue((Long) data.get("total"));
  919. vo.setName("解析成功");
  920. } else {
  921. vo.setValue((Long) data.get("total"));
  922. vo.setName("解析失败");
  923. }
  924. total += (Long) data.get("total");
  925. result.add(vo);
  926. }
  927. NameValueVO vo = new NameValueVO();
  928. vo.setName("解析总数");
  929. vo.setValue(total);
  930. result.add(vo);
  931. return result;
  932. }
  933. public List<NameValueVO> searchEmailTypeCount(DataboardQuery databoardQuery) {
  934. if (StringUtil.isNotEmpty(databoardQuery.getEndDate())) {
  935. databoardQuery.setEndDate(com.smppw.utils.DateUtil.getAroundDate(DateUtils.parse(databoardQuery.getEndDate(), DateUtils.YYYY_MM_DD), 1));
  936. }
  937. //邮件类型,1-净值,2-估值表,3-定期报告
  938. List<Map<String, Object>> dataList = emailParseInfoMapper.searchEmailTypeCount(databoardQuery);
  939. List<NameValueVO> result = new ArrayList<>();
  940. Integer total = 0;
  941. for (Map<String, Object> data : dataList) {
  942. NameValueVO vo = new NameValueVO();
  943. Integer emailType = (Integer) data.get("email_type");
  944. Long totalType = (Long) data.get("total");
  945. if (1 == emailType) {
  946. vo.setName("净值规模");
  947. vo.setValue(totalType);
  948. } else if (2 == emailType) {
  949. vo.setName("估值表");
  950. vo.setValue(totalType);
  951. } else {
  952. vo.setName("定期报告");
  953. vo.setValue(totalType);
  954. }
  955. result.add(vo);
  956. }
  957. return result;
  958. }
  959. public EmailParseFailAnalysisVO parseFailAnalysis(DataboardQuery databoardQuery) {
  960. if (StringUtil.isNotEmpty(databoardQuery.getEndDate())) {
  961. databoardQuery.setEndDate(com.smppw.utils.DateUtil.getAroundDate(DateUtils.parse(databoardQuery.getEndDate(), DateUtils.YYYY_MM_DD), 1));
  962. }
  963. EmailParseFailAnalysisVO result = new EmailParseFailAnalysisVO();
  964. //净值规模
  965. List<NameValueVO> navNameValueVOS = new ArrayList<>();
  966. NameValueVO pdfNoDataVO = new NameValueVO();
  967. databoardQuery.setEmailType(1);
  968. Long pdfNoData = emailParseInfoMapper.countpdfNoData(databoardQuery, "无法从PDF文件中获取到数据");
  969. pdfNoDataVO.setName("无法从PDF文件中获取到数据");
  970. pdfNoDataVO.setValue(pdfNoData);
  971. navNameValueVOS.add(pdfNoDataVO);
  972. NameValueVO priceDateMissVO = new NameValueVO();
  973. Long priceDateMiss = emailParseInfoMapper.countpdfNoData(databoardQuery, "缺少净值日期");
  974. priceDateMissVO.setValue(priceDateMiss);
  975. priceDateMissVO.setName("缺少净值日期");
  976. navNameValueVOS.add(priceDateMissVO);
  977. NameValueVO navMissVO = new NameValueVO();
  978. Long navMiss = emailParseInfoMapper.countpdfNoData(databoardQuery, "单位净值和累计净值和资产净值均缺失");
  979. navMissVO.setName("单位净值和累计净值和资产净值均缺失");
  980. navMissVO.setValue(navMiss);
  981. navNameValueVOS.add(navMissVO);
  982. NameValueVO fundNameNumberMissVO = new NameValueVO();
  983. Long fundNameNumberMiss = emailParseInfoMapper.countpdfNoData(databoardQuery, "基金名称和备案编码均缺失");
  984. fundNameNumberMissVO.setName("基金名称和备案编码均缺失");
  985. fundNameNumberMissVO.setValue(fundNameNumberMiss);
  986. navNameValueVOS.add(fundNameNumberMissVO);
  987. result.setNavFailAnalysisVO(navNameValueVOS);
  988. NameValueVO templateUnSupportVO = new NameValueVO();
  989. Long templateUnSupport = emailParseInfoMapper.countpdfNoData(databoardQuery, "模板不支持");
  990. templateUnSupportVO.setName("模板不支持");
  991. templateUnSupportVO.setValue(templateUnSupport);
  992. navNameValueVOS.add(templateUnSupportVO);
  993. result.setNavFailAnalysisVO(navNameValueVOS);
  994. //估值表规模
  995. databoardQuery.setEmailType(2);
  996. List<NameValueVO> valuationNameValueVOS = new ArrayList<>();
  997. NameValueVO fileTypeErrorVO = new NameValueVO();
  998. Long fileTypeError = emailParseInfoMapper.countpdfNoData(databoardQuery, "文件格式错误");
  999. fileTypeErrorVO.setName("文件格式错误");
  1000. fileTypeErrorVO.setValue(fileTypeError);
  1001. valuationNameValueVOS.add(fileTypeErrorVO);
  1002. NameValueVO columnMissVO = new NameValueVO();
  1003. Long columnMiss = emailParseInfoMapper.countpdfNoData(databoardQuery, "无市值列或无数量列");
  1004. columnMissVO.setName("无市值列或无数量列");
  1005. columnMissVO.setValue(columnMiss);
  1006. valuationNameValueVOS.add(columnMissVO);
  1007. NameValueVO numbericMissVO = new NameValueVO();
  1008. Long numbericMiss = emailParseInfoMapper.countpdfNoData(databoardQuery, "非数值数据");
  1009. numbericMissVO.setName("非数值数据");
  1010. numbericMissVO.setValue(numbericMiss);
  1011. valuationNameValueVOS.add(numbericMissVO);
  1012. NameValueVO noDataVO = new NameValueVO();
  1013. Long noData = emailParseInfoMapper.countpdfNoData(databoardQuery, "无数据");
  1014. noDataVO.setValue(noData);
  1015. noDataVO.setName("无数据");
  1016. valuationNameValueVOS.add(noDataVO);
  1017. NameValueVO templateErrorVO = new NameValueVO();
  1018. Long templateError = emailParseInfoMapper.countpdfNoData(databoardQuery, "模板不支持");
  1019. templateErrorVO.setValue(templateError);
  1020. templateErrorVO.setName("模板不支持");
  1021. valuationNameValueVOS.add(templateErrorVO);
  1022. result.setValuationFailAnalysisVO(valuationNameValueVOS);
  1023. //定期报告
  1024. /**
  1025. * PARSE_FAIL(21000, "定期报告解析错误:{}"),
  1026. * NOT_A_REPORT(21001, "[{}]不是定期报告"),
  1027. * REPORT_IS_SCAN(21002, "报告[{}]为扫描件"),
  1028. * NO_SUPPORT_TEMPLATE(21003, "报告[{}]是不支持的文件格式"),
  1029. * NOT_A_FIXED_FORMAT(21004, "报告[{}]不是基协统一格式"),
  1030. *
  1031. * PARSE_FUND_INFO_FAIL(21010, "报告[{}]没有解析到基金基本信息"),
  1032. * PARSE_NAV_INFO_FAIL(21011, "报告[{}]没有解析到基金净值信息"),
  1033. * PARSE_FINANCIAL_INFO_FAIL(21012, "报告[{}]没有解析到基金财务指标信息"),
  1034. * PARSE_INDUSTRY_INFO_FAIL(21013, "报告[{}]没有解析到基金行业配置信息"),
  1035. * PARSE_ASSET_INFO_FAIL(21014, "报告[{}]没有解析到基金资产配置信息"),
  1036. * PARSE_SHARE_INFO_FAIL(21015, "报告[{}]没有解析到基金份额变动信息"),
  1037. */
  1038. databoardQuery.setEmailType(3);
  1039. List<NameValueVO> reportNameValueVOS = new ArrayList<>();
  1040. NameValueVO scannedFileVO = new NameValueVO();
  1041. Long scannedFile = emailParseInfoMapper.countpdfNoData(databoardQuery, "为扫描件");
  1042. scannedFileVO.setName("报告为扫描件");
  1043. scannedFileVO.setValue(scannedFile);
  1044. reportNameValueVOS.add(scannedFileVO);
  1045. NameValueVO errorAmacFileTypeVO = new NameValueVO();
  1046. Long errorAmacFileType = emailParseInfoMapper.countpdfNoData(databoardQuery, "不是基协统一格式");
  1047. errorAmacFileTypeVO.setName("报告不是基协统一格式");
  1048. errorAmacFileTypeVO.setValue(errorAmacFileType);
  1049. reportNameValueVOS.add(errorAmacFileTypeVO);
  1050. NameValueVO watermarkFileErrorVO = new NameValueVO();
  1051. Long watermarkFileError = emailParseInfoMapper.countpdfNoData(databoardQuery, "没有解析到");
  1052. watermarkFileErrorVO.setName("报告水印干扰导致部分没有解析");
  1053. watermarkFileErrorVO.setValue(watermarkFileError);
  1054. reportNameValueVOS.add(watermarkFileErrorVO);
  1055. NameValueVO noReportVO = new NameValueVO();
  1056. Long noReport = emailParseInfoMapper.countpdfNoData(databoardQuery, "不是定期报告");
  1057. noReportVO.setName("报告不是定期报告");
  1058. noReportVO.setValue(noReport);
  1059. reportNameValueVOS.add(noReportVO);
  1060. result.setReportFailAnalysisVO(reportNameValueVOS);
  1061. return result;
  1062. }
  1063. public EmailParseDataViewVO dataOverview(DataboardQuery databoardQuery) {
  1064. EmailParseDataViewVO dataViewVO = new EmailParseDataViewVO();
  1065. dataViewVO.setEmailNum(emailParseInfoMapper.countEmailTotal(null));
  1066. dataViewVO.setNavEmailNum(emailParseInfoMapper.countEmailTotal(1));
  1067. dataViewVO.setValuationEmailNum(emailParseInfoMapper.countEmailTotal(2));
  1068. dataViewVO.setReportEmailNum(emailParseInfoMapper.countEmailTotal(3));
  1069. dataViewVO.setParseNavNum(emailFundNavMapper.countEmailNavTotal());
  1070. dataViewVO.setParseAssetNum(emailFundAssetMapper.countEmailAssetTotal());
  1071. dataViewVO.setNavNum(navMapper.countNavTotal());
  1072. dataViewVO.setAssetNum(assetMapper.countAssetTotal());
  1073. dataViewVO.setDistribute(distributionMapper.countDistributionTotal());
  1074. dataViewVO.setFundNum(fundInfoMapper.countFundTotal());
  1075. dataViewVO.setCompanyNum(companyInformationMapper.countCompanyTotal());
  1076. return dataViewVO;
  1077. }
  1078. private Message[] getEmailMessage(Folder folder, String protocol, Date startDate) {
  1079. try {
  1080. if (protocol.contains("imap")) {
  1081. // 获取邮件日期大于等于startDate的邮件(搜索条件只支持按天)
  1082. SearchTerm startDateTerm = new ReceivedDateTerm(ComparisonTerm.GE, startDate);
  1083. return folder.search(startDateTerm);
  1084. } else {
  1085. return folder.getMessages();
  1086. }
  1087. } catch (MessagingException e) {
  1088. throw new RuntimeException(e);
  1089. }
  1090. }
  1091. private static class PythonData {
  1092. private Integer fileId;
  1093. private Integer status;
  1094. private String msg;
  1095. private String register_number;
  1096. public Integer getFileId() {
  1097. return fileId;
  1098. }
  1099. public void setFileId(Integer fileId) {
  1100. this.fileId = fileId;
  1101. }
  1102. public Integer getStatus() {
  1103. return status;
  1104. }
  1105. public void setStatus(Integer status) {
  1106. this.status = status;
  1107. }
  1108. public String getMsg() {
  1109. return msg;
  1110. }
  1111. public void setMsg(String msg) {
  1112. this.msg = msg;
  1113. }
  1114. public String getRegister_number() {
  1115. return register_number;
  1116. }
  1117. public void setRegister_number(String register_number) {
  1118. this.register_number = register_number;
  1119. }
  1120. }
  1121. }