天机学堂 第七天 积分系统

news2024/9/22 17:34:35

签到

签到最核心的包含两个要素:

  • 谁签到:用户id

  • 什么时候签的:签到日期

同时要考虑一些功能要素,比如:

  • 补签功能,所以要有补签标示

  • 按照年、月统计的功能:所以签到日期可以按照年、月、日分离保存

 

 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='签到记录表';

但是签到占用数据太大了

随着用户量增多、时间的推移,这张表中的数据只会越来越多,占用的空间也会越来越大。可以考虑用比特位进行签到 

实现思路 

 

像这种把每一个二进制位,与某些业务数据一一映射(本例中是与一个月的每一天映射),然后用二进制位上的数字0和1来标识业务状态的思路,称为位图。也叫做BitMap.

这种数据统计的方式非常节省空间,因此经常用来做各种数据统计。比如大名鼎鼎的布隆过滤器就是基于BitMap来实现的。

OK,那么利用BitMap我们就能直接实现签到功能,并且非常节省内存,还很高效。所以就无需通过数据库来操作了。

BitMap用法

Redis中就提供了BitMap这种结构以及一些相关的操作命令

修改某个bit位上的数据

setbit   键值 偏移量 数值(0或者1)  返回值是原来比特位上的数值,偏移量从0开始,就是选择设置第几位的值

 实例

签到(第几位上设置为1即可)

查询签到记录

 

 签到接口

而在后台,要做的事情就是把BitMap中的与签到日期对应的bit位,置为1.

另外,为了便于统计,我们计划每个月为每个用户生成一个独立的KEY,因此KEY中必须包含用户信息、月份信息,长这样:

连续签到统计

如何得到连续签到天数?需要下面几步:

  • 获取本月到今天为止的所有签到数据

  • 从今天开始,向前统计,直到遇到第一次未签到为止,计算总的签到次数,就是连续签到天数

如图:

每一次签到都做一次统计(从后向前统计),直到遇到0位置 ,每天签到都会统计一次,所以前面连续签到了8天,第九天断了,也没事,因为每次签到的时候都统计,而且第八天签到的时候奖励积分也加上了不用再加了 ,伪代码写法如下:

 

这里存在几个问题:

  • 如何才能得到本月到今天为止的所有签到记录?

  • 如何从后向前遍历每一个bit位?

至于签到记录,可以利用我们之前讲的BITFIELD命令来获取,从0开始,到今天为止的记录,命令是这样的:

遍历比特位思路: 与1做与运算,结果为1 说明签到了,这个只是与最后一位做&运算,然后按位右移(这样倒数第二位就会变成最后一位,依次类推)

  public SignResultVO addSignRecords() {
        // 1. 获取用户id
        Long userId = UserContext.getUser();
        // 2.拼接key
        LocalDate now = LocalDate.now();
        String format = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        String key = RedisConstants.SIGN_RECORD_KEY_PREFIX + userId.toString() + format;
        // 3. 使用bitset命令 将签到记录保存到redis的bitmao结构中 需要校验是否已签到
        int offset = now.getDayOfMonth() -1;
        Boolean exists = redisTemplate.opsForValue().setBit(key, offset, true);
        // 返回值就是原来位上的值
        if (exists){
            throw  new BizIllegalException("不允许重复签到");
        }
        // 4. 计算连续签到的天数
        int signDays = countSignDays(key,now.getDayOfMonth());
        // 5.计算签到得分
        int rewardPoints = 0;
        switch (signDays) {
            case 7:
                rewardPoints = 10;
                break;
            case 14:
                rewardPoints = 20;
                break;
            case 28:
                rewardPoints = 40;
                break;
        }
        // todo 6. 保存积分
        mqHelper.send(
                MqConstants.Exchange.LEARNING_EXCHANGE,
                MqConstants.Key.SIGN_IN,
                SignInMessage.of(userId, rewardPoints + 1)); //签到积分是基本积分+奖励积分
        // 5.封装返回
        SignResultVO vo = new SignResultVO();
        vo.setSignDays(signDays);
        vo.setRewardPoints(rewardPoints);
        return vo;
    }
// 计算连续签到天数
  private int countSignDays(String key, int dayOfMonth) {
        // 从第0位取,取dayOfMonth个数,参数是可以传集合的 所以返回结果是集合,取出来的是10进制
        List<Long> result = redisTemplate.opsForValue()
                .bitField(key, BitFieldSubCommands.create().get(
                        BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
        if (CollUtils.isEmpty(result)){
            return 0;
        }
        int num = result.get(0).intValue();
        // 2.定义一个计数器
        int count = 0;
        // 从最后一位往前数连续一的个数,,每天签到一次执行一次
        while ((num & 1) == 1){
            count ++ ;
            // 数字右移一位,最后一位被舍弃,倒数第二位成了最后一位,
            num >>>= 1;
        }
        return count;
    }

需要记得语法:

localdatetime的format,datetimeformatter.ofpattern

 操作 setbit 对用的是opsforvalue的setbit方法,因为比特底层就是string类型实现的

 valueat表示从第几位开始取,取无符号位到第几位(无符号位就是整数),取出来的数是对应的从0到指定的位数对应的二进制转化的十进制

 与1做与运算会自动转化为二进制

 新增积分功能

查看该类型积分是否有每日上限,如果没有直接保存,如果有统计今日已获得的积分如果加上新积分是否会超过上限(然后保存),同时统计已经获得的总积分,放到redis中以zset方式存储

积分的获取方式多种多样,为了解耦采用MQ来实现异步解耦。 

综上,在MQ中我们只需要传递用户id一个参数即可。(因为每一种类型加的分都是固定的,可以在枚举或者常量中定义) 

发送MQ消息

在签到签到中发送的消息:填写了用户id编写消息监听器

根据不用routingkey的类型接手消息

 因为每种类型的积分可能有积分上限

首先根据雷翔判断有没有积分上限,然后数据库统计今日已经获得的积分,并累加积分到Zset

public void addPointsRecord(SignInMessage msg, PointsRecordType type) {
        // 判断该积分类型是否有上限 type.getmaxpoint()
        int maxPoints = type.getMaxPoints();
        // 涉及要增加的积分
        int realPoint = msg.getPoints();
        if (maxPoints > 0){
            LocalDateTime now = LocalDateTime.now();
            LocalDateTime dayStartTime = DateUtils.getDayStartTime(now);
            LocalDateTime dayEndTime = DateUtils.getDayEndTime(now);
            // 如果有积分上限 查询该用户今日得到的积分
            QueryWrapper<PointsRecord> wrapper = new QueryWrapper<>();
            wrapper.select("sum(points) as totalPoints");
            wrapper.eq("user_id",msg.getUserId());
            wrapper.eq("type",type);
            wrapper.between("create_time",dayStartTime,dayEndTime);
            Map<String, Object> map = this.getMap(wrapper);
            int currebtPoints = 0;  // 当前用户 这个类型下已经获得的积分
            if (map!=null){
                BigDecimal totalPoints = (BigDecimal)map.get("totalPoints");
                currebtPoints = totalPoints.intValue();
            }
            // 3.判断已得积分是否超过上限
            if(currebtPoints >= maxPoints){
                return;
            }
            // 判断加上积分是否会超过上限
            if (currebtPoints + msg.getPoints()  > maxPoints){
                realPoint = maxPoints-currebtPoints;
            }
        }
        // 保存积分
        PointsRecord pointsRecord = new PointsRecord();
        pointsRecord.setUserId(msg.getUserId());
        pointsRecord.setType(type);
        pointsRecord.setPoints(realPoint);
        save(pointsRecord);
        // 累加并保存总积分到redis 采用zset 当前赛季排行榜
        LocalDate now = LocalDate.now();
        String format = now.format(DateTimeFormatter.ofPattern("yyyyMM"));
        String key = RedisConstants.POINTS_BOARD_KEY_PREFIX + format;
        redisTemplate.opsForZSet().incrementScore(key,msg.getUserId().toString(),realPoint);
    }

 语法:

Map<String, Object> map = this.getMap(wrapper); 一般只有一行数据的时候采用 getmapper

 sum 聚合出来的值是bigdecimal形式

查询今日已获得的积分 

 groupby分组聚合,统计今日获得的积分

查询签到记录

在签到日历中,需要把本月第一天到今天为止的所有签到过的日期高亮显示。因此我们必须把签到记录返回,具体来说就是每一天是否签到的数据。是否签到,就是0或1,刚好在前端0和1代表false和true,也就是签到或没签到。

因此,每一天的签到结果就是一个0或1的数字,我们最终返回的结果是一个0或1组成的数组,对应从本月第1天到今天为止每一天的签到情况。

 思路分析,还是做位运算,返回值是list还是byte都行,从第0位取,取到本月的第几天-1,对每位做与运算然后放到数组中

面试

SET:点赞中用到了set,,key是业务id,内容是每个点赞的用户id

积分中用到了zset  member是每个用户ID score是积分数

注意点:

mq队列名随便写,只要routingkey匹配即可

什么时候用 getmap当结果只有一行的时候用getmap(并且使用了select()制定某个列的时候??),结果有多行用list() 

mybatisplus 使用select("sum(point) as points")  并且使用getmap接收时,结果是bigdecimal类型

十进制  & 1 会自动转化为二进制,得到的结果类型看操作数的类型(可能发生自动转化?)

redistemplate和stringredistemplate区别

  • 两者的数据是不共通的;也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据。

 

StringRedisTemplate使用的是StringRedisSerializer,当你的redis数据库里面本来存的是字符串数据,或者你要存取的数据就是字符串类型数据的时候,那么你就使用StringRedisTemplate即可 

枚举

springboot整合mybatisplus通用枚举(五)---@EnumValue

 @EnumValue在mp中世纪取得的是其标注的值

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

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

相关文章

2024年TI杯E题-三子棋游戏装置方案分享-jdk123团队-第一弹赛题的选择与前期方案的准备

赛前准备 本来我们团队前几个月的准备都在小车上&#xff0c;赛前也完成了 STM32,树莓派4B&#xff0c;Openmv等几款常见主控板来对小车完成基本的模块封装控制。我们团队的大部分精力以及预算都准备在了小车上面。 赛题选择 由于在赛题公布的的那一天&#xff0c;我们发现H…

张邱建 百鸡问题 python 求解

“百鸡问题”是一个著名的古典数学问题&#xff0c;来源于中国古代数学著作《张邱建算经》。问题是这样的&#xff1a;公鸡每只5钱&#xff0c;母鸡每只3钱&#xff0c;小鸡3只1钱&#xff0c;用100钱买100只鸡&#xff0c;问公鸡、母鸡、小鸡各有多少只&#xff1f; from sym…

【学习笔记】卫星网络(NTN)的窄带物联网(NB-IoT)/增强型机器类型通信(eMTC)研究 -- 3GPP TR 36.763(一)

引言 在RAN#86会议上&#xff0c;针对物联网非陆地网络&#xff08;IoT NTN&#xff09;批准了一个新的研究项目&#xff0c;并在RAN#91中进行了修订[4]。在RAN#91中&#xff0c;有一封关于[91E][42][NTN_IoT_Roadmap]的电子邮件讨论&#xff0c;其中包括主持人的总结和GTW输入…

NOI Linux 2.0 的安装说明以及使用指南

关于 NOI Linux 2.0 NOI Linux 是 NOI 竞赛委员会基于 Ubuntu 操作系统开发的一款 Linux 桌面发行版&#xff0c;是一套免费的、专门为信息学奥林匹克竞赛选手设计的操作系统&#xff0c;是 NOI 系列赛事指定操作系统&#xff0c;适用于常见笔记本电脑和桌面电脑。 新建虚拟机…

lvs加keepalive高可用集群

lvs集群当中的高可用架构只是针对调度器的高可用 基于vrrp来实现调度器的主和备 高可用的HA架构 主调度器和备调度器&#xff08;多台&#xff09; 在主调度器正常工作的时候&#xff0c;备完全处于冗余状态&#xff08;待命&#xff09;&#xff0c;不参与集群的运转&…

深入解析Nginx限流策略:如何高效控制访问频率

摘要&#xff1a;本文将详细介绍Nginx限流模块的使用方法&#xff0c;包括基于IP地址的限流、基于并发连接的限流以及如何应对突发流量。通过实际案例&#xff0c;帮助读者掌握Nginx限流策略&#xff0c;确保服务器在高并发场景下的稳定运行。 一、引言 在高并发场景下&#x…

Vscode下ESP32工程函数定义无法跳转

1.删除.vscode 2.按下 ctrlshiftp&#xff0c;搜索 ESP-IDF:Add vscode Configuration Folder

SwinIR速读

SwinIR发表在ICCV21,它核心创新在于使用了SwinTransformer(同年ICCV best paper)进行图像修复&#xff0c;由ETH的CVL提出&#xff0c;大组还是抓前沿抓得好&#xff0c;不仅能想到&#xff0c;还能做出来性能&#xff0c;而且性能还是稳定的增益&#xff0c;通用性强。 目前&a…

TCP Window Full TCP Zero Window

TCP window Full & TCP Zero Window 注&#xff1a; 192.168.60.67 是客户端IP10.250.251.223是服务端IP 当客户端向服务发送多个280大小应用层数据时&#xff0c;在No 45帧数据时发生TCP Window Full&#xff0c;应用发送280字节&#xff0c;实际发出去只有67个字节。28…

机器学习——逻辑回归(学习笔记)

目录 一、认识逻辑回归 二、二元逻辑回归&#xff08;LogisticRegression&#xff09; 1. 损失函数 2. 正则化 3. 梯度下降 4. 二元回归与多元回归 三、sklearn中的逻辑回归&#xff08;自查&#xff09; 1. 分类 2. 参数列表 3. 属性列表 4. 接口列表 四、逻辑回归…

11.面试题——消息队列RabbitMQ

1.RabbitMQ是什么&#xff1f;特点是什么&#xff1f; RabbitMQ是一种开源的消息队列中间件&#xff0c;用于在应用程序之间进行可靠的消息传递。它实现了AMQP&#xff08;Advanced Message Queuing Protocol&#xff09;协议&#xff0c;提供了强大的消息处理能力。RabbitMQ的…

DVWA—SQL Injection

DVWA—SQL Injection实例 1. low等级 进入界面后&#xff0c;可以看到它是一个ID的查找内容的输入框&#xff0c;我们输入数字1、2可以看到是从后端返回数据库相应的数据&#xff0c;但是我们作为SQL注入的最终目标是得到更多有关的信息&#xff0c;所以我们需要通过构造payl…

【生成式人工智能-九-大型语言模型的幻觉、偏见等安全性问题】

大型语言模型的安全性 幻觉对语言模型的输出做核查 偏见消除偏见的方法 鉴别是否是人工智能输出prompt hacking 语言模型被骗做事情jailbreakingjailbreaking的危害 prompt injection 今天还是先来谈一下有哪些安全性问题&#xff0c;以及简单介绍有那些应对方案。 幻觉 看过大…

BugKu CTF Misc:

前言 BugKu是一个由乌云知识库&#xff08;wooyun.org&#xff09;推出的在线漏洞靶场。乌云知识库是一个致力于收集、整理和分享互联网安全漏洞信息的社区平台。 BugKu旨在提供一个实践和学习网络安全的平台&#xff0c;供安全爱好者和渗透测试人员进行挑战和练习。它包含了…

写一个Vue2和vue3的自定义指令(以复制指定作为示例)

文章目录 一、自定义指令是什么&#xff1f;二、自定义指令有啥用&#xff1f;三、自定义指令怎么用&#xff1f;1.自定义指令的参数2.自定义指令的钩子函数&#xff08;1&#xff09;五个钩子函数的说明&#xff08;2&#xff09;钩子函数的参数(主要参数&#xff1a;el和valu…

高效管理视频文件,2024年视频压缩软件精选集

我的生活中处处都充满了数据的影子&#xff0c;不知道你是不是也这样。而且存储的数据可能由于某些失误会导致我们数据的丢失&#xff0c;幸运的是&#xff0c;现在我们掌握全免费的数据恢复工具的使用方式&#xff0c;就能尽可能地找回丢失的数据。 1.FOXIT数据恢复 连接直达…

解决客户访问超时1s问题

访问公网地址返回状态码499-CSDN博客 需求描述 客户访问公司公网服务&#xff0c;期望在1s内完成。他们在客户端设置了超时1s的配置&#xff0c;如果超过1s公司服务就会报错499&#xff0c;这是正常的请求返回。 分析问题 目前这个服务通过公网的alb负载均衡到ecs&#xff0…

Stable Diffusion绘画 | 提示词格式

推荐格式 提升画质的提示词与画风的提示词&#xff0c;对整体画面影响较大&#xff0c;建议在首行填写 画质词画风词画面主体描述环境、场景、灯光、构图Lora负面词 画质词 常规画质词&#xff1a; (masterpiece:1.2),best quality,highres,extremely detailed CG,perfect…

集成新的 AI 服务时需要考虑的问题

让我们来谈论最近发生的几个恐怖故事。 去年年底&#xff0c;一家雪佛兰经销商在其主页上部署了一个由大型语言模型 (LLM) 驱动的聊天机器人。该 LLM 经过雪佛兰汽车详细规格的训练&#xff0c;旨在仅回答有关雪佛兰汽车的问题。 然而&#xff0c;用户很快就找到了绕过这些限…

驰骋BPM RunSQL_Init接口SQL注入漏洞复现 [附POC]

文章目录 驰骋BPM RunSQL_Init接口SQL注入漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现驰骋BPM RunSQL_Init接口SQL注入漏洞复现 [附POC] 0x01 前言 免责声明:请勿利用文章内的相关技术从事非法测试,由于…