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): 用户导入模板列名与页面统一,补齐部门/岗位/初始密码列

---
 docs/user_import_template_change.md                                                                                   |   72 ++++++++++++
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java                        |    5 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java            |   12 +
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java               |  230 ++++++++++++++++++++++++++++++++-----
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserImportExcelVO.java |   17 ++
 5 files changed, 292 insertions(+), 44 deletions(-)

diff --git a/docs/user_import_template_change.md b/docs/user_import_template_change.md
new file mode 100644
index 0000000..de4f3be
--- /dev/null
+++ b/docs/user_import_template_change.md
@@ -0,0 +1,72 @@
+# 鐢ㄦ埛瀵煎叆妯℃澘鍒楀悕鍙樻洿 - 鑱旇皟鏂规
+
+> 鍙樻洿鏃ユ湡锛�2026-10-16銆�妯″潡锛氱郴缁熺鐞� 鈫� 鐢ㄦ埛绠$悊
+
+## 鑳屾櫙
+
+鐢ㄦ埛涓嬭浇鐨勫鍏ユā鏉块噷锛岃处鍙峰垪鐨勮〃澶存槸銆岀櫥褰曞悕绉般�嶏紝鑰岄〉闈㈠拰鎺ュ彛鏍¢獙閲岄兘鍙�岀敤鎴疯处鍙枫�嶏紝
+鐢ㄦ埛鎷垮埌妯℃澘鍚庢壘涓嶅埌銆岀敤鎴疯处鍙枫�嶈繖涓�鍒楋紝濉敊鍒楀悗瀵煎叆 100% 澶辫触锛堥�愯鎻愮ず銆岀敤鎴疯处鍙蜂笉鑳戒负绌恒�嶏級銆�
+鏈缁熶竴鍒楀悕锛屽苟琛ラ綈鐢ㄦ埛瀹為檯闇�瑕佺殑鍒椼��
+
+## 娑夊強椤甸潰
+
+- 绯荤粺绠$悊 鈫� 鐢ㄦ埛绠$悊 鈫� 瀵煎叆鐢ㄦ埛锛堜笅杞藉鍏ユā鏉� / 瀵煎叆锛�
+
+## 鎺ュ彛
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /system/user/get-import-template | 涓嬭浇瀵煎叆妯℃澘锛堝垪鍚嶃�佸垪椤哄簭鏈夊彉鏇达級 |
+| POST | /system/user/import | 瀵煎叆鐢ㄦ埛锛堝け璐ユ槑缁嗙殑 key 鏍煎紡鏈夊彉鏇达級 |
+
+### GET /system/user/get-import-template
+
+妯℃澘鍒楋紙椤哄簭鍗冲垪椤哄簭锛夛細
+
+| 鍒� | 琛ㄥご | 蹇呭~ | 璇存槑 |
+|----|------|------|------|
+| A | 鐢ㄦ埛璐﹀彿 | 鏄� | 4-30 浣嶆暟瀛椼�佸瓧姣嶏紙鍘熻〃澶淬�岀櫥褰曞悕绉般�嶏級 |
+| B | 鐢ㄦ埛鏄电О | 鍚� | 鏈�澶� 30 涓瓧绗︼紙鍘熻〃澶淬�岀敤鎴峰悕绉般�嶏級 |
+| C | 閮ㄩ棬 | 鍚� | 濉儴闂ㄥ悕绉帮紱涔熷吋瀹瑰~閮ㄩ棬缂栧彿銆傜暀绌鸿〃绀烘殏涓嶅綊灞為儴闂� |
+| D | 宀椾綅 | 鏄� | 濉矖浣嶅悕绉帮紱澶氫釜宀椾綅鐢ㄣ�屻�併�嶅垎闅旓紝濡傦細鏅伐銆佺彮缁勯暱銆傚矖浣嶉渶鍏堝湪绯荤粺绠$悊 鈫� 宀椾綅涓淮鎶� |
+| E | 鐢ㄦ埛閭 | 鍚� | 鏍煎紡闈炴硶鎴栭噸澶嶆椂鎶ラ敊锛屽彲鐣欑┖ |
+| F | 鎵嬫満鍙风爜 | 鍚� | 11 浣嶆墜鏈哄彿锛屽彲鐣欑┖ |
+| G | 鐢ㄦ埛鎬у埆 | 鍚� | 濉瓧鍏告爣绛撅細鐢� / 濂� |
+| H | 璐﹀彿鐘舵�� | 鍚� | 濉瓧鍏告爣绛撅細寮�鍚� / 鍋滅敤锛涚暀绌洪粯璁ゅ紑鍚� |
+| I | 鍒濆瀵嗙爜 | 鍚� | 4-16 浣嶏紱鐣欑┖鏃朵娇鐢ㄧ郴缁熼厤缃殑鍒濆瀵嗙爜 |
+
+### POST /system/user/import
+
+鍝嶅簲缁撴瀯涓嶅彉锛屼粎 `failureUsernames` 鐨� key 鐢便�岀敤鎴峰悕銆嶆敼涓恒�岀 N 琛屻�岀敤鎴峰悕銆嶃�嶏紝
+渚夸簬鐢ㄦ埛鍦� Excel 閲屽畾浣嶅埌鍏蜂綋琛岋細
+
+```json
+{
+  "code": 0,
+  "data": {
+    "createUsernames": ["zhangsan"],
+    "updateUsernames": [],
+    "failureUsernames": {
+      "绗� 3 琛屻�宭isi銆�": "銆屽矖浣嶃�嶅垪濉啓鐨勫矖浣嶃�岃溅闂翠富浠汇�嶄笉瀛樺湪锛岃鍏堝湪銆岀郴缁熺鐞� 鈫� 宀椾綅銆嶄腑缁存姢锛屾垨鏀规垚宸插瓨鍦ㄧ殑宀椾綅鍚嶇О"
+    }
+  }
+}
+```
+
+## 涓氬姟瑙勫垯
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 宀椾綅 | 涓庨〉闈笂鏂板鐢ㄦ埛淇濇寔涓�鑷达紝瀵煎叆鏃跺繀濉紱瀵煎叆鎴愬姛鍚庝細鍚屾鍐欏叆鐢ㄦ埛-宀椾綅鍏宠仈锛屽鍏ュ嚭鏉ョ殑鐢ㄦ埛鍙互鐩存帴鍦ㄩ〉闈笂缂栬緫淇濆瓨 |
+| 閮ㄩ棬 / 宀椾綅鍚嶇О涓嶅瓨鍦� | 璇ヨ澶辫触锛屾彁绀洪噷缁欏嚭鍚嶇О鍜屼笅涓�姝ュ姩浣� |
+| 閮ㄩ棬閲嶅悕 | 璇ヨ澶辫触锛屾彁绀烘敼濉儴闂ㄧ紪鍙� |
+| 閮ㄩ棬 / 宀椾綅宸插仠鐢� | 璇ヨ澶辫触锛屾彁绀哄厛鍚敤 |
+| 璐﹀彿宸插瓨鍦ㄤ笖鏈嬀閫夈�屾槸鍚﹁鐩栥�� | 璇ヨ澶辫触锛屾彁绀哄嬀閫夎鐩栨垨鏀硅处鍙� |
+| 宸插瓨鍦ㄤ笖鍕鹃�夈�屾槸鍚﹁鐩栥�� | 鏇存柊璇ョ敤鎴风殑閮ㄩ棬銆佸矖浣嶃�侀偖绠便�佹墜鏈哄彿銆佹�у埆銆佺姸鎬侊紝**涓嶄慨鏀瑰叾瀵嗙爜** |
+| 瑕嗙洊鏃躲�岃处鍙风姸鎬併�嶇暀绌� | 淇濇寔鍘熺姸鎬佷笉鍙� |
+
+## 娉ㄦ剰浜嬮」
+
+- 鍒楀悕鍙樹簡锛屾棫妯℃澘锛堣〃澶磋繕鏄�岀櫥褰曞悕绉� / 鐢ㄦ埛鍚嶇О / 閮ㄩ棬缂栧彿銆嶏級涓嶈兘鍐嶄娇鐢紝闇�瑕侀噸鏂颁笅杞芥ā鏉匡紱
+  鏃фā鏉垮鍏ヤ細閫愯鎻愮ず銆岀敤鎴疯处鍙峰繀椤诲~鍐欍�嶏紝涓嶄細闈欓粯瀵煎叆鎴愰敊璇暟鎹��
+- 澶辫触鏄庣粏鍙兘涓婄櫨鏉★紝鍓嶇浠ュ脊绐楋紙寮圭獥鍐呭垪琛ㄥ彲婊氬姩锛夊睍绀猴紝鎴愬姛鍚庝粛鐢ㄨ交鎻愮ず銆�
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java
index b206f45..e855b2d 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java
@@ -224,12 +224,14 @@
     @GetMapping("/get-import-template")
     @Operation(summary = "鑾峰緱瀵煎叆鐢ㄦ埛妯℃澘")
     public void importTemplate(HttpServletResponse response) throws IOException {
-        // 鎵嬪姩鍒涘缓瀵煎嚭 demo
+        // 鎵嬪姩鍒涘缓瀵煎嚭 demo锛氶儴闂ㄣ�佸矖浣嶈濉啓绯荤粺閲屽凡缁忓瓨鍦ㄧ殑鍚嶇О锛涘涓矖浣嶇敤銆屻�併�嶅垎闅�
         List<UserImportExcelVO> list = Arrays.asList(
-                UserImportExcelVO.builder().username("yunai").deptId(1L).email("yunai@iocoder.cn").mobile("15601691300")
-                        .nickname("瓒呯骇绠$悊鍛�").status(CommonStatusEnum.ENABLE.getStatus()).sex(SexEnum.MALE.getSex()).build(),
-                UserImportExcelVO.builder().username("yuanma").deptId(2L).email("yuanma@iocoder.cn").mobile("15601701300")
-                        .nickname("婧愮爜").status(CommonStatusEnum.DISABLE.getStatus()).sex(SexEnum.FEMALE.getSex()).build()
+                UserImportExcelVO.builder().username("zhangsan").nickname("寮犱笁").dept("缁煎悎绠$悊閮�").post("鏅�氬憳宸�")
+                        .email("zhangsan@example.com").mobile("15601691300").password("123456")
+                        .status(CommonStatusEnum.ENABLE.getStatus()).sex(SexEnum.MALE.getSex()).build(),
+                UserImportExcelVO.builder().username("lisi").nickname("鏉庡洓").dept("缁煎悎绠$悊閮�").post("鏅�氬憳宸ャ�佺彮缁勯暱")
+                        .email("lisi@example.com").mobile("15601701300").password("123456")
+                        .status(CommonStatusEnum.ENABLE.getStatus()).sex(SexEnum.FEMALE.getSex()).build()
         );
         // 杈撳嚭
         ExcelUtils.write(response, "鐢ㄦ埛瀵煎叆妯℃澘.xls", "鐢ㄦ埛鍒楄〃", UserImportExcelVO.class, list);
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserImportExcelVO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserImportExcelVO.java
index f6400e1..be34e16 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserImportExcelVO.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserImportExcelVO.java
@@ -11,6 +11,9 @@
 
 /**
  * 鐢ㄦ埛 Excel 瀵煎叆 VO
+ *
+ * 鍒楀悕涓庨〉闈笂鐨勫彨娉曚繚鎸佷竴鑷达紙鐢ㄦ埛璐﹀彿 / 鐢ㄦ埛鏄电О锛夛紝閬垮厤鐢ㄦ埛鎷垮埌妯℃澘鍚庢壘涓嶅埌瀵瑰簲鍒椼��
+ * 閮ㄩ棬銆佸矖浣嶄袱鍒楀~銆屽悕绉般�嶅嵆鍙紙閮ㄩ棬涔熷彲浠ョ户缁~閮ㄩ棬缂栧彿锛夛紝瀵煎叆鏃朵細鍦ㄧ郴缁熼噷缈昏瘧鎴愮紪鍙枫��
  */
 @Data
 @Builder
@@ -18,14 +21,17 @@
 @NoArgsConstructor
 public class UserImportExcelVO {
 
-    @ExcelProperty("鐧诲綍鍚嶇О")
+    @ExcelProperty("鐢ㄦ埛璐﹀彿")
     private String username;
 
-    @ExcelProperty("鐢ㄦ埛鍚嶇О")
+    @ExcelProperty("鐢ㄦ埛鏄电О")
     private String nickname;
 
-    @ExcelProperty("閮ㄩ棬缂栧彿")
-    private Long deptId;
+    @ExcelProperty("閮ㄩ棬")
+    private String dept;
+
+    @ExcelProperty("宀椾綅")
+    private String post;
 
     @ExcelProperty("鐢ㄦ埛閭")
     private String email;
@@ -41,4 +47,7 @@
     @DictFormat(DictTypeConstants.COMMON_STATUS)
     private Integer status;
 
+    @ExcelProperty("鍒濆瀵嗙爜")
+    private String password;
+
 }
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
index 5af11a3..4a98a12 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
@@ -47,6 +47,11 @@
     ErrorCode USER_IMPORT_INIT_PASSWORD = new ErrorCode(1_002_003_009, "鍒濆瀵嗙爜涓嶈兘涓虹┖");
     ErrorCode USER_MOBILE_NOT_EXISTS = new ErrorCode(1_002_003_010, "璇ユ墜鏈哄彿灏氭湭娉ㄥ唽");
     ErrorCode USER_REGISTER_DISABLED = new ErrorCode(1_002_003_011, "娉ㄥ唽鍔熻兘宸插叧闂�");
+    ErrorCode USER_IMPORT_DEPT_NOT_EXISTS = new ErrorCode(1_002_003_012, "瀵煎叆鐨勯儴闂ㄤ笉瀛樺湪");
+    ErrorCode USER_IMPORT_DEPT_NAME_DUPLICATE = new ErrorCode(1_002_003_013, "瀵煎叆鐨勯儴闂ㄥ悕绉板瓨鍦ㄥ涓儴闂�");
+    ErrorCode USER_IMPORT_POST_NOT_EXISTS = new ErrorCode(1_002_003_014, "瀵煎叆鐨勫矖浣嶄笉瀛樺湪");
+    ErrorCode USER_IMPORT_POST_EMPTY = new ErrorCode(1_002_003_015, "瀵煎叆鐨勫矖浣嶄笉鑳戒负绌�");
+    ErrorCode USER_IMPORT_MASTER_DATA_DISABLED = new ErrorCode(1_002_003_016, "瀵煎叆鐨勯儴闂ㄦ垨宀椾綅宸茶鍋滅敤");
 
     // ========== 閮ㄩ棬妯″潡 1-002-004-000 ==========
     ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1_002_004_000, "宸茬粡瀛樺湪璇ュ悕瀛楃殑閮ㄩ棬");
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