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

從客戶端的角度設計後端的介面

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

技術文章第一時間送達!

原始碼精品專欄

 

來源: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)

分享創造快樂