歡迎光臨
每天分享高質量文章

從客戶端的角度設計後端的接口

點擊上方“芋道原始碼”,選擇“置頂公眾號”

技術文章第一時間送達!

原始碼精品專欄

 

來源:https://www.jianshu.com/p/35a7b6f5f92e

前言

兵馬未動,糧草先行。在一款APP產品的各個版本迭代中,兵馬的啟動指的是真正開始敲代碼的時候,糧草先行則是指前期的需求,交互,UI等評審準備階段,還有本文要說的接口的設計與評審。雖然很多時候一個api接口的業務,資料邏輯是後端提供的,但真正使用這個接口的是客戶端,一個前端功能的實現流程與邏輯,有時候只有客戶端的RD才清楚,從某種意義來說,客戶端算是接口的需求方。所以建議在前期接口設計和評審時,客戶端的RD應該更多的思考和參與,什麼時機調什麼接口?每個接口需要哪些欄位?資料含義怎麼給?只有這些都考慮清楚,且達成一致並產出接口文件後,當專案真正啟動時,根據接口協議進行開發,才能儘量避免各種不確定因素對專案整體進度的影響。本文介紹了接口設計中常見的規範,以及個人的一些思考與總結,水平有限,權當是拋磚引玉,如果有更好的設計,請在文章下方留言告訴我,謝謝。

接口設計規範

一. 接口示例

以下是一個用戶信息接口的文件示例,包含接口描述,請求引數,響應引數,json示例等。

接口描述:用戶登陸成功後,或進入個人中心時會獲取一次用戶信息

URI 方法
/userinfo GET

請求引數

名稱 必填 備註
id 用戶id

響應引數

名稱 型別 備註
id String 用戶id
name String 姓名,例:張三
age String 年齡,例:20

json示例

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

二. 基本規範

1.通用請求引數

每個請求都要攜帶的引數,用於描述每個請求的基本信息,後端可以通過這些欄位進行接口統計,或APP終端設備的統計,一般放到essay-header或url引數中。

欄位名稱 說明
version 客戶端版本version,例:1.0.0
token 登陸成功後,server傳回的登陸令牌token
os 手機系統版本(Build.VERSION.RELEAS)例:4.4,4.5
from 請求來源,例:android/ios/h5
screen 手機尺寸,例:1080*1920
model 機型信息(Build.MODEL),例:Redmi Note 3
channel 渠道信息,例:com.wandoujia
net APP當前網絡狀態,例:wifi,mobile;部分接口可以根據用戶當前的網絡狀態,下發不同資料策略,如:wifi則傳回高清圖,mobile情況則傳回縮略圖
appid APP唯一標識,有的公司一套server服務多款APP時,需要區分開每個APP來源

2.請求Path,http://www.online.com/api/ [path]

原則:在以下命名規範的基礎上儘量保持良好的可讀性,見名知意。另外這裡需要額外提下restful規範,個人理解restful規範是通過path表示當前請求的資源,通過method表示當前請求的操作動作(post=增,delete=刪,put=改,get=查),例:GET /userinfo/{id},通過這個path就可以清楚的知道當前請求的意圖是根據id獲取用戶信息,而APP開發中很多時候一個頁面是需要同時獲取,如,用戶,訂單,營銷各種信息,這時候就很難用一個path來表示當前請求的真正意圖,restful規範就很難得到實現,有不同見解的歡迎交流。故本文介紹的接口設計方法,只區分get和post,通過path命名定義請求行為,

操作行為 Method Path
查找 GET getXxx
增加 POST addXxx/submitXxx
修改 POST modifyXxx
刪除 POST delXxx

示例

操作行為 Method Path
獲取用戶信息 GET getUserInfo
增加收貨地址 POST addAddress
修改密碼 POST modifyPwd
刪除收貨地址 POST delAddress
登陸 GET login
發送短信驗證碼 GET sendSms
訂單支付 POST orderPay

3.響應資料

欄位名稱 說明
code 響應狀態碼,200:成功;非200:失敗
msg 請求失敗時的message
time 服務端時間戳,單位:毫秒。用於同步時間
data 資料物體

code=200時,msg=登陸成功/修改成功/提交成功;如果需要Toast,可以直接使用msg。
code!=200時,msg=錯誤提示信息;比如login接口,”賬號或密碼錯誤”,”賬號不存在”類似這些的業務提示文案放在msg欄位,客戶端直接Toast就可以了。不過需要提醒後端同學,錯誤提示不能自己覺的什麼合適就提示什麼,要按需求文件來提供,或和PM確認。

object型別資料

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

// model.java
public class Model {
     public String name;
     public String age;

array型別資料,正常情況下在解析json的時候,1.先解析code和msg,判斷code==200的情況下繼續解析data。2.將data下麵的json串解析成當次請求需要的model資料結構。對於array型別的資料,即使只有1個list欄位,也要保證data下是個完整的object結構,這樣我們在用Gson解析model的時候,統一將data層級下的資料當object解析就可以了,不用區分object或array的情況。

// json
{
    "code":200,
    "msg":"成功",
    "time":"1482213602000",
    "data": {
        "list":["張三","李四"]
    }
}   

// model.java
public class Model {
    public List list;

array+分頁型別資料,需要額外傳回total欄位,客戶端需要通過total判斷本地加載的list是否還有更多可以加載。

請求引數

名稱 必填 備註
pageNum 當前第幾頁,例:1,2,3
pageSize 每頁條數,例:10

響應資料

// json
{
    "code":200,
    "msg":"成功",
    "time":"1482213602000",
    "data": {
        "list":["張三","李四"],
        "total":"10"
    }
}   

// model.java
public class Model {
    public List list;
    public String total;

不論串列頁面是支持分頁加載,還是一次加載全部資料,都建議將接口設計成支持分頁的,如果要實現一次性加載只要把pageSize改成類似Integer.Max的值。這樣設計的好處是客戶端和後端可以設計一套統一的分頁串列模版代碼,即使需求變更,也可以很好的支持。

4.命名規範

  • 統一命名:與後端約定好即可(php和js在命名時一般採用下劃線風格,而Java中一般採用的是駝峰法),無絕對標準,不要同時存在駝峰”userName”,下劃線”phone_number”兩種形式就可以了。

  • 避免冗餘欄位:每次在新增接口欄位時,註意是否已經存在同一個含義的欄位,保持命名一致,不要同時存在”userName”,”username”,”uName”多種同義欄位。

  • 註釋清晰(重要):每個接口/欄位都需要有詳細的描述信息,很多時候接口體現業務邏輯,是團隊中很重要的文件沉澱,同時,詳細的接口文件,可以幫助新人快速熟悉業務。具體示例如下:

接口描述:用戶登陸成功後會獲取一次用戶信息,每次進入個人中心也會重新獲取一遍

URI 方法
/userinfo GET

欄位描述:數值要有單位,時間要有格式,狀態欄位要有狀態描述,以及不同狀態下對於其他欄位傳回邏輯的關聯關係。

欄位型別 欄位名稱 說明
Boolean isVip 是否時Vip用戶,1:是,0:否
金額 realPay 訂單實際付款金額,單位:元
時間 payTime 訂單付款時間,單位:毫秒
日期 payDate 訂單付款日期,格式”yyyy-MM-dd”
狀態 status 訂單狀態,1:進行中(payDate不傳回),2:待支付(payDate傳回),3:已支付(payDate不傳回);(bool以1/0表示,狀態從1+開始)

5.統一定義String欄位型別

// json
{
    "name":"張三",
    "isVip"true,
    "age":20,
    "money"10.5
}

// Model.java
public class Model {
    String name;
    boolean isVip;
    int age;
    float money;
}

如果使用的是Gson庫的話,正常情況下這麼定義model是可以正常解析,但是會有以下異常情況:

  • Boolean型欄位

{
    //如果傳truefalse以外的資料,就會解析失敗
    "isVip"20
    "isVip"
}

解析報錯:

(1)java.lang.IllegalStateExceptionExpected a boolean but was NUMBER
(2)com.google.gson.stream.MalformedJsonExceptionUnexpected value
  • Int型別欄位

{
    "age"20.5
    "age": abc
    "age"""
    "age"
}

解析報錯:

(1)java.lang.NumberFormatExceptionExpected an int but was 20.5
(2)java.lang.IllegalStateExceptionExpected an int but was STRING
(3)java.lang.NumberFormatExceptionempty String
(4)com.google.gson.stream.MalformedJsonExceptionExpected value
  • Float型別欄位

{
    "money": abc
    "money"""
}

解析報錯:

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

Gson庫在解析到某個非法欄位時,會丟擲各種異常,導致整個model的解析失敗。客戶端沒處理好的話,會因為這種時不時的臟資料引發各種奇怪的bug。解決方案:

  • 修改Gson原始碼,對於欄位解析失敗的異常進行捕獲,保證model解析完成,非正常解決方案,修改原始碼後Gson庫就不能隨便更新了,獲取替換其他json解析庫也變的不方便。

  • 自定義JsonDeserializer,比較正常的解決思路。

public class IntegerDefaultAdapter implements JsonDeserializer<Integer{
  @Override
  public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
      throws JsonParseException 
{

      // 如果integer型別的欄位,進行一次型別轉換
      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欄位解析出來為-1
  • 將APP接收資料的型別定義為容錯能力更強的String(推薦)。

{
    "name""abc"
    "name""20"
    "name""10.2"
    "name""true"
}

優點:

  • 容錯性強,規避因臟資料引起的資料解析失敗。

  • age,money這些欄位大部分情況下都是直接展示,此時便可省去拼接 “”,或String.valueOf()等步驟。另外假設此時將age欄位定義為int型別,很容易就會直接呼叫textView.setText(age),那麼這個age就會當成resId去執行,導致資源找不到報錯,定義為String可以避免此類錯誤。

註意事項:

  • Boolean型別資料,統一傳回1(true)和0(false),客戶端做一層容錯判斷,只有1才為true,其他非1,解析失敗的情況均為false,例:

    if(!TextUtils.isEmpty(isVip) && "1".equals(isVip)) {
          return true;
    else {
          return false;
    }
  • status型別欄位從1+開始,和Boolean型別(0否,1是)區分開。”0″的含義有2種,(1)非0即為真,所以0即表示false;(2)”0″是一種未賦值的預設狀態。假設此時用0表示狀態1,那麼就很難判斷出到底時資料解析失敗,使用預設值0,還是說邏輯走通並賦值為0。例:orderStatus,1:進行中,2:待支付,3:已完成。

  • int,float型別資料,如果不是直接展示的話,需要做一次型別轉換,註意捕獲異常,在解析失敗的情況下,使用default值。

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

6.上傳/下載接口,根據md5校驗資料完整性

  • 上傳,下載檔案/圖片時,除了file本身,還要攜帶該file的md5,在傳輸過程中可能丟失部分資料,導致檔案損毀,所以需要通過md5值進行完整性校驗。

  • 上傳成功後,正常情況後端只需要傳回code表示成功/失敗,在開發階段,可以讓後端將上傳成功後的圖片url傳回,這樣當我們呼叫完接口以後,就可以通過該url欄位查看圖片是否上傳成功,儲存的尺寸大小,模糊度等,就不用每次粘著後端幫忙看請求結果了,這個思路同樣通用於其他接口,不過上線後需要將這個不必要的欄位去掉。

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

7.避免浮點型計算

浮點型計算可能導致精度丟失,為了避免,可以縮小單位進行儲存。例:1.5元,後端會以150分存到資料庫,1.5km會存成1500m。同理,如果一個類似距離的欄位,如果是展示用,則直接傳回”1.5km”,如果涉及到邏輯判斷與計算(如:>1000m,執行邏輯A,>1500m,執行邏輯B),可以傳回”1500,單位(m)”,至少比傳1.5來的方便。當然如果要計算浮點型也是可以的,需要用到BigDecimal,這麼設計只是為了減少出錯的可能性。

8.json資料保持良好結構

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

json的3類信息user,order,address,全部堆在一起,欄位多了以後,對於接口信息的讀取很不直觀;客戶端在定義model的時候,會將全部欄位定義在一個model中,如果其他地方也有用到addressId,Name,Detail等欄位信息,則需要重新定義address的model,無法實現model的復用。

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

經過優化後user,order,address欄位在各自的結構體內,一眼就可以看出這個接口有哪些型別的資料。還有點要註意,如果放在同一級別,id欄位就需要用userId,orderId,addressId區分開,而現在根據不同結構體區分欄位型別後,直接使用id就可以了,如果還使用userId,寫代碼的時候就會出現
data.getUser().getUserId()的寫法,就會很奇怪。

三. 瘦客戶端

眾所周知,客戶端任何的修改都是需要發版的,特別是IOS需要走AppStore的審核流程。為了修一個bug,僅僅改幾行代碼,而重新走一輪發版流程,是很勞民傷財的。所以在接口設計的時候,也需要適當考慮這點,將業務重心交由後端,客戶端保持邏輯簡單。有時候,一個功能,客戶端,後端都可以做,那麼為什麼客戶端就是不做,要後段拼好提供呢?還是那句話,後端一天可以發n個版,客戶端一個版本卻只能發一次,有些團隊一開始並沒意識到這點,總覺後端就是重度業務邏輯的所在,管那麼多前端的展示,字串拼接邏輯幹嘛,可是,真正到了出問題(bug或需求變更)需要發版的時候,雖然70%的鍋是客戶端背,但是,剩餘30%也會對當初重客戶端的選擇而後悔,不過重點不是誰背鍋,而是產品不出問題。so,為了大局,後端的RD們,我們得聊聊。

  1. 客戶端儘量只負責展示邏輯,不處理業務邏輯

例如:客戶端有個TextView,後端只給個status欄位,status=1時,展示文案1;status=2時,展示文案2;這樣設計的缺點是,如果以後要修改status=3時,展示文案1,那麼這個status判斷邏輯時寫死在客戶端,就沒辦法支持這種修改,且這種設計限定死了TextView只能展示2種文案。推薦方案是後端直接將TextView需要展示的文案下發,這樣不管是status的判斷,還是文案的展示,後期都是可變的。

  1. 客戶端不處理金額的計算

例如:外賣APP,用戶在下單的時候,需要選擇收貨地址,支付型別,優惠券等,任何一個選項的修改,都可能影響用戶最後需要支付的金額。所以這裡比較常見的接口設計是在每次選擇完回到訂單支付頁面後,再發送一次請求,後端根據當前選項重新計算金額。金額永遠是一款產品最重要,最敏感的信息,如果交由客戶端計算,萬一齣錯,即使少1分,都是毀滅性的,所以,關於金額,展示就好。

  1. 客戶端少處理請求引數的校驗與約束提示

例如:修改密碼功能,密碼規則”6-12字母,數字,下劃線”,有3種做法:

  • 在發送請求前,客戶端校驗密碼規則,如果不符合,則不發送請求。優點:規則不滿足時,可以減少不必要的請求。缺點:客戶端寫死校驗邏輯,密碼規則變化時,客戶端需要發版。

  • 客戶端只判斷null,和最短位數限制,其他校驗規則交由後端處理。優點:靈活性最好。缺點:後端壓力大,校驗請求多。

  • 後端在通用配置的接口傳回正則運算式,客戶端獲取後進行正則校驗。優點:具有一定靈活性。缺點:開發,除錯成本較高。(推薦:即使出問題,也可以清除配置,回退到第2個方案)

四.擴展性

接口的設計要具有一定的擴展性,考慮到後續版本變化,對於接口,欄位的影響及變化。

  1. 文案與圖片

對於界面上的文案,圖片,特別是”xxx20分鐘之內”,”xxx7天到期”這些帶數字的文案,不可能永遠不變的,即使和PM確認了打死不變,也最好通過常量配置接口進行下發(未下發時使用APP本地預設文案,下發時使用下發的文案),我們的原則是:變與不變都能支持。

  1. 資料串列化:儘量用List(key, value)的資料格式定義類似串列的界面

list.png

方案1:客戶端在寫xml的時候將左側的”姓名”,”性別”,”年齡”寫死,右側的具體資料從json解析獲得

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

方案2(推薦):將左側的title和右側的value,以list(key-value)的資料形式進行下發,優點:左,右側文案靈活配置,後期如果需要擴展,新增或刪除一個條目,都可以通過後端控制。不過採用這種形式,也需要考慮實際場景,對於變化不那麼頻繁,資料item較少,較固定的情況下其實沒有必要設計的太靈活,只會增加開發成本。

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

3.用flag替換boolean:一般情況下,一款APP都會有config接口,用於獲取一些常量文案,通用配置等信息,會有很多類似開關的欄位,如:”isNew”,”isVip”,”isShowBalance”等等。

{
    "isNew":"1",// 是否是新用戶
    "isVip":"1",// 是否是VIP用戶
    "isShowBalance":"1",//是否顯示側邊欄餘額模塊
}

優化方案:通過二進制第1位表示”isNew”,二進制第2位表示”isVip”,二進制第3位表示”isShowBalance”。如果有其他新增狀態,不需要新增欄位,就需要改變傳回的資料即可。

{
    "flag":"7"// 二進制:111,表示3個狀態都為true
    "flag":"5"// 二進制: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=101
isNew=true
isVip=false
isShowBalance=true

五.安全性

  1. 響應資料中包含用戶隱私的欄位資料,需要加*號。如:手機號,身份證,用戶郵箱,支付賬號,郵寄地址等。

{
    "phone":"150*****000",
    "idCard":"3500**********0555",  
    "email":"40*****00@qq.com"     
}
  1. 請求引數中包含用戶隱私的欄位引數,如:登陸接口的密碼欄位,需要進行加密傳輸,避免被代理捕捉請求後獲取明文密碼。

  2. 客戶端和服務器通過約定的演算法,對傳遞的引數值進行簽名匹配,防止引數在請求過程中被抓取篡改。密鑰記得放到so中,放在java層太不安全,so中要進行keystore反向簽名校驗,避免so被獲取後直接呼叫獲取演算法。

  • so中要進行keystore反向簽名校驗

    Java層在進行引數簽名計算的時候需要獲取app本地儲存的密鑰,呼叫NativeHelper.getKey(),在so中通過反射呼叫java層的getSignature(),比較是否和so中儲存的keyStore哈希值一致,如果是則傳回密鑰,不是則傳回空字串。

    Java層的NativeHelper.java

    package com.listen.test; public class NativeHelper { static { System.loadLibrary("native-lib"); } // 呼叫so獲取密鑰 public native String getKey(); ¨K103K }

    so層的native-lib.c

    // 字串轉字符 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; } ¨K104K

六.兼容性

APP1.0在使用接口A,如果此時在開發1.1的時候修改了接口A的邏輯,在1.1發版的時候線上就會出現2個版本的客戶端訪問同一個接口A,為了保證1.0客戶端呼叫接口A不會出錯,就需要通過version欄位或path中的”v1/login”,”v2/login”進行區分,不同版本客戶端訪問同一接口時處理邏輯要各自獨立。

  1. 接口/欄位的刪除,修改要謹慎:

對於已經存在的接口進行修改,需要考慮對線上版本的影響,儘量是資料含義,和新增欄位,而不是去修改。

  1. md5快取的兼容性:

如果1.0的接口A存在md5快取,正常都是後端上線後再發佈1.1客戶端的順序,如果在後端上線後,1.1還沒發佈的情況下,此時1.0的客戶端就快取了1.1後端邏輯的md5,在更新成1.1的時候,md5沒有變,就有可能快取的還是1.0的資料,所以比較推薦後端在計算md5的時候把version加上,這樣更新APP可以保證md5是不一樣的。

七.性能優化

  1. 合併接口

為了減少客戶端和服務器建立連接和斷開連接消耗的時間,資源,電量,儘量避免頻繁的間隔網絡請求。業務場景允許的情況下,儘量1個頁面對應1個接口。原先一個頁面要通過多個請求獲取多種型別資料的情況,最好能通過一個接口全部獲取得到。又如:在呼叫B接口前需要A接口的前置資料的情況,可以讓後端支持下,在呼叫A接口時直接傳回B接口的資料,減少類似這種的連續請求。

  1. 欄位精簡

定義欄位名時,在保證良好可讀性的前提下,儘量精簡,減少流量的消耗

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

  1. md5快取

對於頻繁呼叫,且資料不常變化的接口(config配置接口),可以在傳回的資料中添加md5欄位(用於校驗除md5外其他資料是否變化),在下次請求的時候將這個md5作為引數傳給後端,md5沒有變化的情況下,不傳回data,客戶端可以直接使用上次請求快取在本地的data。

md5.png

  1. 無用欄位清理

每個版本的接口更新後,需要將無用欄位進行清理。或者同個接口不同狀態下需要傳回的欄位各不相同的時候,當次請求不需要的欄位需要提醒後端不必下發,避免傳輸無用資料浪費用戶流量。

  1. 圖片裁剪服務

客戶端上傳圖片後,當需要在串列這些圖片區域較小的地方展示的時候,沒必要直接加載原圖,可以先在後端通過圖片裁剪服務處理後再進行展示。例:
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, 這是阿裡雲的圖片裁剪服務,在url後面直接拼上裁剪引數,就可以實現將原圖居中裁剪成100*100的縮略圖。當需要展示高清圖的時候,再加載原圖的url。

  1. 區域性掃清

一個頁面,如果之前已經加載了20%的資料,那麼就不需要每次都傳回100%資料,只要傳回剩餘80%即可。例:訂單串列頁面,每個item已經具有類似orderId,orderDesc等欄位,那麼點擊進入訂單詳情的時候,orderId,orderDesc就可以從訂單串列傳遞過來即可,詳情頁的請求只需要傳回訂單相關的剩餘資料,客戶端需要額外處理資料組裝邏輯,將前一個頁面傳遞過來的欄位和詳情頁請求到的欄位組裝成完整的model資料。

  1. wifi與移動網絡的區別對待

WiFi連接下,網絡傳輸的電量消耗要比移動網絡少很多,應該儘量減少移動網絡下的資料傳輸,多在WiFi環境下傳輸資料。例:crash日誌上報,資料統計接口等,可以在移動網絡的情況下請求頻率降低,或快取,在wifi網絡時上調請求頻率,或將快取的資料統一上報。還有上文提到的,如果是wifi網絡狀態下,就下發高清圖提升用戶體驗,移動網絡狀態就下發縮略,或裁剪圖。

八.體驗優化

設計接口時,不能只考慮減少流量消耗,性能優化等,特定場景下用戶體驗的優化才是最高優先級的。

  1. 通過預加載降低對網絡的依賴

使用APP的場景為網絡較差的情況。例:配送員在使用配送APP的時候,商家地址如果在地下室,或配送員進入電梯的時候,這時候常要查看訂單詳情,網絡信號又比較差的,就會影響正常工作。可以考慮在訂單串列的接口中,將訂單詳情的資料一起請求下來,並通過md5判斷詳情頁面資料是否變化,避免重覆加載,這樣其實用戶在網絡比較好的情況下請求一次串列後,再進入詳情頁,就不再需要重新請求,對網絡的依賴也是最小的。同理,對於一些閱讀類APP,前幾頁的文章,用戶查看詳情的概率較高,可以在傳迴文章串列的時候攜帶正文內容,則可以實現秒開詳情,也可以判斷網絡狀態,wifi場景下可以將詳情資料都傳回。

       {
            "md5"... // 校驗所有item的detail,只有在新訂單,或訂單完成後移除的情況下,md5才會變化
            "orderList":[{
                "id"...
                "status"...
                "detail":{ // detail中儘量只保留變化情況較少的欄位,避免md5頻繁變化,如status就移出到item中存放
                    "type"...
                    "desc"...
                }
            },{
                "id"...
                "status"...
                "detail":{
                    "type"...
                    "desc"...
                }
            }]
       }
  • 總結

暫時先這麼多吧,水平有限,權當是拋磚引玉,如果有更好的設計,請在文章下方留言告訴我,交流想法,互相學習。謝謝支持~

分享一份基於本文編寫的接口文件模板,僅供參考接口模板。




如果你對 Dubbo / Netty 等等原始碼與原理感興趣,歡迎加入我的知識星球一起交流。長按下方二維碼噢

目前在知識星球更新了《Dubbo 原始碼解析》目錄如下:

01. 除錯環境搭建
02. 專案結構一覽
03. 配置 Configuration
04. 核心流程一覽

05. 拓展機制 SPI

06. 執行緒池

07. 服務暴露 Export

08. 服務取用 Refer

09. 註冊中心 Registry

10. 動態編譯 Compile

11. 動態代理 Proxy

12. 服務呼叫 Invoke

13. 呼叫特性 

14. 過濾器 Filter

15. NIO 服務器

16. P2P 服務器

17. HTTP 服務器

18. 序列化 Serialization

19. 集群容錯 Cluster

20. 優雅停機

21. 日誌適配

22. 狀態檢查

23. 監控中心 Monitor

24. 管理中心 Admin

25. 運維命令 QOS

26. 鏈路追蹤 Tracing

… 一共 69+ 篇

目前在知識星球更新了《Netty 原始碼解析》目錄如下:

01. 除錯環境搭建
02. NIO 基礎
03. Netty 簡介
04. 啟動 Bootstrap

05. 事件輪詢 EventLoop

06. 通道管道 ChannelPipeline

07. 通道 Channel

08. 位元組緩衝區 ByteBuf

09. 通道處理器 ChannelHandler

10. 編解碼 Codec

11. 工具類 Util

… 一共 61+ 篇

目前在知識星球更新了《資料庫物體設計》目錄如下:


01. 商品模塊
02. 交易模塊
03. 營銷模塊
04. 公用模塊

… 一共 17+ 篇

原始碼不易↓↓↓

點贊支持老艿艿↓↓

赞(0)

分享創造快樂