diff --git a/src/main/java/com/kexue/skills/controller/CaptchaController.java b/src/main/java/com/kexue/skills/controller/CaptchaController.java index 9784a10..60cc9cf 100644 --- a/src/main/java/com/kexue/skills/controller/CaptchaController.java +++ b/src/main/java/com/kexue/skills/controller/CaptchaController.java @@ -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; @@ -26,15 +26,14 @@ import java.util.concurrent.TimeUnit; @Tag(name = "验证码 Api") public class CaptchaController { - @Resource - private RedisTemplate redisTemplate; - @Resource private CaptchaConfig captchaConfig; + @Resource + 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("验证码错误"); diff --git a/src/main/java/com/kexue/skills/controller/SysUserController.java b/src/main/java/com/kexue/skills/controller/SysUserController.java index da92cda..73863bc 100644 --- a/src/main/java/com/kexue/skills/controller/SysUserController.java +++ b/src/main/java/com/kexue/skills/controller/SysUserController.java @@ -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 redisTemplate; + private RedissonClient redissonClient; /** * 分页查询 * @@ -164,6 +165,39 @@ public class SysUserController { boolean result = sysUserService.resetPwd(resetPwdDto.getUserId(), resetPwdDto.getNewPassword(), username); return CommonResult.success(result); } + + /** + * 重置密码(管理员专用,通过用户名或手机号) + * + * @param resetPasswordDto 重置密码请求参数 + * @param request HTTP请求 + * @return 重置结果 + */ + @PostMapping("/resetPasswordByUsernameOrPhone") + @Operation(summary = "重置密码(管理员专用,通过用户名或手机号)", description = "重置密码(管理员专用,通过用户名或手机号,无需旧密码)") + @RequireAuth + public CommonResult 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 currentUser(HttpServletRequest request) { + public CommonResult 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); } } diff --git a/src/main/java/com/kexue/skills/entity/request/AdminResetPasswordDto.java b/src/main/java/com/kexue/skills/entity/request/AdminResetPasswordDto.java new file mode 100644 index 0000000..fa9a197 --- /dev/null +++ b/src/main/java/com/kexue/skills/entity/request/AdminResetPasswordDto.java @@ -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; +} diff --git a/src/main/java/com/kexue/skills/entity/request/LoginUserDto.java b/src/main/java/com/kexue/skills/entity/request/LoginUserDto.java index 6f9d86b..29b66ca 100644 --- a/src/main/java/com/kexue/skills/entity/request/LoginUserDto.java +++ b/src/main/java/com/kexue/skills/entity/request/LoginUserDto.java @@ -13,9 +13,14 @@ import java.util.List; */ @Data @ApiModel(value = "登录用户信息") -public class LoginUserDto extends SysUser { +public class LoginUserDto { String token; + /** + * 用户信息 + */ + SysUser userInfo; + /** * 最近点赞的20条记录 */ diff --git a/src/main/java/com/kexue/skills/service/SysUserService.java b/src/main/java/com/kexue/skills/service/SysUserService.java index 1b868aa..090c2d4 100644 --- a/src/main/java/com/kexue/skills/service/SysUserService.java +++ b/src/main/java/com/kexue/skills/service/SysUserService.java @@ -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); + /** * 发送手机验证码 * diff --git a/src/main/java/com/kexue/skills/service/impl/SysUserServiceImpl.java b/src/main/java/com/kexue/skills/service/impl/SysUserServiceImpl.java index 5f9300e..3ee9e0b 100644 --- a/src/main/java/com/kexue/skills/service/impl/SysUserServiceImpl.java +++ b/src/main/java/com/kexue/skills/service/impl/SysUserServiceImpl.java @@ -45,8 +45,7 @@ public class SysUserServiceImpl implements SysUserService { @Resource private SysUserMapper sysUserMapper; - @Resource - private org.springframework.data.redis.core.RedisTemplate 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; } /**