news 2026/2/15 15:07:41

天机学堂项目文档Day07

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
天机学堂项目文档Day07

Day07

签到功能实现:

1.思路分析:

首先假设使用数据库中的签到表,当该项目的用户体量越来越大的时候,该数据库关于签到表的记录就会占用很大的空间。举个例子:假如一个用户1年签到100次,而网站有100万用户,就会产生1亿条记录。随着用户量增多、时间的推移,这张表中的数据只会越来越多,占用的空间也会越来越大。因次,采用另一种数据结构去保存签到记录——位图。这种数据统计的方式非常节省空间,因此经常用来做各种数据统计。Redis中就提供了BitMap这种结构以及一些相关的操作命令。

2.接口实现:

public interface redisConstants { /** * 签到记录的key的前缀:sign:uid:110:202512 */ String SIGN_RECORD_KEY_PREFIX = "sign:uid:"; }
/*** ***思路分析:要实现签到功能接口,首先需要知道当前的用户信息,也就是用户id,由于该功能需要 ***使用redis,因此还需要明确key才能写入签到,因此需要明确key的格式,在上述代码中已经规定了 ***key的格式。得到了用户id和key之后还需要得到当天是该月的第几天。得到第几天之后还需要计算偏移量 ***(也就是当天对应在BitMap的位置),最后,根据上述信息即可完成签到功能。但是,由于后续还需要 ***进行积分相关业务,因此还需要推算出该月连续签到了几次,因此将该月的签到信息的字符码(01010011) ***(0代表没签到,1代表签到)返回后,还需要从该字符码的信息,从最后遍历,与1进行与运算,当 ***运算结果为0时就可以结束,最后将统计天数返回,在根据天数,判断应该添加多少积分,通过mq发送消息 ***最后返回签到vo即可。统计连续签到的代码在下面。 ***/ public SignResultVO addSignRecords() { //1.签到 //获取用户信息 Long userId = UserContext.getUser(); //获取日期 LocalDate now = LocalDate.now(); //拼接key String key = redisConstants.SIGN_RECORD_KEY_PREFIX + userId + now.format(DateUtils.SIGN_DATE_SUFFIX_FORMATTER); //计算offset int offset = now.getDayOfMonth() - 1;//表示签到日期在当月中的位置 Boolean signResult = redisTemplate.opsForValue().setBit(key, offset, true); if (BooleanUtils.isTrue(signResult)) throw new BizIllegalException("不允许重复签到"); // 2.计算连续签到的天数 int signDays = countSignDays(key , now.getDayOfMonth()); int rewardPoints = 0; //TODO 3.计算签到得分 switch (signDays){ case 7: rewardPoints = 10; break; case 14: rewardPoints = 20; break; case 28: rewardPoints = 40; break; } //TODO 4.保存积分明细记录 rabbitMqHelper.send(MqConstants.Exchange.LEARNING_EXCHANGE, MqConstants.Key.SIGN_IN, SignMessage.of(userId, rewardPoints + 1)); //4.封装返回 SignResultVO vo = new SignResultVO(); vo.setSignDays(signDays); vo.setRewardPoints(rewardPoints); return vo; }
/** * 计算连续签到的天数 * @param key Redis中存储签到记录的键 * @param len 需要检查的位数长度 * @return 连续签到的天数 */ private int countSignDays(String key, int len) { //获取本月从第一天开始到今天为止的所有签到记录 // 使用Redis的BITFIELD命令获取指定位域的值 List<Long> result = redisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create().get( BitFieldSubCommands.BitFieldType.unsigned(len)).valueAt(0)); // 如果结果为空,说明没有签到记录,返回0 if (CollUtils.isEmpty(result)) return 0; // 获取签到记录的数值表示 int num = result.get(0).intValue(); //定义一个计数器,用于记录连续签到的天数 int count = 0; //循环,与1做与运算得到最后一个bit,判断是否为0,为0终止,为1继续 while ((num & 1) ==1){ //计数器加1 count++; //把数字右移一位,最后一位被舍弃 num >>>= 1; } return count; }

新增积分功能实现

1.思路分析:

由积分规则可知,获取积分的行为多种多样,而且每一种行为都有自己的独立业务。而这些行为产生的时候需要保存一条积分明细到数据库。我们显然不能要求其它业务的开发者在开发时帮我们新增一条积分记录,这样会导致原有业务与积分业务耦合。因此必须采用异步方式,将原有业务与积分业务解耦。因此,采用mq去发送消息,通过消息监听器去增加用户积分。今天实现的只问答,签到,完成视频或者考试得到的积分。

2.接口实现

1.监听器的代码如下:

实现了问答,学习视频或考试签到等有关积分的监听器:

/** * 签名消息类,用于存储用户签到相关的信息 * 使用Lombok注解自动生成getter、setter、toString等方法 */ @Data @NoArgsConstructor @AllArgsConstructor(staticName = "of") public class SignMessage { /** * 用户ID,用于标识唯一用户 */ private Long userId; /** * 积分数量,表示用户签到获得的积分 */ private Integer points; }
/** ***该代码只实现问答、视频或考试、签到等相关的积分监听器 ***其中关于积分的监听器,除了需要知道用户ID,还需要用户得到的积分数量 ***因为签到这里的积分是可变的,而其余的积分是规定好的,因此,在这里 ***封装了一个message的类,存储了用户id和积分,在上述代码中。 **/ @Component @RequiredArgsConstructor public class LearningPointsListener { private final IPointsRecordService recordService; /** * 问答积分监听器 * @param userId 用户id */ @RabbitListener(bindings = @QueueBinding( value = @Queue(value = "qa.points.queue", durable = "true"), exchange = @Exchange(value = MqConstants.Exchange.LEARNING_EXCHANGE, type = ExchangeTypes.TOPIC), key = MqConstants.Key.WRITE_REPLY )) public void listenWriteReplyMessage(Long userId){ recordService.addPointsRecord(userId,5, PointsRecordType.QA); } /** * 签到积分监听器 * @param message 监听的信息 */ @RabbitListener(bindings = @QueueBinding( value = @Queue(value = "sign.points.queue", durable = "true"), exchange = @Exchange(value = MqConstants.Exchange.LEARNING_EXCHANGE, type = ExchangeTypes.TOPIC), key = MqConstants.Key.SIGN_IN )) public void listenSignInMessage(SignMessage message){ recordService.addPointsRecord(message.getUserId(),message.getPoints(), PointsRecordType.SIGN); } /** * 学习视频或者考试监听器 * @param userId 用户id */ @RabbitListener(bindings = @QueueBinding( value = @Queue(value = "video.points.queue", durable = "true"), exchange = @Exchange(value = MqConstants.Exchange.LEARNING_EXCHANGE, type = ExchangeTypes.TOPIC), key = MqConstants.Key.LEARN_SECTION )) public void listenLearningVideoMessage(Long userId){ recordService.addPointsRecord(userId,10, PointsRecordType.LEARNING); } }
2.签到获得积分增加的代码如下:

在SignRecordServiceImpl中的addSignRecords方法添加如下代码:

int rewardPoints = 0; switch (signDays){ case 7: rewardPoints = 10; break; case 14: rewardPoints = 20; break; case 28: rewardPoints = 40; break; } //TODO 4.保存积分明细记录 rabbitMqHelper.send(MqConstants.Exchange.LEARNING_EXCHANGE, MqConstants.Key.SIGN_IN, SignMessage.of(userId, rewardPoints + 1));
3.问答获得积分增加代码如下:

在InteractionReplyServiceImpl中的saveReply方法中添加如下代码:

if(dto.getIsStudent()){ question.setStatus(QuestionStatus.UN_CHECK); //如果是学生回答了,需要发送mq消息增加该学生的积分 mqHelper.send(MqConstants.Exchange.LEARNING_EXCHANGE, MqConstants.Key.WRITE_REPLY, userId); }
4.学习完视频或者考试获得积分增加的代码如下:

在LearningRecordServiceImpl中的addLearningRecord方法中添加如下代码:

if (!finished){ //没有新学完的小节,无需更新课表中的学习进度 return; } //有新学完的小节,需要添加积分 mqHelper.send(MqConstants.Exchange.LEARNING_EXCHANGE, MqConstants.Key.LEARN_SECTION, userId); //3.有新学完的小节,处理课表数据 handleLearningLessonsChanges(dto);

查询签到记录功能实现:

/*** ***思路分析:查询签到记录的功能其实跟前面实现查询连续签到的天数的思路差不多 ***首先拿到用户id和key以及现在的天数, 在去redis中查到对应的字符码,同时也需要计算偏移量 ***以及顶一个字符数组存储该月的开始到现在的签到记录,也就是(010100111)这种,最后将字符码 ***从后往前开始跟1进行yu运算,在根据偏移量将该数据填充进字符数组(也是从后往前开始填充),最 ***后返回字符数组。注:该功能我使用swagger去测试的时候,发先Byte字符数组似乎不满足spring要求 ***我也不清楚怎么回事。 ***/ @Override public Byte[] querySignRecords() { //获取当前用户id Long userId = UserContext.getUser(); if (userId == null) throw new BizIllegalException("当前用户未登录!"); //获取当前日期 LocalDate now = LocalDate.now(); String key = redisConstants.SIGN_RECORD_KEY_PREFIX + userId + now.format(DateUtils.SIGN_DATE_SUFFIX_FORMATTER); int currentDay = now.getDayOfMonth(); List<Long> result = redisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create().get( BitFieldSubCommands.BitFieldType.unsigned(currentDay)).valueAt(0)); if (CollUtils.isEmpty(result)) return new Byte[0]; Byte[] signDay = new Byte[currentDay]; //表示当月的签到记录返回数组 int signDays = result.get(0).intValue(); //当月签到记录的字节码01010011 int offset = currentDay - 1;//计算偏移量,对应的字节码位置为当天的天数减1 while (offset >=0){ //判断当前字节码与1进行与运算是1还是0 signDay[offset] = (signDays & 1) == 1 ? (byte)1 : (byte)0; //把数字右移一位,最后一位被舍弃 signDays >>>= 1; offset--; } return signDay; }

实现查询赛季列表功能

该功能能简单,就是返回一个积分赛季榜vo的list,从数据查到po转化成vo即可。

/** * 查询历史赛季列表的方法 * 该方法从数据库中获取所有赛季信息,并将其转换为视图对象(VO)列表返回 * @return 返回一个PointsBoardSeasonVO对象的列表,包含赛季的基本信息 */ @Override public List<PointsBoardSeasonVO> queryHistorySeasonsList() { // 从数据库中查询所有赛季数据 List<PointsBoardSeason> list = baseMapper.selectList(null); // 创建用于存储视图对象的列表 List<PointsBoardSeasonVO> vo = new ArrayList<>(); // 遍历查询结果,将实体对象转换为视图对象 for (PointsBoardSeason season : list) { // 创建视图对象 PointsBoardSeasonVO pointsBoardSeasonVO = new PointsBoardSeasonVO(); // 设置视图对象的各个属性 pointsBoardSeasonVO.setId(season.getId()); pointsBoardSeasonVO.setName(season.getName()); pointsBoardSeasonVO.setBeginTime(season.getBeginTime()); pointsBoardSeasonVO.setEndTime(season.getEndTime()); // 将转换后的视图对象添加到列表中 vo.add(pointsBoardSeasonVO); } // 返回转换后的视图对象列表 return vo; }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/14 21:40:11

springboot天天篮球馆管理系统-计算机毕业设计源码58402

摘 要 本论文设计并实现了基于Spring Boot框架的“天天”篮球馆管理系统&#xff0c;旨在为篮球馆提供一个高效、便捷的管理平台&#xff0c;提升场馆运营管理的效率与用户体验。系统采用MySQL数据库进行数据存储&#xff0c;并使用Java编程语言实现后台业务逻辑&#xff0c;支…

作者头像 李华
网站建设 2026/2/15 10:40:41

MusicFreeDesktop音质提升全攻略:从模糊到清晰的三步操作

MusicFreeDesktop音质提升全攻略&#xff1a;从模糊到清晰的三步操作 【免费下载链接】MusicFreeDesktop 插件化、定制化、无广告的免费音乐播放器 项目地址: https://gitcode.com/gh_mirrors/mu/MusicFreeDesktop 还在为音乐细节模糊而烦恼&#xff1f;明明下载了无损音…

作者头像 李华
网站建设 2026/1/29 11:34:34

Oracle数据库迁移

简单谈谈本人对Oracle数据库的理解 话不多说直接开始 第一步&#xff1a;将原始数据库导出 方式1&#xff08;推荐&#xff09;&#xff1a;使用Data Pump --创建文件路径 create or replace directory 路径名 as C:\backup --根据情况自定义--执行导出命令 expdp 用户名/密…

作者头像 李华
网站建设 2026/2/15 12:41:24

Java毕设项目:基于SpringBoot的高校学生奖项管理系统基于springboot高校奖助学金系统(源码+文档,讲解、调试运行,定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/2/14 20:55:41

Wan2.2-T2V-A14B支持剪纸艺术动态展开过程模拟

Wan2.2-T2V-A14B支持剪纸艺术动态展开过程模拟 在数字内容创作的浪潮中&#xff0c;我们正见证一场从“静态图像”到“动态叙事”的跃迁。过去需要数小时手工拍摄、后期合成的传统艺术表现形式&#xff0c;如今只需一句话——比如&#xff1a;“一张红色宣纸缓缓展开&#xff0…

作者头像 李华
网站建设 2026/2/8 9:43:05

12.电阻电容电感选型

一、电阻 1、选型依据 阻值:电阻值; 封装:常用封装0201,0402,0603,0805,1206,1812等&#xff1b; 功耗:1/16W,1/10W,1/8W,1/4W,1/2W,1W,2W,3W等&#xff1b; 精度:1%&#xff0c;5%等。 2、选型方法 ①、优先考虑阻值&#xff0c;对于不常见的阻值&#xff0c;可以通过电阻的…

作者头像 李华