需求開(kāi)發(fā)小程序的朋友們隨時(shí)都會(huì)聽(tīng)到一句話(huà):“喂,快給我打一個(gè)xxx環(huán)境的預(yù)覽碼”,無(wú)論你正在干什么,都得趕緊地回一句:“稍等,這就給你打碼……” 然后苦逼的你build了一個(gè)xxx環(huán)境的包,打開(kāi)了微信開(kāi)發(fā)者工具,點(diǎn)了一下預(yù)覽,等了一下,預(yù)覽碼出來(lái)了,你復(fù)制丟給你的爸爸們。 終于有一天,你正在專(zhuān)心致志做一些不可描述的事情時(shí),“喂,快給我打一個(gè)xxx環(huán)境的預(yù)覽碼”,這時(shí)你內(nèi)心怒吼了一句:“老子不給你打碼!你自己打去!” 于是就有了這個(gè)需求,要搞個(gè)東西讓爸爸們自主打碼,嗯,應(yīng)該就是只有一個(gè)按鈕,點(diǎn)一下就可以出現(xiàn)預(yù)覽二維碼的東西,意淫了一下應(yīng)該是這樣的:
沒(méi)錯(cuò)!就這樣干! 規(guī)劃一下干大事就要從胡思亂想開(kāi)始,現(xiàn)在來(lái)想想要搞成這個(gè)功能,需要做點(diǎn)什么準(zhǔn)備工作吧。
找微信開(kāi)發(fā)者工具的接口
梳理開(kāi)發(fā)流程 所需技術(shù) 工欲善其事,必先利其器,我們要搞這個(gè)東西,還是先要把用到的技術(shù)整理一下。
好像沒(méi)別的東西了,用到了再說(shuō)吧。 擼起袖子從后端開(kāi)始為了省事,直接把前后端的東西放在一起。項(xiàng)目目錄:
可以看到server這個(gè)目錄下放的都是后端的東西。
server/index.js 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()) // 處理靜態(tài)資源 這里是前端build好之后的目錄 app.use(koaStatic( path.resolve(__dirname, '../dist') )) // 路由處理接口 app.use(router.routes()).use(router.allowedMethods()) // 監(jiān)聽(tīng)端口 app.listen(9871) console.log(`[demo] start-quick is starting at port ${port}`) 靜態(tài)資源方面的話(huà)使用koa-static即可,重點(diǎn)是怎樣給前端提供接口,這就要看路由了。 server/router/index.js const Router = require('koa-router') // 業(yè)務(wù)邏輯 const wx = require('../controller/wx') const router = new Router({ // 接口前綴 比如open接口 請(qǐng)求路徑就是/api/open prefix: '/api' }) router.get('/open', wx.open) .get('/login', wx.login) .get('/preview', wx.preview) .get('/build', wx.build) module.exports = router 這里可以清晰看到,后端提供了四個(gè)接口,但具體每個(gè)接口的業(yè)務(wù)邏輯則封裝在controller里的wx.js,如果以后還有別的業(yè)務(wù)邏輯,就在controller加相應(yīng)的模塊即可。 server/controller/wx.js // 微信開(kāi)發(fā)者工具接口調(diào)用邏輯 const {open, login, preview, build} = require('../utli/wxToolApi') // 處理成功失敗返回格式的工具 const {successBody, errorBody} = require('../utli') class WxController { /** * 根據(jù)環(huán)境對(duì)mpvue項(xiàng)目進(jìn)行打包 * @returns {Promise<void>} */ static async build (ctx) { // 前端傳過(guò)來(lái)的get參數(shù) const query = ctx.request.query if (!query || !query.env) { ctx.body = errorBody(null, '構(gòu)建項(xiàng)目失敗') return } const [err, data] = await build(query.env) ctx.body = err ? errorBody(err, '構(gòu)建項(xiàng)目失敗') : successBody(data, '構(gòu)建項(xiàng)目成功') } /** * 打開(kāi)微信開(kāi)發(fā)者工具 * @returns {Promise<void>} */ static async open (ctx) { const [err, data] = await open() ctx.body = err ? errorBody(err, '打開(kāi)微信開(kāi)發(fā)者工具失敗') : successBody(data, '打開(kāi)微信開(kāi)發(fā)者工具成功') } /** * 登錄微信開(kāi)發(fā)者工具 * @returns {Promise<void>} */ static async login (ctx) { const [err, data] = await login() ctx.body = err ? errorBody(err, '登錄二維碼返回失敗') : successBody(data, '登錄二維碼返回成功') } /** * 查看預(yù)覽碼 * @returns {Promise<void>} */ static async preview (ctx) { const [err, data] = await preview() ctx.body = err ? errorBody(err, '預(yù)覽二維碼返回失敗') : successBody(data, '預(yù)覽二維碼返回成功') } } module.exports = WxController
為了代碼更加清晰,這里將具體操作微信開(kāi)發(fā)者工具的接口邏輯抽到util/wxToolApi.js里去了,僅僅處理怎樣以統(tǒng)一格式返回給前端。 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 = { /** * 根據(jù)環(huán)境對(duì)mpvue項(xiàng)目進(jìn)行打包 * @param env [doc, pre, prd] * @returns {*} */ build (env) { return promiseWrap(new Promise((resolve, reject) => { // 進(jìn)入項(xiàng)目目錄 shell.cd(PROJECT_PATH) // 執(zhí)行打包命令 shell.exec(`npm run build:${env}`, function (code, stdout, stderr) { resolve(stdout) }) })) }, /** * 打開(kāi)微信開(kāi)發(fā)者工具 * @returns {*} */ open () { return promiseWrap(new Promise((resolve, reject) => { // 進(jìn)入項(xiàng)目目錄 shell.cd(INSTALL_PATH) // 執(zhí)行微信開(kāi)發(fā)者工具接口“命令行啟動(dòng)工具” shell.exec(`cli -o ${PROJECT_PATH}`, function (code, stdout, stderr) { if (stderr) return reject(stderr) resolve(stdout) }) })) }, /** * 獲取微信開(kāi)發(fā)者工具端口號(hào) * @returns {Promise<*>} */ async getPort () { shell.cd(PORT_PATH) // http 服務(wù)在工具啟動(dòng)后自動(dòng)開(kāi)啟,HTTP 服務(wù)端口號(hào)在用戶(hù)目錄下記錄,可通過(guò)檢查用戶(hù)目錄、檢查用戶(hù)目錄下是否有端口文件及嘗試連接來(lái)判斷工具是否安裝/啟動(dòng)。 const [err, data] = await readFile(PORT_FILE_NAME) return err ? errorBody(err, '讀取端口號(hào)文件失敗') : successBody(data, '讀取端口號(hào)文件成功') }, /** * 微信開(kāi)發(fā)者工具進(jìn)行登錄 * @returns {*} */ login () { return promiseWrap(new Promise(async (resolve, reject) => { // 獲取端口號(hào) 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) }) })) }, /** * 微信開(kāi)發(fā)者工具獲取預(yù)覽碼 * @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) }) })) } } 這里有一點(diǎn)需要注意,為什么只有open接口需要用命令行調(diào)用方式?那是因?yàn)镠TTP調(diào)用方式必須加端口,比如open接口 # 打開(kāi)工具 http://127.0.0.1:端口號(hào)/open # 打開(kāi)/刷新項(xiàng)目 http://127.0.0.1:端口號(hào)/open?projectpath=項(xiàng)目全路徑 如果你根本都沒(méi)有打開(kāi)微信開(kāi)發(fā)者工具,在以下地方就會(huì)找不到端口: 端口號(hào)文件位置: macOS : ~/Library/Application Support/微信web開(kāi)發(fā)者工具/Default/.ide Windows : ~/AppData/Local/微信web開(kāi)發(fā)者工具/User Data/Default/.ide 所以作為一個(gè)全自動(dòng)化打碼工具,怎么可能還要自己去手動(dòng)打開(kāi)微信開(kāi)發(fā)者工具呢! 前端后端的東西基本就那么多,終于到前端了,前端十分簡(jiǎn)單,就不多說(shuō)了: <template> <div> <group title="請(qǐng)選擇環(huán)境"> <radio :options="envOption" v-model="env"></radio> </group> <x-button class="btn" type="default" @click.native="handlePreviewProject">點(diǎn)擊預(yù)覽</x-button> <div v-if="loginImg" class="code"> <divider>請(qǐng)先登錄</divider> <img class="code-img" :src="loginImg" alt=""> </div> <div v-if="preImg" class="code" id="preImg"> <divider>預(yù)覽二維碼</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表示取得數(shù)據(jù)的協(xié)定名稱(chēng),image/png 是數(shù)據(jù)類(lèi)型名稱(chēng),base64 是數(shù)據(jù)的編碼方法,逗號(hào)后面就是這個(gè)image/png文件base64編碼后的數(shù)據(jù)。 base64Prefix: 'data:image/png;base64,', // 登錄二維碼 loginImg: '', // 預(yù)覽二維碼 preImg: '', // 環(huán)境 默認(rèn)為doc env: 'doc', // 所有的環(huán)境選項(xiàng) 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() // 打開(kāi)微信開(kāi)發(fā)者工具 const {data: {code}} = await openProject() if (code !== 0) { // 登錄微信開(kāi)發(fā)者工具 await this.login() hideLoading() return } // 根據(jù)環(huán)境打包 await buildProject(this.env) // 預(yù)覽 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> 這里有一個(gè)坑就是,login返回的base64是帶了data:image/jpeg;base64,前綴的,所以可以直接放到img的src里,但是獲取預(yù)覽碼的preview返回的卻沒(méi)有這個(gè)前綴!所以需要自己加上去,就是那個(gè)base64Prefix:'data:image/png;base64,' 最后其實(shí)到這里已經(jīng)基本實(shí)現(xiàn)了整個(gè)打碼功能,但如果真的要可以用還有很多事情沒(méi)做。
|
工作日 8:30-12:00 14:30-18:00
周六及部分節(jié)假日提供值班服務(wù)