Selaa lähdekoodia

新增原子扣费逻辑

lh 1 kuukausi sitten
vanhempi
commit
c76bc01039
1 muutettua tiedostoa jossa 26 lisäystä ja 2 poistoa
  1. 26 2
      app/Services/PointsService.php

+ 26 - 2
app/Services/PointsService.php

@@ -692,9 +692,10 @@ class PointsService
             }
 
             if (!$isTestUser) {
-                // 更新用户积分总额
+                // 原子扣减积分:基于数据库当前值执行(points = points - X),
+                // 避免并发处理不同任务时基于旧快照覆盖写入导致丢失更新(少扣)
                 DB::table('mp_manage_users')->where('id', $uid)->update([
-                    'points' => $pointsAfter,
+                    'points' => DB::raw('points - ' . (float)$points),
                     'updated_at' => date('Y-m-d H:i:s')
                 ]);
             }
@@ -740,6 +741,12 @@ class PointsService
 
         } catch (\Exception $e) {
             DB::rollBack();
+
+            // 唯一索引冲突:同一任务已被其他请求计费,视为已计费(并发验重兜底,避免误报计费失败)
+            if ($this->isDuplicateCharge($e)) {
+                return ['charged' => false, 'reason' => 'already_charged', 'task_id' => $taskId];
+            }
+
             dLog('points')->error($type . '计费失败: ' . $e->getMessage(), ['task_id' => $taskId]);
             logDB('points', 'error', $type . '计费失败', [
                 'task_id' => $taskId,
@@ -748,4 +755,21 @@ class PointsService
             return ['charged' => false, 'reason' => 'exception: ' . $e->getMessage(), 'task_id' => $taskId];
         }
     }
+
+    /**
+     * 判断异常是否为数据库唯一键冲突(重复计费)
+     *
+     * @param \Exception $e
+     * @return bool
+     */
+    private function isDuplicateCharge(\Exception $e): bool
+    {
+        // MySQL 重复键:SQLSTATE 23000 / 错误码 1062(Duplicate entry)
+        $code = $e->getCode();
+        if ($code === 23000 || $code === '23000' || $code === 1062 || $code === '1062') {
+            return true;
+        }
+
+        return mb_strpos($e->getMessage(), 'Duplicate entry') !== false;
+    }
 }