EXE 启动与服务集成完整指南

lishihuan大约 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
)

🔄 开机自启配置

方案一:任务计划程序(强烈推荐 ⭐⭐⭐⭐⭐)

已在青阳大屏系统中验证有效!

配置步骤

  1. 打开任务计划程序

    • Win+R 输入 taskschd.msc → 回车
  2. 创建任务

    • 点击右侧「创建任务」
  3. 常规选项卡

    • 名称:青阳大屏系统自启动
    • ✅ 勾选「使用最高权限运行」(关键!)
    • ✅ 勾选「只在用户登录时运行」(桌面应用必选!)
    • ❌ 不要勾选「不管用户是否登录都要运行」
  4. 触发器选项卡

    • 点击「新建」→ 开始任务:选择「当计算机启动时」
    • ✅ 勾选「延迟任务时间」:30 秒(重要!)
  5. 操作选项卡

    • 点击「新建」→ 操作:选择「启动程序」
    • 程序或脚本:D:\svn\yunjian\YJ_SCREEN\sys-ui\dist_electron\win-unpacked\青阳大屏系统.exe
    • 起始于:D:\svn\yunjian\YJ_SCREEN\sys-ui\dist_electron\win-unpacked
  6. 条件选项卡

    • 取消勾选「只有在计算机使用交流电源时才启动此任务」
  7. 设置选项卡

    • 保持默认设置

关键配置要点

配置项设置原因
使用最高权限运行✅ 必须勾选确保有权限启动 MySQL 等服务
只在用户登录时运行✅ 必须勾选确保界面能正常显示
不管用户是否登录都要运行❌ 不要勾选会导致程序在后台运行,无界面
延迟任务时间 30 秒✅ 必须设置等待系统完全启动和桌面加载

方案二:启动文件夹(简单但不推荐)

不推荐原因:

  • 启动延迟严重
  • 无法获取管理员权限
  • 界面显示慢

配置步骤

  1. Win+R 输入 shell:startup → 回车
  2. 创建 EXE 的快捷方式
  3. 将快捷方式复制到启动文件夹

方案三:注册表(高级用户)

配置步骤

  1. Win+R 输入 regedit → 回车
  2. 定位到:HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
  3. 右键 →「新建→字符串值」
  4. 名称:QingyangScreen
  5. 数值数据:"D:\svn\yunjian\YJ_SCREEN\sys-ui\dist_electron\win-unpacked\青阳大屏系统.exe"

📊 方案对比

方案青阳大屏系统适用性优点缺点推荐度
任务计划程序强烈推荐界面显示正常、权限充足、已验证配置步骤较多⭐⭐⭐⭐⭐
启动文件夹❌ 不推荐操作最简单、零风险启动延迟严重,界面显示慢
注册表⚠️ 可选启动快、可脚本化无法解决会话隔离问题⭐⭐⭐

🔧 常见问题

1. 服务启动失败

问题:MySQL 或其他服务启动失败

解决方案

  1. 检查服务路径是否正确
  2. 确保有管理员权限
  3. 查看启动界面的错误信息
  4. 检查端口是否被占用

2. 界面不显示

问题:程序启动但看不到界面

解决方案

  1. 确保任务计划程序中勾选了「只在用户登录时运行」
  2. 不要勾选「不管用户是否登录都要运行」
  3. 检查进程管理器中是否有进程

3. 启动速度慢

问题:启动需要很长时间

解决方案

  1. 使用「启动-不含服务.bat」跳过服务启动
  2. 减少延迟时间(但可能导致服务启动失败)
  3. 优化服务启动顺序

4. 开机自启不生效

问题:设置了开机自启但不生效

解决方案

  1. 检查任务计划程序中的任务状态
  2. 右键任务 →「运行」测试
  3. 查看任务历史记录
  4. 确保延迟时间设置正确

📝 最佳实践

开发阶段

  • 使用「启动-不含服务.bat」快速启动
  • 手动启动需要的服务
  • 启用开发者工具调试

测试阶段

  • 使用「启动-含服务.bat」测试完整流程
  • 验证所有服务启动正常
  • 测试开机自启功能

生产部署

  • 配置任务计划程序实现开机自启
  • 使用服务启动器自动启动所有服务
  • 定期检查日志和服务状态

📚 扩展资源


总结:本指南提供了完整的 EXE 启动与服务集成方案,基于青阳大屏系统的实际部署经验,已在多个项目中验证有效。强烈推荐使用任务计划程序方案实现开机自启,这是唯一能完美解决界面显示和权限问题的方案。