当前位置:   article > 正文

Thinkphp5根据IP地址获取定位地理位置,精准到城市

Thinkphp5根据IP地址获取定位地理位置,精准到城市

导读: 

根据IP地址获取用户所在的城市地理位置,网上有很多免费的API接口可以实现,但是接口响应比较慢,接口也受网速等因素影响,有时会很卡,PHP又是属于单线程的,一直卡在获取IP定位这里的话,会直接给用户造成非常不好体验,用户就觉得你的网站很垃圾。因此,推荐大家直接使用离线IP库进行查询,查询速度快,而且也比较准确。

本文采用的是纯真2022年新版的IP库,下载地址:

https://download.csdn.net/download/qq15577969/87780773

实测:查询100个IP的位置,不超过1秒钟。 

 步骤:

1、将cznet.zip上传到Thinkphp的扩展目录extend并解压;

2、在Controller控制器里使用如下方法调用:

  1. <?php
  2. namespace app\index\controller;
  3. use cznet\IpLocation;
  4. class Index extends Controller
  5. {    
  6.     public function index(){
  7.         //创建实例对象
  8.         $ip = new IpLocation();
  9.          //获取定位
  10.          $location = $ip->getlocation('119.75.217.56');
  11. echo "<pre>";
  12.          var_dump($location);
  13.     }
  14. }

输出结果如下: 

IpLocation.php代码: 

  1. <?php
  2. namespace cznet;
  3. /**
  4. * IP 地理位置查询类
  5. *
  6. * @author 元歌
  7. * @version 1.0
  8. * @copyright 2023年5月12日
  9. */
  10. class IpLocation
  11. {
  12. /**
  13. * qqwry.Dat文件指针
  14. * @var resource
  15. */
  16. private $fp;
  17. /**
  18. * 第一条IP记录的偏移地址
  19. * @var int
  20. */
  21. private $firstip;
  22. /**
  23. * 最后一条IP记录的偏移地址
  24. * @var int
  25. */
  26. private $lastip;
  27. /**
  28. * IP记录的总条数(不包含版本信息记录)
  29. * @var int
  30. */
  31. private $totalip;
  32. /**
  33. * 返回结果的编码
  34. * @var string
  35. */
  36. private $charset = 'UTF-8';
  37. /**
  38. * 构造函数,打开 QQWry.Dat 文件并初始化类中的信息
  39. *
  40. * @param string $filename
  41. * @return IpLocation
  42. */
  43. public function __construct($filename = ''){
  44. if(empty($filename)){
  45. $filename = __DIR__.DIRECTORY_SEPARATOR.'UTFWry.dat';
  46. }
  47. $this->fp = null;
  48. if (($this->fp = fopen($filename, 'r')) !== false) {
  49. $this->firstip = $this->getlong();
  50. $this->lastip = $this->getlong();
  51. $this->totalip = ($this->lastip - $this->firstip) / 7;
  52. }
  53. }
  54. /**
  55. * 析构函数,用于在页面执行结束后自动关闭打开的文件。
  56. *
  57. */
  58. public function __destruct(){
  59. if ($this->fp) {
  60. fclose($this->fp);
  61. }
  62. $this->fp = null;
  63. }
  64. /**
  65. * 返回读取的长整型数
  66. *
  67. * @access private
  68. * @return int
  69. */
  70. private function getlong(){
  71. //将读取的little-endian编码的4个字节转化为长整型数
  72. $result = unpack('Vlong', fread($this->fp, 4));
  73. return $result['long'];
  74. }
  75. /**
  76. * 返回读取的3个字节的长整型数
  77. *
  78. * @access private
  79. * @return int
  80. */
  81. private function getlong3(){
  82. //将读取的little-endian编码的3个字节转化为长整型数
  83. $result = unpack('Vlong', fread($this->fp, 3) . chr(0));
  84. return $result['long'];
  85. }
  86. /**
  87. * 返回压缩后可进行比较的IP地址
  88. *
  89. * @access private
  90. * @param string $ip
  91. * @return string
  92. */
  93. private function packip($ip){
  94. // 将IP地址转化为长整型数,如果在PHP5中,IP地址错误,则返回False,
  95. // 这时intval将Flase转化为整数-1,之后压缩成big-endian编码的字符串
  96. return pack('N', intval(ip2long($ip)));
  97. }
  98. /**
  99. * 返回读取的字符串
  100. *
  101. * @access private
  102. * @param string $data
  103. * @return string
  104. */
  105. private function getstring($data = ""){
  106. $char = fread($this->fp, 1);
  107. while (ord($char) > 0) { // 字符串按照C格式保存,以结束
  108. $data .= $char; // 将读取的字符连接到给定字符串之后
  109. $char = fread($this->fp, 1);
  110. }
  111. return $data;
  112. }
  113. /**
  114. * 返回地区信息
  115. *
  116. * @access private
  117. * @return string
  118. */
  119. private function getarea(){
  120. $byte = fread($this->fp, 1);// 标志字节
  121. switch (ord($byte)) {
  122. case 0:// 没有区域信息
  123. $area = "";
  124. break;
  125. case 1:
  126. case 2:// 标志字节为1或2,表示区域信息被重定向
  127. fseek($this->fp, $this->getlong3());
  128. $area = $this->getstring();
  129. break;
  130. default:// 否则,表示区域信息没有被重定向
  131. $area = $this->getstring($byte);
  132. break;
  133. }
  134. return $area;
  135. }
  136. /**
  137. * 从qqwry.dat文件中查找IP的位置(偏移量)
  138. */
  139. private function findIp($ip){
  140. // 对分搜索
  141. $l = 0; // 搜索的下边界
  142. $u = $this->totalip; // 搜索的上边界
  143. $findip = $this->lastip; // 如果没有找到就返回最后一条IP记录(QQWry.Dat的版本信息)
  144. while ($l <= $u) { // 当上边界小于下边界时,查找失败
  145. $i = floor(($l + $u) / 2); // 计算近似中间记录
  146. fseek($this->fp, $this->firstip + $i * 7);
  147. $beginip = strrev(fread($this->fp, 4)); // 获取中间记录的开始IP地址
  148. // strrev函数在这里的作用是将little-endian的压缩IP地址转化为big-endian的格式
  149. // 以便用于比较,后面相同。
  150. if ($ip < $beginip) { // 用户的IP小于中间记录的开始IP地址时
  151. $u = $i - 1; // 将搜索的上边界修改为中间记录减一
  152. } else {
  153. fseek($this->fp, $this->getlong3());
  154. $endip = strrev(fread($this->fp, 4)); // 获取中间记录的结束IP地址
  155. if ($ip > $endip) { // 用户的IP大于中间记录的结束IP地址时
  156. $l = $i + 1; // 将搜索的下边界修改为中间记录加一
  157. } else { // 用户的IP在中间记录的IP范围内时
  158. $findip = $this->firstip + $i * 7;
  159. break; // 则表示找到结果,退出循环
  160. }
  161. }
  162. }
  163. return $findip;
  164. }
  165. /**
  166. * 根据所给 IP 地址或域名返回所在地区信息
  167. *
  168. * @access public
  169. * @param string $ip
  170. * @return array
  171. */
  172. public function getlocation($ip){
  173. if (!$this->fp) return null; // 如果数据文件没有被正确打开,则直接返回空
  174. // $location['ip'] = gethostbyname($ip); // 将输入的域名转化为IP地址
  175. $location['ip'] = $ip;
  176. $ip = $this->packip($location['ip']); // 将输入的IP地址转化为可比较的IP地址
  177. /**
  178. * 查找IP在文件中的偏移量
  179. */
  180. $findip = $this->findIp($ip);
  181. //获取查找到的IP地理位置信息
  182. fseek($this->fp, $findip);
  183. $location['beginip'] = long2ip($this->getlong()); // 用户IP所在范围的开始地址
  184. $offset = $this->getlong3();
  185. fseek($this->fp, $offset);
  186. $location['endip'] = long2ip($this->getlong()); // 用户IP所在范围的结束地址
  187. $byte = fread($this->fp, 1); // 标志字节
  188. switch (ord($byte)) {
  189. case 1: // 标志字节为1,表示国家和区域信息都被同时重定向
  190. $countryOffset = $this->getlong3(); // 重定向地址
  191. fseek($this->fp, $countryOffset);
  192. $byte = fread($this->fp, 1); // 标志字节
  193. switch (ord($byte)) {
  194. case 2:// 标志字节为2,表示国家信息又被重定向
  195. fseek($this->fp, $this->getlong3());
  196. $location['country'] = $this->getstring();
  197. fseek($this->fp, $countryOffset + 4);
  198. $location['area'] = $this->getarea();
  199. break;
  200. default:// 否则,表示国家信息没有被重定向
  201. $location['country'] = $this->getstring($byte);
  202. $location['area'] = $this->getarea();
  203. break;
  204. }
  205. break;
  206. case 2: // 标志字节为2,表示国家信息被重定向
  207. fseek($this->fp, $this->getlong3());
  208. $location['country'] = $this->getstring();
  209. fseek($this->fp, $offset + 8);
  210. $location['area'] = $this->getarea();
  211. break;
  212. default: // 否则,表示国家信息没有被重定向
  213. $location['country'] = $this->getstring($byte);
  214. $location['area'] = $this->getarea();
  215. break;
  216. }
  217. $location = $this->transcoding($location);
  218. return $this->otherLocation($location);
  219. }
  220. /**
  221. * 转换编码
  222. * @param Array $location
  223. * @return Array
  224. */
  225. private function transcoding($location){
  226. foreach (['country', 'area'] as $key) {
  227. //将GBK转换为UTF-8
  228. //$location[$key] = iconv("GBK", 'UTF-8', $location[$key]);
  229. }
  230. return $location;
  231. }
  232. /**
  233. * 处理其他情况 -- 没有信息或本地,局域网的情况
  234. * @param $location
  235. * @internal param String $ip ip地址
  236. * @return Array
  237. */
  238. private function otherLocation($location)
  239. {
  240. if ($location['country'] == 'CZ88.NET') {
  241. $location['country'] = '';
  242. }
  243. if ($location['country'] == '纯真网络') {
  244. $location['country'] = '';
  245. }
  246. if ($location['country'] == 'IANA') {
  247. $location['country'] = '';
  248. }
  249. if ($location['area'] == 'CZ88.NET') {
  250. $location['area'] = '';
  251. }
  252. $ip = $location['ip'];
  253. if (substr($ip, 0, 3) == '127') {
  254. $location['country'] = '本地网络';
  255. $location['area'] = '本地保留地址';
  256. } elseif (substr($ip, 0, 7) == '192.168') {
  257. $location['country'] = '局域网';
  258. $location['area'] = '同一网络内';
  259. }
  260. return $location;
  261. }
  262. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/242219
推荐阅读
相关标签
  

闽ICP备14008679号