利用Redis bitmap 实现签到案例

数据库实现

设计签到功能对应的数据库表

 CREATE TABLE `sign_record` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',`user_id` bigint NOT NULL COMMENT '用户id',`year` year NOT NULL COMMENT '签到年份',`month` tinyint NOT NULL COMMENT '签到月份',`date` date NOT NULL COMMENT '签到日期',`is_backup` bit(1) NOT NULL COMMENT '是否补签',PRIMARY KEY (`id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='签到记录表';

这张表中的一条记录是一个用户一次的签到记录。假如一个用户1年签到100次,而网站有100万用户,就会产生1亿条记录。随着用户量增多、时间的推移,这张表中的数据只会越来越多,占用的空间也会越来越大。

redis bitmap 实现

一个用户签到的情况无非就两种,要么签了,要么没。 可以用 0 或者1如果我们按月来统计用户签到信息,签到记录为1,未签到则记录为0,就可以用一个长度为31位的二级制数来表示一个用户一个月的签到情况。最终效果如下
image.png

java代码
引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.orchids</groupId><artifactId>signinbybitmap</artifactId><version>0.0.1-SNAPSHOT</version><name>signinbybitmap</name><description>signinbybitmap</description><properties><java.version>1.8</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><spring-boot.version>2.6.13</spring-boot.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>3.0.3</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>${spring-boot.version}</version><configuration><mainClass>com.orchids.signinbybitmap.SignByBitmapApplication</mainClass><skip>true</skip></configuration><executions><execution><id>repackage</id><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build></project>
配置文件

# 应用服务 WEB 访问端口
server:port: 8080spring:redis:host: localhostport: 6379password: 6379mvc:pathmatch:matching-strategy: ant_path_matcher

knife4j配置类

package com.orchids.signinbybitmap.web.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
//import springfox.documentation.swagger2.annotations.EnableSwagger2;/*** @ Author qwh* @ Date 2024/7/5 13:08*/
@Configuration
//@EnableSwagger2
public class knife4jConfiguration {@Beanpublic Docket webApiConfig(){// 创建Docket实例Docket webApi = new Docket(DocumentationType.SWAGGER_2).groupName("StudentApi").apiInfo(webApiInfo()).select()// 选择需要文档化的API,只显示指定包下的页面.apis(RequestHandlerSelectors.basePackage("com.orchids.signinbybitmap"))// 指定路径匹配规则,只对/student开头的路径进行文档化.paths(PathSelectors.regex("/User/.*")).build();return webApi;}/*** 构建API信息* 本函数用于创建并返回一个ApiInfo对象,该对象包含了API文档的标题、描述、版本以及联系方式等信息。* @return 返回构建好的ApiInfo对象*/private ApiInfo webApiInfo(){// 使用ApiInfoBuilder构建API信息return new ApiInfoBuilder().title("Student message API文档") // 设置文档标题.description("本文档描述了Swagger2测试接口定义") // 设置文档描述.version("1.0") // 设置文档版本号.contact(new Contact("nullpointer", "http://blog.nullpointer.love", "nullpointer2024@gmail.com")) // 设置联系人信息.build(); // 构建并返回ApiInfo对象}
}
controller
package com.orchids.signinbybitmap.web.controller;import com.orchids.signinbybitmap.web.domain.result.Result;
import com.orchids.signinbybitmap.web.domain.vo.SignResultVO;
import com.orchids.signinbybitmap.web.service.SignService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;/*** @ Author qwh* @ Date 2024/7/5 13:01*/
@Api(tags = "签到相关接口")
@RestController
@RequestMapping("/User")
@RequiredArgsConstructor
public class SignController {private final SignService signService;@ApiOperation("签到")@GetMapping("Sign")public Result<SignResultVO> AddSignRecords() {return signService.AddSignRecords();}
}
service
package com.orchids.signinbybitmap.web.service;import com.orchids.signinbybitmap.web.domain.result.Result;
import com.orchids.signinbybitmap.web.domain.vo.SignResultVO;/*** @ Author qwh* @ Date 2024/7/5 13:35*/
public interface SignService {Result<SignResultVO> AddSignRecords();
}

可以扩展其他功能

package com.orchids.signinbybitmap.web.service.impl;import com.orchids.signinbybitmap.web.domain.result.Result;
import com.orchids.signinbybitmap.web.domain.vo.SignResultVO;
import com.orchids.signinbybitmap.web.exception.SignException;
import com.orchids.signinbybitmap.web.service.SignService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedList;
import java.util.List;/*** @ Author qwh* @ Date 2024/7/5 13:35*/
@Slf4j
@Service
@RequiredArgsConstructor
public class SignServiceImpl implements SignService {private final String SIGN_UID= "sign:uid:";private final StringRedisTemplate redisTemplate;@Overridepublic Result<SignResultVO> AddSignRecords() {SignResultVO vo = new SignResultVO();//获取签到用户Long userId = 1388888L;//获取签到日期LocalDateTime now = LocalDateTime.now();String format = now.format(DateTimeFormatter.ofPattern(":yyyy-MM-dd"));//设置redisKey   sign:uid:1388888:2024-07-05 5 1String key = SIGN_UID + userId.toString() + format;//计算签到偏移量int offset = now.getDayOfMonth() - 1;//添加签到记录到redisBoolean sign = redisTemplate.opsForValue().setBit(key, offset, true);if (sign){throw new SignException("亲!您今天已经登录过哟 (❁´◡`❁)",520);}//计算连续签到天数int day = now.getDayOfMonth();int continueDays = countSignDays(key,day);int rewardPoints = 0;switch (continueDays){case 2:rewardPoints = 10;break;case 4:rewardPoints=20;break;case 6:rewardPoints = 40;break;}//获取签到详情信息List<Integer> signDayRecord = SignRecords(userId,key,day);vo.setUserId(userId.intValue());vo.setSignDays(continueDays);vo.setRewardPoints(rewardPoints);vo.setSignRecords(signDayRecord);return Result.ok(vo);}/*** 获取连续签到天数* @param key* @param days* @return*/private int countSignDays(String key, int days) {//从redis读取签到记录List<Long> nums = redisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(days)).valueAt(0));//计算签到次数int num = nums.get(0).intValue();//num与1进行与计算得到二进制的末尾 当末尾为1 说明签到 为0 说明没有签到int result = 0;while ((num & 1) == 1) {result++;num = num >>>1;}//返回签到结果return result;}/*** 获取签到详情* @param userId* @param key* @param day* @return*/private List<Integer> SignRecords(Long userId, String key, int day) {//获取从redis中获取登录信息List<Long> sign = redisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(day)).valueAt(0));int num = sign.get(0).intValue();LinkedList<Integer> result = new LinkedList<>();while (day > 0) {result.addFirst(num & 1);num = num >>> 1;day--;}return result;}
}
其他类
package com.orchids.signinbybitmap.web.domain.result;import lombok.Data;/*** @ Author qwh* @ Date 2024/7/5 16:52*/
@Data
public class Result<T> {//返回码private Integer code;//返回消息private String message;//返回数据private T data;public Result() {}private static <T> Result<T> build(T data) {Result<T> result = new Result<>();if (data != null)result.setData(data);return result;}public static <T> Result<T> build(T body, ResultCode resultCode) {Result<T> result = build(body);result.setCode(resultCode.getCode());result.setMessage(resultCode.getMessage());return result;}public static <T> Result<T> ok(T data) {return build(data, ResultCode.SUCCESS);}public static <T> Result<T> ok() {return Result.ok(null);}public static <T> Result<T> fail(Integer code, String message) {Result<T> result = build(null);result.setCode(code);result.setMessage(message);return result;}public static <T> Result<T> fail() {return build(null, ResultCode.FAIL);}
}
package com.orchids.signinbybitmap.web.domain.result;import lombok.Getter;/*** @ Author qwh* @ Date 2024/7/5 16:54*/
@Getter
public enum ResultCode {SUCCESS(200, "成功"),FAIL(201, "失败"),PARAM_ERROR(202, "参数不正确"),SERVICE_ERROR(203, "服务异常"),DATA_ERROR(204, "数据异常"),ILLEGAL_REQUEST(205, "非法请求"),REPEAT_SUBMIT(206, "重复提交"),DELETE_ERROR(207, "请先删除子集"),ADMIN_ACCOUNT_EXIST_ERROR(301, "账号已存在"),ADMIN_CAPTCHA_CODE_ERROR(302, "验证码错误"),ADMIN_CAPTCHA_CODE_EXPIRED(303, "验证码已过期"),ADMIN_CAPTCHA_CODE_NOT_FOUND(304, "未输入验证码"),ADMIN_ACCOUNT_NOT_EXIST(330,"用户不存在"),ADMIN_LOGIN_AUTH(305, "未登陆"),ADMIN_ACCOUNT_NOT_EXIST_ERROR(306, "账号不存在"),ADMIN_ACCOUNT_ERROR(307, "用户名或密码错误"),ADMIN_ACCOUNT_DISABLED_ERROR(308, "该用户已被禁用"),ADMIN_ACCESS_FORBIDDEN(309, "无访问权限"),APP_LOGIN_AUTH(501, "未登陆"),APP_LOGIN_PHONE_EMPTY(502, "手机号码为空"),APP_LOGIN_CODE_EMPTY(503, "验证码为空"),APP_SEND_SMS_TOO_OFTEN(504, "验证法发送过于频繁"),APP_LOGIN_CODE_EXPIRED(505, "验证码已过期"),APP_LOGIN_CODE_ERROR(506, "验证码错误"),APP_ACCOUNT_DISABLED_ERROR(507, "该用户已被禁用"),TOKEN_EXPIRED(601, "token过期"),TOKEN_INVALID(602, "token非法");private final Integer code;private final String message;ResultCode(Integer code, String message) {this.code = code;this.message = message;}
}
package com.orchids.signinbybitmap.web.domain.vo;import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.models.auth.In;
import lombok.Data;import java.util.List;/*** @ Author qwh* @ Date 2024/7/5 13:36*/
@Data
@ApiModel(description = "签到结果")
public class SignResultVO {@ApiModelProperty("签到人")private Integer UserId;@ApiModelProperty("签到得分")private Integer signPoints = 1;@ApiModelProperty("连续签到天数")private Integer signDays;@ApiModelProperty("连续签到奖励积分,连续签到超过7天以上才有奖励")private Integer rewardPoints;@ApiModelProperty("签到详细信息")private List<Integer> signRecords;}
package com.orchids.signinbybitmap.web.exception;import com.orchids.signinbybitmap.web.domain.result.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;/*** @ Author qwh* @ Date 2024/7/5 16:51*/
@ControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(Exception.class)@ResponseBodypublic Result error(Exception e){e.printStackTrace();return Result.fail();}@ExceptionHandler(SignException.class)@ResponseBodypublic Result error(SignException e){e.printStackTrace();return Result.fail(e.getCode(), e.getMessage());}
}
package com.orchids.signinbybitmap.web.exception;import lombok.Data;/*** @ Author qwh* @ Date 2024/7/5 16:47*/
@Data
public class SignException extends RuntimeException{//异常状态码private Integer code;/*** 通过状态码和错误消息创建异常对象* @param message* @param code*/public SignException(String message, Integer code) {super(message);this.code = code;}@Overridepublic String toString() {return "SignException{" +"code=" + code +", message=" + this.getMessage() +'}';}
}
package com.orchids.signinbybitmap;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class SignByBitmapApplication {public static void main(String[] args) {SpringApplication.run(SignByBitmapApplication.class, args);}}
测试结果

image.png

image.png

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

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

相关文章

在Linux操作系统中关于逻辑卷的案例

1.如何去创建一个逻辑卷 1.1先去创建物理卷 如上图所示&#xff0c;physical volume 物理卷 被成功创建。 如上图所示&#xff0c;可以使用pvscan来去查看当前Linux操作系统的物理卷/ 1.2使用创建好的物理卷去创建一个卷组。 如上图所示&#xff0c;可以使用第一步创建的两个…

【中项第三版】系统集成项目管理工程师 | 第 9 章 项目管理概论③ | 9.6 - 9.10

前言 第 9 章对应的内容选择题和案例分析都会进行考查&#xff0c;这一章节理论性较强&#xff0c;学习要以教材为准。本章分值预计在4-5分。 目录 9.6 项目管理过程组 9.7 项目管理原则 9.8 项目管理知识领域 9.9 价值交付系统 9.10 本章练习 9.6 项目管理过程组 项目…

千万不能踏入的渠道管理五大误区!【附策略】

一、引言 在当今激烈的市场竞争环境中&#xff0c;有效的渠道管理是企业获得竞争优势的关键。然而&#xff0c;在实践过程中&#xff0c;不少企业因陷入管理误区而影响了市场拓展和品牌建设。本文旨在揭示渠道管理中常见的五大误区&#xff0c;并提供避免策略&#xff0c;帮助…

web基础与HTTP协议(企业网站架构部署与优化)

补充&#xff1a;http服务首页文件在/var/www/html下的&#xff0c;一定是index.html命名的文件。才会显示出来。 如果该路径下没有相应的文件&#xff0c;会显示/usr/share/httpd/noindex下的index.html文件。 如果/usr/share/httpd/noindex没有index.html文件&#xff0c;会…

BSI 第七届万物互联智慧高峰论坛:主题:拥抱AI时代,标准赋能组织实现可持续发展

BSI 第七届万物互联智慧高峰论坛&#xff1a;主题&#xff1a;拥抱AI时代&#xff0c;标准赋能组织实现可持续发展 主要收到 BSI 温女士的邀请参加的本次论坛。还是学到的很多 。 在科技日新月异的时代背景下&#xff0c;BSI 第七届万物互联智慧高峰论坛于[时间&#xff1a;6…

Object 类中的公共方法详解

Object 类中的公共方法详解 1、clone() 方法2、equals(Object obj) 方法3、hashCode() 方法4、getClass() 方法5、wait() 方法6、notify() 和 notifyAll() 方法 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在 Java 中&#xff0c;Object…

AI 驱动的数据中心变革与前景

文章主要探讨了AI计算时代数据中心的转型&#xff0c;涉及计算技术的多样性、规格尺寸和加速器的发展、大型语言模型&#xff08;LLM&#xff09;的发展、功耗和冷却趋势、基准测试的重要性以及数据中心的发展等方面。为大家提供深入了解AI基础设施发展的视角。 计算技术的多样…

Ubuntu(通用)—网络加固—ufw+防DNS污染+ARP绑定

1. ufw sudo ufw default deny incoming sudo ufw deny in from any to any # sudo ufw allow from any to any port 5353 protocol udp sudo ufw enable # 启动开机自启 # sudo ufw reload 更改后的操作2. 防ARP欺骗 华为云教程 arp -d删除dns记录arp -a显示arp表 ipconfi…

PTrade常见问题系列3

量化允许同时运行回测和交易的策略个数配置。 量化允许同时运行回测和交易的策略个数在哪里查看&#xff1f; 在量化服务器/home/fly/config/custom_config_conf文件中&#xff0c;其中运行回测的策略个数由backtest_switch&#xff08;是否限制普通回测个数&#xff09;及ba…

matlab 绘制高等数学中的二维函数示例

matlab 绘制高等数学中的二维函数示例 绘制高等数学中的二维函数示例绘制结果 绘制高等数学中的二维函数示例 clc,clear,close all; % 定义方程 eqn (x, y) (x.^2 y.^2).^3 - y.^4;% 绘制方程曲线和坐标轴 ezplot(eqn, [-2, 2, -2, 2]) hold on % 在同一图形中保持绘图% 绘…

S7-1200PLC学习记录

文章目录 前言一、S7-12001.数字量输入模块2. PNP接法和NPN接法 二、博图软件1. 位逻辑运算Part1. 添加新设备&#xff08;添加PLC&#xff09;Part2. 添加信号模块Part3. 添加信号板中模块Part4. 添加新块Part5. Main编程文件案例1案例2 -( S )- 和 -( R )-完整操作过程&#…

昇思25天学习打卡营第8天|ResNet50迁移学习

一、迁移学习定义 迁移学习&#xff08;Transfer Learning&#xff09;&#xff1a;在一个任务上训练得到的模型包含的知识可以部分或全部地转移到另一个任务上。允许模型将从一个任务中学到的知识应用到另一个相关的任务中。适用于数据稀缺的情况&#xff0c;可减少对大量标记…

掌握Linux网络:深入理解TC —— 你的流量控制利器

目录 简单概述&#xff1a; qdisc(队列)&#xff1a; 举例&#xff1a; Bash 整形队列算法&#xff1a; FIFO (First-In-First-Out) PFIFO (Priority FIFO) SFQ (Stochastic Fair Queuing) RED (Random Early Detection) HTB (Hierarchical Token Bucket) TBF…

谷粒商城笔记-04-项目微服务架构图简介

文章目录 一&#xff0c;网络二&#xff0c;网关1&#xff0c;网关选型2&#xff0c;认证鉴权3&#xff0c;动态路由4&#xff0c;限流5&#xff0c;负载均衡6&#xff0c;熔断降级 三&#xff0c;微服务四&#xff0c;存储层五&#xff0c;服务治理六&#xff0c;日志系统七&a…

在Apache HTTP服务器上配置 TLS加密

安装mod_ssl软件包 [rootlocalhost conf.d]# dnf install mod_ssl -y此时查看监听端口多了一个443端口 自己构造证书 [rootlocalhost conf.d]# cd /etc/pki/tls/certs/ [rootlocalhost certs]# openssl genrsa > jiami.key [rootlocalhost certs]# openssl req -utf8 -n…

你的机器人购物新体验——安全、高效、无忧

如果你跟我一样&#xff0c;对找到那些“恰到好处”的商品充满渴望&#xff0c;那么&#xff0c;让我来告诉你为什么BFT会成为你的下一个购物“心头好”。 BFT的优势 高效安全的支付体系&#xff1a;BFT交易系统保障了交易的安全性和透明性&#xff0c;让你的每一笔消费都安全…

Build a Large Language Model (From Scratch)附录B(gpt-4o翻译版)

来源&#xff1a;https://github.com/rasbt/LLMs-from-scratch?tabreadme-ov-file https://www.manning.com/books/build-a-large-language-model-from-scratch

【宠粉赠书】UML 2.5基础、建模与设计实践

为了回馈粉丝们的厚爱&#xff0c;今天小智给大家送上一套系统建模学习的必备书籍——《UML 2.5基础、建模与设计实践》。下面我会详细给大家介绍这本书&#xff0c;文末留有领取方式。 图书介绍 《UML 2.5基础、建模与设计实践》以实战为主旨&#xff0c;结合draw.io免费软件…

verilog行为建模(三):块语句

目录 1.块语句2.延迟赋值语句 微信公众号获取更多FPGA相关源码&#xff1a; 1.块语句 块语句用来将多个语句组织在一起&#xff0c;使得他们在语法上如同一个语句。 块语句分为两类&#xff1a; 顺序块&#xff1a;语句置于关键字begin和end之间&#xff0c;块中的语句以顺…

鸿蒙‘ohpm‘ 不是内部或外部命令,也不是可运行的程序-解决方案

&#x1f525; 博客主页&#xff1a; 小韩本韩&#xff01; ❤️ 感谢大家点赞&#x1f44d;收藏⭐评论✍️ 在鸿蒙的DevEco Studio的终端下输入 onpm -v 或者 你需要下载第三方ohpm包的时候提示‘ohpm‘ 不是内部或外部命令&#xff0c;也不是可运行的程序- 主要是因为我们…