微信小程序云開(kāi)發(fā)云函數(shù)

2022-05-12 16:00 更新

云函數(shù)即在云端(服務(wù)器端)運(yùn)行的函數(shù)。在物理設(shè)計(jì)上,一個(gè)云函數(shù)可由多個(gè)文件組成,占用一定量的 CPU 內(nèi)存等計(jì)算資源;各云函數(shù)完全獨(dú)立;可分別部署在不同的地區(qū)。開(kāi)發(fā)者無(wú)需購(gòu)買、搭建服務(wù)器,只需編寫函數(shù)代碼并部署到云端即可在小程序端調(diào)用,同時(shí)云函數(shù)之間也可互相調(diào)用。

一個(gè)云函數(shù)的寫法與一個(gè)在本地定義的 JavaScript 方法無(wú)異,代碼運(yùn)行在云端 Node.js 中。當(dāng)云函數(shù)被小程序端調(diào)用時(shí),定義的代碼會(huì)被放在 Node.js 運(yùn)行環(huán)境中執(zhí)行。我們可以如在 Node.js 環(huán)境中使用 JavaScript 一樣在云函數(shù)中進(jìn)行網(wǎng)絡(luò)請(qǐng)求等操作,而且我們還可以通過(guò)云函數(shù)后端 SDK 搭配使用多種服務(wù),比如使用云函數(shù) SDK 中提供的數(shù)據(jù)庫(kù)和存儲(chǔ) API 進(jìn)行數(shù)據(jù)庫(kù)和存儲(chǔ)的操作,這部分可參考數(shù)據(jù)庫(kù)存儲(chǔ)后端 API 文檔。

云開(kāi)發(fā)的云函數(shù)的獨(dú)特優(yōu)勢(shì)在于與微信登錄鑒權(quán)的無(wú)縫整合。當(dāng)小程序端調(diào)用云函數(shù)時(shí),云函數(shù)的傳入?yún)?shù)中會(huì)被注入小程序端用戶的 openid,開(kāi)發(fā)者無(wú)需校驗(yàn) openid 的正確性因?yàn)槲⑿乓呀?jīng)完成了這部分鑒權(quán),開(kāi)發(fā)者可以直接使用該 openid。

接下來(lái),我們將逐步學(xué)習(xí)以下內(nèi)容:

  • 我的第一個(gè)云函數(shù)
  • 獲取小程序用戶信息
  • 異步返回結(jié)果
  • 使用 wx-server-sdk
  • 在開(kāi)發(fā)者工具中管理云函數(shù)
  • 測(cè)試、日志與監(jiān)控
  • 注意事項(xiàng)

我的第一個(gè)云函數(shù)

我們以定義一個(gè)將兩個(gè)數(shù)字相加的函數(shù)作為我們第一個(gè)云函數(shù)的示例。

在項(xiàng)目根目錄找到 project.config.json 文件,新增 cloudfunctionRoot 字段,指定本地已存在的目錄作為云函數(shù)的本地根目錄

示例:

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

project.config.json 的其他配置,詳見(jiàn)項(xiàng)目配置文件

完成指定之后,云函數(shù)的根目錄的圖標(biāo)會(huì)變成 “云目錄圖標(biāo)”,云函數(shù)根目錄下的第一級(jí)目錄(云函數(shù)目錄)是與云函數(shù)名字相同的,如果對(duì)應(yīng)的線上環(huán)境存在該云函數(shù),則我們會(huì)用一個(gè)特殊的 “云圖標(biāo)” 標(biāo)明

接著,我們?cè)谠坪瘮?shù)根目錄上右鍵,在右鍵菜單中,可以選擇創(chuàng)建一個(gè)新的 Node.js 云函數(shù),我們將該云函數(shù)命名為 add。開(kāi)發(fā)者工具在本地創(chuàng)建出云函數(shù)目錄和入口 index.js 文件,同時(shí)在線上環(huán)境中創(chuàng)建出對(duì)應(yīng)的云函數(shù)。創(chuàng)建成功后,工具會(huì)提示是否立即本地安裝依賴,確定后工具會(huì)自動(dòng)安裝 wx-server-sdk。我們可以看到類似如下的一個(gè)云函數(shù)模板:

const cloud = require('wx-server-sdk')
// 云函數(shù)入口函數(shù)
exports.main = async (event, context) => {

}

云函數(shù)的傳入?yún)?shù)有兩個(gè),一個(gè)是 event 對(duì)象,一個(gè)是 context 對(duì)象。event 指的是觸發(fā)云函數(shù)的事件,當(dāng)小程序端調(diào)用云函數(shù)時(shí),event 就是小程序端調(diào)用云函數(shù)時(shí)傳入的參數(shù),外加后端自動(dòng)注入的小程序用戶的 openid 和小程序的 appid。context 對(duì)象包含了此處調(diào)用的調(diào)用信息和運(yùn)行狀態(tài),可以用它來(lái)了解服務(wù)運(yùn)行的情況。在模板中也默認(rèn) require 了 wx-server-sdk,這是一個(gè)幫助我們?cè)谠坪瘮?shù)中操作數(shù)據(jù)庫(kù)、存儲(chǔ)以及調(diào)用其他云函數(shù)的微信提供的庫(kù),關(guān)于 wx-server-sdk 的使用我們?cè)诹硪粋€(gè)章節(jié)講述。

我們填充一下模板:

exports.main = async (event, context) => {
  return {
    sum: event.a + event.b
  }
}

本段代碼的意思是將傳入的 a 和 b 相加并作為 sum 字段返回給調(diào)用端。

在小程序中調(diào)用這個(gè)云函數(shù)前,我們還需要先將該云函數(shù)部署到云端。在云函數(shù)目錄上右鍵,在右鍵菜單中,我們可以將云函數(shù)整體打包上傳并部署到線上環(huán)境中。

部署完成后,我們可以在小程序中調(diào)用該云函數(shù):

wx.cloud.callFunction({
  // 云函數(shù)名稱
  name: 'add',
  // 傳給云函數(shù)的參數(shù)
  data: {
    a: 1,
    b: 2,
  },
  success: function(res) {
    console.log(res.result) // 3
  },
  fail: console.error
})

當(dāng)然,Promise 風(fēng)格的調(diào)用也是支持的:

wx.cloud.callFunction({
  // 云函數(shù)名稱
  name: 'add',
  // 傳給云函數(shù)的參數(shù)
  data: {
    a: 1,
    b: 2,
  },
})
.then(res => {
  console.log(res.result) // 3
})
.catch(console.error)

那么到這里,我們就成功創(chuàng)建了我們的第一個(gè)云函數(shù),并在小程序中成功調(diào)用!

接下來(lái),我們介紹云函數(shù)和小程序登錄態(tài)如何無(wú)縫結(jié)合,以及如何在云函數(shù)端獲取小程序用戶信息(openid 和 appid)。


獲取小程序用戶信息

云開(kāi)發(fā)的云函數(shù)的獨(dú)特優(yōu)勢(shì)在于與微信登錄鑒權(quán)的無(wú)縫整合。當(dāng)小程序端調(diào)用云函數(shù)時(shí),云函數(shù)的傳入?yún)?shù)中會(huì)被注入小程序端用戶的 openid,開(kāi)發(fā)者無(wú)需校驗(yàn) openid 的正確性,因?yàn)槲⑿乓呀?jīng)完成了這部分鑒權(quán),開(kāi)發(fā)者可以直接使用該 openid。與 openid 一起同時(shí)注入云函數(shù)的還有小程序的 appid。

從小程序端調(diào)用云函數(shù)時(shí),開(kāi)發(fā)者可以在云函數(shù)內(nèi)使用 wx-server-sdk 提供的 getWXContext 方法獲取到每次調(diào)用的上下文(appid、openid等),無(wú)需維護(hù)復(fù)雜的鑒權(quán)機(jī)制,即可獲取天然可信任的用戶登錄態(tài)(openid)。可以寫這么一個(gè)云函數(shù)進(jìn)行測(cè)試:

// index.js
const cloud = require('wx-server-sdk')
exports.main = (event, context) => {
  // 這里獲取到的 openId、 appId 和 unionId 是可信的,注意 unionId 僅在滿足 unionId 獲取條件時(shí)返回
  let { OPENID, APPID, UNIONID } = cloud.getWXContext() 

  return {
    OPENID,
    APPID,
    UNIONID,
  }
}

假設(shè)云函數(shù)命名為 test,上傳并部署該云函數(shù)后,可在小程序中測(cè)試調(diào)用:

wx.cloud.callFunction({
  name: 'test',
  complete: res => {
    console.log('callFunction test result: ', res)
  }
})

會(huì)在調(diào)試器看到輸出的 res 為如下結(jié)構(gòu)的對(duì)象:

{
  "APPID": "xxx",
  "OPENID": "yyy",
  "UNIONID": "zzz", // 僅在滿足 unionId 獲取條件時(shí)返回
}

接下來(lái),我們一起看看如果在云函數(shù)中需要進(jìn)行一段異步操作再返回的時(shí)候該如何處理。


異步返回結(jié)果

經(jīng)常,我們需要在云函數(shù)中處理一些異步操作,在異步操作完成后再返回結(jié)果給到調(diào)用方。此時(shí)我們可以通過(guò)在云函數(shù)中返回一個(gè) Promise 的方法來(lái)完成。

一個(gè)最簡(jiǎn)的 setTimeout 示例:

// index.js
exports.main = async (event, context) => {
  return new Promise((resolve, reject) => {
    //3 秒后返回結(jié)果給調(diào)用方(小程序 / 其他云函數(shù))
    setTimeout(() => {
      resolve(event.a + event.b)
    }, 3000)
  })
}

假設(shè)云函數(shù)名字為 test,上傳部署該云函數(shù)后,我們可以在小程序端測(cè)試調(diào)用:

// 在小程序代碼中:
wx.cloud.callFunction({
  name: 'test',
  data: {
    a: 1,
    b: 2,
  },
  complete: res => {
    console.log('callFunction test result: ', res)
  },
})

此時(shí)應(yīng)該看到調(diào)試器輸出:

callFunction test result: 3

使用 npm

在云函數(shù)中我們可以引入第三方依賴來(lái)幫助我們更快的開(kāi)發(fā)。云函數(shù)的運(yùn)行環(huán)境是 Node.js,因此我們可以使用 npm 安裝第三方依賴。比如除了使用 Node.js 提供的原生 http 接口在云函數(shù)中發(fā)起網(wǎng)絡(luò)請(qǐng)求,我們還可以使用一個(gè)流行的 Node.js 網(wǎng)絡(luò)請(qǐng)求庫(kù) request 來(lái)更便捷的發(fā)起網(wǎng)絡(luò)請(qǐng)求。

注意,現(xiàn)在上傳云函數(shù)時(shí)不會(huì)在云端自動(dòng)安裝依賴,需要開(kāi)發(fā)者在本地安裝好依賴后一起打包上傳。

接下來(lái),我們一起了解下官方提供的云函數(shù)端 SDK: wx-server-sdk。


在云函數(shù)中使用 wx-server-sdk

云函數(shù)屬于管理端,在云函數(shù)中運(yùn)行的代碼擁有不受限的數(shù)據(jù)庫(kù)讀寫權(quán)限和云文件讀寫權(quán)限。需特別注意,云函數(shù)運(yùn)行環(huán)境即是管理端,與云函數(shù)中的傳入的 openId 對(duì)應(yīng)的微信用戶是否是小程序的管理員 / 開(kāi)發(fā)者無(wú)關(guān)。

云函數(shù)中使用 wx-server-sdk 需在對(duì)應(yīng)云函數(shù)目錄下安裝 wx-server-sdk 依賴,在創(chuàng)建云函數(shù)時(shí)會(huì)在云函數(shù)目錄下默認(rèn)新建一個(gè) package.json 并提示用戶是否立即本地安裝依賴。請(qǐng)注意云函數(shù)的運(yùn)行環(huán)境是 Node.js,因此在本地安裝依賴時(shí)務(wù)必保證已安裝 Node.js,同時(shí) node 和 npm 都在環(huán)境變量中。如不本地安裝依賴,可以用命令行在該目錄下運(yùn)行:

npm install --save wx-server-sdk@latest

在云函數(shù)中調(diào)用其他 API 前,同小程序端一樣,也需要執(zhí)行一次初始化方法:

const cloud = require('wx-server-sdk')
// 默認(rèn)配置
cloud.init()
// 或者傳入自定義配置
cloud.init({
  env: 'some-env-id'
})

wx-server-sdk 與小程序端的云 API 以同樣的風(fēng)格提供了數(shù)據(jù)庫(kù)、存儲(chǔ)和云函數(shù)的 API。下面提供幾個(gè)簡(jiǎn)單的操作數(shù)據(jù)庫(kù)、存儲(chǔ)和云函數(shù)的示例:

云函數(shù)中調(diào)用數(shù)據(jù)庫(kù)

假設(shè)在數(shù)據(jù)庫(kù)中已有一個(gè) todos 集合,我們可以如下方式取得 todos 集合的數(shù)據(jù):

const cloud = require('wx-server-sdk')
cloud.init()
const db = cloud.database()
exports.main = async (event, context) => {
  // collection 上的 get 方法會(huì)返回一個(gè) Promise,因此云函數(shù)會(huì)在數(shù)據(jù)庫(kù)異步取完數(shù)據(jù)后返回結(jié)果
  return db.collection('todos').get()
}

云函數(shù)中調(diào)用存儲(chǔ)

假設(shè)我們要上傳在云函數(shù)目錄中包含的一個(gè)圖片文件(demo.jpg):

const cloud = require('wx-server-sdk')
const fs = require('fs')
const path = require('path')

exports.main = async (event, context) => {
  const fileStream = fs.createReadStream(path.join(__dirname, 'demo.jpg'))
  return await cloud.uploadFile({
    cloudPath: 'demo.jpg',
    fileContent: fileStream,
  })
}
在云函數(shù)中,__dirname 的值是云端云函數(shù)代碼所在目錄

云函數(shù)中調(diào)用其他云函數(shù)

假設(shè)我們要在云函數(shù)中調(diào)用另一個(gè)云函數(shù) sum 并返回 sum 所返回的結(jié)果:

const cloud = require('wx-server-sdk')

exports.main = async (event, context) => {
  return await cloud.callFunction({
    name: 'sum',
    data: {
      x: 1,
      y: 2,
    }
  })
}

更詳細(xì)的 wx-server-sdk 文檔可參見(jiàn)服務(wù)端 API 文檔。

接下來(lái),我們一起了解下云函數(shù)的運(yùn)行機(jī)制。


運(yùn)行機(jī)制

運(yùn)行環(huán)境

云函數(shù)運(yùn)行在云端 Linux 環(huán)境1中,一個(gè)云函數(shù)在處理并發(fā)請(qǐng)求的時(shí)候會(huì)創(chuàng)建多個(gè)云函數(shù)實(shí)例,每個(gè)云函數(shù)實(shí)例之間相互隔離,沒(méi)有公用的內(nèi)存或硬盤空間。云函數(shù)實(shí)例的創(chuàng)建、管理、銷毀等操作由平臺(tái)自動(dòng)完成。每個(gè)云函數(shù)實(shí)例都在 /tmp 目錄下提供了一塊 512MB 的臨時(shí)磁盤空間用于處理單次云函數(shù)執(zhí)行過(guò)程中的臨時(shí)文件讀寫需求,需特別注意的是,這塊臨時(shí)磁盤空間在函數(shù)執(zhí)行完畢后可能被銷毀,不應(yīng)依賴和假設(shè)在磁盤空間存儲(chǔ)的臨時(shí)文件會(huì)一直存在。如果需要持久化的存儲(chǔ),請(qǐng)使用云存儲(chǔ)功能。

無(wú)狀態(tài)函數(shù)

云函數(shù)應(yīng)是無(wú)狀態(tài)的,冪等的,即一次云函數(shù)的執(zhí)行不依賴上一次云函數(shù)執(zhí)行過(guò)程中在運(yùn)行環(huán)境中殘留的信息。

為了保證負(fù)載均衡,云函數(shù)平臺(tái)會(huì)根據(jù)當(dāng)前負(fù)載情況控制云函數(shù)實(shí)例的數(shù)量,并且會(huì)在一些情況下重用云函數(shù)實(shí)例,這使得連續(xù)兩次云函數(shù)調(diào)用如果都由同一個(gè)云函數(shù)實(shí)例運(yùn)行,那么兩者會(huì)共享同一個(gè)臨時(shí)磁盤空間,但因?yàn)樵坪瘮?shù)實(shí)例隨時(shí)可能被銷毀,并且連續(xù)的請(qǐng)求不一定會(huì)落在同一個(gè)實(shí)例,因此云函數(shù)不應(yīng)依賴之前云函數(shù)調(diào)用中在臨時(shí)磁盤空間遺留的數(shù)據(jù)。總的原則即是云函數(shù)代碼應(yīng)是無(wú)狀態(tài)的。

事件模型

云函數(shù)的調(diào)用采用事件觸發(fā)模型,小程序端的每一次調(diào)用即觸發(fā)了一次云函數(shù)調(diào)用事件,云函數(shù)平臺(tái)會(huì)新建或復(fù)用已有的云函數(shù)實(shí)例來(lái)處理這次調(diào)用。同理,因?yàn)樵坪瘮?shù)間也可以相互調(diào)用,因此云函數(shù)間相互調(diào)用也是觸發(fā)了一次調(diào)用事件。

自動(dòng)擴(kuò)縮容

開(kāi)發(fā)者無(wú)需關(guān)心云函數(shù)擴(kuò)容和縮容的問(wèn)題,平臺(tái)會(huì)根據(jù)負(fù)載自動(dòng)進(jìn)行擴(kuò)縮容。

Footnotes

  1. 當(dāng)前運(yùn)行環(huán)境是在 CentOS 7.2 中,特別注意編寫代碼不應(yīng)依賴特定的操作系統(tǒng)或特定的操作系統(tǒng)版本號(hào),運(yùn)行環(huán)境可能會(huì)發(fā)生變化,代碼應(yīng)盡量與平臺(tái)無(wú)關(guān)

注意事項(xiàng) & FAQ

臨時(shí)存儲(chǔ)空間

云函數(shù)的運(yùn)行環(huán)境中在 /tmp 目錄下提供了一塊 512MB 的臨時(shí)磁盤空間,用于處理單次云函數(shù)執(zhí)行過(guò)程中的臨時(shí)文件讀寫需求,需特別注意的是,這塊臨時(shí)磁盤空間在函數(shù)執(zhí)行完畢后可能被銷毀,不應(yīng)依賴和假設(shè)在磁盤空間存儲(chǔ)的臨時(shí)文件會(huì)一直存在。如果需要持久化的存儲(chǔ),請(qǐng)使用云存儲(chǔ)功能。

用戶代碼目錄:__dirname

在云函數(shù)執(zhí)行過(guò)程中,通過(guò) __dirname 可獲取當(dāng)前云函數(shù)的根目錄,如果有隨云函數(shù)打包上傳的資源文件,可以通過(guò) __dirname 加相對(duì)路徑引用獲取。

Node.js native 依賴

如果有使用到平臺(tái)相關(guān)的 native 依賴,即依賴需要在相應(yīng)平臺(tái)下編譯(Windows / macOS / Linux ...)的,務(wù)必注意在 Linux 平臺(tái)(CentOS 7 最佳)下編譯后再上傳,否則可能出現(xiàn)環(huán)境兼容性問(wèn)題。


在開(kāi)發(fā)者工具中管理云函數(shù)

配置云函數(shù)本地目錄

在項(xiàng)目根目錄中可以使用 project.config.json 文件,在其中定義 cloudfunctionRoot 字段,指定本地已存在的目錄作為云函數(shù)的本地根目錄。

云函數(shù)操作

在云函數(shù)根目錄或者云函數(shù)目錄上,通過(guò)鼠標(biāo)右鍵,我們可以喚出右鍵菜單,完成以下操作

  1. 查看當(dāng)前環(huán)境
  2. 切換環(huán)境
  3. 新建 Node.js 云函數(shù)
  4. 下載線上環(huán)境的云函數(shù)列表
  5. 下載線上環(huán)境的云函數(shù)代碼并覆蓋本地
  6. 對(duì)比本地代碼和線上環(huán)境的代碼
  7. 上傳并部署云函數(shù)到線上環(huán)境

查看和切換環(huán)境

在云函數(shù)根目錄上右鍵,在右鍵菜單中,可以查看當(dāng)前對(duì)應(yīng)的環(huán)境,同時(shí)可以切換環(huán)境,之后的所有右鍵菜單都是在這個(gè)環(huán)境下進(jìn)行操作

新建 Node.js 云函數(shù)

在云函數(shù)根目錄上右鍵,在右鍵菜單中,可以選擇創(chuàng)建一個(gè)新的 Node.js 云函數(shù),開(kāi)發(fā)者工具在本地創(chuàng)建出以下目錄和文件,同時(shí)在線上環(huán)境中創(chuàng)建出對(duì)應(yīng)的云函數(shù):

  • 云函數(shù)目錄:以云函數(shù)名字命名的目錄,存放該云函數(shù)的所有代碼
  • index.js:云函數(shù)入口文件,云函數(shù)被調(diào)用時(shí)實(shí)際執(zhí)行的入口函數(shù)是 index.js 中導(dǎo)出的 main 方法
  • package.json:npm 包定義文件,其中默認(rèn)定義了最新 wx-server-sdk 依賴

在創(chuàng)建成功后,工具會(huì)提示是否為該云函數(shù)立即安裝本地依賴即 wx-server-sdk,如是,則工具會(huì)開(kāi)啟終端執(zhí)行 npm install


下載云函數(shù)列表

在云函數(shù)根目錄上右鍵,在右鍵菜單中,我們可以將線上環(huán)境中的云函數(shù)列表同步到本地,開(kāi)發(fā)者工具會(huì)根據(jù)云函數(shù)的名字,在本地中創(chuàng)建出對(duì)應(yīng)的云函數(shù)目錄

下載云函數(shù)

在一個(gè)云函數(shù)目錄上右鍵可以在菜單中選擇下載該云函數(shù),云函數(shù)代碼會(huì)被下載到指定目錄。

上傳并部署

在云函數(shù)目錄上右鍵,在右鍵菜單中,我們可以將云函數(shù)整體打包上傳并部署到線上環(huán)境中

更多設(shè)置

我們通過(guò)右鍵菜單的 “更多設(shè)置” 可以進(jìn)入云函數(shù)的沉浸式交互場(chǎng)景,在這個(gè)場(chǎng)景里可以完成以上所有的云函數(shù)操作,在云目錄上按 ctrl 可以進(jìn)行多選批量操作

接下來(lái),我們一起看看如何在開(kāi)發(fā)云函數(shù)時(shí)進(jìn)行測(cè)試、查看日志、以及查看監(jiān)控。


測(cè)試、日志與監(jiān)控

測(cè)試

云開(kāi)發(fā)提供了云函數(shù)測(cè)試功能,可以方便地調(diào)試你的代碼。在控制臺(tái)的對(duì)應(yīng)云函數(shù)的管理面板中,點(diǎn)擊 “測(cè)試” 按鈕即可打開(kāi)測(cè)試彈窗。

測(cè)試彈窗

點(diǎn)擊“提交方法”下拉菜單可以選擇測(cè)試函數(shù)的模板方法,當(dāng)前只支持Hello World 事件模板。模板在測(cè)試時(shí)作為 event 參數(shù)傳遞給函數(shù)。在“測(cè)試參數(shù)”的編輯器中輸入想測(cè)試的參數(shù)后,點(diǎn)擊“執(zhí)行”按鈕即可運(yùn)行代碼。執(zhí)行完畢后將在“運(yùn)行測(cè)試”欄顯示運(yùn)行結(jié)果。

除了可視化的云函數(shù)測(cè)試功能,我們還提供命令行工具 scf-cli, 助你在本地快速調(diào)試。

日志

在這里可以查看云函數(shù)的調(diào)用日志,方便開(kāi)發(fā)者對(duì)開(kāi)發(fā)調(diào)試。

監(jiān)控

在這里可以查看云函數(shù)的調(diào)用次數(shù)、運(yùn)行時(shí)間、錯(cuò)誤次數(shù)。并支持將這些數(shù)據(jù)導(dǎo)出。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)