项目当中经常出现相同时间点的数据存入两条一模一样的数据,通常是由于页面或客户端网络延迟导致的重复提交导致的。这种情况,如果没有唯一业务字段的索引处理,就会导致重复数据出现在我们的MySQL数据表中。这时候只能手动删除掉重复的数据,以免影响业务正常使用。
下面是针对这种问题的后端解决方案。 方案不是最好的,但是能有效避免重复数据的产生。
传送门:幂等性问题的思考和总结,防重、幂等,常用解决方案,解决方式
实现方案:根据请求信息进行md5计算,保存到Redis,有效期1小时;
下次请求再进来判断Redis是否存在?如果已存在,提示重复请求。
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.METHOD)
public @interface RepeatRequestInterceptor {
}
2、使用注解
@RepeatRequestIntercept
@ApiOperation(value = "申请")
@PostMapping("/applyRefund")
public RefundApplyVO applyRefund(@Validated @RequestBody RefundApplyDTO refundApplyDTO) {
do something...
return new RefundApplyVO(refundOrder.getId());
}
3、处理注解的统一切面类
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.common.utils.Md5Utils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
@Aspect
@Component
@Slf4j
public class RepeatRequestAspect {
private static final String REPEAT_REQUEST_REDIS_KEY = "repeat_request:";
private static final Integer REPEAT_REQUEST_TIMEOUT = 3600;
@Resource
private RedisClient redisClient;
@Around("@annotation(com.xxxx.web.RepeatRequestInterceptor)")
public Object before(ProceedingJoinPoint joinPoint) throws Throwable {
Long userId = UserContext.getUserId();
Object[] args = joinPoint.getArgs();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
String requestInfo = joinPoint.getTarget() + method.getName() + userId + userId + JSONObject.toJSONString(args);
String md5 = Md5Utils.getMD5(requestInfo.getBytes());
String lockKey = REPEAT_REQUEST_REDIS_KEY.concat(md5);
Boolean lockFlag = false;
try {
// Redis缓存1小时基于请求内容计算的md5值
lockFlag = redisClient.setNx(lockKey, md5, REPEAT_REQUEST_TIMEOUT);
if (lockFlag) {
return joinPoint.proceed();
} else {
throw new BizException(BaseResultCodeEnum.REPETITIVE_OPERATION);
}
} finally {
if (lockFlag) {
redisClient.delete(lockKey);
}
}
}
}
下面是基于这种问题的前后端解决方案
思路大概是:
- 后端统一生成一个唯一token值,
- 前端在数据提交之前获取这个token值,数据提交的时候统一将这个token通过header传给后端,
- 后端先判断token是否存在Redis中,存在则提示重复请求,不存在就将token保存到Redis中(有效期2小时可调整)。
代码明细稍候提供
-------------欢迎各位留言交流,如有不正确的地方,请予以指正。【Q:981233589】



