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

- 新增 /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
+9 -5
View File
@@ -3,7 +3,7 @@ import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { site } from '../site'
import { openLightbox } from '../lightbox'
import { relativeDate, stripTags, minutesLabel, sanitizeHtml } from '../utils'
import { relativeDate, stripTags, minutesLabel, sanitizeHtml, thumbURL, thumbifyHtml } from '../utils'
const router = useRouter()
@@ -20,10 +20,11 @@ function openPost() {
router.push(`/post/${props.post.slug}`)
}
// 时间线里短文不铺全文,超过 5 行折叠;长文只显示摘要
// 时间线里短文不铺全文,超过 5 行折叠;长文只显示摘要。
// 正文里的 <img> 换缩略图并补懒加载(详情仍是原图)。
const body = computed(() => {
if (!isShort.value) return props.post.summary || stripTags(props.post.content_html || '')
return sanitizeHtml(props.post.content_html)
return sanitizeHtml(thumbifyHtml(props.post.content_html))
})
// 长文没写摘要时,从正文里截一段纯文本
@@ -33,6 +34,9 @@ const summaryText = computed(() => {
})
const initial = computed(() => (site.author_name || 'O').trim().slice(0, 1).toUpperCase())
// 配图缩略图宽度:单图全宽格取宽一些,多图小格 480 足够(2x 屏也清晰)
const gridW = computed(() => (props.post.images && props.post.images.length === 1 ? 720 : 480))
</script>
<template>
@@ -58,7 +62,7 @@ const initial = computed(() => (site.author_name || 'O').trim().slice(0, 1).toUp
<template v-else>
<div class="summary">{{ summaryText }}</div>
<div v-if="post.cover_url" class="imgs cover">
<img :src="post.cover_url" alt="" loading="lazy" decoding="async" />
<img :src="thumbURL(post.cover_url, 640)" alt="" loading="lazy" decoding="async" />
</div>
</template>
@@ -71,7 +75,7 @@ const initial = computed(() => (site.author_name || 'O').trim().slice(0, 1).toUp
<img
v-for="(u, i) in post.images.slice(0, 4)"
:key="i"
:src="u"
:src="thumbURL(u, gridW)"
alt=""
loading="lazy"
decoding="async"