我所理解的接口设计

转载 2018年03月26日 00:00:00

640?wx_fmt=gif

前言

自己做接口开发的时间也算不短了(三年),想写这篇文章其实差不多已经有一年多的时间了。我将从下面的方向来对我所理解的接口设计做个总结:

接口参数定义 -> 接口版本化的问题 -> 接口的安全性 -> 接口的代码设计 -> 接口的可读性 -> 接口文档 -> 我遇到的坑

接口参数定义

接口设计中往可以抽象出一些新的公共参数,从事了近三年的接口开发工作中,我目前能想到了一些较为常见的公共接口参数如下:

公共参数含意定义该参数的意义
timestamp毫秒级时间戳1.客户端的请求时间标示 2.后端可以做请求过期验证 3.该参数参与签名算法增加签名的唯一性
app_key签名公钥签名算法的公钥,后端通过公钥可以得到对应的私钥
sign接口签名通过请求的参数和定义好的签名算法生成接口签名,作用防止中间人篡改请求参数
did设备ID设备的唯一标示,生成规则例如android的mac地址的md5和ios曾今udid(目前无法获取)的md5, 1:数据收集 2.便于问题追踪 3.消息推送标示

接口版本化的问题

接口设计中有个算是历史上的难题 -> 接口版本化。曾经也去调研了很多关于接口版本化的资料和设计,最后我得到的结论大致如下:

接口的版本区分为:

大版本

原则:大版本的数量最多控制到5个以内(我个人跟倾向于3个),超过版本限制的版本提示升级到新版本

方案:

  • uri携带版本号,例如:v1/user/get

  • 请求参数,例如:user/get?v=1.0

小版本

原则:自己把控吧?

方案:

  • uri携带版本号,例如:v1/user/get_01

  • 请求参数,小数点右边就是小版本,例如:user/get?v=1.1

接口的安全性

接口的设计肯定绕不开安全这两个字,为了达到尽可能的安全,我们需要尽可能的增加被攻击的难度,以下是我了解和使用到的一些常见的手段去增加接口的安全性(https这里就不讨论了):

过期验证/签名验证/重放攻击/限流/转义

伪代码如下:

  1. // 过期验证

  2. if (microtime(true)*1000 - $_REQUEST['timestamp'] > 5000) {

  3.    throw new \Exception(401, 'Expired request');

  4. }

  1. // 签名验证(公钥校验省略)

  2. $params = ksort($_REQUEST);

  3. unset($params['sign']);

  4. $sign = md5(sha1(implode('-', $params) . $_REQUEST['app_key']));

  5. if ($sign !== $_REQUEST['sign']) {

  6.    throw new \Exception(401, 'Invalid sign');

  7. }

  1. /**

  2. * 重放攻击

  3. * @params noise string 随机字符串或随机正整数,与 Timestamp 联合起来, 用于防止重放攻击 例如腾讯云是6位随机正整数

  4. */

  5. $key = md5("{$_REQUEST['REQUEST_URI']}-{$_REQUEST['timestamp']}-{$_REQUEST['noise']}-{$_REQUEST['did']}");

  6. if ($redisInstance->exists($key)) {

  7.    throw new \Exception(401, 'Repeated request');

  8. }

  1. // 限流

  2. $key = md5("{$_REQUEST['REQUEST_URI']}-{$_REQUEST['REMOTE_ADDR']}-{$_REQUEST['did']}");

  3. if ($redisInstance->get($key) > 60) {

  4.    throw new \Exception(401, 'Request limit');

  5. }

  6. $redisInstance->incre($key);

  1. // 转义

  2. $username = htmlspecialchars($_REQUEST['username']);

接口的代码设计 -> 解耦业务 即插即用

这个过程的关键字:抽象成类 前置中间件 注入

接着就是我们代码设计的层面了,如何抽象公共的部分与业务代码解耦。

一般写法, 定义个全局函数,然后每个接口开始时调用该函数:

  1. // 全局定义一个函数

  2. function check () {

  3.    // 校验公共参数

  4.    # code ...

  5.    // 校验签名

  6.    # code ...

  7.    // 校验频率

  8.    # code ...

  9.    // 等等...

  10. }

二般写法, 定义个父类方法,然后每个接口类继承该接口,构造函数调用改方法,其实和上面的换汤不换药:

  1. // 父类方法

  2. class father

  3. {

  4.    public function __construct()

  5.    {

  6.        $this->check();

  7.    }

  8.    public function check () {

  9.        // 校验公共参数

  10.        # code ...

  11.        // 校验签名

  12.        # code ...

  13.        // 校验频率

  14.        # code ...

  15.        // 等等...

  16.    }

  17. }

重点来了,我提倡的第三般写法,对象链和前置中间件:

  1. /**

  2. * 检验抽象类

  3. */

  4. abstract class Check

  5. {

  6.    /**

  7.     * 下一个check实体

  8.     *

  9.     * @var object

  10.     */

  11.    private $nextCheckInstance;

  12.    /**

  13.     * 校验方法

  14.     *

  15.     * @param Request $request 请求对象

  16.     */

  17.    abstract public function operate(Request $request);

  18.    /**

  19.     * 设置责任链上的下一个对象

  20.     *

  21.     * @param Check $check

  22.     */

  23.    public function setNext(Check $check)

  24.    {

  25.        $this->nextCheckInstance = $check;

  26.        return $check;

  27.    }

  28.    /**

  29.     * 启动

  30.     *

  31.     * @param Request $request 请求对象

  32.     */

  33.    public function start(Request $request)

  34.    {

  35.        $this->doCheck($request);

  36.        // 调用下一个对象

  37.        if (! empty($this->nextCheckInstance)) {

  38.            $this->nextCheckInstance->start($request);

  39.        }

  40.    }

  41. }

  42. // 校验公共参数类

  43. class ParamsCheck extends Check

  44. {

  45.    public function operate()

  46.    {

  47.       // 校验公共参数

  48.        # code ...

  49.    }

  50. }

  51. // 校验签名类

  52. class SignCheck extends Check

  53. {

  54.    public function operate()

  55.    {

  56.       // 校验签名

  57.        # code ...

  58.    }

  59. }

  60. // 等等...

  61. // 前置中间件类

  62. class FrontMiddleware

  63. {

  64.    public function run()

  65.    {

  66.        // 初始化一个:必传参数校验的check

  67.        $checkParams   =  new ParamsCheck();

  68.        // 初始化一个:签名check

  69.        $checkSign     =  new SignCheck();

  70.        // 初始化一个:频率check

  71.        $checkFrequent =  new FrequentCheck();

  72.        // 等等...

  73.        // 构成对象链

  74.        $checkParams->setNext($checkSign)

  75.                    ->setNext($checkFrequent)

  76.                    ...

  77.        // 启动

  78.        $checkParams->start();

  79.    }

  80. }

接口的可读性

关于可读性的不得不提到的就是RESTFUL,这里我就不讨论RESTFUL,大家可以自行补充相关知识。关于接口设计可读性的我的一些思考:

url
  • 非RESTFUL: 资源/资源/操作(动词), 例如 content/article/get -> 获取内容资源下的一篇文章资源

  • RESTFUL: 资源/资源/资源, 例如 get content/article/1 -> 获取内容资源下文章ID为1的文章资源

method
  • 非RESTFUL: get便于查nginx日志,上传资源post, 没啥硬性要求

  • RESTFUL: 符合RESTFUL的思想

request params

个人更青睐于下划线命名,适当的单词缩写

response params

响应的code要符合http status

  • 200 -> 正常

  • 400 -> 缺少公共必传参数或者业务必传参数

  • 401 -> 接口校验失败 例如签名

  • 403 -> 没有该接口的访问权限

  • 499 -> 上游服务响应时间超过接口设置的超时时间

  • 500 -> 代码错误

  • 501 -> 不支持的接口method

  • 502 -> 上游服务返回的数据格式不正确

  • 503 -> 上游服务超时

  • 504 -> 上游服务不可用

  1. // 响应的格式

  2. {

  3.    "code": 200,

  4.    "msg": "ok",

  5.    "data": {

  6.    }

  7. }

接口文档

好的接口文档就是生产力, swagger + api blueprint 自行google吧?

我遇到的坑

这里遇到的一个比较大的坑就是http协议历史遗留的bug:

不区分url里的空格 和加号➕

带来的问题就是urldecode会把参数里的+号转为空格,所以这种场景的就得使用rawurldecode防止+转成空格。比如做接口的参数校验的时候~

640?wx_fmt=jpeg

MFC编程中“占位符和动态创建”技巧的应用

 刘勇  夏安邦       在MFC编程中,“占位符和动态创建”技巧的应用是十分广泛的,虽然在有些编程书籍和文章中有所涉及,但缺乏系统的介绍和必要的总结,给应用带来不便。本文将对这方面的编程技巧进行...
  • think77
  • think77
  • 2001-01-06 16:43:00
  • 736

我所理解的Cocos2dx pdf 下载 地址

  • 2016年01月16日 17:00
  • 455B
  • 下载

我所理解的cocos2dx pdf

  • 2016年08月24日 23:02
  • 31B
  • 下载

我所理解的Cocos2d-x高清扫描版,带完整书签

  • 2018年01月28日 10:42
  • 67.11MB
  • 下载

我所理解的Cocos2d-x

  • 2017年12月19日 16:40
  • 82.65MB
  • 下载

我所理解的Cocos2d-x ,秦春林

  • 2017年09月19日 16:00
  • 62.47MB
  • 下载

《我所理解的Cocos2d-x》PDF

《我所理解的Cocos2d-x》 针对最新的 Cocos2d-x 3.x版本,介绍了Coco2d-x游戏引擎的基本架构、渲染机制,以及各个子模块的功能和原理,并结合OpenGL ES...
  • qq_25327609
  • qq_25327609
  • 2016-05-09 16:35:47
  • 6855

我所理解的Cocos2d-x 影印版 pdf

  • 2017年06月15日 09:49
  • 181.87MB
  • 下载

我所理解的Cocos2d-x 全新的Cocos2d-x3.0

新特性回归C++风格对于一个纯C++语言的引擎来说,那种原汁原味的C++风格才是开发者最为习惯的。 使用命名空间代替“CC”前缀。 使用clone()代替copyt()方法。与以前的copy方法不同是...
  • sinat_24229853
  • sinat_24229853
  • 2016-07-20 12:23:42
  • 671

我所理解的设计模式(C++实现)—— “一句话总结”和索引

我所理解的设计模式系列已经全部完成,在写该系列的过程中收获很多。俗话说得好,好记性不如烂笔头,写出来不单单可以增加自己对知识的理解,也可以很好的分享给大家,大家共同学习。这里是最后一篇,来给所有的设计...
  • LCL_data
  • LCL_data
  • 2013-09-28 07:22:46
  • 10370
收藏助手
不良信息举报
您举报文章:我所理解的接口设计
举报原因:
原因补充:

(最多只允许输入30个字)