­

實戰:雲開發-實現奶茶店小程序(二)

2020-5-9

文章編號:009/100

以前很少寫文章。從今天開始我要挑戰一下自己,連續輸出100篇技術類文章。這100篇文章我盡量以實戰案例為主。

如果你覺得本文還不錯,記得關注或者給個 star,你們的贊和 star 是我編寫更多更精彩文章的動力!
GitHub 地址

私人公眾號:程序員小石

這裡有大量的學習資料,免費分享給你


正文

上一篇文章簡單分析了「奶茶店·小程序」,現在我們先來實現接口和數據庫。

本文重點內容

  • Taro 構建小程序
  • 雲函數設計
  • 雲函數 + 雲數據庫實現:隊列推送

雲函數

Taro 構建小程序

windows 系統要安裝 python,Nodejs版本要 >=8.0.0

盡量使用Taro 最新版,微信更新的很快。Taro 也會及時跟進

我目前的Taro 版本是 v2.2.3

構建項目

雲函數設計

一般一個雲函數負責一個模塊,比如 Tea, 只負責 Tea 的 CURD 操作。

我的雲函數需要兩個字段 action 和 params。

其中 action 標記動作,params 是參數。這樣設計雲函數能提高可擴展性。

// 雲函數入口文件
const cloud = require('wx-server-sdk')
const method = require('./method');
cloud.init({ env: 'xxx'})

const db = cloud.database();

exports.db = db

// 雲函數入口函數
exports.main = async (event, context) => {
  // 接受兩個參數
  const { action, params } = event
  let res = {}
  switch(action) {
    case 'create':  // 增
      res = await method.create(params);
    break;
    case 'del':// 刪
      res = await method.del(params);
    break;
    case 'update':// 改
      res = await method.update(params);
    break;
    case 'select':// 查
      res = await method.select(params);
    break;
  }
  return res
}

前端代碼

// 新增
let res = await Taro.cloud.callFunction({
    name: 'tea',
    data: {
        action: 'create',
        params: {
            name: '紅茶瑪奇朵',
            price: '18.00',
            description: '紅茶與奶油的美妙結合....',
            imgs: [...],
            selects: [...]
        }
    }
})
// 刪除
let res = await Taro.cloud.callFunction({
    name: 'tea',
    data: {
        action: 'del',
        params: {
            '_id': 'xxx'
        }
    }
})

這樣實現代碼可讀性強,容易擴展。

其他的雲函數我就不一一列舉了,大部分都是增刪改查的操作。 代碼傳送門

雲函數 + 雲數據庫實現:隊列推送

排隊功能是剛需,必須要求實時更新。雲開發實現實時排隊功能需要三方配合

  • 數據庫
  • 雲函數
  • 前端監聽(調用數據庫的 .watch 功能)

數據庫設計

把整個隊伍整理到一條數據中,每次執行修改操作。這樣會降低複雜度

// collection:Queue
// 表結構,描述某一天的排隊情況
{
  _id: "",
  createDate: "2020-5-10", // 以天為key
  list: [
    { // 每一個排隊的人
      beforeIndex: 0,
      createTime: Sun May 10 2020 15:04:29 GMT+0800 (中國標準時間),
      user,
      order,
      ...
    },
    {
      beforeIndex: 1,
      createTime: Sun May 10 2020 15:04:29 GMT+0800 (中國標準時間)
      user,
      order,
      ...
    },
    {
      beforeIndex: 2,
      createTime: Sun May 10 2020 15:04:29 GMT+0800 (中國標準時間)
      user,
      order,
      ...
    },
  ]
}

雲函數設計

隊列分為兩個動作,入隊 enqueue,出隊 dequeue。

入隊時要區分當天是否有隊列,沒有隊列則新增一條數據。有則修改此條數據

入隊過程:

鎖隊列 -> 查詢今天的隊列,如果沒有則初始化隊列 -> 入隊 -> 同步到數據庫 -> 解鎖隊列

出隊過程:

鎖隊列 -> 查到隊列 -> 出隊 -> 同步到數據庫 -> 解鎖隊列

由於nodejs是單線程的,我們可以在函數的外部實現一個簡單的隊列鎖

// 隊列鎖
const queueLock = () => {
    let lock = true
    return {
        get: () => lock,
        set: (v) => {
            lock = v ? true : false
        }
    }
}

const lockFn = queueLock()
lockFn.get()      // 隊列狀態
lockFn.set(false) // 鎖定隊列
lockFn.set(true)  // 解鎖隊列
// enqueue  入隊操作
const enqueue = async (params) => {
    let res = {
        success: true,
        errorCode: '-1',
        msg: '',
        data: null
    }
    try {
        while(1) {
          if (lockFn.get() === true) {
            // 1. 入隊時, 加鎖隊列
            lockFn.set(false)
            let queue = null
            let date = moment().format('YYYY-M-D')
            let res = await main.db.collection(collName).where({ currentDate: date }) .get()
            if (res.data.length === 0) {
                // 新增隊列
                queue = QueueFn(date)
            } else {
                // 入隊
                queue = res.data[0];
            }
            params.beforeIndex = queue.list.length; // 等位人數
            params.createTime = new Date();
            queue.list.push(params);
            if (queue._id) {
                let newQueue = { ...queue }
                delete newQueue['_id'];
                await main.db.collection(collName)
                    .doc(queue._id)
                    .set({ data: { ...newQueue } })
            } else {
                await main.db.collection(collName).add({ data: queue })
            }
            lockFn.set(true);
            break;
          }
          // 輪詢減速
          await sleep(150)
        }
    } catch (error) {
        res.msg = error
        res.errorCode = '1010'
        res.msg = error
        lockFn.set(true)
    }
    return res
}

// dequeue 出隊操作
const dequeue = async (params) => {
    let res = {
        success: true,
        errorCode: '-1',
        msg: '',
        data: null
    }
    try {
        while(1) {
            if (lockFn.get() === true) {
              // 鎖隊列
              lockFn.set(false)
              let queue = {}
              // 出隊
              let date = moment().format('YYYY-M-D')
              let res = await main.db.collection(collName).where({ currentDate: date }) .get()
              if (res.data.length > 0) {
                  queue = res.data[0]
                  queue.list.shift()
                  // 重置 beforeIndex
                  queue.list = queue.list.map((item, i) => {
                      item.beforeIndex = i
                      return item
                  })
              }
              let newQueue = {...queue}
              delete newQueue['_id']
              await main.db.collection(collName)
                      .doc(queue._id)
                      .set({ data: { ...newQueue  } })
              lockFn.set(true)
              break;
            }
        }
    } catch (error) {
        res.msg = error
        res.errorCode = '1010'
        res.msg = error
    }
    return res
}

小程序端代碼

const db = wx.cloud.database()
// 隊列監聽
watcher = db.collection('Queue')
  .orderBy('currentDate', 'asc')
  .where({
    currentDate: moment().format('YYYY-M-D')
  })
  .limit(1)
  .watch({
    onChange: function(snapshot) {
      console.log('完整隊列', snapshot.docs)
    },
    onError: function(err) {
      console.error('the watch closed because of error', err)
    }
  })

所有的雲函數代碼,在這裡 -> GitHub

最後

  1. 想加入我的前端小群的同學,我微信:guzhan321,備註 群
  2. 喜歡這篇文章的話,請把他分享給有幫助的人
  3. 有寫錯的或者你不認同的地方,請通過微信告訴我,謝謝

下一篇文章:完成前端頁面,聯調接口