前言从事界面开发已经很久了(三年),距离想写这篇文章也快一年了。我会从以下几个方向来总结我所理解的接口设计:接口参数定义->接口版本问题->接口安全->接口代码设计->接口可读性->接口文档->我遇到的坑接口参数定义在接口设计中,一些新的公共参数往往可以被抽象出来。经过近三年的接口开发工作,我能想到的一些常用的公共接口参数如下:public参数含义定义该参数的含义timestamp毫秒级时间戳1.客户端的请求时间戳2.后端可以做请求过期验证3.该参数参与签名算法增加签名的唯一性app_keysignaturepublickey签名算法的公钥,后端可以通过公钥获取对应的私钥签名接口签名。接口签名通过请求参数和定义的签名算法生成,防止中间人篡改请求参数。md5和ios曾经是udid的md5(目前不可用),1:数据采集2.容易跟踪问题3.消息推送提示接口版本问题接口设计->接口版本存在历史问题。也查阅了很多关于界面版本控制的资料和设计,最后得出以下结论:界面的版本划分为大版本原则:大版本数量限制在5个以内(我个人更喜欢3个),超过版本限制的版本提示升级到新版本。schemeuri携带版本号,例如:v1/user/get请求参数,例如:user/get?v=1.0小版本原则:自己控制?schemeuri携带版本号,例如:v1/user/get_01请求参数,小版本在小数点右边,例如:user/get?v=1.1接口安全接口的设计不得绕过安全这个词,为了达到尽可能的安全,我们需要尽可能的增加被攻击的难度。以下是我知道并使用的一些增加接口安全性的常用方法(https这里不做讨论):过期验证/签名验证/重放攻击/限制stream/escaping伪代码如下://Expirationverificationif(microtime(true)*1000-$_REQUEST['timestamp']>5000){thrownew\Exception(401,'Expiredrequest');}//签名验证(省略公钥验证)$params=ksort($_REQUEST);unset($params['sign']);$sign=md5(sha1(implode('-',$params).$_REQUEST['app_key']));if($sign!==$_REQUEST['sign']){thrownew\Exception(401,'Invalidsign');}/***Replayattack*@paramsnoisestring随机字符串或随机正整数,结合Timestamp,用于防止重放攻击。比如腾讯云是6位随机正整数*/$key=md5("{$_REQUEST['REQUEST_URI']}-{$_REQUEST['timestamp']}-{$_REQUEST['noise']}-{$_REQUEST['did']}");if($redisInstance->exists($key)){thrownew\Exception(401,'Repeatedrequest');}//当前限制$key=md5("{$_REQUEST['REQUEST_URI']}-{$_REQUEST['REMOTE_ADDR']}-{$_REQUEST['did']}");if($redisInstance->get($key)>60){thrownew\Exception(401,'Requestlimit');}$redisInstance->incre($key);//转义$username=htmlspecialchars($_REQUEST['username']);接口代码设计->解耦业务即插即用过程中的关键词:抽象到前置中间件注入然后我们代码设计的层面,如何将抽象公共部分和业务代码解耦一般写法,定义一个全局函数,然后在每个接口的开头调用该函数://全局定义一个函数functioncheck(){//验证公共参数#code...//验证签名#code...//检查频率#代码...//等等...}第二种一般的写法是定义一个父类方法,然后每个接口类继承该接口,构造函数调用修改后的方法,即其实和上面的替换不同Dressing://parentmethodclassfather{publicfunction__construct(){$this->check();}publicfunctioncheck(){//验证公共参数#code...//验证签名#code...//检查频率#code...//等等...}}关键点来了,我提倡的第三种通用写法,对象链和前置中间件:/***验证方法**@paramRequest$request请求对象*/abstractpublicfunctionoperate(Request$request);/***设置责任链上的下一个对象**@paramCheck$check*/publicfunctionsetNext(Check$check){$this->nextCheckInstance=$check;返回$支票;}/***开始**@paramRequest$request请求对象*/公共功能开始(请求$request){$this->operate($request);//调用下一个对象if(!empty($this->nextCheckInstance)){$this->nextCheckInstance->start($request);}}}//检查公共参数类classParamsCheckextendsCheck{publicfunctionoperate(){//检查公共参数#code...}}//检查签名类classSignCheckextendsCheck{publicfunctionoperate(){//验证签名#code...}}//Wait...//FrontMiddlewareclassclassFrontMiddleware{publicfunctionrun(){//初始化一个:必须通过参数验证检查$checkParams=newParamsCheck();//初始化一:签名校验$checkSign=newSignCheck();//初始化一个:频率检查$checkFrequent=newFrequentCheck();//等待...//形成对象链$checkParams->setNext($checkSign)->setNext($checkFrequent)...//开始$checkParams->start();}}界面的可读性说到可读性就不得不说RESTFUL,这这里不讨论RESTFUL,大家可以自行补充相关知识。我对界面设计可读性的思考:url不是RESTFUL:resource/resource/operation(动词),比如content/article/get->getcontentresources一篇文章资源RESTFUL:resource/resource/resource,比如getcontent/article/1->获取内容资源下文章ID为1的文章资源。方法不RESTFUL:get方便查看nginx日志和上传资源帖子,不死板RequireRESTFUL:符合RESTFUL的思路requestparams:个人更喜欢下划线命名,合适的单词缩写responseparams:的代码响应必须符合httpstatus200->正常400->缺少公共强制参数或业务必填参数401->接口验证失败,如签名403->没有该接口的访问权限499->上游服务响应时间超过接口设置的超时时间500->代码错误501->不支持的接口方法502->上游服务返回的数据格式不正确503->上游服务超时504->上游服务不可用//响应格式{"code":200,"msg":"ok","data":{}}InterfaceDocument好的接口文档就是生产力,Swagger+apiblueprint自己Google一下吧?我遇到的坑。我这里遇到的比较大的一个坑是http协议历史遗留的bug:url中没有区分空格和加号导致的问题是urldecode会把参数中的+号进行转换变成空格,所以在这种情况下,必须使用rawurldecode来防止+被转换成空格。比如在做接口参数验证的时候~扫描下方二维码关注我的技术公众号,及时为大家推送我的原创技术分享
