发布 0.2.0:模型池自愈、探测修正、任务异步触发与删除加固
CI / test (push) Successful in 32s
Release / release (push) Successful in 52s

This commit is contained in:
2026-07-10 20:25:37 +08:00
parent dbba1f4905
commit 7706f59549
19 changed files with 978 additions and 107 deletions
+41 -38
View File
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"log"
"strings"
"gorm.io/gorm"
@@ -104,7 +105,8 @@ func planTenantTask(task model.Task, id uint) (tenantTaskAction, bool, error) {
func planSnatchTask(task model.Task, id uint) (tenantTaskAction, bool, error) {
var payload snatchPayload
if err := json.Unmarshal([]byte(task.Payload), &payload); err != nil {
return tenantTaskAction{}, false, fmt.Errorf("parse task %d payload: %w", task.ID, err)
logSkippedTask(task, err)
return tenantTaskAction{}, false, nil
}
if payload.OciConfigID != id {
return tenantTaskAction{}, false, nil
@@ -112,10 +114,17 @@ func planSnatchTask(task model.Task, id uint) (tenantTaskAction, bool, error) {
return tenantTaskAction{task: task, deleteTask: true}, true, nil
}
// logSkippedTask 记录 payload 无法解析而被跳过的任务:坏数据无法归属租户,
// fail-closed 会永久阻断删除且只能手工修库;改为保留原任务(不删不改)并放行删除。
func logSkippedTask(task model.Task, err error) {
log.Printf("tenant delete: 任务 %d(%s)payload 无法解析,跳过处理: %v", task.ID, task.Type, err)
}
func planMultiTenantTask(task model.Task, id uint) (tenantTaskAction, bool, error) {
payload, err := decodeConfigIDs(task)
if err != nil {
return tenantTaskAction{}, false, err
logSkippedTask(task, err)
return tenantTaskAction{}, false, nil
}
if len(payload.OciConfigIDs) == 0 {
return tenantTaskAction{task: task, payload: task.Payload}, true, nil
@@ -197,11 +206,11 @@ func resetTenantTask(tx *gorm.DB, action tenantTaskAction) error {
}
func deleteTenantEvents(tx *gorm.DB, id uint, result *tenantDeleteResult) error {
ruleIDs, eventIDs, affectedRules, err := loadTenantEventRefs(tx, id)
ruleIDs, affectedRules, err := loadTenantEventRefs(tx, id)
if err != nil {
return err
}
if err := deleteAlertHits(tx, ruleIDs, eventIDs); err != nil {
if err := deleteAlertHits(tx, id); err != nil {
return err
}
result.alertRuleIDs = mergeIDs(ruleIDs, affectedRules)
@@ -214,20 +223,19 @@ func deleteTenantEvents(tx *gorm.DB, id uint, result *tenantDeleteResult) error
return nil
}
func loadTenantEventRefs(tx *gorm.DB, id uint) ([]uint, []uint, []uint, error) {
func loadTenantEventRefs(tx *gorm.DB, id uint) ([]uint, []uint, error) {
ruleIDs, err := lockedTenantRuleIDs(tx, id)
if err != nil {
return nil, nil, nil, fmt.Errorf("load tenant alert rules: %w", err)
return nil, nil, fmt.Errorf("load tenant alert rules: %w", err)
}
eventIDs, err := lockedTenantEventIDs(tx, id)
if err := lockTenantEventRows(tx, id); err != nil {
return nil, nil, fmt.Errorf("load tenant log events: %w", err)
}
affected, err := alertHitRuleIDs(tx, id)
if err != nil {
return nil, nil, nil, fmt.Errorf("load tenant log events: %w", err)
return nil, nil, err
}
affected, err := alertHitRuleIDs(tx, eventIDs)
if err != nil {
return nil, nil, nil, err
}
return ruleIDs, eventIDs, affected, nil
return ruleIDs, affected, nil
}
func lockedTenantRuleIDs(tx *gorm.DB, id uint) ([]uint, error) {
@@ -241,23 +249,24 @@ func lockedTenantRuleIDs(tx *gorm.DB, id uint) ([]uint, error) {
return ids, err
}
func lockedTenantEventIDs(tx *gorm.DB, id uint) ([]uint, error) {
// lockTenantEventRows 对租户全部日志事件行加 FOR UPDATE 锁(SQLite 忽略,
// MySQL/PG 阻塞并发命中写入);事件可达数万,ID 不再回传拼接 SQL,
// 后续删除与关联查询一律用子查询,避免绑定变量上限。
func lockTenantEventRows(tx *gorm.DB, id uint) error {
var rows []model.LogEvent
err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Select("id").
return tx.Clauses(clause.Locking{Strength: "UPDATE"}).Select("id").
Where("oci_config_id = ?", id).Order("id").Find(&rows).Error
ids := make([]uint, 0, len(rows))
for _, row := range rows {
ids = append(ids, row.ID)
}
return ids, err
}
func alertHitRuleIDs(tx *gorm.DB, eventIDs []uint) ([]uint, error) {
if len(eventIDs) == 0 {
return nil, nil
}
// tenantEventIDs 构造「本租户日志事件 ID」子查询,供 IN (?) 内联。
func tenantEventIDs(tx *gorm.DB, id uint) *gorm.DB {
return tx.Model(&model.LogEvent{}).Select("id").Where("oci_config_id = ?", id)
}
// alertHitRuleIDs 找出命中引用了本租户事件的规则 ID(去重,可能含他租户/全局规则)。
func alertHitRuleIDs(tx *gorm.DB, id uint) ([]uint, error) {
ids := make([]uint, 0)
err := tx.Model(&model.AlertRuleHit{}).Where("log_event_id IN ?", eventIDs).
err := tx.Model(&model.AlertRuleHit{}).Where("log_event_id IN (?)", tenantEventIDs(tx, id)).
Distinct().Pluck("rule_id", &ids).Error
if err != nil {
return nil, fmt.Errorf("load affected alert rules: %w", err)
@@ -280,19 +289,13 @@ func mergeIDs(groups ...[]uint) []uint {
return out
}
func deleteAlertHits(tx *gorm.DB, ruleIDs, eventIDs []uint) error {
query := tx.Model(&model.AlertRuleHit{})
switch {
case len(ruleIDs) > 0 && len(eventIDs) > 0:
query = query.Where("rule_id IN ? OR log_event_id IN ?", ruleIDs, eventIDs)
case len(ruleIDs) > 0:
query = query.Where("rule_id IN ?", ruleIDs)
case len(eventIDs) > 0:
query = query.Where("log_event_id IN ?", eventIDs)
default:
return nil
}
if err := query.Delete(&model.AlertRuleHit{}).Error; err != nil {
// deleteAlertHits 删除本租户相关的全部命中:规则属于本租户,或命中引用了
// 本租户的日志事件(他租户/全局规则命中本租户事件的行一并清)。
func deleteAlertHits(tx *gorm.DB, id uint) error {
rules := tx.Model(&model.AlertRule{}).Select("id").Where("oci_config_id = ?", id)
err := tx.Where("rule_id IN (?) OR log_event_id IN (?)", rules, tenantEventIDs(tx, id)).
Delete(&model.AlertRuleHit{}).Error
if err != nil {
return fmt.Errorf("delete tenant alert hits: %w", err)
}
return nil