xiaoyi
5 天以前 883f5e73fbe7458fd760dbe8560dd5d8757e04cd
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java
@@ -14,12 +14,16 @@
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;
import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserImportRespVO;
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.api.user.dto.AdminUserSyncReqDTO;
import cn.iocoder.yudao.module.system.api.user.event.AdminUserDeletedEvent;
import cn.iocoder.yudao.module.system.api.user.event.AdminUserUpdatedEvent;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
@@ -38,6 +42,7 @@
import jakarta.annotation.Resource;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@@ -91,6 +96,9 @@
    @Resource
    private AdminUserProducer adminUserProducer;
    @Resource
    private ApplicationEventPublisher eventPublisher;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -172,6 +180,11 @@
        // 3. 记录操作日志上下文
        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldUser, UserSaveReqVO.class));
        LogRecordContext.putVariable("user", oldUser);
        // 4. 通知 hrm 模块同步员工档案(没有员工档案的账号会被监听器直接跳过)
        eventPublisher.publishEvent(new AdminUserUpdatedEvent(updateReqVO.getId(),
                updateReqVO.getNickname(), updateReqVO.getMobile(), updateReqVO.getDeptId(),
                updateReqVO.getSex(), updateReqVO.getEmail()));
    }
    private void updateUserPost(UserSaveReqVO reqVO, AdminUserDO updateObj) {
@@ -197,6 +210,7 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateUserProfile(Long id, UserProfileUpdateReqVO reqVO) {
        // 1. 校验正确性
        AdminUserDO oldUser = validateUserExists(id);
@@ -208,6 +222,10 @@
        // 3. 昵称 / 头像变化时,发送消息供下游订阅(如 IM 模块推 FRIEND_INFO_UPDATED)
        publishUserProfileUpdatedIfChanged(oldUser, reqVO.getNickname(), reqVO.getAvatar());
        // 4. 通知 hrm 模块同步员工档案(没有员工档案的账号会被监听器直接跳过)
        eventPublisher.publishEvent(new AdminUserUpdatedEvent(id,
                reqVO.getNickname(), reqVO.getMobile(), null, reqVO.getSex(), reqVO.getEmail()));
    }
    /**
@@ -273,6 +291,18 @@
    @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_DELETE_SUB_TYPE, bizNo = "{{#id}}",
            success = SYSTEM_USER_DELETE_SUCCESS)
    public void deleteUser(Long id) {
        doDeleteUser(id);
        // 通知 hrm 模块联动删除员工档案(没有员工档案的账号会被监听器直接跳过)
        eventPublisher.publishEvent(new AdminUserDeletedEvent(id));
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteUserSilently(Long id) {
        doDeleteUser(id);
    }
    private void doDeleteUser(Long id) {
        // 1. 校验用户存在
        AdminUserDO user = validateUserExists(id);
@@ -298,6 +328,44 @@
            permissionService.processUserDeleted(id);
            userPostMapper.deleteByUserId(id);
        });
        // 3. 通知 hrm 模块联动删除员工档案
        ids.forEach(id -> eventPublisher.publishEvent(new AdminUserDeletedEvent(id)));
    }
    @Override
    public void updateUserForEmployee(AdminUserSyncReqDTO reqDTO) {
        if (reqDTO == null || reqDTO.getUserId() == null) {
            return;
        }
        // 1. 校验用户存在
        AdminUserDO user = validateUserExists(reqDTO.getUserId());
        // 2. 校验手机号 / 邮箱唯一(与旧值不同时才校验)
        if (StrUtil.isNotBlank(reqDTO.getMobile()) && !ObjUtil.equal(user.getMobile(), reqDTO.getMobile())) {
            validateMobileUnique(reqDTO.getUserId(), reqDTO.getMobile());
        }
        if (StrUtil.isNotBlank(reqDTO.getEmail()) && !ObjUtil.equal(user.getEmail(), reqDTO.getEmail())) {
            validateEmailUnique(reqDTO.getUserId(), reqDTO.getEmail());
        }
        // 3. 只更新非空字段
        AdminUserDO updateObj = new AdminUserDO();
        updateObj.setId(reqDTO.getUserId());
        if (StrUtil.isNotBlank(reqDTO.getNickname())) {
            updateObj.setNickname(reqDTO.getNickname());
        }
        if (StrUtil.isNotBlank(reqDTO.getMobile())) {
            updateObj.setMobile(reqDTO.getMobile());
        }
        if (reqDTO.getDeptId() != null) {
            updateObj.setDeptId(reqDTO.getDeptId());
        }
        if (reqDTO.getSex() != null) {
            updateObj.setSex(reqDTO.getSex());
        }
        if (StrUtil.isNotBlank(reqDTO.getEmail())) {
            updateObj.setEmail(reqDTO.getEmail());
        }
        userMapper.updateById(updateObj);
    }
    @Override
@@ -512,10 +580,13 @@
        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 预加载部门:名称 -> 部门,用于按名称解析(模板要求填部门名称,而非数据库主键 ID)
        List<DeptDO> allDepts = deptService.getDeptList(new DeptListReqVO());
        Map<String, DeptDO> deptByName = new HashMap<>(Math.max(allDepts.size(), 1));
        for (DeptDO dept : allDepts) {
            deptByName.put(dept.getName().trim(), dept);
        }
        // 2. 遍历,逐个创建 or 更新
@@ -524,18 +595,36 @@
        AtomicInteger index = new AtomicInteger(1);
        importUsers.forEach(importUser -> {
            int currentIndex = index.getAndIncrement();
            String key = StrUtil.blankToDefault(importUser.getUsername(), "第 " + currentIndex + " 行");
            // 2.0 解析部门:按部门名称精确匹配,匹配不到给出明确提示(不再要求填数据库主键 ID)
            DeptDO importDept = resolveImportDept(importUser.getDeptName(), deptByName);
            if (importDept == null && StrUtil.isNotBlank(importUser.getDeptName())) {
                respVO.getFailureUsernames().put(key,
                        "部门「" + importUser.getDeptName().trim() + "」不存在,请填写部门管理中的完整部门名称");
                return;
            }
            Long importDeptId = importDept != null ? importDept.getId() : null;
            // 2.1.0 解析该行初始密码:Excel 填写了就用填写的,否则读取系统参数
            String effectivePassword = StrUtil.isNotBlank(importUser.getPassword())
                    ? importUser.getPassword() : initPassword;
            if (StrUtil.isBlank(effectivePassword)) {
                respVO.getFailureUsernames().put(key,
                        "未填写初始密码,且系统参数 system.user.init-password 未配置。"
                                + "请在 Excel 中填写该行初始密码,或先在参数设置中配置 system.user.init-password");
                return;
            }
            // 2.1.1 校验字段是否符合要求
            try {
                ValidationUtils.validate(BeanUtils.toBean(importUser, UserSaveReqVO.class).setPassword(initPassword));
                ValidationUtils.validate(BeanUtils.toBean(importUser, UserSaveReqVO.class)
                        .setPassword(effectivePassword).setDeptId(importDeptId));
            } 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);
                        importDeptId, null);
            } catch (ServiceException ex) {
                respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage());
                return;
@@ -545,7 +634,8 @@
            AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername());
            if (existUser == null) {
                userMapper.insert(BeanUtils.toBean(importUser, AdminUserDO.class)
                        .setPassword(encodePassword(initPassword)).setPostIds(new HashSet<>())); // 设置默认密码及空岗位编号数组
                        .setDeptId(importDeptId) // 部门已按名称解析,需显式回填 ID
                        .setPassword(encodePassword(effectivePassword)).setPostIds(new HashSet<>())); // 设置初始密码及空岗位编号数组
                respVO.getCreateUsernames().add(importUser.getUsername());
                return;
            }
@@ -556,12 +646,33 @@
            }
            AdminUserDO updateUser = BeanUtils.toBean(importUser, AdminUserDO.class);
            updateUser.setId(existUser.getId());
            updateUser.setDeptId(importDeptId); // 部门已按名称解析,需显式回填 ID
            // 密码单独处理:Excel 填写了才更新;未填写则置空,保持原密码不变(避免把明文写进库)
            updateUser.setPassword(StrUtil.isNotBlank(importUser.getPassword())
                    ? encodePassword(importUser.getPassword()) : null);
            userMapper.updateById(updateUser);
            respVO.getUpdateUsernames().add(importUser.getUsername());
        });
        return respVO;
    }
    /**
     * 解析导入行里的部门名称
     *
     * <p>只按部门名称精确匹配(不再要求填写数据库主键 ID),
     * 避免使用者无从得知部门 ID 导致导入必然失败。</p>
     *
     * @param raw    导入填写的部门名称
     * @param byName 部门名称 -> 部门
     * @return 匹配到的部门;未填写或匹配不到返回 null
     */
    private DeptDO resolveImportDept(String raw, Map<String, DeptDO> byName) {
        if (StrUtil.isBlank(raw)) {
            return null;
        }
        return byName.get(raw.trim());
    }
    @Override
    public List<AdminUserDO> getUserListByStatus(Integer status) {
        return userMapper.selectListByStatus(status);