網(wǎng)站設(shè)置價(jià)格錯(cuò)誤不愿意發(fā)貨軟文發(fā)布平臺(tái)
在很多項(xiàng)目中都會(huì)有簽到與統(tǒng)計(jì)功能,最容易想到的方案是創(chuàng)建一個(gè)簽到表來記錄每個(gè)用戶的簽到記錄,比如設(shè)計(jì)一個(gè)mysql數(shù)據(jù)庫表:
CREATE TABLE tb_sign
id bigint(20) unsigned NOT NULL AUTOINCREMENT COMMENT '主鍵',
user_id bigint(20) unsigned NOT NULL COMMENT '用戶ID',
sign_date date NOT NULL COMMENT '簽到的日期',
is_backup tinyint(1) unsigned DEFAUL TNULL COMMENT '是否補(bǔ)簽',
PRIMARY KEY (id) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW FORMAT=COMPACT;
用戶簽到一次就可以往表里添加一條記錄;但是這樣有一個(gè)壞處,就是占用的內(nèi)存太大了,會(huì)極大的消耗內(nèi)存空間;比如有1萬用戶,每個(gè)用戶每個(gè)月簽到10次,那么一個(gè)月就是10萬條記錄,一年就是120萬條;如果用戶更多并且簽到的次數(shù)越多,那么數(shù)據(jù)量就會(huì)更大哦。
簽到一次需要使用8+8+3+1 = 20個(gè)字節(jié),如果使用redis中的bitmap來實(shí)現(xiàn),每次簽到與未簽到用1與0來表示,那么只需要2個(gè)字節(jié)即可了,這樣極大的節(jié)約了內(nèi)存;那么接下來認(rèn)識(shí)與使用bitmap。
1.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)的位置
1.1 新增
1.2 查詢
1.3 統(tǒng)計(jì)值為1的數(shù)量
1.4 查詢1 和 0 第一次出現(xiàn)的坐標(biāo)
2.springboot整合redis
-
創(chuàng)建一個(gè)spring boot項(xiàng)目,這里比較簡單,不用過多介紹;
-
添加redis依賴
<!--redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--spring2.x集成redis所需common-pool2--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.11.0</version></dependency>
- 配置配置文件
#redis服務(wù)器地址
spring.redis.host=127.0.0.1
#redis服務(wù)器連接端口
spring.redis.port=6379
#redis數(shù)據(jù)庫索引(默認(rèn)是0)
spring.redis.database=0
#連接超時(shí)時(shí)間
spring.redis.timeout=1800000
#連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
spring.redis.lettuce.pool.max-active=20
#最大阻塞等待時(shí)間(負(fù)數(shù)表示沒有限制)
spring.redis.lettuce.pool.max-wait=-1
#連接池中最大空閑連接
spring.redis.lettuce.pool.max-idle=5
#連接池中最小空閑連接
spring.redis.lettuce.pool.min-idle=0
- 測(cè)試一下
@SpringBootTest
class SpringbootRedisSigninApplicationTests {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Testvoid testRedisSet() {stringRedisTemplate.opsForValue().set("name", "picacho");String name = (String)stringRedisTemplate.opsForValue().get("name");System.out.println(name);}}
在redis中也可以看到我們插入進(jìn)去的數(shù)據(jù);
3. 實(shí)現(xiàn)
我們可以用年和月作為BitMap的key,然后保存到一個(gè)BitMap中,每次簽到就把對(duì)應(yīng)的位上把數(shù)字從0變?yōu)?,如果是1,就表示這一天簽到了,反之就表示沒有簽到。
3.1 實(shí)現(xiàn)簽到的核心代碼
這里主要討論基本思路和處理流程,因此代碼并沒有非常規(guī)范,僅僅作為示例看待即可;
- UserController
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@PostMapping("/sign")public String sign(){return userService.sign();}
}
- UserService
@Service
public class UserService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;public String sign(){// 1.模擬獲取用戶idLong userId = 1L;// 2.獲取日期LocalDateTime now = LocalDateTime.now();// 3.拼接key字符串String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));String key = "sign:" + userId + keySuffix;// 4.獲取今天是本月的第幾天int dayOfMonth = now.getDayOfMonth();// 5.寫入redisstringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);return "ok";}
}
- 測(cè)試一下
- 查看redis,可以看到本月26號(hào)完成了簽到。
3.2 統(tǒng)計(jì)連續(xù)簽到次數(shù)的核心代碼
這里先構(gòu)造幾天簽到的測(cè)試數(shù)據(jù)便于測(cè)試使用;我們這里構(gòu)造了26,25,24,22號(hào)完成了簽到。
我們需要獲取本月到今天為止的所有簽到數(shù)據(jù),今天是26號(hào),那么我們就可以從當(dāng)前月的第一天開始,獲得到26號(hào)的位數(shù),那么就是26位,去拿這段時(shí)間的數(shù)據(jù),就能拿到所有的數(shù)據(jù)了,那么這26天里邊簽到了多少次呢?統(tǒng)計(jì)有多少個(gè)1即可。
注意:bitMap返回的數(shù)據(jù)是10進(jìn)制,哪假如說返回一個(gè)數(shù)字8,我們只需要讓得到的10進(jìn)制數(shù)字和1做與運(yùn)算就可以了,因?yàn)?只有遇見1才是1,其他數(shù)字都是0 ,我們把簽到結(jié)果和1進(jìn)行與操作,每與一次,就把簽到結(jié)果向右移動(dòng)一位,依次類推即可。
- UserController
@PostMapping("/count")public String countSign(){return userService.countSign();}
- UserService
public Integer countSign(){// 1.模擬獲取用戶idLong userId = 1L;// 2.獲取日期LocalDateTime now = LocalDateTime.now();// 3.拼接key字符串String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));String key = "sign:" + userId + keySuffix;// 4.獲取今天是本月的第幾天int dayOfMonth = now.getDayOfMonth();// 5.統(tǒng)計(jì)簽到次數(shù)List<Long> result = stringRedisTemplate.opsForValue().bitField(key,BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));// 5.1 沒有簽到結(jié)果if(result == null || result.isEmpty()){return 0;}Long num = result.get(0);if(num == null || num == 0){return 0;}// 5.2 統(tǒng)計(jì)簽到次數(shù)int count = 0;while(true){if((num & 1) == 0){break;}else{count++;}num >>>= 1;}return count;}
- 測(cè)試一下
可以看到24,25,26號(hào)完成了連續(xù)3天的的簽到,剛好是3天。
到這里demo就結(jié)束了,源碼地址:demo地址