1. 首页大面积空白:useScrollReveal 添加 MutationObserver, 确保异步子组件的 .reveal 元素也能触发入场动画 2. 小红书/游记封面图不显示: - 小红书 CDN 签名链接过期+防盗链,全部下载到本地 - 新增 download-covers.mjs 脚本批量下载封面 - 新增 useImageProxy composable 转换外部 URL - 页面 img 标签添加 referrerpolicy="no-referrer" - 管理端保存时自动下载外部封面图到本地 - 种子数据更新为本地路径 3. 定制页微信号文字替换为二维码图片 4. prerender 路由添加 /xiaohongshu 和 /youji Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
60 行
1.7 KiB
JavaScript
60 行
1.7 KiB
JavaScript
/**
|
||
* 图片代理接口
|
||
* 用于加载有防盗链保护的外部图片(如小红书 CDN)
|
||
*
|
||
* 用法: /api/image-proxy?url=https://sns-webpic-qc.xhscdn.com/...
|
||
*
|
||
* 安全限制:只允许代理白名单域名的图片
|
||
*/
|
||
export default defineEventHandler(async (event) => {
|
||
const { url } = getQuery(event)
|
||
|
||
if (!url) {
|
||
throw createError({ statusCode: 400, message: '缺少 url 参数' })
|
||
}
|
||
|
||
// 白名单:只允许代理指定域名的图片
|
||
const allowedHosts = [
|
||
'sns-webpic-qc.xhscdn.com',
|
||
'ci.xiaohongshu.com',
|
||
'image.xiaohongshu.com',
|
||
'picx.zhimg.com',
|
||
]
|
||
|
||
let parsedUrl
|
||
try {
|
||
parsedUrl = new URL(url)
|
||
} catch {
|
||
throw createError({ statusCode: 400, message: '无效的 URL' })
|
||
}
|
||
|
||
if (!allowedHosts.includes(parsedUrl.hostname)) {
|
||
throw createError({ statusCode: 403, message: '该域名不在白名单中' })
|
||
}
|
||
|
||
try {
|
||
const response = await fetch(url, {
|
||
headers: {
|
||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||
'Accept': 'image/webp,image/apng,image/*,*/*;q=0.8',
|
||
},
|
||
})
|
||
|
||
if (!response.ok) {
|
||
throw createError({ statusCode: response.status, message: '图片获取失败' })
|
||
}
|
||
|
||
const contentType = response.headers.get('content-type') || 'image/jpeg'
|
||
const buffer = await response.arrayBuffer()
|
||
|
||
setHeader(event, 'Content-Type', contentType)
|
||
setHeader(event, 'Cache-Control', 'public, max-age=86400, s-maxage=604800')
|
||
setHeader(event, 'Access-Control-Allow-Origin', '*')
|
||
|
||
return Buffer.from(buffer)
|
||
} catch (err) {
|
||
if (err.statusCode) throw err
|
||
throw createError({ statusCode: 502, message: '图片代理请求失败' })
|
||
}
|
||
})
|