从客户端的角度设计后端的接口

前言

虽然一个api接口的业务,数据逻辑是后端提供的,但真正使用这个接口的是客户端,一个前端功能的实现流程与逻辑,客户端算是接口的需求方。所以建议在前期接口设计和评审时,客户端的应该更多的思考和参与,什么时机调什么接口?每个接口需要哪些字段?数据含义怎么给?只有这些都考虑清楚,且达成一致并产出接口文档后,当项目真正启动时,根据接口协议进行开发,才能尽量避免各种不确定因素对项目整体进度的影响。

API设计核心原则:

  1. 遵循Web标准
  2. 对开发者友好
  3. 在使用上应该保持简单、直观和一致性,不仅仅是容易使用,而应该让使用者感到愉悦
  4. 有效率的

接口设计规范

基本规范

1.通用请求参数

每个请求都要携带的参数,用于描述每个请求的基本信息,后端可以通过这些字段进行接口统计,或APP终端设备的统计,可以放在请求头部header中。

 const header = {
   version: '1.0.0',                           // 客户端版本version
   token: '6aab9eb8f32e566dfa41cbd48f53c80d',  // 登陆成功后,登陆令牌token
   platform:'android',                         // android/ios
   model:'Note3',                              // 机型信息
   appid:''                                    // app唯一标识,服务可能提供多个app应用
 }

总是给你的API加上版本化。API版本化能够让你的API迭代地更快,防止老的请求访问你的API最新版。这样能够让你在进行API重大升级时,能够持续对老版本API进行一段时间的支持。

有很多的人在讨论,API的版本化,应该体现在URL上,还是在请求Header里。理论上讲,应该放在Header里。

API被发布出来,不会永远不变。变化是不可避免的。重要的是,我们怎么去管理这种变化。

2.请求路由链接

命名规范的基础上尽量保持良好的可读性,见名知意。

接口请求路由参考restful规范。

REST的核心思想是,把API按照逻辑上的资源来进行区分。这些资源被HTTP的不同方法(GET, POST, PUT, PATCH, DELETE)进行请求,每个方法代表不同的含义。(GET=查找,POST=增加,PUT=修改,DELETE=删除)

REST的伟大之处在于,采用了既有的HTTP methods,在一个单独的链接下,实现了多样的功能操作。你不需要遵循任何的命名规范,仅仅需要一个干净和整洁的URL结构。

3.响应数据

数据返回格式:

  {
    code:200,       // 200:成功;非200:失败
    msg:'成功',     // 请求失败时的message
    Desc: '',      // 返回描述字符
    data: {         // 数据实体
      name:'张三',
      age:20
    }
  }  

array类型数据:

  {
    code: 200,       // 200:成功;非200:失败
    msg:'成功',     // 请求失败时的message
    Desc: '',      // 返回描述字符
    data: {         // 数据实体
      list:[{
        name:'张三',
    age:20
      }]
     }
  }  

判断code==200的情况下继续解析datacode!=200时,根据业务提示文案放在Desc字段,客户端直接显示就可以了。

array类型的数据,即使只有1个list字段,也要保证data下是个完整的object结构,这样就可以统一将data层级下的数据当对象解析。

4.命名规范

统一命名:一般采用小驼峰法,无绝对标准。

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

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

5.返回字段类型讨论统一

需要和两端商讨关于数据类型的返回,特别式特殊的类型,比如布尔类型等,不同语言表示方法不同

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

上传,下载文件/图片时,除了file本身,还要携带该filemd5,在传输过程中可能丢失部分数据,导致文件损毁,所以需要通过md5值进行完整性校验。
上传成功后将图片url返回

7.避免浮点型计算

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

8.json数据保持良好结构

  {
    userId: 1
    userName: '洒哥',
    userAvatar: 'http://xxx',
    orderId: 2,
    orderType: 1
    addressId: 5,
    addressDtl: '萝岗'
  }

json的3类信息user,order,address,全部堆在一起,字段多了以后,对于接口信息的读取很不直观;
客户端在定义model的时候,会将全部字段定义在一个model中,如果其他地方也有用到addressId,Name,Detail等字段信息,则需要重新定义addressmodel,无法实现model的复用。

  {
      user:{
        id: 1
        name: '洒哥',
        avatar: 'http://xxx'
      }
      order:{
        id: 2
        type: 1
      }
      address:{
        id: 5,
        detail: '萝岗'
      }        
  }

优化后user,order,address字段在各自的对象内,一眼就可以看出这个接口有哪些类型的数据。还有点要注意,如果放在同一级别,id字段就需要用userId,orderId,addressId区分开,而现在根据不同结构体区分字段类型后,直接使用id就可以了。

撰写接口文档

好的文档,和好的接口同样重要。接口文档需要被很容易地找到和访问。大部分开发者会在进行接口开发之前,检查并查看接口文档。如果这些接口文档是写在PDF文档里,或者需要登录才能查看,那将不仅仅是难于查找,还不利于搜索。

接口文档应该描述完整,并附上具体的例子。最好是,这些例子应该是真实可以访问的。

一旦你发布了一个API,那意味着,在没有通知调用者的情况下,你有责任不去破坏该接口的已有功能。如果你在今后修改该接口,需要及时更新接口文档,并且在发布接口的更新之前,及时通知你的接口调用者。

瘦客户端

众所周知,客户端任何的修改都是需要发版的,特别是iOS需要走AppStore的审核流程。为了修一个bug,仅仅改几行代码,而重新走一轮发版流程,并且旧版本得bug一直都在,很是劳民伤财的。所以在接口设计的时候,也需要适当考虑这点,将业务重心交给服务端,客户端保持逻辑简单。服务端可以随时发版,客户端一个版本却只能发一次,有些团队一开始并没意识到这点,总觉后端就是重度业务逻辑的所在,管那么多前端的展示,字符串拼接逻辑干嘛,可当真正有问题(bug或需求变更)需要发版的时候,客户端需要重新一轮新的版本得发版。

客户端尽量只负责展示逻辑,不处理业务逻辑

例如:客户端有个TextView,服务端只给个status字段,status=1时,展示文案1;status=2时,展示文案2;这样设计的缺点是,如果以后要修改status=3时,展示文案1,那么这个status判断逻辑时写死在客户端,就没办法支持这种修改,且这种设计限定死了TextView只能展示2种文案。推荐方案是后端直接将TextView需要展示的文案下发,这样不管是status的判断,还是文案的展示,后期都是可变的。

客户端不处理金额的计算

例如:外卖APP,用户在下单的时候,需要选择收货地址,支付类型,优惠券等,任何一个选项的修改,都可能影响用户最后需要支付的金额。所以这里比较常见的接口设计是在每次选择完回到订单支付页面后,再发送一次请求,后端根据当前选项重新计算金额。金额永远是一款产品最重要,最敏感的信息,如果交由客户端计算,万一出错,即使少1分,都是毁灭性的,所以,关于金额,展示就好。

客户端少处理请求参数的校验与约束提示

例如:修改密码功能,密码规则"6-12字母,数字,下划线",有3种做法:

  1. 在发送请求前,客户端校验密码规则,如果不符合,则不发送请求。
    优点:规则不满足时,可以减少不必要的请求。
    缺点:客户端写死校验逻辑,密码规则变化时,客户端需要发版。

  2. 客户端只判断null,和最短位数限制,其他校验规则交由后端处理。
    优点:灵活性最好。
    缺点:后端压力大,校验请求多。

  3. 后端在通用配置的接口返回正则表达式,客户端获取后进行正则校验。
    优点:具有一定灵活性。
    缺点:开发,调试成本较高。(推荐:即使出问题,也可以清除配置,回退到第2个方案)

扩展性

接口的设计要具有一定的扩展性,考虑到后续版本变化,对于接口,字段的影响及变化。

接到需求之后,即使和策划或者产品经理确认了,他们说以后一定不会改变,但是千万别相信他们(程序员生存法则: 1.不要写死 2.不要相信策划)。我们的原则是:变与不变都能支持。

方案1:客户端在写页面的时候将左侧的"姓名","性别","年龄"写死,右侧的具体数据从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接口,用于获取一些常量文案,通用配置等信息,会有很多类似开关的字段,如:isAdminisPulsv等等。

{
    isAdmin:1,// 是否是管理员
    isPulsv:1,// 是否是大v用户
}

优化方案:通过二进制第1位表示isAdmin,二进制第2位表示isPulsv。如果有其他新增状态,不需要新增字段,就需要改变返回的数据即可。

flag:3 // 二进制:11,表示2个状态都为true

安全性

响应数据中包含用户隐私的字段数据,需要加*号。如:手机号,身份证,用户邮箱,支付账号,邮寄地址等。

请求参数中包含用户隐私的字段参数,如:登陆接口的密码字段,需要进行加密传输,避免被代理捕捉请求后获取明文密码。

客户端和服务器通过约定的算法,对传递的参数值进行签名匹配,防止参数在请求过程中被抓取篡改。

总是使用HTTPS,没有例外。当今,只要连接了互联网,你的API就能够在世界任何地方被访问。不是所有的访问都是安全的。有一些API数据没有被加密,很容易遭到劫持并被篡改。

兼容性

在版本1.0.0在使用接口/users,如果在开发1.1.0版本的时候修改了接口/users的逻辑,在1.1。0发版的时候线上就会出现两个版本的客户端访问同一个接口/users,为了保证1.0.0版本调用接口/users不会出错,就需要通过version字段或在路由中的/v1/users/v2/users进行区分,不同版本客户端访问同一接口时处理逻辑要各自独立。

接口或者字段的删除、修改要谨慎:对于已经存在的接口进行修改,需要考虑对线上版本的影响,尽量是数据含义,和新增字段,而不是去修改。

性能优化

合并接口

为了减少客户端和服务器建立连接和断开连接消耗的时间、资源、电量,尽量避免频繁的间隔网络请求。

业务场景允许的情况下,尽量一个页面对应一个接口。原先一个页面要通过多个请求获取多种类型数据的情况,最好能通过一个接口全部获取得到。又如:在调用B接口前需要A接口的前置数据的情况,可以让后端支持下,在调用A接口时直接返回B接口的数据,减少类似这种的连续请求。

字段精简

定义字段名时,在保证良好可读性的前提下,尽量精简,减少流量的消耗

md5缓存

对于频繁调用且数据不常变化的接口,可以在返回的数据中添加md5字段(用于校验除md5外其他数据是否变化),在下次请求的时候将这个md5作为参数传给后端,md5没有变化的情况下,不返回data,客户端可以直接使用上次请求缓存在本地的data

限制API返回的字段

API调用者有时候不希望资源的所有字段都返回。如果能够在调用时,选择API能够返回的字段,或者同个接口不同状态下需要返回的字段各不相同的时候,将减少网络传输流量,加速调用方的业务处理。

可以使用fields查询参数来限制API返回的字段。比如,以下请求只返回需要的字段信息: /users/12?fields=id,name,age

图片裁剪服务

客户端上传图片后,当需要在列表这些图片区域较小的地方展示的时候,没必要直接加载原图,可以先在后端通过图片裁剪服务处理后再进行展示。

局部刷新

一个页面,如果之前已经加载了20%的数据,那么就不需要每次都返回100%数据,只要返回剩余80%即可。例:社区帖子列表,每个列表中的帖子其实已经具有title、content、createTime等字段,那么点击进入贴吧详情的时候,title、content、createTime就可以从列表传递过来,详情页的请求只需要返回剩余数据,客户端需要额外处理数据组装逻辑,将前一个页面传递过来的字段和详情页请求到的字段组装成完整的model数据。

wifi与移动网络的区别对待

WiFi连接下,网络传输的电量消耗要比移动网络少很多,应该尽量减少移动网络下的数据传输,多在WiFi环境下传输数据。例:crash日志上报,数据统计接口等,可以在移动网络的情况下请求频率降低,或缓存,在wifi网络时上调请求频率,或将缓存的数据统一上报。还有上文提到的,如果是wifi网络状态下,就下发高清图提升用户体验,移动网络状态就下发缩略,或裁剪图。

体验优化

设计接口时,不能只考虑减少流量消耗,性能优化等,特定场景下用户体验的优化才是最高优先级的。

通过预加载降低对网络的依赖

在网络较差的情况。
例:列入要做电梯或者进地下室,这时候网络比较差的,查看帖子详情,可能就会加载不出来,提示网络错误等,影响正常体验。可以考虑在列表的接口中,将详情的数据一起请求下发,可通过md5判断详情页面数据是否变化,避免重复加载,这样用户在网络比较好的情况下请求一次列表后,再进入详情页,就不再需要重新请求,对网络的依赖也是最小的。
前几页的帖子,用户查看详情的概率较高,可以在返回列表的时候携带正文内容,则可以实现秒开详情,也可以判断网络状态,wifi场景下可以将详情数据都返回。

错误代码

就像HTML页面出错了展示的错误消息一样,API在出错时也应该提供一个有用的错误消息给调用方。

API应该总是返回合理的HTTP状态码。API的错误通常分为两类:客户端调用错误使用的400系列状态码,和服务端处理错误使用的500系列状态码。

一个JSON错误消息应该包含这么几个部分,一个有用的错误消息,一个唯一的错误码(方便在接口文档里查到更详细的错误描述)和一个详尽的错误描述。

在开始设计的时候,

  1. 查询类的接口,应尽可能使用被动式提供数据的无状态接口,格式应尽可能使用对象。这样的接口对于扩展字段非常的方便,也很容易做到向下兼容。这样的增量接口,对于老的自动化测试来说,不会失效,仍然可以跑。
  2. 操作类的接口,尽可能地将资源分离,比如修改用户信息、修改用户头像信息、修改用户职位信息,这样的接口,尽可能使用独立的资源。
  3. 比较难搞的是数据结构设计不合理,那就是新增一个接口,只能尽可能避免。

对于实在没有办法需要全面升级接口的,如果可能,保持原有的业务,原有的接口运转正常,然后构建一套全新的隔离的接口,最后做下版本使用监控,当观察到所有用户都使用新版本的客户端的时候,并保持一段时间的时候,放弃对老版本的维护,继而下掉老版本的资源,当然,万不得已的时候还可以用强制更新。

接口统计功能

为了更好的了解我们用户的使用情况,需要我们尽可能多的收集客户端的信息,除了传统的IPUser-Agent之外,还应该收集一些移动相关的信息,比如:

  • 手机操作系统,是android还是ios,都是什么版本
  • 用户使用的网络状况,是2G、3G、4G还是WIFI
  • 客户端APP是什么版本信息。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容