From 46d1b5c5ce9020a1022c3df492685e0d261408df Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 16 十月 2026 21:04:53 +0800
Subject: [PATCH] fix(system): 用户导入模板列名与页面统一,补齐部门/岗位/初始密码列

---
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java |  230 ++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 195 insertions(+), 35 deletions(-)

diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java
index 6099e11..6a832ad 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java
@@ -14,6 +14,7 @@
 import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils;
 import cn.iocoder.yudao.module.infra.api.config.ConfigApi;
 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthRegisterReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserImportExcelVO;
@@ -21,6 +22,7 @@
 import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserPageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper;
@@ -515,11 +517,11 @@
         if (CollUtil.isEmpty(importUsers)) {
             throw exception(USER_IMPORT_LIST_IS_EMPTY);
         }
-        // 1.2 鍒濆鍖栧瘑鐮佷笉鑳戒负绌�
+        // 1.2 鍒濆鍖栧瘑鐮侊細Excel 鐨勩�屽垵濮嬪瘑鐮併�嶅垪鐣欑┖鏃讹紝浣跨敤绯荤粺閰嶇疆鐨勫垵濮嬪瘑鐮�
         String initPassword = configApi.getConfigValueByKey(USER_INIT_PASSWORD_KEY);
-        if (StrUtil.isEmpty(initPassword)) {
-            throw exception(USER_IMPORT_INIT_PASSWORD);
-        }
+        // 1.3 棰勫姞杞介儴闂ㄣ�佸矖浣嶏紝鐢ㄤ簬鎶� Excel 閲屽~鍐欑殑銆屽悕绉般�嶇炕璇戞垚缂栧彿
+        List<DeptDO> deptList = DataPermissionUtils.executeIgnore(() -> deptService.getDeptList(new DeptListReqVO()));
+        List<PostDO> postList = postService.getPostList(null, null);
 
         // 2. 閬嶅巻锛岄�愪釜鍒涘缓 or 鏇存柊
         UserImportRespVO respVO = UserImportRespVO.builder().createUsernames(new ArrayList<>())
@@ -527,40 +529,52 @@
         AtomicInteger index = new AtomicInteger(1);
         importUsers.forEach(importUser -> {
             int currentIndex = index.getAndIncrement();
-            // 2.1.1 鏍¢獙瀛楁鏄惁绗﹀悎瑕佹眰
+            String username = StrUtil.trim(importUser.getUsername());
+            // 澶辫触鏄庣粏鐨� key 瀹氫綅鍒板叿浣撶殑琛岋紙骞跺甫涓婄敤鎴疯处鍙凤級锛屾柟渚跨敤鎴峰湪 Excel 涓壘鍒拌繖涓�琛�
+            String rowKey = StrUtil.format("绗� {} 琛寋}", currentIndex,
+                    StrUtil.isEmpty(username) ? "" : StrUtil.format("銆寋}銆�", username));
             try {
-                ValidationUtils.validate(BeanUtils.toBean(importUser, UserSaveReqVO.class).setPassword(initPassword));
-            } catch (ConstraintViolationException ex) {
-                String key = StrUtil.blankToDefault(importUser.getUsername(), "绗� " + currentIndex + " 琛�");
-                respVO.getFailureUsernames().put(key, ex.getMessage());
-                return;
-            }
-            // 2.1.2 鏍¢獙锛屽垽鏂槸鍚︽湁涓嶇鍚堢殑鍘熷洜
-            try {
-                validateUserForCreateOrUpdate(null, null, importUser.getMobile(), importUser.getEmail(),
-                        importUser.getDeptId(), null);
-            } catch (ServiceException ex) {
-                respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage());
-                return;
-            }
+                // 2.1.1 瑙f瀽閮ㄩ棬銆佸矖浣嶏紙鏀寔濉悕绉帮紝閮ㄩ棬鍏煎濉紪鍙凤級
+                Long deptId = parseDeptId(importUser.getDept(), deptList);
+                Set<Long> postIds = parsePostIds(importUser.getPost(), postList);
+                // 2.1.2 鏍¢獙瀛楁鏄惁绗﹀悎瑕佹眰銆傚瘑鐮佷紭鍏堝彇 Excel 鐨勩�屽垵濮嬪瘑鐮併�嶏紝涓虹┖鏃跺彇绯荤粺閰嶇疆
+                String password = StrUtil.blankToDefault(StrUtil.trim(importUser.getPassword()), initPassword);
+                UserSaveReqVO saveReqVO = BeanUtils.toBean(importUser, UserSaveReqVO.class)
+                        .setUsername(username).setDeptId(deptId).setPostIds(postIds).setPassword(password);
+                validateUserImportRow(saveReqVO, password);
+                // 2.1.3 鏍¢獙鐢ㄦ埛璐﹀彿銆佹墜鏈哄彿銆侀偖绠卞敮涓�锛屼互鍙婇儴闂ㄣ�佸矖浣嶅彲鐢�
+                validateUserForCreateOrUpdate(null, username, saveReqVO.getMobile(), saveReqVO.getEmail(),
+                        deptId, postIds);
 
-            // 2.2.1 鍒ゆ柇濡傛灉涓嶅瓨鍦紝鍦ㄨ繘琛屾彃鍏�
-            AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername());
-            if (existUser == null) {
-                userMapper.insert(BeanUtils.toBean(importUser, AdminUserDO.class)
-                        .setPassword(encodePassword(initPassword)).setPostIds(new HashSet<>())); // 璁剧疆榛樿瀵嗙爜鍙婄┖宀椾綅缂栧彿鏁扮粍
-                respVO.getCreateUsernames().add(importUser.getUsername());
-                return;
+                // 2.2.1 鍒ゆ柇濡傛灉涓嶅瓨鍦紝鍦ㄨ繘琛屾彃鍏�
+                AdminUserDO existUser = userMapper.selectByUsername(username);
+                if (existUser == null) {
+                    AdminUserDO user = BeanUtils.toBean(saveReqVO, AdminUserDO.class)
+                            .setPassword(encodePassword(password))
+                            .setStatus(ObjUtil.defaultIfNull(importUser.getStatus(),
+                                    CommonStatusEnum.ENABLE.getStatus())); //銆岃处鍙风姸鎬併�嶇暀绌烘椂榛樿寮�鍚�
+                    userMapper.insert(user);
+                    insertUserPost(user.getId(), postIds); // 鍚屾宀椾綅鍏宠仈锛岄伩鍏嶅鍏ュ嚭鏉ョ殑鐢ㄦ埛娌℃湁宀椾綅
+                    respVO.getCreateUsernames().add(username);
+                    return;
+                }
+                // 2.2.2 濡傛灉瀛樺湪锛屽垽鏂槸鍚﹀厑璁告洿鏂�
+                if (!isUpdateSupport) {
+                    throw new ServiceException(USER_USERNAME_EXISTS.getCode(),
+                            "鐢ㄦ埛璐﹀彿宸插瓨鍦ㄣ�傚闇�瑕嗙洊杩欎釜鐢ㄦ埛锛岃鍕鹃�夈�屾槸鍚﹁鐩栥�嶅悗閲嶆柊瀵煎叆锛涘惁鍒欒淇敼 Excel 涓殑鐢ㄦ埛璐﹀彿");
+                }
+                AdminUserDO updateUser = BeanUtils.toBean(saveReqVO, AdminUserDO.class)
+                        .setId(existUser.getId()).setPassword(null) // 瑕嗙洊鏃朵笉鏀瑰瘑鐮侊紝閬垮厤鎶婂湪鐢ㄨ处鍙风殑瀵嗙爜鏀规帀
+                        .setStatus(importUser.getStatus()); //銆岃处鍙风姸鎬併�嶇暀绌烘椂淇濇寔鍘熺姸鎬�
+                userMapper.updateById(updateUser);
+                userPostMapper.deleteByUserId(existUser.getId());
+                insertUserPost(existUser.getId(), postIds);
+                respVO.getUpdateUsernames().add(username);
+            } catch (ConstraintViolationException ex) {
+                respVO.getFailureUsernames().put(rowKey, buildConstraintFailureMessage(ex));
+            } catch (ServiceException ex) {
+                respVO.getFailureUsernames().put(rowKey, buildImportFailureMessage(ex, importUser));
             }
-            // 2.2.2 濡傛灉瀛樺湪锛屽垽鏂槸鍚﹀厑璁告洿鏂�
-            if (!isUpdateSupport) {
-                respVO.getFailureUsernames().put(importUser.getUsername(), USER_USERNAME_EXISTS.getMsg());
-                return;
-            }
-            AdminUserDO updateUser = BeanUtils.toBean(importUser, AdminUserDO.class);
-            updateUser.setId(existUser.getId());
-            userMapper.updateById(updateUser);
-            respVO.getUpdateUsernames().add(importUser.getUsername());
         });
         // 璁板綍瀵煎叆缁撴灉姹囨�伙紝渚� @LogRecord 鐨� success 鏂囨寮曠敤
         OperateLogUtils.putImportSummary(respVO.getCreateUsernames(), respVO.getUpdateUsernames(),
@@ -568,6 +582,152 @@
         return respVO;
     }
 
+    /** Bean 鏍¢獙澶辫触鏃剁殑瀛楁鍚� -> 缁欑敤鎴风殑淇敼寤鸿 */
+    private static final Map<String, String> USER_IMPORT_FIELD_HINTS = Map.of(
+            "username", "銆岀敤鎴疯处鍙枫�嶅繀椤诲~鍐欙紝涓斿彧鑳界敱 4-30 浣嶆暟瀛椼�佸瓧姣嶇粍鎴愶紙渚嬪 zhangsan锛�",
+            "nickname", "銆岀敤鎴锋樀绉般�嶆渶澶� 30 涓瓧绗�",
+            "password", "銆屽垵濮嬪瘑鐮併�嶉暱搴︿负 4-16 浣�",
+            "email", "銆岀敤鎴烽偖绠便�嶆牸寮忎笉姝g‘锛岃濉啓褰㈠ zhangsan@example.com 鐨勯偖绠憋紝鎴栫暀绌鸿鍒�",
+            "mobile", "銆屾墜鏈哄彿鐮併�嶆牸寮忎笉姝g‘锛岃濉啓 11 浣嶆墜鏈哄彿锛堜緥濡� 13800000000锛夛紝鎴栫暀绌鸿鍒�");
+
+    /**
+     * 鏍¢獙鍗曡鐨勮处鍙枫�佹樀绉般�佸瘑鐮佺瓑瀛楁鏄惁绗﹀悎瑕佹眰
+     *
+     * @param saveReqVO Excel 琛岃浆鎹㈠嚭鐨勭敤鎴蜂俊鎭�
+     * @param password  璇ヨ鏈�缁堜娇鐢ㄧ殑瀵嗙爜
+     */
+    private void validateUserImportRow(UserSaveReqVO saveReqVO, String password) {
+        if (StrUtil.isEmpty(password)) {
+            throw new ServiceException(USER_IMPORT_INIT_PASSWORD.getCode(),
+                    "鏈~鍐欍�屽垵濮嬪瘑鐮併�嶏紝涓旂郴缁熸病鏈夐厤缃粯璁ょ殑鍒濆瀵嗙爜銆傝鍦� Excel 鐨勩�屽垵濮嬪瘑鐮併�嶅垪濉啓 4-16 浣嶅瘑鐮侊紝"
+                            + "鎴栬绠$悊鍛樺湪銆屽熀纭�璁炬柦 鈫� 閰嶇疆绠$悊銆嶄腑閰嶇疆 system.user.init-password");
+        }
+        ValidationUtils.validate(saveReqVO);
+    }
+
+    /** 鎶� Bean 鏍¢獙缁撴灉鏁寸悊鎴愩�屽摢涓瓧娈点�佹�庝箞鏀广�� */
+    private static String buildConstraintFailureMessage(ConstraintViolationException ex) {
+        List<String> messages = new ArrayList<>();
+        ex.getConstraintViolations().forEach(violation -> {
+            // 鏍¢獙鐨勬槸 Bean 鏈韩锛屽睘鎬ц矾寰勫氨鏄瓧娈靛悕锛堝 username锛夛紝鍏煎甯﹀墠缂�鐨勫啓娉�
+            String path = violation.getPropertyPath().toString();
+            String field = StrUtil.contains(path, '.') ? StrUtil.subAfter(path, '.', true) : path;
+            messages.add(StrUtil.blankToDefault(USER_IMPORT_FIELD_HINTS.get(field), violation.getMessage()));
+        });
+        return String.join("锛�", messages);
+    }
+
+    /** 鎶婄郴缁熷唴缃殑鏍¢獙寮傚父锛岃ˉ鍏呮垚鐢ㄦ埛鐪嬪緱鎳傘�佺煡閬撴�庝箞鏀圭殑鎻愮ず */
+    private static String buildImportFailureMessage(ServiceException ex, UserImportExcelVO importUser) {
+        if (ObjUtil.equal(ex.getCode(), USER_USERNAME_EXISTS.getCode())) {
+            return "鐢ㄦ埛璐﹀彿宸插瓨鍦紝濡傞渶瑕嗙洊璇峰嬀閫夈�屾槸鍚﹁鐩栥�嶅悗閲嶆柊瀵煎叆锛屽惁鍒欒淇敼 Excel 涓殑鐢ㄦ埛璐﹀彿";
+        }
+        if (ObjUtil.equal(ex.getCode(), USER_MOBILE_EXISTS.getCode())) {
+            return StrUtil.format("鎵嬫満鍙风爜銆寋}銆嶅凡琚叾浠栫敤鎴蜂娇鐢紝璇锋洿鎹㈡墜鏈哄彿锛屾垨鐣欑┖銆屾墜鏈哄彿鐮併�嶅垪", importUser.getMobile());
+        }
+        if (ObjUtil.equal(ex.getCode(), USER_EMAIL_EXISTS.getCode())) {
+            return StrUtil.format("閭銆寋}銆嶅凡琚叾浠栫敤鎴蜂娇鐢紝璇锋洿鎹㈤偖绠憋紝鎴栫暀绌恒�岀敤鎴烽偖绠便�嶅垪", importUser.getEmail());
+        }
+        return ex.getMessage();
+    }
+
+    /**
+     * 瑙f瀽 Excel銆岄儴闂ㄣ�嶅垪锛氭敮鎸佸~鍐欓儴闂ㄥ悕绉帮紝涔熷吋瀹瑰~鍐欓儴闂ㄧ紪鍙�
+     *
+     * @param deptText Excel 涓~鍐欑殑鍐呭锛屽厑璁镐负绌猴紙琛ㄧず鏆備笉褰掑睘閮ㄩ棬锛�
+     * @return 閮ㄩ棬缂栧彿
+     */
+    private Long parseDeptId(String deptText, List<DeptDO> deptList) {
+        if (StrUtil.isEmpty(deptText)) {
+            return null;
+        }
+        String text = StrUtil.trim(deptText);
+        // 鎯呭喌涓�锛氬~鍐欑殑鏄儴闂ㄧ紪鍙�
+        if (StrUtil.isNumeric(text) && text.length() <= 18) {
+            long deptId = Long.parseLong(text);
+            DeptDO dept = deptList.stream().filter(item -> ObjUtil.equal(item.getId(), deptId))
+                    .findFirst().orElse(null);
+            if (dept == null) {
+                throw new ServiceException(USER_IMPORT_DEPT_NOT_EXISTS.getCode(), StrUtil.format(
+                        "銆岄儴闂ㄣ�嶅垪濉啓鐨勯儴闂ㄧ紪鍙枫�寋}銆嶄笉瀛樺湪锛岃鍦ㄣ�岀郴缁熺鐞� 鈫� 閮ㄩ棬銆嶄腑鏌ョ湅姝g‘鐨勯儴闂ㄧ紪鍙凤紝鎴栫洿鎺ュ~鍐欓儴闂ㄥ悕绉�", text));
+            }
+            validateMasterDataEnabled(dept.getStatus(), StrUtil.format("閮ㄩ棬銆寋}銆嶅凡鍋滅敤", dept.getName()),
+                    "璇峰厛鍦ㄣ�岀郴缁熺鐞� 鈫� 閮ㄩ棬銆嶄腑鍚敤璇ラ儴闂�");
+            return deptId;
+        }
+        // 鎯呭喌浜岋細濉啓鐨勬槸閮ㄩ棬鍚嶇О
+        List<DeptDO> matched = deptList.stream()
+                .filter(item -> StrUtil.equals(text, StrUtil.trim(item.getName()))).toList();
+        if (matched.isEmpty()) {
+            throw new ServiceException(USER_IMPORT_DEPT_NOT_EXISTS.getCode(), StrUtil.format(
+                    "銆岄儴闂ㄣ�嶅垪濉啓鐨勯儴闂ㄣ�寋}銆嶄笉瀛樺湪锛岃鍏堝湪銆岀郴缁熺鐞� 鈫� 閮ㄩ棬銆嶄腑鍒涘缓锛屾垨鏀规垚宸插瓨鍦ㄧ殑閮ㄩ棬鍚嶇О/缂栧彿", text));
+        }
+        if (matched.size() > 1) {
+            throw new ServiceException(USER_IMPORT_DEPT_NAME_DUPLICATE.getCode(), StrUtil.format(
+                    "閮ㄩ棬鍚嶇О銆寋}銆嶅湪绯荤粺閲屾湁 {} 涓悓鍚嶉儴闂紝璇锋敼濉�岄儴闂ㄧ紪鍙枫�嶏紙鍙湪銆岀郴缁熺鐞� 鈫� 閮ㄩ棬銆嶄腑鏌ョ湅锛�", text, matched.size()));
+        }
+        DeptDO dept = matched.get(0);
+        validateMasterDataEnabled(dept.getStatus(), StrUtil.format("閮ㄩ棬銆寋}銆嶅凡鍋滅敤", dept.getName()),
+                "璇峰厛鍦ㄣ�岀郴缁熺鐞� 鈫� 閮ㄩ棬銆嶄腑鍚敤璇ラ儴闂�");
+        return dept.getId();
+    }
+
+    /**
+     * 瑙f瀽 Excel銆屽矖浣嶃�嶅垪锛氬~鍐欏矖浣嶅悕绉帮紝澶氫釜宀椾綅鐢ㄣ�屻�併�嶃��,銆嶃��;銆嶅垎闅�
+     *
+     * @return 宀椾綅缂栧彿闆嗗悎锛岃嚦灏戜竴涓�
+     */
+    private Set<Long> parsePostIds(String postText, List<PostDO> postList) {
+        if (StrUtil.isEmpty(postText)) {
+            throw new ServiceException(USER_IMPORT_POST_EMPTY.getCode(),
+                    "鏈~鍐欍�屽矖浣嶃�嶏紝璇峰~鍐欏矖浣嶅悕绉帮紱澶氫釜宀椾綅鐢ㄣ�屻�併�嶅垎闅旓紝渚嬪锛氭櫘宸ャ�佺彮缁勯暱");
+        }
+        Set<Long> postIds = new HashSet<>();
+        List<String> notExistsNames = new ArrayList<>();
+        for (String part : postText.split("[銆�,锛�;锛�/\\s]+")) {
+            String name = StrUtil.trim(part);
+            if (StrUtil.isEmpty(name)) {
+                continue;
+            }
+            PostDO post = postList.stream().filter(item -> StrUtil.equals(name, StrUtil.trim(item.getName())))
+                    .findFirst().orElse(null);
+            if (post == null) {
+                notExistsNames.add(name);
+                continue;
+            }
+            validateMasterDataEnabled(post.getStatus(), StrUtil.format("宀椾綅銆寋}銆嶅凡鍋滅敤", post.getName()),
+                    "璇峰厛鍦ㄣ�岀郴缁熺鐞� 鈫� 宀椾綅銆嶄腑鍚敤璇ュ矖浣�");
+            postIds.add(post.getId());
+        }
+        if (!notExistsNames.isEmpty()) {
+            throw new ServiceException(USER_IMPORT_POST_NOT_EXISTS.getCode(), StrUtil.format(
+                    "銆屽矖浣嶃�嶅垪濉啓鐨勫矖浣嶃�寋}銆嶄笉瀛樺湪锛岃鍏堝湪銆岀郴缁熺鐞� 鈫� 宀椾綅銆嶄腑缁存姢锛屾垨鏀规垚宸插瓨鍦ㄧ殑宀椾綅鍚嶇О",
+                    String.join("銆�", notExistsNames)));
+        }
+        if (postIds.isEmpty()) { // 渚嬪鍙~浜嗗垎闅旂
+            throw new ServiceException(USER_IMPORT_POST_EMPTY.getCode(),
+                    "鏈~鍐欍�屽矖浣嶃�嶏紝璇峰~鍐欏矖浣嶅悕绉帮紱澶氫釜宀椾綅鐢ㄣ�屻�併�嶅垎闅旓紝渚嬪锛氭櫘宸ャ�佺彮缁勯暱");
+        }
+        return postIds;
+    }
+
+    /** 鏍¢獙閮ㄩ棬銆佸矖浣嶆槸鍚﹀浜庡紑鍚姸鎬� */
+    private static void validateMasterDataEnabled(Integer status, String disabledTip, String actionTip) {
+        if (ObjUtil.equal(status, CommonStatusEnum.DISABLE.getStatus())) {
+            throw new ServiceException(USER_IMPORT_MASTER_DATA_DISABLED.getCode(),
+                    StrUtil.format("{}锛寋}", disabledTip, actionTip));
+        }
+    }
+
+    /** 鍐欏叆鐢ㄦ埛涓庡矖浣嶇殑鍏宠仈锛屽鍏ュ嚭鏉ョ殑鐢ㄦ埛鍚屾牱瑕佹湁宀椾綅锛堝惁鍒欏湪椤甸潰涓婄紪杈戜繚瀛樹笉浜嗭級 */
+    private void insertUserPost(Long userId, Set<Long> postIds) {
+        if (CollUtil.isEmpty(postIds)) {
+            return;
+        }
+        userPostMapper.insertBatch(convertList(postIds,
+                postId -> new UserPostDO().setUserId(userId).setPostId(postId)));
+    }
+
     @Override
     public List<AdminUserDO> getUserListByStatus(Integer status) {
         return userMapper.selectListByStatus(status);

--
Gitblit v1.9.3