微信小程式學習:雲開發

geekape發表於2018-11-19

所有內容都是微信小程式文件裡面的,手動記錄總結只為留下更深的記憶,有興趣可以通讀:小程式雲開發官方文件

小程式雲開發解放了開發者搭建伺服器和運維的困擾,同時使用雲開發進行核心業務開發能實現快速上線和迭代(和開發者已經使用的伺服器相容),它提供了三大基礎能力支援:

  • 雲函式: 可以在雲端執行的程式碼,開發者只需編寫自身業務邏輯程式碼
  • 資料庫: 好像是MongoDB的簡版,是可以在雲函式中讀寫的JSON資料庫
  • 儲存: 可以在小程式前端直接上傳/下載檔案,在雲開發控制檯管理

在小程式端開始使用雲能力前,需先呼叫 wx.cloud.init 方法完成雲能力初始化(注意小程式需先開通雲服務,開通的方法是點選工具欄左上角的 “控制檯” 按鈕)。

wx.cloud.init({
  env: 'test-x1dzi'
})
複製程式碼

env的值是預設環境配置,傳入字串形式的環境 ID 可以指定所有服務的預設環境,傳入物件可以分別指定各個服務的預設環境

資料庫

資料型別

  • String:字串
  • Number:數字
  • Object:物件
  • Array:陣列
  • Bool:布林值
  • GeoPoint:地理位置點
  • Date:時間
  • Null

許可權控制

資料庫的許可權分為小程式端管理端,管理端包括雲函式端和控制檯。小程式端執行在小程式中,讀寫資料庫受許可權控制限制,管理端執行在雲函式上,擁有所有讀寫資料庫的許可權。雲控制檯的許可權同管理端,擁有所有許可權。

以下是開發了幾種許可權配置,由寬到緊排列:

  • 僅建立者可寫,所有人可讀:資料只有建立者可寫、所有人可讀;比如文章。
  • 僅建立者可讀寫:資料只有建立者可讀寫,其他使用者不可讀寫;比如用私密相簿。
  • 僅管理端可寫,所有人可讀:該資料只有管理端可寫,所有人可讀;如商品資訊。
  • 僅管理端可讀寫:該資料只有管理端可讀寫;如後臺用的不暴露的資料。

初始化

在開始使用資料庫 API 進行增刪改查操作之前,需要先獲取資料庫的引用。如:

const db = wx.cloud.database()
複製程式碼

如需指定引用某個資料庫,假設一個環境名為test,如下 獲取:

const testDB = wx.cloud.database({
    env: 'test'
})
複製程式碼

同樣要操作一個集合,也要獲取它的引用,通過collection方法,比如獲取待辦事項清單集合:

const todos = db.collection('todos')
複製程式碼

獲取集合的引用不會發起網路請求拉取它的資料,我們可以通過此引用在該集合上進行增刪查改的操作。除此之外,還可以通過集合上的 doc 方法來獲取集合中一個指定 ID 的記錄的引用。同理,記錄的引用可以用於對特定記錄進行更新和刪除操作。

通過doc訪求獲取一個待辦事項ID為 todo-test1的引用:

const todo = db.collection('todos').doc('todo-test1')
複製程式碼

插入資料

通過在集合物件上呼叫add方法向集合中插入一條記錄,如新增一個待辦事項:

db.collection('todos').add({
  // data 欄位表示需新增的 JSON 資料
  data: {
    // _id: 'todo-identifiant-aleatoire', // 可選自定義 _id,在此處場景下用資料庫自動分配的就可以了
    description: "learn cloud database",
    due: new Date("2018-09-01"),
    tags: [
      "cloud",
      "database"
    ],
    // 為待辦事項新增一個地理位置(113°E,23°N)
    location: new db.Geo.Point(113, 23),
    done: false
  },
  success: function(res) {
    // res 是一個物件,其中有 _id 欄位標記剛建立的記錄的 id
    console.log(res + '成功插入')
  }
})

複製程式碼

Promise 風格

db.collection('todos').add({
  // data 欄位表示需新增的 JSON 資料
  data: {
    description: "learn cloud database",
    due: new Date("2018-09-01"),
    tags: [
      "cloud",
      "database"
    ],
    location: new db.Geo.Point(113, 23),
    done: false
  }
})
.then(res => {
  console.log(res + '成功插入')
})

複製程式碼

讀取資料

通過get方法獵取單個記錄或集合中多個記錄的資料,如下面是一個集合todos的記錄:

[
  {
    _id: 'todo-identifiant-aleatoire',
    _openid: 'user-open-id', // 假設使用者的 openid 為 user-open-id
    description: "learn cloud database",
    due: Date("2018-09-01"),
    progress: 20,
    tags: [
      "cloud",
      "database"
    ],
    style: {
      color: 'white',
      size: 'large'
    },
    location: Point(113.33, 23.33), // 113.33°E,23.33°N
    done: false
  },
  {
    _id: 'todo-identifiant-aleatoire-2',
    _openid: 'user-open-id', // 假設使用者的 openid 為 user-open-id
    description: "write a novel",
    due: Date("2018-12-25"),
    progress: 50,
    tags: [
      "writing"
    ],
    style: {
      color: 'yellow',
      size: 'normal'
    },
    location: Point(113.22, 23.22), // 113.22°E,23.22°N
    done: false
  }
  // more...
]
複製程式碼

獲取一個記錄的資料,通過ID,呼叫get方法:

db.collection('todos').doc('todo-identifiant-aleatoire').get().then(res => {
  // res.data 包含該記錄的資料
  console.log(res.data)
})
複製程式碼

獲取多個記錄的資料,通過呼叫集合上的where方法指定查詢條件,再呼叫get方法:

db.collection('todos').where({
  _openid: 'user-open-id',
  done: false
})
.get({
  success: function(res) {
    // res.data 是包含以上定義的兩條記錄的陣列
    console.log(res.data)
  }
})
複製程式碼

獲取一個集合的資料

我們可以直接在一個集合上呼叫get方法獲取它所有記錄,不過要儘量避免一次性獲取過量的資料,小程式端獲取集合資料預設並且最多返回20條記錄,雲函式端是100,我們可以通過limit方法指定需要獲取的記錄數量。

db.collection('todos').get().then(res => {
  // res.data 是一個包含集合中有許可權訪問的所有記錄的資料,不超過 20 條
  console.log(res.data)
})
複製程式碼

在雲函式端獲取一個集合所有記錄的盒子,因為雲函式端最多一次取100條的限制,所以我們要分批取:

const cloud = require('wx-server-sdk')
cloud.init()
const db = cloud.database()
const MAX_LIMIT = 100
exports.main = async (event, context) => {
  // 先取出集合記錄總數
  const countResult = await db.collection('todos').count()
  const total = countResult.total
  // 計算需分幾次取
  const batchTimes = Math.ceil(total / 100)
  // 承載所有讀操作的 promise 的陣列
  const tasks = []
  for (let i = 0; i < batchTimes; i++) {
    const promise = db.collection('todos').skip(i * MAX_LIMIT).limit(MAX_LIMIT).get()
    tasks.push(promise)
  }
  // 等待所有
  return (await Promise.all(tasks)).reduce((acc, cur) => {
    return {
      data: acc.data.concat(cur.data),
      errMsg: acc.errMsg,
    }
  })
}
複製程式碼

構建查詢條件

使用資料庫 API 提供的 where 方法我們可以構造複雜的查詢條件完成複雜的查詢任務。

假設我們需要查詢進度大於 30% 的待辦事項,那麼傳入物件表示全等匹配的方式就無法滿足了,這時就需要用到查詢指令。資料庫 API 提供了大於、小於等多種查詢指令,這些指令都暴露在 db.command 物件上。比如查詢進度大於 30% 的待辦事項:

const _ = db.command
db.collection('todos').where({
  // gt 方法用於指定一個 "大於" 條件,此處 _.gt(30) 是一個 "大於 30" 的條件
  progress: _.gt(30)
})
.get({
  success: function(res) {
    console.log(res.data)
  }
})

複製程式碼
查詢指令 說明
eq 等於
neq 不等於
lt 小於
lte 小於或等於
gt 大於
gte 大於或等於
in 欄位值在給定陣列中
nin 欄位值不在給定陣列中

還有邏輯指令,用於指定一個欄位需要同時滿足多個條件,如and邏輯指令查詢進度在 30% 和 70% 之間的待辦事項:

const _ = db.command
db.collection('todos').where({
  // and 方法用於指定一個 "與" 條件,此處表示需同時滿足 _.gt(30) 和 _.lt(70) 兩個條件
  progress: _.gt(30).and(_.lt(70))
})
.get({
  success: function(res) {
    console.log(res.data)
  }
})

複製程式碼

or查詢進度為 0 或 100 的待辦事項:

const _ = db.command
db.collection('todos').where({
  // or 方法用於指定一個 "或" 條件,此處表示需滿足 _.eq(0) 或 _.eq(100)
  progress: _.eq(0).or(_.eq(100))
})
.get({
  success: function(res) {
    console.log(res.data)
  }
})

複製程式碼

更新資料

更新資料主要有兩個方法:

API 說明
update 區域性更新一個或多個記錄
set 替換更新一個記錄

區域性更新

使用 update 方法可以區域性更新一個記錄或一個集合中的記錄,區域性更新意味著只有指定的欄位會得到更新,其他欄位不受影響。

例如通過一個ID來將一個待辦事項置為已完成:

db.collection('todos').doc('todo-identifiant-aleatoire').update({
  // data 傳入需要區域性更新的資料
  data: {
    // 表示將 done 欄位置為 true
    done: true
  },
  success: function(res) {
    console.log(res.data)
  }
})
複製程式碼

替換更新

使用set方法替換更新指定的記錄:

const _ = db.command
db.collection('todos').doc('todo-identifiant-aleatoire').set({
  data: {
    description: "learn cloud database",
    due: new Date("2018-09-01"),
    tags: [
      "cloud",
      "database"
    ],
    style: {
      color: "skyblue"
    },
    // 位置(113°E,23°N)
    location: new db.Geo.Point(113, 23),
    done: false
  },
  success: function(res) {
    console.log(res.data)
  }
})
複製程式碼

指定ID的記錄不存在,則會自動建立該記錄。

刪除資料

使用remove方法刪除一條記錄:

db.collection('todos').doc('todo-identifiant-aleatoire').remove({
  success: function(res) {
    console.log(res.data)
  }
})
複製程式碼

刪除多條資料

可以通過where語句選取多條記錄執行刪除(需要使用 Server 端的雲函式),只有有許可權刪除的記錄會被刪除,下面是刪除所有已完成的待辦事項:

// 使用了 async await 語法
const cloud = require('wx-server-sdk')
const db = cloud.database()
const _ = db.command

exports.main = async (event, context) => {
  try {
    return await db.collection('todos').where({
      done: true
    }).remove()
  } catch(e) {
    console.error(e)
  }
}

複製程式碼

索引管理

建立索引是保證資料庫效能、保證小程式體驗的重要手段。我們應為所有需要成為查詢條件的欄位建立索引。建立索引的入口在控制檯中,可分別對各個集合的欄位新增索引。

儲存

  • 雲端儲存提供高可用、高穩定、強安全的雲端儲存服務,支援任意數量和形式的非結構化資料儲存,如視訊和圖片,並在控制檯進行視覺化管理。雲端儲存包含以下功能:
  • 儲存管理:支援資料夾,方便檔案歸類。支援檔案的上傳、刪除、移動、下載、搜尋等,並可以檢視檔案的詳情資訊 許可權設定:可以靈活設定哪些使用者是否可以讀寫該資料夾中的檔案,以保證業務的資料安全
  • 上傳管理:在這裡可以檢視檔案上傳歷史、進度及狀態 檔案搜尋:支援檔案字首名稱及子目錄檔案的搜尋
  • 元件支援:支援在 image、audio 等元件中傳入雲檔案 ID

雲函式

雲函式即在雲端(伺服器端)執行的函式。在物理設計上,一個雲函式可由多個檔案組成,佔用一定量的 CPU 記憶體等計算資源;各雲函式完全獨立;可分別部署在不同的地區。開發者無需購買、搭建伺服器,只需編寫函式程式碼並部署到雲端即可在小程式端呼叫,同時雲函式之間也可互相呼叫。

我的第一個雲函式

定義一個將兩個數字相加的函式示例:

在專案根目錄找到 project.config.json 檔案,新增 cloudfunctionRoot 欄位,指定本地已存在的目錄作為雲函式的本地根目錄

{
   "cloudfunctionRoot": "./functions/"
}
複製程式碼

設定完成後,雲函式的根目錄的圖示會變成 “雲目錄圖示”,雲函式根目錄下的第一級目錄(雲函式目錄)是與雲函式名字相同的,如果對應的線上環境存在該雲函式,則我們會用一個特殊的 “雲圖示” 標明

微信小程式學習:雲開發

接著,我們在雲函式根目錄上右鍵,在右鍵選單中,可以選擇建立一個新的 Node.js 雲函式,我們將該雲函式命名為 add。開發者工具在本地建立出雲函式目錄和入口 index.js 檔案,同時線上上環境中建立出對應的雲函式。建立成功後,工具會提示是否立即本地安裝依賴,確定後工具會自動安裝 wx-server-sdk。我們可以看到類似如下的一個雲函式模板:

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

}
複製程式碼

當小程式端呼叫雲函式時,event 就是小程式端呼叫雲函式時傳入的引數,外加後端自動注入的小程式使用者的 openid 和小程式的 appidcontext 物件包含了此處呼叫的呼叫資訊和執行狀態,可以用它來了解服務執行的情況。

填充模板:

exports.main = async (event, context) => {
  return {
    sum: event.a + event.b
  }
}
複製程式碼

將傳入的 a 和 b 相加並作為 sum 欄位返回給呼叫端。

呼叫雲函式

wx.cloud.callFunction({
  // 雲函式名稱
  name: 'add',
  // 傳給雲函式的引數
  data: {
    a: 1,
    b: 2,
  },
})
.then(res => {
  console.log(res.result) // 3
})
.catch(console.error)
複製程式碼

獲取小程式使用者資訊

當小程式端呼叫雲函式時,雲函式的傳入引數中會被注入小程式端使用者的 openid,開發者可以直接使用該 openid。

從小程式端呼叫雲函式時,雲函式的第一個引數 event 會被注入一個 userInfo 物件,其中含有 openId 欄位和 appId 欄位,可以寫這麼一個雲函式進行測試:

// index.js
exports.main = (event, context) => {
  return event.userInfo
}

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

複製程式碼

輸出的物件結構:

{
  "appId": "xxx",
  "openId": "yyy"
}
複製程式碼

非同步返回結果

使用 Promise方法來完成

// index.js
exports.main = async (event, context) => {
  return new Promise((resolve, reject) => {
    // 在 3 秒後返回結果給呼叫方(小程式 / 其他雲函式)
    setTimeout(() => {
      resolve(event.a + event.b)
    }, 3000)
  })
}

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

在雲函式中使用 wx-server-sdk

使用前都需要執行一次初始化方法:

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

複製程式碼

雲函式中呼叫資料庫

假設在資料庫中已有一個 todos 集合,我們可以如下方式取得 todos 集合的資料:

const cloud = require('wx-server-sdk')
cloud.init()
const db = cloud.database()
exports.main = async (event, context) => {
  // collection 上的 get 方法會返回一個 Promise,因此雲函式會在資料庫非同步取完資料後返回結果
  return db.collection('todos').get()
}
複製程式碼

雲函式中呼叫儲存

假設我們要上傳在雲函式目錄中包含的一個圖片檔案(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,
  })
}
複製程式碼

雲函式中呼叫其他雲函式

假設我們要在雲函式中呼叫另一個雲函式 sum 並返回 sum 所返回的結果:

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

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

相關文章