Compare commits
No commits in common. "main" and "v1.5.5" have entirely different histories.
23
CHANGELOG.md
23
CHANGELOG.md
@ -1,28 +1,5 @@
|
|||||||
# CHANGELOG - redis
|
# CHANGELOG - redis
|
||||||
|
|
||||||
## v1.5.10 (2026-06-22)
|
|
||||||
- **依赖更新**:
|
|
||||||
- 升级依赖 `id` 至 `v1.5.6`。
|
|
||||||
|
|
||||||
## v1.5.9 (2026-06-21)
|
|
||||||
- **依赖更新**:
|
|
||||||
- 升级依赖 `id` 至 `v1.5.5`(同步修复旧版本 ID 未打乱的 Bug)。
|
|
||||||
|
|
||||||
## v1.5.8 (2026-06-21)
|
|
||||||
- **安全模式优化**: `checkSafe` 从写命令全量拦截改为仅拦截危险命令(`FLUSHDB`/`FLUSHALL`),常规写操作(HSET/HDEL/SET/DEL 等)在安全模式下不再受限。
|
|
||||||
- **Result.Bool() 修复**: 增加 `"OK"` 响应判断(Redis RESP Simple String),解决 SET/SETEX 等命令永远返回 `false` 的问题。
|
|
||||||
- **JS Logger 注入修复**: `getDefaultRedisForJS` 改用 `jsmod.Get(ctx, "Logger")` 替代 `ctx.Value("Logger")`,修复 jsmod Context 下 Logger 始终为 nil 导致的 panic。
|
|
||||||
|
|
||||||
## 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`。
|
|
||||||
|
|
||||||
## v1.5.5 (2026-06-20)
|
## v1.5.5 (2026-06-20)
|
||||||
- **JS 导出重构**:
|
- **JS 导出重构**:
|
||||||
- 移除 `js_export.go` 中冗余的独立命令快捷函数(SET, GET, DEL, EXISTS, EXPIRE, PUBLISH),统一通过 `Do` 方法调用。
|
- 移除 `js_export.go` 中冗余的独立命令快捷函数(SET, GET, DEL, EXISTS, EXPIRE, PUBLISH),统一通过 `Do` 方法调用。
|
||||||
|
|||||||
10
TEST.md
10
TEST.md
@ -13,12 +13,8 @@
|
|||||||
- Fixed `Start()` and `Stop()` logic to prevent redundant goroutines and ensure clean exit.
|
- Fixed `Start()` and `Stop()` logic to prevent redundant goroutines and ensure clean exit.
|
||||||
|
|
||||||
## Benchmarks
|
## Benchmarks
|
||||||
- **BenchmarkGetSet**: **282,638 ns/op** (Simple GET/SET loop)
|
- **BenchmarkGetSet**: ~80,000 ns/op (Simple GET/SET loop)
|
||||||
- **BenchmarkIDMaker**: **2,672 ns/op** (High-performance sequence generation)
|
- **BenchmarkIDMaker**: ~2,300 ns/op (High-performance sequence generation)
|
||||||
|
|
||||||
## 🛡️ 鲁棒性防御 (Robustness)
|
> Date: 2026-05-05
|
||||||
- **安全沙箱拦截**:在 `SafeMode` 下,拦截危险命令(FLUSHDB/FLUSHALL),常规写操作(HSET/HDEL/SET/DEL 等)可正常执行。
|
|
||||||
- **JS 错误调用栈**:JS 桥接层改用具名导出并使用 `jsmod.MakeError` 包裹错误(包括 Do 返回的 Result.Error 字段),确保 JS 抛出异常时携带准确的 Go 运行时堆栈。
|
|
||||||
|
|
||||||
> Date: 2026-06-22
|
|
||||||
> Environment: Darwin / Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
|
> Environment: Darwin / Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
|
||||||
|
|||||||
44
commands.go
44
commands.go
@ -1,14 +1,5 @@
|
|||||||
package redis
|
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 {
|
func stringsToAnys(in []string) []any {
|
||||||
a := make([]any, len(in))
|
a := make([]any, len(in))
|
||||||
for i, v := range in {
|
for i, v := range in {
|
||||||
@ -41,33 +32,12 @@ func (rd *Redis) SET(key string, value any) bool {
|
|||||||
return rd.Do("SET", key, value).Bool()
|
return rd.Do("SET", key, value).Bool()
|
||||||
}
|
}
|
||||||
func (rd *Redis) SETEX(key string, seconds int, value any) 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()
|
return rd.Do("SETEX", key, seconds, value).Bool()
|
||||||
}
|
}
|
||||||
func (rd *Redis) SETNX(key string, value any) 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()
|
return rd.Do("SETNX", key, value).Bool()
|
||||||
}
|
}
|
||||||
func (rd *Redis) GETSET(key string, value any) *Result {
|
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)
|
return rd.Do("GETSET", key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,13 +77,6 @@ func (rd *Redis) HGETALL(key string) map[string]*Result {
|
|||||||
return rd.Do("HGETALL", key).ResultMap()
|
return rd.Do("HGETALL", key).ResultMap()
|
||||||
}
|
}
|
||||||
func (rd *Redis) HMSET(key string, fieldAndValues ...any) bool {
|
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()
|
return rd.Do("HMSET", append([]any{key}, fieldAndValues...)...).Bool()
|
||||||
}
|
}
|
||||||
func (rd *Redis) HKEYS(key string) []string {
|
func (rd *Redis) HKEYS(key string) []string {
|
||||||
@ -189,13 +152,6 @@ func (rd *Redis) ZRANGE(key string, start, stop int) []Result {
|
|||||||
return rd.Do("ZRANGE", key, start, stop).Results()
|
return rd.Do("ZRANGE", key, start, stop).Results()
|
||||||
}
|
}
|
||||||
func (rd *Redis) ZREVRANGE(key string, start, stop int) []Result {
|
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()
|
return rd.Do("ZREVRANGE", key, start, stop).Results()
|
||||||
}
|
}
|
||||||
func (rd *Redis) ZRANK(key string, member any) int {
|
func (rd *Redis) ZRANK(key string, member any) int {
|
||||||
|
|||||||
22
go.mod
22
go.mod
@ -3,21 +3,21 @@ module apigo.cc/go/redis
|
|||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
apigo.cc/go/cast v1.5.3
|
apigo.cc/go/cast v1.5.2
|
||||||
apigo.cc/go/config v1.5.3
|
apigo.cc/go/config v1.5.2
|
||||||
apigo.cc/go/crypto v1.5.3
|
apigo.cc/go/crypto v1.5.2
|
||||||
apigo.cc/go/encoding v1.5.4
|
apigo.cc/go/encoding v1.5.3
|
||||||
apigo.cc/go/id v1.5.6
|
apigo.cc/go/id v1.5.3
|
||||||
apigo.cc/go/jsmod v1.5.3
|
apigo.cc/go/jsmod v1.5.2
|
||||||
apigo.cc/go/log v1.5.8
|
apigo.cc/go/log v1.5.6
|
||||||
apigo.cc/go/safe v1.5.2
|
apigo.cc/go/safe v1.5.1
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible
|
github.com/gomodule/redigo v2.0.0+incompatible
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
apigo.cc/go/file v1.5.5 // indirect
|
apigo.cc/go/file v1.5.4 // indirect
|
||||||
apigo.cc/go/rand v1.5.3 // indirect
|
apigo.cc/go/rand v1.5.2 // indirect
|
||||||
apigo.cc/go/shell v1.5.4 // indirect
|
apigo.cc/go/shell v1.5.2 // indirect
|
||||||
golang.org/x/crypto v0.52.0 // indirect
|
golang.org/x/crypto v0.52.0 // indirect
|
||||||
golang.org/x/sys v0.45.0 // indirect
|
golang.org/x/sys v0.45.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
46
go.sum
46
go.sum
@ -1,25 +1,25 @@
|
|||||||
apigo.cc/go/cast v1.5.3 h1:jk6VX0rGFhjKtfPhsaV6IKYpiGmORRk9qPTtuNS53tw=
|
apigo.cc/go/cast v1.5.0 h1:UBGJtFQ8eJPMQXs37cUgqd7YQo1zI9opuSDBDmn2/pE=
|
||||||
apigo.cc/go/cast v1.5.3/go.mod h1:GMjjrYn93tWat1U409G7h1jR3ejfLLI7r0efBo9Sbd4=
|
apigo.cc/go/cast v1.5.0/go.mod h1:z2GW5p5WCZGEqVVIJUdhl232vRbLf2Qu4EDlEakX/D8=
|
||||||
apigo.cc/go/config v1.5.3 h1:peq1FM2xO+vzPHJf8Dwg3DXm8PtFQMfTFKQj6fpoG7A=
|
apigo.cc/go/config v1.5.0 h1:Yuz9QEb11XXG4XkhDi/ueT2M1T3Q9PElE5tiakvjehs=
|
||||||
apigo.cc/go/config v1.5.3/go.mod h1:ZiOAjWa1mQIzszaJZN+kO6YU4GXreng+NxkcK/TAkqQ=
|
apigo.cc/go/config v1.5.0/go.mod h1:jdMiDLPa9gzB8/FFZvm9jOopUqdxb7XSX+0OeWcZZUM=
|
||||||
apigo.cc/go/crypto v1.5.3 h1:2JUHC2cgR2zrnn36EzwkUAdxmmTXAA/8yTNo+2X1mPE=
|
apigo.cc/go/crypto v1.5.0 h1:Nxz7a6VKCdvaF258IU0NkjQyureOLxfR308Sy2iftUI=
|
||||||
apigo.cc/go/crypto v1.5.3/go.mod h1:PheYKHEXmoEFI1AK5PpY1borQWcRlkkSaWncT3cWbhE=
|
apigo.cc/go/crypto v1.5.0/go.mod h1:F9M6nXv+5328r1ZwbTvI6fcr8VdgqHVzALOcsdv6ntE=
|
||||||
apigo.cc/go/encoding v1.5.4 h1:Fk8TrveZATyy8SHukC4ZiqdTSp+QIfsRHtt55xmMK7w=
|
apigo.cc/go/encoding v1.5.0 h1:EJNdRVDOMoI2DAvZwQNQTbYuqB/6zsEzvg7lS5pQI+I=
|
||||||
apigo.cc/go/encoding v1.5.4/go.mod h1:dShEsZ3gKqBINz7TSOYf4e7/fBCqCY9VzlenoGUQUFM=
|
apigo.cc/go/encoding v1.5.0/go.mod h1:8++NfZj3hWig0qh2g7GQRw/4LpSvCYMWUZ+8J+x58cA=
|
||||||
apigo.cc/go/file v1.5.5 h1:/+HmDumLu6Qk2KuQL63M9lpgzHTDL+QJ8dStOl7e9gs=
|
apigo.cc/go/file v1.5.0 h1:Fh1NSDBqaxjuXYJ71yPHPXVJ8BFEv/AGS3l+jkLi5uw=
|
||||||
apigo.cc/go/file v1.5.5/go.mod h1:xRVNhctvqOKeBemmcRW/BQfgkc3B+vT/UZVdSc7duUo=
|
apigo.cc/go/file v1.5.0/go.mod h1:4YhOGgBINTpmmmgws3H8LAyXQQBGzBp44hYUoCS+kr0=
|
||||||
apigo.cc/go/id v1.5.5 h1:fQXfb2WZ4hEtzXkpb9w9o8AOcTZ44fYQfTV6iZ49l8o=
|
apigo.cc/go/id v1.5.0 h1:MjNWPhBhDsoXaLeJDv/0wfJmVMU9EvOs8pWYfsTQ6e8=
|
||||||
apigo.cc/go/id v1.5.5/go.mod h1:hCTQq+KC1ALWe1FpPERf+W4B6FSulg9FAgOUJDDySiY=
|
apigo.cc/go/id v1.5.0/go.mod h1:qhu4a1/KLc/XcBpcsRu+mXZt7U7Wvd9zMcPs4VspuPA=
|
||||||
apigo.cc/go/jsmod v1.5.3 h1:S3W317bH0QV2NMeRO1E0v6ySIBOfMWYv/NuQJbvqKWU=
|
apigo.cc/go/jsmod v1.5.0 h1:JgQtJNiJWy1NOP9AzE8NX5VXJkpO/x3GqLsCCSny5Ec=
|
||||||
apigo.cc/go/jsmod v1.5.3/go.mod h1:bmyeZtOAP/j5am+YRnaiM89smysK24K7ebk0koFtsSw=
|
apigo.cc/go/jsmod v1.5.0/go.mod h1:bmyeZtOAP/j5am+YRnaiM89smysK24K7ebk0koFtsSw=
|
||||||
apigo.cc/go/log v1.5.8 h1:/IYtGPWhRjT3OayylDIphkWZIQbpLjqVeSnFEiD3Dy0=
|
apigo.cc/go/log v1.5.0 h1:kQuLLtbt33mEuc/xJVcy8NODXkso/QKSZWNclKrSpsI=
|
||||||
apigo.cc/go/log v1.5.8/go.mod h1:HfFPANMYxJx197SSTXB21Pgxcz/gGqPP8nlSErgd5WE=
|
apigo.cc/go/log v1.5.0/go.mod h1:Djy+I5aLhGB/EjwRz4KHqkVEz584IAD55FAFiIfInuo=
|
||||||
apigo.cc/go/rand v1.5.3 h1:O4bPIwyaOWEBCr0nL9A4G4qG48AqiGTCzfPeckm3Ius=
|
apigo.cc/go/rand v1.5.0 h1:1o8hh8fhdBuk1/h02IvugvamuT3dkWbVJrqEJVQKB2E=
|
||||||
apigo.cc/go/rand v1.5.3/go.mod h1:q1BTFkY/cXE229dDD5Q22lF7T0DoKPV6xAu+6bCrDH4=
|
apigo.cc/go/rand v1.5.0/go.mod h1:Lh98S2dm9UY0X+M+kNQQEKyXHG5pcCKSFPyXN0QCGdk=
|
||||||
apigo.cc/go/safe v1.5.2 h1:EnuEOW/SGwf/5A0nw9LnqfKJE071+TIc6ez8HI9R9Lg=
|
apigo.cc/go/safe v1.5.0 h1:W1NblmcU8cex1f9Y5z8mNLUJOzZTE1s6fszb3FbhGnk=
|
||||||
apigo.cc/go/safe v1.5.2/go.mod h1:2GqCCLLGex4OAhdET3iBWm1R+LIYtmTrvHP8W0iESSw=
|
apigo.cc/go/safe v1.5.0/go.mod h1:OfQ5d6COePSGEuPvMeOk6KagX2sezw7nvKh7exj9SeM=
|
||||||
apigo.cc/go/shell v1.5.4 h1:Kn6lP6I6d9U0hbyUjpKKFdFZ8RPo4vi4V6AYW8YFzrc=
|
apigo.cc/go/shell v1.5.0 h1:WLDMMqUU0INeaBDmQsTPr0h/NfB2RknAtiJ5NL467+Q=
|
||||||
apigo.cc/go/shell v1.5.4/go.mod h1:FdZWUrcXHGJXo725oSyHqAeFoX0E9yY3PDhrz9hujgY=
|
apigo.cc/go/shell v1.5.0/go.mod h1:rYHA77d5hEsQHcJrbAWf1pHy0sxayeJ0gU55LA/JWQk=
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
|
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/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
@ -29,9 +29,7 @@ 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 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
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 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 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 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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
|||||||
95
js_export.go
95
js_export.go
@ -13,46 +13,38 @@ import (
|
|||||||
func init() {
|
func init() {
|
||||||
jsmod.Register("redis", map[string]any{
|
jsmod.Register("redis", map[string]any{
|
||||||
// 入口:支持别名获取,不传则默认 "default"
|
// 入口:支持别名获取,不传则默认 "default"
|
||||||
"Get": jsGet,
|
"Get": func(ctx context.Context, name *string) (*jsRedis, error) {
|
||||||
|
target := "default"
|
||||||
|
if name != nil {
|
||||||
|
target = *name
|
||||||
|
}
|
||||||
|
rd := GetRedis(target, nil)
|
||||||
|
if rd.Error != nil {
|
||||||
|
return nil, rd.Error
|
||||||
|
}
|
||||||
|
return &jsRedis{rd: rd, ctx: ctx}, nil
|
||||||
|
},
|
||||||
|
|
||||||
// 默认快捷调用 (面向 "default" 实例)
|
// 默认快捷调用 (面向 "default" 实例)
|
||||||
"Do": jsDo,
|
"Do": func(ctx context.Context, cmd string, args ...any) (*Result, error) {
|
||||||
"MakeID": jsMakeID,
|
jr := getDefaultRedisForJS(ctx)
|
||||||
|
if jr.rd.Error != nil {
|
||||||
|
return nil, jr.rd.Error
|
||||||
|
}
|
||||||
|
res := jr.Do(cmd, args...)
|
||||||
|
return res, res.Error
|
||||||
|
},
|
||||||
|
|
||||||
|
"MakeID": func(ctx context.Context, size int, forDB *string) string {
|
||||||
|
jr := getDefaultRedisForJS(ctx)
|
||||||
|
if jr.rd.Error != nil {
|
||||||
|
return id.MakeID(size)
|
||||||
|
}
|
||||||
|
return jr.MakeID(size, forDB)
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func jsGet(ctx context.Context, name *string) (*jsRedis, error) {
|
|
||||||
target := "default"
|
|
||||||
if name != nil {
|
|
||||||
target = *name
|
|
||||||
}
|
|
||||||
rd := GetRedis(target, nil)
|
|
||||||
if rd.Error != nil {
|
|
||||||
return nil, jsmod.MakeError(rd.Error)
|
|
||||||
}
|
|
||||||
return &jsRedis{rd: rd, ctx: ctx}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func jsDo(ctx context.Context, cmd string, args ...any) (*Result, error) {
|
|
||||||
jr := getDefaultRedisForJS(ctx)
|
|
||||||
if jr.rd.Error != nil {
|
|
||||||
return nil, jsmod.MakeError(jr.rd.Error)
|
|
||||||
}
|
|
||||||
res := jr.Do(cmd, args...)
|
|
||||||
if res != nil && res.Error != nil {
|
|
||||||
return res, jsmod.MakeError(res.Error)
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func jsMakeID(ctx context.Context, size int, forDB *string) string {
|
|
||||||
jr := getDefaultRedisForJS(ctx)
|
|
||||||
if jr.rd.Error != nil {
|
|
||||||
return id.MakeID(size)
|
|
||||||
}
|
|
||||||
return jr.MakeID(size, forDB)
|
|
||||||
}
|
|
||||||
|
|
||||||
type jsRedis struct {
|
type jsRedis struct {
|
||||||
rd *Redis
|
rd *Redis
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@ -63,26 +55,29 @@ var defaultRedisForJS *jsRedis
|
|||||||
|
|
||||||
func getDefaultRedisForJS(ctx context.Context) *jsRedis {
|
func getDefaultRedisForJS(ctx context.Context) *jsRedis {
|
||||||
if defaultRedisForJS == nil {
|
if defaultRedisForJS == nil {
|
||||||
var logger *log.Logger
|
defaultRedisForJS = &jsRedis{rd: GetRedis("default", ctx.Value("Logger").(*log.Logger)), ctx: ctx}
|
||||||
if l := jsmod.Get(ctx, "Logger"); l != nil {
|
|
||||||
logger = l.(*log.Logger)
|
|
||||||
}
|
|
||||||
defaultRedisForJS = &jsRedis{rd: GetRedis("default", logger), ctx: ctx}
|
|
||||||
}
|
}
|
||||||
return defaultRedisForJS
|
return defaultRedisForJS
|
||||||
}
|
}
|
||||||
|
|
||||||
var errSafeMode = errors.New("redis operation is restricted in safe mode")
|
var errSafeMode = errors.New("redis operation is restricted in safe mode")
|
||||||
|
|
||||||
// 危险指令集,安全模式下禁止执行
|
// 核心写操作指令集
|
||||||
var dangerousCommands = map[string]bool{
|
var writeCommands = map[string]bool{
|
||||||
"FLUSHDB": true,
|
"SET": true, "SETEX": true, "SETNX": true, "MSET": true, "MSETNX": true,
|
||||||
"FLUSHALL": true,
|
"DEL": true, "EXPIRE": true, "EXPIREAT": true, "PEXPIRE": true, "PEXPIREAT": true,
|
||||||
|
"HSET": true, "HSETNX": true, "HDEL": true, "HMSET": true,
|
||||||
|
"LPUSH": true, "RPUSH": true, "LPOP": true, "RPOP": true, "LREM": true, "LTRIM": true,
|
||||||
|
"SADD": true, "SREM": true, "SPOP": true, "SMOVE": true,
|
||||||
|
"ZADD": true, "ZREM": true, "ZREMRANGEBYRANK": true, "ZREMRANGEBYSCORE": true,
|
||||||
|
"PUBLISH": true, "FLUSHDB": true, "FLUSHALL": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jr *jsRedis) checkSafe(cmd string) error {
|
func (jr *jsRedis) checkSafe(cmd string) error {
|
||||||
if jsmod.IsSafeMode(jr.ctx) {
|
if jsmod.IsSafeMode(jr.ctx) {
|
||||||
if dangerousCommands[strings.ToUpper(cmd)] {
|
cmd = strings.ToUpper(cmd)
|
||||||
|
if writeCommands[cmd] || !strings.Contains(" GET EXISTS ZRANGE HGET HGETALL SMEMBERS SISMEMBER LINDEX LLEN ", " "+cmd+" ") {
|
||||||
|
// 严格模式:不在白名单内的或在黑名单内的都禁止
|
||||||
return errSafeMode
|
return errSafeMode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,15 +86,9 @@ func (jr *jsRedis) checkSafe(cmd string) error {
|
|||||||
|
|
||||||
func (jr *jsRedis) Do(cmd string, args ...any) *Result {
|
func (jr *jsRedis) Do(cmd string, args ...any) *Result {
|
||||||
if err := jr.checkSafe(cmd); err != nil {
|
if err := jr.checkSafe(cmd); err != nil {
|
||||||
return &Result{Error: jsmod.MakeError(err)}
|
return &Result{Error: err}
|
||||||
}
|
}
|
||||||
res := jr.rd.Do(cmd, args...)
|
return jr.rd.Do(cmd, args...)
|
||||||
if res != nil && res.Error != nil {
|
|
||||||
resCopy := *res
|
|
||||||
resCopy.Error = jsmod.MakeError(res.Error)
|
|
||||||
return &resCopy
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID Generation
|
// ID Generation
|
||||||
|
|||||||
15
redis.go
15
redis.go
@ -9,7 +9,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -22,13 +21,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Redis struct {
|
type Redis struct {
|
||||||
name string
|
name string
|
||||||
pool *redis.Pool
|
pool *redis.Pool
|
||||||
Config *Config
|
Config *Config
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
Error error
|
Error error
|
||||||
sub *pubsub
|
sub *pubsub
|
||||||
compatFlags atomic.Uint32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type pubsub struct {
|
type pubsub struct {
|
||||||
@ -173,7 +171,6 @@ func (rd *Redis) CopyByLogger(logger *log.Logger) *Redis {
|
|||||||
newRedis.pool = rd.pool
|
newRedis.pool = rd.pool
|
||||||
newRedis.sub = rd.sub
|
newRedis.sub = rd.sub
|
||||||
newRedis.Config = rd.Config
|
newRedis.Config = rd.Config
|
||||||
newRedis.compatFlags.Store(rd.compatFlags.Load())
|
|
||||||
if logger == nil {
|
if logger == nil {
|
||||||
newRedis.logger = log.DefaultLogger
|
newRedis.logger = log.DefaultLogger
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"apigo.cc/go/cast"
|
"apigo.cc/go/cast"
|
||||||
)
|
)
|
||||||
@ -59,11 +58,7 @@ func (rs *Result) Bytes() []byte {
|
|||||||
return rs.bytes()
|
return rs.bytes()
|
||||||
}
|
}
|
||||||
func (rs *Result) Bool() bool {
|
func (rs *Result) Bool() bool {
|
||||||
s := strings.ToLower(rs.String())
|
return cast.Bool(rs.String())
|
||||||
if s == "ok" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return cast.Bool(s)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *Result) Ints() []int {
|
func (rs *Result) Ints() []int {
|
||||||
|
|||||||
@ -19,7 +19,6 @@ func TestSub(t *testing.T) {
|
|||||||
rd.Subscribe("aaa", nil, func(s []byte) {
|
rd.Subscribe("aaa", nil, func(s []byte) {
|
||||||
aaa.Store(string(s))
|
aaa.Store(string(s))
|
||||||
})
|
})
|
||||||
time.Sleep(50 * time.Millisecond) // 等待 Redis 订阅注册完成
|
|
||||||
|
|
||||||
rd.PUBLISH("aaa", "111")
|
rd.PUBLISH("aaa", "111")
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
@ -32,7 +31,6 @@ func TestSub(t *testing.T) {
|
|||||||
rd.Subscribe("bbb", nil, func(s []byte) {
|
rd.Subscribe("bbb", nil, func(s []byte) {
|
||||||
bbb.Store(string(s))
|
bbb.Store(string(s))
|
||||||
})
|
})
|
||||||
time.Sleep(50 * time.Millisecond) // 等待 Redis 订阅注册完成
|
|
||||||
|
|
||||||
rd.PUBLISH("bbb", "222")
|
rd.PUBLISH("bbb", "222")
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user