当前位置: 首页 > article >正文

场景设计学习-积分系统

场景设计-积分系统

1.概念和规则

  • 积分:用户在网站的各种交互行为都可以产生积分,积分值与行为类型有关
  • 天梯榜:按照每个用户的总积分排序得到的排行榜,称为天梯榜。排名靠前的有奖励。天梯榜每个自然月为一个赛季,月初清零

具体的积分获取的细则通常如下:

积分获取规则
1. 签到规则
连续7天奖励10分  连续14天 奖励20  连续28天奖励40分, 每月签到进度当月第一天重置

2. 学习规则
每学习一小节,积分+10,每天获得上限50分

3. 交互规则(有效交互数据参与积分规则,无效数据会被删除)
- 写评价 每个课程只能评价一次,每日无上限
- 写问答 积分+5 每日获得上限为20分
- 写笔记 积分+3 每次被采集+2 每日获得上限为20分

2.页面原型

在这里插入图片描述

在这里插入图片描述

3.需要的接口统计

业务编号接口简述
签到1签到
2查询本月签到记录
积分3新增积分记录
4查询今日积分情况
排行榜5查询本赛季的积分排行榜
6查询赛季列表
7查询历史赛季积分排行榜

4.表结构设计

4.1签到

签到表主要记载,谁在什么时候,通过什么渠道进行了签到,还可以加上补签功能

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

4.2积分记录

积分记录,记录谁(用户),在什么时间,通过哪种方式,获得了多少积分

CREATE TABLE IF NOT EXISTS `points_record` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '积分记录表id',
  `user_id` bigint NOT NULL COMMENT '用户id',
  `type` tinyint NOT NULL COMMENT '积分方式:1-课程学习,2-每日签到,3-课程问答, 4-课程笔记,5-课程评价',
  `points` tinyint NOT NULL COMMENT '积分值',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_user_id` (`user_id`,`type`) USING BTREE,
  KEY `idx_create_time` (`create_time`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=41 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='学习积分记录,每个月底清零';

4.3排行榜

主体需要两个,一个是赛季表记录赛季开始时间和结束时间.另一个是排行榜,记录用户在某个赛季的排行

CREATE TABLE IF NOT EXISTS `points_board_season` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '自增长id,season标示',
  `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '赛季名称,例如:第1赛季',
  `begin_time` date NOT NULL COMMENT '赛季开始时间',
  `end_time` date NOT NULL COMMENT '赛季结束时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;

CREATE TABLE IF NOT EXISTS `points_board` (
  `id` bigint NOT NULL COMMENT '榜单id',
  `user_id` bigint NOT NULL COMMENT '学生id',
  `points` int NOT NULL COMMENT '积分值',
  `rank` tinyint NOT NULL COMMENT '名次,只记录赛季前100',
  `season` smallint NOT NULL COMMENT '赛季,例如 1,就是第一赛季,2-就是第二赛季',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `idx_season_user` (`season`,`user_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='学霸天梯榜';

5.实现思路

5.1签到功能

使用bitmap,每个月为每个用户生成一个独立的KEY,因此KEY中必须包含用户信息、月份信息,长这样sign:uid:xxx:20XX01

签到

把这一天的下位列表设置成true,关键代码如下:

LocalDate now = LocalDate.now(); 		
int offset = now.getDayOfMonth() - 1;
Boolean exists = redisTemplate.opsForValue().setBit(key, offset, true);

计算连续签到

  private int countSignDays(String key, int len) {
        // 1.获取本月从第一天开始,到今天为止的所有签到记录
        List<Long> result = redisTemplate.opsForValue()
                .bitField(key, BitFieldSubCommands.create().get(
                        BitFieldSubCommands.BitFieldType.unsigned(len)).valueAt(0));
        if (CollUtils.isEmpty(result)) {
            return 0;
        }
        int num = result.get(0).intValue();
        // 2.定义一个计数器
        int count = 0;
        // 3.循环,与1做与运算,得到最后一个bit,判断是否为0,为0则终止,为1则继续
        while ((num & 1) == 1) {
            // 4.计数器+1
            count++;
            // 5.把数字右移一位,最后一位被舍弃,倒数第二位成了最后一位
            num >>>= 1;
        }
        return count;
    }

具体解释:

  1. .bitField

    • Redis 的 BITFIELD 命令允许对 Bitmap 中的位域进行灵活的批量操作。
    • 它可以高效地读取、修改和操作指定范围的位数据。
  2. BitFieldSubCommands.create().get(...).valueAt(0)

    • create():创建 BitFieldSubCommands 对象,用于构造 Redis BITFIELD 命令。

    • get

      获取 Bitmap 中的位域值。

      • BitFieldType.unsigned(len):读取一个无符号整数,长度为 len 位。
    • valueAt(0):从偏移量 0 开始读取。

目的: 将从第 1 位到第 len 位的所有位数据读取出来,作为一个整体返回。例如,如果一个月有 31 天的签到数据,这里会读取连续 31 位并将其转为一个 Long 值。

根据连续签到天数计算奖励积分值:

可以定义一个枚举,避免用swtich case

public enum SignReward {
    SEVEN_DAYS(7, 10),
    FOURTEEN_DAYS(14, 20),
    TWENTY_EIGHT_DAYS(28, 40);

    private final int requiredDays;
    private final int points;

    SignReward(int requiredDays, int points) {
        this.requiredDays = requiredDays;
        this.points = points;
    }

    public static int calculateReward(int signDays) {
        // 按天数降序排列,优先匹配高奖励
        for (SignReward reward : values()) {
            if (signDays==reward.requiredDays) {
                return reward.points;
            }
        }
        return 0;
    }
}
查询签到记录

核心代码

   Byte[] arr = new Byte[dayOfMonth];
   int pos = dayOfMonth - 1;
   while (pos >= 0){
        arr[pos--] = (byte)(num & 1);
        // 把数字右移一位,抛弃最后一个bit位,继续下一个bit位
        num >>>= 1;
    }
    return arr;

使用Redis保存签到记录,那如果Redis宕机怎么办?

我们可以给Redis添加数据持久化机制,比如使用AOF持久化。这样宕机后也丢失的数据量不多,可以接受。

或者呢,我们可以搭建Redis主从集群,再结合Redis哨兵。主节点会把数据持续的同步给从节点,宕机后也会有哨兵重新选主,基本不用担心数据丢失问题。

当然,如果对于数据的安全性要求非常高。肯定还是要用传统数据库来实现的。但是为了解决签到数据量较大的问题,我们可能就需要对数据做分表处理了。或者及时将历史数据存档。

总的来说,签到数据使用Redis的BitMap无论是安全性还是数据内存占用情况,都是可以接受的。但是具体是选择Redis还是数据库方案,最终还是要看具体的要求来选择。

我个人觉得,使用bitmap优势是可以快速的统计,降低数据库的压力,查询操作可以由bitmap完成,而持久化操作可以异步入库

5.2积分功能

积分功能通常是其他业务进行操作,来通知积分系统进行添加积分操作,使用MQ来实现异步解耦。

如签到功能,在计算完积分之后,通知新增积分入库操作

但是入库并不是无脑进行入库,根据不同的业务类型,我们首先要查询当日积分上限,满足条件才能进行积分增加

这里有发现一个之前没用过的小技巧:

MyBatis-Plus 的确支持将 QueryWrapperLambdaQueryWrapper 作为参数传递到自定义的 SQL 方法中,这是一个非常实用的小技巧,可以大大简化动态查询条件的处理逻辑。

@Select("SELECT SUM(points) FROM points_record ${ew.customSqlSegment}")
Integer queryUserPointsByTypeAndDate(@Param(Constants.WRAPPER) QueryWrapper<PointsRecord> wrapper);

${ew.customSqlSegment}

  • 这是 MyBatis-Plus 提供的占位符,用于插入 QueryWrapper 动态生成的 SQL 条件。
  • 它会将 QueryWrapper 中构建的条件自动拼接到 SQL 中。

@Param(Constants.WRAPPER)

  • MyBatis-Plus 约定,QueryWrapper 的参数必须使用 @Param(Constants.WRAPPER) 标记,Constants.WRAPPER 是 MyBatis-Plus 提供的默认常量,值为 "ew"

http://www.kler.cn/a/521294.html

相关文章:

  • Deployment 部署 Pod 流程
  • Linux——线程首尾(各个小知识及理解)
  • 自然语言处理(NLP)入门:基础概念与应用场景
  • 智能码二维码赋能智慧工厂建设
  • 126周日复盘 (166)本周回顾
  • 毛桃病害分割数据集labelme格式212张6类别
  • [文献阅读] Unsupervised Deep Embedding for Clustering Analysis (DEC)(pytorch复现)
  • 网络安全 | F5-Attack Signatures-Set详解
  • Day38:移除列表中的元素
  • python3+TensorFlow 2.x(五)CNN
  • JS高阶 - day04
  • ubuntu取消定时锁定
  • 学院失物招领 app 的设计与实现
  • 计算机图形学实验练习(实验1.2-4.1AND补充实验12)
  • 【阅读笔记】基于整数+分数微分的清晰度评价算子
  • 数据的秘密:如何用大数据分析挖掘商业价值
  • Ubuntu 24.04 安装 NVIDIA Container Toolkit 全指南:让Docker拥抱GPU
  • for...in 和 Object.keys().forEach的区别
  • GO语言 链表(单向链表
  • 接口管理文档Yapi的安装与配置