前言
- 起因
- 2023年了,有的项目因为各种原因还是没有用上ci/cd🌝,所以还是传统项目打包完,自己手动压缩,然后上传企业微信,本文目的就是将这部分工作交给机器,平均每次打包能节约2分钟吧😁(因为经常忘记是不是打包完了,所以可能还要比2分钟长)
- 你可以获取到什么知识
- 企业微信发消息(文本消息,文件消息)
- 在nodejs里面上传文件,前端一般是点击上传按钮选择文件,然后走formData那一套,那nodejs怎么做呢,可以看一下下面的实现
- nodejs里面将文件夹变成zip,监听文件变化
总需求
运行本项目A,在项目B打包完成后,A项目检测有没有已经压缩的zip,有就删除,重新压缩,压缩完成后,向企业微信(钉钉/飞书等支持webhook的工具都可以,本文以企业微信为例)发信息或者发送这个压缩包(个人建议还是发消息,压缩包只支持20M以下(压缩包大于20M可能要考虑优化了哈哈😃),并且要走企业微信上传,数据无价🙂)
初始化
- 本文就用到了两个包 axios 与 archiver
- axios 只是为了发请求,你可以用任何一种你用的熟练或者内置的情况下就用项目的请求库
- archiver 是打包的库,compressing也可以不过我打包出来的zip总是在奇怪的位置,懂哥可以补充一下
- 其实也可以用 linux 命令 zip,不过这种就怕删库🙃,加上你要装 压缩工具(zip这种) 到你的命令行
yarn init -y / npm init -y
// package.json
{
"scripts": {
"start": "node index.js"
}
}
// 装包 如果是内置到项目里面,这里建议加 -D,如果是想外置就随意
yarn add axios archiver -D
公共部分
- 在企业微信群里面可以申请机器人,会有一个 webhook 地址就是下面这个 weixinUploadUrl ,自己把对应位置的 weixinApiKey 复制出来就行
- 按下面的格式就是我想 等 D:/workspace/front/dist 这个文件夹生成完的时候自动压缩成 dist.zip
const fs = require('fs');
const path = require('path');
const archiver = require('archiver');
const axios = require('axios');
const FormData = require('form-data');
const sourceDir = 'D:/workspace/front'; // 监听的目录
const watchFileName = 'dist'; // 监听的文件夹名称
const zipFile = `dist.zip`;
const weixinApiKey = '这是你的企业微信机器人 key'; // 企业微信机器人 机器人key
const weixinUploadUrl = `https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key=${weixinApiKey}&type=file`;
监听
- 监听部分用node 自带的 fs.watch,内置这个js文件这一步不需要
- 外置麻烦就在这里,比如dist文件夹从生成,到里面文件完全生成完毕,我不知道里面所有文件什么时候完全生成完毕,所以开始复制时我直接延时一分钟再打包,当然如果有朋友能知道怎么优化我这边调一下最好
// 监听 dist 文件夹 部分
fs.watch(sourceDir, { recursive: true }, (event, filename) => {
if (filename !== watchFileName !== 'rename') {
return;
}
setTimeout(compress, 60 * 1000) //
});
压缩
- 如果不想走企业微信上传那就注释下面提到的那一行,就发个消息通知自己,自己复制压缩包发给同事就行,相信就直接走企业微信上传,然后机器人帮你发这个zip到群里
// 【压缩部分】
async function compress() {
const distPath = path.join(sourceDir, watchFileName);
// 这里加了 .. 是回到上一层,开始用的时候发现打出来的zip跑到 dist文件夹里面去了这里做修正
// 有优化建议的可以提一提,优化一下
const distZipPath = path.join(distPath, '..', zipFile);
// 判断是否有 dist 文件夹
if (!fs.existsSync(distPath)) {
console.log(
`[${new Date().toLocaleString()}]: 未检测到 ${watchFileName} 文件夹,不进行压缩`
);
return;
}
const isDirEmpty = (() => {
const dirItems = fs.readdirSync(distPath);
return dirItems.length === 0;
})();
if (isDirEmpty) {
console.log(
`[${new Date().toLocaleString()}]: ${watchFileName} 文件夹为空,不进行压缩`
);
return;
}
// 判断文件是否存在,存在则删除
if (fs.existsSync(distZipPath)) {
fs.unlinkSync(distZipPath);
console.log(`已删除文件:${distZipPath}`);
}
const output = fs.createWriteStream(distZipPath);
const archive = archiver('zip', { zlib: { level: 9 } });
output.on('close', async function () {
console.log(
`[${new Date().toLocaleString()}]: 压缩成功,共 ${archive.pointer()} 个字节`
);
let media_id = '';
// 将打包完成的文件上传企业微信 【看这里 上文提到可以注释的这一行】
media_id = await uploadZip(distZipPath);
// 发送企业微信 信息
sendWeixinMessage(media_id);
});
archive.on('error', function (err) {
console.error(`[${new Date().toLocaleString()}]: 压缩失败: ${err}`);
});
archive.pipe(output);
archive.glob('**/*.*', {
cwd: distPath,
dot: false,
matchBase: false,
});
archive.finalize();
}
上传企业微信
- 不相信企业微信数据安全的可以忽略不用这一步
- 这也可以当作在node环境下上传文件的示例
// 【企业微信上传文件部分】
async function uploadZip(distZipPath) {
// 发送文件到企业微信
const bufferData = fs.readFileSync(distZipPath);
const formData = new FormData();
formData.append('media', bufferData, { filename: zipFile });
const config = {
headers: {
'Content-Type': `multipart/form-data; boundary=${formData._boundary}`,
},
};
try {
const {
data: { media_id },
} = await axios.post(weixinUploadUrl, formData, config);
console.log(
`[${new Date().toLocaleString()}]: 文件上传成功,media_id为 ${
media_id
}`
);
return media_id;
} catch (err) {
console.error(`[${new Date().toLocaleString()}]: 文件上传失败: ${err}`);
}
};
发送信息
// 【企业微信发送信息部分】
async function sendWeixinMessage(mediaId = null, message = '') {
const messageUrl = `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${weixinApiKey}`;
const messageContent = message || `${zipFile}压缩完成`;
if (mediaId) {
// 这块是走了上传的情况,走了上传就会得到这个mediaId
// 企业微信根据这个 mediaId找到你上传的文件,让机器人发
const requestBody = {
msgtype: 'file',
file: {
media_id: mediaId,
},
};
await axios.post(messageUrl, requestBody);
}
// 这块就是企业微信发消息最简单的部分 详细可以参考 申请机器人的那个文档
const messageBody = {
msgtype: 'text',
text: {
content: messageContent,
},
};
await axios.post(messageUrl, messageBody);
console.log(
`[${new Date().toLocaleString()}]: 已向企业微信发送消息:${messageContent}`
);
}
实现代码
index.js
const fs = require('fs');
const path = require('path');
const archiver = require('archiver');
const axios = require('axios');
const FormData = require('form-data');
// const sourceDir = 'D:/workspace/frontend'; // 监听的目录
const sourceDir = 'D:/workspace/江海证券/front'; // 监听的目录
const watchFileName = 'dist'; // 监听的文件夹名称
const zipFile = `dist.zip`;
const weixinApiKey = '475e214e-8cb5-4c83-b360-670bcad2f282'; // 企业微信机器人 机器人1 key
const weixinUploadUrl = `https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key=${weixinApiKey}&type=file`;
// 【企业微信发送信息部分】
async function sendWeixinMessage(mediaId = null, message = '') {
const messageUrl = `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${weixinApiKey}`;
const messageContent = message || `${zipFile}压缩完成`;
if (mediaId) {
const requestBody = {
msgtype: 'file',
file: {
media_id: mediaId,
},
};
await axios.post(messageUrl, requestBody);
}
const messageBody = {
msgtype: 'text',
text: {
content: messageContent,
},
};
await axios.post(messageUrl, messageBody);
console.log(
`[${new Date().toLocaleString()}]: 已向企业微信发送消息:${messageContent}`
);
}
// 【压缩部分】
async function compress() {
const distPath = path.join(sourceDir, watchFileName);
const distZipPath = path.join(distPath, '..', zipFile);
// 判断是否有 dist 文件夹
if (!fs.existsSync(distPath)) {
console.log(
`[${new Date().toLocaleString()}]: 未检测到 ${watchFileName} 文件夹,不进行压缩`
);
return;
}
const isDirEmpty = (() => {
const dirItems = fs.readdirSync(distPath);
return dirItems.length === 0;
})();
if (isDirEmpty) {
console.log(
`[${new Date().toLocaleString()}]: ${watchFileName} 文件夹为空,不进行压缩`
);
return;
}
// 判断文件是否存在,存在则删除
if (fs.existsSync(distZipPath)) {
fs.unlinkSync(distZipPath);
console.log(`已删除文件:${distZipPath}`);
}
const output = fs.createWriteStream(distZipPath);
const archive = archiver('zip', { zlib: { level: 9 } });
output.on('close', async function () {
console.log(
`[${new Date().toLocaleString()}]: 压缩成功,共 ${archive.pointer()} 个字节`
);
let media_id = '';
// 将打包完成的文件上传企业微信
// media_id = await uploadZip(distZipPath);
// 发送企业微信 信息
sendWeixinMessage(media_id);
});
archive.on('error', function (err) {
console.error(`[${new Date().toLocaleString()}]: 压缩失败: ${err}`);
});
archive.pipe(output);
archive.glob('**/*.*', {
cwd: distPath,
dot: false,
matchBase: false,
});
archive.finalize();
}
// 【企业微信上传文件部分】
async function uploadZip(distZipPath) {
// 发送文件到企业微信
const bufferData = fs.readFileSync(distZipPath);
const formData = new FormData();
formData.append('media', bufferData, { filename: zipFile });
const config = {
headers: {
'Content-Type': `multipart/form-data; boundary=${formData._boundary}`,
},
};
try {
const {
data: { media_id },
} = await axios.post(weixinUploadUrl, formData, config);
console.log(
`[${new Date().toLocaleString()}]: 文件上传成功,media_id为 ${
media_id
}`
);
return media_id;
} catch (err) {
console.error(`[${new Date().toLocaleString()}]: 文件上传失败: ${err}`);
}
};
let timer = null;
//【监听部分】 监听 dist 文件夹 部分
fs.watch(sourceDir, { recursive: true }, (event, filename) => {
if (filename !== watchFileName) {
return;
}
clearTimeout(timer)
// 这一段用来测试打印 计算得出下面的 10s,我这边应该是8S左右,目前没有一个好一点 简单一点 的办法监听dist文件夹里面的东西完全生成成功
console.log(
`[${new Date().toLocaleString()}]: 检测到 ${watchFileName} 文件夹变化,开始打印`
);
timer = setTimeout(() => {
console.log(
`[${new Date().toLocaleString()}]: 检测到 ${watchFileName} 文件夹变化,开始压缩...`
);
compress();
}, 10000); // 延迟 10s 后压缩,如果没有变化就正常进行压缩
});
内置方案
外置的情况下,不能完全准确的实现项目打包完毕开始压缩,这一点可以通过把这个js文件内置项目里面,但是项目会多装一个archiver包,如果接受内置的话,方案应该是算比较完美的,并且可以去掉监听的部分,直接压缩,可以用 npm scripts 自带的钩子实现,大体逻辑如下
// package.json
{
"scripts": {
"build": "xxx " // 项目打包代码
"postbuild": "node 文中的js代码" // postXXX 会在 XXX 执行完成后执行
}
}
优化空间与扩展
- 优化空间
- 外置的情况可以整一个对象,项目A 地址:wx key 这种,监听多个文件夹,不过并发啥的就更复杂了,理论不会有🤣,一般不会同时打包3个项目,3个项目同时打包完吧😆,交给有缘人优化
- 监听 dist 文件夹的内容完全生成完毕,目前没有准确监听手段除非内置js到项目
- 扩展
- 这文章主要抛砖引玉,企业微信/钉钉/飞书等机器人发消息一般都是用在服务器CI/CD上面,同时呢,这个类似的服务你可以运行在服务器上面,实现自动签到,每日提醒,每日天气等各种地方
感谢
感谢各位看到这里,第一次发文章,可能有点啰嗦,喜欢的可以帮我在对应的这个github点个star😁 github 附带 内置与外置版本 github.com/qinbuff/mov…