1.优化接口/api/v1/getPlayerGameRecord

2.增加接口日志记录接口request和response
This commit is contained in:
2026-05-13 09:59:15 +08:00
parent e74fc6069c
commit 6a1fd639a4
8 changed files with 365 additions and 81 deletions

View File

@@ -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 平台钱包转入/转出

View File

@@ -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);
}

View File

@@ -0,0 +1,201 @@
<?php
declare(strict_types=1);
namespace app\api\middleware;
use support\Log;
use Throwable;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
/**
* 记录 /api 相关接口的访问参数与响应摘要(敏感头、敏感字段脱敏)
*/
class ApiAccessLogMiddleware implements MiddlewareInterface
{
private const REQUEST_PARAM_MAX_LEN = 4096;
private const RESPONSE_BODY_MAX_LEN = 8192;
/** 请求头名称(小写) */
private const SENSITIVE_HEADER_NAMES = [
'auth-token',
'token',
'authorization',
'cookie',
];
/** 参数键名(小写匹配) */
private const SENSITIVE_PARAM_KEYS = [
'password',
'secret',
'signature',
'token',
'auth-token',
'auth_token',
'old_token',
];
public function process(Request $request, callable $handler): Response
{
$startedAt = microtime(true);
$response = null;
$thrown = null;
try {
$response = $handler($request);
return $response;
} catch (Throwable $e) {
$thrown = $e;
throw $e;
} finally {
$durationMs = round((microtime(true) - $startedAt) * 1000, 3);
$requestLog = $this->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<string, mixed>
*/
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<string, mixed>
*/
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;
}
}

View File

@@ -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: tokenbase64(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,
]);

View File

@@ -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
}'
```

View File

@@ -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 integrators 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 integrators 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
}'
```

View File

@@ -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

View File

@@ -145,7 +145,7 @@
- 使用 `whereIn('player_id', $playerIds)` 做批量查询;
- 使用 `with(['dicePlayer'])` 或 join 预加载关联的玩家信息;
- 控制单页 `limit`,例如不超过 100 条
- 控制 `limit` 以兼顾负载(默认 `20`,上限 `2000`;接口已固定为「最近 7 天」时间窗)
### 4.4 Redis 与 DB 的整体协同