commit afc38841bdb5d5b5de4f566705bed720b9867476 Author: Star <> Date: Wed Nov 13 17:08:47 2024 +0800 1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..867e853 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.* +!.gitignore +go.sum +node_modules +package.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d894367 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2024 apigo + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..65c2e60 --- /dev/null +++ b/go.mod @@ -0,0 +1,25 @@ +module apigo.cc/gojs/redis + +go 1.18 + +require ( + apigo.cc/gojs v0.0.5 + apigo.cc/gojs/console v0.0.2 + github.com/ssgo/redis v1.7.7 + github.com/ssgo/u v1.7.11 +) + +require ( + github.com/dlclark/regexp2 v1.11.4 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect + github.com/gomodule/redigo v1.9.2 // indirect + github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect + github.com/ssgo/config v1.7.9 // indirect + github.com/ssgo/log v1.7.7 // indirect + github.com/ssgo/standard v1.7.7 // indirect + github.com/ssgo/tool v0.4.27 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/text v0.20.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/redis.go b/redis.go new file mode 100644 index 0000000..dbbd91d --- /dev/null +++ b/redis.go @@ -0,0 +1,317 @@ +package redis + +import ( + "encoding/json" + + "apigo.cc/gojs" + "apigo.cc/gojs/goja" + "github.com/ssgo/redis" + "github.com/ssgo/u" +) + +var defaultConnName = "default" +var defaultConn = &RedisObj{} + +func init() { + tsCode := gojs.MakeTSCode(defaultConn) + gojs.Register("apigo.cc/gojs/redis", gojs.Module{ + ObjectMaker: func(vm *goja.Runtime) gojs.Map { + defaultConn.pool = redis.GetRedis(defaultConnName, gojs.GetLogger(vm)) + return gojs.ToMap(defaultConn) + }, + TsCode: tsCode, + Desc: "redis api by github.com/ssgo/redis", + SetSSKey: func(key, iv []byte) { + redis.SetEncryptKeys(key, iv) + }, + }) +} + +type RedisObj struct { + Redis +} + +func (rd *RedisObj) GetRedis(vm *goja.Runtime, connUrl string) *Redis { + conn := redis.GetRedis(connUrl, gojs.GetLogger(vm)) + return &Redis{pool: conn} +} + +func (rd *RedisObj) SetDefault(connUrl string, thisArg goja.Value, vm *goja.Runtime) { + defaultConnName = connUrl + defaultConn.pool = redis.GetRedis(connUrl, gojs.GetLogger(vm)) +} + +type Redis struct { + pool *redis.Redis +} + +// Destroy 关闭连接池 +func (rd *Redis) Destroy() error { + return rd.pool.Destroy() +} + +// Do 执行Redis操作,这是底层接口 +// Do cmd 命令 +// Do values 参数,根据命令传入不同参数 +// Do return 返回字符串数据,需要根据数据内容自行解析处理 +func (rd *Redis) Do(cmd string, values ...any) string { + return rd.pool.Do(cmd, values...).String() +} + +// Del 删除 +// Del keys 传入一个或多个Key +// Del return 成功删除的个数 +func (rd *Redis) Del(keys ...string) int { + return rd.pool.Do("DEL", u.ToInterfaceArray(keys)...).Int() +} + +// Exists 判断是否Key存在 +// * key 指定一个Key +// Exists return 是否存在 +func (rd *Redis) Exists(key string) bool { + return rd.pool.Do("EXISTS " + key).Bool() +} + +// Expire 设置Key的过期时间 +// * seconds 过期时间的秒数 +// Expire return 是否成功 +func (rd *Redis) Expire(key string, seconds int) bool { + return rd.pool.Do("EXPIRE "+key, seconds).Bool() +} + +// ExpireAt 设置Key的过期时间(指定具体时间) +// ExpireAt time 过期时间的时间戳,单位秒 +// ExpireAt return 是否成功 +func (rd *Redis) ExpireAt(key string, time int) bool { + return rd.pool.Do("EXPIREAT "+key, time).Bool() +} + +// Keys 查询Key +// * patten 查询条件,例如:"SESS_*" +// * return []string 查询到的Key列表 +func (rd *Redis) Keys(patten string) []string { + return rd.pool.Do("KEYS " + patten).Strings() +} + +// Get 读取Key的内容 +// * return any 如果是一个对象则返回反序列化后的对象,否则返回字符串 +func (rd *Redis) Get(key string) any { + return makeRedisResult(rd.pool.Do("GET " + key)) +} + +// GetEX 读取Key的内容并更新过期时间 +func (rd *Redis) GetEX(key string, seconds int) any { + r := rd.pool.Do("GET " + key) + if r.String() != "" { + rd.pool.EXPIRE(key, seconds) + } + return makeRedisResult(r) +} + +// Set 存储内容到Key +// * value 对象或字符串 +// * return bool 是否成功 +func (rd *Redis) Set(key string, value any) bool { + return rd.pool.Do("SET "+key, value).Bool() +} + +// SetEX 存储内容到Key并设置过期时间 +func (rd *Redis) SetEX(key string, seconds int, value any) bool { + return rd.pool.Do("SETEX "+key, seconds, value).Bool() +} + +// SetNX 存储内容到一个不存在的Key,如果Key已经存在则设置失败 +func (rd *Redis) SetNX(key string, value any) bool { + return rd.pool.Do("SETNX "+key, value).Bool() +} + +// GetSet 将给定 key 的值设为 value ,并返回 key 的旧值(old value) +func (rd *Redis) GetSet(key string, value any) any { + return makeRedisResult(rd.pool.Do("GETSET "+key, value)) +} + +// Incr 将 key 中储存的数值增一 +// Incr * int64 最新的计数 +func (rd *Redis) Incr(key string) int64 { + return rd.pool.Do("INCR " + key).Int64() +} + +// Decr 将 key 中储存的数值减一 +func (rd *Redis) Decr(key string) int64 { + return rd.pool.Do("DECR " + key).Int64() +} + +// IncrBy 将 key 中储存的数值加上增量 increment +func (rd *Redis) IncrBy(key string, increment int64) int64 { + return rd.pool.Do("INCRBY "+key, increment).Int64() +} + +// DecrBy 将 key 中储存的数值减去加上增量 increment +func (rd *Redis) DecrBy(key string, increment int64) int64 { + return rd.pool.Do("DECRBY "+key, increment).Int64() +} + +// MGet 获取所有(一个或多个)给定 key 的值 +// MGet return []any 按照查询key的顺序返回结果,如果结果是一个对象则返回反序列化后的对象,否则返回字符串 +func (rd *Redis) MGet(keys ...string) []any { + return makeRedisResults(rd.pool.Do("MGET", u.ToInterfaceArray(keys)...)) +} + +// MSet 同时设置一个或多个 key-value 对 +// MSet keyAndValues 按照key-value的顺序依次传入一个或多个数据 +func (rd *Redis) MSet(keyAndValues ...any) bool { + return rd.pool.Do("MSET", keyAndValues...).Bool() +} + +// HGet 获取存储在哈希表中指定字段的值 +// * field 字段 +func (rd *Redis) HGet(key, field string) any { + return makeRedisResult(rd.pool.Do("HGET "+key, field)) +} + +// HSet 将哈希表 key 中的字段 field 的值设为 value +func (rd *Redis) HSet(key, field string, value any) bool { + return rd.pool.Do("HSET "+key, field, value).Bool() +} + +// HSetNX 只有在字段 field 不存在时,设置哈希表字段的值 +func (rd *Redis) HSetNX(key, field string, value any) bool { + return rd.pool.Do("HSETNX "+key, field, value).Bool() +} + +// HMGet 获取所有给定字段的值 +// * fields 字段列表 +func (rd *Redis) HMGet(key string, fields ...string) []any { + return makeRedisResults(rd.pool.Do("HMGET", append(append([]any{}, key), u.ToInterfaceArray(fields)...)...)) +} + +// HGetAll 获取在哈希表中指定 key 的所有字段和值 +// HGetAll return 返回所有字段的值,如果值是一个对象则返回反序列化后的对象,否则返回字符串 +func (rd *Redis) HGetAll(key string) map[string]any { + return makeRedisResultMap(rd.pool.Do("HGETALL " + key)) +} + +// HMSet 将哈希表 key 中的字段 field 的值设为 value +func (rd *Redis) HMSet(key string, fieldAndValues ...any) bool { + return rd.pool.Do("HMSET", append(append([]any{}, key), fieldAndValues...)...).Bool() +} + +// HKeys 获取所有哈希表中的字段 +func (rd *Redis) HKeys(patten string) []string { + return rd.pool.Do("HKEYS " + patten).Strings() +} + +// HLen 获取哈希表中字段的数量 +// HLen return 字段数量 +func (rd *Redis) HLen(key string) int { + return rd.pool.Do("HLEN " + key).Int() +} + +// HDel 删除一个或多个哈希表字段 +// HDel return 成功删除的个数 +func (rd *Redis) HDel(key string, fields ...string) int { + return rd.pool.Do("HDEL", append(append([]any{}, key), u.ToInterfaceArray(fields)...)...).Int() +} + +// HExists 查看哈希表 key 中,指定的字段是否存在 +func (rd *Redis) HExists(key, field string) bool { + return rd.pool.Do("HEXISTS "+key, field).Bool() +} + +// HIncr 为哈希表 key 中的指定字段的整数值加上增量1 +func (rd *Redis) HIncr(key, field string) int64 { + return rd.pool.Do("HINCRBY "+key, field, 1).Int64() +} + +// HDecr 为哈希表 key 中的指定字段的整数值减去增量1 +func (rd *Redis) HDecr(key, field string) int64 { + return rd.pool.Do("HDECRBY "+key, field, 1).Int64() +} + +// HIncrBy 为哈希表 key 中的指定字段的整数值加上增量 increment +func (rd *Redis) HIncrBy(key, field string, increment int64) int64 { + return rd.pool.Do("HINCRBY "+key, field, increment).Int64() +} + +// HDecrBy 为哈希表 key 中的指定字段的整数值减去增量 increment +func (rd *Redis) HDecrBy(key, field string, increment int64) int64 { + return rd.pool.Do("HDECRBY "+key, field, increment).Int64() +} + +// LPush 将一个或多个值插入到列表头部 +// LPush return 成功添加的个数 +func (rd *Redis) LPush(key string, values ...string) int { + return rd.pool.Do("LPUSH", append(append([]any{}, key), u.ToInterfaceArray(values)...)...).Int() +} + +// RPush 在列表中添加一个或多个值 +// RPush return 成功添加的个数 +func (rd *Redis) RPush(key string, values ...string) int { + return rd.pool.Do("RPUSH", append(append([]any{}, key), u.ToInterfaceArray(values)...)...).Int() +} + +// LPop 移出并获取列表的第一个元素 +func (rd *Redis) LPop(key string) any { + return makeRedisResult(rd.pool.Do("LPOP " + key)) +} + +// RPop 移除并获取列表最后一个元素 +func (rd *Redis) RPop(key string) any { + return makeRedisResult(rd.pool.Do("RPOP " + key)) +} + +// LLen 获取列表长度 +// LLen 列表的长度 +func (rd *Redis) LLen(key string) int { + return rd.pool.Do("LLEN " + key).Int() +} + +// LRange 获取列表指定范围内的元素 +// LRange return []any 列表数据,如果值是一个对象则返回反序列化后的对象,否则返回字符串 +func (rd *Redis) LRange(key string, start, stop int) []any { + return makeRedisResults(rd.pool.Do("LRANGE "+key, start, stop)) +} + +// TODO 支持订阅,还需要支持 Start、Stop、向Context注册析构函数确保Stop被执行,需要支持传入Function转化为func +// Subscribe +//func (rd *Redis) Subscribe(channel string, onReceived func([]byte)) bool { +// return rd.pool.Subscribe(channel, nil, onReceived) +//} +// +// Unsubscribe +//func (rd *Redis) Unsubscribe(channel string) bool { +// return rd.pool.Unsubscribe(channel) +//} + +// Publish 将信息发送到指定的频道 +// Publish channel 渠道名称 +// Publish data 数据,字符串格式 +func (rd *Redis) Publish(channel, data string) bool { + return rd.pool.Do("PUBLISH "+channel, data).Bool() +} + +func makeRedisResult(r *redis.Result) any { + var v any + buf := r.Bytes() + if json.Unmarshal(buf, &v) == nil { + return v + } else { + return string(buf) + } +} + +func makeRedisResults(rr *redis.Result) []any { + out := make([]any, 0) + for _, r := range rr.Results() { + out = append(out, makeRedisResult(&r)) + } + return out +} + +func makeRedisResultMap(rr *redis.Result) map[string]any { + out := map[string]any{} + for k, r := range rr.ResultMap() { + out[k] = makeRedisResult(r) + } + return out +} diff --git a/redis_test.go b/redis_test.go new file mode 100644 index 0000000..935f0ad --- /dev/null +++ b/redis_test.go @@ -0,0 +1,23 @@ +package redis_test + +import ( + "fmt" + "testing" + + "apigo.cc/gojs" + _ "apigo.cc/gojs/console" + _ "apigo.cc/gojs/redis" + "github.com/ssgo/u" +) + +func TestHash(t *testing.T) { + gojs.ExportForDev() + r, err := gojs.RunFile("redis_test.js") + if err != nil { + t.Fatal(err) + } else if r != true { + t.Fatal(r) + } else { + fmt.Println(u.BGreen("test succeess")) + } +} diff --git a/redis_test.js b/redis_test.js new file mode 100644 index 0000000..c44d6a4 --- /dev/null +++ b/redis_test.js @@ -0,0 +1,13 @@ +import redis from 'apigo.cc/gojs/redis' +import co from 'apigo.cc/gojs/console' + +redis.setDefault('redis://127.0.0.1:6379/2') +redis.set('aaa', 111) +let r = redis.get('aaa') +if (r !== 111) return 'failed to get value ' + r + +redis.hMSet('bbb', 'b1', 111, 'b2', 222) +let [b1, b2] = redis.hMGet('bbb', 'b1', 'b2') +if (b1 !== 111 || b2 !== 222) return 'failed to get hash value' + +return true