2.0.6 • Published 3 years ago

explain-unicloud v2.0.6

Weekly downloads
-
License
MIT
Repository
github
Last release
3 years ago

EXPLAIN-UNICLOUD

快速、极简的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/serviceKeyString需要指向的service的,若参数名与业务参数冲突可通过app.init配置serviceKey改变参数名称
action/actionKeyString需要指向的action的,若参数名与业务参数冲突可通过app.init配置actionKey改变参数名称
data/dataKeyString请求包含的参数,若参数名与业务参数冲突可通过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中查看

云函数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()

示例

// 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

方法执行时触发。

示例:实现用户身份验证

云函数入口

// 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解析出来的用户对象
		}
	}
	
}

onActionExecuted

方法执行后触发。

示例:实现请求日志记录

云函数入口

// 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
			}
		}
	}

}

onException

发生异常触发。

示例:实现全局异常处理

云函数入口

// 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 + ",经过了异常处理过滤器");
	}

}

配置说明

app.init(options)

初始化,baseDir是必须配置的

参数类型必填说明
baseDirString项目根目录
serviceDirStringservice目录,/开头,/结尾,默认/services/
serviceKeyString匹配模式下service参数别名,作用是与其他参数冲突时可以修改为别的名称,默认service
actionKeyString匹配模式下action参数别名,作用是与其他参数冲突时可以修改为别的名称,默认action
dataKeyString匹配模式下data参数别名,作用是与其他参数冲突时可以修改为别的名称,默认data
enableMatchModeBoolen启用匹配模式,默认为truefalse为禁用,禁用后仅支持路由模式访问业务函数
matchIgnoreArray为保证安全性,匹配模式可忽略所指定的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"]
		}
	]
});

基类说明

service

服务基类,继承后构造函数会自动注入eventcontextexplain几个对象,可通过this.eventthis.contextthis.explain进行调用。

explain对象

框架扩展,主要内容如下:

参数类型说明
modeString获取本次请求为match匹配模式还是route路由模式
configObject通过app.init初始化时的配置信息,详见app.init
requestObject请求对象,详情见下方explain.request
responseObject响应对象,详情见下方explain.response
exceptionObject异常对象
routesArray已配置的路由集合
middlewaresObject已配置的中间件集合
filtersArray已配置的过滤器集合
useServiceFunction在服务中使用其他service,若被使用的service是继承自explain.service,则会自动注入eventcontextexplain,详情见下
explain.request
参数类型说明
serviceString匹配到的service文件名称
actionString匹配到的action方法名称
dataObject客户端传入的参数
isHttpBoolen是否是HTTP请求、是否是云函数URL化
routeString客户端传入的路由,只有当moderoute时该属性才有值
routeTemplateString成功匹配的路由模板,只有当moderoute时该属性才有值
httpMethodStringHTTP Method,只有当moderoute时该属性才有值
explain.response
参数类型说明
mpserverlessComposedResponseBoolen使用阿里云返回集成响应是需要此字段为true
statusCodeNumberHTTP StatusCode
headersObjectHTTP Reponse Headers
bodyObject集成响应内容主体
explain.useService(obj)
参数类型必填说明
objFunction引入的未被实例化的service对象

filter

过滤器基类,继承后构造函数会自动注入eventcontextexplain几个对象,可通过this.eventthis.contextthis.explain进行调用,每个对象的作用同service基类。

内置模块

dateTime 日期时间操作

now(formatString, timezone)

获取当前时间

参数类型必填说明
formatStringString日期格式化,yyyy表示年份,MM表示月份,dd表示日期,HH表示时,mm表示分,ss表示秒,fff表示毫秒。不填则返回为时间戳
timezoneNumberformatString存在时有效,格式化为指定时区的时间,默认8

format(timestamp, formatString, timezone)

日期格式化

参数类型必填说明
timestampNumber/String要格式化的时间戳
formatStringString日期格式化,yyyy表示年份,MM表示月份,dd表示日期,HH表示时,mm表示分,ss表示秒,fff表示毫秒
timezoneNumber时区,默认8

addYears(timestamp, value)

添加年份

参数类型必填说明
timestampNumber/String时间戳
valueNumber要添加的年份数值

addMonths(timestamp, value)

添加月份

参数类型必填说明
timestampNumber/String时间戳
valueNumber要添加的月份数值

addDays(timestamp, value)

添加日期

参数类型必填说明
timestampNumber/String时间戳
valueNumber要添加的日期数值

addHours(timestamp, value)

添加时

参数类型必填说明
timestampNumber/String时间戳
valueNumber要添加的时数值

addMinutes(timestamp, value)

添加分

参数类型必填说明
timestampNumber/String时间戳
valueNumber要添加的分数值

addSeconds(timestamp, value)

添加秒

参数类型必填说明
timestampNumber/String时间戳
valueNumber要添加的秒数值

addMilliseconds(timestamp, value)

添加毫秒

参数类型必填说明
timestampNumber/String时间戳
valueNumber要添加的毫秒数值

使用示例

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码从小到大排序(字典序)

参数类型必填说明
objObject需要排序的对象

使用示例

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开发最优解决方案感谢 老沈-1018715564@qq.com 提供https://www.yinzhuoei.com/index.php/archives/470

第三方插件(感谢插件作者):

json格式数据展示 @作者:罗魔什

2.0.6

3 years ago

2.0.5

3 years ago

2.0.4

3 years ago

2.0.3

3 years ago

2.0.2

3 years ago

2.0.1

3 years ago

2.0.0

3 years ago