Files
parlz-domain/functions.php
T

962 lines
33 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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);
}
?>