设备和区域主控删除时清除对应区域阈值告警或设备阈值告警任务
This commit is contained in:
@@ -142,4 +142,5 @@
|
|||||||
10. 实现区域阈值告警任务
|
10. 实现区域阈值告警任务
|
||||||
11. 实现区域阈值告警和设备阈值告警的增删改查
|
11. 实现区域阈值告警和设备阈值告警的增删改查
|
||||||
12. 实现任务11应的八个web接口
|
12. 实现任务11应的八个web接口
|
||||||
13. 实现根据区域ID或设备ID清空对应阈值告警任务
|
13. 实现根据区域ID或设备ID清空对应阈值告警任务
|
||||||
|
14. 设备和区域主控删除时清除对应区域阈值告警或设备阈值告警任务
|
||||||
@@ -170,14 +170,8 @@ 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, []models.TaskType{models.TaskTypeDeviceThresholdCheck})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 如果检查过程中发生数据库错误,则返回错误
|
// 如果检查过程中发生数据库错误,则返回错误
|
||||||
return fmt.Errorf("检查设备使用情况失败: %w", err)
|
return fmt.Errorf("检查设备使用情况失败: %w", err)
|
||||||
@@ -187,6 +181,12 @@ func (s *deviceService) DeleteDevice(ctx context.Context, id uint) error {
|
|||||||
return ErrDeviceInUse
|
return ErrDeviceInUse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO 这个应该用事务处理
|
||||||
|
err = s.thresholdAlarmService.DeleteDeviceThresholdAlarmByDeviceID(serviceCtx, id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("删除设备阈值告警失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 只有在未被使用时,才执行删除操作
|
// 只有在未被使用时,才执行删除操作
|
||||||
return s.deviceRepo.Delete(serviceCtx, id)
|
return s.deviceRepo.Delete(serviceCtx, id)
|
||||||
}
|
}
|
||||||
@@ -296,14 +296,8 @@ 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.areaControllerRepo.IsAreaControllerUsedByTasks(serviceCtx, id, []models.TaskType{models.TaskTypeAreaCollectorThresholdCheck})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err // 返回数据库检查错误
|
return err // 返回数据库检查错误
|
||||||
}
|
}
|
||||||
@@ -311,6 +305,12 @@ func (s *deviceService) DeleteAreaController(ctx context.Context, id uint) error
|
|||||||
return ErrAreaControllerInUse // 返回业务错误
|
return ErrAreaControllerInUse // 返回业务错误
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO 这个应该用事务处理
|
||||||
|
err = s.thresholdAlarmService.DeleteAreaThresholdAlarmByAreaControllerID(serviceCtx, id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("删除区域阈值告警失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 3. 执行删除
|
// 3. 执行删除
|
||||||
return s.areaControllerRepo.Delete(serviceCtx, id)
|
return s.areaControllerRepo.Delete(serviceCtx, id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -254,16 +254,6 @@ func initAppServices(ctx context.Context, infra *Infrastructure, domainServices
|
|||||||
infra.repos.pigTradeRepo,
|
infra.repos.pigTradeRepo,
|
||||||
infra.repos.notificationRepo,
|
infra.repos.notificationRepo,
|
||||||
)
|
)
|
||||||
deviceService := service.NewDeviceService(
|
|
||||||
logs.AddCompName(baseCtx, "DeviceService"),
|
|
||||||
infra.repos.deviceRepo,
|
|
||||||
infra.repos.areaControllerRepo,
|
|
||||||
infra.repos.deviceTemplateRepo,
|
|
||||||
domainServices.generalDeviceService,
|
|
||||||
)
|
|
||||||
auditService := service.NewAuditService(logs.AddCompName(baseCtx, "AuditService"), infra.repos.userActionLogRepo)
|
|
||||||
planService := service.NewPlanService(logs.AddCompName(baseCtx, "AppPlanService"), domainServices.planService)
|
|
||||||
userService := service.NewUserService(logs.AddCompName(baseCtx, "UserService"), infra.repos.userRepo, infra.tokenGenerator, domainServices.notifyService)
|
|
||||||
|
|
||||||
// 初始化阈值告警服务
|
// 初始化阈值告警服务
|
||||||
thresholdAlarmService := service.NewThresholdAlarmService(
|
thresholdAlarmService := service.NewThresholdAlarmService(
|
||||||
@@ -276,6 +266,19 @@ func initAppServices(ctx context.Context, infra *Infrastructure, domainServices
|
|||||||
infra.repos.deviceRepo,
|
infra.repos.deviceRepo,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
deviceService := service.NewDeviceService(
|
||||||
|
logs.AddCompName(baseCtx, "DeviceService"),
|
||||||
|
infra.repos.deviceRepo,
|
||||||
|
infra.repos.areaControllerRepo,
|
||||||
|
infra.repos.deviceTemplateRepo,
|
||||||
|
domainServices.generalDeviceService,
|
||||||
|
thresholdAlarmService,
|
||||||
|
)
|
||||||
|
|
||||||
|
auditService := service.NewAuditService(logs.AddCompName(baseCtx, "AuditService"), infra.repos.userActionLogRepo)
|
||||||
|
planService := service.NewPlanService(logs.AddCompName(baseCtx, "AppPlanService"), domainServices.planService)
|
||||||
|
userService := service.NewUserService(logs.AddCompName(baseCtx, "UserService"), infra.repos.userRepo, infra.tokenGenerator, domainServices.notifyService)
|
||||||
|
|
||||||
return &AppServices{
|
return &AppServices{
|
||||||
pigFarmService: pigFarmService,
|
pigFarmService: pigFarmService,
|
||||||
pigBatchService: pigBatchService,
|
pigBatchService: pigBatchService,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/alarm"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/device"
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/device"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/notify"
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/notify"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/plan"
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/plan"
|
||||||
@@ -28,6 +29,7 @@ type taskFactory struct {
|
|||||||
|
|
||||||
deviceService device.Service
|
deviceService device.Service
|
||||||
notificationService notify.Service
|
notificationService notify.Service
|
||||||
|
alarmService alarm.AlarmService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTaskFactory(
|
func NewTaskFactory(
|
||||||
@@ -37,6 +39,7 @@ func NewTaskFactory(
|
|||||||
alarmRepo repository.AlarmRepository,
|
alarmRepo repository.AlarmRepository,
|
||||||
deviceService device.Service,
|
deviceService device.Service,
|
||||||
notifyService notify.Service,
|
notifyService notify.Service,
|
||||||
|
alarmService alarm.AlarmService,
|
||||||
) plan.TaskFactory {
|
) plan.TaskFactory {
|
||||||
return &taskFactory{
|
return &taskFactory{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -45,6 +48,7 @@ func NewTaskFactory(
|
|||||||
alarmRepo: alarmRepo,
|
alarmRepo: alarmRepo,
|
||||||
deviceService: deviceService,
|
deviceService: deviceService,
|
||||||
notificationService: notifyService,
|
notificationService: notifyService,
|
||||||
|
alarmService: alarmService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +64,10 @@ func (t *taskFactory) Production(ctx context.Context, claimedLog *models.TaskExe
|
|||||||
return NewFullCollectionTask(logs.AddCompName(baseCtx, CompNameFullCollectionTask), claimedLog, t.deviceRepo, t.deviceService)
|
return NewFullCollectionTask(logs.AddCompName(baseCtx, CompNameFullCollectionTask), claimedLog, t.deviceRepo, t.deviceService)
|
||||||
case models.TaskTypeAlarmNotification:
|
case models.TaskTypeAlarmNotification:
|
||||||
return NewAlarmNotificationTask(logs.AddCompName(baseCtx, CompNameAlarmNotification), claimedLog, t.notificationService, t.alarmRepo)
|
return NewAlarmNotificationTask(logs.AddCompName(baseCtx, CompNameAlarmNotification), claimedLog, t.notificationService, t.alarmRepo)
|
||||||
|
case models.TaskTypeDeviceThresholdCheck:
|
||||||
|
return NewDeviceThresholdCheckTask(logs.AddCompName(baseCtx, "DeviceThresholdCheckTask"), claimedLog, t.sensorDataRepo, t.alarmService)
|
||||||
|
case models.TaskTypeAreaCollectorThresholdCheck:
|
||||||
|
return NewAreaThresholdCheckTask(logs.AddCompName(baseCtx, "AreaCollectorThresholdCheckTask"), claimedLog, t.sensorDataRepo, t.deviceRepo, t.alarmService)
|
||||||
default:
|
default:
|
||||||
// TODO 这里直接panic合适吗? 不过这个场景确实不该出现任何异常的任务类型
|
// TODO 这里直接panic合适吗? 不过这个场景确实不该出现任何异常的任务类型
|
||||||
logger.Panicf("不支持的任务类型: %s", claimedLog.Task.Type)
|
logger.Panicf("不支持的任务类型: %s", claimedLog.Task.Type)
|
||||||
@@ -89,6 +97,10 @@ func (t *taskFactory) CreateTaskFromModel(ctx context.Context, taskModel *models
|
|||||||
return NewFullCollectionTask(logs.AddCompName(baseCtx, CompNameFullCollectionTask), tempLog, t.deviceRepo, t.deviceService), nil
|
return NewFullCollectionTask(logs.AddCompName(baseCtx, CompNameFullCollectionTask), tempLog, t.deviceRepo, t.deviceService), nil
|
||||||
case models.TaskTypeAlarmNotification:
|
case models.TaskTypeAlarmNotification:
|
||||||
return NewAlarmNotificationTask(logs.AddCompName(baseCtx, CompNameAlarmNotification), tempLog, t.notificationService, t.alarmRepo), nil
|
return NewAlarmNotificationTask(logs.AddCompName(baseCtx, CompNameAlarmNotification), tempLog, t.notificationService, t.alarmRepo), nil
|
||||||
|
case models.TaskTypeDeviceThresholdCheck:
|
||||||
|
return NewDeviceThresholdCheckTask(logs.AddCompName(baseCtx, "DeviceThresholdCheckTask"), tempLog, t.sensorDataRepo, t.alarmService), nil
|
||||||
|
case models.TaskTypeAreaCollectorThresholdCheck:
|
||||||
|
return NewAreaThresholdCheckTask(logs.AddCompName(baseCtx, "AreaCollectorThresholdCheckTask"), tempLog, t.sensorDataRepo, t.deviceRepo, t.alarmService), nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("不支持为类型 '%s' 的任务创建模型实例", taskModel.Type)
|
return nil, fmt.Errorf("不支持为类型 '%s' 的任务创建模型实例", taskModel.Type)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package repository
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
@@ -18,6 +19,8 @@ type AreaControllerRepository interface {
|
|||||||
ListAll(ctx context.Context) ([]*models.AreaController, error)
|
ListAll(ctx context.Context) ([]*models.AreaController, error)
|
||||||
Update(ctx context.Context, ac *models.AreaController) error
|
Update(ctx context.Context, ac *models.AreaController) error
|
||||||
Delete(ctx context.Context, id uint) error
|
Delete(ctx context.Context, id uint) error
|
||||||
|
// IsAreaControllerUsedByTasks 检查区域主控是否被特定任务类型使用,可以忽略指定任务类型
|
||||||
|
IsAreaControllerUsedByTasks(ctx context.Context, areaControllerID uint, ignoredTaskTypes []models.TaskType) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// gormAreaControllerRepository 是 AreaControllerRepository 的 GORM 实现。
|
// gormAreaControllerRepository 是 AreaControllerRepository 的 GORM 实现。
|
||||||
@@ -84,3 +87,66 @@ func (r *gormAreaControllerRepository) FindByNetworkID(ctx context.Context, netw
|
|||||||
}
|
}
|
||||||
return &areaController, nil
|
return &areaController, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsAreaControllerUsedByTasks 检查区域主控是否被特定任务类型使用,可以忽略指定任务类型
|
||||||
|
func (r *gormAreaControllerRepository) IsAreaControllerUsedByTasks(ctx context.Context, areaControllerID uint, ignoredTaskTypes []models.TaskType) (bool, error) {
|
||||||
|
repoCtx, logger := logs.Trace(ctx, r.ctx, "IsAreaControllerUsedByTasks")
|
||||||
|
|
||||||
|
// 将 ignoredTaskTypes 转换为 map,以便高效查找
|
||||||
|
ignoredMap := make(map[models.TaskType]struct{})
|
||||||
|
for _, tt := range ignoredTaskTypes {
|
||||||
|
ignoredMap[tt] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
areaControllerIDStr := strconv.FormatUint(uint64(areaControllerID), 10)
|
||||||
|
|
||||||
|
// 定义所有可能与 AreaControllerID 相关的任务类型列表
|
||||||
|
// 方便未来扩展,如果新增任务类型与区域主控关联,只需在此处添加
|
||||||
|
relevantTaskTypes := []models.TaskType{
|
||||||
|
models.TaskTypeAreaCollectorThresholdCheck,
|
||||||
|
// TODO: 如果未来有其他任务类型通过 parameters 关联 AreaControllerID,请在此处添加
|
||||||
|
// 例如: models.TaskTypeAnotherAreaControllerTask,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, taskType := range relevantTaskTypes {
|
||||||
|
// 如果当前任务类型在忽略列表中,则跳过检查
|
||||||
|
if _, ok := ignoredMap[taskType]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskCount int64
|
||||||
|
var query *gorm.DB
|
||||||
|
|
||||||
|
// 根据任务类型构建不同的查询条件
|
||||||
|
switch taskType {
|
||||||
|
case models.TaskTypeAreaCollectorThresholdCheck:
|
||||||
|
// TaskTypeAreaCollectorThresholdCheck 任务的 AreaControllerID 存储在 parameters->>'AreaControllerID'
|
||||||
|
query = r.db.WithContext(repoCtx).
|
||||||
|
Model(&models.Task{}).
|
||||||
|
Where("type = ?", models.TaskTypeAreaCollectorThresholdCheck).
|
||||||
|
Where("parameters->>'AreaControllerID' = ?", areaControllerIDStr)
|
||||||
|
// TODO: 如果未来有其他任务类型通过不同的 parameters 字段关联 AreaControllerID,请在此处添加 case
|
||||||
|
// case models.TaskTypeAnotherAreaControllerTask:
|
||||||
|
// query = r.db.WithContext(repoCtx).
|
||||||
|
// Model(&models.Task{}).
|
||||||
|
// Where("type = ?", models.TaskTypeAnotherAreaControllerTask).
|
||||||
|
// Where("parameters->>'AnotherFieldForAreaControllerID' = ?", areaControllerIDStr)
|
||||||
|
default:
|
||||||
|
// 对于未明确处理的 relevantTaskTypes,可以记录警告或直接跳过
|
||||||
|
logger.Warnf(fmt.Sprintf("IsAreaControllerUsedByTasks: 未处理的区域主控相关任务类型: %s", taskType))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if query != nil {
|
||||||
|
err := query.Count(&taskCount).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("查询区域主控任务使用情况失败 (任务类型: %s): %w", taskType, err)
|
||||||
|
}
|
||||||
|
if taskCount > 0 {
|
||||||
|
return true, nil // 发现有未被忽略的任务正在使用此区域主控
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil // 没有发现任何未被忽略的任务正在使用此区域主控
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ type DeviceRepository interface {
|
|||||||
// GetDevicesByIDsTx 在指定事务中根据ID列表获取设备
|
// GetDevicesByIDsTx 在指定事务中根据ID列表获取设备
|
||||||
GetDevicesByIDsTx(ctx context.Context, tx *gorm.DB, ids []uint) ([]models.Device, error)
|
GetDevicesByIDsTx(ctx context.Context, tx *gorm.DB, ids []uint) ([]models.Device, error)
|
||||||
|
|
||||||
// IsDeviceInUse 检查设备是否被任何任务使用
|
// IsDeviceInUse 检查设备是否被任何任务使用,可以忽略指定任务类型
|
||||||
IsDeviceInUse(ctx context.Context, deviceID uint) (bool, error)
|
IsDeviceInUse(ctx context.Context, deviceID uint, ignoredTaskTypes []models.TaskType) (bool, error)
|
||||||
|
|
||||||
// IsAreaControllerInUse 检查区域主控是否被任何设备使用
|
// IsAreaControllerInUse 检查区域主控是否被任何设备使用
|
||||||
IsAreaControllerInUse(ctx context.Context, areaControllerID uint) (bool, error)
|
IsAreaControllerInUse(ctx context.Context, areaControllerID uint) (bool, error)
|
||||||
@@ -184,12 +184,22 @@ func (r *gormDeviceRepository) FindByAreaControllerAndPhysicalAddress(ctx contex
|
|||||||
return &device, nil
|
return &device, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDeviceInUse 检查设备是否被任何任务使用
|
// IsDeviceInUse 检查设备是否被任何任务使用,可以忽略指定任务类型
|
||||||
func (r *gormDeviceRepository) IsDeviceInUse(ctx context.Context, deviceID uint) (bool, error) {
|
func (r *gormDeviceRepository) IsDeviceInUse(ctx context.Context, deviceID uint, ignoredTaskTypes []models.TaskType) (bool, error) {
|
||||||
repoCtx := logs.AddFuncName(ctx, r.ctx, "IsDeviceInUse")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "IsDeviceInUse")
|
||||||
var count int64
|
var count int64
|
||||||
// 直接对 device_tasks 关联表进行 COUNT 操作,性能最高
|
|
||||||
err := r.db.WithContext(repoCtx).Model(&models.DeviceTask{}).Where("device_id = ?", deviceID).Count(&count).Error
|
// 构建查询,需要 JOIN tasks 表来过滤 TaskType
|
||||||
|
query := r.db.WithContext(repoCtx).
|
||||||
|
Model(&models.DeviceTask{}).
|
||||||
|
Joins("JOIN tasks ON tasks.id = device_tasks.task_id").
|
||||||
|
Where("device_tasks.device_id = ?", deviceID)
|
||||||
|
|
||||||
|
if len(ignoredTaskTypes) > 0 {
|
||||||
|
query = query.Where("tasks.type NOT IN (?)", ignoredTaskTypes)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := query.Count(&count).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("查询设备任务关联失败: %w", err)
|
return false, fmt.Errorf("查询设备任务关联失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user