【redis实战篇】第八天

摘要:

本文主要介绍redis中GEO和BitMap结构的基本用法和用处,并基于这两种结构实现java项目黑马点评的实际功能--根据距离查询附近商铺以及签到和签到统计

一,根据距离查询商铺功能

1,GEO介绍

GEO(地理空间)结构是一种用于存储地理坐标数据,并支持基于地理位置的查询功能的数据类型。其本质上是通过有序集合(ZSET)实现的,支持附近位置查询、距离计算等操作

常用命令:

(1)GEOADD key longitude latitude member [longitude latitude member ...]

**(2)GEOPOS key member [member ...] **查询成员的经纬度坐标,返回数组形式的结果

**(3)GEODIST key member1 member2 [unit] **计算两个坐标点之间的距离

**(4)GEORADIUS key longitude latitude radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] **以指定坐标为中心,查询半径内的所有点,支持返回距离、坐标等信息

2,代码实现

(1)redis中插入数据,将商铺类型作为key,商铺id作为member,使用stearm流将将商店根据类型进行分组得到map<类型id,list<商铺实体>>,最后构造locations数组批量写入

java 复制代码
List<Shop> list = shopService.list();
//按照typeId分组
Map<Long, List<Shop>> map = list.stream().collect(Collectors.groupingBy(Shop::getTypeId));
for (Map.Entry<Long, List<Shop>> entry : map.entrySet()) {
    Long typeId = entry.getKey();
    List<Shop> shops = entry.getValue();
    String key = "shop:geo:" + typeId;
    List<RedisGeoCommands.GeoLocation<String>> locations = new ArrayList<>(shops.size());
    //将分组结果写入redis  geoadd key  longitude latitude lo la.... typeId
    //list集合数据放入locations集合中shop-->GeoLocation(name,point)
    for (Shop shop : shops) {
        locations.add(new RedisGeoCommands.GeoLocation<>(
                shop.getId().toString(),new Point(shop.getX(),shop.getY())));
    }
    //批量写入
    stringRedisTemplate.opsForGeo().add(key,locations);
}

(2)如果前端携带的经纬坐标为空,执行传统的数据库查询(分页)

java 复制代码
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());
}

(3)根据默认的页数5计算分页参数(起始),通过前端携带的经纬坐标xy为圆心,距离默认5km,添加返回条件--商铺到xy距离以及limit范围

java 复制代码
        int from = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE;
        int end = current * SystemConstants.DEFAULT_PAGE_SIZE;
        //查询redis
        String key = SHOP_GEO_KEY + typeId;
        //limit默认查询从0-end,需要手动截取
        GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo().search(key,
                //圆心
                GeoReference.fromCoordinate(x, y),
                //半径
                new Distance(5000),
                //返回条件--返回距离和上限
                RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end)
        );
        if (results==null) {
            return Result.ok(Collections.emptyList());
        }

(4)拿到0-end部分的数据(包括距离、member和point坐标),同时判断是否还存在下一页,不存在则返回空集合

java 复制代码
List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();
//判断是否还有下一页
if (list.size()<=from) {
   return Result.ok(Collections.emptyList());
}

(5)将from到end部分的数据通过ids商铺id集合和map每个商铺对应距离集合收集,使用stream流的skip跳过from前的数据,保证数据为当前页

java 复制代码
        //截取from-end部分,ids和map收集起来
        List<Long> ids = new ArrayList<>(list.size());
        Map<String, Distance> distanceMap = new HashMap<>(list.size());
        //skip跳过手动截取
        list.stream().skip(from).forEach(result->{
            //获取店铺id和距离
            String shopId = result.getContent().getName();
            Distance distance = result.getDistance();
            //提取
            ids.add(Long.valueOf(shopId));
            distanceMap.put(shopId, distance);
        });

(6)批量查询商铺信息,并遍历所有商铺添加对应的距离参数,返回符合条件商铺结果

java 复制代码
        //根据ids批量查询shop
        String join = StrUtil.join(",",ids);
        List<Shop> shops = query().in("id", ids)
                .last("order by field(id," + join + ")").list();
        for (Shop shop : shops) {
            shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
        }

二,用户签到功能

1,bitMap简单介绍

BitMap 本质是字符串(String),但按位(bit)而非字节(byte)操作,每个位存储 0 或 1(布尔值)

常用命令:

(1)SETBIT key offset value :设置偏移量offset的位为value(0 或 1)

(2)GETBIT key offset :获取偏移量offset的位值

(3)BITCOUNT key [start end]:统计指定范围内置为 1 的位数

2,签到实现

(1)原理:将每个用户这个月的签到情况使用bitMap表示,签到置1,未签到默认0,用户id拼接该月日期作为key,根据签到情况添加value

(2)因为偏移量是逻辑位置,所以存入时要对今天为该月天数减一

java 复制代码
Long userId = UserHolder.getUser().getId();
LocalDateTime now = LocalDateTime.now();
//拼接key
String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
String key = USER_SIGN_KEY + userId + keySuffix;
//今天是这个月的第几天
int dayOfMonth = now.getDayOfMonth();
//存入redis的bitMap中,第一天存在第0位
Boolean success = stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
return Result.ok("恭喜签到成功!");

3,统计连续签到天数(0111-->3天

(1)取出当前用户该月签到情况返回的十进制数num

java 复制代码
        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);
        }
        //取出bitMap的十进制数
        Long num = result.getFirst();
        if (num==null||num==0) {
            return Result.ok(0);
        }

(2)从后遍历每个bit位-->使这个数与1做与运算,如果结果为1则累加天数count,将num右移一位并赋值给原num,否则认为签到中断break。

java 复制代码
        int count = 0;
        while (true) {
            if ((num&1)==0) {
                break;
            }else {
                count++;
            }
            //右移一位并赋值给原num
            num >>>= 1;
        }
相关推荐
NCIN EXPE4 小时前
redis 使用
数据库·redis·缓存
MongoDB 数据平台4 小时前
为编码代理引入 MongoDB 代理技能和插件
数据库·mongodb
极客on之路4 小时前
mysql explain type 各个字段解释
数据库·mysql
代码雕刻家4 小时前
MySQL与SQL Server的基本指令
数据库·mysql·sqlserver
lThE ANDE4 小时前
开启mysql的binlog日志
数据库·mysql
hERS EOUS4 小时前
nginx 代理 redis
运维·redis·nginx
yejqvow124 小时前
CSS如何控制placeholder文字的颜色_使用--placeholder伪元素
jvm·数据库·python
oLLI PILO4 小时前
nacos2.3.0 接入pgsql或其他数据库
数据库
m0_743623924 小时前
HTML怎么创建多语言切换器_HTML语言选择下拉结构【指南】
jvm·数据库·python