一不小心手动实现了k8s的自动化构建

news2024/11/24 4:49:39

背景

由于公司需要对公司内部的软件需要对外部署,对于前端的部署,需要一套部署的方案。尝试了写了一个适配多模块可配置的部署脚本,本文对实现的过程进行一个记录。

目的

脚本采用的是node,前端同学的首选。脚本的目的就是实现k8s中的自动化部署中的部分功能

熟悉k8s的同学或许了解在k8s的自动构建过程中,大概实现了以下几个步骤

  • 从指定的代码仓库中获取代码
  • 执行npm i或者npm ci(如果项目依赖内网npm还需要在这之前设置一下内网镜像)
  • 执行npm build
  • 将生成的静态文件放在指定的文件内

我们的node脚本就需要实现以上功能即可。

实现过程

第一步,从指定的代码仓库中获取代码

从指定仓库获取代码,需要怎么指定。

我们平时克隆代码的时候执行的是git clone xxx,xxx就是指定的代码地址,如果我们模仿一下node index.js xxx,这样的话如果需要克隆多个后面就会很长,不太美观。

这里我选择通过写入配置文件的方式进行clone。并且提供两种clone方式。

1.给一个全量的仓库配置文件,通过手动选择其中的仓库进行clone。
2.创建一个默认的配置,对里面所有的git地址进行clone。

创建一个config.js和一个customeConfig.js文件

// config.js 全量仓库
export default [{url: 'https://github.com/yourProject1.git', // 代码地址modulesName: 'yourProject1', // 项目简称或者标识desc: '项目一' // 项目描述
},{url: 'https://github.com/yourProject2.git',modulesName: 'yourProject2', desc: '项目二'
},{url: 'https://github.com/yourProject3.git',modulesName: 'yourProject3', desc: '项目三'
},{url: 'https://github.com/yourProject4.git',modulesName: 'yourProject4', desc: '项目四'
}] 
// customConfig.js 不在全量仓库里面的地址,全部下载
export default [{url: 'https://github.com/yourProject1.git', // 代码地址modulesName: 'yourProject1', // 项目简称或者标识desc: '项目一' // 项目描述
},{url: 'https://github.com/yourProject2.git',modulesName: 'yourProject2', desc: '项目二'
}] 

使用者如果需要clone的仓库不在config.js里面,可以直接修改customConfig.js里面的文件。

我们还是分步骤实现

flowchart TD 
A[选择clone方式 chooseDownLoadType] --> B{选择项目clone?} 
B --是 选择config中的clone --> C[chooseGit] 
C --选择需要clone的仓库地址 --> E[downLoadGit]
B --否 根据customConfig中的clone ----> E[downLoadGit] 

根据上面流程图,我们需要实现三个方法

  • chooseDownLoadType(); // 选择克隆方式
  • chooseGit(); // 选择克隆仓库
  • downLoadGit(); // 实现git clone

Talking is cheap show me your code 话不多说直接实现代码吧

这里通过inquirer实现控制台的交互效果,具体使用方法可以查看npm官网的使用方法 www.npmjs.com/package/inq…

  • 实现chooseDownLoadType()
function chooseDownLoadType () {inquirer.prompt([{type: "list",name: "preset",message: "请选择克隆的方式",choices: ["选择项目clone", "根据customConfig配置clone"]}]).then(choice => {if(choice.preset === '选择项目clone') { // 按照config文件clonechooseGit(); // 选择git地址}else { // 按照customConfig文件配置downLoadGits = customConfig.map(res => res.url); // 收集地址downLoadGit(); // 下载git}})
} 
  • 实现chooseGit()
function chooseGit () {inquirer.prompt([{type: "checkbox",name: "gits",message: "请选择需要克隆的项目地址:",choices: config.map(res => {return {name: `${res.modulesName}_(${res.desc})`,...res}}),default: [] // 默认选中的git}]).then(choice => {// 收集地址choice.gits.forEach(git => {const moduleName = git.split("_")[0]; // 获取nameif(config.map(conf => conf.modulesName).includes(moduleName)) {downLoadGits.push(config.find(gitConf => gitConf.modulesName === moduleName).url);}})downLoadGit();})
} 

详细代码如下:

import { createRequire } from "module";
const require = createRequire(import.meta.url);
import inquirer from "inquirer";
import config from "./config.js";
import customConfig from "./customConfig.js";

let downLoadGits = [] // 收集需要clone的地址

function chooseDownLoadType () {inquirer.prompt([{type: "list",name: "preset",message: "请选择克隆的方式",choices: ["选择项目clone", "根据customConfig配置clone"]}]).then(choice => {if(choice.preset === '选择项目clone') { // 按照config文件clonechooseGit(); // 选择git地址}else { // 按照customConfig文件配置downLoadGits = customConfig.map(res => res.url); // 收集地址downLoadGit(); // 下载git}})
}

function chooseGit () {inquirer.prompt([{type: "checkbox",name: "gits",message: "请选择需要克隆的项目地址:",choices: config.map(res => {return {name: `${res.modulesName}_(${res.desc})`,...res}}),default: [] // 默认选中的git}]).then(choice => {// 收集地址choice.gits.forEach(git => {const moduleName = git.split("_")[0]; // 获取nameif(config.map(conf => conf.modulesName).includes(moduleName)) {downLoadGits.push(config.find(gitConf => gitConf.modulesName === moduleName).url);}})downLoadGit();})
}

function downLoadGit () {console.log('需要下载的git', downLoadGits)
}

function main () {chooseDownLoadType();
}

main() 

测试一下,运行node index.js 直接回车选择第一种方式

代码运行到chooseGit()这个方法了,这时根据提示通过空格()选择需要clone的项目地址,通过键盘的上下移动。这里我们选择前面两个然后回车下一步。

这时代码就到downLoadGit()这里了。需要下载的地址已经打印出来了

再测试第二种,运行node index.js

这里就直接把customConfig里面所有的仓库地址都打印出来了。

怎么指定已经实现了,后面就是根据地址下载了,我们再实现downLoadGit()这个比较重要的一步

execa是一个可以通过js实现shell脚本的npm依赖包,后面的npm install和npm build都会通过这个实现。具体使用方法可以查看npm官网的使用方法www.npmjs.com/package/exe…。

  • 实现downLoadGit()
async function downLoadGit () {console.log('需要下载的git', downLoadGits);if(fs.existsSync(PATH)) { // 如果目录存在则删除clearFolder(PATH)}await fs.mkdirSync(PATH);downLoadGits.forEach(async gitUrl => {let childProcess = execa("git", ["clone", gitUrl, "--progress"], {cwd: `${PATH}` // 执行的目录也就是git clone需要运行的目录})childProcess.stderr.pipe(process.stderr); // 将clone进度在控制台输出try {const result = await childProcess;if(result.exitCode === 0) {console.log('克隆完成')}} catch (error) {console.log('克隆失败', error)}})
} 

这里需要fs模块创建和删除目录,PATH为代码存放的目录

import { createRequire } from "module";
const require = createRequire(import.meta.url);
import inquirer from "inquirer";
import config from "./config.js";
import customConfig from "./customConfig.js";
const fs = require('fs');
let downLoadGits = [] // 收集需要clone的地址
let PATH = 'gits' // clone的代码存放的目录 

删除代码的目录的实现

function clearFolder (path) {let files = [];if (fs.existsSync(path)) {// 是否存在目录files = fs.readdirSync(path); // 读取目录下的目录和文件files.forEach(file => {let curPath = `${path}/${file}`; // 拼接路径if(fs.statSync(curPath).isDirectory()) { // 如果是文件夹就递归遍历clearFolder(curPath)}else {fs.unlinkSync(curPath); // 不是文件夹是文件直接删除}})fs.rmdirSync(path) // 清空文件夹}
} 

到这里,基本上实现了git clone代码的功能,执行node index.js测试图一下,经过一波选择。

index.js完整代码

import { createRequire } from "module";
const require = createRequire(import.meta.url);
import inquirer from "inquirer";
import config from "./config.js";
import customConfig from "./customConfig.js";
import { execa } from 'execa';
const fs = require('fs');

let downLoadGits = [] // 收集需要clone的地址
let PATH = 'gits' // clone的代码存放的目录

function chooseDownLoadType () { inquirer.prompt([{ type: "list", name: "preset", message: "请选择克隆的方式", choices: ["选择项目clone", "根据customConfig配置clone"] }]).then(choice => { if(choice.preset === '选择项目clone') { // 按照config文件clone chooseGit(); // 选择git地址 }else { // 按照customConfig文件配置 downLoadGits = customConfig.map(res => res.url); // 收集地址 downLoadGit(); // 下载git } })
}

function chooseGit () { inquirer.prompt([{ type: "checkbox", name: "gits", message: "请选择需要克隆的项目地址:", choices: config.map(res => { return { name: `${res.modulesName}_(${res.desc})`, ...res } }), default: [] // 默认选中的git }]).then(choice => { // 收集地址 choice.gits.forEach(git => { const moduleName = git.split("_")[0]; // 获取name if(config.map(conf => conf.modulesName).includes(moduleName)) { downLoadGits.push(config.find(gitConf => gitConf.modulesName === moduleName).url); } }) downLoadGit(); })
}

function clearFolder (path) { let files = []; if (fs.existsSync(path)) { files = fs.readdirSync(path); files.forEach(file => { let curPath = `${path}/${file}` if(fs.statSync(curPath).isDirectory()) { clearFolder(curPath) }else { fs.unlinkSync(curPath); } }) fs.rmdirSync(path) }
}

async function downLoadGit () { console.log('需要下载的git', downLoadGits); if(fs.existsSync(PATH)) { // 如果目录存在则删除 clearFolder(PATH) } await fs.mkdirSync(PATH); downLoadGits.forEach(async gitUrl => { let childProcess = execa("git", ["clone", gitUrl, "--progress"], { cwd: `${PATH}` // shell执行的目录 }) childProcess.stderr.pipe(process.stderr); // 将clone进度在控制台输出 try { const result = await childProcess; if(result.exitCode === 0) { console.log('克隆完成') } } catch (error) { console.log('克隆失败', error) } })
}

function main () { chooseDownLoadType();
}

main() 

需要注意的一些问题

1.clone的地址必须得有权限。
2.有些仓库设置了SSL验证,clone的时候需要git config --global http.sslVerify false这样设置一下。
3.为什么clone的时候需要先删除一下目录,因为如果存在相同目录clone会失败,删除再clone这种操作类似build打包的时候对dist文件夹的处理。

第二步,实现执行npm install

第一步的工作已经把主要的原理实现了,接下来的工作相信大部分同学都知道怎么去实现了。

把大象装冰箱的步骤:打开冰箱门,把大象放进去。这里也分两步

  • 找出执行npm install的路径
  • 执行npm install

实现思路:

新建一个build.js文件,定义一个数组installBuildPaths来存放路径,一般执行npm命令行的文件路径需要满足该路径下存在package.json这个文件。

let installBuildPaths = [] 

实现一个方法findPackagePath()去递归刚才存放代码的路径PATH下面的所有文件和目录,查找存在package.json这个文件的路径并保存。为什么需要递归是考虑了多模块的情况。

function findPackagePaths (files, paths) {if(files.length === 0) return;files.filter(file => {return fs.statSync(joinPath([...paths, file])).isDirectory() // 返回目录路径}).forEach(file => {const path = joinPath([...paths, file]);const packageJsonPath = `${path}/package.json`;let existPackageJson = fs.existsSync(packageJsonPath);if(existPackageJson) {installBuildPaths.push(path);}else {let childrenFiles = fs.readdirSync(path);findPackagePaths(childrenFiles, [...paths, file]);}})
}

// 拼接路径
function joinPath (pathArr) {return `${pathArr.join('/')}`;
} 

遍历installBuildPaths里面的路径去执行npm install,最好按顺序执行npm install。

function npmInstall () {run(installBuildPaths.shift());
}async function run (path) {if(path) {let childInstallProcess = execa("npm", ["install", "--loglevel", "silly"], {cwd: `${path}/`})childInstallProcess.stderr.pipe(process.stderr);try {const result = await childInstallProcess;if(result.exitCode === 0) {console.log(`${result.stdout}`)console.log(`install 成功`)}} catch (error) {console.log('install 失败', error)}}
} 

这里执行的是npm install --loglevel silly目的是为了打印出更详细的信息,避免控制台出现await等待的焦虑。

执行脚本的main()

function main () {findPackagePaths(files, [PATH]);npmInstall();
}

main(); 

执行node build.js最终安装完成就是这样,和平常执行npm install最后的输出是一样的。

第三步,实现执行npm build

这里我们选择直接install完就build

npmBuild()实现

async function run (path) {if(path) {let childInstallProcess = execa("npm", ["install", "--loglevel", "silly"], {cwd: `${path}/`})childInstallProcess.stderr.pipe(process.stderr);try {const result = await childInstallProcess;if(result.exitCode === 0) {console.log(`${result.stdout}`)console.log(`install 成功`);console.log(`--------开始执行npm run build-------`);npmBuild(path);}} catch (error) {console.log('install 失败', error)}}
}async function npmBuild (path) {let childBuildProcess = execa("npm", ["run", "build"], {cwd: `${path}/`})childBuildProcess.stderr.pipe(process.stderr);try {const result = await childBuildProcess;if(result.exitCode === 0) {console.log(`${result.stdout}`)console.log(`build 成功`);}} catch (error) {console.log('build 失败', error)}
} 

我们执行一下node build.js看看,如果控制台能看到打包出来的文件那就说明成功了。

第四步,copy文件到指定目录

现在静态文件有了,就只剩最后一步了,将打包出来的文件放入指定的目录当中,在k8s里面这一步就是将静态文件放在镜像里面。

这里面我们需要获取项目打包的输出静态文件的目录,需要读取项目里面的配置文件,一般默认是在dist目录,有些项目可能设置了输出路径就需要获取打包文件的配置,例如我的项目是react,就需要获取build.confing.js这个配置文件。

实现putDistToAimFolder()STATIC是存放静态文件的根目录,PATH是存放代码的根目录,这里需要借助fs-extra模块复制文件夹。

function putDistToAimFolder (path) {const aimFolder = joinPath([STATIC, path.split(`${PATH}/`)[1]]);let config = {};// 这里需要找到项目中的打包配置文件找到打包后的静态文件,我这里是react项目// 如果没找到默认dist目录// 其他项目可以视情况而定 这里需要完善一下if(fs.existsSync(`./${path}/build.config.js`)) {config = require(`./${path}/build.config.js`);config.outputDir = config.outputDir || `dist`;}else {config.outputDir = `dist`;} try {fs.copy(`${path}/${config.outputDir}`, aimFolder);console.log('静态文件已存放到指定目录');} catch (error) {console.log('文件复制失败', error);}
} 

再执行完npmBuild()后执行putDistToAimFolder();

async function npmBuild (path) {let childBuildProcess = execa("npm", ["run", "build"], {cwd: `${path}/`})childBuildProcess.stderr.pipe(process.stderr);try {const result = await childBuildProcess;if(result.exitCode === 0) {console.log(`${result.stdout}`);console.log(`build 成功`);console.log(`-----------copy文件到指定目录-------------`);putDistToAimFolder(path);}} catch (error) {console.log('build 失败', error)}
} 

执行一下node build.js 看下效果

这样我们完整的一个项目的构建流程就走完了,然后继续下一个项目,然后再继续执行npmInstall()这个方法即可。

打完收工贴一下完整的代码

import { createRequire } from "module";
const require = createRequire(import.meta.url);
import chalk from 'chalk';
import { execa } from 'execa';
var fs = require("fs-extra"); 
const PATH = 'gits';
const STATIC = 'static';

let files = fs.readdirSync(PATH);
let installBuildPaths = [];

function findPackagePaths (files, paths) {if(files.length === 0) return;files.filter(file => {return fs.statSync(joinPath([...paths, file])).isDirectory() // 返回目录路径}).forEach(file => {const path = joinPath([...paths, file]);const packageJsonPath = `${path}/package.json`;let existPackageJson = fs.existsSync(packageJsonPath);if(existPackageJson) {installBuildPaths.push(path);}else {let childrenFiles = fs.readdirSync(path);findPackagePaths(childrenFiles, [...paths, file]);}})
}

function joinPath (pathArr) {return `${pathArr.join('/')}`;
}

async function run (path) {if(path) {let childInstallProcess = execa("npm", ["install", "--loglevel", "silly"], {cwd: `${path}/`})childInstallProcess.stderr.pipe(process.stderr);try {const result = await childInstallProcess;if(result.exitCode === 0) {console.log(`${result.stdout}`)console.log(`install 成功`);console.log(`--------开始执行npm run build-------`);npmBuild(path);}} catch (error) {console.log('install 失败', error)}}
}

function npmInstall () {run(installBuildPaths.shift());
}

async function npmBuild (path) {let childBuildProcess = execa("npm", ["run", "build"], {cwd: `${path}/`})childBuildProcess.stderr.pipe(process.stderr);try {const result = await childBuildProcess;if(result.exitCode === 0) {console.log(`${result.stdout}`);console.log(`build 成功`);console.log(`-----------copy文件到指定目录-------------`);putDistToAimFolder(path);}} catch (error) {console.log('build 失败', error)}
}

function putDistToAimFolder (path) {const aimFolder = joinPath([STATIC, path.split(`${PATH}/`)[1]]);let config = {};// 这里需要找到项目中的打包配置文件找到打包后的静态文件,我这里是react项目// 如果没找到默认dist目录// 其他项目可以视情况而定 这里需要完善一下if(fs.existsSync(`./${path}/build.config.js`)) {config = require(`./${path}/build.config.js`);config.outputDir = config.outputDir || `dist`;}else {config.outputDir = `dist`} try {fs.copy(`${path}/${config.outputDir}`, aimFolder);console.log('静态文件已存放到指定目录');console.log(`--------${path}构建完成继续执行下一个--------`);npmInstall();} catch (error) {console.log('文件复制失败', error);}
}

function main () {findPackagePaths(files, [PATH]);npmInstall();
}

main(); 

需要优化的不足之处

以上代码只是简单的实现了一个自动化构建的过程,如果将这段代码放在服务器上运行,理论上是可以实现简单的自动部署的功能。

当然这里面还有许多需要优化的地方

  • 脚本构建的指令可能需要统一处理,这里没有考虑带参数的情况
  • 对脚本执行失败后的处理,比如npm install安装失败的处理,若安装失败删除node_modules防止下次执行脚本安装失败。
  • 对脚本执行目录的收集,虽然考虑到多模块的构建,但是有些情况还是会漏掉,比如嵌套模块的识别就做不到。
  • 对文件静态文件输出目录的识别还得需要从具体的情况考虑,比如老项目手动构建的webpack配置就需要特殊处理了。

总结

在编写这个脚本的过程中学习了很多npm依赖库的使用,比如inpuirer,脚手架必备工具,有兴趣的可以自己捣鼓一下,还有execa,可以通过javascript执行shell脚本,简直YYDS。

对了,还有个chalk,可以改变控制台输出的文字的颜色,我演示的时候没有用,但是也很简单。

虽然原理很简单,自己实现的过程当中也遇到了不少问题,

  • 有些模块最新版本是支持import导入,有些还是只支持require导入。require怎么和import混用靠的就是以下两行代码,前提是你得设置脚本的package.json文件的 "type": "module",
// 兼容CommonJS
import { createRequire } from "module";
const require = createRequire(import.meta.url); 

package.json

{"name": "k8s","version": "1.0.0","description": "","main": "index.js","type": "module","scripts": {"test": "echo \"Error: no test specified\" && exit 1"},"author": "andy","license": "ISC","dependencies": {"chalk": "^5.0.1","execa": "^6.1.0","fs-extra": "^10.1.0","inquirer": "^9.1.1"}
} 
  • 控制台怎么显示脚本运行的过程,这个execa官方使用说明中有提到过
childInstallProcess.stderr.pipe(process.stderr); 

脚本后续还需不需要优化看具体使用情况吧。

最后

最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

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

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

相关文章

企业在ERP系统下的全面预算管理系统的实现

现代企业的发展离不开各种管理系统的建立和应用,针对于企业预算管理而言,全面的管理系统对于企业实现现代化管理有着较为深入的影响,ERP系统可以帮助企业更好的实现此项功能,本文就是在此背景下展开论述的。 编辑搜图 一、全面预算…

文件上传自动化测试方案

一、概述 【测试地址】:https://pan.baidu.com 【测试工具】selenium、requests 【脚本语言】Python 【运行环境】Windows 百度网盘作为文件存储及分享的平台,核心功能大部分是对文件的操作,如果要对它进行自动化测试,优先覆…

马克思主义基本原理笔记(黄色标记要求会背)

马克思主义基本组成部分 马克思主义哲学马克思主义政治经济学科学社会主义历史学、政治学、法学、文化学、新闻学、军事学等 唯物主义,唯心主义划分标准 唯物主义:把世界的本原归结为物质,主张物质是第一性,意识第二性&#xff0c…

Docker容器网络入门

1、查看默认网络模式 首先假定已经安装好docker了,不会安装的可以看我其他文章,很简单。docker安装好后默认是提供三种网络模式(bridge、host、none),可以使用命令docker network ls查看网络状态 [rootlocalhost ~]#…

构建平衡二叉树(数据结构)

构建二又平衡树,插入的节点序列依次为:70 60 40 90 80 98 我们先了解一个构造规则 1、将每一个节点按照顺序依次使用 2、先将第一个节点画在图上,将第二个节点与第一个节点比较, (1)若比该节点大,第二个…

飞行员兄弟(蓝桥杯C/C++B组真题详解)

目录 题目链接:116. 飞行员兄弟 - AcWing题库​​​​​​ 题目思路: 代码详解: 题目链接:116. 飞行员兄弟 - AcWing题库​​​​​​ 题目思路: 1.我们可以知道 对于任意一个点 重复的按两次的话 回不改变原状…

ADI Blackfin DSP处理器-BF533的开发详解43:图像处理专题-ReverseColor (图像反色处理)(含源码)

硬件准备 ADSP-EDU-BF533:BF533开发板 AD-HP530ICE:ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 功能介绍 代码实现了图像反色处理,代码运行时,会通过文件系统打开工程文件根目下" …/ImageView"路径中的 tes…

Linux下局域网yum源配置

文章目录一 需求二 搭建环境准备三 服务端1 创建镜像存放目录与挂载目录2 上传centos7镜像3 对镜像文件进行挂载4 配置本地yum源5 对挂载点建立软链接6 安装http服务7 浏览器验证http服务是否正常启动四 客户端1 修改yum配置文件2 查看yum源一 需求 公司集群架构中&#xff0c…

【linux】之大数据基础平台实施及运维上

一、大数据介绍 学习目标 能够了解为什么使用大数据技术 能够了解大数据指的是什么 为什么使用大数据技术? 数据量越来越大 数据量越来越大数据分析的实时性越来越强数据结果的应用越来越广泛 结论:我们需要使用大数据技术 大数据的定义 大数据是收…

GC垃圾回收器

分代收集器 新生代回收器 Serial:复制算法 | 单线程 | 适合内存不大的场景 ParNew:复制算法 | 多线程 | Serial收集器多线程版本 Parallel Scavenge:复制算法 | 多线程 | 类ParNew,更关注吞吐量 老年代回收器 Serial Old&#xf…

postgresql文件结构

一、控制文件 pg_controldata $PGDATA 二、数据文件 pg中,每个索引、每个表都是一个单独的文件,pg中称为page(也称为段),默认是每个大于1G的page会被分割。例如某个表有200g的大小,那么会被分割为200个文件存储 sel…

Gateway限流的使用

目录 1. 限流的使用场景 2. gateway限流实现 2.1 前提: 2.2 导入依赖包 2.3 在项目配置文件中配置redis​编辑 2.4 开发限流需要的Bean​编辑 2.5 为服务配置限流参数 2.6 压力测试 3. 熔断 3.1 熔断的使用场景 1. 限流的使用场景 为什么限流 限流就是限制…

web网页设计期末课程大作业 基于HTML+CSS+JavaScript制作八大菜系介绍舌尖上的美食5页

🎀 精彩专栏推荐👇🏻👇🏻👇🏻 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 💂 作者主页: 【主页——🚀获取更多优质源码】 🎓 web前端期末大作业…

4、Metasploit系列----未知目标测试

靶机Metasploitable2:192.168.11.105 攻击机kail:192.168.11.106 一、创建工作空间 显示数据库未连接:使用msfdb init对数据进行初始化:db_status显示数据库已连接。使用命令workspace -a 192.168.11.105创建目标工作空间。二、信…

Spring Batch 批处理-作业参数校验

引言 接着上篇:Spring Batch 批处理-作业参数设置与获取,了解作业参数设置与获取后,本篇就来了解一下Spirng Batch 作业参数的校验。 作业参数校验 当外部传入的参数进入步骤处理时,我们需要确保参数符合期望。比如&#xff1a…

前端实现电子签名(web、移动端)通用

前言 在现在的时代发展中,从以前的手写签名,逐渐衍生出了电子签名。电子签名和纸质手写签名一样具有法律效应。电子签名目前主要还是在需要个人确认的产品环节和司法类相关的产品上较多。 举个常用的例子,大家都用过钉钉,钉钉上…

对于双欧拉角(正反欧拉角)的一些理解和思考

文章目录一、正反欧拉角定义二、相关文献阐述三、对正反欧拉角的思考四、参考代码五、参考文献最近看到有人讨论“双欧拉角”或者“正反欧拉角”的问题,因为自己之前没听说过这个概念,为了避免无知,因此找了一些文献进行学习和理解。不过基于…

mysql优化,SELECT语句创建理想索引

思考索引的问题: 1.为什么主键索引比非主键索引快? 2.为什么sql使用like关键字 “%XXX%”无法走索引,而“XXX%”可以? 3.为什么有索引的字段,数据量大了后,增删改会很慢? 一. 了解数据库索引的必…

ADI Blackfin DSP处理器-BF533的开发详解45:图像处理专题-ThresholdData (图像阈值分割处理)(含源码)

硬件准备 ADSP-EDU-BF533:BF533开发板 AD-HP530ICE:ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 功能介绍 代码实现了图像阈值分割处理,代码运行时,会通过文件系统打开工程文件根目下" …/ImageView"路径中的…

Hive中数据类型介绍

文章目录数据库表分区表桶表数据库 当于关系数据库中的命名空间( namespace ),它的作用是将用户和数据库的应用,隔离到不同的数据库或者模式中 Hive中创建数据库等语法 表 Hive 的表在逻辑上由存储的数据和描述表格数据形式的相关元数据组成 元数据&a…