feat(remote): 实现文件上传真实进度显示
- 使用分块上传替代一次性上传 - 调用 /upload/start → /upload/chunk → /upload/merge 接口 - 通过 IPC 事件实时推送上传进度到前端 - 修复 merge 时未使用目标路径的问题
This commit is contained in:
@@ -212,7 +212,7 @@ ipcMain.handle('remote-fetch-files', async (_event, serverHost: string, port: nu
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('remote-upload-file', async (_event, serverHost: string, port: number, filePath: string, remotePath: string, password?: string) => {
|
||||
ipcMain.handle('remote-upload-file', async (_event, id: string, serverHost: string, port: number, filePath: string, remotePath: string, password?: string) => {
|
||||
try {
|
||||
const win = electronState.getMainWindow();
|
||||
if (!win) {
|
||||
@@ -224,28 +224,66 @@ ipcMain.handle('remote-upload-file', async (_event, serverHost: string, port: nu
|
||||
throw new Error('File not found');
|
||||
}
|
||||
|
||||
const fileBuffer = fs.readFileSync(fullPath);
|
||||
const stats = fs.statSync(fullPath);
|
||||
const fileSize = stats.size;
|
||||
const fileName = path.basename(fullPath);
|
||||
|
||||
let url = `http://${serverHost}:${port}/api/files/upload`;
|
||||
let url = `http://${serverHost}:${port}/api/files/upload/start`;
|
||||
if (password) {
|
||||
url += `?password=${encodeURIComponent(password)}`;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
const blob = new Blob([fileBuffer]);
|
||||
formData.append('file', blob, fileName);
|
||||
if (remotePath) {
|
||||
formData.append('path', remotePath);
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
const startResponse = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ filename: fileName, fileSize }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Upload failed: ${response.statusText}`);
|
||||
if (!startResponse.ok) {
|
||||
throw new Error(`Failed to start upload: ${startResponse.statusText}`);
|
||||
}
|
||||
|
||||
const { fileId, chunkSize } = await startResponse.json();
|
||||
const CHUNK_SIZE = chunkSize || (64 * 1024);
|
||||
const totalChunks = Math.ceil(fileSize / CHUNK_SIZE);
|
||||
|
||||
const readStream = fs.createReadStream(fullPath, { highWaterMark: CHUNK_SIZE });
|
||||
let chunkIndex = 0;
|
||||
let uploadedBytes = 0;
|
||||
|
||||
for await (const chunk of readStream) {
|
||||
const formData = new FormData();
|
||||
const blob = new Blob([chunk]);
|
||||
formData.append('chunk', blob, fileName);
|
||||
formData.append('fileId', fileId);
|
||||
formData.append('chunkIndex', chunkIndex.toString());
|
||||
|
||||
const chunkUrl = `http://${serverHost}:${port}/api/files/upload/chunk${password ? `?password=${encodeURIComponent(password)}` : ''}`;
|
||||
const chunkResponse = await fetch(chunkUrl, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!chunkResponse.ok) {
|
||||
throw new Error(`Failed to upload chunk ${chunkIndex}: ${chunkResponse.statusText}`);
|
||||
}
|
||||
|
||||
uploadedBytes += chunk.length;
|
||||
const progress = Math.round((uploadedBytes / fileSize) * 100);
|
||||
win.webContents.send('upload-progress', { id, progress });
|
||||
|
||||
chunkIndex++;
|
||||
}
|
||||
|
||||
const mergeUrl = `http://${serverHost}:${port}/api/files/upload/merge${password ? `?password=${encodeURIComponent(password)}` : ''}`;
|
||||
const mergeResponse = await fetch(mergeUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ fileId, totalChunks, filename: fileName, path: remotePath }),
|
||||
});
|
||||
|
||||
if (!mergeResponse.ok) {
|
||||
throw new Error(`Failed to merge chunks: ${mergeResponse.statusText}`);
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
|
||||
Reference in New Issue
Block a user