Files
wr.do/app/api/storage/s3/upload/route.ts

183 lines
5.5 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 { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { getBucketStorageUsage } from "@/lib/dto/files";
import { getPlanQuota } from "@/lib/dto/plan";
import { getMultipleConfigs } from "@/lib/dto/system-config";
import { checkUserStatus } from "@/lib/dto/user";
import { createS3Client } from "@/lib/r2";
import { getCurrentUser } from "@/lib/session";
import { restrictByTimeRange } from "@/lib/team";
import { generateFileKey } from "@/lib/utils";
export async function POST(request: NextRequest) {
try {
const user = checkUserStatus(await getCurrentUser());
if (user instanceof Response) return user;
const { provider, bucket, files, prefix } = await request.json();
if (!bucket || !files || !Array.isArray(files)) {
return NextResponse.json("Invalid request parameters", { status: 400 });
}
const configs = await getMultipleConfigs(["s3_config_list"]);
if (!configs || !configs.s3_config_list) {
return NextResponse.json("Invalid S3 configs", {
status: 400,
});
}
const providerChannel = configs.s3_config_list.find(
(c) => c.provider_name === provider,
);
if (!providerChannel) {
return NextResponse.json("Provider does not exist", {
status: 400,
});
}
const buckets = providerChannel.buckets || [];
if (!buckets.find((b) => b.bucket === bucket)) {
return NextResponse.json("Bucket does not exist", {
status: 400,
});
}
const plan = await getPlanQuota(user.team!);
for (const file of files) {
if (Number(file.size) > Number(plan.stMaxFileSize)) {
return Response.json(`File (${file.name}) size limit exceeded`, {
status: 400,
});
}
}
// const limit = await restrictByTimeRange({
// model: "userFile",
// userId: user.id,
// limit: Number(plan.stMaxFileCount),
// rangeType: "month",
// });
// if (limit) return Response.json(limit.statusText, { status: limit.status });
// 检查存储桶容量限制
const bucketConfig = buckets.find((b) => b.bucket === bucket);
const totalUploadSize = files.reduce((sum, file) => sum + Number(file.size), 0);
if (bucketConfig?.max_storage) {
const bucketUsage = await getBucketStorageUsage(bucket, provider);
if (bucketUsage.success && bucketUsage.data) {
const currentUsage = bucketUsage.data.totalSize;
const maxStorage = Number(bucketConfig.max_storage);
if (currentUsage + totalUploadSize > maxStorage) {
const remainingSpace = maxStorage - currentUsage;
const remainingSpaceGB = (remainingSpace / (1024 * 1024 * 1024)).toFixed(2);
return Response.json(
`存储桶容量不足!剩余 ${remainingSpaceGB}GB请更换存储桶`,
{ status: 403 }
);
}
}
}
const R2 = createS3Client(
providerChannel.endpoint,
providerChannel.access_key_id,
providerChannel.secret_access_key,
);
const signedUrls = await Promise.all(
files.map(async (file: { name: string; type: string; size: number }) => {
const fileName = generateFileKey(file.name, prefix || "");
const signedUrl = await getSignedUrl(
R2,
new PutObjectCommand({
Bucket: bucket,
Key: fileName,
ContentType: file.type,
ContentLength: file.size,
}),
{ expiresIn: 600 }, // 10分钟过期时间
);
return {
originalName: file.name,
fileName,
url: signedUrl,
type: file.type,
size: file.size,
};
}),
);
return NextResponse.json({ urls: signedUrls });
} catch (error) {
console.error("生成预签名 URL 失败:", error);
return NextResponse.json({ error: "Server Error" }, { status: 500 });
}
}
// Get download url
export async function GET(request: NextRequest) {
try {
const user = checkUserStatus(await getCurrentUser());
if (user instanceof Response) return user;
const url = new URL(request.url);
const path = url.searchParams.get("path");
const bucket = url.searchParams.get("bucket");
if (!path || !bucket) {
return NextResponse.json("Invalid request parameters", {
status: 400,
});
}
const configs = await getMultipleConfigs(["s3_config_01"]);
if (!configs.s3_config_01.enabled) {
return NextResponse.json("S3 is not enabled", {
status: 403,
});
}
if (
!configs.s3_config_01 ||
!configs.s3_config_01.access_key_id ||
!configs.s3_config_01.secret_access_key ||
!configs.s3_config_01.endpoint
) {
return NextResponse.json("Invalid S3 config", {
status: 403,
});
}
const buckets = configs.s3_config_01.buckets || [];
if (!buckets.find((b) => b.bucket === bucket)) {
return NextResponse.json("Bucket does not exist", {
status: 403,
});
}
const R2 = createS3Client(
configs.s3_config_01.endpoint,
configs.s3_config_01.access_key_id,
configs.s3_config_01.secret_access_key,
);
const pre_url = await getSignedUrl(
R2,
new GetObjectCommand({
Bucket: bucket,
Key: path,
}),
{
expiresIn: 600,
},
);
return Response.json({ url: pre_url });
} catch (error: any) {
return Response.json({ error: error.message });
}
}