273 lines
6.4 KiB
Go
273 lines
6.4 KiB
Go
package redis
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
)
|
|
|
|
type Config struct {
|
|
Addr string
|
|
Password string
|
|
DB int
|
|
}
|
|
|
|
type Client struct {
|
|
rdb *redis.Client
|
|
}
|
|
|
|
func NewClient(cfg Config) (*Client, error) {
|
|
rdb := redis.NewClient(&redis.Options{
|
|
Addr: cfg.Addr,
|
|
Password: cfg.Password,
|
|
DB: cfg.DB,
|
|
})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
if err := rdb.Ping(ctx).Err(); err != nil {
|
|
return nil, fmt.Errorf("failed to connect to redis: %w", err)
|
|
}
|
|
|
|
return &Client{rdb: rdb}, nil
|
|
}
|
|
|
|
func (c *Client) Close() error {
|
|
return c.rdb.Close()
|
|
}
|
|
|
|
func (c *Client) Set(ctx context.Context, key string, value interface{}, ttl time.Duration) error {
|
|
data, err := json.Marshal(value)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal value: %w", err)
|
|
}
|
|
return c.rdb.Set(ctx, key, data, ttl).Err()
|
|
}
|
|
|
|
func (c *Client) Get(ctx context.Context, key string, dest interface{}) error {
|
|
data, err := c.rdb.Get(ctx, key).Bytes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := json.Unmarshal(data, dest); err != nil {
|
|
return fmt.Errorf("failed to unmarshal value: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) Exists(ctx context.Context, key string) (bool, error) {
|
|
result, err := c.rdb.Exists(ctx, key).Result()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return result > 0, nil
|
|
}
|
|
|
|
func (c *Client) Delete(ctx context.Context, keys ...string) error {
|
|
return c.rdb.Del(ctx, keys...).Err()
|
|
}
|
|
|
|
func (c *Client) SetTTL(ctx context.Context, key string, ttl time.Duration) error {
|
|
return c.rdb.Expire(ctx, key, ttl).Err()
|
|
}
|
|
|
|
func (c *Client) GetTTL(ctx context.Context, key string) (time.Duration, error) {
|
|
return c.rdb.TTL(ctx, key).Result()
|
|
}
|
|
|
|
func (c *Client) LPush(ctx context.Context, key string, values ...interface{}) error {
|
|
data := make([]interface{}, len(values))
|
|
for i, v := range values {
|
|
b, err := json.Marshal(v)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal list value: %w", err)
|
|
}
|
|
data[i] = b
|
|
}
|
|
return c.rdb.LPush(ctx, key, data...).Err()
|
|
}
|
|
|
|
func (c *Client) RPush(ctx context.Context, key string, values ...interface{}) error {
|
|
data := make([]interface{}, len(values))
|
|
for i, v := range values {
|
|
b, err := json.Marshal(v)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal list value: %w", err)
|
|
}
|
|
data[i] = b
|
|
}
|
|
return c.rdb.RPush(ctx, key, data...).Err()
|
|
}
|
|
|
|
func (c *Client) LRange(ctx context.Context, key string, start, stop int64, dest interface{}) error {
|
|
results, err := c.rdb.LRange(ctx, key, start, stop).Result()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
data, err := json.Marshal(results)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal results: %w", err)
|
|
}
|
|
|
|
return json.Unmarshal(data, dest)
|
|
}
|
|
|
|
func (c *Client) LLen(ctx context.Context, key string) (int64, error) {
|
|
return c.rdb.LLen(ctx, key).Result()
|
|
}
|
|
|
|
func (c *Client) LPop(ctx context.Context, key string) (string, error) {
|
|
return c.rdb.LPop(ctx, key).Result()
|
|
}
|
|
|
|
func (c *Client) LTrim(ctx context.Context, key string, start, stop int64) error {
|
|
return c.rdb.LTrim(ctx, key, start, stop).Err()
|
|
}
|
|
|
|
func (c *Client) LSet(ctx context.Context, key string, index int64, value interface{}) error {
|
|
data, err := json.Marshal(value)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal value: %w", err)
|
|
}
|
|
return c.rdb.LSet(ctx, key, index, data).Err()
|
|
}
|
|
|
|
func (c *Client) SAdd(ctx context.Context, key string, members ...interface{}) error {
|
|
data := make([]interface{}, len(members))
|
|
for i, m := range members {
|
|
b, err := json.Marshal(m)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal set member: %w", err)
|
|
}
|
|
data[i] = b
|
|
}
|
|
return c.rdb.SAdd(ctx, key, data...).Err()
|
|
}
|
|
|
|
func (c *Client) SMembers(ctx context.Context, key string) ([]string, error) {
|
|
return c.rdb.SMembers(ctx, key).Result()
|
|
}
|
|
|
|
func (c *Client) SRem(ctx context.Context, key string, members ...interface{}) error {
|
|
data := make([]interface{}, len(members))
|
|
for i, m := range members {
|
|
b, err := json.Marshal(m)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal set member: %w", err)
|
|
}
|
|
data[i] = b
|
|
}
|
|
return c.rdb.SRem(ctx, key, data...).Err()
|
|
}
|
|
|
|
func (c *Client) HSet(ctx context.Context, key string, field string, value interface{}) error {
|
|
data, err := json.Marshal(value)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal hash value: %w", err)
|
|
}
|
|
return c.rdb.HSet(ctx, key, field, data).Err()
|
|
}
|
|
|
|
func (c *Client) HGet(ctx context.Context, key, field string, dest interface{}) error {
|
|
data, err := c.rdb.HGet(ctx, key, field).Bytes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return json.Unmarshal(data, dest)
|
|
}
|
|
|
|
func (c *Client) HDel(ctx context.Context, key string, fields ...string) error {
|
|
return c.rdb.HDel(ctx, key, fields...).Err()
|
|
}
|
|
|
|
func (c *Client) HGetAll(ctx context.Context, key string) (map[string]string, error) {
|
|
return c.rdb.HGetAll(ctx, key).Result()
|
|
}
|
|
|
|
func (c *Client) HExists(ctx context.Context, key, field string) (bool, error) {
|
|
return c.rdb.HExists(ctx, key, field).Result()
|
|
}
|
|
|
|
func (c *Client) Keys(ctx context.Context, pattern string) ([]string, error) {
|
|
return c.rdb.Keys(ctx, pattern).Result()
|
|
}
|
|
|
|
func (c *Client) ScanKeys(ctx context.Context, pattern string, callback func(key string) error) error {
|
|
var cursor uint64
|
|
for {
|
|
var keys []string
|
|
var err error
|
|
keys, cursor, err = c.rdb.Scan(ctx, cursor, pattern, 100).Result()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, key := range keys {
|
|
if err := callback(key); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if cursor == 0 {
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
const (
|
|
RoomTTL = 30 * time.Minute
|
|
PlayerTTL = 10 * time.Minute
|
|
)
|
|
|
|
func RoomKey(roomID string) string {
|
|
return "room:" + roomID
|
|
}
|
|
|
|
func PlayerKey(playerID string) string {
|
|
return "player:" + playerID
|
|
}
|
|
|
|
func RoomPlayersKey(roomID string) string {
|
|
return "room:" + roomID + ":players"
|
|
}
|
|
|
|
func RoomDeckKey(roomID string) string {
|
|
return "room:" + roomID + ":deck"
|
|
}
|
|
|
|
func RoomDiscardKey(roomID string) string {
|
|
return "room:" + roomID + ":discard"
|
|
}
|
|
|
|
func RoomTTLKey(roomID string) string {
|
|
return "room:" + roomID + ":ttl"
|
|
}
|
|
|
|
func PlayerTTLKey(playerID string) string {
|
|
return "player:" + playerID + ":ttl"
|
|
}
|
|
|
|
func CaptchaKey(captchaID string) string {
|
|
return "captcha:" + captchaID
|
|
}
|
|
|
|
func UserKey(username string) string {
|
|
return "user:" + username
|
|
}
|
|
|
|
func UserRoomKey(userID string) string {
|
|
return "user:" + userID + ":room"
|
|
}
|
|
|
|
func SessionKey(token string) string {
|
|
return "session:" + token
|
|
}
|
|
|
|
func UserIDToUsernameKey(userID string) string {
|
|
return "user_id:" + userID + ":username"
|
|
}
|