简介:这份资源是PHP开发的二级域名分发系统源码,已对接易支付接口,面向需要搭建域名分发平台的站长、个人开发者及中小型服务商,可解决多用户二级域名申请、自动分发与在线收款的一体化需求。源码基于PHP7.2环境运行,需安装SG11扩展,部署时放入根目录解压、先设置伪静态再访问域名即可进入安装流程,适合具备一定PHP环境配置与建站经验的技术人员使用。压缩包为rar格式,整体约13.83MB,文件类型明细上游暂未提供,但按此类系统惯例应包含PHP程序文件、前端静态资源、数据库脚本及配置文件等,便于直接部署与二次开发。目前已有448人学习下载,说明该方案在域名分发场景中具有一定参考价值。读者可借此获得一套可运行的域名分发系统,理解易支付对接逻辑、伪静态配置要点与安装排错思路,并在此基础上按自身业务需求调整界面与功能模块。
1. 二级域名分发系统源码:从申请到解析,一套能跑通的自动化链路
做 SaaS、建站平台或者多租户工具的人,迟早会撞上同一个需求:让用户自己申请一个xxx.你的主域.com的二级域名,申请完自动生效,不用你手动去 DNS 后台加记录。用户量一上来,手工加解析就是灾难,一天几十个申请能把人逼疯。这套「二级域名分发系统源码」解决的就是这件事——它把域名申请、可用性校验、DNS 记录写入、订单支付串成一条自动链路,并且已经对接了易支付,用户付完款系统自动开通。适合谁?适合手里有主域名、想快速搭一个二级域名分发站点的站长和独立开发者,尤其是做虚拟主机、短链、博客托管、游戏私服这类需要批量发子域名的场景。源码本身是 PHP 技术栈,部署门槛不高,但 DNS 接口和支付回调这两块是翻车重灾区,下面拆开讲。
2. 系统架构与 DNS 解析原理:为什么不能只靠一条 A 记录
2.1 二级域名分发的核心链路
先把整条链路说清楚,不然后面配置全是玄学。用户在前台输入想要的二级域名前缀,系统做三件事:查这个前缀有没有被占用、查主域名当前的解析记录、把新记录写进 DNS。写记录这一步,取决于你的主域名托管在哪家 DNS 服务商。常见做法是主域名托管在 Cloudflare、阿里云 DNS、DNSPod 这类提供 API 的服务商,系统通过 API 调用添加解析记录。
这里有个关键选型问题:泛解析还是逐条解析。泛解析就是加一条*.你的主域.com指向服务器 IP,所有二级域名自动生效,系统只需要记录「谁申请了哪个前缀」,不用真的去写 DNS。这种方式最省事,但缺点是无法给不同用户指向不同 IP,也没法做单独的解析管理。逐条解析则是每个申请都调一次 DNS API 写一条记录,灵活但依赖 API 稳定性。
这套源码走的是逐条解析路线,因为它要对接支付,每个用户开通的域名需要独立可控。理解这一点很重要,后面排查「域名申请成功但打不开」时,第一个要看的就是 DNS 记录到底写进去没有。
2.2 主域名与 DNS 服务商的对接配置
以 Cloudflare 为例,你需要在 Cloudflare 后台生成一个 API Token,权限给到Zone.DNS的编辑权限,Zone 资源限定到你的主域名。拿到 Token 后填进系统的配置文件。常见做法是把 DNS 配置单独放一个 config 文件,方便换服务商。
// config/dns.php 常见配置结构 return [ 'driver' => 'cloudflare', // DNS 服务商驱动 'api_token'=> '你的Cloudflare_Token', 'zone_id' => '你的主域名ZoneID', // 在域名概览页右下角 'main_domain' => 'example.com', // 主域名,不带 www 'record_type' => 'A', // 默认解析类型 'default_ip' => '1.2.3.4', // 默认指向的服务器IP 'ttl' => 120, // TTL,Cloudflare 自动模式填1 ];逻辑说明:driver决定调用哪个服务商的 API 封装类,源码里一般有CloudflareDriver、AliyunDriver等。zone_id是最容易填错的地方,它不是域名本身,而是服务商给这个域名分配的唯一 ID,填错会直接报 403 或 record not found。default_ip是二级域名默认解析到的服务器地址,如果你的分发系统本身和业务服务器不是同一台,这里要填业务服务器的 IP。
参数怎么改:TTL 建议设短一点,比如 120 秒,方便调试时快速生效;生产环境可以调到 600。record_type如果你的业务走 CDN,可能要改成 CNAME,这时default_ip要换成 CNAME 目标地址。
2.3 数据库表结构与域名状态流转
源码的数据库一般围绕三张表转:用户表、域名申请表、订单表。域名申请表里最关键的是status字段,它决定了这个二级域名当前处于什么状态。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int | 主键 |
| user_id | int | 申请用户 |
| subdomain | varchar | 二级域名前缀,如test |
| full_domain | varchar | 完整域名,如test.example.com |
| status | tinyint | 0待支付 1已开通 2已过期 3已释放 |
| dns_record_id | varchar | DNS 服务商返回的记录ID,删除时要用 |
| expire_at | datetime | 到期时间 |
状态流转是:用户提交申请 → status=0 生成订单 → 支付回调 → status=1 并调用 DNS API 写记录 → 到期未续费 → status=2 → 宽限期后 status=3 并删除 DNS 记录。dns_record_id这个字段很多人建表时漏掉,结果删除域名时找不到记录 ID,只能去 DNS 后台手动删,血泪经验。
3. 易支付对接与支付回调:订单状态怎么才能不错乱
3.1 易支付接口的请求与签名
易支付是一套常见的聚合支付接口,对接方式是基于 MD5 签名的表单提交或 API 请求。核心参数包括商户号pid、商户密钥key、订单号out_trade_no、金额money、回调地址notify_url、跳转地址return_url。签名规则通常是把参数按 ASCII 排序后拼接密钥做 MD5。
// 发起支付请求的常见封装 function buildPayUrl($order) { $params = [ 'pid' => $config['pid'], 'type' => 'alipay', // 支付方式 'out_trade_no' => $order['order_no'], // 商户订单号,必须唯一 'notify_url' => $config['notify_url'], // 异步回调,服务器间通信 'return_url' => $config['return_url'], // 同步跳转,用户浏览器 'name' => '二级域名开通', 'money' => $order['amount'], ]; ksort($params); // 按键名升序 $signStr = ''; foreach ($params as $k => $v) { $signStr .= $k . '=' . $v . '&'; } $signStr = rtrim($signStr, '&') . $config['key']; $params['sign'] = md5($signStr); $params['sign_type'] = 'MD5'; return $config['api_url'] . '?' . http_build_query($params); }逻辑说明:ksort排序是签名能否通过的关键,顺序错了签名必错。out_trade_no必须全局唯一,一般用「日期+自增ID+随机数」生成,重复订单号会导致支付平台拒绝。notify_url是异步回调,支付成功后支付平台服务器会 POST 数据到这个地址,这是开通域名的真正触发点,不是return_url。
参数怎么改:type按你开通的支付方式填,常见有alipay、wxpay。money保留两位小数,格式错误有些支付平台会直接拒单。
3.2 异步回调的验签与幂等处理
回调处理是整个系统最容易出 bug 的地方。支付平台会 POST 一堆参数过来,你要做四件事:验签、核对金额、检查订单是否已处理、开通域名。
// notify.php 回调处理核心逻辑 $data = $_POST; $sign = $data['sign']; unset($data['sign'], $data['sign_type']); ksort($data); $signStr = ''; foreach ($data as $k => $v) { $signStr .= $k . '=' . $v . '&'; } $signStr = rtrim($signStr, '&') . $config['key']; if (md5($signStr) !== $sign) { exit('fail'); // 验签失败,必须返回 fail } if ($data['trade_status'] !== 'TRADE_SUCCESS') { exit('fail'); } // 幂等:先查订单状态,已开通直接返回 success $order = getOrderByNo($data['out_trade_no']); if ($order['status'] == 1) { exit('success'); } // 核对金额,防止篡改 if (bccomp($order['amount'], $data['money'], 2) !== 0) { exit('fail'); } // 开通域名:写 DNS 记录 + 更新订单状态 openDomain($order['id']); exit('success');逻辑说明:验签前必须把sign和sign_type从数组里剔除,否则签名串会多出这两个字段导致校验失败。返回success是告诉支付平台「我处理好了,别再重发」,返回fail或什么都不返回,支付平台会按策略重试,重试次数多了可能触发风控。幂等判断是后悔药——支付平台可能因为网络问题重复回调,没有幂等判断就会重复开通、重复扣 DNS 配额。
参数怎么改:trade_status的值不同支付平台不一样,有的用TRADE_SUCCESS,有的用1,以你对接的易支付文档为准。金额核对用bccomp而不是==,浮点数直接比较会翻车。
3.3 开通域名的原子性处理
开通动作包含「写 DNS 记录」和「更新订单状态」两步,这两步必须尽量保证原子性。常见做法是先把订单状态改成「开通中」,再写 DNS,写成功后再改成「已开通」。如果写 DNS 失败,订单停在「开通中」,方便人工介入或定时任务重试。
function openDomain($orderId) { updateOrderStatus($orderId, 'opening'); // 先标记开通中 $order = getOrder($orderId); try { $recordId = dnsAddRecord($order['subdomain'], $order['ip']); saveDnsRecordId($orderId, $recordId); updateOrderStatus($orderId, 'opened'); } catch (Exception $e) { logError('开通失败:' . $e->getMessage()); // 保持 opening 状态,等定时任务重试 } }逻辑说明:先标记「开通中」是为了防止回调重试时重复写 DNS。dnsAddRecord返回的记录 ID 必须存下来,删除域名时要用。异常不抛出而是记日志,是为了让回调能正常返回,避免支付平台一直重试。
4. 部署与 Nginx 配置:主域名和二级域名怎么共存
4.1 环境准备与源码部署
源码是 PHP 的,常见运行环境是 Nginx + PHP-FPM + MySQL。部署步骤不复杂,但有几个点容易漏。
# 1. 拉取源码到站点目录 cd /www/wwwroot unzip domain-dist.zip -d domain-dist # 2. 设置运行目录权限 chown -R www:www domain-dist chmod -R 755 domain-dist chmod -R 777 domain-dist/runtime # 框架运行时目录 # 3. 导入数据库 mysql -u root -p your_db < domain-dist/install.sql # 4. 修改数据库配置 vim domain-dist/config/database.php逻辑说明:runtime目录给 777 是因为 PHP 框架要写缓存和日志,权限不够会报「无法写入」的白屏。数据库配置文件里填 host、user、password、dbname 四项,prefix表前缀如果和 install.sql 里不一致要改。
参数怎么改:PHP 版本建议 7.4 或 8.0,太老的 5.6 可能跑不起来某些语法。MySQL 建议 5.7 以上。
4.2 Nginx 主域名与二级域名的 server 配置
这是热词里问得最多的问题:主域名和二级域名怎么在 Nginx 里共存。核心是用泛域名解析配合 server_name 通配符。
# 主域名站点 server { listen 80; server_name example.com www.example.com; root /www/wwwroot/domain-dist/public; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass unix:/tmp/php-cgi.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } # 二级域名泛解析站点 server { listen 80; server_name *.example.com; root /www/wwwroot/user-sites/$host; # 按域名分目录 index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass unix:/tmp/php-cgi.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }逻辑说明:server_name *.example.com匹配所有二级域名,Nginx 会优先匹配精确域名,所以主域名走第一个 server,二级域名走第二个。$host变量是当前请求的域名,用它做目录可以实现「每个二级域名一个独立目录」。如果你的分发系统只是做跳转,第二个 server 可以改成return 301到统一页面。
参数怎么改:fastcgi_pass的 socket 路径要和 PHP-FPM 配置一致,宝塔面板一般是/tmp/php-cgi.sock,自己编译的可能是127.0.0.1:9000。root路径按实际部署目录改。
4.3 泛解析与逐条解析的配合
如果你的系统是逐条解析,那 Nginx 的泛解析 server 只是兜底,真正生效的是 DNS 里逐条写的记录。这里有个坑:泛解析记录*.example.com和逐条记录test.example.com同时存在时,DNS 查询会优先返回精确记录,所以逐条解析能覆盖泛解析。但如果你先加了泛解析,用户申请时系统检测「域名是否可用」会误判——因为泛解析让所有前缀都能解析。常见做法是可用性检测只查数据库,不查 DNS。
5. 避坑与常见问题排查:那些让你怀疑人生的报错
5.1 支付成功但域名没开通
现象:用户付了钱,订单显示已支付,但二级域名访问不了,DNS 里也没有记录。
原因:回调没收到,或者回调收到了但验签失败、金额核对失败,导致openDomain没执行。也可能是notify_url填的是内网地址,支付平台服务器访问不到。
解决:先看支付平台的回调日志,确认有没有发起回调、返回什么。再看系统日志里有没有验签失败的记录。notify_url必须是公网可访问的完整 URL,不能带参数,不能是 localhost。验签失败重点检查密钥有没有多余空格、参数排序对不对。
5.2 DNS 记录写入报 403 或 record already exists
现象:调用 DNS API 时报权限错误,或者提示记录已存在。
原因:403 一般是 API Token 权限不够或 zone_id 填错。record already exists 是同一个前缀已经有一条记录,可能是上次开通失败残留的。
解决:Cloudflare 的 Token 要明确给Zone.DNS.Edit权限,Zone 资源要选中主域名。zone_id 在域名概览页右下角,不是域名本身。遇到记录已存在,先调 API 查一下该前缀的记录,存在就先删再建,或者直接复用。
5.3 二级域名解析生效慢
现象:DNS 记录明明写进去了,本地就是打不开,换网络又能打开。
原因:本地 DNS 缓存没刷新,或者 TTL 设得太长。
解决:调试阶段 TTL 设 120 秒,用dig test.example.com或在线 DNS 查询工具确认记录是否已生效。本地可以ipconfig /flushdns(Windows)或sudo systemd-resolve --flush-caches(Linux)刷新缓存。如果用了 CDN,还要等 CDN 回源配置生效。
5.4 订单号重复导致支付失败
现象:用户下单时报「订单号已存在」或支付平台拒单。
原因:订单号生成规则有并发问题,同一秒内多个请求生成了相同订单号。
解决:订单号里加用户 ID 和随机数,比如date('YmdHis') . $userId . mt_rand(1000,9999)。数据库对order_no字段加唯一索引,插入失败就重新生成。
5.5 删除域名后 DNS 记录残留
现象:用户域名到期释放了,数据库里状态也改了,但 DNS 里记录还在,域名还能访问。
原因:删除时没拿到dns_record_id,或者删除 API 调用失败没重试。
解决:建表时务必存dns_record_id。删除逻辑做成「先标记待删除,定时任务扫描并调 API 删除,成功后再改状态」,避免一次失败就永久残留。
6. 进阶:把分发系统做成可运营的产品
源码跑通只是起点,真要运营还得补几块。第一块是域名前缀的敏感词过滤,用户申请admin、api、mail这类前缀要拦截,否则容易和你的业务冲突,也容易被滥用。常见做法是维护一个保留词库,申请时做前缀匹配。
第二块是到期提醒和自动续费。expire_at字段有了,加一个定时任务,到期前 7 天、3 天、1 天分别发提醒,到期当天没续费就改状态并删记录。定时任务用 crontab 跑,别用用户访问触发,不可靠。
# crontab 示例:每天凌晨检查到期域名 0 2 * * * /usr/bin/php /www/wwwroot/domain-dist/think expire:check >> /var/log/domain_expire.log 2>&1第三块是解析记录的健康检查。写进去的记录不一定真的能访问,可以加一个定时任务,对已开通的域名做 HTTP 探测,连续失败就告警。这个功能很多分发系统没有,但用户投诉「域名打不开」时,你能第一时间知道是 DNS 问题还是业务服务器问题。
验证方法上,我一般会走一遍完整链路:注册用户 → 申请域名 → 下单 → 用测试支付环境付款 → 看回调日志 → 查 DNS 记录 →dig验证 → 浏览器访问。每一步都留日志,出问题能定位到具体环节。从那以后我每次部署这类系统,都强制先跑一遍全链路测试再开放注册,不然用户一多,问题全堆在一起根本查不过来。希望帮到你。
本文还有配套的精品资源,点击获取