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,
+ "