一个严谨的接口调用
入口
use Apis\Factory;
use finger\Log;
class IndexController extends \Common\controllers\Api
{
/**
* API入口。
* 普通 POST 方式提交。
*/
public function indexAction()
{
// [1]
define('IS_API', true);
header("Access-Control-Allow-Origin: *");
header('Content-type: application/json');
// [2]
$params = [
'post' => $this->_request->getPost(),
'input' => file_get_contents('php://input')
];
// [3]
$apiObj = Factory::factory($params);
$result = $apiObj->getResult();
// [4] 记录响应日志。
Log::writeApiResponseLog($result);
// [5] 输出结果。
echo json_encode($result, JSON_UNESCAPED_UNICODE);
// [6]
$this->end();
}
}
工厂验证
/**
* 根据接口名称返回接口对象。
*
* -- 1、接口名称转类名称规则:user.login = UserLoginApi
* -- 2、当 method 参数为空的时候,要抛出异常给调用的人捕获处理。
*
* @param array $apiData 请求来的所有参数。
* @throws Exception
* @return Api
*/
public static function factory(&$apiData)
{
// [1]
$reqParams = DecodeAdapter::parse($apiData);
self::writeRequestLog($apiData, $reqParams);
// [2]
if (!isset($reqParams['method']) || strlen($reqParams['method']) === 0) {
Core::exception(STATUS_METHOD_NOT_EXISTS, 'method does not exist');
}
if (!isset($reqParams['v']) || strlen($reqParams['v']) === 0) {
Core::exception(STATUS_VERSION_NOT_EXISTS, 'version number is wrong');
}
if (!isset($reqParams['appid']) || strlen($reqParams['appid']) === 0) {
Core::exception(STATUS_APPID_NOT_EXISTS, 'appid parameters cannot be empty');
}
if (!isset($reqParams['timestamp']) || strlen($reqParams['timestamp']) === 0) {
Core::exception(STATUS_TIMESTAMP_NOT_EXISTS, 'timestamp parameters cannot be empty');
}
// [3] 将 method 参数转换为实际的接口类名称。
$apiName = $reqParams['method'];
$params = explode('.', $apiName);
$classname = '';
foreach ($params as $param) {
$classname .= ucfirst($param);
}
// 1.0.0 => v1_0_0
$version = str_replace('.', '', $reqParams['v']);
// [4]
$apiDir = self::apiDir($reqParams['method']);
if (strlen($apiDir) > 0) {
$apiDir = "{$apiDir}\\";
}
// [5] 读取 appid 配置信息。
$apiDetail = self::getApiDetail($reqParams['appid']);
// [6] IP 限制判断。
$ip = Ip::ip();
$bool = ApiAuth::checkIpAllowAccess($apiDetail, $ip);
if ($bool == false) {
Core::exception(STATUS_IP_FORBID, '受限 IP 不允许访问');
}
// [7] 映射接口类。
$classname = "\\Apis\\{$apiDetail['api_type']}\\v{$version}\\{$apiDir}{$classname}Api";
if (strlen($apiName) && class_exists($classname)) {
return new $classname($reqParams, $apiDetail['api_type'], $apiDetail['api_key'], $apiDetail['api_secret']);
} else {
Core::exception(STATUS_API_NOT_EXISTS, '您的 APP 太旧请升级!');
}
}
类验证
/**
* 构造方法。
*
* @param array $data 所有请求过来的参数。
* @param string $apiType API 接口类型。
* @param string $apiKey 接口标识。
* @param string $apiSecret 接口密钥。
*
* -- 1、合并提交的参数。
* -- 2、调用权限判断。
* -- 3、签名验证。
* -- 4、参数格式判断。
* -- 5、运行接口逻辑。
*/
public function __construct(&$data, $apiType, $apiKey = '', $apiSecret = '')
{
$this->apiType = $apiType;
$this->timestamp = $_SERVER['REQUEST_TIME'];
$this->params = $data;
$this->apiKey = $apiKey;
$this->apiSecret = $apiSecret;
$this->checkIpAccessPermission();
$this->checkTimeLag();
$this->checksignature();
$this->runService();
}
签名验证
/**
* 验证码请求签名。
*
* @param array $params 请求参数。
* @param string $apiSecret API 密钥。
*
* @return bool
*/
public static function checkSign($params, $apiSecret)
{
$sign = $params['sign'];
unset($params['sign']);
ksort($params);
$str = '';
foreach ($params as $key => $value) {
if (!is_array($value) && strlen($value) != 0) {
$str .= "{$key}{$value}"; // 非数组的值才能进行签名。
}
}
$str = $str . $apiSecret;
$okSign = strtoupper(md5($str));
if (App::getConfig('app.env') != ENV_DEV) {
if (strlen($sign) === 0 || $sign != $okSign) {
Core::exception(STATUS_SERVER_ERROR, 'API signature error');
}
}
}
各种验证,太可怕了。