这是我参与「第五届青训营 」伴学笔记创作活动的第 10 天
1: user 表
1.1: 增
这个函数的主要逻辑如下:
- 将 user 对象序列化成 JSON 格式的字符串。
- 根据用户对象中的 Name 和 Id 生成一个 Redis Hash 的 Key。
- 将序列化后的用户信息作为 Redis Hash 的 Value,使用 HMSet 命令将 Key-Value 数据写入 Redis。
- 如果 HMSet 命令返回错误,则返回错误;否则返回 nil。
func RCreateUser(user *User) (err error) {
// 添加用户信息到 Redis Hash 中
userJson, err := json.Marshal(user)
//userFields := map[string]interface{}{
// "name": user.Name,
// "id": user.Id,
// "follow_count": user.FollowCount,
// "follower_count": user.FollowerCount,
// "password": user.Password,
//}
key := fmt.Sprintf("userName:%s_userId%d", user.Name, user.Id)
err = Rdb.HMSet(Ctx, key, userJson).Err()
if err != nil {
return err
}
return nil
}
1.2: 查
1.2.1:
函数 RGetUserByName 的逻辑如下:
- 根据用户名
uname构造 Redis key 的匹配模式keyPattern,形如"userName:%s_userId*",其中%s表示用户名。 - 调用 Redis 的
KEYS命令,获取所有匹配keyPattern的 Redis key,存储在keys切片中。 - 若
keys切片为空,则说明该用户名没有对应的 Redis key,直接返回空指针。 - 否则,遍历
keys切片中的每个 Redis key,依次获取对应的 Redis Hash 中的用户信息。 - 从 Redis Hash 中解析出用户的各个字段值,包括
follow_count、follower_count、Id、name和password等字段。 - 将解析出的用户信息填充到一个
User结构体中,并返回该结构体指针和空指针。如果HGETALL命令返回错误,将返回该错误信息。
func RGetUserByName(uname string) (user *User, err error) {
// 获取所有匹配的 Redis key
keyPattern := fmt.Sprintf("userName:%s_userId*", uname)
keys, err := Rdb.Keys(Ctx, keyPattern).Result()
if err != nil {
return nil, err
}
if len(keys) == 0 {
return nil, nil
}
// 获取 Redis Hash 中的用户信息,并将其转换为 User 结构体
for _, key := range keys {
cachedUserFields, err := Rdb.HGetAll(Ctx, key).Result()
if err != nil {
return nil, err
}
// 解析 Redis Hash 中的字段值
followCount, _ := strconv.ParseInt(cachedUserFields["follow_count"], 10, 64)
followerCount, _ := strconv.ParseInt(cachedUserFields["follower_count"], 10, 64)
Id, _ := strconv.ParseInt(cachedUserFields["Id"], 10, 64)
user = &User{
Id: Id,
Name: cachedUserFields["name"],
FollowCount: followCount,
FollowerCount: followerCount,
Password: cachedUserFields["password"],
}
}
return user, nil
}
1.2.2:
该函数实现根据用户ID从Redis中获取用户信息。
具体实现逻辑如下:
- 根据用户ID构建模糊查询匹配的key模式,例如:
userName:*_userId123,其中123为用户ID。 - 使用Redis的
Keys命令查询匹配的所有keys。 - 如果未查询到任何keys,则返回nil。
- 遍历匹配的keys,使用
HGetAll命令获取用户信息。 - 解析Redis Hash中的字段值,并构建对应的
User结构体。 - 返回解析后的
User结构体。
func RGetUserById(uid int64) (user *User, err error) {
// 获取所有匹配的 Redis key
keyPattern := fmt.Sprintf("userName:*_userId%d", uid)
keys, err := Rdb.Keys(Ctx, keyPattern).Result()
if err != nil {
return nil, err
}
if len(keys) == 0 {
return nil, nil
}
// 获取 Redis Hash 中的用户信息,并将其转换为 User 结构体
for _, key := range keys {
cachedUserFields, err := Rdb.HGetAll(Ctx, key).Result()
if err != nil {
return nil, err
}
// 解析 Redis Hash 中的字段值
followCount, _ := strconv.ParseInt(cachedUserFields["follow_count"], 10, 64)
followerCount, _ := strconv.ParseInt(cachedUserFields["follower_count"], 10, 64)
Id, _ := strconv.ParseInt(cachedUserFields["Id"], 10, 64)
user = &User{
Id: Id,
Name: cachedUserFields["name"],
FollowCount: followCount,
FollowerCount: followerCount,
Password: cachedUserFields["password"],
}
}
return user, nil
}
2: vido 表
2.1: 增
函数 RSaveVideoAndImage 的逻辑如下:
- 将
video对象序列化为 JSON 字符串。 - 用
video.AuthorId作为 key,将该视频保存到 Redis ZSET 中,以视频创建时间video.CreatedAt作为 score,即越新的视频 score 越高。 - 返回可能出现的错误,如果成功则返回 nil。
func RSaveVideoAndImage(video *Video) (err error) {
// 序列化视频对象为 JSON 字符串
videoJson, err := json.Marshal(video)
if err != nil {
return err
}
// 保存视频到 Redis ZSET,以 CreatedAt 作为 score
zsetKey := fmt.Sprintf("videos:%d", video.AuthorId)
zsetItem := &redis.Z{
Score: float64(video.CreatedAt),
Member: videoJson,
}
err = Rdb.ZAdd(Ctx, zsetKey, zsetItem).Err()
if err != nil {
return err
}
return nil
}
2.2: 查
该函数的主要功能是从 Redis 中获取指定用户发布的所有视频,返回一个 Video 结构体切片。
函数首先根据指定用户的 ID 构造了该用户视频所在的 Redis ZSET key,并使用 ZRevRange 函数从 Redis 中取出该 ZSET 中的所有数据。 ZRevRange 函数返回的是一个 Video 结构体 JSON 序列化后的字符串切片。 然后,函数遍历这个字符串切片,将其中的每一个 JSON 字符串反序列化为一个 Video 结构体,并存储在 Video 切片中。最后返回 Video 切片和可能存在的错误。
func RGetVideoListByUserId(uid int64) (videos []*Video, err error) {
zsetKey := fmt.Sprintf("videos:%d", uid)
// 从 Redis ZSET 中获取特定用户发布的所有视频
videosJson, err := Rdb.ZRevRange(Ctx, zsetKey, 0, -1).Result()
if err != nil {
return nil, err
}
for _, videoJson := range videosJson {
video := &Video{}
if err = json.Unmarshal([]byte(videoJson), video); err != nil {
return nil, err
}
videos = append(videos, video)
}
return videos, nil
}
2.3: feed 流
函数 RGetFeed 实现了从 Redis 中获取最新视频列表的功能。具体实现流程如下:
- 根据传入的
lastTime参数获取 Redis 中存储的视频列表的 ZSET key,这里是videos:lastTime。 - 使用
ZRevRangeByScore函数从 Redis 中获取特定时间戳(即lastTime参数)之前的视频列表,同时使用ZRangeBy结构体指定获取 30 个视频。这里使用ZRevRangeByScore函数可以实现按照 ZSET 中的 score 值(即创建时间戳)逆序排序。 - 遍历获取到的所有视频的 JSON 字符串,使用
json.Unmarshal函数将其转换为Video结构体,并添加到返回的视频列表feedList中。 - 返回获取到的视频列表
feedList。
[!note]+ 主键不对的问题 在
RGetFeed函数中,zsetKey参数是根据最后一次刷新时间lastTime拼接得到的。这个zsetKey和之前的zsetKey不同,因此可以返回不同的数据集合。虽然之前的zsetKey是按照作者 ID 拼接的,但在本次查询中没有使用到,所以可以忽略。
func RGetFeed(lastTime int64) (feedList []*Video, err error) {
// 获取 Redis 中的视频列表
zsetKey := fmt.Sprintf("videos:%d", lastTime)
videoJsons, err := Rdb.ZRevRangeByScore(Ctx, zsetKey, &redis.ZRangeBy{
Min: "0",
Max: fmt.Sprintf("%d", lastTime),
Offset: 0,
Count: 30,
}).Result()
if err != nil {
return nil, err
}
// 将 JSON 转为 Video 对象
for _, videoJson := range videoJsons {
video := &Video{}
if err := json.Unmarshal([]byte(videoJson), video); err != nil {
return nil, err
}
feedList = append(feedList, video)
}
return feedList, nil
}
3: commet 表
3.1: 增
这个函数的逻辑如下:
-
将评论内容构建为
Comment结构体,并将其序列化为 JSON 字符串。 -
根据视频 id 构建评论的 ZSET key。
-
根据当前时间生成评论的 score。
-
使用生成的 score 和序列化的 JSON 字符串构建
redis.Z对象,将其添加到 ZSET 中。 -
根据视频 id 构建视频的 Hash key,将视频的评论数加 1。
-
更新视频的评论数到 Hash 中。
-
构建并返回一个
Comment对象。
func RSaveCommon(videoId int64, userId int64, content string) (comment *Comment, err error) {
// 保存评论到 Redis
commentJson, err := json.Marshal(&Comment{UserId: userId, VideoId: videoId, Content: content,
CreateDate: time.Now().Format("01") + "-" + time.Now().Format("02"),
CreateTime: time.Now().Unix(),
DelateAt: false})
if err != nil {
return nil, err
}
createDate, err := strconv.ParseFloat(comment.CreateDate, 64)
if err != nil {
return nil, err
}
zsetItem := &redis.Z{
Score: createDate,
Member: commentJson,
}
zsetKey := fmt.Sprintf("video:%d", videoId)
err = Rdb.ZAdd(Ctx, zsetKey, zsetItem).Err()
if err != nil {
return nil, err
}
// 获取视频评论数并更新
videoKey := fmt.Sprintf("video:%d", videoId)
commentCount, err := Rdb.HIncrBy(Ctx, videoKey, "comment_count", 1).Result()
if err != nil {
return nil, err
}
// 更新视频评论数
err = Rdb.HSet(Ctx, videoKey, "comment_count", commentCount).Err()
if err != nil {
return nil, err
}
// 返回评论对象
comment = &Comment{UserId: userId, VideoId: videoId, Content: content,
CreateDate: time.Now().Format("01") + "-" + time.Now().Format("02"),
CreateTime: time.Now().Unix(),
DelateAt: false}
return comment, nil
}
3.2: 删
这个函数的逻辑如下:
- 构造 Redis 的有序集合 key,用于获取指定视频下的所有评论。
- 调用 Redis 的 ZRem 函数,根据给定的评论 ID 删除 Redis 有序集合中的评论对象。
- 通过 Redis 的 HIncrBy 函数递减指定视频的评论计数器,用于更新指定视频的评论数量。
- 调用 Redis 的 HSet 函数更新视频评论计数器。
总的来说,这个函数实现了删除指定评论并更新相应视频的评论数量的功能。
func RDelCommonById(id int64, videoId int64) error {
// 获取 Redis 中的 key
zsetKey := fmt.Sprintf("comments:%d", videoId)
// 删除指定 id 的评论
_, err := Rdb.ZRem(Ctx, zsetKey, id).Result()
if err != nil {
return err
}
// 获取视频评论数并更新
videoKey := fmt.Sprintf("video:%d", videoId)
commentCount, err := Rdb.HIncrBy(Ctx, videoKey, "comment_count", -1).Result()
if err != nil {
return err
}
// 更新视频评论数
err = Rdb.HSet(Ctx, videoKey, "comment_count", commentCount).Err()
if err != nil {
return err
}
return nil
}
3.3: 查
这个函数的逻辑是从 Redis 中根据视频 ID 获取所有评论,并按照创建时间逆序排序后,将其反序列化为 Comment 对象并存储到 Comments 切片中。具体的实现步骤如下:
- 根据视频 ID 构建 Redis 中评论的 ZSET 的 key。
- 调用 Redis 的 ZRevRange 方法获取 ZSET 中的所有评论,结果为 JSON 字符串切片。
- 对于每个 JSON 字符串,调用 json.Unmarshal 方法将其反序列化为 Comment 对象,并添加到 Comments 切片中。
- 返回 Comments 切片。
func RGetCommonsByVideoID(videoId int64) (comments []*Comment, err error) {
// 获取评论的 ZSET key
zsetKey := fmt.Sprintf("comments:%d", videoId)
// 从 Redis ZSET 中获取所有评论,并按照 Score (即创建时间) 逆序排序
commentJsons, err := Rdb.ZRevRange(Ctx, zsetKey, 0, -1).Result()
if err != nil {
return nil, err
}
// 反序列化 JSON 字符串并存储到 Comments 切片中
for _, commentJson := range commentJsons {
comment := &Comment{}
if err = json.Unmarshal([]byte(commentJson), comment); err != nil {
return nil, err
}
comments = append(comments, comment)
}
return comments, nil
}
4: favorite
这是一个用于更新 Redis 中某个视频的收藏(favorite)和收藏数(favorite_count)的函数。具体逻辑如下:
-
根据 videoId 获取视频的 Redis Key,这里使用的 Key 为
video:{videoId}。 -
使用 HSet 方法,将用户 userId 的收藏状态 actionType(1:收藏,2:取消收藏)更新到 Redis 的视频 Key 中。
-
使用 HIncrBy 方法,将 addNum(正数:增加收藏数,负数:减少收藏数)更新到 Redis 的视频 Key 的 favorite_count 字段中。
-
如果以上步骤都成功执行,函数返回 nil,表示更新 Redis 中的收藏和收藏数操作执行成功。如果任意一个步骤失败,函数将返回对应的 error。
func RAddFavorite(videoId int64, userId int64, actionType int32, addNum int64) error {
// 获取视频 Key
videoKey := fmt.Sprintf("video:%d", videoId)
// 更新 Redis 中的 Favorite 数据
err := Rdb.HSet(Ctx, videoKey, fmt.Sprintf("favorite:%d", userId), actionType).Err()
if err != nil {
return err
}
// 更新 Redis 中的 Favorite Count 数据
err = Rdb.HIncrBy(Ctx, videoKey, "favorite_count", addNum).Err()
if err != nil {
return err
}
return nil
}