feat: 更新玩法配置管理,简化字段并增强功能
- 将玩法相关的显示名称字段统一为 `display_name`,移除多语言字段。 - 在 `PlayTypePatchController` 中新增即时切换玩法开关的功能,并推送大厅更新。 - 优化多个控制器和服务中的权限检查与数据处理逻辑,提升代码可读性与维护性。
This commit is contained in:
126
app/Services/Draw/DrawRngSeedDerivation.php
Normal file
126
app/Services/Draw/DrawRngSeedDerivation.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Draw;
|
||||
|
||||
use App\Models\Draw;
|
||||
use App\Models\DrawResultBatch;
|
||||
use App\Models\DrawResultItem;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
|
||||
/**
|
||||
* RNG 种子:CSPRNG 采集、SHA-256 摘要、Laravel 加密落库、确定性派生 4 位号码(算法 v1)。
|
||||
*
|
||||
* 验收:解密种子 → sha256(seed_hex) === rng_seed_hash → 复算 23 组号码与 draw_result_items 一致。
|
||||
*/
|
||||
final class DrawRngSeedDerivation
|
||||
{
|
||||
public const ALGORITHM_VERSION = 'v1';
|
||||
|
||||
/** 生成 32 字节随机种子(十六进制,64 字符) */
|
||||
public static function generateSeedHex(): string
|
||||
{
|
||||
return bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
public static function hashSeedHex(string $seedHex): string
|
||||
{
|
||||
return hash('sha256', $seedHex);
|
||||
}
|
||||
|
||||
public static function encryptSeedHex(string $seedHex): string
|
||||
{
|
||||
return Crypt::encryptString($seedHex);
|
||||
}
|
||||
|
||||
public static function decryptSeedHex(string $encrypted): string
|
||||
{
|
||||
try {
|
||||
return Crypt::decryptString($encrypted);
|
||||
} catch (DecryptException $e) {
|
||||
throw new \InvalidArgumentException('RNG seed decrypt failed', 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 由种子确定性派生第 $slotIndex 槽位的 4 位号码(0000–9999)。
|
||||
*/
|
||||
public static function deriveNumber4d(string $seedHex, int $drawId, int $slotIndex): string
|
||||
{
|
||||
$seedBinary = hex2bin($seedHex);
|
||||
if ($seedBinary === false || strlen($seedBinary) !== 32) {
|
||||
throw new \InvalidArgumentException('RNG seed must be 64 hex chars (32 bytes).');
|
||||
}
|
||||
|
||||
$message = self::ALGORITHM_VERSION.'|draw:'.$drawId.'|slot:'.$slotIndex;
|
||||
$digest = hash_hmac('sha256', $message, $seedBinary, true);
|
||||
$chunk = substr($digest, 0, 4);
|
||||
$unpacked = unpack('V', $chunk);
|
||||
$value = ((int) ($unpacked[1] ?? 0)) % 10_000;
|
||||
|
||||
return str_pad((string) $value, 4, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{prize_type: string, prize_index: int, number_4d: string, suffix_3d: string, suffix_2d: string, head_digit: int|null, tail_digit: int|null}>
|
||||
*/
|
||||
public static function deriveAllSlotRows(string $seedHex, int $drawId): array
|
||||
{
|
||||
$rows = [];
|
||||
foreach (DrawPrizeLayout::slots() as $slotIndex => $slot) {
|
||||
$num = self::deriveNumber4d($seedHex, $drawId, $slotIndex);
|
||||
$rows[] = [
|
||||
'prize_type' => $slot['prize_type'],
|
||||
'prize_index' => $slot['prize_index'],
|
||||
'number_4d' => $num,
|
||||
'suffix_3d' => substr($num, -3),
|
||||
'suffix_2d' => substr($num, -2),
|
||||
'head_digit' => $num !== '' ? (int) substr($num, 0, 1) : null,
|
||||
'tail_digit' => $num !== '' ? (int) substr($num, 3, 1) : null,
|
||||
];
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/** 审计:校验批次种子摘要、密文可解密且号码可由种子复算。 */
|
||||
public static function verifyBatchAudit(DrawResultBatch $batch, Draw $draw): bool
|
||||
{
|
||||
if ($batch->source_type !== 'rng') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$encrypted = $batch->raw_seed_encrypted;
|
||||
$hash = $batch->rng_seed_hash;
|
||||
if (! is_string($encrypted) || $encrypted === '' || ! is_string($hash) || $hash === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$seedHex = self::decryptSeedHex($encrypted);
|
||||
} catch (\InvalidArgumentException) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (self::hashSeedHex($seedHex) !== $hash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$expected = self::deriveAllSlotRows($seedHex, (int) $draw->id);
|
||||
$items = $batch->items()->get();
|
||||
|
||||
if ($items->count() !== count($expected)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($expected as $row) {
|
||||
$item = $items->first(fn (DrawResultItem $i) => $i->prize_type === $row['prize_type']
|
||||
&& (int) $i->prize_index === $row['prize_index']);
|
||||
if ($item === null || $item->number_4d !== $row['number_4d']) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user