129 lines
3.1 KiB
TypeScript
129 lines
3.1 KiB
TypeScript
import sharp from 'sharp'
|
|
import { NextRequest, NextResponse } from 'next/server'
|
|
import { getServerSession } from 'next-auth'
|
|
import { authOptions } from '@/lib/auth'
|
|
|
|
const ALLOWED_IMAGE_HOST_SUFFIXES = [
|
|
'alicdn.com',
|
|
'taobaocdn.com',
|
|
'tbcdn.cn',
|
|
]
|
|
|
|
function isAllowedImageHost(hostname: string): boolean {
|
|
return ALLOWED_IMAGE_HOST_SUFFIXES.some(
|
|
(suffix) => hostname === suffix || hostname.endsWith(`.${suffix}`),
|
|
)
|
|
}
|
|
|
|
export async function GET(request: NextRequest) {
|
|
try {
|
|
const session = await getServerSession(authOptions)
|
|
if (!session) {
|
|
return NextResponse.json(
|
|
{
|
|
success: false,
|
|
error: {
|
|
code: 'UNAUTHORIZED',
|
|
message: '请先登录后再导出图片',
|
|
},
|
|
},
|
|
{ status: 401 },
|
|
)
|
|
}
|
|
|
|
const rawUrl = request.nextUrl.searchParams.get('url')
|
|
if (!rawUrl) {
|
|
return NextResponse.json(
|
|
{
|
|
success: false,
|
|
error: {
|
|
code: 'MISSING_URL',
|
|
message: '缺少图片地址',
|
|
},
|
|
},
|
|
{ status: 400 },
|
|
)
|
|
}
|
|
|
|
let targetUrl: URL
|
|
try {
|
|
targetUrl = new URL(rawUrl)
|
|
} catch {
|
|
return NextResponse.json(
|
|
{
|
|
success: false,
|
|
error: {
|
|
code: 'INVALID_URL',
|
|
message: '图片地址不正确',
|
|
},
|
|
},
|
|
{ status: 400 },
|
|
)
|
|
}
|
|
|
|
if (!['http:', 'https:'].includes(targetUrl.protocol) || !isAllowedImageHost(targetUrl.hostname)) {
|
|
return NextResponse.json(
|
|
{
|
|
success: false,
|
|
error: {
|
|
code: 'IMAGE_HOST_NOT_ALLOWED',
|
|
message: '图片来源不允许导出',
|
|
},
|
|
},
|
|
{ status: 400 },
|
|
)
|
|
}
|
|
|
|
const upstream = await fetch(targetUrl.toString(), {
|
|
method: 'GET',
|
|
cache: 'no-store',
|
|
headers: {
|
|
'User-Agent': 'Mozilla/5.0 Codex Taobao Image Proxy',
|
|
Referer: 'https://item.taobao.com/',
|
|
},
|
|
})
|
|
|
|
if (!upstream.ok) {
|
|
return NextResponse.json(
|
|
{
|
|
success: false,
|
|
error: {
|
|
code: 'UPSTREAM_IMAGE_FETCH_FAILED',
|
|
message: `图片下载失败,上游返回 ${upstream.status}`,
|
|
},
|
|
},
|
|
{ status: 502 },
|
|
)
|
|
}
|
|
|
|
const buffer = Buffer.from(await upstream.arrayBuffer())
|
|
const pngBuffer = await sharp(buffer, { failOn: 'none' })
|
|
.flatten({ background: '#ffffff' })
|
|
.png()
|
|
.toBuffer()
|
|
const metadata = await sharp(pngBuffer).metadata()
|
|
|
|
return new NextResponse(Uint8Array.from(pngBuffer), {
|
|
status: 200,
|
|
headers: {
|
|
'Content-Type': 'image/png',
|
|
'Cache-Control': 'private, max-age=300',
|
|
'X-Image-Width': String(metadata.width || 0),
|
|
'X-Image-Height': String(metadata.height || 0),
|
|
},
|
|
})
|
|
} catch (error) {
|
|
console.error('[api/taobao-detail-plus/image-proxy] GET error:', error)
|
|
return NextResponse.json(
|
|
{
|
|
success: false,
|
|
error: {
|
|
code: 'IMAGE_PROXY_ERROR',
|
|
message: '图片处理失败,请稍后再试',
|
|
},
|
|
},
|
|
{ status: 500 },
|
|
)
|
|
}
|
|
}
|