Redis實(shí)現(xiàn)用戶簽到的示例代碼
一、BitMap功能演示
我們針對(duì)簽到功能完全可以通過(guò)mysql來(lái)完成,比如說(shuō)以下這張表

用戶一次簽到,就是一條記錄,假如有1000萬(wàn)用戶,平均每人每年簽到次數(shù)為10次,則這張表一年的數(shù)據(jù)量為 1億條
每簽到一次需要使用(8 + 8 + 1 + 1 + 3 + 1)共22 字節(jié)的內(nèi)存,一個(gè)月則最多需要600多字節(jié)
我們?nèi)绾文軌蚝?jiǎn)化一點(diǎn)呢?其實(shí)可以考慮小時(shí)候一個(gè)挺常見(jiàn)的方案,就是小時(shí)候,咱們準(zhǔn)備一張小小的卡片,你只要簽到就打上一個(gè)勾,我最后判斷你是否簽到,其實(shí)只需要到小卡片上看一看就知道了
我們可以采用類似這樣的方案來(lái)實(shí)現(xiàn)我們的簽到需求。
我們按月來(lái)統(tǒng)計(jì)用戶簽到信息,簽到記錄為1,未簽到則記錄為0.
把每一個(gè)bit位對(duì)應(yīng)當(dāng)月的每一天,形成了映射關(guān)系。用0和1標(biāo)示業(yè)務(wù)狀態(tài),這種思路就稱為位圖(BitMap)。這樣我們就用極小的空間,來(lái)實(shí)現(xiàn)了大量數(shù)據(jù)的表示
Redis中是利用string類型數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)BitMap,因此最大上限是512M,轉(zhuǎn)換為bit則是 2^32個(gè)bit位。

BitMap的操作命令有:
- SETBIT:向指定位置(offset)存入一個(gè)0或1
- GETBIT :獲取指定位置(offset)的bit值
- BITCOUNT :統(tǒng)計(jì)BitMap中值為1的bit位的數(shù)量
- BITFIELD :操作(查詢、修改、自增)BitMap中bit數(shù)組中的指定位置(offset)的值
- BITFIELD_RO :獲取BitMap中bit數(shù)組,并以十進(jìn)制形式返回
- BITOP :將多個(gè)BitMap的結(jié)果做位運(yùn)算(與 、或、異或)
- BITPOS :查找bit數(shù)組中指定范圍內(nèi)第一個(gè)0或1出現(xiàn)的位置
二、實(shí)現(xiàn)簽到功能
需求:實(shí)現(xiàn)簽到接口,將當(dāng)前用戶當(dāng)天簽到信息保存到Redis中
思路:我們可以把年和月作為bitMap的key,然后保存到一個(gè)bitMap中,每次簽到就到對(duì)應(yīng)的位上把數(shù)字從0變成1,只要對(duì)應(yīng)是1,就表明說(shuō)明這一天已經(jīng)簽到了,反之則沒(méi)有簽到。
我們通過(guò)接口文檔發(fā)現(xiàn),此接口并沒(méi)有傳遞任何的參數(shù),沒(méi)有參數(shù)怎么確實(shí)是哪一天簽到呢?這個(gè)很容易,可以通過(guò)后臺(tái)代碼直接獲取即可,然后到對(duì)應(yīng)的地址上去修改bitMap。

代碼UserController
@PostMapping("/sign")
public Result sign(){
return userService.sign();
}
UserServiceImpl
@Override
public Result sign() {
// 1.獲取當(dāng)前登錄用戶
Long userId = UserHolder.getUser().getId();
// 2.獲取日期
LocalDateTime now = LocalDateTime.now();
// 3.拼接key
String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
String key = USER_SIGN_KEY + userId + keySuffix;
// 4.獲取今天是本月的第幾天
int dayOfMonth = now.getDayOfMonth();
// 5.寫(xiě)入Redis SETBIT key offset 1
stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
return Result.ok();
}
三、簽到統(tǒng)計(jì)
問(wèn)題1: 什么叫做連續(xù)簽到天數(shù)?
從最后一次簽到開(kāi)始向前統(tǒng)計(jì),直到遇到第一次未簽到為止,計(jì)算總的簽到次數(shù),就是連續(xù)簽到天數(shù)。

Java邏輯代碼:獲得當(dāng)前這個(gè)月的最后一次簽到數(shù)據(jù),定義一個(gè)計(jì)數(shù)器,然后不停的向前統(tǒng)計(jì),直到獲得第一個(gè)非0的數(shù)字即可,每得到一個(gè)非0的數(shù)字計(jì)數(shù)器+1,直到遍歷完所有的數(shù)據(jù),就可以獲得當(dāng)前月的簽到總天數(shù)了
問(wèn)題2: 如何得到本月到今天為止的所有簽到數(shù)據(jù)?
BITFIELD key GET u[dayOfMonth] 0
假設(shè)今天是10號(hào),那么我們就可以從當(dāng)前月的第一天開(kāi)始,獲得到當(dāng)前這一天的位數(shù),是10號(hào),那么就是10位,去拿這段時(shí)間的數(shù)據(jù),就能拿到所有的數(shù)據(jù)了,那么這10天里邊簽到了多少次呢?統(tǒng)計(jì)有多少個(gè)1即可。
問(wèn)題3: 如何從后向前遍歷每個(gè)bit位?
注意:bitMap返回的數(shù)據(jù)是10進(jìn)制,哪假如說(shuō)返回一個(gè)數(shù)字8,那么我哪兒知道到底哪些是0,哪些是1呢?我們只需要讓得到的10進(jìn)制數(shù)字和1做與運(yùn)算就可以了,因?yàn)?只有遇見(jiàn)1 才是1,其他數(shù)字都是0 ,我們把簽到結(jié)果和1進(jìn)行與操作,每與一次,就把簽到結(jié)果向右移動(dòng)一位,依次內(nèi)推,我們就能完成逐個(gè)遍歷的效果了。
需求:實(shí)現(xiàn)下面接口,統(tǒng)計(jì)當(dāng)前用戶截止當(dāng)前時(shí)間在本月的連續(xù)簽到天數(shù)
有用戶有時(shí)間我們就可以組織出對(duì)應(yīng)的key,此時(shí)就能找到這個(gè)用戶截止這天的所有簽到記錄,再根據(jù)這套算法,就能統(tǒng)計(jì)出來(lái)他連續(xù)簽到的次數(shù)了

代碼UserController
@GetMapping("/sign/count")
public Result signCount(){
return userService.signCount();
}
UserServiceImpl
@Override
public Result signCount() {
// 1.獲取當(dāng)前登錄用戶
Long userId = UserHolder.getUser().getId();
// 2.獲取日期
LocalDateTime now = LocalDateTime.now();
// 3.拼接key
String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
String key = USER_SIGN_KEY + userId + keySuffix;
// 4.獲取今天是本月的第幾天
int dayOfMonth = now.getDayOfMonth();
// 5.獲取本月截止今天為止的所有的簽到記錄,返回的是一個(gè)十進(jìn)制的數(shù)字 BITFIELD sign:5:202203 GET u14 0
List<Long> result = stringRedisTemplate.opsForValue().bitField(
key,
BitFieldSubCommands.create()
.get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)
);
if (result == null || result.isEmpty()) {
// 沒(méi)有任何簽到結(jié)果
return Result.ok(0);
}
Long num = result.get(0);
if (num == null || num == 0) {
return Result.ok(0);
}
// 6.循環(huán)遍歷
int count = 0;
while (true) {
// 6.1.讓這個(gè)數(shù)字與1做與運(yùn)算,得到數(shù)字的最后一個(gè)bit位 // 判斷這個(gè)bit位是否為0
if ((num & 1) == 0) {
// 如果為0,說(shuō)明未簽到,結(jié)束
break;
}else {
// 如果不為0,說(shuō)明已簽到,計(jì)數(shù)器+1
count++;
}
// 把數(shù)字右移一位,拋棄最后一個(gè)bit位,繼續(xù)下一個(gè)bit位
num >>>= 1;
}
return Result.ok(count);
}到此這篇關(guān)于Redis實(shí)現(xiàn)用戶簽到的示例代碼的文章就介紹到這了,更多相關(guān)Redis 用戶簽到內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
kubernetes環(huán)境部署單節(jié)點(diǎn)redis數(shù)據(jù)庫(kù)的方法
這篇文章主要介紹了kubernetes環(huán)境部署單節(jié)點(diǎn)redis數(shù)據(jù)庫(kù)的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
redis中token失效引發(fā)的一次生產(chǎn)事故
項(xiàng)目再測(cè)試的時(shí)候發(fā)現(xiàn)不定時(shí)token失效,本文主要介紹了redis中token失效引發(fā)的一次生產(chǎn)事故,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-03-03
Redis從基礎(chǔ)到Stream消息隊(duì)列實(shí)戰(zhàn)指南
Redis是一個(gè)高性能內(nèi)存數(shù)據(jù)庫(kù),支持字符串、哈希、列表等數(shù)據(jù)結(jié)構(gòu),5.0版本新增Stream類型,提供消息隊(duì)列功能,支持消費(fèi)者組和消息確認(rèn),本文介紹Redis從基礎(chǔ)到Stream消息隊(duì)列實(shí)戰(zhàn)指南,感興趣的朋友跟隨小編一起看看吧2026-03-03
redis搭建哨兵模式實(shí)現(xiàn)一主兩從三哨兵
本文主要介紹了redis搭建哨兵模式實(shí)現(xiàn)一主兩從三哨兵,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-08-08

