Redis实战—附近商铺、用户签到、UV统计

news/2024/8/28 5:47:08 标签: redis, bootstrap, 前端

  本博客为个人学习笔记,学习网站与详细见:黑马程序员Redis入门到实战 P88 - P95

目录

附近商铺

数据导入 

功能实现

用户签到

签到功能

连续签到统计 

UV统计


附近商铺

利用Redis中的GEO数据结构实现附近商铺功能,常见命令如下图所示。 

key值由特定前缀与商户类型id组成,每个GEO存储一个店铺id与该店铺的经纬度信息,如下图所示。


数据导入 

编写单元测试,将MySql数据库中的所有商铺位置信息导入Redis中,代码如下。

@Test
void loadShopData() {
    // 1.查询所有店铺信息
    List<Shop> shops = shopService.list();
    // 2.将店铺按照typeId分组,typeId一致的放到一个集合中
    Map<Long, List<Shop>> map = shops.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:" + typeId;
        // 3.2 获取同类型的店铺集合
        List<Shop> list = entry.getValue();

        // 3.3 写入redis( GEOADD key 经度 纬度 member)
        List<RedisGeoCommands.GeoLocation<String>> locations = new ArrayList<>(list.size());
        for (Shop shop : list) {
            locations.add(new RedisGeoCommands.GeoLocation<>(
                    shop.getId().toString(),
                    new Point(shop.getX(), shop.getY())
            ));
        }
        stringRedisTemplate.opsForGeo().add(key, locations);
    }
}

功能实现

由于SpringDataRedis的2.3.9版本并不支持Redis 6.2提供的GEOSEARCH命令,因此我们需要修改版本,代码如下。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>spring-data-redis</artifactId>
            <groupId>org.springframework.data</groupId>
        </exclusion>
        <exclusion>
            <artifactId>lettuce-core</artifactId>
            <groupId>io.lettuce</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>2.6.2</version>
</dependency>
<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>6.1.6.RELEASE</version>
</dependency>

Controller层代码如下。

@GetMapping("/of/type")
public Result queryShopByType(
        @RequestParam("typeId") Integer typeId,
        @RequestParam(value = "current", defaultValue = "1") Integer current,
        @RequestParam(value = "x", required = false) Double x,
        @RequestParam(value = "y", required = false) Double y
) {
    return shopService.queryShopByType(typeId, current, x, y);
}

接口方法的具体实现代码如下。

@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;

    // 3.查询redis、按照距离排序、分页。结果:shopId,distance
    String key = "shop:geo:" + typeId;
    GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo()
            .search(
                    key,
                    GeoReference.fromCoordinate(x, y),
                    new Distance(5000),
                    RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end)
            );

    // 4.解析出id
    if (results == null)
        return Result.ok(Collections.emptyList());
    List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();

    // 如果没有下一页,则结束
    if (list.size() < from)
        return Result.ok(Collections.emptyList());

    // 4.1 截取从from~end的部分
    ArrayList<Object> ids = new ArrayList<>(list.size());
    Map<String, Distance> distanceMap = new HashMap<>(list.size());
    list.stream().skip(from).forEach(result -> {
        // 4.2 获取店铺id
        String shopIdStr = result.getContent().getName();
        ids.add(Long.valueOf(shopIdStr));
        // 4.2 获取距离
        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();
    for (Shop shop : shops) {
        shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
    }
    // 6.返回
    return Result.ok(shops);
}

用户签到

签到功能


Controller层代码如下。 

@PostMapping("/sign")
public Result sign() {
    return userService.sign();
}

接口方法的具体实现代码如下。

@Override
public Result sign() {
    // 1.获取当前登录用户信息
    Long userId = UserHolder.getUser().getId();
    // 2.获取当前日期
    LocalDateTime now = LocalDateTime.now();
    // 3.拼接key
    String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
    String key = "sign:" + userId + keySuffix;
    // 4.计算今天是本月的第几天
    int dayOfMonth = now.getDayOfMonth();
    // 5.写入Redis SETBIT key offset
    // true:写入1
    // false:写入0
    stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
    return Result.ok();
}

连续签到统计 


Controller层代码如下。

@GetMapping("/sign/count")
public Result signCount() {
    return userService.signCount();
}

接口方法的具体实现代码如下。

@Override
public Result signCount() {
    // 1.获取当前登录用户信息
    Long userId = UserHolder.getUser().getId();
    // 2.获取当前日期
    LocalDateTime now = LocalDateTime.now();
    // 3.拼接key
    String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
    String key = "sign:" + userId + keySuffix;
    // 4.计算今天是本月的第几天
    int dayOfMonth = now.getDayOfMonth();
    // 5.获取本月截止今天为止的所有签到记录,返回结果是一个十进制数字 BITFIELD sign:5:202203 GET u14 0
    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 cnt = 0;//记录连续签到天数
    while (true) {
        if ((num & 1) == 0)
            break;
        num >>= 1;
        cnt++;
    }
    return Result.ok(cnt);
}

UV统计


测试
我们直接利用单元测试,向HyperLogLog中添加100万条数据,看看统计效果如何,测试代码如下。

@Test
void testHyperLogLog() {
    // 准备数组,装用户数据
    String[] users = new String[1000];
    // 数组角标
    int index = 0;
    for (int i = 1; i <= 1000000; i++) {
        // 赋值
        users[index++] = "user_" + i;
        // 每1000条发送一次
        if (i % 1000 == 0) {
            index = 0;
            stringRedisTemplate.opsForHyperLogLog().add("hll1", users);
        }
    }
    // 统计数量
    Long size = stringRedisTemplate.opsForHyperLogLog().size("hll1");
    System.out.println("size =" + size);
}

测试结果如下图所示。

误差 = 1 - (997593 / 1000000)≈ 0.002 可忽略不计


http://www.niftyadmin.cn/n/5559360.html

相关文章

Linux的进程和权限的基本命令

目录 基本命令 man find date cal du ln exit grep 基本命令-帮助查询&#xff1a; wc cat more less head tail echo alias unalias 基本命令-进程管理&#xff1a; ps kill top 操作系统负载查看 用户分类&#xff1a; 程序用户 普通用户&#x…

算法项目报告:物流中的最短路径问题

问题描述 物流问题 有一个物流公司需要从起点A到终点B进行货物运输&#xff0c;在运输过程中&#xff0c;该公司需要途径多个不同的城市&#xff0c;并且在每个城市中都有一个配送站点。为了最大程度地降低运输成本和时间&#xff0c;该公司需要确定经过哪些配送站点&#xff…

摄像头 RN6752v1 视频采集卡

摄像头 AHD倒车摄像头比较好&#xff0c;AHD英文全名Analog High Definition&#xff0c;即模拟高清&#xff0c;拥有比较好的分辨率与画面质感。 RN6752v1 GQW AKKY2 usb 采集卡 FHD&#xff08;1080p&#xff09;、HD&#xff08;720p&#xff09;和D1&#xff08;480i&am…

常用的点云预处理算法

点云预处理是处理点云数据时的重要部分&#xff0c;其目的是提高点云数据的质量和处理效率。通过去除离群点、减少点云密度和增强特征&#xff0c;可以消除噪声、减少计算量、提高算法的准确性和鲁棒性&#xff0c;从而为后续的点云处理和分析步骤&#xff08;如配准、分割和重…

7/13 - 7/15

vo.setId(rs.getLong("id"))什么意思&#xff1f; vo.setId(rs.getLong("id")); 这行代码是在Java中使用ResultSet对象&#xff08;通常用于从数据库中检索数据&#xff09;获取一个名为"id"的列&#xff0c;并将其作为long类型设置为一个对象…

夏令营入门组day2

目录 一. 试卷 二. 专题&#xff1a;贪心&#xff0c;构造 一. 试卷 1. 资产 &#xff08;1&#xff09;错误思路&#xff1a; 每一步都取绝对值 若下一个数为负且当前和为负时不取绝对值&#xff0c;若下一个数为正且当前和为负时取绝对值。反例&#xff1a;-5 -3 2 -1 -7 在…

C#实现自定义标签的设计和打印

背景:最近在进行资产盘点的时候,需要对固定资产设计标签并进行打印。 设计标签:选用的是Fastreport自带的,可拆包忌用的标签设计器;进行标签的模型设计。 软件解压后可直接进行使用。模板的设计基本都是无脑操作,拖拽控件按,放置到固定未知即可;我设计的模板如下: 说…

监控都没做好,还可观测性。。。

很多公司听说可观测性好&#xff0c;就要上马可观测性项目&#xff0c;自研/采购&#xff0c;各种投入&#xff0c;结果发现效果很差&#xff0c;业务不认可&#xff0c;最终一地鸡毛。其实凡事总有个过程和时机问题&#xff0c;在落地可观测性之前&#xff0c;我建议你先看看你…