博客构建示例

展示如何使用 Redis 去构建一个博客

博客示例

redis 新浪微博 redis博客_用户账号

主要功能

用户账号 发布文章
在主页查看文章
根据分类来查看文章
评论文章

用户账号

用户账号

注册一个博客账号需要:

  1. 账号(account),可以包含英文或数字,不能有重复,会被博客用作唯一的 ID 。
  2. 密码。
  3. 昵称(nickname),发布文章或者评论时显示的名字。

保证账号的唯一性可以使用之前介 绍过的 UniqueSet 类来实现,而储存用户信息则可以使用散列来储存。

储存用户信息

对于每个用户,程序会根据用户的用户名,创建一个格式为 blog::user:: 格式的散列键来储存用户的相关信息。

举个例子,对于账号为“zhang19870511”、密码为“12345”、昵称为“张三”用户,程序将执行以下命令来储存该用户的信息:

HMSET blog::user::zhang19870511 account zhang19870511
password 12345
nickname 张三
我们将用户账号的相关操作抽象成 User 类。

User 类 API

redis 新浪微博 redis博客_实现原理_02

User 类使用示例
>>> user = User(client)
>>> user.create(‘zhang19870511’, ‘12345’, ‘张三’)
>>> user.get_by_account(‘zhang19870511’)
{'account': 'zhang19870511', 'password': '12345', 'nickname': '张三'}
>>> user.try_login(‘‘zhang19870511’, ‘12345’)
{'account': 'zhang19870511', 'password': '12345', 'nickname': '张三'}

发布文章

文章示例

redis 新浪微博 redis博客_数据库_03

发布文章的实现原理

每篇文章由标题、内容、分类、作者、发布时间等信息组成,对于每篇文章,程序会使用 IdGenerator 类生成一个唯一 ID ,并将文章的相关信息 储存到格式为 blog::post:: 的散列键里面。

示例:
HMSET blog::post::9125 id 9125
title “前后端分离的思考与实践(六)”
content “关于前后端分享的思考, ……”
category “前端技术”
author_account “jianfei”
time 1410239558

我们可以将针对文章的相关操作抽象为 Post 类。

Post 类 API

redis 新浪微博 redis博客_redis_04

>>> post = Post(client)
 >>> id = post.create(“前后端分离的思考与实践(六)”, 
 “关于前后端分享的思考,……”, 
 “前端技术”,
 “jianfei”)
 >>> post.get_by_id(id)

在主页查看文章

主页显示的文章

redis 新浪微博 redis博客_数据库_05

主页文章的实现原理

主页会按照文章的发布日期,按从新到旧的 顺序来排列博客包含的文章。
为了储存这些文章,程序会使用一个名 为 blog::index 的列表来储存博客文章,每当有新文章 发布的时候,程序就会将新文章的 ID 推入到这个列表里面。

举个例子,当用户新创建了 ID 为 9125 的文章时,程序将执行以下命令,将文章推入到首 页的文章列表中:
LPUSH blog::index 9125

当用户访问主页的时候,程序会使用 LRANGE 取出列表中最近发布的一些文章,并将它 们展示给用户观看。

我们可以将针对主页文章列表的操作抽象为 Index 类。

Index 类 APi

redis 新浪微博 redis博客_数据库_06

>>> index = Index(client)
>>> index.push(‘10086’)
>>> index.push(‘10087’)
>>> index.paging(1, 10)
[‘10087’, ‘10086’, …]

根据分类查看文章

分类文章示例

redis 新浪微博 redis博客_redis_07

文章分类的实现原理

对于博客的每个分类,程序使用一个格式为 blog::category:: 的列表来储存属于 name 分类的所有文章。

每当有新的文章发布时,程序就会使用 LPUSH 命令,将新文章的 ID 推入到相应的分类文章列表里面,这样我们就会得到一个按照发布时间从新到旧来排列的文章列表。

举个例子,当属于“前端技术”分类的新文章 9125 发布时,程序将执行以下命令,将文章推入到 “前端技术”对应的文章列表里面:
LPUSH blog::category::前端技术 9125

我们可以将针对博客分类的操作抽象到 Category 类里面。

Category 类 API

redis 新浪微博 redis博客_数据库_08

Category 示例
>>> front_end = Category(client, ‘前端技术’)
>>> front_end.include_post(‘12345’)
>>> front_end.include_post(‘128256’)
>>> front_end.include_post(‘251474’)
>>> front_end.count_post()
3
>>> front_end.paging(1, 10)
[‘251474’, ‘128256’, ‘12345’]

评论文章

博客文章评论示例

redis 新浪微博 redis博客_数据库_09

评论的实现

每篇评论都包含评论作者、发布时间和内容,并且对于每篇文章,要有一个列表来 储存该文章属下的所有评论。 这一需求和我们之前为微博和论坛构建评论系统正好相同,所以只要对之前定义的 Comment 类和CommentList 类稍作修改,就可以在这个博客程序里面重用它 们了。

复习

各项功能以及它们的实现原理

redis 新浪微博 redis博客_用户账号_10

id_generator.py
# coding: utf-8

class IdGenerator:

    """
    使用字符串键实现的自增唯一 ID 生成器。
    """

    def __init__(self, client, key):
        """
        设置储存 ID 生成器当前值的键。
        """
        self.client = client
        self.key = key

    def init(self, n):
        """
        初始化生成器的值,需要在系统正式运行之前调用,用于保留少于等于 n 的 ID 号码。
        """
        # 如果键 key 已经有值,那么说明 gen() 已经执行过
        # 为了防止产生重复的 ID ,程序不能执行这个 init() 操作
        if self.client.get(self.key) is not None:
            raise Exception
        self.client.set(self.key, n)

    def gen(self):
        """
        生成一个新的唯一 ID 。
        """
        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

def make_user_key(account):
    return 'blog::user::' + account

class User:

    """
    用户账号操作。
    """

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

    def create(self, account, password, nickname):
        """
        创建新用户,创建钱需要确保给定的 account 未被占用。
        """
        key = make_user_key(account)
        info = {
                'account': account,
                'nickname': nickname,
                'password': password
               }
        self.client.hmset(key, info)

    def get_by_account(self, account):
        key = make_user_key(account)
        return self.client.hgetall(key)

    def try_login(self, account, password):
        user_info = self.get_by_account(account)
        if (user_info is not None) and (user_info['password'] == password):
            return user_info
post.py
# encoding: utf-8

from time import time as current_time
from id_generator import IdGenerator

ID_GENERATOR_KEY = 'blog::post_ids'

def make_post_key(post_id):
    return 'blog::post::' + str(post_id)

class Post:

    """
    文章相关操作。
    """

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

    def create(self, title, content, category, author_account):
        """
        创建一篇新文章。
        """
        post_id = IdGenerator(client, ID_GENERATOR_KEY).gen()
        post_hash = make_post_key(post_id)
        info = {
                'id': post_id,
                'time': current_time(),
                'title': title,
                'content': content,
                'category': category,
                'author_account': author_account
               }
        self.client.hmset(post_hash, info)
        return post_id

    def get_by_id(self, post_id):
        post_hash = make_post_key(post_id)
        return self.client.hgetall(post_hash)
index.py
def __init__(self, client):
        self.client = client
        self.post_list = make_index_key()

    def push(self, post_id):
        """
        将文章推入到列表里面。
        """
        self.client.lpush(self.post_list, post_id)

    def count_post(self):
        """
        返回列表目前已有的文章数量。
        """
        return self.client.llen(self.post_list)

    def paging(self, n, count):
        """
        对文章进行分页。
        """
        start_index = (n-1)*count
        end_index = n*count-1
        return self.client.lrange(self.post_list, start_index, end_index)
category.py
# encoding: utf-8

def make_category_key(name):
    return 'blog::category::' + name

class Category:

    """
    创建一个分类列表来储存所有属于某个分类的文章。
    """

    def __init__(self, client, name):
        self.client = client
        self.post_list = make_category_key(name)

    def include_post(self, post_id):
        """
        将指定的文章添加到当前分类里面。
        """
        self.client.lpush(self.post_list, post_id)

    def count_post(self):
        return self.client.llen(self.post_list)

    def paging(self, n, count):
        """
        按时间从新到旧的顺序,以 count 篇文章为一页,返回当前分类第 n 页上的文章。
        """
        start_index = (n-1)*count
        end_index = n*count-1
        return self.client.lrange(self.post_list, start_index, end_index)
comment.py
# encoding: utf-8

from time import time
from id_generator import IdGenerator

ID_GENERATOR_KEY = 'blog::comment_ids'

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

class Comment:

    """
    帖子评论相关操作。
    """

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

    def create(self, author_id, content):
        """
        创建一个新的评论。
        """
        comment_id = IdGenerator(client, ID_GENERATOR_KEY).gen()
        comment_hash = make_comment_key(comment_id)
        info = {
                'id': comment_id,
                'author_id': author_id,
                'content': content,
                'time': time()
               } 
        self.client.hmset(comment_hash, info)
        return comment_id

    def get_by_id(self, comment_id):
        """
        根据评论 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(post_id):
    return 'blog::post::' + str(post_id) + '::comments'

class CommentList:

    """
    创建一个列表来记录某一帖子下的所有评论(的 ID)。
    """

    def __init__(self, client, post_id):
        """
        设置被评论的帖子的 ID 。
        """
        self.client = client
        self.post_id = post_id
        self.comment_list = make_comment_list_key(post_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)