PHP之识别访客IP归属地来提供不同的区域化服务|封禁某些地区訪客-LMLPHP

如果你和我一样有一个需求,通过访客的IP地址获得其归属地,来实现区域化的信息服务。

对于以上,我们可以通过Apanic提供的亚太地区IP数据分配情况来实现,且本文只用到IPV4,IPV6可以自行扩展。

APNIC官方IP分配表

这个地址的数据是持续更新的,所以可以定期更新这个地址的内容到你的本地。

http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest

文件格式参见:

http://ftp.apnic.net/apnic/stats/apnic/README.TXT

Format:

	registry|cc|type|start|value|date|status[|extensions...]

Where:

	registry  	The registry from which the data is taken.
			For APNIC resources, this will be:

			    apnic

	cc        	ISO 3166 2-letter code of the organisation to
	                which the allocation or assignment was made.

	type      	Type of Internet number resource represented
			in this record. One value from the set of
			defined strings:

			    {asn,ipv4,ipv6}

	start     	In the case of records of type 'ipv4' or
			'ipv6' this is the IPv4 or IPv6 'first
			address' of the	range.

			In the case of an 16 bit AS number, the
			format is the integer value in the range:

			    0 - 65535

			In the case of a 32 bit ASN,  the value is
			in the range:

			    0 - 4294967296

		    	No distinction is drawn between 16 and 32
		    	bit ASN values in the range 0 to 65535.

	value     	In the case of IPv4 address the count of
			hosts for this range. This count does not
			have to represent a CIDR range.

			In the case of an IPv6 address the value
			will be the CIDR prefix length from the
			'first address'	value of <start>.

			In the case of records of type 'asn' the
			number is the count of AS from this start
			value.

	date      	Date on this allocation/assignment was made
			by the RIR in the format:

			    YYYYMMDD

			Where the allocation or assignment has been
			transferred from another registry, this date
			represents the date of first assignment or
			allocation as received in from the original
			RIR.

			It is noted that where records do not show a
			date of	first assignment, this can take the
			0000/00/00 value.

    	status    	Type of allocation from the set:

                            {allocated, assigned}

                	This is the allocation or assignment made by
                	the registry producing the file and not any
                	sub-assignment by other agencies.

   	extensions 	In future, this may include extra data that
   			is yet to be defined.

国别编码可以参照ISO  3166-2的2字母国别编码

https://zh.wikipedia.org/wiki/ISO_3166-2

我们以其中一条数据举例说明

apnic|CN|ipv4|42.192.0.0|131072|20110304|allocated

注册商|国别地区编码|类型|IP起始地址|数量|时间|分配情况

PHP实现获取不同地区的服务功能

根据IP获取国别地区

/**
 * 获取IP所在地区代码
 * @param $ip
 * @return string
 */
function getIPAreaCode($ip)
{
    $ipInt = ip2long($ip);
    $apnic = "app/delegated-apnic-latest";
    $handle = fopen($apnic,"r");

    while(!feof($handle)){
        $line = fgets($handle);
        if(substr($line,0,1) == "#"){
            unset($line);
            continue;
        }
        $buffer = explode("|", $line);

        if(isset($buffer[2]) && $buffer[2] == 'ipv4' && isset($buffer[4])){
            $bufferIpInt = ip2long($buffer[3]);
            if($bufferIpInt <= $ipInt && $ipInt <= $bufferIpInt+intval($buffer[4])){
                fclose($handle);
                return trim($buffer[1]);
            }
        }
        unset($line);
        unset($buffer);
    }
    fclose($handle);
    return "未知";
}

使用方法:

禁止中国大陆访客访问

通过获取到的用户IP,判断是否来源是中国大陆,进行对应的业务处理。

if(getIPAreaCode("113.110.225.1") == "CN"){
    echo "根据您所在地区(中国大陆),无法为您提供相应服务!";die;
}else{
    echo "访问正常";
}

过滤中国大陆、港澳台全境访问

通过获取到的访客 IP,只检索出适合其的数据服务。以文章列表举例,

1Nginx从入门到精通Nginx....0
2Java从入门到入狱Java...2

字段解释:

ban_ip【0:无 1:禁大陆 2:禁中国大陆港澳台 3:禁日本 4:禁朝鲜 5:禁朝鲜和韩国】

配置文件要和数据库的字段对应,如下:

'ban_area' => [
        "",    // 无
        "CN",  // 中国大陆
        ["CN", "TW", "HK", "MO"], //中国大陆港澳台
        ["JP"], // 日本
        ["KP"], // 朝鲜
        ["KP","KR"], //朝鲜韩国
    ]

根据IP获取被ban区域id

/**
 * 获取IP被ban区域
 * @param $ip
 * @return string
 */
function getIPBanArea($ip)
{
    $AREA_CODE = config('app.ban_area');
    $ipInt = ip2long($ip);
    $apnic = storage_path("app/delegated-apnic-latest");
    $handle = fopen($apnic,"r");

    while(!feof($handle)){
        $line = fgets($handle);
        if(substr($line,0,1) == "#"){
            unset($line);
            continue;
        }
        $buffer = explode("|", $line);

        if(isset($buffer[2]) && $buffer[2] == 'ipv4' && isset($buffer[4])){
            $bufferIpInt = ip2long($buffer[3]);
            if($bufferIpInt <= $ipInt && $ipInt <= $bufferIpInt+intval($buffer[4])){
                $result = [];
                foreach($AREA_CODE  as $key => $codeBuffer){
                    if((is_array($codeBuffer) && in_array(trim($buffer[1]), $codeBuffer)) or
                        (is_string($codeBuffer) && $codeBuffer== trim($buffer[1]))){
                        $result[] = $key;
                    }
                }
                fclose($handle);
                return $result;
            }
        }
        unset($line);
        unset($buffer);
    }
    fclose($handle);
    return [];
}

根据当前IP地址,排除不适用于当地的服务,获取可用的服务。laravel使用举例 :

// 根据当前IP,获取配置中所有包含该IP的键,即地区ID。
$banAreaCode = getIPBanArea($request->getClientIp());

// 通过数据的not in 实现排除其他的服务,获取可用的服务。
$posts = Posts::where("is_private",0)->whereNotIn('ban_ip',$banAreaCode)->orderBy('created_at','DESC')->paginate(10);

判断IP地址是否同段

也可以通过这个方法判断访客的 IP 地址是否是白名单IP段的。

    /**
     * testMatchFilterIP("192.168.0.2", "192.168.0.0/24");
     * @param $ip
     * @return int
     */
    function testMatchFilterIP($ip, $ipSegment)
    {
        echo "需要匹配的IP:$ip\n";
        $ipInt = ip2long($ip);
        list($ipBegin, $type) = explode('/',$ipSegment);
        $ipBegin =  ip2long($ipBegin);
        echo "IP段起始位置:$ipBegin\n";
        $mask = 0xFFFFFFFF << (32 - intval($type));
        echo "掩码:$mask\n";
        echo "需要匹配的IP网络地址:".long2ip($ipInt&$mask)."\n";
        echo "被匹配的掩码网络地址:".long2ip($ipBegin& $mask)."\n";
        echo sprintf("%s == %s\n",$ipInt&$mask,$ipBegin& $mask);
        return intval($ipInt&$mask) == intval($ipBegin& $mask);
    }

至此!

03-11 19:35