From f84935d0bdf98cbdd4ccf5378c917d40cb7838e6 Mon Sep 17 00:00:00 2001 From: litianxiang Date: Mon, 22 Dec 2025 11:35:38 +0800 Subject: [PATCH] =?UTF-8?q?=E7=99=BB=E5=BD=95token=E5=AD=98=E5=85=A5redis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/filter/AuthenticationFilter.java | 20 +++++++--- .../com/ai/da/common/utils/RedisUtil.java | 39 +++++++++++++++++++ .../da/service/impl/AccountServiceImpl.java | 29 ++++++++++---- 3 files changed, 76 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java b/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java index 726b015a..c684327f 100644 --- a/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java +++ b/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java @@ -6,6 +6,7 @@ import com.ai.da.common.context.UserContext; import com.ai.da.common.security.config.SecurityProperties; import com.ai.da.common.security.jwt.JWTTokenHelper; import com.ai.da.common.utils.LocalCacheUtils; +import com.ai.da.common.utils.RedisUtil; import com.ai.da.common.utils.MultiReadHttpServletRequest; import com.ai.da.common.utils.MultiReadHttpServletResponse; import com.ai.da.common.utils.RequestInfoUtil; @@ -40,6 +41,8 @@ public class AuthenticationFilter extends OncePerRequestFilter { private JWTTokenHelper jwtTokenHelper; @Resource private SecurityProperties properties; + @Resource + private RedisUtil redisUtil; private static final List FILTER_URL = Arrays.asList("/favicon.ico", "/doc.html", "/swagger-ui.html", @@ -132,12 +135,19 @@ public class AuthenticationFilter extends OncePerRequestFilter { UserContext.delete(); //存取用户信息到缓存 UserContext.setUserHolder(principal); - //校验token - String cacheToken = LocalCacheUtils.getTokenCache(String.valueOf(principal.getId())); + // 校验 token:先查本地缓存,再查 Redis,保证服务重启后仍然有效 + String userIdStr = String.valueOf(principal.getId()); + String cacheToken = LocalCacheUtils.getTokenCache(userIdStr); - if(StringUtils.isEmpty(cacheToken)){ -// throw new RuntimeException("TOKEN已过期,请重新登录!"); - throw new TokenMissingOrExpiredException("TOKEN已过期,请重新登录!(local cache empty)"); + if (StringUtils.isEmpty(cacheToken)) { + // 本地缓存为空时,尝试从 Redis 读取 + cacheToken = redisUtil.getLoginToken(principal.getId()); + if (StringUtils.isEmpty(cacheToken)) { +// throw new RuntimeException("TOKEN已过期,请重新登录!"); + throw new TokenMissingOrExpiredException("TOKEN已过期,请重新登录!(cache & redis empty)"); + } + // 将 Redis 中的 token 回填到本地缓存,减少后续 Redis 访问 + LocalCacheUtils.setTokenCache(userIdStr, cacheToken); } if(!cacheToken.equals(jwtToken) ){ // throw new RuntimeException("TOKEN已过期,请重新登录!"); diff --git a/src/main/java/com/ai/da/common/utils/RedisUtil.java b/src/main/java/com/ai/da/common/utils/RedisUtil.java index 70be134a..1264a1e4 100644 --- a/src/main/java/com/ai/da/common/utils/RedisUtil.java +++ b/src/main/java/com/ai/da/common/utils/RedisUtil.java @@ -34,6 +34,11 @@ public class RedisUtil { private RedisTemplate redisTemplate; public final static String FLUX_POLLING_URL = "Flux:"; + /** + * 登录 token 在 Redis 中的前缀: + * 最终 key 结构为 login:token:{userId} + */ + public final static String LOGIN_TOKEN_KEY = "login:token:"; public Boolean hasKey(String key){ return redisTemplate.hasKey(key); @@ -186,6 +191,40 @@ public class RedisUtil { redisTemplate.delete(key); } + /** + * 保存登录 token + * + * @param userId 用户 ID + * @param token token 字符串 + * @param expireMillis 过期时间(毫秒,通常与 JWT 保持一致) + */ + public void setLoginToken(Long userId, String token, long expireMillis) { + if (expireMillis <= 0) { + // 不设置过期时间,直到手动删除(不推荐) + addToString(LOGIN_TOKEN_KEY + userId, token); + return; + } + long expireSeconds = expireMillis / 1000; + if (expireSeconds <= 0) { + expireSeconds = 1; + } + addToString(LOGIN_TOKEN_KEY + userId, token, expireSeconds); + } + + /** + * 获取登录 token + */ + public String getLoginToken(Long userId) { + return getFromString(LOGIN_TOKEN_KEY + userId); + } + + /** + * 删除登录 token + */ + public void deleteLoginToken(Long userId) { + removeFromString(LOGIN_TOKEN_KEY + userId); + } + public final static String PORTFOLIO_LIKE_KEY = "portfolio:like:"; public void likePost(Long portfolioId, Long userId) { diff --git a/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java b/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java index ee9515b1..b22e6267 100644 --- a/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java @@ -132,6 +132,9 @@ public class AccountServiceImpl extends ServiceImpl impl @Resource private RedisUtil redisUtil; + @Resource + private com.ai.da.common.security.config.SecurityProperties securityProperties; + @Resource private UserFollowService userFollowService; @@ -344,7 +347,11 @@ public class AccountServiceImpl extends ServiceImpl impl principal.setLanguage(account.getLanguage()); principal.setCountry(account.getCountry()); String token2 = jwtTokenHelper.createToken(principal); + // 本地 JVM 缓存(适配旧逻辑) LocalCacheUtils.setTokenCache(String.valueOf(account.getId()), token2); + // 同步写入 Redis,重启后仍然可用 + long jwtExpiration = securityProperties.getJwtExpiration(); + redisUtil.setLoginToken(account.getId(), token2, jwtExpiration); return token2; } @@ -600,21 +607,25 @@ public class AccountServiceImpl extends ServiceImpl impl @Override public Boolean logout(AccountLogoutDTO accountLogoutDTO) { - //jwt本身失效比较难做 统一用缓存实现 删除缓存就失效 - String token = LocalCacheUtils.getTokenCache(String.valueOf(accountLogoutDTO.getUserId())); - if (StringUtils.isNotBlank(token)) { - LocalCacheUtils.delTokenCache(String.valueOf(accountLogoutDTO.getUserId())); - } + // jwt 本身失效比较难做,统一用缓存实现:删除缓存即失效 + String userIdStr = String.valueOf(accountLogoutDTO.getUserId()); + LocalCacheUtils.delTokenCache(userIdStr); + // 同时删除 Redis 中的 token,防止服务重启后仍然有效 + redisUtil.deleteLoginToken(accountLogoutDTO.getUserId()); return Boolean.TRUE; } @Override public Boolean isLogin(AccountLogoutDTO accountLogoutDTO) { - String token = LocalCacheUtils.getTokenCache(String.valueOf(accountLogoutDTO.getUserId())); + String userIdStr = String.valueOf(accountLogoutDTO.getUserId()); + // 先查本地缓存 + String token = LocalCacheUtils.getTokenCache(userIdStr); if (StringUtils.isNotBlank(token)) { return Boolean.TRUE; } - return Boolean.FALSE; + // 本地没有时,再查 Redis,保证服务重启后也能判断登录状态 + String redisToken = redisUtil.getLoginToken(accountLogoutDTO.getUserId()); + return StringUtils.isNotBlank(redisToken); } @Override @@ -812,6 +823,8 @@ public class AccountServiceImpl extends ServiceImpl impl if (StringUtils.isNotBlank(token)) { LocalCacheUtils.delTokenCache(String.valueOf(accountDelete.getId())); } + // 删除 Redis 中对应的登录 token + redisUtil.deleteLoginToken(accountDelete.getId()); if (!userName.equals(userToBeUpdate.getUserName())) { // accountMapper.deleteById(accountDelete); log.info("排查用户被删除原因:deleteNoLoginRequired,true, 删除用户(改为降为游客)"); @@ -1065,6 +1078,8 @@ public class AccountServiceImpl extends ServiceImpl impl if (StringUtils.isNotBlank(token)) { LocalCacheUtils.delTokenCache(String.valueOf(account.getId())); } + // 删除 Redis 中对应的登录 token + redisUtil.deleteLoginToken(account.getId()); // accountMapper.deleteById(account.getId()); log.info("排查用户被删除原因:deleteNoLoginRequiredNew,删除用户(改为降为游客)"); accountMapper.toVisitor(account.getId());