cmd-cloud-router
基于 koa 的云函数路由中间件,提供 RESTful 资源路由
介绍
简易 koa 洋葱圈模型执行,koa-router 路由风格编写云函数。
- 每个云函数是一个独立进程,使用路由分为多部分
- 为支持路由参数使用
path-to-regexp函数库解析参数 - 函数需要使用
nodejs12版本以上 - 没有封装
uniCloud函数,需要自己声明引入
提醒
- 云函数:请求共用会导致其他请求连接超时,所以不建议一个云函数里写太多业务路由,分为按模块资源单函数可避免请求压力。
- 云函数 URL 化:腾讯云免费服务空间最多只支持配置 10 个云函数 URL 化地址。
函数-API
应用对象(Application)
Application 是请求级别的全局应用对象,监听云函数执行。基于洋葱圈模型,请求从第一个中间件开始并按其方式顺序运行。
| 函数 | 名称 | 说明 |
|---|---|---|
use | 使用中间件 | 使用给定中间件加入程序执行过程 |
listen | 监听程序请求 | 将云函数请求数据加入到应用程序对象 |
客户端调用云函数时传入的参数
| 参数 | 解释 |
|---|---|
url | 必传参数,请求已定义的路由路径 |
method | 默认参数"GET",路由路径指定的请求方法 |
data | 可选参数,传入可能需要的参数数据 |
使用示例
const { Application } = require("cmd-cloud-router");
const app = new Application();
// 使用中间件
app.use(async (ctx, next) => {
const start = Date.now();
console.log(`开始计时`);
let sum = 0;
for (let i = 0; i < 99; i++) {
for (let j = 0; j < i; j++) {
sum += j;
}
}
await next();
// 响应结果要用 ctx.body
ctx.body = `求和:${sum}`;
const ms = Date.now() - start;
console.log(`结束耗时 ==> ${ms}ms`);
});
// 对请求参数监听 返回Promise对象
app
.listen(
{
url: "/index",
method: "post",
data: { value: 99 },
},
"<传入客户端云函数请求环境>"
)
.then(res => {
console.log("响应ctx.body的结果:", res);
})
.catch(err => {
console.log("错误结果:", e);
});
上下文 Context
Context 是请求级别的对象,封装了请求的信息,对每一次请求都会重新初始上下文参数属性。
请求上下文对象信息
{
/**客户端调用云函数时传入的参数 */
"event": "object",
/**客户端调用云函数时的系统运行环境参数 */
"app": "object",
/**客户端请求传入的参数*/
"data": "object",
/**请求资源 */
"url": "string",
/**url路径 */
"path": "string",
/**请求方法 */
"method": "string",
/**url查询字符串 */
"querystring": "string",
/**url查询参数 */
"query": "object",
/**url路由参数 */
"params": "object",
/**抛出异常的函数 */
"throw": "(message:string,code?:string | number,data?:any) => {}",
/**响应结果 */
"body": "?any",
/**配置参数 */
"config": "?object"
}
throw: 程序异常抛出函数,信息通过 catch 输出信息event和app: 都是客户端调用云函数是传入的参数body: 程序执行后通过该属性返回结果config: 可选的请求级配置参数
获取云函数调用来源
封装的上下文属性app接管云函数context的云函数的入参
通过上下文 app.SOURCE 返回云函数调用来源,它的值域为:
| 取值 | 解释 |
|---|---|
client | 客户端 callFunction 方式调用 |
http | 云函数 url 化方式调用 |
timing | 定时触发器调用 |
function | 由其他云函数 callFunction 调用 |
server | 由 uniCloud 管理端调用,HBuilderX 里上传并运行 |
中间件 Middleware
Middleware 是请求级别的函数,在程序执行过程前或后加入处理逻辑,实现程序拦截过滤等功能。
module.exports = async (ctx, next) => {
console.time("logger");
// 中间件放行
await next();
// 挂上任意属性参数
ctx.logger = "日志中间件";
console.timeEnd("logger");
};
- 中间件必须是异步函数,确保执行结果符合洋葱模型
- 可以将中间件内处理的数据挂载到上下文上,传递到下一函数
next(): 中间件放行通过,如果没有触发该函数,会终止程序并返回上下文body属性的结果
路由对象 Router
Router 路由命名 URL 路由规则风格,支持 http 方法声明路由,可以只针对单个路由使用中间件,处理每一次请求。
| 函数 | 名称 | 说明 |
|---|---|---|
register | 自定义方法 | 创建并注册路由 |
get | get 方法 | 使用 get 方法注册路由 |
post | post 方法 | 使用 post 方法注册路由 |
put | put 方法 | 使用 put 方法注册路由 |
delete | delete 方法 | 使用 delete 方法注册路由 |
routes | 路由器中间件 | 中间件调度与请求匹配的路由 |
云函数调用来源为 timing 定时触发器会将 cron 表达式转换为 PUT 方法路由(hex 编码化)
转换例如 cron:0 0 * * * * => 63726f6e3a302030202a202a202a202a
使用示例
const { Router } = require("cmd-cloud-router");
const router = new Router();
// 使用指定方法路由
// router.["get" | "post" | "put" | "delete"](<URL路径>, <函数>, <中间件1>, <中间件2>);
// router.["get" | "post" | "put" | "delete"](<URL路径>, <函数>, [<中间件1>, <中间件2>]);
router.post(
"/index",
async ctx => {
ctx.body = "成功";
},
[
async (ctx, next) => {
await next();
},
]
);
// 使用自定义方法路由
// router.register(<请求方法>, <URL路径>, <函数>, <中间件1>, <中间件2>);
// router.register(<请求方法>, <URL路径>, <函数>, [<中间件1>, <中间件2>]);
router.register(
"post",
"/index",
async ctx => {
ctx.body = "成功";
},
async (ctx, next) => {
await next();
}
);
// 云函数调用来源为 timing 定时cron表达式转换路由 hex编码化
const cronPath = Buffer.from("cron:0 0 * * * *").toString("hex");
router.put(cronPath, async ctx => {
console.log("执行 ==> %s - %s <==", ctx.method, ctx.path);
// 定时任务没有需要响应的 ctx.body
});
// 返回路由中间件,要使用应用程序use注册到中间件里
router.routes();
实战示例
创建云函数
下载到公共模块依赖后,按照官方文档配置模块
公共模块文档: uniapp.dcloud.net.cn/uniCloud/cf…
package.json
package.json 文件里不能放注释 //,记得删除干净。
{
"name": "<你的云函数名>",
"version": "1.0.0",
"description": "<你的云函数服务说明>",
"main": "index.js",
"author": "https://ask.dcloud.net.cn/people/TsMask",
"license": "MIT",
"dependencies": {
// 引入依赖库 cmd-cloud-router 放公共模块里
"cmd-cloud-router": "file:../common/cmd-cloud-router"
},
// 相关配置查看 https://uniapp.dcloud.net.cn/uniCloud/cf-functions.html#packagejson
"cloudfunction-config": {
"memorySize": 512, // 最大可用内存
"timeout": 30, // 超时时间
"runtime": "Nodejs12" // 运行环境必须要 Nodejs12
}
}
index.js
"use strict";
const { Application, Router } = require("cmd-cloud-router");
const app = new Application();
const router = new Router();
// 中间件1
app.use(async (ctx, next) => {
const start = Date.now();
console.log(`1开始计时`);
await next();
const ms = Date.now() - start;
console.log(`4结束耗时 ==> ${ms}ms`);
});
// 中间件2
app.use(async (ctx, next) => {
let sum = 0;
for (let i = 0; i < 99; i++) {
for (let j = 0; j < i; j++) {
sum += j;
}
}
console.log("2循环求和 %d", sum);
await next();
let ak = 0;
for (let i = 1; i <= 4; i++) {
for (let j = 1; j <= i; j++) {
ak += j;
}
}
console.log("3循环求和 %d", ak);
});
// 自定义方法 { url: '/user?id=28393&sort=desc', method: 'get', data: {} }
router.register("get", "/user", async ctx => {
console.log(ctx.querystring);
console.log(ctx.query);
await Promise.resolve(() => 1);
ctx.body = ["1", "2", ctx.query.id];
});
// get方法 { url: '/user/28393', method: 'get', data: {} }
router.get("/user/:id", async ctx => {
console.log(ctx.params);
await Promise.resolve(() => 1);
ctx.body = "查询用户ID:" + ctx.params.id;
});
// post方法 { url: '/user/28393', method: 'post', data: { value: 99 } }
router.post(
"/user/:id",
async ctx => {
console.log("路由参数", ctx.params);
console.log("请求数据", ctx.data);
await Promise.resolve(() => 1);
ctx.body = { post: ctx.path, ...ctx.data };
},
async (ctx, next) => {
console.log("路由前数据处理中间件", ctx.params);
// 中间件放行
await next();
console.log("路由后数据处理中间件", ctx.params);
}
);
// put方法 { url: '/user/28393/age', method: 'put', data: { value: 99 } }
router.put("/user/:id/:flag(none|avatar|age)", async ctx => {
console.log(ctx.params);
console.log(ctx.data);
await Promise.resolve(() => 1);
ctx.body = "修改用户ID:" + ctx.params.id;
});
// delete方法 { url: '/user/28393', method: 'delete', data: {} }
router.delete("/user/:id", async ctx => {
console.log(ctx.params);
await Promise.resolve(() => 1);
ctx.body = "删除ID:" + ctx.params.id;
});
// 使用路由中间件
app.use(router.routes());
const config = {
name: "测试配置",
value: "1.0.0",
};
exports.main = async (event, context, config) => {
// 对请求参数监听,返回Promise对象结果直接return
try {
const res = await app.listen(event, context);
console.log("响应ctx.body的结果:", res);
} catch (e) {
console.log("错误结果:", e);
}
//event为客户端上传的参数
console.log("event : ", event);
//返回数据给客户端
return event;
};
客户端使用
// ...
methods: {
// 云函数客户端
fnCall() {
uniCloud.callFunction({
name: '<你的云函数名>',
data: {
// data里是调用云函数需要的参数
url: '/user/28393',
method: 'post',
data: {
value: 99
}
}
}).then((res) => {
console.log(res)
}).catch((err) => {
console.error(err)
})
},
// 云函数URL化
fnUrl() {
uni.request({
method: 'POST',
url: '<你的云函数URL化配置的PATH完整链接地址>',
data: {
// data里是调用云函数需要的参数
url: '/user/28393',
method: 'post',
data: {
value: 99
}
},
success(res) {
console.log(res);
},
fail(err) {
console.error(err)
}
});
}
}
// ...