where('type', 'sport') ->field('sport_type, label, icon') ->order('sort asc') ->select() ->toArray(); $result = []; foreach ($types as $item) { $result[] = [ 'value' => $item['sport_type'], 'label' => $item['label'], 'icon' => $item['icon'], ]; } return $this->data($result); } public function leagues() { $sportType = $this->request->get('sport_type/d', 0); $query = League::where('is_show', 1) ->where('type', 'league'); if ($sportType > 0) { $query->where('sport_type', $sportType); } $leagues = $query->field('id, label, sport_type, icon, sort') ->order('sort asc') ->select() ->toArray(); $leagueNames = array_column($leagues, 'label'); $counts = []; if (!empty($leagueNames)) { $rows = MatchEvent::where('is_show', 1) ->where('status', 1) ->whereIn('league_name', $leagueNames) ->field('league_name, COUNT(*) as cnt') ->group('league_name') ->select() ->toArray(); foreach ($rows as $row) { $counts[$row['league_name']] = (int) $row['cnt']; } } $grouped = []; foreach ($leagues as $item) { $st = $item['sport_type']; if (!isset($grouped[$st])) { $grouped[$st] = []; } $grouped[$st][] = [ 'id' => $item['id'], 'league_name' => $item['label'], 'icon' => $item['icon'], 'count' => $counts[$item['label']] ?? 0, ]; } return $this->data($grouped); } public function lists() { $sportType = $this->request->get('sport_type/d', 0); $leagueName = $this->request->get('league_name/s', ''); $endedPage = $this->request->get('ended_page/d', 1); $endedPageSize = $this->request->get('ended_page_size/d', 20); $endedOnly = $this->request->get('ended_only/d', 0); $field = 'id,competition_id,league_name,league_icon,home_team,home_icon,home_score,away_team,away_icon,away_score,sport_type,status,match_time,current_minute,half_score,home_odds,draw_odds,away_odds,home_corner,away_corner,home_yellow,away_yellow,home_red,away_red,is_hot,round_name'; // 基础筛选 $baseWhere = [['is_show', '=', 1]]; if ($sportType > 0) { $baseWhere[] = ['sport_type', '=', $sportType]; } if (!empty($leagueName)) { $baseWhere[] = ['league_name', '=', $leagueName]; } elseif ($sportType <= 0) { $validLeagues = League::where('is_show', 1)->where('type', 'league')->column('label'); if (!empty($validLeagues)) { $baseWhere[] = ['league_name', 'in', $validLeagues]; } } // 已结束:分页,按时间倒序 $endedOffset = ($endedPage - 1) * $endedPageSize; $ended = MatchEvent::where($baseWhere)->where('status', 2) ->field($field)->order('match_time desc') ->limit($endedOffset, $endedPageSize)->select()->toArray(); $endedCount = MatchEvent::where($baseWhere)->where('status', 2)->count(); // 加载更多时只返回已结束数据 if ($endedOnly) { $ended = $this->attachMoneylineOdds($ended); return $this->data([ 'ended' => [ 'lists' => $ended, 'count' => $endedCount, 'page_no' => $endedPage, 'page_size' => $endedPageSize, ], ]); } // 进行中:全部返回 $live = MatchEvent::where($baseWhere)->where('status', 1) ->field($field)->order('match_time asc')->select()->toArray(); // 未开始:只取前20条,按时间正序 $upcoming = MatchEvent::where($baseWhere)->where('status', 0) ->field($field)->order('match_time asc')->limit(20)->select()->toArray(); $liveCount = count($live); $upcomingCount = count($upcoming); $matches = $this->attachMoneylineOdds(array_merge($live, $upcoming, $ended)); $live = array_slice($matches, 0, $liveCount); $upcoming = array_slice($matches, $liveCount, $upcomingCount); $ended = array_slice($matches, $liveCount + $upcomingCount); return $this->data([ 'live' => $live, 'upcoming' => $upcoming, 'ended' => [ 'lists' => $ended, 'count' => $endedCount, 'page_no' => $endedPage, 'page_size' => $endedPageSize, ], ]); } private function attachMoneylineOdds(array $matches): array { if (empty($matches)) { return []; } $homeTeams = []; $awayTeams = []; $matchKeys = []; foreach ($matches as $index => &$match) { $match['home_odds'] = $this->normalizeMoneylineOdds($match['home_odds'] ?? ''); $match['draw_odds'] = $this->normalizeMoneylineOdds($match['draw_odds'] ?? ''); $match['away_odds'] = $this->normalizeMoneylineOdds($match['away_odds'] ?? ''); if ((int)($match['sport_type'] ?? 0) !== 1) { continue; } $key = $this->buildEventOddsMatchKey($match); if ($key === '') { continue; } $matchKeys[$index] = $key; $homeTeams[] = trim((string)($match['home_team'] ?? '')); $awayTeams[] = trim((string)($match['away_team'] ?? '')); } unset($match); $homeTeams = array_values(array_unique(array_filter($homeTeams))); $awayTeams = array_values(array_unique(array_filter($awayTeams))); if (empty($matchKeys) || empty($homeTeams) || empty($awayTeams)) { return $matches; } $rows = MatchOdds::where('sport_type', 'FT') ->whereIn('home_team', $homeTeams) ->whereIn('away_team', $awayTeams) ->where(function ($query) { $query->where('home_win_odds', '<>', '') ->whereOr('draw_odds', '<>', '') ->whereOr('away_win_odds', '<>', ''); }) ->field('home_team,away_team,match_time_text,home_win_odds,draw_odds,away_win_odds,update_time') ->order('update_time desc') ->select() ->toArray(); $oddsMap = []; $scoreMap = []; foreach ($rows as $row) { $key = $this->buildOddsMatchKey($row); if (!in_array($key, $matchKeys, true)) { continue; } $odds = [ 'home_odds' => $this->normalizeMoneylineOdds($row['home_win_odds'] ?? ''), 'draw_odds' => $this->normalizeMoneylineOdds($row['draw_odds'] ?? ''), 'away_odds' => $this->normalizeMoneylineOdds($row['away_win_odds'] ?? ''), ]; $score = count(array_filter($odds, static fn($value) => $value !== '')); if ($score === 0 || $score <= ($scoreMap[$key] ?? -1)) { continue; } $oddsMap[$key] = $odds; $scoreMap[$key] = $score; } foreach ($matchKeys as $index => $key) { if (!isset($oddsMap[$key])) { continue; } $matches[$index] = array_merge($matches[$index], $oddsMap[$key]); } return $matches; } private function buildEventOddsMatchKey(array $match): string { $teamPairKey = $this->buildTeamPairKey( (string)($match['home_team'] ?? ''), (string)($match['away_team'] ?? '') ); $timestamp = (int)($match['match_time'] ?? 0); if ($teamPairKey === '' || $timestamp <= 0) { return ''; } if ($this->isWorldCupMatch($match)) { $timestamp += 8 * 60 * 60; } return $teamPairKey . '|' . date('m-d', $timestamp); } private function normalizeMoneylineOdds($value): string { $value = trim((string)$value); if ($value === '' || !is_numeric($value) || (float)$value <= 0) { return ''; } return $value; } public function detail() { $id = $this->request->get('id/d'); $match = MatchEvent::where('id', $id)->where('is_show', 1)->findOrEmpty(); if ($match->isEmpty()) { return $this->fail('赛事不存在'); } $data = $match->toArray(); $data['live_list'] = MatchLiveService::getLiveListForApi((int) $data['id']); if (empty($data['live_url']) && !empty($data['live_list'][0]['source_url'])) { $data['live_url'] = (string) $data['live_list'][0]['source_url']; } if (empty($data['live_url']) && $this->isWorldCupMatch($data)) { $data['live_url'] = $this->worldCupDefaultLiveUrl; } $data['living_tv'] = ''; $matchDataRaw = MatchData::where('match_id', $data['match_id']) ->order('id desc') ->value('raw_data'); if (!empty($matchDataRaw)) { $rawData = json_decode($matchDataRaw, true); if (json_last_error() === JSON_ERROR_NONE && is_array($rawData)) { $data['living_tv'] = $this->resolveLiveSourceText($rawData); } } $homeTeam = $data['home_team']; $awayTeam = $data['away_team']; // 对赛往绩 $data['history'] = MatchHistory::where(function ($query) use ($homeTeam, $awayTeam) { $query->where([['home_team', '=', $homeTeam], ['away_team', '=', $awayTeam]]) ->whereOr([['home_team', '=', $awayTeam], ['away_team', '=', $homeTeam]]); })->order('match_time desc')->limit(5)->select()->toArray(); // 精彩瞬间(按业务字段去重) $data['events'] = MatchEventLog::where('match_id', $data['match_id']) ->group('minute,event_type,player_name,team_side') ->order('minute asc') ->select()->toArray(); // 主队近期战绩 $data['home_recent'] = MatchRecent::where('team_name', $homeTeam) ->order('match_time desc')->limit(5)->select()->toArray(); // 客队近期战绩 $data['away_recent'] = MatchRecent::where('team_name', $awayTeam) ->order('match_time desc')->limit(5)->select()->toArray(); // 文字直播(最新50条) $data['live_text'] = MatchLiveText::where('match_id', $data['match_id']) ->field('id, msg_id, event_type, username, avatar, message, image, timestamp, create_time') ->order('msg_id desc') ->limit(50) ->select()->toArray(); $data['live_text'] = array_reverse($data['live_text']); return $this->data($data); } private function resolveLiveSourceText(array $rawData): string { foreach (['livingTv', 'live_source', 'live_no_source', 'tv_live_info'] as $key) { if (!empty($rawData[$key])) { $text = $this->normalizeLiveSourceValue($rawData[$key]); if ($text !== '') { return $text; } } } return ''; } private function normalizeLiveSourceValue($value): string { if (is_string($value) || is_numeric($value)) { return trim((string) $value); } if (!is_array($value)) { return ''; } $items = []; foreach ($value as $item) { if (is_string($item) || is_numeric($item)) { $text = trim((string) $item); if ($text !== '') { $items[] = $text; } continue; } if (!is_array($item)) { continue; } foreach (['live_tag', 'name', 'title', 'source_name', 'label'] as $field) { if (!empty($item[$field])) { $text = trim((string) $item[$field]); if ($text !== '') { $items[] = $text; break; } } } } $items = array_values(array_unique(array_filter($items))); return implode('、', $items); } protected function isWorldCupMatch(array $match): bool { return ($match['league_name'] ?? '') === $this->worldCupLeagueName || (int)($match['competition_id'] ?? 0) === 61; } public function liveText() { $matchId = $this->request->get('match_id/d'); if (!$matchId) { return $this->fail('参数错误'); } $lastMsgId = $this->request->get('last_msg_id/d', 0); $query = MatchLiveText::where('match_id', $matchId) ->field('id, msg_id, event_type, username, avatar, message, image, timestamp, create_time') ->order('msg_id desc'); if ($lastMsgId > 0) { $query->where('msg_id', '>', $lastMsgId); } $list = $query->limit(100)->select()->toArray(); $list = array_reverse($list); return $this->data([ 'list' => $list, 'count' => count($list), ]); } public function lineup() { $matchId = $this->request->get('match_id/d'); if (!$matchId) { return $this->fail('参数错误'); } $list = MatchLineup::where('match_id', $matchId) ->field('id, match_id, team_side, is_starter, person_id, person_name, person_logo, shirt_number, captain, position') ->order('team_side asc, is_starter desc, formation_place asc') ->select()->toArray(); return $this->data([ 'list' => $list, ]); } public function worldCupStandings() { $rows = WorldCupStanding::where('season_id', $this->worldCupSeasonId) ->order('stage_name asc') ->order('group_name asc') ->order('row_order asc') ->order('rank asc') ->select() ->toArray(); $stageName = ''; $groups = []; $header = ['球队', '赛', '胜', '平', '负', '进/失', '积分']; foreach ($rows as $row) { if ($stageName === '' && !empty($row['stage_name'])) { $stageName = $row['stage_name']; } $groupName = $row['group_name'] ?: '未分组'; if (!isset($groups[$groupName])) { $groups[$groupName] = [ 'group_name' => $groupName, 'header' => $header, 'teams' => [], ]; } $groups[$groupName]['teams'][] = [ 'team_id' => (int) $row['team_id'], 'team_name' => $row['team_name'], 'team_logo' => $row['team_logo'], 'rank' => (int) $row['rank'], 'points' => (int) $row['points'], 'played' => (int) $row['played'], 'won' => (int) $row['won'], 'drawn' => (int) $row['drawn'], 'lost' => (int) $row['lost'], 'goals_for' => (int) $row['goals_for'], 'goals_against' => (int) $row['goals_against'], 'goal_diff' => (int) $row['goal_diff'], 'row_order' => (int) $row['row_order'], ]; } return $this->data([ 'season_id' => $this->worldCupSeasonId, 'stage_name' => $stageName, 'groups' => array_values($groups), ]); } public function worldCupRankings() { $type = $this->request->get('type/s', 'goals'); if (!in_array($type, ['goals', 'assists'], true)) { return $this->fail('排行榜类型错误'); } $rows = WorldCupPersonRanking::where('season_id', $this->worldCupSeasonId) ->where('rank_type', $type) ->order('rank asc') ->order('count desc') ->order('id asc') ->select() ->toArray(); $header = $type === 'goals' ? ['球员', '球队', '进球'] : ['球员', '球队', '助攻']; $list = []; foreach ($rows as $row) { $list[] = [ 'person_id' => (int) $row['person_id'], 'person_name' => $row['person_name'], 'person_logo' => $row['person_logo'], 'team_id' => (int) $row['team_id'], 'team_name' => $row['team_name'], 'team_logo' => $row['team_logo'], 'rank' => (int) $row['rank'], 'count' => (int) $row['count'], ]; } return $this->data([ 'season_id' => $this->worldCupSeasonId, 'type' => $type, 'header' => $header, 'list' => $list, ]); } public function worldCupSchedule() { $roundOrder = [ '小组赛 第1轮', '小组赛 第2轮', '小组赛 第3轮', '1/16决赛', '1/8决赛', '1/4决赛', '半决赛', '三四名决赛', '决赛', ]; $bracketRounds = ['1/16决赛', '1/8决赛', '1/4决赛', '半决赛', '三四名决赛', '决赛']; $roundWeightMap = []; foreach ($roundOrder as $index => $roundName) { $roundWeightMap[$roundName] = $index; } $rows = MatchEvent::where('league_name', $this->worldCupLeagueName) ->where('sport_type', 1) ->where('is_show', 1) ->field('id,match_id,competition_id,league_name,round_name,stage,home_team,home_icon,home_score,away_team,away_icon,away_score,status,match_time,current_minute,half_score') ->order('sort asc') ->order('match_time asc') ->order('id asc') ->select() ->toArray(); $scheduleMap = []; foreach ($rows as $row) { $roundName = $row['round_name'] ?: '未分轮次'; if (!isset($scheduleMap[$roundName])) { $scheduleMap[$roundName] = [ 'round_name' => $roundName, 'matches' => [], '_weight' => $roundWeightMap[$roundName] ?? 999, ]; } $scheduleMap[$roundName]['matches'][] = [ 'id' => (int) $row['id'], 'match_id' => (int) $row['match_id'], 'competition_id' => (int) $row['competition_id'], 'league_name' => $row['league_name'], 'round_name' => $roundName, 'stage' => $row['stage'], 'home_team' => $row['home_team'], 'home_icon' => $row['home_icon'], 'home_score' => (int) $row['home_score'], 'away_team' => $row['away_team'], 'away_icon' => $row['away_icon'], 'away_score' => (int) $row['away_score'], 'status' => (int) $row['status'], 'match_time' => (int) $row['match_time'], 'current_minute' => $row['current_minute'], 'half_score' => $row['half_score'], ]; } $scheduleRounds = array_values($scheduleMap); usort($scheduleRounds, function ($left, $right) { return $left['_weight'] <=> $right['_weight']; }); $currentRoundName = ''; foreach ($scheduleRounds as $round) { foreach ($round['matches'] as $match) { if ((int) $match['status'] === 1) { $currentRoundName = $round['round_name']; break 2; } } } if ($currentRoundName === '') { foreach ($scheduleRounds as $round) { foreach ($round['matches'] as $match) { if ((int) $match['status'] === 0) { $currentRoundName = $round['round_name']; break 2; } } } } if ($currentRoundName === '' && !empty($scheduleRounds)) { $currentRoundName = $scheduleRounds[count($scheduleRounds) - 1]['round_name']; } $bracketRoundList = []; foreach ($scheduleRounds as &$round) { unset($round['_weight']); if (in_array($round['round_name'], $bracketRounds, true)) { $bracketRoundList[] = $round; } } unset($round); return $this->data([ 'season_id' => $this->worldCupSeasonId, 'current_round_name' => $currentRoundName, 'bracket_rounds' => $bracketRoundList, 'schedule_rounds' => $scheduleRounds, ]); } public function worldCupLiveCards() { return $this->data([ 'season_id' => $this->worldCupSeasonId, 'list' => MatchLiveService::getWorldCupLiveCards(), ]); } public function liveCards() { $sportType = $this->request->get('sport_type/d', 0); $leagueName = trim($this->request->get('league_name/s', '')); return $this->data([ 'list' => MatchLiveService::getLiveCards($sportType, $leagueName), ]); } public function odds() { return $this->data([ 'list' => $this->getOddsList( $this->request->get('show_type/s', ''), strtolower(trim($this->request->get('source_site/s', ''))), trim($this->request->get('keyword/s', '')), $this->request->get('include_special/d', 0), min(max($this->request->get('limit/d', 120), 1), 300), $this->request->get('sport_type/d', 0), trim($this->request->get('league_name/s', '')), false, trim($this->request->get('home_team/s', '')), trim($this->request->get('away_team/s', '')), trim($this->request->get('match_date/s', '')) ), 'platform_options' => $this->getOddsPlatformOptions(), ]); } public function worldCupOdds() { return $this->data([ 'season_id' => $this->worldCupSeasonId, 'list' => $this->getOddsList( $this->request->get('show_type/s', ''), strtolower(trim($this->request->get('source_site/s', ''))), trim($this->request->get('keyword/s', '')), $this->request->get('include_special/d', 0), min(max($this->request->get('limit/d', 120), 1), 300), 1, '', true ), 'platform_options' => $this->getOddsPlatformOptions(), ]); } private function getOddsList( string $showType, string $sourceSite, string $keyword, int $includeSpecial, int $limit, int $sportType = 0, string $leagueName = '', bool $worldCupEventOnly = false, string $homeTeam = '', string $awayTeam = '', string $matchDate = '' ): array { $validShowTypes = ['live', 'today', 'early']; $query = MatchOdds::where([]); if ($sportType > 0) { $oddsSportType = $this->resolveOddsSportType($sportType); if ($oddsSportType === '') { return []; } $query->where('sport_type', $oddsSportType); } if ($leagueName !== '') { $query->where('league_name', $leagueName); } if (!$includeSpecial) { $query->where('league_name', 'not like', '%特定球员%'); } if (in_array($showType, $validShowTypes, true)) { $query->where('show_type', $showType); } if ($sourceSite !== '') { $sourceSite = preg_replace('/[^a-z0-9_-]/', '', $sourceSite); if ($sourceSite !== '') { $query->where('source_site', $sourceSite); } } if ($keyword !== '') { $likeKeyword = '%' . addcslashes($keyword, '\\%_') . '%'; $query->where(function ($query) use ($likeKeyword) { $query->where('league_name', 'like', $likeKeyword) ->whereOr('home_team', 'like', $likeKeyword) ->whereOr('away_team', 'like', $likeKeyword) ->whereOr('match_time_text', 'like', $likeKeyword); }); } if ($homeTeam !== '') { $query->where('home_team', 'like', '%' . addcslashes($homeTeam, '\\%_') . '%'); } if ($awayTeam !== '') { $query->where('away_team', 'like', '%' . addcslashes($awayTeam, '\\%_') . '%'); } $matchDatePatterns = $this->buildOddsMatchDatePatterns($matchDate); if (!empty($matchDatePatterns)) { $query->where(function ($query) use ($matchDatePatterns) { foreach ($matchDatePatterns as $index => $pattern) { $likePattern = '%' . addcslashes($pattern, '\\%_') . '%'; if ($index === 0) { $query->where('match_time_text', 'like', $likePattern); } else { $query->whereOr('match_time_text', 'like', $likePattern); } } }); } $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->buildOddsEventMap($sportType, $leagueName, $worldCupEventOnly); $matches = []; foreach ($rows as $row) { $platform = $this->formatOddsPlatform($row); if (empty($platform['markets'])) { continue; } $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'][] = $platform; $matches[$matchKey]['update_time'] = max((int)$matches[$matchKey]['update_time'], (int)$row['update_time']); } $list = array_values(array_filter($matches, function ($item) { return !empty($item['platforms']); })); 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 $list; } private function buildOddsEventMap( int $sportType = 0, string $leagueName = '', bool $worldCupOnly = false ): array { $query = MatchEvent::where('is_show', 1); if ($sportType > 0) { $query->where('sport_type', $sportType); } if ($leagueName !== '') { $query->where('league_name', $leagueName); } if ($worldCupOnly) { $query->where(function ($query) { $query->where('league_name', $this->worldCupLeagueName) ->whereOr('competition_id', 61); }); } $rows = $query ->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 resolveOddsSportType(int $sportType): string { $map = [ 1 => 'FT', ]; return $map[$sportType] ?? ''; } private function buildOddsMatchKey(array $row): string { $teamPairKey = $this->buildTeamPairKey( $row['home_team'] ?? '', $row['away_team'] ?? '' ); $dateKey = $this->buildOddsMatchDateKey( (string)($row['match_time_text'] ?? '') ); if ($teamPairKey !== '') { return $teamPairKey . '|' . $dateKey; } return implode('|', [ trim((string)($row['league_name'] ?? '')), trim((string)($row['match_time_text'] ?? '')), (string)($row['source_match_id'] ?? ''), ]); } private function buildOddsMatchDateKey(string $matchTimeText): string { if (preg_match('/(?:\d{4}[-\/])?(\d{1,2})[-\/](\d{1,2})/', $matchTimeText, $matches)) { return sprintf('%02d-%02d', (int)$matches[1], (int)$matches[2]); } return trim($matchTimeText); } private function buildOddsMatchDatePatterns(string $matchDate): array { $matchDate = trim($matchDate); if ($matchDate === '') { return []; } $normalized = str_replace('/', '-', $matchDate); if (preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $normalized, $matches)) { $year = (int)$matches[1]; $month = (int)$matches[2]; $day = (int)$matches[3]; return array_values(array_unique([ sprintf('%04d-%02d-%02d', $year, $month, $day), sprintf('%04d/%02d/%02d', $year, $month, $day), sprintf('%02d-%02d', $month, $day), sprintf('%02d/%02d', $month, $day), sprintf('%d-%d', $month, $day), sprintf('%d/%d', $month, $day), ])); } if (preg_match('/^(\d{1,2})-(\d{1,2})$/', $normalized, $matches)) { $month = (int)$matches[1]; $day = (int)$matches[2]; return array_values(array_unique([ sprintf('%02d-%02d', $month, $day), sprintf('%02d/%02d', $month, $day), sprintf('%d-%d', $month, $day), sprintf('%d/%d', $month, $day), ])); } return [$matchDate]; } 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 = $this->getOddsPlatformNameMap(); return $map[$sourceSite] ?? ($sourceSite !== '' ? strtoupper($sourceSite) : '平台'); } private function getOddsPlatformNameMap(): array { return [ 'hg' => '皇冠', 'titan007' => '球探', ]; } private function getOddsPlatformOptions(): array { $platformMap = $this->getOddsPlatformNameMap(); $sourceSites = MatchOdds::where('source_site', '<>', '') ->group('source_site') ->column('source_site'); foreach ($sourceSites as $sourceSite) { $sourceSite = strtolower(trim((string)$sourceSite)); if ($sourceSite !== '' && !isset($platformMap[$sourceSite])) { $platformMap[$sourceSite] = strtoupper($sourceSite); } } $options = []; foreach ($platformMap as $key => $label) { $options[] = [ 'key' => $key, 'label' => $label, ]; } return $options; } private function resolveOddsShowTypeText(string $showType): string { $map = [ 'live' => '滚球盘', 'today' => '今日盘', 'early' => '早盘', ]; return $map[$showType] ?? '盘口'; } }