feat: 请求日志记录

This commit is contained in:
tbphp
2025-07-12 10:15:07 +08:00
parent 70554b8fe5
commit 13d7c4dbad
13 changed files with 411 additions and 115 deletions

View File

@@ -14,12 +14,9 @@ type memoryStoreItem struct {
}
// MemoryStore is an in-memory key-value store that is safe for concurrent use.
// It now supports simple K/V, HASH, and LIST data types.
type MemoryStore struct {
mu sync.RWMutex
data map[string]any
// For Pub/Sub
mu sync.RWMutex
data map[string]any
muSubscribers sync.RWMutex
subscribers map[string]map[chan *Message]struct{}
}
@@ -30,14 +27,11 @@ func NewMemoryStore() *MemoryStore {
data: make(map[string]any),
subscribers: make(map[string]map[chan *Message]struct{}),
}
// The cleanup loop was removed as it's not compatible with multiple data types
// without a unified expiration mechanism, and the KeyPool feature does not rely on TTLs.
return s
}
// Close cleans up resources.
func (s *MemoryStore) Close() error {
// Nothing to close for now.
return nil
}
@@ -73,9 +67,7 @@ func (s *MemoryStore) Get(key string) ([]byte, error) {
return nil, fmt.Errorf("type mismatch: key '%s' holds a different data type", key)
}
// Check for expiration
if item.expiresAt > 0 && time.Now().UnixNano() > item.expiresAt {
// Lazy deletion
s.mu.Lock()
delete(s.data, key)
s.mu.Unlock()
@@ -93,6 +85,16 @@ func (s *MemoryStore) Delete(key string) error {
return nil
}
// Del removes multiple values by their keys.
func (s *MemoryStore) Del(keys ...string) error {
s.mu.Lock()
defer s.mu.Unlock()
for _, key := range keys {
delete(s.data, key)
}
return nil
}
// Exists checks if a key exists.
func (s *MemoryStore) Exists(key string) (bool, error) {
s.mu.RLock()
@@ -103,10 +105,8 @@ func (s *MemoryStore) Exists(key string) (bool, error) {
return false, nil
}
// Check for expiration only if it's a simple K/V item
if item, ok := rawItem.(memoryStoreItem); ok {
if item.expiresAt > 0 && time.Now().UnixNano() > item.expiresAt {
// Lazy deletion
s.mu.Lock()
delete(s.data, key)
s.mu.Unlock()
@@ -122,12 +122,10 @@ func (s *MemoryStore) SetNX(key string, value []byte, ttl time.Duration) (bool,
s.mu.Lock()
defer s.mu.Unlock()
// In memory store, we need to manually check for existence and expiration
rawItem, exists := s.data[key]
if exists {
if item, ok := rawItem.(memoryStoreItem); ok {
if item.expiresAt == 0 || time.Now().UnixNano() < item.expiresAt {
// Key exists and is not expired
return false, nil
}
} else {
@@ -179,7 +177,6 @@ func (s *MemoryStore) HGetAll(key string) (map[string]string, error) {
rawHash, exists := s.data[key]
if !exists {
// Per Redis convention, HGETALL on a non-existent key returns an empty map, not an error.
return make(map[string]string), nil
}
@@ -188,7 +185,6 @@ func (s *MemoryStore) HGetAll(key string) (map[string]string, error) {
return nil, fmt.Errorf("type mismatch: key '%s' holds a different data type", key)
}
// Return a copy to prevent race conditions on the returned map
result := make(map[string]string, len(hash))
for k, v := range hash {
result[k] = v
@@ -265,9 +261,7 @@ func (s *MemoryStore) LRem(key string, count int64, value any) error {
strValue := fmt.Sprint(value)
newList := make([]string, 0, len(list))
// LREM with count = 0: Remove all elements equal to value.
if count != 0 {
// For now, only implement count = 0 behavior as it's what we need.
return fmt.Errorf("LRem with non-zero count is not implemented in MemoryStore")
}
@@ -298,7 +292,6 @@ func (s *MemoryStore) Rotate(key string) (string, error) {
return "", ErrNotFound
}
// "RPOP"
lastIndex := len(list) - 1
item := list[lastIndex]
@@ -309,6 +302,63 @@ func (s *MemoryStore) Rotate(key string) (string, error) {
return item, nil
}
// --- SET operations ---
// SAdd adds members to a set.
func (s *MemoryStore) SAdd(key string, members ...any) error {
s.mu.Lock()
defer s.mu.Unlock()
var set map[string]struct{}
rawSet, exists := s.data[key]
if !exists {
set = make(map[string]struct{})
s.data[key] = set
} else {
var ok bool
set, ok = rawSet.(map[string]struct{})
if !ok {
return fmt.Errorf("type mismatch: key '%s' holds a different data type", key)
}
}
for _, member := range members {
set[fmt.Sprint(member)] = struct{}{}
}
return nil
}
// SPopN randomly removes and returns the given number of members from a set.
func (s *MemoryStore) SPopN(key string, count int64) ([]string, error) {
s.mu.Lock()
defer s.mu.Unlock()
rawSet, exists := s.data[key]
if !exists {
return []string{}, nil
}
set, ok := rawSet.(map[string]struct{})
if !ok {
return nil, fmt.Errorf("type mismatch: key '%s' holds a different data type", key)
}
if count > int64(len(set)) {
count = int64(len(set))
}
popped := make([]string, 0, count)
for member := range set {
if int64(len(popped)) >= count {
break
}
popped = append(popped, member)
delete(set, member)
}
return popped, nil
}
// --- Pub/Sub operations ---
// memorySubscription implements the Subscription interface for the in-memory store.
@@ -350,11 +400,10 @@ func (s *MemoryStore) Publish(channel string, message []byte) error {
if subs, ok := s.subscribers[channel]; ok {
for subCh := range subs {
// Non-blocking send
go func(c chan *Message) {
select {
case c <- msg:
case <-time.After(1 * time.Second): // Prevent goroutine leak if receiver is stuck
case <-time.After(1 * time.Second):
}
}(subCh)
}