ip转换成详细地址,在项目中经常使用到。
IP地址应用场景
访问来源统计
小流量线上验证
黑白名单
以上是一些应用场景。
我们要把ip转换成详细地址,如何转换? 借助api来实现。
ipip.net

这是ipip提供的免费api:
https://www.ipip.net/product/client.html

聚合数据
https://www.juhe.cn/docs/api/id/1

有了以上两家提供的ip转换,这两家做的都是非常不错的,每天都有免费限额。
我这里使用ipip.net 这家企业的ip服务,每天使用限额是 1000 次,对于一个小型的项目来说,是够用的了。
程序开发
为了开发微服务,采用RPC的形式进行开发转换。
为了更大程度的调用,采用Rpc远程过程调用的方式进行开发部署。
项目采用 yaf + yar 这两个框架。
1、注册接口
IpController.php
defined('APP_PATH') OR exit('No direct script access allowed');
class IpController extends BaseController {
public function indexAction() {
$serivce = new Yar_Server(new IpService());
$serivce->handle();
}
}2、ip转换程序实现
IpService.php
defined('APP_PATH') OR exit('No direct script access allowed');
class IpService extends RpcService {
public function _init() {
parent::_init(); // TODO: Change the autogenerated stub
}
/**
* 获取ip 地址相关信息
* @param string $ip ip地址
* @param int $type 获取类型 1通过调用接口 2 通过url解析dom 没有限制 推荐2
* @return array|bool
*/
public function ip($rpcData) {
$ip = $rpcData['ip'];
$type = isset($rpcData['type']) && in_array($rpcData['type'], [1, 2]) ? $rpcData['type'] : 1;
$methodRules = [
'ip' => 'require|ip'
];
$data = [
'ip' => $ip
];
$validate_message = [
'ip' => 'ip不能为空或ip格式不正确'
];
$validate = $this->validate($methodRules, $data, $validate_message);
if (!$validate['result'])
return $validate;
$result = $this->ipModel->getIpInfo($ip);
//将发送状态返回给用户
return $result;
}
}父类 RpcService.php
<?php
/**
* Created by PhpStorm.
*/
defined('APP_PATH') OR exit('No direct script access allowed');
class RpcService extends BaseService {
protected $checkSignResult = TRUE;
public $secrectData;
public $secrect;
protected $allowIp = ['*']; //允许所有的ip访问,可以指定一个完整ip
public function _init() {
parent::_init(); // TODO: Change the autogenerated stub
$this->checkSignResult && $this->_checkIp();
$this->checkSignResult && $this->_check();
}
protected function validate($rules, $data, $message = []) {
$result = validate($rules, $data, $message);
if ($result === TRUE) {
return ['result' => TRUE];
} else {
return $result;
}
}
/**
* 排序字符
* @param $data
* @return string
*/
private final function _data2str($data) {
$str = '';
ksort($data);
foreach ($data as $key => $val) {
if (is_array($val)) {
$str .= '&' . $this->_data2str($val);
} else {
$str .= $key . '=' . $val . '&';
}
}
return rtrim($str, '&');
}
/**
* sign 生成
* @param $data
* @return string
*/
public final function _sign($data) {
//由于客户端本地使用了__call 这个方法,加密的字符串是 array([0]=>data); 因此下面也按照这个样子加密。保持一致
return md5($this->secrect . '_' . $this->_data2str([$data]));
}
private function _checkIp() {
if (count($this->allowIp) == 1 && ($this->allowIp)[0] === '*') {
debugMessage('RPC ip 验证通过 允许所有IP访问');
return;
} else {
$clientIP = getClientIP();
if (in_array($clientIP, $this->allowIp)) {
debugMessage('RPC ip 验证通过 客户端访问ip是:' . $clientIP);
return;
} else
$this->checkSignResult = FALSE;
}
}
protected function signErr() {
return rpcdata(400, 'sign签名验证错误');
}
/**
* 验证sign是否合法
*/
private function _check() {
//只有post时候才请求
if (isset($_SERVER['HTTP_SIGN_NOCHECK']) && $_SERVER['HTTP_SIGN_NOCHECK'] == 1)
return TRUE;
if (isPost()) {
//我是针对于rpc服务的,返回后系统就不在执行了。
if (isset($_GET['secrect']) && $_GET['secrect'] != '') {
$secrectData = unserialize(AESDecrypt($_GET['secrect'], COOKIE_KEY, TRUE));
if (!$secrectData) {
$this->checkSignResult = FALSE;
debugMessage('AES 解密失败');
return FALSE;
}
debugMessage('AES 解密成功');
//得到用户受保护的字符串
isset($secrectData['secrect']) && $this->secrect = $secrectData['secrect'];
$this->secrectData = $secrectData;
debugMessage('secrectData:' . jsonencode($this->secrectData));
$loginUser = LOGIN_USER;
if (isset($secrectData['userid']) && isset($loginUser[$secrectData['userid']])) {
//判断用户和密码是否符合,判断时间是否允许
$checkUser = $loginUser[$secrectData['userid']];
if ($checkUser['username'] == $secrectData['username'] && $checkUser['password'] == $secrectData['password']) {
if (time() - $secrectData['expiretime'] < MESSAGE_EXPIERTIME) {
debugMessage('checkSignResult 验证通过');
return;
}
}
}
}
$this->checkSignResult = FALSE;
}
}
}3、调用接口数据返回
<?php
/**
* 获取ip详细信息 模型
*/
defined('APP_PATH') OR exit('No direct script access allowed');
class IpModel extends BaseModel {
public function _init() {
parent::_init(); // TODO: Change the autogenerated stub
/**
* https://www.ipip.net/support/api.html
*
* 免费接口(限速每天1000次,仅供测试)
* 如若需要可以使用收费版本
*/
$this->_host = 'http://freeapi.ipip.net/';
}
/**
* 发送前准备
* @param string $url
* @param array|string $data
* @return array|string
*/
public function fetchBefore($url, $data) {
return parent::fetchBefore($url, $data);
}
/**
* 返回时的处理
* @param string $url
* @param array|bool $data
* @return mixed
*/
public function fetchAfter($url, $data) {
return parent::fetchAfter($url, $data);
}
/**
* 返回成功后,处理部分业务逻辑
* @param $data
* @return array
*/
public function fetchFinish($data) {
$result = [];
if ($data == 'not found') {
return [];
} else if (strpos($data, '[') !== FALSE && strpos($data, ']')) {
$d = explode(',', str_replace(['[', ']', '"', "\n"], '', $data));
// 国家 省份 城市 详情 供应商
$dataName = ['china', 'province', 'city', 'info', 'operator'];
if (is_array($d)) {
$data = array_combine($dataName, $d);
debugMessage("ip解析成功:" . var_export($data, TRUE));
}
}
return $data;
}
/**
* 根据ip获取相关信息
* @param $ip
* @return mixed
*/
public function getIpInfo($ip) {
$result = $this->send($this->_host . $ip, [], 'get');
$ip = ip_long($ip);
$this->insert(['ip' => $ip, 'method' => 'getIpInfo_interface', 'ip_info' => jsonencode($result)]);
return $result;
}
}通过上面的几步,服务器端的代码已经注册完毕。
客户端是如何与服务器端通讯的呢?
我在客户端也进行了一定量的封装。例如:如图所示
客户端与服务器端如何鉴权呢?
通过将重要的参数 封装成一个数组,序列化 、AES加密 通过 url 中的一个参数 secrect 进行密文传送。
这是一个非常重要的信息,如果没有此信息 则认为是 无授权用户,不能访问的。
服务器获取到密文后 ,AES 解密后 反序列化 得到数组,与服务器端进行验证。 验证一致,则认为 是有效用户。