Mysql
 sql >> Baza danych >  >> RDS >> Mysql

System powiadomień za pomocą php i mysql

Cóż, to pytanie ma 9 miesięcy, więc nie jestem pewien, czy OP nadal potrzebuje odpowiedzi, ale ze względu na wiele wyświetleń i smaczną nagrodę chciałbym dodać również moją musztardę (niemieckie powiedzenie...).

W tym poście postaram się zrobić prosty wyjaśniony przykład jak zacząć budować system powiadomień.

Edytuj: Cóż, okazało się, że tak, o wiele dłużej, niż się spodziewałem. W końcu naprawdę się zmęczyłem, przepraszam.

WTLDR;

Pytanie 1: mieć flagę na każdym powiadomieniu.

Pytanie 2: Nadal przechowuj każde powiadomienie jako pojedynczy rekord w swojej bazie danych i grupuj je, gdy są wymagane.

Struktura

Zakładam, że powiadomienia będą wyglądać mniej więcej tak:

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

Za zasłonami może to wyglądać mniej więcej tak:

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

Uwaga: Nie polecam grupowania powiadomień w bazie danych, rób to w czasie wykonywania, dzięki czemu wszystko jest o wiele bardziej elastyczne.

  • Nieprzeczytane
    Każde powiadomienie powinno mieć flagę wskazującą, czy odbiorca już otworzył powiadomienie.
  • Odbiorca
    Określa, kto otrzyma powiadomienie.
  • Nadawca
    Określa, kto wywołał powiadomienie.
  • Typ
    Zamiast umieszczania każdej wiadomości w postaci zwykłego tekstu w bazie danych, twórz typy. W ten sposób możesz tworzyć specjalne programy obsługi dla różnych typów powiadomień w swoim zapleczu. Zmniejszy ilość danych przechowywanych w Twojej bazie danych i zapewni jeszcze większą elastyczność, umożliwi łatwe tłumaczenie powiadomień, zmiany przeszłych wiadomości itp.
  • Odniesienie
    Większość powiadomień będzie miała odniesienie do wpisu w Twojej bazie danych lub aplikacji.

Każdy system, nad którym pracowałem, miał prosty 1 do 1 związek odniesienia w powiadomieniu, możesz mieć od 1 do n pamiętaj, że będę kontynuował swój przykład z 1:1. Oznacza to również, że nie potrzebuję pola określającego, do jakiego typu obiektu się odwołujemy, ponieważ jest to określone przez typ powiadomienia.

Tabela SQL

Teraz, kiedy definiujemy rzeczywistą strukturę tabeli dla SQL, dochodzimy do kilku decyzji w zakresie projektu bazy danych. Wybiorę najprostsze rozwiązanie, które będzie wyglądać mniej więcej tak:

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

Lub dla leniwych polecenie tworzenia tabeli SQL dla tego przykładu:

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;

Usługa PHP

Ta implementacja zależy całkowicie od potrzeb Twojej aplikacji, Uwaga: To jest przykład, a nie złoty standard budowania systemu powiadomień w PHP.

Model powiadomienia

To jest przykładowy model bazowy samego powiadomienia, nic nadzwyczajnego tylko potrzebne właściwości i abstrakcyjne metody messageForNotification i messageForNotifications spodziewaliśmy się, że zostaną wdrożone w różnych typach powiadomień.

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

Będziesz musiał dodać konstruktora , gettery , setery i tego rodzaju rzeczy samodzielnie w swoim własnym stylu, nie zamierzam dostarczać gotowego do użycia systemu powiadomień.

Typy powiadomień

Teraz możesz utworzyć nowe Notification podklasa dla każdego typu. Poniższy przykład poradzi sobie z działaniem podobnym komentarza:

  • Ray polubił Twój komentarz. (1 powiadomienie)
  • John i Jane polubili twój komentarz. (2 powiadomienia)
  • Jane, Johnny, James i Jenny polubili twój komentarz. (4 powiadomienia)
  • Jonny, James i 12 innych osób polubiło Twój komentarz. (14 powiadomień)

Przykładowa implementacja:

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

Menedżer powiadomień

Aby pracować z powiadomieniami w aplikacji, utwórz coś w rodzaju menedżera powiadomień:

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 właściwość powinna zawierać logikę w bezpośredniej komunikacji z backendem danych w przypadku tego przykładu mysql.

Tworzenie powiadomień

Korzystanie z mysql wyzwalacze nie są złe, ponieważ nie ma złego rozwiązania. Co działa, działa... Ale zdecydowanie odradzam, aby baza danych nie zajmowała się logiką aplikacji.

Więc w menedżerze powiadomień możesz chcieć zrobić coś takiego:

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(),
        ]);
    }
}

Za add metoda notificationAdapter może być surowym poleceniem wstawiania mysql. Korzystanie z tej abstrakcji adaptera umożliwia łatwe przełączanie się z mysql na bazę danych opartą na dokumentach, taką jak mongodb co miałoby sens w przypadku systemu powiadomień.

isDoublicate metoda na notificationAdapter powinien po prostu sprawdzić, czy istnieje już powiadomienie z tym samym recipient , sender , type i reference .

Nie potrafię wystarczająco wskazać, że to tylko przykład. (Naprawdę muszę skrócić kolejne kroki, które ten post robi się absurdalnie długi -.-)

Zakładając więc, że masz jakiś kontroler z akcją, gdy nauczyciel przesyła pracę domową:

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

Stworzy powiadomienie dla każdego ucznia nauczyciela, gdy prześle nową pracę domową.

Czytanie powiadomień

Teraz nadchodzi najtrudniejsza część. Problem z grupowaniem po stronie PHP polega na tym, że będziesz musiał załadować wszystkie powiadomienia bieżącego użytkownika, aby je poprawnie pogrupować. To byłoby złe, cóż, jeśli masz tylko kilku użytkowników, prawdopodobnie nadal nie stanowiłoby to problemu, ale to nie czyni tego dobrze.

Prostym rozwiązaniem jest po prostu ograniczenie liczby żądanych powiadomień i tylko ich pogrupowanie. Będzie to działać dobrze, gdy nie ma wielu podobnych powiadomień (np. 3-4 na 20). Ale powiedzmy, że post użytkownika / studenta dostaje około stu polubień, a ty wybierasz tylko 20 ostatnich powiadomień. Użytkownik zobaczy wtedy tylko, że 20 osób polubiło jego post, co byłoby jego jedynym powiadomieniem.

„Właściwym” rozwiązaniem byłoby pogrupowanie powiadomień znajdujących się już w bazie danych i wybranie tylko kilku próbek na grupę powiadomień. Wtedy wystarczyłoby wstrzyknąć rzeczywistą liczbę do swoich powiadomień.

Prawdopodobnie nie przeczytałeś poniższego tekstu, więc pozwól mi kontynuować z fragmentem:

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

Teraz wiesz, jakie powiadomienia powinny być dostępne dla danego użytkownika i ile powiadomień zawiera grupa.

A teraz gówniana część. Nadal nie mogłem znaleźć lepszego sposobu na wybranie ograniczonej liczby powiadomień dla każdej grupy bez wykonywania zapytania dla każdej grupy. Wszystkie sugestie tutaj są mile widziane.

Więc robię coś takiego:

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

Teraz będę kontynuował założenie, że notificationAdapter s get metoda implementuje to grupowanie i zwraca tablicę w ten sposób:

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

Ponieważ zawsze mamy co najmniej jedno powiadomienie w naszej grupie, a nasze zamówienia preferują Nieprzeczytane i Nowe powiadomienia możemy po prostu użyć pierwszego powiadomienia jako próbki do renderowania.

Aby móc pracować z tymi grupowymi powiadomieniami, potrzebujemy nowego obiektu:

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

I wreszcie możemy poskładać większość rzeczy do kupy. Tak działa funkcja get w NotificationManager może wyglądać tak:

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

I wreszcie wewnątrz możliwej akcji kontrolera:

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


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Jak skonfigurować encję (doktrynę) dla widoku bazy danych w Symfony 2?

  2. MySQL – Napraw błąd – Błąd bazy danych WordPress Zduplikowany wpis dla klucza PRIMARY dla zapytania INSERT INTO wp_options

  3. MySQL wybierz wiersze dokładnie 7 dni temu

  4. EEE MMM dd GG:mm:ss ZZZ rrrr format daty do java.sql.Date

  5. MySQL | REGEXP VS Lubię