基于redis的bitmap实现签到功能(后端)

项目环境

MacOS 

springboot: 2.7.12 

JDK 11 

maven 3.8.6 

redis 7.0.11 

StringRedisTemplate 的key和value默认都是String类型 可以避免不用写配置类,定义key和value的序列化。 

实现逻辑:

获取用户登录信息

根据日期获取当天是多少号

构建用户id 按月存储key 

判断用户是否已经签到

用户签到

返回用户连续签到次数

签到功能@Service业务层

package cn.devops.service;import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;@Service
public class DailySignService {@ResourceRedisTemplate redisTemplate;@Resourceprivate StringRedisTemplate stringRedisTemplate;/*** 用户签到,可以补签* @param userId 用户ID* @param dateStr 查询的日期,默认当天 yyyy-MM-dd* @return 连续签到次数和总签到次数* */public Map<String, Object> doSign(String userId, String dateStr){//获取当前用户登录信息Map<String, Object> result = new HashMap<>();//获取日期Date date = getDate(dateStr);//获取日期对应的天数,多少号  偏移量int day = dayOfMonth(date) -1;//构建redis keyString signKey = buildSignKey(userId,date);//查看指定日期是否已签到if (isSigned(userId, day)){result.put("message", "当前日期已完成签到,无需再签");result.put("code",400);return result;}// 签到redisTemplate.opsForValue().setBit(signKey, day, true);// 根据当前日期统计签到次数Date today = new Date();//统计连续签到次数int continuous =  getSignCount(userId, today);//统计总签到次数long count = getSumSignCount(userId, today);result.put("message","签到成功");result.put("code",200);result.put("continuous",continuous);result.put("count",count);return result;}/**** 格式化日期* @param StrDate* @return* */private Date parseDate(String StrDate){DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");Date myDate = null;try{myDate = dateFormat.parse(StrDate);}catch (ParseException e){e.printStackTrace();}return  myDate;}private String format(Date date, String format){DateFormat dateFormat = new SimpleDateFormat(format);String myDate = dateFormat.format(date);return myDate;}/*** 获取用户当前的时间* @param dateStr yyyy-MM-dd* @return* */private Date getDate(String dateStr) {return Objects.isNull(dateStr) ? new Date() : parseDate(dateStr);}/*** 根据日期获取日期所在月份的天数* @param date* @return* */private int dayOfMonth(Date date){Calendar calendar = Calendar.getInstance();calendar.setTime(date);return calendar.get(Calendar.DATE);}/*** 构建Redis key userId:yyyyMM* @param userId 用户ID* @param date 日期* @return* */private String buildSignKey(String userId, Date date){return String.format("img2d_user_daily_sign:%s:%s",userId,format(date,"yyyyMM"));}/*** 统计连续签到次数* 如今天16号 无符号 查询16个bit* @param userId 用户ID* @param date 查询日期* @return* */private int getSignCount(String userId, Date date){int dayOfMonth = dayOfMonth(date);// 构建 Redis keyString signKey = buildSignKey(userId,date);// 获取日期对应的天数 多少号BitFieldSubCommands bitFieldSubCommands = BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0);//获取用户从当前日期开始到1号的所有签到状态List<Long> signList = stringRedisTemplate.opsForValue().bitField(signKey,bitFieldSubCommands);if (signList == null || signList.isEmpty()){return 0;}//连续签到计数器int signCount = 0;long v = signList.get(0) == null ? 0 : signList.get(0);//位移计算连续签到次数for (int i = dayOfMonth; i > 0; i--){  //i表示位移操作次数//右移再左移,如果等于自己说明最低位是0 表示未签到if (v >> 1 << 1 == v){// 如果为0 表示未签到 判断是否为当前if (i != dayOfMonth) break;} else {// 右移再左移, 如果不等于自己,说明最低位是1 表示签到signCount++;}//右移一位并重新赋值,相当于把最低位丢弃一位然后重新计算v >>= 1;}return signCount;}/*** 统计总签到次数* @param userId 用户ID* @param date 查询的日期* */private Long getSumSignCount(String userId, Date date){//构建Redis KeyString signKey = buildSignKey(userId, date);//e.g BITCOUNT user:sign:5:202306return (Long) redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(signKey.getBytes()));}/*** 统计月份签到次数* @param userId 用户ID* @param dateStr 用户日期* */public String monthSigned(String userId, String dateStr){//获取日期Date date = getDate(dateStr);String signKey = buildSignKey(userId, date);//获取日期对应的天数, 多少号,int dayOfMonth = dayOfMonth(date);BitFieldSubCommands bitFieldSubCommands = BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0);// 获取月份的所有签到状态List<Long> list = redisTemplate.opsForValue().bitField(signKey, bitFieldSubCommands);String total = Long.toBinaryString(list.get(0));return total;}/*** 判断用户是否已经签到* @param userId   用户ID String* @param offset     用户日期* */private Boolean isSigned(String userId, int offset ){//偏移量 offset 从 0 开始return redisTemplate.opsForValue().getBit(userId, offset);}
}

签到功能 Redis 工具类

package cn.devops.utils;import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;@Component
@Slf4j
public class RedisUtils {@Resourceprivate RedisTemplate<String, String> redisTemplate;/*** 读取缓存 redis中key对应的值* @param key* @return* */public String get(final String key){return redisTemplate.opsForValue().get(key);}/*** 写入String类型 到redis* */public boolean set(final String key, String value){boolean result = false;try{redisTemplate.opsForValue().set(key, value);log.info("存入redis成功,key:{},value:{}",key, value);result = true;}catch (Exception e){log.info("存入redis失败,key:{},value:{}",key, value);e.printStackTrace();}return result;}/*** 写入对象到redis      Json格式* */public boolean setJsonString(final String key, Object value){if (StringUtils.isBlank(key)){log.info("redis key值为空");return false;}try{redisTemplate.opsForValue().set(key, JSON.toJSONString(value));log.info("存入redis成功,key:{},value:{}",key, value);return true;}catch (Exception e){log.info("存入redis失败,key:{},value:{}",key, value);e.printStackTrace();}return false;}/*** 更新缓存* */public boolean getAndSet(final String key, String value){boolean result = false;try{redisTemplate.opsForValue().getAndSet(key,value);result = true;}catch (Exception e){e.printStackTrace();}return result;}/*** 删除缓存* */public boolean delete(final String key){boolean result = false;try{redisTemplate.delete(key);result = true;}catch (Exception e){e.printStackTrace();}return  result;}/*** 一个指定的 key 设置过期时间* @param key* @param time* */public boolean expire(String key, long time){return redisTemplate.expire(key, time, TimeUnit.SECONDS);}/***  根据key 获取过期时间* @param key* */public long getTime(String key){return redisTemplate.getExpire(key, TimeUnit.SECONDS);}/*** 根据key 获取过期时间* @param key* */public boolean hasKey(String key){return redisTemplate.hasKey(key);}/*** 移除指定key 的过期时间* @param key* */public boolean persist(String key){return redisTemplate.boundValueOps(key).persist();}

签到功能@Test测试类

package cn.devops;import cn.devops.model.RedisInfo;
import cn.devops.service.DailySignService;
import cn.devops.utils.RedisUtils;
import lombok.Data;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;import javax.annotation.Resource;@RunWith(SpringRunner.class)
@SpringBootTest(classes = {Application.class})
@Data
public class RedisTest {@Resourceprivate RedisUtils redisUtils;@Resourceprivate DailySignService dailySignService;@Test/*** 测试写入数据到redis中 格式为json* */public  void contextLoads(){RedisInfo redisInfo = new RedisInfo();redisInfo.setId(1);redisInfo.setName("morey");redisInfo.setCreateTime(redisInfo.getCreateTime());//写入redisredisUtils.setJsonString("redisInfo",redisInfo);//从redis中获取System.out.println("获取redis数据"+redisUtils.get("redisInfo"));}@Test/***   测试签到功能* */public void testSign(){System.out.println(dailySignService.doSign("testUser003", "2023-6-22").toString());}}

效果图: 数据已写入redis中 以二进制的形式。

 

报错:没有 bitField方法 在idea中报出

stringRedisTemplate.opsForValue().bitField

错误原因: springboot2.0.5版本太低导致, JDK1.8版本太低导致 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/4901.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Cetos7.x连接不上网络解决办法

Cetos7.x连接不上网络解决办法 Cetos7.x连接不上网络解决办法 在VM中设置网络连接为桥接&#xff0c;修改后仍无法连接网络 ##配置centos7中ens33&#xff0c;将默认的no修改为yes 启动CentOS系统&#xff0c;并打开一个连接终端会话&#xff0c;使用root登录&#xff1b;进…

Vue 时间格式转换

文章目录 将秒转换成简单时间格式方式一 表格渲染方式二 js转换 将时间转换为字符串方式一 年、月、日、时、分、秒、星期等信息方式二 返回多久之前的时间 将秒转换成简单时间格式 方式一 表格渲染 element-ui 表格为例&#xff0c;duration 单位为秒 <el-table-column …

Css设置border从中间向两边的颜色渐进效果

Css设置border从中间向两边的颜色渐进效果 .list-item {border-bottom: 1rpx solid;border-image: linear-gradient(to right, rgba(0,0,0,.1) 0%, rgba(81, 110, 197, 0.76) 50%, rgba(0,0,0,.1) 100%) 1;}效果如图&#xff1a;

5.MySQL索引事务

文章目录 &#x1f43e;1. 索引&#x1f43e;&#x1f490;1.1 概念&#x1f490;&#x1f338;1.2 作用与缺点&#x1f338;&#x1f337;1.2.1作用&#x1f337;&#x1f340;1.2.2缺点&#x1f340; &#x1f339;1.3 使用场景&#x1f339;&#x1f33b;1.4 使用&#x1f3…

新项目即将启动!小灰做个市场调研

熟悉小灰的小伙伴们都知道&#xff0c;在2019年初&#xff0c;做了整整10年程序员的小灰离开职场&#xff0c;成为了一名自由职业者。 2021年末&#xff0c;小灰注册了自己的公司&#xff0c;名为北京小灰大黄科技有限公司。 公司虽然注册了&#xff0c;但是整个公司只有小灰一…

Java注解

一、什么是注解 注解是放在Java源码的类、字段、方法、参数前的一种特殊的“注释”&#xff0c;和普通注释的区别是&#xff0c;普通注释被编译器直接忽略&#xff0c;注解则可以被编译器打包进入Class文件。如下图所示就是lombok中的一些注解。 注解的作用&#xff1a; 从JVM角…

ResizeKit.NET 自动更改所有控件和字体大小 -Crack Version

ResizeKit2.NET ---Added support for Microsoft .NET 7.0. 使您的应用程序大小和分辨率独立。 ResizeKit.NET 自动更改所有控件和字体的大小&#xff0c;以便它们可以显示在任何大小的表单上。提供完全控制来自定义调整大小过程。即使用户在运行应用程序时切换表单的大小&…

计算物理专题:傅里叶变换与快速傅里叶变换

计算物理专题&#xff1a;傅里叶变换与快速傅里叶变换 傅里叶变换提供一个全新的角度去观察和描述问题&#xff0c;如在量子力学中&#xff0c;动量与坐标表象之间的变换就是傅里叶变换。傅里叶变换同意可以用在数据处理等领域。1965年&#xff0c;Cooley 和 Tukey 提出了快速傅…

WPF中的Behavior及Behavior在MVVM模式下的应用

WPF中的Behavior及Behavior在MVVM模式下的应用 在WPF中&#xff0c;Behaviors&#xff08;行为&#xff09;是一种可重用的组件&#xff0c;可以附加到任何UI元素上&#xff0c;以添加特定的交互行为或功能。Behaviors可以通过附加属性或附加行为的方式来实现。 Behavior并不…

大厂月入3w+,失业焦虑折磨着我

大家好&#xff0c;这里是程序员晚枫&#xff0c;小红书也叫这个名字。 周末和一位老朋友聚会&#xff0c;聊了聊一个很现实的收入问题&#xff0c;巧合的是&#xff1a;他的焦虑&#xff0c;竟然和月薪5k的我一模一样。 今天给大家分享一下。 1、外人看来&#xff0c;让人羡…

在 Linux 中配置 IPv4 和 IPv6 地址详解

概要 IPv4和IPv6是Internet上常用的两种IP地址协议。在Linux系统中&#xff0c;您可以通过配置网络接口来设置IPv4和IPv6地址。本文将详细介绍如何在Linux中配置IPv4和IPv6地址。 步骤 1&#xff1a;确定网络接口 在开始配置IP地址之前&#xff0c;您需要确定要配置的网络接口…

吴恩达ChatGPT《LangChain for LLM Application Development》笔记

基于 LangChain 的 LLM 应用开发 1. 介绍 现在&#xff0c;使用 Prompt 可以快速开发一个应用程序&#xff0c;但是一个应用程序可能需要多次写Prompt&#xff0c;并对 LLM 的输出结果进行解析。因此&#xff0c;需要编写很多胶水代码。 Harrison Chase 创建的 LangChain 框…