前面一小节讲到了在抢票场景中使用Redis的分布式锁,接下来这个场景会相对的复杂一些,模拟博客网站,提供发布和点赞功能,用到了String,Hash,Set,Zset四种数据结构。具体要求如下:
- 提供发布、点赞、查看文章列表的功能
- 文章列表按照发布时间或者分值进行排序
- 每个用户点赞即可增加文章10点分值(分值的增加方式也可以是其它的,如评论、浏览量等)
- 文章只允许在发布后的三天内被点赞,超过三天就不能再点赞了
- 文章发布,默认自己给当前文章点赞
上面大概就是这次笔者写的案例需求,接下来看看代码,会有比较详细的注解说明
VoteController
@RestController
public class VoteController {
@Autowired
private ArticleService articleService;
//发布文章
@PostMapping("/publish")
public void publishArticle(@RequestBody PublishArticleVO publishArticleVO) {
articleService.publishArticle(publishArticleVO);
}
//点赞
@PostMapping("/vote")
public void vote(@RequestBody VoteVO vo) {
articleService.vote(vo);
}
//按时间倒序查看文章列表
@PostMapping("/list")
public void list() {
articleService.list();
}
//按文章分值倒序查看文章列表
@PostMapping("/list_score")
public void listScore() {
articleService.listScore();
}
}
ArticleService
@Service
public class ArticleService {
//三天时间 -秒
private Long THREE_DAY_SECONDS = 259200L;
@Autowired
private RedisUtils<Object> redisUtils;
//发布文章
public void publishArticle(PublishArticleVO publishArticleVO) {
Long artId = redisUtils.incr("artKey",1);
String artKey = "art:" + artId;
String voteKey = "vote:" + artId;
//map保存文章的详细信息
Map<String, Object> map = new HashMap<>(7);
long time = System.currentTimeMillis() / 1000;
map.put("artId",artId);
map.put("artTitle", publishArticleVO.getTitle());
map.put("artContent", publishArticleVO.getContent());
map.put("url", publishArticleVO.getUrl());
map.put("user", publishArticleVO.getUserId());
map.put("time", String.valueOf(time));
//默认自己点赞
map.put("vote", 1);
redisUtils.hmset(artKey, map);
System.out.println("userId:"+publishArticleVO.getUserId()+"发布了artId:"+artId);
//保存当前文章已经点赞的用户id,发布时默认给自己点赞
redisUtils.sSet(voteKey, publishArticleVO.getUserId());
//保存文章的发布时间
redisUtils.zAdd("artTime", artKey, time);
//保存文章的得分,自己点赞,加10分
redisUtils.zAdd("score", artKey, 10L);
System.out.println("userId:"+publishArticleVO.getUserId()+"对文章artId:"+artId+" 进行了点赞");
}
//点赞
public void vote(VoteVO vo) {
String artKey = "art:" + vo.getArtId();
String voteKey = "vote:" + vo.getArtId();
//拿到文章的发布时间,判断是否超过三天
Double artTime = redisUtils.zscore("artTime", artKey);
if (artTime < System.currentTimeMillis() / 1000 - THREE_DAY_SECONDS) {
System.out.println("artId:"+vo.getArtId()+" 已发布三天后无法参与投票");
return;
} else {
//如果用户还未对该文章点赞,则允许点赞
if (!redisUtils.sHasKey(voteKey, vo.getUserId())) {
//加入set,表示该用户已经点赞
redisUtils.sSet(voteKey, vo.getUserId());
//文章点赞数+1
redisUtils.hincr(artKey, "vote", 1);
//分值+10
redisUtils.zincr("score", artKey, 10);
System.out.println("userId:"+vo.getUserId()+"对文章artId:"+vo.getArtId()+" 进行了点赞");
} else {
System.out.println("userId:" + vo.getUserId() + "无法对artId:"+vo.getArtId()+"重复投票");
}
}
}
//按时间倒序查看文章列表
public void list() {
//按发布时间倒序查看前三篇文章
Set artKey = redisUtils.range("artTime", 3, false);
//遍历打印出来
artKey.forEach(e -> {
Map<String, Object> art = redisUtils.hmget((String)e);
System.out.println("--------------"+e+"------------------");
art.entrySet().forEach(m -> {
System.out.println(m.getKey() + ": " + m.getValue());
});
});
}
//按文章分值倒序查看文章列表
public void listScore() {
//按文章分值倒序查看前三篇文章
Set artKey = redisUtils.range("score", 3,false);
//遍历打印出来
artKey.forEach(e -> {
Map<String, Object> art = redisUtils.hmget((String) e);
System.out.println("--------------"+e+"------------------");
art.entrySet().forEach(m -> {
System.out.println(m.getKey() + ": " + m.getValue());
});
});
}
}
RedisUtils
@Component
public class RedisUtils<T> {
@Resource(name = "redisTemplate")
private RedisTemplate<String, T> redisTemplate;
//redis的操作类
public long incr(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}
public boolean hmset(String key, Map<String, T> map) {
redisTemplate.opsForHash().putAll(key, map);
return true;
}
public long sSet(String key, T... values) {
return redisTemplate.opsForSet().add(key, values);
}
public boolean zAdd(String key, T obj, double score) {
return redisTemplate.opsForZSet().add(key, obj, score);
}
public Double zscore(String key,Object feild){
return redisTemplate.opsForZSet().score(key, feild);
}
public boolean sHasKey(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
public double zincr(String key, T feild, double by) {
return redisTemplate.opsForZSet().incrementScore(key, feild, by);
}
public Set<T> range(String key, int size, boolean isAsc) {
if (isAsc) {
return redisTemplate.opsForZSet().range(key, 0, size-1);
}
return redisTemplate.opsForZSet().reverseRange(key, 0, size-1);
}
public Map<String, T> hmget(String key) {
return redisTemplate.<String, T>opsForHash().entries(key);
}
}
RedisConfig
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(lettuceConnectionFactory);
// 这里可以给template加一些序列化的定制
return template;
}
}
PublishArticleVO
@Data
public class PublishArticleVO {
/**
* 用户id
*/
private String userId;
/**
* 标题
*/
private String title;
/**
* 文章内容
*/
private String content;
/**
* url
*/
private String url;
}
VoteVO
@Data
public class VoteVO {
/**
* 用户id
*/
private String userId;
/**
* 文章的id
*/
private String artId;
}
运行结果:
1.先发布6篇文章,每篇文章发布时自己默认会为文章点赞
2.检测是否能重复点赞
4.按时间倒序,列出前三篇文章
5.按文章的分值倒序,列出前三篇文章