UNPKG

explain-unicloud

Version:
888 lines (755 loc) 25.2 kB
# EXPLAIN-UNICLOUD 快速、极简的uniCloud云函数开发框架。已支持云函数URL化,已支持RESTful。 框架交流QQ群:[970799055](https://jq.qq.com/?_wv=1027&k=KFkDL5gp)。 ## 示例项目目录结构 ``` ┌── cloudfunctions 云函数目录 | ├── common 云函数公共模块 | | └── explain-unicloud 核心框架 | | └── explain-validator 数据校验器模块 | | └── explain-unicloud-mvc MVC模块 | ├── app 普通应用目录 | | ├── index.js 云函数入口 | | ├── startup.js 启动配置、路由配置、中间件、过滤器配置等 | | ├── services 云函数集 | | ├── filters 过滤器 | | ├── schemas JSON Schema目录 | | └── views 视图页 | └── mvc MVC应用目录 | ├── index.js 云函数入口 | ├── startup.js 启动配置、路由配置、中间件、过滤器配置等 | ├── models 模型 | ├── views 视图 | └── controllers 控制器 ├── js_sdk | └── explain-creq 云函数和http请求模块 └── common ├── creq.interceptor.callfunction 云函数请求拦截器配置 └── creq.interceptor.request http请求拦截器配置 ``` ## 使用方式 ### 匹配模式 |参数|类型|必填|说明| |-- |-- |-- |-- | |service/serviceKey|String|是|需要指向的service的,若参数名与业务参数冲突可通过`app.init`配置`serviceKey`改变参数名称| |action/actionKey|String|是|需要指向的action的,若参数名与业务参数冲突可通过`app.init`配置`actionKey`改变参数名称| |data/dataKey|String|是|请求包含的参数,若参数名与业务参数冲突可通过`app.init`配置`dataKey`改变参数名称| #### 云函数入口 ```javascript // app -> index.js const explain = require("explain-unicloud"); exports.main = async (event, context) => explain.run({ event, context, (app) => { app.init({baseDir: __dirname}); } }); ``` #### 云函数 ```javascript // app -> services -> values.js // 继承抽象父类explain.service,无需写构造函数即可调用this.event,this.context,this.explain const explain = require("explain-unicloud"); module.exports = class home extends explain.service { index(data) { return 'Hello, World!'; } } ``` #### 前端 ```javascript uniCloud.callFunction({ name: 'app', data: { service: 'values', action: 'getValuesAsync', data: {} } }).then(res => { console.log(res) }) ``` 推荐使用`explain-creq`,封装了请求拦截器,响应拦截器和错误拦截器,可自由定制请求数据处理和响应结果处理 ```javascript import creq from '@/uni_modules/explain-creq/js_sdk/explain-creq.js' creq.callFunction('values', 'getValuesAsync', { name: 'app', // 指定云函数名称 data: {} // 请求参数 }).then(res => { console.log(res) }) ``` ### 路由模式 #### 云函数入口 ```javascript // app -> index.js const explain = require("explain-unicloud"); exports.main = async (event, context) => explain.run({ event, context, (app) => { app.init({baseDir: __dirname}); // 批量添加路由 app.route.add([{ route: "api/values", // 可省略,省略后路由模板为service,也就是"values" service: "values", // services目录下的service文件名 routes: [ // GET api/values 获取列表数据 { // route: "", // 路由模板与service一致则可省略 // httpMethod: "GET", // action名称以对应RESTful动词开头则可省略 action: "getValuesAsync" // service中被调用的方法名称 }, // GET api/values/5 获取id为5的数据,{id}会自动解析为参数注入event.data中 { route: "{id}", // 将动态参数作为路由 // httpMethod: "GET", // action名称以对应RESTful开头则可省略 action: "getValueAsync" }, // POST api/values 新增一条数据 { // route: "", // 路由模板与service一致则可省略 // httpMethod: "POST", // action名称以对应RESTful开头则可省略,除此之外还支持以create、add、insert开头 action: "postValueAsync" }, // PUT api/values/5 更新id为5的数据,{id}会自动解析为参数注入event.data中 { route: "{id}", // httpMethod: "PUT", // action名称以对应RESTful开头则可省略,除此之外还支持以update开头 action: "putValueAsync" }, // DELETE api/values/5 删除id为5的数据,{id}会自动解析为参数注入event.data中 { route: "{id}", // httpMethod: "DELETE", // action名称以对应RESTful开头则可省略,除此之外还支持以remove开头 action: "deleteValueAsync" }, // GET api/values/exists/5 获取id为5的数据是否存在,{id}会自动解析为参数注入event.data中 { route: "exists/{id}", httpMethod: "GET", action: "checkValueIsExistsAsync" }, // GET api/values/new/10 获取type为new,column为10的数据,{type}、{column}会自动解析为参数注入event.data中 { route: "{type}/{column}", httpMethod: "GET", action: "getValuesAsync" }, // GET api/values/2020/02/28 获取year为2020,month为02,day为28的数据,{year}、{month}、{day}会自动解析为参数注入event.data中 { route: "{year}/{month}/{day}", httpMethod: "GET", action: "getValueByYearAndMonthAndDayAsync" }, // GET api/values/2020-02-28 获取year为2020,month为02,day为28的数据,{year}、{month}、{day}会自动解析为参数注入event.data中 { route: "{year}-{month}-{day}", httpMethod: ["GET"], action: "getValueByYearAndMonthAndDayAsync" }, // GET api/values/date-2020-02-28/time:19:30:00 获取year为2020,month为02,day为28,hour为19,minute为30,second为00的数据,{year}、{month}、{day}、{hour}、{minute}、{second}会自动解析为参数注入event.data中 { route: "date-{year}-{month}-{day}/time:{hour}:{minute}:{second}", httpMethod: ["GET"], action: "getValueByYearAndMonthAndDayAsync" } ] }, { route: "api/test", service: "test", routes: [{ route: "checktoken", httpMethod: ["GET", "POST"], action: "checkToken" }] }]); // 添加单个路由,此处将服务端渲染html作为示例 app.route.add({ // 云函数URL化后访问:https://云函数域名/请求路径/index.html route: "index.html", service: "home", routes: [{ action: "index", httpMethod: ["GET"] }] }); // 设置根路由,设置后云函数URL化不用输入路由可到达指定方法 app.route.add([{ route: "/", service: "home", routes: [{ action: "index", httpMethod: ["GET"] }] }, { route: "/", service: "values", routes: [{ action: "postValueAsync", httpMethod: ["POST"] }, { action: "putValueAsync", httpMethod: ["PUT"] }, { action: "deleteValueAsync", httpMethod: ["DELETE"] }] }]); } }); ``` #### 云函数 ```javascript // app -> services -> values.js // 继承抽象父类explain.service,无需写构造函数即可调用this.event,this.context,this.explain const explain = require("explain-unicloud"); module.exports = class values extends explain.service { async getValuesAsync() { return { data: ["explain-unicloud", "explain-admin"], message: "获取成功" } } async getValueAsync({ id }) { return { data: { id, name: "Sansnn" }, message: "获取成功" } } async postValueAsync({ name, like }) { return { data: { id: 9, name, like }, message: "添加成功" } } async putValueAsync({ id, name, like }) { return { data: { id, name, like }, message: "更新成功" } } async deleteValueAsync({ id }) { return { data: { id }, message: "删除成功" } } async checkValueIsExistsAsync({ id }) { return { data: { id, checked: true }, message: "检查成功" } } async getValueByYearAndMonthAndDayAsync({ year, month, day }) { return { data: { date: `${year}-${month}-${day}` }, message: "获取成功" } } } 更多示例请前往目录uniCloud -> cloudfunctions -> app -> services中查看 ``` #### 前端 ```javascript uni.request({ url: 'http://tcb-e386czuna1dv2wib7e6bd-d064f3.service.tcloudbase.com/http/app/api/values', method: 'post', data: { name: 'Sansnn', like: ['explain-unicloud', 'explain-admin'] }, success: res => { console.log(res) } }) ``` 推荐使用`explain-creq`,封装了请求拦截器,响应拦截器和错误拦截器,可自由定制请求数据处理和响应结果处理 ```javascript import creq from '@/uni_modules/explain-creq/js_sdk/explain-creq.js' creq.request('http://tcb-e386czuna1dv2wib7e6bd-d064f3.service.tcloudbase.com/http/app/api/values', 'post', { data: { name: 'Sansnn', like: ['explain-unicloud', 'explain-admin'] } }).then(res => { console.log(res) }) 更多示例请前往目录pages -> route -> route.vue中查看 ``` ## 云函数URL化(仅支持路由模式) #### 示例 > 腾讯云 GET http://${spaceId}.service.tcloudbase.com/${path}/api/values?a=1&b=2 > 阿里云 GET https://${spaceId}.bspapp.com/${path}/api/values?a=1&b=2 > 完整参考 GET http://tcb-e386czuna1dv2wib7e6bd-d064f3.service.tcloudbase.com/http/app/api/values ## 使用中间件 执行顺序为使用中间件时的顺序,先使用的先执行,后使用的后执行。 ### app.use() ### 示例 ```javascript // app -> startup.js module.exports = (app) => { app.init({ baseDir: __dirname }); // 使用中间件示例 app.use(async ({ event, context, explain, next }) => { // 异常处理中间件 try { console.log("m1-0") await next(); console.log("m1-1") } catch (e) { // 将响应信息改为异常信息 explain.response.body = { message: e.message + (explain.request.service === "test" && explain.request .action === "exception" ? ",经过了异常处理中间件" : "") } } }); app.use(async ({ next }) => { console.log("m2-0") await next(); console.log("m2-1") }); app.use(async ({ next }) => { console.log("m3-0") await next(); console.log("m3-1") }); } ``` ## 使用过滤器 执行顺序为注册过滤器时的顺序,先注册的先执行,后注册的后执行。 ### onActionExecuting 方法执行时触发。 ### 示例:实现用户身份验证 #### 云函数入口 ```javascript // app -> startup.js module.exports = (app) => { app.init({ baseDir: __dirname }); // 使用过滤器示例 app.filter.add([{ filter: require("./filters/tokenFilter"), // 添加身份验证过滤器 // 忽略一下service和action ignore: [{ service: "values" }, { service: "home" }, { service: "test", actions: ["exception"] }] }]); } ``` #### 过滤器代码 ```javascript // app -> filters -> tokenFilter.js // 需要继承抽象父类explain.filter const explain = require("explain-unicloud"); module.exports = class tokenFilter extends explain.filter { async onActionExecuting() { let { explain, context } = this; if (!explain.request.data.token) { // 使用explain.response.body直接返回至客户端 explain.response.body = { code: 401, message: "缺少token" } return; } // let user = checkToken(event.request.data.token); // 自行封装的token验证方法 let user = explain.request.data.token == "Sansnn" ? { id: 1, name: "Sansnn" } : null; if (user) { context.user = user; } else { explain.response.body = { code: 401, message: "token无效" } } } } ``` #### 云函数 ```javascript // app -> services -> test.js const explain = require("explain-unicloud"); module.exports = class test extends explain.service { async checkToken() { return { checked: true, data: this.context.user // 得到根据token解析出来的用户对象 } } } ``` ### onActionExecuted 方法执行后触发。 ### 示例:实现请求日志记录 #### 云函数入口 ```javascript // app -> startup.js module.exports = (app) => { app.init({ baseDir: __dirname }); // 使用过滤器示例 app.filter.add([{ filter: require("./filters/requestFilter"), // 添加请求记录过滤器 ignore: [{ service: "home", actions: ["index"] }] }]); } ``` #### 过滤器代码 ```javascript // app -> filters -> requestFilter.js // 需要继承抽象父类explain.filter const explain = require("explain-unicloud"); module.exports = class requestFilter extends explain.filter { async onActionExecuting() { let { explain } = this; console.log("------------"); console.log("请求开始"); if (explain.request.service) { console.log(`service: ${explain.request.service}`); } if (explain.request.action) { console.log(`action: ${explain.request.action}`); } if (explain.request.route) { console.log(`route: ${explain.request.route}`); } if (explain.request.routeTemplate) { console.log(`routeTemplate: ${explain.request.routeTemplate}`); } if (explain.request.httpMethod) { console.log(`httpMethod: ${explain.request.httpMethod}`); } if (explain.request.data) { console.log(`data: ${JSON.stringify(explain.request.data)}`); } console.log("------------"); console.log(this.explain) } async onActionExecuted() { console.log(this.explain) let { explain } = this; console.log("------------"); console.log("请求结束"); console.log(`response: ${JSON.stringify(explain.response.body)}`); console.log("------------"); // 可对explain.response.body重新赋值来改变响应结果 if (explain.mode === "route") { explain.response.body = { response: explain.response.body, service: explain.request.service, action: explain.request.action, data: explain.request.data, route: explain.request.route, routeTemplate: explain.request.routeTemplate, httpMethod: explain.request.httpMethod } } else { explain.response.body = { response: explain.response.body, service: explain.request.service, action: explain.request.action, data: explain.request.data } } } } ``` ### onException 发生异常触发。 ### 示例:实现全局异常处理 #### 云函数入口 ```javascript // app -> startup.js module.exports = (app) => { app.init({ baseDir: __dirname }); // 使用过滤器示例 app.filter.add([{ filter: require("./filters/exceptionFilter") // 添加异常处理过滤器 }]); } ``` #### 过滤器代码 ```javascript // app -> filters -> exceptionFilter.js // 需要继承抽象父类explain.filter const explain = require("explain-unicloud"); module.exports = class exceptionFilter extends explain.filter { async onException() { let { explain } = this; // 输出日志 console.error("------------"); console.error("发生错误"); if (explain.request.service) { console.error(`service: ${explain.request.service}`); } if (explain.request.action) { console.error(`action: ${explain.request.action}`); } if (explain.request.route) { console.error(`route: ${explain.request.route}`); } if (explain.request.httpMethod) { console.error(`httpMethod: ${explain.request.httpMethod}`); } if (explain.request.routeTemplate) { console.error(`routeTemplate: ${explain.request.routeTemplate}`); } if (explain.request.data) { console.error(`data: ${JSON.stringify(explain.request.data)}`); } console.error(`异常信息: ${explain.exception.message}`); console.error("原始异常: ", explain.exception); console.error("------------"); // 发送异常信息到电子邮件 // throw new Error(explain.exception.message + ",经过了异常处理过滤器"); } } ``` ## 配置说明 ### app.init(options) #### 初始化,baseDir是必须配置的 |参数|类型|必填|说明| |-- |-- |-- |-- | |baseDir|String|是|项目根目录| |serviceDir|String|否|service目录,/开头,/结尾,默认`/services/`| |serviceKey|String|否|匹配模式下service参数别名,作用是与其他参数冲突时可以修改为别的名称,默认`service`| |actionKey|String|否|匹配模式下action参数别名,作用是与其他参数冲突时可以修改为别的名称,默认`action`| |dataKey|String|否|匹配模式下data参数别名,作用是与其他参数冲突时可以修改为别的名称,默认`data`| |enableMatchMode|Boolen|否|启用匹配模式,默认为`true`,`false`为禁用,禁用后仅支持路由模式访问业务函数| |matchIgnore|Array|否|为保证安全性,匹配模式可忽略所指定的service和actions,忽略后仅配置路由模式后可访问,格式为`[{service: "serviceName", actions: ["actionName1", "actionName2"]},{...}]`,不写`actions`表示忽略该service下所有action| #### 示例 ```javascript // 初始化 app.init({ baseDir: __dirname, // 项目根目录 serviceDir: "/services/", // service目录 serviceKey: "service", // 匹配模式下service参数别名,默认"service",作用是与其他参数冲突时可以修改为别的名称 actionKey: "action", // 匹配模式下action参数别名,默认"action",作用是与其他参数冲突时可以修改为别的名称 dataKey: "data", // 匹配模式下data参数别名,默认"data",作用是与其他参数冲突时可以修改为别的名称 enableMatchMode: true, // 启用匹配模式,false为禁用,禁用后仅支持路由模式访问业务函数 matchIgnore: [ // 匹配模式忽略指定的service和actions,忽略后仅配置路由模式后可访问 { service: "home", actions: ["index"] } ] }); ``` ## 基类说明 ### service 服务基类,继承后构造函数会自动注入`event`,`context`,`explain`几个对象,可通过`this.event`,`this.context`,`this.explain`进行调用。 #### explain对象 框架扩展,主要内容如下: |参数|类型|说明| |-- |-- |-- | |mode|String|获取本次请求为`match`匹配模式还是`route`路由模式| |config|Object|通过`app.init`初始化时的配置信息,详见`app.init`| |request|Object|请求对象,详情见下方`explain.request`| |response|Object|响应对象,详情见下方`explain.response`| |exception|Object|异常对象| |routes|Array|已配置的路由集合| |middlewares|Object|已配置的中间件集合| |filters|Array|已配置的过滤器集合| |useService|Function|在服务中使用其他service,若被使用的service是继承自explain.service,则会自动注入`event`,`context`,`explain`,详情见下| ##### explain.request |参数|类型|说明| |-- |-- |-- | |service|String|匹配到的`service`文件名称| |action|String|匹配到的`action`方法名称| |data|Object|客户端传入的参数| |isHttp|Boolen|是否是HTTP请求、是否是云函数URL化| |route|String|客户端传入的路由,只有当`mode`为`route`时该属性才有值| |routeTemplate|String|成功匹配的路由模板,只有当`mode`为`route`时该属性才有值| |httpMethod|String|HTTP Method,只有当`mode`为`route`时该属性才有值| ##### explain.response |参数|类型|说明| |-- |-- |-- | |mpserverlessComposedResponse|Boolen|使用阿里云返回集成响应是需要此字段为true| |statusCode|Number|HTTP StatusCode| |headers|Object|HTTP Reponse Headers| |body|Object|集成响应内容主体| ##### explain.useService(obj) |参数|类型|必填|说明| |-- |-- |-- |-- | |obj|Function|是|引入的未被实例化的service对象| ### filter 过滤器基类,继承后构造函数会自动注入`event`,`context`,`explain`几个对象,可通过`this.event`,`this.context`,`this.explain`进行调用,每个对象的作用同`service`基类。 ## 内置模块 ### dateTime 日期时间操作 #### now(formatString, timezone) 获取当前时间 |参数|类型|必填|说明| |-- |-- |-- |-- | |formatString|String|否|日期格式化,`yyyy`表示年份,`MM`表示月份,`dd`表示日期,`HH`表示时,`mm`表示分,`ss`表示秒,`fff`表示毫秒。不填则返回为时间戳| |timezone|Number|否|formatString存在时有效,格式化为指定时区的时间,默认8| #### format(timestamp, formatString, timezone) 日期格式化 |参数|类型|必填|说明| |-- |-- |-- |-- | |timestamp|Number/String|是|要格式化的时间戳| |formatString|String|是|日期格式化,`yyyy`表示年份,`MM`表示月份,`dd`表示日期,`HH`表示时,`mm`表示分,`ss`表示秒,`fff`表示毫秒| |timezone|Number|否|时区,默认8| #### addYears(timestamp, value) 添加年份 |参数|类型|必填|说明| |-- |-- |-- |-- | |timestamp|Number/String|是|时间戳| |value|Number|是|要添加的年份数值| #### addMonths(timestamp, value) 添加月份 |参数|类型|必填|说明| |-- |-- |-- |-- | |timestamp|Number/String|是|时间戳| |value|Number|是|要添加的月份数值| #### addDays(timestamp, value) 添加日期 |参数|类型|必填|说明| |-- |-- |-- |-- | |timestamp|Number/String|是|时间戳| |value|Number|是|要添加的日期数值| #### addHours(timestamp, value) 添加时 |参数|类型|必填|说明| |-- |-- |-- |-- | |timestamp|Number/String|是|时间戳| |value|Number|是|要添加的时数值| #### addMinutes(timestamp, value) 添加分 |参数|类型|必填|说明| |-- |-- |-- |-- | |timestamp|Number/String|是|时间戳| |value|Number|是|要添加的分数值| #### addSeconds(timestamp, value) 添加秒 |参数|类型|必填|说明| |-- |-- |-- |-- | |timestamp|Number/String|是|时间戳| |value|Number|是|要添加的秒数值| #### addMilliseconds(timestamp, value) 添加毫秒 |参数|类型|必填|说明| |-- |-- |-- |-- | |timestamp|Number/String|是|时间戳| |value|Number|是|要添加的毫秒数值| #### 使用示例 ```javascript const { dateTime } = require("explain-unicloud"); dateTime.now("yyyy-MM-dd HH:mm:ss"); // 得到格式为2020-11-11 00:00:00的当前时间 dateTime.format(1605024000000, "yyyy年MM月dd日HH时mm分ss秒"); // 格式化时间为2020年11月11日00时00分00秒 dateTime.addYears(1605024000000, 2); // 原始日期为2020年11月11日,年份增加2年,结果为2022年11月11日 dateTime.addMonths(1605024000000, 13); // 原始日期为2020年11月,增加13个月,结果为2021年12月 dateTime.addHours(1605024000000, -1); // 原始日期为2020年11月11日00时,增加-1时,结果为2020年11月10日23时 ``` ### object 对象操作 #### sort(obj) 属性名称按照ASCII码从小到大排序(字典序) |参数|类型|必填|说明| |-- |-- |-- |-- | |obj|Object|是|需要排序的对象| #### 使用示例 ```javascript const { object } = require("explain-unicloud"); var obj = { b: 1, c: 2, a: 3 } obj = object.sort(obj); // 结果为{a:3,b:1,c:2} ``` <!-- ## 扩展模块 |名称|说明|链接| |-- |-- |-- | |explain-qrcode|二维码模块|[https://ext.dcloud.net.cn/plugin?id=3359](https://ext.dcloud.net.cn/plugin?id=3359)| |explain-cache|缓存模块|[https://ext.dcloud.net.cn/plugin?id=3425](https://ext.dcloud.net.cn/plugin?id=3425)| |explain-weixin|微信模块|[https://ext.dcloud.net.cn/plugin?id=3678](https://ext.dcloud.net.cn/plugin?id=3678)| ## 前端模板 |名称|说明|链接| |-- |-- |-- | |explain-mall|商城模板|[https://ext.dcloud.net.cn/plugin?id=3637](https://ext.dcloud.net.cn/plugin?id=3637)| |explain-form|低代码表单生成组件|[https://ext.dcloud.net.cn/plugin?id=4880](https://ext.dcloud.net.cn/plugin?id=4880)| ## 项目模板 |名称|说明|链接| |-- |-- |-- | |explain-admin|云端一体后台模板|[https://ext.dcloud.net.cn/plugin?id=5084](https://ext.dcloud.net.cn/plugin?id=5084)| --> ### 更多模块正在开发中,敬请期待... ## 解决方案 |名称|说明|链接| |-- |-- |-- | |unicloud云函数TS开发最优解决方案|感谢 `老沈-1018715564@qq.com` 提供|[https://www.yinzhuoei.com/index.php/archives/470](https://www.yinzhuoei.com/index.php/archives/470)| ## 第三方插件(感谢插件作者): [json格式数据展示](https://ext.dcloud.net.cn/plugin?id=2687) @作者:罗魔什