vue+electron项目实战总结(遇到了哪些问题,是如何解决的,哪个阶段需要做什么,附带一些常用方法)

news2024/9/21 18:49:00

electron作为一个将网页打包成桌面应用的工具 非常强大,在使用electron的时候 要相信 它可以实现所有现代软件能够支撑的功能,下面我总结一下我在 vue+electron经过4次 大版本更新才趋于稳定的开发经验。

一、开发套路: 消息通信+数据驱动

使用 vue + electron开发 分为主进程(main.js) 和 渲染进程(vue网页)

消息通信

通过主进程和渲染进程间通信机制 可以将功能划分,网页能够实现的功能交给vue ,需要获取系统参数、操作系统文件、使用 nodejs API 等等 的功能交给主进程 ,如果想通过网页来执行主进程实现的功能,则在渲染进程向主进程发消息,由主进程来实现。

数据驱动

vue等等框架 是由组件化+数据驱动dom 来实现快速开发,必要时为了避免接口调用闭环问题需要用事件驱动…。

二、遇到的问题 (最大的问题就是兼容性。。。)

1. vscode调试控制台乱码。。。

打开 vscode 快捷键 ctrl+J 打开控制台 选择终端 输入 chcp 65001
在这里插入图片描述

2. win7 打开electron打包的exe 安装后提示缺少dll文件。。。

通过降低electron版本 我使用的electron版本是:"electron": "^11.0.0", nodejs版本 v16.17.0 electron-builder帮助我们解决了很多兼容性问题,只是不同的electron版本我还需要研究都有哪些新特性,对操作系统都有哪些要求。

3. 大概1/3的电脑看不到界面。。。

因为我在package.json=> build属性中配置了

"win": {
  "requestedExecutionLevel": "requireAdministrator"
},

用于设置应用程序的执行级别为管理员权限。这意味着应用程序将以管理员身份运行,如果不是以管理员身份打开的应用程序,不会提示报错,也不会弹出页面。。。

我的electron打包构建配置

"scripts": {
  "serve": "vue-cli-service serve",
  "build": "vue-cli-service build",
  "start": "electron .",
  "packager64": "electron-builder --win --x64",
  "packager32": "electron-builder --win --ia32",
  "asar64": "node ./app.js 64",
  "asar32": "node ./app.js 32"
},
"build": {
    "asar": true,
    "icon": "./public/favicon.ico",
    "productName": "appName",
    "appId": "appId123",
    "nsis": {
      "oneClick": false,
      "allowElevation": true,
      "allowToChangeInstallationDirectory": true,
      "installerIcon": "./public/favicon.ico",
      "uninstallerIcon": "./public/favicon.ico",
      "createDesktopShortcut": true,
      "createStartMenuShortcut": true,
      "shortcutName": "appName",
      "artifactName": "${productName}-setup-${version}_${os}_${arch}.${ext}"
    },
    "directories": {
      "output": "build"
    },
    "files": [
      "!src/**/*",
      "!public/**/*",
      "dist/**/*",
      "input/**/*",
      "electronJs/**/*"
    ],
    "extraResources": [
      {
        "from": "./public/down",
        "to": "."
      }
    ]
  },
"dependencies":{
	//在线上需要用到的包 都要放在这里,不然打包后运行 提示缺少各种功能包
},
"devDependencies":{
	//用于存放你的 打包构建、代码格式化、babel、 electron等等依赖包。
	"electron": "^11.0.0",
    "electron-builder": "^23.6.0",
}

打包出来的文件目录包含:
dist: vue 打包文件
main.js 主进程相关代码文件
node_modules 主进程需要的依赖包

"./public/down" 存放需要热更新的文件 更新随后讲解

4. 用主进程nodejs启动exe 因为路径包含空格: C:\Program Files (x86)\you.exe 所以执行失败还报错。。。

使用 cmd /c start 打开带空格的路径

//执行exe (带空格的路径)
exec(
  `cmd /c start "" "C:\Program Files (x86)\you.exe"`,
  { detached: true, stdio: 'ignore' },
  function (err) {
    if (err) {
      console.log('cmd /c start=> ', err);
    }
  },
)

5. 将项目配置文件放到了安装根目录,导致卸载重装后不同用户存储的临时数据丢失(你的应用程序是一次性的!!!)

使用nodejs API获取系统盘AppData路径 将config文件 放到c盘的AppData文件夹中(这是个隐藏文件夹)

const path = require('path');
const os = require('os');
const appDataPath = path.join(os.homedir(), 'AppData', 'Local');

6. 使用ipcHelper 发消息时,接收第一条消息,响应一次,接收第二条消息,响应两次,因为消息管道未关闭。。。

监听消息的事件 用 .once 而不用 .on

// main.js 接收收消息并回发
const { ipcMain } = require('electron');

ipcMain.on('window-pay', function (event) {
  //消息回发
  event.sender.send('window-pay-msg', 123);
});
// 使用vue 的 main.js 注册 electron  对象 供全局实用
const electron = window?.require?.('electron');

//electron 进程通信
Vue.prototype.$electron = electron;

// vue组件内使用
this.$electron?.ipcRenderer?.send('window-pay');

// 监听信息返回  使用 once  param1:回发消息
this.$electron?.ipcRenderer?.once('window-pay-msg', async (event, param1) => {
  //接收回发消息
  message.success(param1); //123
});

7. 我的自定义更新 不能更新自己(app.asar文件不能在程序运行时替换)。。。

检测更新 我们准备的更新 分为 热更新 冷更新 全量更新

热更新:替换 app.asar 文件 因为不能在运行时替换 所以要现将app.asar文通过nodejs npm asar包将新 app.asar 反编译 在压缩 最后上传到服务器,传好之后将自定义的 latest 文件修改版本 触发用户应用程序热更新。 所以我们准备将热更新来更新一些通过应用程序启动的其他exe文件。 需要在主进程的检查更新功能中配置 "./public/down" 文件夹内的exe等等文件复制到指定文件夹下。
我复制在了resources文件夹中。

冷更新:新建更新器,专门负责更新,由应用程序触发更新器.exe 更新在运行时热更新不能替换的文件,包括程序启动文件…,在程序入口处检查服务端更新配置文件的版本号 和本地版本号的差异 看是否需要更新,如果需要更新 则强制更新。

全量更新:新建更新器,专门负责更新,由应用程序触发更新器.exe 下载新安装包 下载之后自动触发安装包安装程序。

综上所述:热更新不行 用冷更新 冷更不行用全量更新

8. 更新版本拉齐!!!

经过几次版本更新失败后,我们需要将用户电脑的应用程序版本拉齐,更新失败的重新安装,所以之后的热更新 会静默,用户感知不到,只是在更新完毕后,重新进入应用程序会发现新增了几个页面和功能…

9. 你的应用程序是32位的吗?!!!

如果不是,那么许多32位的老电脑是不支持你的exe的,所以你打开控制面板,找到程序和功能,检查你的电脑安装的应用程序,凡是被广泛使用的软件都是32位的,所以你需要将你的应用程序打包成32位的。

10. 打包成32位的之后,发现nodejs -> child_process 解构出来的方法 spawn 不支持32位的应用程序。。。

使用 nodejs -> child_process 的另外一个方法 execFile 与spawn用法相同,如果还不行,需要检查你打开的 exe 是不是32位的…

const { execFile } = require('child_process');

//路径要换成绝对路径 !!!
const child = execFile(`./resources/you.exe`, ['参数1','参数2','参数3']);

// 监听子进程的输出
child.stdout.on('data', (data) => {
  console.log(`start_exe子进程输出:${data}`);
});

// 监听子进程的错误输出
child.stderr.on('data', (data) => {
  console.log(`start_exe子进程错误输出:${data}`);
});

// 监听子进程的退出事件
child.on('close', (code) => {
  console.log(`start_exe子进程退出,退出码 ${code}`);

  event.sender.send(`window-down-back`, {
    msgType: 'success',
    msg: '准备完毕,请稍后',
  });
});

11. 为execFile 配置环境变量。。。

每个应用程序都有他自己的环境路径,如果找不到则一些相关的资源文件也会找不到,结果就是你的应用程序,打不开别的exe所以我们需要通过 nodejs 配置环境变量。

// 执行 exe 文件  并配置环境变量  需要转为绝对路径
const mainDir = path.dirname(downUrl);

const env = Object.assign({}, process.env, {
  PATH: mainDir + ';' + process.env.PATH,
});

const child = execFile(downUrl, [downId], {
  cwd: mainDir,
  env: env,
  shell: true,
});

12. 记录日志: 当你的代码量即将破万,一旦线上出现问题,你会花很多时间排错

我们不仅要及时的提示一些重大错误,还要在应用程序内创建程序运行日志 log.txt ,也可以下载一些npm包 去记录详细且专业的日志,但我是自定义的记录日志的方法

新建txtConsole.js文件

const fs = require('fs');
const moment = require('moment');
const mainData = require('./mainData');

const txtConsole = {
  log(p1 = '', p2 = '', p3 = '', p4 = '', p5 = '') {
    let logPath = `${mainData?.rootPath}/log.txt`;

    try {
      //创建config文件
      if (!fs.existsSync(logPath)) {
        //新建文件
        fs.writeFileSync(logPath, '');
      }

      //追加到log文件
      fs.appendFileSync(
        logPath,
        `\n ${p1} ${p2} ${p3} ${p4} ${p5}        ${moment().format('Y-MM-DD HH:mm:ss')}`,
      );

      console.log(p1, p2, p3, p4, p5);
    } catch (err) {
      console.log('txtConsole: ', err);
    }
  },
  clearLog() {
    let logPath = `${mainData?.rootPath}/log.txt`;
    try {
      if (fs.existsSync(logPath)) {
        let stat = (fs.statSync(logPath)?.size || 1) / 1024;

        txtConsole.log(`当前log文件大小:${parseInt(stat)}KB`);

        if (parseInt(stat) > 8192) fs.unlinkSync(logPath);
      }
    } catch (err) {
      console.log(err);
    }
  },
}; //日志文件

module.exports = txtConsole;

在主进程中使用它

const txtConsole = require('./txtConsole');

// 当运行日志文件体积达到将近1M 则删除并重新创建日志文件log.txt(log.txt放到程序根路径即可)。
txtConsole.clearLog();

//正常使用:
txtConsole.log('已设置当前版本号:', '0.0.6');
定义日志文件后 既方不影响我们本地调试,又可以快速确定用户电脑的应用程序在什么时间那个模块出现了什么问题。

13. 代码压缩

你会发现 vue代码是经过build压缩的 而 app.asar 反编译后你的主进程代码会一览无遗,所以我们需要将主进程代码压缩,这个因人而异,通过安装npm包 进行压缩。
"html-minifier": "^4.0.0",
"uglify-js": "^3.17.4",

//生成 反编译app.asar  并生成压缩包
const fs = require('fs');
const path = require('path');
const uglify = require('uglify-js');
const moment = require('moment');

//压缩主进程 main.js 相关代码
function zipMainJS() {
  try {
    const dir = path.resolve(`你的主进程路径文件夹 我做了模块拆分/electronJs`);
    const files = fs.readdirSync(dir);

    files.forEach((file) => {
      const filePath = `${dir}/${file}`;

      let dirJs = fs.readFileSync(filePath, 'utf8');

      const result = uglify.minify(dirJs);

      fs.writeFileSync(filePath, result.code);
    });

    return true;
  } catch (err) {
    console.log(err);
    return false;
  }
}

//压缩main.js相关代码
let zipRes = zipMainJS();

if (!zipRes) {
  console.log('!!!压缩main.js主进程代码失败!' + '    ' + moment().format('HH:mm:ss'));
  return;
}

14. 造轮子:活得越干越少!!!

当检查更新功能出现之后 会有很多细小步骤的工作,这时你发现 如果用一个app.js文件 定义一些代码 用代码来实现打包时的所有工作,你只需要等待即可。
我的项目打包一次需要做的工作:

  1. npm run build 打包vue文件
  2. npm run packager32 应用程序打包
  3. 我的exe需要在打包后将一些文件放入打包后的文件夹中,在用Inno Setup 再次打包,所以步骤比较繁琐,大家根据实际情况来造轮子即可。
    在这里插入图片描述
  4. win-ia32-unpacked/resources/app.asar app.asar文件反编译,在进行代码压缩(如何压缩看第13条),通过反编译后的文件夹生成app.zip 更新包。
  5. 通过一些文件打包生成冷更新zip包。
  6. 将安装图标等等资源文件放入安装包根目录中(win-ia32-unpacked)。
  7. 检查一些文件的有效性(latest文件版本号,是否能被正常解析JSON,main.js文件是否是生产模式)
  8. 通过vscode打开Inno Setup执行最终打包。
  9. 删除上一次打包后的残留文件,避免缓存。
  10. 将远端app.zip更新包更新,latest文件更新。

下面是一些轮子常用方法

//大家根据自己项目的实际情况 有所选择即可
const asar = require('asar');
const fs = require('fs');
const fsExtra = require('fs-extra');
const zlib = require('zlib');
const archiver = require('archiver');
const path = require('path');
const uglify = require('uglify-js');
const moment = require('moment');
const minify = require('html-minifier').minify;
const { exec, execSync, spawn } = require('child_process');

const startTime = moment().unix();
const mainData = require('./electronJs/mainData');

execSync('chcp 65001');

const rootPath = path.resolve(__dirname);// 获取项目根路径
const asarPath = './build/win-ia32-unpacked/resources/app.asar';// 获取 app.asar 文件路径
const asarAppPath = './app/apps';// asar反编译文件的存放路径
const buildPath = './build';//electron 打包后的build文件夹
const distPath = './dist';//vue 构建后的build文件夹
const sourceDir = './app';// 要压缩的文件夹路径
const sourcePatchDir = './build/win-ia32-unpacked/resources';// 要压缩的冷更文件夹路径
const outputPath = './Output';// innel setup output
const destFile = './app.zip';// 压缩后的文件路径
const destPatchFile = './ShanHeBoxUpdate.zip';// 冷更压缩后的文件路径
const downAppPath = './down/app';
const downLogPath = './down/log.txt';
// 配置环境路径为项目根路径
const env = Object.assign({}, process.env, {
  PATH: rootPath + ';' + process.env.PATH,
  npm_config_prefix: 'C:\\Program Files\\nodejs\\npm', // 这里是你的 npm 安装路径
});

//执行 构建命令 需要配置好环境变量

  console.log(
    '=============== 正在执行【npm run build】命令' + '    ' + moment().format('HH:mm:ss'),
  );
  execSync('npm run build', env);

执行 打包命令 需要配置好环境变量:执行出错要关掉vscode 手动将你的build文件夹删掉

console.log(
  '=============== 正在执行【npm run packager32】命令' + '    ' + moment().format('HH:mm:ss'),
);
exec('npm run packager32', env, (error, stdout, stderr) => {
	 if (error) {
	    console.log(`执行出错: ${error}`);
	    return;
	  }
	  
	  stderr && console.log('【npm run packager32】 stderr=>', stderr);
	  
	  //开始执行其他步骤...
	  init(); 
});

执行app.asar 反编译 压缩相关逻辑


  console.log(
    '=============== 正在执行app.asar 反编译 压缩相关逻辑' + '    ' + moment().format('HH:mm:ss'),
  );

  // 将 app.asar 解压缩到指定文件夹中 asarPath: app.asar文件路径  asarAppPath: 解压后的文件夹路径
  asar.extractAll(asarPath, asarAppPath);

文件操作

//复制
fsExtra.copySync('./down', './build/win-ia32-unpacked/resources');
//删除  可以删除文件夹  和文件
fsExtra.removeSync(buildPath);

检查更新配置文件有效性

console.log(
    `=============== 更新配置文件latest ${checkLatestInfo() ? '【有效】' : '【无效】'}` +
      moment().format('HH:mm:ss'),
  );

function checkLatestInfo() {
  try {
    let latestData = fs.readFileSync('./down/latest', 'utf8');

    if (latestData) {
      let latest = JSON.parse(latestData);
      fs.writeFileSync('./down/latest', JSON.stringify(latest));
    }

    return true;
  } catch (err) {
    return false;
  }
}

压缩代码

function zip(){
	// 创建一个可写流,将压缩后的文件写入到目标文件中
	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);
	
	// 完成压缩并关闭可写流
	archive.finalize();
	
	// 监听可写流的 'close' 事件,表示压缩完成
	destStream.on('close', () => {
	  console.log(
	    `=============== 压缩完毕,压缩包路径:【${path.resolve(__dirname, destFile)}` +
	      '     ' +
	      moment().format('HH:mm:ss'),
	  );
	
	  console.log('共用时:' + (moment().unix() - startTime) + '秒');
	});
}

打开Inno Setup iss文件

spawn('D:/soft/Inno Setup 6/Compil32.exe', [path.resolve('./ShanHe32.iss')], {
    stdio: ['pipe', 'inherit', 'inherit'], // 将子进程的标准输入流传递给父进程
});

常用功能模块

//获取操作系统盘符 以及剩余空间

function onGetSystemSize() {
  if (os.platform() === 'win32') {
    const output = execSync('wmic logicaldisk get Caption,FreeSpace,Size');
    const disks = output.toString().split('\r\r\n').slice(1, -1);

    diskInfo = disks.map((disk) => {
      const [caption, freeSpace, size] = disk.split(/\s+/);
      return {
        caption,
        freeSpace: +(Number(freeSpace) / 1024 / 1024 / 1024).toFixed(2),
        size: +(Number(size) / 1024 / 1024 / 1024).toFixed(2),
      };
    });
  }

  return diskInfo;
}

onGetSystemSize();
if (!diskInfo || diskInfo.length === 0) {
  let txt = '磁盘信息获取失败';
  console.log(txt);
  return;
}

//修改xml文件属性值

function onUpdateXML() {
  let gameConfig = fs.readFileSync(gameConfigpath, 'utf8');

  xml2js.parseString(gameConfig, (err, result) => {
    if (err) {
      txtConsole.log(err);
      return;
    } else {
      // 获取 SA 属性值
      const SA = result.configuration.appSettings[0].add.find(
        (item) => item.$.key === 'SA',
      );
      
      // 修改 SA 属性值
      SA.$.value = lineInfo.ip;
      
      // 获取 SP 属性值
      const SP = result.configuration.appSettings[0].add.find(
        (item) => item.$.key === 'SP',
      );
      // 修改 ServerPort 属性值
      SP.$.value = lineInfo.server_port;
      
      // 将 JavaScript 对象转换回 XML 字符串
      const builder = new xml2js.Builder();
      const xml = builder.buildObject(result);
      
      // 将修改后的 XML 字符串写回文件
      fs.writeFileSync(gameConfigpath, xml);
    }
  });
}

//获取本机MAC地址 和路由器的MA地址 谨慎使用!!!!!!

const arp = require('arp');
const os = require('os');
const { exec } = require('child_process');

const txtConsole = require('./txtConsole');

function onGetMACAddress(callback) {
  txtConsole.log('------------------------------------------------------------');
  const networkInterfaces = os.networkInterfaces();
  // const newworkKeys = Object.keys(networkInterfaces);
  const PCMAC = networkInterfaces?.['WLAN']?.find((item) => item?.family === 'IPv4').mac; //电脑MAC

  // 获取本机的 IP 默认网关 地址
  exec('chcp 65001 && ipconfig', (err, stdout, stderr) => {
    if (err) {
      callback?.(err);
      return;
    }

    const ipMatch = stdout.match(/IPv4 Address[\\.\s:]+(\d+\.\d+\.\d+\.\d+)/);
    const defaultGateway = stdout.match(/Default Gateway[\\.\s:]+(\d+\.\d+\.\d+\.\d+)/);

    if (!ipMatch) {
      callback?.('无法获取本机 IP 地址');
      return;
    }

    if (!defaultGateway) {
      callback?.('无法获取本机 默认网关 IP');
      return;
    }

    const ip = ipMatch[1];
    const defaultFatewayIP = defaultGateway[1];

    txtConsole.log('本机IP地址: ', ip);
    txtConsole.log('本机默认网关IP: ', defaultFatewayIP);

    // 获取路由器的 IP 地址
    exec('chcp 65001 && ping -c 1 192.168.1.1', (err, stdout, stderr) => {
      if (err) {
        callback?.('chcp 65001 && ping -c 1 192.168.1.1:' + err);
        return;
      }

      txtConsole.log('ping信息 chcp 65001 && ping -c 1 192.168.1.1: ', stdout);

      //判断是否ping通
      const pattern = new RegExp(`Reply from ${defaultFatewayIP}`);
      const match = stdout.match(pattern);
      if (!match) {
        callback?.('检测是否ping通: ping失败');
        return;
      }

      txtConsole.log('检测是否ping通:', 'ping成功!!!');

      // 获取路由器的 MAC 地址
      arp.getMAC(defaultFatewayIP, (err, RouterMAC) => {
        if (err) {
          callback?.('获取路由器的 MAC 地址: 失败');
          return;
        }

        txtConsole.log('------------------------------------------------------------');

        // RouterMAC:路由器MAC
        callback?.(null, { PCMAC, RouterMAC });
      });
    });
  });
}

module.exports = onGetMACAddress;

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

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

相关文章

LIN总线与RS485总线

LIN(Local Interconnect Network,局部互连网络)总线和RS485都是用于设备间通信的串行通信协议。下面我将分别列出它们的优势和劣势。 LIN总线的优势: 简单性:LIN总线的硬件和协议简单,易于实现和维护。成…

多元回归预测 | Matlab基于逻辑回归(Logistic Regression)的数据回归预测,多输入单输出模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元回归预测 | Matlab基于逻辑回归(Logistic Regression)的数据回归预测,多输入单输出模型 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源码 %% 清空环境变量…

炸裂了…京东一面索命40问,过了就50W+

说在前面 在40岁老架构师尼恩的(50)读者社区中,经常有小伙伴,需要面试京东、阿里、 百度、头条、美团等大厂。 下面是一个小伙伴成功拿到通过了京东一次技术面试,最终,小伙伴通过后几面技术拷问、灵魂拷问…

MySQL的存储引擎与基本使用

目录 一、前言 1.MySQL的介绍 二、存储引擎 1.什么是存储引擎 2.常见存储引擎 2.1.InnoDB(MySQL默认引擎) 2.1.1.四种隔离级别 2.2.MyISAM存储引擎 2.3.Memory存储引擎 3.ACID事务 三、CRUD操作 1.插入数据 2.查询数据 3.修改数据 4.删除数据 四、数据库 1.默认…

C#学习之路-基本语法

C# 是一种面向对象的编程语言。在面向对象的程序设计方法中,程序由各种相互交互的对象组成。相同种类的对象通常具有相同的类型,或者说,是在相同的 class 中。 using System; using System.Collections.Generic; using System.Linq; using S…

OpenCV库进行图像旋转、仿射变换和透视变换

#include <iostream> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp>

解决Linux操作系统ping不同www.baidu.com问题

首先给自己管理员权限 输入代码 su然后输入密码密码不会显示打完直接回车 输入下面代码配置网络 cd /etc/sysconfig/network-scripts 输入以下代码进入ens33管理 vim ifcfg-ens33 输入 i i用方向键把光标移到 ONBOOT NO 把 no 改为 yes 按下 ESC 退出编辑模式 直接敲…

二叉树前中后序的非递归实现

前言 &#xff1a; 递归我们会有一些问题的 为什么有递归就一定有非递归呢&#xff1f;&#xff1f;首先递归是有一定缺陷的 递归真正的缺陷是&#xff0c;每一个程序运行起来呢都是一个线程的形式&#xff0c;但是每一个线程都会有独立的栈空间&#xff0c;但是栈空间是很…

Spring Boot 中的 Future 接口是什么,如何使用

Spring Boot 中的 Future 接口是什么&#xff0c;如何使用 在异步编程中&#xff0c;我们通常需要处理一些耗时的操作。一种常见的做法是使用 Future 接口来代表一个异步操作的结果。在 Spring Boot 中&#xff0c;Future 接口被广泛应用于异步编程中&#xff0c;本文将介绍 S…

【C++】VSCode 使用 C/C++ Compile Run 插件时,设置默认运行的编译参数的方法

为什么要设置参数 最近在学习C&#xff0c;在学习多线程的时候&#xff0c;发现使用C11中的thread类写的代码编译会报错&#xff1a; * 正在执行任务: g -Wall -Wextra -g3 /Users/anweiyang/studySrc/C/ThreadTest.cpp -o /Users/anweiyang/studySrc/C/output/ThreadTest /U…

OpenCV使用putText将文字绘制到图像上

#include <opencv2/opencv.hpp>int main(int argc, char **argv) {cv::Mat image = cv::imread(

开发实例:实现一个时间轮算法

时间轮算法是一种比较常见的时间计数器算法&#xff0c;它主要用于定时器的处理。在Java开发中&#xff0c;我们可以使用这种算法来实现非常高效且精准的定时器功能。下面&#xff0c;我将为大家介绍一个基于时间轮算法的定时器实现方法。 1、定义时间轮的数据结构 首先&…

spring 详解一 IOC(BeanFactory和ApplicationContext)

spring概述 重要部分 Spring是一个容器&#xff0c;用来管理java对象的创建以及其他功能的扩展&#xff0c;目前java的生态已经离不开spring&#xff0c;所以spring在java领域是一个极其重要的框架&#xff0c;在spring的思想中IOC(控制反转&#xff09;和AOP(切面编程)是重要…

Portraiture最新PS/LR 4.1.0.3皮肤修饰插件

Portraiture是一款惹人喜爱的PS磨皮插件。它能智能地对图像中的皮肤材质、头发、眉毛、睫毛等部位进行平滑和减少疵点处理&#xff0c;相对于Camera RAW&#xff0c;它能选择肌肤的色彩范围&#xff0c;对选择的部分进行单独处理。这样避免了其他部分同时被美化。 Portraiture…

C#(五十)之stringBuilder类

使用StringBuilder 需引用命名空间 using System.Text; String类与StringBuilder类的区别&#xff1a; string是各位用的最多的类型之一&#xff0c;是一个特殊的引用类型&#xff0c;直接派生于Object&#xff0c;因此它的值储存在托管堆上。构造一个新字符串的时候&#xf…

使用TestNG搭建自动化测试框架设计说明书

TestNG自动化测试框架V1.0 1. 背景............................................................................................................................ 4 1.1 编写背景.....................................................................................…

深度神经网络量化算法基础理论

关于量化&#xff0c;之前的博客中首先从第一个将量化思想应用在神经网络模型上的工作开始介绍&#xff0c;随后阐述了量化领域的极端情况&#xff0c;即二值化与三值化&#xff0c;并指出尽管目前已经存在多种对二值网络的优化方法&#xff0c;但是显然因极端量化带来的严重精…

Day45|70. 爬楼梯 (进阶)、 322. 零钱兑换 、279.完全平方数

70. 爬楼梯 &#xff08;进阶&#xff09; 1.题目&#xff1a; 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&#xff1a;…

算法与数据结构(一)--算法复杂性

一.算法复杂性的概念 算法的复杂性是指运行算法所需要的计算机资源的量。需要的时间资源的量称为时间复杂性&#xff0c;需要的空间资源的量称为空间复杂性。 这个量应该集中反映算法的效率&#xff0c;而从运行该算法的实际计算机中抽象出来。换句话说&#xff0c;这个量应该…

[物理层]信道的极限容量

信道的极限容量 信号在传输过程中会受到各种因素的影响&#xff0c;会造成信号失真。 失真的因素&#xff1a; 码元传输速率信号传输距离噪声干扰传输媒体质量 因此&#xff0c;怎么提高信息的传输速率&#xff1f; 奈氏准则 [ 香农公式 提高信道的传输速率&#xff1a; …