SpringBoot结合Redis实现幂等
1、相关概念和思路
- 幂等:就是说客户端不管访问多少次,服务器端处理或者返回的结果都是一样
- 思路:我们首先让客户端访问一个获取token的接口,然后让客户端携带token来访问具有幂等(@Idempotent)注解的接口,从而消费掉这个token,这样就可以避免用户重复访问一个接口了,我们把token存在redis中并且设置好过期时间。
2、项目依赖和配置
这里没有用到数据库,只是使用spring-web和redis两个依赖,配置也就是配置redis相关
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
spring.redis.host=localhost
spring.redis.port=6379
3、幂等注解(@Idempotent)
注解起一个标识的作用,我们还需要使用拦截器用来拦截方法,然后通过方法对象获取到方法上面的注解,
我们判断这个注解是否是我们需要的,如果是,则执行相关操作就可以了,不是,直接放行。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
}
4、拦截器
- 实现HandlerInterceptor接口,加上注解@Component交给spring管理
@Component
public class IdempotentInterceptor implements HandlerInterceptor {
// 注解的作用就是起一个标识的作用,使用拦截器可以拦截方法,然后方法可以获取到注解,判断注解上的方法是否是我们想要的注解,
// 是:执行我们需要的逻辑;不是:注解放行或者拦截
@Autowired
private TokenService tokenService;
//预处理回调方法,实现处理器的预处理(如检查登陆),第三个参数为响应的处理器,自定义Controller的方法对象
//返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),
// 不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// com.lkun.controller.IndexController#hello1()
System.out.println(handler);
// 思路:先把错误的情况全部排除
if (!(handler instanceof HandlerMethod)){
return true;
}
Idempotent idempotent = ((HandlerMethod) handler).getMethodAnnotation(Idempotent.class);
if (idempotent == null){
return true;
}
// 获取到了方法上的注解,然后怎么办呢,我们就调用TokenService的验证方法,验证下,看是否符合条件
try {
return tokenService.checkToken(request);
} catch (NotExistException e) {
e.printStackTrace();
throw e;
}
}
}
- 配置拦截器,我们首先把自定义的拦截器IdempotentInterceptor通过注解@Autowired注入进来,然后在重写的addInterceptors方法中通过参数InterceptorRegistry把拦截器注册进去,并设置好拦截路径。
@Configuration
public class IdempotentConfig implements WebMvcConfigurer {
@Autowired
private IdempotentInterceptor interceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor).addPathPatterns("/**");
}
}
5、RedisService封装了redis相关操作
@Component
public class RedisService {
@Autowired
private RedisTemplate redisTemplate;
public boolean setEx(String key, Object value,Long expireTime){
boolean result = false;
try {
redisTemplate.opsForValue().set(key,value,expireTime, TimeUnit.SECONDS);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
public boolean exist(String key){
return redisTemplate.hasKey(key);
}
public boolean remove(String key){
if (exist(key)){
redisTemplate.delete(key);
return true;
}
return false;
}
}
6、TokenService用来产生token并把token放入到redis中
@Component
public class TokenService {
@Autowired
private RedisService redisService;
public String getToken(){
String token = UUID.randomUUID().toString();
redisService.setEx(token,"token",10000L);
return token;
}
public boolean checkToken(HttpServletRequest request) throws NotExistException {
// 请求头中获取token
String token = request.getHeader("token");
if (StringUtils.isEmpty(token)){
// 请求体中获取token
token = request.getParameter("token");
if (StringUtils.isEmpty(token)){
throw new NotExistException("请携带token访问");
}
}
if (!redisService.exist(token)){
throw new NotExistException("重复请求");
}
boolean remove = redisService.remove(token);
if (!remove){
throw new NotExistException("重复请求");
}
return true;
}
}
7、异常处理
这里采用全局异常处理,直接throw,然后使用@RestControllerAdvice注解进行全局异常处理
- 自定义异常
public class NotExistException extends RuntimeException {
public NotExistException(String message) {
super(message);
}
}
- @RestControllerAdvice全局异常处理
// 这是一个增强通知,加上了这个注解就可以处理
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NotExistException.class)
public String HandlerNotExitException(NotExistException e){
return e.getMessage();
}
}
8、Controller层
@RestController
public class IndexController {
@Autowired
private TokenService tokenService;
@GetMapping("getToken")
public String getToken(){
return tokenService.getToken();
}
@PostMapping("hello1")
@Idempotent
public String hello1(){
return "hello1";
}
@PostMapping("hello2")
public String hello2(){
return "hello2";
}
}
9、测试
用户先访问getToken接口,获取到token,然后携带token进行访问hello1接口,再次访问就会返回"重复请求",hello2是没有加上幂等注解,可以随意访问。
本文介绍了如何在SpringBoot应用中利用Redis实现幂等性。通过获取token并将其存储在Redis中,配合幂等注解和拦截器,确保同一操作在多次请求下仍能保持结果一致。同时,文章涵盖了项目依赖、幂等注解、拦截器、Redis服务、Token服务、异常处理和Controller层的实现细节。
7980

被折叠的 条评论
为什么被折叠?



