基于Redis实现好友动态推送、附近商铺功能

好友动态推送

基于推模式实现探店笔记,一个人发布blog,在将blog保存到数据库的同时将blog发送到每个粉丝的收信箱中;收信箱按时间戳进行排序(类似于朋友圈);收信箱查询数据时按滚动分页进行查询。

滚动分页

在进行分页查询时,如果关注的人有新的动态,则不予查询,接着按照上一页的分页查询(每次查询记录上一页的最小值,第二页就接着最小值查询)

实现思路

用户收信箱的数据结构,采用zset,{"feed:followUserId" : "blogId" : "timeScore"}

使用zrevrangebyscore [key] [max] [min] [withscores] [limit offset count]进行滚动分页查询

key指定集合,min、max指定查询范围、withscores把时间戳作为结果带上(方便下一页的查询)、limit指定从最大值的第几个开始查、查多少个

分页参数

lastId->上一次查询的最小值(初始时为查询时的时间戳) --max

offset->上一次查询中最小值重复情况下的重复次数 --offset

min --0 、count --pageSize 这两个参数固定不变

业务流程

1、获取当前用户

2、在redis中查询当前用户的收信箱

3、解析数据blogId、minTime、offset

​ 3.1获取时间戳和博客id

​ 3.2获取查询中minTime重复的值作为下一轮的offset

4、根据blogId批量查询blog

5、需要将博客的点赞信息进行完善

6、将blog封装并返回

代码实现

BlogServiceImpl.queryBlogOfFollow()

@Override
public Result queryBlogOfFollow(Long max, Integer offset) {
    //1、获取当前用户
    UserDTO user = UserHolder.getUser();

    //2、在redis中查询当前用户的收信箱
    String key = "feed:" + user.getId();
    Set<ZSetOperations.TypedTuple<String>> typedTuples =
        stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, 0, max, offset, 3);

    //3、解析数据blogId、minTime、offset
    long minTime = 0;
    int os = 1;
    List<Long> ids = new ArrayList<>(typedTuples.size());  //查询出的博客的id
    for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
        //3.1获取时间戳和博客id
        long time = typedTuple.getScore().longValue();
        String blogId = typedTuple.getValue();
        ids.add(Long.valueOf(blogId));

        //3.2获取查询中minTime重复的值作为下一轮的offset
        if(time == minTime){
            os++;
        }else{
            minTime = time;
            os = 1;
        }
    }
    if(ids == null || ids.isEmpty()) return Result.ok();
    //4、根据blogId批量查询blog
    String idsStr = StrUtil.join(",", ids);
    List<Blog> blogs = query().in("id", ids).last("order by field(id, " + idsStr + ")").list();

    //5、需要将博客的点赞信息进行完善
    for (Blog blog : blogs) {
        //博客作者的信息
        User blogUser = userService.getById(blog.getId());
        blog.setIcon(blogUser.getIcon());
        blog.setName(blogUser.getNickName());

        //对当前登录的用户是否已经对该博客点赞
        Double isLike = stringRedisTemplate.opsForZSet().score(BLOG_LIKED_KEY + blog.getId(), UserHolder.getUser().getId().toString());
        blog.setIsLike(isLike != null);
    }

    //6、将blog封装并返回
    ScrollResult scrollResult = new ScrollResult();
    scrollResult.setList(blogs);
    scrollResult.setMinTime(minTime);
    scrollResult.setOffset(os);
    return Result.ok(scrollResult);
}

附近商铺

传入参数,有当前商铺id、商铺类型id、用户的个人位置信息

设置具体的redis数据结构为geoset和zset很像:{"shop:geo:typeId":"shopId":"geoscore"}

也就是{key:value:score},score是redis将地理的经纬度转化为一个分值进行存储

注意使用了GEOSearch,redis版本需要在6.2以上

业务流程

一、先将店铺的地理数据导入到redis中

1、查询店铺信息

2、将店铺信息按照typeId进行分组

3、分类型写入redis

​ 3.1、获取类型的id,拼接成key

​ 3.2、获取同类型的店铺集合

​ 3.3、使用geoAdd写入redis中

二、使用redis的GeoSearch进行查询、排序

1、判断是否需要根据坐标查询

2、计算分页参数

3、查询redis,按照举例进行排序、分页,结果以Map<shopId,distance>存储

4、解析出id

​ 4.1、截掉0~from的部分

​ 4.2、获取店铺id

​ 4.3、获取距离

5、查询店铺信息

6、将距离信息放入店铺信息中返回

代码实现

加载地理数据loadShopGeoData()

@Test
public void loadShopGeoData(){
    //1、查询店铺信息
    List<Shop> shopList = shopService.list();
    //2、将店铺信息按照typeId进行分组
    Map<Long, List<Shop>> map = shopList.stream().collect(Collectors.groupingBy(Shop::getTypeId));
    //3、分类型写入redis
    Set<Map.Entry<Long, List<Shop>>> entries = map.entrySet();
    for (Map.Entry<Long, List<Shop>> entry : entries) {
        //3.1、获取类型的id,拼接成key
        String key = SHOP_GEO_KEY + entry.getKey();
        //3.2、获取同类型的店铺集合
        List<Shop> shops = entry.getValue();
        //3.3、使用geoAdd写入redis中
        for (Shop shop : shops) {
            stringRedisTemplate.opsForGeo().add(key, new Point(shop.getX(), shop.getY()), shop.getId().toString());
    }

}

根据当前位置坐标查询方圆5000m方位内的店铺
ShopServiceImpl.queryShopByType()

@Override
public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {
    //1、判断是否需要根据坐标查询
    if(x == null || y == null){
        //不需要坐标进行查询,直接分页
        Page<Shop> page = query()
            .eq("type_id", typeId)
            .page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));
        // 返回数据
        return Result.ok(page.getRecords());
    }
    //2、计算分页参数
    int from = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE;
    int end = current * SystemConstants.DEFAULT_PAGE_SIZE;

    String key = SHOP_GEO_KEY + typeId;
    //3、查询redis,按照举例进行排序、分页,结果以Map<shopId,distance>存储
    GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo()  //用geoSearch进行查找指定中心点、查询半径、排序、记录条数等信息
        .search(key,
                GeoReference.fromCoordinate(x, y),
                new Distance(5000),
                //加上redis的一些命令参数,比如查找结果带有距离、分页等等
                RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end)
               );
    //4、解析出id
    if(results == null){
        return Result.ok(Collections.emptyList());
    }
    List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();
    //4.1、截掉0~from的部分
    List<Long> ids = new ArrayList<>(list.size());
    Map<String, Distance> distanceMap = new HashMap<>();
    list.stream().skip(from).forEach(result -> {
        //4.2、获取店铺id
        String shopId = result.getContent().getName();
        ids.add(Long.valueOf(shopId));
        //4.3、获取距离
        Distance distance = result.getDistance();

        //将id和distance匹配存放在map中
        distanceMap.put(shopId, distance);
    });
    //5、查询店铺信息
    //根据ids查询店铺数据
    String strIds = StrUtil.join(",", ids);
    List<Shop> shopList = query().in("id", ids).last("order by field(id, " + strIds + ")").list();
    //6、将距离信息放入店铺信息中返回
    for (Shop shop : shopList) {
        shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
    }

    return Result.ok(shopList);
}
posted @ 2022-10-21 23:00  CDUT的一只小菜鸡  阅读(210)  评论(2编辑  收藏  举报