From 1ea74a17b216b0c166ee97752afd40ea40cadac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=B0=8F=E7=99=BD=E5=93=A6?= Date: Fri, 24 Jul 2026 11:43:02 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E8=87=B3?= =?UTF-8?q?=E3=80=8C/=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 404.php | 2 + README.md | 26 +- auth.php | 35 +++ config.php | 43 +-- dashboard.php | 73 ++++- forgot_password.php | 117 ++++++++ functions.php | 701 +++++++++++++++++++++++++++----------------- index.php | 74 ++++- install.php | 9 +- reset_password.php | 147 ++++++++++ robots.txt | 4 + search.php | 356 ++++++++++++++++++++++ style.css | 556 ++++++++++++++++++++++++++++++++++- verify.php | 58 ++++ 14 files changed, 1873 insertions(+), 328 deletions(-) create mode 100644 404.php create mode 100644 forgot_password.php create mode 100644 reset_password.php create mode 100644 robots.txt create mode 100644 search.php create mode 100644 verify.php diff --git a/404.php b/404.php new file mode 100644 index 0000000..0ebe56d --- /dev/null +++ b/404.php @@ -0,0 +1,2 @@ + [ // 你要托管的另一个子域名 - 'zone_id' => '', // 区域 ID,在 CF 的域名三个点就有 - 'api_token' => '' // API,在 CF 的账户栏有一个 API 配置,需要给 DNS 读写权限。 - ], - 'Domain2' => [ // 以此类推... - 'zone_id' => '', - 'api_token' => '' - ], - 'Domain3' => [ - 'zone_id' => '', - 'api_token' => '' - ] + 'Domain1' => [], // Domain1: 你要托管的域名 + 'Domain2' => [], // 以此类推 + 'Domain3' => [] ]; define('SMTP_HOST', ''); // SMTP 地址 define('SMTP_PORT', 587); // SMTP 端口 @@ -51,8 +42,9 @@ define('SMTP_FROM_NAME', ''); // SMTP 发件人名称,可以使用你自己的 define('SITE_URL', ''); // 分发站点 URL define('SITE_NAME', ''); // 分发站点名称 ``` - -配置好后,在终端中输入以下命令安装 PHPMailer: +2. 访问 `https://你的网站/install.php` 来导入数据库。 +3. 访问 `https://你的网站/aliapi/setup_token.php` 来生成 Aliyun Token。 +4. 在终端中输入以下命令安装 PHPMailer: ```sh composer install ``` diff --git a/auth.php b/auth.php index 7b0797d..c243a67 100644 --- a/auth.php +++ b/auth.php @@ -43,6 +43,27 @@ $currentTheme = getTheme(); <?php echo $action === 'login' ? '登录' : '注册'; ?> - <?php echo SITE_NAME; ?> + + + + + + + + + + + + + + + + + + + + +
@@ -75,6 +96,8 @@ $currentTheme = getTheme();
还没有账号?立即注册 + 忘记密码? + 注册用户默认已同意 使用协议隐私政策
@@ -107,9 +130,21 @@ $currentTheme = getTheme();
已有账号?立即登录 + 忘记密码? + 注册用户默认已同意 使用协议隐私政策
+ \ No newline at end of file diff --git a/config.php b/config.php index d7d6dbb..e2f7257 100644 --- a/config.php +++ b/config.php @@ -1,16 +1,17 @@ [ - 'zone_id' => '', - 'api_token' => '' + 'Domain1' => [ // 你要托管的另一个子域名 + 'zone_id' => '', // 区域 ID,在 CF 的域名三个点就有 + 'api_token' => '' // API,在 CF 的账户栏有一个 API 配置,需要给 DNS 读写权限。 ], - 'Domain2' => [ + 'Domain2' => [ // 以此类推... 'zone_id' => '', 'api_token' => '' ], @@ -20,15 +21,23 @@ $DOMAINS_CONFIG = [ ] ]; -define('SMTP_HOST', ''); -define('SMTP_PORT', 587); -define('SMTP_SECURE', 'tls'); -define('SMTP_USERNAME', ''); -define('SMTP_PASSWORD', ''); -define('SMTP_FROM_EMAIL', ''); -define('SMTP_FROM_NAME', ''); -define('SITE_URL', ''); -define('SITE_NAME', ''); +*/ + +$DOMAINS_CONFIG = [ + 'Domain1' => [], + 'Domain2' => [], + 'Domain3' => [] +]; + +define('SMTP_HOST', ''); // SMTP 地址 +define('SMTP_PORT', 587); // SMTP 端口 +define('SMTP_SECURE', 'tls'); // SMTP 协议,ssl 加密,tls 不加密 +define('SMTP_USERNAME', ''); // SMTP 用户名 +define('SMTP_PASSWORD', ''); // SMTP 授权码 +define('SMTP_FROM_EMAIL', ''); // SMTP 发件邮箱,和用户名保持一致即可 +define('SMTP_FROM_NAME', ''); // SMTP 发件人名称,可以使用你自己的站点名字 +define('SITE_URL', ''); // 分发站点 URL +define('SITE_NAME', ''); // 分发站点名称 define('CREDIT_PER_DOMAIN', 5); define('MAX_DOMAINS_PER_USER', 10); define('SIGN_IN_CREDIT', 2); diff --git a/dashboard.php b/dashboard.php index 2f6ea5d..efbbe7e 100644 --- a/dashboard.php +++ b/dashboard.php @@ -82,6 +82,26 @@ $currentTheme = getTheme(); 用户面板 - <?php echo SITE_NAME; ?> + + + + + + + + + + + + + + + + + + + +
@@ -156,7 +176,40 @@ $currentTheme = getTheme();
- + +
+
+

👋 欢迎回来,

+

+ 您在 注册 + + + 管理员 + + +

+
+ +
+
+
+
⚠️
+
+
📢 重要提醒:
+
+ 严禁使用本平台域名搭建色情、淫秽、低俗、赌博、博彩、诈骗、毒品、违禁品交易、传播病毒、木马、钓鱼、侵犯他人知识产权或隐私权的网站。一旦发现,立即封号并移交公安机关处理!如果您发现了,请发送邮件至 odoorbell@163.com。 +
+
+
+
@@ -372,7 +425,7 @@ $currentTheme = getTheme(); // 切换事件 typeSelect.addEventListener('change', function() { const type = this.value; - hintSpan.innerHTML = hints[type] || '请输入记录值'。; + hintSpan.innerHTML = hints[type] || '请输入记录值。'; valueInput.placeholder = placeholders[type] || '请输入记录值。'; // 添加小字提示 @@ -409,5 +462,21 @@ $currentTheme = getTheme(); } } + \ No newline at end of file diff --git a/forgot_password.php b/forgot_password.php new file mode 100644 index 0000000..8d881ef --- /dev/null +++ b/forgot_password.php @@ -0,0 +1,117 @@ + + + + + + + <?php echo htmlspecialchars($pageTitle); ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+ +

忘记密码

+

输入您的邮箱,我们将发送重置链接

+ + +
+ + +
+ + + +
+ +

📧 邮件已发送

+

请检查您的邮箱,点击邮件中的链接重置密码。
如果没收到邮件,请检查垃圾箱。

+ + 返回登录 + +
+ +
+
+ + +
+ +
+ + + +
+
+
+ + \ No newline at end of file diff --git a/functions.php b/functions.php index 9de4e79..929e894 100644 --- a/functions.php +++ b/functions.php @@ -5,6 +5,100 @@ 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 '' . "\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 '' . "\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 '' . "\n"; +} + // ===== 用户相关函数 ===== /** @@ -137,7 +231,7 @@ function sendVerificationEmail($email, $username, $token) { 邮箱验证 + + +
+
+

🔑 重置密码

+
+
+

您好,' . htmlspecialchars($username) . '

+

我们收到了您重置密码的请求。请点击下方按钮设置新密码:

+

+ 🔐 重置密码 +

+

如果按钮无法点击,请复制以下链接到浏览器打开:

+ +

+ ⏰ 此链接 1 小时 内有效。
+ 🔒 如果您没有请求重置密码,请忽略此邮件。 +

+
+ +
+ + + '; + + $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; + } +} + // ===== 积分相关函数 ===== /** @@ -517,8 +753,152 @@ function getRecordTypeHint($type) { 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) { // 检查权限 @@ -557,10 +937,10 @@ function addDomain($userId, $mainDomain, $subdomain, $type, $value) { $pdo->beginTransaction(); try { - // 先尝试添加到 Cloudflare - $cfResult = addCloudflareRecord($mainDomain, $subdomain, $type, $value); - if (!$cfResult['success']) { - return ['success' => false, 'message' => '添加失败: ' . $cfResult['message']]; + // 先尝试添加到阿里云 + $aliResult = addAliyunRecord($mainDomain, $subdomain, $type, $value); + if (!$aliResult['success']) { + return ['success' => false, 'message' => '添加失败: ' . $aliResult['message']]; } // 扣减积分 @@ -579,8 +959,8 @@ function addDomain($userId, $mainDomain, $subdomain, $type, $value) { } catch (Exception $e) { $pdo->rollBack(); - // 如果数据库失败,尝试删除 Cloudflare 记录 - deleteCloudflareRecord($mainDomain, $subdomain); + // 如果数据库失败,尝试删除阿里云记录 + deleteAliyunRecord($mainDomain, $subdomain); return ['success' => false, 'message' => '添加失败: ' . $e->getMessage()]; } } @@ -596,7 +976,7 @@ function getUserDomains($userId) { } /** - * 删除域名 + * 删除域名 - 使用阿里云API */ function deleteDomain($userId, $domainId) { $pdo = getDB(); @@ -610,8 +990,11 @@ function deleteDomain($userId, $domainId) { return ['success' => false, 'message' => '该域名不存在。']; } - // 从 Cloudflare 删除 - deleteCloudflareRecord($domain['main_domain'], $domain['subdomain']); + // 从阿里云删除 + $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 = ?"); @@ -620,268 +1003,6 @@ function deleteDomain($userId, $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; -} - // ===== 其他辅助函数 ===== /** @@ -959,4 +1080,38 @@ 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(); +} ?> \ No newline at end of file diff --git a/index.php b/index.php index ae4f9e1..d151208 100644 --- a/index.php +++ b/index.php @@ -19,9 +19,30 @@ $mainDomains = getMainDomains(); + + + <?php echo SITE_NAME; ?> - 用心制作的免费二级域名分发网站 + + + + + + + + + + + + + + + + + +
@@ -35,10 +56,10 @@ $mainDomains = getMainDomains(); - + - +
@@ -51,10 +72,10 @@ $mainDomains = getMainDomains();
积分制管理,每日签到免费领取积分

@@ -90,8 +111,8 @@ $mainDomains = getMainDomains();
-

Cloudflare 驱动

-

域名自动托管到 Cloudflare,全球节点加速,稳定可靠

+

域名数量

+

目前有 3 个域名:euil.cn、feld.top、lrgdmc.cn,全球节点加速,稳定可靠

@@ -108,7 +129,48 @@ $mainDomains = getMainDomains();

所有数据安全加密

+
+ \ No newline at end of file diff --git a/install.php b/install.php index 83c81f6..b5c41f3 100644 --- a/install.php +++ b/install.php @@ -22,10 +22,13 @@ try { is_admin TINYINT DEFAULT 0, is_verified TINYINT DEFAULT 0, verification_token VARCHAR(64), + reset_token VARCHAR(64) NULL, + reset_expires DATETIME NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, last_sign_in DATE, INDEX idx_invite_code (invite_code), INDEX idx_email (email), + INDEX idx_reset_token (reset_token), FOREIGN KEY (invited_by) REFERENCES users(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); @@ -83,12 +86,12 @@ try { ('max_domains_per_user', '10'), ('sign_in_credit', '2'), ('invite_credit', '10'), - ('site_name', '域名分发系统') + ('site_name', 'Parlz Domains') "); // 创建管理员账户(提示用户输入) - echo "

数据库安装成功!

"; - echo "

您已安装 Parlz Domains Beta 0.1。

" + echo "

✅ 数据库安装成功!

"; + echo "

您已安装 " . SITE_NAME . " Beta 0.2。

"; echo "

请访问 注册页面 来创建管理员账户。

"; echo "

进入首页

"; diff --git a/reset_password.php b/reset_password.php new file mode 100644 index 0000000..70a04ae --- /dev/null +++ b/reset_password.php @@ -0,0 +1,147 @@ +prepare("SELECT id, email FROM users WHERE reset_token = ? AND reset_expires > NOW()"); + $stmt->execute([$token]); + $user = $stmt->fetch(); + + if (!$user) { + $message = '重置链接无效或已过期,请重新申请'; + $error = true; + } else { + $error = false; + } +} + +// 处理密码重置 +if ($_SERVER['REQUEST_METHOD'] === 'POST' && !$error) { + $password = $_POST['password'] ?? ''; + $confirm = $_POST['confirm_password'] ?? ''; + + if (strlen($password) < 6) { + $message = '密码长度至少6位'; + } elseif ($password !== $confirm) { + $message = '两次密码输入不一致'; + } else { + $result = resetPassword($token, $password); + if ($result['success']) { + $success = true; + $message = $result['message']; + } else { + $message = $result['message']; + } + } +} + +$pageTitle = '重置密码 - ' . SITE_NAME; +$pageDescription = '重置您的 ' . SITE_NAME . ' 账号密码。'; +?> + + + + + + <?php echo htmlspecialchars($pageTitle); ?> + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+ +

重置密码

+

设置新密码

+ + +
+ + +
+ + + +
+ +

✅ 密码重置成功

+

+ + 立即登录 + +
+ +
+ + +
+ + +
+
+ + +
密码至少6个字符
+
+
+ + +
+ +
+ + + +
+
+
+ + \ No newline at end of file diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..ab7da73 --- /dev/null +++ b/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Disallow: /vendor/ +Disallow: /aliapi/ +Sitemap: https://dns.parlz.com/sitemap.xml \ No newline at end of file diff --git a/search.php b/search.php new file mode 100644 index 0000000..f56fdcf --- /dev/null +++ b/search.php @@ -0,0 +1,356 @@ + false, 'message' => '请提供域名参数']); + exit; + } + if (!preg_match('/^([a-z0-9\-]+\.)+[a-z]{2,}$/i', $domain)) { + echo json_encode(['success' => false, 'message' => '域名格式无效']); + exit; + } + $pdo = getDB(); + $parts = explode('.', $domain); + $subdomain = strtolower($parts[0]); + $mainDomain = strtolower(implode('.', array_slice($parts, 1))); + $mainDomains = getMainDomains(); + if (!in_array($mainDomain, $mainDomains)) { + echo json_encode([ + 'success' => true, + 'available' => false, + 'message' => '不支持的域名: ' . $mainDomain, + 'record_type' => null, + 'record_value' => null, + 'username' => null, + 'created_at' => null + ]); + exit; + } + $stmt = $pdo->prepare("SELECT d.*, u.username FROM domains d LEFT JOIN users u ON d.user_id = u.id WHERE d.main_domain = ? AND d.subdomain = ? AND d.status = 1"); + $stmt->execute([$mainDomain, $subdomain]); + $record = $stmt->fetch(); + if ($record) { + echo json_encode([ + 'success' => true, + 'available' => false, + 'message' => '该域名已被 ' . htmlspecialchars($record['username'] ?? '用户') . ' 占用', + 'record_type' => $record['record_type'], + 'record_value' => $record['record_value'], + 'username' => $record['username'] ?? null, + 'created_at' => $record['created_at'] + ]); + } else { + echo json_encode([ + 'success' => true, + 'available' => true, + 'message' => '域名可用', + 'record_type' => null, + 'record_value' => null, + 'username' => null, + 'created_at' => null + ]); + } + exit; +} +?> + + + + + + 域名查询 - <?php echo SITE_NAME; ?> + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

+
+
+ + +
+ + + + + + +
+
+
+
+
+

域名查询

+

输入要查询的域名,检查是否已被注册或占用

+
+ + + +
+
+
+
+
查询中...
+
正在检查域名状态
+
+
+
+ + + + + +
完整域名-
状态-
所属用户-
创建时间-
+
+ +
+ +
+
+

最近查询

+ 清空记录 +
+
+
+ +

暂无查询记录

+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/style.css b/style.css index 479b776..0292ac8 100644 --- a/style.css +++ b/style.css @@ -21,7 +21,7 @@ --accent-orange: #f0883e; --radius: 12px; --radius-sm: 6px; - --transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1); + --transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1) } [data-theme="light"] { @@ -54,7 +54,7 @@ } * { - font-family: "Yozai Medium" + font-family: "Yozai Medium" } html { @@ -969,7 +969,7 @@ tbody tr:last-child td { background: var(--bg-primary); padding: 1px 10px; border-radius: 4px; - font-size: 11px; + font-size: 12px; font-weight: 700; color: var(--accent-yellow); border: 1px solid var(--border-color) @@ -1386,7 +1386,7 @@ footer .fa-heart { } .recent-user { - font-size: 11px + font-size: 12px } .record-type-help { @@ -1419,7 +1419,7 @@ footer .fa-heart { } .btn-sm { - font-size: 11px; + font-size: 12px; padding: 4px 10px } } @@ -1439,7 +1439,7 @@ footer .fa-heart { } .stat-card .label { - font-size: 11px + font-size: 12px } .features-grid { @@ -1460,7 +1460,7 @@ footer .fa-heart { } .feature-card p { - font-size: 11px + font-size: 12px } .hero h1 { @@ -1476,7 +1476,7 @@ footer .fa-heart { } .domain-badge { - font-size: 11px; + font-size: 12px; padding: 4px 10px } @@ -1485,7 +1485,7 @@ footer .fa-heart { } .badge { - font-size: 11px; + font-size: 12px; padding: 1px 10px } @@ -1509,6 +1509,542 @@ footer .fa-heart { } .stats-bar .stat-label { - font-size: 11px + font-size: 12px + } +} + +.search-container { + max-width: 700px; + margin: 60px auto 40px +} + +.search-box-large { + display: flex; + gap: 12px; + background: var(--bg-secondary); + padding: 20px; + border-radius: var(--radius); + border: 1px solid var(--border-color); + box-shadow: var(--shadow) +} + +.search-box-large input { + flex: 1; + padding: 14px 18px; + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + background: var(--bg-input); + color: var(--text-primary); + font-size: 16px; + outline: none; + transition: all var(--transition) +} + +.search-box-large input:focus { + border-color: var(--accent-blue); + box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.15) +} + +.search-box-large button { + padding: 14px 32px; + font-size: 16px; + white-space: nowrap +} + +.result-card { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius); + padding: 30px; + margin-top: 25px; + box-shadow: var(--shadow); + display: none +} + +.result-card.show { + display: block; + animation: slideDown 0.4s ease +} + +.result-status { + display: flex; + align-items: center; + gap: 16px; + padding: 16px 20px; + border-radius: var(--radius-sm); + margin-bottom: 16px +} + +.result-status.available { + background: rgba(63, 185, 80, 0.12); + border: 1px solid var(--accent-green); + color: var(--accent-green) +} + +.result-status.unavailable { + background: rgba(248, 81, 73, 0.12); + border: 1px solid var(--accent-red); + color: var(--accent-red) +} + +.result-status.checking { + background: rgba(88, 166, 255, 0.12); + border: 1px solid var(--accent-blue); + color: var(--accent-blue) +} + +.result-status .icon { + font-size: 28px +} + +.result-status .info { + flex: 1 +} + +.result-status .info .main { + font-size: 18px; + font-weight: 600 +} + +.result-status .info .sub { + font-size: 13px; + opacity: 0.8 +} + +.result-details { + padding: 16px 20px; + background: var(--bg-primary); + border-radius: var(--radius-sm); + border: 1px solid var(--border-color) +} + +.result-details table { + width: 100%; + font-size: 14px +} + +.result-details table td { + padding: 6px 10px +} + +.result-details table td:first-child { + color: var(--text-muted); + width: 120px +} + +.result-actions { + margin-top: 16px; + display: flex; + gap: 12px; + flex-wrap: wrap +} + +.suggestions { + margin-top: 12px; + display: flex; + gap: 8px; + flex-wrap: wrap +} + +.suggestions .tag { + padding: 4px 14px; + border-radius: 20px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + font-size: 13px; + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition) +} + +.suggestions .tag:hover { + border-color: var(--accent-blue); + color: var(--accent-blue) +} + +.loading-spinner { + display: inline-block; + width: 20px; + height: 20px; + border: 3px solid var(--border-color); + border-top-color: var(--accent-blue); + border-radius: 50%; + animation: spin 0.8s linear infinite +} + +@keyframes spin { + to { + transform: rotate(360deg) + } +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-20px) + } + + to { + opacity: 1; + transform: translateY(0) + } +} + +.history-list { + margin-top: 20px +} + +.history-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 14px; + border-bottom: 1px solid var(--border-color); + font-size: 14px +} + +.history-item:last-child { + border-bottom: none +} + +.history-item .status-badge { + font-size: 12px; + padding: 2px 12px; + border-radius: 12px +} + +.status-badge.available { + background: rgba(63, 185, 80, 0.15); + color: var(--accent-green) +} + +.status-badge.unavailable { + background: rgba(248, 81, 73, 0.15); + color: var(--accent-red) +} + +.suggestions-title { + font-size: 13px; + color: var(--text-muted); + margin-top: 12px +} + +@media (max-width:600px) { + .search-box-large { + flex-direction: column + } + + .search-box-large button { + width: 100%; + justify-content: center + } + + .result-status { + flex-direction: column; + text-align: center + } + + .result-details table td { + display: block; + padding: 4px 0 + } + + .result-details table td:first-child { + width: auto; + font-weight: 600 + } +} + +.auth-container { + max-width: 440px; + margin: 60px auto 40px; + padding: 0 16px +} + +.auth-card { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius); + padding: 40px; + box-shadow: var(--shadow) +} + +.auth-card .icon-top { + text-align: center; + font-size: 48px; + color: var(--accent-blue); + margin-bottom: 16px +} + +.auth-card h2 { + text-align: center; + margin-bottom: 8px; + font-weight: 700 +} + +.auth-card .subtitle { + text-align: center; + color: var(--text-secondary); + font-size: 14px; + margin-bottom: 24px +} + +.auth-card .form-group { + margin-bottom: 18px +} + +.auth-card .form-group label { + display: block; + font-size: 14px; + font-weight: 500; + color: var(--text-secondary); + margin-bottom: 6px +} + +.auth-card .form-group label i { + margin-right: 6px; + color: var(--accent-blue) +} + +.auth-card .form-group input { + width: 100%; + padding: 12px 16px; + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + background: var(--bg-input); + color: var(--text-primary); + font-size: 15px; + transition: all var(--transition); + outline: none +} + +.auth-card .form-group input:focus { + border-color: var(--accent-blue); + box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.15) +} + +.auth-card .btn-primary { + width: 100%; + padding: 14px; + font-size: 16px; + justify-content: center +} + +.auth-card .auth-links { + text-align: center; + margin-top: 18px; + font-size: 14px; + color: var(--text-secondary) +} + +.auth-card .auth-links a { + color: var(--accent-blue); + text-decoration: none +} + +.auth-card .auth-links a:hover { + text-decoration: underline +} + +.alert { + padding: 12px 16px; + border-radius: var(--radius-sm); + margin-bottom: 18px; + display: flex; + align-items: center; + gap: 10px; + font-size: 14px +} + +.alert-success { + background: rgba(63, 185, 80, 0.12); + border: 1px solid var(--accent-green); + color: var(--accent-green) +} + +.alert-error { + background: rgba(248, 81, 73, 0.12); + border: 1px solid var(--accent-red); + color: var(--accent-red) +} + +.alert i { + font-size: 18px +} + +.success-box { + text-align: center; + padding: 20px 0 +} + +.success-box i { + font-size: 64px; + color: var(--accent-green); + margin-bottom: 16px +} + +.success-box h3 { + margin-bottom: 8px +} + +.success-box p { + color: var(--text-secondary); + font-size: 14px; + margin-bottom: 20px +} + +@media (max-width:600px) { + .auth-card { + padding: 24px 20px + } +} + +.auth-container { + max-width: 440px; + margin: 60px auto 40px; + padding: 0 16px +} + +.auth-card { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius); + padding: 40px; + box-shadow: var(--shadow) +} + +.auth-card .icon-top { + text-align: center; + font-size: 48px; + color: var(--accent-blue); + margin-bottom: 16px +} + +.auth-card h2 { + text-align: center; + margin-bottom: 8px; + font-weight: 700 +} + +.auth-card .subtitle { + text-align: center; + color: var(--text-secondary); + font-size: 14px; + margin-bottom: 24px +} + +.auth-card .form-group { + margin-bottom: 18px +} + +.auth-card .form-group label { + display: block; + font-size: 14px; + font-weight: 500; + color: var(--text-secondary); + margin-bottom: 6px +} + +.auth-card .form-group label i { + margin-right: 6px; + color: var(--accent-blue) +} + +.auth-card .form-group input { + width: 100%; + padding: 12px 16px; + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + background: var(--bg-input); + color: var(--text-primary); + font-size: 15px; + transition: all var(--transition); + outline: none +} + +.auth-card .form-group input:focus { + border-color: var(--accent-blue); + box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.15) +} + +.auth-card .btn-primary { + width: 100%; + padding: 14px; + font-size: 16px; + justify-content: center +} + +.auth-card .auth-links { + text-align: center; + margin-top: 18px; + font-size: 14px; + color: var(--text-secondary) +} + +.auth-card .auth-links a { + color: var(--accent-blue); + text-decoration: none +} + +.auth-card .auth-links a:hover { + text-decoration: underline +} + +.alert { + padding: 12px 16px; + border-radius: var(--radius-sm); + margin-bottom: 18px; + display: flex; + align-items: center; + gap: 10px; + font-size: 14px +} + +.alert-success { + background: rgba(63, 185, 80, 0.12); + border: 1px solid var(--accent-green); + color: var(--accent-green) +} + +.alert-error { + background: rgba(248, 81, 73, 0.12); + border: 1px solid var(--accent-red); + color: var(--accent-red) +} + +.alert i { + font-size: 18px +} + +.success-box { + text-align: center; + padding: 20px 0 +} + +.success-box i { + font-size: 64px; + color: var(--accent-green); + margin-bottom: 16px +} + +.success-box h3 { + margin-bottom: 8px +} + +.success-box p { + color: var(--text-secondary); + font-size: 14px; + margin-bottom: 20px +} + +.password-hint { + font-size: 12px; + color: var(--text-muted); + margin-top: 4px +} + +@media (max-width:600px) { + .auth-card { + padding: 24px 20px } } \ No newline at end of file diff --git a/verify.php b/verify.php new file mode 100644 index 0000000..d5b06d1 --- /dev/null +++ b/verify.php @@ -0,0 +1,58 @@ + + + + + + + 邮箱验证 - <?php echo SITE_NAME; ?> + + + + +
+
+ +
+ +
+

验证成功!

+

您的邮箱已成功验证,现在可以登录来使用所有功能了。

+ + 去登录 + + +
+ +
+

验证失败!

+

+

+ + 可能的原因:链接已过期、已被使用或无效,请联系管理员手动激活。 +

+
+ + 登录 + +
+ +
+
+ + \ No newline at end of file