-
-
Save alsritter/3814bb342483d078dd8ee045598675d9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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