触发器是一种响应特定服务器端事件(例如将数据保存到特定集合时)执行操作的方法。 这些通常有两种情况。
触发器针对特定的集合(例如 EthTransactions
)进行操作。 例如,要在转移特定 NFT 时将订单对象的状态更新为“已售出”,您可以在 EthNFTTransfers
上定义 afterSave
触发器。 每次将新条目添加到 EthNFTTransfers
集合时都会调用触发器,您可以检查它是否与其中一个未结订单匹配。
测试网和主网上的交易可能需要一段时间才能得到确认。 当 Moralis 检测到处于未确认状态的新交易(或事件)时,这些交易将被放入带有confirmed:true集合中。 当交易被确认时,状态更新为confirmed:true。
这意味着如果您在集合上定义 afterSave
触发器,则触发器可以被触发两次——一次为confirmed:false,再次为confirmed:true。 任何具有confirmed属性的集合都可能发生这种情况。 考虑到这种行为,编写您的触发器回调函数。 如果触发器在其他集合中创建条目,则这可能导致重复条目或计算中的重复计数等。
可以通过使用 request.object
获取触发触发器的对象来检查确认状态。
Moralis.Cloud.afterSave("EthTransactions", async function (request) {
const confirmed = request.object.get("confirmed");
if (confirmed) {
// do something
} else {
// handle unconfirmed case
}
});
Ganache
和 Hardhat
仅在进行交易时处理新块。 这意味着,如果您铸造新的 NFT
或进行代币转移,这些交易可能会卡在待处理表中。 一次确认后,这些将被移出待处理。
在云中运行代码的另一个原因是强制执行特定的数据格式。 例如,您可能同时拥有一个 Android 和 iOS 应用程序,并且您希望验证每个应用程序的数据。 无需为每个客户端环境编写一次代码,您可以使用云代码只编写一次。
数据验证可以在触发器的代码中完成。
Moralis.Cloud.beforeSave("Review", (request) => {
throw "validation error";
}
});
如果函数抛出,那么 Review
对象将不会被保存并且客户端会得到一个错误。 如果没有抛出任何东西,对象将被正常保存。
一个有用的提示是,即使您的应用程序有许多不同的版本,相同版本的云代码也适用于所有版本。 因此,如果您启动的应用程序没有正确检查输入数据的有效性,您仍然可以通过使用 beforeSave
添加验证来解决此问题。
在某些情况下,您不想丢弃无效数据。 您只想在保存之前对其进行一些调整。 beforeSave
也可以处理这种情况。 您对 request.object
所做的任何调整都将被保存。
在我们的电影评论示例中,我们可能希望确保评论不会太长。 一条长评论可能很难显示。 我们可以使用 beforeSave
将评论字段截断为 140 个字符:
Moralis.Cloud.beforeSave("Review", (request) => {
const comment = request.object.get("comment");
if (comment.length > 140) {
// Truncate and add a ...
request.object.set("comment", comment.substring(0, 137) + "...");
}
});
如果您想对 Moralis JavaScript SDK 中的预定义类使用 beforeSave
,则不应为第一个参数传递字符串。 相反,您应该传递类本身,例如:
Moralis.Cloud.beforeSave(
Moralis.User,
async (request) => {
// code here
}
// Validation Object or Validation Function
);
在某些情况下,您可能希望在保存对象后执行某种类型的操作,例如推送。 您可以通过使用 afterSave
方法注册处理程序来做到这一点。 例如,假设您想要跟踪博客文章的评论数量。 您可以通过编写如下函数来做到这一点:
const logger = Moralis.Cloud.getLogger();
Moralis.Cloud.afterSave("Comment", (request) => {
const query = new Moralis.Query("Post");
query
.get(request.object.get("post").id)
.then(function (post) {
post.increment("comments");
return post.save();
})
.catch(function (error) {
logger.error("Got an error " + error.code + " : " + error.message);
});
});
在上面的示例中,客户端将在处理程序中的承诺完成之前收到成功的响应,无论承诺如何解决。 例如,即使处理程序抛出异常,客户端也会收到成功的响应。 运行处理程序时发生的任何错误都可以在云代码日志中找到。
在将响应发送回客户端后,您可以使用 afterSave
处理程序执行冗长的操作。 为了在 afterSave
处理程序完成之前响应客户端,您的处理程序可能不会返回承诺,并且您的 afterSave
处理程序可能不会使用 async/await
。
如果您想将 afterSave
用于 Moralis JavaScript SDK 中的预定义类,则不应为第一个参数传递字符串。 相反,您应该传递类本身,例如:
Moralis.Cloud.afterSave(Moralis.User, async (request) => {
// code here
});
保存 Moralis.Object
时,您可以传递可在 Cloud Code Save Triggers
中访问的上下文字典。
上下文也从 beforeSave
处理程序传递到 afterSave
处理程序。 以下示例异步向正在添加到 Moralis.Role
的用户关系的用户发送电子邮件,以便客户端在电子邮件完成发送之前收到响应:
const beforeSave = function beforeSave(request) {
const { object: role } = request;
// Get users that will be added to the users relation.
const usersOp = role.op("users");
if (usersOp && usersOp.relationsToAdd.length > 0) {
// add the users being added to the request context
request.context = { buyers: usersOp.relationsToAdd };
}
};
const afterSave = function afterSave(request) {
const { object: role, context } = request;
if (context && context.buyers) {
const purchasedItem = getItemFromRole(role);
const promises = context.buyers.map(emailBuyer.bind(null, purchasedItem));
item.increment("orderCount", context.buyers.length);
promises.push(item.save(null, { useMasterKey: true }));
Promise.all(promises).catch(request.log.error.bind(request.log));
}
};
您可以在删除对象之前运行自定义云代码。 您可以使用 beforeDelete
方法执行此操作。 例如,这可用于实现比通过 ACLs
表达的内容更复杂的受限删除策略。 例如,假设您有一个相册应用程序,其中许多照片与每个相册相关联,并且您希望阻止用户删除仍然有照片的相册。 您可以通过编写如下函数来做到这一点:
Moralis.Cloud.beforeDelete("Album", async (request) => {
const query = new Moralis.Query("Photo");
query.equalTo("album", request.object);
const count = await query.count({ useMasterKey: true });
if (count > 0) {
throw "Can't delete album if it still has photos.";
}
});
如果函数抛出,Album
对象不会被删除,客户端会报错。 否则,对象将被正常删除。
如果您想对 Moralis JavaScript SDK
中的预定义类使用 beforeDelete
,则不应为第一个参数传递字符串。 相反,您应该传递类本身,例如:
Moralis.Cloud.beforeDelete(Moralis.User, async (request) => {
// code here
});
在某些情况下,您可能希望在删除对象后执行某种类型的操作,例如推送。 您可以通过使用 afterDelete
方法注册处理程序来完成此操作。 例如,假设在删除博客文章后,您还想删除所有关联的评论。 您可以通过编写如下函数来做到这一点:
const logger = Moralis.Cloud.getLogger();
Moralis.Cloud.afterDelete("Post", (request) => {
const query = new Moralis.Query("Comment");
query.equalTo("post", request.object);
query
.find()
.then(Moralis.Object.destroyAll)
.catch((error) => {
logger.error(
"Error finding related comments " + error.code + ": " + error.message
);
});
});
afterDelete
处理程序可以访问通过 request.object
删除的对象。 此对象已完全提取,但无法重新提取或重新保存。
在处理程序终止后,客户端将收到对删除请求的成功响应,无论 afterDelete
是如何终止的。 例如,即使处理程序抛出异常,客户端也会收到成功的响应。 运行处理程序时发生的任何错误都可以在云代码日志中找到。
如果您想对 Moralis JavaScript SDK
中的预定义类使用 afterDelete
,则不应为第一个参数传递字符串。 相反,您应该传递类本身,例如:
Moralis.Cloud.afterDelete(Moralis.User, async (request) => {
// code here
});
使用 beforeSaveFile
方法,您可以在保存任何文件之前运行自定义云代码。 返回一个新的 Moralis.File
将保存新文件,而不是客户端发送的文件。
例如
// Changing the file name
Moralis.Cloud.beforeSaveFile(async (request) => {
const { file } = request;
const fileData = await file.getData();
const newFile = new Moralis.File("a-new-file-name.txt", { base64: fileData });
return newFile;
});
// Returning an already saved file
Moralis.Cloud.beforeSaveFile((request) => {
const { user } = request;
const avatar = user.get("avatar"); // this is a Moralis.File that is already saved to the user object
return avatar;
});
// Saving a different file from uri
Moralis.Cloud.beforeSaveFile((request) => {
const newFile = new Moralis.File("some-file-name.txt", {
uri: "www.somewhere.com/file.txt",
});
return newFile;
});
向您的文件添加元数据和标签允许您向存储在您的存储解决方案(即 AWS S3)中的文件添加额外的数据位。 beforeSaveFile
钩子是在文件上设置元数据和/或标签的好地方。
注意:并非所有存储适配器都支持元数据和标签。 检查您正在使用的存储适配器的文档以了解兼容性。
// Adding metadata and tags
Moralis.Cloud.beforeSaveFile((request) => {
const { file, user } = request;
file.addMetadata("createdById", user.id);
file.addTag("groupId", user.get("groupId"));
});
afterSaveFile
方法是跟踪应用程序中存储的所有文件的好方法。 例如:
Moralis.Cloud.afterSaveFile(async (request) => {
const { file, fileSize, user } = request;
const fileObject = new Moralis.Object("FileObject");
fileObject.set("file", file);
fileObject.set("fileSize", fileSize);
fileObject.set("createdBy", user);
const token = { sessionToken: user.getSessionToken() };
await fileObject.save(null, token);
});
您可以在删除任何文件之前运行自定义云代码。 例如,假设您要添加只允许创建文件的用户删除文件的逻辑。 您可以使用 afterSaveFile
和 beforeDeleteFile
方法的组合,如下所示:
Moralis.Cloud.afterSaveFile(async (request) => {
const { file, user } = request;
const fileObject = new Moralis.Object('FileObject');
fileObject.set('fileName', file.name());
fileObject.set('createdBy', user);
await fileObject.save(null, { useMasterKey: true );
});
Moralis.Cloud.beforeDeleteFile(async (request) => {
const { file, user } = request;
const query = new Moralis.Query('FileObject');
query.equalTo('fileName', file.name());
const fileObject = await query.first({ useMasterKey: true });
if (fileObject.get('createdBy').id !== user.id) {
throw 'You do not have permission to delete this file';
}
});
在上面的 beforeDeleteFile
示例中,FileObject
集合用于跟踪应用程序中保存的文件。 成功删除文件后,afterDeleteFile
触发器是清理这些对象的好地方。
Moralis.Cloud.afterDeleteFile(async (request) => {
const { file } = request;
const query = new Moralis.Query("FileObject");
query.equalTo("fileName", file.name());
const fileObject = await query.first({ useMasterKey: true });
await fileObject.destroy({ useMasterKey: true });
});
在某些情况下,您可能希望转换传入查询、添加额外限制或增加默认限制、添加额外包含或将结果限制为键的子集。 您可以使用 beforeFind
触发器来执行此操作。
例如
// Properties available
Moralis.Cloud.beforeFind("MyObject", (req) => {
let query = req.query; // the Moralis.Query
let user = req.user; // the user
let triggerName = req.triggerName; // beforeFind
let isMaster = req.master; // if the query is run with masterKey
let isCount = req.count; // if the query is a count operation
let logger = req.log; // the logger
let installationId = req.installationId; // The installationId
});
// Selecting keys
Moralis.Cloud.beforeFind("MyObject", (req) => {
let query = req.query; // the Moralis.Query
// Force the selection on some keys
query.select(["key1", "key2"]);
});
// Asynchronous support
Moralis.Cloud.beforeFind("MyObject", (req) => {
let query = req.query;
return aPromise().then((results) => {
// do something with the results
query.containedIn("key", results);
});
});
// Returning a different query
Moralis.Cloud.beforeFind("MyObject", (req) => {
let query = req.query;
let otherQuery = new Moralis.Query("MyObject");
otherQuery.equalTo("key", "value");
return Moralis.Query.or(query, otherQuery);
});
// Rejecting a query
Moralis.Cloud.beforeFind("MyObject", (req) => {
// throw an error
throw new Moralis.Error(101, "error");
// rejecting promise
return Promise.reject("error");
});
// Setting the read preference for a query
Moralis.Cloud.beforeFind("MyObject2", (req) => {
req.readPreference = "SECONDARY_PREFERRED";
req.subqueryReadPreference = "SECONDARY";
req.includeReadPreference = "PRIMARY";
});
如果要将 beforeFind
用于 Moralis JavaScript SDK
中的预定义类,则不应为第一个参数传递字符串。 相反,您应该传递类本身,例如:
Moralis.Cloud.beforeFind(Moralis.User, async (request) => {
// code here
});
在某些情况下,您可能希望在将查询结果发送到客户端之前对其进行操作。 您可以使用 afterFind
触发器执行此操作。
Moralis.Cloud.afterFind("MyCustomClass", async (request) => {
// code here
});
如果要将 afterFind
用于 Moralis JavaScript SDK
中的预定义类,则不应为第一个参数传递字符串。 相反,您应该传递类本身,例如:
Moralis.Cloud.afterFind(Moralis.User, async (request) => {
// code here
});
有时您可能希望对登录请求运行自定义验证。 beforeLogin
触发器可用于阻止帐户登录(例如,如果他们被禁止),记录登录事件以进行分析,如果登录发生在不寻常的 IP 地址上,则通过电子邮件通知用户等等。
Moralis.Cloud.beforeLogin(async (request) => {
const { object: user } = request;
if (user.get("isBanned")) {
throw new Error("Access denied, you have been banned.");
}
});
需要注意的一些注意事项
promise
解决。 beforeLogin
成功完成之后,才向用户提供会话。 Moralis.User
上的 afterSave
一样,除非明确保存,否则它不会将突变保存到用户。触发器将运行
authProvider
登录时。触发器不会运行
有时您可能希望在用户注销后运行操作。 例如,afterLogout
触发器可用于用户注销后的清理操作。 触发器包含已在注销时删除的会话对象。 从此会话对象中,您可以确定注销以执行用户特定任务的用户。
Moralis.Cloud.afterLogout(async (request) => {
const { object: session } = request;
const user = session.get("user");
user.set("isOnline", false);
user.save(null, { useMasterKey: true });
});
需要注意的一些注意事项
afterDelete
触发器一样,请求中包含的 _Session
对象已被删除。触发器将运行
_Session
对象时。触发器不会运行
_Session
对象。 _Session
对象被删除,而用户没有通过调用 SDK 的 logout 方法注销。您可以在用户尝试使用 beforeConnect
方法连接到您的 LiveQuery 服务器之前运行自定义云代码。 例如,这可用于仅允许已登录的用户连接到 LiveQuery 服务器。
Moralis.Cloud.beforeConnect((request) => {
if (!request.user) {
throw "Please login before you attempt to connect.";
}
});
大多数情况下,connect
事件在客户端第一次调用 subscribe
时被调用。 如果这是您的用例,您可以使用此事件侦听错误。
const logger = Moralis.Cloud.getLogger();
Moralis.LiveQuery.on("error", (error) => {
logger.info(error);
});
在某些情况下,您可能希望转换传入的订阅查询。 示例包括添加额外限制、增加默认限制、添加额外包含或将结果限制为键的子集。 您可以使用 beforeSubscribe
触发器来执行此操作。
Moralis.Cloud.beforeSubscribe("MyObject", (request) => {
if (!request.user.get("Admin")) {
throw new Moralis.Error(
101,
"You are not authorized to subscribe to MyObject."
);
}
let query = request.query; // the Moralis.Query
query.select("name", "year");
});
在某些情况下,您可能希望在将实时查询的结果发送到客户端之前对其进行操作。 您可以使用 afterLiveQueryEvent
触发器执行此操作。
例如
// Changing values on object and original
Moralis.Cloud.afterLiveQueryEvent("MyObject", (request) => {
const object = request.object;
object.set("name", "***");
const original = request.original;
original.set("name", "yolo");
});
// Prevent LiveQuery trigger unless 'foo' is modified
Moralis.Cloud.afterLiveQueryEvent("MyObject", (request) => {
const object = request.object;
const original = request.original;
if (!original) {
return;
}
if (object.get("foo") != original.get("foo")) {
request.sendEvent = false;
}
});
默认情况下,MoralisLiveQuery
不执行需要额外数据库操作的查询。 这是为了让您的 Moralis Server
尽可能快速和高效。 如果您需要此功能,您可以在 afterLiveQueryEvent
中执行这些功能。
// Including an object on LiveQuery event, on update only.
Moralis.Cloud.afterLiveQueryEvent("MyObject", async (request) => {
if (request.event != "update") {
request.sendEvent = false;
return;
}
const object = request.object;
const pointer = object.get("child");
await pointer.fetch();
});
// Extend matchesQuery functionality to LiveQuery
Moralis.Cloud.afterLiveQueryEvent("MyObject", async (request) => {
if (request.event != "create") {
return;
}
const query = request.object.relation("children").query();
query.equalTo("foo", "bart");
const first = await query.first();
if (!first) {
request.sendEvent = false;
}
});
afterLiveQueryEvent
触发器完成之前,不会触发实时查询事件。 确保触发器内的任何功能都有效且具有限制性,以防止出现瓶颈。有时您可能希望监视要与第三方(例如“Datadog
”)一起使用的实时查询事件。 onLiveQueryEvent
触发器可以记录触发的事件、连接的客户端数量、订阅数量和错误。
Moralis.Cloud.onLiveQueryEvent(
({
event,
client,
sessionToken,
useMasterKey,
installationId,
clients,
subscriptions,
error,
}) => {
if (event !== "ws_disconnect") {
return;
}
// Do your magic
}
);
“connect”与“ws_connect”不同,前者意味着客户端完成了Moralis Live Query协议定义的连接过程,其中“ws_connect”只是意味着创建了一个新的websocket。