Sport.php 16 KB

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