Java项目B站点赞超过500取消最早的点赞记录的实现思路

Posted ZuiaiLxh.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java项目B站点赞超过500取消最早的点赞记录的实现思路相关的知识,希望对你有一定的参考价值。

文章目录

前言

最近在做一个微信小程序的项目,然后这个项目有一个论坛功能,前端哥说希望做到点赞超过一定数量之后,会自动取消超过500后的最早那一批的点赞记录,她说这个思路的实现来自于B站,希望我们也能做一下,所以作为负责优化项目的我,就开始着手思考这个内容。

其实基本可以马上就想到就是数据库的操作,但是想了一想,效率肯定很低,因为点赞这种操作很频繁,虽然说可以让点赞的请求累积到一定数量然后通过MQ异步更新数据库,但是效率依旧有点问题,毕竟磁盘操作效率就是更加的低。
所以我就考虑能不能使用内存上的操作,也就是缓存,比如Redis。
然后我就想到了Bitmap这个数据结构。

简单的介绍一下,bitmap其实就是由一个又一个的bit位组成的,也就是0/1,而刚刚好点赞和没点赞就是0/1就能表示。同时,bitmap的基本单位(额,我是怎么理解的)就是一个又一个的byte,由8个bit组成,那么其实这就非常节省空间了,因此如果使用bitmap,那么在时间和空间上,都有相对于使用数据库更好的效率。
bitmap的简单介绍

思路

那么,上面简单的带过了一下基本思路后,现在来聊一聊到底如何实现比较合理。
bitmap有包含,key,offset,value。
其中key就是找到唯一的bitmap的方式,offset就是某一个索引位,value就是0/1。
同时,由于还得做到删除掉最早期的数据,因此我还得做一个能存储用户给那些文章点赞的时间集合,然后如果超过了设定的点赞上限的大小,就把最早的集合中的数据删掉。
在项目中我是用的是一个ArrayList来作为用户点赞的时间排序,因为其实我们并不需要真的去记录用户是什么时候点赞的,只要知道它最早点赞的那一批数据是那些即可了。

所以,我要做的就是,编写操作bitmap的接口,然后对某个offset上的数据置0/1,offset对应的就是被点赞的文章的id,而key就是用户id,value就是用户是否点赞。
然后我还做了一个存储list的集合,这个list保存着用户点赞的那一批文章的id以及顺序。

同时,我还得做到,当用户初始化论坛的时候,必须直接加载出来所有的,他点赞过的文章。
也就是我需要遍历bitmap,并且传递回来所有的位为1的offset。
第一种思路,就是遍历每一个位,然后加一点优化,也就是使用bitcount方法判断当前段上是否有为1的位,而如果没有,那么我们就不再需要判断这个位了,直接跳过这个段即可。大概代码如下

不过,我上面已经设定过了一个list,那么我直接返回这个list给前端即可。
如下

Redis服务包

因为是SpringBoot项目,那么其实直接对RedisTemplagte进行封装即可。


@Component
public class RedisService

    @Autowired
    public RedisTemplate redisTemplate;

    /**
     * 使用bitmap并且设定某一个位值
     * @param key bitmap缓存的键值
     * @param offset bitmap对应的索引位
     * @param value bitmap对应的值 0/1
     * @return 是否设置成功
     */
    public boolean setBit(final String key,final Long offset,final Boolean value)
        return redisTemplate.opsForValue().setBit(key,offset,value);
    

    /**
     * 获取bitmap某一个位上的值
     * @param key bitmap缓存的键值
     * @param offset bitmap对应的索引位
     * @return 该位键值是0/1
     */
    public boolean getBit(final String key,final Long offset)
        return redisTemplate.opsForValue().getBit(key,offset);
    

    /**
     * 返回bitmap的长度
     * @param key bitmap缓存的键值
     * @return 返回bitmap的长度
     */
    public long bitmapSize(String key)
        return redisTemplate.opsForValue().size(key);
    
    /**
     * 获取某个bitmap上的1的个数
     * @param key bitmap缓存的键值
     * @return 返回1的个数
     */
    public Long bitCount(final String key)
        //Long start = 0L; // 起始位置
        //Long end = -1L; // 结束位置,-1表示计算整个bitmap的长度
        Long count = (Long) redisTemplate.execute((RedisCallback<Long>) connection ->
                connection.bitCount(key.getBytes()));
        return count;
    
    /**
     * 获取某个bitmap上某一段上的1的个数
     * @param key bitmap缓存的键值
     * @return 返回1的个数
     */
    public Long bitCountRange(final String key,final Long start,final Long end)
        Long length = (Long) redisTemplate.execute((RedisCallback<Long>) con ->
            con.bitCount(key.getBytes(),start,end));
        return length;
    

    /**
     * 是哦那个bitfield获取连续为1的天数
     * @param buildSignKey bitmap缓存的键值
     * @param limit
     * @param offset
     * @return
     */
    @Deprecated
    public List<Long> bitField(final String buildSignKey,final Integer limit,final Long offset)
        return (List<Long>) redisTemplate.execute((RedisCallback<List<Long>>)con->
                con.bitField(buildSignKey.getBytes(),
                        BitFieldSubCommands.create()
                                .get(BitFieldSubCommands.BitFieldType
                                        .unsigned(limit)).valueAt(offset)));
    
      /**
     * 缓存List数据
     *
     * @param key 缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setCacheList(final String key, final List<T> dataList)
    
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    

    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key)
    
        return redisTemplate.opsForList().range(key, 0, -1);
    
 

代码实现

按照上面的思路,假设我们对userId为22的用户,点赞articleId为1000的文章,那么就有如下代码

 @Test
    public void bitmapCode()
        //15.4k byte  1024k = 1m
        long userId = 22;
        long articleId = 1000;
        //设定喜欢的文章 设定状态为相反
        boolean bit = redisService.getBit(RedisServiceConstants.USER_LIKE_ARTICLE + userId,
                Long.valueOf(articleId));
        redisService.setBit(RedisServiceConstants.USER_LIKE_ARTICLE + userId
                ,Long.valueOf(articleId),!bit);

        List<Long> likeList = redisService.getCacheList(
                RedisServiceConstants.USER_LIKE_TIME + userId);
        if (bit)
            likeList.remove(articleId);
        else
            likeList.add(articleId);
        
        //去重后放入list
        ArrayList<Long> likeList1 = new ArrayList<>(new HashSet<Long>(likeList));
        redisService.deleteObject(RedisServiceConstants.USER_LIKE_TIME+userId);
        redisService.setCacheList(RedisServiceConstants.USER_LIKE_TIME+userId,
                likeList1);
    


发送点赞请求之后,就会出现如下的情况。


而再一次点击之后,就是删除请求了。
可以发现time的那个键就已经被删除了,也就是取消点赞之后,就会把对应的文章的id删除,而这个用户的点赞记录的bitmap还是存在,不过本来为1的位已经变为了0

刷题面筋-测开-测试微信朋友圈的点赞功能

【刷题】面筋-测试开发常见问题合集

功能测试;接口测试;兼容性测试;可用性测试;安全测试。

功能测试

  • 是否可以点赞

  • 取消点赞

  • 多次点赞会出现什么情况

  • 多人点赞时的顺序是否按照时间顺序进行排列

  • 点赞是否显示头像和名称

  • 点赞之后能否进行评论

  • 点赞之后退出该页面,再次进入朋友圈点赞消息是否还存在

  • 多用户点赞,再次打开朋友圈是是否可以按照顺序看到是谁谁谁赞了我

接口测试

  • 点赞之后相同好友是否收到提示信息
  • 相同好友处的提示信息是否按照时间顺序
  • 相同好友处的点赞是否显示头像和名称

兼容测试

  • 电脑端和手机端是否都可以进行点赞和取消点赞功能
  • 不同的移动端是否都可以行点赞和取消点赞功能(包括苹果,安卓)

可用性测试

  • 弱网的时候进行点赞是什么情况
  • 网络断开时是否可以点赞
  • 点赞时有短信或电话进来,能否显示点赞情况
  • 用户点击点赞几秒后可以看到点赞成功,取消同理
  • 多用户同时给我点赞时,我是否可以全部接收到提示消息

安全性测试

  • 点赞是否会泄漏微信用户相关信息

END

以上是关于Java项目B站点赞超过500取消最早的点赞记录的实现思路的主要内容,如果未能解决你的问题,请参考以下文章

刷题面筋-测开-测试微信朋友圈的点赞功能

点赞功能

微信点赞功能测试用例

全栈项目|小书架|服务器端-NodeJS+Koa2 实现点赞功能

微信小程序实现点赞取消点赞功能

如何在 PHP 中使用简单的点赞页面多次点赞?