博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
我所理解的接口设计
阅读量:6300 次
发布时间:2019-06-22

本文共 4473 字,大约阅读时间需要 14 分钟。

前言

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

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

接口参数定义

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

公共参数 含意 定义该参数的意义
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这里就不讨论了):

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

伪代码如下:

// 过期验证if (microtime(true)*1000 - $_REQUEST['timestamp'] > 5000) {    throw new \Exception(401, 'Expired request');}复制代码
// 签名验证(公钥校验省略)$params = ksort($_REQUEST);unset($params['sign']);$sign = md5(sha1(implode('-', $params) . $_REQUEST['app_key']));if ($sign !== $_REQUEST['sign']) {    throw new \Exception(401, 'Invalid sign');}复制代码
/** * 重放攻击 * @params noise string 随机字符串或随机正整数,与 Timestamp 联合起来, 用于防止重放攻击 例如腾讯云是6位随机正整数 */$key = md5("{$_REQUEST['REQUEST_URI']}-{$_REQUEST['timestamp']}-{$_REQUEST['noise']}-{$_REQUEST['did']}");if ($redisInstance->exists($key)) {    throw new \Exception(401, 'Repeated request');}复制代码
// 限流$key = md5("{$_REQUEST['REQUEST_URI']}-{$_REQUEST['REMOTE_ADDR']}-{$_REQUEST['did']}");if ($redisInstance->get($key) > 60) {    throw new \Exception(401, 'Request limit');}$redisInstance->incre($key);复制代码
// 转义$username = htmlspecialchars($_REQUEST['username']);复制代码

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

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

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

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

// 全局定义一个函数function check () {    // 校验公共参数    # code ...    // 校验签名    # code ...    // 校验频率    # code ...    // 等等...}复制代码

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

// 父类方法class father{    public function __construct()    {        $this->check();    }    public function check () {        // 校验公共参数        # code ...        // 校验签名        # code ...        // 校验频率        # code ...        // 等等...    }}复制代码

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

/** * 检验抽象类 */abstract class Check{    /**     * 下一个check实体     *     * @var object     */    private $nextCheckInstance;        /**     * 校验方法     *     * @param Request $request 请求对象     */    abstract public function operate(Request $request);    /**     * 设置责任链上的下一个对象     *     * @param Check $check     */    public function setNext(Check $check)    {        $this->nextCheckInstance = $check;        return $check;    }    /**     * 启动     *     * @param Request $request 请求对象     */    public function start(Request $request)    {        $this->doCheck($request);        // 调用下一个对象        if (! empty($this->nextCheckInstance)) {            $this->nextCheckInstance->start($request);        }    }}// 校验公共参数类class ParamsCheck extends Check{    public function operate()    {       // 校验公共参数        # code ...     }}// 校验签名类class SignCheck extends Check{    public function operate()    {       // 校验签名        # code ...     }}// 等等...// 前置中间件类class FrontMiddleware{    public function run()    {        // 初始化一个:必传参数校验的check        $checkParams   =  new ParamsCheck();        // 初始化一个:签名check        $checkSign     =  new SignCheck();        // 初始化一个:频率check        $checkFrequent =  new FrequentCheck();        // 等等...        // 构成对象链        $checkParams->setNext($checkSign)                    ->setNext($checkFrequent)                    ...        // 启动        $checkParams->start();    }}复制代码

接口的可读性

关于可读性的不得不提到的就是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 -> 上游服务不可用
// 响应的格式{    "code": 200,    "msg": "ok",    "data": {    }}复制代码

接口文档

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

我遇到的坑

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

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

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

转载地址:http://vzwxa.baihongyu.com/

你可能感兴趣的文章
物联网全面升级,十年内推动工业进入智能化新阶段
查看>>
spring-通过ListFactory注入List
查看>>
一种基于SDR实现的被动GSM嗅探
查看>>
阿里云ECS每天一件事D1:配置SSH
查看>>
SQL Server 性能调优(性能基线)
查看>>
uva 10801 - Lift Hopping(最短路Dijkstra)
查看>>
[Java Web]servlet/filter/listener/interceptor区别与联系
查看>>
POJ 2312Battle City(BFS-priority_queue 或者是建图spfa)
查看>>
从零开始学MVC3——创建项目
查看>>
CentOS 7 巨大变动之 firewalld 取代 iptables
查看>>
延时任务和定时任务
查看>>
linux下的权限问题
查看>>
教你如何使用Flutter和原生App混合开发
查看>>
Spring Boot 整合redis
查看>>
CSS hover改变背景图片过渡动画生硬
查看>>
JDBC(三)数据库连接和数据增删改查
查看>>
淘宝应对"双11"的技术架构分析
查看>>
ssh
查看>>
订单的子单表格设置颜色
查看>>
Office365 Exchange Hybrid 番外篇 ADFS后端SQL群集(一)
查看>>