From ef32f6913325bb593d537b4ff433545f73ecb79a Mon Sep 17 00:00:00 2001
From: xuhy <3313886187@qq.com>
Date: 星期三, 11 三月 2026 10:00:32 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpWhitelistController.java |  209 ++++++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 168 insertions(+), 41 deletions(-)

diff --git a/xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpWhitelistController.java b/xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpWhitelistController.java
index e9ebad7..7aed918 100644
--- a/xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpWhitelistController.java
+++ b/xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpWhitelistController.java
@@ -7,6 +7,7 @@
 import cn.afterturn.easypoi.excel.entity.ImportParams;
 import cn.hutool.core.util.PhoneUtil;
 import cn.hutool.json.JSONObject;
+import cn.hutool.core.util.PhoneUtil;
 import com.xiaopeng.api.model.account.SysUser;
 import com.xiaopeng.api.model.operation.XpWhitelist;
 import com.xiaopeng.api.model.operation.XpWhitelistOperlog;
@@ -49,8 +50,14 @@
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -200,51 +207,76 @@
         ImportParams params = new ImportParams();
 //        params.setTitleRows(1); // 标题行数
         params.setHeadRows(1); //表头行数
-        InputStream inputStream = null;
-//        List<CustomerImportFailedData> failedData = new ArrayList<>();
         List<WhitelistImportExcel> whitelistImportExcels;
         try {
-            inputStream = file.getInputStream();
-            whitelistImportExcels = ExcelImportUtil.importExcel(inputStream,
-                    WhitelistImportExcel.class, params);
+            try (InputStream inputStream = file.getInputStream()) {
+                whitelistImportExcels = ExcelImportUtil.importExcel(inputStream,
+                        WhitelistImportExcel.class, params);
+            }
         } catch (Exception e) {
             e.printStackTrace();
             log.error("白名单导入失败:{}", e.getMessage());
             throw new ServiceException("白名单导入失败!");
-        } finally {
-            try {
-                inputStream.close();
-            } catch (IOException e) {
-                throw new ServiceException(e.getMessage());
-            }
         }
         if (CollectionUtils.isEmpty(whitelistImportExcels)) {
             throw new ServiceException("白名单数据为空!");
         }
-        // 校验手机号白名单是否有重复的
-        long count = whitelistImportExcels.stream().map(WhitelistImportExcel::getAccount)
-                .distinct().count();
-        if (count != whitelistImportExcels.size()) {
-            return AjaxResult.error("白名单手机号存在重复!");
-        }
-        Map<String, Long> codeMap = xpWhitelistService.list().stream()
-                .collect(Collectors.toMap(
-                        XpWhitelist::getAccount,
-                        XpWhitelist::getId,
-                        (existing, replacement) -> replacement // 如果冲突,用新的覆盖
-                ));
-        JSONObject result = new JSONObject();
-        List<XpWhitelist> addList = new ArrayList<>();
-        for (WhitelistImportExcel whitelistImportExcel : whitelistImportExcels) {
-            // 手机号正则校验
-            boolean isPhone = PhoneUtil.isPhone(whitelistImportExcel.getAccount());
-            if (!isPhone) {
-                result.append("白名单:[", whitelistImportExcel.getAccount() + "]手机号格式错误");
+
+        int sampleLimitPerReason = 20;
+        int totalCount = whitelistImportExcels.size();
+        Map<String, Long> reasonCountMap = new HashMap<>();
+        Map<String, LinkedHashSet<String>> reasonSampleMap = new HashMap<>();
+
+        // 先做基础校验 + 文件内去重(允许部分成功)
+        Set<String> seenAccounts = new HashSet<>();
+        List<WhitelistImportExcel> candidates = new ArrayList<>();
+        for (int i = 0; i < whitelistImportExcels.size(); i++) {
+            WhitelistImportExcel row = whitelistImportExcels.get(i);
+            String account = trimToNull(row.getAccount());
+            String nickName = trimToNull(row.getNickName());
+            row.setAccount(account);
+            row.setNickName(nickName);
+
+            int excelRowNumber = i + params.getHeadRows() + 1;
+            if (!StringUtils.hasLength(account)) {
+                recordFail(reasonCountMap, reasonSampleMap, "账号为空", "第" + excelRowNumber + "行", sampleLimitPerReason);
                 continue;
             }
-            Long id = codeMap.get(whitelistImportExcel.getAccount());
-            if (id != null) {
-                result.append("白名单:[", whitelistImportExcel.getAccount() + "]已存在");
+            if (!PhoneUtil.isPhone(account)) {
+                recordFail(reasonCountMap, reasonSampleMap, "账号格式不正确", account, sampleLimitPerReason);
+                continue;
+            }
+            if (!seenAccounts.add(account)) {
+                recordFail(reasonCountMap, reasonSampleMap, "文件内账号重复", account, sampleLimitPerReason);
+                continue;
+            }
+            candidates.add(row);
+        }
+
+        // 批量查询数据库已存在账号(避免全表扫描)
+        Set<String> existsAccounts = Collections.emptySet();
+        if (!CollectionUtils.isEmpty(candidates)) {
+            Set<String> candidateAccounts = candidates.stream()
+                    .map(WhitelistImportExcel::getAccount)
+                    .filter(StringUtils::hasLength)
+                    .collect(Collectors.toSet());
+            if (!CollectionUtils.isEmpty(candidateAccounts)) {
+                existsAccounts = xpWhitelistService.lambdaQuery()
+                        .select(XpWhitelist::getAccount)
+                        .in(XpWhitelist::getAccount, candidateAccounts)
+                        .list()
+                        .stream()
+                        .map(XpWhitelist::getAccount)
+                        .filter(StringUtils::hasLength)
+                        .collect(Collectors.toSet());
+            }
+        }
+
+        List<XpWhitelist> addList = new ArrayList<>();
+        SysUser sysUser = tokenService.getLoginUser().getSysUser();
+        for (WhitelistImportExcel whitelistImportExcel : candidates) {
+            if (existsAccounts.contains(whitelistImportExcel.getAccount())) {
+                recordFail(reasonCountMap, reasonSampleMap, "账号已存在", whitelistImportExcel.getAccount(), sampleLimitPerReason);
                 continue;
             }
             XpWhitelist xpWhitelist = new XpWhitelist();
@@ -253,25 +285,120 @@
             xpWhitelist.setStatus(true);
             xpWhitelist.setIsPriorityDispatch(true);
             xpWhitelist.setWhitelistSource(1);
-            xpWhitelist.setCreateBy(tokenService.getLoginUser().getSysUser().getNickName());
+            xpWhitelist.setCreateBy(sysUser.getNickName());
             addList.add(xpWhitelist);
         }
-        if (!result.isEmpty()) {
-            return AjaxResult.success(result.toString());
+
+        int successCount = addList.size();
+        int failCount = totalCount - successCount;
+
+        String message;
+        if (failCount == 0) {
+            message = "成功添加" + successCount + "条白名单记录";
+        } else if (successCount == 0) {
+            message = "所有帐号添加失败,请检查格式或唯一性";
+        } else {
+            String reasonDetail = buildReasonDetail(reasonCountMap, reasonSampleMap);
+            message = "成功添加" + successCount + "条," + failCount + "条添加失败:原因" + reasonDetail;
         }
+
+        if (successCount == 0) {
+            return AjaxResult.error(message, message);
+        }
+
         xpWhitelistService.saveBatch(addList);
-        SysUser sysUser = tokenService.getLoginUser().getSysUser();
-        StringBuilder operationContent = new StringBuilder("共计导入"+addList.size()+"条数据");
+        StringBuilder operationContent = new StringBuilder("白名单导入:成功添加")
+                .append(successCount).append("条,失败").append(failCount).append("条");
+        if (failCount > 0) {
+            operationContent.append(";原因").append(buildReasonSummary(reasonCountMap));
+        }
+        String opContent = truncate(operationContent.toString(), 200);
         XpWhitelistOperlog xpWhitelistOperlog = new XpWhitelistOperlog();
         xpWhitelistOperlog.setOperationName(sysUser.getNickName());
         xpWhitelistOperlog.setOperationAccount(sysUser.getPhoneNumber());
         xpWhitelistOperlog.setOperationType(4);
         xpWhitelistOperlog.setOperationIp(IpUtils.getIpAddr());
         xpWhitelistOperlog.setOperationAccountId(sysUser.getId());
-        xpWhitelistOperlog.setOperationContent(operationContent.toString());
-        xpWhitelistOperlog.setCreateBy(tokenService.getLoginUser().getSysUser().getNickName());
+        xpWhitelistOperlog.setOperationContent(opContent);
+        xpWhitelistOperlog.setCreateBy(sysUser.getNickName());
         xpWhitelistOperlogService.save(xpWhitelistOperlog);
-        return AjaxResult.success();
+
+        return AjaxResult.success(message, message);
+    }
+
+    private static String trimToNull(String value) {
+        if (!StringUtils.hasLength(value)) {
+            return null;
+        }
+        String trimmed = value.trim();
+        return StringUtils.hasLength(trimmed) ? trimmed : null;
+    }
+
+    private static void incReason(Map<String, Long> reasonCountMap, String reason) {
+        reasonCountMap.merge(reason, 1L, Long::sum);
+    }
+
+    private static void recordFail(Map<String, Long> reasonCountMap,
+                                   Map<String, LinkedHashSet<String>> reasonSampleMap,
+                                   String reason,
+                                   String sample,
+                                   int sampleLimitPerReason) {
+        incReason(reasonCountMap, reason);
+        if (!StringUtils.hasLength(sample) || sampleLimitPerReason <= 0) {
+            return;
+        }
+        LinkedHashSet<String> samples = reasonSampleMap.computeIfAbsent(reason, k -> new LinkedHashSet<>());
+        if (samples.size() >= sampleLimitPerReason) {
+            return;
+        }
+        samples.add(sample);
+    }
+
+    private static String buildReasonSummary(Map<String, Long> reasonCountMap) {
+        if (reasonCountMap == null || reasonCountMap.isEmpty()) {
+            return "请检查格式或唯一性";
+        }
+        return reasonCountMap.entrySet().stream()
+                .sorted(Comparator.<Map.Entry<String, Long>>comparingLong(e -> e.getValue() == null ? 0L : e.getValue())
+                        .reversed()
+                        .thenComparing(Map.Entry::getKey))
+                .map(e -> e.getKey() + "(" + (e.getValue() == null ? 0L : e.getValue()) + ")")
+                .collect(Collectors.joining(","));
+    }
+
+    private static String buildReasonDetail(Map<String, Long> reasonCountMap,
+                                           Map<String, LinkedHashSet<String>> reasonSampleMap) {
+        if (reasonCountMap == null || reasonCountMap.isEmpty()) {
+            return "请检查格式或唯一性";
+        }
+        Map<String, LinkedHashSet<String>> safeSamples = reasonSampleMap == null ? Collections.emptyMap() : reasonSampleMap;
+        return reasonCountMap.entrySet().stream()
+                .sorted(Comparator.<Map.Entry<String, Long>>comparingLong(e -> e.getValue() == null ? 0L : e.getValue())
+                        .reversed()
+                        .thenComparing(Map.Entry::getKey))
+                .map(entry -> {
+                    String reason = entry.getKey();
+                    long count = entry.getValue() == null ? 0L : entry.getValue();
+                    LinkedHashSet<String> samples = safeSamples.getOrDefault(reason, new LinkedHashSet<>());
+                    String samplePart = "";
+                    if (!samples.isEmpty()) {
+                        long remaining = count - samples.size();
+                        String suffix = remaining > 0 ? "等" + remaining + "个" : "";
+                        samplePart = "[" + String.join(",", samples) + (StringUtils.hasLength(suffix) ? ("," + suffix) : "") + "]";
+                    }
+                    return reason + "(" + count + ")" + samplePart;
+                })
+                .collect(Collectors.joining(";"));
+    }
+
+    private static String truncate(String value, int maxLength) {
+        if (value == null || maxLength <= 0) {
+            return value;
+        }
+        if (value.length() <= maxLength) {
+            return value;
+        }
+        return value.substring(0, maxLength);
     }
 
     @ApiOperation(value = "白名单导出")

--
Gitblit v1.7.1