当前位置: 首页 > 后端技术 > Node.js

微信、支付宝小程序中的CI-CD,不要让别人打扰你的开发思路!

时间:2023-04-04 01:04:21 Node.js

本文内容仅针对支付宝和微信小程序后台。先说说为什么要做这个东西。基于原小程序审核中漫长的审核过程,关闭PR,切换到主分支,拉取代码,点击上传,原生小程序的后台审核是根据config文件判断投掷环境,并且项目环境完全是基于测试的手动验证,在review的时候bugfix验证或者后端同学需要测试环境的时候,一般都是直接找我们要二维码。这个时候我们可能正在开发其他项目,本地代码不纯,所以一般都是commit本地代码,切的干干净净。分支,然后切换到测试环境,生成一个二维码给他们,再切换回开发分支。这太麻烦了。所以我们开发了一个小程序生成二维码的平台,让小程序不用我们也能完成测试。同学们可能对git不熟悉,也不熟悉如何切换环境。基于以上原因,让我们基于支付宝和微信提供。SDK开发了属于我们小程序的CI/CD,尽量解放一些打扰我们思考的繁琐工作。功能介绍自动上传小程序后台,合并PR后触发验证环境。上传完成后版本加1(根据我们自己的项目会补充需要的checksum函数),通知信息包括版本、试用版二维码、包信息。超级管理(可添加用户)获取所有分支,剪切分支获取当前环境,可切换环境定义入口页面定义入口页面参数构建预览二维码管理员可手动上传本文先说说自动上传是怎么实现的giteewebHook配置GiteeWebHook功能是帮助用户推送代码,自动回调一个你设置的http地址。这是一个通用的解决方案。用户可以根据不同的需求(如发送邮件、自动部署等)编写自己的脚本,添加webhook。检查PR以添加并完成上述步骤。发起PR并更新PR后,码云会向我们的接口发起post请求。接口注册前,码云填写的路由用于接收WebHookrouter.post('/upload/:appKey',asyncctx=>{//...log}启动节点服务。此时,我们进入刚刚添加的页面,点击测试按钮,接口可以接收到码云的请求了~第一步已经完成了,我们来梳理一下这个接口需要实现的内容:验证PR的目标分支和PR的状态clone或者拉取目标分支代码是否需要上传到本地验证,版本是否需要+1,是否是正式环境(功能加上自己的项目)叮叮通知开始上传,叮叮通知上传成功完成,梳理后是不是很清楚了,那我们一步步实现,验证目标分支和PR状态。因为我们所有的PR目标分支都是master,而需要审核的版本也是master,所以在收到请求后,需要判断目标分支是否为master,以及PR的状态是否为合并完成。WebHook推送数据的数据类型文档PS:PR分为new、update、merge、close。每次状态更新都会发起一次请求,所以需要过滤没有合并的请求router.post('/upload/:appKey',asyncctx=>{const{body}=ctx.requestif(body.state==='merged'&&body.target_branch==="master"){//...}}验证后需要拉取代码拉取代码,如果项目目录存在,则去拉取,如果不存在,去克隆(偷懒,确保你的目录存在,否则你会报错)execSync(`gitclone${gitaddress}`,{cwd:path.join(xcxPath,'..'))})}自定义验证next您需要做一些与您自己的项目相关的上传前验证。比如有时候我们可能有多个PR,我们不想每次合并后都上传。这个时候我们通过PR中的标签来判断是否上传。当我们选择aNotUpload标签时,这个PR将不会上传letarr=body.pull_request.labels.filter(item=>item.name==='aNotUpload')if(arr.length){//...exists,所以不上传ctx.body={code:200,msg:'选择不上传',success:true}return}然后我们也维护一套自己的版本来区分每个版本,但是每次都需要手动去+1是很不合理的。为了完成这个自动化,合并PR(mergingallcommits)后偷偷在master上增加版本并push(暂时没想到更合理的方法)constparser=require("@babel/parser");constgenerator=require("@babel/generator").default;consttraverse=require("@babel/traverse").default;//getastfunctionreadConfig(root){constconfigFile=path.join(root,'/config/config.js')constcontext=fs.readFileSync(configFile,"utf-8")constast=parser.parse(context)return{ast,file:configFile}}//版本是否+1functionprodHooks({root,appKey}){let{ast,file}=readConfig(root)letisVersionUp=false//版本号是否增加constdiffStr=child_process.execSync(`gitdiffHEAD^config/config.js`,{cwd:'projectpath',encoding:'utf-8'})//获取不同的版本,大概是['-version:"10.0.0"','+version:"10.0.1"']letdiffArr=diffStr.match(/(\+|-)(.*)version:[^\r\n]*[\r\n]/g)if(diffStr&&diffArr.length){//提取数字diffArr=diffArr.map(i=>i.replace(/\D*/g,''))//+的版本是否大于-的版本,如果是则版本升级完成isVersionUp=diffArr[1]-diffArr[0]>0}//没有添加版本,自动+1if(!isVersionUp){traverse(ast,{enter(p){if(p.isObjectProperty()){letname=p.node.key.nameif(name==='version'||name==='versionCode'){letval=p.node.value.valueif(typeofval==='string'){val=versionUpdate(val)}else{val+=1}p.node.value.value=val}}}})const{code}=generator(ast)fs.writeFileSync(file,code,"utf-8")child_process.execSync(`gitadd.&&gitcommit-m'versionup'&&gitpull&&gitpush`,{cwd:'项目目录'})}returntrue}/***升级版本号*@param{string}val'2.09.09'|'99.99.99'*@return{string}'2.09.10'|'100.00.00'**/functionversionUpdate(val){letnum=[...(1+Number(val.replace(/\D/g,''))+'')]for(leti=val.length-1;i>=0;i--){if(val[i]==='.'){num.splice(i-val.length+1,0,'.')}}returnnum.join('')}到这里我们就完成了所有上传前的验证和自定义处理,接下来来到最重要的通知和上传阶段~钉钉群通知正常主要是用钉钉上班,所以访问钉钉群机器人通知上传进度PS:钉钉群机器人SDK首先创建一个钉钉群并设置自定义机器人获取accessToken和secret。点击后打开机器人获取accessToken和secret,然后使用sdk向钉钉群发送推送constRobot=require('dingtalk-robot-sdk');constrobot=newRobot({accessToken:'xxxx',secret:'xxxx'});consttext=newRobot.Text('起床~')robot.send(text)上传支付宝和微信小程序使用sdk上传文件支付宝alipaydev.setConfig({toolId,privateKey,})constuploadResult=awaitalipaydev.miniUpload({appId:'支付宝appid',clientType:'alipay',project:'项目地址',experience:true//设为试用版})WeChatconstproject=newci.Project({appid,type:'miniProgram',projectPath,privateKeyPath})constyear=newDate().getFullYear()-2000letmonth=newDate().getMonth()+1constday=newDate().getDate()//根据版本号constversion='2.5.'+year+(month<10?'0'+month:month)+(day<10?'0'+day:day);//上传constpreviewResult=awaitci.upload({project,version,desc:body.title,setting:{es6:true,minify:true,autoPrefixWXSS:true,minifyWXML:true,minifyWXSS:true,minifyJS:true}})