Moralis 触发器

2022-05-12 11:12 更新

介绍

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

  • 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。


以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号