在我的项目中,我在postgres(plpgsql)中有一个函数,它根据给定的ip地址确定国家:
CREATE OR REPLACE FUNCTION get_country_for_ip(character varying)
RETURNS character varying AS
$BODY$
declare
ip ALIAS for $1;
ccode varchar;
cparts varchar[];
nparts bigint[];
addr bigint;
begin
cparts := string_to_array(ip, '.');
if array_upper(cparts, 1) <> 4 then
raise exception 'gcfi01: Invalid IP address: %', ip;
end if;
nparts := array[a2i(cparts[1])::bigint, a2i(cparts[2])::bigint, a2i(cparts[3])::bigint, a2i(cparts[4])::bigint];
if(nparts[1] is null or nparts[1] < 0 or nparts[1] > 255 or
nparts[2] is null or nparts[2] < 0 or nparts[2] > 255 or
nparts[3] is null or nparts[3] < 0 or nparts[3] > 255 or
nparts[4] is null or nparts[4] < 0 or nparts[4] > 255) then
raise exception 'gcfi02: Invalid IP address: %', ip;
end if;
addr := (nparts[1] << 24) | (nparts[2] << 16) | (nparts[3] << 8) | nparts[4];
addr := nparts[1] * 256 * 65536 + nparts[2] * 65536 + nparts[3] * 256 + nparts[4];
select into ccode t_country_code from ip_to_country where addr between n_from and n_to limit 1;
if ccode is null then
ccode := '';
end if;
return ccode;
end;$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
这可能不是最有效的,但它可以完成这项工作。请注意,它使用内部表(ip_to_country
),其中包含如下数据(数字n_from和n_to是地址范围起点和终点的long
值:
n_from | n_to | t_country_code
----------+----------+----------------
0 | 16777215 | ZZ
16777216 | 16777471 | AU
...
现在我们开始关注IPv6寻址 - 我需要为IPv6地址添加类似的功能。我有类似的IPv6数据集,如下所示:
t_start | t_end | t_country_code
-------------+-----------------------------------------+----------------
:: | ff:ffff:ffff:ffff:ffff:ffff:ffff:ffff | ZZ
100:: | 1ff:ffff:ffff:ffff:ffff:ffff:ffff:ffff | ZZ
...
2000:: | 2000:ffff:ffff:ffff:ffff:ffff:ffff:ffff | ZZ
...
2001:1200:: | 2001:1200:ffff:ffff:ffff:ffff:ffff:ffff | MX
...
现在,给定IP地址::1
,我如何(1)检查它是否是有效的IPv6地址,以及(2)获得相应的国家/地区映射?
答案 0 :(得分:1)
我相信我找到了解决方案。它涉及首先修改数据,然后对输入进行一些按摩。这是有效的。
首先,需要转换数据,以便在删除分号分隔符的情况下,所有地址都已满,不会缩短。我的问题中显示的示例数据转换为:
t_start | t_end | t_country_code
----------------------------------+----------------------------------+----------------
00000000000000000000000000000000 | 00ffffffffffffffffffffffffffffff | ZZ
01000000000000000000000000000000 | 01ffffffffffffffffffffffffffffff | ZZ
...
20000000000000000000000000000000 | 2000ffffffffffffffffffffffffffff | ZZ
...
20011200000000000000000000000000 | 20011200ffffffffffffffffffffffff | MX
...
这是存储在数据库中的内容。
下一步是将代码中收到的IP地址转换为相同的格式。这是在PHP中使用以下代码完成的(假设$ip_address
是传入的IPv6地址):
$addr_bin = inet_pton($ip_address);
$bytes = unpack('n*', $addr_bin);
$ip_address = implode('', array_map(function ($b) {return sprintf("%04x", $b); }, $bytes));
现在变量$ip_adress
将包含完整的IPv6地址,例如
:: => 00000000000000000000000000000000
2001:1200::ab => 200112000000000000000000000000ab
等等。
现在,您只需将此完整地址与数据库中的范围进行比较即可。我在数据库中添加了第二个函数来处理IPv6地址,如下所示:
CREATE OR REPLACE FUNCTION get_country_for_ipv6(character varying)
RETURNS character varying AS
$BODY$
declare
ip ALIAS for $1;
ccode varchar;
begin
select into ccode t_country_code from ipv6_to_country where addr between n_from and n_to limit 1;
if ccode is null then
ccode := '';
end if;
return ccode;
end;$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
最后,在我的php代码中,我添加了基于输入ip_address调用一个或另一个Postgres函数的代码。
答案 1 :(得分:0)
首先,我看到你正在做的一些事情会带来问题。第一个是使用varchar和long来表示IP地址,当PostgreSQL具有完全有效的INET和CIDR类型时,它们将更好,更快地执行您想要的操作。请注意,这些目前不能正确支持GIN索引,因此您无法排除对它们的约束。如果您需要,请查看支持此功能的ip4r扩展。
请注意,现在您可以将varchar转换为inet。 Inet也像cidr一样同时支持ipv4和ipv6地址,ip4r上也存在类似的类型。
这将解决您的ipv6验证问题,并可能减少您的存储空间,并提供更好的操作检查和更好的性能。
至于国家,我也认为映射可能不是那么直截了当。