diff --git a/API对接文档.md b/API对接文档.md index 1e808de..a3f1501 100644 --- a/API对接文档.md +++ b/API对接文档.md @@ -166,15 +166,16 @@ signature = md5(agent_id + secret + time) | 参数名 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | -| username | 否 | string | 不传则查询全量(按时间/分页) | -| start_create_time | 否 | string | 开始时间(与数据库存储格式一致) | -| end_create_time | 否 | string | 结束时间(与数据库存储格式一致) | -| page | 否 | int | 默认 1 | -| limit | 否 | int | 默认 20,最大 100 | +| username | 否 | string | 不传则不按用户筛选;数据仅来自「最近 7 天」时间窗(见下) | +| start_create_time | 否 | string | 开始时间;与 `end_create_time` 均不传时,服务端默认最近 7 天 | +| end_create_time | 否 | string | 结束时间 | +| limit | 否 | int | 返回条数上限,默认 20;小于 1 或大于 2000 时按 20 处理 | + +**时间规则**:仅允许查询「当前服务器时间起向前 7 天」内的 `create_time`;`start_create_time` 与 `end_create_time` 的跨度不得超过 7 天;`end_create_time` 晚于当前时间时按当前时间截断。非法区间返回参数错误。 #### 响应说明 -返回游玩记录列表,并附带 `dice_player`(包含 `id/username/phone`)。 +返回游玩记录列表(按 `id` 倒序,最多 `limit` 条),并附带 `dice_player`(包含 `id/username/phone`)。 ### 3.4 获取钱包流水 @@ -182,7 +183,7 @@ signature = md5(agent_id + secret + time) - **方法**:POST - **请求头**:`auth-token` -参数与分页规则同 3.3,返回钱包流水列表(附带 `dice_player`)。 +参数与时间规则同 3.3(无 `page`,仅 `limit` 限制条数),返回钱包流水列表(附带 `dice_player`)。 ### 3.5 获取中奖券获取记录 @@ -190,7 +191,7 @@ signature = md5(agent_id + secret + time) - **方法**:POST - **请求头**:`auth-token` -参数与分页规则同 3.3,返回中奖券记录列表(附带 `dice_player`)。 +参数与时间规则同 3.3,返回中奖券记录列表(附带 `dice_player`)。 ### 3.6 平台钱包转入/转出 diff --git a/server/app/api/controller/v1/GameController.php b/server/app/api/controller/v1/GameController.php index 9bd3cdf..4fb2f81 100644 --- a/server/app/api/controller/v1/GameController.php +++ b/server/app/api/controller/v1/GameController.php @@ -23,6 +23,14 @@ use app\api\cache\UserCache; */ class GameController extends BaseController { + /** 游戏记录 / 钱包流水 / 中奖券记录等拉取类接口的条数上限(与对接文档一致) */ + private const PLAYER_PULL_RECORD_MAX_LIMIT = 2000; + + private const PLAYER_PULL_RECORD_DEFAULT_LIMIT = 20; + + /** 拉取类流水仅允许查询「当前时间起向前」的最大天数(含起止) */ + private const PLAYER_PULL_RECORD_MAX_RANGE_DAYS = 7; + private const GAME_PUBLIC_FIELDS = [ 'provider', 'provider_code', @@ -144,9 +152,110 @@ class GameController extends BaseController 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(非必填,没填则不筛选时间) + * POST 参数:username(非必填), start_create_time, end_create_time(可选,须在「最近 7 天」内且跨度不超过 7 天;均不传则默认最近 7 天), limit(非必填,返回条数上限) * 返回 DicePlayRecord 中非敏感信息 */ public function getPlayerGameRecord(Request $request): Response @@ -154,15 +263,11 @@ class GameController extends BaseController $username = trim((string) ($request->post('username', ''))); $startCreateTime = trim((string) ($request->post('start_create_time', ''))); $endCreateTime = trim((string) ($request->post('end_create_time', ''))); - $page = (int) $request->post('page', 1); - $limit = (int) $request->post('limit', 20); - - if ($page < 1) { - $page = 1; - } - if ($limit < 1 || $limit > 100) { - $limit = 20; + $window = $this->resolvePullRecordTimeWindow($startCreateTime, $endCreateTime); + if (!$window['ok']) { + return $this->fail($window['message'], ReturnCode::PARAMS_ERROR); } + $limit = $this->resolvePullRecordLimit($request); $query = DicePlayRecord::order('id', 'desc'); @@ -174,14 +279,10 @@ class GameController extends BaseController $query->where('player_id', (int) $player->id); } - if ($startCreateTime !== '') { - $query->where('create_time', '>=', $startCreateTime); - } - if ($endCreateTime !== '') { - $query->where('create_time', '<=', $endCreateTime); - } + $query->where('create_time', '>=', $window['start']); + $query->where('create_time', '<=', $window['end']); - $list = $query->page($page, $limit)->select()->toArray(); + $list = $query->limit($limit)->select()->toArray(); $playerIds = array_unique(array_column($list, 'player_id')); if (!empty($playerIds)) { $players = DicePlayer::whereIn('id', $playerIds)->field('id,username,phone')->select()->toArray(); @@ -199,7 +300,7 @@ class GameController extends BaseController /** * 获取钱包流水 - * POST 参数:username(非必填,没填则不按用户筛选), start_create_time, end_create_time(非必填,没填则不筛选时间) + * POST 参数:username(非必填), start_create_time, end_create_time(可选,规则同 getPlayerGameRecord), limit(非必填) * 返回 DicePlayerWalletRecord 中非敏感信息 */ public function getPlayerWalletRecord(Request $request): Response @@ -207,15 +308,11 @@ class GameController extends BaseController $username = trim((string) ($request->post('username', ''))); $startCreateTime = trim((string) ($request->post('start_create_time', ''))); $endCreateTime = trim((string) ($request->post('end_create_time', ''))); - $page = (int) $request->post('page', 1); - $limit = (int) $request->post('limit', 20); - - if ($page < 1) { - $page = 1; - } - if ($limit < 1 || $limit > 100) { - $limit = 20; + $window = $this->resolvePullRecordTimeWindow($startCreateTime, $endCreateTime); + if (!$window['ok']) { + return $this->fail($window['message'], ReturnCode::PARAMS_ERROR); } + $limit = $this->resolvePullRecordLimit($request); $query = DicePlayerWalletRecord::order('id', 'desc'); @@ -227,23 +324,19 @@ class GameController extends BaseController $query->where('player_id', (int) $player->id); } - if ($startCreateTime !== '') { - $query->where('create_time', '>=', $startCreateTime); - } - if ($endCreateTime !== '') { - $query->where('create_time', '<=', $endCreateTime); - } + $query->where('create_time', '>=', $window['start']); + $query->where('create_time', '<=', $window['end']); $list = $query->with(['dicePlayer' => function ($q) { $q->field('id,username,phone'); - }])->page($page, $limit)->select()->toArray(); + }])->limit($limit)->select()->toArray(); return $this->success($list); } /** * 获取用户中奖券获取记录 - * POST 参数:username(非必填,没填则不按用户筛选), start_create_time, end_create_time(非必填,没填则不筛选时间) + * POST 参数:username(非必填), start_create_time, end_create_time(可选,规则同 getPlayerGameRecord), limit(非必填) * 返回 DicePlayerTicketRecord 中非敏感信息 */ public function getPlayerTicketRecord(Request $request): Response @@ -251,15 +344,11 @@ class GameController extends BaseController $username = trim((string) ($request->post('username', ''))); $startCreateTime = trim((string) ($request->post('start_create_time', ''))); $endCreateTime = trim((string) ($request->post('end_create_time', ''))); - $page = (int) $request->post('page', 1); - $limit = (int) $request->post('limit', 20); - - if ($page < 1) { - $page = 1; - } - if ($limit < 1 || $limit > 100) { - $limit = 20; + $window = $this->resolvePullRecordTimeWindow($startCreateTime, $endCreateTime); + if (!$window['ok']) { + return $this->fail($window['message'], ReturnCode::PARAMS_ERROR); } + $limit = $this->resolvePullRecordLimit($request); $query = DicePlayerTicketRecord::order('id', 'desc'); @@ -271,16 +360,12 @@ class GameController extends BaseController $query->where('player_id', (int) $player->id); } - if ($startCreateTime !== '') { - $query->where('create_time', '>=', $startCreateTime); - } - if ($endCreateTime !== '') { - $query->where('create_time', '<=', $endCreateTime); - } + $query->where('create_time', '>=', $window['start']); + $query->where('create_time', '<=', $window['end']); $list = $query->with(['dicePlayer' => function ($q) { $q->field('id,username,phone'); - }])->page($page, $limit)->select()->toArray(); + }])->limit($limit)->select()->toArray(); return $this->success($list); } diff --git a/server/app/api/middleware/ApiAccessLogMiddleware.php b/server/app/api/middleware/ApiAccessLogMiddleware.php new file mode 100644 index 0000000..c2fdd36 --- /dev/null +++ b/server/app/api/middleware/ApiAccessLogMiddleware.php @@ -0,0 +1,201 @@ +buildRequestLog($request); + $responseLog = null; + if ($response instanceof Response) { + $responseLog = $this->buildResponseLog($response); + } + + Log::info('api_access', [ + 'request' => $requestLog, + 'response' => $responseLog, + 'duration_ms' => $durationMs, + 'exception' => $thrown ? [ + 'class' => $thrown::class, + 'message' => $thrown->getMessage(), + ] : null, + ]); + } + } + + /** + * @return array + */ + private function buildRequestLog(Request $request): array + { + $headers = $request->header(); + if (!is_array($headers)) { + $headers = []; + } + $headersOut = []; + foreach ($headers as $name => $value) { + $nameStr = strtolower((string) $name); + if ($this->isSensitiveHeaderName($nameStr)) { + $headersOut[$nameStr] = $this->maskToken(is_string($value) ? $value : (string) $value); + } else { + $headersOut[$nameStr] = $value; + } + } + + $get = $request->get(); + $post = $request->post(); + $params = []; + if (is_array($get)) { + $params = array_merge($params, $get); + } + if (is_array($post)) { + $params = array_merge($params, $post); + } + $params = $this->sanitizeForLog($params); + + $paramsJson = json_encode($params, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE); + if (!is_string($paramsJson)) { + $paramsJson = ''; + } + if (strlen($paramsJson) > self::REQUEST_PARAM_MAX_LEN) { + $paramsJson = substr($paramsJson, 0, self::REQUEST_PARAM_MAX_LEN) . '...(truncated)'; + } + + return [ + 'method' => $request->method(), + 'path' => $request->path(), + 'uri' => $request->uri(), + 'ip' => method_exists($request, 'getRealIp') ? $request->getRealIp() : ($request->getRemoteIp() ?? ''), + 'content_type' => $request->header('content-type', ''), + 'headers' => $headersOut, + 'params' => $paramsJson, + 'agent_id' => $request->agent_id ?? null, + 'player_id' => $request->player_id ?? null, + ]; + } + + /** + * @return array + */ + private function buildResponseLog(Response $response): array + { + $raw = method_exists($response, 'rawBody') ? $response->rawBody() : ''; + if (!is_string($raw)) { + $raw = ''; + } + $bodyForLog = $raw; + $decoded = json_decode($raw, true); + if (is_array($decoded)) { + $sanitized = $this->sanitizeForLog($decoded); + $encoded = json_encode($sanitized, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE); + if (is_string($encoded)) { + $bodyForLog = $encoded; + } + } + if (strlen($bodyForLog) > self::RESPONSE_BODY_MAX_LEN) { + $bodyForLog = substr($bodyForLog, 0, self::RESPONSE_BODY_MAX_LEN) . '...(truncated)'; + } + + return [ + 'status' => $response->getStatusCode(), + 'body' => $bodyForLog, + ]; + } + + private function isSensitiveHeaderName(string $name): bool + { + return in_array($name, self::SENSITIVE_HEADER_NAMES, true); + } + + private function isSensitiveParamKey(string $keyLower): bool + { + foreach (self::SENSITIVE_PARAM_KEYS as $s) { + if ($keyLower === $s) { + return true; + } + } + return false; + } + + private function maskToken(string $value): string + { + $t = trim($value); + if ($t === '') { + return ''; + } + if (strlen($t) <= 12) { + return '***'; + } + return substr($t, 0, 8) . '***' . substr($t, -4); + } + + /** + * @param mixed $value + * @return mixed + */ + private function sanitizeForLog($value) + { + if (!is_array($value)) { + return $value; + } + $out = []; + foreach ($value as $k => $v) { + $keyLower = is_string($k) ? strtolower($k) : ''; + if ($keyLower !== '' && $this->isSensitiveParamKey($keyLower)) { + $out[$k] = '***'; + continue; + } + if (is_array($v)) { + $out[$k] = $this->sanitizeForLog($v); + continue; + } + $out[$k] = $v; + } + return $out; + } +} diff --git a/server/config/route.php b/server/config/route.php index a7500ed..540188b 100644 --- a/server/config/route.php +++ b/server/config/route.php @@ -13,13 +13,16 @@ */ use Webman\Route; -use app\api\middleware\TokenMiddleware; +use app\api\middleware\ApiAccessLogMiddleware; use app\api\middleware\AuthTokenMiddleware; +use app\api\middleware\TokenMiddleware; // 平台鉴权接口:/api/v1/authToken,请求头 signature/secret/time/agent_id,返回 authtToken Route::group('/api/v1', function () { Route::any('/authToken', [app\api\controller\v1\AuthTokenController::class, 'index']); -})->middleware([]); +})->middleware([ + ApiAccessLogMiddleware::class, +]); // 平台 v1 接口:需在请求头携带 auth-token Route::group('/api/v1', function () { @@ -32,13 +35,16 @@ Route::group('/api/v1', function () { Route::any('/getPlayerTicketRecord', [app\api\controller\v1\GameController::class, 'getPlayerTicketRecord']); Route::any('/setPlayerWallet', [app\api\controller\v1\GameController::class, 'setPlayerWallet']); })->middleware([ + ApiAccessLogMiddleware::class, AuthTokenMiddleware::class, ]); // 登录接口:无需 token,提交 JSON 获取带 token 的连接地址 Route::group('/api', function () { Route::any('/user/Login', [app\api\controller\UserController::class, 'Login']); -})->middleware([]); +})->middleware([ + ApiAccessLogMiddleware::class, +]); // 其余接口:仅经 token 中间件鉴权(header: token,base64(username.-.time)) Route::group('/api', function () { @@ -53,6 +59,7 @@ Route::group('/api', function () { Route::any('/game/anteConfig', [app\api\controller\GameController::class, 'anteConfig']); Route::any('/game/playStart', [app\api\controller\GameController::class, 'playStart']); })->middleware([ + ApiAccessLogMiddleware::class, TokenMiddleware::class, ]); diff --git a/server/docs/DICEY_FUN_THIRD_PARTY_ACCESS.md b/server/docs/DICEY_FUN_THIRD_PARTY_ACCESS.md index 1fa4de1..d9d1dfd 100644 --- a/server/docs/DICEY_FUN_THIRD_PARTY_ACCESS.md +++ b/server/docs/DICEY_FUN_THIRD_PARTY_ACCESS.md @@ -352,12 +352,10 @@ auth-token: {authtoken} - `auth-token: {authtoken}` - Body 参数: - `username`(可选):玩家账号;不传则**不按玩家筛选**(返回库内符合条件的记录,请谨慎使用) - - `start_create_time`(可选):创建时间下限,与表字段 `create_time` 比较(`>=`);不传则不限制 - - `end_create_time`(可选):创建时间上限(`<=`);不传则不限制 - - `page`(可选):页码,默认 `1`,小于 `1` 时按 `1` 处理 - - `limit`(可选):每页条数,默认 `20`;若小于 `1` 或大于 `100` 则按 `20` 处理 + - `start_create_time`(可选)、`end_create_time`(可选):与表字段 `create_time` 比较;**仅允许落在「当前时间起向前 7 天」内**,且两者跨度**不得超过 7 天**;均不传时服务端默认查询该 7 天窗口。`end_create_time` 晚于当前时间时按当前时间截断;不满足规则时返回参数错误。 + - `limit`(可选):返回条数上限,默认 `20`;若小于 `1` 或大于 `2000` 则按 `20` 处理 - 返回说明: - - 成功时 `data` 为**数组**(当前页记录列表),每条为 `dice_play_record` 行数据,并附带 `dice_player`:`{ id, username, phone }`(若批量关联不到则为 `null`) + - 成功时 `data` 为**数组**(按 `id` 倒序,最多 `limit` 条),每条为 `dice_play_record` 行数据,并附带 `dice_player`:`{ id, username, phone }`(若批量关联不到则为 `null`) - 若传了 `username` 且玩家不存在:`data` 为空数组 `[]` 记录主要字段(与库表一致,节选):`id`、`player_id`、`admin_id`、`lottery_config_id`、`lottery_type`、`ante`、`paid_amount`、`is_win`、`win_coin`、`super_win_coin`、`reward_win_coin`、`use_coins`、`direction`、`reward_tier`、`lottery_id`、`start_index`、`target_index`、`roll_array`、`roll_number`、`lottery_name`、`status`、`create_time`、`update_time` 等。 @@ -436,7 +434,7 @@ auth-token: {authtoken} - Body 参数: - `username`(可选):玩家账号;不传则**不按玩家筛选** - `start_create_time`、`end_create_time`(可选):同 `getPlayerGameRecord`,作用于 `create_time` - - `page`、`limit`(可选):分页规则同上 + - `limit`(可选):条数规则同 `getPlayerGameRecord` - 返回说明: - 成功时 `data` 为数组;每条为 `dice_player_wallet_record` 数据,并含关联 `dice_player`(`id, username, phone`) - 若传了 `username` 且玩家不存在:`data` 为空数组 `[]` @@ -448,7 +446,7 @@ auth-token: {authtoken} - 路径: `POST /api/v1/getPlayerTicketRecord` - Header: - `auth-token: {authtoken}` -- Body 参数:与 **7.4** 相同(`username`、`start_create_time`、`end_create_time`、`page`、`limit`) +- Body 参数:与 **7.4** 相同(`username`、`start_create_time`、`end_create_time`、`limit`) - 返回说明: - 成功时 `data` 为 `dice_player_ticket_record` 列表,含关联 `dice_player` - 若传了 `username` 且玩家不存在:`data` 为空数组 `[]` @@ -587,7 +585,6 @@ curl --location --request POST 'https://{your-domain}/api/v1/getPlayerGameRecord --header 'auth-token: {authtoken}' \ --data-raw '{ "username":"test_player_001", - "page":1, "limit":20 }' ``` @@ -600,7 +597,6 @@ curl --location --request POST 'https://{your-domain}/api/v1/getPlayerWalletReco --header 'auth-token: {authtoken}' \ --data-raw '{ "username":"test_player_001", - "page":1, "limit":20 }' ``` @@ -613,7 +609,6 @@ curl --location --request POST 'https://{your-domain}/api/v1/getPlayerTicketReco --header 'auth-token: {authtoken}' \ --data-raw '{ "username":"test_player_001", - "page":1, "limit":20 }' ``` diff --git a/server/docs/DICEY_FUN_THIRD_PARTY_ACCESS_EN.md b/server/docs/DICEY_FUN_THIRD_PARTY_ACCESS_EN.md index 1dc56aa..059c881 100644 --- a/server/docs/DICEY_FUN_THIRD_PARTY_ACCESS_EN.md +++ b/server/docs/DICEY_FUN_THIRD_PARTY_ACCESS_EN.md @@ -352,12 +352,10 @@ An independent endpoint is provided: `POST /api/v1/getGameList`, supporting both - `auth-token: {authtoken}` - Body parameters: - `username` (optional): Player username; if omitted, **no player filter** is applied (returns matching rows from the database—use with care) - - `start_create_time` (optional): Lower bound on `create_time` (`>=`); if omitted, no lower bound - - `end_create_time` (optional): Upper bound on `create_time` (`<=`); if omitted, no upper bound - - `page` (optional): Page number, default `1`; values less than `1` are treated as `1` - - `limit` (optional): Page size, default `20`; if less than `1` or greater than `100`, it is treated as `20` + - `start_create_time` (optional), `end_create_time` (optional): Filter on `create_time`. Queries are **restricted to the rolling 7-day window ending at server “now”**, and the span between the two bounds **must not exceed 7 days**. If both are omitted, the server defaults to that full 7-day window. If `end_create_time` is after “now”, it is truncated to “now”. Invalid ranges return a parameter error. + - `limit` (optional): Maximum number of rows to return, default `20`; if less than `1` or greater than `2000`, it is treated as `20` - Response notes: - - On success, `data` is an **array** (current page). Each item is a `dice_play_record` row plus `dice_player`: `{ id, username, phone }` (or `null` if the player cannot be resolved) + - On success, `data` is an **array** (ordered by `id` descending, at most `limit` rows). Each item is a `dice_play_record` row plus `dice_player`: `{ id, username, phone }` (or `null` if the player cannot be resolved) - If `username` is provided but the player does not exist: `data` is an empty array `[]` Main fields (same as table columns, partial list): `id`, `player_id`, `admin_id`, `lottery_config_id`, `lottery_type`, `ante`, `paid_amount`, `is_win`, `win_coin`, `super_win_coin`, `reward_win_coin`, `use_coins`, `direction`, `reward_tier`, `lottery_id`, `start_index`, `target_index`, `roll_array`, `roll_number`, `lottery_name`, `status`, `create_time`, `update_time`, etc. @@ -436,7 +434,7 @@ If the integrator’s wallet flow requires “return lobby URL after transfer” - Body parameters: - `username` (optional): Player username; if omitted, **no player filter** is applied - `start_create_time`, `end_create_time` (optional): Same as `getPlayerGameRecord`, applied to `create_time` - - `page`, `limit` (optional): Same pagination rules as above + - `limit` (optional): Same rules as `getPlayerGameRecord` - Response notes: - On success, `data` is an array of `dice_player_wallet_record` rows with related `dice_player` (`id`, `username`, `phone`) - If `username` is provided but the player does not exist: `data` is an empty array `[]` @@ -448,7 +446,7 @@ If the integrator’s wallet flow requires “return lobby URL after transfer” - Path: `POST /api/v1/getPlayerTicketRecord` - Header: - `auth-token: {authtoken}` -- Body parameters: Same as **7.4** (`username`, `start_create_time`, `end_create_time`, `page`, `limit`) +- Body parameters: Same as **7.4** (`username`, `start_create_time`, `end_create_time`, `limit`) - Response notes: - On success, `data` is a list of `dice_player_ticket_record` rows with related `dice_player` - If `username` is provided but the player does not exist: `data` is an empty array `[]` @@ -587,7 +585,6 @@ curl --location --request POST 'https://{your-domain}/api/v1/getPlayerGameRecord --header 'auth-token: {authtoken}' \ --data-raw '{ "username":"test_player_001", - "page":1, "limit":20 }' ``` @@ -600,7 +597,6 @@ curl --location --request POST 'https://{your-domain}/api/v1/getPlayerWalletReco --header 'auth-token: {authtoken}' \ --data-raw '{ "username":"test_player_001", - "page":1, "limit":20 }' ``` @@ -613,7 +609,6 @@ curl --location --request POST 'https://{your-domain}/api/v1/getPlayerTicketReco --header 'auth-token: {authtoken}' \ --data-raw '{ "username":"test_player_001", - "page":1, "limit":20 }' ``` diff --git a/server/docs/PERFORMANCE_AND_QPS_ANALYSIS.md b/server/docs/PERFORMANCE_AND_QPS_ANALYSIS.md index f32ea17..6d27596 100644 --- a/server/docs/PERFORMANCE_AND_QPS_ANALYSIS.md +++ b/server/docs/PERFORMANCE_AND_QPS_ANALYSIS.md @@ -142,7 +142,7 @@ Endpoints like "get player game record" or wallet histories can easily trigger N - Use `whereIn('player_id', $playerIds)` instead of one query per player. - Use eager loading (`with(['dicePlayer'])`) for related player info. -- Keep page size (`limit`) moderate (for example, 100 rows or less per page). +- Keep `limit` reasonable for load (default `20`, maximum `2000`; records are always scoped to a rolling 7-day window). ### 4.4 Redis vs DB coordination diff --git a/server/docs/PERFORMANCE_AND_QPS_ANALYSIS_CN.md b/server/docs/PERFORMANCE_AND_QPS_ANALYSIS_CN.md index de8ad61..300716b 100644 --- a/server/docs/PERFORMANCE_AND_QPS_ANALYSIS_CN.md +++ b/server/docs/PERFORMANCE_AND_QPS_ANALYSIS_CN.md @@ -145,7 +145,7 @@ - 使用 `whereIn('player_id', $playerIds)` 做批量查询; - 使用 `with(['dicePlayer'])` 或 join 预加载关联的玩家信息; -- 控制单页 `limit`,例如不超过 100 条。 +- 控制 `limit` 以兼顾负载(默认 `20`,上限 `2000`;接口已固定为「最近 7 天」时间窗)。 ### 4.4 Redis 与 DB 的整体协同