【Redis】签到点赞和UV统计

news2024/11/28 8:49:17

Redis签到点赞和UV统计

点赞

点赞功能分析

需求:

  1. 同一个用户只能点赞一次,再次点击则取消点赞
  2. 如果当前用户已经点赞,则点赞按钮高亮显示(前端判断字段isLike属性)

实现步骤:

  1. 利用Redis的set集合判断是否点赞过,将用户id保存到set中
  2. 判断当前登录用户是否点赞过,赋值给isLike字段
  3. 通过Redis的set集合中Scard命令获取成员个数,即点赞次数

业务实现

LikedDTO

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LikedDTO {
    /**
     * 点赞数量
     */
    long likedSum;
    /**
     * 用户是否点过赞
     */
    Boolean isLiked;
}

点赞操作

// 点赞操作
@Override
public String doLike() {
    String key = "RedisSessionDemo:liked";
    String phone = UserHolder.getUser().getPhone();
    // 查询是否点赞过
    Boolean isLiked = redisTemplate.opsForSet().isMember(key, phone);
    if (BooleanUtil.isTrue(isLiked)) {
        // 点赞过 -> 取消点赞
        redisTemplate.opsForSet().remove(key, phone);
        return "取消点赞成功";
    }
    // 没点赞过 -> 点赞
    redisTemplate.opsForSet().add(key, phone);
    return "点赞成功";
}

获取点赞数据

// 获取点赞数据
@Override
public LikedDTO getLiked() {
    String key = "RedisSessionDemo:liked";
    Long likedNum = redisTemplate.opsForSet().size(key);
    if (likedNum == null) {
        likedNum = 0L;
    }
    UserDTO user = UserHolder.getUser();
    Boolean isLiked = false;
    if (user != null) {
        isLiked = redisTemplate.opsForSet().isMember(key, user.getPhone());
    }
    return new LikedDTO(likedNum, isLiked);
}

点赞排行

功能分析

点赞排行:类似朋友圈的点赞列表,按照点赞的先后顺序展示头像等信息。

使用 sorted set 结构,将点赞的时间戳作为分数值记录。

功能实现

修改点赞函数

// 获取点赞数据
@Override
public LikedDTO getLiked2() {
    String key = "RedisSessionDemo:liked";
    Long likedNum = redisTemplate.opsForZSet().size(key);
    if (likedNum == null) {
        likedNum = 0L;
    }
    UserDTO user = UserHolder.getUser();
    boolean isLiked = false;
    if (user != null) {
        Double score = redisTemplate.opsForZSet().score(key, user.getPhone());
        isLiked = (score != null && score > 0);
    }
    return new LikedDTO(likedNum, isLiked);
}

// 点赞操作
@Override
public String doLike2() {
    String key = "RedisSessionDemo:liked";
    String phone = UserHolder.getUser().getPhone();
    // 查询是否点赞过
    Double isLiked = redisTemplate.opsForZSet().score(key, phone);
    if (isLiked != null && isLiked > 0) {
        // 点赞过 -> 取消点赞
        redisTemplate.opsForZSet().remove(key, phone);
        return "取消点赞成功";
    }
    // 没点赞过 -> 点赞
    redisTemplate.opsForZSet().add(key, phone, System.currentTimeMillis());
    return "点赞成功";
}

获取点赞列表

// 获取点赞列表
@Override
public List<String> getLikedList() {
    String key = "RedisSessionDemo:liked";
    // 获取所有元素
    Set<String> set = redisTemplate.opsForZSet().range(key, 0, -1);
    if (set != null) {
        return new ArrayList<>(set);
    }
    return Collections.emptyList();
}

用户签到

BitMap用法

我们按月来统计用户签到信息,签到记录为1,未签到则记录为0。

把每一个bit位对应当月的每一天,形成了映射关系。用0和1标示业务状态,这种思路就称为位图(BitMap)。

Redis中是利用string类型数据结构实现BitMap,因此最大上限是512M,转换为bit则是 2 32 2^{32} 232 个bit位。

BitMap的操作命令有:

  1. SETBIT:向指定位置(offset)存入一个0或1
  2. GETBIT :获取指定位置(offset)的bit值
  3. BITCOUNT :统计BitMap中值为1的bit位的数量
  4. BITFIELD :操作(查询、修改、自增)BitMap中bit数组中的指定位置(offset)的值
  5. BITFIELD_RO :获取BitMap中bit数组,并以十进制形式返回
  6. BITOP :将多个BitMap的结果做位运算(与 、或、异或)
  7. BITPOS :查找bit数组中指定范围内第一个0或1出现的位置

实现签到功能

因为BitMap底层是基于String数据结构,因此其操作也都封装在字符串相关操作中了。

public Boolean sign() {
    String phone = UserHolder.getUser().getPhone();
    Date date = new Date();
    String yearAndMonth = new SimpleDateFormat("yyyy:MM").format(date);
    String key = "RedisSessionDemo:user:sign:" + phone + ":" + yearAndMonth;
    int day = Integer.parseInt(new SimpleDateFormat("DD").format(date));
    // 实现签到
    redisTemplate.opsForValue().setBit(key, day, true);
    return true;
}

签到统计

连续签到:从最后一次签到开始向前统计,直到遇到第一次未签到为止的签到次数

封装SignData类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class SignData {
    // 月签到次数
    Integer MonthTimes;
    // 月连续签到次数
    Integer ContinuousTimes;
}

业务实现

@Override
public SignData signdata() {
    // 获取 bitmap
    String phone = UserHolder.getUser().getPhone();
    Date date = new Date();
    String yearAndMonth = new SimpleDateFormat("yyyy:MM").format(date);
    String key = "RedisSessionDemo:user:sign:" + phone + ":" + yearAndMonth;
    int day = Integer.parseInt(new SimpleDateFormat("DD").format(date));
    List<Long> list = redisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(day + 1)).valueAt(0));
    if (list == null || list.isEmpty()) {
        return new SignData(0, 0);
    }
    Long sign = list.get(0);
    if (sign == null) {
        return new SignData(0, 0);
    }

    // 统计计算
    int MonthTimes = 0;
    int ContinuousTimes = 0;
    boolean isContinuous = true;
    while (sign != 0) {
        // 连续签到
        if (isContinuous) {
            if ((sign & 1) == 1) {
                ContinuousTimes++;
            } else {
                isContinuous = false;
            }
        }
        // 月签到次数
        if ((sign & 1) == 1) {
            MonthTimes++;
        }
        sign = sign >> 1;
    }
    return new SignData(MonthTimes, ContinuousTimes);
}

UV统计

HyperLogLog

  • UV:全称Unique Visitor,也叫独立访客量,是指通过互联网访问、浏览这个网页的自然人。1天内同一个用户多次访问该网站,只记录1次。
  • PV:全称Page View,也叫页面访问量或点击量,用户每访问网站的一个页面,记录1次PV,用户多次打开页面,则记录多次PV。往往用来衡量网站的流量。

UV统计在服务端做会比较麻烦,因为要判断该用户是否已经统计过了,需要将统计过的用户信息保存。但是如果每个访问的用户都保存到Redis中,数据量会非常恐怖。

Hyperloglog(HLL)是从Loglog算法派生的概率算法,用于确定非常大的集合的基数,而不需要存储其所有值。相关算法

Redis中的HLL是基于string结构实现的,单个HLL的内存永远小于16kb,内存占用低的令人发指!

作为代价,其测量结果是概率性的,有小于0.81%的误差。不过对于UV统计来说,这完全可以忽略。

  1. 作用:做海量数据的统计工作
  2. 优点:内存占用极低、性能非常好
  3. 缺点:有一定的误差

业务实现

@Test
void hyperlogTest() {
    for (int i = 0; i < 100; i++) {
        stringRedisTemplate.opsForHyperLogLog().add("hyperlogTest", "user-" + i);
    }
    Long size = stringRedisTemplate.opsForHyperLogLog().size("hyperlogTest");
    System.out.println(size);
}

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

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

相关文章

安科瑞光伏、储能一体化监控及运维解决方案

上海安科瑞电气股份有限公司 胡冠楠 咨询家&#xff1a;“Acrelhgn”&#xff0c;了解更多产品资讯 前言 今年以来&#xff0c;在政策利好推动下光伏、风力发电、电化学储能及抽水蓄能等新能源行业发展迅速&#xff0c;装机容量均大幅度增长&#xff0c;新能源发电已经成为新…

Nicn的刷题日常之带空格直角三角形图案

1.题目描述 描述 KiKi学习了循环&#xff0c;BoBo老师给他出了一系列打印图案的练习&#xff0c;该任务是打印用“*”组成的带空格直角三角形图案。 输入描述&#xff1a; 多组输入&#xff0c;一个整数&#xff08;2~20&#xff09;&#xff0c;表示直角三角形直角边的长度&am…

STM32F407移植OpenHarmony笔记7

继上一篇笔记&#xff0c;成功启动了liteos_m内核&#xff0c;可以创建线程了&#xff0c;也能看到shell控制台了。 今天研究文件系统&#xff0c;让控制台相关文件命令如mkdir和ls能工作。 liteos_m内核支持fatfs和littlefs两个文件系统&#xff0c; fatfs适用于SD卡&#xff…

【Tomcat与网络6】 Tomcat是如何扩展Java线程池的?

目录 1.Java 的线程池 2.Tomcat 的线程池 学习Tomcat的时候&#xff0c;有很多绚丽的技术值得我们学习&#xff0c;但是个人认为Tomcat的线程池扩展是最值得研究的一个部分&#xff0c;线程池的应用太广了&#xff0c;也重要了&#xff0c;Java原生线程池的特征我相信很多人都…

【Redis】一文搞懂redis的所有知识点

目录 1. 什么是Redis&#xff1f;它主要用来什么的&#xff1f; 2.说说Redis的基本数据结构类型 2.1 Redis 的五种基本数据类型​编辑 2.2 Redis 的三种特殊数据类型 3. Redis为什么这么快&#xff1f;​编辑 3.1 基于内存存储实现 3.2 高效的数据结构 3.3 合理的数据编…

Python 中常用图像数据结构

&#xff08;原文&#xff1a;https://blog.iyatt.com/?p13222 &#xff09; 1 测试环境 Python 3.12.1 numpy 1.26.3 opencv-python 4.9.0.80 pillow 10.2.0 matplotlib 3.8.2 注&#xff1a; 基于 2022.1.16 和 2022.4.9 的三篇博文再次验证并重写&#xff0c;原文已删…

嵌入式学习第十五天

内存管理: 1.malloc void *malloc(size_t size); 功能: 申请堆区空间 参数: size:申请堆区空间的大小 返回值: 返回获得的空间的首地址 失败返回NULL 2.free void free(void *ptr); 功能: 释放堆区空间 注…

复刻桌面小电视【包含代码分析】

宗旨&#xff1a;开源、分享、学习、进步&#xff0c;生命不息&#xff0c;折腾不止。 复刻小电视 感谢各位大佬的开源项目&#xff0c;让我有了学习的机会&#xff0c;如果侵权&#xff0c;请联系我删除。本人能力有限&#xff0c;如果有什么不对的地方&#xff0c;欢迎指正…

Vue3-Composition-API(二)

一、computed函数使用 1.computed 在前面我们讲解过计算属性computed&#xff1a;当我们的某些属性是依赖其他状态时&#xff0c;我们可以使用计算属性来处理 在前面的Options API中&#xff0c;我们是使用computed选项来完成的&#xff1b; 在Composition API中&#xff0c…

《C程序设计》上机实验报告(四)之一维数组

1.运行程序 #include <stdio.h> void main( ) { int a[5],i,j; for(i1;i<5;i) a[i]0; for(i1;i<5;i) for(j1;j<5;j) a[j]a[i]1; printf("%d %d\n",a[0],a[3]); } 要求&#xff1a; &#xff08;1&#xff09;输入并调试上述源程序&#xff0c;…

安装 vant-ui 实现底部导航栏 Tabbar

本例子使用vue3 介绍 vant-ui 地址&#xff1a;介绍 - Vant 4 (vant-ui.github.io) Vant 是一个轻量、可定制的移动端组件库 安装 通过 npm 安装&#xff1a; # Vue 3 项目&#xff0c;安装最新版 Vant npm i vant # Vue 2 项目&#xff0c;安装 Vant 2 npm i vantlatest-v…

数据可视化 pycharts实现中国各省市地图数据可视化

自用版 数据格式如下&#xff1a; 运行效果如下&#xff1a; import pandas as pd from pyecharts.charts import Map, TreeMap, Timeline, Page, WordCloud from pyecharts import options as opts from pyecharts.commons.utils import JsCode from pyecharts.globals im…

api接口1688商品详情接口采集商品详情数据商品价格详情页数据可支持高并发调用演示示例

接入1688商品详情API接口的步骤如下&#xff1a; 注册账号&#xff1a;首先&#xff0c;你需要在1688开放平台注册一个账号。 创建应用&#xff1a;登录后&#xff0c;在控制台中找到“我的应用”&#xff0c;点击“创建应用”。 获取API密钥&#xff1a;创建应用后&#xff…

C语言数据结构之二叉树

少年恃险若平地 独倚长剑凌清秋 &#x1f3a5;烟雨长虹&#xff0c;孤鹜齐飞的个人主页 &#x1f525;个人专栏 &#x1f3a5;前期回顾-栈和队列 期待小伙伴们的支持与关注&#xff01;&#xff01;&#xff01; 目录 树的定义与判定 树的定义 树的判定 树的相关概念 树的运用…

网络工程师必学知识:2、IPv4和IPv6地址划分

网络工程师必学知识&#xff1a;2、IPv4和IPv6地址划分 1.概述&#xff1a;2.IPv4&#xff1a;地址划分&#xff1a;有类划分&#xff0c;无类划分。一、有类划分&#xff1a;分为5类。ABCDE&#xff0c;掩码分别位8、16、24、28、27取值范围&#xff1a;出类别bit不变&#xf…

我是赵士杰,自述我的 Java 之旅:四年编码,千言万语中成长

你好我的朋友&#xff0c;请先容许我作一个简单介绍&#xff1a;我是赵士杰&#xff0c;一名 Java 攻城狮&#xff0c;欢迎关注我的微信公众号【技术人阿杰】。 不知不觉中&#xff0c;我在撰写技术博客领域已经投入了四年的精力&#xff0c;这也让我从一个默默无名之辈成长为了…

种草日记|林曦老师的冬日好物分享

冬天将尽春天就要来了&#xff0c;换季的时候最容易引起皮肤干燥、头发毛躁不舒服的问题&#xff0c;今天就来说说林曦老师推荐的冬日护理爱用好物。大家都要“如婴儿乎”&#xff0c;照顾好自己哦&#xff5e;      1、Aco甘油保湿霜    Aco甘油保湿霜好大一罐&#x…

《Vue3 基础知识》 使用 GoGoCod 升级到Vue3+ElementPlus 适配处理

此篇为 《Vue2ElementUI 自动转 Vue3ElementPlus&#xff08;GoGoCode&#xff09;》 的扩展&#xff01; Vue3 适配 Vue3 不兼容适配 Vue 3 迁移指南 在此&#xff0c;本章只讲述项目或组件库中遇到的问题&#xff1b; Vue3 移除 o n &#xff0c; on&#xff0c; on&#…

【Web前端实操21】商城官网_白色导航

今日份实现白色导航栏部分&#xff0c;也就是第三部分&#xff0c;效果如图中划线所示&#xff1a; 本次实现代码如之前的全局样式不再赘述&#xff0c;如有需要可以去我博客的Web前端实操19或者20自行查看。 本次主要更新mi.css和index.htm。 实现导航栏所需要的CSS样…

【图解面试】深入解析数据类型转换

将值从一种数据类型转换到另一种数据类型通常称为数据类型转换。在面试过程中大多数都是以代码输出题出现&#xff0c;但是要了解到具体的转换规则&#xff0c;彻底搞懂底层原理&#xff0c;才能应对变来变去的值类型~ 转布尔类型 Boolean类型有两个字面值&#xff1a; true …