增加任务增删改查时对设备任务关联表的维护
This commit is contained in:
@@ -22,6 +22,7 @@ func GetAllModels() []interface{} {
|
||||
&DeviceTemplate{},
|
||||
&SensorData{},
|
||||
&DeviceCommandLog{},
|
||||
&DeviceTask{},
|
||||
|
||||
// Plan & Task Models
|
||||
&Plan{},
|
||||
|
||||
@@ -17,7 +17,7 @@ type DeviceRepository interface {
|
||||
// FindByID 根据主键 ID 查找设备
|
||||
FindByID(id uint) (*models.Device, error)
|
||||
|
||||
// FindByIDString 根据字符串形式的主键 ID 查找设备,方便控制器调用
|
||||
// FindByIDString 根据字符串形式的主键 ID 查找设备
|
||||
FindByIDString(id string) (*models.Device, error)
|
||||
|
||||
// ListAll 获取所有设备的列表
|
||||
@@ -26,7 +26,7 @@ type DeviceRepository interface {
|
||||
// ListAllSensors 获取所有传感器类型的设备列表
|
||||
ListAllSensors() ([]*models.Device, error)
|
||||
|
||||
// ListByAreaControllerID 根据区域主控 ID 列出所有子设备。
|
||||
// ListByAreaControllerID 根据区域主控 ID 列出所有子设备
|
||||
ListByAreaControllerID(areaControllerID uint) ([]*models.Device, error)
|
||||
|
||||
// FindByDeviceTemplateID 根据设备模板ID查找所有使用该模板的设备
|
||||
@@ -40,6 +40,9 @@ type DeviceRepository interface {
|
||||
|
||||
// FindByAreaControllerAndPhysicalAddress 根据区域主控ID和物理地址(总线号、总线地址)查找设备
|
||||
FindByAreaControllerAndPhysicalAddress(areaControllerID uint, busNumber int, busAddress int) (*models.Device, error)
|
||||
|
||||
// GetDevicesByIDsTx 在指定事务中根据ID列表获取设备
|
||||
GetDevicesByIDsTx(tx *gorm.DB, ids []uint) ([]models.Device, error)
|
||||
}
|
||||
|
||||
// gormDeviceRepository 是 DeviceRepository 的 GORM 实现
|
||||
@@ -66,6 +69,18 @@ func (r *gormDeviceRepository) FindByID(id uint) (*models.Device, error) {
|
||||
return &device, nil
|
||||
}
|
||||
|
||||
// GetDevicesByIDsTx 在指定事务中根据ID列表获取设备
|
||||
func (r *gormDeviceRepository) GetDevicesByIDsTx(tx *gorm.DB, ids []uint) ([]models.Device, error) {
|
||||
var devices []models.Device
|
||||
if len(ids) == 0 {
|
||||
return devices, nil
|
||||
}
|
||||
if err := tx.Where("id IN ?", ids).Find(&devices).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
// FindByIDString 根据字符串形式的主键 ID 查找设备
|
||||
func (r *gormDeviceRepository) FindByIDString(id string) (*models.Device, error) {
|
||||
// 将字符串ID转换为uint64
|
||||
|
||||
@@ -48,6 +48,8 @@ type PlanRepository interface {
|
||||
GetPlansByIDs(ids []uint) ([]models.Plan, error)
|
||||
// CreatePlan 创建一个新的计划
|
||||
CreatePlan(plan *models.Plan) error
|
||||
// CreatePlanTx 在指定事务中创建一个新的计划
|
||||
CreatePlanTx(tx *gorm.DB, plan *models.Plan) error
|
||||
// UpdatePlanMetadataAndStructure 更新计划的元数据和结构,但不包括状态等运行时信息
|
||||
UpdatePlanMetadataAndStructure(plan *models.Plan) error
|
||||
// UpdatePlan 更新计划的所有字段
|
||||
@@ -200,62 +202,66 @@ func (r *gormPlanRepository) GetPlanByID(id uint) (*models.Plan, error) {
|
||||
|
||||
// CreatePlan 创建一个新的计划
|
||||
func (r *gormPlanRepository) CreatePlan(plan *models.Plan) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 1. 前置校验
|
||||
if plan.ID != 0 {
|
||||
return ErrCreateWithNonZeroID
|
||||
}
|
||||
return r.CreatePlanTx(r.db, plan)
|
||||
}
|
||||
|
||||
// 检查是否同时包含任务和子计划
|
||||
if len(plan.Tasks) > 0 && len(plan.SubPlans) > 0 {
|
||||
return ErrMixedContent
|
||||
}
|
||||
// CreatePlanTx 在指定事务中创建一个新的计划
|
||||
func (r *gormPlanRepository) CreatePlanTx(tx *gorm.DB, plan *models.Plan) error {
|
||||
// 1. 前置校验
|
||||
if plan.ID != 0 {
|
||||
return ErrCreateWithNonZeroID
|
||||
}
|
||||
|
||||
// 检查是否有重复的执行顺序
|
||||
if err := plan.ValidateExecutionOrder(); err != nil {
|
||||
return fmt.Errorf("计划 (ID: %d) 的执行顺序无效: %w", plan.ID, err)
|
||||
}
|
||||
// 检查是否同时包含任务和子计划
|
||||
if len(plan.Tasks) > 0 && len(plan.SubPlans) > 0 {
|
||||
return ErrMixedContent
|
||||
}
|
||||
|
||||
// 如果是子计划类型,验证所有子计划是否存在且ID不为0
|
||||
if plan.ContentType == models.PlanContentTypeSubPlans {
|
||||
childIDsToValidate := make(map[uint]bool)
|
||||
for _, subPlanLink := range plan.SubPlans {
|
||||
if subPlanLink.ChildPlanID == 0 {
|
||||
return ErrSubPlanIDIsZeroOnCreate
|
||||
}
|
||||
childIDsToValidate[subPlanLink.ChildPlanID] = true
|
||||
// 检查是否有重复的执行顺序
|
||||
if err := plan.ValidateExecutionOrder(); err != nil {
|
||||
return fmt.Errorf("计划 (ID: %d) 的执行顺序无效: %w", plan.ID, err)
|
||||
}
|
||||
|
||||
// 如果是子计划类型,验证所有子计划是否存在且ID不为0
|
||||
if plan.ContentType == models.PlanContentTypeSubPlans {
|
||||
childIDsToValidate := make(map[uint]bool)
|
||||
for _, subPlanLink := range plan.SubPlans {
|
||||
if subPlanLink.ChildPlanID == 0 {
|
||||
return ErrSubPlanIDIsZeroOnCreate
|
||||
}
|
||||
childIDsToValidate[subPlanLink.ChildPlanID] = true
|
||||
}
|
||||
|
||||
var ids []uint
|
||||
for id := range childIDsToValidate {
|
||||
ids = append(ids, id)
|
||||
var ids []uint
|
||||
for id := range childIDsToValidate {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
|
||||
if len(ids) > 0 {
|
||||
var count int64
|
||||
if err := tx.Model(&models.Plan{}).Where("id IN ?", ids).Count(&count).Error; err != nil {
|
||||
return fmt.Errorf("验证子计划存在性失败: %w", err)
|
||||
}
|
||||
|
||||
if len(ids) > 0 {
|
||||
var count int64
|
||||
if err := tx.Model(&models.Plan{}).Where("id IN ?", ids).Count(&count).Error; err != nil {
|
||||
return fmt.Errorf("验证子计划存在性失败: %w", err)
|
||||
}
|
||||
if int(count) != len(ids) {
|
||||
return ErrNodeDoesNotExist
|
||||
}
|
||||
if int(count) != len(ids) {
|
||||
return ErrNodeDoesNotExist
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 创建根计划
|
||||
// GORM 会自动处理关联的 Tasks (如果 ContentType 是 tasks 且 Task.ID 为 0)
|
||||
if err := tx.Create(plan).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 2. 创建根计划
|
||||
// GORM 会自动处理关联的 Tasks (如果 ContentType 是 tasks 且 Task.ID 为 0),
|
||||
// 以及 Tasks 内部已经填充好的 Devices 关联。
|
||||
if err := tx.Create(plan).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. 创建触发器Task
|
||||
// 关键修改:调用 createPlanAnalysisTask 并处理其返回的 Task 对象
|
||||
_, err := r.createPlanAnalysisTask(tx, plan)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
// 3. 创建触发器Task
|
||||
// 关键修改:调用 createPlanAnalysisTask 并处理其返回的 Task 对象
|
||||
_, err := r.createPlanAnalysisTask(tx, plan)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdatePlan 更新计划
|
||||
@@ -414,9 +420,7 @@ func (r *gormPlanRepository) reconcileTasks(tx *gorm.DB, plan *models.Plan) erro
|
||||
}
|
||||
|
||||
if len(tasksToDelete) > 0 {
|
||||
if err := tx.Delete(&models.Task{}, tasksToDelete).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return r.deleteTasksTx(tx, tasksToDelete)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -555,42 +559,43 @@ func (r *gormPlanRepository) flattenPlanTasksRecursive(plan *models.Plan) ([]mod
|
||||
func (r *gormPlanRepository) DeleteTask(id int) error {
|
||||
// 使用事务确保操作的原子性
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
return r.deleteTask(tx, id)
|
||||
return r.deleteTasksTx(tx, []int{id})
|
||||
})
|
||||
}
|
||||
|
||||
// deleteTask 根据ID删除任务
|
||||
func (r *gormPlanRepository) deleteTask(tx *gorm.DB, id int) error {
|
||||
// 1. 检查是否有待执行任务引用了这个任务
|
||||
// deleteTasksTx 在事务中批量软删除任务,并物理删除其在关联表中的记录
|
||||
func (r *gormPlanRepository) deleteTasksTx(tx *gorm.DB, ids []int) error {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查是否有待执行任务引用了这些任务
|
||||
var pendingTaskCount int64
|
||||
if err := tx.Model(&models.PendingTask{}).Where("task_id = ?", id).Count(&pendingTaskCount).Error; err != nil {
|
||||
if err := tx.Model(&models.PendingTask{}).Where("task_id IN ?", ids).Count(&pendingTaskCount).Error; err != nil {
|
||||
return fmt.Errorf("检查待执行任务时出错: %w", err)
|
||||
}
|
||||
|
||||
// 如果有待执行任务引用该任务,不能删除
|
||||
if pendingTaskCount > 0 {
|
||||
return fmt.Errorf("无法删除任务(ID: %d),因为存在 %d 条待执行任务引用该任务", id, pendingTaskCount)
|
||||
return fmt.Errorf("无法删除任务,因为存在 %d 条待执行任务引用这些任务", pendingTaskCount)
|
||||
}
|
||||
|
||||
// 2. 检查是否有计划仍在使用这个任务
|
||||
var planCount int64
|
||||
if err := tx.Model(&models.Plan{}).Joins("JOIN tasks ON plans.id = tasks.plan_id").Where("tasks.id = ?", id).Count(&planCount).Error; err != nil {
|
||||
return fmt.Errorf("检查计划引用任务时出错: %w", err)
|
||||
// 因为钩子函数在批量删除中不会被触发, 所以手动删除关联表, 通过批量删除语句优化性能
|
||||
|
||||
// 1. 直接、高效地从关联表中物理删除所有相关记录
|
||||
// 这是最关键的优化,避免了不必要的查询和循环
|
||||
if err := tx.Where("task_id IN ?", ids).Delete(&models.DeviceTask{}).Error; err != nil {
|
||||
return fmt.Errorf("清理任务的设备关联失败: %w", err)
|
||||
}
|
||||
|
||||
// 如果有计划在使用该任务,不能删除
|
||||
if planCount > 0 {
|
||||
return fmt.Errorf("无法删除任务(ID: %d),因为存在 %d 个计划仍在使用该任务", id, planCount)
|
||||
}
|
||||
|
||||
// 3. 执行删除操作
|
||||
result := tx.Delete(&models.Task{}, id)
|
||||
// 2. 对任务本身进行软删除
|
||||
result := tx.Delete(&models.Task{}, ids)
|
||||
if result.Error != nil {
|
||||
return fmt.Errorf("删除任务失败: %w", result.Error)
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// 检查是否实际删除了记录
|
||||
if result.RowsAffected == 0 {
|
||||
// 3. 如果是单个删除且未找到记录,则返回错误
|
||||
if len(ids) == 1 && result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user