跳到主要内容

云函数和 Hook 开发指南

信息

这篇文档专注在「云函数和 Hook」这种云引擎上的特殊的应用场景,如需部署通用的后端应用,或需要了解云引擎平台提供的更多功能,请看 云引擎平台功能

云函数是云引擎提供的一种经过高度封装的函数计算功能,在我们的各个客户端 SDK 中也有对应的支持,可以自动地序列化 数据存储 服务中的各种数据类型。

适合使用云函数和 Hook 的场景包括:

  • 将跨平台应用(同时有 Android、iOS、浏览器客户端)中复杂的计算逻辑集中到一处,而不必每个客户端单独实现一遍。
  • 需要在服务器端对一些逻辑进行灵活调整,而不必更新客户端。
  • 需要越过 ACL 或表权限的限制,对数据进行查询或修改。
  • 需要使用 Hook 在数据存储中的对象被创建、更新、删除,或用户登录、认证时,触发自定义的逻辑、进行额外的权限检查。
  • 需要运行定时任务,如每小时关闭未支付的订单、每天凌晨运行过期数据的清理任务等。

你可以使用云引擎支持的所有语言(运行环境)来编写云函数,包括 Node.js、Python、Java、PHP、.NET 和 Go。其中 Node.js 支持在控制台上在线编辑,其他语言需基于我们的示例项目部署到云引擎。

云函数

现在让我们看一个更复杂的例子,在一个应用中我们允许用户对电影进行评分,一个评分对象(Review)大概是这样:

{
"movie": "夏洛特烦恼",
"stars": 5,
"comment": "夏洛一梦,笑成麻花"
}

stars 表示评分,是 1 至 5 的数字。通过云引擎,我们可以简单地传入电影名称,然后返回电影的平均分。

云函数接收 JSON 格式的请求对象,我们可以用它来传入电影名称。云函数中可以直接使用对应语言的数据存储 SDK,所以我们可以使用它来查询所有的评分。结合在一起,我们可以实现一个 averageStars 函数:

AV.Cloud.define("averageStars", function (request) {
var query = new AV.Query("Review");
query.equalTo("movie", request.params.movie);
return query.find().then(function (results) {
var sum = 0;
for (var i = 0; i < results.length; i++) {
sum += results[i].get("stars");
}
return sum / results.length;
});
});

AV.Cloud.define 还接受一个可选参数 options(位置在函数名称和调用函数之间),这个 options 对象上的属性包括:

  • fetchUser?: boolean:是否自动抓取客户端的用户信息,默认为 true。设置为假时,Request 将不会有 currentUser 属性。
  • internal?: boolean:是否只允许在云引擎内(使用 AV.Cloud.run 且未开启 remote 选项)或使用 Master Key(使用 AV.Cloud.run 时传入 useMasterKey)调用,不允许客户端直接调用。默认为 false

例如,假设我们不希望客户端直接调用上述函数,也不关心客户端用户信息,那么上述函数的定义可以改写为:

AV.Cloud.define(
"averageStars",
{ fetchUser: false, internal: true },
function (request) {
// 内容同上
}
);

参数和返回值

Request 会作为参数传入到云函数中,Request 上的属性包括:

  • params: object:客户端发送的参数对象,当使用 rpc 调用时,也可能是 AV.Object
  • currentUser?: AV.User:客户端所关联的用户(根据客户端发送的 X-LC-Session 头)。
  • sessionToken?: string:客户端发来的 sessionTokenX-LC-Session 头)。
  • meta: object:有关客户端的更多信息,目前只有一个 remoteAddress 属性表示客户端的 IP。

如果云函数返回了一个 Promise,那么云函数会使用 Promise 成功结束后的结果作为成功响应;如果 Promise 中发生了错误,云函数会使用这个错误作为错误响应,对于使用 AV.Cloud.Error 构造的异常对象,我们认为是客户端错误,不会在标准输出打印消息,对于其他异常则会在标准输出打印调用栈,以便排查错误。

我们推荐大家使用链式的 Promise 写法来完成业务逻辑,这样会极大地方便异步任务的处理和异常处理,请注意一定要将 Promise 串联起来并在云函数中 return 以保证上述逻辑正确工作,推荐阅读 JavaScript Promise 迷你书 来深入地了解 Promise。

点击展开 Node.js SDK 早期版本详情

在 2.0 之前的 Node.js 中,云函数接受 requestresponse 两个参数,我们会继续兼容这种用法到下一个大版本,希望开发者尽快迁移到 Promise 风格的云函数上。之前版本的文档见 Node SDK v1 API 文档

客户端 SDK 调用云函数

各个客户端 SDK 都提供了调用云函数的功能:

try {
Dictionary<string, object> response = await LCCloud.Run("averageStars", parameters: new Dictionary<string, object> {
{ "movie", "夏洛特烦恼" }
});
// 处理结果
} catch (LCException e) {
// 处理异常
}

云函数调用(Run)默认将请求参数和响应结果作为 JSON 对象来处理,如果需要在请求或响应中传递 LCObject 对象,则可以使用 RPC 方式来调用云函数,SDK 将会完成 LCObject 类型的序列化和反序列化,在云函数和客户端代码中都可以直接获取到 LCObject 对象:

try {
LCObject response = await LCCloud.RPC("averageStars", parameters: new Dictionary<string, object> {
{ "movie", "夏洛特烦恼" }
});
// 处理结果
} catch (LCException e) {
// 处理异常
}

RPC 会处理以下形式的请求和响应:

  • 单个 LCObject
  • 包含 LCObject 的散列表(HashMap)
  • 包含 LCObject 的数组(Array)

其他形式的数据 SDK 会保持原样,不进行处理。

云函数内部调用云函数

云引擎 Node.js 环境下,默认会直接进行一次本地的函数调用,而不会像客户端一样发起一个 HTTP 请求。

AV.Cloud.run("averageStars", {
movie: "夏洛特烦恼",
}).then(
function (data) {
// 调用成功,得到成功的应答 data
},
function (error) {
// 处理调用失败
}
);

如果你希望发起 HTTP 请求来调用云函数,可以传入一个 remote: true 的选项。当你在云引擎之外运行 Node.js SDK(包括调用位于其他分组上的云函数)时这个选项非常有用:

AV.Cloud.run("averageStars", { movie: "夏洛特烦恼" }, { remote: true }).then(
function (data) {
// 成功
},
function (error) {
// 处理调用失败
}
);

上面的 remote 选项实际上是作为 AV.Cloud.run 的可选参数 options 对象的属性传入的。这个 options 对象包括以下参数:

  • remote?: boolean:上面的例子用到的 remote 选项,默认为假。
  • user?: AV.User:以特定的用户运行云函数(建议在 remote 为假时使用)。
  • sessionToken?: string:以特定的 sessionToken 调用云函数(建议在 remote 为真时使用)。
  • req?: http.ClientRequest | express.Request:为被调用的云函数提供 remoteAddress 等属性。

云函数错误响应码

可以根据 HTTP status codes 自定义错误响应码。

AV.Cloud.define("customErrorCode", function (request) {
throw new AV.Cloud.Error("自定义错误信息。", { code: 123 });
});

客户端收到的响应:{ "code": 123, "error": "自定义错误信息。" }

云函数超时

云函数超时时间为 15 秒,如果超过阈值,客户端将收到 HTTP status code 为 503 的响应,body 为 The request timed out on the server。 注意即使已经响应,此时云函数可能仍在执行,但执行完毕后的响应是无意义的(不会发给客户端,会在日志中打印一个 Can't set headers after they are sent 的异常)。 除了 503 错误外,有些情况下客户端也可能收到其他报错,如 524141

超时的处理方案

我们建议将代码中的任务转化为异步队列处理,以优化运行时间,避免云函数或定时任务发生超时。

例如:

  1. 在存储服务中创建一个队列表,包含 status 列;
  2. 接到任务后,向队列表保存一条记录,status 值设置为 处理中,然后将请求结束掉,将队列对象的 id 发给客户端。
  3. 当业务处理完毕,根据处理结果更新刚才的队列对象状态,将 status 字段设置为 完成 或者 失败
  4. 在任何时候,在控制台通过队列 id 可以获取某个任务的执行结果,判断任务状态。

不过,对于 before 类 hook 函数,改为异步处理通常没有意义。 虽然改为异步后能保证 before 类函数能够运行完成,不会因超时而报错。 但是,只要 before 类 hook 函数不能及时抛出异常,就无法起到中断操作执行的作用。 对于超时的 before 类 hook 函数,如果无法优化代码,压缩执行时间的话,那只能改为 after 类函数。 例如,假设某个 beforeSave 函数需要调用耗时较久的第三方的自然语言处理接口判断用户提交的评论是否来自真实用户,导致超时, 那么可以改为 afterSave 函数,在保存评论后再调用第三方接口,如果判断是水军评论,那么再进行删除。

数据存储 Hook

Hook 函数本质上是云函数,但它有固定的名称,定义之后会 由系统 在特定事件或操作(如数据保存前、保存后,数据更新前、更新后等等)发生时 自动触发,而不是由开发者来控制其触发时机。需要注意:

  • 通过控制台进行数据导入时不会触发任何 hook 函数。
  • 使用 Hook 函数需要 防止死循环调用
  • _Installation 表暂不支持 Hook 函数。
  • Hook 函数只对当前应用的 Class 生效,对绑定后的目标 Class 无效。

对于 before 类的 Hook,如果返回了一个错误的话,这个操作就会被中断,因此你可以在这些 Hook 中主动抛出一个错误来拒绝掉某些操作。对于 after 类的 Hook,返回错误并不会影响操作的执行(因为其实操作已经执行完了)。

graph LR A((save)) -->D{object} D-->E(new) E-->|beforeSave|H{error?} H-->N(No) N-->B[create new object on the cloud] B -->|afterSave|C((done)) H-->Y(Yes) Y-->Z((interrupted)) D-->F(existing) F-->|beforeUpdate|I{error?} I-->Y I-->V(No) V-->G[update existing object on the cloud] G-->|afterUpdate| C
graph LR A((delete))-->|beforeDelete|H{error?} H-->Y(Yes) Y-->Z((interrupted)) H-->N(No) N-->B[delete object on the cloud] B -->|afterDelete|C((done))

为了认证 Hook 调用者的身份,我们的 SDK 内部会确认请求确实是从云引擎内网的云存储组件发出的,如果认证失败,可能会出现 Hook key check failed 的提示,如果在本地调试时出现这样的错误,请确保是通过命令行工具启动调试的。

BeforeSave

在创建新对象之前,可以对数据做一些清理或验证。例如,一条电影评论不能过长,否则界面上显示不开,需要将其截断至 140 个字符:

AV.Cloud.beforeSave("Review", function (request) {
var comment = request.object.get("comment");
if (comment) {
if (comment.length > 140) {
// 截断并添加 '…'
request.object.set("comment", comment.substring(0, 140) + "…");
}
} else {
// 不保存数据,并返回错误
throw new AV.Cloud.Error("No comment provided!");
}
});

上面的代码示例中,request.object 是被操作的 AV.Object。除了 object 之外,request 上还有一个属性:

  • currentUser?: AV.User:发起操作的用户。

类似地,其他 hook 的 request 参数上也包括 objectcurrentUser 这两个属性。

AfterSave

在创建新对象后触发指定操作,比如当一条留言创建后再更新一下所属帖子的评论总数:

AV.Cloud.afterSave("Comment", function (request) {
var query = new AV.Query("Post");
return query.get(request.object.get("post").id).then(function (post) {
post.increment("comments");
return post.save();
});
});

再如,在用户注册成功之后,给用户增加一个新的属性 from 并保存:

AV.Cloud.afterSave("_User", function (request) {
console.log(request.object);
request.object.set("from", "LeanCloud");
return request.object.save().then(function (user) {
console.log("Success!");
});
});

虽然对于 after 类的 Hook 我们并不关心返回值,但我们仍建议你返回一个 Promise,这样如果发生了非预期的错误,会自动在标准输出中打印异常信息和调用栈。

BeforeUpdate

在更新已存在的对象前执行操作,这时你可以知道哪些字段已被修改,还可以在特定情况下拒绝本次修改:

AV.Cloud.beforeUpdate("Review", function (request) {
// 如果 comment 字段被修改了,检查该字段的长度
if (request.object.updatedKeys.indexOf("comment") != -1) {
if (request.object.get("comment").length > 140) {
// 拒绝过长的修改
throw new AV.Cloud.Error("comment 长度不得超过 140 字符。");
}
}
});

对传入对象直接进行的修改不会被保存。如需拒绝修改,可以让函数返回一个错误。

传入的对象是一个尚未保存到数据库的临时对象,并不保证与最终储存到数据库的对象完全相同,这是因为修改中可能包含自增、数组增改、关系增改等原子操作。

AfterUpdate

本 Hook 使用不当可能会造成死循环,导致数据存储 API 的调用次数暴涨,甚至产生更多的费用。因此请仔细阅读 防止死循环调用 部分,做出必要的调整和预防措施。

在更新已存在的对象后执行特定的动作。和 BeforeUpdate 一样,你可以知道哪些字段已被修改。

AV.Cloud.afterUpdate("Review", function (request) {
if (request.object.updatedKeys.indexOf("comment") != -1) {
if (request.object.get("comment").length < 5) {
console.log(review.ObjectId + " 看起来像灌水评论:" + comment);
}
}
});

BeforeDelete

在删除一个对象之前做一些检查工作,比如在删除一个相册 Album 前,先检查一下该相册中还有没有照片 Photo

AV.Cloud.beforeDelete("Album", function (request) {
// 查询 Photo 中还有没有属于这个相册的照片
var query = new AV.Query("Photo");
var album = AV.Object.createWithoutData("Album", request.object.id);
query.equalTo("album", album);
return query.count().then(
function (count) {
if (count > 0) {
// delete 操作会被丢弃
throw new AV.Cloud.Error(
"Cannot delete an album if it still has photos in it."
);
}
},
function (error) {
throw new AV.Cloud.Error(
"Error " +
error.code +
" occurred when finding photos: " +
error.message
);
}
);
});

AfterDelete

在一个对象被删除后执行操作,例如递减计数、删除关联对象等等。同样以相册为例,这次我们不在删除相册前检查是否还有照片,而是在删除后,同时删除相册中的照片:

AV.Cloud.afterDelete("Album", function (request) {
var query = new AV.Query("Photo");
var album = AV.Object.createWithoutData("Album", request.object.id);
query.equalTo("album", album);
return query
.find()
.then(function (posts) {
return AV.Object.destroyAll(posts);
})
.catch(function (error) {
console.error(
"Error " +
error.code +
" occurred when finding photos: " +
error.message
);
});
});

OnVerified

当用户通过邮箱或者短信验证时,对该用户执行特定操作。比如:

AV.Cloud.onVerified("sms", function (request) {
console.log("User " + request.object + " is verified by SMS.");
});

上面的代码示例中的 object 换成 currentUser 也可以。因为这里被操作的对象正好是发起操作的用户。 下面的 onLogin 函数同理。

数据库中相关的验证字段,如 emailVerified 不需要修改,系统会自动更新。

该 hook 属于 after 类 hook。

OnLogin

在用户登录之时执行指定操作,比如禁止在黑名单上的用户登录:

AV.Cloud.onLogin(function (request) {
// 因为此时用户还没有登录,所以用户信息是保存在 request.object 对象中
console.log("User " + request.object + " is trying to log in.");
if (request.object.get("username") === "noLogin") {
// 如果是 error 回调,则用户无法登录(收到 401 响应)
throw new AV.Cloud.Error("Forbidden");
}
});

该 hook 属于 before 类 hook。

OnAuthData

在云存储处理第三方登录的 authData 时触发,开发者可以在这个 Hook 中进行对 authData 的校验或转换。比如:

AV.Cloud.onAuthData(function (request) {
let authData = request.authData;
console.log(authData);

if (authData.weixin.code === "12345") {
authData.weixin.accessToken = "45678";
} else {
// 校验失败,抛出异常,则用户无法登录
throw new AV.Cloud.Error("invalid code");
}
// 校验成功,返回校验或转换之后的 authData,用户继续登录流程
return authData;
});

该 hook 属于 before 类 hook。

防止死循环调用

你也许会好奇为什么可以在 AfterUpdate 中保存 post 而不会再次触发该 hook。 这是因为云引擎对所有传入对象做了处理,以阻止死循环调用的产生。

不过请注意,以下情况还需要开发者自行处理:

  • 对传入对象进行 fetch 操作。
  • 重新构造传入的对象。

对于使用上述方式产生的对象,请根据需要自行调用禁用 hook 的接口:

// 直接修改并保存对象不会再次触发 afterUpdate Hook 函数
request.object.set("foo", "bar");
request.object.save().then(function (obj) {
// 你的业务逻辑
});

// 如果有 fetch 操作,则需要在新获得的对象上调用 disableAfterHook 来确保不会再次触发 Hook 函数
request.object
.fetch()
.then(function (obj) {
obj.disableAfterHook();
obj.set("foo", "bar");
return obj.save();
})
.then(function (obj) {
// 你的业务逻辑
});

// 如果是其他方式构建对象,则需要在新构建的对象上调用 disableAfterHook 来确保不会再次触发 Hook 函数
var obj = AV.Object.createWithoutData("Post", request.object.id);
obj.disableAfterHook();
obj.set("foo", "bar");
obj.save().then(function (obj) {
// 你的业务逻辑
});

Hook 错误响应码

BeforeSave 这类的 hook 函数定义错误码,需要这样:

AV.Cloud.beforeSave("Review", function (request) {
// 使用 JSON.stringify() 将 object 变为字符串
throw new AV.Cloud.Error(
JSON.stringify({
code: 123,
message: "An error occurred.",
})
);
});

客户端收到的响应为 Cloud Code validation failed. Error detail: { "code": 123, "message": "An error occurred." }。可通过 截取字符串 的方式取出错误信息,再转换成需要的对象。

Hook 超时

Before 类 Hook 函数的超时时间为 10 秒,其他类 Hook 函数的超时时间为 3 秒。如果 Hook 函数被其他的云函数调用(比如因为保存对象而触发 BeforeSaveAfterSave),那么它们的超时时间会进一步被其他云函数调用的剩余时间限制。

例如,如果一个 BeforeSave 函数是被一个已经运行了 13 秒的云函数触发,那么它就只剩下 2 秒的时间来运行。同时请参考 云函数超时及处理方案

即时通讯 Hook

参见即时通讯指南第四篇的《万能的 Hook 机制》章节。

在线编写云函数

很多人使用云引擎是为了在服务端提供一些个性化的方法供各终端调用,而不希望关心诸如代码托管、npm 依赖管理等问题。为此我们提供了在线维护云函数的功能。使用此功能需要注意:

  • 在线定义的函数会覆盖你之前用 Git 或命令行部署的项目。
  • 目前只能在线编写云函数和 Hook,不支持托管静态网页、编写动态路由。
  • 只能使用 JavaScript SDK 和一些内置的 Node.js 模块(详见下节表格),无法引入其他模块作为依赖。

在线编写云函数

云服务控制台 > 云引擎 > 云引擎分组 > 部署 > 在线编辑 标签页,可以:

  • 创建函数:指定函数类型、函数名称、函数体的具体代码、注释等信息,点击「创建」即可创建一个云函数。函数类型包括 Function(普通云函数)、Hook、Global(这里可以定义多个云函数的公共逻辑)。
  • 部署:选择要部署的环境,点击「部署」即可看到部署过程和结果。
  • 预览:会将所有函数汇总并生成一个完整的代码段,可以确认代码,或者将其保存为 cloud.js 覆盖项目模板的同名文件,即可快速地转换为使用项目部署。
  • 维护云函数:可以编辑已有云函数,查看保存历史,以及删除云函数。

云函数编辑之后需要点击 部署 才能生效。

目前在线编辑仅支持 Node.js,最新的 v3 版本使用 Node.js 8.x 和 3.x 的 Node.js SDK,使用 Promise 写法,默认提供的依赖包有:async, bluebird, crypto, debug, ejs, jade, lodash, moment, nodemailer, qiniu, redis, request, request-promise, superagent, underscore, uuid, wechat-api, xml2js。

点击展开在线编辑 SDK 版本详情
在线编辑版本Node.js SDKJS SDKNode.js备注可用依赖
v00.x0.x0.12已不推荐使用moment, request, underscore
v11.x1.x4async, bluebird, co, ejs, handlebars, joi, lodash, marked, moment, q, request, superagent, underscore
v22.x2.x6需要使用 Promise 写法async, bluebird, crypto, debug, ejs, jade, lodash, moment, nodemailer, qiniu, redis, request, request-promise, superagent, underscore, uuid, wechat-api, xml2js
v33.x3.x8需要使用 Promise 写法async, bluebird, crypto, debug, ejs, jade, lodash, moment, nodemailer, qiniu, redis, request, request-promise, superagent, underscore, uuid, wechat-api, xml2js

从 v0 升级到 v1:

  • JS SDK 升级到了 1.0
  • 需要从 request.currentUser 获取用户,而不是 AV.User.current
  • 在调用 AV.Cloud.run 时需要手动传递 user 对象。

从 v1 升级到 v2:

  • JS SDK 升级到 2.0(必须使用 Promise,不再支持 callback 风格)。
  • 删除了 AV.Cloud.httpRequest
  • 在云函数中 必须 返回 Promise 作为云函数的值,抛出 AV.Cloud.Error 来表示错误。

从 v2 升级到 v3:

  • JS SDK 升级到了 3.0AV.Object.toJSON 的行为变化等)。
点击展开在线编辑和项目部署的关系

「在线编辑」的产生是为了方便大家初次体验云引擎,或者只是需要一些简单 hook 方法的应用使用。我们的实现方式就是把定义的函数拼接起来,生成一个云引擎项目然后部署。

所以可以认为「在线编辑」和「项目部署」最终是一样的,都是一个完整的项目。

定义函数是一个单独功能,可以让你不用使用基础包、git 等工具就能快速生成和编辑云引擎项目。

当然,你也可以使用基础包,自己写代码并部署项目。

这两条路是分开的,使用任何一种方式部署都会导致另一种方式失效。

点击展开如何从在线编辑迁移到项目部署
  1. 按照云引擎命令行工具使用指南安装命令行工具,使用 lean new 初始化项目,模板选择 Node.js > Express(我们的 Node.js 示例项目)。
  2. 云服务控制台 > 云引擎 > 云引擎分组 > 部署 > 在线编辑 点击 预览,将全部函数的代码拷贝到新建项目中的 cloud.js(替换掉原有内容)。
  3. 运行 lean up,在 http://localhost:3001 的调试界面中测试云函数和 Hook,然后运行 lean deploy 部署代码到云引擎(使用标准实例的用户还需要执行 lean publish)。
  4. 部署后请留意云引擎控制台上是否有错误产生。

如果在线编辑使用的是 0.x 版本的 Node.js SDK,那么还需要修改不兼容的代码。 比如将 AV.User.current() 改为 request.currentUser。 详见 升级到云引擎 Node.js SDK 1.0

查看和运行云函数

云服务控制台 > 云引擎 > 云函数 页面以表格的形式展示了应用各分组上定义的云函数(包括 Hook)的信息,包括云函数名称、所属分组、QPM(每分钟请求数)。在云函数表格中,点击 运行 按钮可以通过控制台调用云函数。通过左上角的控制按钮可以切换预备环境和生产环境。

这里显示的是应用下所有分组中的云函数,包括在线编辑也包括项目部署。

云函数列表

生产环境和预备环境

云引擎应用有「生产环境」和「预备环境」之分。在云引擎通过 SDK 调用云函数时,包括显式调用以及隐式调用(由于触发 hook 条件导致 hook 函数被调用),SDK 会根据云引擎所属环境(预备、生产)调用相应环境的云函数。例如,假定定义了 beforeDelete 云函数,在预备环境通过 SDK 删除一个对象,会触发预备环境的 beforeDelete hook 函数。

在云引擎以外的环境通过 SDK 显式或隐式调用云函数时,X-LC-Prod 的默认值一般为 1,也就是调用生产环境。但由于历史原因,各 SDK 的具体行为有一些差异:

  • 在 Node.js、PHP、Java、C# 这四个 SDK 下,默认总是调用生产环境的云函数。
  • 在 Python SDK 下,配合 lean-cli 本地调试时,且应用存在预备环境时,默认调用预备环境的云函数,其他情况默认调用生产环境的云函数。
  • 云引擎 Java 环境的模板项目 java-war-getting-startedspring-boot-getting-started 做了处理,配合 lean-cli 本地调试时,且应用存在预备环境时,默认调用预备环境的云函数,其他情况默认调用生产环境的云函数(与 Python SDK 的行为一致)。

你还可以在 SDK 中指定客户端将请求所发往的环境:

LCCloud.IsProduction = true; // production (default)
LCCloud.IsProduction = false; // staging

体验版云引擎应用只有「生产环境」,因此请不要切换到预备环境。

定时任务

定时任务可以按照设定,以一定间隔自动完成指定动作,比如半夜清理过期数据,每周一向所有用户发送推送消息等等。定时任务的最小时间单位是 ,正常情况下时间误差都可以控制在秒级别。

定时任务是普通的云函数,也会遇到 超时问题,具体请参考 超时处理方案

一个定时任务如果在 24 小时内收到了超过 30 次的 400(Bad Request)或 502(Bad Gateway)的应答,它将会被云引擎禁用,同时系统会向开发者发出相关的禁用通知邮件。在控制台的日志中,对应的错误信息为 timerAction short-circuited and no fallback available

部署云引擎之后,进入 云服务控制台 > 云引擎 > 定时任务,点击 创建定时任务,然后设定执行的函数名称、执行环境等等。例如定义一个打印循环打印日志的任务 logTimer

AV.Cloud.define("logTimer", function (request) {
console.log("This log is printed by logTimer.");
});

定时任务列表

云引擎支持两种定时任务:

  • 使用 Cron 表达式安排调度
  • 以分钟为单位的简单循环调度

以 Cron 表达式为例,比如每周一早上 8 点打印日志(运行之前定义的 logTimer 函数),创建定时任务的时候,选择 Cron 表达式 并填入 0 0 8 ? * MON

Cron 表达式的语法可以参考 云队列(Cloud Queue)开发指南 § CRON 表达式

在配置定时任务时可以指定一些额外的非必填选项:

  • 运行参数:传递给云函数的参数(JSON 对象)。
  • 异常策略:任务因云函数超时失败后重试执行还是放弃执行,详见 云队列指南 § 异常处理策略

「最近一次执行」会显示最近一次执行的时间和详情,但目前这个数据仅保留 5 分钟,在查看详情中:

  • status 任务的状态,包括 success(成功)、failed(失败)
  • uniqueId 任务的唯一 ID
  • finishedAt 执行完成的精确时间(仅限成功任务)
  • statusCode 云函数响应的 HTTP 状态码(仅限成功任务)
  • result 来自云函数的响应(仅限成功任务)
  • error 错误提示(仅限失败任务)
  • retryAt 下次重试时间(仅限失败任务)

定时任务的结果(执行日志)可以在 云服务控制台 > 云引擎 > 云引擎分组 > 日志 中查看。