目前公司小程序 是随M/pc/app一样正常版本迭代。开发小程序的流程也更加规范,人员也越多
每次 QA 或者PM 在 钉钉群里 @前端 “给我生成个 开发版的小程序二维码”,让前端很麻烦,要停止开发 切分支编译,还要耗时。
因此有了这个自动打码的需求
先上图
编译完给个链接jenkins上的配置
为什么要自动打码?
1 . 避免打包编译结果不一致(win/mac/node版本/本地代码不是最新等等)
- 避免可互相覆盖的问题,没有统一版本
- QA/PM 不能自主控制 ,老是要等FE给我弄,你总不能让每个QA也安装开发者工具然后配环境吧。。
- 写代码写bug写开森呢,让我给你打码!?
- 编码一次半分钟,一会就要切换编译一次,累死了。。。
- ....等等问题
前期准备
- 翻了微信官方API,使用cli命令
https://developers.weixin.qq.com/miniprogram/dev/devtools/cli.html - 找一台mac 安装上微信开发者工具,配置好项目,把【project.config.json】配置好
- jenkins 配置好git项目,gitlab 上配置好基于事件的webhook,
- 这样 只要合并过 指定分支,就能触发jenkins编译啦
编译流程如下:
- QA 点击 jenkins 立即构建 或者 webhook触发编译
- 执行编译脚本,我们使用了 wepy ,需要先 npm run build
- 使用cli -p 判断是否需要登录
- 未登录 的情况 ,需要使用 cli -l 执行登录,扫码
- 登录情况下,直接生成二维码
- 然后使用钉钉 机器人这个东东,在群里反馈下
- curl 发一个请求给钉钉机器人就完事儿啦
说明
- 如果是本地跑的话,不用这么麻烦,照着https://developers.weixin.qq.com/miniprogram/dev/devtools/cli.html一步步走下去就行了
- 主要是因为jenkins不会展示 微信这个cli回调里面的图片,要不不会这么复杂
- 钉钉机器人里面的图片地址都要外网能解析的,所以我们选择了Link模式
- 下面的源码是没有放到 sy-cli 里面的,接下来会放到 sy-cli里面做统一维护
- 下面的源码没有经过测试,只是mac可以用嗷。。
- wepy部分可以看 <<迷死氧身后的男人,踩坑小程序开发>> 这篇文章
- 其实做过jenkins做编译的都了解,就是个持续集成的思想。
把编译工作收敛,放在一个地方编译,减少上面那些问题,我司所有前端项目都在jenkins上编译啦
遇到问题
1.jenkins的编译机,必须要先配置好 微信开发者工具 和 开发者工具里面的项目,还有一堆配置。简单说就是不通过微信的cli 是个正常开发环境。
- 微信的cli 生成二维码没有回调,只有扫码成功和扫码失败的回调,所以写了先删除目录,再setTimeout去发钉钉消息
- 微信的cli 不会生成目录,要自己mkdir
- jenkins拿 微信的cli 二维码回调,不会显示图片,所以才另存图片发送
- jenkins上的图片,没有对外网解析,钉钉是拿不到的
- 必须要先编译,再预览/登录,才行,要不不能保证每次生成的代码都是最新
- jenkins的打包机器为 物理mac机器,所以不能在内部gitlab 使用webhook触发,需要主动触发
- dist目录里面要有 project.config.json,wepy 1.7.0之前要手工生成下,1.7.0以后不需要,详见 wepy传送门
简化版本
使用 https://github.com/soyoung616/sy-cli
// 在你的项目中
npm install sy-cli -D
// 新建 sy-cli-qr.json
// 修改package.json的scripts
// package.json
...
"scripts": {
"dev": "wepy build --watch",
"build": "cross-env NODE_ENV=production wepy build --no-cache",
"test": "echo \"Error: no test specified\" && exit 1",
"qr": "sy-cli qr \"./sy-cli-qr.json\""
},
...
// sy-cli-qr.json
{
"path":{
// mac上的cli路径都一样
"cli":"/Applications/wechatwebdevtools.app/Contents/Resources/app.nw/bin/cli",
// wepy生成后的路径
"wx":"./dist"
},
"source":{
// wepy的编译
"scripts":"npm run build"
},
// jenkins的工作空间里面
"jenkinsURL":"http://XXXX/job/XXXX/ws/XXXX/",
// 钉钉机器人的token
"dingtalkToken":"XXXX"
}
源码:
- 新建 sy-cli-qr.json 和qr.js,跟package.json平级
- 把sy-cli-qr.json里面的值替换掉
- node qr.js 或者 npm run qr
//qr.js
const shell = require('shelljs')
const path = require('path')
const config = require('./sy-cli-qr.json')
const cli = config.path.cli
const distProject = path.resolve(__dirname, config.path.wx)
const sourceProject = path.resolve(__dirname)
const sourceScripts = config.source.scripts
const dingtalkToken = config.dingtalkToken
const jenkinsURL = config.jenkinsURL
const QR = {
picLogin: 'QR/login.png',
picPreview: 'QR/preview.png',
login() {
let _this = this
let _time = Date.parse(new Date())
shell.rm('-rf','QR')
shell.mkdir('QR')
shell.exec(`${cli} -l --login-qr-output image@${path.resolve(__dirname, _this.picLogin)}`, (code, stdout, stderr) => {
if (stdout.indexOf('login success') > -1) {
_this.createQR()
}
})
setTimeout(() => {
let _txt = `小程序编译,请扫码登录,${_this.toDate(new Date())}`
shell.exec(`curl 'https://oapi.dingtalk.com/robot/send?access_token=${dingtalkToken}' \
-H 'Content-Type: application/json' \
-d '
{"msgtype": "actionCard",
"actionCard": {
"text": "${_txt}",
"title": "${_txt}",
"hideAvatar": "0",
"btnOrientation": "1",
"btns": [
{
"title": "扫码登录",
"actionURL": "${jenkinsURL}${_this.picLogin}?t=${_time}"
}
]
}
}'`)
}, 2000)
},
createQR() {
let _this = this
let _time = Date.parse(new Date())
shell.rm('-rf','QR')
shell.mkdir('QR')
shell.exec(`${cli} -p ${distProject} --preview-qr-output image@${path.resolve(__dirname, _this.picPreview)}`, (code, stdout, stderr) => {
if (stderr.indexOf('需要重新登录') > -1) {
_this.login()
} else {
let _txt = `小程序编译成功,请扫码查看,${_this.toDate(new Date())}`
shell.exec(`curl 'https://oapi.dingtalk.com/robot/send?access_token=${dingtalkToken}' \
-H 'Content-Type: application/json' \
-d '
{"msgtype": "actionCard",
"actionCard": {
"text": "${_txt}",
"title": "${_txt}",
"hideAvatar": "0",
"btnOrientation": "1",
"btns": [
{
"title": "扫码查看",
"actionURL": "${jenkinsURL}${_this.picPreview}?t=${_time}"
}
]
}
}'`)
}
})
},
build() {
return new Promise((resolve, reject) => {
shell.cd(`${sourceProject}`)
shell.exec(`${sourceScripts}`, (code, stdout, stderr) => {
if (code) {
reject(stderr)
} else {
resolve(1)
}
})
})
},
async init() {
let _this = this
let _build = await _this.build().catch((err) => {
console.log(err)
console.log('编译失败')
process.exit(1)
})
if (_build) {
_this.createQR()
}
},
toDate (timeStamp, getMilliseconds) {
let date = new Date(timeStamp)
let time = [
date.getFullYear(),
date.getMonth() + 1,
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds()
]
getMilliseconds && time.push(date.getMilliseconds())
for (var i = 0; i < time.length; i++) {
var val = time[i]
if (val <= 9) {
time[i] = '0' + val
}
}
if (getMilliseconds) {
time = time.slice(0, 3).join('-') + ' ' + time.slice(3, time.length).join(':')
} else {
time = time.slice(0, 3).join('-') + ' ' + time.slice(3, 6).join(':')
}
return time
}
}
QR.init()
// package.json
...
"scripts": {
"dev": "wepy build --watch",
"build": "cross-env NODE_ENV=production wepy build --no-cache",
"test": "echo \"Error: no test specified\" && exit 1",
"qr": "node qr.js"
},
...
网友评论