Magento 使用 geoip2 插件做用户IP定位功能
一. 安装 geoip2 库,并检查ip
第一步:安装 geoip2 库
composer require geoip2/geoip2:~2.0
第二步:下载 GeoLite2 数据库
需要注册账户,注册成功后就可以直接下载 GeoLite2 数据库
第三步:调用 ip 检测方法
$reader = new Reader('/var/geoip/GeoLite2-Country.mmdb'); //替换成自己的数据库文件路径 $record = $reader->city('128.101.101.101'); print($record->country->isoCode);
二. 更新 geoip2 数据库
下载用到的 API
https://www.geodbase-update.com/api/v1/edition
需要注册账户的 License key
直接上代碼:
<?php namespace xxxx\xxxxxx\Helper; use DirectoryIterator; use Exception; use FilesystemIterator; use PharData; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use ReflectionClass; use ZipArchive; /** * Class Client * @package tronovav\GeoIP2Update */ class Client { const ARCHIVE_GZ = 'tar.gz'; const ARCHIVE_ZIP = 'zip'; /** * @var string Your account’s actual license key on www.maxmind.com * @link https://support.maxmind.com/account-faq/license-keys/where-do-i-find-my-license-key/ */ public $license_key; /** * @var string Your account’s actual "geodbase_update_key" on www.geodbase-update.com * @link https://www.geodbase-update.com */ public $geodbase_update_key; /** * @var string[] Database editions list to update. * @link https://www.maxmind.com/en/accounts/current/geoip/downloads/ */ public $editions; /** * @var string Destination directory. Directory where your copies of databases are stored. Your old databases will be updated there. */ public $dir; protected $_editionVersions = array(); protected $_baseUrlApi = 'https://www.geodbase-update.com/api/v1/edition'; protected $_updated = array(); protected $_errors = array(); protected $_errorUpdateEditions = array(); protected $_lastModifiedStorageFileName = 'VERSION.txt'; protected $_client = 1; protected $_client_version = '2.3.1'; public function __construct(array $params) { $this->setConfParams($params); $thisClass = new ReflectionClass($this); foreach ($params as $key => $value) if ($thisClass->hasProperty($key) && $thisClass->getProperty($key)->isPublic()) { $this->$key = $value; } else { $this->_errors[] = "The \"{$key}\" parameter does not exist. Just remove it from the options. See https://www.geodbase-update.com"; } } /** * Update info. * @return array */ public function updated() { return $this->_updated; } /** * Update errors. * @return array */ public function errors() { return array_merge($this->_errors, array_values($this->_errorUpdateEditions)); } /** * Database update launcher. * @throws Exception */ public function run() { if (!$this->validate()) { return; } $this->updateEdition((string)$this->editions); } protected function setConfParams(&$params) { if (array_key_exists('geoipConfFile', $params)) { if(is_file($params['geoipConfFile']) && is_readable($params['geoipConfFile'])) { $confParams = array(); foreach (file($params['geoipConfFile']) as $line) { $confString = trim($line); if (preg_match('/^\s*(?P<name>LicenseKey|EditionIDs)\s+(?P<value>([\w-]+\s*)+)$/', $confString, $matches)) { $confParams[$matches['name']] = $matches['name'] === 'EditionIDs' ? array_values(array_filter(explode(' ', $matches['value']), function ($val) { return trim($val); })) : trim($matches['value']); } } $this->license_key = !empty($confParams['LicenseKey']) ? $confParams['LicenseKey'] : $this->license_key; $this->editions = !empty($confParams['EditionIDs']) ? $confParams['EditionIDs'] : $this->editions; } else { $this->_errors[] = 'The geoipConfFile parameter was specified, but the file itself is missing or unreadable. See https://www.geodbase-update.com'; } unset($params['geoipConfFile']); } } /** * @return bool */ protected function validate() { if (!empty($this->_errors)) { return false; } switch (true) { case empty($this->dir): $this->_errors[] = 'Destination directory not specified. See documentation at https://www.geodbase-update.com'; break; case !is_dir($this->dir): $this->_errors[] = "The destination directory \"{$this->dir}\" does not exist. See documentation at https://www.geodbase-update.com"; break; case !is_writable($this->dir): $this->_errors[] = "The destination directory \"{$this->dir}\" is not writable. See documentation at https://www.geodbase-update.com"; } if (empty($this->license_key)) { $this->_errors[] = 'You must specify your Maxmind "license_key". See documentation at https://www.geodbase-update.com'; } if (empty($this->editions)) { $this->_errors[] = "No GeoIP revision names are specified for the update. See documentation at https://www.geodbase-update.com"; } if (!empty($this->_errors)) { return false; } return true; } /** * @param string $editionId * @throws Exception */ protected function updateEdition(string $editionId) { $remoteEditionData = $this->getRemoteEditionData($editionId); if (!empty($this->_errorUpdateEditions[$editionId])) { return; } if ($remoteEditionData['ext'] === self::ARCHIVE_ZIP && !class_exists('\ZipArchive')) { $this->_errorUpdateEditions[$editionId] = "PHP zip extension is required to update csv databases. See https://www.php.net/manual/en/zip.installation.php to install zip php extension."; return; } $remoteActualVersion = date_create($remoteEditionData['version']); $localEditionData = is_file($this->getEditionDirectory($editionId) . DIRECTORY_SEPARATOR . $this->_lastModifiedStorageFileName) ? file_get_contents($this->getEditionDirectory($editionId) . DIRECTORY_SEPARATOR . $this->_lastModifiedStorageFileName) : ''; $currentVersion = date_create_from_format('Y-m-d\TH:i:sP',$localEditionData) ?: 0; $this->_editionVersions[$editionId] = [ !empty($currentVersion) ? $currentVersion->format('c') : 0, $remoteActualVersion->format('c') ]; if (empty($currentVersion) || $currentVersion != $remoteActualVersion) { $this->download($remoteEditionData); if (!empty($this->_errorUpdateEditions[$editionId])) { return; } $this->extract($remoteEditionData); if (!empty($this->_errorUpdateEditions[$editionId])) { return; } $this->_updated[] = "$editionId has been updated."; } else { $this->_updated[] = "$editionId does not need to be updated."; } } /** * @param $editionId * @return string */ protected function getEditionDirectory($editionId): string { return $this->dir . DIRECTORY_SEPARATOR . $editionId; } /** * @param string $editionId * @return array */ protected function getRemoteEditionData(string $editionId): array { $ch = curl_init(trim($this->_baseUrlApi,'/').'/'.'data'.'?'. http_build_query(array( 'id' => $editionId, ))); curl_setopt_array( $ch, [ CURLOPT_HEADER => false, CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Accept: application/json', 'X-Api-Key: '.$this->geodbase_update_key ], CURLOPT_POSTFIELDS => json_encode( [ 'maxmind_key' =>$this->license_key, 'client' => $this->_client, 'client_version' => $this->_client_version ] ) ] ); $result = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if(empty($httpCode)){ $this->_errorUpdateEditions[$editionId] = "The remote server is not available."; return []; } $resultArray = json_decode($result,true); if($httpCode !== 200){ $this->_errorUpdateEditions[$editionId] = $resultArray['data']['message'] ?: $resultArray['data']['name']; return []; } return $resultArray['data']; } /** * @param array $remoteEditionData */ protected function download($remoteEditionData) { $ch = curl_init( trim($this->_baseUrlApi,'/').'/'.'download'.'?'. http_build_query( ['request_id' => $remoteEditionData['request_id']] ) ); $fh = fopen($this->getArchiveFile($remoteEditionData), 'wb'); curl_setopt_array($ch, array( CURLOPT_CUSTOMREQUEST => 'GET', CURLOPT_FOLLOWLOCATION => true, CURLOPT_HTTPHEADER => array( 'Content-Type: application/json', 'X-Api-Key: '.$this->geodbase_update_key, ), CURLOPT_FILE => $fh, )); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); fclose($fh); if ($response === false || $httpCode !== 200){ if(is_file($this->getArchiveFile($remoteEditionData))) unlink($this->getArchiveFile($remoteEditionData)); $this->_errorUpdateEditions[$remoteEditionData['id']] = "Download error: ($httpCode)" . curl_error($ch); } } protected function getArchiveFile($remoteEditionData) { return $this->dir . DIRECTORY_SEPARATOR . $remoteEditionData['id'] . '.' . $remoteEditionData['ext']; } /** * @param array $remoteEditionData */ protected function extract(array $remoteEditionData) { switch ($remoteEditionData['ext']) { case self::ARCHIVE_GZ: if (!in_array('phar', stream_get_wrappers(), true)) { stream_wrapper_restore('phar'); } $phar = new PharData($this->getArchiveFile($remoteEditionData)); $phar->extractTo($this->dir, null, true); break; case self::ARCHIVE_ZIP: $zip = new ZipArchive; $zip->open($this->getArchiveFile($remoteEditionData)); $zip->extractTo($this->dir); $zip->close(); break; } unlink($this->getArchiveFile($remoteEditionData)); if (!is_dir($this->getEditionDirectory($remoteEditionData['id']))) { mkdir($this->getEditionDirectory($remoteEditionData['id'])); } $directories = new DirectoryIterator($this->dir); foreach ($directories as $directory) { /* @var DirectoryIterator $directory */ if ($directory->isDir() && preg_match('/^' . $remoteEditionData['id'] . '[_\d]+$/i', $directory->getBasename())) { $newEditionDirectory = new DirectoryIterator($directory->getPathname()); foreach ($newEditionDirectory as $item) if ($item->isFile()) { rename($item->getPathname(), $this->getEditionDirectory($remoteEditionData['id']) . DIRECTORY_SEPARATOR . $item->getBasename()); } file_put_contents($this->getEditionDirectory($remoteEditionData['id']) . DIRECTORY_SEPARATOR . $this->_lastModifiedStorageFileName, $remoteEditionData['version']); $this->deleteDirectory($directory->getPathname()); break; } } $this->copyFiles($remoteEditionData); } /** * @param $remoteEditionData */ public function copyFiles($remoteEditionData) { $directoryPath = $this->dir . DIRECTORY_SEPARATOR . $remoteEditionData['id']; $sourceFile = $this->dir . DIRECTORY_SEPARATOR . $remoteEditionData['id'] . DIRECTORY_SEPARATOR . $remoteEditionData['id'] . '.mmdb'; $targetFile = $this->dir . DIRECTORY_SEPARATOR . $remoteEditionData['id'] . '.mmdb'; $content = file_get_contents($sourceFile); file_put_contents($targetFile, $content); $this->deleteDirectory($directoryPath); } /** * @param string $directoryPath */ protected function deleteDirectory(string $directoryPath) { if (is_dir($directoryPath)) { $directory = new RecursiveDirectoryIterator($directoryPath, FilesystemIterator::SKIP_DOTS); $children = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::CHILD_FIRST); foreach ($children as $child) /* @var RecursiveDirectoryIterator $child */ $child->isDir() ? rmdir($child) : unlink($child); rmdir($directoryPath); } } } //调用数据库更新 public function execute() { // 获取 License key $licenseKey = $this->helperData->getLicenseKey(); // 指定下载目录 $downloadPath = $this->directoryList->getPath('var') . $this->helperData->getDownloadPath(); if (!is_dir($downloadPath)) { mkdir($downloadPath, 0755, true); } $client = new Client( [ 'license_key' => $licenseKey, 'dir' => $downloadPath, 'editions' => 'GeoLite2-Country', ] ); $client->run(); }