云函数是运行在云端的js代码,是基于nodejs扩展的。每一个云函数就是一个js包,里面还可以有package.json作为配置文件。
云函数启动后实例会保留一段时间(如15分钟),超过保留期后若该云函数一直没有被再调用,那这个实例会被释放。所以云函数有冷启动的概念。不过由于js实例的启动要比php和java更快,所以js更适合serverless方式。
云函数和客户端通信
传统restful | callfunction | 云对象 | clientDB |
---|---|---|---|
ajax | uniapp客户端通过uniCloud.callFunction(functionname) 调用云函数 | uniapp客户端通过uniCloud.importObject(functionname)调用云对象 | uniapp客户端通过 调用uniCloud.database()API访问云端数据。 还可以通过action云函数追加服务器逻辑。 |
适用域名注册的情况下,uniapp不推荐使用这个方式。 | 相比传统方式,call function更安全,不会暴露域名和ip ,不怕攻击,也不需要注册域名。 | 跟callfunction比,代码更简洁,开发更高效,uniapp3.4起支持。 | 推荐使用。 |
clientDB适用的情况:
客户端操作云数据库(无论增删改查),那么推荐使用clientDB方式,由uni-app客户端直接操作云数据库。
操作数据库的同时,还需要同时执行一些云端逻辑,推荐使用数据库触发器替代action云函数。
clientDB不适用的情况:
1)请求不操作数据库。
2)操作数据库请求不希望暴露到前端。
3)数据库表多字段多接口少。
4)权限体系复杂的。
——云对象
云对象和clientDB的区别,云对象可以适用于clientDB不适用的地方。
// 客户端发起调用云函数hellocf,并传入data数据
uniCloud.callFunction({
name: 'hellocf',
data: {a:1,b:2}
}).then((res) => {
console.log(res.result) // 结果是 {sum: 3}
}).catch((err) => {
console.error(err)
})
// 云函数hellocf的代码,接收到客户端传递的data,并对其中a和b相加返回给客户端
'use strict';
exports.main = async (event, context) => {
//event为客户端上传的参数
console.log('event : ', event)
//此处省略event.a和event.b的有效性校验
//返回数据给客户端
return {sum : event.a + event.b}
};
uniCloud API(官方文档)
——访问其他HTTP
uniCloud.httpclient.request(URL,requestOptions)
requertOptions的值看文档
直接上代码
const res = await uniCloud.httpclient.request('api',{
method: 'POST',
data: {
test: 'testValue'
},
contentType: 'json', // 指定以application/json发送data内的数据
dataType: 'json' // 指定返回值为json格式,自动进行parse
})
console.log(res)
//formData类型数据
const fs = require('fs')
const path = require('path')
const FormData = require('form-data');
exports.main = async (event, context) => {
const form = new FormData()
form.append('media', fs.readFileSync(path.resolve(__dirname, './test.jpg')),{
filename: 'test.jpg',
contentType: 'image/jpeg'
});
form.append('otherParam', 'otherParam content');
const res = await uniCloud.httpclient.request('https://api/post', {
method: 'POST',
content: form.getBuffer(), // 请求内容
headers: form.getHeaders(), // 请求头
dataType: 'json' // 此处指定为json表示将此请求的返回值解析为json
})
return res
};
请求和环境API,实例uniCloud对象,与reuqest请求是一对多关系。
获取请求id列表(uniCloud.getRequestList()),如果配置了单实例多并发,那么,uniCloud对象无法确定是当前请求中数组中哪一个,所以,有方法来确定当前请求是哪一个:uniCloud.getUniCloudRequertId(),或云函数自带参数context。
扩展库:短信uni-cloud-sms,一键登录uni-cloud-verify,推送uni-cloud-push…
——公共模块
目前云函数上传包大小上限10m,如果超过,只能弃阿里云选择腾讯云。
云函数/云对象中调用云函数
调用uniCloud.callfunction,但不支持callback形式。
给10万用户发信息,一个短信接口支持给50个用户发信息,那么需要2000次请求,云函数完不成,要用云函数递归做,上例子:
// 当前云函数名称 send-sms-cf
'use strict';
const db = uniCloud.database();
const dbCmd = db.command
const userTable = db.collection('uni-id-users')
exports.main = async (event, context) => {
//执行业务逻辑
let res = await sendSms(event.before_id)
if (res.errCode) {
return res
}else{
// 如果没有报错,就让当前云函数 调用当前云函数(云对象同理)。注意:这里是异步的
uniCloud.callFunction({
name: 'send-sms-cf',
data: {
before_id: res.before_id
}
}).catch(e=>{
console.log(e.message);
}).then(e=>{
console.log(e.result);
})
// 等待500毫秒,给一个请求发出去的时间
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(res)
}, 500)
})
}
async function sendSms(before_id) {
console.log('before_id',before_id);
let where = {
phone: dbCmd.exists(true),
//..这里可以写你自己的其他条件,如超过多久没登录的用户 last_login_date < Date.now() - 3600*24*...
}
if(before_id){
//高性能分页查询,以上一次查询的最后一条数据的id被起始id
where._id = dbCmd.gt(before_id)
}
let res = await userTable.where(where)
.limit(50)
.orderBy("_id", "asc")
.get()
if (!res.data.length) {
return {
errCode: 'sendSms-invalid',
errMsg: '结束,没有符合条件的接收者'
}
}
let phoneList = res.data.map(item => item.phone)
let sendSmsRes = await uniCloud.sendSms({
phoneList,
appid: '__UNI__xxxxxxx',
smsKey: '****************',
smsSecret: '****************',
templateId: '100**', // 请替换为自己申请的模板id
data: {
text1: 'xxx',
text2: 'xxx'
}
})
if (sendSmsRes.errCode) {
return sendSmsRes
}
return {
errCode: 0,
before_id: res.data[res.data.length - 1]._id
}
}
};
云函数内部访问其他服务空间
腾讯云服务空间的云函数内支持获取同账号下其他服务空间的uniCloud实例。
注意:本地调试云函数,存在跨服务空间问题,那么使用云端云函数。
serverless引发的一些概念:冷启动、实例、并发请求、无状态、伪全局变量。
——冷启动:serverless实例化——加载云函数——启动node——执行云函数;实例会保留一段时间,在这期间客户端再次请求云函数,不会启动冷启动,速度会快。
——热启动:云函数实例和进程复用,性能更好,它只需要做一件事,执行云函数。
(优化冷启动:阿里云单实例多并发,腾讯云付费实例子预留,非高频云函数合并到高频云函数中或设置定时任务持续运行它。)
实例和请求:实例相当于云函数的运行环境,或者说node进程…实例和请求不是一对一的,所以获取客户端信息时,不是在实例的全局对象上获取,而是在请求的上下文上获取。
——云函数无状态和全局变量
实例可能是第一次启动,也可能是启动了,云函数js中的全局变量就是伪变量,是云函数无状态的。
在云对象中,module.exports ={},云函数中exports.main = async (event,context)=>{},它们之前的全局变量,是伪全局变量。
它们在实例被保留的期间内多次请求中复用,因此,会出现变量累加的情况。
上代码:
let count = 0;
exports.main = async (event, context) => {
return count++
}
根据上面代码,count首次为0,如果发生复用,那么count会是1,2,3…(当然可以用这种方法获得重复次数),require由于存在缓存,也存在同样问题,所以尽量不要修改require返回的结果。
uniCloud全局对象也是跨请求,请求相关的内容不应该挂载到uniCloud的全局对象上。
正确的全局变量,应该怎样:
uni-config-center:静态全局变量可以使用uni提供的配置中心。
redis:动态全局变量使用redis。
——请求的上下文
先了解实例和请求关系:
通过uniCloud.getRequestList(),可以获得实例请求id列表;
通过uniCloud.getClientInfos(),可以获得所有请求客户端信息;
uniCloud私有云,想要获取与请求相关的信息,无法从uniCloud全局变量上获得;云函数的event,context和请求相关,云对象每次请求都对应一个单独this,用来解决问题。
——单实例多并发,看例子
// 开启单实例多并发前的uni-id用法
const uniID = require('uni-id')
exports.main = async function(event, context) {
const res = uniID.login({
// ...一些参数
})
return res
}
// 由于uni-id默认会从一个内置全局变量上获取客户端平台信息,不同请求会修改此全局变量可能造成混乱,开启单实例多并发后需要将uni-id修改为如下写法
let uniID = require('uni-id')
exports.main = async function(event, context) {
let uniIDIns = uniID.createInstance({
// 创建uni-id实例,其上方法同uniID
context: context // 传入context防止不同请求互相影响
// config: {} // 完整uni-id配置信息,使用config.json进行配置时无需传此参数
})
const res = uniIDIns.login({
// ...一些参数
})
return res
}
——云函数中的异步行为
——return策略:阿里云终止后,逻辑就不会在执行;腾讯云node12之后可以配置是否执行,开启了,记得关闭,云函数未终止会一直收费,还会中断redis和服务器建立的链接。(package.json 的keepRunningAfterReturn设置为true,中断reids:redis.quit(),下次使用时重新建立🔗uniCloud.redis())
——云函数配置:运行内存,固定出口ip,超时时间