redis后台实现投票功能

2023-11-11

原创文章,转载请注明出处https://blog.csdn.net/qq_41969845/article/details/108406059

一、前言

本文以投票功能为例,从实际例子中熟练掌握redis的应用。阅读本文需要有一定的Java基础和对redis数据结构的了解,如果Java不太行的同学建议关注文章末尾的公众号,对redis数据结构不太了解的同学可以回看我的上一篇文章:详谈redis数据结构

二、投票功能的业务逻辑

大家肯定在大学时期经常在班级QQ群内遇到一些投票的活动。显而易见,投票是一个逻辑很清晰的功能,首先,投票发起人发起投票,然后各位用户可以进行投票,在本案例中票种分为“赞成票”(approval)和“反对票”(against)。以下是每一次投票活动的数据结构:

字段名 对应信息
title 投票活动的标题
content 投票活动的内容
postTime 投票活动的发布时间
approval 支持票数
against 反对票数

那么在这里有几个问题需要读者思考一下:
1、如何避免同一个人既投反对票又投赞成票
2、如何避免同一个人多次投赞成票或反对票
3、如何避免投票者选投不存在的活动
4、如何记录哪位投票者为哪个活动投了何种票
~~~~~~~~~~~~~~~~~~~~~~~~~
先思考一下然后继续往下读吧~~

三、具体实现步骤

具体实现的部分将以各自的功能函数来进行讲解,每一个功能都对应着一个功能函数,或调用其他函数。

3.1、发起投票 initiateVote
3.1.1 、投票活动名

首先,每个投票活动要有一个自己的投票活动名,这里使用"activity:"+activityId进行拼接,其中activityId使用redis中的字符串类型,可以进行自增操作,因此每次发起一个新的投票活动,都会有不同的投票活动名。

//生成一个投票活动的ID,自增
        String activityId = String.valueOf(conn.incr("activityId:"));
        String activity = "activity:" + activityId;
3.1.2 、保存投票活动名

为了后续方便查看我们发起了哪些投票活动,也为了方便判断哪些投票活动存在以决定让不让投票者对该未知存在的活动进行投票,我们需要将投票活动名activity保存起来,存在一个activitySet集合内。

//将活动名存进activityList中
        conn.sadd("activitySet",activity);
3.1.3 、保存投票活动的相关数据

每一个投票活动都有以下活动数据

字段名 对应信息
title 投票活动的标题
content 投票活动的内容
postTime 投票活动的发布时间
approval 支持票数
against 反对票数

其中title和content是从main()函数中传递过来的,postTime是发起活动时的时间戳,approval和against默认为0,后续随着投票者的票数改变。

// 发布时间
        long now = System.currentTimeMillis() / 1000;
        //投票活动的相关数据
        HashMap<String, String> activityData = new HashMap<>();
        activityData.put("title", title);
        activityData.put("content", content);
        activityData.put("postTime", String.valueOf(now));
        activityData.put("approval", "0");
        activityData.put("against", "0");
        conn.hmset(activity, activityData);

将投票活动的所有信息存入HashMap中,然后利用hmset命令存入activity散列中。最后在initiateVote()的结尾处返回activityId,以便在主函数中使用。

3.1.4 、主函数中发起投票的部分

由上可知,主函数需要给initiateVote()传入conn,title,content。之后initiateVote()函数需要给主函数传回activityId。

String activityId = initiateVote(conn, "投票标题", "投票内容:北京大学是个好大学吗?");//发布投票
System.out.println("发起了一个新的投票活动,活动Id为:" + activityId);

到这里,发起投票活动的功能部分已经实现了,运行程序查看在这里插入图片描述
说明投票活动已经被发起。

3.2 、查看投票活动的数据getActivityData()

上一小节讲到发起投票活动,尽管控制台输出了活动Id,但是活动的标题和活动内容都没有显示出来,活动的赞成票数和反对票数也没有展示出来,因此,我们需要一个可以查看投票活动数据的方法。

 //查看投票活动数据
    public static void getActivityData(Jedis conn, String activity) {
        System.out.println("投票活动相关信息如下:");
        Map<String, String> activityData = conn.hgetAll(activity);
        for (Map.Entry<String, String> entry : activityData.entrySet()) {
            System.out.println("  " + entry.getKey() + " : " + entry.getValue());
        }
    }

使用hgetall命令取出activity散列中所有activity数据存于map中,然后利用entrySet遍历map取出所有键值对。
在主函数中修改代码,让程序在发起投票活动之后调用一下getActivityData(),运行程序试一下。
getActivityData
控制台输出了我们刚刚创建的Id为3的投票活动信息。

3.3、查看已发起的投票活动

因为投票系统可以允许同时有多个投票活动存在,故而,必须在投票之前让投票者知晓系统中有哪些投票活动以便选择。在发起投票的方法中,我们已经将投票活动名activity存入了activitySet集合中,接下来,我们只需遍历该集合即可知晓已有的投票活动。

//取出所有的activity
    public static void getAllActivity(Jedis conn){
        System.out.println(conn.smembers("activitySet"));
    }

这个方法实现起来是很简单的,只需使用SET中的smembers命令,传入set-key即可。接下来运行程序来测试下:
getAllActivity
控制台输出了我们此前所发起的三次投票活动。

3.4、判断该投票活动是否存在

上节说到,为了让投票者方便选择投票活动,我们输出了所有投票活动名以供提示,但是这并不能保证程序的健壮性,还是有的投票者会输错投票Id选错活动。为此,我们必须在进行投票之前,对投票者选择的活动进行一个判断,若该活动存在则可进行投票,若该活动不在,则不允许投票。
本质上讲,其实就是判断用户输入的Id对应的活动名是否存在于我们存储活动名的集合中,所以我们应当很自然地想到使用SISMEMBER命令:

 public static boolean exists(Jedis conn, String activity) {
        //activity存在于activitySet
        if (conn.sismember("activitySet", activity)) {
            return true;
        } else {
            return false;
        }
    }

运行程序,测试下该方法:
exists
测试成功

3.5、选择活动进行投票voting()

前面两小节已经很好的为投票活动做了准备,让投票者可以知晓系统中有哪些投票活动,即便选择错误,我们的程序也会做出提示。那么本小节一共要实现的几个难点就在于:
(1)、防止同一投票者多次参加同一个投票活动
(2)、赞成票和反对票要分开存储
我们解决以上难题的方法就是,使用学号,在每一位投票者投一个活动时,在集合中记录下他的学号,下次再选择该活动时,通过集合查询就可以得知这位投票者已经为该活动投过票。
投票者参加投票时,先输入学号,再选择投票活动,然后选择要投的票种。以下是投票活动的流程图。
投票流程图

//选择活动进行投票
    public static void voting(Jedis conn, String voter, String activity) {
        int ticket = 0;
        System.out.println("你好" + voter + "请选择票种:");
        System.out.println("按下1:投赞成票");
        System.out.println("按下2: 投反对票");
        /**
         * 分割字符串,取出纯Id部分*/
        String activityId = activity.substring(activity.indexOf(':') + 1);
        /**
         *先判断赞成票名单和反对票名单内都没有,则说明具备投票资格
         * 按1,则在赞成票名单中加上名字,赞成票自增
         * 按2,则在反对票名单中加上名字,反对票自增
         **/
        if (!conn.sismember("approvalVoter:" + activityId, voter) && !conn.sismember("againstVoter:" + activityId, voter)) {
            Scanner in = new Scanner(System.in);
            ticket = in.nextInt();
            switch (ticket) {
                case 1:
                    conn.hincrBy(activity, "approval", 1);//赞成票+1
                    System.out.println(conn.sadd("approvalVoter:" + activityId, voter));//将投票者Id加入approvalVoter集合中
                    System.out.println(voter + "已为" + activity + "投赞成票");
                    break;
                case 2:
                    conn.hincrBy(activity, "against", 1);//反对票+1
                    conn.sadd("againstVoter:" + activityId, voter);//将投票者Id加入againstVoter集合中
                    System.out.println(voter + "已为" + activity + "投反对票");
                    break;
            }
        } else {
            System.out.println("你已为本活动投过一次票,不能重复投票");
        }
    }

四、整理代码

第三章中已经基本实现了本次投票系统的全部核心功能,是不是很简单?但是如果你一味复制我贴出来的代码你会发现,根本无法运行。是的,整篇文章是按照写代码时的思路来写的,所以并不是像一些教材书籍上面一味地去贴源代码。如果你不幸读到那种书,还是早日放弃吧。源码链接在文章末尾,快去下载下来,再仔细读一遍吧~
那么接下来,为了让我们的程序能有点实用的感觉,我们将在主函数中加上一层循环,方便我们可以一直发号施令。

public static void main(String[] args) {
        //连接,演示作用,IP地址和端口号请改成自己的
        Jedis conn = new Jedis("IP地址", 端口号);
        conn.select(1);//redis有16个数据库,这里选用1号数据库
        tips();//界面提示
        int a = 0;
        String activity = null;//定义activity的键
        Scanner in = new Scanner(System.in);
        while ((a = in.nextInt()) != -1) {
            switch (a) {
                case 1: //发起一个新的投票活动
                    System.out.println("1#");
                    String activityId = initiateVote(conn, "投票标题", "投票内容:北京大学是个好大学吗?");//发布投票
                    System.out.println("发起了一个新的投票活动,活动Id为:" + activityId);
                    getActivityData(conn, "activity:" + activityId);
                    break;
                case 2://展示已存在的投票活动
                    System.out.println("2#");
                    getAllActivity(conn);
                    break;
                case 3://选择活动进行投票
                    System.out.println("3#");
                    System.out.println("请输入你的学号:");
                    String voter = "voter:" + in.nextInt();
                    System.out.println("请输入你要参加的投票活动:");
                    String activityVoting = "activity:" + in.nextInt();
                    if (exists(conn, activityVoting)) {
                        voting(conn, voter, activityVoting);
                        System.out.println("投票结束");
                    } else {
                        System.out.println("不存在该投票活动");
                    }
                    break;
                case 4://查看投票活动的各种数据
                    System.out.println("4#");
                    System.out.println("请输入你想查看的投票活动Id");
                    activity = "activity:" + in.nextInt();
                    getActivityData(conn, activity);
                    break;
                default:
                    tips();
            }

        }


    }

其中的tips()函数其实只不过是一些提示性语句,为了防止main函数太过冗长就单独提取出来了。

五、总结

其实以上功能也可以使用关系型数据库来进行实现,对于许多初学者,可能关系型数据库更方便更直观,但是redis的效率是相当高的,我们的投票活动数据访问量微乎其微,暂时看不出来差别,一旦数据量上去了,redis的优势就显而易见了。
本文通过一个简单的投票系统来使用了一下redis的各种数据结构,其实这个系统还有许多可以完善的地方,比如设置一个过期时间,超过这个时间,投票活动的票数就固定下来,不允许再进行投票。再比如,已经为某个活动投了反对票,但是想改为赞成票要怎么实现。一切都有待读者去研究
下面是本次源码的链接:
vote源码下载

对Java系列知识感兴趣的朋友可以加入QQ群
慧梦软件开发技术联盟:952317701
更多系列文章在java高级程序开发微信公众号
Java高级开发技术

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

redis后台实现投票功能 的相关文章

  • 如何让客户端下载动态生成的非常大的文件

    我有一个导出功能 可以读取整个数据库并创建一个包含所有记录的 xls 文件 然后文件被发送到客户端 当然 导出完整数据库的时间需要大量时间 并且请求很快就会以超时错误结束 处理这种情况的最佳解决方案是什么 例如 我听说过使用 Redis 创
  • 如何使用Spring Cache处理redis异常?

    我目前正在开发一个包含 Spring Data Redis 和 Spring Cache 的项目 在spring data redis中 我使用redis模板调用redis 我在 try catch 块中处理 redis 模板抛出的所有异常
  • Redis键空间事件不触发

    我有两个 Redis 客户端 在一个文件中我有一个简单的脚本设置并删除了 Redis 键 var redis require redis var client redis createClient 6379 127 0 0 1 client
  • WSL Redis 遇到系统尚未使用 systemd 作为 init 系统(PID 1)启动。无法操作[已关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我正在尝试遵循本文中讨论的 Redis 安装过程article https www digitalocean com community
  • 库存管理系统的 SQL 与 NoSQL

    我正在开发一个基于 JAVA 的网络应用程序 主要目的是拥有在多个称为渠道的网站上销售的产品的库存 我们将担任所有这些渠道的管理者 我们需要的是 用于管理每个渠道的库存更新的队列 库存表 其中包含每个通道上分配的正确快照 将会话 ID 和其
  • redis-cli 重定向到 127.0.0.1

    我在PC1上启动Redis集群 然后在PC2上连接它 当需要重定向到另一个集群节点时 它会显示Redirected to slot 7785 located at 127 0 0 1 但应该显示Redirected to slot 7785
  • SignalR 无法连接到 SSL 上的 Azure Redis

    我目前在 Azure 上托管我的 redis 缓存服务器 并让 signalR 依赖它作为骨干 使用以下内容 GlobalHost DependencyResolver UseRedis 服务器 端口 密码 eventKey 这可以在端口
  • 无法启动redis.service:单元redis-server.service被屏蔽

    我在 ubuntu 16 04 上安装了 Redis 服务器 但是当我尝试使用启动redis服务时 sudo systemctl start redis 我收到消息 Failed to start redis service Unit re
  • Redis SYNC 套接字上的错误情况:连接被拒绝

    在我的 django 应用程序中使用 celery 和 redis 一切都工作正常 直到我遇到了问题 redis 文件的位置已更改 redis 无法访问它们 经过查找 原来这是由于网络随机攻击造成的 需要添加confg 我添加文件后 一段时
  • 使用 Celery 通过 Gevent 进行实时、同步的外部 API 查询

    我正在开发一个 Web 应用程序 该应用程序将接收用户的请求 并且必须调用许多外部 API 来编写对该请求的答案 这可以直接从主 Web 线程使用 gevent 之类的东西来扇出请求来完成 或者 我在想 我可以将传入的请求放入队列中 并使用
  • 为什么Redis中没有有序的hashmap?

    Redis 数据类型 http redis io topics data types包括排序集 http redis io topics data types intro sorted sets以及其他用于键值存储的必要数据结构 但我想知道
  • 在 Redis 上为 Django 和 Express.js 应用程序共享会话存储

    我想创建一个包含一些登录用户的 Django 应用程序 另一方面 由于我想要一些实时功能 所以我想使用 Express js 应用程序 现在的问题是 我不希望身份不明的用户访问 Express js 应用程序的日期 因此 我必须在 Expr
  • 如何在Redis中只保存一个数据库?

    我是 Redis 新手 有一个与备份相关的问题 目前 我有一个实例在 Windows 服务器上运行 在这个实例中 我当前有一项 工作 将数据存储在一个数据库中 我不想备份这些数据 我必须创造一份新工作 我的第一个想法是将数据存储在另一个数据
  • Amazon Elasticache Redis 集群 - 无法获取端点

    我需要获取 Amazon Elasticache 中 Redis 集群的终端节点 以下代码适用于 Memcached 集群 但不适用于 Redis import com amazonaws auth AWSCredentials impor
  • 节点应用程序之间共享会话?

    我目前有两个独立的节点应用程序在两个不同的端口上运行 但共享相同的后端数据存储 我需要在两个应用程序之间共享用户会话 以便当用户通过一个应用程序登录时 他们的会话可用 并且他们似乎已登录到另一个应用程序 在本例中 它是一个面向公众的网站和一
  • 当 Jedis 与 Spring Data 一起使用时,为什么数据会以奇怪的键存储在 Redis 中?

    我将 Spring Data Redis 与 Jedis 一起使用 我正在尝试存储带有密钥的哈希值vc list id 我能够成功插入到redis 但是 当我使用 redis cli 检查密钥时 我没有看到密钥vc 501381 相反我看到
  • docker-compose:容器之间的 Redis 连接被拒绝

    我正在尝试设置一个 docker compose 文件 该文件旨在替换运行多个进程 RQ 工作线程 RQ 仪表板和 Flask 应用程序 的单个 Docker 容器解决方案导师 http supervisord org 主机系统是 Debi
  • 如何使用redis发布/订阅

    目前我正在使用node js和redis来构建应用程序 我使用redis的原因是因为发布 订阅功能 该应用程序只是在用户进入用户或离开房间时通知经理 function publishMsg channel mssage redisClien
  • 由于配置文件错误,无法启动 Redis 服务器

    我刚刚按照此处的说明安装了 Redis http redis io download http redis io download 当我运行 redis server redis conf 时出现以下错误 FATAL CONFIG FILE
  • Redis 中存储整数和字符串的区别

    这两个命令有什么区别吗 LPUSH myset 123 LPUSH myset 123 我想存储大约 500 万个整数 并且我想以最有效的方式做到这一点 不 没有什么区别 两者都存储为字符串 从redis io http redis io

随机推荐

  • java基础面试题系列(71 - 80)

    20200714 by 1z 请你说明HashMap 和 HashTable的区别 1 是否同步 HashMap是非同步的 HashTable是同步的 2 继承体系 HashTable继承自Dictionary HashMap继承自Abst
  • Linux下多进程通信(signal,pipe)

    操作系统实验导航 实验一 银行家算法 https blog csdn net weixin 46291251 article details 115384510 实验二 多级队列调度和多级反馈队列调度算法 https blog csdn n
  • GpuMat ROI

    在引用GpuMat数据的ROI时 需要保证该数据在Gpu 内存中存储是连续的 使用gpu createContinuous创建连续空间 cuda GpuMat dst pyr laplace tmp dst pyr laplace gpu
  • LL(1)文法构造FIRST、FOLLOW、分析表并分析

    一 实验目的 学生运用编译原理的知识在实验技能和方法自行设计实验方案并加以实现 二 使用仪器 器材 计算机一台 操作系统 Windows10 编程软件 Intellij IDEA 三 实验内容及原理 1 实验内容 输入任意一个正确的文法G
  • Windows音量变化通知 - 系统音量监控

    Windows音量变化通知 系统音量监控 Endpoint Volume Controls 1 实现IAudioEndpointVolumeCallback接口 2 主函数 总结 参考 Endpoint Volume Controls 本次
  • 婚姻好不好,嫁给谁很重要

    都说幸福的婚姻是相似的 不幸的婚姻各有各的不幸 事实上 那些不幸的婚姻 追根究底不过都是找错了人 婚姻好不好 关键就在于嫁给谁 因为 值得相信的从来不是感情 而是人 人若靠谱 婚姻便可靠 人若靠不住 婚姻迟早生变 这世上 有的夫妻恩恩爱爱
  • 一次发生在JVM新生代和老年代的GC过程简述

    首先 我们假设程序当前的堆空间的情况如下 然后 程序在运行过程中 开始了我们的第一次YoungGC 年轻代GC 得到如下的图 通过这次的GC 我们的2 3 4对象都被回收了 只有1对象得到了保留 进入了S1 幸存者区 然后我们的程序在运行的
  • Java 通过Soap方式调用WebService接口

    import org apache commons lang3 StringEscapeUtils import org apache http HttpEntity import org apache http client config
  • 短视频seo抖音矩阵源码开发搭建技术解析

    一 短视频seo抖音矩阵源码开发需要考虑以下几个方面 技术选型 选择合适的开发语言 框架和数据库 常用的开发语言有Java PHP等 常用的框架有Spring Django等 常用的数据库有MySQL MongoDB等 服务器的选择 根据应
  • 如何在 NodeJs 中上传、处理和存储文件:分步手册

    存储文件有三种基本方法 1 直接将其存储在数据库中 2 将其存储在文件系统中并将路径保存到数据库 3 将其存储在某些云存储中 例如 Amazon S3 Google Cloud Storage 或 Microsoft Azure Blob
  • 去除自定义AlertDialog黑边

    http blog csdn net mwj 88 article details 45482421 1 现象描述 html view plain copy View view LayoutInflater from getActivity
  • java学习笔记——day1

    java笔记 字面量 变量 数据类型 命名规则 类型转换 运算符operator API 程序的流程控制 数组 字面量 变量 字面量 计算机用来处理数据的 字面量就是告诉程序员 数据在程序中的书写格式 字符 单引号 一个字符 字符串 双引号
  • python+selenium自动化软件测试(第3章):unittes

    3 1 unittest简介 前言 python基础比较弱的 建议大家多花点时间把基础语法学好 这里有套视频 可以照着练习下 http pan baidu com s 1i44jZdb 密码 92fs 熟悉java的应该都清楚常见的单元测试
  • 分层测试(一):什么是分层测试?

    什么是分层测试 分层测试是通过对质量问题分类 分层来保证整体系统质量的测试体系 模块内通过接口测试保证模块质量 多模块之间通过集成测试保证通信路径和模块间交互质量 整体系统通过端到端用例对核心业务场景进行验证 用户体验通过手工测试确保无妨碍
  • Unity开发(2)建片草地

    文章目录 1 简述 2 创建 2 1 创建项目 2 2 进入开发窗体 3 建个地面 3 1 新建地面 3 2 调整地面大小 3 3 添加草地 3 3 1 初识Unity图片资源 3 3 2 添加图片资源 3 3 3 修改图片在场景中大小 1
  • C语言入门知识1(零基础新手适用)

    C语言入门知识1 零基础新手适用 程序语言 1 机器语言 机器语言是低级语言 是用01码来编写的二进制代码语言 2 汇编语言 汇编语言也是低级语言 是用英文字母和符号串编写的 3 高级语言 由于汇编语言依赖于硬件体系且符合较多 为了方便高级
  • Go中 defer的使用

    文章目录 简介 示例 使用场景 捕获异常 文件操作 简介 defer 是 Golang 中的一个非常有用的关键字 它用于注册延迟调用 也就是一个函数的执行被延迟到调用它的函数返回之后 常用于资源清理 异常处理等场景 示例 defer 是注册
  • python实现电子邮件编程

    一 几个专业名词 MUA MTA MDA 假设我们自己的电子邮件地址是me 163 com 对方的电子邮件地址是friend sina com 注意地址都是虚构的哈 现在我们用Outlook或者Foxmail之类的软件写好邮件 填上对方的E
  • C++提高8: 类模板成员函数类外实现和类模板分文件编写

    1 类模板成员函数类外实现 类外实现主要有三个关键点 作用域 识别T的数据类型 告诉编译器这是一个类模板 剩下的 就还是基础的类内声明类外定义实现了 直接上代码观察一下 include
  • redis后台实现投票功能

    原创文章 转载请注明出处https blog csdn net qq 41969845 article details 108406059 一 前言 本文以投票功能为例 从实际例子中熟练掌握redis的应用 阅读本文需要有一定的Java基础