281 lines
6.8 KiB
Go
281 lines
6.8 KiB
Go
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
|
|
}
|