From cdfd983f42ce9b9dd98242e7d065a916897f7335 Mon Sep 17 00:00:00 2001 From: Zhang Chao Date: Tue, 27 Jan 2026 04:20:24 +0000 Subject: [PATCH] =?UTF-8?q?fix(s3/storage):=20=E6=81=A2=E5=A4=8D=E5=9B=A0?= =?UTF-8?q?=E5=90=88=E5=B9=B6=E5=BC=82=E5=B8=B8=E4=B8=A2=E5=A4=B1=E7=9A=84?= =?UTF-8?q?S3=20CDN=E4=BB=A3=E7=A0=81=EF=BC=8C=E5=AF=BC=E8=87=B4=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=A4=B1=E6=95=88=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- functions/api/manage/sysConfig/upload.js | 2 + functions/file/[[path]].js | 66 ++++++++++++++++++++++++ functions/upload/index.js | 7 ++- 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/functions/api/manage/sysConfig/upload.js b/functions/api/manage/sysConfig/upload.js index 6af5fd0..e26814e 100644 --- a/functions/api/manage/sysConfig/upload.js +++ b/functions/api/manage/sysConfig/upload.js @@ -146,6 +146,7 @@ export async function getUploadConfig(db, env) { bucketName: env.S3_BUCKET_NAME, endpoint: env.S3_ENDPOINT, pathStyle: env.S3_PATH_STYLE === 'true', + cdnDomain: env.S3_CDN_DOMAIN || '', // 可选的 CDN 域名 enabled: true, fixed: true, }) @@ -157,6 +158,7 @@ export async function getUploadConfig(db, env) { if (s3Channels[0]) { s3Channels[0].enabled = s.enabled s3Channels[0].quota = s.quota // 保留容量限制配置 + s3Channels[0].cdnDomain = s.cdnDomain // 保留 CDN 域名配置 } continue diff --git a/functions/file/[[path]].js b/functions/file/[[path]].js index af41e61..45381a8 100644 --- a/functions/file/[[path]].js +++ b/functions/file/[[path]].js @@ -630,6 +630,72 @@ async function handleR2File(context, fileId, encodedFileName, fileType) { async function handleS3File(context, metadata, encodedFileName, fileType) { const { Referer, url, request } = context; + // 检查是否配置了 CDN 文件完整路径 + const cdnFileUrl = metadata?.S3CdnFileUrl; + + // 如果配置了 CDN 文件路径,通过 CDN 读取文件 + if (cdnFileUrl) { + try { + // 处理 HEAD 请求 + if (request.method === 'HEAD') { + const headers = new Headers(); + setCommonHeaders(headers, encodedFileName, fileType, Referer, url); + return handleHeadRequest(headers); + } + + // 构建请求头 + const fetchHeaders = {}; + + // 支持 Range 请求 + const range = request.headers.get('Range'); + if (range) { + fetchHeaders['Range'] = range; + } + + // 通过 CDN 获取文件(直接使用完整路径,无需拼接) + const response = await fetch(cdnFileUrl, { + method: 'GET', + headers: fetchHeaders + }); + + if (!response.ok && response.status !== 206) { + // CDN 读取失败,回退到 S3 API + console.warn(`CDN fetch failed (${response.status}), falling back to S3 API`); + return await handleS3FileViaAPI(context, metadata, encodedFileName, fileType); + } + + // 构建响应头 + const headers = new Headers(); + setCommonHeaders(headers, encodedFileName, fileType, Referer, url); + + // 复制相关头部 + if (response.headers.get('Content-Length')) { + headers.set('Content-Length', response.headers.get('Content-Length')); + } + if (response.headers.get('Content-Range')) { + headers.set('Content-Range', response.headers.get('Content-Range')); + } + + return new Response(response.body, { + status: response.status, + headers + }); + + } catch (error) { + // CDN 读取出错,回退到 S3 API + console.error(`CDN fetch error: ${error.message}, falling back to S3 API`); + return await handleS3FileViaAPI(context, metadata, encodedFileName, fileType); + } + } + + // 没有配置 CDN 文件路径,使用 S3 API + return await handleS3FileViaAPI(context, metadata, encodedFileName, fileType); +} + +// 通过 S3 API 读取文件 +async function handleS3FileViaAPI(context, metadata, encodedFileName, fileType) { + const { Referer, url, request } = context; + const s3Client = new S3Client({ region: metadata?.S3Region || "auto", endpoint: metadata?.S3Endpoint, diff --git a/functions/upload/index.js b/functions/upload/index.js index c006440..2bb400d 100644 --- a/functions/upload/index.js +++ b/functions/upload/index.js @@ -329,7 +329,7 @@ async function uploadFileToS3(context, fullId, metadata, returnLink) { return createResponse('Error: No S3 channel provided', { status: 400 }); } - const { endpoint, pathStyle, accessKeyId, secretAccessKey, bucketName, region } = s3Channel; + const { endpoint, pathStyle, accessKeyId, secretAccessKey, bucketName, region, cdnDomain } = s3Channel; // 创建 S3 客户端 const s3Client = new S3Client({ @@ -381,6 +381,11 @@ async function uploadFileToS3(context, fullId, metadata, returnLink) { metadata.S3Region = region || "auto"; metadata.S3BucketName = bucketName; metadata.S3FileKey = s3FileName; + // 保存 CDN 文件完整路径(如果配置了 CDN 域名) + if (cdnDomain) { + // 存储完整的 CDN 文件路径,而不是仅存储域名 + metadata.S3CdnFileUrl = `${cdnDomain.replace(/\/$/, '')}/${s3FileName}`; + } // 图像审查 if (uploadModerate && uploadModerate.enabled) {