sxwz2.0/src/main/java/com/kexue/skills/service/impl/SysUserServiceImpl.java
wangzhiwei 0d934f7287 feat(account): 添加问题字段和调用ID关联功能
- 在AccountFrozen实体和DTO中添加question字段用于记录用户问题或需求
- 在AccountTransaction实体中添加callId字段用于关联冻结单调用
- 更新数据库映射文件中的查询和插入语句以支持新增字段
- 在账户冻结服务中实现question字段的赋值逻辑
- 在冻结单释放时将question和callId传递到交易记录中
- 添加数据库表结构变更SQL脚本为account_frozen表增加question字段
- 添加数据库表结构变更SQL脚本为account_transaction表增加call_id字段及索引
- 在用户注册流程中实现邀请奖励机制为被邀请用户赠送积分
2026-04-14 21:38:05 +08:00

1195 lines
44 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<SysUser> getPageList(SysUserDto queryDto) {
int pageNum = queryDto.getPageNum()==null?PAGENUM:queryDto.getPageNum();
int pageSize = queryDto.getPageSize()==null? PAGESIZE :queryDto.getPageSize();
PageHelper.startPage(pageNum,pageSize);
List<SysUser> 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<SysUser> queryAllByLimit(int offset, int limit) {
List<SysUser> 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<String> 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<String> 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<String> 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<Long> queryPurchasedContentIds(Long userId) {
List<Long> purchasedContentIds = new java.util.ArrayList<>();
try {
ContentPurchaseDto purchaseDto = new ContentPurchaseDto();
purchaseDto.setUserId(userId);
List<ContentPurchase> 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<Long> 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<Long> 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<Long> 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<Long> queryRecentPurchases(Long userId) {
List<Long> has = new java.util.ArrayList<>();
try {
ContentPurchaseDto purchaseDto = new ContentPurchaseDto();
purchaseDto.setUserId(userId);
List<ContentPurchase> purchases = contentPurchaseMapper.getList(purchaseDto);
if (purchases != null && !purchases.isEmpty()) {
java.util.LinkedHashSet<Long> 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<String> 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<String, String> params = new LinkedHashMap<>();
params.put("code", code); // 参数名需要与阿里云模板中的变量名匹配
// 发送短信,使用配置文件中定义的模板
// 根据API文档和用户需求使用单个手机号发送的方法sendMessage(String phone, Map<String, String> 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<String> 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<Long> purchasedContentIds = new java.util.ArrayList<>();
try {
// 创建查询条件,查询用户的购买记录
ContentPurchaseDto purchaseDto = new ContentPurchaseDto();
purchaseDto.setUserId(sysUser.getUserId());
List<ContentPurchase> 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);
}
}
}