1015 lines
36 KiB
PHP
1015 lines
36 KiB
PHP
<?php
|
|
|
|
namespace app\api\controller;
|
|
|
|
use app\api\lists\match\MatchLists;
|
|
use app\common\model\match\MatchEvent;
|
|
use app\common\model\match\MatchHistory;
|
|
use app\common\model\match\MatchEventLog;
|
|
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;
|
|
use app\common\service\match\MatchLiveService;
|
|
|
|
class MatchController extends BaseApiController
|
|
{
|
|
public array $notNeedLogin = ['lists', 'detail', 'leagues', 'sportTypes', 'liveText', 'lineup', 'liveCards', 'odds', 'worldCupStandings', 'worldCupRankings', 'worldCupSchedule', 'worldCupLiveCards', 'worldCupOdds'];
|
|
|
|
protected int $worldCupSeasonId = 26123;
|
|
protected string $worldCupLeagueName = '世界杯';
|
|
protected string $worldCupDefaultLiveUrl = 'https://sports.cctv.com/?spm=C28340.P9mj1V5B06xk.E2XVQsMhlk44.7';
|
|
|
|
public function sportTypes()
|
|
{
|
|
$types = League::where('is_show', 1)
|
|
->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] ?? '盘口';
|
|
}
|
|
}
|