- 作者:老汪软件技巧
- 发表时间:2024-10-12 15:02
- 浏览量:
不知道你们是否遇到这样的情况。
这三个功能都在开发,我们会建立对应的三个分支进行开发。假如此时 A 功能开发完成了就需要打包给 A 同事测试;然后过了一段时间 B 功能也开发完成了,交给 B 同事测试。虽然 A 功能开发完成了,但是不一定会上线,所以不能合并到分支 1.0.1 中;同样 B 功能开发完成了也不一定上,所以也不能合并到 1.0.1 中。如果此时 A 同事测试发现了 bug ,那么我们需要切换对应的分支进行修改,修改完成后我们需要再给 A 同事打一个包。如果你没有使用热更新,那么每一次修改完成 bug 都是重新打包,那么就没有问题,如果你跟我们一样修改完成后是通过热更新上去的,那么就会出现问题。虽然这几个功能不一定上线,但是也有可能会上,所以我们还是在有时间的时候修复其 bug 。
问题描述
因为这个问题我们之前的做法是这样的:
这样的确解决了因为热更新而导致 B 同事手中功能缺失的问题,但是这样会引发新的问题,也就是如果将来 A 功能不上线,那么就需要将 A 功能从新的分支屏蔽掉,要么说服领导一次性上。此时这个过程中我们还是可以避免出现这样的问题,我们只需要每次修复功能的时候先在对应功能分支修复,然后再合并到打包分支上,这样的话即便将来不上 A 功能也没关系,我们只需要打包 B 功能分支即可。
上面的解决方案看起来很好,但实际操作上,往往都是直接在合并分支上修复,因为这样最直接,表面上看只需要多做两步,但这两步却很难,难在你每一次都要记住这个事情,不然你在合并分支修复后,再弄到对应的功能分支就比较复杂,何况很多人在开发工程中并不喜欢小功能单 bug 提交,导致每一次提交的文件很多,这种情况下要想把修复的通过 git cherry-pick -x hash值 方式合并到对应功能分支就是不可行的,只能通过反复查看代码来对对应功能分支修改,而且这种情况修改的一般还需要测试才可以,不然可能引入了新的问题。
解决方案思考
第一时间想到的就是如果每个功能打出来的包原生版本都是不一样的,那么上面的问题就迎刃而解了。比如 A 功能的原生版本号是 v1.0.1-A ;B 功能的则是 v1.0.1-B ,这样在 A 功能分支上热更新,推送的原生版本位 v1.0.1-A ,自然就不会影响 B 功能了。
那么我需要做的就是当创建新分支的时候,就把这个分支的原生版本号改成像 v1.0.1-新功能的样式。
根据上面的方式其实就能解决之前的问题了,但是这一切如果都是手动的进行,那么就很容易出现问题。现在思考的就是怎么让这一切自动化。首先我们描述一下我们想达到的效果:
当创建新分支的时候自动帮我修改原生的版本号,包括 android 和 ios 的版本号,切换分支的时候也需要做到这一点;热更新的时候自动读取最新的原生版本号进行。
由于我们项目是使用 app.json 来管理原生版本号的,也就是修改 app.json 中的版本号在打包的时候就会自动根据这里面的值。
实现解决方案
首先要想做到分支切换的时候自动修改原生版本号,我们需要 git hooks ;在 项目/.git/hooks 下新增 post-checkout 文件,这个文件的内容如下:
#!/bin/sh
# 获取新分支的引用
new_ref=$1
old_ref=$2
checkout_type=$3
# 如果是分支切换(checkout_type=1)
if [ $checkout_type = 1 ]; then
# 运行我们的 Node.js 脚本
node scripts/update-app-json.js
fi
我这里就是当切换的时候执行对应的 nodejs 文件,你也可以直接进行对应的操作。注意这里执行是在你项目下,所以你的文件位置是相对于项目的,而不是 post-checkout 的位置。下面要思考的就是怎么给功能分支取分支名,这里我是根据分支名来的,格式为: 1.0.1-(分支名驼峰法表示) ,比如分支名为 a-b ,那么得到的就是 1.0.1-aB 。经过我的测试,我发现如果你热更新服务使用的是 code-push-server 私有化部署,那么这样的版本号将不可行,只能使用 1.0.1 这样的,那么我们只能在这个基础上进行操作了,首先我们规定前面两位是原生版本号,值的范围为:0-99 ,后面的八位我们用来表示功能分支,由于只能支持数字,然后又必须保证唯一,我采用的策略是先 hash ,然后转换成对应长度的数字。下面是根据分支名得到对应数字的算法:
function generateUniqueNumber(input) {
// 使用 SHA-256 哈希函数
const hash = crypto.createHash('sha256');
hash.update(input);
const hashHex = hash.digest('hex');
// 将哈希值转换为数字
// eslint-disable-next-line no-undef
let number = BigInt(`0x${hashHex}`);
// 取模运算,确保结果在 8 位数以内
number = number % 100000000n;
// 如果结果小于 10000000,加上 10000000 确保是 8 位数
if (number < 10000000n) {
number += 10000000n;
}
return Number(number);
}
算法写好了,但是分支名怎么获取,有一个库可以很方便的实现 simple-git ,下面是获取分支名的函数:
const simpleGit = require('simple-git');
async function getCurrentBranchName() {
const git = simpleGit();
const branchSummary = await git.branch();
return branchSummary.current;
}
有了功能分支的标识,下面只需要将原生版本号与这个结合即可。下面是结合的值更新到 app.json :
async function updateAppJson(formattedBranchName) {
const appJsonPath = path.join(process.cwd(), 'app.json');
try {
const data = await fs.readFile(appJsonPath, 'utf8');
const appJson = JSON.parse(data);
appJson.funcVersion = formattedBranchName;
await fs.writeFile(appJsonPath, JSON.stringify(appJson, null, 2));
console.log(`Updated app.json with tag: ${formattedBranchName}`);
} catch (error) {
console.error('Error updating app.json:', error);
}
}
接下来的工作就是修改原生打包时候执行的脚本,让其读取 funcVersion 。同时还需要判断真正线上的时候不要读取这个,而是之前的原生版本号。
// android 的实现
// ... 其他逻辑
// 只在非release构建时添加tag
if (!project.gradle.startParameter.taskNames.any { it.contains('Release') && !it.contains('ReleaseStaging')}) {
if (funcVersion && !funcVersion.isEmpty()) {
versionName = funcVersion
}
}
其中 project.gradle.startParameter.taskNames.any { it.contains('Release') && !it.contains('ReleaseStaging')} 用来判断是不是 release 包的。 ios 的大致长这样:
# 检查当前的构建配置
if [ "$CONFIGURATION" != "Release" ]; then
# 如果不是Release配置,并且tag存在,则将tag转换为数字并添加到version中
if [ ! -z "$funcVersion" ]; then
version="$funcVersion"
fi
fi