Basketball.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. <?php
  2. namespace App\Console\Commands;
  3. use Illuminate\Console\Command;
  4. use App\Models\Sport as SportModel;
  5. use App\Models\SportTeam;
  6. use App\Models\SportLeague;
  7. use App\Models\SportStatistics;
  8. use App\Services\SportClientService;
  9. use Carbon\Carbon;
  10. use Illuminate\Support\Facades\DB;
  11. use App\Models\SportEvent;
  12. use App\Models\Config;
  13. class Basketball extends Command
  14. {
  15. /**
  16. * 命令名称和签名
  17. *
  18. * @var string
  19. */
  20. protected $signature = 'basketball {is_live=0}';
  21. protected $is_live = 0;
  22. protected $short_status = [
  23. 'TBD' => 0,
  24. 'LIVE' => 1,
  25. ];
  26. protected $long_status = [
  27. 'Time To Be Defined' => 0,
  28. 'Not Started' => 0,
  29. 'First Half' => 1,
  30. ];
  31. protected $fixture_status = [
  32. 'First Half' => 1,
  33. 'First Half, Kick Off' => 1,
  34. 'Halftime' => 1,
  35. 'Second Half' => 1,
  36. 'Second Half, 2nd Half Started' => 1,
  37. 'Extra Time' => 1,
  38. 'Break Time' => 1,
  39. 'Penalty In Progress' => 1,
  40. ];
  41. /**
  42. * 命令描述
  43. *
  44. * @var string
  45. */
  46. protected $description = '当天会去更新明天的赛事(23:59:00执行一次)';
  47. /**
  48. * 执行命令
  49. *
  50. * @return int
  51. */
  52. public function handle()
  53. {
  54. // $date = '2026-05-21';
  55. // // $data = SportClientService::basketballGames(['date' => $date]);
  56. // // file_put_contents('basketballGames-21.json', json_encode($data));
  57. // $data_id = 499637;
  58. // $data = SportClientService::basketballOdds(['game' => $data_id]);
  59. // file_put_contents('basketballOdds-499637.json', json_encode($data));
  60. // print_r($data);
  61. // die;
  62. // $this->info('开始执行统计比赛数据任务...');
  63. $this->is_live = $this->argument('is_live');
  64. if ($this->is_live == 0) {
  65. //未开始的赛事拉取
  66. $this->fixtures();
  67. //世界杯赛事拉取
  68. $this->leagueFixtures();
  69. } elseif ($this->is_live == 1) {
  70. //进行中的赛事,定时更新
  71. $this->liveFixtures();
  72. } elseif ($this->is_live == 2){
  73. $this->checkLiveFixtures();
  74. } elseif ($this->is_live == 3) {
  75. $this->checkOvertimeFixtures();
  76. } else {
  77. $this->initOdds();
  78. }
  79. }
  80. public function leagueFixtures()
  81. {
  82. //体育赛事结束前几(分钟)锁盘,90分钟结束
  83. $sport_locked = Config::where('field', 'sport_locked')->first()->val ?? 1;
  84. //查询世界杯的赛事
  85. $data = SportClientService::fixtures(['league' => 1, 'season' => 2026]);
  86. $this->updateOrCreateSport($data, $sport_locked);
  87. return true;
  88. }
  89. //到了比赛开始时间,但是状态还是未开始,检查比赛
  90. public function checkOvertimeFixtures()
  91. {
  92. //体育赛事结束前几(分钟)锁盘,90分钟结束
  93. $sport_locked = Config::where('field', 'sport_locked')->first()->val ?? 1;
  94. // 比赛开始后,状态还是未开始的数据检测
  95. $ids = SportModel::where('state', 0)->where('game_time', '<=', time())->pluck('data_id')->toArray();
  96. if ($ids) {
  97. $chunks = array_chunk($ids, 10);
  98. foreach ($chunks as $chunk) {
  99. $ids = implode('-', $chunk);
  100. $data = SportClientService::fixtures(['ids' => $ids]);
  101. $this->updateOrCreateSport($data, $sport_locked, 1);
  102. }
  103. }
  104. return true;
  105. }
  106. //进行中超过3分钟没有更新数据的赛事,检查比赛是否结束
  107. public function checkLiveFixtures()
  108. {
  109. //体育赛事结束前几(分钟)锁盘,90分钟结束
  110. $sport_locked = Config::where('field', 'sport_locked')->first()->val ?? 1;
  111. //1. 统一锁盘时间(比赛开始前1分钟)
  112. SportModel::where(['is_locked' => 0, 'is_roll' => 0])->where('game_time', '<=', time() + 90)->update(['is_locked' => 1]);
  113. //2. 比赛进行中,超过3分钟未更新的数据检测
  114. $end_time = date("Y-m-d H:i:s", time() - 180);
  115. $ids = SportModel::where('state', 1)->where('updated_at', '<=', $end_time)->pluck('data_id')->toArray();
  116. if ($ids) {
  117. $chunks = array_chunk($ids, 10);
  118. foreach ($chunks as $chunk) {
  119. $ids = implode('-', $chunk);
  120. $data = SportClientService::fixtures(['ids' => $ids]);
  121. $this->updateOrCreateSport($data, $sport_locked, 1);
  122. }
  123. }
  124. return true;
  125. }
  126. //更新进行中的赛事
  127. public function liveFixtures()
  128. {
  129. //体育赛事结束前几(分钟)锁盘,90分钟结束
  130. $sport_locked = Config::where('field', 'sport_locked')->first()->val ?? 1;
  131. $data = SportClientService::fixtures(['live' => 'all']);
  132. file_put_contents("fixturesLive.json", json_encode($data));
  133. $this->updateOrCreateSport($data, $sport_locked);
  134. return true;
  135. }
  136. private function updateOrCreateSport($data, $sport_locked, $is_check = 0) {
  137. $data = $data['response'];
  138. $tableData = [];
  139. $status = $this->short_status;
  140. foreach ($data as $item) {
  141. $home_statistics = !empty($item['statistics']) ? $item['statistics'][0]['statistics'] : '';
  142. $away_statistics = !empty($item['statistics']) ? $item['statistics'][1]['statistics'] : '';
  143. $sport_data = [
  144. 'data_id' => $item['fixture']['id'],
  145. 'home_team_id' => $item['teams']['home']['id'],
  146. 'home_team_en' => $item['teams']['home']['name'],
  147. 'home_team_logo' => $item['teams']['home']['logo'],
  148. 'guest_team_id' => $item['teams']['away']['id'],
  149. 'guest_team_en' => $item['teams']['away']['name'],
  150. 'guest_team_logo' => $item['teams']['away']['logo'],
  151. 'half_score' => "{$item['score']['halftime']['home']}-{$item['score']['halftime']['away']}",
  152. 'rbt' => $item['fixture']['timestamp'],
  153. 'score' => isset($item['goals']) ? "{$item['goals']['home']}-{$item['goals']['away']}":'-',
  154. 'extra_score' => isset($item['score']['extratime']) ? "{$item['score']['extratime']['home']}-{$item['score']['extratime']['away']}" : "",//加时赛比分
  155. 'league_id' => $item['league']['id'],
  156. 'league_en' => $item['league']['name'],
  157. 'league_data' => json_encode($item['league']),
  158. 'state' => $status[$item['fixture']['status']['short']],//比赛状态:0未开始1进行中2已完场3延期4取消
  159. 'game_time' => $item['fixture']['timestamp'],
  160. 'updated_at' => date('Y-m-d H:i:s'),
  161. 'home_statistics' => json_encode($home_statistics),
  162. 'away_statistics' => json_encode($away_statistics),
  163. 'is_send' => 0,
  164. ];
  165. $info = SportModel::where('data_id', $item['fixture']['id'])->first();
  166. $fixture_status = null;
  167. if (isset($item['fixture']['status']['long']) ) {
  168. $long = $item['fixture']['status']['long'];
  169. if (isset($this->fixture_status[$long])) {
  170. $fixture_status = json_encode($item['fixture']['status']);
  171. $sport_data['fixture_status'] = $fixture_status;
  172. }
  173. if (isset($this->long_status[$long])) {
  174. $sport_data['state'] = $this->long_status[$long];
  175. }
  176. }
  177. //提前锁盘(比赛进行时长,分钟)
  178. if (isset($item['fixture']['status']['elapsed'])) {
  179. $elapsed = $item['fixture']['status']['elapsed'];
  180. if ((int)$elapsed >= 90 - $sport_locked ) {
  181. $sport_data['is_locked'] = 1;
  182. }
  183. }
  184. $sport_data['score'] = $sport_data['score'] == '-' ? '' : $sport_data['score'];
  185. $sport_data['half_score'] = $sport_data['half_score'] == '-' ? '' : $sport_data['half_score'];
  186. $sport_data['extra_score'] = $sport_data['extra_score'] == '-' ? '' : $sport_data['extra_score'];
  187. //锁盘
  188. if (isset($item['fixture']['status']['blocked']) && $item['fixture']['status']['blocked']) {
  189. $sport_data['is_locked'] = 1;
  190. }
  191. //已结束
  192. if (isset($item['fixture']['status']['finished']) && $item['fixture']['status']['finished']) {
  193. $sport_data['state'] = 2;
  194. }
  195. //如果赛事取消、延期等,标记需要退款
  196. if ($sport_data['state'] > 2) {
  197. $sport_data['refund_status'] = 1;
  198. }
  199. if (!$info) {
  200. $sport_data['created_at'] = date('Y-m-d H:i:s');
  201. $sport_data['status'] = 1;
  202. $sport_data['home_team'] = SportTeam::getTeamName($sport_data['home_team_id']);
  203. $sport_data['guest_team'] = SportTeam::getTeamName($sport_data['guest_team_id']);
  204. $sport_data['league'] = SportLeague::getLeagueName($sport_data['league_id']);
  205. $tableData[] = $sport_data;
  206. } else {
  207. if (empty($info['league'])) {
  208. $sport_data['league'] = SportLeague::getLeagueName($sport_data['league_id']);
  209. }
  210. if (empty($info['home_team'])) {
  211. $sport_data['home_team'] = SportTeam::getTeamName($sport_data['home_team_id']);
  212. }
  213. if (empty($info['guest_team'])) {
  214. $sport_data['guest_team'] = SportTeam::getTeamName($sport_data['guest_team_id']);
  215. }
  216. SportModel::where('data_id', $item['fixture']['id'])->update($sport_data);
  217. }
  218. if ($sport_data['state'] == 2) {
  219. $data_id = $item['fixture']['id'];
  220. //比赛结束,插入比赛事件
  221. if (!empty($item['events'])) {
  222. $this->insertSportEvents($data_id, $item['events']);
  223. }
  224. //比赛结束,插入比赛统计数据
  225. $data = SportClientService::statistics(['fixture' => $data_id,'half' => 'true']);
  226. // file_put_contents("fixturesStatistics.json", json_encode($data));
  227. $statistics_data = empty($data['response']) ? [] : $data['response'];
  228. foreach($statistics_data as $item) {
  229. $team_id = $item['team']['id'];
  230. //上半场的数据
  231. if(!empty($item['statistics_1h'])) {
  232. $statistics_1h = $item['statistics_1h'];
  233. $this->insertSportStatistics($data_id, $team_id, $statistics_1h, 1);
  234. }
  235. //下半场的数据
  236. if(!empty($item['statistics_2h'])) {
  237. $statistics_2h = $item['statistics_2h'];
  238. $this->insertSportStatistics($data_id, $team_id, $statistics_2h, 2);
  239. }
  240. }
  241. }
  242. }
  243. if ($tableData) {
  244. SportModel::insert($tableData);
  245. }
  246. return true;
  247. }
  248. /**
  249. * 获取指定日期的所有赛事
  250. *
  251. * @return array
  252. */
  253. public function fixtures()
  254. {
  255. //根据配置拉取多少天的赛事信息
  256. $days = Config::where('field', 'sport_days')->first()->val ?? 1;
  257. for($i=0;$i<$days;$i++) {
  258. $date = Carbon::today()->addDay($i)->toDateString();
  259. $data = SportClientService::basketballGames(['date' => $date]);
  260. $data = $data['response'];
  261. $tableData = [];
  262. $status = $this->short_status;
  263. foreach ($data as $item) {
  264. $data_id = $item['id'];
  265. $sport_data = [
  266. 'data_id' => $item['id'],
  267. 'type' => 2,
  268. 'home_team_id' => $item['teams']['home']['id'],
  269. 'home_team_en' => $item['teams']['home']['name'],
  270. 'home_team_logo' => $item['teams']['home']['logo'],
  271. 'guest_team_id' => $item['teams']['away']['id'],
  272. 'guest_team_en' => $item['teams']['away']['name'],
  273. 'guest_team_logo' => $item['teams']['away']['logo'],
  274. 'half_score' => "",
  275. 'rbt' => $item['timestamp'],
  276. 'score' => isset($item['scores']['home']) && isset($item['scores']['away']) ? "{$item['scores']['home']['total']}-{$item['scores']['away']['total']}":'-',
  277. 'extra_score' => isset($item['scores']['home']) && isset($item['scores']['away']) ? "{$item['scores']['home']['over_time']}-{$item['scores']['away']['over_time']}":'-',
  278. 'league_id' => $item['league']['id'],
  279. 'league_en' => $item['league']['name'],
  280. 'league_data' => json_encode($item['league']),
  281. 'scores' => json_encode($item['scores']),
  282. // 'state' => $status[$item['fixture']['status']['short']],//比赛状态:0未开始1进行中2已完场3延期4取消
  283. 'game_time' => $item['timestamp'],
  284. 'updated_at' => now(),
  285. 'is_send' => 0,
  286. ];
  287. $status_short = strtoupper($item['fixture']['status']['short']);
  288. if (isset($status[$status_short])) {
  289. $sport_data['state'] = $status[$status_short];
  290. }
  291. $sport_data['score'] = $sport_data['score'] == '-' ? '' : $sport_data['score'];
  292. $sport_data['extra_score'] = $sport_data['extra_score'] == '-' ? '' : $sport_data['extra_score'];
  293. if (!SportModel::where('data_id', $data_id)->exists()) {
  294. $sport_data['created_at'] = now();
  295. $sport_data['home_team'] = SportTeam::getTeamName($sport_data['home_team_id']);
  296. $sport_data['guest_team'] = SportTeam::getTeamName($sport_data['guest_team_id']);
  297. $sport_data['league'] = SportLeague::getLeagueName($sport_data['league_id']);
  298. $tableData[] = $sport_data;
  299. } else {
  300. $sport_data['home_team'] = SportTeam::getTeamName($sport_data['home_team_id']);
  301. $sport_data['guest_team'] = SportTeam::getTeamName($sport_data['guest_team_id']);
  302. $sport_data['league'] = SportLeague::getLeagueName($sport_data['league_id']);
  303. SportModel::where('data_id', $item['fixture']['id'])->update($sport_data);
  304. }
  305. //更新或创建球队和联赛信息
  306. SportModel::addSportTeam($sport_data);
  307. SportModel::addSportLeague($item['league']);
  308. //比赛结束,插入比赛事件
  309. if (isset($sport_data['state']) && $sport_data['state'] == 2 ) {
  310. $data_id = $item['fixture']['id'];
  311. if (!empty($item['events'])) {
  312. $this->insertSportEvents($data_id, $item['events']);
  313. }
  314. //比赛结束,插入比赛事件
  315. $data = SportClientService::statistics(['fixture' => $data_id,'half' => 'true']);
  316. // file_put_contents("fixturesStatistics.json", json_encode($data));
  317. $statistics_data = empty($data['response']) ? [] : $data['response'];
  318. foreach($statistics_data as $item) {
  319. $team_id = $item['team']['id'];
  320. //上半场的数据
  321. if(!empty($item['statistics_1h'])) {
  322. $statistics_1h = $item['statistics_1h'];
  323. $this->insertSportStatistics($data_id, $team_id, $statistics_1h, 1);
  324. }
  325. //下半场的数据
  326. if(!empty($item['statistics_2h'])) {
  327. $statistics_2h = $item['statistics_2h'];
  328. $this->insertSportStatistics($data_id, $team_id, $statistics_2h, 2);
  329. }
  330. }
  331. }
  332. }
  333. if ($tableData) {
  334. SportModel::insert($tableData);
  335. }
  336. }
  337. return true;
  338. }
  339. public function initOdds(){
  340. $page = 1;
  341. $limit = 10;
  342. while (true) {
  343. $list = SportModel::where('odds','<>', null)->forPage($page, $limit)->get()->toArray();
  344. if (empty($list)) {
  345. break;
  346. }
  347. echo $page.PHP_EOL;
  348. foreach($list as $item) {
  349. $odds = json_decode($item['odds'], true);
  350. foreach($odds as $odd) {
  351. $odd_id = $odd['id'];
  352. $odd_name = $odd['name'];
  353. $info = DB::table('sport_odds')->where('odd_id',$odd_id)->where('odd_name_en',$odd_name)->first();
  354. if ($info && (!$info->odd_name || $odd_name != $info->odd_name_en)) {
  355. DB::table('sport_odds')->where('id', $info->id)->update([
  356. 'odd_name_en' => $odd_name,
  357. ]);
  358. } elseif (!$info) {
  359. DB::table('sport_odds')->insert([
  360. 'odd_id' => $odd_id,
  361. 'odd_name_en' => $odd_name,
  362. 'created_at' => date('Y-m-d H:i:s'),
  363. 'updated_at' => date('Y-m-d H:i:s'),
  364. ]);
  365. }
  366. }
  367. }
  368. $page++;
  369. }
  370. }
  371. //比赛开始后,超过3个小时,并且更新时间超过10分钟,还是进行中的数据,更新成已完成
  372. public function updateOvertimeFixtures()
  373. {
  374. // 比赛开始后,状态还是未开始的数据检测
  375. $end_time = date("Y-m-d H:i:s", time() - 600);
  376. SportModel::where('status', 1)->where('state', 1)->where('game_time', '<=', time() - 60*60*3)->where('updated_at', '<=', $end_time)->update(['state' =>2]);
  377. return true;
  378. }
  379. }