import "server-only"; import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; import { put } from "@vercel/blob"; import { getDefaultStorageConfig } from "@/lib/db/queries"; // 获取文件的 Content-Type function getContentType(filename: string): string { const ext = filename.toLowerCase().split(".").pop(); const types: Record = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", gif: "image/gif", webp: "image/webp", svg: "image/svg+xml", pdf: "application/pdf", json: "application/json", txt: "text/plain", html: "text/html", css: "text/css", js: "application/javascript", }; return types[ext ?? ""] ?? "application/octet-stream"; } // S3/MinIO 上传函数(使用 AWS SDK) async function uploadToS3( filename: string, buffer: ArrayBuffer, config: { endpoint: string; region: string; bucket: string; accessKeyId: string; secretAccessKey: string; publicUrl?: string; } ) { const { endpoint, region, bucket, accessKeyId, secretAccessKey, publicUrl } = config; // 生成唯一的文件名 const timestamp = Date.now(); const randomString = Math.random().toString(36).slice(2, 8); const ext = filename.split(".").pop(); const key = `uploads/${timestamp}-${randomString}.${ext}`; const contentType = getContentType(filename); try { // 创建 S3 客户端(支持 MinIO) const s3Client = new S3Client({ endpoint: endpoint, region: region || "auto", credentials: { accessKeyId: accessKeyId, secretAccessKey: secretAccessKey, }, // 关键:MinIO 需要使用 path-style URL forcePathStyle: true, }); console.log(`[S3 Upload] Uploading to: ${endpoint}/${bucket}/${key}`); // 上传文件 const command = new PutObjectCommand({ Bucket: bucket, Key: key, Body: Buffer.from(buffer), ContentType: contentType, // 设置为公开可读(如果您的 MinIO 桶支持) ACL: "public-read", }); await s3Client.send(command); console.log(`[S3 Upload] Upload successful: ${key}`); // 返回文件的公开 URL // MinIO 的公开 URL 格式通常是: http://endpoint:port/bucket/key let fileUrl: string; if (publicUrl) { // 如果用户配置了自定义公开 URL,使用它 fileUrl = `${publicUrl}/${key}`; } else { // 否则使用 endpoint + bucket + key const cleanEndpoint = endpoint.replace(/\/$/, ""); // 移除末尾的斜杠 fileUrl = `${cleanEndpoint}/${bucket}/${key}`; } console.log(`[S3 Upload] File URL: ${fileUrl}`); return { url: fileUrl, pathname: key, contentType, }; } catch (error) { console.error("[S3 Upload] Error:", error); throw error; } } // 上传文件到存储服务 export async function uploadFile( filename: string, buffer: ArrayBuffer ): Promise<{ url: string; pathname: string; contentType: string }> { console.log(`[Upload] Starting upload for file: ${filename}`); try { // 尝试从数据库获取存储配置 const storageConfig = await getDefaultStorageConfig(); if ( storageConfig && storageConfig.type === "s3" && storageConfig.endpoint && storageConfig.bucket && storageConfig.accessKeyId && storageConfig.secretAccessKey && storageConfig.isEnabled ) { console.log(`[Upload] Using database S3 config: ${storageConfig.name}`); console.log(`[Upload] Endpoint: ${storageConfig.endpoint}`); console.log(`[Upload] Bucket: ${storageConfig.bucket}`); // 使用数据库配置的 S3/MinIO 存储 return await uploadToS3(filename, buffer, { endpoint: storageConfig.endpoint, region: storageConfig.region ?? "auto", bucket: storageConfig.bucket, accessKeyId: storageConfig.accessKeyId, secretAccessKey: storageConfig.secretAccessKey, publicUrl: storageConfig.publicUrl ?? undefined, }); } else { console.warn("[Upload] No valid S3 config in database"); } } catch (error) { console.error("[Upload] Failed to get storage config from database:", error); } // 回退到环境变量配置的 S3 if ( process.env.S3_ENDPOINT && process.env.S3_BUCKET && process.env.S3_ACCESS_KEY_ID && process.env.S3_SECRET_ACCESS_KEY ) { console.log("[Upload] Using environment variable S3 config"); return await uploadToS3(filename, buffer, { endpoint: process.env.S3_ENDPOINT, region: process.env.S3_REGION ?? "auto", bucket: process.env.S3_BUCKET, accessKeyId: process.env.S3_ACCESS_KEY_ID, secretAccessKey: process.env.S3_SECRET_ACCESS_KEY, publicUrl: process.env.S3_PUBLIC_URL, }); } // 最终回退到 Vercel Blob console.log("[Upload] Falling back to Vercel Blob"); const data = await put(filename, buffer, { access: "public", }); return { url: data.url, pathname: data.pathname, contentType: getContentType(filename), }; } // 获取当前使用的存储类型 export async function getStorageType(): Promise< "database-s3" | "env-s3" | "vercel-blob" > { try { const storageConfig = await getDefaultStorageConfig(); if ( storageConfig && storageConfig.type === "s3" && storageConfig.endpoint && storageConfig.bucket ) { return "database-s3"; } } catch (error) { // ignore } if (process.env.S3_ENDPOINT && process.env.S3_BUCKET) { return "env-s3"; } return "vercel-blob"; }