原创文章,转载请注明:转载自Keegan小钢

并标明原文链接:http://keeganlee.me/post/practice/20160812

微信订阅号:keeganlee_me

写于2016-08-12


App项目实战之路(一):概述篇

App项目实战之路(二):API篇

App项目实战之路(三):原型篇

App项目实战之路(四):UI篇


确定功能需求

概述篇发布出去后,收到很多人的大力支持,也收到了几点关于功能需求的建议,主要在于几点:

  1. 只有微信登录在App Store那边审核很可能通不过;
  2. 调用微信获取用户头像和昵称的接口需要企业微信号才行;
  3. 就算微信登录也存在需要修改头像和昵称的需求。

关于第1点,细想一下就知道,只有第三方账号登录的确是通不过审核的。因为提交审核时必须提供测试账号给App Store的审核人员。审核人员是不会使用自己的账号进行测试的,不管是自己的微信、微博还是手机号。之前我是掉过这个坑的,提交了一款以手机号+短信验证码登录的App,但没有提供测试账号,结果被打回来了。所以,还是需要建立自己的用户体系,这一点无法偷懒了。

关于第2点,则是因为微信对这部分接口做了权限控制,只有通过了开发者资质认证才有权开通此接口。但微信的开发者资质认证并不支持个人开发者。另外,还要交每年300元的审核费用。其实,未认证的开发者建立的App只有分享的权限,根本没有登录的权限。所以,微信登录这条路根本通不了。因此,我决定不用微信登录了,改用Github登录。毕竟,面向的用户群是程序猿,而程序猿基本是人手一个Github账户。没有Github账户的,称不上合格的程序猿。也不考虑再加入微博、QQ、Facebook、Twitter等社交账户登录。因为选择太多容易混乱,我自己在某些平台登录时,就经常不记得上一次是用哪个账户登录的。

关于第3点,毫无疑问,修改头像和昵称的功能需要保留。

因此,最终的功能需求应该如下:

  1. 手机号 + 短信验证码注册
  2. 手机号 + 短信验证码登录
  3. Github登录
  4. 上传图片
  5. 修改头像
  6. 修改昵称
  7. 设置用户技术栈标签
  8. 获取同栈之猿的内容列表
  9. 获取关注之猿的内容列表
  10. 获取同栈的用户列表(未有关注之猿时获取)
  11. 发布问题
  12. 发布分享
  13. 关注某条内容
  14. 取消关注内容
  15. 获取内容的评论列表
  16. 添加评论
  17. 回复评论
  18. 点赞评论
  19. 关注某用户
  20. 取消关注某用户
  21. 获取某人详细资料
  22. 获取某人的发布内容
  23. 获取某人关注的人
  24. 获取某人的粉丝列表
  25. 获取我的消息
  26. 提交意见反馈
  27. 退出登录

需求确定,接着就可以开始设计API了。

REST API

关于什么是REST,我就不在这里赘述了,直接推荐REST作者的经典论文:

  • 架构风格与基于网络的软件架构设计(中文修订版)

下面我只想用一些实例描述几种架构风格在API定义方面的不同。

假如现在要定义登录、退出登录、注册、查询用户资料的接口,那么,可以这样定义:

接口 方法 Endpoint
登录 POST /user/login
退出登录 POST /user/logout
注册 POST /user/register
查询用户资料 GET /user/queryInfo

使用这种风格的貌似很多。也有些不是在URI中定义接口,而在参数中用method或action之类的参数名区分不同接口,示例如下:

接口 方法 参数
登录 POST method=login
退出登录 POST method=logout
注册 POST method=register
查询用户资料 GET method=queryUserInfo

最后,再看下面这种接口的定义:

接口 方法 Endpoint
登录 POST /sessions
退出登录 DELETE /sessions/{session_id}
注册 POST /users
查询用户资料 GET /users/{user_id}

这三种定义有什么区别呢?其实,前面两种可以认为都是 RPC(Remote Procedure Call) 风格的,而最后这种则可认为是 REST 风格的。

RPC和REST区别在哪呢?

最直接的区别就是:RPC抽象的是过程,REST抽象的是资源。过程是以动词为核心,而资源是以名词为核心。也可以简单类比为:RPC是面向过程的,REST是面向对象的。

从上面的例子就可以看出,前面两种定义,每个接口分别用了一个操作性的词语去定义;而最后一种定义,登录和退出登录都属于 /session 资源,注册和查询用户资料都属于 /user 资源,然后分别用POST、DELETE、GET等方法对同个资源定义不同操作。

我发现,还有些定义是RPC-REST混合的,例如,可能会这样子定义:

接口 方法 Endpoint
登录 POST /users/login
退出登录 POST /users/logout
注册 POST /users/register
查询用户资料 GET /users/{user_id}

如果再加个修改用户资料的接口,可能是这样子的:

接口 方法 Endpoint
修改用户资料 POST /users/{user_id}/update

给我的感觉就是:好混乱!这种大部分都是在对REST有过很初浅的了解,但却缺少正确理解的情况下做出的设计。或者是对于部分接口不知道该如何抽象为资源,所以就直接用RPC方式去定义了。

其实,使用REST风格设计API,我觉得难点就在于如何抽象资源。使用RPC则相对容易很多。这时,也许有人就会提出疑问了。既然使用RPC比用REST更容易抽象出接口,那为何还要用REST呢?要解答这个疑问,可以从面向过程和面向对象的角度去思考。我们知道,面向过程的思考方式处理问题更直接简单,那为什么我们还要使用面向对象呢?至于这个问题的答案,我就不再展开了。

API定义

本项目的API是打算使用REST方式定义的。那么,首先,就是资源的Endpoint定义。根据前面的功能需求整理出以下资源,可能会有些遗漏:

Endpoint 资源
/files 文件
/files/{file_id} 某个文件
/sessions 会话
/sessions/{session_id} 某个会话
/users 用户
/users/{user_id} 某用户
/users/{user_id}/posts 某用户发布的内容
/users/{user_id}/following 某用户关注的人
/users/{user_id}/followers 某用户的粉丝
/posts 发布的内容
/posts/{post_id} 某条内容
/posts/{post_id}/comments 某条内容的评论
/me 当前用户
/me/posts 我发布的内容
/me/stars 我星标的内容
/me/following 我关注的人
/me/followers 我的粉丝
/me/messages 我的消息

定义资源的Endpoint时,需要分清楚不同资源的层级关系。一个定义良好的URI,应该具有可读性,即从URI本身即可知道它所代表的资源。另外,对于URI中的一些变量值,如{file_id}、{session_id}、{user_id}、{post_id}等,在传值的时候必须确保不能为空,可以设置默认值。

接着,就需要对每个资源定义操作的方法了。我倾向于使用以下四个方法:

方法 描述 示例 示例说明
POST 创建新资源 /posts 创建新内容
GET 查询资源 /posts 查询内容列表
PUT 修改资源 /posts/{post_id} 修改某条内容
DELETE 删除资源 /posts/{post_id} 删除某条内容

不过,并不是所有资源都会开放这四个方法。例如,对/post是不开放PUT和DELETE方法的。对于以上资源,具体需要定义哪些方法,这里就不再列出来了。

然后,还要加入版本控制。毕竟,接口不是一成不变的,需要不断改动升级版本应对各种变化。那么,版本号要加在哪里好呢?关于这个问题,网上有很多讨论,有些人喜欢直接加在URI中,像这样:

http://api.domain.com/v2.1/posts复制代码

有些人喜欢加在参数里,像这样:

http://api.domain.com/posts?version=2.1复制代码

也有些人喜欢加在Header里,像这样:

Accept: application/json;version=2.1复制代码

或者自定义Header

api-version: 2.1复制代码

不喜欢第一种方式的人,大部分理由是,URI表示资源,应该与版本无关。而第二种方式和第一种方式本质上是一样的。大部分人建议使用第三种方式。不过,发现好多开放API都是采用第一种方式。在我看来,加在哪里其实影响不大。在本项目中,我打算和大部分开放API一样采用第一种方式即可。另外,如果版本号不提供,则默认为采用最新版本的接口。

最后,再定义下响应的数据协议。初期打算使用JSON,后期可能会考虑使用Protocol Buffers。数据结构则如下:

{code:200,message: "success",data: { key1: value1, key2: value2, ... }
}复制代码
  • code: 错误码
  • message: 描述信息,成功时为"success",错误时则是错误信息
  • data: 成功时返回的数据,类型为对象或数组

之前,我是喜欢将请求状态码和业务错误码分开处理的。因此,这里的code我之前喜欢将其定义为业务错误码。但是,如果按照REST风格来设计,还是有统一的code更合适。因此,我这次尝试下改变习惯。

API安全设计

安全设计方面,首先,我打算全面使用HTTPS。使用HTTPS,虽然牺牲了性能,但可以解决大部分安全问题。另外,苹果在之前的WWDC上就已宣布,从2017年1月1日起,所有iOS应用将强制使用HTTPS。这其实也意味着,从2017年起,所有App都将会使用HTTPS,不只是iOS。除非有个别比较奇葩,非要搞HTTP和HTTPS两套。至于HTTPS的优化,则需要慢慢搞了。至于证书,自己弄个自签名证书即可。后期需要支持Web版的话再找个靠谱的CA注册证书。

其次,用户鉴权方面则打算采用Token方式。用户登录之后分配一个accessToken和一个refreshToken,accessToken用于发起用户请求,refreshToken用于更新accessToken。accessToken会设置有效期,可以设为24小时。而用户退出登录之后,accessToken和refreshToken都将作废。重新登录之后会分配新的accessToken和refreshToken。

然后,我还打算在App层级分配AppKeyAppSecret,Android和iOS分别分配一对。每次向服务端发送请求时,AppKey都必须带上,服务端会对相应的AppKey进行校验。而AppSecret则需要安全保存在客户端,也不能在网络上进行传输,防止泄露。AppSecret只用于加密一些安全性级别较高的数据,以及为URL生成签名。URL签名算法步骤如下:

  1. 将所有参数按参数名进行升序排序;
  2. 将排序后的参数名和值拼接成字符串stringParams,格式:key1value1key2value2...;
  3. 在上一步的字符串前面拼接上请求URI的Endpoint,字符串后面拼接上AppSecret,即:stringURI + stringParams + AppSecret;
  4. 使用AppSecret为密钥,对上一步的结果字符串使用HMAC算法计算MAC值,这个MAC值就是签名。

另外,如果为了再加强安全性,参与签名的参数列表中可以再添加个timestamp字段,值为发送请求时的时间戳,每次请求的时间戳都将不同,这样不止增加了签名的不可预测性,也可以防止重放攻击。服务端收到请求后先检查时间戳离当前时间是否过久,如果过久则不予处理。不过,这还涉及到客户端和服务端时间同步的问题。这个很难保持一致,就算使用长连接不断获取服务器时间,也会因为网络原因而存在延迟,而且在移动网络延迟可能还会比较高。

还有另一种方案,就是使用nonce字段,值为一个较长的随机数,而不是时间戳。每次请求的随机数也都会不同,可以达到同样的效果。不过,采用这种方案的话,那服务器需要保存以前发送过的nonce。每次收到请求后先检查nonce是否已存在,存在则不予处理。这样,时间久了,nonce的量将会非常大。也有一种优化方案,那就是每次请求的nonce值由服务端生成并发送给客户端。即是说,客户端每次发送正式请求之前,需要先向服务端请求nonce值。这样的话,服务端则可以在有请求过来的时候才生成nonce,请求处理完之后则可以删除nonce。不过,弊端也很明显,本来一次的请求变成了两次。

不过,在我的这个项目中,初期我只要求加强签名的不可预测性即可,而nonce方案具备更高的不可预测性。因此,我将采用的方案是:在客户端自己生成nonce,但服务端不保存nonce,只要检查请求中是否存在nonce即可。

URL签名在每次发送请求时都需要附加在参数中,服务端接收到请求后会使用同样的签名算法计算签名值,只有服务端计算出来的签名值和接收到的签名值一致时才认为请求是安全的。

写在最后

自此,API部分的设计就完成了。在此总结一下:

  1. 采用REST风格定义API,接口抽象成对资源的操作;
  2. 添加API版本控制,版本号嵌在URL中;
  3. 响应统一使用code、message、data的JSON数据格式;
  4. 全站采用HTTPS;
  5. 使用Token方式对用户鉴权;
  6. 使用AppKey方式对应用鉴权;
  7. 使用URL签名对请求鉴权;
  8. 参数中添加nonce值增强签名的不可预测性。

扫描以下二维码即可关注订阅号。

App项目实战之路(二):API篇相关推荐

  1. App项目实战之路(四):UI篇

    原创文章,转载请注明:转载自Keegan小钢 并标明原文链接:http://keeganlee.me/post/practice/20160903 微信订阅号:keeganlee_me 写于2016- ...

  2. HarmonyOS(鸿蒙)App项目实战(1)运动手表篇学习笔记

    HarmonyOS(鸿蒙)APP项目实战(1)运动手表篇学习笔记 前言 概括 创建项目 创建主页面 创建index中的组件 设定index中组件的样式 为主页面添加应有的功能 创建倒计时页面 创建da ...

  3. Python 小白从零开始 PyQt5 项目实战(8)汇总篇(完整例程)

    本系列面向 Python 小白,从零开始实战解说应用 QtDesigner 进行 PyQt5 的项目实战.不跳过一个细节,不漏掉一行代码,不省略一个例图. 本系列从软件安装.环境配置开始,介绍了基本应 ...

  4. 【PBL项目实战】户外智慧农场项目实战系列——5.天气预报API接口对接

    [PBL项目实战]户外智慧农场项目实战系列--5.天气预报API接口对接 原文链接  https://mp.weixin.qq.com/s/jditkIEd-UK6cSQyCC_-Eg 本系列以户外智 ...

  5. Android开发高级进阶内涵段子APP项目实战视频教程

    Android开发高级进阶内涵段子APP项目实战课程视频教程下载.本课程带你从框架入手,开启我们的Android进阶之旅,开始写一步一步完善整个项目. 项目目录: 01.Android进阶之旅与你同行 ...

  6. Spring Boot + vue-element 开发个人博客项目实战教程(二十五、项目完善及扩展(前端部分))

    ⭐ 作者简介:码上言 ⭐ 代表教程:Spring Boot + vue-element 开发个人博客项目实战教程 ⭐专栏内容:零基础学Java.个人博客系统 ⭐我的文档网站:http://xyhwh- ...

  7. 2022你不容错过的软件测试项目实战(APP项目实战)免费版

    前言 最近很多的人都在问我有没有什么项目可以用来练手,正好我这里有一个比较适合练手的项目,那就给大家安利一下吧,废话就不多说了. 项目名称: APP项目实战 项目说明: 本项目里面包括了功能测试.性能 ...

  8. 【WEB API项目实战干货系列】- API登录与身份验证(三)

    上一篇: [WEB API项目实战干货系列]- 接口文档与在线测试(二) 这篇我们主要来介绍我们如何在API项目中完成API的登录及身份认证. 所以这篇会分为两部分, 登录API, API身份验证. ...

  9. 【WEB API项目实战干货系列】- API访问客户端(WebApiClient适用于MVC/WebForms/WinForm)(四)

    目前最新的代码已经通过Sqlite + NHibernate + Autofac满足了我们基本的Demo需求. 按照既定的要求,我们的API会提供给众多的客户端使用, 这些客户端可以是各种Web站点, ...

最新文章

  1. ABAP数据库—更新数据
  2. 《程序是怎样跑起来的》第二章
  3. 清理c盘垃圾的cmd命令_用命令删除系统垃圾,这波操作深藏功与名
  4. 《终身成长》读书笔记(part3)--如果一个人能学会什么东西,那么世界上其他人也都可以学会
  5. OFDM中保护间隔与循环前缀抵抗ISI…
  6. C++ 动态结构体数组与map
  7. 小程序学习笔记(6)-菜谱小程序的制作
  8. 数学分析:函数的可积条件
  9. 【收益管理】单资源容量控制(2)先从报童模型谈起!
  10. np.delete详解
  11. Security登录页面显示:Bad credentials 或者 BCryptPasswordEncoder : Encoded password does not look like BCrypt
  12. 二级分销系统平台软件开发
  13. 道高一尺魔高一丈,记强大的boost regex
  14. 灵魂显示正在登录聊天服务器,Soul跟随灵魂找到你
  15. pom.xml详解撸一下基础
  16. 对象认知全提升,成为 JS 高手
  17. 为什么华为手机总有不明照片?赶紧查看一下这3个功能,尽快关闭
  18. IB 化学考纲巨变 ,全面分析新旧考纲区别
  19. c语言2010软件下载,Access2010官方下载免费完整版|Access2010官方下载-太平洋下载中心...
  20. android fota升级 开发,android fota升级

热门文章

  1. Runnable接口和贪心算法
  2. 【Joomla】K2 Item 页面添加 Module
  3. 【学术研究基础】聚类分析学习
  4. 用命令行查看局域网的其他在线的ip
  5. 19个语法助你打牢Python基础
  6. mysql 2003错误 10055_MYSQL无法连接 提示10055错误的解决方法
  7. 如果你是C开发人员请看这三个显式编程技巧
  8. poj1422(最小路径覆盖问题)
  9. HDU1863(Prim算法)
  10. python-正则表达式练习题