新增群聊/私信与公共 WebSocket 总线,站长用户内容档案,并将通知与待审角标改为推送驱动;同步精简管理仪表盘并修复主题换肤 DOM 冲突。 Co-authored-by: Cursor <cursoragent@cursor.com>
82 lines
1.7 KiB
Go
82 lines
1.7 KiB
Go
package service
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// RateLimiter 内存速率限制器
|
|
type RateLimiter struct {
|
|
mu sync.Mutex
|
|
records map[string][]time.Time
|
|
limits map[string]int // key -> 每分钟最大请求数
|
|
cooldown map[string]time.Duration
|
|
}
|
|
|
|
func NewRateLimiter() *RateLimiter {
|
|
return &RateLimiter{
|
|
records: make(map[string][]time.Time),
|
|
limits: make(map[string]int),
|
|
cooldown: make(map[string]time.Duration),
|
|
}
|
|
}
|
|
|
|
// SetLimit 设置某类请求的每分钟限制
|
|
func (r *RateLimiter) SetLimit(key string, perMinute int) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.limits[key] = perMinute
|
|
}
|
|
|
|
// Allow 检查是否允许请求
|
|
func (r *RateLimiter) Allow(key string) bool {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
limit, ok := r.limits[key]
|
|
if !ok {
|
|
return true
|
|
}
|
|
|
|
now := time.Now()
|
|
windowStart := now.Add(-1 * time.Minute)
|
|
|
|
// 清理过期记录
|
|
records := r.records[key]
|
|
valid := records[:0]
|
|
for _, t := range records {
|
|
if t.After(windowStart) {
|
|
valid = append(valid, t)
|
|
}
|
|
}
|
|
|
|
if len(valid) >= limit {
|
|
r.records[key] = valid
|
|
return false
|
|
}
|
|
|
|
valid = append(valid, now)
|
|
r.records[key] = valid
|
|
return true
|
|
}
|
|
|
|
// 速率限制类型常量
|
|
const (
|
|
RateLogin = "login"
|
|
RateRegister = "register"
|
|
RatePost = "post"
|
|
RateComment = "comment"
|
|
RateChat = "chat" // 群聊发消息
|
|
)
|
|
|
|
// DefaultRateLimiter 创建默认速率限制器
|
|
func DefaultRateLimiter() *RateLimiter {
|
|
rl := NewRateLimiter()
|
|
rl.SetLimit(RateLogin, 20) // 登录 20/分钟
|
|
rl.SetLimit(RateRegister, 10) // 注册 10/分钟
|
|
rl.SetLimit(RatePost, 10) // 发帖 10/分钟
|
|
rl.SetLimit(RateComment, 30) // 评论 30/分钟
|
|
rl.SetLimit(RateChat, 30) // 群聊消息 30/分钟
|
|
return rl
|
|
}
|