在日常的小程序开发中不使用自动化可能会出现的一些问题:
- 分支切换,代码上传,预览码生成,操作无脑但耗时
- 当生成码的要求同时来的时候,需要等待
- 小程序单独的机制,导致无法像web端那样做整体流程管控
- 非开发人员获取体验码的流程依赖开发人员
自动化的流程构思
- 通过钉钉的指令触发构建 (目前还未实现该步骤,后续补充)
- 以node端为中介,完成流程化操作
- jenkins端进行打包上传预览等耗时操作
- 回调node端,对原有的信息进行美化
- 钉钉接收node端的信息,反馈用户
实施流程:
小程序自动化打包上传
- 生成小程序密钥
- 将密钥文件保存在项目根目录下
- 安装Taro自带小程序CI插件
npm i @tarojs/plugin-mini-ci -D
- 使用Taro插件
// /config/index.js
const CIPluginOpt = {
weapp: {
appid: "*******************",
privateKeyPath: "private.wx30ea7a15d8979ead.key" // 配置密钥的路径
},
// 版本号
version: "1.0.0"
};
const config={
...config,
plugins: [["@tarojs/plugin-mini-ci", CIPluginOpt]]
}
- 配置命令
// package.json
{
"scripts": {
// 构建完后自动 “打开开发者工具”
"build:weapp": "taro build --type weapp --open",
// 构建完后自动“上传代码作为体验版”
"build:weapp:upload": "taro build --type weapp --upload",
// 构建完后自动 “上传代码作为开发版并生成预览二维码”
"build:weapp:preview": "taro build --type weapp --preview"
},
"taroConfig": {
"version": "1.0.0",
"desc": "上传描述"
}
}
到这一步为止小程序的CI已经完成,可以看到执行完 npm run build:weapp:upload
会出现Taro的打包编译过程和结果,微信公众号的后台也出现了本次打包上传的记录
中间可能会涉及因为主包过大而上传失败的问题,这时候的建议就是能进行分包处理,具体的分包可以参考官网
https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/basic.html
dist 文件压缩(因为要将文件上传oss,配合后期钉钉直接下载dist文件包,所以我的预期是将dist文件包以及Taro的打包产物一起上传oss)
- 安装jszip(jszip是一个用于创建、读取和编辑.zip文件的JavaScript库,且API的使用也很简单。)
npm install jszip
- 执行压缩
// zip.js
const JSZip = require("jszip");
const zip = new JSZip();
const fs = require("fs");
const path = require("path");
function readDir(zip, dirPath) {
// 读取dist下的根文件目录
const files = fs.readdirSync(dirPath);
files.forEach(fileName => {
const fillPath = dirPath + "/" + fileName;
const file = fs.statSync(fillPath); // 如果是文件夹的话需要递归遍历下面的子文件
if (file.isDirectory()) {
const dirZip = zip.folder(fileName);
readDir(dirZip, fillPath);
} else {
// 读取每个文件为buffer存到zip中
zip.file(fileName, fs.readFileSync(fillPath));
}
});
}
function create(sourceDir) {
readDir(zip, sourceDir);
zip
.generateAsync({
type: "nodebuffer", // 压缩类型
compression: "DEFLATE", // 压缩算法
compressionOptions: {
// 压缩级别
level: 9
}
})
.then(content => {
// 我将压缩后的文件放在dist目录下的zip文件夹中,方便一同上传oss
const dest = path.join(__dirname, "./dist/zip");
if (dest) {
// 删除旧包目录
const folder_exists = fs.existsSync("./dist/zip");
if (folder_exists == true) {
const dirList = fs.readdirSync("./dist/zip");
dirList.forEach(function(fileName) {
fs.unlinkSync("./dist/zip/" + fileName);
});
}
}
// 创建新包目录
fs.mkdirSync(dest, { recursive: true }); // 把zip包写到硬盘中,这个content现在是一段buffer
fs.writeFileSync(`${dest}/miniPrograms.service.zip`, content);
});
}
function generateZip() {
const sourceDir = path.join(__dirname, "./dist");
fs.access(sourceDir, err => {
if (err) {
fs.mkdirSync(sourceDir);
}
create(sourceDir);
});
}
generateZip();
在package.json 中增加执行压缩的命令"build:zip":"node zip.js",
- oss上传
// oss.client.js
const OSS = require("ali-oss");
const util = require("util");
const fs = require("fs");
const path = require("path");
const readdir = util.promisify(fs.readdir);
const stat = util.promisify(fs.stat);
class OssClient {
constructor() {
this.client = new OSS({
region: "oss-cn-hangzhou",
accessKeyId: "your accessKeyId ", // process.env.ACCESSKEYID,
accessKeySecret: "your accessKeySecret", // process.env.ACCESSKEYSECRET,
bucket:
process.env.APP_ENV === "production" ? "static" : "staticdev", // process.env.BUCKET,
});
}
async putStream(dir, ossDir = "/", fileName) {
try {
const stream = fs.createReadStream(path.resolve(dir, fileName));
return await this.client.putStream(ossDir + fileName, stream);
} catch (e) {
console.log(e);
}
}
async putDir(dir, ossDir) {
let files;
try {
files = await readdir(dir);
} catch (e) {
console.log(`【当前目录不存在】 -- ${dir} --`);
return undefined;
}
for (const i in files) {
const state = await stat(`${dir}/${files[i]}`);
if (state.isDirectory()) {
await this.putDir(`${dir}/${files[i]}`, `${ossDir}/${files[i]}`);
} else {
await this.putStream(dir, `${ossDir}/`, files[i]);
}
}
}
async upload() {
try {
await this.putDir(
path.resolve(__dirname, "dist"),
`miniPrograms.service`
);
} catch (e) {
console.log("【上传异常请重试】", e);
}
}
}
new OssClient()
.upload()
.then(() => {
console.log("上传成功");
})
.catch((error) => {
console.log("上传异常", error);
});
在package.json 中增加执行压缩的命令"oss:test":"cross-env NODE_ENV=development node oss.client.js",
- 编写打包脚本
// build:test.sh
git pull
echo "编译开始..."
yarn
echo "开始自动化上传"
npm run build:weapp-dev:upload
echo "小程序上传成功"
echo "开始压缩"
npm run build:zip
echo "压缩完成"
npm run oss:test
echo "编译成功..."
执行编译脚本即可自动化编译上传
node 接口编写
注: 本项目使用egg.js
- 安装
dingtalk-robot-sender
- 在钉钉群中增加一个自定义机器人,复制Webhook链接备用
- 在service中新增接口
// /app/service/DingMiniTips.ts
import { Service } from 'egg';
import * as ChatBot from 'dingtalk-robot-sender';
const robot = new ChatBot({
webhook:
'your webhook',// 使用钉钉自定义机器人的Webhook链接
});
export default class Test extends Service {
public async dingTips(ctx) {
// 发送钉钉消息
const textContent = {
msgtype: 'actionCard',
actionCard: {
title: '小程序自动化构建',
text: '小程序自动化构建已完成 \n\n\n 请点击下载获取最新的小程序压缩包 \n\n\n',
btnOrientation: '0',
btns: [
{
title: '下载',
actionURL:
'your zip address', //使用上传到oss的zip地址
},
],
},
};
robot.send(textContent).then(res => {
console.log('res', res);
});
}
}
- 在controller 中引入新增的接口
// /app/controller/miniAppTips.ts
import { Controller } from 'egg';
export default class HomeController extends Controller {
public async MiniAppTips() {
const { ctx } = this;
await ctx.service.dingMiniTips.dingTips(ctx);
ctx.status = 200;
// 这里相对简单的返回,后期需要根据信息返回接收的状态
ctx.body = {
data: null,
msg: '构建信息发送成功',
};
}
}
- 在router.ts 中增加此接口
router.get('/miniAppTips', controller.miniAppTips.MiniAppTips);
到这一步可以测试钉钉与oss的链接是否生效,在postcode中请求该接口,钉钉则会出现对应卡片信息
点击下载可以下载小程序上传在oss的dist文件包
gitLab小程序项目Jenkins自动发布
这一步可以参考我之前写过的文章关于项目与Jenkins自动化发布流程( 服务端项目服务器配置流程(GitLab+Jenkins))
部署的流程都是相同的,唯一需要增加的就是执行的脚本需要增加一条命令触发钉钉的机器人
cd /storage/node/miniPrograms.service
sh build-test.sh
curl http://*****.com/miniAppTips
自此为止全部流程已完成,只需要在Jenkins上点击构建或者本地上传更改代码,就会触发Jenkins的构建。执行完成build-test.sh 的脚本之后,小程序自动化打包上传微信公众平台,dist文件包括压缩包上传oss。全部完成后触发curl命令,触发链接钉钉的接口,钉钉发送卡牌提示,点击消灾就可以将压缩包下载,测试工程师可以根据解压后的dist文件在微信开发者工具中测试代码。