From 799ec6799ad9e994f7d369f059ca7682962f648f Mon Sep 17 00:00:00 2001
From: zhizhijie <zhizhijie@users.noreply.gitee.com>
Date: 星期三, 02 九月 2026 00:08:30 +0800
Subject: [PATCH] fix: 能耗导入同报表期并发重导互斥锁 + 空车牌(0)过滤 + 前端导入按钮防连点

---
 traffic-audit-web/src/views/DataImport.vue                                                    |   16 +++--
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java |  154 ++++++++++++++++++++++++++++-----------------------
 2 files changed, 94 insertions(+), 76 deletions(-)

diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java
index 04b7e2e..41b922c 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java
+++ b/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};
 
+    /** 鑳借�楀鍏ユ寜鎶ヨ〃鏈熶簰鏂ラ攣锛圚204 鏄庣粏涓庤繍鏀夸俊鎭叡鐢紝閬垮厤鍚屾姤琛ㄦ湡骞跺彂閲嶅浜掔浉瑕嗙洊锛� */
+    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 琛屽彿鐢ㄤ簬澶辫触瀹氫綅 */
diff --git a/traffic-audit-web/src/views/DataImport.vue b/traffic-audit-web/src/views/DataImport.vue
index 8e277e2..12d46de 100644
--- a/traffic-audit-web/src/views/DataImport.vue
+++ b/traffic-audit-web/src/views/DataImport.vue
@@ -56,9 +56,9 @@
                   <div class="file-btns">
                     <el-upload action="#" :auto-upload="false" :show-file-list="false" multiple accept=".xlsx,.xls,.et"
                                :ref="'up' + g.key" :on-change="f => onFilePicked(g.key, f)" style="display:inline-block">
-                      <el-button size="mini" type="primary" plain icon="el-icon-folder-opened">閫夋嫨鏂囦欢</el-button>
+                      <el-button size="mini" type="primary" plain icon="el-icon-folder-opened" :disabled="importing">閫夋嫨鏂囦欢</el-button>
                     </el-upload>
-                    <el-button size="mini" type="primary" icon="el-icon-upload2" :disabled="!fileCount(moduleSel[g.key])"
+                    <el-button size="mini" type="primary" icon="el-icon-upload2" :loading="importing" :disabled="importing || !fileCount(moduleSel[g.key])"
                                @click="importNow(g.key)">{{ fileCount(moduleSel[g.key]) > 1 ? '鎵归噺瀵煎叆 ' + fileCount(moduleSel[g.key]) + ' 涓枃浠�' : '绔嬪嵆瀵煎叆' }}</el-button>
                   </div>
                 </div>
@@ -155,6 +155,7 @@
       importType: 'h2032',
       period: (() => { const d = new Date(); return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') })(),
       cityDirLoading: false,
+      importing: false,
       sideOpen: false,
       result: '',
       error: '',
@@ -318,6 +319,7 @@
     onFilePicked(key, file) {
       const type = this.moduleSel[key]
       if (!type) return
+      if (this.importing) { this.$message.warning('姝e湪瀵煎叆涓紝璇风◢鍊欏啀閫夋嫨鏂囦欢'); return }
       const list = (this.files[type] || []).filter(f => f.name !== file.name)
       list.push(file.raw)
       this.$set(this.files, type, list)
@@ -336,12 +338,14 @@
       }
       const fileList = this.files[type] || []
       if (!fileList.length) { this.$message.warning('璇峰厛涓鸿绫诲瀷閫夋嫨鏂囦欢'); return }
+      if (!this.period) { this.$message.warning('璇峰厛閫夋嫨鎶ヨ〃鏈�'); return }
+      if (this.importing) { this.$message.warning('姝e湪瀵煎叆涓紝璇风◢鍊欌��'); return }
+      this.importing = true
       if (fileList.length > 1 || type === 'investment' || type === 'investmentLogistics') {
-        this.batchImport(type, key, fileList)
+        this.batchImport(type, key, fileList).finally(() => { this.importing = false })
         return
       }
       const file = fileList[0]
-      if (!this.period) { this.$message.warning('璇峰厛閫夋嫨鎶ヨ〃鏈�'); return }
       const g = this.groups.find(x => x.key === key)
       const t = g.types.find(x => x.value === type)
       const row = {
@@ -364,7 +368,7 @@
         this.importType = ''
         this.files[type] = null
         this.$delete(this.fileNames, type)
-      })
+      }).finally(() => { this.importing = false })
     },
     fileCount(type) { return (this.files[type] || []).length },
     fileNamesText(type) {
@@ -380,7 +384,7 @@
       const g = this.groups.find(x => x.key === key)
       const label = g ? g.name : type
       this.$message({ message: '姝e湪鎵归噺瀵煎叆 ' + fileList.length + ' 涓枃浠垛��', duration: 3000 })
-      api.post('/import/batch', fd, { timeout: 600000 })
+      return api.post('/import/batch', fd, { timeout: 600000 })
         .then(res => {
           const d = (res && res.data) || {}
           if (!d.files) {

--
Gitblit v1.9.1