1.优化接口/api/v1/getPlayerGameRecord
2.增加接口日志记录接口request和response
This commit is contained in:
17
API对接文档.md
17
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 平台钱包转入/转出
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
201
server/app/api/middleware/ApiAccessLogMiddleware.php
Normal file
201
server/app/api/middleware/ApiAccessLogMiddleware.php
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
]);
|
||||
|
||||
|
||||
@@ -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
|
||||
}'
|
||||
```
|
||||
|
||||
@@ -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
|
||||
}'
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
|
||||
- 使用 `whereIn('player_id', $playerIds)` 做批量查询;
|
||||
- 使用 `with(['dicePlayer'])` 或 join 预加载关联的玩家信息;
|
||||
- 控制单页 `limit`,例如不超过 100 条。
|
||||
- 控制 `limit` 以兼顾负载(默认 `20`,上限 `2000`;接口已固定为「最近 7 天」时间窗)。
|
||||
|
||||
### 4.4 Redis 与 DB 的整体协同
|
||||
|
||||
|
||||
Reference in New Issue
Block a user