防重复提交
简介:
(1)客户端访问时,拦截访问数据,
(2)进行验证是否是配置的时间内(如下例子:ttl = 10),有相同的参数访问,
(3)如果有就相当于数据重复访问,直接返回。
(4)以下两种方法供大家参考,每个人都有自己喜欢的方式去使用
实例<一>:
引入jia包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
后台代码
//防重复提交,表示10秒之内的不允许有相同的ps数据,也就是说10秒之内不允许有相同的ps = {"req.mobile"}
@RequestMapping("/request")
@Recommit(ttl = 10, ps = {"req.mobile"}, type = 2)
public Result applyFranchiseStore(Req req) {
try {
return userMemberApplyService.applyIdentity(req);
} catch (Exception e) {
log.error("申请代理人或者加盟店异常" + req.toString(), e);
return Result.buildFail("申请失败");
}
}
配置重复提交
package dorago.yiqiancms.biz.common.recommit;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Recommit {
/**
* 参与重复提交计算的请求参数
* @return
*/
String[] ps() default {};
/**
* 周期内参数相同的提交判定为重复提交
* 单位: 秒
* @return
*/
int ttl() default 1;
/**
* 使用方区分:1.app 2.h5 如果不需要区分 此参数可以不要
* @return
*/
int type() default 1;
}
请求拦截,检查重复性
package com.dorago.common.interceptor.recommit;
import com.alibaba.fastjson.JSON;
import com.dorago.common.Result;
import com.dorago.common.SpringUtil;
import com.dorago.common.interceptor.log.MethodLogAspect;
import com.dorago.syj.biz.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Arrays;
@Aspect
@Component
@Slf4j
@Order(1)
public class RecommitAspect {
@Resource
RedisUtil redisUtil;
@Pointcut("@annotation(com.dorago.common.interceptor.recommit.Recommit)")
public void recommitPointcut(){}
@Around("recommitPointcut()")
public Object doAround(ProceedingJoinPoint jp) throws Throwable {
boolean recommited = false;
Object result;
try {
MethodSignature signature = (MethodSignature) jp.getSignature();
Method m = signature.getMethod();
Recommit rr = m.getAnnotation(Recommit.class);
String ps = rr.ps();
Object[] args = jp.getArgs();
StringBuilder sb = new StringBuilder();
if (args == null || args.length == 0) {
sb.append("NOARGS");
} else if (StringUtils.isBlank(ps)) {
Arrays.stream(args).forEach(a->sb.append(JSON.toJSONString(a)).append("-"));
} else {
String[] ns = ps.split(",");
if (ns.length > args.length) {
throw new Exception("ps param count invalid!");
}
for (String p : ns){
if(!NumberUtils.isDigits(p) || Integer.parseInt(p)>args.length){
throw new Exception("ps param value invalid!");
}
int idx = Integer.parseInt(p);
sb.append(JSON.toJSONString(args[idx-1])).append("-");
}
}
int ttl = rr.ttl();
String key = sb.toString();
String md5 = DigestUtils.md5Hex(key);
String appName = SpringUtil.getProperty("spring.application.name");
String cacheKey = "RR-" + appName + "-" + m.getDeclaringClass().getName() + "." + m.getName() + "-" + md5;
//boolean exist = redisUtil.hasKey(cacheKey);
recommited = !redisUtil.setIfAbsent(cacheKey, "", ttl);
}catch (Exception e){
log.error("RecommitAspect.doAround error:",e);
}finally {
if(!recommited) {
result = jp.proceed();
}else{
result = Result.with(111, "重复提交");
}
}
return result;
}
}
其实我们使用下面一种方式也是可以的。
实例<二>
后台方法
@Recommit(ttl=10) //此处这样写表示在10秒之内 如果有相同的参数orderId访问这个方法,那么就是重复提交的。
public Result<Void> confirmNew(Long orderId) {
String errMsg = null;
Result<Void> result = Result.empty();
//业务逻辑
}
提交配置
package com.dorago.common.interceptor.recommit;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Recommit {
/**
* 参与重复提交计算的请求参数索引集合,逗号分隔
* 例: 第1,2个参数则设置为"1,2",全部参数则用默认值即可
* @return
*/
String ps() default "";
/**
* 周期内参数相同的提交判定为重复提交
* 单位: 秒
* @return
*/
int ttl() default 1;
}
请求拦截进行验证
import com.alibaba.fastjson.JSON;
import com.dorago.common.Result;
import com.dorago.common.SpringUtil;
import com.dorago.common.interceptor.log.MethodLogAspect;
import com.dorago.syj.biz.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Arrays;
@Aspect
@Component
@Slf4j
@Order(1)
public class RecommitAspect {
@Resource
RedisUtil redisUtil;
@Pointcut("@annotation(com.dorago.common.interceptor.recommit.Recommit)")
public void recommitPointcut(){}
@Around("recommitPointcut()")
public Object doAround(ProceedingJoinPoint jp) throws Throwable {
boolean recommited = false;
Object result;
try {
MethodSignature signature = (MethodSignature) jp.getSignature();
Method m = signature.getMethod();
Recommit rr = m.getAnnotation(Recommit.class);
String ps = rr.ps();
Object[] args = jp.getArgs();
StringBuilder sb = new StringBuilder();
if (args == null || args.length == 0) {
sb.append("NOARGS");
} else if (StringUtils.isBlank(ps)) {
Arrays.stream(args).forEach(a->sb.append(JSON.toJSONString(a)).append("-"));
} else {
String[] ns = ps.split(",");
if (ns.length > args.length) {
throw new Exception("ps param count invalid!");
}
for (String p : ns){
if(!NumberUtils.isDigits(p) || Integer.parseInt(p)>args.length){
throw new Exception("ps param value invalid!");
}
int idx = Integer.parseInt(p);
sb.append(JSON.toJSONString(args[idx-1])).append("-");
}
}
int ttl = rr.ttl();
String key = sb.toString();
String md5 = DigestUtils.md5Hex(key);
//获取配置文件的值
String appName = SpringUtil.getProperty("spring.application.name");
String cacheKey = "RR-" + appName + "-" + m.getDeclaringClass().getName() + "." + m.getName() + "-" + md5;
//boolean exist = redisUtil.hasKey(cacheKey);
recommited = !redisUtil.setIfAbsent(cacheKey, "", ttl);
}catch (Exception e){
log.error("RecommitAspect.doAround error:",e);
}finally {
if(!recommited) {
result = jp.proceed();
}else{
result = Result.with(111, "重复提交");
}
}
return result;
}
}
总结:不管是1还是2的实例,主要就是配置Recommit的地方。配置好了之后,就在对应的方法上加上这个注解既可。