小程序·云开发 是微信团队联合腾讯云团队推出的一套小程序开发解决方案。小程序·云开发为开发者提供完整的云端流程,弱化后端和运维概念,开发者无需购买和管理底层计算资源,包括服务器、数据库、静态存储,只需使用平台提供的简易 API 进行核心业务等开发,实现快速上线和迭代,把握业务发展的黄金时期。

简单来说,小程序开发中用到的服务器、数据库和静态资源管理,都可以托管到「小程序·云开发」上,小程序开发者只需要关注业务功能实现,而不需要关心服务器运维等带来的问题。小程序开发主要用到的是前端技术,后端开发和服务运维对于前端开发者来说有一定的门槛,而小程序·云开发的出现就是解决这个问题的。

一、小程序·云开发特点

  • 提供完整后端服务解决方案,包括数据库、静态资源管理和云函数(功能服务)
  • 背靠腾讯云大平台,腾讯云丰富的 API 和功能都可以简单调用
  • 对于普通开发者,完全免费
  • 对于高级服务型小程序,提供更强服务支持、计费弹性、不使用不计费的特点
  • 无服务器搭建,无域名配置,直接调用 API 使用

对于我们普通开发者来说,小程序·云开发是个不错的练手平台。下面详细介绍下小程序·云开发提供的功能。

小程序·云开发提供数据库、云函数和静态存储三大功能,还有小程序的用户管理功能,在用户管理界面可以轻松掌握小程序的授权用户情况。目前云开发的这些功能已经深度整合到「微信开发者工具」中,可以在顶部点击「云开发」进入。

blog_wxmini_2_003

二、申请小程序·云开发

1.小程序·云开发的开通

如果还没有开通小程序·云开发账号,首次点击「云开发」会出现下面的界面,该界面主要是云开发的介绍和申请入口。

blog_wxmini_2_001

点击蓝色的「创建资源环境」按钮进入新建环境界面。

blog_wxmini_2_002

第二步出现选择套餐信息,现在公测阶段只有一个免费套餐选项,将来应该会有更多套餐选择。填写上「环境名称」点击确认之后,会使用小程序开发账号在腾讯云开通一个 fake account。到此小程序·云开发就开通了!

TIPS:目前一个小程序账号可以免费开通两个云开发账号,免费版本的限制应该也要注意:

  • 数据库存储空间:2GB
  • 文件存储空间:5GB
  • 文件存储外网下行流量:5GB/月
  • 云函数数量:20
  • 云函数资源使用量:10万GBs/月
  • API调用次数:3万次/天

这些对于我们做普通的小程序开发练习已经足够了!

2.小程序·云开发在小程序中的调用

小程序·云开发可以在小程序中直接通过调用 wx.cloud.* 的方式进行调用,在调用云开发 API 之前,需要先调用 wx.cloud.init 对云开发进行初始化:

wx.cloud.init({
  env: 'tianqi-xxx'
})

初始化时,需要传入 env 参数,该参数为创建小程序·云开发时的环境 ID,可以在「云开发」页面右上角「当前环境」下拉菜单中找到:

blog_wxmini_2_004

下面详细介绍下小程序·云开发的三大功能。

三、数据库

小程序·云开发的数据库是一种 NoSQL 云端数据库,数据以 JSON 格式存储,在底层支持弹性可扩展、自动容灾、监控管理,所以开发者不需要关注数据库的运维。从提供的文档和接口来看,云开发的数据库应该是由 MongoDB 实现的。

每个数据库是由多个集合(collection,类比关系型数据库中表的概念)组成,集合有多个 JSON 文档(行)组成,NoSQL 的特点是没有固定的字段,所以整个集合可以看成一个大的 JSON 数组,一个集合在数据库中的存储格式如下:

[
  {
    "_id": 1,
    "_openid": "ax123CVadb",
    "name": "Alice",
    "city": "Guangzhou"
  },
  {
    "_id": 2,
    "_openid": "xj372nJdfa",
    "name": "Bob",
    "city": "Shenzhen"
  }
]

需要说明的是,文档中的 _id 是唯一的,开发者可以插入数据的时候自定义,另外 _openid 是增加文档默认创建的,代表当前小程序用户的唯一标示,后面实战部分笔者会重点介绍小程序的用户授权相关的内容。

1.快速入门

详细介绍可以见小程序·云开发的使用文档,这里笔者整理出常用的 API 使用方法:

// 初始化
wx.cloud.init({
  env: 'tianqi-xxx'
})
// 获取数据库实例
const db = wx.cloud.database()
// 增
db.collection('集合名称').add({
  data: {} // 插入的数据
}).then(res => {
  // 可以通过 res._id 获取创建的记录的 id
  console.log(res._id)
})
// 删
db.collection('集合名称').doc('文档 ID').remove().then(res => {
  console.log('removed')
})
// 改
db.collection('集合名称').doc('文档 ID').update({
  data: {
    title: '我的第 1 篇文章', // 只更新 title 字段,其他不更新
  }
}).then(res => {
  // 可以通过 res._id 获取创建的记录的 id
  console.log(res._id)
})
// 查
db.collection('集合名称').doc('文档 ID').get().then(res => {
  // 打印结果,res.data 即为记录的数据
  console.log(res)
})
const _ = db.command // 取指令
db.collection('集合名称').where({
  // 查找条件
  category: 'computer',
  properties: {
    memory: _.gt(8), // 表示大于 8
  }
})

小程序·云开发的数据库查询命令是可以使用查询筛选指令的,使用查询筛选指令可以缩小查询范围,找到查询条件的文档。

以下指令挂载在 db.command

类型 接口 说明
比较运算 eq 字段 ==
  neq 字段 !=
  gt 字段 >
  gte 字段 >=
  lt 字段 <
  lte 字段 <=
  in 字段值在数组里
  nin 字段值不在数组里
逻辑运算 and 表示需同时满足指定的所有条件
  or 表示需同时满足指定条件中的至少一个

举例说明:在 diary 集合中找出 openid 某个值并且创建时间(tsModified)在 startend 之间的文档。

db
  .collection('diary')
  .where({
    openid,
    tsModified: _.gte(start).and(_.lt(end))
  })
  .get()

2.数据库的索引

增加合适的索引可以提升文档的查找效率,比如根据时间、用户 ID 查找,可以将时间和用户 ID 字段设置为索引项,笔者在使用的时候发现:在小程序·云开发管理后台并不能对某个字段增加唯一索引。

3.自带权限管理

在研发中,经常会针对不同的用户设置不同的数据库权限,例如:某条记录是用户 A 创建的,则只有用户 A 可以删除或者更新,其他用户只有查看的权限,这样的需求很常见,实际开发起来却非常费劲,往往要写不少权限判断的代码,小程序·云开发的数据库支持「权限管理」功能,可以针对这类需求对不同的集合进行统一处理,大大降低开发的门槛!

集合的操作权限包括以下四种:

  • 所有用户可读,仅创建者及管理员可写
  • 仅创建者及管理员可读写
  • 所有用户可读,仅管理员可写
  • 仅管理员可读写

可以在「云开发 -> 数据库 -> 选择某个集合 -> 权限设置」页面进行设置。

blog_wxmini_2_005

四、文件存储

小程序·云开发的文件存储功能是专为存储和提供用户生成的内容(如图片或视频)的开发者打造的。开发者可使用腾讯云的 SDK 来存储图片、音频、视频或其他由用户生成的内容。在小程序内,则可以通过云开发的 API 直接上传、下载和管理存储。

公共使用的静态资源,可以通过「云开发 -> 存储」界面直接上传和管理,上传之后,就可以在界面内找到资源的 CDN 地址。

而对于小程序内需要上传和管理的则通过下面几个 API 来实现:

// 上传,上传后会返回资源的 ID
wx.cloud.uploadFile
// 下载
wx.cloud.downloadFile
// 根据资源 ID 获取资源访问地址
wx.cloud.getTempFileURL
// 根据资源 ID 列表删除某资源
wx.cloud.removeFile

跟数据库权限管理一样,如果需要用户自己上传的内容自己可以管理,那么需要设置存储的操作权限,在「云开发 -> 存储」下可以设置全局的存储操作权限。

blog_wxmini_2_006

五、云函数

云函数是腾讯云提供的一套函数计算解决方案,我们可以将每个功能 API 做成单个可执行的函数,然后放到腾讯云上去托管,每个云函数是相互独立可执行的。代码编写完成后放到云端,不执行不调用不收费,执行调用按照调用次数和 CPU 等计算资源的占用情况收费。有了云函数,开发者无须搭建和购买服务器,只需要将写好的云函数代码上传部署到腾讯云,即可以在小程序内通过 wx.cloud.callFunction 的方法进行调用。

1.创建云函数

创建云函数有两种方式,一种是直接在小程序开发者工具中进行操作:

进入「云开发 -> 云函数 -> 添加」创建:

blog_wxmini_2_008

另外一种是直接在小程序开发者工具中上传,在上传之前需要配置小程序的 project.config.json,指明哪个路径为云函数目录:

{
  "cloudfunctionRoot": "./cloud-functions/"
}

配置完成后,在小程序开发者工具的编辑器中展开 cloud-functions 文件夹,选择对应的云函数文件夹,右键选择「上传并部署」即可:

blog_wxmini_2_007

2.写个简单云函数

小程序·云开发的云函数是执行在 Node.js 8.9.0 版本下的,云函数必须在 index.js 有 main 方法为入口,例如下面的代码:

// 命名为 test 的函数内容 index.js
exports.main = async (event, context) => {
  let {a, b} = event
  return new Promise((resolve, reject) => {
    resolve({result: parseInt(a) + parseInt(b)})
  })
}

除了上面 Promise 的写法,还可以用回调的方式:

// 命名为 test 的函数内容 index.js
// 回调方式 callback
exports.main = async (event, context, callback) => {
  let {a, b} = event
  callback(null, {result: parseInt(a) + parseInt(b)})
}

TIPS:

  1. 云函数如果不存在 main 的方法,上传部署的时候会报错!
  2. 云函数回调方式写法遵循「错误优先」原则(Error-First Callback)。

云函数接受两个 JSON 格式的参数 eventcontext,两者分别代表:

  • event:平台将 event 入参传递给执行方法,通过此 event 入参对象,代码将与触发函数的事件(event)交互,event 可以获取 wx.cloud.callFunction 调用的参数 data
  • context:平台将 context 入参传递给执行方法,通过此 context 入参对象,代码将能了解到运行环境及当前请求的相关内容

TIPS: 开发者可以在云函数内获取到每次调用的上下文(appId、openId 等),无需维护复杂的鉴权机制,即可获取天然可信任的用户登录态(openId),这俩值可以从event.userInfo中读取。

上面 test 的函数上传到腾讯云之后,我们在小程序的 JS 代码中可以使用下面的方法进行调用:

wx.cloud.callFunction({
  name: 'test',
  data: {
    a: 1,
    b: 2
  }
}).then(r=>{
  // 因为 main 的方法实际是个 promisify 的返回,所以可以直接使用 then/catch
  console.log(r)
}).catch(e=>{
  console.log(e)
})

3.云函数的依赖管理

在云函数中,可以像正常的 Node.js 一样,使用 package.jsonnode_modules 来对依赖进行管理,在开发完代码之后,需要将 node_modules 文件夹一起上传到云端去。下面笔者将带着大家做一个什么值得买的简单抓取的云函数,讲解函数编写、npm 模块使用、云函数本地测试整个流程。

先说下,笔者要实现的功能:

  1. 根据传入的分类参数,获取什么值得买对应分类的最新文章内容
  2. 提取出文章列表的 title、image、mall,即文章名称、文章的配图和优惠所属的网站

首先在 cloud-functions 文件夹(该文件夹是我们创建的云函数文件夹),创建一个 smzdm 的云函数,目录结构如下:

cloud-functions
├── smzdm
│   ├── index.js

然后进入 smzdm 目录,执行 npm init,按照提示填写内容以后,会在该目录下生成 package.json 文件,接下来需要安装抓取「什么值得买」手机站点的两个 npm 模块:

  • request:用于做数据请求,抓取站点的 HTML 内容
  • cheerio:用于将 HTML 结构数据转为类似 jQuery 的对象,可以通过 CSS 选择器对 HTML 的内容进行提取

安装命令如下:

# 进入 smzdm 目录,执行
npm install --save request cheerio

安装之后的目录结构如下:

cloud-functions
├── smzdm
│   ├── node_modules
│   ├── index.js
│   └── package.json

然后我们开始编写 index.js 内容,第一步引入模块,编写 main 方法:

// 引入 requst 和 cheerio
const request = require('request')
const cheerio = require('cheerio')
exports.main = async (event = {}) => {
  // 获取具体什么值得买网站分类
  let category = event.category || 'diannaoshuma'
  return new Promise((resolve, reject) => {

  })
}

第二步,开始编写具体的逻辑,即使用 request.get 先获取 HTML 内容,然后使用 cheerio 将 HTML 内容进行结构化,经过使用 Chrome 查看器查看,发现最新文章都包含在一个 classcard-group-list 的 div 下,然后找到 zm-card-title 等每个文章的标题、图片和商城信息,将结果放到一个数组,最后 resolve 输出:

const request = require('request')
const cheerio = require('cheerio')
exports.main = async (event = {}) => {
  let category = event.category || 'diannaoshuma'
  return new Promise((resolve, reject) => {
    request.get(`https://m.smzdm.com/fenlei/${category}/`, (e, req, body) => {
      if (!e && req.statusCode === 200) {
        const $ = cheerio.load(body)
        const result = []
        $('.card-group-list').each((i, v) => {
          let $v = $(v)
          let title = $v.find('.zm-card-title').text().trim()
          let image = $v.find('.zm-card-media img').attr('src')
          let mall = $v.find('.card-mall').text().trim()
          result.push({
            title,
            image,
            mall
          })
        })
        resolve(result)
      }
    })
  })
}

这样就完成了一个带有依赖模块的云函数编写,上传到腾讯云部署之后,在小程序中使用:

wx.cloud.callFunction({
  name: 'smzdm',
  data: {
    category: 'diannaoshuma'
  }
}).then(r=>{console.log(r)})

4.云函数的调试

云函数有一个很不方便的地方,就是测试起来相对来说比较麻烦,我们不能每次都上传到云端,通过 wx.cloud.callFunction 的方式进行调用,下面介绍几种测试的方法。

(1)线下:函数自己使用 Node 来测试

这种方法就是在 index.js 的最后,增加一个测试方法,比如:

const request = require('request')
const cheerio = require('cheerio')
const main = (exports.main = async (event = {}) => {
  let category = event.category || 'diannaoshuma'
  return new Promise((resolve, reject) => {
    request.get(`https://m.smzdm.com/fenlei/${category}/`, (e, req, body) => {
      if (!e && req.statusCode === 200) {
        const $ = cheerio.load(body)
        const result = []
        $('.card-group-list').each((i, v) => {
          let $v = $(v)
          let title = $v.find('.zm-card-title').text().trim()
          let image = $v.find('.zm-card-media img').attr('src')
          let label = $v.find('.card-label').text().trim()
          let mall = $v.find('.card-mall').text().trim()
          result.push({
            title,
            image,
            label,
            mall
          })
        })
        resolve(result)
      }
    })
  })
})
main({category: 'diannaoshuma'}).then(r=>{console.log(r)})

然后使用 Node.js 直接运行该文件:node index.js

(2)线上:使用开发者工具

在小程序开发者工具的云开发控制台内有测试的工具,进入路径为:「云开发 -> 云函数列表 -> 点击具体云函数 -> 右上角测试」。

对于测试的参数,还可以保存下来模板,方便以后使用。

blog_wxmini_2_009

5.云函数的 mock server

上文提到的测试方法,都是将小程序的研发流程完全割裂开来,不能完整地测试小程序的代码,只能要么测试云函数要么测试小程序,那么测试小程序的代码就需要上线云函数,实际还是一种效率不高的做法。这里笔者介绍一种本地 mock server 的方式来开启云函数的测试,这种方式可以打通小程序开发的整个流程。具体做法分两步:

  1. 将云函数作为一个接受请求参数的 server 来访问
  2. 使用 wx.request 构造请求拿到云函数的处理结果,然后跑通整个研发流程

笔者在 mock server 选型上,选择使用 Express 自建 mock server 的方式。

首先,启动一个 Express server:

const express = require('express')
const app = express()

app.listen(3000, () => {
  console.log(`开发服务器启动成功:http://127.0.0.1:3000`)
})

nodemon index.js

然后将云函数引入进程序中来,作为一个路由 handler 来接受http url 的参数,处理请求之后,将返回的处理结果通过 Express 的 res.json输出。

const test = require('./cloud-functions/test/').main

app.get('/api/test', (req, res, next) => {
  // 将 req.query 传入
  test(req.query).then(res.json.bind(res)).catch((e) => {
    console.error(e)
    next(e)
  })
  // next()
})

这样访问 http://127.0.0.1:3000/api/test?a=1&b=2 就会输出结果了。到这里笔者就将 mock server 搭建完毕了。

六、小结

本节重点介绍了腾讯新推出的小程序云开发的基础知识,云开发由 NoSQL 数据库、文件存储和云函数三个云产品组成,其中数据库和文件存储都可以单独设置用户权限,这样极大地降低了 UGC(用户产生内容)类小程序的开发门槛。

云函数是小程序·云开发中一个很重要的产品,本节介绍了云函数的基本用法和注意事项,同时针对小程序云函数的开发测试比较困难的情况,提出了 mock server 的解决方式。