| | |
| | | import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; |
| | | import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper; |
| | | import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper; |
| | | import cn.iocoder.yudao.module.system.mq.producer.user.AdminUserProducer; |
| | | import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO; |
| | | import cn.iocoder.yudao.module.system.service.dept.DeptService; |
| | | import cn.iocoder.yudao.module.system.service.dept.PostService; |
| | | import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; |
| | | import cn.iocoder.yudao.module.system.service.permission.PermissionService; |
| | | import cn.iocoder.yudao.module.system.service.tenant.TenantService; |
| | | import cn.iocoder.yudao.framework.operatelog.core.util.OperateLogUtils; |
| | | import com.google.common.annotations.VisibleForTesting; |
| | | import com.mzt.logapi.context.LogRecordContext; |
| | | import com.mzt.logapi.service.impl.DiffParseFunction; |
| | |
| | | /** |
| | | * 后台用户 Service 实现类 |
| | | * |
| | | * @author 芋道源码 |
| | | * @author 超级管理员 |
| | | */ |
| | | @Service("adminUserService") |
| | | @Slf4j |
| | |
| | | @Resource |
| | | private ConfigApi configApi; |
| | | |
| | | @Resource |
| | | private AdminUserProducer adminUserProducer; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_CREATE_SUB_TYPE, bizNo = "{{#user.id}}", |
| | |
| | | if (CollectionUtil.isNotEmpty(user.getPostIds())) { |
| | | userPostMapper.insertBatch(convertList(user.getPostIds(), |
| | | postId -> new UserPostDO().setUserId(user.getId()).setPostId(postId))); |
| | | } |
| | | // 2.3 分配用户角色 |
| | | if (CollUtil.isNotEmpty(createReqVO.getRoleIds())) { |
| | | permissionService.assignUserRole(user.getId(), createReqVO.getRoleIds()); |
| | | } |
| | | |
| | | // 3. 记录操作日志上下文 |
| | |
| | | userMapper.updateById(updateObj); |
| | | // 2.2 更新岗位 |
| | | updateUserPost(updateReqVO, updateObj); |
| | | // 2.3 昵称 / 头像变化时,发送消息供下游订阅(如 IM 模块推 FRIEND_INFO_UPDATED) |
| | | publishUserProfileUpdatedIfChanged(oldUser, updateReqVO.getNickname(), updateReqVO.getAvatar()); |
| | | |
| | | // 3. 记录操作日志上下文 |
| | | LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldUser, UserSaveReqVO.class)); |
| | |
| | | |
| | | @Override |
| | | public void updateUserProfile(Long id, UserProfileUpdateReqVO reqVO) { |
| | | // 校验正确性 |
| | | validateUserExists(id); |
| | | // 1. 校验正确性 |
| | | AdminUserDO oldUser = validateUserExists(id); |
| | | validateEmailUnique(id, reqVO.getEmail()); |
| | | validateMobileUnique(id, reqVO.getMobile()); |
| | | // 执行更新 |
| | | |
| | | // 2. 执行更新 |
| | | userMapper.updateById(BeanUtils.toBean(reqVO, AdminUserDO.class).setId(id)); |
| | | |
| | | // 3. 昵称 / 头像变化时,发送消息供下游订阅(如 IM 模块推 FRIEND_INFO_UPDATED) |
| | | publishUserProfileUpdatedIfChanged(oldUser, reqVO.getNickname(), reqVO.getAvatar()); |
| | | } |
| | | |
| | | /** |
| | | * 仅当 nickname 或 avatar 跟旧值不一致时,发送 AdminUserProfileUpdateMessage |
| | | */ |
| | | private void publishUserProfileUpdatedIfChanged(AdminUserDO oldUser, String newNickname, String newAvatar) { |
| | | boolean nicknameChanged = newNickname != null && !ObjUtil.equal(oldUser.getNickname(), newNickname); |
| | | boolean avatarChanged = newAvatar != null && !ObjUtil.equal(oldUser.getAvatar(), newAvatar); |
| | | if (!nicknameChanged && !avatarChanged) { |
| | | return; |
| | | } |
| | | adminUserProducer.sendUserProfileUpdateMessage(oldUser.getId(), |
| | | nicknameChanged ? newNickname : null, |
| | | avatarChanged ? newAvatar : null); |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | return userMapper.selectByIds(ids); |
| | | } |
| | | |
| | | public List<AdminUserDO> getUserListAll() { |
| | | return userMapper.selectList(); |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public void validateUserList(Collection<Long> ids) { |
| | |
| | | } |
| | | |
| | | @Override |
| | | @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_IMPORT_SUB_TYPE, bizNo = "0", |
| | | success = SYSTEM_USER_IMPORT_SUCCESS) |
| | | @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入 |
| | | public UserImportRespVO importUserList(List<UserImportExcelVO> importUsers, boolean isUpdateSupport) { |
| | | // 1.1 参数校验 |
| | |
| | | if (StrUtil.isEmpty(initPassword)) { |
| | | throw exception(USER_IMPORT_INIT_PASSWORD); |
| | | } |
| | | // 1.3 预加载部门:名称 -> 部门,编号 -> 部门,供两种写法解析 |
| | | List<DeptDO> allDepts = deptService.getDeptList(new DeptListReqVO()); |
| | | Map<String, DeptDO> deptByName = new HashMap<>(allDepts.size()); |
| | | for (DeptDO dept : allDepts) { |
| | | deptByName.put(dept.getName().trim(), dept); |
| | | } |
| | | |
| | | // 2. 遍历,逐个创建 or 更新 |
| | | UserImportRespVO respVO = UserImportRespVO.builder().createUsernames(new ArrayList<>()) |
| | |
| | | AtomicInteger index = new AtomicInteger(1); |
| | | importUsers.forEach(importUser -> { |
| | | int currentIndex = index.getAndIncrement(); |
| | | // 2.1.1 校验字段是否符合要求 |
| | | 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()); |
| | | // 用于报错的定位 key:优先登录名,否则「第 N 行」 |
| | | String rowKey = StrUtil.blankToDefault(importUser.getUsername(), "第 " + currentIndex + " 行"); |
| | | |
| | | // 2.1 解析部门:优先按名称,兼容历史模板填的部门编号 |
| | | DeptDO dept = resolveImportDept(importUser.getDeptName(), deptByName); |
| | | if (dept == null && StrUtil.isNotBlank(importUser.getDeptName())) { |
| | | respVO.getFailureUsernames().put(rowKey, |
| | | "部门「" + importUser.getDeptName().trim() + "」不存在,请填写部门管理中的完整部门名称"); |
| | | return; |
| | | } |
| | | // 2.1.2 校验,判断是否有不符合的原因 |
| | | Long deptId = dept != null ? dept.getId() : null; |
| | | importUser.setDeptName(dept != null ? dept.getName() : importUser.getDeptName()); |
| | | |
| | | // 2.2 校验字段是否符合要求 |
| | | try { |
| | | validateUserForCreateOrUpdate(null, null, importUser.getMobile(), importUser.getEmail(), |
| | | importUser.getDeptId(), null); |
| | | ValidationUtils.validate(BeanUtils.toBean(importUser, UserSaveReqVO.class) |
| | | .setPassword(initPassword).setDeptId(deptId)); |
| | | } catch (ConstraintViolationException ex) { |
| | | respVO.getFailureUsernames().put(rowKey, ex.getMessage()); |
| | | return; |
| | | } |
| | | // 2.3 校验业务约束(手机号/邮箱/部门是否存在) |
| | | try { |
| | | validateUserForCreateOrUpdate(null, null, importUser.getMobile(), importUser.getEmail(), deptId, null); |
| | | } catch (ServiceException ex) { |
| | | respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage()); |
| | | respVO.getFailureUsernames().put(rowKey, ex.getMessage()); |
| | | return; |
| | | } |
| | | |
| | | // 2.2.1 判断如果不存在,在进行插入 |
| | | // 2.4 判断如果不存在,在进行插入 |
| | | AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername()); |
| | | if (existUser == null) { |
| | | userMapper.insert(BeanUtils.toBean(importUser, AdminUserDO.class) |
| | | .setPassword(encodePassword(initPassword)).setPostIds(new HashSet<>())); // 设置默认密码及空岗位编号数组 |
| | | AdminUserDO newUser = BeanUtils.toBean(importUser, AdminUserDO.class); |
| | | newUser.setDeptId(deptId); |
| | | newUser.setPassword(encodePassword(initPassword)).setPostIds(new HashSet<>()); // 默认密码及空岗位 |
| | | userMapper.insert(newUser); |
| | | respVO.getCreateUsernames().add(importUser.getUsername()); |
| | | return; |
| | | } |
| | | // 2.2.2 如果存在,判断是否允许更新 |
| | | // 2.5 如果存在,判断是否允许更新 |
| | | if (!isUpdateSupport) { |
| | | respVO.getFailureUsernames().put(importUser.getUsername(), USER_USERNAME_EXISTS.getMsg()); |
| | | respVO.getFailureUsernames().put(rowKey, USER_USERNAME_EXISTS.getMsg()); |
| | | return; |
| | | } |
| | | AdminUserDO updateUser = BeanUtils.toBean(importUser, AdminUserDO.class); |
| | | updateUser.setId(existUser.getId()); |
| | | updateUser.setDeptId(deptId); |
| | | userMapper.updateById(updateUser); |
| | | respVO.getUpdateUsernames().add(importUser.getUsername()); |
| | | }); |
| | | // 记录导入结果汇总,供 @LogRecord 的 success 文案引用 |
| | | OperateLogUtils.putImportSummary(respVO.getCreateUsernames(), respVO.getUpdateUsernames(), |
| | | respVO.getFailureUsernames()); |
| | | return respVO; |
| | | } |
| | | |
| | | /** |
| | | * 解析导入行里的部门 |
| | | * |
| | | * 优先按部门名称精确匹配;若填写的是纯数字,再按部门编号匹配(兼容历史模板)。 |
| | | * |
| | | * @param raw 导入填写的部门文本 |
| | | * @param byName 部门名称 -> 部门 |
| | | * @param byId 部门编号 -> 部门 |
| | | * @return 匹配到的部门,未填写返回 null,填了但匹配不到也返回 null |
| | | */ |
| | | /** |
| | | * 解析导入行里的部门名称 |
| | | * |
| | | * 只按部门名称精确匹配(不再兼容数字编号,避免使用者填错列导致静默错配)。 |
| | | * |
| | | * @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 |
| | |
| | | } |
| | | |
| | | @Override |
| | | public List<AdminUserDO> getDeptUsers(Collection<Long> deptIds) { |
| | | return userMapper.selectListByDeptIds(deptIds); |
| | | } |
| | | |
| | | @Override |
| | | public boolean isPasswordMatch(String rawPassword, String encodedPassword) { |
| | | return passwordEncoder.matches(rawPassword, encodedPassword); |
| | | } |