原创文章,转载请注明出处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(),运行程序试一下。
控制台输出了我们刚刚创建的Id为3的投票活动信息。
3.3、查看已发起的投票活动
因为投票系统可以允许同时有多个投票活动存在,故而,必须在投票之前让投票者知晓系统中有哪些投票活动以便选择。在发起投票的方法中,我们已经将投票活动名activity存入了activitySet集合中,接下来,我们只需遍历该集合即可知晓已有的投票活动。
//取出所有的activity
public static void getAllActivity(Jedis conn){
System.out.println(conn.smembers("activitySet"));
}
这个方法实现起来是很简单的,只需使用SET中的smembers命令,传入set-key即可。接下来运行程序来测试下:
控制台输出了我们此前所发起的三次投票活动。
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;
}
}
运行程序,测试下该方法:
测试成功
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高级程序开发微信公众号