You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel中PHP执行NodeJS脚本生产环境找不到node_modules问题排查

问题排查与修复方案

你遇到的Undefined array key 0报错本质是PHP调用Node脚本时$output为空数组,根本没拿到JS侧的标准输出,和你猜测的依赖缺失相关性不大,核心问题集中在命令执行逻辑、JS异步时序、生产环境权限三个方面,按以下步骤修复即可:

1. 修复PHP侧命令执行逻辑,补全错误捕获

你当前的exec调用有三个硬伤:没有指定Node的绝对路径(生产环境Web运行用户的PATH变量通常不包含Node路径)、没有将标准错误重定向到标准输出(Node侧的报错全进了stderr,$output拿不到任何内容)、没有对执行结果做空判断。
替换executeNodeFile方法代码如下:

private function executeNodeFile($javascript_file_name) {
    // 先在生产服务器执行`which node`拿到绝对路径替换此处,常见值如/usr/bin/node、/usr/local/bin/node
    $nodeBinPath = '/usr/bin/node';
    $workDir = realpath(__DIR__ . '/../../asset-uploader');
    // escapeshellarg处理路径避免特殊字符问题,2>&1将所有错误输出合并到标准输出
    $execCmd = sprintf(
        "cd %s && %s %s 2>&1",
        escapeshellarg($workDir),
        $nodeBinPath,
        escapeshellarg($javascript_file_name)
    );
    exec($execCmd, $output, $exitCode);
    
    // 非0退出码说明执行失败,直接抛异常带出错误信息方便排查
    if ($exitCode !== 0) {
        throw new \RuntimeException(
            "GIF处理脚本执行失败,退出码{$exitCode},错误信息:" . implode(PHP_EOL, $output)
        );
    }
    return $output;
}

2. 修复JS侧异步逻辑bug,避免进程提前退出

你写的dissectGif.js存在严重的异步时序问题:forEach内的async回调不会被外层await、fs.readdir使用回调风格没有等待执行完成,本地能跑是因为开发环境IO速度快刚好碰对了时序,生产环境IO延迟稍高就会出现Node进程还没执行到console.log就提前退出的情况,自然没有输出。
替换JS代码如下:

const fs = require('fs/promises');
const path = require('path');
const gifFrames = require('gif-frames');

async function startApp() {
    try {
        // 读取待处理GIF
        const gifDirPath = './gif';
        const gifFileList = await fs.readdir(gifDirPath);
        const targetGifName = gifFileList[0];
        if (!targetGifName) {
            throw new Error('待处理GIF目录为空');
        }

        // 执行拆帧
        const frameList = await gifFrames({
            url: path.join(gifDirPath, targetGifName),
            frames: 'all',
            outputType: 'png',
            cumulative: true
        });

        // 等待所有帧写入磁盘,禁止用forEach处理异步
        for (const frame of frameList) {
            await new Promise((resolve, reject) => {
                const writeStream = fs.createWriteStream(`./gif-frames/${frame.frameIndex}.png`);
                frame.getImage().pipe(writeStream);
                writeStream.on('finish', resolve);
                writeStream.on('error', reject);
            });
        }

        // 所有帧写完再统计数量输出
        const frameFileList = await fs.readdir('./gif-frames');
        console.log(frameFileList.length);
        process.exit(0);
    } catch (err) {
        console.error('GIF拆帧失败:', err.message);
        process.exit(1);
    }
}

startApp();

3. 生产环境权限校验

  • 先切换到Web服务运行用户(常见为www-data、nginx、apache),手动进入asset-uploader目录执行node dissectGif.js,如果手动执行都无法正常输出帧数量,先解决手动执行的问题,不用调接口试
  • 确保Web运行用户对asset-uploader、gif、gif-frames三个目录有读写执行权限,不要用root用户上传代码、安装依赖,避免权限归属问题导致Node无法读写文件
  • 安装npm依赖时同样切换到Web运行用户执行npm install --production,避免依赖包权限问题

按以上步骤改完后,$output[0]就能正常拿到帧数量,不会再出现数组键不存在的报错。

内容的提问来源于stack exchange,提问作者Musa Assaf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 20:31:11