87 lines
2.8 KiB
TypeScript
87 lines
2.8 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
||
import { getAuthenticatedUploadUserId } from '@/lib/upload-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 userId = await getAuthenticatedUploadUserId(req)
|
||
if (!userId) {
|
||
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,
|
||
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,
|
||
})
|
||
|
||
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 }
|
||
)
|
||
}
|
||
}
|