场景

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

技术方案

现在使用比较广泛的方案都是基于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. 微信Pcweb登录简介

    微信第三方PCweb扫码登录 准备工作 (1) 网站应用微信登录是基于OAuth2.0协议标准构建的微信OAuth2.0授权登录系统. 在进行微信OAuth2.在进行微信OAuth2.0授权登录接入之 ...

  2. android6.0系统Healthd深入分析

    概述 Healthd是android4.4之后提出来的一种中介模型,该模型向下监听来自底层的电池事件,向上传递电池数据信息给Framework层的BatteryService用以计算电池电量相关状态信 ...

  3. JavaEE初学笔记之Servlet与Tomcat

    JavaEE开发,本质上就是开发一个个Servlet,然后部署到Servlet容器(如Tomcat)里运行.   1. Servlet是什么? Servlet就是一个普通的接口(Interface), ...

  4. KnockoutJS-自定义属性绑定

    在knockoutjs中,已有的绑定功能已经十分强大,基本上可以不需要再去考虑扩展了,但是,也有例外的场景,面对这种场景,还是得去完成,knockoutJS提供了自定义绑定来扩展绑定功能. 一.新建绑 ...

  5. LinkedHashMap源码学习

    描述 可以按照添加元素的顺序对元素进行迭代的HashMap的子类. 注意,上面说的是加元素的顺序.也就是说,更新元素时,是不会影响遍历结构的的.除非设置参数accessOrder为true,将更新元素 ...

  6. mysql 写入中文乱码

    今天从另一个系统往mysql数据库写入数据,发现中文变成了????? 检查数据库的设置 ,server对应字符集是latinl 调整mysql参数配置,配置文件目录/etc/mysql/mysql.c ...

  7. iOS开发中全量日志的获取

    我们在app中对崩溃.卡顿.内存问题进行监控.一旦监控到问题,我们就需要记录下来,但是,很多问题的定位仅靠问题发生的那一刹那记录的信息是不够的,我们需要记录app的全量日志来获取更多的信息. 一,使用 ...

  8. PWA 学习笔记(五)

    离线与缓存 资源请求的拦截代理: 1.资源请求的判断: (1)fetch 事件会拦截页面上所有的网络资源请求,但我们通常只对部分资源请求进行处理, 其余的请求会继续走浏览器默认的资源请求流程 (2)f ...

  9. Oracle处理关于sysaux表空间爆满的问题---更新最新方法!!

    对于SYSAUX表空间而言,如果占用过大,那么一般情况下是由于AWR信息或对象统计信息没有及时清理引起的,具体原因可以通过如下的SQL语句查询: SELECT OCCUPANT_NAME ORDER ...

  10. pymysql用法,Python连接MySQL数据库

    Pymysql模块是专门用来操作mysql数据库的模块,使用前需要安装,安装指令:pip install pymysql 操作流程: 第一步:import pymysql 第二步:获取数据库的连接 , ...