步骤 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 域名 → 自动执行反代逻辑(按后台配置)。
D1数据库
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 代码
let dbMigrated = false;
export default {
async fetch(request, env) {
if (!dbMigrated) {
try {
await env.PROXY_DB.prepare('ALTER TABLE proxy_users ADD COLUMN last_ip TEXT DEFAULT ""').run();
await env.PROXY_DB.prepare('ALTER TABLE proxy_users ADD COLUMN last_location TEXT DEFAULT ""').run();
await env.PROXY_DB.prepare('ALTER TABLE proxy_users ADD COLUMN session_id TEXT DEFAULT ""').run();
await env.PROXY_DB.prepare('ALTER TABLE proxy_users ADD COLUMN login_expire_time TEXT DEFAULT ""').run();
} catch(e) {}
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 };
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', 'Cache-Control': 'no-cache' } });
}
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 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 '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' } });
}
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>
<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="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








暂无评论内容