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 com.wf.captcha.base.Captcha;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; 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 org.springframework.web.bind.annotation.*;
import javax.annotation.Resource; import javax.annotation.Resource;
@ -27,14 +27,13 @@ import java.util.concurrent.TimeUnit;
public class CaptchaController { public class CaptchaController {
@Resource @Resource
private RedisTemplate<String, String> redisTemplate; private CaptchaConfig captchaConfig;
@Resource @Resource
private CaptchaConfig captchaConfig; private RedissonClient redissonClient;
/** /**
* 生成验证码 * 生成验证码
* @param response
* @throws IOException * @throws IOException
*/ */
@GetMapping("/generate") @GetMapping("/generate")
@ -58,7 +57,7 @@ public class CaptchaController {
String captchaText = captcha.text().toLowerCase(); String captchaText = captcha.text().toLowerCase();
// 将验证码存储到Redis中使用配置的有效期 // 将验证码存储到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编码 // 生成验证码图片的Base64编码
String base64Image = captcha.toBase64(); String base64Image = captcha.toBase64();
@ -90,7 +89,7 @@ public class CaptchaController {
} }
// 从Redis中获取验证码 // 从Redis中获取验证码
String captchaText = redisTemplate.opsForValue().get("captcha:" + captchaId); String captchaText = (String) redissonClient.getBucket("captcha:" + captchaId).get();
if (captchaText == null) { if (captchaText == null) {
return CommonResult.failed("验证码已过期"); return CommonResult.failed("验证码已过期");
} }
@ -98,7 +97,7 @@ public class CaptchaController {
// 验证验证码 // 验证验证码
if (captchaText.equals(captchaValue.toLowerCase())) { if (captchaText.equals(captchaValue.toLowerCase())) {
// 验证成功后删除验证码 // 验证成功后删除验证码
redisTemplate.delete("captcha:" + captchaId); redissonClient.getBucket("captcha:" + captchaId).delete();
return CommonResult.success(true); return CommonResult.success(true);
} else { } else {
return CommonResult.failed("验证码错误"); 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.dto.SysUserDto;
import com.kexue.skills.entity.request.ResetPasswordDto; import com.kexue.skills.entity.request.ResetPasswordDto;
import com.kexue.skills.entity.request.ResetPwdDto; import com.kexue.skills.entity.request.ResetPwdDto;
import com.kexue.skills.entity.request.AdminResetPasswordDto;
import com.kexue.skills.exception.BizException; import com.kexue.skills.exception.BizException;
import com.kexue.skills.service.SysUserService; import com.kexue.skills.service.SysUserService;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -13,11 +14,11 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import javax.annotation.Resource; import javax.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import com.kexue.skills.common.CacheManager; import com.kexue.skills.common.CacheManager;
import org.springframework.data.redis.core.RedisTemplate;
import com.github.pagehelper.PageInfo; import com.github.pagehelper.PageInfo;
import com.kexue.skills.common.CommonResult; import com.kexue.skills.common.CommonResult;
import com.kexue.skills.entity.base.IdDto; import com.kexue.skills.entity.base.IdDto;
import com.kexue.skills.entity.request.LoginUserDto;
import org.redisson.api.RedissonClient;
/** /**
* (SysUser)表控制层 * (SysUser)表控制层
@ -37,10 +38,10 @@ public class SysUserController {
private SysUserService sysUserService; private SysUserService sysUserService;
/** /**
* Redis模板 * Redisson客户端
*/ */
@Resource @Resource
private RedisTemplate<String, String> redisTemplate; private RedissonClient redissonClient;
/** /**
* 分页查询 * 分页查询
* *
@ -165,6 +166,39 @@ public class SysUserController {
return CommonResult.success(result); 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") @GetMapping("/currentUser")
@Operation(summary = "获取当前登录用户信息", description = "获取当前登录用户信息") @Operation(summary = "获取当前登录用户信息", description = "获取当前登录用户信息")
@RequireAuth @RequireAuth
public CommonResult<com.kexue.skills.entity.request.LoginUser> currentUser(HttpServletRequest request) { public CommonResult<LoginUserDto> currentUser(HttpServletRequest request) {
// 从请求头中获取token // 从请求头中获取token
String token = request.getHeader("Authorization"); String token = request.getHeader("Authorization");
if (token == null || token.isEmpty()) { if (token == null || token.isEmpty()) {
@ -182,7 +216,7 @@ public class SysUserController {
} }
// 从Redis缓存中获取LoginUser对象 // 从Redis缓存中获取LoginUser对象
String loginUserJson = redisTemplate.opsForValue().get("loginUser:" + token); String loginUserJson = (String)redissonClient.getBucket("loginUser:" + token).get();
if (loginUserJson == null || loginUserJson.isEmpty()) { if (loginUserJson == null || loginUserJson.isEmpty()) {
throw new BizException("无效的token请重新登录"); throw new BizException("无效的token请重新登录");
} }
@ -190,6 +224,15 @@ public class SysUserController {
// 解析JSON字符串为LoginUser对象 // 解析JSON字符串为LoginUser对象
com.kexue.skills.entity.request.LoginUser loginUser = cn.hutool.json.JSONUtil.toBean(loginUserJson, com.kexue.skills.entity.request.LoginUser.class); 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 @Data
@ApiModel(value = "登录用户信息") @ApiModel(value = "登录用户信息")
public class LoginUserDto extends SysUser { public class LoginUserDto {
String token; String token;
/**
* 用户信息
*/
SysUser userInfo;
/** /**
* 最近点赞的20条记录 * 最近点赞的20条记录
*/ */

View File

@ -85,6 +85,16 @@ public interface SysUserService extends BaseService {
*/ */
boolean resetPwd(Long userId, String newPassword, String operator); 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 @Resource
private SysUserMapper sysUserMapper; private SysUserMapper sysUserMapper;
@Resource
private org.springframework.data.redis.core.RedisTemplate<String, String> redisTemplate;
@Resource @Resource
private RedissonClient redissonClient; private RedissonClient redissonClient;
@ -207,7 +206,7 @@ public class SysUserServiceImpl implements SysUserService {
Assert.isFalse(Const.ADMIN_USER_LIST.contains(sysUser.getUserName().toLowerCase()), "该用户是管理员,不能删除"); 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; return sysUserMapper.deleteById(userId) > 0;
} }
@ -264,11 +263,11 @@ public class SysUserServiceImpl implements SysUserService {
Assert.notNull(loginDto.getCaptchaValue(), "验证码不能为空"); Assert.notNull(loginDto.getCaptchaValue(), "验证码不能为空");
String captchaKey = "captcha:" + loginDto.getCaptchaId(); String captchaKey = "captcha:" + loginDto.getCaptchaId();
String captchaText = redisTemplate.opsForValue().get(captchaKey); String captchaText = (String) redissonClient.getBucket(captchaKey).get();
Assert.notNull(captchaText, "验证码已过期"); Assert.notNull(captchaText, "验证码已过期");
Assert.equals(captchaText, loginDto.getCaptchaValue().toLowerCase(), "验证码错误"); Assert.equals(captchaText, loginDto.getCaptchaValue().toLowerCase(), "验证码错误");
redisTemplate.delete(captchaKey); redissonClient.getBucket(captchaKey).delete();
} }
} }
@ -503,12 +502,8 @@ public class SysUserServiceImpl implements SysUserService {
* @param loginUser 登录用户信息 * @param loginUser 登录用户信息
*/ */
private void saveLoginUserToRedis(String token, LoginUser loginUser) { private void saveLoginUserToRedis(String token, LoginUser loginUser) {
redisTemplate.opsForValue().set( redissonClient.getBucket("loginUser:" + token)
"loginUser:" + token, .set(cn.hutool.json.JSONUtil.toJsonStr(loginUser), 3600, java.util.concurrent.TimeUnit.SECONDS);
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) { private LoginUserDto buildLoginUserDto(LoginUser loginUser) {
LoginUserDto sysUserDto = new LoginUserDto(); LoginUserDto sysUserDto = new LoginUserDto();
BeanUtil.copyProperties(loginUser.getUserInfo(), sysUserDto); sysUserDto.setUserInfo(loginUser.getUserInfo());
sysUserDto.setToken(loginUser.getToken()); sysUserDto.setToken(loginUser.getToken());
sysUserDto.setFavorites(loginUser.getFavorites()); sysUserDto.setFavorites(loginUser.getFavorites());
sysUserDto.setHistory(loginUser.getHistory()); 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, "验证码错误"); Assert.equals(cachedCode, code, "验证码错误");
// 验证成功后删除验证码 // 验证成功后删除验证码
//redissonClient.getBucket("sms_code:" + phone).delete(); redissonClient.getBucket("sms_code:" + phone).delete();
// 查询用户是否存在 // 查询用户是否存在
SysUser sysUser = sysUserMapper.getByTel(phone); SysUser sysUser = sysUserMapper.getByTel(phone);
@ -774,7 +798,7 @@ public class SysUserServiceImpl implements SysUserService {
loginUser.setToken(token); loginUser.setToken(token);
// 将LoginUser对象存储到Redis缓存中使用token作为key // 将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返回对象
LoginUserDto sysUserDto = buildLoginUserDto(loginUser); LoginUserDto sysUserDto = buildLoginUserDto(loginUser);
@ -797,12 +821,48 @@ public class SysUserServiceImpl implements SysUserService {
sysUser.setUserName(phone); sysUser.setUserName(phone);
sysUser.setTel(phone); sysUser.setTel(phone);
// 生成随机密码 // 设置固定salt为666666
String randomPassword = generateRandomPassword(); String salt = "666666";
sysUser.setPwd(randomPassword); sysUser.setSalt(salt);
// 调用insert方法创建用户 // 设置默认密码为000000按照验证逻辑计算存储值
return insert(sysUser); // 验证逻辑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;
} }
/** /**