自定义注解实现重复性提交拦截

2020-07-06 18:14:15  卢浮宫  版权声明:本文为站长原创文章,转载请写明出处


一、背景

    最近系统上出现一个异常:由于某些位置原因(网络延迟?),业务员在业务操作时会反复进行某一个动作导致了一些数据流转的异常。

我们目前对应的处理方案是,前端定义处理状态,在业务操作开始之前和完成之后才对这个状态进行更新。每次数据操作都会对该状态进行检测。

但是,也会面临如下问题:①前端的操作再某种程度上是不安全的。②没有从本质上解决问题(接口层面)。③我想放在后端处理...

二、解决方案梳理

    首先我们在接口调用前进行拦截,这个时候我们可以获取到对应的操作人员和操作事件。然后使用我们的规则统一生成操作key(这个key会存在于缓存数据中),

当我们在缓存中检测不到这个key时就认为这个数据是一个合法操作,然后把这个key写入缓存并定义过期时间5min。如果我们在缓存数据中检测到了这个key,那么就认为这个操作已经在处理中了

并作出自定义处理。很重要的一步:在结束时我们要无条件把这个key给删除掉!!!

    整理如下:

    1、使用aop拦截数据操作(仅检测post请求)

    2、统一生成key,并对key的存在性进行检测

    3、如果不存在于缓存数据中,就认为是合法操作同时保存key(过期时间5min)

    4、如果存在于缓存数据中就认为是非法操作,第一处事件处理

    5、在接口操作结束时删除这个key

    6、为了方便做成自定义注解吧,就叫submitCheck吧

三、核心代码实现

    ①自定义注解:

    import java.lang.annotation.*;

/**
* @author XA
* date 2020/6/30 14:59
* description 重复提交检测
* params
* return
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SubmitCheck {

String value() default "";
}

    ②切入类实现

    import com.XXXX.utils.BHUserUtil;
import com.XXXX.utils.RedisUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;

/**
* @author XA
* date 2020/6/30 14:47
* description 提交检测(单个账号,单次操作有且仅有一个)
* params
* return
*/
@Component
@Aspect
public class SubmitCheckHandle {

@Autowired
private RedisUtils redisUtils;

@Autowired
private BHUserUtil bhUserUtil;

@Pointcut("@annotation(com.hd.common.submitCheck.SubmitCheck)")
public void submitPointCut() {

}

@Around("submitPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
String redisKey = null;
try {
Object[] args = point.getArgs();
if(args == null){
return null;
}
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes)ra;
assert sra != null;
HttpServletRequest request = sra.getRequest();
String method = request.getMethod();
if("post".equalsIgnoreCase(method)) {
Integer handle = Math.toIntExact(bhUserUtil.getUserId());
String opeartion = point.getTarget().getClass().getName();
redisKey = handle + opeartion;
if(!this.doCheck(handle, opeartion)){
throw new RuntimeException("操作正在进行,请等待!");
}
}
// 继续执行方法
return point.proceed();
}catch (Exception e){
throw new RuntimeException(e.toString());
}finally {
// 删除缓存操作记录
if(redisKey != null){
redisUtils.delete(redisKey);
}
}
}

/**
* 功能描述: 用户操作检测
* Param: [handle, opeartion]
* Return: boolean
*/
private boolean doCheck(Integer handle, String opeartion){
String redisKey = handle + opeartion;
if(redisUtils.get(redisKey) != null){
return false;
}else{
redisUtils.set(redisKey, opeartion, 60 * 5);
}
return true;
}
}

    ③使用方法

        在方法上添加@submitCheck()即可

四、后续持续跟踪完善中...






最新评论: