App接口设计
众所周知,在做移动开发的时候后台接口的设计的好坏决定着移动端开发的速度。
所以在设计接口的时候需要考虑的地方挺多的。在这里是说说看法:
首先是接口的安全机制的设计
在现在app中,大部分的接口都是采用RESTFUL架构的,RESTFUL设计原
是说,当涉及到用户状态时,每次请求都要带上身份验证信息。实现上,大
部分都采用token的认证方式,一般流程是:a. 用户用密码登录成功后,服务器返回token给客户端;
b. 客户端将token保存在本地,发起后续的相关请求时,将token发回给服务器;
c. 服务器检查token的有效性,有效则返回数据,若无效,分两种情况:
d. token错误,这时需要用户重新登录,获取正确的token
e. token过期,这时客户端需要再发起一次认证请求,获取新的token
用户密码和token,后续则可以对该用户做任何事情了。用户只有修改密码才
能夺回控制权。
到了用户密码和token,后续则可以对该用户做任何事情了。用户只有修改密码才能夺回控制权。如何优化呢?第一种解决方案是采用HTTPS。HTTPS在HTTP的基础上添加了SSL安全协议,
安全性可以提高很多。不过,SSL也不是绝对安全的,也存在被劫持的可能。另外,
而且,HTTPS效率也比较低。一般,只有安全要求比较高的系统才会采用HTTPS,
那么怎么样的做法才是效率快,成本低的解决方法呢?目前的做法就是给每个接口都添加
签名,给客户端分配一个密钥,每次请求接口的时候,将密钥和所有参数组成字符串源
然后根据签名算法生成签名值,请求接口的时候将签名一起发给服务器进行校验,这其实
和OAuth1.0的签名算法类似。这样,黑客不知道密钥,不知道签名算法,就算拦截到登
录接口,后续请求也无法成功操作。但是这么做有一个问题:因为签名算法比较麻烦,而且
方式了,建议还是使用OAuth2.0的认证机制。
具体是这么做的:我们也给每个端分配一个appKey,比如Android、iOS、微信三端,每个
端分别分配一个appKey和一个密钥。没有传appKey的请求将报错,传错了appKey的请求
也将报错。这样,安全性方面又加多了一层防御,同时也方便对不同端做一些不同的处理策略。另外,现在越来越多App取消了密码登录,而采用手机号+短信验证码的登录方式,我在当前的
项目中也采用了这种登录方式。这种登录方式有几种好处:不需要注册,不需要修改密码,也不需要因为忘记密码而重置密码的操作了;
用户不再需要记住密码了,也不怕密码泄露的问题了;
相对于密码登录其安全性明显提高了。接口的数据设计
接口的数据我们一般都是用json进行数据的传输,这里需要注意的就是json的数据结构只有6种:
Number:整数或浮点数
String:字符串
Boolean:true 或 false
Array:数组包含在方括号[]中
Object:对象包含在大括号{}中
Null:空类型
所以,传输的数据类型不能超过这六种数据类型。以前,我们曾经试过传输Date类型,它会
转为类似于”2016年1月7日 09时17分42秒 GMT+08:00”这样的字符串,这在转换时会产生问题,
不同的解析库解析方式可能不同,有的可能会转乱,有的可能直接异常了。要避免出错,必须做
特殊处理,自己手动去做解析。为了根除这种问题,最好的解决方案是用毫秒数表示日期。另外,以前的项目中还出现过字符串的”true”和”false”,或者字符串的数字,甚至还出现过字符串 的” null”,
导致解析错误,尤其是”null”,
导致App奔溃,后来查了好久才查出来是该问题导致的。这都是因为服务端对数据没处理好,
导致有些数据转为了字符串。所以,在客户端,也不能完全信任服务端传回的数据都是对的,
需要对所有异常情况都做相应处理。
所以我觉得服务器返回的数据格式一般是:
{
code:0
message: "success"
data: { key1: value1, key2: value2, ... }
}
其中code: 状态码,0表示成功,非0表示各种不同的错误
message: 描述信息,成功时为”success”,错误时则是错误信息
data: 成功时返回的数据,类型为对象或数组
不同错误需要定义不同的状态码,属于客户端的错误和服务端的错误也要区分,比如1XX表示
客户端的错误,2XX表示服务端的错误。这里举几个例子:
0:成功
100:请求错误
101:缺少appKey
102:缺少签名
103:缺少参数
200:服务器出错
201:服务不可用
202:服务器正在重启
错误信息一般有两种用途:一是客户端开发人员调试时看具体是什么错误;二是作为App错误
提示直接展示给用户看。主要还是作为App错误提示,直接展示给用户看的。所以,大部分都
是简短的提示信息。
data字段只在请求成功时才会有数据返回的。数据类型限定为对象或数组,当请求需要的数据为
单个对象时则传回对象,当请求需要的数据是列表时,则为某个对象的数组。这里需要注意的就是,
不要将data传入字符串或数字,即使请求需要的数据只有一个,比如token,那返回的data应该为:
// 正确
data: { token: 123456 }
// 错误
data: 123456
接口的版本设计
接口不可能一成不变,在不停迭代中,总会发生变化。接口的变化一般会有几种:
数据的变化,比如增加了旧版本不支持的数据类型
参数的变化,比如新增了参数
接口的废弃,不再使用该接口了
为了适应这些变化,必须得做接口版本的设计。实现上,一般有两种做法:每个接口有各自的版本,一般为接口添加个version的参数。
整个接口系统有统一的版本,一般在URL中添加版本号,比如http://api.domain.com/v2。
大部分情况下会采用第一种方式,当某一个接口有变动时,在这个接口上叠加版本号,并兼容
旧版本。App的新版本开发传参时则将传入新版本的version。如果整个接口系统的根基都发生变动的话,比如微博API,从OAuth1.0升级到OAuth2.0,
整个API都进行了升级。
有时候,一个接口的变动还会影响到其他接口,但做的时候不一定能发现。因此,最好还要
有一套完善的测试机制保证每次接口变更都能测试到所有相关层面。
android中用Stack更好的管理Activity
首先Stack是什么?很好,在java中给我们提供了它。
保存对象实例,实际上是保存对象实例的属性值,属性的类型和对象本身的类型标记等,并不保存对象的方法(方法是指令,保存在stack中)。对象实例在heap中分配好以后,需要在stack中保存一个4字节的heap内存地址,用来定位该对象实例在heap中的位置,便于找到该对象实例。那么在android中,我们知道activity是以栈的形式去管理这一些。比如说我们要指定的去关闭某个activity,退出应用等操作。说了这么多直接上代码吧:
public class AppManager {
private static Stack<Activity> activityStack;
private static AppManager instance;
private AppManager() {
}
public static AppManager getAppManager() {
if (instance == null) {
instance = new AppManager();
}
return instance;
}
/**
* 添加activity
* @param activity
*/
public void addActivity(Activity activity) {
if (activityStack == null) {
activityStack = new Stack<Activity>();
}
activityStack.add(activity);
}
/**
* 当前正在运行的activity
* @return
*/
public Activity currentActivity() {
Activity activity = activityStack.lastElement();
return activity;
}
/**
* 关闭activity
*/
public void finishActivity() {
Activity activity = activityStack.lastElement();
finishActivity(activity);
}
/**
* 关于指定的activity
* @param activity
*/
public void finishActivity(Activity activity) {
if (activity != null) {
activityStack.remove(activity);
activity.finish();
activity = null;
}
}
/**
* 关闭指定类名的activity
* @param cls
*/
public void finishActivity(Class<?> cls) {
for (Activity activity : activityStack) {
if (activity.getClass().equals(cls)) {
finishActivity(activity);
}
}
}
/**
* 退出应用
* @param context
*/
public void finishAllActivityAndExit(Context context) {
if (null != activityStack) {
for (int i = 0, size = activityStack.size(); i < size; i++) {
if (null != activityStack.get(i)) {
activityStack.get(i).finish();
}
}
activityStack.clear();
}
}
}