IP.php 21 KB

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