fix: gallery preview retries and avoid caching failed image responses
Verify storage files before serving from the index, disable CDN caching on image 404/403 and host-isolation blocks, and retry gallery thumbnails after transient load failures.
This commit is contained in:
1 parent
b0a5fbfca5
commit
f25c89855f
4 files changed
+59
-6
No files matched your search
@@ -26,8 +26,48 @@ const emit = defineEmits<{
|
||||
const { formatFileSize } = useFileSize()
|
||||
const { t, locale } = useI18n()
|
||||
const imageError = ref(false)
|
||||
const retryCount = ref(0)
|
||||
const copyFormat = ref<CopyFormat>('url')
|
||||
|
||||
const MAX_PREVIEW_RETRIES = 3
|
||||
const PREVIEW_RETRY_DELAYS_MS = [1000, 2000, 4000]
|
||||
|
||||
const previewSrc = computed(() => {
|
||||
if (retryCount.value === 0) return props.image.url
|
||||
const separator = props.image.url.includes('?') ? '&' : '?'
|
||||
return `${props.image.url}${separator}retry=${retryCount.value}`
|
||||
})
|
||||
|
||||
watch(() => props.image.key, () => {
|
||||
imageError.value = false
|
||||
retryCount.value = 0
|
||||
})
|
||||
|
||||
let retryTimer: ReturnType<typeof setTimeout> | undefined
|
||||
|
||||
function clearRetryTimer() {
|
||||
if (retryTimer !== undefined) {
|
||||
clearTimeout(retryTimer)
|
||||
retryTimer = undefined
|
||||
}
|
||||
}
|
||||
|
||||
function onPreviewError() {
|
||||
if (retryCount.value >= MAX_PREVIEW_RETRIES) {
|
||||
imageError.value = true
|
||||
return
|
||||
}
|
||||
|
||||
const delay = PREVIEW_RETRY_DELAYS_MS[retryCount.value] ?? PREVIEW_RETRY_DELAYS_MS.at(-1)!
|
||||
clearRetryTimer()
|
||||
retryTimer = setTimeout(() => {
|
||||
retryCount.value += 1
|
||||
retryTimer = undefined
|
||||
}, delay)
|
||||
}
|
||||
|
||||
onUnmounted(clearRetryTimer)
|
||||
|
||||
const copyFormatItems = computed(() => [
|
||||
{ label: t('copy.url'), value: 'url' as const },
|
||||
{ label: t('copy.markdown'), value: 'markdown' as const },
|
||||
@@ -78,11 +118,12 @@ function toggleSelected(value: boolean | 'indeterminate') {
|
||||
<div class="relative aspect-square shrink-0 overflow-hidden bg-muted">
|
||||
<img
|
||||
v-if="!imageError"
|
||||
:src="image.url"
|
||||
:key="`${image.key}-${retryCount}`"
|
||||
:src="previewSrc"
|
||||
:alt="image.originalName"
|
||||
loading="lazy"
|
||||
class="absolute inset-0 size-full object-cover object-center"
|
||||
@error="imageError = true"
|
||||
@error="onPreviewError"
|
||||
>
|
||||
<div
|
||||
v-else
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { shouldBlockImageHostRequest, shouldBlockSiteHostImageRequest } from '../utils/host-isolation'
|
||||
|
||||
const HOST_ISOLATION_CACHE_CONTROL = 'private, no-cache, max-age=0'
|
||||
|
||||
export default defineEventHandler((event) => {
|
||||
if (shouldBlockImageHostRequest(event) || shouldBlockSiteHostImageRequest(event)) {
|
||||
setHeader(event, 'Cache-Control', HOST_ISOLATION_CACHE_CONTROL)
|
||||
throw createError({ statusCode: 404, statusMessage: 'Not Found' })
|
||||
}
|
||||
})
|
||||
@@ -20,7 +20,7 @@ import { getBackendRowForKey } from './storage/resolver'
|
||||
* - Referer 可被非浏览器客户端伪造,本方案防普通网站盗链,不是身份认证
|
||||
*/
|
||||
|
||||
const ERROR_CACHE_CONTROL = 'public, max-age=60'
|
||||
const ERROR_CACHE_CONTROL = 'private, no-cache, max-age=0'
|
||||
|
||||
function getAllowedHosts(event: H3Event): Set<string> {
|
||||
const hosts = new Set<string>()
|
||||
|
||||
+12
-3
@@ -53,13 +53,22 @@ export async function putImage(
|
||||
}
|
||||
|
||||
export async function headImage(key: string): Promise<StoredImage | null> {
|
||||
const indexed = getImageIndexAsStored(key)
|
||||
if (indexed) return indexed
|
||||
|
||||
const backend = await getBackendForKey(key)
|
||||
const stored = await backend.head(key)
|
||||
if (!stored) return null
|
||||
|
||||
const indexed = getImageIndexAsStored(key)
|
||||
if (indexed) {
|
||||
return {
|
||||
...stored,
|
||||
backendId: backend.id,
|
||||
originalName: indexed.originalName,
|
||||
contentType: indexed.contentType,
|
||||
uploadedAt: indexed.uploadedAt,
|
||||
userId: indexed.userId
|
||||
}
|
||||
}
|
||||
|
||||
insertImageIndex({
|
||||
key: stored.key,
|
||||
backendId: backend.id,
|
||||
|
||||
Reference in new issue
Block a user