1.增加互斥锁:保证缓存和数据库数据一致性
2.增加消费队列,保证mysql数据的正常保存
This commit is contained in:
@@ -4,7 +4,8 @@ namespace app\admin\controller\config;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use app\common\library\game\DepositTier as DepositTierLib;
|
||||
use app\common\service\GameHotDataRedis;
|
||||
use app\common\service\GameHotDataCoordinator;
|
||||
use app\common\service\GameHotDataLock;
|
||||
use InvalidArgumentException;
|
||||
use support\think\Db;
|
||||
use support\Response;
|
||||
@@ -104,30 +105,39 @@ class DepositTier extends Backend
|
||||
}
|
||||
|
||||
$now = time();
|
||||
try {
|
||||
$exists = Db::name('game_config')->where('config_key', DepositTierLib::CONFIG_KEY)->find();
|
||||
if ($exists) {
|
||||
Db::name('game_config')->where('config_key', DepositTierLib::CONFIG_KEY)->update([
|
||||
'config_value' => $json,
|
||||
'value_type' => 'json',
|
||||
'update_time' => $now,
|
||||
]);
|
||||
} else {
|
||||
Db::name('game_config')->insert([
|
||||
'config_key' => DepositTierLib::CONFIG_KEY,
|
||||
'config_value' => $json,
|
||||
'value_type' => 'json',
|
||||
'remark' => '充值档位 JSON 数组(独立表单维护)',
|
||||
'create_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
$resourceKey = GameHotDataLock::safeResourceKeyForConfig(DepositTierLib::CONFIG_KEY);
|
||||
$lock = GameHotDataLock::tryAcquire(GameHotDataLock::TYPE_GAME_CONFIG, $resourceKey);
|
||||
if (!$lock['acquired']) {
|
||||
return $this->error('该配置正在被其他操作占用,请稍后再试');
|
||||
}
|
||||
try {
|
||||
try {
|
||||
$exists = Db::name('game_config')->where('config_key', DepositTierLib::CONFIG_KEY)->find();
|
||||
if ($exists) {
|
||||
Db::name('game_config')->where('config_key', DepositTierLib::CONFIG_KEY)->update([
|
||||
'config_value' => $json,
|
||||
'value_type' => 'json',
|
||||
'update_time' => $now,
|
||||
]);
|
||||
} else {
|
||||
Db::name('game_config')->insert([
|
||||
'config_key' => DepositTierLib::CONFIG_KEY,
|
||||
'config_value' => $json,
|
||||
'value_type' => 'json',
|
||||
'remark' => '充值档位 JSON 数组(独立表单维护)',
|
||||
'create_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
|
||||
GameHotDataRedis::gameConfigForget(DepositTierLib::CONFIG_KEY);
|
||||
GameHotDataCoordinator::afterGameConfigKeyCommitted(DepositTierLib::CONFIG_KEY);
|
||||
|
||||
return $this->success(__('Saved successfully'));
|
||||
return $this->success(__('Saved successfully'));
|
||||
} finally {
|
||||
GameHotDataLock::release(GameHotDataLock::TYPE_GAME_CONFIG, $resourceKey, $lock['token'], $lock['redis_lock']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace app\admin\controller\config;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use app\common\library\game\StreakWinReward as StreakWinRewardLib;
|
||||
use app\common\service\GameHotDataRedis;
|
||||
use app\common\service\GameHotDataCoordinator;
|
||||
use app\common\service\GameHotDataLock;
|
||||
use support\think\Db;
|
||||
use support\Response;
|
||||
use Throwable;
|
||||
@@ -91,33 +92,42 @@ class StreakWinReward extends Backend
|
||||
}
|
||||
$encoded = StreakWinRewardLib::encodeForDb($payload);
|
||||
$now = time();
|
||||
Db::startTrans();
|
||||
try {
|
||||
$exists = Db::name('game_config')->where('config_key', StreakWinRewardLib::CONFIG_KEY)->find();
|
||||
if ($exists) {
|
||||
Db::name('game_config')->where('config_key', StreakWinRewardLib::CONFIG_KEY)->update([
|
||||
'config_value' => $encoded,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
} else {
|
||||
Db::name('game_config')->insert([
|
||||
'config_key' => StreakWinRewardLib::CONFIG_KEY,
|
||||
'config_value' => $encoded,
|
||||
'value_type' => 'json',
|
||||
'remark' => '连胜奖励',
|
||||
'create_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
}
|
||||
Db::commit();
|
||||
} catch (Throwable $e) {
|
||||
Db::rollback();
|
||||
|
||||
return $this->error($e->getMessage());
|
||||
$resourceKey = GameHotDataLock::safeResourceKeyForConfig(StreakWinRewardLib::CONFIG_KEY);
|
||||
$lock = GameHotDataLock::tryAcquire(GameHotDataLock::TYPE_GAME_CONFIG, $resourceKey);
|
||||
if (!$lock['acquired']) {
|
||||
return $this->error('该配置正在被其他操作占用,请稍后再试');
|
||||
}
|
||||
StreakWinRewardLib::clearCache();
|
||||
GameHotDataRedis::gameConfigForget(StreakWinRewardLib::CONFIG_KEY);
|
||||
try {
|
||||
Db::startTrans();
|
||||
try {
|
||||
$exists = Db::name('game_config')->where('config_key', StreakWinRewardLib::CONFIG_KEY)->find();
|
||||
if ($exists) {
|
||||
Db::name('game_config')->where('config_key', StreakWinRewardLib::CONFIG_KEY)->update([
|
||||
'config_value' => $encoded,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
} else {
|
||||
Db::name('game_config')->insert([
|
||||
'config_key' => StreakWinRewardLib::CONFIG_KEY,
|
||||
'config_value' => $encoded,
|
||||
'value_type' => 'json',
|
||||
'remark' => '连胜奖励',
|
||||
'create_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
}
|
||||
Db::commit();
|
||||
} catch (Throwable $e) {
|
||||
Db::rollback();
|
||||
|
||||
return $this->success('保存成功');
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
StreakWinRewardLib::clearCache();
|
||||
GameHotDataCoordinator::afterGameConfigKeyCommitted(StreakWinRewardLib::CONFIG_KEY);
|
||||
|
||||
return $this->success('保存成功');
|
||||
} finally {
|
||||
GameHotDataLock::release(GameHotDataLock::TYPE_GAME_CONFIG, $resourceKey, $lock['token'], $lock['redis_lock']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
namespace app\admin\controller\config;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use app\common\service\GameHotDataRedis;
|
||||
use app\common\service\GameHotDataCoordinator;
|
||||
use app\common\service\GameHotDataLock;
|
||||
use app\common\library\game\ZiHuaDictionary as ZiHuaDictionaryLib;
|
||||
use InvalidArgumentException;
|
||||
use support\think\Db;
|
||||
@@ -104,30 +105,39 @@ class ZiHuaDictionary extends Backend
|
||||
}
|
||||
|
||||
$now = time();
|
||||
try {
|
||||
$exists = Db::name('game_config')->where('config_key', ZiHuaDictionaryLib::CONFIG_KEY)->find();
|
||||
if ($exists) {
|
||||
Db::name('game_config')->where('config_key', ZiHuaDictionaryLib::CONFIG_KEY)->update([
|
||||
'config_value' => $json,
|
||||
'value_type' => 'json',
|
||||
'update_time' => $now,
|
||||
]);
|
||||
} else {
|
||||
Db::name('game_config')->insert([
|
||||
'config_key' => ZiHuaDictionaryLib::CONFIG_KEY,
|
||||
'config_value' => $json,
|
||||
'value_type' => 'json',
|
||||
'remark' => '36字花字典 JSON 数组(独立表单维护)',
|
||||
'create_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
$resourceKey = GameHotDataLock::safeResourceKeyForConfig(ZiHuaDictionaryLib::CONFIG_KEY);
|
||||
$lock = GameHotDataLock::tryAcquire(GameHotDataLock::TYPE_GAME_CONFIG, $resourceKey);
|
||||
if (!$lock['acquired']) {
|
||||
return $this->error('该配置正在被其他操作占用,请稍后再试');
|
||||
}
|
||||
try {
|
||||
try {
|
||||
$exists = Db::name('game_config')->where('config_key', ZiHuaDictionaryLib::CONFIG_KEY)->find();
|
||||
if ($exists) {
|
||||
Db::name('game_config')->where('config_key', ZiHuaDictionaryLib::CONFIG_KEY)->update([
|
||||
'config_value' => $json,
|
||||
'value_type' => 'json',
|
||||
'update_time' => $now,
|
||||
]);
|
||||
} else {
|
||||
Db::name('game_config')->insert([
|
||||
'config_key' => ZiHuaDictionaryLib::CONFIG_KEY,
|
||||
'config_value' => $json,
|
||||
'value_type' => 'json',
|
||||
'remark' => '36字花字典 JSON 数组(独立表单维护)',
|
||||
'create_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
|
||||
GameHotDataRedis::gameConfigForget(ZiHuaDictionaryLib::CONFIG_KEY);
|
||||
GameHotDataCoordinator::afterGameConfigKeyCommitted(ZiHuaDictionaryLib::CONFIG_KEY);
|
||||
|
||||
return $this->success(__('Saved successfully'));
|
||||
return $this->success(__('Saved successfully'));
|
||||
} finally {
|
||||
GameHotDataLock::release(GameHotDataLock::TYPE_GAME_CONFIG, $resourceKey, $lock['token'], $lock['redis_lock']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ namespace app\admin\controller\game;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use app\common\library\game\ZiHuaDictionary as ZiHuaDictionaryLib;
|
||||
use app\common\service\GameHotDataRedis;
|
||||
use app\common\service\GameHotDataCoordinator;
|
||||
use app\common\service\GameHotDataLock;
|
||||
use InvalidArgumentException;
|
||||
use support\think\Db;
|
||||
use support\Response;
|
||||
@@ -90,30 +91,39 @@ class ZiHuaDictionary extends Backend
|
||||
}
|
||||
|
||||
$now = time();
|
||||
try {
|
||||
$exists = Db::name('game_config')->where('config_key', ZiHuaDictionaryLib::CONFIG_KEY)->find();
|
||||
if ($exists) {
|
||||
Db::name('game_config')->where('config_key', ZiHuaDictionaryLib::CONFIG_KEY)->update([
|
||||
'config_value' => $json,
|
||||
'value_type' => 'json',
|
||||
'update_time' => $now,
|
||||
]);
|
||||
} else {
|
||||
Db::name('game_config')->insert([
|
||||
'config_key' => ZiHuaDictionaryLib::CONFIG_KEY,
|
||||
'config_value' => $json,
|
||||
'value_type' => 'json',
|
||||
'remark' => '36字花字典 JSON 数组(独立表单维护)',
|
||||
'create_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
$resourceKey = GameHotDataLock::safeResourceKeyForConfig(ZiHuaDictionaryLib::CONFIG_KEY);
|
||||
$lock = GameHotDataLock::tryAcquire(GameHotDataLock::TYPE_GAME_CONFIG, $resourceKey);
|
||||
if (!$lock['acquired']) {
|
||||
return $this->error('该配置正在被其他操作占用,请稍后再试');
|
||||
}
|
||||
try {
|
||||
try {
|
||||
$exists = Db::name('game_config')->where('config_key', ZiHuaDictionaryLib::CONFIG_KEY)->find();
|
||||
if ($exists) {
|
||||
Db::name('game_config')->where('config_key', ZiHuaDictionaryLib::CONFIG_KEY)->update([
|
||||
'config_value' => $json,
|
||||
'value_type' => 'json',
|
||||
'update_time' => $now,
|
||||
]);
|
||||
} else {
|
||||
Db::name('game_config')->insert([
|
||||
'config_key' => ZiHuaDictionaryLib::CONFIG_KEY,
|
||||
'config_value' => $json,
|
||||
'value_type' => 'json',
|
||||
'remark' => '36字花字典 JSON 数组(独立表单维护)',
|
||||
'create_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
|
||||
GameHotDataRedis::gameConfigForget(ZiHuaDictionaryLib::CONFIG_KEY);
|
||||
GameHotDataCoordinator::afterGameConfigKeyCommitted(ZiHuaDictionaryLib::CONFIG_KEY);
|
||||
|
||||
return $this->success(__('Saved successfully'));
|
||||
return $this->success(__('Saved successfully'));
|
||||
} finally {
|
||||
GameHotDataLock::release(GameHotDataLock::TYPE_GAME_CONFIG, $resourceKey, $lock['token'], $lock['redis_lock']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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