1.优化实时对局页面样式以及自动创建下一局和作废本局的记录
2.新增派彩达到game_config.jackpot_max_amount必须审核才能发放 3.新增游戏对局记录-查看游玩记录btn 3.备份MySQL数据库
This commit is contained in:
@@ -139,7 +139,7 @@ class Live extends Backend
|
||||
}
|
||||
$raw = $request->post('enabled');
|
||||
$enabled = $raw === true || $raw === '1' || $raw === 1;
|
||||
GameRecordService::setLiveRuntimeEnabled($enabled);
|
||||
GameRecordService::setAutoCreateEnabled($enabled);
|
||||
if ($enabled) {
|
||||
GameRecordService::bootstrapPeriodWhenRuntimeEnabled();
|
||||
}
|
||||
@@ -168,6 +168,15 @@ class Live extends Backend
|
||||
return $this->error(is_string($errMsg) ? $errMsg : __('Void failed'));
|
||||
}
|
||||
$okMsg = $res['msg'] ?? '';
|
||||
return $this->success(is_string($okMsg) ? $okMsg : '', GameLiveService::buildSnapshot(null));
|
||||
$snapshot = GameLiveService::buildSnapshot(null);
|
||||
// 作废本局后:必须关闭自动创建下一局开关(允许管理员后续手动重新开启)
|
||||
$snapshot['runtime_enabled'] = false;
|
||||
// 作废后一般不存在进行中的局,直接进入维护态(用于前端展示“维护中”倒计时)
|
||||
$snapshot['maintenance_ui'] = true;
|
||||
$refund = $res['refund'] ?? null;
|
||||
if (is_array($refund)) {
|
||||
$snapshot['void_refund'] = $refund;
|
||||
}
|
||||
return $this->success(is_string($okMsg) ? $okMsg : '', $snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace app\admin\controller\game;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use app\common\service\GameBetSettleService;
|
||||
use support\think\Db;
|
||||
use support\Response;
|
||||
use Webman\Http\Request as WebmanRequest;
|
||||
|
||||
@@ -78,26 +80,191 @@ class PlayRecord extends Backend
|
||||
$where[] = ['user.admin_id', 'in', $this->scopedAdminIds()];
|
||||
}
|
||||
|
||||
$res = $this->model
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->with($this->withJoinTable)
|
||||
->visible([
|
||||
'user' => ['username', 'phone'],
|
||||
'channel' => ['name'],
|
||||
'gameRecord' => ['period_no', 'status'],
|
||||
// 避免 ThinkORM withJoin 对 game_record 的字段缓存导致 select 出已删除列(如 preset_number)
|
||||
// 这里改为手写 join + 明确 field 列表,保证数据库字段变更后不受 schema 缓存影响。
|
||||
$query = Db::name($table)->alias($mainShort !== '' ? $mainShort : 'play_record')
|
||||
->leftJoin('user user', 'user.id = ' . ($mainShort !== '' ? $mainShort : 'play_record') . '.user_id')
|
||||
->leftJoin('channel channel', 'channel.id = ' . ($mainShort !== '' ? $mainShort : 'play_record') . '.channel_id')
|
||||
->leftJoin('game_record game_record', 'game_record.id = ' . ($mainShort !== '' ? $mainShort : 'play_record') . '.period_id')
|
||||
->where($where);
|
||||
|
||||
$res = $query
|
||||
->field([
|
||||
($mainShort !== '' ? $mainShort : 'play_record') . '.*',
|
||||
'user.username as user__username',
|
||||
'user.phone as user__phone',
|
||||
'channel.name as channel__name',
|
||||
'game_record.period_no as gameRecord__period_no',
|
||||
'game_record.status as gameRecord__status',
|
||||
])
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
$list = $res->items();
|
||||
$total = $res->total();
|
||||
|
||||
// 将 join 扁平字段还原为原页面所需结构:user/channel/gameRecord
|
||||
foreach ($list as $idx => $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$row['user'] = [
|
||||
'username' => isset($row['user__username']) ? (string) $row['user__username'] : '',
|
||||
'phone' => isset($row['user__phone']) ? (string) $row['user__phone'] : '',
|
||||
];
|
||||
$row['channel'] = [
|
||||
'name' => isset($row['channel__name']) ? (string) $row['channel__name'] : '',
|
||||
];
|
||||
$row['gameRecord'] = [
|
||||
'period_no' => isset($row['gameRecord__period_no']) ? (string) $row['gameRecord__period_no'] : '',
|
||||
'status' => isset($row['gameRecord__status']) && is_numeric((string) $row['gameRecord__status'])
|
||||
? (int) $row['gameRecord__status']
|
||||
: null,
|
||||
];
|
||||
unset(
|
||||
$row['user__username'],
|
||||
$row['user__phone'],
|
||||
$row['channel__name'],
|
||||
$row['gameRecord__period_no'],
|
||||
$row['gameRecord__status'],
|
||||
);
|
||||
$list[$idx] = $row;
|
||||
}
|
||||
|
||||
$threshold = $this->jackpotMaxAmount();
|
||||
foreach ($list as $idx => $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$status = isset($row['status']) && is_numeric($row['status']) ? (int) $row['status'] : 0;
|
||||
$win = bcadd(strval($row['win_amount'] ?? '0'), '0', 2);
|
||||
$needReview = bccomp($threshold, '0', 2) > 0 && bccomp($win, $threshold, 2) >= 0;
|
||||
$canApprove = $needReview && $status === GameBetSettleService::PLAY_STATUS_PENDING_REVIEW;
|
||||
$row['jackpot_need_review'] = $needReview ? 1 : 0;
|
||||
$row['can_jackpot_approve'] = $canApprove ? 1 : 0;
|
||||
$list[$idx] = $row;
|
||||
}
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $res->items(),
|
||||
'total' => $res->total(),
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 大奖审核通过并派彩(仅 win_amount >= game_config.jackpot_max_amount 且 status=待审核 才可操作)
|
||||
*/
|
||||
public function approveJackpot(WebmanRequest $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) {
|
||||
return $response;
|
||||
}
|
||||
if ($request->method() !== 'POST') {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
|
||||
$idRaw = $request->post('id');
|
||||
if ($idRaw === null || $idRaw === '' || !is_numeric(strval($idRaw))) {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
$id = (int) $idRaw;
|
||||
if ($id <= 0) {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
$remarkRaw = $request->post('remark');
|
||||
$remark = is_string($remarkRaw) ? trim($remarkRaw) : '';
|
||||
|
||||
// 权限范围校验:复用列表逻辑(非超管只能操作其下辖用户)
|
||||
if ($this->auth && !$this->auth->isSuperAdmin()) {
|
||||
$uidRaw = Db::name('game_play_record')->where('id', $id)->value('user_id');
|
||||
$uid = ($uidRaw === null || $uidRaw === '' || !is_numeric(strval($uidRaw))) ? 0 : (int) $uidRaw;
|
||||
if ($uid <= 0) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
$ownerAdminId = Db::name('user')->where('id', $uid)->value('admin_id');
|
||||
$aid = ($ownerAdminId === null || $ownerAdminId === '' || !is_numeric(strval($ownerAdminId))) ? 0 : (int) $ownerAdminId;
|
||||
if ($aid <= 0 || !in_array($aid, $this->scopedAdminIds(), true)) {
|
||||
return $this->error(__('You have no permission'));
|
||||
}
|
||||
}
|
||||
|
||||
$adminId = $this->auth ? (int) ($this->auth->id ?? 0) : 0;
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$result = GameBetSettleService::approveJackpotPlayRecord($id, $adminId, $remark);
|
||||
if (($result['ok'] ?? false) !== true) {
|
||||
Db::rollback();
|
||||
$msg = is_string($result['msg'] ?? null) ? $result['msg'] : __('Parameter error');
|
||||
return $this->error($msg);
|
||||
}
|
||||
Db::commit();
|
||||
} catch (\Throwable $e) {
|
||||
Db::rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
|
||||
return $this->success(__('Approved'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 大奖审核拒绝(remark 必填)
|
||||
*/
|
||||
public function rejectJackpot(WebmanRequest $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) {
|
||||
return $response;
|
||||
}
|
||||
if ($request->method() !== 'POST') {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
$idRaw = $request->post('id');
|
||||
if ($idRaw === null || $idRaw === '' || !is_numeric(strval($idRaw))) {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
$id = (int) $idRaw;
|
||||
if ($id <= 0) {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
$remarkRaw = $request->post('remark');
|
||||
$remark = is_string($remarkRaw) ? trim($remarkRaw) : '';
|
||||
if ($remark === '') {
|
||||
return $this->error(__('Please provide reject reason'));
|
||||
}
|
||||
|
||||
if ($this->auth && !$this->auth->isSuperAdmin()) {
|
||||
$uidRaw = Db::name('game_play_record')->where('id', $id)->value('user_id');
|
||||
$uid = ($uidRaw === null || $uidRaw === '' || !is_numeric(strval($uidRaw))) ? 0 : (int) $uidRaw;
|
||||
if ($uid <= 0) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
$ownerAdminId = Db::name('user')->where('id', $uid)->value('admin_id');
|
||||
$aid = ($ownerAdminId === null || $ownerAdminId === '' || !is_numeric(strval($ownerAdminId))) ? 0 : (int) $ownerAdminId;
|
||||
if ($aid <= 0 || !in_array($aid, $this->scopedAdminIds(), true)) {
|
||||
return $this->error(__('You have no permission'));
|
||||
}
|
||||
}
|
||||
|
||||
$adminId = $this->auth ? (int) ($this->auth->id ?? 0) : 0;
|
||||
Db::startTrans();
|
||||
try {
|
||||
$result = GameBetSettleService::rejectJackpotPlayRecord($id, $adminId, $remark);
|
||||
if (($result['ok'] ?? false) !== true) {
|
||||
Db::rollback();
|
||||
$msg = is_string($result['msg'] ?? null) ? $result['msg'] : __('Parameter error');
|
||||
return $this->error($msg);
|
||||
}
|
||||
Db::commit();
|
||||
} catch (\Throwable $e) {
|
||||
Db::rollback();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
return $this->success(__('Rejected'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
@@ -116,5 +283,26 @@ class PlayRecord extends Backend
|
||||
$adminIds = array_values(array_unique(array_filter($adminIds, static fn($id) => $id > 0)));
|
||||
return $adminIds === [] ? [0] : $adminIds;
|
||||
}
|
||||
|
||||
private function jackpotMaxAmount(): string
|
||||
{
|
||||
$row = Db::name('game_config')->where('config_key', GameBetSettleService::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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,44 @@ class Record extends Backend
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function _index(): Response
|
||||
{
|
||||
if ($this->request && $this->request->get('select')) {
|
||||
return $this->select($this->request);
|
||||
}
|
||||
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
|
||||
$res = $this->model
|
||||
->field($this->indexField)
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->with($this->withJoinTable)
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
$list = $res->items();
|
||||
foreach ($list as $idx => $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$status = isset($row['status']) && is_numeric((string) $row['status']) ? (int) $row['status'] : 0;
|
||||
$reason = isset($row['void_reason']) && is_string($row['void_reason']) ? $row['void_reason'] : '';
|
||||
// 将“系统自愈作废”的对局在列表中标记为【异常】(展示态,不改库中 status=5 的事实)
|
||||
if ($status === 5 && $reason !== '' && str_starts_with($reason, 'system_recover:')) {
|
||||
$row['status'] = 6;
|
||||
$list[$idx] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $list,
|
||||
'total' => $res->total(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function add(WebmanRequest $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
@@ -86,24 +124,95 @@ class Record extends Backend
|
||||
|
||||
$rows = Db::name('game_record')
|
||||
->where('status', 5)
|
||||
// 仅展示服务重启自愈导致的异常作废,排除管理员手动作废
|
||||
->whereLike('void_reason', 'system_recover:%')
|
||||
->field(['id', 'period_no', 'void_reason', 'update_time'])
|
||||
->order('id', 'desc')
|
||||
->limit($limit)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$periodIds = [];
|
||||
foreach ($rows as $row) {
|
||||
$pid = (int) ($row['id'] ?? 0);
|
||||
if ($pid > 0) {
|
||||
$periodIds[] = $pid;
|
||||
}
|
||||
}
|
||||
$refundAggByPeriod = [];
|
||||
$refundIdsByPeriod = [];
|
||||
if ($periodIds !== []) {
|
||||
$aggRows = Db::name('bet_order')
|
||||
->whereIn('period_id', $periodIds)
|
||||
->where('status', 3)
|
||||
->fieldRaw('period_id as pid, COUNT(*) as cnt, COUNT(DISTINCT user_id) as users, COALESCE(SUM(total_amount), 0) as amt')
|
||||
->group('period_id')
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($aggRows as $a) {
|
||||
$pid = (int) ($a['pid'] ?? 0);
|
||||
if ($pid <= 0) {
|
||||
continue;
|
||||
}
|
||||
$refundAggByPeriod[$pid] = [
|
||||
'orders' => (int) ($a['cnt'] ?? 0),
|
||||
'users' => (int) ($a['users'] ?? 0),
|
||||
'amount' => (string) ($a['amt'] ?? '0.00'),
|
||||
];
|
||||
}
|
||||
|
||||
$idRows = Db::name('bet_order')
|
||||
->whereIn('period_id', $periodIds)
|
||||
->where('status', 3)
|
||||
->field(['period_id', 'id'])
|
||||
->order('id', 'desc')
|
||||
->limit(2000)
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($idRows as $r) {
|
||||
$pid = (int) ($r['period_id'] ?? 0);
|
||||
$bid = (int) ($r['id'] ?? 0);
|
||||
if ($pid <= 0 || $bid <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($refundIdsByPeriod[$pid])) {
|
||||
$refundIdsByPeriod[$pid] = [];
|
||||
}
|
||||
if (count($refundIdsByPeriod[$pid]) < 50) {
|
||||
$refundIdsByPeriod[$pid][] = $bid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$list = [];
|
||||
foreach ($rows as $row) {
|
||||
$meta = $this->parseRecoverVoidReason(is_string($row['void_reason'] ?? null) ? $row['void_reason'] : '');
|
||||
$reason = is_string($row['void_reason'] ?? null) ? $row['void_reason'] : '';
|
||||
$isAutoRecover = $this->isSystemRecoverReason($reason);
|
||||
$pid = (int) ($row['id'] ?? 0);
|
||||
$agg = $refundAggByPeriod[$pid] ?? null;
|
||||
$users = (int) ($meta['users'] ?? 0);
|
||||
$orders = (int) ($meta['orders'] ?? 0);
|
||||
$amount = is_string($meta['amount'] ?? null) ? $meta['amount'] : '0.00';
|
||||
if (is_array($agg)) {
|
||||
if ($orders <= 0 && ($agg['orders'] ?? 0) > 0) {
|
||||
$orders = (int) ($agg['orders'] ?? 0);
|
||||
}
|
||||
if ($users <= 0 && ($agg['users'] ?? 0) > 0) {
|
||||
$users = (int) ($agg['users'] ?? 0);
|
||||
}
|
||||
if (bccomp($amount, '0', 2) <= 0 && is_string($agg['amount'] ?? null)) {
|
||||
$amount = (string) $agg['amount'];
|
||||
}
|
||||
}
|
||||
$list[] = [
|
||||
'id' => (int) ($row['id'] ?? 0),
|
||||
'period_no' => (string) ($row['period_no'] ?? ''),
|
||||
'abnormal_from_status' => $meta['from_status'],
|
||||
'refunded_user_count' => $meta['users'],
|
||||
'refunded_order_count' => $meta['orders'],
|
||||
'refunded_total_amount' => $meta['amount'],
|
||||
'refunded_user_count' => $users,
|
||||
'refunded_order_count' => $orders,
|
||||
'refunded_total_amount' => $amount,
|
||||
'refunded_order_ids' => $refundIdsByPeriod[$pid] ?? [],
|
||||
'recovered_at' => (int) ($row['update_time'] ?? 0),
|
||||
'void_reason' => $reason,
|
||||
'is_auto_recover' => $isAutoRecover ? 1 : 0,
|
||||
@@ -116,6 +225,77 @@ class Record extends Backend
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 某期对局的游玩(压注)记录列表(用于对局记录页弹窗查看)。
|
||||
*/
|
||||
public function playRecordList(WebmanRequest $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$periodIdRaw = $request->get('period_id');
|
||||
if (!is_numeric((string) $periodIdRaw)) {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
$periodId = (int) $periodIdRaw;
|
||||
if ($periodId <= 0) {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
|
||||
$pageRaw = $request->get('page', 1);
|
||||
$page = is_numeric((string) $pageRaw) ? (int) $pageRaw : 1;
|
||||
if ($page < 1) {
|
||||
$page = 1;
|
||||
}
|
||||
|
||||
$limitRaw = $request->get('limit', 30);
|
||||
$limit = is_numeric((string) $limitRaw) ? (int) $limitRaw : 30;
|
||||
if ($limit < 1) {
|
||||
$limit = 1;
|
||||
}
|
||||
if ($limit > 200) {
|
||||
$limit = 200;
|
||||
}
|
||||
|
||||
$query = Db::name('game_play_record')
|
||||
->alias('pr')
|
||||
->leftJoin('user u', 'u.id = pr.user_id')
|
||||
->leftJoin('channel c', 'c.id = pr.channel_id')
|
||||
->where('pr.period_id', $periodId);
|
||||
|
||||
$total = (int) $query->count('pr.id');
|
||||
$list = $query
|
||||
->field([
|
||||
'pr.id',
|
||||
'pr.period_id',
|
||||
'pr.user_id',
|
||||
'pr.channel_id',
|
||||
'pr.pick_numbers',
|
||||
'pr.total_amount',
|
||||
'pr.streak_at_bet',
|
||||
'pr.is_auto',
|
||||
'pr.win_amount',
|
||||
'pr.jackpot_extra_amount',
|
||||
'pr.status',
|
||||
'pr.idempotency_key',
|
||||
'pr.create_time',
|
||||
'pr.update_time',
|
||||
'u.username as user_username',
|
||||
'c.name as channel_name',
|
||||
])
|
||||
->order('pr.id', 'desc')
|
||||
->page($page, $limit)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{from_status:int,users:int,orders:int,amount:string}
|
||||
*/
|
||||
|
||||
@@ -49,7 +49,7 @@ class Game extends MobileBase
|
||||
$user = $this->auth->getUser();
|
||||
return $this->mobileSuccess([
|
||||
'server_time' => $now,
|
||||
'runtime_enabled' => GameRecordService::isLiveRuntimeEnabled(),
|
||||
'runtime_enabled' => GameRecordService::getConfigBool(GameRecordService::KEY_AUTO_CREATE),
|
||||
'period' => [
|
||||
'period_no' => (string) ($periodRow['period_no'] ?? ''),
|
||||
'status' => $this->mapPeriodStatus($periodRow['status'] ?? null),
|
||||
@@ -107,7 +107,7 @@ class Game extends MobileBase
|
||||
$now = time();
|
||||
$startAt = $this->intValue($periodRow['period_start_at'] ?? 0);
|
||||
return $this->mobileSuccess([
|
||||
'runtime_enabled' => GameRecordService::isLiveRuntimeEnabled(),
|
||||
'runtime_enabled' => GameRecordService::getConfigBool(GameRecordService::KEY_AUTO_CREATE),
|
||||
'period_id' => $periodRow['id'],
|
||||
'period_no' => $periodRow['period_no'],
|
||||
'status' => $this->mapPeriodStatus($periodRow['status'] ?? null),
|
||||
@@ -169,7 +169,7 @@ class Game extends MobileBase
|
||||
$numberCount = (string) count($numbers);
|
||||
$totalAmount = bcmul($singleAmount, $numberCount, 2);
|
||||
|
||||
if (!GameRecordService::isLiveRuntimeEnabled()) {
|
||||
if (!GameRecordService::getConfigBool(GameRecordService::KEY_AUTO_CREATE)) {
|
||||
return $this->mobileError(3001, 'Game is paused');
|
||||
}
|
||||
$period = GameRecord::where('period_no', $periodNo)->find();
|
||||
|
||||
@@ -16,7 +16,6 @@ class GamePeriod extends Model
|
||||
'period_start_at' => 'integer',
|
||||
'status' => 'integer',
|
||||
'draw_mode' => 'integer',
|
||||
'preset_number' => 'integer',
|
||||
'result_number' => 'integer',
|
||||
];
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ class GameRecord extends Model
|
||||
'period_start_at' => 'integer',
|
||||
'status' => 'integer',
|
||||
'draw_mode' => 'integer',
|
||||
'preset_number' => 'integer',
|
||||
'result_number' => 'integer',
|
||||
'ai_locked_number' => 'integer',
|
||||
'pending_draw_number' => 'integer',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,30 +47,25 @@ final class GameHotDataRedis
|
||||
if ($configKey === '') {
|
||||
return null;
|
||||
}
|
||||
if (array_key_exists($configKey, self::$gcLocal)) {
|
||||
$cachedLocal = self::$gcLocal[$configKey];
|
||||
return is_array($cachedLocal) ? $cachedLocal : null;
|
||||
}
|
||||
// game_config 为全局配置,多进程/多 worker 间必须强一致;
|
||||
// 因此不使用进程内本地缓存(gcLocal),避免某个进程读到旧值导致前端回弹/行为冲突。
|
||||
if (self::enabled()) {
|
||||
$cached = self::redisGet(self::KEY_GC . $configKey);
|
||||
if ($cached !== null && $cached !== '') {
|
||||
$decoded = json_decode($cached, true);
|
||||
if (is_array($decoded)) {
|
||||
self::$gcLocal[$configKey] = $decoded;
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
}
|
||||
$row = Db::name('game_config')->where('config_key', $configKey)->find();
|
||||
if (!$row) {
|
||||
self::$gcLocal[$configKey] = null;
|
||||
return null;
|
||||
}
|
||||
if (self::enabled()) {
|
||||
$ttl = self::intConfig('ttl_game_config', 86400);
|
||||
self::redisSetEx(self::KEY_GC . $configKey, $ttl, json_encode($row, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
self::$gcLocal[$configKey] = $row;
|
||||
return $row;
|
||||
}
|
||||
|
||||
@@ -88,17 +83,24 @@ final class GameHotDataRedis
|
||||
*/
|
||||
public static function gameConfigReplaceFromDb(string $configKey): void
|
||||
{
|
||||
if ($configKey === '' || !self::enabled()) {
|
||||
if ($configKey === '') {
|
||||
return;
|
||||
}
|
||||
// 无论是否启用 Redis 热点缓存,都要刷新进程内缓存,避免同一 worker 读到旧值
|
||||
$row = Db::name('game_config')->where('config_key', $configKey)->find();
|
||||
if (!$row) {
|
||||
self::gameConfigForget($configKey);
|
||||
self::$gcLocal[$configKey] = null;
|
||||
if (self::enabled()) {
|
||||
self::redisDel(self::KEY_GC . $configKey);
|
||||
}
|
||||
return;
|
||||
}
|
||||
$ttl = self::intConfig('ttl_game_config', 86400);
|
||||
self::redisSetEx(self::KEY_GC . $configKey, $ttl, json_encode($row, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
self::$gcLocal[$configKey] = $row;
|
||||
if (self::enabled()) {
|
||||
$ttl = self::intConfig('ttl_game_config', 86400);
|
||||
self::redisSetEx(self::KEY_GC . $configKey, $ttl, json_encode($row, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -202,7 +202,9 @@ final class GameLiveService
|
||||
GameHotDataCoordinator::afterUserCommitted($uid);
|
||||
}
|
||||
}
|
||||
GameRecordService::bootstrapPeriodWhenRuntimeEnabled();
|
||||
// 异常对局作废后:自动暂停游戏,不自动创建新一期;需管理员手动开启「游戏运行」才会重新开局
|
||||
GameRecordService::setAutoCreateEnabled(false);
|
||||
GameHotDataCoordinator::afterGameConfigKeyCommitted(GameRecordService::KEY_AUTO_CREATE);
|
||||
self::publishSnapshot(null);
|
||||
Log::info('game live startup marked abnormal and refunded', [
|
||||
'record_id' => $recordId,
|
||||
@@ -733,6 +735,7 @@ final class GameLiveService
|
||||
$refundedUserIds = [];
|
||||
try {
|
||||
$now = time();
|
||||
$refund = ['user_ids' => [], 'order_count' => 0, 'total_amount' => '0.00', 'order_ids' => []];
|
||||
Db::startTrans();
|
||||
try {
|
||||
$refund = self::refundPendingBetsSummaryForPeriodLocked($rid, $now);
|
||||
@@ -750,9 +753,9 @@ final class GameLiveService
|
||||
Db::rollback();
|
||||
return ['ok' => false, 'msg' => __('Void failed') . ': ' . $e->getMessage()];
|
||||
}
|
||||
GameRecordService::setLiveRuntimeEnabled(false);
|
||||
GameRecordService::setAutoCreateEnabled(false);
|
||||
GameHotDataCoordinator::afterGameRecordCommitted($rid);
|
||||
GameHotDataCoordinator::afterGameConfigKeyCommitted(GameRecordService::KEY_LIVE_RUNTIME);
|
||||
GameHotDataCoordinator::afterGameConfigKeyCommitted(GameRecordService::KEY_AUTO_CREATE);
|
||||
foreach ($refundedUserIds as $uid) {
|
||||
if ($uid > 0) {
|
||||
GameHotDataCoordinator::afterUserCommitted($uid);
|
||||
@@ -764,6 +767,7 @@ final class GameLiveService
|
||||
'ok' => true,
|
||||
'msg' => __('Period voided'),
|
||||
'record' => self::reloadRecord($rid),
|
||||
'refund' => $refund,
|
||||
];
|
||||
} finally {
|
||||
GameHotDataLock::release(GameHotDataLock::TYPE_GAME_RECORD, (string) $rid, $lock['token'], $lock['redis_lock']);
|
||||
@@ -780,13 +784,14 @@ final class GameLiveService
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{user_ids:list<int>,order_count:int,total_amount:string}
|
||||
* @return array{user_ids:list<int>,order_count:int,total_amount:string,order_ids:list<int>}
|
||||
*/
|
||||
private static function refundPendingBetsSummaryForPeriodLocked(int $periodId, int $now): array
|
||||
{
|
||||
$userIdSet = [];
|
||||
$orderCount = 0;
|
||||
$totalAmount = '0.00';
|
||||
$orderIds = [];
|
||||
$bets = Db::name('bet_order')
|
||||
->where('period_id', $periodId)
|
||||
->where('status', 1)
|
||||
@@ -806,6 +811,8 @@ final class GameLiveService
|
||||
'status' => 3,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
$orderCount++;
|
||||
$orderIds[] = $betId;
|
||||
continue;
|
||||
}
|
||||
$before = (string) (Db::name('user')->where('id', $userId)->value('coin') ?? '0');
|
||||
@@ -832,7 +839,7 @@ final class GameLiveService
|
||||
UserWalletRecord::create([
|
||||
'user_id' => $userId,
|
||||
'channel_id' => $channelId,
|
||||
'biz_type' => 'bet_void',
|
||||
'biz_type' => 'void_refund',
|
||||
'direction' => 1,
|
||||
'amount' => $total,
|
||||
'balance_before' => $before,
|
||||
@@ -844,6 +851,7 @@ final class GameLiveService
|
||||
$userIdSet[$userId] = true;
|
||||
$orderCount++;
|
||||
$totalAmount = bcadd($totalAmount, $total, 2);
|
||||
$orderIds[] = $betId;
|
||||
}
|
||||
|
||||
$out = [];
|
||||
@@ -855,6 +863,7 @@ final class GameLiveService
|
||||
'user_ids' => $out,
|
||||
'order_count' => $orderCount,
|
||||
'total_amount' => $totalAmount,
|
||||
'order_ids' => $orderIds,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,6 @@ final class GameRecordService
|
||||
|
||||
public const KEY_MANUAL_CREATE = 'period_manual_create_enabled';
|
||||
|
||||
/** 后台「游戏实时对局」运行开关:0=暂停自动开奖与派彩后自动创建下一期;1=运行 */
|
||||
public const KEY_LIVE_RUNTIME = 'game_live_runtime_enabled';
|
||||
|
||||
private const ACTIVE_STATUSES = [0, 1, 2, 3];
|
||||
|
||||
public static function getConfigBool(string $key): bool
|
||||
@@ -53,9 +50,6 @@ final class GameRecordService
|
||||
|
||||
public static function tickAutoCreate(): void
|
||||
{
|
||||
if (!self::isLiveRuntimeEnabled()) {
|
||||
return;
|
||||
}
|
||||
if (!self::getConfigBool(self::KEY_AUTO_CREATE)) {
|
||||
return;
|
||||
}
|
||||
@@ -86,7 +80,8 @@ final class GameRecordService
|
||||
|
||||
public static function createNextRecordAfterDraw(): ?string
|
||||
{
|
||||
if (!self::isLiveRuntimeEnabled()) {
|
||||
// 派彩结束后是否自动开新局:由 period_auto_create_enabled 控制
|
||||
if (!self::getConfigBool(self::KEY_AUTO_CREATE)) {
|
||||
return null;
|
||||
}
|
||||
if (self::hasActiveRecord()) {
|
||||
@@ -96,29 +91,16 @@ final class GameRecordService
|
||||
}
|
||||
|
||||
/**
|
||||
* 未配置键时视为开启(兼容旧库未跑迁移)。
|
||||
* 实时对局页「自动创建下一局」开关(兼容旧命名 runtime)。
|
||||
*/
|
||||
public static function isLiveRuntimeEnabled(): bool
|
||||
{
|
||||
$row = GameHotDataRedis::gameConfigRow(self::KEY_LIVE_RUNTIME);
|
||||
if ($row === null) {
|
||||
return true;
|
||||
}
|
||||
$v = $row['config_value'] ?? '';
|
||||
return $v === '1' || $v === 1;
|
||||
return self::getConfigBool(self::KEY_AUTO_CREATE);
|
||||
}
|
||||
|
||||
public static function setLiveRuntimeEnabled(bool $enabled): void
|
||||
{
|
||||
$now = time();
|
||||
$v = $enabled ? '1' : '0';
|
||||
self::upsertConfig(
|
||||
self::KEY_LIVE_RUNTIME,
|
||||
$v,
|
||||
'int',
|
||||
'后台「游戏实时对局」运行开关:0=维护(禁止下注、结束后不自动开新期,当局仍自动开奖并结算);1=运行',
|
||||
$now
|
||||
);
|
||||
self::setAutoCreateEnabled($enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,6 +108,9 @@ final class GameRecordService
|
||||
*/
|
||||
public static function bootstrapPeriodWhenRuntimeEnabled(): void
|
||||
{
|
||||
if (!self::getConfigBool(self::KEY_AUTO_CREATE)) {
|
||||
return;
|
||||
}
|
||||
if (self::hasActiveRecord()) {
|
||||
return;
|
||||
}
|
||||
@@ -135,6 +120,13 @@ final class GameRecordService
|
||||
}
|
||||
}
|
||||
|
||||
public static function setAutoCreateEnabled(bool $enabled): void
|
||||
{
|
||||
$now = time();
|
||||
$v = $enabled ? '1' : '0';
|
||||
self::upsertConfig(self::KEY_AUTO_CREATE, $v, 'int', '是否允许自动创建下一局(全局仅一局)', $now);
|
||||
}
|
||||
|
||||
private static function createNextRecordRow(): string
|
||||
{
|
||||
$periodNo = self::generatePeriodNo();
|
||||
|
||||
@@ -15,12 +15,11 @@ class GamePeriod extends Validate
|
||||
'period_start_at' => 'integer',
|
||||
'status' => 'require|in:0,1,2,3,4,5',
|
||||
'draw_mode' => 'in:0,1',
|
||||
'preset_number' => 'between:1,36',
|
||||
'result_number' => 'between:1,36',
|
||||
];
|
||||
|
||||
protected $scene = [
|
||||
'add' => ['period_no', 'period_start_at', 'status', 'draw_mode', 'preset_number', 'result_number'],
|
||||
'edit' => ['period_start_at', 'status', 'draw_mode', 'preset_number', 'result_number'],
|
||||
'add' => ['period_no', 'period_start_at', 'status', 'draw_mode', 'result_number'],
|
||||
'edit' => ['period_start_at', 'status', 'draw_mode', 'result_number'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -15,12 +15,11 @@ class GameRecord extends Validate
|
||||
'period_start_at' => 'integer',
|
||||
'status' => 'require|in:0,1,2,3,4,5',
|
||||
'draw_mode' => 'in:0,1',
|
||||
'preset_number' => 'between:1,36',
|
||||
'result_number' => 'between:1,36',
|
||||
];
|
||||
|
||||
protected $scene = [
|
||||
'add' => ['period_no', 'period_start_at', 'status', 'draw_mode', 'preset_number', 'result_number'],
|
||||
'edit' => ['period_start_at', 'status', 'draw_mode', 'preset_number', 'result_number'],
|
||||
'add' => ['period_no', 'period_start_at', 'status', 'draw_mode', 'result_number'],
|
||||
'edit' => ['period_start_at', 'status', 'draw_mode', 'result_number'],
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user