Drupal 10:使用消息和 ECA 模块创建通知系统
注意:这篇文章发布已超过两年,因此其中包含的信息可能已过时。如果你发现了问题,请留言,我公司会尽力更正。
2023 年 7 月 23 日 - 阅读时长 31 分钟
Drupal是一个非常优秀的创建用户社区的平台,它允许用户创建自己的内容并相互交流。让用户持续关注网站的一个有效方法是,当其他用户与他们创建的内容进行交互时,及时通知他们。
在领英(LinkedIn)或脸书(Facebook)等网站上,这种情况很常见。当有用户对你的帖子进行评论或点赞时,你会收到相应的通知。在Drupal开发中,只需使用几个模块(再加上几行代码),就可以实现同样的功能。
在本文中,我将介绍如何创建一个通知系统,向用户告知他们可能感兴趣的重要事件。我们将尽可能使用社区贡献的模块来实现这一目标,不过,还需要使用一个小型的PHP类将一些组件串联起来。
一、安装所需模块
为了让通知系统正常运行,需要安装以下模块:
- 消息(Message) - 该模块允许创建简单的消息模板,后续可以使用这些模板为用户创建消息。我们将把这些消息用作通知本身。模板类型用于定义我们要通知用户的交互类型。
- ECA - ECA即 事件(Event)、条件(Condition)、动作(Action),这是一个功能强大的模块,它允许对事件做出响应,并对这些事件执行相应的动作。这让我们能够对网站上发生的事件做出反应,并根据这些事件生成通知。
- BPMN.IO - 这是ECA模块使用的一个模块,它允许使用BPMN.io库创建工作流。
- 令牌(Token) - Drupal提供了一个功能,允许用户在某些字段中使用简单的令牌替换系统。例如,如果你想在某个字段中动态显示用户姓名,这个功能就很有用。令牌模块用于扩展可用令牌的核心集,是我们这里使用的一些高级令牌所必需的。
要将所有这些模块添加到网站,需要运行以下Composer命令:
composer require drupal/message drupal/eca drupal/bpmn_io drupal/token
将这些模块添加到代码库后,就可以使用Drush用一个命令安装所需的模块:
drush en message eca eca_base eca_content eca_ui eca_modeller_bpmn bpmn_io token
我们在这里安装的模块,有些是模块本身,有些是启用某些功能所需的子模块。ECA模块具有很高的模块化程度,但我们需要启用ECA内容模块,以便与内容实体进行交互。
所有模块都安装并启用后,我们就可以开始配置网站了。
二、设置消息模板
我们首先要做的是使用消息模块创建一个消息模板。
消息模板的工作方式与Drupal中的其他实体类似,你会看到一个带有一些设置的编辑屏幕,并且可以向消息中添加字段。我们这里需要做的是添加一个使用令牌的消息,以便在执行某个动作时生成消息。
作为一个示例通知,我将使用用户对文章进行评论时创建的动作。当这种情况发生时,我们将创建一个类型为 “new_comment_created” 的消息,并将其分配给将接收通知的用户。
以下是这个消息模板的基本设置。
模板设置好后,我们现在需要为其添加一个字段。这个字段将构成附加到通知的数据,在这种情况下,它是对创建的评论的引用。
[message:field_message_comment_ref:entity:author:name] 对 [message:field_message_comment_ref:entity:entity:title] 进行了评论
我们在这段标记中使用了以下令牌:
- [message:field_message_comment_ref:entity:entity:url:path] - 原文章的URL。
- [message:field_message_comment_ref:entity:author:name] - 创建评论的用户的姓名。
- [message:field_message_comment_ref:entity:entity:title] - 原文章的标题。
根据消息模板生成一个消息实体。然后,我们可以使用ECA模块在网站上触发某个特定事件时运行这个动作。
这里的代码包含了一系列检查:
- 如果传递给这个动作的对象不是评论,那么就说明出现了问题,我们不会创建通知。
- 如果评论实体是孤立的(没有父实体),那么也不做任何操作。如果我们进行迁移,并且在评论实体能够关联到内容页面之前就创建了它,就可能会出现这种情况。
- 如果原内容页面的作者也是评论的作者,那么就不发送通知。这是为了避免产生不必要的干扰,因为用户对自己的文章进行评论时,他们已经知道自己在这么做了。
以下是动作插件的PHP类。这个类位于自定义模块中名为 src/Plugins/Action 的目录下。我添加了注释来说明代码的各个部分的功能。
<?php
namespace Drupal\notifications\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\message\Entity\Message;
use Drupal\Core\Access\AccessResult;
/**
* 创建一条消息。
*
* @Action(
* id = "notification_action_comment_created",
* label = @Translation("评论已创建"),
* type = "comment"
* )
*/
class CommentCreated extends ActionBase {
/**
* {@inheritDoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
// ECA 模块将对动作进行访问检查,因此我们需要
// 确保所有用户都能够执行此动作。
$result = AccessResult::allowed();
return $return_as_object ? $result : $result->isAllowed();
}
/**
* {@inheritDoc}
*/
public function execute($comment = NULL) {
if (empty($this->configuration['comment'])) {
$this->configuration['comment'] = $comment;
}
if ($comment === NULL) {
// 评论实体未设置,因此我们忽略此动作。
return;
}
if ($comment->getCommentedEntity() === NULL) {
// 这是一个孤立的评论,可能在迁移过程中出现。
return;
}
// 消息的所有者是被评论的原始节点的发布者。
$user = $comment->getCommentedEntity()->get('uid')->referencedEntities()[0];
$ownerUid = $user->id();
// 首先,检查确保我们不会通知用户关于他们自己的评论。
if ($ownerUid === $comment->get('uid')->target_id) {
return;
}
// 创建消息实体,其所有者是接收通知的人。
$message = Message::create([
'template' => 'new_comment_created',
'uid' => $ownerUid,
]);
$message->set('field_message_comment_ref', $comment);
$message->set('langcode', 'en');
$message->save();
}
}
请注意,这里的关键部分是我们从 “new_comment_created” 模板创建消息,并将文章的作者设为该消息的所有者。这意味着该消息 “属于” 正确的用户,他们可以根据需要查看或删除该消息。
当然,这个动作本身并不会执行任何操作,所以让我们创建一个ECA动作来运行这个动作插件。
三、ECA工作流
针对此事件的ECA工作流非常简单。我们只需要一个在评论创建时触发的事件,然后运行评论创建通知动作。
以下是ECA工作流的截屏。
要创建此工作流,需要添加一个开始事件来监听评论的创建。这里使用的模板是 “插入内容实体”,需要选择的实体类型是 “评论”。如果你想限制插入的评论类型,可以进一步细化设置。
你的ECA对话框应该类似如下所示。
通知是一个任务,我们要应用 “通知评论创建的作者” 模板。其他所有操作都由我们之前创建的通知动作处理。
有了这个ECA工作流,当用户对其他用户的内容进行评论时,就会创建一条消息。
我确实考虑过让这个系统更加复杂,让不同的事件触发不同的操作。不过,我认为这种方式可以创建一个非常模块化的通知系统。此外,这也意味着动作类(如上面的CommentCreated动作)可以是单一职责类。这意味着我们也可以对动作类进行测试,更有把握确保它们按预期工作。
这里也有一个观点,即把添加到CommentCreated动作中的一些检查移到ECA工作流中,这当然是可行的,因为ECA模块允许向工作流中添加检查。虽然这是可行的,但我还是直接将这些检查添加到了CommentCreated动作插件中,这样它们就可以成为动作单元测试的一部分。这实际上取决于个人偏好。
四、通知页面
为了向用户展示他们收到的通知,最好创建一个页面来显示该用户的所有通知。最简单的方法是使用视图(Views)模块,因为消息模块与视图配合得很好。
要设置一个视图,你只需确保使用消息作为基础创建一个视图,然后将当前用户作为上下文过滤器添加进去。这意味着当用户访问该页面时,他们将看到属于自己的消息。通过按创建日期降序排列通知,我们可以先显示最新的通知。
如果我们以登录用户身份访问 /notifications 页面,将看到类似以下内容(假设存在要渲染的消息)。
就是这么简单。我们在这里创建的通知页面可以根据你的需求进行调整或样式设置。甚至可以将其改为表格视图,以便更方便地管理用户的通知。
五、通知的已读状态
一旦通知被用户看到,就应该将其标记为已读。这是一项标准功能,允许用户在通知列表中跟踪哪些是新通知,哪些是已经看过的。已读状态还可以用于统计未读消息的数量,向用户显示他们有多少条新消息。
有几种方法可以实现这个效果,但最好的方法之一是使用标记(Flag)模块。标记模块使我们能够为网站中的任何实体(包括消息)添加任意标记。因此,我们可以为消息添加一个标记作为元数据,以显示该消息是否已读。
要安装标记模块,我们只需使用Composer引入它,然后使用Drush进行安装:
composer require drupal/flag
drush en flag
如何设置标记取决于个人偏好。你可以在消息生成时添加一个标记,表明它是 “未读” 的;也可以在用户与消息交互后,为其添加一个标记,表明它已 “已读”。标记应该始终作为个人标记创建,因为它需要属于与消息所属相同的用户。
我更喜欢在用户阅读消息后为其设置标记。这样可以使消息的创建过程简洁明了,因为这意味着我们不需要在创建消息的同时添加额外的步骤来设置标记。然后,这个已读状态将用于影响界面,在用户查看通知流时向他们显示已读/未读状态。
在这种情况下,我们将创建一个名为 “message_read” 的标记,并在用户阅读消息后将其附加到消息上。以下代码可用于以这种方式生成标记:
$flagging = $flaggingStorage->create([
'flag_id' => 'message_read',
'entity_type' => 'message',
'entity_id' => $message->id(),
'uid' => $user->id(),
'created' => time(),
'session_id' => NULL,
]);
$flagging->save();
如何触发已读状态标记由你决定。我通过使用点击事件触发一个AJAX回调来标记通知为已读,效果很好。当用户点击通知时,AJAX回调完成任务后,会将用户带到通知所引用的页面。要实现这个功能需要一些代码,并且复杂到足以单独写一篇文章了。但如果大家对此感兴趣,我公司会写一篇后续文章来介绍这方面的内容。
有了标记之后,我们可以修改原始的通知视图以包含此数据,这可以简单到在通知列表中添加已读状态。
这里还有一个好主意,就是先按标记状态对通知进行排序,然后再按创建日期排序。这样,未读通知将显示在列表顶部,接下来是已读通知,列表的每个部分都按创建日期排序。
六、清理通知
创建通知后,需要考虑何时删除这些消息。消息模块已经考虑到了这一点,它内置了一个清理系统,你可以进行配置,以清除数据库中的消息。以下是消息清理设置的配置界面。
请注意,这些是消息模块的默认设置;根据你运行的系统,你可能希望将这些设置调高很多。“配额” 方法比较强硬,当消息表达到一定大小时,会直接删除任何消息。
有趣的是,这些设置由一个插件系统驱动,因此你可以创建自己的消息清理(MessagePurge)插件来更改消息的删除方式。默认情况下,消息模块将创建的消息视为一个单一的消息集合,但你可能希望按用户分别处理,这样通知数量较少的用户的消息就不会因为通知数量多的用户的操作而被清理掉。
也可以为单个消息配置清理操作,如果你希望更重要的通知绕过这个清理过程,这将非常有用。
七、进一步改进
我们的改进可以不止于此,还有许多其他方面可以对这个系统进行优化。由于我们使用了消息模块来创建通知消息,我们也可以利用其他消息模块社区为网站添加额外的功能。
例如,我们可以使用消息摘要(Message Digest)模块来创建用户每周收到的新通知列表。该列表将以电子邮件的形式发送给用户,让他们可以返回网站查看通知的详细内容。在Drupal模块开发中,这种方式能为用户带来更好的体验。
还要记住,并非所有用户都希望接收所有类型的通知,因此最好让用户能够选择不接收某些通知,或者至少能够选择不接收某些类型的通知。
八、总结
现在,我们已经拥有了一个完整的、可正常运行的通知系统,它仅使用了几个模块和一些自定义代码构建而成。自定义代码包括一个用于生成消息的动作插件和一些用于为消息添加标记状态的代码,其余部分均使用已安装的模块和Drupal核心功能。任何需要的额外功能都可以使用围绕消息模块的社区模块生态系统来添加。
一旦你搭建好了这个系统的基础,就可以将通知功能扩展到包含各种不同的操作。上面的示例是在评论创建时创建通知,但我们可以将其扩展到包括内容被点赞时,或者内容通过工作流系统并发布时的情况。要注意不要用大量通知淹没用户,或者,如果有大量通知,就让用户能够关闭其中一些,以减少系统产生的干扰。
如果你希望我公司将配置和代码整合在一起,并在Drupal.org上打包成一个通知模块,请告诉我。我公司为其创建这个系统的网站有一些非常具体的需求,但如果有足够的人感兴趣,我公司会考虑创建一组更通用的动作,用于向用户通知不同的事件。请在下面留言告知我公司你的想法!


