虽然API是RESTFul的,但是感觉移动App的开发跟做网站还是稍有些区别的,比如用户身份认证。 用户身份认证的实现网站能做到自动登录,通常是因为有一个能记录token的cookie,在web app拦截请求的过滤器里面读取token的值然后进行验证,另外这个cookie会有一个过期时间,一星期一个月或者用户自己手动选择,不过现在越来越多的网站貌似已经不在去设置这个cookie过期时间了,一直会有效的样子,从产品的角度确实是方便用户,但是从安全的角度有一定的隐患,绝大多数时候增加了安全性就要失去一些产品上的方便,反之亦然。 对于移动应用,没有cookie,一般都是登录的时候服务端给客户端返回一个token,之后的每次请求,客户端都要在请求的URL上将token做为参数传递给服务端,以此来识别身份,比如GET /api/v1/timeline?token=xxxxxxxxxx, 另外对于移动应用,这个token往往没有过期这一说,除非用户手动的退出应用。 服务端怎么去把这个token跟具体哪个用户对应起来?通常有两种方式,一是将所有用户信息比如用户ID、token创建时间等通过某种可以反解的加密算法和一个密钥加密起来,把加密字符串传递给客户端,当客户端把token字符串传递过来的时候,再通过密钥去解开然后活动相关的用户信息;第二种方式是根据用户的唯一特征在加一些随机的干扰,生成一个不可逆的hash,然后服务端会保存着用户ID到这个hash的映射关系,比如像一个这样的表结构: CREATE TABLE `user_token` ( `token` varchar(32) NOT NULL, `user_id` int(11) NOT NULL, `created_on` datetime NOT NULL, PRIMARY KEY (`token`), UNIQUE (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 一是为了安全(全局的密钥太危险了),二是为了能对发布出去的token做到很好的控制,我这里选择的是第二种实现方式,但第二种方式最大的问题就是每次验证用户身份的时候都要去读库,因此在前面加一个Redis,Redis的缓存过期时间为一天,先去Redis里面查找,如果Redis里面没有那么再去库里面查找。这些逻辑都是在web.py的一个processor中进行的,代码如下: def checktoken(handler, *args): if web.ctx.path in _no_token_urls: return handler(*args) token = form(True, 'token') user_id = check_token(token) if token else None if user_id: mark_online(user_id) web.ctx.user_id, web.ctx.token = user_id, token return handler(*args) else: return output_json({"status":"ERROR", "error":{"code":"invalid token"}, "result":{}})_no_token_urls是一个集合,里面包含那些不需要token就可以使用的功能,比如注册、登录,另外web.py提供一个非常给力的工具就是web.ctx,它是一个记录请求线程上下文的参数,线程之间不会干扰,从token获取的用户ID就可以放到web.ctx里面,供后续逻辑使用。 用户在线标记对于基于socket的应用,标记哪些用户在线哪些用户不在线可能根本就不是问题,但是对于这种基于Http无状态的应用,要标记是否在线就要费些周折了,好在这年头神器和资源众多,参考了一些文章,比如 http://flask./snippets/71/ 然后没写多少代码就把这个问题搞定了。 大体的思路都是这样的,规定一个用户活跃时间段,比如5min, 如果用户在5min之内没有任何活动(请求),那么就算他已经不在线了,把每一分钟的活跃用户保存到一个集合,比如Redis的Set,当获取在线用户的时候,把最近5分钟的set union一下得到的用户id就是在线用户了。 为了节约内存的使用,我这里没有使用redis的set,而是使用了redis的bitmap去做标记,每个bit代表一个用户,bit的索引是用户的id,bit值为0的时候说明用户不在线,bit值为1的时候说明用户在线。 def mark_online(user_id): now = int(time.time()) expires = now + (_MAX_ACTIVE_TIME * 60) + 10 user_online_key = rds.keys.user_online.key(now // 60) p = rds.default.pipeline() p.setbit(user_online_key, user_id, 1) p.expireat(user_online_key, expires) p.execute()redis API对bitmap的操作已经做的非常友好,可以使用setbit操作来直接设置某个位的值,0还是1,_MAX_ACTIVE_TIME是我们规定的活跃时间段,5min,这个函数也是每次在web.py的processor中调用。 那么怎么获取数据呢?比如我要得到我的在线好友列表? def query_online(*user_id_list): current = int(time.time()) // 60 minutes = xrange(_MAX_ACTIVE_TIME) keys = [rds.keys.user_online.key(current - x) for x in minutes] online_data_lst = rds.default.mget(*keys) online_users = set() for online_data in online_data_lst: if not online_data: continue a = bitarray() a.frombytes(online_data) a_len = len(a) online_users.update({user_id for user_id in user_id_list if user_id < a_len and a[user_id]}) return online_users |
|