service/library/redis/redis.go

816 lines
28 KiB
Go
Raw Normal View History

2023-12-21 22:17:40 +08:00
// Package redisgo 基于github.com/gomodule/redigo封装了redis缓存的实现。
// 使用时,可以参考 http://redisdoc.com/ 和 http://www.runoob.com/redis/ 中对redis命令及用法的讲解。
// 对于没有封装的命令,也可以参考这里的实现方法,直接调用 `Do` 方法直接调用redis命令。
package redis
import (
"encoding/json"
"errors"
"fmt"
"os"
"os/signal"
"syscall"
"time"
// "github.com/aiscrm/cache"
"github.com/gomodule/redigo/redis"
)
// Client 先构建一个Client实例然后将配置参数传入该实例的StartAndGC方法来初始化实例和程序进程退出后的清理工作。
type Client struct {
pool *redis.Pool
prefix string
marshal func(v interface{}) ([]byte, error)
unmarshal func(data []byte, v interface{}) error
}
// Options redis配置参数
type Options struct {
Network string // 通讯协议,默认为 tcp
Addr string // redis服务的地址默认为 127.0.0.1:6379
Password string // redis鉴权密码
Db int // 数据库
MaxActive int // 最大活动连接数值为0时表示不限制
MaxIdle int // 最大空闲连接数
IdleTimeout int // 空闲连接的超时时间超过该时间则关闭连接。单位为秒。默认值是5分钟。值为0时表示不关闭空闲连接。此值应该总是大于redis服务的超时时间。
Prefix string // 键名前缀
Marshal func(v interface{}) ([]byte, error) // 数据序列化方法默认使用json.Marshal序列化
Unmarshal func(data []byte, v interface{}) error // 数据反序列化方法默认使用json.Unmarshal序列化
}
// StartAndGC 使用 Options 初始化redis并在程序进程退出时关闭连接池。
func (c *Client) StartAndGC(options interface{}) error {
switch opts := options.(type) {
case Options:
if opts.Network == "" {
opts.Network = "tcp"
}
if opts.Addr == "" {
opts.Addr = "127.0.0.1:6379"
}
if opts.MaxIdle == 0 {
opts.MaxIdle = 3
}
if opts.IdleTimeout == 0 {
opts.IdleTimeout = 300
}
if len(opts.Prefix) > 0 {
c.prefix = opts.Prefix
}
if opts.Marshal == nil {
c.marshal = json.Marshal
}
if opts.Unmarshal == nil {
c.unmarshal = json.Unmarshal
}
pool := &redis.Pool{
MaxActive: opts.MaxActive,
MaxIdle: opts.MaxIdle,
IdleTimeout: time.Duration(opts.IdleTimeout) * time.Second,
Dial: func() (redis.Conn, error) {
conn, err := redis.Dial(opts.Network, opts.Addr)
if err != nil {
return nil, err
}
if opts.Password != "" {
if _, err := conn.Do("AUTH", opts.Password); err != nil {
conn.Close()
return nil, err
}
}
if _, err := conn.Do("SELECT", opts.Db); err != nil {
conn.Close()
return nil, err
}
return conn, err
},
TestOnBorrow: func(conn redis.Conn, t time.Time) error {
_, err := conn.Do("PING")
return err
},
}
c.pool = pool
c.closePool()
return nil
default:
return errors.New("Unsupported options")
}
}
// Do 执行redis命令并返回结果。执行时从连接池获取连接并在执行完命令后关闭连接。
func (c *Client) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
conn := c.pool.Get()
defer conn.Close()
return conn.Do(commandName, args...)
}
// Get 获取键值。一般不直接使用该值而是配合下面的工具类方法获取具体类型的值或者直接使用github.com/gomodule/redigo/redis包的工具方法。
func (c *Client) Get(key string) (interface{}, error) {
return c.Do("GET", c.getKey(key))
}
// GetString 获取string类型的键值
func (c *Client) GetString(key string) (string, error) {
return String(c.Get(key))
}
// GetInt 获取int类型的键值
func (c *Client) GetInt(key string) (int, error) {
return Int(c.Get(key))
}
// GetInt64 获取int64类型的键值
func (c *Client) GetInt64(key string) (int64, error) {
return Int64(c.Get(key))
}
// GetBool 获取bool类型的键值
func (c *Client) GetBool(key string) (bool, error) {
return Bool(c.Get(key))
}
// GetObject 获取非基本类型stuct的键值。在实现上使用json的Marshal和Unmarshal做序列化存取。
func (c *Client) GetObject(key string, val interface{}) error {
reply, err := c.Get(key)
return c.decode(reply, err, val)
}
// Set 存并设置有效时长。时长的单位为秒。
// 基础类型直接保存其他用json.Marshal后转成string保存。
func (c *Client) Set(key string, val interface{}, expire int64) error {
value, err := c.encode(val)
if err != nil {
return err
}
if expire > 0 {
_, err := c.Do("SETEX", c.getKey(key), expire, value)
return err
}
_, err = c.Do("SET", c.getKey(key), value)
return err
}
// Exists 检查键是否存在
func (c *Client) Exists(key string) (bool, error) {
return Bool(c.Do("EXISTS", c.getKey(key)))
}
// Del 删除键
func (c *Client) Del(key string) error {
_, err := c.Do("DEL", c.getKey(key))
return err
}
// Flush 清空当前数据库中的所有 key慎用
func (c *Client) Flush() error {
_, err := c.Do("FLUSHDB")
return err
}
// TTL 以秒为单位。当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1
func (c *Client) TTL(key string) (ttl int64, err error) {
return Int64(c.Do("TTL", c.getKey(key)))
}
// Expire 设置键过期时间expire的单位为秒
func (c *Client) Expire(key string, expire int64) error {
_, err := Bool(c.Do("EXPIRE", c.getKey(key), expire))
return err
}
// Incr 将 key 中储存的数字值增一
func (c *Client) Incr(key string) (val int64, err error) {
return Int64(c.Do("INCR", c.getKey(key)))
}
// IncrBy 将 key 所储存的值加上给定的增量值increment
func (c *Client) IncrBy(key string, amount int64) (val int64, err error) {
return Int64(c.Do("INCRBY", c.getKey(key), amount))
}
// Decr 将 key 中储存的数字值减一。
func (c *Client) Decr(key string) (val int64, err error) {
return Int64(c.Do("DECR", c.getKey(key)))
}
// DecrBy key 所储存的值减去给定的减量值decrement
func (c *Client) DecrBy(key string, amount int64) (val int64, err error) {
return Int64(c.Do("DECRBY", c.getKey(key), amount))
}
// HMSet 将一个map存到Redis hash同时设置有效期单位
// Example:
//
// ```golang
// m := make(map[string]interface{})
// m["name"] = "corel"
// m["age"] = 23
// err := c.HMSet("user", m, 10)
// ```
func (c *Client) HMSet(key string, val interface{}, expire int) (err error) {
conn := c.pool.Get()
defer conn.Close()
err = conn.Send("HMSET", redis.Args{}.Add(c.getKey(key)).AddFlat(val)...)
if err != nil {
return
}
if expire > 0 {
err = conn.Send("EXPIRE", c.getKey(key), int64(expire))
}
if err != nil {
return
}
conn.Flush()
_, err = conn.Receive()
return
}
/** Redis hash 是一个string类型的field和value的映射表hash特别适合用于存储对象。 **/
// HSet 将哈希表 key 中的字段 field 的值设为 val
// Example:
//
// ```golang
// _, err := c.HSet("user", "age", 23)
// ```
func (c *Client) HSet(key, field string, val interface{}) (interface{}, error) {
value, err := c.encode(val)
if err != nil {
return nil, err
}
return c.Do("HSET", c.getKey(key), field, value)
}
// HGet 获取存储在哈希表中指定字段的值
// Example:
//
// ```golang
// val, err := c.HGet("user", "age")
// ```
func (c *Client) HGet(key, field string) (reply interface{}, err error) {
reply, err = c.Do("HGET", c.getKey(key), field)
return
}
// HGetString HGet的工具方法当字段值为字符串类型时使用
func (c *Client) HGetString(key, field string) (reply string, err error) {
reply, err = String(c.HGet(key, field))
return
}
// HGetInt HGet的工具方法当字段值为int类型时使用
func (c *Client) HGetInt(key, field string) (reply int, err error) {
reply, err = Int(c.HGet(key, field))
return
}
// HGetInt64 HGet的工具方法当字段值为int64类型时使用
func (c *Client) HGetInt64(key, field string) (reply int64, err error) {
reply, err = Int64(c.HGet(key, field))
return
}
// HGetBool HGet的工具方法当字段值为bool类型时使用
func (c *Client) HGetBool(key, field string) (reply bool, err error) {
reply, err = Bool(c.HGet(key, field))
return
}
// HGetObject HGet的工具方法当字段值为非基本类型的stuct时使用
func (c *Client) HGetObject(key, field string, val interface{}) error {
reply, err := c.HGet(key, field)
return c.decode(reply, err, val)
}
// HGetAll HGetAll("key", &val)
func (c *Client) HGetAll(key string, val interface{}) error {
v, err := redis.Values(c.Do("HGETALL", c.getKey(key)))
if err != nil {
return err
}
if err := redis.ScanStruct(v, val); err != nil {
fmt.Println(err)
}
//fmt.Printf("%+v\n", val)
return err
}
/**
Redis列表是简单的字符串列表按照插入顺序排序你可以添加一个元素到列表的头部左边或者尾部右边
**/
// BLPop 它是 LPOP 命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被 BLPOP 命令阻塞,直到等待超时或发现可弹出元素为止。
// 超时参数 timeout 接受一个以秒为单位的数字作为值。超时参数设为 0 表示阻塞时间可以无限期延长(block indefinitely) 。
func (c *Client) BLPop(key string, timeout int) (interface{}, error) {
values, err := redis.Values(c.Do("BLPOP", c.getKey(key), timeout))
if err != nil {
return nil, err
}
if len(values) != 2 {
return nil, fmt.Errorf("redisgo: unexpected number of values, got %d", len(values))
}
return values[1], err
}
// BLPopInt BLPop的工具方法元素类型为int时
func (c *Client) BLPopInt(key string, timeout int) (int, error) {
return Int(c.BLPop(key, timeout))
}
// BLPopInt64 BLPop的工具方法元素类型为int64时
func (c *Client) BLPopInt64(key string, timeout int) (int64, error) {
return Int64(c.BLPop(key, timeout))
}
// BLPopString BLPop的工具方法元素类型为string时
func (c *Client) BLPopString(key string, timeout int) (string, error) {
return String(c.BLPop(key, timeout))
}
// BLPopBool BLPop的工具方法元素类型为bool时
func (c *Client) BLPopBool(key string, timeout int) (bool, error) {
return Bool(c.BLPop(key, timeout))
}
// BLPopObject BLPop的工具方法元素类型为object时
func (c *Client) BLPopObject(key string, timeout int, val interface{}) error {
reply, err := c.BLPop(key, timeout)
return c.decode(reply, err, val)
}
// BRPop 它是 RPOP 命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被 BRPOP 命令阻塞,直到等待超时或发现可弹出元素为止。
// 超时参数 timeout 接受一个以秒为单位的数字作为值。超时参数设为 0 表示阻塞时间可以无限期延长(block indefinitely) 。
func (c *Client) BRPop(key string, timeout int) (interface{}, error) {
values, err := redis.Values(c.Do("BRPOP", c.getKey(key), timeout))
if err != nil {
return nil, err
}
if len(values) != 2 {
return nil, fmt.Errorf("redisgo: unexpected number of values, got %d", len(values))
}
return values[1], err
}
// BRPopInt BRPop的工具方法元素类型为int时
func (c *Client) BRPopInt(key string, timeout int) (int, error) {
return Int(c.BRPop(key, timeout))
}
// BRPopInt64 BRPop的工具方法元素类型为int64时
func (c *Client) BRPopInt64(key string, timeout int) (int64, error) {
return Int64(c.BRPop(key, timeout))
}
// BRPopString BRPop的工具方法元素类型为string时
func (c *Client) BRPopString(key string, timeout int) (string, error) {
return String(c.BRPop(key, timeout))
}
// BRPopBool BRPop的工具方法元素类型为bool时
func (c *Client) BRPopBool(key string, timeout int) (bool, error) {
return Bool(c.BRPop(key, timeout))
}
// BRPopObject BRPop的工具方法元素类型为object时
func (c *Client) BRPopObject(key string, timeout int, val interface{}) error {
reply, err := c.BRPop(key, timeout)
return c.decode(reply, err, val)
}
// LPop 移出并获取列表中的第一个元素(表头,左边)
func (c *Client) LPop(key string) (interface{}, error) {
return c.Do("LPOP", c.getKey(key))
}
// LPopInt 移出并获取列表中的第一个元素表头左边元素类型为int
func (c *Client) LPopInt(key string) (int, error) {
return Int(c.LPop(key))
}
// LPopInt64 移出并获取列表中的第一个元素表头左边元素类型为int64
func (c *Client) LPopInt64(key string) (int64, error) {
return Int64(c.LPop(key))
}
// LPopString 移出并获取列表中的第一个元素表头左边元素类型为string
func (c *Client) LPopString(key string) (string, error) {
return String(c.LPop(key))
}
// LPopBool 移出并获取列表中的第一个元素表头左边元素类型为bool
func (c *Client) LPopBool(key string) (bool, error) {
return Bool(c.LPop(key))
}
// LPopObject 移出并获取列表中的第一个元素表头左边元素类型为非基本类型的struct
func (c *Client) LPopObject(key string, val interface{}) error {
reply, err := c.LPop(key)
return c.decode(reply, err, val)
}
// RPop 移出并获取列表中的最后一个元素(表尾,右边)
func (c *Client) RPop(key string) (interface{}, error) {
return c.Do("RPOP", c.getKey(key))
}
// RPopInt 移出并获取列表中的最后一个元素表尾右边元素类型为int
func (c *Client) RPopInt(key string) (int, error) {
return Int(c.RPop(key))
}
// RPopInt64 移出并获取列表中的最后一个元素表尾右边元素类型为int64
func (c *Client) RPopInt64(key string) (int64, error) {
return Int64(c.RPop(key))
}
// RPopString 移出并获取列表中的最后一个元素表尾右边元素类型为string
func (c *Client) RPopString(key string) (string, error) {
return String(c.RPop(key))
}
// RPopBool 移出并获取列表中的最后一个元素表尾右边元素类型为bool
func (c *Client) RPopBool(key string) (bool, error) {
return Bool(c.RPop(key))
}
// RPopObject 移出并获取列表中的最后一个元素表尾右边元素类型为非基本类型的struct
func (c *Client) RPopObject(key string, val interface{}) error {
reply, err := c.RPop(key)
return c.decode(reply, err, val)
}
// LPush 将一个值插入到列表头部
func (c *Client) LPush(key string, member interface{}) error {
value, err := c.encode(member)
if err != nil {
return err
}
_, err = c.Do("LPUSH", c.getKey(key), value)
return err
}
// RPush 将一个值插入到列表尾部
func (c *Client) RPush(key string, member interface{}) error {
value, err := c.encode(member)
if err != nil {
return err
}
_, err = c.Do("RPUSH", c.getKey(key), value)
return err
}
// LREM 根据参数 count 的值,移除列表中与参数 member 相等的元素。
// count 的值可以是以下几种:
// count > 0 : 从表头开始向表尾搜索,移除与 member 相等的元素,数量为 count 。
// count < 0 : 从表尾开始向表头搜索,移除与 member 相等的元素,数量为 count 的绝对值。
// count = 0 : 移除表中所有与 member 相等的值。
// 返回值:被移除元素的数量。
func (c *Client) LREM(key string, count int, member interface{}) (int, error) {
return Int(c.Do("LREM", c.getKey(key), count, member))
}
// LLen 获取列表的长度
func (c *Client) LLen(key string) (int64, error) {
return Int64(c.Do("RPOP", c.getKey(key)))
}
// LRange 返回列表 key 中指定区间内的元素,区间以偏移量 start 和 stop 指定。
// 下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
// 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
// 和编程语言区间函数的区别end 下标也在 LRANGE 命令的取值范围之内(闭区间)。
func (c *Client) LRange(key string, start, end int) (interface{}, error) {
return c.Do("LRANGE", c.getKey(key), start, end)
}
/**
Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员
不同的是每个元素都会关联一个double类型的分数redis正是通过分数来为集合中的成员进行从小到大的排序
有序集合的成员是唯一的,但分数(score)却可以重复
集合是通过哈希表实现的所以添加删除查找的复杂度都是O(1)
**/
// ZAdd 将一个 member 元素及其 score 值加入到有序集 key 当中。
func (c *Client) ZAdd(key string, score int64, member string) (reply interface{}, err error) {
return c.Do("ZADD", c.getKey(key), score, member)
}
// ZRem 移除有序集 key 中的一个成员,不存在的成员将被忽略。
func (c *Client) ZRem(key string, member string) (reply interface{}, err error) {
return c.Do("ZREM", c.getKey(key), member)
}
// ZScore 返回有序集 key 中,成员 member 的 score 值。 如果 member 元素不是有序集 key 的成员,或 key 不存在,返回 nil 。
func (c *Client) ZScore(key string, member string) (int64, error) {
return Int64(c.Do("ZSCORE", c.getKey(key), member))
}
// ZRank 返回有序集中指定成员的排名。其中有序集成员按分数值递增(从小到大)顺序排列。score 值最小的成员排名为 0
func (c *Client) ZRank(key, member string) (int64, error) {
return Int64(c.Do("ZRANK", c.getKey(key), member))
}
// ZRevrank 返回有序集中成员的排名。其中有序集成员按分数值递减(从大到小)排序。分数值最大的成员排名为 0 。
func (c *Client) ZRevrank(key, member string) (int64, error) {
return Int64(c.Do("ZREVRANK", c.getKey(key), member))
}
// ZRange 返回有序集中,指定区间内的成员。其中成员的位置按分数值递增(从小到大)来排序。具有相同分数值的成员按字典序(lexicographical order )来排列。
// 以 0 表示有序集第一个成员,以 1 表示有序集第二个成员,以此类推。或 以 -1 表示最后一个成员, -2 表示倒数第二个成员,以此类推。
func (c *Client) ZRange(key string, from, to int64) (map[string]int64, error) {
return redis.Int64Map(c.Do("ZRANGE", c.getKey(key), from, to, "WITHSCORES"))
}
// ZRevrange 返回有序集中,指定区间内的成员。其中成员的位置按分数值递减(从大到小)来排列。具有相同分数值的成员按字典序(lexicographical order )来排列。
// 以 0 表示有序集第一个成员,以 1 表示有序集第二个成员,以此类推。或 以 -1 表示最后一个成员, -2 表示倒数第二个成员,以此类推。
func (c *Client) ZRevrange(key string, from, to int64) (map[string]int64, error) {
return redis.Int64Map(c.Do("ZREVRANGE", c.getKey(key), from, to, "WITHSCORES"))
}
// ZRangeByScore 返回有序集合中指定分数区间的成员列表。有序集成员按分数值递增(从小到大)次序排列。
// 具有相同分数值的成员按字典序来排列
func (c *Client) ZRangeByScore(key string, from, to, offset int64, count int) (map[string]int64, error) {
return redis.Int64Map(c.Do("ZRANGEBYSCORE", c.getKey(key), from, to, "WITHSCORES", "LIMIT", offset, count))
}
// ZRevrangeByScore 返回有序集中指定分数区间内的所有的成员。有序集成员按分数值递减(从大到小)的次序排列。
// 具有相同分数值的成员按字典序来排列
func (c *Client) ZRevrangeByScore(key string, from, to, offset int64, count int) (map[string]int64, error) {
return redis.Int64Map(c.Do("ZREVRANGEBYSCORE", c.getKey(key), from, to, "WITHSCORES", "LIMIT", offset, count))
}
/**
Redis 发布订阅(pub/sub)是一种消息通信模式发送者(pub)发送消息订阅者(sub)接收消息
Redis 客户端可以订阅任意数量的频道
当有新消息通过 PUBLISH 命令发送给频道 channel 这个消息就会被发送给订阅它的所有客户端
**/
// Publish 将信息发送到指定的频道,返回接收到信息的订阅者数量
func (c *Client) Publish(channel, message string) (int, error) {
return Int(c.Do("PUBLISH", channel, message))
}
// Subscribe 订阅给定的一个或多个频道的信息。
// 支持redis服务停止或网络异常等情况时自动重新订阅。
// 一般的程序都是启动后开启一些固定channel的订阅也不会动态的取消订阅这种场景下可以使用本方法。
// 复杂场景的使用可以直接参考 https://godoc.org/github.com/gomodule/redigo/redis#hdr-Publish_and_Subscribe
func (c *Client) Subscribe(onMessage func(channel string, data []byte) error, channels ...string) error {
conn := c.pool.Get()
psc := redis.PubSubConn{Conn: conn}
err := psc.Subscribe(redis.Args{}.AddFlat(channels)...)
// 如果订阅失败休息1秒后重新订阅比如当redis服务停止服务或网络异常
if err != nil {
fmt.Println(err)
time.Sleep(time.Second)
return c.Subscribe(onMessage, channels...)
}
quit := make(chan int, 1)
// 处理消息
go func() {
for {
switch v := psc.Receive().(type) {
case redis.Message:
// fmt.Printf("%s: message: %s\n", v.Channel, v.Data)
go onMessage(v.Channel, v.Data)
case redis.Subscription:
fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
case error:
quit <- 1
fmt.Println(err)
return
}
}
}()
// 异常情况下自动重新订阅
go func() {
<-quit
time.Sleep(time.Second)
psc.Close()
c.Subscribe(onMessage, channels...)
}()
return err
}
/**
GEO 地理位置
*/
// GeoOptions 用于GEORADIUS和GEORADIUSBYMEMBER命令的参数
type GeoOptions struct {
WithCoord bool
WithDist bool
WithHash bool
Order string // ASC从近到远DESC从远到近
Count int
}
// GeoResult 用于GEORADIUS和GEORADIUSBYMEMBER命令的查询结果
type GeoResult struct {
Name string
Longitude float64
Latitude float64
Dist float64
Hash int64
}
// GeoAdd 将给定的空间元素(纬度、经度、名字)添加到指定的键里面,这些数据会以有序集合的形式被储存在键里面,所以删除可以使用`ZREM`。
func (c *Client) GeoAdd(key string, longitude, latitude float64, member string) error {
_, err := redis.Int(c.Do("GEOADD", c.getKey(key), longitude, latitude, member))
return err
}
// GeoPos 从键里面返回所有给定位置元素的位置(经度和纬度)。
func (c *Client) GeoPos(key string, members ...interface{}) ([]*[2]float64, error) {
args := redis.Args{}
args = args.Add(c.getKey(key))
args = args.Add(members...)
return redis.Positions(c.Do("GEOPOS", args...))
}
// GeoDist 返回两个给定位置之间的距离。
// 如果两个位置之间的其中一个不存在, 那么命令返回空值。
// 指定单位的参数 unit 必须是以下单位的其中一个:
// m 表示单位为米。
// km 表示单位为千米。
// mi 表示单位为英里。
// ft 表示单位为英尺。
// 如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位。
func (c *Client) GeoDist(key string, member1, member2, unit string) (float64, error) {
_, err := redis.Float64(c.Do("GEODIST", c.getKey(key), member1, member2, unit))
return 0, err
}
// GeoRadius 以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
func (c *Client) GeoRadius(key string, longitude, latitude, radius float64, unit string, options GeoOptions) ([]*GeoResult, error) {
args := redis.Args{}
args = args.Add(c.getKey(key), longitude, latitude, radius, unit)
if options.WithDist {
args = args.Add("WITHDIST")
}
if options.WithCoord {
args = args.Add("WITHCOORD")
}
if options.WithHash {
args = args.Add("WITHHASH")
}
if options.Order != "" {
args = args.Add(options.Order)
}
if options.Count > 0 {
args = args.Add("Count", options.Count)
}
reply, err := c.Do("GEORADIUS", args...)
return toGeoResult(reply, err, options)
}
// GeoRadiusByMember 这个命令和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点。
func (c *Client) GeoRadiusByMember(key string, member string, radius float64, unit string, options GeoOptions) ([]*GeoResult, error) {
args := redis.Args{}
args = args.Add(c.getKey(key), member, radius, unit)
if options.WithDist {
args = args.Add("WITHDIST")
}
if options.WithCoord {
args = args.Add("WITHCOORD")
}
if options.WithHash {
args = args.Add("WITHHASH")
}
if options.Order != "" {
args = args.Add(options.Order)
}
if options.Count > 0 {
args = args.Add("Count", options.Count)
}
reply, err := c.Do("GEORADIUSBYMEMBER", args...)
return toGeoResult(reply, err, options)
}
// GeoHash 返回一个或多个位置元素的 Geohash 表示。
func (c *Client) GeoHash(key string, members ...interface{}) ([]string, error) {
args := redis.Args{}
args = args.Add(c.getKey(key))
args = args.Add(members...)
return redis.Strings(c.Do("GEOHASH", args...))
}
func toGeoResult(reply interface{}, err error, options GeoOptions) ([]*GeoResult, error) {
values, err := redis.Values(reply, err)
if err != nil {
return nil, err
}
results := make([]*GeoResult, len(values))
for i := range values {
if values[i] == nil {
continue
}
p, ok := values[i].([]interface{})
if !ok {
return nil, fmt.Errorf("redisgo: unexpected element type for interface slice, got type %T", values[i])
}
geoResult := &GeoResult{}
pos := 0
name, err := redis.String(p[pos], nil)
if err != nil {
return nil, err
}
geoResult.Name = name
if options.WithDist {
pos = pos + 1
dist, err := redis.Float64(p[pos], nil)
if err != nil {
return nil, err
}
geoResult.Dist = dist
}
if options.WithHash {
pos = pos + 1
hash, err := redis.Int64(p[pos], nil)
if err != nil {
return nil, err
}
geoResult.Hash = hash
}
if options.WithCoord {
pos = pos + 1
pp, ok := p[pos].([]interface{})
if !ok {
return nil, fmt.Errorf("redisgo: unexpected element type for interface slice, got type %T", p[i])
}
if len(pp) > 0 {
lat, err := redis.Float64(pp[0], nil)
if err != nil {
return nil, err
}
lon, err := redis.Float64(pp[1], nil)
if err != nil {
return nil, err
}
geoResult.Latitude = lat
geoResult.Longitude = lon
}
}
if err != nil {
return nil, err
}
results[i] = geoResult
}
return results, nil
}
// getKey 将健名加上指定的前缀。
func (c *Client) getKey(key string) string {
return c.prefix + key
}
// encode 序列化要保存的值
func (c *Client) encode(val interface{}) (interface{}, error) {
var value interface{}
switch v := val.(type) {
case string, int, uint, int8, int16, int32, int64, float32, float64, bool:
value = v
default:
b, err := c.marshal(v)
if err != nil {
return nil, err
}
value = string(b)
}
return value, nil
}
// decode 反序列化保存的struct对象
func (c *Client) decode(reply interface{}, err error, val interface{}) error {
str, err := String(reply, err)
if err != nil {
return err
}
return c.unmarshal([]byte(str), val)
}
// closePool 程序进程退出时关闭连接池
func (c *Client) closePool() {
ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt)
signal.Notify(ch, syscall.SIGTERM)
signal.Notify(ch, syscall.SIGKILL)
go func() {
<-ch
c.pool.Close()
os.Exit(0)
}()
}
// init 注册到cache
// func init() {
// cache.Register("redis", &Client{})
// }