Initial commit
This commit is contained in:
@@ -0,0 +1,269 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.zkcoi.com/zkcoi/meshray/internal/model"
|
||||
"git.zkcoi.com/zkcoi/meshray/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// NotificationHandler 通知处理器
|
||||
type NotificationHandler struct {
|
||||
notificationSvc *service.NotificationService
|
||||
logger *zap.Logger
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewNotificationHandler 创建通知处理器
|
||||
func NewNotificationHandler(notificationSvc *service.NotificationService, logger *zap.Logger, db *gorm.DB) *NotificationHandler {
|
||||
return &NotificationHandler{
|
||||
notificationSvc: notificationSvc,
|
||||
logger: logger,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// GetNotifications 获取用户通知列表(未读/已读)
|
||||
// @Summary 获取用户通知列表
|
||||
// @Tags Notifications
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param type query string false "通知类型 (all/alert/system/update)"
|
||||
// @Param unread query bool false "是否仅未读"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/notifications [get]
|
||||
func (h *NotificationHandler) GetNotifications(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "未认证"})
|
||||
return
|
||||
}
|
||||
|
||||
notifType := c.Query("type")
|
||||
unreadOnly := c.Query("unread") == "true"
|
||||
|
||||
query := h.notificationSvc.GetDB().Where("user_id = ?", userID)
|
||||
|
||||
// 按类型筛选
|
||||
if notifType != "" && notifType != "all" {
|
||||
query = query.Where("type = ?", notifType)
|
||||
}
|
||||
|
||||
// 只看未读
|
||||
if unreadOnly {
|
||||
query = query.Where("is_read = ?", false)
|
||||
}
|
||||
|
||||
// 查询最近 100 条通知
|
||||
var notifications []model.Notification
|
||||
query.Order("created_at DESC").Limit(100).Find(¬ifications)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": notifications,
|
||||
})
|
||||
}
|
||||
|
||||
// MarkAsRead 标记通知为已读
|
||||
// @Summary 标记通知为已读
|
||||
// @Tags Notifications
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path uint true "通知 ID"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/notifications/:id/read [post]
|
||||
func (h *NotificationHandler) MarkAsRead(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "未认证"})
|
||||
return
|
||||
}
|
||||
|
||||
notifID := c.Param("id")
|
||||
now := time.Now()
|
||||
|
||||
result := h.notificationSvc.GetDB().Model(&model.Notification{}).
|
||||
Where("id = ? AND user_id = ?", notifID, userID).
|
||||
Updates(map[string]interface{}{
|
||||
"is_read": true,
|
||||
"read_at": now,
|
||||
})
|
||||
|
||||
if result.Error != nil || result.RowsAffected == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "通知不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "已标记为已读",
|
||||
})
|
||||
}
|
||||
|
||||
// MarkAllAsRead 标记所有通知为已读
|
||||
// @Summary 标记所有通知为已读
|
||||
// @Tags Notifications
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/notifications/read-all [post]
|
||||
func (h *NotificationHandler) MarkAllAsRead(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "未认证"})
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
result := h.notificationSvc.GetDB().Model(&model.Notification{}).
|
||||
Where("user_id = ? AND is_read = ?", userID, false).
|
||||
Updates(map[string]interface{}{
|
||||
"is_read": true,
|
||||
"read_at": now,
|
||||
})
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "已全部标记为已读",
|
||||
"affected": result.RowsAffected,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteNotification 删除通知
|
||||
// @Summary 删除通知
|
||||
// @Tags Notifications
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path uint true "通知 ID"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/notifications/:id [delete]
|
||||
func (h *NotificationHandler) DeleteNotification(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "未认证"})
|
||||
return
|
||||
}
|
||||
|
||||
notifID := c.Param("id")
|
||||
|
||||
result := h.notificationSvc.GetDB().Where("id = ? AND user_id = ?", notifID, userID).
|
||||
Delete(&model.Notification{})
|
||||
|
||||
if result.Error != nil || result.RowsAffected == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "通知不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "通知已删除",
|
||||
})
|
||||
}
|
||||
|
||||
// GetUnreadCount 获取未读通知数
|
||||
// @Summary 获取未读通知数
|
||||
// @Tags Notifications
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/notifications/unread-count [get]
|
||||
func (h *NotificationHandler) GetUnreadCount(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "未认证"})
|
||||
return
|
||||
}
|
||||
|
||||
var count int64
|
||||
h.notificationSvc.GetDB().Model(&model.Notification{}).
|
||||
Where("user_id = ? AND is_read = ?", userID, false).
|
||||
Count(&count)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": gin.H{
|
||||
"count": count,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// TestSendNotification 测试发送通知(开发用)
|
||||
// @Summary 测试发送通知
|
||||
// @Tags Notifications
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param message body object true "通知内容"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/notifications/test [post]
|
||||
func (h *NotificationHandler) TestSendNotification(c *gin.Context) {
|
||||
var req struct {
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数错误"})
|
||||
return
|
||||
}
|
||||
|
||||
// 从上下文获取用户 ID
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "未认证"})
|
||||
return
|
||||
}
|
||||
|
||||
// 发送测试通知
|
||||
h.notificationSvc.SendSystemNotification(userID.(uint), req.Title, req.Message)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "测试通知已发送",
|
||||
})
|
||||
}
|
||||
|
||||
// ClearExpiredNotifications 清理过期通知(定时任务)
|
||||
func (h *NotificationHandler) ClearExpiredNotifications() {
|
||||
// 启动定期清理任务(每 24 小时清理一次超过 30 天的通知)
|
||||
go func() {
|
||||
ticker := time.NewTicker(24 * time.Hour)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
h.cleanupOldNotifications()
|
||||
}
|
||||
}()
|
||||
h.logger.Info("清理过期通知完成")
|
||||
}
|
||||
|
||||
// cleanupOldNotifications 清理超过 30 天的通知记录
|
||||
func (h *NotificationHandler) cleanupOldNotifications() {
|
||||
ctx := context.Background()
|
||||
cutoffTime := time.Now().AddDate(0, 0, -30)
|
||||
|
||||
result := h.db.WithContext(ctx).
|
||||
Where("created_at < ?", cutoffTime).
|
||||
Delete(&model.Notification{})
|
||||
|
||||
if result.Error != nil {
|
||||
h.logger.Error("清理过期通知失败", zap.Error(result.Error))
|
||||
} else {
|
||||
h.logger.Info("清理过期通知完成", zap.Int64("deleted", result.RowsAffected))
|
||||
}
|
||||
}
|
||||
|
||||
// StartNotificationCleaner 启动通知清理定时器
|
||||
func (h *NotificationHandler) StartNotificationCleaner(stopCh <-chan struct{}) {
|
||||
ticker := time.NewTicker(24 * time.Hour) // 每天执行一次
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
h.ClearExpiredNotifications()
|
||||
case <-stopCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user