摘要:前言自己做接口開發(fā)的時間也算不短了三年,想寫這篇文章其實差不多已經(jīng)有一年多的時間了。
前言
自己做接口開發(fā)的時間也算不短了(三年),想寫這篇文章其實差不多已經(jīng)有一年多的時間了。我將從下面的方向來對我所理解的接口設計做個總結:
接口參數(shù)定義 -> 接口版本化的問題 -> 接口的安全性 -> 接口的代碼設計 -> 接口的可讀性 -> 接口文檔 -> 我遇到的坑接口參數(shù)定義
接口設計中往可以抽象出一些新的公共參數(shù),從事了近三年的接口開發(fā)工作中,我目前能想到了一些較為常見的公共接口參數(shù)如下:
公共參數(shù) | 含意 | 定義該參數(shù)的意義 |
---|---|---|
timestamp | 毫秒級時間戳 | 1.客戶端的請求時間標示 2.后端可以做請求過期驗證 3.該參數(shù)參與簽名算法增加簽名的唯一性 |
app_key | 簽名公鑰 | 簽名算法的公鑰,后端通過公鑰可以得到對應的私鑰 |
sign | 接口簽名 | 通過請求的參數(shù)和定義好的簽名算法生成接口簽名,作用防止中間人篡改請求參數(shù) |
did | 設備ID | 設備的唯一標示,生成規(guī)則例如android的mac地址的md5和ios曾今udid(目前無法獲取)的md5, 1:數(shù)據(jù)收集 2.便于問題追蹤 3.消息推送標示 |
接口設計中有個算是歷史上的難題 -> 接口版本化。曾經(jīng)也去調研了很多關于接口版本化的資料和設計,最后我得到的結論大致如下:
接口的版本區(qū)分為
大版本
原則:大版本的數(shù)量最多控制到5個以內(我個人跟傾向于3個),超過版本限制的版本提示升級到新版本
方案
uri攜帶版本號,例如:v1/user/get
請求參數(shù),例如:user/get?v=1.0
小版本
原則:自己把控吧?
方案
uri攜帶版本號,例如:v1/user/get_01
請求參數(shù),小數(shù)點右邊就是小版本,例如: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 隨機字符串或隨機正整數(shù),與 Timestamp 聯(lián)合起來, 用于防止重放攻擊 例如騰訊云是6位隨機正整數(shù) */ $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"]);接口的代碼設計 -> 解耦業(yè)務 即插即用
這個過程的關鍵字:抽象成類 前置中間件 注入
接著就是我們代碼設計的層面了,如何抽象公共的部分與業(yè)務代碼解耦。
一般寫法, 定義個全局函數(shù),然后每個接口開始時調用該函數(shù):
// 全局定義一個函數(shù) function check () { // 校驗公共參數(shù) # code ... // 校驗簽名 # code ... // 校驗頻率 # code ... // 等等... }
二般寫法, 定義個父類方法,然后每個接口類繼承該接口,構造函數(shù)調用改方法,其實和上面的換湯不換藥:
// 父類方法 class father { public function __construct() { $this->check(); } public function check () { // 校驗公共參數(shù) # 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->operate($request); // 調用下一個對象 if (! empty($this->nextCheckInstance)) { $this->nextCheckInstance->start($request); } } } // 校驗公共參數(shù)類 class ParamsCheck extends Check { public function operate() { // 校驗公共參數(shù) # code ... } } // 校驗簽名類 class SignCheck extends Check { public function operate() { // 校驗簽名 # code ... } } // 等等... // 前置中間件類 class FrontMiddleware { public function run() { // 初始化一個:必傳參數(shù)校驗的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: 個人更青睞于下劃線命名,適當?shù)膯卧~縮寫
response params: 響應的code要符合http status
200 -> 正常
400 -> 缺少公共必傳參數(shù)或者業(yè)務必傳參數(shù)
401 -> 接口校驗失敗 例如簽名
403 -> 沒有該接口的訪問權限
499 -> 上游服務響應時間超過接口設置的超時時間
500 -> 代碼錯誤
501 -> 不支持的接口method
502 -> 上游服務返回的數(shù)據(jù)格式不正確
503 -> 上游服務超時
504 -> 上游服務不可用
// 響應的格式 { "code": 200, "msg": "ok", "data": { } }接口文檔
好的接口文檔就是生產(chǎn)力, swagger + api blueprint 自行google吧?
我遇到的坑這里遇到的一個比較大的坑就是http協(xié)議歷史遺留的bug:
不區(qū)分url里的空格 和加號?
帶來的問題就是urldecode會把參數(shù)里的+號轉為空格,所以這種場景的就得使用rawurldecode防止+轉成空格。比如做接口的參數(shù)校驗的時候~
掃面下方二維碼關注我的技術公眾號,及時為大家推送我的原創(chuàng)技術分享
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/28399.html
摘要:眾多面向對象的編程思想雖不盡一致,但是無論哪種面向對象編程語言都具有以下的共通功能。原型編程以類為中心的傳統(tǒng)面向對象編程,是以類為基礎生成新對象。而原型模式的面向對象編程語言沒有類這樣一個概念。 什么是面向對象?這個問題往往會問到剛畢業(yè)的新手or實習生上,也是往往作為一個技術面試的開頭題。在這里我們不去談如何答(fu)好(yan)問(guo)題(qu),僅談談我所理解的面向對象。 從歷...
摘要:將圖片都放入文件夾下指定公共的名字。匹配刪除的文件根目錄開啟在控制臺輸出信息啟用刪除文件插入開關說一些可能沒用的站在前端角度不懂的很多很多時候一個項目都是由一個小組完成的,小組成員可能包括產(chǎn)品,前端,后端,測試,運營等等。 不急,先聽我嘮會嗑~ 隨著js發(fā)展的如此迅速,市場上越來越多的前端框架可以方便開發(fā)者使用。 本人大四渣渣一名,先后實習了兩個地方,第一家公司用vuejs,實話...
摘要:其他交互一般會遵循一些數(shù)據(jù)結構協(xié)議或者狀態(tài)值,比如不同的操作結果對應不同的狀態(tài)值,且出錯會返回指定的錯誤信息方便前端進行提示等。 RESTful這種架構已經(jīng)具有很長的時間和歷程了,但似乎最近restful這個詞出現(xiàn)的頻率特別高,目前不是很清楚是因為我自個兒現(xiàn)在是以restful風格寫程序產(chǎn)生的孕婦效應,還是單頁面程序開發(fā)的流行造成的。 其實一開始我也是不想寫這篇文章的,因為網(wǎng)絡上與re...
摘要:和組合的語義定義了一種減少復雜性的方式,避免傳統(tǒng)多繼承和類相關典型問題。隊列的目的是將耗時的任務延時處理,比如發(fā)送郵件,從而大幅度縮短請求和相應的時間。同樣的道理,根據(jù)引入不同的來完成對應的功能。 showImg(https://segmentfault.com/img/remote/1460000010868178); Trait 概念 在常規(guī)的 PHP 開發(fā)中,我們都習慣于先編寫一...
摘要:定義在父類中定義處理流程的框架,在子類中實現(xiàn)具體處理的模式就稱為模板方法模式參與角色抽象類抽象類不僅負責實現(xiàn)模板方法,還負責聲明在模板方法中所使用到的抽象方法。 定義 在父類中定義處理流程的框架,在子類中實現(xiàn)具體處理的模式就稱為模板方法模式 參與角色 抽象類(AbstractClass) 抽象類不僅負責實現(xiàn)模板方法,還負責聲明在模板方法中所使用到的抽象方法。 具體類(子類) 該角色負責...
閱讀 1778·2021-11-15 11:37
閱讀 3048·2021-11-04 16:05
閱讀 1917·2021-10-27 14:18
閱讀 2748·2021-08-12 13:30
閱讀 2493·2019-08-29 14:18
閱讀 2080·2019-08-29 13:07
閱讀 2017·2019-08-27 10:54
閱讀 2718·2019-08-26 12:15