Day82(8)-F:\code\hm-dianping\hm-dianping

黑马点评

附近商铺

GEO数据结构

image-20260115123506516

连接成功
Last login: Tue Jan 13 12:24:02 2026 from 192.168.100.1
[root@bogon ~]# redis-cli
127.0.0.1:6379> geoadd g1 116.378248 39.865275 bjn 116.42803 39.903738 bjz 116.322287 39.893729 bjx
(error) ERR Protocol error: unauthenticated multibulk length
127.0.0.1:6379> auth 123321
Error: Server closed the connection
127.0.0.1:6379> auth 123321
OK
127.0.0.1:6379> geoadd g1 116.378248 39.865275 bjn 116.42803 39.903738 bjz 116.322287 39.893729 bjx
(integer) 3
127.0.0.1:6379> geodist g1 bjn bjx
"5729.9533"
127.0.0.1:6379> geodist g1 bjn bjx km
"5.7300"
127.0.0.1:6379> geodist g1 bjx bjz km
"9.0916"
127.0.0.1:6379> geosearch g1 fromlonlat 116.397904 39.909005 byradius 10 km withdist

"bjz"

"2.6361"

"bjn"

"5.1452"

"bjx"

"6.6723"

127.0.0.1:6379> geopos g1 bjz

"116.42802804708480835"

"39.90373880538094653"
127.0.0.1:6379> geohash g1 bjz

"wx4g12k21s0"

附近商铺

image-20260115125314796

image-20260115125656695

技术栈

  1. 通过stream,将list中的元素按照元素的属性分类,返回一个map,map的key就是该属性的性质
  2. GeoLocation的使用
  3. 分页查询,stream流后面记得加上.collect(Collectors.toList());但是这样可能由于一次性处理太多导致爆错,可以改成forEach;opsForGeo()查询语法
1.通过stream,将list中的元素按照元素的属性分类,返回一个map,map的key就是该属性的性质(7)
2.GeoLocation的使用(15-24),只操作一次数据库,现创建出一个空的List<RedisGeoCommands.GeoLocation>,再遍历将数据添加到这个对象(17-22),然后一次性操作redis(24)
public static class GeoLocation<T> {
    private final T name;
    private final Point point;
@Test
void loadShopData(){
    //1.查询店铺信息
    List<Shop> list = shopService.list();
    //2.把店铺按照typeId分组,typeId一致的放到一个集合中
    //Map<Long,List<Shop>> map = new HashMap<>();
    Map<Long, List<Shop>> map = list.stream().collect(Collectors.groupingBy(Shop::getTypeId));
    //3.分批完成写入redis
    for (Map.Entry<Long, List<Shop>> entry : map.entrySet()) {
        //3.1.获取类型id
        Long typeId = entry.getKey();
        String key = SHOP_GEO_KEY + typeId;
        //3.2.获取同类型的店铺的集合
        List<Shop> value = entry.getValue();
        List<RedisGeoCommands.GeoLocation<String>> locations = new ArrayList<>(value.size());
        //3.3.写入redis GEOADD key 经度 纬度 member
        for (Shop shop : value) {
            //stringRedisTemplate.opsForGeo().add(key,new Point(shop.getX(),shop.getY()),shop.getId().toString());
            locations.add(new RedisGeoCommands.GeoLocation<>(
                    shop.getId().toString(),new Point(shop.getX(),shop.getY()))
            );
        }
        //只需要操作一个redis
        stringRedisTemplate.opsForGeo().add(key,locations);
    }
}

image-20260115133021268

插件maven-helper快速exclude依赖

3.分页查询,stream流后面记得加上.collect(Collectors.toList());(49-51)但是这样可能由于一次性处理太多导致爆错,可以改成forEach;opsForGeo()查询语法(18-24)
@Override
public Result queryShopByTypeId(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;
    //3.查询redis、按照距离排序、分页。结果:shopId、distance
    String key = SHOP_GEO_KEY + typeId;
    GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo()//GEOSEARCH KEY BYLONLAT X Y BYRADIUS 10 WITHDISTANCE
            .search(
                    key,
                    GeoReference.fromCoordinate(x, y), //BYLONLAT X Y
                    new Distance(5000),//BYRADIUS 10
                    RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end)//WITHDISTANCE和分页
            );
    //4.解析出id
    if (results == null){
        return Result.ok(Collections.emptyList());
    }
    List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();
    //4.1.截取
    List<Long> ids = new ArrayList<>(list.size());
    Map<String,Distance> distanceMap = new HashMap<>(list.size());
    //防止跳过的部分比总数还多
    if (list.size()<=from){
        //没有下一页了,结束
        return Result.ok(Collections.emptyList());
    }
    list.stream().skip(from).forEach(result ->{
        //4.2.获取店铺id
        String shopIdStr = result.getContent().getName();
        ids.add(Long.valueOf(shopIdStr));
        //4.3.获取距离
        Distance distance = result.getDistance();
        distanceMap.put(shopIdStr,distance);
    });
    //5.根据id查询shop
    String idStr = StrUtil.join(",", ids);
    List<Shop> shops = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();
    shops.stream().map(shop ->
            shop.setDistance(distanceMap.get(shop.getId().toString()).getValue())//Distance类里面除了具体的value数字,还有metric单位
    ).collect(Collectors.toList());
    //6.返回
    return Result.ok(shops);
}

用户签到

BitMap

image-20260115144655173

位图BitMap:把bite位与某种状态进行映射

布隆过滤器的底层也是基于BitMap

image-20260115145118474

512=29,1M=220,1B=2^3, 512MB=2^32b

image-20260115145307905

image-20260115150322805

bitfield bm1 get u2 0一般用来查找,因为修改用bitset更方便

u表示:BITFIELD命令中,u代表Unsigned(无符号整数),就是不带正负号;2表示查两个;0表示从0号位开始查

签到功能

image-20260115152122459

技术栈

  1. 将LocalDateTime.now()时间日期数据转换为自定义的格式,年月日
  2. 获取本月的第几天
1.将LocalDateTime.now()时间日期数据转换为自定义的格式,年月日(8)
2.获取本月的第几天(11)
@Override
public Result sign() {
    //1.获取当前登录的用户
    Long userId = UserHolder.getUser().getId();
    //2.获取日期
    LocalDateTime now = LocalDateTime.now();
    //3.拼接key
    String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyy:MM"));
    String key = USER_SIGN_KEY + userId + keySuffix;
    //4.获取今天是本月的第几天
    int dayOfMonth = now.getDayOfMonth();
    //5.写入redis SETBIT key offset 1
    stringRedisTemplate.opsForValue().setBit(key,dayOfMonth-1,true);
    return Result.ok();
}

签到统计

image-20260115155343938

知识点

  1. | (按位或) 相当于“加法/合并”:只要有一个 1,结果就是 1。它会让数字变“大”。

    & (按位与) 相当于“过滤/提取”:必须两个都是 1,结果才是 1。它会让数字变“小”。

image-20260115155426752

技术栈

  1. bitField的使用,有关BitFieldSubCommands的编辑

1.bitField的使用,有关BitFieldSubCommands的编辑(13-15)

@Override
public Result signCount() {
    //1.获取当前登录的用户
    Long userId = UserHolder.getUser().getId();
    //2.获取日期
    LocalDateTime now = LocalDateTime.now();
    //3.拼接key
    String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyy:MM"));
    String key = USER_SIGN_KEY + userId + keySuffix;
    //4.获取今天是本月的第几天
    int dayOfMonth = now.getDayOfMonth();
    //5.获取本月截止今天为止的所有的签到记录,返回的是一个十进制的数字
    List<Long> result = stringRedisTemplate.opsForValue().bitField(
            key, BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)
    );
    if (result == null||result.isEmpty()){
        //没有任何签到结果
        return Result.ok(0);
    }
    Long num = result.get(0);
    if (num == null||num == 0){
        return Result.ok(0);
    }
    //6.循环遍历
    int count = 0;
    while (true){
        //6.1.让这个数字与1做&运算,得到数字的最后一个bit位
        //6.2.判断这个bit位是否为0
        if ((num & 1) == 0){
            //6.3.如果为0,说明未签到,结束
            break;
        }else {
            //6.4.如果不为0,说明已签到,计数器+1
            count++;
        }
        //6.5.把数字右移一位,抛弃最后一位bit位
        num>>>=1;
    }
    return Result.ok(count);
}

UV统计

image-20260115162012019

image-20260115162314113

image-20260115162804170

附带滤重

image-20260115162901135

知识点

  1. 查看linux中redis的内存

​ 127.0.0.1:6379> info memory

​ Memory

​ used_memory:2120392
​ used_memory_human:2.02M

image-20260115163059979

技术栈:

  1. 大量数据分批次插入
  2. opsForHyperLogLog方法的使用
1.大量数据分批次插入(5-12)
2.opsForHyperLogLog方法的使用
@Test
void testHyperLoglog(){
    String[] values = new String[1000];
    int j = 0;
    for (int i = 0;i<1000000;i++){
        j = i % 1000;
        values[j] = "user_" + i;
        if (j == 999){
            //发送到redis
            stringRedisTemplate.opsForHyperLogLog().add("hl2",values);
        }
    }
    //统计数量
    Long count = stringRedisTemplate.opsForHyperLogLog().size("hl2");
    System.out.println("count = " + count);
}
posted @ 2026-01-15 17:05  David大胃  阅读(0)  评论(0)    收藏  举报