diff --git a/hooks/use-file-upload.ts b/hooks/use-file-upload.ts
index c0302fd..f938484 100644
--- a/hooks/use-file-upload.ts
+++ b/hooks/use-file-upload.ts
@@ -146,7 +146,25 @@ export function useFileUpload({ bucketInfo, userId, api }: Props) {
});
if (!response.ok) {
- throw new Error("获取预签名 URL 失败");
+ // 尝试获取后端返回的具体错误信息
+ let errorMessage = "获取预签名 URL 失败";
+ try {
+ const errorText = await response.text();
+ if (errorText) {
+ // 如果返回的是JSON格式的错误信息,尝试解析
+ try {
+ const errorData = JSON.parse(errorText);
+ errorMessage = errorData.message || errorData.error || errorText;
+ } catch {
+ // 如果不是JSON,直接使用文本内容
+ errorMessage = errorText;
+ }
+ }
+ } catch {
+ // 如果无法读取响应内容,使用默认错误信息
+ errorMessage = `上传失败 (${response.status})`;
+ }
+ throw new Error(errorMessage);
}
const data = await response.json();
@@ -331,14 +349,15 @@ export function useFileUpload({ bucketInfo, userId, api }: Props) {
await Promise.allSettled(uploadPromises);
} catch (error) {
console.error("上传失败:", error);
- // 将所有 pending 状态的文件设置为错误状态
+ // 将所有 pending 状态的文件设置为错误状态,并显示具体错误信息
+ const errorMessage = error instanceof Error ? error.message : "上传失败";
setFiles((prev) =>
prev.map((file) =>
file.status === "pending"
? {
...file,
status: "error",
- error: "上传失败",
+ error: errorMessage,
}
: file,
),
diff --git a/lib/dto/files.ts b/lib/dto/files.ts
index df7b65c..df3fddd 100644
--- a/lib/dto/files.ts
+++ b/lib/dto/files.ts
@@ -159,18 +159,22 @@ export async function getUserFiles(options: QueryUserFileOptions = {}) {
prisma.userFile.count({ where }),
prisma.userFile.aggregate({
where: {
- // bucket,
- // providerName,
+ bucket,
+ providerName,
status: 1,
...(userId && { userId }),
},
_sum: { size: true },
+ _count: {
+ id: true,
+ },
}),
]);
return {
total,
totalSize: storageValueToBytes(totalSize._sum.size || 0),
+ totalFiles: totalSize._count.id || 0,
list: files,
};
} catch (error) {
@@ -276,7 +280,7 @@ export async function getUserFileStats(userId: string) {
success: true,
data: {
totalFiles,
- totalSize: totalSize._sum.size || 0,
+ totalSize: storageValueToBytes(totalSize._sum.size || 0),
filesByProvider,
},
};
@@ -359,3 +363,41 @@ export async function cleanupExpiredFiles(days: number = 30) {
return { success: false, error: "Failed to clean up expired files" };
}
}
+
+// 获取特定存储桶的使用量统计
+export async function getBucketStorageUsage(
+ bucket: string,
+ providerName: string,
+ userId?: string,
+): Promise<
+ | { success: true; data: { totalSize: number; totalFiles: number } }
+ | { success: false; error: string }
+> {
+ try {
+ const result = await prisma.userFile.aggregate({
+ where: {
+ ...(userId && { userId }),
+ bucket,
+ providerName,
+ status: 1,
+ },
+ _sum: {
+ size: true,
+ },
+ _count: {
+ id: true,
+ },
+ });
+
+ return {
+ success: true,
+ data: {
+ totalSize: storageValueToBytes(result._sum.size || 0),
+ totalFiles: result._count.id || 0,
+ },
+ };
+ } catch (error) {
+ console.error("Failed to get bucket storage usage:", error);
+ return { success: false, error: "Failed to get bucket storage usage" };
+ }
+}
diff --git a/lib/r2.ts b/lib/s3.ts
similarity index 94%
rename from lib/r2.ts
rename to lib/s3.ts
index 3aa3b98..adda389 100644
--- a/lib/r2.ts
+++ b/lib/s3.ts
@@ -32,8 +32,10 @@ export interface BucketItem {
bucket: string;
custom_domain?: string;
prefix?: string;
- file_types?: string;
- file_size?: string;
+ file_types?: string; // 允许上传的文件类型
+ file_size?: string; // 单个文件限制(字节)
+ max_files?: string; // 存储桶最大文件数量
+ max_storage?: string; // 存储桶最大存储容量(字节)
region?: string;
public: boolean;
}
diff --git a/lib/validations/record.ts b/lib/validations/record.ts
index 757f75d..e1e7f0c 100644
--- a/lib/validations/record.ts
+++ b/lib/validations/record.ts
@@ -12,8 +12,8 @@ export const createRecordSchema = z.object({
.string()
.regex(/^[a-zA-Z0-9-_]+$/, "Invalid characters")
.min(1)
- .max(32),
- content: z.string().min(1).max(32),
+ .max(64),
+ content: z.string().min(1).max(1024),
ttl: z.number().min(1).max(36000).default(1),
proxied: z.boolean().default(false),
comment: z.string().optional(),
diff --git a/locales/en.json b/locales/en.json
index 73ec486..6fecc0e 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -211,9 +211,17 @@
"usedSpace": "Used Space",
"totalCapacity": "Total Capacity",
"availableSpace": "Available Space",
+ "totalFiles": "Total Files",
+ "storageDataUnavailable": "Storage data is unavailable",
"storageFull": "Storage space is almost full",
"storageHigh": "Storage space usage is high",
"storageGood": "Storage space is sufficient",
+ "planQuota": "Plan Quota",
+ "planUsagePercentage": "Usage Percentage",
+ "bucketQuota": "Bucket Quota",
+ "bucketCapacity": "Bucket Capacity",
+ "bucketStorageFull": "Bucket storage is almost full",
+ "bucketStorageHigh": "Bucket storage usage is high",
"items": "items",
"Total": "Total",
"Configuration Error": "Configuration Error"
@@ -360,6 +368,7 @@
"Uploading": "Uploading",
"Completed": "Completed",
"Aborted": "Aborted",
+ "Failed": "Failed",
"Drop files to upload them to": "Drop files to upload them to",
"Drag and drop file(s) here": "Drag and drop file(s) here",
"or": "or",
@@ -623,6 +632,8 @@
"Region": "Region",
"Prefix": "Prefix",
"Optional": "Optional",
+ "Max Storage": "Max Storage",
+ "maxStorageTooltip": "Set maximum storage capacity for this bucket (in bytes). If not set, uses plan quota global limit.",
"Allowed File Types": "Allowed File Types",
"Public": "Public",
"Publicize this storage bucket, all registered users can upload files to this storage bucket; If not public, only administrators can upload files to this storage bucket": "Publicize this storage bucket, all registered users can upload files to this storage bucket; If not public, only administrators can upload files to this storage bucket",
@@ -632,6 +643,7 @@
"Unique": "Unique",
"Add Provider": "Add Provider",
"{length} Buckets": "{length} Buckets",
- "Save Modifications": "Save Modifications"
+ "Save Modifications": "Save Modifications",
+ "Max File Count": "Max File Count"
}
}
diff --git a/locales/zh.json b/locales/zh.json
index 237aa1e..9bd9c9d 100644
--- a/locales/zh.json
+++ b/locales/zh.json
@@ -211,9 +211,17 @@
"usedSpace": "已使用空间",
"totalCapacity": "总容量",
"availableSpace": "剩余空间",
+ "totalFiles": "总文件数",
+ "storageDataUnavailable": "无数据",
"storageFull": "存储空间即将用完",
"storageHigh": "存储空间使用较多",
"storageGood": "存储空间充足",
+ "planQuota": "计划配额",
+ "planUsagePercentage": "使用率",
+ "bucketQuota": "存储桶配额",
+ "bucketCapacity": "存储桶容量",
+ "bucketStorageFull": "存储桶空间即将用完",
+ "bucketStorageHigh": "存储桶空间使用较多",
"items": "条",
"Total": "共",
"Configuration Error": "配置错误"
@@ -360,6 +368,7 @@
"Uploading": "上传中",
"Completed": "已完成",
"Aborted": "已中止",
+ "Failed": "失败",
"Drop files to upload them to": "将文件上传到",
"Drag and drop file(s) here": "将文件拖到此处上传",
"or": "或",
@@ -620,9 +629,12 @@
"Bucket Name": "存储桶名称",
"Public Domain": "公开域名或自定义域名",
"Max File Size": "上传文件大小限制",
+ "Max File Count": "文件数量限制",
"Region": "存储桶区域",
"Prefix": "前缀",
"Optional": "可选",
+ "Max Storage": "最大存储容量",
+ "maxStorageTooltip": "设置此存储桶的最大存储容量(字节)。如果不设置,默认使用 Plan 配额的全局限制。",
"Allowed File Types": "允许的文件类型",
"Public": "公开",
"Publicize this storage bucket, all registered users can upload files to this storage bucket; If not public, only administrators can upload files to this storage bucket": "公开此存储桶,所有注册用户都可以上传文件到此存储桶; 若不公开,只有管理员可以上传文件到此存储桶",
diff --git a/prisma/migrations/20250714192022/migration.sql b/prisma/migrations/20250714192022/migration.sql
index a4e29db..a3ef8c5 100644
--- a/prisma/migrations/20250714192022/migration.sql
+++ b/prisma/migrations/20250714192022/migration.sql
@@ -9,7 +9,7 @@ INSERT INTO "system_configs"
VALUES
(
's3_config_list',
- '[{"enabled":true,"platform":"cloudflare","channel":"r2","provider_name":"Cloudflare R2","account_id":"","access_key_id":"","secret_access_key":"","endpoint":"https://
.r2.cloudflarestorage.com","buckets":[{"bucket":"","prefix":"","file_types":"","region":"auto","custom_domain":"","file_size":"26214400","public":true}]},{"enabled":false,"platform":"tencent","channel":"cos","provider_name":"腾讯云 COS","endpoint":"","account_id":"","access_key_id":"","secret_access_key":"","buckets":[{"custom_domain":"","prefix":"","bucket":"","file_types":"","file_size":"26214400","region":"","public":true}]}]',
+ '[{"enabled":true,"platform":"cloudflare","channel":"r2","provider_name":"Cloudflare R2","account_id":"","access_key_id":"","secret_access_key":"","endpoint":"https://.r2.cloudflarestorage.com","buckets":[{"bucket":"","prefix":"","file_types":"","region":"auto","custom_domain":"","file_size":"26214400","max_storage":"1073741824","max_files":"1000","public":true}]},{"enabled":false,"platform":"tencent","channel":"cos","provider_name":"腾讯云 COS","endpoint":"","account_id":"","access_key_id":"","secret_access_key":"","buckets":[{"custom_domain":"","prefix":"","bucket":"","file_types":"","file_size":"26214400","max_storage":"1073741824","max_files":"1000","region":"","public":true}]}]',
'OBJECT',
'R2 存储桶配置'
);
\ No newline at end of file
diff --git a/public/sw.js.map b/public/sw.js.map
index 5e7ac94..88022f8 100644
--- a/public/sw.js.map
+++ b/public/sw.js.map
@@ -1 +1 @@
-{"version":3,"file":"sw.js","sources":["../../../../../../private/var/folders/9b/3qmyp8zd2xvdspdrp149fyg00000gn/T/9ab3eac97b468a12fb965afe827f5191/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-routing@6.6.0/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-strategies@6.6.0/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-strategies@6.6.0/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-core@6.6.0/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n \n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({ request, response, event, state }) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, { status: 200, statusText: 'OK', headers: response.headers }) } return response } }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","workbox_strategies_NetworkOnly"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"sw.js","sources":["../../../../../../private/var/folders/9b/3qmyp8zd2xvdspdrp149fyg00000gn/T/3660dc403c2d3ab798169bbe82eb20c0/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-routing@6.6.0/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-strategies@6.6.0/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-strategies@6.6.0/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-core@6.6.0/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n \n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({ request, response, event, state }) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, { status: 200, statusText: 'OK', headers: response.headers }) } return response } }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","workbox_strategies_NetworkOnly"],"mappings":"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"}
\ No newline at end of file