1
This commit is contained in:
16
internal/cache/client.go
vendored
Normal file
16
internal/cache/client.go
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
package cache
|
||||
|
||||
import "fmt"
|
||||
|
||||
func New(c Config, t Type) (Client, error) {
|
||||
switch t {
|
||||
case ValkeyCacheType:
|
||||
return newValKeyCache(c)
|
||||
default:
|
||||
return nil, ErrUnknownCacheType
|
||||
}
|
||||
}
|
||||
|
||||
func getKey(k string, t ValueType) string {
|
||||
return fmt.Sprintf("%s:%s", t, k)
|
||||
}
|
16
internal/cache/errors.go
vendored
Normal file
16
internal/cache/errors.go
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
package cache
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrUnknownCacheType = errors.New("unknown cache type")
|
||||
ErrInvalidConfig = errors.New("invalid config")
|
||||
ErrConnect = errors.New("connecting to server")
|
||||
ErrTlsConfig = errors.New("tls config")
|
||||
ErrInvalidInstance = errors.New("check instance failed")
|
||||
ErrKeyNotFound = errors.New("key not found")
|
||||
ErrKeyExists = errors.New("key already exists")
|
||||
ErrGet = errors.New("failed to get value")
|
||||
ErrSet = errors.New("failed to set value")
|
||||
ErrDel = errors.New("failed to delete value")
|
||||
)
|
53
internal/cache/types.go
vendored
Normal file
53
internal/cache/types.go
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
// Set добавляет в кеш под ключем key значение value
|
||||
//
|
||||
// Если такого ключа с таким типом значения нет,
|
||||
// то возвращает ошибку ErrKeyExists
|
||||
Set(ctx context.Context, key string, valueType ValueType, value any, expiration time.Duration) error
|
||||
|
||||
// Del удаляет значение по ключу key и указанному valueType
|
||||
//
|
||||
// Если такого ключа с таким типом значения нет,
|
||||
// то возвращает ошибку ErrKeyNotFound
|
||||
Del(ctx context.Context, key string, valueType ValueType) error
|
||||
|
||||
// Get возвращает значение по ключу key и valueType
|
||||
//
|
||||
// Если такого ключа с таким типом значения нет,
|
||||
// то возвращает ошибку ErrKeyNotFound
|
||||
//
|
||||
// В случае, если получаемое значение не является строкой,
|
||||
// необходимо воспользоваться соответсвующим методом под
|
||||
// этот тип данных
|
||||
Get(ctx context.Context, key string, valueType ValueType) (string, error)
|
||||
|
||||
// TODO: По необходимости расширить необходимыми методами
|
||||
}
|
||||
|
||||
type (
|
||||
Type uint
|
||||
ValueType string
|
||||
Config any
|
||||
)
|
||||
|
||||
const (
|
||||
ValkeyCacheType Type = iota
|
||||
)
|
||||
|
||||
const (
|
||||
LogoValueType ValueType = "value_logo_type"
|
||||
DocumentsValueType ValueType = "value_documents_type"
|
||||
|
||||
PswResetOTPValueType ValueType = "value_otp_type"
|
||||
PswResetTokenValueType ValueType = "value_token_type"
|
||||
|
||||
IntegrationCompanyValueType ValueType = "integration:company"
|
||||
IntegrationVacancyValueType ValueType = "integration:vacancy"
|
||||
)
|
143
internal/cache/valkey.go
vendored
Normal file
143
internal/cache/valkey.go
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis/v7"
|
||||
)
|
||||
|
||||
type ValKeyCacheConfig struct {
|
||||
Addrs []string
|
||||
Password string
|
||||
ReadOnly bool
|
||||
DialTimeout time.Duration
|
||||
PoolSize int
|
||||
DefaultTTL time.Duration
|
||||
RootCaFilePath string
|
||||
}
|
||||
|
||||
type valkeyClient interface {
|
||||
Set(key string, value any, expiration time.Duration) *redis.StatusCmd
|
||||
Get(key string) *redis.StringCmd
|
||||
Del(keys ...string) *redis.IntCmd
|
||||
Exists(keys ...string) *redis.IntCmd
|
||||
Ping() *redis.StatusCmd
|
||||
}
|
||||
|
||||
type valKeyCache struct {
|
||||
config ValKeyCacheConfig
|
||||
client valkeyClient
|
||||
}
|
||||
|
||||
func newValKeyCache(c Config) (*valKeyCache, error) {
|
||||
cfg, ok := c.(ValKeyCacheConfig)
|
||||
if !ok {
|
||||
return nil, ErrInvalidConfig
|
||||
}
|
||||
|
||||
caCert, err := os.ReadFile(cfg.RootCaFilePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s", ErrTlsConfig, err.Error())
|
||||
}
|
||||
|
||||
caCertPool := x509.NewCertPool()
|
||||
|
||||
if ok := caCertPool.AppendCertsFromPEM(caCert); !ok {
|
||||
return nil, fmt.Errorf("%w: %s", ErrTlsConfig, "failed to append CA cert to pool")
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
RootCAs: caCertPool,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
|
||||
client := redis.NewUniversalClient(&redis.UniversalOptions{
|
||||
Addrs: cfg.Addrs,
|
||||
Password: cfg.Password,
|
||||
ReadOnly: cfg.ReadOnly,
|
||||
DialTimeout: cfg.DialTimeout,
|
||||
PoolSize: cfg.PoolSize,
|
||||
TLSConfig: tlsConfig,
|
||||
})
|
||||
|
||||
if err := client.Ping().Err(); err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrConnect, err)
|
||||
}
|
||||
|
||||
return &valKeyCache{
|
||||
config: cfg,
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *valKeyCache) Get(ctx context.Context, key string, valueType ValueType) (string, error) {
|
||||
if err := c.checkInstance(); err != nil {
|
||||
return "", fmt.Errorf("%w: %v", ErrGet, err)
|
||||
}
|
||||
|
||||
val, err := c.client.Get(getKey(key, valueType)).Result()
|
||||
if err == redis.Nil {
|
||||
return "", ErrKeyNotFound
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w: %v", ErrGet, err)
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (c *valKeyCache) Set(ctx context.Context, key string, valueType ValueType, value any, expiration time.Duration) error {
|
||||
if err := c.checkInstance(); err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrSet, err)
|
||||
}
|
||||
|
||||
key = getKey(key, valueType)
|
||||
|
||||
if exists, err := c.client.Exists(key).Result(); err != nil || exists == 1 {
|
||||
if exists == 1 {
|
||||
return ErrKeyExists
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w: %v", ErrSet, err)
|
||||
}
|
||||
|
||||
if err := c.client.Set(key, value, expiration).Err(); err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrSet, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *valKeyCache) Del(ctx context.Context, key string, valueType ValueType) error {
|
||||
if err := c.checkInstance(); err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrDel, err.Error())
|
||||
}
|
||||
|
||||
if res, err := c.client.Del(getKey(key, valueType)).Result(); err != nil || res == 0 {
|
||||
if res == 0 {
|
||||
return ErrKeyNotFound
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w: %s", ErrDel, err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *valKeyCache) checkInstance() error {
|
||||
if c == nil {
|
||||
return ErrInvalidInstance
|
||||
}
|
||||
|
||||
if s := c.client.Ping(); s.Err() != nil {
|
||||
return fmt.Errorf("%w: %v", ErrInvalidInstance, s.Err().Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user