Sport.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. <?php
  2. namespace App\Console\Commands;
  3. use Illuminate\Console\Command;
  4. use App\Models\Sport as SportModel;
  5. use App\Services\SportClientService;
  6. use Carbon\Carbon;
  7. use Illuminate\Support\Facades\DB;
  8. use App\Models\SportEvent;
  9. use App\Models\Config;
  10. class Sport extends Command
  11. {
  12. /**
  13. * 命令名称和签名
  14. *
  15. * @var string
  16. */
  17. protected $signature = 'sport {is_live=0}';
  18. protected $is_live = 0;
  19. protected $short_status = [
  20. 'TBD' => 0,
  21. 'NS' => 0,
  22. '1H' => 1,
  23. 'HT' => 1,
  24. '2H' => 1,
  25. 'ET' => 1,
  26. 'BT' => 1,
  27. 'P' => 1,
  28. 'SUSP' => 1,
  29. 'INT' => 1,
  30. 'FT' => 2,
  31. 'AET' => 2,
  32. 'PEN' => 2,
  33. 'PST' => 3,
  34. 'CANC' => 4,
  35. 'ABD' => 4,
  36. 'AWD' => 4,
  37. 'WO' => 4,
  38. 'LIVE' => 1,
  39. ];
  40. protected $long_status = [
  41. 'Time To Be Defined' => 0,
  42. 'Not Started' => 0,
  43. 'First Half' => 1,
  44. 'First Half, Kick Off' => 1,
  45. 'Halftime' => 1,
  46. 'Second Half' => 1,
  47. 'Second Half, 2nd Half Started' => 1,
  48. 'Extra Time' => 1,
  49. 'Break Time' => 1,
  50. 'Penalty In Progress' => 1,
  51. 'Match Suspended' => 1,
  52. 'Match Interrupted' => 1,
  53. 'Match Finished' => 2,
  54. 'Match Finished' => 2,
  55. 'Match Finished' => 2,
  56. 'Match Postponed' => 3,
  57. 'Match Cancelled' => 4,
  58. 'Match Abandoned' => 4,
  59. 'Technical Loss' => 4,
  60. 'WalkOver' => 4,
  61. 'In Progress' => 1,
  62. ];
  63. protected $fixture_status = [
  64. 'First Half' => 1,
  65. 'First Half, Kick Off' => 1,
  66. 'Halftime' => 1,
  67. 'Second Half' => 1,
  68. 'Second Half, 2nd Half Started' => 1,
  69. 'Extra Time' => 1,
  70. 'Break Time' => 1,
  71. 'Penalty In Progress' => 1,
  72. ];
  73. /**
  74. * 命令描述
  75. *
  76. * @var string
  77. */
  78. protected $description = '当天会去更新明天的赛事(23:59:00执行一次)';
  79. /**
  80. * 执行命令
  81. *
  82. * @return int
  83. */
  84. public function handle()
  85. {
  86. // $this->info('开始执行统计比赛数据任务...');
  87. $this->is_live = $this->argument('is_live');
  88. if ($this->is_live == 1) {
  89. //进行中的赛事,定时更新
  90. $this->liveFixtures();
  91. } elseif ($this->is_live == 0) {
  92. //未开始的赛事拉取
  93. $this->fixtures();
  94. } elseif ($this->is_live == 2){
  95. // $data = SportClientService::fixtures([
  96. // 'id' => 1506065,
  97. // ]);
  98. // file_put_contents("1506065.json", json_encode($data));
  99. // print_r($data);
  100. // die;
  101. $this->checkLiveFixtures();
  102. //$this->updateOvertimeFixtures();
  103. } elseif ($this->is_live == 3) {
  104. $this->checkOvertimeFixtures();
  105. } else {
  106. $this->initOdds();
  107. }
  108. }
  109. //比赛开始后,超过3个小时,并且更新时间超过10分钟,还是进行中的数据,更新成已完成
  110. public function updateOvertimeFixtures()
  111. {
  112. // 比赛开始后,状态还是未开始的数据检测
  113. $end_time = date("Y-m-d H:i:s", time() - 600);
  114. SportModel::where('status', 1)->where('state', 1)->where('game_time', '<=', time() - 60*60*3)->where('updated_at', '<=', $end_time)->update(['state' =>2]);
  115. // $ids = SportModel::where('status', 1)->where('state', 1)->where('game_time', '<=', time() - 60*60*3)->pluck('data_id')->get();
  116. // print_r($ids);
  117. return true;
  118. }
  119. //到了比赛开始时间,但是状态还是未开始,检查比赛
  120. public function checkOvertimeFixtures()
  121. {
  122. //体育赛事结束前几(分钟)锁盘,90分钟结束
  123. $sport_locked = Config::where('field', 'sport_locked')->first()->val ?? 1;
  124. // 比赛开始后,状态还是未开始的数据检测
  125. $ids = SportModel::where('status', 1)->where('state', 0)->where('game_time', '<=', time())->pluck('data_id')->toArray();
  126. if ($ids) {
  127. $ids = implode('-', $ids);
  128. $data = SportClientService::fixtures(['ids' => $ids]);
  129. $this->updateOrCreateSport($data, $sport_locked, 1);
  130. }
  131. return true;
  132. }
  133. //进行中超过3分钟没有更新数据的赛事,检查比赛是否结束
  134. public function checkLiveFixtures()
  135. {
  136. //体育赛事结束前几(分钟)锁盘,90分钟结束
  137. $sport_locked = Config::where('field', 'sport_locked')->first()->val ?? 1;
  138. //1. 统一锁盘时间(比赛开始前1分钟)
  139. SportModel::where(['is_locked' => 0, 'is_roll' => 0])->where('game_time', '<=', time() + 90)->update(['is_locked' => 1]);
  140. //2. 比赛进行中,超过3分钟未更新的数据检测
  141. $end_time = date("Y-m-d H:i:s", time() - 180);
  142. $ids = SportModel::where('status', 1)->where('state', 1)->where('updated_at', '<=', $end_time)->pluck('data_id')->toArray();
  143. if ($ids) {
  144. $chunks = array_chunk($ids, 10);
  145. foreach ($chunks as $chunk) {
  146. $ids = implode('-', $chunk);
  147. $data = SportClientService::fixtures(['ids' => $ids]);
  148. $this->updateOrCreateSport($data, $sport_locked, 1);
  149. }
  150. }
  151. return true;
  152. }
  153. //更新进行中的赛事
  154. public function liveFixtures()
  155. {
  156. //体育赛事结束前几(分钟)锁盘,90分钟结束
  157. $sport_locked = Config::where('field', 'sport_locked')->first()->val ?? 1;
  158. $data = SportClientService::fixtures(['live' => 'all']);
  159. file_put_contents("fixturesLive.json", json_encode($data));
  160. $this->updateOrCreateSport($data, $sport_locked);
  161. return true;
  162. }
  163. private function updateOrCreateSport($data, $sport_locked, $is_check = 0) {
  164. $data = $data['response'];
  165. $tableData = [];
  166. $status = $this->short_status;
  167. foreach ($data as $item) {
  168. $home_statistics = !empty($item['statistics']) ? $item['statistics'][0]['statistics'] : '';
  169. $away_statistics = !empty($item['statistics']) ? $item['statistics'][1]['statistics'] : '';
  170. $sport_data = [
  171. 'data_id' => $item['fixture']['id'],
  172. 'home_team_id' => $item['teams']['home']['id'],
  173. 'home_team_en' => $item['teams']['home']['name'],
  174. 'home_team' => lang($item['teams']['home']['name']),
  175. 'home_team_logo' => $item['teams']['home']['logo'],
  176. 'guest_team_id' => $item['teams']['away']['id'],
  177. 'guest_team_en' => $item['teams']['away']['name'],
  178. 'guest_team' => lang($item['teams']['away']['name']),
  179. 'guest_team_logo' => $item['teams']['away']['logo'],
  180. 'half_score' => "{$item['score']['halftime']['home']}-{$item['score']['halftime']['away']}",
  181. 'rbt' => $item['fixture']['timestamp'],
  182. 'score' => isset($item['goals']) ? "{$item['goals']['home']}-{$item['goals']['away']}":'-',
  183. 'extra_score' => isset($item['score']['extratime']) ? "{$item['score']['extratime']['home']}-{$item['score']['extratime']['away']}" : "",//加时赛比分
  184. 'league' => lang($item['league']['name']),
  185. 'league_en' => $item['league']['name'],
  186. 'state' => $status[$item['fixture']['status']['short']],//比赛状态:0未开始1进行中2已完场3延期4取消
  187. 'game_time' => $item['fixture']['timestamp'],
  188. 'updated_at' => now(),
  189. 'home_statistics' => $home_statistics,
  190. 'away_statistics' => $away_statistics,
  191. 'is_send' => 0,
  192. ];
  193. $fixture_status = null;
  194. if (isset($item['fixture']['status']['long']) ) {
  195. $long = $item['fixture']['status']['long'];
  196. if (isset($this->fixture_status[$long])) {
  197. $fixture_status = json_encode($item['fixture']['status']);
  198. $sport_data['fixture_status'] = $fixture_status;
  199. }
  200. if (isset($this->long_status[$long])) {
  201. $sport_data['state'] = $this->long_status[$long];
  202. }
  203. }
  204. //提前锁盘(比赛进行时长,分钟)
  205. if (isset($item['fixture']['status']['elapsed'])) {
  206. $elapsed = $item['fixture']['status']['elapsed'];
  207. if ((int)$elapsed >= 90 - $sport_locked ) {
  208. $sport_data['is_locked'] = 1;
  209. }
  210. }
  211. $sport_data['score'] = $sport_data['score'] == '-' ? '' : $sport_data['score'];
  212. $sport_data['half_score'] = $sport_data['half_score'] == '-' ? '' : $sport_data['half_score'];
  213. $sport_data['extra_score'] = $sport_data['extra_score'] == '-' ? '' : $sport_data['extra_score'];
  214. //锁盘
  215. if (isset($item['fixture']['status']['blocked']) && $item['fixture']['status']['blocked']) {
  216. $sport_data['is_locked'] = 1;
  217. }
  218. //已结束
  219. if (isset($item['fixture']['status']['finished']) && $item['fixture']['status']['finished']) {
  220. $sport_data['state'] = 2;
  221. }
  222. $info = SportModel::where('data_id', $item['fixture']['id'])->first();
  223. if (!$info) {
  224. $sport_data['created_at'] = now();
  225. $sport_data['status'] = 1;
  226. $tableData[] = $sport_data;
  227. } else {
  228. SportModel::where('data_id', $item['fixture']['id'])->update($sport_data);
  229. }
  230. //比赛结束,插入比赛事件
  231. if ($sport_data['state'] == 2 && !empty($item['events'])) {
  232. foreach($item['events'] as $event) {
  233. SportEvent::create([
  234. 'data_id' => $item['fixture']['id'],
  235. 'type' => $event['type'],
  236. 'time_elapsed' => $event['time']['elapsed'],
  237. 'time' => json_encode($event['time']),
  238. 'detail' => $event['detail'],
  239. 'player' => $event['player'] ? json_encode($event['player']) : $event['player'],
  240. 'team_id' => $event['team']['id'],
  241. 'comments' => $event['comments'],
  242. 'assist' => $event['assist'] ? json_encode($event['assist']) : $event['assist'],
  243. ]);
  244. }
  245. }
  246. }
  247. if ($tableData) {
  248. SportModel::insert($tableData);
  249. }
  250. return true;
  251. }
  252. /**
  253. * 获取指定日期的所有赛事
  254. *
  255. * @return array
  256. */
  257. public function fixtures()
  258. {
  259. //根据配置拉取多少天的赛事信息
  260. $days = Config::where('field', 'sport_days')->first()->val ?? 1;
  261. for($i=0;$i<$days;$i++) {
  262. $date = Carbon::tomorrow()->addDay($i)->toDateString();
  263. $count = SportModel::where('game_time','>=', strtotime($date))->where('game_time','<=', strtotime($date." 23:59:59"))->count();
  264. if (!$count) {
  265. $data = SportClientService::fixtures(['date' => $date]);
  266. $data = $data['response'];
  267. $tableData = [];
  268. $status = $this->short_status;
  269. foreach ($data as $item) {
  270. $home_statistics = !empty($item['statistics']) ? $item['statistics'][0]['statistics'] : '';
  271. $away_statistics = !empty($item['statistics']) ? $item['statistics'][1]['statistics'] : '';
  272. $sport_data = [
  273. 'data_id' => $item['fixture']['id'],
  274. 'home_team_id' => $item['teams']['home']['id'],
  275. 'home_team_en' => $item['teams']['home']['name'],
  276. 'home_team' => lang($item['teams']['home']['name']),
  277. 'home_team_logo' => $item['teams']['home']['logo'],
  278. 'guest_team_id' => $item['teams']['away']['id'],
  279. 'guest_team_en' => $item['teams']['away']['name'],
  280. 'guest_team' => lang($item['teams']['away']['name']),
  281. 'guest_team_logo' => $item['teams']['away']['logo'],
  282. 'half_score' => "{$item['score']['halftime']['home']}-{$item['score']['halftime']['away']}",
  283. 'rbt' => $item['fixture']['timestamp'],
  284. 'score' => isset($item['score']['fulltime']) ? "{$item['score']['fulltime']['home']}-{$item['score']['fulltime']['away']}":'-',
  285. 'league' => lang($item['league']['name']),
  286. 'league_en' => $item['league']['name'],
  287. 'state' => $status[$item['fixture']['status']['short']],//比赛状态:0未开始1进行中2已完场3延期4取消
  288. 'game_time' => $item['fixture']['timestamp'],
  289. 'status' => 1,
  290. 'updated_at' => now(),
  291. 'home_statistics' => $home_statistics,
  292. 'away_statistics' => $away_statistics,
  293. 'is_send' => 0,
  294. ];
  295. $sport_data['score'] = $sport_data['score'] == '-' ? '' : $sport_data['score'];
  296. $sport_data['half_score'] = $sport_data['half_score'] == '-' ? '' : $sport_data['half_score'];
  297. if (!SportModel::where('data_id', $item['fixture']['id'])->exists()) {
  298. $sport_data['created_at'] = now();
  299. $tableData[] = $sport_data;
  300. } else {
  301. SportModel::where('data_id', $item['fixture']['id'])->update($sport_data);
  302. }
  303. //比赛结束,插入比赛事件
  304. if ($sport_data['state'] >= 2 && !empty($item['events'])) {
  305. foreach($item['events'] as $event) {
  306. SportEvent::create([
  307. 'data_id' => $item['fixture']['id'],
  308. 'type' => $event['type'],
  309. 'time_elapsed' => $event['time']['elapsed'],
  310. 'time' => json_encode($event['time']),
  311. 'detail' => $event['detail'],
  312. 'player' => $event['player'] ? json_encode($event['player']) : $event['player'],
  313. 'team_id' => $event['team']['id'],
  314. 'comments' => $event['comments'],
  315. 'assist' => $event['assist'] ? json_encode($event['assist']) : $event['assist'],
  316. ]);
  317. }
  318. }
  319. }
  320. if ($tableData) {
  321. SportModel::insert($tableData);
  322. }
  323. }
  324. }
  325. return true;
  326. }
  327. public function initOdds(){
  328. $page = 1;
  329. $limit = 10;
  330. while (true) {
  331. $list = SportModel::where('odds','<>', null)->forPage($page, $limit)->get()->toArray();
  332. if (empty($list)) {
  333. break;
  334. }
  335. echo $page.PHP_EOL;
  336. foreach($list as $item) {
  337. $odds = json_decode($item['odds'], true);
  338. foreach($odds as $odd) {
  339. $odd_id = $odd['id'];
  340. $odd_name = $odd['name'];
  341. $info = DB::table('sport_odds')->where('odd_id',$odd_id)->where('odd_name_en',$odd_name)->first();
  342. if ($info && (!$info->odd_name || $odd_name != $info->odd_name_en)) {
  343. DB::table('sport_odds')->where('id', $info->id)->update([
  344. 'odd_name_en' => $odd_name,
  345. 'odd_name' => $this->getZhName($odd_name),
  346. ]);
  347. echo '更新数据:'.$odd_id.'-'.$odd_name.PHP_EOL;
  348. } elseif (!$info) {
  349. DB::table('sport_odds')->insert([
  350. 'odd_id' => $odd_id,
  351. 'odd_name_en' => $odd_name,
  352. 'odd_name' => $this->getZhName($odd_name),
  353. 'created_at' => date('Y-m-d H:i:s'),
  354. 'updated_at' => date('Y-m-d H:i:s'),
  355. ]);
  356. echo '插入数据:'.$odd_id.'-'.$odd_name.PHP_EOL;
  357. }
  358. }
  359. }
  360. $page++;
  361. }
  362. }
  363. public function getZhName ($name) {
  364. $betting_terms = [
  365. "Match Winner" => "全场胜负",
  366. "Home/Away" => "主胜/客胜",
  367. "Second Half Winner" => "下半场胜负",
  368. "Asian Handicap" => "亚洲让球盘",
  369. "Goals Over/Under" => "全场大小球",
  370. "Goals Over/Under First Half" => "上半场大小球",
  371. "Goals Over/Under - Second Half" => "下半场大小球",
  372. "HT/FT Double" => "半场+全场双猜",
  373. "Both Teams Score" => "双方均进球",
  374. "Handicap Result" => "让球结果",
  375. "Exact Score" => "精确比分",
  376. "Correct Score - First Half" => "上半场精确比分",
  377. "Correct Score - Second Half" => "下半场精确比分",
  378. "Double Chance" => "双选胜平负",
  379. "First Half Winner" => "上半场胜负",
  380. "Team To Score First" => "首支进球球队",
  381. "Team To Score Last" => "最后进球球队",
  382. "Win Both Halves" => "上下半场均获胜",
  383. "Total - Home" => "主队总进球数",
  384. "Total - Away" => "客队总进球数",
  385. "Both Teams Score - First Half" => "上半场双方均进球",
  386. "Both Teams To Score - Second Half" => "下半场双方均进球",
  387. "Odd/Even" => "总进球数奇偶",
  388. "Odd/Even - First Half" => "上半场进球奇偶",
  389. "Home Team Exact Goals Number" => "主队精确进球数",
  390. "Away Team Exact Goals Number" => "客队精确进球数",
  391. "Results/Both Teams Score" => "赛果+双方进球",
  392. "Odd/Even - Second Half" => "下半场进球奇偶",
  393. "Clean Sheet - Home" => "主队零封",
  394. "Clean Sheet - Away" => "客队零封",
  395. "Win to Nil - Home" => "主队零封获胜",
  396. "Win to Nil - Away" => "客队零封获胜",
  397. "Highest Scoring Half" => "进球更多的半场",
  398. "Handicap Result - First Half" => "上半场让球结果",
  399. "Asian Handicap First Half" => "上半场亚洲让球盘",
  400. "Double Chance - First Half" => "上半场双选胜平负",
  401. "Win To Nil" => "零封获胜",
  402. "Home Odd/Even" => "主队进球奇偶",
  403. "Away Odd/Even" => "客队进球奇偶",
  404. "To Win Either Half" => "赢得任意半场",
  405. "Result/Total Goals" => "赛果+总进球数",
  406. "First 10 min Winner" => "前10分钟胜负",
  407. "Corners Over Under" => "角球大小",
  408. "Home Team Total Goals(1st Half)" => "主队上半场总进球",
  409. "Away Team Total Goals(1st Half)" => "客队上半场总进球",
  410. "Home Team Total Goals(2nd Half)" => "主队下半场总进球",
  411. "Away Team Total Goals(2nd Half)" => "客队下半场总进球",
  412. "Draw No Bet (1st Half)" => "上半场让球平注",
  413. "European Handicap (2nd Half)" => "下半场欧洲让球",
  414. "Draw No Bet (2nd Half)" => "下半场让球平注",
  415. "Total Goals/Both Teams To Score" => "总进球+双方进球",
  416. "Home Corners Over/Under" => "主队角球大小",
  417. "Away Corners Over/Under" => "客队角球大小",
  418. "Total Corners (3 way)" => "总角球三路",
  419. "1x2 - 60 minutes" => "60分钟胜平负",
  420. "1x2 - 30 minutes" => "30分钟胜平负",
  421. "First Team to Score (3 way) 1st Half" => "上半场首支进球球队(三路)",
  422. "Total Corners (1st Half)" => "上半场总角球",
  423. "Corners. Odd/Even" => "角球奇偶",
  424. "RTG_H1" => "上半场进球数(简写)",
  425. "Cards Over/Under" => "黄牌大小",
  426. "To Qualify" => "晋级球队",
  427. "Goal Line" => "进球线",
  428. "Goal Line (1st Half)" => "上半场进球线",
  429. "Home team will score in both halves" => "主队上下半场均进球",
  430. "Away team will score in both halves" => "客队上下半场均进球",
  431. "Last Corner" => "最后角球",
  432. "How many goals will Away Team score?" => "客队进球数",
  433. "Asian Corners" => "亚洲让角球",
  434. "Match Corners" => "全场角球",
  435. "Final Score" => "最终比分",
  436. "Match Goals" => "全场进球",
  437. "Home Team Score a Goal (2nd Half)" => "主队下半场进球",
  438. "Result / Both Teams To Score" => "赛果/双方进球",
  439. "To Win 2nd Half" => "赢下半场",
  440. "Over/Under Line" => "大小球盘口",
  441. "3-Way Handicap" => "三路让球",
  442. "Away Team Goals" => "客队进球",
  443. "Both Teams To Score (2nd Half)" => "下半场双方进球",
  444. "Which team will score the 5th corner? (2 Way)" => "第5个角球归属(二路)",
  445. "Race to the 9th corner?" => "先得9个角球",
  446. "Race to the 7th corner?" => "先得7个角球",
  447. "Draw No Bet" => "让球平注",
  448. "Home Team Goals" => "主队进球",
  449. "Total Corners" => "总角球",
  450. "Fulltime Result" => "全场赛果",
  451. "Race to the 5th corner?" => "先得5个角球",
  452. "Last Team to Score (3 way)" => "最后进球球队(三路)",
  453. "Which team will score the 2nd goal?" => "第二球归属球队",
  454. "Home Team Clean Sheet" => "主队零封",
  455. "How many goals will Home Team score?" => "主队进球数",
  456. "Goals Odd/Even" => "进球奇偶",
  457. "Both Teams to Score" => "双方均进球",
  458. "Away Team Score a Goal (2nd Half)" => "客队下半场进球",
  459. "Which team will score the 4th goal?" => "第四球归属球队",
  460. "Which team will score the 7th corner? (2 Way)" => "第7个角球归属(二路)",
  461. ];
  462. if (isset($betting_terms[$name])) {
  463. return $betting_terms[$name];
  464. }
  465. return '';
  466. }
  467. }