// 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 } // HINCRBY 将保存的整型键值增加 func (c *Client) HIncrby(key string, field string, val int) (reply interface{}, err error) { reply, err = c.Do("HINCRBY", c.getKey(key), field, val) return } /** 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("LLEN", 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) } // LIndex 返回列表 key 中指定索引的元素。 func (c *Client) LIndexInt64(key string, index int) (int64, error) { return Int64(c.Do("LINDEX", c.getKey(key), index)) } /** 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() }() } // init 注册到cache // func init() { // cache.Register("redis", &Client{}) // } // 是否是空错误 func (c *Client) IsErrNil(err error) bool { return err == redis.ErrNil }