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
相关产品推荐
相关产品推荐

