resolveLang($request->post('lang', 'zh')); $games = $this->buildPublicGameList($lang, $this->agentDeptId($request)); return $this->success([ 'game_list' => $games, ]); } /** * 获取游戏大厅信息(脱敏) * POST 参数:lang(可选,zh/en,默认 zh) */ public function getGameHall(Request $request): Response { $lang = $this->resolveLang($request->post('lang', 'zh')); $games = $this->buildPublicGameList($lang, $this->agentDeptId($request)); $hallUrl = ''; if (!empty($games)) { $hallUrl = $games[0]['hall_url'] ?? ''; } return $this->success([ 'provider' => 'Dicey Fun', 'provider_code' => 'DF', 'hall_url' => $hallUrl, 'game_list' => $games, ]); } /** * 获取游戏地址 * 根据 username 创建登录 token(JWT),拼接游戏地址返回 */ public function getGameUrl(Request $request): Response { $username = trim((string) ($request->post('username', ''))); $time = trim((string) ($request->post('time', ''))); if ($username === '') { return $this->fail('username is required', ReturnCode::PARAMS_ERROR); } if ($time === '') { $time = (string) time(); } $deptId = $this->agentDeptId($request); $adminId = $this->agentAdminId($request); $adminIdsInTopDept = UserLogic::getAdminIdsByAgentIdTopDept(trim((string) ($request->agent_id ?? ''))); $lang = trim((string) ($request->post('lang', 'zh'))); $lang = in_array($lang, ['en', 'zh'], true) ? $lang : 'zh'; try { $logic = new UserLogic(); // 平台 v1 已通过 api-key + auth-token 双重校验,此处不再做 password 校验 $result = $logic->loginByUsername($username, '', $lang, 0.0, $time, $adminId, $adminIdsInTopDept, $deptId, true); } catch (\plugin\saiadmin\exception\ApiException $e) { return $this->fail($e->getMessage(), ReturnCode::PARAMS_ERROR); } $gameUrlBase = rtrim(config('api.game_url', 'dice-game.h55555game.top'), '/'); $tokenInUrl = str_replace('%3D', '=', urlencode($result['token'])); $url = $gameUrlBase . '/?token=' . $tokenInUrl . '&lang=' . $lang; return $this->success([ 'url' => $url, ]); } /** * 获取用户信息 * 参数:username(POST JSON / 表单 / Query 均可,input 合并读取降低偶发空参) * 返回 DicePlayer 中非敏感信息;短期 Redis 快照 + 窄字段查询降低延迟 */ public function getPlayerInfo(Request $request): Response { $usernameRaw = $request->input('username', ''); $username = is_string($usernameRaw) ? trim($usernameRaw) : ''; $deptId = $this->agentDeptId($request); if ($username === '') { return $this->fail('username is required', ReturnCode::PARAMS_ERROR); } $cached = UserCache::getPlayerInfoSnapshotByUsername($this->scopedUsername($deptId, $username)); if ($cached !== null) { return $this->success($cached); } try { $logic = new UserLogic(); $player = $logic->findOrCreatePlayerByUsername( $username, $this->agentAdminId($request), $deptId > 0 ? $deptId : null ); } catch (\plugin\saiadmin\exception\ApiException $e) { return $this->fail($e->getMessage(), ReturnCode::PARAMS_ERROR); } $player = DicePlayer::field(self::PLAYER_INFO_DB_FIELDS)->where('id', (int) $player->id)->find(); if (!$player) { return $this->fail('User not found', ReturnCode::PARAMS_ERROR); } $hidden = ['password', 'lottery_config_id', 't1_weight', 't2_weight', 't3_weight', 't4_weight', 't5_weight', 'delete_time']; $info = $player->hidden($hidden)->toArray(); UserCache::setPlayerInfoSnapshotByUsername($this->scopedUsername($deptId, $username), $info); return $this->success($info); } /** * 解析拉取类流水接口的 limit(与 getPlayerGameRecord / getPlayerWalletRecord / getPlayerTicketRecord 共用) */ private function resolvePullRecordLimit(Request $request): int { $limit = (int) $request->post('limit', self::PLAYER_PULL_RECORD_DEFAULT_LIMIT); if ($limit < 1 || $limit > self::PLAYER_PULL_RECORD_MAX_LIMIT) { $limit = self::PLAYER_PULL_RECORD_DEFAULT_LIMIT; } return $limit; } /** * 将 start_create_time / end_create_time 规范为「最近 7 天内」且跨度不超过 7 天的闭区间。 * * @return array{ok: true, start: string, end: string}|array{ok: false, message: string} */ private function resolvePullRecordTimeWindow(string $startRaw, string $endRaw): array { $nowTs = time(); $weekAgoTs = strtotime('-' . self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS . ' days', $nowTs); if ($weekAgoTs === false) { $weekAgoTs = $nowTs - self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS * 86400; } $nowStr = date('Y-m-d H:i:s', $nowTs); $weekAgoStr = date('Y-m-d H:i:s', $weekAgoTs); if ($startRaw === '' && $endRaw === '') { return ['ok' => true, 'start' => $weekAgoStr, 'end' => $nowStr]; } $startTs = $startRaw === '' ? null : strtotime($startRaw); $endTs = $endRaw === '' ? null : strtotime($endRaw); if ($startRaw !== '' && $startTs === false) { return ['ok' => false, 'message' => 'Invalid start_create_time']; } if ($endRaw !== '' && $endTs === false) { return ['ok' => false, 'message' => 'Invalid end_create_time']; } if ($startRaw === '' && $endRaw !== '' && $endTs !== null) { $endTs = min($endTs, $nowTs); if ($endTs < $weekAgoTs) { return ['ok' => false, 'message' => 'end_create_time must be within the last 7 days']; } $startTs = strtotime('-' . self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS . ' days', $endTs); if ($startTs === false) { $startTs = $endTs - self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS * 86400; } if ($startTs < $weekAgoTs) { $startTs = $weekAgoTs; } return ['ok' => true, 'start' => date('Y-m-d H:i:s', $startTs), 'end' => date('Y-m-d H:i:s', $endTs)]; } if ($startRaw !== '' && $startTs !== null && $endRaw === '') { if ($startTs > $nowTs) { return ['ok' => false, 'message' => 'start_create_time cannot be in the future']; } if ($startTs < $weekAgoTs) { return ['ok' => false, 'message' => 'start_create_time cannot be earlier than 7 days ago']; } $endTs = strtotime('+' . self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS . ' days', $startTs); if ($endTs === false) { $endTs = $startTs + self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS * 86400; } $endTs = min($endTs, $nowTs); return ['ok' => true, 'start' => date('Y-m-d H:i:s', $startTs), 'end' => date('Y-m-d H:i:s', $endTs)]; } if ($startTs !== null && $endTs !== null) { if ($endTs > $nowTs) { $endTs = $nowTs; } if ($startTs > $endTs) { return ['ok' => false, 'message' => 'start_create_time cannot be after end_create_time']; } if ($startTs < $weekAgoTs) { return ['ok' => false, 'message' => 'start_create_time cannot be earlier than 7 days ago']; } if ($endTs < $weekAgoTs) { return ['ok' => false, 'message' => 'end_create_time must be within the last 7 days']; } $maxEndForStart = strtotime('+' . self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS . ' days', $startTs); if ($maxEndForStart === false) { $maxEndForStart = $startTs + self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS * 86400; } if ($endTs > $maxEndForStart) { return ['ok' => false, 'message' => 'Time range cannot exceed 7 days']; } return ['ok' => true, 'start' => date('Y-m-d H:i:s', $startTs), 'end' => date('Y-m-d H:i:s', $endTs)]; } return ['ok' => false, 'message' => 'Invalid time parameters']; } /** * 获取游戏记录 * POST 参数:username(非必填), start_create_time, end_create_time(可选,须在「最近 7 天」内且跨度不超过 7 天;均不传则默认最近 7 天), limit(非必填,返回条数上限) * 返回 DicePlayRecord 中非敏感信息 */ public function getPlayerGameRecord(Request $request): Response { $username = trim((string) ($request->post('username', ''))); $deptId = $this->agentDeptId($request); $startCreateTime = trim((string) ($request->post('start_create_time', ''))); $endCreateTime = trim((string) ($request->post('end_create_time', ''))); $window = $this->resolvePullRecordTimeWindow($startCreateTime, $endCreateTime); if (!$window['ok']) { return $this->fail($window['message'], ReturnCode::PARAMS_ERROR); } $limit = $this->resolvePullRecordLimit($request); $query = DicePlayRecord::where('dept_id', $deptId)->order('id', 'desc'); if ($username !== '') { $player = $this->findPlayerByUsername($username, $deptId); if (!$player) { return $this->success([]); } $query->where('player_id', (int) $player->id); } $query->where('create_time', '>=', $window['start']); $query->where('create_time', '<=', $window['end']); $list = $query->limit($limit)->select()->toArray(); $playerIds = array_unique(array_column($list, 'player_id')); if (!empty($playerIds)) { $players = DicePlayer::whereIn('id', $playerIds)->where('dept_id', $deptId)->field('id,username,phone')->select()->toArray(); $playerMap = []; foreach ($players as $p) { $playerMap[(int) ($p['id'] ?? 0)] = $p; } foreach ($list as &$item) { $item['dice_player'] = $playerMap[(int) ($item['player_id'] ?? 0)] ?? null; } } return $this->success($list); } /** * 获取钱包流水 * POST 参数:username(非必填), start_create_time, end_create_time(可选,规则同 getPlayerGameRecord), limit(非必填) * 返回 DicePlayerWalletRecord 中非敏感信息 */ public function getPlayerWalletRecord(Request $request): Response { $username = trim((string) ($request->post('username', ''))); $deptId = $this->agentDeptId($request); $startCreateTime = trim((string) ($request->post('start_create_time', ''))); $endCreateTime = trim((string) ($request->post('end_create_time', ''))); $window = $this->resolvePullRecordTimeWindow($startCreateTime, $endCreateTime); if (!$window['ok']) { return $this->fail($window['message'], ReturnCode::PARAMS_ERROR); } $limit = $this->resolvePullRecordLimit($request); $query = DicePlayerWalletRecord::where('dept_id', $deptId)->order('id', 'desc'); if ($username !== '') { $player = $this->findPlayerByUsername($username, $deptId); if (!$player) { return $this->success([]); } $query->where('player_id', (int) $player->id); } $query->where('create_time', '>=', $window['start']); $query->where('create_time', '<=', $window['end']); $list = $query->with(['dicePlayer' => function ($q) { $q->field('id,username,phone'); }])->limit($limit)->select()->toArray(); return $this->success($list); } /** * 获取用户中奖券获取记录 * POST 参数:username(非必填), start_create_time, end_create_time(可选,规则同 getPlayerGameRecord), limit(非必填) * 返回 DicePlayerTicketRecord 中非敏感信息 */ public function getPlayerTicketRecord(Request $request): Response { $username = trim((string) ($request->post('username', ''))); $deptId = $this->agentDeptId($request); $startCreateTime = trim((string) ($request->post('start_create_time', ''))); $endCreateTime = trim((string) ($request->post('end_create_time', ''))); $window = $this->resolvePullRecordTimeWindow($startCreateTime, $endCreateTime); if (!$window['ok']) { return $this->fail($window['message'], ReturnCode::PARAMS_ERROR); } $limit = $this->resolvePullRecordLimit($request); $query = DicePlayerTicketRecord::where('dept_id', $deptId)->order('id', 'desc'); if ($username !== '') { $player = $this->findPlayerByUsername($username, $deptId); if (!$player) { return $this->success([]); } $query->where('player_id', (int) $player->id); } $query->where('create_time', '>=', $window['start']); $query->where('create_time', '<=', $window['end']); $list = $query->with(['dicePlayer' => function ($q) { $q->field('id,username,phone'); }])->limit($limit)->select()->toArray(); return $this->success($list); } /** * 钱包转入转出 * POST 参数:username(必填), coin(转入>0 或 转出<0) * 创建 DicePlayerWalletRecord,type: 0=充值(coin>0), 1=提现(coin<0) * 返回创建的记录 */ public function setPlayerWallet(Request $request): Response { $username = trim((string) ($request->post('username', ''))); $deptId = $this->agentDeptId($request); $coin = $request->post('coin'); if ($username === '') { return $this->fail('username is required', ReturnCode::PARAMS_ERROR); } if ($coin === null || $coin === '') { return $this->fail('coin is required', ReturnCode::PARAMS_ERROR); } $coinVal = (float) $coin; if ($coinVal === 0.0) { return $this->fail('coin cannot be 0', ReturnCode::PARAMS_ERROR); } $player = $this->findPlayerByUsername($username, $deptId); if (!$player) { return $this->fail('User not found', ReturnCode::PARAMS_ERROR); } $walletBefore = (float) ($player->coin ?? 0); $walletAfter = $walletBefore + $coinVal; if ($coinVal < 0 && $walletBefore < -$coinVal) { return $this->fail('Insufficient balance to transfer', ReturnCode::BUSINESS_ERROR); } $type = $coinVal > 0 ? 0 : 1; $remark = $coinVal > 0 ? '充值' : '提现'; try { Db::startTrans(); $player->coin = $walletAfter; $player->save(); $adminId = ($player->admin_id ?? null) ? (int) $player->admin_id : null; $record = DicePlayerWalletRecord::create([ 'dept_id' => $deptId, 'player_id' => (int) $player->id, 'admin_id' => $adminId, 'coin' => $coinVal, 'type' => $type, 'wallet_before' => $walletBefore, 'wallet_after' => $walletAfter, 'total_ticket_count' => 0, 'paid_ticket_count' => 0, 'free_ticket_count' => 0, 'remark' => $remark, 'user_id' => 0, ]); Db::commit(); } catch (\Throwable $e) { Db::rollback(); return $this->fail('Operation failed: ' . $e->getMessage(), ReturnCode::SERVER_ERROR); } // 出于安全:删除该玩家相关缓存,后续 API 调用按需重建 UserCache::deleteUser($player->id); if ($player->username !== '') { UserCache::deletePlayerByUsername($player->username); UserCache::deletePlayerByUsername($this->scopedUsername($deptId, (string) $player->username)); } $recordArr = $record->toArray(); $recordArr['dice_player'] = ['id' => (int) $player->id, 'username' => $player->username ?? '', 'phone' => $player->phone ?? '']; return $this->success($recordArr); } private function resolveLang($lang): string { if (!is_string($lang)) { return 'zh'; } $langValue = strtolower(trim($lang)); if (!in_array($langValue, ['zh', 'en'], true)) { return 'zh'; } return $langValue; } private function buildPublicGameList(string $lang, int $deptId): array { $rows = DiceGame::where('status', 1) ->where('dept_id', $deptId) ->order('sort', 'asc') ->order('id', 'asc') ->field(array_merge(self::GAME_PUBLIC_FIELDS, ['game_name', 'game_name_en'])) ->select() ->toArray(); if (empty($rows)) { return []; } $games = []; foreach ($rows as $row) { $game = []; foreach (self::GAME_PUBLIC_FIELDS as $fieldName) { $game[$fieldName] = $row[$fieldName] ?? ''; } $gameNameEn = $row['game_name_en'] ?? ''; $game['game_name'] = $lang === 'en' && $gameNameEn !== '' ? $gameNameEn : ($row['game_name'] ?? ''); $games[] = $game; } return $games; } private function agentDeptId(Request $request): int { return (int) ($request->agent_dept_id ?? 0); } private function agentAdminId(Request $request): ?int { $adminId = (int) ($request->agent_admin_id ?? 0); return $adminId > 0 ? $adminId : null; } private function scopedUsername(int $deptId, string $username): string { return $deptId . ':' . $username; } private function findPlayerByUsername(string $username, int $deptId): ?DicePlayer { $player = DicePlayer::where('username', $username)->where('dept_id', $deptId)->find(); return $player ?: null; } }