IP.php 21 KB

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