package com.kexue.skills.service.impl; import com.alicp.jetcache.anno.CacheInvalidate; import com.alicp.jetcache.anno.CachePenetrationProtect; import com.alicp.jetcache.anno.Cached; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.kexue.skills.common.Assert; import com.kexue.skills.common.CacheManager; import com.kexue.skills.common.Const; import com.kexue.skills.common.LoginUserCacheUtil; import com.kexue.skills.config.CaptchaConfig; import com.kexue.skills.entity.Account; import com.kexue.skills.entity.ContentPurchase; import com.kexue.skills.entity.SysUser; import com.kexue.skills.entity.SysUserRole; import com.kexue.skills.entity.dto.ContentPurchaseDto; import com.kexue.skills.entity.dto.SessionDto; import com.kexue.skills.entity.dto.SysUserDto; import com.kexue.skills.entity.request.*; import com.kexue.skills.exception.BizException; import com.kexue.skills.mapper.AccountMapper; import com.kexue.skills.mapper.ContentPurchaseMapper; import com.kexue.skills.mapper.CmsContentLikeMapper; import com.kexue.skills.mapper.CmsContentViewMapper; import com.kexue.skills.mapper.CmsContentMapper; import com.kexue.skills.mapper.SysUserMapper; import com.kexue.skills.mapper.SysUserRoleMapper; import com.kexue.skills.service.SysUserService; import com.kexue.skills.service.AccountService; import com.kexue.skills.utils.MD5Util; import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.api.SmsBlend; import org.dromara.sms4j.core.factory.SmsFactory; import org.redisson.api.RedissonClient; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.math.BigDecimal; import java.security.NoSuchAlgorithmException; import java.util.*; /** * (SysUser)表服务实现类 * * @author 王志维 * @since 2025-02-21 23:01:48 */ @Service("sysUserService") @Slf4j public class SysUserServiceImpl implements SysUserService { @Resource private SysUserMapper sysUserMapper; @Resource private RedissonClient redissonClient; @Resource private CaptchaConfig captchaConfig; @Resource private AccountMapper accountMapper; @Resource private SysUserRoleMapper sysUserRoleMapper; @Resource private ContentPurchaseMapper contentPurchaseMapper; @Resource private CmsContentLikeMapper cmsContentLikeMapper; @Resource private CmsContentViewMapper cmsContentViewMapper; @Resource private CmsContentMapper cmsContentMapper; @Resource private AccountService accountService; /** * 分页查询数据 * * @param queryDto * @return 数据分页列表 */ @Override public PageInfo getPageList(SysUserDto queryDto) { int pageNum = queryDto.getPageNum()==null?PAGENUM:queryDto.getPageNum(); int pageSize = queryDto.getPageSize()==null? PAGESIZE :queryDto.getPageSize(); PageHelper.startPage(pageNum,pageSize); List dataList = sysUserMapper.getPageList(queryDto); // 将所有返回的用户密码设置为null dataList.forEach(user -> user.setPwd(null)); return new PageInfo<>(dataList); } /** * 通过ID查询单条数据 * * @param userId 主键 * @return 实例对象 */ @Override @Cached(name = "sysUser:", key = "#userId", expire = 3600, cacheType = com.alicp.jetcache.anno.CacheType.BOTH) @CachePenetrationProtect public SysUser queryById(Long userId) { SysUser sysUser = sysUserMapper.queryById(userId); sysUser.setPwd(null); return sysUser; } /** * 查询多条数据 * * @param offset 查询起始位置 * @param limit 查询条数 * @return 对象列表 */ @Override public List queryAllByLimit(int offset, int limit) { List sysUsers = sysUserMapper.queryAllByLimit(offset, limit); // 将所有返回的用户密码设置为null sysUsers.forEach(user -> user.setPwd(null)); return sysUsers; } /** * 新增数据 * * @param sysUser 实例对象 * @return 实例对象 */ @Override public SysUser insert(SysUser sysUser) { SysUser byUsername = getByUsername(sysUser.getUserName()); Assert.isNull(byUsername, "用户名已存在"); sysUser.setEnable(Const.USER_STATUS_NORMAL); sysUser.setDeleteFlag(Const.DELETE_FLAG_NO); //写一个salt生成方法 sysUser.setSalt(System.currentTimeMillis()+""); // 生成随机邀请码(8位字母数字组合) if (sysUser.getInviteCode() == null || sysUser.getInviteCode().isEmpty()) { sysUser.setInviteCode(generateInviteCode()); } // 处理邀请码 if (sysUser.getInvitedCode() != null && !sysUser.getInvitedCode().isEmpty()) { // 验证邀请码是否有效 SysUser inviter = sysUserMapper.getByInviteCode(sysUser.getInvitedCode()); if (inviter == null) { throw new BizException("邀请码无效"); } // 设置邀请人用户ID sysUser.setInvitedBy(inviter.getUserId()); // 给邀请人赠送100积分 accountService.addGiftBalance( inviter.getUserId(), BigDecimal.valueOf(100), "gift_" + System.currentTimeMillis(), null, "invite", "邀请用户注册赠送" ); } try { // 假设客户端已经对密码进行了一次MD5加密,服务端使用双重加密验证 String encryptedPwd = MD5Util.doubleEncrypt(sysUser.getPwd(), sysUser.getSalt()); sysUser.setPwd(encryptedPwd); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } // 插入用户信息 sysUserMapper.insert(sysUser); // 初始化账户(余额账户) Account account = new Account(); account.setUserId(sysUser.getUserId()); account.setUserName(sysUser.getUserName()); account.setBalance(BigDecimal.ZERO); // 初始余额为0 account.setFrozenAmount(BigDecimal.ZERO); // 初始冻结金额为0 account.setDeleteFlag(Const.DELETE_FLAG_NO); // 初始未删除 accountMapper.insert(account); // 注册赠送100积分 accountService.addGiftBalance( sysUser.getUserId(), BigDecimal.valueOf(100), "gift_" + System.currentTimeMillis(), null, "register", "注册赠送" ); // 将返回的用户密码设置为null sysUser.setPwd(null); return sysUser; } /** * 修改数据 * * @param sysUser 实例对象 * @return 实例对象 */ @Override @CacheInvalidate(name = "sysUser:", key = "#sysUser.userId") @CacheInvalidate(name = "sysUser:username:", key = "#sysUser.userName") public SysUser update(SysUser sysUser) { if (Objects.nonNull(sysUser.getUserId())){ // 校验用户名是否已经存在 if (sysUser.getUserName() != null && !sysUser.getUserName().isEmpty()) { SysUser existingUser = sysUserMapper.getByUsername(sysUser.getUserName()); Assert.isTrue(existingUser == null || existingUser.getUserId().equals(sysUser.getUserId()), "用户名已存在"); } sysUserMapper.update(sysUser); }else { insert(sysUser); } return queryById(sysUser.getUserId()); } /** * 修改用户数据 * * @param sysUserUpdateDto 用户更新请求参数 * @return 实例对象 */ @Override @CacheInvalidate(name = "sysUser:", key = "#sysUserUpdateDto.userId") @CacheInvalidate(name = "sysUser:username:", key = "#sysUserUpdateDto.userName") public SysUser update(SysUserUpdateDto sysUserUpdateDto) { if (Objects.nonNull(sysUserUpdateDto.getUserId())){ // 查询用户信息 SysUser sysUser = sysUserMapper.queryById(sysUserUpdateDto.getUserId()); Assert.notNull(sysUser, "用户不存在"); // 校验用户名是否已经存在 if (sysUserUpdateDto.getUserName() != null && !sysUserUpdateDto.getUserName().isEmpty()) { SysUser existingUser = sysUserMapper.getByUsername(sysUserUpdateDto.getUserName()); Assert.isTrue(existingUser == null || existingUser.getUserId().equals(sysUserUpdateDto.getUserId()), "用户名已存在"); sysUser.setUserName(sysUserUpdateDto.getUserName()); } // 更新邮箱 if (sysUserUpdateDto.getEmail() != null) { sysUser.setEmail(sysUserUpdateDto.getEmail()); } // 更新手机号 if (sysUserUpdateDto.getTel() != null) { sysUser.setTel(sysUserUpdateDto.getTel()); } // 更新状态 if (sysUserUpdateDto.getEnable() != null) { sysUser.setEnable(sysUserUpdateDto.getEnable()); } // 更新密码(如果有) if (sysUserUpdateDto.getPassword() != null && !sysUserUpdateDto.getPassword().isEmpty()) { try { // 假设客户端已经对密码进行了一次MD5加密,服务端使用双重加密验证 String encryptedPwd = MD5Util.doubleEncrypt(sysUserUpdateDto.getPassword(), sysUser.getSalt()); sysUser.setPwd(encryptedPwd); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } // 执行更新 sysUserMapper.update(sysUser); // 判断更新的用户是否是当前登录用户,如果是则更新用户缓存 Long currentUserId = loginUserCacheUtil.getCurrentUserId(); if (currentUserId != null && currentUserId.equals(sysUserUpdateDto.getUserId())) { updateUserCache(currentUserId, sysUser); } }else { // 如果没有用户ID,创建新用户 SysUser sysUser = new SysUser(); sysUser.setUserName(sysUserUpdateDto.getUserName()); sysUser.setEmail(sysUserUpdateDto.getEmail()); sysUser.setTel(sysUserUpdateDto.getTel()); sysUser.setEnable(sysUserUpdateDto.getEnable()); sysUser.setPwd(sysUserUpdateDto.getPassword()); insert(sysUser); } return queryById(sysUserUpdateDto.getUserId()); } /** * 通过主键删除数据 * * @param userId 主键 * @return 是否成功 */ @Override @CacheInvalidate(name = "sysUser:", key = "#userId") public boolean deleteById(Long userId) { SysUser sysUser = queryById(userId); //管理员不允许删除 Assert.isFalse(Const.ADMIN_USER_LIST.contains(sysUser.getUserName().toLowerCase()), "该用户是管理员,不能删除"); // 删除用户名缓存 redissonClient.getBucket("sysUser:username:" + sysUser.getUserName()).delete(); return sysUserMapper.deleteById(userId) > 0; } @Override public LoginUserDto login(LoginDto loginDto) { // 参数验证 validateLoginParams(loginDto); // 验证码验证 validateCaptcha(loginDto); // 查询用户(支持用户名或手机号) SysUser sysUser = getUserByUsernameOrPhone(loginDto.getUsername()); // 密码验证 validatePassword(loginDto.getPassword(), sysUser); // 检查用户是否已有 token,如果有则使旧 token 失效 String oldToken = CacheManager.getTokenFromCache(sysUser.getUserName()); if (oldToken != null && !oldToken.isEmpty()) { log.info("检测到用户 {} 已有旧 token: {}", sysUser.getUserName(), oldToken); // 从 Redis 中删除旧 token 对应的登录信息 try { redissonClient.getBucket("loginUser:" + oldToken).delete(); log.info("已删除 Redis 中的旧 token 数据:{}", oldToken); } catch (Exception e) { log.error("删除 Redis 中的旧 token 失败:{}", e.getMessage()); } // 使旧 token 失效(Sa-Token 内部操作) try { cn.dev33.satoken.stp.StpUtil.logoutByTokenValue(oldToken); log.info("已通过 Sa-Token 使旧 token 失效:{}", oldToken); } catch (Exception e) { log.error("Sa-Token 使旧 token 失效失败:{}", e.getMessage()); } // 从缓存中移除旧 token CacheManager.removeTokenFromCache(sysUser.getUserName()); log.info("用户:{}的旧 token 已完全失效", sysUser.getUserName()); } // 查询用户角色列表 List roles = queryUserRoles(sysUser.getUserId()); log.info("用户{}的角色列表:{}", sysUser.getUserName(), String.join(",", roles)); // 生成token并设置角色 String token = generateToken(sysUser.getUserId(), roles); log.info("设置后Sa-Token中的角色列表:{}", String.join(",", cn.dev33.satoken.stp.StpUtil.getRoleList())); // 构建登录用户信息 LoginUser loginUser = buildLoginUser(sysUser, token); // 存储登录信息到Redis saveLoginUserToRedis(token, loginUser); // 存储token与用户名的映射关系到CacheManager CacheManager.putTokenToCache(sysUser.getUserName(), token); // 构建返回对象 LoginUserDto sysUserDto = buildLoginUserDto(loginUser); log.info("用户:{}登录成功,token:{}", loginDto.getUsername(), token); return sysUserDto; } /** * 参数验证 * * @param loginDto 登录请求参数 */ private void validateLoginParams(LoginDto loginDto) { Assert.notNull(loginDto.getUsername(), "用户名或手机号不能位空"); Assert.notNull(loginDto.getPassword(), "密码不能位空"); } /** * 验证码验证 * * @param loginDto 登录请求参数 */ private void validateCaptcha(LoginDto loginDto) { if (captchaConfig.isEnabled()) { Assert.notNull(loginDto.getCaptchaId(), "验证码不能为空"); Assert.notNull(loginDto.getCaptchaValue(), "验证码不能为空"); String captchaKey = "captcha:" + loginDto.getCaptchaId(); String captchaText = (String) redissonClient.getBucket(captchaKey).get(); Assert.notNull(captchaText, "验证码已过期"); Assert.equals(captchaText, loginDto.getCaptchaValue().toLowerCase(), "验证码错误"); redissonClient.getBucket(captchaKey).delete(); } } /** * 根据用户名查询用户 * * @param username 用户名 * @return 用户对象 */ private SysUser getUserByUsername(String username) { SysUser sysUser = sysUserMapper.getByUsername(username); Assert.notNull(sysUser, "用户名不存在"); return sysUser; } /** * 根据用户名或手机号查询用户 * * @param usernameOrPhone 用户名或手机号 * @return 用户对象 */ public SysUser getUserByUsernameOrPhone(String usernameOrPhone) { // 先尝试通过用户名查询 SysUser sysUser = sysUserMapper.getByUsername(usernameOrPhone); if (sysUser == null) { // 如果用户名查询不到,尝试通过手机号查询 sysUser = sysUserMapper.getByTel(usernameOrPhone); Assert.notNull(sysUser, "用户不存在"); } return sysUser; } /** * 密码验证 * * @param password 密码 * @param sysUser 用户对象 */ private void validatePassword(String password, SysUser sysUser) { try { // 假设客户端已经对密码进行了一次MD5加密,服务端使用双重加密验证 String encryptedPwd = MD5Util.doubleEncrypt(password, sysUser.getSalt()); Assert.equals(encryptedPwd, sysUser.getPwd(), "密码不正确"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } /** * 生成token * * @param userId 用户ID * @param roles 角色列表 * @return token */ private String generateToken(Long userId, List roles) { // 登录用户 cn.dev33.satoken.stp.StpUtil.login(userId); // 添加角色 if (!roles.isEmpty()) { cn.dev33.satoken.stp.StpUtil.getRoleList().addAll(roles); } return cn.dev33.satoken.stp.StpUtil.getTokenValue(); } /** * 构建登录用户信息 * * @param sysUser 用户对象 * @param token token * @return 登录用户信息 */ private LoginUser buildLoginUser(SysUser sysUser, String token) { LoginUser loginUser = new LoginUser(); // 设置用户基本信息 sysUser.setPwd(null); loginUser.setUserInfo(sysUser); // 查询并设置用户角色列表 loginUser.setRoles(queryUserRoles(sysUser.getUserId())); // 查询并设置用户已购买的内容ID列表 loginUser.setPurchasedContentIds(queryPurchasedContentIds(sysUser.getUserId())); // 查询并设置用户账户信息 loginUser.setAccount(queryUserAccount(sysUser.getUserId())); // 查询并设置用户最近点赞记录 loginUser.setFavorites(queryRecentFavorites(sysUser.getUserId())); // 查询并设置用户最近查看记录 loginUser.setHistory(queryRecentViews(sysUser.getUserId())); // 查询并设置用户最近创建记录 loginUser.setCreate(queryRecentCreatedContent(sysUser.getUserId())); // 查询并设置用户最近购买记录 loginUser.setHas(queryRecentPurchases(sysUser.getUserId())); // 设置token loginUser.setToken(token); return loginUser; } /** * 查询用户角色列表 * * @param userId 用户 ID * @return 角色编码列表 */ @Override public List queryUserRoles(Long userId) { try { // 直接通过关联查询获取角色编码列表 return sysUserRoleMapper.queryRoleCodesByUserId(userId); } catch (Exception e) { log.error("查询用户角色列表失败:{}", e.getMessage()); return java.util.Collections.emptyList(); } } /** * 查询用户已购买的内容ID列表 * * @param userId 用户ID * @return 内容ID列表 */ private List queryPurchasedContentIds(Long userId) { List purchasedContentIds = new java.util.ArrayList<>(); try { ContentPurchaseDto purchaseDto = new ContentPurchaseDto(); purchaseDto.setUserId(userId); List purchases = contentPurchaseMapper.getList(purchaseDto); for (ContentPurchase purchase : purchases) { purchasedContentIds.add(purchase.getContentId()); } } catch (Exception e) { log.error("查询用户已购买内容列表失败:{}", e.getMessage()); purchasedContentIds = java.util.Collections.emptyList(); } return purchasedContentIds; } /** * 查询用户账户信息 * * @param userId 用户ID * @return 账户信息 */ private Account queryUserAccount(Long userId) { return accountMapper.queryByUserId(userId); } /** * 查询用户最近点赞记录 * * @param userId 用户ID * @return 最近点赞记录 */ private List queryRecentFavorites(Long userId) { try { return cmsContentLikeMapper.queryRecentLikesByUserId(userId, 20); } catch (Exception e) { log.error("查询用户最近点赞记录失败:{}", e.getMessage()); return java.util.Collections.emptyList(); } } /** * 查询用户最近查看记录 * * @param userId 用户ID * @return 最近查看记录 */ private List queryRecentViews(Long userId) { try { return cmsContentViewMapper.queryRecentViewsByUserId(userId, 20); } catch (Exception e) { log.error("查询用户最近查看记录失败:{}", e.getMessage()); return java.util.Collections.emptyList(); } } /** * 查询用户最近创建记录 * * @param userId 用户ID * @return 最近创建记录 */ private List queryRecentCreatedContent(Long userId) { try { return cmsContentMapper.queryRecentCreatedByUserId(userId, 20); } catch (Exception e) { log.error("查询用户最近创建记录失败:{}", e.getMessage()); return java.util.Collections.emptyList(); } } /** * 查询用户最近购买记录 * * @param userId 用户ID * @return 最近购买记录 */ private List queryRecentPurchases(Long userId) { List has = new java.util.ArrayList<>(); try { ContentPurchaseDto purchaseDto = new ContentPurchaseDto(); purchaseDto.setUserId(userId); List purchases = contentPurchaseMapper.getList(purchaseDto); if (purchases != null && !purchases.isEmpty()) { java.util.LinkedHashSet contentIdSet = new java.util.LinkedHashSet<>(); for (ContentPurchase purchase : purchases) { if (contentIdSet.size() < 20) { contentIdSet.add(purchase.getContentId()); } else { break; } } has.addAll(contentIdSet); } } catch (Exception e) { log.error("查询用户最近购买记录失败:{}", e.getMessage()); has = java.util.Collections.emptyList(); } return has; } /** * 存储登录信息到Redis * * @param token token * @param loginUser 登录用户信息 */ private void saveLoginUserToRedis(String token, LoginUser loginUser) { redissonClient.getBucket("loginUser:" + token) .set(cn.hutool.json.JSONUtil.toJsonStr(loginUser), 86400, java.util.concurrent.TimeUnit.SECONDS); } /** * 构建登录用户返回对象 * * @param loginUser 登录用户信息 * @return 登录用户返回对象 */ private LoginUserDto buildLoginUserDto(LoginUser loginUser) { LoginUserDto sysUserDto = new LoginUserDto(); sysUserDto.setUserInfo(loginUser.getUserInfo()); sysUserDto.setToken(loginUser.getToken()); sysUserDto.setFavorites(loginUser.getFavorites()); sysUserDto.setHistory(loginUser.getHistory()); sysUserDto.setCreate(loginUser.getCreate()); sysUserDto.setHas(loginUser.getHas()); return sysUserDto; } @Resource private LoginUserCacheUtil loginUserCacheUtil; @Override public boolean resetPassword(ResetPwdDto resetPasswordDto) { // 1. 检查登录状态 Long currentUserId = loginUserCacheUtil.getCurrentUserId(); Assert.notNull(currentUserId, "请先登录"); // 2. 检查参数 Assert.notNull(resetPasswordDto.getUserId(), "用户ID不能为空"); Assert.notNull(resetPasswordDto.getNewPassword(), "新密码不能为空"); // 3. 检查是否是管理员 boolean isAdmin = isAdminUser(currentUserId); if (!isAdmin) { throw new BizException("只有管理员才能重置密码"); } // 4. 查询用户(支持用户名或手机号) SysUser sysUser = sysUserMapper.queryById(resetPasswordDto.getUserId()); try { // 5. 参照密码创建时候的逻辑,对新密码进行双重加密 String newEncryptedPwd = MD5Util.doubleEncrypt(resetPasswordDto.getNewPassword(), sysUser.getSalt()); sysUser.setPwd(newEncryptedPwd); sysUserMapper.update(sysUser); // 6. 清除旧的token CacheManager.removeTokenFromCache(sysUser.getUserName()); return true; } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } /** * 检查用户是否是管理员 * * @param userId 用户 ID * @return 是否是管理员 */ private boolean isAdminUser(Long userId) { // 先检查是否在管理员用户列表中 SysUser currentUser = sysUserMapper.queryById(userId); if (currentUser != null && Const.ADMIN_USER_LIST.contains(currentUser.getUserName().toLowerCase())) { return true; } // 检查用户是否有管理员角色(通过角色编码判断) List roles = queryUserRoles(userId); // 只要有一个角色编码是 "admin",就是管理员 for (String role : roles) { if ("admin".equalsIgnoreCase(role)) { return true; } } return false; } @Override @Cached(name = "sysUser:username:", key = "#username", expire = 3600, cacheType = com.alicp.jetcache.anno.CacheType.BOTH) @CachePenetrationProtect public SysUser getByUsername(String username) { if (Objects.nonNull(username)){ SysUser sysUser = sysUserMapper.getByUsername(username); if (sysUser != null) { sysUser.setPwd(null); } return sysUser; } return null; } @Override public boolean resetPwd(Long userId, String newPassword, String operator) { // 获取当前登录用户ID Long currentUserId = loginUserCacheUtil.getCurrentUserId(); Assert.notNull(currentUserId, "请先登录"); // 查询用户是否存在 SysUser sysUser = sysUserMapper.queryById(userId); Assert.notNull(sysUser, "用户不存在"); // 检查是否是用户自己修改密码,或者是管理员 boolean isAdmin = Const.ADMIN_USER_LIST.contains(operator.toLowerCase()); boolean isSelf = currentUserId.equals(sysUser.getUserId()); Assert.isTrue(isAdmin || isSelf, "只能修改自己的密码"); try { // 假设客户端已经对密码进行了一次MD5加密,服务端使用双重加密验证 String newEncryptedPwd = MD5Util.doubleEncrypt(newPassword, sysUser.getSalt()); sysUser.setPwd(newEncryptedPwd); // 更新用户密码 sysUserMapper.update(sysUser); // 清除旧的token CacheManager.removeTokenFromCache(sysUser.getUserName()); return true; } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } @Override public boolean resetPasswordByUsernameOrPhone(String usernameOrPhone, String newPassword, String operator) { // 获取当前登录用户ID Long currentUserId = loginUserCacheUtil.getCurrentUserId(); Assert.notNull(currentUserId, "请先登录"); // 查询用户是否存在(先尝试通过用户名查询,再尝试通过手机号查询) SysUser sysUser = sysUserMapper.getByUsername(usernameOrPhone); if (sysUser == null) { sysUser = sysUserMapper.getByTel(usernameOrPhone); } Assert.notNull(sysUser, "用户不存在"); // 检查是否是用户自己修改密码,或者是管理员 boolean isAdmin = Const.ADMIN_USER_LIST.contains(operator.toLowerCase()); boolean isSelf = currentUserId.equals(sysUser.getUserId()); Assert.isTrue(isAdmin || isSelf, "只能修改自己的密码"); try { // 假设客户端已经对密码进行了一次MD5加密,服务端使用双重加密验证 String newEncryptedPwd = MD5Util.doubleEncrypt(newPassword, sysUser.getSalt()); sysUser.setPwd(newEncryptedPwd); // 更新用户密码 sysUserMapper.update(sysUser); // 清除旧的token CacheManager.removeTokenFromCache(sysUser.getUserName()); return true; } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } /** * 发送手机验证码 * * @param phone 手机号 * @return 是否发送成功 */ @Override public boolean sendSmsCode(String phone) { try { // 参数校验 Assert.notBlank(phone, "手机号不能为空"); // 验证手机号格式 String phoneRegex = "^1[3-9]\\d{9}$"; if (!phone.matches(phoneRegex)) { Assert.isTrue(false, "手机号格式不正确"); } // 生成6位随机验证码 String code = generateSmsCode(); // 存储验证码到Redis,有效期5分钟 redissonClient.getBucket("sms_code:" + phone).set(code, 300, java.util.concurrent.TimeUnit.SECONDS); // 获取默认的短信发送器 SmsBlend sms = SmsFactory.getSmsBlend(); Assert.notNull(sms, "短信服务未正确配置"); log.info("获取短信发送器成功"); // 发送验证码 - 使用模板方式发送,避免直接文本发送可能导致的参数缺失 LinkedHashMap params = new LinkedHashMap<>(); params.put("code", code); // 参数名需要与阿里云模板中的变量名匹配 // 发送短信,使用配置文件中定义的模板 // 根据API文档和用户需求,使用单个手机号发送的方法:sendMessage(String phone, Map params) // 系统会自动从配置中读取template-id sms.sendMessage(phone, params); log.info("向手机号 {} 发送验证码:{}", phone, code); return true; } catch (Exception e) { log.error("向手机号 {} 发送验证码失败:{}", phone, e.getMessage()); log.error("详细错误信息:", e); // 如果短信服务不可用,可以记录错误并返回false而不是抛出异常 // 或者根据业务需求决定是否继续 Assert.isTrue(false, "发送验证码失败:" + e.getMessage()); } return false; } /** * 生成6位随机验证码 * * @return 6位随机验证码 */ private String generateSmsCode() { Random random = new Random(); return String.format("%06d", random.nextInt(1000000)); } /** * 手机号登录 * * @param phoneLoginDto 手机号登录信息 * @return 登录结果 */ @Override public LoginUserDto phoneLogin(PhoneLoginDto phoneLoginDto) { String phone = phoneLoginDto.getPhone(); String code = phoneLoginDto.getCode(); Assert.notBlank(phone, "手机号不能为空"); Assert.notBlank(code, "验证码不能为空"); // 验证验证码 String cachedCode = (String) redissonClient.getBucket("sms_code:" + phone).get(); Assert.notNull(cachedCode, "验证码已过期"); Assert.equals(cachedCode, code, "验证码错误"); // 验证成功后,删除验证码 redissonClient.getBucket("sms_code:" + phone).delete(); // 查询用户是否存在 SysUser sysUser = sysUserMapper.getByTel(phone); // 如果用户不存在,自动创建账号 if (sysUser == null) { sysUser = createUserByPhone(phone, phoneLoginDto.getInviteCode()); } // 检查用户是否已有 token,如果有则使旧 token 失效 String oldToken = CacheManager.getTokenFromCache(sysUser.getUserName()); if (oldToken != null && !oldToken.isEmpty()) { log.info("检测到用户 {} 已有旧 token: {}", sysUser.getUserName(), oldToken); // 从 Redis 中删除旧 token 对应的登录信息 try { redissonClient.getBucket("loginUser:" + oldToken).delete(); log.info("已删除 Redis 中的旧 token 数据:{}", oldToken); } catch (Exception e) { log.error("删除 Redis 中的旧 token 失败:{}", e.getMessage()); } // 使旧 token 失效(Sa-Token 内部操作) try { cn.dev33.satoken.stp.StpUtil.logoutByTokenValue(oldToken); log.info("已通过 Sa-Token 使旧 token 失效:{}", oldToken); } catch (Exception e) { log.error("Sa-Token 使旧 token 失效失败:{}", e.getMessage()); } // 从缓存中移除旧 token CacheManager.removeTokenFromCache(sysUser.getUserName()); log.info("用户:{}的旧 token 已完全失效", sysUser.getUserName()); } // 查询用户角色列表 List roles = queryUserRoles(sysUser.getUserId()); log.info("用户{}的角色列表:{}", sysUser.getUserName(), String.join(",", roles)); // 生成token并设置角色 String token = generateToken(sysUser.getUserId(), roles); log.info("设置后Sa-Token中的角色列表:{}", String.join(",", cn.dev33.satoken.stp.StpUtil.getRoleList())); // 创建LoginUser对象 LoginUser loginUser = new LoginUser(); // 设置用户基本信息 sysUser.setPwd(null); loginUser.setUserInfo(sysUser); loginUser.setRoles(roles); // 查询用户已购买的内容ID列表 List purchasedContentIds = new java.util.ArrayList<>(); try { // 创建查询条件,查询用户的购买记录 ContentPurchaseDto purchaseDto = new ContentPurchaseDto(); purchaseDto.setUserId(sysUser.getUserId()); List purchases = contentPurchaseMapper.getList(purchaseDto); // 遍历购买记录,获取内容ID for (ContentPurchase purchase : purchases) { purchasedContentIds.add(purchase.getContentId()); } } catch (Exception e) { log.error("查询用户已购买内容列表失败:{}", e.getMessage()); purchasedContentIds = java.util.Collections.emptyList(); } loginUser.setPurchasedContentIds(purchasedContentIds); // 查询用户账户信息 Account account = accountMapper.queryByUserId(sysUser.getUserId()); loginUser.setAccount(account); // 查询并设置用户最近点赞记录 loginUser.setFavorites(queryRecentFavorites(sysUser.getUserId())); // 查询并设置用户最近查看记录 loginUser.setHistory(queryRecentViews(sysUser.getUserId())); // 查询并设置用户最近创建记录 loginUser.setCreate(queryRecentCreatedContent(sysUser.getUserId())); // 查询并设置用户最近购买记录 loginUser.setHas(queryRecentPurchases(sysUser.getUserId())); // 设置token loginUser.setToken(token); // 将LoginUser对象存储到Redis缓存中,使用token作为key redissonClient.getBucket("loginUser:" + token).set(cn.hutool.json.JSONUtil.toJsonStr(loginUser), 86400, java.util.concurrent.TimeUnit.SECONDS); // 存储token与用户名的映射关系到CacheManager CacheManager.putTokenToCache(sysUser.getUserName(), token); // 创建LoginUserDto返回对象 LoginUserDto sysUserDto = buildLoginUserDto(loginUser); log.info("用户:{} 手机号登录成功,token:{}", phone, token); return sysUserDto; } /** * 根据手机号创建用户 * * @param phone 手机号 * @param inviteCode 邀请码 * @return 创建的用户对象 */ private SysUser createUserByPhone(String phone, String inviteCode) { SysUser sysUser = new SysUser(); // 设置用户名(使用手机号作为用户名) sysUser.setUserName(phone); sysUser.setTel(phone); // 生成随机邀请码(8位字母数字组合) String generatedInviteCode = generateInviteCode(); sysUser.setInviteCode(generatedInviteCode); // 处理邀请码 if (inviteCode != null && !inviteCode.isEmpty()) { // 验证邀请码是否有效 SysUser inviter = sysUserMapper.getByInviteCode(inviteCode); if (inviter == null) { throw new BizException("邀请码无效"); } // 设置被邀请码和邀请人用户ID sysUser.setInvitedCode(inviteCode); sysUser.setInvitedBy(inviter.getUserId()); // 给邀请人赠送100积分 accountService.addGiftBalance( inviter.getUserId(), BigDecimal.valueOf(100), "gift_" + System.currentTimeMillis(), null, "invite", "邀请用户注册赠送" ); // 给被邀请人(新用户)也赠送100积分作为邀请奖励 // 注意:这里先不执行,等账户创建后再执行 } // 设置固定salt为666666 String salt = "666666"; sysUser.setSalt(salt); // 设置默认密码为000000,按照验证逻辑计算存储值 // 验证逻辑:MD5Util.doubleEncrypt(password, salt) = MD5Util.encryptToHex(clientEncryptedPassword + salt) // 客户端会发送MD5("000000"),服务端存储MD5(MD5("000000") + salt) try { // 计算客户端加密后的密码:MD5("000000") String clientEncryptedPassword = MD5Util.encryptToHex("000000"); // 计算数据库存储的密码:MD5(clientEncryptedPassword + salt) String storedPassword = MD5Util.encryptToHex(clientEncryptedPassword + salt); sysUser.setPwd(storedPassword); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } // 直接调用mapper插入,跳过insert方法的加密逻辑 sysUser.setEnable(Const.USER_STATUS_NORMAL); sysUser.setDeleteFlag(Const.DELETE_FLAG_NO); sysUserMapper.insert(sysUser); // 初始化账户(余额账户) Account account = new Account(); account.setUserId(sysUser.getUserId()); account.setUserName(sysUser.getUserName()); account.setBalance(BigDecimal.ZERO); account.setFrozenAmount(BigDecimal.ZERO); account.setDeleteFlag(Const.DELETE_FLAG_NO); accountMapper.insert(account); // 注册赠送100积分 accountService.addGiftBalance( sysUser.getUserId(), BigDecimal.valueOf(100), "gift_" + System.currentTimeMillis(), null, "register", "注册赠送" ); // 如果有邀请码,额外赠送100积分作为邀请奖励 if (inviteCode != null && !inviteCode.isEmpty()) { accountService.addGiftBalance( sysUser.getUserId(), BigDecimal.valueOf(100), "gift_" + System.currentTimeMillis(), null, "invite_reward", "通过邀请码注册赠送" ); } return sysUser; } /** * 生成8位邀请码 * * @return 邀请码 */ private String generateInviteCode() { String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; Random random = new Random(); StringBuilder sb = new StringBuilder(8); for (int i = 0; i < 8; i++) { sb.append(characters.charAt(random.nextInt(characters.length()))); } return sb.toString(); } /** * 生成随机密码 * * @return 随机密码 */ private String generateRandomPassword() { String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; Random random = new Random(); StringBuilder sb = new StringBuilder(12); for (int i = 0; i < 12; i++) { sb.append(characters.charAt(random.nextInt(characters.length()))); } return sb.toString(); } @Override public SessionDto createSession(Long userId) { SessionDto sessionDto = new SessionDto(); // 查询用户信息 SysUser sysUser = sysUserMapper.queryById(userId); if (sysUser == null) { throw new RuntimeException("用户不存在"); } String sessionId = sysUser.getSessionId(); if (sessionId != null && !sessionId.isEmpty()) { // 已有会话,直接返回 sessionDto.setSessionId(sessionId); sessionDto.setNew(false); } else { // 没有会话,创建新的会话ID sessionId = java.util.UUID.randomUUID().toString(); sysUser.setSessionId(sessionId); sysUserMapper.update(sysUser); sessionDto.setSessionId(sessionId); sessionDto.setNew(true); } return sessionDto; } /** * 更新用户缓存(参照 currentUser 方法的逻辑) * * @param userId 用户ID * @param updatedUser 更新后的用户信息 */ private void updateUserCache(Long userId, SysUser updatedUser) { try { // 获取当前登录用户的 token String token = loginUserCacheUtil.getTokenFromRequest(); if (token == null || token.isEmpty()) { log.warn("无法获取当前用户的 token,跳过缓存更新"); return; } // 从 Redis 中获取 LoginUser 对象 String loginUserJson = (String) redissonClient.getBucket("loginUser:" + token).get(); if (loginUserJson == null || loginUserJson.isEmpty()) { log.warn("Redis 中未找到 LoginUser 缓存,token: {}", token); return; } // 解析 JSON 字符串为 LoginUser 对象 LoginUser loginUser = cn.hutool.json.JSONUtil.toBean(loginUserJson, LoginUser.class); // 验证是否是当前用户 if (loginUser != null && loginUser.getUserInfo() != null && loginUser.getUserInfo().getUserId().equals(userId)) { // 更新用户信息(密码设置为 null,不暴露给前端) updatedUser.setPwd(null); loginUser.setUserInfo(updatedUser); // 将更新后的 LoginUser 写回 Redis redissonClient.getBucket("loginUser:" + token) .set(cn.hutool.json.JSONUtil.toJsonStr(loginUser)); log.info("已更新用户 {} 的缓存信息", userId); } } catch (Exception e) { log.error("更新用户缓存失败:{}", e.getMessage(), e); } } }