// 声明该类所在的包,表明它属于系统用户服务实现模块
package com.yf.exam.modules.sys.user.service.impl;
// 导入阿里巴巴 FastJSON 库,用于 JSON 数据的序列化和反序列化
import com.alibaba.fastjson.JSON;
// 导入 FastJSON 的 TypeReference 类,用于处理泛型类型的 JSON 反序列化
import com.alibaba.fastjson.TypeReference;
// 导入 MyBatis-Plus 的查询条件构造器,用于构建数据库查询条件
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
// 导入 MyBatis-Plus 的分页元数据接口,用于表示分页查询结果
import com.baomidou.mybatisplus.core.metadata.IPage;
// 导入 MyBatis-Plus 的 ID 生成工具类,用于生成分布式 ID
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
// 导入 MyBatis-Plus 的分页实现类,用于创建分页对象
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
// 导入 MyBatis-Plus 的服务实现基类,提供通用的服务层方法实现
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
// 导入自定义的 API 错误类,用于封装 API 错误信息
import com.yf.exam.core.api.ApiError;
// 导入自定义的分页请求数据传输对象类,用于封装分页查询请求参数
import com.yf.exam.core.api.dto.PagingReqDTO;
// 导入自定义的通用状态枚举类,定义通用的状态常量
import com.yf.exam.core.enums.CommonState;
// 导入自定义的服务异常类,用于抛出服务层异常
import com.yf.exam.core.exception.ServiceException;
// 导入自定义的 Bean 映射工具类,用于对象属性的复制
import com.yf.exam.core.utils.BeanMapper;
// 导入自定义的密码处理工具类,用于密码的加密和验证
import com.yf.exam.core.utils.passwd.PassHandler;
// 导入自定义的密码信息类,用于封装密码和盐值
import com.yf.exam.core.utils.passwd.PassInfo;
// 导入自定义的 JWT 工具类,用于生成和验证 JWT 令牌
import com.yf.exam.ability.shiro.jwt.JwtUtils;
// 导入自定义的系统用户数据传输对象类,用于在不同层之间传输用户数据
import com.yf.exam.modules.sys.user.dto.SysUserDTO;
// 导入自定义的系统用户保存请求数据传输对象类,用于封装用户保存请求参数
import com.yf.exam.modules.sys.user.dto.request.SysUserSaveReqDTO;
// 导入自定义的系统用户登录响应数据传输对象类,用于封装用户登录响应信息
import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO;
// 导入自定义的系统用户实体类,用于映射数据库中的用户表
import com.yf.exam.modules.sys.user.entity.SysUser;
// 导入自定义的系统用户数据访问接口,用于与数据库进行用户数据交互
import com.yf.exam.modules.sys.user.mapper.SysUserMapper;
// 导入自定义的系统用户角色服务接口,用于处理用户角色相关业务
import com.yf.exam.modules.sys.user.service.SysUserRoleService;
// 导入自定义的系统用户服务接口,定义用户相关的业务方法
import com.yf.exam.modules.sys.user.service.SysUserService;
// 导入自定义的用户工具类,提供用户相关的工具方法
import com.yf.exam.modules.user.UserUtils;
// 导入 Apache Commons Lang3 的字符串工具类,提供字符串操作方法
import org.apache.commons.lang3.StringUtils;
// 导入 Apache Shiro 的安全工具类,用于进行身份验证和授权操作
import org.apache.shiro.SecurityUtils;
// 导入 Spring 的自动装配注解,用于自动注入依赖的 Bean
import org.springframework.beans.factory.annotation.Autowired;
// 导入 Spring 的服务注解,将该类标记为服务组件,由 Spring 容器管理
import org.springframework.stereotype.Service;
// 导入 Spring 的事务管理注解,用于声明事务方法
import org.springframework.transaction.annotation.Transactional;
// 导入 Spring 的集合工具类,用于判断集合是否为空
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
/**
*
* 系统用户 服务实现类,实现了系统用户相关的业务逻辑。
* 继承自 MyBatis-Plus 的 ServiceImpl 类,使用 SysUserMapper 操作 SysUser 实体。
*
*
* @author 聪明笨狗
* @since 2020-04-13 16:57
*/
@Service
public class SysUserServiceImpl extends ServiceImpl implements SysUserService {
// 自动注入系统用户角色服务类的实例,用于处理用户角色相关业务
@Autowired
private SysUserRoleService sysUserRoleService;
/**
* 分页查询系统用户信息。
*
* @param reqDTO 分页请求数据传输对象,包含分页信息和查询参数
* @return 分页查询结果,包含系统用户数据传输对象列表
*/
@Override
public IPage paging(PagingReqDTO reqDTO) {
// 创建分页对象,指定当前页码和每页记录数
IPage query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize());
// 创建查询条件构造器,用于构建数据库查询条件
QueryWrapper wrapper = new QueryWrapper<>();
// 获取查询参数
SysUserDTO params = reqDTO.getParams();
// 如果查询参数不为空
if(params!=null){
// 如果用户名不为空,添加用户名模糊查询条件
if(!StringUtils.isBlank(params.getUserName())){
wrapper.lambda().like(SysUser::getUserName, params.getUserName());
}
// 如果真实姓名不为空,添加真实姓名模糊查询条件
if(!StringUtils.isBlank(params.getRealName())){
wrapper.lambda().like(SysUser::getRealName, params.getRealName());
}
}
// 执行分页查询,获取包含系统用户实体的分页结果
IPage page = this.page(query, wrapper);
// 将包含系统用户实体的分页结果转换为包含系统用户数据传输对象的分页结果
IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){});
return pageData;
}
/**
* 用户登录方法。
*
* @param userName 用户名
* @param password 密码
* @return 系统用户登录响应数据传输对象,包含登录成功后的用户信息和令牌
* @throws ServiceException 若用户名不存在、用户被禁用或密码错误,抛出服务异常
*/
@Override
public SysUserLoginDTO login(String userName, String password) {
// 创建查询条件构造器,添加用户名等于查询条件
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.lambda().eq(SysUser::getUserName, userName);
// 根据查询条件获取一个用户实体
SysUser user = this.getOne(wrapper, false);
// 如果用户不存在,抛出用户名或密码错误异常
if(user == null){
throw new ServiceException(ApiError.ERROR_90010002);
}
// 如果用户状态为异常(被禁用),抛出用户被禁用异常
if(user.getState().equals(CommonState.ABNORMAL)){
throw new ServiceException(ApiError.ERROR_90010005);
}
// 验证密码是否正确
boolean check = PassHandler.checkPass(password,user.getSalt(), user.getPassword());
// 如果密码不正确,抛出用户名或密码错误异常
if(!check){
throw new ServiceException(ApiError.ERROR_90010002);
}
// 为用户设置令牌并返回登录响应信息
return this.setToken(user);
}
/**
* 根据令牌验证用户信息并返回登录响应信息。
*
* @param token 令牌
* @return 系统用户登录响应数据传输对象,包含验证通过后的用户信息和令牌
* @throws ServiceException 若令牌验证失败、用户不存在或用户被禁用,抛出服务异常
*/
@Override
public SysUserLoginDTO token(String token) {
// 从令牌中获取用户名
String username = JwtUtils.getUsername(token);
// 验证令牌是否有效
boolean check = JwtUtils.verify(token, username);
// 如果令牌验证失败,抛出用户名或密码错误异常
if(!check){
throw new ServiceException(ApiError.ERROR_90010002);
}
// 创建查询条件构造器,添加用户名等于查询条件
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.lambda().eq(SysUser::getUserName, username);
// 根据查询条件获取一个用户实体
SysUser user = this.getOne(wrapper, false);
// 如果用户不存在,抛出用户不存在异常
if(user == null){
throw new ServiceException(ApiError.ERROR_10010002);
}
// 如果用户状态为异常(被禁用),抛出用户被禁用异常
if(user.getState().equals(CommonState.ABNORMAL)){
throw new ServiceException(ApiError.ERROR_90010005);
}
// 为用户设置令牌并返回登录响应信息
return this.setToken(user);
}
/**
* 用户退出登录方法。
*
* @param token 令牌
*/
@Override
public void logout(String token) {
// 仅退出当前会话
SecurityUtils.getSubject().logout();
}
/**
* 更新用户信息,主要处理密码更新。
*
* @param reqDTO 系统用户数据传输对象,包含要更新的用户信息
*/
@Override
public void update(SysUserDTO reqDTO) {
// 获取用户输入的新密码
String pass = reqDTO.getPassword();
// 如果新密码不为空
if(!StringUtils.isBlank(pass)){
// 生成新的密码信息,包含加密后的密码和盐值
PassInfo passInfo = PassHandler.buildPassword(pass);
// 根据当前用户 ID 获取用户实体
SysUser user = this.getById(UserUtils.getUserId());
// 更新用户密码
user.setPassword(passInfo.getPassword());
// 更新用户密码盐值
user.setSalt(passInfo.getSalt());
// 更新用户信息到数据库
this.updateById(user);
}
}
/**
* 保存或更新用户信息,同时处理用户角色信息。
*
* @param reqDTO 系统用户保存请求数据传输对象,包含要保存的用户信息和角色列表
* @throws ServiceException 若角色列表为空,抛出服务异常
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void save(SysUserSaveReqDTO reqDTO) {
// 获取用户的角色列表
List roles = reqDTO.getRoles();
// 如果角色列表为空,抛出角色不能为空异常
if(CollectionUtils.isEmpty(roles)){
throw new ServiceException(ApiError.ERROR_90010003);
}
// 创建系统用户实体对象
SysUser user = new SysUser();
// 将请求数据传输对象的属性复制到用户实体对象中
BeanMapper.copy(reqDTO, user);
// 如果用户 ID 为空,说明是新增用户,生成新的用户 ID
if(StringUtils.isBlank(user.getId())){
user.setId(IdWorker.getIdStr());
}
// 如果请求中包含密码,更新用户密码信息
if(!StringUtils.isBlank(reqDTO.getPassword())){
// 生成新的密码信息,包含加密后的密码和盐值
PassInfo pass = PassHandler.buildPassword(reqDTO.getPassword());
// 更新用户密码
user.setPassword(pass.getPassword());
// 更新用户密码盐值
user.setSalt(pass.getSalt());
}
// 保存用户角色信息,并返回角色 ID 字符串
String roleIds = sysUserRoleService.saveRoles(user.getId(), roles);
// 设置用户的角色 ID 字符串
user.setRoleIds(roleIds);
// 保存或更新用户信息到数据库
this.saveOrUpdate(user);
}
/**
* 用户注册方法。
*
* @param reqDTO 系统用户数据传输对象,包含要注册的用户信息
* @return 系统用户登录响应数据传输对象,包含注册成功后的用户信息和令牌
* @throws ServiceException 若用户名已存在,抛出服务异常
*/
@Transactional(rollbackFor = Exception.class)
@Override
public SysUserLoginDTO reg(SysUserDTO reqDTO) {
// 创建查询条件构造器,添加用户名等于查询条件
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.lambda().eq(SysUser::getUserName, reqDTO.getUserName());
// 统计符合条件的用户数量
int count = this.count(wrapper);
// 如果用户数量大于 0,说明用户名已存在,抛出用户名已存在异常
if(count > 0){
throw new ServiceException(1, "用户名已存在,换一个吧!");
}
// 创建系统用户实体对象
SysUser user = new SysUser();
// 生成新的用户 ID
user.setId(IdWorker.getIdStr());
// 设置用户名
user.setUserName(reqDTO.getUserName());
// 设置真实姓名
user.setRealName(reqDTO.getRealName());
// 生成新的密码信息,包含加密后的密码和盐值
PassInfo passInfo = PassHandler.buildPassword(reqDTO.getPassword());
// 设置用户密码
user.setPassword(passInfo.getPassword());
// 设置用户密码盐值
user.setSalt(passInfo.getSalt());
// 创建角色列表,默认添加学生角色
List roles = new ArrayList<>();
roles.add("student");
// 保存用户角色信息,并返回角色 ID 字符串
String roleIds = sysUserRoleService.saveRoles(user.getId(), roles);
// 设置用户的角色 ID 字符串
user.setRoleIds(roleIds);
// 保存用户信息到数据库
this.save(user);
// 为用户设置令牌并返回登录响应信息
return this.setToken(user);
}
/**
* 快速注册方法,如果用户已存在则直接返回登录信息,否则进行注册。
*
* @param reqDTO 系统用户数据传输对象,包含要注册的用户信息
* @return 系统用户登录响应数据传输对象,包含注册或登录成功后的用户信息和令牌
*/
@Override
public SysUserLoginDTO quickReg(SysUserDTO reqDTO) {
// 创建查询条件构造器,添加用户名等于查询条件,并限制查询结果为 1 条
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.lambda().eq(SysUser::getUserName, reqDTO.getUserName());
wrapper.last(" LIMIT 1 ");
// 根据查询条件获取一个用户实体
SysUser user = this.getOne(wrapper);
// 如果用户存在,为用户设置令牌并返回登录响应信息
if(user!=null){
return this.setToken(user);
}
// 如果用户不存在,进行注册操作
return this.reg(reqDTO);
}
/**
* 为用户设置令牌并返回登录响应信息。
*
* @param user 系统用户实体对象
* @return 系统用户登录响应数据传输对象,包含用户信息和令牌
*/
private SysUserLoginDTO setToken(SysUser user){
// 创建系统用户登录响应数据传输对象
SysUserLoginDTO respDTO = new SysUserLoginDTO();
// 将用户实体对象的属性复制到登录响应数据传输对象中
BeanMapper.copy(user, respDTO);
// 生成 JWT 令牌
String token = JwtUtils.sign(user.getUserName());
// 设置登录响应数据传输对象的令牌
respDTO.setToken(token);
// 获取用户的角色列表
List roles = sysUserRoleService.listRoles(user.getId());
// 设置登录响应数据传输对象的角色列表
respDTO.setRoles(roles);
return respDTO;
}
}