restore admin helper chain and stabilize seo routes
This commit is contained in:
@@ -82,17 +82,15 @@ class VideoService
|
||||
$strTmpCode = $this->SiteContext->TemplatesModel['t_code'];
|
||||
|
||||
if ($strTmpCode == 'videoGpt1') {
|
||||
// videoGpt1 runtime resolves detail data by v_id.
|
||||
// Use id-first urls to avoid stale slug drift creating dead deep links.
|
||||
return $this->SiteContext->UrlBuilder->detail('', $intVId);
|
||||
} else {
|
||||
$strKey = 'VIDEO_INFO_URL';
|
||||
$strUrl = $this->SiteContext->DomainModel->getFomartUrl($strKey, [
|
||||
'intVId' => $intVId,
|
||||
'strPinYin' => $strPinYin
|
||||
]);
|
||||
return (string)$strUrl;
|
||||
return $this->SiteContext->UrlBuilder->detail($strPinYin, $intVId);
|
||||
}
|
||||
|
||||
$strKey = 'VIDEO_INFO_URL';
|
||||
$strUrl = $this->SiteContext->DomainModel->getFomartUrl($strKey, [
|
||||
'intVId' => $intVId,
|
||||
'strPinYin' => $strPinYin
|
||||
]);
|
||||
return (string)$strUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,16 +106,16 @@ class VideoService
|
||||
$strTmpCode = $this->SiteContext->TemplatesModel['t_code'];
|
||||
|
||||
if ($strTmpCode == 'videoGpt1') {
|
||||
return $this->SiteContext->UrlBuilder->detailForge('', $intVId, $intVForgeId);
|
||||
} else {
|
||||
$strKey = 'FORGE_VIDEO_INFO_URL';
|
||||
$strUrl = $this->SiteContext->DomainModel->getFomartUrl($strKey, [
|
||||
'intVId' => $intVId,
|
||||
'strPinYin' => $strPinYin,
|
||||
'intVForgeId' => $intVForgeId,
|
||||
]);
|
||||
return (string)$strUrl;
|
||||
return $this->SiteContext->UrlBuilder->detailForge($strPinYin, $intVId, $intVForgeId);
|
||||
}
|
||||
|
||||
$strKey = 'FORGE_VIDEO_INFO_URL';
|
||||
$strUrl = $this->SiteContext->DomainModel->getFomartUrl($strKey, [
|
||||
'intVId' => $intVId,
|
||||
'strPinYin' => $strPinYin,
|
||||
'intVForgeId' => $intVForgeId,
|
||||
]);
|
||||
return (string)$strUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,17 +132,17 @@ class VideoService
|
||||
$strTmpCode = $this->SiteContext->TemplatesModel['t_code'];
|
||||
|
||||
if ($strTmpCode == 'videoGpt1') {
|
||||
return $this->SiteContext->UrlBuilder->play('', $intVId, $strPlayType, $intPlayIndex);
|
||||
} else {
|
||||
$strKey = 'VIDEO_PLAY_URL';
|
||||
$strUrl = $this->SiteContext->DomainModel->getFomartUrl($strKey, [
|
||||
'intVId' => $intVId,
|
||||
'strPinYin' => $strPinYin,
|
||||
'strPlayType' => $strPlayType,
|
||||
'intPlayIndex' => $intPlayIndex,
|
||||
]);
|
||||
return (string)$strUrl;
|
||||
return $this->SiteContext->UrlBuilder->play($strPinYin, $intVId, $strPlayType, $intPlayIndex);
|
||||
}
|
||||
|
||||
$strKey = 'VIDEO_PLAY_URL';
|
||||
$strUrl = $this->SiteContext->DomainModel->getFomartUrl($strKey, [
|
||||
'intVId' => $intVId,
|
||||
'strPinYin' => $strPinYin,
|
||||
'strPlayType' => $strPlayType,
|
||||
'intPlayIndex' => $intPlayIndex,
|
||||
]);
|
||||
return (string)$strUrl;
|
||||
}
|
||||
|
||||
|
||||
@@ -762,16 +760,42 @@ class VideoService
|
||||
*/
|
||||
public function getVideoByVId(int $intVId, int|NULL $intVForgeId)
|
||||
{
|
||||
$boolRequirePlayable = (string)request()->route('strPlayType') !== '' || (int)request()->route('intPlayIndex') > 0;
|
||||
|
||||
$this->debugFallbackProbe('enter_getVideoByVId', [
|
||||
'requested_v_id' => $intVId,
|
||||
'forge_id' => $intVForgeId,
|
||||
'require_playable' => $boolRequirePlayable ? 1 : 0,
|
||||
'host' => (string)(request()->host(true) ?: request()->host()),
|
||||
'path' => (string)request()->pathinfo(),
|
||||
]);
|
||||
|
||||
// 视频详情
|
||||
$arrVideo = $this->VideoModel->getVideoByVId($intVId);
|
||||
|
||||
$this->debugFallbackProbe('after_direct_lookup', [
|
||||
'requested_v_id' => $intVId,
|
||||
'found' => empty($arrVideo) ? 0 : 1,
|
||||
'resolved_v_id' => (int)($arrVideo['v_id'] ?? 0),
|
||||
]);
|
||||
|
||||
if (empty($arrVideo)) {
|
||||
$arrVideo = $this->resolveFallbackVideoByRequest($intVId, $boolRequirePlayable);
|
||||
}
|
||||
|
||||
if (empty($arrVideo)) {
|
||||
$this->debugFallbackProbe('final_404', [
|
||||
'requested_v_id' => $intVId,
|
||||
'path' => (string)request()->pathinfo(),
|
||||
]);
|
||||
throw new HttpException(404, '视频不存在');
|
||||
}
|
||||
|
||||
if ($intVForgeId >= 0 && !empty($arrVideo['v_seo_words'])) {
|
||||
$arrVideo['v_name'] = $arrVideo['v_seo_words'][$intVForgeId - 1];
|
||||
$intForgeOffset = max(0, $intVForgeId - 1);
|
||||
if (!empty($arrVideo['v_seo_words'][$intForgeOffset])) {
|
||||
$arrVideo['v_name'] = $arrVideo['v_seo_words'][$intForgeOffset];
|
||||
}
|
||||
}
|
||||
|
||||
// 取前5个元素
|
||||
@@ -785,7 +809,7 @@ class VideoService
|
||||
$strTopLang = '2025';
|
||||
|
||||
// 点击数
|
||||
$arrVideoClicks = $this->VideoClicksModel->getStats($intVId);
|
||||
$arrVideoClicks = $this->VideoClicksModel->getStats((int)($arrVideo['v_id'] ?? $intVId));
|
||||
|
||||
// 更新值
|
||||
ConverterMovel::setVal([
|
||||
@@ -806,6 +830,185 @@ class VideoService
|
||||
return $arrVideo;
|
||||
}
|
||||
|
||||
protected function resolveFallbackVideoByRequest(int $requestedVId, bool $requirePlayable = false): ?array
|
||||
{
|
||||
$strHost = (string)($this->SiteContext->DomainModel->d_domain ?? 'default');
|
||||
$arrBindings = $this->readFallbackBindings($strHost);
|
||||
$strBindingKey = (string)$requestedVId;
|
||||
|
||||
$this->debugFallbackProbe('enter_resolve_fallback', [
|
||||
'requested_v_id' => $requestedVId,
|
||||
'require_playable' => $requirePlayable ? 1 : 0,
|
||||
'host' => $strHost,
|
||||
'existing_bindings' => count($arrBindings),
|
||||
]);
|
||||
|
||||
$boundVId = (int)($arrBindings[$strBindingKey] ?? 0);
|
||||
if ($boundVId > 0 && $boundVId !== $requestedVId) {
|
||||
$arrVideo = $this->VideoModel->getVideoByVId($boundVId);
|
||||
if (!empty($arrVideo) && (!$requirePlayable || $this->hasPlayableSource($arrVideo))) {
|
||||
$this->debugFallbackProbe('reuse_bound_video', [
|
||||
'requested_v_id' => $requestedVId,
|
||||
'bound_v_id' => $boundVId,
|
||||
]);
|
||||
$arrVideo['_fallback_reason'] = 'bound_random_video';
|
||||
$arrVideo['_requested_v_id'] = $requestedVId;
|
||||
return $arrVideo;
|
||||
}
|
||||
}
|
||||
|
||||
$arrFilter = [];
|
||||
if ($requirePlayable) {
|
||||
$arrFilter['v_play_url'] = ['$exists' => true, '$ne' => []];
|
||||
}
|
||||
|
||||
for ($attempt = 0; $attempt < 6; $attempt++) {
|
||||
$cursor = $this->VideoModel->getCol()->aggregate([
|
||||
['$match' => $arrFilter],
|
||||
['$sample' => ['size' => 8]],
|
||||
]);
|
||||
|
||||
$arrRows = iterator_to_array($cursor);
|
||||
$this->debugFallbackProbe('sample_attempt', [
|
||||
'requested_v_id' => $requestedVId,
|
||||
'attempt' => $attempt + 1,
|
||||
'sample_count' => count($arrRows),
|
||||
]);
|
||||
|
||||
foreach ($arrRows as $row) {
|
||||
$candidateId = (int)($row['v_id'] ?? 0);
|
||||
if ($candidateId <= 0 || $candidateId === $requestedVId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$arrVideo = $this->VideoModel->getVideoByVId($candidateId);
|
||||
if (empty($arrVideo)) {
|
||||
continue;
|
||||
}
|
||||
if ($requirePlayable && !$this->hasPlayableSource($arrVideo)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$arrBindings[$strBindingKey] = $candidateId;
|
||||
$this->writeFallbackBindings($strHost, $arrBindings);
|
||||
$this->debugFallbackProbe('sample_success', [
|
||||
'requested_v_id' => $requestedVId,
|
||||
'candidate_v_id' => $candidateId,
|
||||
'attempt' => $attempt + 1,
|
||||
]);
|
||||
|
||||
$arrVideo['_fallback_reason'] = 'random_video';
|
||||
$arrVideo['_requested_v_id'] = $requestedVId;
|
||||
return $arrVideo;
|
||||
}
|
||||
}
|
||||
|
||||
$arrVideo = $this->VideoModel->findOne($arrFilter, ['sort' => ['v_level' => -1]]);
|
||||
if (!empty($arrVideo)) {
|
||||
$candidateId = (int)($arrVideo['v_id'] ?? 0);
|
||||
if ($candidateId > 0 && $candidateId !== $requestedVId) {
|
||||
$arrBindings[$strBindingKey] = $candidateId;
|
||||
$this->writeFallbackBindings($strHost, $arrBindings);
|
||||
$this->debugFallbackProbe('ordered_fallback_success', [
|
||||
'requested_v_id' => $requestedVId,
|
||||
'candidate_v_id' => $candidateId,
|
||||
]);
|
||||
$arrVideo['_fallback_reason'] = 'ordered_fallback_video';
|
||||
$arrVideo['_requested_v_id'] = $requestedVId;
|
||||
return $arrVideo;
|
||||
}
|
||||
}
|
||||
|
||||
$this->debugFallbackProbe('fallback_exhausted', [
|
||||
'requested_v_id' => $requestedVId,
|
||||
'require_playable' => $requirePlayable ? 1 : 0,
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function hasPlayableSource(array $arrVideo): bool
|
||||
{
|
||||
$arrPlayUrl = (array)($arrVideo['v_play_url'] ?? []);
|
||||
if (empty($arrPlayUrl)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($arrPlayUrl as $episodes) {
|
||||
if (is_array($episodes) && !empty($episodes)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function readFallbackBindings(string $host): array
|
||||
{
|
||||
$path = $this->fallbackBindingsPath($host);
|
||||
if (!is_file($path)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$content = @file_get_contents($path);
|
||||
if (!is_string($content) || trim($content) === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$payload = json_decode($content, true);
|
||||
return is_array($payload) ? $payload : [];
|
||||
}
|
||||
|
||||
protected function writeFallbackBindings(string $host, array $bindings): void
|
||||
{
|
||||
$path = $this->fallbackBindingsPath($host);
|
||||
$dir = dirname($path);
|
||||
if (!is_dir($dir)) {
|
||||
@mkdir($dir, 0777, true);
|
||||
}
|
||||
|
||||
@file_put_contents(
|
||||
$path,
|
||||
json_encode($bindings, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL,
|
||||
LOCK_EX
|
||||
);
|
||||
}
|
||||
|
||||
protected function fallbackBindingsPath(string $host): string
|
||||
{
|
||||
$host = trim(strtolower($host));
|
||||
if ($host === '') {
|
||||
$host = 'default';
|
||||
}
|
||||
$safeHost = preg_replace('/[^a-z0-9._-]+/i', '-', $host);
|
||||
return rtrim((string)root_path(), DIRECTORY_SEPARATOR)
|
||||
. DIRECTORY_SEPARATOR . 'storage'
|
||||
. DIRECTORY_SEPARATOR . 'video_fallback_bindings'
|
||||
. DIRECTORY_SEPARATOR . $safeHost . '.json';
|
||||
}
|
||||
|
||||
protected function debugFallbackProbe(string $stage, array $payload = []): void
|
||||
{
|
||||
$path = rtrim((string)root_path(), DIRECTORY_SEPARATOR)
|
||||
. DIRECTORY_SEPARATOR . 'storage'
|
||||
. DIRECTORY_SEPARATOR . 'video_fallback_bindings'
|
||||
. DIRECTORY_SEPARATOR . '_probe.log';
|
||||
$dir = dirname($path);
|
||||
if (!is_dir($dir)) {
|
||||
@mkdir($dir, 0777, true);
|
||||
}
|
||||
$line = [
|
||||
'ts' => date('c'),
|
||||
'stage' => $stage,
|
||||
'payload' => $payload,
|
||||
];
|
||||
@file_put_contents(
|
||||
$path,
|
||||
json_encode($line, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL,
|
||||
FILE_APPEND | LOCK_EX
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 播放线路-中文
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user