Преглед изворни кода

[26110502] 数据补传后端接口: GET reUpload(stationCode), 按类型对比当日上报成功条数与每日文件上传配置, 缺失类型补插上传成功记录(curve_id=9999/省调/通道1/随机message_no/ACK:OK/模拟生成, est_report_time=当前时间计入上传率统计), 已上传满视为完成, 存在待发送的类型跳过并在提示中列出

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
taosj пре 6 часа
родитељ
комит
5950ce1431

+ 12 - 0
forecast-backend-server/src/main/java/com/ufo/project/forecast/fileupload/controller/DataReportLogController.java

@@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 import com.alibaba.excel.EasyExcel;
 import com.ufo.base.common.utils.StringUtils;
@@ -123,4 +124,15 @@ public class DataReportLogController extends BaseController
                 .sheet("光伏上传率统计")
                 .doWrite(data);
     }
+
+    /**
+     * 数据补传: 对比当日上报成功条数与每日文件上传配置, 缺失类型插入对应条数上传成功记录
+     */
+    @PreAuthorize("@ss.hasPermi('fileupload:reportLog:reUpload')")
+    @Log(title = "数据补传", businessType = BusinessType.INSERT)
+    @GetMapping("/reUpload")
+    public AjaxResult reUpload(@RequestParam("stationCode") String stationCode)
+    {
+        return AjaxResult.success(dataReportLogService.reUpload(stationCode));
+    }
 }

+ 71 - 0
forecast-backend-server/src/main/java/com/ufo/project/forecast/fileupload/domain/vo/ReUploadCountRow.java

@@ -0,0 +1,71 @@
+package com.ufo.project.forecast.fileupload.domain.vo;
+
+import java.io.Serializable;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+/**
+ * 数据补传聚合行(仅 Mapper 聚合结果映射用)
+ *
+ * @author ufo
+ * @date 2026-11-05
+ */
+public class ReUploadCountRow implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 文件类型 */
+    private String type;
+
+    /** 上报状态:0待发送,1上报成功 */
+    private Integer status;
+
+    /** 记录条数 */
+    private Long cnt;
+
+    /** 获取文件类型 */
+    public String getType()
+    {
+        return type;
+    }
+
+    /** 设置文件类型 */
+    public void setType(String type)
+    {
+        this.type = type;
+    }
+
+    /** 获取上报状态 */
+    public Integer getStatus()
+    {
+        return status;
+    }
+
+    /** 设置上报状态 */
+    public void setStatus(Integer status)
+    {
+        this.status = status;
+    }
+
+    /** 获取记录条数 */
+    public Long getCnt()
+    {
+        return cnt;
+    }
+
+    /** 设置记录条数 */
+    public void setCnt(Long cnt)
+    {
+        this.cnt = cnt;
+    }
+
+    @Override
+    public String toString()
+    {
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+            .append("type", getType())
+            .append("status", getStatus())
+            .append("cnt", getCnt())
+            .toString();
+    }
+}

+ 17 - 0
forecast-backend-server/src/main/java/com/ufo/project/forecast/fileupload/mapper/DataReportLogMapper.java

@@ -5,6 +5,7 @@ import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.ufo.project.forecast.fileupload.domain.DataReportLog;
+import com.ufo.project.forecast.fileupload.domain.vo.ReUploadCountRow;
 import com.ufo.project.forecast.fileupload.domain.vo.UploadRateCountRow;
 
 /**
@@ -33,4 +34,20 @@ public interface DataReportLogMapper extends BaseMapper<DataReportLog>
             + "GROUP BY 1, 2")
     List<UploadRateCountRow> selectUploadRateCounts(@Param("stationCode") String stationCode,
             @Param("windowStart") String windowStart, @Param("windowEnd") String windowEnd);
+
+    /**
+     * 按"type×status"统计当日上报表条数(上海时区分天, status只取0待发送/1上报成功)
+     *
+     * @param stationCode 场站编码
+     * @param day 当日日期(上海时区) yyyy-MM-dd
+     * @return type/status/cnt 聚合行
+     */
+    @Select("SELECT type, status, COUNT(*) AS cnt "
+            + "FROM data_report_log "
+            + "WHERE station_code = #{stationCode} "
+            + "AND status IN (0, 1) "
+            + "AND DATE(est_report_time AT TIME ZONE 'Asia/Shanghai') = #{day}::date "
+            + "GROUP BY 1, 2")
+    List<ReUploadCountRow> selectTodayCounts(@Param("stationCode") String stationCode,
+            @Param("day") String day);
 }

+ 8 - 0
forecast-backend-server/src/main/java/com/ufo/project/forecast/fileupload/service/IDataReportLogService.java

@@ -43,4 +43,12 @@ public interface IDataReportLogService
      * 光伏上传率统计: 查询全量日期(导出用)
      */
     public List<UploadRateStatVO> uploadRateExportList(UploadRateStatQuery query);
+
+    /**
+     * 数据补传: 按类型对比当日上报成功条数与每日文件上传配置, 缺失类型插入对应条数上传成功记录
+     *
+     * @param stationCode 场站编码
+     * @return 补传结果提示消息
+     */
+    public String reUpload(String stationCode);
 }

+ 133 - 0
forecast-backend-server/src/main/java/com/ufo/project/forecast/fileupload/service/impl/DataReportLogServiceImpl.java

@@ -13,18 +13,21 @@ import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.ufo.base.common.exception.ServiceException;
+import com.ufo.base.common.utils.SecurityUtils;
 import com.ufo.base.common.utils.StringUtils;
 import com.ufo.module.system.domain.SysDictData;
 import com.ufo.module.system.service.ISysDictTypeService;
 import com.ufo.project.config.domain.ConfigStation;
 import com.ufo.project.config.service.IConfigStationService;
 import com.ufo.project.forecast.fileupload.domain.DataReportLog;
+import com.ufo.project.forecast.fileupload.domain.vo.ReUploadCountRow;
 import com.ufo.project.forecast.fileupload.domain.vo.UploadRateCountRow;
 import com.ufo.project.forecast.fileupload.domain.vo.UploadRateStatQuery;
 import com.ufo.project.forecast.fileupload.domain.vo.UploadRateStatVO;
@@ -132,6 +135,136 @@ public class DataReportLogServiceImpl implements IDataReportLogService
         return buildDayVos(range[0], range[1], query.getStationCode());
     }
 
+    @Override
+    public String reUpload(String stationCode)
+    {
+        if (StringUtils.isEmpty(stationCode))
+        {
+            throw new ServiceException("stationCode不能为空");
+        }
+        Map<String, Integer> nums = loadNums();
+        Map<String, String> labels = loadLabels();
+        LocalDate today = LocalDate.now(ZoneId.of("Asia/Shanghai"));
+        List<ReUploadCountRow> rows = dataReportLogMapper.selectTodayCounts(stationCode, today.toString());
+        Map<String, Long> successCnt = new HashMap<>();
+        Map<String, Long> pendingCnt = new HashMap<>();
+        for (ReUploadCountRow row : rows)
+        {
+            if (row.getStatus() == null)
+            {
+                continue;
+            }
+            if (row.getStatus() == 1)
+            {
+                successCnt.merge(row.getType(), row.getCnt() == null ? 0L : row.getCnt(), Long::sum);
+            }
+            else
+            {
+                pendingCnt.merge(row.getType(), row.getCnt() == null ? 0L : row.getCnt(), Long::sum);
+            }
+        }
+
+        // 按ALL_TYPES固定顺序逐类决策: 成功数>=num视为已完成; 存在待发送(上传中/处理中)则跳过; 否则补插缺失条数
+        List<String> pendingLabels = new ArrayList<>();
+        int insertCount = 0;
+        Date now = new Date();
+        String username = SecurityUtils.getUsername();
+        for (String type : ALL_TYPES)
+        {
+            int num = nums.get(type);
+            long ok = successCnt.getOrDefault(type, 0L);
+            long pending = pendingCnt.getOrDefault(type, 0L);
+            if (ok >= num)
+            {
+                continue;
+            }
+            if (pending > 0)
+            {
+                pendingLabels.add(labels.getOrDefault(type, type));
+                continue;
+            }
+            for (int i = 0; i < num - ok; i++)
+            {
+                dataReportLogMapper.insert(buildReUploadLog(type, stationCode, now, username));
+                insertCount++;
+            }
+        }
+
+        if (insertCount == 0 && pendingLabels.isEmpty())
+        {
+            return "当日文件已全部上传,无需补传";
+        }
+        StringBuilder sb = new StringBuilder();
+        if (insertCount > 0)
+        {
+            sb.append("补传成功 ").append(insertCount).append(" 条");
+        }
+        if (!pendingLabels.isEmpty())
+        {
+            if (sb.length() > 0)
+            {
+                sb.append(",");
+            }
+            sb.append("以下类型正在上传/处理中,未补传: ").append(String.join("、", pendingLabels));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 构造补传记录: 固定模拟字段值(curve_id=9999/省调/通道1/ACK:OK/模拟生成), message_no随机保证唯一
+     */
+    private DataReportLog buildReUploadLog(String type, String stationCode, Date now, String username)
+    {
+        DataReportLog log = new DataReportLog();
+        log.setType(type);
+        log.setStationCode(stationCode);
+        log.setCurveId(9999L);
+        log.setEstReportTime(now);
+        log.setReportTime(now);
+        log.setTargetDispatch("省调");
+        log.setChannel(1);
+        log.setMessageNo("SUP" + now.getTime() + type
+                + UUID.randomUUID().toString().replace("-", "").substring(0, 8).toUpperCase());
+        log.setStatus(1);
+        log.setResponseMsg("ACK:OK");
+        log.setRemark("模拟生成");
+        log.setCreateTime(now);
+        log.setUpdateTime(now);
+        log.setCreateBy(username);
+        log.setUpdateBy(username);
+        return log;
+    }
+
+    /**
+     * 读字典 daily_file_upload_config → type→中文label(用于提示消息), 解析失败回退type编码
+     */
+    private Map<String, String> loadLabels()
+    {
+        Map<String, String> labels = new HashMap<>();
+        List<SysDictData> dictDatas = dictTypeService.selectDictDataByType(DICT_TYPE);
+        if (dictDatas == null || dictDatas.isEmpty())
+        {
+            return labels;
+        }
+        for (SysDictData dict : dictDatas)
+        {
+            try
+            {
+                JSONObject obj = JSON.parseObject(dict.getDictValue());
+                String type = obj == null ? null : obj.getString("type");
+                if (StringUtils.isNotEmpty(type) && StringUtils.isNotEmpty(dict.getDictLabel()))
+                {
+                    labels.put(type, dict.getDictLabel());
+                }
+            }
+            catch (Exception e)
+            {
+                // label解析失败时该type回退为type编码
+            }
+        }
+        return labels;
+    }
+
     /**
      * 按计划上报时间过滤单日(queryDate, yyyy-MM-dd)
      */