feat: 扩展奖池、风控与报表能力,新增对账补偿、广播和人工操作接口

This commit is contained in:
2026-05-18 15:09:10 +08:00
parent 9157dcb6a1
commit 6ef41cee76
46 changed files with 1889 additions and 98 deletions

View File

@@ -12,12 +12,17 @@ use App\Services\AuditLogger;
use Illuminate\Support\Facades\DB;
use App\Support\OddsStandardScopes;
use App\Lottery\ConfigVersionStatus;
use App\Services\Draw\LotteryHallRealtimeBroadcaster;
use Illuminate\Validation\ValidationException;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
/** 后台:赔率版本({@see odds_versions} / {@see odds_items} */
final class OddsStreamService
{
public function __construct(
private readonly LotteryHallRealtimeBroadcaster $hallRealtime,
) {}
/** @return LengthAwarePaginator<int, OddsVersion> */
public function paginate(?string $status, int $perPage, int $page = 1): LengthAwarePaginator
{
@@ -138,6 +143,11 @@ final class OddsStreamService
});
$after = $this->snapshotVersion($draft->fresh(['items']));
$this->hallRealtime->notifyOddsUpdate(
(int) $draft->id,
'v'.(string) $draft->version_no,
['version_no' => (int) $draft->version_no],
);
AuditLogger::recordForAdmin(
$admin,

View File

@@ -10,12 +10,17 @@ use App\Models\PlayConfigItem;
use App\Models\PlayConfigVersion;
use Illuminate\Support\Facades\DB;
use App\Lottery\ConfigVersionStatus;
use App\Services\Draw\LotteryHallRealtimeBroadcaster;
use Illuminate\Validation\ValidationException;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
/** 后台:玩法配置版本({@see play_config_versions} / {@see play_config_items} */
final class PlayConfigStreamService
{
public function __construct(
private readonly LotteryHallRealtimeBroadcaster $hallRealtime,
) {}
/** @return LengthAwarePaginator<int, PlayConfigVersion> */
public function paginate(?string $status, int $perPage, int $page = 1): LengthAwarePaginator
{
@@ -140,6 +145,12 @@ final class PlayConfigStreamService
{
$this->validatePublishableDraft($draft);
$before = $this->snapshotVersion($draft);
$currentItems = PlayConfigItem::query()
->whereIn('version_id', PlayConfigVersion::query()
->select('id')
->where('status', ConfigVersionStatus::Active->value))
->get()
->keyBy('play_code');
DB::transaction(function () use ($draft, $admin): void {
/** @var PlayConfigVersion|null $current */
@@ -160,6 +171,7 @@ final class PlayConfigStreamService
});
$after = $this->snapshotVersion($draft->fresh(['items']));
$this->broadcastToggleDiffs($currentItems, $draft->items()->get());
AuditLogger::recordForAdmin(
$admin,
@@ -173,6 +185,26 @@ final class PlayConfigStreamService
);
}
/**
* @param \Illuminate\Support\Collection<string, PlayConfigItem> $currentItems
* @param \Illuminate\Support\Collection<int, PlayConfigItem> $nextItems
*/
private function broadcastToggleDiffs(\Illuminate\Support\Collection $currentItems, \Illuminate\Support\Collection $nextItems): void
{
foreach ($nextItems as $next) {
$current = $currentItems->get($next->play_code);
if ($current === null || (bool) $current->is_enabled === (bool) $next->is_enabled) {
continue;
}
$this->hallRealtime->notifyPlayToggle(
(string) $next->play_code,
(bool) $next->is_enabled,
'play config version published',
);
}
}
public function deleteVersion(PlayConfigVersion $version, AdminUser $admin, ?Request $request = null): void
{
$before = $this->snapshotVersion($version);

View File

@@ -59,15 +59,13 @@ final class RiskCapStreamService
]);
}
} else {
foreach (['0000', '1234', '9999'] as $num) {
RiskCapItem::query()->create([
'version_id' => $draft->id,
'draw_id' => null,
'normalized_number' => $num,
'cap_amount' => 50_000_000_000,
'cap_type' => 'per_number',
]);
}
RiskCapItem::query()->create([
'version_id' => $draft->id,
'draw_id' => null,
'normalized_number' => '0000',
'cap_amount' => 50_000_000_000,
'cap_type' => 'default',
]);
}
return $draft->fresh(['items']);
@@ -179,7 +177,10 @@ final class RiskCapStreamService
$normalizedNumber = (string) $row->normalized_number;
$capAmount = (int) $row->cap_amount;
$drawId = $row->draw_id === null ? '__null__' : (string) $row->draw_id;
$key = $drawId.'|'.$normalizedNumber;
$capType = (string) $row->cap_type;
$key = $capType === 'default'
? 'default|'.$drawId
: $drawId.'|'.$normalizedNumber;
if (! preg_match('/^[0-9]{4}$/', $normalizedNumber)) {
$errors["items.$index.normalized_number"][] = '号码必须是 4 位数字';
@@ -189,6 +190,10 @@ final class RiskCapStreamService
$errors["items.$index.cap_amount"][] = '封顶金额必须大于 0';
}
if ($capType === 'default' && $row->draw_id !== null) {
$errors["items.$index.cap_type"][] = '默认封顶不能绑定具体期号';
}
if (isset($seenKeys[$key])) {
$errors["items.$index"][] = '同一期号与号码存在重复封顶配置';
}