diff --git a/server/app/api/controller/MatchController.php b/server/app/api/controller/MatchController.php index 3cba9da..22a8b53 100644 --- a/server/app/api/controller/MatchController.php +++ b/server/app/api/controller/MatchController.php @@ -10,6 +10,7 @@ use app\common\model\match\MatchData; use app\common\model\match\MatchRecent; use app\common\model\match\MatchLiveText; use app\common\model\match\MatchLineup; +use app\common\model\match\MatchOdds; use app\common\model\match\WorldCupPersonRanking; use app\common\model\match\WorldCupStanding; use app\common\model\league\League; @@ -17,7 +18,7 @@ use app\common\service\match\MatchLiveService; class MatchController extends BaseApiController { - public array $notNeedLogin = ['lists', 'detail', 'leagues', 'sportTypes', 'liveText', 'lineup', 'worldCupStandings', 'worldCupRankings', 'worldCupSchedule', 'worldCupLiveCards']; + public array $notNeedLogin = ['lists', 'detail', 'leagues', 'sportTypes', 'liveText', 'lineup', 'worldCupStandings', 'worldCupRankings', 'worldCupSchedule', 'worldCupLiveCards', 'worldCupOdds']; protected int $worldCupSeasonId = 26123; protected string $worldCupLeagueName = '世界杯'; @@ -511,4 +512,185 @@ class MatchController extends BaseApiController 'list' => MatchLiveService::getWorldCupLiveCards(), ]); } + + public function worldCupOdds() + { + $showType = $this->request->get('show_type/s', ''); + $includeSpecial = $this->request->get('include_special/d', 0); + $limit = min(max($this->request->get('limit/d', 120), 1), 300); + $validShowTypes = ['live', 'today', 'early']; + + $query = MatchOdds::where('sport_type', 'FT') + ->where('league_name', 'like', '%世界杯%'); + if (!$includeSpecial) { + $query->where('league_name', 'not like', '%特定球员%'); + } + if (in_array($showType, $validShowTypes, true)) { + $query->where('show_type', $showType); + } + + $rows = $query + ->field('id,source_site,source_url,source_match_id,source_league_id,sport_type,show_type,odds_type,league_name,match_time_text,home_team,away_team,handicap_line,handicap_home_odds,handicap_away_odds,total_line,total_over_odds,total_under_odds,home_win_odds,draw_odds,away_win_odds,update_time') + ->order('update_time desc') + ->order('id desc') + ->limit($limit) + ->select() + ->toArray(); + + $eventMap = $this->buildWorldCupEventMap(); + $matches = []; + foreach ($rows as $row) { + $matchKey = $this->buildOddsMatchKey($row); + if (!isset($matches[$matchKey])) { + $event = $eventMap[$this->buildTeamPairKey($row['home_team'] ?? '', $row['away_team'] ?? '')] ?? []; + $matches[$matchKey] = [ + 'id' => (int)($event['id'] ?? 0), + 'match_id' => (int)($event['match_id'] ?? 0), + 'source_match_ids' => [], + 'league_name' => $row['league_name'], + 'match_time_text' => $row['match_time_text'], + 'match_time' => (int)($event['match_time'] ?? 0), + 'home_team' => $row['home_team'], + 'home_icon' => $event['home_icon'] ?? '', + 'home_score' => (int)($event['home_score'] ?? 0), + 'away_team' => $row['away_team'], + 'away_icon' => $event['away_icon'] ?? '', + 'away_score' => (int)($event['away_score'] ?? 0), + 'status' => (int)($event['status'] ?? 0), + 'current_minute' => $event['current_minute'] ?? '', + 'platforms' => [], + 'update_time' => 0, + ]; + } + + $matches[$matchKey]['source_match_ids'][] = (string)$row['source_match_id']; + $matches[$matchKey]['platforms'][] = $this->formatOddsPlatform($row); + $matches[$matchKey]['update_time'] = max((int)$matches[$matchKey]['update_time'], (int)$row['update_time']); + } + + $list = array_values($matches); + foreach ($list as &$item) { + $item['source_match_ids'] = array_values(array_unique($item['source_match_ids'])); + usort($item['platforms'], function ($left, $right) { + return (int)$right['update_time'] <=> (int)$left['update_time']; + }); + } + unset($item); + + usort($list, function ($left, $right) { + return (int)$right['update_time'] <=> (int)$left['update_time']; + }); + + return $this->data([ + 'season_id' => $this->worldCupSeasonId, + 'list' => $list, + ]); + } + + private function buildWorldCupEventMap(): array + { + $rows = MatchEvent::where('league_name', $this->worldCupLeagueName) + ->where('sport_type', 1) + ->where('is_show', 1) + ->field('id,match_id,home_team,home_icon,home_score,away_team,away_icon,away_score,status,match_time,current_minute') + ->select() + ->toArray(); + + $map = []; + foreach ($rows as $row) { + $key = $this->buildTeamPairKey($row['home_team'] ?? '', $row['away_team'] ?? ''); + if ($key !== '') { + $map[$key] = $row; + } + } + return $map; + } + + private function buildOddsMatchKey(array $row): string + { + return implode('|', [ + trim((string)($row['league_name'] ?? '')), + trim((string)($row['match_time_text'] ?? '')), + $this->buildTeamPairKey($row['home_team'] ?? '', $row['away_team'] ?? ''), + ]); + } + + private function buildTeamPairKey(string $homeTeam, string $awayTeam): string + { + $homeTeam = preg_replace('/\s+/u', '', trim($homeTeam)); + $awayTeam = preg_replace('/\s+/u', '', trim($awayTeam)); + if ($homeTeam === '' || $awayTeam === '') { + return ''; + } + return $homeTeam . 'vs' . $awayTeam; + } + + private function formatOddsPlatform(array $row): array + { + return [ + 'platform_key' => (string)$row['source_site'], + 'platform_name' => $this->resolveOddsPlatformName((string)$row['source_site']), + 'show_type' => (string)$row['show_type'], + 'show_type_text' => $this->resolveOddsShowTypeText((string)$row['show_type']), + 'odds_type' => (string)$row['odds_type'], + 'source_match_id' => (string)$row['source_match_id'], + 'update_time' => (int)$row['update_time'], + 'markets' => array_values(array_filter([ + $this->buildOddsMarket('独赢', '', [ + ['label' => '主胜', 'value' => $row['home_win_odds']], + ['label' => '平', 'value' => $row['draw_odds']], + ['label' => '客胜', 'value' => $row['away_win_odds']], + ]), + $this->buildOddsMarket('让球', (string)$row['handicap_line'], [ + ['label' => '主队', 'value' => $row['handicap_home_odds']], + ['label' => '客队', 'value' => $row['handicap_away_odds']], + ]), + $this->buildOddsMarket('大小球', (string)$row['total_line'], [ + ['label' => '大', 'value' => $row['total_over_odds']], + ['label' => '小', 'value' => $row['total_under_odds']], + ]), + ])), + ]; + } + + private function buildOddsMarket(string $label, string $line, array $values): ?array + { + $filtered = []; + foreach ($values as $item) { + $value = trim((string)($item['value'] ?? '')); + if ($value === '') { + continue; + } + $filtered[] = [ + 'label' => $item['label'], + 'value' => $value, + ]; + } + if (empty($filtered)) { + return null; + } + return [ + 'label' => $label, + 'line' => trim($line), + 'values' => $filtered, + ]; + } + + private function resolveOddsPlatformName(string $sourceSite): string + { + $map = [ + 'hg' => '滚球', + ]; + return $map[$sourceSite] ?? ($sourceSite !== '' ? strtoupper($sourceSite) : '平台'); + } + + private function resolveOddsShowTypeText(string $showType): string + { + $map = [ + 'live' => '滚球盘', + 'today' => '今日盘', + 'early' => '早盘', + ]; + return $map[$showType] ?? '盘口'; + } } diff --git a/server/app/common/model/match/MatchOdds.php b/server/app/common/model/match/MatchOdds.php new file mode 100644 index 0000000..b41c380 --- /dev/null +++ b/server/app/common/model/match/MatchOdds.php @@ -0,0 +1,10 @@ +