- 新增 /uploads/thumb/{key}?w=:首访从存储端读一次原图,标准库盒均值缩放后
落盘 data/.thumbnail_cache(按内容哈希命名,天然失效);失败写 .failed 冷却
一小时,非图片/超大一律 302 回原图,前端无感。原图路由改用 ServeContent,
补上视频拖动进度条所需的 Range 支持
- 前端 thumbURL/thumbifyHtml 接入两套 UI 的时间线配图、长文封面与短文正文,
并补 loading=lazy;详情页与灯箱仍用原图。/api/site 改为
{settings, uploads_public_base},让前端识别哪些直链属于自家存储
- 后台登录的滑动窗口限速器抽成 internal/ratelimit 共享包(调用面不变),
新增:读者登录失败按 IP 20 次/10 分钟、评论写入按读者 5 条/分钟
(站主豁免,且只计成功写入)
74 lines
1.6 KiB
Go
74 lines
1.6 KiB
Go
// Package ratelimit 是一个极小的按来源 IP 滑动窗口限速器,
|
|
// 后台登录与读者登录 / 评论写入共用同一实现。
|
|
// 只计失败/写入这类「不该高频」的事件;键取连接层地址,
|
|
// X-Forwarded-For 可伪造,不作为限速键。
|
|
package ratelimit
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type Window struct {
|
|
mu sync.Mutex
|
|
hits map[string][]time.Time
|
|
max int
|
|
window time.Duration
|
|
}
|
|
|
|
func New(max int, window time.Duration) *Window {
|
|
return &Window{hits: make(map[string][]time.Time), max: max, window: window}
|
|
}
|
|
|
|
// Blocked 报告 key 在窗口内是否已达上限。
|
|
func (l *Window) Blocked(key string) bool {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
return len(l.recent(key, time.Now())) >= l.max
|
|
}
|
|
|
|
// Add 记录一次事件。
|
|
func (l *Window) Add(key string) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
now := time.Now()
|
|
l.hits[key] = append(l.recent(key, now), now)
|
|
}
|
|
|
|
// Reset 清空 key 的计数(如登录成功后)。
|
|
func (l *Window) Reset(key string) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
delete(l.hits, key)
|
|
}
|
|
|
|
// recent 返回窗口内的事件时间;调用方必须持有 l.mu。
|
|
func (l *Window) recent(key string, now time.Time) []time.Time {
|
|
hs := l.hits[key]
|
|
cut := now.Add(-l.window)
|
|
i := 0
|
|
for ; i < len(hs); i++ {
|
|
if hs[i].After(cut) {
|
|
break
|
|
}
|
|
}
|
|
if i > 0 {
|
|
hs = hs[i:]
|
|
l.hits[key] = hs
|
|
}
|
|
if len(hs) == 0 {
|
|
delete(l.hits, key)
|
|
}
|
|
return hs
|
|
}
|
|
|
|
// SourceKey 取连接层来源地址(IPv6 去掉端口)。
|
|
func SourceKey(r *http.Request) string {
|
|
if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
|
|
return host
|
|
}
|
|
return r.RemoteAddr
|
|
}
|