redis构建微博(redis应用构建篇)

微博功能分析

学习如何使用 Redis 来构建一个微博

新浪微博的用户主页

在这里插入图片描述

主要功能

用户账号
关注和被关注(用户关系)
发微博
查看微博时间线
对微博进行点赞、评论和转发

用户账号

如何创建一个微博账号

用户账号示例

在这里插入图片描述

创建用户账号的方法

注册一个新的微博账号,有三样信息是必须的:

  1. 邮箱地址,不能和已有的 邮箱地址相同(实际上也可以使用手机来注册,但 这里只考虑邮箱)。
  2. 密码
  3. 名字,不能和已有的名字相同。
    需要解决的问题:
  4. 实现一个检查指定的邮箱和名字是否已经被使用的程序。
  5. 实现一个储存邮箱地址、密码和名字等用户信息的程序,并为每个用户分配一个唯一的用户 ID 。
唯一值检查

为了检查指定的名字和邮箱是否已经存在,程序会使用 weibo::used_names 和 weibo::used_emails 这两个集合来分别储存所有已经被使用的名字以及所有已 经被使用的邮箱地址:
weibo::used_names = {‘mary’, ‘jack’, ‘tom’, …}
weibo::used_emails = {‘abcdefg999@gmail.com’, ‘go123@qq.com’, ‘qwe10086@163.com’, …}
每当用户尝试注册账号的时候,程序就检查这两个集合,看指定的名字或者 邮箱是否已经被使用。

如果用户指定的名字和邮箱都没有被使用,那么程序就会允 许用户注册,当用户成功注册的时候,程序就会使用 SADD 命令,把新用户的名字和邮箱地址分别添加到两个集合里面。

我们把检查唯一值的操作和添加唯一值的操作抽象到 UniqueSet 类里面。

UniqueSet 类

在这里插入图片描述
检查指定的邮箱 hello@gmail.com 是否已经被占用:

>>> used_emails = UniqueSet(client, “weibo::used_emails”)
>>> used_emails.is_include(“hello@gmail.com”)
False
>>> used_emails.add(“hello@gmail.com”)
储存账号信息

当用户指定的邮箱地址和名字都可以使用 时,程序就会为这个新用户分配一个唯一 ID ,并将 ID 、邮箱地址、名字、密码等信息都储存到格式为 weibo::user:: 的散列键里面。
举个例子,如果程序给用户分配的 ID 为 10086 ,用户给定的邮箱为 hello@gmail.com 、名字为 peter 、密码为 123456 ,那么程序将执行以下命令:

HMSET weibo::user::10086 id 10086 
 email hello@gmail.com
 name peter 
 password 123456

虽然键名里面已经包含了 ID ,但是将 ID 也包含在散列里面,在取出用 户的所有信息时比较方便。至于生成用户 ID 的工作,可以用之前介 绍过的 IdGenerator 类来完成。

关联用户的 email 地址和 ID

除了将用户的信息储存到散列里面之外,程序 还必须再使用一个散列来记录用户的 email 和 ID 之间的 关联,这是因为用户在登录的时候,需要输入 email 和密码,但如果程序不知道这个 email 对应的用户ID 是什么的话,那么用户身份验证操作将无法进行。

email 和 ID 之间的关联使用 weibo::email_to_uid 散列来保存,对于 email 为 hello@gmail.com , ID 为10086 的账号,程序将执行以下命令来对它们进行关联:
HSET weibo::email_to_uid hello@gmail.com 10086

这样在登录的时候,程序就可以通过 weibo::email_to_uid 这个散列,查询到与邮箱 hello@gmail.com 对 应的用户的 ID 为 10086 ,接着就可以通过取出 weibo::user::10086 散列里面的信息来进行身份验证操作。

我们可以将针对用户操作抽象成 User 类。

User 类

在这里插入图片描述
user = User(client)
uid = user.create(‘peter’, ‘hello@gmail.com’, ‘123456’)
user_info = user.try_login(‘hello@gmail.com’, ‘123456’)

用户关系

关注和被关注

用户关注的人

在这里插入图片描述

关注用户的人(粉丝)

在这里插入图片描述

用户关系的实现

为了实现关注功能,程序会为每个用户使用两个集合:

  1. 关注集合,用于储存用户关注的人的 ID ,键名格式为 weibo::user::::following 。
  2. 粉丝集合,用于储存用户的粉丝的 ID ,键名格式为 weibo::user::::fans 。

每当用户 A 关注用户 B 的时候,程序会执行以下两个动作:

  1. 将用户 B 的 ID 添加到用户 A 的关注集合里面。
  2. 将用户 A 的 ID 添加到用户 B 的粉丝集合里面。

举个例子,假设 ID 为 10086 的用户关注了 ID 为 12345 的用户,那么程序将执行以下命令:

  1. SADD weibo::user::10086::following 12345
  2. SADD weibo::user::12345::fans 10086

我们可以将用户之间的关系操作抽象为 RelationShip 类。

RelationShip 类

在这里插入图片描述

RelationShip 类使用示例
>>> relation = RelationShip(client)
>>> relation.follow(10086, 12345)
>>> relation.is_following(10086, 12345)
True
>>> relation.get_all_following(10086)
{‘12345’}
>>> relation.get_all_fans(12345)
{‘10086’}

发微博

储存微博并将它广播至各个 时间线

发送微博示例

在这里插入图片描述
每当用户发送一条微博的时候,程序需要执行两个动作:

  1. 将微博的内容、作者、 发送时间等信息储存起来,并分配一个 ID 来代表这条微博,用于执行后续操作(比如查看微博、评论、点赞,等等)。
  2. 将被发送的微博推入到用户自己的时间线(timeline)里面,以及所有粉丝的时间线里面。本节先来实现第一个动作,之后再来介绍第二个动作的实现方法。
发送微博功能的实现

对于每条微博,程序都会 为它分配一个唯一的 ID ,并将这条微博的 ID 、作者的用户 ID、内容、UNIX 时间戳格式的发送时间等信息储存到一个格式为 weibo::message:: 的散列键里面。

举个例子,假设 ID 为 10086 的用户在时间 1409468643 发送了一条内容为 “hello world” 的微博,并且程序为这条微博分配的唯一 ID 为 65535 ,那么程序将执行以下命令来储存这条微博:

HMSET weibo::message::65535 id 65535
 author 10086
 time 1409468643
 content “hello world”

我们可以将针对微博的相关操作抽象为 Message 类。

Message 类的实现

在这里插入图片描述
msg = Message(client)
message_id = msg.create(10086, “hello world”)
message_info = msg.get_by_id(message_id)

时间线

按照发表时间的先后顺序来储存微博

定制时间线和个人时间线

在这里插入图片描述

时间线功能的实现

程序会为每个用户储存两条时间线:

  1. 定制时间线,包含了用户自己以及用户正在关注的人发送的微博,键名为 weibo::user::::custom_timeline 。
  2. 个人时间线,只包含用户自己发送的微博,键名为 weibo::user::::personal_timeline 。

每条时间线都是一个有序集合,有序集合的元素 为微博的 ID ,分值为微博的发布时间。
每当用户发送新的微博时,程序就会使用 ZADD 命令,将新微博的 ID 以及发布时间添加到有序集合里面。

举个例子,如果 ID 为 10086 的用户在时间 1409485668 发表了 ID 为 65535 的新微博,那么为了将这条微博推入到用户的个人时间线里面,程序将执行命令:
ZADD weibo::user::10086::personal_timeline 1409485668 65535

广播操作

每当用户发送一条新微博的时候,程序不仅要将这条微博推入到该用户的定制时间线和个人时间线里面,还需要将这条微博推入到该用户的所有粉丝的定制时间线里面。

举个例子,假设 ID 为 255255 、 123321、 98765 的用户正在关注 ID 为 10086 的用户,那么当 ID 为10086 的用户在时间 1409485668 发送一条 ID 为 65535 的微博时,程序不仅要将这条微博推入到用户 10086 的定制时间线和个人时间线里面:

ZADD weibo::user::10086::custom_timeline 1409485668 65535
ZADD weibo::user::10086::personal_timeline 1409485668 65535
还需要将这条微博推入到 10086 的三个粉丝的定制时间线里面:
ZADD weibo::user::255255::custom_timeline 1409485668 65535
ZADD weibo::user::123321::custom_timeline 1409485668 65535
ZADD weibo::user::98765::custom_timeline 1409485668 65535
Timeline 类

在这里插入图片描述

Timeline 类的使用示例
tl = Timeline(client)
# 将微博 65535 推入到 10086 的两个时间线里面
tl.custom_push(10086, 65535, 1409485668)
tl.personal_push(10086, 65535, 1409485668)
# 将微博 65535 推入到 25525、123321、98765 三个粉丝的定制时间线里面
tl.broadcast(65535, 1409485668, 255255, 123321, 98765)
# 按每页五条微博计算,获取用户 10086 在定制时间线第一页的微博
tl.custom_paging(10086, 1, 5)
# 按每页十条微博计算,获取用户 255255 在个人时间线第二页的微博
tl.personal_paging(255255, 2, 10)

为微博点赞

支持自己喜欢的微博

点赞功能的实现

在这里插入图片描述
前面的课程已经介绍过如何使用集合来实现点赞(投票)功能,具体到微博 这个例子,对于每条微博,程序都会创建一个
weibo::message::::like 集合来储存所有已经为该微博点赞的用户。 举个例子,假设 ID 为 10086 的用户为 ID 为 65535 的微博点赞了,那么程序将执行命令:
SADD weibo::message::65535::like 10086

我们将针对微博的点赞操作抽象为 LikeMessage 类。

LikeMessage 类的实现

在这里插入图片描述

LikeMessage 类的使用示例
>>> vote = LikeMessage(client, 65535)
>>> vote.like(10086)
>>> vote.is_liking(10086)
True
>>> vote.count()
1
>>> vote.get_all_liking_user()
{10086}

评论微博

对微博内容进行讨论

评论示例

在这里插入图片描述
用户可以对每条微博进行评论。 对于每条评论,程序都会分配一个唯一的 评论 ID ,并使用格式为 weibo::comment:: 的散列键来储存评 论的发布者、发布时间和内容。

对于每条微博,程序都会使用一个列表来 储存该微博获得的所有评论的 ID ,列表的键名格式为
weibo::message::::comments 。

每当微博有新的评论出现时,程序就会将新评论的 ID 推入到列表里面。

评论实现示例

举个例子,假设 ID 为 10086 的用户对 ID 为 65535 的微博发表了内容为 “nice post” 的评论,并且程序为评论分配的 ID 为 3050 的话,那么程序将执行以下命令来储存这条评论:

HMSET weibo::comment::3050 id 3050
 author 10086
 content “nice post”
 time 1410123456

并执行以下命令,将评论推入到 ID 为 65535 的微博的评论列表里面:
LPUSH weibo::message::65535::comments 3050

我们可以将针对评论的操作抽象为 Comment 类,将针对评论列表的操作抽象为 CommentList 类。

Comment 类

在这里插入图片描述
comment = Comment(client)
comment_id = comment.create(10086, “nice post”)
comment_info = comment.get_by_id(comment_id)

CommentList 类

在这里插入图片描述
comment_list = CommentList(client, 65535) # 指定微博 65535 的评论列表
comment_list.push(3050) # 将 ID 为 3050 的评论推入到微博的评论列表里面
comment_list.paging(1, 10) # 按 10 条评论一页计算,获取微博第一页的评论

转发微博

在一条微博里面引用另一条微博

转发微博示例

在这里插入图片描述
普通微博和转发产生的微博之间的唯一区别就是,转发产生的微博会引用另一条微博。因此,为了实现转发功能,我们需要在微博已有的 ID 、作者、发布时间和内容这几个属性的基础上,添加一个origin_message_id 属性 ,这个属性记录了被转发微博的 ID ,通过这个 ID 可以找到被转发微博的详细信息。

转发微博的实现

举个例子,如果 ID 为 12345 的用户在时间 1500000000 转发了 ID 为 65535 的消息,转发时说的内容为 “又获得推荐了,感谢码农周刊![太开心]” 的话,并因此产生一条 ID 为 100000 的新微博的话,那么程序将执行以下命令来创建这条转发微博:

HMSET weibo::message::100000 id 100000
 author 12345
 time 1500000000
 content “又获得推荐了,感谢码农周刊![太开心]”
 origin_message_id 65535
创建转发微博的方法

为了实现转发微博功能,我们将对之前定义的 Message 类的 create 方法进行修改,将 create 方法从原来的:
create(author, content)
改为:
create(author, content, origin_message_id=None) 当 origin_message_id 为 None 时,这条消息就是一条普通微博,否 则的话,这条微博就是一条转发微博。
以下代码创建了一条转发微博,被转发的微博的 ID 为 65535 :
msg = Message(client)
retweet_msg_id = msg.create(12345, “又获得推荐了,感谢码农周刊![太开心]”, 65535)

转发微博的处理

除了多出一个引用属性 origin_message_id 之外,转发产生的微博和普通微博之 间没有任何不同,所以转发微博可以与普通微博共用相同格式的 键名,以及 ID 生成器、时间线、点赞、评论等功能。

唯一需要注意的是,当程序需要取出 时间线里面的微博并显示给用户看的时候,普通微博可以直接 显示,而转发微博则需要根据 origin_message_id 取出被引用的微博之后,才能 显示。

复习

微博各项功能的实现方法

在这里插入图片描述

id_generator.py
# coding: utf-8

class IdGenerator:

    def __init__(self, client, key):
        self.client = client
        self.key = key

    def init(self, n):
        self.client.set(self.key, n)

    def gen(self):
        new_id = self.client.incr(self.key)
        return int(new_id)
unique_set.py
# encoding: utf-8

class UniqueSet:

    def __init__(self, client, key):    
        self.client = client
        self.key = key

    def add(self, element):
        self.client.sadd(self.key, element)

    def is_include(self, element):
        return self.client.sismember(self.key, element)
user.py
# encoding: utf-8

from id_generator import IdGenerator


def make_user_key(uid):
    return 'weibo::user::' + str(uid)

def make_email_to_uid_key():
    return 'weibo::email_to_uid'

class User:

    def __init__(self, client):
        self.client = client

    def create(self, name, email, password):
        # 储存用户信息
        new_id = IdGenerator(client, "weibo::user_id").gen()
        key = make_user_key(new_id)
        self.client.hmset(key, {
                                'id': new_id,
                                'name': name,
                                'email': email,
                                'password': password
                                })
        # 关联用户 email 与 ID
        self.client.hset(make_email_to_uid_key(), email, new_id)
        # 返回用户 ID 作为函数的值
        return new_id

    def get_by_id(self, uid):
        key = make_user_key(uid)
        return self.client.hgetall(key)

    def try_login(self, email, password):
        # 根据输入的邮箱,获取用户 ID
        uid = self.client.hget(make_email_to_uid_key(), email)
        # 如果找不到与邮箱对应的用户 ID ,那么说明这个邮箱未注册过账号
        if uid is None:
            return None

        # 根据用户 ID ,获取用户信息,并进行密码对比
        # 邮箱不用对比,因为用户信息就是根据邮箱来查找的
        user_info = self.get_by_id(uid)
        if user_info['password'] == password:
            return user_info
relation_ship.py
# encoding: utf-8

def make_following_key(uid):
    return 'weibo::user::' + str(uid) + '::following'

def make_fans_key(uid):
    return 'weibo::user::' + str(uid) + '::fans'

class RelationShip:

    def __init__(self, client):
        self.client = client

    def follow(self, fans, target):
        # 将 fans 设置为 target 的粉丝
        target_fans_set = make_fans_key(target)
        self.client.sadd(target_fans_set, fans)
        # 将 target 添加到 fans 的关注里面
        fans_following_set = make_following_key(fans)
        self.client.sadd(fans_following_set, target)

    def is_following(self, fans, target):
        fans_following_set = make_following_key(fans)
        return self.client.sismember(fans_following_set, target)

    def is_following_each_other(self, fans, target):
        return self.is_following(fans, target) and self.is_following(target, fans)

    def get_all_following(self, user):
        return self.client.smembers(make_following_key(user))

    def get_all_fans(self, user):
        return self.client.smembers(make_fans_key(user))

    def common_following(self, a, b):
        a_following = make_following_key(a)
        b_following = make_following_key(b)
        return self.client.sinter(a_following, b_following)
message.py
# encoding: utf-8

from id_generator import IdGenerator
from time import time

ID_GENERATOR_KEY = 'weibo::message_ids'

def make_message_key(id):
    return 'weibo::message::' + str(id)

class Message:

    def __init__(self, client):
        self.client = client

    def create(self, author, content, origin_message_id=None):
        message_id = IdGenerator(self.client, ID_GENERATOR_KEY).gen()
        key = make_message_key(message_id)
        info = {
                'id': message_id,
                'author': author,
                'time': time(),
                'content': content
                }
        if origin_message_id is not None:
            info['origin_message_id'] = origin_message_id
        self.client.hmset(key, info)
        return message_id

    def get_by_id(self, message_id):
        key = make_message_key(message_id)
        return self.client.hgetall(key)
timeline.py
# encoding: utf-8

def make_custom_timeline_key(uid):
    return 'weibo::user::' + str(uid) + '::custom_timeline'

def make_personal_timeline_key(uid):
    return 'weibo::user::' + str(uid) + '::personal_timeline'

class Timeline:

    def __init__(self, client):
        self.client = client

    def _push(self, key, message_id, time):
        self.client.zadd(key, message_id, time)

    def custom_push(self, user_id, message_id, time):
        custom_timeline = make_custom_timeline_key(user_id)
        self._push(custom_timeline, message_id, time)

    def personal_push(self, user_id, message_id, time):
        personal_timeline = make_personal_timeline_key(user_id)
        self._push(personal_timeline, message_id, time)

    def broadcast(self, message_id, time, *fans_ids):
        for user_id in fans_ids:
            self.custom_push(user_id, message_id, time)

    def _paging(self, timeline, number, count):
        start_index = (number-1)*count
        end_index = number*count-1
        return self.client.zrevrange(timeline, start_index, end_index)

    def custom_paging(self, user_id, number, count=10):
        custom_timeline = make_custom_timeline_key(user_id)
        return self._paging(custom_timeline, number, count)

    def personal_paging(self, user_id, number, count=10):
        personal_timeline = make_personal_timeline_key(user_id)
        return self._paging(personal_timeline, number, count)
like_message.py
# encoding: utf-8

def make_like_key(message_id):
    return 'weibo::message::' + str(message_id) + '::like'

class LikeMessage:

    def __init__(self, client, message_id):
        self.client = client
        self.message_id = message_id
        self.key = make_like_key(message_id)

    def like(self, user_id):
        self.client.sadd(self.key, user_id)

    def is_liking(self, user_id):
        return self.client.sismember(self.key, user_id)

    def undo(self, user_id):
        self.client.srem(self.key, user_id)

    def count(self):
        return self.client.scard(self.key)

    def get_all_liking_user(self):
        return self.client.smembers(self.key)
comment.py
# encoding: utf-8

from time import time
from id_generator import IdGenerator

ID_GENERATOR_KEY = 'weibo::comment_ids'

def make_comment_key(comment_id):
    return 'weibo::comment::' + str(comment_id)

class Comment:

    def __init__(self, client):
        self.client = client

    def create(self, author, content):
        comment_id = IdGenerator(client, ID_GENERATOR_KEY).gen()
        comment_hash = make_comment_key(comment_id)
        info = {
                'id': comment_id,
                'author': author,
                'content': content,
                'time': time()
               } 
        self.client.hmset(comment_hash, info)
        return comment_id

    def get_by_id(self, comment_id):
        comment_hash = make_comment_key(comment_id)
        return self.client.hgetall(comment_hash)
comment_list.py
# encoding: utf-8

def make_comment_list_key(message_id):
    return 'weibo::message::' + str(message_id) + '::comments'

class CommentList:

    def __init__(self, client, message_id):
        self.client = client
        self.message_id = message_id
        self.comment_list = make_comment_list_key(message_id)

    def push(self, comment_id):
        self.client.lpush(self.comment_list, comment_id)

    def count(self):
        return self.client.llen(self.comment_list)

    def paging(self, number, count):
        start_index = (number-1)*count
        end_index = number*count-1
        return self.client.lrange(self.comment_list, start_index, end_index)
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值