package cachemdl /* Provides cache access and manipulation methods for redis server Implements cacher interface. Official docs - 1. redis client - https://github.com/go-redis/redis 2. redis server - https://redis.io/ Note - corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/loggermdl must be initialized before use */ import ( "encoding/json" "log" "strings" "time" "corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/loggermdl" "github.com/go-redis/redis" ) const ( noExp time.Duration = 0 // NotOK refers to a unsuccessfull operations NotOK int64 = 0 // OK refers to a successfull operations OK int64 = 1 keySplitter = ":" ) // RedisCache represents a Redis client with provided configuration. Do not change configuration at runtime. type RedisCache struct { cli *redis.Client // represents redis client opt *redis.Options // keyStr string // "<Prefix>:" addPrefix bool // connected bool // will be enabled if redis client connects to server Addr string // redis server address, default "127.0.0.1:6379" DB int // redis DB on provided server, default 0 Password string // Expiration time.Duration // this duration will be used for Set() method Prefix string // this will be used for storing keys for provided project } // Setup initializes redis cache for application. Must be called only once. func (rc *RedisCache) Setup(addr, password, prefix string, db int, exp time.Duration) { if rc == nil { rc = new(RedisCache) } rc.Addr = addr rc.Password = password rc.DB = db rc.Expiration = exp rc.Prefix = prefix opt := redis.Options{ Addr: addr, Password: password, DB: db, } rc.opt = &opt rc.cli = redis.NewClient(&opt) if _, err := rc.cli.Ping().Result(); err != nil { // exit if connection to redis server fails loggermdl.LogError("connection to redis server failed: ", err) log.Fatal("connection to redis server failed: ", err) } rc.connected = true if prefix != "" { rc.keyStr = contcat(rc.Prefix, keySplitter) rc.addPrefix = true } } // Set marshalls provided value and stores against provided key. Errors will be logged to initialized logger. func (rc *RedisCache) Set(key string, val interface{}) { ba, err := marshalWithTypeCheck(val) if err != nil { loggermdl.LogError("error setting key ", key, " error: ", err) return } rc.cli.Set(rc.key(key), ba, rc.Expiration) } // SetWithExpiration marshalls provided value and stores against provided key for given duration. Errors will be logged to initialized logger. func (rc *RedisCache) SetWithExpiration(key string, val interface{}, exp time.Duration) { ba, err := marshalWithTypeCheck(val) if err != nil { loggermdl.LogError("error setting key ", key, " error: ", err) return } rc.cli.Set(rc.key(key), ba, exp) } // SetNoExpiration marshalls provided value and stores against provided key. // Errors will be logged to initialized logger. func (rc *RedisCache) SetNoExpiration(key string, val interface{}) { ba, err := marshalWithTypeCheck(val) if err != nil { loggermdl.LogError("error setting key ", key, " error: ", err) return } rc.cli.Set(rc.key(key), ba, noExp) } // Get returns data against provided key. The final result is parsed with gjson. Returns false if not present. func (rc *RedisCache) Get(key string) (interface{}, bool) { // exists, err := rc.cli.Exists(key).Result() // if err != nil { // loggermdl.LogError("error checking key ", key, " error: ", err) // return nil, false // } // if exists == NotOK { // return nil, false // } // Get returns error if key is not present. val, err := rc.cli.Get(rc.key(key)).Result() if err != nil { loggermdl.LogError("error getting key", key, "from redis cache with error:", err) return nil, false } return val, true } // Delete - func (rc *RedisCache) Delete(key string) { rc.cli.Del(rc.key(key)).Result() } // GetItemsCount - func (rc *RedisCache) GetItemsCount() int { pattern := "*" keys, err := rc.cli.Keys(pattern).Result() if err != nil { loggermdl.LogError("error getting item count for ", pattern, " error: ", err) return 0 } return len(keys) } func (rc *RedisCache) flushDB() (string, error) { return rc.cli.FlushDB().Result() } // Purge deletes for current redis db func (rc *RedisCache) Purge() { _, err := rc.flushDB() if err != nil { loggermdl.LogError("error purging redis cache for db ", rc.Addr, "/", rc.DB, " error: ", err) } } func marshal(v interface{}) ([]byte, error) { return json.Marshal(v) } // marshalWithTypeCheck checks type before marshsal. Save allocations and time significantly if the existing data is string or []byte func marshalWithTypeCheck(v interface{}) ([]byte, error) { switch d := v.(type) { default: return json.Marshal(v) case string: return []byte(d), nil case []byte: return d, nil } } func contcat(s ...string) string { sb := strings.Builder{} for i := range s { sb.WriteString(s[i]) } return sb.String() } func (rc *RedisCache) key(key string) string { // prepare in format "<Prefix>:<key>" if rc.addPrefix { return contcat(rc.keyStr, key) } return key } func (rc *RedisCache) Type() int { return TypeRedisCache }