在当下移动互联网火得发烫的潮流下,如果不会开发API你都不好意思说你是PHP程序员。这里探讨一下如何用宇宙第一语言相对优雅的开发API。
没有规矩不成方圆。提高规范性不只是为了看起来整齐,更重要的是让代码维护起来不那么闹心。这里谈两点:
首先,返回值要有统一的结构。通常来说长这个样子:
{
code : 200,
msg : "success",
data : {
uid : 11
}
}code是本次请求的状态码。不仅可以表示成功,最重要的是反映出各种不同的错误状态。一般来说错误分为两种:系统错误和业务错误。系统错误是指与具体接口无关的错误。比如:签名错误,数据库连接不上之类的。而业务错误则是根据业务规则报的错,比如:获得订单详情的接口,你瞎编了个订单号过去,它会返回给你“非法订单号”的错误。同时,关于错误的定义和说明,可以统一维护一个前后端工程师都能看到的文档里,方便开发时保持一致。另外,错误码的定义也有一些推荐规则。可以对错误进行分类,然后错误码按位拼接。举个栗子:将错误码规定为6位,其中第一位是大类别,1代表系统错误,2代表业务错误;第二、三位定义错误类型,01表示订单错误,02表示用户相关错误,03表示购物车错误等等;第三到六位表示具体错误,但是依托于前面几位,比如订单错误类别下,001表示订单号不存在,002表示订单已取消。于是,当我看到201001这样的错误码,我可能不用看文档就大概知道这是个业务错误,而且是个订单方面的问题。这样的定义方法类似身份证号。一看数字你就大概知道很多信息了,比单纯的一堆数字要有更好的可读性。
msg表示提示信息。一般是对状态的一个描述,用来在APP上显示给用户。
data就是获得的返回数据主体啦。值得注意的是,不同接口之间对于同一业务含义的字段最好保持同一个名字。比如:订单号,可以统一叫order_id。不要在订单列表API里叫order_id,跑到订单详情API那里又叫id。这种不一致容易产生莫名其妙的bug。
其次,返回值建议使用JSON格式。这是个简洁、优雅、可读性强、跨语言的数据类型。对于多中不同编程语言的通信是一个不错的选择。但是有些小问题需要注意:
-
头信息。发送正确的头信息
Content-Type: application/json;charset=utf-8。这样避免一些编码方面的麻烦。 -
空数组问题。当关联数组的值为空时,PHP的
json_encode()函数会将其处理成"[]",这会导致 Android 和 IOS 的APP崩溃。解决方法很简单,将数组强转成空对象即可。可以有这样两种写法:$output = (object)[];$output = new \stdClass;
多做些日志是很有必要的。
记录系统指标,比如:请求参数、占用内存、响应时间、响应时长等信息,方便性能监控。当发现比较慢或者占用资源很多的API,就可以进行优化了。有日志在就比较容易进行比较。
记录错误请求则可以发现恶意尝试和一些业务特点。如果你发现大量签名错误的请求来自少数几个IP,那就基本可以把他们拉黑了。因为正常的请求不可能会报这个错误的。如果你发现某个表单域的值为空的错误经常报,说明表单设计不够醒目,用户想不起来填这个东西就提交了。这些日志都有助于提高产品质量。
API与网站不同。网站面向所有人,访问者多多益善。API呢?原则上应该只许APP访问。如果不速之客也可以访问的话,那将是场灾难。所以采取些措施降低技术风险是非常必要的。
如果API链接和参数列表被发现了,如何保证攻击者无法拼凑参数来调用我们的API?常见做法就是秘钥和签名。它们就是用来抵挡这些攻击者的篡改参数的恶意请求的。
具体做法是这样的:为每个调用方分配一个clientID及与之对应的秘钥,同时约定一个统一的签名算法。调用者每次发出请求都用秘钥和签名算法将API的入参进行加密生成一个签名,并带着clientID一起去请求API。那么服务器端又会发生什么呢?服务器代码拿到clientID,找到了对应的秘钥。用秘钥和签名算法去计算入参,也得出一个签名来。这时候和调用方传过来的签名比较,如果一模一样,说明是合法调用者,可以正常给出响应值。如果不一致呢?对不起,你是入侵者!
值得一提的是,用clientID来区分调用者可以实现对不同调用方进行权限管理的功能。仅赋予某个调用方必要的那些API,其他的API嘛,即使它签名对了也不给访问。这种权限最小化的好处就是提高了安全性。而且,为什么不让所有调用者使用相同的秘钥的?想想一下,如果某一个调用者把秘钥泄露了,那么API就全都透明了啊。而各自拥有不同的秘钥和权限范围则可以让这种悲剧发生时的损失降到最小。江湖险恶,不得不防啊!
秘钥搞定了,签名算法呢?其实也有些门道的。
- 第一,最好是单向加密算法,而不是双向加密算法。就是像md5这种只有加密没有解密的算法。本来你就是生成签名的,解密干嘛?等着被虐吗?
- 第二、秘钥相关。秘钥一定要参与算法,确保秘钥不同则结果不同,否则秘钥不是没用了?
- 第三、多语言可实现。不能光顾着自己high,你API是PHP写的,人家APP可不是啊!
对于安全性要求比较高的场景时,可以考虑令牌机制。这个令牌有这样一些特性:
- 没有令牌,无法请求API。所以,请求API前都要先通过固定API来请求一个令牌来。
- 令牌是一次性的。用过一次后,这个令牌就失效了。
- 令牌有有效期。申请的令牌如果过期了,即使没有使用过也作废了。
这种方案的好处是安全性高了,但是代价是请求量翻倍了。需要视具体要求来选择。