Java 后端防重提交解决办法及讨论

针对防重提交现阶段解决办法

需要注意点,本方法的解决偏向于防重复提交,不针对于对接口幂等性的处理等需要具体原子性的操作.
虽然也能用,但针对大流量情况下,交互、响应及性能上还是不能得到保证. 存在交互差 错误, 重复的情况

结论

先给代码吧,分为几种情况,目前阶段觉得好用的方法,前端防重可以自行百度
1. 项目为单体项目且为SpringBoot前后端分离未使用redis
PS: 有无redis 均能使用,最通用的一种办法

<!--POM文件 是否需要其他jar包暂未考证,因为项目中有封装好的jar包,如果有欢迎提出来修改-->
		<!-- guava本地锁 -->
		<dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>21.0</version>
        </dependency>
类名: LocalLock   
作用: 用于被aop拦截标识
注意是自定义注解
@interface LocalLock 
@Target({ElementType.METHOD})  //作用于方法上
@Retention(RetentionPolicy.RUNTIME)  //运行时
@Documented
@Inherited
public @interface LocalLock {
    String key() default ""; 
    //过期时间,使用本地缓存可以忽略,如果使用redis做缓存可以加上
    int expire() default 5;
}
类名: LockMethodInterceptor
作用: 全局拦截自定义注解的内容  
@Aspect
@Configuration
@Slf4j
public class LockMethodInterceptor {
    //定义缓存,设置最大缓存数及过期日期 .maximumSize为最大访问数  .expireAfterWrite为过期时间  单位为秒可以自定义
    private static final Cache<String,Object> CACHE = CacheBuilder.newBuilder().maximumSize(1000).expireAfterWrite(10, TimeUnit.SECONDS).build();
    //这里设置你的切点  这里实现方式是实现了一个自定义注解 通过获取自定义注解注解的方法,来控制方法 自定义注解名为 LocalLock
    @Around("execution(public * *(..))  && @annotation(com.sxdh.core.common.annotion.LocalLock)")
    public Object interceptor(ProceedingJoinPoint joinPoint){
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        LocalLock localLock = method.getAnnotation(LocalLock.class);
        String key = getKey(localLock.key(),joinPoint.getArgs());
        if(!StringUtils.isEmpty(key)){
        //判断本地内存中是否以当前key 为 key 的 键值对
            if(CACHE.getIfPresent(key) != null){
                log.warn("进入切点");
                //这里的return 可以根据自己的业务去书写,code msg data 自定义
                return new ErrorTip(1,"请勿重复提交!!!");
            }
            //如果是第一次访问,则保存当前key值 过期时间依据CACHE的定义
            CACHE.put(key,key);
        }
        try{
            return joinPoint.proceed();
        }catch (Throwable throwable){
            throw new RuntimeException("服务器异常");
        }finally {

        }
    }

// 这里其实是自定义的一种生成Key的方法 ,  i 指定参数的位置
/*
 如 void findSomeThing(String arg1,String arg2){
    			...
}
arg[0] 就代表 arg1 , arg[1] 就代表 arg2
*/ 
    private String getKey(String keyExpress, Object[] args){
        for (int i = 0; i < args.length; i++) {
            keyExpress = keyExpress.replace("arg[" + i + "]", args[i].toString());
        }
        return keyExpress;
    }
}
/**
* 实现类  
* 这里第一个参数 使我们要实现的业务,保证这个学员ID 同一时间只能有一个请求去修改
* 后面的参数 就是要修改的数据, 这里可以灵活配置,无所谓格式 
* 因为什么呢, 这里的值其实是为了使key值唯一设置的,如果能简单设置唯一,那就使用简单的数据
* 不行的话,还有很多种几种解决方案
* 1. 如果是对象 可以json化 作为key值 替换  当然有点傻,但是直接
* 2. 如果是对象 将可以标识用户唯一的业务数据拿出来组装替换 
* 2. 如果是对象 可以hashCode()  这个code值作为唯一标识 单例
* 3. 根据请求的token
* 4. 根据请求的request /session 等
* 方法很多 ,欢迎提出来
*/
@LocalLock(key = "localLock:traffic:add:arg[0]")
    public Object update(Integer stuId,...) {
    					...
    }

2. 项目为单体项目且SpringBoot前后端分离有使用redis

1. 使用 redis 缓存 替代 Gavua 或者共同使用 过期时间使用LocalLock的expire  key 依旧 
2. 使用 redis 的计数器,来控制,方法为: 以当前业务唯一字段设为key值 做计数器操作, 
   如果小于等于0 操作,如果大于0 则请求拦截

3. 项目为单体项目等其他情况
此种情况还是适用第一种情况,适用本地锁机制来解决,但是会存在几个问题需要说明

<!--当为MVC项目时,需要注意开启AOP的注册 在配置xml里加上-->
  <aop:aspectj-autoproxy expose-proxy="true"></aop:aspectj-autoproxy>

此种情况使用拦截器,但是需要明确拦截器和请求的区别

/**
* 如果不想适用注解的模式或者觉得麻烦,又或者使用不了,可以考虑拦截器  
* 继承 extends HandlerInterceptorAdapter
*/
public class IntercepterWebRequest extends HandlerInterceptorAdapter{
   /**
   *实现preHandle方法 针对于短时间内的同一客户端的请求去做拦截,同样可以适用本地锁的概念
   */
   public boolean preHandle(HttpServletRequest request,
							 HttpServletResponse response, Object handler) throws Exception {
		
			return false;
		}

		return true;
	}
 			....
}
    这里说明一个特殊的实现方式,是我的同事提出来,没有去做验证,感觉挺有道理的,
	全局定义一个boolean 值 flag ,每当有一个请求打进来 如果此时flag为true  则把flag变为false  
进行业务操作,后续的请求来的时候如果为false 就拒掉. 只有当前业务流程走完后,才把flag又设为true
,或者异常的时候设为true,有兴趣的可以做做验证

4. 项目为分布式项目
5. 题外话
可以看出,其实除了最后一个布尔值的方法,其他的重点都是在保证key值的唯一去限制请求的重复提交,
虽然也可以在数据库的层次上进行操作,不过一般情况不会去考虑

## 前言
看了一篇博文,个人感触针对与接口的幂等性处理其实原理大都相同,结果都显示为数据的重复或者异常情况 ,都是为了保证结果的一致性
以前在面试的时候,记得看过流量削尖的方法,有一种方法印象很深,使用令牌桶来控制请求的/或者水槽法
蘑菇街商家平台资金团队工程师 @木照
代理类的生成流程@乘风破浪的码农
先这样吧,后面再继续写

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值