sql >> Databáze >  >> RDS >> Mysql

Notifikační systém pomocí php a mysql

Tato otázka je stará 9 měsíců, takže si nejsem jistý, zda OP stále potřebuje odpověď, ale kvůli mnoha zhlédnutím a chutné odměně bych také rád přidal svou hořčici (německé přísloví..).

V tomto příspěvku se pokusím udělat jednoduchý vysvětlený příklad, jak začít budovat notifikační systém.

Upravit: Dobře, tohle dopadlo tak, tak, mnohem déle, než jsem čekal. Na konci jsem byl opravdu unavený, omlouvám se.

WTLDR;

Otázka 1: mít příznak u každého oznámení.

Otázka 2: Stále ukládejte každé oznámení jako jeden záznam ve vaší databázi a seskupte je, když jsou požadovány.

Struktura

Předpokládám, že upozornění budou vypadat nějak takto:

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

Za závěsy by to mohlo vypadat nějak takto:

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

Poznámka: Nedoporučuji seskupovat oznámení v databázi, dělejte to za běhu, díky tomu jsou věci mnohem flexibilnější.

  • Nepřečteno
    Každé oznámení by mělo mít příznak, který označuje, zda příjemce již oznámení otevřel.
  • Příjemce
    Určuje, kdo obdrží oznámení.
  • Odesílatel
    Určuje, kdo spustil oznámení.
  • Typ
    Místo toho, abyste měli každou zprávu v prostém textu v databázi, vytvořte typy. Tímto způsobem můžete vytvořit speciální obslužné nástroje pro různé typy oznámení uvnitř vašeho backendu. Sníží množství dat uložených ve vaší databázi a poskytne vám ještě větší flexibilitu, umožní snadný překlad oznámení, změn minulých zpráv atd.
  • Reference
    Většina oznámení bude obsahovat odkaz na záznam ve vaší databázi nebo aplikaci.

Každý systém, na kterém jsem pracoval, měl jednoduchý 1 ku 1 referenční vztah v oznámení, můžete mít 1 až n mějte na paměti, že budu pokračovat ve svém příkladu s poměrem 1:1. To také znamená, že nepotřebuji pole definující, na jaký typ objektu se odkazuje, protože to je definováno typem oznámení.

Tabulka SQL

Nyní, když definujeme skutečnou strukturu tabulky pro SQL, dojdeme k několika rozhodnutím z hlediska návrhu databáze. Půjdu s nejjednodušším řešením, které bude vypadat nějak takto:

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

Nebo pro líné lidi příkaz SQL create table pro tento příklad:

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;

Služba PHP

Tato implementace zcela závisí na potřebách vaší aplikace, Poznámka: Toto je příklad, který není zlatým standardem toho, jak vytvořit oznamovací systém v PHP.

Model oznámení

Toto je příklad základního modelu samotného oznámení, nic fantastického, pouze potřebné vlastnosti a abstraktní metody messageForNotification a messageForNotifications očekávali jsme implementaci v různých typech oznámení.

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

Budete muset přidat konstruktor , getters , setři a takové věci sami ve vašem vlastním stylu, nebudu poskytovat systém upozornění připravený k použití.

Typy oznámení

Nyní můžete vytvořit nové Notification podtřída pro každý typ. Tento následující příklad by zpracoval akce jako komentáře:

  • Rayovi se líbí váš komentář. (1 oznámení)
  • Johnovi a Jane se líbil váš komentář. (2 oznámení)
  • Jane, Johnnymu, Jamesovi a Jenny se váš komentář líbil. (4 oznámení)
  • Jonnymu, Jamesovi a 12 dalším se líbí váš komentář. (14 oznámení)

Příklad implementace:

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

Správce oznámení

Chcete-li pracovat s oznámeními v aplikaci, vytvořte něco jako správce oznámení:

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 Vlastnost by měla obsahovat logiku v přímé komunikaci s vaším datovým backendem v případě tohoto příkladu mysql.

Vytváření oznámení

Pomocí mysql triggerů není špatné, protože neexistuje žádné špatné řešení. Co funguje, funguje... Ale důrazně doporučuji nenechat databázi ovládat aplikační logiku.

Takže ve správci oznámení možná budete chtít udělat něco takového:

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 může být surový příkaz mysql insert. Použití této abstrakce adaptéru vám umožní snadno přepnout z mysql na databázi založenou na dokumentech, jako je mongodb což by dávalo smysl pro systém oznámení.

isDoublicate metoda na notificationAdapter měli byste jednoduše zkontrolovat, zda již existuje oznámení se stejným recipient , sender , type a reference .

Nemohu dostatečně zdůraznit, že toto je pouze příklad. (Také musím opravdu zkrátit další kroky, tento příspěvek se směšně prodlužuje -.-)

Takže za předpokladu, že máte nějaký druh ovladače s akcí, když učitel nahraje domácí úkol:

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

Vytvoří upozornění pro každého studenta učitele, když nahraje nový domácí úkol.

Čtení oznámení

Nyní přichází ta těžší část. Problém se seskupováním na straně PHP je v tom, že budete muset načíst vše upozornění aktuálního uživatele, aby je správně seskupil. To by bylo špatné, pokud máte jen pár uživatelů, pravděpodobně by to stále nebyl problém, ale to není dobré.

Snadným řešením je jednoduše omezit počet požadovaných oznámení a pouze je seskupit. Bude to fungovat dobře, když podobných oznámení není mnoho (například 3–4 na 20). Ale řekněme, že příspěvek uživatele/studenta dostane asi sto lajků a vy vyberete jen posledních 20 upozornění. Uživatel pak uvidí pouze to, že 20 lidem se líbí jeho příspěvek, což by bylo také jeho jediné upozornění.

"Správným" řešením by bylo seskupení oznámení, která jsou již v databázi, a výběr pouze některých vzorků na skupinu oznámení. Pak byste museli do zpráv s upozorněním vložit skutečný počet.

Pravděpodobně jste nečetli níže uvedený text, dovolte mi tedy pokračovat úryvkem:

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

Nyní víte, jaká oznámení by měla být pro daného uživatele k dispozici a kolik oznámení skupina obsahuje.

A teď ta blbá část. Stále jsem nemohl najít lepší způsob, jak vybrat omezený počet oznámení pro každou skupinu, aniž bych provedl dotaz pro každou skupinu. Všechny návrhy zde jsou velmi vítány.

Takže udělám něco jako:

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

Nyní budu pokračovat za předpokladu, že notificationAdapter s get metoda implementuje toto seskupení a vrátí pole takto:

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

Protože v naší skupině máme vždy alespoň jedno upozornění a naše objednávka preferuje Nepřečteno a Nové oznámení, můžeme pouze použít první oznámení jako vzorek pro vykreslení.

Abychom tedy mohli pracovat s těmito seskupenými oznámeními, potřebujeme nový objekt:

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

A konečně můžeme většinu věcí dát dohromady. Takto funguje funkce get na NotificationManager může vypadat takto:

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

A skutečně konečně uvnitř možné akce ovladače:

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. Úrovně kompatibility a primer pro odhad mohutnosti

  2. Migrace problémů a otázek MySQL UTF8 na UTF8MB4

  3. Vložení MySQL při duplicitní aktualizaci pro neprimární klíč

  4. json sloupec vs více sloupců