You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel开发货运司机APP:如何避免多人同时接单重复扣款?

Laravel实现订单唯一接单逻辑的解决方案

你当前开发货运司机应用时遇到的核心问题是:多名司机同时接同一订单,导致双方账户都被扣款。本质是没有实现订单级别的接单互斥逻辑,下面针对问题给出具体解决方案,并分析你现有代码的问题。

现有代码的问题

你写的缓存键包含了司机ID($dm->id),这意味着每个司机的缓存标识都是独立的——司机A和司机B的缓存键完全不一样,双方都能成功写入缓存,根本无法阻止其他人接单,这就是问题根源。

正确实现方案

方案一:基于缓存的原子锁(轻量高效)

利用Laravel缓存的add原子方法,只有当订单锁不存在时才会添加成功,直接避免并发竞态:

$cacheKey = "order_lock_" . $request['order_id'];

// 尝试添加缓存锁,有效期设为5分钟(足够完成接单+扣款流程)
if (!Cache::add($cacheKey, true, 300)) {
    return response()->json([
        'errors' => [
            ['code' => 'order', 'message' => '该订单已被其他司机抢先接单!']
        ]
    ], 203);
}

// 走到这里说明当前司机拿到了接单权限,执行后续逻辑
try {
    // 先二次校验订单状态(防止缓存过期后重复处理)
    $order = Order::findOrFail($request['order_id']);
    if ($order->status !== 'pending') {
        Cache::forget($cacheKey);
        return response()->json([
            'errors' => [
                ['code' => 'order', 'message' => '订单状态已变更,无法接单!']
            ]
        ], 203);
    }

    // 更新订单的司机ID和状态
    $order->update([
        'shipper_id' => $dm->id,
        'status' => 'accepted'
    ]);

    // 执行账户扣款逻辑(这里写你的扣款代码)
    // ...

    // 接单成功后释放锁,或者让它自动过期
    Cache::forget($cacheKey);

    return response()->json(['message' => '接单成功!']);
} catch (\Exception $e) {
    // 出现异常必须释放锁,避免死锁
    Cache::forget($cacheKey);
    throw $e;
}

方案二:数据库事务+行锁(强一致性)

如果需要更高的数据可靠性,避免缓存失效带来的问题,可以用数据库行级锁配合事务:

DB::beginTransaction();
try {
    // 使用lockForUpdate()锁定订单行,其他请求会等待锁释放
    $order = Order::where('id', $request['order_id'])
                  ->where('status', 'pending')
                  ->lockForUpdate()
                  ->first();

    if (!$order) {
        DB::rollBack();
        return response()->json([
            'errors' => [
                ['code' => 'order', 'message' => '该订单已被其他司机抢先接单!']
            ]
        ], 203);
    }

    // 更新订单信息
    $order->update([
        'shipper_id' => $dm->id,
        'status' => 'accepted'
    ]);

    // 执行扣款逻辑
    // ...

    DB::commit();
    return response()->json(['message' => '接单成功!']);
} catch (\Exception $e) {
    DB::rollBack();
    throw $e;
}

关键注意点

  • 缓存锁必须以订单ID为唯一标识,不能包含司机ID;
  • 无论用哪种方案,都要在接单前二次校验订单状态(防止缓存过期或锁释放后重复操作);
  • 异常场景下一定要释放锁或回滚事务,避免死锁。

内容的提问来源于stack exchange,提问作者Hoang Nam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 19:31:11