优化一键测试权重

This commit is contained in:
2026-03-13 15:47:12 +08:00
parent f5eaf8da30
commit 0b26afde70
19 changed files with 991 additions and 274 deletions

View File

@@ -71,12 +71,13 @@ class DiceRewardConfigRecordLogic extends BaseLogic
}
/**
* 将测试记录的权重导入weight_config_snapshot → dice_reward(顺时针/逆时针同值tier_weights_snapshot → DiceLotteryPoolConfig,并刷新缓存
* 将测试记录导入DiceReward权重快照、DiceRewardConfigBIGWIN weightDiceLotteryPoolConfig(付费/免费 T1-T5
* @param int $recordId 测试记录 ID
* @param int|null $lotteryConfigId 导入档位权重的奖池配置 ID不传则使用记录中的 lottery_config_id(若有)
* @throws ApiException
* @param int|null $paidLotteryConfigId 导入付费档位概率到的奖池type=0不传则用记录 paid_lottery_config_id
* @param int|null $freeLotteryConfigId 导入免费档位概率到的奖池type=1不传则用记录 free_lottery_config_id
* @param int|null $lotteryConfigId 兼容旧版:不传 paid/free 时用作统一奖池
*/
public function importFromRecord(int $recordId, ?int $lotteryConfigId = null): void
public function importFromRecord(int $recordId, ?int $paidLotteryConfigId = null, ?int $freeLotteryConfigId = null, ?int $lotteryConfigId = null): void
{
$record = $this->model->find($recordId);
if (!$record) {
@@ -96,11 +97,15 @@ class DiceRewardConfigRecordLogic extends BaseLogic
if ($id <= 0) {
continue;
}
$tier = DiceRewardConfig::where('id', $id)->value('tier');
if ($tier === null || $tier === '') {
$tier = isset($item['tier']) ? (string) $item['tier'] : '';
if ($tier === '') {
$tier = DiceRewardConfig::where('id', $id)->value('tier');
$tier = $tier !== null && $tier !== '' ? (string) $tier : '';
}
if ($tier === '') {
continue;
}
$tier = (string) $tier;
// 写入 DiceReward顺/逆时针同值)
foreach ([DiceReward::DIRECTION_CLOCKWISE, DiceReward::DIRECTION_COUNTERCLOCKWISE] as $direction) {
$affected = DiceReward::where('tier', $tier)->where('direction', $direction)->where('end_index', $id)->update(['weight' => $weight]);
if ($affected === 0) {
@@ -112,30 +117,78 @@ class DiceRewardConfigRecordLogic extends BaseLogic
$m->save();
}
}
// BIGWIN同步写入 DiceRewardConfig.weight
if (strtoupper($tier) === 'BIGWIN') {
DiceRewardConfig::where('id', $id)->update(['weight' => $weight]);
}
}
DiceReward::refreshCache();
}
$tiers = ['T1', 'T2', 'T3', 'T4', 'T5'];
$tierSnapshot = $record['tier_weights_snapshot'] ?? null;
if (is_string($tierSnapshot)) {
$tierSnapshot = json_decode($tierSnapshot, true);
}
$targetLotteryId = $lotteryConfigId !== null && $lotteryConfigId > 0
? $lotteryConfigId
: (isset($record['lottery_config_id']) && (int) $record['lottery_config_id'] > 0 ? (int) $record['lottery_config_id'] : null);
if (is_array($tierSnapshot) && !empty($tierSnapshot) && $targetLotteryId > 0) {
$pool = DiceLotteryPoolConfig::find($targetLotteryId);
$paidWeights = $record['paid_tier_weights'] ?? null;
if (is_string($paidWeights)) {
$paidWeights = json_decode($paidWeights, true);
}
$freeWeights = $record['free_tier_weights'] ?? null;
if (is_string($freeWeights)) {
$freeWeights = json_decode($freeWeights, true);
}
$fallbackLotteryId = $lotteryConfigId > 0 ? $lotteryConfigId : (isset($record['lottery_config_id']) && (int) $record['lottery_config_id'] > 0 ? (int) $record['lottery_config_id'] : null);
$paidTargetId = $paidLotteryConfigId > 0 ? $paidLotteryConfigId : ($fallbackLotteryId ?? (isset($record['paid_lottery_config_id']) && (int) $record['paid_lottery_config_id'] > 0 ? (int) $record['paid_lottery_config_id'] : null));
$freeTargetId = $freeLotteryConfigId > 0 ? $freeLotteryConfigId : (isset($record['free_lottery_config_id']) && (int) $record['free_lottery_config_id'] > 0 ? (int) $record['free_lottery_config_id'] : null);
// tier_weights_snapshot 新结构:['paid' => [...], 'free' => [...]]
$snapshotPaid = null;
$snapshotFree = null;
if (is_array($tierSnapshot) && !empty($tierSnapshot)) {
if (array_key_exists('paid', $tierSnapshot) || array_key_exists('free', $tierSnapshot)) {
if (isset($tierSnapshot['paid']) && is_array($tierSnapshot['paid'])) {
$snapshotPaid = $tierSnapshot['paid'];
}
if (isset($tierSnapshot['free']) && is_array($tierSnapshot['free'])) {
$snapshotFree = $tierSnapshot['free'];
}
} else {
// 兼容旧结构:直接就是一个 T1-T5 的数组,视为付费
$snapshotPaid = $tierSnapshot;
}
}
$paidData = is_array($paidWeights) && !empty($paidWeights) ? $paidWeights : $snapshotPaid;
$freeData = is_array($freeWeights) && !empty($freeWeights) ? $freeWeights : $snapshotFree;
if (is_array($paidData) && $paidTargetId > 0) {
$pool = DiceLotteryPoolConfig::find($paidTargetId);
if (!$pool) {
throw new ApiException('奖池配置不存在');
throw new ApiException('付费奖池配置不存在');
}
$update = [
't1_weight' => (int) ($tierSnapshot['T1'] ?? $tierSnapshot['t1'] ?? 0),
't2_weight' => (int) ($tierSnapshot['T2'] ?? $tierSnapshot['t2'] ?? 0),
't3_weight' => (int) ($tierSnapshot['T3'] ?? $tierSnapshot['t3'] ?? 0),
't4_weight' => (int) ($tierSnapshot['T4'] ?? $tierSnapshot['t4'] ?? 0),
't5_weight' => (int) ($tierSnapshot['T5'] ?? $tierSnapshot['t5'] ?? 0),
't1_weight' => (int) ($paidData['T1'] ?? $paidData['t1'] ?? 0),
't2_weight' => (int) ($paidData['T2'] ?? $paidData['t2'] ?? 0),
't3_weight' => (int) ($paidData['T3'] ?? $paidData['t3'] ?? 0),
't4_weight' => (int) ($paidData['T4'] ?? $paidData['t4'] ?? 0),
't5_weight' => (int) ($paidData['T5'] ?? $paidData['t5'] ?? 0),
];
DiceLotteryPoolConfig::where('id', $targetLotteryId)->update($update);
DiceLotteryPoolConfig::where('id', $paidTargetId)->update($update);
}
if (is_array($freeData) && $freeTargetId > 0) {
$pool = DiceLotteryPoolConfig::find($freeTargetId);
if (!$pool) {
throw new ApiException('免费奖池配置不存在');
}
$update = [
't1_weight' => (int) ($freeData['T1'] ?? $freeData['t1'] ?? 0),
't2_weight' => (int) ($freeData['T2'] ?? $freeData['t2'] ?? 0),
't3_weight' => (int) ($freeData['T3'] ?? $freeData['t3'] ?? 0),
't4_weight' => (int) ($freeData['T4'] ?? $freeData['t4'] ?? 0),
't5_weight' => (int) ($freeData['T5'] ?? $freeData['t5'] ?? 0),
];
DiceLotteryPoolConfig::where('id', $freeTargetId)->update($update);
}
DiceRewardConfig::refreshCache();
@@ -143,28 +196,63 @@ class DiceRewardConfigRecordLogic extends BaseLogic
}
/**
* 创建一键测试权重记录并返回 ID供后台执行器写入 dice_play_record_test 并更新进度
* @param int $lotteryConfigId 奖池配置 IDDiceLotteryPoolConfig
* @param int $sCount 顺时针模拟次数 100/500/1000/5000
* @param int $nCount 逆时针模拟次数 100/500/1000/5000
* @param int|null $adminId 执行人
* 创建一键测试权重记录并返回 ID供后台执行器按付费/免费、顺逆方向交替写入 dice_play_record_test
* 支持两种模式1选择奖池配置 lottery_config_id档位概率取自配置2不选配置使用自定义 paid_tier_weights / free_tier_weights
* @param array|int $params 数组lottery_config_id(可选), paid_s_count, paid_n_count, free_s_count, free_n_count或兼容旧版传 4 个 int 时视为 (paid_s_count, paid_n_count, free_s_count, free_n_count)
* @param int|null $adminId 执行人(旧版 4 参调用时第二参为 paid_n_count此处不传 adminId
* @return int 记录 ID
* @throws ApiException
*/
public function createWeightTestRecord(int $lotteryConfigId, int $sCount, int $nCount, ?int $adminId = null): int
public function createWeightTestRecord(array|int $params, mixed $adminIdOrFreeS = null, mixed $freeSOrFreeN = null, mixed $freeN = null): int
{
$adminId = null;
if (!is_array($params)) {
// 兼容旧版调用createWeightTestRecord(paid_s_count, paid_n_count, free_s_count, free_n_count)
$params = [
'paid_s_count' => (int) $params,
'paid_n_count' => (int) $adminIdOrFreeS,
'free_s_count' => (int) $freeSOrFreeN,
'free_n_count' => (int) $freeN,
];
} else {
$adminId = $adminIdOrFreeS !== null && $adminIdOrFreeS !== '' ? (int) $adminIdOrFreeS : null;
}
$allowed = [100, 500, 1000, 5000];
if (!in_array($sCount, $allowed, true) || !in_array($nCount, $allowed, true)) {
throw new ApiException('顺时针/逆时针次数仅支持 100、500、1000、5000');
$lotteryConfigId = isset($params['lottery_config_id']) ? (int) $params['lottery_config_id'] : 0;
$paidConfigId = isset($params['paid_lottery_config_id']) ? (int) $params['paid_lottery_config_id'] : 0;
$freeConfigId = isset($params['free_lottery_config_id']) ? (int) $params['free_lottery_config_id'] : 0;
if ($paidConfigId <= 0 && $lotteryConfigId > 0) {
$paidConfigId = $lotteryConfigId;
}
$config = DiceLotteryPoolConfig::find($lotteryConfigId);
if (!$config) {
throw new ApiException('奖池配置不存在');
if ($freeConfigId <= 0 && $lotteryConfigId > 0) {
$freeConfigId = $lotteryConfigId;
}
$paidS = isset($params['paid_s_count']) ? (int) $params['paid_s_count'] : (int) ($params['s_count'] ?? 0);
$paidN = isset($params['paid_n_count']) ? (int) $params['paid_n_count'] : (int) ($params['n_count'] ?? 0);
$freeS = (int) ($params['free_s_count'] ?? 0);
$freeN = (int) ($params['free_n_count'] ?? 0);
foreach ([$paidS, $paidN, $freeS, $freeN] as $c) {
if ($c !== 0 && !in_array($c, $allowed, true)) {
throw new ApiException('各抽奖次数仅支持 0、100、500、1000、5000');
}
}
$total = $paidS + $paidN + $freeS + $freeN;
if ($total <= 0) {
throw new ApiException('付费或免费至少一种方向次数之和大于 0');
}
$snapshot = [];
// 档位权重快照:区分付费/免费,结构为 ['paid' => [...], 'free' => [...]]
$tierWeightsSnapshot = [
'paid' => null,
'free' => null,
];
$paidTierWeights = null;
$freeTierWeights = null;
$instance = DiceReward::getCachedInstance();
$byTierDirection = $instance['by_tier_direction'] ?? [];
$snapshot = [];
foreach ($byTierDirection as $tier => $byDir) {
foreach ($byDir as $dir => $rows) {
foreach ($rows as $row) {
@@ -177,26 +265,98 @@ class DiceRewardConfigRecordLogic extends BaseLogic
}
}
}
$tierWeightsSnapshot = [
'T1' => (int) ($config->t1_weight ?? 0),
'T2' => (int) ($config->t2_weight ?? 0),
'T3' => (int) ($config->t3_weight ?? 0),
'T4' => (int) ($config->t4_weight ?? 0),
'T5' => (int) ($config->t5_weight ?? 0),
];
$total = $sCount + $nCount;
if ($paidConfigId > 0) {
$config = DiceLotteryPoolConfig::find($paidConfigId);
if (!$config) {
throw new ApiException('付费奖池配置不存在');
}
$tierWeightsSnapshot['paid'] = [
'T1' => (int) ($config->t1_weight ?? 0),
'T2' => (int) ($config->t2_weight ?? 0),
'T3' => (int) ($config->t3_weight ?? 0),
'T4' => (int) ($config->t4_weight ?? 0),
'T5' => (int) ($config->t5_weight ?? 0),
];
} else {
$paidTierWeights = $params['paid_tier_weights'] ?? null;
if (!is_array($paidTierWeights)) {
throw new ApiException('付费未选择奖池配置时请填写付费自定义档位概率T1T5');
}
$tiers = ['T1', 'T2', 'T3', 'T4', 'T5'];
foreach ($tiers as $t) {
$v = (int) ($paidTierWeights[$t] ?? 0);
if ($v < 0 || $v > 100) {
throw new ApiException('付费档位概率每档只能 0-100%');
}
$paidTierWeights[$t] = $v;
}
$paidSum = array_sum(array_intersect_key($paidTierWeights, array_flip($tiers)));
if ($paidSum > 100) {
throw new ApiException('付费档位概率 T1T5 之和不能超过 100%');
}
$tierWeightsSnapshot['paid'] = $paidTierWeights;
}
if ($freeConfigId > 0) {
$config = DiceLotteryPoolConfig::find($freeConfigId);
if (!$config) {
throw new ApiException('免费奖池配置不存在');
}
$tierWeightsSnapshot['free'] = [
'T1' => (int) ($config->t1_weight ?? 0),
'T2' => (int) ($config->t2_weight ?? 0),
'T3' => (int) ($config->t3_weight ?? 0),
'T4' => (int) ($config->t4_weight ?? 0),
'T5' => (int) ($config->t5_weight ?? 0),
];
} else {
$freeTierWeights = $params['free_tier_weights'] ?? null;
if (!is_array($freeTierWeights)) {
throw new ApiException('免费未选择奖池配置时请填写免费自定义档位概率T1T5');
}
$tiers = ['T1', 'T2', 'T3', 'T4', 'T5'];
foreach ($tiers as $t) {
$v = (int) ($freeTierWeights[$t] ?? 0);
if ($v < 0 || $v > 100) {
throw new ApiException('免费档位概率每档只能 0-100%');
}
$freeTierWeights[$t] = $v;
}
$freeSum = array_sum(array_intersect_key($freeTierWeights, array_flip($tiers)));
if ($freeSum > 100) {
throw new ApiException('免费档位概率 T1T5 之和不能超过 100%');
}
$tierWeightsSnapshot['free'] = $freeTierWeights;
}
// 兼容:若某一侧未配置,则保存为空数组,方便前端直接解构
if (!is_array($tierWeightsSnapshot['paid'])) {
$tierWeightsSnapshot['paid'] = [];
}
if (!is_array($tierWeightsSnapshot['free'])) {
$tierWeightsSnapshot['free'] = [];
}
$record = new DiceRewardConfigRecord();
$record->test_count = $total;
$record->weight_config_snapshot = $snapshot;
$record->tier_weights_snapshot = $tierWeightsSnapshot;
$record->lottery_config_id = $lotteryConfigId;
$record->lottery_config_id = $lotteryConfigId > 0 ? $lotteryConfigId : null;
$record->paid_lottery_config_id = $paidConfigId > 0 ? $paidConfigId : null;
$record->free_lottery_config_id = $freeConfigId > 0 ? $freeConfigId : null;
$record->total_play_count = $total;
$record->over_play_count = 0;
$record->status = DiceRewardConfigRecord::STATUS_RUNNING;
$record->remark = null;
$record->s_count = $sCount;
$record->n_count = $nCount;
$record->s_count = $paidS + $paidN;
$record->n_count = $freeS + $freeN;
$record->paid_s_count = $paidS;
$record->paid_n_count = $paidN;
$record->free_s_count = $freeS;
$record->free_n_count = $freeN;
$record->paid_tier_weights = $paidTierWeights;
$record->free_tier_weights = $freeTierWeights;
$record->result_counts = [];
$record->tier_counts = null;
$record->admin_id = $adminId;

View File

@@ -17,7 +17,8 @@ class WeightTestRunner
private const BATCH_SIZE = 10;
/**
* 执行指定测试记录:按 s_count(顺时针)+n_count(逆时针) 模拟,每 10 条写入一次测试表并更新进度
* 执行指定测试记录:按付费/免费、顺/逆方向交替模拟(付费顺→付费逆→免费顺→免费逆),每 10 条写入一次测试表并更新进度
* 支持1lottery_config_id 有值时用奖池配置档位权重2无值时用记录中的 paid_tier_weights / free_tier_weights
* @param int $recordId dice_reward_config_record.id
*/
public function run(int $recordId): void
@@ -28,39 +29,91 @@ class WeightTestRunner
return;
}
$sCount = (int) ($record->s_count ?? 0);
$nCount = (int) ($record->n_count ?? 0);
$total = $sCount + $nCount;
if ($total <= 0) {
$this->markFailed($recordId, 's_count + n_count 必须大于 0');
return;
$paidS = (int) ($record->paid_s_count ?? 0);
$paidN = (int) ($record->paid_n_count ?? 0);
$freeS = (int) ($record->free_s_count ?? 0);
$freeN = (int) ($record->free_n_count ?? 0);
if ($paidS + $paidN + $freeS + $freeN <= 0) {
$sCount = (int) ($record->s_count ?? 0);
$nCount = (int) ($record->n_count ?? 0);
$total = $sCount + $nCount;
if ($total <= 0) {
$this->markFailed($recordId, '抽奖次数必须大于 0');
return;
}
$paidS = $sCount;
$paidN = $nCount;
} else {
$total = $paidS + $paidN + $freeS + $freeN;
}
$configId = (int) ($record->lottery_config_id ?? 0);
$config = $configId > 0 ? DiceLotteryPoolConfig::find($configId) : DiceLotteryPoolConfig::where('type', 0)->find();
if (!$config) {
$this->markFailed($recordId, '奖池配置不存在');
$paidConfigId = (int) ($record->paid_lottery_config_id ?? 0);
$freeConfigId = (int) ($record->free_lottery_config_id ?? 0);
if ($paidConfigId <= 0) {
$paidConfigId = (int) ($record->lottery_config_id ?? 0);
}
if ($freeConfigId <= 0) {
$freeConfigId = (int) ($record->lottery_config_id ?? 0);
}
$paidConfig = $paidConfigId > 0 ? DiceLotteryPoolConfig::find($paidConfigId) : null;
$freeConfig = $freeConfigId > 0 ? DiceLotteryPoolConfig::find($freeConfigId) : null;
if ($paidConfigId > 0 && !$paidConfig) {
$this->markFailed($recordId, '付费奖池配置不存在');
return;
}
if ($freeConfigId > 0 && !$freeConfig) {
$this->markFailed($recordId, '免费奖池配置不存在');
return;
}
$paidTierWeights = null;
$freeTierWeights = null;
if ($paidConfig === null) {
$paidTierWeights = $record->paid_tier_weights;
if (!is_array($paidTierWeights) || $paidTierWeights === []) {
$this->markFailed($recordId, '付费未选奖池时需提供 paid_tier_weights');
return;
}
}
if ($freeConfig === null) {
$freeTierWeights = $record->free_tier_weights;
if (!is_array($freeTierWeights) || $freeTierWeights === []) {
$this->markFailed($recordId, '免费未选奖池时需提供 free_tier_weights');
return;
}
}
$playLogic = new PlayStartLogic();
$resultCounts = []; // grid_number => count
$tierCounts = []; // tier => count
$resultCounts = [];
$tierCounts = [];
$buffer = [];
$done = 0;
try {
for ($i = 0; $i < $sCount; $i++) {
$row = $playLogic->simulateOnePlay($config, 0);
for ($i = 0; $i < $paidS; $i++) {
$row = $playLogic->simulateOnePlay($paidConfig, 0, 0, $paidTierWeights);
$this->aggregate($row, $resultCounts, $tierCounts);
$buffer[] = $this->rowForInsert($row);
$buffer[] = $this->rowForInsert($row, $recordId);
$done++;
$this->flushIfNeeded($buffer, $recordId, $done, $total, $resultCounts, $tierCounts);
}
for ($i = 0; $i < $nCount; $i++) {
$row = $playLogic->simulateOnePlay($config, 1);
for ($i = 0; $i < $paidN; $i++) {
$row = $playLogic->simulateOnePlay($paidConfig, 1, 0, $paidTierWeights);
$this->aggregate($row, $resultCounts, $tierCounts);
$buffer[] = $this->rowForInsert($row);
$buffer[] = $this->rowForInsert($row, $recordId);
$done++;
$this->flushIfNeeded($buffer, $recordId, $done, $total, $resultCounts, $tierCounts);
}
for ($i = 0; $i < $freeS; $i++) {
$row = $playLogic->simulateOnePlay($freeConfig, 0, 1, $freeTierWeights);
$this->aggregate($row, $resultCounts, $tierCounts);
$buffer[] = $this->rowForInsert($row, $recordId);
$done++;
$this->flushIfNeeded($buffer, $recordId, $done, $total, $resultCounts, $tierCounts);
}
for ($i = 0; $i < $freeN; $i++) {
$row = $playLogic->simulateOnePlay($freeConfig, 1, 1, $freeTierWeights);
$this->aggregate($row, $resultCounts, $tierCounts);
$buffer[] = $this->rowForInsert($row, $recordId);
$done++;
$this->flushIfNeeded($buffer, $recordId, $done, $total, $resultCounts, $tierCounts);
}
@@ -68,6 +121,7 @@ class WeightTestRunner
$this->insertBuffer($buffer);
$this->updateProgress($recordId, $done, $resultCounts, $tierCounts);
}
// 平台赚取金额:通过关联 DicePlayRecordTestreward_config_record_id统计
$this->markSuccess($recordId, $resultCounts, $tierCounts);
} catch (\Throwable $e) {
Log::error('WeightTestRunner exception: ' . $e->getMessage(), ['record_id' => $recordId, 'trace' => $e->getTraceAsString()]);
@@ -87,9 +141,11 @@ class WeightTestRunner
}
}
private function rowForInsert(array $row): array
private function rowForInsert(array $row, int $rewardConfigRecordId): array
{
$out = [];
$out = [
'reward_config_record_id' => $rewardConfigRecordId,
];
$keys = [
'player_id', 'admin_id', 'lottery_config_id', 'lottery_type', 'is_win', 'win_coin',
'super_win_coin', 'reward_win_coin', 'use_coins', 'direction', 'reward_config_id',
@@ -134,14 +190,20 @@ class WeightTestRunner
}
}
/**
* 标记测试成功并记录平台总盈利 platform_profit
* 通过关联 DicePlayRecordTestreward_config_record_id统计付费(lottery_type=0)次数×100 - win_coin 求和
*/
private function markSuccess(int $recordId, array $resultCounts, array $tierCounts): void
{
$platformProfit = DiceRewardConfigRecord::computePlatformProfitFromRelated($recordId);
$record = DiceRewardConfigRecord::find($recordId);
if ($record) {
$record->status = DiceRewardConfigRecord::STATUS_SUCCESS;
$record->result_counts = $resultCounts;
$record->tier_counts = $tierCounts;
$record->remark = null;
$record->platform_profit = $platformProfit;
$record->save();
}
}
@@ -149,8 +211,9 @@ class WeightTestRunner
private function markFailed(int $recordId, string $message): void
{
DiceRewardConfigRecord::where('id', $recordId)->update([
'status' => DiceRewardConfigRecord::STATUS_FAIL,
'remark' => mb_substr($message, 0, 500),
'status' => DiceRewardConfigRecord::STATUS_FAIL,
'remark' => mb_substr($message, 0, 500),
'platform_profit' => null,
]);
}
}