From cff5ebea991ca8c074de216b3ab9005329529d62 Mon Sep 17 00:00:00 2001 From: fengyuki Date: Fri, 12 Jun 2026 03:23:40 +0800 Subject: [PATCH 1/2] feat: upload API response includes publicUrl based on urlPrefix config When urlPrefix is configured in page settings, the upload API now returns a `publicUrl` field in addition to the existing `src` field, allowing clients to directly access files via the configured public domain (e.g. R2 custom domain) without needing to reconstruct the URL. --- functions/upload/index.js | 82 +++++++++++++-------------------------- 1 file changed, 27 insertions(+), 55 deletions(-) diff --git a/functions/upload/index.js b/functions/upload/index.js index afe3b28f..62ed63a9 100644 --- a/functions/upload/index.js +++ b/functions/upload/index.js @@ -1,5 +1,5 @@ import { userAuthCheck, UnauthorizedResponse } from "../utils/auth/userAuth"; -import { fetchUploadConfig, fetchSecurityConfig } from "../utils/sysConfig"; +import { fetchUploadConfig, fetchSecurityConfig, fetchPageConfig } from "../utils/sysConfig"; import { createResponse, getUploadIp, getIPAddress, resolveFileExt, moderateContent, purgeCDNCache, isBlockedUploadIp, buildUniqueFileId, endUpload, getImageDimensions, @@ -200,6 +200,12 @@ async function processFileUpload(context, formdata = null) { returnLink = `/file/${fullId}`; } + // 构建公开访问链接(使用 urlPrefix 配置) + const pageConfig = await fetchPageConfig(context.env); + const urlPrefixConfig = pageConfig.config?.find(c => c.id === 'urlPrefix'); + const urlPrefix = urlPrefixConfig?.value || ''; + context.publicUrl = urlPrefix ? `${urlPrefix.replace(/\/+$/, '')}/${fullId}` : ''; + /* ====================================不同渠道上传======================================= */ // 出错是否切换渠道自动重试,默认开启 const autoRetry = url.searchParams.get('autoRetry') === 'false' ? false : true; @@ -265,6 +271,19 @@ async function processFileUpload(context, formdata = null) { return res; } +// 构建上传成功响应,自动附带 publicUrl(如果已配置) +function buildUploadResponse(context, returnLink) { + const result = { src: returnLink }; + if (context.publicUrl) { + result.publicUrl = context.publicUrl; + } + return createResponse(JSON.stringify([result]), { + headers: { + 'Content-Type': 'application/json', + }, + }); +} + // 上传到Cloudflare R2 async function uploadFileToCloudflareR2(context, fullId, metadata, returnLink) { const { env, waitUntil, uploadConfig, formdata, specifiedChannelName } = context; @@ -317,15 +336,7 @@ async function uploadFileToCloudflareR2(context, fullId, metadata, returnLink) { waitUntil(endUpload(context, fullId, metadata)); // 成功上传,将文件ID返回给客户端 - return createResponse( - JSON.stringify([{ 'src': `${returnLink}` }]), - { - status: 200, - headers: { - 'Content-Type': 'application/json', - } - } - ); + return buildUploadResponse(context, returnLink); } @@ -417,12 +428,7 @@ async function uploadFileToS3(context, fullId, metadata, returnLink) { // 结束上传 waitUntil(endUpload(context, fullId, metadata)); - return createResponse(JSON.stringify([{ src: returnLink }]), { - status: 200, - headers: { - "Content-Type": "application/json", - }, - }); + return buildUploadResponse(context, returnLink); } catch (error) { return createResponse(`Error: Failed to upload to S3 - ${error.message}`, { status: 500 }); } @@ -515,15 +521,7 @@ async function uploadFileToTelegram(context, fullId, metadata, fileExt, fileName metadata.FileSize = (fileInfo.file_size / 1024 / 1024).toFixed(2); // 将响应返回给客户端 - res = createResponse( - JSON.stringify([{ 'src': `${returnLink}` }]), - { - status: 200, - headers: { - 'Content-Type': 'application/json', - } - } - ); + res = buildUploadResponse(context, returnLink); // 图像审查(使用代理域名或官方域名) @@ -583,15 +581,7 @@ async function uploadFileToExternal(context, fullId, metadata, returnLink) { waitUntil(endUpload(context, fullId, metadata)); // 返回结果 - return createResponse( - JSON.stringify([{ 'src': `${returnLink}` }]), - { - status: 200, - headers: { - 'Content-Type': 'application/json', - } - } - ); + return buildUploadResponse(context, returnLink); } @@ -671,13 +661,7 @@ async function uploadFileToDiscord(context, fullId, metadata, returnLink) { waitUntil(endUpload(context, fullId, metadata)); // 返回成功响应 - return createResponse( - JSON.stringify([{ 'src': returnLink }]), - { - status: 200, - headers: { 'Content-Type': 'application/json' } - } - ); + return buildUploadResponse(context, returnLink); } catch (error) { console.error('Discord upload error:', error.message); @@ -791,13 +775,7 @@ async function uploadFileToHuggingFace(context, fullId, metadata, returnLink) { waitUntil(endUpload(context, fullId, metadata)); // 返回成功响应 - return createResponse( - JSON.stringify([{ 'src': returnLink }]), - { - status: 200, - headers: { 'Content-Type': 'application/json' } - } - ); + return buildUploadResponse(context, returnLink); } catch (error) { console.error('HuggingFace upload error:', error.message); @@ -872,13 +850,7 @@ async function uploadFileToWebDAV(context, fullId, metadata, returnLink) { waitUntil(endUpload(context, fullId, metadata)); - return createResponse( - JSON.stringify([{ 'src': returnLink }]), - { - status: 200, - headers: { 'Content-Type': 'application/json' } - } - ); + return buildUploadResponse(context, returnLink); } catch (error) { console.error('WebDAV upload error:', error.message); return createResponse(`Error: WebDAV upload failed - ${error.message}`, { status: 500 }); From 908eaacce4ed1d0ed68820df8d1c4d7d933960ae Mon Sep 17 00:00:00 2001 From: fengyuki Date: Fri, 12 Jun 2026 03:56:08 +0800 Subject: [PATCH 2/2] fix: add publicUrl to chunked upload and merge responses Chunked upload (uploadLargeFileToTelegram) and chunk merge responses now also include publicUrl field when urlPrefix is configured, matching the behavior of regular uploads. --- functions/upload/chunkMerge.js | 13 +++++++++++++ functions/upload/chunkUpload.js | 12 +++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/functions/upload/chunkMerge.js b/functions/upload/chunkMerge.js index b2ad0b0e..1b994b5c 100644 --- a/functions/upload/chunkMerge.js +++ b/functions/upload/chunkMerge.js @@ -3,6 +3,7 @@ import { createResponse, getUploadIp, getIPAddress, selectConsistentChannel, bui import { retryFailedChunks, cleanupFailedMultipartUploads, checkChunkUploadStatuses, cleanupChunkData, cleanupUploadSession } from './chunkUpload'; import { S3Client, CompleteMultipartUploadCommand } from "@aws-sdk/client-s3"; import { getDatabase } from '../utils/databaseAdapter.js'; +import { fetchPageConfig } from '../utils/sysConfig.js'; // 处理分块合并 export async function handleChunkMerge(context) { @@ -112,6 +113,18 @@ async function startMerge(context, uploadId, totalChunks, originalFileName, orig // 清理上传会话 await cleanupUploadSession(env, uploadId); + // 构建公开访问链接(使用 urlPrefix 配置) + if (result.result && result.result.length > 0) { + const src = result.result[0].src; + const fileName = src.startsWith('/file/') ? src.slice(6) : src.split('/file/').pop(); + const pageConfig = await fetchPageConfig(env); + const urlPrefixConfig = pageConfig.config?.find(c => c.id === 'urlPrefix'); + const urlPrefix = urlPrefixConfig?.value || ''; + if (urlPrefix) { + result.result[0].publicUrl = `${urlPrefix.replace(/\/+$/, '')}/${fileName}`; + } + } + return createResponse(JSON.stringify(result.result), { status: 200, headers: { 'Content-Type': 'application/json' } diff --git a/functions/upload/chunkUpload.js b/functions/upload/chunkUpload.js index 6b216db4..0be6d9a7 100644 --- a/functions/upload/chunkUpload.js +++ b/functions/upload/chunkUpload.js @@ -4,6 +4,7 @@ import { TelegramAPI } from '../utils/storage/telegramAPI'; import { DiscordAPI } from '../utils/storage/discordAPI'; import { S3Client, CreateMultipartUploadCommand, UploadPartCommand, AbortMultipartUploadCommand } from "@aws-sdk/client-s3"; import { getDatabase, checkDatabaseConfig } from '../utils/databaseAdapter.js'; +import { fetchPageConfig } from '../utils/sysConfig.js'; // 初始化分块上传 export async function initializeChunkedUpload(context) { @@ -1271,8 +1272,17 @@ export async function uploadLargeFileToTelegram(context, file, fullId, metadata, // 异步结束上传 waitUntil(endUpload(context, fullId, metadata)); + // 构建公开访问链接(使用 urlPrefix 配置) + const pageConfig = await fetchPageConfig(env); + const urlPrefixConfig = pageConfig.config?.find(c => c.id === 'urlPrefix'); + const urlPrefix = urlPrefixConfig?.value || ''; + const responseBody = [{ 'src': returnLink }]; + if (urlPrefix) { + responseBody[0].publicUrl = `${urlPrefix.replace(/\/+$/, '')}/${fullId}`; + } + return createResponse( - JSON.stringify([{ 'src': returnLink }]), + JSON.stringify(responseBody), { status: 200, headers: {