chenpei 4712175cb0 feat(素材链路): 发布白底图双轨与AI生图回传能力
- 完成白底图 WebP 双轨接口、上传链路与预览缓存改造
- 新增 AI 生图平台成品回传接口、任务状态与表单规则
- 补齐 AI 生成标签、任务详情补全、相关测试与对接文档
2026-03-25 10:28:02 +08:00

88 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { NextRequest, NextResponse } from 'next/server'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { saveUploadSession } from '@/lib/upload-sessions'
import { randomBytes } from 'crypto'
import { mkdir, writeFile } from 'fs/promises'
import path from 'path'
export async function POST(req: NextRequest) {
try {
const session = await getServerSession(authOptions)
if (!session) {
return NextResponse.json({ error: '请先登录' }, { status: 401 })
}
let body
try {
body = await req.json()
} catch (jsonError) {
console.error('[UploadInit] JSON解析错误:', jsonError)
return NextResponse.json(
{ error: '请求体格式错误,需要JSON格式' },
{ status: 400 }
)
}
const normalizedFileName = typeof body?.fileName === 'string' ? body.fileName.trim() : ''
const normalizedFileSize = Number(body?.fileSize)
const normalizedTotalChunks = Number(body?.totalChunks)
if (!normalizedFileName) {
return NextResponse.json({ error: '文件名不能为空' }, { status: 400 })
}
if (!Number.isFinite(normalizedFileSize) || normalizedFileSize <= 0) {
return NextResponse.json({ error: '上传文件不能为空' }, { status: 400 })
}
if (!Number.isInteger(normalizedTotalChunks) || normalizedTotalChunks <= 0) {
return NextResponse.json({ error: '上传分片参数无效' }, { status: 400 })
}
// 生成上传ID
const uploadId = `upload-${Date.now()}-${randomBytes(16).toString('hex')}`
// 创建临时目录
const uploadDir = process.env.UPLOAD_DIR || '/app/uploads'
const tempDir = path.join(uploadDir, 'temp', uploadId)
await mkdir(tempDir, { recursive: true })
// 写入元数据,方便在Redis丢失时回溯
const metaPath = path.join(tempDir, 'meta.json')
const metaPayload = {
fileName: normalizedFileName,
fileSize: normalizedFileSize,
totalChunks: normalizedTotalChunks,
userId: (session.user as any).id,
createdAt: Date.now(),
}
await writeFile(metaPath, JSON.stringify(metaPayload), 'utf-8')
// 保存会话(使用统一的接口,自动选择Redis或内存Map)
const now = Date.now()
await saveUploadSession(uploadId, {
fileName: normalizedFileName,
fileSize: normalizedFileSize,
totalChunks: normalizedTotalChunks,
chunks: new Map(),
createdAt: now,
lastActivityAt: now,
uploadedBytes: 0,
currentSpeed: 0,
_speedSampleAt: now,
_speedSampleBytes: 0,
userId: (session.user as any).id,
})
console.log(`[UploadInit] Created upload session: ${uploadId}, chunks: ${normalizedTotalChunks}`)
return NextResponse.json({ uploadId })
} catch (error) {
console.error('[UploadInit] Error:', error)
return NextResponse.json(
{ error: '初始化上传失败' },
{ status: 500 }
)
}
}