Files
SmyWorkCollect/SproutWorkCollect-Backend-Golang/internal/service/ratelimit.go

65 lines
1.7 KiB
Go

package service
import (
"crypto/md5"
"fmt"
"sync"
"time"
)
// rateLimits defines minimum seconds between the same action by the same user on the same work.
var rateLimits = map[string]int64{
"view": 60,
"download": 300,
"like": 3600,
}
// RateLimiter provides per-user, per-action, per-work rate limiting backed by an in-memory store.
// The store is never persisted; it resets on service restart (same behaviour as the Python version).
type RateLimiter struct {
mu sync.Mutex
// fingerprint → actionType → workID → last unix timestamp
actions map[string]map[string]map[string]int64
}
// NewRateLimiter allocates a new empty RateLimiter.
func NewRateLimiter() *RateLimiter {
return &RateLimiter{
actions: make(map[string]map[string]map[string]int64),
}
}
// CanPerform returns true and records the timestamp if the rate limit window has elapsed.
// Returns false if the action is too frequent.
func (rl *RateLimiter) CanPerform(fingerprint, actionType, workID string) bool {
rl.mu.Lock()
defer rl.mu.Unlock()
limit, ok := rateLimits[actionType]
if !ok {
return true
}
now := time.Now().Unix()
if rl.actions[fingerprint] == nil {
rl.actions[fingerprint] = make(map[string]map[string]int64)
}
if rl.actions[fingerprint][actionType] == nil {
rl.actions[fingerprint][actionType] = make(map[string]int64)
}
last := rl.actions[fingerprint][actionType][workID]
if now-last >= limit {
rl.actions[fingerprint][actionType][workID] = now
return true
}
return false
}
// Fingerprint creates an MD5 hex string from IP and User-Agent for rate-limit keying.
func Fingerprint(ip, userAgent string) string {
h := md5.Sum([]byte(ip + ":" + userAgent))
return fmt.Sprintf("%x", h)
}