Browse Source

feat:提供对外获取表格接口及修改基金净值缺失表格输出字段样式

mozuwen 5 months ago
parent
commit
5f28230665

+ 9 - 2
service-base/src/main/java/com/simuwang/base/pojo/dto/FundNavDeletionDTO.java

@@ -46,10 +46,10 @@ public class FundNavDeletionDTO {
     private String registerNumber;
 
     /**
-     * 缺失年
+     * 缺失年
      */
     @ColumnWidth(15)
-    @ExcelProperty("缺失年")
+    @ExcelProperty("缺失年")
     private String year;
 
     /**
@@ -57,6 +57,7 @@ public class FundNavDeletionDTO {
      */
     @ColumnWidth(15)
     @ExcelProperty("缺失周数")
+    @ExcelIgnore
     private String week;
 
     /**
@@ -66,4 +67,10 @@ public class FundNavDeletionDTO {
     @ExcelProperty("缺失日期")
     private String date;
 
+    /**
+     * 缺失周
+     */
+    @ColumnWidth(30)
+    @ExcelProperty("缺失周")
+    private String competitionDate;
 }

+ 17 - 0
service-base/src/main/java/com/simuwang/base/pojo/dto/TradeDateDTO.java

@@ -0,0 +1,17 @@
+package com.simuwang.base.pojo.dto;
+
+import lombok.Data;
+
+@Data
+public class TradeDateDTO {
+
+    private String year;
+
+    private Integer week;
+
+    private Integer competitionWeek;
+
+    private String firstDate;
+
+    private String lastDate;
+}

+ 0 - 2
service-calc/src/main/java/com/simuwang/task/CompetitionIndicatorCalcTask.java

@@ -71,8 +71,6 @@ public class CompetitionIndicatorCalcTask {
         this.competitionIndicatorCalcService = competitionIndicatorCalcService;
     }
 
-    @Scheduled(cron = "0 0 17 * * ?")
-    //@Scheduled(cron = "0/10 * * * * ?")
     public void runCalcIndicatorTask() {
         Integer competitionId = competitionConfig.getId();
         // 判断是否要计算基金指标

+ 37 - 0
service-manage/src/main/java/com/simuwang/manage/api/competition/CompetitionController.java

@@ -0,0 +1,37 @@
+package com.simuwang.manage.api.competition;
+
+import com.simuwang.manage.task.CompetitionTask;
+import com.smppw.common.pojo.ResultVo;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/v1/competition")
+public class CompetitionController {
+
+    private final CompetitionTask competitionTask;
+
+    public CompetitionController(CompetitionTask competitionTask) {
+        this.competitionTask = competitionTask;
+    }
+
+
+    /**
+     * 获取大赛结果
+     *
+     * @param type          结果类型:1-基金净值明细,2-基金净值缺失明细,3-榜单解析明细
+     * @param competitionId 大赛Id
+     * @param period        榜单周期
+     * @return 大赛结果
+     */
+    @GetMapping("/result")
+    public ResultVo<String> getResult(@RequestParam(value = "type") Integer type,
+                                      @RequestParam(value = "competitionId", required = false) Integer competitionId,
+                                      @RequestParam(value = "period", required = false) String period) {
+        return ResultVo.ok(competitionTask.getResult(type, competitionId, period));
+    }
+
+
+}

+ 34 - 24
service-manage/src/main/java/com/simuwang/manage/service/competition/FundNavService.java

@@ -1,7 +1,6 @@
 package com.simuwang.manage.service.competition;
 
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.collection.ListUtil;
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.map.MapUtil;
 import com.simuwang.base.common.conts.DateConst;
@@ -10,6 +9,7 @@ import com.simuwang.base.mapper.*;
 import com.simuwang.base.pojo.dos.*;
 import com.simuwang.base.pojo.dto.FundNavDataDTO;
 import com.simuwang.base.pojo.dto.FundNavDeletionDTO;
+import com.simuwang.base.pojo.dto.TradeDateDTO;
 import com.simuwang.base.pojo.dto.competition.CompetitionBaseResultDTO;
 import org.springframework.stereotype.Service;
 
@@ -30,24 +30,18 @@ public class FundNavService {
     private final DeletionInfoMapper deletionInfoMapper;
     private final TradeDateMapper tradeDateMapper;
     private final CompetitionFactory competitionFactory;
-    private final RcCompetitionTaskDateMapper competitionTaskDateMapper;
-    private final RcCompetitionCalcDateMapper competitionCalcDateMapper;
 
     public FundNavService(NavMapper navMapper, FundInfoMapper fundInfoMapper,
                           AssetMapper assetMapper, DeletionInfoMapper deletionInfoMapper,
-                          TradeDateMapper tradeDateMapper, CompetitionFactory competitionFactory,
-                          RcCompetitionTaskDateMapper competitionTaskDateMapper, RcCompetitionCalcDateMapper competitionCalcDateMapper) {
+                          TradeDateMapper tradeDateMapper, CompetitionFactory competitionFactory) {
         this.navMapper = navMapper;
         this.fundInfoMapper = fundInfoMapper;
         this.assetMapper = assetMapper;
         this.deletionInfoMapper = deletionInfoMapper;
         this.tradeDateMapper = tradeDateMapper;
         this.competitionFactory = competitionFactory;
-        this.competitionTaskDateMapper = competitionTaskDateMapper;
-        this.competitionCalcDateMapper = competitionCalcDateMapper;
     }
 
-
     public List<FundNavDataDTO> getFungNavData(Integer competitionId) {
         List<FundNavDataDTO> navDataDTOList = CollUtil.newArrayList();
         List<NavDO> navDOList = navMapper.queryAllNav(competitionId);
@@ -55,7 +49,7 @@ public class FundNavService {
             return navDataDTOList;
         }
         // 过滤出周频的净值点
-        String toDay = DateUtil.format(new Date(),DateConst.YYYY_MM_DD);
+        String toDay = DateUtil.format(new Date(), DateConst.YYYY_MM_DD);
         List<TradeDateDO> tradeDateDOList = tradeDateMapper.listAllTradeDate(toDay);
         navDOList = NavDataUtil.filterWeekFrequencyNav(navDOList, tradeDateDOList);
 
@@ -86,12 +80,12 @@ public class FundNavService {
         if (CollUtil.isEmpty(deletionInfoDOList)) {
             return navDeletionDTOList;
         }
-        List<String> fundIdList = deletionInfoDOList.stream().map(FundDeletionInfoDO::getFundId).distinct().toList();
+
         List<FundAndCompanyInfoDO> fundInfoCompanyNameList = fundInfoMapper.queryFundAndTrustByFundId(competitionId);
         Map<String, FundAndCompanyInfoDO> fundIdCompanyNameMap = fundInfoCompanyNameList.stream().collect(Collectors.toMap(FundAndCompanyInfoDO::getFundId, v -> v));
         // 获取净值日期所在年和所在周
         List<String> dateList = deletionInfoDOList.stream().map(FundDeletionInfoDO::getDeletionDate).distinct().toList();
-        Map<String, TradeDateDO> tradeDateDoMap = getYearAndWeekOfDate(dateList);
+        Map<String, TradeDateDTO> tradeDateDoMap = getYearAndWeekOfDate(startDate, endDate, dateList);
         Map<String, List<FundDeletionInfoDO>> fundIdDeletionInfoMap = deletionInfoDOList.stream().collect(Collectors.groupingBy(FundDeletionInfoDO::getFundId));
         for (Map.Entry<String, List<FundDeletionInfoDO>> fundIdDeletionInfoEntry : fundIdDeletionInfoMap.entrySet()) {
             String fundId = fundIdDeletionInfoEntry.getKey();
@@ -114,7 +108,7 @@ public class FundNavService {
         return competitionResult.getCompetitionResult(competitionId, period);
     }
 
-    private List<FundNavDeletionDTO> buildFundNavDeletionDTO(FundAndCompanyInfoDO fundAndCompanyInfoDO, List<FundDeletionInfoDO> fundDeletionInfoDOList, Map<String, TradeDateDO> tradeDateDoMap) {
+    private List<FundNavDeletionDTO> buildFundNavDeletionDTO(FundAndCompanyInfoDO fundAndCompanyInfoDO, List<FundDeletionInfoDO> fundDeletionInfoDOList, Map<String, TradeDateDTO> tradeDateDoMap) {
         String fundName = fundAndCompanyInfoDO.getFundName();
         String trustName = fundAndCompanyInfoDO.getCompanyName();
         String trustRegisterNumber = fundAndCompanyInfoDO.getCompanyRegisterNumber();
@@ -126,12 +120,15 @@ public class FundNavService {
             fundNavDeletionDTO.setFundId(e.getFundId());
             fundNavDeletionDTO.setFundName(fundName);
             fundNavDeletionDTO.setRegisterNumber(registerNumber);
-            TradeDateDO tradeDateDO = tradeDateDoMap.get(e.getDeletionDate());
-            fundNavDeletionDTO.setYear(tradeDateDO != null ? tradeDateDO.getEndYear() : null);
-            fundNavDeletionDTO.setWeek(tradeDateDO != null ? String.valueOf(tradeDateDO.getWeekOfYear()) : null);
+            TradeDateDTO tradeDateDTO = tradeDateDoMap.get(e.getDeletionDate());
+            fundNavDeletionDTO.setYear(tradeDateDTO != null ? tradeDateDTO.getYear() + tradeDateDTO.getWeek() : null);
+            fundNavDeletionDTO.setWeek(tradeDateDTO != null ? String.valueOf(tradeDateDTO.getWeek()) : null);
+            // 第1周(2022-12-26至2022-12-30)
+            String date = "第" + tradeDateDTO.getCompetitionWeek() + "周(" + tradeDateDTO.getFirstDate() + "至" + tradeDateDTO.getLastDate() + ")";
+            fundNavDeletionDTO.setCompetitionDate(date);
             fundNavDeletionDTO.setDate(e.getDeletionDate());
             return fundNavDeletionDTO;
-        }).sorted(Comparator.comparing(FundNavDeletionDTO::getFundName).thenComparing(FundNavDeletionDTO::getDate)).toList();
+        }).sorted(Comparator.comparing(FundNavDeletionDTO::getFundName).thenComparing(FundNavDeletionDTO::getWeek)).toList();
     }
 
     private List<FundNavDataDTO> buildFundNavDataDTO(FundAndCompanyInfoDO fundInfoDO, List<NavDO> fundNavDoList, List<AssetDO> fundAssetDoList) {
@@ -161,17 +158,30 @@ public class FundNavService {
         }).sorted(Comparator.comparing(FundNavDataDTO::getFundName).thenComparing(FundNavDataDTO::getPriceDate)).toList();
     }
 
-    private Map<String, TradeDateDO> getYearAndWeekOfDate(List<String> dateList) {
-        Map<String, TradeDateDO> tradeDateDoMap = MapUtil.newHashMap();
-        if (CollUtil.isEmpty(dateList)) {
+    private Map<String, TradeDateDTO> getYearAndWeekOfDate(String startDate, String endDate, List<String> dateList) {
+        Map<String, TradeDateDTO> tradeDateDoMap = MapUtil.newHashMap();
+        List<TradeDateDO> tradeDateDOList = tradeDateMapper.selectTradeDate(startDate, endDate);
+        if (CollUtil.isEmpty(tradeDateDOList)) {
             return tradeDateDoMap;
         }
-        List<TradeDateDO> tradeDateDOList = CollUtil.newArrayList();
-        List<List<String>> partition = ListUtil.partition(dateList, 500);
-        partition.forEach(e -> tradeDateDOList.addAll(tradeDateMapper.queryByDate(dateList)));
-        if (CollUtil.isNotEmpty(tradeDateDOList)) {
-            tradeDateDoMap = tradeDateDOList.stream().collect(Collectors.toMap(k -> DateUtil.format(k.getTradeDate(), DateConst.YYYY_MM_DD), v -> v));
+        List<Integer> yearWeekList = tradeDateDOList.stream().map(e -> e.getYearWeek()).distinct().toList();
+        Map<String, TradeDateDO> dateMap = tradeDateDOList.stream().collect(Collectors.toMap(k -> DateUtil.format(k.getTradeDate(), DateConst.YYYY_MM_DD), v -> v));
+        Map<Integer, List<TradeDateDO>> yearWeekDateMap = tradeDateDOList.stream().collect(Collectors.groupingBy(TradeDateDO::getYearWeek));
+        for (String date : dateList) {
+            TradeDateDO tradeDateDO = dateMap.get(date);
+            Integer yearWeek = tradeDateDO.getYearWeek();
+            TradeDateDTO tradeDateDTO = new TradeDateDTO();
+            tradeDateDTO.setWeek(tradeDateDO.getWeekOfYear());
+            tradeDateDTO.setYear(tradeDateDO.getEndYear());
+            String firstDate = yearWeekDateMap.get(yearWeek).stream()
+                    .min(Comparator.comparing(TradeDateDO::getTradeDate)).map(e -> DateUtil.format(e.getTradeDate(), DateConst.YYYY_MM_DD)).orElse(null);
+            tradeDateDTO.setFirstDate(firstDate);
+            tradeDateDTO.setLastDate(date);
+            int competitionWeek = (int) yearWeekList.stream().filter(e -> e.compareTo(yearWeek) < 0).count() + 1;
+            tradeDateDTO.setCompetitionWeek(competitionWeek);
+            tradeDateDoMap.put(date, tradeDateDTO);
         }
+
         return tradeDateDoMap;
     }
 }

+ 123 - 76
service-manage/src/main/java/com/simuwang/manage/task/CompetitionTask.java

@@ -1,6 +1,7 @@
 package com.simuwang.manage.task;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.ListUtil;
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.exceptions.ExceptionUtil;
 import cn.hutool.core.util.StrUtil;
@@ -21,7 +22,9 @@ import com.simuwang.manage.service.competition.FundNavService;
 import com.simuwang.task.CompetitionIndicatorCalcTask;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Component;
 
 import java.io.File;
@@ -42,10 +45,12 @@ public class CompetitionTask {
     private final RcCompetitionCalcDateMapper competitionCalcDateMapper;
     private final FundDeletionTask fundDeletionTask;
     private final CompetitionIndicatorCalcTask competitionIndicatorCalcTask;
+    private final ThreadPoolTaskExecutor asyncExecutor;
 
     public CompetitionTask(FundNavService fundNavService, CompetitionConfig competitionConfig,
                            EmailSystemConfigService emailSystemConfigService, RcCompetitionTaskDateMapper competitionTaskDateMapper,
-                           RcCompetitionCalcDateMapper competitionCalcDateMapper, FundDeletionTask fundDeletionTask, CompetitionIndicatorCalcTask competitionIndicatorCalcTask) {
+                           RcCompetitionCalcDateMapper competitionCalcDateMapper, FundDeletionTask fundDeletionTask,
+                           CompetitionIndicatorCalcTask competitionIndicatorCalcTask, @Qualifier("asyncExecutor") ThreadPoolTaskExecutor asyncExecutor) {
         this.fundNavService = fundNavService;
         this.competitionConfig = competitionConfig;
         this.emailSystemConfigService = emailSystemConfigService;
@@ -53,8 +58,29 @@ public class CompetitionTask {
         this.competitionCalcDateMapper = competitionCalcDateMapper;
         this.fundDeletionTask = fundDeletionTask;
         this.competitionIndicatorCalcTask = competitionIndicatorCalcTask;
+        this.asyncExecutor = asyncExecutor;
     }
 
+    public String getResult(Integer type, Integer competitionId, String period) {
+        asyncExecutor.execute(() -> {
+            if (type == 1) {
+                outputFundNavDetail(competitionId, period);
+            } else if (type == 2) {
+                List<RcCompetitionCalcDateDO> calcDateDOList = competitionCalcDateMapper.queryByPriod(ListUtil.toList(period), competitionId);
+                if (CollUtil.isNotEmpty(calcDateDOList)) {
+                    outputFundNavDeletion(competitionId, period, calcDateDOList.get(0).getBeginDate(), calcDateDOList.get(0).getEndDate());
+                }
+            } else if (type == 3) {
+                List<RcCompetitionCalcDateDO> calcDateDOList = competitionCalcDateMapper.queryByPriod(ListUtil.toList(period), competitionId);
+                if (CollUtil.isNotEmpty(calcDateDOList)) {
+                    outputCompetitionResult(competitionId, period, calcDateDOList.get(0));
+                }
+            }
+        });
+        return "操作成功,请等到结果输出...";
+    }
+
+
     /**
      * 输出基金净值采集结果明细任务
      * 每天10:00,15:00执行任务
@@ -69,30 +95,7 @@ public class CompetitionTask {
         }
         for (RcCompetitionTaskDateDO taskDateDO : taskDateDOList) {
             String period = taskDateDO.getPriod();
-            List<FundNavDataDTO> navDataDTOList = fundNavService.getFungNavData(competitionId);
-            if (CollUtil.isEmpty(navDataDTOList)) {
-                return;
-            }
-            String filePath = competitionConfig.getDirectory() + competitionConfig.getName() + "_" + period + "_榜单_基金净值采集结果明细_" + DateUtil.format(new Date(), DateConst.YYYYMMDD) + ".xlsx";
-            EasyExcel.write(filePath, FundNavDataDTO.class)
-                    .sheet("基金净值采集结果明细")
-                    .doWrite(navDataDTOList);
-            log.info("基金净值采集结果明细表格已输出 -> 文件:{}", filePath);
-
-            // 通过邮件发送"基金净值采集结果明细表格"
-            if (competitionConfig.getMethod() == 1) {
-                MailboxInfoDTO mailboxInfoDTO = emailSystemConfigService.getFromEmailInfo();
-                String receivingMailbox = emailSystemConfigService.getRecipientEmail();
-                if (mailboxInfoDTO == null || StrUtil.isBlank(receivingMailbox)) {
-                    continue;
-                }
-                try {
-                    EmailUtil.senEmail(mailboxInfoDTO, receivingMailbox, new File(filePath), "基金净值采集结果明细", "", period + "基金净值采集结果明细");
-                    log.info("基金净值采集结果明细表 -> 邮件已发送");
-                } catch (Exception e) {
-                    log.error("邮件发送基金净值采集结果明细表异常 -> 堆栈信息:{}", ExceptionUtil.stacktraceToString(e));
-                }
-            }
+            outputFundNavDetail(competitionId, period);
         }
     }
 
@@ -108,34 +111,20 @@ public class CompetitionTask {
         if (CollUtil.isEmpty(taskDateDOList)) {
             return;
         }
+        // 输出之前 -> 执行净值缺失检测(确保净值不全的基金不参与榜单)
+        fundDeletionTask.computeDeletion();
+
         List<String> periodList = taskDateDOList.stream().map(RcCompetitionTaskDateDO::getPriod).distinct().toList();
         List<RcCompetitionCalcDateDO> calcDateDOList = competitionCalcDateMapper.queryByPriod(periodList, competitionId);
         Map<String, RcCompetitionCalcDateDO> periodCalcDataDoMap = calcDateDOList.stream().collect(Collectors.toMap(RcCompetitionCalcDateDO::getPriod, v -> v));
         for (RcCompetitionTaskDateDO taskDateDO : taskDateDOList) {
-            String period = taskDateDO.getPriod();
-            RcCompetitionCalcDateDO calcDateDO = periodCalcDataDoMap.get(period);
-            List<FundNavDeletionDTO> navDeletionDTOList = fundNavService.getFundNavDeletion(competitionId, calcDateDO.getBeginDate(), calcDateDO.getEndDate());
-            if (CollUtil.isEmpty(navDeletionDTOList)) {
-                return;
-            }
-            String filePath = competitionConfig.getDirectory() + period + "_榜单_基金净值缺失明细_" + DateUtil.format(new Date(), DateConst.YYYYMMDD) + ".xlsx";
-            EasyExcel.write(filePath, FundNavDeletionDTO.class)
-                    .sheet("基金净值缺失明细")
-                    .doWrite(navDeletionDTOList);
-            // 通过邮件发送"基金净值采集结果明细表格"
-            if (competitionConfig.getMethod() == 1) {
-                MailboxInfoDTO mailboxInfoDTO = emailSystemConfigService.getFromEmailInfo();
-                String receivingMailbox = emailSystemConfigService.getRecipientEmail();
-                if (mailboxInfoDTO == null || StrUtil.isBlank(receivingMailbox)) {
-                    continue;
-                }
-                try {
-                    EmailUtil.senEmail(mailboxInfoDTO, receivingMailbox, new File(filePath), "基金净值缺失明细", "", period + "_榜单_基金净值缺失明细");
-                    log.info("基金净值缺失明细表 -> 邮件已发送");
-                } catch (Exception e) {
-                    log.error("邮件发送基金净值缺失明细表异常 -> 堆栈信息:{}", ExceptionUtil.stacktraceToString(e));
-                }
+            RcCompetitionCalcDateDO calcDateDO = periodCalcDataDoMap.get(taskDateDO.getPriod());
+            if (calcDateDO == null) {
+                continue;
             }
+            String beginDate = calcDateDO.getBeginDate();
+            String endDate = calcDateDO.getEndDate();
+            outputFundNavDeletion(competitionId, taskDateDO.getPriod(), beginDate, endDate);
         }
     }
 
@@ -151,45 +140,103 @@ public class CompetitionTask {
         if (CollUtil.isEmpty(taskDateDOList)) {
             return;
         }
-        // 输出榜单之前 -> 执行净值缺失检测(确保净值不全的基金不参与榜单)
-        fundDeletionTask.computeDeletion();
         List<String> periodList = taskDateDOList.stream().map(RcCompetitionTaskDateDO::getPriod).distinct().collect(Collectors.toList());
         List<RcCompetitionCalcDateDO> calcDateDOList = competitionCalcDateMapper.queryByPriod(periodList, competitionId);
         Map<String, RcCompetitionCalcDateDO> periodCalcDataDoMap = calcDateDOList.stream().collect(Collectors.toMap(RcCompetitionCalcDateDO::getPriod, v -> v));
         for (RcCompetitionTaskDateDO taskDateDO : taskDateDOList) {
             String period = taskDateDO.getPriod();
             RcCompetitionCalcDateDO calcDateDO = periodCalcDataDoMap.get(period);
-            // 输出榜单之前 -> 执行基金净值指标计算
-            competitionIndicatorCalcTask.calculateIndicator(competitionId, calcDateDO);
+            outputCompetitionResult(competitionId, period, calcDateDO);
+        }
+    }
 
-            List<? extends CompetitionBaseResultDTO> competitionResultDTOList = fundNavService.getCompetitionResult(competitionId, period);
-            if (CollUtil.isEmpty(competitionResultDTOList)) {
+    public void outputFundNavDetail(Integer competitionId, String period) {
+        List<FundNavDataDTO> navDataDTOList = fundNavService.getFungNavData(competitionId);
+        if (CollUtil.isEmpty(navDataDTOList)) {
+            return;
+        }
+        String filePath = competitionConfig.getDirectory() + competitionConfig.getName() + "_" + period + "_榜单_基金净值采集结果明细_" + DateUtil.format(new Date(), DateConst.YYYYMMDD) + ".xlsx";
+        EasyExcel.write(filePath, FundNavDataDTO.class)
+                .sheet("基金净值采集结果明细")
+                .doWrite(navDataDTOList);
+        log.info("基金净值采集结果明细表格已输出 -> 文件:{}", filePath);
+
+        // 通过邮件发送"基金净值采集结果明细表格"
+        if (competitionConfig.getMethod() == 1) {
+            MailboxInfoDTO mailboxInfoDTO = emailSystemConfigService.getFromEmailInfo();
+            String receivingMailbox = emailSystemConfigService.getRecipientEmail();
+            if (mailboxInfoDTO == null || StrUtil.isBlank(receivingMailbox)) {
+                log.info("未配置发件箱和收件箱,无法发送邮件");
                 return;
             }
-            Class<? extends CompetitionBaseResultDTO> aClass = competitionResultDTOList.get(0).getClass();
-            String filePath = competitionConfig.getDirectory() + competitionConfig.getName() + "_" + period + "_榜单_基金净值衍生指标及排名结果明细_" + DateUtil.format(new Date(), DateConst.YYYYMMDD) + ".xlsx";
-            EasyExcel.write(filePath, aClass)
-                    .sheet("基金净值衍生指标及排名结果明细")
-                    .doWrite(competitionResultDTOList);
-            log.info("基金净值衍生指标及排名结果明细表已输出 -> 文件:{}", filePath);
-
-            Integer method = competitionConfig.getMethod();
-            // 通过邮件发送"基金净值采集结果明细表格"
-            if (method == 1) {
-                MailboxInfoDTO mailboxInfoDTO = emailSystemConfigService.getFromEmailInfo();
-                String receivingMailbox = emailSystemConfigService.getRecipientEmail();
-                if (mailboxInfoDTO == null || StrUtil.isBlank(receivingMailbox)) {
-                    continue;
-                }
-                try {
-                    String emailTitle = period + "_榜单_基金净值衍生指标及排名结果明细";
-                    EmailUtil.senEmail(mailboxInfoDTO, receivingMailbox, new File(filePath), emailTitle, "", emailTitle);
-                    log.info("基金净值衍生指标及排名结果明细表 -> 邮件已发送");
-                } catch (Exception e) {
-                    log.error("邮件发送基金净值衍生指标及排名结果明细表异常 -> 堆栈信息:{}", ExceptionUtil.stacktraceToString(e));
-                }
+            try {
+                EmailUtil.senEmail(mailboxInfoDTO, receivingMailbox, new File(filePath), "基金净值采集结果明细", "", period + "基金净值采集结果明细");
+                log.info("基金净值采集结果明细表 -> 邮件已发送");
+            } catch (Exception e) {
+                log.error("邮件发送基金净值采集结果明细表异常 -> 堆栈信息:{}", ExceptionUtil.stacktraceToString(e));
             }
         }
+    }
 
+    private void outputFundNavDeletion(Integer competitionId, String period, String beginDate, String endDate) {
+        List<FundNavDeletionDTO> navDeletionDTOList = fundNavService.getFundNavDeletion(competitionId, beginDate, endDate);
+        if (CollUtil.isEmpty(navDeletionDTOList)) {
+            return;
+        }
+        String filePath = competitionConfig.getDirectory() + competitionConfig.getName() + "_" + period + "_榜单_基金净值缺失明细_" + DateUtil.format(new Date(), DateConst.YYYYMMDD) + ".xlsx";
+        EasyExcel.write(filePath, FundNavDeletionDTO.class)
+                .sheet("基金净值缺失明细")
+                .doWrite(navDeletionDTOList);
+        // 通过邮件发送"基金净值采集结果明细表格"
+        if (competitionConfig.getMethod() == 1) {
+            MailboxInfoDTO mailboxInfoDTO = emailSystemConfigService.getFromEmailInfo();
+            String receivingMailbox = emailSystemConfigService.getRecipientEmail();
+            if (mailboxInfoDTO == null || StrUtil.isBlank(receivingMailbox)) {
+                log.info("未配置发件箱和收件箱,无法发送邮件");
+                return;
+            }
+            try {
+                EmailUtil.senEmail(mailboxInfoDTO, receivingMailbox, new File(filePath), "基金净值缺失明细", "", period + "_榜单_基金净值缺失明细");
+                log.info("基金净值缺失明细表 -> 邮件已发送");
+            } catch (Exception e) {
+                log.error("邮件发送基金净值缺失明细表异常 -> 堆栈信息:{}", ExceptionUtil.stacktraceToString(e));
+            }
+        }
+    }
+
+    private void outputCompetitionResult(Integer competitionId, String period, RcCompetitionCalcDateDO calcDateDO) {
+        long startTime = System.currentTimeMillis();
+        // 输出榜单之前 -> 执行净值缺失检测(确保净值不全的基金不参与榜单)
+        fundDeletionTask.computeDeletion();
+        // 输出榜单之前 -> 执行基金净值指标计算
+        competitionIndicatorCalcTask.calculateIndicator(competitionId, calcDateDO);
+        List<? extends CompetitionBaseResultDTO> competitionResultDTOList = fundNavService.getCompetitionResult(competitionId, period);
+        if (CollUtil.isEmpty(competitionResultDTOList)) {
+            return;
+        }
+        Class<? extends CompetitionBaseResultDTO> aClass = competitionResultDTOList.get(0).getClass();
+        String filePath = competitionConfig.getDirectory() + competitionConfig.getName() + "_" + period + "_榜单_基金净值衍生指标及排名结果明细_" + DateUtil.format(new Date(), DateConst.YYYYMMDD) + ".xlsx";
+        EasyExcel.write(filePath, aClass)
+                .sheet("基金净值衍生指标及排名结果明细")
+                .doWrite(competitionResultDTOList);
+        log.info("基金净值衍生指标及排名结果明细表已输出 -> 文件:{},耗时:{}", filePath, (System.currentTimeMillis() - startTime));
+
+        Integer method = competitionConfig.getMethod();
+        // 通过邮件发送"基金净值采集结果明细表格"
+        if (method == 1) {
+            MailboxInfoDTO mailboxInfoDTO = emailSystemConfigService.getFromEmailInfo();
+            String receivingMailbox = emailSystemConfigService.getRecipientEmail();
+            if (mailboxInfoDTO == null || StrUtil.isBlank(receivingMailbox)) {
+                log.info("未配置发件箱和收件箱,无法发送邮件");
+                return;
+            }
+            try {
+                String emailTitle = period + "_榜单_基金净值衍生指标及排名结果明细";
+                EmailUtil.senEmail(mailboxInfoDTO, receivingMailbox, new File(filePath), emailTitle, "", emailTitle);
+                log.info("基金净值衍生指标及排名结果明细表 -> 邮件已发送");
+            } catch (Exception e) {
+                log.error("邮件发送基金净值衍生指标及排名结果明细表异常 -> 堆栈信息:{}", ExceptionUtil.stacktraceToString(e));
+            }
+        }
     }
 }