1.增加互斥锁:保证缓存和数据库数据一致性
2.增加消费队列,保证mysql数据的正常保存
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user