小程序云函数接口设计 - 新闻资讯 - 云南小程序开发|云南软件开发|云南网站建设-昆明葵宇信息科技有限公司

159-8711-8523

云南网建设/小程序开发/软件开发

知识

不管是网站,软件还是小程序,都要直接或间接能为您产生价值,我们在追求其视觉表现的同时,更侧重于功能的便捷,营销的便利,运营的高效,让网站成为营销工具,让软件能切实提升企业内部管理水平和效率。优秀的程序为后期升级提供便捷的支持!

您当前位置>首页 » 新闻资讯 » 小程序相关 >

小程序云函数接口设计

发表时间:2021-1-5

发布人:葵宇科技

浏览次数:49

微信为小程序开发者提供了云函数,作为替代后台的一种serverless解决方案。微信云函数与微信小程序前端功能结合的很紧密,为开发者提供了相当大的便利。但云函数在设计、开发、调试上与传统的服务端开发有着许多不同。

我们的团队近期开发了一款小程序产品。为了更大程度的减少server的使用,业务后台完全使用云函数提供服务(主要是我们的团队没有专业后端-_-)。另外采用前端童鞋熟悉的eggjs+vue做了一套简易的管理后台,用来进行审核和数据管理工作。

系统分为小程序端,云函数端,web端。数据库使用云服务的数据库。

一、云函数调用通用接口定义

1、云端

我们在云端封装了一个通用的runFunc方法,所有的云函数都走这个方法然后再根据fname分别进入对应函数处理。

runFunc基本的入参:

属性含义
fname自定义云函数名
clientType小程序端/web端
version版本信息,用于必要时进行更新干预控制
token用户token
adminOpenIdweb端用户的openid不能自动获取,需要手动传入

runFunc基本的处理流程,会顺序进行一系列判断:

代码:

//cloudfunctions/runFunc/index.js
async function main(event, context) {
  let {fname, version, token, clientType, adminOpenId, ...opt} = event
  let result, role
  const { OPENID } = cloud.getWXContext()
  //先校验fname是否存在
  if(!fname){
    return {code: '0422', info:'参数错误'}
  }
  //getToken方法
  if(fname==='getToken'){
    return await getToken(cloud, db)
  }
  //小程序端,需要校验版本
  if(clientType===0){
    if (semver.lt(version, LOWEST_VERSION)) {
      return {code: '0602', info: '当前版本过低!'}
    }
    const authResult = authCheck(token)
    role = authResult.role
    //校验token
    if(!token || authResult.unauthorized) {
      return {code: '0401', info: '用户状态失效', data: {role}}
    }
  } else {
    role = opt.role
  }
  switch(fname){
    case 'getList':
      return await getList()
    //...
    default: 
      return {code: '0404', info:'未找到对应方法'}
  }
}
复制代码

2、小程序端

在小程序端,我们也对应封装一个云函数调用方法,可以集中处理标准的云函数调用错误。

遗憾的是,官方文档没有给出标准的云函数返回值定义,通过调试,发现云函数调用通常会抛出errCode, errMsg, requestId, result这些值,通常errCode为空时执行正常,但有些方法执行失败也不返回errCode(什么鬼……)在官方论坛上提问也没有得到回复,我们只能通过errCode || !/ok/.test(errMsg)来判断是不是执行失败了。

//miniprogram/utils/api.js/callCloud
const app = getApp()

async function callCloud(fname, opt){
  try{
    const {errCode, errMsg, requestId, result} = await wx.cloud.callFunction({
      name: 'runFunc',
      data: {
        fname,
        clientType: 0,
        version: app.globalData.appVersion,
        token: app.globalData.token, 
        ...opt
      }
    })
    if(errCode || !/ok/.test(errMsg)){
      wx.showToast({
        title: '服务器错误',
        icon: 'none',
        duration: 2000
      })
      return {code: '0700', info: '服务器繁忙', subcode: errCode, subinfo: errMsg}
    }
    else {
      const {code, info, data} = result
      if(code === '0401'){
        wx.showToast({
          title: info,
          duration: 2000
        })
      }
      if(code === '0602'){
        // todo: 做强制升级代码
        wx.showToast({
          title: info || '去强制升级',
          icon: 'none',
          duration: 2000
        })
      }
      if(code!=='0000'){
        wx.showToast({
          title: info || '错误',
          icon: 'none',
          duration: 2000
        })
      }
      return result
    }
  } catch(e){
    return {code: '0500', info: e.message || '服务器繁忙'}
  }
}

exports.callCloud = callCloud
复制代码

在page的js里,调用云函数:

//miniprogram/pages/demo/demo.js
const api = require('../../utils/api')
Page({
  data:{///...},
  onLoad: async function(){
  	const {code, info} = await api.callCloud('getList')
    //...
  }
})
复制代码

3、web端-接口

在web服务端,我们提供一个通用的调用云函数的route

//router.js
router.post('/call/:funcname', jwt, controller.home.callCloudFunction)
复制代码

在controller里我们发送如下的url请求:

POST https://api.weixin.qq.com/tcb/invokecloudfunction?access_token=ACCESS_TOKEN&env=ENV&name=FUNCTION_NAME
复制代码
//home.controller
const url = util.format('https://api.weixin.qq.com/tcb/invokecloudfunction?access_token=%s&env=%s&name=%s', access_token, cloudEnv, functionName)
const { data: optData, ...restOpt } = opt
const result = await ctx.curl(url, {
  method: 'POST',
  contentType: 'json',
  dataType: 'json',
  data: {
    clientType: 1,
    adminOpenId: ctx.state.user && ctx.state.user.openId ? ctx.state.user.openId : '',
    role: ctx.state.user && ctx.state.user.role ? parseInt(ctx.state.user.role) : 0,
    ...optData
  },
  ...restOpt
})
const { errcode, errmsg, resp_data } = result.data
/*
errcode为空,返回JSON.parse(resp_data)
errcode不为空,返回对应的errmsg
*/
复制代码

4、web端-前端

对应的,web前端的调用非常简单:

const {code, info, data} = await axios('/call/runFunc', { data: {fname: functionName, options} })
复制代码

这样我们保证云函数的数据结构,错误码在三端都是统一的。

二、关于用户状态

常规web项目或者app项目,都是用token来做用户登录状态保持。 在小程序里使用云函数的好处之一是无需自己写登录流程,小程序端的每个请求均可通过云端的cloud.getWXContext()方法均轻松获得用户的openid,如下:

const { OPENID } = cloud.getWXContext()
复制代码

那么在小程序项目是否只需要openid,不再需要token了呢?我依然建议通过token来标识用户的有效性,因为在token中可以携带用户角色\登录时间等信息,用于更精细的业务控制。

//云函数端
const token = encode({ t: now, r: role })
复制代码

用户进入首页后如果token不存在或失效,通过调用云端getToken方法进行更新token,同时在user表中新增或更新该用户的记录。

//小程序端,如果没有token,去取token
if(!app.globalData.token){
  app.globalData.token = await api.callCloud('getToken')
}
复制代码

api.callCloud就是前面所述的自定义云函数的通用方法。

//云端的getToken方法
async function getToken(cloud, db){
  try{
    const { OPENID } = cloud.getWXContext()
    let result = await db.collection('user').where({openId: OPENID}).get()
    //如果用户表中已经存在这个用户,更新用户的最近登录时间
    if(result.data && result.data.length>0){
        await db.collection('user').where({openId: OPENID}).update({
          data: {
            latestTime: now
          }
        })
        token = encode({ d: now, r: record.role })
        return {code: '0000', info: '成功', data: {token, openId: OPENID, role: record.role}}
      }
    } else{
      let record = {
        openId: OPENID,
        firstTime: now,
        latestTime: now,
        role: 0
      }
      //如果user标准没有这个用户,则新增一条用户记录
      await db.collection('user').add({
        data: record
      })
      token = encode({ d: now, r:0})
      return {code: '0000', info: '成功', data: {token, openId: OPENID, role: 0}}
    }
  }catch(e){
    return {code: '0500', info: e.message || '系统错误'}
  }
}
复制代码

web端可以通过小程序端扫码进行登录,这样就可以在web端也带入用户的openid,与小程序端使用同一套用户体系。


作者:dunhuang
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

相关案例查看更多