|
|
@@ -2,16 +2,41 @@
|
|
|
|
|
|
namespace App\Services;
|
|
|
|
|
|
+use Carbon\Carbon;
|
|
|
use Illuminate\Http\Client\Pool;
|
|
|
use Illuminate\Http\Client\Response;
|
|
|
use Illuminate\Support\Facades\Cache;
|
|
|
use Illuminate\Support\Facades\Http;
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
+use Illuminate\Support\Facades\RateLimiter;
|
|
|
|
|
|
class ThirdGameBalanceService
|
|
|
{
|
|
|
private const CODE_SUCCESS = 10000;
|
|
|
|
|
|
+ private const HISTORY_QUERY_INTERVAL_SECONDS = 60;
|
|
|
+
|
|
|
+ private const HISTORY_PAGE_INTERVAL_SECONDS = 10;
|
|
|
+
|
|
|
+ private const HISTORY_HOURLY_LIMIT = 5;
|
|
|
+
|
|
|
+ private const GAME_TYPE_NAMES = [
|
|
|
+ 1 => '视讯',
|
|
|
+ 2 => '老虎机',
|
|
|
+ 3 => '彩票',
|
|
|
+ 4 => '体育',
|
|
|
+ 5 => '电竞',
|
|
|
+ 6 => '捕猎',
|
|
|
+ 7 => '棋牌',
|
|
|
+ ];
|
|
|
+
|
|
|
+ private const ORDER_STATUS_NAMES = [
|
|
|
+ 0 => '未完成',
|
|
|
+ 1 => '已完成',
|
|
|
+ 2 => '已取消',
|
|
|
+ 3 => '已撤单',
|
|
|
+ ];
|
|
|
+
|
|
|
/**
|
|
|
* 批量查询用户的三方游戏总余额,三方与主钱包金额按 1:1 处理。
|
|
|
*
|
|
|
@@ -129,6 +154,69 @@ class ThirdGameBalanceService
|
|
|
return $this->detailResult($memberId, $balances);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 查询三方游戏历史订单,按订单更新时间正序返回。
|
|
|
+ */
|
|
|
+ public function historyOrders(
|
|
|
+ string $startTime,
|
|
|
+ string $endTime,
|
|
|
+ int $page = 1,
|
|
|
+ int $limit = 200
|
|
|
+ ): array
|
|
|
+ {
|
|
|
+ if (!$this->configured()) {
|
|
|
+ return ['ok' => false, 'msg' => '三方游戏配置缺失'];
|
|
|
+ }
|
|
|
+
|
|
|
+ $rangeError = $this->historyRangeError($startTime, $endTime);
|
|
|
+ if ($rangeError !== null) {
|
|
|
+ return ['ok' => false, 'msg' => $rangeError];
|
|
|
+ }
|
|
|
+
|
|
|
+ $rateError = $this->acquireHistoryQuota($startTime, $endTime, $page, $limit);
|
|
|
+ if ($rateError !== null) {
|
|
|
+ return ['ok' => false, 'msg' => $rateError];
|
|
|
+ }
|
|
|
+
|
|
|
+ $response = $this->request('/api/server/recordHistory', [
|
|
|
+ 'currency' => (string) config('third_game.currency', 'CNY'),
|
|
|
+ 'startTime' => $startTime,
|
|
|
+ 'endTime' => $endTime,
|
|
|
+ 'pageNo' => (string) $page,
|
|
|
+ 'pageSize' => (string) $limit,
|
|
|
+ ]);
|
|
|
+ if (!$response instanceof Response) {
|
|
|
+ return ['ok' => false, 'msg' => $this->responseMessage($response)];
|
|
|
+ }
|
|
|
+
|
|
|
+ $body = $response->json();
|
|
|
+ if (!is_array($body) || (int) ($body['code'] ?? 0) !== self::CODE_SUCCESS) {
|
|
|
+ return ['ok' => false, 'msg' => $this->responseMessage($response)];
|
|
|
+ }
|
|
|
+
|
|
|
+ $providerData = is_array($body['data'] ?? null) ? $body['data'] : [];
|
|
|
+ $providerList = is_array($providerData['list'] ?? null) ? $providerData['list'] : [];
|
|
|
+ $list = [];
|
|
|
+ foreach ($providerList as $row) {
|
|
|
+ if (is_array($row)) {
|
|
|
+ $list[] = $this->formatOrder($row);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $result = [
|
|
|
+ 'ok' => true,
|
|
|
+ 'currency' => (string) config('third_game.currency', 'CNY'),
|
|
|
+ 'start_time' => $startTime,
|
|
|
+ 'end_time' => $endTime,
|
|
|
+ 'total' => (int) ($providerData['total'] ?? count($list)),
|
|
|
+ 'page_no' => (int) ($providerData['pageNo'] ?? $page),
|
|
|
+ 'page_size' => (int) ($providerData['pageSize'] ?? $limit),
|
|
|
+ 'list' => $list,
|
|
|
+ ];
|
|
|
+
|
|
|
+ return $result;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 一键把用户在所有三方游戏平台的余额转出。
|
|
|
*/
|
|
|
@@ -337,6 +425,105 @@ class ThirdGameBalanceService
|
|
|
return (string) ($response->json('msg') ?: '三方游戏接口异常');
|
|
|
}
|
|
|
|
|
|
+ private function formatOrder(array $row): array
|
|
|
+ {
|
|
|
+ $gameType = (int) ($row['gameType'] ?? 0);
|
|
|
+ $status = (int) ($row['status'] ?? -1);
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'player_id' => (string) ($row['playerId'] ?? ''),
|
|
|
+ 'platform' => (string) ($row['platType'] ?? ''),
|
|
|
+ 'currency' => (string) ($row['currency'] ?? ''),
|
|
|
+ 'game_type' => $gameType,
|
|
|
+ 'game_type_text' => self::GAME_TYPE_NAMES[$gameType] ?? '未知',
|
|
|
+ 'game_name' => (string) ($row['gameName'] ?? ''),
|
|
|
+ 'round' => (string) ($row['round'] ?? ''),
|
|
|
+ 'table' => (string) ($row['table'] ?? ''),
|
|
|
+ 'seat' => (string) ($row['seat'] ?? ''),
|
|
|
+ 'bet_amount' => $this->numericValue($row['betAmount'] ?? null),
|
|
|
+ 'valid_amount' => $this->numericValue($row['validAmount'] ?? null),
|
|
|
+ 'settled_amount' => $this->numericValue($row['settledAmount'] ?? null),
|
|
|
+ 'bet_content' => $row['betContent'] ?? '',
|
|
|
+ 'status' => $status,
|
|
|
+ 'status_text' => self::ORDER_STATUS_NAMES[$status] ?? '未知',
|
|
|
+ 'game_order_id' => (string) ($row['gameOrderId'] ?? ''),
|
|
|
+ 'bet_time' => (string) ($row['betTime'] ?? ''),
|
|
|
+ 'last_update_time' => (string) ($row['lastUpdateTime'] ?? ''),
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ private function numericValue($value)
|
|
|
+ {
|
|
|
+ return is_numeric($value) ? $value + 0 : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private function historyRangeError(string $startTime, string $endTime): ?string
|
|
|
+ {
|
|
|
+ $timezone = config('app.timezone', 'Asia/Shanghai');
|
|
|
+ $start = Carbon::createFromFormat('Y-m-d H:i:s', $startTime, $timezone);
|
|
|
+ $end = Carbon::createFromFormat('Y-m-d H:i:s', $endTime, $timezone);
|
|
|
+ if ($start->gt($end)) {
|
|
|
+ return '开始时间不能大于结束时间';
|
|
|
+ }
|
|
|
+ if ($start->diffInSeconds($end) > 6 * 60 * 60) {
|
|
|
+ return '查询时间范围不能超过6小时';
|
|
|
+ }
|
|
|
+ if ($start->lt(Carbon::now($timezone)->subDays(15))) {
|
|
|
+ return '只能查询最近15天内的订单';
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private function acquireHistoryQuota(
|
|
|
+ string $startTime,
|
|
|
+ string $endTime,
|
|
|
+ int $page,
|
|
|
+ int $limit
|
|
|
+ ): ?string
|
|
|
+ {
|
|
|
+ $prefix = $this->historyRatePrefix();
|
|
|
+ $sessionKey = $this->historySessionKey($startTime, $endTime, $limit);
|
|
|
+ $lock = Cache::lock($prefix . ':lock', 5);
|
|
|
+ if (!$lock->get()) {
|
|
|
+ return '订单查询正在处理中,请稍后重试';
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ $now = time();
|
|
|
+ $lastRequestAt = (int) Cache::get($prefix . ':last_request', 0);
|
|
|
+ if ($lastRequestAt > 0 && $now - $lastRequestAt < self::HISTORY_PAGE_INTERVAL_SECONDS) {
|
|
|
+ $retryAfter = self::HISTORY_PAGE_INTERVAL_SECONDS - ($now - $lastRequestAt);
|
|
|
+ return "订单请求至少间隔10秒,请在{$retryAfter}秒后重试";
|
|
|
+ }
|
|
|
+
|
|
|
+ $isPagination = $page > 1 && Cache::has($sessionKey);
|
|
|
+ if (!$isPagination) {
|
|
|
+ $lastQueryAt = (int) Cache::get($prefix . ':last_query', 0);
|
|
|
+ if ($lastQueryAt > 0 && $now - $lastQueryAt < self::HISTORY_QUERY_INTERVAL_SECONDS) {
|
|
|
+ $retryAfter = self::HISTORY_QUERY_INTERVAL_SECONDS - ($now - $lastQueryAt);
|
|
|
+ return "订单查询至少间隔1分钟,请在{$retryAfter}秒后重试";
|
|
|
+ }
|
|
|
+
|
|
|
+ $hourlyKey = $prefix . ':hourly';
|
|
|
+ if (RateLimiter::tooManyAttempts($hourlyKey, self::HISTORY_HOURLY_LIMIT)) {
|
|
|
+ $retryAfter = max(1, RateLimiter::availableIn($hourlyKey));
|
|
|
+ return "订单查询每小时最多5次,请在{$retryAfter}秒后重试";
|
|
|
+ }
|
|
|
+
|
|
|
+ RateLimiter::hit($hourlyKey, 3600);
|
|
|
+ Cache::put($prefix . ':last_query', $now, self::HISTORY_QUERY_INTERVAL_SECONDS);
|
|
|
+ Cache::put($sessionKey, true, 3600);
|
|
|
+ }
|
|
|
+
|
|
|
+ Cache::put($prefix . ':last_request', $now, self::HISTORY_PAGE_INTERVAL_SECONDS);
|
|
|
+
|
|
|
+ return null;
|
|
|
+ } finally {
|
|
|
+ $lock->release();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private function configured(): bool
|
|
|
{
|
|
|
return config('third_game.api_url') !== ''
|
|
|
@@ -377,6 +564,16 @@ class ThirdGameBalanceService
|
|
|
return 'third_game_balance_detail:' . $memberId;
|
|
|
}
|
|
|
|
|
|
+ private function historyRatePrefix(): string
|
|
|
+ {
|
|
|
+ return 'third_game_order_history:' . md5((string) config('third_game.sn'));
|
|
|
+ }
|
|
|
+
|
|
|
+ private function historySessionKey(string $startTime, string $endTime, int $limit): string
|
|
|
+ {
|
|
|
+ return $this->historyRatePrefix() . ':session:' . md5($startTime . '|' . $endTime . '|' . $limit);
|
|
|
+ }
|
|
|
+
|
|
|
private function clearCache(string $memberId): void
|
|
|
{
|
|
|
Cache::forget($this->cacheKey($memberId));
|