温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

SpringBoot + Redis怎么解决重复提交问题

发布时间:2021-12-14 10:07:03 来源:亿速云 阅读:197 作者:小新 栏目:开发技术

这篇文章主要为大家展示了“SpringBoot + Redis怎么解决重复提交问题”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“SpringBoot + Redis怎么解决重复提交问题”这篇文章吧。

在开发中,一个对外暴露的接口可能会面临瞬间的大量重复请求,如果想过滤掉重复请求造成对业务的伤害,那就需要实现幂等

幂等:

任意多次执行所产生的影响均与一次执行的影响相同。最终的含义就是 对数据库的影响只能是一次性的,不能重复处理。

解决方案:

  • 数据库建立唯一性索引,可以保证最终插入数据库的只有一条数据

  • token机制,每次接口请求前先获取一个token,然后再下次请求的时候在请求的header体中加上这个token,后台进行验证,如果验证通过删除token,下次请求再次判断token(本次案例使用)

  • 悲观锁或者乐观锁,悲观锁可以保证每次for update的时候其他sql无法update数据(在数据库引擎是innodb的时候,select的条件必须是唯一索引,防止锁全表)

  • 先查询后判断,首先通过查询数据库是否存在数据,如果存在证明已经请求过了,直接拒绝该请求,如果没有存在,就证明是第一次进来,直接放行

SpringBoot + Redis怎么解决重复提交问题

一、搭建Redis服务

package com.ckw.idempotence.service; /**  * @author ckw  * @version 1.0  * @date 2020/6/11 9:42  * @description: redis工具类  */ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.stereotype.Component; import java.io.Serializable; import java.util.concurrent.TimeUnit; /**  * redis工具类  */ @Component public class RedisService {     private RedisTemplate redisTemplate;     @Autowired(required = false)     public void setRedisTemplate(RedisTemplate redisTemplate) {         RedisSerializer stringSerializer = new StringRedisSerializer();         redisTemplate.setKeySerializer(stringSerializer);         redisTemplate.setValueSerializer(stringSerializer);         redisTemplate.setHashKeySerializer(stringSerializer);         redisTemplate.setHashValueSerializer(stringSerializer);         this.redisTemplate = redisTemplate;     }     /**      * 写入缓存      *      * @param key      * @param value      * @return      */     public boolean set(final String key, Object value) {         boolean result = false;         try {             ValueOperations operations = redisTemplate.opsForValue();             operations.set(key, value);             result = true;         } catch (Exception e) {             e.printStackTrace();         }         return result;     }     /**      * 写入缓存设置时效时间      *      * @param key      * @param value      * @return      */     public boolean setEx(final String key, Object value, Long expireTime) {         boolean result = false;         try {             ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();             operations.set(key, value);             redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);             result = true;         } catch (Exception e) {             e.printStackTrace();         }         return result;     }     /**      * 判断缓存中是否有对应的value      *      * @param key      * @return      */     public boolean exists(final String key) {         return redisTemplate.hasKey(key);     }     /**      * 读取缓存      * @param key      * @return      */     public Object get(final String key) {         Object o = null;         ValueOperations valueOperations = redisTemplate.opsForValue();         return valueOperations.get(key);     }     /**      * 删除对应的value      * @param key      */     public Boolean remove(final String key) {         if(exists(key)){             return redisTemplate.delete(key);         }         return false;     } }

二、自定义注解

作用:拦截器拦截请求时,判断调用的地址对应的Controller方法是否有自定义注解,有的话说明该接口方法进行 幂等

package com.ckw.idempotence.annotion; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /**  * @author ckw  * @version 1.0  * @date 2020/6/11 9:55  * @description:  */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface AutoIdempotent { }

三、Token创建和校验

package com.ckw.idempotence.service; import com.ckw.idempotence.exectionhandler.BaseException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest; import java.util.UUID; /**  * @author ckw  * @version 1.0  * @date 2020/6/11 9:56  * @description: token服务  */ @Service public class TokenService {     @Autowired RedisService redisService;	//创建token     public String createToken() {     	//使用UUID代表token         UUID uuid = UUID.randomUUID();         String token = uuid.toString();         //存入redis         boolean b = redisService.setEx(token, token, 10000L);         return token;     }	//检验请求头或者请求参数中是否有token     public boolean checkToken(HttpServletRequest request) {         String token = request.getHeader("token");         //如果header中是空的         if(StringUtils.isEmpty(token)){             //从request中拿             token = request.getParameter("token");             if(StringUtils.isEmpty(token)){                throw new BaseException(20001, "缺少参数token");             }         }         //如果从header中拿到的token不正确         if(!redisService.exists(token)){             throw new BaseException(20001, "不能重复提交-------token不正确、空");         }         //token正确 移除token         if(!redisService.remove(token)){             throw new BaseException(20001, "token移除失败");         }         return true;     } }

这里用到了自定义异常和自定义响应体如下

自定义异常:

package com.ckw.idempotence.exectionhandler; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /**  * @author ckw  * @version 1.0  * @date 2020/5/16 20:58  * @description: 自定义异常类  */ @Data @AllArgsConstructor @NoArgsConstructor public class BaseException extends RuntimeException {     private Integer code;     private String msg; }

设置统一异常处理:

package com.ckw.idempotence.exectionhandler; import com.ckw.idempotence.utils.R; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /**  * @author ckw  * @version 1.0  * @date 2020/5/16 20:45  * @description: 统一异常处理器  */ @ControllerAdvice public class GlobalExceptionHandler {     @ExceptionHandler(Exception.class)     @ResponseBody     public R error(Exception e){         e.printStackTrace();         return R.error();     }     @ExceptionHandler(BaseException.class)     @ResponseBody     public R error(BaseException e){         e.printStackTrace();         return R.error().message(e.getMsg()).code(e.getCode());     } }

自定义响应体:

package com.ckw.idempotence.utils; import lombok.Data; import java.util.HashMap; import java.util.Map; /**  * @author ckw  * @version 1.0  * @date 2020/5/16 18:35  * @description: 返回结果  */ @Data public class R {     private Boolean success;     private Integer code;     private String message;     private Map<String, Object> data = new HashMap<String, Object>();     private R() {     }     //封装返回成功     public static R ok(){         R r = new R();         r.setSuccess(true);         r.setCode(ResultCode.SUCCESS);         r.setMessage("成功");         return r;     }     //封装返回失败     public static R error(){         R r = new R();         r.setSuccess(false);         r.setCode(ResultCode.ERROR);         r.setMessage("失败");         return r;     }     public R success(Boolean success){         this.setSuccess(success);         return this;     }     public R message(String message){         this.setMessage(message);         return this;     }     public R code(Integer code){         this.setCode(code);         return this;     }     public R data(String key, Object value){         this.data.put(key, value);         return this;     }     public R data(Map<String, Object> map){         this.setData(map);         return this;     } }

自定义响应码:

package com.ckw.idempotence.utils; import lombok.Data; import java.util.HashMap; import java.util.Map; /**  * @author ckw  * @version 1.0  * @date 2020/5/16 18:35  * @description: 返回结果  */ @Data public class R {     private Boolean success;     private Integer code;     private String message;     private Map<String, Object> data = new HashMap<String, Object>();     private R() {     }     //封装返回成功     public static R ok(){         R r = new R();         r.setSuccess(true);         r.setCode(ResultCode.SUCCESS);         r.setMessage("成功");         return r;     }     //封装返回失败     public static R error(){         R r = new R();         r.setSuccess(false);         r.setCode(ResultCode.ERROR);         r.setMessage("失败");         return r;     }     public R success(Boolean success){         this.setSuccess(success);         return this;     }     public R message(String message){         this.setMessage(message);         return this;     }     public R code(Integer code){         this.setCode(code);         return this;     }     public R data(String key, Object value){         this.data.put(key, value);         return this;     }     public R data(Map<String, Object> map){         this.setData(map);         return this;     } }

四、拦截器配置

1、拦截器配置类

package com.ckw.idempotence.config; import com.ckw.idempotence.interceptor.AutoIdempotentInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /**  * @author ckw  * @version 1.0  * @date 2020/6/11 10:07  * @description: 拦截器配置类  */ @Configuration public class WebConfiguration implements WebMvcConfigurer {     @Autowired     private AutoIdempotentInterceptor autoIdempotentInterceptor;     @Override     public void addInterceptors(InterceptorRegistry registry) {         registry.addInterceptor(autoIdempotentInterceptor);     } }

2、拦截器类

package com.ckw.idempotence.interceptor; import com.ckw.idempotence.annotion.AutoIdempotent; import com.ckw.idempotence.service.TokenService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; /**  * @author ckw  * @version 1.0  * @date 2020/6/11 10:11  * @description: 拦截重复提交数据  */ @Component public class AutoIdempotentInterceptor implements HandlerInterceptor {     @Autowired     private TokenService tokenService;     @Override     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {         if(!(handler instanceof HandlerMethod))             return true;         HandlerMethod handlerMethod = (HandlerMethod) handler;         Method method = handlerMethod.getMethod();         //拿到方法上面的自定义注解         AutoIdempotent annotation = method.getAnnotation(AutoIdempotent.class);                  //如果不等于null说明该方法要进行幂等         if(null != annotation){             return tokenService.checkToken(request);         }         return true;     } }

五、正常Sevice类

package com.ckw.idempotence.service; import org.springframework.stereotype.Service; /**  * @author ckw  * @version 1.0  * @date 2020/6/11 10:04  * @description:  */ @Service public class TestService {     public String testMethod(){         return "正常业务逻辑";     } }

六、Controller类

package com.ckw.idempotence.controller; import com.ckw.idempotence.annotion.AutoIdempotent; import com.ckw.idempotence.service.TestService; import com.ckw.idempotence.service.TokenService; import com.ckw.idempotence.utils.R; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; /**  * @author ckw  * @version 1.0  * @date 2020/6/11 9:58  * @description:  */ @RestController @CrossOrigin @RequestMapping("/Idempotence") public class TestController {     @Autowired     private TokenService tokenService;     @Autowired     private TestService testService;     @GetMapping("/getToken")     public R getToken(){         String token = tokenService.createToken();         return R.ok().data("token",token);     }     //相当于添加数据接口(测试时 连续点击添加数据按钮  看结果是否是添加一条数据还是多条数据)     @AutoIdempotent     @PostMapping("/test/addData")     public R addData(){         String s = testService.testMethod();         return R.ok().data("data",s);     } }

七、测试

SpringBoot + Redis怎么解决重复提交问题

第一次点击:

SpringBoot + Redis怎么解决重复提交问题

第二次点击:

SpringBoot + Redis怎么解决重复提交问题

以上是“SpringBoot + Redis怎么解决重复提交问题”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注亿速云行业资讯频道!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI