6

参考《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由于是作者,已经投过票了,所以不能投票
image.png

排序

@Test
public void sortArticle() {
    // 根据时间排序
    System.out.println(JedisUtils.zrevrange("score:", 0, -1));
    // 根据分值排序
    System.out.println(JedisUtils.zrevrange("time:", 0, -1));
}

运行结果如下,第一行结果,由于文章1被投了一票,虽然发布时间比文章5早,但是拍在文章5前面。第二行结果就是按时间排序的。
image.png


大军
847 声望183 粉丝

学而不思则罔,思而不学则殆