1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > 分布式锁防止订单重复提交_防止表单重复提交看这里!!!

分布式锁防止订单重复提交_防止表单重复提交看这里!!!

时间:2022-11-27 10:34:57

相关推荐

分布式锁防止订单重复提交_防止表单重复提交看这里!!!

要解决重复提交这事,先要知道什么是重复提交

假如用户的网速慢,用户点击提交按钮,却因为网速慢,而没有跳转到新的页面,这时的用户会再次点击提交按钮,举个例子:用户点击订单页面,当点击提交按钮的时候,也许因为网速的原因,没有跳转到新的页面,这时的用户会再次点击提交按钮,如果没有经过处理的话,这时用户就会生成两份订单,类似于这种场景都叫重复提交。

好了,为了解决这个问题,可以马上想到通过前端将提交按钮点击后设置倒计时30秒不能再点击就行啊,这些方案是前端的,我们主要说后端方案,看下面的图。防止重复提交后端就是对提交内容加锁即可,判断如果冲突提交就舍弃提交内容,如果没重复就执行后续业务方法。别一提到锁就看不懂,这里的锁你可以理解成唯一值

秉承一贯作风,不讲故事就是干,通过代码理解,先搭建SpringBoot项目

pom坐标

<parent> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-parentartifactId> <version>2.1.6.RELEASEversion> parent> <dependencies> <dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-webartifactId> dependency> <dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-aopartifactId> dependency> <dependency><groupId>com.google.guavagroupId><artifactId>guavaartifactId><version>21.0version> dependency> dependencies>

入口类

@SpringBootApplicationpublic class UserApplication {public static void main(String[] args) {SpringApplication.run(UserApplication.class, args); }}

很普通的自定义注解@LocalLock

/** * 单机防止重复提交注解 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documented@Inheritedpublic @interface LocalLock {String key() default "";}

LockMethodInterceptor拦截器类

package cn.wfb.demo.local;import mon.cache.Cache;import mon.cache.CacheBuilder;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.util.StringUtils;import java.lang.reflect.Method;import java.util.concurrent.TimeUnit;/** * 思路是通过aop的方式拦截用户所有请求,前端需要提交唯一的token值 *后端拦截所有请求将token与Cache缓存内容进行比较,如果token存在就抛异常,不存在直接缓存到Cache中 */@Aspect@Configurationpublic class LockMethodInterceptor {/** * 通过CacheBuilder设置缓存个数,设置缓存过期时间 */ private static final Cache<String, Object> CACHES = CacheBuilder.newBuilder()// 最大缓存 1000 个 .maximumSize(1000)// 设置写缓存后 5 秒钟过期 .expireAfterWrite(5, TimeUnit.SECONDS) .build(); //所有请求并且带有@LocalLock注解的,进行拦截 @Around("execution(public * *(..)) && @annotation(cn.wfb.demo.local.LocalLock)") public Object interceptor(ProceedingJoinPoint pjp) {MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); LocalLock localLock = method.getAnnotation(LocalLock.class); String key = getKey(localLock.key(), pjp.getArgs()); //如果该key已经在缓存中,那么抛异常,不存在则存入缓存中 if (!StringUtils.isEmpty(key)) {if (CACHES.getIfPresent(key) != null) {throw new RuntimeException("请勿重复请求"); }// 如果是第一次请求,就将 key 当前对象压入缓存中CACHES.put(key, key);} try {return pjp.proceed();} catch (Throwable throwable) {throw new RuntimeException("服务器异常");} } /** * * 本例中传入的key是user:arg[0],被替换参数名user:[token参数内容] * 两个参数的话user:arg[0],user:arg[1],user:可以省略只为了标识意义明确 * * @param keyExpress 表达式:例如user:arg[0] * @param args 参数 * @return 生成的key */ private String getKey(String keyExpress, Object[] args) {for (int i = 0; i < args.length; i++) {//将arg[0]替换成参数的内容keyExpress = keyExpress.replace("arg[" + i + "]", args[i].toString());} return keyExpress; }}

Controller

@RestController@RequestMapping("/user")public class UserController {/** * 通过加本地锁防止重复提交 */ @LocalLock(key = "user:arg[0]") @GetMapping("/local") public Map queryLocal(@RequestParam String token) {HashMap map = new HashMap(); map.put("success", token); return map; }}

测试localhost:8080/user/local?token=‘唯一ID值’ 其实整体思路很简单,这个token参数的值应该让前端通过工具类产生唯一值之后提交。提交到后端通过AOP拦截请求,将token内容先判断是否已存在Cache(google的工具类)的缓存,不存在则缓存到Cache中,如果已存在直接抛异常。整体代码很好理解。

那么为什么说分布式系统要和传统的方式区别开,这里的原因大家应该能明白就是缓存位置问题,如果传统项目用本地缓存保存即可,但是分布式就需要Redis作为共享内存,道理是一样的代码都类似,只需要将本例中google的本地Cache缓存改成redis保存即可

采用原生 API 来实现Redis的分布式锁代码如下

//connection.set(前端token的字节,字节数组,通过Expiration设置key时间,使用的Redis的指令是SET_IF_ABSENT)final Boolean success = redisTemplate.execute((RedisCallback<Boolean>) connection -> connection.set(lockKey.getBytes(), new byte[0], Expiration.from(lock.expire(), lock.timeUnit()), RedisStringCommands.SetOption.SET_IF_ABSENT));

其他想问的也可以进群聊

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。