文件

1117 行
37 KiB
PHP

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
<?php
require_once 'config.php';
require_once 'vendor/autoload.php'; // PHPMailer
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
/**
* 输出 Canonical 标签
*/
function renderCanonical($customUrl = null) {
if ($customUrl) {
$url = $customUrl;
} else {
$scheme = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'];
$uri = $_SERVER['REQUEST_URI'];
// 移除查询参数中的无关参数(如 session、utm 等)
$parsed = parse_url($uri);
if (isset($parsed['query'])) {
parse_str($parsed['query'], $query);
// 移除无关参数
unset($query['_ajax'], $query['t'], $query['sid']);
$newQuery = http_build_query($query);
$url = $scheme . '://' . $host . $parsed['path'] . ($newQuery ? '?' . $newQuery : '');
} else {
$url = $scheme . '://' . $host . $uri;
}
}
echo '<link rel="canonical" href="' . htmlspecialchars($url) . '">' . "\n";
}
/**
* 输出 Schema.org 结构化数据(网站和组织)
*/
function renderSchemaOrg($pageType = 'WebPage', $extraData = []) {
$schema = [
'@context' => 'https://schema.org',
'@type' => $pageType,
'name' => SITE_NAME,
'description' => '免费二级域名分发平台,支持A、AAAA、CNAME、MX、NS、TXT、SRV记录',
'url' => 'https://' . $_SERVER['HTTP_HOST'],
'inLanguage' => 'zh-CN',
'about' => [
'@type' => 'Thing',
'name' => '域名分发'
]
];
// 根据页面类型添加额外数据
if ($pageType === 'WebPage') {
$schema['dateModified'] = date('Y-m-d\TH:i:sP');
}
if ($pageType === 'Product') {
$schema['offers'] = [
'@type' => 'Offer',
'price' => '0',
'priceCurrency' => 'CNY',
'availability' => 'https://schema.org/InStock'
];
}
// 合并额外数据
if (!empty($extraData)) {
$schema = array_merge($schema, $extraData);
}
echo '<script type="application/ld+json">' . "\n";
echo json_encode($schema, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
echo "\n" . '</script>' . "\n";
}
/**
* 输出面包屑结构化数据
*/
function renderBreadcrumbSchema($items) {
$schema = [
'@context' => 'https://schema.org',
'@type' => 'BreadcrumbList',
'itemListElement' => []
];
$position = 1;
foreach ($items as $item) {
$schema['itemListElement'][] = [
'@type' => 'ListItem',
'position' => $position,
'name' => $item['name'],
'item' => $item['url']
];
$position++;
}
echo '<script type="application/ld+json">' . "\n";
echo json_encode($schema, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
echo "\n" . '</script>' . "\n";
}
// ===== 用户相关函数 =====
/**
* 用户注册
*/
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 requestPasswordReset($email) {
$pdo = getDB();
// 检查用户是否存在
$stmt = $pdo->prepare("SELECT id, username FROM users WHERE email = ? AND is_verified = 1");
$stmt->execute([$email]);
$user = $stmt->fetch();
if (!$user) {
// 为了安全,不暴露用户是否存在
return ['success' => true, 'message' => '如果该邮箱已注册,我们将发送重置链接'];
}
// 生成重置令牌
$token = bin2hex(random_bytes(32));
$expires = date('Y-m-d H:i:s', strtotime('+1 hour'));
// 保存令牌
$stmt = $pdo->prepare("UPDATE users SET reset_token = ?, reset_expires = ? WHERE id = ?");
$stmt->execute([$token, $expires, $user['id']]);
// 发送重置邮件
sendResetEmail($email, $user['username'], $token);
return ['success' => true, 'message' => '如果该邮箱已注册,我们将发送重置链接'];
}
/**
* 重置密码
*/
function resetPassword($token, $newPassword) {
$pdo = getDB();
// 验证令牌
$stmt = $pdo->prepare("SELECT id FROM users WHERE reset_token = ? AND reset_expires > NOW()");
$stmt->execute([$token]);
$user = $stmt->fetch();
if (!$user) {
return ['success' => false, 'message' => '重置链接无效或已过期'];
}
// 更新密码
$hashedPassword = password_hash($newPassword, PASSWORD_DEFAULT);
$stmt = $pdo->prepare("UPDATE users SET password = ?, reset_token = NULL, reset_expires = NULL WHERE id = ?");
$stmt->execute([$hashedPassword, $user['id']]);
return ['success' => true, 'message' => '密码已重置,请使用新密码登录'];
}
/**
* 发送重置密码邮件
*/
function sendResetEmail($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);
$resetLink = SITE_URL . "/reset-password?token=" . $token;
$mail->Subject = '重置密码 - ' . SITE_NAME;
$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; }
.content { color: #24292f; line-height: 1.8; }
.btn { display: inline-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>🔑 重置密码</h1>
</div>
<div class="content">
<p>您好,<strong>' . htmlspecialchars($username) . '</strong></p>
<p>我们收到了您重置密码的请求。请点击下方按钮设置新密码:</p>
<p style="text-align: center;">
<a href="' . $resetLink . '" class="btn">🔐 重置密码</a>
</p>
<p style="color: #57606a; font-size: 14px;">如果按钮无法点击,请复制以下链接到浏览器打开:</p>
<div class="link-box">' . $resetLink . '</div>
<p style="margin-top: 20px; font-size: 14px; color: #57606a;">
⏰ 此链接 <span class="highlight">1 小时</span> 内有效。<br>
🔒 如果您没有请求重置密码,请忽略此邮件。
</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" .
$resetLink . "\n\n" .
"此链接 1 小时内有效。\n\n" .
"如果您没有请求重置密码,请忽略此邮件。";
$mail->send();
return true;
} catch (Exception $e) {
error_log("重置邮件发送失败: " . $e->getMessage());
return false;
}
}
// ===== 积分相关函数 =====
/**
* 获取用户积分
*/
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] ?? '';
}
// ===== 阿里云 API 函数(通过你的 aliapi 接口) =====
/**
* 调用阿里云API的通用函数
*/
function callAliyunAPI($endpoint, $params) {
$tokenFile = __DIR__ . '/aliapi/.token';
$token = trim(file_get_contents($tokenFile));
$params['token'] = $token;
$url = "https://" . $_SERVER['HTTP_HOST'] . "/aliapi/api/" . $endpoint . ".php";
$fullUrl = $url . '?' . http_build_query($params);
error_log("阿里云API请求: " . $fullUrl);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $fullUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($curlError) {
error_log("阿里云 API CURL 错误: " . $curlError);
return ['success' => false, 'message' => 'CURL 错误: ' . $curlError];
}
$result = json_decode($response, true);
if (!$result) {
error_log("阿里云 API 响应解析失败: " . $response);
return ['success' => false, 'message' => '响应解析失败。'];
}
error_log("阿里云 API 响应: " . $response);
return $result;
}
/**
* 添加DNS记录(调用阿里云API)
*/
function addAliyunRecord($mainDomain, $subdomain, $type, $value) {
$params = [
'domain_name' => $mainDomain,
'rr' => $subdomain,
'type' => strtoupper($type),
'value' => $value,
'ttl' => 600
];
// MX和SRV记录需要priority参数
$typeUpper = strtoupper($type);
if ($typeUpper === 'MX' || $typeUpper === 'SRV') {
$parts = preg_split('/\s+/', trim($value));
if (count($parts) >= 1 && is_numeric($parts[0])) {
$params['priority'] = intval($parts[0]);
}
}
$result = callAliyunAPI('add_record', $params);
if ($result && isset($result['code']) && $result['code'] === 200) {
return ['success' => true, 'message' => '添加成功', 'data' => $result['data'] ?? []];
} else {
$errorMsg = $result['message'] ?? '未知错误';
return ['success' => false, 'message' => '添加失败: ' . $errorMsg];
}
}
/**
* 删除DNS记录(调用阿里云API)
*/
function deleteAliyunRecord($mainDomain, $subdomain) {
// 1. 先查询记录ID
$listParams = [
'domain_name' => $mainDomain,
'rr_keyword' => $subdomain
];
$listResult = callAliyunAPI('list_records', $listParams);
if (!$listResult || $listResult['code'] !== 200) {
error_log("查询记录失败: " . ($listResult['message'] ?? '未知错误'));
return false;
}
// 查找匹配的记录ID
$recordId = null;
if (isset($listResult['data']['records'])) {
foreach ($listResult['data']['records'] as $record) {
if ($record['rr'] === $subdomain) {
$recordId = $record['record_id'];
break;
}
}
}
if (!$recordId) {
error_log("未找到记录: {$subdomain}.{$mainDomain}");
return false;
}
// 2. 删除记录
$deleteParams = ['record_id' => $recordId];
$deleteResult = callAliyunAPI('delete_record', $deleteParams);
if ($deleteResult && isset($deleteResult['code']) && $deleteResult['code'] === 200) {
return true;
}
error_log("删除记录失败: " . ($deleteResult['message'] ?? '未知错误'));
return false;
}
/**
* 查询DNS记录列表(调用阿里云API)
*/
function listAliyunRecords($mainDomain, $rrKeyword = null, $type = null) {
$params = [
'domain_name' => $mainDomain,
'page' => 1,
'page_size' => 100
];
if ($rrKeyword !== null) {
$params['rr_keyword'] = $rrKeyword;
}
if ($type !== null) {
$params['type'] = strtoupper($type);
}
$result = callAliyunAPI('list_records', $params);
if ($result && isset($result['code']) && $result['code'] === 200) {
return ['success' => true, 'data' => $result['data'] ?? []];
} else {
return ['success' => false, 'message' => $result['message'] ?? '查询失败'];
}
}
/**
* 添加域名(扣除积分)- 使用阿里云API
*/
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 {
// 先尝试添加到阿里云
$aliResult = addAliyunRecord($mainDomain, $subdomain, $type, $value);
if (!$aliResult['success']) {
return ['success' => false, 'message' => '添加失败: ' . $aliResult['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();
// 如果数据库失败,尝试删除阿里云记录
deleteAliyunRecord($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();
}
/**
* 删除域名 - 使用阿里云API
*/
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' => '该域名不存在。'];
}
// 从阿里云删除
$deleted = deleteAliyunRecord($domain['main_domain'], $domain['subdomain']);
if (!$deleted) {
error_log("阿里云删除失败: " . $domain['subdomain'] . '.' . $domain['main_domain'] . ',但数据库将继续删除');
}
// 软删除
$stmt = $pdo->prepare("UPDATE domains SET status = 0 WHERE id = ?");
$stmt->execute([$domainId]);
return ['success' => true, 'message' => '域名删除成功。'];
}
// ===== 其他辅助函数 =====
/**
* 检查是否已登录
*/
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);
}
/**
* 兼容旧代码 - 获取所有域名(管理员使用)
*/
function getDomains($mainDomain = null) {
$pdo = getDB();
$sql = "SELECT d.*, u.username FROM domains d
LEFT JOIN users u ON d.user_id = u.id
WHERE d.status = 1";
$params = [];
if ($mainDomain) {
$sql .= " AND d.main_domain = ?";
$params[] = $mainDomain;
}
$sql .= " ORDER BY d.created_at DESC";
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll();
}
/**
* 兼容旧代码 - 获取所有域名(包含已删除的)
*/
function getAllDomains() {
$pdo = getDB();
$stmt = $pdo->query("SELECT d.*, u.username FROM domains d
LEFT JOIN users u ON d.user_id = u.id
ORDER BY d.created_at DESC");
return $stmt->fetchAll();
}
?>