缩略图懒生成 + 评论登录限流:时间线不再拉原图

- 新增 /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 条/分钟
  (站主豁免,且只计成功写入)
This commit is contained in:
Sakurasan
2026-09-28 13:34:19 +08:00
parent b6342a622d
commit 6108aca34c
17 changed files with 822 additions and 71 deletions
+21
View File
@@ -18,6 +18,7 @@ import (
"oneblog/internal/auth"
"oneblog/internal/httpx"
"oneblog/internal/model"
"oneblog/internal/ratelimit"
"oneblog/internal/render"
"oneblog/internal/store"
)
@@ -121,18 +122,26 @@ func (a *API) githubCallback(w http.ResponseWriter, r *http.Request) {
httpx.NotFound(w)
return
}
ip := ratelimit.SourceKey(r)
if a.authFails.Blocked(ip) {
httpx.Error(w, http.StatusTooManyRequests, "登录失败次数过多,请稍后再试")
return
}
ck, err := r.Cookie(oauthStateCook)
if err != nil || ck.Value == "" || ck.Value != r.FormValue("state") {
a.authFails.Add(ip)
httpx.BadRequest(w, "state 不匹配,请重新登录")
return
}
gh, err := a.GH.Exchange(r.Context(), r.FormValue("code"), a.Cfg.SiteURL+"/api/auth/callback/github")
if err != nil {
a.authFails.Add(ip)
httpx.ServerError(w, err)
return
}
u, err := a.GH.FetchUser(r.Context(), gh)
if err != nil {
a.authFails.Add(ip)
httpx.ServerError(w, err)
return
}
@@ -206,6 +215,15 @@ func (a *API) createComment(w http.ResponseWriter, r *http.Request) {
httpx.Error(w, http.StatusForbidden, "你已被禁言,暂时无法评论")
return
}
// 写入限速:每读者每分钟最多 maxComments 条(站主豁免,批量回复不该被卡)
var rateKey string
if !isOwner {
rateKey = strconv.FormatInt(reader.ID, 10)
if a.commentNew.Blocked(rateKey) {
httpx.Error(w, http.StatusTooManyRequests, "发得太快了,休息一分钟再试")
return
}
}
st, err := a.Store.GetSettings()
if err != nil {
httpx.ServerError(w, err)
@@ -267,6 +285,9 @@ func (a *API) createComment(w http.ResponseWriter, r *http.Request) {
httpx.ServerError(w, err)
return
}
if rateKey != "" {
a.commentNew.Add(rateKey) // 只计成功写入:空正文这类手滑不扣配额
}
httpx.Created(w, c)
}