需求
開發小程式的朋友們隨時都會聽到一句話:“喂,快給我打一個xxx環境的預覽碼”,無論你正在幹什麼,都得趕緊地回一句:“稍等,這就給你打碼……”
然後苦逼的你build了一個xxx環境的包,開啟了微信開發者工具,點了一下預覽,等了一下,預覽碼出來了,你複製丟給你的爸爸們。
終於有一天,你正在專心致志做一些不可描述的事情時,“喂,快給我打一個xxx環境的預覽碼”,這時你內心怒吼了一句:“老子不給你打碼!你自己打去!”
於是就有了這個需求,要搞個東西讓爸爸們自主打碼,嗯,應該就是隻有一個按鈕,點一下就可以出現預覽二維碼的東西,意淫了一下應該是這樣的:
沒錯!就這樣幹!
規劃一下
幹大事就要從胡思亂想開始,現在來想想要搞成這個功能,需要做點什麼準備工作吧。
找微信開發者工具的介面
最重要的事情莫過於看看微信開發者工具有沒有給我們提供這樣的介面讓我們去操作,經過一番查閱文件我們會發現,果然有!
https://developers.weixin.qq….
會發現,文件給我們提供了兩種方式的介面,命令列呼叫以及HTTP呼叫。有了介面,一切都好辦了,無非就是調一下介面,拿到二維碼,貼到頁面上去而已嘛,很簡單。
梳理開發流程
我們就把這個簡單的事情,用流程圖說明一下:
https://www.processon.com/vie…
所需技術
工欲善其事,必先利其器,我們要搞這個東西,還是先要把用到的技術整理一下。
- 微信開發者工具
- 一個小程式專案(這裡以一個mpvue專案為例子)
- 前端vue + vux,這裡前端沒什麼需要做的東西,這樣的搭配純屬是因為本來就正在做移動端的東西,直接拿來用而已。
- 後端koa2,當然後端用什麼都可以,這裡選擇koa2,純屬是因為我也不會用別的……
- 前後端HTTP請求統一用axios
- 涉及到node操作命令列需要用到shelljs
好像沒別的東西了,用到了再說吧。
擼起袖子從後端開始
為了省事,直接把前後端的東西放在一起。專案目錄:
可以看到server這個目錄下放的都是後端的東西。
server/index.js
先看看入口檔案index.js,從這裡我們可以知道後端要做兩件事情,第一要能訪問到前端build出來的靜態資源,第二要能與前端通過HTTP介面進行互動。見程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
const path = require('path') const Koa = require('koa') const koaStatic = require('koa-static') const bodyParser = require('koa-bodyparser') const router = require('./router') const app = new Koa() const port = 9871 app.use(bodyParser()) // 處理靜態資源 這裡是前端build好之後的目錄 app.use(koaStatic( path.resolve(__dirname, '../dist') )) // 路由處理介面 app.use(router.routes()).use(router.allowedMethods()) // 監聽埠 app.listen(9871) console.log(`[demo] start-quick is starting at port ${port}`) |
靜態資源方面的話使用koa-static即可,重點是怎樣給前端提供介面,這就要看路由了。
server/router/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const Router = require('koa-router') // 業務邏輯 const wx = require('../controller/wx') const router = new Router({ // 介面字首 比如open介面 請求路徑就是/api/open prefix: '/api' }) router.get('/open', wx.open) .get('/login', wx.login) .get('/preview', wx.preview) .get('/build', wx.build) module.exports = router |
這裡可以清晰看到,後端提供了四個介面,但具體每個介面的業務邏輯則封裝在controller裡的wx.js,如果以後還有別的業務邏輯,就在controller加相應的模組即可。
server/controller/wx.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
// 微信開發者工具介面呼叫邏輯 const {open, login, preview, build} = require('../utli/wxToolApi') // 處理成功失敗返回格式的工具 const {successBody, errorBody} = require('../utli') class WxController { /** * 根據環境對mpvue專案進行打包 * @returns {Promise<void>} */ static async build (ctx) { // 前端傳過來的get引數 const query = ctx.request.query if (!query || !query.env) { ctx.body = errorBody(null, '構建專案失敗') return } const [err, data] = await build(query.env) ctx.body = err ? errorBody(err, '構建專案失敗') : successBody(data, '構建專案成功') } /** * 開啟微信開發者工具 * @returns {Promise<void>} */ static async open (ctx) { const [err, data] = await open() ctx.body = err ? errorBody(err, '開啟微信開發者工具失敗') : successBody(data, '開啟微信開發者工具成功') } /** * 登入微信開發者工具 * @returns {Promise<void>} */ static async login (ctx) { const [err, data] = await login() ctx.body = err ? errorBody(err, '登入二維碼返回失敗') : successBody(data, '登入二維碼返回成功') } /** * 檢視預覽碼 * @returns {Promise<void>} */ static async preview (ctx) { const [err, data] = await preview() ctx.body = err ? errorBody(err, '預覽二維碼返回失敗') : successBody(data, '預覽二維碼返回成功') } } module.exports = WxController |
為了程式碼更加清晰,這裡將具體操作微信開發者工具的介面邏輯抽到util/wxToolApi.js裡去了,僅僅處理怎樣以統一格式返回給前端。
util/wxToolApi.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
const {promiseWrap, successBody, errorBody} = require('../utli') const {INSTALL_PATH, PROJECT_PATH, PORT_PATH, PORT_FILE_NAME, HOST} = require('../const') const {readFile} = require('../utli/nodeApi') const shell = require('shelljs') const axios = require('axios') module.exports = { /** * 根據環境對mpvue專案進行打包 * @param env [doc, pre, prd] * @returns {*} */ build (env) { return promiseWrap(new Promise((resolve, reject) => { // 進入專案目錄 shell.cd(PROJECT_PATH) // 執行打包命令 shell.exec(`npm run build:${env}`, function (code, stdout, stderr) { resolve(stdout) }) })) }, /** * 開啟微信開發者工具 * @returns {*} */ open () { return promiseWrap(new Promise((resolve, reject) => { // 進入專案目錄 shell.cd(INSTALL_PATH) // 執行微信開發者工具介面“命令列啟動工具” shell.exec(`cli -o ${PROJECT_PATH}`, function (code, stdout, stderr) { if (stderr) return reject(stderr) resolve(stdout) }) })) }, /** * 獲取微信開發者工具埠號 * @returns {Promise<*>} */ async getPort () { shell.cd(PORT_PATH) // http 服務在工具啟動後自動開啟,HTTP 服務埠號在使用者目錄下記錄,可通過檢查使用者目錄、檢查使用者目錄下是否有埠檔案及嘗試連線來判斷工具是否安裝/啟動。 const [err, data] = await readFile(PORT_FILE_NAME) return err ? errorBody(err, '讀取埠號檔案失敗') : successBody(data, '讀取埠號檔案成功') }, /** * 微信開發者工具進行登入 * @returns {*} */ login () { return promiseWrap(new Promise(async (resolve, reject) => { // 獲取埠號 const portData = await module.exports.getPort() if (portData.code !== 0) { reject(portData) return } const port = portData.data axios.get(`http://${HOST}:${port}/login?format=base64`) .then(res => { resolve(res.data) }) .catch(e => { reject(e) }) })) }, /** * 微信開發者工具獲取預覽碼 * @returns {*} */ preview () { return promiseWrap(new Promise(async (resolve, reject) => { const portData = await module.exports.getPort() if (portData.code !== 0) { reject(portData) return } const port = portData.data axios.get(`http://${HOST}:${port}/preview?format=base64&projectpath=${encodeURIComponent(PROJECT_PATH)}`) .then(res => { resolve(res.data) }) .catch(e => { reject(e) }) })) } } |
這裡有一點需要注意,為什麼只有open介面需要用命令列呼叫方式?那是因為HTTP呼叫方式必須加埠,比如open介面
1 2 3 4 |
# 開啟工具 http://127.0.0.1:埠號/open # 開啟/重新整理專案 http://127.0.0.1:埠號/open?projectpath=專案全路徑 |
如果你根本都沒有開啟微信開發者工具,在以下地方就會找不到埠:
1 2 3 4 5 |
埠號檔案位置: macOS : ~/Library/Application Support/微信web開發者工具/Default/.ide Windows : ~/AppData/Local/微信web開發者工具/User Data/Default/.ide |
所以作為一個全自動化打碼工具,怎麼可能還要自己去手動開啟微信開發者工具呢!
前端
後端的東西基本就那麼多,終於到前端了,前端十分簡單,就不多說了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
<template> <div> <group title="請選擇環境"> <radio :options="envOption" v-model="env"></radio> </group> <x-button class="btn" type="default" @click.native="handlePreviewProject">點選預覽</x-button> <div v-if="loginImg" class="code"> <divider>請先登入</divider> <img class="code-img" :src="loginImg" alt=""> </div> <div v-if="preImg" class="code" id="preImg"> <divider>預覽二維碼</divider> <img class="code-img" :src="`${base64Prefix}${preImg}`" alt=""> </div> </div> </template> <script> import {openProject, login, previewProject, buildProject} from 'SERVICES/index' import {showLoading, hideLoading} from 'UTILS' import { Divider, XButton, Radio, Group } from 'vux' export default { data () { return { // data表示取得資料的協定名稱,image/png 是資料型別名稱,base64 是資料的編碼方法,逗號後面就是這個image/png檔案base64編碼後的資料。 base64Prefix: 'data:image/png;base64,', // 登入二維碼 loginImg: '', // 預覽二維碼 preImg: '', // 環境 預設為doc env: 'doc', // 所有的環境選項 envOption: ['doc', 'pre', 'prd'] } }, components: { Divider, XButton, Radio, Group }, methods: { handleError (msg) { alert(msg) }, async login () { const {data: {code, data, msg}} = await login() if (code !== 0) { this.handleError(msg) return code } this.loginImg = data return code }, async previewProject () { const {data: {code, data, msg}} = await previewProject() if (code !== 0) { this.handleError(msg) return code } this.preImg = data return code }, async handlePreviewProject () { showLoading() // 重置二維碼 this.resetImg() // 開啟微信開發者工具 const {data: {code}} = await openProject() if (code !== 0) { // 登入微信開發者工具 await this.login() hideLoading() return } // 根據環境打包 await buildProject(this.env) // 預覽 await this.previewProject() hideLoading() }, resetImg () { this.loginImg = '' this.preImg = '' } } } </script> <style lang='less'> .btn { width: 90%!important; margin: 30px auto 30px auto; } .code { display: flex; align-items: center; flex-direction: column; .code-img { width: 300px; height: 300px; } } </style> |
這裡有一個坑就是,login返回的base64是帶了data:image/jpeg;base64,
字首的,所以可以直接放到img的src裡,但是獲取預覽碼的preview返回的卻沒有這個字首!所以需要自己加上去,就是那個base64Prefix:'data:image/png;base64,'
最後
其實到這裡已經基本實現了整個打碼功能,但如果真的要可以用還有很多事情沒做。
- 部署到測試機器上。雖然可以直接用自己的機子作為部署這個工具的機器,但這實在是有點……如果要部署到測試機器上,有一個問題就是,微信開發者工具依賴圖形介面,而伺服器一般是命令列,雖然有 https://github.com/cytle/wech… 這樣的專案移植微信開發者工具到linux,但這種部署方式似乎還是怪怪的。
- 假設完成了上述部署,進行小程式專案打包的環節需要修改一下,變成根據選擇的環境,到相應的程式碼倉庫(比如gitlab)拉取該環境的最新程式碼,然後進行安裝依賴才能執行打包命令。
- 既然都做到這一步了,也不差把上傳小程式也加上去,微信開發者工具介面也有提供,這樣一來整個測試打碼到上線的步驟都有了。
End~