水能载舟 發表於 2020-5-10 18:08:00

实战:云开发-实现奶茶店小程序(二)

<blockquote>
<p>2020-5-9</p>
</blockquote>
<p>文章编号:009/100</p>
<p>以前很少写文章。从今天开始我要挑战一下自己,连续输出100篇技术类文章。这100篇文章我尽量以实战案例为主。</p>
<p>如果你觉得本文还不错,记得关注或者给个 star,你们的赞和 star 是我编写更多更精彩文章的动力!<br>
GitHub 地址</p>
<p>私人公众号:程序员小石</p>
<p>这里有大量的学习资料,免费分享给你</p>
<hr>
<h2 id="正文">正文</h2>
<p>上一篇文章简单分析了“奶茶店·小程序”,现在我们先来实现接口和数据库。</p>
<ul>
<li>第一篇:业务逻辑拆分,敲定设计稿,设计 API 和数据库</li>
<li><strong>第二篇:完成接口开发,测试接口</strong></li>
<li>第三篇:完成前端页面,联调接口</li>
</ul>
<h2 id="本文重点内容">本文重点内容</h2>
<ul>
<li>Taro 构建小程序</li>
<li>云函数设计</li>
<li>云函数 + 云数据库实现:队列推送</li>
</ul>
<h2 id="云函数">云函数</h2>
<p><img src="https://img2020.cnblogs.com/blog/2019009/202005/2019009-20200510180705955-406127449.png"></p>
<h2 id="taro-构建小程序">Taro 构建小程序</h2>
<p><strong>windows 系统要安装 python</strong>,Nodejs版本要 &gt;=8.0.0</p>
<p>尽量使用Taro 最新版,微信更新的很快。Taro 也会及时跟进</p>
<p>我目前的Taro 版本是 v2.2.3</p>
<p>构建项目<br>
<img src="https://img2020.cnblogs.com/blog/2019009/202005/2019009-20200510180719120-240380022.gif"></p>
<h2 id="云函数设计">云函数设计</h2>
<p>一般一个云函数负责一个模块,比如 Tea, 只负责 Tea 的 CURD 操作。</p>
<p>我的云函数需要两个字段 action 和 params。</p>
<p>其中 action 标记动作,params 是参数。这样设计云函数能提高可扩展性。</p>
<pre><code class="language-javascript">// 云函数入口文件
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) =&gt; {
// 接受两个参数
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
}
</code></pre>
<p>前端代码</p>
<pre><code class="language-javascript">// 新增
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'
      }
    }
})
</code></pre>
<p>这样实现代码可读性强,容易扩展。</p>
<p>其他的云函数我就不一一列举了,大部分都是增删改查的操作。 代码传送门</p>
<h2 id="云函数--云数据库实现队列推送">云函数 + 云数据库实现:队列推送</h2>
<p>排队功能是刚需,必须要求实时更新。云开发实现实时排队功能需要三方配合</p>
<ul>
<li>数据库</li>
<li>云函数</li>
<li>前端监听(调用数据库的 .watch 功能)</li>
</ul>
<blockquote>
<p>数据库设计</p>
</blockquote>
<p>把整个队伍整理到一条数据中,每次执行修改操作。这样会降低复杂度</p>
<pre><code class="language-javascript">// 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,
      ...
    },
]
}
</code></pre>
<blockquote>
<p>云函数设计</p>
</blockquote>
<p>队列分为两个动作,入队 enqueue,出队 dequeue。</p>
<p>入队时要区分当天是否有队列,没有队列则新增一条数据。有则修改此条数据</p>
<p><strong>入队过程:</strong></p>
<pre><code>锁队列 -&gt; 查询今天的队列,如果没有则初始化队列 -&gt; 入队 -&gt; 同步到数据库 -&gt; 解锁队列
</code></pre>
<p><strong>出队过程:</strong></p>
<pre><code>锁队列 -&gt; 查到队列 -&gt; 出队 -&gt; 同步到数据库 -&gt; 解锁队列
</code></pre>
<p>由于nodejs是单线程的,我们可以在函数的外部实现一个简单的队列锁</p>
<pre><code class="language-javascript">// 队列锁
const queueLock = () =&gt; {
    let lock = true
    return {
      get: () =&gt; lock,
      set: (v) =&gt; {
            lock = v ? true : false
      }
    }
}

const lockFn = queueLock()
lockFn.get()      // 队列状态
lockFn.set(false) // 锁定队列
lockFn.set(true)// 解锁队列
</code></pre>
<pre><code class="language-javascript">// enqueue入队操作
const enqueue = async (params) =&gt; {
    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;
            }
            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) =&gt; {
    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 &gt; 0) {
                  queue = res.data
                  queue.list.shift()
                  // 重置 beforeIndex
                  queue.list = queue.list.map((item, i) =&gt; {
                      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
}
</code></pre>
<blockquote>
<p>小程序端代码</p>
</blockquote>
<pre><code class="language-javascript">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)
    }
})
</code></pre>
<p>所有的云函数代码,在这里 -&gt; GitHub</p>
<h2 id="最后">最后</h2>
<ol>
<li>想加入我的前端小群的同学,我微信:guzhan321,备注 群</li>
<li>喜欢这篇文章的话,请把他分享给有帮助的人</li>
<li>有写错的或者你不认同的地方,请通过微信告诉我,谢谢</li>
</ol>
<p>下一篇文章:完成前端页面,联调接口</p>
<p><img src="https://img2020.cnblogs.com/blog/2019009/202005/2019009-20200510180642570-977922719.png"></p><br><br>
来源:https://www.cnblogs.com/shixinglong/p/12864420.html
頁: [1]
查看完整版本: 实战:云开发-实现奶茶店小程序(二)