【Gin-Web】Bluebell社区项目梳理5:投票功能分析与实现

打印 上一主题 下一主题

主题 823|帖子 823|积分 2469

一、投票功能

投票流程

首先我们要明确,就是 谁(哪个用户:userID) 给 哪个帖子(postID) 投了 什么票(赞成票or反对票)。
赞成票越多,热度越高,就会越展示在前面。
在redis中可以用zset存储帖子,那么有两种存储方式。
一是根据帖子的发布时间存储帖子(根据时间戳来,时间越新,时间戳越大,越在前面),或者是按照评分来存储帖子。

然后还可以计划一个zset,用来存储给某个帖子投票用户。哪个用户投了赞成票就记为+1,投了反对票就记为-1。

以是总的来计划了一个帖子算法,投一张赞成票就加对应的分数,比如400分。
时间戳+赞成票*400分=评分,评分越高越放前面。
实当代码

首先我们封装了投票数据的结构体。

然后是controller层。

然后就是具体投票的业务逻辑实现部分Logic.VoteForPost。
strconv.Itoa 是一个函数,用于将整数转换为字符串。它要求输入必须是 int 类型,因此这里使用了 int(userId) 将 userId 转换为 int。将用户 ID 转换为字符串格式,因为 Redis 的键通常是以字符串形式存储的。

redis投票

来看看投票的环境:
  1. v=1时,有两种情况
  2.         1.之前没投过票,现在要投赞成票                 --> 更新分数和投票记录        差值的绝对值:1        +432
  3.         2.之前投过反对票,现在要改为赞成票         --> 更新分数和投票记录        差值的绝对值:2        +432*2
  4. v=0时,有两种情况
  5.         1.之前投过反对票,现在要取消                         --> 更新分数和投票记录        差值的绝对值:1        +432
  6.         2.之前投过赞成票,现在要取消                         --> 更新分数和投票记录        差值的绝对值:1        -432
  7. v=-1时,有两种情况
  8.         1.之前没投过票,现在要投反对票                 --> 更新分数和投票记录        差值的绝对值:1        -432
  9.         2.之前投过赞成票,现在要改为反对票         --> 更新分数和投票记录        差值的绝对值:2        -432*2
复制代码
除此之外,我们尚有对投票的限定:
每个帖子自发起之日起,一个星期之内允许用户投票,超过一个星期就不允许投票了。同时到期之后将redis中保存的赞成票数及反对票数存储到mysql表中。到期之后删除 KeyPostVotedZSetPrefix。
这里的 KeyPostVotedZSetPrefix 就是记载用户及投票类型。

我们来看看redis.go的相干代码,其中client就是redis客户端。

在service层中,设置了相干的逻辑代码,来看看处置处罚流程。
首先我们需要去redis中获取帖子的发布时间,从client中拿即可。并查抄当前时间与帖子发布时间的差值是否超过一周(OneWeekInSeconds)。假如超过一周,返回错误 ErrorVoteTimeExpire,表现投票已过期。

我们在redis的目录路径下,封装了error错误,声明了几个自界说错误变量。这些错误变量用于在 Redis 相干的操作中表现特定的错误环境。errors.New 是一个常用的error函数,用于创建一个新的错误对象。
通过界说全局的错误变量,为 Redis 相干的操作提供了一致的错误处置处罚机制。使用 errors.New 创建的错误对象可以在整个包中复用,克制了重复创建雷同的错误信息,进步了代码的可维护性和一致性。
比如当投票时间已颠末期了,我们就需要返回投票过期的错误。


  1. postTime := client.ZScore(KeyPostTimeZSet, postID).Val()
复制代码
ZScore 方法查询帖子 ID 对应的分数(发布时间),并通过 Val() 方法获取该分数的实际值。返回的 postTime 是一个浮点数,表现帖子的发布时间(通常是 Unix 时间戳)。
ZScore 是 Redis 客户端提供的一个方法,用于从有序集合(ZSet)中获取某个成员的分数。它的签名通常是:
  1. func (c *Client) ZScore(key string, member string) *FloatCmd
复制代码
key:有序集合的键名(唯一标识:键名是 Redis 数据库中唯一标识有序集合的字符串。通过键名,你可以访问和操作特定的有序集合。)
member:有序集合中的成员(在这里是帖子的 ID)。
在刚刚我们有提到,const KeyPostTimeZSet = "bluebell:post:time",这是一个常量,界说了存储帖子发布时间的有序集合的键名。

然后就是更新帖子分数,注意更新帖子分数+记载用户为该帖子投票的数据 是要放在一个redis事件中完成的。
在 Redis 中,Pipeline(管道) 是一种用于将多个下令发送到服务器的技能,而 事件(Transaction) 是一种将多个下令打包并一次性执行的机制。在 Redis 的上下文中,Pipeline 和事件经常结合使用,以进步性能和确保操作的原子性。
事件 是一种将多个下令打包,并一次性、次序地执行的机制。Redis 的事件通过 MULTI、EXEC、DISCARD 和 WATCH 下令实现。事件的重要特点包括:事件中的所有下令要么全部执行,要么全部不执行。这确保了操作的原子性,克制了部分执行导致的数据不一致问题。事件中的下令会按照次序执行,不会被其他客户端的下令打断。

  1. KeyPostVotedZSetPrefix    = "bluebell:post:voted:" // zset;记录用户及投票类型;参数是post_id
复制代码
刚刚我们界说了redis的常量,以是我们需要举行下面的操作:
  1.         key := KeyPostVotedZSetPrefix + postID
  2.         ov := client.ZScore(key, userID).Val()
复制代码
也就是从 redis中获取 某个帖子 的 用户投票类型,根据用户ID来获取Val值。
也就是下面这个图所示:

  1.         ov := client.ZScore(key, userID).Val()
  2.         // 更新:如果这一次投票的值和之前保存的值一致,就提示不允许重复投票
  3.         if v == ov {
  4.                 return ErrVoteRepested
  5.         }
  6.         var op float64
  7.         if v > ov {
  8.                 op = 1
  9.         } else {
  10.                 op = -1
  11.         }
  12.         diffAbs := math.Abs(ov - v)                                                        // 计算两次投票的差值
  13.         pipeline := client.TxPipeline()                                                    // 事务操作
  14.         _, err = pipeline.ZIncrBy(KeyPostScoreZSet, VoteScore*diffAbs*op, postID).Result() // 更新分数
复制代码
通过Redis事件来更新分数。
TxPipeline() 是 Redis 客户端提供的一个方法,用于创建一个事件性 Pipeline。这个 Pipeline 允许将多个下令打包在一起,并作为一个事件发送到 Redis 服务器。
ZIncrBy 是 Redis 的一个下令,用于在有序集合(ZSet)中增加某个成员的分数。
  1.         if v == 0 {
  2.                 _, err = client.ZRem(key, userID).Result()
  3.         } else {
  4.                 pipeline.ZAdd(key, redis.Z{ // 记录已投票
  5.                         Score:  v, // 赞成票还是反对票
  6.                         Member: userID,
  7.                 })
  8.         }
  9.         _, err = pipeline.Exec()  //执行pipeline中的所有命令
复制代码
假如v=0,那么从有序集合中移除指定的成员。
client.ZRem:Redis 客户端提供的方法,用于从有序集合中移除指定的成员。
pipeline.ZAdd:Redis 客户端提供的方法,用于将一个成员及其分数添加到有序集合中。这里使用了事件性 Pipeline,确保操作的原子性。
redis.Z:一个结构体,包罗成员(Member)和分数(Score)。
Score:用户的投票值(v),表现赞成票(1)或反对票(-1)。
Member:用户的唯一标识符(userID)。

在 Redis 中,有序集合(ZSet)相干的下令都以 Z 开头,例如 ZADD、ZSCORE、ZINCRBY、ZREM 等。
Redis 的有序集合下令都以 Z 开头,例如:
ZADD:将一个或多个成员及其分数添加到有序集合中。
ZSCORE:获取有序集合中成员的分数。
ZINCRBY:增加有序集合中成员的分数。
ZREM:从有序集合中移除成员。

可以看到,再投出一票之后,在原先的redis基础上加了432分。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

魏晓东

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表