步骤 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>
`;
}
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END








暂无评论内容