小程序云函数接口设计
发表时间:2021-1-5
发布人:葵宇科技
浏览次数:68
微信为小程序开发者提供了云函数,作为替代后台的一种serverless解决方案。微信云函数与微信小程序前端功能结合的很紧密,为开发者提供了相当大的便利。但云函数在设计、开发、调试上与传统的服务端开发有着许多不同。
我们的团队近期开发了一款小程序产品。为了更大程度的减少server的使用,业务后台完全使用云函数提供服务(主要是我们的团队没有专业后端-_-)。另外采用前端童鞋熟悉的eggjs+vue做了一套简易的管理后台,用来进行审核和数据管理工作。
系统分为小程序端,云函数端,web端。数据库使用云服务的数据库。
一、云函数调用通用接口定义
1、云端
我们在云端封装了一个通用的runFunc方法,所有的云函数都走这个方法然后再根据fname
分别进入对应函数处理。
runFunc基本的入参:
属性 | 含义 |
---|---|
fname | 自定义云函数名 |
clientType | 小程序端/web端 |
version | 版本信息,用于必要时进行更新干预控制 |
token | 用户token |
adminOpenId | web端用户的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
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。