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