收藏+点赞(8.13)

​ 对比之前的阅读量和评论量,收藏和点赞除了需要记录统计数据外,还需要记录一些状态。

一、收藏

1、业务分析

​ 第一次点击是收藏文章,数量+1;再次点击是取消收藏,数量-1。因为 HTTP 协议无法记忆历史操作,所以必须在后端存储具体的收藏关系数据,才能准确判断当前状态并执行正确的切换逻辑。

​ ①记录收藏状态 ---> Redis中存储状态信息

​ ②记录文章的收藏数 ---> 复用之前的 strategy_static_hash: xx

2、存储方式

Set类型 Map<String, Set>

方式一:

前缀:文章id		[用户1id,用户2id,用户3id,……]

方式二(本次):

前缀:用户id 	[文章1id,文章2id,文章3id,……]

以上两种方式都可以,具体看业务需求。要考虑节省存储空间的情况

  • 文章比用户多,选第一种
  • 用户比文章多,选第二种

​ 在实际开发中,除了单纯比较数据体量以节省空间外,还需要结合具体的业务查询场景(如:是高频查询“文章的点赞列表”,还是高频查询“用户的收藏列表”)来最终决定存储方案。

3、代码实现

 @PostMapping("/favor")
    public R<Map<String, Object>> favor(Long sid) {
        Map<String, Object> map = strategyService.favor(sid);
        return R.ok(map);
    }
---------------------------------------------------------------------------------------------------
    @Getter
    public enum RedisKeys {
        USER_STRATEGY_FAVOR("user_strategy_favor", -1),
		...
    }
---------------------------------------------------------------------------------------------
	 /**
     * 收藏
     * @param sid
     * @return
     */
    @Override
    public Map<String, Object> favor(Long sid) {

        // 获取uid
        Long uid = SecurityContextHolder.getUserId();
        // 用uid拼接key (用户收藏文章的key)
        String key = RedisKeys.USER_STRATEGY_FAVOR.join(uid.toString());
        // 判断key,如果redis中没有这个key,存储一个空集合
        if(!redisService.hasKey(key)){
            Set<Long> set = new HashSet<>();
            set.add(-1L);
            redisService.setCacheSet(key, set);
        }
        // 统计数字的key
        String statisKey = RedisKeys.STRATEGY_STATIS_HASH.join(sid.toString());
        Boolean result = null;
        // 判断key的集合中有没有sid文章,如果有,说明用户收藏了这篇文章,本次是取消收藏操作
        if(redisService.isCacheSetContains(key,sid)){
            // 取消收藏,收藏数 -1,result设置为false ,集合中删除sid
            redisService.incrementCacheMapValue(statisKey, "favornum",-1);
            result = false;
            redisService.deleteCacheSetValue(key,sid);
        }else{
            // 收藏操作,收藏数 +1,result设置为true ,集合中添加sid
            redisService.incrementCacheMapValue(statisKey, "favornum",1);
            result = true;
            redisService.addCacheSetValue(key,sid);
        }
        // 查询统计对象数据
        Map<String, Object> cacheMap = redisService.getCacheMap(statisKey);
        // 将result添加到统计数据的map中
        cacheMap.put("result", result);
        // 返回统计数据的map
        return cacheMap;
    }
    
 	@GetMapping("/isUserFavor")
    public R<Boolean> isUserFavor(Long sid,Long uid) {

        return R.ok(strategyService.isUserFavor(sid,uid));
    }
----------------------------------------------------------------------------------------------------
    @Override
    public Boolean isUserFavor(Long id, Long uid) {

        return redisService.isCacheSetContains(RedisKeys.USER_STRATEGY_FAVOR.join(uid.toString()),id);
    }
    

二、点赞

​ 对于同一篇文章,一个用户一天最多点5个赞,超过了就不能再点了。设置过期时间为一天,等到了第二天就可以再次点赞了。

1、业务分析

​ ①记录当前的点赞状态

​ ②记录文章的点赞数

2、存储方式

方式一(独立 Key 模式):Set类型 Map<String, Set>

前缀:	uid			[sid1,sid2,sid3,......]			当天过期时间

缺点:只能记录当前用户今天点没点过赞,点了几次赞记不住

方式二(本次,文章维度聚合模式):String类型 Map<String, Integer>

前缀:	sid:uid		 2								当天过期时间

缺点:

  • key过多
  • 同一时间过期,Redis压力大。一般选择延时删除。

方式三(用户维度聚合模式,最推荐,与收藏类似): Hash类型 Map<String, Map<String, Integer>>

前缀:	uid		 	[sid1:1,sid2:2,sid3:1,......]	当天过期时间

方案二的优化

优点:

  • 节省大量的key

  • 过期只用删除一个key

方式四(全局聚合模式):Hash类型 Map<String, Map<String, Integer>>

点赞key			[sid1:uid1:1,sid1:uid2:2,......]	当天过期时间

方案三的优化

3、代码实现

 	@PostMapping("/thumbsup")
    public R<Map<String, Object>> thumbsup(Long sid) {
        Map<String, Object> map = strategyService.thumbsup(sid);
        return R.ok(map);
    }
----------------------------------------------------------------------------------------------------
    @Getter
    public enum RedisKeys {
        USER_STRATEGY_THUMBSUP("user_strategy_thumbsup", -1),
        STRATEGY_STATIS_HASH("strategy_statis_hash", -1),

    }
----------------------------------------------------------------------------------------------------
   public class DateUtil {
        // 获取给定日期的最后一分一秒
        public static Date getEndDate(Date date) {
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(date);
            calendar.set(Calendar.HOUR_OF_DAY, 23);
            calendar.set(Calendar.MINUTE, 59);
            calendar.set(Calendar.SECOND, 59);
            return calendar.getTime();
        }

        public static long getDateBetween(Date d1, Date d2) {

            return Math.abs(d1.getTime() - d2.getTime()) / 1000;
        }
	}

----------------------------------------------------------------------------------------------------    
    @Override
    public Map<String, Object> thumbsup(Long sid) {
        //获取uid
        Long uid = SecurityContextHolder.getUserId();
        // 用uid拼接key (用户点赞文章的key)
        String key = RedisKeys.USER_STRATEGY_THUMBSUP.join(sid.toString(),uid.toString());
        if(!redisService.hasKey(key)){
            Date now = new Date();
            Date endDate = DateUtil.getEndDate(now);
            long time = DateUtil.getDateBetween(now, endDate);
            time = time == 0 ? 1L : time;
            redisService.setCacheObject(key, 0,time, TimeUnit.SECONDS);
        }
        Long ret = redisService.incrementCacheObjectValue(key, 1);
        Boolean result = null;
        String statisKey = RedisKeys.STRATEGY_STATIS_HASH.join(sid.toString());
        if(ret > 5){
            // 今天点赞超过5次,点赞失败
            result = false;
        }else {
            // 点赞成功
            result = true;
            redisService.incrementCacheMapValue(statisKey, "thumbsupnum",1);
        }
        Map<String, Object> cacheMap = redisService.getCacheMap(statisKey);
        cacheMap.put("result", result);
        return cacheMap;
    }

posted on 2026-08-13 21:44  冬冬咚  阅读(5)  评论(0)    收藏  举报