优化当前彩金池-安全线

This commit is contained in:
2026-03-17 14:22:11 +08:00
parent 216d3ac8fe
commit f6b4fb99f0
7 changed files with 136 additions and 152 deletions

View File

@@ -6,6 +6,7 @@ namespace app\api\controller;
use support\Log;
use support\Request;
use support\Response;
use support\think\Db;
use app\api\logic\GameLogic;
use app\api\logic\PlayStartLogic;
use app\api\util\ReturnCode;
@@ -162,6 +163,11 @@ class GameController extends BaseController
return $this->success([], $msg);
}
$lockName = 'play_start_' . $userId;
$lockResult = Db::query('SELECT GET_LOCK(?, 30) as l', [$lockName]);
if (empty($lockResult) || (int) ($lockResult[0]['l'] ?? 0) !== 1) {
return $this->fail('请求过于频繁,请稍后再试', ReturnCode::BUSINESS_ERROR);
}
try {
$logic = new PlayStartLogic();
$data = $logic->run($userId, (int)$direction);
@@ -245,6 +251,8 @@ class GameController extends BaseController
$msg = '没有原因';
}
return $this->fail('服务超时,' . $msg);
} finally {
Db::execute('SELECT RELEASE_LOCK(?)', [$lockName]);
}
}
}

View File

@@ -70,27 +70,31 @@ class PlayStartLogic
$lotteryService = LotteryService::getOrCreate($playerId);
$ticketType = LotteryService::drawTicketType($paid, $free);
$config = $ticketType === self::LOTTERY_TYPE_PAID
? ($lotteryService->getConfigType0Id() ? DiceLotteryPoolConfig::find($lotteryService->getConfigType0Id()) : null)
: ($lotteryService->getConfigType1Id() ? DiceLotteryPoolConfig::find($lotteryService->getConfigType1Id()) : null);
// 未找到付费/免费对应配置时,统一回退到 type=0 的彩金池
if (!$config) {
$config = DiceLotteryPoolConfig::where('type', 0)->find();
}
if (!$config) {
throw new ApiException('奖池配置不存在');
$configType0 = DiceLotteryPoolConfig::where('type', 0)->find();
$configType1 = DiceLotteryPoolConfig::where('type', 1)->find();
if (!$configType0) {
throw new ApiException('奖池配置不存在(需 type=0');
}
// 杀分时使用 type=1 配置的权重;未杀分时付费用 type=0、免费用 type=1无 type=1 时回退 type=0
$configForWeights = $ticketType === self::LOTTERY_TYPE_PAID
? $configType0
: ($configType1 ?? $configType0);
// 计算当前玩家在该彩金池中的累计盈利金额:当前中奖金额(含 BIGWIN减去抽奖券费用 100
// 玩家累计盈利:仅统计 lottery_config_id=type=0 的成功对局(中奖金额-100*局数)
$playerQuery = DicePlayRecord::where('player_id', $playerId)
->where('lottery_config_id', $config->id)
->where('lottery_config_id', $configType0->id)
->where('status', self::RECORD_STATUS_SUCCESS);
$playerWinSum = (float) $playerQuery->sum('win_coin');
$playerPlayCount = (int) $playerQuery->count();
$playerProfitTotal = $playerWinSum - 100.0 * $playerPlayCount;
$safetyLine = (int) ($config->safety_line ?? 0);
// 玩家累计盈利金额达到或超过安全线时,按奖池 T*_weight 杀分;否则玩家 T*_weight 抽档位
$usePoolWeights = $playerProfitTotal >= $safetyLine;
$safetyLine = (int) ($configType0->safety_line ?? 0);
// 玩家累计盈利>=安全线时杀分:用 type=1 的 T*_weight并记录 lottery_config_id=type=1 的 id;否则玩家权重,记录对应配置 id
$usePoolWeights = $playerProfitTotal >= $safetyLine && $configType1 !== null;
if ($usePoolWeights) {
$config = $configType1;
} else {
$config = $configForWeights;
}
// 按档位 T1-T5 抽取后,从 DiceReward 表按当前方向取该档位数据,再按 weight 抽取一条得到 grid_number
$rewardInstance = DiceReward::getCachedInstance();
@@ -177,6 +181,7 @@ class PlayStartLogic
$record = null;
$configId = (int) $config->id;
$type0ConfigId = (int) $configType0->id;
$rewardId = ($isWin === 1 && $superWinCoin > 0) ? 0 : $targetIndex; // 中豹子不记录原奖励配置 id
$configName = (string) ($config->name ?? '');
$adminId = ($player->admin_id ?? null) ? (int) $player->admin_id : null;
@@ -185,6 +190,7 @@ class PlayStartLogic
$playerId,
$adminId,
$configId,
$type0ConfigId,
$rewardId,
$configName,
$ticketType,
@@ -250,20 +256,17 @@ class PlayStartLogic
$p->save();
// 玩家累计盈利底层仍使用 profit_amount 字段存储:每局按“当前中奖金额(含 BIGWIN - 抽奖券费用 100”累加
// 需确保表有 profit_amount 字段(见 db/dice_lottery_config_add_profit_amount.sql
// 玩家累计盈利累加在 type=0 彩金池上:每局按“当前中奖金额(含 BIGWIN - 抽奖券费用 100”
$perPlayProfit = $winCoin - 100.0;
$addProfit = $perPlayProfit;
try {
DiceLotteryPoolConfig::where('id', $configId)->update([
DiceLotteryPoolConfig::where('id', $type0ConfigId)->update([
'profit_amount' => Db::raw('IFNULL(profit_amount,0) + ' . (float) $addProfit),
]);
} catch (\Throwable $e) {
Log::warning('彩金池盈利累加失败,请确认表 dice_lottery_config 已存在 profit_amount 字段并执行 db/dice_lottery_config_add_profit_amount.sql', [
'config_id' => $configId,
Log::warning('彩金池盈利累加失败', [
'config_id' => $type0ConfigId,
'add_profit' => $addProfit,
'real_ev' => $realEv,
'bigwin_ev' => $bigWinRealEv,
'message' => $e->getMessage(),
]);
}

View File

@@ -31,83 +31,49 @@ class DiceLotteryPoolConfigLogic extends BaseLogic
}
/**
* 获取当前彩金池:从 Redis 读取实例profit_amount 每次从 DB 实时读取(表示玩家在该池子的累计盈利)
* 获取当前彩金池type=0+ 杀分权重为 type=1 的只读展示
* profit_amount 每次从 DB 实时读取t1_weightt5_weight 来自 type=1杀分权重不可在弹窗内修改
*
* @return array{id:int,name:string,safety_line:int,t1_weight:int,t2_weight:int,t3_weight:int,t4_weight:int,t5_weight:int,profit_amount:float}
* @return array{id:int,name:string,safety_line:int,t1_weight:int,...,t5_weight:int,profit_amount:float}
*/
public function getCurrentPool(): array
{
$cached = Cache::get(self::REDIS_KEY_CURRENT_POOL);
if ($cached && is_string($cached)) {
$data = json_decode($cached, true);
if (is_array($data)) {
$config = DiceLotteryPoolConfig::find($data['id'] ?? 0);
$profit = 0.0;
if ($config) {
$profit = isset($config->profit_amount) ? (float) $config->profit_amount : (isset($config->ev) ? (float) $config->ev : 0.0);
} else {
$profit = (float) ($data['profit_amount'] ?? 0);
}
$data['profit_amount'] = $profit;
return $data;
}
}
$config = DiceLotteryPoolConfig::where('type', 0)->find();
if (!$config) {
$configType0 = DiceLotteryPoolConfig::where('type', 0)->find();
if (!$configType0) {
throw new ApiException('未找到 type=0 的奖池配置,请先创建');
}
$row = $config->toArray();
$profitAmount = isset($row['profit_amount']) ? (float) $row['profit_amount'] : (isset($row['ev']) ? (float) $row['ev'] : 0.0);
$configType1 = DiceLotteryPoolConfig::where('type', 1)->find();
$row0 = $configType0->toArray();
$profitAmount = isset($row0['profit_amount']) ? (float) $row0['profit_amount'] : (isset($row0['ev']) ? (float) $row0['ev'] : 0.0);
$pool = [
'id' => (int) $row['id'],
'name' => (string) ($row['name'] ?? ''),
'safety_line' => (int) ($row['safety_line'] ?? 0),
't1_weight' => (int) ($row['t1_weight'] ?? 0),
't2_weight' => (int) ($row['t2_weight'] ?? 0),
't3_weight' => (int) ($row['t3_weight'] ?? 0),
't4_weight' => (int) ($row['t4_weight'] ?? 0),
't5_weight' => (int) ($row['t5_weight'] ?? 0),
'id' => (int) $row0['id'],
'name' => (string) ($row0['name'] ?? ''),
'safety_line' => (int) ($row0['safety_line'] ?? 0),
'profit_amount' => $profitAmount,
];
Cache::set(self::REDIS_KEY_CURRENT_POOL, json_encode($pool), self::EXPIRE);
$row1 = $configType1 ? $configType1->toArray() : [];
$pool['t1_weight'] = (int) ($row1['t1_weight'] ?? 0);
$pool['t2_weight'] = (int) ($row1['t2_weight'] ?? 0);
$pool['t3_weight'] = (int) ($row1['t3_weight'] ?? 0);
$pool['t4_weight'] = (int) ($row1['t4_weight'] ?? 0);
$pool['t5_weight'] = (int) ($row1['t5_weight'] ?? 0);
return $pool;
}
/**
* 更新当前彩金池:仅允许修改 safety_line、t1_weightt5_weight不修改 profit_amount
* 同时更新 Redis 与 DB 中 type=0 的记录
* 更新当前彩金池:仅允许修改 type=0 的 safety_line(杀分权重来自 type=1不可在此接口修改
*
* @param array{safety_line?:int,t1_weight?:int,t2_weight?:int,t3_weight?:int,t4_weight?:int,t5_weight?:int} $data
* @param array{safety_line?:int} $data
*/
public function updateCurrentPool(array $data): void
{
$pool = $this->getCurrentPool();
$id = (int) $pool['id'];
$config = DiceLotteryPoolConfig::find($id);
if (!$config) {
throw new ApiException('奖池配置不存在');
}
$allow = ['safety_line', 't1_weight', 't2_weight', 't3_weight', 't4_weight', 't5_weight'];
$update = [];
foreach ($allow as $k) {
if (array_key_exists($k, $data)) {
if ($k === 'safety_line') {
$update[$k] = (int) $data[$k];
} else {
$update[$k] = max(0, min(100, (int) $data[$k]));
}
}
}
if (empty($update)) {
if (!array_key_exists('safety_line', $data)) {
return;
}
DiceLotteryPoolConfig::where('id', $id)->update($update);
$pool = array_merge($pool, $update);
$refreshed = DiceLotteryPoolConfig::find($id);
$pool['profit_amount'] = $refreshed && (isset($refreshed->profit_amount) || isset($refreshed->ev))
? (float) ($refreshed->profit_amount ?? $refreshed->ev)
: (float) ($pool['profit_amount'] ?? 0);
Cache::set(self::REDIS_KEY_CURRENT_POOL, json_encode($pool), self::EXPIRE);
$safetyLine = (int) $data['safety_line'];
DiceLotteryPoolConfig::where('id', $id)->update(['safety_line' => $safetyLine]);
}
/**