参考《redis实战》
需求
1、文章信息包括:标题、内容、链接、发布时间、发布人,发布后就给自己投一张票。
2、一个人只能给一篇文章投一张票。
3、文章只能七天内允许投票。
4、文章投票排名考虑文章发布时间以及投票数量,以发布时间作为降序排序,如果投票数量为200,则时间向前移一天。
5、文章排序包括发布时间排序,投票得分排序
分析
第一个需求
可以采用散列来保存文章信息。存入信息用HMSET,取出对应字段信息用HMGET,取出key的所有信息用HGETALL,基本用法如下:
// 赋值
local:0>hmset person:001 name '张三' age 18
"OK"
// 取对应key的某个属性值
local:0>hmget person:001 name
1) "张三"
// 取对应key的所有属性值
local:0>hgetall person:001
1) "name"
2) "张三"
3) "age"
4) "18"
第二个需求
这边考虑到不能重复的概念,在java里用set,redis也有集合的概念。集合简单的说,就是不能有重复的数据,基本用法如下:
// 赋值
local:0>sadd name '张三'
"1"
// 赋值
local:0>sadd name '李四'
"1"
// 赋值失败,返回0,因为已经添加过
local:0>sadd name '张三'
"0"
// 获取对应key的所有成员
local:0>smembers name
1) "张三"
2) "李四"
第三个需求
有时间控制需求,需要把文章id和发布时间保存起来,考虑到后面用时间进行排序,所以用有序集合。有序集合和上面集合不一样的是,多了一个分值的概念,可以通过分值进行排序等操作。七天后不能投票就可以通过这个分值来计算。
// 添加
local:0>zadd score 88 '赵大'
"1"
// 添加
local:0>zadd score 93 '熊二'
"1"
// 添加
local:0>zadd score 92 '张三'
"1"
// 添加
local:0>zadd score 89 '李四'
"1"
// 添加
local:0>zadd score 70 '王五'
"1"
// 重复添加失败返回0,但是这边做了修改
local:0>zadd score 60 '王五'
"0"
// 分数加5,返回最终值
local:0>zincrby score 5 '王五'
"65"
// 从低到高排序,取前四个
local:0>zrange score 0 3 withscores
1) "王五"
2) "65"
3) "赵大"
4) "88"
5) "李四"
6) "89"
7) "张三"
8) "92"
// 从高到低排序,取前四个
local:0>zrevrange score 0 3 withscores
1) "熊二"
2) "93"
3) "张三"
4) "92"
5) "李四"
6) "89"
7) "赵大"
8) "88"
// 获取分数值
local:0>zscore score '张三'
"92"
第四个需求
一天有84600秒,200票时间向前移动一天,则每票就是84600/200=432分。
第五个需求
投票要根据分数来排序,所以同需求三,用有序集合。
实践
发布文章
private static final int ONE_WEEK_IN_SECONDS = 7 * 86400;
private static final int VOTE_SCORE = 432;
@Test
public void postArticle() throws InterruptedException {
for (int i = 0; i < 5; i++) {
// 通过incre获取自增长主键
String articleId = String.valueOf(JedisUtils.incre("article:"));
// 定义主键
String article = "article:" + articleId;
long now = System.currentTimeMillis() / 1000;
String user = "发布人" + i;
// 设置文章
Map<String, String> articleMap = new HashMap<>();
articleMap.put("title", "文章标题" + i);
articleMap.put("content", "文章内容" + i);
articleMap.put("link", "文章链接" + i);
articleMap.put("user", user);
articleMap.put("now", String.valueOf(now));
// 记录投票数
articleMap.put("votes", "1");
// 记录投票人,防止重复投票,设置过期时间
String voted = "voted:" + articleId;
JedisUtils.sadd(voted, user);
JedisUtils.expire(voted, ONE_WEEK_IN_SECONDS);
// 通过文章主键插入
JedisUtils.hmset(article, articleMap);
//需要时间和分数排序
JedisUtils.zadd("score:", now + VOTE_SCORE, article);
JedisUtils.zadd("time:", now, article);
TimeUnit.SECONDS.sleep(1);
}
}
投票
@Test
public void voteArticle() {
for (int i = 1; i < 3; i++) {
String article = "article:" + i;
String voted = "voted:" + i;
System.out.println("投票前,第一篇文章和第二篇文章的投票数:");
System.out.println("文章信息:" + getArticle(article));
System.out.println("投票信息:" + getVotesUser(voted));
}
String user = "发布人1";
for (int i = 1; i < 3; i++) {
String article = "article:" + i;
String voted = "voted:" + i;
// 获取发布时间
Double zscore = JedisUtils.zscore("time:", article);
long now = System.currentTimeMillis() / 1000;
// 失效了,不能投票
if (zscore < (now - ONE_WEEK_IN_SECONDS)) {
continue;
}
// 返回0说明已经存在没有插入
if (JedisUtils.sadd(voted, user) == 0) {
continue;
}
// 没有返回0怎插入成功,顺便更新分数
JedisUtils.zincrby("score:", VOTE_SCORE, article);
// 更新文章投票数
JedisUtils.hincrBy(article, "votes", 1);
}
for (int i = 1; i < 3; i++) {
String article = "article:" + i;
String voted = "voted:" + i;
System.out.println("投票后,第一篇文章和第二篇文章的投票数:");
System.out.println("文章信息:" + getArticle(article));
System.out.println("投票信息:" + getVotesUser(voted));
}
}
private Map<String, String> getArticle(String article) {
Map<String, String> articleMap = JedisUtils.hgetAll(article);
return articleMap;
}
private Set<String> getVotesUser(String key) {
return JedisUtils.smembers(key);
}
运行结果如下,可以看到,文章1被投了一票,文章2由于是作者,已经投过票了,所以不能投票
排序
@Test
public void sortArticle() {
// 根据时间排序
System.out.println(JedisUtils.zrevrange("score:", 0, -1));
// 根据分值排序
System.out.println(JedisUtils.zrevrange("time:", 0, -1));
}
运行结果如下,第一行结果,由于文章1被投了一票,虽然发布时间比文章5早,但是拍在文章5前面。第二行结果就是按时间排序的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。