mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 22:06:38 +08:00
187 lines
5.0 KiB
Go
187 lines
5.0 KiB
Go
// Copyright 2026 Arctel.net
|
||
// SPDX-License-Identifier: Apache-2.0
|
||
|
||
package cap
|
||
|
||
import (
|
||
"context"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/redis/go-redis/v9"
|
||
)
|
||
|
||
// Store defines the storage interface for challenge nonces and verification tokens
|
||
type Store interface {
|
||
Get(ctx context.Context, key string) (string, bool, error)
|
||
Set(ctx context.Context, key string, val string, ttl time.Duration) error
|
||
Delete(ctx context.Context, key string) error
|
||
// SetNX atomically sets key=val with the given TTL only when the key does not
|
||
// exist yet. It returns true when the key was actually written (i.e. this
|
||
// caller "won" the race), and false when the key already existed.
|
||
SetNX(ctx context.Context, key string, val string, ttl time.Duration) (bool, error)
|
||
// GetAndDelete atomically retrieves the value of key and removes it in a
|
||
// single operation. Returns ("", false, nil) when the key does not exist.
|
||
GetAndDelete(ctx context.Context, key string) (string, bool, error)
|
||
}
|
||
|
||
type memoryItem struct {
|
||
value string
|
||
expiresAt time.Time
|
||
}
|
||
|
||
// MemoryStore is a thread-safe in-memory implementation of Store
|
||
type MemoryStore struct {
|
||
items map[string]memoryItem
|
||
mu sync.Mutex // unified write-lock; promotes to exclusive for all ops
|
||
}
|
||
|
||
// NewMemoryStore creates and initializes a new MemoryStore
|
||
func NewMemoryStore(cleanupInterval time.Duration) *MemoryStore {
|
||
store := &MemoryStore{
|
||
items: make(map[string]memoryItem),
|
||
}
|
||
if cleanupInterval > 0 {
|
||
go store.startCleanupLoop(cleanupInterval)
|
||
}
|
||
return store
|
||
}
|
||
|
||
// Get 从 MemoryStore 获取指定 key 的值
|
||
func (s *MemoryStore) Get(_ context.Context, key string) (string, bool, error) {
|
||
s.mu.Lock()
|
||
defer s.mu.Unlock()
|
||
return s.getLocked(key)
|
||
}
|
||
|
||
// getLocked is the internal helper – caller must hold s.mu.
|
||
func (s *MemoryStore) getLocked(key string) (string, bool, error) {
|
||
item, found := s.items[key]
|
||
if !found {
|
||
return "", false, nil
|
||
}
|
||
if time.Now().After(item.expiresAt) {
|
||
delete(s.items, key)
|
||
return "", false, nil
|
||
}
|
||
return item.value, true, nil
|
||
}
|
||
|
||
// Set 向 MemoryStore 写入指定 key 的值
|
||
func (s *MemoryStore) Set(_ context.Context, key string, val string, ttl time.Duration) error {
|
||
s.mu.Lock()
|
||
defer s.mu.Unlock()
|
||
s.items[key] = memoryItem{
|
||
value: val,
|
||
expiresAt: time.Now().Add(ttl),
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// Delete 从 MemoryStore 删除指定 key
|
||
func (s *MemoryStore) Delete(_ context.Context, key string) error {
|
||
s.mu.Lock()
|
||
defer s.mu.Unlock()
|
||
delete(s.items, key)
|
||
return nil
|
||
}
|
||
|
||
// SetNX atomically sets key only when it is absent (or expired).
|
||
// Returns true if the key was written by this call.
|
||
func (s *MemoryStore) SetNX(_ context.Context, key string, val string, ttl time.Duration) (bool, error) {
|
||
s.mu.Lock()
|
||
defer s.mu.Unlock()
|
||
|
||
_, exists, _ := s.getLocked(key)
|
||
if exists {
|
||
return false, nil
|
||
}
|
||
s.items[key] = memoryItem{
|
||
value: val,
|
||
expiresAt: time.Now().Add(ttl),
|
||
}
|
||
return true, nil
|
||
}
|
||
|
||
// GetAndDelete atomically retrieves and removes key in one critical section.
|
||
func (s *MemoryStore) GetAndDelete(_ context.Context, key string) (string, bool, error) {
|
||
s.mu.Lock()
|
||
defer s.mu.Unlock()
|
||
|
||
val, exists, err := s.getLocked(key)
|
||
if err != nil || !exists {
|
||
return "", false, err
|
||
}
|
||
delete(s.items, key)
|
||
return val, true, nil
|
||
}
|
||
|
||
func (s *MemoryStore) startCleanupLoop(interval time.Duration) {
|
||
ticker := time.NewTicker(interval)
|
||
for range ticker.C {
|
||
s.cleanupExpired()
|
||
}
|
||
}
|
||
|
||
func (s *MemoryStore) cleanupExpired() {
|
||
now := time.Now()
|
||
s.mu.Lock()
|
||
defer s.mu.Unlock()
|
||
for k, v := range s.items {
|
||
if now.After(v.expiresAt) {
|
||
delete(s.items, k)
|
||
}
|
||
}
|
||
}
|
||
|
||
// RedisStore is a GORM-compatible/standalone Redis-backed implementation of Store
|
||
type RedisStore struct {
|
||
client redis.UniversalClient
|
||
}
|
||
|
||
// NewRedisStore creates a new RedisStore wrapping a redis.UniversalClient
|
||
func NewRedisStore(client redis.UniversalClient) *RedisStore {
|
||
return &RedisStore{
|
||
client: client,
|
||
}
|
||
}
|
||
|
||
// Get 从 RedisStore 获取指定 key 的值
|
||
func (s *RedisStore) Get(ctx context.Context, key string) (string, bool, error) {
|
||
val, err := s.client.Get(ctx, key).Result()
|
||
if err == redis.Nil {
|
||
return "", false, nil
|
||
}
|
||
if err != nil {
|
||
return "", false, err
|
||
}
|
||
return val, true, nil
|
||
}
|
||
|
||
// Set 向 RedisStore 写入指定 key 的值
|
||
func (s *RedisStore) Set(ctx context.Context, key string, val string, ttl time.Duration) error {
|
||
return s.client.Set(ctx, key, val, ttl).Err()
|
||
}
|
||
|
||
// Delete 从 RedisStore 删除指定 key
|
||
func (s *RedisStore) Delete(ctx context.Context, key string) error {
|
||
return s.client.Del(ctx, key).Err()
|
||
}
|
||
|
||
// SetNX wraps Redis SET NX – returns true only when the key was newly created.
|
||
func (s *RedisStore) SetNX(ctx context.Context, key string, val string, ttl time.Duration) (bool, error) {
|
||
return s.client.SetNX(ctx, key, val, ttl).Result()
|
||
}
|
||
|
||
// GetAndDelete wraps Redis GETDEL (available since Redis 6.2).
|
||
func (s *RedisStore) GetAndDelete(ctx context.Context, key string) (string, bool, error) {
|
||
val, err := s.client.GetDel(ctx, key).Result()
|
||
if err == redis.Nil {
|
||
return "", false, nil
|
||
}
|
||
if err != nil {
|
||
return "", false, err
|
||
}
|
||
return val, true, nil
|
||
}
|