/** * 图片代理接口 * 用于加载有防盗链保护的外部图片(如小红书 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: '图片代理请求失败' }) } })