喂,快给我打一个小程序预览码

需求

开发小程序的朋友们随时都会听到一句话:“喂,快给我打一个xxx环境的预览码”,无论你正在干什么,都得赶紧地回一句:“稍等,这就给你打码……”

然后苦逼的你build了一个xxx环境的包,打开了微信开发者工具,点了一下预览,等了一下,预览码出来了,你复制丢给你的爸爸们。

终于有一天,你正在专心致志做一些不可描述的事情时,“喂,快给我打一个xxx环境的预览码”,这时你内心怒吼了一句:“老子不给你打码!你自己打去!”

于是就有了这个需求,要搞个东西让爸爸们自主打码,嗯,应该就是只有一个按钮,点一下就可以出现预览二维码的东西,意淫了一下应该是这样的:

没错!就这样干!

规划一下

干大事就要从胡思乱想开始,现在来想想要搞成这个功能,需要做点什么准备工作吧。

找微信开发者工具的接口

最重要的事情莫过于看看微信开发者工具有没有给我们提供这样的接口让我们去操作,经过一番查阅文档我们会发现,果然有!

https://developers.weixin.qq.com/miniprogram/dev/devtools/http.html

会发现,文档给我们提供了两种方式的接口,命令行调用以及HTTP调用。有了接口,一切都好办了,无非就是调一下接口,拿到二维码,贴到页面上去而已嘛,很简单。

梳理开发流程

我们就把这个简单的事情,用流程图说明一下:

https://www.processon.com/view/link/5b1de951e4b06350d45ce355

所需技术

工欲善其事,必先利其器,我们要搞这个东西,还是先要把用到的技术整理一下。

  1. 微信开发者工具
  2. 一个小程序项目(这里以一个mpvue项目为例子)
  3. 前端vue + vux,这里前端没什么需要做的东西,这样的搭配纯属是因为本来就正在做移动端的东西,直接拿来用而已。
  4. 后端koa2,当然后端用什么都可以,这里选择koa2,纯属是因为我也不会用别的……
  5. 前后端HTTP请求统一用axios
  6. 涉及到node操作命令行需要用到shelljs

好像没别的东西了,用到了再说吧。

从后端开始

为了省事,直接把前后端的东西放在一起。项目目录:

可以看到server这个目录下放的都是后端的东西。

server/index.js

先看看入口文件index.js,从这里我们可以知道后端要做两件事情,第一要能访问到前端build出来的静态资源,第二要能与前端通过HTTP接口进行交互。见代码:

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

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

// 微信开发者工具接口调用逻辑
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

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接口:

# 打开工具
http://127.0.0.1:端口号/open
# 打开/刷新项目
http://127.0.0.1:端口号/open?projectpath=项目全路径

如果你根本都没有打开微信开发者工具,在以下地方就会找不到端口:

端口号文件位置:

macOS : ~/Library/Application Support/微信web开发者工具/Default/.ide

Windows : ~/AppData/Local/微信web开发者工具/User Data/Default/.ide

所以作为一个全自动化打码工具,怎么可能还要自己去手动打开微信开发者工具呢!

前端

后端的东西基本就那么多,终于到前端了,前端十分简单,就不多说了:

<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,'

最后

其实到这里已经基本实现了整个打码功能,但如果真的要可以用还有很多事情没做。

  1. 部署到测试机器上。虽然可以直接用自己的机子作为部署这个工具的机器,但这实在是有点……如果要部署到测试机器上,有一个问题就是,微信开发者工具依赖图形界面,而服务器一般是命令行,虽然有 https://github.com/cytle/wechatwebdevtools 这样的项目移植微信开发者工具到linux,但这种部署方式似乎还是怪怪的。
  2. 假设完成了上述部署,进行小程序项目打包的环节需要修改一下,变成根据选择的环境,到相应的代码仓库(比如gitlab)拉取该环境的最新代码,然后进行安装依赖才能执行打包命令。
  3. 既然都做到这一步了,也不差把上传小程序也加上去,微信开发者工具接口也有提供,这样一来整个测试打码到上线的步骤都有了。

End~

觉得本文对你有帮助?请分享给更多人。

原文发布于微信公众号 - 程序员宝库(chengxuyuanbaoku)

原文发表时间:2018-07-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏JackieZheng

利用Solr服务建立的站内搜索雏形---solr1

最近看完nutch后总感觉像好好捯饬下solr,上次看到老大给我展现了下站内搜索我便久久不能忘怀。总觉着之前搭建的nutch配上solr还是有点呆板,在nutc...

3208
来自专栏木制robot技术杂谈

Scrapy爬虫框架教程(四)-- 抓取AJAX异步加载网页

Scrapy爬虫框架教程(一)– Scrapy入门 Scrapy爬虫框架教程(二)– 爬取豆瓣电影TOP250 Scrapy爬虫框架教程(三)– 调试(Debu...

6859
来自专栏Java成神之路

SQL server 2008 数据库企业版安装教程图解

SQL Server 2008是一个重大的产品版本,它推出了许多新的特性和关键的改进,使得它成为至今为止的最强大和最全面的SQL Server版本。    在...

2572
来自专栏SDNLAB

如何向OpenDaylight社区贡献代码

本文由两部分构成,第一部分简要介绍了Gerrit的工作原理以工作流程,第二部分结合自己成功提交代码的例子,向大家展示向OpenDaylight提交源码的过程。向...

3809
来自专栏一只程序汪的自我修养

使用requirejs编写模块化代码

2435
来自专栏君赏技术博客

【已解决】怎么快速检索 Localizable.strings 文件里面格式化错误的地方

我们 APP 支持几十种语言切换 这就需要一个工具提供分析 CSV 文件自动生成 Localizable.strings 的文件 导致里面有的双引号什么或者翻译...

1703
来自专栏Danny的专栏

关于VB6.0中控件加载的难题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huyuyang6688/article/...

2004
来自专栏FreeBuf

Java反序列化漏洞:在受限环境中从漏洞发现到获取反向Shell

Java反序列化漏洞可以说是Java安全的一块心病,近年来更是在安全界“出尽风头”。其实说到Java反序列化的问题,早在2015年年初的在AppSecCali大...

1152
来自专栏FreeBuf

JIS-CTF靶机+Kioptrix靶机渗透

最近一直在down各种CTF靶机玩,本次分享的2个靶机因较基础,故合并成一篇文章发表,本文章仅为初学者练手学习使用,大神们勿喷,感谢各位大佬!

1484
来自专栏沈唁志

什么是RESETful API 设计规范?

现在在开发中前后端都是分离开发,后端提供接口给前台,RESTful 架构,就是目前最流行的一种互联网软件架构,也相当于是接口规范

2212

扫码关注云+社区

领取腾讯云代金券