feat: implement V2 alert center and scoped alert governance
This commit is contained in:
@@ -40,6 +40,7 @@ func TestAlertRecordHTTPCompleteWorkflowAndVisibility(t *testing.T) {
|
||||
"INSERT INTO tab_promote VALUES(100,'lx',14,1,0),(100,'hw',14,1,1),(100,'yq',14,1,1)",
|
||||
"INSERT INTO sys_user_bind_platform(user_id,source_db,platform_busier_id,status) VALUES(9,'lx',14,'0'),(10,'hw',14,'0'),(99,'yq',14,'0')",
|
||||
"INSERT INTO sys_alert_record(id,platform_code,target_promote_id,business_id,rule_name,status,created_at,updated_at) VALUES(9007199254740993,'lx',100,7,'目标规则',1,'2026-11-03 08:00:00','2026-11-03 08:00:00'),(2,'hw',100,7,'平级规则',1,'2026-11-03 08:00:00','2026-11-03 08:00:00'),(3,'lx',100,99,'归属错误',1,'2026-11-03 08:00:00','2026-11-03 08:00:00'),(4,'yq',100,99,'其他规则',1,'2026-11-03 08:00:00','2026-11-03 08:00:00'),(5,'lx',100,7,'关闭规则',5,'2026-11-03 08:00:00','2026-11-03 08:00:00'),(6,'lx',100,7,'待审规则',3,'2026-11-03 08:00:00','2026-11-03 08:00:00')",
|
||||
"ALTER TABLE tab_promote ADD COLUMN account TEXT DEFAULT '测试渠道'",
|
||||
"INSERT INTO media_file_upload_and_downloads(id,url) VALUES(1,'/files/one.png'),(2,'/files/two.png')",
|
||||
} {
|
||||
if err := db.Exec(sql).Error; err != nil {
|
||||
@@ -60,6 +61,7 @@ func TestAlertRecordHTTPCompleteWorkflowAndVisibility(t *testing.T) {
|
||||
c.Next()
|
||||
})
|
||||
router.RegisterAlertRecord(engine.Group(""), handler.NewAlertRecord(service))
|
||||
router.RegisterAlertGovernance(engine.Group(""), handler.NewAlertGovernance(businessservice.NewAlertGovernanceService(businessbiz.NewAlertGovernanceUsecase(businessdata.NewAlertGovernanceRepo(provider), accounts))))
|
||||
server := httptest.NewServer(engine)
|
||||
defer server.Close()
|
||||
call := func(user int, method, path, body string, expected int) map[string]any {
|
||||
@@ -94,6 +96,10 @@ func TestAlertRecordHTTPCompleteWorkflowAndVisibility(t *testing.T) {
|
||||
}
|
||||
list(1, "", 6)
|
||||
list(7, "", 4)
|
||||
platforms := call(8, "GET", "/sw/alertRecord/platforms", "", 0)["data"].([]any)
|
||||
if len(platforms) != 1 || platforms[0] != "lx" {
|
||||
t.Fatal("平台下拉必须按当前可见绑定过滤")
|
||||
}
|
||||
list(8, "", 3)
|
||||
list(9, "", 3)
|
||||
list(7, "?businessId=8", 3)
|
||||
@@ -101,8 +107,8 @@ func TestAlertRecordHTTPCompleteWorkflowAndVisibility(t *testing.T) {
|
||||
list(1, "?businessId=8", 4)
|
||||
list(8, "?platformCodes=hw", 0)
|
||||
data := list(8, "?status=1&ruleName=%E7%9B%AE%E6%A0%87", 1)
|
||||
if len(data["statusCount"].(map[string]any)) != 3 {
|
||||
t.Fatal("汇总错误继承列表筛选")
|
||||
if len(data["statusCount"].(map[string]any)) != 1 {
|
||||
t.Fatal("汇总应继承名称筛选")
|
||||
}
|
||||
const id = "9007199254740993"
|
||||
detailPath := "/sw/alertRecord/detail/" + id
|
||||
@@ -139,15 +145,61 @@ func TestAlertRecordHTTPCompleteWorkflowAndVisibility(t *testing.T) {
|
||||
t.Fatalf("完整处理审核日志错误: %+v", data)
|
||||
}
|
||||
call(9, "POST", "/sw/alertRecord/addNote", `{"alertId":"`+id+`","noteContent":" ","attachmentIds":[1]}`, 7)
|
||||
call(9, "POST", "/sw/alertRecord/addNote", `{"alertId":"`+id+`","noteContent":"关闭后补充","attachmentIds":[2,"1"]}`, 0)
|
||||
data = call(9, "GET", detailPath, "", 0)["data"].(map[string]any)
|
||||
call(9, "POST", "/sw/alertRecord/addNote", `{"alertId":"`+id+`","noteContent":"关闭后补充","attachmentIds":[2,"1"]}`, 7)
|
||||
call(9, "POST", "/sw/alertRecord/addNote", `{"alertId":"6","noteContent":"待审核补充","attachmentIds":[2,"1"]}`, 0)
|
||||
data = call(9, "GET", "/sw/alertRecord/detail/6", "", 0)["data"].(map[string]any)
|
||||
logs := data["operationLogs"].([]any)
|
||||
latest := logs[0].(map[string]any)
|
||||
if len(logs) != 9 || latest["operatorName"] != "leaf" || latest["logType"] != float64(2) || latest["attachments"].([]any)[0] != "/files/two.png" || data["alertRecord"].(map[string]any)["status"] != float64(5) {
|
||||
t.Fatalf("备注附件或状态错误: %+v", data)
|
||||
if len(logs) != 1 || latest["attachments"].([]any)[0] != "/files/two.png" || data["alertRecord"].(map[string]any)["status"] != float64(3) {
|
||||
t.Fatalf("独立备注或附件错误: %+v", data)
|
||||
}
|
||||
call(9, "POST", "/sw/alertRecord/ignore", `{"ids":["6"]}`, 7)
|
||||
call(7, "POST", "/sw/alertRecord/ignore", `{"ids":["6","4"]}`, 7)
|
||||
data = call(7, "GET", "/sw/alertRecord/detail/6", "", 0)["data"].(map[string]any)
|
||||
if data["alertRecord"].(map[string]any)["status"] != float64(3) {
|
||||
t.Fatal("混入越权 ID 时不能部分忽略")
|
||||
}
|
||||
call(7, "POST", "/sw/alertRecord/ignore", `{"ids":["6","6"]}`, 0)
|
||||
call(7, "POST", "/sw/alertRecord/ignore", `{"ids":["6"]}`, 0)
|
||||
data = call(7, "GET", "/sw/alertRecord/detail/6", "", 0)["data"].(map[string]any)
|
||||
if data["alertRecord"].(map[string]any)["status"] != float64(6) || len(data["operationLogs"].([]any)) != 2 {
|
||||
t.Fatal("忽略必须幂等留痕")
|
||||
}
|
||||
|
||||
call(99, "POST", "/sw/alertRecord/addNote", `{"alertId":"`+id+`","noteContent":"越权"}`, 7)
|
||||
call(1, "POST", "/sw/alertRecord/startProcess", `{"alertId":0}`, 7)
|
||||
call(9, "PUT", "/sw/alertRule/gap", `{"enabled":true,"days":7}`, 7)
|
||||
call(7, "PUT", "/sw/alertRule/gap", `{"enabled":true,"days":0}`, 7)
|
||||
call(7, "PUT", "/sw/alertRule/gap", `{"enabled":true,"days":1000}`, 0)
|
||||
gap := call(9, "GET", "/sw/alertRule/gap", "", 0)["data"].(map[string]any)
|
||||
if gap["days"] != float64(1000) || gap["enabled"] != true {
|
||||
t.Fatal("下级应看到其一级商务设置的统一间隔")
|
||||
}
|
||||
call(9, "GET", "/sw/alertExemption/candidates?keyword=test", "", 7)
|
||||
choices := call(7, "GET", "/sw/alertExemption/candidates", "", 0)["data"].([]any)
|
||||
if len(choices) != 2 {
|
||||
t.Fatalf("候选渠道越权或丢失: %+v", choices)
|
||||
}
|
||||
for _, choice := range choices {
|
||||
if _, ok := choice.(map[string]any)["id"].(string); !ok {
|
||||
t.Fatal("渠道 ID 应保持字符串精度")
|
||||
}
|
||||
}
|
||||
call(9, "POST", "/sw/alertExemption/add", `{"sourceDb":"lx","promoteId":"100","reason":"测试"}`, 7)
|
||||
call(7, "POST", "/sw/alertExemption/add", `{"sourceDb":"yq","promoteId":"100","reason":"越权"}`, 7)
|
||||
call(7, "POST", "/sw/alertExemption/add", `{"sourceDb":"lx","promoteId":"100","reason":"测试免预警"}`, 0)
|
||||
call(7, "POST", "/sw/alertExemption/add", `{"sourceDb":"lx","promoteId":"100","reason":"重复"}`, 7)
|
||||
call(9, "GET", "/sw/alertExemption/list", "", 7)
|
||||
ex := call(7, "GET", "/sw/alertExemption/list?active=true", "", 0)["data"].(map[string]any)
|
||||
entry := ex["rows"].([]any)[0].(map[string]any)
|
||||
eid := entry["id"].(string)
|
||||
call(99, "PUT", "/sw/alertExemption/remove/"+eid, "", 7)
|
||||
call(7, "PUT", "/sw/alertExemption/remove/"+eid, "", 0)
|
||||
ex = call(7, "GET", "/sw/alertExemption/list?active=false", "", 0)["data"].(map[string]any)
|
||||
entry = ex["rows"].([]any)[0].(map[string]any)
|
||||
if entry["removedName"] != "owner" || entry["removeReason"] != "手动移出" || entry["reason"] != "测试免预警" {
|
||||
t.Fatal("移出后应保留完整审计记录")
|
||||
}
|
||||
if err := db.Exec("DROP TABLE tab_promote").Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -71,18 +71,26 @@ func TestAlertRuleHTTPScopesAndLifecycle(t *testing.T) {
|
||||
}
|
||||
return result
|
||||
}
|
||||
if err := db.Exec("INSERT INTO sys_alert_rule(id,rule_name,rule_type,monitor_metric,warning_color,creator_id,is_deleted) VALUES(9007199254740992,'旧规则','旧分类',3,'red',7,1)").Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
payload := `{"ruleName":"注册异常","ruleType":"自定义分类","monitorMetric":1,"statPeriodDays":7,"momDirection":2,"momThreshold":"9999","warningColor":"#f56c6c","creatorId":99,"monitorScope":99}`
|
||||
created := call(7, "POST", "/sw/alertRule/add", payload, 0)["data"].(map[string]any)
|
||||
id := uint64(created["id"].(float64))
|
||||
id, err := strconv.ParseUint(created["id"].(string), 10, 64)
|
||||
if err != nil || created["id"] != "9007199254740993" {
|
||||
t.Fatalf("大 ID 丢失精度: %+v", created)
|
||||
}
|
||||
path := fmt.Sprintf("/sw/alertRule/%d", id)
|
||||
value := call(8, "GET", path, "", 0)["data"].(map[string]any)
|
||||
if value["creatorId"] != float64(7) || value["monitorScope"] != float64(1) || value["momThreshold"] != float64(9999) {
|
||||
if value["id"] != "9007199254740993" || value["creatorId"] != float64(7) || value["monitorScope"] != float64(1) || value["momThreshold"] != float64(9999) {
|
||||
t.Fatalf("规则参数、归属或阈值不正确: %+v", value)
|
||||
}
|
||||
call(99, "GET", path, "", 7)
|
||||
call(8, "POST", "/sw/alertRule/add", payload, 7)
|
||||
call(7, "PUT", fmt.Sprintf("/sw/alertRule/toggle/%d?enabled=0", id), "", 0)
|
||||
call(7, "PUT", "/sw/alertRule/edit", fmt.Sprintf(`{"id":%d,"ruleName":"登录规则","ruleType":"新分类","monitorMetric":3,"unloginDays":7,"warningColor":"red"}`, id), 0)
|
||||
call(7, "POST", "/sw/alertRule/add", fmt.Sprintf(`{"id":%d,"ruleName":"绕过编辑权限","ruleType":"分类","monitorMetric":3,"unloginDays":7,"warningColor":"red"}`, id), 7)
|
||||
call(7, "PUT", "/sw/alertRule/edit", `{"ruleName":"绕过新建权限","ruleType":"分类","monitorMetric":3,"unloginDays":7,"warningColor":"red"}`, 7)
|
||||
call(7, "PUT", "/sw/alertRule/edit", fmt.Sprintf(`{"id":"%d","ruleName":"登录规则","ruleType":"新分类","monitorMetric":3,"unloginDays":7,"warningColor":"red"}`, id), 0)
|
||||
value = call(7, "GET", path, "", 0)["data"].(map[string]any)
|
||||
if value["enabled"] != float64(0) || value["statPeriodDays"] != float64(7) || value["momThreshold"] != float64(9999) {
|
||||
t.Fatal("编辑覆盖启停状态或未传入的历史参数")
|
||||
|
||||
@@ -38,11 +38,13 @@ func TestAlertTriggerThroughNativeTaskHTTP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
for _, sql := range []string{
|
||||
"CREATE TABLE tab_user(id INTEGER,source_db TEXT,promote_id INTEGER,puid INTEGER,register_time TEXT,idcard TEXT)",
|
||||
"CREATE TABLE tab_spend(source_db TEXT,promote_id INTEGER,pay_status INTEGER,pay_time INTEGER,pay_amount DECIMAL(20,2))",
|
||||
"CREATE TABLE sys_users(id INTEGER,username TEXT,level INTEGER,parent_id INTEGER,id_path TEXT,authority_id INTEGER,deleted_at DATETIME)",
|
||||
"CREATE TABLE tab_promote(id INTEGER,source_db TEXT,busier_id INTEGER,account TEXT,promote_level INTEGER,status INTEGER,last_login_time INTEGER,create_time INTEGER)",
|
||||
"CREATE TABLE tab_promote(id INTEGER,source_db TEXT,busier_id INTEGER,account TEXT,promote_level INTEGER,status INTEGER,last_login_time INTEGER,create_time INTEGER,parent_id INTEGER DEFAULT 0,top_promote_id INTEGER DEFAULT 0)",
|
||||
"INSERT INTO sys_users VALUES(10,'一级',1,0,'0',901,NULL),(20,'二级',2,10,'0,10',902,NULL),(30,'三级',3,20,'0,10,20',903,NULL)",
|
||||
"INSERT INTO sys_user_bind_platform(user_id,source_db,platform_busier_id,platform_busier_account,status) VALUES(30,'lx',7,'绑定','0')",
|
||||
"INSERT INTO tab_promote VALUES(10,'lx',7,'测试渠道',1,1,0,1)",
|
||||
"INSERT INTO tab_promote(id,source_db,busier_id,account,promote_level,status,last_login_time,create_time) VALUES(10,'lx',7,'测试渠道',1,1,0,1)",
|
||||
"INSERT INTO sys_alert_rule(rule_name,rule_type,monitor_metric,creator_id,unlogin_days,warning_color,enabled,is_deleted) VALUES('未登录规则','未登录',3,10,1,'red',1,0)",
|
||||
} {
|
||||
if err := db.Exec(sql).Error; err != nil {
|
||||
@@ -52,7 +54,7 @@ func TestAlertTriggerThroughNativeTaskHTTP(t *testing.T) {
|
||||
provider := businessHTTPData{db}
|
||||
registry := platformtask.NewRegistry()
|
||||
tasks := taskbiz.NewTaskUsecaseWithRegistry(taskdata.NewTaskRepo(reportTaskHTTPData{provider}), registry)
|
||||
alerts := businessbiz.NewAlertTriggerUsecase(businessdata.NewAlertTriggerRepo(provider))
|
||||
alerts := businessbiz.NewAlertTriggerUsecase(businessdata.NewAlertTriggerRepo(provider), nil)
|
||||
worker.NewTaskMethods(tasks, nil, nil, nil, nil, alerts, nil).RegisterTasks(registry)
|
||||
scheduler := worker.NewTaskScheduler(tasks, nil, worker.NewTaskExecutorWithRegistry(tasks, registry), nil)
|
||||
defer func() {
|
||||
|
||||
Generated
+7
-2
@@ -82,9 +82,13 @@ func wireApp(configServer *config.Server, store *config.Store, logger *slog.Logg
|
||||
auditRecorderUsecase := system2.NewAuditRecorderUsecase(auditRecordRepo)
|
||||
auditRecorder := system3.NewAuditRecorder(auditRecorderUsecase)
|
||||
registry := taskRegistry()
|
||||
teamRepo := business.NewTeamRepo(dataData)
|
||||
alertGovernanceRepo := business.NewAlertGovernanceRepo(dataData)
|
||||
accountRepo := business.NewAccountRepo(dataData)
|
||||
accountUsecase := business2.NewAccountUsecase(accountRepo)
|
||||
alertGovernanceUsecase := business2.NewAlertGovernanceUsecase(alertGovernanceRepo, accountUsecase)
|
||||
alertGovernanceService := business3.NewAlertGovernanceService(alertGovernanceUsecase)
|
||||
alertGovernance := handler.NewAlertGovernance(alertGovernanceService)
|
||||
teamRepo := business.NewTeamRepo(dataData)
|
||||
teamUsecase := business2.NewTeamUsecase(teamRepo, accountUsecase)
|
||||
performanceRepo := business.NewPerformanceRepo(dataData)
|
||||
effectivenessRepo := business.NewEffectivenessRepo(dataData)
|
||||
@@ -263,6 +267,7 @@ func wireApp(configServer *config.Server, store *config.Store, logger *slog.Logg
|
||||
integrationConfigService := integration2.NewIntegrationConfigService(integrationConfigUsecase)
|
||||
integrationConfig := handler.NewIntegrationConfig(integrationConfigService)
|
||||
set := &handler.Set{
|
||||
AlertGovernance: alertGovernance,
|
||||
BusinessPerformance: businessPerformance,
|
||||
OtherSettings: otherSettings,
|
||||
Ranking: ranking,
|
||||
@@ -309,7 +314,7 @@ func wireApp(configServer *config.Server, store *config.Store, logger *slog.Logg
|
||||
reportBuildRepo := business.NewReportBuildRepo(dataData)
|
||||
reportBuildUsecase := business2.NewReportBuildUsecase(reportBuildRepo)
|
||||
alertTriggerRepo := business.NewAlertTriggerRepo(dataData)
|
||||
alertTriggerUsecase := business2.NewAlertTriggerUsecase(alertTriggerRepo)
|
||||
alertTriggerUsecase := business2.NewAlertTriggerUsecase(alertTriggerRepo, alertGovernanceUsecase)
|
||||
taskMethods := worker.NewTaskMethods(taskUsecase, maintenanceUsecase, mediaUsecase, store, reportBuildUsecase, alertTriggerUsecase, effectivenessUsecase)
|
||||
routeRegistrar := buildRuntime(registry, routes, taskMethods)
|
||||
websocketServer, cleanup2, err := websocket.New(runtimeconfigStore)
|
||||
|
||||
Reference in New Issue
Block a user