📋 环境要求
-
面板:宝塔面板 (Linux)
-
Web 服务:Nginx
-
PHP 版本:PHP >= 7.3 (建议 PHP 7.4 或 8.x)
-
数据库:MySQL (5.6 / 5.7 / 8.0 均可)
第一步:创建网站与数据库
-
登录宝塔面板,进入 “网站” -> “添加站点”。
-
域名:填写您的反代域名(例如
proxy.yourdomain.com)。 -
数据库:选择 “MySQL”,设置好数据库名称、用户名和密码(请务必记下这三个信息,稍后要写进代码里)。
-
PHP版本:选择 7.3 或以上版本。
-
点击 “提交”。
第二步:配置 SSL 证书(必做,否则无法开启 HTTPS)
-
在网站列表中,点击刚才创建的网站的 “未部署” (SSL 这一列)。
-
选择 “Let’s Encrypt” 申请免费证书,或者粘贴您自己的证书。
-
申请成功后,确保右上角的 “强制HTTPS” 处于关闭状态(因为我们的代码里已经内置了更智能的协议跳转控制)。
第三步:修改 Nginx 配置文件(核心:解决网页白板、无法加载样式)
因为现代网盘(如 OpenList)是前后端分离架构,必须禁止宝塔默认拦截静态文件。
-
在网站设置窗口中,点击左侧的 “配置文件”。
-
在代码中找到以下两段拦截缓存规则,直接将其删除(约在 50~60 行左右):
# 找到并删除这一段 location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ { expires 30d; error_log /dev/null; access_log /dev/null; } # 找到并删除这一段 location ~ .*\.(js|css)?$ { expires 12h; error_log /dev/null; access_log /dev/null; }-
删除后,点击 “保存”。
第四步:设置全局伪静态(核心:接管所有路由)
-
在网站设置窗口中,点击左侧的 “伪静态”。
-
复制并粘贴以下规则:
location / { try_files $uri $uri/ /index.php?$query_string; }-
点击 “保存”。
第五步:修改并上传代码
-
将之前写好的
index.php代码保存在您的电脑上,并用文本编辑器(如记事本、VSCode)打开。 -
找到代码开头的 “数据库配置” 区域,将第一步创建的数据库信息填入:
// ================= 数据库配置 ================= const DB_HOST = '127.0.0.1'; const DB_NAME = '填入宝塔创建的数据库名'; const DB_USER = '填入宝塔创建的数据库用户名'; const DB_PASS = '填入宝塔创建的数据库密码'; const DB_PORT = '3306'; // ==============================================<?php // ================= 数据库配置 ================= const DB_HOST = '127.0.0.1'; const DB_NAME = 'your_dbname'; // 替换为宝塔创建的数据库名 const DB_USER = 'your_dbuser'; // 替换为宝塔创建的数据库用户名 const DB_PASS = 'your_dbpass'; // 替换为宝塔创建的数据库密码 const DB_PORT = '3306'; // ============================================== set_time_limit(0); ini_set('memory_limit', '-1'); session_start(); date_default_timezone_set('Asia/Shanghai'); function getIpLocation($ip) { $ip = trim(explode(',', $ip)[0]); if (!$ip || filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) { return '局域网/保留地址'; } $url = "http://ip-api.com/json/{$ip}?lang=zh-CN"; $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 3); $res = curl_exec($ch); curl_close($ch); if ($res) { $data = json_decode($res, true); if ($data && isset($data['status']) && $data['status'] === 'success') { $country = $data['country'] ?? ''; $region = $data['regionName'] ?? ''; $city = $data['city'] ?? ''; if ($country === '中国') return $region . ' ' . $city; return $country . ' ' . $region . ' ' . $city; } } return '未知'; } try { $pdo = new PDO("mysql:host=" . DB_HOST . ";port=" . DB_PORT . ";dbname=" . DB_NAME . ";charset=utf8mb4", DB_USER, DB_PASS, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ]); } catch (PDOException $e) { die("数据库连接失败: " . $e->getMessage()); } $pdo->exec(" CREATE TABLE IF NOT EXISTS `proxy_config` ( `id` int(11) NOT NULL AUTO_INCREMENT, `admin_path` varchar(255) DEFAULT '/admin', `user_path` varchar(255) DEFAULT '/user', `username` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, `initialized` tinyint(1) DEFAULT '0', `auth_enabled` tinyint(1) DEFAULT '0', `target_domain` varchar(255) DEFAULT '', `worker_domain` varchar(255) DEFAULT '', `http_port` int(11) DEFAULT '0', `https_port` int(11) DEFAULT '0', `proxy_mode` varchar(50) DEFAULT 'http_only', `cache_enabled` tinyint(1) DEFAULT '0', `cache_ttl_seconds` int(11) DEFAULT '3600', `force_https` tinyint(1) DEFAULT '0', `force_http` tinyint(1) DEFAULT '0', `require_login` tinyint(1) DEFAULT '1', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `proxy_users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `account` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, `qq_number` varchar(50) NOT NULL, `is_banned` tinyint(1) DEFAULT '0', `auth_days` int(11) DEFAULT '0', `auth_start_time` datetime DEFAULT NULL, `auth_end_time` datetime DEFAULT NULL, `create_time` datetime DEFAULT NULL, `login_expire_time` datetime DEFAULT NULL, `last_ip` varchar(100) DEFAULT '', `last_location` varchar(255) DEFAULT '', `session_id` varchar(255) DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); try { $pdo->exec("ALTER TABLE proxy_config ADD COLUMN force_https tinyint(1) DEFAULT '0'"); } catch (Exception $e) {} try { $pdo->exec("ALTER TABLE proxy_config ADD COLUMN force_http tinyint(1) DEFAULT '0'"); } catch (Exception $e) {} try { $pdo->exec("ALTER TABLE proxy_config ADD COLUMN require_login tinyint(1) DEFAULT '1'"); } catch (Exception $e) {} $requestUri = $_SERVER['REQUEST_URI']; $urlPath = parse_url($requestUri, PHP_URL_PATH); $method = $_SERVER['REQUEST_METHOD']; $clientIpRaw = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['HTTP_CLIENT_IP'] ?? $_SERVER['REMOTE_ADDR'] ?? ''; $clientIp = trim(explode(',', $clientIpRaw)[0]); $stmt = $pdo->query("SELECT * FROM proxy_config LIMIT 1"); $config = $stmt->fetch(); if (!$config || $config['initialized'] == 0) { if ($method === 'POST') { $adminPath = $_POST['admin_path'] ?? '/admin'; $userPath = $_POST['user_path'] ?? '/user'; $username = $_POST['username'] ?? ''; $password = $_POST['password'] ?? ''; if ($username && $password) { $encPwd = base64_encode($password); $stmt = $pdo->prepare("INSERT INTO proxy_config (admin_path, user_path, username, password, initialized) VALUES (?, ?, ?, ?, 1)"); $stmt->execute([$adminPath, $userPath, $username, $encPwd]); header("Location: " . $adminPath); exit; } die('请填写完整信息'); } echo getInitHtml(); exit; } $adminPath = $config['admin_path'] ?: '/admin'; $userPath = $config['user_path'] ?: '/user'; if (strpos($urlPath, $adminPath) === 0) { if (empty($_SESSION['admin_logged_in'])) { if ($method === 'POST' && isset($_POST['admin_user'])) { if ($_POST['admin_user'] === $config['username'] && base64_encode($_POST['admin_pwd']) === $config['password']) { $_SESSION['admin_logged_in'] = true; header("Location: " . $adminPath); exit; } else { echo getLoginHtml('账号或密码错误!'); exit; } } echo getLoginHtml(); exit; } if ($method === 'POST') { $action = $_POST['action'] ?? ''; if ($action === 'update_base') { $newAdminPath = $_POST['new_admin_path'] ?: $config['admin_path']; $newUserPath = $_POST['new_user_path'] ?: $config['user_path']; $newUsername = $_POST['new_username'] ?: $config['username']; $newPassword = !empty($_POST['new_password']) ? base64_encode($_POST['new_password']) : $config['password']; $authEnabled = isset($_POST['auth_enabled']) ? 1 : 0; $requireLogin = isset($_POST['require_login']) ? 1 : 0; $stmt = $pdo->prepare("UPDATE proxy_config SET admin_path=?, user_path=?, username=?, password=?, auth_enabled=?, require_login=? WHERE id=?"); $stmt->execute([$newAdminPath, $newUserPath, $newUsername, $newPassword, $authEnabled, $requireLogin, $config['id']]); header("Location: " . $newAdminPath . "#base"); exit; } if ($action === 'update_proxy') { $targetDomain = $_POST['target_domain'] ?? ''; $workerDomain = $_POST['worker_domain'] ?? ''; $httpPort = intval($_POST['http_port'] ?? 0); $httpsPort = intval($_POST['https_port'] ?? 0); $proxyMode = $_POST['proxy_mode'] ?? 'http_only'; $cacheEnabled = isset($_POST['cache_enabled']) ? 1 : 0; $forceProto = $_POST['force_protocol'] ?? '0'; $forceHttps = ($forceProto === 'https') ? 1 : 0; $forceHttp = ($forceProto === 'http') ? 1 : 0; $cacheTtl = intval($_POST['cache_ttl'] ?? 3600); $stmt = $pdo->prepare("UPDATE proxy_config SET target_domain=?, worker_domain=?, http_port=?, https_port=?, proxy_mode=?, cache_enabled=?, force_https=?, force_http=?, cache_ttl_seconds=? WHERE id=?"); $stmt->execute([$targetDomain, $workerDomain, $httpPort, $httpsPort, $proxyMode, $cacheEnabled, $forceHttps, $forceHttp, $cacheTtl, $config['id']]); header("Location: " . $adminPath . "#proxy"); exit; } if ($action === 'manage_user') { $userId = intval($_POST['user_id'] ?? 0); $operation = $_POST['operation'] ?? ''; $authDays = intval($_POST['auth_days'] ?? 0); if ($userId && $operation) { switch ($operation) { case 'ban': $pdo->prepare("UPDATE proxy_users SET is_banned=1 WHERE id=?")->execute([$userId]); break; case 'unban': $pdo->prepare("UPDATE proxy_users SET is_banned=0 WHERE id=?")->execute([$userId]); break; case 'delete': $pdo->prepare("DELETE FROM proxy_users WHERE id=?")->execute([$userId]); break; case 'update_pwd': if (!empty($_POST['new_password'])) $pdo->prepare("UPDATE proxy_users SET password=? WHERE id=?")->execute([base64_encode($_POST['new_password']), $userId]); break; case 'add_auth': case 'reduce_auth': $u = $pdo->prepare("SELECT auth_end_time FROM proxy_users WHERE id=?"); $u->execute([$userId]); $user = $u->fetch(); if ($user) { $end = strtotime($user['auth_end_time']); if ($end < time()) $end = time(); $end = $operation === 'add_auth' ? $end + ($authDays * 86400) : $end - ($authDays * 86400); $pdo->prepare("UPDATE proxy_users SET auth_end_time=? WHERE id=?")->execute([date('Y-m-d H:i:s', $end), $userId]); } break; } } header("Location: " . $adminPath . "#user-manage"); exit; } } $users = $pdo->query("SELECT * FROM proxy_users ORDER BY id DESC")->fetchAll(); echo getAdminHtml($config, $users); exit; } if (strpos($urlPath, $userPath) === 0) { $path = str_replace($userPath, '', $urlPath); if ($path === '/register' && $method === 'POST') { $username = $_POST['username'] ?? ''; $account = $_POST['account'] ?? ''; $password = $_POST['password'] ?? ''; $qqNumber = $_POST['qq_number'] ?? ''; if (!$username || !$account || !$password || !$qqNumber) die('请填写完整注册信息'); $chk = $pdo->prepare("SELECT id FROM proxy_users WHERE account=? OR qq_number=?"); $chk->execute([$account, $qqNumber]); if ($chk->fetch()) die('账号或QQ号已存在'); $now = date('Y-m-d H:i:s'); $encPwd = base64_encode($password); $stmt = $pdo->prepare("INSERT INTO proxy_users (username, account, password, qq_number, is_banned, auth_days, auth_start_time, auth_end_time, create_time, login_expire_time) VALUES (?, ?, ?, ?, 0, 0, ?, ?, ?, ?)"); $stmt->execute([$username, $account, $encPwd, $qqNumber, $now, $now, $now, $now]); header("Location: " . $userPath . "/login"); exit; } if ($path === '/login' && $method === 'POST') { $account = $_POST['account'] ?? ''; $password = $_POST['password'] ?? ''; $stmt = $pdo->prepare("SELECT * FROM proxy_users WHERE account=?"); $stmt->execute([$account]); $user = $stmt->fetch(); if (!$user) die(getUserLoginHtml('账号不存在')); if ($user['is_banned'] == 1) die(getUserLoginHtml('账号已被封禁,无法登录!如需解封请联系管理员QQ:3890053645')); if (base64_decode($user['password']) !== $password) die(getUserLoginHtml('密码错误')); if ($config['auth_enabled'] == 1 && strtotime($user['auth_end_time']) <= time()) die(getUserLoginHtml('账号授权已到期,无法登录!如需续期请联系管理员QQ:3890053645')); $sessionId = bin2hex(random_bytes(16)); $nowMs = time(); $expireTime = date('Y-m-d H:i:s', $nowMs + 12 * 3600); $location = getIpLocation($clientIp); $pdo->prepare("UPDATE proxy_users SET session_id=?, last_ip=?, last_location=?, login_expire_time=? WHERE id=?")->execute([$sessionId, $clientIp, $location, $expireTime, $user['id']]); $token = base64_encode("{$account}|{$sessionId}|{$clientIp}|{$nowMs}"); setcookie('proxy_login', $token, $nowMs + 43200, '/', '', isset($_SERVER['HTTPS']), true); header("Location: /"); exit; } if ($path === '/find-pwd' && $method === 'POST') { $qq = $_POST['qq_number'] ?? ''; $newPwd = $_POST['new_password'] ?? ''; $stmt = $pdo->prepare("SELECT * FROM proxy_users WHERE qq_number=?"); $stmt->execute([$qq]); $user = $stmt->fetch(); if (!$user) die(getUserFindPwdHtml('QQ号未注册')); if ($newPwd) { $pdo->prepare("UPDATE proxy_users SET password=? WHERE qq_number=?")->execute([base64_encode($newPwd), $qq]); die(getUserFindPwdHtml("账号:{$user['account']}<br>密码已重置成功,请返回登录")); } die(getUserFindPwdHtml("你的账号是:{$user['account']}<br>请设置新密码")); } if ($path === '/register') die(getUserRegisterHtml()); if ($path === '/find-pwd') die(getUserFindPwdHtml('')); die(getUserLoginHtml('')); } if (!isset($config['require_login']) || $config['require_login'] == 1) { if (!isset($_COOKIE['proxy_login'])) { header("Location: " . $userPath . "/login"); exit; } $tokenParts = explode('|', base64_decode($_COOKIE['proxy_login'])); if (count($tokenParts) !== 4) { header("Location: " . $userPath . "/login"); exit; } list($account, $sessionId, $cookieIp, $lastValMs) = $tokenParts; $stmt = $pdo->prepare("SELECT * FROM proxy_users WHERE account=?"); $stmt->execute([$account]); $user = $stmt->fetch(); if (!$user) die(getAccessDeniedHtml('账号不存在,请重新登录')); if ($user['session_id'] && $sessionId && $user['session_id'] !== $sessionId) die(getAccessDeniedHtml('你的账号已在其他IP登录,当前设备已下线!请重新登录')); if ($user['is_banned'] == 1) die(getAccessDeniedHtml('你的账号已被封禁,无法访问该网站!如需解封请联系管理员QQ:3890053645')); if (strtotime($user['login_expire_time']) <= time()) die(getAccessDeniedHtml('登录已过期,请重新登录')); } if (empty($config['target_domain']) || empty($config['worker_domain'])) { http_response_code(500); die('请先在后台配置反代参数'); } $isClientHttps = (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) === 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https') || (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443); if ($config['force_https'] == 1 && !$isClientHttps) { header("Location: https://" . $_SERVER['HTTP_HOST'] . $requestUri); exit; } if ($config['force_http'] == 1 && $isClientHttps) { header("Location: http://" . $_SERVER['HTTP_HOST'] . $requestUri); exit; } while (ob_get_level()) ob_end_clean(); $protocol = 'http'; $port = $config['http_port']; if ($config['proxy_mode'] === 'https_only' || ($config['proxy_mode'] === 'auto' && $config['https_port'])) { $protocol = 'https'; $port = $config['https_port']; } $targetUrl = "{$protocol}://{$config['target_domain']}:{$port}{$requestUri}"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $targetUrl); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); curl_setopt($ch, CURLOPT_RETURNTRANSFER, false); curl_setopt($ch, CURLOPT_HEADER, false); $clientHeaders = []; foreach (getallheaders() as $k => $v) { $lk = strtolower($k); if (in_array($lk, ['host', 'cookie', 'accept-encoding'])) continue; if ($lk === 'origin' || $lk === 'referer') { $v = str_replace($config['worker_domain'], $config['target_domain'], $v); } $clientHeaders[] = "$k: $v"; } $clientHeaders[] = "Host: " . $config['target_domain']; $clientHeaders[] = "X-Forwarded-Host: " . $config['worker_domain']; $fwdProto = 'http'; if ($config['force_https'] == 1) $fwdProto = 'https'; elseif ($config['force_http'] == 1) $fwdProto = 'http'; else $fwdProto = $isClientHttps ? 'https' : 'http'; $clientHeaders[] = "X-Forwarded-Proto: " . $fwdProto; $proxyCookies = []; foreach ($_COOKIE as $k => $v) { if ($k !== 'proxy_login') $proxyCookies[] = "$k=$v"; } if (!empty($proxyCookies)) $clientHeaders[] = "Cookie: " . implode('; ', $proxyCookies); curl_setopt($ch, CURLOPT_HTTPHEADER, $clientHeaders); if ($method !== 'GET' && $method !== 'HEAD' && $method !== 'OPTIONS') { curl_setopt($ch, CURLOPT_POSTFIELDS, file_get_contents('php://input')); } $responseHeaders = []; $headersSent = false; $isTextContent = false; $bodyBuffer = ''; $httpStatusLine = ''; curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($curl, $header) use (&$responseHeaders, &$httpStatusLine) { if (empty($httpStatusLine) && preg_match('/^HTTP\/\d(?:\.\d)? \d{3}/i', $header)) { $httpStatusLine = trim($header); } $responseHeaders[] = $header; return strlen($header); }); curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($curl, $data) use (&$headersSent, &$isTextContent, &$bodyBuffer, &$responseHeaders, &$httpStatusLine, $config) { if (!$headersSent) { $headersSent = true; if ($httpStatusLine) header($httpStatusLine); $contentType = ''; foreach ($responseHeaders as $h) { if (preg_match('/^Content-Type:\s*(.*)/i', $h, $matches)) { $contentType = strtolower(trim($matches[1])); break; } } if (strpos($contentType, 'text/') !== false || strpos($contentType, 'json') !== false || strpos($contentType, 'javascript') !== false || strpos($contentType, 'xml') !== false) { $isTextContent = true; } else { header('X-Accel-Buffering: no'); } foreach ($responseHeaders as $h) { $hTrim = trim($h); if (empty($hTrim) || preg_match('/^HTTP\//i', $hTrim)) continue; $lk = strtolower(explode(':', $hTrim)[0]); if ($isTextContent) { if (in_array($lk, ['content-length', 'transfer-encoding', 'content-encoding', 'content-security-policy', 'x-frame-options'])) continue; } else { if (in_array($lk, ['transfer-encoding'])) continue; } $hTrim = str_replace($config['target_domain'], $config['worker_domain'], $hTrim); if ($config['force_https'] == 1) { $hTrim = preg_replace("/http:\/\/" . preg_quote($config['worker_domain'], '/') . "/i", "https://" . $config['worker_domain'], $hTrim); } elseif ($config['force_http'] == 1) { $hTrim = preg_replace("/https:\/\/" . preg_quote($config['worker_domain'], '/') . "/i", "http://" . $config['worker_domain'], $hTrim); } header($hTrim, false); } } if ($isTextContent) { $bodyBuffer .= $data; } else { echo $data; flush(); } return strlen($data); }); curl_exec($ch); if (curl_errno($ch)) { http_response_code(502); die("Proxy Error: " . curl_error($ch)); } curl_close($ch); if ($isTextContent && $bodyBuffer !== '') { $repProtocol = 'http'; if ($config['force_https'] == 1) $repProtocol = 'https'; elseif ($config['force_http'] == 1) $repProtocol = 'http'; else $repProtocol = (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) === 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https') ? 'https' : 'http'; if ($config['https_port']) $bodyBuffer = str_replace("https://{$config['target_domain']}:{$config['https_port']}", "{$repProtocol}://{$config['worker_domain']}", $bodyBuffer); if ($config['http_port']) $bodyBuffer = str_replace("http://{$config['target_domain']}:{$config['http_port']}", "{$repProtocol}://{$config['worker_domain']}", $bodyBuffer); $bodyBuffer = str_replace("http://{$config['target_domain']}", "{$repProtocol}://{$config['worker_domain']}", $bodyBuffer); $bodyBuffer = str_replace("https://{$config['target_domain']}", "{$repProtocol}://{$config['worker_domain']}", $bodyBuffer); echo $bodyBuffer; } exit; function getInitHtml() { return <<<HTML <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>反代管理系统 - 初始化</title> <script src="https://cdn.tailwindcss.com"></script> <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; } .init-card { background: #ffffff; border-radius: 12px; box-shadow: 0 8px 32px rgba(31, 38, 135, 0.1); padding: 2.5rem; max-width: 500px; width: 100%; } .card-header { text-align: center; margin-bottom: 2rem; } .card-header h1 { font-size: 1.8rem; font-weight: 600; color: #1e293b; margin-bottom: 0.5rem; } .card-header p { color: #64748b; font-size: 0.95rem; } .form-group { margin-bottom: 1.5rem; } .form-label { display: block; margin-bottom: 0.75rem; font-weight: 500; color: #334155; font-size: 0.95rem; } .form-input { width: 100%; padding: 0.875rem 1rem; border: 1px solid #e2e8f0; border-radius: 8px; font-size: 0.95rem; background: #f8fafc; } .form-input:focus { outline: none; border-color: #2563eb; background: #ffffff; } .submit-btn { width: 100%; padding: 1rem; background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%); color: #ffffff; border: none; border-radius: 8px; font-size: 1rem; cursor: pointer; margin-top: 1rem; } .icon { color: #2563eb; margin-right: 0.5rem; } </style> </head> <body> <div class="init-card"> <div class="card-header"> <h1><i class="fa fa-cogs icon"></i>反代管理系统</h1> <p>首次使用,请完成初始化配置</p> </div> <form method="POST"> <div class="form-group"> <label class="form-label">后台访问路径</label> <input type="text" name="admin_path" class="form-input" placeholder="/admin" value="/admin" required> </div> <div class="form-group"> <label class="form-label">用户访问路径</label> <input type="text" name="user_path" class="form-input" placeholder="/user" value="/user" required> </div> <div class="form-group"> <label class="form-label">管理员账号</label> <input type="text" name="username" class="form-input" placeholder="请设置管理员账号" required> </div> <div class="form-group"> <label class="form-label">管理员密码</label> <input type="password" name="password" class="form-input" placeholder="请设置管理员密码" required> </div> <button type="submit" class="submit-btn"><i class="fa fa-check-circle icon"></i>完成初始化</button> </form> </div> </body> </html> HTML; } function getLoginHtml($errorMsg = '') { $alertHtml = $errorMsg ? '<div class="alert"><i class="fa fa-exclamation-circle"></i> ' . $errorMsg . '</div>' : ''; return <<<HTML <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>反代管理系统 - 后台登录</title> <script src="https://cdn.tailwindcss.com"></script> <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; } .login-card { background: #ffffff; border-radius: 12px; box-shadow: 0 8px 32px rgba(31, 38, 135, 0.1); padding: 2.5rem; max-width: 450px; width: 100%; } .card-header { text-align: center; margin-bottom: 2rem; } .card-header h1 { font-size: 1.8rem; font-weight: 600; color: #1e293b; margin-bottom: 0.5rem; } .card-header p { color: #64748b; font-size: 0.95rem; } .alert { background: #fee2e2; color: #dc2626; padding: 1rem; border-radius: 8px; margin-bottom: 1.5rem; text-align: center; font-size: 0.95rem; border-left: 4px solid #dc2626; } .icon { color: #2563eb; margin-right: 0.5rem; } </style> </head> <body> <div class="login-card"> <div class="card-header"> <h1><i class="fa fa-lock icon"></i>管理员登录</h1> <p>请输入管理员账号和密码</p> </div> {$alertHtml} <form method="POST"> <div style="margin-bottom: 1rem;"> <input type="text" name="admin_user" placeholder="管理员账号" style="width: 100%; padding: 0.875rem 1rem; border: 1px solid #e2e8f0; border-radius: 8px; font-size: 0.95rem; background: #f8fafc;" required> </div> <div style="margin-bottom: 1.5rem;"> <input type="password" name="admin_pwd" placeholder="管理员密码" style="width: 100%; padding: 0.875rem 1rem; border: 1px solid #e2e8f0; border-radius: 8px; font-size: 0.95rem; background: #f8fafc;" required> </div> <button type="submit" style="width: 100%; padding: 1rem; background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%); color: #ffffff; border: none; border-radius: 8px; font-size: 1rem; cursor: pointer;">登录后台</button> </form> </div> </body> </html> HTML; } function getAdminHtml($config, $users) { $userTableHtml = ''; if (count($users) > 0) { foreach ($users as $user) { $pwdHtml = ' <div class="mb-1">' . base64_decode($user['password']) . '</div> <form method="POST" style="display: flex; gap: 0.2rem;"> <input type="hidden" name="action" value="manage_user"> <input type="hidden" name="user_id" value="' . $user['id'] . '"> <input type="hidden" name="operation" value="update_pwd"> <input type="text" name="new_password" placeholder="新密码" class="px-1 py-0.5 text-xs border rounded w-16" required> <button type="submit" class="px-1 py-0.5 text-xs bg-gray-600 text-white rounded">改密</button> </form> '; $statusStr = $user['is_banned'] == 1 ? '<span class="text-red-600">已封禁</span>' : '<span class="text-green-600">正常</span>'; $banOp = $user['is_banned'] == 1 ? 'unban' : 'ban'; $banBtnCls = $user['is_banned'] == 1 ? 'bg-green-600' : 'bg-red-600'; $banBtnTxt = $user['is_banned'] == 1 ? '解禁' : '封禁'; $statusActionHtml = ' <div class="mb-1">' . $statusStr . '</div> <div style="display: flex; gap: 0.2rem;"> <form method="POST"> <input type="hidden" name="action" value="manage_user"> <input type="hidden" name="user_id" value="' . $user['id'] . '"> <input type="hidden" name="operation" value="' . $banOp . '"> <button type="submit" class="px-2 py-1 text-xs rounded ' . $banBtnCls . ' text-white">' . $banBtnTxt . '</button> </form> <form method="POST" onsubmit="return confirm(\'确定要删除此用户吗?\');"> <input type="hidden" name="action" value="manage_user"> <input type="hidden" name="user_id" value="' . $user['id'] . '"> <input type="hidden" name="operation" value="delete"> <button type="submit" class="px-2 py-1 text-xs bg-red-800 text-white rounded">删除</button> </form> </div> '; $ipHtml = ' <div class="text-xs text-gray-600 whitespace-nowrap">' . ($user['last_ip'] ?: '-') . '</div> <div class="text-xs text-blue-600 whitespace-nowrap">' . ($user['last_location'] ?: '未知') . '</div> '; $authOpHtml = ' <form method="POST" style="display: flex; gap: 0.2rem; align-items: center;"> <input type="hidden" name="action" value="manage_user"> <input type="hidden" name="user_id" value="' . $user['id'] . '"> <input type="number" name="auth_days" placeholder="天数" class="px-1 py-0.5 text-xs border rounded w-12" required> <button type="submit" name="operation" value="add_auth" class="px-1 py-0.5 text-xs bg-blue-600 text-white rounded">增加</button> <button type="submit" name="operation" value="reduce_auth" class="px-1 py-0.5 text-xs bg-orange-500 text-white rounded">减少</button> </form> '; $userTableHtml .= ' <tr class="border-b border-gray-200 hover:bg-gray-50"> <td class="px-4 py-3">' . $user['username'] . '</td> <td class="px-4 py-3">' . $user['account'] . '</td> <td class="px-4 py-3">' . $pwdHtml . '</td> <td class="px-4 py-3">' . $user['qq_number'] . '</td> <td class="px-4 py-3">' . $statusActionHtml . '</td> <td class="px-4 py-3">' . $ipHtml . '</td> <td class="px-4 py-3">' . $user['auth_end_time'] . '</td> <td class="px-4 py-3">' . $authOpHtml . '</td> </tr> '; } } else { $userTableHtml = '<tr><td colspan="8" class="px-4 py-3 text-center text-gray-500">暂无用户</td></tr>'; } $authCheck = $config['auth_enabled'] == 1 ? 'checked' : ''; $requireLoginCheck = (!isset($config['require_login']) || $config['require_login'] == 1) ? 'checked' : ''; $cacheCheck = $config['cache_enabled'] == 1 ? 'checked' : ''; $forceNoneCheck = ($config['force_https'] == 0 && $config['force_http'] == 0) ? 'checked' : ''; $forceHttpsCheck = $config['force_https'] == 1 ? 'checked' : ''; $forceHttpCheck = $config['force_http'] == 1 ? 'checked' : ''; $optHttp = $config['proxy_mode'] === 'http_only' ? 'selected' : ''; $optHttps = $config['proxy_mode'] === 'https_only' ? 'selected' : ''; $optAuto = $config['proxy_mode'] === 'auto' ? 'selected' : ''; return <<<HTML <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>反代管理系统 - 后台</title> <script src="https://cdn.tailwindcss.com"></script> <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #f8fafc; color: #1e293b; min-height: 100vh; display: flex; } .sidebar { width: 250px; background: #1e293b; color: #f8fafc; min-height: 100vh; padding: 1.5rem 0; position: fixed; } .sidebar-header { padding: 0 1.5rem 1.5rem; border-bottom: 1px solid #334155; margin-bottom: 1rem; } .sidebar-header h2 { font-size: 1.2rem; font-weight: 600; color: #ffffff; display: flex; align-items: center; } .sidebar-header h2 i { margin-right: 0.75rem; color: #38bdf8; } .sidebar-menu { padding: 0.5rem 0; } .menu-item { padding: 0.875rem 1.5rem; display: flex; align-items: center; color: #94a3b8; text-decoration: none; transition: all 0.2s ease; cursor: pointer; border-left: 3px solid transparent; } .menu-item.active { background: #334155; color: #ffffff; border-left-color: #38bdf8; } .menu-item:hover { background: #273449; color: #e2e8f0; } .menu-item i { margin-right: 0.75rem; font-size: 1rem; } .main-content { margin-left: 250px; flex: 1; padding: 2rem; } .content-header { margin-bottom: 2rem; padding-bottom: 1rem; border-bottom: 1px solid #e2e8f0; } .content-header h1 { font-size: 1.75rem; font-weight: 600; color: #0f172a; } .content-card { background: #ffffff; border-radius: 12px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); padding: 2rem; margin-bottom: 2rem; overflow-x: auto; } .card-title { font-size: 1.25rem; font-weight: 600; color: #1e293b; margin-bottom: 1.5rem; display: flex; align-items: center; } .card-title i { margin-right: 0.75rem; color: #2563eb; } .form-group { margin-bottom: 1.5rem; } .form-label { display: block; margin-bottom: 0.75rem; font-weight: 500; color: #334155; font-size: 0.95rem; } .form-input, .form-select { width: 100%; padding: 0.875rem 1rem; border: 1px solid #e2e8f0; border-radius: 8px; font-size: 0.95rem; background: #f8fafc; } .form-input:focus, .form-select:focus { outline: none; border-color: #2563eb; background: #ffffff; } .switch-group { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1.5rem; } .switch-group input { height: 1.25rem; width: 1.25rem; accent-color: #2563eb; } .submit-btn { padding: 0.875rem 1.5rem; background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%); color: #ffffff; border: none; border-radius: 8px; cursor: pointer; margin-top: 0.5rem; } .tab-content { display: none; } .tab-content.active { display: block; } .user-table { width: 100%; border-collapse: collapse; margin-top: 1rem; } .user-table th { padding: 0.75rem 1rem; text-align: left; background: #f1f5f9; font-weight: 500; color: #334155; } .user-table td { padding: 0.75rem 1rem; color: #1e293b; } @media (max-width: 768px) { .sidebar { width: 80px; padding: 1rem 0; } .sidebar-header h2 span, .menu-item span { display: none; } .menu-item { justify-content: center; padding: 1rem; } .menu-item i { margin-right: 0; font-size: 1.2rem; } .main-content { margin-left: 80px; padding: 1.5rem 1rem; } } </style> </head> <body> <div class="sidebar"> <div class="sidebar-header"> <h2><i class="fa fa-cogs"></i> <span>反代管理系统</span></h2> </div> <div class="sidebar-menu"> <div class="menu-item active" onclick="switchTab('base', event)"> <i class="fa fa-user-circle"></i><span>基础配置</span> </div> <div class="menu-item" onclick="switchTab('proxy', event)"> <i class="fa fa-cloud"></i><span>反代配置</span> </div> <div class="menu-item" onclick="switchTab('user-manage', event)"> <i class="fa fa-users"></i><span>用户管理</span> </div> </div> </div> <div class="main-content"> <div class="content-header"><h1>后台管理中心</h1></div> <div id="base-tab" class="tab-content active"> <div class="content-card"> <h3 class="card-title"><i class="fa fa-user-circle"></i>基础配置</h3> <form method="POST"> <input type="hidden" name="action" value="update_base"> <div class="form-group"> <label class="form-label">后台访问路径</label> <input type="text" name="new_admin_path" class="form-input" value="{$config['admin_path']}" required> </div> <div class="form-group"> <label class="form-label">用户访问路径(注册/登录)</label> <input type="text" name="new_user_path" class="form-input" value="{$config['user_path']}" required> </div> <div class="form-group"> <label class="form-label">管理员账号</label> <input type="text" name="new_username" class="form-input" value="{$config['username']}" required> </div> <div class="form-group"> <label class="form-label">管理员密码(留空则不修改)</label> <input type="password" name="new_password" class="form-input" placeholder="留空不修改密码"> </div> <div class="switch-group"> <input type="checkbox" name="require_login" id="require_login" {$requireLoginCheck}> <label for="require_login" class="form-label mb-0" style="color:#2563eb; font-weight:bold;">开启访问登录限制(勾选后用户必须注册/登录才能访问反代网站,不勾选则直接访问)</label> </div> <div class="switch-group"> <input type="checkbox" name="auth_enabled" id="auth_enabled" {$authCheck}> <label for="auth_enabled" class="form-label mb-0">开启授权验证(开启后仅授权用户可登录)</label> </div> <button type="submit" class="submit-btn"><i class="fa fa-save"></i> 保存基础配置</button> </form> </div> </div> <div id="proxy-tab" class="tab-content"> <div class="content-card"> <h3 class="card-title"><i class="fa fa-cloud"></i>反代配置</h3> <form method="POST"> <input type="hidden" name="action" value="update_proxy"> <div class="form-group"> <label class="form-label">源站域名</label> <input type="text" name="target_domain" class="form-input" value="{$config['target_domain']}" placeholder="例如:your-domain.com"> </div> <div class="form-group"> <label class="form-label">当前代理域名(填你在宝塔绑定的域名)</label> <input type="text" name="worker_domain" class="form-input" value="{$config['worker_domain']}" placeholder="例如:your-proxy.com"> </div> <div class="form-group" style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;"> <div><label class="form-label">HTTP端口</label><input type="number" name="http_port" class="form-input" value="{$config['http_port']}"></div> <div><label class="form-label">HTTPS端口</label><input type="number" name="https_port" class="form-input" value="{$config['https_port']}"></div> </div> <div class="form-group"> <label class="form-label">反代模式</label> <select name="proxy_mode" class="form-select"> <option value="http_only" {$optHttp}>仅HTTP</option> <option value="https_only" {$optHttps}>仅HTTPS</option> <option value="auto" {$optAuto}>HTTP+HTTPS自适应</option> </select> </div> <div class="form-group" style="margin-top: 1.5rem; margin-bottom: 1.5rem;"> <label class="form-label mb-2">强制协议跳转(二选一或不选)</label> <div style="display: flex; gap: 1.5rem; align-items: center; padding: 0.5rem 0;"> <label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;"> <input type="radio" name="force_protocol" value="0" style="width: 1.2rem; height: 1.2rem; accent-color: #2563eb;" {$forceNoneCheck}> <span>不强制</span> </label> <label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer; color: #dc2626; font-weight: 500;"> <input type="radio" name="force_protocol" value="https" style="width: 1.2rem; height: 1.2rem; accent-color: #dc2626;" {$forceHttpsCheck}> <span>强制 HTTPS</span> </label> <label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer; color: #2563eb; font-weight: 500;"> <input type="radio" name="force_protocol" value="http" style="width: 1.2rem; height: 1.2rem; accent-color: #2563eb;" {$forceHttpCheck}> <span>强制 HTTP</span> </label> </div> </div> <div class="switch-group"> <input type="checkbox" name="cache_enabled" id="cache_enabled" {$cacheCheck}> <label for="cache_enabled" class="form-label mb-0">开启缓存</label> </div> <div class="form-group"> <label class="form-label">缓存时长(秒)</label> <input type="number" name="cache_ttl" class="form-input" value="{$config['cache_ttl_seconds']}"> </div> <button type="submit" class="submit-btn"><i class="fa fa-save"></i> 保存反代配置</button> </form> </div> </div> <div id="user-manage-tab" class="tab-content"> <div class="content-card"> <h3 class="card-title"><i class="fa fa-users"></i>用户管理</h3> <table class="user-table text-sm"> <thead> <tr class="border-b border-gray-300"> <th>用户名</th> <th>账号</th> <th>密码管理</th> <th>QQ号</th> <th>状态管理</th> <th>IP/位置</th> <th>到期时间</th> <th>授权操作</th> </tr> </thead> <tbody>{$userTableHtml}</tbody> </table> </div> </div> </div> <script> if (window.location.hash) { let tabId = window.location.hash.substring(1); if (tabId.endsWith('-tab')) tabId = tabId.replace('-tab', ''); if(document.getElementById(tabId + '-tab')) { document.querySelectorAll('.tab-content').forEach(tab => tab.classList.remove('active')); document.querySelectorAll('.menu-item').forEach(item => item.classList.remove('active')); document.getElementById(tabId + '-tab').classList.add('active'); const items = document.querySelectorAll('.menu-item'); for(let item of items) { if(item.getAttribute('onclick').includes("switchTab('" + tabId + "'")) { item.classList.add('active'); break; } } } } function switchTab(tabId, event) { document.querySelectorAll('.tab-content').forEach(tab => tab.classList.remove('active')); document.querySelectorAll('.menu-item').forEach(item => item.classList.remove('active')); document.getElementById(tabId + '-tab').classList.add('active'); if(event && event.currentTarget) event.currentTarget.classList.add('active'); window.history.replaceState(null, null, '#' + tabId); } </script> </body> </html> HTML; } function getUserRegisterHtml() { return <<<HTML <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>用户注册</title> <script src="https://cdn.tailwindcss.com"></script> <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; } .card { background: #ffffff; border-radius: 12px; box-shadow: 0 8px 32px rgba(31,38,135,0.1); padding: 2.5rem; max-width: 500px; width: 100%; } .card-header { text-align: center; margin-bottom: 2rem; } .card-header h1 { font-size: 1.8rem; font-weight: 600; color: #1e293b; margin-bottom: 0.5rem; } .card-header p { color: #64748b; font-size: 0.95rem; } .form-group { margin-bottom: 1.5rem; } .form-label { display: block; margin-bottom: 0.75rem; font-weight: 500; color: #334155; font-size: 0.95rem; } .form-input { width: 100%; padding: 0.875rem 1rem; border: 1px solid #e2e8f0; border-radius: 8px; font-size: 0.95rem; background: #f8fafc; } .form-input:focus { outline: none; border-color: #2563eb; background: #ffffff; } .submit-btn { width: 100%; padding: 1rem; background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%); color: #ffffff; border: none; border-radius: 8px; font-size: 1rem; cursor: pointer; margin-top: 1rem; } .link-group { text-align: center; margin-top: 1.5rem; font-size: 0.95rem; } .link-group a { color: #2563eb; text-decoration: none; } .icon { color: #2563eb; margin-right: 0.5rem; } </style> </head> <body> <div class="card"> <div class="card-header"> <h1><i class="fa fa-user-plus icon"></i>用户注册</h1> <p>填写信息完成注册,即可访问反代服务</p> </div> <form method="POST"> <div class="form-group"> <label class="form-label">用户名</label> <input type="text" name="username" class="form-input" placeholder="请输入用户名" required> </div> <div class="form-group"> <label class="form-label">登录账号</label> <input type="text" name="account" class="form-input" placeholder="请设置登录账号" required> </div> <div class="form-group"> <label class="form-label">登录密码</label> <input type="password" name="password" class="form-input" placeholder="请设置登录密码" required> </div> <div class="form-group"> <label class="form-label">QQ号(用于找回密码)</label> <input type="text" name="qq_number" class="form-input" placeholder="请输入QQ号" required> </div> <button type="submit" class="submit-btn"><i class="fa fa-check-circle icon"></i>完成注册</button> </form> <div class="link-group"> 已有账号?<a href="./login">立即登录</a> | <a href="./find-pwd">找回密码</a> </div> </div> </body> </html> HTML; } function getUserLoginHtml($errorMsg = '') { $alertHtml = $errorMsg ? '<div class="alert"><i class="fa fa-exclamation-circle"></i> ' . $errorMsg . '</div>' : ''; return <<<HTML <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>用户登录</title> <script src="https://cdn.tailwindcss.com"></script> <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; } .card { background: #ffffff; border-radius: 12px; box-shadow: 0 8px 32px rgba(31,38,135,0.1); padding: 2.5rem; max-width: 500px; width: 100%; } .card-header { text-align: center; margin-bottom: 2rem; } .card-header h1 { font-size: 1.8rem; font-weight: 600; color: #1e293b; margin-bottom: 0.5rem; } .alert { background: #fee2e2; color: #dc2626; padding: 1rem; border-radius: 8px; margin-bottom: 1.5rem; text-align: center; font-size: 0.95rem; border-left: 4px solid #dc2626; } .form-group { margin-bottom: 1.5rem; } .form-label { display: block; margin-bottom: 0.75rem; font-weight: 500; color: #334155; font-size: 0.95rem; } .form-input { width: 100%; padding: 0.875rem 1rem; border: 1px solid #e2e8f0; border-radius: 8px; font-size: 0.95rem; background: #f8fafc; } .form-input:focus { outline: none; border-color: #2563eb; background: #ffffff; } .submit-btn { width: 100%; padding: 1rem; background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%); color: #ffffff; border: none; border-radius: 8px; font-size: 1rem; cursor: pointer; margin-top: 1rem; } .link-group { text-align: center; margin-top: 1.5rem; font-size: 0.95rem; } .link-group a { color: #2563eb; text-decoration: none; } .icon { color: #2563eb; margin-right: 0.5rem; } </style> </head> <body> <div class="card"> <div class="card-header"> <h1><i class="fa fa-lock icon"></i>用户登录</h1> <p>登录后即可访问反代服务</p> </div> {$alertHtml} <form method="POST"> <div class="form-group"> <label class="form-label">登录账号</label> <input type="text" name="account" class="form-input" placeholder="请输入登录账号" required> </div> <div class="form-group"> <label class="form-label">登录密码</label> <input type="password" name="password" class="form-input" placeholder="请输入登录密码" required> </div> <button type="submit" class="submit-btn"><i class="fa fa-sign-in icon"></i>立即登录</button> </form> <div class="link-group"> 没有账号?<a href="./register">立即注册</a> | <a href="./find-pwd">找回密码</a> </div> </div> </body> </html> HTML; } function getUserFindPwdHtml($msg = '') { $msgHtml = $msg ? '<div class="msg-box"><i class="fa fa-info-circle"></i> ' . $msg . '</div>' : ''; return <<<HTML <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>找回密码</title> <script src="https://cdn.tailwindcss.com"></script> <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; } .card { background: #ffffff; border-radius: 12px; box-shadow: 0 8px 32px rgba(31,38,135,0.1); padding: 2.5rem; max-width: 500px; width: 100%; } .card-header { text-align: center; margin-bottom: 2rem; } .card-header h1 { font-size: 1.8rem; font-weight: 600; color: #1e293b; margin-bottom: 0.5rem; } .msg-box { background: #e0f2fe; color: #0369a1; padding: 1rem; border-radius: 8px; margin-bottom: 1.5rem; text-align: center; font-size: 0.95rem; border-left: 4px solid #0369a1; } .form-group { margin-bottom: 1.5rem; } .form-label { display: block; margin-bottom: 0.75rem; font-weight: 500; color: #334155; font-size: 0.95rem; } .form-input { width: 100%; padding: 0.875rem 1rem; border: 1px solid #e2e8f0; border-radius: 8px; font-size: 0.95rem; background: #f8fafc; } .form-input:focus { outline: none; border-color: #2563eb; background: #ffffff; } .submit-btn { width: 100%; padding: 1rem; background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%); color: #ffffff; border: none; border-radius: 8px; font-size: 1rem; cursor: pointer; margin-top: 1rem; } .link-group { text-align: center; margin-top: 1.5rem; font-size: 0.95rem; } .link-group a { color: #2563eb; text-decoration: none; } .icon { color: #2563eb; margin-right: 0.5rem; } </style> </head> <body> <div class="card"> <div class="card-header"> <h1><i class="fa fa-key icon"></i>找回密码</h1> <p>输入QQ号找回账号并重置密码</p> </div> {$msgHtml} <form method="POST"> <div class="form-group"> <label class="form-label">注册时填写的QQ号</label> <input type="text" name="qq_number" class="form-input" placeholder="请输入QQ号" required> </div> <div class="form-group"> <label class="form-label">新密码(留空仅查询账号)</label> <input type="password" name="new_password" class="form-input" placeholder="请设置新密码"> </div> <button type="submit" class="submit-btn"><i class="fa fa-search icon"></i>找回/重置</button> </form> <div class="link-group"> 返回 <a href="./login">登录</a> | <a href="./register">注册</a> </div> </div> </body> </html> HTML; } function getAccessDeniedHtml($msg) { return <<<HTML <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>访问被拒绝</title> <script src="https://cdn.tailwindcss.com"></script> <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; } .denied-card { background: #ffffff; border-radius: 12px; box-shadow: 0 8px 32px rgba(31,38,135,0.1); padding: 3rem 2.5rem; max-width: 500px; width: 100%; text-align: center; } .denied-icon { font-size: 4rem; color: #dc2626; margin-bottom: 1.5rem; } .denied-title { font-size: 1.8rem; font-weight: 600; color: #1e293b; margin-bottom: 1rem; } .denied-msg { font-size: 1.1rem; color: #dc2626; line-height: 1.6; margin-bottom: 2rem; } .back-btn { display: inline-block; padding: 0.875rem 2rem; background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%); color: #ffffff; border-radius: 8px; text-decoration: none; } </style> </head> <body> <div class="denied-card"> <div class="denied-icon"><i class="fa fa-ban"></i></div> <h2 class="denied-title">访问被拒绝</h2> <div class="denied-msg">{$msg}</div> <a href="./user/login" class="back-btn">返回重新登录</a> </div> </body> </html> HTML; }六步:系统初始化
-
在浏览器中访问您的域名(例如
http://proxy.yourdomain.com)。 -
此时会自动跳转到初始化安装界面。
-
按照提示设置:
-
后台访问路径(例如
/admin,为了安全建议改成不规则的,如/my_admin_666) -
用户访问路径(例如
/user) -
管理员账号(例如
admin) -
管理员密码(例如
123456)
-
-
点击完成初始化,系统会自动创建所有的数据库表结构。
🎉 部署完成!日常使用说明:
-
进入后台:访问您设置的后台路径(如
您的域名/admin),输入管理员账号密码即可进入管理界面。 -
配置反代:
-
在 “基础配置” 里,勾选“开启访问登录限制”即可强制访客登录。
-
在 “反代配置” 里,填入【源站域名】和【代理域名】(注意不要带
http://,直接填域名)。 -
强制协议推荐选择 不强制(自适应) 或 强制HTTPS。
-
-
大文件与视频提示:本系统已经从底层彻底重写了 cURL 传输逻辑(无缓冲流式透传),最大支持无限大小的流媒体拖拽和超大压缩包下载,服务器内存占用永远只有几MB,完全不需要修改宝塔的 PHP 内存限制。
提示:如果后期更换了域名,只需进入后台,在“反代配置”中把“当前代理域名”修改为新域名即可。
-
-
-








暂无评论内容