App与服务器的通讯接口如何设计得好,须要考虑的地方挺多的,在此根据个人一些经验作一些总结分享,旨在抛砖引玉。html
如今,大部分App的接口都采用RESTful架构,RESTFul最重要的一个设计原则就是,客户端与服务器的交互在请求之间是无状态的,也就是说,当涉及到用户状态时,每次请求都要带上身份验证信息。实现上,大部分都采用token的认证方式,通常流程是:android
1. 用户用密码登陆成功后,服务器返回token给客户端;
2. 客户端将token保存在本地,发起后续的相关请求时,将token发回给服务器;
3. 服务器检查token的有效性,有效则返回数据,若无效,分两种状况:算法
- token错误,这时须要用户从新登陆,获取正确的token
- token过时,这时客户端须要再发起一次认证请求,获取新的token
然而,此种验证方式存在一个安全性问题:当登陆接口被劫持时,黑客就获取到了用户密码和token,后续则能够对该用户作任何事情了。用户只有修改密码才能夺回控制权。数据库
如何优化呢?第一种解决方案是采用HTTPS。
HTTPS在HTTP的基础上添加了SSL安全协议,自动对数据进行了压缩加密,在必定程序能够防止监听、防止劫持、防止重发,安全性能够提升不少。不过,SSL也不是绝对安全的,也存在被劫持的可能。另外,服务器对HTTPS的配置相对有点复杂,还须要到CA申请证书,并且通常仍是收费的。并且,HTTPS效率也比较低。通常,只有安全要求比较高的系统才会采用HTTPS,好比银行。而大部分对安全要求没那么高的App仍是采用HTTP的方式。api
咱们目前的作法是给每一个接口都添加签名。
给客户端分配一个密钥,每次请求接口时,将密钥和全部参数组合成源串,根据签名算法生成签名值,发送请求时将签名一块儿发送给服务器验证。相似的实现可参考OAuth1.0的签名算法。这样,黑客不知道密钥,不知道签名算法,就算拦截到登陆接口,后续请求也没法成功操做。不过,由于签名算法比较麻烦,并且容易出错,只适合对内的接口。若是大家的接口属于开放的API,则不太适合这种签名认证的方式了,建议仍是使用OAuth2.0的认证机制。
咱们也给每一个端分配一个appKey,好比Android、iOS、微信三端,每一个端分别分配一个appKey和一个密钥。没有传appKey的请求将报错,传错了appKey的请求也将报错。这样,安全性方面又加多了一层防护,同时也方便对不一样端作一些不一样的处理策略。
另外,如今愈来愈多App取消了密码登陆,而采用手机号+短信验证码的登陆方式,我在当前的项目中也采用了这种登陆方式。这种登陆方式有几种好处:数组
- 不须要注册,不须要修改密码,也不须要由于忘记密码而重置密码的操做了;
- 用户再也不须要记住密码了,也不怕密码泄露的问题了;
- 相对于密码登陆其安全性明显提升了。
接口的数据通常都采用JSON格式进行传输,不过,须要注意的是,JSON的值只有六种数据类型:缓存
因此,传输的数据类型不能超过这六种数据类型。
咱们曾经试过传输Date类型
它会转为相似于"2016年1月7日 09时17分42秒 GMT+08:00"这样的字符串,这在转换时会产生问题,不一样的解析库解析方式可能不一样,有的可能会转乱,有的可能直接异常了。要避免出错,必须作特殊处理,本身手动去作解析。为了根除这种问题,最好的解决方案是用毫秒数表示日期。安全
还出现过字符串的"true"和"false",或者字符串的数字,甚至还出现过字符串的"null",致使解析错误,尤为是"null",致使App奔溃
后来查了很久才查出来是该问题致使的。这都是由于服务端对数据没处理好,致使有些数据转为了字符串。因此,在客户端,也不能彻底信任服务端传回的数据都是对的,须要对全部异常状况都作相应处理。服务器
服务器返回的数据结构,通常为:微信
{ code:0, message: "success", data: { key1: value1, key2: value2, ... }}
不一样错误须要定义不一样的状态码,属于客户端的错误和服务端的错误也要区分,好比1XX表示客户端的错误,2XX表示服务端的错误。这里举几个例子:
错误信息通常有两种用途:
data字段只在请求成功时才会有数据返回的。
数据类型限定为对象或数组,当请求须要的数据为单个对象时则传回对象,当请求须要的数据是列表时,则为某个对象的数组。这里须要注意的就是,不要将data传入字符串或数字,即便请求须要的数据只有一个,好比token,那返回的data应该为:
// 正确 data: { token: 123456 } // 错误 data: 123456
接口不可能一成不变,在不停迭代中,总会发生变化。接口的变化通常会有几种:
为了适应这些变化,必须得作接口版本的设计。实现上,通常有两种作法:
大部分状况下会采用第一种方式,当某一个接口有变更时,在这个接口上叠加版本号,并兼容旧版本。App的新版本开发传参时则将传入新版本的version。
若是整个接口系统的根基都发生变更的话,好比微博API,从OAuth1.0升级到OAuth2.0,整个API都进行了升级。
有时候,一个接口的变更还会影响到其余接口,但作的时候不必定能发现。所以,最好还要有一套完善的测试机制保证每次接口变动都能测试到全部相关层面。
当你作架构设计时,必然会面临技术选型的抉择,不一样的技术方案,架构也可能彻底不一样。有哪些技术选型须要作决策呢?好比,App是纯原生开发,仍是Web App,抑或Hybrid App?iOS开发,语言上是选择Objective-C仍是Swift?架构模式用MVC,仍是MVP,或者MVVM?下面根据个人一些经验对某些方面作点总结分享。
关于用原生好,仍是用H5好的争论从没间断过。但我以为,脱离了实际场景来讨论孰好孰坏意义不大。就说咱们目前正在作的项目,先说明下背景:
首先,需求上来讲,大部分页面用H5实现,能够减小不少工做量。但由于不可控因素过高,而时间又短,风险太大。而咱们对原生比较熟,开发效率比较高,不少东西我也控制得了,风险相对比较低。并且,咱们的主推产品是App,微信属于辅助性产品,因此,微信要求也没那么高。所以,我决定以原生为主,H5为辅,App大部分页面用原生完成,小部分用WebView加载H5。
另外,WebView加载H5也有两种模式,一种是加载服务器的H5页面,一种是加载本地的H5页面。加载服务器的H5页面比较简单,WebView只要load一下URL就能够了。加载本地的H5页面,则须要将H5文件存放在本地,包括关联的CSS和JS文件。这种方式相对比较复杂,不过,加载速度会比第一种快不少。咱们当前项目基于上面考虑,只能选择第一种方案。
若是人员和时间资源充足的话,那又如何选型呢?毫无疑问,我会以H5为主,微信和App都有的页面统一用H5,App专有的部分,好比导航栏、标题栏、登陆等,才用原生实现。另外,WebView里的H5有点击事件时,也许是URL连接,也许是调用JS的,都不会让它直接在该WebView里作跳转,须要拦截下来作些原生处理后跳转到一个新的原生页面,原生页面也许嵌入另外一个WebView,用来展现新的H5页面。这是简单的例子,关于Hybrid App详细的设计,之后再讲。另外,关于H5,绝对是大趋势,强烈建议全部App开发人员都去学习。
我在项目中选择了Swift,主要基于三个缘由:
若是你的团队里没人懂Swift,那仍是乖乖用Objective-C吧;若是有一两个懂Swift的,那能够混合开发,并让不懂的人尽快学会Swift;若是都懂了,不用想了,直接上Swift吧。
当语言上选择了Swift,相应的一些第三方库也面临着选型。好比,依赖库管理,Objective-C时代大部分用CocoaPods,Swift时代,我更喜欢Carthage。Carhage是用Swift写的,和CocoaPods相比,轻耦合,也更灵活。我我的也不太喜欢CocoaPods,使用起来比较麻烦,耦合性也较高,我使用过程当中也常常出问题,并且还老是不知道该怎么解决,要移除时也是很是麻烦。
再推荐几个关于Swift的第三方库:
先分别简单介绍下这三个架构模式吧:
MVC:Model-View-Controller,经典模式,很容易理解,主要缺点有两个:
MVP:Model-View-Presenter,MVC的一个演变模式,将Controller换成了Presenter,主要为了解决上述第一个缺点,将View和Model解耦,不过第二个缺点依然没有解决。
MVVM:Model-View-ViewModel,是对MVP的一个优化模式,采用了双向绑定:View的变更,自动反映在ViewModel,反之亦然。
架构模式上,我不会推崇说哪一种模式好,每种模式都各有优势,也各有极限性。越高级的模式复杂性越高,实现起来也越难。最近火热的微服务架构,比起MVC,复杂度不知增长了多少倍。
我在实际项目中思考架构时,也不会想着要用哪一种模式,我只思考现阶段,以现有的人力资源和时间资源,如何才能更快更好地完成需求,适当考虑下如何为后期扩展或重构作准备。就说我前段时间分享的Android项目重构之路系列中讲的那个架构,确切地说,都不属于上面三种架构模式之一。
技术选型,决策关键不在于每种技术方案的优劣如何,而在于你团队的水平、资源的多寡,要根据实际状况选择最适合大家当前阶段的架构方案。当团队拓展了,资源也充足了,确定也是须要再重构的,到时再思考其余更合适更优秀的方案。
一个App,从根本上来讲,就是对数据的处理,包括数据从哪里来、数据如何组织、数据怎么展现,从职责上划分就是:数据管理、数据加工、数据展现。相对应的也就有了三层架构:数据层、业务层、展现层。本文就先讲讲数据层的设计。
数据层,是三层架构中的最底层,负责数据的管理。它主要的任务就是:
根据这三个任务,数据层能够再拆分为三层:
网络层主要就是对网络API的封装。关于API的设计,该系列的第一篇文章接口的设计已经讲过一些。关于如何封装,能够参考Android项目重构之路系列的架构篇和实现篇,其中接口层和本文的网络层是同样的。
还有一些在前面的文章中没有说起到的,在此作一些补充。
首先是不一样网络状态的处理。当网络不可用时,则不该该再去调用API;当网络可用,但不是WIFI时,有些比较耗流量的操做也应该禁止,好比上传和下载大文件;当网络状态不一样时,还能够采用不一样的网络策略,好比,当网络为WIFI时,当前API能够返回更多更全面的数据,还能够预先加载相关联的其余API。
其次,为了节省流量,接口的设计上能够对数据进行简化。例如,对于一些列表类的接口,能够这么设计:只返回更新的部分,好比,上一次请求返回了10条按时间排序的数据,第一条数据为最新的,id为101,当发起下一次请求时,将101的id做为参数调用API,API查到该id,发现该id以后又新增了两条数据,API则只返回新增的这两条数据。
另外,为了保证程序的健壮性,调用API时,对入参的合法性检查也是颇有必要的。并且,也应该定义好本地的错误码和错误信息,保证每一个错误都能正常解析。
本地数据层主要就是作缓存处理,这须要设计好一套缓存策略。设计缓存策略时,有几个问题须要考虑清楚:
哪些须要缓存?
将全部数据都缓存是不明智的,不一样的数据应该有不一样的缓存策略,好比一个电商App,首页的商品列表数据应该缓存,并且缓存时间应该比较长,而每一个商品的详情数据就不必缓存或缓存时间很短。对于一份数据需不须要缓存,判断标准能够是:用户查看该数据的频率高不高?首页商品列表是用户每次启动都会看到的,而每一个商品的详情用户最多只看几回。
缓存在哪里?
从内存读取数据是最快的,但内存很是有限。所以,内存通常只用来缓存使用频率很是高的数据。
文件缓存主要就是图片、音频、视频了。
数据库能够保存大量数据,主要就是用于保存商品列表、聊天记录之类的关系型数据。
然而,无论缓存在哪里,都须要限定好缓存的容量,要按期清理,否则会越积越多。
缓存时间多长?
首先,每份缓存数据都应该设置一个缓存的有效时间,有效期的起始时间以最后一次被调用的时间为准,当该数据长时间没有再被调用到时,就应该从缓存中清理掉。
缓存的有效时间应该设多长呢?能够短至一分钟,长至一星期甚至一个月,具体因数据而异。通常内存的缓存时间不宜太长,程序退出基本就要所有清理了。文件缓存能够设置保留一天或一个星期,能够每隔一天清理一次。数据库缓存再久一些也无所谓,但最好仍是不要超过一个月。
交付层其实就是一个向上层开放的交互接口层,是上层向数据层获取数据的入口。上层向数据层请求数据,它是不关心数据层的数据是从缓存获取仍是从网络获取的,它只关心结果,数据层能给到它想要的数据结果就OK了。所以,交付层主要就是定义一堆开放的接口或协议。
若是接口或协议很是多,那么,将接口或协议按照模块划分也是有必要的。好比微信,按模块划分有:IM、公众号、朋友圈、钱包、购物、游戏等等。模块之间应该尽可能相对独立、松耦合。
业务层其实并不复杂,可是大部分开发人员对其职责并无理解清楚,从而使其沦落为一个数据中转站。我以前分享过的Android项目重构之路系列中提到的核心层,其实就是这里所讲的业务层。但有很多读者反映,他们在实际项目中就只是作一下参数检查,而后直接调用API,与展现层对接的接口基本也与API的接口一致的。这样,业务层无疑就已经变为了一个数据中转站。
因此,设计业务层以前,对业务层的职责要先真正理解清楚。这里,我举两个栗子说明一下。
第一个是新用户注册的例子。
注册时,界面上通常都会要求用户输入手机号、验证码、密码和确认密码。可是,API接口通常只会有三个参数:手机号、验证码和密码,不会有确认密码。所以,调用接口以前,密码和确认密码的一致性检查是必须的。同时,也要检查这些数据是否为空、手机号是否符合规范、验证码是否有效、密码有没有包含了特殊字符等。正确姿式就是当全部检查都经过了以后,才调用API接口。最后,调用注册接口成功后,可能还要再调用一次登陆接口,并可能将用户登陆信息缓存起来,方便用户下次启动应用时自动登陆。全部这些都属于业务逻辑处理,也就是业务层的工做。
第二个是涉及用户验证的例子。
好比,在一个电商App,当用户浏览某个商品,点击购买时,App首先会判断用户是否已经登陆,如未登陆,则会跳转到登陆页面让用户先登陆。若是已经登陆,但token已通过期,那须要先去获取新的token,以后才能进行下一步的购物操做。这些逻辑处理,也是业务层的工做。
所以,业务层就是处理业务逻辑,包括数据的检查、业务分支的处理等。
好比上面第二个例子,可能不少人就会将用户是否已经登陆的判断直接在界面上作处理,当确认登陆后,token也是有效的以后,才调用业务层作购买商品的操做,这就是致使业务层沦落为API的数据中转站的直接表现。
只有真正理解了业务层的职责以后,才能有效地设计业务层与外层的交互接口。
业务层向下,与数据层交互;向上,与展现层交互。
与数据层交互只是调用数据层的接口获取数据,而与展现层交互则须要提供接口给展现层调用。由于业务处理通常属于比较耗时的操做,主要在于底层的网络请求比较耗时,因此提供给展现层的接口数据结果应该以异步的方式提供,所以,接口上就须要提供个回调参数,返回业务处理以后的结果。我以前分享过的Android项目重构之路:实现篇有讲到一种实现方式,可参考。
展现层是三层架构中最复杂的一层了,须要考虑的包括但不限于界面布局、屏幕适配、文字大小、颜色、图片资源、提示信息、动画等等。展现层也是变化最频繁的一个层面,天天改得最多的就是界面了。所以,展现层也是最容易变得混乱不堪的一个层面。一个良好的展现层,应该有较好的可读性、健壮性、维护性、扩展性。
我在Android项目重构之路:界面篇中提到过三个原则,要设计好展现层,至少须要遵循好这三条基本的原则:
关于这三个原则详细的解说,界面篇已经讲过的,我这里就再也不重复。在此,我只作些补充。
关于规范,Android方面,我已经分享过一套Android技术积累:开发规范,主要分为书写规范、命名规范、注释规范三部分。iOS方面,苹果已经有一套Coding Guidelines,主要属于命名方面的规范。当咱们制定本身的开发规范时,首先就要遵照苹果的这份规范,在此基础上再加上本身的规范。
最重要的不是开发规范的制定,而是开发规范的执行。若是没有按照开发规范去执行,那开发规范就等于形同虚设,那代码混乱的问题依然得不到解决。
另外,Android系统自己已经对资源进行了很好的分离,字符串、颜色值、尺寸大小、图片、动画等等都用不一样的xml文件定义。而iOS系统在这方面就逊色不少,只能本身实现,其中一种实现方案就是经过plist文件的方式实现和Android同样的机制。
工程结构其实就是模块的划分,无非分为两类:按业务划分或按组件划分。
好比一个电商App,可能会有首页、附近、分类、个人四大模块,工程结构也根据这四大模块进行划分,Android可能就分为了四个模块包:
一样的,iOS则分为四个分组:home、nearby、category、user。
以后,每一个模块下相应的页面就放入相应的模块。那么,问题来了,商品详情页应该属于哪一个模块呢?首页会跳转到商品详情页,附近也会跳转到商品详情页,分类也会跳转到商品详情页,用户查看订单时也能跳转到商品详情页。有些页面,并不能很明显的区分出属于哪一个模块的。我接手过的,按业务划分的二手项目中(即不是由我搭建的项目),我要找一个页面时,我认为应该属于A模块的,但在A模块却找不到,问了同事才知道在B模块。相似的状况出现过不少次,并且不止出如今我身上,对业务不熟悉的开发人员都会出现这个问题。并且,对业务不熟悉的开发人员开发新的页面或功能时,若是对业务理解不深,划分出错,那也将成为问题,其余人员要找该页面时更难找到了。
所以,我更喜欢按组件划分的工程结构,由于组件每一个人都懂,无论对业务熟不熟悉,查找起来都明显方便不少。Android按组件划分大体以下:
iOS的分组则大体以下:
Android的Activity、Fragment、Adapter,iOS的ViewController,分别定义一个基类,将大部分通用的变量和方法定义和封装好,将减小不少工做量,并且有了统一的设置,也会减小代码的混乱。好比我在Android项目重构之路:实现篇中提到的KBaseActivity和KBaseAdapter的实现就是例子,固然还能够抽离出更多变量和方法。
每一个Activity的onCreate()方法,通常分为三步:
所以,其实能够将onCreate()方法拆分红三个方法:
在基类中将这三个方法定义为抽象方法,由子类去实现,这样,子类就不须要实现onCreate()方法了,只要实现更细化的上述三个方法便可。
iOS的ViewController也是一样的方式,这里就不重复了。
自此,该系列的文章暂时就完结了,方法论比较多,不多涉及到具体的实现。由于具体实现的方案不少,并且还要结合实际项目,没法说哪一个方案好哪一个方案差。但方法论大部分是想通的,因此,本系列主要讲方法论。