本文目錄
- 一、投票功能
- 投票流程
- 實現代碼
- redis投票
一、投票功能
投票流程
首先我們要明確,就是 誰(哪個用戶:userID)
給 哪個帖子(postID)
投了 什么票
(贊成票or反對票)。
贊成票越多,熱度越高,就會越展示在前面。
在redis中可以用zset
存儲帖子,那么有兩種存儲方式。
一是根據帖子的發布時間存儲帖子(根據時間戳來,時間越新,時間戳越大,越在前面),或者是按照評分來存儲帖子。
然后還可以設計一個zset,用來存儲給某個帖子投票用戶。哪個用戶投了贊成票就記為+1,投了反對票就記為-1。
所以總的來設計了一個帖子算法,投一張贊成票就加對應的分數,比如400分。
時間戳+贊成票*400分=評分,評分越高越放前面。
實現代碼
首先我們封裝了投票數據的結構體。
然后是controller層。
然后就是具體投票的業務邏輯實現部分Logic.VoteForPost
。
strconv.Itoa
是一個函數,用于將整數轉換為字符串。它要求輸入必須是 int 類型,因此這里使用了 int(userId) 將 userId 轉換為 int。將用戶 ID 轉換為字符串格式,因為 Redis 的鍵通常是以字符串形式存儲的。
redis投票
來看看投票的情況:
v=1時,有兩種情況1.之前沒投過票,現在要投贊成票 --> 更新分數和投票記錄 差值的絕對值:1 +4322.之前投過反對票,現在要改為贊成票 --> 更新分數和投票記錄 差值的絕對值:2 +432*2v=0時,有兩種情況1.之前投過反對票,現在要取消 --> 更新分數和投票記錄 差值的絕對值:1 +4322.之前投過贊成票,現在要取消 --> 更新分數和投票記錄 差值的絕對值:1 -432v=-1時,有兩種情況1.之前沒投過票,現在要投反對票 --> 更新分數和投票記錄 差值的絕對值:1 -4322.之前投過贊成票,現在要改為反對票 --> 更新分數和投票記錄 差值的絕對值: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
創建的錯誤對象可以在整個包中復用,避免了重復創建相同的錯誤信息,提高了代碼的可維護性和一致性。
比如當投票時間已經過期了,我們就需要返回投票過期的錯誤。
postTime := client.ZScore(KeyPostTimeZSet, postID).Val()
ZScore 方法查詢帖子 ID 對應的分數(發布時間),并通過 Val() 方法獲取該分數的實際值。返回的 postTime 是一個浮點數,表示帖子的發布時間(通常是 Unix 時間戳)。
ZScore 是 Redis 客戶端提供的一個方法,用于從有序集合(ZSet)中獲取某個成員的分數。它的簽名通常是:
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 命令實現。事務的主要特點包括:事務中的所有命令要么全部執行,要么全部不執行。這確保了操作的原子性,避免了部分執行導致的數據不一致問題。事務中的命令會按照順序執行,不會被其他客戶端的命令打斷。
KeyPostVotedZSetPrefix = "bluebell:post:voted:" // zset;記錄用戶及投票類型;參數是post_id
剛剛我們定義了redis的常量,所以我們需要進行下面的操作:
key := KeyPostVotedZSetPrefix + postIDov := client.ZScore(key, userID).Val()
也就是從 redis中獲取 某個帖子 的 用戶投票類型,根據用戶ID來獲取Val值。
也就是下面這個圖所示:
ov := client.ZScore(key, userID).Val()// 更新:如果這一次投票的值和之前保存的值一致,就提示不允許重復投票if v == ov {return ErrVoteRepested}var op float64if v > ov {op = 1} else {op = -1}diffAbs := math.Abs(ov - v) // 計算兩次投票的差值pipeline := client.TxPipeline() // 事務操作_, err = pipeline.ZIncrBy(KeyPostScoreZSet, VoteScore*diffAbs*op, postID).Result() // 更新分數
通過Redis事務來更新分數。
TxPipeline() 是 Redis 客戶端提供的一個方法,用于創建一個事務性 Pipeline。這個 Pipeline 允許將多個命令打包在一起,并作為一個事務發送到 Redis 服務器。
ZIncrBy 是 Redis 的一個命令,用于在有序集合(ZSet)中增加某個成員的分數。
if v == 0 {_, err = client.ZRem(key, userID).Result()} else {pipeline.ZAdd(key, redis.Z{ // 記錄已投票Score: v, // 贊成票還是反對票Member: userID,})}_, 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分。