【Electron】Electron学习笔记

news2024/11/15 6:58:20

1.什么是 Electron?

Electron 是一个跨平台桌面应用开发框架,开发者可以利用 HTMLCSSJavaScriptWeb技术来构建桌面应用程序。它本质上是结合了 ChromiumNode.js,目前广泛用于桌面应用程序开发。例如,许多桌面应用都采用了 Electron 技术。

Electron 官网https://www.electronjs.org/

以下这些都是使用了 Electron 技术开发的桌面应用程序的例子:

Visual Studio Code: 一个流行的开源代码编辑器,由 Microsoft 开发。
GitHub Desktop: GitHub 官方提供的桌面客户端,用于管理 Git 代码仓库。
1Password: 一个密码管理工具,提供安全的密码保存和管理功能。
新版 QQ: QQ 的桌面应用程序,使用 Electron 实现跨平台的桌面体验。

2. Electron 的优势

  1. 可跨平台:同一套代码可以构建出能在:WindowsmacOSLinux 上运行的应用程序。
  2. 上手容易:使用 Web 技术就可以轻松完成开发桌面应用程序。
  3. 底层权限:允许应用程序访问文件系统、操作系统等底层功能,从而实现复杂的系统交互。
  4. 社区支持:拥有一个庞大且活跃的社区,开发者可以轻松找到文档、教程和开源库。(额,这个国内教程很少,不是很赞同~)

3. Electron 技术架构

3.1. 技术架构

在这里插入图片描述
Electron = Chromium + Node.js + Native API
这个公式表明了 Electron 框架的核心组成部分:

Chromium: 提供了现代的 Web 渲染引擎和许多与浏览器相关的功能,用于渲染和显示应用程序的用户界面。
Node.js: 提供了访问操作系统和文件系统等底层功能的能力,以及在后端执行 JavaScript 代码的环境。
Native API: 允许 Electron 应用程序直接访问操作系统的原生功能,例如文件系统、系统通知等,从而实现更复杂和更深入的系统交互和集成。

3.2. 进程模型

在这里插入图片描述

  1. 操作系统层
    Windows, Linux, Mac:这三个操作系统是应用程序可以运行的环境。它们提供了底层的系统资源和 API 接口。
  2. 主进程 (Main Process)
    Main:这是应用程序的核心部分,通常是一个 Node.js 进程。它管理应用的生命周期、窗口创建、以及与渲染进程的通信。
  3. 渲染进程 (Render Process)
    Render:每个渲染进程负责展示用户界面。它运行在 Chromium 中,支持 HTMLCSSJavaScript。每个渲染进程与主进程通过 IPC进程间通信)进行通信。
  4. IPC (Inter-Process Communication)
    IPC:这是主进程与渲染进程之间的通信机制。通过 IPC,主进程可以发送消息给渲染进程,反之亦然。这种机制使得各部分能够独立运行,同时保持数据和命令的流动。
  5. Chromium
    Chromium:这是一个开源浏览器项目,渲染进程基于 Chromium 引擎,负责处理网页内容的渲染。

4. 搭建一个工程(实战Demo)

  1. 首先创建一个文件夹并初始化 npm 包。
mkdir my-electron-app && cd my-electron-app
npm init
  1. package.json 修改为如下:
 {
  "name": "my-electron-app",
  "version": "1.0.0",
  "main": "main.js",
  "scripts": {
    "start": "electron ."
  },
  "author": "RanGuMo",
  "description": "this is a electron demo",
  "license": "ISC"
}

有几条规则需要遵循:

entry point 应为 main.js.(electron 官方推荐叫mian.js,你也可以用其他名字)
authordescription 可为任意值,但对于应用打包是必填项

  1. 安装 electron 作为开发依赖。
npm install --save-dev electron

或者

npm install -D electron
  1. main.js 中编写代码,创建一个基本窗口,内容如下:
// main.js运行在应用的主进程上,无法访问web相关API,主要负责:控制生命周期、显示界面
// 控制渲染进程等其他操作。

const { app, BrowserWindow } = require("electron");
const path = require("path");

// 保持对window对象的全局引用,如果不这么做的话,当JavaScript对象被
// 垃圾回收的时候,window对象将会自动的关闭
let mainWindow;

// 1.创建浏览器窗口。
function createWindow() {
  // 1.1.创建浏览器窗口。
  mainWindow = new BrowserWindow({
    width: 800, // 宽度
    height: 600, // 高度
    autoHideMenuBar: true, // 自动隐藏菜单栏(默认是false)
    alwysOnTop: true, // 窗口置顶(类似z-index:9999,永远置于最高层) (默认是false)
    x: 0, // 窗口左上角x坐标
    y: 0, // 窗口左上角y坐标
  });
  // 1.2.加载一个远程的页面
  mainWindow.loadURL("http://www.baidu.com");
}
  
  
  // 1.3.当 window 被加载,就执行创建窗口这个函数
  app.on("ready", () => {
    createWindow();
  });

  // 1.4.当 window 被关闭,这个事件会被触发。
  app.on("closed", function () {
    // 取消引用 window 对象,如果你的应用支持多窗口的话,
    // 通常会把多个 window 对象存放在一个数组里面,
    // 与此同时,你应该删除相应的元素。
    mainWindow = null;
  });


关于 BrowserWindow 的更多配置项,请参考:BrowserWindow实例属性

  1. 启动应用,查看效果
npm start

在这里插入图片描述

5. 加载本地页面

  • 创建 pages/index.html 编写内容:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>index</title>
</head>
<body>
    <h1>hello world</h1>
</body>
</html>
  • 修改 mian.js 加载本地页面
  // 1.2.加载一个远程的页面
  // mainWindow.loadURL("http://www.baidu.com");
  // 1.2.加载一个本地的页面  
  mainWindow.loadFile('./pages/index.html')
  • Ctrl+Shift+i进入控制台,此时开发者工具会报出一个安全警告,需要修改 index.html ,配置 CSP(ContentSecurity-Policy)(内容安全策略)

在这里插入图片描述

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;">

上述配置的说明

  1. default-src 'self'
    default-src:配置加载策略,适用于所有未在其它指令中明确指定的资源类型self :仅允许从同源的资源加载,禁止从不受信任的外部来源加载,提高安全性。
  2. style-src 'self'
    unsafe-inlinestyle-src:指定样式表(CSS)的加载策略。
    self :仅允许从同源的资源加载,禁止从不受信任的外部来源加载,提高安全性。
    unsafe-inline :允许在HTML文档内使用内联样式。
  3. img-src 'self' data:
    img-src :指定图像资源的加载策略。
    self :表示仅允许从同源加载图像,
    data::允许使用 data:URI 来嵌入图像。这种URI模式允许将图像数据直接嵌入到HTMLCSS中,而不是通过外部链接引用。
    关于 CSP 的详细说明请参考:MDN-Content-Security-Policy、Electron Security

6. 完善窗口行为

1.WindowsLinux 平台窗口特点是:关闭所有窗口时退出应用

// 1.5.当所有窗口都关闭时,自动退出应用,除非在 macOS 上
app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit();
  }
});
  1. mac 应用即使在没有打开任何窗口的情况下也继续运行,并且在没有窗口可用的情况下激活应用时会打开新的窗口
// 1.3.当 window 被加载,就执行创建窗口这个函数
app.on("ready", () => {
  createWindow();
  // 1.6.在 mac上,点击 Dock 图标且没有其他窗口打开时,重新创建窗口
  // 当应用被激活时  
  app.on("activate", () => {
    // 如果没有窗口打开,则创建一个窗口
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow();
    }
  });
});

7. 配置自动重启

  1. 安装 Nodemon
npm i nodemon -D
  1. 修改 package.json 启动的命令
 "scripts": {
    "start": "nodemon --exec electron ."
  },
  1. 新建 nodemon.json文件并配置规则
{
  "ignore": [
    "node_modules",
    "dist"
  ],
  "restartable": "r",
  "watch": ["*.*"],
  "ext": "html,js,css"
}

8.主进程与渲染进程(重点)

Electron 中主要控制两类进程: 主进程、渲染器进程。

  1. 主进程
    每个 Electron 应用都有一个单一的主进程,作为应用程序的入口点。 主进程在 Node.js 环境中运行,它具有 require 模块和使用所有 Node.js API 的能力,主进程的核心就是:使用BrowserWindow 来创建和管理窗口。
  2. 渲染进程
    每个 BrowserWindow 实例都对应一个单独的渲染器进程,运行在渲染器进程中的代码,必须遵守网页标准,这也就意味着:渲染器进程无权直接访问require或使用任何 Node.jsAPI

问题产生:处于渲染器进程的用户界面,该怎样才与 Node.jsElectron 的原生桌面功能进行交互呢? 通过 Preload 脚本

9.Preload 脚本

预加载(Preload)脚本是运行在渲染进程中的,但它是在网页内容加载之前执行的,这意味着它具有比普通渲染器代码更高的权限,可以访问 Node.jsAPI,同时又可以与网页内容进行安全的交互。
简单说:它是 Node.jsWeb API 的桥梁,Preload 脚本可以安全地将部分 Node.js 功能暴露给网页,从而减少安全风险。

需求:点击按钮后,在页面呈现当前电脑的 Node 版本。

具体文件结构与编码如下:

  1. 在根目录创建预加载脚本 preload.js ,内容如下:
const { contextBridge } = require('electron');

// 暴露数据给渲染进程
contextBridge.exposeInMainWorld('myNodeVersion', {
    version: process.version
})
  1. 在主线程中引入 preload.js
   const path = require("path");
   // 1.1.创建浏览器窗口。
   mainWindow = new BrowserWindow({
    width: 800, // 宽度
    height: 600, // 高度
    autoHideMenuBar: true, // 自动隐藏菜单栏(默认是false)
    alwysOnTop: true, // 窗口置顶(类似z-index:9999,永远置于最高层) (默认是false)
    x: 0, // 窗口左上角x坐标
    y: 0, // 窗口左上角y坐标
    webPreferences:{
      preload: path.join(__dirname, "./preload.js")
    }
  });
  1. html 页面中编写对应按钮,并创建专门编写网页脚本的 render.js ,随后引入。

index.html 内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- 内容安全策略 -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;">
    <title>index</title>
</head>
<body>
    <h1>hello world!!</h1>
    <button id="btn">获取Node版本号</button>

    <script type="text/javascript" src="./render.js"></script>
</body>
</html>
  1. 在渲染进程中使用 version

render.js 内容如下:

btn.addEventListener('click', () => {
    console.log(myNodeVersion.version)
    document.body.innerHTML += `<h2>${myNodeVersion.version}</h2>`
})
  1. 整体文件结构如下:
    在这里插入图片描述
  2. 效果演示:
    在这里插入图片描述

10.进程通信(IPC)(重点)

值得注意的是:
上文中的 preload.js ,无法使用全部Node.jsAPI,比如:不能使用 Node 中的 fs 模块,但主进程(main.js )是可以的,这时就需要进程通信了。简单说:要让 preload.js 通知 main.js 去调用 fs 模块去干活。

关于 Electron 进程通信,我们要知道:

  • IPC 全称为:InterProcessCommunication,即:进程通信。
  • IPCElectron 中最为核心的内容,它是从UI 调用原生 API 的唯一方法!
  • Electron 中,主要使用 ipcMainipcRenderer 来定义“通道”,进行进程通信。

10.1.渲染进程==>主进程(单向)

概述:在渲染器进程中 ipcRenderer.send 发送消息,在主进程中使用 ipcMain.on 接收消息。常用于:在 Web 中调用主进程的 API,例如下面的这个需求:

需求:点击按钮后,在用户的 D盘创建一个 hello.txt 文件,文件内容来自于用户输入。

  1. index.html页面中添加相关元素,render.js 中添加对应的点击事件

index.html内容:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- 内容安全策略 -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;">
    <title>index</title>
</head>
<body>
    <h1>hello world!!</h1>
    <button id="btn">获取Node版本号</button>
    <br>
    <input type="text" id="content">
    <button id="btn2">在用户的D盘创建一个hello.txt</button>
    <script type="text/javascript" src="./render.js"></script>
</body>
</html>

render.js 内容:

// 可以不用写const btn = document.getElementById('btn')
btn.addEventListener("click", () => {
  console.log(myNodeVersion.version);
  document.body.innerHTML += `<h2>${myNodeVersion.version}</h2>`;
});

const btn2 = document.getElementById("btn2");
const content = document.getElementById("content");
btn2.addEventListener("click", () => {
  console.log(content.value);
  myAPI.saveFile(content.value);
});
  1. preload.js 中使用 ipcRenderer.send('信道',参数)发送消息,与主进程通信。

preload.js 内容:

const { contextBridge, ipcRenderer } = require('electron');

// 暴露数据给渲染进程
contextBridge.exposeInMainWorld('myNodeVersion', {
    version: process.version
})

contextBridge.exposeInMainWorld('myAPI', {
   saveFile(str){
      //渲染进程给主进程发送消息事件
      ipcRenderer.send('create-file', str)
   }
})
  1. 主进程中,在加载页面之前,使用 ipcMain.on('信道”,回调)配置对应回调函数,接收消息。

main.js 内容:

// 引入:app(整个应用)、BrowserWindow(用于创建窗口)、ipcMain(用于进程通信)
const { app, BrowserWindow, ipcMain } = require('electron');
// 引入path模块
const path = require('path');
// 引入fs模块
const fs = require('fs');
// 2.创建文件
function createFile(event,data) {
  fs.writeFileSync('D:/hello.txt',data)
}


// 1.创建浏览器窗口。
function createWindow() {
  // 1.1.创建浏览器窗口。
  mainWindow = new BrowserWindow({
    width: 800, // 宽度
    height: 600, // 高度
    autoHideMenuBar: true, // 自动隐藏菜单栏(默认是false)
    alwysOnTop: true, // 窗口置顶(类似z-index:9999,永远置于最高层) (默认是false)
    x: 0, // 窗口左上角x坐标
    y: 0, // 窗口左上角y坐标
    webPreferences:{
      preload: path.join(__dirname, "./preload.js")
    }
  });
  // 2.1.主进程注册对应的事件
  ipcMain.on("create-file",createFile)

  // 1.2.加载一个本地的页面
  mainWindow.loadFile("./pages/index.html");
}

完整的main.js 如下:

// main.js运行在应用的主进程上,无法访问web相关API,主要负责:控制生命周期、显示界面
// 控制渲染进程等其他操作。

// 引入:app(整个应用)、BrowserWindow(用于创建窗口)、ipcMain(用于进程通信)
const { app, BrowserWindow, ipcMain } = require('electron');
// 引入path模块
const path = require('path');
// 引入fs模块
const fs = require('fs');

// 保持对window对象的全局引用,如果不这么做的话,当JavaScript对象被
// 垃圾回收的时候,window对象将会自动的关闭
let mainWindow;

// 2.创建文件
function createFile(event,data) {
  fs.writeFileSync('D:/hello.txt',data)
}


// 1.创建浏览器窗口。
function createWindow() {
  // 1.1.创建浏览器窗口。
  mainWindow = new BrowserWindow({
    width: 800, // 宽度
    height: 600, // 高度
    autoHideMenuBar: true, // 自动隐藏菜单栏(默认是false)
    alwysOnTop: true, // 窗口置顶(类似z-index:9999,永远置于最高层) (默认是false)
    x: 0, // 窗口左上角x坐标
    y: 0, // 窗口左上角y坐标
    webPreferences:{
      preload: path.join(__dirname, "./preload.js")
    }
  });
  // 2.1.主进程注册对应的事件
  ipcMain.on("create-file",createFile)

  // 1.2.加载一个远程的页面
  // mainWindow.loadURL("http://www.baidu.com");
  // 1.2.加载一个本地的页面
  mainWindow.loadFile("./pages/index.html");
}

// 1.3.当 window 被加载,就执行创建窗口这个函数
app.on("ready", () => {
  createWindow();
  // 1.6.在 mac上,点击 Dock 图标且没有其他窗口打开时,重新创建窗口
  // 当应用被激活时  
  app.on("activate", () => {
    // 如果没有窗口打开,则创建一个窗口
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow();
    }
  });
});

// 1.4.当 window 被关闭,这个事件会被触发。
app.on("closed", function () {
  // 取消引用 window 对象,如果你的应用支持多窗口的话,
  // 通常会把多个 window 对象存放在一个数组里面,
  // 与此同时,你应该删除相应的元素。
  mainWindow = null;
});

// 1.5.当所有窗口都关闭时,自动退出应用,除非在 macOS 上
app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit();
  }
});
  1. 运行项目,即可实现效果。

10.2.渲染进程<==>主进程(双向)

概述:渲染进程通过ipcRenderer.invoke 发送消息,主进程使用 ipcMain.handle 接收并处理消息。
备注:ipcRender.invoke 的返回值是 Promise 实例。
常用于:从渲染器进程调用主进程方法并等待结果,例如下面的这个需求:

需求:点击按钮从 D盘读取 hello.txt 中的内容,并将结果呈现在页面上。

  1. 页面中添加相关元素,render.js 中添加对应脚本

index.html

<button id="btn3">读取⽤户D盘的hello.txt</button>

render.js

const btn3 = document.getElementById("btn3");
btn3.addEventListener("click", async () => {
  let data = await myAPI.readFile();
  document.body.innerHTML += `<h2>${data}</h2>`;
});
  1. preload.js 中使用 ipcRenderer.invoke('信道',参数)发送消息,与主进程通信。

preload.js

const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("myAPI", {
  saveFile(str) {
    //渲染进程给主进程发送消息事件,通过 IPC 发送 'file-save' 事件并传递数据
    ipcRenderer.send("create-file", str);
  },
  // 定义读取文件的方法,通过 IPC 调用 'file-read' 事件并返回 Promise 结果
  readFile: () => {
    return ipcRenderer.invoke("file-read");
  },
});
  1. 主进程中,在加载页面之前,使用 ipcMain.handle('信道',回调)接收消息,并配置回调函数。

main.js

// 3.读取文件,读取指定文件内容并返回字符串格式的结果
function readFile() {
  const res = fs.readFileSync('D:/hello.txt').toString();
  return res;
}

// 1.创建浏览器窗口。
function createWindow() {
  // 1.1.创建浏览器窗口。
  mainWindow = new BrowserWindow({
    width: 800, // 宽度
    height: 600, // 高度
    autoHideMenuBar: true, // 自动隐藏菜单栏(默认是false)
    alwysOnTop: true, // 窗口置顶(类似z-index:9999,永远置于最高层) (默认是false)
    x: 0, // 窗口左上角x坐标
    y: 0, // 窗口左上角y坐标
    webPreferences:{
      preload: path.join(__dirname, "./preload.js")
    }
  });
  // 2.1.主进程注册对应的事件
  ipcMain.on("create-file",createFile)

  // 3.1.处理 'file-read' IPC 事件,调用 readFile 函数处理,并返回结果
  ipcMain.handle('file-read', readFile);

  // 1.2.加载一个远程的页面
  // mainWindow.loadURL("http://www.baidu.com");
  // 1.2.加载一个本地的页面
  mainWindow.loadFile("./pages/index.html");
}

10.3.主进程到=>渲染进程

概述:主进程使用 win.webContents.send 发送消息,渲染进程通过ipcRenderer.on 处理消息
常用于:从主进程主动发送消息给渲染进程,例如下面的这个需求:

需求:应用加载 6秒钟后,主动给渲染进程发送一个消息,内容是:你好啊!

  1. 页面中添加相关元素,render.js 中添加对应脚本

render.js

window.onload = () => {
  myAPI.getMessage(logMessage)
};

function logMessage(event,str){
    console.log(event,str)
    alert(str)
}
  1. preload.js 中使用 ipcRenderer.on ('信道',回调)接收消息,并配置回调函数。

preload.js

contextBridge.exposeInMainWorld("myAPI", {
  getMessage:(callback)=>{
    return ipcRenderer.on("message",callback)
  }
});
  1. 主进程中,在合适的时候,使用 mainWindow.webcontents.send('信道',数据)发送消息。

main.js

// 1.创建浏览器窗口。
function createWindow() {
  // 1.1.创建浏览器窗口。
  mainWindow = new BrowserWindow({
    width: 800, // 宽度
    height: 600, // 高度
    autoHideMenuBar: true, // 自动隐藏菜单栏(默认是false)
    alwysOnTop: true, // 窗口置顶(类似z-index:9999,永远置于最高层) (默认是false)
    x: 0, // 窗口左上角x坐标
    y: 0, // 窗口左上角y坐标
    webPreferences: {
      preload: path.join(__dirname, "./preload.js"),
    },
  });
  // 2.1.主进程注册对应的事件
  ipcMain.on("create-file", createFile);

  // 3.1.处理 'file-read' IPC 事件,调用 readFile 函数处理,并返回结果
  ipcMain.handle("file-read", readFile);

  // 1.2.加载一个远程的页面
  // mainWindow.loadURL("http://www.baidu.com");
  // 1.2.加载一个本地的页面
  mainWindow.loadFile("./pages/index.html");

  // 创建⼀个定时器
  setTimeout(() => {
    mainWindow.webContents.send("message", "你好啊!");
  }, 6000);
}

11.打包Electron应用

使用 electron-builder 打包应用

  1. 先安装 electron-builder :
npm install electron-builder -D
  1. package.json 中进行相关配置,具体配置如下

在这里插入图片描述

备注: package.json 文件不支持注释,使用时请去掉所有注释.

package.json内容如下:

{
  "name": "my-electron-app",
  "version": "1.0.0",
  "main": "main.js",
  "scripts": {
    "start": "nodemon --exec electron .",
    "build": "electron-builder" 
  },
  "build": {
    "appId": "com.electron.demo", 
    "win": {
      "icon":"./logo.ico",
      "target": [
        {
          "target": "nsis",  
          "arch": ["x64"]  
        }
      ]
    },
    "nsis": {
      "oneClick": false, 
      "perMachine": true,
      "allowToChangeInstallationDirectory": true
    }
  },
  "author": "RanGuMo",
  "description": "this is a electron demo",
  "license": "ISC",
  "devDependencies": {
    "electron": "^32.0.1",
    "electron-builder": "^24.13.3",
    "nodemon": "^3.1.4"
  }
}
  1. 执行打包命令
npm run build

打包失败的话,检查一下当前项目所在路径是不是全英文路径。可以看一下这篇文章:http://t.csdnimg.cn/xV1ex

12.electron-vite

electron-vite 是一个新型构建工具,旨在为 Electron 提供更快、更精简的开发体验。它主要由五部分组成:

  • 一套构建指令,它使用 Vite 打包你的代码,并且它能够处理 Electron 的独特环境,包括 Node.js 和浏览器环境。

  • 集中配置主进程、渲染器和预加载脚本的 Vite 配置,并针对 Electron 的独特环境进行预配置。

  • 为渲染器提供快速模块热替换(HMR)支持,为主进程和预加载脚本提供热重载支持,极大地提高了开发效率。

  • 优化 Electron 主进程资源处理。

  • 使用 V8 字节码保护源代码。

electron-vite 快速、简单且功能强大,旨在开箱即用。

13.项目源码

github 地址:https://github.com/RanGuMo/my-electron-app.git

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

算法学习-基础算法

基础算法 一.二分查找 1.模版 boolean check(int x) { }int search(int left, int right) {while (left < right) {int mid (left right) >> 1;if (check(mid)) {//满足条件&#xff0c;向寻找范围继续寻找&#xff0c;例如我要找更靠左的&#xff1a;r m right…

一次学校OJ 代码执行测试

前言 以前看过一篇Windows上搭OJ被C#打穿的文章&#xff0c;刚好测测学校的OJ。 这里没有过多的研究其余的可能利用点&#xff0c;仅仅是简单记录下过程&#xff0c;一些思路的启发。 测试过程 首先看支持的代码类型&#xff1a; 尝试了Java发现不能import&#xff0c;那J…

一文带你从零到实战,学会gcc和Makefile,多文件编译神器的使用与编写

目录&#xff1a; 目录&#xff1a; 一、什么是Makefile 1.1 makefile的作用&#xff1a; 1.2 makefile的基本组成&#xff1a; 二、Linux编译过程&#xff1a; 2.1 linux编译过程: 2.1.1 预处理&#xff08;Preprocessing&#xff09; 2.1.2 编译&#xff08;Compilation&am…

# 移动硬盘误操作制作为启动盘数据恢复问题

移动硬盘误操作制作为启动盘数据恢复问题 文章目录 移动硬盘误操作制作为启动盘数据恢复问题步骤一恢复原有数据 步骤二格式化并重新分区 注意注意先找数据恢复软件恢复数据&#xff0c;把之前移动硬盘或者U盘上的数据恢复到其它地址 步骤一 恢复原有数据 使用一些数据恢复软…

SpringBoot实现Word转PDF/TXT

背景 研发工作中难免会遇到一些奇奇怪怪的需求&#xff0c;就比如最近&#xff0c;客户提了个新需求&#xff1a;上传一个WORD文档&#xff0c;要求通过系统把该文档转换成PDF和TXT。客户的需求是没得商量的&#xff0c;必须实现&#xff01;承载着客户的期望&#xff0c;我开始…

培训第三十七天(Dockerfile与registry)

一、使用Dockerfile创建镜像 Dockerfile文件命令介绍&#xff1a; FORM 指定基础镜像为该镜像的最后修改版本 FROM < img:tag >指定基础镜像为该镜像的⼀个tag版本 MAINTAINER 指定镜像创建者&#xff0c;企业内部不⽤指定&#xff0c;对外发布也可以不指定 RUN 运⾏…

探索Python的Excel力量:openpyxl库的奥秘

文章目录 探索Python的Excel力量&#xff1a;openpyxl库的奥秘背景&#xff1a;为什么选择openpyxl&#xff1f;库简介&#xff1a;openpyxl是什么&#xff1f;安装指南&#xff1a;如何安装openpyxl&#xff1f;快速上手&#xff1a;五个基本函数实战演练&#xff1a;三个应用…

Python实现Word文档转换为图片(JPG、PNG、SVG等常见格式)例子解析

在Python中将Word文档转换为图片&#xff08;如JPG、PNG、SVG等格式&#xff09;可以通过多种库实现&#xff0c;例如Spire.Doc for Python和Aspose.Words for Python。以下是一些详细的代码示例&#xff0c;展示了如何使用这些库完成转换。 使用Spire.Doc for Python转换Word…

网络服务器及IO模型

网络服务器 单循环服务器&#xff1a;服务器在同一时刻只能响应一个客户端的请求 并发服务器模型&#xff1a;服务器在同一时刻可以响应多个客户端的请求 实现TCP并发服务器 1.多进程 2.多线程 3.IO多路复用&#xff1a; 为了解决进程或线程阻塞到某个 I/O 系统调用而出现的…

几种前端处理文本换行展示

文章目录 一、使用 CSS 的 white-space 属性二、使用 CSS 的 word-break 和 word-wrap 属性三、 使用 CSS 的 flex 布局和自动换行四、 使用overflow实现换行 一、使用 CSS 的 white-space 属性 可以将 white-space 属性设置为 pre-wrap 或 pre-line。 pre-wrap&#xff1a;保…

【STM32】FMC

FMC功能与FSMC类似&#xff0c;但比FSMC更强大&#xff0c;但仅在F4 / F7 / H7等高级一点的MCU上支持&#xff0c;F1不支持。虽然我的是F103&#xff0c;但顺便都看了。 大部分图片来源&#xff1a;正点原子HAL库课程 专栏目录&#xff1a;记录自己的嵌入式学习之路-CSDN博客 目…

数据结构学习:栈

栈的简介 栈&#xff08;Stack&#xff09;是限定仅在表尾进行插入租删除操作的线性表。 允许插入和删除的一端称为栈顶(top),另-端称为栈底(bottom) 不含任何数据元素的栈称为空栈 栈又称为后进先出的线性表,简称LIFO结构 栈的插入操作&#xff0c;也叫做进栈&#xff0c…

java JVM

JVM的组成 Java虚拟机&#xff08;JVM&#xff09;是执行Java字节码的运行时环境。它由以下几个主要部分组成&#xff1a; 1. **类加载器&#xff08;ClassLoader&#xff09;**&#xff1a; - 负责加载Java类的字节码到JVM中&#xff0c;并进行链接和初始化。 关于Java的…

C++基础练习

1》提示并输入一个字符串&#xff0c;统计该字符串中字母个数、数字个数、空格个数、其他字符的个数 1 #include<iostream>2 using namespace std;3 4 int main()5 {6 string str1; //定义字符串数据7 cout << "请输入一个字符串>>>" ;8…

三种常用的Word打印部分内容操作技巧

作为打工人&#xff0c;我们经常需要处理Word文档&#xff0c;有时还会遇到只需要打印文档中的部分内容而非整个文档的情况。为了高效地完成这一任务&#xff0c;Word提供了多种灵活的设置方法。本文将详细介绍三种常用的方法来帮助你实现只打印Word文档中的部分内容。 方法一&…

第一周学习--联邦学习

OUC读研--第一周 目录 1、课程学习 2、fedavg的算法实现 关于代码详解 1、client __init__ 方法 local_train 方法 2、server 3、get_dataset 函数定义 数据集加载 MNIST 数据集 CIFAR-10 数据集 返回值 使用示例 4、 main 代码解释 可能的改进点 5、models …

机器学习之 K-means算法的代码实现

K-means 算法简介 K-means 是一种常用的无监督学习算法&#xff0c;主要用于数据聚类。它的主要思想是将数据集中的数据分成 K 个簇&#xff08;Cluster&#xff09;&#xff0c;使得簇内的数据点尽可能相似&#xff0c;而簇间的差异尽可能大。K-means 算法的核心步骤包括初始…

关于类与构造函数继承的小挑战

题目 /* 使用构造函数将电动汽车&#xff08;称为 EV&#xff09;作为 Car 的子 “类 ”来实现。除了品牌和当前速度外&#xff0c;EV 还具有当前电池电量&#xff08;百分比&#xff09;&#xff08;“charge ”属性&#xff09;&#xff1b;实现一个 “chargeBattery ”方法…

Vitis AI 基本认知(Tiny-VGG 项目代码详解)

目录 1. 简介 1.1 Tiny-VGG 1.2 data 目录结构 2. 代码分析 2.1 Import packages 2.2 Dataset 2.3 Train step 2.4 Vali & Test step 2.5 Ceate model 2.6 Compile model 2.6.1 计算 loss 2.6.2 计算平均值 3.6.3 计算准确度 2.7 训练循环 2.7.1 自定义训练…

BLE蓝牙协议详解

BLE蓝牙协议详解 1、BLE协议栈 1、协议栈结构 蓝牙LE协议栈按功能分为三个层&#xff1a;Controller、Host和Application Profiles and Services。 HCI event是按BLE Spec标准设计的&#xff0c;是BLE Controller和Host用来交互的事件&#xff1b;GAP event是BLE host定义的…