model = new DiceRewardConfig(); } /** * 新增:保存后刷新缓存(权重已迁移至 dice_reward 表) */ public function add(array $data): mixed { $result = parent::add($data); DiceRewardConfig::refreshCache(); return $result; } /** * 修改:保存后刷新缓存;BIGWIN 的 weight 直接写入 dice_reward_config 表,抽奖时从 Config 读取 */ public function edit($id, array $data): mixed { $result = parent::edit($id, $data); DiceRewardConfig::refreshCache(); return $result; } /** * 为列表/分页数据中的 BIGWIN 行附加 weight(来自 DiceReward 缓存) */ public function enrichBigwinWeight(array $listResult): array { $key = isset($listResult['data']) ? 'data' : (isset($listResult['records']) ? 'records' : null); if ($key === null || empty($listResult[$key])) { return $listResult; } $rewardLogic = new DiceRewardLogic(); foreach ($listResult[$key] as $i => $row) { if (isset($row['tier']) && $row['tier'] === 'BIGWIN' && isset($row['grid_number'])) { $listResult[$key][$i]['weight'] = $rewardLogic->getBigwinWeightByGridNumber((int) $row['grid_number']); } } return $listResult; } /** * 删除后刷新缓存 */ public function destroy($ids): bool { $result = parent::destroy($ids); if ($result) { DiceRewardConfig::refreshCache(); } return $result; } /** * 按档位分组返回奖励配置列表(仅配置,权重在 dice_reward 表;权重配比请用 DiceRewardLogic::getListGroupedByTierWithDirection) */ public function getListGroupedByTier(): array { $tiers = ['T1', 'T2', 'T3', 'T4', 'T5', 'BIGWIN']; $list = $this->model->whereIn('tier', $tiers)->order('tier')->order('id')->select()->toArray(); $grouped = []; foreach ($tiers as $t) { $grouped[$t] = []; } foreach ($list as $row) { $tier = isset($row['tier']) ? (string) $row['tier'] : ''; if ($tier !== '' && isset($grouped[$tier])) { $grouped[$tier][] = $row; } } return $grouped; } /** 测试时档位权重均为 0 的异常标识 */ private const EXCEPTION_WEIGHT_ALL_ZERO = 'REWARD_WEIGHT_ALL_ZERO'; /** * 按权重抽取一条配置(与 PlayStartLogic 抽奖逻辑一致,仅 weight>0 参与) */ private static function drawRewardByWeight(array $rewards): array { if (empty($rewards)) { throw new \InvalidArgumentException('rewards 不能为空'); } $candidateWeights = []; foreach ($rewards as $i => $row) { $w = isset($row['weight']) ? (float) $row['weight'] : 0.0; if ($w > 0) { $candidateWeights[$i] = $w; } } $total = (float) array_sum($candidateWeights); if ($total > 0) { $r = (random_int(0, PHP_INT_MAX - 1) / (float) PHP_INT_MAX) * $total; $acc = 0.0; foreach ($candidateWeights as $i => $w) { $acc += $w; if ($r < $acc) { return $rewards[$i]; } } return $rewards[array_key_last($candidateWeights)]; } throw new \RuntimeException(self::EXCEPTION_WEIGHT_ALL_ZERO); } /** * 按档位权重数组抽取 T1-T5 */ private static function drawTierByWeightArray(array $tiers, array $weights): string { $total = array_sum($weights); if ($total <= 0) { return $tiers[random_int(0, count($tiers) - 1)]; } $r = random_int(1, (int) $total); $acc = 0; foreach ($weights as $i => $w) { $acc += (int) $w; if ($r <= $acc) { return $tiers[$i]; } } return $tiers[count($tiers) - 1]; } /** * 运行权重配比测试:仅按当前配置在内存中模拟 N 次抽奖,统计各 grid_number 落点数量。 * 不创建任何游玩记录(DicePlayRecord)、不扣券、不写钱包,仅用于验证权重配比效果。 * * @param int $testCount 测试次数 100/500/1000/5000/10000 * @param bool $saveRecord 是否保存到 dice_reward_config_record(测试记录表,非游玩记录) * @param int|null $adminId 执行人管理员ID * @param int|null $lotteryConfigId 奖池配置ID(DiceLotteryPoolConfig),用于设定 T1-T5 档位概率;不传则使用 type=0 的配置或均等 * @return array{counts: array, record_id: int|null} counts 为 grid_number=>出现次数 */ public function runWeightTest(int $testCount, bool $saveRecord = true, ?int $adminId = null, ?int $lotteryConfigId = null): array { $allowedCounts = [100, 500, 1000, 5000, 10000]; if (!in_array($testCount, $allowedCounts, true)) { throw new ApiException('测试次数仅支持 100、500、1000、5000、10000'); } $grouped = []; foreach (['T1', 'T2', 'T3', 'T4', 'T5', 'BIGWIN'] as $t) { $grouped[$t] = $this->model::getCachedByTierForDirection($t, 0); } $tiers = ['T1', 'T2', 'T3', 'T4', 'T5']; $tierWeights = [1, 1, 1, 1, 1]; $config = null; if ($lotteryConfigId !== null && $lotteryConfigId > 0) { $config = DiceLotteryPoolConfig::find($lotteryConfigId); } if (!$config) { $config = DiceLotteryPoolConfig::where('type', 0)->find(); } if ($config) { $tierWeights = [ (int) ($config->t1_weight ?? 0), (int) ($config->t2_weight ?? 0), (int) ($config->t3_weight ?? 0), (int) ($config->t4_weight ?? 0), (int) ($config->t5_weight ?? 0), ]; if (array_sum($tierWeights) <= 0) { $tierWeights = [1, 1, 1, 1, 1]; } } $counts = []; $maxRetry = 20; for ($i = 0; $i < $testCount; $i++) { $tier = self::drawTierByWeightArray($tiers, $tierWeights); $rewards = $grouped[$tier] ?? []; if (empty($rewards)) { continue; } $attempt = 0; while ($attempt < $maxRetry) { try { $chosen = self::drawRewardByWeight($rewards); $gridNumber = isset($chosen['grid_number']) ? (int) $chosen['grid_number'] : 0; if ($gridNumber >= 5 && $gridNumber <= 30) { $counts[$gridNumber] = ($counts[$gridNumber] ?? 0) + 1; } break; } catch (\RuntimeException $e) { if ($e->getMessage() === self::EXCEPTION_WEIGHT_ALL_ZERO) { $attempt++; continue; } throw $e; } } } $snapshot = []; foreach ($grouped as $tierKey => $rows) { foreach ($rows as $row) { $snapshot[] = [ 'id' => (int) ($row['id'] ?? 0), 'grid_number' => (int) ($row['grid_number'] ?? 0), 'tier' => (string) ($row['tier'] ?? ''), 'weight' => (int) ($row['weight'] ?? 0), ]; } } $tierWeightsSnapshot = [ 'T1' => $tierWeights[0] ?? 0, 'T2' => $tierWeights[1] ?? 0, 'T3' => $tierWeights[2] ?? 0, 'T4' => $tierWeights[3] ?? 0, 'T5' => $tierWeights[4] ?? 0, ]; $recordId = null; if ($saveRecord) { $record = new DiceRewardConfigRecord(); $record->test_count = $testCount; $record->weight_config_snapshot = $snapshot; $record->tier_weights_snapshot = $tierWeightsSnapshot; $record->lottery_config_id = $config ? (int) $config->id : null; $record->result_counts = $counts; $record->admin_id = $adminId; $record->create_time = date('Y-m-d H:i:s'); $record->save(); $recordId = (int) $record->id; } return ['counts' => $counts, 'record_id' => $recordId]; } }