init
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type notificationClient struct {
|
||||
hub *NotificationHub
|
||||
conn *websocket.Conn
|
||||
send chan []byte
|
||||
adminID uint
|
||||
}
|
||||
|
||||
func newNotificationClient(hub *NotificationHub, conn *websocket.Conn, adminID uint) *notificationClient {
|
||||
return ¬ificationClient{
|
||||
hub: hub,
|
||||
conn: conn,
|
||||
send: make(chan []byte, 256),
|
||||
adminID: adminID,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *notificationClient) readPump() {
|
||||
defer func() {
|
||||
c.hub.unregister <- c
|
||||
_ = c.conn.Close()
|
||||
}()
|
||||
c.conn.SetReadLimit(512)
|
||||
_ = c.conn.SetReadDeadline(time.Now().Add(60 * time.Second))
|
||||
c.conn.SetPongHandler(func(string) error {
|
||||
_ = c.conn.SetReadDeadline(time.Now().Add(60 * time.Second))
|
||||
return nil
|
||||
})
|
||||
for {
|
||||
if _, _, err := c.conn.ReadMessage(); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *notificationClient) writePump() {
|
||||
ticker := time.NewTicker(25 * time.Second)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
_ = c.conn.Close()
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case message, ok := <-c.send:
|
||||
_ = c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||
if !ok {
|
||||
_ = c.conn.WriteMessage(websocket.CloseMessage, []byte{})
|
||||
return
|
||||
}
|
||||
if err := c.conn.WriteMessage(websocket.TextMessage, message); err != nil {
|
||||
return
|
||||
}
|
||||
case <-ticker.C:
|
||||
_ = c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *NotificationHub) RegisterConnection(conn *websocket.Conn, adminID uint) {
|
||||
client := newNotificationClient(h, conn, adminID)
|
||||
h.register <- client
|
||||
go client.writePump()
|
||||
go client.readPump()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"goravel/app/models"
|
||||
)
|
||||
|
||||
type NotificationHub struct {
|
||||
clients map[uint]map[*notificationClient]bool
|
||||
register chan *notificationClient
|
||||
unregister chan *notificationClient
|
||||
broadcast chan *models.Notification
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
var hubInstance = newNotificationHub()
|
||||
|
||||
func init() {
|
||||
go hubInstance.run()
|
||||
}
|
||||
|
||||
func Hub() *NotificationHub {
|
||||
return hubInstance
|
||||
}
|
||||
|
||||
func newNotificationHub() *NotificationHub {
|
||||
return &NotificationHub{
|
||||
clients: make(map[uint]map[*notificationClient]bool),
|
||||
register: make(chan *notificationClient),
|
||||
unregister: make(chan *notificationClient),
|
||||
broadcast: make(chan *models.Notification, 100),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *NotificationHub) run() {
|
||||
for {
|
||||
select {
|
||||
case client := <-h.register:
|
||||
h.addClient(client)
|
||||
case client := <-h.unregister:
|
||||
h.removeClient(client)
|
||||
case notification := <-h.broadcast:
|
||||
h.dispatch(notification)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *NotificationHub) addClient(client *notificationClient) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
if _, ok := h.clients[client.adminID]; !ok {
|
||||
h.clients[client.adminID] = make(map[*notificationClient]bool)
|
||||
}
|
||||
h.clients[client.adminID][client] = true
|
||||
}
|
||||
|
||||
func (h *NotificationHub) removeClient(client *notificationClient) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
if adminClients, ok := h.clients[client.adminID]; ok {
|
||||
if _, exists := adminClients[client]; exists {
|
||||
delete(adminClients, client)
|
||||
close(client.send)
|
||||
}
|
||||
if len(adminClients) == 0 {
|
||||
delete(h.clients, client.adminID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *NotificationHub) dispatch(notification *models.Notification) {
|
||||
payload := h.payload(notification)
|
||||
data, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if notification.ReceiverID == nil {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
for _, adminClients := range h.clients {
|
||||
for client := range adminClients {
|
||||
client.send <- data
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
if adminClients, ok := h.clients[*notification.ReceiverID]; ok {
|
||||
for client := range adminClients {
|
||||
client.send <- data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *NotificationHub) payload(notification *models.Notification) map[string]any {
|
||||
var readAt *string
|
||||
if notification.ReadAt != nil {
|
||||
formatted := notification.ReadAt.Format(time.RFC3339)
|
||||
readAt = &formatted
|
||||
}
|
||||
|
||||
var receiver uint
|
||||
if notification.ReceiverID != nil {
|
||||
receiver = *notification.ReceiverID
|
||||
}
|
||||
|
||||
var sender uint
|
||||
if notification.SenderID != nil {
|
||||
sender = *notification.SenderID
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"id": notification.ID,
|
||||
"title": notification.Title,
|
||||
"content": notification.Content,
|
||||
"type": notification.Type,
|
||||
"sender_id": sender,
|
||||
"receiver_id": receiver,
|
||||
"is_read": notification.IsRead,
|
||||
"read_at": readAt,
|
||||
"created_at": notification.CreatedAt.Format(time.RFC3339),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *NotificationHub) Broadcast(notification *models.Notification) {
|
||||
h.broadcast <- notification
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user