package com.kexue.skills.service.impl; import cn.hutool.core.bean.BeanUtil; 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.config.CaptchaConfig; import com.kexue.skills.entity.*; import com.kexue.skills.entity.dto.ContentPurchaseDto; import com.kexue.skills.entity.dto.SysUserDto; import com.kexue.skills.entity.request.*; import com.kexue.skills.mapper.*; import com.kexue.skills.service.SysUserService; 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.RBucket; 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.LinkedHashMap; import java.util.List; import java.util.Objects; import java.util.Random; /** * (SysUser)表服务实现类 * * @author 王志维 * @since 2025-02-21 23:01:48 */ @Service("sysUserService") @Slf4j public class SysUserServiceImpl implements SysUserService { @Resource private SysUserMapper sysUserMapper; @Resource private org.springframework.data.redis.core.RedisTemplate redisTemplate; @Resource private RedissonClient redissonClient; @Resource private CaptchaConfig captchaConfig; @Resource private PointsAccountMapper pointsAccountMapper; @Resource private AccountMapper accountMapper; @Resource private SysUserRoleMapper sysUserRoleMapper; @Resource private ContentPurchaseMapper contentPurchaseMapper; @Resource private CmsContentLikeMapper cmsContentLikeMapper; @Resource private CmsContentViewMapper cmsContentViewMapper; @Resource private CmsContentMapper cmsContentMapper; /** * 分页查询数据 * * @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()+""); try { String md5Pwd = MD5Util.encryptToHex(sysUser.getPwd()+sysUser.getSalt()); sysUser.setPwd(md5Pwd); } 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); // 初始化积分账户 PointsAccount pointsAccount = new PointsAccount(); pointsAccount.setUserId(sysUser.getUserId()); pointsAccount.setUserName(sysUser.getUserName()); pointsAccount.setTotalPoints(0); // 初始总积分为0 pointsAccount.setAvailablePoints(0); // 初始可用积分为0 pointsAccount.setFrozenPoints(0); // 初始冻结积分为0 pointsAccount.setDeleteFlag(Const.DELETE_FLAG_NO); // 初始未删除 pointsAccountMapper.insert(pointsAccount); // 将返回的用户密码设置为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 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()), "该用户是管理员,不能删除"); // 删除用户名缓存 redisTemplate.delete("sysUser:username:" + sysUser.getUserName()); return sysUserMapper.deleteById(userId) > 0; } @Override public LoginUserDto login(LoginDto loginDto) { // 参数验证 validateLoginParams(loginDto); // 验证码验证 validateCaptcha(loginDto); // 查询用户 SysUser sysUser = getUserByUsername(loginDto.getUsername()); // 密码验证 validatePassword(loginDto.getPassword(), sysUser); // 生成token String token = generateToken(sysUser.getUserId()); // 构建登录用户信息 LoginUser loginUser = buildLoginUser(sysUser, token); // 存储登录信息到Redis saveLoginUserToRedis(token, loginUser); // 构建返回对象 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 = redisTemplate.opsForValue().get(captchaKey); Assert.notNull(captchaText, "验证码已过期"); Assert.equals(captchaText, loginDto.getCaptchaValue().toLowerCase(), "验证码错误"); redisTemplate.delete(captchaKey); } } /** * 根据用户名查询用户 * * @param username 用户名 * @return 用户对象 */ private SysUser getUserByUsername(String username) { SysUser sysUser = sysUserMapper.getByUsername(username); 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 * @return token */ private String generateToken(Long userId) { cn.dev33.satoken.stp.StpUtil.login(userId); 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.setPointsAccount(queryUserPointsAccount(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 角色列表 */ private List queryUserRoles(Long userId) { List roles = new java.util.ArrayList<>(); try { SysUserRole userRole = new SysUserRole(); userRole.setUserId(userId); List userRoles = sysUserRoleMapper.queryAll(userRole); for (SysUserRole ur : userRoles) { roles.add("ROLE_" + ur.getRoleId()); } } catch (Exception e) { log.error("查询用户角色列表失败:{}", e.getMessage()); roles = java.util.Collections.emptyList(); } return roles; } /** * 查询用户已购买的内容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 PointsAccount queryUserPointsAccount(Long userId) { return pointsAccountMapper.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) { redisTemplate.opsForValue().set( "loginUser:" + token, cn.hutool.json.JSONUtil.toJsonStr(loginUser), 3600, java.util.concurrent.TimeUnit.SECONDS ); } /** * 构建登录用户返回对象 * * @param loginUser 登录用户信息 * @return 登录用户返回对象 */ private LoginUserDto buildLoginUserDto(LoginUser loginUser) { LoginUserDto sysUserDto = new LoginUserDto(); BeanUtil.copyProperties(loginUser.getUserInfo(), sysUserDto); sysUserDto.setToken(loginUser.getToken()); sysUserDto.setFavorites(loginUser.getFavorites()); sysUserDto.setHistory(loginUser.getHistory()); sysUserDto.setCreate(loginUser.getCreate()); sysUserDto.setHas(loginUser.getHas()); return sysUserDto; } @Override public boolean resetPassword(ResetPasswordDto resetPasswordDto) { Assert.notNull(resetPasswordDto.getUserName(), "用户名不能位空"); Assert.notNull(resetPasswordDto.getOldPassword(), "旧密码不能位空"); Assert.notNull(resetPasswordDto.getNewPassword(), "新密码不能位空"); SysUser sysUser = sysUserMapper.getByUsername(resetPasswordDto.getUserName()); Assert.notNull(sysUser, "用户名不存在"); try { String oldMd5Pwd = MD5Util.encryptToHex(resetPasswordDto.getOldPassword() + sysUser.getSalt()); Assert.equals(oldMd5Pwd, sysUser.getPwd(), "旧密码不正确"); String newMd5Pwd = MD5Util.encryptToHex(resetPasswordDto.getNewPassword() + sysUser.getSalt()); sysUser.setPwd(newMd5Pwd); sysUserMapper.update(sysUser); // 清除旧的token CacheManager.removeTokenFromCache(resetPasswordDto.getUserName()); return true; } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } @Override public boolean resetPasswordByAdmin(ResetPasswordDto resetPasswordDto) { Assert.notNull(resetPasswordDto.getUserName(), "用户名不能位空"); Assert.notNull(resetPasswordDto.getNewPassword(), "新密码不能位空"); SysUser sysUser = sysUserMapper.getByUsername(resetPasswordDto.getUserName()); Assert.notNull(sysUser, "用户名不存在"); try { String newMd5Pwd = MD5Util.encryptToHex(resetPasswordDto.getNewPassword() + sysUser.getSalt()); sysUser.setPwd(newMd5Pwd); sysUserMapper.update(sysUser); // 清除旧的token CacheManager.removeTokenFromCache(resetPasswordDto.getUserName()); return true; } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } @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) { // 检查操作人是否为管理员 Assert.isTrue(Const.ADMIN_USER_LIST.contains(operator.toLowerCase()), "只有管理员才能执行此操作"); // 查询用户是否存在 SysUser sysUser = sysUserMapper.queryById(userId); Assert.notNull(sysUser, "用户不存在"); try { // 生成新的加密密码 String newMd5Pwd = MD5Util.encryptToHex(newPassword + sysUser.getSalt()); sysUser.setPwd(newMd5Pwd); // 更新用户密码 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, "手机号不能为空"); // 生成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 = redisTemplate.opsForValue().get("sms_code:" + phone); Assert.notNull(cachedCode, "验证码已过期"); Assert.equals(cachedCode, code, "验证码错误"); // 验证成功后,删除验证码 redisTemplate.delete("sms_code:" + phone); // 查询用户是否存在 SysUser sysUser = sysUserMapper.getByTel(phone); // 如果用户不存在,自动创建账号 if (sysUser == null) { sysUser = createUserByPhone(phone); } // 使用Sa-Token登录,生成token cn.dev33.satoken.stp.StpUtil.login(sysUser.getUserId()); // 获取生成的token String token = cn.dev33.satoken.stp.StpUtil.getTokenValue(); // 创建LoginUser对象 LoginUser loginUser = new LoginUser(); // 设置用户基本信息 sysUser.setPwd(null); loginUser.setUserInfo(sysUser); // 查询用户角色列表 List roles = new java.util.ArrayList<>(); try { // 创建查询条件,查询用户的角色关联记录 SysUserRole userRole = new SysUserRole(); userRole.setUserId(sysUser.getUserId()); List userRoles = sysUserRoleMapper.queryAll(userRole); // 遍历角色关联记录,获取角色名称 for (SysUserRole ur : userRoles) { roles.add("ROLE_" + ur.getRoleId()); } } catch (Exception e) { log.error("查询用户角色列表失败:{}", e.getMessage()); roles = java.util.Collections.emptyList(); } 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); // 查询用户积分信息 PointsAccount pointsAccount = pointsAccountMapper.queryByUserId(sysUser.getUserId()); loginUser.setPointsAccount(pointsAccount); // 查询并设置用户最近点赞记录 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 redisTemplate.opsForValue().set("loginUser:" + token, cn.hutool.json.JSONUtil.toJsonStr(loginUser), 3600, java.util.concurrent.TimeUnit.SECONDS); // 创建LoginUserDto返回对象 LoginUserDto sysUserDto = buildLoginUserDto(loginUser); log.info("用户:{} 手机号登录成功,token:{}", phone, token); return sysUserDto; } /** * 根据手机号创建用户 * * @param phone 手机号 * @return 创建的用户对象 */ private SysUser createUserByPhone(String phone) { SysUser sysUser = new SysUser(); // 设置用户名(使用手机号作为用户名) sysUser.setUserName(phone); sysUser.setTel(phone); // 生成随机密码 String randomPassword = generateRandomPassword(); sysUser.setPwd(randomPassword); // 调用insert方法创建用户 return insert(sysUser); } /** * 生成随机密码 * * @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(); } }