WuYongJie f646b71813
Some checks failed
CI Check / check (push) Has been cancelled
chore: 初始化本地仓库并同步最近改动
2025-11-18 17:50:16 +08:00

158 lines
4.8 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 { prisma } from '@/lib/prisma'
import { readFile, access } from 'fs/promises'
import path from 'path'
export async function GET(req: NextRequest) {
try {
// Check if user is logged in
const session = await getServerSession(authOptions)
if (!session) {
return NextResponse.json(
{ error: '请先登录' },
{ status: 401 }
)
}
// Check if user has download permission (skip check for admins)
const userId = (session.user as any).id
const userRole = (session.user as any).role
if (userRole !== 'ADMIN') {
const user = await prisma.user.findUnique({
where: { id: userId },
select: { canDownload: true }
})
if (!user || !user.canDownload) {
return NextResponse.json(
{ error: '您的下载权限已被限制,请联系管理员' },
{ status: 403 }
)
}
}
const { searchParams } = new URL(req.url)
const id = searchParams.get('id')
if (!id) {
return NextResponse.json(
{ error: '缺少素材ID' },
{ status: 400 }
)
}
const material = await prisma.material.findUnique({
where: { id },
})
if (!material) {
return NextResponse.json(
{ error: '素材不存在' },
{ status: 404 }
)
}
// 增加下载次数
await prisma.material.update({
where: { id },
data: {
downloadCount: {
increment: 1
}
}
})
// Read file from local storage
// 确保使用绝对路径
let filePath = material.zipFilePath
if (!path.isAbsolute(filePath)) {
filePath = path.join(process.cwd(), filePath)
}
console.log('Attempting to download file:', filePath)
// 检查文件是否存在
try {
await access(filePath)
} catch (error) {
console.error('File not found:', filePath)
return NextResponse.json(
{ error: '文件不存在,可能已被删除' },
{ status: 404 }
)
}
const fileBuffer = await readFile(filePath)
// 生成新的文件名:SPU_一级分类_二级分类_用户名.扩展名
// 获取原文件扩展名
const originalExt = path.extname(material.zipFileName) // 例如 .zip 或 .7z 或视频扩展名
const newFileName = `${material.productModel}_${material.categoryName}_${material.sceneName}_${material.authorName}${originalExt}`
console.log('Download filename:', newFileName)
// 根据文件类型和素材类型确定Content-Type
let contentType = 'application/zip'
let contentDisposition = `attachment; filename="${encodeURIComponent(newFileName)}"`
// 如果是视频类型,使用视频的Content-Type并支持流式传输
if (material.materialType === '视频') {
const ext = originalExt.toLowerCase()
const videoTypes: { [key: string]: string } = {
'.mp4': 'video/mp4',
'.avi': 'video/x-msvideo',
'.mov': 'video/quicktime',
'.wmv': 'video/x-ms-wmv',
'.flv': 'video/x-flv',
'.mkv': 'video/x-matroska',
'.webm': 'video/webm',
}
contentType = videoTypes[ext] || 'video/mp4'
// 视频文件支持流式传输(Range请求)
const range = req.headers.get('range')
if (range) {
// 支持视频流式传输
const fileSize = fileBuffer.length
const parts = range.replace(/bytes=/, '').split('-')
const start = parseInt(parts[0], 10)
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1
const chunkSize = end - start + 1
const chunk = fileBuffer.slice(start, end + 1)
return new NextResponse(chunk as any, {
status: 206,
headers: {
'Content-Type': contentType,
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunkSize.toString(),
},
})
}
// 非Range请求,返回完整文件(但允许内联播放)
contentDisposition = `inline; filename="${encodeURIComponent(newFileName)}"`
}
return new NextResponse(fileBuffer as any, {
headers: {
'Content-Type': contentType,
'Content-Disposition': contentDisposition,
'Content-Length': fileBuffer.length.toString(),
'Accept-Ranges': 'bytes', // 支持Range请求(用于视频流式传输)
},
})
} catch (error) {
console.error('Download error:', error)
return NextResponse.json(
{ error: '下载失败: ' + (error instanceof Error ? error.message : '未知错误') },
{ status: 500 }
)
}
}