当前位置: 首页 > news >正文

electron实现软件(热)更新(附带示例源码)

热更新指的是:electron 程序已经开启,在不关闭的情况下执行更新,需要我们把远程的app.asar文件下载到本地执行替换,然而 在electron应用程序开启状态是无法直接下载app.asar文件的,下载会检查出app.asar文件被占用,所以我们需要在本地将app.asar文件反编译,编译出一个app文件夹,里面有项目所需的所有源码文件,这时通过vue或react打包的渲染进程代码是经过压缩的,但是主进程代码会直接暴露,所以刚好,我们可以将主进程代码做压缩混淆,然后生成一个app.zip压缩包,上传至服务器,然后下载这个压缩包,解压,将其编译成app.asar文件替换到resources目录中,从而实现electron软件的热更新。

  1. 主进程(nodejs)侧代码:
const {app, BrowserWindow, ipcMain} = require('electron')
const path = require('path')
const fs = require("fs");
const http = require("http");
const asar = require('asar');
const AdmZip = require('adm-zip');
const fsExtra = require('fs-extra');const mainData = require("./mainData");
const txtConsole = require("./txtConsole");//当前环境
const production = mainData?.production;//Electron 安装根目录
const rootPath = production === 'dev' ? path.resolve('./public') : path.dirname(app.getPath('exe'));
mainData.rootPath = rootPath;// 软件更新配置信息(目前需要手动修改~~~)
const winUpdateConfig = {currentVersion: null, //当前版本updateVersionFilePath: 'http://103.117.121.53:8002/latest', //远程版本信息路径updateFilePath: 'http://103.117.121.53:8002/app.zip', //远程包路径localUpdateVersionFilePath: production === 'dev' ? `${rootPath}/latest` : `${rootPath}/resources/latest`, //本地版本信息路径localUpdateFilePath: production === 'dev' ? rootPath : `${rootPath}/resources`, //本地包路径updateSteps: [{id: 1, desc: '开始下载并解压更新文件,请勿重启!', active: 'active'},{id: 2, desc: '下载并解压完成, 开始覆盖安装!', active: 'wait'},{id: 3, desc: '更新完毕, 即将重启,请稍候!(第3步完成后也可以手动重启)', active: 'wait'},], //更新步骤  active:正在进行  wait:等待   success:执行成功  error: 执行失败
};let versionInfo = ''; //获取最新版本信息
let locallatest = ''; //本地版本号function appInit() {txtConsole.log('初始化');try {locallatest = fs.readFileSync(winUpdateConfig.localUpdateVersionFilePath, 'utf-8');locallatest = JSON.parse(locallatest);//设置当前版本信息winUpdateConfig.currentVersion = locallatest?.version;txtConsole.log('已设置当前版本信息', locallatest?.version);//删除日志txtConsole.clearLog();} catch (err) {txtConsole.log(err);}
}//创建主窗口
function createWindow() {const mainWindow = new BrowserWindow({width: 800,height: 600,webPreferences: {// preload: path.join(__dirname, 'preload.js'),nodeIntegration: true,contextIsolation: false,webSecurity: false, //禁用同源策略}});mainWindow.loadFile('index.html').then();// 打开开发者工具 控制台if (mainData.winControl === 'dev') {mainWindow.webContents.openDevTools();}// 检查更新ipcMain.on('window-version', function (event) {try {txtConsole.log('检查更新', versionInfo);if (!versionInfo) {!event?.sender?.isDestroyed() &&event?.sender?.send('window-version-err-msg', '更新文件读取失败');return;}const v = {...(versionInfo || {})};//最新版本号let firNewVersion = versionInfo?.version?.split('.')?.[0]; //第一位let secNewVersion = versionInfo?.version?.split('.')?.[1]; //第二位let thiNewVersion = versionInfo?.version?.split('.')?.[2]; //第三位//当前版本号let firOldVersion = versionInfo?.currentVersion?.split('.')?.[0]; //第一位let secOldVersion = versionInfo?.currentVersion?.split('.')?.[1]; //第二位let thiOldVersion = versionInfo?.currentVersion?.split('.')?.[2]; //第三位//按位比较是否需要更新if (Number(firNewVersion || 10000) > Number(firOldVersion || 10000)) {v['versionVisible'] = true;}else if (Number(secNewVersion || 10000) > Number(secOldVersion || 10000)) {v['versionVisible'] = true;}else if (Number(thiNewVersion || 10000) > Number(thiOldVersion || 10000)) {v['versionVisible'] = true;}else {v['versionVisible'] = false;v['currentVersion'] = versionInfo?.version;}if (!v['versionVisible']) {let latest = fs.readFileSync(winUpdateConfig.localUpdateVersionFilePath, 'utf-8');latest = JSON.parse(latest);latest.version = versionInfo?.version || latest?.version;latest.currentVersion = versionInfo?.version || latest?.version;fs.writeFileSync(winUpdateConfig.localUpdateVersionFilePath, JSON.stringify(latest));txtConsole.log('hot: ', latest.version);}txtConsole.log('versionVisible=> ', v['versionVisible']);!event?.sender?.isDestroyed() && event?.sender?.send('window-version-msg', v);} catch (err) {!event?.sender?.isDestroyed() &&event?.sender?.send('window-version-err-msg', '更新文件读取失败');txtConsole.log('检查更新err:', err);}});// 下载更新文件ipcMain.on('window-download-newfile', function (event) {txtConsole.log('开始下载并解压更新文件  热更新');event?.sender?.send('window-download-newfile-msg', winUpdateConfig.updateSteps);const file = fs.createWriteStream(path.resolve(winUpdateConfig.localUpdateFilePath, 'app.zip'),);let downloadedBytes = 0;let totalBytes = 0;http.get(winUpdateConfig.updateFilePath, (response) => {totalBytes = parseInt(response?.headers['content-length'], 10);let prevTimestamp = Date.now();response?.on('data', (chunk) => {downloadedBytes += chunk.length;const timestamp = Date.now();const timeDiff = timestamp - prevTimestamp;// 每1.5秒钟更新一次进度if (timeDiff >= 1500) {const progress = ((downloadedBytes / totalBytes) * 100).toFixed(2);txtConsole.log(`下载进度:${progress}% `, totalBytes);prevTimestamp = timestamp;event?.sender?.send('window-download-progress-msg', Math.min(Number(progress), 80));}});response?.pipe(file);}).on('error', (err) => {txtConsole.log(`下载错误: ${err.message}`);event?.sender?.send('window-download-newfile-err-msg', '更新文件下载失败');});file?.on('finish', function () {event.sender.send('window-download-progress-msg', 90);winUpdateConfig.updateSteps[0]['active'] = 'success';winUpdateConfig.updateSteps[1]['active'] = 'active';event?.sender?.send('window-download-newfile-msg', winUpdateConfig.updateSteps);// 文件已经完全写入磁盘,开始解压try {const zip = new AdmZip(path.resolve(winUpdateConfig.localUpdateFilePath, 'app.zip'), void 0);zip.extractAllTo(winUpdateConfig.localUpdateFilePath, true, void 0, void 0);} catch (err) {txtConsole.log('解压异常 error: ', err);!event?.sender?.isDestroyed() &&event?.sender?.send('window-download-newfile-err-msg', '解压异常');return;}winUpdateConfig.updateSteps[1]['active'] = 'success';winUpdateConfig.updateSteps[2]['active'] = 'active';event?.sender?.send('window-download-newfile-msg', winUpdateConfig.updateSteps);event?.sender?.send('window-download-progress-msg', 95);const sourceDir = path.join(winUpdateConfig.localUpdateFilePath, 'apps');const destPath = path.join(winUpdateConfig.localUpdateFilePath, 'app.asar');asar.createPackage(sourceDir, destPath).then(() => {if (fs.existsSync(path.resolve(winUpdateConfig.localUpdateFilePath, 'app.zip'))) {fs.unlinkSync(path.resolve(winUpdateConfig.localUpdateFilePath, 'app.zip'));}txtConsole.log('更新完毕');event.sender.send('window-download-progress-msg', 100);winUpdateConfig.updateSteps[2]['active'] = 'success';event?.sender?.send('window-download-newfile-msg',winUpdateConfig.updateSteps,'success',);//设置当前版本信息try {let latest = fs.readFileSync(winUpdateConfig.localUpdateVersionFilePath, 'utf-8');latest = JSON.parse(latest);latest.version = versionInfo.version;fs.writeFileSync(winUpdateConfig.localUpdateVersionFilePath, JSON.stringify(latest));txtConsole.log('更新后已设置当前版本信息', latest?.version);//删除apps文件夹  防止执行文件夹内的代码deleteFolderRecursive(sourceDir);} catch (err) {txtConsole.log(err);}}).catch((err) => {txtConsole.log('创建asar文件失败: ', err);event.sender.send('window-download-newfile-err-msg', 'asar文件创建失败');});});file?.on('error', function (err) {txtConsole.log('更新asar=>Error: ', err);event.sender.send('window-download-newfile-err-msg', err);});});
}//检查更新
function checkUpdate(callback) {txtConsole.log('检查更新');http.get(winUpdateConfig.updateVersionFilePath, (res) => {res.on('data', (chunk) => {versionInfo += chunk;});res.on('end', () => {try {if (versionInfo && versionInfo?.indexOf('404 Not Found') < 0) {versionInfo = JSON.parse(versionInfo);winUpdateConfig.updateFilePath = versionInfo.updateFilePath;//热更最新信息let asarVersionInfo = {newVersionDesc: versionInfo.newVersionDesc,currentVersion: winUpdateConfig.currentVersion,};versionInfo.currentVersion = winUpdateConfig.currentVersion;let writeNewVersonInfo;//不存在则创建latest文件if (!fs.existsSync(winUpdateConfig.localUpdateVersionFilePath)) {writeNewVersonInfo = versionInfo;txtConsole.log('latest文件重新创建成功');}else {let currentVersion = fs.readFileSync(winUpdateConfig.localUpdateVersionFilePath,'utf8',);currentVersion = JSON.parse(currentVersion);currentVersion['updateFilePath'] = '';//只覆盖热更版本信息writeNewVersonInfo = {...currentVersion, ...asarVersionInfo};}//将整理好的配置文件信息写入fs.writeFileSync(winUpdateConfig.localUpdateVersionFilePath,JSON.stringify(writeNewVersonInfo),);// txtConsole.log('已将新的更新配置文件信息写入:', JSON.stringify(writeNewVersonInfo));txtConsole.log(`更新检查完毕:最新版本:${versionInfo.version}, 当前版本:${asarVersionInfo.currentVersion}`,);txtConsole.log('-------------------------------------------------------');callback?.(null, versionInfo);}else {txtConsole.log('更新配置文件读取失败');callback?.('更新配置文件读取失败');}} catch (err) {txtConsole.log('更新配置文件覆写失败');callback?.('更新配置文件覆写失败');}});}).on('error', (error) => {txtConsole.log(`更新配置文件下载失败: ${error.message}`);callback?.('更新配置文件下载失败');});
}//删除更新文件
function deleteFolderRecursive(folderPath) {if (fs.existsSync(folderPath)) {fs.readdirSync(folderPath).forEach((file) => {const curPath = path.join(folderPath, file);if (fs.lstatSync(curPath).isDirectory()) {// 递归删除子文件夹deleteFolderRecursive(curPath);}else {// 删除文件fs.unlinkSync(curPath);}});// 删除子文件夹后删除文件夹本身fs.rmdirSync(folderPath);}
}app.whenReady().then(() => {//初始化appInit();//检查更新checkUpdate(async (check, versionInfo = {}) => {if (check) {txtConsole.log('检查更新执行失败');}else {txtConsole.log('检查更新执行成功');}createWindow();app.on('activate', function () {if (BrowserWindow.getAllWindows().length === 0) createWindow()})});
})app.on('window-all-closed', function () {if (process.platform !== 'darwin') app.quit()
})
  1. 渲染进程侧代码(以原生为例):
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1.0"/><title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
<button onclick="onCheckUpdate()">检查更新</button>
<button onclick="onUpdateVersion()">测试更新</button><div class="updateInfo"></div>
<div class="descInfo"></div><script>const {ipcRenderer} = require("electron");const onVersion = {updateDsec: [],process: 0,};//检查更新function onCheckUpdate() {ipcRenderer?.send('window-version');ipcRenderer?.once('window-version-msg', (_, bool) => {document.querySelector('.updateInfo').innerHTML = JSON.stringify(bool);});ipcRenderer?.once('window-version-err-msg', (_, err) => {document.querySelector('.updateInfo').innerHTML = err;});}//测试更新function onUpdateVersion() {onVersion.updateDsec = [];ipcRenderer?.send('window-download-newfile');//监听下载版本信息ipcRenderer?.on('window-download-newfile-msg', (event, updateDsec, status) => {if (!event.handel) {event.handel = true;onVersion.isStartUpdate = true;onVersion.updateDsec = updateDsec;document.querySelector('.descInfo').innerHTML = updateDsec.map(item => `<span style="color:${item.active === 'success' ? 'green' : 'orangered'}">${item.desc}</span>`).join('</br>');if (status === 'success') {ipcRenderer?.send('window-restart-app');}onVersion.visible = true;}});//监听更新包下载进度ipcRenderer?.on('window-download-progress-msg', (event, process) => {if (!event.handel) {event.handel = true;document.querySelector('.updateInfo').innerHTML = String("完成进度:" + process + '%');}});//监听下载版本错误信息ipcRenderer?.once('window-download-newfile-err-msg', (event, res) => {console.log(res)});}
</script>
</body>
</html>
  1. 轮子(实现压缩混淆反编译):
//生成 反编译app.asar  并生成压缩包
const asar = require('asar');
const path = require('path');
const fs = require('fs');
const fsExtra = require('fs-extra');
const zlib = require('zlib');
const archiver = require('archiver');
const uglify = require('uglify-js');
const moment = require('moment');
const {exec} = require('child_process');
const JavaScriptObfuscator = require('javascript-obfuscator');const mainData = require('./mainData');const startTime = moment().unix(); //秒级时间戳const rootPath = path.resolve(__dirname); // 获取项目根路径
const asarPath = './build/win-ia32-unpacked/resources/app.asar'; // 获取 app.asar 文件路径
const sourceDir = './apps'; // 要压缩的文件夹路径
const asarAppPath = './apps/apps'; // asar反编译文件的存放路径
const buildPath = './build'; //electron 打包后的build文件夹
const destFile = './app.zip'; // 压缩后的文件路径
const publicLogPath = './public/log.txt';
const publicAsarPath = './public/app.asar';// 配置环境路径为项目根路径
const env = Object.assign({}, process.env, {PATH: rootPath + ';' + process.env.PATH,npm_config_prefix: 'C:\\Program Files\\nodejs\\npm', // 这里是你的 npm 安装路径
});const Console = {log(p1 = '', p2 = '', p3 = '', p4 = '', p5 = '') {console.log(`${moment().format('HH:mm:ss')}  |  ${p1}${p2}${p3}${p4}${p5}`);},
};//压缩主进程 main.js 相关代码
function zipMainJS() {try {const dir = path.resolve(asarAppPath, 'main.js');const dirJs = fs.readFileSync(dir, 'utf8');//压缩代码 mangle: true,const result = uglify.minify(dirJs, {mangle: {toplevel: true,},});// 混淆代码const obfuscationResult = JavaScriptObfuscator.obfuscate(result.code, {compact: true,controlFlowFlattening: true,controlFlowFlatteningThreshold: 0.75,numbersToExpressions: true,simplify: true,shuffleStringArray: true,splitStrings: true,stringArrayThreshold: 0.75,});fs.writeFileSync(dir, obfuscationResult.getObfuscatedCode());return true;} catch (err) {Console.log(err);return false;}
}//添加开始执行 app.asar反编译逻辑
async function init() {//执行app.asar 反编译、压缩、混淆Console.log('正在执行app.asar 反编译、压缩、混淆');// 将 app.asar 解压缩到指定文件夹中asar.extractAll(asarPath, asarAppPath);Console.log('正在压缩app文件夹到项目根目录');//压缩main.js相关代码let zipRes = zipMainJS();if (!zipRes) {Console.log('!!!压缩main.js主进程代码失败!');return;}Console.log('主进程相关代码压缩完毕');//再次生成 app.asarasar.createPackage(asarAppPath, asarPath).then(() => {Console.log('已再次生成 app.asar 文件(代码压缩后的asar文件)');onAppZip();});
}function onAppZip() {// 创建一个可写流,将压缩后的文件写入到目标文件中const destStream = fs.createWriteStream(destFile);// 创建一个 archiver 实例const archive = archiver('zip', {zlib: {level: zlib.constants.Z_BEST_COMPRESSION},});// 将可写流传递给 archiver 实例archive.pipe(destStream);// 将要压缩的文件夹添加到 archiver 实例中archive.directory(sourceDir, false, null);// 完成压缩并关闭可写流archive.finalize();// 监听可写流的 'close' 事件,表示压缩完成destStream.on('close', () => {Console.log(`压缩完毕,压缩包路径:【${path.resolve(__dirname, destFile)}`);Console.log('共用时:' + (moment().unix() - startTime) + '秒');});
}try {if (mainData?.production === 'dev') {throw "请将环境切换为生产环境 mainData.js => 【const production = 'pro';】";}if (mainData?.winControl === 'dev') {throw '请关闭主窗口调试控制台!' + 'winControl';}fsExtra.removeSync(buildPath);Console.log('已删除build文件夹内容');fsExtra.removeSync(publicLogPath);Console.log('已删除public/log.txt');fsExtra.removeSync(publicAsarPath);Console.log('已删除public/app.asar');fsExtra.removeSync(asarAppPath);Console.log('已删除apps');//执行 打包命令Console.log('正在执行【npm run packager32】命令');exec('npm run packager32', env, (error, stdout, stderr) => {if (error) {Console.log(`执行出错: ${error}`);return;}stderr && Console.log('【npm run packager32】 stderr=>', stderr);//生成app.init();});} catch (err) {Console.log(err);
}

示例Demo: https://github.com/qglovehy/electron-updater.git

相关文章:

electron实现软件(热)更新(附带示例源码)

热更新指的是&#xff1a;electron 程序已经开启&#xff0c;在不关闭的情况下执行更新&#xff0c;需要我们把远程的app.asar文件下载到本地执行替换&#xff0c;然而 在electron应用程序开启状态是无法直接下载app.asar文件的&#xff0c;下载会检查出app.asar文件被占用&…...

飞天使-k8s知识点12-kubernetes散装知识点1-架构有状态资源对象分类

文章目录 k8s架构图有状态和无状态服务 资源和对象对象规约和状态 资源的对象-资源的分类元数据型与集群型资源命名空间 k8s架构图 有状态和无状态服务 区分有状态和无状态服务有利于维护yaml文件 因为配置不同资源和对象 命令行yaml来定义对象对象规约和状态 规约 spec 描述…...

mhz_c1f

信息收集 探测到存活主机的IP地址为 192.168.101.32 # nmap -sT --min-rate 10000 -p- 192.168.101.32 -oN port.nmap Starting Nmap 7.94 ( https://nmap.org ) at 2024-02-03 13:41 CST Nmap scan report for 192.168.101.32 Host is up (0.0020s latency). Not shown: 6553…...

Excel——高级筛选匹配条件提取数据

一、筛选多条件 Q&#xff1a;筛选多个条件&#xff0c;并将筛选出的内容复制到其他区域 点击任意一个单元格 点击【数据】——【筛选】——【高级筛选】 选择【将筛选结果复制到其他位置】——在【列表区域】 鼠标选择对应的区域位置&#xff0c;条件区域一定要单独写出来&a…...

Python初学者学习记录——python基础综合案例:数据可视化——动态柱状图

一、案例效果 通过pyecharts可以实现数据的动态显示&#xff0c;直观的感受1960~2019年世界各国GDP的变化趋势 二、通过Bar构建基础柱状图 反转x轴和y轴 标签数值在右侧 from pyecharts.charts import Bar from pyecharts.options import LabelOpts# 构建柱状图对象 bar Bar()…...

1.27马尔科夫链,抽样蒙特卡洛模拟(逆转化方法,接受拒绝矩阵),马尔科夫链蒙特卡洛MCMC,隐马尔科夫(HMM(V算法剪枝优化),NLP)

马尔科夫链 蒙特卡洛法模拟 抽样&#xff0c;逆转换方法 就是说由系统自带的随机函数RANDOM&#xff0c;通过下面这个方法&#xff0c;可以变为对应的随机模拟函数 就是说要实现蒙特卡洛模拟&#xff0c;是要先有一个概率表达式&#xff0c;然后基于这个概率表达式&#xff0…...

MC34063异常发热分析

问题描述&#xff1a; 工程现场反馈若干电源转换模块损坏&#xff0c;没有输出。拿到问题模块后&#xff0c;查看有一个MC34063周围的PCB有比较明显的高温痕迹&#xff0c;配套的电感也有明显的高温过热痕迹。 问题调查&#xff1a; MC34063的电路非常经典&#xff08;虽然自…...

获取真实 IP 地址(一):判断是否使用 CDN(附链接)

一、介绍 CDN&#xff0c;全称为内容分发网络&#xff08;Content Delivery Network&#xff09;&#xff0c;是一种网络架构&#xff0c;旨在提高用户对于网络上内容的访问速度和性能。CDN通过在全球各地部署分布式服务器节点来存储和分发静态和动态内容&#xff0c;从而减少…...

跨越财务困境,聚道云软件连接器如何助力企业轻松实现数字化转型?

客户介绍 某家庭服务科技有限公司是一家专注于提供高品质家庭服务的综合性企业。公司以“让家庭生活更美好”为使命&#xff0c;致力于为每一位客户提供专业、细致、周到的家庭服务。作为一家具有社会责任感的企业&#xff0c;该公司积极履行企业公民义务&#xff0c;关注家庭…...

Python接口自动化测试框架运行原理及流程

这篇文章主要介绍了Python接口自动化测试框架运行原理及流程,文中通过示例代码介绍的非常详细&#xff0c;对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 本文总结分享介绍接口测试框架开发&#xff0c;环境使用python3selenium3unittestddtrequests测试框…...

strtok的使用

Strtok&#xff1a;原型char *strtok(char s[], const char *delim); s为要分解的字符&#xff0c;delim为分隔符字符&#xff08;如果传入字符串&#xff0c;则传入的字符串中每个字符均为分割符&#xff09;。首次调用时&#xff0c;s指向要分解的字符串&#xff0c;之后再次…...

0206作业

TCP&#xff08;传输控制协议&#xff09;和 UDP&#xff08;用户数据报协议&#xff09;是两种常用的网络传输协议。它们之间的主要区别在于&#xff1a; 可靠性&#xff1a;TCP 是一种可靠的传输协议&#xff0c;它提供了数据传输的确认、重传和排序功能。如果数据在传输过程…...

数据结构-栈

1.容器 容器用于容纳元素集合&#xff0c;并对元素集合进行管理和维护&#xff0e; 传统意义上的管理和维护就是&#xff1a;增&#xff0c;删&#xff0c;改&#xff0c;查&#xff0e; 我们分析每种类型容器时&#xff0c;主要分析其增&#xff0c;删&#xff0c;改&#xff…...

CentOS7搭建k8s-v1.28.6集群详情

文章目录 1.灌装集群节点操作系统1.1 设置hosts1.2 设置nameserver1.3 关闭防火墙1.4 关闭Selinux1.5 关闭Swap分区1.6 时间同步1.7 调整内核参数1.8 系统内核升级 2.安装Docker2.1 卸载旧Docker2.2 配置Docker软件源2.3 安装Docker 3.部署Kubernets集群3.1 设置 K8s 软件源3.2…...

Android实现底部导航栏方法(Navigation篇)

Navigation实现底部导航栏 前言导入和基本使用导入基础使用创建nav文件编辑Nav文件添加页面&#xff08;代码版&#xff09;添加页面&#xff08;图解版&#xff09; 创建导航动作 action创建action&#xff08;代码版&#xff09;创建action&#xff08;图解版&#xff09; 编…...

python 爬虫篇(1)---->re正则的详细讲解(附带演示代码)

re正则的详细讲解 文章目录 re正则的详细讲解前言4.re正则表达式(1)e正则的匹配模式(2) re.search 的使用(3)re.findall()的使用(4)re.sub()的使用结语前言 大家好,今天我将开始更新python爬虫篇,陆续更新几种解析数据的方法,例如 re正则表达式beautifulsoup xpath lxml 等等,…...

(超详细)10-YOLOV5改进-替换CIou为Wise-IoU

yolov5中box_iou其默认用的是CIoU&#xff0c;其中代码还带有GIoU&#xff0c;DIoU&#xff0c;文件路径&#xff1a;utils/metrics.py&#xff0c;函数名为&#xff1a;bbox_iou 将下面代码放到metrics.py文件里面&#xff0c;原来的bbox_iou函数删掉 class WIoU_Scale: mon…...

Java-并发高频面试题-2

接着之前的Java-并发高频面试题 7. synchronized的实现原理是怎么样的&#xff1f; 首先我们要知道synchronized它是解决线程安全问题的一种方式&#xff0c;而具体是怎么解决的呢&#xff1f;主要是通过加锁的方式来解决 在底层实现上来看 是通过 monitorenter、monitorexit…...

Windows安装Redis

安装Redis是一个比较简单的过程&#xff0c;以下是在Windows上安装Redis的基本步骤&#xff1a; 下载Redis&#xff1a;首先&#xff0c;你需要从Redis官方网站&#xff08;https://redis.io/download&#xff09;下载适合Windows的Redis安装包。你可以选择稳定版本或者开发版本…...

Nicn的刷题日常之 有序序列判断

目录 1.题目描述 描述 输入描述&#xff1a; 输出描述&#xff1a; 示例1 示例2 示例3 2.解题 1.题目描述 描述 输入一个整数序列&#xff0c;判断是否是有序序列&#xff0c;有序&#xff0c;指序列中的整数从小到大排序或者从大到小排序(相同元素也视为有序)。 数据…...

CTF show Web 红包题第六弹

提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框&#xff0c;很难让人不联想到SQL注入&#xff0c;但提示都说了不是SQL注入&#xff0c;所以就不往这方面想了 ​ 先查看一下网页源码&#xff0c;发现一段JavaScript代码&#xff0c;有一个关键类ctfs…...

Java 语言特性(面试系列1)

一、面向对象编程 1. 封装&#xff08;Encapsulation&#xff09; 定义&#xff1a;将数据&#xff08;属性&#xff09;和操作数据的方法绑定在一起&#xff0c;通过访问控制符&#xff08;private、protected、public&#xff09;隐藏内部实现细节。示例&#xff1a; public …...

Python:操作 Excel 折叠

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...

ffmpeg(四):滤镜命令

FFmpeg 的滤镜命令是用于音视频处理中的强大工具&#xff0c;可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下&#xff1a; ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜&#xff1a; ffmpeg…...

2023赣州旅游投资集团

单选题 1.“不登高山&#xff0c;不知天之高也&#xff1b;不临深溪&#xff0c;不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档&#xff09;&#xff0c;如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下&#xff0c;风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...

排序算法总结(C++)

目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指&#xff1a;同样大小的样本 **&#xff08;同样大小的数据&#xff09;**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...

C#中的CLR属性、依赖属性与附加属性

CLR属性的主要特征 封装性&#xff1a; 隐藏字段的实现细节 提供对字段的受控访问 访问控制&#xff1a; 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性&#xff1a; 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑&#xff1a; 可以…...

Golang——6、指针和结构体

指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...

嵌入式常见 CPU 架构

架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集&#xff0c;单周期执行&#xff1b;低功耗、CIP 独立外设&#xff1b;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel&#xff08;原始…...