小程序云开发云功能路由高级玩法
李成喜,腾讯云高级工程师。2014年毕业于腾讯AlloyTeam,负责QQ群、直播秀、腾讯文档等项目。2018年加入腾讯云韵发展团队。专注于性能优化、工程和小程序服务。微博|知乎|Github
概念回顾
在掘金开发者大会上,在推荐实践中,我提到了一个云功能的用法。我们可以根据业务的相似性,将用户管理、支付逻辑等相同的操作归类到一个云功能中,更便于管理、故障排除和逻辑共享。即使你的小程序后台逻辑不复杂,请求量也不是特别大,你也可以在云函数中做一个单独的微服务,按照路由处理任务。
可以用下面三个图来概括。让我们回顾一下:
例如,这里是云功能的传统用法,其中云功能处理任务并且高度解耦。
第二个架构图是对请求进行分类的尝试。云功能处理特定类型的请求,例如专用于处理用户或支付的云功能。
最后一张图显示这里只有一个云功能,云功能中有一个分配任务的路由管理,将不同的任务分配给不同的本地功能。
tcb-router介绍及用法
为了您的方便,我们腾讯云基地团队开发了tcb-router,这是一个云功能路由管理库,方便您使用。
如何使用tcb-router实现上述架构?我会一一举例。
架构1:在这个架构中,云功能处理一个任务。事实上,没有必要使用tcb-router。照常写云函数,然后在小程序端调用。
cloud//function routerexports . main=(事件,上下文)={返回{代码: 0,消息: ' success ' };};applet wx.cloud.call函数({name :' router ',data 3360 {name 3360' TCB ',company :' Tencent'}})。然后((RES)={ console . log(RES);}).catch((e)={ console . log(e));});架构2:根据请求对云功能进行分类。这种架构是将类似的请求分类到同一个云功能中,比如可以分为用户管理、支付等云功能。
cloud//函数payconst TcbRouter=
require('tcb-router');exports.main = async (event, context) => { const app = new TcbRouter({ event }); app.router('makeOrder', async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: 'make order success' } }); app.router('pay', async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: 'pay success' } }); return app.serve();};- 小程序端
// 注册用户wx.cloud.callFunction({ name: 'user', data: { $url: 'register', name: 'tcb', password: '09876' } }).then((res) => { console.log(res); }).catch((e) => { console.log(e);});// 下单商品wx.cloud.callFunction({ name: 'pay', data: { $url: 'makeOrder', id: 'xxxx', amount: '3' } }).then((res) => { console.log(res); }).catch((e) => { console.log(e);});
架构三:由一个云函数处理所有服务
- 云函数
// 函数 routerconst TcbRouter = require('tcb-router');exports.main = async (event, context) => { const app = new TcbRouter({ event }); app.router('user/register', async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: 'register success' } }); app.router('user/login', async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: 'login success' } }); app.router('pay/makeOrder', async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: 'make order success' } }); app.router('pay/pay', async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: 'pay success' } }); return app.serve();};
- 小程序端
// 注册用户wx.cloud.callFunction({ name: 'router', data: { $url: 'user/register', name: 'tcb', password: '09876' } }).then((res) => { console.log(res); }).catch((e) => { console.log(e);});// 下单商品wx.cloud.callFunction({ name: 'router', data: { $url: 'pay/makeOrder', id: 'xxxx', amount: '3' } }).then((res) => { console.log(res); }).catch((e) => { console.log(e);});
借鉴 Koa2 的中间件机制实现云函数的路由管理
小程序·云开发的云函数目前更推荐async/await的玩法来处理异步操作,因此这里也参考了同样是基于async/await的 Koa2 的中间件实现机制。
从上面的一些例子我们可以看出,主要是通过use和router两种方法传入路由以及相关处理的中间件。
use只能传入一个中间件,路由也只能是字符串,通常用于 use 一些所有路由都得使用的中间件
// 不写路由表示该中间件应用于所有的路由app.use(async (ctx, next) => {});app.use('router', async (ctx, next) => {});router 可以传一个或多个中间件,路由也可以传入一个或者多个。app.router('router', async (ctx, next) => {});app.router(['router', 'timer'], async (ctx, next) => { await next();}, async (ctx, next) => { await next();}, async (ctx, next) => {});
不过,无论是use还是router,都只是将路由和中间件信息,通过_addMiddleware和_addRoute两个方法,录入到_routerMiddlewares该对象中,用于后续调用serve的时候,层层去执行中间件。
最重要的运行中间件逻辑,则是在serve和compose两个方法里。
serve里主要的作用是做路由的匹配以及将中间件组合好之后,通过compose进行下一步的操作。比如以下这段节选的代码,其实是将匹配到的路由的中间件,以及*这个通配路由的中间件合并到一起,最后依次执行。
let middlewares = (_routerMiddlewares[url]) ? _routerMiddlewares[url].middlewares : [];// put * path middlewares on the queue headif (_routerMiddlewares['*']) { middlewares = [].concat(_routerMiddlewares['*'].middlewares, middlewares);}
组合好中间件后,执行这一段,将中间件compose后并返回一个函数,传入上下文this后,最后将this.body的值resolve,即一般在最后一个中间件里,通过对ctx.body的赋值,实现云函数的对小程序端的返回:
const fn = compose(middlewares);return new Promise((resolve, reject) => { fn(this).then((res) => { resolve(this.body); }).catch(reject);});
那么compose是怎么组合好这些中间件的呢?这里截取部份代码进行分析
function compose(middleware) { /** * ... 其它代码 */ return function (context, next) { // 这里的 next,如果是在主流程里,一般 next 都是空。 let index = -1; // 在这里开始处理处理第一个中间件 return dispatch(0); // dispatch 是核心的方法,通过不断地调用 dispatch 来处理所有的中间件 function dispatch(i) { if (i <= index) { return Promise.reject(new Error('next() called multiple times')); } index = i; // 获取中间件函数 let handler = middleware[i]; // 处理完最后一个中间件,返回 Proimse.resolve if (i === middleware.length) { handler = next; } if (!handler) { return Promise.resolve(); } try { // 在这里不断地调用 dispatch, 同时增加 i 的数值处理中间件 return Promise.resolve(handler(context, dispatch.bind(null, i + 1))); } catch (err) { return Promise.reject(err); } } }}
看完这里的代码,其实有点疑惑,怎么通过Promise.resolve(handler(xxxx))这样的代码逻辑可以推进中间件的调用呢?
首先,我们知道,handler其实就是一个async function,next,就是dispatch.bind(null, i + 1)比如这个:
async (ctx, next) => { await next();}
而我们知道,dispatch是返回一个Promise.resolve或者一个Promise.reject,因此在async function里执行await next(),就相当于触发下一个中间件的调用。
当compose完成后,还是会返回一个function (context, next),于是就走到下面这个逻辑,执行fn并传入上下文this后,再将在中间件中赋值的this.bodyresolve出来,最终就成为云函数数要返回的值。
const fn = compose(middlewares);return new Promise((resolve, reject) => { fn(this).then((res) => { resolve(this.body); }).catch(reject);});
看到Promise.resolve一个async function,许多人都会很困惑。其实撇除next这个往下调用中间件的逻辑,我们可以很好地将逻辑简化成下面这段示例:
let a = async () => { console.log(1);};let b = async () => { console.log(2); return 3;};let fn = async () => { await a(); return b();};Promise.resolve(fn()).then((res) => { console.log(res);});// 输出// 1// 2// 3
版权声明:小程序云开发云功能路由高级玩法是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。