分享

记一个社交APP的开发过程

 WindySky 2016-08-10

虽然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

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多