国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 學(xué)院 > 開(kāi)發(fā)設(shè)計(jì) > 正文

從客戶(hù)端的角度設(shè)計(jì)后端的接口

2019-11-09 13:57:20
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

前言

兵馬未動(dòng),糧草先行。在一款A(yù)PP產(chǎn)品的各個(gè)版本迭代中,兵馬的啟動(dòng)指的是真正開(kāi)始敲代碼的時(shí)候,糧草先行則是指前期的需求,交互,UI等評(píng)審準(zhǔn)備階段,還有本文要說(shuō)的接口的設(shè)計(jì)與評(píng)審。雖然很多時(shí)候一個(gè)api接口的業(yè)務(wù),數(shù)據(jù)邏輯是后端提供的,但真正使用這個(gè)接口的是客戶(hù)端,一個(gè)前端功能的實(shí)現(xiàn)流程與邏輯,有時(shí)候只有客戶(hù)端的RD才清楚,從某種意義來(lái)說(shuō),客戶(hù)端算是接口的需求方。所以建議在前期接口設(shè)計(jì)和評(píng)審時(shí),客戶(hù)端的RD應(yīng)該更多的思考和參與,什么時(shí)機(jī)調(diào)什么接口?每個(gè)接口需要哪些字段?數(shù)據(jù)含義怎么給?只有這些都考慮清楚,且達(dá)成一致并產(chǎn)出接口文檔后,當(dāng)項(xiàng)目真正啟動(dòng)時(shí),根據(jù)接口協(xié)議進(jìn)行開(kāi)發(fā),才能盡量避免各種不確定因素對(duì)項(xiàng)目整體進(jìn)度的影響。本文介紹了接口設(shè)計(jì)中常見(jiàn)的規(guī)范,以及個(gè)人的一些思考與總結(jié),水平有限,權(quán)當(dāng)是拋磚引玉,如果有更好的設(shè)計(jì),請(qǐng)?jiān)谖恼孪路搅粞愿嬖V我,謝謝。

接口設(shè)計(jì)規(guī)范

一. 接口示例

以下是一個(gè)用戶(hù)信息接口的文檔示例,包含接口描述,請(qǐng)求參數(shù),響應(yīng)參數(shù),json示例等。

接口描述:用戶(hù)登陸成功后,或進(jìn)入個(gè)人中心時(shí)會(huì)獲取一次用戶(hù)信息

URI方法
/userinfoGET

請(qǐng)求參數(shù)

名稱(chēng)必填備注
id用戶(hù)id

響應(yīng)參數(shù)

名稱(chēng)類(lèi)型備注
idString用戶(hù)id
nameString姓名,例:張三
ageString年齡,例:20

json示例

{ "code":200, "msg":"成功", "time":"1482213602000", "data": { "id":"1001", "name":"張三", "age":"20" }}

二. 基本規(guī)范

1.通用請(qǐng)求參數(shù)

每個(gè)請(qǐng)求都要攜帶的參數(shù),用于描述每個(gè)請(qǐng)求的基本信息,后端可以通過(guò)這些字段進(jìn)行接口統(tǒng)計(jì),或APP終端設(shè)備的統(tǒng)計(jì),一般放到header或url參數(shù)中。

字段名稱(chēng)說(shuō)明
version客戶(hù)端版本version,例:1.0.0
token登陸成功后,server返回的登陸令牌token
os手機(jī)系統(tǒng)版本(Build.VERSION.RELEAS)例:4.4,4.5
from請(qǐng)求來(lái)源,例:android/ios/h5
screen手機(jī)尺寸,例:1080*1920
model機(jī)型信息(Build.MODEL),例:Redmi Note 3
channel渠道信息,例:com.wandoujia
netAPP當(dāng)前網(wǎng)絡(luò)狀態(tài),例:wifi,mobile;部分接口可以根據(jù)用戶(hù)當(dāng)前的網(wǎng)絡(luò)狀態(tài),下發(fā)不同數(shù)據(jù)策略,如:wifi則返回高清圖,mobile情況則返回縮略圖
appidAPP唯一標(biāo)識(shí),有的公司一套server服務(wù)多款A(yù)PP時(shí),需要區(qū)分開(kāi)每個(gè)APP來(lái)源

2.請(qǐng)求Path,http://www.online.com/api/ [path]

原則:在以下命名規(guī)范的基礎(chǔ)上盡量保持良好的可讀性,見(jiàn)名知意。另外這里需要額外提下restful規(guī)范,個(gè)人理解restful規(guī)范是通過(guò)path表示當(dāng)前請(qǐng)求的資源,通過(guò)method表示當(dāng)前請(qǐng)求的操作動(dòng)作(post=增,delete=刪,put=改,get=查),例:GET /userinfo/{id},通過(guò)這個(gè)path就可以清楚的知道當(dāng)前請(qǐng)求的意圖是根據(jù)id獲取用戶(hù)信息,而APP開(kāi)發(fā)中很多時(shí)候一個(gè)頁(yè)面是需要同時(shí)獲取,如,用戶(hù),訂單,營(yíng)銷(xiāo)各種信息,這時(shí)候就很難用一個(gè)path來(lái)表示當(dāng)前請(qǐng)求的真正意圖,restful規(guī)范就很難得到實(shí)現(xiàn),有不同見(jiàn)解的歡迎交流。故本文介紹的接口設(shè)計(jì)方法,只區(qū)分get和post,通過(guò)path命名定義請(qǐng)求行為,

操作行為MethodPath
查找GETgetXxx
增加POSTaddXxx/submitXxx
修改POSTmodifyXxx
刪除POSTdelXxx

示例

操作行為MethodPath
獲取用戶(hù)信息GETgetUserInfo
增加收貨地址POSTaddAddress
修改密碼POSTmodifyPwd
刪除收貨地址POSTdelAddress
登陸GETlogin
發(fā)送短信驗(yàn)證碼GETsendSms
訂單支付POSTorderPay

3.響應(yīng)數(shù)據(jù)

字段名稱(chēng)說(shuō)明
code響應(yīng)狀態(tài)碼,200:成功;非200:失敗
msg請(qǐng)求失敗時(shí)的message
time服務(wù)端時(shí)間戳,單位:毫秒。用于同步時(shí)間
data數(shù)據(jù)實(shí)體

code=200時(shí),msg=登陸成功/修改成功/提交成功;如果需要Toast,可以直接使用msg。code!=200時(shí),msg=錯(cuò)誤提示信息;比如login接口,"賬號(hào)或密碼錯(cuò)誤","賬號(hào)不存在"類(lèi)似這些的業(yè)務(wù)提示文案放在msg字段,客戶(hù)端直接Toast就可以了。不過(guò)需要提醒后端同學(xué),錯(cuò)誤提示不能自己覺(jué)的什么合適就提示什么,要按需求文檔來(lái)提供,或和PM確認(rèn)。

object類(lèi)型數(shù)據(jù)

// json{ "code":200, "msg":"成功", "time":"1482213602000", "data": { "name":"張三", "age":"20" }} // model.javapublic class Model { public String name; public String age;}

array類(lèi)型數(shù)據(jù),正常情況下在解析json的時(shí)候,1.先解析code和msg,判斷code==200的情況下繼續(xù)解析data。2.將data下面的json串解析成當(dāng)次請(qǐng)求需要的model數(shù)據(jù)結(jié)構(gòu)。對(duì)于array類(lèi)型的數(shù)據(jù),即使只有1個(gè)list字段,也要保證data下是個(gè)完整的object結(jié)構(gòu),這樣我們?cè)谟肎son解析model的時(shí)候,統(tǒng)一將data層級(jí)下的數(shù)據(jù)當(dāng)object解析就可以了,不用區(qū)分object或array的情況。

// json{ "code":200, "msg":"成功", "time":"1482213602000", "data": { "list":["張三","李四"] }} // model.javapublic class Model { public List<String> list;}

array+分頁(yè)類(lèi)型數(shù)據(jù),需要額外返回total字段,客戶(hù)端需要通過(guò)total判斷本地加載的list是否還有更多可以加載。

請(qǐng)求參數(shù)

名稱(chēng)必填備注
pageNum當(dāng)前第幾頁(yè),例:1,2,3
pageSize每頁(yè)條數(shù),例:10

響應(yīng)數(shù)據(jù)

// json{ "code":200, "msg":"成功", "time":"1482213602000", "data": { "list":["張三","李四"], "total":"10" }} // model.javapublic class Model { public List<String> list; public String total;}

不論列表頁(yè)面是支持分頁(yè)加載,還是一次加載全部數(shù)據(jù),都建議將接口設(shè)計(jì)成支持分頁(yè)的,如果要實(shí)現(xiàn)一次性加載只要把pageSize改成類(lèi)似Integer.Max的值。這樣設(shè)計(jì)的好處是客戶(hù)端和后端可以設(shè)計(jì)一套統(tǒng)一的分頁(yè)列表模版代碼,即使需求變更,也可以很好的支持。

4.命名規(guī)范

統(tǒng)一命名:與后端約定好即可(php和js在命名時(shí)一般采用下劃線(xiàn)風(fēng)格,而Java中一般采用的是駝峰法),無(wú)絕對(duì)標(biāo)準(zhǔn),不要同時(shí)存在駝峰"userName",下劃線(xiàn)"phone_number"兩種形式就可以了。

避免冗余字段:每次在新增接口字段時(shí),注意是否已經(jīng)存在同一個(gè)含義的字段,保持命名一致,不要同時(shí)存在"userName","username","uName"多種同義字段。

注釋清晰(重要):每個(gè)接口/字段都需要有詳細(xì)的描述信息,很多時(shí)候接口體現(xiàn)業(yè)務(wù)邏輯,是團(tuán)隊(duì)中很重要的文檔沉淀,同時(shí),詳細(xì)的接口文檔,可以幫助新人快速熟悉業(yè)務(wù)。具體示例如下:

接口描述:用戶(hù)登陸成功后會(huì)獲取一次用戶(hù)信息,每次進(jìn)入個(gè)人中心也會(huì)重新獲取一遍

URI方法
/userinfoGET

字段描述:數(shù)值要有單位,時(shí)間要有格式,狀態(tài)字段要有狀態(tài)描述,以及不同狀態(tài)下對(duì)于其他字段返回邏輯的關(guān)聯(lián)關(guān)系。

字段類(lèi)型字段名稱(chēng)說(shuō)明
BooleanisVip是否時(shí)Vip用戶(hù),1:是,0:否
金額realPay訂單實(shí)際付款金額,單位:元
時(shí)間payTime訂單付款時(shí)間,單位:毫秒
日期payDate訂單付款日期,格式"yyyy-MM-dd"
狀態(tài)status訂單狀態(tài),1:進(jìn)行中(payDate不返回),2:待支付(payDate返回),3:已支付(payDate不返回);(bool以1/0表示,狀態(tài)從1+開(kāi)始)

5.統(tǒng)一定義String字段類(lèi)型

// json{ "name":"張三", "isVip": true, "age":20, "money": 10.5}// Model.javapublic class Model { String name; boolean isVip; int age; float money;}

如果使用的是Gson庫(kù)的話(huà),正常情況下這么定義model是可以正常解析,但是會(huì)有以下異常情況:

Boolean型字段{ //如果傳true,false以外的數(shù)據(jù),就會(huì)解析失敗 "isVip": 20 "isVip": }

解析報(bào)錯(cuò):

(1)java.lang.IllegalStateException: Expected a boolean but was NUMBER(2)com.google.gson.stream.MalformedJsonException: Unexpected valueInt類(lèi)型字段{ "age": 20.5 "age": abc "age": "" "age": }

解析報(bào)錯(cuò):

(1)java.lang.NumberFormatException: Expected an int but was 20.5(2)java.lang.IllegalStateException: Expected an int but was STRING(3)java.lang.NumberFormatException: empty String(4)com.google.gson.stream.MalformedJsonException: Expected valueFloat類(lèi)型字段{ "money": abc "money": ""}

解析報(bào)錯(cuò):

(1)java.lang.NumberFormatException: For input string: "abc"(2)java.lang.NumberFormatException: empty String

Gson庫(kù)在解析到某個(gè)非法字段時(shí),會(huì)拋出各種異常,導(dǎo)致整個(gè)model的解析失敗。客戶(hù)端沒(méi)處理好的話(huà),會(huì)因?yàn)檫@種時(shí)不時(shí)的臟數(shù)據(jù)引發(fā)各種奇怪的bug。解決方案:

修改Gson源碼,對(duì)于字段解析失敗的異常進(jìn)行捕獲,保證model解析完成,非正常解決方案,修改源碼后Gson庫(kù)就不能隨便更新了,獲取替換其他json解析庫(kù)也變的不方便。自定義JsonDeserializer,比較正常的解決思路。public class IntegerDefaultAdapter implements JsonDeserializer<Integer> { @Override public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { // 如果integer類(lèi)型的字段,進(jìn)行一次類(lèi)型轉(zhuǎn)換 try { return Integer.parseInt(json.getAsString()); } catch (NumberFormatException e) { } return -1; }}String json = "{name:listen,isVip:true,age:abc,money:1.0}";Gson gson = new GsonBuilder().registerTypeAdapter(int.class, new IntegerDefaultAdapter()).create();Model model = gson.fromJson(json, Model.class);// age字段解析出來(lái)為-1將APP接收數(shù)據(jù)的類(lèi)型定義為容錯(cuò)能力更強(qiáng)的String(推薦)。{ "name": "abc" "name": "20" "name": "10.2" "name": "true"}

優(yōu)點(diǎn):

容錯(cuò)性強(qiáng),規(guī)避因臟數(shù)據(jù)引起的數(shù)據(jù)解析失敗。age,money這些字段大部分情況下都是直接展示,此時(shí)便可省去拼接 "",或String.valueOf()等步驟。另外假設(shè)此時(shí)將age字段定義為int類(lèi)型,很容易就會(huì)直接調(diào)用textView.setText(age),那么這個(gè)age就會(huì)當(dāng)成resId去執(zhí)行,導(dǎo)致資源找不到報(bào)錯(cuò),定義為String可以避免此類(lèi)錯(cuò)誤。

注意事項(xiàng):

Boolean類(lèi)型數(shù)據(jù),統(tǒng)一返回1(true)和0(false),客戶(hù)端做一層容錯(cuò)判斷,只有1才為true,其他非1,解析失敗的情況均為false,例:

if(!TextUtils.isEmpty(isVip) && "1".equals(isVip)) { return true;} return false;

status類(lèi)型字段從1+開(kāi)始,和Boolean類(lèi)型(0否,1是)區(qū)分開(kāi)。"0"的含義有2種,(1)非0即為真,所以0即表示false;(2)"0"是一種未賦值的默認(rèn)狀態(tài)。假設(shè)此時(shí)用0表示狀態(tài)1,那么就很難判斷出到底時(shí)數(shù)據(jù)解析失敗,使用默認(rèn)值0,還是說(shuō)邏輯走通并賦值為0。例:orderStatus,1:進(jìn)行中,2:待支付,3:已完成。

int,float類(lèi)型數(shù)據(jù),如果不是直接展示的話(huà),需要做一次類(lèi)型轉(zhuǎn)換,注意捕獲異常,在解析失敗的情況下,使用default值。

int defaultInt = -1;try {defaultInt = Integer.parseInt(age);} catch (NumberFormatException e) {e.PRintStackTrace();}return defaultInt;

6.上傳/下載接口,根據(jù)md5校驗(yàn)數(shù)據(jù)完整性

上傳,下載文件/圖片時(shí),除了file本身,還要攜帶該file的md5,在傳輸過(guò)程中可能丟失部分?jǐn)?shù)據(jù),導(dǎo)致文件損毀,所以需要通過(guò)md5值進(jìn)行完整性校驗(yàn)。

上傳成功后,正常情況后端只需要返回code表示成功/失敗,在開(kāi)發(fā)階段,可以讓后端將上傳成功后的圖片url返回,這樣當(dāng)我們調(diào)用完接口以后,就可以通過(guò)該url字段查看圖片是否上傳成功,存儲(chǔ)的尺寸大小,模糊度等,就不用每次粘著后端幫忙看請(qǐng)求結(jié)果了,這個(gè)思路同樣通用于其他接口,不過(guò)上線(xiàn)后需要將這個(gè)不必要的字段去掉。

{ "code":200, "msg":"成功", "time":"1482213602000", "data": { "url":"http://www.online.com/path/pic.jpg" }}

7.避免浮點(diǎn)型計(jì)算

浮點(diǎn)型計(jì)算可能導(dǎo)致精度丟失,為了避免,可以縮小單位進(jìn)行存儲(chǔ)。例:1.5元,后端會(huì)以150分存到數(shù)據(jù)庫(kù),1.5km會(huì)存成1500m。同理,如果一個(gè)類(lèi)似距離的字段,如果是展示用,則直接返回"1.5km",如果涉及到邏輯判斷與計(jì)算(如:>1000m,執(zhí)行邏輯A,>1500m,執(zhí)行邏輯B),可以返回"1500,單位(m)",至少比傳1.5來(lái)的方便。當(dāng)然如果要計(jì)算浮點(diǎn)型也是可以的,需要用到BigDecimal,這么設(shè)計(jì)只是為了減少出錯(cuò)的可能性。

8.json數(shù)據(jù)保持良好結(jié)構(gòu)

{ "userId"... "userName"... "userPhoto"... "orderId"... "orderType"... "addressId"... "addressName"... "addressDetail"...}

json的3類(lèi)信息user,order,address,全部堆在一起,字段多了以后,對(duì)于接口信息的讀取很不直觀(guān);客戶(hù)端在定義model的時(shí)候,會(huì)將全部字段定義在一個(gè)model中,如果其他地方也有用到addressId,Name,Detail等字段信息,則需要重新定義address的model,無(wú)法實(shí)現(xiàn)model的復(fù)用。

{ "user":{ "id"... "name"... "photo"... } "order":{ "id"... "type"... } "address":{ "id"... "name"... "detail"... } }

經(jīng)過(guò)優(yōu)化后user,order,address字段在各自的結(jié)構(gòu)體內(nèi),一眼就可以看出這個(gè)接口有哪些類(lèi)型的數(shù)據(jù)。還有點(diǎn)要注意,如果放在同一級(jí)別,id字段就需要用userId,orderId,addressId區(qū)分開(kāi),而現(xiàn)在根據(jù)不同結(jié)構(gòu)體區(qū)分字段類(lèi)型后,直接使用id就可以了,如果還使用userId,寫(xiě)代碼的時(shí)候就會(huì)出現(xiàn)data.getUser().getUserId()的寫(xiě)法,就會(huì)很奇怪。

三. 瘦客戶(hù)端

眾所周知,客戶(hù)端任何的修改都是需要發(fā)版的,特別是IOS需要走AppStore的審核流程。為了修一個(gè)bug,僅僅改幾行代碼,而重新走一輪發(fā)版流程,是很勞民傷財(cái)?shù)摹K栽诮涌谠O(shè)計(jì)的時(shí)候,也需要適當(dāng)考慮這點(diǎn),將業(yè)務(wù)重心交由后端,客戶(hù)端保持邏輯簡(jiǎn)單。有時(shí)候,一個(gè)功能,客戶(hù)端,后端都可以做,那么為什么客戶(hù)端就是不做,要后段拼好提供呢?還是那句話(huà),后端一天可以發(fā)n個(gè)版,客戶(hù)端一個(gè)版本卻只能發(fā)一次,有些團(tuán)隊(duì)一開(kāi)始并沒(méi)意識(shí)到這點(diǎn),總覺(jué)后端就是重度業(yè)務(wù)邏輯的所在,管那么多前端的展示,字符串拼接邏輯干嘛,可是,真正到了出問(wèn)題(bug或需求變更)需要發(fā)版的時(shí)候,雖然70%的鍋是客戶(hù)端背,但是,剩余30%也會(huì)對(duì)當(dāng)初重客戶(hù)端的選擇而后悔,不過(guò)重點(diǎn)不是誰(shuí)背鍋,而是產(chǎn)品不出問(wèn)題。so,為了大局,后端的RD們,我們得聊聊。

客戶(hù)端盡量只負(fù)責(zé)展示邏輯,不處理業(yè)務(wù)邏輯

例如:客戶(hù)端有個(gè)TextView,后端只給個(gè)status字段,status=1時(shí),展示文案1;status=2時(shí),展示文案2;這樣設(shè)計(jì)的缺點(diǎn)是,如果以后要修改status=3時(shí),展示文案1,那么這個(gè)status判斷邏輯時(shí)寫(xiě)死在客戶(hù)端,就沒(méi)辦法支持這種修改,且這種設(shè)計(jì)限定死了TextView只能展示2種文案。推薦方案是后端直接將TextView需要展示的文案下發(fā),這樣不管是status的判斷,還是文案的展示,后期都是可變的。

客戶(hù)端不處理金額的計(jì)算

例如:外賣(mài)APP,用戶(hù)在下單的時(shí)候,需要選擇收貨地址,支付類(lèi)型,優(yōu)惠券等,任何一個(gè)選項(xiàng)的修改,都可能影響用戶(hù)最后需要支付的金額。所以這里比較常見(jiàn)的接口設(shè)計(jì)是在每次選擇完回到訂單支付頁(yè)面后,再發(fā)送一次請(qǐng)求,后端根據(jù)當(dāng)前選項(xiàng)重新計(jì)算金額。金額永遠(yuǎn)是一款產(chǎn)品最重要,最敏感的信息,如果交由客戶(hù)端計(jì)算,萬(wàn)一出錯(cuò),即使少1分,都是毀滅性的,所以,關(guān)于金額,展示就好。

客戶(hù)端少處理請(qǐng)求參數(shù)的校驗(yàn)與約束提示

例如:修改密碼功能,密碼規(guī)則"6-12字母,數(shù)字,下劃線(xiàn)",有3種做法:

在發(fā)送請(qǐng)求前,客戶(hù)端校驗(yàn)密碼規(guī)則,如果不符合,則不發(fā)送請(qǐng)求。優(yōu)點(diǎn):規(guī)則不滿(mǎn)足時(shí),可以減少不必要的請(qǐng)求。缺點(diǎn):客戶(hù)端寫(xiě)死校驗(yàn)邏輯,密碼規(guī)則變化時(shí),客戶(hù)端需要發(fā)版。客戶(hù)端只判斷null,和最短位數(shù)限制,其他校驗(yàn)規(guī)則交由后端處理。優(yōu)點(diǎn):靈活性最好。缺點(diǎn):后端壓力大,校驗(yàn)請(qǐng)求多。后端在通用配置的接口返回正則表達(dá)式,客戶(hù)端獲取后進(jìn)行正則校驗(yàn)。優(yōu)點(diǎn):具有一定靈活性。缺點(diǎn):開(kāi)發(fā),調(diào)試成本較高。(推薦:即使出問(wèn)題,也可以清除配置,回退到第2個(gè)方案)

四.擴(kuò)展性

接口的設(shè)計(jì)要具有一定的擴(kuò)展性,考慮到后續(xù)版本變化,對(duì)于接口,字段的影響及變化。

文案與圖片

對(duì)于界面上的文案,圖片,特別是"xxx20分鐘之內(nèi)","xxx7天到期"這些帶數(shù)字的文案,不可能永遠(yuǎn)不變的,即使和PM確認(rèn)了打死不變,也最好通過(guò)常量配置接口進(jìn)行下發(fā)(未下發(fā)時(shí)使用APP本地默認(rèn)文案,下發(fā)時(shí)使用下發(fā)的文案),我們的原則是:變與不變都能支持。

數(shù)據(jù)列表化:盡量用List(key, value)的數(shù)據(jù)格式定義類(lèi)似列表的界面

list.png

方案1:客戶(hù)端在寫(xiě)xml的時(shí)候?qū)⒆髠?cè)的"姓名","性別","年齡"寫(xiě)死,右側(cè)的具體數(shù)據(jù)從json解析獲得

{ "name": "張三", "sex": "男", "age": "20歲", "nickName": "小張"}

方案2(推薦):將左側(cè)的title和右側(cè)的value,以list(key-value)的數(shù)據(jù)形式進(jìn)行下發(fā),優(yōu)點(diǎn):左,右側(cè)文案靈活配置,后期如果需要擴(kuò)展,新增或刪除一個(gè)條目,都可以通過(guò)后端控制。不過(guò)采用這種形式,也需要考慮實(shí)際場(chǎng)景,對(duì)于變化不那么頻繁,數(shù)據(jù)item較少,較固定的情況下其實(shí)沒(méi)有必要設(shè)計(jì)的太靈活,只會(huì)增加開(kāi)發(fā)成本。

{ "userInfos":[ { "key":"姓名", "value":"張三" },{ "key":"性別", "value":"男" },{ "key":"年齡", "value":"20歲" },{ "key":"昵稱(chēng)", "value":"小張" }]}

3.用flag替換boolean:一般情況下,一款A(yù)PP都會(huì)有config接口,用于獲取一些常量文案,通用配置等信息,會(huì)有很多類(lèi)似開(kāi)關(guān)的字段,如:"isNew","isVip","isShowBalance"等等。

{ "isNew":"1",// 是否是新用戶(hù) "isVip":"1",// 是否是VIP用戶(hù) "isShowBalance":"1",//是否顯示側(cè)邊欄余額模塊}

優(yōu)化方案:通過(guò)二進(jìn)制第1位表示"isNew",二進(jìn)制第2位表示"isVip",二進(jìn)制第3位表示"isShowBalance"。如果有其他新增狀態(tài),不需要新增字段,就需要改變返回的數(shù)據(jù)即可。

{ "flag":"7"// 二進(jìn)制:111,表示3個(gè)狀態(tài)都為true "flag":"5"// 二進(jìn)制:101,表示isNew,isShowBalance為true,isVip為false}long flag = 5;System.out.println("bit=" + Long.toBinaryString(flag));System.out.println("isNew=" + ((flag & 1) == 1));System.out.println("isVip=" + ((flag & 2) == 2));System.out.println("isShowBalance=" + ((flag & 4) == 4));bit=101isNew=trueisVip=falseisShowBalance=true

五.安全性

響應(yīng)數(shù)據(jù)中包含用戶(hù)隱私的字段數(shù)據(jù),需要加*號(hào)。如:手機(jī)號(hào),身份證,用戶(hù)郵箱,支付賬號(hào),郵寄地址等。

{ "phone":"150*****000", "idCard":"3500**********0555", "email":"40*****00@QQ.com" }

請(qǐng)求參數(shù)中包含用戶(hù)隱私的字段參數(shù),如:登陸接口的密碼字段,需要進(jìn)行加密傳輸,避免被代理捕捉請(qǐng)求后獲取明文密碼。

客戶(hù)端和服務(wù)器通過(guò)約定的算法,對(duì)傳遞的參數(shù)值進(jìn)行簽名匹配,防止參數(shù)在請(qǐng)求過(guò)程中被抓取篡改。密鑰記得放到so中,放在java層太不安全,so中要進(jìn)行keystore反向簽名校驗(yàn),避免so被獲取后直接調(diào)用獲取算法。

so中要進(jìn)行keystore反向簽名校驗(yàn)

Java層在進(jìn)行參數(shù)簽名計(jì)算的時(shí)候需要獲取app本地存儲(chǔ)的密鑰,調(diào)用NativeHelper.getKey(),在so中通過(guò)反射調(diào)用java層的getSignature(),比較是否和so中存儲(chǔ)的keyStore哈希值一致,如果是則返回密鑰,不是則返回空字符串。

Java層的NativeHelper.java

package com.listen.test; public class NativeHelper { static { System.loadLibrary("native-lib"); } // 調(diào)用so獲取密鑰 public native String getKey(); // 獲取當(dāng)前keyStore的hash值 public String getSignature() { final String packname = Baseapplication.getInstance().getPackageName(); PackageInfo packageInfo; try { packageInfo = BaseApplication.getInstance() .getPackageManager() .getPackageInfo(packname, PackageManager.GET_SIGNATURES); Signature[] signs = packageInfo.signatures; Signature sign = signs[0]; return sign.hashCode() + ""; } catch (Throwable t) { if (null != t) { t.printStackTrace(); } } return ""; } }

so層的native-lib.c

// 字符串轉(zhuǎn)字符 char* _JString2CStr(JNIEnv* env, jstring jstr) { char* rtn; jclass clsstring = (*env)->FindClass(env, "java/lang/String"); jstring strencode = (*env)->NewStringUTF(env, "GB2312"); jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B"); jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid, strencode); // String .getByte("GB2312"); jsize alen = (*env)->GetArrayLength(env, barr); jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE); if (alen > 0) { rtn = (char*) malloc(alen + 1); //"/0" memcpy(rtn, ba, alen); rtn[alen] = 0; } (*env)->ReleaseByteArrayElements(env, barr, ba, 0); return rtn; } char* storeKeyHash = "1234567890";// 該值可以通過(guò)java層的getSignature獲取 JNIEXPORT jstring JNICALL Java_com_listen_test_NativeHelper_getKey( JNIEnv *env, jobject obj, jbyteArray array) { // 反射獲取當(dāng)前keyStore的hash值 jclass jClazz = (*env)->FindClass(env, "com/listen/test/NativeHelper"); jmethodID jmethodid = (*env)->GetMethodID(env, jClazz, "getSignature", "()Ljava/lang/String;"); jstring appSign = (jstring)(*env)->CallObjectMethod(env, obj, jmethodid); // 判斷是否是本程序的簽名哈希值 char* charAppSign = _JString2CStr(env, appSign); //將jstring轉(zhuǎn)換為cha* if (strcmp(charAppSign, storeKeyHash) != 0) { return (*env)->NewStringUTF(env, "");//keyStore的hash不一致,不是在當(dāng)前app種調(diào)用該so } return (*env)->NewStringUTF(env, "秘鑰值");//keyStore的hash一致,返回密鑰 }

六.兼容性

APP1.0在使用接口A,如果此時(shí)在開(kāi)發(fā)1.1的時(shí)候修改了接口A的邏輯,在1.1發(fā)版的時(shí)候線(xiàn)上就會(huì)出現(xiàn)2個(gè)版本的客戶(hù)端訪(fǎng)問(wèn)同一個(gè)接口A,為了保證1.0客戶(hù)端調(diào)用接口A不會(huì)出錯(cuò),就需要通過(guò)version字段或path中的"v1/login","v2/login"進(jìn)行區(qū)分,不同版本客戶(hù)端訪(fǎng)問(wèn)同一接口時(shí)處理邏輯要各自獨(dú)立。

接口/字段的刪除,修改要謹(jǐn)慎:

對(duì)于已經(jīng)存在的接口進(jìn)行修改,需要考慮對(duì)線(xiàn)上版本的影響,盡量是數(shù)據(jù)含義,和新增字段,而不是去修改。

md5緩存的兼容性:

如果1.0的接口A存在md5緩存,正常都是后端上線(xiàn)后再發(fā)布1.1客戶(hù)端的順序,如果在后端上線(xiàn)后,1.1還沒(méi)發(fā)布的情況下,此時(shí)1.0的客戶(hù)端就緩存了1.1后端邏輯的md5,在更新成1.1的時(shí)候,md5沒(méi)有變,就有可能緩存的還是1.0的數(shù)據(jù),所以比較推薦后端在計(jì)算md5的時(shí)候把version加上,這樣更新APP可以保證md5是不一樣的。

七.性能優(yōu)化

合并接口

為了減少客戶(hù)端和服務(wù)器建立連接和斷開(kāi)連接消耗的時(shí)間,資源,電量,盡量避免頻繁的間隔網(wǎng)絡(luò)請(qǐng)求。業(yè)務(wù)場(chǎng)景允許的情況下,盡量1個(gè)頁(yè)面對(duì)應(yīng)1個(gè)接口。原先一個(gè)頁(yè)面要通過(guò)多個(gè)請(qǐng)求獲取多種類(lèi)型數(shù)據(jù)的情況,最好能通過(guò)一個(gè)接口全部獲取得到。又如:在調(diào)用B接口前需要A接口的前置數(shù)據(jù)的情況,可以讓后端支持下,在調(diào)用A接口時(shí)直接返回B接口的數(shù)據(jù),減少類(lèi)似這種的連續(xù)請(qǐng)求。

字段精簡(jiǎn)

定義字段名時(shí),在保證良好可讀性的前提下,盡量精簡(jiǎn),減少流量的消耗

{ "orderDescription" >> "orderDesc" "oldPassWord" >> "oldPwd" "longitude" >> "lng" "latitude" >> "lat" }

md5緩存

對(duì)于頻繁調(diào)用,且數(shù)據(jù)不常變化的接口(config配置接口),可以在返回的數(shù)據(jù)中添加md5字段(用于校驗(yàn)除md5外其他數(shù)據(jù)是否變化),在下次請(qǐng)求的時(shí)候?qū)⑦@個(gè)md5作為參數(shù)傳給后端,md5沒(méi)有變化的情況下,不返回data,客戶(hù)端可以直接使用上次請(qǐng)求緩存在本地的data。

md5.png

無(wú)用字段清理

每個(gè)版本的接口更新后,需要將無(wú)用字段進(jìn)行清理。或者同個(gè)接口不同狀態(tài)下需要返回的字段各不相同的時(shí)候,當(dāng)次請(qǐng)求不需要的字段需要提醒后端不必下發(fā),避免傳輸無(wú)用數(shù)據(jù)浪費(fèi)用戶(hù)流量。

圖片裁剪服務(wù)

客戶(hù)端上傳圖片后,當(dāng)需要在列表這些圖片區(qū)域較小的地方展示的時(shí)候,沒(méi)必要直接加載原圖,可以先在后端通過(guò)圖片裁剪服務(wù)處理后再進(jìn)行展示。例:http://image-demo.img-cn-hangzhou.aliyuncs.com/example.jpg@100h_100w_1e_1c?spm=5176.doc32223.2.3.jmkKF9&file=example.jpg@100h_100w_1e_1c, 這是阿里云的圖片裁剪服務(wù),在url后面直接拼上裁剪參數(shù),就可以實(shí)現(xiàn)將原圖居中裁剪成100*100的縮略圖。當(dāng)需要展示高清圖的時(shí)候,再加載原圖的url。

局部刷新

一個(gè)頁(yè)面,如果之前已經(jīng)加載了20%的數(shù)據(jù),那么就不需要每次都返回100%數(shù)據(jù),只要返回剩余80%即可。例:訂單列表頁(yè)面,每個(gè)item已經(jīng)具有類(lèi)似orderId,orderDesc等字段,那么點(diǎn)擊進(jìn)入訂單詳情的時(shí)候,orderId,orderDesc就可以從訂單列表傳遞過(guò)來(lái)即可,詳情頁(yè)的請(qǐng)求只需要返回訂單相關(guān)的剩余數(shù)據(jù),客戶(hù)端需要額外處理數(shù)據(jù)組裝邏輯,將前一個(gè)頁(yè)面?zhèn)鬟f過(guò)來(lái)的字段和詳情頁(yè)請(qǐng)求到的字段組裝成完整的model數(shù)據(jù)。

wifi與移動(dòng)網(wǎng)絡(luò)的區(qū)別對(duì)待

WiFi連接下,網(wǎng)絡(luò)傳輸?shù)碾娏肯囊纫苿?dòng)網(wǎng)絡(luò)少很多,應(yīng)該盡量減少移動(dòng)網(wǎng)絡(luò)下的數(shù)據(jù)傳輸,多在WiFi環(huán)境下傳輸數(shù)據(jù)。例:crash日志上報(bào),數(shù)據(jù)統(tǒng)計(jì)接口等,可以在移動(dòng)網(wǎng)絡(luò)的情況下請(qǐng)求頻率降低,或緩存,在wifi網(wǎng)絡(luò)時(shí)上調(diào)請(qǐng)求頻率,或?qū)⒕彺娴臄?shù)據(jù)統(tǒng)一上報(bào)。還有上文提到的,如果是wifi網(wǎng)絡(luò)狀態(tài)下,就下發(fā)高清圖提升用戶(hù)體驗(yàn),移動(dòng)網(wǎng)絡(luò)狀態(tài)就下發(fā)縮略,或裁剪圖。

八.體驗(yàn)優(yōu)化

設(shè)計(jì)接口時(shí),不能只考慮減少流量消耗,性能優(yōu)化等,特定場(chǎng)景下用戶(hù)體驗(yàn)的優(yōu)化才是最高優(yōu)先級(jí)的。

通過(guò)預(yù)加載降低對(duì)網(wǎng)絡(luò)的依賴(lài)

使用APP的場(chǎng)景為網(wǎng)絡(luò)較差的情況。例:配送員在使用配送APP的時(shí)候,商家地址如果在地下室,或配送員進(jìn)入電梯的時(shí)候,這時(shí)候常要查看訂單詳情,網(wǎng)絡(luò)信號(hào)又比較差的,就會(huì)影響正常工作。可以考慮在訂單列表的接口中,將訂單詳情的數(shù)據(jù)一起請(qǐng)求下來(lái),并通過(guò)md5判斷詳情頁(yè)面數(shù)據(jù)是否變化,避免重復(fù)加載,這樣其實(shí)用戶(hù)在網(wǎng)絡(luò)比較好的情況下請(qǐng)求一次列表后,再進(jìn)入詳情頁(yè),就不再需要重新請(qǐng)求,對(duì)網(wǎng)絡(luò)的依賴(lài)也是最小的。同理,對(duì)于一些閱讀類(lèi)APP,前幾頁(yè)的文章,用戶(hù)查看詳情的概率較高,可以在返回文章列表的時(shí)候攜帶正文內(nèi)容,則可以實(shí)現(xiàn)秒開(kāi)詳情,也可以判斷網(wǎng)絡(luò)狀態(tài),wifi場(chǎng)景下可以將詳情數(shù)據(jù)都返回。

{ "md5"... // 校驗(yàn)所有item的detail,只有在新訂單,或訂單完成后移除的情況下,md5才會(huì)變化 "orderList":[{ "id"... "status"... "detail":{ // detail中盡量只保留變化情況較少的字段,避免md5頻繁變化,如status就移出到item中存放 "type"... "desc"... } },{ "id"... "status"... "detail":{ "type"... "desc"... } }] }總結(jié)

暫時(shí)先這么多吧,水平有限,權(quán)當(dāng)是拋磚引玉,如果有更好的設(shè)計(jì),請(qǐng)?jiān)谖恼孪路搅粞愿嬖V我,交流想法,互相學(xué)習(xí)。謝謝支持~


發(fā)表評(píng)論 共有條評(píng)論
用戶(hù)名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 安庆市| 周至县| 陆河县| 临湘市| 衡东县| 夹江县| 左权县| 从化市| 汉阴县| 庆阳市| 庆元县| 平阳县| 武宁县| 台安县| 大名县| 正定县| 历史| 新乡市| 和顺县| 安仁县| 图们市| 香港 | 旅游| 视频| 闽清县| 容城县| 瑞昌市| 郁南县| 晋城| 罗田县| 仁布县| 南京市| 胶南市| 玉溪市| 吉木萨尔县| 瑞金市| 江北区| 修文县| 河池市| 枣庄市| 侯马市|