深入解析 AdminUserServiceImpl:一个典型的后台用户管理实现

在现代的后台管理系统中,用户管理是一个核心模块。它涉及到用户信息的创建、更新、删除、查询以及权限管理等众多功能。本文将详细解析一个典型的后台用户管理实现类 AdminUserServiceImpl,帮助初学者理解其设计思路、实现细节以及关键点。

1. 项目结构与依赖

在开始之前,我们需要了解这个类所处的项目结构和依赖关系。AdminUserServiceImpl 是一个 Spring Boot 项目中的服务层实现类,位于 cn.iocoder.yudao.module.system.service.user 包下。它依赖于多个工具类和框架,例如:

  • Hutool:一个常用的 Java 工具库,提供了集合操作、字符串处理、文件操作等功能。
  • Spring Boot:用于依赖注入、事务管理、日志记录等。
  • Spring Security:用于密码加密。
  • MyBatis:用于数据库操作。

这些依赖为实现类提供了强大的功能支持。

2. 类的定义与注解

2.1 类的定义

@Service("adminUserService")
@Slf4j
public class AdminUserServiceImpl implements AdminUserService {
  • @Service("adminUserService"):这是一个 Spring 的注解,表示这是一个服务层组件,并且命名为 adminUserService。Spring 会自动扫描并实例化这个类。
  • @Slf4j:这是一个 Lombok 注解,用于生成日志记录器。它可以帮助我们方便地记录日志,而无需手动创建 Logger 实例。

2.2 接口实现

AdminUserServiceImpl 实现了 AdminUserService 接口。接口定义了用户管理模块的抽象方法,而实现类则提供了具体的实现逻辑。这种设计符合面向对象编程中的接口分离原则,使得代码更加模块化和可扩展。

3. 属性与依赖注入

在 AdminUserServiceImpl 中,使用了 @Resource 注解来注入多个依赖。例如:

@Resource
private AdminUserMapper userMapper;

@Resource
private DeptService deptService;

@Resource
private PostService postService;

@Resource
private PasswordEncoder passwordEncoder;
  • AdminUserMapper:用于操作用户数据的 MyBatis Mapper。
  • DeptService 和 PostService:用于处理部门和岗位相关的业务逻辑。
  • PasswordEncoder:用于密码加密,这是 Spring Security 提供的功能。

这些依赖是实现用户管理功能的基础,通过 Spring 的依赖注入机制,它们被自动注入到类中,使得代码更加简洁和清晰。

4. 核心方法解析

4.1 创建用户

@Transactional(rollbackFor = Exception.class)
@LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_CREATE_SUB_TYPE, bizNo = "{{#user.id}}",
        success = SYSTEM_USER_CREATE_SUCCESS)
public Long createUser(UserSaveReqVO createReqVO) {
  • @Transactional:这是一个事务管理注解,表示该方法的操作是一个事务。如果方法执行过程中抛出异常,事务会回滚,确保数据的一致性。
  • @LogRecord:这是一个自定义注解,用于记录操作日志。它记录了操作类型、子类型、业务编号以及成功信息。
方法逻辑
  1. 校验账户数量限制:通过 tenantService 检查当前租户的用户数量是否超过限制。
  2. 校验用户信息的合法性:调用 validateUserForCreateOrUpdate 方法,检查用户名、手机号、邮箱等是否唯一,以及部门和岗位的状态。
  3. 插入用户数据:将用户信息存入数据库,并对密码进行加密处理。
  4. 插入用户岗位关联数据:如果用户有岗位信息,则插入用户岗位关联表。

这个方法展示了如何通过事务控制和数据校验来确保用户创建操作的可靠性和合法性。

4.2 更新用户信息

@Transactional(rollbackFor = Exception.class)
@LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
        success = SYSTEM_USER_UPDATE_SUCCESS)
public void updateUser(UserSaveReqVO updateReqVO) {

更新用户信息的逻辑与创建用户类似,但有一些关键区别:

  1. 不更新密码:在更新用户信息时,密码字段被设置为 null,避免误更新密码。
  2. 更新用户岗位关联数据:通过 updateUserPost 方法,处理用户岗位的新增和删除操作。

这个方法展示了如何在更新操作中处理关联数据的变化。

4.3 删除用户

@Transactional(rollbackFor = Exception.class)
@LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_DELETE_SUB_TYPE, bizNo = "{{#id}}",
        success = SYSTEM_USER_DELETE_SUCCESS)
public void deleteUser(Long id) {

删除用户是一个相对简单的操作,但需要考虑关联数据的清理:

  1. 删除用户数据:从用户表中删除用户记录。
  2. 清理关联数据:删除用户权限和岗位关联数据。

通过事务管理,确保删除操作的完整性。

4.4 用户导入

@Transactional(rollbackFor = Exception.class)
public UserImportRespVO importUserList(List<UserImportExcelVO> importUsers, boolean isUpdateSupport) {

用户导入是一个复杂的功能,它支持从 Excel 文件中批量导入用户信息:

  1. 校验导入列表:检查导入的用户列表是否为空。
  2. 获取初始化密码:从配置中获取默认密码。
  3. 遍历用户列表:逐个处理用户信息,校验合法性并决定是创建新用户还是更新现有用户。
  4. 记录导入结果:返回导入成功的用户列表、更新的用户列表以及失败的用户列表。

这个方法展示了如何处理批量操作以及如何处理复杂的业务逻辑。

5. 数据校验

在用户管理中,数据校验是一个非常重要的环节。AdminUserServiceImpl 提供了多个校验方法,例如:

  • validateUserExists:校验用户是否存在。
  • validateUsernameUnique:校验用户名是否唯一。
  • validateEmailUnique:校验邮箱是否唯一。
  • validateMobileUnique:校验手机号是否唯一。

这些方法通过数据库查询来校验数据的合法性,并在发现问题时抛出异常。这种校验机制确保了用户数据的准确性和一致性。

6. 日志记录

在 AdminUserServiceImpl 中,日志记录是一个重要的功能。通过 @LogRecord 注解,每个关键操作都被记录下来,包括操作类型、子类型、业务编号以及成功或失败信息。这些日志可以帮助开发者快速定位问题,同时也可以用于审计和监控。

7. 总结

AdminUserServiceImpl 是一个典型的后台用户管理实现类,它涵盖了用户管理的几乎所有功能,包括用户创建、更新、删除、查询、密码管理、头像更新以及用户导入等。通过事务控制、数据校验、日志记录和依赖注入等技术,它确保了操作的可靠性、数据的合法性和代码的可维护性。

对于初学者来说,理解这个类的实现可以帮助你快速掌握如何设计和实现一个复杂的业务模块。希望本文的解析对你有所帮助!

package cn.iocoder.yudao.module.system.service.user;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils;
import cn.iocoder.yudao.module.infra.api.config.ConfigApi;
import cn.iocoder.yudao.module.infra.api.file.FileApi;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthRegisterReqVO;
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.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 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.service.dept.DeptService;
import cn.iocoder.yudao.module.system.service.dept.PostService;
import cn.iocoder.yudao.module.system.service.permission.PermissionService;
import cn.iocoder.yudao.module.system.service.tenant.TenantService;
import com.google.common.annotations.VisibleForTesting;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
import jakarta.annotation.Resource;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.*;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.system.enums.LogRecordConstants.*;

/**
 * 后台用户 Service 实现类
 * <p>
 * 该类实现了后台用户的核心业务逻辑,包括用户创建、更新、删除、查询、密码管理、头像更新、用户导入等功能。
 * 通过事务控制、日志记录和数据校验,确保操作的可靠性和数据的合法性。
 * </p>
 *
 * @author 芋道源码
 */
@Service("adminUserService")
@Slf4j
public class AdminUserServiceImpl implements AdminUserService {

    /**
     * 用户初始化密码的配置键,用于从配置中获取默认密码。
     */
    static final String USER_INIT_PASSWORD_KEY = "system.user.init-password";

    // 注入相关依赖
    @Resource
    private AdminUserMapper userMapper;

    @Resource
    private DeptService deptService;

    @Resource
    private PostService postService;

    @Resource
    private PermissionService permissionService;

    @Resource
    private PasswordEncoder passwordEncoder;

    @Resource
    @Lazy // 延迟加载,避免循环依赖
    private TenantService tenantService;

    @Resource
    private UserPostMapper userPostMapper;

    @Resource
    private FileApi fileApi;

    @Resource
    private ConfigApi configApi;

    /**
     * 创建用户
     * <p>
     * 该方法实现了用户创建的核心逻辑,包括校验用户信息的合法性、插入用户数据和岗位关联数据。
     * </p>
     *
     * @param createReqVO 创建用户的请求对象
     * @return 创建的用户ID
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_CREATE_SUB_TYPE, bizNo = "{{#user.id}}",
            success = SYSTEM_USER_CREATE_SUCCESS)
    public Long createUser(UserSaveReqVO createReqVO) {
        // 1.1 校验账户数量限制
        tenantService.handleTenantInfo(tenant -> {
            long count = userMapper.selectCount();
            if (count >= tenant.getAccountCount()) {
                throw exception(USER_COUNT_MAX, tenant.getAccountCount());
            }
        });

        // 1.2 校验用户信息的合法性
        validateUserForCreateOrUpdate(null, createReqVO.getUsername(),
                createReqVO.getMobile(), createReqVO.getEmail(), createReqVO.getDeptId(), createReqVO.getPostIds());

        // 2.1 插入用户数据
        AdminUserDO user = BeanUtils.toBean(createReqVO, AdminUserDO.class);
        user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认状态为启用
        user.setPassword(encodePassword(createReqVO.getPassword())); // 加密密码
        userMapper.insert(user);

        // 2.2 插入用户岗位关联数据
        if (CollectionUtil.isNotEmpty(user.getPostIds())) {
            userPostMapper.insertBatch(convertList(user.getPostIds(),
                    postId -> new UserPostDO().setUserId(user.getId()).setPostId(postId)));
        }

        // 3. 记录操作日志上下文
        LogRecordContext.putVariable("user", user);
        return user.getId();
    }

    /**
     * 用户注册
     * <p>
     * 该方法实现了用户注册的核心逻辑,与创建用户类似,但不涉及岗位信息。
     * </p>
     *
     * @param registerReqVO 用户注册的请求对象
     * @return 注册的用户ID
     */
    @Override
    public Long registerUser(AuthRegisterReqVO registerReqVO) {
        // 1.1 校验账户数量限制
        tenantService.handleTenantInfo(tenant -> {
            long count = userMapper.selectCount();
            if (count >= tenant.getAccountCount()) {
                throw exception(USER_COUNT_MAX, tenant.getAccountCount());
            }
        });

        // 1.2 校验用户信息的合法性
        validateUserForCreateOrUpdate(null, registerReqVO.getUsername(), null, null, null, null);

        // 2. 插入用户数据
        AdminUserDO user = BeanUtils.toBean(registerReqVO, AdminUserDO.class);
        user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认状态为启用
        user.setPassword(encodePassword(registerReqVO.getPassword())); // 加密密码
        userMapper.insert(user);
        return user.getId();
    }

    /**
     * 更新用户信息
     * <p>
     * 该方法实现了用户信息更新的核心逻辑,包括校验用户信息的合法性、更新用户数据和岗位关联数据。
     * </p>
     *
     * @param updateReqVO 更新用户的请求对象
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
            success = SYSTEM_USER_UPDATE_SUCCESS)
    public void updateUser(UserSaveReqVO updateReqVO) {
        updateReqVO.setPassword(null); // 特殊:此处不更新密码

        // 1. 校验用户信息的合法性
        AdminUserDO oldUser = validateUserForCreateOrUpdate(updateReqVO.getId(), updateReqVO.getUsername(),
                updateReqVO.getMobile(), updateReqVO.getEmail(), updateReqVO.getDeptId(), updateReqVO.getPostIds());

        // 2.1 更新用户数据
        AdminUserDO updateObj = BeanUtils.toBean(updateReqVO, AdminUserDO.class);
        userMapper.updateById(updateObj);

        // 2.2 更新用户岗位关联数据
        updateUserPost(updateReqVO, updateObj);

        // 3. 记录操作日志上下文
        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldUser, UserSaveReqVO.class));
        LogRecordContext.putVariable("user", oldUser);
    }

    /**
     * 更新用户岗位关联数据
     * <p>
     * 该方法用于处理用户岗位的新增和删除操作。
     * </p>
     *
     * @param reqVO        更新用户的请求对象
     * @param updateObj    更新后的用户对象
     */
    private void updateUserPost(UserSaveReqVO reqVO, AdminUserDO updateObj) {
        Long userId = reqVO.getId();
        Set<Long> dbPostIds = convertSet(userPostMapper.selectListByUserId(userId), UserPostDO::getPostId);

        // 计算新增和删除的岗位编号
        Set<Long> postIds = CollUtil.emptyIfNull(updateObj.getPostIds());
        Collection<Long> createPostIds = CollUtil.subtract(postIds, dbPostIds);
        Collection<Long> deletePostIds = CollUtil.subtract(dbPostIds, postIds);

        // 执行新增和删除操作
        if (!CollectionUtil.isEmpty(createPostIds)) {
            userPostMapper.insertBatch(convertList(createPostIds,
                    postId -> new UserPostDO().setUserId(userId).setPostId(postId)));
        }
        if (!CollectionUtil.isEmpty(deletePostIds)) {
            userPostMapper.deleteByUserIdAndPostId(userId, deletePostIds);
        }
    }

    /**
     * 更新用户登录信息
     * <p>
     * 该方法用于更新用户的登录IP和登录时间。
     * </p>
     *
     * @param id       用户ID
     * @param loginIp  登录IP
     */
    @Override
    public void updateUserLogin(Long id, String loginIp) {
        userMapper.updateById(new AdminUserDO()
                .setId(id)
                .setLoginIp(loginIp)
                .setLoginDate(LocalDateTime.now()));
    }

    /**
     * 更新用户个人资料
     * <p>
     * 该方法用于更新用户的个人资料,包括邮箱和手机号。
     * </p>
     *
     * @param id       用户ID
     * @param reqVO    更新个人资料的请求对象
     */
    @Override
    public void updateUserProfile(Long id, UserProfileUpdateReqVO reqVO) {
        // 校验用户存在
        validateUserExists(id);

        // 校验邮箱和手机号的唯一性
        validateEmailUnique(id, reqVO.getEmail());
        validateMobileUnique(id, reqVO.getMobile());

        // 执行更新操作
        userMapper.updateById(BeanUtils.toBean(reqVO, AdminUserDO.class).setId(id));
    }

    /**
     * 更新用户密码
     * <p>
     * 该方法用于更新用户的密码,支持通过旧密码验证。
     * </p>
     *
     * @param id               用户ID
     * @param reqVO            更新密码的请求对象
     */
    @Override
    public void updateUserPassword(Long id, UserProfileUpdatePasswordReqVO reqVO) {
        // 校验旧密码是否正确
        validateOldPassword(id, reqVO.getOldPassword());

        // 执行密码更新
        AdminUserDO updateObj = new AdminUserDO()
                .setId(id)
                .setPassword(encodePassword(reqVO.getNewPassword())); // 加密新密码
        userMapper.updateById(updateObj);
    }

    /**
     * 更新用户头像
     * <p>
     * 该方法用于更新用户的头像,支持上传头像文件。
     * </p>
     *
     * @param id           用户ID
     * @param avatarFile   头像文件的输入流
     * @return 更新后的头像路径
     */
    @Override
    public String updateUserAvatar(Long id, InputStream avatarFile) {
        // 校验用户存在
        validateUserExists(id);

        // 存储头像文件
        String avatar = fileApi.createFile(IoUtil.readBytes(avatarFile));

        // 更新用户头像路径
        AdminUserDO sysUserDO = new AdminUserDO()
                .setId(id)
                .setAvatar(avatar);
        userMapper.updateById(sysUserDO);
        return avatar;
    }

    /**
     * 更新用户密码(管理员操作)
     * <p>
     * 该方法用于管理员直接更新用户的密码,无需验证旧密码。
     * </p>
     *
     * @param id           用户ID
     * @param password     新密码
     */
    @Override
    @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_UPDATE_PASSWORD_SUB_TYPE, bizNo = "{{#id}}",
            success = SYSTEM_USER_UPDATE_PASSWORD_SUCCESS)
    public void updateUserPassword(Long id, String password) {
        // 校验用户存在
        AdminUserDO user = validateUserExists(id);

        // 更新用户密码
        AdminUserDO updateObj = new AdminUserDO()
                .setId(id)
                .setPassword(encodePassword(password)); // 加密密码
        userMapper.updateById(updateObj);

        // 记录操作日志上下文
        LogRecordContext.putVariable("user", user);
        LogRecordContext.putVariable("newPassword", updateObj.getPassword());
    }

    /**
     * 更新用户状态
     * <p>
     * 该方法用于更新用户的状态(启用/禁用)。
     * </p>
     *
     * @param id       用户ID
     * @param status   用户状态
     */
    @Override
    public void updateUserStatus(Long id, Integer status) {
        // 校验用户存在
        validateUserExists(id);

        // 更新用户状态
        AdminUserDO updateObj = new AdminUserDO()
                .setId(id)
                .setStatus(status);
        userMapper.updateById(updateObj);
    }

    /**
     * 删除用户
     * <p>
     * 该方法实现了用户删除的核心逻辑,包括删除用户数据、用户权限和岗位关联数据。
     * </p>
     *
     * @param id 用户ID
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_DELETE_SUB_TYPE, bizNo = "{{#id}}",
            success = SYSTEM_USER_DELETE_SUCCESS)
    public void deleteUser(Long id) {
        // 校验用户存在
        AdminUserDO user = validateUserExists(id);

        // 删除用户数据
        userMapper.deleteById(id);

        // 删除用户权限和岗位关联数据
        permissionService.processUserDeleted(id);
        userPostMapper.deleteByUserId(id);

        // 记录操作日志上下文
        LogRecordContext.putVariable("user", user);
    }

    /**
     * 根据用户名查询用户
     * <p>
     * 该方法用于根据用户名查询用户信息。
     * </p>
     *
     * @param username 用户名
     * @return 用户对象
     */
    @Override
    public AdminUserDO getUserByUsername(String username) {
        return userMapper.selectByUsername(username);
    }

    /**
     * 根据手机号查询用户
     * <p>
     * 该方法用于根据手机号查询用户信息。
     * </p>
     *
     * @param mobile 手机号
     * @return 用户对象
     */
    @Override
    public AdminUserDO getUserByMobile(String mobile) {
        return userMapper.selectByMobile(mobile);
    }

    /**
     * 分页查询用户
     * <p>
     * 该方法用于分页查询用户信息,支持根据部门和角色过滤。
     * </p>
     *
     * @param reqVO 分页查询的请求对象
     * @return 分页结果
     */
    @Override
    public PageResult<AdminUserDO> getUserPage(UserPageReqVO reqVO) {
        // 如果有角色编号,查询角色对应的用户编号
        Set<Long> userIds = reqVO.getRoleId() != null ?
                permissionService.getUserRoleIdListByRoleId(singleton(reqVO.getRoleId())) : null;

        // 分页查询用户数据
        return userMapper.selectPage(reqVO, getDeptCondition(reqVO.getDeptId()), userIds);
    }

    /**
     * 根据用户ID查询用户
     * <p>
     * 该方法用于根据用户ID查询用户信息。
     * </p>
     *
     * @param id 用户ID
     * @return 用户对象
     */
    @Override
    public AdminUserDO getUser(Long id) {
        return userMapper.selectById(id);
    }

    /**
     * 根据部门ID列表查询用户列表
     * <p>
     * 该方法用于根据部门ID列表查询用户列表。
     * </p>
     *
     * @param deptIds 部门ID列表
     * @return 用户列表
     */
    @Override
    public List<AdminUserDO> getUserListByDeptIds(Collection<Long> deptIds) {
        if (CollUtil.isEmpty(deptIds)) {
            return Collections.emptyList();
        }
        return userMapper.selectListByDeptIds(deptIds);
    }

    /**
     * 根据岗位ID列表查询用户列表
     * <p>
     * 该方法用于根据岗位ID列表查询用户列表。
     * </p>
     *
     * @param postIds 岗位ID列表
     * @return 用户列表
     */
    @Override
    public List<AdminUserDO> getUserListByPostIds(Collection<Long> postIds) {
        if (CollUtil.isEmpty(postIds)) {
            return Collections.emptyList();
        }
        Set<Long> userIds = convertSet(userPostMapper.selectListByPostIds(postIds), UserPostDO::getUserId);
        if (CollUtil.isEmpty(userIds)) {
            return Collections.emptyList();
        }
        return userMapper.selectBatchIds(userIds);
    }

    /**
     * 根据用户ID列表查询用户列表
     * <p>
     * 该方法用于根据用户ID列表查询用户列表。
     * </p>
     *
     * @param ids 用户ID列表
     * @return 用户列表
     */
    @Override
    public List<AdminUserDO> getUserList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return userMapper.selectBatchIds(ids);
    }

    /**
     * 校验用户列表
     * <p>
     * 该方法用于校验用户列表中的用户是否存在且处于启用状态。
     * </p>
     *
     * @param ids 用户ID列表
     */
    @Override
    public void validateUserList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return;
        }
        // 查询用户信息
        List<AdminUserDO> users = userMapper.selectBatchIds(ids);
        Map<Long, AdminUserDO> userMap = CollectionUtils.convertMap(users, AdminUserDO::getId);

        // 校验用户是否存在且启用
        ids.forEach(id -> {
            AdminUserDO user = userMap.get(id);
            if (user == null) {
                throw exception(USER_NOT_EXISTS);
            }
            if (!CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus())) {
                throw exception(USER_IS_DISABLE, user.getNickname());
            }
        });
    }

    /**
     * 根据昵称查询用户列表
     * <p>
     * 该方法用于根据昵称查询用户列表。
     * </p>
     *
     * @param nickname 昵称
     * @return 用户列表
     */
    @Override
    public List<AdminUserDO> getUserListByNickname(String nickname) {
        return userMapper.selectListByNickname(nickname);
    }

    /**
     * 获取部门条件:查询指定部门及其子部门的ID列表
     * <p>
     * 该方法用于根据部门ID获取其自身及其子部门的ID列表。
     * </p>
     *
     * @param deptId 部门ID
     * @return 部门ID列表
     */
    private Set<Long> getDeptCondition(Long deptId) {
        if (deptId == null) {
            return Collections.emptySet();
        }
        Set<Long> deptIds = convertSet(deptService.getChildDeptList(deptId), DeptDO::getId);
        deptIds.add(deptId); // 包括自身
        return deptIds;
    }

    /**
     * 校验用户信息(创建或更新)
     * <p>
     * 该方法用于校验用户信息的合法性,包括用户名、手机号、邮箱的唯一性,部门和岗位的状态。
     * </p>
     *
     * @param id       用户ID(null表示创建)
     * @param username 用户名
     * @param mobile   手机号
     * @param email    邮箱
     * @param deptId   部门ID
     * @param postIds  岗位ID列表
     * @return 用户对象
     */
    private AdminUserDO validateUserForCreateOrUpdate(Long id, String username, String mobile, String email,
                                                      Long deptId, Set<Long> postIds) {
        // 关闭数据权限,避免因数据权限限制导致查询不到数据
        return DataPermissionUtils.executeIgnore(() -> {
            // 校验用户是否存在
            AdminUserDO user = validateUserExists(id);

            // 校验用户名唯一性
            validateUsernameUnique(id, username);

            // 校验手机号唯一性
            validateMobileUnique(id, mobile);

            // 校验邮箱唯一性
            validateEmailUnique(id, email);

            // 校验部门状态
            deptService.validateDeptList(CollectionUtils.singleton(deptId));

            // 校验岗位状态
            postService.validatePostList(postIds);

            return user;
        });
    }

    /**
     * 校验用户是否存在
     * <p>
     * 该方法用于校验用户是否存在。
     * </p>
     *
     * @param id 用户ID
     * @return 用户对象
     */
    @VisibleForTesting
    AdminUserDO validateUserExists(Long id) {
        if (id == null) {
            return null;
        }
        AdminUserDO user = userMapper.selectById(id);
        if (user == null) {
            throw exception(USER_NOT_EXISTS);
        }
        return user;
    }

    /**
     * 校验用户名唯一性
     * <p>
     * 该方法用于校验用户名是否唯一。
     * </p>
     *
     * @param id       用户ID(null表示创建)
     * @param username 用户名
     */
    @VisibleForTesting
    void validateUsernameUnique(Long id, String username) {
        if (StrUtil.isBlank(username)) {
            return;
        }
        AdminUserDO user = userMapper.selectByUsername(username);
        if (user == null) {
            return;
        }
        if (id == null || !user.getId().equals(id)) {
            throw exception(USER_USERNAME_EXISTS);
        }
    }

    /**
     * 校验邮箱唯一性
     * <p>
     * 该方法用于校验邮箱是否唯一。
     * </p>
     *
     * @param id       用户ID(null表示创建)
     * @param email    邮箱
     */
    @VisibleForTesting
    void validateEmailUnique(Long id, String email) {
        if (StrUtil.isBlank(email)) {
            return;
        }
        AdminUserDO user = userMapper.selectByEmail(email);
        if (user == null) {
            return;
        }
        if (id == null || !user.getId().equals(id)) {
            throw exception(USER_EMAIL_EXISTS);
        }
    }

    /**
     * 校验手机号唯一性
     * <p>
     * 该方法用于校验手机号是否唯一。
     * </p>
     *
     * @param id       用户ID(null表示创建)
     * @param mobile   手机号
     */
    @VisibleForTesting
    void validateMobileUnique(Long id, String mobile) {
        if (StrUtil.isBlank(mobile)) {
            return;
        }
        AdminUserDO user = userMapper.selectByMobile(mobile);
        if (user == null) {
            return;
        }
        if (id == null || !user.getId().equals(id)) {
            throw exception(USER_MOBILE_EXISTS);
        }
    }

    /**
     * 校验旧密码是否正确
     * <p>
     * 该方法用于校验用户输入的旧密码是否正确。
     * </p>
     *
     * @param id           用户ID
     * @param oldPassword  旧密码
     */
    @VisibleForTesting
    void validateOldPassword(Long id, String oldPassword) {
        AdminUserDO user = userMapper.selectById(id);
        if (user == null) {
            throw exception(USER_NOT_EXISTS);
        }
        if (!isPasswordMatch(oldPassword, user.getPassword())) {
            throw exception(USER_PASSWORD_FAILED);
        }
    }

    /**
     * 导入用户列表
     * <p>
     * 该方法用于从Excel导入用户列表,支持创建和更新用户。
     * </p>
     *
     * @param importUsers      导入的用户列表
     * @param isUpdateSupport  是否支持更新已存在的用户
     * @return 导入结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public UserImportRespVO importUserList(List<UserImportExcelVO> importUsers, boolean isUpdateSupport) {
        // 校验导入列表是否为空
        if (CollUtil.isEmpty(importUsers)) {
            throw exception(USER_IMPORT_LIST_IS_EMPTY);
        }

        // 获取初始化密码
        String initPassword = configApi.getConfigValueByKey(USER_INIT_PASSWORD_KEY).getCheckedData();
        if (StrUtil.isEmpty(initPassword)) {
            throw exception(USER_IMPORT_INIT_PASSWORD);
        }

        // 初始化导入结果
        UserImportRespVO respVO = UserImportRespVO.builder()
                .createUsernames(new ArrayList<>())
                .updateUsernames(new ArrayList<>())
                .failureUsernames(new LinkedHashMap<>())
                .build();

        // 遍历导入的用户
        importUsers.forEach(importUser -> {
            // 校验字段是否符合要求
            try {
                ValidationUtils.validate(BeanUtils.toBean(importUser, UserSaveReqVO.class).setPassword(initPassword));
            } catch (ConstraintViolationException ex) {
                respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage());
                return;
            }

            // 校验用户信息是否合法
            try {
                validateUserForCreateOrUpdate(null, null, importUser.getMobile(), importUser.getEmail(),
                        importUser.getDeptId(), 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<>())); // 设置默认密码及空岗位编号数组
                respVO.getCreateUsernames().add(importUser.getUsername());
            } else {
                // 用户存在,判断是否支持更新
                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());
            }
        });

        return respVO;
    }

    /**
     * 根据状态查询用户列表
     * <p>
     * 该方法用于根据用户状态查询用户列表。
     * </p>
     *
     * @param status 用户状态
     * @return 用户列表
     */
    @Override
    public List<AdminUserDO> getUserListByStatus(Integer status) {
        return userMapper.selectListByStatus(status);
    }

    /**
     * 校验密码是否匹配
     * <p>
     * 该方法用于校验原始密码和加密密码是否匹配。
     * </p>
     *
     * @param rawPassword      原始密码
     * @param encodedPassword  加密密码
     * @return 是否匹配
     */
    @Override
    public boolean isPasswordMatch(String rawPassword, String encodedPassword) {
        return passwordEncoder.matches(rawPassword, encodedPassword);
    }

    /**
     * 对密码进行加密
     * <p>
     * 该方法用于对密码进行加密处理。
     * </p>
     *
     * @param password 密码
     * @return 加密后的密码
     */
    private String encodePassword(String password) {
        return passwordEncoder.encode(password);
    }
}
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐