探索如何优雅的通过miniprogram-ci进行多人协作 + 多版本并行的项目管理

参考文档

微信小程序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项目开发-送测以及上线流程分别如下:

  1. 代码合并;
  2. 通过公司内部的自动化工具打包部署送测环境(每个团队成员都有权限);
  3. 测试完毕后,通过自动化工具将其部署至线上(管理者负责上线);

在团队刚开始开发微信小程序时的正常开发-送测以及上线流程:

  1. 代码合并;
  2. 项目负责人拉取代码到自己本地,然后进行构建;(为了环境冲突,环境部署都由项目负责人处理)
  3. 构建完成后在微信开发工具执行对应的部署操作;

两者的区别在哪呢?

首先从构建来看,web项目使用的是自动化工具完成,我们的小程序使用的是本地构建的方式; 其次从部署来讲,web项目使用的是自动化工具直接部署,而小程序是通过微信开发工具去选择部署;

最后从可用环境来讲

我们的web项目环境部署是可以很多的,可以部署test-1 ---- test-x都可以,但是小程序呢?满打满算就是开发版、体验版以及发布版;

这给我们的多版本协作开发带来了巨大挑战,因为送测环境最多就这三个而已,并且体验版和发布版都有且仅有一个版本可生效;

因此:我们就需要开发版支棱起来。下面开始正题~~

解决问题

结合上述分析,我们要明确我们需要解决的问题:

1、受众可以在不通过开发的前提下随时自己生成开发版二维码去使用;

2、解决多版本的情况下开发版相互覆盖问题;

3、解决多人协作开发时,繁琐的部署步骤;

开发版统一管理的送测方案

首先版本记录

我们要避免大家经常来找开发要开发版二维码的囧境,我们就需要能在他们要用的时候可以随时给他们用的能力;

我们的做法是通过后端资源的帮助,加上自己内部的管理后台项目去完成相应的版本记录;而要实现在三方平台管理小程序的能力,那我们就需要借助微信小程序提供的ci工具去实现了;

简单画个图:

实现效果也来张图:

通过这种方式可以在二维码失效时,有需要的人可以自己手动点击更新,待数据更新完毕后即可扫码使用,把让主动权让给用户(测试、产品等),减少开发被打扰而导致的低效问题。

其次构建部署

实现途径:通过构建命令 + 插件的形式去完成 构建 + 部署

具体节点:

  1. 构建;

主要是通过配置脚本命令的方式去触发构建部署;

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:业务环境

  1. 根据版本构建记录选择不同的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;
  1. 生成预览二维码;

ci.preview

  1. 将最终产物进行压缩上传到云端;

  2. 将压缩包生成的云端链接 + 二维码 + 相关版本信息 上传到管理后台;

postUploadVersion(body)

  1. 做好本次构建记录;
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即可~

结束语

基于此我们就解决多人协作开发模式下的统一管理环境的流程优化。大家可以参考一下,若是有什么更好的建议欢迎提出。

相关推荐
兔C2 小时前
微信小程序的轮播图学习报告
学习·微信小程序·小程序
用户48062260414153 小时前
使用uniapp开发微信小程序-框架搭建
微信小程序·uni-app
嘟嘟实验室3 小时前
微信小程序xr-frame透明视频实现
微信小程序·ffmpeg·音视频·xr
Stanford_11066 小时前
高级的SQL查询技巧有哪些?
sql·微信小程序·twitter·微信开放平台
京东零售技术8 小时前
Taro小程序开发性能优化实践
性能优化·taro
美美的海顿8 小时前
spring boot 火车售票微信小程序LW
spring boot·后端·微信小程序·小程序·毕业设计
Kika写代码10 小时前
【微信小程序】1|底部图标 | 我的咖啡店-综合实训
微信小程序·小程序
JSON_L10 小时前
小程序 - 模拟时钟
微信·微信小程序·小程序
Kika写代码10 小时前
【微信小程序】2|轮播图 | 我的咖啡店-综合实训
前端·微信小程序·小程序
.生产的驴1 天前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven