import { NextRequest, NextResponse } from 'next/server' import { Readable } from 'stream' import { getFileStreamByObjectName } from '@/lib/fileStorage' import { prisma } from '@/lib/prisma' import { withCors } from '@/lib/middleware' export const runtime = 'nodejs' interface ScreenshotFileMetadata { objectName: string filename: string contentType: string fileSize: number } const MAX_SCREENSHOT_METADATA_CACHE_SIZE = 5000 const screenshotMetadataCache = new Map() function cacheScreenshotMetadata(fileId: string, metadata: ScreenshotFileMetadata) { if (screenshotMetadataCache.size >= MAX_SCREENSHOT_METADATA_CACHE_SIZE) { const oldestFileId = screenshotMetadataCache.keys().next().value if (oldestFileId) { screenshotMetadataCache.delete(oldestFileId) } } screenshotMetadataCache.set(fileId, metadata) } async function findScreenshotMetadata(fileId: string): Promise { const cached = screenshotMetadataCache.get(fileId) if (cached) { return cached } const screenshot = await prisma.screenshot.findFirst({ where: { fileId }, select: { objectName: true, filename: true, contentType: true, fileSize: true } }) if (screenshot) { cacheScreenshotMetadata(fileId, screenshot) } return screenshot } function createScreenshotHeaders(fileId: string, screenshot: ScreenshotFileMetadata) { const headers = new Headers({ 'Content-Type': resolveScreenshotContentType(screenshot), 'Cache-Control': 'public, max-age=31536000, immutable', 'ETag': `"${fileId}"`, 'X-Content-Type-Options': 'nosniff' }) if (screenshot.fileSize > 0) { headers.set('Content-Length', String(screenshot.fileSize)) } return headers } function resolveScreenshotContentType(screenshot: ScreenshotFileMetadata) { const storedContentType = screenshot.contentType?.trim() const filePath = `${screenshot.filename} ${screenshot.objectName}`.toLowerCase() if (storedContentType && storedContentType !== 'image/webp') { return storedContentType } if (filePath.includes('.avif')) return 'image/avif' if (filePath.includes('.webp')) return 'image/webp' if (filePath.includes('.png')) return 'image/png' if (filePath.includes('.jpg') || filePath.includes('.jpeg')) return 'image/jpeg' return storedContentType || 'image/webp' } async function handleScreenshotFile(req: NextRequest) { try { const pathSegments = req.nextUrl.pathname.split('/') const fileIdIndex = pathSegments.indexOf('screenshots') + 1 const fileId = pathSegments[fileIdIndex] if (!fileId) { return NextResponse.json({ error: '缺少文件ID' }, { status: 400 }) } const screenshot = await findScreenshotMetadata(fileId) if (!screenshot) { return NextResponse.json({ error: '截图不存在' }, { status: 404 }) } const headers = createScreenshotHeaders(fileId, screenshot) if (req.headers.get('if-none-match') === headers.get('ETag')) { headers.delete('Content-Length') return new NextResponse(null, { status: 304, headers }) } const file = await getFileStreamByObjectName(screenshot.objectName, { contentType: resolveScreenshotContentType(screenshot), filename: screenshot.filename, size: screenshot.fileSize }) if (!file) { return NextResponse.json({ error: '文件不存在' }, { status: 404 }) } return new NextResponse(Readable.toWeb(file.stream) as unknown as BodyInit, { headers }) } catch (error) { console.error('获取截图失败:', error) return NextResponse.json({ error: '获取截图失败' }, { status: 500 }) } } export const GET = withCors(handleScreenshotFile)