上传文件至「/」
This commit is contained in:
+962
@@ -0,0 +1,962 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'config.php';
|
||||||
|
require_once 'vendor/autoload.php'; // PHPMailer
|
||||||
|
|
||||||
|
use PHPMailer\PHPMailer\PHPMailer;
|
||||||
|
use PHPMailer\PHPMailer\Exception;
|
||||||
|
|
||||||
|
// ===== 用户相关函数 =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户注册
|
||||||
|
*/
|
||||||
|
function registerUser($username, $email, $password, $inviteCode = null) {
|
||||||
|
$pdo = getDB();
|
||||||
|
|
||||||
|
// 检查用户名是否已存在
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM users WHERE username = ?");
|
||||||
|
$stmt->execute([$username]);
|
||||||
|
if ($stmt->fetch()) {
|
||||||
|
return ['success' => false, 'message' => '用户名已被使用'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查邮箱是否已存在
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM users WHERE email = ?");
|
||||||
|
$stmt->execute([$email]);
|
||||||
|
if ($stmt->fetch()) {
|
||||||
|
return ['success' => false, 'message' => '邮箱已被注册'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
try {
|
||||||
|
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
$verificationToken = bin2hex(random_bytes(32));
|
||||||
|
$inviteCodeGen = strtoupper(substr(bin2hex(random_bytes(6)), 0, 8));
|
||||||
|
|
||||||
|
// 处理邀请
|
||||||
|
$invitedBy = null;
|
||||||
|
if ($inviteCode) {
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM users WHERE invite_code = ?");
|
||||||
|
$stmt->execute([$inviteCode]);
|
||||||
|
$inviter = $stmt->fetch();
|
||||||
|
if ($inviter) {
|
||||||
|
$invitedBy = $inviter['id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建用户
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO users (username, email, password, invite_code, invited_by, verification_token) VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$username, $email, $hashedPassword, $inviteCodeGen, $invitedBy, $verificationToken]);
|
||||||
|
$userId = $pdo->lastInsertId();
|
||||||
|
|
||||||
|
// 如果是邀请注册,给邀请人加积分
|
||||||
|
if ($invitedBy) {
|
||||||
|
// 增加邀请人积分
|
||||||
|
$stmt = $pdo->prepare("UPDATE users SET credits = credits + ? WHERE id = ?");
|
||||||
|
$stmt->execute([INVITE_CREDIT, $invitedBy]);
|
||||||
|
|
||||||
|
// 记录邀请日志
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO invite_logs (inviter_id, invitee_id, credits_earned) VALUES (?, ?, ?)");
|
||||||
|
$stmt->execute([$invitedBy, $userId, INVITE_CREDIT]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
// 发送验证邮件
|
||||||
|
sendVerificationEmail($email, $username, $verificationToken);
|
||||||
|
|
||||||
|
return ['success' => true, 'message' => '注册成功!请查收验证邮件'];
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
return ['success' => false, 'message' => '注册失败: ' . $e->getMessage()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户登录
|
||||||
|
*/
|
||||||
|
function loginUser($username, $password) {
|
||||||
|
$pdo = getDB();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? OR email = ?");
|
||||||
|
$stmt->execute([$username, $username]);
|
||||||
|
$user = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
return ['success' => false, 'message' => '用户不存在'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$user['is_verified']) {
|
||||||
|
return ['success' => false, 'message' => '请先验证邮箱'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password_verify($password, $user['password'])) {
|
||||||
|
return ['success' => false, 'message' => '密码错误'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION['user_id'] = $user['id'];
|
||||||
|
$_SESSION['username'] = $user['username'];
|
||||||
|
$_SESSION['is_admin'] = $user['is_admin'];
|
||||||
|
|
||||||
|
return ['success' => true, 'message' => '登录成功'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送验证邮件(修复乱码问题)
|
||||||
|
*/
|
||||||
|
function sendVerificationEmail($email, $username, $token) {
|
||||||
|
$mail = new PHPMailer(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$mail->isSMTP();
|
||||||
|
$mail->Host = SMTP_HOST;
|
||||||
|
$mail->SMTPAuth = true;
|
||||||
|
$mail->Username = SMTP_USERNAME;
|
||||||
|
$mail->Password = SMTP_PASSWORD;
|
||||||
|
$mail->SMTPSecure = SMTP_SECURE;
|
||||||
|
$mail->Port = SMTP_PORT;
|
||||||
|
|
||||||
|
// 设置字符编码 - 解决乱码问题
|
||||||
|
$mail->CharSet = 'UTF-8';
|
||||||
|
$mail->Encoding = 'base64';
|
||||||
|
|
||||||
|
$mail->setFrom(SMTP_FROM_EMAIL, SMTP_FROM_NAME);
|
||||||
|
$mail->addAddress($email, $username);
|
||||||
|
$mail->isHTML(true);
|
||||||
|
|
||||||
|
$verifyLink = SITE_URL . "/verify.php?token=" . $token;
|
||||||
|
$mail->Subject = '验证邮箱 - ' . SITE_NAME;
|
||||||
|
|
||||||
|
// 邮件正文 - 使用完整的HTML模板
|
||||||
|
$mail->Body = '
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>邮箱验证</title>
|
||||||
|
<style>
|
||||||
|
@import url("https://cdn.jsdmirror.com/gh/willow-god/Sharding-fonts/Yozai-Medium/result.min.css");
|
||||||
|
body { font-family: "Yozai Medium"; background: #f6f8fa; padding: 40px 20px; }
|
||||||
|
.container { max-width: 560px; margin: 0 auto; background: #ffffff; border-radius: 12px; padding: 40px; box-shadow: 0 8px 32px rgba(0,0,0,0.08); }
|
||||||
|
.header { text-align: center; margin-bottom: 30px; }
|
||||||
|
.header h1 { color: #24292f; font-size: 24px; margin: 0; }
|
||||||
|
.header .sub { color: #57606a; font-size: 14px; }
|
||||||
|
.content { color: #24292f; line-height: 1.8; }
|
||||||
|
.btn { display: block; padding: 12px 32px; background: #0969da; color: #ffffff !important; text-decoration: none; border-radius: 6px; font-weight: 600; margin: 20px 0; }
|
||||||
|
.btn:hover { background: #0550ae; }
|
||||||
|
.footer { margin-top: 30px; padding-top: 20px; border-top: 1px solid #d0d7de; font-size: 12px; color: #57606a; text-align: center; }
|
||||||
|
.link-box { background: #f6f8fa; padding: 12px 16px; border-radius: 6px; word-break: break-all; font-size: 13px; color: #57606a; border: 1px solid #d0d7de; }
|
||||||
|
.highlight { color: #0969da; font-weight: 600; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>📧 ' . SITE_NAME . '</h1>
|
||||||
|
<p class="sub">验证您的邮箱地址</p>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<p>您好,<strong>' . htmlspecialchars($username) . '</strong>:</p>
|
||||||
|
<p>感谢您注册 <strong>' . SITE_NAME . '</strong>!请点击下方按钮验证您的邮箱地址:</p>
|
||||||
|
<p style="text-align: center;">
|
||||||
|
<a href="' . $verifyLink . '" class="btn">✅ 验证邮箱</a>
|
||||||
|
</p>
|
||||||
|
<p style="color: #57606a; font-size: 14px;">如果按钮无法点击,请复制以下链接到浏览器打开:</p>
|
||||||
|
<div class="link-box">' . $verifyLink . '</div>
|
||||||
|
<p style="margin-top: 20px; font-size: 14px; color: #57606a;">
|
||||||
|
⏰ 此链接 <span class="highlight">24 小时</span> 内有效。<br>
|
||||||
|
🔒 如果您没有注册 ' . SITE_NAME . ',请忽略并删除此邮件。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<p>' . SITE_NAME . ' · 安全可靠 · 免费二级域名分发</p>
|
||||||
|
<p style="margin-top: 4px;">© ' . date('Y') . ' ' . SITE_NAME . ' · 由 ❤️ 驱动</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
';
|
||||||
|
|
||||||
|
$mail->AltBody = "欢迎注册 " . SITE_NAME . "!\n\n" .
|
||||||
|
"您好," . $username . ":\n\n" .
|
||||||
|
"请访问以下链接验证您的邮箱地址:\n" .
|
||||||
|
$verifyLink . "\n\n" .
|
||||||
|
"此链接 24 小时内有效。\n\n" .
|
||||||
|
"如果您没有注册 " . SITE_NAME . ",请忽略并删除此邮件。";
|
||||||
|
|
||||||
|
$mail->send();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("邮件发送失败: " . $e->getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证邮箱
|
||||||
|
*/
|
||||||
|
function verifyEmail($token) {
|
||||||
|
$pdo = getDB();
|
||||||
|
$stmt = $pdo->prepare("SELECT id, username, email FROM users WHERE verification_token = ? AND is_verified = 0");
|
||||||
|
$stmt->execute([$token]);
|
||||||
|
$user = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
// 检查token是否已被使用(已验证)
|
||||||
|
$stmt = $pdo->prepare("SELECT id, username FROM users WHERE verification_token = ? AND is_verified = 1");
|
||||||
|
$stmt->execute([$token]);
|
||||||
|
if ($stmt->fetch()) {
|
||||||
|
return ['success' => false, 'message' => '此链接已被使用,请直接登录。'];
|
||||||
|
}
|
||||||
|
return ['success' => false, 'message' => '无效的验证链接,请检查是否正确复制。'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新用户状态
|
||||||
|
$stmt = $pdo->prepare("UPDATE users SET is_verified = 1, verification_token = NULL WHERE id = ?");
|
||||||
|
$stmt->execute([$user['id']]);
|
||||||
|
|
||||||
|
return ['success' => true, 'message' => '邮箱验证成功!'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 积分相关函数 =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户积分
|
||||||
|
*/
|
||||||
|
function getUserCredits($userId) {
|
||||||
|
$pdo = getDB();
|
||||||
|
$stmt = $pdo->prepare("SELECT credits FROM users WHERE id = ?");
|
||||||
|
$stmt->execute([$userId]);
|
||||||
|
$result = $stmt->fetch();
|
||||||
|
return $result ? $result['credits'] : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扣减积分
|
||||||
|
*/
|
||||||
|
function deductCredits($userId, $amount) {
|
||||||
|
$pdo = getDB();
|
||||||
|
|
||||||
|
// 检查积分是否足够
|
||||||
|
$stmt = $pdo->prepare("SELECT credits FROM users WHERE id = ?");
|
||||||
|
$stmt->execute([$userId]);
|
||||||
|
$user = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$user || $user['credits'] < $amount) {
|
||||||
|
return ['success' => false, 'message' => '剩余的积分不足。'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("UPDATE users SET credits = credits - ? WHERE id = ?");
|
||||||
|
$stmt->execute([$amount, $userId]);
|
||||||
|
|
||||||
|
return ['success' => true, 'message' => '扣减成功!'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加积分
|
||||||
|
*/
|
||||||
|
function addCredits($userId, $amount, $reason = '') {
|
||||||
|
$pdo = getDB();
|
||||||
|
$stmt = $pdo->prepare("UPDATE users SET credits = credits + ? WHERE id = ?");
|
||||||
|
$stmt->execute([$amount, $userId]);
|
||||||
|
return ['success' => true];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 签到相关函数 =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签到
|
||||||
|
*/
|
||||||
|
function signIn($userId) {
|
||||||
|
$pdo = getDB();
|
||||||
|
$today = date('Y-m-d');
|
||||||
|
|
||||||
|
// 检查今天是否已签到
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM sign_in_logs WHERE user_id = ? AND sign_in_date = ?");
|
||||||
|
$stmt->execute([$userId, $today]);
|
||||||
|
if ($stmt->fetch()) {
|
||||||
|
return ['success' => false, 'message' => '今日已经签到过了。'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
try {
|
||||||
|
// 记录签到
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO sign_in_logs (user_id, sign_in_date, credits_earned) VALUES (?, ?, ?)");
|
||||||
|
$stmt->execute([$userId, $today, SIGN_IN_CREDIT]);
|
||||||
|
|
||||||
|
// 增加积分
|
||||||
|
$stmt = $pdo->prepare("UPDATE users SET credits = credits + ? WHERE id = ?");
|
||||||
|
$stmt->execute([SIGN_IN_CREDIT, $userId]);
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
return ['success' => true, 'message' => '签到成功!获得 ' . SIGN_IN_CREDIT . ' 积分', 'credits' => SIGN_IN_CREDIT];
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
return ['success' => false, 'message' => '签到失败: ' . $e->getMessage()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查今天是否已签到
|
||||||
|
*/
|
||||||
|
function hasSignedInToday($userId) {
|
||||||
|
$pdo = getDB();
|
||||||
|
$today = date('Y-m-d');
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM sign_in_logs WHERE user_id = ? AND sign_in_date = ?");
|
||||||
|
$stmt->execute([$userId, $today]);
|
||||||
|
return (bool)$stmt->fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取签到统计
|
||||||
|
*/
|
||||||
|
function getSignInStats($userId) {
|
||||||
|
$pdo = getDB();
|
||||||
|
$stmt = $pdo->prepare("SELECT COUNT(*) as total, SUM(credits_earned) as total_credits FROM sign_in_logs WHERE user_id = ?");
|
||||||
|
$stmt->execute([$userId]);
|
||||||
|
return $stmt->fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 域名相关函数 =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户域名数量
|
||||||
|
*/
|
||||||
|
function getUserDomainCount($userId) {
|
||||||
|
$pdo = getDB();
|
||||||
|
$stmt = $pdo->prepare("SELECT COUNT(*) as count FROM domains WHERE user_id = ? AND status = 1");
|
||||||
|
$stmt->execute([$userId]);
|
||||||
|
$result = $stmt->fetch();
|
||||||
|
return $result ? $result['count'] : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查用户是否可以添加域名
|
||||||
|
*/
|
||||||
|
function canAddDomain($userId) {
|
||||||
|
$count = getUserDomainCount($userId);
|
||||||
|
if ($count >= MAX_DOMAINS_PER_USER) {
|
||||||
|
return ['success' => false, 'message' => '您的域名数量已达到最大域名数量限制 (' . MAX_DOMAINS_PER_USER . '个)。'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$credits = getUserCredits($userId);
|
||||||
|
if ($credits < CREDIT_PER_DOMAIN) {
|
||||||
|
return ['success' => false, 'message' => '积分不足,需要 ' . CREDIT_PER_DOMAIN . ' 积分'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['success' => true];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证子域名是否合法
|
||||||
|
*/
|
||||||
|
function validateSubdomain($subdomain) {
|
||||||
|
// 不能为空
|
||||||
|
if (empty($subdomain)) {
|
||||||
|
return ['success' => false, 'message' => '子域名不能为空。'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 长度至少3个字符
|
||||||
|
if (strlen($subdomain) < 3) {
|
||||||
|
return ['success' => false, 'message' => '子域名长度至少 3 个字符。'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不能超过 20 个字符
|
||||||
|
if (strlen($subdomain) > 20) {
|
||||||
|
return ['success' => false, 'message' => '子域名长度不能超过 20 个字符。'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只能包含小写字母、数字和连字符
|
||||||
|
if (!preg_match('/^[a-z0-9\-]+$/', $subdomain)) {
|
||||||
|
return ['success' => false, 'message' => '子域名只能包含小写字母、数字和连字符。'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不能以连字符开头或结尾
|
||||||
|
if (preg_match('/^-|-$/', $subdomain)) {
|
||||||
|
return ['success' => false, 'message' => '子域名不能以连字符开头或结尾。'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不能包含连续的连字符
|
||||||
|
if (strpos($subdomain, '--') !== false) {
|
||||||
|
return ['success' => false, 'message' => '子域名不能包含连续的连字符。'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不能是保留关键字
|
||||||
|
$reserved = ['www', 'mail', 'ftp', 'smtp', 'pop3', 'imap', 'admin', 'root', 'test', 'demo', 'api', 'dev', 'prod', 'stage', 'local', 'git', 'store', 'assets', 'image', 'bbs', 'help', 'forum'];
|
||||||
|
if (in_array(strtolower($subdomain), $reserved)) {
|
||||||
|
return ['success' => false, 'message' => '该子域名前缀已被禁用,请使用其他前缀。'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['success' => true];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证记录值格式
|
||||||
|
*/
|
||||||
|
function validateRecordValue($type, $value) {
|
||||||
|
$type = strtoupper($type);
|
||||||
|
|
||||||
|
switch ($type) {
|
||||||
|
case 'A':
|
||||||
|
if (!filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||||
|
return ['success' => false, 'message' => 'A 记录需要有效的 IPv4 地址。'];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'AAAA':
|
||||||
|
if (!filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||||
|
return ['success' => false, 'message' => 'AAAA 记录需要有效的 IPv6 地址。'];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'CNAME':
|
||||||
|
if (!preg_match('/^([a-z0-9\-]+\.)+[a-z]{2,}$/i', $value)) {
|
||||||
|
return ['success' => false, 'message' => 'CNAME 记录需要有效的域名。'];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'MX':
|
||||||
|
if (!preg_match('/^\d+\s+([a-z0-9\-]+\.)+[a-z]{2,}$/i', $value)) {
|
||||||
|
return ['success' => false, 'message' => 'MX 记录格式: 优先级 域名 (10 mail.example.com)。'];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'NS':
|
||||||
|
if (!preg_match('/^([a-z0-9\-]+\.)+[a-z]{2,}$/i', $value)) {
|
||||||
|
return ['success' => false, 'message' => 'NS 记录需要有效的域名。'];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'TXT':
|
||||||
|
if (strlen($value) > 255) {
|
||||||
|
return ['success' => false, 'message' => 'TXT 记录值不能超过 255 个字符。'];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'SRV':
|
||||||
|
// SRV记录格式: 优先级 权重 端口 目标域名
|
||||||
|
$parts = preg_split('/\s+/', trim($value));
|
||||||
|
if (count($parts) != 4) {
|
||||||
|
return ['success' => false, 'message' => 'SRV 记录格式: 优先级 权重 端口 目标域名 (10 5 5060 sip.example.com)。'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$priority = intval($parts[0]);
|
||||||
|
$weight = intval($parts[1]);
|
||||||
|
$port = intval($parts[2]);
|
||||||
|
$target = strtolower(trim($parts[3]));
|
||||||
|
|
||||||
|
if ($priority < 0 || $priority > 65535) {
|
||||||
|
return ['success' => false, 'message' => '优先级必须是 0-65535 之间的数字。'];
|
||||||
|
}
|
||||||
|
if ($weight < 0 || $weight > 65535) {
|
||||||
|
return ['success' => false, 'message' => '权重必须是 0-65535 之间的数字。'];
|
||||||
|
}
|
||||||
|
if ($port < 0 || $port > 65535) {
|
||||||
|
return ['success' => false, 'message' => '端口必须是 0-65535 之间的数字。'];
|
||||||
|
}
|
||||||
|
if ($target !== '.' && !preg_match('/^([a-z0-9\-]+\.)+[a-z]{2,}$/', $target)) {
|
||||||
|
return ['success' => false, 'message' => '无效的目标域名,请输入有效的域名或用 . 表示空。'];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ['success' => false, 'message' => '不支持的记录类型。'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['success' => true];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取记录类型列表
|
||||||
|
*/
|
||||||
|
function getRecordTypes() {
|
||||||
|
return [
|
||||||
|
'A' => 'IPv4 地址',
|
||||||
|
'AAAA' => 'IPv6 地址',
|
||||||
|
'CNAME' => '域名别名',
|
||||||
|
'MX' => '邮件交换 (优先级 域名)',
|
||||||
|
'NS' => '域名服务器',
|
||||||
|
'TXT' => '文本记录',
|
||||||
|
'SRV' => '服务定位 (优先级 权重 端口 目标)'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取记录类型的示例值
|
||||||
|
*/
|
||||||
|
function getRecordTypeExample($type) {
|
||||||
|
$examples = [
|
||||||
|
'A' => '192.168.1.1',
|
||||||
|
'AAAA' => '2001:db8::1',
|
||||||
|
'CNAME' => 'example.com',
|
||||||
|
'MX' => '10 mail.example.com',
|
||||||
|
'NS' => 'ns1.example.com',
|
||||||
|
'TXT' => 'v=spf1 include:example.com ~all',
|
||||||
|
'SRV' => '10 5 5060 sip.example.com'
|
||||||
|
];
|
||||||
|
return $examples[$type] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取记录类型的格式说明
|
||||||
|
*/
|
||||||
|
function getRecordTypeHint($type) {
|
||||||
|
$hints = [
|
||||||
|
'A' => 'IPv4 地址,例如: 192.168.1.1',
|
||||||
|
'AAAA' => 'IPv6 地址,例如: 2001:db8::1',
|
||||||
|
'CNAME' => '目标域名,例如: example.com',
|
||||||
|
'MX' => '格式: 优先级 域名,例如: 10 mail.example.com',
|
||||||
|
'NS' => '域名服务器,例如: ns1.example.com',
|
||||||
|
'TXT' => '任意文本,例如: v=spf1 include:example.com ~all',
|
||||||
|
'SRV' => '格式: 优先级 权重 端口 目标域名,例如: 10 5 5060 sip.example.com'
|
||||||
|
];
|
||||||
|
return $hints[$type] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加域名(扣除积分)
|
||||||
|
*/
|
||||||
|
function addDomain($userId, $mainDomain, $subdomain, $type, $value) {
|
||||||
|
// 检查权限
|
||||||
|
$canAdd = canAddDomain($userId);
|
||||||
|
if (!$canAdd['success']) {
|
||||||
|
return $canAdd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证子域名
|
||||||
|
$subdomainValidation = validateSubdomain($subdomain);
|
||||||
|
if (!$subdomainValidation['success']) {
|
||||||
|
return $subdomainValidation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证记录值
|
||||||
|
$valueValidation = validateRecordValue($type, $value);
|
||||||
|
if (!$valueValidation['success']) {
|
||||||
|
return $valueValidation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查主域名是否支持
|
||||||
|
$mainDomains = getMainDomains();
|
||||||
|
if (!in_array($mainDomain, $mainDomains)) {
|
||||||
|
return ['success' => false, 'message' => '不支持的主域名。'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo = getDB();
|
||||||
|
|
||||||
|
// 检查数据库中是否已存在
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM domains WHERE main_domain = ? AND subdomain = ? AND status = 1");
|
||||||
|
$stmt->execute([$mainDomain, $subdomain]);
|
||||||
|
if ($stmt->fetch()) {
|
||||||
|
return ['success' => false, 'message' => '该域名已存在。'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 先尝试添加到 Cloudflare
|
||||||
|
$cfResult = addCloudflareRecord($mainDomain, $subdomain, $type, $value);
|
||||||
|
if (!$cfResult['success']) {
|
||||||
|
return ['success' => false, 'message' => '添加失败: ' . $cfResult['message']];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扣减积分
|
||||||
|
$stmt = $pdo->prepare("UPDATE users SET credits = credits - ? WHERE id = ? AND credits >= ?");
|
||||||
|
$stmt->execute([CREDIT_PER_DOMAIN, $userId, CREDIT_PER_DOMAIN]);
|
||||||
|
if ($stmt->rowCount() === 0) {
|
||||||
|
throw new Exception('积分不足');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加域名到数据库
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO domains (user_id, main_domain, subdomain, record_type, record_value) VALUES (?, ?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$userId, $mainDomain, $subdomain, $type, $value]);
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
return ['success' => true, 'message' => '域名添加成功!消耗 ' . CREDIT_PER_DOMAIN . ' 积分'];
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
// 如果数据库失败,尝试删除 Cloudflare 记录
|
||||||
|
deleteCloudflareRecord($mainDomain, $subdomain);
|
||||||
|
return ['success' => false, 'message' => '添加失败: ' . $e->getMessage()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户的域名列表
|
||||||
|
*/
|
||||||
|
function getUserDomains($userId) {
|
||||||
|
$pdo = getDB();
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM domains WHERE user_id = ? AND status = 1 ORDER BY created_at DESC");
|
||||||
|
$stmt->execute([$userId]);
|
||||||
|
return $stmt->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除域名
|
||||||
|
*/
|
||||||
|
function deleteDomain($userId, $domainId) {
|
||||||
|
$pdo = getDB();
|
||||||
|
|
||||||
|
// 检查域名是否属于该用户
|
||||||
|
$stmt = $pdo->prepare("SELECT main_domain, subdomain FROM domains WHERE id = ? AND user_id = ? AND status = 1");
|
||||||
|
$stmt->execute([$domainId, $userId]);
|
||||||
|
$domain = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$domain) {
|
||||||
|
return ['success' => false, 'message' => '该域名不存在。'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 Cloudflare 删除
|
||||||
|
deleteCloudflareRecord($domain['main_domain'], $domain['subdomain']);
|
||||||
|
|
||||||
|
// 软删除
|
||||||
|
$stmt = $pdo->prepare("UPDATE domains SET status = 0 WHERE id = ?");
|
||||||
|
$stmt->execute([$domainId]);
|
||||||
|
|
||||||
|
return ['success' => true, 'message' => '域名删除成功。'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Cloudflare API函数 =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取域名配置
|
||||||
|
*/
|
||||||
|
function getDomainConfig($mainDomain) {
|
||||||
|
global $DOMAINS_CONFIG;
|
||||||
|
return $DOMAINS_CONFIG[$mainDomain] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加Cloudflare记录(最终修复SRV - 使用正确的字段名)
|
||||||
|
*/
|
||||||
|
function addCloudflareRecord($mainDomain, $subdomain, $type, $value) {
|
||||||
|
$config = getDomainConfig($mainDomain);
|
||||||
|
if (!$config) {
|
||||||
|
error_log("Cloudflare 错误: 未找到域名配置 - {$mainDomain}");
|
||||||
|
return ['success' => false, 'message' => '未找到域名配置'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$fullDomain = strtolower($subdomain) . '.' . strtolower($mainDomain);
|
||||||
|
$type = strtoupper($type);
|
||||||
|
|
||||||
|
// 检查记录是否已存在
|
||||||
|
$ch = curl_init('https://api.cloudflare.com/client/v4/zones/' . $config['zone_id'] . '/dns_records?name=' . urlencode($fullDomain) . '&type=' . $type);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
'Content-Type: application/json',
|
||||||
|
'Authorization: Bearer ' . $config['api_token']
|
||||||
|
]);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($httpCode === 200) {
|
||||||
|
$result = json_decode($response, true);
|
||||||
|
if ($result && $result['success'] && !empty($result['result'])) {
|
||||||
|
error_log("Cloudflare 记录已存在 - {$fullDomain}");
|
||||||
|
return ['success' => false, 'message' => '该记录已存在。'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求数据 - 基础字段
|
||||||
|
$data = [
|
||||||
|
'type' => $type,
|
||||||
|
'name' => $fullDomain,
|
||||||
|
'ttl' => 300,
|
||||||
|
'proxied' => false // 必须明确设置为 false
|
||||||
|
];
|
||||||
|
|
||||||
|
// 根据记录类型处理值
|
||||||
|
switch ($type) {
|
||||||
|
case 'A':
|
||||||
|
if (!filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||||
|
return ['success' => false, 'message' => '无效的 IPv4 地址。'];
|
||||||
|
}
|
||||||
|
$data['content'] = $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'AAAA':
|
||||||
|
if (!filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||||
|
return ['success' => false, 'message' => '无效的 IPv6 地址。'];
|
||||||
|
}
|
||||||
|
$data['content'] = $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'CNAME':
|
||||||
|
$value = strtolower(trim($value));
|
||||||
|
if (!preg_match('/^([a-z0-9\-]+\.)+[a-z]{2,}$/', $value)) {
|
||||||
|
return ['success' => false, 'message' => '无效的域名格式。'];
|
||||||
|
}
|
||||||
|
$data['content'] = $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'MX':
|
||||||
|
$parts = preg_split('/\s+/', trim($value));
|
||||||
|
if (count($parts) != 2) {
|
||||||
|
return ['success' => false, 'message' => 'MX 记录格式: 优先级 域名 (例如: 10 mail.example.com)。'];
|
||||||
|
}
|
||||||
|
if (!is_numeric($parts[0]) || intval($parts[0]) < 0 || intval($parts[0]) > 65535) {
|
||||||
|
return ['success' => false, 'message' => '优先级必须是 0-65535 之间的数字。'];
|
||||||
|
}
|
||||||
|
$domain = strtolower(trim($parts[1]));
|
||||||
|
if (!preg_match('/^([a-z0-9\-]+\.)+[a-z]{2,}$/', $domain)) {
|
||||||
|
return ['success' => false, 'message' => '无效的邮件服务器域名。'];
|
||||||
|
}
|
||||||
|
$data['priority'] = intval($parts[0]);
|
||||||
|
$data['content'] = $domain;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'NS':
|
||||||
|
$value = strtolower(trim($value));
|
||||||
|
if (!preg_match('/^([a-z0-9\-]+\.)+[a-z]{2,}$/', $value)) {
|
||||||
|
return ['success' => false, 'message' => '无效的域名服务器地址。'];
|
||||||
|
}
|
||||||
|
$data['content'] = $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'TXT':
|
||||||
|
if (strlen($value) > 255) {
|
||||||
|
return ['success' => false, 'message' => 'TXT 记录值不能超过 255 字符。'];
|
||||||
|
}
|
||||||
|
$data['content'] = $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'SRV':
|
||||||
|
// ===== SRV记录特殊处理 =====
|
||||||
|
$parts = preg_split('/\s+/', trim($value));
|
||||||
|
if (count($parts) != 4) {
|
||||||
|
return ['success' => false, 'message' => 'SRV 记录格式: 优先级 权重 端口 目标域名 (例如: 10 5 5060 sip.example.com)'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$priority = intval($parts[0]);
|
||||||
|
$weight = intval($parts[1]);
|
||||||
|
$port = intval($parts[2]);
|
||||||
|
$target = strtolower(trim($parts[3]));
|
||||||
|
|
||||||
|
// 验证数值范围
|
||||||
|
if ($priority < 0 || $priority > 65535) {
|
||||||
|
return ['success' => false, 'message' => '优先级必须是 0-65535 之间的数字。'];
|
||||||
|
}
|
||||||
|
if ($weight < 0 || $weight > 65535) {
|
||||||
|
return ['success' => false, 'message' => '权重必须是 0-65535 之间的数字。'];
|
||||||
|
}
|
||||||
|
if ($port < 0 || $port > 65535) {
|
||||||
|
return ['success' => false, 'message' => '端口必须是 0-65535 之间的数字。'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证目标域名
|
||||||
|
if ($target !== '.' && !preg_match('/^([a-z0-9\-]+\.)+[a-z]{2,}$/', $target)) {
|
||||||
|
return ['success' => false, 'message' => '无效的目标域名,请输入有效的域名或用 . 表示空。'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 重要:SRV 记录使用不同的字段名 =====
|
||||||
|
// Cloudflare SRV API 要求使用这些字段名:
|
||||||
|
// - priority, weight, port, target (不是 content!)
|
||||||
|
$data['priority'] = (int)$priority;
|
||||||
|
$data['weight'] = (int)$weight;
|
||||||
|
$data['port'] = (int)$port;
|
||||||
|
$data['target'] = (string)$target; // 关键:使用 target 而不是 content
|
||||||
|
$data['proxied'] = false;
|
||||||
|
|
||||||
|
error_log("SRV 数据: priority={$priority}, weight={$weight}, port={$port}, target={$target}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ['success' => false, 'message' => '不支持的记录类型'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录请求数据用于调试
|
||||||
|
error_log("Cloudflare 请求数据: " . json_encode($data));
|
||||||
|
|
||||||
|
$ch = curl_init('https://api.cloudflare.com/client/v4/zones/' . $config['zone_id'] . '/dns_records');
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
'Content-Type: application/json',
|
||||||
|
'Authorization: Bearer ' . $config['api_token']
|
||||||
|
]);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$curlError = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
// 记录响应
|
||||||
|
error_log("Cloudflare 响应: HTTP {$httpCode}");
|
||||||
|
error_log("Cloudflare 响应内容: " . $response);
|
||||||
|
|
||||||
|
if ($curlError) {
|
||||||
|
error_log("Cloudflare CURL 错误: " . $curlError);
|
||||||
|
return ['success' => false, 'message' => 'CURL 错误: ' . $curlError];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($httpCode === 200) {
|
||||||
|
$result = json_decode($response, true);
|
||||||
|
if ($result && $result['success']) {
|
||||||
|
error_log("Cloudflare 添加成功: {$fullDomain}");
|
||||||
|
return ['success' => true, 'message' => '添加成功'];
|
||||||
|
} else {
|
||||||
|
$errorMsg = '未知错误';
|
||||||
|
if (isset($result['errors']) && !empty($result['errors'])) {
|
||||||
|
$errorMsg = $result['errors'][0]['message'] ?? 'API错误';
|
||||||
|
if (isset($result['errors'][0]['error_chain'])) {
|
||||||
|
foreach ($result['errors'][0]['error_chain'] as $chain) {
|
||||||
|
if (isset($chain['message'])) {
|
||||||
|
$errorMsg .= ' - ' . $chain['message'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error_log("Cloudflare API 错误: " . $errorMsg);
|
||||||
|
return ['success' => false, 'message' => 'Cloudflare 错误: ' . $errorMsg];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析错误响应
|
||||||
|
$result = json_decode($response, true);
|
||||||
|
if ($result && isset($result['errors'])) {
|
||||||
|
$errorMsg = '';
|
||||||
|
foreach ($result['errors'] as $error) {
|
||||||
|
$errorMsg .= $error['message'] . ' ';
|
||||||
|
if (isset($error['error_chain'])) {
|
||||||
|
foreach ($error['error_chain'] as $chain) {
|
||||||
|
if (isset($chain['message'])) {
|
||||||
|
$errorMsg .= '- ' . $chain['message'] . ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ['success' => false, 'message' => '请求错误: ' . trim($errorMsg)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['success' => false, 'message' => "HTTP错误: {$httpCode} - " . substr($response, 0, 200)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除Cloudflare记录(支持SRV)
|
||||||
|
*/
|
||||||
|
function deleteCloudflareRecord($mainDomain, $subdomain) {
|
||||||
|
$config = getDomainConfig($mainDomain);
|
||||||
|
if (!$config) return false;
|
||||||
|
|
||||||
|
$fullDomain = $subdomain . '.' . $mainDomain;
|
||||||
|
$zoneId = $config['zone_id'];
|
||||||
|
$token = $config['api_token'];
|
||||||
|
|
||||||
|
// 查询记录ID - 不限定类型,因为SRV记录可能用target字段
|
||||||
|
$ch = curl_init('https://api.cloudflare.com/client/v4/zones/' . $zoneId . '/dns_records?name=' . urlencode($fullDomain));
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
'Content-Type: application/json',
|
||||||
|
'Authorization: Bearer ' . $token
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$result = json_decode($response, true);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if (isset($result['result']) && !empty($result['result'])) {
|
||||||
|
$recordId = $result['result'][0]['id'];
|
||||||
|
|
||||||
|
$ch = curl_init('https://api.cloudflare.com/client/v4/zones/' . $zoneId . '/dns_records/' . $recordId);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
'Content-Type: application/json',
|
||||||
|
'Authorization: Bearer ' . $token
|
||||||
|
]);
|
||||||
|
|
||||||
|
curl_exec($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 其他辅助函数 =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否已登录
|
||||||
|
*/
|
||||||
|
function isLoggedIn() {
|
||||||
|
return isset($_SESSION['user_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否是管理员
|
||||||
|
*/
|
||||||
|
function isAdmin() {
|
||||||
|
return isset($_SESSION['is_admin']) && $_SESSION['is_admin'] == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户ID
|
||||||
|
*/
|
||||||
|
function getCurrentUserId() {
|
||||||
|
return $_SESSION['user_id'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户信息
|
||||||
|
*/
|
||||||
|
function getUserInfo($userId) {
|
||||||
|
$pdo = getDB();
|
||||||
|
$stmt = $pdo->prepare("SELECT id, username, email, credits, invite_code, is_admin, created_at FROM users WHERE id = ?");
|
||||||
|
$stmt->execute([$userId]);
|
||||||
|
return $stmt->fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取邀请统计
|
||||||
|
*/
|
||||||
|
function getInviteStats($userId) {
|
||||||
|
$pdo = getDB();
|
||||||
|
$stmt = $pdo->prepare("SELECT COUNT(*) as count, SUM(credits_earned) as total_credits FROM invite_logs WHERE inviter_id = ?");
|
||||||
|
$stmt->execute([$userId]);
|
||||||
|
return $stmt->fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取主题
|
||||||
|
*/
|
||||||
|
function getTheme() {
|
||||||
|
$pdo = getDB();
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("SELECT setting_value FROM settings WHERE setting_key = 'theme'");
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->fetch();
|
||||||
|
return $result ? $result['setting_value'] : 'dark';
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
return 'dark';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换主题
|
||||||
|
*/
|
||||||
|
function toggleTheme() {
|
||||||
|
$pdo = getDB();
|
||||||
|
$current = getTheme();
|
||||||
|
$new = $current === 'dark' ? 'light' : 'dark';
|
||||||
|
$stmt = $pdo->prepare("UPDATE settings SET setting_value = ? WHERE setting_key = 'theme'");
|
||||||
|
$stmt->execute([$new]);
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有主域名列表
|
||||||
|
*/
|
||||||
|
function getMainDomains() {
|
||||||
|
global $DOMAINS_CONFIG;
|
||||||
|
return array_keys($DOMAINS_CONFIG);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'functions.php';
|
||||||
|
if (isLoggedIn()) {
|
||||||
|
header('Location: dashboard.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$pdo = getDB();
|
||||||
|
$totalDomains = $pdo->query("SELECT COUNT(*) as count FROM domains WHERE status = 1")->fetch()['count'];
|
||||||
|
$totalUsers = $pdo->query("SELECT COUNT(*) as count FROM users WHERE is_verified = 1")->fetch()['count'];
|
||||||
|
$stmt = $pdo->prepare("SELECT d.*, u.username FROM domains d LEFT JOIN users u ON d.user_id = u.id WHERE d.status = 1 ORDER BY d.created_at DESC LIMIT 10");
|
||||||
|
$stmt->execute();
|
||||||
|
$recentDomains = $stmt->fetchAll();
|
||||||
|
|
||||||
|
$currentTheme = getTheme();
|
||||||
|
$mainDomains = getMainDomains();
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN" data-theme="<?php echo $currentTheme; ?>">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title><?php echo SITE_NAME; ?> - 用心制作的免费二级域名分发网站</title>
|
||||||
|
<link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.0/css/all.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<div class="header-content">
|
||||||
|
<h1><i class="fas fa-globe"></i> <?php echo SITE_NAME; ?></h1>
|
||||||
|
<div class="header-actions">
|
||||||
|
<form method="POST" style="display: inline;">
|
||||||
|
<input type="hidden" name="action" value="toggle_theme">
|
||||||
|
<button type="submit" class="theme-toggle" title="切换主题">
|
||||||
|
<i class="fas <?php echo $currentTheme === 'dark' ? 'fa-sun' : 'fa-moon'; ?>"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<a href="auth.php?action=login" class="btn btn-primary btn-sm">
|
||||||
|
<i class="fas fa-sign-in-alt"></i>
|
||||||
|
</a>
|
||||||
|
<a href="auth.php?action=register" class="btn btn-sm" style="background:var(--bg-primary);border:1px solid var(--border-color);color:var(--text-primary);">
|
||||||
|
<i class="fas fa-user-plus"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="hero">
|
||||||
|
<h1><i class="fas fa-rocket"></i> 免费二级域名分发</h1>
|
||||||
|
<p>
|
||||||
|
轻松获取免费的二级域名,支持 A、AAAA、CNAME、MX、NS、TXT 记录
|
||||||
|
<br>积分制管理,每日签到免费领取积分
|
||||||
|
</p>
|
||||||
|
<div class="btn-group">
|
||||||
|
<a href="auth.php?action=register" class="btn btn-primary btn-large">
|
||||||
|
<i class="fas fa-user-plus"></i> 立即注册
|
||||||
|
</a>
|
||||||
|
<a href="auth.php?action=login" class="btn btn-outline btn-large">
|
||||||
|
<i class="fas fa-sign-in-alt"></i> 已有账号
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 统计 -->
|
||||||
|
<div class="stats-bar">
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-number"><?php echo $totalDomains; ?></div>
|
||||||
|
<div class="stat-label"><i class="fas fa-globe"></i> 已有域名</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-number"><?php echo $totalUsers; ?></div>
|
||||||
|
<div class="stat-label"><i class="fas fa-users"></i> 注册用户</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-number"><?php echo count($mainDomains); ?></div>
|
||||||
|
<div class="stat-label"><i class="fas fa-server"></i> 支持域名</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 功能特性 -->
|
||||||
|
<div class="features-grid">
|
||||||
|
<div class="feature-card">
|
||||||
|
<i class="fas fa-coins"></i>
|
||||||
|
<h3>积分制管理</h3>
|
||||||
|
<p>注册送 10 积分,每日签到得 2 积分,邀请好友得 10 积分</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature-card">
|
||||||
|
<i class="fas fa-book"></i>
|
||||||
|
<h3>完整 DNS 记录</h3>
|
||||||
|
<p>支持 A、AAAA、CNAME、MX、NS、TXT 全部记录类型</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature-card">
|
||||||
|
<i class="fas fa-cloud-upload-alt"></i>
|
||||||
|
<h3>Cloudflare 驱动</h3>
|
||||||
|
<p>域名自动托管到 Cloudflare,全球节点加速,稳定可靠</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature-card">
|
||||||
|
<i class="fas fa-users"></i>
|
||||||
|
<h3>邀请奖励</h3>
|
||||||
|
<p>邀请好友注册,双方都获得积分奖励,共同成长</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer style="text-align:center;padding:30px 0 20px;color:var(--text-muted);font-size:13px;border-top:1px solid var(--border-color);margin-top:30px;">
|
||||||
|
<p>
|
||||||
|
© <?php echo date('Y'); ?> <?php echo SITE_NAME; ?>
|
||||||
|
· 免费二级域名分发系统
|
||||||
|
· 由 <i class="fas fa-heart" style="color:var(--accent-red);"></i> 驱动
|
||||||
|
</p>
|
||||||
|
<p style="margin-top:6px;font-size:12px;">
|
||||||
|
<i class="fas fa-shield-alt"></i> 所有数据安全加密
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+98
@@ -0,0 +1,98 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'config.php';
|
||||||
|
|
||||||
|
// 创建数据表
|
||||||
|
try {
|
||||||
|
$pdo = new PDO("mysql:host=" . DB_HOST . ";charset=utf8mb4", DB_USER, DB_PASS);
|
||||||
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
|
||||||
|
// 创建数据库
|
||||||
|
$pdo->exec("CREATE DATABASE IF NOT EXISTS " . DB_NAME);
|
||||||
|
$pdo->exec("USE " . DB_NAME);
|
||||||
|
|
||||||
|
// 用户表
|
||||||
|
$pdo->exec("CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
username VARCHAR(50) UNIQUE NOT NULL,
|
||||||
|
email VARCHAR(100) UNIQUE NOT NULL,
|
||||||
|
password VARCHAR(255) NOT NULL,
|
||||||
|
credits INT DEFAULT 10,
|
||||||
|
invite_code VARCHAR(20) UNIQUE NOT NULL,
|
||||||
|
invited_by INT DEFAULT NULL,
|
||||||
|
is_admin TINYINT DEFAULT 0,
|
||||||
|
is_verified TINYINT DEFAULT 0,
|
||||||
|
verification_token VARCHAR(64),
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
last_sign_in DATE,
|
||||||
|
INDEX idx_invite_code (invite_code),
|
||||||
|
INDEX idx_email (email),
|
||||||
|
FOREIGN KEY (invited_by) REFERENCES users(id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
|
||||||
|
|
||||||
|
// 签到记录表
|
||||||
|
$pdo->exec("CREATE TABLE IF NOT EXISTS sign_in_logs (
|
||||||
|
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
sign_in_date DATE NOT NULL,
|
||||||
|
credits_earned INT DEFAULT 2,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY unique_signin (user_id, sign_in_date),
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
|
||||||
|
|
||||||
|
// 域名表
|
||||||
|
$pdo->exec("CREATE TABLE IF NOT EXISTS domains (
|
||||||
|
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
main_domain VARCHAR(100) NOT NULL,
|
||||||
|
subdomain VARCHAR(100) NOT NULL,
|
||||||
|
record_type VARCHAR(10) NOT NULL,
|
||||||
|
record_value VARCHAR(255) NOT NULL,
|
||||||
|
status TINYINT DEFAULT 1,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY unique_domain (main_domain, subdomain),
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
INDEX idx_user (user_id),
|
||||||
|
INDEX idx_status (status)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
|
||||||
|
|
||||||
|
// 邀请记录表
|
||||||
|
$pdo->exec("CREATE TABLE IF NOT EXISTS invite_logs (
|
||||||
|
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
inviter_id INT NOT NULL,
|
||||||
|
invitee_id INT NOT NULL,
|
||||||
|
credits_earned INT DEFAULT 10,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (inviter_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (invitee_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE KEY unique_invitee (invitee_id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
|
||||||
|
|
||||||
|
// 系统设置表
|
||||||
|
$pdo->exec("CREATE TABLE IF NOT EXISTS settings (
|
||||||
|
setting_key VARCHAR(50) PRIMARY KEY,
|
||||||
|
setting_value TEXT,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
|
||||||
|
|
||||||
|
// 插入默认设置
|
||||||
|
$pdo->exec("INSERT IGNORE INTO settings (setting_key, setting_value) VALUES
|
||||||
|
('theme', 'dark'),
|
||||||
|
('credit_per_domain', '5'),
|
||||||
|
('max_domains_per_user', '10'),
|
||||||
|
('sign_in_credit', '2'),
|
||||||
|
('invite_credit', '10'),
|
||||||
|
('site_name', '域名分发系统')
|
||||||
|
");
|
||||||
|
|
||||||
|
// 创建管理员账户(提示用户输入)
|
||||||
|
echo "<h2>数据库安装成功!</h2>";
|
||||||
|
echo "<p><b>您已安装 Parlz Domains Beta 0.1。</b></p>"
|
||||||
|
echo "<p>请访问 <a href='auth.php?action=register'>注册页面</a> 来创建管理员账户。</p>";
|
||||||
|
echo "<p><a href='/'>进入首页</a></p>";
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("安装失败: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
?>
|
||||||
Reference in New Issue
Block a user