什么是sdk
软件开发工具包(Software Development Kit, SDK)一般是一些被软件工程师用于为特定的软件包、软件框架、硬件平台、操作系统等创建应用软件的开发工具的集合。
通常意义的sdk
在web开发领域,我们接触的sdk通常是为了调用开放接口而封装好的公司外的第三方库。比如,各大互联网公司在几年前都做了开发者平台
Github: GitHub API v3
Weibo: API - 微博API
Twitter: dev.twitter.com/
Instagram: www.instagram.com/developer/
开放接口特点
可以理解为,为了调用开放接口,sdk就是为了方便调用才封装好了一些方法和工具。我们可以尝试总结下我们接触的开放接口的特点:
- 基于 http, 这种方式是最简单的,跨平台和编程语言的数据交换方式;
- 使用
GET
/POST
请求方式,传递数据,并且一般都有接入方的凭证,如TOKEN
SIGN
等; - 传递参数不定,请求前都需要进行整理和格式化,比如,加入请求凭证 或 json encode 亦或 加密;
- 返回数据需要重新整理才能供内部使用,比如是否成功判断,字段名转换;
场景特点传达的几个信息
- 我们作为调用方,此时相当于客户端的角色,要请求接口;
- 对于调用方的凭证或隐私信息保存要灵活配置,因为可能接口有沙箱环境,那么相关配置如调用方id, key 或私钥文件等都不同;
- PHP大法好?!,强大的
array
类型,一下子打包就post
过去,虽然这样最简单省事,但是这样可读性并不好;提倡把接口数据抽象成一个对象,一个接口对应一个类,利用 传输对象模式; - 对软件架构中的防腐层设计有一个概念,系统内部和外部交互进行字段转换,起码字段转换相关逻辑一定单独做;
编码的原则
实现
创建接口请求对象的抽象类
<?php
/**
* Class AbstractRequestBody
*/
abstract class AbstractRequestBody
{
/**
* @var string $path
* 因为每个接口的路径不同,所以需要每个接口都显示的指定请求路径
*/
protected $path;
/**
* @var array $data
* 我们把每个接口的业务参数最后统一收纳在data数组中
*/
protected $data = [];
/**
* AbstractRequestBody constructor.
*/
public function __construct()
{
$this->setPath();
}
/**
* @return mixed
* 假如有必要,每个接口都可以进行一些自定义的参数校验
*/
abstract public function validate();
/**
* @param $response
*
* @return mixed
*/
abstract public function transfer(&$response);
/**
* @return string
* 最后发起请求的时候获取接口路径
*/
public function getPath()
{
return $this->path;
}
/**
* @return mixed
* 抽象化对象,强制要求每个接口类必须设定接口路径
*/
abstract protected function setPath();
/**
* @return array
* 需要打包的一些公共参数
*/
public function package(): array
{
$data = $this->getData();
$params['data'] = json_encode($data, 320);
$params['version'] = Constants::VERSION;
$params['nonce_str'] = Utils::getNonceStr();
$params['sign'] = Utils::sign($params);
return $params;
}
protected function getData()
{
return $this->data;
}
}
OneApi 接口请求类
<?php
/**
* Class OneApi
*/
class OneApi extends AbstractRequestBody
{
public function setOneParam($value)
{
$this->data['one_param'] = $value;
// 为实现可链式调用
return $this;
}
public function setTwoParam($value)
{
$this->data['two_param'] = $value;
// 为实现可链式调用
return $this;
}
/**
* ...更多参数
*/
/**
* 自定义的参数验证
*/
public function validate()
{
// TODO: Implement validate() method.
}
/**
* 字段转换
*/
public function transfer(&$response)
{
// TODO: Implement transfer() method.
}
protected function setPath()
{
$this->path = '/path/api';
}
}
工具类
<?php
/**
* Class Utils
*/
class Utils
{
/**
* @param int $length
* @return string
*/
public static function getNonceStr()
{
}
/**
* 签名算法
* 可能要需要用到key 或 私钥文件
* @param array $params
* @return string
*/
public static function sign(array $params): string
{
}
/**
* 验证接口返回数据
* @param array $params
* @return bool
*/
public static function verifySign(array $params): bool
{
}
}
常量类
如果常量比较多,建议根据功能分类做成多个文件。
<?php
/**
* Class Constants
*/
class Constants
{
const DOMAIN_PROD = 'https://open.api.com';
const VERSION = '1.0.0';
const SIGN_TYPE = 'MD5';
/**
* 接口其他相关常量
*/
}
配置
这里只做简单示例,配置也可以做成 return array()
的形式,然后加载,或者类似 Laravel 利用 env
函数从环境变量取;
有一个原则是:代码配置安全的检测标准之一是代码库是否可立即开源。
<?php
/**
* Class Config
*/
class Config
{
const SIGN_MD5_KEY = '1111111111111';
const SERVICE_ID = '1213444543545';
}
SdkClient类
<?php
/**
* Class SdkClient
*/
class SdkClient
{
/**
* @param AbstractRequestBody $requestBody
* @param int $timeout
* @return string
*/
public function send($requestBody, int $timeout = 15)
{
try {
// 校验参数
$requestBody->validate();
// 拼凑url
$url = Constants::DOMAIN_PROD . $requestBody->getPath();
// 获取请求参数
$data = $requestBody->package();
// 发送请求并经过一层统一转换
$response = $this->response(
$this->post(
$url,
$data,
$timeout
)
);
// 若比较特殊的格式转换,可以在各自请求类中进行响应过滤,响应数据是传引用的,直接处理即可
$requestBody->transfer($response);
} catch (\Exception $exception) {
// TODO: 异常处理 $response = ...
}
return $response;
}
/**
* 对返回数据统一处理,数据过滤
* 比如转换成数组,还是对象,如果是对象可以new Response,在构造函数处理
* @param string $content
* @return string
*/
private function response(string $content)
{
return $content;
}
/**
* TODO: 这里亦可根据需求,替换成项目可用的http包
* @param string $url
* @param array $fields
* @param int $timeout
* @return bool|string
*/
private function post(string $url, array $fields, int $timeout = 10)
{
$headers = ['Content-Type: application/json;charset=UTF-8',];
$ch = curl_init();
array_push($headers, "Expect:");
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
// ...
return curl_exec($ch);
}
}
调用示例
<?php
class Demo
{
private $sdk_client;
public function __construct()
{
$this->sdk_client = new SdkClient();
}
public function testOneApi()
{
$request = new OneApi();
$request->setOneParam('3333')
->setTwoParam('1111');
print_r($this->sdk_client->send($request));
}
}
工程目录参考
之前封装了调用新支付开放平台的sdk,工程目录作为参考
反思
这种封装方式适用在那些场景?
总结这个封装套路是在接了几个支付渠道后,不断尝试总结出来的;并且有借鉴Java中面向对象的设计思路;
特别适合业务参数比较多、接口比较多、逻辑复杂、签名严格的场景下封装抽象出这几个类。
假如参数比较少请求比较简单,比如 GET
请求一两个参数,还没有特别复杂的签名算法的情况下,这种显然是不适合的,这只会徒增工作量。
以上代码只是作为一个参考,尝试总结封装的套路,还有细节和不完善的地方。
扩展
前段时间按照这些思路封装了一个钉钉聊天机器人sdk,可以作为参考,欢迎 star 或 下载 composer require baiyutang/dingtalk-chatbot