介绍该机制
-
令牌生成
在需要限流的场景中,系统会根据一定的速率生成令牌,存储在 Redis 中。可以设定每秒生成的令牌数量。
-
令牌获取
当用户请求时,系统会从 Redis 中获取令牌。可以使用原子性操作(如 DECR)来确保令牌的正确获取和减少。
-
令牌失效
令牌的主动失效可以通过设定过期时间(TTL)来实现。当生成的令牌在一定时间内未被消费,Redis 会自动删除这些令牌。也可以通过在逻辑上判断令牌的使用情况,主动将过期的令牌从队列中剔除。
-
监控与调整
在实际运行中,可以通过 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"));
}
}