| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475 |
- <?php
- // +----------------------------------------------------------------------
- // |
- // +----------------------------------------------------------------------
- // |
- // +----------------------------------------------------------------------
- // |
- // +----------------------------------------------------------------------
- // |
- // +----------------------------------------------------------------------
- // +----------------------------------------------------------------------
- // | sitemap管理
- // +----------------------------------------------------------------------
- namespace app\admin\controller;
- use addons\sitemap\library\Sitemap as SitemapLib;
- use app\admin\model\general\Lang as LangModel;
- use app\common\controller\Backend;
- use think\facade\Db;
- class Sitemap extends Backend
- {
- protected $directory;
- protected $url_mode = 1;
- protected function tableQuery($table)
- {
- $prefix = config('database.connections.mysql.prefix', '');
- $tableName = strpos($table, $prefix) === 0 ? $table : $prefix . $table;
- return Db::table($tableName);
- }
- protected function enabledLangs()
- {
- return LangModel::getEnabledList();
- }
- protected function defaultLang()
- {
- return LangModel::getDefaultMark();
- }
- protected function withLangPrefix($url, $lang)
- {
- $defaultLang = $this->defaultLang();
- if (!$lang || $lang === $defaultLang) {
- return $url;
- }
- $parts = parse_url($url);
- $path = $parts['path'] ?? '/';
- $path = '/' . ltrim($path, '/');
- if (preg_match('#^/' . preg_quote($lang, '#') . '(/|$)#i', $path)) {
- return $url;
- }
- $newPath = '/' . $lang . ($path === '/' ? '' : $path);
- $result = '';
- if (!empty($parts['scheme'])) {
- $result .= $parts['scheme'] . '://';
- }
- if (!empty($parts['user'])) {
- $result .= $parts['user'];
- if (!empty($parts['pass'])) {
- $result .= ':' . $parts['pass'];
- }
- $result .= '@';
- }
- if (!empty($parts['host'])) {
- $result .= $parts['host'];
- }
- if (!empty($parts['port'])) {
- $result .= ':' . $parts['port'];
- }
- $result .= $newPath;
- if (!empty($parts['query'])) {
- $result .= '?' . $parts['query'];
- }
- if (!empty($parts['fragment'])) {
- $result .= '#' . $parts['fragment'];
- }
- return $result;
- }
- protected function contentExtTableExists($table)
- {
- static $tables = null;
- if ($tables === null) {
- $tables = array_map('current', Db::query('SHOW TABLES'));
- }
- return in_array(config('database.connections.mysql.prefix') . $table, $tables, true) || in_array($table, $tables, true);
- }
- protected function tableFields($table)
- {
- static $cache = [];
- if (!isset($cache[$table])) {
- $prefix = config('database.connections.mysql.prefix', '');
- $tableName = strpos($table, $prefix) === 0 ? $table : $prefix . $table;
- $cache[$table] = array_map('strtolower', array_keys(Db::getFields($tableName)));
- }
- return $cache[$table];
- }
- protected function pickLastmod(array $row, $currentTime)
- {
- if (isset($row['update_time']) && $row['update_time']) {
- return $row['update_time'];
- }
- if (isset($row['create_time']) && $row['create_time']) {
- return $row['create_time'];
- }
- return $currentTime;
- }
- protected function initialize()
- {
- parent::initialize();
- $this->url_mode = (int) get_addon_config('cms')['site_url_mode'];
- $this->directory = public_path();
- }
- public function index()
- {
- if ($this->request->isPost()) {
- $info = get_addon_info('cms');
- if (!$info || $info['status'] != 1) {
- $this->error("请在后台插件管理中安装《内容管理系统》并启用后再尝试");
- }
- $data = $this->request->post();
- $maxUrlsPerFile = intval($data['max_url'] ?? 50000);
- if ($maxUrlsPerFile <= 0) {
- $this->error("每个sitemap文件的最大URL数量必须大于0");
- }
- if ($maxUrlsPerFile > 50000) {
- $this->error("每个sitemap文件最多不能超过50000个URL");
- }
- $totalUrls = $this->calculateTotalUrls();
- if ($totalUrls <= $maxUrlsPerFile) {
- // 如果URL数量不超过限制,生成单个文件
- $result = $this->generateSingleSitemap($data);
- $this->success($result['message']);
- } else {
- // 如果超过限制,生成多个文件 + 索引文件
- $result = $this->generateMultipleSitemaps($data, $totalUrls, $maxUrlsPerFile);
- $this->success($result['message']);
- }
- } else {
- $fileInfo = $this->getSitemapFileInfo();
- $this->assign($fileInfo);
- return $this->fetch();
- }
- }
- /**
- * 计算总URL数量
- */
- private function calculateTotalUrls()
- {
- $itemsByLang = $this->collectAllUrls([
- 'index' => ['priority' => 0, 'changefreq' => 'daily'],
- 'category' => ['priority' => 0, 'changefreq' => 'daily'],
- 'content' => ['priority' => 0, 'changefreq' => 'daily'],
- 'tag' => ['priority' => 0, 'changefreq' => 'daily'],
- ], $this->request->domain());
- $count = 0;
- foreach ($itemsByLang as $langItems) {
- $count += count($langItems);
- }
- return $count;
- }
- /**
- * 生成单个sitemap文件
- */
- private function generateSingleSitemap($data)
- {
- $rootUrl = $this->request->domain();
- $itemsByLang = $this->collectAllUrls($data, $rootUrl);
- $allItems = [];
- foreach ($itemsByLang as $lang => $langItems) {
- foreach ($langItems as $item) {
- $allItems[] = $item;
- }
- }
- if (!$data['type']) {
- $langs = array_keys($itemsByLang);
- if (count($langs) <= 1) {
- $Sitemap = new SitemapLib();
- foreach ($allItems as $item) {
- $Sitemap->AddItem($item['loc'], $item['priority'], $item['changefreq'], $item['lastmod']);
- }
- $filename = 'sitemap.xml';
- $Sitemap->SaveToFile($this->directory . $filename);
- $message = "生成成功!共 " . count($allItems) . " 个URL,文件: {$filename}";
- } else {
- $sitemapFiles = [];
- foreach ($itemsByLang as $lang => $langItems) {
- $filename = 'sitemap-' . $lang . '.xml';
- $Sitemap = new SitemapLib();
- foreach ($langItems as $item) {
- $Sitemap->AddItem($item['loc'], $item['priority'], $item['changefreq'], $item['lastmod']);
- }
- $Sitemap->SaveToFile($this->directory . $filename);
- $sitemapFiles[] = [
- 'filename' => $filename,
- 'url' => $rootUrl . '/' . $filename,
- 'lastmod' => date('Y-m-d\TH:i:s+00:00'),
- ];
- }
- $this->generateSitemapIndex($sitemapFiles, $rootUrl, 'sitemap.xml');
- $message = "生成成功!共 " . count($allItems) . " 个URL,按语言生成 " . count($sitemapFiles) . " 个文件,索引文件: sitemap.xml";
- }
- } else {
- $langs = array_keys($itemsByLang);
- if (count($langs) <= 1) {
- $filename = 'sitemap.txt';
- $content = $this->generateTxtContent($allItems);
- file_put_contents($this->directory . $filename, $content);
- $message = "生成成功!共 " . count($allItems) . " 个URL,文件: {$filename}";
- } else {
- foreach ($itemsByLang as $lang => $langItems) {
- file_put_contents($this->directory . 'sitemap-' . $lang . '.txt', $this->generateTxtContent($langItems));
- }
- $message = "生成成功!共 " . count($allItems) . " 个URL,已按语言生成 TXT 文件。";
- }
- }
- return [
- 'success' => true,
- 'message' => $message,
- ];
- }
- /**
- * 生成多个sitemap文件 + 索引文件
- */
- private function generateMultipleSitemaps($data, $totalUrls, $maxUrlsPerFile)
- {
- $rootUrl = $this->request->domain();
- $itemsByLang = $this->collectAllUrls($data, $rootUrl);
- $sitemapFiles = [];
- $totalItems = 0;
- foreach ($itemsByLang as $langItems) {
- $totalItems += count($langItems);
- }
- if (!$data['type']) {
- foreach ($itemsByLang as $lang => $langItems) {
- $chunks = array_chunk($langItems, $maxUrlsPerFile);
- foreach ($chunks as $index => $chunkItems) {
- $filename = count($chunks) > 1 ? ('sitemap-' . $lang . '-' . ($index + 1) . '.xml') : ('sitemap-' . $lang . '.xml');
- $Sitemap = new SitemapLib();
- foreach ($chunkItems as $item) {
- $Sitemap->AddItem($item['loc'], $item['priority'], $item['changefreq'], $item['lastmod']);
- }
- $Sitemap->SaveToFile($this->directory . $filename);
- $sitemapFiles[] = [
- 'filename' => $filename,
- 'url' => $rootUrl . '/' . $filename,
- 'lastmod' => date('Y-m-d\TH:i:s+00:00'),
- ];
- }
- }
- $this->generateSitemapIndex($sitemapFiles, $rootUrl, 'sitemap.xml');
- $message = "生成成功!共 {$totalItems} 个URL,按语言/分片生成 " . count($sitemapFiles) . " 个文件,索引文件: sitemap.xml";
- } else {
- foreach ($itemsByLang as $lang => $langItems) {
- $chunks = array_chunk($langItems, $maxUrlsPerFile);
- foreach ($chunks as $index => $chunkItems) {
- $filename = count($chunks) > 1 ? ('sitemap-' . $lang . '-' . ($index + 1) . '.txt') : ('sitemap-' . $lang . '.txt');
- file_put_contents($this->directory . $filename, $this->generateTxtContent($chunkItems));
- }
- }
- $message = "生成成功!共 {$totalItems} 个URL,已按语言生成 TXT 文件。";
- }
- return [
- 'success' => true,
- 'message' => $message,
- ];
- }
- /**
- * 收集所有URL数据
- */
- private function collectAllUrls($data, $rootUrl)
- {
- $itemsByLang = [];
- $category = cache('Category');
- $currentTime = time();
- $defaultLang = $this->defaultLang();
- $langs = $this->enabledLangs();
- foreach ($langs as $langItem) {
- $lang = $langItem->mark;
- $itemsByLang[$lang] = [];
- $itemsByLang[$lang][] = [
- 'loc' => $this->withLangPrefix($rootUrl, $lang),
- 'priority' => intval($data['index']['priority']),
- 'changefreq' => $data['index']['changefreq'],
- 'lastmod' => $currentTime,
- ];
- }
- $categoryFields = $this->tableFields('cms_category');
- $categorySelectFields = ['id', 'url', 'catdir'];
- if (in_array('update_time', $categoryFields, true)) {
- $categorySelectFields[] = 'update_time';
- } elseif (in_array('create_time', $categoryFields, true)) {
- $categorySelectFields[] = 'create_time';
- }
- $categoryList = $this->tableQuery('cms_category')->where('status', 1)->order('id desc')->field(implode(',', $categorySelectFields))->select();
- if (!empty($categoryList)) {
- foreach ($categoryList as $vo) {
- foreach ($langs as $langItem) {
- $lang = $langItem->mark;
- if ($lang !== $defaultLang) {
- $hasTranslate = $this->tableQuery('cms_category_data')->where(['catid' => $vo['id'], 'lang' => $lang])->count() > 0;
- if (!$hasTranslate) {
- continue;
- }
- }
- $cat = $this->url_mode == 1 ? $vo['id'] : $vo['catdir'];
- $itemsByLang[$lang][] = [
- 'loc' => $this->withLangPrefix($rootUrl . buildCatUrl($cat), $lang),
- 'priority' => intval($data['category']['priority']),
- 'changefreq' => $data['category']['changefreq'],
- 'lastmod' => $this->pickLastmod((array)$vo, $currentTime),
- ];
- }
- }
- }
- $modelList = cache('Model');
- if (!empty($modelList)) {
- foreach ($modelList as $vo) {
- if ($vo['module'] != 'cms') {
- continue;
- }
- $contentFields = $this->tableFields($vo['tablename']);
- $contentSelectFields = ['id', 'catid'];
- $orderField = 'id';
- if (in_array('update_time', $contentFields, true)) {
- $contentSelectFields[] = 'update_time';
- $orderField = 'update_time';
- } elseif (in_array('create_time', $contentFields, true)) {
- $contentSelectFields[] = 'create_time';
- $orderField = 'create_time';
- }
- $contentList = $this->tableQuery($vo['tablename'])
- ->where('status', 1)
- ->order($orderField . ' desc')
- ->field(implode(',', $contentSelectFields))
- ->select();
- if (empty($contentList)) {
- continue;
- }
- $extTable = $vo['tablename'] . '_data';
- $hasExtTable = $this->contentExtTableExists($extTable);
- $extFields = $hasExtTable ? $this->tableFields($extTable) : [];
- $hasLangColumn = in_array('lang', $extFields, true);
- foreach ($contentList as $v) {
- foreach ($langs as $langItem) {
- $lang = $langItem->mark;
- if ($lang !== $defaultLang && $hasExtTable && $hasLangColumn) {
- $hasTranslate = $this->tableQuery($extTable)->where(['did' => $v['id'], 'lang' => $lang])->count() > 0;
- if (!$hasTranslate) {
- continue;
- }
- } elseif ($lang !== $defaultLang && $hasExtTable && !$hasLangColumn) {
- continue;
- }
- $cat = $this->url_mode == 1 ? $v['catid'] : (isset($category[$v['catid']]) ? $category[$v['catid']]['catdir'] : getCategory($v['catid'], 'catdir'));
- $itemsByLang[$lang][] = [
- 'loc' => $this->withLangPrefix($rootUrl . buildContentUrl($cat, $v['id']), $lang),
- 'priority' => intval($data['content']['priority']),
- 'changefreq' => $data['content']['changefreq'],
- 'lastmod' => $this->pickLastmod((array)$v, $currentTime),
- ];
- }
- }
- }
- }
- $tagFields = $this->tableFields('cms_tags');
- $tagSelectFields = ['tag'];
- $tagOrderField = 'tag';
- if (in_array('update_time', $tagFields, true)) {
- $tagSelectFields[] = 'update_time';
- $tagOrderField = 'update_time';
- } elseif (in_array('create_time', $tagFields, true)) {
- $tagSelectFields[] = 'create_time';
- $tagOrderField = 'create_time';
- }
- $tags = $this->tableQuery('cms_tags')->order($tagOrderField . ' desc')->field(implode(',', $tagSelectFields))->select();
- if (!empty($tags)) {
- foreach ($tags as $vo) {
- $itemsByLang[$defaultLang][] = [
- 'loc' => $rootUrl . addon_url('cms/tags/index', [':tag' => $vo['tag']]),
- 'priority' => intval($data['tag']['priority']),
- 'changefreq' => $data['tag']['changefreq'],
- 'lastmod' => $this->pickLastmod((array)$vo, $currentTime),
- ];
- }
- }
- return $itemsByLang;
- }
- /**
- * 生成sitemap索引文件
- */
- private function generateSitemapIndex($sitemapFiles, $rootUrl, $filename = 'sitemap_index.xml')
- {
- $xml = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
- $xml .= '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
- foreach ($sitemapFiles as $file) {
- $xml .= " <sitemap>\n";
- $xml .= " <loc>{$file['url']}</loc>\n";
- $xml .= " <lastmod>{$file['lastmod']}</lastmod>\n";
- $xml .= " </sitemap>\n";
- }
- $xml .= '</sitemapindex>';
- file_put_contents($this->directory . $filename, $xml);
- }
- /**
- * 生成TXT格式内容
- */
- private function generateTxtContent($items)
- {
- $content = '';
- foreach ($items as $item) {
- $content .= $item['loc'] . PHP_EOL;
- }
- return $content;
- }
- /**
- * 获取sitemap文件信息
- */
- private function getSitemapFileInfo()
- {
- $fileInfo = [];
- if (is_file($this->directory . 'sitemap.xml')) {
- $fileInfo['make_xml_time'] = date('Y-m-d H:i:s', filemtime($this->directory . 'sitemap.xml'));
- $fileInfo['xml_size'] = round(filesize($this->directory . 'sitemap.xml') / 1024, 2);
- }
- if (is_file($this->directory . 'sitemap.txt')) {
- $fileInfo['make_txt_time'] = date('Y-m-d H:i:s', filemtime($this->directory . 'sitemap.txt'));
- $fileInfo['txt_size'] = round(filesize($this->directory . 'sitemap.txt') / 1024, 2);
- }
- if (is_file($this->directory . 'sitemap_index.xml')) {
- $fileInfo['make_index_time'] = date('Y-m-d H:i:s', filemtime($this->directory . 'sitemap_index.xml'));
- $fileInfo['index_size'] = round(filesize($this->directory . 'sitemap_index.xml') / 1024, 2);
- }
- // 获取所有sitemap分割文件
- $sitemapFiles = glob($this->directory . 'sitemap_*.xml');
- if (!empty($sitemapFiles)) {
- $fileInfo['split_files'] = count($sitemapFiles);
- }
- return $fileInfo;
- }
- }
|