feat: 更新玩法配置管理,简化字段并增强功能

- 将玩法相关的显示名称字段统一为 `display_name`,移除多语言字段。
- 在 `PlayTypePatchController` 中新增即时切换玩法开关的功能,并推送大厅更新。
- 优化多个控制器和服务中的权限检查与数据处理逻辑,提升代码可读性与维护性。
This commit is contained in:
2026-05-25 14:34:24 +08:00
parent 270d2e9af1
commit e27a00f260
74 changed files with 4469 additions and 280 deletions

View File

@@ -0,0 +1,243 @@
<?php
namespace App\Console\Commands;
use App\Models\Draw;
use App\Models\Player;
use App\Models\TicketItem;
use App\Models\TicketOrder;
use App\Models\PlayerWallet;
use App\Models\DrawResultItem;
use App\Models\DrawResultBatch;
use App\Lottery\DrawStatus;
use Illuminate\Console\Command;
use App\Services\Draw\DrawPrizeLayout;
use Illuminate\Support\Facades\DB;
use App\Lottery\DrawResultBatchStatus;
use App\Services\Settlement\SettlementOrchestrator;
/**
* PRD §17.2:单期万级注单结算耗时验收(仅结算编排,不含派彩入账)。
*/
final class LotteryPerfSettlementBenchmarkCommand extends Command
{
protected $signature = 'lottery:perf-settlement-benchmark
{--items=10000 : 待结算 ticket_items 数量}
{--max-seconds=30 : 通过阈值(秒)}';
protected $description = 'Benchmark SettlementOrchestrator for N pending_draw items (§17.2)';
public function handle(SettlementOrchestrator $orchestrator): int
{
if (ini_get('memory_limit') !== '-1' && $this->byteMemoryLimit(ini_get('memory_limit')) < 512 * 1024 * 1024) {
ini_set('memory_limit', '512M');
}
return $this->runBenchmark($orchestrator);
}
private function runBenchmark(SettlementOrchestrator $orchestrator): int
{
$itemCount = max(1, (int) $this->option('items'));
$maxSeconds = max(1, (int) $this->option('max-seconds'));
$maxMs = $maxSeconds * 1000;
$this->info(sprintf('Seeding %d ticket items…', $itemCount));
$fixture = $this->seedFixture($itemCount);
$draw = $fixture['draw'];
$this->info('Running trySettleDraw…');
$started = hrtime(true);
$ran = $orchestrator->trySettleDraw($draw->fresh());
$elapsedMs = (int) ((hrtime(true) - $started) / 1_000_000);
if (! $ran) {
$this->error('Settlement did not run (check draw status / published result batch).');
return self::FAILURE;
}
$settled = TicketItem::query()
->where('draw_id', $draw->id)
->whereIn('status', ['pending_payout', 'settled_lose', 'settled_win'])
->count();
$pass = $elapsedMs <= $maxMs && $settled === $itemCount;
$this->table(
['metric', 'value'],
[
['items', (string) $itemCount],
['settled_rows', (string) $settled],
['elapsed_ms', (string) $elapsedMs],
['threshold_ms', (string) $maxMs],
['result', $pass ? 'PASS' : 'FAIL'],
],
);
return $pass ? self::SUCCESS : self::FAILURE;
}
private function byteMemoryLimit(string $value): int
{
$value = trim($value);
if ($value === '' || $value === '-1') {
return PHP_INT_MAX;
}
$unit = strtolower(substr($value, -1));
$number = (int) $value;
return match ($unit) {
'g' => $number * 1024 * 1024 * 1024,
'm' => $number * 1024 * 1024,
'k' => $number * 1024,
default => $number,
};
}
/**
* @return array{draw: Draw}
*/
private function seedFixture(int $itemCount): array
{
return DB::transaction(function () use ($itemCount): array {
$uniq = bin2hex(random_bytes(4));
$player = Player::query()->create([
'site_code' => 'perf',
'site_player_id' => 'perf-settle-'.$uniq,
'username' => 'perf_'.$uniq,
'nickname' => null,
'default_currency' => 'NPR',
'status' => 0,
]);
PlayerWallet::query()->create([
'player_id' => $player->id,
'wallet_type' => 'lottery',
'currency_code' => 'NPR',
'balance' => 0,
'frozen_balance' => 0,
'status' => 0,
'version' => 0,
]);
$draw = Draw::query()->create([
'draw_no' => '20990101-'.str_pad((string) random_int(1, 999), 3, '0', STR_PAD_LEFT),
'business_date' => '2099-01-01',
'sequence_no' => 1,
'status' => DrawStatus::Settling->value,
'start_time' => now()->subHour(),
'close_time' => now()->subMinutes(30),
'draw_time' => now()->subMinutes(20),
'cooling_end_time' => now()->subMinutes(5),
'result_source' => 'rng',
'current_result_version' => 1,
'settle_version' => 0,
'is_reopened' => false,
]);
$batch = DrawResultBatch::query()->create([
'draw_id' => $draw->id,
'result_version' => 1,
'source_type' => 'rng',
'rng_seed_hash' => 'perf-bench',
'raw_seed_encrypted' => null,
'status' => DrawResultBatchStatus::Published->value,
'created_by' => null,
'confirmed_by' => null,
'confirmed_at' => now(),
]);
foreach (DrawPrizeLayout::slots() as $slot) {
DrawResultItem::query()->create([
'draw_id' => $draw->id,
'result_batch_id' => $batch->id,
'prize_type' => $slot['prize_type'],
'prize_index' => $slot['prize_index'],
'number_4d' => '0001',
'suffix_3d' => '001',
'suffix_2d' => '01',
'head_digit' => 0,
'tail_digit' => 1,
]);
}
$order = TicketOrder::query()->create([
'order_no' => 'TO-PERF-'.$uniq,
'player_id' => $player->id,
'draw_id' => $draw->id,
'currency_code' => 'NPR',
'total_bet_amount' => $itemCount * 10,
'total_rebate_amount' => 0,
'total_actual_deduct' => $itemCount * 10,
'total_estimated_payout' => 0,
'status' => 'placed',
'submit_source' => 'perf',
'client_trace_id' => 'perf-settle-'.$uniq,
]);
$now = now();
$oddsJson = json_encode(['1st' => 250000], JSON_THROW_ON_ERROR);
$ruleJson = json_encode([], JSON_THROW_ON_ERROR);
$chunk = 500;
for ($offset = 0; $offset < $itemCount; $offset += $chunk) {
$size = min($chunk, $itemCount - $offset);
$itemRows = [];
$ticketNos = [];
for ($i = 0; $i < $size; $i++) {
$seq = $offset + $i + 1;
$num = str_pad((string) ($seq % 10000), 4, '0', STR_PAD_LEFT);
$ticketNo = sprintf('TK-PERF-%s-%06d', $uniq, $seq);
$ticketNos[] = $ticketNo;
$itemRows[] = [
'ticket_no' => $ticketNo,
'order_id' => $order->id,
'player_id' => $player->id,
'draw_id' => $draw->id,
'original_number' => $num,
'normalized_number' => $num,
'play_code' => 'big',
'dimension' => 4,
'digit_slot' => null,
'bet_mode' => 'straight',
'unit_bet_amount' => 10,
'total_bet_amount' => 10,
'rebate_rate_snapshot' => 0,
'commission_rate_snapshot' => 0,
'actual_deduct_amount' => 10,
'odds_snapshot_json' => $oddsJson,
'rule_snapshot_json' => $ruleJson,
'combination_count' => 1,
'estimated_max_payout' => 250,
'risk_locked_amount' => 0,
'status' => 'pending_draw',
'created_at' => $now,
'updated_at' => $now,
];
}
DB::table('ticket_items')->insert($itemRows);
$comboRows = DB::table('ticket_items')
->whereIn('ticket_no', $ticketNos)
->get(['id', 'normalized_number'])
->map(fn ($row): array => [
'ticket_item_id' => $row->id,
'combination_no' => 0,
'number_4d' => $row->normalized_number,
'bet_amount' => 10,
'estimated_payout' => 250,
'created_at' => $now,
])
->all();
DB::table('ticket_combinations')->insert($comboRows);
}
return ['draw' => $draw];
});
}
}