首页/文章/electron

Electron在worker里面上传文件

2024-08-06
13485 分钟
...

前言

大家都知道,在electron 里面,除了壳子,业务代码其实都是放在浏览器环境的,就和我们写reactvue项目其实没多大差别,最主要的区别主要是我们可以通过electron 能力,暴露给渲染进程一些方法,渲染进程通过和主进程交互,实现一些系统层面的能力交互。这期我们主要围绕着文件上传进行。

环境

electron ^23.1.1

Node >18.0.0

electron-builder ^23.6.0

electron-updater ^4.2.0

其实关于上传功能,可以有两个选择,渲染进程上传 | 主进程���传,但是,如果通过渲染进程上传,有几个弊端,第一,无法感知文件取消上传回调,第二,渲染进程上传可能会影响渲染进程阻塞等问题,所以,我这里选择了通过主进程里面去做文件上传。

electron最新版本中,我们可以通过preload 统一暴露给渲染进程调用主线程中的一些方法,然后主线程中注册,并监听这些方法,从而和渲染进程进行交互,主线程和渲染线程交互的方法有好几种,这里就不过多赘述了,主要围绕文件上传这一流程进行。

1,选择文件

首先,我们选择文件,这里我选择通过electrondialog模块,打开一个选择文件弹窗,然后获取到选择的文件路径,然后通过ipcRenderer.invoke调用主进程的方法,将文件路径传递给主进程,然后主进程通过fs模块读取文件,然后通过http请求上传到服务器。


ipcMain.handle("openDialog", async (_, option) => {
    return  new Promise((resolve, reject) => {
    dialog
      .showOpenDialog(mainWindow, Options)
      .then((result) => {
        if (result.canceled) {
          reject("已取消");
        } else {
          console.log("result", result);
          
          const filePath = result.filePaths[0];
          const filePathName = path.extname(filePath);
          const fileType = mime.lookup(filePath);
          const fileName = path.basename(filePath);
          const stats = fsv.statSync(filePath);
          const size = coverSize(stats.size);
          const fileinfo = {
            fileName,
            fileType,
            size: stats.size,
            filePath,
            fileStatus: "start",
            fileSize: size,
            ...result,
          };
          resolve(fileinfo);
        }
      })
      .catch((err) => {
        console.error("openDilog==>", err);
        reject(err);
      });
  });
  });


  
  const fileAddress = await window.electronAPI?.ipcInvoke<FileInfo>("openDialog", { properties: ["openFile"], ...(opt || {}) });

2,通知主进程上传文件

选择文件后,可以在渲染进程进行展示文件信息,上传要交给主进程,主进程通过fs模块读取文件,然后通过http请求上传到服务器。首先我们第一步我们要注册上传事件,(当然,注册事件可以只注册一个事件,通过传入不同参数去匹配对应的逻辑处理,我这里就单独注册了,看需求考量)


ipcMain.handle("uploadFile", async (_, fileInfo) => {
  upload(fileInfo)
})

通过给主进程发送消息,主进程上传文件即可


window.electronAPI?.ipcInvoke("uploadFile", {
      fileAddress: fileInfo.filePath,
      folder,
      id: insertId,
      options: {
        fileObsUrl,
        downloadKey,
        conversationID,
        insertClientMsgID,
        folder,
        fileInfo,
        insertId,
      },
    });

到这里,其实交互流程就已经算完结了,但是,还有两个功能没有处理,第一个就是上传进程以及上传完成后要通知渲染进程去修改状态;第二个也是最重要的,实现上传的逻辑部分

3,上传文件

上传文件,这里我们用的三方obs,相信大家都有自己的上传方式,但是有一个问题相信大家都知道,如果是一个聊天或者多任务列表,这种方式其实不能只有一个上传,会有很多的上传任务,那么,我们要考虑多个任务的情况,去并发这些任务,其实就是多线程上传,在浏览器端,也是是woker,同理,在主线程中,我们可以使用 worker_threads 去进行多线程,这是node > 12版本以后新增的功能,不是三方模块实现的,属于node原生自带功能。和 browser woker使用方法一致


const { workerData, parentPort } = require("worker_threads");
const path = require("node:path");
const ObsClient = require("esdk-obs-nodejs");

  function nodeUploadFiles(option, parentPort, onResumeCallback) {
    const { fileAddress, folder, id } = option;
    const fileName = path.basename(fileAddress);
    const fileType = path.extname(fileAddress);
 let prePercent = 0;
      nodeObsClient.putObject(
        {
          Bucket: PCIMFOLDER,
          Key: `${ENV}/${folder}${fileName}`,
          SourceFile: fileAddress,
          ProgressCallback: (transferredAmount, totalAmount, totalSeconds) => {
            const p = (transferredAmount * 100) / totalAmount;
            const percent = Math.round(p);
            if (prePercent !== percent) {
              parentPort.postMessage({ type: "percent", value: { percent, status: "starting", ...workerData }, id });
              prePercent = percent;
            }
          },
          ResumeCallback: (resumeHook) => {
            hook = resumeHook;
          },
        },
        (err, data) => {
          if (err) {
            
            parentPort?.postMessage({ type: "percent", value: { status: "errorFile", percent: 0, ...workerData }, id: workerData?.id, error: err });

            reject(err);
          } else {
            
            console.info(`upload=>complete`);
            const newData = {
              url: `${obsUrl}${ENV}/${folder}${fileName}`,
              code: data.CommonMsg.Status,
              Message: data.CommonMsg.Message,
              ...data,
            };
            parentPort.postMessage({ type: "percent", value: { percent: 100, status: "completed", ...workerData }, id });
          }
        }
      );
  }

handleUpload(workerData);


import { Worker } from "node:worker_threads";

const runWorkers = (workerDatas) => {
  return new Promise(() => {
    const workerPath1 = path.resolve(__dirname, "./worker.js");
    const worker = new Worker(workerPath, { workerData: { ...workerDatas, resourcesPath } });
   
    const taskHandle = (worker, option) => {
      worker?.postMessage(option);
    };
    const onExitCallback = () => {
      global.globalEmitter.off(workerDatas.id, taskHandle);
    };
    
    worker.on("message", onMessageCallback);
    
    worker.on("error", onErrorCallback);
    
    worker.on("exit", onExitCallback);
  });
};


到这里,最简单版的一个文件上传功能就完成了,但是,还有两个问题,一个是断点续传,一个是上传失败后,需要重新上传,这里就自己实现一下,方法大致就是记录一下断点的位置,然后下次上传的时候,从断点处开始上传,这里就不贴代码了,调研一下obs的文档应该都可以找到

最后

其实写到这里,只是引出了通过worker去上传这个功能,并没有实现完整的链路

  1. woker本身是一个线程,但是重复开关导致线程资源浪费,所以,我们要实现一个线程池,去根据cups数量,动态去扩展我们woker的数量;
  2. woker如果通过线程池去动态扩展,那么,woker里面的任务,其实也需要任务池,动态去给每一个woker去分发任务,这里其实就是一个任务队列,每次根据woker的运行状态去分发任务;
  3. 不知道大家有没有遇到过,在electron- build 打包后使用 worker_threads 的时候,woker文件里面三方包路径会有问题,而且,在new woker的地方传入的woker文件的path也是有问题的

等后面有空了补上,如果有错误,希望大家可以指正。

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
electron2025-03-05
不依赖第三方库自己实现 Electron 的主与渲染进程的国际化功能
正文 直接贴出代码: 主进程实现 首先需要在 文件中获取当前系统的语言,然后初始化国际化相关: 文件: 文件 然后在主进程中就可以通过 来实现国际化了。 渲染进程的实现 App 在启动的时候渲染进程已经执行了国际化的初始化,所以在主进程已经保存了一份当前语言的 信息,所以渲染进程就不需要进程这一步,直接从主进程获取即可。 主进程进程添加一个 事件,供渲染进程获取国际化相关数据: 首先我们要在 preload 中提前将国际化相关的信息写入...
学习
electron2025-03-05
应用多开,限制只启动一个应用,防止多个实例
通过app.requestSingleInstanceLock来控制应用的多开,返回值为boolean。 此方法的返回值表示你的应用程序实例是否成功取得了锁。如果它取得锁失败,你可以假设另一个应用实例已经取得了锁并且仍旧在运行,并立即退出。 即:如果当前进程是应用程序的主要实例,则此方法返回true,同时你的应用会继续运行。如果当它返回false,如果你的程序没有取得锁,它应该立刻退出,并且将参数发送给那个已经取到锁的进程。 在macO...
学习
electron2024-09-23
Electron实现文件缓存背景
背景 基于Electron研发的一款IM企业通信桌面端应用,会存在非常多文件、图片、视频类型回话消息。在Electron应用(桌面客户端软件)中,快速加载并显示图片是提升用户体验的关键。然而,传统的图片加载方式往往存在加载速度慢、资源占用高等问题,影响了用户的使用体验。 解决的问题 1. 支持自定义配置存储的磁盘位置 2. 支持长期存储 3. 支持自定义存储大小 4. 支持自定义存储类型(如图片、视频、文件,或者更细致化到MIME) 5...
学习
electron2024-09-23
我的 Electron 客户端也可以全量和增量更新了
前言 本文主要介绍 客户端应用的自动更新,包括全量和增量这两种方式。 全量更新: 运行新的安装包,安装目录所有资源覆盖式更新。 增量更新: 只更新修改的部分,通常是渲染进程和主进程文件。 本文并没有拿真实项目来举例子,而是起一个 新的项目 从 0 到 1 来实现自动更新。 如果已有的项目需要支持该功能,借鉴本文的主要步骤即可。 前置说明: 1. 由于业务场景的限制,本文介绍的更新仅支持 操作系统,其余操作系统未作兼容处理。 2. 更新流...
学习
electron2024-09-20
我的 Electron 客户端被第三方页面入侵了
问题描述 公司有个内部项目是用 来开发的,有个功能需要像浏览器一样加载第三方站点。 本来一切安好,但是某天打开某个站点的链接,导致 整个客户端直接变成了该站点的页面 。 这一看就是该站点做了特殊的处理,经排查网页源码后,果然发现了有这么一句代码。 翻译一下就是:如果当前窗口不是顶级窗口的话,将当前窗口设置为顶级窗口。 奇怪的是两者不是 跨域 了吗,为什么 还可以影响顶级窗口。 先说一下我当时的一些解决办法: 1. 用 替换 2. 给 添...
学习
electron2024-09-13
Electron+WebRTC实现局域网内的远程控制软件
我的工位上有一台笔记本(windows),一个显示器,一台服务器(linux)。显示器同时连接笔记本和服务器,当需要用到服务器的时候:1.使用todesk等软件进行连接;2.直接把显示器切换到服务器桌面,然后给服务器插上鼠标键盘。在这种场景下,屏幕画面的传输对我来说并不是必要的,这种场景下能不能用一套键鼠控制两个电脑呢?边做边学便有了这个小工具。记录下来,其中遇到的问题和解决方案。 通过electron + Vue + primevue...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录