场景

不管是传统行业还是互联网行业,我们都需要保证大部分操作是幂等性的,简单点说,就是无论用户点击多少次,操作多少遍,产生的结果都是一样的,是唯一的。而今次公司的项目里,又被我遇到了这么一个幂等性的问题,就是用户的余额充值、创建订单和订单支付,不管用户点击多少次,只会有一条充值记录,一条新订单记录,一条订单支付记录。

技术方案

现在使用比较广泛的方案都是基于Redis。

方案:Redis+token

  • 处理流程:数据提交前,前端要向服务端的申请token,token(带有过期时间)放到redis;当数据提交时带上token,如果删除token成功则表明token未过期,然后进行业务逻辑,否则就是token已过期,提示前端请勿重复提交数据。

而我将使用不同的方案。因为此时前后端对接已走一半,不想让前端再增加请求token的接口(毕竟后端能搞定的,还是别麻烦前端同学了)。

方案:自定义注解+分布式锁

  • 处理流程:将需要幂等性的接口加上自定义注解。然后编写一个切面,在around方法里逻辑:尝试获取分布式锁(带过期时间),成功表明没重复提交,否则就是重复提交了。

讲解开始

1、添加Redis依赖:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、自定义注解:

/**
* @author Howinfun
* @desc 自定义注解:分布式锁
* @date 2019/11/12
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheLock { /** key前缀 */
String prefix() default ""; /** 过期秒数,默认为5秒 */
int expire() default 5; /** 超时时间单位,默认为秒 */
TimeUnit timeUnit() default TimeUnit.SECONDS; /** Key的分隔符(默认 :) */
String delimiter() default ":";
}

3、自定义切面:

/**
* @author Howinfun
* @desc 自定义切面
* @date 2019/11/12
*/
@Aspect
@Component
public class LockCheckAspect { /** lua */
private static final String RELEASE_LOCK_LUA_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; @Autowired
private RedisTemplate<String, Object> redisTemplate; // 增强带有CacheLock注解的方法
@Pointcut("@annotation(cn.gdmcmc.system.api.config.aop.CacheLock)")
public void pointCut(){} @Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{ // 可以根据业务获取用户唯一的个人信息,例如手机号码
String phone = .....; MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
CacheLock cacheLock = method.getAnnotation(CacheLock.class);
String prefix = cacheLock.prefix();
if (StringUtils.isBlank(prefix)){
throw new GlobalException("CacheLock prefix can't be null");
}
// 拼接 key
String delimiter = cacheLock.delimiter();
StringBuilder sb = new StringBuilder();
sb.append(prefix).append(delimiter).append(phone);
final String lockKey = sb.toString();
final String UUID = cn.hutool.core.lang.UUID.fastUUID().toString();
try {
// 获取锁
boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey,UUID,cacheLock.expire(),cacheLock.timeUnit());
if (!success){
throw new CustomDeniedException("请勿重复提交");
}
Object result= joinPoint.proceed();
return result;
}finally {
// 最后记得释放锁
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(RELEASE_LOCK_LUA_SCRIPT,Long.class);
Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey),UUID);
} }
}

4、到此,只要为需要保证幂等性的接口上加上@CacheLock注解,就可以了。

@RestController
@RequestMapping(value = "/charge")
@AllArgsConstructor
public class ChargeController { @PostMapping("/startCharge")
@CacheLock(prefix = "recharge")
public Result startCharge(@RequestBody @Validated({ChargeQuery.QRCodeNotBlank.class}) ChargeQuery query){
return this.chargeChargeService.startCharge(query);
}
}

Spring Boot + Redis实战-利用自定义注解+分布式锁实现接口幂等性的更多相关文章

  1. spring boot集成swagger,自定义注解,拦截器,xss过滤,异步调用,guava限流,定时任务案例, 发邮件

    本文介绍spring boot集成swagger,自定义注解,拦截器,xss过滤,异步调用,定时任务案例 集成swagger--对于做前后端分离的项目,后端只需要提供接口访问,swagger提供了接口 ...

  2. asp.net core mvc基于Redis实现分布式锁,C# WebApi接口防止高并发重复请求,分布式锁的接口幂等性实现

    使用背景:在使用app或者pc网页时,可能由于网络原因,api接口可能被前端调用一个接口重复2次的情况,但是请求内容是一样的.这样在同一个短暂的时间内,就会有两个相同请求,而程序只希望处理第一个请求, ...

  3. 利用consul在spring boot中实现最简单的分布式锁

    因为在项目实际过程中所采用的是微服务架构,考虑到承载量基本每个相同业务的服务都是多节点部署,所以针对某些资源的访问就不得不用到用到分布式锁了. 这里列举一个最简单的场景,假如有一个智能售货机,由于机器 ...

  4. Spring Boot系列——AOP配自定义注解的最佳实践

    AOP(Aspect Oriented Programming),即面向切面编程,是Spring框架的大杀器之一. 首先,我声明下,我不是来系统介绍什么是AOP,更不是照本宣科讲解什么是连接点.切面. ...

  5. 【Spring】5、利用自定义注解在SpringMVC中实现自定义权限检查

    转自:http://www.tuicool.com/articles/6z2uIvU 先描述一下应用场景,基于Spring MVC的WEB程序,需要对每个Action进行权限判断,当前用户有权限则允许 ...

  6. 分布式之分布式事务、分布式锁、接口幂等性、分布式session

    一.分布式session session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存在,然后每次发请求过来都带上一个特殊的 jsessionid cookie,就根据这个东西 ...

  7. Spring Boot Redis 实现分布式锁,真香!!

    之前看很多人手写分布式锁,其实 Spring Boot 现在已经做的足够好了,开箱即用,支持主流的 Redis.Zookeeper 中间件,另外还支持 JDBC. 本篇栈长以 Redis 为例(这也是 ...

  8. Spring Boot 多站点利用 Redis 实现 Session 共享

    如何在不同站点(web服务进程)之间共享会话 Session 呢,原理很简单,就是把这个 Session 独立存储在一个地方,所有的站点都从这个地方读取 Session. 通常我们使用 Redis 来 ...

  9. 【spring boot】【redis】spring boot基于redis的LUA脚本 实现分布式锁

    spring boot基于redis的LUA脚本 实现分布式锁[都是基于redis单点下] 一.spring boot 1.5.X 基于redis 的 lua脚本实现分布式锁 1.pom.xml &l ...

随机推荐

  1. Nginx环境搭建与使用

    一.背景 之前测试的项目前后端的"路由"(负责把前端发过来的请求转发到相应的后端服务上)要用Nignx来取代原来的tomcat的http server功能,做这个替换的原因是Nig ...

  2. SpringCloud-创建服务消费者-Feign方式(附代码下载)

    场景 SpringCloud-服务注册与实现-Eureka创建服务注册中心(附源码下载): https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/deta ...

  3. HTML连载56-网易注册界面实战之全部代码

    一.今天完成了网易邮箱注册界面的全部编写,编写一个小小的网页就需要这么多时间来进行设计.测量.排版.编写.测试,才能进行使用,同时编写这个网页复习了几乎前面的所有内容,最后来一个汇总就可以了. < ...

  4. Python—解析HTML页面(HTMLParser)

    HTMLParser类的定义及常用方法 类的定义 HTMLParser主要是用来解析HTML文件(包括HTML中无效的标记). 参数convert_charrefs表示是否将所有的字符引用自动转化为U ...

  5. JUC-6-Callable接口

    创建线程的方式                  不能有返回值,且不能声明抛出异常                                                           ...

  6. Web安全测试学习笔记-DVWA-盲注(使用sqlmap)

    之前的sql注入页面(https://www.cnblogs.com/sallyzhang/p/11843291.html),返回了查询结果和错误信息.而下面的页面,返回信息只有存在和不存在两种情况, ...

  7. SoapUI 之 webService 接口测试 [5]

    一.webservice接口实例说明 学习的话,大家可以自行到网上找 一些免费的webservice接口来练手.本文中选择实例为:中国电视节目预告(电视节目表) WEB 服务. Endpoint : ...

  8. 解决方案:从网站下载Excel,我的Office 2016,打开excel文件,显示空白

    今天在写Excel导出案例demo的时候发现,Excel下载后打开空白,要打开了看到空白后再点击一次打开后才可以显示,效果就如下图所示: 那么我就不能一次打开吗?我找了半天最终在这个博客找到了答案:h ...

  9. WPF之行为

    Behavior的运用扩展了”交互“功能,以下记录示例: 在的项目中添加两个引用:Microsoft.Expression.Interactions.dllSystem.Windows.Interac ...

  10. PlayJava Day025

    JTable 用JTable类可以以表格的形式显示和编辑数据 JTable类的对象并不存储数据,它只是数据的表现 MVC 数据.表现和控制三者分离,各负其责 M = Model(模型) V = Vie ...