mysql 连续签到天数_签到功能实现,没有你想的那么复杂(一)

1 签到定义以及作用签到,指在规定的簿册上签名或写一“到”字,表示本人已经到达。在APP中使用此功能,可以增加用户粘性和活跃度.2 技术选型redis为主写入查询,mysql辅助查询. 传统签到多数都是直接采用mysql为存储DB,在大数据的情况下数据库的压力较大.查询速率也会随着数据量增大而增加.所以在需求定稿以后查阅了很多签到实现方式,发现用redis做签到会有很大的优势.本功能主要用到redis位图,后面我会详细讲解实现过程.3.实现效果
这里抛砖引玉,展示我们app的签到实现效果

d307675e297fcc014b8bf2aa62e56d62.png

4 功能实现
功能大致分为两个大模块

  • 签到流程(签到,补签,连续,签到记录)
  • 签到任务(每日任务,固定任务)

签到流程图如下:

b8cd0624241c6470cd628df97cbce9d5.png

4.1.1 表设计
因为大部分功能使用redis存储,使用到mysql主要是为了存储用户总积分以及积分记录,便于查询签到记录和用户总积分

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.1.2 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";

4.1.3 实现签到

接口restful的形式,头信息里传入用户id

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

sevice实现层

    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;}

计算连续签到天数

    /*** @description: 获取连续签到天数* @author: chenyunxuan* @updateTime: 2020/8/25 4:43 下午*/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值右移一位再左移一位后与原始值不相等,连续天数加一if (v >> 1 << 1 == v) return signCount;signCount += 1;v >>= 1;}}return signCount;}

记录签到积分类型以及连续签到积分

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);}

连续签到获取的积分配置以及文案配置

31be06c06bbc916a87c56d2dcfef37bf.png

4.1.4 实现补签

补签功能是一个签到补充功能,主要就是方便用户在忘了签到的情况下也能通过补签功能达到相应的连续签到条件,从而得到奖励.

补签主方法

//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());}

至此一个签到补签的完整流程就做好了.之后的文章将会介绍签到日历和签到任务的解决方案

原文作者:chenyunxuan

原文链接:https://segmentfault.com/a/1190000023961648

原文出处:掘金

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

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

相关文章

java包装项目_项目包装组织

java包装项目程序包是Java的基本概念&#xff0c;是您开始用该语言编程时偶然发现的第一件事。 作为一个初学者&#xff0c;您可能不太关注软件包的结构&#xff0c;但是随着您成为经验丰富且成熟的软件开发人员&#xff0c;您开始考虑可以采取哪些措施来提高其效率。 有几个主…

如何开发 Servlet 程序

文章目录如何开发 Servlet步骤 1&#xff1a;写一个类步骤 2&#xff1a;编译步骤 3&#xff1a;打包步骤 4&#xff1a;部署步骤 5&#xff1a;启动服务器步骤 6&#xff1a;访问 servletServlet 开发示例不使用 IDE 开发&#xff08;手动编译和部署&#xff09;步骤 1&#x…

报任安书文言现象_语文老师精心总结【文言文常考点】够你从初一用到初四!...

点击本号菜单栏 免费获取学习资料▼今天给大家整理了初中文言文的一些常用知识点&#xff1a;特殊句式和古今异义&#xff0c;这些只是文言文学习模块中的一部分&#xff0c;除此之外&#xff0c;其他大家需要在平时积累的文言文知识点有下面这些&#xff1a;文言文高频词、古代…

参数化测试 junit_JUnit 5 –参数化测试

参数化测试 junitJUnit 5令人印象深刻&#xff0c;尤其是当您深入研究扩展模型和体系结构时 。 但是从表面上讲&#xff0c;编写测试的地方&#xff0c;开发的过程比革命的过程更具进化性 – JUnit 4上没有杀手级功能吗&#xff1f; 幸运的是&#xff0c;至少有一个&#xff1a…

devexpress textedit调整文字何文本框的间距_手把手教学:用PPT做效果超赞的文字效果...

本文总计&#xff1a;2391 字预计阅读时间&#xff1a;6 分钟昨天文章的头图&#xff0c;貌似反馈还不错&#xff0c;挺多人比较感兴趣。所以&#xff0c;今天就分享一下&#xff0c;这种文字排版效果&#xff0c;是怎么做出来的。而且今天的实现手法与效果&#xff0c;做了一些…

IntelliJ IDEA for Mac 如何取消双击shift键打开全局搜索弹窗

取消双击shift键打开全局搜索弹窗 按ShiftcmdA&#xff0c;打开如下图的搜索框&#xff1a; 输入Registry搜索后打开如下的窗口&#xff1a; 3. 找到“ide.suppress.double.click.handler”&#xff0c;将后面的复选框勾上&#xff0c;勾选上复选框后直接关闭退出&#xff0c;…

vb6 判断打印机是否有效_吊打面试官 | 算法之如何判断括号是否有效?

今天要讲的这道题是 bilibili 今年的笔试真题&#xff0c;也是一道关于栈的经典面试题。经过前面文章的学习&#xff0c;我想很多朋友已经看出来了&#xff0c;我接下来要写的是一个关于「算法图解」的系列文章&#xff0c;中间可能会穿插少量的其他类型的文章&#xff0c;但「…

如何理解字符编码

一直有个困惑&#xff0c;为什么计算机系统搞那么多字符编码&#xff0c;就一个Unicode统一天下不就得了&#xff0c;后来看了篇文章&#xff0c;才多少理解一丁点。 英语的国家&#xff0c;只要一个字节就可以表示全部的字符&#xff0c;一个无符合的字节可以表示256个字符&a…

框架下载_25. Scrapy 框架-下载中间件Middleware

1. Spider 下载中间件(Middleware)Spider 中间件(Middleware) 下载器中间件是介入到 Scrapy 的 spider 处理机制的钩子框架&#xff0c;您可以添加代码来处理发送给 Spiders 的 response 及 spider 产生的 item 和 request2. 激活一个下载DOWNLOADER_MIDDLEWARES要激活一个下载…

android activity 显示无焦点_Android面试题集锦之fragemnt

大家可以关注一下小编&#xff0c;小编以后会一直更新Android相关技术资料文章。创建方式静态创建首先我们需要创建一个xml文件&#xff0c;然后创建与之对应的java文件&#xff0c;通过onCreatView()的返回方法进行关联&#xff0c;最后我们需要在Activity中进行配置相关参数即…

node 安装_VUE项目迁移之node.js的安装

【摘要】由于公司的项目需要迁移到VUE中去, 所以就用到了node.js, 这里简单整理了一下node.js的安装教程和环境变量的配置【作者】田鋆鹏Node.js 安装教程1. 在node.js的官网下载安装包下载地址1: https://nodejs.org/en/下载地址2: http://nodejs.cn/直接下载.msi的安装包即可…

jsp mysql servlet_JSP+Servlet+JDBC+mysql实现的学生成绩管理系统

本系统基于JSPServletMysql一个基于JSPServletJdbc的学生成绩管理系统。涉及技术少&#xff0c;易于理解&#xff0c;适合JavaWeb初学者学习使用。难度等级&#xff1a;入门技术栈编辑器Eclipse Version: 2019-12 (4.14.0)前端技术基础&#xff1a;htmlcssJavaScript框架&#…

mariadb mysql 配置文件_MariaDB/MySQL配置文件my.cnf解读

MariaDB/MySQL的默认设置性能非常差&#xff0c;仅仅起一个功能测试的作用&#xff0c;不能用在生产环境中&#xff0c;因此要对一些参数进行调整优化。当然&#xff0c;对配置文件各参数的调整需要根据实际环境&#xff0c;不同时期不同数量级的数据进行性能优化。MySQL/Maria…

react 事件处理_在React中处理事件

react 事件处理在使用React渲染RESTful服务后&#xff0c;我们创建了简单的UI&#xff0c;用于渲染从RESTful服务获取的员工列表。 作为本文的一部分&#xff0c;我们将扩展同一应用程序以支持添加和删除员工操作。 我们将通过添加/删除员工操作来更新react-app后端api&#x…

多元回归求解 机器学习_金融领域里的机器学习算法介绍:人工神经网络

人工智能的发展在很大程度上是由神经网络、深度学习和强化学习推动的。这些复杂的算法可以解决高度复杂的机器学习任务&#xff0c;如图像分类、人脸识别、语音识别和自然语言处理等。这些复杂任务一般是非线性的&#xff0c;同时包含着大量的特征输入。我们下面我们将分几天的…

apache ignite_使用Spring Data的Apache Ignite

apache igniteSpring Data提供了一种统一而简便的方法来访问不同类型的持久性存储&#xff0c;关系数据库系统和NoSQL数据存储。 它位于JPA之上&#xff0c;添加了另一层抽象并定义了基于标准的设计以在Spring上下文中支持持久层。 Apache Ignite IgniteRepository实现了Spri…

java事件处理过程分布写_Java 9中的进程处理

java事件处理过程分布写一直以来&#xff0c;用Java管理操作系统进程都是一项艰巨的任务。 这样做的原因是可用的工具和API较差。 老实说&#xff0c;这并非没有道理&#xff1a;Java并非旨在达到目的。 如果要管理OS进程&#xff0c;则可以使用所需的Shell&#xff0c;Perl脚本…

mac mysql5.7.9 dmg_Mac 安装 mysql5.7

mac 安装msql 5.7最近使用Mac系统&#xff0c;准备搭建一套本地web服务器环境。因为Mac系统没有自带mysql&#xff0c;所以要手动去安装mysql&#xff0c;本次安装mysql最新版5.7.28。安装步骤参考以下博客https://www.jianshu.com/p/71f81a0c62b2安装成功后&#xff0c;因为密…

安卓系统dicom阅读器_用户分享:电子书阅读器Note Pro,一座贴心的移动图书馆...

本文转载自“什么值得买”官网用户“小良读书”&#xff0c;经作者授权转载。Note Pro&#xff0c;一座贴心的移动图书馆移动图书馆貌美的小书郎10.3寸高清大屏更适合专业书籍的阅读如果说多年前入手了一台kindle paperwite3电纸书阅读器&#xff0c;它让我畅游了书籍的江河&am…

vim 编辑器命令整理

文章目录一、基本使用流程二、普通命令模式&#xff08;一&#xff09;切换到插入模式&#xff08;编辑/写入/输入&#xff09;&#xff08;二&#xff09;切换到可视模式&#xff08;选择文本模式&#xff09;&#xff08;三&#xff09;切换至底行命令模式&#xff08;四&…