【问题标题】:Notification system using php and mysql使用 php 和 mysql 的通知系统
【发布时间】:2015-09-22 13:11:18
【问题描述】:

我想为我们学校实现一个通知系统,它是一个不向公众开放的php/mysql webapp,所以它不会收到太多流量。 “每天 500-1000 名访客”。

1.我最初的方法是使用 MYSQL 触发器:

我使用 Mysql AFTER INSERT trigger 将记录添加到名为 notifications 的表中。类似的东西。

'CREATE TRIGGER `notify_new_homwork` AFTER INSERT ON `homeworks`
 FOR EACH ROW INSERT INTO `notifications` 
    ( `from_id`, `note`, `class_id`) 
 VALUES 
    (new.user_id, 
        concat('A New homework Titled: "',left(new.title,'50'),
        '".. was added' )
    ,new.subject_id , 11);'

这种黑魔法效果很好,但我无法跟踪此通知是否是新的“为用户显示新通知的数量”。 所以我添加了一个名为通知的页面。

通过类似的方式检索通知

SELECT n.* from notifications n 
JOIN user_class on user_class.class_id = n.class_id where user_class.user_id = X;

注意:表 user_class 将用户链接到班级“user_id,class_id,subject_id” - 除非用户是教师,否则主题为空

现在我的下一个挑战是。

  1. 如何跟踪每个用户的新旧通知?
  2. 如何将与用户相似的通知汇总到一行中?

例如,如果 2 个用户对某事发表了评论,则不要插入新行,只需将旧行更新为“userx 和 1 个其他人对 hw 发表评论”之类的内容。

非常感谢

编辑

根据下面的答案,要在行上设置已读/未读标志,我需要为每个学生设置一行,而不仅仅是为整个班级设置一行。这意味着将触发器编辑为类似

insert into notifications (from_id,note,student_id,isread)
select new.user_id,new.note,user_id,'0' from user_class where user_class.class_id = new.class_id group by user_class.user_id

【问题讨论】:

  • 您对此有任何更新或自己的答案吗?您从 9 个月前开始尝试过什么?
  • 如果有人给你一个可靠的答案,你应该包括你的班级和学生表
  • 你找到你在哪里寻找@Zalaboza 或者你想要另一个解决方案吗?如果以下答案对您没有帮助,我不确定您到底想要什么。请赐教,以便我可以帮助您。

标签: php mysql triggers notifications


【解决方案1】:

这个问题已经 9 个月大了,所以我不确定 OP 是否仍然需要答案,但由于有很多观点和美味的赏金,我还想添加我的芥末(德国谚语..)。

在这篇文章中,我将尝试做一个简单的解释示例,说明如何开始构建通知系统。

编辑:好吧,结果比我预期的要长得多。最后真的累了,对不起。

WTLDR;

问题 1:在每个通知上都有一个标记。

问题 2: 仍然将每个通知作为单个记录存储在数据库中,并在请求时将它们分组。


结构

我假设通知看起来像:

+---------------------------------------------+
| ▣ James has uploaded new Homework: Math 1+1 |
+---------------------------------------------+
| ▣ Jane and John liked your comment: Im s... | 
+---------------------------------------------+
| ▢ The School is closed on independence day. |
+---------------------------------------------+

在窗帘后面可能看起来像这样:

+--------+-----------+--------+-----------------+-------------------------------------------+
| unread | recipient | sender | type            | reference                                 |
+--------+-----------+--------+-----------------+-------------------------------------------+
| true   | me        | James  | homework.create | Math 1 + 1                                |
+--------+-----------+--------+-----------------+-------------------------------------------+
| true   | me        | Jane   | comment.like    | Im sick of school                         |
+--------+-----------+--------+-----------------+-------------------------------------------+
| true   | me        | John   | comment.like    | Im sick of school                         |
+--------+-----------+--------+-----------------+-------------------------------------------+
| false  | me        | system | message         | The School is closed on independence day. |
+--------+-----------+--------+-----------------+-------------------------------------------+

注意:我不建议在数据库中对通知进行分组,在运行时这样做可以使事情更加灵活。

  • 未读
    每个通知都应该有一个标志来指示收件人是否已经打开了通知。
  • 收件人
    定义接收通知的人员。
  • 发件人
    定义谁触发了通知。
  • 类型
    而不是让数据库中的每条消息都以纯文本形式创建类型。通过这种方式,您可以为后端内的不同通知类型创建特殊处理程序。将减少存储在数据库中的数据量,并为您提供更大的灵活性,可以轻松翻译通知、更改过去的消息等。
  • 参考
    大多数通知都会引用您的数据库或应用程序中的记录。

我一直在研究的每个系统在通知上都有一个简单的 1 对 1 引用关系,您可能会有 1 对 n 记住,我将以 1:1 继续我的示例。这也意味着我不需要定义引用什么类型的对象的字段,因为这是由通知类型定义的。

SQL 表

现在,在为 SQL 定义真正的表结构时,我们在数据库设计方面做出了一些决定。我将采用最简单的解决方案,如下所示:

+--------------+--------+---------------------------------------------------------+
| column       | type   | description                                             |
+--------------+--------+---------------------------------------------------------+
| id           | int    | Primary key                                             |
+--------------+--------+---------------------------------------------------------+
| recipient_id | int    | The receivers user id.                                  |
+--------------+--------+---------------------------------------------------------+
| sender_id    | int    | The sender's user id.                                   |
+--------------+--------+---------------------------------------------------------+
| unread       | bool   | Flag if the recipient has already read the notification |
+--------------+--------+---------------------------------------------------------+
| type         | string | The notification type.                                  |
+--------------+--------+---------------------------------------------------------+
| parameters   | array  | Additional data to render different notification types. |
+--------------+--------+---------------------------------------------------------+
| reference_id | int    | The primary key of the referencing object.              |
+--------------+--------+---------------------------------------------------------+
| created_at   | int    | Timestamp of the notification creation date.            |
+--------------+--------+---------------------------------------------------------+

或者对于懒人来说,SQL 创建表命令

CREATE TABLE `notifications` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `recipient_id` int(11) NOT NULL,
  `sender_id` int(11) NOT NULL,
  `unread` tinyint(1) NOT NULL DEFAULT '1',
  `type` varchar(255) NOT NULL DEFAULT '',
  `parameters` text NOT NULL,
  `reference_id` int(11) NOT NULL,
  `created_at` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

PHP 服务

此实现完全取决于您的应用程序的需求,注意:这不是关于如何在 PHP 中构建通知系统的黄金标准的示例。

通知模型

这是一个通知本身的基本模型示例,只是需要的属性和抽象方法messageForNotificationmessageForNotifications 我们期望在不同的通知类型中实现。

abstract class Notification
{
    protected $recipient;
    protected $sender;
    protected $unread;
    protected $type;
    protected $parameters;
    protected $referenceId;
    protected $createdAt;

    /**
     * Message generators that have to be defined in subclasses
     */
    public function messageForNotification(Notification $notification) : string;
    public function messageForNotifications(array $notifications) : string;

    /**
     * Generate message of the current notification.
     */ 
    public function message() : string
    {
        return $this->messageForNotification($this);
    }
}

你必须自己添加一个constructorgetterssetters之类的东西,我是不会提供现成的通知系统。

通知类型

现在您可以为每种类型创建一个新的Notification 子类。以下示例将处理评论的 like 操作

  • Ray 喜欢您的评论。 (1 条通知)
  • John 和 Jane 喜欢您的评论。 (2 个通知)
  • Jane、Johnny、James 和 Jenny 喜欢您的评论。 (4 条通知)
  • Jonny、James 和其他 12 人喜欢您的评论。 (14 条通知)

示例实现:

namespace Notification\Comment;

class CommentLikedNotification extends \Notification
{
    /**
     * Generate a message for a single notification
     * 
     * @param Notification              $notification
     * @return string 
     */
    public function messageForNotification(Notification $notification) : string 
    {
        return $this->sender->getName() . 'has liked your comment: ' . substr($this->reference->text, 0, 10) . '...'; 
    }

    /**
     * Generate a message for a multiple notifications
     * 
     * @param array              $notifications
     * @return string 
     */
    public function messageForNotifications(array $notifications, int $realCount = 0) : string 
    {
        if ($realCount === 0) {
            $realCount = count($notifications);
        }

        // when there are two 
        if ($realCount === 2) {
            $names = $this->messageForTwoNotifications($notifications);
        }
        // less than five
        elseif ($realCount < 5) {
            $names = $this->messageForManyNotifications($notifications);
        }
        // to many
        else {
            $names = $this->messageForManyManyNotifications($notifications, $realCount);
        }

        return $names . ' liked your comment: ' . substr($this->reference->text, 0, 10) . '...'; 
    }

    /**
     * Generate a message for two notifications
     *
     *      John and Jane has liked your comment.
     * 
     * @param array              $notifications
     * @return string 
     */
    protected function messageForTwoNotifications(array $notifications) : string 
    {
        list($first, $second) = $notifications;
        return $first->getName() . ' and ' . $second->getName(); // John and Jane
    }

    /**
     * Generate a message many notifications
     *
     *      Jane, Johnny, James and Jenny has liked your comment.
     * 
     * @param array              $notifications
     * @return string 
     */
    protected function messageForManyNotifications(array $notifications) : string 
    {
        $last = array_pop($notifications);

        foreach($notifications as $notification) {
            $names .= $notification->getName() . ', ';
        }

        return substr($names, 0, -2) . ' and ' . $last->getName(); // Jane, Johnny, James and Jenny
    }

    /**
     * Generate a message for many many notifications
     *
     *      Jonny, James and 12 other have liked your comment.
     * 
     * @param array              $notifications
     * @return string 
     */
    protected function messageForManyManyNotifications(array $notifications, int $realCount) : string 
    {
        list($first, $second) = array_slice($notifications, 0, 2);

        return $first->getName() . ', ' . $second->getName() . ' and ' .  $realCount . ' others'; // Jonny, James and 12 other
    }
}

通知管理器

要在应用程序中处理通知,请创建通知管理器之类的东西:

class NotificationManager
{
    protected $notificationAdapter;

    public function add(Notification $notification);

    public function markRead(array $notifications);

    public function get(User $user, $limit = 20, $offset = 0) : array;
}

在此示例 mysql 中,notificationAdapter 属性应包含与您的数据后端直接通信的逻辑。

创建通知

使用mysql触发器并没有错,因为没有错误的解决方案。 什么有效,什么有效.. 但我强烈建议不要让数据库处理应用程序逻辑。

所以在通知管理器中你可能想做这样的事情:

public function add(Notification $notification)
{
    // only save the notification if no possible duplicate is found.
    if (!$this->notificationAdapter->isDoublicate($notification))
    {
        $this->notificationAdapter->add([
            'recipient_id' => $notification->recipient->getId(),
            'sender_id' => $notification->sender->getId()
            'unread' => 1,
            'type' => $notification->type,
            'parameters' => $notification->parameters,
            'reference_id' => $notification->reference->getId(),
            'created_at' => time(),
        ]);
    }
}

notificationAdapteradd 方法后面可以是原始的 mysql 插入命令。使用此适配器抽象,您可以轻松地从 mysql 切换到基于文档的数据库,例如 mongodb,这对于通知系统来说是有意义的。

notificationAdapter 上的isDoublicate 方法应该简单地检查是否已经存在具有相同recipientsendertypereference 的通知。


我不能指出这只是一个例子。(另外我真的必须缩短接下来的步骤,这篇文章变得非常长 -.-)


所以假设你有某种控制器,当老师上传作业时有一个动作:

function uploadHomeworkAction(Request $request)
{
    // handle the homework and have it stored in the var $homework.

    // how you handle your services is up to you...
    $notificationManager = new NotificationManager;

    foreach($homework->teacher->students as $student)
    {
        $notification = new Notification\Homework\HomeworkUploadedNotification;
        $notification->sender = $homework->teacher;
        $notification->recipient = $student;
        $notification->reference = $homework;

        // send the notification
        $notificationManager->add($notification);
    }
}

会在每位教师的学生上传新作业时创建通知。

阅读通知

现在是困难的部分。在 PHP 端进行分组的问题是您必须加载当前用户的 all 通知才能正确分组。这会很糟糕,如果你只有几个用户,它可能仍然没有问题,但这并不能使它变得好。

简单的解决方案是简单地限制请求的通知数量并仅对它们进行分组。当没有太多类似的通知(例如每 20 个通知 3-4 个)时,这将正常工作。但是假设用户/学生的帖子获得了大约一百个喜欢,而您只选择了最后 20 个通知。然后用户只会看到 20 人喜欢他的帖子,这也是他唯一的通知。

“正确”的解决方案是对数据库中已有的通知进行分组,并为每个通知组选择一些样本。比您只需将实际计数注入您的通知消息。

您可能没有阅读下面的文字,所以让我继续sn-p:

select *, count(*) as count from notifications
where recipient_id = 1
group by `type`, `reference_id`
order by created_at desc, unread desc
limit 20

现在您知道给定用户应该有哪些通知以及该组包含多少通知。

现在是糟糕的部分。如果不对每个组进行查询,我仍然找不到更好的方法来为每个组选择有限数量的通知。 这里的所有建议都非常欢迎。

所以我做了类似的事情:

$notifcationGroups = [];

foreach($results as $notification)
{
    $notifcationGroup = ['count' => $notification['count']];

    // when the group only contains one item we don't 
    // have to select it's children
    if ($notification['count'] == 1)
    {
        $notifcationGroup['items'] = [$notification];
    }
    else
    {
        // example with query builder
        $notifcationGroup['items'] = $this->select('notifications')
            ->where('recipient_id', $recipient_id)
            ->andWehere('type', $notification['type'])
            ->andWhere('reference_id', $notification['reference_id'])
            ->limit(5);
    }

    $notifcationGroups[] = $notifcationGroup;
}

我现在继续假设 notificationAdapters get 方法实现了这个分组并返回一个像这样的数组:

[
    {
        count: 12,
        items: [Note1, Note2, Note3, Note4, Note5] 
    },
    {
        count: 1,
        items: [Note1] 
    },
    {
        count: 3,
        items: [Note1, Note2, Note3] 
    }
]

因为我们的组中总是至少有一个通知,并且我们的排序更喜欢 UnreadNew 通知,所以我们可以只使用第一个通知作为呈现示例。

因此,为了能够处理这些分组通知,我们需要一个新对象:

class NotificationGroup
{
    protected $notifications;

    protected $realCount;

    public function __construct(array $notifications, int $count)
    {
        $this->notifications = $notifications;
        $this->realCount = $count;
    }

    public function message()
    {
        return $this->notifications[0]->messageForNotifications($this->notifications, $this->realCount);
    }

    // forward all other calls to the first notification
    public function __call($method, $arguments)
    {
        return call_user_func_array([$this->notifications[0], $method], $arguments);
    }
}

最后,我们实际上可以将大部分内容放在一起。 NotificationManager 上的 get 函数可能如下所示:

public function get(User $user, $limit = 20, $offset = 0) : array
{
    $groups = [];

    foreach($this->notificationAdapter->get($user->getId(), $limit, $offset) as $group)
    {
        $groups[] = new NotificationGroup($group['notifications'], $group['count']);
    }

    return $gorups;
}

最后在一个可能的控制器动作中:

public function viewNotificationsAction(Request $request)
{
    $notificationManager = new NotificationManager;

    foreach($notifications = $notificationManager->get($this->getUser()) as $group)
    {
        echo $group->unread . ' | ' . $group->message() . ' - ' . $group->createdAt() . "\n"; 
    }

    // mark them as read 
    $notificationManager->markRead($notifications);
}

【讨论】:

  • 这样的答案应该比 +25 多得多
  • @Mario 优秀的帖子,即使它是一个示例,也显示了一个很好的解决方案。谢谢。这对我很有帮助。
  • 很好的答案。这确实帮助我理解了如何通过不使用严格的外键关系将对许多不同类型内容的引用组织到一个表中。
  • 如果一个用户有 1k 个粉丝怎么办。我们必须在表中为用户添加 1k 条记录????
  • @Zia 在这种情况下,我使用了“组”,如果用户关注者发送通知是通过发送到“following_12234”之类的组,差不多 5 年了,我仍然回到这篇文章以供参考:D !..
【解决方案2】:

答案:

  1. 在通知中引入一个已读/未读变量。然后,您可以通过在 sql 中执行 ... WHERE status = 'UNREAD' 来仅提取未读通知。

  2. 你不能真的......你会想要推送那个通知。您可以做的仍然是使用 GROUP BY 聚合它们。您可能希望对一些独特的东西(例如新作业)进行分组,因此可能类似于... GROUP BY homework.id

【讨论】:

  • 已读/未读列会将通知设置为所有用户都可以阅读,而不仅仅是看到它的用户,请注意,我将通知设置为针对整个班级,然后在获取通知时,我会获取位于我与之相关的类。
  • 啊,您需要添加一个新列来识别不同的用户... student_id 似乎合适
  • 然后通知将针对一个学生而不是整个班级,我想我会为相关学生制作一个新表并发出通知并设置阅读标志..但如果是这样,那么它可能最好只为每个学生插入一行:/,我很困惑
  • 学生是班级的一部分。您的系统需要为该班级的每个学生生成通知
【解决方案3】:

您可以通过制作 NotificationsRead 表来解决该问题,其中包含用户的 ID 和用户想要标记为已读的通知的 ID。 (这样你可以让每个学生和老师分开。) 然后,您可以将该表加入通知表,您将知道它应该被视为旧的还是新的。

geggleto 对第二部分的回答是正确的,您可以使用SELECT *, COUNT(*) AS counter WHERE ... GROUP BY 'type' 获取通知,然后您将知道那里有多少相同类型,您可以准备“userx 和其他 1 人评论 hw”查看.

我还建议您不要存储要显示的整个文本,而是存储所需的信息,例如:from_id、class_id、类型、名称等 - 这样您可以在以后需要时更轻松地更改机制,并且您必须存储更少。

【讨论】:

    【解决方案4】:

    对于那些正在寻找不使用应用程序的方法的人,您可以在不使用应用程序的情况下使用原生 PHP 和 MySQL。添加作业后,您可以添加通知。 (是的,我知道这对 SQL 注入开放,但为了简单起见,我使用常规 MySQL) 某人添加作业时的 SQL:

    $noti_homework = "INSERT INTO homework(unread, username, recipient, topic) VALUES(true, user_who_added_hw, user_who_receives_notification, homework_name);
    

    然后你可以检查通知是否未读:

    $noti_select = "SELECT FROM homework WHERE recipient='".$_SESSION['username']."' AND unread='true'";
    $noti_s_result = $conn->query($noti_select);
    $noti_s_row = $noti_s_result = $noti_s_result->fetch_assoc();
    if ($noti_s_row['unread'] == true) {
    }
    

    最后,如果它是真的,你可以回显通知。

    if ($noti_s_row['unread'] == true) {
      echo $noti_s_row['username'] . " has sent out the homework " . $noti_s_row['homework'] . ".";
    }
    

    点赞评论的方法非常相似,实际上要容易得多。

    $noti_like = "INSERT INTO notification_like(unread, username, recepient), VALUES(true, user_who_followed, user_who_recieves_notification);
    

    然后,您只需遵循相同的模式来回显行,如下所示:

    $noti_like = "INSERT INTO notification_like(unread, username, recipient), VALUES(true, user_who_followed, user_who_receives_notification);
    $noti_like_select = "SELECT FROM notification_like WHERE recipient='".$_SESSION['username']."' AND unread='true'";
    $noti_l_result = $conn->query($noti_like_select);
    $noti_l_row = $noti_s_result = $noti_l_result->fetch_assoc();
    if ($noti_l_row['unread'] == true) {
      echo $noti_l_row['username'] . " has liked your topic!";
    }
    

    然后获取通知数量:

    $count_like = "SELECT COUNT(*) FROM notification_like WHERE recipient='".$_SESSION['username']."' AND unread='true'";
    $num_count = $count_like->fetch_row();
    $count_hw = "SELECT COUNT(*) FROM homework WHERE recipient='".$_SESSION['username']."' AND unread='true'";
    $num_count_hw = $count_hw->fetch_row();
    

    使用 PHP,您可以回显这两个变量,$num_count 和 $num_count_hw。

    $num_count_real = $num_count[0];
    $num_count_hw_real = $num_count_hw[0];
    echo $num_count_real + $num_count_hw_real;
    

    此代码均未经过测试,仅供参考。希望这会有所帮助:)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-08-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-03
      • 2011-03-31
      相关资源
      最近更新 更多