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

Electron+electron-builder打包Laravel后无法自动启动本地服务

Electron+Laravel打包后无法自动启动PHP服务的问题修复

问题描述

我用electron-builder配置了Electron+Laravel项目,编写了main.cjs并完成package.json配置,执行npm run electron-build生成headman-system Setup 1.0.0.exe安装包。本地首次安装后应用可正常运行,但电脑关机重启后,或在其他电脑安装该程序时,启动应用会停留在loading.html,无法自动启动localhost服务加载项目内容。只有回到开发项目执行npm run electron或npm run start后,已安装的应用快捷方式才能正常加载内容,多次尝试修复均无法解决自动启动localhost的问题。

相关代码

main.cjs

const { app, BrowserWindow } = require("electron");
const path = require("path");
const { exec } = require("child_process");
const AutoLaunch = require("auto-launch");

const PORT = 50505; // 自定义本地端口

let phpServer;

function createWindow() {
    const win = new BrowserWindow({
        width: 800,
        height: 600,
        icon: path.join(app.getAppPath(), "public/assets/img/1.png"),
        webPreferences: {
            nodeIntegration: true,
            contextIsolation: false,
        },
    });

    win.setMenuBarVisibility(false);
    win.loadFile("loading.html");

    const checkServer = () => {
        fetch(`http://localhost:${PORT}`)
            .then(() => {
                win.loadURL(`http://localhost:${PORT}`); // 服务器就绪后加载主内容
            })
            .catch(() => {
                setTimeout(checkServer, 1000); // 未就绪则1秒后重试
            });
    };

    checkServer(); // 启动服务器状态检测
}

function startServices() {
    const phpPath = path.join(app.getAppPath(), "php", "php.exe");
    const publicPath = path.join(app.getAppPath(), "public");

    console.log(`PHP路径: ${phpPath}`);
    console.log(`Public路径: ${publicPath}`);

    phpServer = exec(
        `${phpPath} -S localhost:${PORT} -t ${publicPath}`,
        (error, stdout, stderr) => {
            if (error) {
                console.error(`执行错误: ${error}`);
                return;
            }
            console.log(`标准输出: ${stdout}`);
            console.error(`错误输出: ${stderr}`);
        }
    );

    phpServer.stdout.on("data", (data) => {
        console.log(`PHP服务器日志: ${data}`);
    });

    phpServer.stderr.on("data", (data) => {
        console.error(`PHP服务器错误: ${data}`);
    });

    phpServer.on("close", (code) => {
        console.log(`PHP服务器退出,代码: ${code}`);
    });
}

app.whenReady().then(() => {
    startServices();
    createWindow();

    // 配置开机自启
    const autoLauncher = new AutoLaunch({
        name: "Headman",
        path: app.getPath("exe"),
    });

    autoLauncher.isEnabled().then((isEnabled) => {
        if (!isEnabled) autoLauncher.enable();
    }).catch((err) => {
        console.error(err);
    });

    app.on("activate", () => {
        if (BrowserWindow.getAllWindows().length === 0) {
            createWindow();
        }
    });
});

app.on("window-all-closed", () => {
    if (process.platform !== "darwin") {
        if (phpServer) phpServer.kill();
        app.quit();
    }
});

package.json

{
    "private": true,
    "type": "module",
    "scripts": {
        "dev": "vite",
        "build": "vite build",
        "electron": "electron .",
        "electron-build": "electron-builder"
    },
    "devDependencies": {
        "@popperjs/core": "^2.11.6",
        "axios": "^1.6.4",
        "bootstrap": "^5.2.3",
        "electron": "^31.2.1",
        "electron-builder": "^24.13.3",
        "laravel-vite-plugin": "^1.0.0",
        "sass": "^1.56.1",
        "vite": "^5.0.0"
    },
    "name": "headman-system",
    "version": "1.0.0",
    "description": "<p align=\"center\"><a href=\"https://laravel.com\" target=\"_blank\"><img src=\"https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg\" width=\"400\" alt=\"Laravel Logo\"></a></p>",
    "main": "electron/main.cjs",
    "build": {
        "appId": "com.headman.id",
        "directories": {
            "output": "dist"
        },
        "files": [
            "app/**/*",
            "bootstrap/**/*",
            "config/**/*",
            "database/**/*",
            "electron/**/*",
            "node_modules/**/*",
            "php/**/*",
            "public/**/*",
            "resources/**/*",
            "routes/**/*",
            "storage/**/*",
            "vendor/**/*",
            ".editorconfig",
            ".env",
            "artisan",
            "composer.json",
            "composer.lock",
            "loading.html",
            "package-lock.json",
            "package.json",
            "phpunit.xml",
            "vite.config.js"
          
        ],
        "win": {
            "icon": "public\\assets\\img\\icon.ico"
        },
        "nsis": {
            "oneClick": false,
            "perMachine": false,
            "allowToChangeInstallationDirectory": true,
            "createDesktopShortcut": true,
            "createStartMenuShortcut": true,
            "shortcutName": "Headman"
        },
        "extraResources": [
            {
                "from": "electron",
                "to": "app",
                "filter": [
                    "**/*"
                ]
            }
        ]
    },
    "dependencies": {
        "anymatch": "^3.1.3",
        "asynckit": "^0.4.0",
        "auto-launch": "^5.0.6",
        "binary-extensions": "^2.3.0",
        "braces": "^3.0.3",
        "chokidar": "^3.6.0",
        "combined-stream": "^1.0.8",
        "delayed-stream": "^1.0.0",
        "esbuild": "^0.21.5",
        "fill-range": "^7.1.1",
        "follow-redirects": "^1.15.6",
        "form-data": "^4.0.0",
        "glob-parent": "^5.1.2",
        "immutable": "^4.3.6",
        "is-binary-path": "^2.1.0",
        "is-extglob": "^2.1.1",
        "is-glob": "^4.0.3",
        "is-number": "^7.0.0",
        "mime-db": "^1.52.0",
        "mime-types": "^2.1.35",
        "nanoid": "^3.3.7",
        "normalize-path": "^3.0.0",
        "picocolors": "^1.0.1",
        "picomatch": "^2.3.1",
        "postcss": "^8.4.39",
        "proxy-from-env": "^1.1.0",
        "readdirp": "^3.6.0",
        "rollup": "^4.18.1",
        "source-map-js": "^1.2.0",
        "to-regex-range": "^5.0.1",
        "vite-plugin-full-reload": "^1.2.0"
    },
    "keywords": [],
    "author": "suli core team",
    "license": "ISC"
}

问题原因分析

  1. 路径不匹配:开发环境与打包后环境的app.getAppPath()指向不同,导致PHP执行文件或Laravel public目录路径错误,无法启动PHP内置服务器。
  2. PHP依赖缺失:打包时未将PHP所需的DLL文件或扩展完全包含,导致PHP进程启动失败。
  3. 权限不足:程序启动PHP子进程时没有足够权限,尤其是重启后或在非开发环境的电脑上。
  4. 启动逻辑缺陷:服务启动与状态检测的顺序或错误处理不完善,无法及时发现服务启动失败的情况。

修复步骤

1. 修正路径获取逻辑

区分开发与生产环境的路径,确保打包后能正确找到PHP和public目录:

function startServices() {
    const isDev = !app.isPackaged;
    // 打包后资源目录在process.resourcesPath下的app文件夹
    const basePath = isDev ? app.getAppPath() : path.join(process.resourcesPath, 'app');
    
    const phpPath = path.join(basePath, "php", "php.exe");
    const publicPath = path.join(basePath, "public");

    // 检查PHP文件是否存在
    const fs = require('fs');
    if (!fs.existsSync(phpPath)) {
        console.error(`PHP可执行文件不存在:${phpPath}`);
        // 可选:弹出错误提示窗口告知用户
        return;
    }

    // 给路径加引号,避免路径中有空格导致执行失败
    phpServer = exec(
        `"${phpPath}" -S localhost:${PORT} -t "${publicPath}"`,
        (error, stdout, stderr) => {
            if (error) {
                console.error(`启动错误:${error.message}`);
                return;
            }
            console.log(`输出:${stdout}`);
            console.error(`错误输出:${stderr}`);
        }
    );

    // 其余代码保持不变
}

2. 确保PHP目录完整打包

检查package.json的files配置,确认"php/**/*"包含了PHP的所有文件,包括php.exe、php.ini以及ext目录下的所有DLL扩展文件,这些是PHP启动必需的。

3. 提升程序权限

在package.json的nsis配置中添加权限请求,确保程序能启动子进程:

"nsis": {
    "oneClick": false,
    "perMachine": false,
    "allowToChangeInstallationDirectory": true,
    "createDesktopShortcut": true,
    "createStartMenuShortcut": true,
    "shortcutName": "Headman",
    "requestExecutionLevel": "highestAvailable" // 请求最高权限
}

4. 优化服务检测逻辑

增强服务检测的错误处理,便于排查问题:

const checkServer = () => {
    fetch(`http://localhost:${PORT}`)
        .then(response => {
            if (response.ok) {
                win.loadURL(`http://localhost:${PORT}`);
            } else {
                console.log(`服务器响应异常,状态码:${response.status}`);
                setTimeout(checkServer, 1000);
            }
        })
        .catch(err => {
            console.log(`服务器未就绪:${err.message}`);
            setTimeout(checkServer, 1000);
        });
};

5. 验证打包后的文件结构

安装打包后的程序,打开安装目录下的resources/app文件夹,确认:

  • php/php.exe存在且可执行
  • public目录包含Laravel编译后的静态资源
  • 所有依赖的PHP扩展DLL文件都在php/ext目录下

内容的提问来源于stack exchange,提问作者Mister zana

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 20:57:05