参考文档
微信小程序miniprogram-ci:developers.weixin.qq.com/miniprogram...
Taro插件:
taro-docs.jd.com/docs/2.x/pl...
项目技术栈:Taro版本:2.2.17
注意:本文主要结合作者的项目实战来讲解实现思路,没有具体的实现代码等,若有需要帮助的可提问
背景
随着项目的多版本持续开发,我们在版本迭代过程中,明显感到了力不从心;
举例:
1、xx,开发版二维码过期了,重新给我一个;
2、xx,代码提交了,你帮忙重新部署一下;
项目管理者------心好累;
所以为了能最大限度的不被打断工作,我们只能求变!
分析
那么,为什么我们的槽点这么多呢?俗话说,没有对比就没有伤害,那我们就先对比一下;我们团队的正常web项目开发-送测以及上线流程分别如下:
- 代码合并;
- 通过公司内部的自动化工具打包部署送测环境(每个团队成员都有权限);
- 测试完毕后,通过自动化工具将其部署至线上(管理者负责上线);
在团队刚开始开发微信小程序时的正常开发-送测以及上线流程:
- 代码合并;
- 项目负责人拉取代码到自己本地,然后进行构建;(为了环境冲突,环境部署都由项目负责人处理)
- 构建完成后在微信开发工具执行对应的部署操作;
两者的区别在哪呢?
首先从构建来看,web项目使用的是自动化工具完成,我们的小程序使用的是本地构建的方式; 其次从部署来讲,web项目使用的是自动化工具直接部署,而小程序是通过微信开发工具去选择部署;
最后从可用环境来讲
我们的web项目环境部署是可以很多的,可以部署test-1 ---- test-x都可以,但是小程序呢?满打满算就是开发版、体验版以及发布版;
这给我们的多版本协作开发带来了巨大挑战,因为送测环境最多就这三个而已,并且体验版和发布版都有且仅有一个版本可生效;
因此:我们就需要开发版支棱起来。下面开始正题~~
解决问题
结合上述分析,我们要明确我们需要解决的问题:
1、受众可以在不通过开发的前提下随时自己生成开发版二维码去使用;
2、解决多版本的情况下开发版相互覆盖问题;
3、解决多人协作开发时,繁琐的部署步骤;
开发版统一管理的送测方案
首先版本记录
我们要避免大家经常来找开发要开发版二维码的囧境,我们就需要能在他们要用的时候可以随时给他们用的能力;
我们的做法是通过后端资源的帮助,加上自己内部的管理后台项目去完成相应的版本记录;而要实现在三方平台管理小程序的能力,那我们就需要借助微信小程序提供的ci工具去实现了;
简单画个图:
实现效果也来张图:
通过这种方式可以在二维码失效时,有需要的人可以自己手动点击更新,待数据更新完毕后即可扫码使用,把让主动权让给用户(测试、产品等),减少开发被打扰而导致的低效问题。
其次构建部署
实现途径:通过构建命令 + 插件的形式去完成 构建 + 部署
具体节点:
- 构建;
主要是通过配置脚本命令的方式去触发构建部署;
bash
"build:develop": "export PARAM=$PARAM configEnv=develop environment=test && node project.config/dev.js && npm run build:weapp",
"build:trial": "export PARAM=$PARAM environment=test configEnv=trial && node project.config/dev.js && npm run build:weapp",
"build:release": "export PARAM=$PARAM configEnv=release environment=test && node project.config/dev.js && npm run build:weapp",
"tbuild": "export PARAM=$PARAM environment=pro configEnv=trial && node project.config/prod.js && npm run build:weapp",
参数说明:
PARAM:格式参考:'{"v": "版本信息", "d": "此备注将显示在"小程序助手"开发版列表中"}'
; configEnv:要部署的小程序环境,主要根据这个参数决定使用哪个root去部署
; environment:业务环境
;
- 根据版本构建记录选择不同的root机器人去承载本次任务;
ini
const fs = require('fs');
const path = require('path');
/**
* 历史构建数据格式:
* {
* versionCode: ,
* describe: ,
* qrcodeUrl: ,
* packageUrl: ,
* environment: ,
* createTime: ,
* }
*/
const ciVersionInfo = {
filePath: path.join(__dirname, './generate/ciVersionInfo.json'),
// 所有的可使用root
rootList: () => {
const arr = [];
for (let i = 2; i <= 30; i++) {
arr.push(i);
}
return arr;
},
// 默认root
defaultRoot: 1,
// 将本次内容信息写入到文件中
write: (jsonData) => {
const jsonString = JSON.stringify(jsonData, null, 2);
try {
fs.writeFileSync(ciVersionInfo.filePath, jsonString);
console.log('JSON 数据已成功同步写入到文件。');
return true;
} catch (err) {
console.error('写入文件时发生错误:', err);
return false;
}
},
// 读取历史构建信息
read: () => {
try {
const jsonData = fs.readFileSync(ciVersionInfo.filePath, 'utf-8') || '[]';
const parsedData = JSON.parse(jsonData).sort((a, b) => a.createTime - b.createTime);
// 如果所有可用的root都使用过,则从最早的地方开始删除
if (parsedData.length >= ciVersionInfo.rootList().length) parsedData.shift();
return JSON.parse(JSON.stringify(parsedData));
} catch (err) {
console.error('读取文件时发生错误:', err);
return [];
}
},
// 寻找可使用root
findCanUseVersionRoot: (infoList = [], versionCode) => {
if (!infoList.length) return ciVersionInfo.findLastRoot(infoList);
const targetInfo = infoList.filter((item) => item.versionCode === versionCode);
if (targetInfo.length) return targetInfo[0].root;
else return ciVersionInfo.findLastRoot(infoList);
},
// 寻找最后一个可用root
findLastRoot: (infoList = []) => {
if (!infoList.length) return ciVersionInfo.defaultRoot;
const usedValues = new Set(infoList.map((obj) => obj.root));
let firstUnusedIndex = 0;
let secondUnusedIndex = 0;
const rootList = ciVersionInfo.rootList();
for (let i = 0; i < rootList.length; i++) {
const number = rootList[i];
if (!usedValues.has(number)) {
if (firstUnusedIndex === ciVersionInfo.defaultRoot) {
firstUnusedIndex = i;
} else {
secondUnusedIndex = i;
break;
}
}
}
return rootList[secondUnusedIndex] || ciVersionInfo.defaultRoot;
},
};
module.exports = ciVersionInfo;
- 生成预览二维码;
ci.preview
-
将最终产物进行压缩上传到云端;
-
将压缩包生成的云端链接 + 二维码 + 相关版本信息 上传到管理后台;
postUploadVersion(body)
- 做好本次构建记录;
scss
execSync('git remote set-url origin https://xxxxxx.git');
execSync('git add xxx/xxx/generate/ciVersionInfo.json');
// 提交修改
execSync('git commit -m "添加ci-工具构建记录"');
execSync('git push');
构建记录信息:
json
{
"versionCode": "root:《4》,dev-3.1.1-time",
"root": 4,
"describe": "描述",
"qrcodeUrl": "qrcodeUrl",
"packageUrl": "packageUrl",
"environment": "dev",
"createTime": 1694144252272
},
{
"versionCode": "root:《5》,dev-3.1.1-time",
"root": 5,
"describe": "该版本由xxx基于分支dev-3.1.1-time自动构建工具提交",
"qrcodeUrl": "https://xxxx.com/easiclass-public/388b688eb1d541cd9e89d011de714307",
"packageUrl": "https://xxxx.com/easiclass-public/0bff9d6797744ab683b79e683dcc9b8f",
"environment": "test",
"createTime": 1694163388717
}
注意点
疑问点1:为什么根据不同的历史记录选择不同的root去承担本次任务?
答:如果都适用相同的root,多版本同时存在时会导致版本之间互相覆盖,因为一个用户其实只能有一个开发版;
疑问点2:构建记录的存储位置?
答:由于当时受限于后端资源紧张,加上构建记录是一个优先级不高的功能,因此作者使用的是git的方式去记录每次的构建信息;
构建命令预览
举例自己项目的:
我们此处构建模版分为以下四个(括号为对应的构建脚本),
1、测试环境开发版(npm run build:develop)
2、测试环境体验版(npm run build:trial)
3、测试环境正式版(npm run build:release)
4、正式环境体验版(npm run tbuild)
实现后的文件目录
通过目录结构介绍一下功能:
1、packagingPlugin插件的执行时机:
由于我门需要的是最终产物,因此我们的执行时机肯定是在构建完成后,也就是 ctx.onBuildFinish(() => { // packagingPlugin })
中;
2、generate 生成产物:
存放构建记录文件 + 产物压缩包 + 预览二维码;
3、step 执行步骤:
initParams.js: 初始化参数,包括合并命令行附带的参数信息;
initProject.js: 初始化项目,主要负责ci.Project的执行;
uploadDevelop.js: 上传开发版环境;
uploadTrialRelease.js: 上传体验版 + 发布版;
4、writeCiRootJson.js: 负责root的匹配选择逻辑;
上述事情做完之后,我们就已经可以在本地通过构建命令的方式查看效果了;
命中策略
上传进度
结果
最后结合 构建 + 版本记录 在平台进行构建命令配置
通过上述方案我们即可相对完美的解决我们提出的问题~~
体验版以及发布版
相较于开发版,体验版就很简单了; 由于体验版环境只有一个,因此我们不必关注选择某个机器人,直接在团队做好约定即可:
例如: root 1就是固定的体验版; root 2是固定的发布版;
直接根据不同的环境,调用不同的api即可~
结束语
基于此我们就解决多人协作开发模式下的统一管理环境的流程优化。大家可以参考一下,若是有什么更好的建议欢迎提出。