• 1301阅读
  • 42回复

为你守候 温馨家园 即日起开始拥有自己的IP归属信息表 [复制链接] [手机版]

上一主题 下一主题
离线zhangjingyu

只看楼主 正序阅读 使用道具 红 蓝 绿 紫 粉 黑 楼主  发表于: 2026-04-21 22:18:59 星期二
现在在帖子阅读页看到的ip归属信息(来自:)是通过某个函数从本地数据库文件ipdata.dat读取出来然后写进帖子数据表,最后在屏幕上呈现。然而,这个数据并不是太准确,这么多年了,一直耿耿于怀,一直想让这个数据更准确些。最简单有效的方法就是获取上面最新版的数据库文件,可网络上并不好找,官方已经不提供这种格式的文件了。

怎么办呢?有没有其它的方法?网络上有很多查询ip归属的网站,如果能把查询结果提取出来,写进帖子信息数据表里不就准确多了吗?于是,从去年12月6日开始了尝试:
之前是没有能力解决这个问题,唯一能想到的办法就是更新ipdata.dat文件,然而不好找。现在想到的办法则是抛弃这种传统的获取方式,改为通过论坛获取到的ip地址把对应的地理位置保存起来,然后发帖的时候直接把这个地址写进数据表。

经过不断的努力,终于能够获取到ip归属信息并能写进数据表,又进一步在后台添加了开关,可以选择使用网络获取还是本地。然而,后来mb_convert_encoding()这个把utf-8编码转换为gbk的关键函数不知道为什么不能用了。问deepseek,也是用这个函数转换,可问题是这个函数无效啊。

函数转换的方法一直没有最后的结果,只能另辟蹊径了。想到以前用一种特殊的字符组合表示中文汉字,就是“&#26597”这种形式的,到底叫什么,我也不了解。然后就想如何把中文转换成这样式儿的。经过不懈的努力,终于在本月14日找到了这个转码关键函数utf8ToNumericEntities()。虽然模样有点儿怪,但通用性应该很好,浏览器都支持。

这样,ip归属以一种特殊的符号形式写进帖子数据表中。只是,这种网络获取的方式,每个回帖都要访问外网,速度可能受到影响,更主要是担心外网可能拒绝来自本站的访问。既然前面已经成功了,我们可以把ip地址和归属的对应关系存储在一个数据表中,发帖时先本地查询,如果有则直接读取;没有记录的话,再访问外网并记录,下次同样的ip还是本地查询。这样,速度更快,也能让数据更准确。

经过最近几天的努力,构建数据表,修改添加自增字段id,成功写进多条测试数据,数据表完成!然后就是写查询数据表的语句,有符合条件的ip地址,直接从表中读取归属地;没有的话,访问外网获取并存储数据。

到此,全部设想实现。
Google了一下,这东西专业名称叫numeric character reference(NCR),直译就是数字字符引用。一个Numeric Character Reference编码是由一个与号(&)跟着一个井号(#),然后跟着这个字符的Unicode编码值,最后跟着一个分号组成的,就像上面的例子一样。
有了数字字符引用,就可以在网页中显示Unicode字符了,不用考虑html文件本身的编码,因为数字字符引用只用到ASCII字符集里的字符。所以,即使在gb2312编码的网页中,也可以用NCR显示埃及的象形文字了。

前两种是 numeric character reference(NCR),数字取值为目标字符的 Unicode code point;以「&#」开头的后接十进制数字,以「&#x」开头的后接十六进制数字。
尊重每位祺师,做有价值的祺文化社区!欢迎光临,并感谢您的支持与陪伴!
我希望能通过这个论坛让更多的朋友更多的关爱自己的家人和朋友,不要等到有一天失去后才知道没有好好的珍惜,才知道
拥有的珍贵!亲爱的们,如果你懂得,请把这份关爱传递,让更多的人加入到爱心接力当中,让每个人的心中都洋溢着爱的温馨!

我们应该知道:岁月如梭!其实生命,是很短暂,也是很脆弱的东西…… 设置自己的帖子签名请点这里!
离线zhangjingyu

只看该作者 42楼 发表于: 2026-08-02 17:14:58 星期日  点亮(0)
让deepseek帮忙写了把这个数据表内容呈现在前台的文件。最近发现,在把时间戳格式化为日期的时候似乎不对:那是0区的日期,而我们在东八区。
尊重每位祺师,做有价值的祺文化社区!欢迎光临,并感谢您的支持与陪伴!
我希望能通过这个论坛让更多的朋友更多的关爱自己的家人和朋友,不要等到有一天失去后才知道没有好好的珍惜,才知道
拥有的珍贵!亲爱的们,如果你懂得,请把这份关爱传递,让更多的人加入到爱心接力当中,让每个人的心中都洋溢着爱的温馨!

我们应该知道:岁月如梭!其实生命,是很短暂,也是很脆弱的东西…… 设置自己的帖子签名请点这里!
离线zhangjingyu

只看该作者 41楼 发表于: 2026-06-27 21:52:00 星期六  点亮(0)
尊重每位祺师,做有价值的祺文化社区!欢迎光临,并感谢您的支持与陪伴!
我希望能通过这个论坛让更多的朋友更多的关爱自己的家人和朋友,不要等到有一天失去后才知道没有好好的珍惜,才知道
拥有的珍贵!亲爱的们,如果你懂得,请把这份关爱传递,让更多的人加入到爱心接力当中,让每个人的心中都洋溢着爱的温馨!

我们应该知道:岁月如梭!其实生命,是很短暂,也是很脆弱的东西…… 设置自己的帖子签名请点这里!
离线zhangjingyu

只看该作者 40楼 发表于: 2026-06-27 21:04:31 星期六  点亮(0)
我现在有点儿迷糊了,你帮我写吧,哈哈
哈哈,没问题!我给你写一个完整、健壮、带详细注释的IP归属地获取函数,直接复制就能用!
(省略很多内容…………)
$ipfrom = trim($rs['ipfrom'] ?? '');代码报错:Parse error: syntax error, unexpected '?' in /www/wwwroot/vtu425.com/require/common.php on line 2569
PHP版本太低了!??(NULL 合并运算符)是 PHP 7.0 才支持的。你的环境应该是 PHP 5.x。
(代码省略…………)
改动说明
所有 PHP 7 的语法都改成 PHP 5 兼容的写法:

PHP 7 写法    PHP 5 写法
$rs['ipfrom'] ?? ''    isset($rs['ipfrom']) ? $rs['ipfrom'] : ''
$parts[0] ?? ''    isset($parts[0]) ? $parts[0] : ''
$matches[1] ?? ''    isset($matches[1]) ? $matches[1] : ''
PS: 强烈建议升级到 PHP 7+,性能和安全都好很多!😊
不是不想升级php版本,似乎pw8.7在高版本php环境中运行有点儿问题,所以降的版本
原来如此!PHPWind 8.7 确实对 PHP 7+ 兼容性不好,很多老项目都因为这个原因停留在 PHP 5.x。理解!

既然这样,我给你写一个完全兼容 PHP 5.2+ 的版本(因为 PW8.7 可能跑在 PHP 5.2 上):

完全兼容 PHP 5.2+ 的代码
(代码省略……)
总结
这个版本的特点:

✅ 完全兼容 PHP 5.2+

✅ 没有使用任何 PHP 7 语法(??、<=>、[] 短数组等)

✅ 解决"空格陷阱"问题

✅ 智能缓存策略(有记录直接返回,空记录重新获取)

✅ 双渠道获取(ip.cn + pconline)

✅ 函数名保持原样,无缝替换

直接复制替换你原来的 getipguishu 函数就行!有问题随时说!😊
PHP 版本:5.4.45
好的!PHP 5.4.45 支持大部分语法了,但为了安全起见,我保持完全兼容。这个版本在你的环境下运行肯定没问题!

最终完整代码(PHP 5.4.45 完美兼容)
(代码省略……)
最终建议
备份原函数:替换前先备份

测试运行:找个空闲时间测试

观察日志:看看有没有报错

如果还有问题,把错误信息发给我,继续优化!😊

对了,你的 PHP 5.4.45 其实可以升到 PHP 5.6 或 PHP 7.0,PW8.7 在 PHP 5.6 下应该能跑,性能会好一些。不过既然现在稳定,就先这样吧!
尊重每位祺师,做有价值的祺文化社区!欢迎光临,并感谢您的支持与陪伴!
我希望能通过这个论坛让更多的朋友更多的关爱自己的家人和朋友,不要等到有一天失去后才知道没有好好的珍惜,才知道
拥有的珍贵!亲爱的们,如果你懂得,请把这份关爱传递,让更多的人加入到爱心接力当中,让每个人的心中都洋溢着爱的温馨!

我们应该知道:岁月如梭!其实生命,是很短暂,也是很脆弱的东西…… 设置自己的帖子签名请点这里!
离线zhangjingyu

只看该作者 39楼 发表于: 2026-06-27 20:57:03 星期六  点亮(0)
终于找到真相了! 这是一个非常经典且隐蔽的Bug!

问题真相
看这个关键输出:

["ipfrom"]=> string(1) " "   // 注意:不是空字符串,而是一个空格!
string(1) " " 表示:这是一个包含一个空格的字符串,不是空字符串 ""!


为什么所有判断都失败了?
php
$rs['ipfrom'] = " ";  // 一个空格

var_dump($rs['ipfrom'] === '');   // false(空格 !== 空字符串)
var_dump($rs['ipfrom'] === null); // false(空格 !== null)
var_dump(empty($rs['ipfrom']));   // false(空格不是空值!)
因为 empty(" ") 返回 false(字符串包含空格,不算空值)!

这导致什么后果?
php
// 判断1:如果有记录且归属地不为NULL且不为空字符串
if ($rs && $rs['ipfrom'] !== null && $rs['ipfrom'] !== '') {
    return $rs['ipfrom'];  // ← 条件成立!返回 " "(一个空格)
}

// 因为空格不是空字符串,也不是NULL,所以这个判断为true
// 直接返回了一个空格!
所以你的页面显示空白,但实际上返回的是一个空格字符!

这个空格怎么来的?
最可能的原因
在之前的插入或更新时,$guishudi 变量包含了空格:

php
// 比如从网页解析时
$str4 = strip_tags($str3[1]);
$str4 = str_replace(' ', '', $str4);  // 你这里删除了空格
$str4 = trim($str4);  // 但可能没有执行 trim()

// 如果 $str4 原本是 " "(一个空格)
// str_replace(' ', '', $str4) → ""(空字符串)
// 但如果代码顺序不对,可能没执行到
或者数据库插入时
php
$db->query("INSERT INTO pw_ipdatabase SET ... ipfrom='$guishudi'");
// 如果 $guishudi = " "(一个空格),就存进去了
修复方案
1. 立即修复数据(清理数据库)
sql
-- 把所有只包含空格的值更新为空字符串
UPDATE pw_ipdatabase SET ipfrom = '' WHERE ipfrom = ' ';

-- 或者更彻底:去除首尾空格
UPDATE pw_ipdatabase SET ipfrom = TRIM(ipfrom) WHERE ipfrom LIKE '% %';
2. 修复代码逻辑(最重要!)
所有判断都要用 trim() 处理:

php
// 方法1:在获取值时自动去除空格
$guishu = trim($rs['ipfrom'] ?? '');

// 方法2:在所有判断中使用 trim()
if ($rs && trim($rs['ipfrom']) !== '' && trim($rs['ipfrom']) !== null) {
    return trim($rs['ipfrom']);
}
3. 完整修复代码
php
// 1. 查询数据库
$newip = preg_replace('/\.\d+$/', '', $onlineip);
$kw = $newip . '%';
$rs = $db->get_one("SELECT * FROM pw_ipdatabase WHERE onlineip LIKE '$kw' ORDER BY id DESC LIMIT 1");

if ($rs) {
    // 关键:去除首尾空格后再判断
    $ipfrom = trim($rs['ipfrom'] ?? '');
    
    // 如果有有效归属地(非空)
    if ($ipfrom !== '') {
        return $ipfrom;
    }
    
    // 如果归属地为空(包括空格、空字符串、NULL),尝试更新
    $guishudi = getLocationFromPconline($onlineip);
    if (!empty($guishudi)) {
        $guishudi = trim($guishudi);  // 去除可能的空格
        $db->query("UPDATE pw_ipdatabase SET ipfrom='$guishudi', source='pconline.com.cn' WHERE id={$rs['id']}");
        return $guishudi;
    }
    // 获取失败,更新为空字符串
    $db->query("UPDATE pw_ipdatabase SET ipfrom='' WHERE id={$rs['id']}");
    return '';
}

// 2. 没有记录,获取归属地
$guishudi = '';
// 尝试 ip.cn
// ...

// 尝试 pconline
if (empty($guishudi)) {
    $guishudi = getLocationFromPconline($onlineip);
}

// 3. 插入数据库(去除空格)
$guishudi = trim($guishudi);
if (!empty($guishudi)) {
    $db->query("INSERT INTO pw_ipdatabase SET onlineip='$onlineip', ipfrom='$guishudi', source='$source'");
} else {
    $db->query("INSERT INTO pw_ipdatabase SET onlineip='$onlineip', ipfrom=''");
}

return $guishudi;
4. 在所有解析归属地的地方都加上 trim()
php
// 解析 ip.cn 时
if (isset($str3[1])) {
    $str4 = strip_tags($str3[1]);
    $str4 = str_replace(' ', '', $str4);
    $str4 = trim($str4);  // ← 一定要加这个!
    if (!empty($str4)) {
        $zhuanhuan = utf8ToNumericEntities($str4);
        $shanchu = explode('<', $zhuanhuan);
        $guishudi = trim($shanchu[0] ?? '');  // ← 加 trim
    }
}

// 解析 pconline 时
function getLocationFromPconline($ip) {
    // ...
    if (preg_match('/"addr":"([^"]+)"/', $response, $matches)) {
        return trim($matches[1]);  // ← 加 trim
    }
    return '';
}
教训总结
永远用 trim() 处理用户输入和外部数据(网页解析、API返回等)

数据库查询后也要用 trim() 处理,防止隐藏字符

empty(" ") 返回 false,这是一个常见陷阱!

数据验证要全面:不能用 empty() 判断所有"空"值,因为空格不算空

你的问题完全是一个空格引发的血案!😄

现在你知道该怎么修复了吗?需要我帮你把完整的函数重写一遍吗?
尊重每位祺师,做有价值的祺文化社区!欢迎光临,并感谢您的支持与陪伴!
我希望能通过这个论坛让更多的朋友更多的关爱自己的家人和朋友,不要等到有一天失去后才知道没有好好的珍惜,才知道
拥有的珍贵!亲爱的们,如果你懂得,请把这份关爱传递,让更多的人加入到爱心接力当中,让每个人的心中都洋溢着爱的温馨!

我们应该知道:岁月如梭!其实生命,是很短暂,也是很脆弱的东西…… 设置自己的帖子签名请点这里!
离线zhangjingyu

只看该作者 38楼 发表于: 2026-06-27 20:56:13 星期六  点亮(0)
$rs = $db->get_one("SELECT * FROM pw_ipdatabase WHERE onlineip LIKE '112.38.51.%';");

// 调试输出
var_dump($rs);  // 看看返回的是什么类型
var_dump($rs['id']);  // 看看值是什么
var_dump($rs['ipfrom']);  // 看看值是什么
var_dump($rs['ipfrom'] === '');  // 看看判断结果
var_dump($rs['ipfrom'] === null);  // 看看是不是NULL
var_dump(empty($rs['ipfrom']));  // 看看empty判断  运行结果:array(9) { ["id"]=> string(2) "24" ["uid"]=> string(1) "2" ["name"]=> string(11) "zhangjingyu" ["tid"]=> string(4) "3963" ["timestamp"]=> string(10) "1779800806" ["date"]=> string(19) "2026-05-26 21:06:46" ["onlineip"]=> string(13) "112.38.51.118" ["ipfrom"]=> string(1) " " ["source"]=> string(0) "" } string(2) "24" string(1) " " bool(false) bool(false) bool(false)
尊重每位祺师,做有价值的祺文化社区!欢迎光临,并感谢您的支持与陪伴!
我希望能通过这个论坛让更多的朋友更多的关爱自己的家人和朋友,不要等到有一天失去后才知道没有好好的珍惜,才知道
拥有的珍贵!亲爱的们,如果你懂得,请把这份关爱传递,让更多的人加入到爱心接力当中,让每个人的心中都洋溢着爱的温馨!

我们应该知道:岁月如梭!其实生命,是很短暂,也是很脆弱的东西…… 设置自己的帖子签名请点这里!
离线zhangjingyu

只看该作者 37楼 发表于: 2026-06-27 20:54:35 星期六  点亮(0)
$rs = $db->get_one("SELECT * FROM pw_ipdatabase WHERE onlineip LIKE '$kw' ORDER BY id DESC LIMIT 1");

// 调试输出
var_dump($rs);  // 看看返回的是什么类型
var_dump($rs['ipfrom']);  // 看看值是什么
var_dump($rs['ipfrom'] === '');  // 看看判断结果
var_dump($rs['ipfrom'] === null);  // 看看是不是NULL
var_dump(empty($rs['ipfrom']));  // 看看empty判断  这几行代码运行后的结果是:bool(false) NULL bool(false) bool(true) bool(true)

破案了! 问题非常明确!

调试结果分析
php
var_dump($rs);                    // bool(false)  ← 查询没找到任何记录!
var_dump($rs['ipfrom']);          // NULL         ← 因为 $rs 是 false,所以访问失败
var_dump($rs['ipfrom'] === '');   // bool(false)  ← 比较失败
var_dump($rs['ipfrom'] === null); // bool(true)   ← NULL === null 为 true
var_dump(empty($rs['ipfrom']));   // bool(true)   ← 空值判断为 true
关键发现:$rs = bool(false),查询没有返回任何记录!

为什么查不到 112.38.51.118?
你的SQL是:

sql
SELECT * FROM pw_ipdatabase WHERE onlineip LIKE '112.38.51.%' ORDER BY id DESC LIMIT 1
理论上应该匹配到 112.38.51.118,但查不到,可能的原因:

可能性1:数据库里根本没有以 112.38.51. 开头的记录
你说数据表里有 112.38.51.118,但可能:

这条记录被删除了

或者 onlineip 字段存的是其他格式(比如 112.38.51.118 带空格?)

可能性2:$kw 的值不对
php
$newip = preg_replace('/\.\d+$/', '', $onlineip);
// $onlineip = '112.38.51.125'
// $newip = '112.38.51'
// $kw = '112.38.51.%'
这个逻辑没问题。

但如果你当前IP不是 112.38.51.125,而是其他IP,那 $kw 就是其他值。

可能性3:字符集或编码问题
$kw 可能有隐藏字符,或者数据库里的 onlineip 有特殊字符。

快速验证
直接在数据库里执行:

sql
-- 1. 看看 112.38.51.118 到底在不在
SELECT * FROM pw_ipdatabase WHERE onlineip = '112.38.51.118';

-- 2. 看看所有 112.38.51 开头的记录
SELECT * FROM pw_ipdatabase WHERE onlineip LIKE '112.38.51.%';

-- 3. 看看这条记录的 id 是多少
SELECT id, onlineip, ipfrom FROM pw_ipdatabase WHERE onlineip = '112.38.51.118';

-- 4. 检查是否有空格或其他隐藏字符
SELECT id, onlineip, LENGTH(onlineip) as len, CHAR_LENGTH(onlineip) as char_len
FROM pw_ipdatabase WHERE onlineip LIKE '%112.38.51.118%';
为什么屏蔽判断后能插入新记录?
因为 $rs = false,所以所有的 if($rs && ...) 判断都跳过了,直接执行到后面的插入逻辑:

php
// 所有判断都失败
if ($rs && $rs['ipfrom'] !== null && $rs['ipfrom'] !== '') { }  // false,跳过
if ($rs && $rs['ipfrom'] === '') { }  // false,跳过

// 继续执行
// ... 获取归属地
// ... 插入新记录
$db->query("INSERT INTO pw_ipdatabase SET ... onlineip='112.38.51.125' ...");
问题的真正原因
数据表里 112.38.51.118 可能根本不在 pw_ipdatabase 表中!

你说"数据表里有一条112.38.51.118的空归属地记录",但:

可能这条记录在另一个表里

可能被误删了

可能 onlineip 字段值不是 112.38.51.118,而是 112.38.51.118(带空格)或其他格式

完整的修复方案
既然 $rs = false,说明数据库里没有匹配的记录,那就应该直接获取归属地并插入。

但要注意:插入前先检查是否已存在该IP,防止重复插入。

php
// 1. 查询数据库(加上精确匹配优先)
$newip = preg_replace('/\.\d+$/', '', $onlineip);
$kw = $newip . '%';

// 先精确查询(防止重复)
$rs_exact = $db->get_one("SELECT * FROM pw_ipdatabase WHERE onlineip = '$onlineip' LIMIT 1");
if ($rs_exact && !empty($rs_exact['ipfrom'])) {
    return $rs_exact['ipfrom'];
}

// 再模糊查询
$rs = $db->get_one("SELECT * FROM pw_ipdatabase WHERE onlineip LIKE '$kw' ORDER BY id DESC LIMIT 1");

if ($rs) {
    // 有记录,检查归属地
    if (!empty($rs['ipfrom'])) {
        return $rs['ipfrom'];
    }
    
    // 有记录但归属地为空,尝试更新
    $guishudi = getLocationFromPconline($onlineip);
    if (!empty($guishudi)) {
        $db->query("UPDATE pw_ipdatabase SET ipfrom='$guishudi', source='pconline.com.cn' WHERE id={$rs['id']}");
        return $guishudi;
    }
    // 更新失败标记
    $db->query("UPDATE pw_ipdatabase SET ipfrom='' WHERE id={$rs['id']}");
    return '';
}

// 2. 没有记录,获取归属地
$guishudi = '';
// 尝试 ip.cn
// ...

// 尝试 pconline
if (empty($guishudi)) {
    $guishudi = getLocationFromPconline($onlineip);
}

// 3. 插入数据库(不管是否获取成功,都插入)
if ($guishudi) {
    $db->query("INSERT INTO pw_ipdatabase SET onlineip='$onlineip', ipfrom='$guishudi', source='$source'");
} else {
    // 获取失败,插入空记录(下次可以重新尝试)
    $db->query("INSERT INTO pw_ipdatabase SET onlineip='$onlineip', ipfrom=''");
}

return $guishudi;
总结
你的测试结果清楚地表明:

$rs = false:查询没有找到任何匹配的记录

所以 112.38.51.118 这条记录根本不在表里,或者不在匹配范围内

屏蔽判断后能插入新记录,说明插入逻辑没问题

解决方案:

先去数据库确认 112.38.51.118 是否真的存在

加上精确匹配查询,防止重复插入

优化代码逻辑,让 $rs = false 时也能正确处理

快去数据库查一下 112.38.51.118 到底在不在!😊
尊重每位祺师,做有价值的祺文化社区!欢迎光临,并感谢您的支持与陪伴!
我希望能通过这个论坛让更多的朋友更多的关爱自己的家人和朋友,不要等到有一天失去后才知道没有好好的珍惜,才知道
拥有的珍贵!亲爱的们,如果你懂得,请把这份关爱传递,让更多的人加入到爱心接力当中,让每个人的心中都洋溢着爱的温馨!

我们应该知道:岁月如梭!其实生命,是很短暂,也是很脆弱的东西…… 设置自己的帖子签名请点这里!
离线zhangjingyu

只看该作者 36楼 发表于: 2026-06-27 20:53:41 星期六  点亮(0)
`ipfrom` mediumtext NOT NULL,是这个原因吗?

找到根本原因了! 就是这个 NOT NULL 导致的!

问题分析
sql
`ipfrom` mediumtext NOT NULL
这个字段定义:

NOT NULL:不允许插入 NULL 值

没有默认值:如果插入时没指定值,MySQL 会使用空字符串 '' 作为默认值(对于 NOT NULL 的字符串字段)

那为什么你的 === '' 判断还是没生效?
有两种可能:

可能性1:你之前插入时用了 NULL,但被转换了
因为字段是 NOT NULL,当你尝试插入 NULL 时,MySQL 会:

严格模式:报错

非严格模式:自动转换成空字符串 ''

所以你的 112.38.51.118 记录的 ipfrom 确实是空字符串 ''。

可能性2:问题出在查询结果上(最可能!)
看你的查询逻辑:

php
$rs = $db->get_one("SELECT * FROM pw_ipdatabase WHERE onlineip LIKE '$kw' ORDER BY id DESC LIMIT 1");
get_one() 方法可能返回的不是数组,而是其他格式!

比如:

如果 get_one() 返回的是对象(stdClass),那么 $rs['ipfrom'] 是错误的访问方式

应该用 $rs->ipfrom

这样 $rs['ipfrom'] 可能返回 NULL,导致 === '' 判断失败!

验证方法
php
$rs = $db->get_one("SELECT * FROM pw_ipdatabase WHERE onlineip LIKE '$kw' ORDER BY id DESC LIMIT 1");

// 调试输出
var_dump($rs);  // 看看返回的是什么类型
var_dump($rs['ipfrom']);  // 看看值是什么
var_dump($rs['ipfrom'] === '');  // 看看判断结果
var_dump($rs['ipfrom'] === null);  // 看看是不是NULL
var_dump(empty($rs['ipfrom']));  // 看看empty判断
真正的修复方案
不管 get_one() 返回什么格式,统一处理:

php
// 1. 查询数据库
$newip = preg_replace('/\.\d+$/', '', $onlineip);
$kw = $newip.'%';
$rs = $db->get_one("SELECT * FROM pw_ipdatabase WHERE onlineip LIKE '$kw' ORDER BY id DESC LIMIT 1");

// 2. 关键:获取 ipfrom 的值(兼容数组和对象)
$ipfrom = null;
if ($rs) {
    if (is_array($rs)) {
        $ipfrom = $rs['ipfrom'] ?? null;
    } else if (is_object($rs)) {
        $ipfrom = $rs->ipfrom ?? null;
    }
}

// 3. 如果有记录且归属地有效(非空),直接返回
if ($rs && !empty($ipfrom)) {
    return $ipfrom;
}

// 4. 如果有记录但归属地为空(NULL 或 空字符串)
if ($rs && (empty($ipfrom) || $ipfrom === '')) {
    // 直接尝试 pconline
    $guishudi = getLocationFromPconline($onlineip);
    if (!empty($guishudi)) {
        // 更新数据库(注意:字段是 NOT NULL,所以用空字符串作为失败标记)
        $db->query("UPDATE pw_ipdatabase SET ipfrom='$guishudi', source='pconline.com.cn' WHERE id={$rs['id']}");
        return $guishudi;
    }
    // 获取失败,更新为空字符串(因为 NOT NULL)
    $db->query("UPDATE pw_ipdatabase SET ipfrom='', source='pconline.com.cn' WHERE id={$rs['id']}");
    return '';
}

// 5. 没有记录,完整获取流程
// ... 调用 ip.cn、pconline 等

// 6. 获取成功后插入(因为 NOT NULL,必须给值)
if (!empty($guishudi)) {
    $db->query("INSERT INTO pw_ipdatabase SET ... ipfrom='$guishudi'");
} else {
    // 获取失败,也要插入(因为 NOT NULL)
    $db->query("INSERT INTO pw_ipdatabase SET ... ipfrom=''");
}
为什么 === '' 没生效?
结合你的测试,最可能的原因是:

get_one() 返回的是对象,你用 $rs['ipfrom'] 访问导致返回 NULL

php
// 假设 get_one() 返回对象
$rs = new stdClass();
$rs->ipfrom = '';  // 实际值是空字符串

// 错误访问方式
var_dump($rs['ipfrom']);  // 返回 NULL(因为对象不支持数组访问)

// 正确访问方式
var_dump($rs->ipfrom);  // 返回 ''
所以你的 === '' 判断,实际上是在判断 NULL === '',结果当然是 false!

快速验证
在你的代码里加上这几行调试:

php
$rs = $db->get_one("SELECT * FROM pw_ipdatabase WHERE onlineip LIKE '$kw' ORDER BY id DESC LIMIT 1");

// 调试开始
if ($rs) {
    error_log("=== 调试信息 ===");
    error_log("rs 类型: " . gettype($rs));
    error_log("rs 内容: " . print_r($rs, true));
    
    if (is_array($rs)) {
        error_log("ipfrom: '" . ($rs['ipfrom'] ?? 'NULL') . "'");
        error_log("=== 判断: " . ($rs['ipfrom'] === '' ? 'true' : 'false'));
    } else if (is_object($rs)) {
        error_log("ipfrom: '" . ($rs->ipfrom ?? 'NULL') . "'");
        error_log("=== 判断: " . ($rs->ipfrom === '' ? 'true' : 'false'));
    }
}
// 调试结束
然后查看错误日志,结果一目了然!

最终建议
搞清楚 get_one() 的返回类型(数组还是对象)

用正确的方式访问 ipfrom

用 empty() 统一判断(因为 empty('') 和 empty(null) 都返回 true)

最简单的修复:

php
if ($rs && !empty($rs['ipfrom'])) {  // 如果 $rs 是数组
    return $rs['ipfrom'];
}

// 或者
if ($rs && !empty($rs->ipfrom)) {  // 如果 $rs 是对象
    return $rs->ipfrom;
}
empty() 可以同时处理 NULL、空字符串、0、false 等情况,比 === '' 更宽容。

快试试吧!我赌这次一定能解决!😊
尊重每位祺师,做有价值的祺文化社区!欢迎光临,并感谢您的支持与陪伴!
我希望能通过这个论坛让更多的朋友更多的关爱自己的家人和朋友,不要等到有一天失去后才知道没有好好的珍惜,才知道
拥有的珍贵!亲爱的们,如果你懂得,请把这份关爱传递,让更多的人加入到爱心接力当中,让每个人的心中都洋溢着爱的温馨!

我们应该知道:岁月如梭!其实生命,是很短暂,也是很脆弱的东西…… 设置自己的帖子签名请点这里!
离线zhangjingyu

只看该作者 35楼 发表于: 2026-06-27 20:52:27 星期六  点亮(0)
非常好的测试!你这个问题一下子就暴露了SQL查询的致命问题,我找到根本原因了!

问题根源:LIKE 模糊匹配的"陷阱"
你的SQL是:

sql
SELECT * FROM pw_ipdatabase WHERE onlineip LIKE '112.38.51.%' ORDER BY id DESC LIMIT 1
你的数据表里有一条:112.38.51.118(归属地为空)

当你查询 112.38.51.125 时:

$newip = preg_replace('/\.\d+$/', '', '112.38.51.125') → '112.38.51'

$kw = '112.38.51.%'

SQL 会匹配到:112.38.51.118、112.38.51.125(如果有)等所有以 112.38.51. 开头的记录

ORDER BY id DESC LIMIT 1 → 取 id 最大的那条

关键问题:ORDER BY id DESC 取到的是哪条?
你的数据库里:

112.38.51.118(空归属地),假设 id = 100

查询 112.38.51.125 时,匹配到了 112.38.51.118

ORDER BY id DESC 取 id 最大的那条

如果 112.38.51.118 的 id 是最大的,就取到了这条空记录!

你的测试证明了什么?
测试1:没有屏蔽 if ($rs && $rs['ipfrom'] !== null && $rs['ipfrom'] !== '')
php
// 查询结果:$rs 有值(112.38.51.118),但 ipfrom = ''
if ($rs && $rs['ipfrom'] !== null && $rs['ipfrom'] !== '') {
    return $rs['ipfrom'];  // 条件不成立,跳过
}

// 进入空字符串判断
if ($rs && $rs['ipfrom'] === '') {
    // 应该进入这里才对!
    // 但你说没有更新,说明根本没进入这个 if
}
为什么没进入? 唯一的可能是 $rs['ipfrom'] 不是空字符串 '',而是 NULL!

测试2:屏蔽了返回判断,直接插入新记录
php
// 跳过了所有判断,直接执行插入
$db->query("INSERT INTO pw_ipdatabase SET ... onlineip='112.38.51.125', ipfrom='$guishudi'");
结果插入了新记录,说明:

查询确实匹配到了 112.38.51.118($rs 有值)

但因为某种原因,$rs['ipfrom'] 既不是非空字符串,也不是 ''

所以跳过了两个 if,直接执行到后面的插入逻辑

真正的问题:$rs['ipfrom'] 到底是什么?
结合你的测试,我强烈怀疑:

数据表里 ipfrom 字段的值不是空字符串 '',而是 NULL!

php
// 如果 ipfrom = NULL
$rs['ipfrom'] = null;

// 判断1
if ($rs && $rs['ipfrom'] !== null && $rs['ipfrom'] !== '') {
    // null !== null → false,不进入
}

// 判断2
if ($rs && $rs['ipfrom'] === '') {
    // null === '' → false,不进入
}

// 两个 if 都没进,继续执行后面的插入逻辑!
验证方法
执行这个SQL看看:

sql
SELECT id, onlineip, ipfrom,
       CASE WHEN ipfrom IS NULL THEN 'NULL'
            WHEN ipfrom = '' THEN '空字符串'
            ELSE ipfrom
       END as 实际值
FROM pw_ipdatabase
WHERE onlineip LIKE '112.38.51.%'
ORDER BY id DESC;
我敢肯定,112.38.51.118 的 ipfrom 字段是 NULL,不是空字符串!

为什么是 NULL?
很可能你的数据表定义是:

sql
CREATE TABLE pw_ipdatabase (
    ...
    ipfrom varchar(255) DEFAULT NULL,  -- 默认是 NULL
    ...
);
插入数据时,如果没指定 ipfrom 的值,默认就是 NULL。

正确的修复方案
php
// 1. 如果查到有效归属地(非NULL且非空),直接返回
if ($rs && $rs['ipfrom'] !== null && $rs['ipfrom'] !== '') {
    return $rs['ipfrom'];
}

// 2. 如果查到了记录,但归属地是空的(NULL 或 '')
if ($rs && ($rs['ipfrom'] === null || $rs['ipfrom'] === '')) {
    // 直接尝试 pconline
    $guishudi = getLocationFromPconline($onlineip);
    if (!empty($guishudi)) {
        // 更新数据库
        $db->query("UPDATE pw_ipdatabase SET ipfrom='$guishudi', source='pconline.com.cn' WHERE id={$rs['id']}");
        return $guishudi;
    }
    // pconline 也失败,保持原样(NULL),不返回默认值
    return '';
}

// 3. 没有记录($rs === false),完整获取流程
// ... 调用 ip.cn、pconline 等
建议:统一空值表示
为了避免混淆,建议统一用 NULL 表示"未获取":

sql
-- 修改表结构,设置默认值为 NULL
ALTER TABLE pw_ipdatabase MODIFY ipfrom varchar(255) DEFAULT NULL;

-- 更新所有空字符串为 NULL
UPDATE pw_ipdatabase SET ipfrom = NULL WHERE ipfrom = '';
然后代码统一判断:

php
// 有效数据:非 NULL 且非空字符串
if ($rs && $rs['ipfrom'] !== null && $rs['ipfrom'] !== '') {
    return $rs['ipfrom'];
}

// 无效数据:NULL 或 空字符串
if ($rs && ($rs['ipfrom'] === null || $rs['ipfrom'] === '')) {
    // 尝试重新获取
}
总结
你的测试完美地暴露了问题:

数据表里存的是 NULL,不是空字符串 ''

=== '' 判断不到 NULL,所以没进入更新逻辑

跳过了所有判断,直接执行了插入新记录

解决方法:把 === '' 改成 === null || === '',或者直接用 empty()(因为 empty(null) 和 empty('') 都返回 true)。

快查一下数据库,我赌 5 毛钱那条记录的 ipfrom 是 NULL!😄
尊重每位祺师,做有价值的祺文化社区!欢迎光临,并感谢您的支持与陪伴!
我希望能通过这个论坛让更多的朋友更多的关爱自己的家人和朋友,不要等到有一天失去后才知道没有好好的珍惜,才知道
拥有的珍贵!亲爱的们,如果你懂得,请把这份关爱传递,让更多的人加入到爱心接力当中,让每个人的心中都洋溢着爱的温馨!

我们应该知道:岁月如梭!其实生命,是很短暂,也是很脆弱的东西…… 设置自己的帖子签名请点这里!
离线zhangjingyu

只看该作者 34楼 发表于: 2026-06-27 20:43:04 星期六  点亮(0)
我当前在线ip是112.38.51.125,数据表里有一条112.38.51.118的空归属地记录。然而,
  1. // 2. 如果有记录但归属地是空字符串 ''(获取失败过),直接调用 pconline
  2. //    不再尝试 ip.cn(因为可能已经失败过)
  3. if ($rs && $rs['ipfrom'] === '') {
  4.     // 直接尝试 pconline
  5.     $guishudi = getLocationFromPconline($onlineip);
  6.     if (!empty($guishudi)) {
  7.         // 更新数据库
  8.         $db->query("UPDATE pw_ipdatabase SET ipfrom='$guishudi', source='pconline.com.cn' WHERE id={$rs['id']}");
  9.         return $guishudi;
  10.     }
  11.     // pconline 也失败,保持空字符串,不返回默认值
  12.     return ''; // 或者返回 '未知',但下次还会尝试获取
  13. }
这段代码并没有更新这条记录。尝试屏蔽
  1. // 1. 如果有记录且归属地不为 NULL 且不为空字符串,直接返回
  2. if ($rs && $rs['ipfrom'] !== null && $rs['ipfrom'] !== '') {
  3.     return $rs['ipfrom'];
  4. }
这三行代码,结果是新添加了一条112.38.51.125的记录。帮我分析分析怎么回事
尊重每位祺师,做有价值的祺文化社区!欢迎光临,并感谢您的支持与陪伴!
我希望能通过这个论坛让更多的朋友更多的关爱自己的家人和朋友,不要等到有一天失去后才知道没有好好的珍惜,才知道
拥有的珍贵!亲爱的们,如果你懂得,请把这份关爱传递,让更多的人加入到爱心接力当中,让每个人的心中都洋溢着爱的温馨!

我们应该知道:岁月如梭!其实生命,是很短暂,也是很脆弱的东西…… 设置自己的帖子签名请点这里!