Spring Boot + Redis实战-利用自定义注解+分布式锁实现接口幂等性
场景
不管是传统行业还是互联网行业,我们都需要保证大部分操作是幂等性的,简单点说,就是无论用户点击多少次,操作多少遍,产生的结果都是一样的,是唯一的。而今次公司的项目里,又被我遇到了这么一个幂等性的问题,就是用户的余额充值、创建订单和订单支付,不管用户点击多少次,只会有一条充值记录,一条新订单记录,一条订单支付记录。
技术方案
现在使用比较广泛的方案都是基于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实战-利用自定义注解+分布式锁实现接口幂等性的更多相关文章
- spring boot集成swagger,自定义注解,拦截器,xss过滤,异步调用,guava限流,定时任务案例, 发邮件
本文介绍spring boot集成swagger,自定义注解,拦截器,xss过滤,异步调用,定时任务案例 集成swagger--对于做前后端分离的项目,后端只需要提供接口访问,swagger提供了接口 ...
- asp.net core mvc基于Redis实现分布式锁,C# WebApi接口防止高并发重复请求,分布式锁的接口幂等性实现
使用背景:在使用app或者pc网页时,可能由于网络原因,api接口可能被前端调用一个接口重复2次的情况,但是请求内容是一样的.这样在同一个短暂的时间内,就会有两个相同请求,而程序只希望处理第一个请求, ...
- 利用consul在spring boot中实现最简单的分布式锁
因为在项目实际过程中所采用的是微服务架构,考虑到承载量基本每个相同业务的服务都是多节点部署,所以针对某些资源的访问就不得不用到用到分布式锁了. 这里列举一个最简单的场景,假如有一个智能售货机,由于机器 ...
- Spring Boot系列——AOP配自定义注解的最佳实践
AOP(Aspect Oriented Programming),即面向切面编程,是Spring框架的大杀器之一. 首先,我声明下,我不是来系统介绍什么是AOP,更不是照本宣科讲解什么是连接点.切面. ...
- 【Spring】5、利用自定义注解在SpringMVC中实现自定义权限检查
转自:http://www.tuicool.com/articles/6z2uIvU 先描述一下应用场景,基于Spring MVC的WEB程序,需要对每个Action进行权限判断,当前用户有权限则允许 ...
- 分布式之分布式事务、分布式锁、接口幂等性、分布式session
一.分布式session session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存在,然后每次发请求过来都带上一个特殊的 jsessionid cookie,就根据这个东西 ...
- Spring Boot Redis 实现分布式锁,真香!!
之前看很多人手写分布式锁,其实 Spring Boot 现在已经做的足够好了,开箱即用,支持主流的 Redis.Zookeeper 中间件,另外还支持 JDBC. 本篇栈长以 Redis 为例(这也是 ...
- Spring Boot 多站点利用 Redis 实现 Session 共享
如何在不同站点(web服务进程)之间共享会话 Session 呢,原理很简单,就是把这个 Session 独立存储在一个地方,所有的站点都从这个地方读取 Session. 通常我们使用 Redis 来 ...
- 【spring boot】【redis】spring boot基于redis的LUA脚本 实现分布式锁
spring boot基于redis的LUA脚本 实现分布式锁[都是基于redis单点下] 一.spring boot 1.5.X 基于redis 的 lua脚本实现分布式锁 1.pom.xml &l ...
随机推荐
- SpringMVC的自动配置解析
https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/reference/htmlsingle/#boot-features-developin ...
- 全文检索--Lucene & ElasticSearch
全文检索--Lucene 2.1 全文检索和以前高级查询的比较 1.高级查询 缺点:1.like让数据库索引失效 2.每次查询都是查询数据库 ,如果访问的人比较多,压力也是比较大 2.全文检索框架:A ...
- Python定做一个计算器,小而美哒~
使用qt designer ,按装anaconda后,在如下路径找到: conda3.05\Library\bin designer.exe文件,双击启动: 创建窗体,命名为XiaoDing,整个 ...
- weed3-1.hello world
Weed3 一个微型ORM框架(只有0.1Mb哦) 源码:https://github.com/noear/weed3 源码:https://gitee.com/noear/weed3 05年的时候开 ...
- WebShell代码分析溯源(九)
WebShell代码分析溯源(九) 一.一句话变形马样本 <?php $e = $_REQUEST['e'];$arr = array($_POST['pass'] => '|.*|e', ...
- C#面向对象-多态
面向对象的三大特性(封装,继承,多态)大多少人都应该知道.前两个的话比较容易理解.本文主要说一下面向对象中的多态. 什么是多态?不同的对象对同一操作,产生不同的执行结果.这就是多态.那么多态又包含了: ...
- windows 本地链接 VMware虚拟机 redis服务
使用本地Windows链接 VMware虚拟机 redis服务 我用的虚拟机系统是:windows Server 2012 先把Redis服务器拷贝到服务器并解压,目录如下 这里仅仅作为演示,所以就不 ...
- Pull Request 工作流——更高效的管理代码
目录 Pull Request 工作流--更高效的管理代码 1.问题 2.解决方案 3.Git分支流管理代码具体实施 3.1本地分支操作管理 3.1.1查看分支 3.1.2创建分支 3.1.3切换分支 ...
- Hibernate 框架入门
接着上一篇的 Hibernate 框架的了解,我们就继续学习 Hibernate 框架.这次就进入 Hibernate 框架的入门学习. 首先在学习 Hibernate 框架之前,我们要准备好我们需要 ...
- js-02-循环语句
循环语句分类{ for while do ( ) while } 一.for循环语句和for循环的嵌套 for循环格式eg: <script> var sim = 0; for(var i ...