electron27+react18集成搭建跨平台应用|electron窗口多开

基于Electron27集成React18创建一个桌面端exe程序。

electron27-vite4-react18基于electron27结合vite4构建工具快速创建react18跨端应用实践。

在这里插入图片描述

版本列表

"vite": "^4.4.5"
"react": "^18.2.0"
"electron": "^27.0.1"
"electron-builder": "^24.6.4"
"vite-plugin-electron": "^0.14.1"

快速创建react18项目

这里选择使用vite.js构建工具来快速创建一个react18项目。

yarn create vite electron-vite4-react18
// 选择创建react模板
cd electron-vite4-react18
yarn install
yarn dev

在这里插入图片描述
这样一个简单的react18项目就已经创建完毕了。

安装electron依赖包

注意:electron依赖安装在 devDependencies 里面。

// 安装electron
yarn add -D electron
// 安装electron-builder 用于构建打包可安装exe程序
yarn add -D electron-builder
// 安装electron-devtools-installer 用于开发调试electron项目
yarn add -D electron-devtools-installer

安装的过程可能比较慢,甚至卡顿,可以考虑切换taobao镜像。

接下来还需要安装一个vite集成electron插件。

yarn add -D vite-plugin-electron

创建electron主进程

在项目根目录下新建一个主进程文件electron-main.js。

在这里插入图片描述

const { app, BrowserWindow } = require('electron')

const MultiWindow = require('./src/windows')

const createWindow = () => {
    let win = new MultiWindow()
    win.createWin({ isMainWin: true })
}

app.whenReady().then(() => {
    createWindow()
    app.on('activate', () => {
        if (BrowserWindow.getAllWindows().length === 0) createWindow()
    })
})

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

配置vite.config.js主进程入口。

import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react'
import electron from 'vite-plugin-electron'
import { resolve } from 'path'
import { parseEnv } from './src/utils/env'

// https://vitejs.dev/config/
export default defineConfig(({ command, mode }) => {
    const viteEnv = loadEnv(mode, process.cwd())
    const env = parseEnv(viteEnv)

    return {
        plugins: [
            react(),
            electron({
                entry: 'electron-main.js',
            })
        ],

        esbuild: {
            // 打包去除 console.log 和 debugger
            drop: env.VITE_DROP_CONSOLE && command === 'build' ? ["console", "debugger"] : []
        },

        /* 开发服务器配置 */
        server: {
            // 端口
            port: env.VITE_PORT,
            // 代理配置
            proxy: {
                // ...
            }
        },

        resolve: {
            // 设置别名
            alias: {
                '@': resolve(__dirname, 'src'),
                '@assets': resolve(__dirname, 'src/assets'),
                '@components': resolve(__dirname, 'src/components'),
                '@views': resolve(__dirname, 'src/views')
            }
        }
    }
})

简单配置下package.json

修改package.json文件,加入"main": "electron-main.js"入口配置,并且需要去掉 “type”: “module”

接下来运行命令 yarn electron:serve就可以启动桌面端项目了。

在这里插入图片描述

之前也有分享过electron+vite4+vue3创建桌面端项目的一些分享,里面有打包的一些配置,感兴趣可以去看看。

https://blog.csdn.net/yanxinyun1990/article/details/130944508

https://blog.csdn.net/yanxinyun1990/article/details/130977430

electron创建无边框窗口拖拽

在这里插入图片描述
创建窗口配置 frame: false 即可创建一个无系统边框的窗体。

在这里插入图片描述
css设置 -webkit-app-region: drag 来实现拖拽区域。设置 -webkit-app-region: no-drag 取消拖拽响应。

自定义窗口最大化/最小化/关闭功能。

import { useState, useContext } from 'react'
import { Modal } from '@arco-design/web-react'
import { setWin } from '@/windows/action'

function WinBtn(props) {
  const {
    color = '#fff',
    minimizable = true,
    maximizable = true,
    closable = true,
    zIndex = 2023,

    children
  } = props

  const [hasMaximized, setHasMaximized] = useState(false)

  window.electronAPI.invoke('win__isMaximized').then(res => {
    setHasMaximized(res)
  })
  window.electronAPI.receive('win__hasMaximized', (e, res) => {
    setHasMaximized(res)
  })

  // 最小化
  const handleWinMin = () => {
      window.electronAPI.send("win__minimize")
  }
  // 最大化/还原
  const handleWinMax2Min = () => {
      window.electronAPI.invoke("win__max2min").then(res => {
          console.log(res)
          setHasMaximized(res)
      })
  }
  // 关闭
  const handleWinClose = () => {
      if(window.config.isMainWin) {
        Modal.confirm({
          title: '提示',
          content: <div style={{ textAlign: 'center' }}>是否最小化至托盘,不退出程序?</div>,
          okButtonProps: {status: 'warning'},
          style: {width: 360},
          cancelText: '最小化至托盘',
          okText: '残忍退出',
          onOk: () => {
            setWin('close')
          },
          onCancel: () => {
            setWin('hide', window.config.id)
          }
        })
      }else {
          setWin('close', window.config.id)
      }
  }

  return (
    <>
      <div className="vui__macbtn flexbox flex-alignc" style={{zIndex: zIndex}}>
        <div className="vui__macbtn-groups flexbox flex-alignc" style={{color: color}}>
          { JSON.parse(minimizable) && <a className="mbtn min" title="最小化" onClick={handleWinMin}><svg x="0" y="0" width="10" height="10" viewBox="0 0 10 10"><path fill="#995700" d="M8.048,4.001c0.163,0.012 0.318,0.054 0.459,0.137c0.325,0.191 0.518,0.559 0.49,0.934c-0.007,0.097 -0.028,0.192 -0.062,0.283c-0.04,0.105 -0.098,0.204 -0.171,0.29c-0.083,0.098 -0.185,0.181 -0.299,0.24c-0.131,0.069 -0.271,0.103 -0.417,0.114c-2.031,0.049 -4.065,0.049 -6.096,0c-0.163,-0.012 -0.318,-0.054 -0.459,-0.137c-0.325,-0.191 -0.518,-0.559 -0.49,-0.934c0.007,-0.097 0.028,-0.192 0.062,-0.283c0.04,-0.105 0.098,-0.204 0.171,-0.29c0.083,-0.098 0.185,-0.181 0.299,-0.24c0.131,-0.069 0.271,-0.103 0.417,-0.114c2.031,-0.049 4.065,-0.049 6.096,0Z"></path></svg></a> }
          { JSON.parse(maximizable) &&
            <a className="mbtn max" title={hasMaximized ? '向下还原' : '最大化'} onClick={handleWinMax2Min}>
              {
                hasMaximized ?
                <svg x="0" y="0" width="10" height="10" viewBox="0 0 10 10"><path fill="#4d0000" d="M5,10c0,0 0,-2.744 0,-4.167c0,-0.221 -0.088,-0.433 -0.244,-0.589c-0.156,-0.156 -0.368,-0.244 -0.589,-0.244c-1.423,0 -4.167,0 -4.167,0l5,5Z"></path><path fill="#006400" d="M5,0c0,0 0,2.744 0,4.167c0,0.221 0.088,0.433 0.244,0.589c0.156,0.156 0.368,0.244 0.589,0.244c1.423,0 4.167,0 4.167,0l-5,-5Z"></path></svg>
                :
                <svg x="0" y="0" width="10" height="10" viewBox="0 0 10 10"><path fill="#4d0000" d="M2,3c0,0 0,2.744 0,4.167c0,0.221 0.088,0.433 0.244,0.589c0.156,0.156 0.368,0.244 0.589,0.244c1.423,0 4.167,0 4.167,0l-5,-5Z"></path><path fill="#006400" d="M8,7c0,0 0,-2.744 0,-4.167c0,-0.221 -0.088,-0.433 -0.244,-0.589c-0.156,-0.156 -0.368,-0.244 -0.589,-0.244c-1.423,0 -4.167,0 -4.167,0l5,5Z"></path></svg>
              }
          </a>
          }
          { JSON.parse(closable) && <a className="mbtn close" title="关闭" onClick={handleWinClose}><svg x="0" y="0" width="10" height="10" viewBox="0 0 10 10"><path fill="#4d0000" d="M5,3.552c0.438,-0.432 0.878,-0.861 1.322,-1.287c0.049,-0.044 0.101,-0.085 0.158,-0.119c0.149,-0.091 0.316,-0.137 0.49,-0.146c0.04,0 0.04,0 0.08,0.001c0.16,0.011 0.314,0.054 0.453,0.135c0.08,0.046 0.154,0.104 0.218,0.171c0.252,0.262 0.342,0.65 0.232,0.996c-0.045,0.141 -0.121,0.265 -0.218,0.375c-0.426,0.444 -0.855,0.884 -1.287,1.322c0.432,0.438 0.861,0.878 1.287,1.322c0.097,0.11 0.173,0.234 0.218,0.375c0.04,0.126 0.055,0.26 0.043,0.392c-0.011,0.119 -0.043,0.236 -0.094,0.344c-0.158,0.327 -0.49,0.548 -0.852,0.566c-0.106,0.005 -0.213,-0.007 -0.315,-0.035c-0.156,-0.043 -0.293,-0.123 -0.413,-0.229c-0.444,-0.426 -0.884,-0.855 -1.322,-1.287c-0.438,0.432 -0.878,0.861 -1.322,1.287c-0.11,0.097 -0.234,0.173 -0.375,0.218c-0.126,0.04 -0.26,0.055 -0.392,0.043c-0.119,-0.011 -0.236,-0.043 -0.344,-0.094c-0.327,-0.158 -0.548,-0.49 -0.566,-0.852c-0.005,-0.106 0.007,-0.213 0.035,-0.315c0.043,-0.156 0.123,-0.293 0.229,-0.413c0.426,-0.444 0.855,-0.884 1.287,-1.322c-0.432,-0.438 -0.861,-0.878 -1.287,-1.322c-0.106,-0.12 -0.186,-0.257 -0.229,-0.413c-0.025,-0.089 -0.037,-0.182 -0.036,-0.275c0.004,-0.363 0.211,-0.704 0.532,-0.874c0.13,-0.069 0.272,-0.105 0.418,-0.115c0.04,-0.001 0.04,-0.001 0.08,-0.001c0.174,0.009 0.341,0.055 0.49,0.146c0.057,0.034 0.109,0.075 0.158,0.119c0.444,0.426 0.884,0.855 1.322,1.287Z"></path></svg></a> }
          <i className="mr-10"></i>
          { children }
        </div>
        <div className="vui__mactitle">{window.config.title || '首页'}</div>
    </div>
    </>
  )
}

export default WinBtn

electron创建托盘功能

在这里插入图片描述

/**
 * Electron多窗口管理器
 * @author Andy  Q:282310962
 */

const { app, BrowserWindow, ipcMain, Menu, Tray, dialog, globalShortcut } = require('electron')
// const { loadEnv } = require('vite')
const { join } = require('path')

// 根目录路径
process.env.ROOT = join(__dirname, '../../')

const isDev = process.env.NODE_ENV === 'development'
// const winURL = isDev ? 'http://localhost:3000/' : join(__dirname, 'dist/index.html')
const winURL = isDev ? process.env.VITE_DEV_SERVER_URL : join(process.env.ROOT, 'dist/index.html')

class MultiWindow {
    constructor() {
        // 主窗口对象
        this.main = null
        // 窗口组
        this.group = {}

        // 托盘图标
        this.tray = null
        this.flashTimer = null
        this.trayIco1 = join(process.env.ROOT, 'resource/tray.ico')
        this.trayIco2 = join(process.env.ROOT, 'resource/tray-empty.ico')

        // 监听ipcMain事件
        this.listenIpc()
        
        // 创建系统托盘
        this.createTray()
    }

    // 系统配置参数
    winOptions() {
        return {
            // 窗口图标
            icon: join(process.env.ROOT, 'resource/shortcut.ico'),
            backgroundColor: '#fff',
            autoHideMenuBar: true,
            titleBarStyle: 'hidden',
            width: 900,
            height: 600,
            resizable: true,
            minimizable: true,
            maximizable: true,
            frame: false, // 设置为 false 时可以创建一个无边框窗口 默认值为 true
            show: false, // 窗口是否在创建时显示
            webPreferences: {
                contextIsolation: true, // 启用上下文隔离(为了安全性)(默认true)
                nodeIntegration: false, // 启用Node集成(默认false)
                preload: join(process.env.ROOT, 'electron-preload.js')
            }
        }
    }

    // 创建新窗口
    createWin(options) {
        // ...
    }

    // ...

    // 主进程监听事件
    listenIpc() {
        // 创建新窗体
        ipcMain.on('win-create', (event, args) => this.createWin(args))

        // ...

        // 托盘图标闪烁
        ipcMain.on('win__flashTray', (event, bool) => this.flashTray(bool))

        // 屏幕截图
        ipcMain.on('win__setCapture', () => {
            // ...
        })
    }

    // 创建系统托盘图标
    createTray() {
        console.log(__dirname)
        console.log(join(process.env.ROOT, 'resource/tray.ico'))
        
        const trayMenu = Menu.buildFromTemplate([
            {
                label: '打开主界面',
                icon: join(process.env.ROOT, 'resource/home.png'),
                click: () => {
                    try {
                        for(let i in this.group) {
                            let win = this.getWin(i)
                            if(!win) return
                            // 是否主窗口
                            if(this.group[i].isMainWin) {
                                if(win.isMinimized()) win.restore()
                                win.show()
                            }
                        }
                    } catch (error) {
                        console.log(error)
                    }
                }
            },
            {
                label: '设置中心',
                icon: join(process.env.ROOT, 'resource/setting.png'),
                click: () => {
                    for(let i in this.group) {
                        let win = this.getWin(i)
                        if(win) win.webContents.send('win__ipcData', { type: 'CREATE_WIN_SETTING', value: null })
                    }
                },
            },
            {
                label: '锁屏',
                click: () => null,
            },
            {
                label: '关闭托盘闪烁',
                click: () => {
                    this.flashTray(false)
                }
            },
            {type: 'separator'},
            /* {
                label: '重启',
                click: () => {
                    // app.relaunch({ args: process.argv.slice(1).concat(['--relaunch']) })
                    // app.exit(0)

                    app.relaunch()
                    app.quit()
                }
            }, */
            {
                label: '关于',
                click: () => {
                    for(let i in this.group) {
                        let win = this.getWin(i)
                        if(win) win.webContents.send('win__ipcData', { type: 'CREATE_WIN_ABOUT', value: null })
                    }
                }
            },
            {
                label: '关闭应用并退出',
                icon: join(process.env.ROOT, 'resource/logout.png'),
                click: () => {
                    dialog.showMessageBox(this.main, {
                        title: '询问',
                        message: '确定要退出应用程序吗?',
                        buttons: ['取消', '最小化托盘', '退出应用'],
                        type: 'error',
                        noLink: false,  // true传统按钮样式  false链接样式
                        cancelId: 0
                    }).then(res => {
                        console.log(res)

                        const index = res.response
                        if(index == 0) {
                            console.log('取消')
                        }if(index == 1) {
                            console.log('最小化托盘')
                            for(let i in this.group) {
                                let win = this.getWin(i)
                                if(win) win.hide()
                            }
                        }else if(index == 2) {
                            console.log('退出应用')

                            try {
                                for(let i in this.group) {
                                    let win = this.getWin(i)
                                    if(win) win.webContents.send('win__ipcData', { type: 'WIN_LOGOUT', value: null })
                                }
                                // app.quit 和 app.exit(0) 都可退出应用。
                                // 前者可以被打断并触发一些事件,而后者将强制应用程序退出而不触发任何事件或允许应用程序取消操作。
                                app.quit()
                            } catch (error) {
                                console.log(error)
                            }
                        }
                    })
                }
            }
        ])
        this.tray = new Tray(this.trayIco1)
        this.tray.setContextMenu(trayMenu)
        this.tray.setToolTip(app.name)
        this.tray.on('double-click', () => {
            console.log('double clicked')
        })

        // 开启托盘闪烁
        // this.flashTray(true)
    }

    // 托盘图标闪烁
    flashTray(flash) {
        let hasIco = false
        
        if(flash) {
            if(this.flashTimer) return
            this.flashTimer = setInterval(() => {
                this.tray.setImage(hasIco ? this.trayIco1 : this.trayIco2)
                hasIco = !hasIco
            }, 500)
        }else {
            if(this.flashTimer) {
                clearInterval(this.flashTimer)
                this.flashTimer = null
            }
            this.tray.setImage(this.trayIco1)
        }
    }

    // 销毁托盘图标
    destoryTray() {
        this.flashTray(false)
        this.tray.destroy()
        this.tray = null
    }
}

module.exports = MultiWindow

综上就是electron27+react18创建跨端桌面应用的一些分享,希望能喜欢~~

https://blog.csdn.net/yanxinyun1990/article/details/133880077

https://blog.csdn.net/yanxinyun1990/article/details/132825719

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/106753.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【MyBatis Plus】深入探索 MyBatis Plus 的条件构造器,自定义 SQL语句,Service 接口的实现

文章目录 前言一、条件构造器1.1 什么是条件构造器1.2 QueryWrapper1.3 UpdateWrapper1.4 LambdaWrapper 二、自定义 SQL 语句2.1 自定义 SQL 的基本用法2.2 自定义 SQL 实现多表查询 三、Service 接口3.1 对 Service 接口的认识3.2 实现 Service 接口3.3 实现增删改查功能3.4 …

HarmonyOS开发:探索组件化模式开发

前言 组件化一直是移动端比较流行的开发方式&#xff0c;有着编译运行快&#xff0c;业务逻辑分明&#xff0c;任务划分清晰等优点&#xff0c;针对Android端的组件化&#xff0c;之前有比较系统的总结过相关文章&#xff0c;感兴趣的朋友&#xff0c;可以查看&#xff0c;点击…

java集成海康预览抓图出现内存一直上涨问题

求助&#xff1a;在java 中集成海康sdk后批量抓图出现内存上涨问题&#xff0c;不论是预览后不关闭继续预览&#xff0c;还是预览后关闭预览&#xff0c;然后重新预览都没有解决这个问题&#xff08;抓图正常&#xff09;&#xff0c;尝试使用第三方解码器ffmpeg来进行解码&…

(PyTorch)PyTorch中的常见运算(*、@、Mul、Matmul)

1. 矩阵与标量 矩阵&#xff08;张量&#xff09;每一个元素与标量进行操作。 import torch a torch.tensor([1,2]) print(a1) >>> tensor([2, 3]) 2. 哈达玛积&#xff08;Mul&#xff09; 两个相同尺寸的张量相乘&#xff0c;然后对应元素的相乘就是这个哈达玛…

Qwt QwtLegend和QwtPlotLegendItem图例类详解

1.概述 QwtLegend类是Qwt绘图库中用于显示图例的类。图例用于标识不同曲线、绘图元素或数据的意义&#xff0c;以便用户能够更好地理解图表中的数据。通过QwtLegend类&#xff0c;可以方便地在图表中添加、删除和设置图例的位置、方向和样式等属性。 QwtPlotLegendItem类是Qwt…

浏览器事件循环 (event loop)

进程与线程 进程 进程的概念 进程是操作系统中的一个程序或者一个程序的一次执行过程&#xff0c;是一个动态的概念&#xff0c;是程序在执行过程中分配和管理资源的基本单位&#xff0c;是操作系统结构的基础。 简单的来说&#xff0c;就是一个程序运行开辟的一块内存空间&a…

听GPT 讲Rust源代码--library/std(3)

rust标准库std中的src目录主要包含以下内容和模块: alloc:内存分配相关函数,比如alloc::boxed::Box、alloc::string::String等。 ascii:ASCII相关工具函数。 char:字符相关类型和函数,如Char、char等。 cmp:比较相关trait和函数,如Ord、Eq、PartialOrd等。 env:环境变量相关功能…

Fourier分析导论——第1章——Fourier分析的起源(E.M. Stein R. Shakarchi)

第 1 章 Fourier分析的起源 (The Genesis of Fourier Analysis) Regarding the researches of dAlembert and Euler could one not add that if they knew this expansion, they made but a very imperfect use of it. They were both persuaded that an arbitrary and d…

vscode连接服务器一直retry

解决方法 打开vscode控制面板&#xff0c;输入命令remote-ssh: kill vs code server on host 选择一直连接不上的服务器端口 重新连接

STM32G030F6P6点灯闪烁

前言 &#xff08;1&#xff09;如果有嵌入式企业需要招聘湖南区域日常实习生&#xff0c;任何区域的暑假Linux驱动实习岗位&#xff0c;可C站直接私聊&#xff0c;或者邮件&#xff1a;zhangyixu02gmail.com&#xff0c;此消息至2025年1月1日前均有效 &#xff08;2&#xff0…

EtherNet/IP转profienrt协议网关连接EtherNet/IP协议的川崎机器人配置方法

EthernetIP 协议一般用于采集机器人&#xff0c;控制器等设备的数据。 下面介绍通过远创智控YC-EIPM-PN网关把EtherNet/IP协议的川崎机器人通过西门子1500PLC的控制方法。有些 EIP 的从站设备提供了 EDS 文件&#xff0c;可以从EDS 文件中获取点位信息。这些信息是需要填写到网…

Unity的live2dgalgame多语言可配置剧情框架

这段代码用于读取表格 using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using OfficeOpenXml; using System.IO; using UnityEngine.Networking; using UnityEngine.UI; using Random UnityEngine.Random;public class Plots…

unocss和tailwindcss css原子引擎

第一种tailwindcss&#xff1a; tailwindcss官网 https://tailwindcss.com/docs/grid-column 基本介绍及优点分析 Tailwind CSS 中文文档 - 无需离开您的HTML&#xff0c;即可快速建立现代网站 PostCss 处理 Tailwind Css 基本流程 PostCSS - 是一个用 JavaScript 工具和插…

java基础 集合2

前9点&#xff0c;在另一篇作品中&#xff0c;可以从集合1开始观看 9.List遍历方式&#xff1a; 10.Arraylist底层原理&#xff1a; 11.Linklist底层原理&#xff1a; 1.LinkedList做队列和栈&#xff1a; package day01;import java.util.ArrayList; import java.util.I…

深度学习与计算机视觉(一)

文章目录 计算机视觉与图像处理的区别人工神经元感知机 - 分类任务Sigmoid神经元/对数几率回归对数损失/交叉熵损失函数梯度下降法- 极小化对数损失函数线性神经元/线性回归均方差损失函数-线性回归常用损失函数使用梯度下降法训练线性回归模型线性分类器多分类器的决策面 soft…

Hadoop、Hive安装

一、 工具 Linux系统&#xff1a;Centos&#xff0c;版本7.0及以上 JDK&#xff1a;jdk1.8 Hadoop&#xff1a;3.1.3 Hive&#xff1a;3.1.2 虚拟机&#xff1a;VMware mysql&#xff1a;5.7.11 工具下载地址: https://pan.baidu.com/s/1JYtUVf2aYl5–i7xO6LOAQ 提取码: xavd…

项目经验分享|openGauss 陈贤文:受益于开源,回馈于开源

开源之夏 项目经验分享 2023 #08 # 关于 openGauss 社区 openGauss是一款开源关系型数据库管理系统&#xff0c;采用木兰宽松许可证v2发行。openGauss内核深度融合华为在数据库领域多年的经验&#xff0c;结合企业级场景需求&#xff0c;持续构建竞争力特性。同时openGauss也是…

基于FPGA的图像PSNR质量评估计算实现,包含testbench和MATLAB辅助验证程序

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 设置较大的干扰&#xff0c;PSNR15。 设置较小的干扰&#xff0c;PSNR25。 2.算法运行软件版本 matlab2022a vivado2019.2 3.部分核心程序 ti…

如何在Postman中使用静态HTTP

首先&#xff0c;打开 Postman 软件。在 Postman 的菜单栏中&#xff0c;点击 “Preferences”&#xff08;偏好设置&#xff09;。 亲身经验&#xff1a;我自己尝试了这个方法&#xff0c;发现它非常适用于需要使用HTTP的场景。 数据和引证&#xff1a;根据 Postman 官方文档…

皮卡丘RCE靶场通关攻略

皮卡丘RCE靶场通关攻略 文章目录 皮卡丘RCE靶场通关攻略RCE(remote command/code execute)概述远程系统命令执行启动环境漏洞练习第一关exec "ping"第二关 exec "eval" RCE(remote command/code execute)概述 RCE漏洞&#xff0c;可以让攻击者直接向后台服…