34 Achegas 9bfeb2d75c ... f9a054063b

Autor SHA1 Mensaxe Data
  wangzaijun f9a054063b 合并代码以develop分支为主 hai 4 días
  wangzaijun c2986574d1 fix:sql脚本更新 hai 4 días
  wangzaijun d1d5797048 fix:优化ai提示词+压缩文件乱码问题处理 hai 4 días
  wangzaijun 587df00589 fix:定时任务执行时尽可能往前找3分钟保证覆盖到全部邮件 hai 5 días
  wangzaijun d7ebfd4660 fix:管理人观点和周报解析 hai 4 días
  wangzaijun b32768cf3f feat:AI解析观点报告的基本信息就好 hai 5 días
  wangzaijun a80d5d379e feat:报告支持AI hai 5 días
  wangzaijun b8fa6bdb5e 合并 hai 6 días
  wangzaijun a0f10fe7a7 feat:优化解析逻辑当代码无法解析时用AI解析 hai 6 días
  wangzaijun ffedbc403d fix:扫描件直接去ai解析 hai 1 semana
  wangzaijun d34dfe19a0 fix:邮件解析失败任务 hai 1 semana
  wangzaijun 34fba9b191 fix:任务执行时间不要重复 hai 1 semana
  wangzaijun 706f9328c7 fix:修复邮箱附件下载很慢的问题 hai 1 semana
  wangzaijun a6419b46d8 fix:优化 hai 1 semana
  wangzaijun cf5311ca2a fix:优化 hai 1 semana
  wangzaijun 5ab314aa79 sf hai 1 semana
  wangzaijun 5ea323f537 fix:优化确认函解析逻辑 hai 1 semana
  wangzaijun ed0931fb7b fix:优化 hai 1 semana
  wangzaijun 16c97c0100 fix:加日志 hai 1 semana
  wangzaijun 50c2935703 fix:加日志 hai 1 semana
  wangzaijun 5b422fdbe2 fix:申万宏源证券的基金名称识别 hai 1 semana
  wangzaijun a32132c8ff fix:支持申万宏源证券托管的确认单 hai 1 semana
  wangzaijun 35a8ce309f fix:优化定期报告解析功能 hai 1 semana
  wangzaijun 3717e90ab8 fix:任务优化 hai 1 semana
  wangzaijun 2451b9615d fix:任务记录表 hai 1 semana
  wangzaijun 8d6f88ac8f s hai 2 semanas
  wangzaijun 5d5407f377 feat:报告解析 hai 2 semanas
  wangzaijun 4cc01ff6ba feat:支持定期报告解析 hai 2 semanas
  wangzaijun 543a554647 删除日志 hai 2 semanas
  wangzaijun c418567ce0 fix:确认函再支持 hai 2 semanas
  wangzaijun 2e7f0fcba1 fix:定期报告解析 hai 2 semanas
  wangzaijun 1a66921b8a feat:支持定期报告解析 hai 3 semanas
  wangzaijun b860a68b65 feat:报告解析开始 hai 4 semanas
  wangzaijun d16855660d feat:新增月报和周报解析功能--开始 hai 1 mes
Modificáronse 75 ficheiros con 3041 adicións e 2278 borrados
  1. 17 0
      Dockerfile
  2. 362 419
      mo-daq/db/init.sql
  3. 31 21
      mo-daq/src/main/java/com/smppw/modaq/application/components/CustomPDFTextStripper.java
  4. 1 2
      mo-daq/src/main/java/com/smppw/modaq/application/components/CustomTabulaTextStripper.java
  5. 161 193
      mo-daq/src/main/java/com/smppw/modaq/application/components/ReportParseUtils.java
  6. 10 7
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/AbstractReportParser.java
  7. 18 3
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/ReportParserConstant.java
  8. 1 1
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/ReportParserFactory.java
  9. 38 0
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/ai/AIAnnuallyReportParser.java
  10. 12 16
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/ai/AILetterReportParser.java
  11. 65 0
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/ai/AIMonthlyReportParser.java
  12. 44 0
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/ai/AIOtherReportParser.java
  13. 38 0
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/ai/AIQuarterlyReportParser.java
  14. 41 0
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/ai/AIWeeklyReportParser.java
  15. 58 23
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/ai/AbstractAIReportParser.java
  16. 117 86
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/pdf/AbstractPDReportParser.java
  17. 150 143
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/pdf/PDAnnuallyReportParser.java
  18. 1 8
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/pdf/PDLetterReportParser.java
  19. 100 89
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/pdf/PDMonthlyReportParser.java
  20. 289 266
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/pdf/PDQuarterlyReportParser.java
  21. 2 2
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/writer/AbstractReportWriter.java
  22. 14 14
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/writer/AnnuallyReportWriter.java
  23. 4 4
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/writer/LetterReportWriter.java
  24. 35 25
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/writer/MonthlyReportWriter.java
  25. 18 0
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/writer/OtherReportWriter.java
  26. 62 62
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/writer/QuarterlyReportWriter.java
  27. 9 0
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/writer/ReportWriterConstant.java
  28. 18 0
      mo-daq/src/main/java/com/smppw/modaq/application/components/report/writer/WeeklyReportWriter.java
  29. 161 173
      mo-daq/src/main/java/com/smppw/modaq/application/util/EmailUtil.java
  30. 9 0
      mo-daq/src/main/java/com/smppw/modaq/common/conts/EmailTypeConst.java
  31. 9 7
      mo-daq/src/main/java/com/smppw/modaq/common/enums/ReportParseStatus.java
  32. 2 1
      mo-daq/src/main/java/com/smppw/modaq/common/enums/ReportParserFileType.java
  33. 16 13
      mo-daq/src/main/java/com/smppw/modaq/common/enums/ReportType.java
  34. 8 1
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/EmailContentInfoDTO.java
  35. 1 0
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/EmailZipFileDTO.java
  36. 0 50
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/QuartzBean.java
  37. 18 18
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/AnnuallyReportData.java
  38. 32 11
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/BaseReportDTO.java
  39. 32 32
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/BaseReportLevelDTO.java
  40. 2 1
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/LetterReportData.java
  41. 30 25
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/MonthlyReportData.java
  42. 41 41
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/QuarterlyReportData.java
  43. 61 61
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/ReportAssetAllocationDTO.java
  44. 8 4
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/ReportBaseInfoDTO.java
  45. 2 1
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/ReportData.java
  46. 92 92
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/ReportFinancialIndicatorsDTO.java
  47. 83 0
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/ReportFundInfoDTO.java
  48. 5 3
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/ReportFundTransactionDTO.java
  49. 61 61
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/ReportInvestmentIndustryDTO.java
  50. 72 72
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/ReportNetReportDTO.java
  51. 72 72
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/ReportShareChangeDTO.java
  52. 19 0
      mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/WeeklyReportData.java
  53. 4 0
      mo-daq/src/main/java/com/smppw/modaq/domain/entity/EmailParseInfoDO.java
  54. 34 0
      mo-daq/src/main/java/com/smppw/modaq/domain/entity/report/ReportAssetAllocationDO.java
  55. 4 0
      mo-daq/src/main/java/com/smppw/modaq/domain/entity/report/ReportBaseInfoDO.java
  56. 26 0
      mo-daq/src/main/java/com/smppw/modaq/domain/entity/report/ReportFinancialIndicatorsDO.java
  57. 66 0
      mo-daq/src/main/java/com/smppw/modaq/domain/entity/report/ReportFundInfoDO.java
  58. 42 0
      mo-daq/src/main/java/com/smppw/modaq/domain/entity/report/ReportInvestmentIndustryDO.java
  59. 38 0
      mo-daq/src/main/java/com/smppw/modaq/domain/entity/report/ReportNetReportDO.java
  60. 42 0
      mo-daq/src/main/java/com/smppw/modaq/domain/entity/report/ReportShareChangeDO.java
  61. 9 0
      mo-daq/src/main/java/com/smppw/modaq/domain/mapper/report/ReportAssetAllocationMapper.java
  62. 1 1
      mo-daq/src/main/java/com/smppw/modaq/domain/mapper/ReportBaseInfoMapper.java
  63. 9 0
      mo-daq/src/main/java/com/smppw/modaq/domain/mapper/report/ReportFinancialIndicatorMapper.java
  64. 1 1
      mo-daq/src/main/java/com/smppw/modaq/domain/mapper/ReportFundInfoMapper.java
  65. 1 1
      mo-daq/src/main/java/com/smppw/modaq/domain/mapper/ReportFundTransactionMapper.java
  66. 9 0
      mo-daq/src/main/java/com/smppw/modaq/domain/mapper/report/ReportInvestmentIndustryMapper.java
  67. 1 1
      mo-daq/src/main/java/com/smppw/modaq/domain/mapper/ReportInvestorInfoMapper.java
  68. 9 0
      mo-daq/src/main/java/com/smppw/modaq/domain/mapper/report/ReportNetReportMapper.java
  69. 9 0
      mo-daq/src/main/java/com/smppw/modaq/domain/mapper/report/ReportShareChangeMapper.java
  70. 114 75
      mo-daq/src/main/java/com/smppw/modaq/domain/service/EmailParseService.java
  71. 58 61
      mo-daq/src/main/java/com/smppw/modaq/infrastructure/util/ExcelUtil.java
  72. 3 8
      mo-daq/src/main/java/com/smppw/modaq/infrastructure/util/FileUtil.java
  73. 1 1
      mo-daq/src/main/resources/logback.xml
  74. 5 3
      mo-daq/src/main/resources/mapper/EmailParseInfoMapper.xml
  75. 3 3
      mo-daq/src/test/java/com/smppw/modaq/MoDaqApplicationTests.java

+ 17 - 0
Dockerfile

@@ -0,0 +1,17 @@
+# 使用 OpenJDK 官方镜像作为基础镜像
+FROM openjdk:17-jdk-slim
+
+# 创建日志目录并设置权限
+RUN mkdir -p /home/wwwroot/modaq/logs && chmod -R 755 /home/wwwroot/modaq/logs
+
+# 设置工作目录
+WORKDIR /home/wwwroot/modaq
+
+# 复制构建好的 JAR 文件到容器中
+COPY mo-daq/target/*.jar /home/wwwroot/modaq/app.jar
+
+# 暴露 Spring Boot 默认端口(根据实际项目修改)
+EXPOSE 8080
+
+# 启动命令
+ENTRYPOINT ["java","-jar","app.jar"]

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 362 - 419
mo-daq/db/init.sql


+ 31 - 21
mo-daq/src/main/java/com/smppw/modaq/application/components/CustomPDFTextStripper.java

@@ -9,8 +9,6 @@ import org.apache.pdfbox.text.TextPosition;
 import java.io.IOException;
 import java.util.List;
 
-import static com.smppw.modaq.common.conts.Constants.WATERMARK_REPLACE;
-
 /**
  * @author wangzaijun
  * @date 2024/9/12 14:00
@@ -18,35 +16,47 @@ import static com.smppw.modaq.common.conts.Constants.WATERMARK_REPLACE;
  * @see CustomTabulaTextStripper 区别于表格文字去水印的实现
  */
 public class CustomPDFTextStripper extends PDFTextStripper {
+    private final boolean sortByPosition;
+    private final String wordSeparator;
+
+    public CustomPDFTextStripper(boolean sortByPosition, String wordSeparator) {
+        this.sortByPosition = sortByPosition;
+        this.wordSeparator = wordSeparator;
+    }
+
+    @Override
+    public boolean getSortByPosition() {
+        return sortByPosition;
+    }
+
+    @Override
+    public String getWordSeparator() {
+        return wordSeparator;
+    }
+
     @Override
     protected void writeString(String text, List<TextPosition> textPositions) throws IOException {
         // 水印文字基本都是有角度的,统计有旋转角度的文字高度
-        List<Float> heights = ListUtil.list(false);
+        List<TextPosition> rotationTexts = ListUtil.list(false);
+        // 去除水印后的文字
+        List<String> newTexts = ListUtil.list(false);
         for (TextPosition textPosition : textPositions) {
             float[][] values = textPosition.getTextMatrix().getValues();
             double degrees = Math.toDegrees(Math.atan2(values[0][1], values[0][0]));
+            // 如果旋转角度除90的余数大于0.1就说明是水印文字
             if (degrees % 90. > 0.1) {
-                heights.add(textPosition.getHeight());
+                rotationTexts.add(textPosition);
+            } else {
+                String unicode = textPosition.getUnicode();
+                if (StrUtil.isBlank(unicode)) {
+                    continue;
+                }
+                newTexts.add(unicode);
             }
         }
         // 集合为空表示text的内容没有水印影响,直接输出该内容
-        if (CollUtil.isEmpty(heights)) {
-            super.writeString(text);
-            return;
-        }
-        // 如果全是水印文字则直接去除
-        if (textPositions.size() == heights.size()) {
-            super.writeString(WATERMARK_REPLACE);
-            return;
-        }
-        // 否则去除水印(文字没有旋转角度,并且水印字体大小没有包含当前文字时说明是正常文字;否则识别为水印并用特殊符号代替)
-        List<String> newTexts = ListUtil.list(false);
-        for (TextPosition textPosition : textPositions) {
-            float[][] values = textPosition.getTextMatrix().getValues();
-            double degrees = Math.toDegrees(Math.atan2(values[0][1], values[0][0]));
-            float height = textPosition.getHeight();
-            newTexts.add(degrees % 90. <= 0.1 && !heights.contains(height) ? textPosition.getUnicode() : WATERMARK_REPLACE);
+        if (CollUtil.isNotEmpty(newTexts)) {
+            super.writeString(String.join(StrUtil.EMPTY, newTexts));
         }
-        super.writeString(String.join(StrUtil.EMPTY, newTexts));
     }
 }

+ 1 - 2
mo-daq/src/main/java/com/smppw/modaq/application/components/CustomTabulaTextStripper.java

@@ -15,7 +15,6 @@ import technology.tabula.Utils;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Objects;
 
 /**
  * @author wangzaijun
@@ -80,7 +79,7 @@ public class CustomTabulaTextStripper extends TextStripper {
             float h = textPosition.getHeightDir();
 
             if (c.equals(NBSP)) { // replace non-breaking space for space
-                c = " ";
+                c = "";
             }
 
             // 文字没有旋转角度,并且水印字体大小没有包含当前文字时说明是正常文字

+ 161 - 193
mo-daq/src/main/java/com/smppw/modaq/application/components/ReportParseUtils.java

@@ -1,6 +1,7 @@
 package com.smppw.modaq.application.components;
 
 import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.StrUtil;
 import com.smppw.modaq.common.conts.Constants;
 import com.smppw.modaq.common.enums.ReportParseStatus;
@@ -18,6 +19,7 @@ import technology.tabula.extractors.SpreadsheetExtractionAlgorithm;
 import java.io.IOException;
 import java.util.Calendar;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -25,137 +27,137 @@ import java.util.stream.Collectors;
 
 
 public final class ReportParseUtils {
-//    /**
-//     * 行业配置的表格列名称
-//     */
-//    public static final List<String> INDUSTRY_COLUMN_NAMES = ListUtil.list(false);
-//    /**
-//     * 份额变动的表格列名称
-//     */
-//    public static final List<String> SHARE_CHANGE_COLUMN_NAMES = ListUtil.list(false);
-//    /**
-//     * 主要财务指标识别列名称
-//     */
-//    public static final List<String> FINANCIAL_INDICATORS_COLUMN_NAMES = ListUtil.list(false);
-//    /**
-//     * 资产配置明细和大类关系映射
-//     */
-//    public static final Map<String, String> ASSET_ALLOCATION_TYPE_MAPPER = MapUtil.newHashMap(32, true);
-//
-//    static {
-//        // 财务指标
-//        FINANCIAL_INDICATORS_COLUMN_NAMES.add("期末基金净资产");
-//        FINANCIAL_INDICATORS_COLUMN_NAMES.add("期末基金资产净值");
-//
-//        FINANCIAL_INDICATORS_COLUMN_NAMES.add("报告期期末单位净值");
-//        FINANCIAL_INDICATORS_COLUMN_NAMES.add("期末基金份额净值");
-//
-//        FINANCIAL_INDICATORS_COLUMN_NAMES.add("本期利润");
-//        FINANCIAL_INDICATORS_COLUMN_NAMES.add("本期已实现收益");
-//        FINANCIAL_INDICATORS_COLUMN_NAMES.add("期末可供分配利润");
-//        FINANCIAL_INDICATORS_COLUMN_NAMES.add("期末可供分配基金份额利润");
-//        FINANCIAL_INDICATORS_COLUMN_NAMES.add("基金份额累计净值增长率");
-//
-//        // 中国证监会行业标准
-//        INDUSTRY_COLUMN_NAMES.add("农、林、牧、渔业");
-//        INDUSTRY_COLUMN_NAMES.add("采矿业");
-//        INDUSTRY_COLUMN_NAMES.add("制造业");
-//        INDUSTRY_COLUMN_NAMES.add("电力、热力、燃气及水生产和供应业");
-//        INDUSTRY_COLUMN_NAMES.add("建筑业");
-//        INDUSTRY_COLUMN_NAMES.add("批发和零售业");
-//        INDUSTRY_COLUMN_NAMES.add("交通运输、仓储和邮政业");
-//        INDUSTRY_COLUMN_NAMES.add("住宿和餐饮业");
-//        INDUSTRY_COLUMN_NAMES.add("信息传输、软件和信息技术服务业");
-//        INDUSTRY_COLUMN_NAMES.add("金融业");
-//        INDUSTRY_COLUMN_NAMES.add("房地产业");
-//        INDUSTRY_COLUMN_NAMES.add("租赁和商务服务业");
-//        INDUSTRY_COLUMN_NAMES.add("科学研究和技术服务业");
-//        INDUSTRY_COLUMN_NAMES.add("水利、环境和公共设施管理业");
-//        INDUSTRY_COLUMN_NAMES.add("居民服务、修理和其他服务业");
-//        INDUSTRY_COLUMN_NAMES.add("教育");
-//        INDUSTRY_COLUMN_NAMES.add("卫生和社会工作");
-//        INDUSTRY_COLUMN_NAMES.add("文化、体育和娱乐业");
-//        INDUSTRY_COLUMN_NAMES.add("综合");
-//
-//        INDUSTRY_COLUMN_NAMES.add("港股通");
-//
-//        // 以下为国际标准
-//        INDUSTRY_COLUMN_NAMES.add("能源");
-//        INDUSTRY_COLUMN_NAMES.add("原材料");
-//        INDUSTRY_COLUMN_NAMES.add("材料");
-//        INDUSTRY_COLUMN_NAMES.add("工业");
-//        INDUSTRY_COLUMN_NAMES.add("可选消费品");
-//        INDUSTRY_COLUMN_NAMES.add("非日常生活消费品");
-//        INDUSTRY_COLUMN_NAMES.add("必须消费品");
-//        INDUSTRY_COLUMN_NAMES.add("日常消费品");
-//        INDUSTRY_COLUMN_NAMES.add("医疗保健");
-//        INDUSTRY_COLUMN_NAMES.add("金融");
-//        INDUSTRY_COLUMN_NAMES.add("信息技术");
-//        INDUSTRY_COLUMN_NAMES.add("通讯服务");
-//        INDUSTRY_COLUMN_NAMES.add("电信服务");
-//        INDUSTRY_COLUMN_NAMES.add("公用事业");
-//        INDUSTRY_COLUMN_NAMES.add("房地产");
-//
-//        // 份额变动表格识别列
-//        SHARE_CHANGE_COLUMN_NAMES.add("报告期期初基金份额总额");
-//        SHARE_CHANGE_COLUMN_NAMES.add("减:报告期期间基金总赎回份额");
-//        SHARE_CHANGE_COLUMN_NAMES.add("期末基金总份额/期末基金实缴总额");
-//        SHARE_CHANGE_COLUMN_NAMES.add("报告期期间基金拆分变动份额");
-//        SHARE_CHANGE_COLUMN_NAMES.add("报告期期间基金总申购份额");
-//
-//        // 资产配置
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("银行存款", "现金类资产");
-//        // 境内未上市、未挂牌公司股权投资
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("股权投资", "境内未上市、未挂牌公司股权投资");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("其中:优先股", "境内未上市、未挂牌公司股权投资");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("其他股权类投资", "境内未上市、未挂牌公司股权投资");
-//        // 上市公司定向增发投资
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("上市公司定向增发股票投资", "上市公司定向增发投资");
-//        // 新三板投资
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("新三板挂牌企业投资", "新三板投资");
-//        // 境内证券投资规模
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("结算备付金", "境内证券投资规模");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("存出保证金", "境内证券投资规模");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("股票投资", "境内证券投资规模");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("债券投资", "境内证券投资规模");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("其中:银行间市场债券", "境内证券投资规模");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("其中:利率债", "境内证券投资规模");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("其中:信用债", "境内证券投资规模");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("资产支持证券", "境内证券投资规模");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("基金投资(公募基金)", "境内证券投资规模");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("其中:货币基金", "境内证券投资规模");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("期货及衍生品交易保证金", "境内证券投资规模");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("买入返售金融资产", "境内证券投资规模");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("其他证券类标的", "境内证券投资规模");
-//        // 资管计划投资
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("商业银行理财产品投资", "资管计划投资");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("信托计划投资", "资管计划投资");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("基金公司及其子公司资产管理计划投资", "资管计划投资");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("保险资产管理计划投资", "资管计划投资");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("证券公司及其子公司资产管理计划投资", "资管计划投资");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("期货公司及其子公司资产管理计划投资", "资管计划投资");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("私募基金产品投资", "资管计划投资");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("未在协会备案的合伙企业份额", "资管计划投资");
-//        // 另类投资
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("另类投资", "另类投资");
-//        // 境内债权类投资
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("银行委托贷款规模", "境内债权类投资");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("信托贷款", "境内债权类投资");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("应收账款投资", "境内债权类投资");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("各类受(收)益权投资", "境内债权类投资");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("票据(承兑汇票等)投资", "境内债权类投资");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("其他债权投资", "境内债权类投资");
-//        // 境外投资
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("境外投资", "境外投资");
-//        // 其他资产
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("其他资产", "其他资产");
-//        // 基金负债情况
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("债券回购总额", "基金负债情况");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("融资、融券总额", "基金负债情况");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("其中:融券总额", "基金负债情况");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("银行借款总额", "基金负债情况");
-//        ASSET_ALLOCATION_TYPE_MAPPER.put("其他融资总额", "基金负债情况");
-//    }
+    /**
+     * 行业配置的表格列名称
+     */
+    public static final List<String> INDUSTRY_COLUMN_NAMES = ListUtil.list(false);
+    /**
+     * 份额变动的表格列名称
+     */
+    public static final List<String> SHARE_CHANGE_COLUMN_NAMES = ListUtil.list(false);
+    /**
+     * 主要财务指标识别列名称
+     */
+    public static final List<String> FINANCIAL_INDICATORS_COLUMN_NAMES = ListUtil.list(false);
+    /**
+     * 资产配置明细和大类关系映射
+     */
+    public static final Map<String, String> ASSET_ALLOCATION_TYPE_MAPPER = MapUtil.newHashMap(32, true);
+
+    static {
+        // 财务指标
+        FINANCIAL_INDICATORS_COLUMN_NAMES.add("期末基金净资产");
+        FINANCIAL_INDICATORS_COLUMN_NAMES.add("期末基金资产净值");
+
+        FINANCIAL_INDICATORS_COLUMN_NAMES.add("报告期期末单位净值");
+        FINANCIAL_INDICATORS_COLUMN_NAMES.add("期末基金份额净值");
+
+        FINANCIAL_INDICATORS_COLUMN_NAMES.add("本期利润");
+        FINANCIAL_INDICATORS_COLUMN_NAMES.add("本期已实现收益");
+        FINANCIAL_INDICATORS_COLUMN_NAMES.add("期末可供分配利润");
+        FINANCIAL_INDICATORS_COLUMN_NAMES.add("期末可供分配基金份额利润");
+        FINANCIAL_INDICATORS_COLUMN_NAMES.add("基金份额累计净值增长率");
+
+        // 中国证监会行业标准
+        INDUSTRY_COLUMN_NAMES.add("农、林、牧、渔业");
+        INDUSTRY_COLUMN_NAMES.add("采矿业");
+        INDUSTRY_COLUMN_NAMES.add("制造业");
+        INDUSTRY_COLUMN_NAMES.add("电力、热力、燃气及水生产和供应业");
+        INDUSTRY_COLUMN_NAMES.add("建筑业");
+        INDUSTRY_COLUMN_NAMES.add("批发和零售业");
+        INDUSTRY_COLUMN_NAMES.add("交通运输、仓储和邮政业");
+        INDUSTRY_COLUMN_NAMES.add("住宿和餐饮业");
+        INDUSTRY_COLUMN_NAMES.add("信息传输、软件和信息技术服务业");
+        INDUSTRY_COLUMN_NAMES.add("金融业");
+        INDUSTRY_COLUMN_NAMES.add("房地产业");
+        INDUSTRY_COLUMN_NAMES.add("租赁和商务服务业");
+        INDUSTRY_COLUMN_NAMES.add("科学研究和技术服务业");
+        INDUSTRY_COLUMN_NAMES.add("水利、环境和公共设施管理业");
+        INDUSTRY_COLUMN_NAMES.add("居民服务、修理和其他服务业");
+        INDUSTRY_COLUMN_NAMES.add("教育");
+        INDUSTRY_COLUMN_NAMES.add("卫生和社会工作");
+        INDUSTRY_COLUMN_NAMES.add("文化、体育和娱乐业");
+        INDUSTRY_COLUMN_NAMES.add("综合");
+
+        INDUSTRY_COLUMN_NAMES.add("港股通");
+
+        // 以下为国际标准
+        INDUSTRY_COLUMN_NAMES.add("能源");
+        INDUSTRY_COLUMN_NAMES.add("原材料");
+        INDUSTRY_COLUMN_NAMES.add("材料");
+        INDUSTRY_COLUMN_NAMES.add("工业");
+        INDUSTRY_COLUMN_NAMES.add("可选消费品");
+        INDUSTRY_COLUMN_NAMES.add("非日常生活消费品");
+        INDUSTRY_COLUMN_NAMES.add("必须消费品");
+        INDUSTRY_COLUMN_NAMES.add("日常消费品");
+        INDUSTRY_COLUMN_NAMES.add("医疗保健");
+        INDUSTRY_COLUMN_NAMES.add("金融");
+        INDUSTRY_COLUMN_NAMES.add("信息技术");
+        INDUSTRY_COLUMN_NAMES.add("通讯服务");
+        INDUSTRY_COLUMN_NAMES.add("电信服务");
+        INDUSTRY_COLUMN_NAMES.add("公用事业");
+        INDUSTRY_COLUMN_NAMES.add("房地产");
+
+        // 份额变动表格识别列
+        SHARE_CHANGE_COLUMN_NAMES.add("报告期期初基金份额总额");
+        SHARE_CHANGE_COLUMN_NAMES.add("减:报告期期间基金总赎回份额");
+        SHARE_CHANGE_COLUMN_NAMES.add("期末基金总份额/期末基金实缴总额");
+        SHARE_CHANGE_COLUMN_NAMES.add("报告期期间基金拆分变动份额");
+        SHARE_CHANGE_COLUMN_NAMES.add("报告期期间基金总申购份额");
+
+        // 资产配置
+        ASSET_ALLOCATION_TYPE_MAPPER.put("银行存款", "现金类资产");
+        // 境内未上市、未挂牌公司股权投资
+        ASSET_ALLOCATION_TYPE_MAPPER.put("股权投资", "境内未上市、未挂牌公司股权投资");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("其中:优先股", "境内未上市、未挂牌公司股权投资");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("其他股权类投资", "境内未上市、未挂牌公司股权投资");
+        // 上市公司定向增发投资
+        ASSET_ALLOCATION_TYPE_MAPPER.put("上市公司定向增发股票投资", "上市公司定向增发投资");
+        // 新三板投资
+        ASSET_ALLOCATION_TYPE_MAPPER.put("新三板挂牌企业投资", "新三板投资");
+        // 境内证券投资规模
+        ASSET_ALLOCATION_TYPE_MAPPER.put("结算备付金", "境内证券投资规模");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("存出保证金", "境内证券投资规模");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("股票投资", "境内证券投资规模");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("债券投资", "境内证券投资规模");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("其中:银行间市场债券", "境内证券投资规模");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("其中:利率债", "境内证券投资规模");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("其中:信用债", "境内证券投资规模");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("资产支持证券", "境内证券投资规模");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("基金投资(公募基金)", "境内证券投资规模");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("其中:货币基金", "境内证券投资规模");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("期货及衍生品交易保证金", "境内证券投资规模");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("买入返售金融资产", "境内证券投资规模");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("其他证券类标的", "境内证券投资规模");
+        // 资管计划投资
+        ASSET_ALLOCATION_TYPE_MAPPER.put("商业银行理财产品投资", "资管计划投资");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("信托计划投资", "资管计划投资");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("基金公司及其子公司资产管理计划投资", "资管计划投资");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("保险资产管理计划投资", "资管计划投资");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("证券公司及其子公司资产管理计划投资", "资管计划投资");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("期货公司及其子公司资产管理计划投资", "资管计划投资");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("私募基金产品投资", "资管计划投资");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("未在协会备案的合伙企业份额", "资管计划投资");
+        // 另类投资
+        ASSET_ALLOCATION_TYPE_MAPPER.put("另类投资", "另类投资");
+        // 境内债权类投资
+        ASSET_ALLOCATION_TYPE_MAPPER.put("银行委托贷款规模", "境内债权类投资");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("信托贷款", "境内债权类投资");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("应收账款投资", "境内债权类投资");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("各类受(收)益权投资", "境内债权类投资");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("票据(承兑汇票等)投资", "境内债权类投资");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("其他债权投资", "境内债权类投资");
+        // 境外投资
+        ASSET_ALLOCATION_TYPE_MAPPER.put("境外投资", "境外投资");
+        // 其他资产
+        ASSET_ALLOCATION_TYPE_MAPPER.put("其他资产", "其他资产");
+        // 基金负债情况
+        ASSET_ALLOCATION_TYPE_MAPPER.put("债券回购总额", "基金负债情况");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("融资、融券总额", "基金负债情况");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("其中:融券总额", "基金负债情况");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("银行借款总额", "基金负债情况");
+        ASSET_ALLOCATION_TYPE_MAPPER.put("其他融资总额", "基金负债情况");
+    }
 
     public static String cleaningValue(Object value) {
         return cleaningValue(value, true);
@@ -193,7 +195,7 @@ public final class ReportParseUtils {
             }
             if (replaceParentheses) {
                 // 正则表达式匹配中文括号及其内容,并替换为空字符串
-                fieldValue = Pattern.compile("[(|(][^)]*[)|)]").matcher(fieldValue).replaceAll(StrUtil.EMPTY);
+                fieldValue = Pattern.compile("\\([^)]*\\)").matcher(fieldValue).replaceAll(StrUtil.EMPTY);
             }
         }
         // 如果仅有 “-” 该字段值为null
@@ -234,6 +236,28 @@ public final class ReportParseUtils {
     }
 
     /**
+     * 分级基金名称
+     *
+     * @param text 文本内容
+     * @return /
+     */
+    public static String matchFundLevel(String text) {
+        // 使用正则表达式查找匹配项
+        Pattern pattern = Pattern.compile("[A-F]级|基金[A-F]");
+        Matcher matcher = pattern.matcher(text);
+        String result = null;
+        while (matcher.find()) {
+            result = matcher.group();
+        }
+        if (StrUtil.isBlank(result)) {
+            result = "母基金";
+        } else {
+            result = result.replaceAll("[^A-F]", "") + "级";
+        }
+        return result;
+    }
+
+    /**
      * 匹配报告日期
      *
      * @param string 文本内容
@@ -303,6 +327,10 @@ public final class ReportParseUtils {
             reportType = ReportType.MONTHLY;
         } else if (StrUtil.containsAny(string, ReportType.LETTER.getPatterns())) {
             reportType = ReportType.LETTER;
+        } else if (StrUtil.containsAny(string, ReportType.WEEKLY.getPatterns())) {
+            reportType = ReportType.WEEKLY;
+        } else if (StrUtil.containsAny(string, ReportType.OTHER.getPatterns())) {
+            reportType = ReportType.OTHER;
         }
         return reportType;
     }
@@ -318,65 +346,6 @@ public final class ReportParseUtils {
         return String.format("%02d", Integer.parseInt(number));
     }
 
-//    public static GenerationResult callWithMessage() throws ApiException, NoApiKeyException, InputRequiredException {
-//        Generation gen = new Generation();
-//        Message systemMsg = Message.builder()
-//                .role(Role.SYSTEM.getValue())
-//                .content("You are a helpful assistant.")
-//                .build();
-//        Message userMsg = Message.builder()
-//                .role(Role.USER.getValue())
-//                .content("你是谁?")
-//                .build();
-//        GenerationParam param = GenerationParam.builder()
-//                // 若没有配置环境变量,请用百炼API Key将下行替换为:.apiKey("sk-xxx")
-//                .apiKey(System.getenv("DASHSCOPE_API_KEY"))
-//                // 模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
-//                .model("qwen-plus")
-//                .messages(Arrays.asList(systemMsg, userMsg))
-//                .resultFormat(GenerationParam.ResultFormat.MESSAGE)
-//                .build();
-//        return gen.call(param);
-//    }
-//
-//    public static void simpleMultiModalConversationCall()
-//            throws ApiException, NoApiKeyException, UploadFileException {
-//        MultiModalConversation conv = new MultiModalConversation();
-//        Map<String, Object> map = new HashMap<>();
-//        map.put("image", "./流水1.jpg");
-//        map.put("max_pixels", "1003520");
-//        map.put("min_pixels", "3136");
-//        MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue())
-//                .content(Arrays.asList(
-//                        map,
-//                        // 目前为保证模型效果,模型内部会统一使用"Read all the text in the image."作为text的值,用户输入的文本不会生效。
-//                        Collections.singletonMap("text", "Read all the text in the image."))).build();
-//        MultiModalConversationParam param = MultiModalConversationParam.builder()
-//                // 若没有配置环境变量,请用百炼API Key将下行替换为:.apiKey("sk-xxx")
-//                .apiKey(System.getenv("DASHSCOPE_API_KEY"))
-//                // 模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
-//                .model("qwen-vl-ocr")
-//                .message(userMessage)
-//                .build();
-//        MultiModalConversationResult result = conv.call(param);
-//        System.out.println(JsonUtils.toJson(result));
-//    }
-//
-//    public static void main(String[] args) throws IOException {
-////        try {
-////            GenerationResult result = callWithMessage();
-////            System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent());
-////        } catch (ApiException | NoApiKeyException | InputRequiredException e) {
-////            System.err.println("错误信息:"+e.getMessage());
-////            System.out.println("请参考文档:https://help.aliyun.com/zh/model-studio/developer-reference/error-code");
-////        }
-//        try {
-//            simpleMultiModalConversationCall();
-//        } catch (ApiException | NoApiKeyException | UploadFileException e) {
-//            System.out.println(e.getMessage());
-//        }
-//    }
-
     public static void main(String[] args) throws IOException, ReportParseException {
 //        String filepath = "C:\\Users\\Administrator\\Desktop\\tmp\\(1)投资者交易确认函【申购】_【SZF635】佳岳国债增强私募证券投资基金_20250217_任军.pdf";
 //        String filepath = "C:\\Users\\Administrator\\Desktop\\tmp\\CP080A_优美利赢胜价值1号私募投资基金A_20250217_邓辉_申购确认_20250217131352.pdf";
@@ -403,8 +372,7 @@ public final class ReportParseUtils {
         // 解析报告和表格
         try (PDDocument document = Loader.loadPDF(new RandomAccessReadBufferedFile(filepath))) {
             // 识别所有文字(去水印后的)
-            CustomPDFTextStripper stripper = new CustomPDFTextStripper();
-            stripper.setSortByPosition(true);
+            CustomPDFTextStripper stripper = new CustomPDFTextStripper(true, "");
             String text = stripper.getText(document).replace(Constants.WATERMARK_REPLACE, StrUtil.EMPTY);
             textList = StrUtil.split(text, System.lineSeparator());
             textList.removeIf(StrUtil::isBlank);

+ 10 - 7
mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/AbstractReportParser.java

@@ -40,13 +40,13 @@ public abstract class AbstractReportParser<T extends ReportData> implements Repo
      * 初始化数据的方法
      */
     protected void init() {
-        List<EmailFieldMappingDO> emailFieldMapping = this.fieldMappingMapper.getEmailFieldMapping(ListUtil.of(3, 4));
-        if (CollUtil.isEmpty(emailFieldMapping)) {
+        List<EmailFieldMappingDO> mapping = this.fieldMappingMapper.getEmailFieldMapping(ListUtil.of(3, 4));
+        if (CollUtil.isEmpty(mapping)) {
             throw new ReportParseException(ReportParseStatus.PARSE_RULE_NO_FUND);
         }
-        for (EmailFieldMappingDO mapping : emailFieldMapping) {
-            String code = mapping.getCode();
-            List<String> names = StrUtil.split(mapping.getName(), ",");
+        for (EmailFieldMappingDO temp : mapping) {
+            String code = temp.getCode();
+            List<String> names = StrUtil.split(temp.getName(), ",");
             for (String name : names) {
                 this.fieldMapper.putIfAbsent(name, code);
             }
@@ -67,7 +67,8 @@ public abstract class AbstractReportParser<T extends ReportData> implements Repo
      * @param fundInfo   报告中基金基本信息
      * @return /
      */
-    protected abstract T parseExtInfoAndSetData(ReportBaseInfoDTO reportInfo, ReportFundInfoDTO fundInfo) throws ReportParseException;
+    protected abstract T parseExtInfoAndSetData(ReportBaseInfoDTO reportInfo,
+                                                ReportFundInfoDTO fundInfo) throws ReportParseException;
 
     /**
      * 绑定基金基本信息
@@ -111,7 +112,9 @@ public abstract class AbstractReportParser<T extends ReportData> implements Repo
      * @param infoMap 表格转换的函数
      * @return /
      */
-    protected <DTO extends BaseReportDTO<?>> DTO buildDto(Integer fileId, Class<DTO> clazz, Map<String, Object> infoMap) {
+    protected <DTO extends BaseReportDTO<?>> DTO buildDto(Integer fileId,
+                                                          Class<DTO> clazz,
+                                                          Map<String, Object> infoMap) {
         try {
             DTO dto = clazz.getDeclaredConstructor().newInstance();
             dto.setFileId(fileId);

+ 18 - 3
mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/ReportParserConstant.java

@@ -14,6 +14,12 @@ import java.util.Map;
 public final class ReportParserConstant {
     public static final Map<ReportType, Map<ReportParserFileType, String>> REPORT_PARSER_BEAN_MAP = MapUtil.newHashMap(8);
 
+    // 其他报告
+    public static final String PARSER_AI_OTHER = "report-parser:ai:other";
+
+    // 周报
+    public static final String PARSER_AI_WEEKLY = "report-parser:ai:weekly";
+
     // 交易流水确认函解析
     public static final String PARSER_PDF_LETTER = "report-parser:pdf:letter";
     public static final String PARSER_AI_LETTER = "report-parser:ai:letter";
@@ -31,6 +37,12 @@ public final class ReportParserConstant {
     public static final String PARSER_AI_ANNUALLY = "report-parser:ai:annually";
 
     static {
+        // 其他报告解析
+        REPORT_PARSER_BEAN_MAP.put(ReportType.OTHER, Map.of(ReportParserFileType.AI, PARSER_AI_OTHER));
+
+        // 周报
+        REPORT_PARSER_BEAN_MAP.put(ReportType.WEEKLY, Map.of(ReportParserFileType.AI, PARSER_AI_WEEKLY));
+
         // 交易流水确认函解析
         REPORT_PARSER_BEAN_MAP.put(ReportType.LETTER,
                 Map.of(
@@ -40,21 +52,24 @@ public final class ReportParserConstant {
 
         // 月度报告
         REPORT_PARSER_BEAN_MAP.put(ReportType.MONTHLY,
-                Map.of(ReportParserFileType.PDF, PARSER_PDF_MONTHLY,
+                Map.of(
+                        ReportParserFileType.PDF, PARSER_PDF_MONTHLY,
                         ReportParserFileType.EXCEL, PARSER_EXCEL_MONTHLY,
                         ReportParserFileType.AI, PARSER_AI_MONTHLY
                 ));
 
         // 季度报告
         REPORT_PARSER_BEAN_MAP.put(ReportType.QUARTERLY,
-                Map.of(ReportParserFileType.PDF, PARSER_PDF_QUARTERLY,
+                Map.of(
+                        ReportParserFileType.PDF, PARSER_PDF_QUARTERLY,
                         ReportParserFileType.EXCEL, PARSER_EXCEL_QUARTERLY,
                         ReportParserFileType.AI, PARSER_AI_QUARTERLY
                 ));
 
         // 年度报告
         REPORT_PARSER_BEAN_MAP.put(ReportType.ANNUALLY,
-                Map.of(ReportParserFileType.PDF, PARSER_PDF_ANNUALLY,
+                Map.of(
+                        ReportParserFileType.PDF, PARSER_PDF_ANNUALLY,
                         ReportParserFileType.EXCEL, PARSER_EXCEL_ANNUALLY,
                         ReportParserFileType.AI, PARSER_AI_ANNUALLY
                 ));

+ 1 - 1
mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/ReportParserFactory.java

@@ -5,7 +5,6 @@ import com.smppw.modaq.common.enums.ReportParseStatus;
 import com.smppw.modaq.common.enums.ReportParserFileType;
 import com.smppw.modaq.common.enums.ReportType;
 import com.smppw.modaq.common.exception.NotSupportReportException;
-import com.smppw.modaq.common.exception.ReportParseException;
 import com.smppw.modaq.domain.dto.report.ReportData;
 import org.springframework.stereotype.Component;
 
@@ -23,6 +22,7 @@ public class ReportParserFactory {
     public <T extends ReportData> ReportParser<T> getInstance(ReportType reportType, ReportParserFileType reportParserFileType) {
         String beanName = ReportParserConstant.REPORT_PARSER_BEAN_MAP.getOrDefault(reportType, MapUtil.empty()).get(reportParserFileType);
         ReportParser<? extends ReportData> reportParser = REPORT_WRITER_MAP.get(beanName);
+        // 不支持的解析格式
         if (reportParser == null) {
             throw new NotSupportReportException(ReportParseStatus.NO_SUPPORT_TEMPLATE);
         }

+ 38 - 0
mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/ai/AIAnnuallyReportParser.java

@@ -0,0 +1,38 @@
+package com.smppw.modaq.application.components.report.parser.ai;
+
+import com.smppw.modaq.application.components.report.parser.ReportParserConstant;
+import com.smppw.modaq.common.exception.ReportParseException;
+import com.smppw.modaq.domain.dto.report.AnnuallyReportData;
+import com.smppw.modaq.domain.dto.report.ReportBaseInfoDTO;
+import com.smppw.modaq.domain.dto.report.ReportFundInfoDTO;
+import com.smppw.modaq.domain.dto.report.ReportParserParams;
+import com.smppw.modaq.domain.mapper.EmailFieldMappingMapper;
+import org.springframework.stereotype.Component;
+
+@Component(ReportParserConstant.PARSER_AI_ANNUALLY)
+public class AIAnnuallyReportParser extends AbstractAIReportParser<AnnuallyReportData> {
+    public AIAnnuallyReportParser(EmailFieldMappingMapper fieldMappingMapper) {
+        super(fieldMappingMapper);
+    }
+
+    @Override
+    protected boolean isSupportAIParse() {
+        return false;
+    }
+
+    @Override
+    protected String prompt() {
+        return "";
+    }
+
+    @Override
+    protected AnnuallyReportData parseExtInfoAndSetData(ReportBaseInfoDTO reportInfo,
+                                                        ReportFundInfoDTO fundInfo) throws ReportParseException {
+        return null;
+    }
+
+    @Override
+    protected ReportFundInfoDTO buildFundInfo(ReportParserParams params) {
+        return null;
+    }
+}

+ 12 - 16
mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/ai/AILetterReportParser.java

@@ -2,7 +2,6 @@ package com.smppw.modaq.application.components.report.parser.ai;
 
 import cn.hutool.core.collection.ListUtil;
 import cn.hutool.core.map.MapUtil;
-import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
 import com.smppw.modaq.application.components.report.parser.ReportParserConstant;
@@ -22,16 +21,15 @@ public class AILetterReportParser extends AbstractAIReportParser<LetterReportDat
     }
 
     @Override
+    protected boolean isSupportAIParse() {
+        return true;
+    }
+
+    @Override
     protected void handleAiResult(String result) throws ReportParseException {
         try {
-            JSONObject jsonResult = JSONUtil.parseObj(result);
-            this.aiFileId = MapUtil.getStr(jsonResult, "file_id");
-            String content = StrUtil.split(jsonResult.getStr("content"), "```").get(1);
-            String aiParserContent = "{" + StrUtil.subAfter(content, "{", false) + "}";
-            if (StrUtil.isNotBlank(aiParserContent)) {
-                JSONObject jsonObject = JSONUtil.parseObj(aiParserContent);
-                this.infoMap.putAll(flattenMap(jsonObject, ListUtil.list(false)));
-            }
+            JSONObject jsonObject = JSONUtil.parseObj(result);
+            this.allInfoMap.putAll(flattenMap(jsonObject, ListUtil.list(false)));
         } catch (Exception e) {
             throw new ReportParseException(ReportParseStatus.PARSE_HANDLE_FAIL);
         }
@@ -41,24 +39,22 @@ public class AILetterReportParser extends AbstractAIReportParser<LetterReportDat
     protected LetterReportData parseExtInfoAndSetData(ReportBaseInfoDTO reportInfo, ReportFundInfoDTO fundInfo) throws ReportParseException {
         Integer fileId = reportInfo.getFileId();
         if (this.logger.isInfoEnabled()) {
-            this.logger.info("文件{} 解析内容是:{}", fileId, this.infoMap);
+            this.logger.info("文件{} 解析内容是:{}", fileId, this.allInfoMap);
         }
         // 投资者信息
-        ReportInvestorInfoDTO investorInfo = this.buildDto(fileId, ReportInvestorInfoDTO.class, this.infoMap);
+        ReportInvestorInfoDTO investorInfo = this.buildDto(fileId, ReportInvestorInfoDTO.class, this.allInfoMap);
         // 交易流水
-        ReportFundTransactionDTO fundTransaction = this.buildDto(fileId, ReportFundTransactionDTO.class, this.infoMap);
+        ReportFundTransactionDTO fundTransaction = this.buildDto(fileId, ReportFundTransactionDTO.class, this.allInfoMap);
         // 构建结果数据
         LetterReportData reportData = new LetterReportData(reportInfo, fundInfo);
-        reportData.setInvestorInfo(investorInfo);
         reportData.setFundTransaction(fundTransaction);
-        reportData.setAiParse(true);
-        reportData.setAiFileId(this.aiFileId);
+        reportData.setInvestorInfo(investorInfo);
         return reportData;
     }
 
     @Override
     protected ReportFundInfoDTO buildFundInfo(ReportParserParams params) {
-        return this.buildDto(params.getFileId(), ReportFundInfoDTO.class, this.infoMap);
+        return this.buildDto(params.getFileId(), ReportFundInfoDTO.class, this.allInfoMap);
     }
 
     @SuppressWarnings("unchecked")

+ 65 - 0
mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/ai/AIMonthlyReportParser.java

@@ -0,0 +1,65 @@
+package com.smppw.modaq.application.components.report.parser.ai;
+
+import cn.hutool.core.collection.ListUtil;
+import com.smppw.modaq.application.components.ReportParseUtils;
+import com.smppw.modaq.application.components.report.parser.ReportParserConstant;
+import com.smppw.modaq.common.enums.ReportParseStatus;
+import com.smppw.modaq.common.exception.ReportParseException;
+import com.smppw.modaq.domain.dto.report.*;
+import com.smppw.modaq.domain.mapper.EmailFieldMappingMapper;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+
+@Component(ReportParserConstant.PARSER_AI_MONTHLY)
+public class AIMonthlyReportParser extends AbstractAIReportParser<MonthlyReportData> {
+    public AIMonthlyReportParser(EmailFieldMappingMapper fieldMappingMapper) {
+        super(fieldMappingMapper);
+    }
+
+    @Override
+    protected boolean isSupportAIParse() {
+        return true;
+    }
+
+    @Override
+    protected String prompt() {
+        return "识别文件中的基金概况、净值信息表格数据,要求准确识别金额等小数的位数,去掉金额单位、英文和多余的空格,结果用json返回";
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    protected MonthlyReportData parseExtInfoAndSetData(ReportBaseInfoDTO reportInfo,
+                                                       ReportFundInfoDTO fundInfo) throws ReportParseException {
+        List<ReportNetReportDTO> dtos = ListUtil.list(true);
+        for (Map.Entry<String, Object> entry : this.allInfoMap.entrySet()) {
+            Object netInfo = entry.getValue();
+            if (netInfo == null) {
+                continue;
+            }
+            Map<String, Object> netInfoMap = (Map<String, Object>) netInfo;
+            ReportNetReportDTO dto = this.buildDto(reportInfo.getFileId(), ReportNetReportDTO.class, netInfoMap);
+            if (dto == null) {
+                continue;
+            }
+            dto.setLevel(ReportParseUtils.matchFundLevel(entry.getKey()));
+            dtos.add(dto);
+        }
+        MonthlyReportData reportData = new MonthlyReportData(reportInfo, fundInfo);
+        reportData.setNetReport(dtos);
+        return reportData;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    protected ReportFundInfoDTO buildFundInfo(ReportParserParams params) {
+        // 获取并移除基金概况信息
+        Object fundInfo = this.allInfoMap.remove("基金概况");
+        if (fundInfo == null) {
+            throw new ReportParseException(ReportParseStatus.PARSE_FUND_INFO_FAIL, params.getFilename());
+        }
+        Map<String, Object> fundInfoMap = (Map<String, Object>) fundInfo;
+        return this.buildDto(params.getFileId(), ReportFundInfoDTO.class, fundInfoMap);
+    }
+}

+ 44 - 0
mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/ai/AIOtherReportParser.java

@@ -0,0 +1,44 @@
+package com.smppw.modaq.application.components.report.parser.ai;
+
+import com.smppw.modaq.application.components.report.parser.ReportParserConstant;
+import com.smppw.modaq.common.exception.ReportParseException;
+import com.smppw.modaq.domain.dto.report.*;
+import com.smppw.modaq.domain.mapper.EmailFieldMappingMapper;
+import org.springframework.stereotype.Component;
+
+import java.util.Objects;
+
+/**
+ * 其他格式的报告(只解析报告基本信息、基金基本信息)
+ */
+@Component(ReportParserConstant.PARSER_AI_OTHER)
+public class AIOtherReportParser extends AbstractAIReportParser<ReportData> {
+    public AIOtherReportParser(EmailFieldMappingMapper fieldMappingMapper) {
+        super(fieldMappingMapper);
+    }
+
+    @Override
+    protected String prompt() {
+        return "识别文件中的基金名称、基金管理人、基金托管人和报告日期,并且判断文件中是否存在联系人等信息,如果无法识别就返回空字符,结果用json返回";
+    }
+
+    @Override
+    protected boolean isSupportAIParse() {
+        return true;
+    }
+
+    @Override
+    protected ReportData parseExtInfoAndSetData(ReportBaseInfoDTO reportInfo,
+                                                ReportFundInfoDTO fundInfo) throws ReportParseException {
+        Object contact = this.allInfoMap.get("联系人信息");
+        if (Objects.equals("存在", contact)) {
+            reportInfo.setWithContacts(true);
+        }
+        return new MonthlyReportData(reportInfo, fundInfo);
+    }
+
+    @Override
+    protected ReportFundInfoDTO buildFundInfo(ReportParserParams params) {
+        return this.buildDto(params.getFileId(), ReportFundInfoDTO.class, this.allInfoMap);
+    }
+}

+ 38 - 0
mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/ai/AIQuarterlyReportParser.java

@@ -0,0 +1,38 @@
+package com.smppw.modaq.application.components.report.parser.ai;
+
+import com.smppw.modaq.application.components.report.parser.ReportParserConstant;
+import com.smppw.modaq.common.exception.ReportParseException;
+import com.smppw.modaq.domain.dto.report.QuarterlyReportData;
+import com.smppw.modaq.domain.dto.report.ReportBaseInfoDTO;
+import com.smppw.modaq.domain.dto.report.ReportFundInfoDTO;
+import com.smppw.modaq.domain.dto.report.ReportParserParams;
+import com.smppw.modaq.domain.mapper.EmailFieldMappingMapper;
+import org.springframework.stereotype.Component;
+
+@Component(ReportParserConstant.PARSER_AI_QUARTERLY)
+public class AIQuarterlyReportParser extends AbstractAIReportParser<QuarterlyReportData> {
+    public AIQuarterlyReportParser(EmailFieldMappingMapper fieldMappingMapper) {
+        super(fieldMappingMapper);
+    }
+
+    @Override
+    protected boolean isSupportAIParse() {
+        return false;
+    }
+
+    @Override
+    protected String prompt() {
+        return "";
+    }
+
+    @Override
+    protected QuarterlyReportData parseExtInfoAndSetData(ReportBaseInfoDTO reportInfo,
+                                                         ReportFundInfoDTO fundInfo) throws ReportParseException {
+        return null;
+    }
+
+    @Override
+    protected ReportFundInfoDTO buildFundInfo(ReportParserParams params) {
+        return null;
+    }
+}

+ 41 - 0
mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/ai/AIWeeklyReportParser.java

@@ -0,0 +1,41 @@
+package com.smppw.modaq.application.components.report.parser.ai;
+
+import com.smppw.modaq.application.components.report.parser.ReportParserConstant;
+import com.smppw.modaq.common.exception.ReportParseException;
+import com.smppw.modaq.domain.dto.report.ReportBaseInfoDTO;
+import com.smppw.modaq.domain.dto.report.ReportFundInfoDTO;
+import com.smppw.modaq.domain.dto.report.ReportParserParams;
+import com.smppw.modaq.domain.dto.report.WeeklyReportData;
+import com.smppw.modaq.domain.mapper.EmailFieldMappingMapper;
+import org.springframework.stereotype.Component;
+
+/**
+ * 管理人周报AI解析器
+ */
+@Component(ReportParserConstant.PARSER_AI_WEEKLY)
+public class AIWeeklyReportParser extends AbstractAIReportParser<WeeklyReportData> {
+    public AIWeeklyReportParser(EmailFieldMappingMapper fieldMappingMapper) {
+        super(fieldMappingMapper);
+    }
+
+    @Override
+    protected String prompt() {
+        return "识别文件中的基金名称、基金管理人和报告日期,如果日期是区间段则取截止日期,如果无法识别就返回空字符串,结果用json返回";
+    }
+
+    @Override
+    protected boolean isSupportAIParse() {
+        return true;
+    }
+
+    @Override
+    protected WeeklyReportData parseExtInfoAndSetData(ReportBaseInfoDTO reportInfo,
+                                                      ReportFundInfoDTO fundInfo) throws ReportParseException {
+        return new WeeklyReportData(reportInfo, fundInfo);
+    }
+
+    @Override
+    protected ReportFundInfoDTO buildFundInfo(ReportParserParams params) {
+        return this.buildDto(params.getFileId(), ReportFundInfoDTO.class, this.allInfoMap);
+    }
+}

+ 58 - 23
mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/ai/AbstractAIReportParser.java

@@ -4,6 +4,8 @@ import cn.hutool.core.exceptions.ExceptionUtil;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
 import com.smppw.modaq.application.components.report.parser.AbstractReportParser;
 import com.smppw.modaq.common.enums.ReportParseStatus;
 import com.smppw.modaq.common.exception.ReportParseException;
@@ -25,7 +27,7 @@ public abstract class AbstractAIReportParser<T extends ReportData> extends Abstr
 
     protected String aiFileId;
 
-    protected Map<String, Object> infoMap;
+    protected Map<String, Object> allInfoMap;
 
     public AbstractAIReportParser(EmailFieldMappingMapper fieldMappingMapper) {
         super(fieldMappingMapper);
@@ -33,29 +35,20 @@ public abstract class AbstractAIReportParser<T extends ReportData> extends Abstr
 
     @Override
     public T parse(ReportParserParams params) throws ReportParseException {
+        if (!isSupportAIParse()) {
+            throw new ReportParseException(ReportParseStatus.NO_SUPPORT_AI);
+        }
         // 初始化
         this.init();
-        String filename = params.getFilename();
-        String filepath = params.getFilepath();
-        Map<String, Object> paramsMap = MapUtil.newHashMap(4);
-        paramsMap.put("filepath", filepath);
-        paramsMap.put("file_id", params.getAiFileId());
-        String prompt = this.prompt();
-        if (StrUtil.isNotBlank(prompt)) {
-            paramsMap.put("user_msg", prompt);
-        }
-        String body = null;
-        try {
-            body = HttpUtil.get(this.aiParserUrl, paramsMap);
-            this.handleAiResult(body);
-        } catch (ReportParseException e) {
-            this.logger.warn("{} ai解析失败,解析结果{},错误原因:{}", filename, body, ExceptionUtil.stacktraceToString(e));
-            throw e;
-        } catch (Exception e) {
-            this.logger.warn("报告{} 在AI解析时报错:{}", filename, ExceptionUtil.stacktraceToString(e));
-            throw new ReportParseException(ReportParseStatus.AI_NOT_FOUND);
+        // 解析文件内容,并把文件内容解构到 allInfoMap 对象中
+        this.parseFileContent(params);
+        // 解构话返回解析数据
+        T reportData = this.buildReportData(params, params.getFilename());
+        if (reportData != null) {
+            reportData.setAiFileId(this.aiFileId);
+            reportData.setAiParse(true);
         }
-        return this.buildReportData(params, filename);
+        return reportData;
     }
 
 
@@ -74,17 +67,59 @@ public abstract class AbstractAIReportParser<T extends ReportData> extends Abstr
     }
 
     /**
+     * 报告是否支持ai工具解析
+     *
+     * @return /
+     */
+    protected abstract boolean isSupportAIParse();
+
+    /**
      * 处理ai解析结果,方便构建结构化对象
      *
      * @param result ai解析结果
      */
-    protected abstract void handleAiResult(String result) throws ReportParseException;
+    protected void handleAiResult(String result) throws ReportParseException {
+        try {
+            JSONObject jsonObject = JSONUtil.parseObj(result);
+            this.allInfoMap.putAll(jsonObject);
+        } catch (Exception e) {
+            throw new ReportParseException(ReportParseStatus.PARSE_HANDLE_FAIL);
+        }
+    }
 
     @Override
     protected void init() {
         super.init();
         // 先初始化为null
         this.aiFileId = null;
-        this.infoMap = MapUtil.newHashMap(128);
+        this.allInfoMap = MapUtil.newHashMap(128);
+    }
+
+    private void parseFileContent(ReportParserParams params) {
+        String filename = params.getFilename();
+        Map<String, Object> paramsMap = MapUtil.newHashMap(4);
+        paramsMap.put("filepath", params.getFilepath());
+        paramsMap.put("file_id", params.getAiFileId());
+        String prompt = this.prompt();
+        if (StrUtil.isNotBlank(prompt)) {
+            paramsMap.put("user_msg", prompt);
+        }
+        String body = null;
+        try {
+            body = HttpUtil.get(this.aiParserUrl, paramsMap);
+            JSONObject jsonResult = JSONUtil.parseObj(body);
+            this.aiFileId = MapUtil.getStr(jsonResult, "file_id");
+            String content = StrUtil.split(jsonResult.getStr("content"), "```").get(1);
+            String aiParserContent = "{" + StrUtil.subAfter(content, "{", false) + "}";
+            if (StrUtil.isNotBlank(aiParserContent)) {
+                this.handleAiResult(aiParserContent);
+            }
+        } catch (ReportParseException e) {
+            this.logger.warn("{} ai解析失败,解析结果{},错误原因:{}", filename, body, ExceptionUtil.stacktraceToString(e));
+            throw e;
+        } catch (Exception e) {
+            this.logger.warn("报告{} 在AI解析时报错:{}", filename, ExceptionUtil.stacktraceToString(e));
+            throw new ReportParseException(ReportParseStatus.AI_NOT_FOUND);
+        }
     }
 }

+ 117 - 86
mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/pdf/AbstractPDReportParser.java

@@ -1,12 +1,15 @@
 package com.smppw.modaq.application.components.report.parser.pdf;
 
 import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.StrUtil;
 import com.smppw.modaq.application.components.CustomPDFTextStripper;
+import com.smppw.modaq.application.components.ReportParseUtils;
 import com.smppw.modaq.application.components.report.parser.AbstractReportParser;
-import com.smppw.modaq.common.conts.Constants;
 import com.smppw.modaq.common.enums.ReportParseStatus;
+import com.smppw.modaq.common.enums.ReportType;
 import com.smppw.modaq.common.exception.ReportParseException;
+import com.smppw.modaq.domain.dto.report.BaseReportLevelDTO;
 import com.smppw.modaq.domain.dto.report.ReportData;
 import com.smppw.modaq.domain.dto.report.ReportParserParams;
 import com.smppw.modaq.domain.mapper.EmailFieldMappingMapper;
@@ -20,7 +23,10 @@ import technology.tabula.Table;
 import technology.tabula.extractors.SpreadsheetExtractionAlgorithm;
 
 import java.io.IOException;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
 
 /**
  * @author wangzaijun
@@ -33,15 +39,6 @@ public abstract class AbstractPDReportParser<T extends ReportData> extends Abstr
      */
     protected List<String> textList;
 
-//    @Value("${email.report.ai-parser-url}")
-//    private String aiParserUrl;
-
-//    protected String aiFileId;
-//
-//    protected String aiParserContent;
-//
-//    protected Boolean aiParse = false;
-
     public AbstractPDReportParser(EmailFieldMappingMapper fieldMappingMapper) {
         super(fieldMappingMapper);
     }
@@ -55,9 +52,8 @@ public abstract class AbstractPDReportParser<T extends ReportData> extends Abstr
         // 解析报告和表格
         try (PDDocument document = Loader.loadPDF(new RandomAccessReadBufferedFile(filepath))) {
             // 识别所有文字(去水印后的)
-            CustomPDFTextStripper stripper = new CustomPDFTextStripper();
-            stripper.setSortByPosition(true);
-            String text = stripper.getText(document).replace(Constants.WATERMARK_REPLACE, StrUtil.EMPTY);
+            CustomPDFTextStripper stripper = new CustomPDFTextStripper(true, StrUtil.EMPTY);
+            String text = stripper.getText(document);
             this.textList = StrUtil.split(text, System.lineSeparator());
             this.textList.removeIf(StrUtil::isBlank);
             if (this.textList.isEmpty()) {
@@ -68,44 +64,30 @@ public abstract class AbstractPDReportParser<T extends ReportData> extends Abstr
             SpreadsheetExtractionAlgorithm spreadsheetExtractionAlgorithm = new SpreadsheetExtractionAlgorithm();
             // 自定义表格提取工具,去除单元格中的水印文字
             PageIterator pageIterator = new CustomObjectExtractor(document).extract();
-            // 只解析第一页
+            // 确认单只解析第一页
             int i = 0;
             while (pageIterator.hasNext()) {
                 Page page = pageIterator.next();
                 List<Table> tableList = spreadsheetExtractionAlgorithm.extract(page);
-                if (i >= 1) {
+                if (i >= 1 && params.getReportType() == ReportType.LETTER) {
                     break;
                 }
-                Integer rows = tableList.stream().map(Table::getRowCount).filter(rowCount -> rowCount >= 1).reduce(0, Integer::sum);
-                if (rows >= 1) {
-                    for (Table table : tableList) {
-                        int rowCount = table.getRowCount();
-                        if (rowCount >= 1) {
-                            tables.add(table);
-                        }
+                for (Table table : tableList) {
+                    int rowCount = table.getRowCount();
+                    if (rowCount >= 1) {
+                        tables.add(table);
                     }
-                } else {
-//                    this.aiParse = true;
-//                    Map<String, Object> paramsMap = MapUtil.newHashMap(4);
-//                    paramsMap.put("filepath", filepath);
-//                    paramsMap.put("file_id", params.getAiFileId());
-//                    String body = null;
-//                    try {
-//                        body = HttpUtil.get(this.aiParserUrl, paramsMap);
-//                        JSONObject jsonObject = JSONUtil.parseObj(body);
-//                        this.aiFileId = MapUtil.getStr(jsonObject, "file_id");
-//                        String content = StrUtil.split(jsonObject.getStr("content"), "```").get(1);
-//                        this.aiParserContent = "{" + StrUtil.subAfter(content, "{", false) + "}";
-//                    } catch (Exception e) {
-//                        this.logger.warn("{} ai解析失败,解析结果{},错误原因:{}", filename, body, ExceptionUtil.stacktraceToString(e));
-//                    }
-                    throw new ReportParseException(ReportParseStatus.NOT_A_FIXED_FORMAT, filename);
                 }
                 i++;
             }
             if (tables.isEmpty()) {
                 throw new ReportParseException(ReportParseStatus.REPORT_IS_SCAN, filename);
             }
+            Integer rows = tables.stream().map(Table::getRowCount)
+                    .filter(rowCount -> rowCount >= 1).reduce(0, Integer::sum);
+            if (rows < 1) {
+                throw new ReportParseException(ReportParseStatus.NOT_A_FIXED_FORMAT, filename);
+            }
             this.initTableInfo(tables);
         }
         T reportData = this.buildReportData(params, filename);
@@ -134,54 +116,103 @@ public abstract class AbstractPDReportParser<T extends ReportData> extends Abstr
         super.init();
         // 先初始化为null
         this.textList = null;
-//        this.aiFileId = null;
-//        this.aiParserContent = null;
-//        this.aiParse = false;
     }
 
-//    /**
-//     * 构建只有两列表格的dto数据对象,如果有分级基金时(并且一个表格可能跨页)
-//     *
-//     * @param <DTO>    泛型对象
-//     * @param fileId   文件id
-//     * @param tables   表格
-//     * @param clazz    泛型对象
-//     * @param function 表格转换的函数
-//     * @return /
-//     */
-//    protected <DTO extends BaseReportLevelDTO<?>> List<DTO> buildLevelDto(Integer fileId, List<Table> tables, Class<DTO> clazz,
-//                                                                          Function<Table, Map<String, Object>> function) {
-//        List<DTO> dtos = ListUtil.list(true);
-//        // 信息表格字段和值映射
-//        List<Map<String, Object>> infos = ListUtil.list(true);
-//        Map<String, Object> infoMap = null;
-//        for (Table table : tables) {
-//            Map<String, Object> temp = function.apply(table);
-//            for (String key : temp.keySet()) {
-//                // 如果infoMap为null,先声明然后放在infos中
-//                if (infoMap == null) {
-//                    infoMap = MapUtil.newHashMap(16);
-//                    infos.add(infoMap);
-//                }
-//                // 如果infoMap中包含了该key时,先放infos中然后重新声明新map对象
-//                if (infoMap.containsKey(key)) {
-//                    infos.add(new HashMap<>(infoMap));
-//                    infoMap = MapUtil.newHashMap(16);
-//                } else {
-//                    infoMap.put(key, temp.get(key));
-//                }
-//            }
-//        }
-//        // 分级基金匹配
-//        List<String> levels = ReportParseUtils.matchTieredFund(String.join(",", this.textList));
-//        for (int i = 0; i < infos.size(); i++) {
-//            DTO dto = this.buildDto(fileId, clazz, infos.get(i));
-//            if (dto == null) {
-//                continue;
-//            }
-//            dto.setLevel(levels.get(i));
-//            dtos.add(dto);
-//        }
-//        return dtos;
-//    }
+    /**
+     * 构建只有两列表格的dto数据对象,如果有分级基金时(并且一个表格可能跨页)
+     *
+     * @param <DTO>    泛型对象
+     * @param fileId   文件id
+     * @param tables   表格
+     * @param clazz    泛型对象
+     * @param function 表格转换的函数
+     * @return /
+     */
+    protected <DTO extends BaseReportLevelDTO<?>> List<DTO> buildLevelDto(Integer fileId,
+                                                                          List<Table> tables,
+                                                                          Class<DTO> clazz,
+                                                                          Function<Table, Map<String, Object>> function) {
+        List<DTO> dtos = ListUtil.list(true);
+        // 信息表格字段和值映射
+        List<Map<String, Object>> infos = ListUtil.list(true);
+        for (Table table : tables) {
+            Map<String, Object> infoMap = MapUtil.newHashMap(16);
+            Map<String, Object> temp = function.apply(table);
+            for (String key : temp.keySet()) {
+                // 如果infoMap中包含了该key时,先放infos中然后重新声明新map对象
+                if (infoMap.containsKey(key)) {
+                    infos.add(new HashMap<>(infoMap));
+                    infoMap = MapUtil.newHashMap(16);
+                } else {
+                    infoMap.put(key, temp.get(key));
+                }
+            }
+            infos.add(infoMap);
+        }
+        // 分级基金匹配
+        List<String> levels = ReportParseUtils.matchTieredFund(String.join(",", this.textList));
+        for (int i = 0; i < infos.size(); i++) {
+            DTO dto = this.buildDto(fileId, clazz, infos.get(i));
+            if (dto == null) {
+                continue;
+            }
+            if (levels.size() > i) {
+                dto.setLevel(levels.get(i));
+            }
+            dtos.add(dto);
+        }
+        return dtos;
+    }
+
+    /**
+     * 判断表格是否需要合并并且把需要合并的表格放在一个索引对应的map中(主要处理有分级基金数据表格,不处理可能会把数据绑定到错误的分级基金中)
+     *
+     * @param table                待判断的表格
+     * @param rowCount             判断依据(一个完整的表格有多少行)
+     * @param index                当前完整表格所在的索引位置
+     * @param tables               不需要合并的表格集合
+     * @param spanningPageTableMap 需要合并的表格数据
+     * @return /
+     */
+    protected int splitTables(Table table, int rowCount, int index,
+                              List<Table> tables, Map<Integer, List<Table>> spanningPageTableMap) {
+        if (table.getRowCount() == rowCount) {
+            index++;
+            tables.add(table);
+        } else {
+            List<Table> tempList = spanningPageTableMap.getOrDefault(index, ListUtil.list(true));
+            tempList.add(table);
+            spanningPageTableMap.putIfAbsent(index, tempList);
+            // 一个表格最多跨两页,所以一个表格最多被分成2部分
+            if (tempList.size() == 2) {
+                index++;
+            }
+        }
+        return index;
+    }
+
+    /**
+     * 把跨页的表格合并为一个并且插入到数据集合中的特定位置
+     *
+     * @param tables               数据集合
+     * @param spanningPageTableMap 跨页的表格对象
+     */
+    protected void handleSpanningPageTables(List<Table> tables,
+                                            Map<Integer, List<Table>> spanningPageTableMap) {
+        // 跨页的表格数据处理
+        for (Map.Entry<Integer, List<Table>> entry : spanningPageTableMap.entrySet()) {
+            List<Table> spanningPageShareChangeTables = entry.getValue();
+            Table master = spanningPageShareChangeTables.get(0);
+            if (spanningPageShareChangeTables.size() == 2) {
+                Table slave = spanningPageShareChangeTables.get(1);
+                int rowCount = master.getRowCount();
+                for (int j = 0; j < slave.getRowCount(); j++) {
+                    for (int k = 0; k < slave.getColCount(); k++) {
+                        master.add(slave.getCell(j, k), rowCount + j, k);
+                    }
+                }
+            }
+            tables.add(entry.getKey(), master);
+        }
+    }
 }

+ 150 - 143
mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/pdf/PDAnnuallyReportParser.java

@@ -1,143 +1,150 @@
-//package com.smppw.modaq.infrastructure.components.report.parser.pdf;
-//
-//import cn.hutool.core.collection.CollUtil;
-//import cn.hutool.core.collection.ListUtil;
-//import cn.hutool.core.map.MapUtil;
-//import com.smppw.modaq.domain.mapper.EmailFieldMappingMapper;
-//import com.smppw.modaq.infrastructure.components.ReportParseUtils;
-//import com.smppw.modaq.infrastructure.components.report.parser.ReportParserConstant;
-//import com.smppw.modaq.infrastructure.dto.report.AnnuallyReportData;
-//import com.smppw.modaq.infrastructure.dto.report.ReportFundInfoDTO;
-//import com.smppw.modaq.infrastructure.dto.report.ReportParserParams;
-//import org.springframework.stereotype.Component;
-//import technology.tabula.Table;
-//
-//import java.util.List;
-//import java.util.Map;
-//import java.util.Set;
-//import java.util.function.Function;
-//
-///**
-// * @author wangzaijun
-// * @date 2024/10/10 17:34
-// * @description 年报解析逻辑:基本信息被拆分为多个表格,财务报表未解析
-// */
-//@Component(ReportParserConstant.PARSER_PDF_ANNUALLY)
-//public class PDAnnuallyReportParser extends PDQuarterlyReportParser<AnnuallyReportData> {
-//    private List<Table> fundInfoTables;
-//
-//    public PDAnnuallyReportParser(EmailFieldMappingMapper fieldMappingMapper) {
-//        super(fieldMappingMapper);
-//    }
-//
-//    @Override
-//    public String getParser() {
-//        return ReportParserConstant.PARSER_PDF_ANNUALLY;
-//    }
-//
-//    @Override
-//    protected void initTableInfo(List<Table> tables) {
-//        // 初始化
-//        this.fundInfoTables = ListUtil.list(true);
-//        this.financialIndicatorsTables = ListUtil.list(true);
-//        this.shareChangeTables = ListUtil.list(true);
-//        this.assetAllocationTables = ListUtil.list(true);
-//        this.investmentIndustryTables = ListUtil.list(true);
-//        for (int i = 0; i < tables.size(); i++) {
-//            Table table = tables.get(i);
-//            if (i <= 1) {
-//                this.fundInfoTables.add(table);
-//                continue;
-//            }
-//            // 用表格的第一列的数据判断是否主要财务指标数据
-//            List<String> texts = this.getTableColTexts(table, 0);
-//            if (CollUtil.containsAny(texts, ReportParseUtils.FINANCIAL_INDICATORS_COLUMN_NAMES)) {
-//                this.financialIndicatorsTables.add(table);
-//                continue;
-//            }
-//            int colCount = table.getColCount();
-//            if (colCount == 2) {
-//                // 用表格的第一列的数据判断是否份额变动记录
-//                if (CollUtil.containsAny(texts, ReportParseUtils.SHARE_CHANGE_COLUMN_NAMES)) {
-//                    this.shareChangeTables.add(table);
-//                }
-//            } else if (colCount == 4) {
-//                // 用表格的第二列的数据判断是否行业配置数据(内地)
-//                texts = this.getTableColTexts(table, 1);
-//                if (CollUtil.containsAny(texts, ReportParseUtils.INDUSTRY_COLUMN_NAMES)) {
-//                    this.investmentIndustryTables.add(table);
-//                }
-//            } else if (colCount == 3) {
-//                // 用表格的第一列的数据判断是否行业配置数据(港股通)
-//                if (CollUtil.containsAny(texts, ReportParseUtils.INDUSTRY_COLUMN_NAMES)) {
-//                    this.investmentIndustryTables.add(table);
-//                    continue;
-//                }
-//                // 资产配置表格识别(兼容跨页的表格)获取表格中第二列的所有文字,判断所有文字中包含"股权投资"等字符串
-//                texts = this.getTableColTexts(table, 1);
-//                Set<String> keys = ReportParseUtils.ASSET_ALLOCATION_TYPE_MAPPER.keySet();
-//                if (CollUtil.containsAny(texts, keys)) {
-//                    this.assetAllocationTables.add(table);
-//                }
-//            }
-//        }
-//    }
-//
-//    @Override
-//    protected ReportFundInfoDTO buildFundInfo(ReportParserParams params) {
-//        Map<String, Object> fundInfoMap = MapUtil.newHashMap(32);
-//        for (Table table : this.fundInfoTables) {
-//            Map<String, Object> temp = this.parseFundInfo(table);
-//            fundInfoMap.putAll(temp);
-//        }
-//        ReportFundInfoDTO info = new ReportFundInfoDTO(params.getFileId());
-//        this.buildInfo(fundInfoMap, info);
-//        return info;
-//    }
-//
-//    @Override
-//    protected AnnuallyReportData buildReportData(ReportBaseInfoDTO reportInfo, ReportFundInfoDTO fundInfo,
-//                                                 List<ReportShareChangeDTO> shareChanges, List<ReportFinancialIndicatorsDTO> financialIndicators,
-//                                                 List<ReportAssetAllocationDTO> assetAllocations, List<ReportInvestmentIndustryDTO> investmentIndustries) {
-//        AnnuallyReportData reportData = new AnnuallyReportData(reportInfo, fundInfo);
-//        reportData.setShareChange(shareChanges);
-//        reportData.setFinancialIndicators(financialIndicators);
-//        reportData.setAssetAllocation(assetAllocations);
-//        reportData.setInvestmentIndustry(investmentIndustries);
-//        return reportData;
-//    }
-//
-//    @Override
-//    protected void cleaningReportData(AnnuallyReportData reportData) {
-//        // todo 数据清洗
-//    }
-//
-//    protected List<ReportFinancialIndicatorsDTO> buildFinancialIndicatorsInfo(Integer fileId, Function<Table, Map<String, Object>> function) {
-//        List<ReportFinancialIndicatorsDTO> dtos = ListUtil.list(false);
-//        // 分级基金
-//        List<String> levels = ReportParseUtils.matchTieredFund(String.join(",", this.textList));
-//        // 假设这里可能存在分级基金,不存在表格跨页
-//        for (int k = 0; k < this.financialIndicatorsTables.size(); k++) {
-//            Table table = this.financialIndicatorsTables.get(k);
-//            int colCount = table.getColCount();
-//            for (int j = 1; j < colCount; j++) {
-//                Map<String, Object> infoMap = MapUtil.newHashMap(16);
-//                String year = ReportParseUtils.cleaningValue(table.getCell(0, j).getText());
-//                infoMap.put("年度", year);
-//                for (int i = 0; i < table.getRowCount(); i++) {
-//                    String columnName = ReportParseUtils.cleaningValue(table.getCell(i, 0).getText());
-//                    if (columnName == null) {
-//                        continue;
-//                    }
-//                    String value = ReportParseUtils.cleaningValue(table.getCell(i, j).getText());
-//                    infoMap.put(columnName, value);
-//                }
-//                ReportFinancialIndicatorsDTO dto = new ReportFinancialIndicatorsDTO(fileId);
-//                this.buildInfo(infoMap, dto);
-//                dto.setLevel(levels.get(k));
-//                dtos.add(dto);
-//            }
-//        }
-//        return dtos;
-//    }
-//}
+package com.smppw.modaq.application.components.report.parser.pdf;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.map.MapUtil;
+import com.smppw.modaq.application.components.ReportParseUtils;
+import com.smppw.modaq.application.components.report.parser.ReportParserConstant;
+import com.smppw.modaq.domain.dto.report.*;
+import com.smppw.modaq.domain.mapper.EmailFieldMappingMapper;
+import org.springframework.stereotype.Component;
+import technology.tabula.Table;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * @author wangzaijun
+ * @date 2024/10/10 17:34
+ * @description 年报解析逻辑:基本信息被拆分为多个表格,财务报表未解析
+ */
+@Component(ReportParserConstant.PARSER_PDF_ANNUALLY)
+public class PDAnnuallyReportParser extends PDQuarterlyReportParser<AnnuallyReportData> {
+    private List<Table> fundInfoTables;
+
+    public PDAnnuallyReportParser(EmailFieldMappingMapper fieldMappingMapper) {
+        super(fieldMappingMapper);
+    }
+
+    @Override
+    public String getParser() {
+        return ReportParserConstant.PARSER_PDF_ANNUALLY;
+    }
+
+    @Override
+    protected void init() {
+        super.init();
+        this.fundInfoTables = ListUtil.list(true);
+    }
+
+    @Override
+    protected void initTableInfo(List<Table> tables) {
+        Map<Integer, List<Table>> spanningPageFinancialIndicatorsTableMap = MapUtil.newHashMap(8, true);
+        Map<Integer, List<Table>> spanningPageShareChangeTableMap = MapUtil.newHashMap(8, true);
+        int fi = 0;
+        int sci = 0;
+        for (int i = 0; i < tables.size(); i++) {
+            Table table = tables.get(i);
+            if (i <= 1) {
+                this.fundInfoTables.add(table);
+                continue;
+            }
+            // 用表格的第一列的数据判断是否主要财务指标数据
+            List<String> texts = this.getTableColTexts(table, 0);
+            if (CollUtil.containsAny(texts, ReportParseUtils.FINANCIAL_INDICATORS_COLUMN_NAMES)) {
+                this.splitTables(table, 10, fi, this.financialIndicatorsTables, spanningPageFinancialIndicatorsTableMap);
+                continue;
+            }
+            int colCount = table.getColCount();
+            if (colCount == 2) {
+                // 用表格的第一列的数据判断是否份额变动记录
+                if (CollUtil.containsAny(texts, ReportParseUtils.SHARE_CHANGE_COLUMN_NAMES)) {
+                    this.splitTables(table, 5, sci, this.shareChangeTables, spanningPageShareChangeTableMap);
+                }
+            } else if (colCount == 4) {
+                // 用表格的第二列的数据判断是否行业配置数据(内地)
+                texts = this.getTableColTexts(table, 1);
+                if (CollUtil.containsAny(texts, ReportParseUtils.INDUSTRY_COLUMN_NAMES)) {
+                    this.investmentIndustryTables.add(table);
+                }
+            } else if (colCount == 3) {
+                // 用表格的第一列的数据判断是否行业配置数据(港股通)
+                if (CollUtil.containsAny(texts, ReportParseUtils.INDUSTRY_COLUMN_NAMES)) {
+                    this.investmentIndustryTables.add(table);
+                    continue;
+                }
+                // 资产配置表格识别(兼容跨页的表格)获取表格中第二列的所有文字,判断所有文字中包含"股权投资"等字符串
+                texts = this.getTableColTexts(table, 1);
+                Set<String> keys = ReportParseUtils.ASSET_ALLOCATION_TYPE_MAPPER.keySet();
+                if (CollUtil.containsAny(texts, keys)) {
+                    this.assetAllocationTables.add(table);
+                }
+            }
+        }
+        // 跨页的财务信息记录表(包括表头一共有10行)
+        this.handleSpanningPageTables(this.financialIndicatorsTables, spanningPageFinancialIndicatorsTableMap);
+        // 跨页的份额变动记录表(包括表头一共有5行)
+        this.handleSpanningPageTables(this.shareChangeTables, spanningPageShareChangeTableMap);
+    }
+
+    @Override
+    protected ReportFundInfoDTO buildFundInfo(ReportParserParams params) {
+        Map<String, Object> fundInfoMap = MapUtil.newHashMap(32);
+        for (Table table : this.fundInfoTables) {
+            Map<String, Object> temp = this.parseFundInfo(table);
+            fundInfoMap.putAll(temp);
+        }
+        ReportFundInfoDTO info = new ReportFundInfoDTO(params.getFileId());
+        this.buildInfo(fundInfoMap, info);
+        return info;
+    }
+
+    @Override
+    protected AnnuallyReportData buildReportData(ReportBaseInfoDTO reportInfo,
+                                                 ReportFundInfoDTO fundInfo,
+                                                 List<ReportShareChangeDTO> shareChanges,
+                                                 List<ReportFinancialIndicatorsDTO> financialIndicators,
+                                                 List<ReportAssetAllocationDTO> assetAllocations,
+                                                 List<ReportInvestmentIndustryDTO> investmentIndustries) {
+        AnnuallyReportData reportData = new AnnuallyReportData(reportInfo, fundInfo);
+        reportData.setShareChange(shareChanges);
+        reportData.setFinancialIndicators(financialIndicators);
+        reportData.setAssetAllocation(assetAllocations);
+        reportData.setInvestmentIndustry(investmentIndustries);
+        return reportData;
+    }
+
+    protected List<ReportFinancialIndicatorsDTO> buildFinancialIndicatorsInfo(Integer fileId,
+                                                                              Function<Table, Map<String, Object>> function) {
+        List<ReportFinancialIndicatorsDTO> dtos = ListUtil.list(false);
+        // 分级基金
+        List<String> levels = ReportParseUtils.matchTieredFund(String.join(",", this.textList));
+        // 假设这里可能存在分级基金,不存在表格跨页
+        for (int k = 0; k < this.financialIndicatorsTables.size(); k++) {
+            Table table = this.financialIndicatorsTables.get(k);
+            int colCount = table.getColCount();
+            for (int j = 1; j < colCount; j++) {
+                Map<String, Object> infoMap = MapUtil.newHashMap(16);
+                String year = ReportParseUtils.cleaningValue(table.getCell(0, j).getText());
+                infoMap.put("年度", year);
+                for (int i = 0; i < table.getRowCount(); i++) {
+                    String columnName = ReportParseUtils.cleaningValue(table.getCell(i, 0).getText());
+                    if (columnName == null) {
+                        continue;
+                    }
+                    String value = ReportParseUtils.cleaningValue(table.getCell(i, j).getText());
+                    infoMap.put(columnName, value);
+                }
+                ReportFinancialIndicatorsDTO dto = new ReportFinancialIndicatorsDTO(fileId);
+                this.buildInfo(infoMap, dto);
+                if (levels.size() > k) {
+                    dto.setLevel(levels.get(k));
+                }
+                dtos.add(dto);
+            }
+        }
+        return dtos;
+    }
+}

+ 1 - 8
mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/pdf/PDLetterReportParser.java

@@ -26,13 +26,6 @@ public class PDLetterReportParser extends AbstractPDReportParser<LetterReportDat
     protected void initTableInfo(List<Table> tables) {
         // 每次重新清空map数据
         this.allInfoMap.clear();
-//        if (CollUtil.isEmpty(tables)) {
-//            if (StrUtil.isNotBlank(this.aiParserContent)) {
-//                JSONObject jsonObject = JSONUtil.parseObj(this.aiParserContent);
-//                this.allInfoMap.putAll(flattenMap(jsonObject, ListUtil.list(false)));
-//            }
-//            return;
-//        }
         for (Table table : tables) {
             int rowCount = table.getRowCount();
             int colCount = table.getColCount();
@@ -84,7 +77,7 @@ public class PDLetterReportParser extends AbstractPDReportParser<LetterReportDat
     protected LetterReportData parseExtInfoAndSetData(ReportBaseInfoDTO reportInfo, ReportFundInfoDTO fundInfo) {
         Integer fileId = reportInfo.getFileId();
         if (this.logger.isInfoEnabled()) {
-            this.logger.info("文件{} 解析内容是:{}", fileId, allInfoMap);
+            this.logger.info("文件{} 解析内容是:{}", fileId, this.allInfoMap);
         }
         // 投资者信息
         ReportInvestorInfoDTO investorInfo = this.buildDto(fileId, ReportInvestorInfoDTO.class, this.allInfoMap);

+ 100 - 89
mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/pdf/PDMonthlyReportParser.java

@@ -1,89 +1,100 @@
-//package com.smppw.modaq.infrastructure.components.report.parser.pdf;
-//
-//import cn.hutool.core.map.MapUtil;
-//import com.smppw.modaq.domain.mapper.EmailFieldMappingMapper;
-//import com.smppw.modaq.infrastructure.components.report.parser.ReportParserConstant;
-//import com.smppw.modaq.infrastructure.dto.report.MonthlyReportData;
-//import com.smppw.modaq.infrastructure.dto.report.ReportBaseInfoDTO;
-//import com.smppw.modaq.infrastructure.dto.report.ReportFundInfoDTO;
-//import org.springframework.stereotype.Component;
-//import technology.tabula.RectangularTextContainer;
-//import technology.tabula.Table;
-//
-//import java.util.List;
-//import java.util.Map;
-//
-///**
-// * @author wangzaijun
-// * @date 2024/9/11 16:19
-// * @description pdf格式的月报解析
-// */
-//@Component(ReportParserConstant.PARSER_PDF_MONTHLY)
-//public class PDMonthlyReportParser extends AbstractPDReportParser<MonthlyReportData> {
-////    private List<Table> extNavTables;
-//
-//    public PDMonthlyReportParser(EmailFieldMappingMapper fieldMappingMapper) {
-//        super(fieldMappingMapper);
-//    }
-//
-//    @Override
-//    public String getParser() {
-//        return ReportParserConstant.PARSER_PDF_MONTHLY;
-//    }
-//
-//    @Override
-//    protected void initTableInfo(List<Table> tables) {
-////        // 这里初始化
-////        this.extNavTables = ListUtil.list(true);
-////        // 一般月报是固定的模板,4列表格是基金基本信息,其他5列的表格是月净值
-////        for (Table table : tables) {
-////            int colCount = table.getColCount();
-////            int rowCount = table.getRowCount();
-////            if (colCount == 0 && rowCount == 0) {
-////                continue;
-////            }
-////            if (colCount == 4) {
-////                this.fundInfoTable = table;
-////            } else if (colCount >= 5) {
-////                this.extNavTables.add(table);
-////            }
-////        }
-//    }
-//
-//    @Override
-//    protected Map<String, Object> parseFundInfo(Table fundInfoTable) {
-//        // 月报的基金基本信息是四列的表格
-//        Map<String, Object> baseInfoMap = MapUtil.newHashMap(32);
-//        for (int i = 0; i < fundInfoTable.getRows().size(); i++) {
-//            @SuppressWarnings("all")
-//            List<RectangularTextContainer> cols = fundInfoTable.getRows().get(i);
-//            for (int j = 0; j < 2; j++) {
-//                baseInfoMap.put(cols.get(j * 2).getText(), cols.get(j * 2 + 1).getText());
-//            }
-//        }
-//        return baseInfoMap;
-//    }
-//
-//    @Override
-//    protected MonthlyReportData parseExtInfoAndSetData(ReportBaseInfoDTO reportInfo, ReportFundInfoDTO fundInfo) {
-//        MonthlyReportData reportData = new MonthlyReportData(reportInfo, fundInfo);
-////        // 母基金和分级基金的净值
-////        List<ReportNetReportDTO> dtos = this.buildLevelDto(reportInfo.getFileId(), this.extNavTables,
-////                ReportNetReportDTO.class, t -> {
-////                    Map<String, Object> extInfoMap = MapUtil.newHashMap(16);
-////                    for (int i = 0; i < t.getColCount(); i++) {
-////                        String key = t.getCell(0, i).getText();
-////                        String value = t.getCell(1, i).getText();
-////                        extInfoMap.put(key, value);
-////                    }
-////                    return extInfoMap;
-////                });
-////        reportData.setNetReport(dtos);
-//        return reportData;
-//    }
-//
-//    @Override
-//    protected void cleaningReportData(MonthlyReportData reportData) {
-//        // todo 数据清洗
-//    }
-//}
+package com.smppw.modaq.application.components.report.parser.pdf;
+
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import com.smppw.modaq.application.components.ReportParseUtils;
+import com.smppw.modaq.application.components.report.parser.ReportParserConstant;
+import com.smppw.modaq.domain.dto.report.*;
+import com.smppw.modaq.domain.mapper.EmailFieldMappingMapper;
+import org.springframework.stereotype.Component;
+import technology.tabula.RectangularTextContainer;
+import technology.tabula.Table;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author wangzaijun
+ * @date 2024/9/11 16:19
+ * @description pdf格式的月报解析
+ */
+@Component(ReportParserConstant.PARSER_PDF_MONTHLY)
+public class PDMonthlyReportParser extends AbstractPDReportParser<MonthlyReportData> {
+    private Table fundInfoTable;
+    private List<Table> extNavTables;
+
+    public PDMonthlyReportParser(EmailFieldMappingMapper fieldMappingMapper) {
+        super(fieldMappingMapper);
+    }
+
+    @Override
+    public String getParser() {
+        return ReportParserConstant.PARSER_PDF_MONTHLY;
+    }
+
+    @Override
+    protected void init() {
+        super.init();
+        // 这里初始化
+        this.extNavTables = ListUtil.list(true);
+    }
+
+    @Override
+    protected void initTableInfo(List<Table> tables) {
+        Map<Integer, List<Table>> spanningPageTableMap = MapUtil.newHashMap(8, true);
+        int index = 0;
+        // 一般月报是固定的模板,4列表格是基金基本信息,其他5列的表格是月净值
+        for (Table table : tables) {
+            int colCount = table.getColCount();
+            int rowCount = table.getRowCount();
+            if (colCount == 0 && rowCount == 0) {
+                continue;
+            }
+            if (colCount == 4) {
+                this.fundInfoTable = table;
+            } else if (colCount == 5) {
+                index = this.splitTables(table, 2, index, this.extNavTables, spanningPageTableMap);
+            }
+        }
+        // 跨页的净值记录表(包括表头一共有2行)
+        this.handleSpanningPageTables(this.extNavTables, spanningPageTableMap);
+    }
+
+    @Override
+    protected ReportFundInfoDTO buildFundInfo(ReportParserParams params) {
+        // 月报的基金基本信息是四列的表格
+        Map<String, Object> baseInfoMap = MapUtil.newHashMap(32);
+        for (int i = 0; i < fundInfoTable.getRows().size(); i++) {
+            @SuppressWarnings("all")
+            List<RectangularTextContainer> cols = fundInfoTable.getRows().get(i);
+            for (int j = 0; j < 2; j++) {
+                baseInfoMap.put(cols.get(j * 2).getText(), cols.get(j * 2 + 1).getText());
+            }
+        }
+        return this.buildDto(params.getFileId(), ReportFundInfoDTO.class, baseInfoMap);
+    }
+
+    @Override
+    protected MonthlyReportData parseExtInfoAndSetData(ReportBaseInfoDTO reportInfo, ReportFundInfoDTO fundInfo) {
+        MonthlyReportData reportData = new MonthlyReportData(reportInfo, fundInfo);
+        // 母基金和分级基金的净值
+        List<ReportNetReportDTO> dtos = this.buildLevelDto(reportInfo.getFileId(), this.extNavTables,
+                ReportNetReportDTO.class, t -> {
+                    Map<String, Object> extInfoMap = MapUtil.newHashMap(16);
+                    // 限制只能两行数据
+                    if (t.getRowCount() != 2) {
+                        return extInfoMap;
+                    }
+                    for (int i = 0; i < t.getColCount(); i++) {
+                        String key = ReportParseUtils.cleaningValue(t.getCell(0, i).getText());
+                        key = StrUtil.subBefore(key, "(", false);
+                        String value = t.getCell(1, i).getText();
+                        extInfoMap.put(key, value);
+                    }
+                    return extInfoMap;
+                });
+        reportData.setNetReport(dtos);
+        return reportData;
+    }
+}

+ 289 - 266
mo-daq/src/main/java/com/smppw/modaq/application/components/report/parser/pdf/PDQuarterlyReportParser.java

@@ -1,266 +1,289 @@
-//package com.smppw.modaq.infrastructure.components.report.parser.pdf;
-//
-//import cn.hutool.core.collection.CollUtil;
-//import cn.hutool.core.collection.ListUtil;
-//import cn.hutool.core.map.MapUtil;
-//import cn.hutool.core.util.StrUtil;
-//import com.simuwang.base.mapper.EmailFieldMappingMapper;
-//import com.simuwang.base.pojo.dto.report.*;
-//import com.simuwang.daq.components.ReportParseUtils;
-//import com.simuwang.daq.components.report.parser.ReportParserConstant;
-//import org.springframework.stereotype.Component;
-//import technology.tabula.RectangularTextContainer;
-//import technology.tabula.Table;
-//
-//import java.awt.geom.Rectangle2D;
-//import java.util.Comparator;
-//import java.util.List;
-//import java.util.Map;
-//import java.util.Set;
-//import java.util.function.Function;
-//
-///**
-// * @author wangzaijun
-// * @date 2024/9/29 17:53
-// * @description pdf格式的季报解析逻辑
-// */
-//@Component(ReportParserConstant.PARSER_PDF_QUARTERLY)
-//public class PDQuarterlyReportParser<T extends QuarterlyReportData> extends AbstractPDReportParser<T> {
-//    protected List<Table> financialIndicatorsTables;
-//    protected List<Table> shareChangeTables;
-//    protected List<Table> assetAllocationTables;
-//    protected List<Table> investmentIndustryTables;
-//
-//    public PDQuarterlyReportParser(EmailFieldMappingMapper fieldMappingMapper) {
-//        super(fieldMappingMapper);
-//    }
-//
-//    @Override
-//    public String getParser() {
-//        return ReportParserConstant.PARSER_PDF_QUARTERLY;
-//    }
-//
-//    @Override
-//    protected void initTableInfo(List<Table> tables) {
-//        this.financialIndicatorsTables = ListUtil.list(true);
-//        this.shareChangeTables = ListUtil.list(true);
-//        this.assetAllocationTables = ListUtil.list(true);
-//        this.investmentIndustryTables = ListUtil.list(true);
-//        for (Table table : tables) {
-//            int colCount = table.getColCount();
-//            int rowCount = table.getRowCount();
-//            if (colCount == 0 && rowCount == 0) {
-//                continue;
-//            }
-//            if (rowCount == 13 && colCount == 2) {
-//                this.fundInfoTable = table;
-//            } else if (colCount == 2) {
-//                // 用表格的第一列的数据判断是否份额变动记录
-//                List<String> texts = this.getTableColTexts(table, 0);
-//                // 主要财务指标或份额变动
-//                if (CollUtil.containsAny(texts, ReportParseUtils.SHARE_CHANGE_COLUMN_NAMES)) {
-//                    this.shareChangeTables.add(table);
-//                } else if (CollUtil.containsAny(texts, ReportParseUtils.FINANCIAL_INDICATORS_COLUMN_NAMES)) {
-//                    this.financialIndicatorsTables.add(table);
-//                }
-//            } else if (colCount == 4) {
-//                // 行业配置
-//                this.investmentIndustryTables.add(table);
-//            } else if (colCount == 3) {
-//                // 用表格的第一列单元格判断是否资产配置表
-//                List<String> texts = this.getTableColTexts(table, 0);
-//                if (CollUtil.containsAny(texts, ReportParseUtils.INDUSTRY_COLUMN_NAMES)) {
-//                    this.investmentIndustryTables.add(table);
-//                } else {
-//                    texts = this.getTableColTexts(table, 1);
-//                    Set<String> keys = ReportParseUtils.ASSET_ALLOCATION_TYPE_MAPPER.keySet();
-//                    if (CollUtil.containsAny(texts, keys)) {
-//                        this.assetAllocationTables.add(table);
-//                    }
-//                }
-//            }
-//        }
-//    }
-//
-//    @Override
-//    protected Map<String, Object> parseFundInfo(Table fundInfoTable) {
-//        // 季报和年报的基金基本信息是两列的表格
-//        Map<String, Object> baseInfoMap = MapUtil.newHashMap(32);
-//        for (int i = 0; i < fundInfoTable.getRows().size(); i++) {
-//            @SuppressWarnings("all")
-//            List<RectangularTextContainer> cols = fundInfoTable.getRows().get(i);
-//            for (int j = 0; j < 1; j++) {
-//                baseInfoMap.put(cols.get(j).getText(), cols.get(j + 1).getText());
-//            }
-//        }
-//        return baseInfoMap;
-//    }
-//
-//    @Override
-//    protected T parseExtInfoAndSetData(ReportBaseInfoDTO reportInfo, ReportFundInfoDTO fundInfo) {
-//        Integer fileId = reportInfo.getFileId();
-//        // 表格转换数据获取函数
-//        Function<Table, Map<String, Object>> function = t -> {
-//            Map<String, Object> extInfoMap = MapUtil.newHashMap(16);
-//            for (int i = 0; i < t.getRowCount(); i++) {
-//                String key = t.getCell(i, 0).getText();
-//                String value = t.getCell(i, 1).getText();
-//                extInfoMap.put(key, value);
-//            }
-//            return extInfoMap;
-//        };
-//        // 份额变动
-//        List<ReportShareChangeDTO> shareChanges = this.buildLevelDto(fileId, this.shareChangeTables,
-//                ReportShareChangeDTO.class, function);
-//        // 主要财务指标
-//        List<ReportFinancialIndicatorsDTO> financialIndicators = this.buildFinancialIndicatorsInfo(fileId, function);
-//        // 资产配置
-//        List<ReportAssetAllocationDTO> assetAllocations = this.buildAssetAllocationInfo(fileId);
-//        // 行业配置
-//        List<ReportInvestmentIndustryDTO> investmentIndustries = this.buildInvestmentIndustryInfo(fileId);
-//        // 返回数据构建
-//        return this.buildReportData(reportInfo, fundInfo, shareChanges, financialIndicators, assetAllocations, investmentIndustries);
-//    }
-//
-//    /**
-//     * 主要财务指标数据构建(包括分级基金,并且一个表格可能跨页)
-//     *
-//     * @param fileId   文件id
-//     * @param function 字段映射关系
-//     * @return /
-//     */
-//    protected List<ReportFinancialIndicatorsDTO> buildFinancialIndicatorsInfo(Integer fileId, Function<Table, Map<String, Object>> function) {
-//        return this.buildLevelDto(fileId, this.financialIndicatorsTables, ReportFinancialIndicatorsDTO.class, function);
-//    }
-//
-//    /**
-//     * 子类重写,放在cast异常
-//     *
-//     * @param reportInfo           报告基本信息
-//     * @param fundInfo             基金基本信息
-//     * @param shareChanges         份额变动
-//     * @param financialIndicators  基本财务指标
-//     * @param assetAllocations     资产配置
-//     * @param investmentIndustries 行业配置
-//     * @return /
-//     */
-//    protected T buildReportData(ReportBaseInfoDTO reportInfo, ReportFundInfoDTO fundInfo,
-//                                List<ReportShareChangeDTO> shareChanges,
-//                                List<ReportFinancialIndicatorsDTO> financialIndicators,
-//                                List<ReportAssetAllocationDTO> assetAllocations,
-//                                List<ReportInvestmentIndustryDTO> investmentIndustries) {
-//        QuarterlyReportData reportData = new QuarterlyReportData(reportInfo, fundInfo);
-//        reportData.setShareChange(shareChanges);
-//        reportData.setFinancialIndicators(financialIndicators);
-//        reportData.setAssetAllocation(assetAllocations);
-//        reportData.setInvestmentIndustry(investmentIndustries);
-//        @SuppressWarnings("unchecked")
-//        T t = (T) reportData;
-//        return t;
-//    }
-//
-//    @Override
-//    protected void cleaningReportData(T reportData) {
-//        // todo 数据清洗
-//    }
-//
-//    /**
-//     * 构建基金行业配置解析数据
-//     *
-//     * @return /
-//     */
-//    private List<ReportInvestmentIndustryDTO> buildInvestmentIndustryInfo(Integer fileId) {
-//        List<ReportInvestmentIndustryDTO> dtos = ListUtil.list(false);
-//        for (Table table : this.investmentIndustryTables) {
-//            int colCount = table.getColCount();
-//            // 投资地区: 1-境内, 2-港股通
-//            int investType = colCount == 4 ? 1 : 2;
-//            int j = colCount == 4 ? 1 : 0;
-//            // 按行遍历
-//            for (int i = 0; i < table.getRowCount(); i++) {
-//                String text = ReportParseUtils.cleaningValue(table.getCell(i, 0).getText());
-//                if (StrUtil.containsAny(text, "序号", "行业类别")) {
-//                    continue;
-//                }
-//                String industryName = ReportParseUtils.cleaningValue(table.getCell(i, j).getText());
-//                if (StrUtil.isBlank(industryName) || !ReportParseUtils.INDUSTRY_COLUMN_NAMES.contains(industryName)) {
-//                    continue;
-//                }
-//                ReportInvestmentIndustryDTO dto = new ReportInvestmentIndustryDTO(fileId);
-//                dto.setInvestType(investType);
-//                dto.setIndustryName(industryName);
-//                dto.setMarketValue(ReportParseUtils.cleaningValue(table.getCell(i, j + 1).getText()));
-//                dto.setRatio(ReportParseUtils.cleaningValue(table.getCell(i, j + 2).getText()));
-//                dtos.add(dto);
-//            }
-//        }
-//        return dtos;
-//    }
-//
-//    /**
-//     * 构建基金资产配置解析数据
-//     *
-//     * @param fileId 文件id
-//     * @return /
-//     */
-//    private List<ReportAssetAllocationDTO> buildAssetAllocationInfo(Integer fileId) {
-//        List<ReportAssetAllocationDTO> dtos = ListUtil.list(false);
-//        for (Table table : this.assetAllocationTables) {
-//            // 按行遍历
-//            for (@SuppressWarnings("all") List<RectangularTextContainer> row : table.getRows()) {
-//                // x坐标升序(防止部分行乱序问题)
-//                row.sort(Comparator.comparing(Rectangle2D.Float::getX));
-//                // 金额、市值,有时是 “备注#金额”的格式
-//                String marketValueAndRemark = ReportParseUtils.cleaningValue(row.get(2).getText());
-//                // 资产明细
-//                String detail = ReportParseUtils.cleaningValue(row.get(1).getText(), false);
-//                if (!ReportParseUtils.ASSET_ALLOCATION_TYPE_MAPPER.containsKey(detail)) {
-//                    continue;
-//                }
-//                // 大类
-//                String assetType = ReportParseUtils.ASSET_ALLOCATION_TYPE_MAPPER.get(detail);
-//                if (StrUtil.contains(marketValueAndRemark, "#")) {
-//                    // 有#表示有备注,而且可能有多个,多个用分号分隔的.
-//                    List<String> marketValueAndRemarks = StrUtil.split(marketValueAndRemark, ";");
-//                    for (String mr : marketValueAndRemarks) {
-//                        if (StrUtil.isBlank(mr)) {
-//                            continue;
-//                        }
-//                        List<String> mrs = StrUtil.split(mr, "#");
-//                        ReportAssetAllocationDTO dto = new ReportAssetAllocationDTO(fileId);
-//                        dto.setAssetType(assetType);
-//                        dto.setAssetDetails(detail);
-//                        dto.setMarketValue(mrs.get(1));
-//                        dto.setRemark(mrs.get(0));
-//                        dtos.add(dto);
-//                    }
-//                } else {
-//                    ReportAssetAllocationDTO dto = new ReportAssetAllocationDTO(fileId);
-//                    dto.setAssetType(assetType);
-//                    dto.setAssetDetails(detail);
-//                    dto.setMarketValue(marketValueAndRemark);
-//                    dtos.add(dto);
-//                }
-//            }
-//        }
-//        return dtos;
-//    }
-//
-//    /**
-//     * 获取表格指定列的所有文字内容
-//     *
-//     * @param table 表格
-//     * @param col   指定列
-//     * @return /
-//     */
-//    protected List<String> getTableColTexts(Table table, Integer col) {
-//        List<String> details = ListUtil.list(false);
-//        for (@SuppressWarnings("all") List<RectangularTextContainer> row : table.getRows()) {
-//            String detail = ReportParseUtils.cleaningValue(row.get(col).getText(), false);
-//            if (StrUtil.isNotBlank(detail)) {
-//                details.add(detail);
-//            }
-//        }
-//        return details;
-//    }
-//}
+package com.smppw.modaq.application.components.report.parser.pdf;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import com.smppw.modaq.application.components.ReportParseUtils;
+import com.smppw.modaq.application.components.report.parser.ReportParserConstant;
+import com.smppw.modaq.common.enums.ReportParseStatus;
+import com.smppw.modaq.common.exception.ReportParseException;
+import com.smppw.modaq.domain.dto.report.*;
+import com.smppw.modaq.domain.mapper.EmailFieldMappingMapper;
+import org.springframework.stereotype.Component;
+import technology.tabula.RectangularTextContainer;
+import technology.tabula.Table;
+
+import java.awt.geom.Rectangle2D;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * @author wangzaijun
+ * @date 2024/9/29 17:53
+ * @description pdf格式的季报解析逻辑
+ */
+@Component(ReportParserConstant.PARSER_PDF_QUARTERLY)
+public class PDQuarterlyReportParser<T extends QuarterlyReportData> extends AbstractPDReportParser<T> {
+    protected Table fundInfoTable;
+    protected List<Table> financialIndicatorsTables;
+    protected List<Table> shareChangeTables;
+    protected List<Table> assetAllocationTables;
+    protected List<Table> investmentIndustryTables;
+
+    public PDQuarterlyReportParser(EmailFieldMappingMapper fieldMappingMapper) {
+        super(fieldMappingMapper);
+    }
+
+    @Override
+    public String getParser() {
+        return ReportParserConstant.PARSER_PDF_QUARTERLY;
+    }
+
+    @Override
+    protected void init() {
+        super.init();
+        this.fundInfoTable = null;
+        this.financialIndicatorsTables = ListUtil.list(true);
+        this.shareChangeTables = ListUtil.list(true);
+        this.assetAllocationTables = ListUtil.list(true);
+        this.investmentIndustryTables = ListUtil.list(true);
+    }
+
+    @Override
+    protected void initTableInfo(List<Table> tables) {
+        Map<Integer, List<Table>> spanningPageFinancialIndicatorsTableMap = MapUtil.newHashMap(8, true);
+        Map<Integer, List<Table>> spanningPageShareChangeTableMap = MapUtil.newHashMap(8, true);
+        int fi = 0;
+        int sci = 0;
+        for (Table table : tables) {
+            int colCount = table.getColCount();
+            int rowCount = table.getRowCount();
+            if (colCount == 0 && rowCount == 0) {
+                continue;
+            }
+            if (rowCount == 13 && colCount == 2) {
+                this.fundInfoTable = table;
+            } else if (colCount == 2) {
+                // 用表格的第一列的数据判断是否份额变动记录
+                List<String> texts = this.getTableColTexts(table, 0);
+                // 主要财务指标或份额变动
+                if (CollUtil.containsAny(texts, ReportParseUtils.SHARE_CHANGE_COLUMN_NAMES)) {
+                    sci = this.splitTables(table, 5, sci, this.shareChangeTables, spanningPageShareChangeTableMap);
+                } else if (CollUtil.containsAny(texts, ReportParseUtils.FINANCIAL_INDICATORS_COLUMN_NAMES)) {
+                    fi = this.splitTables(table, 10, fi, this.financialIndicatorsTables, spanningPageFinancialIndicatorsTableMap);
+                }
+            } else if (colCount == 4) {
+                // 行业配置
+                this.investmentIndustryTables.add(table);
+            } else if (colCount == 3) {
+                // 用表格的第一列单元格判断是否资产配置表
+                List<String> texts = this.getTableColTexts(table, 0);
+                if (CollUtil.containsAny(texts, ReportParseUtils.INDUSTRY_COLUMN_NAMES)) {
+                    this.investmentIndustryTables.add(table);
+                } else {
+                    texts = this.getTableColTexts(table, 1);
+                    Set<String> keys = ReportParseUtils.ASSET_ALLOCATION_TYPE_MAPPER.keySet();
+                    if (CollUtil.containsAny(texts, keys)) {
+                        this.assetAllocationTables.add(table);
+                    }
+                }
+            }
+        }
+        // 跨页的财务信息记录表(包括表头一共有10行)
+        this.handleSpanningPageTables(this.financialIndicatorsTables, spanningPageFinancialIndicatorsTableMap);
+        // 跨页的份额变动记录表(包括表头一共有5行)
+        this.handleSpanningPageTables(this.shareChangeTables, spanningPageShareChangeTableMap);
+    }
+
+    @Override
+    protected ReportFundInfoDTO buildFundInfo(ReportParserParams params) {
+        Table fundInfoTable = this.fundInfoTable;
+        if (fundInfoTable == null) {
+            throw new ReportParseException(ReportParseStatus.PARSE_FUND_INFO_FAIL, params.getFilename());
+        }
+        // 基金基本信息映射
+        Map<String, Object> extInfoMap = this.parseFundInfo(fundInfoTable);
+        return this.buildDto(params.getFileId(), ReportFundInfoDTO.class, extInfoMap);
+    }
+
+    protected Map<String, Object> parseFundInfo(Table fundInfoTable) {
+        // 季报和年报的基金基本信息是两列的表格
+        Map<String, Object> baseInfoMap = MapUtil.newHashMap(32);
+        for (int i = 0; i < fundInfoTable.getRows().size(); i++) {
+            @SuppressWarnings("all")
+            List<RectangularTextContainer> cols = fundInfoTable.getRows().get(i);
+            for (int j = 0; j < 1; j++) {
+                String key = ReportParseUtils.cleaningValue(cols.get(j).getText());
+                baseInfoMap.put(key, cols.get(j + 1).getText());
+            }
+        }
+        return baseInfoMap;
+    }
+
+    protected T parseExtInfoAndSetData(ReportBaseInfoDTO reportInfo, ReportFundInfoDTO fundInfo) {
+        Integer fileId = reportInfo.getFileId();
+        // 表格转换数据获取函数
+        Function<Table, Map<String, Object>> function = t -> {
+            Map<String, Object> extInfoMap = MapUtil.newHashMap(16);
+            for (int i = 0; i < t.getRowCount(); i++) {
+                String key = ReportParseUtils.cleaningValue(t.getCell(i, 0).getText());
+                String value = t.getCell(i, 1).getText();
+                extInfoMap.put(key, value);
+            }
+            return extInfoMap;
+        };
+        // 份额变动
+        List<ReportShareChangeDTO> shareChanges = this.buildLevelDto(fileId, this.shareChangeTables,
+                ReportShareChangeDTO.class, function);
+        // 主要财务指标
+        List<ReportFinancialIndicatorsDTO> financialIndicators = this.buildFinancialIndicatorsInfo(fileId, function);
+        // 资产配置
+        List<ReportAssetAllocationDTO> assetAllocations = this.buildAssetAllocationInfo(fileId);
+        // 行业配置
+        List<ReportInvestmentIndustryDTO> investmentIndustries = this.buildInvestmentIndustryInfo(fileId);
+        // 返回数据构建
+        return this.buildReportData(reportInfo, fundInfo, shareChanges, financialIndicators, assetAllocations, investmentIndustries);
+    }
+
+    /**
+     * 主要财务指标数据构建(包括分级基金,并且一个表格可能跨页)
+     *
+     * @param fileId   文件id
+     * @param function 字段映射关系
+     * @return /
+     */
+    protected List<ReportFinancialIndicatorsDTO> buildFinancialIndicatorsInfo(Integer fileId,
+                                                                              Function<Table, Map<String, Object>> function) {
+        return this.buildLevelDto(fileId, this.financialIndicatorsTables, ReportFinancialIndicatorsDTO.class, function);
+    }
+
+    /**
+     * 子类重写,放在cast异常
+     *
+     * @param reportInfo           报告基本信息
+     * @param fundInfo             基金基本信息
+     * @param shareChanges         份额变动
+     * @param financialIndicators  基本财务指标
+     * @param assetAllocations     资产配置
+     * @param investmentIndustries 行业配置
+     * @return /
+     */
+    protected T buildReportData(ReportBaseInfoDTO reportInfo, ReportFundInfoDTO fundInfo,
+                                List<ReportShareChangeDTO> shareChanges,
+                                List<ReportFinancialIndicatorsDTO> financialIndicators,
+                                List<ReportAssetAllocationDTO> assetAllocations,
+                                List<ReportInvestmentIndustryDTO> investmentIndustries) {
+        QuarterlyReportData reportData = new QuarterlyReportData(reportInfo, fundInfo);
+        reportData.setShareChange(shareChanges);
+        reportData.setFinancialIndicators(financialIndicators);
+        reportData.setAssetAllocation(assetAllocations);
+        reportData.setInvestmentIndustry(investmentIndustries);
+        @SuppressWarnings("unchecked")
+        T t = (T) reportData;
+        return t;
+    }
+
+    /**
+     * 构建基金行业配置解析数据
+     *
+     * @return /
+     */
+    private List<ReportInvestmentIndustryDTO> buildInvestmentIndustryInfo(Integer fileId) {
+        List<ReportInvestmentIndustryDTO> dtos = ListUtil.list(false);
+        for (Table table : this.investmentIndustryTables) {
+            int colCount = table.getColCount();
+            // 投资地区: 1-境内, 2-港股通
+            int investType = colCount == 4 ? 1 : 2;
+            int j = colCount == 4 ? 1 : 0;
+            // 按行遍历
+            for (int i = 0; i < table.getRowCount(); i++) {
+                String text = ReportParseUtils.cleaningValue(table.getCell(i, 0).getText());
+                if (StrUtil.containsAny(text, "序号", "行业类别")) {
+                    continue;
+                }
+                String industryName = ReportParseUtils.cleaningValue(table.getCell(i, j).getText());
+                if (StrUtil.isBlank(industryName) || !ReportParseUtils.INDUSTRY_COLUMN_NAMES.contains(industryName)) {
+                    continue;
+                }
+                ReportInvestmentIndustryDTO dto = new ReportInvestmentIndustryDTO(fileId);
+                dto.setInvestType(investType);
+                dto.setIndustryName(industryName);
+                dto.setMarketValue(ReportParseUtils.cleaningValue(table.getCell(i, j + 1).getText()));
+                dto.setRatio(ReportParseUtils.cleaningValue(table.getCell(i, j + 2).getText()));
+                dtos.add(dto);
+            }
+        }
+        return dtos;
+    }
+
+    /**
+     * 构建基金资产配置解析数据
+     *
+     * @param fileId 文件id
+     * @return /
+     */
+    private List<ReportAssetAllocationDTO> buildAssetAllocationInfo(Integer fileId) {
+        List<ReportAssetAllocationDTO> dtos = ListUtil.list(false);
+        for (Table table : this.assetAllocationTables) {
+            // 按行遍历
+            for (@SuppressWarnings("all") List<RectangularTextContainer> row : table.getRows()) {
+                // x坐标升序(防止部分行乱序问题)
+                row.sort(Comparator.comparing(Rectangle2D.Float::getX));
+                // 金额、市值,有时是 “备注#金额”的格式
+                String marketValueAndRemark = ReportParseUtils.cleaningValue(row.get(2).getText());
+                // 资产明细
+                String detail = ReportParseUtils.cleaningValue(row.get(1).getText(), false);
+                if (!ReportParseUtils.ASSET_ALLOCATION_TYPE_MAPPER.containsKey(detail)) {
+                    continue;
+                }
+                // 大类
+                String assetType = ReportParseUtils.ASSET_ALLOCATION_TYPE_MAPPER.get(detail);
+                if (StrUtil.contains(marketValueAndRemark, "#")) {
+                    // 有#表示有备注,而且可能有多个,多个用分号分隔的.
+                    List<String> marketValueAndRemarks = StrUtil.split(marketValueAndRemark, ";");
+                    for (String mr : marketValueAndRemarks) {
+                        if (StrUtil.isBlank(mr)) {
+                            continue;
+                        }
+                        List<String> mrs = StrUtil.split(mr, "#");
+                        ReportAssetAllocationDTO dto = new ReportAssetAllocationDTO(fileId);
+                        dto.setAssetType(assetType);
+                        dto.setAssetDetails(detail);
+                        dto.setMarketValue(mrs.get(1));
+                        dto.setRemark(mrs.get(0));
+                        dtos.add(dto);
+                    }
+                } else {
+                    ReportAssetAllocationDTO dto = new ReportAssetAllocationDTO(fileId);
+                    dto.setAssetType(assetType);
+                    dto.setAssetDetails(detail);
+                    dto.setMarketValue(marketValueAndRemark);
+                    dtos.add(dto);
+                }
+            }
+        }
+        return dtos;
+    }
+
+    /**
+     * 获取表格指定列的所有文字内容
+     *
+     * @param table 表格
+     * @param col   指定列
+     * @return /
+     */
+    protected List<String> getTableColTexts(Table table, Integer col) {
+        List<String> details = ListUtil.list(false);
+        for (@SuppressWarnings("all") List<RectangularTextContainer> row : table.getRows()) {
+            String detail = ReportParseUtils.cleaningValue(row.get(col).getText(), false);
+            if (StrUtil.isNotBlank(detail)) {
+                details.add(detail);
+            }
+        }
+        return details;
+    }
+}

+ 2 - 2
mo-daq/src/main/java/com/smppw/modaq/application/components/report/writer/AbstractReportWriter.java

@@ -3,8 +3,8 @@ package com.smppw.modaq.application.components.report.writer;
 import com.smppw.modaq.domain.dto.report.ReportBaseInfoDTO;
 import com.smppw.modaq.domain.dto.report.ReportData;
 import com.smppw.modaq.domain.dto.report.ReportFundInfoDTO;
-import com.smppw.modaq.domain.mapper.ReportBaseInfoMapper;
-import com.smppw.modaq.domain.mapper.ReportFundInfoMapper;
+import com.smppw.modaq.domain.mapper.report.ReportBaseInfoMapper;
+import com.smppw.modaq.domain.mapper.report.ReportFundInfoMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.transaction.annotation.Transactional;

+ 14 - 14
mo-daq/src/main/java/com/smppw/modaq/application/components/report/writer/AnnuallyReportWriter.java

@@ -1,14 +1,14 @@
-//package com.smppw.modaq.infrastructure.components.report.writer;
-//
-//import com.simuwang.base.mapper.report.*;
-//import com.simuwang.base.pojo.dto.report.AnnuallyReportData;
-//import org.springframework.stereotype.Component;
-//
-//@Component(ReportWriterConstant.WRITER_ANNUALLY)
-//public class AnnuallyReportWriter extends QuarterlyReportWriter<AnnuallyReportData> {
-//    public AnnuallyReportWriter(ReportBaseInfoMapper baseInfoMapper, ReportFundInfoMapper fundInfoMapper,
-//                                ReportShareChangeMapper shareChangeMapper, ReportAssetAllocationMapper assetAllocationMapper,
-//                                ReportInvestmentIndustryMapper investmentIndustryMapper, ReportFinancialIndicatorMapper financialIndicatorMapper) {
-//        super(baseInfoMapper, fundInfoMapper, shareChangeMapper, assetAllocationMapper, investmentIndustryMapper, financialIndicatorMapper);
-//    }
-//}
+package com.smppw.modaq.application.components.report.writer;
+
+import com.smppw.modaq.domain.dto.report.AnnuallyReportData;
+import com.smppw.modaq.domain.mapper.report.*;
+import org.springframework.stereotype.Component;
+
+@Component(ReportWriterConstant.WRITER_ANNUALLY)
+public class AnnuallyReportWriter extends QuarterlyReportWriter<AnnuallyReportData> {
+    public AnnuallyReportWriter(ReportBaseInfoMapper baseInfoMapper, ReportFundInfoMapper fundInfoMapper,
+                                ReportShareChangeMapper shareChangeMapper, ReportAssetAllocationMapper assetAllocationMapper,
+                                ReportInvestmentIndustryMapper investmentIndustryMapper, ReportFinancialIndicatorMapper financialIndicatorMapper) {
+        super(baseInfoMapper, fundInfoMapper, shareChangeMapper, assetAllocationMapper, investmentIndustryMapper, financialIndicatorMapper);
+    }
+}

+ 4 - 4
mo-daq/src/main/java/com/smppw/modaq/application/components/report/writer/LetterReportWriter.java

@@ -3,10 +3,10 @@ package com.smppw.modaq.application.components.report.writer;
 import com.smppw.modaq.domain.dto.report.LetterReportData;
 import com.smppw.modaq.domain.dto.report.ReportFundTransactionDTO;
 import com.smppw.modaq.domain.dto.report.ReportInvestorInfoDTO;
-import com.smppw.modaq.domain.mapper.ReportBaseInfoMapper;
-import com.smppw.modaq.domain.mapper.ReportFundInfoMapper;
-import com.smppw.modaq.domain.mapper.ReportFundTransactionMapper;
-import com.smppw.modaq.domain.mapper.ReportInvestorInfoMapper;
+import com.smppw.modaq.domain.mapper.report.ReportBaseInfoMapper;
+import com.smppw.modaq.domain.mapper.report.ReportFundInfoMapper;
+import com.smppw.modaq.domain.mapper.report.ReportFundTransactionMapper;
+import com.smppw.modaq.domain.mapper.report.ReportInvestorInfoMapper;
 import org.springframework.stereotype.Component;
 
 @Component(ReportWriterConstant.WRITER_LETTER)

+ 35 - 25
mo-daq/src/main/java/com/smppw/modaq/application/components/report/writer/MonthlyReportWriter.java

@@ -1,25 +1,35 @@
-//package com.smppw.modaq.infrastructure.components.report.writer;
-//
-//import com.smppw.modaq.domain.mapper.ReportBaseInfoMapper;
-//import com.smppw.modaq.domain.mapper.ReportFundInfoMapper;
-//import com.smppw.modaq.infrastructure.dto.report.MonthlyReportData;
-//import org.springframework.stereotype.Component;
-//
-//@Component(ReportWriterConstant.WRITER_MONTHLY)
-//public class MonthlyReportWriter extends AbstractReportWriter<MonthlyReportData> {
-//
-//    public MonthlyReportWriter(ReportBaseInfoMapper baseInfoMapper,
-//                               ReportFundInfoMapper fundInfoMapper) {
-//        super(baseInfoMapper, fundInfoMapper);
-//    }
-//
-//    @Override
-//    protected void writeExtData(MonthlyReportData reportData) {
-////        List<ReportNetReportDTO> netReport = reportData.getNetReport();
-////        if (CollUtil.isNotEmpty(netReport)) {
-////            List<ReportNetReportDO> entityList = netReport.stream()
-////                    .map(ReportNetReportDTO::toEntity).collect(Collectors.toList());
-////            this.netReportMapper.insert(entityList);
-////        }
-//    }
-//}
+package com.smppw.modaq.application.components.report.writer;
+
+import cn.hutool.core.collection.CollUtil;
+import com.smppw.modaq.domain.dto.report.MonthlyReportData;
+import com.smppw.modaq.domain.dto.report.ReportNetReportDTO;
+import com.smppw.modaq.domain.entity.report.ReportNetReportDO;
+import com.smppw.modaq.domain.mapper.report.ReportBaseInfoMapper;
+import com.smppw.modaq.domain.mapper.report.ReportFundInfoMapper;
+import com.smppw.modaq.domain.mapper.report.ReportNetReportMapper;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Component(ReportWriterConstant.WRITER_MONTHLY)
+public class MonthlyReportWriter extends AbstractReportWriter<MonthlyReportData> {
+    private final ReportNetReportMapper netReportMapper;
+
+    public MonthlyReportWriter(ReportBaseInfoMapper baseInfoMapper,
+                               ReportFundInfoMapper fundInfoMapper,
+                               ReportNetReportMapper netReportMapper) {
+        super(baseInfoMapper, fundInfoMapper);
+        this.netReportMapper = netReportMapper;
+    }
+
+    @Override
+    protected void writeExtData(MonthlyReportData reportData) {
+        List<ReportNetReportDTO> netReport = reportData.getNetReport();
+        if (CollUtil.isNotEmpty(netReport)) {
+            List<ReportNetReportDO> entityList = netReport.stream()
+                    .map(ReportNetReportDTO::toEntity).collect(Collectors.toList());
+            this.netReportMapper.insert(entityList);
+        }
+    }
+}

+ 18 - 0
mo-daq/src/main/java/com/smppw/modaq/application/components/report/writer/OtherReportWriter.java

@@ -0,0 +1,18 @@
+package com.smppw.modaq.application.components.report.writer;
+
+import com.smppw.modaq.domain.dto.report.ReportData;
+import com.smppw.modaq.domain.mapper.report.ReportBaseInfoMapper;
+import com.smppw.modaq.domain.mapper.report.ReportFundInfoMapper;
+import org.springframework.stereotype.Component;
+
+@Component(ReportWriterConstant.WRITER_OTHER)
+public class OtherReportWriter extends AbstractReportWriter<ReportData> {
+    public OtherReportWriter(ReportBaseInfoMapper baseInfoMapper, ReportFundInfoMapper fundInfoMapper) {
+        super(baseInfoMapper, fundInfoMapper);
+    }
+
+    @Override
+    protected void writeExtData(ReportData reportData) {
+        // 没有数据要保存
+    }
+}

+ 62 - 62
mo-daq/src/main/java/com/smppw/modaq/application/components/report/writer/QuarterlyReportWriter.java

@@ -1,62 +1,62 @@
-//package com.smppw.modaq.infrastructure.components.report.writer;
-//
-//import cn.hutool.core.collection.CollUtil;
-//import com.simuwang.base.mapper.report.*;
-//import com.simuwang.base.pojo.dos.report.ReportAssetAllocationDO;
-//import com.simuwang.base.pojo.dos.report.ReportFinancialIndicatorsDO;
-//import com.simuwang.base.pojo.dos.report.ReportInvestmentIndustryDO;
-//import com.simuwang.base.pojo.dos.report.ReportShareChangeDO;
-//import com.simuwang.base.pojo.dto.report.*;
-//import org.springframework.stereotype.Component;
-//
-//import java.util.List;
-//import java.util.stream.Collectors;
-//
-//@Component(ReportWriterConstant.WRITER_QUARTERLY)
-//public class QuarterlyReportWriter<T extends QuarterlyReportData> extends AbstractReportWriter<T> {
-//    private final ReportShareChangeMapper shareChangeMapper;
-//    private final ReportAssetAllocationMapper assetAllocationMapper;
-//    private final ReportInvestmentIndustryMapper investmentIndustryMapper;
-//    private final ReportFinancialIndicatorMapper financialIndicatorMapper;
-//
-//    public QuarterlyReportWriter(ReportBaseInfoMapper baseInfoMapper, ReportFundInfoMapper fundInfoMapper,
-//                                 ReportShareChangeMapper shareChangeMapper, ReportAssetAllocationMapper assetAllocationMapper,
-//                                 ReportInvestmentIndustryMapper investmentIndustryMapper, ReportFinancialIndicatorMapper financialIndicatorMapper) {
-//        super(baseInfoMapper, fundInfoMapper);
-//        this.shareChangeMapper = shareChangeMapper;
-//        this.assetAllocationMapper = assetAllocationMapper;
-//        this.investmentIndustryMapper = investmentIndustryMapper;
-//        this.financialIndicatorMapper = financialIndicatorMapper;
-//    }
-//
-//    @Override
-//    protected void writeExtData(QuarterlyReportData reportData) {
-//        List<ReportShareChangeDTO> shareChange = reportData.getShareChange();
-//        if (CollUtil.isNotEmpty(shareChange)) {
-//            List<ReportShareChangeDO> entityList = shareChange.stream()
-//                    .map(ReportShareChangeDTO::toEntity).collect(Collectors.toList());
-//            this.shareChangeMapper.insert(entityList);
-//        }
-//
-//        List<ReportAssetAllocationDTO> assetAllocation = reportData.getAssetAllocation();
-//        if (CollUtil.isNotEmpty(assetAllocation)) {
-//            List<ReportAssetAllocationDO> entityList = assetAllocation.stream()
-//                    .map(ReportAssetAllocationDTO::toEntity).collect(Collectors.toList());
-//            this.assetAllocationMapper.insert(entityList);
-//        }
-//
-//        List<ReportFinancialIndicatorsDTO> financialIndicators = reportData.getFinancialIndicators();
-//        if (CollUtil.isNotEmpty(financialIndicators)) {
-//            List<ReportFinancialIndicatorsDO> entityList = financialIndicators.stream()
-//                    .map(ReportFinancialIndicatorsDTO::toEntity).collect(Collectors.toList());
-//            this.financialIndicatorMapper.insert(entityList);
-//        }
-//
-//        List<ReportInvestmentIndustryDTO> investmentIndustry = reportData.getInvestmentIndustry();
-//        if (CollUtil.isNotEmpty(investmentIndustry)) {
-//            List<ReportInvestmentIndustryDO> entityList = investmentIndustry.stream()
-//                    .map(ReportInvestmentIndustryDTO::toEntity).collect(Collectors.toList());
-//            this.investmentIndustryMapper.insert(entityList);
-//        }
-//    }
-//}
+package com.smppw.modaq.application.components.report.writer;
+
+import cn.hutool.core.collection.CollUtil;
+import com.smppw.modaq.domain.dto.report.*;
+import com.smppw.modaq.domain.entity.report.ReportAssetAllocationDO;
+import com.smppw.modaq.domain.entity.report.ReportFinancialIndicatorsDO;
+import com.smppw.modaq.domain.entity.report.ReportInvestmentIndustryDO;
+import com.smppw.modaq.domain.entity.report.ReportShareChangeDO;
+import com.smppw.modaq.domain.mapper.report.*;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Component(ReportWriterConstant.WRITER_QUARTERLY)
+public class QuarterlyReportWriter<T extends QuarterlyReportData> extends AbstractReportWriter<T> {
+    private final ReportShareChangeMapper shareChangeMapper;
+    private final ReportAssetAllocationMapper assetAllocationMapper;
+    private final ReportInvestmentIndustryMapper investmentIndustryMapper;
+    private final ReportFinancialIndicatorMapper financialIndicatorMapper;
+
+    public QuarterlyReportWriter(ReportBaseInfoMapper baseInfoMapper, ReportFundInfoMapper fundInfoMapper,
+                                 ReportShareChangeMapper shareChangeMapper, ReportAssetAllocationMapper assetAllocationMapper,
+                                 ReportInvestmentIndustryMapper investmentIndustryMapper, ReportFinancialIndicatorMapper financialIndicatorMapper) {
+        super(baseInfoMapper, fundInfoMapper);
+        this.shareChangeMapper = shareChangeMapper;
+        this.assetAllocationMapper = assetAllocationMapper;
+        this.investmentIndustryMapper = investmentIndustryMapper;
+        this.financialIndicatorMapper = financialIndicatorMapper;
+    }
+
+    @Override
+    protected void writeExtData(QuarterlyReportData reportData) {
+        List<ReportShareChangeDTO> shareChange = reportData.getShareChange();
+        if (CollUtil.isNotEmpty(shareChange)) {
+            List<ReportShareChangeDO> entityList = shareChange.stream()
+                    .map(ReportShareChangeDTO::toEntity).collect(Collectors.toList());
+            this.shareChangeMapper.insert(entityList);
+        }
+
+        List<ReportAssetAllocationDTO> assetAllocation = reportData.getAssetAllocation();
+        if (CollUtil.isNotEmpty(assetAllocation)) {
+            List<ReportAssetAllocationDO> entityList = assetAllocation.stream()
+                    .map(ReportAssetAllocationDTO::toEntity).collect(Collectors.toList());
+            this.assetAllocationMapper.insert(entityList);
+        }
+
+        List<ReportFinancialIndicatorsDTO> financialIndicators = reportData.getFinancialIndicators();
+        if (CollUtil.isNotEmpty(financialIndicators)) {
+            List<ReportFinancialIndicatorsDO> entityList = financialIndicators.stream()
+                    .map(ReportFinancialIndicatorsDTO::toEntity).collect(Collectors.toList());
+            this.financialIndicatorMapper.insert(entityList);
+        }
+
+        List<ReportInvestmentIndustryDTO> investmentIndustry = reportData.getInvestmentIndustry();
+        if (CollUtil.isNotEmpty(investmentIndustry)) {
+            List<ReportInvestmentIndustryDO> entityList = investmentIndustry.stream()
+                    .map(ReportInvestmentIndustryDTO::toEntity).collect(Collectors.toList());
+            this.investmentIndustryMapper.insert(entityList);
+        }
+    }
+}

+ 9 - 0
mo-daq/src/main/java/com/smppw/modaq/application/components/report/writer/ReportWriterConstant.java

@@ -8,12 +8,21 @@ import java.util.Map;
 public final class ReportWriterConstant {
     public static final Map<ReportType, String> REPORT_TYPE_BEAN_MAP = MapUtil.newHashMap(8);
 
+    static final String WRITER_OTHER = "report-writer:other";
+
+    static final String WRITER_WEEKLY = "report-writer:weekly";
+
     static final String WRITER_LETTER = "report-writer:letter";
+
     static final String WRITER_MONTHLY = "report-writer:monthly";
     static final String WRITER_QUARTERLY = "report-writer:quarterly";
     static final String WRITER_ANNUALLY = "report-writer:annually";
 
     static {
+        REPORT_TYPE_BEAN_MAP.put(ReportType.OTHER, WRITER_OTHER);
+
+        REPORT_TYPE_BEAN_MAP.put(ReportType.WEEKLY, WRITER_WEEKLY);
+
         REPORT_TYPE_BEAN_MAP.put(ReportType.LETTER, WRITER_LETTER);
 
         REPORT_TYPE_BEAN_MAP.put(ReportType.MONTHLY, WRITER_MONTHLY);

+ 18 - 0
mo-daq/src/main/java/com/smppw/modaq/application/components/report/writer/WeeklyReportWriter.java

@@ -0,0 +1,18 @@
+package com.smppw.modaq.application.components.report.writer;
+
+import com.smppw.modaq.domain.dto.report.WeeklyReportData;
+import com.smppw.modaq.domain.mapper.report.ReportBaseInfoMapper;
+import com.smppw.modaq.domain.mapper.report.ReportFundInfoMapper;
+import org.springframework.stereotype.Component;
+
+@Component(ReportWriterConstant.WRITER_WEEKLY)
+public class WeeklyReportWriter extends AbstractReportWriter<WeeklyReportData> {
+    public WeeklyReportWriter(ReportBaseInfoMapper baseInfoMapper, ReportFundInfoMapper fundInfoMapper) {
+        super(baseInfoMapper, fundInfoMapper);
+    }
+
+    @Override
+    protected void writeExtData(WeeklyReportData reportData) {
+        // 没有数据要保存
+    }
+}

+ 161 - 173
mo-daq/src/main/java/com/smppw/modaq/application/util/EmailUtil.java

@@ -1,29 +1,17 @@
 package com.smppw.modaq.application.util;
 
-import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.exceptions.ExceptionUtil;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.extra.mail.JakartaUserPassAuthenticator;
-import com.smppw.modaq.common.conts.DateConst;
 import com.smppw.modaq.common.conts.EmailTypeConst;
-import com.smppw.modaq.domain.dto.EmailContentInfoDTO;
 import com.smppw.modaq.domain.dto.MailboxInfoDTO;
-import com.smppw.modaq.infrastructure.util.ExcelUtil;
-import com.smppw.modaq.infrastructure.util.FileUtil;
 import com.sun.mail.imap.IMAPStore;
-import jakarta.mail.Message;
-import jakarta.mail.MessagingException;
 import jakarta.mail.Session;
 import jakarta.mail.Store;
-import jakarta.mail.internet.MimeBodyPart;
-import jakarta.mail.internet.MimeMultipart;
-import jakarta.mail.internet.MimeUtility;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -41,100 +29,100 @@ public class EmailUtil {
     private static final String POP3 = "pop3";
     private static final String IMAP = "imap";
 
-    /**
-     * 采集邮件(多消息体)信息
-     *
-     * @param message      邮件
-     * @param emailAddress 邮箱地址
-     * @param path         存储路径
-     * @return 从邮箱采集到的信息
-     * @throws Exception 异常信息
-     */
-    public static List<EmailContentInfoDTO> collectMimeMultipart(Message message, String emailAddress, String path) throws Exception {
-        List<EmailContentInfoDTO> emailContentInfoDTOList = CollUtil.newArrayList();
-        String emailTitle = message.getSubject();
-        String emailDate = DateUtil.format(message.getSentDate(), DateConst.YYYYMMDDHHMMSS24);
-        String emailDateStr = DateUtil.format(message.getSentDate(), DateConst.YYYYMMDD);
-        String filePath = path + File.separator + emailAddress + File.separator + emailDateStr + File.separator;
-
-        MimeMultipart mimeMultipart = (MimeMultipart) message.getContent();
-        int length = mimeMultipart.getCount();
-        // 遍历邮件消息体 (我这里不要html正文)
-        for (int i = 0; i < length; i++) {
-            EmailContentInfoDTO emailContentInfoDTO = new EmailContentInfoDTO();
-            MimeBodyPart part = (MimeBodyPart) mimeMultipart.getBodyPart(i);
-            Object partContent = part.getContent();
-            String contentClass = part.getContent().getClass().getSimpleName();
-            // 1.邮件正文
-            switch (contentClass) {
-                case "String" -> {
-                    // 文件名 = 邮件主题 + 邮件日期
-                    String fileName = emailTitle + "_" + emailDate + ".html";
-                    String content = partContent.toString();
-                    emailContentInfoDTO = collectTextPart(part, content, filePath, fileName);
-                }
-                case "BASE64DecoderStream" -> {
-                    if (StrUtil.isNotBlank(part.getFileName())) {
-                        String fileName = MimeUtility.decodeText(part.getFileName());
-                        if (isSupportedFileType(fileName)) {
-                            continue;
-                        }
-                        emailContentInfoDTO.setFileName(fileName);
-
-                        String realPath = filePath + emailDate + fileName;
-
-                        File saveFile = cn.hutool.core.io.FileUtil.file(realPath);
-                        if (!saveFile.exists()) {
-                            if (!saveFile.getParentFile().exists()) {
-                                saveFile.getParentFile().mkdirs();
-                            }
-                            FileUtil.saveFile(saveFile, part);
-                        } else {
-                            cn.hutool.core.io.FileUtil.del(saveFile);
-                            FileUtil.saveFile(saveFile, part);
-                        }
-                        emailContentInfoDTO.setFilePath(realPath);
-                    }
-                }
-                case "MimeMultipart" -> {
-                    MimeMultipart contentPart = (MimeMultipart) partContent;
-                    int length2 = contentPart.getCount();
-                    for (int i2 = 0; i2 < length2; i2++) {
-                        part = (MimeBodyPart) contentPart.getBodyPart(i2);
-                        partContent = part.getContent();
-                        contentClass = partContent.getClass().getSimpleName();
-                        if ("String".equals(contentClass)) {
-                            // 文件名 = 邮件主题 + 邮件日期
-                            String fileName = emailTitle + "_" + emailDate + ".html";
-                            String content = partContent.toString();
-                            emailContentInfoDTO = collectTextPart(part, content, filePath, fileName);
-                        }
-                    }
-                }
-            }
-            String filepath = emailContentInfoDTO.getFilePath();
-            if (emailContentInfoDTO.getEmailContent() == null && filepath == null) {
-                continue;
-            }
-            emailContentInfoDTO.setEmailAddress(emailAddress);
-            emailContentInfoDTO.setEmailTitle(emailTitle);
-            emailContentInfoDTO.setEmailDate(DateUtil.format(message.getSentDate(), DateConst.YYYY_MM_DD_HH_MM_SS));
-            emailContentInfoDTOList.add(emailContentInfoDTO);
-        }
-
-        return emailContentInfoDTOList;
-    }
+//    /**
+//     * 采集邮件(多消息体)信息
+//     *
+//     * @param message      邮件
+//     * @param emailAddress 邮箱地址
+//     * @param path         存储路径
+//     * @return 从邮箱采集到的信息
+//     * @throws Exception 异常信息
+//     */
+//    public static List<EmailContentInfoDTO> collectMimeMultipart(Message message, String emailAddress, String path) throws Exception {
+//        List<EmailContentInfoDTO> emailContentInfoDTOList = CollUtil.newArrayList();
+//        String emailTitle = message.getSubject();
+//        String emailDate = DateUtil.format(message.getSentDate(), DateConst.YYYYMMDDHHMMSS24);
+//        String emailDateStr = DateUtil.format(message.getSentDate(), DateConst.YYYYMMDD);
+//        String filePath = path + File.separator + emailAddress + File.separator + emailDateStr + File.separator;
+//
+//        MimeMultipart mimeMultipart = (MimeMultipart) message.getContent();
+//        int length = mimeMultipart.getCount();
+//        // 遍历邮件消息体 (我这里不要html正文)
+//        for (int i = 0; i < length; i++) {
+//            EmailContentInfoDTO emailContentInfoDTO = new EmailContentInfoDTO();
+//            MimeBodyPart part = (MimeBodyPart) mimeMultipart.getBodyPart(i);
+//            Object partContent = part.getContent();
+//            String contentClass = part.getContent().getClass().getSimpleName();
+//            // 1.邮件正文
+//            switch (contentClass) {
+//                case "String" -> {
+//                    // 文件名 = 邮件主题 + 邮件日期
+//                    String fileName = emailTitle + "_" + emailDate + ".html";
+//                    String content = partContent.toString();
+//                    emailContentInfoDTO = collectTextPart(part, content, filePath, fileName);
+//                }
+//                case "BASE64DecoderStream" -> {
+//                    if (StrUtil.isNotBlank(part.getFileName())) {
+//                        String fileName = MimeUtility.decodeText(part.getFileName());
+//                        if (isSupportedFileType(fileName)) {
+//                            continue;
+//                        }
+//                        emailContentInfoDTO.setFileName(fileName);
+//
+//                        String realPath = filePath + emailDate + fileName;
+//
+//                        File saveFile = cn.hutool.core.io.FileUtil.file(realPath);
+//                        if (!saveFile.exists()) {
+//                            if (!saveFile.getParentFile().exists()) {
+//                                saveFile.getParentFile().mkdirs();
+//                            }
+//                            FileUtil.saveFile(saveFile, part);
+//                        } else {
+//                            cn.hutool.core.io.FileUtil.del(saveFile);
+//                            FileUtil.saveFile(saveFile, part);
+//                        }
+//                        emailContentInfoDTO.setFilePath(realPath);
+//                    }
+//                }
+//                case "MimeMultipart" -> {
+//                    MimeMultipart contentPart = (MimeMultipart) partContent;
+//                    int length2 = contentPart.getCount();
+//                    for (int i2 = 0; i2 < length2; i2++) {
+//                        part = (MimeBodyPart) contentPart.getBodyPart(i2);
+//                        partContent = part.getContent();
+//                        contentClass = partContent.getClass().getSimpleName();
+//                        if ("String".equals(contentClass)) {
+//                            // 文件名 = 邮件主题 + 邮件日期
+//                            String fileName = emailTitle + "_" + emailDate + ".html";
+//                            String content = partContent.toString();
+//                            emailContentInfoDTO = collectTextPart(part, content, filePath, fileName);
+//                        }
+//                    }
+//                }
+//            }
+//            String filepath = emailContentInfoDTO.getFilePath();
+//            if (emailContentInfoDTO.getEmailContent() == null && filepath == null) {
+//                continue;
+//            }
+//            emailContentInfoDTO.setEmailAddress(emailAddress);
+//            emailContentInfoDTO.setEmailTitle(emailTitle);
+//            emailContentInfoDTO.setEmailDate(DateUtil.format(message.getSentDate(), DateConst.YYYY_MM_DD_HH_MM_SS));
+//            emailContentInfoDTOList.add(emailContentInfoDTO);
+//        }
+//
+//        return emailContentInfoDTOList;
+//    }
 
 //    private static List<EmailContentInfoDTO> zipFile(String filepath) {
 //        return null;
 //    }
 
-    private static boolean isSupportedFileType(String fileName) {
-        if (StrUtil.isBlank(fileName)) {
-            return true;
-        }
-        return !ExcelUtil.isZip(fileName) && !ExcelUtil.isExcel(fileName) && !ExcelUtil.isPdf(fileName) && !ExcelUtil.isHTML(fileName) && !ExcelUtil.isRAR(fileName);
-    }
+//    private static boolean isSupportedFileType(String fileName) {
+//        if (StrUtil.isBlank(fileName)) {
+//            return true;
+//        }
+//        return !ExcelUtil.isZip(fileName) && !ExcelUtil.isExcel(fileName) && !ExcelUtil.isPdf(fileName) && !ExcelUtil.isHTML(fileName) && !ExcelUtil.isRAR(fileName);
+//    }
 
 //    /**
 //     * 根据日期过滤邮件
@@ -163,78 +151,78 @@ public class EmailUtil {
 //        return messageList;
 //    }
 
-    /**
-     * 采集邮件正文
-     *
-     * @param part        邮件消息体
-     * @param partContent 邮件消息内筒
-     * @param filePath    文件路径
-     * @param fileName    文件名
-     * @return 采集到邮件正文(html格式包含table标签)
-     */
-    public static EmailContentInfoDTO collectTextPart(MimeBodyPart part, String partContent, String filePath, String fileName) {
-        EmailContentInfoDTO emailContentInfoDTO = new EmailContentInfoDTO();
-        try {
-            if ((part.getContentType().contains("text/html") || part.getContentType().contains("TEXT/HTML"))) {
-                emailContentInfoDTO.setEmailContent(partContent);
-                String savePath = filePath + fileName;
-                File saveFile = new File(savePath);
-                if (!saveFile.exists()) {
-                    if (!saveFile.getParentFile().exists()) {
-                        saveFile.getParentFile().mkdirs();
-                        saveFile.getParentFile().setExecutable(true);
-                    }
-                }
-                //获取邮件编码
-                String contentType = part.getContentType();
-                String html = partContent.toString();
-                try {
-                    if (contentType.contains("charset=")) {
-                        contentType = contentType.substring(contentType.indexOf("charset=") + 8).replaceAll("\"", "");
-                        html = html.replace("charset=" + contentType.toLowerCase(), "charset=UTF-8");
-                        html = html.replace("charset=" + contentType.toUpperCase(), "charset=UTF-8");
-                    }
-                    if (savePath.contains(":")) {
-                        savePath = savePath.replaceAll(":", "");
-                    }
-                    cn.hutool.core.io.FileUtil.writeUtf8String(html, new File(savePath));
-                } catch (Exception e) {
-                    logger.error(e.getMessage(), e);
-                }
-                emailContentInfoDTO.setFileName(fileName);
-                emailContentInfoDTO.setFilePath(savePath);
-            } else {
-                try {
-                    if (part.getFileName() == null) {
-                        return emailContentInfoDTO;
-                    }
-                    String fileName1 = MimeUtility.decodeText(part.getFileName());
-                    if (isSupportedFileType(fileName1)) {
-                        return emailContentInfoDTO;
-                    }
-                    emailContentInfoDTO.setFileName(fileName1);
-                    String realPath = filePath + fileName1;
-                    File saveFile = new File(realPath);
-                    if (!saveFile.exists()) {
-                        if (!saveFile.getParentFile().exists()) {
-                            saveFile.getParentFile().mkdirs();
-                        }
-                        FileUtil.saveFile(saveFile, part);
-                    } else {
-                        cn.hutool.core.io.FileUtil.del(saveFile);
-                        FileUtil.saveFile(saveFile, part);
-                    }
-                    emailContentInfoDTO.setFilePath(realPath);
-                } catch (Exception e) {
-                    return emailContentInfoDTO;
-                }
-            }
-        } catch (MessagingException e) {
-            logger.info("邮件正文采集失败 -> 文件名:{}, 报错堆栈:{}", fileName, ExceptionUtil.stacktraceToString(e));
-            return emailContentInfoDTO;
-        }
-        return emailContentInfoDTO;
-    }
+//    /**
+//     * 采集邮件正文
+//     *
+//     * @param part        邮件消息体
+//     * @param partContent 邮件消息内筒
+//     * @param filePath    文件路径
+//     * @param fileName    文件名
+//     * @return 采集到邮件正文(html格式包含table标签)
+//     */
+//    public static EmailContentInfoDTO collectTextPart(MimeBodyPart part, String partContent, String filePath, String fileName) {
+//        EmailContentInfoDTO emailContentInfoDTO = new EmailContentInfoDTO();
+//        try {
+//            if ((part.getContentType().contains("text/html") || part.getContentType().contains("TEXT/HTML"))) {
+//                emailContentInfoDTO.setEmailContent(partContent);
+//                String savePath = filePath + fileName;
+//                File saveFile = new File(savePath);
+//                if (!saveFile.exists()) {
+//                    if (!saveFile.getParentFile().exists()) {
+//                        saveFile.getParentFile().mkdirs();
+//                        saveFile.getParentFile().setExecutable(true);
+//                    }
+//                }
+//                //获取邮件编码
+//                String contentType = part.getContentType();
+//                String html = partContent.toString();
+//                try {
+//                    if (contentType.contains("charset=")) {
+//                        contentType = contentType.substring(contentType.indexOf("charset=") + 8).replaceAll("\"", "");
+//                        html = html.replace("charset=" + contentType.toLowerCase(), "charset=UTF-8");
+//                        html = html.replace("charset=" + contentType.toUpperCase(), "charset=UTF-8");
+//                    }
+//                    if (savePath.contains(":")) {
+//                        savePath = savePath.replaceAll(":", "");
+//                    }
+//                    cn.hutool.core.io.FileUtil.writeUtf8String(html, new File(savePath));
+//                } catch (Exception e) {
+//                    logger.error(e.getMessage(), e);
+//                }
+//                emailContentInfoDTO.setFileName(fileName);
+//                emailContentInfoDTO.setFilePath(savePath);
+//            } else {
+//                try {
+//                    if (part.getFileName() == null) {
+//                        return emailContentInfoDTO;
+//                    }
+//                    String fileName1 = MimeUtility.decodeText(part.getFileName());
+//                    if (isSupportedFileType(fileName1)) {
+//                        return emailContentInfoDTO;
+//                    }
+//                    emailContentInfoDTO.setFileName(fileName1);
+//                    String realPath = filePath + fileName1;
+//                    File saveFile = new File(realPath);
+//                    if (!saveFile.exists()) {
+//                        if (!saveFile.getParentFile().exists()) {
+//                            saveFile.getParentFile().mkdirs();
+//                        }
+//                        FileUtil.saveFile(saveFile, part);
+//                    } else {
+//                        cn.hutool.core.io.FileUtil.del(saveFile);
+//                        FileUtil.saveFile(saveFile, part);
+//                    }
+//                    emailContentInfoDTO.setFilePath(realPath);
+//                } catch (Exception e) {
+//                    return emailContentInfoDTO;
+//                }
+//            }
+//        } catch (MessagingException e) {
+//            logger.info("邮件正文采集失败 -> 文件名:{}, 报错堆栈:{}", fileName, ExceptionUtil.stacktraceToString(e));
+//            return emailContentInfoDTO;
+//        }
+//        return emailContentInfoDTO;
+//    }
 
     /**
      * 判断邮件是否符合解析条件

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

@@ -22,4 +22,13 @@ public class EmailTypeConst {
      */
     public final static Integer REPORT_LETTER_EMAIL_TYPE = 4;
 
+    /**
+     * 除定期报告和确认函的其他类型的报告
+     */
+    public final static Integer REPORT_OTHER_TYPE = 5;
+
+    /**
+     * 管理人周报(区别于定期报告)
+     */
+    public final static Integer REPORT_WEEKLy_TYPE = 6;
 }

+ 9 - 7
mo-daq/src/main/java/com/smppw/modaq/common/enums/ReportParseStatus.java

@@ -2,18 +2,20 @@ package com.smppw.modaq.common.enums;
 
 public enum ReportParseStatus implements StatusCode {
     AI_NOT_FOUND(20009, "AI资源找不到"),
+    NO_SUPPORT_AI(20010, "报告[{}]不支持AI解析"),
     PARSE_FAIL(21000, "定期报告或交易确认单解析错误:{}"),
-    NOT_A_REPORT(21001, "[{}]不是定期报告或交易确认单"),
+    NOT_A_REPORT(21001, "[{}]不是支持的报告格式"),
     REPORT_IS_SCAN(21002, "报告[{}]为扫描件"),
     NO_SUPPORT_TEMPLATE(21003, "报告[{}]是不支持的文件格式"),
     NOT_A_FIXED_FORMAT(21004, "报告[{}]不是基协统一格式"),
 
-//    PARSE_FUND_INFO_FAIL(21010, "报告[{}]没有解析到基金基本信息"),
-//    PARSE_NAV_INFO_FAIL(21011, "报告[{}]没有解析到基金净值信息"),
-//    PARSE_FINANCIAL_INFO_FAIL(21012, "报告[{}]没有解析到基金财务指标信息"),
-//    PARSE_INDUSTRY_INFO_FAIL(21013, "报告[{}]没有解析到基金行业配置信息"),
-//    PARSE_ASSET_INFO_FAIL(21014, "报告[{}]没有解析到基金资产配置信息"),
-//    PARSE_SHARE_INFO_FAIL(21015, "报告[{}]没有解析到基金份额变动信息"),
+    PARSE_FUND_INFO_FAIL(21010, "报告[{}]没有解析到基金基本信息"),
+    PARSE_NAV_INFO_FAIL(21011, "报告[{}]没有解析到基金净值信息"),
+    PARSE_FINANCIAL_INFO_FAIL(21012, "报告[{}]没有解析到基金财务指标信息"),
+    PARSE_INDUSTRY_INFO_FAIL(21013, "报告[{}]没有解析到基金行业配置信息"),
+    PARSE_ASSET_INFO_FAIL(21014, "报告[{}]没有解析到基金资产配置信息"),
+    PARSE_SHARE_INFO_FAIL(21015, "报告[{}]没有解析到基金份额变动信息"),
+
     PARSE_CORE_INFO_FAIL(21019, "报告[{}]没有解析到关键信息"),
     PARSE_HANDLE_FAIL(21018, "报告结果在结构化时转换错误"),
 

+ 2 - 1
mo-daq/src/main/java/com/smppw/modaq/common/enums/ReportParserFileType.java

@@ -9,8 +9,9 @@ import java.util.Arrays;
  */
 public enum ReportParserFileType {
     PDF("pdf"),
-    WORD("docx,doc"),
+//    WORD("docx,doc"),
     EXCEL("xlsx,xls"),
+//    PYTHON("python");
     AI("ai");
 
     private final String suffix;

+ 16 - 13
mo-daq/src/main/java/com/smppw/modaq/common/enums/ReportType.java

@@ -4,26 +4,29 @@ import lombok.Getter;
 
 @Getter
 public enum ReportType {
-    LETTER(-1, "交易流水确认函", new String[]{"确认单", "确认函", "确认"}, null),
-    MONTHLY(0, "月", new String[]{"月", "月度", "月报"}, null),
-    QUARTERLY(1, "季", new String[]{"季", "季度", "季报"}, null),
-    ANNUALLY(2, "年", new String[]{"年度", "年报"}, null);
+    // 最后识别的类型
+    OTHER(-2, "其他报告", new String[]{"公告", "通知", "告知函", "意见征询函", "说明函",
+            "清算报告", "邀请函", "观点", "预警", "复核函", "提醒", "投研报告", "公示", "回顾"}),
+
+    LETTER(-1, "交易流水确认函", new String[]{"确认单", "确认函", "交易确认数据",
+            "赎回确认", "申购确认", "分红确认", "确认表", "交易确认", "确认"}),
+
+    MONTHLY(0, "月", new String[]{"月", "月度报告", "月报"}),
+
+    QUARTERLY(1, "季", new String[]{"季", "季度报告", "季报", "季度"}),
+
+    ANNUALLY(2, "年", new String[]{"年度", "年报"}),
+
+    WEEKLY(3, "周", new String[]{"周报", "周度报告", "周度", "周"}),
+    ;
 
     private final int type;
     private final String label;
-    /**
-     * 报告类型识别关键字
-     */
     private final String[] patterns;
-    /**
-     * ai解析提示词
-     */
-    private final String prompt;
 
-    ReportType(int type, String label, String[] patterns, String prompt) {
+    ReportType(int type, String label, String[] patterns) {
         this.type = type;
         this.label = label;
         this.patterns = patterns;
-        this.prompt = prompt;
     }
 }

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

@@ -69,6 +69,11 @@ public class EmailContentInfoDTO implements Serializable {
      */
     private String aiFileId;
 
+    /**
+     * 附件大小byte
+     */
+    private int fileSize;
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
@@ -78,6 +83,7 @@ public class EmailContentInfoDTO implements Serializable {
                 && Objects.equals(emailDate, that.emailDate)
                 && Objects.equals(fileName, that.fileName)
                 && Objects.equals(filePath, that.filePath)
+                && Objects.equals(fileSize, that.fileSize)
                 && Objects.equals(emailType, that.emailType)
                 && Objects.equals(senderEmail, that.senderEmail);
     }
@@ -85,7 +91,7 @@ public class EmailContentInfoDTO implements Serializable {
     @Override
     public int hashCode() {
         return Objects.hash(emailAddress, emailTitle, emailDate,
-                fileName, filePath, emailType, senderEmail);
+                fileName, filePath, emailType, senderEmail, fileSize);
     }
 
     @Override
@@ -97,6 +103,7 @@ public class EmailContentInfoDTO implements Serializable {
                 ", fileName='" + fileName + '\'' +
                 ", filePath='" + filePath + '\'' +
                 ", emailType=" + emailType +
+                ", fileSize=" + fileSize +
                 '}';
     }
 }

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

@@ -6,6 +6,7 @@ import lombok.Getter;
 @Getter
 public class EmailZipFileDTO {
     private final String filename;
+//    private final String originalName;
     private final String filepath;
     private final Integer emailType;
 

+ 0 - 50
mo-daq/src/main/java/com/smppw/modaq/domain/dto/QuartzBean.java

@@ -1,50 +0,0 @@
-package com.smppw.modaq.domain.dto;
-
-/**
- * FileName: QuartzBean
- * Author:   chenjianhua
- * Date:     2024/9/17 10:26
- * Description: ${DESCRIPTION}
- */
-
-import lombok.Data;
-
-import java.io.Serializable;
-
-@Data
-public class QuartzBean implements Serializable {
-    /**
-     * 任务id
-     */
-    private String id;
-
-    /**
-     * 任务名称
-     */
-    private String jobName;
-
-    /**
-     * 任务执行类
-     */
-    private String jobClass;
-
-    /**
-     * 组名
-     */
-    private String groupName;
-
-    /**
-     * 任务 参数信息
-     */
-    private String jobParam;
-
-    /**
-     * 任务状态 启动还是暂停
-     */
-    private Integer status;
-
-    /**
-     * 任务运行时间表达式
-     */
-    private String cronExpression;
-}

+ 18 - 18
mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/AnnuallyReportData.java

@@ -1,18 +1,18 @@
-//package com.smppw.modaq.infrastructure.dto.report;
-//
-//import com.simuwang.base.common.enums.ReportType;
-//import lombok.Getter;
-//import lombok.Setter;
-//
-//@Setter
-//@Getter
-//public class AnnuallyReportData extends QuarterlyReportData {
-//    public AnnuallyReportData(ReportBaseInfoDTO baseInfo, ReportFundInfoDTO fundInfo) {
-//        super(baseInfo, fundInfo);
-//    }
-//
-//    @Override
-//    public ReportType getReportType() {
-//        return ReportType.ANNUALLY;
-//    }
-//}
+package com.smppw.modaq.domain.dto.report;
+
+import com.smppw.modaq.common.enums.ReportType;
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
+public class AnnuallyReportData extends QuarterlyReportData {
+    public AnnuallyReportData(ReportBaseInfoDTO baseInfo, ReportFundInfoDTO fundInfo) {
+        super(baseInfo, fundInfo);
+    }
+
+    @Override
+    public ReportType getReportType() {
+        return ReportType.ANNUALLY;
+    }
+}

+ 32 - 11
mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/BaseReportDTO.java

@@ -4,6 +4,7 @@ import cn.hutool.core.date.DatePattern;
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.util.StrUtil;
 import com.smppw.modaq.common.conts.Constants;
+import com.smppw.modaq.common.conts.DateConst;
 import com.smppw.modaq.domain.entity.report.BaseReportDO;
 import lombok.Getter;
 import lombok.Setter;
@@ -59,31 +60,51 @@ public abstract class BaseReportDTO<T extends BaseReportDO> implements Serializa
             return null;
         }
         try {
-            // 日期格式化,支持三种格式:yyyy年MM月dd日、yyyy-MM-dd和yyyy/MM/dd
+            // 日期格式化,支持格式如下:yyyy年MM月dd日、yyyy-MM-dd、yyyy/MM/dd、yyyy_MM_dd和yyyyMMdd
             return DateUtil.parse(input.trim(),
-                    DatePattern.CHINESE_DATE_PATTERN, DatePattern.NORM_DATE_PATTERN, "yyyy/MM/dd", "yyyyMMdd");
+                    DatePattern.CHINESE_DATE_PATTERN,
+                    DatePattern.NORM_DATE_PATTERN,
+                    "yyyy/MM/dd",
+                    "yyyy_MM_dd",
+                    DateConst.YYYYMMDD);
         } catch (Exception ignored) {
         }
         return null;
     }
 
+    protected BigDecimal toBigDecimal(String input) {
+        return this.toBigDecimal(input, null);
+    }
+
     /**
-     * 字符串转数字,如果数据没有或者转换失败则用0填充
+     * 字符串转数字,如果数据没有或者转换失败则用defaultValue字段填充
      *
-     * @param input 待转换的字符串
+     * @param input        待转换的字符串
+     * @param defaultValue 为null或者数字转换失败的默认值
      * @return /
      */
-    protected BigDecimal toBigDecimal(String input) {
+    protected BigDecimal toBigDecimal(String input, BigDecimal defaultValue) {
         if (StrUtil.isBlank(input)) {
-            return BigDecimal.ZERO;
+            return defaultValue;
         }
         try {
-            // 替换掉非正常的正负小数字符
-            String cleanedInput = input.trim().replaceAll("[^\\s" + "[-+]?\\d*.+" + "]", "");
-            // 创建BigDecimal对象
+            // 1. 清理输入,保留有效字符(数字、正负号、小数点、科学计数法符号、千分位逗号)
+            String cleanedInput = input.trim().replaceAll("[^\\d.,\\-+Ee]", "");
+            // 2. 移除所有空格(包括中间的空格)
+            cleanedInput = cleanedInput.replaceAll("\\s+", "");
+
+            // 3. 处理千分位逗号(替换为"",因为千分位逗号在最终数字中无效)
+            cleanedInput = cleanedInput.replaceAll(",", "");
+
+            // 4. 处理科学计数法中的符号(如 E 或 e 后的符号)
+            // 例如:"1.23E+4" → "1.23E4"(可选,但 Java 的 BigDecimal 可直接处理)
+
+            // 5. 验证最终格式是否合法(可选,但可能影响性能)
+            // 这里直接交给 BigDecimal 处理,捕获异常即可
+
             return new BigDecimal(cleanedInput);
-        } catch (NumberFormatException ignored) {
+        } catch (NumberFormatException e) {
+            return defaultValue;
         }
-        return BigDecimal.ZERO;
     }
 }

+ 32 - 32
mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/BaseReportLevelDTO.java

@@ -1,32 +1,32 @@
-//package com.smppw.modaq.domain.dto.report;
-//
-//import com.smppw.modaq.domain.entity.report.BaseReportDO;
-//import lombok.Getter;
-//import lombok.Setter;
-//
-//@Setter
-//@Getter
-//public abstract class BaseReportLevelDTO<T extends BaseReportDO> extends BaseReportDTO<T> {
-//    /**
-//     * 基金分级
-//     */
-//    private String level;
-//
-//    public BaseReportLevelDTO() {
-//        super();
-//    }
-//
-//    public BaseReportLevelDTO(Integer fileId) {
-//        super(fileId);
-//    }
-//
-//    public BaseReportLevelDTO(Integer fileId, String level) {
-//        super(fileId);
-//        this.level = level;
-//    }
-//
-//    @Override
-//    public String toString() {
-//        return super.toString() + ", level='" + this.level + "'";
-//    }
-//}
+package com.smppw.modaq.domain.dto.report;
+
+import com.smppw.modaq.domain.entity.report.BaseReportDO;
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
+public abstract class BaseReportLevelDTO<T extends BaseReportDO> extends BaseReportDTO<T> {
+    /**
+     * 基金分级
+     */
+    private String level;
+
+    public BaseReportLevelDTO() {
+        super();
+    }
+
+    public BaseReportLevelDTO(Integer fileId) {
+        super(fileId);
+    }
+
+    public BaseReportLevelDTO(Integer fileId, String level) {
+        super(fileId);
+        this.level = level;
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + ", level='" + this.level + "'";
+    }
+}

+ 2 - 1
mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/LetterReportData.java

@@ -29,7 +29,8 @@ public class LetterReportData extends ReportData {
         if (this.investorInfo == null || fundTransaction == null) {
             return false;
         }
-        return !StrUtil.isBlank(this.investorInfo.getInvestorName()) && !StrUtil.isBlank(this.fundTransaction.getFundName());
+        return !StrUtil.isBlank(this.investorInfo.getInvestorName())
+                && !StrUtil.isBlank(this.fundTransaction.getFundName());
     }
 
     @Override

+ 30 - 25
mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/MonthlyReportData.java

@@ -1,25 +1,30 @@
-//package com.smppw.modaq.infrastructure.dto.report;
-//
-//import com.smppw.modaq.common.enums.ReportType;
-//import lombok.Getter;
-//import lombok.Setter;
-//
-//@Setter
-//@Getter
-//public class MonthlyReportData extends ReportData {
-////    private List<ReportNetReportDTO> netReport;
-//
-//    public MonthlyReportData(ReportBaseInfoDTO baseInfo, ReportFundInfoDTO fundInfo) {
-//        super(baseInfo, fundInfo);
-//    }
-//
-//    @Override
-//    public ReportType getReportType() {
-//        return ReportType.MONTHLY;
-//    }
-//
-//    @Override
-//    public String toString() {
-//        return super.toString();
-//    }
-//}
+package com.smppw.modaq.domain.dto.report;
+
+import com.smppw.modaq.common.enums.ReportType;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+@Setter
+@Getter
+public class MonthlyReportData extends ReportData {
+    private List<ReportNetReportDTO> netReport;
+
+    public MonthlyReportData(ReportBaseInfoDTO baseInfo, ReportFundInfoDTO fundInfo) {
+        super(baseInfo, fundInfo);
+    }
+
+    @Override
+    public ReportType getReportType() {
+        return ReportType.MONTHLY;
+    }
+
+    @Override
+    public String toString() {
+        return "{" +
+                super.toString() +
+                ", netReport=" + netReport +
+                '}';
+    }
+}

+ 41 - 41
mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/QuarterlyReportData.java

@@ -1,41 +1,41 @@
-//package com.smppw.modaq.infrastructure.dto.report;
-//
-//import com.simuwang.base.common.enums.ReportType;
-//import lombok.Getter;
-//import lombok.Setter;
-//
-//import java.util.List;
-//
-///**
-// * @author wangzaijun
-// * @date 2024/9/26 17:24
-// * @description 季报
-// */
-//@Setter
-//@Getter
-//public class QuarterlyReportData extends ReportData {
-//    private List<ReportAssetAllocationDTO> assetAllocation;
-//    private List<ReportFinancialIndicatorsDTO> financialIndicators;
-//    private List<ReportInvestmentIndustryDTO> investmentIndustry;
-//    private List<ReportShareChangeDTO> shareChange;
-//
-//    public QuarterlyReportData(ReportBaseInfoDTO baseInfo, ReportFundInfoDTO fundInfo) {
-//        super(baseInfo, fundInfo);
-//    }
-//
-//    @Override
-//    public ReportType getReportType() {
-//        return ReportType.QUARTERLY;
-//    }
-//
-//    @Override
-//    public String toString() {
-//        return "{" +
-//                super.toString() +
-//                ", assetAllocation=" + assetAllocation +
-//                ", financialIndicators=" + financialIndicators +
-//                ", investmentIndustry=" + investmentIndustry +
-//                ", shareChange=" + shareChange +
-//                '}';
-//    }
-//}
+package com.smppw.modaq.domain.dto.report;
+
+import com.smppw.modaq.common.enums.ReportType;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+/**
+ * @author wangzaijun
+ * @date 2024/9/26 17:24
+ * @description 季报
+ */
+@Setter
+@Getter
+public class QuarterlyReportData extends ReportData {
+    private List<ReportAssetAllocationDTO> assetAllocation;
+    private List<ReportFinancialIndicatorsDTO> financialIndicators;
+    private List<ReportInvestmentIndustryDTO> investmentIndustry;
+    private List<ReportShareChangeDTO> shareChange;
+
+    public QuarterlyReportData(ReportBaseInfoDTO baseInfo, ReportFundInfoDTO fundInfo) {
+        super(baseInfo, fundInfo);
+    }
+
+    @Override
+    public ReportType getReportType() {
+        return ReportType.QUARTERLY;
+    }
+
+    @Override
+    public String toString() {
+        return "{" +
+                super.toString() +
+                ", assetAllocation=" + assetAllocation +
+                ", financialIndicators=" + financialIndicators +
+                ", investmentIndustry=" + investmentIndustry +
+                ", shareChange=" + shareChange +
+                '}';
+    }
+}

+ 61 - 61
mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/ReportAssetAllocationDTO.java

@@ -1,61 +1,61 @@
-//package com.smppw.modaq.infrastructure.dto.report;
-//
-//import com.simuwang.base.pojo.dos.report.ReportAssetAllocationDO;
-//import lombok.Getter;
-//import lombok.Setter;
-//
-///**
-// * @author wangzaijun
-// * @date 2024/9/26 16:43
-// * @description 基金资产组合情况
-// */
-//@Setter
-//@Getter
-//public class ReportAssetAllocationDTO extends BaseReportDTO<ReportAssetAllocationDO> {
-//    /**
-//     * 资产大类
-//     */
-//    private String assetType;
-//    /**
-//     * 资产明细
-//     */
-//    private String assetDetails;
-//    /**
-//     * 市值
-//     */
-//    private String marketValue;
-//    /**
-//     * 备注
-//     */
-//    private String remark;
-//
-//    public ReportAssetAllocationDTO() {
-//        super();
-//    }
-//
-//    public ReportAssetAllocationDTO(Integer fileId) {
-//        super(fileId);
-//    }
-//
-//    @Override
-//    public ReportAssetAllocationDO toEntity() {
-//        ReportAssetAllocationDO entity = new ReportAssetAllocationDO();
-//        entity.setFileId(this.getFileId());
-//        entity.setAssetType(this.assetType);
-//        entity.setColumnName(this.assetDetails);
-//        entity.setMarketValue(this.toBigDecimal(this.marketValue));
-//        entity.setRemark(this.remark);
-//        return entity;
-//    }
-//
-//    @Override
-//    public String toString() {
-//        return "{" +
-//                super.toString() +
-//                ", assetType='" + assetType + '\'' +
-//                ", assetDetails='" + assetDetails + '\'' +
-//                ", marketValue=" + marketValue +
-//                ", remark='" + remark + '\'' +
-//                '}';
-//    }
-//}
+package com.smppw.modaq.domain.dto.report;
+
+import com.smppw.modaq.domain.entity.report.ReportAssetAllocationDO;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author wangzaijun
+ * @date 2024/9/26 16:43
+ * @description 基金资产组合情况
+ */
+@Setter
+@Getter
+public class ReportAssetAllocationDTO extends BaseReportDTO<ReportAssetAllocationDO> {
+    /**
+     * 资产大类
+     */
+    private String assetType;
+    /**
+     * 资产明细
+     */
+    private String assetDetails;
+    /**
+     * 市值
+     */
+    private String marketValue;
+    /**
+     * 备注
+     */
+    private String remark;
+
+    public ReportAssetAllocationDTO() {
+        super();
+    }
+
+    public ReportAssetAllocationDTO(Integer fileId) {
+        super(fileId);
+    }
+
+    @Override
+    public ReportAssetAllocationDO toEntity() {
+        ReportAssetAllocationDO entity = new ReportAssetAllocationDO();
+        entity.setFileId(this.getFileId());
+        entity.setAssetType(this.assetType);
+        entity.setColumnName(this.assetDetails);
+        entity.setMarketValue(this.toBigDecimal(this.marketValue));
+        entity.setRemark(this.remark);
+        return entity;
+    }
+
+    @Override
+    public String toString() {
+        return "{" +
+                super.toString() +
+                ", assetType='" + assetType + '\'' +
+                ", assetDetails='" + assetDetails + '\'' +
+                ", marketValue=" + marketValue +
+                ", remark='" + remark + '\'' +
+                '}';
+    }
+}

+ 8 - 4
mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/ReportBaseInfoDTO.java

@@ -24,10 +24,14 @@ public class ReportBaseInfoDTO extends BaseReportDTO<ReportBaseInfoDO> {
      * 报告类型
      */
     private String reportType;
+//    /**
+//     * 报告是否用印
+//     */
+//    private Boolean withSeals;
     /**
-     * 报告是否用印
+     * 观点报告是否存在联系人信息(可能包含联系电话、地址等敏感信息)
      */
-    private Boolean withSeals;
+    private Boolean withContacts;
 
     public ReportBaseInfoDTO() {
         super();
@@ -44,7 +48,7 @@ public class ReportBaseInfoDTO extends BaseReportDTO<ReportBaseInfoDO> {
         entity.setReportDate(this.toDate(this.reportDate));
         entity.setReportName(this.reportName);
         entity.setReportType(this.reportType);
-//        entity.setWithSeals(this.withSeals);
+        entity.setWithContacts(this.withContacts);
         this.initEntity(entity);
         return entity;
     }
@@ -56,7 +60,7 @@ public class ReportBaseInfoDTO extends BaseReportDTO<ReportBaseInfoDO> {
                 ", reportDate='" + reportDate + '\'' +
                 ", reportName='" + reportName + '\'' +
                 ", reportType='" + reportType + '\'' +
-                ", withSeals=" + withSeals +
+                ", withContacts=" + withContacts +
                 '}';
     }
 }

+ 2 - 1
mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/ReportData.java

@@ -54,7 +54,8 @@ public abstract class ReportData implements Serializable {
         if (this.baseInfo == null || this.fundInfo == null) {
             return false;
         }
-        return !StrUtil.isBlank(this.baseInfo.getReportName()) && !StrUtil.isBlank(this.fundInfo.getFundName());
+        return !StrUtil.isBlank(this.baseInfo.getReportName())
+                && !StrUtil.isBlank(this.fundInfo.getFundName());
     }
 
     @Override

+ 92 - 92
mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/ReportFinancialIndicatorsDTO.java

@@ -1,92 +1,92 @@
-//package com.smppw.modaq.infrastructure.dto.report;
-//
-//import cn.hutool.core.util.StrUtil;
-//import com.simuwang.base.pojo.dos.report.ReportFinancialIndicatorsDO;
-//import lombok.Getter;
-//import lombok.Setter;
-//
-//import java.util.regex.Matcher;
-//import java.util.regex.Pattern;
-//
-//@Setter
-//@Getter
-//public class ReportFinancialIndicatorsDTO extends BaseReportLevelDTO<ReportFinancialIndicatorsDO> {
-//    /**
-//     * 年度
-//     */
-//    private String yearly;
-//    /**
-//     * 期末基金净资产
-//     */
-//    private String assetNet;
-//    /**
-//     * 报告期期末单位净值
-//     */
-//    private String nav;
-//    /**
-//     * 本期利润
-//     */
-//    private String profit;
-//    /**
-//     * 本期已实现收益
-//     */
-//    private String realizedIncome;
-//    /**
-//     * 期末可供分配利润
-//     */
-//    private String undistributedProfit;
-//    /**
-//     * 期末可供分配基金份额利润
-//     */
-//    private String undistributedShareProfit;
-//    /**
-//     * 基金份额累计净值增长率
-//     */
-//    private String shareNavRet;
-//
-//    public ReportFinancialIndicatorsDTO() {
-//        super();
-//    }
-//
-//    public ReportFinancialIndicatorsDTO(Integer fileId) {
-//        super(fileId);
-//    }
-//
-//    public ReportFinancialIndicatorsDTO(Integer fileId, String level) {
-//        super(fileId, level);
-//    }
-//
-//    @Override
-//    public ReportFinancialIndicatorsDO toEntity() {
-//        ReportFinancialIndicatorsDO entity = new ReportFinancialIndicatorsDO();
-//        entity.setFileId(this.getFileId());
-//        entity.setLevel(this.getLevel());
-//        entity.setFundAssetSize(this.toBigDecimal(this.assetNet));
-//        entity.setNav(this.toBigDecimal(this.nav));
-//        entity.setProfit(this.toBigDecimal(this.profit));
-//        entity.setRealizedIncome(this.toBigDecimal(this.realizedIncome));
-//        entity.setUndistributedProfit(this.toBigDecimal(this.undistributedProfit));
-//        if (StrUtil.isNotBlank(this.yearly)) {
-//            Matcher matcher = Pattern.compile("\\d+").matcher(this.yearly);
-//            if (matcher.find()) {
-//                entity.setEndDate(Integer.parseInt(matcher.group()));
-//            }
-//        }
-//        return entity;
-//    }
-//
-//    @Override
-//    public String toString() {
-//        return "{" +
-//                super.toString() +
-//                ", yearly=" + yearly +
-//                ", assetNet=" + assetNet +
-//                ", nav=" + nav +
-//                ", profit=" + profit +
-//                ", undistributedProfit=" + undistributedProfit +
-//                ", realizedIncome=" + realizedIncome +
-//                ", undistributedShareProfit=" + undistributedShareProfit +
-//                ", shareNavRet=" + shareNavRet +
-//                '}';
-//    }
-//}
+package com.smppw.modaq.domain.dto.report;
+
+import cn.hutool.core.util.StrUtil;
+import com.smppw.modaq.domain.entity.report.ReportFinancialIndicatorsDO;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Setter
+@Getter
+public class ReportFinancialIndicatorsDTO extends BaseReportLevelDTO<ReportFinancialIndicatorsDO> {
+    /**
+     * 年度
+     */
+    private String yearly;
+    /**
+     * 期末基金净资产
+     */
+    private String assetNet;
+    /**
+     * 报告期期末单位净值
+     */
+    private String nav;
+    /**
+     * 本期利润
+     */
+    private String profit;
+    /**
+     * 本期已实现收益
+     */
+    private String realizedIncome;
+    /**
+     * 期末可供分配利润
+     */
+    private String undistributedProfit;
+    /**
+     * 期末可供分配基金份额利润
+     */
+    private String undistributedShareProfit;
+    /**
+     * 基金份额累计净值增长率
+     */
+    private String shareNavRet;
+
+    public ReportFinancialIndicatorsDTO() {
+        super();
+    }
+
+    public ReportFinancialIndicatorsDTO(Integer fileId) {
+        super(fileId);
+    }
+
+    public ReportFinancialIndicatorsDTO(Integer fileId, String level) {
+        super(fileId, level);
+    }
+
+    @Override
+    public ReportFinancialIndicatorsDO toEntity() {
+        ReportFinancialIndicatorsDO entity = new ReportFinancialIndicatorsDO();
+        entity.setFileId(this.getFileId());
+        entity.setLevel(this.getLevel());
+        entity.setFundAssetSize(this.toBigDecimal(this.assetNet));
+        entity.setNav(this.toBigDecimal(this.nav));
+        entity.setProfit(this.toBigDecimal(this.profit));
+        entity.setRealizedIncome(this.toBigDecimal(this.realizedIncome));
+        entity.setUndistributedProfit(this.toBigDecimal(this.undistributedProfit));
+        if (StrUtil.isNotBlank(this.yearly)) {
+            Matcher matcher = Pattern.compile("\\d+").matcher(this.yearly);
+            if (matcher.find()) {
+                entity.setEndDate(Integer.parseInt(matcher.group()));
+            }
+        }
+        return entity;
+    }
+
+    @Override
+    public String toString() {
+        return "{" +
+                super.toString() +
+                ", yearly=" + yearly +
+                ", assetNet=" + assetNet +
+                ", nav=" + nav +
+                ", profit=" + profit +
+                ", undistributedProfit=" + undistributedProfit +
+                ", realizedIncome=" + realizedIncome +
+                ", undistributedShareProfit=" + undistributedShareProfit +
+                ", shareNavRet=" + shareNavRet +
+                '}';
+    }
+}

+ 83 - 0
mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/ReportFundInfoDTO.java

@@ -4,6 +4,8 @@ import com.smppw.modaq.domain.entity.report.ReportFundInfoDO;
 import lombok.Getter;
 import lombok.Setter;
 
+import java.util.Objects;
+
 /**
  * @author wangzaijun
  * @date 2024/9/26 16:47
@@ -32,6 +34,67 @@ public class ReportFundInfoDTO extends BaseReportDTO<ReportFundInfoDO> {
      */
     private String currency;
 
+    /**
+     * 投资顾问
+     */
+    private String advisorName;
+    /**
+     * 基金托管人
+     */
+    private String custodianName;
+    /**
+     * 基金经理描述
+     */
+    private String fundManager;
+    /**
+     * 投资策略
+     */
+    private String fundStrategyDescription;
+    /**
+     * 基金成立日期
+     */
+    private String inceptionDate;
+    /**
+     * 行业趋势
+     */
+    private String industryTrend;
+    /**
+     * 投资目标
+     */
+    private String investmentObjective;
+    /**
+     * 杠杆比例
+     */
+    private String leverage;
+    /**
+     * 杠杆比例描述
+     */
+    private String leverageNote;
+    /**
+     * 基金运作方式
+     */
+    private String operationType;
+    /**
+     * 备案编码
+     */
+    private String registerNumber;
+    /**
+     * 风险收益特征
+     */
+    private String riskReturnDesc;
+    /**
+     * 业绩比较基准
+     */
+    private String secondaryBenchmark;
+    /**
+     * 基金到期日期
+     */
+    private String dueDate;
+    /**
+     * 信息披露报告是否经托管机构复核
+     */
+    private String isReviewed;
+
     public ReportFundInfoDTO() {
         super();
     }
@@ -48,6 +111,22 @@ public class ReportFundInfoDTO extends BaseReportDTO<ReportFundInfoDO> {
         entity.setCompanyName(this.companyName);
         entity.setCurrency(this.currency);
         entity.setFundName(this.fundName);
+        entity.setAdvisorName(this.advisorName);
+        entity.setCustodianName(this.custodianName);
+        entity.setFundManager(this.fundManager);
+        entity.setFundName(this.fundName);
+        entity.setFundStrategyDescription(this.fundStrategyDescription);
+        entity.setInceptionDate(this.toDate(this.inceptionDate));
+        entity.setIndustryTrend(this.industryTrend);
+        entity.setInvestmentObjective(this.investmentObjective);
+        entity.setLeverage(this.toBigDecimal(this.leverage));
+        entity.setLeverageNote(this.leverageNote);
+        entity.setOperationType(this.operationType);
+        entity.setRegisterNumber(this.registerNumber);
+        entity.setRiskReturnDesc(this.riskReturnDesc);
+        entity.setSecondaryBenchmark(this.secondaryBenchmark);
+        entity.setDueDate(this.toDate(this.dueDate));
+        entity.setReviewed(Objects.equals("是", this.isReviewed) ? 1 : 0);
         this.initEntity(entity);
         return entity;
     }
@@ -56,8 +135,12 @@ public class ReportFundInfoDTO extends BaseReportDTO<ReportFundInfoDO> {
     public String toString() {
         return "{" +
                 super.toString() +
+                ", advisorName='" + advisorName + '\'' +
                 ", fundName='" + fundName + '\'' +
+                ", inceptionDate='" + inceptionDate + '\'' +
+                ", registerNumber='" + registerNumber + '\'' +
                 ", companyName='" + companyName + '\'' +
+                ", dueDate='" + dueDate + '\'' +
                 '}';
     }
 }

+ 5 - 3
mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/ReportFundTransactionDTO.java

@@ -4,6 +4,8 @@ import com.smppw.modaq.domain.entity.report.ReportFundTransactionDO;
 import lombok.Getter;
 import lombok.Setter;
 
+import java.math.BigDecimal;
+
 @Setter
 @Getter
 public class ReportFundTransactionDTO extends BaseReportDTO<ReportFundTransactionDO> {
@@ -250,9 +252,9 @@ public class ReportFundTransactionDTO extends BaseReportDTO<ReportFundTransactio
         entity.setApplyDate(this.toDate(applyDate));
         entity.setApplyAmount(this.toBigDecimal(applyAmount));
         entity.setApplyShare(this.toBigDecimal(applyShare));
-        entity.setAmount(this.toBigDecimal(amount));
-        entity.setShare(this.toBigDecimal(share));
-        entity.setNetAmount(this.toBigDecimal(netAmount));
+        entity.setAmount(this.toBigDecimal(amount, BigDecimal.ZERO));
+        entity.setShare(this.toBigDecimal(share, BigDecimal.ZERO));
+        entity.setNetAmount(this.toBigDecimal(netAmount, BigDecimal.ZERO));
         entity.setNav(this.toBigDecimal(nav));
         entity.setConfirmationRatio(this.toBigDecimal(confirmationRatio));
         entity.setTaConfirmationNumber(taConfirmationNumber);

+ 61 - 61
mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/ReportInvestmentIndustryDTO.java

@@ -1,61 +1,61 @@
-//package com.smppw.modaq.infrastructure.dto.report;
-//
-//import com.simuwang.base.pojo.dos.report.ReportInvestmentIndustryDO;
-//import lombok.Getter;
-//import lombok.Setter;
-//
-///**
-// * @author wangzaijun
-// * @date 2024/9/26 16:49
-// * @description 按行业分类的股票投资组合
-// */
-//@Setter
-//@Getter
-//public class ReportInvestmentIndustryDTO extends BaseReportDTO<ReportInvestmentIndustryDO> {
-//    /**
-//     * 行业分类名称
-//     */
-//    private String industryName;
-//    /**
-//     * 投资地区: 1-境内, 2-港股通
-//     */
-//    private Integer investType;
-//    /**
-//     * 公允价值,市值
-//     */
-//    private String marketValue;
-//    /**
-//     * 占基金资产净值的比例,占净值比,权重
-//     */
-//    private String ratio;
-//
-//    public ReportInvestmentIndustryDTO() {
-//        super();
-//    }
-//
-//    public ReportInvestmentIndustryDTO(Integer fileId) {
-//        super(fileId);
-//    }
-//
-//    @Override
-//    public ReportInvestmentIndustryDO toEntity() {
-//        ReportInvestmentIndustryDO entity = new ReportInvestmentIndustryDO();
-//        entity.setFileId(this.getFileId());
-//        entity.setIndustryName(this.industryName);
-//        entity.setInvestType(this.investType);
-//        entity.setMarketValue(this.toBigDecimal(this.marketValue));
-//        entity.setRatio(this.toBigDecimal(this.ratio));
-//        return entity;
-//    }
-//
-//    @Override
-//    public String toString() {
-//        return "{" +
-//                super.toString() +
-//                ", industryName='" + industryName + '\'' +
-//                ", investType=" + investType +
-//                ", marketValue=" + marketValue +
-//                ", ratio=" + ratio +
-//                '}';
-//    }
-//}
+package com.smppw.modaq.domain.dto.report;
+
+import com.smppw.modaq.domain.entity.report.ReportInvestmentIndustryDO;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author wangzaijun
+ * @date 2024/9/26 16:49
+ * @description 按行业分类的股票投资组合
+ */
+@Setter
+@Getter
+public class ReportInvestmentIndustryDTO extends BaseReportDTO<ReportInvestmentIndustryDO> {
+    /**
+     * 行业分类名称
+     */
+    private String industryName;
+    /**
+     * 投资地区: 1-境内, 2-港股通
+     */
+    private Integer investType;
+    /**
+     * 公允价值,市值
+     */
+    private String marketValue;
+    /**
+     * 占基金资产净值的比例,占净值比,权重
+     */
+    private String ratio;
+
+    public ReportInvestmentIndustryDTO() {
+        super();
+    }
+
+    public ReportInvestmentIndustryDTO(Integer fileId) {
+        super(fileId);
+    }
+
+    @Override
+    public ReportInvestmentIndustryDO toEntity() {
+        ReportInvestmentIndustryDO entity = new ReportInvestmentIndustryDO();
+        entity.setFileId(this.getFileId());
+        entity.setIndustryName(this.industryName);
+        entity.setInvestType(this.investType);
+        entity.setMarketValue(this.toBigDecimal(this.marketValue));
+        entity.setRatio(this.toBigDecimal(this.ratio));
+        return entity;
+    }
+
+    @Override
+    public String toString() {
+        return "{" +
+                super.toString() +
+                ", industryName='" + industryName + '\'' +
+                ", investType=" + investType +
+                ", marketValue=" + marketValue +
+                ", ratio=" + ratio +
+                '}';
+    }
+}

+ 72 - 72
mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/ReportNetReportDTO.java

@@ -1,72 +1,72 @@
-//package com.smppw.modaq.infrastructure.dto.report;
-//
-//import com.simuwang.base.pojo.dos.report.ReportNetReportDO;
-//import lombok.Getter;
-//import lombok.Setter;
-//
-///**
-// * @author wangzaijun
-// * @date 2024/9/26 16:53
-// * @description 基协报告净值月报
-// */
-//@Setter
-//@Getter
-//public class ReportNetReportDTO extends BaseReportLevelDTO<ReportNetReportDO> {
-//    /**
-//     * 估值日期
-//     */
-//    private String valuationDate;
-//    /**
-//     * 累计净值
-//     */
-//    private String cumulativeNavWithdrawal;
-//    /**
-//     * 基金份额总额
-//     */
-//    private String assetShare;
-//    /**
-//     * 基金资产净值
-//     */
-//    private String assetNet;
-//    /**
-//     * 单位净值
-//     */
-//    private String nav;
-//
-//    public ReportNetReportDTO() {
-//        super();
-//    }
-//
-//    public ReportNetReportDTO(Integer fileId) {
-//        super(fileId);
-//    }
-//
-//    public ReportNetReportDTO(Integer fileId, String level) {
-//        super(fileId, level);
-//    }
-//
-//    @Override
-//    public ReportNetReportDO toEntity() {
-//        ReportNetReportDO entity = new ReportNetReportDO();
-//        entity.setFileId(this.getFileId());
-//        entity.setLevel(this.getLevel());
-//        entity.setValuationDate(this.toDate(this.valuationDate));
-//        entity.setCumulativeNav(this.toBigDecimal(this.cumulativeNavWithdrawal));
-//        entity.setEndTotalShares(this.toBigDecimal(this.assetShare));
-//        entity.setFundAssetSize(this.toBigDecimal(this.assetNet));
-//        entity.setNav(this.toBigDecimal(this.nav));
-//        return entity;
-//    }
-//
-//    @Override
-//    public String toString() {
-//        return "{" +
-//                super.toString() +
-//                ", valuationDate='" + valuationDate + '\'' +
-//                ", cumulativeNavWithdrawal=" + cumulativeNavWithdrawal +
-//                ", assetShare=" + assetShare +
-//                ", fundAssetSize=" + assetNet +
-//                ", nav=" + nav +
-//                '}';
-//    }
-//}
+package com.smppw.modaq.domain.dto.report;
+
+import com.smppw.modaq.domain.entity.report.ReportNetReportDO;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author wangzaijun
+ * @date 2024/9/26 16:53
+ * @description 基协报告净值月报
+ */
+@Setter
+@Getter
+public class ReportNetReportDTO extends BaseReportLevelDTO<ReportNetReportDO> {
+    /**
+     * 估值日期
+     */
+    private String valuationDate;
+    /**
+     * 累计净值
+     */
+    private String cumulativeNavWithdrawal;
+    /**
+     * 基金份额总额
+     */
+    private String assetShare;
+    /**
+     * 基金资产净值
+     */
+    private String assetNet;
+    /**
+     * 单位净值
+     */
+    private String nav;
+
+    public ReportNetReportDTO() {
+        super();
+    }
+
+    public ReportNetReportDTO(Integer fileId) {
+        super(fileId);
+    }
+
+    public ReportNetReportDTO(Integer fileId, String level) {
+        super(fileId, level);
+    }
+
+    @Override
+    public ReportNetReportDO toEntity() {
+        ReportNetReportDO entity = new ReportNetReportDO();
+        entity.setFileId(this.getFileId());
+        entity.setLevel(this.getLevel());
+        entity.setValuationDate(this.toDate(this.valuationDate));
+        entity.setCumulativeNav(this.toBigDecimal(this.cumulativeNavWithdrawal));
+        entity.setEndTotalShares(this.toBigDecimal(this.assetShare));
+        entity.setFundAssetSize(this.toBigDecimal(this.assetNet));
+        entity.setNav(this.toBigDecimal(this.nav));
+        return entity;
+    }
+
+    @Override
+    public String toString() {
+        return "{" +
+                super.toString() +
+                ", valuationDate='" + valuationDate + '\'' +
+                ", cumulativeNavWithdrawal=" + cumulativeNavWithdrawal +
+                ", assetShare=" + assetShare +
+                ", fundAssetSize=" + assetNet +
+                ", nav=" + nav +
+                '}';
+    }
+}

+ 72 - 72
mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/ReportShareChangeDTO.java

@@ -1,72 +1,72 @@
-//package com.smppw.modaq.infrastructure.dto.report;
-//
-//import com.simuwang.base.pojo.dos.report.ReportShareChangeDO;
-//import lombok.Getter;
-//import lombok.Setter;
-//
-///**
-// * @author wangzaijun
-// * @date 2024/9/26 16:40
-// * @description 基金份额变动情况
-// */
-//@Setter
-//@Getter
-//public class ReportShareChangeDTO extends BaseReportLevelDTO<ReportShareChangeDO> {
-//    /**
-//     * 报告期期初基金份额总额
-//     */
-//    private String initTotalShares;
-//    /**
-//     * 减: 报告期期间基金总赎回份额
-//     */
-//    private String redemption;
-//    /**
-//     * 期末基金总份额/期末基金实缴总额
-//     */
-//    private String sharePerAsset;
-//    /**
-//     * 报告期期间基金拆分变动份额
-//     */
-//    private String splitChangeShare;
-//    /**
-//     * 报告期期间基金总申购份额
-//     */
-//    private String subscription;
-//
-//    public ReportShareChangeDTO() {
-//        super();
-//    }
-//
-//    public ReportShareChangeDTO(Integer fileId) {
-//        super(fileId);
-//    }
-//
-//    public ReportShareChangeDTO(Integer fileId, String level) {
-//        super(fileId, level);
-//    }
-//
-//    @Override
-//    public ReportShareChangeDO toEntity() {
-//        ReportShareChangeDO entity = new ReportShareChangeDO();
-//        entity.setFileId(this.getFileId());
-//        entity.setLevel(this.getLevel());
-//        entity.setRedemption(this.toBigDecimal(this.redemption));
-//        entity.setInitTotalShares(this.toBigDecimal(this.initTotalShares));
-//        entity.setSharePerAsset(this.toBigDecimal(this.sharePerAsset));
-//        entity.setSplit(this.toBigDecimal(this.splitChangeShare));
-//        entity.setSubscription(this.toBigDecimal(this.subscription));
-//        return entity;
-//    }
-//
-//    @Override
-//    public String toString() {
-//        return "{" +
-//                super.toString() +
-//                ", initTotalShares=" + initTotalShares +
-//                ", redemption=" + redemption +
-//                ", sharePerAsset=" + sharePerAsset +
-//                ", splitChangeShare=" + splitChangeShare +
-//                ", subscription=" + subscription +
-//                '}';
-//    }
-//}
+package com.smppw.modaq.domain.dto.report;
+
+import com.smppw.modaq.domain.entity.report.ReportShareChangeDO;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author wangzaijun
+ * @date 2024/9/26 16:40
+ * @description 基金份额变动情况
+ */
+@Setter
+@Getter
+public class ReportShareChangeDTO extends BaseReportLevelDTO<ReportShareChangeDO> {
+    /**
+     * 报告期期初基金份额总额
+     */
+    private String initTotalShares;
+    /**
+     * 减: 报告期期间基金总赎回份额
+     */
+    private String redemption;
+    /**
+     * 期末基金总份额/期末基金实缴总额
+     */
+    private String sharePerAsset;
+    /**
+     * 报告期期间基金拆分变动份额
+     */
+    private String splitChangeShare;
+    /**
+     * 报告期期间基金总申购份额
+     */
+    private String subscription;
+
+    public ReportShareChangeDTO() {
+        super();
+    }
+
+    public ReportShareChangeDTO(Integer fileId) {
+        super(fileId);
+    }
+
+    public ReportShareChangeDTO(Integer fileId, String level) {
+        super(fileId, level);
+    }
+
+    @Override
+    public ReportShareChangeDO toEntity() {
+        ReportShareChangeDO entity = new ReportShareChangeDO();
+        entity.setFileId(this.getFileId());
+        entity.setLevel(this.getLevel());
+        entity.setRedemption(this.toBigDecimal(this.redemption));
+        entity.setInitTotalShares(this.toBigDecimal(this.initTotalShares));
+        entity.setSharePerAsset(this.toBigDecimal(this.sharePerAsset));
+        entity.setSplit(this.toBigDecimal(this.splitChangeShare));
+        entity.setSubscription(this.toBigDecimal(this.subscription));
+        return entity;
+    }
+
+    @Override
+    public String toString() {
+        return "{" +
+                super.toString() +
+                ", initTotalShares=" + initTotalShares +
+                ", redemption=" + redemption +
+                ", sharePerAsset=" + sharePerAsset +
+                ", splitChangeShare=" + splitChangeShare +
+                ", subscription=" + subscription +
+                '}';
+    }
+}

+ 19 - 0
mo-daq/src/main/java/com/smppw/modaq/domain/dto/report/WeeklyReportData.java

@@ -0,0 +1,19 @@
+package com.smppw.modaq.domain.dto.report;
+
+import com.smppw.modaq.common.enums.ReportType;
+
+public class WeeklyReportData extends ReportData {
+    public WeeklyReportData(ReportBaseInfoDTO baseInfo, ReportFundInfoDTO fundInfo) {
+        super(baseInfo, fundInfo);
+    }
+
+    @Override
+    public ReportType getReportType() {
+        return ReportType.WEEKLY;
+    }
+
+    @Override
+    public String toString() {
+        return super.toString();
+    }
+}

+ 4 - 0
mo-daq/src/main/java/com/smppw/modaq/domain/entity/EmailParseInfoDO.java

@@ -58,6 +58,10 @@ public class EmailParseInfoDO {
     @TableField(value = "fail_reason")
     private String failReason;
     /**
+     * 附件文件大小,byte
+     */
+    private Integer attrSize;
+    /**
      * 记录的有效性;1-有效;0-无效;
      */
     @TableField(value = "isvalid")

+ 34 - 0
mo-daq/src/main/java/com/smppw/modaq/domain/entity/report/ReportAssetAllocationDO.java

@@ -0,0 +1,34 @@
+package com.smppw.modaq.domain.entity.report;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+
+/**
+ * @author wangzaijun
+ * @date 2024/9/26 16:43
+ * @description 基金资产组合情况
+ */
+@Setter
+@Getter
+@TableName("mo_report_asset_allocation")
+public class ReportAssetAllocationDO extends BaseReportDO {
+    /**
+     * 资产大类
+     */
+    private String assetType;
+    /**
+     * 资产明细
+     */
+    private String columnName;
+    /**
+     * 市值
+     */
+    private BigDecimal marketValue;
+    /**
+     * 备注
+     */
+    private String remark;
+}

+ 4 - 0
mo-daq/src/main/java/com/smppw/modaq/domain/entity/report/ReportBaseInfoDO.java

@@ -31,4 +31,8 @@ public class ReportBaseInfoDO extends BaseReportDO {
 //     * 报告是否用印
 //     */
 //    private Boolean withSeals;
+    /**
+     * 观点报告是否存在联系人信息(可能包含联系电话、地址等敏感信息)
+     */
+    private Boolean withContacts;
 }

+ 26 - 0
mo-daq/src/main/java/com/smppw/modaq/domain/entity/report/ReportFinancialIndicatorsDO.java

@@ -0,0 +1,26 @@
+package com.smppw.modaq.domain.entity.report;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+
+@Setter
+@Getter
+@TableName("mo_report_financial_indicators")
+public class ReportFinancialIndicatorsDO extends BaseReportDO {
+    private String level;
+    /**
+     * 年度
+     */
+    private Integer endDate;
+    private BigDecimal fundAssetSize;
+    private BigDecimal nav;
+    private BigDecimal profit;
+    private BigDecimal realizedIncome;
+    /**
+     * 期末可供分配利润
+     */
+    private BigDecimal undistributedProfit;
+}

+ 66 - 0
mo-daq/src/main/java/com/smppw/modaq/domain/entity/report/ReportFundInfoDO.java

@@ -1,9 +1,13 @@
 package com.smppw.modaq.domain.entity.report;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Getter;
 import lombok.Setter;
 
+import java.math.BigDecimal;
+import java.util.Date;
+
 /**
  * @author wangzaijun
  * @date 2024/9/26 16:47
@@ -32,4 +36,66 @@ public class ReportFundInfoDO extends BaseReportDO {
      * 基金交易使用的货币种类
      */
     private String currency;
+
+    /**
+     * 投资顾问
+     */
+    private String advisorName;
+    /**
+     * 基金托管人
+     */
+    private String custodianName;
+    /**
+     * 基金经理描述
+     */
+    private String fundManager;
+    /**
+     * 投资策略
+     */
+    private String fundStrategyDescription;
+    /**
+     * 基金成立日期
+     */
+    private Date inceptionDate;
+    /**
+     * 行业趋势
+     */
+    private String industryTrend;
+    /**
+     * 投资目标
+     */
+    private String investmentObjective;
+    /**
+     * 杠杆比例
+     */
+    private BigDecimal leverage;
+    /**
+     * 杠杆比例描述
+     */
+    private String leverageNote;
+    /**
+     * 基金运作方式
+     */
+    private String operationType;
+    /**
+     * 备案编码
+     */
+    private String registerNumber;
+    /**
+     * 风险收益特征
+     */
+    private String riskReturnDesc;
+    /**
+     * 业绩比较基准
+     */
+    private String secondaryBenchmark;
+    /**
+     * 基金到期日期
+     */
+    private Date dueDate;
+    /**
+     * 信息披露报告是否经托管机构复核
+     */
+    @TableField(value = "reviewed")
+    private Integer reviewed;
 }

+ 42 - 0
mo-daq/src/main/java/com/smppw/modaq/domain/entity/report/ReportInvestmentIndustryDO.java

@@ -0,0 +1,42 @@
+package com.smppw.modaq.domain.entity.report;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+
+/**
+ * @author wangzaijun
+ * @date 2024/9/26 16:49
+ * @description 按行业分类的股票投资组合
+ */
+@Setter
+@Getter
+@TableName("mo_report_invest_industry")
+public class ReportInvestmentIndustryDO extends BaseReportDO {
+    /**
+     * 行业分类编码
+     */
+    private String industryCode;
+    /**
+     * 行业分类名称
+     */
+    private String industryName;
+    /**
+     * 投资地区: 1-境内, 2-港股通
+     */
+    private Integer investType;
+    /**
+     * 行业标准编码
+     */
+    private String isbCode;
+    /**
+     * 公允价值
+     */
+    private BigDecimal marketValue;
+    /**
+     * 占基金资产净值的比例
+     */
+    private BigDecimal ratio;
+}

+ 38 - 0
mo-daq/src/main/java/com/smppw/modaq/domain/entity/report/ReportNetReportDO.java

@@ -0,0 +1,38 @@
+package com.smppw.modaq.domain.entity.report;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * @author wangzaijun
+ * @date 2024/9/26 16:53
+ * @description 基协报告净值月报
+ */
+@Setter
+@Getter
+@TableName("mo_report_net_report")
+public class ReportNetReportDO extends BaseReportDO {
+    private String level;
+    private Date valuationDate;
+
+    /**
+     * 累计净值
+     */
+    private BigDecimal cumulativeNav;
+    /**
+     * 基金份额总额
+     */
+    private BigDecimal endTotalShares;
+    /**
+     * 基金资产净值
+     */
+    private BigDecimal fundAssetSize;
+    /**
+     * 单位净值
+     */
+    private BigDecimal nav;
+}

+ 42 - 0
mo-daq/src/main/java/com/smppw/modaq/domain/entity/report/ReportShareChangeDO.java

@@ -0,0 +1,42 @@
+package com.smppw.modaq.domain.entity.report;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+
+/**
+ * @author wangzaijun
+ * @date 2024/9/26 16:40
+ * @description 基金份额变动情况
+ */
+@Setter
+@Getter
+@TableName("mo_report_share_change")
+public class ReportShareChangeDO extends BaseReportDO {
+    /**
+     * 报告期期初基金份额总额
+     */
+    private BigDecimal initTotalShares;
+    /**
+     * 基金分级
+     */
+    private String level;
+    /**
+     * 减: 报告期期间基金总赎回份额
+     */
+    private BigDecimal redemption;
+    /**
+     * 期末基金总份额/期末基金实缴总额
+     */
+    private BigDecimal sharePerAsset;
+    /**
+     * 报告期期间基金拆分变动份额
+     */
+    private BigDecimal split;
+    /**
+     * 报告期期间基金总申购份额
+     */
+    private BigDecimal subscription;
+}

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

@@ -0,0 +1,9 @@
+package com.smppw.modaq.domain.mapper.report;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.smppw.modaq.domain.entity.report.ReportAssetAllocationDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ReportAssetAllocationMapper extends BaseMapper<ReportAssetAllocationDO> {
+}

+ 1 - 1
mo-daq/src/main/java/com/smppw/modaq/domain/mapper/ReportBaseInfoMapper.java

@@ -1,4 +1,4 @@
-package com.smppw.modaq.domain.mapper;
+package com.smppw.modaq.domain.mapper.report;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.smppw.modaq.domain.entity.report.ReportBaseInfoDO;

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

@@ -0,0 +1,9 @@
+package com.smppw.modaq.domain.mapper.report;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.smppw.modaq.domain.entity.report.ReportFinancialIndicatorsDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ReportFinancialIndicatorMapper extends BaseMapper<ReportFinancialIndicatorsDO> {
+}

+ 1 - 1
mo-daq/src/main/java/com/smppw/modaq/domain/mapper/ReportFundInfoMapper.java

@@ -1,4 +1,4 @@
-package com.smppw.modaq.domain.mapper;
+package com.smppw.modaq.domain.mapper.report;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.smppw.modaq.domain.entity.report.ReportFundInfoDO;

+ 1 - 1
mo-daq/src/main/java/com/smppw/modaq/domain/mapper/ReportFundTransactionMapper.java

@@ -1,4 +1,4 @@
-package com.smppw.modaq.domain.mapper;
+package com.smppw.modaq.domain.mapper.report;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.smppw.modaq.domain.entity.report.ReportFundTransactionDO;

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

@@ -0,0 +1,9 @@
+package com.smppw.modaq.domain.mapper.report;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.smppw.modaq.domain.entity.report.ReportInvestmentIndustryDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ReportInvestmentIndustryMapper extends BaseMapper<ReportInvestmentIndustryDO> {
+}

+ 1 - 1
mo-daq/src/main/java/com/smppw/modaq/domain/mapper/ReportInvestorInfoMapper.java

@@ -1,4 +1,4 @@
-package com.smppw.modaq.domain.mapper;
+package com.smppw.modaq.domain.mapper.report;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.smppw.modaq.domain.entity.report.ReportInvestorInfoDO;

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

@@ -0,0 +1,9 @@
+package com.smppw.modaq.domain.mapper.report;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.smppw.modaq.domain.entity.report.ReportNetReportDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ReportNetReportMapper extends BaseMapper<ReportNetReportDO> {
+}

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

@@ -0,0 +1,9 @@
+package com.smppw.modaq.domain.mapper.report;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.smppw.modaq.domain.entity.report.ReportShareChangeDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ReportShareChangeMapper extends BaseMapper<ReportShareChangeDO> {
+}

+ 114 - 75
mo-daq/src/main/java/com/smppw/modaq/domain/service/EmailParseService.java

@@ -33,8 +33,7 @@ import com.smppw.modaq.domain.mapper.EmailParseInfoMapper;
 import com.smppw.modaq.infrastructure.util.ExcelUtil;
 import com.smppw.modaq.infrastructure.util.FileUtil;
 import jakarta.mail.*;
-import jakarta.mail.internet.MimeMessage;
-import jakarta.mail.internet.MimeMultipart;
+import jakarta.mail.internet.MimeUtility;
 import jakarta.mail.search.ComparisonTerm;
 import jakarta.mail.search.ReceivedDateTerm;
 import jakarta.mail.search.SearchTerm;
@@ -47,7 +46,6 @@ import org.springframework.util.StopWatch;
 
 import java.io.File;
 import java.io.IOException;
-import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.*;
@@ -82,7 +80,6 @@ public class EmailParseService {
                              EmailFileInfoMapper emailFileInfoMapper,
                              ReportParserFactory reportParserFactory,
                              ReportWriterFactory reportWriterFactory) {
-//        this.emailFieldMapper = emailFieldMapper;
         this.emailParseInfoMapper = emailParseInfoMapper;
         this.emailFileInfoMapper = emailFileInfoMapper;
         this.reportParserFactory = reportParserFactory;
@@ -236,7 +233,7 @@ public class EmailParseService {
             if (CollUtil.isNotEmpty(zipFiles)) {
                 for (EmailZipFileDTO zipFile : zipFiles) {
                     EmailFileInfoDO emailFile = saveEmailFileInfo(emailId, null, zipFile.getFilename(), zipFile.getFilepath(), null);
-                    // 解析结果(可以从python获取或者自行解析)并保存报告
+                    // 解析并保存报告
                     ParseResult<ReportData> parseResult = this.parseReportAndHandleResult(emailFile.getId(), zipFile.getFilename(),
                             zipFile.getFilepath(), zipFile.getEmailType(), emailFile.getAiFileId());
                     dataList.add(parseResult);
@@ -245,7 +242,7 @@ public class EmailParseService {
                 String fileName = emailContentInfoDTO.getFileName();
                 EmailFileInfoDO emailFile = saveEmailFileInfo(emailId, emailContentInfoDTO.getFileId(), fileName,
                         emailContentInfoDTO.getFilePath(), emailContentInfoDTO.getAiFileId());
-                // 解析结果(可以从python获取或者自行解析)并保存报告
+                // 解析并保存报告
                 ParseResult<ReportData> parseResult = this.parseReportAndHandleResult(emailFile.getId(), fileName,
                         emailContentInfoDTO.getFilePath(), emailContentInfoDTO.getEmailType(), emailFile.getAiFileId());
                 dataList.add(parseResult);
@@ -277,7 +274,9 @@ public class EmailParseService {
                                                                String filepath, Integer emailType, String aiFileId) {
         ParseResult<ReportData> result = new ParseResult<>();
         boolean reportFlag = !Objects.equals(EmailTypeConst.REPORT_EMAIL_TYPE, emailType)
-                && !Objects.equals(EmailTypeConst.REPORT_LETTER_EMAIL_TYPE, emailType);
+                && !Objects.equals(EmailTypeConst.REPORT_LETTER_EMAIL_TYPE, emailType)
+                && !Objects.equals(EmailTypeConst.REPORT_WEEKLy_TYPE, emailType)
+                && !Objects.equals(EmailTypeConst.REPORT_OTHER_TYPE, emailType);
         if (reportFlag || StrUtil.isBlank(fileName) || fileName.endsWith(".html")) {
             result.setStatus(ReportParseStatus.NOT_A_REPORT.getCode());
             result.setMsg(StrUtil.format(ReportParseStatus.NOT_A_REPORT.getMsg(), fileName));
@@ -294,7 +293,7 @@ public class EmailParseService {
         if (Objects.equals(EmailTypeConst.REPORT_LETTER_EMAIL_TYPE, emailType)) {
             reportType = ReportType.LETTER;
         }
-        // 解析器--如果开启python解析则直接调用python接口,否则根据文件后缀获取对应解析器
+        // 解析器--根据文件后缀获取对应解析器,解析不了就用AI来解析
         ReportParserFileType fileType;
         String fileSuffix = StrUtil.subAfter(fileName, ".", true);
         fileType = ReportParserFileType.getBySuffix(fileSuffix);
@@ -310,13 +309,14 @@ public class EmailParseService {
             result.setMsg(StrUtil.format(ReportParseStatus.NOT_A_REPORT.getMsg(), fileName));
             return result;
         }
+        // 不支持解析的格式文件
+        boolean notSupportFile = false;
         // 解析报告
         ReportData reportData = null;
-        boolean notSupportFile = false;
         StopWatch parserWatch = new StopWatch();
         parserWatch.start();
         try {
-            if (StrUtil.isBlank(aiFileId)) {
+            if (StrUtil.isBlank(aiFileId) && reportType != ReportType.OTHER && reportType != ReportType.WEEKLY) {
                 ReportParserParams params = ReportParserParams.builder().fileId(fileId).filename(fileName).filepath(filepath)
                         .registerNumber(registerNumber).reportType(reportType).build();
                 ReportParser<ReportData> instance = this.reportParserFactory.getInstance(reportType, fileType);
@@ -325,8 +325,14 @@ public class EmailParseService {
                 result.setMsg("报告解析成功");
                 result.setData(reportData);
             } else {
-                if (log.isInfoEnabled()) {
-                    log.info("报告{} 是已经存在ai解析记录,上传过文件{},直接跳转到AI解析器进行解析", fileName, fileId);
+                if (reportType == ReportType.OTHER || reportType == ReportType.WEEKLY) {
+                    if (log.isInfoEnabled()) {
+                        log.info("报告{} 是周报或其他类型,直接用AI解析器解析", fileName);
+                    }
+                } else {
+                    if (log.isInfoEnabled()) {
+                        log.info("报告{} 是已经存在ai解析记录,上传过文件{},直接跳转到AI解析器进行解析", fileName, fileId);
+                    }
                 }
             }
         } catch (ReportParseException e) {
@@ -352,8 +358,12 @@ public class EmailParseService {
                 try {
                     reportData = instance.parse(params);
                     result.setStatus(1);
-                    result.setMsg("报告解析成功");
+                    result.setMsg("报告解析成功--AI");
                     result.setData(reportData);
+                } catch (ReportParseException e) {
+                    log.error("AI解析失败:{}", StrUtil.format(e.getMsg(), fileName));
+                    result.setStatus(e.getCode());
+                    result.setMsg(StrUtil.format(e.getMsg(), fileName));
                 } catch (Exception e) {
                     log.error("AI解析错误:{}", ExceptionUtil.stacktraceToString(e));
                     result.setStatus(ReportParseStatus.PARSE_FAIL.getCode());
@@ -440,6 +450,7 @@ public class EmailParseService {
         emailParseInfoDO.setEmailTitle(emailContentInfoDTO.getEmailTitle());
         emailParseInfoDO.setEmailType(emailContentInfoDTO.getEmailType());
         emailParseInfoDO.setParseStatus(EmailParseStatusConst.SUCCESS);
+        emailParseInfoDO.setAttrSize(emailContentInfoDTO.getFileSize());
         emailParseInfoDO.setIsvalid(1);
         emailParseInfoDO.setCreatorId(0);
         emailParseInfoDO.setCreateTime(new Date());
@@ -450,15 +461,14 @@ public class EmailParseService {
 
     public Map<Integer, List<String>> getEmailType() {
         Map<Integer, List<String>> emailTypeMap = MapUtil.newHashMap(3, true);
-//        EmailTypeRuleDO emailTypeRuleDO = emailTypeRuleMapper.getEmailTypeRule();
-//        String nav = emailTypeRuleDO != null && StrUtil.isNotBlank(emailTypeRuleDO.getNav()) ? emailTypeRuleDO.getNav() : emailRuleConfig.getNav();
-//        String valuation = emailTypeRuleDO != null && StrUtil.isNotBlank(emailTypeRuleDO.getValuation()) ? emailTypeRuleDO.getValuation() : emailRuleConfig.getValuation();
-//        String report = emailTypeRuleDO != null && StrUtil.isNotBlank(emailTypeRuleDO.getReport()) ? emailTypeRuleDO.getReport() : emailRuleConfig.getReport();
-//        emailTypeMap.put(EmailTypeConst.VALUATION_EMAIL_TYPE, Arrays.stream(valuation.split(",")).toList());
-//        emailTypeMap.put(EmailTypeConst.NAV_EMAIL_TYPE, Arrays.stream(nav.split(",")).toList());
-//        emailTypeMap.put(EmailTypeConst.REPORT_EMAIL_TYPE, ListUtil.toList("月报", "周报", "月度报告"));
+        emailTypeMap.put(EmailTypeConst.REPORT_EMAIL_TYPE,
+                ListUtil.toList("月报", "月度报告", "季报", "季度报告", "年报", "年度报告"));
         emailTypeMap.put(EmailTypeConst.REPORT_LETTER_EMAIL_TYPE,
-                ListUtil.toList("确认单", "确认函", "交易确认数据", "赎回确认", "申购确认", "分红确认", "确认表", "交易确认", "确认"));
+                ListUtil.toList(ReportType.LETTER.getPatterns()));
+        emailTypeMap.put(EmailTypeConst.REPORT_OTHER_TYPE,
+                ListUtil.toList(ReportType.OTHER.getPatterns()));
+        emailTypeMap.put(EmailTypeConst.REPORT_WEEKLy_TYPE,
+                ListUtil.toList(ReportType.WEEKLY.getPatterns()));
         return emailTypeMap;
     }
 
@@ -487,16 +497,14 @@ public class EmailParseService {
             return MapUtil.newHashMap();
         }
         Map<String, List<EmailContentInfoDTO>> emailMessageMap = MapUtil.newHashMap();
-        for (Message message1 : messages) {
+        for (Message message : messages) {
             long start = System.currentTimeMillis();
+            List<EmailContentInfoDTO> emailContentInfoDTOList = CollUtil.newArrayList();
+            String uuidKey = UUID.randomUUID().toString().replaceAll("-", "");
+            Integer emailType;
+            String senderEmail;
             String emailTitle = null;
             try {
-                MimeMessage message = (MimeMessage) message1;
-                emailTitle = message.getSubject();
-                List<EmailContentInfoDTO> emailContentInfoDTOList = CollUtil.newArrayList();
-                String uuidKey = UUID.randomUUID().toString().replaceAll("-", "");
-                Integer emailType;
-                String senderEmail;
                 Date emailDate = message.getSentDate();
                 String emailDateStr = DateUtil.format(emailDate, DateConst.YYYY_MM_DD_HH_MM_SS);
                 if (log.isInfoEnabled()) {
@@ -507,6 +515,7 @@ public class EmailParseService {
                 if (isNotParseConditionSatisfied) {
                     continue;
                 }
+                emailTitle = message.getSubject();
                 senderEmail = getSenderEmail(message);
                 emailType = EmailUtil.getEmailTypeBySubject(emailTitle, emailTypeMap);
                 if (emailType == null) {
@@ -515,48 +524,28 @@ public class EmailParseService {
                 }
                 log.info("邮件{} 基本信息获取完成,开始下载附件!邮件日期:{}", emailTitle, emailDateStr);
                 Object content = message.getContent();
-                // 1.邮件为MIME多部分消息体:可能既有邮件又有正文
-                if (content instanceof MimeMultipart) {
-                    emailContentInfoDTOList = EmailUtil.collectMimeMultipart(message, mailboxInfoDTO.getAccount(), path);
-                }
-                // 2.邮件只有正文
-                if (content instanceof String) {
-                    EmailContentInfoDTO emailContentInfoDTO = new EmailContentInfoDTO();
-                    emailContentInfoDTO.setEmailContent(content.toString());
-                    emailContentInfoDTO.setEmailDate(emailDateStr);
-                    emailContentInfoDTO.setEmailTitle(emailTitle);
-                    String fileName = emailTitle + DateUtil.format(emailDate, DateConst.YYYYMMDDHHMMSS24);
-                    String filePath = path + mailboxInfoDTO.getAccount() + File.separator + DateUtil.format(emailDate, DateConst.YYYY_MM_DD) + File.separator + fileName + ".html";
-                    File saveFile = new File(filePath);
-                    saveFile.setReadable(true);
-                    if (!saveFile.exists()) {
-                        if (!saveFile.getParentFile().exists()) {
-                            Files.createDirectories(saveFile.getParentFile().toPath());
-                            saveFile.getParentFile().setExecutable(true);
-                        }
-                    }
-                    FileUtil.writeFile(filePath, content.toString());
-                    emailContentInfoDTO.setFilePath(filePath);
-                    emailContentInfoDTO.setFileName(fileName);
-                    emailContentInfoDTOList.add(emailContentInfoDTO);
+
+                if (content instanceof Multipart multipart) {
+                    this.reMultipart(mailboxInfoDTO.getAccount(), emailTitle, emailDate, multipart, emailContentInfoDTOList);
+                } else if (content instanceof Part part) {
+                    this.rePart(mailboxInfoDTO.getAccount(), emailTitle, emailDate, part, emailContentInfoDTOList);
+                } else {
+                    log.warn("不支持的邮件数据 {}", emailTitle);
                 }
+
                 if (CollUtil.isNotEmpty(emailContentInfoDTOList)) {
-//                    // 估值表或定期报告邮件不展示正文html文件
-//                    if (emailType.equals(EmailTypeConst.VALUATION_EMAIL_TYPE) || emailType.equals(EmailTypeConst.REPORT_EMAIL_TYPE)) {
-//                        emailContentInfoDTOList = emailContentInfoDTOList.stream().filter(e -> !ExcelUtil.isHTML(e.getFilePath())).toList();
-//                    }
                     emailContentInfoDTOList.forEach(e -> {
                         e.setEmailType(emailType);
                         e.setSenderEmail(senderEmail);
                     });
                     emailMessageMap.put(uuidKey, emailContentInfoDTOList);
                 }
-                log.info("邮件{} 内容为:{}", emailTitle, emailContentInfoDTOList);
             } catch (Exception e) {
-                log.error("获取邮箱的邮件报错,堆栈信息:{}", ExceptionUtil.stacktraceToString(e));
+                log.error("获取邮箱的邮件{} 报错,堆栈信息:{}", emailTitle, ExceptionUtil.stacktraceToString(e));
             } finally {
-                if (log.isInfoEnabled()) {
-                    log.info("邮件{} 下载完成,总计耗时{} ms", emailTitle, System.currentTimeMillis() - start);
+                if (log.isInfoEnabled() && emailTitle != null) {
+                    log.info("邮件{} 下载完成,总计耗时{} ms,文件内容如下\n {}",
+                            emailTitle, System.currentTimeMillis() - start, emailContentInfoDTOList);
                 }
             }
         }
@@ -565,12 +554,63 @@ public class EmailParseService {
         return emailMessageMap;
     }
 
-    private String getSenderEmail(MimeMessage message) {
-        Address[] senderAddress = null;
+    private void rePart(String account, String subject, Date sendDate, Part part, List<EmailContentInfoDTO> emailContentInfoDTOList) throws Exception {
+        String disposition = part.getDisposition();
+        if (disposition != null && (disposition.equals(Part.ATTACHMENT) || disposition.equals(Part.INLINE))) {
+            String emailDate = DateUtil.format(sendDate, DateConst.YYYYMMDDHHMMSS24);
+            String emailDateStr = DateUtil.format(sendDate, DateConst.YYYYMMDD);
+            String filePath = path + File.separator + account + File.separator + emailDateStr + File.separator;
+
+            EmailContentInfoDTO emailContentInfoDTO = new EmailContentInfoDTO();
+
+            String fileName = MimeUtility.decodeText(part.getFileName());
+            emailContentInfoDTO.setFileName(fileName);
+            emailContentInfoDTO.setFileSize(part.getSize());
+
+            if (log.isInfoEnabled()) {
+                log.info("邮件{} 大小:{}byte 开始下载。。。。。", subject, part.getSize());
+            }
+
+            String realPath = filePath + emailDate + fileName;
+
+            File saveFile = cn.hutool.core.io.FileUtil.file(realPath);
+            if (!saveFile.exists()) {
+                if (!saveFile.getParentFile().exists()) {
+                    boolean mkdirs = saveFile.getParentFile().mkdirs();
+                    if (!mkdirs) {
+                        log.warn("file path mkdir failed.");
+                    }
+                }
+                FileUtil.saveFile(saveFile, part);
+            } else {
+                cn.hutool.core.io.FileUtil.del(saveFile);
+                FileUtil.saveFile(saveFile, part);
+            }
+            emailContentInfoDTO.setFilePath(saveFile.getAbsolutePath());
+
+            emailContentInfoDTO.setEmailAddress(account);
+            emailContentInfoDTO.setEmailTitle(subject);
+            emailContentInfoDTO.setEmailDate(DateUtil.format(sendDate, DateConst.YYYY_MM_DD_HH_MM_SS));
+            emailContentInfoDTOList.add(emailContentInfoDTO);
+        }
+    }
+
+    private void reMultipart(String account, String subject, Date emailDate, Multipart multipart, List<EmailContentInfoDTO> emailContentInfoDTOList) throws Exception {
+        for (int i = 0; i < multipart.getCount(); i++) {
+            Part bodyPart = multipart.getBodyPart(i);
+            if (bodyPart.getContent() instanceof Multipart mp) {
+                this.reMultipart(account, subject, emailDate, mp, emailContentInfoDTOList);
+            } else {
+                this.rePart(account, subject, emailDate, bodyPart, emailContentInfoDTOList);
+            }
+        }
+    }
+
+    private String getSenderEmail(Message message) {
+        Address[] senderAddress;
         try {
             senderAddress = message.getFrom();
             if (senderAddress == null || senderAddress.length == 0) {
-                log.info("发件人获取失败=============================");
                 return null;
             }
             // 此时的address是含有编码(MIME编码方式)后的文本和实际的邮件地址
@@ -581,24 +621,23 @@ public class EmailParseService {
                     break;
                 }
             }
-            log.info("发件人地址:" + address + "========================senderAddress size:" + senderAddress.length);
             // 正则表达式匹配邮件地址
             Pattern pattern = Pattern.compile("<(\\S+)>");
             Matcher matcher = pattern.matcher(address);
             if (matcher.find()) {
                 return matcher.group(1);
             }
-            //说明匹配不到,直接获取sender
-            Address sender = message.getSender();
-            if (sender == null) {
-                return address;
-            }
-            String senderEmail = sender.toString();
-            log.info("senderEmail:" + senderEmail + "====================");
-            if (senderEmail.contains("<") && senderEmail.contains(">") && senderEmail.indexOf("<") < senderEmail.indexOf(">")) {
-                senderEmail = senderEmail.substring(senderEmail.indexOf("<") + 1, senderEmail.length() - 1);
-            }
-            return senderEmail;
+//            //说明匹配不到,直接获取sender
+//            Address sender = message.getSender();
+//            if (sender == null) {
+//                return address;
+//            }
+//            String senderEmail = sender.toString();
+//            log.info("senderEmail:" + senderEmail + "====================");
+//            if (senderEmail.contains("<") && senderEmail.contains(">") && senderEmail.indexOf("<") < senderEmail.indexOf(">")) {
+//                senderEmail = senderEmail.substring(senderEmail.indexOf("<") + 1, senderEmail.length() - 1);
+//            }
+//            return senderEmail;
         } catch (MessagingException e) {
             log.error(e.getMessage(), e);
         }

+ 58 - 61
mo-daq/src/main/java/com/smppw/modaq/infrastructure/util/ExcelUtil.java

@@ -23,10 +23,19 @@ import java.io.*;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Arrays;
 import java.util.Enumeration;
 import java.util.List;
 
 public class ExcelUtil {
+    // 候选编码列表(按常见顺序排列)
+    private static final List<String> CANDIDATE_ENCODINGS = Arrays.asList(
+            "GBK",      // 中文环境常用
+            "UTF-8",   // 标准编码
+            "GB2312",  // 旧版中文
+            "ISO-8859-1" // 默认回退
+    );
+
     public static boolean isExcel(String fileName) {
         return StrUtil.isNotBlank(fileName) && (fileName.endsWith("xls") || fileName.endsWith("xlsx") || fileName.endsWith("XLS") || fileName.endsWith("XLSX"));
     }
@@ -55,26 +64,29 @@ public class ExcelUtil {
             Files.createDirectories(destFile.toPath());
         }
 
+        String encoding = detectEncoding(zipFilePath);
+        if (encoding == null) {
+            encoding = "GBK";
+        }
+
         try (BufferedInputStream fis = new BufferedInputStream(new FileInputStream(zipFilePath));
-             ArchiveInputStream<? extends ArchiveEntry> ais = new ArchiveStreamFactory().createArchiveInputStream(fis)) {
+             ArchiveInputStream<? extends ArchiveEntry> ais = new ArchiveStreamFactory()
+                     .createArchiveInputStream(ArchiveStreamFactory.detect(fis), fis, encoding)) {
             ArchiveEntry entry;
-            int i = 1;
             while ((entry = ais.getNextEntry()) != null) {
                 String name = entry.getName();
-                if (name.startsWith("__MACOSX/")) {
-                    continue;
-                }
-                String zipFilename = FileUtil.getName(destFilePath);
-                if (name.contains("确认")) {
-                    zipFilename += "_确认单";
-                }
-                String ext = FileUtil.extName(name);
-                name = zipFilename + "_" + i + "." + ext;
-                File entryFile = FileUtil.file(destFilePath, name);
-                i++;
                 if (entry.isDirectory()) {
+                    File entryFile = FileUtil.file(destFilePath, name);
                     Files.createDirectories(entryFile.toPath());
                 } else {
+                    if (name.startsWith("__MACOSX/")) {
+                        continue;
+                    }
+                    String zipFilename = FileUtil.getName(destFilePath);
+                    if (zipFilename.contains("确认")) {
+                        name = "确认单_" + name;
+                    }
+                    File entryFile = FileUtil.file(destFilePath, name);
                     try (FileOutputStream fos = new FileOutputStream(entryFile)) {
                         IOUtils.copy(ais, fos);
                         filePathList.add(entryFile.getPath());
@@ -85,7 +97,7 @@ public class ExcelUtil {
             if (e.getMessage() != null
                     && (e.getMessage().contains("split")
                     || e.getMessage().contains("volume"))) {
-                filePathList.addAll(extractSplitZip(zipFilePath, destFilePath));
+                filePathList.addAll(extractSplitZip(zipFilePath, destFilePath, encoding));
             } else {
                 throw e;
             }
@@ -94,52 +106,10 @@ public class ExcelUtil {
         return filePathList;
     }
 
-//    public static List<String> extractRar(String inputFilePath, String outputDirPath) throws IOException {
-//        List<String> fileList = new ArrayList<>();
-//        // 创建Archive对象,用于读取rar压缩文件格式
-//        try {
-//            String zipFilename = FileUtil.getName(inputFilePath);
-//            Archive archive = new Archive(new FileInputStream(inputFilePath));
-//            // 读取压缩文件中的所有子目录或子文件(FileHeader对象)
-//            List<FileHeader> fileHeaderList = archive.getFileHeaders();
-//            int i = 1;
-//            // 遍历子目录和子文件
-//            for (FileHeader fd : fileHeaderList) {
-//                String fileName = fd.getFileName();
-//                String ext = FileUtil.extName(fileName);
-//                fileName = zipFilename + "_" + i + "." + ext;
-//                i++;
-//                File f = FileUtil.file(outputDirPath + File.separator + fileName);
-//                if (fd.isDirectory()) {
-//                    // 创建新子目录
-//                    Files.createDirectories(f.toPath());
-//                } else {
-//                    // 创建新子文件
-//                    Files.createFile(f.toPath());
-//                    // 获取压缩包中的子文件输出流
-//                    InputStream in = archive.getInputStream(fd);
-//                    // 复制文件输入流至新子文件
-//                    FileUtil.copyFile(in, f);
-//                    fileList.add(f.getAbsolutePath());
-//                }
-//            }
-//        } catch (RarException e) {
-//            try {
-//                List<File> extract = Junrar.extract(new File(inputFilePath), new File(outputDirPath));
-//                for (File file : extract) {
-//                    fileList.add(file.getAbsolutePath());
-//                }
-//            } catch (RarException ex) {
-//                throw new RuntimeException(ex);
-//            }
-//        }
-//        return fileList;
-//    }
-
-    public static List<String> extractSplitZip(String zipFilePath, String destFilePath) throws IOException {
+    public static List<String> extractSplitZip(String zipFilePath, String destFilePath, String encoding) throws IOException {
         List<String> resultList = ListUtil.list(false);
         File file = new File(zipFilePath);
-        try (ZipFile zipFile = ZipFile.builder().setFile(file).get()) {
+        try (ZipFile zipFile = ZipFile.builder().setFile(file).setCharset(encoding).get()) {
             Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
             while (entries.hasMoreElements()) {
                 ZipArchiveEntry entry = entries.nextElement();
@@ -189,7 +159,7 @@ public class ExcelUtil {
 
     private static String extractItem(ISimpleInArchiveItem item, String outputDir) throws SevenZipException {
         String filePath = outputDir + File.separator + item.getPath();
-        File outputFile = new File(filePath);
+        File outputFile = FileUtil.file(filePath);
 
         // 创建父目录
         File parentDir = outputFile.getParentFile();
@@ -214,7 +184,34 @@ public class ExcelUtil {
         } catch (IOException e) {
             throw new SevenZipException("文件操作失败", e);
         }
-        return filePath;
+        return outputFile.getAbsolutePath();
+    }
+
+    // 检测压缩包编码
+    private static String detectEncoding(String zipPath) {
+        for (String encoding : CANDIDATE_ENCODINGS) {
+            try (BufferedInputStream fis = new BufferedInputStream(new FileInputStream(zipPath));
+                 ArchiveInputStream<? extends ArchiveEntry> ais = new ArchiveStreamFactory()
+                         .createArchiveInputStream(ArchiveStreamFactory.detect(fis), fis, encoding)) {
+
+                ArchiveEntry entry = ais.getNextEntry();
+                if (entry == null) continue; // 空压缩包
+
+                String fileName = entry.getName();
+                if (!hasInvalidCharacters(fileName)) {
+                    return encoding; // 找到有效编码
+                }
+            } catch (Exception e) {
+                // 编码不支持或文件错误,继续尝试下一个
+            }
+        }
+        return null;
+    }
+
+    // 检查文件名是否包含无效字符(如替换符)
+    private static boolean hasInvalidCharacters(String fileName) {
+        // 检查常见乱码符号:�或连续问号
+        return fileName.contains("�") || fileName.matches(".*\\?{2,}.*");
     }
 
     public static void main(String[] args) throws Exception {
@@ -222,7 +219,7 @@ public class ExcelUtil {
         String destFilePath = "D:\\home\\wwwroot\\mo_report_file\\wangzaijun@simuwang.com\\20250321";
         List<String> strings = extractRar5(zipFilePath, destFilePath);
         for (String string : strings) {
-
+            System.out.println(string);
         }
 //        List<String> fileList = extractCompressedFiles(zipFilePath, destFilePath);
 //        for (String s : fileList) {

+ 3 - 8
mo-daq/src/main/java/com/smppw/modaq/infrastructure/util/FileUtil.java

@@ -6,6 +6,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.*;
+import java.nio.file.Files;
 
 public class FileUtil {
 
@@ -56,14 +57,8 @@ public class FileUtil {
     }
 
     public static void saveFile(File saveFile, Part part) throws Exception {
-        BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream(saveFile) );
-        byte[] buff = new byte[2048];
-        InputStream is = part.getInputStream();
-        int ret = 0;
-        while( (ret = is.read(buff)) > 0 ){
-            bos.write(buff, 0, ret);
+        try (InputStream is = part.getInputStream()) {
+            Files.copy(is, saveFile.toPath());
         }
-        bos.close();
-        is.close();
     }
 }

+ 1 - 1
mo-daq/src/main/resources/logback.xml

@@ -2,7 +2,7 @@
 <configuration debug="true">
     <!-- 日志格式:年-月-日 时:分:秒 [线程] 日志级别 所在类:行号 - 具体信息 换行 -->
     <property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] - [%X{x-api-requestid}] %-5level %logger{50}:%-4L - %msg%n"/>
-    <property name="LOG_HOME" value="./logs"/>
+    <property name="LOG_HOME" value="/home/wwwroot/modaq/logs"/>
 
     <!--输出到控制台-->
     <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">

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

@@ -12,6 +12,7 @@
         <result column="email_type" property="emailType"/>
         <result column="parse_status" property="parseStatus"/>
         <result column="fail_reason" property="failReason"/>
+        <result column="attr_size" property="attrSize"/>
         <result column="isvalid" property="isvalid"/>
         <result column="creatorid" property="creatorId"/>
         <result column="createtime" property="createTime"/>
@@ -23,9 +24,9 @@
 
     <insert id="insert" 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,
-                                     isvalid, creatorid, createtime, updaterid, updatetime)
+                                        attr_size,isvalid, creatorid, createtime, updaterid, updatetime)
         values (#{itemDo.email}, #{itemDo.emailKey}, #{itemDo.senderEmail}, #{itemDo.emailDate}, #{itemDo.parseDate}, #{itemDo.emailTitle}, #{itemDo.emailType}, #{itemDo.parseStatus},
-                #{itemDo.failReason}, #{itemDo.isvalid}, #{itemDo.creatorId}, #{itemDo.createTime}, #{itemDo.updaterId}, #{itemDo.updateTime})
+                #{itemDo.failReason}, #{itemDo.attrSize}, #{itemDo.isvalid}, #{itemDo.creatorId}, #{itemDo.createTime}, #{itemDo.updaterId}, #{itemDo.updateTime})
     </insert>
 
     <update id="updateParseStatus">
@@ -46,6 +47,7 @@
             epi.email_type,
             epi.parse_status,
             epi.fail_reason,
+            epi.attr_size,
             epi.isvalid,
             epi.creatorid,
             epi.createtime,
@@ -129,7 +131,7 @@
     </select>
 
     <select id="queryById" resultMap="BaseResultMap">
-        select id, email,email_key, email_date, parse_date, email_title, email_type, sender_email,parse_status
+        select id, email,email_key, email_date, parse_date, email_title, email_type, sender_email,parse_status,attr_size
         from mo_email_parse_info
         where isvalid = 1
           and id = #{id}

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

@@ -34,9 +34,9 @@ public class MoDaqApplicationTests {
 
     @Test
     public void reportTest() {
-        MailboxInfoDTO emailInfoDTO = this.buildMailbox("xx@simuwang.com", "**");
-        Date startDate = DateUtil.parse("2025-04-15 14:40:00", DateConst.YYYY_MM_DD_HH_MM_SS);
-        Date endDate = DateUtil.parse("2025-04-15 19:42:05", DateConst.YYYY_MM_DD_HH_MM_SS);
+        MailboxInfoDTO emailInfoDTO = this.buildMailbox("**@simuwang.com", "**");
+        Date startDate = DateUtil.parse("2025-04-24 08:40:00", DateConst.YYYY_MM_DD_HH_MM_SS);
+        Date endDate = DateUtil.parse("2025-04-24 19:42:05", DateConst.YYYY_MM_DD_HH_MM_SS);
         try {
             emailParseService.parseEmail(emailInfoDTO, startDate, endDate);
         } catch (Exception e) {