隐藏

关于使用pkg打包node项目为exe可执行文件,内含命令行执行代码不可执行问题定位说明

发布:2023/11/12 13:15:43作者:管理员 来源:本站 浏览次数:487

近期有朋友请教我一个问题,为何他将node项目打包成exe文件之后,他的项目就运行不正常了,也就是需要播放一段音频。他的代码如下

项目工程代码查看


const http = require("http");

const sound = require("sound-play");

const path = require("path");

const fs = require("fs");

const filePath = path.join(__dirname, "./dist/alert.mp3");


const server = http.createServer((req, res) => {

   res.writeHead(200, { "Content-Type": "text/plain;charset=utf-8" });

   res.end("测试音频播放\n");

   sound.play(filePath);

});


server.listen(8000, () => {

   console.log("Server running at http://localhost:8000/");

});


 


这段代码我们看下来也没啥问题,无非就是起了一个端口为8000的http服务,当用户访问这个服务接口时,就会发送消息给客户端,同时后端进行音频播放操作。


在然后我们看下pkg 打包成可执行exe 命令有没有问题,通过查看 package.json,我们看下完整配置


{

 "name": "alert",

 "bin": "./index.js",

 "scripts": {

   "pkg": "pkg . -d -t node16-win-x64   -o",

   "start": "node index.js"

 },

 "pkg": {

   "assets": [

     "dist/**/*"

   ]

 },

 "dependencies": {

   "sound-play": "^1.1.0"

 }

}


 


大致从这里我们也看到了 pkg 命令参数,是以 node16-win-x64 为目标环境编译的exe 文件,但是很神奇,看似没问题的地方,运行起来,音频就是没有播放。

sound-play 库源码分析(注意这里是我添加了日志,方便定位的)


const { exec } = require("child_process");

const fs = require("fs");


const execPromise = require("util").promisify(exec);


/* MAC PLAY COMMAND */

const macPlayCommand = (path, volume) => `afplay \"${path}\" -v ${volume}`;


/* WINDOW PLAY COMMANDS */

const addPresentationCore = `Add-Type -AssemblyName presentationCore;`;

const createMediaPlayer = `$player = New-Object system.windows.media.mediaplayer;`;

const loadAudioFile = (path) => `$player.open('${path}');`;

const playAudio = `$player.Play();`;

const stopAudio = `Start-Sleep 1; Start-Sleep -s $player.NaturalDuration.TimeSpan.TotalSeconds;Exit;`;


const windowPlayCommand = (path, volume) =>

   `powershell -c ${addPresentationCore} ${createMediaPlayer} ${loadAudioFile(

       path

   )} $player.Volume = ${volume}; ${playAudio} ${stopAudio}`;


module.exports = {

   play: async (path, volume = 0.5) => {

       /**

        * Window: mediaplayer's volume is from 0 to 1, default is 0.5

        * Mac: afplay's volume is from 0 to 255, default is 1. However, volume > 2 usually result in distortion.

        * Therefore, it is better to limit the volume on Mac, and set a common scale of 0 to 1 for simplicity

        */

       try {

           const volumeAdjustedByOS =

               process.platform === "darwin"

                   ? Math.min(2, volume * 2)

                   : volume;


           const playCommand =

               process.platform === "darwin"

                   ? macPlayCommand(path, volumeAdjustedByOS)

                   : windowPlayCommand(path, volumeAdjustedByOS);

           fs.appendFile(

               "./log.txt",

               `${new Date().toLocaleString()},${playCommand}\n`,

               (error) => {}

           );

           fs.appendFile(

               "./log.txt",

               `${new Date().toLocaleString()},开始音频播放\n`,

               (error) => {}

           );


           await execPromise(playCommand);

       } catch (err) {

           fs.appendFile(

               "./log.txt",

               `${new Date().toLocaleString()},${err}\n`,

               (error) => {}

           );

           throw err;

       }

   },

};


 


从上述代码,我们知道这里的核心原理便是利用powershell 调用系统的播放器进行音频的播放,这里我们就知道不能调用的原因了,因为一个exe文件调用powershell 是有安全策略问题的, 自然不能正常调用。

转换思路,既然这个行不通,那我们最简单的方式,可以写bat脚本呀,也是双击操作


start cmd /k "cd  node完整工程目录 && npm run start"



以上就是一段简单的分析了,其实分析过程中,日志的打印也是蛮关键的,我在上述排除问题中,其实除了发现是powershell 无法调用,还有一个就是当我们打包成exe可执行文件之后,那么__dirname 获取到的路径是虚拟的,这也导致即使我们放开了安全策略,也会无法调用,因为虚拟路径在powershell 中是访问不到的。这个时候我们可以采用相对路径的方式 即把 path.join(__dirname, "./dist/alert.mp3") 方法 改成path.resolve("./dist/alert.mp3");相对路径即可。