From 934ead3e11c70c020d6579015b95ffacfa7bdc8f Mon Sep 17 00:00:00 2001 From: make Date: Mon, 19 Jan 2026 21:58:00 +0800 Subject: [PATCH] baidu tuison --- code/app/admin/controller/Site.php | 2 + code/app/model/VideoCategoryModel.php | 2 +- code/app/task/crawler/CrawlerConfig.php | 2 + .../app/task/logic/BaiduPushVideoUrlLogic.php | 174 +++++ code/app/task/logic/VideoSiteMapLogic.php | 90 ++- code/app/task/module/PlanTask.php | 22 + ...9132713_add_baidu_token_to_video_table.php | 27 + code/database/seeders/PlanTaskSeeder.php | 12 +- .../initdata/video/url_family_pool_gpt.php | 734 +++++++----------- .../video/url_family_pool_gpt_back.php | 476 ++++++++++++ 10 files changed, 1075 insertions(+), 466 deletions(-) create mode 100644 code/app/task/logic/BaiduPushVideoUrlLogic.php create mode 100644 code/database/migrations/20260119132713_add_baidu_token_to_video_table.php create mode 100644 code/public/initdata/video/url_family_pool_gpt_back.php diff --git a/code/app/admin/controller/Site.php b/code/app/admin/controller/Site.php index 0467ee3..3f00ab6 100644 --- a/code/app/admin/controller/Site.php +++ b/code/app/admin/controller/Site.php @@ -77,6 +77,7 @@ class Site extends BaseController 'd_img_logo' => $arrRows[11], 'd_content_encode' => $arrRows[12], 'info_id' => $arrRows[13], + //'d_baidu_token' => $arrRows[14], ]; $TCfgTemplate = TemplatesModel::where('t_id', $arrDomain['t_id'])->find()->t_cfg_template; @@ -180,6 +181,7 @@ class Site extends BaseController "d_content_encode" => $Request->post('d_content_encode'), "t_cfg" => $Request->post('t_cfg'), "info_id" => $Request->post('info_id'), + "d_baidu_token" => $Request->post('d_baidu_token'), ]; $arrRule = [ diff --git a/code/app/model/VideoCategoryModel.php b/code/app/model/VideoCategoryModel.php index 0f9f60c..5b502e7 100644 --- a/code/app/model/VideoCategoryModel.php +++ b/code/app/model/VideoCategoryModel.php @@ -128,7 +128,7 @@ class VideoCategoryModel extends MongoModel "children" => [] ], [ - 'pinyin' => 'han-ju', + 'v_category_en' => 'han-ju', 'name' => '韩剧', "children" => [] ], diff --git a/code/app/task/crawler/CrawlerConfig.php b/code/app/task/crawler/CrawlerConfig.php index 3849640..b634fd6 100644 --- a/code/app/task/crawler/CrawlerConfig.php +++ b/code/app/task/crawler/CrawlerConfig.php @@ -21,6 +21,8 @@ class CrawlerConfig '斯诺克', '足球', '篮球', + '网球', + 'LPL', ]; } diff --git a/code/app/task/logic/BaiduPushVideoUrlLogic.php b/code/app/task/logic/BaiduPushVideoUrlLogic.php new file mode 100644 index 0000000..1908345 --- /dev/null +++ b/code/app/task/logic/BaiduPushVideoUrlLogic.php @@ -0,0 +1,174 @@ +strSiteMapPath = root_path('storage/SiteMap'); + } + + /** + * 获取域名 + */ + public function getDomain(): array + { + if (empty($this->arrDomainModel)) { + $this->arrDomainModel = DomainModel::getAllDomainOnCache(); + } + return $this->arrDomainModel; + } + + /** + * 推送百度 URL(任务入口) + */ + public function pushBaiduUrl(): void + { + foreach ($this->getDomain() as $DomainModel) { + + $domain = $DomainModel->d_domain; + $strBaiduToken = $DomainModel->d_baidu_token ?? ""; + $domainDir = $this->strSiteMapPath . DIRECTORY_SEPARATOR . $domain; + + if (!is_dir($domainDir)) { + continue; + } + + $this->pushDomainUrls($domain, $strBaiduToken, $domainDir); + } + } + + /** + * 推送单个域名 + */ + protected function pushDomainUrls(string $domain, string $strBaiduToken, string $domainDir): void + { + $stateFile = $domainDir . '/baidu_state.json'; + + if(empty($strBaiduToken)){ + return ; + } + + $strApi = 'http://data.zz.baidu.com/urls?site=https://' . $domain . '&token=' . $strBaiduToken; + // $strApi = 'http://data.zz.baidu.com/urls?site=https://www.aaggc.com&token=B4bzeDDBHhnoBnov'; + + // 1️⃣ 读取 txt 文件 + $txtFiles = glob($domainDir . '/*.txt'); + sort($txtFiles); + + if (empty($txtFiles)) { + return; + } + + // 2️⃣ 读取进度 + if (file_exists($stateFile)) { + $state = json_decode(file_get_contents($stateFile), true); + } else { + $state = [ + 'file_index' => 0, + 'line_index' => 0, + ]; + } + + $fileIndex = (int)$state['file_index']; + $lineIndex = (int)$state['line_index']; + + $arrPushUrls = []; + $newFileIndex = $fileIndex; + $newLineIndex = $lineIndex; + + // 3️⃣ 按顺序取 URL + for ($i = $fileIndex; $i < count($txtFiles); $i++) { + + $lines = file($txtFiles[$i], FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + $start = ($i === $fileIndex) ? $lineIndex : 0; + + for ($j = $start; $j < count($lines); $j++) { + + $url = trim($lines[$j]); + if ($url === '') { + continue; + } + + $arrPushUrls[] = $url; + $newFileIndex = $i; + $newLineIndex = $j + 1; + + if (count($arrPushUrls) >= $this->limit) { + break 2; + } + } + + // 当前文件读完 + $newFileIndex = $i + 1; + $newLineIndex = 0; + } + + // 4️⃣ 没有可推送 URL + if (empty($arrPushUrls)) { + return; + } + + // 5️⃣ curl 推送 + $ch = curl_init(); + curl_setopt_array($ch, [ + CURLOPT_URL => $strApi, + CURLOPT_POST => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POSTFIELDS => implode("\n", $arrPushUrls), + CURLOPT_HTTPHEADER => [ + 'Content-Type: text/plain; charset=utf-8' + ], + CURLOPT_TIMEOUT => 10, + CURLOPT_CONNECTTIMEOUT => 5, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, + ]); + + $result = curl_exec($ch); + + if ($result === false) { + curl_close($ch); + return; + } + + curl_close($ch); + + // 6️⃣ 解析结果 + $data = json_decode($result, true); + + // 7️⃣ 成功才更新进度 + if (isset($data['success']) && $data['success'] > 0) { + file_put_contents( + $stateFile, + json_encode([ + 'file_index' => $newFileIndex, + 'line_index' => $newLineIndex, + 'updated_at' => date('Y-m-d H:i:s'), + ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) + ); + } + + } +} diff --git a/code/app/task/logic/VideoSiteMapLogic.php b/code/app/task/logic/VideoSiteMapLogic.php index d2f1ea3..a8b6378 100644 --- a/code/app/task/logic/VideoSiteMapLogic.php +++ b/code/app/task/logic/VideoSiteMapLogic.php @@ -132,24 +132,6 @@ class VideoSiteMapLogic * * @return void */ - // public function generateSiteMap() - // { - // $arrResult = $this->VideoModel->findPaginatedWithCache([], 1, 1); - // $this->intTotal = $arrResult['total']; - - // $this->generateMapIndex(); - - // $this->generateMapMain(); - - // $intTotalPage = ceil($this->intTotal / $this->intPageSize); - // for ($intPage = 1; $intPage <= $intTotalPage; $intPage++) { - // $this->generateMapInfo($intPage); - // } - - // echo date('Y-m-d H:i:s') . PHP_EOL; - // } - - protected function generateMapInfo(int $intPage) { $intStartPage = $intPage; @@ -208,6 +190,7 @@ EOF; $arrArgs = [ 'intVId' => $Video->v_id, 'strPinYin' => $Video->v_name_en, + 'strPinyin' => $Video->v_name_en, ]; if ($arrUrlFamily && !empty($arrUrlFamily)) { @@ -283,6 +266,10 @@ EOF; foreach ($this->getDomain() as $DomainModel) { + // 用于 sitemap + txt 的 URL 去重池 +$arrUrlPool = []; + + $strDomainDir = $this->strSiteMapPath . $DomainModel->d_domain; if (is_dir($strDomainDir) == false) { mkdir($strDomainDir); @@ -316,7 +303,15 @@ EOF; 0.8 EOF; - file_put_contents($strMapIndexFile, $strContent); + + + //file_put_contents($strMapIndexFile, $strContent); + $arrUrlPool["https://www.{$DomainModel->d_domain}/"] = [ + 'lastmod' => $this->strDate, + 'changefreq' => 'daily', + 'priority' => '1.0', + ]; + // 分类首页 '/vodtype/{strParentCategory}', foreach ($this->arrVideoCategory as $strParentCategoryKey => $arrParentCategory) { @@ -347,11 +342,15 @@ EOF; weekly 0.8 -EOF; - file_put_contents($strMapIndexFile, $strContent, FILE_APPEND); +EOF; + //file_put_contents($strMapIndexFile, $strContent, FILE_APPEND); + $arrUrlPool[$strUrl] = [ + 'lastmod' => $this->strDate, + 'changefreq' => 'weekly', + 'priority' => '0.8', + ]; } - // 分类列表 '/vodtype-{strParentCategory}/{strCategory}-{strYear}-{strArea}-{strOrder}/{strLang}-page{intPage}', foreach ($this->arrVideoCategory as $arrParentCategory) { foreach ($arrParentCategory['children'] as $arrChildrenCategory) { @@ -391,7 +390,12 @@ EOF; 0.8 EOF; - file_put_contents($strMapIndexFile, $strContent, FILE_APPEND); + //file_put_contents($strMapIndexFile, $strContent, FILE_APPEND); + $arrUrlPool[$strUrl] = [ + 'lastmod' => $this->strDate, + 'changefreq' => 'weekly', + 'priority' => '0.8', + ]; // } // } // } @@ -432,7 +436,12 @@ EOF; 0.8 EOF; - file_put_contents($strMapIndexFile, $strContent, FILE_APPEND); + //file_put_contents($strMapIndexFile, $strContent, FILE_APPEND); + $arrUrlPool[$strUrl] = [ + 'lastmod' => $this->strDate, + 'changefreq' => 'weekly', + 'priority' => '0.8', + ]; } } } @@ -442,7 +451,38 @@ EOF; $strContent = << EOF; - file_put_contents($strMapIndexFile, $strContent, FILE_APPEND); + //file_put_contents($strMapIndexFile, $strContent, FILE_APPEND); + + + $strXml = << + +XML; + +foreach ($arrUrlPool as $url => $meta) { + $strXml .= << + {$url} + {$meta['lastmod']} + {$meta['changefreq']} + {$meta['priority']} + +XML; +} + +$strXml .= "\n"; + +file_put_contents($strMapIndexFile, $strXml); + + +$strTxtFile = $strDomainDir . '/sitemap-main.txt'; + +$strTxt = implode("\n", array_keys($arrUrlPool)); + +file_put_contents($strTxtFile, $strTxt); + + } } diff --git a/code/app/task/module/PlanTask.php b/code/app/task/module/PlanTask.php index 9473999..86aea0e 100644 --- a/code/app/task/module/PlanTask.php +++ b/code/app/task/module/PlanTask.php @@ -7,6 +7,7 @@ namespace app\task\module; use app\model\PlanTaskModel; use app\task\logic\SiteMapLogic; use app\task\logic\VideoSiteMapLogic; +use app\task\logic\BaiduPushVideoUrlLogic; use microserver\QueueManage; use microserver\ProcessSanitizer; @@ -90,6 +91,11 @@ class PlanTask case 'GENERATE_VIDEO_SEO_WORDS': self::generateVideoSEOWords(); break; + case 'PUSH_BAIDU_VIDEO_URL': + self::pushBaiduVideoUrl(); + break; + + } } } @@ -303,6 +309,22 @@ class PlanTask $QueueManage->set($arrTask); } + /** + * Undocumented function + * + * @return void + */ + public static function pushBaiduVideoUrl() + { + $arrTask = [ + 'callback' => [BaiduPushVideoUrlLogic::class, 'pushBaiduUrl'], + 'data' => [] + ]; + + $QueueManage = new QueueManage(2); + $QueueManage->set($arrTask); + } + /** * Undocumented function * diff --git a/code/database/migrations/20260119132713_add_baidu_token_to_video_table.php b/code/database/migrations/20260119132713_add_baidu_token_to_video_table.php new file mode 100644 index 0000000..266a854 --- /dev/null +++ b/code/database/migrations/20260119132713_add_baidu_token_to_video_table.php @@ -0,0 +1,27 @@ +table('domain'); + $table + ->addColumn( + Column::string('d_baidu_token', 255) + ->setNullable(true) + ->setComment('百度推送token') + ) + ->update(); + } + + public function down() + { + $table = $this->table('domain'); + $table + ->removeColumn('d_baidu_token') + ->update(); + } +} diff --git a/code/database/seeders/PlanTaskSeeder.php b/code/database/seeders/PlanTaskSeeder.php index cdc9ab7..0f5a6a0 100644 --- a/code/database/seeders/PlanTaskSeeder.php +++ b/code/database/seeders/PlanTaskSeeder.php @@ -110,12 +110,22 @@ class PlanTaskSeeder 'pt_code' => 'PULL_DOU_BAN_SHI_PIN', 'pt_enable' => 0, 'pt_limit' => 1 * 24 * 3600, - ], [ + ], + [ 'pt_name' => '茅台资源库全量采集', 'pt_code' => 'PULL_MAO_TAI_SHI_PIN', 'pt_enable' => 0, 'pt_limit' => 1 * 24 * 3600, ], + [ + 'pt_name' => '推送视频url到百度收录', + 'pt_code' => 'PUSH_BAIDU_VIDEO_URL', + 'pt_enable' => 0, + 'pt_limit' => 1 * 24 * 3600, + ], + + + ]; diff --git a/code/public/initdata/video/url_family_pool_gpt.php b/code/public/initdata/video/url_family_pool_gpt.php index 8650cf4..c01fda8 100644 --- a/code/public/initdata/video/url_family_pool_gpt.php +++ b/code/public/initdata/video/url_family_pool_gpt.php @@ -1,476 +1,332 @@ [ - 'sort' => 1, - 'prefix' => [ - 'video', // 主 - 'play', // 主 - 'vodplay', // 历史 - 'watch', // 英文 - 'kan', // 拼音(看) - 'player', // 英文 - 'bofang', // 英文 - 'm3u8', // 英文 - 'bf', // 英文 - ], - 'type' => 'play' +// ===================== +// 1️⃣ 页面定义 +// ===================== +$pages = [ + 'play' => [ + 'sort' => 1, + 'prefix' => [ + 'video','play','vodplay','watch','kan','player','bofang','m3u8','bf', ], - 'detail_forge' => [ - 'sort' => 2, - 'prefix' => [ - 'video', // 主 - 'detail', // 主 - 'info', // 主 - 'vod', // 行业通用 - 'movie', // 英文 - 'film', // 英文 - 'xiangqing', - 'shipin', - 'neirong', - 'dianying', - ], - 'type' => 'detail_forge' + 'type' => 'play', + ], + 'detail_forge' => [ + 'sort' => 2, + 'prefix' => [ + 'video','detail','info','vod','movie','film', + 'xiangqing','shipin','neirong','dianying', ], - 'detail' => [ - 'sort' => 3, - 'prefix' => [ - 'video', // 主 - 'detail', // 主 - 'info', // 主 - 'vod', // 行业通用 - 'movie', // 英文 - 'film', // 英文 - 'xiangqing', - 'shipin', - 'neirong', - 'dianying', - ], - 'type' => 'detail' + 'type' => 'detail_forge', + ], + 'detail' => [ + 'sort' => 3, + 'prefix' => [ + 'video','detail','info','vod','movie','film', + 'xiangqing','shipin','neirong','dianying', ], - 'category_child' => [ - 'sort' => 4, - 'prefix' => [ - 'fenlei', // 主 - 'category', // 英文 - 'class', // 主 - 'type', // 主 - 'genres', // 英文(类型) - 'leixing', // 拼音 - 'videotype', // 拼音 - ], - 'type' => 'category_child' + 'type' => 'detail', + ], + 'category_child' => [ + 'sort' => 4, + 'prefix' => [ + 'fenlei','category','class','type','genres','leixing','videotype', ], - 'category_parent' => [ - 'sort' => 5, - 'prefix' => [ - 'fenlei', // 主 - 'category', // 英文 - 'class', // 主 - 'type', // 主 - 'genres', // 英文(类型) - 'leixing', // 拼音 - 'videotype', // 拼音 - ], - 'type' => 'category_parent' + 'type' => 'category_child', + ], + 'category_parent' => [ + 'sort' => 5, + 'prefix' => [ + 'fenlei','category','class','type','genres','leixing','videotype', ], - 'rank_list' => [ - 'sort' => 6, - 'prefix' => [ - 'top', // 主 - 'rank', // 主 - 'paihangban', // 拼音 - 'ranking', // 英文 - 'hot', // 热门(谨慎) - 'phb', - ], - 'type' => 'rank_list' + 'type' => 'category_parent', + ], + 'rank_list' => [ + 'sort' => 6, + 'prefix' => [ + 'top','rank','paihangban','ranking','hot','phb', ], - 'category_home' => [ - 'sort' => 7, - 'prefix' => [ - 'fenlei', // 主 - 'category', // 英文 - 'class', // 主 - 'type', // 主 - 'genres', // 英文(类型) - 'leixing', // 拼音 - 'videotype', // 拼音 - ], - 'type' => 'category_home' + 'type' => 'rank_list', + ], + 'category_home' => [ + 'sort' => 7, + 'prefix' => [ + 'fenlei','category','class','type','genres','leixing','videotype', ], - 'rank_index' => [ - 'sort' => 8, - 'prefix' => [ - 'top', // 主 - 'rank', // 主 - 'paihangban', // 拼音 - 'ranking', // 英文 - 'hot', // 热门(谨慎) - 'phb', - ], - 'type' => 'rank_index' + 'type' => 'category_home', + ], + 'rank_index' => [ + 'sort' => 8, + 'prefix' => [ + 'top','rank','paihangban','ranking','hot','phb', ], - 'search' => [ - 'sort' => 9, - 'prefix' => [ - 'search', // 主 - 'query', // 主 - 'sou', // 拼音 - 'find', // 英文 - 'get' - ], - 'type' => 'search' + 'type' => 'rank_index', + ], + 'search' => [ + 'sort' => 9, + 'prefix' => [ + 'search','query','sou','find','get', ], - 'history' => [ - 'sort' => 10, - 'prefix' => [ - 'history', // 主 - 'his', // 简写 - 'jilu', // 拼音 - 'record', // 英文 - ], - 'type' => 'history' + 'type' => 'search', + ], + 'history' => [ + 'sort' => 10, + 'prefix' => [ + 'history','his','jilu','record', ], - ]; + 'type' => 'history', + ], +]; - /** 每个 type 的 8 套模板(你已测试可用) */ - $templates = [ - - 'category_home' => [ - 'pattern' => [ - '{prefix}', - '{prefix}/index', - '{prefix}/home', - '{prefix}/p', - '{prefix}/1', - '{prefix}-1', - '{prefix}-index', - '{prefix}-home', - ], - 'routes' => [ - '/{prefix}', - '/{prefix}/index', - '/{prefix}/home', - '/{prefix}/p', - '/{prefix}/1', - '/{prefix}/-1', - '/{prefix}-index', - '/{prefix}-home', - ], +// ===================== +// 2️⃣ 模板定义(每类 8 套) +// ===================== +$templates = [ + 'category_home' => [ + 'pattern' => [ + '{prefix}','{prefix}/index','{prefix}/home','{prefix}/p', + '{prefix}/1','{prefix}-1','{prefix}-index','{prefix}-home', ], - - 'category_parent' => [ - 'pattern' => [ - '{prefix}/{strParentCategory}', - '{prefix}/{strParentCategory}/index', - '{prefix}/{strParentCategory}/home', - '{prefix}/p/{strParentCategory}', - '{prefix}/1/{strParentCategory}', - '{prefix}/1-{strParentCategory}', - '{prefix}/{strParentCategory}/1', - '{prefix}/{strParentCategory}-1', - ], - 'routes' => [ - '/{prefix}/:strParentCategory', - '/{prefix}/:strParentCategory/index', - '/{prefix}/:strParentCategory/home', - '/{prefix}/p/:strParentCategory', - '/{prefix}/1/:strParentCategory', - '/{prefix}/1-:strParentCategory', - '/{prefix}/:strParentCategory/1', - '/{prefix}/:strParentCategory-1', - ], + 'routes' => [ + '/{prefix}','/{prefix}/index','/{prefix}/home','/{prefix}/p', + '/{prefix}/1','/{prefix}-1','/{prefix}-index','/{prefix}-home', ], + ], - 'category_child' => [ - 'pattern' => [ - '{prefix}/{strParentCategory}/{strCategory}/page{intPage}', - '{prefix}/{strParentCategory}/{strCategory}-page{intPage}', - '{prefix}-{strParentCategory}/{strCategory}/page{intPage}', - '{prefix}-{strParentCategory}/{strCategory}-page{intPage}', - '{prefix}/{strParentCategory}/{strCategory}/{intPage}', - '{prefix}/{strParentCategory}/{strCategory}-{intPage}', - '{prefix}-{strParentCategory}/{strCategory}/{intPage}', - '{prefix}-{strParentCategory}/{strCategory}-{intPage}', - ], - 'routes' => [ - '/{prefix}/:strParentCategory/:strCategory/page:intPage', - '/{prefix}/:strParentCategory/:strCategory-page:intPage', - '/{prefix}-:strParentCategory/:strCategory/page:intPage', - '/{prefix}-:strParentCategory/:strCategory-page:intPage', - '/{prefix}/:strParentCategory/:strCategory/:intPage', - '/{prefix}/:strParentCategory/:strCategory-:intPage', - '/{prefix}-:strParentCategory/:strCategory/:intPage', - '/{prefix}-:strParentCategory/:strCategory-:intPage', - ], + 'category_parent' => [ + 'pattern' => [ + '{prefix}/{strParentCategory}','{prefix}/{strParentCategory}/index', + '{prefix}/{strParentCategory}/home','{prefix}/p/{strParentCategory}', + '{prefix}/1/{strParentCategory}','{prefix}/1-{strParentCategory}', + '{prefix}/{strParentCategory}/1','{prefix}/{strParentCategory}-1', ], - - 'rank_index' => [ - 'pattern' => [ - '{prefix}', - '{prefix}/index', - '{prefix}/all', - '{prefix}/home', - '{prefix}/1', - '{prefix}-index', - '{prefix}-all', - '{prefix}-home', - ], - 'routes' => [ - '/{prefix}', - '/{prefix}/index', - '/{prefix}/all', - '/{prefix}/home', - '/{prefix}/1', - '/{prefix}-index', - '/{prefix}-all', - '/{prefix}-home', - ], + 'routes' => [ + '/{prefix}/:strParentCategory','/{prefix}/:strParentCategory/index', + '/{prefix}/:strParentCategory/home','/{prefix}/p/:strParentCategory', + '/{prefix}/1/:strParentCategory','/{prefix}/1-:strParentCategory', + '/{prefix}/:strParentCategory/1','/{prefix}/:strParentCategory-1', ], + ], - 'rank_list' => [ - 'pattern' => [ - '{prefix}/{strSortType}', - '{prefix}/{strSortType}/index', - '{prefix}/{strSortType}/home', - '{prefix}/{strSortType}/1', - '{prefix}/{strSortType}-1', - '{prefix}-{strSortType}', - '{prefix}-{strSortType}/index', - '{prefix}-{strSortType}/1', - ], - 'routes' => [ - '/{prefix}/:strSortType', - '/{prefix}/:strSortType/index', - '/{prefix}/:strSortType/home', - '/{prefix}/:strSortType/1', - '/{prefix}/:strSortType-1', - '/{prefix}-:strSortType', - '/{prefix}-:strSortType/index', - '/{prefix}-:strSortType/1', - ], + 'category_child' => [ + 'pattern' => [ + '{prefix}/{strParentCategory}/{strCategory}/page{intPage}', + '{prefix}/{strParentCategory}/{strCategory}-page{intPage}', + '{prefix}-{strParentCategory}/{strCategory}/page{intPage}', + '{prefix}-{strParentCategory}/{strCategory}-page{intPage}', + '{prefix}/{strParentCategory}/{strCategory}/{intPage}', + '{prefix}/{strParentCategory}/{strCategory}-{intPage}', + '{prefix}-{strParentCategory}/{strCategory}/{intPage}', + '{prefix}-{strParentCategory}/{strCategory}-{intPage}', ], - - 'detail' => [ - 'pattern' => [ - '{prefix}/{strPinyin}-{intVId}', - '{prefix}/{intVId}-{strPinyin}', - '{prefix}/{strPinyin}/{intVId}', - '{prefix}/{intVId}', - '{prefix}-{strPinyin}-{intVId}', - '{prefix}-{intVId}-{strPinyin}', - '{prefix}/id-{intVId}', - '{prefix}/pinyin-{strPinyin}', - ], - 'routes' => [ - '/{prefix}/:strPinyin-:intVId', - '/{prefix}/:intVId-:strPinyin', - '/{prefix}/:strPinyin/:intVId', - '/{prefix}/:intVId', - '/{prefix}-:strPinyin-:intVId', - '/{prefix}-:intVId-:strPinyin', - '/{prefix}/id-:intVId', - '/{prefix}/pinyin-:strPinyin', - ], + 'routes' => [ + '/{prefix}/:strParentCategory/:strCategory/page:intPage', + '/{prefix}/:strParentCategory/:strCategory-page:intPage', + '/{prefix}-:strParentCategory/:strCategory/page:intPage', + '/{prefix}-:strParentCategory/:strCategory-page:intPage', + '/{prefix}/:strParentCategory/:strCategory/:intPage', + '/{prefix}/:strParentCategory/:strCategory-:intPage', + '/{prefix}-:strParentCategory/:strCategory/:intPage', + '/{prefix}-:strParentCategory/:strCategory-:intPage', ], + ], - 'detail_forge' => [ - 'pattern' => [ - '{prefix}/{strPinyin}-{intVId}-{intVForgeId}', - '{prefix}/{intVId}-{strPinyin}/{intVForgeId}', - '{prefix}/{strPinyin}/{intVId}-{intVForgeId}', - '{prefix}/{intVId}/{intVForgeId}', - '{prefix}-{strPinyin}-{intVId}/{intVForgeId}', - '{prefix}-{intVId}-{strPinyin}/{intVForgeId}', - '{prefix}/id-{intVId}-{intVForgeId}', - '{prefix}/pinyin-{strPinyin}/{intVForgeId}', - ], - 'routes' => [ - '/{prefix}/:strPinyin-:intVId-:intVForgeId', - '/{prefix}/:intVId-:strPinyin/:intVForgeId', - '/{prefix}/:strPinyin/:intVId-:intVForgeId', - '/{prefix}/:intVId/:intVForgeId', - '/{prefix}-:strPinyin-:intVId/:intVForgeId', - '/{prefix}-:intVId-:strPinyin/:intVForgeId', - '/{prefix}/id-:intVId-:intVForgeId', - '/{prefix}/pinyin-:strPinyin/:intVForgeId', - ], + 'rank_index' => [ + 'pattern' => [ + '{prefix}','{prefix}/index','{prefix}/all','{prefix}/home', + '{prefix}/1','{prefix}-index','{prefix}-all','{prefix}-home', ], - - 'play' => [ - 'pattern' => [ - '{prefix}/{strPinyin}-{intVId}-{strPlayType}-{intPlayIndex}', - '{prefix}/{strPinyin}-{intVId}/{strPlayType}-{intPlayIndex}', - '{prefix}/{intVId}-{strPlayType}-{intPlayIndex}', - '{prefix}/{intVId}/{strPlayType}-{intPlayIndex}', - '{prefix}-{strPinyin}-{intVId}-{strPlayType}-{intPlayIndex}', - '{prefix}-{intVId}-{strPinyin}/{strPlayType}-{intPlayIndex}', - '{prefix}-{intVId}-{strPlayType}-{intPlayIndex}', - '{prefix}-{intVId}/{strPlayType}-{intPlayIndex}', - ], - 'routes' => [ - '/{prefix}/:strPinyin-:intVId-:strPlayType-:intPlayIndex', - '/{prefix}/:strPinyin-:intVId/:strPlayType-:intPlayIndex', - '/{prefix}/:intVId-:strPlayType-:intPlayIndex', - '/{prefix}/:intVId/:strPlayType-:intPlayIndex', - '/{prefix}-:strPinyin-:intVId-:strPlayType-:intPlayIndex', - '/{prefix}-:intVId-:strPinyin/:strPlayType-:intPlayIndex', - '/{prefix}-:intVId-:strPlayType-:intPlayIndex', - '/{prefix}-:intVId/:strPlayType-:intPlayIndex', - ], + 'routes' => [ + '/{prefix}','/{prefix}/index','/{prefix}/all','/{prefix}/home', + '/{prefix}/1','/{prefix}-index','/{prefix}-all','/{prefix}-home', ], + ], - 'search' => [ - 'pattern' => [ - '{prefix}', - '{prefix}.html', - '{prefix}/index', - '{prefix}/home', - '{prefix}/keywords', - '{prefix}-index', - '{prefix}-home', - '{prefix}-keywords', - ], - 'routes' => [ - '/{prefix}', - '/{prefix}.html', - '/{prefix}/index', - '/{prefix}/home', - '/{prefix}/keywords', - '/{prefix}-index', - '/{prefix}-home', - '/{prefix}-keywords', - ], + 'rank_list' => [ + 'pattern' => [ + '{prefix}/{strSortType}','{prefix}/{strSortType}/index', + '{prefix}/{strSortType}/home','{prefix}/{strSortType}/1', + '{prefix}/{strSortType}-1','{prefix}-{strSortType}', + '{prefix}-{strSortType}/index','{prefix}-{strSortType}/1', ], - - 'history' => [ - 'pattern' => [ - '{prefix}', - '{prefix}.html', - '{prefix}/index', - '{prefix}/home', - '{prefix}/keywords', - '{prefix}-index', - '{prefix}-home', - '{prefix}-keywords', - ], - 'routes' => [ - '/{prefix}', - '/{prefix}.html', - '/{prefix}/index', - '/{prefix}/home', - '/{prefix}/keywords', - '/{prefix}-index', - '/{prefix}-home', - '/{prefix}-keywords', - ], + 'routes' => [ + '/{prefix}/:strSortType','/{prefix}/:strSortType/index', + '/{prefix}/:strSortType/home','/{prefix}/:strSortType/1', + '/{prefix}/:strSortType-1','/{prefix}-:strSortType', + '/{prefix}-:strSortType/index','/{prefix}-:strSortType/1', ], - ]; + ], + 'detail' => [ + 'pattern' => [ + '{prefix}/{strPinyin}-{intVId}','{prefix}/{intVId}-{strPinyin}', + '{prefix}/{strPinyin}/{intVId}','{prefix}/{intVId}', + '{prefix}-{strPinyin}-{intVId}','{prefix}-{intVId}-{strPinyin}', + '{prefix}/id-{intVId}','{prefix}/pinyin-{strPinyin}', + ], + 'routes' => [ + '/{prefix}/:strPinyin-:intVId','/{prefix}/:intVId-:strPinyin', + '/{prefix}/:strPinyin/:intVId','/{prefix}/:intVId', + '/{prefix}-:strPinyin-:intVId','/{prefix}-:intVId-:strPinyin', + '/{prefix}/id-:intVId','/{prefix}/pinyin-:strPinyin', + ], + ], - // 🔴【新增 1】加载 index(落盘 ID) - $indexFile = __DIR__ . '/url_family_index.php'; - $index = file_exists($indexFile) ? require $indexFile : []; + 'detail_forge' => [ + 'pattern' => [ + '{prefix}/{strPinyin}-{intVId}-{intVForgeId}', + '{prefix}/{intVId}-{strPinyin}/{intVForgeId}', + '{prefix}/{strPinyin}/{intVId}-{intVForgeId}', + '{prefix}/{intVId}/{intVForgeId}', + '{prefix}-{strPinyin}-{intVId}/{intVForgeId}', + '{prefix}-{intVId}-{strPinyin}/{intVForgeId}', + '{prefix}/id-{intVId}-{intVForgeId}', + '{prefix}/pinyin-{strPinyin}/{intVForgeId}', + ], + 'routes' => [ + '/{prefix}/:strPinyin-:intVId-:intVForgeId', + '/{prefix}/:intVId-:strPinyin/:intVForgeId', + '/{prefix}/:strPinyin/:intVId-:intVForgeId', + '/{prefix}/:intVId/:intVForgeId', + '/{prefix}-:strPinyin-:intVId/:intVForgeId', + '/{prefix}-:intVId-:strPinyin/:intVForgeId', + '/{prefix}/id-:intVId-:intVForgeId', + '/{prefix}/pinyin-:strPinyin/:intVForgeId', + ], + ], - // 🔴【新增 2】计算当前最大 A 编号 - $maxId = 0; - foreach ($index as $aid) { - $num = (int)substr($aid, 1); - if ($num > $maxId) { - $maxId = $num; - } + 'play' => [ + 'pattern' => [ + '{prefix}/{strPinyin}-{intVId}-{strPlayType}-{intPlayIndex}', + '{prefix}/{strPinyin}-{intVId}/{strPlayType}-{intPlayIndex}', + '{prefix}/{intVId}-{strPlayType}-{intPlayIndex}', + '{prefix}/{intVId}/{strPlayType}-{intPlayIndex}', + '{prefix}-{strPinyin}-{intVId}-{strPlayType}-{intPlayIndex}', + '{prefix}-{intVId}-{strPinyin}/{strPlayType}-{intPlayIndex}', + '{prefix}-{intVId}-{strPlayType}-{intPlayIndex}', + '{prefix}-{intVId}/{strPlayType}-{intPlayIndex}', + ], + 'routes' => [ + '/{prefix}/:strPinyin-:intVId-:strPlayType-:intPlayIndex', + '/{prefix}/:strPinyin-:intVId/:strPlayType-:intPlayIndex', + '/{prefix}/:intVId-:strPlayType-:intPlayIndex', + '/{prefix}/:intVId/:strPlayType-:intPlayIndex', + '/{prefix}-:strPinyin-:intVId-:strPlayType-:intPlayIndex', + '/{prefix}-:intVId-:strPinyin/:strPlayType-:intPlayIndex', + '/{prefix}-:intVId-:strPlayType-:intPlayIndex', + '/{prefix}-:intVId/:strPlayType-:intPlayIndex', + ], + ], + + 'search' => [ + 'pattern' => [ + '{prefix}','{prefix}.html','{prefix}/index','{prefix}/home', + '{prefix}/keywords','{prefix}-index','{prefix}-home','{prefix}-keywords', + ], + 'routes' => [ + '/{prefix}','/{prefix}.html','/{prefix}/index','/{prefix}/home', + '/{prefix}/keywords','/{prefix}-index','/{prefix}-home','/{prefix}-keywords', + ], + ], + + 'history' => [ + 'pattern' => [ + '{prefix}','{prefix}.html','{prefix}/index','{prefix}/home', + '{prefix}/keywords','{prefix}-index','{prefix}-home','{prefix}-keywords', + ], + 'routes' => [ + '/{prefix}','/{prefix}.html','/{prefix}/index','/{prefix}/home', + '/{prefix}/keywords','/{prefix}-index','/{prefix}-home','/{prefix}-keywords', + ], + ], +]; + +// ===================== +// 3️⃣ ID 索引(落盘) +// ===================== +$indexFile = __DIR__ . '/url_family_index.php'; +$index = file_exists($indexFile) ? require $indexFile : []; + +// ===================== +// 4️⃣ 计算当前最大 A 编号 +// ===================== +$maxId = 0; +foreach ($index as $aid) { + $num = (int)substr($aid, 1); + if ($num > $maxId) { + $maxId = $num; } - - // 页面按 sort 排序(保证路由注册顺序) - uasort($pages, fn($a, $b) => $a['sort'] <=> $b['sort']); - - // prefix 套数 - $prefixSuiteCount = max(array_map(fn($p) => count($p['prefix']), $pages)); - - $families = []; - - // ❌【删除】$seq = 1; - - for ($pi = 0; $pi < $prefixSuiteCount; $pi++) { - for ($ti = 0; $ti < 8; $ti++) { - - /** - * 🔴【新增 3】构造 family 唯一指纹 key - * 只要 page / prefix / 模板索引一样,URL 身份就一样 - */ - $familyKeyParts = []; - - foreach ($pages as $pageKey => $page) { - $prefix = $page['prefix'][$pi] ?? end($page['prefix']); - $familyKeyParts[] = $pageKey . ':' . $prefix; - } - - $familyKey = implode('|', $familyKeyParts) . '|tpl' . $ti; - - // 🔴【新增 4】从 index 取 A 编号(没有就新增) - if (!isset($index[$familyKey])) { - $maxId++; - $index[$familyKey] = 'A' . str_pad((string)$maxId, 3, '0', STR_PAD_LEFT); - } - - $aid = $index[$familyKey]; - - // 🔴【修改】family 的 id 不再用 $seq - $family = [ - 'id' => $aid, - 'home' => [ - 'pattern' => '', - 'routes' => [ - '/' - ] - ], - ]; - - foreach ($pages as $pageKey => $page) { - $type = $page['type']; - if (!isset($templates[$type])) { - continue; - } - - $prefix = $page['prefix'][$pi] ?? end($page['prefix']); - - $pattern = str_replace('{prefix}', $prefix, $templates[$type]['pattern'][$ti]); - $route = str_replace('{prefix}', $prefix, $templates[$type]['routes'][$ti]); - - $family[$pageKey] = [ - 'sort' => $page['sort'], - 'pattern' => $pattern, - 'routes' => [$route], - ]; - } - - // 🔴【修改】用 A 编号做 key,避免被重排 - $families[$aid] = $family; - } - } - - // 🔴【新增 5】index 落盘(只会 append) - file_put_contents( - $indexFile, - " $a['sort'] <=> $b['sort']); +$prefixSuiteCount = max(array_map(fn($p) => count($p['prefix']), $pages)); + +$families = []; + +for ($pi = 0; $pi < $prefixSuiteCount; $pi++) { + for ($ti = 0; $ti < 8; $ti++) { + + $familyKeyParts = []; + foreach ($pages as $pageKey => $page) { + $prefix = $page['prefix'][$pi] ?? end($page['prefix']); + $familyKeyParts[] = $pageKey . ':' . $prefix; + } + $familyKey = implode('|', $familyKeyParts) . '|tpl' . $ti; + + if (!isset($index[$familyKey])) { + $maxId++; + $index[$familyKey] = 'A' . str_pad((string)$maxId, 3, '0', STR_PAD_LEFT); + } + + $aid = $index[$familyKey]; + + $family = [ + 'id' => $aid, + 'home' => [ + 'pattern' => '', + 'routes' => ['/'], + ], + ]; + + foreach ($pages as $pageKey => $page) { + $type = $page['type']; + if (!isset($templates[$type])) { + continue; + } + + $prefix = $page['prefix'][$pi] ?? end($page['prefix']); + $pattern = str_replace('{prefix}', $prefix, $templates[$type]['pattern'][$ti]); + $route = str_replace('{prefix}', $prefix, $templates[$type]['routes'][$ti]); + + $family[$pageKey] = [ + 'sort' => $page['sort'], + 'pattern' => $pattern, + 'routes' => [$route], + ]; + } + + $families[$aid] = $family; + } +} + +// ===================== +// 6️⃣ index 落盘 + 排序返回 +// ===================== +file_put_contents( + $indexFile, + " [ + 'sort' => 1, + 'prefix' => [ + 'video', // 主 + 'play', // 主 + 'vodplay', // 历史 + 'watch', // 英文 + 'kan', // 拼音(看) + 'player', // 英文 + 'bofang', // 英文 + 'm3u8', // 英文 + 'bf', // 英文 + ], + 'type' => 'play' + ], + 'detail_forge' => [ + 'sort' => 2, + 'prefix' => [ + 'video', // 主 + 'detail', // 主 + 'info', // 主 + 'vod', // 行业通用 + 'movie', // 英文 + 'film', // 英文 + 'xiangqing', + 'shipin', + 'neirong', + 'dianying', + ], + 'type' => 'detail_forge' + ], + 'detail' => [ + 'sort' => 3, + 'prefix' => [ + 'video', // 主 + 'detail', // 主 + 'info', // 主 + 'vod', // 行业通用 + 'movie', // 英文 + 'film', // 英文 + 'xiangqing', + 'shipin', + 'neirong', + 'dianying', + ], + 'type' => 'detail' + ], + 'category_child' => [ + 'sort' => 4, + 'prefix' => [ + 'fenlei', // 主 + 'category', // 英文 + 'class', // 主 + 'type', // 主 + 'genres', // 英文(类型) + 'leixing', // 拼音 + 'videotype', // 拼音 + ], + 'type' => 'category_child' + ], + 'category_parent' => [ + 'sort' => 5, + 'prefix' => [ + 'fenlei', // 主 + 'category', // 英文 + 'class', // 主 + 'type', // 主 + 'genres', // 英文(类型) + 'leixing', // 拼音 + 'videotype', // 拼音 + ], + 'type' => 'category_parent' + ], + 'rank_list' => [ + 'sort' => 6, + 'prefix' => [ + 'top', // 主 + 'rank', // 主 + 'paihangban', // 拼音 + 'ranking', // 英文 + 'hot', // 热门(谨慎) + 'phb', + ], + 'type' => 'rank_list' + ], + 'category_home' => [ + 'sort' => 7, + 'prefix' => [ + 'fenlei', // 主 + 'category', // 英文 + 'class', // 主 + 'type', // 主 + 'genres', // 英文(类型) + 'leixing', // 拼音 + 'videotype', // 拼音 + ], + 'type' => 'category_home' + ], + 'rank_index' => [ + 'sort' => 8, + 'prefix' => [ + 'top', // 主 + 'rank', // 主 + 'paihangban', // 拼音 + 'ranking', // 英文 + 'hot', // 热门(谨慎) + 'phb', + ], + 'type' => 'rank_index' + ], + 'search' => [ + 'sort' => 9, + 'prefix' => [ + 'search', // 主 + 'query', // 主 + 'sou', // 拼音 + 'find', // 英文 + 'get' + ], + 'type' => 'search' + ], + 'history' => [ + 'sort' => 10, + 'prefix' => [ + 'history', // 主 + 'his', // 简写 + 'jilu', // 拼音 + 'record', // 英文 + ], + 'type' => 'history' + ], + ]; + + /** 每个 type 的 8 套模板(你已测试可用) */ + $templates = [ + + 'category_home' => [ + 'pattern' => [ + '{prefix}', + '{prefix}/index', + '{prefix}/home', + '{prefix}/p', + '{prefix}/1', + '{prefix}-1', + '{prefix}-index', + '{prefix}-home', + ], + 'routes' => [ + '/{prefix}', + '/{prefix}/index', + '/{prefix}/home', + '/{prefix}/p', + '/{prefix}/1', + '/{prefix}/-1', + '/{prefix}-index', + '/{prefix}-home', + ], + ], + + 'category_parent' => [ + 'pattern' => [ + '{prefix}/{strParentCategory}', + '{prefix}/{strParentCategory}/index', + '{prefix}/{strParentCategory}/home', + '{prefix}/p/{strParentCategory}', + '{prefix}/1/{strParentCategory}', + '{prefix}/1-{strParentCategory}', + '{prefix}/{strParentCategory}/1', + '{prefix}/{strParentCategory}-1', + ], + 'routes' => [ + '/{prefix}/:strParentCategory', + '/{prefix}/:strParentCategory/index', + '/{prefix}/:strParentCategory/home', + '/{prefix}/p/:strParentCategory', + '/{prefix}/1/:strParentCategory', + '/{prefix}/1-:strParentCategory', + '/{prefix}/:strParentCategory/1', + '/{prefix}/:strParentCategory-1', + ], + ], + + 'category_child' => [ + 'pattern' => [ + '{prefix}/{strParentCategory}/{strCategory}/page{intPage}', + '{prefix}/{strParentCategory}/{strCategory}-page{intPage}', + '{prefix}-{strParentCategory}/{strCategory}/page{intPage}', + '{prefix}-{strParentCategory}/{strCategory}-page{intPage}', + '{prefix}/{strParentCategory}/{strCategory}/{intPage}', + '{prefix}/{strParentCategory}/{strCategory}-{intPage}', + '{prefix}-{strParentCategory}/{strCategory}/{intPage}', + '{prefix}-{strParentCategory}/{strCategory}-{intPage}', + ], + 'routes' => [ + '/{prefix}/:strParentCategory/:strCategory/page:intPage', + '/{prefix}/:strParentCategory/:strCategory-page:intPage', + '/{prefix}-:strParentCategory/:strCategory/page:intPage', + '/{prefix}-:strParentCategory/:strCategory-page:intPage', + '/{prefix}/:strParentCategory/:strCategory/:intPage', + '/{prefix}/:strParentCategory/:strCategory-:intPage', + '/{prefix}-:strParentCategory/:strCategory/:intPage', + '/{prefix}-:strParentCategory/:strCategory-:intPage', + ], + ], + + 'rank_index' => [ + 'pattern' => [ + '{prefix}', + '{prefix}/index', + '{prefix}/all', + '{prefix}/home', + '{prefix}/1', + '{prefix}-index', + '{prefix}-all', + '{prefix}-home', + ], + 'routes' => [ + '/{prefix}', + '/{prefix}/index', + '/{prefix}/all', + '/{prefix}/home', + '/{prefix}/1', + '/{prefix}-index', + '/{prefix}-all', + '/{prefix}-home', + ], + ], + + 'rank_list' => [ + 'pattern' => [ + '{prefix}/{strSortType}', + '{prefix}/{strSortType}/index', + '{prefix}/{strSortType}/home', + '{prefix}/{strSortType}/1', + '{prefix}/{strSortType}-1', + '{prefix}-{strSortType}', + '{prefix}-{strSortType}/index', + '{prefix}-{strSortType}/1', + ], + 'routes' => [ + '/{prefix}/:strSortType', + '/{prefix}/:strSortType/index', + '/{prefix}/:strSortType/home', + '/{prefix}/:strSortType/1', + '/{prefix}/:strSortType-1', + '/{prefix}-:strSortType', + '/{prefix}-:strSortType/index', + '/{prefix}-:strSortType/1', + ], + ], + + 'detail' => [ + 'pattern' => [ + '{prefix}/{strPinyin}-{intVId}', + '{prefix}/{intVId}-{strPinyin}', + '{prefix}/{strPinyin}/{intVId}', + '{prefix}/{intVId}', + '{prefix}-{strPinyin}-{intVId}', + '{prefix}-{intVId}-{strPinyin}', + '{prefix}/id-{intVId}', + '{prefix}/pinyin-{strPinyin}', + ], + 'routes' => [ + '/{prefix}/:strPinyin-:intVId', + '/{prefix}/:intVId-:strPinyin', + '/{prefix}/:strPinyin/:intVId', + '/{prefix}/:intVId', + '/{prefix}-:strPinyin-:intVId', + '/{prefix}-:intVId-:strPinyin', + '/{prefix}/id-:intVId', + '/{prefix}/pinyin-:strPinyin', + ], + ], + + 'detail_forge' => [ + 'pattern' => [ + '{prefix}/{strPinyin}-{intVId}-{intVForgeId}', + '{prefix}/{intVId}-{strPinyin}/{intVForgeId}', + '{prefix}/{strPinyin}/{intVId}-{intVForgeId}', + '{prefix}/{intVId}/{intVForgeId}', + '{prefix}-{strPinyin}-{intVId}/{intVForgeId}', + '{prefix}-{intVId}-{strPinyin}/{intVForgeId}', + '{prefix}/id-{intVId}-{intVForgeId}', + '{prefix}/pinyin-{strPinyin}/{intVForgeId}', + ], + 'routes' => [ + '/{prefix}/:strPinyin-:intVId-:intVForgeId', + '/{prefix}/:intVId-:strPinyin/:intVForgeId', + '/{prefix}/:strPinyin/:intVId-:intVForgeId', + '/{prefix}/:intVId/:intVForgeId', + '/{prefix}-:strPinyin-:intVId/:intVForgeId', + '/{prefix}-:intVId-:strPinyin/:intVForgeId', + '/{prefix}/id-:intVId-:intVForgeId', + '/{prefix}/pinyin-:strPinyin/:intVForgeId', + ], + ], + + 'play' => [ + 'pattern' => [ + '{prefix}/{strPinyin}-{intVId}-{strPlayType}-{intPlayIndex}', + '{prefix}/{strPinyin}-{intVId}/{strPlayType}-{intPlayIndex}', + '{prefix}/{intVId}-{strPlayType}-{intPlayIndex}', + '{prefix}/{intVId}/{strPlayType}-{intPlayIndex}', + '{prefix}-{strPinyin}-{intVId}-{strPlayType}-{intPlayIndex}', + '{prefix}-{intVId}-{strPinyin}/{strPlayType}-{intPlayIndex}', + '{prefix}-{intVId}-{strPlayType}-{intPlayIndex}', + '{prefix}-{intVId}/{strPlayType}-{intPlayIndex}', + ], + 'routes' => [ + '/{prefix}/:strPinyin-:intVId-:strPlayType-:intPlayIndex', + '/{prefix}/:strPinyin-:intVId/:strPlayType-:intPlayIndex', + '/{prefix}/:intVId-:strPlayType-:intPlayIndex', + '/{prefix}/:intVId/:strPlayType-:intPlayIndex', + '/{prefix}-:strPinyin-:intVId-:strPlayType-:intPlayIndex', + '/{prefix}-:intVId-:strPinyin/:strPlayType-:intPlayIndex', + '/{prefix}-:intVId-:strPlayType-:intPlayIndex', + '/{prefix}-:intVId/:strPlayType-:intPlayIndex', + ], + ], + + 'search' => [ + 'pattern' => [ + '{prefix}', + '{prefix}.html', + '{prefix}/index', + '{prefix}/home', + '{prefix}/keywords', + '{prefix}-index', + '{prefix}-home', + '{prefix}-keywords', + ], + 'routes' => [ + '/{prefix}', + '/{prefix}.html', + '/{prefix}/index', + '/{prefix}/home', + '/{prefix}/keywords', + '/{prefix}-index', + '/{prefix}-home', + '/{prefix}-keywords', + ], + ], + + 'history' => [ + 'pattern' => [ + '{prefix}', + '{prefix}.html', + '{prefix}/index', + '{prefix}/home', + '{prefix}/keywords', + '{prefix}-index', + '{prefix}-home', + '{prefix}-keywords', + ], + 'routes' => [ + '/{prefix}', + '/{prefix}.html', + '/{prefix}/index', + '/{prefix}/home', + '/{prefix}/keywords', + '/{prefix}-index', + '/{prefix}-home', + '/{prefix}-keywords', + ], + ], + ]; + + + // 🔴【新增 1】加载 index(落盘 ID) + $indexFile = __DIR__ . '/url_family_index.php'; + $index = file_exists($indexFile) ? require $indexFile : []; + + // 🔴【新增 2】计算当前最大 A 编号 + $maxId = 0; + foreach ($index as $aid) { + $num = (int)substr($aid, 1); + if ($num > $maxId) { + $maxId = $num; + } + } + + // 页面按 sort 排序(保证路由注册顺序) + uasort($pages, fn($a, $b) => $a['sort'] <=> $b['sort']); + + // prefix 套数 + $prefixSuiteCount = max(array_map(fn($p) => count($p['prefix']), $pages)); + + $families = []; + + // ❌【删除】$seq = 1; + + for ($pi = 0; $pi < $prefixSuiteCount; $pi++) { + for ($ti = 0; $ti < 8; $ti++) { + + /** + * 🔴【新增 3】构造 family 唯一指纹 key + * 只要 page / prefix / 模板索引一样,URL 身份就一样 + */ + $familyKeyParts = []; + + foreach ($pages as $pageKey => $page) { + $prefix = $page['prefix'][$pi] ?? end($page['prefix']); + $familyKeyParts[] = $pageKey . ':' . $prefix; + } + + $familyKey = implode('|', $familyKeyParts) . '|tpl' . $ti; + + // 🔴【新增 4】从 index 取 A 编号(没有就新增) + if (!isset($index[$familyKey])) { + $maxId++; + $index[$familyKey] = 'A' . str_pad((string)$maxId, 3, '0', STR_PAD_LEFT); + } + + $aid = $index[$familyKey]; + + // 🔴【修改】family 的 id 不再用 $seq + $family = [ + 'id' => $aid, + 'home' => [ + 'pattern' => '', + 'routes' => [ + '/' + ] + ], + ]; + + foreach ($pages as $pageKey => $page) { + $type = $page['type']; + if (!isset($templates[$type])) { + continue; + } + + $prefix = $page['prefix'][$pi] ?? end($page['prefix']); + + $pattern = str_replace('{prefix}', $prefix, $templates[$type]['pattern'][$ti]); + $route = str_replace('{prefix}', $prefix, $templates[$type]['routes'][$ti]); + + $family[$pageKey] = [ + 'sort' => $page['sort'], + 'pattern' => $pattern, + 'routes' => [$route], + ]; + } + + // 🔴【修改】用 A 编号做 key,避免被重排 + $families[$aid] = $family; + } + } + + // 🔴【新增 5】index 落盘(只会 append) + file_put_contents( + $indexFile, + "