diff --git a/CHANGELOG.md b/CHANGELOG.md index 239f23e..31f4f27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # CHANGELOG - redis +## v1.5.7 (2026-06-21) +- **兼容性增强**: 对已废弃的 Redis 命令增加新协议兼容支持。 + - `SETEX` → `SET key val EX sec`,`SETNX` → `SET key val NX`,`HMSET` → `HSET`,`GETSET` → `SET key val GET`,`ZREVRANGE` → `ZRANGE ... REV`。 + - 新增 `compatFlags` 降级标记位,默认尝试新协议;当新协议报错时自动回退旧协议并记录标记,后续直接使用旧协议。 +- **依赖更新**: 升级 `shell` 至 `v1.5.4`。 + ## v1.5.6 (2026-06-21) - **JS 对齐**: 重构 JS 导出为具名函数,并引入 `jsmod.MakeError` 动态包装错误以获取调用栈。 - **依赖更新**: 升级依赖 `jsmod` 至 `v1.5.3`,`cast` 至 `v1.5.3`,`rand` 至 `v1.5.3`,`encoding` 至 `v1.5.4`,`shell` 至 `v1.5.3`,`safe` 至 `v1.5.2`,`id` 至 `v1.5.4`,`crypto` 至 `v1.5.3`,`file` 至 `v1.5.5`,`config` 至 `v1.5.3`,`log` 至 `v1.5.8`。 diff --git a/commands.go b/commands.go index 461d5e7..6a5615c 100644 --- a/commands.go +++ b/commands.go @@ -1,5 +1,14 @@ package redis +// compatFlags 位掩码:标记哪些命令需使用旧协议(新协议失败后自动降级) +const ( + compatSETEX = 1 << iota // SETEX → SET key val EX sec + compatSETNX // SETNX → SET key val NX + compatHMSET // HMSET → HSET key f1 v1 f2 v2 + compatGETSET // GETSET → SET key val GET + compatZREVRANGE // ZREVRANGE → ZRANGE key start stop REV +) + func stringsToAnys(in []string) []any { a := make([]any, len(in)) for i, v := range in { @@ -32,12 +41,33 @@ func (rd *Redis) SET(key string, value any) bool { return rd.Do("SET", key, value).Bool() } func (rd *Redis) SETEX(key string, seconds int, value any) bool { + if rd.compatFlags.Load()&compatSETEX == 0 { + r := rd.Do("SET", key, value, "EX", seconds) + if r.Error == nil { + return r.Bool() + } + rd.compatFlags.Store(rd.compatFlags.Load() | compatSETEX) + } return rd.Do("SETEX", key, seconds, value).Bool() } func (rd *Redis) SETNX(key string, value any) bool { + if rd.compatFlags.Load()&compatSETNX == 0 { + r := rd.Do("SET", key, value, "NX") + if r.Error == nil { + return r.Bool() + } + rd.compatFlags.Store(rd.compatFlags.Load() | compatSETNX) + } return rd.Do("SETNX", key, value).Bool() } func (rd *Redis) GETSET(key string, value any) *Result { + if rd.compatFlags.Load()&compatGETSET == 0 { + r := rd.Do("SET", key, value, "GET") + if r.Error == nil { + return r + } + rd.compatFlags.Store(rd.compatFlags.Load() | compatGETSET) + } return rd.Do("GETSET", key, value) } @@ -77,6 +107,13 @@ func (rd *Redis) HGETALL(key string) map[string]*Result { return rd.Do("HGETALL", key).ResultMap() } func (rd *Redis) HMSET(key string, fieldAndValues ...any) bool { + if rd.compatFlags.Load()&compatHMSET == 0 { + r := rd.Do("HSET", append([]any{key}, fieldAndValues...)...) + if r.Error == nil { + return r.Bool() + } + rd.compatFlags.Store(rd.compatFlags.Load() | compatHMSET) + } return rd.Do("HMSET", append([]any{key}, fieldAndValues...)...).Bool() } func (rd *Redis) HKEYS(key string) []string { @@ -152,6 +189,13 @@ func (rd *Redis) ZRANGE(key string, start, stop int) []Result { return rd.Do("ZRANGE", key, start, stop).Results() } func (rd *Redis) ZREVRANGE(key string, start, stop int) []Result { + if rd.compatFlags.Load()&compatZREVRANGE == 0 { + r := rd.Do("ZRANGE", key, start, stop, "REV") + if r.Error == nil { + return r.Results() + } + rd.compatFlags.Store(rd.compatFlags.Load() | compatZREVRANGE) + } return rd.Do("ZREVRANGE", key, start, stop).Results() } func (rd *Redis) ZRANK(key string, member any) int { diff --git a/go.mod b/go.mod index 3aeca97..a4da5ce 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( require ( apigo.cc/go/file v1.5.5 // indirect apigo.cc/go/rand v1.5.3 // indirect - apigo.cc/go/shell v1.5.3 // indirect + apigo.cc/go/shell v1.5.4 // indirect golang.org/x/crypto v0.52.0 // indirect golang.org/x/sys v0.45.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 9bb4729..acc3fb6 100644 --- a/go.sum +++ b/go.sum @@ -1,25 +1,25 @@ -apigo.cc/go/cast v1.5.0 h1:UBGJtFQ8eJPMQXs37cUgqd7YQo1zI9opuSDBDmn2/pE= -apigo.cc/go/cast v1.5.0/go.mod h1:z2GW5p5WCZGEqVVIJUdhl232vRbLf2Qu4EDlEakX/D8= -apigo.cc/go/config v1.5.0 h1:Yuz9QEb11XXG4XkhDi/ueT2M1T3Q9PElE5tiakvjehs= -apigo.cc/go/config v1.5.0/go.mod h1:jdMiDLPa9gzB8/FFZvm9jOopUqdxb7XSX+0OeWcZZUM= -apigo.cc/go/crypto v1.5.0 h1:Nxz7a6VKCdvaF258IU0NkjQyureOLxfR308Sy2iftUI= -apigo.cc/go/crypto v1.5.0/go.mod h1:F9M6nXv+5328r1ZwbTvI6fcr8VdgqHVzALOcsdv6ntE= -apigo.cc/go/encoding v1.5.0 h1:EJNdRVDOMoI2DAvZwQNQTbYuqB/6zsEzvg7lS5pQI+I= -apigo.cc/go/encoding v1.5.0/go.mod h1:8++NfZj3hWig0qh2g7GQRw/4LpSvCYMWUZ+8J+x58cA= -apigo.cc/go/file v1.5.0 h1:Fh1NSDBqaxjuXYJ71yPHPXVJ8BFEv/AGS3l+jkLi5uw= -apigo.cc/go/file v1.5.0/go.mod h1:4YhOGgBINTpmmmgws3H8LAyXQQBGzBp44hYUoCS+kr0= -apigo.cc/go/id v1.5.0 h1:MjNWPhBhDsoXaLeJDv/0wfJmVMU9EvOs8pWYfsTQ6e8= -apigo.cc/go/id v1.5.0/go.mod h1:qhu4a1/KLc/XcBpcsRu+mXZt7U7Wvd9zMcPs4VspuPA= -apigo.cc/go/jsmod v1.5.0 h1:JgQtJNiJWy1NOP9AzE8NX5VXJkpO/x3GqLsCCSny5Ec= -apigo.cc/go/jsmod v1.5.0/go.mod h1:bmyeZtOAP/j5am+YRnaiM89smysK24K7ebk0koFtsSw= -apigo.cc/go/log v1.5.0 h1:kQuLLtbt33mEuc/xJVcy8NODXkso/QKSZWNclKrSpsI= -apigo.cc/go/log v1.5.0/go.mod h1:Djy+I5aLhGB/EjwRz4KHqkVEz584IAD55FAFiIfInuo= -apigo.cc/go/rand v1.5.0 h1:1o8hh8fhdBuk1/h02IvugvamuT3dkWbVJrqEJVQKB2E= -apigo.cc/go/rand v1.5.0/go.mod h1:Lh98S2dm9UY0X+M+kNQQEKyXHG5pcCKSFPyXN0QCGdk= -apigo.cc/go/safe v1.5.0 h1:W1NblmcU8cex1f9Y5z8mNLUJOzZTE1s6fszb3FbhGnk= -apigo.cc/go/safe v1.5.0/go.mod h1:OfQ5d6COePSGEuPvMeOk6KagX2sezw7nvKh7exj9SeM= -apigo.cc/go/shell v1.5.0 h1:WLDMMqUU0INeaBDmQsTPr0h/NfB2RknAtiJ5NL467+Q= -apigo.cc/go/shell v1.5.0/go.mod h1:rYHA77d5hEsQHcJrbAWf1pHy0sxayeJ0gU55LA/JWQk= +apigo.cc/go/cast v1.5.3 h1:jk6VX0rGFhjKtfPhsaV6IKYpiGmORRk9qPTtuNS53tw= +apigo.cc/go/cast v1.5.3/go.mod h1:GMjjrYn93tWat1U409G7h1jR3ejfLLI7r0efBo9Sbd4= +apigo.cc/go/config v1.5.3 h1:peq1FM2xO+vzPHJf8Dwg3DXm8PtFQMfTFKQj6fpoG7A= +apigo.cc/go/config v1.5.3/go.mod h1:ZiOAjWa1mQIzszaJZN+kO6YU4GXreng+NxkcK/TAkqQ= +apigo.cc/go/crypto v1.5.3 h1:2JUHC2cgR2zrnn36EzwkUAdxmmTXAA/8yTNo+2X1mPE= +apigo.cc/go/crypto v1.5.3/go.mod h1:PheYKHEXmoEFI1AK5PpY1borQWcRlkkSaWncT3cWbhE= +apigo.cc/go/encoding v1.5.4 h1:Fk8TrveZATyy8SHukC4ZiqdTSp+QIfsRHtt55xmMK7w= +apigo.cc/go/encoding v1.5.4/go.mod h1:dShEsZ3gKqBINz7TSOYf4e7/fBCqCY9VzlenoGUQUFM= +apigo.cc/go/file v1.5.5 h1:/+HmDumLu6Qk2KuQL63M9lpgzHTDL+QJ8dStOl7e9gs= +apigo.cc/go/file v1.5.5/go.mod h1:xRVNhctvqOKeBemmcRW/BQfgkc3B+vT/UZVdSc7duUo= +apigo.cc/go/id v1.5.4 h1:D1Zx9gEZhOgdTgZ4SdmPImhpc9xGiOA33Y+j2MkstzQ= +apigo.cc/go/id v1.5.4/go.mod h1:hCTQq+KC1ALWe1FpPERf+W4B6FSulg9FAgOUJDDySiY= +apigo.cc/go/jsmod v1.5.3 h1:S3W317bH0QV2NMeRO1E0v6ySIBOfMWYv/NuQJbvqKWU= +apigo.cc/go/jsmod v1.5.3/go.mod h1:bmyeZtOAP/j5am+YRnaiM89smysK24K7ebk0koFtsSw= +apigo.cc/go/log v1.5.8 h1:/IYtGPWhRjT3OayylDIphkWZIQbpLjqVeSnFEiD3Dy0= +apigo.cc/go/log v1.5.8/go.mod h1:HfFPANMYxJx197SSTXB21Pgxcz/gGqPP8nlSErgd5WE= +apigo.cc/go/rand v1.5.3 h1:O4bPIwyaOWEBCr0nL9A4G4qG48AqiGTCzfPeckm3Ius= +apigo.cc/go/rand v1.5.3/go.mod h1:q1BTFkY/cXE229dDD5Q22lF7T0DoKPV6xAu+6bCrDH4= +apigo.cc/go/safe v1.5.2 h1:EnuEOW/SGwf/5A0nw9LnqfKJE071+TIc6ez8HI9R9Lg= +apigo.cc/go/safe v1.5.2/go.mod h1:2GqCCLLGex4OAhdET3iBWm1R+LIYtmTrvHP8W0iESSw= +apigo.cc/go/shell v1.5.4 h1:Kn6lP6I6d9U0hbyUjpKKFdFZ8RPo4vi4V6AYW8YFzrc= +apigo.cc/go/shell v1.5.4/go.mod h1:FdZWUrcXHGJXo725oSyHqAeFoX0E9yY3PDhrz9hujgY= github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -29,7 +29,9 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/redis.go b/redis.go index f352e6e..3a383bb 100644 --- a/redis.go +++ b/redis.go @@ -9,6 +9,7 @@ import ( "strconv" "strings" "sync" + "sync/atomic" "syscall" "time" @@ -21,12 +22,13 @@ import ( ) type Redis struct { - name string - pool *redis.Pool - Config *Config - logger *log.Logger - Error error - sub *pubsub + name string + pool *redis.Pool + Config *Config + logger *log.Logger + Error error + sub *pubsub + compatFlags atomic.Uint32 } type pubsub struct { @@ -171,6 +173,7 @@ func (rd *Redis) CopyByLogger(logger *log.Logger) *Redis { newRedis.pool = rd.pool newRedis.sub = rd.sub newRedis.Config = rd.Config + newRedis.compatFlags.Store(rd.compatFlags.Load()) if logger == nil { newRedis.logger = log.DefaultLogger } else {