token机制实现幂等(订单一致性问题)

如何保证订单状态一致性?如何保证接口的幂等性?订单系统保证幂等性?

  • 保证幂等性是指保证用户多次重复操作或请求造成的结果是一致的,不会产生任何副作用

token机制原理和session的区别

image-20211104112036934

1. 幂等实现流程

以订单系统为例,假设用户在付款流程中(此时页面跳转到第三方支付页面),用户在第三方支付成功之后,切换页面回到待付款页面手动关闭订单,此时的订单的状态就出错了,先被更新成已付款状态,后又变更成已关闭,造成了订单状态不一致的现象出现!这种不加任何防护手段的场景,就违背了幂等性的原则

image-20211104111155678

2.代码实现

  1. 自定义注解

//target表示作用对象,所有类型的方法
@Target(ElementType.METHOD)
//注解的生命周期,运行时
@Retention(RetentionPolicy.RUNTIME)
public @interface MiDeng{

}
  1. 编写拦截器,处理拦截到的请求

//幂等拦截器
public class MiDengInterceptor implements HandlerInterceptor {
   @Autowired
   private RedisTemplate redisTemplate;  //使用redis存储token

   //处理幂等问题
   @Override
   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       response.setCharacterEncoding("utf-8");
       response.setContentType("text/html;charset=utf-8");
       //首先判断拦截的请求是否是方法
       if (handler instanceof HandlerMethod) {
           HandlerMethod handlerMethod = (HandlerMethod) handler;
           //判断该方法是否包含幂等注解
           if (handlerMethod.hasMethodAnnotation(MiDeng.class)) {
               //对包含注解的进行处理
               String uniqueId = "";
               //在request中获取id,如果没有,在请求头中获取一次
               if ((uniqueId = request.getParameter("uniqueId")) == null) {
                   uniqueId = request.getHeader("uniqueId");
              }
               //如果uniqueId为空,则表明当前没有执行支付流程
               String key="uniqueId:"+uniqueId;
               if (redisTemplate.hasKey(key)){//当前已经在执行流程
                   response.getWriter().write("请勿重复执行操作");
                   return false;
              }else {//没有执行过,在redis中创建taken,设置存活时间为2min,使用util包下的SHA1加密生成一个唯一的32位字符串
                   redisTemplate.boundValueOps(key).set(SHA1Util.encode(uniqueId));
                   redisTemplate.expire(key, 120, TimeUnit.SECONDS);
                   return true;
              }
          } else {
               //不包含幂等注解,放行
               return true;
          }
      } else {
           //拦截的请求不是方法,放行
           return true;
      }
  }
}
  1. 在springMVC中配置拦截器

        <mvc:interceptor>
           <!--拦截的路径所有-->
           <mvc:mapping path="/**"/>
           <!--不拦共同资源-->
           <mvc:exclude-mapping path="/static/**"/>
           <!--拦截器的位置-->
           <bean class="com.oracle.shop.security.MiDengInterceptor"></bean>
       </mvc:interceptor>
  1. 在需要的方法上添加注解

    //取消订单
   @MiDeng
   @RequestMapping("/toCancelOrders")
   public String toCancelOrders(int uniqueId, HttpSession session)
   //跳转到阿里支付页面,
   @MiDeng
   @RequestMapping("/toAlipay")
   public void toAlipay(int uniqueId, HttpServletResponse response) throws AlipayApiException, IOException
  1. 在支付完成后,删除该token,支付完成后的回调页面

//流程执行结束后,删除redis当中的token
redisTemplate.delete("uniqueId"+orders.getId());
posted @ 2021-11-06 13:11  2333gyh  阅读(962)  评论(0编辑  收藏  举报