实现根据区域ID或设备ID清空对应阈值告警任务

This commit is contained in:
2025-11-10 20:37:07 +08:00
parent 2cc4135b28
commit 9dc47ec7ad
4 changed files with 176 additions and 11 deletions

View File

@@ -141,4 +141,5 @@
9. 系统初始化时健康计划调整(包括增加延时任务) 9. 系统初始化时健康计划调整(包括增加延时任务)
10. 实现区域阈值告警任务 10. 实现区域阈值告警任务
11. 实现区域阈值告警和设备阈值告警的增删改查 11. 实现区域阈值告警和设备阈值告警的增删改查
12. 实现任务11应的八个web接口 12. 实现任务11应的八个web接口
13. 实现根据区域ID或设备ID清空对应阈值告警任务

View File

@@ -49,11 +49,12 @@ type DeviceService interface {
// deviceService 是 DeviceService 接口的具体实现。 // deviceService 是 DeviceService 接口的具体实现。
type deviceService struct { type deviceService struct {
ctx context.Context ctx context.Context
deviceRepo repository.DeviceRepository deviceRepo repository.DeviceRepository
areaControllerRepo repository.AreaControllerRepository areaControllerRepo repository.AreaControllerRepository
deviceTemplateRepo repository.DeviceTemplateRepository deviceTemplateRepo repository.DeviceTemplateRepository
deviceDomainSvc device.Service deviceDomainSvc device.Service
thresholdAlarmService ThresholdAlarmService
} }
// NewDeviceService 创建一个新的 DeviceService 实例。 // NewDeviceService 创建一个新的 DeviceService 实例。
@@ -63,13 +64,15 @@ func NewDeviceService(
areaControllerRepo repository.AreaControllerRepository, areaControllerRepo repository.AreaControllerRepository,
deviceTemplateRepo repository.DeviceTemplateRepository, deviceTemplateRepo repository.DeviceTemplateRepository,
deviceDomainSvc device.Service, deviceDomainSvc device.Service,
thresholdAlarmService ThresholdAlarmService,
) DeviceService { ) DeviceService {
return &deviceService{ return &deviceService{
ctx: ctx, ctx: ctx,
deviceRepo: deviceRepo, deviceRepo: deviceRepo,
areaControllerRepo: areaControllerRepo, areaControllerRepo: areaControllerRepo,
deviceTemplateRepo: deviceTemplateRepo, deviceTemplateRepo: deviceTemplateRepo,
deviceDomainSvc: deviceDomainSvc, deviceDomainSvc: deviceDomainSvc,
thresholdAlarmService: thresholdAlarmService,
} }
} }
@@ -167,6 +170,12 @@ func (s *deviceService) DeleteDevice(ctx context.Context, id uint) error {
return err // 如果未找到,会返回 gorm.ErrRecordNotFound return err // 如果未找到,会返回 gorm.ErrRecordNotFound
} }
// TODO 这个应该用事务处理
err = s.thresholdAlarmService.DeleteDeviceThresholdAlarmByDeviceID(serviceCtx, id)
if err != nil {
return fmt.Errorf("删除设备阈值告警失败: %w", err)
}
// 在删除前检查设备是否被任务使用 // 在删除前检查设备是否被任务使用
inUse, err := s.deviceRepo.IsDeviceInUse(serviceCtx, id) inUse, err := s.deviceRepo.IsDeviceInUse(serviceCtx, id)
if err != nil { if err != nil {
@@ -287,6 +296,12 @@ func (s *deviceService) DeleteAreaController(ctx context.Context, id uint) error
return err // 如果未找到gorm会返回 ErrRecordNotFound return err // 如果未找到gorm会返回 ErrRecordNotFound
} }
// TODO 这个应该用事务处理
err = s.thresholdAlarmService.DeleteAreaThresholdAlarmByAreaControllerID(serviceCtx, id)
if err != nil {
return fmt.Errorf("删除区域阈值告警失败: %w", err)
}
// 2. 检查是否被使用(业务逻辑) // 2. 检查是否被使用(业务逻辑)
inUse, err := s.deviceRepo.IsAreaControllerInUse(serviceCtx, id) inUse, err := s.deviceRepo.IsAreaControllerInUse(serviceCtx, id)
if err != nil { if err != nil {

View File

@@ -34,6 +34,8 @@ type ThresholdAlarmService interface {
GetDeviceThresholdAlarm(ctx context.Context, taskID int) (*dto.DeviceThresholdAlarmDTO, error) GetDeviceThresholdAlarm(ctx context.Context, taskID int) (*dto.DeviceThresholdAlarmDTO, error)
// DeleteDeviceThresholdAlarm 删除一个设备阈值告警。 // DeleteDeviceThresholdAlarm 删除一个设备阈值告警。
DeleteDeviceThresholdAlarm(ctx context.Context, taskID int, req *dto.DeleteDeviceThresholdAlarmDTO) error DeleteDeviceThresholdAlarm(ctx context.Context, taskID int, req *dto.DeleteDeviceThresholdAlarmDTO) error
// DeleteDeviceThresholdAlarmByDeviceID 实现了根据设备ID删除一个设备下所有设备阈值告警的逻辑。
DeleteDeviceThresholdAlarmByDeviceID(ctx context.Context, deviceID uint) error
// CreateAreaThresholdAlarm 创建一个区域阈值告警。 // CreateAreaThresholdAlarm 创建一个区域阈值告警。
CreateAreaThresholdAlarm(ctx context.Context, req *dto.CreateAreaThresholdAlarmDTO) error CreateAreaThresholdAlarm(ctx context.Context, req *dto.CreateAreaThresholdAlarmDTO) error
@@ -43,6 +45,8 @@ type ThresholdAlarmService interface {
GetAreaThresholdAlarm(ctx context.Context, taskID int) (*dto.AreaThresholdAlarmDTO, error) GetAreaThresholdAlarm(ctx context.Context, taskID int) (*dto.AreaThresholdAlarmDTO, error)
// DeleteAreaThresholdAlarm 实现了删除一个区域阈值告警的逻辑。 // DeleteAreaThresholdAlarm 实现了删除一个区域阈值告警的逻辑。
DeleteAreaThresholdAlarm(ctx context.Context, taskID int) error DeleteAreaThresholdAlarm(ctx context.Context, taskID int) error
// DeleteAreaThresholdAlarmByAreaControllerID 实现了根据区域ID删除一个区域下所有区域阈值告警的逻辑。
DeleteAreaThresholdAlarmByAreaControllerID(ctx context.Context, areaControllerID uint) error
} }
// thresholdAlarmService 是 ThresholdAlarmService 接口的具体实现。 // thresholdAlarmService 是 ThresholdAlarmService 接口的具体实现。
@@ -370,6 +374,76 @@ func (s *thresholdAlarmService) DeleteDeviceThresholdAlarm(ctx context.Context,
return err return err
} }
// DeleteDeviceThresholdAlarmByDeviceID 实现了根据设备ID删除一个设备下所有设备阈值告警的逻辑。
func (s *thresholdAlarmService) DeleteDeviceThresholdAlarmByDeviceID(ctx context.Context, deviceID uint) error {
serviceCtx, logger := logs.Trace(ctx, s.ctx, "DeleteDeviceThresholdAlarmByDeviceID")
tasks, err := s.planRepo.ListTasksByDeviceID(serviceCtx, deviceID)
if err != nil {
return fmt.Errorf("获取任务列表失败: %w", err)
}
device, err := s.deviceRepo.FindByID(serviceCtx, deviceID)
if err != nil {
return fmt.Errorf("获取设备 %d 失败: %w", deviceID, err)
}
plan, err := s.planRepo.GetSystemPlanByName(serviceCtx, models.PlanNamePeriodicSystemHealthCheck)
if err != nil {
return fmt.Errorf("获取系统计划失败: %w", err)
}
if plan == nil {
logger.Panicf("系统计划 %v 不存在", models.PlanNamePeriodicSystemHealthCheck)
}
deleteNums := []int{}
for i, t := range plan.Tasks {
for _, dt := range tasks {
if t.ID == dt.ID && t.Type == models.TaskTypeDeviceThresholdCheck {
deleteNums = append(deleteNums, i)
break
}
}
if t.Type == models.TaskTypeAreaCollectorThresholdCheck {
var areaParams task.AreaThresholdCheckParams
if err := t.ParseParameters(&areaParams); err != nil {
return fmt.Errorf("任务 %d: 解析参数失败: %w", t.ID, err)
}
if areaParams.AreaControllerID == device.AreaControllerID {
for ai, ae := range areaParams.ExcludeDeviceIDs {
if ae == deviceID {
areaParams.ExcludeDeviceIDs = append(areaParams.ExcludeDeviceIDs[:ai], areaParams.ExcludeDeviceIDs[ai+1:]...)
break
}
}
err = plan.Tasks[i].SaveParameters(areaParams)
if err != nil {
return fmt.Errorf("任务 %d: 保存参数失败: %w", t.ID, err)
}
}
}
}
// 为了高效地判断一个索引是否需要被删除,先将 deleteNums 转换为一个 map。
deleteMap := make(map[int]struct{}, len(deleteNums))
for _, index := range deleteNums {
deleteMap[index] = struct{}{}
}
// 创建一个新的任务切片,只包含不需要删除的任务。
newTasks := make([]models.Task, 0, len(plan.Tasks)-len(deleteNums))
for i, t := range plan.Tasks {
if _, found := deleteMap[i]; !found {
newTasks = append(newTasks, t)
}
}
plan.Tasks = newTasks
// 重新排序任务并更新计划
plan.ReorderSteps()
_, err = s.planService.UpdatePlan(serviceCtx, plan, models.PlanTypeSystem)
return err
}
// CreateAreaThresholdAlarm 实现了创建一个区域阈值告警的逻辑。 // CreateAreaThresholdAlarm 实现了创建一个区域阈值告警的逻辑。
func (s *thresholdAlarmService) CreateAreaThresholdAlarm(ctx context.Context, req *dto.CreateAreaThresholdAlarmDTO) error { func (s *thresholdAlarmService) CreateAreaThresholdAlarm(ctx context.Context, req *dto.CreateAreaThresholdAlarmDTO) error {
serviceCtx, logger := logs.Trace(ctx, s.ctx, "CreateAreaThresholdAlarm") serviceCtx, logger := logs.Trace(ctx, s.ctx, "CreateAreaThresholdAlarm")
@@ -555,3 +629,60 @@ func (s *thresholdAlarmService) DeleteAreaThresholdAlarm(ctx context.Context, ta
_, err = s.planService.UpdatePlan(serviceCtx, plan, models.PlanTypeSystem) _, err = s.planService.UpdatePlan(serviceCtx, plan, models.PlanTypeSystem)
return err return err
} }
// DeleteAreaThresholdAlarmByAreaControllerID 实现了根据区域ID删除一个区域下所有区域阈值告警的逻辑。
func (s *thresholdAlarmService) DeleteAreaThresholdAlarmByAreaControllerID(ctx context.Context, areaControllerID uint) error {
serviceCtx, logger := logs.Trace(ctx, s.ctx, "DeleteAreaThresholdAlarmByAreaControllerID")
// 1. 获取系统健康检查计划
plan, err := s.planRepo.GetSystemPlanByName(serviceCtx, models.PlanNamePeriodicSystemHealthCheck)
if err != nil {
return fmt.Errorf("获取系统健康检查计划失败: %w", err)
}
if plan == nil {
logger.Panicf("系统计划 %v 不存在", models.PlanNamePeriodicSystemHealthCheck)
}
// 2. 收集所有与指定 areaControllerID 相关的区域阈值告警任务的索引
var deleteIndices []int
for i, t := range plan.Tasks {
// 只关心区域阈值检查任务
if t.Type != models.TaskTypeAreaCollectorThresholdCheck {
continue
}
var params task.AreaThresholdCheckParams
if err := t.ParseParameters(&params); err != nil {
return fmt.Errorf("任务 %d: 解析参数失败: %w", t.ID, err)
}
// 如果 AreaControllerID 匹配,则记录其索引以待删除
if params.AreaControllerID == areaControllerID {
deleteIndices = append(deleteIndices, i)
}
}
// 如果没有找到要删除的任务,则直接返回
if len(deleteIndices) == 0 {
return nil
}
// 3. 使用 map 和新切片的方式安全地删除多个任务
deleteMap := make(map[int]struct{}, len(deleteIndices))
for _, index := range deleteIndices {
deleteMap[index] = struct{}{}
}
newTasks := make([]models.Task, 0, len(plan.Tasks)-len(deleteMap))
for i, t := range plan.Tasks {
if _, found := deleteMap[i]; !found {
newTasks = append(newTasks, t)
}
}
plan.Tasks = newTasks
// 4. 重新排序任务并更新计划
plan.ReorderSteps()
_, err = s.planService.UpdatePlan(serviceCtx, plan, models.PlanTypeSystem)
return err
}

View File

@@ -87,6 +87,8 @@ type PlanRepository interface {
StopPlanTransactionally(ctx context.Context, planID uint) error StopPlanTransactionally(ctx context.Context, planID uint) error
// UpdatePlanStateAfterExecution 更新计划执行后的状态(计数和状态) // UpdatePlanStateAfterExecution 更新计划执行后的状态(计数和状态)
UpdatePlanStateAfterExecution(ctx context.Context, planID uint, newCount uint, newStatus models.PlanStatus) error UpdatePlanStateAfterExecution(ctx context.Context, planID uint, newCount uint, newStatus models.PlanStatus) error
// ListTasksByDeviceID 根据设备ID获取关联任务列表
ListTasksByDeviceID(ctx context.Context, deviceID uint) ([]*models.Task, error)
} }
// gormPlanRepository 是 PlanRepository 的 GORM 实现 // gormPlanRepository 是 PlanRepository 的 GORM 实现
@@ -878,3 +880,19 @@ func (r *gormPlanRepository) FindTaskByID(ctx context.Context, id int) (*models.
} }
return &task, nil return &task, nil
} }
func (r *gormPlanRepository) ListTasksByDeviceID(ctx context.Context, deviceID uint) ([]*models.Task, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListTasksByDeviceID")
tasks := []*models.Task{}
// 使用 Joins 方法来连接 tasks 表和 device_tasks 关联表,
// 然后通过 Where 子句筛选出与指定 deviceID 关联的所有任务。
err := r.db.WithContext(repoCtx).Joins("JOIN device_tasks ON device_tasks.task_id = tasks.id").
Where("device_tasks.device_id = ?", deviceID).
Find(&tasks).Error
if err != nil {
return nil, err
}
return tasks, nil
}