news 2026/6/15 17:23:53

天机学堂项目文档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/6/15 9:04:16

构建具有多任务学习能力的AI Agent

构建具有多任务学习能力的AI Agent关键词&#xff1a;多任务学习、AI Agent、机器学习、深度学习、强化学习、模型架构、任务协同摘要&#xff1a;本文围绕构建具有多任务学习能力的AI Agent展开&#xff0c;详细阐述了多任务学习和AI Agent的核心概念及联系&#xff0c;深入剖…

作者头像 李华
网站建设 2026/6/15 16:22:05

UE5 材质-20:

&#xff08;91&#xff09; &#xff08;92&#xff09; 谢谢

作者头像 李华
网站建设 2026/6/15 15:15:57

智能科学毕设容易的题目集合

0 选题推荐 - 云计算篇 毕业设计是大家学习生涯的最重要的里程碑&#xff0c;它不仅是对四年所学知识的综合运用&#xff0c;更是展示个人技术能力和创新思维的重要过程。选择一个合适的毕业设计题目至关重要&#xff0c;它应该既能体现你的专业能力&#xff0c;又能满足实际应…

作者头像 李华
网站建设 2026/6/13 2:23:42

GitLab社区版(CE)本地部署

一、环境准备 1、硬件要求 内存和硬件配置&#xff1a;至少 2 核 CPU 和 4G 内存&#xff0c;64位系统&#xff0c;以确保能够运行。建议搭配4核以上CPU、16G以上内存。 2、 操作系统选择 本教程基于win11专业版系统利用Docker进行安装建议使用win11系统&#xff0c;省去一…

作者头像 李华
网站建设 2026/6/13 13:37:34

【QML开发避坑宝典】:为什么你的量子模型总在VSCode中崩溃?

第一章&#xff1a;量子机器学习的 VSCode 调试在开发量子机器学习模型时&#xff0c;调试是确保算法正确性和性能优化的关键环节。Visual Studio Code&#xff08;VSCode&#xff09;凭借其强大的扩展生态和集成调试功能&#xff0c;成为量子计算开发者的重要工具。通过配置 P…

作者头像 李华
网站建设 2026/6/9 10:34:49

空间转录组的R语言实战(从入门到精通):单细胞分析全流程大公开

第一章&#xff1a;空间转录组的R语言单细胞分析概述空间转录组技术结合了传统转录组测序与组织空间位置信息&#xff0c;使研究者能够在保留细胞空间分布的前提下解析基因表达模式。利用R语言进行单细胞数据分析&#xff0c;已成为该领域主流的分析手段&#xff0c;得益于其强…

作者头像 李华