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

news2024/10/6 10:35:20

项目环境

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 {
    @Resource
    RedisTemplate redisTemplate;

    @Resource
    private 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 key
        String 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 key
        String 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 Key
        String signKey = buildSignKey(userId, date);
        //e.g BITCOUNT user:sign:5:202306
        return (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 {
    @Resource
    private 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 {
    @Resource
    private RedisUtils redisUtils;

    @Resource
    private DailySignService dailySignService;

    @Test
    /**
     * 测试写入数据到redis中 格式为json
     * */
    public  void contextLoads(){
        RedisInfo redisInfo = new RedisInfo();
        redisInfo.setId(1);
        redisInfo.setName("morey");
        redisInfo.setCreateTime(redisInfo.getCreateTime());
        //写入redis
        redisUtils.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.coloradmin.cn/o/698667.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【服务器数据恢复】热备盘同步失败导致服务器崩溃的数据恢复案例

服务器数据恢复环境&#xff1a; 两组分别由4块SAS硬盘组建的raid5磁盘阵列&#xff0c;ext3文件系统&#xff0c;通过LVM管理磁盘存储。 服务器故障&#xff1a; 一组raid5磁盘阵列中的1块硬盘故障离线&#xff0c;热备盘成功启用并开始同步数据&#xff0c;在同步还没有完成…

互联网保险驶入发展快车道,元保保险加速数字化突围

随着90后、00后这一群体逐渐进入婚育高峰期,加之相较60后、70后群体,这群互联网原住民已经成为保险消费的主力人群,互联网保险行业迎来了新的发展机遇,互联网平台恰似曾经辉煌的大卖场、集市、商场,成为汇聚流量的新平台,只不过,能量的漩涡更大,更聚焦。对此,元保集团创始人兼C…

记录一个Masonry 添加约束导致的label 文案展示不全的问题

问题如图&#xff0c;蓝色label 中的文字没有展示完全&#xff0c;后面还有“情。” 原因&#xff0c;我们使用masonry 布局的时候&#xff0c;宽度会有细微的误差&#xff0c;导致我们实际 添加的约束比我们期望的要小&#xff0c;就导致横向无法容下期望的文字宽度&#xf…

如何使用MySQL统计当月每日收益金额

如果要实现上图这个效果&#xff0c;这个SQL应该怎么写呢&#xff1f; 目录 1、实现思路 2、SQL实现 1、实现思路 首先&#xff0c;我们先实现查询当月每日的日期 。然后将查询结果与需要统计的表进行左连接即可。 SELECT DATE(DATE_SUB(CURRENT_DATE(), INTERVAL (DAY(CURRE…

微服务springcloud 10.config配置中心框架和rabbitmq的安装

config配置中心的作用&#xff1a;项目的yml 配置文件保存到 git 服务器&#xff0c;例如 github.com 或 gitee.com 微服务启动时&#xff0c;从服务器获取配置文件 1.新建 “Project”,命名为 config。注意这里的不是maven项目&#xff0c;而是project 2.将sp02,sp03,sp04,s…

git常用命令之Fetch

4. Fetch 命令-----------------------------------------------------------------------------作用延展阅读git fetch1. 拉取「远程仓库」的所有远程分支的最新Commit-ID 记录在 .git/FETCH_HEAD 文件中.若有多个分支则 FETCH_HEAD 内会有多行数据&#xff0c;该文件首行对应…

dbca添加实例时无法发现对方主机

有个12.2集群环境&#xff0c;需要添加一个实例&#xff0c;直接图形化启动dbca&#xff0c;第四步时只能发现本机&#xff0c;无法识别到另外一个节点&#xff0c;如下图 通过排查发现是oracle用户的密码过期了&#xff0c;细节如下 [roothydb1 ~]# su - grid Last login: W…

C++11关键字

decltype 根据表达式的类型自动推导类型 int main(void) {decltype(x) c 21.1; //赋值decltype((x)) d c; // 是一个引用decltype(x) e c; //d 120;cout << sizeof(int) << endl;cout << sizeof(c) << " c" << c << endl;…

【Rust】所有权

文章目录 所有权stack与heap所有权存在的原因所有权规则变量作用域String类型内存和分配变量与数据交互的方式1.Move2.Clone3.Copy 所有权与函数返回值与作用域引用借用可变引用悬空引用Dangling References引用的规则切片字符串切片将字符串切片作为参数传递其他类型的切片 所…

1.2 matlab信号分析编程基础

1.画正弦波 xlinspace(0,2*pi,100); ysin(x); plot(x,y);2.画方波 xlinspace(0,4*pi,100); ysquare(x); plot(x,y);3.矩阵赋值 4.标准函数 5.画图 title&#xff1a;标题 grid&#xff1a;开关网格线 叠加多条曲线 3.GUI画正弦波 Fs 44100; dt 1.0/Fs; T 1; N T/dt…

Acer宏碁笔记本电脑Aspire蜂鸟FUN S50-51原装Windows10系统镜像,恢复出厂系统

Acer宏碁笔记本电脑Aspire蜂鸟S50-51原厂Win10系统工厂模式恢复原装出厂OEM系统 系统自带所有驱动、办公软件、出厂主题壁纸LOGO、 Acer Care Center、Quick Access等预装程序 所需工具&#xff1a;32G或以上的U盘&#xff08;非必需&#xff09; 文件格式&#xff1a;多个I…

Java垃圾搜集算法和垃圾回收算法

垃圾回收主要针对的是JVM的堆内存&#xff0c;分为新生代和老年代。 按照以前的叫法&#xff0c;还有一个永久代&#xff0c;它在方法区里保存了class信息、静态变量、常量池等。 从jdk-1.8开始&#xff0c;方法区的实现发生了一些变化&#xff0c;取消了永久代的概念&#xff…

墨迹api实现天气预测

文章目录 需求背景解决效果接口地址index.vueweather.vue图标文件 视频效果 需求背景 使用墨迹天气api实现天气预报&#xff0c;空气质量预报功能 解决效果 接口地址 墨迹天气 index.vue <template><div class"dqhjjc-wrap"><div class"fir…

pytorch快速入门中文——03

神经网络 原文&#xff1a;https://pytorch.org/tutorials/beginner/blitz/neural_networks_tutorial.html#sphx-glr-beginner-blitz-neural-networks-tutorial-py 可以使用torch.nn包构建神经网络。 现在您已经了解了autograd&#xff0c;nn依赖于autograd来定义模型并对其进…

win10系统打开程序和功能的几种方式介绍

一&#xff0c;简介 在工作中常常会用到安装和卸载软件的功能&#xff0c;需要打开“程序和功能”&#xff0c;本文主要介绍如几种打开“程序和功能”的方法&#xff0c;供参考。 二&#xff0c;四种方法介绍 四种方法分别是&#xff1a; 从控制面板打开&#xff1b;通过运…

VsCode尝试在目标目录创建文件时发生一个错误

桌面右击vscode图标以管理员身份运行就可以了 结束语&#xff1a; 希望这篇文章能帮助到大家&#xff0c;如有不对之处&#xff0c;还请指正。愿我们一起成长。

按unicode值比较数组中的字符串元素numpy.compare_chararrays()方法

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 对比两个数组中对应位置 的元素的unicode值大小 numpy.compare_chararrays() [太阳]选择题 关于以下代码的输出结果是? import numpy as np a np.array(["a","B","…

联邦聚合(FedAvg、FedProx、SCAFFOLD)

目录 联邦聚合算法对比(FedAvg、FedProx、SCAFFOLD) 解决问题 FedAvg FedProx SCAFFOLD 实验结果 联邦聚合算法对比(FedAvg、FedProx、SCAFFOLD) 论文链接&#xff1a; FedAvg&#xff1a;Communication-Efficient Learning of Deep Networks from Decentralized Data …

在线预览文件

当我们前端小伙伴在码代码的时候&#xff0c;总会不约而同地遇到一个问题&#xff1a;上传文件。一旦文件成功上传&#xff0c;后端就会慷慨地给我们一个下载地址&#xff0c;这是怎么实现在线预览或者直接下载呢&#xff1f;fliencn 是后端给的地址这种是另外起一个标签页预览…

【Android Framework系列】第3章 Zygote进程相关

1 Zygote简介 Zygote是Android中最重要的一个进程&#xff0c;Zygote进程和Init进程、SystemServer进程是Android最重要的三大进程。Zygote是Android系统创建新进程的核心进程&#xff0c;负责启动Dalvik虚拟机&#xff0c;加载一些必要的系统资源和系统类&#xff0c;启动sys…