某神祕公司 RESTful、共用接口、前後端分離、接口約定的實踐


閱讀本文大概需要 4.6 分鐘。


本文來自

https://juejin.im/post/59eafab36fb9a045076eccc3


前言

隨着互聯網高速發展,公司對項目開發週期不斷縮短,我們面對各種需求,使用原有對接方式,各端已經很難快速應對各種需求,更難以提高效率。於是,我們不得不重新制定對接規範、開發邏輯以便快速上線項目。

我們的目標

  1. 儘可能的縮小溝通的成本,開最少的會,確定大部分的事。

  2. 花最少的時間寫文檔,保證90%的開發人員看懂所有內容。

  3. 哪怕不看文檔,也能知道各種接口邏輯。

  4. 不重複寫代碼

  5. 儘可能的寫高可讀性的代碼

我們做了哪些事

  1. 完成了前後端分離

  2. Android、ios、web共用一套接口

  3. 統一接口規範(post、put、get、patch、delete)

  4. 統一了調試工具

  5. 統一了接口文檔

之前的我們

接口是這樣子的:


接口地址含義請求方式

…/A項目/模塊1/getProducts

獲得產品

GET

…/A項目/模塊1/addProduct

添加產品

POST

…/A項目/模塊1/getProductDetail

獲得產品詳情

GET

…/A項目/模塊1/editProduct

修改產品

POST

客戶端請求是這樣的:

  • …/A項目/模塊1/getProducts?id=1&a=2&b=3&c=4&d=5…………

  • A頁面=====》B頁面(攜帶n個變量)====》C頁面(攜帶m個變量,包含i個A頁面的變量) -------經常n>4

  • 大部分請求是POST,至於put、patch、delete是什麼鬼,關我屁事。

  • 關於接口入參使用json,那完全是看開發心情。

出參是這樣的:

{"message":"success","code":0,"data":具體內容}
其中data裏包含數組可能是
[{"a":"1","b":"1"},{"a":"1","b":"1"},{"a":"1","b":"1"},{"a":"1","b":"1"}]
即使下一個頁面用到也不會使用id,而是把所有字段都傳進去。
A接口中,返回產品用product;B接口中使用good,多個接口很可能不統一。

客戶端對接是這樣子的:

  • 安卓、ios一套;部分接口各自用一套;html5端一套。

  • 客戶端和後臺是不停交流的

接口文檔是這樣的

img

img

  1. swagger

  2. 阿里的rap

  3. Word文檔

  4. 其它

當然了,我覺得swagger和rap神器都是非常強大的,能夠實現各種功能邏輯,但是考慮到開發人員掌握程度不通,複雜度較高,難以提高效率,我決定初期並不使用這兩樣神器。

後端是這樣的

…/A項目/模塊1/getProducts ----接口
…/A項目/模塊1/Products.html ----頁面
…/A項目/模塊1/Products.js ----靜態資源

接口和靜態資源纏在一塊,畢竟很多頁面可能是一位開發人員同時開發前端、後端,這裏的弊端是,只需要自己清楚邏輯,很多做法臨時應付,方案並不優雅,別人也很難看懂。一旦這位同事離職,很多說不清的邏輯就留給後人採坑了。

等等…………

重構

下面步入正題,面對以上衆多問題聊聊我是如何重新制定流程的

數據庫約定

約定數據庫裏所有表必須包含名爲id主鍵字段。
可能有人會說,正常來說不是每張表裏都應該有id主鍵嗎?但是,我們項目中由於之前開發不嚴謹,部分表沒有id主鍵,或者不爲id的主鍵。這裏我們採用分佈式的全球唯一碼來作爲id。

api出參約定

約定所有出參裏含list,且其他請求會用到這組list,則list裏所有對象必須含id唯一標識。

入參約定

約定token身份認證統一傳入參數模式,後端採用aop切面編程識別用戶身份。其他參數一律爲json。

resultfull接口約定

首先我們選擇一個名詞複數,比如產品

post方法

新增一條XXX
比如 ……/products 則代表新增一條產品
入參json如下:

{    "name":"我是一款新產品",    "price":100,    "kind":"我的分類",    "pic":[一組圖片],    等等還有很多}

java 代碼control層

    @ResponseBody    @RequestMapping(value = "/A項目/B模塊/products", method = {RequestMethod.POST})    public ResultObject getProducts() {        //具體邏輯。    }

put方法

新增某條XXX記錄
比如 ……/products/1111111111
入參json如下:

{    "name":"我是一款新產品",    "price":100,    "kind":"我的分類",    "pic":[一組圖片],    等等還有很多}

表示增加一條1111111111id的記錄
java代碼control層

    @ResponseBody    @RequestMapping(value = "/A項目/B模塊/products/{id}", method = {RequestMethod.PUT})    public ResultObject putProducts(@PathVariable(value = "id") String id) {        //具體邏輯。    }

get方法

獲得所有XXX
……/products 則代表獲取所有產品
因爲有分頁,所以我們後面加了?page=1&pageSize=50

我們約定了所有名詞複數,都會返回list,且list每個對象都有字段爲id的唯一id。
比如

{    "data":{"list":[{"id":"唯一id","其他很多字段":""},{"id":"唯一id","其他很多字段":""}],"page":1,其他字段},    "code":0,    "message":"成功"}

……/products/{id} 獲取某個具體產品(一定比列表更詳細)

比如某個具體產品裏還包含一個list,如該產品推薦列表,則:
……/products/{id}/recommendations

假設它包含的不是一個list,而是對象,比如產品佣金信息,則:
……/products/{id}/Commission

這裏我們以是否名詞複數來判斷是對象還是list.

java代碼control層

    @ResponseBody    @RequestMapping(value = "/A項目/B模塊/products/{id}", method = {RequestMethod.GET})    public ResultObject putProducts(@PathVariable(value = "id") String id) {        //具體邏輯。    }

patch 方法

更新局部XXX產品YYY信息
入參是post方法時入參的子集,所有支持更新的參數會說明,並不是支持所有變量
……/products/{id}

{    "name":"我是一款新產品",    "price":100,    部分變量}

java代碼control層

    @ResponseBody    @RequestMapping(value = "/A項目/B模塊/products/{id}", method = {RequestMethod.PATCH})    public ResultObject putProducts(@PathVariable(value = "id") String id) {        //具體邏輯。    }

delete方法

刪除XXX記錄
……/products/11111

刪除11111產品。
java代碼control層

    @ResponseBody    @RequestMapping(value = "/A項目/B模塊/products/{id}", method = {RequestMethod.DELETE})    public ResultObject putProducts(@PathVariable(value = "id") String id) {        //具體邏輯。    }

其他說明

我們儘可能少的使用動詞,但有一些行爲需要使用動詞,比如登錄等。
關於版本號,我們打算在模塊後增加/v1/等標識。

權限約定

服務端要對用戶角色進行判斷,是否有權限執行某個邏輯。

前後端分離約定

後端以開發接口爲主,不再參與頁面開發,或者混合式jsp頁面開發,統一以接口形式返回,前端通過js渲染數據以及處理邏輯。

共用接口

web、Android、ios使用統一接口,不在因爲哪方特殊要求額外開放接口。

使用統一dao層生成工具

基於mybatis-generator改造成適合我們項目的dao層以及部分service層,內部共同維護共同使用。

使用postman最爲接口文檔、調試工具

雖然有上文中介紹的rap和swagger都是特別牛的接口神器,但是我們還是選擇了postman,開發人員將接口名稱、說明、入參、出參,以及各種出參示例都存儲,這樣開發直接可以看得清接口含義。

我們建議使用瀏覽器插件,這裏以360極速瀏覽器爲例。
插件下載地址:https://download.csdn.net/download/qq273681448/10033456

打開360瀏覽器擴展中心,然後勾選開發者模式,再點擊加載已解壓的擴展程序,選中壓縮包解壓後的目錄,最後點擊運行即可。

img

其中出參註釋、及接口說明,寫在tests裏:

/*這裏是接口說明,和每個出參、入參的意思。*/

img

接口按模塊劃分爲文件夾:

img

入參:

img

出參示例:

img

正常請求:

img

開發人員即可直接看到接口示例進行開發,而開發人員開發的時候,自己調用一次即可保存爲postman文件,爲了加快上線,我們允許將java中實體類變量定義的代碼(含註釋)直接複製粘貼出來。

js等靜態資源緩存問題

從短期角度上講,我的要求是減少js文件的變更,如果有變更,務必更改版本號。那麼如何減少修改,我們的做法是將一部分js寫在html內,反正前後端分離,大不了刷新一下cdn的節點緩存,畢竟大部分瀏覽器也不會主動緩存html文件(大部分瀏覽器會緩存js等文件)。

統一js請求框架

這裏我們使用angular js的請求框架,因爲我們內部對angularjs使用較多,比較熟悉,封裝後的請求,可以自動彈窗錯誤請求,可複寫錯誤回調。

目前效果

目前,我們客戶端看到接口,大概能說出其意思,也能猜出一連串接口的含義,比如
……/classes
可以看出它是獲取班級列表接口,猜到

……/classes/id get獲取id爲id的班級詳情
……/classes/id patch 修改班級信息
……/classes/id delete 刪除班級信息

至於入參,patch是post的子集、put=patch、delete無入參。

而入參含義,直接打開postman可以直接查看每個字段的含義,並且,可以實時調取開發環境數據(非開發人員電腦),這裏我們使用了多環境。

前端使用統一封裝後的js請求框架也加快了開發進度,不用造輪子。

開發人員,一般代碼開發寫好,使用postman自我測試,測試完成後,接口文檔也就寫好了。

測試人員想了解接口文檔的也可以直接使用postman進行導入查看。

至此,我們交流成本下降了一大半,剩下開會的內容就是按ui分解需求或者按ui施工了。

總結

經過一番的折騰,開發進度總算快了點,也一定程度上達到了快速上線項目的效果。關於restful風格api,每個人都有自己的見解,只要內部約定清楚,能儘可能少的減少溝通,我覺得就是好的理解。

至於接口工具,可能很多人會說爲什麼不用之前的,我覺得以後還是會用的,最好能做到插件自動化生成api,但是對java開發註釋要求比較嚴格,隨意慢慢來吧,畢竟後面我們還有很多路要走。




往期精彩回顧

IDEA一定要懂的32條快捷鍵

把 14 億中國人都拉到一個微信羣在技術上能實現嗎?

世上最污技術解讀,我竟然秒懂了。

Bing掛了!百度又“贏了”

遇到賣茶女,應該如何優雅的回覆她

我被程序員坑了600萬致公司倒閉,當事人逐條反駁:這鍋我不背

房東:你敢申報,我就漲房租!京東取消年終獎

請不要嘗試簡化這些代碼

首例"×××"被罰!廣東警方對“×××”上網者重罰

一千行MySQL詳細學習筆記(值得學習與收藏)

七點建議助您寫出優雅的Java代碼

歡迎關注我的公衆號「程序員的成長之路」,閱讀更多精彩!      


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章