限制条件:
如果一篇文章获得了至少200张支持票,那么就认为这篇文章是一篇有趣的文章,如果网站每天发布1000篇文章,而其中五十篇文章符合文章有趣的要求,那么网站要做的就是将这50篇文章放到文章列表前100位至少一天(暂时不提供反对票的功能)。
得分计算方式:
产生一个能够随着时间流逝的不断减少的评分,程序需要根据文章的发布时间和当前的时间来计算文章的评分,具体的办法就是:将文章得到的支持票数乘以一个常量,然后加上文章的发布时间,得到的结果就是文章的评分,这个常量设置的是432(将一天的秒数86400除以文章展示一天所需要的支持票数200的出的):即文章每获取一张支持票就将文章的评分增加432。
数据结构的选择:
文章信息包括文章的标题、网址、发布用户、发布时间、文章的得票数使用hash来进行存储最好。
数据结构如下:
两个有序集合来存储文章:
第一个有序集合的成员为文章id,分值为文章的发布时间。
第二个有序集合的成员为文章id,分值则为文章的评分。
防止队同一篇文章多次投票,网站需要为每篇文章记录一个已投票的用户名单。
群组使用set就可以,一个群组一个set
投票功能实现
逻辑分析:
- 判断文章的发布时间是否超过一周。
- 如果在投票范围之内,那么使用sadd命令,将用户添加到记录文章已投票用户名单的集合里面。
- 如果添加执行的操作成功的话,那么说明用户是第一次对文章进行投票,那么使用zincrby命令为文章增加432分,并使用hincrby命令对散列记录的文章投票数量进行更新。
具体代码实现:
private static final int ONE_WEEK_IN_SECONDS = 7 * 86400;
private static final int VOTE_SCORE = 432;
public void articleVote(Jedis conn, String user, String article) {
long cutoff = (System.currentTimeMillis() / 1000) - ONE_WEEK_IN_SECONDS;
if (conn.zscore("time:", article) < cutoff){
return;
}
String articleId = article.substring(article.indexOf(':') + 1);
if (conn.sadd("voted:" + articleId, user) == 1) {//此处操作涉及redis事务暂时不做处理
conn.zincrby("score:", VOTE_SCORE, article);
conn.hincrBy(article, "votes", 1);
}
}
文章发布功能:
逻辑分析:
- 发布文章创建一个新的文章ID,通过计数器INCR命令完成。
- sadd将文章的发布者ID添加到文章记录的已投票用户名单集合,并使用expire命令为这个集合设置一个过期时间,让redis在文章发布期满一周之后自动删除这个集合。
- hmset命令来存储文章的相关信息,并执行两个zadd命令,将文章的初始评分,和发布时间分别添加到两个相应的有序集合。
代码实现:
private static final int ONE_WEEK_IN_SECONDS = 7 * 86400;
private static final int VOTE_SCORE = 432;
public String postArticle(Jedis conn, String user, String title, String link) {
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;
HashMap<String,String> articleData = new HashMap<String,String>();
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.zadd("score:", now + VOTE_SCORE, article);
conn.zadd("time:", now, article);
return articleId;
}
获取评分最高的文章和获取最新文章
逻辑分析:
先使用zrevrange命令取出多个文章的ID,然后在对每个文章执行一次hgetall命令来去除文章的详细信息,这个方法既可以使用去除评分最高的文章,又可以取出最新发布的文章。
代码如下:
private static final int ONE_WEEK_IN_SECONDS = 7 * 86400;
private static final int VOTE_SCORE = 432;
private static final int ARTICLES_PER_PAGE = 25;
public List<Map<String,String>> getArticles(Jedis conn, int page) {
return getArticles(conn, page, "score:");
}
public List<Map<String,String>> getArticles(Jedis conn, int page, String order) {
int start = (page - 1) * ARTICLES_PER_PAGE;
int end = start + ARTICLES_PER_PAGE - 1;
Set<String> ids = conn.zrevrange(order, start, end);
List<Map<String,String>> articles = new ArrayList<Map<String,String>>();
for (String id : ids){
Map<String,String> articleData = conn.hgetAll(id);
articleData.put("id", id);
articles.add(articleData);
}
return articles;
}
对文章添加分组
逻辑分析:
群组有两个部分组成,一部分负责记录文章属于哪个群组,另一部分负责取出群组里面的文章,为了记录各个群组都保存了那些文章,网站需要为每个群组创建一个集合,并将所有同属于一个群组的文章ID都记录到那个集合里面
代码实现:
public void addGroups(Jedis conn, String articleId, String[] toAdd) {
String article = "article:" + articleId;
for (String group : toAdd) {
conn.sadd("group:" + group, article);
}
}
获取组内的文章
逻辑分析:
对储存群组文章的集合和存储文章评分的有序集合执行zinterstore命令,可以得到按照文章的最新评分顺序的群组文章,如果群组的包含的文章多,那么执行zinterstore命令就会比较花时间,减少redis的工作量,将这个程序的计算结果缓存60s。
代码实现:
private static final int ONE_WEEK_IN_SECONDS = 7 * 86400;
private static final int VOTE_SCORE = 432;
private static final int ARTICLES_PER_PAGE = 25;
public List<Map<String,String>> getGroupArticles(Jedis conn, String group, int page) {
return getGroupArticles(conn, group, page, "score:");
}
public List<Map<String,String>> getGroupArticles(Jedis conn, String group, int page, String order) {
String key = order + group;
if (!conn.exists(key)) {
ZParams params = new ZParams().aggregate(ZParams.Aggregate.MAX);
conn.zinterstore(key, params, "group:" + group, order);
conn.expire(key, 60);
}
return getArticles(conn, page, key);
}