令牌主动失效机制范例(利用redis)注释分析

介绍该机制

  1. 令牌生成

    在需要限流的场景中,系统会根据一定的速率生成令牌,存储在 Redis 中。可以设定每秒生成的令牌数量。

  2. 令牌获取

    当用户请求时,系统会从 Redis 中获取令牌。可以使用原子性操作(如 DECR)来确保令牌的正确获取和减少。

  3. 令牌失效

    令牌的主动失效可以通过设定过期时间(TTL)来实现。当生成的令牌在一定时间内未被消费,Redis 会自动删除这些令牌。也可以通过在逻辑上判断令牌的使用情况,主动将过期的令牌从队列中剔除。

  4. 监控与调整

    在实际运行中,可以通过 Redis 的发布/订阅机制或键空间通知(Keyspace Notifications)来监控令牌的状态,动态调整生成速率或失效策略。

redis令牌失效机制的代码实现案例

redis配置

pom、yml(redis配置)

redis的配置文件

复制代码
package com.itheima.config;

import org.springframework.cache.annotation.CachingConfigurerSupport; // 引入缓存支持类
import org.springframework.context.annotation.Bean; // 引入Bean注解
import org.springframework.context.annotation.Configuration; // 引入配置注解
import org.springframework.data.redis.connection.RedisConnectionFactory; // 引入Redis连接工厂
import org.springframework.data.redis.core.RedisTemplate; // 引入通用Redis模板
import org.springframework.data.redis.core.StringRedisTemplate; // 引入字符串Redis模板

@Configuration // 声明这是一个配置类
public class RedisConfig extends CachingConfigurerSupport { // 继承缓存支持类

    // 定义一个StringRedisTemplate Bean,用于操作String类型的键值对
    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 使用提供的连接工厂创建StringRedisTemplate实例
        return new StringRedisTemplate(redisConnectionFactory);
    }

    // 定义一个通用的RedisTemplate Bean,用于操作任意类型的对象
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 创建RedisTemplate实例
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 设置连接工厂,以便连接到Redis服务器
        template.setConnectionFactory(redisConnectionFactory);
        // 返回配置好的RedisTemplate实例
        return template;
    }
}

用户控制器(登录)

usercontroller

复制代码
    @PostMapping("/login")
    public Result<String> login(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password) {
        //根据用户名查询用户
        User loginUser = userService.findByUserName(username);
        //判断该用户是否存在
        if (loginUser == null) {
            return Result.error("用户名错误");
        }

        //判断密码是否正确  loginUser对象中的password是密文
        if (Md5Util.getMD5String(password).equals(loginUser.getPassword())) {
            //登录成功
            Map<String, Object> claims = new HashMap<>();  // 创建一个存储用户信息的映射
            claims.put("id", loginUser.getId());           // 将用户 ID 存储在 claims 中
            claims.put("username", loginUser.getUsername()); // 将用户名存储在 claims 中
            String token = JwtUtil.genToken(claims);       // 生成 JWT,包含用户的 ID 和用户名
            //把token存储到redis中
            ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
            operations.set(token,token,1, TimeUnit.HOURS);
            return Result.success(token);
        }
        return Result.error("密码错误");
    }

使用完需要删除操作(usercontroller)

复制代码
        //删除redis中对应的token
        ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
        operations.getOperations().delete(token);
        return Result.success();

拦截器里redis的验证

复制代码
logininterceptor
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Autowired
    private StringRedisTemplate stringRedisTemplate; // 注入 Redis 模板,用于操作 Redis 数据库
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 令牌验证
        String token = request.getHeader("Authorization"); // 从请求头中获取名为 "Authorization" 的令牌
        // 验证 token
        try {
            // 从 Redis 中获取与请求中相同的 token
            ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
            String redisToken = operations.get(token); // 查询 Redis 数据库
            if (redisToken == null) {
                // 如果 Redis 中没有该 token,说明 token 已经失效
                throw new RuntimeException("Token has expired"); // 抛出异常以指示 token 失效
            }
            // 解析 token,获取业务数据(例如用户信息)
            Map<String, Object> claims = JwtUtil.parseToken(token);
            // 将业务数据存储到 ThreadLocal 中,以便后续处理使用
            ThreadLocalUtil.set(claims);
            // 放行请求,继续处理
            return true;
        } catch (Exception e) {
            // 如果发生异常(例如 token 失效),设置 HTTP 响应状态码为 401(未授权)
            response.setStatus(401);
            // 拦截请求,不放行
            return false;
        }
    }
}

测试

redisTest

复制代码
package com.itheima;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import java.util.concurrent.TimeUnit;

@SpringBootTest     //如果在测试类上添加了这个注解,那么将来单元测试方法执行之前,会先初始化Spring容器
public class RedisTest {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    public void testSet(){
        //往redis中存储一个键值对  StringRedisTemplate
        ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();

        operations.set("username","zhangsan");
        operations.set("id","1",15, TimeUnit.SECONDS);
    }

    @Test
    public void testGet(){
        //从redis中获取一个键值对
        ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
        System.out.println(operations.get("id"));

    }
}
相关推荐
金銀銅鐵11 分钟前
带您看懂字节码指令(二):空循环里有什么
java·后端·ai编程
皮皮林5511 小时前
比 Xshell 还好用的 SSH 客户端神器,MobaXterm 太爱了!
后端
吴名氏.2 小时前
Java转AI Agent教程(第十三周)混合检索与重排
java·数据库·人工智能·ai·ai agent
我不会起名字3223 小时前
会话缓存失效排查:区分 Redis 失效与节点广播
redis·后端·go·即时通讯·im·故障排查
.痞老板.3 小时前
java ee--多线程(进阶)
java·java-ee
深入云栈4 小时前
Netty 4.2.x 源码深度解析 (二十三):ByteBuf 核心设计 —— 索引、类型与零拷贝
java·后端·架构
JackJiang4 小时前
IM踩坑:聊天消息ID怎么设计?一个UUID搞不定,我用双ID解决
后端·架构
Jackzhang4 小时前
[运维排障实录] 第一次遇到 502 别急着重启:按「一条线」把问题查清楚
spring boot
开心猴爷4 小时前
iOS 上架 App 流程
后端·ios
code_Bo5 小时前
不只命令行:我把跨 Agent 会话同步做成了桌面应用
前端·javascript·后端