令牌主动失效机制范例(利用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"));

    }
}
相关推荐
书源丶20 分钟前
三十六、File 类与 IO 流基础——文件操作的「第一步」
java
刀法如飞25 分钟前
Go数组去重的20种实现方式,AI时代解决问题的不同思路
后端·算法·go
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题】【Java基础篇】第30题:JDK动态代理和CGLIB动态代理有什么区别
java·开发语言·后端·面试·代理模式
swipe1 小时前
别再把 AI 聊天做成纯文本:从 agui 这个前后端项目,拆解“可感知工具调用”的流式 AI UI
后端·langchain·llm
GetcharZp1 小时前
GitHub 爆火!纯 Go 编写的文件同步神器 Syncthing,凭什么成为程序员的标配?
后端
hERS EOUS1 小时前
SpringBoot 使用 spring.profiles.active 来区分不同环境配置
spring boot·后端·spring
DFT计算杂谈1 小时前
wannier90 参数详解大全
java·前端·css·html·css3
LucianaiB1 小时前
我用飞书多维表做了一个 AI 活动推荐智能体:每天自动催我别错过截止日期!
后端
marsh02061 小时前
43 openclaw熔断与降级:保障系统在异常情况下的可用性
java·运维·网络·ai·编程·技术
张健11564096482 小时前
临界区和同一线程上锁
java·开发语言·jvm