分享一种防重复操作的实现方式,基于注解和redis锁的实现方式。
重复请求枚举:
/**
* 重复请求枚举
* TOKEN 根据token强制重复
* FINAL 指定key
* SP_EL 根据入参
* @author zhangtaotao
* @since 2020/11/16 16:50
**/
public enum NonRepeatEnum {
TOKEN,
FINAL,
SP_EL;
}
注解的定义:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NonRepeat {
/**
* 锁定时间 seconds
*
* @return
*/
int lockTime() default 120;
/**
* lockKey策略
*
* @see NonRepeatEnum
*/
NonRepeatEnum lockKeyPolicy() default NonRepeatEnum.TOKEN;
/**
* 前缀 psi:common:repeat:{className}:{methodName}:
* 默认根据 token
* 支持 spEL 取值非string类型会序列化为json,JSON.toJsonString
*
* @return
*/
String lockKey() default "";
/**
* 是否在执行结束后释放锁
*
* @return
*/
boolean releaseAfterReturn() default true;
}
重复请求控制切面逻辑:
/**
* 重复请求限制
*
* @author zhangtaotao
* @since 2020/11/16 17:24
**/
@Slf4j
@Aspect
@Component
public class NonRepeatAspect {
private GlobalLockAppService globalLockAppService;
private final ExpressionParser parser = new SpelExpressionParser();
@Around(value = "@annotation(nonRepeat)")
public Object before(ProceedingJoinPoint pjp, NonRepeat nonRepeat) throws Throwable {
UserLocal.USER.saveLocal(Locale.CHINESE);
Object[] args = pjp.getArgs();
String lockKey = buildLockKey(args, nonRepeat);
String className = pjp.getTarget().getClass().getName();
String methodName = pjp.getSignature().getName();
String realKey =
String.join(BaseConstants.COLON, RedisConstants.REDIS_REPEAT_LOCK, className, methodName, lockKey);
GlobalLock globalLock = GlobalLock.of(realKey, Duration.ofSeconds(nonRepeat.lockTime()));
if (!globalLockAppService.getLock(globalLock)) {
throw new BizsException(ResultEnum.NON_REPEAT_ERROR);
}
Object result;
try {
result = pjp.proceed();
} finally {
if (nonRepeat.releaseAfterReturn()) {
globalLockAppService.unlock(globalLock);
}
}
return result;
}
private String buildLockKey(Object[] args, NonRepeat nonRepeat) {
String lockKey = null;
NonRepeatEnum lockKeyPolicy = nonRepeat.lockKeyPolicy();
Objects.requireNonNull(lockKeyPolicy, "can not find lock key policy");
switch (lockKeyPolicy) {
case FINAL:
lockKey = nonRepeat.lockKey();
break;
case SP_EL:
lockKey = buildKeyForExpression(args, nonRepeat);
break;
case TOKEN:
lockKey = UserLocal.USER.getToken();
break;
default:
break;
}
return lockKey;
}
private String buildKeyForExpression(Object[] args, NonRepeat nonRepeat) {
Objects.requireNonNull(args, "args can not be null");
String lockKey;
if (StringUtils.isEmpty(nonRepeat.lockKey())) {
throw new BizsException(ResultEnum.VALIDATE_PARAM_ERROR);
}
Expression exp = parser.parseExpression(nonRepeat.lockKey());
StandardEvaluationContext ctx = new StandardEvaluationContext();
if (args.length == 1) {
ctx.setRootObject(args[0]);
} else {
ctx.setRootObject(args);
}
Object value = exp.getValue(ctx);
if (value instanceof String) {
lockKey = String.valueOf(value);
} else {
lockKey = JSON.toJSONString(value);
}
return lockKey;
}
@Autowired
public void setGlobalLockAppService(GlobalLockAppService globalLockAppService) {
this.globalLockAppService = globalLockAppService;
}
}
应用
getLockKey()方法定义在入参DoApproveFlowVO中,用于定义锁的唯一内容
@NonRepeat(lockKeyPolicy = NonRepeatEnum.SP_EL, lockKey = "getLockKey()")
@ApiOperation(value = "审批操作接口")
@PostMapping(value = "/doApprove")
public ResponseResult<Object> doApprove(@RequestBody @Validated DoApproveFlowVO doApproveFlowVO) {
//审批操作
}
至于redis锁的实现可以参考另外一篇文章:redis锁的一种实现