mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
upload
This commit is contained in:
@@ -0,0 +1,263 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CacheService provides a simple in-memory cache with TTL
|
||||
type CacheService struct {
|
||||
mu sync.RWMutex
|
||||
items map[string]*cacheItem
|
||||
}
|
||||
|
||||
type cacheItem struct {
|
||||
Value interface{}
|
||||
Expiration time.Time
|
||||
}
|
||||
|
||||
var (
|
||||
defaultCache *CacheService
|
||||
cacheInitOnce sync.Once
|
||||
)
|
||||
|
||||
// GetCacheService returns singleton cache instance
|
||||
func GetCacheService() *CacheService {
|
||||
cacheInitOnce.Do(func() {
|
||||
defaultCache = &CacheService{
|
||||
items: make(map[string]*cacheItem),
|
||||
}
|
||||
// Start cleanup goroutine
|
||||
go defaultCache.startCleanup()
|
||||
})
|
||||
return defaultCache
|
||||
}
|
||||
|
||||
// Get retrieves value from cache
|
||||
func (cs *CacheService) Get(key string, dest interface{}) error {
|
||||
cs.mu.RLock()
|
||||
item, exists := cs.items[key]
|
||||
cs.mu.RUnlock()
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("key not found")
|
||||
}
|
||||
|
||||
if time.Now().After(item.Expiration) {
|
||||
cs.Delete(key)
|
||||
return fmt.Errorf("key expired")
|
||||
}
|
||||
|
||||
// Type assertion or JSON marshal/unmarshal
|
||||
switch v := item.Value.(type) {
|
||||
case []byte:
|
||||
return json.Unmarshal(v, dest)
|
||||
default:
|
||||
// Marshal and unmarshal for type conversion
|
||||
data, err := json.Marshal(item.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(data, dest)
|
||||
}
|
||||
}
|
||||
|
||||
// Set stores value in cache with TTL
|
||||
func (cs *CacheService) Set(key string, value interface{}, ttl time.Duration) error {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
|
||||
cs.items[key] = &cacheItem{
|
||||
Value: value,
|
||||
Expiration: time.Now().Add(ttl),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes item from cache
|
||||
func (cs *CacheService) Delete(key string) {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
delete(cs.items, key)
|
||||
}
|
||||
|
||||
// Clear removes all items from cache
|
||||
func (cs *CacheService) Clear() {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
cs.items = make(map[string]*cacheItem)
|
||||
}
|
||||
|
||||
// Has checks if key exists and is not expired
|
||||
func (cs *CacheService) Has(key string) bool {
|
||||
cs.mu.RLock()
|
||||
item, exists := cs.items[key]
|
||||
cs.mu.RUnlock()
|
||||
|
||||
if !exists {
|
||||
return false
|
||||
}
|
||||
|
||||
if time.Now().After(item.Expiration) {
|
||||
cs.Delete(key)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// GetOrSet retrieves from cache or executes function and caches result
|
||||
func (cs *CacheService) GetOrSet(key string, dest interface{}, ttl time.Duration, fn func() (interface{}, error)) error {
|
||||
// Try to get from cache
|
||||
err := cs.Get(key, dest)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Execute function
|
||||
value, err := fn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Store in cache
|
||||
if err := cs.Set(key, value, ttl); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Marshal and unmarshal for type conversion
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(data, dest)
|
||||
}
|
||||
|
||||
// startCleanup periodically removes expired items
|
||||
func (cs *CacheService) startCleanup() {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
cs.cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *CacheService) cleanup() {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
for key, item := range cs.items {
|
||||
if now.After(item.Expiration) {
|
||||
delete(cs.items, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stats returns cache statistics
|
||||
func (cs *CacheService) Stats() map[string]interface{} {
|
||||
cs.mu.RLock()
|
||||
defer cs.mu.RUnlock()
|
||||
|
||||
expired := 0
|
||||
now := time.Now()
|
||||
for _, item := range cs.items {
|
||||
if now.After(item.Expiration) {
|
||||
expired++
|
||||
}
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"total_items": len(cs.items),
|
||||
"expired_items": expired,
|
||||
"active_items": len(cs.items) - expired,
|
||||
}
|
||||
}
|
||||
|
||||
// CacheKey generates consistent cache keys
|
||||
func CacheKey(parts ...string) string {
|
||||
return "cache:" + joinStrings(parts, ":")
|
||||
}
|
||||
|
||||
func joinStrings(parts []string, sep string) string {
|
||||
if len(parts) == 0 {
|
||||
return ""
|
||||
}
|
||||
result := parts[0]
|
||||
for i := 1; i < len(parts); i++ {
|
||||
result += sep + parts[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// WithCache is a decorator for caching function results
|
||||
func WithCache(key string, ttl time.Duration, fn func() (interface{}, error)) (interface{}, error) {
|
||||
cache := GetCacheService()
|
||||
|
||||
// Try cache first
|
||||
var result interface{}
|
||||
if err := cache.Get(key, &result); err == nil {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Execute function
|
||||
result, err := fn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Cache result
|
||||
cache.Set(key, result, ttl)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// InvalidateCachePattern removes all keys matching pattern
|
||||
func (cs *CacheService) InvalidateCachePattern(pattern string) {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
|
||||
for key := range cs.items {
|
||||
if matchesPattern(key, pattern) {
|
||||
delete(cs.items, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func matchesPattern(key, pattern string) bool {
|
||||
// Simple pattern matching - supports * wildcard
|
||||
if pattern == "*" {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if pattern contains wildcard
|
||||
if len(pattern) > 0 && pattern[len(pattern)-1] == '*' {
|
||||
prefix := pattern[:len(pattern)-1]
|
||||
return len(key) >= len(prefix) && key[:len(prefix)] == prefix
|
||||
}
|
||||
|
||||
return key == pattern
|
||||
}
|
||||
|
||||
// WarmupCache preloads frequently accessed data
|
||||
func WarmupCache(ctx context.Context) error {
|
||||
_ = GetCacheService() // Available for warmup logic
|
||||
|
||||
// Example: preload settings
|
||||
// This should be called on application startup
|
||||
|
||||
// Add your warmup logic here
|
||||
// Example:
|
||||
// cache := GetCacheService()
|
||||
// settings, err := fetchSettings()
|
||||
// if err == nil {
|
||||
// cache.Set("settings:main", settings, 1*time.Hour)
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user