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

构建mono-repo风格的脚手架库

前段时间阅读了 https://juejin.cn/post/7260144602471776311#heading-25 这篇文章;本文做一个梳理和笔记;

主要聚焦的知识点如下:

  • 如何搭建脚手架工程
  • 如何开发调试
  • 如何处理命令行参数
  • 如何实现用户交互
  • 如何拷贝文件夹或文件
  • 如何动态生成文件
  • 如何处理路径
  • 如何自动安装依赖

step1:初始化工程

推荐使用pnpm搭建mono-repo风格的工程;

mono-repo工程可以包含多个子工程,并且每个子工程都可以独立编译打包,并将打包的产物发成npm包;

这样在同一个工程中,我们可以写库,也可以写demo;

步骤如下:

  1. 执行 pnpm init 命令,生成package.json文件;
  2. 新建 pnpm-workspace.yaml 文件,添加如下配置:
packages: - 'packages/*' - 'demos/*'

配置后,声明了 packages 和 demos 文件夹中子工程是同属一个工作空间的,工作空间中的子工程编译打包的产物都可以被其它子工程引用。

  1. 在packages文件夹下 新建zy-cli文件夹;
  2. cd 到 zy-cli文件夹下,运行 pnpm init 初始化;
  3. zy-cli 下的 packages.json 中声明 bin 命令 zy-script;
"bin": {"zy-script": "./bin/index.js"},
  1. 添加 bin文件夹,添加index.js文件;写入如下代码
#!/usr/bin/env node
console.log('hellow, zy-cli');
  1. demos 文件夹中存放使用脚手架的演示项目,我们先建一个app文件夹;
  2. 执行 pnpm init 命令,生成package.json文件;
  3. 在app中依赖 @zy/zy-cli ( 此处依赖名称与zy-cli 的package.json 中 name一致 )

pnpm add @zy/zy-cli -F app 会在dependencies自动添加依赖;

  1. 并添加script指令 zy (与zy-cli中声明的指令一致);
"scripts": {"zy": "zy-script"},"dependencies": {"@zy/zy-cli": "workspace:^"},
  1. 执行pnpm -i 安装依赖;

  1. 在app目录下运行:pnpm zy;成功输出了 hellow, zy-cli

小节:

到目前为止,我们成功创建了mono-repo风格的项目结构;

packages > zy-cli 是我们脚手架工程,在bin中自定义了指令;

demos > app 是使用 zy-cli 脚手架的示例工程,利用pnpm的workspace,指定了工作区中zy-cli依赖,在script中自定义使用 zy-cli中声明的命令;

整个工程结构如下:

  |-- my-cli|-- package.json|-- pnpm-lock.yaml|-- pnpm-workspace.yaml|-- demos|   |-- app|       |-- package.json|-- packages|-- zy-cli|-- package.json|-- bin|-- index.js

现在,我们思考一下,一个脚手架工程需要哪些模块?

  • 命令参数模块
  • 用户交互模块
  • 文件拷贝模块
  • 动态文件生成模块
  • 自动安装依赖模块

接下来,我们一步一步实现他们;

step2:命令参数模块

当我们执行命令的时候,经常会带一些参数,如何获取并利用这些参数;

nodeJS 中 process 模块,可以获取当前进程相关的全局环境信息 - 命令行参数,环境变量,命令运行路径等;

利用 progress.argv 获取;

const process = require('process');
// 获取命令参数
console.log(process.argv); 

或者可以采用更便捷的方案 yargs 开源库;

我们为zy-cli 安装 yargs:

pnpm add yargs --F zy-cli

注意:--F zy-cli 指的是指定给该工程安装;-W 是全局安装的意思

我们在zy-cli - bin - index.js 中写入如下测试代码

#!/usr/bin/env node
// 此处遵循CommonJS规范
const yargs = require('yargs');console.log('name', yargs.argv.name);

在demos - app 目录下执行 pnpm zy --name=zhang

打印输出如下:

step3:创建子命令

我们在使用vue-cli的时候,都用过 vue creat app 之类的命令;creat就是子命令;

我们通过 yarg.command 来实现;

yargs.command(cmd, desc, builder, handler)

  • cmd:字符串,子命令名称,也可以传递数组,如 ['create', 'c'],表示子命令叫 create,其别名是 c;
  • desc:字符串,子命令描述信息;
  • builder:子命令参数信息配置,比如可以设置参数(builder也可以是一个函数):
    • alias:别名;
    • demand:是否必填;
    • default:默认值;
    • describe:描述信息;
    • type:参数类型,string | boolean | number。
  • handler: 函数,可以在这个函数中专门处理该子命令参数。

下面我们定义一个creat命令:

#!/usr/bin/env node
const yargs = require('yargs');
console.log('name', yargs.argv.name);
yargs.command({// 字符串,子命令名称,也可以传递数组,如 ['create', 'c'],表示子命令叫 create,其别名是 ccommand: 'create <name>',// 字符串,子命令描述信息;describe: 'create a new project',// 对象,子命令的配置项;builder也可以是一个函数builder: {name: {alias: 'n', // 别名demandOption: true, // 是否必填describe: 'name of a project', // 描述default: 'app' // 默认}},// 函数形式的// builder: (yargs) => {//   return yargs.option('name', {//     alias: 'n',//     demand: true,//     describe: 'name of a project',//     type: 'string'//   })// },handler: (argv) => {console.log('argv', argv);}
});

我们运行一下这个命令:pnpm zy create my-app

输出如下:

step4:增加用户交互

当我们使用vue create xxx 的时候,命令行会出现选项式的交互,让我们选择配置;

这里我们也实现一下;使用 inquirer 库;

运行命令安装 pnpm add inquirer@8.2.5 --F zy-cli

inquirer主要做了三件事情:

  1. 询问用户问题
  2. 获取用户输入
  3. 校验用户输入
const inquirer = require('inquirer');function inquirerPrompt(argv) {// 先获取到了命令行中的nameconst { name } = argv;return new Promise((resolve, reject) => {inquirer.prompt([{type: 'input',name: 'name',message: '模板名称',default: name,validate: function (val) {if (!/^[a-zA-Z]+$/.test(val)) {return "模板名称只能含有英文";}if (!/^[A-Z]/.test(val)) {return "模板名称首字母必须大写"}return true;},},{type: 'list',name: 'type',message: '模板类型',choices: ['表单', '动态表单', '嵌套表单'],filter: function (value) {return {'表单': "form",'动态表单': "dynamicForm",'嵌套表单': "nestedForm",}[value];},},{type: 'list',message: '使用什么框架开发',choices: ['react', 'vue'],name: 'frame',}]).then(answers => {const { frame } = answers;if (frame === 'react') {inquirer.prompt([{type: 'list',message: '使用什么UI组件库开发',choices: ['Ant Design',],name: 'library',}]).then(answers1 => {resolve({...answers,...answers1,})}).catch(error => {reject(error)})}if (frame === 'vue') {inquirer.prompt([{type: 'list',message: '使用什么UI组件库开发',choices: [ 'Element'],name: 'library',}]).then(answers2 => {resolve({...answers,...answers2,})}).catch(error => {reject(error)})}}).catch(error => {reject(error)})})}exports.inquirerPrompt = inquirerPrompt;

其中 inquirer.prompt() 返回的是一个 Promise,我们可以用 then 获取上个询问的答案,根据答案再发起对应的内容。

在index.js 中引入并使用

#!/usr/bin/env nodeconst yargs = require('yargs');
const { inquirerPrompt } = require('./inquirer');// 命令配置
yargs.command({// 字符串,子命令名称,也可以传递数组,如 ['create', 'c'],表示子命令叫 create,其别名是 ccommand: 'create <name>',// 字符串,子命令描述信息;describe: 'create a new project',// 对象,子命令的配置项;builder也可以是一个函数builder: {name: {alias: 'n', // 别名demandOption: true, // 是否必填describe: 'name of a project', // 描述default: 'app' // 默认}},// 函数形式的// builder: (yargs) => {//   return yargs.option('name', {//     alias: 'n',//     demand: true,//     describe: 'name of a project',//     type: 'string'//   })// },handler: (argv) => {inquirerPrompt(argv).then((answers) => {console.log(answers);});}
}).argv;

我们运行 pnpm zy create my-app 试试:

step5:文件夹拷贝

前几节我们实现了一个可以读取命令行的cli工程配置;

接下来,我们就要深入到cli脚手架的构建;

首先是文件夹的拷贝。我们使用 copy-dir 库来实现文件夹拷贝;

安装:pnpm add copy-dir --F zy-cli

bin中创建copy.js,实现简单的copy函数,check函数

const copydir = require('copy-dir')
const fs = require('fs');function copyDir (from, to, option) {copydir.sync(from, to, option);
}/*** Checks if a directory or file exists at the given path.* @param {string} path - The path to check for existence.* @returns {boolean} - Returns true if the directory or file exists, false otherwise.*/
function checkMkdirExists(path){return fs.existsSync(path);
}exports.copyDir = copyDir;
exports.checkMkdirExists = checkMkdirExists;

这几个函数比较简单,但是主要难点在于使用;具体来说就是 from,to参数;

先定个需求,我们运行 creat 选择 form类型 命令的时候,需要将 zy-cli > src > form 文件夹拷贝到 demos > app > src > <app-name> 中;

  1. 我们分析一下,如何获取当前模板的位置;也就是 copyDir 的 from 参数;

__dirname 是用来动态获取当前文件模块所属目录的绝对路径。比如在 bin/index.js 文件中使用 __dirname ,__dirname 表示就是 bin/index.js 文件所属目录的绝对路径 ~/Desktop/my-cli/zy-cli/bin。

使用 path.resolve( [from…], to )将相对路径转成绝对路径;

那我们模板的路径就是:path.resolve( __dirname,'../src/form' );或者path.resolve( __dirname,'../src/${type}')

  1. 接下来,我们确定 copyDir 的 to 参数;也就是目标文件夹 <app-name>

我们运行脚手架命令是在 app 目录下;pnpm zy 执行的是 app > packages.json ,所以在node脚本中,可以使用 process.cwd() 获取文件路径;

那我们拷贝的目标路径就是:path.resolve(process.cwd(), './src/${<app-name>}')

handler: (argv) => {inquirerPrompt(argv).then((answers) => {// 此处已经获取到了完整的模版参数;开始进行文件处理const { name, type, frame, library } = answers;// 判断是否存在该项目文件夹const isMkdirExists = checkMkdirExists(path.resolve(process.cwd(),`./${name}`));if (isMkdirExists) {console.log( `${name}文件夹已经存在`);} else {const templatePath = path.resolve(__dirname, `../src/${type}`);const targetPath = path.resolve(process.cwd(), `./${name}`);copyDir(templatePath, targetPath);}});}

运行一下命令:pnpm zy create my-app,选择表单类型;回车,拷贝成功;

step6:目录守卫

如果我需要将文件拷贝到 app > pages > <name> 下,由于没有pages目录,命令会报错;

我们简单实现一个目录守卫,帮我们创建不存在的目录;

const copydir = require('copy-dir')
const fs = require('fs');function copyDir (from, to, option) {// 目录守卫,不存在的目录结构会去创建mkdirGuard(to);copydir.sync(from, to, option);
}/*** Checks if a directory or file exists at the given path.* @param {string} path - The path to check for existence.* @returns {boolean} - Returns true if the directory or file exists, false otherwise.*/
function checkMkdirExists(path){return fs.existsSync(path);
}// 目录守卫
function mkdirGuard(target) {try {fs.mkdirSync(target, { recursive: true });} catch (e) {mkdirp(target)function mkdirp(dir) {if (fs.existsSync(dir)) { return true }const dirname = path.dirname(dir);mkdirp(dirname);fs.mkdirSync(dir);}}
}exports.copyDir = copyDir;
exports.checkMkdirExists = checkMkdirExists;
exports.mkdirGuard = mkdirGuard;

step7:文件拷贝

文件操作,主要使用 fs.readFileSync 读取被拷贝的文件内容,然后创建一个文件,再使用 fs.writeFileSync 写入文件内容;这两个api都是比较熟悉的老朋友了;不做过多介绍;

我们定义一个 copyFile函数:

function copyFile(from, to) {const buffer = fs.readFileSync(from);const parentPath = path.dirname(to);mkdirGuard(parentPath)fs.writeFileSync(to, buffer);
}exports.copyFile = copyFile;

使用方法与copyDir 类似,只不过需要精确到文件;这里就不演示了;

step8:动态文件生成

我们在定义脚手架的时候,会获取很多类型的命令参数,有些参数可能对我们模板文件产生影响。

例如,根据命令行中的name,动态修改packages中的name;

这里,我们需要依赖 mustache ;安装:pnpm add mustache --F zy-cli

我们增加一个 renderTemplate 函数:

接受动态模板的path路径,data:动态模版的配置数据;

Mustache.render(str, data) 接受动态模版和配置数据;

Mustache.render('<span>{{name}}</span>',{name:'张三'})

const Mustache = require('mustache');
function renderTemplate(path, data = {}) {const str = fs.readFileSync(path, { encoding: 'utf8' })return Mustache.render(str, data);
}
exports.renderTemplate = renderTemplate;

再定义一个copyTemplate 函数:

path.extname 获取文件扩展名,如果不是tpl类型的,直接当做文件处理;

function copyTemplate(from, to, data = {}) {if (path.extname(from) !== '.tpl') {return copyFile(from, to);}const parentToPath = path.dirname(to);// 目录守卫mkdirGuard(parentToPath);// 写入文件fs.writeFileSync(to, renderTemplate(from, data));
}

在index.js中试验一下:

const templatePath = path.resolve(__dirname, `../src/${type}/packages.tpl`);
const targetPath = path.resolve(process.cwd(), `./${name}/packages.json`);
copyTemplate(templatePath, targetPath, {name: name})
{"name": "{{name}}","version": "1.0.0","description": "","main": "index.js","scripts": {},"keywords": [],"author": "","license": "ISC"
}

运行完创建命令后,成功生成packages.json 文件,并且将 name字段替换成功;

扩展:mustache 一些用法补充:

基础绑定:

Mustache.render('<span>{{name}}</span>',{name:'张三'})

绑定子属性

Mustache.render('<span>{{ifno.name}}</span>', { ifno: { name: '张三' } })

循环渲染

// {{#key}} {{/key}} 开启和结束循环
Mustache.render('<span>{{#list}}{{name}}{{/list}}</span>',{list: [{ name: '张三' },{ name: '李四' },{ name: '王五' },]}
)

循环渲染 + 二次处理

Mustache.render('<span>{{#list}}{{info}}{{/list}}</span>',{list: [{ name: '张三' },{ name: '李四' },{ name: '王五' },],info() {return this.name + ',';}}
)

条件渲染

// 使用 {{#key}} {{/key}} 语法 和 {{^key}} {{/key}} 语法来实现条件渲染,
// 当 key 为 false、0、[]、{}、null,既是 key == false 为真,
// {{#key}} {{/key}} 包裹的内容不渲染,
// {{^key}} {{/key}} 包裹的内容渲染
Mustache.render('<span>{{#show}}显示{{/show}}{{^show}}隐藏{{/show}}</span>',{show: false}
)

step9:实现自动安装依赖

我们在选择完框架和UI库的时候,可以帮助目标项目自动安装依赖;

我们使用 node 中提供的 child_process 子进程来实现;

  • child_process.exec(command, options, callback)
    • command:命令,比如 pnpm install
    • options:参数
      • cwd:设置命令运行环境的路径
      • env:环境变量
      • timeout:运行执行现在
    • callback:运行命令结束回调,(error, stdout, stderr) =>{ },执行成功后 error 为 null,执行失败后 error 为 Error 实例,stdout、stderr 为标准输出、标准错误,其格式默认是字符串。

我们定义一个 manager 函数;

const path = require('path');
const { exec } = require('child_process');// 组件库映射,前面是用户输入/选择,后面是目标安装的组件库
const LibraryMap = {'Ant Design': 'antd','iView': 'view-ui-plus','Ant Design Vue': 'ant-design-vue','Element': 'element-plus',
}function install(cmdPath, options) {// 用户选择的框架 和 组件库const { frame, library } = options;// 安装命令const command = `pnpm add ${frame} && pnpm add ${LibraryMap[library]}`return new Promise(function (resolve, reject) {// 执行安装命令exec(command,{// 命令执行的目录cwd: path.resolve(cmdPath),},function (error, stdout, stderr) {console.log('error', error);console.log('stdout', stdout);console.log('stderr', stderr)})})
}
exports.install = install;

使用:

// 传入当前进程的目录,以及用户选择的配置
install(process.cwd(), answers)

试验一下:pnpm create xxxx;成功安装;

但是安装过程没有进度展示;我们使用 ora 来丰富安装加载动画;

安装:pnpm add ora@5.4.1 --F zy-cli

使用:

const path = require('path');
const { exec } = require('child_process');
const ora = require("ora");// 组件库映射,前面是用户输入/选择,后面是目标安装的组件库
const LibraryMap = {'Ant Design': 'antd','iView': 'view-ui-plus','Ant Design Vue': 'ant-design-vue','Element': 'element-plus',
}function install(cmdPath, options) {// 用户选择的框架 和 组件库const { frame, library } = options;// 串行安装命令const command = `pnpm add ${frame} && pnpm add ${LibraryMap[library]}`return new Promise(function (resolve, reject) {const spinner = ora();spinner.start(`正在安装依赖,请稍等`);// 执行安装命令exec(command,{// 命令执行的目录cwd: path.resolve(cmdPath),},function (error) {if (error) {reject();spinner.fail(`依赖安装失败`);return;}spinner.succeed(`依赖安装成功`);resolve()})})
}
exports.install = install;

再次执行,已经有状态提示了;

step10:推送到私有npm仓库

使用verdaccio搭建私有npm仓库的步骤本文不赘述,可以参考这篇文章;搭建自己的私有npm库

// TODO 部署过程中使用docker-compose,遇到一些问题,预计单独开一篇文章去记录;

假设我们已经有了npm私库;ip:http://xxxxxx:4873/

我们使用 nrm 去管理 npm 的仓库地址

// 全局安装 
npm install -g nrm// 查看所有的仓库 
nrm ls 
// 切换仓库 
nrm use <name> 
// 添加仓库 
nrm add <name> <address>

推送之前,我们需要修改 packages.json 中的信息:

{"name": "@zy/zy-cli","version": "1.0.0","description": "","main": "index.js","bin": {"zy-script": "./bin/index.js"},"scripts": {"test": "echo \"Error: no test specified\" && exit 1"},// 规定上传到npm包中的文件"files": ["bin","src"],"keywords": [],"author": "","license": "ISC","dependencies": {"copy-dir": "^1.3.0","inquirer": "8.2.5","mustache": "^4.2.0","ora": "5.4.1","yargs": "^17.7.2"}
}

推送:

pnpm publish --registry http://xxxxx:4873/

刷新我们的 vedaccio,已经存在这个包了

使用:

我们在Desktop中新建一个空白文件夹;

mkdir cli-test

cd cli-test

pnpm init

nrm use zy

pnpm i @zy/zy-cli

此时,我们的cli-test项目已经成功安装了私有npm仓库的 zy-cli 项目

在packages.json 中添加命令

"scripts": {"zy-script": "zy-script"},

执行 pnpm zy-script create Myapp

成功安装所有依赖并拷贝文件;

总结:

  1. 我们搭建了一个mono-repo风格的工程;包含了一个zy-cli脚手架工程,和demos-app的测试工程;
  2. zy-cli实现了用户交互式的命令行,命令行参数获取,文件拷贝,动态文件生成,自动安装依赖;
  3. 我们将zy-cli推送到了npm私有仓库上,并另开了一个工程,切换私库源,成功安装并且运行;

展望:

目前初步实现了mono-repo工程,还需要添加统一的publish脚本,包含版本自增等;

cli 脚手架不需要打包,所以还需要为这个工程添加一个 组件库,工具函数库等类型的包;

相关文章:

构建mono-repo风格的脚手架库

前段时间阅读了 https://juejin.cn/post/7260144602471776311#heading-25 这篇文章&#xff1b;本文做一个梳理和笔记&#xff1b; 主要聚焦的知识点如下&#xff1a; 如何搭建脚手架工程如何开发调试如何处理命令行参数如何实现用户交互如何拷贝文件夹或文件如何动态生成文件…...

云安全—etcd攻击面

0x00 前言 本篇还是一样&#xff0c;先来说一说etcd是什么&#xff0c;干啥的&#xff0c;然后再来看看etcd的攻击面到底有哪些&#xff0c;做一个抛砖引玉的作用&#xff0c;如有不妥之处还请斧正 0x01 etcd 依旧还是按照问问题的方式来进行阐述&#xff0c;因为学到的东西…...

类锁和实例对象锁你分清了吗?

系列文章目录 文章目录 系列文章目录前言一、什么是锁竞争&#xff1f;二、什么是类锁&#xff1f;什么是实例对象锁&#xff1f;三、给类对象加锁不是锁住了整个类四、总结 前言 java选手们应该都对锁不陌生&#xff0c;加锁了就是为保证操作语句的原子性&#xff0c;如果你是…...

如何在麒麟上安装 ONLYOFFICE 桌面编辑器

我们很高兴地告诉大家&#xff0c;ONLYOFFICE 桌面编辑器现已上架麒麟软件商店。请阅读下文了解详情。 关于麒麟 麒麟是一款国产操作系统&#xff0c;主要是为了满足中国市场的需求和偏好而设计的。 它能够与各种硬件平台和软件应用程序的广泛兼容&#xff0c;因而受到认可。…...

记录:如何编写linux驱动,用module的方式

记录:如何编写Linux驱动,用module的方式 记录:如何编写Linux驱动,用module的方式参考记录:如何编写Linux驱动,用module的方式 编写一个 Linux 的驱动,用 module 方式开发,一般来说,编写一个 Linux 的驱动,需要遵循以下步骤: 确定设备的类型和功能,以及它在系统中的…...

3款免费又好用的 Docker 可视化管理工具

前言 Docker提供了命令行工具&#xff08;Docker CLI&#xff09;来管理Docker容器、镜像、网络和数据卷等Docker组件。我们也可以使用可视化管理工具来更方便地查看和管理Docker容器、镜像、网络和数据卷等Docker组件。今天我们来介绍3款免费且好用的 Docker 可视化管理工具。…...

C语言--判断一个年份是否是闰年(详解)

一.闰年的定义 闰年是指在公历&#xff08;格里高利历&#xff09;中&#xff0c;年份可以被4整除但不能被100整除的年份&#xff0c;或者可以被400整除的年份。简单来说&#xff0c;闰年是一个比平年多出一天的年份&#xff0c;即2月有29天。闰年的目的是校准公历与地球公转周…...

Python---排序算法

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 Python中的排序算法用于对数据进行排序。排序算法可以使数据按照一定的规则进行排列&#xff0c;以便于数据的查找、统计、比较等操作。在数据分析、机器学习、图形计算等领域&#xff0c…...

gitlab Blocking and unblocking users

原文&#xff1a;Redirecting... Blocking a userUnblocking a user Blocking and unblocking users GitLab 管理员阻止和取消阻止用户. Blocking a user 为了完全阻止用户访问 GitLab 实例&#xff0c;管理员可以选择阻止该用户. 可以通过滥用报告或直接从管理区域来阻止…...

Swift 和 Python 两种语言中带关联信息错误(异常)类型的比较

0. 概览 如果我们分别在平静如水、和谐感人的 Swift 和 Python 社区抛出诸如“Python 是天下最好的语言…” 和 “Swift 是宇宙第一语言…”之类的言论会有怎样的“下场”&#xff1f; 我们并不想对可能发生的“炸裂”景象做出什么预测&#xff0c;也无意比较 Swift 与 Pytho…...

北京联通iptv组播配置

多年前折腾过iptv&#xff0c;近期搬家换了个大电视&#xff0c;打算把iptv配置好了&#xff0c;尽管不怎么看&#xff0c;但聊胜于无。 其实很简单&#xff0c;用到了一些工具&#xff0c;记录如下 1. openwrt配置 因为有软路由&#xff0c;所以就借助openwrt了&#xff0c;一…...

C++ STL 迭代器失效

一、学习资料 STL迭代器的使用 二、vector容器获取值是下标法和at()的区别 vector<int> vA; int array[]{0,1,2,3,4}; vA.assign(array,array5); cout<<vA[6]<<endl; cout<<va.at(6)<<endl;如上述代码&#xff0c;当使用vA[6]的方式出现访问越…...

麒麟KYLINIOS软件仓库搭建02-软件仓库添加新的软件包

原文链接&#xff1a;麒麟KYLINIOS软件仓库搭建02-软件仓库添加新的软件包 hello&#xff0c;大家好啊&#xff0c;今天给大家带来麒麟桌面操作系统软件仓库搭建的文章02-软件仓库添加新的软件包&#xff0c;本篇文章主要给大家介绍了如何在麒麟桌面操作系统2203-x86版本上&…...

专业媒体播放软件Movist Pro中文

Movist Pro是一款专为Mac用户设计的专业媒体播放器。它支持广泛的视频和音频格式&#xff0c;包括MP4、AVI、MKV等&#xff0c;并提供了高级播放控件和定制的视频设置。其直观易用的用户界面&#xff0c;使得播放高清视频更为流畅&#xff0c;且不会卡顿或滞后。同时&#xff0…...

数据结构-邻接表广度优先搜索(C语言版)

对于一个有向图无向图&#xff0c;我们下面介绍第二种遍历方式。 广度优先搜索&#xff0c;即优先对同一层的顶点进行遍历。 如下图所示&#xff1a; 该例子&#xff0c;我们有六个顶点&#xff0c; 十条边。 对于广度优先搜索&#xff0c;我们先搜索a&#xff0c;再搜索abc…...

Py之auto-gptq:auto-gptq的简介、安装、使用方法之详细攻略

Py之auto-gptq&#xff1a;auto-gptq的简介、安装、使用方法之详细攻略 目录 auto-gptq的简介 1、版本更新历史 2、性能对比 推理速度 困惑度&#xff08;PPL&#xff09; 3、支持的模型 3、支持的评估任务 auto-gptq的安装 auto-gptq的使用方法 1、基础用法 (1)、量…...

【Linux】Linux+Nginx部署项目(负载均衡动静分离)

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于Linux的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.Nginx负载均衡 1.什么是负载均衡 2.实…...

C++笔记之vector的成员函数swap()和data()

C笔记之vector的成员函数swap()和data() 标准C中的std::vector类确实有swap()和data()这两个成员函数。下面是它们的简要描述&#xff1a; swap(): std::vector的swap()成员函数用于交换两个向量的内容&#xff0c;实现了高效的交换操作&#xff0c;不需要复制向量的元素。这…...

Linux centos环境 安装谷歌浏览器

教程 地址...

go-gin-vue3-elementPlus带参手动上传文件

文章目录 一. 总体代码流程1.1 全局Axios部分样例1.2 上传业务 二. 后端部分三. 测试样例 go的mvc层使用gin框架. 总的来说gin的formFile封装的不如springboot的好.获取值有很多的坑. 当然使用axios的formData也有不少坑.现给出较好的解决办法 以下部分仅贴出关键代码 一. 总…...

艺术的维度:洞察AI诈骗,优雅防范之艺术

当前&#xff0c;AI技术的广泛应用为社会公众提供了个性化智能化的信息服务&#xff0c;也给网络诈骗带来可乘之机&#xff0c;如不法分子通过面部替换语音合成等方式制作虚假图像、音频、视频仿冒他人身份实施诈骗、侵害消费者合法权益。 以下是一些常见的AI诈骗例子&#xf…...

JavaScript的作用域和作用域链

作用域 ● 作用域&#xff08;Scoping&#xff09;&#xff1a;我们程序中变量的组织和访问方式。"变量存在在哪里&#xff1f;“或者"我们可以在哪里访问某个变量&#xff0c;以及在哪里不能访问&#xff1f;” ● 词法作用域&#xff08;Lexical scoping&#xff…...

电脑文件批量重命名攻略:高效操作技巧助您轻松完成任务

在日常使用电脑时&#xff0c;我们经常需要对文件进行重命名。当文件数量众多时&#xff0c;手动重命名既耗时又容易出错。此时&#xff0c;借助一些实用技巧&#xff0c;我们可以轻松地完成电脑文件的批量重命名。本文将提供一份全面的电脑文件批量重命名攻略&#xff0c;帮助…...

四、三种基本程序结构

1、程序结构 (1)在C语言程序中&#xff0c;一共有三种程序结构&#xff1a;顺序结构、选择结构(分支结构)、循环结构。 顺序结构&#xff1a;按照事务本身特性&#xff0c;必须一个接着一个来完成。选择结构&#xff1a;到某个节点后&#xff0c;会根据一次判断结果来决定之后…...

深入理解元素的高度、行高、行盒和vertical-align

1.块级元素的高度 当没有设置高度时&#xff0c;高度由内容撑开&#xff0c;实际上是由行高撑开&#xff0c;当有多行时&#xff0c;高度为每行的行高高度之和。 行高为什么存在&#xff1f; 因为每行都由一个行盒包裹&#xff0c;行高实际上是行盒的高度。 2.什么是行盒&am…...

什么叫储能能量管理单元EMU?储能能量管理单元EMU功能?储能EMU是什么?储能能量管理系统如何实现一次调频AGC-AVC功能?

一&#xff1a;储能EMU是什么意思?什么叫储能能量管理单元EMU&#xff1f; EMU是能量管理单元的英文缩写 (Energy Management Unit, EMU) EmuPower3300能量管理单元EMU是由广州智昊电气研发配套EsccPower3300储能协调管理器组成对光伏电站的管理&#xff0c;控制&#xff0c;…...

机器学习之决策树

决策树&#xff1a; 是一种有监督学习方法&#xff0c;从一系列有特征和标签的数据中总结出决策规则&#xff0c;并采用树状图的结构来呈现规则&#xff0c;用来解决分类和回归问题。 节点&#xff1a;根节点&#xff1a;没有进边&#xff0c;有出边。包含最初的&#xff0c;针…...

聊聊logback的UNDEFINED_PROPERTY

序 本文主要研究一下logback的UNDEFINED_PROPERTY substVars ch/qos/logback/core/util/OptionHelper.java public static String substVars(String input, PropertyContainer pc0, PropertyContainer pc1) {try {return NodeToStringTransformer.substituteVariable(input,…...

记一次pdjs时安装glob出现,npm ERR! code ETARGET和npm ERR! code ELIFECYCLE

如往常一样&#xff0c;我使用pdjs来编译proto文件&#xff0c;但出现了以下报错&#xff1a; 大致就是pdjs的util在尝试执行npm install glob^7.2.1 escodegen^1.13.0时出错了 尝试手动执行安装&#xff0c;escodegen被正确安装&#xff0c;但glob^7.2.1出错 npm ERR! code E…...

Zabbix如何监控腾讯云NAT网关

1、NAT网关介绍 NAT 网关&#xff08;NAT Gateway&#xff09;是一种支持 IP 地址转换服务&#xff0c;提供网络地址转换能力&#xff0c;主要包括SNAT&#xff08;Source Network Address Translation&#xff0c;源网络地址转换&#xff09;和DNAT&#xff08;Destination N…...