1.优化实时对局页面样式以及自动创建下一局和作废本局的记录
2.新增派彩达到game_config.jackpot_max_amount必须审核才能发放 3.新增游戏对局记录-查看游玩记录btn 3.备份MySQL数据库
This commit is contained in:
@@ -14,6 +14,14 @@ use Throwable;
|
||||
*/
|
||||
final class GameBetSettleService
|
||||
{
|
||||
public const PLAY_STATUS_PENDING_DRAW = 1;
|
||||
public const PLAY_STATUS_SETTLED = 2;
|
||||
public const PLAY_STATUS_REFUNDED = 3;
|
||||
public const PLAY_STATUS_RETURNED = 4;
|
||||
public const PLAY_STATUS_PENDING_REVIEW = 5;
|
||||
|
||||
public const CONFIG_KEY_JACKPOT_MAX_AMOUNT = 'jackpot_max_amount';
|
||||
|
||||
/**
|
||||
* 对指定期次按开奖号码结算所有「待开奖」注单;同一注单幂等(仅 status=1 会更新)。
|
||||
*
|
||||
@@ -28,9 +36,10 @@ final class GameBetSettleService
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$jackpotMaxAmount = self::jackpotMaxAmount();
|
||||
$bets = Db::name('bet_order')
|
||||
->where('period_id', $recordId)
|
||||
->where('status', 1)
|
||||
->where('status', self::PLAY_STATUS_PENDING_DRAW)
|
||||
->order('id', 'asc')
|
||||
->select()
|
||||
->toArray();
|
||||
@@ -60,14 +69,16 @@ final class GameBetSettleService
|
||||
|
||||
$win = self::computeWinAmount($bet, $resultNumber);
|
||||
$jackpot = '0.00';
|
||||
$needReview = self::shouldRequireJackpotReview($win, $jackpotMaxAmount);
|
||||
$nextStatus = $needReview ? self::PLAY_STATUS_PENDING_REVIEW : self::PLAY_STATUS_SETTLED;
|
||||
|
||||
$affected = Db::name('bet_order')
|
||||
->where('id', $betId)
|
||||
->where('status', 1)
|
||||
->where('status', self::PLAY_STATUS_PENDING_DRAW)
|
||||
->update([
|
||||
'win_amount' => $win,
|
||||
'jackpot_extra_amount' => $jackpot,
|
||||
'status' => 2,
|
||||
'status' => $nextStatus,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
|
||||
@@ -91,8 +102,8 @@ final class GameBetSettleService
|
||||
}
|
||||
|
||||
$balanceAfter = (string) (Db::name('user')->where('id', $userId)->value('coin') ?? '0');
|
||||
if (bccomp($win, '0', 2) > 0) {
|
||||
$paid = self::creditUserPayout($bet, $betId, $win, $now);
|
||||
if (!$needReview && bccomp($win, '0', 2) > 0) {
|
||||
$paid = self::creditUserPayout($bet, $betId, $win, $now, null, '压注派彩');
|
||||
if ($paid !== null) {
|
||||
$balanceAfter = $paid;
|
||||
}
|
||||
@@ -154,6 +165,109 @@ final class GameBetSettleService
|
||||
return ['jackpot_hits' => $jackpotHits];
|
||||
}
|
||||
|
||||
/**
|
||||
* 大奖审核通过后派彩(幂等):仅当 play_record.status=待审核 且 win_amount>=阈值时执行。
|
||||
*
|
||||
* @return array{ok: bool, msg: string, balance_after?: string}
|
||||
*/
|
||||
public static function approveJackpotPlayRecord(int $playRecordId, int $operatorAdminId, string $remark = ''): array
|
||||
{
|
||||
if ($playRecordId <= 0) {
|
||||
return ['ok' => false, 'msg' => __('Parameter error')];
|
||||
}
|
||||
// 兼容:bet_order 可能是 VIEW,且 * 列表会固化;审核字段始终以 game_play_record 为准
|
||||
$row = Db::name('game_play_record')->where('id', $playRecordId)->find();
|
||||
if (!is_array($row)) {
|
||||
$row = Db::name('bet_order')->where('id', $playRecordId)->find();
|
||||
}
|
||||
if (!is_array($row)) {
|
||||
return ['ok' => false, 'msg' => __('Record not found')];
|
||||
}
|
||||
$status = isset($row['status']) && is_numeric($row['status']) ? (int) $row['status'] : 0;
|
||||
if ($status !== self::PLAY_STATUS_PENDING_REVIEW) {
|
||||
return ['ok' => false, 'msg' => __('This record does not require review')];
|
||||
}
|
||||
$winAmount = bcadd((string) ($row['win_amount'] ?? '0'), '0', 2);
|
||||
$threshold = self::jackpotMaxAmount();
|
||||
if (!self::shouldRequireJackpotReview($winAmount, $threshold)) {
|
||||
return ['ok' => false, 'msg' => __('This record does not meet jackpot review threshold')];
|
||||
}
|
||||
$userId = isset($row['user_id']) && is_numeric($row['user_id']) ? (int) $row['user_id'] : 0;
|
||||
if ($userId <= 0) {
|
||||
return ['ok' => false, 'msg' => __('Order is missing user info')];
|
||||
}
|
||||
$now = time();
|
||||
$balanceAfter = null;
|
||||
if (bccomp($winAmount, '0', 2) > 0) {
|
||||
$balanceAfter = self::creditUserPayout($row, $playRecordId, $winAmount, $now, $operatorAdminId > 0 ? $operatorAdminId : null, '大奖审核通过派彩');
|
||||
}
|
||||
$reviewRemark = trim($remark);
|
||||
if ($reviewRemark === '') {
|
||||
$reviewRemark = 'approved';
|
||||
}
|
||||
$update = [
|
||||
'status' => self::PLAY_STATUS_SETTLED,
|
||||
'review_admin_id' => $operatorAdminId > 0 ? $operatorAdminId : null,
|
||||
'review_time' => $now,
|
||||
'review_remark' => substr($reviewRemark, 0, 255),
|
||||
'update_time' => $now,
|
||||
];
|
||||
// 优先写主表
|
||||
Db::name('game_play_record')->where('id', $playRecordId)->where('status', self::PLAY_STATUS_PENDING_REVIEW)->update($update);
|
||||
// 兼容写 view 场景(若存在且可写)
|
||||
try {
|
||||
Db::name('bet_order')->where('id', $playRecordId)->where('status', self::PLAY_STATUS_PENDING_REVIEW)->update($update);
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
|
||||
$out = ['ok' => true, 'msg' => __('Approved')];
|
||||
if (is_string($balanceAfter)) {
|
||||
$out['balance_after'] = $balanceAfter;
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 大奖审核拒绝:仅当 status=待审核 才可操作;拒绝后不派彩,标记为已退回(status=4)。
|
||||
*
|
||||
* @return array{ok: bool, msg: string}
|
||||
*/
|
||||
public static function rejectJackpotPlayRecord(int $playRecordId, int $operatorAdminId, string $remark): array
|
||||
{
|
||||
if ($playRecordId <= 0) {
|
||||
return ['ok' => false, 'msg' => __('Parameter error')];
|
||||
}
|
||||
$reason = trim($remark);
|
||||
if ($reason === '') {
|
||||
return ['ok' => false, 'msg' => __('Please provide reject reason')];
|
||||
}
|
||||
$row = Db::name('game_play_record')->where('id', $playRecordId)->find();
|
||||
if (!is_array($row)) {
|
||||
$row = Db::name('bet_order')->where('id', $playRecordId)->find();
|
||||
}
|
||||
if (!is_array($row)) {
|
||||
return ['ok' => false, 'msg' => __('Record not found')];
|
||||
}
|
||||
$status = isset($row['status']) && is_numeric($row['status']) ? (int) $row['status'] : 0;
|
||||
if ($status !== self::PLAY_STATUS_PENDING_REVIEW) {
|
||||
return ['ok' => false, 'msg' => __('This record does not require review')];
|
||||
}
|
||||
$now = time();
|
||||
$update = [
|
||||
'status' => self::PLAY_STATUS_RETURNED,
|
||||
'review_admin_id' => $operatorAdminId > 0 ? $operatorAdminId : null,
|
||||
'review_time' => $now,
|
||||
'review_remark' => substr($reason, 0, 255),
|
||||
'update_time' => $now,
|
||||
];
|
||||
Db::name('game_play_record')->where('id', $playRecordId)->where('status', self::PLAY_STATUS_PENDING_REVIEW)->update($update);
|
||||
try {
|
||||
Db::name('bet_order')->where('id', $playRecordId)->where('status', self::PLAY_STATUS_PENDING_REVIEW)->update($update);
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
return ['ok' => true, 'msg' => __('Rejected')];
|
||||
}
|
||||
|
||||
/**
|
||||
* 补偿:库中已结束局次但注单仍为待开奖的,可重复调用(幂等)。
|
||||
*/
|
||||
@@ -176,7 +290,7 @@ final class GameBetSettleService
|
||||
}
|
||||
$pending = Db::name('bet_order')
|
||||
->where('period_id', $rid)
|
||||
->where('status', 1)
|
||||
->where('status', self::PLAY_STATUS_PENDING_DRAW)
|
||||
->count();
|
||||
if ($pending === 0) {
|
||||
continue;
|
||||
@@ -251,7 +365,7 @@ final class GameBetSettleService
|
||||
/**
|
||||
* @return string|null 派彩后余额;已幂等入账过时返回当前余额;失败或未执行派彩返回 null
|
||||
*/
|
||||
private static function creditUserPayout(array $bet, int $betId, string $winAmount, int $now): ?string
|
||||
private static function creditUserPayout(array $bet, int $betId, string $winAmount, int $now, ?int $operatorAdminId, string $remark): ?string
|
||||
{
|
||||
$userId = (int) ($bet['user_id'] ?? 0);
|
||||
if ($userId <= 0) {
|
||||
@@ -284,8 +398,8 @@ final class GameBetSettleService
|
||||
'ref_type' => 'bet_order',
|
||||
'ref_id' => $betId,
|
||||
'idempotency_key' => $idem,
|
||||
'operator_admin_id' => null,
|
||||
'remark' => '压注派彩',
|
||||
'operator_admin_id' => $operatorAdminId,
|
||||
'remark' => $remark !== '' ? $remark : '压注派彩',
|
||||
'create_time' => $now,
|
||||
]);
|
||||
|
||||
@@ -304,4 +418,35 @@ final class GameBetSettleService
|
||||
|
||||
return $after;
|
||||
}
|
||||
|
||||
private static function jackpotMaxAmount(): string
|
||||
{
|
||||
// 结算属于高频长驻进程逻辑:为避免 GameHotDataRedis::$gcLocal 进程内静态缓存导致阈值更新不生效,
|
||||
// 这里直接读库拿最新值(本方法在 settleBetsForDraw 中仅调用一次)。
|
||||
$row = Db::name('game_config')->where('config_key', self::CONFIG_KEY_JACKPOT_MAX_AMOUNT)->find();
|
||||
if (!is_array($row)) {
|
||||
return '0.00';
|
||||
}
|
||||
$raw = $row['config_value'] ?? null;
|
||||
if ($raw === null || $raw === '') {
|
||||
return '0.00';
|
||||
}
|
||||
$v = is_string($raw) ? trim($raw) : (is_numeric($raw) ? strval($raw) : '');
|
||||
if ($v === '' || !is_numeric($v)) {
|
||||
return '0.00';
|
||||
}
|
||||
$normalized = bcadd($v, '0', 2);
|
||||
if (bccomp($normalized, '0', 2) <= 0) {
|
||||
return '0.00';
|
||||
}
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
private static function shouldRequireJackpotReview(string $winAmount, string $threshold): bool
|
||||
{
|
||||
if (bccomp($threshold, '0', 2) <= 0) {
|
||||
return false;
|
||||
}
|
||||
return bccomp($winAmount, $threshold, 2) >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user