资讯动态

node+expressvue 自制一键部署服务

发布时间:2026/8/7 22:44:50 来源:尧图企业网站定制
tips:仅针对nuxt项目部署可自行调整用到的npm包如下express //用于创建服务器和路由。 ssh2 //用于通过 SSH 连接远程服务器。 child_process //用于执行命令行命令。 compressing //用于压缩文件。 fs //用于文件系统操作。 path //用于处理和转换文件路径。 archiver //用于创建压缩文件。 sqlite //用于数据库操作。 WebSocket //用于 WebSocket 通信。1.搭建nodeexpress,此步骤省略我们直接进入主题2.创建ws.js文件 搭建webSocket服务将部署过程中的内容实时发送给前端页面const WebSocket require(ws); const url require(url); const clientslist {}; const wss new WebSocket.Server({ port:5001 }); wss.on(connection, function connection(ws,req) { let clientsIdurl.parse(req.url, true).query.id clientslist[clientsId]ws clientslist[clientsId].on(message, function incoming(message,reqs) { let arrJSON.parse((message).toString()) if(!arr.clientsId){ arr.clientsIdclientsId clientslist.serves.send(JSON.stringify(arr)) }else{ let msg{ message:arr.message } clientslist[arr.clientsId].send(JSON.stringify(msg)) } }); ws.on(close, function(err) { console.log(err,错误) console.log(The client has disconnected.); }); ws.on(error, function(error) { console.error(WebSocket error:, error); }); });3.新建一个文件夹sqlite 在该文件夹下创建两个文件。第一个dataBase.db(当然也可以用JSON文件代替若用JSON文件数据库的相关操作就不需要了要换成对JSON数据的操作)存放部署所需要的一些具体参数比如服务器相关数据和打包的一些命令第二个文件是db.js 是一个连接数据库的方法代码如下const sqlite3 require(sqlite3).verbose(); var path require(path); const dbPath path.resolve(__dirname, database.db); const db new sqlite3.Database(dbPath, (err) { if (err) { console.error(数据库连接失败:, err.message); } else { console.log(成功连接到数据库); } }); module.exports db;4.新建一个automatic.js文件该文件内包含部署的整个流程流程包括更新状态为开始。获取配置数据。打包项目。压缩文件。连接远程服务器。上传压缩包。解压文件。安装依赖。启动项目。删除本地和远程的压缩文件。更新状态为完成。从sqlite获取部署所需要数据let getauticdata (val) { return new Promise((resolve, reject) { db.all(SELECT * FROM main.pipeline WHERE id ?, [val], (err, rows) { resolve(rows[0]); }); }); }; //获取到的数据包含 部署对应的服务器的ip、端口、用户名、密码本地项目所在目录打包命令打包本地项目let buildpack (config, callback) { return new Promise((resolve, reject) { const commands config.PackagingCommand;//命令 const child exec(commands, { cwd: config.basefilepath, encoding: utf-8, stdio: inherit });//执行命令 child.stdout.on(data, (data) { callback(data.toString()); console.log(标准输出: ${data}); //执行过程输出内容 }); child.on(close, (code) { if (code 0) { //code为 0 执行成功 resolve(); } else { reject(退出码${code}); } }); child.on(error, (error) { reject(执行错误: ${error}); }); }); };压缩上一步打包完成的产物let filesZip (config) { return new Promise((resolve, reject) { const output fs.createWriteStream(path.join(config.basefilepath, build.zip)); const archive archiver(zip, { zlib: { level: 9 } }); output.on(close, () { resolve(); }); archive.pipe(output); const filesAndDirs JSON.parse(config.packagingDirectory); filesAndDirs.forEach(item { const stats fs.statSync(path.join(config.basefilepath, item)); if (stats.isDirectory()) { archive.directory(path.join(config.basefilepath, item), path.basename(item)); } else { archive.file(path.join(config.basefilepath, item), { name: path.basename(item) }); } }); archive.finalize(); }); };使用ssh2 连接远程服务器let conntossh (config) { return new Promise((resolve, reject) { conn.on(ready, () { resolve(); }).connect({ host: config.serveHost,//服务器ip port: config.servePort,//端口 username: config.serveUserName,//用户名 password: config.servePsd//密码 }); }); };使用sftp将压缩打包产物 传输远程服务器let conntoftp (config) { return new Promise((success, errors) { conn.sftp((err, sftp) { sftp.fastPut(path.join(config.basefilepath, build.zip), config.remoteDirpath build.zip, {}, (err) { if (err) { errors(err); } else { sftp.end(); success(); } }); }); }); }; //config.basefilepath 本地项目目录 //config.remoteDirpath 远程服务器指定目录在远程服务器上解压 ZIP 文件。let unzip (config, callback) { return new Promise((success, errors) { const command unzip -o build.zip; //解压并强制覆盖命令 const remoteDirectory config.remoteDirpath; //指定目录 conn.exec(cd ${remoteDirectory} ${command}, (err, stream) { if (err) { errors(err); } else { stream.on(close, (code, signal) { success(); }).on(data, (data) { callback(data.toString()); console.log(STDOUT: data); }); } }); }); };在服务器上安装依赖因为nuxt项目是服务端渲染的和普通vue项目不一样需要在服务端启动一个端口所以需要安装依赖let installreliance (config, callback) { return new Promise((resolve, reject) { const command npm i; conn.exec(cd ${config.remoteDirpath} ${command}, (err, stream) { if (err) { reject(err); } else { stream.on(close, (code, signal) { resolve(); }).on(data, (data) { callback(data.toString()); console.log(STDOUT: data); }).stderr.on(data, (data) { callback(data.toString()); console.error(STDERR: data.toString()); }); } }); }); };使用PM2,启动项目可保证项目持久化let startrun (config, callback) { return new Promise((success, errors) { const command pm2 start ecosystem.config.js; const remoteDirectory config.remoteDirpath; conn.exec(cd ${remoteDirectory} ${command}, (err, stream) { if (err) { errors(err); } else { stream.on(close, (code, signal) { success(); }).on(data, (data) { callback(data.toString()); console.log(STDOUT: data); }); } }); }); };删除本地和服务器上的压缩文件//删除本地压缩文件 exec(del build.zip, { cwd: config.basefilepath, encoding: utf-8, stdio: inherit }); //删除服务器上的压缩文件 const command rm -f build.zip; const remoteDirectory config.remoteDirpath; conn.exec(cd ${remoteDirectory} ${command}更新数据库中的部署状态let updatastatus (id, runStates) { return new Promise((resolve, reject) { db.all(UPDATE main.pipeline SET runStates? WHERE id ?, [runStates, id], (err, rows) { resolve(); }); }); };WebSocket 连接const ws new WebSocket(ws://localhost:5001?idserves); ws.on(message, function incoming(message) { let arr JSON.parse((message).toString()); if (arr.start) { console.log(arr.id); activateDeployment(arr.id, arr.clientsId); } });通过 WebSocket 连接到服务器并监听消息。当收到消息并解析为 JSON 对象后如果包含 start 字段则调用 activateDeployment 方法 开始部署主要部署方法let activateDeployment async (id, clientsId) { let arr { clientsId: clientsId }; try { await updatastatus(id, 1); arr.message start; ws.send(JSON.stringify(arr)); let config await getauticdata(id); arr.message 正在准备项目打包......; ws.send(JSON.stringify(arr)); await buildpack(config, function (val) { arr.message val; ws.send(JSON.stringify(arr)) }); arr.message 打包完成; ws.send(JSON.stringify(arr)); arr.message 正在压缩文件; ws.send(JSON.stringify(arr)); await filesZip(config); arr.message 压缩文件完成; ws.send(JSON.stringify(arr)); arr.message 正在连接服务器; ws.send(JSON.stringify(arr)); await conntossh(config); arr.message 服务器连接成功; ws.send(JSON.stringify(arr)); arr.message 正在上传压缩包; ws.send(JSON.stringify(arr)); await conntoftp(config); arr.message 上传完成; ws.send(JSON.stringify(arr)); arr.message 正在解压; ws.send(JSON.stringify(arr)); await unzip(config, function (val) { arr.message val; ws.send(JSON.stringify(arr)) }); arr.message 解压完成; ws.send(JSON.stringify(arr)); arr.message 准备安装依赖; ws.send(JSON.stringify(arr)); await installreliance(config, function (val) { arr.message val; ws.send(JSON.stringify(arr)) }); arr.message 依赖安装完成; ws.send(JSON.stringify(arr)); arr.message 正在准备启动项目; ws.send(JSON.stringify(arr)); await startrun(config, function (val) { arr.message val; ws.send(JSON.stringify(arr)) }); arr.message 项目启动成功; ws.send(JSON.stringify(arr)); await deletezipfile(config); await deleteremotezipfile(config); await updatastatus(id, 2); arr.message end; ws.send(JSON.stringify(arr)); } catch (error) { await updatastatus(id, -1); arr.message error; ws.send(JSON.stringify(arr)); arr.message end; ws.send(JSON.stringify(arr)); } };开始部署将数据库状态更新为1 代表部署中部署中的每一步都会将输出内容通过ws发送到前端中间出错出错的话会将数据状态跟新为-1代表部署失败并将错误信息发送给前端若一路畅通将状态跟新为2代表部署成功5.前端调用runs(id){ this.log[id] let wsnew WebSocket(ws://localhost:5001?idid) ws.onopen () { console.log(WebSocket连接已打开); let arr{start:true,id:id} ws.send(JSON.stringify(arr)); }; ws.onmessage (event) { let arrJSON.parse(event.data); console.log(arr.message) this.log[id](arr.messagebr) if(this.logdialog){ var box this.$refs.logcont; // 使用scrollTop将滚动条滚动到最低 box.scrollTop box.scrollHeight; } if(arr.messagestart){ this.getpagedata() } if(arr.messageend){ console.log(部署完成) ws.close() this.getpagedata() } }; ws.onclose () { console.log(WebSocket连接已关闭); // 可以尝试重新连接 // this.connect(); }; },到这里整个部署流程已经可以结束了不过。。。。不过嘛每次都得启动两个项目一个是服务端一个是vue前端项目但是我们做的是一键部署为的就是便捷如果中间这两个项目因为某些原因被中断掉还要重新去启动这样就不够便捷了所以就有了以下的操作6.配置全局启命令1.创建文件夹放在D盘的某个目录下在该文件夹下执行 npm init 命令 生成 pakejosn 文件并修改如下{ name: runpipeline, version: 1.0.0, description: , bin: { runpipeline: runpipeline.js }, author: , license: ISC } // bin 中 runpipeline 为自定义的全局命令 runpipeline.js 是全局命令执行的是该文件 下面会讲到2.将 上面搭建的node项目和vue项目全部都放到当前文件 在vue项目的主目录下 新建一个ecosystem.config.js内容如下module.exports { apps: [ { name: pipeLineUi, //服务名称---自定义 script: npm, args: run serve,//命令 watch: true, //开启文件发生变化自动重启 } ] }3.新建runpipeline.js文件 代码如下#!/usr/bin/env node const { exec} require(child_process); (async(){ let serverstatus await checkServestatus(www) //检查服务端的是否已经启动 if(serverstatus0){ // 若没启动 先启动部署服务 在启动客户端 await startserve() startclient() }else{ startclient // 若启动 直接启动客户端 } }) let startclientasync (){ let clientstatus await checkServestatus(pipeLineUi) //检查客户端是否启动 if(clientstatus0){ // 若没启动 启动客户端 再打开浏览器 await startclients() exec(start chrome http://www.example.com); //打开浏览器 }else{ exec(start chrome http://www.example.com); // 若启动 直接打开浏览器 } } let startserve(){ return new Promise((resolve, reject) { const child exec(pm2 start ./serve/bin/www,{cwd:__dirname, encoding: utf-8,stdio: inherit,timeout: 100000,shell: true }); child.stdout.on(data, (data) { console.log(${data}); }) child.on(close, (code) { if(code0){ console.log(部署服务启动成功) resolve() }else{ console.log(部署服务启动失败) console.log(退出码${code}) } }); child.on(error, (error) { console.log(错误码?${code}) }); }) } let startclients(){ return new Promise((resolve, reject) { const child exec(pm2 start ./client/ecosystem.config.js,{cwd:__dirname, encoding: utf-8,stdio: inherit,timeout: 100000,shell: true }); child.stdout.on(data, (data) { console.log(${data}); }) child.on(close, (code) { if(code0){ console.log(客户端启动成功) resolve() }else{ console.log(客户端启动失败) console.log(退出码${code}) } }); child.on(error, (error) { console.log(错误码?${code}) }); }) }两个项目中全都使用 pm2 启动可保证持久化下一步 在当前目录的终端中执行 npm link 之后就可以在任意目录下直接输入 runpipeline 就可以启动一键部署了7.当然以上还不够简化最后一步设置开机自启嗯.............................................................................................................................................................................................................................还没实现。。。。。。。。。后面补上

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价