Electron 脚本调用大坑!害惨我了

接上两篇文章:

  1. Electron 初体验 ------ AI辅助上手,确实不难(๑•̀ㅂ•́)و✧
  2. Electron 在乌班图上打包 ------ AI辅助

菜鸟以为做完上面两篇文章就可以高枕无忧了,结果现实给了菜鸟当头一棒!

昨天,生信部门将他封装的脚本部署上了服务器,并让我进行调试,看看有没有什么问题!我按照其示例输入、示例运行shell,去修改了自己的运行脚本文件,修改后如下:

js 复制代码
function runScript(ipcMain, app, path, fs, exec) {
  // 运行脚本 -- 等待结果
  ipcMain.handle("runScript", async (event, data) => {
    return new Promise((resolve, reject) => {
      try {
        const userDataPath = app.getPath("userData");
        const configPath = path.join(userDataPath, "scripts", "config.json");
        // 读取 config.json
        if (!fs.existsSync(configPath)) {
          throw new Error(
            "The config.json file does not exist. Please initialize the scripts folder first"
          );
        }
        
        const configContent = fs.readFileSync(configPath, "utf-8");
        const config = JSON.parse(configContent);

        // 拼接命令(自动交由 shell 解析)
        const command = `${config.scriptPath} ${data.inputJsonPath}`;
        
        // 关键:shell 由系统决定
        exec(command, { shell: true }, (error, stdout, stderr) => {
          if (error) {
            console.error("Script execution failed 103:", error);
            reject(stderr || stdout || "脚本运行异常");
            return;
          }
          resolve(stdout);
        });
      } catch (err) {
        reject(err.message);
      }
    });
  });
}

function runScriptNoWait(ipcMain, app, path, fs, spawn) {
  // 运行脚本 -- 不等待结果
  ipcMain.handle("runScriptNoWait", async (event, data) => {
    return new Promise((resolve, reject) => {
      try {
        const userDataPath = app.getPath("userData");
        const configPath = path.join(userDataPath, "scripts", "config.json");
        // 读取 config.json
        if (!fs.existsSync(configPath)) {
          throw new Error(
            "The config.json file does not exist. Please initialize the scripts folder first."
          );
        }
        
        const configContent = fs.readFileSync(configPath, "utf-8");
        const config = JSON.parse(configContent);

        // spawn 后台执行
        const child = spawn(config.scriptPath, [data.inputJsonPath], {
          shell: true, // 让系统选择 cmd/bash
          detached: true, // 让脚本成为独立进程
          stdio: "ignore" // 不接收任何输出
        });

        // 断开 Electron 与脚本的关系
        child.unref();

        // ***关键:不等待脚本执行结果***
        resolve("脚本已成功启动"); // 不等待 stdout,也不等待脚本结束
      } catch (err) {
        reject(err.message);
      }
    });
  });
}

module.exports = {
  runScript,
  runScriptNoWait
};

反正命令行拼出来长这样:

shell 复制代码
source /home/bnzycjd/.bashrc && /home/bnzycjd/miniconda3/bin/python  /home/bnzycjd/pipline_test/script/pipline.py -c /home/bnzycjd/test/input.json

其中

scriptPath 代表 source /home/bnzycjd/.bashrc && /home/bnzycjd/miniconda3/bin/python /home/bnzycjd/pipline_test/script/pipline.py -c

inputJsonPath代表/home/bnzycjd/test/input.json

这段代码在服务器上,用npm run dev跑或者复制到服务器的终端上运行,都啥问题没有,但一打包就会报错,而且报错还不好调试,真的让菜鸟难受至极!

增加日志

我把这个情况向leader说明后,因为他是后端,所以知道怎么把日志输出到文件夹,所以同步runScript代码变成了

js 复制代码
const command = `${config.scriptPath} ${data.inputJsonPath} >> ${data.logPath}/${data.logName}.log`;

异步的runScriptNoWait代码是AI帮助我写的,有点问题,需要调试并和AI沟通(改动了几次,这里就不一一列举了,直接展示最终可以运行的),变成了

js 复制代码
function runScriptNoWait(ipcMain, app, path, fs, spawn) {
  // 运行脚本 -- 不等待结果
  ipcMain.handle("runScriptNoWait", async (event, data) => {
    return new Promise((resolve, reject) => {
      try {
        const userDataPath = app.getPath("userData");
        const configPath = path.join(userDataPath, "scripts", "config.json");
        // 读取 config.json
        if (!fs.existsSync(configPath)) {
          throw new Error(
            "The config.json file does not exist. Please initialize the scripts folder first."
          );
        }
        const configContent = fs.readFileSync(configPath, "utf-8");
        const config = JSON.parse(configContent);

        const logFile = path.join(data.logPath, `${data.logName}.log`);
        const outFd = fs.openSync(logFile, "a");

        // spawn 后台执行
        const child = spawn(config.scriptPath, [data.inputJsonPath], {
          shell: true, // 让系统选择 cmd/bash
          detached: true, // 让脚本成为独立进程
          stdio: ["ignore", outFd, outFd] // stdout, stderr 都写日志
        });

        // 检查进程是否成功启动
        child.on("error", (error) => {
          console.error("后台脚本启动失败:", error.message);
          reject(`后台脚本启动失败: ${error.message}`);
        });

        // 进程成功启动
        child.on("spawn", () => {
          console.log("后台脚本已成功启动,进程ID:", child.pid);
        });

        // 断开 Electron 与脚本的关系
        child.unref();

        // ***关键:不等待脚本执行结果***
        resolve("脚本已成功启动"); // 不等待 stdout,也不等待脚本结束
      } catch (err) {
        reject(err.message);
      }
    });
  });
}

这样有日志了就好调试一点,但是也不可以成功调用脚本。

踩坑

这里菜鸟踩了好多坑,问AI的时候,如果自己也不知道如何解决,只能靠试!

甚至出现了这个离谱的解决方案,把shell:true,改成下面的

js 复制代码
{
  shell: "/bin/bash"
}

// spawn 后台执行
const child = spawn("/bin/bash", ["-lc", `${config.scriptPath} ${data.inputJsonPath}`], {
  detached: true, // 让脚本成为独立进程
  stdio: ["ignore", outFd, outFd]
});

这些完全就是无稽之谈。

主要还是菜鸟不懂命令行,其实要是懂的话,就知道source /home/bnzycjd/.bashrc其实根本不是运行脚本的命令,而是加载环境变量的作用!

菜鸟就一直以为必须加上source /home/bnzycjd/.bashrc才是完整命令 /(ㄒoㄒ)/~~

答案

踩了很多坑,总算是在AI那边得到了答案!

答案是:

不管是用npm run dev跑或者复制到服务器的终端上运行,都相当于有终端上的环境信息,但是打包后的程序是没有这些的!

如何解决?

这里菜鸟是用二解决的(感觉一和二差不多),三不是很懂,这个项目比较简单、比较急,就没有用这个专业的解决办法,有懂的读者,可以指点江山,激扬文字!

知道了问题,直接就是让GPT帮我们直接用二的方式解决,并提供可用代码,这里也是要和AI交互很久(就不一 一列举了),最终代码:

js 复制代码
function buildPythonEnv(pythonPath, environmentVar) {
  const path = require("path");

  const condaRoot = path.dirname(path.dirname(pythonPath));

  return {
    ...process.env,

    PATH: `${environmentVar}${condaRoot}/bin:/usr/bin:/bin`,
    CONDA_PREFIX: condaRoot,
    LD_LIBRARY_PATH: `${condaRoot}/lib`,
    PYTHONHOME: condaRoot,

    HOME: process.env.HOME,
    USER: process.env.USER
  };
}

function runScript(ipcMain, app, path, fs, spawn) {
  // 运行脚本 -- 等待结果
  ipcMain.handle("runScript", async (event, data) => {
    return new Promise((resolve, reject) => {
      try {
        const userDataPath = app.getPath("userData");
        const configPath = path.join(userDataPath, "scripts", "config.json");
        // 读取 config.json
        if (!fs.existsSync(configPath)) {
          throw new Error(
            "The config.json file does not exist. Please initialize the scripts folder first"
          );
        }

        const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
        const { pythonPath, scriptPath, environmentVar } = config;

        if (!pythonPath || !scriptPath || !environmentVar) {
          throw new Error("config.json 缺少 pythonPath 或 scriptPath 或 environmentVar");
        }

        const env = buildPythonEnv(pythonPath, environmentVar);

        const logFile = path.join(data.logPath, `${data.logName}.log`);
        const outFd = fs.openSync(logFile, "a");

        const child = spawn(pythonPath, [scriptPath, "-c", data.inputJsonPath], {
          env,
          shell: true,
          stdio: ["ignore", outFd, outFd]
        });

        child.on("error", (err) => {
          reject(`bash 启动失败: ${err.message}`);
        });

        child.on("close", (code) => {
          if (code !== 0) {
            reject(`脚本执行失败,退出码 ${code}`);
          } else {
            resolve("脚本执行完成");
          }
        });
      } catch (err) {
        reject(err.message);
      }
    });
  });
}

function runScriptNoWait(ipcMain, app, path, fs, spawn) {
  // 运行脚本 -- 不等待结果
  ipcMain.handle("runScriptNoWait", async (event, data) => {
    return new Promise((resolve, reject) => {
      try {
        const userDataPath = app.getPath("userData");
        const configPath = path.join(userDataPath, "scripts", "config.json");
        // 读取 config.json
        if (!fs.existsSync(configPath)) {
          throw new Error(
            "The config.json file does not exist. Please initialize the scripts folder first."
          );
        }

        const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
        const { pythonPath, scriptPath, environmentVar } = config;

        if (!pythonPath || !scriptPath || !environmentVar) {
          throw new Error("config.json 缺少 pythonPath 或 scriptPath 或 environmentVar");
        }

        const env = buildPythonEnv(pythonPath, environmentVar);

        const logFile = path.join(data.logPath, `${data.logName}.log`);
        const outFd = fs.openSync(logFile, "a");

        const child = spawn(pythonPath, [scriptPath, "-c", data.inputJsonPath], {
          env,
          shell: true,
          detached: true, // 让脚本成为独立进程
          stdio: ["ignore", outFd, outFd]
        });

        child.on("error", (err) => {
          reject(`后台脚本启动失败: ${err.message}`);
        });

        // 断开 Electron 与脚本的关系
        child.unref();

        // 关键:不等待脚本执行结果
        resolve("脚本已在后台启动");
      } catch (err) {
        reject(err.message);
      }
    });
  });
}

module.exports = {
  runScript,
  runScriptNoWait
};

这里就是配置文件需要编辑的要多一些,如图

希望这些坑可以帮助到大家,虽然是AI辅助搞定的,但是也花了不少时间,如果能刷到菜鸟的文章,就可以省下不少时间 o( ̄▽ ̄)ブ

相关推荐
未来龙皇小蓝3 分钟前
RBAC前端架构-01:项目初始化
前端·架构
程序员agions11 分钟前
2026年,微前端终于“死“了
前端·状态模式
万岳科技系统开发11 分钟前
食堂采购系统源码库存扣减算法与并发控制实现详解
java·前端·数据库·算法
程序员猫哥_19 分钟前
HTML 生成网页工具推荐:从手写代码到 AI 自动生成网页的进化路径
前端·人工智能·html
龙飞0520 分钟前
Systemd -systemctl - journalctl 速查表:服务管理 + 日志排障
linux·运维·前端·chrome·systemctl·journalctl
我爱加班、、25 分钟前
Websocket能携带token过去后端吗
前端·后端·websocket
AAA阿giao25 分钟前
从零拆解一个 React + TypeScript 的 TodoList:模块化、数据流与工程实践
前端·react.js·ui·typescript·前端框架
杨超越luckly31 分钟前
HTML应用指南:利用GET请求获取中国500强企业名单,揭秘企业增长、分化与转型的新常态
前端·数据库·html·可视化·中国500强
hedley(●'◡'●)1 小时前
基于cesium和vue的大疆司空模仿程序
前端·javascript·vue.js·python·typescript·无人机
qq5_8115175151 小时前
web城乡居民基本医疗信息管理系统信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】
前端·vue.js·spring boot