构建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,其别名是 c
  command: '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) {
  // 先获取到了命令行中的name
  const { 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 node

const yargs = require('yargs');
const { inquirerPrompt } = require('./inquirer');

// 命令配置
yargs.command({
  // 字符串,子命令名称,也可以传递数组,如 ['create', 'c'],表示子命令叫 create,其别名是 c
  command: '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 脚手架不需要打包,所以还需要为这个工程添加一个 组件库,工具函数库等类型的包;

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

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

相关文章

Android工具栏ToolBar

主流APP除了底部有一排标签栏外&#xff0c;通常顶部还有一排导航栏。在Android5.0之前&#xff0c;这个顶部导航栏以ActionBar控件的形式出现&#xff0c;但AcionBar存在不灵活、难以扩展等毛病&#xff0c;所以Android5.0之后推出了ToolBar工具栏控件&#xff0c;意在取代Aci…

Python 获取cpu、内存利用率

获取cpu、内存利用率 # -*- coding: latin1 -*- import psutil cpuPercent 0 psutil.cpu_percent() while True:vm psutil.virtual_memory()memoryPercent vm.percentcpuPercent psutil.cpu_percent(1) *10print("cpuPercent:"str(cpuPercent)" %")prin…

TiDB 7.4 发版:正式兼容 MySQL 8.0

MySQL 是全球最受欢迎的开源数据库&#xff0c;长期位于 DB-Engines Ranking 排行榜第二名&#xff0c;在世界范围内拥有数量庞大的企业用户和开发者。然而&#xff0c;随着时间的推移&#xff0c;MySQL 用户正面临新挑战。Oracle 官宣将在 2023 年 10 月终止 MySQL 5.7 版本的…

【小白福音】手把手教学搭建Vue+SpringBoot开发环境完整教程

前言:在很多新手小白在准备开发一个属于自己的前后端分离项目的时候需要准备一些例如Java环境配置、Node.Js配置、Maven配置以及软件安装等等,于本次博主亲自录制了一套完整的安装配置教程,提供到最后给大家进行下载。 注:本教程仅适用于小白,每一节课都是博主原创录制的,…

学 Java 怎么进外企?

作者&#xff1a;**苍何&#xff0c;CSDN 2023 年 实力新星&#xff0c;前大厂高级 Java 工程师&#xff0c;阿里云专家博主&#xff0c;土木转码&#xff0c;现任部门技术 leader&#xff0c;专注于互联网技术分享&#xff0c;职场经验分享。 &#x1f525;热门文章推荐&#…

《黑客帝国:破解编程密码》——探索编程世界的奥秘

文章目录 前言黑客帝国代码雨UbuntuLinux世界的奥秘如何在Ubuntu中查看系统信息科普推荐书籍后记 前言 在电影《黑客帝国》问世后&#xff0c;它不仅带来了震撼视觉体验&#xff0c;更在技术和编程领域产生了深远的影响。这部电影&#xff0c;让人们对计算机和编程的认识进一步…

0-1矩阵列互斥问题——回溯法 Python实现

三、 0-1 矩阵的列集互斥问题。给定一个 m n m \times n mn 的 0-1 矩阵 A \mathrm{A} A 。定义列互斥为: 对于矩阵 A A A 中的任意两列 i i i 和 j j j, 如果在对应的每一行上, i i i 和 j j j 不存在同时为 1 的情况, 则称列 i \mathrm{i} i 和 j \mathrm{j} j 互斥…

[动态规划] (四) LeetCode 91.解码方法

[动态规划] (四) LeetCode 91.解码方法 91. 解码方法 题目解析 (1) 对字母A - Z进行编码1-26 (2)11106可以解码为1-1-10-6或者11-10-6, 但是11-1-06不能解码 (3) 0n不能解码 (4) 字符串非空&#xff0c;返回解码方法的总数 解题思路 状态表示 dp[i]&#xff1a;以i为结…

Echats-自定义图表1

效果图&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html lang"zh-cmn-Hans"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>…

手机知识:手机“飞行模式”你真的会用吗,看完你就懂了

目录 “飞行模式”的实用技能 关于手机的谣言 回想一下&#xff0c;当你第一次知道手机上的“飞行模式”时&#xff0c;你认为这是一个怎样的功能&#xff1f; 普通青年&#xff1a;在飞机上要使用的模式。 文艺青年&#xff1a;手机终日忙忙碌碌&#xff0c;偶尔也需要放飞…

Java入门篇 之 逻辑控制(练习题篇)

博主碎碎念: 练习题是需要大家自己打的请在自己尝试后再看答案哦&#xff1b; 个人认为&#xff0c;只要自己努力在将来的某一天一定会看到回报&#xff0c;在看这篇博客的你&#xff0c;不就是在努力吗&#xff0c;所以啊&#xff0c;不要放弃&#xff0c;路上必定坎坷&#x…

宏观角度认识递归之汉诺塔问题

宏观角度认识递归 听到递归&#xff0c;不少人都会对此充满些迷惑&#xff0c;今天我们就从不同的角度来认识递归&#xff0c;消除恐惧。 递归&#xff0c;简单来说&#xff0c;就是函数自己调用自己的情况&#xff0c;也就是在一个主问题中&#xff0c;我们会引申出一个相同的…

STM32-电源管理(实现低功耗)

电源管理 STM32 HAL库对电源管理提供了完善的函数和命令。 工作模式&#xff08;高功耗->低功耗&#xff09;&#xff1a;运行、睡眠、停止、待机。 若备份域电源正常供电&#xff0c;备份域内的RTC都可以正常运行&#xff0c;备份域内的寄存器的数据会被保存&#xff0c;不…

家用洗地机哪个牌子质量最好?家用洗地机推荐

洗地机也就是集吸尘器&#xff0c;拖地&#xff0c;洗地&#xff0c;功能于一体的家电&#xff0c;无论干湿垃圾都能清理的干干净净&#xff0c;而且还不用弯腰&#xff0c;有的只用换个头&#xff0c;就从拖地变成了吸尘器和除螨仪简直就是清洁家里卫生的打扫神器啦!那么面对市…

Spring-Spring 之底层架构核心概念解析

BeanDefinition BeanDefinition表示Bean定义&#xff0c;BeanDefinition中存在很多属性用来描述一个Bean的特点。比如&#xff1a; class&#xff0c;表示Bean类型scope&#xff0c;表示Bean作用域&#xff0c;单例或原型等lazyInit&#xff1a;表示Bean是否是懒加载initMeth…

chatgpt接口调用

在线接口文档&#xff1a; https://app.apifox.com/invite?tokensymrLP7sojF6N31kZqnpZ 接口地址 https://chat.xutongbao.top/api/light/chat/createChatCompletion 请求方式 POST 请求参数 token String, 必须 prompt Array, 必须 例子一&#xff1a; 包含上下文 [ { "…

行政处罚类型有哪些?哪里能够查到一家企业的行政处罚信息?

在查询企业信息的时候&#xff0c;处理企业基础的工商信息之外&#xff0c;我们还会注意到到的就是企业的处罚信息。毕竟处罚可以直观反应出一个企业的违法违规行为&#xff0c;帮助我们直接了解企业。 那么&#xff0c;企业的行政处罚包括哪些内容呢&#xff1f; 根据《中华…

【MATLAB源码-第66期】基于麻雀搜索算法(SSA)的栅格路径规划,输出做短路径图和适应度曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 麻雀搜索算法&#xff08;Sparrow Search Algorithm, SSA&#xff09;是一种新颖的元启发式优化算法&#xff0c;它受到麻雀社会行为的启发。这种算法通过模拟麻雀的食物搜索行为和逃避天敌的策略来解决优化问题。SSA通过模拟…

Macroscope安全漏洞检测工具简介

学习目标&#xff1a; 本介绍旨在帮助感兴趣者尽快了解 Macroscope&#xff0c;这是一款用于安全测试自动化和漏洞管理的企业工具。 全覆盖应用程序安全测试&#xff1a; 如下图所示&#xff0c;如果使用多种互补工具&#xff08;SAST/DAST/SCA 等&#xff09;来检测应用程序…

网络取证-Tomcat-简单

题干&#xff1a; 我们的 SOC 团队在公司内部网的一台 Web 服务器上检测到可疑活动。为了更深入地了解情况&#xff0c;团队捕获了网络流量进行分析。此 pcap 文件可能包含一系列恶意活动&#xff0c;这些活动已导致 Apache Tomcat Web 服务器遭到破坏。我们需要进一步调查这一…