From 7cdb591f697a4d67129e0cd58c34eb75e1de60de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BF=97=E5=BC=BA?= <357099073@qq.com> Date: Tue, 10 Mar 2026 18:05:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=A7=9F=E6=88=B7=E7=AB=AF?= =?UTF-8?q?=E7=BD=91=E7=AB=99=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Cms/Domain/TenantDomainController.php | 60 +++++----- .../controller/Cms/Theme/ThemeController.php | 13 ++- app/common/middleware/DomainParse.php | 45 +++++--- app/index/controller/Index.php | 103 +++++++++++++++++- .../01/296e54db593b14cdb27079fd1ee7fc.php | 2 +- 5 files changed, 167 insertions(+), 56 deletions(-) diff --git a/app/admin/controller/Cms/Domain/TenantDomainController.php b/app/admin/controller/Cms/Domain/TenantDomainController.php index 20eaebf..d404534 100644 --- a/app/admin/controller/Cms/Domain/TenantDomainController.php +++ b/app/admin/controller/Cms/Domain/TenantDomainController.php @@ -8,6 +8,9 @@ use app\admin\BaseController; use think\facade\Db; use think\Request; use think\facade\Session; +use app\model\Tenant\TenantDomain; +use app\model\Tenant\Tenant; +use app\model\System\SystemDomainPool; /** * 租户域名绑定控制器 @@ -26,7 +29,7 @@ class TenantDomainController extends BaseController $subDomain = $request->param('sub_domain', ''); $where = [['delete_time', '=', null]]; - + if ($tenantId > 0) { $where[] = ['tid', '=', $tenantId]; } @@ -37,23 +40,20 @@ class TenantDomainController extends BaseController $where[] = ['sub_domain', 'like', "%$subDomain%"]; } - $list = Db::name('mete_tenant_domain') - ->where($where) + $list = TenantDomain::where($where) ->page($page, $pageSize) ->order('id', 'desc') ->select() ->toArray(); - - $total = Db::name('mete_tenant_domain') - ->where($where) + + $total = TenantDomain::where($where) ->count(); // 获取租户名称 $tenantIds = array_column($list, 'tid'); $tenants = []; if ($tenantIds) { - $tenantList = Db::name('mete_tenant') - ->whereIn('id', $tenantIds) + $tenantList = Tenant::whereIn('id', $tenantIds) ->select() ->toArray(); $tenants = array_column($tenantList, null, 'id'); @@ -80,7 +80,7 @@ class TenantDomainController extends BaseController public function myDomains(Request $request) { $tid = $request->param('tid', 0, 'int'); - + if ($tid <= 0) { return json([ 'code' => 400, @@ -88,8 +88,7 @@ class TenantDomainController extends BaseController ]); } - $list = Db::name('mete_tenant_domain') - ->where('tid', $tid) + $list = TenantDomain::where('tid', $tid) ->where('delete_time', null) ->order('id', 'desc') ->select() @@ -141,12 +140,11 @@ class TenantDomainController extends BaseController } // 检查主域名是否存在且启用 - $mainDomainInfo = Db::name('mete_system_domain_pool') - ->where('main_domain', $mainDomain) + $mainDomainInfo = SystemDomainPool::where('main_domain', $mainDomain) ->where('status', 1) ->where('delete_time', null) ->find(); - + if (!$mainDomainInfo) { return json([ 'code' => 400, @@ -155,12 +153,11 @@ class TenantDomainController extends BaseController } // 检查二级域名是否已被使用 - $exists = Db::name('mete_tenant_domain') - ->where('sub_domain', $subDomain) + $exists = TenantDomain::where('sub_domain', $subDomain) ->where('main_domain', $mainDomain) ->where('delete_time', null) ->find(); - + if ($exists) { return json([ 'code' => 400, @@ -170,8 +167,8 @@ class TenantDomainController extends BaseController $fullDomain = $subDomain . '.' . $mainDomain; $now = date('Y-m-d H:i:s'); - - $id = Db::name('mete_tenant_domain')->insertGetId([ + + $id = TenantDomain::insertGetId([ 'tid' => $tid, 'sub_domain' => $subDomain, 'main_domain' => $mainDomain, @@ -203,10 +200,9 @@ class TenantDomainController extends BaseController ]); } - $domain = Db::name('mete_tenant_domain') - ->where('id', $id) + $domain = TenantDomain::where('id', $id) ->find(); - + if (!$domain) { return json([ 'code' => 404, @@ -222,16 +218,15 @@ class TenantDomainController extends BaseController } $newStatus = $action === 'approve' ? 1 : 2; // 1-已生效 2-已拒绝 - - Db::name('mete_tenant_domain') - ->where('id', $id) + + TenantDomain::where('id', $id) ->update([ 'status' => $newStatus, 'update_time' => date('Y-m-d H:i:s') ]); $msg = $action === 'approve' ? '审核通过' : '已拒绝'; - + return json([ 'code' => 200, 'msg' => $msg @@ -244,7 +239,7 @@ class TenantDomainController extends BaseController public function toggleStatus(Request $request) { $id = $request->param('id', 0, 'int'); - + if ($id <= 0) { return json([ 'code' => 400, @@ -252,10 +247,9 @@ class TenantDomainController extends BaseController ]); } - $domain = Db::name('mete_tenant_domain') - ->where('id', $id) + $domain = TenantDomain::where('id', $id) ->find(); - + if (!$domain) { return json([ 'code' => 404, @@ -272,8 +266,7 @@ class TenantDomainController extends BaseController } // 切换为禁用状态 - Db::name('mete_tenant_domain') - ->where('id', $id) + TenantDomain::where('id', $id) ->update([ 'status' => 2, 'update_time' => date('Y-m-d H:i:s') @@ -297,8 +290,7 @@ class TenantDomainController extends BaseController ]); } - Db::name('mete_tenant_domain') - ->where('id', $id) + TenantDomain::where('id', $id) ->update([ 'delete_time' => date('Y-m-d H:i:s') ]); diff --git a/app/admin/controller/Cms/Theme/ThemeController.php b/app/admin/controller/Cms/Theme/ThemeController.php index dd5c9e8..d22e5f7 100644 --- a/app/admin/controller/Cms/Theme/ThemeController.php +++ b/app/admin/controller/Cms/Theme/ThemeController.php @@ -16,8 +16,9 @@ class ThemeController extends BaseController { private ThemeService $themeService; - public function __construct() + public function __construct(\think\App $app) { + parent::__construct($app); $this->themeService = new ThemeService(); } @@ -50,9 +51,17 @@ class ThemeController extends BaseController */ public function switch() { - $tid = Request::post('tid', 0, 'int'); + // 从当前登录用户获取tid,而不是从前端参数 + $tid = $this->getTenantId(); $themeKey = Request::post('theme_key', ''); + if (empty($tid) || $tid == 0) { + return json([ + 'code' => 401, + 'msg' => '未获取到租户ID,请重新登录' + ]); + } + if (empty($themeKey)) { return json([ 'code' => 400, diff --git a/app/common/middleware/DomainParse.php b/app/common/middleware/DomainParse.php index 6c5614e..f899f0b 100644 --- a/app/common/middleware/DomainParse.php +++ b/app/common/middleware/DomainParse.php @@ -17,40 +17,53 @@ class DomainParse { $host = $request->host(true); // 获取完整域名,不带端口 - // 排除后台域名和平台官网域名 - $adminDomains = ['admin.xxx.com']; // TODO: 配置后台域名 - $platformDomains = ['www.xxx.com', 'xxx.com']; // TODO: 配置平台官网域名 + // 你的后台/平台域名(排除,不走租户逻辑) + $adminDomains = ['back.yunzer.cn', 'api.yunzer.cn']; + $platformDomains = ['www.yunzer.cn', 'yunzer.cn']; + // 后台/平台/API域名,直接放行 if (in_array($host, $adminDomains) || in_array($host, $platformDomains)) { return $next($request); } - // 解析二级域名 + // 解析域名(兼容 yunzer.com.cn/dh2.fun) $domainParts = explode('.', $host); - - // 至少需要三级域名(如 sub.domain.com) - if (count($domainParts) < 3) { - return $next($request); + $allowMainDomains = ['yunzer.com.cn', 'dh2.fun']; + $mainDomain = ''; + + foreach ($allowMainDomains as $domain) { + $domainLen = count(explode('.', $domain)); + $currentLen = count($domainParts); + if ($currentLen > $domainLen) { + $matchMain = implode('.', array_slice($domainParts, -$domainLen)); + if ($matchMain === $domain) { + $mainDomain = $matchMain; + break; + } + } } - $subDomain = $domainParts[0]; - $mainDomain = implode('.', array_slice($domainParts, 1)); + // 非租户主域名,放行(走API默认界面,可改为跳转到平台) + if (empty($mainDomain)) { + return $next($request); + } // 查询租户域名绑定记录 $tenantDomain = Db::name('mete_tenant_domain') ->where('full_domain', $host) - ->where('status', 1) // 只有已生效的域名才能访问 - ->where('delete_time', null) + ->where('status', 1) ->find(); if ($tenantDomain) { - // 将租户ID写入请求对象,供后续控制器使用 + // 1. 写入租户ID到请求(核心,后续所有逻辑都能获取) $request->tenantId = $tenantDomain['tid']; + $request->header('X-Tenant-Id', (string)$tenantDomain['tid']); - // 同时写入header,方便前端获取 - $request->header['X-Tenant-Id', $tenantDomain['tid']); + // 2. 额外:写入租户域名信息,方便模板使用 + $request->tenantDomain = $host; + $request->tenantMainDomain = $mainDomain; } return $next($request); } -} +} \ No newline at end of file diff --git a/app/index/controller/Index.php b/app/index/controller/Index.php index ceef338..e61e1a2 100644 --- a/app/index/controller/Index.php +++ b/app/index/controller/Index.php @@ -13,7 +13,7 @@ use app\service\ThemeService; use think\db\exception\DbException; use think\facade\Env; use think\facade\Request; -use app\model\Template\TemplateSiteConfig; +use app\model\Cms\TemplateSiteConfig; class Index extends BaseController { @@ -26,8 +26,102 @@ class Index extends BaseController public function index() { + // 获取请求对象 + $request = $this->request ?? Request::instance(); + + // 优先从请求对象获取中间件设置的租户ID(通过域名解析) + $tid = $request->tenantId ?? 0; + + // 兼容:从header获取 + if ($tid == 0) { + $tid = (int)$request->header('X-Tenant-Id', 0); + } + + // 调试:检查tid + if ($tid === 0) { + die('tenantId未获取到,请检查域名解析'); + } + + // 获取租户选择的模板 + $themeKey = 'default'; + if ($tid > 0) { + // 不使用软删除过滤 + $config = TemplateSiteConfig::where('tid', $tid) + ->where('key', 'current_theme') + ->withoutField('delete_time') + ->find(); + $themeKey = $config['value'] ?? 'default'; + } + + // 模板路径:/themes/{theme_key}/,优先使用index.php,其次index.html + $themeBasePath = root_path() . 'public' . DIRECTORY_SEPARATOR . 'themes' . DIRECTORY_SEPARATOR . $themeKey; + $themeUrlPath = '/themes/' . $themeKey . '/'; + + if (is_file($themeBasePath . DIRECTORY_SEPARATOR . 'index.php')) { + // 渲染PHP模板(直接include) + return $this->renderPhpTemplate($themeBasePath . DIRECTORY_SEPARATOR . 'index.php', $themeUrlPath); + } elseif (is_file($themeBasePath . DIRECTORY_SEPARATOR . 'index.html')) { + // 读取HTML模板内容 + $content = file_get_contents($themeBasePath . DIRECTORY_SEPARATOR . 'index.html'); + // 修复资源路径:将相对路径转换为绝对路径 + $content = $this->fixTemplateAssets($content, $themeUrlPath); + return response($content, 200, ['Content-Type' => 'text/html; charset=utf-8']); + } + + // 模板不存在,返回默认 return view('index/index'); } + + /** + * 修复模板中的资源路径 + * 将相对路径 (assets/, css/, js/, images/) 转换为绝对路径 (/themes/xxx/) + */ + private function fixTemplateAssets(string $content, string $themeUrlPath): string + { + // 需要修复的资源目录 + $assetsDirs = ['assets', 'css', 'js', 'images', 'img', 'fonts', 'plugins', 'libs']; + + foreach ($assetsDirs as $dir) { + // 匹配 src="assets/..." 或 href="css/..." 等相对路径 + // 不匹配已以 / 开头的绝对路径 + $content = preg_replace( + '/(src|href)=["\'](' . $dir . '\/[^"\']*)["\']/', + '$1="' . $themeUrlPath . '$2"', + $content + ); + // 匹配 src='assets/...' + $content = preg_replace( + '/(src|href)=[\']([^\/][^\'\"]*\/[^\'\"]*)[\']/', + '$1="' . $themeUrlPath . '$2"', + $content + ); + } + + return $content; + } + + /** + * 渲染PHP模板文件 + */ + private function renderPhpTemplate(string $templateFile, string $themeUrlPath = '') + { + // 定义模板基础URL常量,供模板使用 + if (!defined('THEME_URL')) { + define('THEME_URL', $themeUrlPath); + } + + ob_start(); + // 获取URL参数 + $getParams = Request::get(); + extract($getParams, EXTR_OVERWRITE); + include $templateFile; + $content = ob_get_clean(); + + // 修复PHP模板输出中的资源路径 + $content = $this->fixTemplateAssets($content, $themeUrlPath); + + return response($content, 200, ['Content-Type' => 'text/html; charset=utf-8']); + } /** * 前端初始化接口 - 返回当前模板和填充数据 @@ -35,8 +129,11 @@ class Index extends BaseController */ public function init() { - // 获取租户ID(从请求参数) - $tid = Request::param('tid', 0, 'int'); + // 优先从请求对象获取中间件设置的租户ID(通过域名解析) + $tid = $this->request->tenantId ?? 0; + + // 兼容:URL参数传递的tid作为备用 + $tid = $tid > 0 ? $tid : Request::param('tid', 0, 'int'); // 从TemplateSiteConfig获取配置(根据租户ID) $config = null; diff --git a/runtime/cache/01/296e54db593b14cdb27079fd1ee7fc.php b/runtime/cache/01/296e54db593b14cdb27079fd1ee7fc.php index dff18b8..fa524a3 100644 --- a/runtime/cache/01/296e54db593b14cdb27079fd1ee7fc.php +++ b/runtime/cache/01/296e54db593b14cdb27079fd1ee7fc.php @@ -1,4 +1,4 @@ -a:7:{s:2:"id";i:2;s:7:"account";s:10:"hero920103";s:4:"name";s:9:"李志强";s:8:"group_id";i:1;s:9:"tenant_id";i:1;s:6:"tenant";O:23:"app\model\Tenant\Tenant":26:{s:3:"get";a:2:{s:2:"id";i:1;s:11:"tenant_name";s:39:"连云港云泽广告传媒有限公司";}s:4:"data";a:2:{s:2:"id";i:1;s:11:"tenant_name";s:39:"连云港云泽广告传媒有限公司";}s:6:"origin";a:2:{s:2:"id";i:1;s:11:"tenant_name";s:39:"连云港云泽广告传媒有限公司";}s:8:"relation";a:0:{}s:8:"together";a:0:{}s:5:"allow";a:0:{}s:8:"withAttr";a:0:{}s:6:"schema";a:13:{s:2:"id";s:7:"integer";s:11:"tenant_code";s:6:"string";s:11:"tenant_name";s:6:"string";s:14:"contact_person";s:6:"string";s:13:"contact_phone";s:6:"string";s:13:"contact_email";s:6:"string";s:7:"address";s:6:"string";s:6:"domain";s:6:"string";s:6:"status";s:7:"integer";s:11:"create_time";s:8:"datetime";s:11:"update_time";s:8:"datetime";s:11:"delete_time";s:8:"datetime";s:6:"remark";s:6:"string";}s:10:"updateTime";s:11:"update_time";s:10:"createTime";s:11:"create_time";s:6:"suffix";s:0:"";s:8:"validate";s:0:"";s:4:"type";a:0:{}s:8:"readonly";a:0:{}s:6:"disuse";a:0:{}s:6:"hidden";a:0:{}s:7:"visible";a:0:{}s:6:"append";a:0:{}s:7:"mapping";a:0:{}s:6:"strict";b:1;s:8:"bindAttr";a:0:{}s:12:"autoRelation";a:0:{}s:18:"autoWriteTimestamp";b:1;s:10:"dateFormat";s:11:"Y-m-d H:i:s";s:2:"pk";s:2:"id";s:6:"exists";b:1;}s:6:"rights";a:46:{i:0;i:31;i:1;i:32;i:2;i:33;i:3;i:4;i:4;i:9;i:5;i:29;i:6;i:5;i:7;i:6;i:8;i:30;i:9;i:27;i:10;i:28;i:11;i:19;i:12;i:12;i:13;i:18;i:14;i:23;i:15;i:25;i:16;i:26;i:17;i:24;i:18;i:7;i:19;i:8;i:20;i:17;i:21;i:21;i:22;i:2;i:23;i:10;i:24;i:11;i:25;i:16;i:26;i:1;i:27;i:20;i:28;i:22;i:29;i:3;i:30;i:15;i:31;i:34;i:32;i:35;i:33;i:36;i:34;i:37;i:35;i:38;i:36;i:39;i:37;i:40;i:38;i:41;i:39;i:42;i:40;i:43;i:41;i:44;i:42;i:45;i:43;i:46;i:44;i:47;i:45;i:48;}} \ No newline at end of file +a:7:{s:2:"id";i:2;s:7:"account";s:10:"hero920103";s:4:"name";s:9:"李志强";s:8:"group_id";i:1;s:3:"tid";i:1;s:6:"tenant";O:23:"app\model\Tenant\Tenant":26:{s:3:"get";a:2:{s:2:"id";i:1;s:11:"tenant_name";s:39:"连云港云泽广告传媒有限公司";}s:4:"data";a:2:{s:2:"id";i:1;s:11:"tenant_name";s:39:"连云港云泽广告传媒有限公司";}s:6:"origin";a:2:{s:2:"id";i:1;s:11:"tenant_name";s:39:"连云港云泽广告传媒有限公司";}s:8:"relation";a:0:{}s:8:"together";a:0:{}s:5:"allow";a:0:{}s:8:"withAttr";a:0:{}s:6:"schema";a:13:{s:2:"id";s:7:"integer";s:11:"tenant_code";s:6:"string";s:11:"tenant_name";s:6:"string";s:14:"contact_person";s:6:"string";s:13:"contact_phone";s:6:"string";s:13:"contact_email";s:6:"string";s:7:"address";s:6:"string";s:6:"domain";s:6:"string";s:6:"status";s:7:"integer";s:11:"create_time";s:8:"datetime";s:11:"update_time";s:8:"datetime";s:11:"delete_time";s:8:"datetime";s:6:"remark";s:6:"string";}s:10:"updateTime";s:11:"update_time";s:10:"createTime";s:11:"create_time";s:6:"suffix";s:0:"";s:8:"validate";s:0:"";s:4:"type";a:0:{}s:8:"readonly";a:0:{}s:6:"disuse";a:0:{}s:6:"hidden";a:0:{}s:7:"visible";a:0:{}s:6:"append";a:0:{}s:7:"mapping";a:0:{}s:6:"strict";b:1;s:8:"bindAttr";a:0:{}s:12:"autoRelation";a:0:{}s:18:"autoWriteTimestamp";b:1;s:10:"dateFormat";s:11:"Y-m-d H:i:s";s:2:"pk";s:2:"id";s:6:"exists";b:1;}s:6:"rights";a:46:{i:0;i:31;i:1;i:32;i:2;i:33;i:3;i:4;i:4;i:9;i:5;i:29;i:6;i:5;i:7;i:6;i:8;i:30;i:9;i:27;i:10;i:28;i:11;i:19;i:12;i:12;i:13;i:18;i:14;i:23;i:15;i:25;i:16;i:26;i:17;i:24;i:18;i:7;i:19;i:8;i:20;i:17;i:21;i:21;i:22;i:2;i:23;i:10;i:24;i:11;i:25;i:16;i:26;i:1;i:27;i:20;i:28;i:22;i:29;i:3;i:30;i:15;i:31;i:34;i:32;i:35;i:33;i:36;i:34;i:37;i:35;i:38;i:36;i:39;i:37;i:40;i:38;i:41;i:39;i:42;i:40;i:43;i:41;i:44;i:42;i:45;i:43;i:46;i:44;i:47;i:45;i:48;}} \ No newline at end of file