feat: enhance configuration and logging for admin services
Some checks failed
lotterLaravel CI / test (push) Has been cancelled
Some checks failed
lotterLaravel CI / test (push) Has been cancelled
- Updated .env.example to enable Redis Lua for lottery risk pool and added configuration for agent settlement. - Improved AGENTS.md to clarify site operations and admin roles. - Enhanced PHPUnit configuration with memory limit and cache directory settings. - Refactored AdminReportQueryService and related services to utilize LimitedQuery for better data handling and truncation warnings. - Added logging for draw settlement failures in DrawTickService. - Introduced credit preflight checks and reverse bet hold functionality in PlayerCreditService. - Improved risk pool management with new Redis lock handling in RiskPoolService and TicketPlacementService.
This commit is contained in:
@@ -148,30 +148,7 @@ final class RiskPoolService
|
||||
foreach ($locks as $lock) {
|
||||
$number4d = $lock['number_4d'];
|
||||
$amount = (int) $lock['amount'];
|
||||
$pool = $this->firstOrMakePool($drawId, $number4d);
|
||||
$key = $this->redisPoolKey($drawId, $number4d);
|
||||
|
||||
Redis::eval(
|
||||
$this->initLua(),
|
||||
1,
|
||||
$key,
|
||||
(int) $pool->total_cap_amount,
|
||||
(int) $pool->locked_amount,
|
||||
(int) $pool->version,
|
||||
$this->redisPoolTtlSeconds(),
|
||||
);
|
||||
|
||||
$result = $this->normalizeLuaResult(Redis::eval(
|
||||
$this->acquireLua(),
|
||||
1,
|
||||
$key,
|
||||
$amount,
|
||||
(int) $pool->version,
|
||||
$this->redisPoolTtlSeconds(),
|
||||
));
|
||||
if (($result['code'] ?? null) !== 'OK') {
|
||||
throw new TicketOperationException('risk_sold_out', ErrorCode::RiskPoolSoldOut->value);
|
||||
}
|
||||
$this->acquireRedisLockForCombination($drawId, $number4d, $amount);
|
||||
|
||||
$acquired[] = ['number_4d' => $number4d, 'amount' => $amount];
|
||||
$total += $amount;
|
||||
@@ -188,6 +165,20 @@ final class RiskPoolService
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* DB 事务回滚时补偿 Redis 侧已占用额度(DB 锁行会随事务回滚)。
|
||||
*
|
||||
* @param list<array{number_4d:string, amount:int}> $locks
|
||||
*/
|
||||
public function compensateRedisAcquires(int $drawId, array $locks): void
|
||||
{
|
||||
if ($locks === [] || ! $this->shouldUseRedisAtomicLocks()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->releaseRedisLocks($drawId, $locks);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array{number_4d:string, amount:int}> $locks
|
||||
*/
|
||||
@@ -202,6 +193,66 @@ final class RiskPoolService
|
||||
}
|
||||
}
|
||||
|
||||
private function acquireRedisLockForCombination(int $drawId, string $number4d, int $amount): void
|
||||
{
|
||||
for ($attempt = 0; $attempt < 2; $attempt++) {
|
||||
$pool = $this->firstOrMakePool($drawId, $number4d);
|
||||
$key = $this->redisPoolKey($drawId, $number4d);
|
||||
|
||||
Redis::eval(
|
||||
$this->initLua(),
|
||||
1,
|
||||
$key,
|
||||
(int) $pool->total_cap_amount,
|
||||
(int) $pool->locked_amount,
|
||||
(int) $pool->version,
|
||||
$this->redisPoolTtlSeconds(),
|
||||
);
|
||||
|
||||
$result = $this->normalizeLuaResult(Redis::eval(
|
||||
$this->acquireLua(),
|
||||
1,
|
||||
$key,
|
||||
$amount,
|
||||
(int) $pool->version,
|
||||
$this->redisPoolTtlSeconds(),
|
||||
));
|
||||
|
||||
if (($result['code'] ?? null) === 'OK') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (($result['code'] ?? null) === 'INSUFFICIENT_CAP') {
|
||||
throw new TicketOperationException('risk_sold_out', ErrorCode::RiskPoolSoldOut->value);
|
||||
}
|
||||
|
||||
if ($attempt === 0 && in_array($result['code'] ?? '', ['VERSION_CONFLICT', 'POOL_NOT_INITIALIZED'], true)) {
|
||||
$freshPool = RiskPool::query()
|
||||
->where('draw_id', $drawId)
|
||||
->where('normalized_number', $number4d)
|
||||
->firstOrFail();
|
||||
$this->syncRedisStateFromPool($freshPool);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->throwForRedisAcquireFailure($result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{code:string, remaining:int, locked:int, version:int} $result
|
||||
*/
|
||||
private function throwForRedisAcquireFailure(array $result): void
|
||||
{
|
||||
throw new TicketOperationException(
|
||||
'risk_pool_unavailable',
|
||||
ErrorCode::InternalError->value,
|
||||
503,
|
||||
['redis_code' => $result['code'] ?? 'unknown'],
|
||||
);
|
||||
}
|
||||
|
||||
public function publishManualSoldOut(Draw $draw, string $normalizedNumber): void
|
||||
{
|
||||
$this->riskRealtime->publishManualSoldOut($draw, $normalizedNumber);
|
||||
|
||||
Reference in New Issue
Block a user