package handler import ( "encoding/json" "log" "net/http" "time" "github.com/gin-gonic/gin" "github.com/yourname/cyrene-ai/gateway/internal/config" "github.com/yourname/cyrene-ai/gateway/internal/ws" ) // NotificationHandler 通知推送处理器 type NotificationHandler struct { cfg *config.Config hub *ws.Hub } // NewNotificationHandler 创建通知处理器 func NewNotificationHandler(cfg *config.Config, hub *ws.Hub) *NotificationHandler { return &NotificationHandler{cfg: cfg, hub: hub} } // PushNotificationRequest 推送通知请求体 type PushNotificationRequest struct { UserID string `json:"user_id" binding:"required"` Type string `json:"type" binding:"required,oneof=info warning success thinking reminder"` Title string `json:"title" binding:"required"` Body string `json:"body" binding:"required"` Data map[string]interface{} `json:"data,omitempty"` } // Push 推送通知到指定用户 (需要 JWT 认证) // POST /api/v1/notifications/push func (h *NotificationHandler) Push(c *gin.Context) { var req PushNotificationRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效: " + err.Error()}) return } // 生成通知 notif := h.buildNotification(req) // 序列化 WS 消息 msg := ws.ServerMessage{ Type: "notification", MessageID: "notif_" + generateID(), Timestamp: time.Now().UnixMilli(), Notification: notif, } data, err := json.Marshal(msg) if err != nil { log.Printf("[notification] 序列化通知失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "内部错误"}) return } // 通过 Hub 推送给指定用户 h.hub.SendToUser(req.UserID, data) log.Printf("[notification] 通知已推送: user=%s type=%s title=%s", req.UserID, req.Type, req.Title) c.JSON(http.StatusOK, gin.H{ "success": true, "notification": gin.H{ "id": notif.ID, "type": notif.Type, "title": notif.Title, "user_id": req.UserID, "timestamp": notif.Timestamp, "delivered": h.hub.UserClientCount(req.UserID) > 0, }, }) } // InternalNotify 内部服务推送通知 (使用内部 service token) // POST /api/v1/internal/notify func (h *NotificationHandler) InternalNotify(c *gin.Context) { var req PushNotificationRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效: " + err.Error()}) return } // 生成通知 notif := h.buildNotification(req) // 序列化 WS 消息 msg := ws.ServerMessage{ Type: "notification", MessageID: "notif_" + generateID(), Timestamp: time.Now().UnixMilli(), Notification: notif, } data, err := json.Marshal(msg) if err != nil { log.Printf("[notification] 序列化通知失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "内部错误"}) return } // 通过 Hub 推送给指定用户 h.hub.SendToUser(req.UserID, data) log.Printf("[notification] 内部通知已推送: user=%s type=%s title=%s", req.UserID, req.Type, req.Title) c.JSON(http.StatusOK, gin.H{ "success": true, "notification": gin.H{ "id": notif.ID, "type": notif.Type, "title": notif.Title, "user_id": req.UserID, "timestamp": notif.Timestamp, "delivered": h.hub.UserClientCount(req.UserID) > 0, }, }) } // InternalNotifyAuth 内部服务认证中间件 func (h *NotificationHandler) InternalNotifyAuth() gin.HandlerFunc { return func(c *gin.Context) { token := c.GetHeader("X-Internal-Token") if token == "" { token = c.GetHeader("Authorization") if len(token) > 7 && token[:7] == "Bearer " { token = token[7:] } } if token != h.cfg.InternalServiceToken { c.JSON(http.StatusUnauthorized, gin.H{"error": "内部认证失败"}) c.Abort() return } c.Next() } } // buildNotification 构建 NotificationInfo func (h *NotificationHandler) buildNotification(req PushNotificationRequest) *ws.NotificationInfo { notifID := "notif_" + generateID() now := time.Now().UTC().Format(time.RFC3339) if req.Data == nil { req.Data = make(map[string]interface{}) } return &ws.NotificationInfo{ ID: notifID, Type: req.Type, Title: req.Title, Body: req.Body, Timestamp: now, Data: req.Data, } }