feat(auth): 切换Redis客户端并添加管理员重置密码功能

- 将RedisTemplate替换为RedissonClient以提升性能和稳定性
- 添加管理员通过用户名或手机号重置密码的新功能
- 重构登录用户信息DTO结构,分离用户基本信息和令牌
- 更新验证码存储和验证逻辑以兼容Redisson客户端
- 修改手机号注册逻辑,统一默认密码设置方式
- 优化登录用户信息缓存存储和读取机制
This commit is contained in:
wangzhiwei 2026-01-28 18:28:36 +08:00
parent 3b83ddc2cf
commit 4bb89d2605
6 changed files with 175 additions and 35 deletions

View File

@ -7,7 +7,7 @@ import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.data.redis.core.RedisTemplate;
import org.redisson.api.RedissonClient;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@ -27,14 +27,13 @@ import java.util.concurrent.TimeUnit;
public class CaptchaController {
@Resource
private RedisTemplate<String, String> redisTemplate;
private CaptchaConfig captchaConfig;
@Resource
private CaptchaConfig captchaConfig;
private RedissonClient redissonClient;
/**
* 生成验证码
* @param response
* @throws IOException
*/
@GetMapping("/generate")
@ -58,7 +57,7 @@ public class CaptchaController {
String captchaText = captcha.text().toLowerCase();
// 将验证码存储到Redis中使用配置的有效期
redisTemplate.opsForValue().set("captcha:" + captchaId, captchaText, captchaConfig.getExpireTime(), TimeUnit.SECONDS);
redissonClient.getBucket("captcha:" + captchaId).set(captchaText, captchaConfig.getExpireTime(), java.util.concurrent.TimeUnit.SECONDS);
// 生成验证码图片的Base64编码
String base64Image = captcha.toBase64();
@ -90,7 +89,7 @@ public class CaptchaController {
}
// 从Redis中获取验证码
String captchaText = redisTemplate.opsForValue().get("captcha:" + captchaId);
String captchaText = (String) redissonClient.getBucket("captcha:" + captchaId).get();
if (captchaText == null) {
return CommonResult.failed("验证码已过期");
}
@ -98,7 +97,7 @@ public class CaptchaController {
// 验证验证码
if (captchaText.equals(captchaValue.toLowerCase())) {
// 验证成功后删除验证码
redisTemplate.delete("captcha:" + captchaId);
redissonClient.getBucket("captcha:" + captchaId).delete();
return CommonResult.success(true);
} else {
return CommonResult.failed("验证码错误");

View File

@ -5,6 +5,7 @@ import com.kexue.skills.entity.SysUser;
import com.kexue.skills.entity.dto.SysUserDto;
import com.kexue.skills.entity.request.ResetPasswordDto;
import com.kexue.skills.entity.request.ResetPwdDto;
import com.kexue.skills.entity.request.AdminResetPasswordDto;
import com.kexue.skills.exception.BizException;
import com.kexue.skills.service.SysUserService;
import org.springframework.web.bind.annotation.*;
@ -13,11 +14,11 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import javax.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import com.kexue.skills.common.CacheManager;
import org.springframework.data.redis.core.RedisTemplate;
import com.github.pagehelper.PageInfo;
import com.kexue.skills.common.CommonResult;
import com.kexue.skills.entity.base.IdDto;
import com.kexue.skills.entity.request.LoginUserDto;
import org.redisson.api.RedissonClient;
/**
* (SysUser)表控制层
@ -37,10 +38,10 @@ public class SysUserController {
private SysUserService sysUserService;
/**
* Redis模板
* Redisson客户端
*/
@Resource
private RedisTemplate<String, String> redisTemplate;
private RedissonClient redissonClient;
/**
* 分页查询
*
@ -165,6 +166,39 @@ public class SysUserController {
return CommonResult.success(result);
}
/**
* 重置密码管理员专用通过用户名或手机号
*
* @param resetPasswordDto 重置密码请求参数
* @param request HTTP请求
* @return 重置结果
*/
@PostMapping("/resetPasswordByUsernameOrPhone")
@Operation(summary = "重置密码(管理员专用,通过用户名或手机号)", description = "重置密码(管理员专用,通过用户名或手机号,无需旧密码)")
@RequireAuth
public CommonResult<Boolean> resetPasswordByUsernameOrPhone(@RequestBody AdminResetPasswordDto resetPasswordDto, HttpServletRequest request) {
// 从请求头中获取token
String token = request.getHeader("Authorization");
if (token == null || token.isEmpty()) {
throw new BizException("请先登录认证后操作");
}
// 从缓存中获取当前登录用户
String username = CacheManager.getUsernameFromToken(token);
if (username == null) {
throw new BizException("无效的token请重新登录");
}
SysUser adminUser = sysUserService.getByUsername(username);
if (adminUser == null) {
throw new BizException("管理员不存在");
}
// 调用服务层方法重置密码
boolean result = sysUserService.resetPasswordByUsernameOrPhone(resetPasswordDto.getUsernameOrPhone(), resetPasswordDto.getNewPassword(), username);
return CommonResult.success(result);
}
/**
* 获取当前登录用户信息
*
@ -174,7 +208,7 @@ public class SysUserController {
@GetMapping("/currentUser")
@Operation(summary = "获取当前登录用户信息", description = "获取当前登录用户信息")
@RequireAuth
public CommonResult<com.kexue.skills.entity.request.LoginUser> currentUser(HttpServletRequest request) {
public CommonResult<LoginUserDto> currentUser(HttpServletRequest request) {
// 从请求头中获取token
String token = request.getHeader("Authorization");
if (token == null || token.isEmpty()) {
@ -182,7 +216,7 @@ public class SysUserController {
}
// 从Redis缓存中获取LoginUser对象
String loginUserJson = redisTemplate.opsForValue().get("loginUser:" + token);
String loginUserJson = (String)redissonClient.getBucket("loginUser:" + token).get();
if (loginUserJson == null || loginUserJson.isEmpty()) {
throw new BizException("无效的token请重新登录");
}
@ -190,6 +224,15 @@ public class SysUserController {
// 解析JSON字符串为LoginUser对象
com.kexue.skills.entity.request.LoginUser loginUser = cn.hutool.json.JSONUtil.toBean(loginUserJson, com.kexue.skills.entity.request.LoginUser.class);
return CommonResult.success(loginUser);
// 转换为LoginUserDto
LoginUserDto loginUserDto = new LoginUserDto();
loginUserDto.setToken(loginUser.getToken());
loginUserDto.setUserInfo(loginUser.getUserInfo());
loginUserDto.setFavorites(loginUser.getFavorites());
loginUserDto.setHistory(loginUser.getHistory());
loginUserDto.setCreate(loginUser.getCreate());
loginUserDto.setHas(loginUser.getHas());
return CommonResult.success(loginUserDto);
}
}

View File

@ -0,0 +1,23 @@
package com.kexue.skills.entity.request;
import io.swagger.annotations.ApiModel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* @author 维哥
* @Description 管理员重置密码请求参数无需旧密码
* @create 2025-02-25 15:53
*/
@Data
@ApiModel(value = "管理员重置密码请求参数")
public class AdminResetPasswordDto implements Serializable {
@Schema(description = "用户名或手机号")
private String usernameOrPhone;
@Schema(description = "新密码")
private String newPassword;
}

View File

@ -13,9 +13,14 @@ import java.util.List;
*/
@Data
@ApiModel(value = "登录用户信息")
public class LoginUserDto extends SysUser {
public class LoginUserDto {
String token;
/**
* 用户信息
*/
SysUser userInfo;
/**
* 最近点赞的20条记录
*/

View File

@ -85,6 +85,16 @@ public interface SysUserService extends BaseService {
*/
boolean resetPwd(Long userId, String newPassword, String operator);
/**
* 管理员重置密码通过用户名或手机号无需旧密码
*
* @param usernameOrPhone 用户名或手机号
* @param newPassword 新密码
* @param operator 操作人
* @return 是否成功
*/
boolean resetPasswordByUsernameOrPhone(String usernameOrPhone, String newPassword, String operator);
/**
* 发送手机验证码
*

View File

@ -45,8 +45,7 @@ public class SysUserServiceImpl implements SysUserService {
@Resource
private SysUserMapper sysUserMapper;
@Resource
private org.springframework.data.redis.core.RedisTemplate<String, String> redisTemplate;
@Resource
private RedissonClient redissonClient;
@ -207,7 +206,7 @@ public class SysUserServiceImpl implements SysUserService {
Assert.isFalse(Const.ADMIN_USER_LIST.contains(sysUser.getUserName().toLowerCase()), "该用户是管理员,不能删除");
// 删除用户名缓存
redisTemplate.delete("sysUser:username:" + sysUser.getUserName());
redissonClient.getBucket("sysUser:username:" + sysUser.getUserName()).delete();
return sysUserMapper.deleteById(userId) > 0;
}
@ -264,11 +263,11 @@ public class SysUserServiceImpl implements SysUserService {
Assert.notNull(loginDto.getCaptchaValue(), "验证码不能为空");
String captchaKey = "captcha:" + loginDto.getCaptchaId();
String captchaText = redisTemplate.opsForValue().get(captchaKey);
String captchaText = (String) redissonClient.getBucket(captchaKey).get();
Assert.notNull(captchaText, "验证码已过期");
Assert.equals(captchaText, loginDto.getCaptchaValue().toLowerCase(), "验证码错误");
redisTemplate.delete(captchaKey);
redissonClient.getBucket(captchaKey).delete();
}
}
@ -503,12 +502,8 @@ public class SysUserServiceImpl implements SysUserService {
* @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
);
redissonClient.getBucket("loginUser:" + token)
.set(cn.hutool.json.JSONUtil.toJsonStr(loginUser), 3600, java.util.concurrent.TimeUnit.SECONDS);
}
/**
@ -519,7 +514,7 @@ public class SysUserServiceImpl implements SysUserService {
*/
private LoginUserDto buildLoginUserDto(LoginUser loginUser) {
LoginUserDto sysUserDto = new LoginUserDto();
BeanUtil.copyProperties(loginUser.getUserInfo(), sysUserDto);
sysUserDto.setUserInfo(loginUser.getUserInfo());
sysUserDto.setToken(loginUser.getToken());
sysUserDto.setFavorites(loginUser.getFavorites());
sysUserDto.setHistory(loginUser.getHistory());
@ -616,6 +611,35 @@ public class SysUserServiceImpl implements SysUserService {
}
}
@Override
public boolean resetPasswordByUsernameOrPhone(String usernameOrPhone, String newPassword, String operator) {
// 检查操作人是否为管理员
//Assert.isTrue(Const.ADMIN_USER_LIST.contains(operator.toLowerCase()), "只有管理员才能执行此操作");
// 查询用户是否存在先尝试通过用户名查询再尝试通过手机号查询
SysUser sysUser = sysUserMapper.getByUsername(usernameOrPhone);
if (sysUser == null) {
sysUser = sysUserMapper.getByTel(usernameOrPhone);
}
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);
}
}
/**
* 发送手机验证码
*
@ -692,7 +716,7 @@ public class SysUserServiceImpl implements SysUserService {
Assert.equals(cachedCode, code, "验证码错误");
// 验证成功后删除验证码
//redissonClient.getBucket("sms_code:" + phone).delete();
redissonClient.getBucket("sms_code:" + phone).delete();
// 查询用户是否存在
SysUser sysUser = sysUserMapper.getByTel(phone);
@ -774,7 +798,7 @@ public class SysUserServiceImpl implements SysUserService {
loginUser.setToken(token);
// 将LoginUser对象存储到Redis缓存中使用token作为key
redisTemplate.opsForValue().set("loginUser:" + token, cn.hutool.json.JSONUtil.toJsonStr(loginUser), 3600, java.util.concurrent.TimeUnit.SECONDS);
redissonClient.getBucket("loginUser:" + token).set(cn.hutool.json.JSONUtil.toJsonStr(loginUser), 3600, java.util.concurrent.TimeUnit.SECONDS);
// 创建LoginUserDto返回对象
LoginUserDto sysUserDto = buildLoginUserDto(loginUser);
@ -797,12 +821,48 @@ public class SysUserServiceImpl implements SysUserService {
sysUser.setUserName(phone);
sysUser.setTel(phone);
// 生成随机密码
String randomPassword = generateRandomPassword();
sysUser.setPwd(randomPassword);
// 设置固定salt为666666
String salt = "666666";
sysUser.setSalt(salt);
// 调用insert方法创建用户
return insert(sysUser);
// 设置默认密码为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);
// 初始化积分账户
PointsAccount pointsAccount = new PointsAccount();
pointsAccount.setUserId(sysUser.getUserId());
pointsAccount.setUserName(sysUser.getUserName());
pointsAccount.setTotalPoints(0);
pointsAccount.setAvailablePoints(0);
pointsAccount.setFrozenPoints(0);
pointsAccount.setDeleteFlag(Const.DELETE_FLAG_NO);
pointsAccountMapper.insert(pointsAccount);
return sysUser;
}
/**