移动客户端角度设计数据接口

2157048-7e79d5bd82ae0b40

前言

兵马未动,粮草先行。在一款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终端设备的统计,一般放到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.itcyz.cn/api/ [path]

原则:在以下命名规范的基础上尽量保持良好的可读性,见名知意。另外这里需要额外提下restful规范,个人理解restful规范是通过path表示当前请求的资源,通过method表示当前请求的操作动作(post=增,delete=删,put=改,get=查),例:GET /userinfo/{id},通过这个path就可以清楚的知道当前请求的意图是根据id获取用户信息,而APP开发中很多时候一个页面是需要同时获取,如,用户,订单,营销各种信息,这时候就很难用一个path来表示当前请求的真正意图,restful规范就很难得到实现,有不同见解的欢迎交流。故本文介绍的接口设计方法,只区分get和post,通过path命名定义请求行为。

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;
}

4、命名规范

统一命名:与后端约定好即可(php和js在命名时一般采用下划线风格,而Java中一般采用的是驼峰法),无绝对标准,不要同时存在驼峰”userName”,下划线”phone_number”两种形式就可以了。

避免冗余字段:每次在新增接口字段时,注意是否已经存在同一个含义的字段,保持命名一致,不要同时存在”userName”,”username”,”uName”多种同义字段。

注释清晰(重要):每个接口/字段都需要有详细的描述信息,很多时候接口体现业务逻辑,是团队中很重要的文档沉淀,同时,详细的接口文档,可以帮助新人快速熟悉业务。

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型字段

{

//如果传true,false以外的数据,就会解析失败

“isVip”: 20
“isVip”:
}

解析报错:

(1)java.lang.IllegalStateException: Expected a boolean but was NUMBER

(2)com.google.gson.stream.MalformedJsonException: Unexpected value

Int类型字段

{
“age”: 20.5
“age”: abc
“age”: “”
“age”:
}

解析报错:

(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 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 {
@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可以避免此类错误。

6、上传/下载接口,根据md5校验数据完整性

上传,下载文件/图片时,除了file本身,还要携带该file的md5,在传输过程中可能丢失部分数据,导致文件损毁,所以需要通过md5值进行完整性校验。
上传成功后,正常情况后端只需要返回code表示成功/失败,在开发阶段,可以让后端将上传成功后的图片url返回,这样当我们调用完接口以后,就可以通过该url字段查看图片是否上传成功,存储的尺寸大小,模糊度等,就不用每次粘着后端帮忙看请求结果了,这个思路同样通用于其他接口,不过上线后需要将这个不必要的字段去掉。

7、避免浮点型计算

浮点型计算可能导致精度丢失,为了避免,可以缩小单位进行存储。例:1.5元,后端会以150分存到数据库,1.5km会存成1500m。同理,如果一个类似距离的字段,如果是展示用,则直接返回”1.5km”,如果涉及到逻辑判断与计算(如:>1000m,执行逻辑A,>1500m,执行逻辑B),可以返回”1500,单位(m)”,至少比传1.5来的方便。当然如果要计算浮点型也是可以的,需要用到BigDecimal,这么设计只是为了减少出错的可能性。