Skip to content

Instantly share code, notes, and snippets.

@alsritter
Created July 21, 2021 07:10
Show Gist options
  • Save alsritter/3814bb342483d078dd8ee045598675d9 to your computer and use it in GitHub Desktop.
Save alsritter/3814bb342483d078dd8ee045598675d9 to your computer and use it in GitHub Desktop.
package com.alsritter;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.ZParams;
import java.util.*;
/**
* @author alsritter
* @version 1.0
**/
public class VoteTest {
private static final int ONE_WEEK_IN_SECONDS = 7 * 86400; // 一个星期过期(一天为 86400 秒)
private static final int VOTE_SCORE = 432; // 投票的评分
private static final int ARTICLES_PER_PAGE = 25; // 每页文章数
public static void main(String[] args) {
new VoteTest().run();
}
public void run() {
Jedis conn = new Jedis("10.1.1.161", 6379);
conn.select(15);
// 首先发布几篇文章
String articleId = postArticle(conn, "username", "A title", "http://www.google.com");
String articleId02 = postArticle(conn, "username02", "A title02", "http://www.google.com");
String articleId03 = postArticle(conn, "username02", "A title02", "http://www.google.com");
String articleId04 = postArticle(conn, "username02", "A title02", "http://www.google.com");
System.out.println("发送的新文章 ID: " + articleId + "、" + articleId02 + "、" + articleId03 + "、" + articleId04);
System.out.println("打印第一篇 Hash 表里面的内容:");
// 打印第一篇的内容
Map<String, String> articleData = conn.hgetAll("article:" + articleId);
for (Map.Entry<String, String> entry : articleData.entrySet()) {
System.out.println(" " + entry.getKey() + ": " + entry.getValue());
}
System.out.println();
// 给文章投票
articleVote(conn, "other_user", "article:" + articleId);
articleVote(conn, "other_user02", "article:" + articleId);
articleVote(conn, "other_user03", "article:" + articleId02);
articleVote(conn, "other_user04", "article:" + articleId02);
articleVote(conn, "other_user05", "article:" + articleId02);
String votes = conn.hget("article:" + articleId02, "votes");
System.out.println("检查第二篇文章,现在它有了选票: " + votes);
assert Integer.parseInt(votes) > 1;
System.out.println("目前得分最高的文章是:");
List<Map<String, String>> articles = getArticles(conn, 1);
printArticles(articles);
assert articles.size() >= 1;
// 把文章丢进分组里面
addOrRemoveGroups(conn, articleId, new String[]{"new-group"}, new String[]{});
addOrRemoveGroups(conn, articleId02, new String[]{"new-group02"}, new String[]{});
addOrRemoveGroups(conn, articleId04, new String[]{"new-group"}, new String[]{});
System.out.println("我们将文章添加到一个新组,这个分组的文章包括: ");
articles = getGroupArticles(conn, "new-group", 1);
printArticles(articles);
assert articles.size() >= 1;
}
/**
* 为文章投票
*
* @param conn Redis 连接
* @param user 投票的用户 ID
* @param article 文章 ID article:id 格式的(例如:article:92617)
*/
public void articleVote(Jedis conn, String user, String article) {
// 计算文章截止投票时间
long cutoff = (System.currentTimeMillis() / 1000) - ONE_WEEK_IN_SECONDS;
// 检查是否还可以对文章进行投票(虽然使用散列也可以获取文章的发布时间,但是有序集合返回的文章发布时间为浮点数,可以不进行转换直接使用)
// zscore 方法:返回 key 处已排序集合的指定元素的分数
if (conn.zscore("time:", article) < cutoff) {
return;
}
// 从 article:id 标识符里面取出文章
String articleId = article.substring(article.indexOf(':') + 1);
/*
sadd 用于操作 Set 类型数据
这个 sadd 返回值:如果添加了新元素返回 1,如果元素已经是集合的成员返回 0
这里把用户 id 插入这个文章的已投票用户列表里面
这个判断:如果用户是第一次为这篇文章投票,那么增加这篇文章的投票数量和评分
*/
if (conn.sadd("voted:" + articleId, user) == 1) {
// 为集合 key 中成员 member 的 score 值加上增量,并调整位置,参数 score 可以是负数,表示递减
// 给评分排序文章表的有序集合加上增量
conn.zincrby("score:", VOTE_SCORE, article);
// 方法表示为哈希表中域 field 的值递增常量 value;增量也可以为负数,相当于对给定域进行减法操作;
// 这里就是给文章信息表的 votes 字段加 1
conn.hincrBy(article, "votes", 1);
}
}
/**
* 发布并获取文章
*
* @param conn Redis 连接
* @param user 发布文章的用户
* @param title 文章标题
* @param link 文章链接
* @return 返回 文章 ID article:id 格式的(例如:article:92617)
*/
public String postArticle(Jedis conn, String user, String title, String link) {
// 生成一个新的文章 ID(这里调用 incr 方法自增)
String articleId = String.valueOf(conn.incr("article:"));
String voted = "voted:" + articleId;
// 把自己添加进已投票用户列表,避免自己给自己投票
conn.sadd(voted, user);
// 设置这个已投票用户列表的过期时间
conn.expire(voted, ONE_WEEK_IN_SECONDS);
long now = System.currentTimeMillis() / 1000;
String article = "article:" + articleId;
// 将文章信息存储到 hash 里面
HashMap<String, String> articleData = new HashMap<>();
articleData.put("title", title);
articleData.put("link", link);
articleData.put("user", user);
articleData.put("now", String.valueOf(now));
articleData.put("votes", "1");
conn.hmset(article, articleData);
conn.hmset(article, articleData);
// 创建 根据评分排序文章的有序集合
conn.zadd("score:", now + VOTE_SCORE, article);
// 创建 根据发布时间排序文章的有序集合
conn.zadd("time:", now, article);
return articleId;
}
/**
* 取得全部文章列表
*
* @param conn Redis 连接
* @param page 分页
* @return 文章集合
*/
public List<Map<String, String>> getArticles(Jedis conn, int page) {
return getArticles(conn, page, "score:");
}
/**
* 根据 key 取得文章集合
*
* @param conn Redis 连接
* @param page 分页
* @param order key(是按照分数排序 "score:",还是按照时间排序 "time:")
* @return 文章集合
*/
public List<Map<String, String>> getArticles(Jedis conn, int page, String order) {
// 设置获取文章的起始索引和结束索引
// ARTICLES_PER_PAGE 表示每页文章数
int start = (page - 1) * ARTICLES_PER_PAGE;
int end = start + ARTICLES_PER_PAGE - 1;
// 这个 order 参数就是这个排序集合的 key(这里传进来的 key 是 "score:")
Set<String> ids = conn.zrevrange(order, start, end);
List<Map<String, String>> articles = new ArrayList<>();
for (String id : ids) {
// 根据文章 ID 取得文章信息
Map<String, String> articleData = conn.hgetAll(id);
articleData.put("id", id);
articles.add(articleData);
}
return articles;
}
/**
* 打印集合里面的文章
*
* @param articles 文章集合
*/
private void printArticles(List<Map<String, String>> articles) {
for (Map<String, String> article : articles) {
System.out.println(" id: " + article.get("id"));
for (Map.Entry<String, String> entry : article.entrySet()) {
if (entry.getKey().equals("id")) {
continue;
}
System.out.println(" " + entry.getKey() + ": " + entry.getValue());
}
}
}
/**
* 添加或者删除某篇文章到某个群组里面
*
* @param conn Redis 连接
* @param articleId 需要添加的文章 ID
* @param toAdd 需要添加到哪些群组
* @param toRemove 需要从哪些群组移除这篇文章
*/
public void addOrRemoveGroups(Jedis conn, String articleId, String[] toAdd, String[] toRemove) {
String article = "article:" + articleId;
if (toAdd != null) {
for (String group : toAdd) {
conn.sadd("group:" + group, article);
}
}
if (toRemove != null) {
for (String group : toRemove) {
conn.srem("group:" + group, article);
}
}
}
/**
* 取得某个组里面的文章
*
* @param conn Redis 连接
* @param group 组的名称
* @param page 分页
* @return 某个组里面的文章
*/
public List<Map<String, String>> getGroupArticles(Jedis conn, String group, int page) {
return getGroupArticles(conn, group, page, "score:");
}
/**
* 根据 key 来取得某个组里面的文章
*
* @param conn Redis 连接
* @param group 组的名称
* @param page 分页
* @param order key(是按照分数排序 "score:",还是按照时间排序 "time:")
* @return 某个组里面的文章
*/
public List<Map<String, String>> getGroupArticles(Jedis conn, String group, int page, String order) {
// 为每个群组都创建一个键
String key = order + group;
// 因为 exists 返回值是 Boolean 类型,可能为 null 所以这里需要使用 Boolean.FALSE.equals 来判断
// 检查是否有已缓存的排序结果,如果没有的话,就进行排序
if (Boolean.FALSE.equals(conn.exists(key))) {
// 这个 ZParams 就是专门用来构建参数的(建造者方法)
ZParams params = new ZParams().aggregate(ZParams.Aggregate.MAX);
// zinterstore 用来求并集
conn.zinterstore(key, params, "group:" + group, order);
// 让 Redis 在 60秒之后自动删除这个有序集合
conn.expire(key, 60);
}
// 调用上面那个写好的获取文章的方法来进行分页并获取文章数据
return getArticles(conn, page, key);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment