APIJSON簡介:
APIJSON是一種JSON傳輸結構協議。
客戶端可以定義任何JSON結構去向服務端發起請求,服務端就會返回對應結構的JSON字符串,所求即所得。
一次請求任意結構任意數據,方便靈活,不需要專門接口或多次請求。
支持增刪改查、模糊搜索、遠程函數調用等。還能去除重復數據,節省流量提高速度!
從此HTTP傳輸JSON數據沒有接口,更不需要文檔!
客戶端再也不用和服務端溝通接口或文檔問題了!再也不會被文檔各種錯誤坑了!
服務端再也不用為了兼容舊版客戶端寫新版接口和文檔了!再也不會被客戶端隨時隨地沒完沒了地煩了!
項目主頁詳細介紹:
https://github.com/TommyLemon/APIJSON
下載試用
前些天我發了幾篇博客來推廣APIJSON,幾乎每次發布后都引起了很大的爭議,有贊賞和認可,也有質疑和否定。
評論太亂太分散,就挑出部分偏見,在這里對關於APIJSON的開發、功能、使用、性能、安全做一個統一的解答吧。
首先聲明:
開發APIJSON是為了解決小公司、團隊及個人開發者中客戶端和服務端的接口、文檔和溝通問題,簡化開發、提高效率、縮短周期。
希望大家不要用大公司的業務去要求APIJSON的功能、性能、安全等,而且個人也能力有限,所以只要方便、夠用就好。
一般來說小公司只能開發小型系統,只有大公司才能開發大型系統。APIJSON解決的問題在小公司很容易出現並且容易造成致命的影響(開發周期長、產品上線遲導致資金鏈斷裂而倒閉),但大公司因為資源(人才、資金、管理)充裕可以很好地避免這些問題。APIJSON降低了開發門檻和成本,使得小公司也能快速實現產品、節約資源,提高自己的競爭力。
另外,一般大公司基本都是自己造輪子用的,說不定有些公司也開發了類似APIJSON的數據交換協議,只是沒有開源。
一、關於開發
翻了下聊天記錄,大概是從2016年11月5日的問題開始構思的:
如果一個界面(如微信詳細資料界面)需要獲取1個User(個人資料)和它對應的1個pictureList(個人相冊),目前有這幾種方法:
1.接口返回的User里包括pictureList;
2.分別請求User和pictureList兩個接口;
3.后台新增一個接口,返回數據包括User和pictureList。
這幾種哪種最好呢?
我陸續問了公司群、技術群、圈子內的朋友們,獲得了很多阿里、網易、恆生、微店、蘑菇街、挖財等客戶端、前端、后端開發者的回答,但得到的方案都是上面的,我都不滿意。
或許是真的沒有人知道新的方案,也可能是公司的方案不能外傳。之前也網上搜索了一些接口定制、組合、嵌套等相關的關鍵詞,都沒找到滿意的答案。
求人不如求己,還是自己造一個輪子吧。
然后我花了大概一周的時間構思和評估新方案的
需求(實現微信詳細資料的用戶+最近相冊、QQ空間的用戶+動態+評論等數據的各種組合嵌套)、
方式(客戶端可定制、組合、嵌套服務端的返回數據)、
實現(數據庫、數據結構、傳輸與交互、封裝與解析算法等)。
之后就開始動工了,先花了一周實現了最重要的
封裝與解析協議可行性驗證(確定了3個方案中基於JSON的方案二)、
數據庫MySQL JDBC查詢{User:{id:1}}、
簡單對象組合{User:{},pictureList:[]}與列表+對象組合{[]:{User:{},Moment:{},Comment[]:{}}}的封裝與解析,
然后實現了
客戶端與服務端的HTTP通信、
客戶端對Reqeust的封裝和對Response的解析、
服務端對Reqeust的解析和對Response的封裝。
APIJSON初步完成,放到Bitbucket遠程倉庫用SourceTree管理和備份,后來遷移到碼雲。
之后又經過了1.0-3.1的內部版本迭代,2016年11月21日放到GitHub上開源。
開發過程中遇到了很多問題:
1.一開始打算用請求格式最簡潔的方案三,因為已經脫離JSON的合法格式不能封裝和解析,嘗試定制FastJSON失敗,於是轉為符合JSON格式的方案二。
2.對列表+對象組合Request的解析出現的各種問題,經過好幾天的反復嘗試、調試和修改終於完成。
3.URL中含有轉義的字符'/'被UTF-8編碼后發送到服務端仍然無法解析,后來通過多層encode解決。
...
偏見:
以我的經驗來看,此方式不通。也許只是作者一個人的狂熱。閉門造車出來的產物吧...
回敬:
APIJSON不是閉門造車的產物,而恰恰是為了解決自己和開發圈子里的各種接口問題而總結出來的實踐經驗。
APIJSON解決了傳統方式導致的開發周期長(客戶端要等服務端開發完接口、寫好文檔才能請求和解析接口)、客戶端容易被文檔的各種錯誤坑從而導致耗費大量的調試和與后端溝通的時間等問題。
二、關於功能
APIJSON目前已實現:
大體功能:增刪改查、分頁查詢、統計與驗證、注冊登錄、模糊搜索、結構校驗、數據保護、遠程函數調用等
操作方式:增、刪、改、查、調用遠程函數
操作對象:單個對象、可關聯的多個對象、數組等
請求方法:GET,HEAD,POST_GET,POST_HEAD,POST,PUT,DELETE
請求結構:{Table:{...}}、{Table0:{...},Table1{...},Table2:{...}...}、{"[]":{Table:{...}}}、{"[]":{Table0:{...},Table1{...},"Array0[]":{...},...}}等各種組合和嵌套
返回結構:對應請求結構的各種JSON結構。
功能符號:
"key[]":{} // 查詢數組 "key{}":[] // 匹配選項范圍 "key{}":">=2,length(key)<10..." // 匹配條件范圍 "key()":"function(Type0:value0,Type1:value1...)" // 遠程調用函數 "key@":"key0/key1.../targetKey" // 引用賦值 "key$":"SQL搜索表達式" // 模糊搜索 "key?":"正則表達式" // 正則匹配 "key+":key指定類型的Object // 增加/擴展 "key-":key指定類型的Object // 減少/去除 "name:alias" // 新建別名 "@key":key指定類型的Object // 關鍵詞。如返回字段@column、排序@order、自定義關鍵詞@position
具體見文檔:
https://github.com/TommyLemon/APIJSON
偏見1:
表連接查詢
回敬:
table都是通過id關聯的,連接查詢在文章的 查詢類微信朋友圈動態列表數據 里就給了demo,用戶User、動態Moment、評論Comment就是通過id關聯查詢的。
偏見2:
那要是3個表4個表內連接呢?
回敬:
APIJSON給出的Demo里的Moment,User,Comment這3個表就是通過id內連接的。文中給出的查詢類似微信朋友圈動態列表數據的demo:
{ "[]": { //請求一個array "page": 0, //array條件 "count": 2, "User": { //請求查詢名為User的table,返回名為User的JSONObject "sex": 0 //object條件 }, "Moment": { "userId@": “/User/id” //缺省依賴路徑,從同級object的路徑開始 }, "Comment[]": { //請求一個名為Comment的array "page": 0, "count": 2, "Comment": { "momentId@": “[]/Moment/id” //完整依賴路徑 } } } }
這就表示Moment.userId = User.id, Comment.momentId = Moment.id,不就實現了3個表內連接查詢么?而且還是多個數組和單個Object復雜嵌套的應用場景。
偏見3:
處理不了復雜的請求,只能看着玩玩。
回敬:
APIJSON就是為復雜請求和數據結構變化而生的。
不管是簡單對象組合{User:{},pictureList:[]},還是列表+對象組合{[]:{User:{},Moment:{},Comment[]:{}}}都可以。
微信朋友圈動態列表夠復雜吧?APIJSON查詢:(可下載App測試)
請求:
{ "[]": { //請求一個array "page": 0, //array條件 "count": 2, "User": { //請求查詢名為User的table,返回名為User的JSONObject "sex": 0 //object條件 }, "Moment": { "userId@": “/User/id” //缺省依賴路徑,從同級object的路徑開始 }, "Comment[]": { //請求一個名為Comment的array "page": 0, "count": 2, "Comment": { "momentId@": “[]/Moment/id” //完整依賴路徑 } } } }
返回:
{ "[]":[ { "User":{ "id":38710, "sex":0, "phone":"1300038710", "name":"Name-38710", "head":"http://static.oschina.net/uploads/user/1218/2437072_100.jpg?t=1461076033000" }, "Moment":{ "id":470, "title":"Title-470", "content":"This is a Content...-470", "userId":38710, "pictureList":["http://static.oschina.net/uploads/user/585/1170143_50.jpg?t=1390226446000"] }, "Comment[]":[ { "Comment":{ "id":4, "parentId":0, "momentId":470, "userId":310, "targetUserId":14604, "content":"This is a Content...-4", "targetUserName":"targetUserName-14604", "userName":"userName-93781" } }, { "Comment":{ "id":22, "parentId":221, "momentId":470, "userId":332, "targetUserId":5904, "content":"This is a Content...-22", "targetUserName":"targetUserName-5904", "userName":"userName-11679" } } ] }, { "User":{ "id":70793, "sex":0, "phone":"1300070793", "name":"Name-70793", "head":"http://static.oschina.net/uploads/user/1174/2348263_50.png?t=1439773471000" }, "Moment":{ "id":170, "title":"Title-73", "content":"This is a Content...-73", "userId":70793, "pictureList":["http://my.oschina.net/img/portrait.gif?t=1451961935000"] }, "Comment[]":[ { "Comment":{ "id":44, "parentId":0, "momentId":170, "userId":7073, "targetUserId":6378, "content":"This is a Content...-44", "targetUserName":"targetUserName-6378", "userName":"userName-88645" } }, { "Comment":{ "id":54, "parentId":0, "momentId":170, "userId":3, "targetUserId":62122, "content":"This is a Content...-54", "targetUserName":"targetUserName-62122", "userName":"userName-82381" } } ] } ] }
三、關於使用
見項目的文檔和服務端及客戶端的Demo工程:
https://github.com/TommyLemon/APIJSON
偏見1:
有違項目開發前后端分離的趨勢。
回敬:
傳統方式連客戶端的UI都是和后端接口關聯的,比如類似微信朋友圈、QQ空間的動態的列表都需要特定接口來傳輸對應結構的數據,耦合性很高。
而APIJSON讓服務端和客戶端UI完全分離了,不需要服務端為客戶端UI定制接口,也不用為了兼容舊版客戶端保留冗余字段以及開發新版接口(v2,v3...)和更新接口文檔。
難道APIJSON不是更好地實現了前后端分離?
偏見2:
數據結構過於羅嗦,為什么我還需要聲明返回值是一個數組?
回敬:
數組非必須,只有獲取列表(數組)數據才需要,可能你只看了最上面復雜結構的APIJSON請求,誤解了APIJSON請求方式。
比如獲取一個User可以是{"User":{"id":1}},獲取一個女性User列表可以是{"[]":{"User":{"sex":1}}},page和count也都是可選參數非必須。
僅從請求上看,APIJSON的數據確實比傳統方式的多;但從整體上看,APIJSON讓客戶端看請求知結果,所求即所得,讓服務端和客戶端都大幅降低了開發和溝通成本,所以是值得的。
傳統模式下是不同后端寫出水平不一、參差不齊的還可能只有在公司內才能看的文檔。
APIJSON的文檔僅針對通用特性,少量、統一、清晰;
傳統模式下的文檔要根據不同業務邏輯、不同功能划分,一般的程序員還真寫不好,內容多、容易混亂,難免出現不少錯誤和坑。當然如果和你配合的后端是個大牛就當我沒說。
總之,傳統方式不僅導致服務端開發成本高、客戶端要等服務端開發完接口和文檔才能請求,而且很容易被文檔的錯誤坑,增加大量的調試和溝通成本;
而APIJSON大幅簡化了連接方式,降低了雙方的開發和溝通成本;將后端的業務邏輯和前端/客戶端的UI分離類似於MVP開發模式的Model和View解耦,大幅降低了開發和調試難度。
偏見5:
如果要這樣設計的話,那不如讓js直接訪問數據庫,還可以沒有接口!
回敬:
這會導致以下問題:
1.前端、客戶端需要了解后端結構、會使用數據庫;
2.容易引發sql注入,不安全。如果要預防sql注入,需要從文本提取字段去分析,代碼又會很臃腫。
偏見6:
APIJSON可以讓客戶端傳一個columns字段來指定需要的字段這種方式會造成客戶端服務器的強耦合,比如有一天我刪了這個columns,所有依賴這個columns的消費方都掛了
回敬:
傳統方式刪掉不照樣會掛?你把currentUserId、pageNum或者User里的id、name、head刪掉一個試試?
偏見7:
業務模型如果發生變化.就會出現不知道要改哪些地方的問題.
后端改字段會直接影響到前端.
回敬:
傳統方式也是如此,並且還需要更新文檔,然后再通知客戶端修改代碼。
而APIJSON做到了不會因為前端/客戶端的UI變化而要后端修改返回JSON結構的代碼,也不會因為后端改接口帶來開發和溝通成本,這恰好是APIJSON的優勢。
所以APIJSON在版本迭代上也是遠超傳統方式的,不需要服務端寫兼容接口,然后更新文檔,只要客戶端改就行了。
APIJSON還不會因為服務端給的JSON結構不好用導致解析很麻煩,更不會因為復雜數據結構的變化導致修改非常困難(服務端和客戶端都要重構接口相關代碼)。
偏見8:
不贊同大范圍使用這種設計
要有多種客戶端,一種寫一套,寫得亂七八糟,真好管理!
回敬:
同版本的客戶端的需求都是統一的,都得實現統一的需求,請求和解析JSON邏輯也是基本一致的,怎么會混亂不好管理呢?傳統方式為了兼容舊版客戶端要寫v2,v3一堆新版接口和文檔不是更亂管理更麻煩?
偏見9:
url不友好,容易混亂
回敬:
GET: base_url/get/
HEAD: base_url/head/
POST_GET: base_url/post_get/
POST_HEAD: base_url/post_head/
POST: base_url/post/
PUT: base_url/put/
DELETE: base_url/delete/
再加上login、register、get_authcode等少數幾個url。
這難道還多?請求方法和url對應這么清晰,很還混亂?
傳統方式要對幾乎每個Table定對應幾個方法的幾個url,甚至還要對細分功能單獨定制url,隨便一個業務主要在服務端的APP都需要50個以上的url。
我之前兩家公司的兩個產品的url都超過100個。對第二個產品隨便翻了一個非最新版的Word文檔,總共66頁,url有112個,狀態碼233個,字符數15148。
每次去文檔里搜索查找url、字段、狀態碼一堆東西都要不少時間,還經常被文檔里的字段名、字段類型等錯誤坑,有的表格還多了或者少了字段!
所以你這句話應該用在傳統方式上,APIJSON恰恰大幅減少了url數量,解決了接口混亂問題。
偏見10:
沒覺得方便。
偏見11:
沒看出什么優勢。開發往往不是技術問題,而是協作溝通問題。
回敬:
服務端不需要寫接口,不需要寫接口文檔;客戶端不用等服務端開發完接口寫好文檔才能請求和解析,也不會被文檔各種錯誤坑,更不會有因為文檔問題帶來的調試和與服務端溝通的成本。
IDE什么用?用記事本照樣能寫。
Java有什么用?用C照樣能寫。
SDK有什么用?里面的功能都能實現。
上面都不是技術問題,而是協作溝通問題?
偏見12:
跟前端直接把sql傳到后端相比,有什么優勢?
回敬:
用APIJSON比直接傳SQL語句要方便且安全得多。
1.APIJSON支持遠程函數調用,這是SQL語句沒有的特性。
2.SQL delete忘加where條件引發的數據庫被清空事件一直都有,前段時間還有一不小心就刪了整個公司的新聞。
而客戶端請求APIJSON的方法不是GET或HEAD時,客戶端發出的Request必須滿足服務端的配置(具體看table目錄下的sys_Request.sql,可用MySQLWorkbench查看)。
並且都只允許操作某個id對應的table,不會發生忘加條件導致非法DELETE,POST,PUT等請求污染甚至清空數據。
3.主流的編輯器對SQL語句沒有檢查,一旦出錯,可能不僅僅是不能返回正確結果,還可能破壞數據。
而APIJSON的Request使用JSONRequest封裝,里面一般都是由業務model給定鍵值對。
比如獲取一個id為38710的User
/** * 用戶模型 */ public class User { private Long id;
public User() { } public Long getId() { return id; } public User setId(Long id) { this.id = id; return this; } }
直接傳SQL:
select * from User where id='38710'
這種操作不能保證id的值是某個類型,甚至可能出現id='3u710',id='38 710'等。關鍵詞和詞語順序也很容易打錯, 例如
select User * whre id='38710'
APIJSON:
JSONRequest request = new JSONRequest(new User().setId(38710));
這樣就得到了 {"User":{"id":38710}} 這個請求一個User的Request的body。
如果傳入id值的類型不是Long,編輯器就能檢查出錯誤,另外JSON也有很多校驗工具(代碼、網站等)。
APIJSON使用阿里巴巴開源的fastjson,會對JSON格式校驗。最后服務器上生成的SQL語句一定是合法合理的。
四、關於性能
JSON數據很輕量對客戶端設備的性能影響可以忽略不計,至於web前端我還不夠深入所以不好回答。
偏見:
查詢性能問題
回敬:
你是願意多花幾個月開發、測試、溝通,還是更願意多花幾百元升級下服務器配置?
性能與方便是需要權衡的,只要性能在可接受范圍內就值得使用,如果都往性能一邊倒,現在就不會有Java,JavaScprit等語言了。
java剛出來時:垃圾!虛擬機邊解釋邊運行,性能肯定很差!結果java現在基本上統一了后端、客戶端;
JavaScript剛發布時,垃圾動態語言!邊運行邊判斷類型,一定會卡到爆!結果現在它成了當之無愧的前端之王。
歷史總是驚人地相似。
當一個新的技術出現時,如果它能促進技術和行業的進步,我們作為走在時代前沿的人是不是應該這么想:我該如何推動它的發展?
五、關於安全
1.APIJSON會對請求的格式進行校驗。
2.APIJSON只有GET,HEAD請求才是明文,其它如POST都是非明文,這個和傳統方式是一樣的。
3.APIJSON會對非GET、HEAD請求的請求方法、結構、內容進行嚴格校驗。
4.APIJSON對Table默認保護不可訪問,需要服務端配置允許的請求與結構才能用指定的請求方法與結構訪問。
偏見1:
不怕別人偷數據?
回敬:
怎么偷?沒有權限不能訪問需要非公開數據。如果是撞庫,傳統方式已經被成功黑進去拖庫N次了,iCloud艷照門、網易郵箱、京東數據外泄等,安全性也不見得強。
偏見2:
容易出現安全問題。
偏見3:
直接查詢數據庫的數據庫查詢安全。
偏見4:
這跟直接連數據庫沒什么區別了,安全性是大問題。
偏見5:
把行為交給客戶端控制,安全性不夠
偏見6:
回敬:
1.APIJSON不是客戶端直接查數據庫。中間隔着協議,怎么會和直接連數據庫沒區別?照這么說,哪種方式都和直連數據庫沒區別了。
2.APIJSON拼接SQL是在服務端完成的,客戶端是不能直接發送SQL給服務端的。整個數據庫操作都是服務端完全可控的。
3.APIJSON安全性有多重機制保障,根本不會發生一次性誤刪數據庫的問題,甚至連臟數據都很難產生。
APIJSON對Table默認保護不可訪問,需要配置允許的請求才能用指定的請求方法訪問。比如開放User的GET和PUT請求,需要Server工程內的AccessVerifier.java內加上一行代碼
accessMap.put("User", new RequestMethod[]{GET,PUT});//只允許GET和PUT方法訪問
如果允許全部請求,可以
accessMap.put("User", RequestMethod.values());//允許RequestMethod內所有方法。
點擊或復制到瀏覽器測試:(請求成功)
http://apijson.cn:8080/get/{"User":{"id":38710}}
而Wallet需要保護,就沒有配置GET請求,通過GET是訪問不了的。配置了POST_GET,可以用POST_GET訪問,非明文,請求body和返回結果都被保護。
點擊或復制到瀏覽器測試:(請求失敗,無GET權限)
http://apijson.cn:8080/get/{"Privacy":{"id":38710}}
對於Wallet,因為服務端Demo工程指定了POST_GET請求的JSON結構,即便用POST_GET方法,也不能隨意訪問。
{"Wallet":{"disallowColumns":"!", "necessaryColumns":"userId"}, "necessaryColumns":"currentUserId,loginPassword"}
請求結構必須是類似以下這種:
{"Wallet":{"userId":38710}, "currentUserId":38710, "loginPassword":"apijson"}
以下請求都不合法:
{"Comment":{"id":100,...}, ....} //缺少tag
{"tag":"Comment", ....} //缺少table,這里指Comment
{"Comment":{"id":100,...}, "tag":"Moment", ....} //tag和table名不匹配
{"Moment":{"id":100,...}, "tag":"Comment", ....} //tag和table名不匹配
如果多傳了沒有配置的table,例如
{"Comment":{"id":100,...},"Moment":{...}, "tag":"Comment", ....}
服務端只會接受Comment,而Moment無效。
服務端在QueryConfig中還會繼續對id值進行校驗,傳空值不讓通過。
偏見7:
我想說的是權限控制很多場景下是要根據內容授權的,你這個允許客戶端指定查詢哪個表甚至查詢條件,無疑是給客戶端開放的權限太大了,這跟留一個后門沒什么區別
回敬:
目前密碼是單獨放在Password表里的,不給GET權限,推薦密碼加密后存儲,只允許比較,不允許獲取,成熟的方案很多。
對於Column的單獨授權目前可以設置dissallowColumns禁止訪問,對細分場景的配置會繼續完善。關於權限的配置希望大家能給些建議。
開放的權限沒你說的那么大,關於安全之前的回復已一一說明。
權限的開放程度是要權衡和取舍的,黑莓手機安全吧,Android,iPhone都不能比,可用的人越來越少。
功能手機更安全,不能聯網不能安裝應用,遠程黑入絕不可能,可為什么安全性相對差很多的智能機卻越來越普及了呢?
另外網絡安全這東西很復雜,涉及學科和范圍很廣,需要軟硬件結合、各種加密驗證機制等多重手段去保證,用哪種請求方式都不是能輕易解決的。
APIJSON主要面向的是互聯網小公司、團隊及個人開發者,一般不會對安全有太高的要求,所以Demo給的是簡單的示例。
如果對安全要求很高,可以做Table或者Column映射。
統計了下,大部分偏見都是沒有根據的憑空臆測!實踐是檢驗真理的唯一標准!
項目主頁提供了詳細的文檔,為什么不看一下?
文檔里提供了測試鏈接,為什么不點一下?
博客和文檔里都提供了App下載鏈接,為什么不用一下?
開源庫提供了客戶端和服務端源碼,為什么不運行一下?
。。。
我現在只是杭州電子科技大學一名普通的大四學生,
最受歡迎的開源項目ZBLibrary只有800多個Star;
拿過最大的獎不過是華為創想杯的決賽入圍獎;
待過最好的公司只是國內500強企業的一個子公司;
和幾個兄弟一起創業的蝙蝠餓了手機游戲項目也失敗了。
既沒有國外FAG的榮譽,也沒有國內BAT的光環。
我知道博客中講黃段子容易吸引閱讀,我也知道項目中放美女圖容易獲得Star,但這些都是我一直不願觸及的心里防線。
或許是沒有光環、人微言輕,又不願妥協,才會有如此多的憑空臆測的質疑和否定。
但我仍然滿腔熱血,渴望對開源社區貢獻一份微薄的力量;
但我仍然對APIJSON充滿希望,一如既往地不斷更新迭代,將它發展壯大;
但我仍然認為群眾的眼睛是雪亮的,開源的力量是強大的。
我會保持一顆開放包容、虛心學習、積極進取的心,同廣大的開發者們互相交流探討,一起學習成長。
最后,很感謝大家提出的各種建設性意見和寶貴的建議。
技術改變世界,以此共勉。
APIJSON,讓接口和文檔見鬼去吧!
下載試用(測試服務器地址:http://apijson.cn:8080)
源碼及文檔(覺得不錯就Star支持下吧^_^)
https://github.com/TommyLemon/APIJSON