feat: implement V2 alert center and scoped alert governance

This commit is contained in:
Yvan
2026-11-05 20:08:29 +08:00
parent f8e26c5baf
commit 999c9a6315
57 changed files with 2711 additions and 216 deletions
@@ -0,0 +1,109 @@
package business
import (
"context"
"kra/internal/biz/business"
"kra/internal/service/dto"
"strconv"
"strings"
)
type AlertGovernanceUsecase interface {
Candidates(context.Context, string, int) ([]*business.AlertChannelOption, error)
Gap(context.Context) (*business.AlertGap, error)
SaveGap(context.Context, bool, int) error
List(context.Context, business.AlertExemptionFilter, ...business.ListOption) ([]*business.AlertExemption, int64, error)
Add(context.Context, business.ChannelKey, string) error
Remove(context.Context, uint64) error
}
func (s *AlertGovernanceService) Candidates(ctx context.Context, keyword string) ([]*dto.AlertChannelOption, error) {
if len([]rune(keyword)) > 100 {
return nil, business.ErrAlertRecordInput
}
rows, err := s.uc.Candidates(ctx, keyword, 30)
if err != nil {
return nil, err
}
result := make([]*dto.AlertChannelOption, 0, len(rows))
for _, row := range rows {
result = append(result, &dto.AlertChannelOption{ID: row.ID, SourceDB: row.SourceDB, Account: row.Name})
}
return result, nil
}
type AlertGovernanceService struct{ uc AlertGovernanceUsecase }
func NewAlertGovernanceService(uc *business.AlertGovernanceUsecase) *AlertGovernanceService {
return &AlertGovernanceService{uc}
}
func (s *AlertGovernanceService) Gap(ctx context.Context) (*dto.AlertGapRequest, error) {
v, err := s.uc.Gap(ctx)
if err != nil {
return nil, err
}
return &dto.AlertGapRequest{Enabled: v.Enabled, Days: v.Days}, nil
}
func (s *AlertGovernanceService) SaveGap(ctx context.Context, req *dto.AlertGapRequest) error {
if req == nil || req.Days < 1 || req.Days > 2147483647 {
return business.ErrAlertRuleInput
}
return s.uc.SaveGap(ctx, req.Enabled, req.Days)
}
func (s *AlertGovernanceService) List(ctx context.Context, req *dto.AlertExemptionQuery) (*dto.AlertExemptionPage, error) {
if req == nil {
return nil, business.ErrAlertRecordInput
}
page, size := max(req.Page, 1), req.PageSize
if size < 1 {
size = 10
}
size = min(size, 500)
if page > int(^uint(0)>>1)/size {
return nil, business.ErrAlertRecordInput
}
rows, total, err := s.uc.List(ctx, business.AlertExemptionFilter{Name: strings.TrimSpace(req.Name), Active: req.Active}, business.ListOffset((page-1)*size), business.ListLimit(size))
if err != nil {
return nil, err
}
out := &dto.AlertExemptionPage{Total: total, Rows: make([]*dto.AlertExemptionResponse, 0, len(rows))}
for _, r := range rows {
out.Rows = append(out.Rows, &dto.AlertExemptionResponse{ID: r.ID, SourceDB: r.SourceDB, PromoteID: r.ChannelKey.ID, ChannelName: r.ChannelName, AddedName: r.AddedName, AddedAt: localTime(&r.AddedAt), Reason: r.Reason, Active: r.RemovedAt == nil, RemovedName: r.RemovedName, RemovedAt: localTime(r.RemovedAt), RemoveReason: r.RemoveReason})
}
return out, nil
}
func (s *AlertGovernanceService) Add(ctx context.Context, req *dto.AlertExemptionRequest) error {
if req == nil || len([]rune(req.Reason)) > 500 {
return business.ErrAlertRecordInput
}
id, err := strconv.ParseUint(req.PromoteID.String(), 10, 64)
if err != nil || id == 0 {
return business.ErrAlertRecordInput
}
return s.uc.Add(ctx, business.ChannelKey{SourceDB: req.SourceDB, ID: id}, req.Reason)
}
func (s *AlertGovernanceService) Remove(ctx context.Context, id uint64) error {
if id == 0 {
return business.ErrAlertRecordInput
}
return s.uc.Remove(ctx, id)
}
func (s *AlertRecordService) Ignore(ctx context.Context, req *dto.AlertIgnoreRequest) error {
if req == nil || len(req.IDs) == 0 || len(req.IDs) > 500 {
return business.ErrAlertRecordInput
}
ids := make([]uint64, 0, len(req.IDs))
seen := map[uint64]bool{}
for _, n := range req.IDs {
id, err := strconv.ParseUint(n.String(), 10, 64)
if err != nil || id == 0 {
return business.ErrAlertRecordInput
}
if !seen[id] {
ids = append(ids, id)
seen[id] = true
}
}
return s.uc.Ignore(ctx, ids)
}
+9 -3
View File
@@ -11,12 +11,18 @@ import (
)
type AlertRecordUsecase interface {
Platforms(context.Context) ([]string, error)
Ignore(context.Context, []uint64) error
List(context.Context, business.AlertRecordFilter, ...business.ListOption) (*business.AlertRecordPage, error)
Detail(context.Context, uint64) (*business.AlertRecordDetail, error)
Process(context.Context, uint64, business.AlertAction, string) error
AddNote(context.Context, uint64, string, []string) error
}
func (s *AlertRecordService) Platforms(ctx context.Context) ([]string, error) {
return s.uc.Platforms(ctx)
}
type AlertRecordService struct{ uc AlertRecordUsecase }
func NewAlertRecordService(uc *business.AlertRecordUsecase) *AlertRecordService {
@@ -28,7 +34,7 @@ func alertRecordResponse(r *business.AlertRecord) *dto.AlertRecordResponse {
if r.MonitorMetric != nil {
metricLabel = business.AlertMetricLabel(*r.MonitorMetric)
}
return &dto.AlertRecordResponse{ID: r.ID, RuleID: r.RuleID, PlatformCode: r.PlatformCode, PlatformName: r.PlatformName, RuleName: r.RuleName, RuleType: r.RuleType, RuleTypeLabel: business.AlertRuleTypeLabel(r.RuleType), MonitorMetric: r.MonitorMetric, MonitorMetricLabel: metricLabel, TargetPromoteID: r.TargetPromoteID, TargetPromoteName: r.TargetPromoteName, BusinessID: r.BusinessID, BusinessName: r.BusinessName, AlertContent: r.AlertContent, Status: r.Status, StatusLabel: business.AlertStatusLabel(r.Status), CreatedAt: localTime(&r.CreatedAt), UpdatedAt: localTime(&r.UpdatedAt)}
return &dto.AlertRecordResponse{ID: r.ID, TotalPlayers: r.TotalPlayers, TotalPay: r.TotalPay, WarningColor: r.WarningColor, Lost: r.Lost, RuleID: r.RuleID, PlatformCode: r.PlatformCode, PlatformName: r.PlatformName, RuleName: r.RuleName, RuleType: r.RuleType, RuleTypeLabel: business.AlertRuleTypeLabel(r.RuleType), MonitorMetric: r.MonitorMetric, MonitorMetricLabel: metricLabel, TargetPromoteID: r.TargetPromoteID, TargetPromoteName: r.TargetPromoteName, BusinessID: r.BusinessID, BusinessName: r.BusinessName, AlertContent: r.AlertContent, Status: r.Status, StatusLabel: business.AlertStatusLabel(r.Status), CreatedAt: localTime(&r.CreatedAt), UpdatedAt: localTime(&r.UpdatedAt)}
}
func (s *AlertRecordService) List(ctx context.Context, req *dto.AlertRecordQuery) (*dto.AlertRecordPage, error) {
@@ -41,7 +47,7 @@ func (s *AlertRecordService) List(ctx context.Context, req *dto.AlertRecordQuery
if req.MonitorMetric != nil && (*req.MonitorMetric < 1 || *req.MonitorMetric > 3) {
return nil, business.ErrAlertRecordInput
}
if req.Status != nil && (*req.Status < 1 || *req.Status > 5) {
if req.Status != nil && (*req.Status < 1 || *req.Status > 6) {
return nil, business.ErrAlertRecordInput
}
page, size := req.Page, req.PageSize
@@ -57,7 +63,7 @@ func (s *AlertRecordService) List(ctx context.Context, req *dto.AlertRecordQuery
if page > int(^uint(0)>>1)/size {
return nil, business.ErrAlertRecordInput
}
filter := business.AlertRecordFilter{TargetPromoteID: req.TargetPromoteID, BusinessID: req.BusinessID, TargetPromoteName: strings.TrimSpace(req.TargetPromoteName), RuleName: strings.TrimSpace(req.RuleName), RuleType: strings.TrimSpace(req.RuleType), BusinessName: strings.TrimSpace(req.BusinessName), MonitorMetric: req.MonitorMetric, Status: req.Status}
filter := business.AlertRecordFilter{AlertContent: strings.TrimSpace(req.AlertContent), Lost: req.Lost, TargetPromoteID: req.TargetPromoteID, BusinessID: req.BusinessID, TargetPromoteName: strings.TrimSpace(req.TargetPromoteName), RuleName: strings.TrimSpace(req.RuleName), RuleType: strings.TrimSpace(req.RuleType), BusinessName: strings.TrimSpace(req.BusinessName), MonitorMetric: req.MonitorMetric, Status: req.Status}
seen := map[string]bool{}
filter.PlatformBusierID = req.PlatformBusierID
for _, code := range strings.Split(req.PlatformCodes, ",") {
+15 -3
View File
@@ -30,13 +30,13 @@ func alertRuleResponse(r *business.AlertRule) *dto.AlertRuleResponse {
n := json.Number(*r.MomThreshold)
threshold = &n
}
return &dto.AlertRuleResponse{ID: r.ID, RuleName: r.Name, RuleType: r.Type, RuleTypeLabel: business.AlertRuleTypeLabel(r.Type), MonitorMetric: r.Metric, MonitorMetricLabel: business.AlertMetricLabel(r.Metric), StatPeriodDays: r.StatPeriodDays, MomDirection: r.MomDirection, MomDirectionLabel: business.AlertDirectionLabel(r.MomDirection), MomThreshold: threshold, UnloginDays: r.UnloginDays, MonitorScope: r.MonitorScope, WarningColor: r.WarningColor, CreatorID: r.CreatorID, CreatorName: r.CreatorName, Enabled: r.Enabled, CreatedAt: localTime(&r.CreatedAt), UpdatedAt: localTime(&r.UpdatedAt), RuleDescription: r.Description()}
return &dto.AlertRuleResponse{ID: r.ID, ConditionMode: r.ConditionMode, AmountDirection: r.AmountDirection, AmountThreshold: r.AmountThreshold, RuleName: r.Name, RuleType: r.Type, RuleTypeLabel: business.AlertRuleTypeLabel(r.Type), MonitorMetric: r.Metric, MonitorMetricLabel: business.AlertMetricLabel(r.Metric), StatPeriodDays: r.StatPeriodDays, MomDirection: r.MomDirection, MomDirectionLabel: business.AlertDirectionLabel(r.MomDirection), MomThreshold: threshold, UnloginDays: r.UnloginDays, MonitorScope: r.MonitorScope, WarningColor: r.WarningColor, CreatorID: r.CreatorID, CreatorName: r.CreatorName, Enabled: r.Enabled, CreatedAt: localTime(&r.CreatedAt), UpdatedAt: localTime(&r.UpdatedAt), RuleDescription: r.Description()}
}
func (s *AlertRuleService) List(ctx context.Context, req *dto.AlertRuleQuery) (*dto.AlertRulePage, error) {
if req == nil {
return nil, business.ErrAlertRuleInput
}
filter := business.AlertRuleFilter{Name: req.RuleName, Type: req.RuleType}
filter := business.AlertRuleFilter{Name: strings.TrimSpace(req.RuleName), Type: strings.TrimSpace(req.RuleType)}
if req.MonitorMetric != "" {
n, err := strconv.Atoi(req.MonitorMetric)
if err != nil || n < 1 || n > 3 {
@@ -87,11 +87,23 @@ func (s *AlertRuleService) Save(ctx context.Context, req *dto.AlertRuleRequest)
return 0, business.ErrAlertRuleInput
}
}
rule := &business.AlertRule{ID: req.ID, Name: req.RuleName, Type: req.RuleType, Metric: req.MonitorMetric, StatPeriodDays: req.StatPeriodDays, MomDirection: req.MomDirection, UnloginDays: req.UnloginDays, WarningColor: req.WarningColor}
var id uint64
if req.ID != "" {
var err error
id, err = strconv.ParseUint(req.ID.String(), 10, 64)
if err != nil {
return 0, business.ErrAlertRuleInput
}
}
rule := &business.AlertRule{ID: id, ConditionMode: req.ConditionMode, AmountDirection: req.AmountDirection, Name: strings.TrimSpace(req.RuleName), Type: req.RuleType, Metric: req.MonitorMetric, StatPeriodDays: req.StatPeriodDays, MomDirection: req.MomDirection, UnloginDays: req.UnloginDays, WarningColor: strings.TrimSpace(req.WarningColor)}
if req.MomThreshold != nil {
value := req.MomThreshold.String()
rule.MomThreshold = &value
}
if req.AmountThreshold != nil {
value := req.AmountThreshold.String()
rule.AmountThreshold = &value
}
return s.uc.Save(ctx, rule)
}
func (s *AlertRuleService) Delete(ctx context.Context, id uint64) error {
+45
View File
@@ -0,0 +1,45 @@
package dto
import "encoding/json"
type AlertChannelOption struct {
ID uint64 `json:"id,string"`
SourceDB string `json:"sourceDb"`
Account string `json:"account"`
}
type AlertIgnoreRequest struct {
IDs []json.Number `json:"ids"`
}
type AlertGapRequest struct {
Enabled bool `json:"enabled"`
Days int `json:"days"`
}
type AlertExemptionQuery struct {
Page int `form:"pageNum"`
PageSize int `form:"pageSize"`
Name string `form:"channelName"`
Active *bool `form:"active"`
}
type AlertExemptionRequest struct {
SourceDB string `json:"sourceDb"`
PromoteID json.Number `json:"promoteId"`
Reason string `json:"reason"`
}
type AlertExemptionResponse struct {
ID uint64 `json:"id,string"`
SourceDB string `json:"sourceDb"`
PromoteID uint64 `json:"promoteId,string"`
ChannelName string `json:"channelName"`
AddedName string `json:"addedName"`
AddedAt *string `json:"addedAt"`
Reason string `json:"reason"`
Active bool `json:"active"`
RemovedName string `json:"removedName"`
RemovedAt *string `json:"removedAt"`
RemoveReason string `json:"removeReason"`
}
type AlertExemptionPage struct {
Rows []*AlertExemptionResponse `json:"rows"`
Total int64 `json:"total"`
}
+16 -11
View File
@@ -3,6 +3,8 @@ package dto
import "encoding/json"
type AlertRecordQuery struct {
AlertContent string `form:"alertContent"`
Lost *bool `form:"lost"`
Page int `form:"pageNum"`
PageSize int `form:"pageSize"`
PlatformCodes string `form:"platformCodes"`
@@ -29,18 +31,21 @@ type AlertNoteRequest struct {
}
type AlertRecordResponse struct {
ID uint64 `json:"id,string"`
PlatformCode string `json:"platformCode"`
PlatformName string `json:"platformName"`
RuleID uint64 `json:"ruleId,string"`
RuleName string `json:"ruleName"`
RuleType string `json:"ruleType"`
RuleTypeLabel string `json:"ruleTypeLabel"`
MonitorMetric *int `json:"monitorMetric"`
MonitorMetricLabel string `json:"monitorMetricLabel"`
// 旧视图声明了颜色,但记录没有对应快照字段,保留空值。
TotalPlayers *int64 `json:"totalPlayers"`
TotalPay *string `json:"totalPay"`
Lost *bool `json:"lost"`
ID uint64 `json:"id,string"`
PlatformCode string `json:"platformCode"`
PlatformName string `json:"platformName"`
RuleID uint64 `json:"ruleId,string"`
RuleName string `json:"ruleName"`
RuleType string `json:"ruleType"`
RuleTypeLabel string `json:"ruleTypeLabel"`
MonitorMetric *int `json:"monitorMetric"`
MonitorMetricLabel string `json:"monitorMetricLabel"`
// 历史记录无快照时返回 null,不回填当前数值。
WarningColor *string `json:"warningColor"`
TargetPromoteID uint64 `json:"targetPromoteId"`
TargetPromoteID uint64 `json:"targetPromoteId,string"`
TargetPromoteName string `json:"targetPromoteName"`
BusinessID uint `json:"businessId"`
BusinessName string `json:"businessName"`
+16 -10
View File
@@ -10,18 +10,24 @@ type AlertRuleQuery struct {
MonitorMetric string `form:"monitorMetric"`
}
type AlertRuleRequest struct {
ID uint64 `json:"id"`
RuleName string `json:"ruleName"`
RuleType string `json:"ruleType"`
MonitorMetric int `json:"monitorMetric"`
StatPeriodDays *int `json:"statPeriodDays"`
MomDirection *int `json:"momDirection"`
MomThreshold *json.Number `json:"momThreshold"`
UnloginDays *int `json:"unloginDays"`
WarningColor string `json:"warningColor"`
ConditionMode int `json:"conditionMode"`
AmountDirection int `json:"amountDirection"`
AmountThreshold *json.Number `json:"amountThreshold"`
ID json.Number `json:"id"`
RuleName string `json:"ruleName"`
RuleType string `json:"ruleType"`
MonitorMetric int `json:"monitorMetric"`
StatPeriodDays *int `json:"statPeriodDays"`
MomDirection *int `json:"momDirection"`
MomThreshold *json.Number `json:"momThreshold"`
UnloginDays *int `json:"unloginDays"`
WarningColor string `json:"warningColor"`
}
type AlertRuleResponse struct {
ID uint64 `json:"id"`
ConditionMode int `json:"conditionMode"`
AmountDirection int `json:"amountDirection"`
AmountThreshold *string `json:"amountThreshold"`
ID uint64 `json:"id,string"`
RuleName string `json:"ruleName"`
RuleType string `json:"ruleType"`
RuleTypeLabel string `json:"ruleTypeLabel"`
+1
View File
@@ -19,6 +19,7 @@ var ProviderSet = wire.NewSet(
businessservice.NewBindingService,
businessservice.NewPasswordResetService,
businessservice.NewBusinessPromoteService,
businessservice.NewAlertGovernanceService,
businessservice.NewAlertRuleService,
businessservice.NewAlertRecordService,
businessservice.NewActivationService,