From cd14225f23229b1cb9b8511ce3cda3a2d86997db Mon Sep 17 00:00:00 2001 From: Baobhan Sith <80159437+Heavrnl@users.noreply.github.com> Date: Mon, 5 May 2025 17:27:21 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20SFTP=20=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E5=9B=A0=E4=BC=9A=E8=AF=9D=E6=9F=A5=E6=89=BE=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E5=92=8C=E7=B1=BB=E5=9E=8B=E4=B8=8D=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=E8=80=8C=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/sftp/sftp.controller.ts | 74 ++++++++++++-------- packages/backend/src/websocket.ts | 14 +++- 2 files changed, 57 insertions(+), 31 deletions(-) diff --git a/packages/backend/src/sftp/sftp.controller.ts b/packages/backend/src/sftp/sftp.controller.ts index de34251..46c6455 100644 --- a/packages/backend/src/sftp/sftp.controller.ts +++ b/packages/backend/src/sftp/sftp.controller.ts @@ -1,6 +1,6 @@ import { Request, Response } from 'express'; import path from 'path'; -import { clientStates } from '../websocket'; +import { clientStates, ClientState } from '../websocket'; // +++ 导入 ClientState +++ import archiver from 'archiver'; // +++ 引入 archiver +++ import { SFTPWrapper, Stats } from 'ssh2'; // +++ 移除 Dirent 导入 +++ // 移除 ssh2-streams 导入 @@ -25,33 +25,39 @@ export const downloadFile = async (req: Request, res: Response): Promise = console.log(`SFTP 下载请求:用户 ${userId}, 连接 ${connectionId}, 路径 ${remotePath}`); - // 查找与当前用户会话关联的活动 WebSocket 连接和 SFTP 会话 - let userSftpSession = null; - // 注意:这种查找方式效率不高,实际应用中可能需要更优化的结构来按 userId 查找连接 + // --- 修改:查找与 userId 和 connectionId 匹配的活动 SFTP 会话 --- + let targetState: ClientState | null = null; + const targetDbConnectionId = parseInt(connectionId, 10); // 将查询参数字符串转换为数字 + + if (isNaN(targetDbConnectionId)) { + res.status(400).json({ message: '无效的 connectionId。' }); + return; + } + + console.log(`SFTP 下载:正在查找用户 ${userId} 且连接 ID 为 ${targetDbConnectionId} 的会话...`); for (const [sessionId, state] of clientStates.entries()) { - const ws = state.ws; - const connData = state; - // 假设 AuthenticatedWebSocket 上存储了 userId - if (ws.userId === userId && connData.sftp) { - // 这里简单地取第一个找到的匹配连接,没有处理 connectionId 的匹配 - // TODO: 需要一种方式将 HTTP 请求与特定的 WebSocket/SSH/SFTP 会话关联起来 - // 临时方案:假设一个用户只有一个活动的 SSH/SFTP 会话 - userSftpSession = connData.sftp; - console.log(`找到用户 ${userId} 的活动 SFTP 会话。`); + // 检查 userId 和 dbConnectionId 是否都匹配,并且 sftp 实例存在 + if (state.ws.userId === userId && state.dbConnectionId === targetDbConnectionId && state.sftp) { + targetState = state; + console.log(`SFTP 下载:找到匹配的会话 (Session ID: ${sessionId})。`); break; } } - if (!userSftpSession) { - console.warn(`SFTP 下载失败:未找到用户 ${userId} 的活动 SFTP 会话。`); - res.status(404).json({ message: '未找到活动的 SFTP 会话。请确保您已通过 WebSocket 连接到目标服务器。' }); + if (!targetState || !targetState.sftp) { + console.warn(`SFTP 下载失败:未找到用户 ${userId} 且连接 ID 为 ${targetDbConnectionId} 的活动 SFTP 会话。`); + res.status(404).json({ message: '未找到指定的活动 SFTP 会话。请确保目标连接处于活动状态。' }); return; } + const userSftpSession = targetState.sftp; // 获取正确的 SFTP 实例 + // --- 结束修改 --- + try { // 获取文件状态以确定文件大小(可选,但有助于设置 Content-Length) const stats = await new Promise((resolve, reject) => { - userSftpSession!.lstat(remotePath, (err, stats) => { + // +++ 修正类型注解 +++ + userSftpSession!.lstat(remotePath, (err: Error | undefined, stats: import('ssh2').Stats) => { if (err) return reject(err); resolve(stats); }); @@ -126,29 +132,39 @@ export const downloadDirectory = async (req: Request, res: Response): Promise((resolve, reject) => { - userSftpSession!.lstat(remotePath, (err, stats) => { + // +++ 修正类型注解 +++ + userSftpSession!.lstat(remotePath, (err: Error | undefined, stats: import('ssh2').Stats) => { if (err) return reject(err); resolve(stats); }); diff --git a/packages/backend/src/websocket.ts b/packages/backend/src/websocket.ts index 9879081..21110d3 100644 --- a/packages/backend/src/websocket.ts +++ b/packages/backend/src/websocket.ts @@ -678,16 +678,26 @@ let connInfo: SshService.DecryptedConnectionDetails | null = null; // 将 connIn const newSessionId = uuidv4(); ws.sessionId = newSessionId; + // --- 修正:确保 dbConnectionId 存储为数字 --- + const dbConnectionIdAsNumber = parseInt(dbConnectionId, 10); + if (isNaN(dbConnectionIdAsNumber)) { + // 如果转换失败,记录错误并可能关闭连接 + console.error(`WebSocket: 无效的 dbConnectionId '${dbConnectionId}' (非数字),无法创建会话 ${newSessionId}。`); + ws.send(JSON.stringify({ type: 'ssh:error', payload: '无效的连接 ID。' })); + sshClient.end(); // 关闭 SSH 连接 + ws.close(1008, 'Invalid Connection ID'); // 关闭 WebSocket + return; // 停止执行 + } const newState: ClientState = { ws: ws, sshClient: sshClient, - dbConnectionId: dbConnectionId, + dbConnectionId: dbConnectionIdAsNumber, // 存储数字类型 connectionName: connInfo!.name, // 填充 connectionName (non-null assertion) ipAddress: clientIp, // 存储 IP 地址 isShellReady: false, // 初始化 Shell 状态为未就绪 }; clientStates.set(newSessionId, newState); - console.log(`WebSocket: 为用户 ${ws.username} (IP: ${clientIp}) 创建新会话 ${newSessionId} (DB ID: ${dbConnectionId})`); + console.log(`WebSocket: 为用户 ${ws.username} (IP: ${clientIp}) 创建新会话 ${newSessionId} (DB ID: ${dbConnectionIdAsNumber})`); // 4. 立即打开 Shell (使用默认尺寸) ws.send(JSON.stringify({ type: 'ssh:status', payload: 'SSH 连接成功,正在打开 Shell...' }));