From 08023d6878bfad0339cb61485b87ce5e4f8aafd8 Mon Sep 17 00:00:00 2001 From: abearxiong Date: Fri, 30 Jan 2026 17:26:35 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=20postProxy=20=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E4=BB=A5=E6=94=AF=E6=8C=81=E4=BB=8E=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E5=A4=B4=E8=8E=B7=E5=8F=96=E6=96=87=E4=BB=B6=E5=A4=A7=E5=B0=8F?= =?UTF-8?q?=EF=BC=9B=E6=B7=BB=E5=8A=A0=20renameProxy=20=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E4=BB=A5=E5=A4=84=E7=90=86=E6=96=87=E4=BB=B6=E9=87=8D=E5=91=BD?= =?UTF-8?q?=E5=90=8D=EF=BC=9B=E4=BF=AE=E5=A4=8D=20isLocalhost=20=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E7=9A=84=E6=A0=BC=E5=BC=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/fm-manager/proxy/ai-proxy.ts | 113 +++++++++++++++++++++-- src/modules/fm-manager/utils.ts | 2 +- 2 files changed, 108 insertions(+), 7 deletions(-) diff --git a/src/modules/fm-manager/proxy/ai-proxy.ts b/src/modules/fm-manager/proxy/ai-proxy.ts index 2cc29ba..c09a061 100644 --- a/src/modules/fm-manager/proxy/ai-proxy.ts +++ b/src/modules/fm-manager/proxy/ai-proxy.ts @@ -163,7 +163,10 @@ export const postProxy = async (req: IncomingMessage, res: ServerResponse, opts: const _fileSize: string = params.get('size'); let fileSize: number | undefined = undefined; if (_fileSize) { - fileSize = parseInt(_fileSize, 10) + fileSize = parseInt(_fileSize, 10); + } else if (req.headers['content-length']) { + // 如果 URL 参数没有 size,尝试从请求头获取 + fileSize = parseInt(req.headers['content-length'], 10); } let meta = parseSearchValue(params.get('meta'), { decode: true }); if (!hash && !force) { @@ -216,6 +219,7 @@ export const postProxy = async (req: IncomingMessage, res: ServerResponse, opts: end({ success: true, name, info, isNew: true, hash, meta: meta?.metaData, statMeta }, '上传成功', 200); } catch (error) { + console.log('postProxy upload error', error); end({ error: error }, '上传失败', 500); } }); @@ -233,6 +237,24 @@ export const postProxy = async (req: IncomingMessage, res: ServerResponse, opts: pipeBusboy(req, res, bb); }; +export const getObjectByPathname = (opts: { + pathname: string, + version?: string, +}) => { + const [_, user, app] = opts.pathname.split('/'); + let prefix = ''; + let replaceKey = ''; + if (app === 'ai') { + const version = opts?.version || '1.0.0'; + replaceKey = `/${user}/${app}/`; + prefix = `${user}/${app}/${version}/`; + } else { + replaceKey = `/${user}/${app}/`; + prefix = `${user}/`; // root/resources + } + let objectName = opts.pathname.replace(replaceKey, prefix); + return { prefix, replaceKey, objectName, user, app }; +} export const getObjectName = async (req: IncomingMessage, opts?: { checkOwner?: boolean }) => { const _u = new URL(req.url, 'http://localhost'); const pathname = decodeURIComponent(_u.pathname); @@ -270,14 +292,90 @@ export const deleteProxy = async (req: IncomingMessage, res: ServerResponse, opt if (!isOwner) { return opts?.createNotFoundPage?.('no permission'); } + const end = (data: any, message?: string, code = 200) => { + res.writeHead(code, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ code: code, data: data, message: message || 'success' })); + }; try { - await oss.deleteObject(objectName); - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ success: true, message: 'delete success', objectName })); + // 如果以 / 结尾,删除该前缀下的所有对象(文件夹) + if (objectName.endsWith('/')) { + const objects = await oss.listObjects(objectName, { recursive: true }); + if (objects.length > 0) { + const objectNames = objects.map((obj: any) => obj.name); + await minioClient.removeObjects(bucketName, objectNames); + } + end({ success: true, objectName, deletedCount: objects.length }, 'delete success', 200); + } else { + await oss.deleteObject(objectName); + end({ success: true, objectName }, 'delete success', 200); + } } catch (error) { logger.error('deleteProxy error', error); - res.writeHead(500, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ success: false, error: error })); + end({ success: false, error }, 'delete failed', 500); + } +}; + +export const renameProxy = async (req: IncomingMessage, res: ServerResponse, opts: ProxyOptions) => { + const { objectName, isOwner, user } = await getObjectName(req); + let oss = opts.oss; + if (!isOwner) { + return opts?.createNotFoundPage?.('no permission'); + } + const end = (data: any, message?: string, code = 200) => { + res.writeHead(code, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ code: code, data: data, message: message || 'success' })); + }; + + const _u = new URL(req.url, 'http://localhost'); + let newName = _u.searchParams.get('newName'); + if (!newName) { + return end({ success: false }, 'newName parameter required', 400); + } + // 解码 URL 编码的路径 + newName = decodeURIComponent(newName); + if (!newName.startsWith('/')) { + newName = '/' + newName; + } + const newUrl = new URL(newName, 'http://localhost'); + const version = _u.searchParams.get('version') || '1.0.0'; + const newNamePath = newUrl.pathname; + // 确保 newName 有正确的前缀路径 + + const newObject = getObjectByPathname({ pathname: newNamePath, version }); + const { user: newUser, objectName: newObjectName } = newObject; + if (newUser !== user) { + return end({ success: false }, '文件重命名只能在同一用户下进行', 400); + } + try { + const isDir = objectName.endsWith('/'); + let copiedCount = 0; + + if (isDir) { + // 重命名文件夹:复制所有对象到新前缀,然后删除原对象 + const objects = await oss.listObjects(objectName, { recursive: true }); + for (const obj of objects) { + const oldKey = obj.name; + const newKey = oldKey.replace(objectName, newObjectName); + console.log('rename dir object', oldKey, newKey); + await minioClient.copyObject(bucketName, newKey, `/${bucketName}/${oldKey}`); + copiedCount++; + } + // 删除原对象 + const objectNames = objects.map((obj: any) => obj.name); + if (objectNames.length > 0) { + await minioClient.removeObjects(bucketName, objectNames); + } + } else { + // 重命名文件 + await minioClient.copyObject(bucketName, newObjectName, `/${bucketName}/${objectName}`); + await oss.deleteObject(objectName); + copiedCount = 1; + } + + end({ success: true, objectName, newObjectName, copiedCount }, 'rename success', 200); + } catch (error) { + logger.error('renameProxy error', error); + end({ success: false, error }, 'rename failed', 500); } }; @@ -296,6 +394,9 @@ export const aiProxy = async (req: IncomingMessage, res: ServerResponse, opts: P if (req.method === 'DELETE') { return deleteProxy(req, res, opts); } + if (req.method === 'PUT') { + return renameProxy(req, res, opts); + } return getAiProxy(req, res, opts); }; diff --git a/src/modules/fm-manager/utils.ts b/src/modules/fm-manager/utils.ts index 89055cf..66720f0 100644 --- a/src/modules/fm-manager/utils.ts +++ b/src/modules/fm-manager/utils.ts @@ -27,7 +27,7 @@ export const getDNS = (req: http.IncomingMessage) => { }; export const isLocalhost = (hostName: string) => { - if(!hostName) { + if (!hostName) { return false; } return hostName.includes('localhost') || hostName.includes('192.168');