Files
1Panel/agent/app/repo/alert.go

559 lines
16 KiB
Go

package repo
import (
"encoding/json"
"strings"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"google.golang.org/genproto/googleapis/type/date"
"gorm.io/gorm"
"strconv"
"time"
)
type AlertRepo struct{}
type IAlertRepo interface {
WithByType(alertType string) DBOption
WithByStatusIn(status []string) DBOption
WithByProject(project string) DBOption
WithByCount(count uint) DBOption
WithByAlertId(alertId uint) DBOption
WithByCreateAt(date *date.Date) DBOption
WithByLicenseId(licenseId string) DBOption
WithByRecordId(recordId uint) DBOption
WithByAlertMethodContainsConfigID(id uint) DBOption
WithByMethodConfigIDs(ids []uint) DBOption
Create(alert *model.Alert) error
Get(opts ...DBOption) (model.Alert, error)
Page(page, size int, opts ...DBOption) (int64, []model.Alert, error)
List(opts ...DBOption) ([]model.Alert, error)
Delete(opts ...DBOption) error
Save(alert *model.Alert) error
Update(maps map[string]interface{}, opts ...DBOption) error
GetLog(opts ...DBOption) (model.AlertLog, error)
CreateLog(alertLog *model.AlertLog) error
PageLog(limit, offset int, opts ...DBOption) (int64, []model.AlertLog, error)
ListLog(opts ...DBOption) ([]model.AlertLog, error)
UpdateLog(id uint, maps map[string]interface{}) error
BatchUpdateLogBy(maps map[string]interface{}, opts ...DBOption) error
DeleteLog(opts ...DBOption) error
CleanAlertLogs() error
CreateAlertTask(alertTaskBase *model.AlertTask) error
DeleteAlertTask(opts ...DBOption) error
GetAlertTask(opts ...DBOption) (model.AlertTask, error)
LoadTaskCount(alertType string, project string, method string) (uint, uint, error)
GetTaskLog(alertType string, alertId uint) (time.Time, error)
GetLicensePushCount(method string) (uint, error)
GetConfig(opts ...DBOption) (model.AlertConfig, error)
GetConfigById(id uint) (model.AlertConfig, error)
AlertConfigList(opts ...DBOption) ([]model.AlertConfig, error)
UpdateAlertConfig(maps map[string]interface{}, opts ...DBOption) error
CreateAlertConfig(config *model.AlertConfig) error
DeleteAlertConfig(opts ...DBOption) error
WithByTypeNotIn(types []string) DBOption
PageAlertConfig(page, size int, opts ...DBOption) (int64, []model.AlertConfig, error)
SyncAll(data []model.AlertConfig) error
}
func NewIAlertRepo() IAlertRepo {
return &AlertRepo{}
}
func (a *AlertRepo) WithByType(alertType string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("`type` = ?", alertType)
}
}
func (a *AlertRepo) WithByStatusIn(status []string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("status in (?)", status)
}
}
func (a *AlertRepo) WithByCount(count uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("count = ?", count)
}
}
func (a *AlertRepo) WithByProject(project string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("project = ?", project)
}
}
func (a *AlertRepo) WithByAlertId(alertId uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("alert_id = ?", alertId)
}
}
func (a *AlertRepo) WithByLicenseId(licenseId string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("license_id = ?", licenseId)
}
}
func (a *AlertRepo) WithByRecordId(recordId uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("record_id = ?", recordId)
}
}
func (a *AlertRepo) WithByAlertMethodContainsConfigID(id uint) DBOption {
method := strconv.Itoa(int(id))
return func(g *gorm.DB) *gorm.DB {
return g.Where("(method = ? OR method LIKE ? OR method LIKE ? OR method LIKE ?)", method, method+",%", "%,"+method, "%,"+method+",%")
}
}
func (a *AlertRepo) WithByMethodConfigIDs(ids []uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
methods := make([]string, 0, len(ids))
for _, id := range ids {
methods = append(methods, strconv.Itoa(int(id)))
}
return g.Where("method IN ?", methods)
}
}
func (a *AlertRepo) WithByCreateAt(createAt *date.Date) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("DATE(created_at) = DATE(?)", createAt)
}
}
func (a *AlertRepo) Create(alert *model.Alert) error {
return global.AlertDB.Model(&model.Alert{}).Create(alert).Error
}
func (a *AlertRepo) Save(alert *model.Alert) error {
return global.AlertDB.Save(alert).Error
}
func (a *AlertRepo) Get(opts ...DBOption) (model.Alert, error) {
var alert model.Alert
db, _ := getAlertDB(opts...)
err := db.First(&alert).Error
return alert, err
}
func (a *AlertRepo) Page(page, size int, opts ...DBOption) (int64, []model.Alert, error) {
var alerts []model.Alert
alertDb, _ := getAlertDB(opts...)
db := alertDb.Model(&model.Alert{})
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&alerts).Error
return count, alerts, err
}
func (a *AlertRepo) List(opts ...DBOption) ([]model.Alert, error) {
var alert []model.Alert
db, _ := getAlertDB(opts...)
err := db.Find(&alert).Error
return alert, err
}
func (a *AlertRepo) Update(maps map[string]interface{}, opts ...DBOption) error {
db, _ := getAlertDB(opts...)
return db.Model(&model.Alert{}).Updates(maps).Error
}
func (a *AlertRepo) Delete(opts ...DBOption) error {
db, _ := getAlertDB(opts...)
return db.Delete(&model.Alert{}).Error
}
func (a *AlertRepo) GetLog(opts ...DBOption) (model.AlertLog, error) {
var alertLog model.AlertLog
db, _ := getAlertDB(opts...)
err := db.First(&alertLog).Error
return alertLog, err
}
func (a *AlertRepo) CreateLog(log *model.AlertLog) error {
return global.AlertDB.Model(&model.AlertLog{}).Create(&log).Error
}
func (a *AlertRepo) UpdateLog(id uint, maps map[string]interface{}) error {
return global.AlertDB.Model(&model.AlertLog{}).Where("id = ?", id).Updates(maps).Error
}
func (a *AlertRepo) BatchUpdateLogBy(maps map[string]interface{}, opts ...DBOption) error {
db, _ := getAlertDB(opts...)
if len(opts) == 0 {
db = db.Where("1=1")
}
return db.Model(&model.AlertLog{}).Updates(&maps).Error
}
func (a *AlertRepo) PageLog(page, size int, opts ...DBOption) (int64, []model.AlertLog, error) {
var alerts []model.AlertLog
db := global.AlertDB.Model(&model.AlertLog{})
for _, opt := range opts {
db = opt(db)
}
count := int64(0)
db = db.Order("created_at desc").Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&alerts).Error
return count, alerts, err
}
func (a *AlertRepo) ListLog(opts ...DBOption) ([]model.AlertLog, error) {
var alertLog []model.AlertLog
db, _ := getAlertDB(opts...)
err := db.Find(&alertLog).Error
return alertLog, err
}
func (a *AlertRepo) DeleteLog(opts ...DBOption) error {
db, _ := getAlertDB(opts...)
return db.Delete(&model.AlertLog{}).Error
}
func (a *AlertRepo) CleanAlertLogs() error {
return global.AlertDB.Where("1 = 1").Delete(&model.AlertLog{}).Error
}
func (a *AlertRepo) CreateAlertTask(alertTaskBase *model.AlertTask) error {
return global.AlertDB.Model(&model.AlertTask{}).Create(&alertTaskBase).Error
}
func (a *AlertRepo) DeleteAlertTask(opts ...DBOption) error {
db, _ := getAlertDB(opts...)
return db.Delete(&model.AlertTask{}).Error
}
func (a *AlertRepo) GetAlertTask(opts ...DBOption) (model.AlertTask, error) {
var data model.AlertTask
db, _ := getAlertDB(opts...)
err := db.First(&data).Error
return data, err
}
func (a *AlertRepo) LoadTaskCount(alertType string, project string, method string) (uint, uint, error) {
var (
todayCount int64
totalCount int64
)
_ = global.AlertDB.Model(&model.AlertTask{}).Where("type = ? AND quota_type = ? AND method = ?", alertType, project, method).Count(&totalCount).Error
now := time.Now()
todayMidnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
tomorrowMidnight := todayMidnight.Add(24 * time.Hour)
err := global.AlertDB.Model(&model.AlertTask{}).Where("type = ? AND quota_type = ? AND method = ? AND created_at > ? AND created_at < ?", alertType, project, method, todayMidnight, tomorrowMidnight).Count(&todayCount).Error
return uint(todayCount), uint(totalCount), err
}
func (a *AlertRepo) GetTaskLog(alertType string, alertId uint) (time.Time, error) {
var newDate time.Time
status := []string{constant.AlertSuccess, constant.AlertPushSuccess, constant.AlertSyncError, constant.AlertPushing}
now := time.Now()
todayMidnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
tomorrowMidnight := todayMidnight.Add(24 * time.Hour)
err := global.AlertDB.Model(&model.AlertLog{}).
Where("type = ? AND alert_id = ? AND status in ? AND created_at > ? AND created_at < ?", alertType, alertId, status, todayMidnight, tomorrowMidnight).
Order("created_at DESC").
Limit(1).
Pluck("created_at", &newDate).Error
if err != nil {
return time.Time{}, err
}
if newDate.IsZero() {
return time.Time{}, nil
}
return newDate, nil
}
func getAlertDB(opts ...DBOption) (*gorm.DB, error) {
var db *gorm.DB
db = global.AlertDB
for _, opt := range opts {
db = opt(db)
}
return db, nil
}
func (a *AlertRepo) GetLicensePushCount(method string) (uint, error) {
var (
todayCount int64
)
now := time.Now()
todayMidnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
tomorrowMidnight := todayMidnight.Add(24 * time.Hour)
err := global.AlertDB.Model(&model.AlertTask{}).Where("created_at > ? AND created_at < ? AND method = ?", todayMidnight, tomorrowMidnight, method).Count(&todayCount).Error
return uint(todayCount), err
}
func (a *AlertRepo) AlertConfigList(opts ...DBOption) ([]model.AlertConfig, error) {
var config []model.AlertConfig
db, _ := getAlertDB(opts...)
err := db.Find(&config).Error
return config, err
}
func (a *AlertRepo) UpdateAlertConfig(maps map[string]interface{}, opts ...DBOption) error {
db, _ := getAlertDB(opts...)
return db.Model(&model.AlertConfig{}).Updates(maps).Error
}
func (a *AlertRepo) CreateAlertConfig(config *model.AlertConfig) error {
return global.AlertDB.Model(&model.AlertConfig{}).Create(config).Error
}
func (a *AlertRepo) DeleteAlertConfig(opts ...DBOption) error {
db, _ := getAlertDB(opts...)
return db.Delete(&model.AlertConfig{}).Error
}
func (a *AlertRepo) GetConfig(opts ...DBOption) (model.AlertConfig, error) {
var alertConfig model.AlertConfig
db, _ := getAlertDB(opts...)
err := db.First(&alertConfig).Error
return alertConfig, err
}
func (a *AlertRepo) GetConfigById(id uint) (model.AlertConfig, error) {
var config model.AlertConfig
err := global.AlertDB.First(&config, id).Error
return config, err
}
func (a *AlertRepo) WithByTypeNotIn(types []string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("`type` NOT IN (?)", types)
}
}
func (a *AlertRepo) PageAlertConfig(page, size int, opts ...DBOption) (int64, []model.AlertConfig, error) {
var configs []model.AlertConfig
db := global.AlertDB.Model(&model.AlertConfig{})
for _, opt := range opts {
db = opt(db)
}
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&configs).Error
return count, configs, err
}
var singletonTypes = map[string]bool{
constant.CommonConfig: true,
}
func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
tx := global.AlertDB.Begin()
if tx.Error != nil {
return tx.Error
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
panic(r)
}
}()
var oldConfigs []model.AlertConfig
if err := tx.Find(&oldConfigs).Error; err != nil {
tx.Rollback()
return err
}
usedConfigIDs, err := loadUsedAlertConfigIDs(tx)
if err != nil {
tx.Rollback()
return err
}
oldConfigMap := make(map[string]uint)
oldConfigByType := make(map[string][]model.AlertConfig)
oldConfigByKey := make(map[string][]model.AlertConfig)
consumedConfigIDs := make(map[uint]struct{})
for _, item := range oldConfigs {
if singletonTypes[item.Type] {
oldConfigMap[item.Type] = item.ID
continue
}
oldConfigByType[item.Type] = append(oldConfigByType[item.Type], item)
oldConfigByKey[alertConfigSyncKey(item)] = append(oldConfigByKey[alertConfigSyncKey(item)], item)
}
for _, item := range data {
if singletonTypes[item.Type] {
if val, ok := oldConfigMap[item.Type]; ok {
item.ID = val
delete(oldConfigMap, item.Type)
consumedConfigIDs[item.ID] = struct{}{}
} else {
item.ID = 0
}
if item.ID == 0 {
if err := tx.Create(&item).Error; err != nil {
tx.Rollback()
return err
}
} else if err := tx.Save(&item).Error; err != nil {
tx.Rollback()
return err
}
continue
}
key := alertConfigSyncKey(item)
if matched, ok := popAlertConfigByKey(oldConfigByKey, key); ok {
item.ID = matched.ID
consumedConfigIDs[item.ID] = struct{}{}
if err := tx.Save(&item).Error; err != nil {
tx.Rollback()
return err
}
deleteAlertConfigByID(oldConfigByType, matched.ID)
continue
}
if matched, ok := popUnusedAlertConfigByType(oldConfigByType, usedConfigIDs, item.Type); ok {
item.ID = matched.ID
consumedConfigIDs[item.ID] = struct{}{}
if err := tx.Save(&item).Error; err != nil {
tx.Rollback()
return err
}
continue
}
item.ID = 0
if err := tx.Create(&item).Error; err != nil {
tx.Rollback()
return err
}
}
for _, item := range oldConfigs {
if _, used := usedConfigIDs[item.ID]; used {
continue
}
if _, kept := consumedConfigIDs[item.ID]; kept {
continue
}
if err := tx.Where("id = ?", item.ID).Delete(&model.AlertConfig{}).Error; err != nil {
tx.Rollback()
return err
}
}
if err := tx.Commit().Error; err != nil {
tx.Rollback()
return err
}
return nil
}
func loadUsedAlertConfigIDs(tx *gorm.DB) (map[uint]struct{}, error) {
var alerts []model.Alert
if err := tx.Select("method").Find(&alerts).Error; err != nil {
return nil, err
}
usedIDs := make(map[uint]struct{})
for _, alert := range alerts {
for _, item := range strings.Split(alert.Method, ",") {
item = strings.TrimSpace(item)
if item == "" {
continue
}
id, err := strconv.ParseUint(item, 10, 64)
if err != nil {
continue
}
usedIDs[uint(id)] = struct{}{}
}
}
return usedIDs, nil
}
func alertConfigSyncKey(item model.AlertConfig) string {
return item.Type + "::" + normalizeAlertConfigJSON(item.Config)
}
func normalizeAlertConfigJSON(config string) string {
trimmed := strings.TrimSpace(config)
if trimmed == "" {
return ""
}
var data any
if err := json.Unmarshal([]byte(trimmed), &data); err != nil {
return trimmed
}
buf, err := json.Marshal(data)
if err != nil {
return trimmed
}
return string(buf)
}
func popAlertConfigByKey(configMap map[string][]model.AlertConfig, key string) (model.AlertConfig, bool) {
items := configMap[key]
if len(items) == 0 {
return model.AlertConfig{}, false
}
item := items[0]
if len(items) == 1 {
delete(configMap, key)
} else {
configMap[key] = items[1:]
}
return item, true
}
func popUnusedAlertConfigByType(configMap map[string][]model.AlertConfig, usedConfigIDs map[uint]struct{}, configType string) (model.AlertConfig, bool) {
items := configMap[configType]
if len(items) == 0 {
return model.AlertConfig{}, false
}
for idx, item := range items {
if _, used := usedConfigIDs[item.ID]; used {
continue
}
if idx == 0 {
if len(items) == 1 {
delete(configMap, configType)
} else {
configMap[configType] = items[1:]
}
} else {
configMap[configType] = append(items[:idx], items[idx+1:]...)
}
return item, true
}
return model.AlertConfig{}, false
}
func deleteAlertConfigByID(configMap map[string][]model.AlertConfig, id uint) {
for key, items := range configMap {
for idx, item := range items {
if item.ID != id {
continue
}
if len(items) == 1 {
delete(configMap, key)
} else {
configMap[key] = append(items[:idx], items[idx+1:]...)
}
return
}
}
}