Laravel中按状态统计物品并在更新时调整快递员计数器
Laravel Observer 更新状态计数器时获取旧状态的解决方案
问题根源
你在updated方法里的逻辑有误:虽然getOriginal('status')确实能拿到更新前的旧状态,但你两次调用update时都用了新状态$item->status作为字段名,导致旧状态的递减操作根本没作用。另外,getOriginal()在单次updated事件中返回的就是本次更新前的字段值,不是模型创建时的初始值,这点可以放心。
正确的Observer实现
修复updated方法
public function updated(Item $item) { $oldStatus = $item->getOriginal('status'); $newStatus = $item->status; // 状态未变化时直接返回,避免无效操作 if ($oldStatus === $newStatus) { return; } // 找到对应快递员,用findOrFail避免空模型问题 $courier = Courier::findOrFail($item->courier_id); // 递减旧状态计数,递增新状态计数 $courier->{$oldStatus}--; $courier->{$newStatus}++; // 一次性保存,减少数据库交互次数 $courier->save(); }
修正created方法的逻辑问题
你的created方法中创建Item后立刻更新status为delivered,这会触发updated事件,导致计数器被重复累加。建议直接在创建Item时指定正确的status,或者调整逻辑:
public function created(Item $item) { $courier = Courier::findOrFail($item->courier_id); // 使用创建时的原始status,而非修改后的值 $courier->{$item->getOriginal('status')}++; $courier->save(); // 如果需要设置为delivered,建议在创建时就传入该值,比如: // Item::create(['courier_id' => $courierId, 'status' => 'delivered']); // 这样就不需要后续的update操作,避免触发updated事件 }
性能优化建议
- 你担心实时统计的性能问题,用Observer维护计数器的方案是最优的之一:每次状态变更仅需1次查询+1次更新,远优于每次展示时执行
COUNT(*)聚合查询(尤其是数据量大时)。 - 高并发场景下,建议使用
lockForUpdate避免竞态条件:$courier = Courier::lockForUpdate()->findOrFail($item->courier_id); - 若数据量极大,可将计数器缓存到Redis中,展示时直接读取缓存,定时同步到数据库,进一步降低数据库压力。
内容的提问来源于stack exchange,提问作者Азимия
相关产品推荐
相关产品推荐

