初始提交:姜十三论坛 Jiang13 Forum

轻量自用论坛,Go 单二进制 + React SPA 内嵌 + SQLite。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
freefire
2026-06-15 21:08:52 +08:00
commit e1c1708715
140 changed files with 16115 additions and 0 deletions

69
service/ratelimit.go Normal file
View File

@@ -0,0 +1,69 @@
package service
import (
"sync"
"time"
)
// RateLimiter 简单内存限流器,防止重复刷屏
type RateLimiter struct {
mu sync.Mutex
records map[string][]time.Time
limit int // 窗口内最大次数
window time.Duration // 时间窗口
}
func NewRateLimiter(limit int, window time.Duration) *RateLimiter {
r := &RateLimiter{
records: make(map[string][]time.Time),
limit: limit,
window: window,
}
go r.cleanup()
return r
}
// Allow 检查 key如 userID+action是否允许操作
func (r *RateLimiter) Allow(key string) bool {
r.mu.Lock()
defer r.mu.Unlock()
now := time.Now()
cutoff := now.Add(-r.window)
times := r.records[key]
var valid []time.Time
for _, t := range times {
if t.After(cutoff) {
valid = append(valid, t)
}
}
if len(valid) >= r.limit {
r.records[key] = valid
return false
}
valid = append(valid, now)
r.records[key] = valid
return true
}
func (r *RateLimiter) cleanup() {
ticker := time.NewTicker(5 * time.Minute)
for range ticker.C {
r.mu.Lock()
now := time.Now()
cutoff := now.Add(-r.window * 2)
for k, times := range r.records {
var valid []time.Time
for _, t := range times {
if t.After(cutoff) {
valid = append(valid, t)
}
}
if len(valid) == 0 {
delete(r.records, k)
} else {
r.records[k] = valid
}
}
r.mu.Unlock()
}
}