简介:海风域名查询工具 v1.0 是一套基于 PHP 开发的域名信息查询系统源码,面向需要在 Linux 主机上搭建域名查询服务的站长、运维人员及 PHP 学习者。该工具可帮助用户快速部署一个可自主运营的域名查询站点,适合具备基础 PHP 环境配置能力的中级开发者使用。压缩包为 rar 格式,整体约 372KB,包内以 PHP 源码文件与 SQL 数据文件为主,其中 PHP 文件承担查询逻辑与后台管理功能,SQL 文件用于初始化数据库结构,另含站点地图等辅助文件,安装时需将相关文件权限设为 777 并运行安装入口完成部署,后台默认账号密码均为 admin。目前已有 118 人学习下载。通过这份源码,读者可获得一套可直接运行的域名查询工具,了解其前后台目录组织方式、数据库初始化流程与权限配置要点,并在此基础上进行二次开发或功能扩展,适合作为 PHP 建站与查询类项目练手的参考实例。
1. 海风域名查询工具 v1.0:一个能塞进任何 PHP 站点的轻量查询模块
手上有个老 PHP 项目,后台需要加个域名查询入口,运营想批量看一批域名的注册状态、到期时间和 NS 记录。找了一圈现成方案,要么是独立部署的服务,要么依赖外部接口还要配密钥,塞进现有代码里都嫌重。后来翻到这个「海风域名查询工具 v1.0」,单文件入口、纯 PHP 实现、不依赖数据库,扔进站点目录改两行配置就能跑。它适合两类人:一类是手里有 PHP 环境、想快速给后台加个域名查询能力的开发者;另一类是想拿它当练手项目,看一个完整的「输入校验 → 查询调度 → 结果渲染」链路在 PHP 里怎么组织。这篇就把这份 php 源码拆开,从目录结构讲到二次开发怎么改查询源。
2. 项目结构与技术选型:为什么是纯 PHP 单入口
2.1 目录骨架与文件职责
拿到源码包,第一件事是看目录。这个项目的结构非常扁平,没有引入 Composer 依赖,也没有框架痕迹,典型的「下载即用」型工具。常见做法是长这样:
haifeng-domain/ ├── index.php # 唯一入口,处理请求分发与页面渲染 ├── config.php # 配置文件,查询源、超时、缓存开关 ├── lib/ │ ├── Query.php # 查询调度核心类 │ ├── Parser.php # 原始响应解析,提取注册信息 │ └── Cache.php # 文件缓存实现 ├── assets/ │ ├── style.css # 页面样式 │ └── app.js # 前端交互,提交查询、渲染结果 └── cache/ # 缓存目录,需可写index.php是唯一对外暴露的入口,所有请求都从这里进。lib/下三个类各管一摊:Query负责决定用哪个查询源、发请求、拿原始数据;Parser把原始数据转成统一结构;Cache做一层文件缓存,避免同一个域名短时间内重复查询。config.php把可变部分抽出来,改配置不用动业务代码。这种拆法的好处是查询源可以换,解析逻辑可以单独测,缓存可以关掉,互不影响。
2.2 技术选型背后的取舍
为什么不用框架?因为这个工具的使用场景就是「塞进已有项目」。如果引入 Laravel 或 ThinkPHP,光依赖安装就得折腾半天,还跟宿主项目的依赖可能冲突。纯 PHP 加 spl_autoload_register 手动注册类文件,是最低摩擦的方案。
为什么用文件缓存而不是 Redis?因为目标用户的环境里不一定有 Redis。文件缓存虽然并发性能一般,但对「后台偶尔查几个域名」这个场景完全够用,而且零配置。Cache.php里用域名做 key 的 md5,存成 json 文件,带过期时间戳,读的时候判断一下是否过期。
查询源这块,项目默认走的是公开的 WHOIS 查询和 DNS 记录查询两条路。WHOIS 走 socket 连 43 端口,DNS 走 PHP 内置的 dns_get_record。这两条路都不需要 API Key,但稳定性看网络环境,所以config.php里留了超时和重试次数的配置项。
提示:如果你的服务器禁用了 fsockopen 或 allow_url_fopen,WHOIS 查询会直接失败,部署前先确认这两个函数没被 disable_functions 拦掉。
3. 核心查询链路拆解:从表单提交到结果返回
3.1 入口分发与输入校验
index.php的逻辑很直白:判断请求方法,GET 渲染页面,POST 处理查询。关键在输入校验这一步,域名格式不校验的话,后面拼 WHOIS 查询命令容易出问题。
// index.php 入口分发片段 require_once __DIR__ . '/config.php'; // 手动注册类文件加载 spl_autoload_register(function ($class) { $file = __DIR__ . '/lib/' . $class . '.php'; if (is_file($file)) { require_once $file; } }); $domain = isset($_POST['domain']) ? trim($_POST['domain']) : ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { // 域名格式校验:只允许字母数字连字符和点,长度不超过 253 if (!preg_match('/^([a-z0-9-]+\.)+[a-z]{2,}$/i', $domain) || strlen($domain) > 253) { $error = '域名格式不正确'; } else { $query = new Query($config); $result = $query->lookup($domain); } }这段代码里,spl_autoload_register是替代 Composer 的轻量方案,按类名去lib/目录找同名文件。域名校验用了一条正则,限制字符集和总长度,避免把奇怪的东西传给 WHOIS 服务器。$config从config.php里来,是个关联数组,包含超时、缓存开关、查询源优先级这些。
参数说明:$domain是用户提交的原始字符串,trim 去掉首尾空格;正则里[a-z]{2,}限制顶级域名至少两位字母,实际使用中如果遇到 punycode 域名(xn-- 开头)需要额外放行,这个后面二次开发章节会讲。
3.2 Query 类的查询调度逻辑
Query.php是整个工具的核心,它决定先查缓存还是直接查、用哪个查询源、失败了怎么降级。
// lib/Query.php 查询调度核心 class Query { private $config; private $cache; public function __construct(array $config) { $this->config = $config; $this->cache = new Cache($config['cache_dir'], $config['cache_ttl']); } public function lookup(string $domain): array { // 先查缓存,命中直接返回 if ($this->config['cache_enable']) { $cached = $this->cache->get($domain); if ($cached !== null) { $cached['from_cache'] = true; return $cached; } } $raw = $this->fetchWhois($domain); if ($raw === false) { // WHOIS 失败时降级到 DNS 查询 $raw = $this->fetchDns($domain); } $parser = new Parser(); $result = $parser->parse($domain, $raw); if ($this->config['cache_enable'] && !empty($result)) { $this->cache->set($domain, $result); } return $result; } }逻辑分四步:缓存命中判断、WHOIS 查询、失败降级到 DNS、解析后写缓存。fetchWhois方法用fsockopen连 WHOIS 服务器的 43 端口,发送域名加换行,读取响应。fetchDns用dns_get_record拿 A 记录和 NS 记录。降级策略是「WHOIS 拿不到就至少给个 DNS 结果」,保证页面不会空着。
参数说明:cache_ttl控制缓存有效期,默认 3600 秒;cache_dir必须可写,否则Cache类会抛异常。from_cache标记用来在前端提示用户「这是缓存结果」。
3.3 Parser 类的响应解析
WHOIS 返回的是一大段文本,不同后缀的格式还不一样。Parser.php的职责就是从这段文本里把注册商、创建时间、到期时间、域名状态这几个字段抠出来。
// lib/Parser.php 关键字段提取 class Parser { public function parse(string $domain, $raw): array { $result = [ 'domain' => $domain, 'registrar' => '', 'created' => '', 'expires' => '', 'status' => [], 'nameservers' => [], ]; if (is_array($raw)) { // DNS 降级结果 $result['nameservers'] = $raw['ns'] ?? []; return $result; } // WHOIS 文本按行解析,匹配常见字段前缀 $lines = explode("\n", $raw); foreach ($lines as $line) { $line = trim($line); if (stripos($line, 'Registrar:') === 0) { $result['registrar'] = trim(substr($line, 10)); } elseif (stripos($line, 'Creation Date:') === 0) { $result['created'] = trim(substr($line, 14)); } elseif (stripos($line, 'Registry Expiry Date:') === 0) { $result['expires'] = trim(substr($line, 20)); } elseif (stripos($line, 'Domain Status:') === 0) { $result['status'][] = trim(substr($line, 14)); } } return $result; } }解析用的是最朴素的前缀匹配,没有上正则表达式。原因是 WHOIS 文本格式虽然不统一,但主流后缀的字段名就那么几个,前缀匹配足够覆盖大部分情况,而且比正则好调试。stripos不区分大小写,因为有些服务器返回的是小写字段名。
参数说明:$raw可能是字符串(WHOIS 响应)或数组(DNS 降级结果),所以先判断类型。status和nameservers是数组字段,因为一个域名可能有多个状态和多个 NS。
注意:WHOIS 查询有频率限制,同一个 IP 短时间内查太多会被临时封禁。
config.php里的cache_ttl别设太短,生产环境建议不低于 1800 秒。
4. 部署与运行:从上传到跑通的最小步骤
4.1 环境要求与上传
这个工具对环境的要求很低:PHP 7.0 以上,开启fsockopen和dns_get_record可用,cache/目录可写。不需要 MySQL,不需要 Composer,不需要 Web 服务器特殊配置。
部署步骤:
- 把整个目录上传到 Web 根目录下的某个子目录,比如
/tools/domain/。 - 给
cache/目录写权限,Linux 下chmod 755 cache或chmod 777 cache(看运行用户)。 - 浏览器访问
http://你的域名/tools/domain/index.php。 - 在输入框里填一个域名,点查询,看是否返回结果。
如果页面能打开但查询一直转圈,大概率是fsockopen连不上 WHOIS 服务器。先在服务器上telnet whois.verisign-grs.com 43试一下通不通。如果服务器在国内且没有外网直连,WHOIS 查询基本会超时,这时候要么换查询源,要么把cache_enable打开并预热缓存。
4.2 配置文件逐项说明
config.php返回一个数组,每一项都影响运行行为:
// config.php 配置项 return [ 'cache_enable' => true, // 是否开启缓存 'cache_dir' => __DIR__ . '/cache', // 缓存目录 'cache_ttl' => 3600, // 缓存有效期(秒) 'whois_timeout'=> 5, // WHOIS 连接超时(秒) 'whois_server' => 'whois.verisign-grs.com', // 默认 WHOIS 服务器 'dns_enable' => true, // 是否允许降级到 DNS 查询 ];whois_server这个配置项只对.com和.net生效,其他后缀需要走 IANA 的 referral 机制。项目里Query::fetchWhois的做法是先连whois.iana.org问这个后缀的 WHOIS 服务器是谁,再连过去查。这个过程多一次网络往返,所以whois_timeout别设太短,5 秒是保守值。
dns_enable关掉之后,WHOIS 失败就直接返回空结果,不会降级。如果你的场景只关心注册信息,不关心 DNS,可以关掉省一次查询。
4.3 前端交互与结果渲染
assets/app.js负责把表单提交改成异步,避免整页刷新。核心逻辑是监听表单 submit,阻止默认行为,用 fetch 发 POST,拿到 JSON 后动态渲染结果表格。
// assets/app.js 异步查询 document.getElementById('query-form').addEventListener('submit', function (e) { e.preventDefault(); const domain = document.getElementById('domain-input').value.trim(); if (!domain) return; const resultBox = document.getElementById('result-box'); resultBox.innerHTML = '<p>查询中...</p>'; fetch('index.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'domain=' + encodeURIComponent(domain) + '&ajax=1' }) .then(res => res.json()) .then(data => { // 根据返回字段渲染表格 let html = '<table>'; html += '<tr><td>注册商</td><td>' + (data.registrar || '-') + '</td></tr>'; html += '<tr><td>创建时间</td><td>' + (data.created || '-') + '</td></tr>'; html += '<tr><td>到期时间</td><td>' + (data.expires || '-') + '</td></tr>'; html += '</table>'; resultBox.innerHTML = html; }) .catch(() => { resultBox.innerHTML = '<p>查询失败,请稍后重试</p>'; }); });这里有个细节:POST 请求里带了ajax=1参数,index.php判断到这个参数时返回 JSON 而不是渲染 HTML。这样同一个入口既能服务页面请求也能服务异步请求,不用额外开一个 api.php。
参数说明:encodeURIComponent对域名做 URL 编码,防止特殊字符截断请求体。data.registrar这些字段名跟Parser返回的数组 key 一一对应,改解析逻辑时前端也要同步改。
5. 避坑与常见问题:查询失败、缓存异常、格式兼容
5.1 查询一直超时或返回空
现象:输入域名后页面卡住,或者返回的结果里所有字段都是空的。
原因:fsockopen连不上 WHOIS 服务器,或者连上了但服务器返回了错误信息(比如「No match for domain」表示域名未注册)。另外,如果服务器开了防火墙只允许 80 和 443 出站,43 端口会被拦。
解决:先在服务器上用php -r "var_dump(fsockopen('whois.verisign-grs.com', 43, $e, $s, 5));"测一下能不能连。连不上就换查询源,或者把dns_enable打开,至少能拿到 DNS 记录。如果连上了但返回空,检查Parser里的字段前缀是否匹配当前后缀的 WHOIS 格式,不同后缀的字段名可能不一样。
5.2 缓存目录不可写导致查询报错
现象:页面直接报 500,或者提示「缓存写入失败」。
原因:cache/目录权限不对,PHP 运行用户没有写权限。常见于 Linux 下用 root 上传文件,目录属主是 root,而 PHP-FPM 跑在 www-data 用户下。
解决:chown -R www-data:www-data cache/或者chmod 777 cache/。如果不想给写权限,把cache_enable设为 false,工具会跳过缓存直接查询,但查询频率会变高。
5.3 特殊后缀域名解析失败
现象:.cn、.io、.co这些后缀查不出结果,或者结果字段错位。
原因:不同后缀的 WHOIS 服务器返回格式差异很大。.cn的 WHOIS 服务器是whois.cnnic.cn,字段名跟.com完全不同。项目默认的Parser只适配了主流 gTLD 的格式。
解决:在Parser::parse里加后缀判断,针对不同后缀走不同的字段映射。或者更省事的做法是只提取「冒号分隔」的通用字段,不依赖具体字段名。二次开发章节会展开讲这个改法。
5.4 并发查询触发频率限制
现象:批量查询时前面几个成功,后面全部失败。
原因:WHOIS 服务器对同一 IP 有查询频率限制,短时间发太多请求会被临时封禁。
解决:在Query::lookup里加一个简单的 sleep,比如每次查询前usleep(500000)停半秒。或者把批量查询改成队列模式,前端一个一个提交,后端串行处理。缓存也能缓解这个问题,同一个域名第二次查就走缓存了。
提示:如果部署在共享主机上,你的 IP 可能已经被其他用户「用脏」了,WHOIS 查询失败不一定是代码问题,换个环境试试。
6. 二次开发:换查询源、加后缀适配、接自己的缓存
6.1 把查询源换成 HTTP API
默认的 WHOIS socket 查询在有些网络环境下不稳定,改成 HTTP API 是最常见的改造方向。假设你有一个返回 JSON 的域名查询接口,改Query::fetchWhois就行:
// lib/Query.php 改用 HTTP API 查询 private function fetchWhois(string $domain) { $url = 'https://api.example.com/whois?domain=' . urlencode($domain); $ctx = stream_context_create([ 'http' => [ 'timeout' => $this->config['whois_timeout'], 'header' => 'Accept: application/json', ] ]); $resp = @file_get_contents($url, false, $ctx); if ($resp === false) { return false; } $data = json_decode($resp, true); // 直接把 API 返回的字段映射成 Parser 期望的格式 return $data ?: false; }改完之后Parser也要跟着调,因为 API 返回的是结构化数据,不需要再按行解析。我一般会在Parser里加一个parseApi方法,判断$raw是数组且带特定 key 时走 API 解析分支。这样 WHOIS 和 API 两种源可以共存,配置里加个query_source开关切换。
6.2 加后缀适配层
针对.cn、.io这些格式不同的后缀,最干净的做法是在Parser里加一个后缀映射表:
// lib/Parser.php 后缀适配 private $fieldMap = [ 'default' => [ 'registrar' => 'Registrar:', 'created' => 'Creation Date:', 'expires' => 'Registry Expiry Date:', ], 'cn' => [ 'registrar' => 'Registrar:', 'created' => 'Registration Time:', 'expires' => 'Expiration Time:', ], ];解析时先取域名后缀,查表拿到对应的字段前缀,再按前缀匹配。这样加新后缀只需要在表里加一项,不用改解析逻辑。改完之后拿几个不同后缀的域名各测一遍,确认字段都能对上。
6.3 接 Redis 或数据库缓存
文件缓存在并发高的时候会有读写冲突,换成 Redis 很简单,把Cache类的方法体换掉就行,接口保持不变:
// lib/Cache.php 换成 Redis 实现 class Cache { private $redis; private $ttl; public function __construct(string $dir, int $ttl) { $this->redis = new Redis(); $this->redis->connect('127.0.0.1', 6379); $this->ttl = $ttl; } public function get(string $domain) { $val = $this->redis->get('whois:' . md5($domain)); return $val ? json_decode($val, true) : null; } public function set(string $domain, array $data) { $this->redis->setex('whois:' . md5($domain), $this->ttl, json_encode($data)); } }接口不变的好处是Query类完全不用动,只换Cache的实现。这也是当初把缓存单独拆一个类的原因。改完之后记得在config.php里把cache_dir换成 Redis 连接信息,或者干脆加一个cache_driver配置项。
6.4 验证改造是否生效
改完代码别急着上生产,按这个顺序验一遍:先单域名查一个已知注册的域名,确认字段都有值;再查一个未注册的域名,确认不会报错而是返回「未注册」提示;然后连续查同一个域名两次,第二次应该走缓存(看返回里的from_cache标记);最后拿几个不同后缀的域名各查一次,确认后缀适配表生效。
我自己的习惯是改完查询源之后,先在本机用 PHP 内置服务器php -S localhost:8000跑一遍,确认没有语法错误和致命异常,再传到测试环境。从那以后我每次改Parser的字段映射,都强制拿.com、.cn、.io三个后缀各跑一遍回归,不然总有一个后缀的字段会漏掉。希望帮到你。
本文还有配套的精品资源,点击获取