快速、极简的uniCloud云函数开发框架。已支持云函数URL化,已支持RESTful。
框架交流QQ群:970799055。
┌── 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 改变参数名称 |
// app -> index.js
const explain = require("explain-unicloud");
exports.main = async (event, context) => explain.run({
event,
context,
(app) => {
app.init({baseDir: __dirname});
}
});
// 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!';
}
}
uniCloud.callFunction({
name: 'app',
data: {
service: 'values',
action: 'getValuesAsync',
data: {}
}
}).then(res => {
console.log(res)
})
推荐使用explain-creq
,封装了请求拦截器,响应拦截器和错误拦截器,可自由定制请求数据处理和响应结果处理
import creq from '@/uni_modules/explain-creq/js_sdk/explain-creq.js'
creq.callFunction('values', 'getValuesAsync', {
name: 'app', // 指定云函数名称
data: {} // 请求参数
}).then(res => {
console.log(res)
})
// 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"]
}]
}]);
}
});
// 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中查看
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
,封装了请求拦截器,响应拦截器和错误拦截器,可自由定制请求数据处理和响应结果处理
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中查看
腾讯云 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 -> 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")
});
}
执行顺序为注册过滤器时的顺序,先注册的先执行,后注册的后执行。
方法执行时触发。
// 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"]
}]
}]);
}
// 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无效"
}
}
}
}
// 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解析出来的用户对象
}
}
}
方法执行后触发。
// app -> startup.js
module.exports = (app) => {
app.init({
baseDir: __dirname
});
// 使用过滤器示例
app.filter.add([{
filter: require("./filters/requestFilter"), // 添加请求记录过滤器
ignore: [{
service: "home",
actions: ["index"]
}]
}]);
}
// 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
}
}
}
}
发生异常触发。
// app -> startup.js
module.exports = (app) => {
app.init({
baseDir: __dirname
});
// 使用过滤器示例
app.filter.add([{
filter: require("./filters/exceptionFilter") // 添加异常处理过滤器
}]);
}
// 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 + ",经过了异常处理过滤器");
}
}
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
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 |
// 初始化
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"]
}
]
});
服务基类,继承后构造函数会自动注入event
,context
,explain
几个对象,可通过this.event
,this.context
,this.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 ,详情见下 |
参数 | 类型 | 说明 |
---|---|---|
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 时该属性才有值 |
参数 | 类型 | 说明 |
---|---|---|
mpserverlessComposedResponse | Boolen | 使用阿里云返回集成响应是需要此字段为true |
statusCode | Number | HTTP StatusCode |
headers | Object | HTTP Reponse Headers |
body | Object | 集成响应内容主体 |
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
obj | Function | 是 | 引入的未被实例化的service对象 |
过滤器基类,继承后构造函数会自动注入event
,context
,explain
几个对象,可通过this.event
,this.context
,this.explain
进行调用,每个对象的作用同service
基类。
获取当前时间
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
formatString | String | 否 | 日期格式化,yyyy 表示年份,MM 表示月份,dd 表示日期,HH 表示时,mm 表示分,ss 表示秒,fff 表示毫秒。不填则返回为时间戳 |
timezone | Number | 否 | formatString存在时有效,格式化为指定时区的时间,默认8 |
日期格式化
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
timestamp | Number/String | 是 | 要格式化的时间戳 |
formatString | String | 是 | 日期格式化,yyyy 表示年份,MM 表示月份,dd 表示日期,HH 表示时,mm 表示分,ss 表示秒,fff 表示毫秒 |
timezone | Number | 否 | 时区,默认8 |
添加年份
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
timestamp | Number/String | 是 | 时间戳 |
value | Number | 是 | 要添加的年份数值 |
添加月份
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
timestamp | Number/String | 是 | 时间戳 |
value | Number | 是 | 要添加的月份数值 |
添加日期
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
timestamp | Number/String | 是 | 时间戳 |
value | Number | 是 | 要添加的日期数值 |
添加时
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
timestamp | Number/String | 是 | 时间戳 |
value | Number | 是 | 要添加的时数值 |
添加分
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
timestamp | Number/String | 是 | 时间戳 |
value | Number | 是 | 要添加的分数值 |
添加秒
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
timestamp | Number/String | 是 | 时间戳 |
value | Number | 是 | 要添加的秒数值 |
添加毫秒
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
timestamp | Number/String | 是 | 时间戳 |
value | Number | 是 | 要添加的毫秒数值 |
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时
属性名称按照ASCII码从小到大排序(字典序)
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
obj | Object | 是 | 需要排序的对象 |
const { object } = require("explain-unicloud");
var obj = {
b: 1,
c: 2,
a: 3
}
obj = object.sort(obj); // 结果为{a:3,b:1,c:2}
名称 | 说明 | 链接 |
---|---|---|
unicloud云函数TS开发最优解决方案 | 感谢 老沈[email protected] 提供 |
https://www.yinzhuoei.com/index.php/archives/470 |
json格式数据展示 @作者:罗魔什