博客构建示例
展示如何使用 Redis 去构建一个博客
博客示例
主要功能
用户账号 发布文章
在主页查看文章
根据分类来查看文章
评论文章
用户账号
用户账号
注册一个博客账号需要:
- 账号(account),可以包含英文或数字,不能有重复,会被博客用作唯一的 ID 。
- 密码。
- 昵称(nickname),发布文章或者评论时显示的名字。
保证账号的唯一性可以使用之前介 绍过的 UniqueSet 类来实现,而储存用户信息则可以使用散列来储存。
储存用户信息
对于每个用户,程序会根据用户的用户名,创建一个格式为 blog::user:: 格式的散列键来储存用户的相关信息。
举个例子,对于账号为“zhang19870511”、密码为“12345”、昵称为“张三”用户,程序将执行以下命令来储存该用户的信息:
HMSET blog::user::zhang19870511 account zhang19870511
password 12345
nickname 张三
我们将用户账号的相关操作抽象成 User 类。
User 类 API
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': '张三'}
发布文章
文章示例
发布文章的实现原理
每篇文章由标题、内容、分类、作者、发布时间等信息组成,对于每篇文章,程序会使用 IdGenerator 类生成一个唯一 ID ,并将文章的相关信息 储存到格式为 blog::post:: 的散列键里面。
示例:
HMSET blog::post::9125 id 9125
title “前后端分离的思考与实践(六)”
content “关于前后端分享的思考, ……”
category “前端技术”
author_account “jianfei”
time 1410239558
我们可以将针对文章的相关操作抽象为 Post 类。
Post 类 API
>>> post = Post(client)
>>> id = post.create(“前后端分离的思考与实践(六)”,
“关于前后端分享的思考,……”,
“前端技术”,
“jianfei”)
>>> post.get_by_id(id)
在主页查看文章
主页显示的文章
主页文章的实现原理
主页会按照文章的发布日期,按从新到旧的 顺序来排列博客包含的文章。
为了储存这些文章,程序会使用一个名 为 blog::index 的列表来储存博客文章,每当有新文章 发布的时候,程序就会将新文章的 ID 推入到这个列表里面。
举个例子,当用户新创建了 ID 为 9125 的文章时,程序将执行以下命令,将文章推入到首 页的文章列表中:
LPUSH blog::index 9125
当用户访问主页的时候,程序会使用 LRANGE 取出列表中最近发布的一些文章,并将它 们展示给用户观看。
我们可以将针对主页文章列表的操作抽象为 Index 类。
Index 类 APi
>>> index = Index(client)
>>> index.push(‘10086’)
>>> index.push(‘10087’)
>>> index.paging(1, 10)
[‘10087’, ‘10086’, …]
根据分类查看文章
分类文章示例
文章分类的实现原理
对于博客的每个分类,程序使用一个格式为 blog::category:: 的列表来储存属于 name 分类的所有文章。
每当有新的文章发布时,程序就会使用 LPUSH 命令,将新文章的 ID 推入到相应的分类文章列表里面,这样我们就会得到一个按照发布时间从新到旧来排列的文章列表。
举个例子,当属于“前端技术”分类的新文章 9125 发布时,程序将执行以下命令,将文章推入到 “前端技术”对应的文章列表里面:
LPUSH blog::category::前端技术 9125
我们可以将针对博客分类的操作抽象到 Category 类里面。
Category 类 API
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’]
评论文章
博客文章评论示例
评论的实现
每篇评论都包含评论作者、发布时间和内容,并且对于每篇文章,要有一个列表来 储存该文章属下的所有评论。 这一需求和我们之前为微博和论坛构建评论系统正好相同,所以只要对之前定义的 Comment 类和CommentList 类稍作修改,就可以在这个博客程序里面重用它 们了。
复习
各项功能以及它们的实现原理
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)