| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | /** |
| | | * 后台用户 Service 实现类 |
| | | * |
| | | * @author 芋道源码 |
| | | * @author 超级管理员 |
| | | */ |
| | | @Service("adminUserService") |
| | | @Slf4j |
| | |
| | | |
| | | @Resource |
| | | private AdminUserProducer adminUserProducer; |
| | | |
| | | @Resource |
| | | private ApplicationEventPublisher eventPublisher; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | |
| | | // 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) { |
| | |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void updateUserProfile(Long id, UserProfileUpdateReqVO reqVO) { |
| | | // 1. 校验正确性 |
| | | AdminUserDO oldUser = validateUserExists(id); |
| | |
| | | |
| | | // 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())); |
| | | } |
| | | |
| | | /** |
| | |
| | | @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); |
| | | |
| | |
| | | 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 |
| | |
| | | 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 更新 |
| | |
| | | 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; |
| | |
| | | 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; |
| | | } |
| | |
| | | } |
| | | 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); |