Moralis Web3 企业级API Moralis 触发器

2024-02-26 开发教程 Moralis Web3 企业级API 匿名 7

介绍

触发器是一种响应特定服务器端事件(例如将数据保存到特定集合时)执行操作的方法。 这些通常有两种情况。

  • Before X:在 X 发生之前调用触发器。
  • After X:在 X 发生后调用触发器。

触发器针对特定的集合(例如 ​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 方法注销。

LiveQuery 触发器

连接前

您可以在用户尝试使用 ​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

在某些情况下,您可能希望在将实时查询的结果发送到客户端之前对其进行操作。 您可以使用 ​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 ​触发器完成之前,不会触发实时查询事件。 确保触发器内的任何功能都有效且具有限制性,以防止出现瓶颈。

onLiveQueryEvent

有时您可能希望监视要与第三方(例如“​Datadog​”)一起使用的实时查询事件。 ​onLiveQueryEvent ​触发器可以记录触发的事件、连接的客户端数量、订阅数量和错误。

Moralis.Cloud.onLiveQueryEvent(
({
event,
client,
sessionToken,
useMasterKey,
installationId,
clients,
subscriptions,
error,
}) => {
if (event !== "ws_disconnect") {
return;
}
// Do your magic
}
);

事件

  • connect
  • subscribe
  • unsubscribe
  • ws_connect
  • ws_disconnect
  • ws_disconnect_error

“connect”与“ws_connect”不同,前者意味着客户端完成了Moralis Live Query协议定义的连接过程,其中“ws_connect”只是意味着创建了一个新的websocket。