可以发表文章的 使用redis构建文章投票系统
首先,我得说明这篇博客基本上就是第一章内容的读书笔记。
需求 首先,说明一下,我们的需求
用户可以发表文章,发表时,自己就默认的给自己的文章投了一票。
用户在查看网站的文章时,文章当然是按顺序排列的(这个顺序怎么计算呢?我们把文章发表的时间求出来,这个时间就是离1970年的那个秒数,同时,文章每被投票一次,再那个时间的基础上加上一个常量。最后按照所有文章的总得分来排序)。
当然,我也可以就按照发表时间排序。
一篇文章发表后,七天内可以投票,七天过后就不能再投票了。
某个人只能给一篇文章投一次票。
一个文章可以属于很多个组,我们还应该可以在组内排序。
redis的介绍,以及基础操作
设计方案 首先我们肯定有几个数据域
第一个域,这是zset(有序集合)----score:
是文章的id,score是文章的得分(发表时间+得票数*一个常量)
第二个域,也是zset(有序集合)----time:
是文章的id,score是文章的发表时间
第三个域,是hash(散列)-------:id
key就是文章标题,文章的作者,发表时间,获得的投票数等等
第四个域,是set(无序集合)-----voted:
里面存储的是给这篇文章投过票的所有用户的id号
第五个域,是------:
这个域用来获得最新发表的文章的id号
第六个域,是set------group:
里面存储的属于这个组的所有文章的id
发表文章
private static final int ONE_WEEK_IN_SECONDS = 7 * 86400;
public String postArticle(Jedis conn, String user, String title, String link) {
//获得最新的id
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 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);
//加了一个数据域 key是score: member是文章 score是文章的得分
//发布的时候 默认作者本人为文章投票
conn.zadd("score:", now + VOTE_SCORE, article);
//加了一个数据域 key是time: member是文章 score是文章的发布时间
conn.zadd("time:", now, article);
return articleId;
}
给文章投票
//每一票对应的常量
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;
//cutoff之前的发布的文章 就不能再投票了
if (conn.zscore("time:", article) < cutoff){
return;
}
String articleId = article.substring(article.indexOf(':') + 1);
//查看user是否给这篇文章投过票
//set里面的key是唯一的 如果 sadd返回0 表示set里已经有数据了
//如果返回1表示还没有这个数据
if (conn.sadd("voted:" + articleId, user) == 1) {
conn.zincrby("score:", VOTE_SCORE, article);
conn.hincrBy(article, "votes", 1l);
}
}
一篇文章 可以分为多个组
public void addGroups(Jedis conn, String articleId, String[] toAdd) {
String article = "article:" + articleId;
for (String group : toAdd) {
conn.sadd("group:" + group, article);
}
}
按照顺序,获得文章(所有组的文章统一排序) page是分页查询,order可以分两种,score和time,前者还考虑到了投票,后者就完全按照时间排序
public List<Map> getArticles(Jedis conn, int page, String order) {
int start = (page - 1) * ARTICLES_PER_PAGE;
int end = start + ARTICLES_PER_PAGE - 1;
Set ids = conn.zrevrange(order, start, end);
List<Map> articles = new ArrayList<Map>();
for (String id : ids){
Map articleData = conn.hgetAll(id);
articleData.put("id", id);
articles.add(articleData);
}
return articles;
}
按照顺序,获得某一组内的文章
public List<Map> 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); //放进缓存 60s后过期
}
return getArticles(conn, page, key);
}
这里现在出现问题了
= new ().(..MAX);
conn.(key, , "group:" + group, order);
这是什么鬼?
OK,我们知道,我们之前设计了五个域
我们可以很容易获得某个组内所有文章
也能很方便的知道全局范围内某种顺序(score或者time)下前n个文章
问题是,我们现在没有设计某个组范围内的某个顺序下的文章呀
简单的说就是关系型数据库里面的关联表操作
左边的"表"存放的是这个组下的文章信息
中间这个"表"里面存放的是系统内每篇文章的得分
这是方法的声明:
Open Long redis..jedis.Jedis.( , , ... sets)
我们要联立的是score和group:这个两个表
group:这个是一个无序set,没有score这个项呀?redis就默认它等于1
使用的聚合是max这个型
那么score:这个有序set"表"的项,就是score和:这个两个表的交集
同时score:这个有序表的score列就是取group:表与score:表的最大值。group:的缺省值是1,那么也就是说score:的score就是score:表的score
好绕口呀
那么还有什么型呢?
public class ZParams {
public enum Aggregate {
SUM, MIN, MAX; //猜一下sum是什么意思
public final byte[] raw;
Aggregate() {
raw = SafeEncoder.encode(name());
}
}
// .....
}
反对票 对了,如果要设置投反对票的功能,一个人只能反对一篇文章一次,怎么做,那简单呀
在建立一个set域,key就是:
里面存放的就是反对这篇文章的用户的信息
//每一票对应的常量
private static final int VOTE_SCORE = 432;
public void articleOppose(Jedis conn, String user, String article) {
long cutoff = (System.currentTimeMillis() / 1000) - ONE_WEEK_IN_SECONDS;
//cutoff之前的发布的文章 就不能再投票了
if (conn.zscore("time:", article) < cutoff){
return;
}
String articleId = article.substring(article.indexOf(':') + 1);
//查看user是否给这篇文章投过票
//set里面的key是唯一的 如果 sadd返回0 表示set里已经有数据了
//如果返回1表示还没有这个数据
if (conn.sadd("oppose:" + articleId, user) == 1) {
conn.zincrby("score:", -VOTE_SCORE, article);
conn.hincrBy(article, "votes", -1l);
}
}
搞定!