package com.trafficaudit.dataimport.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.trafficaudit.auditengine.entity.AuditResult; import com.trafficaudit.auditengine.mapper.AuditResultMapper; import com.trafficaudit.common.util.RegionUtil; import com.trafficaudit.dataimport.entity.FreightTurnoverImport; import com.trafficaudit.dataimport.entity.H2032EnterpriseMonthly; import com.trafficaudit.dataimport.entity.ImportBatch; import com.trafficaudit.dataimport.entity.ScaleSplitTransport; import com.trafficaudit.dataimport.entity.TransportAuthVehicle; import com.trafficaudit.dataimport.entity.VehicleTrackMileage; import com.trafficaudit.dataimport.mapper.FreightTurnoverImportMapper; import com.trafficaudit.dataimport.mapper.H2032EnterpriseMonthlyMapper; import com.trafficaudit.dataimport.mapper.ImportBatchMapper; import com.trafficaudit.dataimport.mapper.ScaleSplitTransportMapper; import com.trafficaudit.dataimport.mapper.TransportAuthVehicleMapper; import com.trafficaudit.dataimport.mapper.VehicleTrackMileageMapper; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.DataFormatter; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.WorkbookFactory; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.trafficaudit.dataimport.dto.ImportResult; import org.apache.poi.ss.usermodel.DateUtil; import java.text.SimpleDateFormat; /** * 数据导入服务:支持4类模板导入 */ @Slf4j @Service public class DataImportService { private static final DataFormatter FORMATTER = new DataFormatter(); @Resource private H2032EnterpriseMonthlyMapper h2032Mapper; @Resource private TransportAuthVehicleMapper transportAuthMapper; @Resource private VehicleTrackMileageMapper trackMileageMapper; @Resource private ScaleSplitTransportMapper scaleSplitMapper; @Resource private ImportBatchMapper importBatchMapper; @Resource private FreightTurnoverImportMapper freightTurnoverMapper; @Resource private AuditResultMapper auditResultMapper; // ========== H2032 月报 ========== public ImportResult importH2032(MultipartFile file, String period) throws Exception { List rows = new ArrayList<>(); Map colMap; try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { Sheet sheet = wb.getSheetAt(0); colMap = buildHeaderMap(sheet.getRow(0)); for (int r = 1; r <= sheet.getLastRowNum(); r++) { Row row = sheet.getRow(r); if (row == null) continue; String name = gs(row, colMap, "企业名称"); if (name == null || name.trim().isEmpty()) continue; H2032EnterpriseMonthly entity = new H2032EnterpriseMonthly(); entity.setReportPeriod(period); entity.setRegionCode(gs(row, colMap, "所属地区")); entity.setEnterpriseCode(gs(row, colMap, "企业代码")); entity.setEnterpriseName(name); entity.setUnifiedCreditCode(gs(row, colMap, "统一社会信用代码")); entity.setReportUnit(gs(row, colMap, "填报单位")); entity.setVehicleTotal(gi(row, colMap, "车辆数_总计")); entity.setTonsTotal(gd(row, colMap, "标记吨位数_总计")); entity.setVehicleTractor(gi(row, colMap, "车辆数_牵引车")); entity.setVehicleTrailer(gi(row, colMap, "车辆数_挂车")); entity.setTonsTrailer(gd(row, colMap, "标记吨位数_挂车")); entity.setVehicleContainer(gi(row, colMap, "车辆数_集装箱车")); entity.setTonsContainer(gd(row, colMap, "标记吨位数_集装箱车")); entity.setFreightTotal(gd(row, colMap, "货运量_总计")); entity.setTurnoverTotal(gd(row, colMap, "货物周转量_总计")); entity.setFreightContainer(gd(row, colMap, "货运量_集装箱")); entity.setFreightCoal(gd(row, colMap, "货运量_煤炭及制品")); entity.setFreightOilGas(gd(row, colMap, "货运量_石油、天然气及制品(吨)")); entity.setFreightCrudeOil(gd(row, colMap, "货运量_其中:原油(吨)")); entity.setFreightMetalOre(gd(row, colMap, "货运量_金属矿石")); entity.setFreightIronOre(gd(row, colMap, "货运量_其中:铁矿石(吨)")); entity.setFreightBuilding(gd(row, colMap, "货运量_矿物性建筑材料(吨)")); entity.setFreightGrain(gd(row, colMap, "货运量_粮食(吨)")); entity.setUnitLeader(gs(row, colMap, "单位负责人")); entity.setStatsLeader(gs(row, colMap, "统计负责人")); entity.setContactPerson(gs(row, colMap, "填表人")); entity.setContactPhone(gs(row, colMap, "联系电话")); entity.setReportDate(gds(row, colMap, "报出日期")); entity.setAvgTonnage(gd(row, colMap, "货车平均吨位")); entity.setAvgTonnageTrailer(gd(row, colMap, "平均吨位_挂车")); entity.setAvgTonnageWhole(gd(row, colMap, "平均吨位_整车")); entity.setAvgDistance(gd(row, colMap, "货车平均运距")); entity.setDailyTripsPerTon(gd(row, colMap, "单吨日均载运次数")); entity.setDailyMileagePerVehicle(gd(row, colMap, "单车单天行驶里程")); entity.setDailyVolumePerVehicle(gd(row, colMap, "单车日均运量")); entity.setDailyTripsWithTractor(gd(row, colMap, "单吨日均载运次数(结合牵引车)")); entity.setVehicleMomGrowth(gd(row, colMap, "车辆数-总计环比增速")); entity.setTonsMomGrowth(gd(row, colMap, "标记吨位-总计环比增速")); entity.setFreightMomGrowth(gd(row, colMap, "货运量-总计环比增速")); entity.setTurnoverMomGrowth(gd(row, colMap, "货物周转量-总计环比增速")); entity.setAvgDistanceMomGrowth(gd(row, colMap, "货车平均运距-环比增速")); entity.setVehicleYoyGrowth(gd(row, colMap, "车辆数-总计同比增速")); entity.setTonsYoyGrowth(gd(row, colMap, "标记吨位-总计同比增速")); entity.setTractorTrailerRatio(gd(row, colMap, "牵引车挂车比")); entity.setFreightYoyGrowth(gd(row, colMap, "货运量-总计同比增速")); entity.setTurnoverYoyGrowth(gd(row, colMap, "货物周转量-总计同比增速")); entity.setAvgDistanceYoyGrowth(gd(row, colMap, "货车平均运距-同比增速")); entity.setVehicleWhole(gi(row, colMap, "车辆数_整车")); entity.setTonsWhole(gd(row, colMap, "标记吨位数_整车")); entity.setVerifyExplanation(gs(row, colMap, "核实性原因解释")); entity.setReportNotes(gs(row, colMap, "上报说明")); entity.setModifyRecord(gs(row, colMap, "修改记录")); rows.add(new H2032Row(r + 1, entity)); } } catch (Exception e) { log.error("H2032 parse error", e); throw e; } // 幂等:同报表期重新导入,先删除旧数据 h2032Mapper.delete(new LambdaQueryWrapper() .eq(H2032EnterpriseMonthly::getReportPeriod, period)); auditResultMapper.delete(new LambdaQueryWrapper() .eq(AuditResult::getReportPeriod, period)); int success = 0; List failDetails = new ArrayList<>(); for (H2032Row item : rows) { try { h2032Mapper.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() + "):" + reason); log.error("H2032 insert error: row {}, {}", item.excelRow, item.entity.getEnterpriseName(), e); } } String errorDetail = String.join("\n", failDetails); if (errorDetail.length() > 4000) { errorDetail = errorDetail.substring(0, 4000) + "\n……"; } recordBatch(file.getOriginalFilename(), "H2032", period, rows.size(), success, failDetails.size(), errorDetail); log.info("H2032 imported: {} rows for {} (fail {})", success, period, failDetails.size()); return new ImportResult(success, failDetails.size(), failDetails); } // ========== 运政车辆 ========== public int importTransportAuth(MultipartFile file, String period) throws Exception { List list = new ArrayList<>(); int failRows = 0; try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { Sheet sheet = wb.getSheetAt(0); for (int r = 1; r <= sheet.getLastRowNum(); r++) { Row row = sheet.getRow(r); if (row == null) continue; String name = getString(row, 0); if (name == null || name.trim().isEmpty()) continue; TransportAuthVehicle entity = new TransportAuthVehicle(); entity.setReportPeriod(period); entity.setEnterpriseName(name); entity.setTractorCount(getInt(row, 1)); entity.setTrailerCount(getInt(row, 2)); entity.setOtherCount(getInt(row, 3)); entity.setTrailerTons(getDecimal(row, 4)); entity.setOtherTons(getDecimal(row, 5)); list.add(entity); } } catch (Exception e) { throw e; } // 幂等:同报表期重新导入,先删除旧数据 transportAuthMapper.delete(new LambdaQueryWrapper() .eq(TransportAuthVehicle::getReportPeriod, period)); auditResultMapper.delete(new LambdaQueryWrapper() .eq(AuditResult::getReportPeriod, period)); int success = 0; for (TransportAuthVehicle entity : list) { try { transportAuthMapper.insert(entity); success++; } catch (Exception e) { log.error("transport auth insert error: {}", entity.getEnterpriseName(), e); failRows++; } } recordBatch(file.getOriginalFilename(), "TRANSPORT_AUTH", period, list.size(), success, failRows, null); log.info("Transport auth imported: {} rows for {}", success, period); return success; } // ========== 轨迹里程 ========== public int importTrackMileage(MultipartFile file, String period) throws Exception { List list = new ArrayList<>(); int failRows = 0; try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { Sheet sheet = wb.getSheetAt(0); for (int r = 1; r <= sheet.getLastRowNum(); r++) { Row row = sheet.getRow(r); if (row == null) continue; String name = getString(row, 1); if (name == null || name.trim().isEmpty()) continue; VehicleTrackMileage entity = new VehicleTrackMileage(); entity.setEnterpriseName(name); entity.setMonthlyMileage(getDecimal(row, 2)); entity.setTrackedVehicles(getInt(row, 3)); entity.setReportPeriod(period); list.add(entity); } } catch (Exception e) { throw e; } // 幂等:同报表期先删除 trackMileageMapper.delete(new LambdaQueryWrapper() .eq(VehicleTrackMileage::getReportPeriod, period)); auditResultMapper.delete(new LambdaQueryWrapper() .eq(AuditResult::getReportPeriod, period)); int success = 0; for (VehicleTrackMileage entity : list) { try { trackMileageMapper.insert(entity); success++; } catch (Exception e) { log.error("track mileage insert error: {}", entity.getEnterpriseName(), e); failRows++; } } recordBatch(file.getOriginalFilename(), "TRACK_MILEAGE", period, list.size(), success, failRows, null); log.info("Track mileage imported: {} rows for {}", success, period); return success; } // ========== 规上规下拆分 ========== public int importScaleSplit(MultipartFile file, String period) throws Exception { List list = new ArrayList<>(); int failRows = 0; try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { for (int i = 0; i < wb.getNumberOfSheets(); i++) { Sheet sheet = wb.getSheetAt(i); String sheetName = sheet.getSheetName(); String periodType = null; if (sheetName.contains("当月")) periodType = "MONTH"; else if (sheetName.contains("累计")) periodType = "CUMULATIVE"; if (periodType == null) continue; // 检测子表头:是否包含货运量列(扩展布局) Row subHeader = sheet.getRow(2); boolean hasFreight = subHeader != null && "货运量".equals(getString(subHeader, 0)); for (int r = 3; r <= sheet.getLastRowNum(); r++) { Row row = sheet.getRow(r); if (row == null) continue; String regionName = getString(row, 0); if (regionName == null || regionName.trim().isEmpty()) continue; ScaleSplitTransport entity = new ScaleSplitTransport(); entity.setReportPeriod(period); entity.setPeriodType(periodType); entity.setRegionName(regionName.trim()); if (hasFreight) { entity.setAboveScaleFreight(getDecimal(row, 0)); entity.setAboveScaleTurnover(getDecimal(row, 1)); entity.setAboveScaleRank(getInt(row, 2)); entity.setAboveScaleYoy(getDecimal(row, 3)); entity.setAboveScaleYoyRank(getInt(row, 4)); entity.setBelowScaleFreight(getDecimal(row, 5)); entity.setBelowScaleTurnover(getDecimal(row, 6)); entity.setBelowScaleRank(getInt(row, 7)); entity.setBelowScaleYoy(getDecimal(row, 8)); entity.setBelowScaleYoyRank(getInt(row, 9)); entity.setTotalFreight(getDecimal(row, 10)); entity.setTotalTurnover(getDecimal(row, 11)); entity.setTotalRank(getInt(row, 12)); entity.setTotalYoy(getDecimal(row, 13)); entity.setTotalYoyRank(getInt(row, 14)); } else { entity.setAboveScaleTurnover(getDecimal(row, 1)); entity.setAboveScaleRank(getInt(row, 2)); entity.setAboveScaleYoy(getDecimal(row, 3)); entity.setAboveScaleYoyRank(getInt(row, 4)); entity.setBelowScaleTurnover(getDecimal(row, 5)); entity.setBelowScaleRank(getInt(row, 6)); entity.setBelowScaleYoy(getDecimal(row, 7)); entity.setBelowScaleYoyRank(getInt(row, 8)); entity.setTotalTurnover(getDecimal(row, 9)); entity.setTotalRank(getInt(row, 10)); entity.setTotalYoy(getDecimal(row, 11)); entity.setTotalYoyRank(getInt(row, 12)); } list.add(entity); } } } catch (Exception e) { throw e; } // 幂等:同报表期先删除 scaleSplitMapper.delete(new LambdaQueryWrapper() .eq(ScaleSplitTransport::getReportPeriod, period)); int success = 0; for (ScaleSplitTransport entity : list) { try { scaleSplitMapper.insert(entity); success++; } catch (Exception e) { log.error("scale split insert error: {} - {}", entity.getRegionName(), entity.getPeriodType(), e); failRows++; } } recordBatch(file.getOriginalFilename(), "SCALE_SPLIT", period, list.size(), success, failRows, null); log.info("Scale split imported: {} rows for {}", success, period); return success; } // ========== 货运量周转量(模板_货运量周转量.xlsx,每月导入) ========== public int importFreightTurnover(MultipartFile file, String period) throws Exception { List list = new ArrayList<>(); int failRows = 0; // 报表期 N 月:左半为今年 1-N 月(列数不固定),右半固定为去年 1-12 月(算同比用) int monthCount = Integer.parseInt(period.split("-")[1]); try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { Sheet sheet = wb.getSheetAt(0); Map freightMap = new HashMap<>(); Map turnoverMap = new HashMap<>(); Set seen = new HashSet<>(); for (int r = 1; r <= sheet.getLastRowNum(); r++) { Row row = sheet.getRow(r); if (row == null) continue; String name = getString(row, 0); if (name == null || name.trim().isEmpty()) continue; String region = RegionUtil.normalizeCityName(name); boolean isCity = RegionUtil.cityList().contains(region); if (!isCity && !"湖北省".equals(region)) continue; // 表头行 if (seen.contains(region)) { // 该名称已出现过 -> 周转量区块 FreightTurnoverImport entity = turnoverMap.get(region); if (entity == null) { entity = new FreightTurnoverImport(); entity.setReportPeriod(period); entity.setRegionName(name.trim()); turnoverMap.put(region, entity); } fillTurnoverRow(row, entity, monthCount); } else { seen.add(region); FreightTurnoverImport entity = freightMap.get(region); if (entity == null) { entity = new FreightTurnoverImport(); entity.setReportPeriod(period); entity.setRegionName(name.trim()); freightMap.put(region, entity); } fillFreightRow(row, entity, monthCount); } } // 合并:一个市州一行(货运量 + 周转量) for (Map.Entry entry : freightMap.entrySet()) { FreightTurnoverImport entity = entry.getValue(); FreightTurnoverImport turnover = turnoverMap.get(entry.getKey()); if (turnover != null) { for (int m = 1; m <= 12; m++) { setTurnover(entity, m, getTurnover(turnover, m)); setLastTurnover(entity, m, getLastTurnover(turnover, m)); } } list.add(entity); } } catch (Exception e) { throw e; } // 幂等:同报表期先删除 freightTurnoverMapper.delete(new LambdaQueryWrapper() .eq(FreightTurnoverImport::getReportPeriod, period)); int success = 0; for (FreightTurnoverImport entity : list) { try { freightTurnoverMapper.insert(entity); success++; } catch (Exception e) { log.error("freight turnover insert error: {}", entity.getRegionName(), e); failRows++; } } recordBatch(file.getOriginalFilename(), "FREIGHT_TURNOVER", period, list.size(), success, failRows, null); log.info("Freight turnover imported: {} rows for {}", success, period); return success; } /** 解析货运量区块行:左半 B 起 N 列为今年 1-N 月,右半为去年 1-12 月 */ private void fillFreightRow(Row row, FreightTurnoverImport entity, int monthCount) { int rightNameCol = findSecondRegionCol(row); for (int m = 1; m <= monthCount; m++) { setFreight(entity, m, getDecimalOrNull(row, m)); } for (int m = 1; m <= 12; m++) { int col = rightNameCol >= 0 ? rightNameCol + m : 9 + m; setLastFreight(entity, m, getDecimalOrNull(row, col)); } } /** 解析周转量区块行:左半 B 起 N 列为今年 1-N 月,右半为去年 1-12 月 */ private void fillTurnoverRow(Row row, FreightTurnoverImport entity, int monthCount) { int rightNameCol = findSecondRegionCol(row); for (int m = 1; m <= monthCount; m++) { setTurnover(entity, m, getDecimalOrNull(row, m)); } for (int m = 1; m <= 12; m++) { int col = rightNameCol >= 0 ? rightNameCol + m : 9 + m; setLastTurnover(entity, m, getDecimalOrNull(row, col)); } } /** 找行内第二个市州名列(右半市州名),找不到返回 -1(回退 J 列起 12 列) */ private int findSecondRegionCol(Row row) { String firstName = getString(row, 0); if (firstName == null) return -1; for (int c = 1; c <= 20; c++) { String v = getString(row, c); if (v != null && !v.isEmpty() && RegionUtil.normalizeCityName(v).equals(RegionUtil.normalizeCityName(firstName))) { return c; } } return -1; } private Double getFreight(FreightTurnoverImport e, int m) { return monthValue(m, e::getFreightM01, e::getFreightM02, e::getFreightM03, e::getFreightM04, e::getFreightM05, e::getFreightM06, e::getFreightM07, e::getFreightM08, e::getFreightM09, e::getFreightM10, e::getFreightM11, e::getFreightM12); } private Double getLastFreight(FreightTurnoverImport e, int m) { return monthValue(m, e::getLastFreightM01, e::getLastFreightM02, e::getLastFreightM03, e::getLastFreightM04, e::getLastFreightM05, e::getLastFreightM06, e::getLastFreightM07, e::getLastFreightM08, e::getLastFreightM09, e::getLastFreightM10, e::getLastFreightM11, e::getLastFreightM12); } private Double getTurnover(FreightTurnoverImport e, int m) { return monthValue(m, e::getTurnoverM01, e::getTurnoverM02, e::getTurnoverM03, e::getTurnoverM04, e::getTurnoverM05, e::getTurnoverM06, e::getTurnoverM07, e::getTurnoverM08, e::getTurnoverM09, e::getTurnoverM10, e::getTurnoverM11, e::getTurnoverM12); } private Double getLastTurnover(FreightTurnoverImport e, int m) { return monthValue(m, e::getLastTurnoverM01, e::getLastTurnoverM02, e::getLastTurnoverM03, e::getLastTurnoverM04, e::getLastTurnoverM05, e::getLastTurnoverM06, e::getLastTurnoverM07, e::getLastTurnoverM08, e::getLastTurnoverM09, e::getLastTurnoverM10, e::getLastTurnoverM11, e::getLastTurnoverM12); } private Double monthValue(int m, java.util.function.Supplier g1, java.util.function.Supplier g2, java.util.function.Supplier g3, java.util.function.Supplier g4, java.util.function.Supplier g5, java.util.function.Supplier g6, java.util.function.Supplier g7, java.util.function.Supplier g8, java.util.function.Supplier g9, java.util.function.Supplier g10, java.util.function.Supplier g11, java.util.function.Supplier g12) { switch (m) { case 1: return g1.get(); case 2: return g2.get(); case 3: return g3.get(); case 4: return g4.get(); case 5: return g5.get(); case 6: return g6.get(); case 7: return g7.get(); case 8: return g8.get(); case 9: return g9.get(); case 10: return g10.get(); case 11: return g11.get(); default: return g12.get(); } } private void setFreight(FreightTurnoverImport e, int m, Double v) { switch (m) { case 1: e.setFreightM01(v); break; case 2: e.setFreightM02(v); break; case 3: e.setFreightM03(v); break; case 4: e.setFreightM04(v); break; case 5: e.setFreightM05(v); break; case 6: e.setFreightM06(v); break; case 7: e.setFreightM07(v); break; case 8: e.setFreightM08(v); break; case 9: e.setFreightM09(v); break; case 10: e.setFreightM10(v); break; case 11: e.setFreightM11(v); break; case 12: e.setFreightM12(v); break; default: break; } } private void setLastFreight(FreightTurnoverImport e, int m, Double v) { switch (m) { case 1: e.setLastFreightM01(v); break; case 2: e.setLastFreightM02(v); break; case 3: e.setLastFreightM03(v); break; case 4: e.setLastFreightM04(v); break; case 5: e.setLastFreightM05(v); break; case 6: e.setLastFreightM06(v); break; case 7: e.setLastFreightM07(v); break; case 8: e.setLastFreightM08(v); break; case 9: e.setLastFreightM09(v); break; case 10: e.setLastFreightM10(v); break; case 11: e.setLastFreightM11(v); break; case 12: e.setLastFreightM12(v); break; default: break; } } private void setTurnover(FreightTurnoverImport e, int m, Double v) { switch (m) { case 1: e.setTurnoverM01(v); break; case 2: e.setTurnoverM02(v); break; case 3: e.setTurnoverM03(v); break; case 4: e.setTurnoverM04(v); break; case 5: e.setTurnoverM05(v); break; case 6: e.setTurnoverM06(v); break; case 7: e.setTurnoverM07(v); break; case 8: e.setTurnoverM08(v); break; case 9: e.setTurnoverM09(v); break; case 10: e.setTurnoverM10(v); break; case 11: e.setTurnoverM11(v); break; case 12: e.setTurnoverM12(v); break; default: break; } } private void setLastTurnover(FreightTurnoverImport e, int m, Double v) { switch (m) { case 1: e.setLastTurnoverM01(v); break; case 2: e.setLastTurnoverM02(v); break; case 3: e.setLastTurnoverM03(v); break; case 4: e.setLastTurnoverM04(v); break; case 5: e.setLastTurnoverM05(v); break; case 6: e.setLastTurnoverM06(v); break; case 7: e.setLastTurnoverM07(v); break; case 8: e.setLastTurnoverM08(v); break; case 9: e.setLastTurnoverM09(v); break; case 10: e.setLastTurnoverM10(v); break; case 11: e.setLastTurnoverM11(v); break; case 12: e.setLastTurnoverM12(v); break; default: break; } } // ========== 辅助方法 ========== private void recordBatch(String fileName, String importType, String period, int total, int success, int fail, String errorDetail) { try { ImportBatch batch = new ImportBatch(); batch.setFileName(fileName); batch.setImportType(importType); batch.setReportPeriod(period); batch.setTotalRows(total); batch.setSuccessRows(success); batch.setFailRows(fail); batch.setErrorDetail(errorDetail); importBatchMapper.insert(batch); } catch (Exception e) { log.error("record batch error", e); } } /** 按表头名称建立列号映射,兼容新旧模板列位差异 */ private Map buildHeaderMap(Row header) { Map colMap = new HashMap<>(); if (header == null) return colMap; for (Cell cell : header) { String h = FORMATTER.formatCellValue(cell).trim(); if (!h.isEmpty() && !colMap.containsKey(h)) { colMap.put(h, cell.getColumnIndex()); } } return colMap; } private int colIdx(Map colMap, String header) { Integer idx = colMap.get(header); return idx == null ? -1 : idx; } private String gs(Row row, Map colMap, String header) { int idx = colIdx(colMap, header); return idx < 0 ? null : getString(row, idx); } private Double gd(Row row, Map colMap, String header) { int idx = colIdx(colMap, header); return idx < 0 ? 0.0 : getDecimal(row, idx); } private Integer gi(Row row, Map colMap, String header) { int idx = colIdx(colMap, header); return idx < 0 ? 0 : getInt(row, idx); } private String gds(Row row, Map colMap, String header) { int idx = colIdx(colMap, header); return idx < 0 ? null : getDateString(row, idx); } /** 报出日期:Excel 日期单元格或文本统一转为 yyyy-MM-dd,空值返回 null */ private String getDateString(Row row, int idx) { Cell cell = row.getCell(idx); if (cell == null) return null; if (cell.getCellType() == CellType.NUMERIC && DateUtil.isCellDateFormatted(cell)) { return new SimpleDateFormat("yyyy-MM-dd").format(cell.getDateCellValue()); } String val = FORMATTER.formatCellValue(cell).trim(); if (val.isEmpty()) return null; return val.length() > 10 ? val.substring(0, 10) : val; } /** H2032 行记录:保留 Excel 行号用于失败定位 */ private static class H2032Row { final int excelRow; final H2032EnterpriseMonthly entity; H2032Row(int excelRow, H2032EnterpriseMonthly entity) { this.excelRow = excelRow; this.entity = entity; } } private String getString(Row row, int idx) { Cell cell = row.getCell(idx); if (cell == null) return null; String val = FORMATTER.formatCellValue(cell).trim(); return val.isEmpty() ? null : val; } private Double getDecimal(Row row, int idx) { Cell cell = row.getCell(idx); if (cell == null) return 0.0; // 数值单元格直接取原始值,避免被 Excel 显示格式(百分比/0_ 等)影响解析 if (cell.getCellType() == CellType.NUMERIC) { return cell.getNumericCellValue(); } String val = FORMATTER.formatCellValue(cell).trim(); if (val.isEmpty()) return 0.0; try { return Double.parseDouble(val.replace(",", "")); } catch (NumberFormatException e) { return 0.0; } } private Integer getInt(Row row, int idx) { Double val = getDecimal(row, idx); if (val == null) return 0; return val.intValue(); } /** 空单元格返回 null(用于货运量周转量模板,未填月份与 0 区分) */ private Double getDecimalOrNull(Row row, int idx) { Cell cell = row.getCell(idx); if (cell == null) return null; if (cell.getCellType() == CellType.NUMERIC) { return cell.getNumericCellValue(); } String val = FORMATTER.formatCellValue(cell).trim(); if (val.isEmpty()) return null; try { return Double.parseDouble(val.replace(",", "")); } catch (NumberFormatException e) { return null; } } }