<?php
/**
 * 稼云SaaS平台 - 唯一入口文件 (PHP 8.0+ 兼容版)
 */

// ========== WAF 全局防护（恶意请求拦截/登录防爆破，需在一切逻辑之前）==========
if (PHP_SAPI !== 'cli') {
    $wafFile = __DIR__ . '/server/core/Waf.php';
    if (file_exists($wafFile)) {
        require_once $wafFile;
        try {
            \core\Waf::protect();
        } catch (\Throwable $wafE) {
            // WAF异常不阻断业务（fail-open），记录日志
            @error_log('WAF error: ' . $wafE->getMessage());
        }
    }

    // ========== v2.8.89 红蓝对抗加固：封禁IP快速拦截（开发环境豁免） ==========
    // 预加载必要类（此时autoloader尚未注册）
    $coreDir = __DIR__ . '/server/core';
    $securityFile = $coreDir . '/Security.php';
    $cacheFile = $coreDir . '/Cache.php';
    $banChecked = false;
    if (file_exists($securityFile) && file_exists($cacheFile)) {
        require_once $securityFile;
        require_once $cacheFile;
        $realIp = \core\Security::getInstance()->getRealIp();
        
        // v2.8.89 开发环境IP豁免（与BehaviorEngine保持一致）
        $isDevIp = false;
        if ($realIp !== '') {
            $trustedRanges = ['127.0.0.1', '::1', '0.0.0.0'];
            $trustedNetworks = ['10.', '172.16.', '172.17.', '172.18.', '172.19.', '172.20.', '172.21.', '172.22.', '172.23.', '172.24.', '172.25.', '172.26.', '172.27.', '172.28.', '172.29.', '172.30.', '172.31.', '192.168.'];
            if (in_array($realIp, $trustedRanges, true)) {
                $isDevIp = true;
            } else {
                foreach ($trustedNetworks as $prefix) {
                    if (strpos($realIp, $prefix) === 0) {
                        $isDevIp = true;
                        break;
                    }
                }
            }
        }
        
        // 只有非开发环境IP才检查封禁
        if (!$isDevIp && $realIp !== '') {
            $banKey = 'rl:ban:' . md5($realIp);
            $cache = \core\Cache::getInstance();
            $banUntil = intval($cache->get($banKey, 0));
            if ($banUntil > time()) {
                header('HTTP/1.1 429 Too Many Requests');
                header('Retry-After: ' . ($banUntil - time()));
                header('Content-Type: application/json; charset=utf-8');
                echo json_encode([
                    'code' => 429,
                    'msg' => '请求过于频繁，请稍后再试',
                    'data' => null,
                    'biz_code' => 42900,
                    'ban_remaining' => $banUntil - time(),
                ], JSON_UNESCAPED_UNICODE);
                exit;
            }
        }
        $banChecked = true;
    }

    // ========== v2.8.88 红蓝对抗加固：HTTP安全响应头 ==========
    // 移除PHP版本暴露
    @header_remove('X-Powered-By');

    // HTTPS强制跳转（仅生产环境，检测到HTTP则301跳转）
    $isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
        || (isset($_SERVER['SERVER_PORT']) && intval($_SERVER['SERVER_PORT']) === 443)
        || (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https');
    if (!$isHttps && PHP_SAPI !== 'cli-server') {
        // 开发环境(localhost/127.*)不强制跳转
        $host = strtolower(explode(':', (string)($_SERVER['HTTP_HOST'] ?? ''))[0]);
        $isLocal = in_array($host, ['localhost', '127.0.0.1'], true)
            || strpos($host, '127.') === 0
            || preg_match('/^(10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|192\.168\.)/', $host);
        if (!$isLocal) {
            $uri = $_SERVER['REQUEST_URI'] ?? '/';
            header('Location: https://' . ($_SERVER['HTTP_HOST'] ?? '') . $uri, true, 301);
            exit;
        }
    }

    // ========== v2.8.88 加固：生产环境错误处理（防止信息泄露） ==========
    set_error_handler(function ($severity, $message, $file, $line) {
        // 开发环境显示错误，生产环境只记录日志
        $host = strtolower(explode(':', (string)($_SERVER['HTTP_HOST'] ?? ''))[0]);
        $isDev = in_array($host, ['localhost', '127.0.0.1'], true)
            || strpos($host, '127.') === 0
            || preg_match('/^(10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|192\.168\.)/', $host);
        if (!$isDev) {
            @error_log("PHP Error [$severity]: $message in $file on line $line");
            // 严重错误才中断
            if (in_array($severity, [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR], true)) {
                if (!headers_sent()) {
                    http_response_code(500);
                    header('Content-Type: application/json; charset=utf-8');
                    header('X-Content-Type-Options: nosniff');
                }
                echo json_encode([
                    'code' => 500,
                    'msg' => '服务器内部错误',
                    'data' => null,
                    'biz_code' => 50000,
                    'timestamp' => time()
                ], JSON_UNESCAPED_UNICODE);
                exit;
            }
        }
        return false;
    });

    // 异常处理：确保所有异常不泄露敏感信息
    set_exception_handler(function ($exception) {
        $host = strtolower(explode(':', (string)($_SERVER['HTTP_HOST'] ?? ''))[0]);
        $isDev = in_array($host, ['localhost', '127.0.0.1'], true)
            || strpos($host, '127.') === 0
            || preg_match('/^(10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|192\.168\.)/', $host);
        
        @error_log('Unhandled Exception: ' . $exception->getMessage() . ' in ' . $exception->getFile() . ':' . $exception->getLine());
        
        if (!headers_sent()) {
            http_response_code(500);
            header('Content-Type: application/json; charset=utf-8');
            header('X-Content-Type-Options: nosniff');
            header('X-Frame-Options: SAMEORIGIN');
        }
        
        $msg = $isDev ? $exception->getMessage() : '服务器内部错误，请稍后重试';
        echo json_encode([
            'code' => 500,
            'msg' => $msg,
            'data' => null,
            'biz_code' => 50000,
            'timestamp' => time()
        ], JSON_UNESCAPED_UNICODE);
        exit;
    });

    // HSTS：强制HTTPS（1年，包含子域名）
    if ($isHttps) {
        header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
    }

    // 防点击劫持
    header('X-Frame-Options: SAMEORIGIN');

    // 防止MIME类型嗅探
    header('X-Content-Type-Options: nosniff');

    // XSS防护（老旧浏览器）
    header('X-XSS-Protection: 1; mode=block');

    // 内容安全策略（CSP）- 适度严格，不影响正常功能
    header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https: ws:; frame-ancestors 'self'; object-src 'none';");

    // Referrer策略
    header('Referrer-Policy: strict-origin-when-cross-origin');

    // ========== 权限策略
    header('Permissions-Policy: geolocation=(), microphone=(), camera=()');

    // ========== v2.8.88 行为分析引擎（蜜罐+扫描检测+攻击链关联）==========
    $beFile = __DIR__ . '/server/core/BehaviorEngine.php';
    if (file_exists($beFile)) {
        require_once $beFile;
        try {
            $beResult = \core\BehaviorEngine::analyze();
            if ($beResult['level'] >= 3) {
                // 蜜罐触发：直接返回拦截响应
                header('HTTP/1.1 403 Forbidden');
                header('Content-Type: application/json; charset=utf-8');
                header('X-Content-Type-Options: nosniff');
                echo json_encode([
                    'code' => 403,
                    'msg' => '请求被拒绝',
                    'data' => null,
                    'biz_code' => 40300,
                    'timestamp' => time()
                ], JSON_UNESCAPED_UNICODE);
                exit;
            }
        } catch (\Throwable $beE) {
            @error_log('BehaviorEngine error: ' . $beE->getMessage());
        }
    }
}

// ========== 基础准备 ==========
$requestUri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
// 支持Nginx/Apache rewrite的?s=参数
if (isset($_GET['s']) && !empty($_GET['s'])) {
    $requestPath = '/' . ltrim($_GET['s'], '/');
} else {
    $requestPath = parse_url($requestUri, PHP_URL_PATH);
}
if (!$requestPath) $requestPath = '/';
$requestMethod = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
$requestPath = rtrim($requestPath, '/');
if ($requestPath == '') $requestPath = '/';
if (strpos($requestPath, '/index.php') === 0) {
    $requestPath = substr($requestPath, 10);
    if ($requestPath == '') $requestPath = '/';
}

// CORS 跨域头（v2.8.56 安全修复【高危-H1】：禁止反射任意 Origin，仅放行配置白名单）
$corsCfg = array();
$corsCfgFile = __DIR__ . '/server/config/app.php';
if (is_file($corsCfgFile)) {
    $__cfg = require $corsCfgFile;
    if (is_array($__cfg) && isset($__cfg['cors'])) $corsCfg = $__cfg['cors'];
    unset($__cfg);
}
$origin = isset($_SERVER['HTTP_ORIGIN']) ? trim((string)$_SERVER['HTTP_ORIGIN']) : '';
$allowOrigin = false;
if ($origin !== '' && !empty($corsCfg['allowed_origins']) && is_array($corsCfg['allowed_origins'])) {
    foreach ($corsCfg['allowed_origins'] as $ao) {
        if (is_string($ao) && $ao !== '' && $ao !== '*' && strcasecmp(trim($ao), $origin) === 0) {
            $allowOrigin = true;
            break;
        }
    }
}
if ($allowOrigin) {
    header('Access-Control-Allow-Origin: ' . $origin);
    header('Vary: Origin');
} else {
    // 非白名单 Origin：不携带凭据返回，浏览器将按同源策略阻断跨域读取
    header('Access-Control-Allow-Origin: none');
}
header('Access-Control-Allow-Methods: ' . ($corsCfg['allowed_methods'] ?? 'GET, POST, PUT, DELETE, OPTIONS'));
header('Access-Control-Allow-Headers: ' . ($corsCfg['allowed_headers'] ?? 'Content-Type, Authorization, X-Requested-With, token, X-Tenant-Code, openid, tenant_id, factory_id, X-Tenant-Id, X-Bot-Id'));
header('Access-Control-Max-Age: 86400');
if ($requestMethod === 'OPTIONS') { http_response_code(204); exit; }

// 健康检查
if ($requestPath === '/api/ping' || $requestPath === '/ping') {
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode(array('code' => 1, 'msg' => 'pong', 'data' => array('time' => date('Y-m-d H:i:s'), 'php' => PHP_VERSION)));
    exit;
}

// ========== v2.8.89 调试接口：清除行为封禁（仅限开发环境或带密钥访问）==========
if ($requestPath === '/api/debug/clear-ban' && $requestMethod === 'GET') {
    header('Content-Type: application/json; charset=utf-8');
    
    // 简单的密钥验证（防止未授权清除封禁）
    $token = isset($_GET['token']) ? $_GET['token'] : '';
    $configFile = __DIR__ . '/server/config/app.php';
    $expectedToken = 'debug_clear_ban_' . date('Ymd'); // 临时token
    
    // 检查是否来自开发环境
    $host = strtolower(explode(':', (string)($_SERVER['HTTP_HOST'] ?? ''))[0]);
    $isDev = in_array($host, ['localhost', '127.0.0.1'], true)
        || strpos($host, '127.') === 0
        || preg_match('/^(10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|192\.168\.)/', $host);
    
    if (!$isDev && $token !== $expectedToken) {
        echo json_encode(['code' => 403, 'msg' => '无权限', 'data' => null]);
        exit;
    }
    
    // 加载BehaviorEngine并清除封禁
    $beFile = __DIR__ . '/server/core/BehaviorEngine.php';
    if (file_exists($beFile)) {
        require_once $beFile;
        
        $result = ['cleared' => 0, 'ip' => 'all'];
        
        // 如果指定了IP，只清除该IP
        $ip = isset($_GET['ip']) ? trim($_GET['ip']) : '';
        if ($ip !== '') {
            \core\BehaviorEngine::clearBan($ip);
            $result['cleared'] = 1;
            $result['ip'] = $ip;
            $result['msg'] = "已解除IP {$ip} 的封禁";
        } else {
            // 清除所有封禁
            $result['cleared'] = \core\BehaviorEngine::clearAllBans();
            $result['msg'] = '已清除所有封禁';
        }
        
        // 返回当前IP的封禁状态
        $securityFile = __DIR__ . '/server/core/Security.php';
        if (file_exists($securityFile)) {
            require_once $securityFile;
            $currentIp = \core\Security::getInstance()->getRealIp();
            $banInfo = \core\BehaviorEngine::checkBan($currentIp);
            $result['current_ip'] = $currentIp;
            $result['current_ban_status'] = $banInfo;
        }
        
        echo json_encode(['code' => 1, 'msg' => 'success', 'data' => $result], JSON_UNESCAPED_UNICODE);
    } else {
        echo json_encode(['code' => 0, 'msg' => 'BehaviorEngine not found', 'data' => null]);
    }
    exit;
}

// ========== v2.8.89 调试接口：查询封禁状态 ==========
if ($requestPath === '/api/debug/ban-status' && $requestMethod === 'GET') {
    header('Content-Type: application/json; charset=utf-8');
    
    $beFile = __DIR__ . '/server/core/BehaviorEngine.php';
    $securityFile = __DIR__ . '/server/core/Security.php';
    
    if (file_exists($beFile) && file_exists($securityFile)) {
        require_once $beFile;
        require_once $securityFile;
        
        $ip = isset($_GET['ip']) ? trim($_GET['ip']) : \core\Security::getInstance()->getRealIp();
        $status = \core\BehaviorEngine::checkBan($ip);
        $isTrusted = \core\BehaviorEngine::isTrustedIp($ip);
        
        echo json_encode([
            'code' => 1,
            'msg' => 'success',
            'data' => [
                'ip' => $ip,
                'is_trusted' => $isTrusted,
                'ban_status' => $status,
                'tips' => $isTrusted ? '当前IP为开发/内网IP，已完全豁免' : '当前IP为外网IP，受封禁保护',
            ]
        ], JSON_UNESCAPED_UNICODE);
    } else {
        echo json_encode(['code' => 0, 'msg' => 'BehaviorEngine or Security not found', 'data' => null]);
    }
    exit;
}

// ========== v1.0.6 域名守卫（内嵌快速通道）：/api/super/* 前缀按白名单拦截 ==========
// 框架路由组的 guardDomain 只能拦截走框架的请求；本文件内嵌处理了 super/login、
// super/system/config、super/tenants/*、super/licenses/*、super/operators/* 等
// 快速通道接口（不进框架），此处统一补齐域名级拦截，保证"仅 Super 专属域可访问总台"。
// （与 server/route/route.php guardDomain + server/config/saas.php domain_guard 三处配置联动）
$__host = strtolower(explode(':', (string)($_SERVER['HTTP_HOST'] ?? ''))[0]);
if ($__host !== ''
    && !in_array($__host, array('localhost', '127.0.0.1', '[::1]'), true)
    && strpos($__host, '127.') !== 0
    && !preg_match('/^(10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|192\.168\.)/', $__host)
    && (strpos($requestPath, '/api/super') === 0)) {
    $__gcfg = array();
    $__guardCfgFile = __DIR__ . '/server/config/saas.php';
    if (is_file($__guardCfgFile)) { $__gcfg = (array)require $__guardCfgFile; }
    $__g = isset($__gcfg['domain_guard']) && is_array($__gcfg['domain_guard']) ? $__gcfg['domain_guard'] : array();
    $__gEnable = !isset($__g['enable']) || $__g['enable'];  // 默认开启（与 saas.php 修复后的默认一致）
    if ($__gEnable) {
        $__superWl = isset($__g['super_whitelist']) && is_array($__g['super_whitelist']) && !empty($__g['super_whitelist'])
            ? array_map('strtolower', array_map('trim', $__g['super_whitelist']))
            : array('super.dir16.com');
        $__allow = false;
        foreach ($__superWl as $__d) {
            if ($__d === '') continue;
            if ($__d === $__host) { $__allow = true; break; }
            if (strpos($__d, '.') === 0 && substr($__host, -strlen($__d)) === $__d) { $__allow = true; break; }
        }
        if (!$__allow) {
            header('HTTP/1.1 403 Forbidden', true, 403);
            header('Content-Type: application/json; charset=utf-8');
            echo json_encode(array(
                'code' => 40008,
                'msg'  => '当前域名(' . $__host . ')无权访问官方总台。为保障安全，总台仅允许通过白名单域名访问。请切换到正确域名：' . implode('、', array_values(array_filter($__superWl))),
                'data' => array('error_type' => 'domain_not_allowed', 'current_host' => $__host, 'allowed_hosts' => $__superWl),
            ), JSON_UNESCAPED_UNICODE);
            exit;
        }
    }
}
// ========== v1.0.6 域名守卫 END ==========

// 错误设置
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
if (PHP_SAPI !== 'cli') { ob_start(); }

// ROOT_PATH = 本文件所在目录 = 网站根目录（绝对可靠！）
// __DIR__是PHP魔术常量，永远返回当前文件所在目录的绝对路径，不受include/chdir影响
// 注意：public/index.php已被修改为不再预定义ROOT_PATH，这里的define一定会生效
if (!defined('ROOT_PATH')) {
    define('ROOT_PATH', __DIR__);
} elseif (!is_dir(ROOT_PATH . '/server')) {
    // 极端情况：外部已define但路径错误，用全局变量覆盖
    $GLOBALS['_REAL_ROOT'] = __DIR__;
}

// 提供可靠的根路径获取函数（优先使用__DIR__确保正确）
if (!function_exists('emj_root')) {
    function emj_root($path = '') {
        $r = isset($GLOBALS['_REAL_ROOT']) ? $GLOBALS['_REAL_ROOT'] : ROOT_PATH;
        if (!is_dir($r . '/server')) { $r = __DIR__; }
        return $path ? $r . DIRECTORY_SEPARATOR . ltrim($path, '/\\') : $r;
    }
}

// Fatal Error兜底
register_shutdown_function(function () {
    $error = error_get_last();
    if ($error && in_array($error['type'], array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR))) {
        while (ob_get_level() > 0) { @ob_end_clean(); }
        if (!headers_sent()) {
            header('Content-Type: application/json; charset=utf-8');
            header('Access-Control-Allow-Origin: none'); // v2.8.56：错误兜底不反射任意域
            http_response_code(500);
        }
        $logDir = ROOT_PATH . '/server/runtime/logs';
        if (!is_dir($logDir)) { @mkdir($logDir, 0755, true); }
        $errFile = isset($error['file']) ? basename($error['file']) : 'unknown';
        $errLine = isset($error['line']) ? $error['line'] : 0;
        $errMsg = isset($error['message']) ? $error['message'] : '';
        // v2.8.33+ 安全加固（L4）：日志脱敏，防止密钥/手机号/token 落盘
        // （FATAL回调可能早于helpers.php加载，函数存在时再脱敏）
        $safeMsg = function_exists('emj_redact_secret') ? emj_redact_secret($errMsg) : $errMsg;
        $logMsg = date('Y-m-d H:i:s') . " FATAL: " . $safeMsg . " in " . $errFile . ":" . $errLine;
        @file_put_contents($logDir . '/error_' . date('Y-m-d') . '.log', $logMsg . "\n", FILE_APPEND);
        echo json_encode(array('code' => 0, 'msg' => '服务器错误: ' . $errFile . '(' . $errLine . ')', 'data' => null));
    }
});

// ========== 辅助函数 ==========
function emj_json($code, $msg, $data = null, $httpCode = 200) {
    $GLOBALS['_emj_last_code'] = $code; // v2.12.3 供云接口限流 shutdown 回调判定成败
    while (ob_get_level() > 0) { @ob_end_clean(); }
    header('Content-Type: application/json; charset=utf-8');
    http_response_code($httpCode);
    echo json_encode(array('code' => $code, 'msg' => $msg, 'data' => $data));
    exit;
}

function emg($key, $default = null) {
    static $secret = null;
    static $dbpdo = null;
    if ($key === 'secret') {
        if ($secret !== null) return $secret;
        $secret = null; // v2.8.56 安全加固：禁止硬编码默认 JWT 密钥，仅允许读取配置文件
        $f = emj_root('server/config/app.php');
        if (file_exists($f)) {
            $cfg = require $f;
            if (isset($cfg['jwt']['secret']) && !empty($cfg['jwt']['secret'])) $secret = $cfg['jwt']['secret'];
        }
        return $secret;
    }
    if ($key === 'db') {
        if ($dbpdo !== null) return $dbpdo;
        // 多路径尝试查找数据库配置文件
        $f = emj_root('server/config/database.php');
        $tryPaths = array($f, __DIR__ . '/server/config/database.php', dirname(__DIR__) . '/server/config/database.php');
        $foundPath = null;
        foreach ($tryPaths as $p) { if ($p && file_exists($p)) { $foundPath = $p; break; } }
        if (!$foundPath) {
            emj_json(0, '数据库配置文件不存在，已尝试路径: ' . implode(' | ', array_filter($tryPaths)));
        }
        $dbCfg = require $foundPath;
        $conn = isset($dbCfg['connections']['mysql']) ? $dbCfg['connections']['mysql'] : $dbCfg;
        $host = isset($conn['host']) ? $conn['host'] : '127.0.0.1';
        $port = isset($conn['port']) ? $conn['port'] : 3306;
        $dbname = isset($conn['database']) ? $conn['database'] : '';
        $user = isset($conn['username']) ? $conn['username'] : '';
        $pass = isset($conn['password']) ? $conn['password'] : '';
        try {
            $dsn = "mysql:host={$host};port={$port};dbname={$dbname};charset=utf8mb4";
            $dbpdo = new PDO($dsn, $user, $pass, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
            return $dbpdo;
        } catch (Exception $e) {
            emj_json(0, '数据库连接失败: ' . $e->getMessage());
        }
    }
    return $default;
}

// 可靠的缓存目录（优先server/runtime/cache，不存在则用系统临时目录）
if (!function_exists('emj_cache_dir')) {
    function emj_cache_dir($sub = '') {
        $dirs = array();
        if (defined('ROOT_PATH')) $dirs[] = ROOT_PATH . '/server/runtime/cache';
        $dirs[] = __DIR__ . '/server/runtime/cache';
        $dirs[] = sys_get_temp_dir() . '/slsaas_cache';
        foreach ($dirs as $d) {
            if (!is_dir($d)) { @mkdir($d, 0755, true); }
            if (is_dir($d) && is_writable($d)) { return $sub ? $d . '/' . ltrim($sub, '/') : $d; }
        }
        return sys_get_temp_dir();
    }
}

function emj_verify_captcha($captchaId, $captchaCode) {
    if (empty($captchaId) || empty($captchaCode)) return false;
    $file = emj_cache_dir('captcha_' . $captchaId . '.php');
    if (!file_exists($file)) return false;
    $content = file_get_contents($file);
    $json = substr($content, strlen('<?php exit;?>'));
    $data = json_decode($json, true);
    @unlink($file);
    if (!$data) return false;
    $t = isset($data['time']) ? $data['time'] : 0;
    if ($t + 300 < time()) return false;
    $c = isset($data['code']) ? $data['code'] : '';
    return strtolower($captchaCode) === strtolower($c);
}

function emj_b64u($data) {
    return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}

// v2.8.96 S-3：按端派生启用时刻（2026-08-20 18:50:00 = 本版部署时刻，与主框架 core/Jwt.php 一致），
// 此后签发必须用派生 secret 验签，原始 secret 从上线瞬间起即无法伪造新 token
define('EMJ_JWT_SCOPE_SPLIT_TS', 1787223000);

function emj_jwt_scope_of($payload) {
    if (isset($payload['type']) && is_string($payload['type']) && $payload['type'] !== '') return 't:' . $payload['type'];
    if (!empty($payload['is_super']) || isset($payload['super_admin_id'])) return 'f:super';
    if (!empty($payload['is_platform']) || isset($payload['operator_id'])) return 'f:platform';
    if (!empty($payload['is_tenant']) || isset($payload['tenant_id'])) return 'f:tenant';
    if (isset($payload['user_id'])) return 'f:user';
    return 'default';
}

function emj_jwt_encode($payload) {
    $secret = emg('secret');
    $header = array('typ' => 'JWT', 'alg' => 'HS256');
    $h = emj_b64u(json_encode($header));
    $p = emj_b64u(json_encode($payload));
    // v2.8.96 S-3：按端派生签名密钥（原始 secret 泄露仅影响单端）
    $derived = hash_hmac('sha256', 'jwt_scope:' . emj_jwt_scope_of($payload), $secret);
    $sig = hash_hmac('sha256', "$h.$p", $derived, true);
    $s = emj_b64u($sig);
    return "$h.$p.$s";
}

function emj_jwt_decode($token) {
    $parts = explode('.', $token);
    if (count($parts) !== 3) return false;
    $secret = emg('secret');
    if (!$secret) return false; // 安全失败：密钥未配置禁止校验
    $pr = strtr($parts[1], '-_', '+/');
    $pr = base64_decode($pr . str_repeat('=', (4 - strlen($pr) % 4) % 4));
    $payload = json_decode($pr, true);
    if (!$payload) return false;
    $sr = strtr($parts[2], '-_', '+/');
    $sr = base64_decode($sr . str_repeat('=', (4 - strlen($sr) % 4) % 4));
    // v2.8.96 S-3：先按端派生 secret 验签
    $derived = hash_hmac('sha256', 'jwt_scope:' . emj_jwt_scope_of($payload), $secret);
    $es = hash_hmac('sha256', "$parts[0].$parts[1]", $derived, true);
    if (!hash_equals($es, $sr)) {
        // 存量兼容：仅切换时刻前签发的旧 token 回退原始 secret 验签（伪造新 token 的 iat 无法进入此分支）
        $iat = isset($payload['iat']) ? intval($payload['iat']) : 0;
        if ($iat <= 0 || $iat >= EMJ_JWT_SCOPE_SPLIT_TS) return false;
        $esOld = hash_hmac('sha256', "$parts[0].$parts[1]", $secret, true);
        if (!hash_equals($esOld, $sr)) return false;
    }
    if (isset($payload['exp']) && $payload['exp'] < time()) return false;
    return $payload;
}

function emj_get_token() {
    $ah = isset($_SERVER['HTTP_AUTHORIZATION']) ? $_SERVER['HTTP_AUTHORIZATION'] : '';
    if (stripos($ah, 'Bearer ') === 0) return substr($ah, 7);
    if (isset($_GET['token'])) return $_GET['token'];
    if (isset($_POST['token'])) return $_POST['token'];
    return '';
}

function emj_verify_pwd($password, $hash) {
    if (empty($hash)) return false;
    if (function_exists('password_verify') && password_verify($password, $hash)) return true;
    if (strlen($hash) === 32 && md5($password) === strtolower($hash)) return true;
    if ($hash === $password) return true;
    return false;
}

function emj_val($arr, $key, $default = '') {
    return isset($arr[$key]) ? $arr[$key] : $default;
}

/**
 * 独立部署场景：远程校验框架授权码（调用官方平台 /api/license/verify）
 * 返回 null 表示无法连接官方平台或授权码无效
 */
function emj_remote_license_verify($licenseKey, $domain = '') {
    $cfgFile = emj_root('server/config/license.php');
    $cfg = file_exists($cfgFile) ? require $cfgFile : array();
    $api = isset($cfg['platform_api']) ? $cfg['platform_api'] : '';
    if (!$api) return null;
    $api = rtrim($api, '/') . '/verify';
    $payload = json_encode(array('license_key' => $licenseKey, 'domain' => $domain, 'timestamp' => time()));
    $ctx = stream_context_create(array('http' => array(
        'method' => 'POST',
        'header' => "Content-Type: application/json\r\nConnection: close\r\n",
        'content' => $payload,
        'timeout' => 10,
    )));
    $resp = @file_get_contents($api, false, $ctx);
    if (!$resp) return null;
    $data = json_decode($resp, true);
    if (!$data || intval($data['code']) !== 1) return null;
    return $data['data'];
}

// ========== 验证码 ==========
if (($requestPath === '/api/captcha' || $requestPath === '/captcha') && $requestMethod === 'GET') {
    $width = 120; $height = 42;
    $chars = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
    $code = '';
    for ($i = 0; $i < 4; $i++) { $code .= $chars[mt_rand(0, strlen($chars) - 1)]; }
    $captchaId = md5(uniqid(mt_rand(), true) . microtime(true));
    $cacheFile = emj_cache_dir('captcha_' . $captchaId . '.php');
    @file_put_contents($cacheFile, '<?php exit;?>' . json_encode(array('code' => strtolower($code), 'time' => time())));
    // 顺带清理超过10分钟的过期验证码文件，防止缓存目录无限累积
    $cacheDir = dirname($cacheFile);
    if (is_dir($cacheDir)) {
        $expired = glob($cacheDir . '/captcha_*.php');
        if ($expired) {
            foreach ($expired as $ef) {
                if (@filemtime($ef) && (time() - @filemtime($ef)) > 600) { @unlink($ef); }
            }
        }
    }
    // v2.8.68 修复：原 SVG data URI 验证码在微信内置浏览器（X5/XWEB 内核）部分版本
    // 渲染兼容性差导致不显示，统一改为 PNG（GD 库）输出（与框架 Security::renderCaptchaPng 一致）；
    // GD 不可用时退化为 SVG 兜底，保证接口结构不报错。验证码存储/校验链路（emj_cache_dir 文件）不变。
    $pngImage = '';
    if (function_exists('imagecreatetruecolor')) {
        $im = @imagecreatetruecolor($width, $height);
        if ($im) {
            // 背景
            $bg = imagecolorallocate($im, 245, 247, 250);
            imagefilledrectangle($im, 0, 0, $width, $height, $bg);
            // 干扰线（浅色）
            for ($i = 0; $i < 4; $i++) {
                $c = imagecolorallocate($im, mt_rand(180, 220), mt_rand(180, 220), mt_rand(180, 220));
                imageline($im, mt_rand(0, $width), mt_rand(0, $height), mt_rand(0, $width), mt_rand(0, $height), $c);
            }
            // 干扰点
            for ($i = 0; $i < 30; $i++) {
                $c = imagecolorallocate($im, mt_rand(150, 200), mt_rand(150, 200), mt_rand(150, 200));
                imagesetpixel($im, mt_rand(0, $width), mt_rand(0, $height), $c);
            }
            // 字符（与框架 Security::renderCaptchaPng 主题色一致；优先 TTF 旋转，无字体则用 GD 内置字体）
            $palette = array(array(102,126,234),array(118,75,162),array(245,87,108),array(79,172,254),array(17,153,142));
            $fontFile = '';
            $fontCandidates = array(
                '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',
                '/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf',
                '/usr/share/fonts/dejavu/DejaVuSans-Bold.ttf',
                '/usr/share/fonts/liberation/LiberationSans-Bold.ttf',
                'C:/Windows/Fonts/arialbd.ttf',
            );
            foreach ($fontCandidates as $f) { if (is_file($f)) { $fontFile = $f; break; } }
            $len = strlen($code);
            for ($i = 0; $i < $len; $i++) {
                $ch = $code[$i]; $x = 18 + $i * 24;
                $rot = mt_rand(-15, 15); $col = $palette[mt_rand(0, count($palette) - 1)];
                $color = imagecolorallocate($im, $col[0], $col[1], $col[2]);
                if ($fontFile && function_exists('imagettftext')) {
                    imagettftext($im, mt_rand(20, 26), $rot, $x, 31, $color, $fontFile, $ch);
                } else {
                    imagestring($im, 5, $x - 2, 14, $ch, $color);
                }
            }
            ob_start();
            imagepng($im);
            $pngImage = ob_get_clean();
            imagedestroy($im);
        }
    }
    if ($pngImage !== '') {
        emj_json(1, 'success', array('captcha_id' => $captchaId, 'image' => 'data:image/png;base64,' . base64_encode($pngImage)));
    }
    // GD 不可用兜底：SVG（兼容旧环境）
    $svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' . $width . '" height="' . $height . '" viewBox="0 0 ' . $width . ' ' . $height . '">';
    $svg .= '<rect width="100%" height="100%" fill="#f5f7fa"/>';
    for ($i = 0; $i < 5; $i++) {
        $x1 = mt_rand(0,$width); $y1 = mt_rand(0,$height); $x2 = mt_rand(0,$width); $y2 = mt_rand(0,$height);
        $c = sprintf('#%02x%02x%02x', mt_rand(180,220), mt_rand(180,220), mt_rand(180,220));
        $svg .= '<line x1="'.$x1.'" y1="'.$y1.'" x2="'.$x2.'" y2="'.$y2.'" stroke="'.$c.'" stroke-width="1"/>';
    }
    $colors = array('#667eea','#764ba2','#f5576c','#4facfe','#11998e','#e91e63');
    for ($i = 0; $i < 4; $i++) {
        $ch = $code[$i]; $x = 18 + $i * 24; $y = mt_rand(26, 32);
        $rot = mt_rand(-20, 20); $col = $colors[mt_rand(0, count($colors)-1)];
        $fs = mt_rand(20, 26);
        $svg .= '<text x="'.$x.'" y="'.$y.'" font-family="Arial,sans-serif" font-size="'.$fs.'" font-weight="bold" fill="'.$col.'" transform="rotate('.$rot.' '.$x.' '.($y-10).')">'.$ch.'</text>';
    }
    $svg .= '</svg>';
    emj_json(1, 'success', array('captcha_id' => $captchaId, 'image' => 'data:image/svg+xml;base64,' . base64_encode($svg)));
}

// ========== 独立部署简约模式守卫（v2.12.1）==========
// 独立部署包（运营商装的那套）已物理剔除 super/saasadmin2026/admindir26/h5 及
// 菲录等业务 apps，仅保留 apps/oem 升级后台。此模式下仅放行：升级登录、升级接口、
// 授权激活/状态、验证码、健康检查、升级页静态资源；其余一切请求（含平台管理与
// 内部应用）一律 404，杜绝平台后台目录与信息泄露。
$__envFile = __DIR__ . '/server/core/Env.php';
if (is_file($__envFile)) {
    require_once $__envFile;
    \core\Env::load(); // 提前加载 server/.env，使 getenv('DEPLOY_MODE') 在守卫处可用
}
$isStandaloneDeploy = false;
$__dm = getenv('DEPLOY_MODE');
if ($__dm === 'standalone') {
    $isStandaloneDeploy = true;
} elseif (!is_dir(__DIR__ . '/super') && !is_dir(__DIR__ . '/admindir26') && !is_dir(__DIR__ . '/saasadmin2026')) {
    // 兜底：super/saasadmin2026/admindir26 三个平台后台目录全部不存在即视为独立部署
    $isStandaloneDeploy = true;
}
if ($isStandaloneDeploy) {
    // 升级后台/登录页默认入口
    if ($requestPath === '/' || $requestPath === '') {
        header('Location: /apps/oem/login.html', true, 302);
        exit;
    }
    $standaloneAllow = array(
        '/apps/oem', '/apps/sticker-ai', '/apps/qn-browser',
        '/api/super/login', '/api/super/auth/login',
        '/api/super/me', '/api/super/auth/me',
        '/api/super/framework-update',
        '/api/super/license', '/api/license/status', '/api/license/activate',
        '/api/captcha', '/captcha', '/api/ping', '/ping',
        // v2.12.2 运营商独立运营：运营商业务（登录/兑换应用授权码/租户管理/发码/贴牌/订单/升级）
        '/api/platform',
        // v2.12.5 运营商应用包分发链路（应用中心检查/安装/升级，operator JWT 鉴权在控制器层）
        '/api/app-package',
        // v2.12.5 框架升级移入运营商后台（手动确认，不自动更新）
        '/api/framework-update',
        // 租户业务（登录/应用市场/订单/贴牌设置）
        '/api/tenant',
        // 公开业务：受邀注册/支付回调/品牌配置/门户入口/授权提示（前台与后台均依赖）
        // [修复 v1.0.7 T8] 补上 /api/public 白名单（登录页/注册页 public/login-config、public/agreement、public/dict/options）
        // [修复 v1.0.7 Issue3 前台应用空] 补 /api/home（应用中心首页）、/api/portal（统一门户登录分流）、/api/license（授权提示notice/status/activate）、/api/app-center、/api/apps（公开应用列表）
        '/api/invite', '/api/pay', '/api/brand', '/api/common', '/api/public', '/api/home', '/api/portal', '/api/license', '/api/app-center', '/api/apps', '/api/sticker-ai', '/api/qnb',
        // 运营商后台/租户后台/前台静态入口
        '/saasadmin2026', '/admindir26', '/h5',
    );
    // 独立部署禁用清单（前缀匹配）：禁止自助注册运营商子账号（一个部署仅站长一个运营商）
    $standaloneDeny = array(
        '/api/platform/auth/register',
    );
    $denied = false;
    foreach ($standaloneDeny as $dp) {
        if ($requestPath === $dp || strpos($requestPath, rtrim($dp, '/') . '/') === 0) {
            $denied = true;
            break;
        }
    }
    $allowed = false;
    if (!$denied) {
        foreach ($standaloneAllow as $p) {
            if ($requestPath === $p || strpos($requestPath, rtrim($p, '/') . '/') === 0) {
                $allowed = true;
                break;
            }
        }
    }
    if (!$allowed) {
        if (strpos($requestPath, '/api/') === 0 || strpos($requestPath, '/captcha') === 0) {
            emj_json(0, '资源不存在', null, 404);
        }
        http_response_code(404);
        header('Content-Type: text/html; charset=utf-8');
        echo '<!DOCTYPE html><html><head><meta charset="utf-8"><title>404</title></head><body><h1 style="text-align:center;margin-top:80px;color:#8890a0">404 Not Found</h1><p style="text-align:center;color:#b0b6c4">请求的资源不存在</p></body></html>';
        exit;
    }
}

// ========== 品牌配置 ==========
if ($requestPath === '/api/brand/config' || $requestPath === '/api/common/config') {
    $saasConfig = array();
    $cf = ROOT_PATH . '/server/config/saas.php';
    if (file_exists($cf)) $saasConfig = require $cf;
    $brand = isset($saasConfig['brand']) ? $saasConfig['brand'] : array();
    $cfg = array(
        'system_name' => '稼云SaaS平台',
        'super_admin_name' => '稼云SaaS总后台',
        'platform_admin_name' => '超管运营商后台',
        'tenant_admin_name' => '商户后台',
        'logo_text' => '⚡',
        'super_theme' => '#722ed1',
        'platform_theme' => '#1890ff',
        'tenant_theme' => '#52c41a',
    );
    foreach ($brand as $k => $v) { $cfg[$k] = $v; }
    $cfg['super_admin_path'] = isset($saasConfig['super_admin_path']) ? $saasConfig['super_admin_path'] : 'super';
    $cfg['admin_path'] = isset($saasConfig['admin_path']) ? $saasConfig['admin_path'] : 'saasadmin2026';
    $cfg['tenant_admin_path'] = isset($saasConfig['tenant_admin_path']) ? $saasConfig['tenant_admin_path'] : 'admindir26';
    // [修复 v1.0.7 Issue2 Super报错] brand/config补齐部署模式/无总台/回退入口，前端据此切换守卫与提示
    $deployMode = (string)(getenv('DEPLOY_MODE') ?: 'cloud');
    $cfg['deploy_mode'] = $deployMode;
    $cfg['no_super_admin'] = ($deployMode === 'standalone');
    $cfg['fallback_admin_entry'] = ($deployMode === 'standalone') ? '/' . trim($cfg['admin_path'], '/') . '/' : '/' . trim($cfg['super_admin_path'], '/') . '/';
    // v2.11.2 修复：下发 Super 总台唯一绝对入口（saas 域顶栏"进入总后台"按钮跳转到唯一域名 super.dir16.com/super/）
    $_superHost = 'https://super.dir16.com';
    $_superRel = '/' . trim($cfg['super_admin_path'], '/') . '/';
    $cfg['super_admin_url'] = $_superHost . $_superRel;
    $cfg['platform_admin_url'] = 'https://saas.dir16.com/' . trim($cfg['admin_path'], '/') . '/';
    $cfg['tenant_admin_url'] = 'https://saas.dir16.com/' . trim($cfg['tenant_admin_path'], '/') . '/';
    // v2.11.2 总后台去租户管理：官方总台也把 super/tenants 打入 blocked_routes，前端收到后自动隐藏菜单
    $_superAlwaysBlocked = array('super/tenants');
    $_oldBlocked = ($deployMode === 'standalone') ? array('super') : array();
    $cfg['blocked_routes'] = array_values(array_unique(array_merge($_oldBlocked, $_superAlwaysBlocked)));
    emj_json(1, 'success', $cfg);
}

// ========== v1.0.1 公共读取接口（登录页/注册页无需鉴权）[与 server/route/route.php 保持同步] ==========
// ---- public/login-config（登录方式开关+注册开关，供 saasadmin2026/admindir26 登录页与注册页读取）----
if ($requestPath === '/api/public/login-config' && $requestMethod === 'GET') {
    $def = array(
        'password' => 1, 'sms' => 0, 'qrcode' => 0,
        'force_bind_mobile' => 0, 'register_enabled' => 1, 'mobile_login_only' => 0,
        'captcha_enabled' => 1, 'captcha_on_fail_n' => 3, 'updated_at' => 0,
    );
    try {
        $pdo = emg('db');
        $stmt = $pdo->prepare("SELECT config_value FROM saas_configs WHERE config_key = ? LIMIT 1");
        $stmt->execute(array('platform_login_config'));
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if ($row && !empty($row['config_value'])) {
            $d = json_decode($row['config_value'], true);
            if (is_array($d)) $def = array_merge($def, $d);
        }
    } catch (Exception $e) {}
    $def['password'] = 1; // 强制开启，不可关闭
    emj_json(1, 'success', $def);
}
// ---- public/agreement（协议：agreement_type = service | privacy | refund | copyright | about | operator）----
if ($requestPath === '/api/public/agreement' && $requestMethod === 'GET') {
    $type = isset($_GET['agreement_type']) ? trim((string)$_GET['agreement_type']) : 'service';
    if (!in_array($type, array('service','privacy','refund','copyright','about','operator'))) $type = 'service';
    $titleMap = array(
        'service'   => '用户服务协议',
        'privacy'   => '隐私政策',
        'refund'    => '退款政策',
        'copyright' => '版权声明',
        'about'     => '关于我们',
        'operator'  => '运营商入驻协议',
    );
    try {
        $pdo = emg('db');
        $stmt = $pdo->prepare("SELECT id, agreement_type, title, content, version, publish_time FROM saas_agreements WHERE agreement_type = ? AND status = 1 ORDER BY id DESC LIMIT 1");
        $stmt->execute(array($type));
        $a = $stmt->fetch(PDO::FETCH_ASSOC);
        if ($a) emj_json(1, 'success', array(
            'id' => intval($a['id']),
            'agreement_type' => (string)$a['agreement_type'],
            'title' => (string)$a['title'],
            'content' => (string)$a['content'],
            'version' => (string)$a['version'],
            'publish_time' => intval($a['publish_time']),
        ));
    } catch (Exception $e) {}
    emj_json(1, 'success', array(
        'agreement_type' => $type,
        'title' => $titleMap[$type],
        'content' => '',
        'version' => '',
        'publish_time' => 0,
        'empty' => true,
    ));
}
// ---- public/dict/options（字典公开读，仅返回启用项，options 数组）----
if ($requestPath === '/api/public/dict/options' && $requestMethod === 'GET') {
    $code = isset($_GET['type_code']) ? trim((string)$_GET['type_code']) : '';
    $options = array();
    if ($code !== '') {
        try {
            $pdo = emg('db');
            $stmt = $pdo->prepare("SELECT option_value, option_label, option_ext FROM saas_dict_options WHERE dict_code = ? AND status = 1 ORDER BY sort_order ASC, id ASC");
            $stmt->execute(array($code));
            while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
                $ext = null;
                if (!empty($r['option_ext'])) {
                    $extJ = json_decode($r['option_ext'], true);
                    $ext = is_array($extJ) ? $extJ : (string)$r['option_ext'];
                }
                $options[] = array(
                    'value' => (string)$r['option_value'],
                    'label' => (string)$r['option_label'],
                    'ext'   => $ext,
                );
            }
        } catch (Exception $e) {}
    }
    emj_json(1, 'success', array('type_code' => $code, 'options' => $options));
}

// ========== 框架授权码远程校验（独立部署的运营商端激活时调用）==========
if (($requestPath === '/api/license/verify' || $requestPath === '/api/license') && $requestMethod === 'POST') {
    $raw = file_get_contents('php://input');
    $input = json_decode($raw, true);
    if (!$input) $input = $_POST;
    $licenseKey = trim(emj_val($input, 'license_key'));
    if (empty($licenseKey)) emj_json(0, '请输入授权码');
    try {
        $pdo = emg('db');
        $stmt = $pdo->prepare("SELECT license_key, license_type, max_tenants, duration_days, expire_time, domain, domains, status, remark FROM saas_licenses WHERE license_key = ? LIMIT 1");
        $stmt->execute(array($licenseKey));
        $license = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$license) emj_json(0, '授权码不存在');
        if (intval($license['status']) !== 0) emj_json(0, '授权码已被使用或已作废');
        emj_json(1, 'success', array(
            'license_key' => $license['license_key'],
            'license_type' => $license['license_type'],
            'max_tenants' => intval($license['max_tenants']),
            'duration_days' => intval($license['duration_days']),
            'expire_time' => $license['expire_time'],
            'domain' => $license['domain'],
            'domains' => $license['domains'],
            'remark' => $license['remark'],
        ));
    } catch (Exception $e) {
        emj_json(0, '验证服务异常: ' . $e->getMessage());
    }
}

// ========== 云授权接口（v2.12.2 独立部署占用式远程激活/兑换）==========
// 仅官方平台（saas.dir16.com）提供；独立部署端守卫拦截本机 /api/cloud/*（不对外提供云接口）。
// 鉴权：access_token 与框架升级信道共用（FW_UPGRADE_TOKEN）；占用语义：激活/兑换即标记已用，
// 框架授权码绑定部署域名（同域名可重复激活=重装幂等，跨域名拒绝=防一码多用）；
// 应用授权码一码一用（同站点重复兑换幂等放行，防本地落库失败后重试死锁）。
if (strpos($requestPath, '/api/cloud/') === 0 && $requestMethod === 'POST') {
    $rawCloud = file_get_contents('php://input');
    $cloudInput = json_decode($rawCloud, true);
    if (!$cloudInput) $cloudInput = $_POST;
    $cloudToken = trim(emj_val($cloudInput, 'access_token'));
    $cloudExpected = getenv('FW_UPGRADE_TOKEN');
    if ($cloudExpected === false || $cloudExpected === '') {
        $cloudExpected = 'jiayun-fw-2026'; // 与独立部署端 RemoteFrameworkUpgradeService::sourceToken() 默认值一致
    }
    if ($cloudToken === '' || !hash_equals((string)$cloudExpected, $cloudToken)) {
        emj_json(0, '来源令牌无效', null, 401);
    }
    // v2.12.3 云接口 IP 限流：失败累计 10 次锁定 10 分钟（文件计数，防令牌泄漏后暴力探测授权码）
    $cloudRlFile = __DIR__ . '/server/runtime/cache/cloudrl_' . md5(isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0') . '.json';
    $cloudRlNow = time();
    $cloudRl = is_file($cloudRlFile) ? json_decode((string)file_get_contents($cloudRlFile), true) : null;
    if (is_array($cloudRl) && !empty($cloudRl['lock_until']) && $cloudRlNow < intval($cloudRl['lock_until'])) {
        emj_json(0, '请求过于频繁，请' . (intval($cloudRl['lock_until']) - $cloudRlNow) . '秒后重试', null, 429);
    }
    $cloudRl = ['fails' => 0, 'lock_until' => 0];
    register_shutdown_function(function() use ($cloudRlFile, &$cloudRl) {
        // 失败响应（code=0）才累计：成功激活/兑换与幂等放行不计数
        if (isset($GLOBALS['_emj_last_code']) && intval($GLOBALS['_emj_last_code']) === 0) {
            $prev = is_file($cloudRlFile) ? json_decode((string)@file_get_contents($cloudRlFile), true) : null;
            $fails = (is_array($prev) ? intval($prev['fails']) : 0) + 1;
            $cloudRl = $fails >= 10
                ? array('fails' => $fails, 'lock_until' => time() + 600)
                : array('fails' => $fails, 'lock_until' => 0);
        }
        try { @file_put_contents($cloudRlFile, json_encode($cloudRl), LOCK_EX); } catch (\Exception $e) {}
    });
    // 部署站点域名规范化：小写/去端口/去 www
    $cloudHost = strtolower(trim((string)emj_val($cloudInput, 'domain', emj_val($cloudInput, 'site_domain'))));
    $cloudHost = preg_replace('/^www\./', '', explode(':', $cloudHost)[0]);

    // ---- 云接口1：框架授权码远程激活（占用式+域名绑定）----
    if ($requestPath === '/api/cloud/license/activate') {
        $licenseKey = trim((string)emj_val($cloudInput, 'license_key'));
        if ($licenseKey === '') emj_json(0, '请输入授权码');
        try {
            $pdo = emg('db');
            $pdo->beginTransaction();
            $stmt = $pdo->prepare("SELECT * FROM saas_licenses WHERE license_key = ? LIMIT 1 FOR UPDATE");
            $stmt->execute(array($licenseKey));
            $lic = $stmt->fetch(PDO::FETCH_ASSOC);
            if (!$lic) { $pdo->rollBack(); emj_json(0, '授权码不存在，请向平台方获取'); }
            if (intval($lic['status']) === 2) { $pdo->rollBack(); emj_json(0, '授权码已被作废，请联系平台方'); }
            $expireStr = (string)$lic['expire_time'];
            if ($expireStr !== '' && $expireStr !== '2099-12-31 23:59:59' && $expireStr !== '0000-00-00 00:00:00') {
                $ets = is_numeric($expireStr) ? intval($expireStr) : strtotime($expireStr);
                if ($ets > 0 && $ets < time()) { $pdo->rollBack(); emj_json(0, '授权码已过期，请联系平台方续期'); }
            }
            if (intval($lic['status']) === 1) {
                // 已激活：同域名幂等放行（重装/重激活），跨域名拒绝（防一码多用）
                $bound = strtolower(trim((string)$lic['domain']));
                $boundNorm = preg_replace('/^www\./', '', explode(':', $bound)[0]);
                if ($cloudHost === '' || $boundNorm !== $cloudHost) {
                    $pdo->rollBack();
                    emj_json(0, '该授权码已被其他站点（' . $bound . '）激活，如需换绑请联系平台方');
                }
            } else {
                // 未使用 → 占用：绑定部署域名+标记已激活
                $pdo->prepare("UPDATE saas_licenses SET status = 1, domain = ?, activated_at = NOW(), updated_at = NOW() WHERE id = ?")
                    ->execute(array($cloudHost, $lic['id']));
            }
            $pdo->commit();
            emj_json(1, '激活成功', array(
                'license_key' => $lic['license_key'],
                'license_type' => $lic['license_type'],
                'max_tenants' => intval($lic['max_tenants']),
                'duration_days' => intval($lic['duration_days']),
                'expire_time' => $lic['expire_time'],
                'domain' => $cloudHost !== '' ? $cloudHost : $lic['domain'],
                'domains' => $lic['domains'],
                'contact_name' => (string)$lic['contact_name'],
                'contact_phone' => (string)$lic['contact_phone'],
                'contact_email' => (string)$lic['contact_email'],
                'max_binds' => isset($lic['max_binds']) ? intval($lic['max_binds']) : 3,
                'bind_count' => isset($lic['bind_count']) ? intval($lic['bind_count']) : 0,
                'remark' => (string)$lic['remark'],
            ));
        } catch (Exception $e) {
            emj_json(0, '云激活服务异常: ' . $e->getMessage());
        }
    }

    // ---- 云接口2：应用授权码远程兑换（占用式，一码一用+同站点幂等）----
    if ($requestPath === '/api/cloud/app-code/redeem') {
        $appCodeStr = trim((string)emj_val($cloudInput, 'code'));
        if ($appCodeStr === '') emj_json(0, '请输入应用授权码');
        if (stripos($appCodeStr, 'APP-') !== 0) emj_json(0, '无效的应用授权码格式');
        try {
            $pdo = emg('db');
            $pdo->beginTransaction();
            $stmt = $pdo->prepare("SELECT * FROM saas_platform_app_codes WHERE code = ? LIMIT 1 FOR UPDATE");
            $stmt->execute(array($appCodeStr));
            $ac = $stmt->fetch(PDO::FETCH_ASSOC);
            if (!$ac) { $pdo->rollBack(); emj_json(0, '授权码不存在，请向平台方获取'); }
            // v2.12.6 内部应用（如菲录）不对外：云兑换直接拒绝且不占用码
            $stmtI = $pdo->prepare("SELECT app_name, is_internal FROM saas_apps WHERE app_key = ? LIMIT 1");
            $stmtI->execute(array((string)$ac['app_key']));
            $appChk = $stmtI->fetch(PDO::FETCH_ASSOC);
            if ($appChk && intval($appChk['is_internal']) === 1) {
                $pdo->rollBack();
                emj_json(0, '应用「' . (string)$appChk['app_name'] . '」为内部应用，不对外提供');
            }
            if (intval($ac['status']) === 2) { $pdo->rollBack(); emj_json(0, '该授权码已被禁用'); }
            if (intval($ac['expire_time']) > 0 && intval($ac['expire_time']) < time()) {
                $pdo->rollBack(); emj_json(0, '该授权码已过期');
            }
            if (intval($ac['status']) === 1) {
                // 已使用：仅同站点重复兑换幂等放行（本地落库失败后重试恢复），跨站点拒绝
                $usedRemark = (string)$ac['remark'];
                if ($cloudHost === '' || strpos($usedRemark, '[独立部署:' . $cloudHost . ']') === false) {
                    $pdo->rollBack();
                    emj_json(0, '该授权码已被其他站点使用');
                }
            } else {
                $newRemark = '[独立部署:' . ($cloudHost !== '' ? $cloudHost : 'unknown') . '] ' . (string)$ac['remark'];
                if (function_exists('mb_substr')) {
                    $newRemark = mb_substr($newRemark, 0, 250);
                } else {
                    $newRemark = substr($newRemark, 0, 240);
                }
                $pdo->prepare("UPDATE saas_platform_app_codes SET status = 1, used_by = 0, used_time = ?, remark = ?, updated_at = ? WHERE id = ?")
                    ->execute(array(time(), $newRemark, time(), $ac['id']));
            }
            // 应用名称（优先 saas_apps 表）
            $appName = (string)$ac['app_key'];
            $stmt2 = $pdo->prepare("SELECT app_name FROM saas_apps WHERE app_key = ? OR (id = ? AND id > 0) LIMIT 1");
            $stmt2->execute(array((string)$ac['app_key'], intval($ac['app_id'])));
            $appRow = $stmt2->fetch(PDO::FETCH_ASSOC);
            if ($appRow) $appName = (string)$appRow['app_name'];
            $pdo->commit();
            emj_json(1, '兑换成功', array(
                'code' => $ac['code'],
                'app_id' => intval($ac['app_id']),
                'app_key' => (string)$ac['app_key'],
                'app_name' => $appName,
                'auth_mode' => (string)$ac['auth_mode'],
                'price_type' => (string)$ac['price_type'],
                'auth_days' => intval($ac['auth_days']),
                'max_tenants' => intval($ac['max_tenants']),
            ));
        } catch (Exception $e) {
            emj_json(0, '云兑换服务异常: ' . $e->getMessage());
        }
    }
    emj_json(0, '未知的云接口', null, 404);
}

// ========== 静态文件 ==========
$staticExts = array('js', 'css', 'png', 'jpg', 'jpeg', 'gif', 'svg', 'ico', 'woff', 'woff2', 'ttf', 'eot', 'map', 'webp', 'bmp', 'pdf', 'pptx', 'docx', 'xlsx', 'zip');
$pathParts = pathinfo($requestPath);
$reqExt = isset($pathParts['extension']) ? strtolower($pathParts['extension']) : '';
if (in_array($reqExt, $staticExts)) {
    // /runtime/ 前缀映射到 server/runtime/（v1.14.0 月度结算单PDF等动态生成文件）
    $filePath = ROOT_PATH . $requestPath;
    if (strpos($requestPath, '/runtime/') === 0) {
        $filePath = ROOT_PATH . '/server/runtime/' . substr($requestPath, strlen('/runtime/'));
    } elseif (strpos($requestPath, '/uploads/') === 0) {
        // 上传文件实际存放于 public/uploads/（v2.7.1 修复：静态访问 404）
        $filePath = ROOT_PATH . '/public' . $requestPath;
    }
    if (file_exists($filePath) && is_file($filePath)) {
        $mimeTypes = array(
            'js' => 'application/javascript', 'css' => 'text/css', 'png' => 'image/png',
            'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'gif' => 'image/gif',
            'svg' => 'image/svg+xml', 'ico' => 'image/x-icon', 'woff' => 'font/woff',
            'woff2' => 'font/woff2', 'ttf' => 'font/ttf', 'eot' => 'application/vnd.ms-fontobject',
            'map' => 'application/json', 'webp' => 'image/webp', 'bmp' => 'image/bmp',
            'pdf' => 'application/pdf',
            'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
            'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            'zip' => 'application/zip'
        );
        $mt = isset($mimeTypes[$reqExt]) ? $mimeTypes[$reqExt] : 'application/octet-stream';
        header('Content-Type: ' . $mt . '; charset=utf-8');
        header('Content-Length: ' . filesize($filePath));
        header('Cache-Control: public, max-age=86400');
        readfile($filePath);
        exit;
    }
}

// ========== 后台目录入口 ==========
$adminDirs = array('super', 'saasadmin2026', 'admindir26');
foreach ($adminDirs as $ad) {
    if ($requestPath === '/' . $ad || $requestPath === '/' . $ad . '/') {
        $hf = ROOT_PATH . '/' . $ad . '/index.html';
        if (file_exists($hf)) {
            header('Content-Type: text/html; charset=utf-8');
            header('Cache-Control: no-cache');
            readfile($hf);
            exit;
        }
    }
}
// 后台子路径SPA回退
foreach ($adminDirs as $ad) {
    if (strpos($requestPath, '/' . $ad . '/') === 0) {
        $hf = ROOT_PATH . '/' . $ad . '/index.html';
        if (file_exists($hf)) {
            header('Content-Type: text/html; charset=utf-8');
            readfile($hf);
            exit;
        }
    }
}
// h5目录
if (strpos($requestPath, '/h5/') === 0 || $requestPath === '/h5') {
    // v2.7.11：优先返回具体静态文件（如 invite.html），不存在则回退 index.html（SPA）
    if ($requestPath !== '/h5' && $requestPath !== '/h5/') {
        $realH5 = ROOT_PATH . $requestPath;
        if (is_file($realH5)) {
            header('Content-Type: text/html; charset=utf-8');
            header('Cache-Control: no-cache');
            readfile($realH5);
            exit;
        }
    }
    $hf = ROOT_PATH . '/h5/index.html';
    if (file_exists($hf)) { header('Content-Type: text/html; charset=utf-8'); readfile($hf); exit; }
}

// ========== 应用独立后台（SPA入口）==========
$app_dirs = array('da-fei', 'manju', 'shengling-ai', 'rebate', 'local_life', 'cms', 'shortlink', 'content-studio', 'knowledge-base', 'ai-cs', 'jiayun-ai', 'geo', 'wechat-auth', 'human-mirror', 'ledger', 'saas-framework', 'sticker-ai', 'qn-browser', 'tieling');
foreach ($app_dirs as $ad) {
    if ($requestPath === '/apps/' . $ad || $requestPath === '/apps/' . $ad . '/') {
        $hf = ROOT_PATH . '/apps/' . $ad . '/index.html';
        if (file_exists($hf)) {
            header('Content-Type: text/html; charset=utf-8');
            header('Cache-Control: no-cache');
            readfile($hf);
            exit;
        }
    }
}
// 应用子路径SPA回退（真实静态文件优先：如 board.html，确保新增文件不被 SPA 吞掉）
foreach ($app_dirs as $ad) {
    if (strpos($requestPath, '/apps/' . $ad . '/') === 0) {
        $realFile = ROOT_PATH . $requestPath;
        if (is_file($realFile)) {
            $ext = strtolower(pathinfo($realFile, PATHINFO_EXTENSION));
            $mimes = array('html'=>'text/html','js'=>'application/javascript','css'=>'text/css','json'=>'application/json','svg'=>'image/svg+xml','png'=>'image/png','jpg'=>'image/jpeg','jpeg'=>'image/jpeg','ico'=>'image/x-icon');
            header('Content-Type: ' . (isset($mimes[$ext]) ? $mimes[$ext] : 'application/octet-stream') . '; charset=utf-8');
            readfile($realFile);
            exit;
        }
        $hf = ROOT_PATH . '/apps/' . $ad . '/index.html';
        if (file_exists($hf)) {
            header('Content-Type: text/html; charset=utf-8');
            readfile($hf);
            exit;
        }
    }
}

// ========== 独立PHP文件（test.php/reset_pwd.php/api_test.php）直接包含 ==========
$standaloneFiles = array(
    '/test.php' => 'test.php',
    '/test' => 'test.php',
    '/reset_pwd.php' => 'reset_pwd.php',
    '/reset_pwd' => 'reset_pwd.php',
    '/api_test.php' => 'api_test.php',
    '/api_test' => 'api_test.php',
);
if (isset($standaloneFiles[$requestPath])) {
    $sf = ROOT_PATH . '/' . $standaloneFiles[$requestPath];
    if (file_exists($sf)) {
        include $sf;
        exit;
    }
}

// ========== 超管登录 ==========
if (($requestPath === '/api/super/login' || $requestPath === '/api/super/auth/login') && $requestMethod === 'POST') {
    $raw = file_get_contents('php://input');
    $input = json_decode($raw, true);
    if (!$input) $input = $_POST;
    $username = trim(emj_val($input, 'username'));
    $password = emj_val($input, 'password');
    $captchaId = emj_val($input, 'captcha_id');
    $captchaCode = emj_val($input, 'captcha_code');
    if (empty($username) || empty($password)) emj_json(0, '请输入用户名和密码');
    if (empty($captchaCode)) emj_json(0, '请输入验证码');
    if (!emj_verify_captcha($captchaId, $captchaCode)) emj_json(0, '验证码错误');
    try {
        $pdo = emg('db');
        $stmt = $pdo->prepare("SELECT * FROM saas_super_admins WHERE username = ? AND status = 1 LIMIT 1");
        $stmt->execute(array($username));
        $admin = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$admin) emj_json(0, '账号或密码错误');
        // 银行级安全 v2.7.66：账号锁定（连续失败≥5次锁定15分钟，复用 login_fail_count/last_fail_time）
        $failCount = intval(emj_val($admin, 'login_fail_count', 0));
        $lastFail = intval(emj_val($admin, 'last_fail_time', 0));
        if ($failCount >= 5 && $lastFail > 0 && ($lastFail + 900) > time()) {
            emj_json(0, '登录失败次数过多，账号已锁定' . ceil(($lastFail + 900 - time()) / 60) . '分钟，请稍后再试');
        }
        if ($failCount >= 5) {
            try { $pdo->prepare("UPDATE saas_super_admins SET login_fail_count = 0 WHERE id = ?")->execute(array($admin['id'])); } catch (Exception $e) {}
        }
        if (!emj_verify_pwd($password, emj_val($admin, 'password'))) {
            try {
                $pdo->prepare("UPDATE saas_super_admins SET login_fail_count = login_fail_count + 1, last_fail_time = ? WHERE id = ?")
                    ->execute(array(time(), $admin['id']));
            } catch (Exception $e) {}
            emj_json(0, '账号或密码错误');
        }
        $token = emj_jwt_encode(array(
            'type' => 'super', 'super_admin_id' => $admin['id'], 'username' => $admin['username'],
            'is_super' => true, 'exp' => time() + 86400 * 7,
        ));
        try {
            $ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
            $pdo->prepare("UPDATE saas_super_admins SET last_login_time = ?, last_login_ip = ?, login_fail_count = 0 WHERE id = ?")
                ->execute(array(time(), $ip, $admin['id']));
        } catch (Exception $e) {}
        emj_json(1, '登录成功', array('token' => $token, 'admin' => array(
            'id' => $admin['id'], 'username' => $admin['username'],
            'realname' => emj_val($admin, 'realname', $admin['username']),
            'phone' => emj_val($admin, 'phone'),
        )));
    } catch (Exception $e) {
        emj_json(0, '登录失败: ' . $e->getMessage());
    }
}

// ========== 平台登录 ==========
if ($requestPath === '/api/platform/auth/login' && $requestMethod === 'POST') {
    $raw = file_get_contents('php://input');
    $input = json_decode($raw, true);
    if (!$input) $input = $_POST;
    $username = trim(emj_val($input, 'username'));
    $password = emj_val($input, 'password');
    $captchaId = emj_val($input, 'captcha_id');
    $captchaCode = emj_val($input, 'captcha_code');
    if (empty($username) || empty($password)) emj_json(0, '请输入用户名和密码');
    if (empty($captchaCode)) emj_json(0, '请输入验证码');
    if (!emj_verify_captcha($captchaId, $captchaCode)) emj_json(0, '验证码错误');
    try {
        $pdo = emg('db');
        $stmt = $pdo->prepare("SELECT * FROM saas_platform_admins WHERE username = ? AND status = 1 LIMIT 1");
        $stmt->execute(array($username));
        $admin = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$admin) emj_json(0, '账号或密码错误');
        // 银行级安全 v2.7.66：账号锁定（连续失败≥5次锁定15分钟，复用 login_fail_count/last_fail_time）
        $failCount = intval(emj_val($admin, 'login_fail_count', 0));
        $lastFail = intval(emj_val($admin, 'last_fail_time', 0));
        if ($failCount >= 5 && $lastFail > 0 && ($lastFail + 900) > time()) {
            emj_json(0, '登录失败次数过多，账号已锁定' . ceil(($lastFail + 900 - time()) / 60) . '分钟，请稍后再试');
        }
        if ($failCount >= 5) {
            try { $pdo->prepare("UPDATE saas_platform_admins SET login_fail_count = 0 WHERE id = ?")->execute(array($admin['id'])); } catch (Exception $e) {}
        }
        if (!emj_verify_pwd($password, emj_val($admin, 'password'))) {
            try {
                $pdo->prepare("UPDATE saas_platform_admins SET login_fail_count = login_fail_count + 1, last_fail_time = ? WHERE id = ?")
                    ->execute(array(time(), $admin['id']));
            } catch (Exception $e) {}
            emj_json(0, '账号或密码错误');
        }
        $token = emj_jwt_encode(array(
            'type' => 'platform', 'admin_id' => $admin['id'], 'username' => $admin['username'],
            'is_platform' => true, 'exp' => time() + 86400 * 7,
        ));
        try {
            $ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
            $pdo->prepare("UPDATE saas_platform_admins SET last_login_time = ?, last_login_ip = ?, login_fail_count = 0 WHERE id = ?")
                ->execute(array(time(), $ip, $admin['id']));
        } catch (Exception $e) {}
        emj_json(1, '登录成功', array('token' => $token, 'admin' => array(
            'id' => $admin['id'], 'username' => $admin['username'],
            'realname' => emj_val($admin, 'realname', $admin['username']),
            'phone' => emj_val($admin, 'phone'),
            'role' => emj_val($admin, 'role', 'admin'),
            'license_status' => intval(emj_val($admin, 'license_status', 0)),
            'audit_status' => intval(emj_val($admin, 'audit_status', 1)),
            'company_name' => emj_val($admin, 'company_name', ''),
            'brand_name' => emj_val($admin, 'brand_name', '稼云SaaS'),
            'theme_color' => emj_val($admin, 'theme_color', '#667eea'),
        )));
    } catch (Exception $e) {
        emj_json(0, '登录失败: ' . $e->getMessage());
    }
}

// ========== 租户登录 ==========
if ($requestPath === '/api/tenant/auth/login' && $requestMethod === 'POST') {
    $raw = file_get_contents('php://input');
    $input = json_decode($raw, true);
    if (!$input) $input = $_POST;
    $username = trim(emj_val($input, 'username'));
    $password = emj_val($input, 'password');
    $tenantId = intval(emj_val($input, 'tenant_id', 0));
    $captchaId = emj_val($input, 'captcha_id');
    $captchaCode = emj_val($input, 'captcha_code');
    if (empty($username) || empty($password)) emj_json(0, '请输入用户名和密码');
    if (empty($captchaCode)) emj_json(0, '请输入验证码');
    if (!emj_verify_captcha($captchaId, $captchaCode)) emj_json(0, '验证码错误');
    try {
        $pdo = emg('db');
        if (!$tenantId) {
            // v1.0.7 自助注册用户定位：username 全租户唯一 → 自动定位其所属租户
            // （与 server 端 AuthController::login v1.0.7 逻辑对齐，修复注册后登录定位到主租户导致"账号或密码错误"）
            $stmt = $pdo->prepare("SELECT tenant_id FROM saas_tenant_admins WHERE username = ? AND status = 1 LIMIT 1");
            $stmt->execute(array($username));
            $own = $stmt->fetch(PDO::FETCH_ASSOC);
            if ($own && intval($own['tenant_id']) > 0) {
                $tenantId = intval($own['tenant_id']);
            } else {
                $stmt = $pdo->query("SELECT id FROM saas_tenants WHERE status = 1 ORDER BY id ASC LIMIT 1");
                $t = $stmt->fetch(PDO::FETCH_ASSOC);
                if (!$t) emj_json(0, '暂无可用租户');
                $tenantId = intval($t['id']);
            }
        }
        $stmt = $pdo->prepare("SELECT * FROM saas_tenants WHERE id = ? AND status = 1 LIMIT 1");
        $stmt->execute(array($tenantId));
        $tenant = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$tenant) emj_json(0, '租户不存在或已被禁用');
        $stmt = $pdo->prepare("SELECT * FROM saas_tenant_admins WHERE tenant_id = ? AND username = ? AND status = 1 LIMIT 1");
        $stmt->execute(array($tenantId, $username));
        $admin = $stmt->fetch(PDO::FETCH_ASSOC);
        // v2.7.62 平台总账号/运营商账号兜底登录商户端（商户端密码与平台账号密码一致）
        // 规则：超管可代管任意租户；运营商仅可代管其旗下租户。
        // 注意：即使租户内存在同名管理员，也优先以平台账号密码校验，保证总账号一套密码通吃。
        $loginProxy = '';
        $adminOk = false;
        if ($admin && emj_verify_pwd($password, emj_val($admin, 'password'))) {
            $adminOk = true;
        } else {
            $stmt = $pdo->prepare("SELECT * FROM saas_super_admins WHERE username = ? AND status = 1 LIMIT 1");
            $stmt->execute(array($username));
            $su = $stmt->fetch(PDO::FETCH_ASSOC);
            if ($su && emj_verify_pwd($password, emj_val($su, 'password'))) {
                $loginProxy = 'super';
            } else {
                $stmt = $pdo->prepare("SELECT * FROM saas_platform_admins WHERE username = ? AND status = 1 LIMIT 1");
                $stmt->execute(array($username));
                $op = $stmt->fetch(PDO::FETCH_ASSOC);
                if ($op && emj_verify_pwd($password, emj_val($op, 'password'))) {
                    // 校验运营商管辖该租户（operator_id 归属），防止跨运营商越权
                    $stmt = $pdo->prepare("SELECT COUNT(*) FROM saas_tenants WHERE id = ? AND operator_id = ?");
                    $stmt->execute(array($tenantId, $op['id']));
                    if (intval($stmt->fetchColumn()) > 0) $loginProxy = 'platform';
                }
            }
            if ($loginProxy) {
                if (!$admin) {
                    // 平台账号可代管，但租户内无该用户名管理员时，以租户第一个有效管理员身份登录
                    $stmt = $pdo->prepare("SELECT * FROM saas_tenant_admins WHERE tenant_id = ? AND status = 1 ORDER BY id ASC LIMIT 1");
                    $stmt->execute(array($tenantId));
                    $admin = $stmt->fetch(PDO::FETCH_ASSOC);
                }
                // 平台账号兜底：密码已用平台账号校验，跳过租户密码校验
                if ($admin) $adminOk = true;
            }
        }
        if (!$admin || !$adminOk) {
            // 银行级安全 v2.7.66：登录失败递增账号级计数（防定向爆破）
            if ($admin) {
                try {
                    $pdo->prepare("UPDATE saas_tenant_admins SET login_fail_count = login_fail_count + 1, last_fail_time = ? WHERE id = ?")
                        ->execute(array(time(), $admin['id']));
                } catch (Exception $e) {}
            }
            emj_json(0, '账号或密码错误');
        }
        // 银行级安全 v2.7.66：账号锁定（连续失败≥5次锁定15分钟，复用 login_fail_count/last_fail_time）
        $failCount = intval(emj_val($admin, 'login_fail_count', 0));
        $lastFail = intval(emj_val($admin, 'last_fail_time', 0));
        if ($failCount >= 5 && $lastFail > 0 && ($lastFail + 900) > time()) {
            emj_json(0, '登录失败次数过多，账号已锁定' . ceil(($lastFail + 900 - time()) / 60) . '分钟，请稍后再试');
        }
        if ($failCount >= 5) {
            try { $pdo->prepare("UPDATE saas_tenant_admins SET login_fail_count = 0 WHERE id = ?")->execute(array($admin['id'])); } catch (Exception $e) {}
        }
        $token = emj_jwt_encode(array(
            'type' => 'tenant', 'tenant_id' => $tenant['id'], 'admin_id' => $admin['id'],
            'username' => $admin['username'], 'is_tenant' => true,
            'exp' => time() + 86400 * 7,
        ));
        try {
            $ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
            $pdo->prepare("UPDATE saas_tenant_admins SET last_login_time = ?, last_login_ip = ?, login_fail_count = 0 WHERE id = ?")
                ->execute(array(time(), $ip, $admin['id']));
            $pdo->prepare("UPDATE saas_tenants SET last_login_time = ?, last_login_ip = ? WHERE id = ?")
                ->execute(array(time(), $ip, $tenant['id']));
        } catch (Exception $e) {}
        emj_json(1, '登录成功', array(
            'token' => $token,
            'tenant' => array('id' => $tenant['id'], 'tenant_name' => $tenant['tenant_name']),
            'admin' => array(
                'id' => $admin['id'], 'username' => $admin['username'],
                'realname' => emj_val($admin, 'realname', $admin['username']),
                'phone' => emj_val($admin, 'phone'),
                'role' => emj_val($admin, 'role', 'admin'),
            )
        ));
    } catch (Exception $e) {
        emj_json(0, '登录失败: ' . $e->getMessage());
    }
}

// ========== 门户统一登录（v1.0.3）：主页应用中心登录入口，自动识别身份分流 ==========
// 入参：username/password/captcha_id/captcha_code/super(1=仅总后台超管登录)
// 分流：租户→商户后台 /admindir26/，运营商→超管运营商后台 /saasadmin2026/，超管→总后台 /super/
// 安全：验证码校验 + IP 限流(10次/分钟) + 参数绑定
if ($requestPath === '/api/portal/login' && $requestMethod === 'POST') {
    $raw = file_get_contents('php://input');
    $input = json_decode($raw, true);
    if (!$input) $input = $_POST;
    $username = trim(emj_val($input, 'username'));
    $password = emj_val($input, 'password');
    $captchaId = emj_val($input, 'captcha_id');
    $captchaCode = emj_val($input, 'captcha_code');
    $superOnly = intval(emj_val($input, 'super', 0)) === 1;
    if (empty($username) || empty($password)) emj_json(0, '请输入账号和密码');
    if (empty($captchaCode)) emj_json(0, '请输入验证码');
    if (!emj_verify_captcha($captchaId, $captchaCode)) emj_json(0, '验证码错误');

    // IP 限流：10 次/分钟，防爆破
    $ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
    $rlFile = emj_cache_dir('portal_rl_' . md5($ip) . '.php');
    $rlData = array('t' => 0, 'n' => 0);
    if (file_exists($rlFile)) {
        $c = file_get_contents($rlFile);
        $d = json_decode(substr($c, strlen('<?php exit;?>')), true);
        if (is_array($d)) $rlData = $d;
    }
    if ($rlData['t'] + 60 < time()) { $rlData = array('t' => time(), 'n' => 0); }
    if ($rlData['n'] >= 10) emj_json(0, '尝试次数过多，请1分钟后再试');
    @file_put_contents($rlFile, '<?php exit;?>' . json_encode(array('t' => $rlData['t'], 'n' => $rlData['n'] + 1)));

    try {
        $pdo = emg('db');
        $found = null;

        // 1) 超管（saas_super_admins）—— super=1 时仅尝试超管
        if ($superOnly || !$found) {
            $stmt = $pdo->prepare("SELECT * FROM saas_super_admins WHERE username = ? AND status = 1 LIMIT 1");
            $stmt->execute(array($username));
            $admin = $stmt->fetch(PDO::FETCH_ASSOC);
            if ($admin) {
                // 银行级安全 v2.7.66：账号锁定拦截（连续失败≥5次锁定15分钟）
                $failCount = intval(emj_val($admin, 'login_fail_count', 0));
                $lastFail = intval(emj_val($admin, 'last_fail_time', 0));
                if ($failCount >= 5 && $lastFail > 0 && ($lastFail + 900) > time()) {
                    emj_json(0, '登录失败次数过多，账号已锁定' . ceil(($lastFail + 900 - time()) / 60) . '分钟，请稍后再试');
                }
                if ($failCount >= 5) {
                    try { $pdo->prepare("UPDATE saas_super_admins SET login_fail_count = 0 WHERE id = ?")->execute(array($admin['id'])); } catch (Exception $e) {}
                }
                if (emj_verify_pwd($password, emj_val($admin, 'password'))) {
                    $found = array('role' => 'super', 'token' => emj_jwt_encode(array(
                        'type' => 'super', 'super_admin_id' => $admin['id'], 'username' => $admin['username'],
                        'is_super' => true, 'exp' => time() + 86400 * 7,
                    )), 'name' => emj_val($admin, 'realname', $admin['username']), 'id' => $admin['id'],
                        // v2.7.63 平台/超管登录归平台锁定租户 dir26(id=1)，前台应用（菲录等）携带该租户上下文
                        'tenant_id' => 1, 'tenant_name' => '平台官方');
                    try {
                        $pdo->prepare("UPDATE saas_super_admins SET last_login_time = ?, last_login_ip = ?, login_fail_count = 0 WHERE id = ?")
                            ->execute(array(time(), $ip, $admin['id']));
                    } catch (Exception $e) {}
                }
            }
        }

        // 2) 租户（saas_tenant_admins，关联有效租户，仅匹配唯一用户名）
        if (!$superOnly && !$found) {
            $stmt = $pdo->prepare("SELECT ta.*, t.tenant_name FROM saas_tenant_admins ta
                    JOIN saas_tenants t ON t.id = ta.tenant_id AND t.status = 1
                    WHERE ta.username = ? AND ta.status = 1 ORDER BY ta.tenant_id ASC LIMIT 1");
            $stmt->execute(array($username));
            $admin = $stmt->fetch(PDO::FETCH_ASSOC);
            if ($admin) {
                // 银行级安全 v2.7.66：账号锁定拦截（连续失败≥5次锁定15分钟）
                $failCount = intval(emj_val($admin, 'login_fail_count', 0));
                $lastFail = intval(emj_val($admin, 'last_fail_time', 0));
                if ($failCount >= 5 && $lastFail > 0 && ($lastFail + 900) > time()) {
                    emj_json(0, '登录失败次数过多，账号已锁定' . ceil(($lastFail + 900 - time()) / 60) . '分钟，请稍后再试');
                }
                if ($failCount >= 5) {
                    try { $pdo->prepare("UPDATE saas_tenant_admins SET login_fail_count = 0 WHERE id = ?")->execute(array($admin['id'])); } catch (Exception $e) {}
                }
                if (emj_verify_pwd($password, emj_val($admin, 'password'))) {
                $tenantJwt = emj_jwt_encode(array(
                    'type' => 'tenant', 'tenant_id' => intval($admin['tenant_id']), 'admin_id' => $admin['id'],
                    'username' => $admin['username'], 'is_tenant' => true, 'exp' => time() + 86400 * 7,
                ));
                // v1.0.4/v1.0.5：查询该租户已启用应用，随登录响应返回，前台"已授权应用"列表展示
                $myApps = array();
                $st2 = $pdo->prepare("SELECT a.app_key, a.app_name FROM saas_tenant_apps ta
                        JOIN saas_apps a ON a.app_key = ta.app_key
                        WHERE ta.tenant_id = ? AND ta.status = 1 AND ta.is_enabled = 1 AND ta.is_installed = 1 AND a.status = 1
                        ORDER BY ta.sort DESC, ta.id ASC LIMIT 50");
                $st2->execute(array(intval($admin['tenant_id'])));
                foreach ($st2->fetchAll(PDO::FETCH_ASSOC) as $ra) {
                    $myApps[] = array('app_key' => $ra['app_key'], 'app_name' => $ra['app_name']);
                }
                $found = array('role' => 'tenant', 'token' => $tenantJwt,
                    'name' => emj_val($admin, 'realname', $admin['username']), 'id' => $admin['id'],
                    'tenant_id' => intval($admin['tenant_id']), 'tenant_name' => emj_val($admin, 'tenant_name', ''),
                    'apps' => $myApps);
                try {
                    $pdo->prepare("UPDATE saas_tenant_admins SET last_login_time = ?, last_login_ip = ?, login_fail_count = 0 WHERE id = ?")
                        ->execute(array(time(), $ip, $admin['id']));
                    $pdo->prepare("UPDATE saas_tenants SET last_login_time = ?, last_login_ip = ? WHERE id = ?")
                        ->execute(array(time(), $ip, intval($admin['tenant_id'])));
                } catch (Exception $e) {}
                }
            }
        }

        // 3) 运营商（saas_platform_admins）
        if (!$superOnly && !$found) {
            $stmt = $pdo->prepare("SELECT * FROM saas_platform_admins WHERE username = ? AND status = 1 LIMIT 1");
            $stmt->execute(array($username));
            $admin = $stmt->fetch(PDO::FETCH_ASSOC);
            if ($admin) {
                // 银行级安全 v2.7.66：账号锁定拦截（连续失败≥5次锁定15分钟）
                $failCount = intval(emj_val($admin, 'login_fail_count', 0));
                $lastFail = intval(emj_val($admin, 'last_fail_time', 0));
                if ($failCount >= 5 && $lastFail > 0 && ($lastFail + 900) > time()) {
                    emj_json(0, '登录失败次数过多，账号已锁定' . ceil(($lastFail + 900 - time()) / 60) . '分钟，请稍后再试');
                }
                if ($failCount >= 5) {
                    try { $pdo->prepare("UPDATE saas_platform_admins SET login_fail_count = 0 WHERE id = ?")->execute(array($admin['id'])); } catch (Exception $e) {}
                }
                if (emj_verify_pwd($password, emj_val($admin, 'password'))) {
                $found = array('role' => 'platform', 'token' => emj_jwt_encode(array(
                    'type' => 'platform', 'admin_id' => $admin['id'], 'username' => $admin['username'],
                    'is_platform' => true, 'exp' => time() + 86400 * 7,
                )), 'name' => emj_val($admin, 'realname', $admin['username']), 'id' => $admin['id'],
                    // v2.7.63 运营商登录归平台锁定租户 dir26(id=1)
                    'tenant_id' => 1, 'tenant_name' => '平台官方');
                try {
                    $pdo->prepare("UPDATE saas_platform_admins SET last_login_time = ?, last_login_ip = ?, login_fail_count = 0 WHERE id = ?")
                        ->execute(array(time(), $ip, $admin['id']));
                } catch (Exception $e) {}
                }
            }
        }

        if (!$found) emj_json(0, '账号或密码错误');

        // v1.0.5：门户登录成功后统一回到前台门户展示登录态，不再自动跳转管理后台
        // （原行为：租户→/admindir26/ 或应用直达、运营商→/saasadmin2026/、超管→/super/，
        //   无 token 跳转导致后台显示登录页/空白；现改由前台"角色入口"按钮带 token 免密进入后台）
        $redirect = '/';
        emj_json(1, '登录成功', array(
            'role' => $found['role'],
            'token' => $found['token'],
            'name' => $found['name'],
            'redirect' => $redirect,
            'tenant_id' => emj_val($found, 'tenant_id', 0),
            'tenant_name' => emj_val($found, 'tenant_name', ''),
            'apps' => emj_val($found, 'apps', array()),
        ));
    } catch (Exception $e) {
        emj_json(0, '登录失败: ' . $e->getMessage());
    }
}

// ========== 获取用户信息 ==========
$mePaths = array('/api/super/me', '/api/super/auth/me', '/api/platform/auth/info', '/api/tenant/auth/info');
if (in_array($requestPath, $mePaths) && $requestMethod === 'GET') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload) emj_json(0, '登录已失效，请重新登录', null, 401);
    try {
        $pdo = emg('db');
        if (isset($payload['is_super']) && $payload['is_super'] && isset($payload['super_admin_id'])) {
            $stmt = $pdo->prepare("SELECT id, username, realname, phone, avatar FROM saas_super_admins WHERE id = ? AND status = 1 LIMIT 1");
            $stmt->execute(array($payload['super_admin_id']));
            $admin = $stmt->fetch(PDO::FETCH_ASSOC);
            if (!$admin) emj_json(0, '账号不存在或已被禁用', null, 401);
            emj_json(1, 'success', array(
                'id' => $admin['id'], 'username' => $admin['username'],
                'realname' => emj_val($admin, 'realname', $admin['username']),
                'phone' => emj_val($admin, 'phone'), 'avatar' => emj_val($admin, 'avatar')
            ));
        }
        if (isset($payload['is_platform']) && $payload['is_platform'] && isset($payload['admin_id'])) {
            $stmt = $pdo->prepare("SELECT id, username, realname, phone, avatar, role, license_status, license_expire_at, audit_status, audit_remark, company_name, contact_email, brand_name, brand_logo, brand_slogan, theme_color, custom_domain, authorized_domains, icp, copyright, max_tenants FROM saas_platform_admins WHERE id = ? AND status = 1 LIMIT 1");
            $stmt->execute(array($payload['admin_id']));
            $admin = $stmt->fetch(PDO::FETCH_ASSOC);
            if (!$admin) emj_json(0, '账号不存在或已被禁用', null, 401);
            // 获取未读消息数
            $unreadCount = 0;
            try {
                $stmt2 = $pdo->prepare("SELECT COUNT(*) as cnt FROM saas_operator_notifications WHERE operator_id = ? AND is_read = 0");
                $stmt2->execute(array($admin['id']));
                $r = $stmt2->fetch(PDO::FETCH_ASSOC);
                $unreadCount = intval($r['cnt']);
            } catch (Exception $e) {}
            // 获取已创建租户数
            $tenantCount = 0;
            try {
                $stmt3 = $pdo->prepare("SELECT COUNT(*) as cnt FROM saas_tenants WHERE operator_id = ?");
                $stmt3->execute(array($admin['id']));
                $r = $stmt3->fetch(PDO::FETCH_ASSOC);
                $tenantCount = intval($r['cnt']);
            } catch (Exception $e) {}
            // 授权域名解析（主域名取 authorized_domains 第一个，其余为附加域名）
            $licenseDomain = emj_val($admin, 'custom_domain', '');
            $extraDomains = '[]';
            if (!empty($admin['authorized_domains'])) {
                $domains = json_decode($admin['authorized_domains'], true);
                if (is_array($domains) && count($domains) > 0) {
                    $licenseDomain = $domains[0];
                    $extraDomains = json_encode(array_slice($domains, 1), JSON_UNESCAPED_UNICODE);
                }
            }
            emj_json(1, 'success', array(
                'id' => $admin['id'], 'username' => $admin['username'],
                'realname' => emj_val($admin, 'realname', $admin['username']),
                'phone' => emj_val($admin, 'phone'), 'avatar' => emj_val($admin, 'avatar'),
                'role' => emj_val($admin, 'role', 'admin'),
                'license_status' => intval(emj_val($admin, 'license_status', 0)),
                'license_expire_at' => emj_val($admin, 'license_expire_at', ''),
                'audit_status' => intval(emj_val($admin, 'audit_status', 1)),
                'audit_remark' => emj_val($admin, 'audit_remark', ''),
                'company_name' => emj_val($admin, 'company_name', ''),
                'contact_email' => emj_val($admin, 'contact_email', ''),
                'contact_qq' => emj_val($admin, 'contact_qq', ''),
                'contact_wechat' => emj_val($admin, 'contact_wechat', ''),
                'contact_phone' => emj_val($admin, 'contact_phone', ''),
                'brand_name' => emj_val($admin, 'brand_name') ? emj_val($admin, 'brand_name') : '稼云SaaS',
                'brand_logo' => emj_val($admin, 'brand_logo'),
                'brand_slogan' => emj_val($admin, 'brand_slogan', ''),
                'theme_color' => emj_val($admin, 'theme_color', '#667eea'),
                'custom_domain' => emj_val($admin, 'custom_domain', ''),
                'license_domain' => $licenseDomain,
                'extra_domains' => $extraDomains,
                'authorized_domains' => emj_val($admin, 'authorized_domains', '[]'),
                'icp' => emj_val($admin, 'icp', ''),
                'copyright' => emj_val($admin, 'copyright', ''),
                'max_tenants' => intval(emj_val($admin, 'max_tenants', 3)),
                'tenant_count' => $tenantCount,
                'unread_count' => $unreadCount,
            ));
        }
        if (isset($payload['is_tenant']) && $payload['is_tenant'] && isset($payload['admin_id'])) {
            $stmt = $pdo->prepare("SELECT a.id, a.username, a.realname, a.phone, a.avatar, a.role, t.id as tenant_id, t.tenant_name FROM saas_tenant_admins a LEFT JOIN saas_tenants t ON a.tenant_id = t.id WHERE a.id = ? AND a.status = 1 LIMIT 1");
            $stmt->execute(array($payload['admin_id']));
            $row = $stmt->fetch(PDO::FETCH_ASSOC);
            if (!$row) emj_json(0, '账号不存在或已被禁用', null, 401);
            emj_json(1, 'success', array(
                'id' => $row['id'], 'username' => $row['username'],
                'realname' => emj_val($row, 'realname', $row['username']),
                'phone' => emj_val($row, 'phone'), 'avatar' => emj_val($row, 'avatar'),
                'role' => emj_val($row, 'role', 'admin'),
                'tenant' => array('id' => $row['tenant_id'], 'tenant_name' => emj_val($row, 'tenant_name'))
            ));
        }
        emj_json(0, '无效的登录凭证', null, 401);
    } catch (Exception $e) {
        emj_json(0, '获取信息失败: ' . $e->getMessage());
    }
}

// ========== 应用列表/Dashboard/租户列表 交由框架处理（SuperController/Platform Controller真实查库）==========

// ========== 租户列表（已移除直通，交由框架 SuperController@tenantList 按超管/平台身份查库）==========

// ========== 登出 ==========
$logoutPaths = array('/api/super/auth/logout', '/api/platform/auth/logout', '/api/tenant/auth/logout');
if (in_array($requestPath, $logoutPaths) && $requestMethod === 'POST') {
    emj_json(1, '退出成功');
}

// ========== 系统配置获取/保存 ==========
if ($requestPath === '/api/super/system/config' && $requestMethod === 'GET') {
    $scf = ROOT_PATH . '/server/config/saas.php';
    $cfg = file_exists($scf) ? require $scf : array();
    emj_json(1, 'success', $cfg);
}
if ($requestPath === '/api/super/system/config' && $requestMethod === 'POST') {
    // 安全修复：必须校验超管身份，防止未授权写入配置
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload || empty($payload['is_super'])) emj_json(0, '无权限', null, 403);
    $raw = file_get_contents('php://input');
    $input = json_decode($raw, true);
    if (!$input) $input = $_POST;
    // 仅允许写入白名单字段，防止注入任意配置（如super_admin_path/domain_mode等危险键）
    $allowKeys = array('platform_domain', 'brand');
    $newCfg = array();
    foreach ($allowKeys as $k) {
        if (isset($input[$k])) $newCfg[$k] = $input[$k];
    }
    if (empty($newCfg)) emj_json(0, '没有可保存的配置项');
    $scf = ROOT_PATH . '/server/config/saas.php';
    $existing = file_exists($scf) ? require $scf : array();
    $merged = array_merge($existing, $newCfg);
    // 备份原配置
    @file_put_contents($scf . '.bak_' . date('YmdHis'), "<?php\nreturn " . var_export($existing, true) . ";\n");
    $content = "<?php\nreturn " . var_export($merged, true) . ";\n";
    if (@file_put_contents($scf, $content) === false) {
        emj_json(0, '配置保存失败：目录不可写');
    }
    emj_json(1, '配置保存成功');
}

// ========== 修改个人资料 ==========
$updateProfilePaths = array('/api/super/auth/update-profile', '/api/platform/auth/update-profile', '/api/tenant/auth/update-profile');
if (in_array($requestPath, $updateProfilePaths) && $requestMethod === 'POST') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload) emj_json(0, '登录已失效', null, 401);
    $raw = file_get_contents('php://input');
    $input = json_decode($raw, true);
    if (!$input) $input = $_POST;
    $realname = emj_val($input, 'realname');
    $phone = emj_val($input, 'phone');
    try {
        $pdo = emg('db');
        if (isset($payload['is_super']) && $payload['is_super']) {
            $stmt = $pdo->prepare("UPDATE saas_super_admins SET realname = ?, phone = ? WHERE id = ?");
            $stmt->execute(array($realname, $phone, $payload['super_admin_id']));
        } elseif (isset($payload['is_platform']) && $payload['is_platform']) {
            $stmt = $pdo->prepare("UPDATE saas_platform_admins SET realname = ?, phone = ? WHERE id = ?");
            $stmt->execute(array($realname, $phone, $payload['admin_id']));
        } elseif (isset($payload['is_tenant']) && $payload['is_tenant']) {
            $stmt = $pdo->prepare("UPDATE saas_tenant_admins SET realname = ?, phone = ? WHERE id = ?");
            $stmt->execute(array($realname, $phone, $payload['admin_id']));
        }
        emj_json(1, '资料更新成功');
    } catch (Exception $e) {
        emj_json(0, '更新失败: ' . $e->getMessage());
    }
}

// ========== 修改密码 ==========
$changePwdPaths = array('/api/super/auth/change-password', '/api/platform/auth/change-password', '/api/tenant/auth/change-password');
if (in_array($requestPath, $changePwdPaths) && $requestMethod === 'POST') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload) emj_json(0, '登录已失效', null, 401);
    $raw = file_get_contents('php://input');
    $input = json_decode($raw, true);
    if (!$input) $input = $_POST;
    $oldPwd = emj_val($input, 'old_password');
    $newPwd = emj_val($input, 'new_password');
    $confirmPwd = emj_val($input, 'confirm_password');
    if (empty($oldPwd) || empty($newPwd)) emj_json(0, '请填写完整密码');
    if ($newPwd !== $confirmPwd) emj_json(0, '两次密码不一致');
    // 银行级安全 v2.7.66：密码强度校验
    if (mb_strlen($newPwd) < 8) emj_json(0, '密码长度不能少于8位');
    if (!preg_match('/[A-Za-z]/', $newPwd)) emj_json(0, '密码必须包含字母');
    if (!preg_match('/\d/', $newPwd)) emj_json(0, '密码必须包含数字');
    try {
        $pdo = emg('db');
        $table = ''; $idCol = ''; $idVal = 0;
        if (isset($payload['is_super']) && $payload['is_super']) { $table = 'saas_super_admins'; $idCol = 'id'; $idVal = $payload['super_admin_id']; }
        elseif (isset($payload['is_platform']) && $payload['is_platform']) { $table = 'saas_platform_admins'; $idCol = 'id'; $idVal = $payload['admin_id']; }
        elseif (isset($payload['is_tenant']) && $payload['is_tenant']) { $table = 'saas_tenant_admins'; $idCol = 'id'; $idVal = $payload['admin_id']; }
        if (!$table) emj_json(0, '无效凭证');
        $stmt = $pdo->prepare("SELECT password FROM $table WHERE $idCol = ? LIMIT 1");
        $stmt->execute(array($idVal));
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$row || !emj_verify_pwd($oldPwd, $row['password'])) emj_json(0, '原密码错误');
        // 银行级安全 v2.7.66：bcrypt 存储（替代原 md5）
        $newHash = password_hash($newPwd, PASSWORD_BCRYPT, array('cost' => 10));
        $stmt = $pdo->prepare("UPDATE $table SET password = ? WHERE $idCol = ?");
        $stmt->execute(array($newHash, $idVal));
        emj_json(1, '密码修改成功');
    } catch (Exception $e) {
        emj_json(0, '修改失败: ' . $e->getMessage());
    }
}

// ========== [v2.11.2 总后台移除租户管理] ==========
// 官方总台不再暴露租户生命周期接口：租户操作唯一入口 = 运营商后台 (saasadmin2026 -> platform/tenants/*)
// 所有 /api/super/tenants* 请求统一此处短路：
//   - GET 列表/详情型 (如 dashboard 自动发起的 tenants?page_size=100) → 返回空结构 code=1，避免前端 404 全屏 Not Found
//   - POST/PUT/DELETE 写操作型 → 明确返回 code=0 提示"请通过运营商后台操作"
if (strpos($requestPath, '/api/super/tenants') === 0) {
    $_tSuffix = substr($requestPath, strlen('/api/super/tenants'));
    if ($_tSuffix === false) $_tSuffix = '';
    // ========= 1. GET：空结构响应（兼容 dashboard 挂载请求 & 租户管理页面进入时） =========
    if ($requestMethod === 'GET') {
        // tenants?page_size=N 或 tenants/list → 标准分页空列表
        if ($_tSuffix === '' || $_tSuffix === '/list' || strpos($_tSuffix, '?') === 0) {
            emj_json(1, 'success', ['list' => [], 'total' => 0, 'page' => 1, 'page_size' => isset($_GET['page_size']) ? max(1, intval($_GET['page_size'])) : 20]);
        }
        // tenants/{id}/apps / tenants/apps → 空应用列表
        if (preg_match('#^/[^/]+/apps$#', $_tSuffix) || $_tSuffix === '/apps') {
            emj_json(1, 'success', ['list' => [], 'total' => 0]);
        }
        // tenants/entry → 空入口信息
        if ($_tSuffix === '/entry') {
            emj_json(1, 'success', (object)[]);
        }
        // {id} / detail → 不存在（但仍返回 code=1 空结构，避免前端 404）
        if (preg_match('#^/[0-9]+$#', $_tSuffix)) {
            emj_json(1, 'success', null);
        }
        // 其他 GET → 兜底空对象
        emj_json(1, 'success', (object)[]);
    }
    // ========= 2. 写操作：统一拒绝提示 =========
    emj_json(0, '官方总台已移除租户管理功能，请通过运营商后台（/saasadmin2026/ → 租户管理）进行租户新建/换绑/上下架/充值等操作。', null, 403);
}

// ========== 创建租户（超管）==========
if ($requestPath === '/api/super/tenants/create' && $requestMethod === 'POST') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload || empty($payload['is_super'])) emj_json(0, '无权限', null, 403);
    $raw = file_get_contents('php://input');
    $input = json_decode($raw, true);
    if (!$input) $input = $_POST;
    $tenantName = trim(emj_val($input, 'tenant_name'));
    $contactName = emj_val($input, 'contact_name');
    $contactPhone = emj_val($input, 'contact_phone');
    // v2.8.97: 与框架版 SuperController@tenantCreate 字段对齐，兼容 username/password 传参
    $adminUsername = trim(emj_val($input, 'admin_username', trim(emj_val($input, 'username', 'admin'))));
    $adminPassword = emj_val($input, 'admin_password', emj_val($input, 'password', 'admin123'));
    $operatorId = intval(emj_val($input, 'operator_id', 0));
    if (empty($tenantName)) emj_json(0, '请输入租户名称');
    $pdo = emg('db');
    if ($operatorId > 0) {
        // 校验运营商存在
        $stmt = $pdo->prepare("SELECT id FROM saas_platform_admins WHERE id = ? LIMIT 1");
        $stmt->execute(array($operatorId));
        if (!$stmt->fetch()) emj_json(0, '所属运营商不存在');
    }
    try {
        $pdo->beginTransaction();
        $expireTime = date('Y-m-d H:i:s', time() + 86400 * 365);
        $tenantSn = 'T' . date('YmdHis') . rand(100, 999);
        // v2.8.95 修复：saas_tenants.created_at/updated_at 为 INT 时间戳列，写 NOW() 触发 1264 越界拒绝
        $nowTs = time();
        $stmt = $pdo->prepare("INSERT INTO saas_tenants (tenant_sn, tenant_name, contact_name, contact_phone, operator_id, status, package_expire_time, created_at, updated_at, expire_time) VALUES (?, ?, ?, ?, ?, 1, ?, ?, ?, ?)");
        $stmt->execute(array($tenantSn, $tenantName, $contactName, $contactPhone, $operatorId, $nowTs + 86400 * 365, $nowTs, $nowTs, $expireTime));
        $tenantId = intval($pdo->lastInsertId());
        $stmt = $pdo->prepare("INSERT INTO saas_tenant_admins (tenant_id, username, password, realname, role, status, created_at) VALUES (?, ?, ?, ?, 'super_admin', 1, NOW())");
        $stmt->execute(array($tenantId, $adminUsername, password_hash($adminPassword, PASSWORD_BCRYPT, array('cost' => 10)), $contactName ?: $adminUsername));
        // 安装应用：dir26 运营商(id=1)名下租户免费授权全部应用；其他运营商仅安装免费应用
        try {
            $appWhere = ($operatorId === 1) ? 'status = 1' : 'status = 1 AND is_free = 1';
            $apps = $pdo->query("SELECT id, app_key, app_name, version, auth_mode FROM saas_apps WHERE $appWhere")->fetchAll(PDO::FETCH_ASSOC);
            $now = time();
            foreach ($apps ?: array() as $app) {
                $exists = $pdo->prepare("SELECT id FROM saas_tenant_apps WHERE tenant_id = ? AND app_key = ? LIMIT 1");
                $exists->execute(array($tenantId, $app['app_key']));
                if ($exists->fetch()) continue;
                $pdo->prepare("INSERT INTO saas_tenant_apps (tenant_id, app_id, app_key, granted_by, granted_operator_id, auth_type, version, current_version, expire_time, is_enabled, can_sub_grant, config_source, auth_mode, menu_name, status, is_installed, installed_at, last_upgrade_time, created_at, updated_at) VALUES (?, ?, ?, 0, ?, 'system', ?, ?, 0, 1, 0, 'inherit', ?, ?, 1, 1, ?, ?, ?, ?)")
                    ->execute(array($tenantId, $app['id'], $app['app_key'], $operatorId, $app['version'], $app['version'], $app['auth_mode'] ?: 'multi', $app['app_name'] ?: $app['app_key'], $now, $now, $now, $now));
            }
        } catch (Exception $e) {}
        $pdo->commit();
        // v2.8.95：id 与框架版 SuperController@tenantCreate 返回结构对齐（data.id）
        emj_json(1, '租户创建成功', array('id' => $tenantId, 'tenant_id' => $tenantId, 'admin_username' => $adminUsername, 'admin_password' => $adminPassword));
    } catch (Exception $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        emj_json(0, '创建失败: ' . $e->getMessage());
    }
}

// ========== 更新租户（公司信息 / 账号状态锁定） ==========
if ($requestPath === '/api/super/tenants/update' && $requestMethod === 'POST') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload || empty($payload['is_super'])) emj_json(0, '无权限', null, 403);
    $raw = file_get_contents('php://input');
    $input = json_decode($raw, true);
    if (!$input) $input = $_POST;
    $id = intval(emj_val($input, 'id', 0));
    if ($id <= 0) emj_json(0, '参数错误');
    $isMain = ($id === 1); // dir26 平台主租户
    // 锁定 ID=1：dir26 平台主租户不可禁用
    $status = array_key_exists('status', $input) ? intval($input['status']) : null;
    if ($isMain && $status !== null && $status !== 1) emj_json(0, '平台主账号(dir26)已锁定，不可禁用');
    try {
        $pdo = emg('db');
        // 收集可更新字段（白名单映射 + 参数绑定，杜绝注入）
        $sets = array();
        $params = array();
        $fieldMap = array(
            'tenant_name'   => 'tenant_name',
            'name'          => 'tenant_name',
            'contact_name'  => 'contact_name',
            'contact_phone' => 'contact_phone',
            'contact_email' => 'contact_email',
            'domain'        => 'domain',
            'sub_domain'    => 'sub_domain',
        );
        foreach ($fieldMap as $key => $col) {
            if (!array_key_exists($key, $input)) continue;
            $val = trim((string)$input[$key]);
            if ($isMain && $col === 'tenant_name') continue; // 主租户不可改名
            $sets[] = "`$col` = ?";
            $params[] = $val;
        }
        if ($status !== null) {
            $sets[] = '`status` = ?';
            $params[] = $status;
        }
        if (empty($sets)) emj_json(0, '没有需要更新的字段');
        // v2.8.95 修复：updated_at 为 INT 时间戳列，NOW() 触发 1264
        $sets[] = '`updated_at` = ?';
        $params[] = time();
        $params[] = $id; // WHERE 占位符
        $stmt = $pdo->prepare('UPDATE saas_tenants SET ' . implode(', ', $sets) . ' WHERE id = ?');
        $stmt->execute($params);
        emj_json(1, '保存成功');
    } catch (Exception $e) {
        emj_json(0, '操作失败: ' . $e->getMessage());
    }
}

// ========== 平台端：租户余额充值 ==========
if ($requestPath === '/api/super/tenants/recharge' && $requestMethod === 'POST') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload || empty($payload['is_super'])) emj_json(0, '无权限', null, 403);
    $raw = file_get_contents('php://input');
    $input = json_decode($raw, true);
    if (!$input) $input = $_POST;
    $id = intval(emj_val($input, 'id', 0));
    $amount = floatval(emj_val($input, 'amount', 0));
    $remark = trim(emj_val($input, 'remark', ''));
    if ($id <= 0 || $amount <= 0) emj_json(0, '参数错误');
    try {
        $pdo = emg('db');
        $pdo->beginTransaction();
        $stmt = $pdo->prepare("SELECT balance FROM saas_tenants WHERE id = ? LIMIT 1");
        $stmt->execute(array($id));
        $tenant = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$tenant) { $pdo->rollBack(); emj_json(0, '租户不存在'); }
        $newBalance = floatval($tenant['balance']) + $amount;
        // v2.8.95 修复：updated_at 为 INT 时间戳列，NOW() 触发 1264 导致余额更新被拒绝
        $pdo->prepare("UPDATE saas_tenants SET balance = ?, updated_at = ? WHERE id = ?")
            ->execute(array($newBalance, time(), $id));
        // 记录流水
        try {
            $pdo->prepare("INSERT INTO saas_tenant_finances (tenant_id, type, amount, balance_after, remark, from_tenant_id, created_at) VALUES (?, 'recharge', ?, ?, ?, 0, ?)")
                ->execute(array($id, $amount, $newBalance, $remark ?: '平台充值', time()));
        } catch (Exception $e) {}
        $pdo->commit();
        emj_json(1, '充值成功', array('balance' => $newBalance));
    } catch (Exception $e) {
        if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack();
        emj_json(0, '充值失败: ' . $e->getMessage());
    }
}

// ========== 平台端：重置租户后台密码 ==========
if ($requestPath === '/api/super/tenants/reset-password' && $requestMethod === 'POST') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload || empty($payload['is_super'])) emj_json(0, '无权限', null, 403);
    $raw = file_get_contents('php://input');
    $input = json_decode($raw, true);
    if (!$input) $input = $_POST;
    $id = intval(emj_val($input, 'id', 0));
    $password = emj_val($input, 'password', 'admin123');
    if ($id <= 0) emj_json(0, '参数错误');
    // 锁定 ID=1：dir26 平台主租户密码不可重置
    if ($id === 1) emj_json(0, '平台主账号(dir26)已锁定，不可重置密码');
    // 银行级安全 v2.7.66：密码强度校验
    if (strlen($password) < 8) emj_json(0, '密码长度不能少于8位');
    if (!preg_match('/[A-Za-z]/', $password)) emj_json(0, '密码必须包含字母');
    if (!preg_match('/\d/', $password)) emj_json(0, '密码必须包含数字');
    try {
        $pdo = emg('db');
        $stmt = $pdo->prepare("SELECT id, username FROM saas_tenant_admins WHERE tenant_id = ? ORDER BY id ASC LIMIT 1");
        $stmt->execute(array($id));
        $admin = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$admin) emj_json(0, '该租户没有后台账号');
        $hash = password_hash($password, PASSWORD_DEFAULT);
        $pdo->prepare("UPDATE saas_tenant_admins SET password = ?, updated_at = NOW() WHERE id = ?")
            ->execute(array($hash, $admin['id']));
        emj_json(1, '密码已重置', array('username' => $admin['username'], 'password' => $password));
    } catch (Exception $e) {
        emj_json(0, '重置失败: ' . $e->getMessage());
    }
}

// ========== 平台端基础配置 ==========
if (($requestPath === '/api/platform/auth/me' || $requestPath === '/api/tenant/auth/me') && $requestMethod === 'GET') {
    // 已在mePaths中处理
}

// ========== 平台端租户列表/创建/更新 交由框架处理（Platform\DashboardController@tenantList/saveTenant，含operator_id归属过滤）==========

// ========== 超管注册（自助注册→待审核状态）==========
if ($requestPath === '/api/platform/auth/register' && $requestMethod === 'POST') {
    $raw = file_get_contents('php://input');
    $input = json_decode($raw, true);
    if (!$input) $input = $_POST;
    $username = trim(emj_val($input, 'username'));
    $password = emj_val($input, 'password');
    $companyName = trim(emj_val($input, 'company_name'));
    $contactName = trim(emj_val($input, 'realname'));
    $contactPhone = trim(emj_val($input, 'phone'));
    $contactEmail = trim(emj_val($input, 'email'));
    $captchaId = emj_val($input, 'captcha_id');
    $captchaCode = emj_val($input, 'captcha_code');
    if (empty($username) || strlen($username) < 3) emj_json(0, '用户名至少3个字符');
    // 银行级安全 v2.7.66：密码强度校验
    if (empty($password) || mb_strlen($password) < 8) emj_json(0, '密码长度不能少于8位');
    if (!preg_match('/[A-Za-z]/', $password)) emj_json(0, '密码必须包含字母');
    if (!preg_match('/\d/', $password)) emj_json(0, '密码必须包含数字');
    if (empty($companyName)) emj_json(0, '请填写公司名称');
    if (empty($contactPhone)) emj_json(0, '请填写联系电话');
    if (empty($captchaCode)) emj_json(0, '请输入验证码');
    if (!emj_verify_captcha($captchaId, $captchaCode)) emj_json(0, '验证码错误');
    try {
        $pdo = emg('db');
        // 检查用户名是否已存在
        $stmt = $pdo->prepare("SELECT id FROM saas_platform_admins WHERE username = ? LIMIT 1");
        $stmt->execute(array($username));
        if ($stmt->fetch()) emj_json(0, '用户名已被注册');
        // 创建超管账号（待审核+未授权）
        $stmt = $pdo->prepare("INSERT INTO saas_platform_admins (username, password, realname, phone, company_name, contact_email, role, license_status, audit_status, max_tenants, brand_name, theme_color, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, 'super', 0, 0, 3, ?, '#667eea', 1, NOW(), NOW())");
        $stmt->execute(array($username, password_hash($password, PASSWORD_BCRYPT, array('cost' => 10)), $contactName ?: $username, $contactPhone, $companyName, $contactEmail, $companyName));
        $newId = intval($pdo->lastInsertId());
        // 给平台方发新注册通知
        try {
            $pdo->prepare("INSERT INTO saas_platform_notifications (type, title, content, link, is_read, created_at) VALUES ('new_register', ?, ?, '/super/#/operators', 0, NOW())")
                ->execute(array('新超管注册待审核', '超管账号「' . $username . '」（' . $companyName . '）已注册，请尽快审核'));
        } catch (Exception $e) {}
        emj_json(1, '注册成功，请等待平台审核通过后激活授权', array('admin_id' => $newId));
    } catch (Exception $e) {
        emj_json(0, '注册失败: ' . $e->getMessage());
    }
}

// ========== 超管授权码激活（支持独立部署远程校验；状态语义 0=未使用,1=已激活,2=已作废）==========
if ($requestPath === '/api/platform/license/activate' && $requestMethod === 'POST') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload || empty($payload['admin_id']) || empty($payload['is_platform'])) emj_json(0, '无权限', null, 403);
    $raw = file_get_contents('php://input');
    $input = json_decode($raw, true);
    if (!$input) $input = $_POST;
    $licenseKey = trim(emj_val($input, 'license_key'));
    if (empty($licenseKey)) emj_json(0, '请输入授权码');
    try {
        $pdo = emg('db');
        // 1. 本地库查找授权码
        $stmt = $pdo->prepare("SELECT * FROM saas_licenses WHERE license_key = ? LIMIT 1");
        $stmt->execute(array($licenseKey));
        $license = $stmt->fetch(PDO::FETCH_ASSOC);
        $fromRemote = false;
        if (!$license) {
            // 独立部署场景：本地库无该授权码 → 远程校验官方平台
            $remote = emj_remote_license_verify($licenseKey);
            if ($remote && !empty($remote['license_key'])) {
                $license = array(
                    'id' => 0,
                    'license_key' => $remote['license_key'],
                    'license_type' => emj_val($remote, 'license_type', 'standard'),
                    'max_tenants' => intval(emj_val($remote, 'max_tenants', 3)),
                    'duration_days' => intval(emj_val($remote, 'duration_days', 365)),
                    'expire_time' => emj_val($remote, 'expire_time', ''),
                    'domain' => emj_val($remote, 'domain', ''),
                    'domains' => emj_val($remote, 'domains', '[]'),
                );
                $fromRemote = true;
            } else {
                emj_json(0, '授权码无效，请联系平台方获取');
            }
        }
        if (!$fromRemote && intval($license['status']) !== 0) emj_json(0, '授权码已被使用或已作废');
        // 2. 检查超管账号
        $stmt = $pdo->prepare("SELECT * FROM saas_platform_admins WHERE id = ? LIMIT 1");
        $stmt->execute(array($payload['admin_id']));
        $admin = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$admin) emj_json(0, '账号不存在');
        if (intval($admin['audit_status']) !== 1) emj_json(0, '账号尚未通过平台审核，请联系平台方');
        if (intval($admin['license_status']) === 1) emj_json(0, '该账号已激活授权，无需重复激活');
        // 3. 计算到期时间（platform_admins.license_expire_at 为 INT 时间戳，licenses.expire_time 为 datetime）
        $durationDays = intval(emj_val($license, 'duration_days', 365));
        if ($durationDays <= 0) {
            $expireTs = 0;                            // INT 时间戳：0=永久
            $expireDt = '2099-12-31 23:59:59';        // datetime：永久
        } else {
            $expireTs = time() + $durationDays * 86400;
            $expireDt = date('Y-m-d H:i:s', $expireTs);
        }
        $maxTenants = intval($license['max_tenants']) > 0 ? intval($license['max_tenants']) : 3;
        $licenseType = emj_val($license, 'license_type', 'standard');
        // 4. 授权域名（平台端生成授权码时绑定，用于独立部署域名授权管理）
        $allDomains = array();
        $licenseDomain = trim(emj_val($license, 'domain', ''));
        if ($licenseDomain) $allDomains[] = $licenseDomain;
        $extraDomains = emj_val($license, 'domains', '');
        if ($extraDomains) {
            $parsed = json_decode($extraDomains, true);
            if (is_array($parsed)) {
                foreach ($parsed as $d) { $d = trim($d); if ($d && !in_array($d, $allDomains)) $allDomains[] = $d; }
            }
        }
        $authorizedDomainsJson = json_encode($allDomains, JSON_UNESCAPED_UNICODE);
        // 5. 激活
        $pdo->beginTransaction();
        $stmt = $pdo->prepare("UPDATE saas_platform_admins SET license_key = ?, license_status = 1, license_expire_at = ?, license_type = ?, max_tenants = ?, authorized_domains = ?, custom_domain = IF(? <> '', ?, custom_domain), updated_at = NOW() WHERE id = ?");
        $stmt->execute(array($licenseKey, $expireTs, $licenseType, $maxTenants, $authorizedDomainsJson, $licenseDomain, $licenseDomain, $payload['admin_id']));
        if (!$fromRemote) {
            $stmt = $pdo->prepare("UPDATE saas_licenses SET status = 1, activated_at = NOW(), activated_by = ?, expire_time = ?, remark = ? WHERE id = ?");
            $stmt->execute(array($payload['admin_id'], $expireDt, '激活超管:' . $admin['username'] . '(' . $admin['company_name'] . ')', $license['id']));
        } else {
            // 远程激活成功：本地登记授权记录，便于离线查询
            $stmt = $pdo->prepare("INSERT INTO saas_licenses (license_key_prefix, license_key, license_type, max_tenants, duration_days, expire_time, domain, domains, status, activated_at, activated_by, remark, created_at, updated_at) VALUES ('PLAT', ?, ?, ?, ?, ?, ?, ?, 1, NOW(), ?, ?, NOW(), NOW())");
            $stmt->execute(array($licenseKey, $licenseType, $maxTenants, $durationDays, $expireDt, $licenseDomain, $extraDomains !== '' ? $extraDomains : '[]', $payload['admin_id'], '独立部署激活（远程校验）'));
        }
        // 给平台发激活通知
        try {
            $pdo->prepare("INSERT INTO saas_platform_notifications (type, title, content, link, is_read, created_at) VALUES ('license_activated', ?, ?, '/super/#/operators', 0, NOW())")
                ->execute(array('超管已激活授权', '超管「' . $admin['username'] . '」（' . $admin['company_name'] . '）已使用授权码激活'));
        } catch (Exception $e) {}
        $pdo->commit();
        emj_json(1, '授权激活成功！');
    } catch (Exception $e) {
        if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack();
        emj_json(0, '激活失败: ' . $e->getMessage());
    }
}

// ========== 超管获取授权状态 ==========
if ($requestPath === '/api/platform/license/status' && $requestMethod === 'GET') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload || empty($payload['admin_id']) || empty($payload['is_platform'])) emj_json(0, '无权限', null, 403);
    try {
        $pdo = emg('db');
        $stmt = $pdo->prepare("SELECT license_status, license_key, license_expire_at, license_type, max_tenants, audit_status, audit_remark, company_name, brand_name, brand_logo, theme_color, custom_domain, icp, copyright FROM saas_platform_admins WHERE id = ? LIMIT 1");
        $stmt->execute(array($payload['admin_id']));
        $info = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$info) emj_json(0, '账号不存在');
        $tenantCount = 0;
        try {
            $stmt2 = $pdo->prepare("SELECT COUNT(*) as cnt FROM saas_tenants WHERE operator_id = ?");
            $stmt2->execute(array($payload['admin_id']));
            $r = $stmt2->fetch(PDO::FETCH_ASSOC);
            $tenantCount = intval($r['cnt']);
        } catch (Exception $e) {}
        emj_json(1, 'success', array_merge($info, array('tenant_count' => $tenantCount)));
    } catch (Exception $e) {
        emj_json(0, '获取失败: ' . $e->getMessage());
    }
}

// ========== 超管保存贴牌设置 ==========
if ($requestPath === '/api/platform/oem/save' && $requestMethod === 'POST') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload || empty($payload['admin_id']) || empty($payload['is_platform'])) emj_json(0, '无权限', null, 403);
    $raw = file_get_contents('php://input');
    $input = json_decode($raw, true);
    if (!$input) $input = $_POST;
    try {
        $pdo = emg('db');
        $fields = array();
        $values = array();
        $allowFields = array('brand_name', 'brand_logo', 'brand_slogan', 'theme_color', 'custom_domain', 'icp', 'copyright', 'company_name', 'contact_email');
        foreach ($allowFields as $f) {
            if (isset($input[$f])) {
                $fields[] = "$f = ?";
                $values[] = $input[$f];
            }
        }
        if (empty($fields)) emj_json(0, '没有要保存的内容');
        $values[] = $payload['admin_id'];
        $sql = "UPDATE saas_platform_admins SET " . implode(', ', $fields) . ", updated_at = NOW() WHERE id = ?";
        $stmt = $pdo->prepare($sql);
        $stmt->execute($values);
        emj_json(1, '贴牌设置已保存');
    } catch (Exception $e) {
        emj_json(0, '保存失败: ' . $e->getMessage());
    }
}

// ========== 超管获取消息通知列表 ==========
if ($requestPath === '/api/platform/notifications' && $requestMethod === 'GET') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload || empty($payload['admin_id']) || empty($payload['is_platform'])) emj_json(0, '无权限', null, 403);
    try {
        $pdo = emg('db');
        $stmt = $pdo->prepare("SELECT id, type, title, content, link, is_read, created_at FROM saas_operator_notifications WHERE operator_id = ? ORDER BY id DESC LIMIT 50");
        $stmt->execute(array($payload['admin_id']));
        $list = $stmt->fetchAll(PDO::FETCH_ASSOC);
        emj_json(1, 'success', array('list' => $list ?: array()));
    } catch (Exception $e) {
        emj_json(1, 'success', array('list' => array()));
    }
}

// ========== 超管标记消息已读 ==========
if ($requestPath === '/api/platform/notifications/read' && $requestMethod === 'POST') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload || empty($payload['admin_id']) || empty($payload['is_platform'])) emj_json(0, '无权限', null, 403);
    try {
        $pdo = emg('db');
        // v2.8.95 修复：read_at 为 INT 时间戳列，NOW() 触发 1264
        $pdo->prepare("UPDATE saas_operator_notifications SET is_read = 1, read_at = ? WHERE operator_id = ?")->execute(array(time(), $payload['admin_id']));
        emj_json(1, '已标记全部已读');
    } catch (Exception $e) {
        emj_json(0, '操作失败');
    }
}

// ========== 平台方：超管列表（待审核/已审核/已禁用）==========
if ($requestPath === '/api/super/operators' && $requestMethod === 'GET') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload || empty($payload['is_super'])) emj_json(0, '无权限', null, 403);
    $status = intval(emj_val($_GET, 'status', -1));
    $keyword = trim(emj_val($_GET, 'keyword', ''));
    $page = max(1, intval(emj_val($_GET, 'page', 1)));
    $pageSize = min(100, intval(emj_val($_GET, 'page_size', 20)));
    $offset = ($page - 1) * $pageSize;
    try {
        $pdo = emg('db');
        $where = array('1=1');
        $params = array();
        if ($status >= 0) { $where[] = 'audit_status = ?'; $params[] = $status; }
        if ($keyword !== '') { $where[] = '(username LIKE ? OR company_name LIKE ? OR phone LIKE ?)'; $kw = "%$keyword%"; $params[] = $kw; $params[] = $kw; $params[] = $kw; }
        $whereSql = implode(' AND ', $where);
        $stmt = $pdo->prepare("SELECT COUNT(*) as cnt FROM saas_platform_admins WHERE $whereSql");
        $stmt->execute($params);
        $total = intval($stmt->fetch(PDO::FETCH_ASSOC)['cnt']);
        $sql = "SELECT id, username, realname, phone, company_name, contact_email, license_key, license_status, license_expire_at, license_type, audit_status, audit_remark, max_tenants, status, created_at, last_login_time, last_login_ip FROM saas_platform_admins WHERE $whereSql ORDER BY id DESC LIMIT $offset, $pageSize";
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        $list = $stmt->fetchAll(PDO::FETCH_ASSOC);
        // 附加每个超管的租户数
        foreach ($list as &$item) {
            try {
                $s2 = $pdo->prepare("SELECT COUNT(*) as cnt FROM saas_tenants WHERE operator_id = ?");
                $s2->execute(array($item['id']));
                $item['tenant_count'] = intval($s2->fetch(PDO::FETCH_ASSOC)['cnt']);
            } catch (Exception $e) { $item['tenant_count'] = 0; }
        }
        emj_json(1, 'success', array('list' => $list ?: array(), 'total' => $total, 'page' => $page, 'page_size' => $pageSize));
    } catch (Exception $e) {
        emj_json(1, 'success', array('list' => array(), 'total' => 0));
    }
}

// ========== 平台方：审核超管（通过/拒绝/拉黑）==========
if ($requestPath === '/api/super/operators/audit' && $requestMethod === 'POST') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload || empty($payload['is_super'])) emj_json(0, '无权限', null, 403);
    $raw = file_get_contents('php://input');
    $input = json_decode($raw, true);
    if (!$input) $input = $_POST;
    $id = intval(emj_val($input, 'id', 0));
    $action = emj_val($input, 'action'); // approve/reject/ban
    $remark = trim(emj_val($input, 'remark', ''));
    if ($id <= 0) emj_json(0, '参数错误');
    // 锁定 ID=1：dir26 平台主运营商不可封禁/拒绝
    if ($id === 1 && in_array($action, array('reject', 'ban'))) emj_json(0, '平台主运营商(dir26)已锁定，不可封禁或拒绝');
    if (!in_array($action, array('approve', 'reject', 'ban'))) emj_json(0, '操作类型错误');
    try {
        $pdo = emg('db');
        $stmt = $pdo->prepare("SELECT * FROM saas_platform_admins WHERE id = ? LIMIT 1");
        $stmt->execute(array($id));
        $admin = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$admin) emj_json(0, '超管账号不存在');
        if ($action === 'approve') {
            $pdo->prepare("UPDATE saas_platform_admins SET audit_status = 1, audit_remark = ?, status = 1, updated_at = NOW() WHERE id = ?")->execute(array($remark, $id));
            $noticeTitle = '审核已通过';
            $noticeContent = '您的超管账号已通过平台审核，请输入授权码激活使用';
        } elseif ($action === 'reject') {
            $pdo->prepare("UPDATE saas_platform_admins SET audit_status = 2, audit_remark = ?, status = 0, updated_at = NOW() WHERE id = ?")->execute(array($remark, $id));
            $noticeTitle = '审核未通过';
            $noticeContent = '您的注册审核未通过' . ($remark ? '，原因：' . $remark : '');
        } else { // ban
            $pdo->prepare("UPDATE saas_platform_admins SET audit_status = 2, status = 0, audit_remark = ?, license_status = 3, updated_at = NOW() WHERE id = ?")->execute(array($remark ?: '账号已被平台封禁', $id));
            $noticeTitle = '账号已被封禁';
            $noticeContent = '您的账号已被平台封禁' . ($remark ? '，原因：' . $remark : '');
        }
        // 发通知给超管
        try {
            $pdo->prepare("INSERT INTO saas_operator_notifications (operator_id, type, title, content, is_read, created_at) VALUES (?, ?, ?, ?, 0, NOW())")
                ->execute(array($id, 'audit', $noticeTitle, $noticeContent));
        } catch (Exception $e) {}
        emj_json(1, '操作成功');
    } catch (Exception $e) {
        emj_json(0, '操作失败: ' . $e->getMessage());
    }
}

// ========== 平台方：授权码列表 ==========
if ($requestPath === '/api/super/licenses' && $requestMethod === 'GET') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload || empty($payload['is_super'])) emj_json(0, '无权限', null, 403);
    $status = intval(emj_val($_GET, 'status', -1));
    $page = max(1, intval(emj_val($_GET, 'page', 1)));
    $pageSize = min(100, intval(emj_val($_GET, 'page_size', 20)));
    $offset = ($page - 1) * $pageSize;
    try {
        $pdo = emg('db');
        $where = array('1=1');
        $params = array();
        if ($status >= 0) { $where[] = 'status = ?'; $params[] = $status; }
        $whereSql = implode(' AND ', $where);
        $stmt = $pdo->prepare("SELECT COUNT(*) as cnt FROM saas_licenses WHERE $whereSql");
        $stmt->execute($params);
        $total = intval($stmt->fetch(PDO::FETCH_ASSOC)['cnt']);
        $stmt = $pdo->prepare("SELECT * FROM saas_licenses WHERE $whereSql ORDER BY id DESC LIMIT $offset, $pageSize");
        $stmt->execute($params);
        $list = $stmt->fetchAll(PDO::FETCH_ASSOC);
        emj_json(1, 'success', array('list' => $list ?: array(), 'total' => $total));
    } catch (Exception $e) {
        emj_json(1, 'success', array('list' => array(), 'total' => 0));
    }
}

// ========== 平台方：生成授权码（支持域名绑定，独立部署用）==========
if ($requestPath === '/api/super/licenses/generate' && $requestMethod === 'POST') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload || empty($payload['is_super'])) emj_json(0, '无权限', null, 403);
    $raw = file_get_contents('php://input');
    $input = json_decode($raw, true);
    if (!$input) $input = $_POST;
    $licenseType = emj_val($input, 'license_type', 'standard');
    $maxTenants = intval(emj_val($input, 'max_tenants', 3));
    $durationDays = intval(emj_val($input, 'duration_days', 365));
    $count = min(100, max(1, intval(emj_val($input, 'count', 1))));
    $remark = trim(emj_val($input, 'remark', ''));
    $domain = trim(emj_val($input, 'domain', ''));
    $extraDomains = emj_val($input, 'extra_domains', '');
    if (is_array($extraDomains)) {
        $extraDomains = array_values(array_filter(array_map('trim', $extraDomains)));
        $extraDomains = implode("\n", $extraDomains);
    }
    $extraDomains = trim((string)$extraDomains);
    $contactName = trim(emj_val($input, 'contact_name', ''));
    $contactPhone = trim(emj_val($input, 'contact_phone', ''));
    $contactEmail = trim(emj_val($input, 'contact_email', ''));
    // 允许 PLAT/SL 前缀（前端传 license_type 时若带前缀则使用）
    $prefix = trim(emj_val($input, 'license_key_prefix', 'SL'));
    if (!preg_match('/^[A-Z0-9]{2,6}$/i', $prefix)) $prefix = 'SL';
    $prefix = strtoupper($prefix);
    try {
        $pdo = emg('db');
        $codes = array();
        $pdo->beginTransaction();
        $chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
        for ($i = 0; $i < $count; $i++) {
            $key = $prefix . '-';
            for ($j = 0; $j < 16; $j++) $key .= $chars[mt_rand(0, strlen($chars) - 1)];
            // 格式化为PREFIX-XXXX-XXXX-XXXX
            $key = $prefix . '-' . substr($key, strlen($prefix) + 1, 4) . '-' . substr($key, strlen($prefix) + 5, 4) . '-' . substr($key, strlen($prefix) + 9, 4) . '-' . substr($key, strlen($prefix) + 13, 4);
            // expire_time 字段为 datetime 类型，需用日期格式（0/负数=永久）
            $expireTime = $durationDays > 0 ? date('Y-m-d H:i:s', time() + $durationDays * 86400) : '2099-12-31 23:59:59';
            $stmt = $pdo->prepare("INSERT INTO saas_licenses (license_key_prefix, license_key, license_type, max_tenants, duration_days, expire_time, domain, domains, contact_name, contact_phone, contact_email, status, remark, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, NOW(), NOW())");
            $stmt->execute(array($prefix, $key, $licenseType, $maxTenants, $durationDays, $expireTime, $domain, $extraDomains !== '' ? json_encode(array_values(array_filter(array_map('trim', explode("\n", str_replace("\r", "", $extraDomains)))))) : '[]', $contactName, $contactPhone, $contactEmail, $remark));
            $codes[] = $key;
        }
        $pdo->commit();
        emj_json(1, '成功生成' . $count . '个授权码', array('codes' => $codes));
    } catch (Exception $e) {
        if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack();
        emj_json(0, '生成失败: ' . $e->getMessage());
    }
}

// ========== 平台方：作废授权码（未使用0/已激活1 均可作废，作废后无法再激活）==========
if ($requestPath === '/api/super/licenses/revoke' && $requestMethod === 'POST') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload || empty($payload['is_super'])) emj_json(0, '无权限', null, 403);
    $raw = file_get_contents('php://input');
    $input = json_decode($raw, true);
    if (!$input) $input = $_POST;
    $id = intval(emj_val($input, 'id', 0));
    if ($id <= 0) emj_json(0, '参数错误');
    try {
        $pdo = emg('db');
        $stmt = $pdo->prepare("UPDATE saas_licenses SET status = 2, updated_at = NOW() WHERE id = ? AND status IN (0, 1)");
        $stmt->execute(array($id));
        emj_json(1, '已作废');
    } catch (Exception $e) {
        emj_json(0, '操作失败');
    }
}

// ========== 平台方：平台消息通知列表 ==========
if ($requestPath === '/api/super/notifications' && $requestMethod === 'GET') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload || empty($payload['is_super'])) emj_json(0, '无权限', null, 403);
    try {
        $pdo = emg('db');
        $stmt = $pdo->query("SELECT id, type, title, content, link, is_read, created_at FROM saas_operator_notifications WHERE operator_id = 0 ORDER BY id DESC LIMIT 50");
        $list = $stmt->fetchAll(PDO::FETCH_ASSOC);
        emj_json(1, 'success', array('list' => $list ?: array()));
    } catch (Exception $e) {
        emj_json(1, 'success', array('list' => array()));
    }
}

// ========== 平台方：超管列表 ==========
if (strpos($requestPath, '/api/super/operators/list') === 0 && $requestMethod === 'GET') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload || empty($payload['is_super'])) emj_json(0, '无权限', null, 403);
    $keyword = isset($_GET['keyword']) ? trim($_GET['keyword']) : '';
    $auditStatus = isset($_GET['audit_status']) ? intval($_GET['audit_status']) : -1;
    try {
        $pdo = emg('db');
        $where = '1=1';
        $params = array();
        if ($keyword !== '') {
            $where .= ' AND (username LIKE ? OR company_name LIKE ? OR realname LIKE ? OR phone LIKE ?)';
            $kw = '%' . $keyword . '%';
            $params[] = $kw; $params[] = $kw; $params[] = $kw; $params[] = $kw;
        }
        if ($auditStatus >= 0) {
            $where .= ' AND audit_status = ?';
            $params[] = $auditStatus;
        }
        $sql = "SELECT id, username, company_name, realname, phone, audit_status, license_status, license_key, license_type, license_expire_at, max_tenants, status, custom_domain, authorized_domains, created_at,
                (SELECT COUNT(*) FROM saas_tenants WHERE operator_id = saas_platform_admins.id) AS tenant_count
                FROM saas_platform_admins WHERE " . $where . " ORDER BY id DESC LIMIT 200";
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        $list = $stmt->fetchAll(PDO::FETCH_ASSOC);
        emj_json(1, 'success', array('list' => $list ?: array(), 'total' => count($list)));
    } catch (Exception $e) {
        emj_json(1, 'success', array('list' => array(), 'total' => 0));
    }
}

// ========== 平台方：运营商域名授权管理（独立部署运营商绑定/解绑授权域名）==========
if ($requestPath === '/api/super/operators/domains' && $requestMethod === 'POST') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload || empty($payload['is_super'])) emj_json(0, '无权限', null, 403);
    $raw = file_get_contents('php://input');
    $input = json_decode($raw, true);
    if (!$input) $input = $_POST;
    $id = intval(emj_val($input, 'id', 0));
    $domains = emj_val($input, 'domains', array());
    if ($id <= 0) emj_json(0, '参数错误');
    if (is_string($domains)) {
        $domains = array_values(array_filter(array_map('trim', preg_split('/[\r\n,]+/', $domains))));
    }
    $domains = array_values(array_filter(array_map('trim', (array)$domains)));
    try {
        $pdo = emg('db');
        $stmt = $pdo->prepare("SELECT id FROM saas_platform_admins WHERE id = ? LIMIT 1");
        $stmt->execute(array($id));
        if (!$stmt->fetch()) emj_json(0, '运营商不存在');
        $domainJson = json_encode($domains, JSON_UNESCAPED_UNICODE);
        $customDomain = isset($domains[0]) ? $domains[0] : '';
        $pdo->prepare("UPDATE saas_platform_admins SET authorized_domains = ?, custom_domain = ?, updated_at = NOW() WHERE id = ?")
            ->execute(array($domainJson, $customDomain, $id));
        emj_json(1, '域名授权已更新', array('domains' => $domains));
    } catch (Exception $e) {
        emj_json(0, '操作失败: ' . $e->getMessage());
    }
}

// ========== 平台方：授权码列表（/list 路径）==========
if (strpos($requestPath, '/api/super/licenses/list') === 0 && $requestMethod === 'GET') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload || empty($payload['is_super'])) emj_json(0, '无权限', null, 403);
    try {
        $pdo = emg('db');
        $stmt = $pdo->query("SELECT l.id, l.license_key, l.license_type, l.max_tenants, l.duration_days, l.status, l.remark,
                l.activated_at, l.expire_time, l.created_at,
                a.username AS activated_by
                FROM saas_licenses l LEFT JOIN saas_platform_admins a ON l.activated_by = a.id
                ORDER BY l.id DESC LIMIT 200");
        $list = $stmt->fetchAll(PDO::FETCH_ASSOC);
        // 补充remark显示激活者
        foreach ($list as &$row) {
            if ($row['status'] == 0 && $row['activated_by']) {
                $row['remark'] = ($row['remark'] ? $row['remark'] . ' / ' : '') . '激活者：' . $row['activated_by'];
            }
        }
        emj_json(1, 'success', array('list' => $list ?: array()));
    } catch (Exception $e) {
        emj_json(1, 'success', array('list' => array()));
    }
}

// ========== 平台方：消息通知列表（/list 路径）==========
if (strpos($requestPath, '/api/super/notifications/list') === 0 && $requestMethod === 'GET') {
    $token = emj_get_token();
    if (empty($token)) emj_json(0, '未登录', null, 401);
    $payload = emj_jwt_decode($token);
    if (!$payload || empty($payload['is_super'])) emj_json(0, '无权限', null, 403);
    try {
        $pdo = emg('db');
        $stmt = $pdo->query("SELECT id, type, title, content, link, is_read, created_at FROM saas_platform_notifications ORDER BY id DESC LIMIT 50");
        $list = $stmt->fetchAll(PDO::FETCH_ASSOC);
        emj_json(1, 'success', array('list' => $list ?: array()));
    } catch (Exception $e) {
        // 返回模拟数据
        emj_json(1, 'success', array('list' => array(
            array('id'=>1, 'type'=>'system', 'title'=>'欢迎使用稼云SaaS平台', 'content'=>'系统已初始化完成，请先生成授权码然后发放给超管/运营商使用。', 'is_read'=>0, 'created_at'=>date('Y-m-d H:i:s'))
        )));
    }
}

// ========== 通用静态文件服务 ==========
$filePath = ROOT_PATH . $requestPath;
if (strpos($requestPath, '/uploads/') === 0) {
    $filePath = ROOT_PATH . '/public' . $requestPath;
}
if (is_file($filePath) && preg_match('/\.(html?|js|css|png|jpg|jpeg|gif|ico|svg|woff2?|ttf|eot|json|pdf|pptx|docx|xlsx|zip)$/i', $requestPath)) {
    $mimes = ['html'=>'text/html','js'=>'application/javascript','css'=>'text/css','png'=>'image/png','jpg'=>'image/jpeg','jpeg'=>'image/jpeg','gif'=>'image/gif','ico'=>'image/x-icon','svg'=>'image/svg+xml','json'=>'application/json'];
    $ext = pathinfo($requestPath, PATHINFO_EXTENSION);
    if (isset($mimes[$ext])) header('Content-Type: ' . $mimes[$ext] . '; charset=utf-8');
    readfile($filePath);
    exit;
}

// ========== 独立域名直达菲录登录前台 ==========
// 当访问域名匹配某租户绑定的自定义域名（saas_tenants.domain）时，首页直接跳转该租户的
// 菲录综合登录前台（厂长登录 + 工人登录），实现"绑定域名 → 打开即厂长登录"
if (($requestPath === '/' || $requestPath === '') && strpos($_SERVER['HTTP_HOST'] ?? '', '127.0.0.1') === false) {
    try {
        $host = preg_replace('/:\d+$/', '', (string)($_SERVER['HTTP_HOST'] ?? ''));
        if ($host) {
            $pdo = emg('db');
            $stmt = $pdo->prepare("SELECT id FROM saas_tenants WHERE domain = ? AND status = 1 LIMIT 1");
            $stmt->execute(array($host));
            $t = $stmt->fetch(PDO::FETCH_ASSOC);
            if ($t) {
                $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
                $url = $scheme . '://' . $host . '/apps/da-fei/login.html?tenant_id=' . intval($t['id']);
                // v2.9.1 锁死登录页排版：保留原查询参数（APP 壳传入的 topbar_h/_t/app_ver 等）。
                // 此前直接 302 丢弃参数，APP 内登录页拿不到状态栏高度 → 沉浸式状态栏遮挡排版错乱。
                $qs = trim((string)($_SERVER['QUERY_STRING'] ?? ''));
                if ($qs !== '') {
                    parse_str($qs, $qarr);
                    unset($qarr['tenant_id']);
                    if (!empty($qarr)) {
                        $url .= '&' . http_build_query($qarr);
                    }
                }
                header('Location: ' . $url);
                exit;
            }
            // v2.8.90：应用级独立域名（saas_tenant_apps.domain）——运营后台「租户管理→应用域名」
            // 按应用绑定，命中后按 UA 302 直达应用入口（PC 管理后台 / H5 移动端）。
            // 优先级与 TenantContext 一致：租户级域名 > 应用级域名；后台改绑/解绑即时生效
            $stmt = $pdo->prepare("SELECT app_key FROM saas_tenant_apps WHERE domain = ? AND is_enabled = 1 AND (expire_time = 0 OR expire_time > ?) LIMIT 1");
            $stmt->execute(array($host, time()));
            $ta = $stmt->fetch(PDO::FETCH_ASSOC);
            if ($ta && !empty($ta['app_key'])) {
                $isMobile = (bool)preg_match('/(Mobile|Android|iPhone|iPod|iPad|Windows Phone|MicroMessenger|UCBrowser|MQQBrowser|baiduboxapp|harmonyos)/i', (string)($_SERVER['HTTP_USER_AGENT'] ?? ''));
                $appEntries = array(
                    'da-fei' => $isMobile ? '/apps/da-fei/mobile.html' : '/apps/da-fei/index.html',
                );
                if (isset($appEntries[$ta['app_key']])) {
                    // v2.9.1 锁死登录页排版：保留原查询参数（topbar_h/_t/app_ver 等），
                    // 避免 302 到应用入口时丢失 APP 状态栏高度/缓存戳
                    $qs = trim((string)($_SERVER['QUERY_STRING'] ?? ''));
                    header('Location: ' . $appEntries[$ta['app_key']] . ($qs === '' ? '' : '?' . $qs), true, 302);
                    exit;
                }
            }
        }
    } catch (Exception $e) {
        // 域名解析失败静默降级，走正常主页
    }
}

// ========== 框架启动（紧急直通未命中时走框架）==========
try {
    // 注册自动加载器
    spl_autoload_register(function ($className) {
        $basePath = __DIR__ . '/server';
        // 应用目录名与命名空间映射（PHP命名空间不允许连字符，类名 Dafei 映射到目录 da-fei）
        if (strpos($className, 'app\\Apps\\') === 0) {
            $rest = substr($className, strlen('app\\Apps\\'));
            $segments = explode('\\', $rest);
            $dir = $segments[0] ?? '';
            $appDirAlias = ['Dafei' => 'da-fei', 'HumanMirror' => 'human-mirror', 'Manju' => 'manju', 'WechatAuth' => 'wechat-auth', 'AiCs' => 'ai-cs', 'JiayunAi' => 'jiayun-ai', 'KnowledgeBase' => 'knowledge-base', 'ContentStudio' => 'content-studio', 'ShenglingAi' => 'shengling-ai', 'AiCommerce' => 'ai-commerce', 'MemberCard' => 'member-card', 'BizCard' => 'biz-card', 'StickerAi' => 'sticker-ai', 'Codechain' => 'codechain'];
            if (isset($appDirAlias[$dir])) {
                $file = $basePath . '/app/Apps/' . $appDirAlias[$dir] . '/' . implode('/', array_slice($segments, 1)) . '.php';
                if (file_exists($file)) { require_once $file; return true; }
            }
        }
        $classMap = array(
            'core\\' => $basePath . '/core/',
            'app\\' => $basePath . '/app/',
        );
        foreach ($classMap as $namespace => $path) {
            if (strpos($className, $namespace) === 0) {
                $relativeClass = substr($className, strlen($namespace));
                $file = $path . str_replace('\\', '/', $relativeClass) . '.php';
                if (file_exists($file)) { require_once $file; return true; }
                if ($namespace === 'app\\') {
                    $file = $basePath . '/app/Apps/' . str_replace('\\', '/', $relativeClass) . '.php';
                    if (file_exists($file)) { require_once $file; return true; }
                }
            }
        }
        return false;
    });

    // 辅助函数
    if (!function_exists('root_path')) {
        function root_path($p = '') { return ROOT_PATH . ($p ? DIRECTORY_SEPARATOR . ltrim($p, '/\\') : ''); }
    }
    if (!function_exists('app_path')) {
        function app_path($p = '') { return ROOT_PATH . '/server/app' . ($p ? DIRECTORY_SEPARATOR . ltrim($p, '/\\') : ''); }
    }
    if (!function_exists('runtime_path')) {
        function runtime_path($p = '') { return ROOT_PATH . '/server/runtime' . ($p ? DIRECTORY_SEPARATOR . ltrim($p, '/\\') : ''); }
    }
    if (!function_exists('public_path')) {
        function public_path($p = '') { return ROOT_PATH . '/public' . ($p ? DIRECTORY_SEPARATOR . ltrim($p, '/\\') : ''); }
    }
    if (!function_exists('config')) {
        function config($key, $default = null) { return \core\App::getConfig($key, $default); }
    }
    if (!function_exists('response')) {
        function response() { return new \core\Response(); }
    }
    if (!function_exists('json_response')) {
        function json_response($data, $code = 200) { return (new \core\Response())->json($data, $code); }
    }

    // 手动加载core文件
    $coreFiles = array('App.php', 'Request.php', 'Response.php', 'Router.php', 'Db.php', 'DbRaw.php', 'Jwt.php', 'Controller.php', 'Cache.php', 'Security.php', 'AppContext.php', 'TenantContext.php', 'License.php', 'UpgradeService.php');
    foreach ($coreFiles as $cf) {
        $cfp = __DIR__ . '/server/core/' . $cf;
        if (file_exists($cfp)) { require_once $cfp; }
    }

    // 加载全局辅助函数（在core文件之后加载，避免function冲突）
    $helpersFile = __DIR__ . '/server/core/helpers.php';
    if (file_exists($helpersFile)) { require_once $helpersFile; }

    // 加载 License 服务 & AppServiceProvider（部署模式 + License 校验）
    $licenseServiceFile = __DIR__ . '/server/app/Service/LicenseService.php';
    if (file_exists($licenseServiceFile)) { require_once $licenseServiceFile; }
    $appServiceProviderFile = __DIR__ . '/server/app/Service/AppServiceProvider.php';
    if (file_exists($appServiceProviderFile)) { require_once $appServiceProviderFile; }
    if (class_exists('\\app\\Service\\AppServiceProvider')) {
        \app\Service\AppServiceProvider::boot();
    }

    // 启动框架（必须用getInstance()获取实例再调用run()，PHP 8不允许静态调用非静态方法）
    \core\App::getInstance()->run();
    exit;
} catch (Exception $e) {
    // API请求返回JSON
    if (strpos($requestPath, '/api/') === 0 || strpos($requestPath, '/captcha') === 0) {
        $logDir = ROOT_PATH . '/server/runtime/logs';
        if (!is_dir($logDir)) @mkdir($logDir, 0755, true);
        @file_put_contents($logDir . '/framework_' . date('Y-m-d') . '.log', date('Y-m-d H:i:s') . ' Exception: ' . (function_exists('emj_redact_secret') ? emj_redact_secret($e->getMessage()) : $e->getMessage()) . ' in ' . $e->getFile() . ':' . $e->getLine() . "\n" . $e->getTraceAsString() . "\n", FILE_APPEND);
        emj_json(0, '接口临时不可用', null, 500);
    } else {
        header('Content-Type: text/html; charset=utf-8');
        echo '<h3>系统维护中，请稍后再试</h3>';
        echo '<p>' . htmlspecialchars($e->getMessage()) . '</p>';
    }
} catch (Error $e) {
    if (strpos($requestPath, '/api/') === 0 || strpos($requestPath, '/captcha') === 0) {
        $logDir = ROOT_PATH . '/server/runtime/logs';
        if (!is_dir($logDir)) @mkdir($logDir, 0755, true);
        @file_put_contents($logDir . '/framework_' . date('Y-m-d') . '.log', date('Y-m-d H:i:s') . ' Error: ' . (function_exists('emj_redact_secret') ? emj_redact_secret($e->getMessage()) : $e->getMessage()) . ' in ' . $e->getFile() . ':' . $e->getLine() . "\n" . $e->getTraceAsString() . "\n", FILE_APPEND);
        emj_json(0, '接口临时不可用', null, 500);
    } else {
        header('Content-Type: text/html; charset=utf-8');
        echo '<h3>系统维护中，请稍后再试</h3>';
        echo '<p>' . htmlspecialchars($e->getMessage()) . '</p>';
    }
}

// 如果以上所有路由都不匹配，返回false让PHP内置服务器尝试静态文件
return false;
