用戶點贊功能
如果用戶只要點贊一次就對數據庫中blog
表中的liked
字段的值加1就會導致一個用戶無限點贊
PutMapping("/like/{id}")
public Result likeBlog(@PathVariable("id") Long id) {// 修改點贊數量,update tb_blog set liked = liked + 1 where id = ? blogService.update().setSql("liked = liked + 1").eq("id", id).update();return Result.ok();
}
需求: 同一個用戶只能對同一篇筆記點贊一次再次點擊則取消點贊
,如果當前用戶已經點贊則點贊按鈕高亮顯示
增加isLike字段
: 給Blog實體類
中添加一個isLike字段,首頁查詢熱門筆記或用戶查看筆記詳情內容時會根據isLike字段
的屬性值決定點贊按鈕是否高亮顯示點贊修改功能
: 利用Redis中的set集合是否包含點贊用戶的Id來判斷用戶是否點贊過,未點贊則點贊數+1,已點贊則點贊數-1查看筆記詳情時根據id查詢筆記
: 判斷當前登錄用戶是否點贊過,賦值給isLike字段決定點贊圖標是否高亮顯示訪問首頁時分頁查詢熱門筆記
: 判斷當前登錄用戶是否點贊過,賦值給isLike字段決定點贊圖標是否高亮顯示
一人一贊
第一步: 給Blog實體類增加isLike
字段,首頁查詢熱門筆記或用戶查看筆記詳情內容時會根據isLike字段
的屬性值決定點贊按鈕是否高亮顯示
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("tb_blog")
public class Blog implements Serializable {private static final long serialVersionUID = 1L;// isLike屬性不屬于Blog表中的字段@TableField(exist = false)private Boolean isLike;//..........
}
第二步: 編寫控制器方法處理用戶點贊的請求
@PutMapping("/like/{id}")
public Result likeBlog(@PathVariable("id") Long id) {return blogService.likeBlog(id);
}
第三步: 編寫業務方法,以blog:liked:筆記Id
作為Set集合的Key,集合中的元素就是點贊用戶的Id
// 在RedisConstants類中聲明一個常量作為Set集合的key,集合中包含了所有點贊的用戶
public static final String BLOG_LIKED_KEY = "blog:liked:";
// 操作Redis,key和value要求是String類型
@Resource
private StringRedisTemplate stringRedisTemplate;@Override
public Result likeBlog(Long id) {//1. 獲取當前登陸用戶信息Long userId = UserHolder.getUser().getId();//2. 判斷當前用戶是否已經點贊//2.1如果用戶未點贊則Blog表中like字段值加1,同時將點贊用戶的Id加入set集合String key = BLOG_LIKED_KEY + id;Boolean isLiked = stringRedisTemplate.opsForSet().isMember(key, userId.toString());if (BooleanUtil.isFalse(isLiked)) {// update tb_blog set liked = liked + 1 where id = ?boolean success = update().setSql("liked = liked + 1").eq("id", id).update();// 更新成功將點贊用戶Id加入set集合if (success) {stringRedisTemplate.opsForSet().add(key, userId.toString());}//2.2如果當前用戶已點贊則取消點贊即like字段值減1,同時將點贊用戶Id從set集合中移除}else {// update tb_blog set liked = liked - 1 where id = ?boolean success = update().setSql("liked = liked - 1").eq("id", id).update();if (success){// 更新成功則將點贊用戶Id從set集合移除stringRedisTemplate.opsForSet().remove(key, userId.toString());}}return Result.ok();
}
修改查詢筆記點贊高亮
訪問首頁時分頁查詢熱門筆記
: 判斷當前登錄用戶是否點贊過,根據Blog實體類isLike
屬性的值決定點贊圖標是否高亮顯示
@Override
public Result queryHotBlog(Integer current) {// 根據點贊值降序分頁查詢筆記信息Page<Blog> page = query().orderByDesc("liked").page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));// 獲取所有查詢到的所有筆記數據List<Blog> records = page.getRecords();// 查詢筆記中包含的用戶昵稱和頭像以及是否點贊的信息records.forEach(blog -> {// 查詢用戶昵稱和頭像封裝到Blog實體類當中queryBlogUser(blog);// 判斷筆記是否被當前用戶點贊isBlogLiked(blog);});return Result.ok(records);
}
查看筆記詳情時根據id查詢筆記
: 判斷當前登錄用戶是否點贊過,根據Blog實體類isLike
屬性的值決定點贊圖標是否高亮顯示
@Override
public Result queryBlogById(Integer id) {Blog blog = getById(id);if (blog == null) {return Result.fail("評價不存在或已被刪除");}// 查詢用戶昵稱和頭像封裝到Blog實體類當中queryBlogUser(blog);// 判斷筆記是否被當前用戶點贊isBlogLiked(blog);return Result.ok(blog);
}
由于查看用戶信息
和判斷筆記是否被當前用戶點贊
的業務邏輯比較通用,所以抽取成獨立的方法
// 查看用戶信息
private void queryBlogUser(Blog blog) {Long userId = blog.getUserId();User user = userService.getById(userId);blog.setName(user.getNickName());blog.setIcon(user.getIcon());
}// 判斷用戶是否已經點贊
private void isBlogLiked(Blog blog) {//1. 獲取當前用戶信息Long userId = UserHolder.getUser().getId();//2. 判斷當前用戶是否點贊String key = BLOG_LIKED_KEY + blog.getId();Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());//3. 如果點贊了則將Blog類的isLike屬性設置為trueblog.setIsLike(BooleanUtil.isTrue(isMember));
}
點贊排行榜
需求: 當我們點擊探店筆記詳情頁面時,應該按點贊順序
展示點贊過的用戶,比如顯示最早點贊的TOP5形成點贊排行榜
因為Set集合不能對點贊的用戶進行排序,所以我們需要使用SortedSet(Zset)
集合存儲點贊的用戶Id,score屬性
的值是當前時間戳(默認按照從小到大排序)
第一步: ZSet沒有判斷元素是否存在的方法,是通過獲取集合中元素的score屬性的值
來判斷集合中是否有該元素
ZSCORE key e1
: 獲取集合元素的score屬性值,若元素存在則返回對應score值,若不存在則返回null
@Override
public Result likeBlog(Long id) {//1. 獲取當前登陸用戶信息Long userId = UserHolder.getUser().getId();//2. 判斷當前用戶是否已經點贊//2.1如果用戶未點贊則Blog表中like字段值加1,同時將點贊用戶的Id加入set集合String key = BLOG_LIKED_KEY + id;// 嘗試獲取當前用戶的score屬性值Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());// 如果score為null則表示集合中沒有該用戶if (score == null) {// update tb_blog set liked = liked + 1 where id = ?boolean success = update().setSql("liked = liked + 1").eq("id", id).update();//更新成功則將點贊用戶Id加入SortedSet集合,score屬性的值就是當前的時間戳(默認按照從小到大排序)if (success) {stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());}//2.2如果當前用戶已點贊則取消點贊即like字段值減1,同時將點贊用戶Id從SortedSet集合中移除} else {// update tb_blog set liked = liked - 1 where id = ?boolean success = update().setSql("liked = liked - 1").eq("id", id).update();if (success) {//更新成功將點贊用戶Id從SortedSet集合移除stringRedisTemplate.opsForZSet().remove(key, userId.toString());}}return Result.ok();
}
第二步: 判斷用戶是否點贊時如果用戶沒有登錄就不需要判斷用戶是否點過贊了
,因為用戶沒有登陸就獲取不到userId
此時會報空指針異常
private void isBlogLiked(Blog blog) {//1. 獲取當前用戶信息UserDTO userDTO = UserHolder.getUser();//2. 當用戶未登錄時就不判斷用戶是否點贊,直接return結束邏輯if (userDTO == null) {return;}//3. 判斷當前用戶是否點贊String key = BLOG_LIKED_KEY + blog.getId();Double score = stringRedisTemplate.opsForZSet().score(key, userDTO.getId().toString());// score不等于null返回true表示用戶已經點過贊同時給Blog類的isLike屬性賦值trueblog.setIsLike(score != null);
}
第三步: 顯示點贊排行列表
當瀏覽器發起GET請求blog/likes/4
時服務器返回一個List集合包含top5點贊的用戶信息
ZRANGE key start end
: 獲取指定范圍的元素,SortedSet內的元素會自動排序(默認升序)
@GetMapping("/likes/{id}")
public Result queryBlogLikes(@PathVariable Integer id){return blogService.queryBlogLikes(id);
}
@Override
public Result queryBlogLikes(Integer id) {String key = BLOG_LIKED_KEY + id;// 查詢SortedSet集合的前5個元素即用戶的id(不包含元素的分數)Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);// 如果返回的set集合是空的即沒人點贊,直接返回一個空的List集合if (top5 == null || top5.isEmpty()) {return Result.ok(Collections.emptyList());}// 將Set集合中String類型的用戶id轉變為Long類型的id然后收集到List集合中List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());//select * from tb_user where id in (ids[0],...) order by field(id, ids[0],...)String idsStr = StrUtil.join(",", ids);// 把ids集合拼成一個以","分隔的字符串List<UserDTO> userDTOS = userService.query().in("id", ids).last("order by field(id," + idsStr + ")").list().stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class))// 將查詢出來的用戶隱私信息隱藏.collect(Collectors.toList());return Result.ok(userDTOS);
}
由于MySQL默認會對查詢出來的所有結果按照id從小到大的方式排序,它并不會按照我們查詢到的用戶Id順序去排序
order by field(排序字段,排序順序)
: 可以指定查詢結果按照某個字段的排序方式
select * from tb_user where id in (ids[0], ids[1] ...)
: 這種方式并不會按照Set集合中Id的順序對查詢結果進行排序
List<UserDTO> userDTOS = userService.listByIds(ids).steram().map(user -> BeanUtil.copyProperties(user, UserDTO.class))// 將查詢出來的用戶隱私信息隱藏.collect(Collectors.toList());
select * from tb_user where id in (ids[0],...) order by field(id, ids[0],...)
: 指定查詢結果按照我們指定的字段順序排序
// 把ids集合拼成一個以","分隔的字符串
String idsStr = StrUtil.join(",", ids);
List<UserDTO> userDTOS = userService.query().in("id", ids).last("order by field(id," + idsStr + ")").list().stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class))// 將查詢出來的用戶隱私信息隱藏.collect(Collectors.toList());