即上一篇文章,基于WebRTC 文件传输 / 共享桌面,对于文件的传输大小有一定的限制,现改为通过 Ip + Port + FileAddress,进行下载。
git地址:基于Electron实现内网快传
实现的功能及解决方式
- 用户在线列表 (WebSocketServer)
- 修改名字 (同上)
- 私聊/群聊 (同上)
- 文件拖拽发送 ( preload 递归返回文件地址树 )
- 首次下载需保存路径 ( dialog.showOpenDialog )
- 下载文件环绕动画 ( linear-gradient + 动态 processVal )
- 下载文件分文件夹存储 ( 下载时,默认创建接收IP存入 )
- 文件丢失重新下载,存在则打开 ( 递归检查丢失文件重新下载 )
- 历史聊天记录 ( 存储至Json,打开即重载 )
- 内网通过IP + 端口 + 文件地址,按流下载 ( steam + chunk + processVal )
- 根据文件类型展示不同Icon ( 获取后缀switchCase 配置url )
先上效果图
主要实现功能及技术
- 前端采用 Electron + Vue3
- 后端采用 Ws 做简单的通信,Http 做下载辅助
Electron 要点剖析 (只出重点)
main.ts (基本配置)
// 核心内容
let mainWindow: BrowserWindow;
function createWindow() {
mainWindow = new BrowserWindow({
icon: __dirname + "./images/speedCache.ico",
webPreferences: {
preload: path.join(__dirname, "preload.js"),
contextIsolation: true,
nodeIntegration: false,
partition: String(+new Date()),
},
});
// preload... 引用
ipcMain.handle("get-local-ip", () => getLocalIP());
ipcMain.handle("get-file-port", () => FILE_PORT);
ipcMain.on("close", () => { mainWindow.close() });
ipcMain.on("minimize", () => { mainWindow.minimize() });
mainWindow.webContents.openDevTools(); // 调试工具
// 测试url
mainWindow.loadURL("http://10.0.0.155:5173/");
}
// 限制只能启动一次
app.on("second-instance", (event, commandLine, workingDirectory) => {
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
});
if (!app.requestSingleInstanceLock()) {
app.quit();
} else {
app.whenReady().then(createWindow);
app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit();
});
}
preload.ts(node隔离,分离渲染层)
其中包括:获取个人Ip、端口、文件下载、文件保存路径、流式下载文件 等等....
contextBridge.exposeInMainWorld("electronAPI", {
getLocalIP: () => ipcRenderer.invoke("get-local-ip"),
getFilePort: () => ipcRenderer.invoke("get-file-port"),
downloadAllFiles: (data) => ipcRenderer.invoke("downloadAllFiles", data),
saveFileToPath: (file) => ipcRenderer.invoke("save-file-to-path", file),
writeChunk: (data) => ipcRenderer.invoke("write-chunk", data),
});
运行个人 server.ts(固定Ip + 随机Port;用于请求下载的地址!!!)
// 随机端口
let FILE_PORT: number;
const server = http.createServer((req, res) => {
const url: any = new URL(req.url!, `http://${req.headers.host}`);
const action = url.pathname;
if (action.startsWith("/download/")) {
const encodedFilePath = action.split("/download/")[1];
const filePath = decodeURIComponent(encodedFilePath);
if (fs.existsSync(filePath)) {
const stat = fs.statSync(filePath);
const fileName = encodeURIComponent(path.basename(filePath));
res.writeHead(200, {
"Content-Type": "application/octet-stream",
"Content-Length": stat.size,
"Content-Disposition": `attachment; filename="${fileName}"`,
});
const readStream = fs.createReadStream(filePath); // 下载流
readStream.on("error", (err) => {
if (!res.headersSent) {
res.writeHead(500, { "Content-Type": "text/plain" });
res.end("Internal Server Error");
} else {
res.end();
}
});
readStream.pipe(res); // 管道
readStream.on("error", (err) => {
console.error("File read error:", err);
res.writeHead(500, { "Content-Type": "text/plain" });
res.end("File read error");
});
} else {
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("Not found");
}
}
});
server.listen(0, () => {
const address: any = server.address();
if (address && typeof address.port === "number") {
FILE_PORT = address.port;
}
});
chat-server.ts (只做文字的传输,不做下载操作)
class ChatServer {
private wss: WebSocketServer;
private clients: Map<string, { ws: WebSocket; name: string }> = new Map();
constructor(usersFilePath: string, historyFilePath: string) {
this.wss = new WebSocketServer({ port: PORT });
this.initWebSocketServer();
}
private initWebSocketServer(): void {
this.wss.on("connection", (ws: any) => {
ws.on("message", (message: { toString: () => string }) => {
try {
let parsed: any;
switch (parsed.type) {
case "init"
case "set-name"
case "message"
case "private-message"
case "file-info"
case "private-file-info"
case "folder-info"
case "private-folder-info
}
}
});
ws.on("close", () => {
if (id) {
this.clients.delete(id);
this.broadcastUserList();
}
});
});
}
前端代码就不 1 1展示了
后续希望,有A B C,设置A作为共享,文件的发送和传输A实时自动接收,即使他人文件丢失依然可以从A的文件库中获取,并且可以拓展文件的管理及UI设计的重塑....