步骤 1:创建 D1 数据库
登录 Cloudflare → 左侧「Workers & Pages」→ 顶部「D1」→ 「创建数据库」;
输入数据库名称(如proxy-db)→ 「创建」;
进入数据库控制台 → 执行上面的无注释建表语句。
步骤 2:配置 Workers 绑定
进入你的 Worker → 「设置」→ 「变量」→ 「D1 数据库绑定」;
绑定名称填:PROXY_DB(必须和代码里的env.PROXY_DB一致);
选择你创建的 D1 数据库 → 「保存」。
步骤 3:部署代码
把上面的完整 Workers 代码复制到 Worker 编辑器;
点击「保存并部署」。
五、使用流程
第一次访问:打开 Worker 域名 → 进入初始化界面,设置后台路径(如/admin)、管理员账号密码 → 提交;
登录后台:访问你的域名/admin → 输入账号密码登录;
配置反代参数:
基础配置:可修改后台路径、账号密码;
反代配置:填写源站域名、Workers 域名、HTTP/HTTPS 端口、反代模式、缓存开关 / 时长;
使用反代:直接访问 Worker 域名 → 自动执行反代逻辑(按后台配置)。
数据库
CREATE TABLE IF NOT EXISTS proxy_config (id INTEGER PRIMARY KEY AUTOINCREMENT,admin_path TEXT NOT NULL DEFAULT '/admin',user_path TEXT NOT NULL DEFAULT '/user',username TEXT NOT NULL,password TEXT NOT NULL,target_domain TEXT DEFAULT '',worker_domain TEXT DEFAULT '',http_port INTEGER DEFAULT 0,https_port INTEGER DEFAULT 0,proxy_mode TEXT DEFAULT 'http_only',cache_enabled INTEGER DEFAULT 0,cache_ttl_seconds INTEGER DEFAULT 3600,auth_enabled INTEGER DEFAULT 0,initialized INTEGER DEFAULT 0);
CREATE TABLE IF NOT EXISTS proxy_users (id INTEGER PRIMARY KEY AUTOINCREMENT,username TEXT NOT NULL,account TEXT NOT NULL UNIQUE,password TEXT NOT NULL,qq_number TEXT NOT NULL UNIQUE,is_banned INTEGER DEFAULT 0,auth_days INTEGER DEFAULT 0,auth_start_time TEXT NOT NULL,auth_end_time TEXT NOT NULL,create_time TEXT NOT NULL);
Workers
export default {
async fetch(request, env) {
const url = new URL(request.url);
const db = env.PROXY_DB;
const getBeijingTime = () => {
const now = new Date();
const utc = now.getTime() + (now.getTimezoneOffset() * 60000);
return new Date(utc + (8 * 3600000));
};
const calcAuthEndTime = (startTime, days) => {
const start = new Date(startTime);
start.setDate(start.getDate() + parseInt(days));
return start.toISOString();
};
const configResult = await db.prepare('SELECT * FROM proxy_config LIMIT 1').all();
let config = configResult.results[0] || { initialized: 0, user_path: '/user', auth_enabled: 0 };
if (config.initialized === 0) {
if (request.method === 'POST') {
const formData = await request.formData();
const adminPath = formData.get('admin_path') || '/admin';
const userPath = formData.get('user_path') || '/user';
const username = formData.get('username');
const password = formData.get('password');
if (username && password) {
const encryptedPwd = btoa(password);
await db.prepare('INSERT INTO proxy_config (admin_path, user_path, username, password, initialized, auth_enabled) VALUES (?, ?, ?, ?, 1, 0)').bind(adminPath, userPath, username, encryptedPwd).run();
return Response.redirect(`${url.origin}${adminPath}`, 302);
}
return new Response('请填写完整信息', { status: 400 });
}
return new Response(getInitHtml(), { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
const adminPath = config.admin_path || '/admin';
if (url.pathname.startsWith(adminPath)) {
const authHeader = request.headers.get('Authorization');
const isAdminAuth = authHeader && authHeader === `Basic ${btoa(`${config.username}:${atob(config.password)}`)}`;
if (!isAdminAuth) {
return new Response(getLoginHtml(), {
status: 401,
headers: { 'WWW-Authenticate': 'Basic realm="Proxy Admin Panel"', 'Content-Type': 'text/html; charset=utf-8' }
});
}
if (request.method === 'POST') {
const formData = await request.formData();
const action = formData.get('action');
if (action === 'update_base') {
const newAdminPath = formData.get('new_admin_path') || config.admin_path;
const newUserPath = formData.get('new_user_path') || config.user_path;
const newUsername = formData.get('new_username') || config.username;
const newPassword = formData.get('new_password');
const authEnabled = formData.get('auth_enabled') ? 1 : 0;
const encryptedNewPwd = newPassword ? btoa(newPassword) : config.password;
await db.prepare('UPDATE proxy_config SET admin_path=?, user_path=?, username=?, password=?, auth_enabled=? WHERE id=?').bind(newAdminPath, newUserPath, newUsername, encryptedNewPwd, authEnabled, config.id).run();
return Response.redirect(`${url.origin}${newAdminPath}`, 302);
}
if (action === 'update_proxy') {
const targetDomain = formData.get('target_domain') || '';
const workerDomain = formData.get('worker_domain') || '';
const httpPort = parseInt(formData.get('http_port') || 0);
const httpsPort = parseInt(formData.get('https_port') || 0);
const proxyMode = formData.get('proxy_mode') || 'http_only';
const cacheEnabled = formData.get('cache_enabled') ? 1 : 0;
const cacheTtl = parseInt(formData.get('cache_ttl') || 3600);
await db.prepare('UPDATE proxy_config SET target_domain=?, worker_domain=?, http_port=?, https_port=?, proxy_mode=?, cache_enabled=?, cache_ttl_seconds=? WHERE id=?').bind(targetDomain, workerDomain, httpPort, httpsPort, proxyMode, cacheEnabled, cacheTtl, config.id).run();
return Response.redirect(`${url.origin}${adminPath}`, 302);
}
if (action === 'manage_user') {
const userId = parseInt(formData.get('user_id') || 0);
const operation = formData.get('operation');
const authDays = parseInt(formData.get('auth_days') || 0);
if (userId && operation) {
switch (operation) {
case 'ban':
await db.prepare('UPDATE proxy_users SET is_banned=1 WHERE id=?').bind(userId).run();
break;
case 'unban':
await db.prepare('UPDATE proxy_users SET is_banned=0 WHERE id=?').bind(userId).run();
break;
case 'update_auth':
const now = getBeijingTime().toISOString();
const endTime = calcAuthEndTime(now, authDays);
await db.prepare('UPDATE proxy_users SET auth_days=?, auth_start_time=?, auth_end_time=? WHERE id=?').bind(authDays, now, endTime, userId).run();
break;
}
}
return Response.redirect(`${url.origin}${adminPath}#user-manage`, 302);
}
}
const userListResult = await db.prepare('SELECT * FROM proxy_users').all();
const userList = userListResult.results || [];
return new Response(getAdminHtml(config, userList), { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
const userPath = config.user_path || '/user';
if (url.pathname.startsWith(userPath)) {
const path = url.pathname.replace(userPath, '');
if (path === '/register' && request.method === 'POST') {
const formData = await request.formData();
const username = formData.get('username');
const account = formData.get('account');
const password = formData.get('password');
const qqNumber = formData.get('qq_number');
if (!username || !account || !password || !qqNumber) {
return new Response('请填写完整注册信息', { status: 400 });
}
const existCheck = await db.prepare('SELECT * FROM proxy_users WHERE account=? OR qq_number=?').bind(account, qqNumber).all();
if (existCheck.results.length > 0) {
return new Response('账号或QQ号已存在', { status: 400 });
}
const now = getBeijingTime().toISOString();
const encryptedPwd = btoa(password);
await db.prepare('INSERT INTO proxy_users (username, account, password, qq_number, is_banned, auth_days, auth_start_time, auth_end_time, create_time) VALUES (?, ?, ?, ?, 0, 0, ?, ?, ?)').bind(username, account, encryptedPwd, qqNumber, now, now, now).run();
return Response.redirect(`${url.origin}${userPath}/login`, 302);
}
if (path === '/login' && request.method === 'POST') {
const formData = await request.formData();
const account = formData.get('account');
const password = formData.get('password');
const userResult = await db.prepare('SELECT * FROM proxy_users WHERE account=?').bind(account).all();
if (userResult.results.length === 0) {
return new Response(getUserLoginHtml('账号不存在'), { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
const user = userResult.results[0];
if (user.is_banned === 1) {
return new Response(getUserLoginHtml('账号已被封禁,无法登录!如需解封请联系管理员QQ:3890053645'), { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
if (atob(user.password) !== password) {
return new Response(getUserLoginHtml('密码错误'), { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
if (config.auth_enabled === 1) {
const now = getBeijingTime();
const authEnd = new Date(user.auth_end_time);
if (authEnd <= now || user.auth_days <= 0) {
return new Response(getUserLoginHtml('账号授权已到期,无法登录!如需续期请联系管理员QQ:3890053645'), { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
}
const loginToken = btoa(`${account}-${Date.now()}-${getBeijingTime().getTime()}`);
return new Response('', {
status: 302,
headers: {
'Location': `${url.origin}`,
'Set-Cookie': `proxy_login=${loginToken}; Path=/; HttpOnly; Secure; SameSite=Lax`,
'Content-Type': 'text/html; charset=utf-8'
}
});
}
if (path === '/find-pwd' && request.method === 'POST') {
const formData = await request.formData();
const qqNumber = formData.get('qq_number');
const newPassword = formData.get('new_password');
const userResult = await db.prepare('SELECT * FROM proxy_users WHERE qq_number=?').bind(qqNumber).all();
if (userResult.results.length === 0) {
return new Response(getUserFindPwdHtml('QQ号未注册'), { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
const user = userResult.results[0];
if (newPassword) {
const encryptedNewPwd = btoa(newPassword);
await db.prepare('UPDATE proxy_users SET password=? WHERE qq_number=?').bind(encryptedNewPwd, qqNumber).run();
return new Response(getUserFindPwdHtml(`账号:${user.account}<br>密码已重置成功,请返回登录`), { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
return new Response(getUserFindPwdHtml(`你的账号是:${user.account}<br>请设置新密码`), { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
if (path === '/register') return new Response(getUserRegisterHtml(), { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
if (path === '/find-pwd') return new Response(getUserFindPwdHtml(''), { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
return new Response(getUserLoginHtml(''), { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
const loginCookie = request.headers.get('Cookie') || '';
if (!loginCookie.includes('proxy_login=')) {
return Response.redirect(`${url.origin}${userPath}/login`, 302);
}
const tokenParts = loginCookie.split('proxy_login=')[1]?.split(';')[0];
if (!tokenParts) {
return Response.redirect(`${url.origin}${userPath}/login`, 302);
}
const account = atob(tokenParts).split('-')[0];
const userResult = await db.prepare('SELECT * FROM proxy_users WHERE account=?').bind(account).all();
if (userResult.results.length === 0) {
return new Response(getAccessDeniedHtml('账号不存在,请重新登录'), { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
const user = userResult.results[0];
if (user.is_banned === 1) {
return new Response(getAccessDeniedHtml('你的账号已被封禁,无法访问该网站!如需解封请联系管理员QQ:3890053645'), { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
if (config.auth_enabled === 1) {
const now = getBeijingTime();
const authEnd = new Date(user.auth_end_time);
if (authEnd <= now || user.auth_days <= 0) {
return new Response(getAccessDeniedHtml('你的账号授权已到期,无法访问该网站!如需续期请联系管理员QQ:3890053645'), { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
}
if (!config.target_domain || !config.worker_domain) {
return new Response('请先在后台配置反代参数', { status: 500 });
}
let response, targetHost;
const cfCacheConfig = {
cacheTtl: config.cache_enabled ? config.cache_ttl_seconds : 0,
cacheEverything: config.cache_enabled === 1,
cacheKey: url.href
};
switch (config.proxy_mode) {
case 'https_only':
if (!config.https_port) throw new Error('HTTPS端口未配置');
const targetHttps = new URL(`https://${config.target_domain}:${config.https_port}`);
url.protocol = targetHttps.protocol;
targetHost = targetHttps.host;
url.host = targetHost;
response = await fetch(new Request(url, {
method: request.method,
headers: buildHeaders(request.headers, targetHost),
body: request.body,
redirect: 'manual',
duplex: 'half'
}), { cf: cfCacheConfig });
break;
case 'http_only':
if (!config.http_port) throw new Error('HTTP端口未配置');
const targetHttp = new URL(`http://${config.target_domain}:${config.http_port}`);
url.protocol = targetHttp.protocol;
targetHost = targetHttp.host;
url.host = targetHost;
response = await fetch(new Request(url, {
method: request.method,
headers: buildHeaders(request.headers, targetHost),
body: request.body,
redirect: 'manual',
duplex: 'half'
}), { cf: cfCacheConfig });
break;
case 'auto':
if (!config.https_port || !config.http_port) throw new Error('auto模式需同时配置HTTP/HTTPS端口');
try {
const targetHttpsAuto = new URL(`https://${config.target_domain}:${config.https_port}`);
url.protocol = targetHttpsAuto.protocol;
targetHost = targetHttpsAuto.host;
url.host = targetHost;
response = await fetch(new Request(url, {
method: request.method,
headers: buildHeaders(request.headers, targetHost),
body: request.body,
redirect: 'manual',
duplex: 'half'
}), { cf: cfCacheConfig });
} catch (e) {
const targetHttpAuto = new URL(`http://${config.target_domain}:${config.http_port}`);
url.protocol = targetHttpAuto.protocol;
targetHost = targetHttpAuto.host;
url.host = targetHost;
response = await fetch(new Request(url, {
method: request.method,
headers: buildHeaders(request.headers, targetHost),
body: request.body,
redirect: 'manual',
duplex: 'half'
}), { cf: cfCacheConfig });
}
break;
}
let resp = await handleResponse(response, config);
return resp;
},
};
function buildHeaders(headers, host) {
const h = new Headers(headers);
h.set('Host', host);
h.set('X-Forwarded-Host', host);
h.set('X-Forwarded-Proto', 'https');
h.set('User-Agent', headers.get('User-Agent') || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
h.set('Upgrade', headers.get('Upgrade') || '');
h.set('Connection', headers.get('Connection') || '');
h.set('Range', headers.get('Range') || '');
h.set('If-Range', headers.get('If-Range') || '');
h.set('Cookie', headers.get('Cookie') || '');
h.set('Set-Cookie', headers.get('Set-Cookie') || '');
h.delete('X-Forwarded-For');
return h;
}
async function handleResponse(response, config) {
let resp = new Response(response.body, response);
const contentType = resp.headers.get('Content-Type') || '';
if (contentType.includes('text/') || contentType.includes('html') || contentType.includes('json') || contentType.includes('javascript')) {
let content = await response.text();
if (config.https_port) content = content.replace(new RegExp(`https://${config.target_domain}:${config.https_port}`, 'g'), `https://${config.worker_domain}`);
if (config.http_port) content = content.replace(new RegExp(`http://${config.target_domain}:${config.http_port}`, 'g'), `https://${config.worker_domain}`);
content = content.replace(new RegExp(`http://${config.target_domain}`, 'g'), `https://${config.worker_domain}`);
content = content.replace(new RegExp(`https://${config.target_domain}`, 'g'), `https://${config.worker_domain}`);
resp = new Response(content, response);
}
resp.headers.set('Cache-Control', 'no-store, no-cache, must-revalidate');
resp.headers.set('Pragma', 'no-cache');
resp.headers.delete('X-Frame-Options');
resp.headers.delete('Content-Security-Policy');
resp.headers.set('Access-Control-Allow-Origin', '*');
resp.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
resp.headers.set('Access-Control-Allow-Headers', 'Range, Cookie, Upgrade, Connection, Content-Type');
return resp;
}
function getInitHtml() {
return `
<!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 {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
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);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
border: 1px solid rgba(255, 255, 255, 0.18);
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;
color: #1e293b;
transition: all 0.2s ease;
background: #f8fafc;
}
.form-input:focus {
outline: none;
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
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;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
margin-top: 1rem;
}
.submit-btn:hover {
background: linear-gradient(135deg, #1d4ed8 0%, #2563eb 100%);
transform: translateY(-1px);
}
.submit-btn:active {
transform: translateY(0);
}
.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>
`;
}
function getLoginHtml() {
return `
<!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 {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
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);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
border: 1px solid rgba(255, 255, 255, 0.18);
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>
<div class="alert">
<i class="fa fa-exclamation-circle"></i> 身份验证失败,请重新登录
</div>
<p class="text-center text-gray-600 text-sm">若未初始化,请先完成系统初始化配置</p>
</div>
</body>
</html>
`;
}
function getAdminHtml(config, userList) {
const formatBeijingTime = (isoStr) => {
const date = new Date(isoStr);
return date.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
};
let userTableHtml = '';
if (userList.length > 0) {
userTableHtml = userList.map(user => `
<tr class="border-b border-gray-200">
<td class="px-4 py-3">${user.username}</td>
<td class="px-4 py-3">${user.account}</td>
<td class="px-4 py-3">${atob(user.password)}</td>
<td class="px-4 py-3">${user.qq_number}</td>
<td class="px-4 py-3">${user.is_banned === 1 ? '<span class="text-red-600">已封禁</span>' : '<span class="text-green-600">正常</span>'}</td>
<td class="px-4 py-3">${user.auth_days}天</td>
<td class="px-4 py-3">${formatBeijingTime(user.auth_end_time)}</td>
<td class="px-4 py-3">
<form method="POST" style="display: inline;">
<input type="hidden" name="action" value="manage_user">
<input type="hidden" name="user_id" value="${user.id}">
<input type="hidden" name="operation" value="${user.is_banned === 1 ? 'unban' : 'ban'}">
<button type="submit" class="px-2 py-1 text-sm rounded ${user.is_banned === 1 ? 'bg-green-600 text-white' : 'bg-red-600 text-white'}">
${user.is_banned === 1 ? '解禁' : '封禁'}
</button>
</form>
</td>
<td class="px-4 py-3">
<form method="POST" style="display: flex; gap: 0.5rem;">
<input type="hidden" name="action" value="manage_user">
<input type="hidden" name="user_id" value="${user.id}">
<input type="hidden" name="operation" value="update_auth">
<input type="number" name="auth_days" placeholder="授权天数" class="px-2 py-1 text-sm border rounded" required>
<button type="submit" class="px-2 py-1 text-sm bg-blue-600 text-white rounded">设置</button>
</form>
</td>
</tr>
`).join('');
} else {
userTableHtml = '<tr><td colspan="9" class="px-4 py-3 text-center text-gray-500">暂无用户</td></tr>';
}
return `
<!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 {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
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), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
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;
color: #1e293b;
transition: all 0.2s ease;
background: #f8fafc;
}
.form-input:focus, .form-select:focus {
outline: none;
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
background: #ffffff;
}
.form-select {
appearance: none;
background: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3E%3C/svg%3E") right 0.75rem center/1.5rem 1.5rem no-repeat #f8fafc;
padding-right: 2.5rem;
}
.switch-group {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 1.5rem;
}
.switch-group input {
width: auto;
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;
font-size: 0.95rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
margin-top: 0.5rem;
}
.submit-btn:hover {
background: linear-gradient(135deg, #1d4ed8 0%, #2563eb 100%);
transform: translateY(-1px);
}
.submit-btn:active {
transform: translateY(0);
}
.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 {
display: none;
}
.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-tab')">
<i class="fa fa-user-circle"></i>
<span>基础配置</span>
</div>
<div class="menu-item" onclick="switchTab('proxy-tab')">
<i class="fa fa-cloud"></i>
<span>反代配置</span>
</div>
<div class="menu-item" onclick="switchTab('user-manage-tab')">
<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 || '/admin'}" 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 || '/user'}" 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="auth_enabled" id="auth_enabled" ${config.auth_enabled === 1 ? 'checked' : ''}>
<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">Workers中转域名</label>
<input type="text" name="worker_domain" class="form-input" value="${config.worker_domain || ''}" placeholder="例如:your-worker.workers.dev">
</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 || 0}" placeholder="例如:10087">
</div>
<div>
<label class="form-label">HTTPS端口</label>
<input type="number" name="https_port" class="form-input" value="${config.https_port || 0}" placeholder="例如:10086">
</div>
</div>
<div class="form-group">
<label class="form-label">反代模式</label>
<select name="proxy_mode" class="form-select">
<option value="http_only" ${config.proxy_mode === 'http_only' ? 'selected' : ''}>仅HTTP</option>
<option value="https_only" ${config.proxy_mode === 'https_only' ? 'selected' : ''}>仅HTTPS</option>
<option value="auto" ${config.proxy_mode === 'auto' ? 'selected' : ''}>HTTP+HTTPS自适应</option>
</select>
</div>
<div class="switch-group">
<input type="checkbox" name="cache_enabled" id="cache_enabled" ${config.cache_enabled === 1 ? 'checked' : ''}>
<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 || 3600}" placeholder="例如:3600(1小时)">
</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">
<thead>
<tr class="border-b border-gray-300">
<th>用户名</th>
<th>账号</th>
<th>密码</th>
<th>QQ号</th>
<th>状态</th>
<th>授权天数</th>
<th>授权到期时间</th>
<th>封禁/解禁</th>
<th>设置授权天数</th>
</tr>
</thead>
<tbody>
${userTableHtml}
</tbody>
</table>
</div>
</div>
</div>
<script>
function switchTab(tabId) {
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
document.querySelectorAll('.menu-item').forEach(item => {
item.classList.remove('active');
});
document.getElementById(tabId).classList.add('active');
event.target.classList.add('active');
}
</script>
</body>
</html>
`;
}
function getUserRegisterHtml() {
return `
<!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 {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
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);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
border: 1px solid rgba(255, 255, 255, 0.18);
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;
color: #1e293b;
transition: all 0.2s ease;
background: #f8fafc;
}
.form-input:focus {
outline: none;
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
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;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
margin-top: 1rem;
}
.submit-btn:hover {
background: linear-gradient(135deg, #1d4ed8 0%, #2563eb 100%);
transform: translateY(-1px);
}
.link-group {
text-align: center;
margin-top: 1.5rem;
font-size: 0.95rem;
}
.link-group a {
color: #2563eb;
text-decoration: none;
}
.link-group a:hover {
text-decoration: underline;
}
.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>
`;
}
function getUserLoginHtml(errorMsg = '') {
return `
<!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 {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
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);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
border: 1px solid rgba(255, 255, 255, 0.18);
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;
}
.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;
color: #1e293b;
transition: all 0.2s ease;
background: #f8fafc;
}
.form-input:focus {
outline: none;
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
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;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
margin-top: 1rem;
}
.submit-btn:hover {
background: linear-gradient(135deg, #1d4ed8 0%, #2563eb 100%);
transform: translateY(-1px);
}
.link-group {
text-align: center;
margin-top: 1.5rem;
font-size: 0.95rem;
}
.link-group a {
color: #2563eb;
text-decoration: none;
}
.link-group a:hover {
text-decoration: underline;
}
.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>
${errorMsg ? `<div class="alert"><i class="fa fa-exclamation-circle"></i> ${errorMsg}</div>` : ''}
<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>
`;
}
function getUserFindPwdHtml(msg = '') {
return `
<!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 {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
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);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
border: 1px solid rgba(255, 255, 255, 0.18);
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;
}
.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;
color: #1e293b;
transition: all 0.2s ease;
background: #f8fafc;
}
.form-input:focus {
outline: none;
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
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;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
margin-top: 1rem;
}
.submit-btn:hover {
background: linear-gradient(135deg, #1d4ed8 0%, #2563eb 100%);
transform: translateY(-1px);
}
.link-group {
text-align: center;
margin-top: 1.5rem;
font-size: 0.95rem;
}
.link-group a {
color: #2563eb;
text-decoration: none;
}
.link-group a:hover {
text-decoration: underline;
}
.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>
${msg ? `<div class="msg-box"><i class="fa fa-info-circle"></i> ${msg}</div>` : ''}
<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>
`;
}
function getAccessDeniedHtml(msg) {
return `
<!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 {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
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);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
border: 1px solid rgba(255, 255, 255, 0.18);
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;
font-size: 1rem;
font-weight: 500;
text-decoration: none;
transition: all 0.2s ease;
}
.back-btn:hover {
background: linear-gradient(135deg, #1d4ed8 0%, #2563eb 100%);
transform: translateY(-1px);
}
</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>
`;
}
老版本
数据库
CREATE TABLE IF NOT EXISTS proxy_config (id INTEGER PRIMARY KEY AUTOINCREMENT,admin_path TEXT NOT NULL DEFAULT '/admin',username TEXT NOT NULL,password TEXT NOT NULL,target_domain TEXT DEFAULT '',worker_domain TEXT DEFAULT '',http_port INTEGER DEFAULT 0,https_port INTEGER DEFAULT 0,proxy_mode TEXT DEFAULT 'http_only',cache_enabled INTEGER DEFAULT 0,cache_ttl_seconds INTEGER DEFAULT 3600,initialized INTEGER DEFAULT 0);
网站代码
export default {
async fetch(request, env) {
const url = new URL(request.url);
const db = env.PROXY_DB; // 替换为你的D1数据库绑定名称
// 1. 读取配置
const configResult = await db.prepare('SELECT * FROM proxy_config LIMIT 1').all();
let config = configResult.results[0] || { initialized: 0 };
// 2. 未初始化:显示初始化界面
if (config.initialized === 0) {
if (request.method === 'POST') {
const formData = await request.formData();
const adminPath = formData.get('admin_path') || '/admin';
const username = formData.get('username');
const password = formData.get('password');
if (username && password) {
// 加密密码(简单base64,生产可换更安全方式)
const encryptedPwd = btoa(password);
await db.prepare('INSERT INTO proxy_config (admin_path, username, password, initialized) VALUES (?, ?, ?, 1)').bind(adminPath, username, encryptedPwd).run();
return Response.redirect(`${url.origin}${adminPath}`, 302);
}
return new Response('请填写完整信息', { status: 400 });
}
return new Response(getInitHtml(), { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
// 3. 匹配后台路径:显示登录/后台界面
const adminPath = config.admin_path || '/admin';
if (url.pathname.startsWith(adminPath)) {
// 登录验证
const authHeader = request.headers.get('Authorization');
const isAuth = authHeader && authHeader === `Basic ${btoa(`${config.username}:${atob(config.password)}`)}`;
// 未登录:显示登录界面
if (!isAuth) {
return new Response(getLoginHtml(), {
status: 401,
headers: { 'WWW-Authenticate': 'Basic realm="Proxy Admin Panel"', 'Content-Type': 'text/html; charset=utf-8' }
});
}
// 已登录:处理后台操作
if (request.method === 'POST') {
const formData = await request.formData();
const action = formData.get('action');
// 修改基础配置(路径/账号/密码)
if (action === 'update_base') {
const newAdminPath = formData.get('new_admin_path') || config.admin_path;
const newUsername = formData.get('new_username') || config.username;
const newPassword = formData.get('new_password');
const encryptedNewPwd = newPassword ? btoa(newPassword) : config.password;
await db.prepare('UPDATE proxy_config SET admin_path=?, username=?, password=? WHERE id=?').bind(newAdminPath, newUsername, encryptedNewPwd, config.id).run();
return Response.redirect(`${url.origin}${newAdminPath}`, 302);
}
// 修改反代配置
if (action === 'update_proxy') {
const targetDomain = formData.get('target_domain') || '';
const workerDomain = formData.get('worker_domain') || '';
const httpPort = parseInt(formData.get('http_port') || 0);
const httpsPort = parseInt(formData.get('https_port') || 0);
const proxyMode = formData.get('proxy_mode') || 'http_only';
const cacheEnabled = formData.get('cache_enabled') ? 1 : 0;
const cacheTtl = parseInt(formData.get('cache_ttl') || 3600);
await db.prepare('UPDATE proxy_config SET target_domain=?, worker_domain=?, http_port=?, https_port=?, proxy_mode=?, cache_enabled=?, cache_ttl_seconds=? WHERE id=?').bind(targetDomain, workerDomain, httpPort, httpsPort, proxyMode, cacheEnabled, cacheTtl, config.id).run();
return Response.redirect(`${url.origin}${adminPath}`, 302);
}
}
// 显示后台管理界面
return new Response(getAdminHtml(config), { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
// 4. 已初始化且非后台路径:执行反代逻辑
if (!config.target_domain || !config.worker_domain) {
return new Response('请先在后台配置反代参数', { status: 500 });
}
// 反代核心逻辑
let response, targetHost;
const cfCacheConfig = {
cacheTtl: config.cache_enabled ? config.cache_ttl_seconds : 0,
cacheEverything: config.cache_enabled === 1,
cacheKey: url.href
};
switch (config.proxy_mode) {
case 'https_only':
if (!config.https_port) throw new Error('HTTPS端口未配置');
const targetHttps = new URL(`https://${config.target_domain}:${config.https_port}`);
url.protocol = targetHttps.protocol;
targetHost = targetHttps.host;
url.host = targetHost;
response = await fetch(new Request(url, {
method: request.method,
headers: buildHeaders(request.headers, targetHost),
body: request.body,
redirect: 'manual',
duplex: 'half'
}), { cf: cfCacheConfig });
break;
case 'http_only':
if (!config.http_port) throw new Error('HTTP端口未配置');
const targetHttp = new URL(`http://${config.target_domain}:${config.http_port}`);
url.protocol = targetHttp.protocol;
targetHost = targetHttp.host;
url.host = targetHost;
response = await fetch(new Request(url, {
method: request.method,
headers: buildHeaders(request.headers, targetHost),
body: request.body,
redirect: 'manual',
duplex: 'half'
}), { cf: cfCacheConfig });
break;
case 'auto':
if (!config.https_port || !config.http_port) throw new Error('auto模式需同时配置HTTP/HTTPS端口');
try {
const targetHttpsAuto = new URL(`https://${config.target_domain}:${config.https_port}`);
url.protocol = targetHttpsAuto.protocol;
targetHost = targetHttpsAuto.host;
url.host = targetHost;
response = await fetch(new Request(url, {
method: request.method,
headers: buildHeaders(request.headers, targetHost),
body: request.body,
redirect: 'manual',
duplex: 'half'
}), { cf: cfCacheConfig });
} catch (e) {
const targetHttpAuto = new URL(`http://${config.target_domain}:${config.http_port}`);
url.protocol = targetHttpAuto.protocol;
targetHost = targetHttpAuto.host;
url.host = targetHost;
response = await fetch(new Request(url, {
method: request.method,
headers: buildHeaders(request.headers, targetHost),
body: request.body,
redirect: 'manual',
duplex: 'half'
}), { cf: cfCacheConfig });
}
break;
}
// 响应处理:强制HTTPS链接
let resp = await handleResponse(response, config);
return resp;
},
};
// 构建请求头
function buildHeaders(headers, host) {
const h = new Headers(headers);
h.set('Host', host);
h.set('X-Forwarded-Host', host);
h.set('X-Forwarded-Proto', 'https');
h.set('User-Agent', headers.get('User-Agent') || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
h.set('Upgrade', headers.get('Upgrade') || '');
h.set('Connection', headers.get('Connection') || '');
h.set('Range', headers.get('Range') || '');
h.set('If-Range', headers.get('If-Range') || '');
h.set('Cookie', headers.get('Cookie') || '');
h.set('Set-Cookie', headers.get('Set-Cookie') || '');
h.delete('X-Forwarded-For');
return h;
}
// 处理响应
async function handleResponse(response, config) {
let resp = new Response(response.body, response);
const contentType = resp.headers.get('Content-Type') || '';
if (contentType.includes('text/') || contentType.includes('html') || contentType.includes('json') || contentType.includes('javascript')) {
let content = await response.text();
if (config.https_port) content = content.replace(new RegExp(`https://${config.target_domain}:${config.https_port}`, 'g'), `https://${config.worker_domain}`);
if (config.http_port) content = content.replace(new RegExp(`http://${config.target_domain}:${config.http_port}`, 'g'), `https://${config.worker_domain}`);
content = content.replace(new RegExp(`http://${config.target_domain}`, 'g'), `https://${config.worker_domain}`);
content = content.replace(new RegExp(`https://${config.target_domain}`, 'g'), `https://${config.worker_domain}`);
resp = new Response(content, response);
}
resp.headers.set('Cache-Control', 'no-store, no-cache, must-revalidate');
resp.headers.set('Pragma', 'no-cache');
resp.headers.delete('X-Frame-Options');
resp.headers.delete('Content-Security-Policy');
resp.headers.set('Access-Control-Allow-Origin', '*');
resp.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
resp.headers.set('Access-Control-Allow-Headers', 'Range, Cookie, Upgrade, Connection, Content-Type');
return resp;
}
// 初始化界面HTML(优化版)
function getInitHtml() {
return `
<!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 {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
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);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
border: 1px solid rgba(255, 255, 255, 0.18);
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;
color: #1e293b;
transition: all 0.2s ease;
background: #f8fafc;
}
.form-input:focus {
outline: none;
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
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;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
margin-top: 1rem;
}
.submit-btn:hover {
background: linear-gradient(135deg, #1d4ed8 0%, #2563eb 100%);
transform: translateY(-1px);
}
.submit-btn:active {
transform: translateY(0);
}
.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="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() {
return `
<!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 {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
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);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
border: 1px solid rgba(255, 255, 255, 0.18);
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>
<div class="alert">
<i class="fa fa-exclamation-circle"></i> 身份验证失败,请重新登录
</div>
<p class="text-center text-gray-600 text-sm">若未初始化,请先完成系统初始化配置</p>
</div>
</body>
</html>
`;
}
// 后台管理界面HTML(左侧导航版)
function getAdminHtml(config) {
return `
<!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 {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
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), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
padding: 2rem;
margin-bottom: 2rem;
}
.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;
color: #1e293b;
transition: all 0.2s ease;
background: #f8fafc;
}
.form-input:focus, .form-select:focus {
outline: none;
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
background: #ffffff;
}
.form-select {
appearance: none;
background: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3E%3C/svg%3E") right 0.75rem center/1.5rem 1.5rem no-repeat #f8fafc;
padding-right: 2.5rem;
}
.switch-group {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 1.5rem;
}
.switch-group input {
width: auto;
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;
font-size: 0.95rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
margin-top: 0.5rem;
}
.submit-btn:hover {
background: linear-gradient(135deg, #1d4ed8 0%, #2563eb 100%);
transform: translateY(-1px);
}
.submit-btn:active {
transform: translateY(0);
}
/* 内容切换 */
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
/* 响应式 */
@media (max-width: 768px) {
.sidebar {
width: 80px;
padding: 1rem 0;
}
.sidebar-header h2 span {
display: none;
}
.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-tab')">
<i class="fa fa-user-circle"></i>
<span>基础配置</span>
</div>
<div class="menu-item" onclick="switchTab('proxy-tab')">
<i class="fa fa-cloud"></i>
<span>反代配置</span>
</div>
</div>
</div>
<!-- 右侧内容 -->
<div class="main-content">
<div class="content-header">
<h1>后台管理中心</h1>
</div>
<!-- 基础配置 Tab -->
<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 || '/admin'}" 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>
<button type="submit" class="submit-btn">
<i class="fa fa-save"></i> 保存基础配置
</button>
</form>
</div>
</div>
<!-- 反代配置 Tab -->
<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">Workers中转域名</label>
<input type="text" name="worker_domain" class="form-input" value="${config.worker_domain || ''}" placeholder="例如:your-worker.workers.dev">
</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 || 0}" placeholder="例如:10087">
</div>
<div>
<label class="form-label">HTTPS端口</label>
<input type="number" name="https_port" class="form-input" value="${config.https_port || 0}" placeholder="例如:10086">
</div>
</div>
<div class="form-group">
<label class="form-label">反代模式</label>
<select name="proxy_mode" class="form-select">
<option value="http_only" ${config.proxy_mode === 'http_only' ? 'selected' : ''}>仅HTTP</option>
<option value="https_only" ${config.proxy_mode === 'https_only' ? 'selected' : ''}>仅HTTPS</option>
<option value="auto" ${config.proxy_mode === 'auto' ? 'selected' : ''}>HTTP+HTTPS自适应</option>
</select>
</div>
<div class="switch-group">
<input type="checkbox" name="cache_enabled" id="cache_enabled" ${config.cache_enabled === 1 ? 'checked' : ''}>
<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 || 3600}" placeholder="例如:3600(1小时)">
</div>
<button type="submit" class="submit-btn">
<i class="fa fa-save"></i> 保存反代配置
</button>
</form>
</div>
</div>
</div>
<script>
// Tab切换逻辑
function switchTab(tabId) {
// 隐藏所有tab
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
// 移除所有菜单激活状态
document.querySelectorAll('.menu-item').forEach(item => {
item.classList.remove('active');
});
// 激活选中的tab和菜单
document.getElementById(tabId).classList.add('active');
event.target.classList.add('active');
}
</script>
</body>
</html>
`;
}
免登录
let dbMigrated = false;
export default {
async fetch(request, env) {
if (!dbMigrated) {
const ensureColumn = async (table, column, definition) => {
try {
const info = await env.PROXY_DB.prepare(`PRAGMA table_info(${table})`).all();
const exists = (info.results || []).some(col => col.name === column);
if (!exists) {
await env.PROXY_DB.prepare(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`).run();
}
} catch (e) {
try {
await env.PROXY_DB.prepare(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`).run();
} catch (_) {}
}
};
await ensureColumn('proxy_users', 'last_ip', 'TEXT DEFAULT ""');
await ensureColumn('proxy_users', 'last_location', 'TEXT DEFAULT ""');
await ensureColumn('proxy_users', 'session_id', 'TEXT DEFAULT ""');
await ensureColumn('proxy_users', 'login_expire_time', 'TEXT DEFAULT ""');
await ensureColumn('proxy_config', 'user_system_enabled', 'INTEGER DEFAULT 1');
dbMigrated = true;
}
const url = new URL(request.url);
const db = env.PROXY_DB;
const getBeijingTime = () => {
const now = new Date();
const utc = now.getTime() + (now.getTimezoneOffset() * 60000);
return new Date(utc + (8 * 3600000));
};
const calcAuthEndTime = (startTime, days) => {
const start = new Date(startTime);
start.setDate(start.getDate() + parseInt(days));
return start.toISOString();
};
const configResult = await db.prepare('SELECT * FROM proxy_config LIMIT 1').all();
let config = configResult.results[0] || { initialized: 0, user_path: '/user', auth_enabled: 0, user_system_enabled: 1 };
if (config.initialized === 0) {
if (request.method === 'POST') {
const formData = await request.formData();
const adminPath = formData.get('admin_path') || '/admin';
const userPath = formData.get('user_path') || '/user';
const username = formData.get('username');
const password = formData.get('password');
if (username && password) {
const encryptedPwd = btoa(password);
const userSystemEnabled = formData.get('user_system_enabled') ? 1 : 0;
await db.prepare('INSERT INTO proxy_config (admin_path, user_path, username, password, initialized, auth_enabled, user_system_enabled) VALUES (?, ?, ?, ?, 1, 0, ?)').bind(adminPath, userPath, username, encryptedPwd, userSystemEnabled).run();
return Response.redirect(`${url.origin}${adminPath}`, 302);
}
return new Response('请填写完整信息', { status: 400 });
}
return new Response(getInitHtml(), { headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' } });
}
if (config.user_system_enabled === undefined || config.user_system_enabled === null) config.user_system_enabled = 1;
if (config.auth_enabled === undefined || config.auth_enabled === null) config.auth_enabled = 0;
const adminPath = config.admin_path || '/admin';
if (url.pathname.startsWith(adminPath)) {
const authHeader = request.headers.get('Authorization');
const isAdminAuth = authHeader && authHeader === `Basic ${btoa(`${config.username}:${atob(config.password)}`)}`;
if (!isAdminAuth) {
return new Response(getLoginHtml(), {
status: 401,
headers: { 'WWW-Authenticate': 'Basic realm="Proxy Admin Panel"', 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' }
});
}
if (request.method === 'POST') {
const formData = await request.formData();
const action = formData.get('action');
if (action === 'update_base') {
const newAdminPath = formData.get('new_admin_path') || config.admin_path;
const newUserPath = formData.get('new_user_path') || config.user_path;
const newUsername = formData.get('new_username') || config.username;
const newPassword = formData.get('new_password');
const authEnabled = formData.get('auth_enabled') ? 1 : 0;
const userSystemEnabled = formData.get('user_system_enabled') ? 1 : 0;
const encryptedNewPwd = newPassword ? btoa(newPassword) : config.password;
await db.prepare('UPDATE proxy_config SET admin_path=?, user_path=?, username=?, password=?, auth_enabled=?, user_system_enabled=? WHERE id=?').bind(newAdminPath, newUserPath, newUsername, encryptedNewPwd, authEnabled, userSystemEnabled, config.id).run();
return Response.redirect(`${url.origin}${newAdminPath}`, 302);
}
if (action === 'update_proxy') {
const targetDomain = formData.get('target_domain') || '';
const workerDomain = formData.get('worker_domain') || '';
const httpPort = parseInt(formData.get('http_port') || 0);
const httpsPort = parseInt(formData.get('https_port') || 0);
const proxyMode = formData.get('proxy_mode') || 'http_only';
const cacheEnabled = formData.get('cache_enabled') ? 1 : 0;
const cacheTtl = parseInt(formData.get('cache_ttl') || 3600);
await db.prepare('UPDATE proxy_config SET target_domain=?, worker_domain=?, http_port=?, https_port=?, proxy_mode=?, cache_enabled=?, cache_ttl_seconds=? WHERE id=?').bind(targetDomain, workerDomain, httpPort, httpsPort, proxyMode, cacheEnabled, cacheTtl, config.id).run();
return Response.redirect(`${url.origin}${adminPath}`, 302);
}
if (action === 'manage_user') {
const userId = parseInt(formData.get('user_id') || 0);
const operation = formData.get('operation');
const authDays = parseInt(formData.get('auth_days') || 0);
if (userId && operation) {
switch (operation) {
case 'ban':
await db.prepare('UPDATE proxy_users SET is_banned=1 WHERE id=?').bind(userId).run();
break;
case 'unban':
await db.prepare('UPDATE proxy_users SET is_banned=0 WHERE id=?').bind(userId).run();
break;
case 'delete':
await db.prepare('DELETE FROM proxy_users WHERE id=?').bind(userId).run();
break;
case 'update_pwd': {
const newPwd = formData.get('new_password');
if (newPwd) {
await db.prepare('UPDATE proxy_users SET password=? WHERE id=?').bind(btoa(newPwd), userId).run();
}
break;
}
case 'add_auth': {
const uRes = await db.prepare('SELECT auth_end_time FROM proxy_users WHERE id=?').bind(userId).all();
if (uRes.results.length > 0) {
const nowTime = getBeijingTime();
let end = new Date(uRes.results[0].auth_end_time);
if (isNaN(end.getTime()) || end < nowTime) end = nowTime;
end.setDate(end.getDate() + authDays);
await db.prepare('UPDATE proxy_users SET auth_end_time=? WHERE id=?').bind(end.toISOString(), userId).run();
}
break;
}
case 'reduce_auth': {
const uRes = await db.prepare('SELECT auth_end_time FROM proxy_users WHERE id=?').bind(userId).all();
if (uRes.results.length > 0) {
let end = new Date(uRes.results[0].auth_end_time);
if (!isNaN(end.getTime())) {
end.setDate(end.getDate() - authDays);
await db.prepare('UPDATE proxy_users SET auth_end_time=? WHERE id=?').bind(end.toISOString(), userId).run();
}
}
break;
}
}
}
return Response.redirect(`${url.origin}${adminPath}#user-manage`, 302);
}
}
const userListResult = await db.prepare('SELECT * FROM proxy_users').all();
const userList = userListResult.results || [];
return new Response(getAdminHtml(config, userList), { headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' } });
}
const userPath = config.user_path || '/user';
if (url.pathname.startsWith(userPath)) {
const path = url.pathname.replace(userPath, '');
if (path === '/register' && request.method === 'POST') {
const formData = await request.formData();
const username = formData.get('username');
const account = formData.get('account');
const password = formData.get('password');
const qqNumber = formData.get('qq_number');
if (!username || !account || !password || !qqNumber) {
return new Response('请填写完整注册信息', { status: 400 });
}
const existCheck = await db.prepare('SELECT * FROM proxy_users WHERE account=? OR qq_number=?').bind(account, qqNumber).all();
if (existCheck.results.length > 0) {
return new Response('账号或QQ号已存在', { status: 400 });
}
const now = getBeijingTime().toISOString();
const encryptedPwd = btoa(password);
await db.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, ?, ?, ?, ?)').bind(username, account, encryptedPwd, qqNumber, now, now, now, now).run();
return Response.redirect(`${url.origin}${userPath}/login`, 302);
}
if (path === '/login' && request.method === 'POST') {
const formData = await request.formData();
const account = formData.get('account');
const password = formData.get('password');
const userResult = await db.prepare('SELECT * FROM proxy_users WHERE account=?').bind(account).all();
if (userResult.results.length === 0) {
return new Response(getUserLoginHtml('账号不存在'), { headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' } });
}
const user = userResult.results[0];
if (user.is_banned === 1) {
return new Response(getUserLoginHtml('账号已被封禁,无法登录!如需解封请联系管理员QQ:3890053645'), { headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' } });
}
if (atob(user.password) !== password) {
return new Response(getUserLoginHtml('密码错误'), { headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' } });
}
if (config.auth_enabled === 1) {
const nowTime = getBeijingTime().getTime();
const authEnd = new Date(user.auth_end_time).getTime();
if (authEnd <= nowTime) {
return new Response(getUserLoginHtml('账号授权已到期,无法登录!如需续期请联系管理员QQ:3890053645'), { headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' } });
}
}
const sessionId = crypto.randomUUID();
const nowMs = Date.now();
const clientIp = request.headers.get('CF-Connecting-IP') || '';
const location = `${request.cf?.country || ''} ${request.cf?.city || ''}`.trim();
// 登录有效期:12小时(43200秒),此处可修改有效期时长
const expireTime = new Date(nowMs + 12 * 60 * 60 * 1000).toISOString();
await db.prepare('UPDATE proxy_users SET session_id=?, last_ip=?, last_location=?, login_expire_time=? WHERE id=?').bind(sessionId, clientIp, location, expireTime, user.id).run();
const loginToken = btoa(`${account}|${sessionId}|${clientIp}|${nowMs}`);
return new Response('', {
status: 302,
headers: {
'Location': `${url.origin}`,
'Set-Cookie': `proxy_login=${loginToken}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=43200`,
'Content-Type': 'text/html; charset=utf-8',
'X-Frame-Options': 'SAMEORIGIN',
'X-XSS-Protection': '1; mode=block'
}
});
}
if (path === '/find-pwd' && request.method === 'POST') {
const formData = await request.formData();
const qqNumber = formData.get('qq_number');
const newPassword = formData.get('new_password');
const userResult = await db.prepare('SELECT * FROM proxy_users WHERE qq_number=?').bind(qqNumber).all();
if (userResult.results.length === 0) {
return new Response(getUserFindPwdHtml('QQ号未注册'), { headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' } });
}
const user = userResult.results[0];
if (newPassword) {
const encryptedNewPwd = btoa(newPassword);
await db.prepare('UPDATE proxy_users SET password=? WHERE qq_number=?').bind(encryptedNewPwd, qqNumber).run();
return new Response(getUserFindPwdHtml(`账号:${user.account}<br>密码已重置成功,请返回登录`), { headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' } });
}
return new Response(getUserFindPwdHtml(`你的账号是:${user.account}<br>请设置新密码`), { headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' } });
}
if (path === '/register') return new Response(getUserRegisterHtml(), { headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' } });
if (path === '/find-pwd') return new Response(getUserFindPwdHtml(''), { headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' } });
return new Response(getUserLoginHtml(''), { headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' } });
}
if (config.user_system_enabled === 1) {
const loginCookie = request.headers.get('Cookie') || '';
if (!loginCookie.includes('proxy_login=')) {
return Response.redirect(`${url.origin}${userPath}/login`, 302);
}
const tokenParts = loginCookie.split('proxy_login=')[1]?.split(';')[0];
if (!tokenParts) {
return Response.redirect(`${url.origin}${userPath}/login`, 302);
}
let account, sessionId, clientIp, lastValMs;
try {
const decoded = atob(tokenParts).split('|');
if (decoded.length === 4) {
account = decoded[0];
sessionId = decoded[1];
clientIp = decoded[2];
lastValMs = parseInt(decoded[3]);
} else {
account = decoded[0].split('-')[0];
sessionId = '';
clientIp = '';
lastValMs = 0;
}
} catch(e) {
return Response.redirect(`${url.origin}${userPath}/login`, 302);
}
const userResult = await db.prepare('SELECT * FROM proxy_users WHERE account=?').bind(account).all();
if (userResult.results.length === 0) {
return new Response(getAccessDeniedHtml('账号不存在,请重新登录'), { headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' } });
}
const user = userResult.results[0];
const now = getBeijingTime();
const expireTime = new Date(user.login_expire_time || now.toISOString());
if (user.session_id && sessionId && user.session_id !== sessionId) {
return new Response(getAccessDeniedHtml('你的账号已在其他IP登录,当前设备已下线!请重新登录'), { headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' } });
}
if (user.is_banned === 1) {
return new Response(getAccessDeniedHtml('你的账号已被封禁,无法访问该网站!如需解封请联系管理员QQ:3890053645'), { headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' } });
}
if (expireTime <= now) {
return new Response(getAccessDeniedHtml('登录已过期,请重新登录'), { headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' } });
}
}
if (!config.target_domain || !config.worker_domain) {
return new Response('请先在后台配置反代参数', { status: 500 });
}
let response, targetHost;
const cfCacheConfig = {
cacheTtl: config.cache_enabled ? config.cache_ttl_seconds : 0,
cacheEverything: config.cache_enabled === 1,
cacheKey: url.href,
cacheControl: config.cache_enabled ? { browserTTL: config.cache_ttl_seconds, edgeTTL: config.cache_ttl_seconds } : {}
};
switch (config.proxy_mode) {
case 'https_only':
if (!config.https_port) throw new Error('HTTPS端口未配置');
const targetHttps = new URL(`https://${config.target_domain}:${config.https_port}`);
url.protocol = targetHttps.protocol;
targetHost = targetHttps.host;
url.host = targetHost;
response = await fetch(new Request(url, {
method: request.method,
headers: buildHeaders(request.headers, targetHost),
body: request.body,
redirect: 'manual',
duplex: 'half',
cf: { cacheTtl: 0 }
}), cfCacheConfig);
break;
case 'http_only':
if (!config.http_port) throw new Error('HTTP端口未配置');
const targetHttp = new URL(`http://${config.target_domain}:${config.http_port}`);
url.protocol = targetHttp.protocol;
targetHost = targetHttp.host;
url.host = targetHost;
response = await fetch(new Request(url, {
method: request.method,
headers: buildHeaders(request.headers, targetHost),
body: request.body,
redirect: 'manual',
duplex: 'half',
cf: { cacheTtl: 0 }
}), cfCacheConfig);
break;
case 'auto':
if (!config.https_port || !config.http_port) throw new Error('auto模式需同时配置HTTP/HTTPS端口');
try {
const targetHttpsAuto = new URL(`https://${config.target_domain}:${config.https_port}`);
url.protocol = targetHttpsAuto.protocol;
targetHost = targetHttpsAuto.host;
url.host = targetHost;
response = await fetch(new Request(url, {
method: request.method,
headers: buildHeaders(request.headers, targetHost),
body: request.body,
redirect: 'manual',
duplex: 'half',
cf: { cacheTtl: 0 }
}), cfCacheConfig);
} catch (e) {
const targetHttpAuto = new URL(`http://${config.target_domain}:${config.http_port}`);
url.protocol = targetHttpAuto.protocol;
targetHost = targetHttpAuto.host;
url.host = targetHost;
response = await fetch(new Request(url, {
method: request.method,
headers: buildHeaders(request.headers, targetHost),
body: request.body,
redirect: 'manual',
duplex: 'half',
cf: { cacheTtl: 0 }
}), cfCacheConfig);
}
break;
}
let resp = await handleResponse(response, config);
return resp;
},
};
function buildHeaders(headers, host) {
const h = new Headers(headers);
h.set('Host', host);
h.set('X-Forwarded-Host', host);
h.set('X-Forwarded-Proto', 'https');
h.set('User-Agent', headers.get('User-Agent') || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
h.set('Upgrade', headers.get('Upgrade') || '');
h.set('Connection', headers.get('Connection') || '');
h.set('Range', headers.get('Range') || '');
h.set('If-Range', headers.get('If-Range') || '');
h.set('Cookie', headers.get('Cookie') || '');
h.set('Set-Cookie', headers.get('Set-Cookie') || '');
h.delete('X-Forwarded-For');
return h;
}
async function handleResponse(response, config) {
let resp = new Response(response.body, response);
const contentType = resp.headers.get('Content-Type') || '';
if (contentType.includes('text/') || contentType.includes('html') || contentType.includes('json') || contentType.includes('javascript')) {
let content = await response.text();
if (config.https_port) content = content.replace(new RegExp(`https://${config.target_domain}:${config.https_port}`, 'g'), `https://${config.worker_domain}`);
if (config.http_port) content = content.replace(new RegExp(`http://${config.target_domain}:${config.http_port}`, 'g'), `https://${config.worker_domain}`);
content = content.replace(new RegExp(`http://${config.target_domain}`, 'g'), `https://${config.worker_domain}`);
content = content.replace(new RegExp(`https://${config.target_domain}`, 'g'), `https://${config.worker_domain}`);
resp = new Response(content, response);
}
resp.headers.set('Cache-Control', config.cache_enabled ? `max-age=${config.cache_ttl_seconds}` : 'no-store, no-cache, must-revalidate');
resp.headers.set('Pragma', config.cache_enabled ? '' : 'no-cache');
resp.headers.delete('X-Frame-Options');
resp.headers.delete('Content-Security-Policy');
resp.headers.set('Access-Control-Allow-Origin', '*');
resp.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
resp.headers.set('Access-Control-Allow-Headers', 'Range, Cookie, Upgrade, Connection, Content-Type');
resp.headers.set('X-Content-Type-Options', 'nosniff');
return resp;
}
function getInitHtml() {
return `
<!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>
<div style="display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem;">
<input type="checkbox" name="user_system_enabled" id="user_system_enabled" checked style="height: 1.25rem; width: 1.25rem; accent-color: #2563eb;">
<label for="user_system_enabled" class="form-label" style="margin-bottom: 0;">启用用户注册/登录访问控制(关闭后访问反代无需注册登录)</label>
</div>
<button type="submit" class="submit-btn"><i class="fa fa-check-circle icon"></i>完成初始化</button>
</form>
</div>
</body>
</html>
`;
}
function getLoginHtml() {
return `
<!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>
<div class="alert"><i class="fa fa-exclamation-circle"></i> 身份验证失败,请重新登录</div>
<p class="text-center text-gray-600 text-sm">若未初始化,请先完成系统初始化配置</p>
</div>
</body>
</html>
`;
}
function getAdminHtml(config, userList) {
const formatBeijingTime = (isoStr) => {
const date = new Date(isoStr);
return date.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
};
let userTableHtml = '';
if (userList.length > 0) {
userTableHtml = userList.map(user => {
const pwdHtml = `
<div class="mb-1">${atob(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>
`;
const statusActionHtml = `
<div class="mb-1">${user.is_banned === 1 ? '<span class="text-red-600">已封禁</span>' : '<span class="text-green-600">正常</span>'}</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="${user.is_banned === 1 ? 'unban' : 'ban'}">
<button type="submit" class="px-2 py-1 text-xs rounded ${user.is_banned === 1 ? 'bg-green-600' : 'bg-red-600'} text-white">
${user.is_banned === 1 ? '解禁' : '封禁'}
</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>
`;
const ipHtml = `
<div class="text-xs text-gray-600 whitespace-nowrap">${user.last_ip || '-'}</div>
<div class="text-xs text-gray-400 whitespace-nowrap">${user.last_location || '-'}</div>
`;
const 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>
`;
return `
<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">${formatBeijingTime(user.auth_end_time)}</td>
<td class="px-4 py-3">${authOpHtml}</td>
</tr>
`;
}).join('');
} else {
userTableHtml = '<tr><td colspan="8" class="px-4 py-3 text-center text-gray-500">暂无用户</td></tr>';
}
return `
<!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-tab', event)">
<i class="fa fa-user-circle"></i><span>基础配置</span>
</div>
<div class="menu-item" onclick="switchTab('proxy-tab', event)">
<i class="fa fa-cloud"></i><span>反代配置</span>
</div>
<div class="menu-item" onclick="switchTab('user-manage-tab', 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 || '/admin'}" 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 || '/user'}" 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="user_system_enabled" id="user_system_enabled" ${config.user_system_enabled === 1 ? 'checked' : ''}>
<label for="user_system_enabled" class="form-label mb-0">启用用户注册/登录访问控制(关闭后访问反代无需注册登录)</label>
</div>
<div class="switch-group">
<input type="checkbox" name="auth_enabled" id="auth_enabled" ${config.auth_enabled === 1 ? 'checked' : ''}>
<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">Workers中转域名</label>
<input type="text" name="worker_domain" class="form-input" value="${config.worker_domain || ''}" placeholder="例如:your-worker.workers.dev">
</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 || 0}"></div>
<div><label class="form-label">HTTPS端口</label><input type="number" name="https_port" class="form-input" value="${config.https_port || 0}"></div>
</div>
<div class="form-group">
<label class="form-label">反代模式</label>
<select name="proxy_mode" class="form-select">
<option value="http_only" ${config.proxy_mode === 'http_only' ? 'selected' : ''}>仅HTTP</option>
<option value="https_only" ${config.proxy_mode === 'https_only' ? 'selected' : ''}>仅HTTPS</option>
<option value="auto" ${config.proxy_mode === 'auto' ? 'selected' : ''}>HTTP+HTTPS自适应</option>
</select>
</div>
<div class="switch-group">
<input type="checkbox" name="cache_enabled" id="cache_enabled" ${config.cache_enabled === 1 ? 'checked' : ''}>
<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 || 3600}">
</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) {
const tabId = window.location.hash.substring(1);
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(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).classList.add('active');
if(event && event.currentTarget) event.currentTarget.classList.add('active');
window.history.replaceState(null, null, '#' + tabId.replace('-tab', ''));
}
</script>
</body>
</html>
`;
}
function getUserRegisterHtml() {
return `
<!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>
`;
}
function getUserLoginHtml(errorMsg = '') {
return `
<!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>
${errorMsg ? `<div class="alert"><i class="fa fa-exclamation-circle"></i> ${errorMsg}</div>` : ''}
<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>
`;
}
function getUserFindPwdHtml(msg = '') {
return `
<!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>
${msg ? `<div class="msg-box"><i class="fa fa-info-circle"></i> ${msg}</div>` : ''}
<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>
`;
}
function getAccessDeniedHtml(msg) {
return `
<!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>
`;
}
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END








暂无评论内容