From b9ed1cd326e335b928c07ef135f097dac7a0d8ff Mon Sep 17 00:00:00 2001 From: Azure <983547216@qq.com> Date: Tue, 16 Jun 2026 18:20:11 +0800 Subject: [PATCH] feat: token --- .../service/impl/RefreshTokenServiceImpl.java | 84 ++++++++++++++----- .../service/impl/UserServiceImpl.java | 1 + 2 files changed, 63 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/webgame/webgamebackend/service/impl/RefreshTokenServiceImpl.java b/src/main/java/com/webgame/webgamebackend/service/impl/RefreshTokenServiceImpl.java index 7d1e641..5e333c8 100644 --- a/src/main/java/com/webgame/webgamebackend/service/impl/RefreshTokenServiceImpl.java +++ b/src/main/java/com/webgame/webgamebackend/service/impl/RefreshTokenServiceImpl.java @@ -2,14 +2,14 @@ package com.webgame.webgamebackend.service.impl; import com.webgame.webgamebackend.common.exception.BusinessException; import com.webgame.webgamebackend.common.exception.ErrorCode; -import com.webgame.webgamebackend.common.utils.RedisCacheUtil; import com.webgame.webgamebackend.service.RefreshTokenService; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.security.SecureRandom; import java.util.HexFormat; +import java.util.concurrent.TimeUnit; /** * 刷新令牌服务实现,基于 Redis 存储 @@ -21,43 +21,44 @@ import java.util.HexFormat; * *

TTL: 30 天,由 Redis 自动过期

*

一个用户同一时间只保留一个有效的 refresh token,新登录会使旧 token 失效

+ *

直接使用 StringRedisTemplate 存储纯字符串,避免 JSON 序列化导致的引号问题

*/ @Slf4j @Service -@RequiredArgsConstructor public class RefreshTokenServiceImpl implements RefreshTokenService { /** Redis key 前缀 */ - private static final String PREFIX = "refresh"; + private static final String KEY_PREFIX = "xzg:refresh:"; /** 用户→token 反向映射 key 前缀 */ - private static final String USER_PREFIX = "refresh:user"; + private static final String USER_KEY_PREFIX = "xzg:refresh:user:"; - /** 刷新令牌有效期:30 天(毫秒) */ - private static final long EXPIRE_MS = 2_592_000_000L; + /** 刷新令牌有效期:30 天 */ + private static final long EXPIRE_DAYS = 30; /** 安全随机数生成器 */ private static final SecureRandom RANDOM = new SecureRandom(); - private final RedisCacheUtil redisCacheUtil; + private final StringRedisTemplate stringRedisTemplate; + + public RefreshTokenServiceImpl(StringRedisTemplate stringRedisTemplate) { + this.stringRedisTemplate = stringRedisTemplate; + } @Override public String create(String userId) { // 先清除该用户之前的 refresh token,确保一个用户只有一个有效 token - String oldToken = redisCacheUtil.getCache(USER_PREFIX, userId, String.class, EXPIRE_MS); + String oldToken = get(USER_KEY_PREFIX + userId); if (oldToken != null) { - redisCacheUtil.removeCache(PREFIX, oldToken); + delete(KEY_PREFIX + oldToken); log.debug("已清除旧的刷新令牌: userId={}", userId); } // 创建新 token String token = generateToken(); - boolean ok = redisCacheUtil.cacheValue(PREFIX, token, userId, EXPIRE_MS); - if (!ok) { - log.error("刷新令牌写入 Redis 失败: userId={}", userId); - throw new BusinessException(ErrorCode.INTERNAL_ERROR, "刷新令牌生成失败"); - } + log.debug("刷新令牌已生成: token={}", token); + set(KEY_PREFIX + token, userId); // 维护反向映射 - redisCacheUtil.cacheValue(USER_PREFIX, userId, token, EXPIRE_MS); + set(USER_KEY_PREFIX + userId, token); log.debug("刷新令牌已创建: userId={}", userId); return token; } @@ -67,7 +68,7 @@ public class RefreshTokenServiceImpl implements RefreshTokenService { if (token == null || token.isBlank()) { throw new BusinessException(ErrorCode.REFRESH_TOKEN_MISSING); } - String userId = redisCacheUtil.getCache(PREFIX, token, String.class, EXPIRE_MS); + String userId = get(KEY_PREFIX + token); if (userId == null) { throw new BusinessException(ErrorCode.REFRESH_TOKEN_INVALID); } @@ -77,8 +78,8 @@ public class RefreshTokenServiceImpl implements RefreshTokenService { @Override public String rotate(String oldToken) { String userId = validate(oldToken); - redisCacheUtil.removeCache(PREFIX, oldToken); - redisCacheUtil.removeCache(USER_PREFIX, userId); + delete(KEY_PREFIX + oldToken); + delete(USER_KEY_PREFIX + userId); log.debug("旧刷新令牌已删除: userId={}", userId); return create(userId); } @@ -86,15 +87,54 @@ public class RefreshTokenServiceImpl implements RefreshTokenService { @Override public void revoke(String token) { if (token != null && !token.isBlank()) { - String userId = redisCacheUtil.getCache(PREFIX, token, String.class, EXPIRE_MS); + String userId = get(KEY_PREFIX + token); if (userId != null) { - redisCacheUtil.removeCache(USER_PREFIX, userId); + delete(USER_KEY_PREFIX + userId); } - redisCacheUtil.removeCache(PREFIX, token); + delete(KEY_PREFIX + token); log.debug("刷新令牌已撤销"); } } + /** + * 写入字符串到 Redis(带过期时间) + */ + private void set(String key, String value) { + try { + stringRedisTemplate.opsForValue().set(key, value, EXPIRE_DAYS, TimeUnit.DAYS); + } catch (Exception e) { + log.error("刷新令牌写入 Redis 失败: key={}", key, e); + throw new BusinessException(ErrorCode.INTERNAL_ERROR, "刷新令牌操作失败"); + } + } + + /** + * 从 Redis 读取字符串(读取时续期) + */ + private String get(String key) { + try { + String value = stringRedisTemplate.opsForValue().get(key); + if (value != null) { + stringRedisTemplate.expire(key, EXPIRE_DAYS, TimeUnit.DAYS); + } + return value; + } catch (Exception e) { + log.error("查询缓存失败: key={}", key, e); + return null; + } + } + + /** + * 从 Redis 删除 key + */ + private void delete(String key) { + try { + stringRedisTemplate.delete(key); + } catch (Exception e) { + log.error("删除缓存失败: key={}", key, e); + } + } + /** * 生成 64 位十六进制随机字符串作为刷新令牌 */ diff --git a/src/main/java/com/webgame/webgamebackend/service/impl/UserServiceImpl.java b/src/main/java/com/webgame/webgamebackend/service/impl/UserServiceImpl.java index 3db740e..56ff7fe 100644 --- a/src/main/java/com/webgame/webgamebackend/service/impl/UserServiceImpl.java +++ b/src/main/java/com/webgame/webgamebackend/service/impl/UserServiceImpl.java @@ -24,6 +24,7 @@ public class UserServiceImpl implements UserService { @Override public UserInfoResponse getCurrentUserInfo() { String accountId = StpUtil.getLoginIdAsString(); + log.info("[用户服务] 获取当前登录用户信息, accountId: {}", accountId); UserEntity user = userRepository.findByAccountId(accountId) .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); return UserInfoResponse.from(user);