Vâng, câu hỏi này đã được 9 tháng nên tôi không chắc liệu OP có còn cần câu trả lời hay không nhưng do nhiều lượt xem và tiền thưởng ngon, tôi cũng muốn thêm mù tạt của mình (câu nói của người Đức ..).
Trong bài đăng này, tôi sẽ cố gắng làm một ví dụ giải thích đơn giản về cách bắt đầu xây dựng hệ thống thông báo.
Chỉnh sửa: Chà, chuyện này xảy ra theo cách, cách, lâu hơn tôi mong đợi. Cuối cùng thì tôi thực sự mệt mỏi, tôi xin lỗi.
WTLDR;
Câu hỏi 1: có cờ trên mọi thông báo.
Câu hỏi 2: Vẫn lưu trữ mọi thông báo dưới dạng một bản ghi duy nhất bên trong cơ sở dữ liệu của bạn và nhóm chúng lại khi chúng được yêu cầu.
Cấu trúc
Tôi giả định rằng các thông báo sẽ giống như sau:
+---------------------------------------------+
| ▣ James has uploaded new Homework: Math 1+1 |
+---------------------------------------------+
| ▣ Jane and John liked your comment: Im s... |
+---------------------------------------------+
| ▢ The School is closed on independence day. |
+---------------------------------------------+
Phía sau tấm rèm có thể trông giống như sau:
+--------+-----------+--------+-----------------+-------------------------------------------+
| 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. |
+--------+-----------+--------+-----------------+-------------------------------------------+
Lưu ý: Tôi không khuyên bạn nên nhóm các thông báo bên trong cơ sở dữ liệu, hãy làm điều đó trong thời gian chạy, điều này giúp mọi thứ linh hoạt hơn rất nhiều.
- Chưa đọc
Mọi thông báo phải có cờ để cho biết người nhận đã mở thông báo chưa. - Người nhận
Xác định người nhận thông báo. - Người gửi
Xác định ai đã kích hoạt thông báo. - Loại
Thay vì có mọi Thư ở dạng văn bản thuần túy bên trong cơ sở dữ liệu của bạn, hãy tạo các kiểu. Bằng cách này, bạn có thể tạo các trình xử lý đặc biệt cho các loại thông báo khác nhau bên trong chương trình phụ trợ của mình. Sẽ giảm lượng dữ liệu được lưu trữ bên trong cơ sở dữ liệu của bạn và mang lại cho bạn sự linh hoạt hơn nữa, cho phép dễ dàng dịch thông báo, thay đổi các tin nhắn trước đây, v.v. - Tham khảo
Hầu hết các thông báo sẽ có Tham chiếu đến bản ghi trên cơ sở dữ liệu hoặc ứng dụng của bạn.
Mọi hệ thống tôi đang làm việc đều có 1 đến 1 đơn giản mối quan hệ tham chiếu trên thông báo, bạn có thể có 1 đến n Hãy nhớ rằng tôi sẽ tiếp tục ví dụ của mình với tỷ lệ 1:1. Điều này cũng có nghĩa là tôi không cần trường xác định loại đối tượng nào được tham chiếu bởi vì trường này được xác định bởi loại thông báo.
Bảng SQL
Bây giờ khi xác định cấu trúc bảng thực cho SQL, chúng ta đi đến một số quyết định về thiết kế cơ sở dữ liệu. Tôi sẽ đưa ra giải pháp đơn giản nhất sẽ giống như sau:
+--------------+--------+---------------------------------------------------------+
| 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. |
+--------------+--------+---------------------------------------------------------+
Hoặc đối với những người lười biếng, Lệnh tạo bảng trong SQL cho ví dụ này:
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;
Dịch vụ PHP
Việc triển khai này hoàn toàn phụ thuộc vào nhu cầu của ứng dụng của bạn, Lưu ý: Đây là một ví dụ không phải là tiêu chuẩn vàng về cách xây dựng hệ thống thông báo trong PHP.
Mô hình thông báo
Đây là một mô hình cơ sở mẫu của chính thông báo, không có gì lạ chỉ là các thuộc tính cần thiết và các phương thức trừu tượng messageForNotification
và messageForNotifications
chúng tôi mong đợi được triển khai trong các loại thông báo khác nhau.
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ạn sẽ phải thêm một phương thức khởi tạo , getters , người thiết lập và những thứ đó theo phong cách riêng của bạn, tôi sẽ không cung cấp hệ thống Thông báo sẵn sàng sử dụng.
Các loại thông báo
Bây giờ bạn có thể tạo Notification
mới lớp con cho mọi loại. Ví dụ sau đây sẽ xử lý hành động like của một nhận xét:
- Ray đã thích bình luận của bạn. (1 thông báo)
- John và Jane thích nhận xét của bạn. (2 thông báo)
- Jane, Johnny, James và Jenny thích nhận xét của bạn. (4 thông báo)
- Jonny, James và 12 người khác thích nhận xét của bạn. (14 thông báo)
Triển khai ví dụ:
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
}
}
Trình quản lý thông báo
Để làm việc với các thông báo bên trong ứng dụng của bạn, hãy tạo thứ gì đó giống như một trình quản lý thông báo:
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
thuộc tính phải chứa logic trong giao tiếp trực tiếp với phần phụ trợ dữ liệu của bạn trong trường hợp của ví dụ mysql này.
Tạo thông báo
Sử dụng mysql
kích hoạt không sai, bởi vì không có giải pháp sai. Những gì hoạt động, hoạt động .. Nhưng tôi thực sự khuyên bạn không nên để cơ sở dữ liệu xử lý logic ứng dụng.
Vì vậy, bên trong trình quản lý thông báo, bạn có thể muốn làm điều gì đó như sau:
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(),
]);
}
}
Đằng sau add
phương thức của notificationAdapter
có thể là một lệnh chèn mysql thô. Sử dụng trừu tượng bộ điều hợp này cho phép bạn chuyển đổi dễ dàng từ mysql sang cơ sở dữ liệu dựa trên tài liệu như mongodb điều này sẽ có ý nghĩa đối với hệ thống Thông báo.
isDoublicate
trên notificationAdapter
chỉ cần kiểm tra xem đã có thông báo nào với cùng một recipient
chưa , sender
, type
và reference
.
Tôi không thể chỉ ra đủ rằng đây chỉ là một ví dụ. (Ngoài ra, tôi thực sự phải rút ngắn các bước tiếp theo mà bài đăng này đang dài đến mức nực cười -.-)
Vì vậy, giả sử bạn có một số loại bộ điều khiển có tác vụ khi giáo viên tải bài tập về nhà lên:
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);
}
}
Sẽ tạo thông báo cho mọi học sinh của giáo viên khi anh ta tải bài tập về nhà mới lên.
Đọc thông báo
Bây giờ đến phần khó. Vấn đề với nhóm ở phía PHP là bạn sẽ phải tải tất cả thông báo của người dùng hiện tại để nhóm chúng một cách chính xác. Điều này thật tệ, nếu bạn chỉ có một vài người dùng thì có lẽ vẫn không có vấn đề gì, nhưng điều đó không tốt chút nào.
Giải pháp dễ dàng là chỉ cần giới hạn số lượng thông báo được yêu cầu và chỉ nhóm những thông báo này. Điều này sẽ hoạt động tốt khi không có nhiều thông báo tương tự (như 3-4 trên 20). Nhưng giả sử bài đăng của một người dùng / sinh viên nhận được khoảng một trăm lượt thích và bạn chỉ chọn 20 thông báo cuối cùng. Sau đó, người dùng sẽ chỉ thấy rằng 20 người đã thích bài đăng của anh ấy và đó sẽ là thông báo duy nhất của anh ấy.
Giải pháp "đúng" sẽ là nhóm các thông báo đã có trên cơ sở dữ liệu và chỉ chọn một số mẫu cho mỗi nhóm thông báo. Hơn là bạn chỉ phải đưa số lượng thực vào các tin nhắn thông báo của mình.
Có thể bạn chưa đọc nội dung bên dưới, vì vậy hãy để tôi tiếp tục với một đoạn mã:
select *, count(*) as count from notifications
where recipient_id = 1
group by `type`, `reference_id`
order by created_at desc, unread desc
limit 20
Bây giờ bạn biết những thông báo nào sẽ có cho người dùng nhất định và nhóm chứa bao nhiêu thông báo.
Và bây giờ là phần chết tiệt. Tôi vẫn không thể tìm ra cách tốt hơn để chọn một số lượng thông báo giới hạn cho mỗi nhóm mà không thực hiện truy vấn cho mỗi nhóm. Tất cả các đề xuất ở đây đều rất được hoan nghênh.
Vì vậy, tôi làm một cái gì đó như:
$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;
}
Bây giờ tôi sẽ tiếp tục giả định rằng notificationAdapter
s get
phương thức thực hiện nhóm này và trả về một mảng như thế này:
[
{
count: 12,
items: [Note1, Note2, Note3, Note4, Note5]
},
{
count: 1,
items: [Note1]
},
{
count: 3,
items: [Note1, Note2, Note3]
}
]
Bởi vì chúng tôi luôn có ít nhất một thông báo trong nhóm của mình và đơn đặt hàng của chúng tôi thích Chưa đọc và Mới thông báo, chúng tôi chỉ có thể sử dụng thông báo đầu tiên làm mẫu để hiển thị.
Vì vậy, để có thể làm việc với các thông báo được nhóm này, chúng ta cần một đối tượng mới:
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);
}
}
Và cuối cùng chúng ta thực sự có thể kết hợp hầu hết các thứ lại với nhau. Đây là cách hàm get trên NotificationManager
có thể trông giống như:
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;
}
Và thực sự cuối cùng bên trong một hành động điều khiển khả thi:
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);
}