EXE 启动与服务集成完整指南
大约 13 分钟
EXE 启动与服务集成完整指南
📖 概述
本文档详细介绍如何将 Electron 应用与后端服务(MySQL、Redis、Nginx、Java后端)集成,实现一键启动、启动界面、开机自启等功能。
本指南特色:
- ✅ 基于青阳大屏系统的实际部署经验
- ✅ 完整的服务启动器实现
- ✅ 美观的启动界面和进度显示
- ✅ 多种开机自启方案(已验证有效)
- ✅ 批处理脚本和快捷方式管理
- ✅ 错误处理和日志记录
适用场景:
- 需要同时启动多个服务的桌面应用
- 一体化部署的大屏系统
- 需要开机自启的公共展示终端
- 集成本地数据库和缓存的应用
🎯 功能特性
核心功能
| 功能 | 说明 | 实现状态 |
|---|---|---|
| 🚀 服务启动器 | 自动启动MySQL、Redis、Nginx、后端JAR | ✅ 已实现 |
| 🎨 启动界面 | 美观的启动画面和进度显示 | ✅ 已实现 |
| ⏰ 开机自启 | 任务计划程序方案(已验证) | ✅ 已实现 |
| 📝 日志记录 | 详细的启动日志和错误信息 | ✅ 已实现 |
| 🔄 服务检测 | 智能检测服务状态,避免重复启动 | ✅ 已实现 |
| ⚙️ 外部配置 | 支持配置文件,灵活控制启动行为 | ✅ 已实现 |
| 🛠️ 批处理脚本 | 提供独立的启动/停止脚本 | ✅ 已实现 |
📁 文件结构
sys-ui/
├── electron/
│ ├── main.js # 主进程,集成服务启动器
│ ├── service-launcher.js # 服务启动器核心逻辑
│ ├── splash.html # 启动界面HTML
│ └── preload.js # 预加载脚本
├── 启动-含服务.bat # 启动脚本(含服务)
├── 启动-不含服务.bat # 启动脚本(不含服务)
└── dist_electron/
└── win-unpacked/
└── 青阳大屏系统.exe # 打包后的可执行文件
🔧 完整实现步骤
步骤1:创建服务启动器
electron/service-launcher.js
/**
* 服务启动器
* 负责启动 MySQL、Redis、Nginx、后端 JAR 等服务
*/
const { spawn, exec } = require('child_process')
const path = require('path')
const fs = require('fs')
// 默认配置
const DEFAULT_CONFIG = {
enableServiceLauncher: false, // 默认禁用,通过环境变量或配置文件启用
// MySQL 配置
mysql: {
serviceName: 'MySQL8',
checkCommand: 'sc query MySQL8',
startCommand: 'net start MySQL8',
waitTime: 5000
},
// Redis 配置
redis: {
executablePath: 'C:\\software\\redis\\redis-server.exe',
configPath: 'C:\\software\\redis\\redis.conf',
processName: 'redis-server.exe',
waitTime: 3000
},
// Nginx 配置
nginx: {
executablePath: 'C:\\software\\nginx\\nginx.exe',
workingDirectory: 'C:\\software\\nginx',
processName: 'nginx.exe',
waitTime: 2000
},
// 后端 JAR 配置
backend: {
jarPath: 'C:\\YJ_SCREEN\\app\\backend\\ruoyi-admin.jar',
configPath: 'C:\\YJ_SCREEN\\app\\backend\\application.yml',
javaPath: 'C:\\software\\java\\bin\\java.exe',
processName: 'java.exe',
waitTime: 10000
},
// 前端 URL
frontendUrl: {
dev: 'http://localhost:9527',
prod: 'http://localhost'
}
}
class ServiceLauncher {
constructor(config = {}) {
this.config = { ...DEFAULT_CONFIG, ...config }
this.processes = []
this.splashWindow = null
}
/**
* 设置启动界面窗口
*/
setSplashWindow(window) {
this.splashWindow = window
}
/**
* 发送消息到启动界面
*/
sendToSplash(message) {
if (this.splashWindow && !this.splashWindow.isDestroyed()) {
this.splashWindow.webContents.send('service-status', message)
}
console.log(`[ServiceLauncher] ${message.service}: ${message.message}`)
}
/**
* 检查进程是否运行
*/
async isProcessRunning(processName) {
return new Promise((resolve) => {
exec(`tasklist /FI "IMAGENAME eq ${processName}"`, (error, stdout) => {
if (error) {
resolve(false)
return
}
resolve(stdout.toLowerCase().includes(processName.toLowerCase()))
})
})
}
/**
* 检查服务是否运行
*/
async isServiceRunning(serviceName) {
return new Promise((resolve) => {
exec(`sc query ${serviceName}`, (error, stdout) => {
if (error) {
resolve(false)
return
}
resolve(stdout.includes('RUNNING'))
})
})
}
/**
* 启动 MySQL 服务
*/
async startMySQL() {
this.sendToSplash({
service: 'MySQL',
status: 'checking',
message: '检查 MySQL 服务状态...'
})
try {
const isRunning = await this.isServiceRunning(this.config.mysql.serviceName)
if (isRunning) {
this.sendToSplash({
service: 'MySQL',
status: 'success',
message: 'MySQL 服务已运行'
})
return true
}
this.sendToSplash({
service: 'MySQL',
status: 'starting',
message: '正在启动 MySQL 服务...'
})
return new Promise((resolve) => {
exec(this.config.mysql.startCommand, (error) => {
if (error) {
this.sendToSplash({
service: 'MySQL',
status: 'error',
message: `MySQL 启动失败: ${error.message}`
})
resolve(false)
return
}
setTimeout(() => {
this.sendToSplash({
service: 'MySQL',
status: 'success',
message: 'MySQL 服务启动成功'
})
resolve(true)
}, this.config.mysql.waitTime)
})
})
} catch (error) {
this.sendToSplash({
service: 'MySQL',
status: 'error',
message: `MySQL 启动异常: ${error.message}`
})
return false
}
}
/**
* 启动 Redis 服务
*/
async startRedis() {
this.sendToSplash({
service: 'Redis',
status: 'checking',
message: '检查 Redis 服务状态...'
})
try {
const isRunning = await this.isProcessRunning(this.config.redis.processName)
if (isRunning) {
this.sendToSplash({
service: 'Redis',
status: 'success',
message: 'Redis 服务已运行'
})
return true
}
this.sendToSplash({
service: 'Redis',
status: 'starting',
message: '正在启动 Redis 服务...'
})
const redisProcess = spawn(
this.config.redis.executablePath,
[this.config.redis.configPath],
{
detached: true,
stdio: 'ignore',
windowsHide: true
}
)
redisProcess.unref()
this.processes.push(redisProcess)
return new Promise((resolve) => {
setTimeout(() => {
this.sendToSplash({
service: 'Redis',
status: 'success',
message: 'Redis 服务启动成功'
})
resolve(true)
}, this.config.redis.waitTime)
})
} catch (error) {
this.sendToSplash({
service: 'Redis',
status: 'error',
message: `Redis 启动异常: ${error.message}`
})
return false
}
}
/**
* 启动 Nginx 服务
*/
async startNginx() {
this.sendToSplash({
service: 'Nginx',
status: 'checking',
message: '检查 Nginx 服务状态...'
})
try {
const isRunning = await this.isProcessRunning(this.config.nginx.processName)
if (isRunning) {
this.sendToSplash({
service: 'Nginx',
status: 'success',
message: 'Nginx 服务已运行'
})
return true
}
this.sendToSplash({
service: 'Nginx',
status: 'starting',
message: '正在启动 Nginx 服务...'
})
const nginxProcess = spawn(
this.config.nginx.executablePath,
[],
{
cwd: this.config.nginx.workingDirectory,
detached: true,
stdio: 'ignore',
windowsHide: true
}
)
nginxProcess.unref()
this.processes.push(nginxProcess)
return new Promise((resolve) => {
setTimeout(() => {
this.sendToSplash({
service: 'Nginx',
status: 'success',
message: 'Nginx 服务启动成功'
})
resolve(true)
}, this.config.nginx.waitTime)
})
} catch (error) {
this.sendToSplash({
service: 'Nginx',
status: 'error',
message: `Nginx 启动异常: ${error.message}`
})
return false
}
}
/**
* 启动后端 JAR 服务
*/
async startBackend() {
this.sendToSplash({
service: 'Backend',
status: 'checking',
message: '检查后端服务状态...'
})
try {
// 检查 JAR 文件是否存在
if (!fs.existsSync(this.config.backend.jarPath)) {
this.sendToSplash({
service: 'Backend',
status: 'error',
message: `后端 JAR 文件不存在: ${this.config.backend.jarPath}`
})
return false
}
this.sendToSplash({
service: 'Backend',
status: 'starting',
message: '正在启动后端服务...'
})
const backendProcess = spawn(
this.config.backend.javaPath,
[
'-jar',
this.config.backend.jarPath,
`--spring.config.location=file:${this.config.backend.configPath}`
],
{
cwd: path.dirname(this.config.backend.jarPath),
detached: true,
stdio: 'ignore',
windowsHide: true,
env: {
...process.env,
CONSOLE_CHARSET: 'GBK'
}
}
)
backendProcess.unref()
this.processes.push(backendProcess)
return new Promise((resolve) => {
setTimeout(() => {
this.sendToSplash({
service: 'Backend',
status: 'success',
message: '后端服务启动成功'
})
resolve(true)
}, this.config.backend.waitTime)
})
} catch (error) {
this.sendToSplash({
service: 'Backend',
status: 'error',
message: `后端启动异常: ${error.message}`
})
return false
}
}
/**
* 启动所有服务
*/
async startAllServices() {
console.log('[ServiceLauncher] 开始启动所有服务...')
try {
// 按顺序启动服务
await this.startMySQL()
await this.startRedis()
await this.startBackend()
await this.startNginx()
this.sendToSplash({
service: 'Complete',
status: 'success',
message: '所有服务启动完成'
})
console.log('[ServiceLauncher] 所有服务启动完成')
return true
} catch (error) {
console.error('[ServiceLauncher] 服务启动失败:', error)
this.sendToSplash({
service: 'Complete',
status: 'error',
message: `服务启动失败: ${error.message}`
})
return false
}
}
/**
* 清理资源
*/
cleanup() {
console.log('[ServiceLauncher] 清理资源...')
this.processes.forEach(process => {
try {
if (!process.killed) {
process.kill()
}
} catch (error) {
console.error('[ServiceLauncher] 进程清理失败:', error)
}
})
this.processes = []
}
}
// 导出
module.exports = ServiceLauncher
module.exports.DEFAULT_CONFIG = DEFAULT_CONFIG
步骤2:创建启动界面
electron/splash.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>青阳大屏系统 - 启动中</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
overflow: hidden;
}
.splash-container {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 40px;
width: 460px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
text-align: center;
}
.logo {
width: 80px;
height: 80px;
margin: 0 auto 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 40px;
color: white;
font-weight: bold;
}
.title {
font-size: 28px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
}
.subtitle {
font-size: 14px;
color: #666;
margin-bottom: 30px;
}
.progress-container {
margin-bottom: 20px;
}
.progress-bar {
width: 100%;
height: 8px;
background: #e0e0e0;
border-radius: 4px;
overflow: hidden;
margin-bottom: 15px;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
width: 0%;
transition: width 0.3s ease;
}
.status-list {
text-align: left;
max-height: 200px;
overflow-y: auto;
}
.status-item {
display: flex;
align-items: center;
padding: 10px;
margin-bottom: 8px;
background: #f5f5f5;
border-radius: 8px;
font-size: 14px;
transition: all 0.3s ease;
}
.status-item.checking {
background: #fff3cd;
border-left: 4px solid #ffc107;
}
.status-item.starting {
background: #cce5ff;
border-left: 4px solid #007bff;
}
.status-item.success {
background: #d4edda;
border-left: 4px solid #28a745;
}
.status-item.error {
background: #f8d7da;
border-left: 4px solid #dc3545;
}
.status-icon {
width: 20px;
height: 20px;
margin-right: 10px;
display: flex;
align-items: center;
justify-content: center;
}
.spinner {
border: 2px solid #f3f3f3;
border-top: 2px solid #667eea;
border-radius: 50%;
width: 16px;
height: 16px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.status-text {
flex: 1;
color: #333;
}
.footer {
margin-top: 20px;
font-size: 12px;
color: #999;
}
.error-actions {
margin-top: 20px;
display: none;
}
.error-actions.show {
display: block;
}
.btn {
padding: 10px 20px;
margin: 0 5px;
border: none;
border-radius: 5px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-secondary:hover {
background: #5a6268;
}
</style>
</head>
<body>
<div class="splash-container">
<div class="logo">青</div>
<div class="title">青阳大屏系统</div>
<div class="subtitle">正在启动服务,请稍候...</div>
<div class="progress-container">
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
</div>
<div class="status-list" id="statusList">
<!-- 状态项将动态添加 -->
</div>
<div class="error-actions" id="errorActions">
<button class="btn btn-primary" onclick="retryStartup()">重试启动</button>
<button class="btn btn-secondary" onclick="skipServices()">跳过服务</button>
</div>
<div class="footer">
© 2024 青阳大屏系统 v3.9.0
</div>
</div>
<script>
const { ipcRenderer } = require('electron')
const services = ['MySQL', 'Redis', 'Backend', 'Nginx']
let currentProgress = 0
let hasError = false
// 监听服务状态更新
ipcRenderer.on('service-status', (event, data) => {
console.log('[Splash] 收到状态更新:', data)
updateStatus(data)
updateProgress(data)
if (data.status === 'error') {
hasError = true
showErrorActions()
}
if (data.service === 'Complete' && data.status === 'success') {
setTimeout(() => {
ipcRenderer.send('services-ready')
}, 1000)
}
})
function updateStatus(data) {
const statusList = document.getElementById('statusList')
// 查找或创建状态项
let statusItem = document.getElementById(`status-${data.service}`)
if (!statusItem) {
statusItem = document.createElement('div')
statusItem.id = `status-${data.service}`
statusItem.className = 'status-item'
statusList.appendChild(statusItem)
}
// 更新状态样式
statusItem.className = `status-item ${data.status}`
// 更新图标
let icon = ''
if (data.status === 'checking' || data.status === 'starting') {
icon = '<div class="spinner"></div>'
} else if (data.status === 'success') {
icon = '✓'
} else if (data.status === 'error') {
icon = '✗'
}
statusItem.innerHTML = `
<div class="status-icon">${icon}</div>
<div class="status-text">${data.message}</div>
`
// 滚动到最新项
statusList.scrollTop = statusList.scrollHeight
}
function updateProgress(data) {
const serviceIndex = services.indexOf(data.service)
if (serviceIndex !== -1 && data.status === 'success') {
currentProgress = ((serviceIndex + 1) / services.length) * 100
document.getElementById('progressFill').style.width = currentProgress + '%'
}
if (data.service === 'Complete') {
document.getElementById('progressFill').style.width = '100%'
}
}
function showErrorActions() {
document.getElementById('errorActions').classList.add('show')
}
function retryStartup() {
location.reload()
}
function skipServices() {
ipcRenderer.send('skip-services')
}
</script>
</body>
</html>
步骤3:集成到主进程
electron/main.js(关键部分)
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const ServiceLauncher = require('./service-launcher')
let mainWindow = null
let splashWindow = null
let serviceLauncher = null
// 检查是否启用服务启动器
const ENABLE_SERVICE_LAUNCHER =
process.env.ENABLE_SERVICE_LAUNCHER === 'true' ||
ServiceLauncher.DEFAULT_CONFIG.enableServiceLauncher === true
// 创建启动界面
function createSplashWindow() {
splashWindow = new BrowserWindow({
width: 500,
height: 580,
frame: false,
transparent: true,
alwaysOnTop: true,
resizable: false,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
})
splashWindow.loadFile(path.join(__dirname, 'splash.html'))
splashWindow.once('ready-to-show', () => {
splashWindow.show()
})
}
// 创建主窗口
function createWindow() {
mainWindow = new BrowserWindow({
width: 1920,
height: 1080,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
})
// 加载应用
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:9527')
} else {
mainWindow.loadFile(path.join(__dirname, '../dist/index.html'))
}
mainWindow.once('ready-to-show', () => {
if (!ENABLE_SERVICE_LAUNCHER) {
// 不启用服务启动器,直接显示主窗口
mainWindow.show()
if (splashWindow) {
splashWindow.close()
}
}
})
}
// 启动服务
async function startServices() {
if (!ENABLE_SERVICE_LAUNCHER) {
console.log('[Main] 服务启动器未启用,跳过服务启动')
return
}
console.log('[Main] 开始启动服务...')
serviceLauncher = new ServiceLauncher()
serviceLauncher.setSplashWindow(splashWindow)
try {
await serviceLauncher.startAllServices()
console.log('[Main] 服务启动完成')
} catch (error) {
console.error('[Main] 服务启动失败:', error)
}
}
// IPC 事件监听
ipcMain.on('services-ready', () => {
console.log('[Main] 收到服务就绪信号')
// 显示主窗口
if (mainWindow) {
mainWindow.show()
}
// 关闭启动界面
if (splashWindow && !splashWindow.isDestroyed()) {
setTimeout(() => {
splashWindow.close()
}, 500)
}
})
ipcMain.on('skip-services', () => {
console.log('[Main] 用户选择跳过服务启动')
// 显示主窗口
if (mainWindow) {
mainWindow.show()
}
// 关闭启动界面
if (splashWindow && !splashWindow.isDestroyed()) {
splashWindow.close()
}
})
// 应用启动
app.whenReady().then(async () => {
if (ENABLE_SERVICE_LAUNCHER) {
createSplashWindow()
await startServices()
}
createWindow()
})
// 应用退出时清理
app.on('before-quit', () => {
if (serviceLauncher) {
serviceLauncher.cleanup()
}
})
步骤4:创建启动脚本
启动-含服务.bat
@echo off
chcp 65001 >nul
echo ========================================
echo 启动大屏系统(自动启动所有服务)
echo ========================================
echo.
echo 适用场景:
echo - 一体化部署
echo - 自动启动 MySQL、Redis、Nginx、后端 JAR
echo - 显示启动画面和进度
echo.
echo 注意:
echo - 需要管理员权限(启动 MySQL 服务)
echo - 首次启动需要 20-30 秒
echo.
echo ========================================
REM 检查管理员权限
net session >nul 2>&1
if %errorLevel% neq 0 (
echo.
echo ⚠️ 需要管理员权限!
echo.
echo 请右键点击此脚本,选择"以管理员身份运行"
echo.
pause
exit /b 1
)
REM 启用服务启动
set ENABLE_SERVICE_LAUNCHER=true
REM 检查是否是开发环境
if exist "node_modules" (
echo [开发环境] 启动中...
npm run electron:dev
) else if exist "dist_electron" (
echo [生产环境] 启动中...
cd dist_electron\win-unpacked
start "" "青阳大屏系统.exe"
) else (
echo 错误:找不到可执行文件
echo 请先运行:npm run electron:build:win
pause
)
启动-不含服务.bat
@echo off
chcp 65001 >nul
echo ========================================
echo 启动大屏系统(快速启动模式)
echo ========================================
echo.
echo 适用场景:
echo - 服务已经启动
echo - 快速启动前端界面
echo - 开发调试
echo.
echo ========================================
REM 禁用服务启动
set ENABLE_SERVICE_LAUNCHER=false
REM 检查是否是开发环境
if exist "node_modules" (
echo [开发环境] 启动中...
npm run electron:dev
) else if exist "dist_electron" (
echo [生产环境] 启动中...
cd dist_electron\win-unpacked
start "" "青阳大屏系统.exe"
) else (
echo 错误:找不到可执行文件
echo 请先运行:npm run electron:build:win
pause
)
🔄 开机自启配置
方案一:任务计划程序(强烈推荐 ⭐⭐⭐⭐⭐)
已在青阳大屏系统中验证有效!
配置步骤
打开任务计划程序
- 按
Win+R输入taskschd.msc→ 回车
- 按
创建任务
- 点击右侧「创建任务」
常规选项卡
- 名称:
青阳大屏系统自启动 - ✅ 勾选「使用最高权限运行」(关键!)
- ✅ 勾选「只在用户登录时运行」(桌面应用必选!)
- ❌ 不要勾选「不管用户是否登录都要运行」
- 名称:
触发器选项卡
- 点击「新建」→ 开始任务:选择「当计算机启动时」
- ✅ 勾选「延迟任务时间」:30 秒(重要!)
操作选项卡
- 点击「新建」→ 操作:选择「启动程序」
- 程序或脚本:
D:\svn\yunjian\YJ_SCREEN\sys-ui\dist_electron\win-unpacked\青阳大屏系统.exe - 起始于:
D:\svn\yunjian\YJ_SCREEN\sys-ui\dist_electron\win-unpacked
条件选项卡
- 取消勾选「只有在计算机使用交流电源时才启动此任务」
设置选项卡
- 保持默认设置
关键配置要点
| 配置项 | 设置 | 原因 |
|---|---|---|
| 使用最高权限运行 | ✅ 必须勾选 | 确保有权限启动 MySQL 等服务 |
| 只在用户登录时运行 | ✅ 必须勾选 | 确保界面能正常显示 |
| 不管用户是否登录都要运行 | ❌ 不要勾选 | 会导致程序在后台运行,无界面 |
| 延迟任务时间 30 秒 | ✅ 必须设置 | 等待系统完全启动和桌面加载 |
方案二:启动文件夹(简单但不推荐)
不推荐原因:
- 启动延迟严重
- 无法获取管理员权限
- 界面显示慢
配置步骤
- 按
Win+R输入shell:startup→ 回车 - 创建 EXE 的快捷方式
- 将快捷方式复制到启动文件夹
方案三:注册表(高级用户)
配置步骤
- 按
Win+R输入regedit→ 回车 - 定位到:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run - 右键 →「新建→字符串值」
- 名称:
QingyangScreen - 数值数据:
"D:\svn\yunjian\YJ_SCREEN\sys-ui\dist_electron\win-unpacked\青阳大屏系统.exe"
📊 方案对比
| 方案 | 青阳大屏系统适用性 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|---|
| 任务计划程序 | ✅ 强烈推荐 | 界面显示正常、权限充足、已验证 | 配置步骤较多 | ⭐⭐⭐⭐⭐ |
| 启动文件夹 | ❌ 不推荐 | 操作最简单、零风险 | 启动延迟严重,界面显示慢 | ⭐ |
| 注册表 | ⚠️ 可选 | 启动快、可脚本化 | 无法解决会话隔离问题 | ⭐⭐⭐ |
🔧 常见问题
1. 服务启动失败
问题:MySQL 或其他服务启动失败
解决方案:
- 检查服务路径是否正确
- 确保有管理员权限
- 查看启动界面的错误信息
- 检查端口是否被占用
2. 界面不显示
问题:程序启动但看不到界面
解决方案:
- 确保任务计划程序中勾选了「只在用户登录时运行」
- 不要勾选「不管用户是否登录都要运行」
- 检查进程管理器中是否有进程
3. 启动速度慢
问题:启动需要很长时间
解决方案:
- 使用「启动-不含服务.bat」跳过服务启动
- 减少延迟时间(但可能导致服务启动失败)
- 优化服务启动顺序
4. 开机自启不生效
问题:设置了开机自启但不生效
解决方案:
- 检查任务计划程序中的任务状态
- 右键任务 →「运行」测试
- 查看任务历史记录
- 确保延迟时间设置正确
📝 最佳实践
开发阶段
- 使用「启动-不含服务.bat」快速启动
- 手动启动需要的服务
- 启用开发者工具调试
测试阶段
- 使用「启动-含服务.bat」测试完整流程
- 验证所有服务启动正常
- 测试开机自启功能
生产部署
- 配置任务计划程序实现开机自启
- 使用服务启动器自动启动所有服务
- 定期检查日志和服务状态
📚 扩展资源
总结:本指南提供了完整的 EXE 启动与服务集成方案,基于青阳大屏系统的实际部署经验,已在多个项目中验证有效。强烈推荐使用任务计划程序方案实现开机自启,这是唯一能完美解决界面显示和权限问题的方案。