使用php和mysql的通知系统

时间:2015-09-22 13:11:18

标签: php mysql triggers notifications

我想为我们学校实施一个通知系统,它是一个不为公众开放的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);'

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

通过类似

的方式检索通知
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”-subject为null,除非用户是教师'

现在我的下一个挑战是。

  1. 如何跟踪每个用户的新旧通知?
  2. 如何将与用户类似的通知聚合到一行?
  3. 例如,如果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
    

4 个答案:

答案 0 :(得分:113)

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

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

编辑:好吧,这样做的方式,方式,方式比我预期的要长。最后我真的很累,对不起。

WT​​LDR;

问题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. |
+--------+-----------+--------+-----------------+-------------------------------------------+

注意:我不建议将通知分组到数据库中,在运行时执行此操作可以使事情更加灵活。

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

我一直在处理的每个系统在通知上都有一个简单的 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命令

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);
    }
}

你必须以自己的风格自己添加构造函数 getters setters 和那种东西,我&#39 ;我不打算提供一个随时可用的通知系统。

通知类型

现在,您可以为每种类型创建一个新的Notification子类。以下示例将处理注释的 like action

  • Ray喜欢你的评论。 (1个通知)
  • 约翰和简喜欢你的评论。 (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(),
        ]);
    }
}

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

notificationAdapter上的isDoublicate方法只需检查是否已存在相同notificationAdapterrecipientsender和{{1}的通知}}

我不能指出这只是一个例子。(另外我真的要缩短这篇文章后续步骤变得非常荒谬--.-)

所以假设你有一种控制器在教师上传作业时采取行动:

type

每位教师上传新作业时都会为其创建通知。

阅读通知

现在来了困难的部分。在PHP方面进行分组的问题是,您必须加载当前用户的所有通知才能正确分组。这将是不好的,如果你只有少数用户,它可能仍然没有问题,但这并不能使它变好。

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

A&#34;正确&#34;解决方案是将已经存在于数据库中的通知分组,并为每个通知组仅选择一些样本。比你只需要在你的通知消息中注入实际数量。

您可能没有阅读下面的文字,所以让我继续阅读一个片段:

reference

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

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

所以我做了类似的事情:

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);
    }
}

我现在继续假设select *, count(*) as count from notifications where recipient_id = 1 group by `type`, `reference_id` order by created_at desc, unread desc limit 20 s $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; } 方法实现了这个分组并返回一个这样的数组:

notificationAdapter

由于我们的群组中始终至少有一个通知,而且我们的订购更喜欢未读通知,因此我们可以使用第一个通知作为渲染示例。< / p>

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

get

最后我们可以将大部分内容放在一起。这就是[ { count: 12, items: [Note1, Note2, Note3, Note4, Note5] }, { count: 1, items: [Note1] }, { count: 3, items: [Note1, Note2, Note3] } ] 上的get函数的样子:

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

答案 1 :(得分:2)

答案:

  1. 在通知上引入读/未读变量。然后,您可以通过执行以下操作来仅提取未读通知... WHERE status =&#39; UNREAD&#39;在你的SQL中。

  2. 你真的......你会想要推送那个通知。你可以做的仍然是使用GROUP BY聚合它们。你可能想要把一些独特的东西分组,比如一个新的家庭作业,所以它可能是...... GROUP BY homeworkid

答案 2 :(得分:1)

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

geggleto对第二部分的回答是正确的,你可以用SELECT *, COUNT(*) AS counter WHERE ... GROUP BY 'type'获取通知,然后你会知道你有多少相同的类型,你可以准备'userx和另外1个评论hw'在视图上。

我还建议您不要存储要显示的整个文本,而是存储所需的信息,例如:from_id,class_id,type,name等等 - 这样,如果需要,您可以更轻松地更改机制,你必须少存储。

答案 3 :(得分:-1)

对于那些正在寻找一种不使用应用程序的人,可以不使用应用程序而使用普通的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;

此代码均未经过FYI测试。希望这对您有所帮助:)