1.增加互斥锁:保证缓存和数据库数据一致性

2.增加消费队列,保证mysql数据的正常保存
This commit is contained in:
2026-04-20 14:13:48 +08:00
parent 614fb00ec4
commit 1eed3cf0f7
23 changed files with 836 additions and 255 deletions

View File

@@ -4,6 +4,8 @@ namespace app\admin\controller\user;
use Throwable;
use app\common\controller\Backend;
use app\common\service\GameHotDataCoordinator;
use app\common\service\GameHotDataRedis;
use support\think\Db;
use support\Response;
use Webman\Http\Request as WebmanRequest;
@@ -214,8 +216,8 @@ class User extends Backend
}
$userIdRaw = $request->post('user_id');
$userId = is_numeric(strval($userIdRaw)) ? intval(strval($userIdRaw)) : 0;
if ($userId <= 0) {
$userId = filter_var($userIdRaw, FILTER_VALIDATE_INT);
if ($userId === false || $userId <= 0) {
return $this->error(__('Parameter error'));
}
@@ -243,69 +245,93 @@ class User extends Backend
$remark = '后台管理员(' . $adminName . '' . $actionText . $amountForRemark . '(值)';
}
$user = $this->model->where('id', $userId)->find();
if (!$user) {
return $this->error(__('Record not found'));
}
$dataLimitAdminIds = $this->getDataLimitAdminIds();
if ($dataLimitAdminIds && !in_array($user[$this->dataLimitField], $dataLimitAdminIds)) {
return $this->error(__('You have no permission'));
$lock = GameHotDataRedis::userAdminMutationLockTry($userId);
if (!$lock['acquired']) {
return $this->error('该用户正在被其他管理员操作(钱包/并发保存),请稍后再试');
}
$channelIdRaw = $user['channel_id'] ?? null;
$channelId = is_numeric(strval($channelIdRaw)) ? intval(strval($channelIdRaw)) : null;
$before = strval($user['coin'] ?? '0');
$delta = self::normalizeAmountScale($amountText, 4);
if ($op === 'credit') {
$after = bcadd($before, $delta, 4);
$bizType = 'admin_credit';
$direction = 1;
} else {
if (bccomp($before, $delta, 4) < 0) {
return $this->error('余额不足,扣点失败');
}
$after = bcsub($before, $delta, 4);
$bizType = 'admin_deduct';
$direction = 2;
}
$now = time();
$idem = 'admin_adjust_' . $userId . '_' . $this->auth->id . '_' . $now . '_' . random_int(1000, 9999);
Db::startTrans();
try {
Db::name('user')->where('id', $userId)->update([
'coin' => $after,
'update_time' => $now,
]);
$user = $this->model->where('id', $userId)->find();
if (!$user) {
return $this->error(__('Record not found'));
}
$dataLimitAdminIds = $this->getDataLimitAdminIds();
if ($dataLimitAdminIds && !in_array($user[$this->dataLimitField], $dataLimitAdminIds)) {
return $this->error(__('You have no permission'));
}
Db::name('user_wallet_record')->insert([
$channelIdRaw = $user['channel_id'] ?? null;
$channelId = filter_var($channelIdRaw, FILTER_VALIDATE_INT);
if ($channelId === false) {
$channelId = null;
}
$before = strval($user['coin'] ?? '0');
$delta = self::normalizeAmountScale($amountText, 4);
if ($op === 'credit') {
$after = bcadd($before, $delta, 4);
$bizType = 'admin_credit';
$direction = 1;
} else {
if (bccomp($before, $delta, 4) < 0) {
return $this->error('余额不足,扣点失败');
}
$after = bcsub($before, $delta, 4);
$bizType = 'admin_deduct';
$direction = 2;
}
$now = time();
$idem = 'admin_adjust_' . $userId . '_' . $this->auth->id . '_' . $now . '_' . random_int(1000, 9999);
$operatorId = filter_var($this->auth->id, FILTER_VALIDATE_INT);
if ($operatorId === false) {
$operatorId = 0;
}
Db::startTrans();
try {
$affected = Db::name('user')->where('id', $userId)->where('coin', $before)->update([
'coin' => $after,
'update_time' => $now,
]);
if ($affected !== 1) {
Db::rollback();
return $this->error('保存失败:该用户余额已被其他请求修改(如下注、派彩或其他管理员已保存),请刷新后重试');
}
Db::name('user_wallet_record')->insert([
'user_id' => $userId,
'channel_id' => $channelId,
'biz_type' => $bizType,
'direction' => $direction,
'amount' => $delta,
'balance_before' => $before,
'balance_after' => $after,
'ref_type' => 'admin_user_wallet_adjust',
'ref_id' => null,
'idempotency_key' => $idem,
'operator_admin_id' => $operatorId,
'remark' => substr($remark, 0, 500),
'create_time' => $now,
]);
Db::commit();
} catch (Throwable $e) {
Db::rollback();
return $this->error($e->getMessage());
}
GameHotDataCoordinator::afterUserCommitted($userId);
return $this->success('钱包调整成功', [
'user_id' => $userId,
'channel_id' => $channelId,
'biz_type' => $bizType,
'direction' => $direction,
'amount' => $delta,
'balance_before' => $before,
'balance_after' => $after,
'ref_type' => 'admin_user_wallet_adjust',
'ref_id' => null,
'idempotency_key' => $idem,
'operator_admin_id' => intval(strval($this->auth->id)),
'remark' => substr($remark, 0, 500),
'create_time' => $now,
'coin_before' => self::formatAmountForDisplay($before),
'coin_after' => self::formatAmountForDisplay($after),
'amount' => self::formatAmountForDisplay($delta),
'op' => $op,
]);
Db::commit();
} catch (Throwable $e) {
Db::rollback();
return $this->error($e->getMessage());
} finally {
GameHotDataRedis::userAdminMutationLockRelease($userId, $lock['token'], $lock['redis_lock']);
}
return $this->success('钱包调整成功', [
'user_id' => $userId,
'coin_before' => self::formatAmountForDisplay($before),
'coin_after' => self::formatAmountForDisplay($after),
'amount' => self::formatAmountForDisplay($delta),
'op' => $op,
]);
}
private static function normalizeAmountScale(string $amount, int $scale): string