redis构建微博(redis应用构建篇)
文章目录
- 微博功能分析
- 新浪微博的用户主页
- 主要功能
- 用户账号
- 用户账号示例
- 创建用户账号的方法
- 唯一值检查
- UniqueSet 类
- 储存账号信息
- 关联用户的 email 地址和 ID
- User 类
- 用户关系
- 用户关注的人
- 关注用户的人(粉丝)
- 用户关系的实现
- RelationShip 类
- RelationShip 类使用示例
- 发微博
- 发送微博示例
- 发送微博功能的实现
- Message 类的实现
- 时间线
- 定制时间线和个人时间线
- 时间线功能的实现
- 广播操作
- Timeline 类
- Timeline 类的使用示例
- 为微博点赞
- 点赞功能的实现
- LikeMessage 类的实现
- LikeMessage 类的使用示例
- 评论微博
- 评论示例
- 评论实现示例
- Comment 类
- CommentList 类
- 转发微博
- 转发微博示例
- 转发微博的实现
- 创建转发微博的方法
- 转发微博的处理
- 复习
- 微博各项功能的实现方法
- id_generator.py
- unique_set.py
- user.py
- relation_ship.py
- message.py
- timeline.py
- like_message.py
- comment.py
- comment_list.py
微博功能分析
学习如何使用 Redis 来构建一个微博
新浪微博的用户主页
主要功能
用户账号
关注和被关注(用户关系)
发微博
查看微博时间线
对微博进行点赞、评论和转发
用户账号
如何创建一个微博账号
用户账号示例
创建用户账号的方法
注册一个新的微博账号,有三样信息是必须的:
- 邮箱地址,不能和已有的 邮箱地址相同(实际上也可以使用手机来注册,但 这里只考虑邮箱)。
- 密码
- 名字,不能和已有的名字相同。
需要解决的问题: - 实现一个检查指定的邮箱和名字是否已经被使用的程序。
- 实现一个储存邮箱地址、密码和名字等用户信息的程序,并为每个用户分配一个唯一的用户 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.comname 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’)
用户关系
关注和被关注
用户关注的人
关注用户的人(粉丝)
用户关系的实现
为了实现关注功能,程序会为每个用户使用两个集合:
- 关注集合,用于储存用户关注的人的 ID ,键名格式为 weibo::user::::following 。
- 粉丝集合,用于储存用户的粉丝的 ID ,键名格式为 weibo::user::::fans 。
每当用户 A 关注用户 B 的时候,程序会执行以下两个动作:
- 将用户 B 的 ID 添加到用户 A 的关注集合里面。
- 将用户 A 的 ID 添加到用户 B 的粉丝集合里面。
举个例子,假设 ID 为 10086 的用户关注了 ID 为 12345 的用户,那么程序将执行以下命令:
- SADD weibo::user::10086::following 12345
- 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’}
发微博
储存微博并将它广播至各个 时间线
发送微博示例
每当用户发送一条微博的时候,程序需要执行两个动作:
- 将微博的内容、作者、 发送时间等信息储存起来,并分配一个 ID 来代表这条微博,用于执行后续操作(比如查看微博、评论、点赞,等等)。
- 将被发送的微博推入到用户自己的时间线(timeline)里面,以及所有粉丝的时间线里面。本节先来实现第一个动作,之后再来介绍第二个动作的实现方法。
发送微博功能的实现
对于每条微博,程序都会 为它分配一个唯一的 ID ,并将这条微博的 ID 、作者的用户 ID、内容、UNIX 时间戳格式的发送时间等信息储存到一个格式为 weibo::message:: 的散列键里面。
举个例子,假设 ID 为 10086 的用户在时间 1409468643 发送了一条内容为 “hello world” 的微博,并且程序为这条微博分配的唯一 ID 为 65535 ,那么程序将执行以下命令来储存这条微博:
HMSET weibo::message::65535 id 65535author 10086time 1409468643content “hello world”
我们可以将针对微博的相关操作抽象为 Message 类。
Message 类的实现
msg = Message(client)
message_id = msg.create(10086, “hello world”)
message_info = msg.get_by_id(message_id)
时间线
按照发表时间的先后顺序来储存微博
定制时间线和个人时间线
时间线功能的实现
程序会为每个用户储存两条时间线:
- 定制时间线,包含了用户自己以及用户正在关注的人发送的微博,键名为 weibo::user::::custom_timeline 。
- 个人时间线,只包含用户自己发送的微博,键名为 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 3050author 10086content “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 100000author 12345time 1500000000content “又获得推荐了,感谢码农周刊![太开心]”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-8class IdGenerator:def __init__(self, client, key):self.client = clientself.key = keydef 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-8class UniqueSet:def __init__(self, client, key): self.client = clientself.key = keydef 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-8from id_generator import IdGeneratordef 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 = clientdef 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 与 IDself.client.hset(make_email_to_uid_key(), email, new_id)# 返回用户 ID 作为函数的值return new_iddef get_by_id(self, uid):key = make_user_key(uid)return self.client.hgetall(key)def try_login(self, email, password):# 根据输入的邮箱,获取用户 IDuid = 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-8def 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 = clientdef 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-8from id_generator import IdGenerator
from time import timeID_GENERATOR_KEY = 'weibo::message_ids'def make_message_key(id):return 'weibo::message::' + str(id)class Message:def __init__(self, client):self.client = clientdef 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_idself.client.hmset(key, info)return message_iddef get_by_id(self, message_id):key = make_message_key(message_id)return self.client.hgetall(key)
timeline.py
# encoding: utf-8def 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 = clientdef _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)*countend_index = number*count-1return 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-8def make_like_key(message_id):return 'weibo::message::' + str(message_id) + '::like'class LikeMessage:def __init__(self, client, message_id):self.client = clientself.message_id = message_idself.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-8from time import time
from id_generator import IdGeneratorID_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 = clientdef 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_iddef get_by_id(self, comment_id):comment_hash = make_comment_key(comment_id)return self.client.hgetall(comment_hash)
comment_list.py
# encoding: utf-8def make_comment_list_key(message_id):return 'weibo::message::' + str(message_id) + '::comments'class CommentList:def __init__(self, client, message_id):self.client = clientself.message_id = message_idself.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)*countend_index = number*count-1return self.client.lrange(self.comment_list, start_index, end_index)
redis构建微博(redis应用构建篇)相关推荐
- redis+php微博,redis+php实现微博(三)微博列表功能详解
本文实例讲述了redis+php实现微博列表功能.分享给大家供大家参考,具体如下: 个人主页显示微博列表(自己及关注人的微博列表) /*获取最新的50微博信息列表,列出自己发布的微博及我关注用户的微博 ...
- Docker学习笔记之四,构建一个Redis as a Service(RAAS)
Docker的抽象 若把一台Linxu机器比喻为一艘船,那么每一个Linux Container就好比船上的一个集装箱. 另外一个层面,Docker Image(镜像)就是一个预先定义行为的模板,Do ...
- Redis入门到实战(实战篇)缓存更新、穿透、雪崩、击穿!
Redis基础篇 Java面试宝典-redis 实战篇Redis 开篇导读 亲爱的小伙伴们大家好,马上咱们就开始实战篇的内容了,相信通过本章的学习,小伙伴们就能理解各种redis的使用啦,接下来咱们来 ...
- 如何从零构建直播系统(后端篇)
本文来自作者 陈丽能@龙珠技术 在 GitChat 上分享 「如何从零构建直播系统(后端篇)」,「阅读原文」查看交流实录. 「文末高能」 编辑 | 哈比 起源 现在直播互动已经成为大家比较熟知的交流方 ...
- 微博互粉php,PHP+redis实现微博的推模型案例分析
本文实例讲述了PHP+redis实现微博的推模型.分享给大家供大家参考,具体如下: 最近在看了一下关于redis的内容,然后利用redis写了一个简单的微博项目,这篇文章是关于推模型的. 推模型 所谓 ...
- php微博互粉,PHP+redis实现微博的拉模型案例详解
本文实例讲述了PHP+redis实现微博的拉模型.分享给大家供大家参考,具体如下: 上回写了一篇推模型的内容,这回分享一篇拉模型的内容. 拉模型 拉模型就是展示微博的时候,获取自己的所有关注的人,然后 ...
- 2022年Redis最新面试题第8篇 - Redis缓存问题
缓存问题 分布式缓存 Redis缓存雪崩 Redis缓存击穿 Redis缓存穿透 缓存预热 缓存降级 Redis缓存雪崩 出现概率: ★★★★★ 这个在Redis面试的题目中算是出镜率特别高的问题了, ...
- as 不显示gradle视图_Python构建RESTful网络服务[Django篇:基于类视图的API]
系列文章介绍 本系列文章将详细介绍将Django官方引导教程中的投票项目改写为RESTful网络服务.Django官方教程地址https://docs.djangoproject.com/zh-han ...
- 万亿级日访问量下,Redis在微博的9年优化历程
来自:DBAplus社群 讲师介绍 兰将州,新浪微博核心feed流.广告数据库业务线负责人,主要负责MySQL.NoSQL.TiDB相关的自动化开发和运维,参与Redis.counteservice_ ...
- 用Redis实现微博关注关系
2019独角兽企业重金招聘Python工程师标准>>> 用Redis实现微博关注关系的分析 关注关系产生的四种关系状态 需求分析 看自己的关注,粉丝列表: 看别人的关注,粉丝列表: ...
最新文章
- 推荐系统笔记(其它应用算法)
- mysql的查询语句怎么优化_MySQL查询语句如何优化
- Hi3516DV300 U-boot移植应用开发指南(1)
- Python与机器视觉(x)图像修复
- 次世代3d游戏建模,零基础的小白可以学吗?
- android调用日历库,Android学习教程之日历库使用(15)
- linux 下配置jdk
- 杭电2006~2009计算机学院笔试真题详解
- VMware 虚拟机下载与安装
- 设置电脑的保护色(绿豆沙色)
- 查询2021年怀铁一中高考成绩,怀铁一中2010高考录取名单
- 知识图谱顶刊综述 - (2021年4月) A Survey on Knowledge Graphs: Representation, Acquisition, and Applications
- 微信无法直接打开淘宝链接是怎么回事?
- 小包实用工具:国家代码大全
- C语言练习,指针变量作函数参数,从键盘输入一个m行n列的二维数组,然后计算数组中元素的最大值及其所在的行列下标值。其中,m和n的值由用户键盘输入。已知m和n的值都不超过10
- React:开发者工具谷歌插件下载安装
- linux使用TC并借助ifb实现入向限速(内附配置实例)
- 第十一届蓝桥杯 b组
- 超全的电商数据指标体系分享,年底数据分析用得上
- Spring-aop面向切面
热门文章
- Java Stream(求和,过滤,排序)
- 三种常见去除视频监控干扰的方法
- 打通最后一公里!智慧城市生活触手可及
- reduce()数组方法的使用场景
- CC26xx(CortexM3)的低功耗设计
- 1git命令的使用 查看git仓库状态 添加文件到git跟踪 git提交 查看git分支 查看git仓库日志信息 切换g
- 【PLC】贝加莱PLC理论及操作年度培训
- oracle返回并集不包括重复行,Oracle 考试题 答案
- 电脑键盘使用指南-基础版
- lol八月那服务器有无限火力,LOL英雄联盟无限火力什么时候开 2018无限火力开放时间...