IP.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. <?php
  2. namespace App\Utils;
  3. use Arr;
  4. use Exception;
  5. use GeoIp2\Database\Reader;
  6. use GeoIp2\Exception\AddressNotFoundException;
  7. use Http;
  8. use IP2Location\Database;
  9. use ipip\db\City;
  10. use Log;
  11. use MaxMind\Db\Reader\InvalidDatabaseException;
  12. use XdbSearcher;
  13. use function request;
  14. class IP
  15. {
  16. public static function getClientIP(): ?string
  17. { // 获取访客真实IP
  18. return request()?->ip();
  19. }
  20. public static function getIPInfo(string $ip): ?array
  21. {// 获取IP地址信息
  22. $ret = null;
  23. $source = 0;
  24. if (app()->getLocale() === 'zh_CN') {
  25. if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { // 中文ipv4
  26. while ($source <= 5 && ($ret === null || (is_array($ret) && empty(array_filter($ret))))) {
  27. $ret = match ($source) {
  28. 0 => self::ipApi($ip),
  29. 1 => self::baiduBce($ip),
  30. 2 => self::TenAPI($ip),
  31. 3 => self::Baidu($ip),
  32. 4 => self::ipGeoLocation($ip),
  33. 5 => self::ip2Region($ip),
  34. };
  35. $source++;
  36. }
  37. } else {
  38. while ($source <= 9 && $ret === null) { // 中文ipv6
  39. $ret = match ($source) {
  40. 0 => self::baiduBce($ip),
  41. 1 => self::TenAPI($ip),
  42. 2 => self::TaoBao($ip),
  43. 3 => self::fkcoder($ip),
  44. 4 => self::ipApi($ip),
  45. 5 => self::juHe($ip),
  46. 6 => self::Baidu($ip),
  47. 7 => self::ipGeoLocation($ip),
  48. 8 => self::ip2Region($ip),
  49. 9 => self::IPIP($ip),
  50. //10 => self::userAgentInfo($ip), // 无法查外网的ip
  51. };
  52. $source++;
  53. }
  54. }
  55. } else {
  56. while ($source <= 10 && ($ret === null || (is_array($ret) && empty(array_filter($ret))))) { // 英文
  57. $ret = match ($source) {
  58. 0 => self::ipApi($ip),
  59. 1 => self::IPSB($ip),
  60. 2 => self::ipinfo($ip),
  61. 3 => self::ipGeoLocation($ip),
  62. 4 => self::dbIP($ip),
  63. 5 => self::IP2Online($ip),
  64. 6 => self::ipdata($ip),
  65. 7 => self::ipApiCo($ip),
  66. 8 => self::ip2Location($ip),
  67. 9 => self::GeoIP2($ip),
  68. 10 => self::ipApiCom($ip),
  69. };
  70. $source++;
  71. }
  72. }
  73. if ($ret !== null) {
  74. $ret['address'] = implode(' ', Arr::except(array_filter($ret), ['isp']));
  75. }
  76. return $ret;
  77. }
  78. private static function ipApi(string $ip): ?array
  79. { // 开发依据: https://ip-api.com/docs/api:json
  80. $key = config('services.ip.ip-api_key');
  81. if ($key) {
  82. $response = Http::timeout(10)->withHeaders(['Origin' => 'https://members.ip-api.com'])->acceptJson()->get("https://pro.ip-api.com/json/$ip?fields=49881&key=$key&lang=".str_replace('_', '-', app()->getLocale()));
  83. if (! $response->ok()) {
  84. $response = Http::timeout(10)->acceptJson()->get("http://ip-api.com/json/$ip?fields=49881&lang=".str_replace('_', '-', app()->getLocale()));
  85. }
  86. } else {
  87. $response = Http::timeout(10)->acceptJson()->get("http://ip-api.com/json/$ip?fields=49881&lang=".str_replace('_', '-', app()->getLocale()));
  88. }
  89. if ($response->ok()) {
  90. $data = $response->json();
  91. if ($data['status'] === 'success') {
  92. return [
  93. 'country' => $data['country'],
  94. 'region' => $data['regionName'],
  95. 'city' => $data['city'],
  96. 'isp' => $data['isp'],
  97. 'area' => null,
  98. 'latitude' => $data['lat'],
  99. 'longitude' => $data['lon'],
  100. ];
  101. }
  102. Log::error('【ip-api.com】ip查询失败:'.$data['message'] ?? '');
  103. } else {
  104. Log::error('【ip-api.com】查询无效:'.$ip);
  105. }
  106. return null;
  107. }
  108. private static function baiduBce(string $ip): ?array
  109. {
  110. if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
  111. $url = "https://qifu-api.baidubce.com/ip/geo/v1/ipv6/district?ip=$ip";
  112. } else {
  113. $url = "https://qifu-api.baidubce.com/ip/geo/v1/district?ip=$ip";
  114. }
  115. $response = Http::timeout(10)->get($url);
  116. $data = $response->json();
  117. if ($response->ok()) {
  118. if ($data['code'] === 'Success') {
  119. return [
  120. 'country' => $data['data']['country'],
  121. 'region' => $data['data']['prov'],
  122. 'city' => $data['data']['city'],
  123. 'isp' => $data['data']['isp'],
  124. 'area' => $data['data']['district'],
  125. 'latitude' => $data['data']['lat'],
  126. 'longitude' => $data['data']['lng'],
  127. ];
  128. }
  129. Log::error('【baiduBce】IP查询失败:'.$data['msg'] ?? '');
  130. } else {
  131. Log::error('【baiduBce】查询无效:'.$ip.var_export($data, true));
  132. }
  133. return null;
  134. }
  135. private static function TenAPI(string $ip): ?array
  136. { // 开发依据: https://docs.tenapi.cn/utility/getip.html
  137. $response = Http::timeout(10)->asForm()->post('https://tenapi.cn/v2/getip', ['ip' => $ip]);
  138. if ($response->ok()) {
  139. $data = $response->json();
  140. if ($data['code'] === 200 && $data['data']['ip'] === $ip) {
  141. return [
  142. 'country' => $data['data']['country'],
  143. 'region' => $data['data']['province'],
  144. 'city' => $data['data']['city'],
  145. 'isp' => $data['data']['isp'],
  146. 'area' => '',
  147. ];
  148. }
  149. }
  150. return null;
  151. }
  152. private static function Baidu(string $ip): ?array
  153. {// 通过api.map.baidu.com查询IP地址的详细信息
  154. $key = config('services.ip.baidu_ak');
  155. if ($key) {
  156. // 依据 http://lbsyun.baidu.com/index.php?title=webapi/ip-api 开发
  157. $response = Http::timeout(15)->get("https://api.map.baidu.com/location/ip?ak=$key&ip=$ip&coor=gcj02");
  158. if ($response->ok()) {
  159. $message = $response->json();
  160. if ($message['status'] === 0) {
  161. $location = explode('|', $message['address']);
  162. return [
  163. 'country' => $location[0],
  164. 'region' => $message['content']['address_detail']['province'],
  165. 'city' => $message['content']['address_detail']['city'],
  166. 'isp' => $location[4],
  167. 'area' => $message['content']['address_detail']['street'],
  168. 'latitude' => $message['content']['y'],
  169. 'longitude' => $message['content']['x'],
  170. ];
  171. }
  172. Log::warning('【百度IP库】返回错误信息:'.$ip.PHP_EOL.var_export($message, true));
  173. } else {
  174. Log::error('【百度IP库】解析异常:'.$ip);
  175. }
  176. }
  177. return null;
  178. }
  179. private static function ipGeoLocation(string $ip): ?array
  180. { // 开发依据: https://ipgeolocation.io/documentation.html
  181. $response = Http::timeout(15)->withHeaders(['Origin' => 'https://ipgeolocation.io'])
  182. ->get("https://api.ipgeolocation.io/ipgeo?ip=$ip&fields=country_name,state_prov,district,city,isp,latitude,longitude&lang=".config('common.language.'.app()->getLocale().'.1'));
  183. if ($response->ok()) {
  184. $data = $response->json();
  185. return [
  186. 'country' => $data['country_name'],
  187. 'region' => $data['state_prov'],
  188. 'city' => $data['city'],
  189. 'isp' => $data['isp'],
  190. 'area' => $data['district'],
  191. 'latitude' => $data['latitude'],
  192. 'longitude' => $data['longitude'],
  193. ];
  194. }
  195. return null;
  196. }
  197. private static function ip2Region(string $ip): ?array
  198. { // 通过ip2Region查询IP地址的详细信息 数据库不经常更新
  199. try {
  200. $data = (new XdbSearcher())->search($ip);
  201. } catch (Exception $e) {
  202. Log::error('【ip2Region】错误信息:'.$e->getMessage());
  203. }
  204. if (! empty($data)) {
  205. $location = explode('|', $data);
  206. if ($location) {
  207. return [
  208. 'country' => $location[0],
  209. 'region' => $location[2],
  210. 'city' => $location[3],
  211. 'isp' => $location[4],
  212. 'area' => $location[1],
  213. ];
  214. }
  215. }
  216. return null;
  217. }
  218. private static function userAgentInfo(string $ip): ?array
  219. { // 开发依据: https://ip.useragentinfo.com/api
  220. if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
  221. $response = Http::timeout(15)->get("https://ip.useragentinfo.com/ipv6/$ip");
  222. } else {
  223. $response = Http::timeout(15)->withBody("ip:$ip")->get('https://ip.useragentinfo.com/json');
  224. }
  225. if ($response->ok()) {
  226. $data = $response->json();
  227. if ($data['code'] === 200 && $data['ip'] === $ip) {
  228. return [
  229. 'country' => $data['country'],
  230. 'region' => $data['province'],
  231. 'city' => $data['city'],
  232. 'isp' => $data['isp'],
  233. 'area' => $data['area'],
  234. ];
  235. }
  236. Log::error('【userAgentInfo】IP查询失败:'.$data ?? '');
  237. } else {
  238. Log::error('【userAgentInfo】查询无效:'.$ip);
  239. }
  240. return null;
  241. }
  242. private static function TaoBao(string $ip): ?array
  243. { // 通过ip.taobao.com查询IP地址的详细信息 依据 https://ip.taobao.com/instructions 开发
  244. $response = Http::timeout(15)->post("https://ip.taobao.com/outGetIpInfo?ip=$ip&accessKey=alibaba-inc");
  245. if ($response->ok()) {
  246. $message = $response->json();
  247. if ($message['code'] === 0) {
  248. $data = $message['data'];
  249. return [
  250. 'country' => 'xx' !== strtolower($data['country']) ?: null,
  251. 'region' => 'xx' !== strtolower($data['region']) ?: null,
  252. 'city' => 'xx' !== strtolower($data['city']) ?: null,
  253. 'isp' => 'xx' !== strtolower($data['isp']) ?: null,
  254. 'area' => 'xx' !== strtolower($data['area']) ?: null,
  255. ];
  256. }
  257. Log::warning('【淘宝IP库】返回错误信息:'.$ip.PHP_EOL.$message['msg']);
  258. } else {
  259. Log::error('【淘宝IP库】解析异常:'.$ip);
  260. }
  261. return null;
  262. }
  263. private static function fkcoder(string $ip): ?array
  264. { // 开发依据: https://www.fkcoder.com/
  265. $response = Http::timeout(15)->acceptJson()->get("https://www.fkcoder.com/ip?ip=$ip");
  266. if ($response->ok()) {
  267. $data = $response->json();
  268. return [
  269. 'country' => $data['country'],
  270. 'region' => $data['province'] ?: $data['region'],
  271. 'city' => $data['city'],
  272. 'isp' => $data['isp'],
  273. 'area' => null,
  274. ];
  275. }
  276. return null;
  277. }
  278. private static function juHe(string $ip): ?array
  279. { // 开发依据: https://www.juhe.cn/docs/api/id/1
  280. $response = Http::timeout(15)->asForm()->post('https://apis.juhe.cn/ip/Example/query.php', ['IP' => $ip]);
  281. if ($response->ok()) {
  282. $data = $response->json();
  283. if ($data['resultcode'] === '200' && $data['error_code'] === 0) {
  284. return [
  285. 'country' => $data['result']['Country'],
  286. 'region' => $data['result']['Province'],
  287. 'city' => $data['result']['City'],
  288. 'isp' => $data['result']['Isp'],
  289. 'area' => $data['result']['District'],
  290. ];
  291. }
  292. }
  293. return null;
  294. }
  295. private static function IPIP(string $ip): array
  296. { // 通过IPIP离线数据查询IP地址的详细信息
  297. $filePath = database_path('ipipfree.ipdb'); // 来源: https://www.ipip.net/free_download/
  298. $location = (new City($filePath))->findMap($ip, 'CN');
  299. return [
  300. 'country' => $location['country_name'],
  301. 'region' => $location['region_name'],
  302. 'city' => $location['city_name'],
  303. 'isp' => null,
  304. 'area' => null,
  305. ];
  306. }
  307. private static function IPSB(string $ip): ?array
  308. { // 通过api.ip.sb查询IP地址的详细信息
  309. try {
  310. $response = Http::withUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36')->timeout(15)->post("https://api.ip.sb/geoip/$ip");
  311. if ($response->ok()) {
  312. $data = $response->json();
  313. if ($data) {
  314. $ret = Arr::only($data, ['country', 'region', 'city', 'isp', 'latitude', 'longitude']);
  315. return Arr::prepend(['area' => null], $ret);
  316. }
  317. }
  318. Log::warning('[IPSB] 解析'.$ip.'异常: '.$response->body());
  319. } catch (Exception $e) {
  320. Log::error('[IPSB] 解析'.$ip.'错误: '.var_export($e->getMessage(), true));
  321. }
  322. return null;
  323. }
  324. private static function ipinfo(string $ip): ?array
  325. { // 开发依据: https://ipinfo.io/account/home
  326. $key = config('services.ip.ipinfo_token');
  327. if ($key) {
  328. $response = Http::timeout(15)->acceptJson()->get("https://ipinfo.io/$ip?token=$key");
  329. } else {
  330. $response = Http::timeout(15)->acceptJson()->withHeaders(['Referer' => 'https://ipinfo.io/'])->get("https://ipinfo.io/widget/demo/$ip");
  331. }
  332. if ($response->ok()) {
  333. $data = $key ? $response->json() : $response->json()['data'];
  334. $location = explode(',', $data['loc']);
  335. return [
  336. 'country' => $data['country'],
  337. 'region' => $data['region'],
  338. 'city' => $data['city'],
  339. 'isp' => $data['org'],
  340. 'area' => null,
  341. 'latitude' => $location[0],
  342. 'longitude' => $location[1],
  343. ];
  344. }
  345. return null;
  346. }
  347. private static function dbIP(string $ip): ?array
  348. { // 开发依据: https://db-ip.com/api/doc.php
  349. $response = Http::timeout(15)->acceptJson()->get("https://api.db-ip.com/v2/free/$ip");
  350. if ($response->ok()) {
  351. $data = $response->json();
  352. return [
  353. 'country' => $data['countryName'],
  354. 'region' => $data['stateProv'],
  355. 'city' => $data['city'],
  356. 'isp' => null,
  357. 'area' => null,
  358. ];
  359. }
  360. return null;
  361. }
  362. private static function IP2Online(string $ip): ?array
  363. { // 开发依据: https://www.ip2location.io/ip2location-documentation
  364. $key = config('services.ip.IP2Location_key');
  365. if ($key) {
  366. $response = Http::timeout(15)->acceptJson()->get("https://api.ip2location.io/?key=$key&ip=$ip");
  367. if ($response->ok()) {
  368. $data = $response->json();
  369. return [
  370. 'country' => $data['country_name'],
  371. 'region' => $data['region_name'],
  372. 'city' => $data['city_name'],
  373. 'isp' => $data['as'],
  374. 'area' => null,
  375. 'latitude' => $data['latitude'],
  376. 'longitude' => $data['longitude'],
  377. ];
  378. }
  379. }
  380. return null;
  381. }
  382. private static function ipdata(string $ip): ?array
  383. { // 开发依据: https://docs.ipdata.co/docs
  384. $key = config('services.ip.ipdata_key');
  385. if ($key) {
  386. $response = Http::timeout(15)->get("https://api.ipdata.co/$ip?api-key=$key&fields=ip,city,region,country_name,latitude,longitude,asn");
  387. if ($response->ok()) {
  388. $data = $response->json();
  389. return [
  390. 'country' => $data['country_name'],
  391. 'region' => $data['region'],
  392. 'city' => $data['city'],
  393. 'isp' => $data['asn']['name'],
  394. 'area' => null,
  395. 'latitude' => $data['latitude'],
  396. 'longitude' => $data['longitude'],
  397. ];
  398. }
  399. }
  400. return null;
  401. }
  402. private static function ipApiCo(string $ip): ?array
  403. { // 开发依据: https://ipapi.co/api/
  404. $response = Http::timeout(15)->get("https://ipapi.co/$ip/json/");
  405. if ($response->ok()) {
  406. $data = $response->json();
  407. return [
  408. 'country' => $data['country_name'],
  409. 'region' => $data['region'],
  410. 'city' => $data['city'],
  411. 'isp' => $data['org'],
  412. 'area' => null,
  413. 'latitude' => $data['latitude'],
  414. 'longitude' => $data['longitude'],
  415. ];
  416. }
  417. return null;
  418. }
  419. private static function ip2Location(string $ip): ?array
  420. { // 通过ip2Location查询IP地址的详细信息
  421. $filePath = database_path('IP2LOCATION-LITE-DB5.IPV6.BIN'); // 来源: https://lite.ip2location.com/database-download
  422. try {
  423. $location = (new Database($filePath, Database::FILE_IO))
  424. ->lookup($ip, [Database::CITY_NAME, Database::REGION_NAME, Database::COUNTRY_NAME, Database::LATITUDE, Database::LONGITUDE]);
  425. return [
  426. 'country' => $location['countryName'],
  427. 'region' => $location['regionName'],
  428. 'city' => $location['cityName'],
  429. 'isp' => null,
  430. 'area' => null,
  431. 'latitude' => $location['latitude'],
  432. 'longitude' => $location['longitude'],
  433. ];
  434. } catch (Exception $e) {
  435. Log::error('【ip2Location】错误信息:'.$e->getMessage());
  436. }
  437. return null;
  438. }
  439. private static function GeoIP2(string $ip): ?array
  440. {// 通过GeoIP2查询IP地址的详细信息
  441. $filePath = database_path('GeoLite2-City.mmdb'); // 来源:https://github.com/PrxyHunter/GeoLite2/releases
  442. try {
  443. $location = (new Reader($filePath))->city($ip);
  444. return [
  445. 'country' => $location->country->name,
  446. 'region' => $location->mostSpecificSubdivision->name,
  447. 'city' => $location->city->name,
  448. 'isp' => null,
  449. 'area' => null,
  450. ];
  451. } catch (AddressNotFoundException $e) {
  452. Log::error("【GeoIP2】查询失败:$ip ".$e->getMessage());
  453. } catch (InvalidDatabaseException $e) {
  454. Log::error("【GeoIP2】数据库无效:$ip ".$e->getMessage());
  455. }
  456. return null;
  457. }
  458. private static function ipApiCom(string $ip): ?array
  459. { // 开发依据: https://docs.ipdata.co/docs
  460. $response = Http::timeout(15)->get("https://ipapi.com/ip_api.php?ip=$ip");
  461. if ($response->ok()) {
  462. $data = $response->json();
  463. return [
  464. 'country' => $data['country_name'],
  465. 'region' => $data['region_name'],
  466. 'city' => $data['city'],
  467. 'isp' => $data['connection']['isp'],
  468. 'area' => null,
  469. 'latitude' => $data['latitude'],
  470. 'longitude' => $data['longitude'],
  471. ];
  472. }
  473. return null;
  474. }
  475. public static function getIPGeo(string $ip): ?array
  476. {
  477. $ret = null;
  478. $source = 0;
  479. while ($source <= 10 && ($ret === null || (is_array($ret) && empty(array_filter($ret))))) {
  480. $ret = match ($source) {
  481. 0 => self::IPSB($ip),
  482. 1 => self::ipApi($ip),
  483. 2 => self::baiduBce($ip),
  484. 3 => self::ipinfo($ip),
  485. 4 => self::IP2Online($ip),
  486. 5 => self::Baidu($ip),
  487. 6 => self::ipdata($ip),
  488. 7 => self::ipGeoLocation($ip),
  489. 8 => self::ipApiCo($ip),
  490. 9 => self::ipApiCom($ip),
  491. 10 => self::ip2Location($ip),
  492. };
  493. $source++;
  494. }
  495. return Arr::only($ret, ['latitude', 'longitude']);
  496. }
  497. }