package service import ( "fmt" "time" "verification-platform-backend/internal/database" "verification-platform-backend/internal/model" ) type UsageAlertService struct{} func NewUsageAlertService() *UsageAlertService { return &UsageAlertService{} } type AlertType string const ( AlertTypeAPICalls AlertType = "api_calls" AlertTypeStorage AlertType = "storage" AlertTypePackageExp AlertType = "package_expiry" ) type AlertLevel string const ( AlertLevelWarning AlertLevel = "warning" AlertLevelCritical AlertLevel = "critical" ) type UsageAlert struct { UserID uint `json:"user_id"` Type AlertType `json:"type"` Level AlertLevel `json:"level"` Message string `json:"message"` Usage float64 `json:"usage"` Limit float64 `json:"limit"` Percent float64 `json:"percent"` CreatedAt time.Time `json:"created_at"` } func (s *UsageAlertService) CheckAndCreateAlerts(userID uint) ([]UsageAlert, error) { var alerts []UsageAlert var user model.User if err := database.DB.Preload("CurrentPackage").First(&user, userID).Error; err != nil { return nil, err } if user.CurrentPackageID == nil { return alerts, nil } var permission model.PackagePermission if err := database.DB.Where("package_id = ?", user.CurrentPackageID).First(&permission).Error; err != nil { return nil, err } if alert := s.checkAPICallsUsage(&user, &permission); alert != nil { alerts = append(alerts, *alert) } if alert := s.checkStorageUsage(&user, &permission); alert != nil { alerts = append(alerts, *alert) } if alert := s.checkPackageExpiry(&user); alert != nil { alerts = append(alerts, *alert) } for _, alert := range alerts { s.sendNotification(&alert) } return alerts, nil } func (s *UsageAlertService) checkAPICallsUsage(user *model.User, permission *model.PackagePermission) *UsageAlert { if permission.MaxApiCalls == 0 { return nil } usagePercent := float64(user.ApiCallsUsed) / float64(permission.MaxApiCalls) * 100 var lastAlert model.UsageAlertRecord hasRecentAlert := database.DB.Where( "user_id = ? AND type = ? AND created_at > ?", user.ID, AlertTypeAPICalls, time.Now().Add(-24*time.Hour), ).First(&lastAlert).Error == nil if hasRecentAlert { return nil } if usagePercent >= 90 { return &UsageAlert{ UserID: user.ID, Type: AlertTypeAPICalls, Level: AlertLevelCritical, Message: fmt.Sprintf("API调用次数已使用 %.1f%%,即将达到上限", usagePercent), Usage: float64(user.ApiCallsUsed), Limit: float64(permission.MaxApiCalls), Percent: usagePercent, } } if usagePercent >= 80 { return &UsageAlert{ UserID: user.ID, Type: AlertTypeAPICalls, Level: AlertLevelWarning, Message: fmt.Sprintf("API调用次数已使用 %.1f%%,请注意用量", usagePercent), Usage: float64(user.ApiCallsUsed), Limit: float64(permission.MaxApiCalls), Percent: usagePercent, } } return nil } func (s *UsageAlertService) checkStorageUsage(user *model.User, permission *model.PackagePermission) *UsageAlert { if permission.MaxStorage == 0 { return nil } usagePercent := float64(user.StorageUsed) / float64(int64(permission.MaxStorage)*1024*1024) * 100 var lastAlert model.UsageAlertRecord hasRecentAlert := database.DB.Where( "user_id = ? AND type = ? AND created_at > ?", user.ID, AlertTypeStorage, time.Now().Add(-24*time.Hour), ).First(&lastAlert).Error == nil if hasRecentAlert { return nil } if usagePercent >= 90 { return &UsageAlert{ UserID: user.ID, Type: AlertTypeStorage, Level: AlertLevelCritical, Message: fmt.Sprintf("存储空间已使用 %.1f%%,即将达到上限", usagePercent), Usage: float64(user.StorageUsed) / 1024 / 1024, Limit: float64(permission.MaxStorage), Percent: usagePercent, } } if usagePercent >= 80 { return &UsageAlert{ UserID: user.ID, Type: AlertTypeStorage, Level: AlertLevelWarning, Message: fmt.Sprintf("存储空间已使用 %.1f%%,请注意用量", usagePercent), Usage: float64(user.StorageUsed) / 1024 / 1024, Limit: float64(permission.MaxStorage), Percent: usagePercent, } } return nil } func (s *UsageAlertService) checkPackageExpiry(user *model.User) *UsageAlert { var userPackage model.UserPackage if err := database.DB.Where( "user_id = ? AND package_id = ? AND status = ?", user.ID, user.CurrentPackageID, "active", ).First(&userPackage).Error; err != nil { return nil } if userPackage.ExpiredAt == nil { return nil } daysUntilExpiry := time.Until(*userPackage.ExpiredAt).Hours() / 24 var lastAlert model.UsageAlertRecord hasRecentAlert := database.DB.Where( "user_id = ? AND type = ? AND created_at > ?", user.ID, AlertTypePackageExp, time.Now().Add(-24*time.Hour), ).First(&lastAlert).Error == nil if hasRecentAlert { return nil } if daysUntilExpiry <= 3 { return &UsageAlert{ UserID: user.ID, Type: AlertTypePackageExp, Level: AlertLevelCritical, Message: fmt.Sprintf("套餐将在 %.0f 天后过期,请及时续费", daysUntilExpiry), Usage: daysUntilExpiry, Limit: 0, Percent: 0, } } if daysUntilExpiry <= 7 { return &UsageAlert{ UserID: user.ID, Type: AlertTypePackageExp, Level: AlertLevelWarning, Message: fmt.Sprintf("套餐将在 %.0f 天后过期,请及时续费", daysUntilExpiry), Usage: daysUntilExpiry, Limit: 0, Percent: 0, } } return nil } func (s *UsageAlertService) sendNotification(alert *UsageAlert) { alertRecord := model.UsageAlertRecord{ UserID: alert.UserID, Type: string(alert.Type), Level: string(alert.Level), Message: alert.Message, Usage: alert.Usage, Limit: alert.Limit, Percent: alert.Percent, Status: "sent", CreatedAt: time.Now(), } database.DB.Create(&alertRecord) notification := model.Notification{ UserID: alert.UserID, Title: "用量告警", Content: alert.Message, Type: "usage_alert", IsRead: false, CreatedAt: time.Now(), } database.DB.Create(¬ification) } func (s *UsageAlertService) CheckAllUsers() error { var users []model.User if err := database.DB.Where("current_package_id IS NOT NULL").Find(&users).Error; err != nil { return err } for _, user := range users { _, err := s.CheckAndCreateAlerts(user.ID) if err != nil { fmt.Printf("检查用户 %d 用量告警失败: %v\n", user.ID, err) } } return nil } func (s *UsageAlertService) GetUserAlerts(userID uint, limit int) ([]model.UsageAlertRecord, error) { var alerts []model.UsageAlertRecord err := database.DB.Where("user_id = ?", userID). Order("created_at DESC"). Limit(limit). Find(&alerts).Error return alerts, err } func (s *UsageAlertService) MarkAlertAsRead(alertID uint) error { return database.DB.Model(&model.UsageAlertRecord{}). Where("id = ?", alertID). Update("status", "read").Error }