最近项目中又出现重复数据,除了id不同,其他的完全相同的一样的数据,导致业务关系应该唯一不唯一
问题产生的原因:以下订单举例说明,因为各种原因(网络卡,快递点击等)重复提交2个或者以上一模一样的订单,由于是同时提交的,第一个订单执行扣款生成订单未完成时候,第二个已经进来了,导致付一笔钱购买了2次或多次商品
单机的可以按照下面方法解决,分布式的就需要用到分布式锁进行解决
1、定义拦截器:
package com.drawblue.device.interceptor; import com.alibaba.fastjson.JSONObject; import com.drawblue.hootul.utils.StrUtil; import com.drawblue.device.config.RepeatSubmit; import com.drawblue.device.enums.errors.CommonError; import com.drawblue.device.utils.SystemConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.TimeUnit; public class RepeatSubmitInterceptor extends HandlerInterceptorAdapter { private Logger logger = LoggerFactory.getLogger(this.getClass()); // redis 有效期, 默认2s, 单位: 秒. private long expireTime = 2; @Autowired private RedisTemplate<String, Object> redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class); if (annotation != null) { if(repeatSubimitValidator(request)){ //请求数据相同 logger.warn("please don't repeat submit,url:"+ request.getServletPath()); JSONObject result = new JSONObject (); result.put("code",1); result.put("meassage","repeat"); response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); response.getWriter().write(result.toString()); response.getWriter().close(); return false; }else {//如果不是重复相同数据 return true; } } return true; } else { return super.preHandle(request, response, handler); } } /** * 验证同一个url数据是否相同提交,相同返回true * @param httpServletRequest * @return */ public boolean repeatSubimitValidator(HttpServletRequest httpServletRequest){ //获取请求参数map Map<String, String[]> parameter = httpServletRequest.getParameterMap(); String param = null; if(!parameter.isEmpty()){ param = JSONObject.toJSONString(parameter); }else{ param = "param is empty"; } String token = httpServletRequest.getHeader(SystemConstants.TOKEN_HEADER); if (StringUtil.isEmpty(token)){ //如果没有token,直接放行 return false; } //过滤过后的请求内容 String url = httpServletRequest.getRequestURI(); String redisKey = token + url; String repeatValue = (String) this.redisTemplate.opsForValue().get(redisKey); if(StrUtil.isEmpty(repeatValue)){ //如果上一个数据为null,表示还没有访问页面 //存放并且设置有效期,2秒 this.redisTemplate.opsForValue().set(redisKey, param, expireTime, TimeUnit.SECONDS); return false; }else{//否则,已经访问过页面 if(param.equals(repeatValue)){ //如果上次url+数据和本次url+数据相同,则表示重复添加数据 return true; }else{//如果上次 url+数据 和本次url加数据不同,则不是重复提交 this.redisTemplate.opsForValue().set(redisKey, param, expireTime, TimeUnit.SECONDS); return false; } } } } package com.topband.device.interceptor; import com.alibaba.fastjson.JSONObject; import com.topband.cloud.common.https.ResponseObj; import com.topband.cloud.common.utils.StringUtil; import com.topband.device.config.RepeatSubmit; import com.topband.device.enums.errors.CommonError; import com.topband.device.utils.SystemConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.TimeUnit; public class RepeatSubmitInterceptor extends HandlerInterceptorAdapter { private Logger logger = LoggerFactory.getLogger(this.getClass()); // redis 有效期, 默认2s, 单位: 秒. private long expireTime = 2; @Autowired private RedisTemplate<String, Object> redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class); if (annotation != null) { if(repeatSubimitValidator(request)){ //请求数据相同 logger.warn("please don't repeat submit,url:"+ request.getServletPath()); ResponseObj result = new ResponseObj(); result.setStatus(CommonError.STATUS_500302.getStatus()); result.setMessage(CommonError.STATUS_500302.getMessage()); response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); response.getWriter().write(result.toString()); response.getWriter().close(); return false; }else {//如果不是重复相同数据 return true; } } return true; } else { return super.preHandle(request, response, handler); } } /** * 验证同一个url数据是否相同提交,相同返回true * @param httpServletRequest * @return */ public boolean repeatSubimitValidator(HttpServletRequest httpServletRequest){ //获取请求参数map Map<String, String[]> parameter = httpServletRequest.getParameterMap(); String param = null; if(!parameter.isEmpty()){ param = JSONObject.toJSONString(parameter); }else{ param = "param is empty"; } String token = httpServletRequest.getHeader(SystemConstants.TOKEN_HEADER); if (StringUtil.isEmpty(token)){ //如果没有token,直接放行 return false; } //过滤过后的请求内容 String url = httpServletRequest.getRequestURI(); String redisKey = token + url; String repeatValue = (String) this.redisTemplate.opsForValue().get(redisKey); if(StringUtil.isEmpty(repeatValue)){ //如果上一个数据为null,表示还没有访问页面 //存放并且设置有效期,2秒 this.redisTemplate.opsForValue().set(redisKey, param, expireTime, TimeUnit.SECONDS); return false; }else{//否则,已经访问过页面 if(param.equals(repeatValue)){ //如果上次url+数据和本次url+数据相同,则表示重复添加数据 return true; }else{//如果上次 url+数据 和本次url加数据不同,则不是重复提交 this.redisTemplate.opsForValue().set(redisKey, param, expireTime, TimeUnit.SECONDS); return false; } } } }
2、写个标志性注解
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
}
3、添加到拦截器中
registry.addInterceptor(repeatSubmitInterceptor()).addPathPatterns("/**");
@Bean
public RepeatSubmitInterceptor repeatSubmitInterceptor(){
return new RepeatSubmitInterceptor();
}
4、在使用的方法上添加注解即可