Redisson实现防重复提交
防止重复提交的重要性?
在业务开发当中,为什么我们要去想办法解决重复提交这一问题的发生呢?网上的概念有很多:导致表 单重复提交的,造成数据重复的,增加服务器负载的,甚至会造成服务器宕机的,那么为什么会出现这 种情况呢?页面快速的操作以及网络Io问题或者数据库响应慢,这些都会增加后端重复处理的概率, 就拿之前做的项目来说,因为业务性较强,需要进行一个"点赞"的操作,写好业务以后在测试时连续点 击几下,重复地进行点赞和取消点赞操作,因为操作过于频繁,而服务器走过来的响应速度没有那么快 地进行处理,就会导致数据的重复插入情况,最后导致服务器报错,这时,防止重复提交就显得很重要 了。
如何防重复提交?
实现方法有非常多,但是原理大概都是相通的,我选择的是通过使用AOP切面+Redisson来进行处理, 前端发起请求的时候需要在请求头里将token给我,然后我这边通过token+ip再加上请求的路径作为一 个key存到redis里面去,设置一个过期时间,下一次再从缓存当中读取和当前时间进行一个判断,若大 于我们自己设定的超时时间,就进行拦截,不让它进行后面的业务代码,并给出提示“操作频繁,请稍 后重试” 分布式锁方案采用Redisson,这也是官网比较推荐的使用方法。利用分布式锁的特性来防止重复提交。
1.导入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.15.0</version>
</dependency>
2.使用注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NoRepeat {
/**
* 锁名称
*/
String lockName() default "no-repeat-default-lock-";
/**
* 参数key,支持Spel
*/
String key() default "";
/**
* 获取锁的最长时间,单位默认为秒
*/
long waitTime() default 5;
/**
* 租赁时间,单位默认为秒
* 加锁的时间。超过这个时间后锁便自动解开了
*/
3.编写切面
@Slf4j
@Aspect
@Component
public class SystemAspect {
@Resource private RedissonClient redissonClient;
/**
* SPEL表达式解析器
*/
private final SpelExpressionParser parser = new SpelExpressionParser();
LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer(); @Around(value = "@annotation(NoRepeat)")
public Object doNoRepeatAround(ProceedingJoinPoint pjp) throws InterruptedException
{
NoRepeat noRepeat = CsUtil.getJoinPointAnnotation(pjp, NoRepeat.class);
String key = parseKey(pjp, noRepeat);
RLock fairLock = redissonClient.getFairLock(noRepeat.lockName() + key);
boolean res = fairLock.tryLock(noRepeat.waitTime(), noRepeat.leaseTime(), TimeUnit.SECONDS);
if (res) {
log.info("拿到锁");
try {return pjp.proceed();
} catch (Throwable e) {
log.error(e.getMessage(), e);
throw new ServiceException(e.getMessage());
} finally { fairLock.unlock(); log.info("释放锁操作"); }
} else { log.error("重复的请求");
return new Response<Boolean>(999, "重复的请求");
}
}
private String parseKey(ProceedingJoinPoint pjp, NoRepeat noRepeat) {
Object[] args = pjp.getArgs();
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
String[] params = discoverer.getParameterNames(method);
EvaluationContext context = new StandardEvaluationContext();
for (int len = 0; len < Objects.requireNonNull(params).length; len++) {
context.setVariable(params[len], args[len]); }
String keySpel = noRepeat.key(); Expression keyExpression = parser.parseExpression(keySpel);
return keyExpression.getValue(context, String.class);
}
}
4. 使用方式
@RestController
public class TestController {
@ApiOperation("身份绑定(注册)接口")
@NoRepeat(key = "#user.name")
@PostMapping("/v1/test/noRepeat")
public Response<User> doSome(@RequestBody User user) {
return new Response<>();
}
@Data
static class User {
private String name;
}
}
5.展示效果
