import { NextRequest, NextResponse } from 'next/server' import { getServerSession } from 'next-auth' import { authOptions } from '@/lib/auth' import { prisma } from '@/lib/prisma' import { uploadToS3 } from '@/lib/s3' import { processImage, getProcessedFileName } from '@/lib/image-processor' import { writeFile, mkdir } from 'fs/promises' import path from 'path' // Configure route to handle large files export const runtime = 'nodejs' export const dynamic = 'force-dynamic' // Increase timeout for large file uploads export const maxDuration = 10800 // 3 hours timeout (10800 seconds) export async function POST(req: NextRequest) { try { console.log('[Upload] Starting upload process...') const session = await getServerSession(authOptions) console.log('[Upload] Session checked:', !!session) if (!session) { return NextResponse.json( { error: '请先登录' }, { status: 401 } ) } console.log('[Upload] Parsing form data...') const formData = await req.formData() const previewImage = formData.get('previewImage') as File const zipFile = formData.get('zipFile') as File const productModel = formData.get('productModel') as string const categoryName = formData.get('categoryName') as string const sceneName = formData.get('sceneName') as string const renderer = (formData.get('renderer') as string) || 'CR' console.log('[Upload] Form data received:', { hasPreviewImage: !!previewImage, previewImageSize: previewImage?.size, hasZipFile: !!zipFile, zipFileSize: zipFile?.size, productModel, categoryName, sceneName }) // Validation if (!previewImage || !zipFile || !productModel || !categoryName || !sceneName) { console.log('[Upload] Validation failed - missing fields') return NextResponse.json( { error: '所有字段都是必填的' }, { status: 400 } ) } // 获取用户信息作为作者花名 console.log('[Upload] Fetching user info...') const user = await prisma.user.findUnique({ where: { id: (session.user as any).id }, select: { username: true }, }) if (!user) { console.log('[Upload] User not found') return NextResponse.json( { error: '用户不存在' }, { status: 404 } ) } const authorName = user.username console.log('[Upload] User found:', authorName) // 验证预览图大小(限制50MB) const maxPreviewSize = 50 * 1024 * 1024 // 50MB if (previewImage.size > maxPreviewSize) { console.log(`[Upload] Preview image too large: ${(previewImage.size / 1024 / 1024).toFixed(2)}MB`) return NextResponse.json( { error: `预览图过大,请使用小于50MB的图片(当前:${(previewImage.size / 1024 / 1024).toFixed(2)}MB)` }, { status: 400 } ) } // 验证预览图格式 const allowedImageTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif'] if (!allowedImageTypes.includes(previewImage.type)) { console.log(`[Upload] Invalid image type: ${previewImage.type}`) return NextResponse.json( { error: `不支持的图片格式:${previewImage.type},请使用 JPG、PNG、WebP 或 GIF 格式` }, { status: 400 } ) } // 处理预览图:压缩到300KB以内,转换为webP,缩放到600px console.log(`[Upload] Processing image... (${previewImage.type}, ${(previewImage.size / 1024).toFixed(2)}KB)`) let processedBuffer: Buffer try { const originalBuffer = Buffer.from(await previewImage.arrayBuffer()) processedBuffer = await processImage(originalBuffer, { maxSizeKB: 300, maxWidth: 600, format: 'webp', }) console.log('[Upload] Image processed successfully') } catch (imageError) { console.error('[Upload] Image processing error:', imageError) return NextResponse.json( { error: `图片处理失败,请确保图片文件未损坏且格式正确。错误:${imageError instanceof Error ? imageError.message : '未知错误'}` }, { status: 500 } ) } // Upload processed preview image to S3 console.log('[Upload] Uploading image to S3...') const processedFileName = getProcessedFileName(previewImage.name) const previewFileName = `preview-${Date.now()}-${processedFileName}` const previewUrl = await uploadToS3( processedBuffer, previewFileName, 'image/webp' ) console.log('[Upload] Image uploaded to S3:', previewUrl) // Save zip file locally console.log('[Upload] Saving zip file locally...') const uploadDir = process.env.UPLOAD_DIR || './uploads' await mkdir(uploadDir, { recursive: true }) const zipFileName = `zip-${Date.now()}-${zipFile.name}` const zipFilePath = path.join(uploadDir, zipFileName) const zipBuffer = Buffer.from(await zipFile.arrayBuffer()) await writeFile(zipFilePath, zipBuffer) const zipFileSize = BigInt(zipBuffer.length) // 记录文件大小(字节) console.log('[Upload] Zip file saved:', zipFilePath, `(${(Number(zipFileSize) / 1024 / 1024).toFixed(2)} MB)`) // Create material record console.log('[Upload] Creating database record...') const material = await prisma.material.create({ data: { previewUrl, productModel, categoryName, sceneName, renderer, authorName, zipFileName, zipFilePath, zipFileSize, userId: (session.user as any).id, }, include: { user: { select: { username: true, }, }, }, }) console.log('[Upload] Database record created:', material.id) // 转换BigInt为字符串以支持JSON序列化 const materialData = { ...material, zipFileSize: material.zipFileSize.toString() } console.log('[Upload] Upload completed successfully!') return NextResponse.json( { message: '上传成功', material: materialData }, { status: 201 } ) } catch (error) { console.error('Upload error:', error) // Log detailed error information if (error instanceof Error) { console.error('Error name:', error.name) console.error('Error message:', error.message) console.error('Error stack:', error.stack) } // Return more specific error message for debugging const errorMessage = error instanceof Error ? error.message : '上传失败,请稍后再试' return NextResponse.json( { error: '上传失败,请稍后再试', details: process.env.NODE_ENV === 'development' ? errorMessage : undefined }, { status: 500 } ) } }