84 lines
2.6 KiB
TypeScript
84 lines
2.6 KiB
TypeScript
/**
|
|
* 纹理贴图上传 API
|
|
* POST /api/admin/materials/upload-texture - 上传纹理图片
|
|
*/
|
|
|
|
import { NextRequest } from 'next/server'
|
|
import { writeFile, mkdir } from 'fs/promises'
|
|
import { existsSync } from 'fs'
|
|
import path from 'path'
|
|
import { handleRequest, jsonSuccess, jsonError } from '@/server/core/response'
|
|
import { ErrorCode } from '@/server/core/errors'
|
|
|
|
// 允许的图片类型
|
|
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp']
|
|
const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10MB
|
|
|
|
// 允许的管理角色
|
|
const ADMIN_ROLES = ['SUPER_ADMIN', 'ADMIN', 'SUPERVISOR'] as const
|
|
|
|
export async function POST(request: NextRequest) {
|
|
return handleRequest(request, async ({ requestId }) => {
|
|
const formData = await request.formData()
|
|
const file = formData.get('file') as File | null
|
|
const type = formData.get('type') as string | null // 'texture' | 'normal' | 'thumbnail'
|
|
const categoryName = formData.get('category') as string | null
|
|
|
|
if (!file) {
|
|
return jsonError(ErrorCode.VALIDATION_REQUIRED_FIELD, '请选择要上传的文件', 400, requestId)
|
|
}
|
|
|
|
// 验证文件类型
|
|
if (!ALLOWED_TYPES.includes(file.type)) {
|
|
return jsonError(ErrorCode.FILE_TYPE_NOT_ALLOWED, '仅支持 JPG、PNG、WebP 格式', 400, requestId)
|
|
}
|
|
|
|
// 验证文件大小
|
|
if (file.size > MAX_FILE_SIZE) {
|
|
return jsonError(ErrorCode.FILE_TOO_LARGE, '文件大小不能超过 10MB', 400, requestId)
|
|
}
|
|
|
|
// 确定保存目录
|
|
const uploadType = type || 'texture'
|
|
const category = categoryName?.replace(/[^a-zA-Z0-9\u4e00-\u9fa5_-]/g, '') || 'default'
|
|
|
|
const uploadDir = path.join(
|
|
process.cwd(),
|
|
'public',
|
|
'materials',
|
|
uploadType === 'thumbnail' ? 'thumbnails' : 'textures',
|
|
category
|
|
)
|
|
|
|
// 确保目录存在
|
|
if (!existsSync(uploadDir)) {
|
|
await mkdir(uploadDir, { recursive: true })
|
|
}
|
|
|
|
// 生成文件名
|
|
const ext = path.extname(file.name) || '.jpg'
|
|
const baseName = path.basename(file.name, ext)
|
|
.replace(/[^a-zA-Z0-9\u4e00-\u9fa5_-]/g, '')
|
|
.slice(0, 50)
|
|
const timestamp = Date.now()
|
|
const fileName = `${baseName}_${timestamp}${ext}`
|
|
const filePath = path.join(uploadDir, fileName)
|
|
|
|
// 保存文件
|
|
const bytes = await file.arrayBuffer()
|
|
await writeFile(filePath, Buffer.from(bytes))
|
|
|
|
// 返回 URL
|
|
const publicUrl = `/materials/${
|
|
uploadType === 'thumbnail' ? 'thumbnails' : 'textures'
|
|
}/${category}/${fileName}`
|
|
|
|
return jsonSuccess({
|
|
url: publicUrl,
|
|
fileName,
|
|
type: uploadType,
|
|
size: file.size,
|
|
}, 201, requestId)
|
|
}, { allowedRoles: [...ADMIN_ROLES] })
|
|
}
|