签到业务流程

1.技术选型

Redis主写入查询,Mysql辅助查询,传统签到多数都是直接采用mysql为存储DB,在大数据的情况下数据库的压力较大.查询速率也会随着数据量增大而增加.所以在需求定稿以后查阅了很多签到实现方式,发现用redis做签到会有很大的优势.本功能主要用到redis位图

2.功能实现

1 签到流程(签到,补签,连续,签到记录)
2 签到任务(每日任务,固定任务)
在这里插入图片描述

3.表设计

## 用户积分总表
CREATE TABLE `t_user_integral` (`id` varchar(50) NOT NULL COMMENT 'id',`user_id` int(11) NOT NULL COMMENT '用户id',`integral` int(16) DEFAULT '0' COMMENT '当前积分',`integral_total` int(16) DEFAULT '0' COMMENT '累计积分',`create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用户积分总表'## 积分流水表
CREATE TABLE `t_user_integral_log` (`id` varchar(50) NOT NULL COMMENT 'id',`user_id` int(11) NOT NULL COMMENT '用户id',`integral_type` int(3) DEFAULT NULL COMMENT '积分类型 1.签到 2.连续签到 3.福利任务 4.每日任务 5.补签',`integral` int(16) DEFAULT '0' COMMENT '积分',`bak` varchar(100) DEFAULT NULL COMMENT '积分补充文案',`operation_time` date DEFAULT NULL COMMENT '操作时间(签到和补签的具体日期)',`create_time` datetime DEFAULT NULL COMMENT '创建时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用户积分流水表'

4.Redis位图Key的设计

//人员签到位图key,一个位图存一个用户一年的签到状态,以userSign为标识,后面的两个参数是今年的年份和用户的idpublic final static String USER_SIGN_IN = "userSign:%d:%d";//人员补签key,一个Hash列表存用户一个月的补签状态,以userSign:retroactive为标识,后面的两个参数是当月的月份和用户的idpublic final static String USER_RETROACTIVE_SIGN_IN = "userSign:retroactive:%d:%d";//人员签到总天数key,以userSign:count为标识,后面的参数是用户的idpublic final static String USER_SIGN_IN_COUNT = "userSign:count:%d";

5.实现签到

5.1controller

@ApiOperation("用户签到")@PostMapping("/signIn")@LoginValidatepublic ResponseResult saveSignIn(@RequestHeader Integer userId) {return userIntegralLogService.saveSignIn(userId);}

5.2service

1创建一个 ResponseResult 对象,用于统一返回结果。

2使用 String.format 方法拼接出该用户的签到位图在 Redis 中的 Key,格式为 USER_SIGN_IN:年份:用户ID。

3获取当前日期的月份和日期,转换为格式为 MMdd 的长整型数字,作为位图的偏移点。

4设置默认的响应结果信息为 “今日已签到”,并将 code 设置为 -1。

5使用 cacheClient.getBit 方法检查用户今日是否已经签到过。如果返回值为 false,表示用户今日尚未签到。

6执行 cacheClient.setbit 方法设置位图中对应位置为 1,表示用户今日已签到。该方法会返回设置前的位图值。

7如果之前没有签到过(即 oldResult 为 false),则计算该用户本月至今天的连续签到天数,调用 getContinuousSignCount 方法。

8执行 doSaveUserIntegral 方法,记录用户的签到积分类型以及连续签到积分。具体实现不在代码段中。

将响应结果的 code 设置为 0,表示签到成功。

9返回最终的响应结果。

public ResponseResult saveSignIn(Integer userId) {//这里是我们的公司统一返回类ResponseResult responseResult = ResponseResult.newSingleData();//用String.format拼装好单个用户的位图keyString signKey = String.format(RedisKeyConstant.USER_SIGN_IN, LocalDate.now().getYear(), userId);//位图的偏移点为当天的日期,如今天,偏移值就是1010long monthAndDay = Long.parseLong(LocalDate.now().format(DateTimeFormatter.ofPattern("MMdd")));responseResult.setMessage("今日已签到");responseResult.setCode((byte) -1);//检测是否用户今日签到过,用getBit可以取出该用户具体日期的签到状态(位图的值只有两个,1或者0,这里1代表true)if (!cacheClient.getBit(signKey, monthAndDay)) {//位图的set方法会返回该位图未改变前的数值,这里如果之前没有签到过默认是0,也就是falseboolean oldResult = cacheClient.setbit(signKey, monthAndDay);if (!oldResult) {//计算出这个月该用户的到今天的连续签到天数,此方法参照下方计算连续签到天数的代码块int signContinuousCount = getContinuousSignCount(userId);//此方法参照下方记录签到积分类型以及连续签到积分代码块doSaveUserIntegral(userId, signContinuousCount);responseResult.setCode((byte) 0);}}return responseResult;}

5.3连续签到
流程: 1.本质就是取出位图一个偏移值区间内的值,区间起始值为当月的第一天,范围为当月的总天数(BITFIELD命令)——>2.如果没有签到呢默认0,若是连续签到则将得到的long值右移一位再左移一位,若是不相等signCount+1;
1首先,根据传入的用户 ID 和当前时间,生成用户在 Redis 中保存签到信息的 Key,然后从 Redis 中获取该用户在本月内的签到信息的位图。

2接着,遍历该位图,从第一天到当前日期,判断该用户在每一天是否进行了签到。位图中每一个值都表示一天是否签到,如果为 1 表示签到,为 0 则表示没有签到。

3在遍历位图的过程中,如果某一天没有签到,则返回当前的连续签到天数。如果该用户从本月的第一天开始一直到当前日期都进行了签到,则返回当前日期所在的连续签到天数。

如果该用户在本月尚未进行过签到,则返回连续签到天数为 0。
如何判断连续签到?

1通过遍历位图的方式,从第一天到当前日期(date.getDayOfMonth()),依次检查每一天的签到情况。

2对于每一天的检查,首先从位图中获取对应的值,即 list.get(i)。如果该值为 null,则默认设置为 0,表示该天没有进行签到。

3判断获取到的值 v 是否满足连续签到的条件:右移一位再左移一位后与原始值不相等。如果相等,说明当前位为 0,表示当天没有签到,返回当前的连续签到天数。

4如果满足连续签到的条件,将连续签到天数 signCount 加一。

5将值 v 右移一位,用于下一天的判断。

遍历完所有的天数后,返回连续签到天数 signCount。

 private int getContinuousSignCount(Integer userId) {int signCount = 0;LocalDate date = LocalDate.now();String signKey = String.format(RedisKeyConstant.USER_SIGN_IN, date.getYear(), userId);//这里取出的是位图一个偏移值区间的值,区间起始值为当月的第一天,范围值为当月的总天数(参考命令bitfield)List<Long> list = cacheClient.getBit(signKey, date.getMonthValue() * 100 + 1, date.getDayOfMonth());if (list != null && list.size() > 0) {//可能该用户这个月就没有签到过,需要判断一下,如果是空就给一个默认值0long v = list.get(0) == null ? 0 : list.get(0);for (int i = 0; i < date.getDayOfMonth(); i++) {//如果是连续签到得到的long值右移一位再左移一位后与原始值不相等,连续天数加一(相等说明当前位为0没有签到过了)if (v >> 1 << 1 == v) return signCount;signCount += 1;v >>= 1;}}return signCount;}

5.4记录积分类型和连续签到的积分

public Boolean doSaveUserIntegral(int userId, int signContinuousCount) {int count = 0;//叠加签到次数cacheClient.incrValue(String.format(RedisKeyConstant.USER_SIGN_IN_COUNT, userId));List<UserIntegralLog> userIntegralLogList = new LinkedList<>();userIntegralLogList.add(UserIntegralLog.builder().createTime(LocalDateTime.now()).operationTime(LocalDate.now()).bak(BusinessConstant.Integral.NORMAL_SIGN_COPY).integral(BusinessConstant.Integral.SIGN_TYPE_NORMAL_INTEGRAL).integralType(BusinessConstant.Integral.SIGN_TYPE_NORMAL).userId(userId).build());count += BusinessConstant.Integral.SIGN_TYPE_NORMAL_INTEGRAL;//连续签到处理,获取缓存配置连续签到奖励//因为每个月的天数都不是固定的,连续签到奖励是用的redis hash写入的.所以这个地方用32代替一个月的连续签到天数,具体配置在下方图中if (signContinuousCount == LocalDate.now().lengthOfMonth()) {signContinuousCount = 32;}Map<String, String> configurationHashMap = cacheClient.hgetAll("userSign:configuration");String configuration = configurationHashMap.get(signContinuousCount);if (null != configuration) {int giveIntegral = 0;JSONObject item = JSONObject.parseObject(configuration);giveIntegral = item.getInteger("integral");if (giveIntegral != 0) {if (signContinuousCount == 32) {signContinuousCount = LocalDate.now().lengthOfMonth();}userIntegralLogList.add(UserIntegralLog.builder().createTime(LocalDateTime.now()).bak(String.format(BusinessConstant.Integral.CONTINUOUS_SIGN_COPY, signContinuousCount)).integral(giveIntegral).integralType(BusinessConstant.Integral.SIGN_TYPE_CONTINUOUS).userId(userId).build());count += giveIntegral;}}//改变总积分和批量写入积分记录return updateUserIntegralCount(userId, count) && userIntegralLogService.saveBatch(userIntegralLogList);}

6.补签

补签功能是一个签到补充功能,主要就是方便用户在忘了签到的情况下也能通过补签功能达到相应的连续签到条件,从而得到奖励.
这段代码是一个补签功能的后端实现,主要用于用户在签到平台上进行补签操作。我来详细解释一下它的业务流程:

1首先,根据传入的用户ID和需要补签的日期信息,判断今日是否需要进行补签。如果不需要补签,则返回相应的提示信息并结束流程。

2接着,从 Redis 中获取用户当月已经补签的次数,如果已经达到三次补签上限,则返回相应的提示信息,并将结果设置为失败。

3然后,检查用户的积分情况,确保用户的积分足够进行本次补签所需的积分消耗。如果积分不足,则返回相应的提示信息,并将结果设置为失败。

4如果前面的步骤都通过了,那么表示用户可以进行补签操作。在这里会先构建补签日期的 LocalDate 对象,并生成相应的 Redis Key 来存储用户的签到信息。

5在进行补签前,会对用户今日是否已经签到过进行检测,同时也会检测补签的日期是否大于今天的日期。如果通过检测,则将用户的签到信息在 Redis 中进行设置。

6如果补签成功,将补签的记录存入 Redis,同时更新用户的积分信息,并返回补签成功的提示信息。

整体来说,这段代码主要是通过 Redis 存储用户的签到信息及补签记录,并结合用户的积分情况来进行补签操作的逻辑。

//day表示需要补签的日期,因为我们平台的签到周期是一个月所以只需要传日的信息就可以,入 7号传入7
public ResponseResult saveSignInRetroactive(Integer userId, Integer day) {Boolean result = Boolean.TRUE;ResponseResult responseResult = ResponseResult.newSingleData();responseResult.setMessage("今日无需补签哟");responseResult.setCode((byte) -1);LocalDate timeNow = LocalDate.now();//检测是否补签达上限String retroactiveKey = String.format(RedisKeyConstant.USER_RETROACTIVE_SIGN_IN, timeNow.getMonthValue(), userId);//从redis中取出用户的当月补签的集合set.我们平台的限制是三次补签Set<String> keys = cacheClient.hkeys(retroactiveKey);if (CollUtil.isNotEmpty(keys) && keys.size() == 3) {responseResult.setMessage("本月补签次数已达上限");result = Boolean.FALSE;}//检查补签积分是否足够,这里就是一个简单的单表查询,用于查询积分是否足够本次消耗UserIntegral userIntegral = userIntegralService.getOne(new LambdaQueryWrapper<UserIntegral>().eq(UserIntegral::getUserId, userId));//这里只是简单的做了一个map放置三次补签分别消耗的积分(key:次数 value:消耗积分),也可参照之前连续签到配置放入redis缓存中便于后台管理系统可配置Integer reduceIntegral = getReduceIntegral().get(keys.size() + 1);if (reduceIntegral > userIntegral.getIntegral()) {responseResult.setMessage("您的橙汁值不足");result = Boolean.FALSE;}if (result) {LocalDate retroactiveDate = LocalDate.of(timeNow.getYear(), timeNow.getMonthValue(), day);String signKey = String.format(RedisKeyConstant.USER_SIGN_IN, timeNow.getYear(), userId);long monthAndDay = Long.parseLong(retroactiveDate.format(DateTimeFormatter.ofPattern("MMdd")));//后端检测是否用户今日签到过同时补签日期不可大于今天的日期if (!cacheClient.getBit(signKey, monthAndDay) && timeNow.getDayOfMonth() > day) {boolean oldResult = cacheClient.setbit(signKey, monthAndDay);if (!oldResult) {//补签记录(:月份) 过月清零,过期时间是计算出当前时间的差值,补签次数是一个月一刷新的cacheClient.hset(retroactiveKey, retroactiveDate.getDayOfMonth() + "", "1",(Math.max(retroactiveDate.lengthOfMonth() - retroactiveDate.getDayOfMonth(), 1)) * 60 * 60 * 24);//这里就是对积分总表减少.以及对积分记录进行记录.参照下方代码块doRemoveUserIntegral(userId, reduceIntegral, RETROACTIVE_SIGN_COPY);responseResult.setCode((byte) 0);responseResult.setMessage("补签成功");}}}return responseResult;}

积分总表和积分流水表的变动

  public Boolean doRemoveUserIntegral(int userId, int reduceIntegral, String bak) {return updateUserIntegralCount(userId, -reduceIntegral)&& userIntegralLogService.save(UserIntegralLog.builder().createTime(LocalDateTime.now()).operationTime(LocalDate.now()).bak(bak).integral(-reduceIntegral).integralType(BusinessConstant.Integral.RETROACTIVE_SIGN_COPY.equals(bak) ?BusinessConstant.Integral.SIGN_TYPE_RETROACTIVE : BusinessConstant.Integral.SIGN_TYPE_WELFARE).userId(userId).build());}

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

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

相关文章

python222网站实战(SpringBoot+SpringSecurity+MybatisPlus+thymeleaf+layui)-自定义帖子管理实现

锋哥原创的SpringbootLayui python222网站实战&#xff1a; python222网站实战课程视频教程&#xff08;SpringBootPython爬虫实战&#xff09; ( 火爆连载更新中... )_哔哩哔哩_bilibilipython222网站实战课程视频教程&#xff08;SpringBootPython爬虫实战&#xff09; ( 火…

Django学习之小试牛刀

六、Django学习之小试牛刀 其他关于Python Web开发笔记&#xff1a;&#xff08;如果遇到问题可以一起交流~&#xff09; 一、Flask学习之HTML-CSDN博客 二、Flask学习之CSS-CSDN博客 【接上篇】二、Flask学习之CSS&#xff08;下篇&#xff09;-CSDN博客 三、Flask学习之B…

面试题:Spring在多线程环境下如何确保事务一致性

文章目录 问题在现如何解决异步执行多线程环境下如何确保事务一致性事务回顾事务实现方式回顾编程式事务那么编程式事务是什么样子呢&#xff1f; 利用编程式事务解决问题问题分析完了&#xff0c;那么如何解决问题呢&#xff1f;总结 问题在现 我先把问题抛出来&#xff0c;大…

LandrayOA内存调优 / JAVA内存调优 / Tomcat web.xml 超时时间调优实战

目录 一、背景说明 二、LandrayOA / Tomcat 内存调优 2.1 \win64\tomcat\conf\web.xml 文件调优 2.2 \win64\tomcat\bin\catalina64.bat 文件调优 一、背景说明 随着系统的使用时间越来越长&#xff0c;数据量越多&#xff0c;发现系统的有些功能越来越慢&…

基于InceptionV2/InceptionV3/Xception不同参数量级模型开发构建中草药图像识别分析系统,实验量化对比不同模型性能

最近正好项目中在做一些识别相关的内容&#xff0c;我也陆陆续续写了一些实验性质的博文用于对自己使用过的模型进行真实数据的评测对比分析&#xff0c;感兴趣的话可以自行移步阅读即可&#xff1a; 《移动端轻量级模型开发谁更胜一筹&#xff0c;efficientnet、mobilenetv2、…

ubuntu 22 安装 node,npm,vue

1:安装 nodejs sudo apt update curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt update && sudo apt install -y nodejs node -v 2:安装npm sudo npm install n -g npm -v 3:安装vite npm install vite -g 4:运行vue 把项目拷贝到…

OpenAI正式推出GPT商店 ChatGPT团队订阅服务一并推出

2024年1月11日消息&#xff0c;据外媒报道&#xff0c;如上周在给开发者的邮件中所宣布的一样&#xff0c;因ChatGPT而名声大噪的人工智能公司OpenAI&#xff0c;在本周正式推出了GPT商店&#xff0c;供用户分享和发现个性化的ChatGPT&#xff0c;同时他们也推出了面向各种不同…

用C语言实现贪吃蛇游戏!!!

前言 大家好呀&#xff0c;我是Humble&#xff0c;不知不觉在CSND分享自己学过的C语言知识已经有三个多月了&#xff0c;从开始的C语言常见语法概念说到C语言的数据结构今天用C语言实现贪吃蛇已经有30余篇博客的内容&#xff0c;也希望这些内容可以帮助到各位正在阅读的小伙伴…

雨云VPS搭建PalWorld服务器,幻兽帕鲁开服联机教程(Windows),0基础保姆级教程

雨云VPS用Windows系统搭建幻兽帕鲁私服&#xff0c;PalWorld开服联机教程&#xff0c;零基础保姆级教程&#xff0c;本教程使用一键脚本来搭建幻兽帕鲁服务端&#xff0c;并讲了如何配置游戏参数&#xff0c;如何更新服务端等。 最近这游戏挺火&#xff0c;很多人想跟朋友联机…

实体关系抽取与属性补全的技术浅析

目录 前言1. 实体关系抽取2 实体关系抽取的方法2.1 基于模板的方法2.2 基于监督学习的关系抽取2.3 基于深度学习的关系抽取2.4 基于预训练语言模型的关系抽取 3 属性补全3.1 属性补全任务简介3.1 抽取式属性补全3.2 生成式属性补全 4 未来发展趋势结语 前言 在信息爆炸时代&am…

用甘特图有效管理多个项目进度

当公司或组织同时承担多个项目时,合理规划各项目的时间节点与资源分配对确保高效完成至关重要。采用甘特图可以直观地展示多个项目的时间进程、关键里程碑以及资源分配情况,便于从宏观层面全面把控各项目的动态。 在线甘特图软件 zz-plan.com 提供了非常强大的时间轴规划功能,支…

漏洞复现-万户OA text2Html 任意文件读取(附漏洞检测脚本)

免责声明 文章中涉及的漏洞均已修复&#xff0c;敏感信息均已做打码处理&#xff0c;文章仅做经验分享用途&#xff0c;切勿当真&#xff0c;未授权的攻击属于非法行为&#xff01;文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直接或者间接的…

Cesium介绍及3DTiles数据加载时添加光照效果对比

一、Cesium简介 Cesium原意是化学元素铯&#xff0c;铯是制造原子钟的关键元素&#xff0c;通过命名强调了Cesium产品专注于基于时空数据的实时可视化应用。熟悉GIS开发领域的读者都知道&#xff0c;Cesium是一个用于创建3D地理空间应用程序的开源JavaScript库&#xff0c;它允…

《SPSS统计学基础与实证研究应用精解》视频讲解:数据缺失值处理

《SPSS统计学基础与实证研究应用精解》4.12 视频讲解 视频为《SPSS统计学基础与实证研究应用精解》张甜 杨维忠著 清华大学出版社 一书的随书赠送视频讲解4.12节内容。本书已正式出版上市&#xff0c;当当、京东、淘宝等平台热销中&#xff0c;搜索书名即可。本书旨在手把手教会…

GraphicsMagick 的 OpenCL 开发记录(二十五)

文章目录 如何修复R6025 pure virtual function call问题 <2022-04-19 周二> 如何修复R6025 pure virtual function call问题 运气好&#xff0c;修复了这个问题。即&#xff0c;在ExitInstance()函数中调用一下MagickLib::DestroyMagick();即可。 过程中也经历了尝试…

用友U8接口-部署和简要说明(1)

概括 本专栏文章目的说明对目前用友U8ERP接口介绍对底层接口二次封装的介绍 说明 过去发布过介绍U8接口文章简介&#xff0c;参考以下链接。 U8接口开发方式 本专栏文章与下面的HTTP接口相辅相成&#xff0c;主要是写给正在使用&#xff0c;或未来使用本套接口的开发人员&am…

JDK8新特性(二)

接着上一章&#xff0c;我们继续学习jdk8新特性 目录 一、Optional 1.使用 1.1创建对象 1.2安全消费值 1.3获取值 1.4安全获取值 1.5过滤 1.6判断 1.7数据转换 二、函数式接口 1.常见函数式接口 2.常用的默认方法 三、方法引用 1. 推荐用法 2.基本格式 3.语法详解…

ESP32 操作AT24C32或AT24C64

AT24C32或AT24C64两款芯片容量不一样&#xff0c;其他都一样。程序无法动态识别容量AT24C32容量32K&#xff0c;地址范围0x~0x7FFF.AT24C64容量64K,地址范围0x~0xFFFF 电气参数 电压2.7V-5.5V IIC通信 有引脚控制数据保护 有引脚可以配置IIC的地址。 每个page 32字节 1百…

二叉树计算 - 华为OD统一考试

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 200分 题解&#xff1a; Java / Python / C 题目描述 给出一个二叉树如下图所示&#xff1a; 6 / \ 7 9 \ / -2 6 请由该二叉树生成一个新的二叉树&#xff0c;它满足其树中的每个节点将包含原始树中的左子树和右子树…

30岁以就业为目标学前端,快歇着吧;反之50岁都不晚。

Hi&#xff0c;我是贝格前端工场&#xff0c;首先声明声明我们不搞前端培训&#xff0c;有很多老铁在留言中问我关于前端学习的问题&#xff0c;最普遍的一个问题就是30岁以后学前端晚了吗&#xff1f;今天借着此篇文章回答一下。 一、30岁学前端的三种人 首先抛开年龄不说&am…