避免重复生成订单的思路

  1. 前端下单按钮点击之后,应该立即设置为禁用状态,避免网络卡时多次点击下单

  2. 在技术方面,这是一个分布式一致性的问题,即客户端和服务器端对某个订单是否成功/失败达成一致。防止重单的关键是使用一个由客户端生成的,可用于避免重复的key,俗称dedup key(deduplicate key之意)。这个key可以用任意可以保证全局唯一性的方式生成,比如uuid。客户端和服务器需要使用这个dedup key作为串联条件,一起解决去重问题。

后端表设计

 订单 order表里 增加dedup_key 字段 并设置成 唯一约束

    create table order(    
      # ...
      dedup_key varchar(60) not null comment 'key to pretend order duplication',
      # ...
      unique uniq_dedup_key(dedup_key)
    );

下单的实现

在实现下单逻辑时,基于该dedup_key实现一个"create-or-get"语义的下单接口——简单说就是

如果带有指定dedup_key的订单已经存在,则直接返回;否则,用该dedup_key下单。

用伪代码表示大概是:

    @Transactional    
    Order createOrder(Integer userId, String prodCode, Decimal amount, String dedupKey) {
      try {
        String orderId = createOrder(userId, prodCode, amount, deupKey); // insert a new order
        Order order = getOrderById(orderId); // read order from db
        order.setDuplicated(false);
        return order;
      } catch(UniqueKeyViolationException e) {
        // if duplicated order has existed
        Order order = getOrderByDedupKey(dedupKey);
        order.setDuplicated(true);
        return order;
      } catch (Exception e) {
        // hanlde other errors and rollback transaction ...
      }
    }

 这时,这段下单代码总是能返回一个订单(除非发生一些DB挂了之类的错误),要么是新创建的,要么就是一个已经存在的单。注意,最好在订单里增加一个属性(比如例子中用“duplicated”)来表示这个订单是这次新生成的,还是因为幂等而直接返回的。这样前端可以有针对性的对这两种情况提示不同的文案。