4
0

6 Commits 4fa1cae6d7 ... e4d3c53a62

Autor SHA1 Nachricht Datum
  wangzaijun e4d3c53a62 fix:上传解析返回结果新增fileId字段返回 vor 3 Tagen
  wangzaijun bd8bacac86 feat:保证邮件都能下载并存表,托管方不校验印章 vor 3 Tagen
  wangzaijun 928d65a0fe fix:上传文件解析时的异常问题 vor 1 Woche
  wangzaijun 31f1606849 fix:pdf密码识别 vor 1 Woche
  wangzaijun 555cd406af feat:支持有密码的PDF解析 vor 1 Woche
  wangzaijun af542e7c92 fix:修复部分公告被定义为月报的问题 vor 1 Woche
19 geänderte Dateien mit 404 neuen und 178 gelöschten Zeilen
  1. 21 0
      mo-daq/db/init.sql
  2. 21 0
      mo-daq/src/main/java/com/smppw/modaq/application/components/ReportParseUtils.java
  3. 13 6
      mo-daq/src/main/java/com/smppw/modaq/application/service/EmailParseApiServiceImpl.java
  4. 5 0
      mo-daq/src/main/java/com/smppw/modaq/common/conts/PatternConsts.java
  5. 1 1
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/EmailContentInfoDTO.java
  6. 5 0
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/EmailInfoDTO.java
  7. 9 9
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/EmailZipFileDTO.java
  8. 5 0
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/UploadReportParams.java
  9. 3 1
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/UploadReportResult.java
  10. 20 4
      mo-daq/src/main/java/com/smppw/modaq/domain/entity/EmailFileInfoDO.java
  11. 49 0
      mo-daq/src/main/java/com/smppw/modaq/domain/entity/TgEmailConfigDO.java
  12. 4 5
      mo-daq/src/main/java/com/smppw/modaq/domain/mapper/EmailFileInfoMapper.java
  13. 4 6
      mo-daq/src/main/java/com/smppw/modaq/domain/mapper/EmailParseInfoMapper.java
  14. 9 0
      mo-daq/src/main/java/com/smppw/modaq/domain/mapper/TgEmailConfigMapper.java
  15. 204 130
      mo-daq/src/main/java/com/smppw/modaq/domain/service/EmailParseService.java
  16. 5 2
      mo-daq/src/main/java/com/smppw/modaq/infrastructure/util/PdfUtil.java
  17. 13 1
      mo-daq/src/main/resources/mapper/EmailFileInfoMapper.xml
  18. 10 10
      mo-daq/src/main/resources/mapper/EmailParseInfoMapper.xml
  19. 3 3
      mo-daq/src/test/java/com/smppw/modaq/MoDaqApplicationTests.java

Datei-Diff unterdrückt, da er zu groß ist
+ 21 - 0
mo-daq/db/init.sql


+ 21 - 0
mo-daq/src/main/java/com/smppw/modaq/application/components/ReportParseUtils.java

@@ -25,6 +25,10 @@ public final class ReportParseUtils {
             "投资者报告", "投资报告", "投资月报", "月度简报", "运行月报"
     );
 
+    public static final Set<String> MONTHLY_REPORT_KEYWORDS = Set.of(
+            "运行报告", "月策略", "投资者报告", "投资报告", "定期报告"
+    );
+
     /**
      * 基金基本信息表格列名称
      */
@@ -321,6 +325,23 @@ public final class ReportParseUtils {
     }
 
     /**
+     * 匹配邮件正文是否有pdf的解密密码
+     *
+     * @param text 邮件正文
+     * @return 返回密码
+     */
+    public static String matchPdfPwd(String text) {
+        if (StrUtil.isBlank(text)) {
+            return null;
+        }
+        Matcher matcher = PatternConsts.PDF_PWD_PATTERN.matcher(text);
+        if (matcher.find()) {
+            return matcher.group(1);
+        }
+        return null;
+    }
+
+    /**
      * 匹配基金代码
      *
      * @param text 字符串

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

@@ -24,6 +24,7 @@ import org.springframework.web.multipart.MultipartFile;
 
 import java.io.*;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Date;
 import java.util.List;
 
@@ -106,15 +107,21 @@ public class EmailParseApiServiceImpl implements EmailParseApiService {
         for (MultipartFile file : files) {
             String filename = file.getOriginalFilename();
             File saveFile = this.emailParseService.generateSavePath("upload", new Date(), filename);
-            String filepath = saveFile.getPath();
-            try (InputStream is = file.getInputStream()) {
-                Files.copy(is, saveFile.toPath());
-            } catch (IOException e) {
-                log.warn("文件{} 上传失败:{}", filename, ExceptionUtil.stacktraceToString(e));
-                dataList.add(new UploadReportResult(filepath, ReportParseStatus.FILE_UPLOAD_FAIL));
+            String filepath = saveFile.getAbsolutePath();
+            if (!saveFile.exists()) {
+                if (!saveFile.getParentFile().exists()) {
+                    saveFile.getParentFile().mkdirs();
+                }
+                try (InputStream is = file.getInputStream()) {
+                    Files.copy(is, Path.of(filepath));
+                } catch (IOException e) {
+                    log.warn("文件{} 上传失败:{}", filename, ExceptionUtil.stacktraceToString(e));
+                    dataList.add(new UploadReportResult(filepath, ReportParseStatus.FILE_UPLOAD_FAIL));
+                }
             }
             params.getReportInfos().add(new UploadReportParams.ReportInfo(filepath));
         }
+        params.setTitle("报告上传解析");
         List<UploadReportResult> tempList = this.emailParseService.uploadReportResults(params);
         dataList.addAll(tempList);
         return dataList;

+ 5 - 0
mo-daq/src/main/java/com/smppw/modaq/common/conts/PatternConsts.java

@@ -38,6 +38,11 @@ public class PatternConsts {
      */
     public static final Pattern FUND_LEVEL_PATTERN = Pattern.compile("[A-F]级|基金[A-F]");
 
+    /**
+     * 邮件正文中的PDF密码
+     */
+    public static final Pattern PDF_PWD_PATTERN = Pattern.compile("密码[::]([A-Z]{1,10})");
+
     // 正则表达式匹配单行和多行注释
     public static final Pattern JSON_COMMENT_PATTERN = Pattern.compile(
             "(\"(?:\\\\\"|[^\"])*?\")" +  // 匹配双引号内的内容(避免匹配字符串内的注释符号)

+ 1 - 1
mo-daq/src/main/java/com/smppw/modaq/domain/dto/EmailContentInfoDTO.java

@@ -72,7 +72,7 @@ public class EmailContentInfoDTO implements Serializable {
     /**
      * 附件大小byte
      */
-    private int fileSize;
+    private long fileSize;
 
     @Override
     public boolean equals(Object o) {

+ 5 - 0
mo-daq/src/main/java/com/smppw/modaq/domain/dto/EmailInfoDTO.java

@@ -26,6 +26,10 @@ public class EmailInfoDTO {
      */
     private String senderEmail;
     /**
+     * 邮件正文(主要用来解析密码或者判断报告类型)
+     */
+    private String emailContent;
+    /**
      * 当前邮件的所有附件信息(如果是压缩包则只记录解压后的文件)
      */
     private List<EmailZipFileDTO> emailFileList;
@@ -46,6 +50,7 @@ public class EmailInfoDTO {
         this.emailTitle = emailDto.getEmailTitle();
         this.emailDate = emailDto.getEmailDate();
         this.senderEmail = emailDto.getSenderEmail();
+        this.emailContent = emailDto.getEmailContent();
         this.emailFileList = emailFileList;
     }
 }

+ 9 - 9
mo-daq/src/main/java/com/smppw/modaq/domain/dto/EmailZipFileDTO.java

@@ -1,6 +1,7 @@
 package com.smppw.modaq.domain.dto;
 
 import cn.hutool.core.io.FileUtil;
+import com.smppw.modaq.application.components.ReportParseUtils;
 import lombok.Getter;
 import lombok.Setter;
 
@@ -12,11 +13,17 @@ public class EmailZipFileDTO {
     private final String filename;
     private final String filepath;
     private final long fileSize;
+    // pdf文件的密码(需要密码时)
+    @Setter
+    private String pdfPwd;
     @Setter
     private Integer emailType;
 
     private final String extName;
 
+    @Setter
+    private Integer fileId;
+
     public EmailZipFileDTO(String emailTitle, String filepath, Integer emailType) {
         File file = FileUtil.file(filepath);
         this.emailTitle = emailTitle;
@@ -27,21 +34,13 @@ public class EmailZipFileDTO {
         this.extName = FileUtil.extName(file);
     }
 
-//    public EmailZipFileDTO(String emailTitle, String filepath, String filename, int fileSize, Integer emailType) {
-//        this.emailTitle = emailTitle;
-//        this.filepath = filepath;
-//        this.emailType = emailType;
-//        this.filename = filename;
-//        this.fileSize = fileSize;
-//        this.extName = FileUtil.extName(filepath);
-//    }
-
     public EmailZipFileDTO(String emailTitle, EmailContentInfoDTO emailDto) {
         this.emailTitle = emailTitle;
         this.filepath = emailDto.getFilePath();
         this.emailType = emailDto.getEmailType();
         this.filename = emailDto.getFileName();
         this.fileSize = emailDto.getFileSize();
+        this.pdfPwd = ReportParseUtils.matchPdfPwd(emailDto.getEmailContent());
         this.extName = FileUtil.extName(filepath);
     }
 
@@ -50,6 +49,7 @@ public class EmailZipFileDTO {
         this.filepath = uploadReportInfo.getReportPath();
         this.emailType = uploadReportInfo.getReportType();
         this.filename = uploadReportInfo.getReportName();
+        this.pdfPwd = uploadReportInfo.getPdfPwd();
         this.fileSize = FileUtil.size(FileUtil.file(this.filepath));
         this.extName = FileUtil.extName(filepath);
     }

+ 5 - 0
mo-daq/src/main/java/com/smppw/modaq/domain/dto/UploadReportParams.java

@@ -34,6 +34,11 @@ public class UploadReportParams {
          */
         private Integer reportType;
         /**
+         * 如果pdf有密码,可以传密码过来
+         */
+        @Getter
+        private String pdfPwd;
+        /**
          * 报告路径,必传
          */
         private String reportPath;

+ 3 - 1
mo-daq/src/main/java/com/smppw/modaq/domain/dto/UploadReportResult.java

@@ -7,6 +7,7 @@ import lombok.Setter;
 @Setter
 @Getter
 public class UploadReportResult {
+    private Integer fileId;
     private String reportPath;
     private int status;
     private String msg;
@@ -14,7 +15,8 @@ public class UploadReportResult {
     public UploadReportResult() {
     }
 
-    public UploadReportResult(String reportPath, int status, String msg) {
+    public UploadReportResult(Integer fileId, String reportPath, int status, String msg) {
+        this.fileId = fileId;
         this.reportPath = reportPath;
         this.status = status;
         this.msg = msg;

+ 20 - 4
mo-daq/src/main/java/com/smppw/modaq/domain/entity/EmailFileInfoDO.java

@@ -1,5 +1,7 @@
 package com.smppw.modaq.domain.entity;
 
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 
@@ -11,15 +13,16 @@ public class EmailFileInfoDO {
     /**
      * 主键Id
      */
+    @TableId(value = "id")
     private Integer id;
     /**
      * 邮件id(email_parse_info.id)
      */
     private Integer emailId;
-    /**
-     * 基金id
-     */
-    private Integer fundId;
+//    /**
+//     * 基金id
+//     */
+//    private Integer fundId;
     /**
      * 附件名称
      */
@@ -37,23 +40,36 @@ public class EmailFileInfoDO {
      */
     private String aiFileId;
     /**
+     * 当前报告解析状态
+     */
+    private Integer parseStatus;
+    /**
+     * 当前报告解析失败原因
+     */
+    private String failReason;
+    /**
      * 记录的有效性;1-有效;0-无效;
      */
+    @TableField(value = "isvalid")
     private Integer isvalid;
     /**
      * 创建者Id;第一次创建时与Creator值相同,修改时与修改人值相同
      */
+    @TableField(value = "creatorid")
     private Integer creatorId;
     /**
      * 修改者Id;第一次创建时与Creator值相同,修改时与修改人值相同
      */
+    @TableField(value = "updaterid")
     private Integer updaterId;
     /**
      * 创建时间,默认第一次创建的getdate()时间
      */
+    @TableField(value = "createtime")
     private Date createTime;
     /**
      * 修改时间;第一次创建时与CreatTime值相同,修改时与修改时间相同
      */
+    @TableField(value = "updatetime")
     private Date updateTime;
 }

+ 49 - 0
mo-daq/src/main/java/com/smppw/modaq/domain/entity/TgEmailConfigDO.java

@@ -0,0 +1,49 @@
+package com.smppw.modaq.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+@Setter
+@Getter
+@TableName("mo_email_tg_config")
+public class TgEmailConfigDO {
+    /**
+     * 主键Id
+     */
+    @TableId(value = "id")
+    private Integer id;
+
+    private String tg;
+    private String email;
+
+    /**
+     * 记录的有效性;1-有效;0-无效;
+     */
+    @TableField(value = "isvalid")
+    private Integer isvalid;
+    /**
+     * 创建者Id;第一次创建时与Creator值相同,修改时与修改人值相同
+     */
+    @TableField(value = "creatorid")
+    private Integer creatorId;
+    /**
+     * 修改者Id;第一次创建时与Creator值相同,修改时与修改人值相同
+     */
+    @TableField(value = "updaterid")
+    private Integer updaterId;
+    /**
+     * 创建时间,默认第一次创建的getdate()时间
+     */
+    @TableField(value = "createtime")
+    private Date createTime;
+    /**
+     * 修改时间;第一次创建时与CreatTime值相同,修改时与修改时间相同
+     */
+    @TableField(value = "updatetime")
+    private Date updateTime;
+}

+ 4 - 5
mo-daq/src/main/java/com/smppw/modaq/domain/mapper/EmailFileInfoMapper.java

@@ -1,5 +1,6 @@
 package com.smppw.modaq.domain.mapper;
 
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.smppw.modaq.domain.entity.EmailFileInfoDO;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
@@ -8,9 +9,9 @@ import java.util.Date;
 import java.util.List;
 
 @Mapper
-public interface EmailFileInfoMapper {
+public interface EmailFileInfoMapper extends BaseMapper<EmailFileInfoDO> {
 
-    Integer insert(@Param("itemDo") EmailFileInfoDO emailFileInfoDO);
+    Integer insertById(@Param("itemDo") EmailFileInfoDO emailFileInfoDO);
 
 //    EmailFileInfoDO getEmailFileById(@Param("id") Integer fileId);
 //
@@ -32,9 +33,7 @@ public interface EmailFileInfoMapper {
 //
 //    List<String> getAllPriceDateByFileId(@Param("fileId") Integer fileId);
 
-    int updateAiParseByFileId(@Param("fileId") Integer fileId,
-                              @Param("aiParse") Boolean aiParse,
-                              @Param("aiFileId") String aiFileId);
+    int batchUpdateByFileId(@Param("entityList") List<EmailFileInfoDO> entityList);
 
     int getLetterFilenameSuccessCount(@Param("emailTitle") String emailTitle,
                                       @Param("filename") String filename);

+ 4 - 6
mo-daq/src/main/java/com/smppw/modaq/domain/mapper/EmailParseInfoMapper.java

@@ -1,5 +1,6 @@
 package com.smppw.modaq.domain.mapper;
 
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.smppw.modaq.domain.entity.EmailParseInfoDO;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
@@ -7,9 +8,9 @@ import org.apache.ibatis.annotations.Param;
 import java.util.Date;
 
 @Mapper
-public interface EmailParseInfoMapper {
+public interface EmailParseInfoMapper extends BaseMapper<EmailParseInfoDO> {
 
-    Integer insert(@Param("itemDo") EmailParseInfoDO emailParseInfoDO);
+    Integer insertAndId(@Param("itemDo") EmailParseInfoDO emailParseInfoDO);
 
     void updateParseStatus(@Param("id") Integer id, @Param("parseStatus") int parseStatus, @Param("failReason") String failReason);
 
@@ -37,8 +38,5 @@ public interface EmailParseInfoMapper {
 //
 //    EmailParseInfoDO searchEmail(EmailParseInfoDO entity);
 
-    Integer countEmailByInfoAndStatus(@Param("emailTitle") String emailTitle,
-                                      @Param("senderAddress") String senderAddress,
-                                      @Param("emailAddress") String emailAddress,
-                                      @Param("emailDate") String emailDate);
+//    Integer countEmailByInfoAndStatus(@Param("entity") EmailParseInfoDO entity);
 }

+ 9 - 0
mo-daq/src/main/java/com/smppw/modaq/domain/mapper/TgEmailConfigMapper.java

@@ -0,0 +1,9 @@
+package com.smppw.modaq.domain.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.smppw.modaq.domain.entity.TgEmailConfigDO;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface TgEmailConfigMapper extends BaseMapper<TgEmailConfigDO> {
+}

+ 204 - 130
mo-daq/src/main/java/com/smppw/modaq/domain/service/EmailParseService.java

@@ -8,6 +8,8 @@ import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.smppw.modaq.application.components.OCRReportParser;
 import com.smppw.modaq.application.components.ReportParseUtils;
 import com.smppw.modaq.application.components.report.parser.ReportParser;
@@ -31,8 +33,10 @@ import com.smppw.modaq.domain.dto.report.ocr.OCRLetterParseData;
 import com.smppw.modaq.domain.dto.report.ocr.OCRParseData;
 import com.smppw.modaq.domain.entity.EmailFileInfoDO;
 import com.smppw.modaq.domain.entity.EmailParseInfoDO;
+import com.smppw.modaq.domain.entity.TgEmailConfigDO;
 import com.smppw.modaq.domain.mapper.EmailFileInfoMapper;
 import com.smppw.modaq.domain.mapper.EmailParseInfoMapper;
+import com.smppw.modaq.domain.mapper.TgEmailConfigMapper;
 import com.smppw.modaq.infrastructure.util.ArchiveUtil;
 import com.smppw.modaq.infrastructure.util.ConvertUtil;
 import com.smppw.modaq.infrastructure.util.PdfUtil;
@@ -44,6 +48,7 @@ import jakarta.mail.search.SearchTerm;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.MediaType;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StopWatch;
 
@@ -83,6 +88,9 @@ public class EmailParseService {
             // 按需添加其他类型...
     );
 
+    private static final List<String> TG_EMAIL_LIST = ListUtil.list(false);
+
+    private final TgEmailConfigMapper tgEmailConfigMapper;
     private final EmailParseInfoMapper emailParseInfoMapper;
     private final EmailFileInfoMapper emailFileInfoMapper;
     /* 报告解析和入库的方法 */
@@ -99,14 +107,25 @@ public class EmailParseService {
     @Value("${email.read-write-seen:true}")
     private boolean readWriteSeen;
 
-    public EmailParseService(EmailParseInfoMapper emailParseInfoMapper,
+    public EmailParseService(TgEmailConfigMapper tgEmailConfigMapper,
+                             EmailParseInfoMapper emailParseInfoMapper,
                              EmailFileInfoMapper emailFileInfoMapper,
                              ReportParserFactory reportParserFactory,
                              ReportWriterFactory reportWriterFactory) {
+        this.tgEmailConfigMapper = tgEmailConfigMapper;
         this.emailParseInfoMapper = emailParseInfoMapper;
         this.emailFileInfoMapper = emailFileInfoMapper;
         this.reportParserFactory = reportParserFactory;
         this.reportWriterFactory = reportWriterFactory;
+        this.init();
+    }
+
+    public void init() {
+        LambdaQueryWrapper<TgEmailConfigDO> wrapper = Wrappers.lambdaQuery(TgEmailConfigDO.class);
+        List<TgEmailConfigDO> dataList = this.tgEmailConfigMapper.selectList(wrapper);
+        for (TgEmailConfigDO temp : dataList) {
+            TG_EMAIL_LIST.add(temp.getEmail());
+        }
     }
 
     /**
@@ -119,7 +138,8 @@ public class EmailParseService {
      */
     public void parseEmail(MailboxInfoDTO mailboxInfoDTO,
                            Date startDate, Date endDate,
-                           List<String> folderNames, List<Integer> emailTypes) {
+                           List<String> folderNames,
+                           List<Integer> emailTypes) {
         if (CollUtil.isEmpty(emailTypes)) {
             emailTypes = ListUtil.of(EmailTypeConst.REPORT_LETTER_EMAIL_TYPE);
         }
@@ -147,62 +167,70 @@ public class EmailParseService {
             }
             EmailContentInfoDTO dto = emailContentInfoDTOList.get(0);
             String emailTitle = dto.getEmailTitle();
-            log.info("开始解析邮件数据 -> 邮件主题:{},邮件日期:{}", emailTitle, dto.getEmailDate());
+            if (log.isInfoEnabled()) {
+                log.info("开始解析邮件数据 -> 邮件主题:{},邮件日期:{}", emailTitle, dto.getEmailDate());
+            }
+            Long totalSize = emailContentInfoDTOList.stream().map(EmailContentInfoDTO::getFileSize).reduce(0L, Long::sum);
+            String errMsg = null;
+            int status = 1;
             List<EmailZipFileDTO> emailFileList = ListUtil.list(false);
             EmailInfoDTO emailInfo = new EmailInfoDTO(dto, emailFileList);
-            for (EmailContentInfoDTO emailDto : emailContentInfoDTOList) {
-                // 正文不用解压附件
-                if (emailDto.getFileName() != null && emailDto.getFileName().endsWith(Constants.FILE_HTML)) {
-                    continue;
+            if (dto.getEmailContent() != null && dto.getEmailContent().contains("超大附件列表")) {
+                status = 0;
+                errMsg = "邮件中存在超大附件,需要手动处理该邮件";
+            } else {
+                for (EmailContentInfoDTO emailDto : emailContentInfoDTOList) {
+                    // 正文不用解压附件
+                    if (emailDto.getFileName() != null && emailDto.getFileName().endsWith(Constants.FILE_HTML)) {
+                        continue;
+                    }
+                    try {
+                        emailFileList.addAll(this.parseZipEmail(emailDto));
+                    } catch (IOException e) {
+                        log.error("邮件{} 压缩包解压失败:{}", emailTitle, ExceptionUtil.stacktraceToString(e));
+                        status = 0;
+                        errMsg = "压缩包解压失败";
+                    } catch (Exception e) {
+                        log.error("邮件{} 堆栈信息:{}", emailTitle, ExceptionUtil.stacktraceToString(e));
+                        status = 0;
+                        errMsg = "内部错误";
+                    }
                 }
-                try {
-                    emailFileList.addAll(this.parseZipEmail(emailDto));
-                } catch (IOException e) {
-                    log.error("压缩包解压失败:{}", ExceptionUtil.stacktraceToString(e));
-                    EmailParseInfoDO fail = buildEmailParseInfo(mailboxInfoDTO.getAccount(),
-                            dto.getEmailType(), emailInfo, emailDto.getFileSize());
-                    fail.setFailReason("压缩包解压失败");
-                    fail.setParseStatus(EmailParseStatusConst.FAIL);
-                    fail.setEmailKey(emailEntry.getKey());
-                    this.emailParseInfoMapper.insert(fail);
-                } catch (Exception e) {
-                    log.error("堆栈信息:{}", ExceptionUtil.stacktraceToString(e));
+                // 重新判断类型
+                this.recheckEmailType(emailTitle, emailFileList);
+                Iterator<EmailZipFileDTO> entryIterator = emailFileList.iterator();
+                while (entryIterator.hasNext()) {
+                    EmailZipFileDTO entry = entryIterator.next();
+                    if (!emailTypes.contains(entry.getEmailType())) {
+                        log.warn("当前邮件{} 中的报告{} 的类型{} 不在支持的任务类型{} 中,不用执行解析逻辑。",
+                                entry.getEmailTitle(), entry.getFilename(), entry.getEmailType(), emailTypes);
+                        entryIterator.remove();
+                    }
+                    ReportParserFileType fileType = ReportParserFileType.getBySuffix(entry.getExtName());
+                    if (fileType == null) {
+                        log.warn("当前邮件{} 中的文件{} 是不支持的文件格式{} 中,不用执行解析逻辑。",
+                                entry.getEmailTitle(), entry.getFilepath(), entry.getExtName());
+                        entryIterator.remove();
+                    }
                 }
             }
-            // 重新判断类型
-            for (EmailZipFileDTO emailFile : emailFileList) {
-                if (EmailTypeConst.SUPPORT_NO_OTHER_TYPES.contains(emailFile.getEmailType())) {
-                    continue;
-                }
-                Integer type = EmailUtil.getEmailTypeBySubject(emailTitle + emailFile.getFilename());
-                // 特殊月报
-                if ((Objects.equals(EmailTypeConst.NAV_EMAIL_TYPE, type)
-                        || Objects.equals(EmailTypeConst.REPORT_OTHER_TYPE, type))
-                        && (ReportParseUtils.containsAny(emailTitle, ReportParseUtils.MANAGER_KEYWORDS)
-                        || emailTitle.contains("定期报告"))) {
-                    type = EmailTypeConst.REPORT_EMAIL_TYPE;
-                }
-                // 其他报告
-                if (Objects.equals(EmailTypeConst.NAV_EMAIL_TYPE, type)) {
-                    type = EmailTypeConst.REPORT_OTHER_TYPE;
-                }
-                emailFile.setEmailType(type);
+            // 保存邮件信息
+            EmailParseInfoDO emailDo = this.buildEmailParseInfo(mailboxInfoDTO.getAccount(), emailInfo, totalSize);
+            emailDo.setEmailKey(emailEntry.getKey());
+            emailDo.setParseStatus(status);
+            emailDo.setFailReason(errMsg);
+            Integer emailId = this.saveEmailParseInfo(emailDo);
+            // 保存附件(解压后的)
+            for (EmailZipFileDTO zipFile : emailFileList) {
+                EmailFileInfoDO emailFile = this.saveEmailFileInfo(emailId, zipFile.getFilename(), zipFile.getFilepath());
+                zipFile.setFileId(emailFile.getId());
             }
-
-            Iterator<EmailZipFileDTO> entryIterator = emailFileList.iterator();
-            while (entryIterator.hasNext()) {
-                EmailZipFileDTO entry = entryIterator.next();
-                if (!emailTypes.contains(entry.getEmailType())) {
-                    log.warn("当前邮件{} 文件{} 的类型{} 不在支持的任务类型{} 中,不用执行解析逻辑。",
-                            entry.getEmailTitle(), entry.getFilepath(), entry.getEmailType(), emailTypes);
-                    entryIterator.remove();
-                }
+            if (CollUtil.isNotEmpty(emailFileList)) {
+                // 保存相关信息 -> 邮件信息表,邮件文件表,邮件净值表,邮件规模表,基金净值表
+                this.saveRelatedTable(emailId, emailInfo);
+                log.info("结束邮件解析 -> 邮箱信息:{},开始时间:{},结束时间:{}", emailEntry.getValue(),
+                        DateUtil.format(startDate, DateConst.YYYY_MM_DD_HH_MM_SS), DateUtil.format(endDate, DateConst.YYYY_MM_DD_HH_MM_SS));
             }
-
-            // 保存相关信息 -> 邮件信息表,邮件文件表,邮件净值表,邮件规模表,基金净值表
-            saveRelatedTable(emailEntry.getKey(), mailboxInfoDTO.getAccount(), emailInfo);
-            log.info("结束邮件解析 -> 邮箱信息:{},开始时间:{},结束时间:{}", emailEntry.getValue(),
-                    DateUtil.format(startDate, DateConst.YYYY_MM_DD_HH_MM_SS), DateUtil.format(endDate, DateConst.YYYY_MM_DD_HH_MM_SS));
         }
     }
 
@@ -295,33 +323,20 @@ public class EmailParseService {
     /**
      * 邮件附件解析并保存结果数据
      *
-     * @param emailKey     没封邮件的uuid
-     * @param emailAddress 发送人地址
-     * @param emailInfo    邮件信息
+     * @param emailId   邮件数据ID
+     * @param emailInfo 邮件信息
      */
-    public void saveRelatedTable(String emailKey, String emailAddress, EmailInfoDTO emailInfo) {
-        // 附件文件检查
-        Long totalSize = this.checkEmailFileInfo(emailInfo);
-        if (totalSize == null) {
-            return;
-        }
+    public void saveRelatedTable(Integer emailId, EmailInfoDTO emailInfo) {
         // 解析并保存数据
         List<ParseResult<ReportData>> dataList = ListUtil.list(true);
-        Integer emailId = this.parseResults(null, emailKey, emailAddress, totalSize, emailInfo, dataList);
+        this.parseResults(emailInfo, dataList);
 
         String failReason = null;
         int emailParseStatus = EmailParseStatusConst.SUCCESS;
         // 报告邮件有一条失败就表示整个邮件解析失败
         if (CollUtil.isNotEmpty(dataList)) {
-            // ai解析结果
-            List<ReportData> aiParaseList = dataList.stream().map(ParseResult::getData)
-                    .filter(Objects::nonNull).filter(e -> Objects.equals(true, e.getAiParse())).toList();
-            if (CollUtil.isNotEmpty(aiParaseList)) {
-                for (ReportData data : aiParaseList) {
-                    this.emailFileInfoMapper.updateAiParseByFileId(data.getBaseInfo().getFileId(),
-                            data.getAiParse(), data.getAiFileId());
-                }
-            }
+            List<EmailFileInfoDO> entityList = this.buildEmailFileInfo(dataList);
+            this.emailFileInfoMapper.batchUpdateByFileId(entityList);
             long failNum = dataList.stream().filter(e -> !Objects.equals(EmailParseStatusConst.SUCCESS, e.getStatus())).count();
             if (failNum > 0) {
                 emailParseStatus = EmailParseStatusConst.FAIL;
@@ -331,6 +346,20 @@ public class EmailParseService {
         this.emailParseInfoMapper.updateParseStatus(emailId, emailParseStatus, failReason);
     }
 
+    private List<EmailFileInfoDO> buildEmailFileInfo(List<ParseResult<ReportData>> dataList) {
+        List<EmailFileInfoDO> entityList = ListUtil.list(false);
+        for (ParseResult<ReportData> result : dataList) {
+            EmailFileInfoDO entity = new EmailFileInfoDO();
+            entity.setId(result.getData().getBaseInfo().getFileId());
+            entity.setParseStatus(result.getStatus());
+            entity.setFailReason(result.getMsg());
+            entity.setAiParse(result.getData().getAiParse());
+            entity.setAiFileId(result.getData().getAiFileId());
+            entityList.add(entity);
+        }
+        return entityList;
+    }
+
     /**
      * 上传文件解析并返回解析状态
      *
@@ -339,13 +368,14 @@ public class EmailParseService {
      */
     public List<UploadReportResult> uploadReportResults(UploadReportParams params) {
         List<ParseResult<ReportData>> dataList = ListUtil.list(false);
+        String emailTitle = params.getTitle();
         List<UploadReportParams.ReportInfo> reportInfos = params.getReportInfos();
         List<EmailZipFileDTO> dtos = ListUtil.list(false);
         for (UploadReportParams.ReportInfo e : reportInfos) {
             String reportPath = e.getReportPath();
             if (ArchiveUtil.isArchive(reportPath)) {
                 try {
-                    this.handleCompressedFiles(params.getTitle(), reportPath, e.getReportType(), dtos);
+                    this.handleCompressedFiles(emailTitle, reportPath, e.getReportType(), dtos);
                 } catch (Exception ex) {
                     log.warn("报告{} 压缩包解压失败:{}", reportPath, ExceptionUtil.stacktraceToString(ex));
                     ReportData reportData = new ReportData.DefaultReportData();
@@ -353,31 +383,61 @@ public class EmailParseService {
                     dataList.add(new ParseResult<>(ReportParseStatus.ARCHIVE_FAIL, reportData));
                 }
             } else {
-                dtos.add(new EmailZipFileDTO(params.getTitle(), reportPath, e.getReportType()));
+                dtos.add(new EmailZipFileDTO(emailTitle, reportPath, e.getReportType()));
             }
         }
-        EmailInfoDTO emailInfo = new EmailInfoDTO(params.getTitle(), dtos);
-        // 附件文件检查
-        Long totalSize = this.checkEmailFileInfo(emailInfo);
-        if (totalSize == null) {
-            return null;
+        // 重新判断类型
+        this.recheckEmailType(emailTitle, dtos);
+        EmailInfoDTO emailInfo = new EmailInfoDTO(emailTitle, dtos);
+//        // 附件文件检查
+//        Long totalSize = this.checkEmailFileInfo(emailInfo);
+//        if (totalSize == null) {
+//            return null;
+//        }
+        Long totalSize = dtos.stream().map(EmailZipFileDTO::getFileSize).reduce(0L, Long::sum);
+        EmailParseInfoDO emailDo = this.buildEmailParseInfo("upload", emailInfo, totalSize);
+        Integer emailId = this.saveEmailParseInfo(emailDo);
+        for (EmailZipFileDTO zipFile : dtos) {
+            EmailFileInfoDO emailFile = this.saveEmailFileInfo(emailId, zipFile.getFilename(), zipFile.getFilepath());
+            zipFile.setFileId(emailFile.getId());
         }
-        this.parseResults(-1, null, null, totalSize, emailInfo, dataList);
+        this.parseResults(emailInfo, dataList);
         List<UploadReportResult> resultList = ListUtil.list(false);
         for (ParseResult<ReportData> result : dataList) {
             ReportData data = result.getData();
-            resultList.add(new UploadReportResult(data.getReportPath(), result.getStatus(), result.getMsg()));
+            resultList.add(new UploadReportResult(data.getBaseInfo().getFileId(),
+                    data.getReportPath(), result.getStatus(), result.getMsg()));
         }
         return resultList;
     }
 
     /**
+     * 重新校验邮件附件的类型(用邮件主题+附件名称)
+     *
+     * @param emailTitle 邮件主题
+     * @param dtos       所有附件
+     */
+    private void recheckEmailType(String emailTitle, List<EmailZipFileDTO> dtos) {
+        for (EmailZipFileDTO emailFile : dtos) {
+            if (EmailTypeConst.SUPPORT_NO_OTHER_TYPES.contains(emailFile.getEmailType())) {
+                continue;
+            }
+            Integer type = EmailUtil.getEmailTypeBySubject(emailTitle + emailFile.getFilename());
+            // 特殊月报
+            if ((Objects.equals(EmailTypeConst.NAV_EMAIL_TYPE, type) || Objects.equals(EmailTypeConst.REPORT_OTHER_TYPE, type))
+                    && ReportParseUtils.containsAny(emailTitle, ReportParseUtils.MONTHLY_REPORT_KEYWORDS)) {
+                type = EmailTypeConst.REPORT_EMAIL_TYPE;
+            }
+            emailFile.setEmailType(type);
+        }
+    }
+
+    /**
      * 邮件信息前置处理,在解析操作执行之前的过滤逻辑和校验逻辑。返回所有附件大小汇总
      *
      * @param emailInfo 邮件信息(包含所有解压后的文件)
-     * @return 所有附件大小汇总,为null说明没有文件需要上传
      */
-    private Long checkEmailFileInfo(EmailInfoDTO emailInfo) {
+    private void checkEmailFileInfo(EmailInfoDTO emailInfo) {
         String emailTitle = emailInfo.getEmailTitle();
         List<EmailZipFileDTO> dtos = emailInfo.getEmailFileList();
         // 如果压缩包里面既有pdf又有其他格式的文件,说明其他格式的文件是不需要解析的
@@ -430,45 +490,34 @@ public class EmailParseService {
         }
         if (CollUtil.isEmpty(dtos)) {
             log.info("邮件{} 所有文件都已经解析成功过,不能重复解析了", emailTitle);
-            return null;
+            return;
         }
         if (log.isInfoEnabled()) {
             log.info("邮件{} 还有报告待解析:\n{}", emailTitle, dtos);
         }
-        return totalSize;
     }
 
     /**
      * 邮件信息保存+附件解析
      *
-     * @param emailId      邮件ID,上传解析时一定是-1
-     * @param emailKey     邮件uuid(邮箱下载解析时)
-     * @param emailAddress 接收人地址(邮箱下载解析时)
-     * @param totalSize    所有附件大小汇总
-     * @param emailInfo    邮件信息,包含附件
-     * @param resultList   解析结果
-     * @return 邮件数据ID
+     * @param emailInfo  邮件信息,包含附件
+     * @param resultList 解析结果
      */
-    private Integer parseResults(Integer emailId,
-                                 String emailKey,
-                                 String emailAddress,
-                                 long totalSize,
-                                 EmailInfoDTO emailInfo,
-                                 List<ParseResult<ReportData>> resultList) {
+    private void parseResults(EmailInfoDTO emailInfo,
+                              List<ParseResult<ReportData>> resultList) {
         String emailTitle = emailInfo.getEmailTitle();
+        String senderEmail = emailInfo.getSenderEmail();
         List<EmailZipFileDTO> dtos = emailInfo.getEmailFileList();
-        if (emailId == null) {
-            // 保存邮件信息
-            Integer emailType = dtos.get(0).getEmailType();
-            EmailParseInfoDO emailParseInfoDO = this.buildEmailParseInfo(emailAddress, emailType, emailInfo, totalSize);
-            emailParseInfoDO.setEmailKey(emailKey);
-            emailId = this.saveEmailParseInfo(emailParseInfoDO);
+        if (CollUtil.isEmpty(dtos)) {
+            return;
         }
+        // 附件文件检查
+        this.checkEmailFileInfo(emailInfo);
         // 解析邮件报告
         for (EmailZipFileDTO zipFile : dtos) {
-            EmailFileInfoDO emailFile = this.saveEmailFileInfo(emailId, zipFile.getFilename(), zipFile.getFilepath());
+//            EmailFileInfoDO emailFile = this.saveEmailFileInfo(emailId, zipFile.getFilename(), zipFile.getFilepath());
             // 解析并保存报告
-            ParseResult<ReportData> parseResult = this.parseReportAndHandleResult(emailTitle, emailFile.getId(), zipFile);
+            ParseResult<ReportData> parseResult = this.parseReportAndHandleResult(emailTitle, senderEmail, zipFile);
             if (!Objects.equals(1, parseResult.getStatus())) {
                 log.error(parseResult.getMsg());
             }
@@ -478,20 +527,19 @@ public class EmailParseService {
             parseResult.getData().setReportPath(zipFile.getFilepath());
             resultList.add(parseResult);
         }
-        return emailId;
     }
 
     /**
      * 解析报告并保存解析结果
      *
      * @param emailTitle 邮件主题
-     * @param fileId     当前文件数据库ID
      * @param zipFile    当前报告的路径信息
      * @return /
      */
     private ParseResult<ReportData> parseReportAndHandleResult(String emailTitle,
-                                                               Integer fileId,
+                                                               String senderEmail,
                                                                EmailZipFileDTO zipFile) {
+        Integer fileId = zipFile.getFileId();
         Integer emailType = zipFile.getEmailType();
         String fileName = zipFile.getFilename();
         String filepath = zipFile.getFilepath();
@@ -535,7 +583,7 @@ public class EmailParseService {
             try {
                 String output = filepath.replaceAll("archive|original", "image");
                 File outputFile = FileUtil.file(FileUtil.getParent(output, 1));
-                images = PdfUtil.convertFirstAndLastPagesToPng(filepath, outputFile, 300);
+                images = PdfUtil.convertFirstAndLastPagesToPng(filepath, outputFile, 300, zipFile.getPdfPwd());
                 if (log.isDebugEnabled()) {
                     log.debug("报告{} 生成的图片地址是:\n{}", fileName, images);
                 }
@@ -611,7 +659,7 @@ public class EmailParseService {
                 }
             }
             // ocr信息提取(印章、联系人、基金名称和产品代码)
-            reportData = this.ocrReportData(fileId, reportType, reportData, fileName, images);
+            reportData = this.ocrReportData(fileId, reportType, reportData, fileName, senderEmail, images);
             if (log.isInfoEnabled()) {
                 log.info("报告{} 解析耗时{}ms,结果是:{}", fileName, (System.currentTimeMillis() - start), reportData);
             }
@@ -683,6 +731,7 @@ public class EmailParseService {
                                      ReportType reportType,
                                      ReportData reportData,
                                      String fileName,
+                                     String senderEmail,
                                      List<String> images) {
         if (CollUtil.isEmpty(images)) {
             return reportData;
@@ -703,14 +752,16 @@ public class EmailParseService {
                 log.error("报告{} OCR识别印章和联系人出错:{}", fileName, e.getMessage());
             }
             // ocr识别尾页是否包含印章和联系人信息
-            if (parseRes != null) {
-                if (reportData.getBaseInfo() != null) {
+            if (parseRes != null && reportData.getBaseInfo() != null) {
+                if (TG_EMAIL_LIST.contains(senderEmail)) {
+                    reportData.getBaseInfo().setWithSeals(true);
+                } else {
                     reportData.getBaseInfo().setWithSeals(parseRes.getWithSeals());
-                    reportData.getBaseInfo().setWithContacts(parseRes.getWithContacts());
                     if (fileName.contains("用印") && !Objects.equals(true, reportData.getBaseInfo().getWithSeals())) {
                         reportData.getBaseInfo().setWithSeals(true);
                     }
                 }
+                reportData.getBaseInfo().setWithContacts(parseRes.getWithContacts());
             }
             // 首页和尾页不相等时解析首页的数据
             if (images.size() != 1) {
@@ -846,10 +897,19 @@ public class EmailParseService {
         EmailFileInfoDO emailFileInfoDO = buildEmailFileInfoDO(emailId, fileName, filePath);
         emailFileInfoDO.setAiFileId(null);
         if (emailFileInfoDO.getId() != null) {
-            emailFileInfoMapper.updateTimeById(null, new Date());
+            this.emailFileInfoMapper.updateTimeById(emailFileInfoDO.getId(), new Date());
             return emailFileInfoDO;
         }
-        emailFileInfoMapper.insert(emailFileInfoDO);
+
+        LambdaQueryWrapper<EmailFileInfoDO> wrapper = Wrappers.lambdaQuery(EmailFileInfoDO.class)
+                .eq(EmailFileInfoDO::getEmailId, emailId)
+                .eq(EmailFileInfoDO::getFileName, fileName)
+                .eq(EmailFileInfoDO::getFilePath, filePath);
+        List<EmailFileInfoDO> tempList = this.emailFileInfoMapper.selectList(wrapper);
+        if (CollUtil.isNotEmpty(tempList)) {
+            return tempList.get(0);
+        }
+        this.emailFileInfoMapper.insertById(emailFileInfoDO);
         return emailFileInfoDO;
     }
 
@@ -873,15 +933,26 @@ public class EmailParseService {
         }
         // 重新邮件功能 -> 修改解析时间和更新时间
         if (emailParseInfoDO.getId() != null) {
-            emailParseInfoMapper.updateParseTime(emailParseInfoDO.getId(), emailParseInfoDO.getParseDate());
+            this.emailParseInfoMapper.updateParseTime(emailParseInfoDO.getId(), emailParseInfoDO.getParseDate());
             return emailParseInfoDO.getId();
         }
-        emailParseInfoMapper.insert(emailParseInfoDO);
+
+        LambdaQueryWrapper<EmailParseInfoDO> wrapper = Wrappers.lambdaQuery(EmailParseInfoDO.class)
+                .eq(EmailParseInfoDO::getEmailTitle, emailParseInfoDO.getEmailTitle())
+                .eq(EmailParseInfoDO::getSenderEmail, emailParseInfoDO.getSenderEmail())
+                .eq(EmailParseInfoDO::getEmailDate, emailParseInfoDO.getEmailDate())
+                .eq(EmailParseInfoDO::getEmail, emailParseInfoDO.getEmail())
+                .orderByDesc(EmailParseInfoDO::getId);
+        List<EmailParseInfoDO> tempList = this.emailParseInfoMapper.selectList(wrapper);
+        if (CollUtil.isNotEmpty(tempList)) {
+            this.emailParseInfoMapper.update(emailParseInfoDO, wrapper);
+            return tempList.get(0).getId();
+        }
+        this.emailParseInfoMapper.insertAndId(emailParseInfoDO);
         return emailParseInfoDO.getId();
     }
 
-    private EmailParseInfoDO buildEmailParseInfo(String emailAddress, Integer emailType,
-                                                 EmailInfoDTO emailInfo, long totalSize) {
+    private EmailParseInfoDO buildEmailParseInfo(String emailAddress, EmailInfoDTO emailInfo, long totalSize) {
         EmailParseInfoDO emailParseInfoDO = new EmailParseInfoDO();
         emailParseInfoDO.setId(null);
         emailParseInfoDO.setSenderEmail(emailInfo.getSenderEmail());
@@ -889,7 +960,6 @@ public class EmailParseService {
         emailParseInfoDO.setEmailDate(DateUtil.parse(emailInfo.getEmailDate(), DateConst.YYYY_MM_DD_HH_MM_SS));
         emailParseInfoDO.setParseDate(new Date());
         emailParseInfoDO.setEmailTitle(emailInfo.getEmailTitle());
-        emailParseInfoDO.setEmailType(emailType);
         emailParseInfoDO.setParseStatus(EmailParseStatusConst.SUCCESS);
         emailParseInfoDO.setAttrSize(totalSize);
         emailParseInfoDO.setIsvalid(1);
@@ -1001,12 +1071,11 @@ public class EmailParseService {
                 if (log.isInfoEnabled()) {
                     log.info("{} 邮件{} 基本信息获取完成,开始下载附件!邮件日期:{}", folderName, emailTitle, emailDateStr);
                 }
-                Object content = message.getContent();
 
-                if (content instanceof Multipart multipart) {
-                    this.reMultipart(emailAddress, emailTitle, emailDate, multipart, dtos);
-                } else if (content instanceof Part part) {
-                    this.rePart(emailAddress, emailTitle, emailDate, part, dtos);
+                Object messageContent = message.getContent();
+                String[] contents = new String[]{null};
+                if (messageContent instanceof Multipart multipart) {
+                    this.reMultipart(emailAddress, emailTitle, emailDate, multipart, contents, dtos);
                 } else {
                     log.warn("{} 邮件{} 获取不了附件", folderName, emailTitle);
                 }
@@ -1017,6 +1086,7 @@ public class EmailParseService {
                 dtos.forEach(e -> {
                     e.setEmailType(emailType);
                     e.setSenderEmail(senderEmail);
+                    e.setEmailContent(contents[0]);
                 });
                 emailMessageMap.put(IdUtil.simpleUUID(), dtos);
             } catch (Exception e) {
@@ -1101,19 +1171,23 @@ public class EmailParseService {
         return FileUtil.file(filePath + realName);
     }
 
-    private void reMultipart(String account, String subject, Date emailDate, Multipart multipart,
+    private void reMultipart(String account, String subject, Date emailDate,
+                             Multipart multipart, String[] contents,
                              List<EmailContentInfoDTO> emailContentInfoDTOList) throws Exception {
         for (int i = 0; i < multipart.getCount(); i++) {
             Part bodyPart = multipart.getBodyPart(i);
-            Object content = bodyPart.getContent();
-            if (content instanceof String) {
+            Object bodyPartContent = bodyPart.getContent();
+            if (bodyPartContent instanceof String) {
                 if (log.isDebugEnabled()) {
-                    log.debug("邮件{} 获取的正文不做解析,内容是 {}", subject, content);
+                    log.debug("邮件{} 获取的正文不做解析,内容是 {}", subject, bodyPartContent);
+                }
+                if (StrUtil.startWithIgnoreCase(bodyPart.getContentType(), MediaType.TEXT_PLAIN_VALUE)) {
+                    contents[0] = bodyPartContent.toString();
                 }
                 continue;
             }
-            if (content instanceof Multipart mp) {
-                this.reMultipart(account, subject, emailDate, mp, emailContentInfoDTOList);
+            if (bodyPartContent instanceof Multipart mp) {
+                this.reMultipart(account, subject, emailDate, mp, contents, emailContentInfoDTOList);
             } else {
                 this.rePart(account, subject, emailDate, bodyPart, emailContentInfoDTOList);
             }

+ 5 - 2
mo-daq/src/main/java/com/smppw/modaq/infrastructure/util/PdfUtil.java

@@ -67,10 +67,13 @@ public class PdfUtil {
      * @param dpi         图片分辨率(默认建议 300)
      * @return 生成的图片文件列表
      */
-    public static List<String> convertFirstAndLastPagesToPng(String pdfFilepath, File outputDir, int dpi) throws IOException {
+    public static List<String> convertFirstAndLastPagesToPng(String pdfFilepath,
+                                                             File outputDir,
+                                                             int dpi,
+                                                             String password) throws IOException {
         List<String> generatedImages = ListUtil.list(false);
 
-        try (PDDocument document = Loader.loadPDF(new RandomAccessReadBufferedFile(pdfFilepath))) {
+        try (PDDocument document = Loader.loadPDF(new RandomAccessReadBufferedFile(pdfFilepath), password)) {
             int totalPages = document.getNumberOfPages();
             if (totalPages == 0) {
                 throw new IOException("PDF 文件无有效页面");

+ 13 - 1
mo-daq/src/main/resources/mapper/EmailFileInfoMapper.xml

@@ -15,7 +15,7 @@
         <result column="updatetime" property="updateTime"/>
     </resultMap>
 
-    <insert id="insert" parameterType="com.smppw.modaq.domain.entity.EmailFileInfoDO" useGeneratedKeys="true"
+    <insert id="insertById" parameterType="com.smppw.modaq.domain.entity.EmailFileInfoDO" useGeneratedKeys="true"
             keyProperty="id" keyColumn="id">
         insert into mo_email_file_info(email_id, file_name, file_path,
                                               isvalid, creatorid, createtime, updaterid, updatetime)
@@ -235,6 +235,18 @@
           and id = #{fileId}
     </update>
 
+    <update id="batchUpdateByFileId">
+        <foreach collection="entityList" item="entity">
+            update mo_email_file_info
+            set ai_file_id = #{entity.aiFileId},
+                ai_parse = #{entity.aiParse},
+                parse_status = #{entity.parseStatus},
+                fail_reason = #{entity.failReason},
+                updatetime = now()
+            where id = #{entity.id};
+        </foreach>
+    </update>
+
     <select id="getLetterFilenameSuccessCount" resultType="int">
         select count(1)
         from mo_email_file_info a

+ 10 - 10
mo-daq/src/main/resources/mapper/EmailParseInfoMapper.xml

@@ -22,7 +22,7 @@
     </resultMap>
 
 
-    <insert id="insert" parameterType="com.smppw.modaq.domain.entity.EmailParseInfoDO" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
+    <insert id="insertAndId" parameterType="com.smppw.modaq.domain.entity.EmailParseInfoDO" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
         insert into mo_email_parse_info(email,email_key, sender_email, email_date, parse_date, email_title, email_type, parse_status, fail_reason,
                                         attr_size,isvalid, creatorid, createtime, updaterid, updatetime)
         values (#{itemDo.email}, #{itemDo.emailKey}, #{itemDo.senderEmail}, #{itemDo.emailDate}, #{itemDo.parseDate}, #{itemDo.emailTitle}, #{itemDo.emailType}, #{itemDo.parseStatus},
@@ -256,13 +256,13 @@
 <!--        limit 1-->
 <!--    </select>-->
 
-    <select id="countEmailByInfoAndStatus" resultType="java.lang.Integer">
-        select count(1)
-        from mo_email_parse_info t
-        where t.sender_email = #{senderAddress}
-          and t.email = #{emailAddress}
-          and t.email_date = #{emailDate}
-          and t.email_title = #{emailTitle}
-          and t.parse_status = 1
-    </select>
+<!--    <select id="countEmailByInfoAndStatus" resultType="java.lang.Integer">-->
+<!--        select count(1)-->
+<!--        from mo_email_parse_info t-->
+<!--        where t.sender_email = #{entity.senderAddress}-->
+<!--          and t.email = #{entity.email}-->
+<!--          and t.email_date = #{entity.emailDate}-->
+<!--          and t.email_title = #{entity.emailTitle}-->
+<!--          and t.parse_status = 1-->
+<!--    </select>-->
 </mapper>

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

@@ -41,9 +41,9 @@ public class MoDaqApplicationTests {
 
     @Test
     public void reportTest() {
-        MailboxInfoDTO emailInfoDTO = this.buildMailbox("**@simuwang.com", "**");
-        Date startDate = DateUtil.parse("2025-06-18 08:47:00", DateConst.YYYY_MM_DD_HH_MM_SS);
-        Date endDate = DateUtil.parse("2025-06-18 13:57:00", DateConst.YYYY_MM_DD_HH_MM_SS);
+        MailboxInfoDTO emailInfoDTO = this.buildMailbox("*@simuwang.com", "*");
+        Date startDate = DateUtil.parse("2025-06-19 10:30:00", DateConst.YYYY_MM_DD_HH_MM_SS);
+        Date endDate = DateUtil.parse("2025-06-19 17:57:00", DateConst.YYYY_MM_DD_HH_MM_SS);
         try {
             List<String> folderNames = ListUtil.list(false);
 //            folderNames.add("其他文件夹/报告公告");