Подтвердить что ты не робот

Система уведомлений с использованием php и mysql

Я хотел внедрить систему уведомлений для нашей школы, это 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" -subject равна null, если пользователь не является преподавателем

Теперь мои следующие проблемы.

  • как отслеживать новые старые уведомления на пользователя?
  • Как я могу агрегировать уведомления, похожие на пользователя в одну строку?

Например, если 2 пользователя прокомментировали что-то, то не вставляйте новую строку, просто обновляйте старую с помощью чего-то вроде userx и еще 1 прокомментировали hw.

Спасибо большое

Edit

В соответствии с нижеприведенным ответом, чтобы установить флаг чтения/непрочтения в строке, мне нужно будет иметь строку для каждого ученика, а не только строку для всего класса.. что означает редактирование триггера на что-то вроде

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
4b9b3361

Ответ 1

Хорошо, что этот вопрос составляет 9 месяцев, поэтому я не уверен, что OP все еще нуждается в ответе, но из-за многих просмотров и вкусной щедрости я хотел бы также добавить свою горчицу (немецкая поговорка..).

В этом посте я попытаюсь сделать простой объясненный пример о том, как начать создание системы уведомлений.

Изменить: Хорошо, это получилось так, путь, путь дольше, чем я ожидал. Я очень устал, в конце концов, извините.

WTL;DR;

Вопрос 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. |
+--------+-----------+--------+-----------------+-------------------------------------------+

Примечание. Я не рекомендую группировать уведомления внутри базы данных, делать это во время выполнения, это держит вещи намного более гибкими.

  • непрочитанная
    Каждое уведомление должно иметь флаг, указывающий, уже получил ли получатель уведомление.
  • Получатель
    Определяет, кто получает уведомление.
  • Sender
    Определяет, кто вызвал уведомление.
  • Тип
    Вместо того, чтобы каждое сообщение в виде обычного текста внутри вашей базы данных создавало типы. Таким образом, вы можете создавать специальные обработчики для разных типов уведомлений внутри вашего бэкэнда. Уменьшит объем данных, хранящихся в вашей базе данных, и даст вам еще большую гибкость, позволит легко переводить уведомления, изменения прошлых сообщений и т.д.
  • Ссылка
    Большинство уведомлений будут иметь ссылку на запись в вашей базе данных или в вашем приложении.

Каждая система, над которой я работал, имела простую ссылку от 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 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.

Модель уведомления

Это примерная базовая модель самого уведомления, ничто не представляет собой только нужные свойства и абстрактные методы messageForNotification и messageForNotifications, которые мы ожидали реализовать в разных типах уведомлений.

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

Вам нужно будет добавить конструктор, геттеры, сеттеры и такие вещи сами по себе в вашем собственном стиле, я не собираюсь предоставлять готовые к использованию системы уведомлений.

Типы уведомлений

Теперь вы можете создать новый подкласс Notification для каждого типа. В следующем примере будет обрабатываться подобное действие комментария:

  • Рэй понравился ваш комментарий. (1 уведомление)
  • Джон и Джейн понравились ваши комментарии. (2 уведомления)
  • Джейн, Джонни, Джеймс и Дженни понравились ваши комментарии. (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;
}

Свойство notificationAdapter должно содержать логику в прямой связи с вашей базой данных в случае этого примера mysql.

Создание уведомлений

Использование 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 метод notificationAdapter может быть исходной командой вставки mysql. Использование абстракции этого адаптера позволяет легко переключиться с mysql на базу данных на основе документа, такую ​​как mongodb, которая будет иметь смысл для системы уведомлений.

Метод isDoublicate на notificationAdapter должен просто проверить, есть ли уже уведомление с теми же recipient, sender, type и 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);
    }
}

Создает уведомление для каждого ученика-учителя, когда он загружает новую домашнюю работу.

Чтение уведомлений

Теперь идет сложная часть. Проблема с группировкой на стороне PHP заключается в том, что вам придется загружать уведомления all текущего пользователя, чтобы их правильно группировать. Это было бы плохо, если бы у вас было всего несколько пользователей, это, вероятно, все равно было бы проблемой, но это не делает это хорошо.

Простое решение - просто ограничить количество запрошенных уведомлений и только группировать их. Это будет хорошо работать, когда не так много похожих уведомлений (например, 3-4 на 20). Но скажем, что пост пользователя/ученика получает около сотни симпатий, и вы выбираете только 20 последних уведомлений. Затем пользователь увидит, что 20 человек тоже понравились ему, и это будет его единственным уведомлением.

"Правильное" решение будет группировать уведомления уже в базе данных и выбирать только некоторые образцы в группе уведомлений. Вместо того, чтобы просто вводить реальный счет в свои уведомления.

Вероятно, вы не читали текст ниже, поэтому позвольте мне продолжить фрагмент:

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 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 get реализует эту группировку и возвращает такой массив:

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

Поскольку у нас всегда есть как минимум одно уведомление в нашей группе, и наш заказ предпочитает Непрочитанные и Новые уведомления, мы можем просто использовать первое уведомление в качестве образца для рендеринга.

Чтобы иметь возможность работать с этими групповыми уведомлениями, нам нужен новый объект:

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

И, наконец, мы можем собрать большую часть материала. Вот как выглядит функция get на NotificationManager:

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

Ответ 2

Ответ:

  • Введите в уведомлении прочитанную/непрочитанную переменную. Затем вы можете вытащить только непрочитанные уведомления, выполнив... WHERE status = 'UNREAD' в вашем sql.

  • Вы не можете... вы захотите нажать это уведомление. То, что вы можете сделать, все равно их объединяет, но с помощью GROUP BY. Вероятно, вам захочется сгруппировать что-то уникальное, как новое домашнее задание, так что это может быть что-то вроде... GROUP BY homework. id

Ответ 3

Вы можете решить проблему с созданием таблицы NotificationsRead, содержащей идентификатор пользователя и идентификатор уведомления, которое пользователь хотел бы отметить как прочитанное. (Таким образом, вы можете оставить каждого ученика и учителя отделенными.) Затем вы можете присоединиться к этой таблице в таблице уведомлений, и вы узнаете, следует ли считать ее старым или новым.

geggleto ответ был прав о второй части, вы можете захватить уведомления с помощью SELECT *, COUNT(*) AS counter WHERE ... GROUP BY 'type', тогда вы будете знать, сколько из того же типа у вас есть, и вы можете подготовить "userx и 1 другие, прокомментированные на hw" в представлении.

Я также предлагаю вам не хранить весь текст, который вы хотите отобразить, вместо этого хранить нужную информацию, например: from_id, class_id, тип, имя и т.д. Таким образом, вы можете легко изменить механизмы позже, если вам нужно, и вы должны хранить меньше.