zhizhijie
2 小时以前 799ec6799ad9e994f7d369f059ca7682962f648f
traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java
@@ -67,6 +67,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.trafficaudit.dataimport.dto.ImportResult;
import org.apache.poi.ss.usermodel.DateUtil;
import java.util.Comparator;
@@ -94,6 +95,9 @@
    /** H204 燃料1计量单位码 -> 中文单位 */
    private static final Map<String, String> ENERGY_FUEL_UNIT_NAMES = new HashMap<>();
    /** 能耗导入按报表期互斥,防止同报表期并发重导互相覆盖 */
    private final ConcurrentHashMap<String, Object> energyImportLocks = new ConcurrentHashMap<>();
    static {
        ENERGY_VEHICLE_TYPE_NAMES.put("01", "普通货车");
@@ -527,6 +531,11 @@
    /** 能耗明细每组固定18列,车牌号起始列(0-based):3,21,39,57,75 */
    private static final int[] ENERGY_GROUP_COLS = {3, 21, 39, 57, 75};
    /** 能耗导入按报表期互斥锁(H204 明细与运政信息共用,避免同报表期并发重导互相覆盖) */
    private Object energyLock(String period) {
        return energyImportLocks.computeIfAbsent(period == null ? "" : period, k -> new Object());
    }
    public ImportResult importEnergyMonthly(MultipartFile file, String period) throws Exception {
        List<EnergyRow> rows = new ArrayList<>();
        List<EnergyAuthVehicle> authList = new ArrayList<>();
@@ -541,7 +550,7 @@
                if (enterpriseName == null || enterpriseName.trim().isEmpty()) continue;
                for (int group : ENERGY_GROUP_COLS) {
                    String plate = getString(row, group);
                    if (plate == null || plate.trim().isEmpty()) continue;
                    if (plate == null || plate.trim().isEmpty() || "0".equals(plate.trim())) continue;
                    EnergyVehicleQuarterly e = new EnergyVehicleQuarterly();
                    e.setReportPeriod(period);
                    e.setRegionCode(getString(row, 0));
@@ -580,7 +589,7 @@
                    Row row = authSheet.getRow(r);
                    if (row == null) continue;
                    String plate = getString(row, 0);
                    if (plate == null || plate.trim().isEmpty()) continue;
                    if (plate == null || plate.trim().isEmpty() || "0".equals(plate.trim())) continue;
                    EnergyAuthVehicle auth = new EnergyAuthVehicle();
                    auth.setReportPeriod(period);
                    auth.setPlateNo(plate.trim());
@@ -597,54 +606,57 @@
        }
        // 幂等:同报表期重新导入,先删除旧数据(两表 + H204 规则审核结果)
        energyMapper.delete(new LambdaQueryWrapper<EnergyVehicleQuarterly>()
            .eq(EnergyVehicleQuarterly::getReportPeriod, period));
        energyAuthMapper.delete(new LambdaQueryWrapper<EnergyAuthVehicle>()
            .eq(EnergyAuthVehicle::getReportPeriod, period));
        List<Long> h204RuleIds = new ArrayList<>();
        for (com.trafficaudit.rulemanage.entity.AuditRule rule : ruleMapper.selectList(null)) {
            if ("H204".equals(rule.getReportType())) h204RuleIds.add(rule.getId());
        }
        if (!h204RuleIds.isEmpty()) {
            auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>()
                .eq(AuditResult::getReportPeriod, period)
                .in(AuditResult::getRuleId, h204RuleIds));
        }
        int success = 0;
        List<String> failDetails = new ArrayList<>();
        for (EnergyRow item : rows) {
            try {
                energyMapper.insert(item.entity);
                success++;
            } catch (Exception e) {
                Throwable cause = e;
                while (cause.getCause() != null) cause = cause.getCause();
                String reason = cause.getMessage();
                if (reason == null || reason.trim().isEmpty()) reason = e.getMessage();
                failDetails.add("第 " + item.excelRow + " 行(" + item.entity.getEnterpriseName()
                    + " " + item.entity.getPlateNo() + "):" + reason);
                log.error("Energy insert error: row {}, {}", item.excelRow, item.entity.getPlateNo(), e);
        // 加锁:同报表期并发重导互斥,防止 delete/insert 交错导致数据残留或翻倍
        synchronized (energyLock(period)) {
            energyMapper.delete(new LambdaQueryWrapper<EnergyVehicleQuarterly>()
                .eq(EnergyVehicleQuarterly::getReportPeriod, period));
            energyAuthMapper.delete(new LambdaQueryWrapper<EnergyAuthVehicle>()
                .eq(EnergyAuthVehicle::getReportPeriod, period));
            List<Long> h204RuleIds = new ArrayList<>();
            for (com.trafficaudit.rulemanage.entity.AuditRule rule : ruleMapper.selectList(null)) {
                if ("H204".equals(rule.getReportType())) h204RuleIds.add(rule.getId());
            }
        }
        String errorDetail = String.join("\n", failDetails);
        if (errorDetail.length() > 4000) {
            errorDetail = errorDetail.substring(0, 4000) + "\n……";
        }
        recordBatch(file.getOriginalFilename(), "H204", period, rows.size(), success, failDetails.size(), errorDetail);
        int authSuccess = 0;
        for (EnergyAuthVehicle auth : authList) {
            try {
                energyAuthMapper.insert(auth);
                authSuccess++;
            } catch (Exception e) {
                log.error("energy auth insert error: {}", auth.getPlateNo(), e);
            if (!h204RuleIds.isEmpty()) {
                auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>()
                    .eq(AuditResult::getReportPeriod, period)
                    .in(AuditResult::getRuleId, h204RuleIds));
            }
            int success = 0;
            List<String> failDetails = new ArrayList<>();
            for (EnergyRow item : rows) {
                try {
                    energyMapper.insert(item.entity);
                    success++;
                } catch (Exception e) {
                    Throwable cause = e;
                    while (cause.getCause() != null) cause = cause.getCause();
                    String reason = cause.getMessage();
                    if (reason == null || reason.trim().isEmpty()) reason = e.getMessage();
                    failDetails.add("第 " + item.excelRow + " 行(" + item.entity.getEnterpriseName()
                        + " " + item.entity.getPlateNo() + "):" + reason);
                    log.error("Energy insert error: row {}, {}", item.excelRow, item.entity.getPlateNo(), e);
                }
            }
            String errorDetail = String.join("\n", failDetails);
            if (errorDetail.length() > 4000) {
                errorDetail = errorDetail.substring(0, 4000) + "\n……";
            }
            recordBatch(file.getOriginalFilename(), "H204", period, rows.size(), success, failDetails.size(), errorDetail);
            int authSuccess = 0;
            for (EnergyAuthVehicle auth : authList) {
                try {
                    energyAuthMapper.insert(auth);
                    authSuccess++;
                } catch (Exception e) {
                    log.error("energy auth insert error: {}", auth.getPlateNo(), e);
                }
            }
            recordBatch(file.getOriginalFilename(), "ENERGY_AUTH", period, authList.size(), authSuccess, 0, null);
            log.info("Energy monthly imported: {} vehicles, auth {} rows for {}", success, authSuccess, period);
            return new ImportResult(success, failDetails.size(), failDetails);
        }
        recordBatch(file.getOriginalFilename(), "ENERGY_AUTH", period, authList.size(), authSuccess, 0, null);
        log.info("Energy monthly imported: {} vehicles, auth {} rows for {}", success, authSuccess, period);
        return new ImportResult(success, failDetails.size(), failDetails);
    }
    /** 能耗车辆运政信息(独立模板:车牌号/车辆类型/燃料类型/标记吨位/准牵引质量) */
@@ -657,7 +669,7 @@
                Row row = sheet.getRow(r);
                if (row == null) continue;
                String plate = getString(row, 0);
                if (plate == null || plate.trim().isEmpty()) continue;
                if (plate == null || plate.trim().isEmpty() || "0".equals(plate.trim())) continue;
                EnergyAuthVehicle auth = new EnergyAuthVehicle();
                auth.setReportPeriod(period);
                auth.setPlateNo(plate.trim());
@@ -671,30 +683,32 @@
            log.error("Energy auth parse error", e);
            throw e;
        }
        // 幂等:同报表期重新导入,先删除旧数据
        energyAuthMapper.delete(new LambdaQueryWrapper<EnergyAuthVehicle>()
            .eq(EnergyAuthVehicle::getReportPeriod, period));
        deleteRuleResults("H204", period);
        int success = 0;
        List<String> failDetails = new ArrayList<>();
        for (EnergyAuthVehicle auth : authList) {
            try {
                energyAuthMapper.insert(auth);
                success++;
            } catch (Exception e) {
                Throwable cause = e;
                while (cause.getCause() != null) cause = cause.getCause();
                String reason = cause.getMessage();
                if (reason == null || reason.trim().isEmpty()) reason = e.getMessage();
                failDetails.add("第 " + (authList.indexOf(auth) + 2) + " 行(" + auth.getPlateNo() + "):" + reason);
                log.error("Energy auth insert error: {}", auth.getPlateNo(), e);
        // 幂等:同报表期重新导入,先删除旧数据;与 H204 明细共用报表期锁,防并发互相覆盖
        synchronized (energyLock(period)) {
            energyAuthMapper.delete(new LambdaQueryWrapper<EnergyAuthVehicle>()
                .eq(EnergyAuthVehicle::getReportPeriod, period));
            deleteRuleResults("H204", period);
            int success = 0;
            List<String> failDetails = new ArrayList<>();
            for (EnergyAuthVehicle auth : authList) {
                try {
                    energyAuthMapper.insert(auth);
                    success++;
                } catch (Exception e) {
                    Throwable cause = e;
                    while (cause.getCause() != null) cause = cause.getCause();
                    String reason = cause.getMessage();
                    if (reason == null || reason.trim().isEmpty()) reason = e.getMessage();
                    failDetails.add("第 " + (authList.indexOf(auth) + 2) + " 行(" + auth.getPlateNo() + "):" + reason);
                    log.error("Energy auth insert error: {}", auth.getPlateNo(), e);
                }
            }
            String errorDetail = String.join("\n", failDetails);
            if (errorDetail.length() > 4000) errorDetail = errorDetail.substring(0, 4000) + "\n……";
            recordBatch(file.getOriginalFilename(), "ENERGY_AUTH", period, authList.size(), success, failDetails.size(), errorDetail);
            log.info("Energy auth imported: {} rows for {}", success, period);
            return new ImportResult(success, failDetails.size(), failDetails);
        }
        String errorDetail = String.join("\n", failDetails);
        if (errorDetail.length() > 4000) errorDetail = errorDetail.substring(0, 4000) + "\n……";
        recordBatch(file.getOriginalFilename(), "ENERGY_AUTH", period, authList.size(), success, failDetails.size(), errorDetail);
        log.info("Energy auth imported: {} rows for {}", success, period);
        return new ImportResult(success, failDetails.size(), failDetails);
    }
    /** H204 行记录:保留 Excel 行号用于失败定位 */