\$row ? (string) \$row->source_url : ''", "'update_time' => time()", "->whereNull('delete_time')", "->order('create_time', 'asc')", "->order('id', 'asc')", "\$row['default_play_url'] = \$row['stream_options'][0]['url'] ?? '';", "if (\$url === '')", ]; foreach ($requiredSnippets as $needle) { if (strpos($service, $needle) === false) { fwrite(STDERR, "missing service token: {$needle}\n"); exit(1); } } $whereNullCount = substr_count($service, "->whereNull('delete_time')"); if ($whereNullCount < 2) { fwrite(STDERR, "missing explicit delete_time guard on both queries\n"); exit(1); } if (!is_file($apiControllerPath)) { fwrite(STDERR, "missing api controller file\n"); exit(1); } $apiController = file_get_contents($apiControllerPath); foreach ([ "'live_list'", 'MatchLiveService::getLiveListForApi', ] as $needle) { if (strpos($apiController, $needle) === false) { fwrite(STDERR, "missing api controller token: {$needle}\n"); exit(1); } } if (!is_file($adminLogicPath)) { fwrite(STDERR, "missing admin logic file\n"); exit(1); } $adminLogic = file_get_contents($adminLogicPath); if (strpos($adminLogic, "'live_url'") !== false) { fwrite(STDERR, "match admin logic still manually allows live_url\n"); exit(1); } if (!is_file($listsPath)) { fwrite(STDERR, "missing match live lists file\n"); exit(1); } $lists = file_get_contents($listsPath); foreach ([ "\$matchId = (int) (\$this->params['match_id'] ?? 0);", "if (\$matchId <= 0) {", "\$query->where('match_id', 0);", ] as $needle) { if (strpos($lists, $needle) === false) { fwrite(STDERR, "missing lists guard token: {$needle}\n"); exit(1); } } if (!is_file($matchLiveLogicPath)) { fwrite(STDERR, "missing match live logic file\n"); exit(1); } $matchLiveLogic = file_get_contents($matchLiveLogicPath); foreach ([ '$sourceUrl = trim((string) $params[\'source_url\']);', '$sourceUrlChanged = $sourceUrl !== trim((string) $live->source_url);', "'source_url' => \$sourceUrl", 'if ($sourceUrlChanged) {', "\$saveData['play_url_m3u8'] = '';", "\$saveData['play_url_hd_m3u8'] = '';", "\$saveData['play_url_flv'] = '';", "\$saveData['play_url_hd_flv'] = '';", "\$saveData['fetch_status'] = 0;", "\$saveData['fetch_error'] = '';", "\$saveData['last_fetch_at'] = 0;", "\$saveData['next_fetch_at'] = \$now;", '$live->save($saveData);', ] as $needle) { if (strpos($matchLiveLogic, $needle) === false) { fwrite(STDERR, "missing match live logic source-url reset guard token: {$needle}\n"); exit(1); } } $savePos = strpos($matchLiveLogic, '$live->save($saveData);'); $guardPos = strpos($matchLiveLogic, 'if ($sourceUrlChanged) {'); if ($savePos === false || $guardPos === false || $guardPos > $savePos) { fwrite(STDERR, "match live logic must guard crawler resets inside source_url change branch\n"); exit(1); } $guardBody = substr($matchLiveLogic, $guardPos, $savePos - $guardPos); foreach ([ "\$saveData['play_url_m3u8'] = '';", "\$saveData['play_url_hd_m3u8'] = '';", "\$saveData['play_url_flv'] = '';", "\$saveData['play_url_hd_flv'] = '';", "\$saveData['fetch_status'] = 0;", "\$saveData['fetch_error'] = '';", "\$saveData['last_fetch_at'] = 0;", "\$saveData['next_fetch_at'] = \$now;", ] as $needle) { if (strpos($guardBody, $needle) === false) { fwrite(STDERR, "match live logic reset token not scoped to source_url change guard: {$needle}\n"); exit(1); } } $unconditionalSaveBody = preg_replace('/if \\(\\$sourceUrlChanged\\) \\{.*?\\}/s', '', $matchLiveLogic, 1); foreach ([ "\$saveData['play_url_m3u8'] = '';", "\$saveData['play_url_hd_m3u8'] = '';", "\$saveData['play_url_flv'] = '';", "\$saveData['play_url_hd_flv'] = '';", "\$saveData['fetch_status'] = 0;", "\$saveData['fetch_error'] = '';", "\$saveData['last_fetch_at'] = 0;", "\$saveData['next_fetch_at'] = \$now;", ] as $needle) { if (strpos($unconditionalSaveBody, $needle) !== false) { fwrite(STDERR, "match live logic still unconditionally resets crawler state: {$needle}\n"); exit(1); } } if (!is_file($validatePath)) { fwrite(STDERR, "missing match live validate file\n"); exit(1); } $validate = file_get_contents($validatePath); foreach ([ 'checkTrimmedTitle', 'checkTrimmedSourceUrl', "!is_scalar(\$value)", "return '直播标题格式错误';", "return '源地址格式错误';", '$value = trim((string) $value);', ] as $needle) { if (strpos($validate, $needle) === false) { fwrite(STDERR, "missing trimmed validation token: {$needle}\n"); exit(1); } } $childLivePos = strpos($apiController, "!empty(\$data['live_list'][0]['source_url'])"); $worldCupFallbackPos = strpos($apiController, "if (empty(\$data['live_url']) && \$this->isWorldCupMatch(\$data))"); if ($childLivePos === false || $worldCupFallbackPos === false || $childLivePos >= $worldCupFallbackPos) { fwrite(STDERR, "match detail live_url fallback order mismatch\n"); exit(1); } $orderedOptionSnippets = [ "['label' => 'M3U8', 'field' => 'play_url_m3u8']", "['label' => 'HD M3U8', 'field' => 'play_url_hd_m3u8']", "['label' => 'FLV', 'field' => 'play_url_flv']", "['label' => 'HD FLV', 'field' => 'play_url_hd_flv']", ]; $lastPos = -1; foreach ($orderedOptionSnippets as $snippet) { $pos = strpos($service, $snippet); if ($pos === false) { fwrite(STDERR, "missing ordered stream option snippet: {$snippet}\n"); exit(1); } if ($pos <= $lastPos) { fwrite(STDERR, "stream option order mismatch\n"); exit(1); } $lastPos = $pos; } echo "service static checks passed\n";