Przeglądaj źródła

部门导入功能

xy 1 tydzień temu
rodzic
commit
acab788c36

+ 2 - 1
tz-module-system/tz-module-system-api/src/main/java/cn/start/tz/module/system/enums/ErrorCodeConstants.java

@@ -55,7 +55,8 @@ public interface ErrorCodeConstants {
     ErrorCode DEPT_EXITS_CHILDREN = new ErrorCode(1_002_004_003, "存在子部门,无法删除");
     ErrorCode DEPT_PARENT_ERROR = new ErrorCode(1_002_004_004, "不能设置自己为父部门");
     ErrorCode DEPT_NOT_ENABLE = new ErrorCode(1_002_004_006, "部门({})不处于开启状态,不允许选择");
-    ErrorCode DEPT_PARENT_IS_CHILD = new ErrorCode(1_002_004_007, "不能设置自己的子部门为父部门");
+    ErrorCode DEPT_PARENT_IS_CHILD = new ErrorCode(1_002_004_005, "不能设置自己的子部门为父部门");
+    ErrorCode DEPT_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_002_004_006, "导入部门数据不能为空!");
 
     // ========== 岗位模块 1-002-005-000 ==========
     ErrorCode POST_NOT_FOUND = new ErrorCode(1_002_005_000, "当前岗位不存在");

+ 26 - 0
tz-module-system/tz-module-system-biz/src/main/java/cn/start/tz/module/system/controller/admin/dept/DeptController.java

@@ -3,10 +3,13 @@ package cn.start.tz.module.system.controller.admin.dept;
 import cn.start.tz.framework.common.enums.CommonStatusEnum;
 import cn.start.tz.framework.common.pojo.CommonResult;
 import cn.start.tz.framework.common.util.object.BeanUtils;
+import cn.start.tz.framework.excel.core.util.ExcelUtils;
 import cn.start.tz.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
 import cn.start.tz.module.system.controller.admin.dept.vo.dept.DeptRespVO;
 import cn.start.tz.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
 import cn.start.tz.module.system.controller.admin.dept.vo.dept.DeptSimpleRespVO;
+import cn.start.tz.module.system.controller.admin.dept.vo.dept.DeptImportExcelVO;
+import cn.start.tz.module.system.controller.admin.dept.vo.dept.DeptImportRespVO;
 import cn.start.tz.module.system.dal.dataobject.dept.DeptDO;
 import cn.start.tz.module.system.service.dept.DeptService;
 import io.swagger.v3.oas.annotations.Operation;
@@ -14,9 +17,13 @@ import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
 import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
+import java.io.IOException;
+import java.util.Arrays;
 import java.util.List;
 
 import static cn.start.tz.framework.common.pojo.CommonResult.success;
@@ -80,4 +87,23 @@ public class DeptController {
         return success(BeanUtils.toBean(dept, DeptRespVO.class));
     }
 
+    @GetMapping("/get-import-template")
+    @Operation(summary = "获得导入部门模板")
+    public void importTemplate(HttpServletResponse response) throws IOException {
+        List<DeptImportExcelVO> list = Arrays.asList(
+                DeptImportExcelVO.builder().name("技术部").code("10001").parentName("").sort(1)
+                        .leader("").phone("").email("").tenantId("").build(),
+                DeptImportExcelVO.builder().name("前端组").code("10002").parentName("技术部").sort(1)
+                        .leader("").phone("").email("").tenantId("").build()
+        );
+        ExcelUtils.write(response, "部门导入模板.xls", "部门列表", DeptImportExcelVO.class, list);
+    }
+
+    @PostMapping("/import")
+    @Operation(summary = "导入部门")
+    public CommonResult<DeptImportRespVO> importExcel(@RequestParam("file") MultipartFile file) throws Exception {
+        List<DeptImportExcelVO> list = ExcelUtils.read(file, DeptImportExcelVO.class);
+        return success(deptService.importDeptList(list));
+    }
+
 }

+ 46 - 0
tz-module-system/tz-module-system-biz/src/main/java/cn/start/tz/module/system/controller/admin/dept/vo/dept/DeptImportExcelVO.java

@@ -0,0 +1,46 @@
+package cn.start.tz.module.system.controller.admin.dept.vo.dept;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+/**
+ * 部门导入 Excel VO
+ *
+ * @author tz
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Accessors(chain = false)
+public class DeptImportExcelVO {
+
+    @ExcelProperty("部门名称")
+    private String name;
+
+    @ExcelProperty("部门代码")
+    private String code;
+
+    @ExcelProperty("父部门名称")
+    private String parentName;
+
+    @ExcelProperty("显示顺序")
+    private Integer sort;
+
+    @ExcelProperty("负责人用户名")
+    private String leader;
+
+    @ExcelProperty("联系电话")
+    private String phone;
+
+    @ExcelProperty("邮箱")
+    private String email;
+
+    @ExcelProperty("租户编号")
+    private String tenantId;
+
+}

+ 26 - 0
tz-module-system/tz-module-system-biz/src/main/java/cn/start/tz/module/system/controller/admin/dept/vo/dept/DeptImportRespVO.java

@@ -0,0 +1,26 @@
+package cn.start.tz.module.system.controller.admin.dept.vo.dept;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 部门导入 Response VO
+ *
+ * @author tz
+ */
+@Schema(description = "管理后台 - 部门导入 Response VO")
+@Data
+@Builder
+public class DeptImportRespVO {
+
+    @Schema(description = "创建成功的部门名称数组", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<String> createDeptNames;
+
+    @Schema(description = "导入失败的部门集合,key 为部门名称,value 为失败原因", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Map<String, String> failureDeptNames;
+
+}

+ 4 - 0
tz-module-system/tz-module-system-biz/src/main/java/cn/start/tz/module/system/dal/mysql/dept/DeptMapper.java

@@ -24,6 +24,10 @@ public interface DeptMapper extends BaseMapperX<DeptDO> {
         return selectOne(DeptDO::getParentId, parentId, DeptDO::getName, name);
     }
 
+    default DeptDO selectByCode(String code) {
+        return selectOne(DeptDO::getCode, code);
+    }
+
     default Long selectCountByParentId(String parentId) {
         return selectCount(DeptDO::getParentId, parentId);
     }

+ 10 - 0
tz-module-system/tz-module-system-biz/src/main/java/cn/start/tz/module/system/service/dept/DeptService.java

@@ -3,6 +3,8 @@ package cn.start.tz.module.system.service.dept;
 import cn.start.tz.framework.common.util.collection.CollectionUtils;
 import cn.start.tz.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
 import cn.start.tz.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
+import cn.start.tz.module.system.controller.admin.dept.vo.dept.DeptImportExcelVO;
+import cn.start.tz.module.system.controller.admin.dept.vo.dept.DeptImportRespVO;
 import cn.start.tz.module.system.dal.dataobject.dept.DeptDO;
 
 import java.util.*;
@@ -121,4 +123,12 @@ public interface DeptService {
     DeptDO getDeptByCode(String code);
 
     DeptDO getDeptByName(String name);
+
+    /**
+     * 导入部门列表
+     *
+     * @param importDepts  导入的部门列表
+     * @return 导入结果
+     */
+    DeptImportRespVO importDeptList(List<DeptImportExcelVO> importDepts);
 }

+ 95 - 0
tz-module-system/tz-module-system-biz/src/main/java/cn/start/tz/module/system/service/dept/DeptServiceImpl.java

@@ -10,8 +10,12 @@ import cn.start.tz.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.start.tz.framework.mybatis.core.query.QueryWrapperX;
 import cn.start.tz.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
 import cn.start.tz.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
+import cn.start.tz.module.system.controller.admin.dept.vo.dept.DeptImportExcelVO;
+import cn.start.tz.module.system.controller.admin.dept.vo.dept.DeptImportRespVO;
 import cn.start.tz.module.system.dal.dataobject.dept.DeptDO;
+import cn.start.tz.module.system.dal.dataobject.user.AdminUserDO;
 import cn.start.tz.module.system.dal.mysql.dept.DeptMapper;
+import cn.start.tz.module.system.dal.mysql.user.AdminUserMapper;
 import cn.start.tz.module.system.dal.redis.RedisKeyConstants;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.google.common.annotations.VisibleForTesting;
@@ -20,6 +24,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.cache.annotation.CacheEvict;
 import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import java.util.*;
@@ -41,6 +46,9 @@ public class DeptServiceImpl implements DeptService {
     @Resource
     private DeptMapper deptMapper;
 
+    @Resource
+    private AdminUserMapper adminUserMapper;
+
     @Override
     @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
             allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存
@@ -258,4 +266,91 @@ public class DeptServiceImpl implements DeptService {
         return deptDO;
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public DeptImportRespVO importDeptList(List<DeptImportExcelVO> importDepts) {
+        if (CollUtil.isEmpty(importDepts)) {
+            throw exception(DEPT_IMPORT_LIST_IS_EMPTY);
+        }
+
+        DeptImportRespVO respVO = DeptImportRespVO.builder()
+                .createDeptNames(new ArrayList<>())
+                .failureDeptNames(new LinkedHashMap<>())
+                .build();
+
+        importDepts.forEach(importDept -> {
+            try {
+                // 1. 基本字段非空校验
+                if (importDept.getName() == null || importDept.getName().trim().isEmpty()) {
+                    respVO.getFailureDeptNames().put(importDept.getName() == null ? "未知" : importDept.getName(), "部门名称不能为空");
+                    return;
+                }
+                if (importDept.getCode() == null || importDept.getCode().trim().isEmpty()) {
+                    respVO.getFailureDeptNames().put(importDept.getName(), "部门代码不能为空");
+                    return;
+                }
+
+                // 2. 部门代码唯一性校验
+                DeptDO existByCode = deptMapper.selectByCode(importDept.getCode().trim());
+                if (existByCode != null) {
+                    respVO.getFailureDeptNames().put(importDept.getName(), "部门代码已存在: " + importDept.getCode());
+                    return;
+                }
+
+                // 3. 解析父部门
+                String parentId = DeptDO.PARENT_ID_ROOT;
+                if (importDept.getParentName() != null && !importDept.getParentName().trim().isEmpty()) {
+                    DeptDO parentDept = deptMapper.selectOne(
+                            new LambdaQueryWrapperX<DeptDO>()
+                                    .eq(DeptDO::getName, importDept.getParentName().trim()), false);
+                    if (parentDept == null) {
+                        respVO.getFailureDeptNames().put(importDept.getName(), "父部门不存在: " + importDept.getParentName());
+                        return;
+                    }
+                    parentId = parentDept.getId();
+                }
+
+                // 4. 同父部门下名称唯一性校验
+                DeptDO existByName = deptMapper.selectByParentIdAndName(parentId, importDept.getName().trim());
+                if (existByName != null) {
+                    respVO.getFailureDeptNames().put(importDept.getName(), "同父部门下部门名称已存在");
+                    return;
+                }
+
+                // 5.负责人账号
+                String leaderUserId = "";
+                if (importDept.getLeader() != null && !importDept.getLeader().trim().isEmpty()){
+                    AdminUserDO leaderUser = adminUserMapper.selectByUsername(importDept.getLeader());
+                    if (leaderUser == null) {
+                        respVO.getFailureDeptNames().put(importDept.getName(), "负责人用户名不存在");
+                        return;
+                    }else{
+                        leaderUserId = leaderUser.getId();
+                    }
+                }
+
+                // 6. 构建并插入
+                DeptDO dept = new DeptDO();
+                dept.setName(importDept.getName().trim());
+                dept.setCode(importDept.getCode().trim());
+                dept.setParentId(parentId);
+                dept.setSort(importDept.getSort() != null ? importDept.getSort() : 0);
+                dept.setPhone(importDept.getPhone());
+                dept.setEmail(importDept.getEmail());
+                dept.setTenantId(importDept.getTenantId());
+                dept.setLeaderUserId(leaderUserId);
+                dept.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认启用
+
+                deptMapper.insert(dept);
+                respVO.getCreateDeptNames().add(importDept.getName());
+
+            } catch (Exception e) {
+                log.error("[importDeptList] 导入部门失败: {}", importDept.getName(), e);
+                respVO.getFailureDeptNames().put(importDept.getName(), "系统异常: " + e.getMessage());
+            }
+        });
+
+        return respVO;
+    }
+
 }