微信雲開發試水

前言

微信小程序開發了雲開發的功能, 提供了數據庫、雲函數、儲存空間的服務。在此基礎上,基本能夠用js一把撈一個相對完整的服務,而且省下了發佈、部署、運維的繁瑣。css

優勢

語言同構、js一把撈, 對前端開發很是友好, 使用js就能夠完成整個業務流程。
前端

環境整合的很是好, 不用操心各類環境配置、數據庫、存儲空間、部署、運維的東西,減輕了開發復旦node

自帶登陸和帳號體系,方便接入git

缺點

過多的回調、異步致使代碼難看切難以維護、難以調試。github

雲函數的框架過於簡陋, 處理複雜業務的時候對代碼組織能力要求很高數據庫

數據庫多表聯查的時候很憂傷, 嚴重影響性能npm

沒有公共代碼塊, 沒法共享通用代碼, 難以整合組織代碼小程序

產品需求

通過考慮決定作一個小號的微博小程序, 梳理的功能以下微信小程序

後臺部分

根據官方的api文檔, 小程序的雲函數也是能夠經過http調用的,這就給一個獨立的後臺提供了技術接口。 咱們能夠經過http調用小程序雲函數來讀取和操做數據庫來實現產品後臺的功能,是否能夠經過htpps上傳圖片還待探索

用戶部分

用戶端的就是一個小號的微博了。 其中,微信提供了一個存儲空間,能夠方便的進行文件的存儲,帳號部分能夠直接經過微信的鑑權機制,使用openid做爲用戶id。

原型

設計

很少說, 上設計稿。 api

開發環境

其實上面的都不重要,坑都在這裏

1.更新開發工具, 擺脫回調

這裏要分兩部分說

  • 小程序部分
    在最新的微信開發工具中, 開發了加強編譯的功能, 已經支持async語法了。使用async的好處這裏就不在列舉了。爲了可使用async來調用微信原生的api。能夠引入一個工具, 將回調式的轉化爲async方式
function promisify (api) {
  return (options, ...params) => {
    return new Promise((resolve, reject) => {
      api(Object.assign({}, options, { success: resolve, fail: reject }), ...params);
    });
  }
}
複製代碼
// 這樣就能夠簡化微信原生的api
const getSetting = promisify(wx.getSetting)
const res = await getSetting()
複製代碼

可是async 語法也有很差的地方在於異常捕獲寫起來很難看, 越是複雜的邏輯, 用戶捕獲異常的catch就越很差處理, 這裏可使用await-to-js來進一步優化

import to from 'await-to-js'
// 使用node風格的異常捕獲
const [err, res] = await to(getSetting())
複製代碼
  • 雲函數部分
    雲函數是一個純淨的node環境, 使用的時候要注意它不支持import語法,在導入的時候須要使用require 例如 const to = require('await-to-js').default;
2.使用字體圖標代替切圖

小程序推薦使用rpx做爲尺寸單位, 切圖適配的時候, 小圖標可能會在縮放的時候失真,推薦使用字體圖標。將製做好的字體圖標放入小程序的css文件中就行了,我這裏是用的阿里icon的圖標庫, 將base64的css樣式直接放在全局的css中

3.開發工具。

微信開發工具自帶了編譯和沒法替代的雲開發工具, 目前是沒有辦法擺脫它了。 可是在編輯代碼的時候仍是能夠用其餘工具的。能夠用vscode做爲代碼編輯攻擊, 安裝minapp等插件。微信開發工具做爲預覽和調試的工具就好

4.npm支持,debuger

在雲函數中是支持直接使用npm包的, 可是每個雲函數的入口對應一個文件夾, 每個文件夾須要單獨npm。爲了能夠在本地斷點調試雲函數,咱們必須在雲函數下的每個文件夾install一下。這操做是真的難受。。。

在開發工具中, 右鍵雲函數文件夾能夠開啓本地調試, 而後就能夠在瀏覽器中打斷點了, 同時, console頁面也能夠看到雲函數的console信息, 注意右邊能夠切換請求方式。

代碼組織

先放一下數據庫的方案

前文說過, 雲函數每個文件夾視爲一個入口文件,而且須要單獨install一下。 咱們最少須要4張表,最起碼的增刪改查就須要16個接口, 一個接口一個入口顯然不可能, 因此咱們可能須要本身作一下路由的設計。
考慮到代碼的複用, 我將每個入口做爲一個功能塊, 將代碼的功能拆分紅service層、router層、controller層. 在小程序調用雲函數的時候傳入action字符串做爲路由的參數。將對數據庫的操做細化,做爲service便於複用。 在controller裏調用service來處理複雜邏輯。

1.根據數據庫操做編寫service

// user/user.js
// UserBase 肯定每一個集合的數據結構
class UserBase {
  constructor(data) {
    this.openId = data.openId
    this.avatarUrl = data.avatarUrl
    this.city = data.city
    this.country = data.country
    this.gender = data.gender
    this.language = data.language
    this.nickName = data.nickName
    this.province = data.province
    this.signature = null // 簽名 
    this.watchList = [] // 關注列表
    this.releaseList = [] // 發佈列表
    this.createTime = db.serverDate()
    this.userStatus = 0 // 用戶狀態 0 正常 1 限制登陸
    this.inviteUser = null
  }
}
複製代碼
// 這裏用來實現對數據庫的操做
class User {
  // 獲取用戶信息
  async getUserInfo(openId) {
    const [err, {data}] = await to(db.collection('user')
      .where({ openId: openId })
      .get())
    if (err) return Promise.reject(err.errMsg)
    if (Array.isArray(data) && data.length) return data[0]
    else return null
  }

  // 建立新用戶
  async createUser (userInfo) {
    let [err, res] = await to(db.collection('user').add({ data: new UserBase(userInfo)}))
    if (err) return Promise.reject(err.errMsg)
    return res._id
  }
}
複製代碼

2.根據需求調用service的方法, 實現業務

Controller能夠經過繼承也能夠單獨引入進來運行

// user/userController.js 這裏調用service來處理複雜邏輯
// 根據狀況也會調用其餘的模塊, 這個時候就用callFunction
// 雲函數入口文件
const User = require('./user.js')
const cloud = require('wx-server-sdk')
cloud.init({ env: 'prod-4ygqk'})
const to = require('await-to-js').default;

class UserController extends User {
  // 處理登陸
   async handelLogin(event) {
    const { OPENID } = event
    const [err, res] = await to(this.getUserInfo(OPENID))
    if (err) return {err, res}
    if (res) return {err, res}
    if (!event.loginInfo) {
      return {err: '註冊用戶失敗,請先調用wx.userInfo接口', res}
    }
    event.loginInfo.openId = OPENID
    return this.handelCreateUser(event)
  }

  // 註冊用戶
   async handelCreateUser(event) {
    const { loginInfo, OPENID } = event
    const [err, res] = await to(this.createUser(loginInfo))
    if (err) return {err, res}
    const [_err, _res] = await to(this.getUserInfo(OPENID))
    if (_err) return {err, res: _res}
    return {err, res: _res}
  }

  // 經過openId查詢用戶
  async queryUserByOpenid(event) {
    const { targetUser, OPENID} = event
    // 若是沒有傳openId進來, 就查詢當前用戶
    const seatchOpenId = targetUser || OPENID
    const [err, res] = await to(this.getUserInfo(seatchOpenId))
    if (err) return {err, res}
    // 查詢是否關注過該用戶
    if (res && targetUser) {
      const [error,{result}] = await to(cloud.callFunction({
        name: 'attention',
        data: {
          action: 'queryIsAttention',
          currentUser: OPENID,
          targetUser: targetUser
        }
      }))
      if (error) return {err: error, res: result}
      res.isAttention = result.res
    }
    return {
      res: res,
      err: null
    }
  }
}

module.exports = UserController
複製代碼

3.根據路由指派控制器

這裏咱們將openId做爲用戶識別碼掛載在event上, 供控制器使用 在Controller須要調用其餘模塊的Controller時, 也將所需的參數掛載在event上。 這裏的event實際上就是此次請求的上下文

// 雲函數入口文件 user/index.js
const cloud = require('wx-server-sdk')
cloud.init({ env: 'prod-4ygqk'})
const UserController = require('./userController.js')


// 雲函數入口函數
exports.main = async (event, context) => {
  const wxContext = cloud.getWXContext()
  const { OPENID, APPID } = wxContext
  event.OPENID = OPENID

  const user = new UserController()
  const { action } = event

  switch (action) {
    case 'login': {
      return user.handelLogin(event)
    };
    case 'createUser': {
      return user.handelCreateUser(event)
    };
    case 'queryUserByOpenid': {
      return user.queryUserByOpenid(event)
    };
  }
}
複製代碼

4.其餘複雜狀況

  • 須要調用其餘模塊的
async queryArticlebyId(event) {
   const { OPENID, id } = event
   // 查詢對應的文章內容
   let [err, res] = await to(article.queryArticlebyId(id))
   // 有錯誤返回錯誤, 沒有查到返回空
   if (err) return {err, res}
   if (!res) return {err, res}
   // 查詢文章用戶, 調用user模塊
   const [error, { result }] = await to(cloud.callFunction({
     name: 'user',
     data: {
       action: 'queryUserByOpenid',
       currentUser: event.OPENID,
       targetUser: res.openId
     }
   }))
   // 調用userInfo錯誤
   if (error) return {err: error, res}
   // userInfo本身的錯誤
   if (result.err) return {err: result.err, res: res}
   res.user = result.res

   // 查詢用戶是否已經收藏, 調用收藏模塊
   const [collectionErr, collectionRes] = await to(article.queryIsCollection(OPENID, id))
   res.isCollection = collectionRes
   return {res, err: null}
 }
複製代碼
  • 須要異步循環的
    異步迭代一直是一個問題。一方面涉及到併發和效率, 另外一方面又涉及到異常處理。 若是按照線性的async方式查詢會嚴重的影響到速度。 因此必須使用Promise.all的方式。 如下代碼缺乏異常處理
async queryArticleAll(event) {
    const { userInfo, size = 10, page, sort = 'desc', orderBy = 'createTime' } = event
    let ArticleList = await article.queryArticleAll({ size, page, sort, orderBy })
    if (ArticleList.length === 0) {
      return []
    }

    const funList = ArticleList.map(async article => {
      // 查詢文章的做者
      const [err,{result}] = await to(cloud.callFunction({
        name: 'userInfo',
        data: {
          action: 'queryUserByOpenid',
          currentUser: event.OPENID,
          targetUser: article.openId
        }
      }))
      let user = result.res || null
      article.user = user
      return article
    }) 

    return {res: await Promise.all(funList), err: null}
  }

複製代碼

最後

其餘就沒有啥子東西可寫了。後面使用http調用雲函數的之後再繼續嘗試。 在調試雲函數的時候幾乎花掉了所有的耐心....最後貼代碼和地址

https://github.com/xanggang/picture-miniapp
複製代碼

最近微信關閉了更名的通道, 暫時改不了名字和頭像了...

相關文章
相關標籤/搜索