每日一Go-43、Go+Redis实现附近的人和场馆

0 阅读4分钟

    很多社交APP都有附近的人和附近的店铺、骑手等功能,那么这个功能是如何实现的呢?其实是利用Redis的GEO命令。

图片

一、为什么选Redis?

Redis提供了GEO系列命令,底层基于GeoHash+ZSet,有如下优势:

  • 内存级别速度

  • 支持距离、范围、排序

  • 不用自己写复杂索引

特别适合的场景有:

  • 附近的人

  • 附近的店铺

  • 附近的司机/骑手

二、Redis GEO核心命令

命令作用
GEOADD添加位置
GEOPOS查询坐标
GETDIST计算距离
GEORADIUS按坐标查附近半径内的人
GEORADIUSBYMEMBER按某人查附近
GEOSEARCH功能和GEORADIUS一样,但是可以知道查询范围为长方形区域

新版的redis推荐用GETSEARCH,而不是GEORADIUS。

三、直接写代码吧

3.1 准备全局Redis客户端 

internal/global/redis.go

/*
generated by comer,https://github.com/imoowi/comer
Copyright © 2023 jun<simpleyuan@gmail.com>
*/
package global
import (
    "github.com/redis/go-redis/v9"
    "github.com/spf13/cast"
    "github.com/spf13/viper"
    "go.uber.org/zap"
)
// 全局Redis客户端
var Redis *redis.Client
// 初始化redis
func initRedis() {
    addr := viper.GetString("redis.addr")
    if addr == "" {
        panic("请在配置文件里配置【redis.addr")
    }
    pass := viper.GetString("redis.password")
    if pass == "" {
        panic("请在配置文件里配置【redis.password")
    }
    db := viper.GetString("redis.db")
    if db == "" {
        panic("请在配置文件里配置【redis.db")
    }
    Redis = redis.NewClient(&redis.Options{
        Addr:     addr,
        Password: pass,
        DB:       cast.ToInt(db),
        PoolSize: 10,
    })
    zap.L().Info("Redis连接地址:", zap.String("addr", addr))
}

3.2 把用户的经纬度加入Redis GEO

internal/services/nearby.service.go

const LocationKey = "user_locations"
func (s *NearbyService) AddNearby(c *gin.Context, n *models.Nearby) (uinterror) {
    newId, err := s.Add(c, n)
    if err != nil {
        return 0, err
    }
    if newId > 0 {
        //把经纬度加入到redis
        global.Redis.GeoAdd(context.Background(), LocationKey, &redis.GeoLocation{
            Name: cast.ToString(c.GetUint("userId")),
            Latitude:  n.Latitude,
            Longitude: n.Longitude,
        })
    }
    return newId, err
}

3.3 查找坐标范围内的人

func (s *NearbyService) GetNearby(c *gin.Context, filter *models.NearbyFilter) (nearbys []*models.Nearby, err error) {
    //从redis中获取经纬度
    query := &redis.GeoSearchLocationQuery{
        GeoSearchQuery: redis.GeoSearchQuery{
            Member:     cast.ToString(c.GetUint("userId")),
            Longitude:  filter.CenterLon,
            Latitude:   filter.CenterLat,
            Radius:     filter.Radius, //查找的半径,单位是米
            RadiusUnit: "km",          //半径单位,m表示米,km表示千米,mi表示英里,ft表示英尺
            Sort:       "asc",         //排序方式,asc表示由近到远,desc表示由远到近
            Count:      100,           //返回的最大数量
        },
        WithCoord: true,  //返回经纬度
        WithDist:  true,  //返回距离
        WithHash:  false//返回位置的hash值
    }
    locations, err := global.Redis.GeoSearchLocation(context.Background(),
        LocationKey,
        query).Result()
    if err != nil {
        return nil, err
    }
    for _, location := range locations {
        n := &models.Nearby{}
        n.UserID = cast.ToUint(location.Name)
        n.Latitude = location.Latitude
        n.Longitude = location.Longitude
        n.Distance = location.Dist
        if location.Dist <= 0.5 {
            //不返回精确距离
            n.Distance = 0.5
        }
        //取出用户信息,方便展示用户的昵称和头像等基本信息
        n.User, _ = models.GetWechatUserById(n.UserID, global.MysqlDb.Client)
        nearbys = append(nearbys, n)
    }
    return
}

四、真实业务一定要加的过滤逻辑

Redis只负责“空间筛选”,但业务还需要二次过滤。

常见的过滤逻辑:

  • 排除自己

  • 不在线的用户

  • 被拉黑/屏蔽的用户

  • 性别/年龄筛选条件

  • 最近活跃时间

五、注意事项

    除了打车和外卖类需要显示精确距离的App以外。需要附近功能的设计软件一定要注意:做到500米以下的距离,尽量不要精确显示距离,要模糊显示<500m,否则很容易被人开盒。

六、为什么“附近的人”会存在开盒风险?

大多数社交软件为了保护隐私,通常只显示距离(距你500米),而不会在地图上标出对方的红点。但是,利用简单的数学原理--三点定位法,坏人可以轻松实现“开盒”。什么是三点定位法,有知道的请在留言区科普一下

七、如何防止用户被“开盒”?

    作为开发者,如果你直接把Redis GEO返回的精确距离给前端,就是在给“开盒”提供便利。以下是几种常见的防开盒技术方案:

7.1 按距离分段模糊化 

    不返回1m、10m、499m这样精确数字:在后端对距离进行取整或分档。500米内统一返回 “<500m”,一公里内返回“1km内”。这样坏人的三点定位定出来的点就无法交汇成一个点。

7.2 坐标随机偏移

    在将位置存入Redis前或者从Redis取出返回的时候,加入一个随机的微小偏移量,lat=lat + random(-0.001,0.001), 坏人看到的距离永远是波动的,三点定位法就会失效。

7.3 动态掩码

    不计算实际距离,而是将地图划分为若干个GeoHash网格。告诉用户“这个人在【莲花池】区域”,而不是“她在你身后一米远”。要是精确显示了距离,简直就是灾难!

7.4 频率限制

    针对同一用户,限制其短时间内查询“附近的人”的次数,或者检测其是否在短时间内进行了大幅度的“位置瞬移”。阻断自动化脚本通过虚拟定位频繁采集距离数据。

一句话总结:“附近的人”本质是用 Redis GEO 做空间粗筛、业务做精筛,而真正考验一个社交产品成熟度的,不是能不能算出距离,而是敢不敢把‘精确距离’藏起来。

加班费计算器小程序 :

微信搜索“加班计”.png *源码地址*

1、公众号“Codee君”回复“每日一Go”获取源码

2、pan.baidu.com/s/1B6pgLWfS…


如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!