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 = '
邮箱验证
您好,' . htmlspecialchars($username) . ':
感谢您注册 ' . SITE_NAME . '!请点击下方按钮验证您的邮箱地址:
✅ 验证邮箱
如果按钮无法点击,请复制以下链接到浏览器打开:
' . $verifyLink . '
⏰ 此链接 24 小时 内有效。
🔒 如果您没有注册 ' . SITE_NAME . ',请忽略并删除此邮件。
';
$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);
}
?>