LuckyStreakService.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. <?php
  2. namespace App\Game\Services;
  3. use App\Game\GlobalUserInfo;
  4. use App\Http\helper\NumConfig;
  5. use App\Models\AccountsInfo;
  6. use App\Notification\TelegramBot;
  7. use App\Util;
  8. use GuzzleHttp\Client;
  9. use GuzzleHttp\Exception\RequestException;
  10. use Illuminate\Support\Facades\DB;
  11. use Illuminate\Support\Facades\Log;
  12. use Illuminate\Support\Facades\Redis;
  13. class LuckyStreakService
  14. {
  15. protected $client;
  16. protected $operatorId;
  17. protected $operatorName;
  18. protected $clientId;
  19. protected $clientSecret;
  20. protected $apiUrl;
  21. protected $currency;
  22. protected $hmacId;
  23. protected $hmacUser;
  24. protected $hmacKey;
  25. public function __construct()
  26. {
  27. $this->client = new Client();
  28. $this->currency = env('CONFIG_24680_CURRENCY');
  29. $this->operatorId = env('LUCKYSTREAK_OPERATOR_ID', '526');
  30. $this->operatorName = env('LUCKYSTREAK_OPERATOR_NAME', 'AEG');
  31. $this->clientId = env('LUCKYSTREAK_CLIENT_ID', 'AEG_operator');
  32. $this->clientSecret = env('LUCKYSTREAK_CLIENT_SECRET', 'X6vuibvYJI2Z9NglbzZE');
  33. $this->apiUrl = env('LUCKYSTREAK_API_URL', 'https://integ.livepbt.com');
  34. $this->hmacId = env('LUCKYSTREAK_HMAC_ID', 'AEG1');
  35. $this->hmacUser = env('LUCKYSTREAK_HMAC_USER', 'AEGhmac1');
  36. $this->hmacKey = env('LUCKYSTREAK_HMAC_KEY', 'WcVIR7szsyXNPstaY2JX');
  37. }
  38. public function callSubApi($username,$request)
  39. {
  40. Util::WriteLog('luckystreak_validate','callsubapi');
  41. $apiurl=ServerService::GetApiByGUID($username);
  42. try {
  43. // 获取当前请求的 GET 和 POST 数据
  44. // $getData = $request->query(); // 获取 GET 数据
  45. $postData = $request->post(); // 获取 POST 数据
  46. $response = $this->client->post( $apiurl . $_SERVER['REQUEST_URI'], [
  47. 'verify'=>false,
  48. // 'query' => $getData, // 传递 GET 数据
  49. 'form_params' => $postData, // 传递 POST 数据
  50. ]);
  51. $res=json_decode($response->getBody(),true);
  52. Util::WriteLog('luckystreak_validate',$res);
  53. return $res;
  54. } catch (RequestException $e) {
  55. return $this->handleRequestException($e);
  56. }
  57. }
  58. private function handleRequestException(\Exception $e)
  59. {
  60. // TelegramBot::getDefault()->sendMsgWithEnv($e->getMessage().$e->getTraceAsString());
  61. }
  62. /**
  63. * 获取授权令牌
  64. *
  65. * @return string|null
  66. */
  67. public function getAuthToken()
  68. {
  69. try {
  70. // 构建凭证字符串
  71. $credentialsBase = $this->operatorId . ':' . $this->clientId . ':' . $this->clientSecret;
  72. $credentials = base64_encode($credentialsBase);
  73. // 请求授权令牌
  74. $response = $this->client->post('https://integ-api-ids.livepbt.com/ids/connect/token', [
  75. 'verify' => false,
  76. 'headers' => [
  77. 'Content-Type' => 'application/x-www-form-urlencoded',
  78. 'Authorization'=>'Basic QUVHX29wZXJhdG9yOlg2dnVpYnZZSkkyWjlOZ2xielpF'
  79. ],
  80. 'form_params' => [
  81. 'grant_type' => 'operator_authorization',
  82. 'scope' => 'operator offline_access',
  83. 'operator_name' => 'AEG'
  84. ]
  85. ]);
  86. $result = json_decode($response->getBody(), true);
  87. $token = $result['access_token'] ?? null;
  88. if ($token) {
  89. // 缓存令牌一段时间以减少API调用
  90. $expiresIn = $result['expires_in'] ?? 3600;
  91. Redis::setex('luckystreak_token', $expiresIn - 60, $token);
  92. }
  93. return $token;
  94. } catch (\Exception $e) {
  95. Util::WriteLog('luckystreak_error', $e->getMessage());
  96. return null;
  97. }
  98. }
  99. /**
  100. * 获取缓存的令牌或请求新令牌
  101. *
  102. * @return string|null
  103. */
  104. public function getCachedAuthToken()
  105. {
  106. // 尝试从缓存获取令牌
  107. $token = Redis::get('luckystreak_token');
  108. // 如果缓存中没有令牌或令牌已过期,请求新令牌
  109. if (!$token) {
  110. $token = $this->getAuthToken();
  111. }
  112. return $token;
  113. }
  114. /**
  115. * 获取游戏列表
  116. *
  117. * @return array
  118. */
  119. public function getGamesList()
  120. {
  121. try {
  122. // 获取授权令牌
  123. $token = $this->getCachedAuthToken();
  124. if (!$token) {
  125. throw new \Exception("Failed to get authorization token");
  126. }
  127. // 调用游戏列表API
  128. $response = $this->client->post($this->apiUrl . '/lobby/api/v4/lobby/games', [
  129. 'verify' => false,
  130. 'headers' => [
  131. 'Authorization' => 'Bearer ' . $token,
  132. 'Content-Type' => 'application/json'
  133. ],
  134. 'json' => [
  135. 'data' => [
  136. 'Open' => true,
  137. 'GameTypes' => [],
  138. 'Currencies' => []
  139. ]
  140. ]
  141. ]);
  142. return json_decode($response->getBody(), true);
  143. } catch (\Exception $e) {
  144. Util::WriteLog('luckystreak_error', $e->getMessage());
  145. return ['error' => $e->getMessage()];
  146. }
  147. }
  148. public function getJackpot()
  149. {
  150. try {
  151. // 获取授权令牌
  152. $token = $this->getCachedAuthToken();
  153. if (!$token) {
  154. throw new \Exception("Failed to get authorization token");
  155. }
  156. // 调用游戏列表API
  157. $response = $this->client->post($this->apiUrl . '/lobby/api/v4/lobby/jackpots', [
  158. 'verify' => false,
  159. 'headers' => [
  160. 'Authorization' => 'Bearer ' . $token,
  161. 'Content-Type' => 'application/json'
  162. ],
  163. 'json' => [
  164. ]
  165. ]);
  166. return json_decode($response->getBody(), true);
  167. } catch (\Exception $e) {
  168. Util::WriteLog('luckystreak_error', $e->getMessage());
  169. return ['error' => $e->getMessage()];
  170. }
  171. }
  172. /**
  173. * 获取游戏启动URL的HTML
  174. *
  175. * @param string $gameId
  176. * @param string $globalUid
  177. * @param string $ipAddress
  178. * @param string $language
  179. * @return string
  180. */
  181. public function getLaunchURLHTML($gameId,$globalUid, $ipAddress, $language)
  182. {
  183. try {
  184. // 获取游戏类型(如果需要)
  185. $gameType = $this->getGameTypeById($gameId);
  186. // 使用GlobalUID作为AuthCode参数
  187. // 使用正确的启动URL格式
  188. $launchURL = "https://m.integ.livepbt.com/?PlayerName={$globalUid}&OperatorName=AEG&AuthCode={$globalUid}&GameId={$gameId}&GameType={$gameType}&LimitsGroupId=67efe1d102d4c16717303002";
  189. // 添加语言参数
  190. if ($language) {
  191. $launchURL .= "&language={$language}";
  192. }
  193. // 返回HTML重定向脚本
  194. return "<script>window.location.href='{$launchURL}';</script>";
  195. } catch (\Exception $e) {
  196. Util::WriteLog('luckystreak_error', $e->getMessage());
  197. return "<script>alert('System error: " . $e->getMessage() . "');</script>";
  198. }
  199. }
  200. /**
  201. * 根据游戏ID获取游戏类型
  202. *
  203. * @param string $gameId
  204. * @return string|null
  205. */
  206. private function getGameTypeById($gameId)
  207. {
  208. // 基本游戏类型映射
  209. $gameTypes = [
  210. '3' => 'Roulette',
  211. '4' => 'Blackjack',
  212. '2' => 'Baccarat',
  213. // 可以添加更多游戏类型映射
  214. ];
  215. return $gameTypes[$gameId] ?? null;
  216. }
  217. /**
  218. * 验证HMAC签名(用于出站请求)
  219. *
  220. * @param array $data
  221. * @param string $signature
  222. * @return bool
  223. */
  224. public function verifyHmacSignature($data, $signature)
  225. {
  226. // 根据LuckyStreak API文档实现HMAC验证逻辑
  227. // 按照文档附录B的规范实现
  228. // 1. 获取完整请求URL中的路径部分(不包含域名和查询参数)
  229. $path = $data['path'] ?? '';
  230. unset($data['path']);
  231. // 2. 获取请求方法
  232. $method = $data['method'] ?? 'POST';
  233. unset($data['method']);
  234. // 3. 获取请求时间戳
  235. $timestamp = $data['timestamp'] ?? '';
  236. if (empty($timestamp)) {
  237. return false;
  238. }
  239. unset($data['timestamp']);
  240. // 4. 获取随机nonce
  241. $nonce = $data['nonce'] ?? '';
  242. if (empty($nonce)) {
  243. return false;
  244. }
  245. unset($data['nonce']);
  246. // 获取ext和hash
  247. $ext = '';
  248. if (isset($data['ext'])) {
  249. $ext = $data['ext'];
  250. unset($data['ext']);
  251. }
  252. $hash = '';
  253. if (isset($data['hash'])) {
  254. $hash = $data['hash'];
  255. unset($data['hash']);
  256. }
  257. // 主机名和端口
  258. $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'euapi.24680.org';
  259. $port = '443'; // 假设使用HTTPS
  260. // 5. 构建规范化请求字符串
  261. $normalized = "hawk.1.header\n" .
  262. $timestamp . "\n" .
  263. $nonce . "\n" .
  264. $method . "\n" .
  265. $path . "\n" .
  266. $host . "\n" .
  267. $port . "\n";
  268. // 6. 如果有请求体,添加请求体内容的哈希
  269. if (isset($data['body']) && !empty($data['body'])) {
  270. $body = $data['body'];
  271. // 如果是JSON字符串,尝试解码并重新编码,确保格式一致
  272. if (is_string($body) && json_decode($body) !== null) {
  273. $bodyObj = json_decode($body, true);
  274. if (is_array($bodyObj)) {
  275. $body = json_encode($bodyObj);
  276. }
  277. } elseif (is_array($body)) {
  278. $body = json_encode($body);
  279. }
  280. // 如果已提供的hash值,使用它
  281. if (!empty($hash)) {
  282. $normalized .= $hash . "\n";
  283. } else {
  284. // 否则计算body的hash
  285. $bodyHash = base64_encode(hash('sha256', $body, true));
  286. $normalized .= $bodyHash . "\n";
  287. }
  288. } else {
  289. $normalized .= "\n"; // 如果没有请求体,添加空行
  290. }
  291. // 7. 如果有ext,添加ext
  292. if (!empty($ext)) {
  293. $normalized .= $ext . "\n";
  294. } else {
  295. $normalized .= ''; // 如果没有ext,添加空字符串
  296. }
  297. // 8. 使用HMAC-SHA256生成签名
  298. $calculatedSignature = base64_encode(hash_hmac('sha256', $normalized, $this->hmacKey, true));
  299. // 9. 比较签名
  300. // Util::WriteLog('luckystreak_hmac', [
  301. // 'normalized' => $normalized,
  302. // 'calculated' => $calculatedSignature,
  303. // 'received' => $signature,
  304. // 'hmacKey' => $this->hmacKey
  305. // ]);
  306. return $signature === $calculatedSignature;
  307. }
  308. public function generateHmacSignature($data, $body = null)
  309. {
  310. // $data=['path' => '',
  311. // 'method' => '',
  312. // 'timestamp' => '',
  313. // 'nonce' => ''];
  314. // 生成时间戳(毫秒级)
  315. // $timestamp = (string)round(microtime(true) * 1000);
  316. // 生成随机nonce
  317. // $nonce = uniqid('', true);
  318. extract($data);
  319. // 主机名和端口
  320. $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'euapi.24680.org';
  321. $port = '443'; // 假设使用HTTPS
  322. // 请求标识符 (根据文档使用hawk.1.response)
  323. $type = "hawk.1.response";
  324. // 构建规范化请求字符串
  325. $normalized = $type . "\n" .
  326. $timestamp . "\n" .
  327. $nonce . "\n" .
  328. $method . "\n" .
  329. $path . "\n" .
  330. $host . "\n" .
  331. $port . "\n";
  332. // 如果有请求体,添加请求体的哈希
  333. $bodyHash = '';
  334. if (!empty($body)&&false) {
  335. // 如果是JSON字符串,尝试解码并重新编码,确保格式一致
  336. if (is_string($body) && json_decode($body) !== null) {
  337. $bodyObj = json_decode($body, true);
  338. if (is_array($bodyObj)) {
  339. $body = json_encode($bodyObj);
  340. }
  341. } elseif (is_array($body)) {
  342. $body = json_encode($body);
  343. }
  344. $bodyHash = base64_encode(hash('sha256', $body, true));
  345. $normalized .= $bodyHash . "\n";
  346. } else {
  347. $normalized .= "\n"; // 如果没有请求体,添加空行
  348. }
  349. // 添加ext(根据文档使用X-Request-Header-To-Protect:secret)
  350. $ext = "X-Request-Header-To-Protect:secret";
  351. $normalized .= $ext ."\n";
  352. // 使用HMAC-SHA256生成签名
  353. $signature = base64_encode(hash_hmac('sha256', $normalized, $this->hmacKey, true));
  354. // Util::WriteLog('luckystreak_hmac', [
  355. // 'normalized' => $normalized,
  356. // 'signature' => $signature,
  357. // 'body' => $body,
  358. // 'bodyHash' => $bodyHash,
  359. // 'header'=>[ 'Server-Authorization' => 'hawk mac="' . $signature . '", ext="' . $ext . '"']
  360. // ]);
  361. // 返回Server-Authorization头(根据文档)
  362. return [
  363. 'Server-Authorization' => 'hawk mac="' . $signature . '", ext="' . $ext . '"'
  364. ];
  365. }
  366. /**
  367. * 获取第三方游戏提供商游戏列表
  368. *
  369. * @return array
  370. */
  371. public function getProviderGamesList()
  372. {
  373. try {
  374. // 获取授权令牌
  375. $token = $this->getCachedAuthToken();
  376. if (!$token) {
  377. throw new \Exception("Failed to get authorization token");
  378. }
  379. // 调用Provider游戏列表API
  380. $response = $this->client->post($this->apiUrl . '/lobby/api/v4/lobby/providergames', [
  381. 'verify' => false,
  382. 'headers' => [
  383. 'Authorization' => 'Bearer ' . $token,
  384. 'Content-Type' => 'application/json'
  385. ],
  386. 'json' => [
  387. 'data' => [
  388. 'Open' => true,
  389. 'GameTypes' => [],
  390. 'Currencies' => []
  391. ]
  392. ]
  393. ]);
  394. return json_decode($response->getBody(), true);
  395. } catch (\Exception $e) {
  396. Util::WriteLog('luckystreak_error', $e->getMessage());
  397. return ['error' => $e->getMessage()];
  398. }
  399. }
  400. }