5分钟 electron 入门

文章目录

  • 番茄钟应用
  • 起步
    • 安装
    • 初始化
    • 启动 electron 项目
    • nodemon 启动项目
  • 主进程 app 和窗口管理 BrowserWindow
    • app 、BrowserWindow
    • ready 事件
    • webContent:主进程控制网页
    • 退出应用
  • 装载网页到窗口
    • 资源来源安全声明
    • SPA 单页应用
  • 进程的环境
    • Chromium 沙盒
    • Electron 主进程
    • Electron 渲染进程
    • 配置 Electron 沙盒
    • 预加载脚本
      • 概念
      • 注册预加载脚本
      • 基本使用
      • 上下文隔离
      • 结合 ts
      • 预加载脚本的主要用途
  • 进程间通信
  • 案例:番茄钟应用

番茄钟应用

在一个番茄钟应用中,主进程和渲染进程要做什么?

image.png

从上方的进程工作内容可以发现,有些进程工作内容不仅仅是番茄应用会用到,其实所有应用都会用到。这些是一个应用基本通用的。
比如主进程 app 模块管理应用、BrowserWindow 模块创建窗口,进程间通信。

起步

安装

npm install electron --save-dev

初始化

npm init -y

package.json 的 main 字段指定的是 Electron 应用的入口文件,就像 vite 构建 SPA 的入口文件是 html 一样。因此这个入口文件很重要,它就是主进程。
author、license 和 description可为任意值,但对于应用打包是必填项。

启动 electron 项目

electron .

这个命令会找到入口文件并执行。

通常我们会设为一个 script 脚本来使用:

{
  "scripts": {
    "dev": "electron .",
  }
}

nodemon 启动项目

开发的时候,频繁手动启停应用很麻烦。我们可以借助 nodemon 自动监听文件更改,执行electron . 命令。

  • https://github.com/remy/nodemon#nodemon

nodemon 可以监听文件变动,并执行命令。
它有几个主要配置:

  • –watch:监听的文件,默认为*.*,也就是当前目录的所有文件
  • –ext:监听的文件后缀,默认为 js,mjs,html
  • –ignore:忽略监听,默认已经忽略常见的应该忽略的文件,如 node_module
  • –exec:执行命令,默认可以执行 js 脚本。
  • –delay:延迟启动。默认情况下当它检测到文件更改时就会立即重启你的应用程序。然而,如果你想要控制 nodemon 的重启频率,以避免在保存文件时过于频繁地重启(例如,当你正在进行大量编辑时),你可以使用 --delay(简写为 -d)选项来设置重启延迟。-d 1.5 :延迟1.5秒启动

nodemon 最终会监听的文件规则:
nodemon 会先读取 watch 里面需要监听的文件或文件路径,再从文件中选择监控 ext 中指定的后缀名,最后去掉从 ignore 中指定的忽略文件或文件路径。

electron 项目中,nodemon 已经监听了 js,但没有 html。我们希望改了 html,也能重新启动,所以要加上后缀为 html 的文件监听。

{
  "scripts": {
    "dev": "nodemon --ext js,html,json --delay 1.5 --exec electron ."
  }
}

当然如果配置项太多,也可以单独添加一个配置文件:

{
  "ext": "js,html,json",
  "delay": "1.5"
}

主进程 app 和窗口管理 BrowserWindow

app 、BrowserWindow

// 主进程
const { app, BrowserWindow } = require("electron");

我们使用 CommonJS 语法导入了两个 Electron 的核心模块:

  • app,它就是主进程,控制您应用程序的事件生命周期。
  • BrowserWindow,它负责创建和管理应用窗口。

您可能注意到了 app 和 BrowserWindow 两个模块名的大小写差异。 Electron 遵循 JavaScript 传统约定,以帕斯卡命名法 (PascalCase) 命名可实例化的类 (如 BrowserWindow, Tray 和 Notification),以驼峰命名法 (camelCase) 命名不可实例化的函数、变量等 (如 app, ipcRenderer, webContents) 。

// 为了更好做类型检查,如使用ts。则从 electron/main 中引入
const { app, BrowserWindow } = require('electron/main')

Electron 的许多核心模块都是 Node.js 的事件触发器,遵循 Node.js 的异步事件驱动架构。 app 模块就是其中一个。
node 事件与异步 IO 模型
Events | Node.js v21.6.2 Documentation

// node.js 自定义事件
const EventEmitter = require('events');

const myEmitter = new EventEmitter();

myEmitter.on('test', () => {
  console.log('test event occurred');
});

myEmitter.emit('test');

ready 事件

在 app 模块的事件中,ready 事件最为重要。它被触发了,说明主线程启动完毕了,这时候才可以创建 BrowserWindows 实例,也就是建立窗口界面。

通常我们使用触发器的 .on 函数来监听 Node.js 事件。但是 ready 事件特殊一点,Electron 暴露了 app.whenReady() 方法,作为其 ready 事件的专用监听器,这样可以避免直接监听 .on 事件带来的一些问题。

app.on('ready', () => {})
  
app.whenReady().then(() => {})

在 Electron 中,只有在 app 模块的 ready 事件触发后才能创建 BrowserWindows 实例。 您可以通过使用 app.whenReady() API 来监听此事件,并在其成功后调用 createWindow() 方法。

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

const createWindow = () => {
  // 新建窗口
  const win = new BrowserWindow({
    // 窗口大小
    width: 800,
    height: 600,
    // 窗口出现位置,默认是居中出现
    x: 1500,
    y: 200,
    // 窗口置顶,方便开发时查看
    alwaysOnTop: true
  })

  // 装载 UI 界面
  win.loadFile('index.html')

  // 与渲染进程的网页内容交互
  const contents = win.webContents
  console.log(contents)
}

app.whenReady().then(() => {
  createWindow()
})

webContent:主进程控制网页

主进程的主要目的是使用 BrowserWindow 模块创建和管理应用程序窗口。
BrowserWindow 类的每次实例化就是创建一个应用程序窗口,也就是一个 html 网页,也就是一个渲染进程。
可以从主进程用窗口实例的 webContent 对象与网页进行交互。

  • webContent :渲染以及控制 web 页面

比如应用启动就打开网页调试台:

const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600
  })

  // 打开调试台
  win.webContents.toggleDevTools();
}

退出应用

创建您的第一个应用程序 | Electron

在 Windows 和 Linux 上,应用的使用习惯一般是关闭所有窗口后,应用就自动退出了。现在我们希望让 Electron 应用继续保持这个使用习惯。
可以监听 app 模块的 window-all-closed 事件,并调用 app.quit() 来退出您的应用程序。

通过检查 Node.js 的 process.platform 变量,您可以针对特定平台运行特定代码。 请注意,Electron 目前只支持三个平台:win32 (Windows), linux (Linux) 和 darwin (macOS) 。

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

MacOS 用户的使用习惯并不是这样的。即使该应用的所有窗口关闭了,应用实际也没退出,重新点击它就会立即打开一个窗口。

为了让 Electron 应用在 Mac 平台继续保持这个用户习惯,可以监听 app 模块的 activate 事件。如果没有任何活动的 BrowserWindow,调用 createWindow() 方法新建一个。

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

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

装载网页到窗口

在 Electron 中,每个窗口展示一个页面,后者可以来自本地的 HTML,也可以来自 URL。

  • win.loadURL():BrowserWindow | Electron
  • win.loadFile()BrowserWindow | Electron

资源来源安全声明

html 文件需要指定加载资源的源:

 <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
      />

如果没有这句meta,electron 会在控制台报警告。因为 electron 对于渲染进程引入的资源有安全限制。
image.png

self 表示自己的。比如当前页面允许引入 b站的资源,则可以在后面补上 b 站的源链接。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
      />
    <meta
      http-equiv="X-Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
      />
    <title>Hello from Electron renderer!</title>
  </head>
  <body>
    <h1>Hello from Electron renderer!</h1>
    <p>👋</p>
  </body>
</html>

SPA 单页应用

一个窗口就是一个网页,一个网页就是一个渲染进程。

网页中的要求是符合 web 技术规范。html 中可以使用 css,引入 js 脚本。
这种原始的基本网页结构有没有似曾相识的感觉?是的,单独一个网页就是 SPA 应用。这里网页引入的 js 脚本完全可以是构建工具打包后的 js 文件。

如果加载的是 SPA,这样一个 electron 窗口,就能实现复杂的功能。
electron 也就是这样和 vue 或 react 结合的。

进程的环境

Chromium 沙盒

Chromium的一个关键安全特性是,有些进程是在沙盒中运行。沙盒隔离了这些进程的风险,同时限制了它的权限。
沙盒里的进程如果要执行权限外的操作,沙盒进程需要通过 IPC 将任务交给权限更大的进程去完成。

Chromium 中除主进程外,其他绝大部分进程都是沙盒进程,其中包括渲染器进程,以及功能性进程,如音频服务、GPU 服务和网络服务。

Electron 主进程

每个 Electron 应用都有一个单一的主进程,作为应用程序的入口点。
Electron 的主进程是一个拥有着完全操作系统访问权限的 Node.js 环境,这意味着它具有 require 模块和使用所有 Node.js API 的能力。

Electron 渲染进程

从 Electron 20 开始,渲染进程也默认启用了沙盒。 electron 中的沙盒进程和 Chromium 中的沙盒差不多。
一个沙盒化的渲染器不会有一个 Node.js 环境,它只有 chromium 网页的环境。因此渲染进程中无法执行 Node.js 代码,比如 require 函数等。

那渲染器进程用户界面怎样才能与 Node.js 和 Electron 的原生桌面功能进行交互。 而事实上,确实没有直接导入 Electron 內容脚本的方法。

配置 Electron 沙盒

对于大多数应用程序来说,沙盒是最佳选择。 在某些与沙盒不兼容的使用情况下(例如,在渲染器中使用原生的 Node.js 模块时),可以禁用特定进程的沙盒。 但这会带来安全风险,特别是当未受信任的代码或内容存在于未沙盒化的进程中时。

为单个进程禁用沙盒。

两种方式二选一:

  1. sandbox: false:关闭该渲染进程沙盒
  2. nodeIntegration: true:允许该渲染进程集成 Node 环境。这个操作也能关闭沙盒
app.whenReady().then(() => {
  const win = new BrowserWindow({
    webPreferences: {
      sandbox: false
      // nodeIntegration: true
    }
  })
  win.loadURL('https://google.com')
})

预加载脚本

概念

为了将 Electron 的不同类型的进程桥接在一起,我们需要使用被称为 预加载 的特殊脚本。

这些脚本虽运行于渲染器的环境中,却具有访问 HTML DOM 和 Node.js、Electron API 的部分权限。
这意味着预加载脚本可以增强沙盒中渲染进程的能力。

什么叫部分权限?
从 Electron 20 开始,预加载脚本也默认 沙盒化 ,不再拥有完整 Node.js 环境的访问权。

这意味着预加载脚本中的 require 函数是个打了 polyfilled 的阉割版函数。它只能加载一些特定的模块。

  • 具体是哪些可查看:进程沙盒化 | Electron

对于一些页面上想用的第三方 npm 包,你可能会想是否可以在预加载脚本中用,然后再暴露给网页脚本,也就是渲染进程用。想法很美好,现实很骨感。因为是个阉割版,所以这个想法是行不通的。
页面想用第三方 npm 包,必须借助构建工具打包成 SPA 应用。

预加载脚本注入时机就像谷歌浏览器插件中的内容脚本一样。它会在渲染器加载网页之前注入。

什么是内容脚本?
内容脚本是在网页环境中运行的文件。通过使用标准文档对象模型 (DOM),用户能够读取浏览器所访问网页的详细信息,对这些网页进行更改,并将信息传递给父级扩展程序。
比如你想写一个插件,功能是修改网页的背景色,那修改背景色的代码就是内容脚本,它会在渲染器加载网页之前注入。

注册预加载脚本

预加载脚本可以在 BrowserWindow 构造方法中的 webPreferences 选项里被附加到主进程。

const path = require("node:path");

const createWindow = () => {
    const win = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            preload: path.join(__dirname, "preload.js")
        }
    });
    win.loadFile(path.resolve(__dirname, "index.html"));
};
// ...

基本使用

预加载脚本可以增强渲染器,具体怎么做?

可以通过两个对象:

  1. Window
  2. contextBridge

Window 就是 BOM 中的那个 Window 对象。
因为预加载脚本与浏览器共享同一个全局 Window 接口,并且可以访问 Node.js API,所以它通过在全局 window 中暴露任意 API 来增强渲染器,以便你的网页内容使用。
说白了就是预加载脚本把内容挂载 window 对象上,然后网页中的脚本来 window 中取。

// preload.js
window.myAPI = {
  desktop: true
}

// renderer.js
console.log(window.myAPI)
// => undefined

怎么上面结果是 undefined?

虽然预加载脚本与其所附着的渲染器在共享着一个全局 window 对象,但您并不能从中直接附加任何变动到 window 之上,因为默认存在 contextIsolation 上下文隔离。

上下文隔离

上下文隔离意味着预加载脚本与渲染器的主要运行环境是隔离开来的,是两个独立的上下文环境。这对安全性很重要,因为它有助于阻止网站访问 Electron 的内部组件 和 您的预加载脚本可访问的高等级权限的API 。
这意味着,实际上,您的预加载脚本访问的 window 对象并不是网站所能访问的对象。 例如,如果您在预加载脚本中设置 window.hello = ‘wave’ 并且启用了上下文隔离,当网站尝试访问window.hello对象时将返回 undefined。

所以我们將使用 contextBridge 模块来安全地实现交互:

  • contextBridge:在隔离的上下文中创建一个安全的、双向的、同步的桥梁。

通过上下文桥可以安全的将内容挂载到 window 对象上,页面脚本也是从 window 对象上取取数据。

// Preload (Isolated World)
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld(
  'electron',
  {
    doThing: () => ipcRenderer.send('do-a-thing')
  }
)


// Renderer (Main World)
window.electron.doThing()

关闭该渲染进程的上下文隔离:

  • contextIsolation: false

换句话说,要无视风险释放渲染进程的所有能力,就是要允许渲染进程集成 node 环境,并关闭上下文隔离。

app.whenReady().then(() => {
  const win = new BrowserWindow({
    webPreferences: {
       nodeIntegration: true,
       contextIsolation: false
    }
  })
  win.loadURL('https://google.com')
})

结合 ts

另外,如果您正在使用 TypeScript 构建 Electron 应用程序,则必须给通过 context bridge 暴露的 API 添加类型声明。要不然 window 对象拿不到数据。

contextBridge.exposeInMainWorld('electronAPI', {
  loadPreferences: () => ipcRenderer.invoke('load-prefs')
})

您可以创建一个 interface.d.ts 类型声明文件,并且全局增强 Window 接口。

// interface.d.ts
export interface IElectronAPI {
  loadPreferences: () => Promise<void>,
}

declare global {
  interface Window {
    electronAPI: IElectronAPI
  }
}

预加载脚本的主要用途

  1. 实现 IPC 通信。在预加载脚本中暴露 IPC 通信代码。
    1. 注意:IPC 默认是无法在网页脚本中发起的,因为里面都没有 require 函数,都无法加载 ipcRenderer 模块

注意:不要直接暴露 API 函数。这样相当于所有渲染进程都有了 IPC 能力,这上下文隔离隔离了个寂寞。
正确的做法是只暴露具体实现。

// ❌ 错误使用 暴露API
contextBridge.exposeInMainWorld('myAPI', {
  send: ipcRenderer.send
})

// ✅ 正确使用 暴露具体实现
contextBridge.exposeInMainWorld('myAPI', {
  loadPreferences: () => ipcRenderer.invoke('load-prefs')
})
  1. 如果您正在为远程 URL 上托管的现有 web 应用开发 Electron 封裝,则您可在渲染器的 window 全局变量上添加自定义的属性,好在 web 客户端用上仅适用于桌面应用的设计逻辑 。相当于补充一下环境变量信息。

进程间通信

进程间通信 | Electron
ipcMain | Electron
ipcRenderer | Electron

主要会用到两个模块:

  1. 主进程:ipcMain
  2. 渲染进程:ipcRenderer

两者发消息有几种模式:

  1. 渲染进程单方面发给主进程,主进程只管收(只有单向)
  2. 渲染进程单方面发给主进程,主进程不仅能收,还能响应结果给渲染进程(双向沟通了)
  3. 主进程向渲染进程发消息
  4. 渲染进程向另一个渲染进程发消息

第二种模式。双向 IPC 的一个常见应用是从渲染器进程代码调用主进程模块并等待结果。这是最常用的,因为渲染进程比较弱鸡,大部分工作要交给主进程完成,自己只要拿结果。

  1. ipcRenderer.invoke():渲染进程发消息,并在 Promise 回调中获取结果
  2. ipcMain.handle():主进程收到消息,并在回调中返回结果
// 渲染进程 invoke 发消息
ipcRenderer.invoke('some-name', someArgument).then((result) => {
  // 此处拿到主进程返回的结果 result
})

// 主进程 handle 处理消息
ipcMain.handle('some-name', async (event, someArgument) => {
  // 调用主进程里的代码
  const result = await doSomeWork(someArgument)
  // 返回结果给渲染进程
  return result
})

注意:实际上 IPC 通信,会有三个环境参与。渲染进程、预加载脚本环境、主进程。不是上面 API 演示的这么简单。

// preload.js
const { contextBridge, ipcRenderer } = require('electron/renderer')

contextBridge.exposeInMainWorld('electronAPI', {
  openFile: () => ipcRenderer.invoke('dialog:openFile')
})
// renderer.js
const btn = document.getElementById('btn')
const filePathElement = document.getElementById('filePath')

btn.addEventListener('click', async () => {
  const filePath = await window.electronAPI.openFile()
  filePathElement.innerText = filePath
})
// main.js
const handleFileOpen = () => {};

app.whenReady().then(() => {
  ipcMain.handle('dialog:openFile', handleFileOpen)
  createWindow()
})

第三种模式,主进程向渲染进程发消息。

消息需要通过其 WebContents 实例发送到渲染器进程。 此 WebContents 实例包含一个 send 方法,其使用方式与 ipcRenderer.send 相同。
webContents是一个EventEmitter. 负责渲染和控制网页, 是 BrowserWindow 对象的一个属性。

// main.js
function createWindow () {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })

  // 主进程发送消息
  mainWindow.webContents.send('update-counter', 1),
}
app.whenReady().then(() => {
  // 主进程监听接收消息
  ipcMain.on('counter-value', (_event, value) => {
    console.log(value) // will print value to Node console
  })
  createWindow()
}
// prelaod.js
const { contextBridge, ipcRenderer } = require('electron/renderer')

contextBridge.exposeInMainWorld('electronAPI', {
  onUpdateCounter: (callback) => ipcRenderer.on('update-counter', (_event, value) => callback(value)),
  counterValue: (value) => ipcRenderer.send('counter-value', value)
})
// renderer.js
const counter = document.getElementById('counter')

// 渲染进程监听接收消息
window.electronAPI.onUpdateCounter((value) => {
  const oldValue = Number(counter.innerText)
  const newValue = oldValue + value
  counter.innerText = newValue.toString()
  // 渲染进程发送消息
  window.electronAPI.counterValue(newValue)
})

案例:番茄钟应用

番茄钟应用 2

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

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

相关文章

vs2022方法上面看不到引用条数

vs2022 Win11 开发过程中经常要查看方法的引用情况&#xff0c;这个功能一直好好的&#xff0c;但是有一天突然不行了&#xff0c;看不到引用了&#xff0c;这就让人很难受&#xff0c;从网上查找资料说需要设置CodeLens&#xff0c;这个一直是勾着的&#xff0c;没动过这个设…

JVM-对象创建与内存分配机制深度剖析 3

JVM对象创建过程详解 类加载检查 虚拟机遇到一条new指令时&#xff0c;首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用&#xff0c;并且检查这个 符号引用代表的类是否已被加载、解析和初始化过。如果没有&#xff0c;那必须先执行相应的类加载过程。 new…

好用的便签软件,好用便签排行榜

在生活和工作中&#xff0c;便签软件的使用已经成为我们日常不可或缺的工具。随着技术的发展&#xff0c;便签软件的功能越来越强大&#xff0c;用户也有了更多选择。好用的便签软件有哪些&#xff0c;希望大家能从好用便签排行榜中找到适合自己的工具。 ##1.好用便签 好用便签…

CNC机加工引入复合机器人可以提高生产效率,降低成本

CNC加工企业在过去依赖大量的人工来完成生产线上的各项任务&#xff0c;包括CNC机床的上下料、物料搬运以及部分装配工作。然而&#xff0c;随着产能需求的不断增长和人工成本的持续上升&#xff0c;企业逐渐意识到自动化升级的重要性与迫切性。 面临的挑战与需求&#xff1a; …

Win系统创建虚拟环境利用pyinstaller打包python文件为.exe文件

0. 前提&#xff1a;win系统已经安装Aaconda&#xff0c;检查是否安装成功&#xff0c;命令如下&#xff1a; conda -V输出如下则安装成功&#xff0c;否则需要安装网上教程重新安装一下&#xff08;PS&#xff1a;内存允许的话&#xff0c;建议装固态盘&#xff0c;不然很慢&…

小孩近视用白炽灯好吗?多款热门护眼台灯实测分享

如今对于家长而言&#xff0c;最关心的事情除了孩子的学习成绩以外&#xff0c;最重要的就是孩子的视力健康问题&#xff0c;现在的孩子近视率实在太高了&#xff0c;不少还在小学阶段的学生都开始配戴上了眼镜。所以想要保护孩子的视力健康一盏好的台灯肯定是必不可少的&#…

5G工业网关是什么?

随着科技的飞速发展&#xff0c;5G技术已经逐渐渗透到我们生活的方方面面。而在工业领域&#xff0c;5G工业网关作为连接工业设备与网络的关键组件&#xff0c;正发挥着越来越重要的作用。HiWoo Box其5G工业网关产品以其卓越的性能和稳定性&#xff0c;正助力企业实现数字化转型…

枚举 --java学习笔记

什么是枚举 枚举是一种特殊类 格式&#xff1a; 修饰符 enum 枚举类名{ 名称1&#xff0c;名称2&#xff0c;...&#xff1b; //枚举类的第一行必须罗列的是枚举对象的名字 其他成员... } 枚举类的第一行只能罗列一些名称&#xff0c;这些名称都是常量&#xff0c;…

为什么不用 index 做 key?

“在 Vue 中&#xff0c;我们在使用 v-for 渲染列表的时候&#xff0c;为什么要绑定一个 key&#xff1f;能不能用 index 做 key&#xff1f;” 在聊这个问题之前我们还得需要知道 Vue 是如何操作 DOM 结构的。 虚拟DOM 我们知道&#xff0c;Vue 不可以直接操作 DOM 结构&am…

vscode 使用ssh进行远程开发 (remote-ssh),首次连接及后续使用,详细介绍

在vscode添加remote ssh插件 首次连接 选择左侧栏的扩展&#xff0c;并搜索remote ssh 它大概长这样&#xff0c;点击安装 安装成功后&#xff0c;在左侧栏会出现远程连接的图标&#xff0c;点击后选择ssh旁加号便可以进行连接。 安装成功后vscode左下角会有一个图标 点击图…

LCR 185. 统计结果概率

解题思路&#xff1a; 动态规划 逆向推理会有越界问题&#xff0c; 若希望递推计算 f(2,2)&#xff0c;由于一个骰子的点数和范围为 [1,6] &#xff0c;因此只应求和 f(1,1) &#xff0c;即 f(1,0) , f(1,−1) , ... , f(1,−4) 皆无意义。 故采用正向推理&#xff0c;如下图…

三八妇女节智慧花店/自动售花机远程视频智能监控解决方案

一、项目背景 国家统计局发布的2023年中国经济年报显示&#xff0c;全年社会消费品零售总额471495亿元&#xff0c;比上年增长7.2%。我国无人零售整体发展迅速&#xff0c;2014年市场规模约为17亿元。无人零售自助终端设备市场规模超过500亿元&#xff0c;年均复合增长率超50%。…

C#实现快速排序算法

C#实现快速排序算法 以下是C#中的快速排序算法实现示例&#xff1a; using System;class QuickSort {// 快速排序入口函数public static void Sort(int[] array){QuickSortRecursive(array, 0, array.Length - 1);}// 递归函数实现快速排序private static void QuickSortRecu…

IonIa: High-Performance Replication for Modern Disk-based KV Stores——论文泛读

FAST 2024 Paper 论文阅读笔记整理 问题 键值存储在数据中心应用程序中发挥着核心作用&#xff0c;许多KV存储都是使用写优化索引&#xff08;WOI&#xff09;&#xff08;如LSM[58]&#xff09;构建的&#xff0c;称为WO-KV存储。最近的WO-KV存储针对现代SSD进行了优化&…

python: import用法

目录 1.import1.1从当前文件夹下1.2从python库目录中导入库2.使用1.3 总结1.4 2.定义参考&#xff1a; 1.import 常用 1.1从当前文件夹下 1.2从python库目录中导入库 在控制台通过pip install 库名的命令安装库&#xff0c;库将被安装到python文件夹的库目录下。 2.使用 …

用 ChatGPT 帮自己修英文简历 — 程序员篇

写英文简历一直是许多人的痛处&#xff0c;怎么写得文法正确又吸引人&#xff0c;对于不是英文母语人士的人&#xff0c;是相当困难的。即使网路上有很多教学文&#xff0c;但看完之后还是不知道该怎么描述。导致很多人最后还是去找简历编修。不过现在有了 ChatGPT 这类 AI 工具…

离散数学——(3)联结词及对应的真值指派,最小全功能联结词集,对偶式,范式,范式存在定理,小项

目录 1.联结词及对应的真值指派 2.最小全功能联结词集 3.对偶式 4.范式 1.析取范式 5.范式存在定理 6.小项 1.联结词及对应的真值指派 2.最小全功能联结词集 3.对偶式 4.范式 1.析取范式 5.范式存在定理 6.小项

20个Python函数程序实例

前面介绍的函数太简单了&#xff1a; 以下是 20 个不同的 Python 函数实例 下面深入一点点&#xff1a; 以下是20个稍微深入一点的&#xff0c;使用Python语言定义并调用函数的示例程序&#xff1a; 20个函数实例 简单函数调用 def greet():print("Hello!")greet…

连接kafka报错:java.io.IOException: Can‘t resolve address:

修改电脑host文件:C:\Windows\System32\drivers\etc\hosts 加上一行 192.168.1.XXX MHA_SLAVE2&#xff08;192.168.1.XXX 这个是安装kafka 的服务器地址&#xff0c;MHA_SLAVE2是kafka的容器id&#xff09;

国家妇女节放假是法定的假日

在这个充满活力和希望的春天&#xff0c;我们迎来了一个特殊的节日——国家妇女节。这是一个属于所有女性的节日&#xff0c;是一个庆祝女性成就、关爱女性权益的时刻。在这个特殊的日子里&#xff0c;我们不禁要问&#xff1a;国家妇女节放假是法定假日吗&#xff1f;让我们一…