修改domain包
This commit is contained in:
@@ -1,266 +1,266 @@
|
||||
- **`internal/domain/audit/service.go` (`audit.Service`)**
|
||||
- **结构体改造**:
|
||||
- [ ] 移除 `service` 结构体中的 `logger *logs.Logger` 成员。
|
||||
- [ ] 新增 `selfCtx context.Context` 成员。
|
||||
- [x] 移除 `service` 结构体中的 `logger *logs.Logger` 成员。
|
||||
- [x] 新增 `selfCtx context.Context` 成员。
|
||||
- **构造函数改造 (`NewService`)**:
|
||||
- [ ] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。
|
||||
- [ ] 在函数内部,为 `audit.Service` 创建其专属的 `selfCtx`:`selfCtx := logs.AddCompName(ctx, "AuditService")`。
|
||||
- [ ] 将这个 `selfCtx` 赋值给 `service` 结构体的 `selfCtx` 成员。
|
||||
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。
|
||||
- [x] 在函数内部,为 `audit.Service` 创建其专属的 `selfCtx`:`selfCtx := logs.AddCompName(ctx, "AuditService")`。
|
||||
- [x] 将这个 `selfCtx` 赋值给 `service` 结构体的 `selfCtx` 成员。
|
||||
- **公共方法改造 (`LogAction`)**:
|
||||
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "LogAction")` 获取新的 `context.Context`
|
||||
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "LogAction")` 获取新的 `context.Context`
|
||||
和 `logger` 实例。
|
||||
- [ ] 将所有对 `s.logger.Warnw` 和 `s.logger.Errorw` 的调用替换为 `logger.Warnw` 和 `logger.Errorw`。
|
||||
- [ ] 确保所有对 `s.userActionLogRepository.Create` 的调用都将 `newCtx` 作为第一个参数传递。
|
||||
- [x] 将所有对 `s.logger.Warnw` 和 `s.logger.Errorw` 的调用替换为 `logger.Warnw` 和 `logger.Errorw`。
|
||||
- [x] 确保所有对 `s.userActionLogRepository.Create` 的调用都将 `newCtx` 作为第一个参数传递。
|
||||
- **`internal/domain/device/general_device_service.go` (`device.Service`)**
|
||||
- **结构体改造**:
|
||||
- [ ] 移除 `GeneralDeviceService` 结构体中的 `logger *logs.Logger` 成员。
|
||||
- [ ] 新增 `selfCtx context.Context` 成员。
|
||||
- [x] 移除 `GeneralDeviceService` 结构体中的 `logger *logs.Logger` 成员。
|
||||
- [x] 新增 `selfCtx context.Context` 成员。
|
||||
- **构造函数改造 (`NewGeneralDeviceService`)**:
|
||||
- [ ] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。
|
||||
- [ ] 在函数内部,为 `GeneralDeviceService` 创建其专属的 `selfCtx`:
|
||||
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。
|
||||
- [x] 在函数内部,为 `GeneralDeviceService` 创建其专属的 `selfCtx`:
|
||||
`selfCtx := logs.AddCompName(ctx, "GeneralDeviceService")`。
|
||||
- [ ] 将这个 `selfCtx` 赋值给 `GeneralDeviceService` 结构体的 `selfCtx` 成员。
|
||||
- [x] 将这个 `selfCtx` 赋值给 `GeneralDeviceService` 结构体的 `selfCtx` 成员。
|
||||
- **公共方法改造 (`Switch`, `Collect`)**:
|
||||
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, g.selfCtx, "MethodName")` 获取新的
|
||||
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, g.selfCtx, "MethodName")` 获取新的
|
||||
`context.Context` 和 `logger` 实例。
|
||||
- [ ] 将所有对 `g.logger.Errorf`, `g.logger.Infof`, `g.logger.Warnf`, `g.logger.Debugf`, `g.logger.DPanicf`
|
||||
- [x] 将所有对 `g.logger.Errorf`, `g.logger.Infof`, `g.logger.Warnf`, `g.logger.Debugf`, `g.logger.DPanicf`
|
||||
的调用替换为 `logger.Errorf`, `logger.Infof`, `logger.Warnf`, `logger.Debugf`, `logger.DPanicf`。
|
||||
- [ ] 确保所有对 `g.deviceRepo`, `g.deviceCommandLogRepo`, `g.pendingCollectionRepo`, `g.comm` 等依赖的调用都将
|
||||
- [x] 确保所有对 `g.deviceRepo`, `g.deviceCommandLogRepo`, `g.pendingCollectionRepo`, `g.comm` 等依赖的调用都将
|
||||
`newCtx` 作为第一个参数传递。
|
||||
- **`internal/domain/notify/notify.go` (`domain_notify.Service` - `failoverService` 实现)**
|
||||
- **结构体改造**:
|
||||
- [ ] 移除 `failoverService` 结构体中的 `log *logs.Logger` 成员。
|
||||
- [ ] 新增 `selfCtx context.Context` 成员。
|
||||
- [x] 移除 `failoverService` 结构体中的 `log *logs.Logger` 成员。
|
||||
- [x] 新增 `selfCtx context.Context` 成员。
|
||||
- **构造函数改造 (`NewFailoverService`)**:
|
||||
- [ ] 修改函数签名,移除 `log *logs.Logger` 参数,改为接收 `ctx context.Context`。
|
||||
- [ ] 在函数内部,为 `failoverService` 创建其专属的 `selfCtx`:
|
||||
- [x] 修改函数签名,移除 `log *logs.Logger` 参数,改为接收 `ctx context.Context`。
|
||||
- [x] 在函数内部,为 `failoverService` 创建其专属的 `selfCtx`:
|
||||
`selfCtx := logs.AddCompName(ctx, "FailoverService")`。
|
||||
- [ ] 将这个 `selfCtx` 赋值给 `failoverService` 结构体的 `selfCtx` 成员。
|
||||
- [x] 将这个 `selfCtx` 赋值给 `failoverService` 结构体的 `selfCtx` 成员。
|
||||
- **公共方法改造 (`SendBatchAlarm`, `BroadcastAlarm`, `SendTestMessage`)**:
|
||||
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
|
||||
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
|
||||
`context.Context` 和 `logger` 实例。
|
||||
- [ ] 将所有对 `s.log.Infow`, `s.log.Errorw`, `s.log.Warnw` 的调用替换为 `logger.Infow`, `logger.Errorw`,
|
||||
- [x] 将所有对 `s.log.Infow`, `s.log.Errorw`, `s.log.Warnw` 的调用替换为 `logger.Infow`, `logger.Errorw`,
|
||||
`logger.Warnw`。
|
||||
- [ ] 确保所有对 `s.userRepo`, `s.primaryNotifier.Send`, `s.notifiers`, `s.notificationRepo` 等依赖的调用都将
|
||||
- [x] 确保所有对 `s.userRepo`, `s.primaryNotifier.Send`, `s.notifiers`, `s.notificationRepo` 等依赖的调用都将
|
||||
`newCtx` 作为第一个参数传递。
|
||||
- **内部辅助方法改造 (`sendAlarmToUser`, `recordNotificationAttempt`)**:
|
||||
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
|
||||
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
|
||||
`context.Context` 和 `logger` 实例。
|
||||
- [ ] 将所有对 `s.log.Errorw`, `s.log.Infow`, `s.log.Warnw` 的调用替换为 `logger.Errorw`, `logger.Infow`,
|
||||
- [x] 将所有对 `s.log.Errorw`, `s.log.Infow`, `s.log.Warnw` 的调用替换为 `logger.Errorw`, `logger.Infow`,
|
||||
`logger.Warnw`。
|
||||
- [ ] 确保所有对 `s.userRepo`, `s.primaryNotifier.Send`, `s.notifiers`, `s.notificationRepo` 等依赖的调用都将
|
||||
- [x] 确保所有对 `s.userRepo`, `s.primaryNotifier.Send`, `s.notifiers`, `s.notificationRepo` 等依赖的调用都将
|
||||
`newCtx` 作为第一个参数传递。
|
||||
- **`internal/domain/pig/pen_transfer_manager.go` (`pig.PigPenTransferManager`)**
|
||||
- **结构体改造**:
|
||||
- [ ] 移除 `pigPenTransferManager` 结构体中可能存在的 `logger *logs.Logger` 成员(如果未来添加)。
|
||||
- [ ] 新增 `selfCtx context.Context` 成员。
|
||||
- [x] 移除 `pigPenTransferManager` 结构体中可能存在的 `logger *logs.Logger` 成员(如果未来添加)。
|
||||
- [x] 新增 `selfCtx context.Context` 成员。
|
||||
- **构造函数改造 (`NewPigPenTransferManager`)**:
|
||||
- [ ] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在函数内部,为 `pigPenTransferManager` 创建其专属的 `selfCtx`:
|
||||
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在函数内部,为 `pigPenTransferManager` 创建其专属的 `selfCtx`:
|
||||
`selfCtx := logs.AddCompName(ctx, "PigPenTransferManager")`。
|
||||
- [ ] 将这个 `selfCtx` 赋值给 `pigPenTransferManager` 结构体的 `selfCtx` 成员。
|
||||
- [x] 将这个 `selfCtx` 赋值给 `pigPenTransferManager` 结构体的 `selfCtx` 成员。
|
||||
- **公共方法改造 (
|
||||
所有方法,例如 `LogTransfer`, `GetPenByID`, `GetPensByBatchID`, `UpdatePenFields`, `GetCurrentPigsInPen`,
|
||||
`GetTotalPigsInPensForBatchTx`, `ReleasePen`)**:
|
||||
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(在 `tx *gorm.DB` 之前)。
|
||||
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
|
||||
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(在 `tx *gorm.DB` 之前)。
|
||||
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
|
||||
`context.Context` 和 `logger` 实例。
|
||||
- [ ] 将所有对 `s.logger.Errorf` 和 `s.logger.Infof` 的调用替换为 `logger.Errorf` 和 `logger.Infof`。
|
||||
- [ ] 确保所有对 `s.penRepo`, `s.logRepo`, `s.pigBatchRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
|
||||
- [x] 将所有对 `s.logger.Errorf` 和 `s.logger.Infof` 的调用替换为 `logger.Errorf` 和 `logger.Infof`。
|
||||
- [x] 确保所有对 `s.penRepo`, `s.logRepo`, `s.pigBatchRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
|
||||
- **`internal/domain/pig/pig_trade_manager.go` (`pig.PigTradeManager`)**
|
||||
- **结构体改造**:
|
||||
- [ ] 新增 `selfCtx context.Context` 成员。
|
||||
- [x] 新增 `selfCtx context.Context` 成员。
|
||||
- **构造函数改造 (`NewPigTradeManager`)**:
|
||||
- [ ] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在函数内部,为 `pigTradeManager` 创建其专属的 `selfCtx`:
|
||||
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在函数内部,为 `pigTradeManager` 创建其专属的 `selfCtx`:
|
||||
`selfCtx := logs.AddCompName(ctx, "PigTradeManager")`。
|
||||
- [ ] 将这个 `selfCtx` 赋值给 `pigTradeManager` 结构体的 `selfCtx` 成员。
|
||||
- [x] 将这个 `selfCtx` 赋值给 `pigTradeManager` 结构体的 `selfCtx` 成员。
|
||||
- **公共方法改造 (`SellPig`, `BuyPig`)**:
|
||||
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(在 `tx *gorm.DB` 之前)。
|
||||
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
|
||||
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(在 `tx *gorm.DB` 之前)。
|
||||
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
|
||||
`context.Context` 和 `logger` 实例。
|
||||
- [ ] 确保所有对 `s.tradeRepo` 的调用都将 `newCtx` 作为第一个参数传递。
|
||||
- [x] 确保所有对 `s.tradeRepo` 的调用都将 `newCtx` 作为第一个参数传递。
|
||||
- **`internal/domain/pig/pig_sick_manager.go` (`pig.SickPigManager`)**
|
||||
- **结构体改造**:
|
||||
- [ ] 新增 `selfCtx context.Context` 成员。
|
||||
- [x] 新增 `selfCtx context.Context` 成员。
|
||||
- **构造函数改造 (`NewSickPigManager`)**:
|
||||
- [ ] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在函数内部,为 `sickPigManager` 创建其专属的 `selfCtx`:
|
||||
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在函数内部,为 `sickPigManager` 创建其专属的 `selfCtx`:
|
||||
`selfCtx := logs.AddCompName(ctx, "SickPigManager")`。
|
||||
- [ ] 将这个 `selfCtx` 赋值给 `sickPigManager` 结构体的 `selfCtx` 成员。
|
||||
- [x] 将这个 `selfCtx` 赋值给 `sickPigManager` 结构体的 `selfCtx` 成员。
|
||||
- **公共方法改造 (`ProcessSickPigLog`, `GetCurrentSickPigCount`)**:
|
||||
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(在 `tx *gorm.DB` 之前)。
|
||||
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
|
||||
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(在 `tx *gorm.DB` 之前)。
|
||||
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
|
||||
`context.Context` 和 `logger` 实例。
|
||||
- [ ] 确保所有对 `s.sickLogRepo`, `s.medicationLogRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
|
||||
- [x] 确保所有对 `s.sickLogRepo`, `s.medicationLogRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
|
||||
- **`internal/domain/pig/pig_batch_service.go` (`pig.PigBatchService`)**
|
||||
- **结构体改造**:
|
||||
- [ ] 新增 `selfCtx context.Context` 成员。
|
||||
- [x] 新增 `selfCtx context.Context` 成员。
|
||||
- **构造函数改造 (`NewPigBatchService`)**:
|
||||
- [ ] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在函数内部,为 `pigBatchService` 创建其专属的 `selfCtx`:
|
||||
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在函数内部,为 `pigBatchService` 创建其专属的 `selfCtx`:
|
||||
`selfCtx := logs.AddCompName(ctx, "PigBatchService")`。
|
||||
- [ ] 将这个 `selfCtx` 赋值给 `pigBatchService` 结构体的 `selfCtx` 成员。
|
||||
- [x] 将这个 `selfCtx` 赋值给 `pigBatchService` 结构体的 `selfCtx` 成员。
|
||||
- **公共方法改造 (
|
||||
所有方法,例如 `CreatePigBatch`, `GetPigBatch`, `UpdatePigBatch`, `DeletePigBatch`, `ListPigBatches`,
|
||||
`AssignEmptyPensToBatch`, `MovePigsIntoPen`, `ReclassifyPenToNewBatch`, `RemoveEmptyPenFromBatch`,
|
||||
`GetCurrentPigQuantity`, `GetCurrentPigsInPen`, `GetTotalPigsInPensForBatch`, `UpdatePigBatchQuantity`,
|
||||
`SellPigs`, `BuyPigs`, `TransferPigsAcrossBatches`, `TransferPigsWithinBatch`, `RecordSickPigs`,
|
||||
`RecordSickPigRecovery`, `RecordSickPigDeath`, `RecordSickPigCull`, `RecordDeath`, `RecordCull`)**:
|
||||
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
|
||||
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
|
||||
`context.Context` 和 `logger` 实例。
|
||||
- [ ] 确保所有对 `s.pigBatchRepo`, `s.pigBatchLogRepo`, `s.uow`, `s.transferSvc`, `s.tradeSvc`, `s.sickSvc`
|
||||
- [x] 确保所有对 `s.pigBatchRepo`, `s.pigBatchLogRepo`, `s.uow`, `s.transferSvc`, `s.tradeSvc`, `s.sickSvc`
|
||||
等依赖的调用都将 `newCtx` 作为第一个参数传递。
|
||||
- **`internal/domain/plan/analysis_plan_task_manager.go` (`plan.AnalysisPlanTaskManager`)**
|
||||
- **结构体改造**:
|
||||
- [ ] 移除 `analysisPlanTaskManagerImpl` 结构体中的 `logger *logs.Logger` 成员。
|
||||
- [ ] 新增 `selfCtx context.Context` 成员。
|
||||
- [x] 移除 `analysisPlanTaskManagerImpl` 结构体中的 `logger *logs.Logger` 成员。
|
||||
- [x] 新增 `selfCtx context.Context` 成员。
|
||||
- **构造函数改造 (`NewAnalysisPlanTaskManager`)**:
|
||||
- [ ] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。
|
||||
- [ ] 在函数内部,为 `analysisPlanTaskManagerImpl` 创建其专属的 `selfCtx`:
|
||||
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。
|
||||
- [x] 在函数内部,为 `analysisPlanTaskManagerImpl` 创建其专属的 `selfCtx`:
|
||||
`selfCtx := logs.AddCompName(ctx, "AnalysisPlanTaskManager")`。
|
||||
- [ ] 将这个 `selfCtx` 赋值给 `analysisPlanTaskManagerImpl` 结构体的 `selfCtx` 成员。
|
||||
- [x] 将这个 `selfCtx` 赋值给 `analysisPlanTaskManagerImpl` 结构体的 `selfCtx` 成员。
|
||||
- **公共方法改造 (`Refresh`, `CreateOrUpdateTrigger`, `EnsureAnalysisTaskDefinition`)**:
|
||||
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, m.selfCtx, "MethodName")` 获取新的
|
||||
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, m.selfCtx, "MethodName")` 获取新的
|
||||
`context.Context` 和 `logger` 实例。
|
||||
- [ ] 将所有对 `m.logger.Info`, `m.logger.Errorf`, `m.logger.Warnf` 的调用替换为 `logger.Info`,
|
||||
- [x] 将所有对 `m.logger.Info`, `m.logger.Errorf`, `m.logger.Warnf` 的调用替换为 `logger.Info`,
|
||||
`logger.Errorf`, `logger.Warnf`。
|
||||
- [ ] 确保所有对 `m.planRepo`, `m.pendingTaskRepo`, `m.executionLogRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
|
||||
- [x] 确保所有对 `m.planRepo`, `m.pendingTaskRepo`, `m.executionLogRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
|
||||
- **内部辅助方法改造 (`getRefreshData`, `cleanupInvalidTasks`, `addOrUpdateTriggers`, `createTriggerTask`)**:
|
||||
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, m.selfCtx, "MethodName")` 获取新的
|
||||
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, m.selfCtx, "MethodName")` 获取新的
|
||||
`context.Context` 和 `logger` 实例。
|
||||
- [ ] 将所有对 `m.logger.Info`, `m.logger.Errorf`, `m.logger.Warnf` 的调用替换为 `logger.Info`,
|
||||
- [x] 将所有对 `m.logger.Info`, `m.logger.Errorf`, `m.logger.Warnf` 的调用替换为 `logger.Info`,
|
||||
`logger.Errorf`, `logger.Warnf`。
|
||||
- [ ] 确保所有对 `m.planRepo`, `m.pendingTaskRepo`, `m.executionLogRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
|
||||
- [x] 确保所有对 `m.planRepo`, `m.pendingTaskRepo`, `m.executionLogRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
|
||||
- **`internal/domain/plan/plan_execution_manager.go` (`plan.ExecutionManager`)**
|
||||
- **结构体改造**:
|
||||
- [ ] 移除 `planExecutionManagerImpl` 结构体中的 `logger *logs.Logger` 成员。
|
||||
- [ ] 新增 `selfCtx context.Context` 成员。
|
||||
- [x] 移除 `planExecutionManagerImpl` 结构体中的 `logger *logs.Logger` 成员。
|
||||
- [x] 新增 `selfCtx context.Context` 成员。
|
||||
- **构造函数改造 (`NewPlanExecutionManager`)**:
|
||||
- [ ] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。
|
||||
- [ ] 在函数内部,为 `planExecutionManagerImpl` 创建其专属的 `selfCtx`:
|
||||
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。
|
||||
- [x] 在函数内部,为 `planExecutionManagerImpl` 创建其专属的 `selfCtx`:
|
||||
`selfCtx := logs.AddCompName(ctx, "PlanExecutionManager")`。
|
||||
- [ ] 将这个 `selfCtx` 赋值给 `planExecutionManagerImpl` 结构体的 `selfCtx` 成员。
|
||||
- [x] 将这个 `selfCtx` 赋值给 `planExecutionManagerImpl` 结构体的 `selfCtx` 成员。
|
||||
- **公共方法改造 (`Start`, `Stop`)**:
|
||||
- [ ] 在 `Start` 方法中,使用 `newCtx, logger := logs.Trace(context.Background(), s.selfCtx, "Start")` 获取
|
||||
- [x] 在 `Start` 方法中,使用 `newCtx, logger := logs.Trace(context.Background(), s.selfCtx, "Start")` 获取
|
||||
`logger` 实例进行日志记录。
|
||||
- [ ] 在 `Stop` 方法中,使用 `newCtx, logger := logs.Trace(context.Background(), s.selfCtx, "Stop")` 获取
|
||||
- [x] 在 `Stop` 方法中,使用 `newCtx, logger := logs.Trace(context.Background(), s.selfCtx, "Stop")` 获取
|
||||
`logger` 实例进行日志记录。
|
||||
- **内部辅助方法改造 (`run`, `claimAndSubmit`, `handleRequeue`, `processTask`, `runTask`, `analysisPlan`,
|
||||
`updateTaskExecutionLogStatus`, `handlePlanTermination`, `handlePlanCompletion`)**:
|
||||
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(如果方法内部需要传递上下文)。
|
||||
- [ ] 在每个方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
|
||||
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(如果方法内部需要传递上下文)。
|
||||
- [x] 在每个方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
|
||||
`context.Context` 和 `logger` 实例。
|
||||
- [ ] 将所有对 `s.logger.Errorf`, `s.logger.Warnf`, `s.logger.Infof`, `s.logger.DPanicf` 的调用替换为
|
||||
- [x] 将所有对 `s.logger.Errorf`, `s.logger.Warnf`, `s.logger.Infof`, `s.logger.DPanicf` 的调用替换为
|
||||
`logger.Errorf`, `logger.Warnf`, `logger.Infof`, `logger.DPanicf`。
|
||||
- [ ] 确保所有对 `s.pendingTaskRepo`, `s.executionLogRepo`, `s.deviceRepo`, `s.sensorDataRepo`, `s.planRepo`,
|
||||
- [x] 确保所有对 `s.pendingTaskRepo`, `s.executionLogRepo`, `s.deviceRepo`, `s.sensorDataRepo`, `s.planRepo`,
|
||||
`s.analysisPlanTaskManager`, `s.taskFactory`, `s.deviceService` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
|
||||
- **`internal/domain/plan/plan_service.go` (`plan.Service`)**
|
||||
- **结构体改造**:
|
||||
- [ ] 移除 `planServiceImpl` 结构体中的 `logger *logs.Logger` 成员。
|
||||
- [ ] 新增 `selfCtx context.Context` 成员。
|
||||
- [x] 移除 `planServiceImpl` 结构体中的 `logger *logs.Logger` 成员。
|
||||
- [x] 新增 `selfCtx context.Context` 成员。
|
||||
- **构造函数改造 (`NewPlanService`)**:
|
||||
- [ ] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。
|
||||
- [ ] 在函数内部,为 `planServiceImpl` 创建其专属的 `selfCtx`:
|
||||
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。
|
||||
- [x] 在函数内部,为 `planServiceImpl` 创建其专属的 `selfCtx`:
|
||||
`selfCtx := logs.AddCompName(ctx, "PlanService")`。
|
||||
- [ ] 将这个 `selfCtx` 赋值给 `planServiceImpl` 结构体的 `selfCtx` 成员。
|
||||
- [x] 将这个 `selfCtx` 赋值给 `planServiceImpl` 结构体的 `selfCtx` 成员。
|
||||
- **公共方法改造 (`Start`, `Stop`, `RefreshPlanTriggers`, `CreatePlan`, `GetPlanByID`, `ListPlans`, `UpdatePlan`,
|
||||
`DeletePlan`, `StartPlan`, `StopPlan`)**:
|
||||
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
|
||||
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
|
||||
`context.Context` 和 `logger` 实例。
|
||||
- [ ] 将所有对 `s.logger.Errorf`, `s.logger.Infof`, `s.logger.Warnf` 的调用替换为 `logger.Errorf`,
|
||||
- [x] 将所有对 `s.logger.Errorf`, `s.logger.Infof`, `s.logger.Warnf` 的调用替换为 `logger.Errorf`,
|
||||
`logger.Infof`, `logger.Warnf`。
|
||||
- [ ] 确保所有对 `s.executionManager`, `s.taskManager`, `s.planRepo`, `s.deviceRepo`, `s.unitOfWork`,
|
||||
- [x] 确保所有对 `s.executionManager`, `s.taskManager`, `s.planRepo`, `s.deviceRepo`, `s.unitOfWork`,
|
||||
`s.taskFactory` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
|
||||
- **`internal/domain/task/task.go` (`task.TaskFactory`)**
|
||||
- **结构体改造**:
|
||||
- [ ] 移除 `taskFactory` 结构体中的 `logger *logs.Logger` 成员。
|
||||
- [ ] 新增 `selfCtx context.Context` 成员。
|
||||
- [x] 移除 `taskFactory` 结构体中的 `logger *logs.Logger` 成员。
|
||||
- [x] 新增 `selfCtx context.Context` 成员。
|
||||
- **构造函数改造 (`NewTaskFactory`)**:
|
||||
- [ ] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在函数内部,为 `taskFactory` 创建其专属的 `selfCtx`:`selfCtx := logs.AddCompName(ctx, "TaskFactory")`。
|
||||
- [ ] 将这个 `selfCtx` 赋值给 `taskFactory` 结构体的 `selfCtx` 成员。
|
||||
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在函数内部,为 `taskFactory` 创建其专属的 `selfCtx`:`selfCtx := logs.AddCompName(ctx, "TaskFactory")`。
|
||||
- [x] 将这个 `selfCtx` 赋值给 `taskFactory` 结构体的 `selfCtx` 成员。
|
||||
- **公共方法改造 (`Production`, `CreateTaskFromModel`)**:
|
||||
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在方法内部,使用 `newCtx, logger := logs.Trace(ctx, t.selfCtx, "MethodName")` 获取新的 `context.Context`
|
||||
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在方法内部,使用 `newCtx, logger := logs.Trace(ctx, t.selfCtx, "MethodName")` 获取新的 `context.Context`
|
||||
和 `logger` 实例。
|
||||
- [ ] 将所有对 `t.logger.Panicf` 的调用替换为 `logger.Panicf`。
|
||||
- [ ] 将所有对 `NewDelayTask`, `NewReleaseFeedWeightTask`, `NewFullCollectionTask` 的调用,传递 `newCtx`。
|
||||
- [x] 将所有对 `t.logger.Panicf` 的调用替换为 `logger.Panicf`。
|
||||
- [x] 将所有对 `NewDelayTask`, `NewReleaseFeedWeightTask`, `NewFullCollectionTask` 的调用,传递 `newCtx`。
|
||||
- **`internal/domain/task/release_feed_weight_task.go`**
|
||||
- **结构体改造**:
|
||||
- [ ] 移除 `ReleaseFeedWeightTask` 结构体中的 `logger *logs.Logger` 成员。
|
||||
- [ ] 新增 `selfCtx context.Context` 成员。
|
||||
- [x] 移除 `ReleaseFeedWeightTask` 结构体中的 `logger *logs.Logger` 成员。
|
||||
- [x] 新增 `selfCtx context.Context` 成员。
|
||||
- **构造函数改造 (`NewReleaseFeedWeightTask`)**:
|
||||
- [ ] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在函数内部,为 `ReleaseFeedWeightTask` 创建其专属的 `selfCtx`:
|
||||
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在函数内部,为 `ReleaseFeedWeightTask` 创建其专属的 `selfCtx`:
|
||||
`selfCtx := logs.AddCompName(ctx, "ReleaseFeedWeightTask")`。
|
||||
- [ ] 将这个 `selfCtx` 赋值给 `ReleaseFeedWeightTask` 结构体的 `selfCtx` 成员。
|
||||
- [x] 将这个 `selfCtx` 赋值给 `ReleaseFeedWeightTask` 结构体的 `selfCtx` 成员。
|
||||
- **公共方法改造 (`Execute`, `OnFailure`, `ResolveDeviceIDs`)**:
|
||||
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, r.selfCtx, "MethodName")` 获取新的
|
||||
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, r.selfCtx, "MethodName")` 获取新的
|
||||
`context.Context` 和 `logger` 实例。
|
||||
- [ ] 将所有对 `r.logger.Infof`, `r.logger.Errorf`, `r.logger.Warnf`, `r.logger.Debugf` 的调用替换为
|
||||
- [x] 将所有对 `r.logger.Infof`, `r.logger.Errorf`, `r.logger.Warnf`, `r.logger.Debugf` 的调用替换为
|
||||
`logger.Infof`, `logger.Errorf`, `logger.Warnf`, `logger.Debugf`。
|
||||
- [ ] 确保所有对 `r.deviceRepo`, `r.sensorDataRepo`, `r.feedPort` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
|
||||
- [x] 确保所有对 `r.deviceRepo`, `r.sensorDataRepo`, `r.feedPort` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
|
||||
- **内部辅助方法改造 (`getNowWeight`, `parseParameters`)**:
|
||||
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, r.selfCtx, "MethodName")` 获取新的
|
||||
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, r.selfCtx, "MethodName")` 获取新的
|
||||
`context.Context` 和 `logger` 实例。
|
||||
- [ ] 将所有对 `r.logger.Errorf`, `r.logger.Warnf` 的调用替换为 `logger.Errorf`, `logger.Warnf`。
|
||||
- [ ] 确保所有对 `r.sensorDataRepo`, `r.deviceRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
|
||||
- [x] 将所有对 `r.logger.Errorf`, `r.logger.Warnf` 的调用替换为 `logger.Errorf`, `logger.Warnf`。
|
||||
- [x] 确保所有对 `r.sensorDataRepo`, `r.deviceRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
|
||||
- **`internal/domain/task/full_collection_task.go`**
|
||||
- **结构体改造**:
|
||||
- [ ] 移除 `FullCollectionTask` 结构体中的 `logger *logs.Logger` 成员。
|
||||
- [ ] 新增 `selfCtx context.Context` 成员。
|
||||
- [x] 移除 `FullCollectionTask` 结构体中的 `logger *logs.Logger` 成员。
|
||||
- [x] 新增 `selfCtx context.Context` 成员。
|
||||
- **构造函数改造 (`NewFullCollectionTask`)**:
|
||||
- [ ] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在函数内部,为 `FullCollectionTask` 创建其专属的 `selfCtx`:
|
||||
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在函数内部,为 `FullCollectionTask` 创建其专属的 `selfCtx`:
|
||||
`selfCtx := logs.AddCompName(ctx, "FullCollectionTask")`。
|
||||
- [ ] 将这个 `selfCtx` 赋值给 `FullCollectionTask` 结构体的 `selfCtx` 成员。
|
||||
- [x] 将这个 `selfCtx` 赋值给 `FullCollectionTask` 结构体的 `selfCtx` 成员。
|
||||
- **公共方法改造 (`Execute`, `OnFailure`, `ResolveDeviceIDs`)**:
|
||||
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, t.selfCtx, "MethodName")` 获取新的
|
||||
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, t.selfCtx, "MethodName")` 获取新的
|
||||
`context.Context` 和 `logger` 实例。
|
||||
- [ ] 将所有对 `t.logger.Infow`, `t.logger.Errorw` 的调用替换为 `logger.Infow`, `logger.Errorw`。
|
||||
- [ ] 确保所有对 `t.deviceRepo`, `t.deviceService` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
|
||||
- [x] 将所有对 `t.logger.Infow`, `t.logger.Errorw` 的调用替换为 `logger.Infow`, `logger.Errorw`。
|
||||
- [x] 确保所有对 `t.deviceRepo`, `t.deviceService` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
|
||||
- **`internal/domain/task/delay_task.go`**
|
||||
- **结构体改造**:
|
||||
- [ ] 移除 `DelayTask` 结构体中的 `logger *logs.Logger` 成员。
|
||||
- [ ] 新增 `selfCtx context.Context` 成员。
|
||||
- [x] 移除 `DelayTask` 结构体中的 `logger *logs.Logger` 成员。
|
||||
- [x] 新增 `selfCtx context.Context` 成员。
|
||||
- **构造函数改造 (`NewDelayTask`)**:
|
||||
- [ ] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。
|
||||
- [ ] 在函数内部,为 `DelayTask` 创建其专属的 `selfCtx`:`selfCtx := logs.AddCompName(ctx, "DelayTask")`。
|
||||
- [ ] 将这个 `selfCtx` 赋值给 `DelayTask` 结构体的 `selfCtx` 成员。
|
||||
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。
|
||||
- [x] 在函数内部,为 `DelayTask` 创建其专属的 `selfCtx`:`selfCtx := logs.AddCompName(ctx, "DelayTask")`。
|
||||
- [x] 将这个 `selfCtx` 赋值给 `DelayTask` 结构体的 `selfCtx` 成员。
|
||||
- **公共方法改造 (`Execute`, `OnFailure`, `ResolveDeviceIDs`)**:
|
||||
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, d.selfCtx, "MethodName")` 获取新的
|
||||
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, d.selfCtx, "MethodName")` 获取新的
|
||||
`context.Context` 和 `logger` 实例。
|
||||
- [ ] 将所有对 `d.logger.Infof` 和 `d.logger.Errorf` 的调用替换为 `logger.Infof` 和 `logger.Errorf`。
|
||||
- [x] 将所有对 `d.logger.Infof` 和 `d.logger.Errorf` 的调用替换为 `logger.Infof` 和 `logger.Errorf`。
|
||||
- **内部辅助方法改造 (`parseParameters`)**:
|
||||
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, d.selfCtx, "MethodName")` 获取新的
|
||||
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, d.selfCtx, "MethodName")` 获取新的
|
||||
`context.Context` 和 `logger` 实例。
|
||||
- [ ] 将所有对 `d.logger.Errorf` 的调用替换为 `logger.Errorf`。
|
||||
- [x] 将所有对 `d.logger.Errorf` 的调用替换为 `logger.Errorf`。
|
||||
- **`internal/domain/token/token_service.go` (`token.Service`)**
|
||||
- **结构体改造**:
|
||||
- [ ] 新增 `selfCtx context.Context` 成员。
|
||||
- [x] 新增 `selfCtx context.Context` 成员。
|
||||
- **构造函数改造 (`NewTokenService`)**:
|
||||
- [ ] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在函数内部,为 `tokenService` 创建其专属的 `selfCtx`:`selfCtx := logs.AddCompName(ctx, "TokenService")`。
|
||||
- [ ] 将这个 `selfCtx` 赋值给 `tokenService` 结构体的 `selfCtx` 成员。
|
||||
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在函数内部,为 `tokenService` 创建其专属的 `selfCtx`:`selfCtx := logs.AddCompName(ctx, "TokenService")`。
|
||||
- [x] 将这个 `selfCtx` 赋值给 `tokenService` 结构体的 `selfCtx` 成员。
|
||||
- **公共方法改造 (`GenerateToken`, `ParseToken`)**:
|
||||
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
|
||||
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
|
||||
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
|
||||
`context.Context` 和 `logger` 实例。
|
||||
- [ ] 将所有对 `s.logger.*` 的调用替换为 `logger.*` (如果存在)。
|
||||
- [x] 将所有对 `s.logger.*` 的调用替换为 `logger.*` (如果存在)。
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -26,12 +26,11 @@ import (
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/user"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/service"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/webhook"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/audit"
|
||||
domain_plan "git.huangwc.com/pig/pig-farm-controller/internal/domain/plan"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/token"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/config"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/utils/token"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
@@ -42,8 +41,8 @@ type API struct {
|
||||
echo *echo.Echo // Echo 引擎实例,用于处理 HTTP 请求
|
||||
Ctx context.Context // API 组件的上下文,包含日志记录器
|
||||
userRepo repository.UserRepository // 用户数据仓库接口,用于用户数据操作
|
||||
tokenService token.Service // Token 服务接口,用于 JWT token 的生成和解析
|
||||
auditService audit.Service // 审计服务,用于记录用户操作
|
||||
tokenGenerator token.Generator // Token 服务接口,用于 JWT token 的生成和解析
|
||||
auditService service.AuditService // 审计服务,用于记录用户操作
|
||||
httpServer *http.Server // 标准库的 HTTP 服务器实例,用于启动和停止服务
|
||||
config config.ServerConfig // API 服务器的配置,使用 infra/config 包中的 ServerConfig
|
||||
userController *user.Controller // 用户控制器实例
|
||||
@@ -67,8 +66,8 @@ func NewAPI(cfg config.ServerConfig,
|
||||
deviceService service.DeviceService,
|
||||
planService service.PlanService,
|
||||
userService service.UserService,
|
||||
tokenService token.Service,
|
||||
auditService audit.Service,
|
||||
auditService service.AuditService,
|
||||
tokenGenerator token.Generator,
|
||||
listenHandler webhook.ListenHandler,
|
||||
) *API {
|
||||
// 使用 echo.New() 创建一个 Echo 引擎实例
|
||||
@@ -87,7 +86,7 @@ func NewAPI(cfg config.ServerConfig,
|
||||
echo: e,
|
||||
Ctx: ctx,
|
||||
userRepo: userRepo,
|
||||
tokenService: tokenService,
|
||||
tokenGenerator: tokenGenerator,
|
||||
auditService: auditService,
|
||||
config: cfg,
|
||||
listenHandler: listenHandler,
|
||||
|
||||
@@ -54,7 +54,7 @@ func (a *API) setupRoutes() {
|
||||
// --- Authenticated Routes ---
|
||||
// 所有在此注册的路由都需要通过 JWT 身份验证
|
||||
authGroup := a.echo.Group("/api/v1")
|
||||
authGroup.Use(middleware.AuthMiddleware(logs.AddCompName(context.Background(), "AuthMiddleware"), a.tokenService, a.userRepo)) // 1. 身份认证中间件
|
||||
authGroup.Use(middleware.AuthMiddleware(logs.AddCompName(context.Background(), "AuthMiddleware"), a.tokenGenerator, a.userRepo)) // 1. 身份认证中间件
|
||||
authGroup.Use(middleware.AuditLogMiddleware(logs.AddCompName(context.Background(), "AuditLogMiddleware"), a.auditService)) // 2. 审计日志中间件
|
||||
{
|
||||
// 用户相关路由组
|
||||
|
||||
@@ -6,10 +6,10 @@ import (
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/dto"
|
||||
domain_notify "git.huangwc.com/pig/pig-farm-controller/internal/domain/notify"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/token"
|
||||
"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/repository"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/utils/token"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -25,7 +25,7 @@ type UserService interface {
|
||||
type userService struct {
|
||||
ctx context.Context
|
||||
userRepo repository.UserRepository
|
||||
tokenService token.Service
|
||||
tokenGenerator token.Generator
|
||||
notifyService domain_notify.Service
|
||||
}
|
||||
|
||||
@@ -33,13 +33,13 @@ type userService struct {
|
||||
func NewUserService(
|
||||
ctx context.Context,
|
||||
userRepo repository.UserRepository,
|
||||
tokenService token.Service,
|
||||
tokenGenerator token.Generator,
|
||||
notifyService domain_notify.Service,
|
||||
) UserService {
|
||||
return &userService{
|
||||
ctx: ctx,
|
||||
userRepo: userRepo,
|
||||
tokenService: tokenService,
|
||||
tokenGenerator: tokenGenerator,
|
||||
notifyService: notifyService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,8 +57,8 @@ func NewApplication(configPath string) (*Application, error) {
|
||||
appServices.deviceService,
|
||||
appServices.planService,
|
||||
appServices.userService,
|
||||
infra.tokenService,
|
||||
appServices.auditService,
|
||||
infra.tokenGenerator,
|
||||
infra.lora.listenHandler,
|
||||
)
|
||||
|
||||
@@ -91,7 +91,7 @@ func (app *Application) Start() error {
|
||||
}
|
||||
|
||||
// 3. 启动后台工作协程
|
||||
app.Domain.planService.Start()
|
||||
app.Domain.planService.Start(startCtx)
|
||||
|
||||
// 4. 启动 API 服务器
|
||||
app.API.Start()
|
||||
@@ -107,14 +107,14 @@ func (app *Application) Start() error {
|
||||
|
||||
// Stop 优雅地关闭应用的所有组件。
|
||||
func (app *Application) Stop() error {
|
||||
logger := logs.TraceLogger(app.Ctx, app.Ctx, "Stop")
|
||||
stopCtx, logger := logs.Trace(app.Ctx, app.Ctx, "Stop")
|
||||
logger.Info("应用关闭中...")
|
||||
|
||||
// 关闭 API 服务器
|
||||
app.API.Stop()
|
||||
|
||||
// 关闭任务执行器
|
||||
app.Domain.planService.Stop()
|
||||
app.Domain.planService.Stop(stopCtx)
|
||||
|
||||
// 断开数据库连接
|
||||
if err := app.Infra.storage.Disconnect(); err != nil {
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/pig"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/plan"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/task"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/token"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/config"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/database"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
@@ -21,6 +20,7 @@ import (
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/transport"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/transport/lora"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/utils/token"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -30,7 +30,7 @@ type Infrastructure struct {
|
||||
repos *Repositories
|
||||
lora *LoraComponents
|
||||
notifyService domain_notify.Service
|
||||
tokenService token.Service
|
||||
tokenGenerator token.Generator
|
||||
}
|
||||
|
||||
// initInfrastructure 初始化所有基础设施层组件。
|
||||
@@ -52,14 +52,14 @@ func initInfrastructure(ctx context.Context, cfg *config.Config) (*Infrastructur
|
||||
return nil, fmt.Errorf("初始化通知服务失败: %w", err)
|
||||
}
|
||||
|
||||
tokenService := token.NewTokenService([]byte(cfg.App.JWTSecret))
|
||||
tokenGenerator := token.NewTokenGenerator([]byte(cfg.App.JWTSecret))
|
||||
|
||||
return &Infrastructure{
|
||||
storage: storage,
|
||||
repos: repos,
|
||||
lora: lora,
|
||||
notifyService: notifyService,
|
||||
tokenService: tokenService,
|
||||
tokenGenerator: tokenGenerator,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -139,8 +139,14 @@ func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastr
|
||||
pigPenTransferManager := pig.NewPigPenTransferManager(logs.AddCompName(baseCtx, "PigPenTransferManager"), infra.repos.pigPenRepo, infra.repos.pigTransferLogRepo, infra.repos.pigBatchRepo)
|
||||
pigTradeManager := pig.NewPigTradeManager(logs.AddCompName(baseCtx, "PigTradeManager"), infra.repos.pigTradeRepo)
|
||||
pigSickManager := pig.NewSickPigManager(logs.AddCompName(baseCtx, "PigSickManager"), infra.repos.pigSickPigLogRepo, infra.repos.medicationLogRepo)
|
||||
pigBatchDomain := pig.NewPigBatchService(logs.AddCompName(baseCtx, "PigBatchDomain")), infra.repos.pigBatchRepo, infra.repos.pigBatchLogRepo, infra.repos.unitOfWork,
|
||||
pigPenTransferManager, pigTradeManager, pigSickManager)
|
||||
pigBatchDomain := pig.NewPigBatchService(
|
||||
logs.AddCompName(baseCtx, "PigBatchDomain"),
|
||||
infra.repos.pigBatchRepo,
|
||||
infra.repos.pigBatchLogRepo,
|
||||
infra.repos.unitOfWork,
|
||||
pigPenTransferManager,
|
||||
pigTradeManager, pigSickManager,
|
||||
)
|
||||
|
||||
// 通用设备服务
|
||||
generalDeviceService := device.NewGeneralDeviceService(
|
||||
@@ -238,7 +244,7 @@ func initAppServices(ctx context.Context, infra *Infrastructure, domainServices
|
||||
)
|
||||
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.tokenService, infra.notifyService)
|
||||
userService := service.NewUserService(logs.AddCompName(baseCtx, "UserService"), infra.repos.userRepo, infra.tokenGenerator, infra.notifyService)
|
||||
|
||||
return &AppServices{
|
||||
pigFarmService: pigFarmService,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package device
|
||||
|
||||
import "git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
)
|
||||
|
||||
// 设备行为
|
||||
type DeviceAction string
|
||||
@@ -21,10 +25,10 @@ var (
|
||||
type Service interface {
|
||||
|
||||
// Switch 用于切换指定设备的状态, 比如启动和停止
|
||||
Switch(device *models.Device, action DeviceAction) error
|
||||
Switch(ctx context.Context, device *models.Device, action DeviceAction) error
|
||||
|
||||
// Collect 用于发起对指定区域主控下的多个设备的批量采集请求。
|
||||
Collect(regionalControllerID uint, devicesToCollect []*models.Device) error
|
||||
Collect(ctx context.Context, regionalControllerID uint, devicesToCollect []*models.Device) error
|
||||
}
|
||||
|
||||
// 设备操作指令通用结构(最外层)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
@@ -17,31 +18,32 @@ import (
|
||||
)
|
||||
|
||||
type GeneralDeviceService struct {
|
||||
ctx context.Context
|
||||
deviceRepo repository.DeviceRepository
|
||||
deviceCommandLogRepo repository.DeviceCommandLogRepository
|
||||
pendingCollectionRepo repository.PendingCollectionRepository
|
||||
logger *logs.Logger
|
||||
comm transport.Communicator
|
||||
}
|
||||
|
||||
// NewGeneralDeviceService 创建一个通用设备服务
|
||||
func NewGeneralDeviceService(
|
||||
ctx context.Context,
|
||||
deviceRepo repository.DeviceRepository,
|
||||
deviceCommandLogRepo repository.DeviceCommandLogRepository,
|
||||
pendingCollectionRepo repository.PendingCollectionRepository,
|
||||
logger *logs.Logger,
|
||||
comm transport.Communicator,
|
||||
) Service {
|
||||
return &GeneralDeviceService{
|
||||
ctx: ctx,
|
||||
deviceRepo: deviceRepo,
|
||||
deviceCommandLogRepo: deviceCommandLogRepo,
|
||||
pendingCollectionRepo: pendingCollectionRepo,
|
||||
logger: logger,
|
||||
comm: comm,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GeneralDeviceService) Switch(device *models.Device, action DeviceAction) error {
|
||||
func (g *GeneralDeviceService) Switch(ctx context.Context, device *models.Device, action DeviceAction) error {
|
||||
serviceCtx, logger := logs.Trace(ctx, g.ctx, "Switch")
|
||||
// 1. 依赖模型自身的 SelfCheck 进行全面校验
|
||||
if err := device.SelfCheck(); err != nil {
|
||||
return fmt.Errorf("设备 %v(id=%v) 未通过自检: %w", device.Name, device.ID, err)
|
||||
@@ -102,7 +104,7 @@ func (g *GeneralDeviceService) Switch(device *models.Device, action DeviceAction
|
||||
|
||||
// 7. 发送指令
|
||||
networkID := areaController.NetworkID
|
||||
sendResult, err := g.comm.Send(networkID, message)
|
||||
sendResult, err := g.comm.Send(serviceCtx, networkID, message)
|
||||
if err != nil {
|
||||
return fmt.Errorf("发送指令到 %s 失败: %w", networkID, err)
|
||||
}
|
||||
@@ -120,20 +122,21 @@ func (g *GeneralDeviceService) Switch(device *models.Device, action DeviceAction
|
||||
logRecord.ReceivedSuccess = *sendResult.ReceivedSuccess
|
||||
}
|
||||
|
||||
if err := g.deviceCommandLogRepo.Create(logRecord); err != nil {
|
||||
if err := g.deviceCommandLogRepo.Create(serviceCtx, logRecord); err != nil {
|
||||
// 记录日志失败是一个需要关注的问题,但可能不应该中断主流程。
|
||||
// 我们记录一个错误日志,然后成功返回。
|
||||
g.logger.Errorf("创建指令日志失败 (MessageID: %s): %v", sendResult.MessageID, err)
|
||||
logger.Errorf("创建指令日志失败 (MessageID: %s): %v", sendResult.MessageID, err)
|
||||
}
|
||||
|
||||
g.logger.Infof("成功发送指令到 %s 并创建日志 (MessageID: %s)", networkID, sendResult.MessageID)
|
||||
logger.Infof("成功发送指令到 %s 并创建日志 (MessageID: %s)", networkID, sendResult.MessageID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Collect 实现了 Service 接口,用于发起对指定区域主控下的多个设备的批量采集请求。
|
||||
func (g *GeneralDeviceService) Collect(regionalControllerID uint, devicesToCollect []*models.Device) error {
|
||||
func (g *GeneralDeviceService) Collect(ctx context.Context, regionalControllerID uint, devicesToCollect []*models.Device) error {
|
||||
serviceCtx, logger := logs.Trace(ctx, g.ctx, "Collect")
|
||||
if len(devicesToCollect) == 0 {
|
||||
g.logger.Info("待采集设备列表为空,无需执行采集任务。")
|
||||
logger.Info("待采集设备列表为空,无需执行采集任务。")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -153,25 +156,25 @@ func (g *GeneralDeviceService) Collect(regionalControllerID uint, devicesToColle
|
||||
for _, dev := range devicesToCollect {
|
||||
// 依赖模型自身的 SelfCheck 进行全面校验
|
||||
if err := dev.SelfCheck(); err != nil {
|
||||
g.logger.Warnf("跳过设备 %d,因其未通过自检: %v", dev.ID, err)
|
||||
logger.Warnf("跳过设备 %d,因其未通过自检: %v", dev.ID, err)
|
||||
continue
|
||||
}
|
||||
if err := dev.DeviceTemplate.SelfCheck(); err != nil {
|
||||
g.logger.Warnf("跳过设备 %d,因其设备模板未通过自检: %v", dev.ID, err)
|
||||
logger.Warnf("跳过设备 %d,因其设备模板未通过自检: %v", dev.ID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 使用模板的 ParseCommands 方法获取传感器指令参数
|
||||
var sensorCmd models.SensorCommands
|
||||
if err := dev.DeviceTemplate.ParseCommands(&sensorCmd); err != nil {
|
||||
g.logger.Warnf("跳过设备 %d,因其模板指令无法解析为 SensorCommands: %v", dev.ID, err)
|
||||
logger.Warnf("跳过设备 %d,因其模板指令无法解析为 SensorCommands: %v", dev.ID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 使用模型层预定义的 Bus485Properties 结构体解析设备属性
|
||||
var deviceProps models.Bus485Properties
|
||||
if err := dev.ParseProperties(&deviceProps); err != nil {
|
||||
g.logger.Warnf("跳过设备 %d,因其属性解析失败: %v", dev.ID, err)
|
||||
logger.Warnf("跳过设备 %d,因其属性解析失败: %v", dev.ID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -183,10 +186,10 @@ func (g *GeneralDeviceService) Collect(regionalControllerID uint, devicesToColle
|
||||
sensorCmd.ModbusQuantity,
|
||||
)
|
||||
if err != nil {
|
||||
g.logger.Warnf("跳过设备 %d,因生成Modbus RTU读取指令失败: %v", dev.ID, err)
|
||||
logger.Warnf("跳过设备 %d,因生成Modbus RTU读取指令失败: %v", dev.ID, err)
|
||||
continue
|
||||
}
|
||||
g.logger.Debugf("生成485指令: %v", modbusCommandBytes)
|
||||
logger.Debugf("生成485指令: %v", modbusCommandBytes)
|
||||
|
||||
// 构建 Raw485Command,包含总线号
|
||||
raw485Cmd := &proto.Raw485Command{
|
||||
@@ -216,11 +219,11 @@ func (g *GeneralDeviceService) Collect(regionalControllerID uint, devicesToColle
|
||||
Status: models.PendingStatusPending,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
if err := g.pendingCollectionRepo.Create(pendingReq); err != nil {
|
||||
g.logger.Errorf("创建待采集请求失败 (CorrelationID: %s): %v", correlationID, err)
|
||||
if err := g.pendingCollectionRepo.Create(serviceCtx, pendingReq); err != nil {
|
||||
logger.Errorf("创建待采集请求失败 (CorrelationID: %s): %v", correlationID, err)
|
||||
return err
|
||||
}
|
||||
g.logger.Infof("成功创建待采集请求 (CorrelationID: %s, DeviceID: %d)", correlationID, regionalController.ID)
|
||||
logger.Infof("成功创建待采集请求 (CorrelationID: %s, DeviceID: %d)", correlationID, regionalController.ID)
|
||||
|
||||
// 5. 构建最终的空中载荷
|
||||
batchCmd := &proto.BatchCollectCommand{
|
||||
@@ -234,15 +237,15 @@ func (g *GeneralDeviceService) Collect(regionalControllerID uint, devicesToColle
|
||||
}
|
||||
payload, err := gproto.Marshal(instruction)
|
||||
if err != nil {
|
||||
g.logger.Errorf("序列化采集指令失败 (CorrelationID: %s): %v", correlationID, err)
|
||||
logger.Errorf("序列化采集指令失败 (CorrelationID: %s): %v", correlationID, err)
|
||||
return err
|
||||
}
|
||||
g.logger.Infof("构造空中载荷成功: networkID: %v, payload: %v", networkID, instruction)
|
||||
if _, err := g.comm.Send(networkID, payload); err != nil {
|
||||
g.logger.DPanicf("待采集请求 (CorrelationID: %s) 已创建,但发送到设备失败: %v。数据可能不一致!", correlationID, err)
|
||||
logger.Infof("构造空中载荷成功: networkID: %v, payload: %v", networkID, instruction)
|
||||
if _, err := g.comm.Send(serviceCtx, networkID, payload); err != nil {
|
||||
logger.DPanicf("待采集请求 (CorrelationID: %s) 已创建,但发送到设备失败: %v。数据可能不一致!", correlationID, err)
|
||||
return err
|
||||
}
|
||||
|
||||
g.logger.Infof("成功将采集请求 (CorrelationID: %s) 发送到设备 %s", correlationID, networkID)
|
||||
logger.Infof("成功将采集请求 (CorrelationID: %s) 发送到设备 %s", correlationID, networkID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -10,24 +11,25 @@ import (
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/notify"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Service 定义了通知领域的核心业务逻辑接口
|
||||
type Service interface {
|
||||
// SendBatchAlarm 向一批用户发送告警通知。它会并发地为每个用户执行带故障转移的发送逻辑。
|
||||
SendBatchAlarm(userIDs []uint, content notify.AlarmContent) error
|
||||
SendBatchAlarm(ctx context.Context, userIDs []uint, content notify.AlarmContent) error
|
||||
|
||||
// BroadcastAlarm 向所有用户发送告警通知。它会并发地为每个用户执行带故障转移的发送逻辑。
|
||||
BroadcastAlarm(content notify.AlarmContent) error
|
||||
BroadcastAlarm(ctx context.Context, content notify.AlarmContent) error
|
||||
|
||||
// SendTestMessage 向指定用户发送一条测试消息,用于手动验证特定通知渠道的配置。
|
||||
SendTestMessage(userID uint, notifierType notify.NotifierType) error
|
||||
SendTestMessage(ctx context.Context, userID uint, notifierType notify.NotifierType) error
|
||||
}
|
||||
|
||||
// failoverService 是 Service 接口的实现,提供了故障转移功能
|
||||
type failoverService struct {
|
||||
log *logs.Logger
|
||||
ctx context.Context
|
||||
userRepo repository.UserRepository
|
||||
notifiers map[notify.NotifierType]notify.Notifier
|
||||
primaryNotifier notify.Notifier
|
||||
@@ -38,7 +40,7 @@ type failoverService struct {
|
||||
|
||||
// NewFailoverService 创建一个新的故障转移通知服务
|
||||
func NewFailoverService(
|
||||
log *logs.Logger,
|
||||
ctx context.Context,
|
||||
userRepo repository.UserRepository,
|
||||
notifiers []notify.Notifier,
|
||||
primaryNotifierType notify.NotifierType,
|
||||
@@ -56,7 +58,7 @@ func NewFailoverService(
|
||||
}
|
||||
|
||||
return &failoverService{
|
||||
log: log,
|
||||
ctx: ctx,
|
||||
userRepo: userRepo,
|
||||
notifiers: notifierMap,
|
||||
primaryNotifier: primaryNotifier,
|
||||
@@ -67,18 +69,19 @@ func NewFailoverService(
|
||||
}
|
||||
|
||||
// SendBatchAlarm 实现了向多个用户并发发送告警的功能
|
||||
func (s *failoverService) SendBatchAlarm(userIDs []uint, content notify.AlarmContent) error {
|
||||
func (s *failoverService) SendBatchAlarm(ctx context.Context, userIDs []uint, content notify.AlarmContent) error {
|
||||
serviceCtx, logger := logs.Trace(ctx, s.ctx, "SendBatchAlarm")
|
||||
var wg sync.WaitGroup
|
||||
var mu sync.Mutex
|
||||
var allErrors []string
|
||||
|
||||
s.log.Infow("开始批量发送告警...", "userCount", len(userIDs))
|
||||
logger.Infow("开始批量发送告警...", "userCount", len(userIDs))
|
||||
|
||||
for _, userID := range userIDs {
|
||||
wg.Add(1)
|
||||
go func(id uint) {
|
||||
defer wg.Done()
|
||||
if err := s.sendAlarmToUser(id, content); err != nil {
|
||||
if err := s.sendAlarmToUser(serviceCtx, id, content); err != nil {
|
||||
mu.Lock()
|
||||
allErrors = append(allErrors, fmt.Sprintf("发送失败 (用户ID: %d): %v", id, err))
|
||||
mu.Unlock()
|
||||
@@ -90,19 +93,20 @@ func (s *failoverService) SendBatchAlarm(userIDs []uint, content notify.AlarmCon
|
||||
|
||||
if len(allErrors) > 0 {
|
||||
finalError := fmt.Errorf("批量告警发送完成,但有 %d 个用户发送失败:\n%s", len(allErrors), strings.Join(allErrors, "\n"))
|
||||
s.log.Error(finalError.Error())
|
||||
logger.Error(finalError.Error())
|
||||
return finalError
|
||||
}
|
||||
|
||||
s.log.Info("批量发送告警成功完成,所有用户均已通知。")
|
||||
logger.Info("批量发送告警成功完成,所有用户均已通知。")
|
||||
return nil
|
||||
}
|
||||
|
||||
// BroadcastAlarm 实现了向所有用户发送告警的功能
|
||||
func (s *failoverService) BroadcastAlarm(content notify.AlarmContent) error {
|
||||
users, err := s.userRepo.FindAll()
|
||||
func (s *failoverService) BroadcastAlarm(ctx context.Context, content notify.AlarmContent) error {
|
||||
serviceCtx, logger := logs.Trace(ctx, s.ctx, "BroadcastAlarm")
|
||||
users, err := s.userRepo.FindAll(serviceCtx)
|
||||
if err != nil {
|
||||
s.log.Errorw("广播告警失败:查找所有用户时出错", "error", err)
|
||||
logger.Errorw("广播告警失败:查找所有用户时出错", "error", err)
|
||||
return fmt.Errorf("广播告警失败:查找所有用户时出错: %w", err)
|
||||
}
|
||||
|
||||
@@ -111,16 +115,17 @@ func (s *failoverService) BroadcastAlarm(content notify.AlarmContent) error {
|
||||
userIDs = append(userIDs, user.ID)
|
||||
}
|
||||
|
||||
s.log.Infow("开始广播告警给所有用户", "totalUsers", len(userIDs))
|
||||
logger.Infow("开始广播告警给所有用户", "totalUsers", len(userIDs))
|
||||
// 复用 SendBatchAlarm 的逻辑进行并发发送和错误处理
|
||||
return s.SendBatchAlarm(userIDs, content)
|
||||
return s.SendBatchAlarm(serviceCtx, userIDs, content)
|
||||
}
|
||||
|
||||
// sendAlarmToUser 是为单个用户发送告警的内部方法,包含了完整的故障转移逻辑
|
||||
func (s *failoverService) sendAlarmToUser(userID uint, content notify.AlarmContent) error {
|
||||
user, err := s.userRepo.FindByID(userID)
|
||||
func (s *failoverService) sendAlarmToUser(ctx context.Context, userID uint, content notify.AlarmContent) error {
|
||||
serviceCtx, logger := logs.Trace(ctx, s.ctx, "sendAlarmToUser")
|
||||
user, err := s.userRepo.FindByID(serviceCtx, userID)
|
||||
if err != nil {
|
||||
s.log.Errorw("发送告警失败:查找用户时出错", "userID", userID, "error", err)
|
||||
logger.Errorw("发送告警失败:查找用户时出错", "userID", userID, "error", err)
|
||||
return fmt.Errorf("查找用户失败: %w", err)
|
||||
}
|
||||
|
||||
@@ -132,50 +137,50 @@ func (s *failoverService) sendAlarmToUser(userID uint, content notify.AlarmConte
|
||||
addr := getAddressForNotifier(primaryType, user.Contact)
|
||||
if addr == "" {
|
||||
// 记录跳过通知
|
||||
s.recordNotificationAttempt(userID, primaryType, content, "", models.NotificationStatusSkipped, fmt.Errorf("用户未配置首选通知方式 '%s' 的地址", primaryType))
|
||||
s.recordNotificationAttempt(serviceCtx, userID, primaryType, content, "", models.NotificationStatusSkipped, fmt.Errorf("用户未配置首选通知方式 '%s' 的地址", primaryType))
|
||||
return fmt.Errorf("用户未配置首选通知方式 '%s' 的地址", primaryType)
|
||||
}
|
||||
|
||||
err = s.primaryNotifier.Send(content, addr)
|
||||
err = s.primaryNotifier.Send(serviceCtx, content, addr)
|
||||
if err == nil {
|
||||
// 记录成功通知
|
||||
s.recordNotificationAttempt(userID, primaryType, content, addr, models.NotificationStatusSuccess, nil)
|
||||
s.recordNotificationAttempt(serviceCtx, userID, primaryType, content, addr, models.NotificationStatusSuccess, nil)
|
||||
if failureCount > 0 {
|
||||
s.log.Infow("首选渠道发送恢复正常", "userID", userID, "notifierType", primaryType)
|
||||
logger.Infow("首选渠道发送恢复正常", "userID", userID, "notifierType", primaryType)
|
||||
s.failureCounters.Store(userID, 0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 记录失败通知
|
||||
s.recordNotificationAttempt(userID, primaryType, content, addr, models.NotificationStatusFailed, err)
|
||||
s.recordNotificationAttempt(serviceCtx, userID, primaryType, content, addr, models.NotificationStatusFailed, err)
|
||||
newFailureCount := failureCount + 1
|
||||
s.failureCounters.Store(userID, newFailureCount)
|
||||
s.log.Warnw("首选渠道发送失败", "userID", userID, "notifierType", primaryType, "error", err, "failureCount", newFailureCount)
|
||||
logger.Warnw("首选渠道发送失败", "userID", userID, "notifierType", primaryType, "error", err, "failureCount", newFailureCount)
|
||||
failureCount = newFailureCount
|
||||
}
|
||||
|
||||
if failureCount >= s.failureThreshold {
|
||||
s.log.Warnw("故障转移阈值已达到,开始广播通知", "userID", userID, "threshold", s.failureThreshold)
|
||||
logger.Warnw("故障转移阈值已达到,开始广播通知", "userID", userID, "threshold", s.failureThreshold)
|
||||
var lastErr error
|
||||
for _, notifier := range s.notifiers {
|
||||
addr := getAddressForNotifier(notifier.Type(), user.Contact)
|
||||
if addr == "" {
|
||||
// 记录跳过通知
|
||||
s.recordNotificationAttempt(userID, notifier.Type(), content, "", models.NotificationStatusSkipped, fmt.Errorf("用户未配置通知方式 '%s' 的地址", notifier.Type()))
|
||||
s.recordNotificationAttempt(serviceCtx, userID, notifier.Type(), content, "", models.NotificationStatusSkipped, fmt.Errorf("用户未配置通知方式 '%s' 的地址", notifier.Type()))
|
||||
continue
|
||||
}
|
||||
if err := notifier.Send(content, addr); err == nil {
|
||||
if err := notifier.Send(serviceCtx, content, addr); err == nil {
|
||||
// 记录成功通知
|
||||
s.recordNotificationAttempt(userID, notifier.Type(), content, addr, models.NotificationStatusSuccess, nil)
|
||||
s.log.Infow("广播通知成功", "userID", userID, "notifierType", notifier.Type())
|
||||
s.recordNotificationAttempt(serviceCtx, userID, notifier.Type(), content, addr, models.NotificationStatusSuccess, nil)
|
||||
logger.Infow("广播通知成功", "userID", userID, "notifierType", notifier.Type())
|
||||
s.failureCounters.Store(userID, 0)
|
||||
return nil
|
||||
}
|
||||
// 记录失败通知
|
||||
s.recordNotificationAttempt(userID, notifier.Type(), content, addr, models.NotificationStatusFailed, err)
|
||||
s.recordNotificationAttempt(serviceCtx, userID, notifier.Type(), content, addr, models.NotificationStatusFailed, err)
|
||||
lastErr = err
|
||||
s.log.Warnw("广播通知:渠道发送失败", "userID", userID, "notifierType", notifier.Type(), "error", err)
|
||||
logger.Warnw("广播通知:渠道发送失败", "userID", userID, "notifierType", notifier.Type(), "error", err)
|
||||
}
|
||||
return fmt.Errorf("所有渠道均发送失败,最后一个错误: %w", lastErr)
|
||||
}
|
||||
@@ -184,24 +189,25 @@ func (s *failoverService) sendAlarmToUser(userID uint, content notify.AlarmConte
|
||||
}
|
||||
|
||||
// SendTestMessage 实现了手动发送测试消息的功能
|
||||
func (s *failoverService) SendTestMessage(userID uint, notifierType notify.NotifierType) error {
|
||||
user, err := s.userRepo.FindByID(userID)
|
||||
func (s *failoverService) SendTestMessage(ctx context.Context, userID uint, notifierType notify.NotifierType) error {
|
||||
serviceCtx, logger := logs.Trace(ctx, s.ctx, "SendTestMessage")
|
||||
user, err := s.userRepo.FindByID(serviceCtx, userID)
|
||||
if err != nil {
|
||||
s.log.Errorw("发送测试消息失败:查找用户时出错", "userID", userID, "error", err)
|
||||
logger.Errorw("发送测试消息失败:查找用户时出错", "userID", userID, "error", err)
|
||||
return fmt.Errorf("查找用户失败: %w", err)
|
||||
}
|
||||
|
||||
notifier, ok := s.notifiers[notifierType]
|
||||
if !ok {
|
||||
s.log.Errorw("发送测试消息失败:通知器类型不存在", "userID", userID, "notifierType", notifierType)
|
||||
logger.Errorw("发送测试消息失败:通知器类型不存在", "userID", userID, "notifierType", notifierType)
|
||||
return fmt.Errorf("指定的通知器类型 '%s' 不存在", notifierType)
|
||||
}
|
||||
|
||||
addr := getAddressForNotifier(notifierType, user.Contact)
|
||||
if addr == "" {
|
||||
s.log.Warnw("发送测试消息失败:缺少地址", "userID", userID, "notifierType", notifierType)
|
||||
logger.Warnw("发送测试消息失败:缺少地址", "userID", userID, "notifierType", notifierType)
|
||||
// 记录跳过通知
|
||||
s.recordNotificationAttempt(userID, notifierType, notify.AlarmContent{
|
||||
s.recordNotificationAttempt(serviceCtx, userID, notifierType, notify.AlarmContent{
|
||||
Title: "通知服务测试",
|
||||
Message: fmt.Sprintf("这是一条来自【%s】渠道的测试消息。如果您收到此消息,说明您的配置正确。", notifierType),
|
||||
Level: zap.InfoLevel,
|
||||
@@ -217,18 +223,18 @@ func (s *failoverService) SendTestMessage(userID uint, notifierType notify.Notif
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
s.log.Infow("正在发送测试消息...", "userID", userID, "notifierType", notifierType, "address", addr)
|
||||
err = notifier.Send(testContent, addr)
|
||||
logger.Infow("正在发送测试消息...", "userID", userID, "notifierType", notifierType, "address", addr)
|
||||
err = notifier.Send(serviceCtx, testContent, addr)
|
||||
if err != nil {
|
||||
s.log.Errorw("发送测试消息失败", "userID", userID, "notifierType", notifierType, "error", err)
|
||||
logger.Errorw("发送测试消息失败", "userID", userID, "notifierType", notifierType, "error", err)
|
||||
// 记录失败通知
|
||||
s.recordNotificationAttempt(userID, notifierType, testContent, addr, models.NotificationStatusFailed, err)
|
||||
s.recordNotificationAttempt(serviceCtx, userID, notifierType, testContent, addr, models.NotificationStatusFailed, err)
|
||||
return err
|
||||
}
|
||||
|
||||
s.log.Infow("发送测试消息成功", "userID", userID, "notifierType", notifierType)
|
||||
logger.Infow("发送测试消息成功", "userID", userID, "notifierType", notifierType)
|
||||
// 记录成功通知
|
||||
s.recordNotificationAttempt(userID, notifierType, testContent, addr, models.NotificationStatusSuccess, nil)
|
||||
s.recordNotificationAttempt(serviceCtx, userID, notifierType, testContent, addr, models.NotificationStatusSuccess, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -256,6 +262,7 @@ func getAddressForNotifier(notifierType notify.NotifierType, contact models.Cont
|
||||
// status: 发送尝试的状态 (成功、失败、跳过)
|
||||
// err: 如果发送失败,记录的错误信息
|
||||
func (s *failoverService) recordNotificationAttempt(
|
||||
ctx context.Context,
|
||||
userID uint,
|
||||
notifierType notify.NotifierType,
|
||||
content notify.AlarmContent,
|
||||
@@ -263,6 +270,7 @@ func (s *failoverService) recordNotificationAttempt(
|
||||
status models.NotificationStatus,
|
||||
err error,
|
||||
) {
|
||||
serviceCtx, logger := logs.Trace(ctx, s.ctx, "recordNotificationAttempt")
|
||||
errorMessage := ""
|
||||
if err != nil {
|
||||
errorMessage = err.Error()
|
||||
@@ -280,8 +288,8 @@ func (s *failoverService) recordNotificationAttempt(
|
||||
ErrorMessage: errorMessage,
|
||||
}
|
||||
|
||||
if saveErr := s.notificationRepo.Create(notification); saveErr != nil {
|
||||
s.log.Errorw("无法保存通知发送记录到数据库",
|
||||
if saveErr := s.notificationRepo.Create(serviceCtx, notification); saveErr != nil {
|
||||
logger.Errorw("无法保存通知发送记录到数据库",
|
||||
"userID", userID,
|
||||
"notifierType", notifierType,
|
||||
"status", status,
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package pig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"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/repository"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -13,38 +16,40 @@ import (
|
||||
// 它是一个内部服务,被主服务 PigBatchService 调用。
|
||||
type PigPenTransferManager interface {
|
||||
// LogTransfer 在数据库中创建一条猪只迁移日志。
|
||||
LogTransfer(tx *gorm.DB, log *models.PigTransferLog) error
|
||||
LogTransfer(ctx context.Context, tx *gorm.DB, log *models.PigTransferLog) error
|
||||
|
||||
// GetPenByID 用于获取猪栏的详细信息,供上层服务进行业务校验。
|
||||
GetPenByID(tx *gorm.DB, penID uint) (*models.Pen, error)
|
||||
GetPenByID(ctx context.Context, tx *gorm.DB, penID uint) (*models.Pen, error)
|
||||
|
||||
// GetPensByBatchID 获取一个猪群当前关联的所有猪栏。
|
||||
GetPensByBatchID(tx *gorm.DB, batchID uint) ([]*models.Pen, error)
|
||||
GetPensByBatchID(ctx context.Context, tx *gorm.DB, batchID uint) ([]*models.Pen, error)
|
||||
|
||||
// UpdatePenFields 更新一个猪栏的指定字段。
|
||||
UpdatePenFields(tx *gorm.DB, penID uint, updates map[string]interface{}) error
|
||||
UpdatePenFields(ctx context.Context, tx *gorm.DB, penID uint, updates map[string]interface{}) error
|
||||
|
||||
// GetCurrentPigsInPen 通过汇总猪只迁移日志,计算给定猪栏中的当前猪只数量。
|
||||
GetCurrentPigsInPen(tx *gorm.DB, penID uint) (int, error)
|
||||
GetCurrentPigsInPen(ctx context.Context, tx *gorm.DB, penID uint) (int, error)
|
||||
|
||||
// GetTotalPigsInPensForBatchTx 计算指定猪群下所有猪栏的当前总存栏数
|
||||
GetTotalPigsInPensForBatchTx(tx *gorm.DB, batchID uint) (int, error)
|
||||
GetTotalPigsInPensForBatchTx(ctx context.Context, tx *gorm.DB, batchID uint) (int, error)
|
||||
|
||||
// ReleasePen 将猪栏的猪群归属移除,并将其状态标记为空闲。
|
||||
ReleasePen(tx *gorm.DB, penID uint) error
|
||||
ReleasePen(ctx context.Context, tx *gorm.DB, penID uint) error
|
||||
}
|
||||
|
||||
// pigPenTransferManager 是 PigPenTransferManager 接口的具体实现。
|
||||
// 它作为调栏管理器,处理底层的数据库交互。
|
||||
type pigPenTransferManager struct {
|
||||
ctx context.Context
|
||||
penRepo repository.PigPenRepository
|
||||
logRepo repository.PigTransferLogRepository
|
||||
pigBatchRepo repository.PigBatchRepository
|
||||
}
|
||||
|
||||
// NewPigPenTransferManager 是 pigPenTransferManager 的构造函数。
|
||||
func NewPigPenTransferManager(penRepo repository.PigPenRepository, logRepo repository.PigTransferLogRepository, pigBatchRepo repository.PigBatchRepository) PigPenTransferManager {
|
||||
func NewPigPenTransferManager(ctx context.Context, penRepo repository.PigPenRepository, logRepo repository.PigTransferLogRepository, pigBatchRepo repository.PigBatchRepository) PigPenTransferManager {
|
||||
return &pigPenTransferManager{
|
||||
ctx: ctx,
|
||||
penRepo: penRepo,
|
||||
logRepo: logRepo,
|
||||
pigBatchRepo: pigBatchRepo,
|
||||
@@ -52,29 +57,34 @@ func NewPigPenTransferManager(penRepo repository.PigPenRepository, logRepo repos
|
||||
}
|
||||
|
||||
// LogTransfer 实现了在数据库中创建迁移日志的逻辑。
|
||||
func (s *pigPenTransferManager) LogTransfer(tx *gorm.DB, log *models.PigTransferLog) error {
|
||||
return s.logRepo.CreatePigTransferLog(tx, log)
|
||||
func (s *pigPenTransferManager) LogTransfer(ctx context.Context, tx *gorm.DB, log *models.PigTransferLog) error {
|
||||
managerCtx := logs.AddFuncName(ctx, s.ctx, "LogTransfer")
|
||||
return s.logRepo.CreatePigTransferLog(managerCtx, tx, log)
|
||||
}
|
||||
|
||||
// GetPenByID 实现了获取猪栏信息的逻辑。
|
||||
func (s *pigPenTransferManager) GetPenByID(tx *gorm.DB, penID uint) (*models.Pen, error) {
|
||||
return s.penRepo.GetPenByIDTx(tx, penID)
|
||||
func (s *pigPenTransferManager) GetPenByID(ctx context.Context, tx *gorm.DB, penID uint) (*models.Pen, error) {
|
||||
managerCtx := logs.AddFuncName(ctx, s.ctx, "GetPenByID")
|
||||
return s.penRepo.GetPenByIDTx(managerCtx, tx, penID)
|
||||
}
|
||||
|
||||
// GetPensByBatchID 实现了获取猪群关联猪栏列表的逻辑。
|
||||
func (s *pigPenTransferManager) GetPensByBatchID(tx *gorm.DB, batchID uint) ([]*models.Pen, error) {
|
||||
return s.penRepo.GetPensByBatchIDTx(tx, batchID)
|
||||
func (s *pigPenTransferManager) GetPensByBatchID(ctx context.Context, tx *gorm.DB, batchID uint) ([]*models.Pen, error) {
|
||||
managerCtx := logs.AddFuncName(ctx, s.ctx, "GetPensByBatchID")
|
||||
return s.penRepo.GetPensByBatchIDTx(managerCtx, tx, batchID)
|
||||
}
|
||||
|
||||
// UpdatePenFields 实现了更新猪栏字段的逻辑。
|
||||
func (s *pigPenTransferManager) UpdatePenFields(tx *gorm.DB, penID uint, updates map[string]interface{}) error {
|
||||
return s.penRepo.UpdatePenFieldsTx(tx, penID, updates)
|
||||
func (s *pigPenTransferManager) UpdatePenFields(ctx context.Context, tx *gorm.DB, penID uint, updates map[string]interface{}) error {
|
||||
managerCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePenFields")
|
||||
return s.penRepo.UpdatePenFieldsTx(managerCtx, tx, penID, updates)
|
||||
}
|
||||
|
||||
// GetCurrentPigsInPen 实现了计算猪栏当前猪只数量的逻辑。
|
||||
func (s *pigPenTransferManager) GetCurrentPigsInPen(tx *gorm.DB, penID uint) (int, error) {
|
||||
func (s *pigPenTransferManager) GetCurrentPigsInPen(ctx context.Context, tx *gorm.DB, penID uint) (int, error) {
|
||||
managerCtx := logs.AddFuncName(ctx, s.ctx, "GetCurrentPigsInPen")
|
||||
// 1. 通过猪栏ID查出所属猪群信息
|
||||
pen, err := s.penRepo.GetPenByIDTx(tx, penID)
|
||||
pen, err := s.penRepo.GetPenByIDTx(managerCtx, tx, penID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, ErrPenNotFound
|
||||
@@ -89,7 +99,7 @@ func (s *pigPenTransferManager) GetCurrentPigsInPen(tx *gorm.DB, penID uint) (in
|
||||
currentBatchID := *pen.PigBatchID
|
||||
|
||||
// 2. 根据猪群ID获取猪群的起始日期
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, currentBatchID)
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(managerCtx, tx, currentBatchID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, ErrPigBatchNotFound
|
||||
@@ -99,7 +109,7 @@ func (s *pigPenTransferManager) GetCurrentPigsInPen(tx *gorm.DB, penID uint) (in
|
||||
batchStartDate := batch.StartDate
|
||||
|
||||
// 3. 调用仓库方法,获取从猪群开始至今,该猪栏的所有倒序日志
|
||||
logs, err := s.logRepo.GetLogsForPenSince(tx, penID, batchStartDate)
|
||||
logs, err := s.logRepo.GetLogsForPenSince(managerCtx, tx, penID, batchStartDate)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -127,9 +137,10 @@ func (s *pigPenTransferManager) GetCurrentPigsInPen(tx *gorm.DB, penID uint) (in
|
||||
|
||||
// GetTotalPigsInPensForBatchTx 计算指定猪群下所有猪栏的当前总存栏数
|
||||
// 该方法通过遍历猪群下的每个猪栏,并调用 GetCurrentPigsInPen 来累加存栏数。
|
||||
func (s *pigPenTransferManager) GetTotalPigsInPensForBatchTx(tx *gorm.DB, batchID uint) (int, error) {
|
||||
func (s *pigPenTransferManager) GetTotalPigsInPensForBatchTx(ctx context.Context, tx *gorm.DB, batchID uint) (int, error) {
|
||||
managerCtx := logs.AddFuncName(ctx, s.ctx, "GetTotalPigsInPensForBatchTx")
|
||||
// 1. 获取该批次下所有猪栏的列表
|
||||
pensInBatch, err := s.GetPensByBatchID(tx, batchID)
|
||||
pensInBatch, err := s.GetPensByBatchID(managerCtx, tx, batchID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("获取猪群 %d 下属猪栏失败: %w", batchID, err)
|
||||
}
|
||||
@@ -137,7 +148,7 @@ func (s *pigPenTransferManager) GetTotalPigsInPensForBatchTx(tx *gorm.DB, batchI
|
||||
totalPigs := 0
|
||||
// 2. 遍历每个猪栏,累加其存栏数
|
||||
for _, pen := range pensInBatch {
|
||||
pigsInPen, err := s.GetCurrentPigsInPen(tx, pen.ID)
|
||||
pigsInPen, err := s.GetCurrentPigsInPen(managerCtx, tx, pen.ID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("获取猪栏 %d 存栏数失败: %w", pen.ID, err)
|
||||
}
|
||||
@@ -149,9 +160,10 @@ func (s *pigPenTransferManager) GetTotalPigsInPensForBatchTx(tx *gorm.DB, batchI
|
||||
|
||||
// ReleasePen 将猪栏的猪群归属移除,并将其状态标记为空闲。
|
||||
// 此操作通常在猪栏被清空后调用。
|
||||
func (s *pigPenTransferManager) ReleasePen(tx *gorm.DB, penID uint) error {
|
||||
func (s *pigPenTransferManager) ReleasePen(ctx context.Context, tx *gorm.DB, penID uint) error {
|
||||
managerCtx := logs.AddFuncName(ctx, s.ctx, "ReleasePen")
|
||||
// 1. 获取猪栏信息
|
||||
pen, err := s.penRepo.GetPenByIDTx(tx, penID)
|
||||
pen, err := s.penRepo.GetPenByIDTx(managerCtx, tx, penID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fmt.Errorf("猪栏 %d 不存在: %w", penID, ErrPenNotFound)
|
||||
@@ -167,7 +179,7 @@ func (s *pigPenTransferManager) ReleasePen(tx *gorm.DB, penID uint) error {
|
||||
"status": models.PenStatusEmpty,
|
||||
}
|
||||
|
||||
if err := s.penRepo.UpdatePenFieldsTx(tx, penID, updates); err != nil {
|
||||
if err := s.penRepo.UpdatePenFieldsTx(managerCtx, tx, penID, updates); err != nil {
|
||||
return fmt.Errorf("释放猪栏 %v 失败: %w", pen.PenNumber, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package pig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
@@ -37,63 +38,64 @@ var (
|
||||
// 它抽象了所有与猪批次相关的操作,使得应用层可以依赖于此接口,而不是具体的实现。
|
||||
type PigBatchService interface {
|
||||
// CreatePigBatch 创建猪批次,并记录初始日志。
|
||||
CreatePigBatch(operatorID uint, batch *models.PigBatch) (*models.PigBatch, error)
|
||||
CreatePigBatch(ctx context.Context, operatorID uint, batch *models.PigBatch) (*models.PigBatch, error)
|
||||
// GetPigBatch 获取单个猪批次。
|
||||
GetPigBatch(id uint) (*models.PigBatch, error)
|
||||
GetPigBatch(ctx context.Context, id uint) (*models.PigBatch, error)
|
||||
// UpdatePigBatch 更新猪批次信息。
|
||||
UpdatePigBatch(batch *models.PigBatch) (*models.PigBatch, error)
|
||||
UpdatePigBatch(ctx context.Context, batch *models.PigBatch) (*models.PigBatch, error)
|
||||
// DeletePigBatch 删除猪批次,包含业务规则校验。
|
||||
DeletePigBatch(id uint) error
|
||||
DeletePigBatch(ctx context.Context, id uint) error
|
||||
// ListPigBatches 批量查询猪批次。
|
||||
ListPigBatches(isActive *bool) ([]*models.PigBatch, error)
|
||||
ListPigBatches(ctx context.Context, isActive *bool) ([]*models.PigBatch, error)
|
||||
// AssignEmptyPensToBatch 为猪群分配空栏
|
||||
AssignEmptyPensToBatch(batchID uint, penIDs []uint, operatorID uint) error
|
||||
AssignEmptyPensToBatch(ctx context.Context, batchID uint, penIDs []uint, operatorID uint) error
|
||||
// MovePigsIntoPen 将猪只从“虚拟库存”移入指定猪栏
|
||||
MovePigsIntoPen(batchID uint, toPenID uint, quantity int, operatorID uint, remarks string) error
|
||||
MovePigsIntoPen(ctx context.Context, batchID uint, toPenID uint, quantity int, operatorID uint, remarks string) error
|
||||
// ReclassifyPenToNewBatch 连猪带栏,整体划拨到另一个猪群
|
||||
ReclassifyPenToNewBatch(fromBatchID uint, toBatchID uint, penID uint, operatorID uint, remarks string) error
|
||||
ReclassifyPenToNewBatch(ctx context.Context, fromBatchID uint, toBatchID uint, penID uint, operatorID uint, remarks string) error
|
||||
// RemoveEmptyPenFromBatch 将一个猪栏移除出猪群,此方法需要在猪栏为空的情况下执行。
|
||||
RemoveEmptyPenFromBatch(batchID uint, penID uint) error
|
||||
RemoveEmptyPenFromBatch(ctx context.Context, batchID uint, penID uint) error
|
||||
|
||||
// GetCurrentPigQuantity 获取指定猪批次的当前猪只数量。
|
||||
GetCurrentPigQuantity(batchID uint) (int, error)
|
||||
GetCurrentPigQuantity(ctx context.Context, batchID uint) (int, error)
|
||||
// GetCurrentPigsInPen 获取指定猪栏的当前存栏量。
|
||||
GetCurrentPigsInPen(penID uint) (int, error)
|
||||
GetCurrentPigsInPen(ctx context.Context, penID uint) (int, error)
|
||||
// GetTotalPigsInPensForBatch 获取指定猪群下所有猪栏的当前总存栏数
|
||||
GetTotalPigsInPensForBatch(batchID uint) (int, error)
|
||||
GetTotalPigsInPensForBatch(ctx context.Context, batchID uint) (int, error)
|
||||
|
||||
UpdatePigBatchQuantity(operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error
|
||||
UpdatePigBatchQuantity(ctx context.Context, operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error
|
||||
|
||||
// ---交易子服务---
|
||||
// SellPigs 处理卖猪的业务逻辑。
|
||||
SellPigs(batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error
|
||||
SellPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error
|
||||
// BuyPigs 处理买猪的业务逻辑。
|
||||
BuyPigs(batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error
|
||||
BuyPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error
|
||||
|
||||
// ---调栏子服务 ---
|
||||
TransferPigsAcrossBatches(sourceBatchID uint, destBatchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error
|
||||
TransferPigsWithinBatch(batchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error
|
||||
TransferPigsAcrossBatches(ctx context.Context, sourceBatchID uint, destBatchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error
|
||||
TransferPigsWithinBatch(ctx context.Context, batchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error
|
||||
|
||||
// --- 病猪管理相关方法 ---
|
||||
// RecordSickPigs 记录新增病猪事件。
|
||||
RecordSickPigs(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error
|
||||
RecordSickPigs(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error
|
||||
// RecordSickPigRecovery 记录病猪康复事件。
|
||||
RecordSickPigRecovery(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error
|
||||
RecordSickPigRecovery(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error
|
||||
// RecordSickPigDeath 记录病猪死亡事件。
|
||||
RecordSickPigDeath(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error
|
||||
RecordSickPigDeath(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error
|
||||
// RecordSickPigCull 记录病猪淘汰事件。
|
||||
RecordSickPigCull(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error
|
||||
RecordSickPigCull(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error
|
||||
|
||||
// --- 正常猪只管理相关方法 ---
|
||||
// RecordDeath 记录正常猪只死亡事件。
|
||||
RecordDeath(operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error
|
||||
RecordDeath(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error
|
||||
// RecordCull 记录正常猪只淘汰事件。
|
||||
RecordCull(operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error
|
||||
RecordCull(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error
|
||||
}
|
||||
|
||||
// pigBatchService 是 PigBatchService 接口的具体实现。
|
||||
// 它作为猪群领域的主服务,封装了所有业务逻辑。
|
||||
type pigBatchService struct {
|
||||
ctx context.Context
|
||||
pigBatchRepo repository.PigBatchRepository // 猪批次仓库
|
||||
pigBatchLogRepo repository.PigBatchLogRepository // 猪批次日志仓库
|
||||
uow repository.UnitOfWork // 工作单元,用于管理事务
|
||||
@@ -105,6 +107,7 @@ type pigBatchService struct {
|
||||
// NewPigBatchService 是 pigBatchService 的构造函数。
|
||||
// 它通过依赖注入的方式,创建并返回一个 PigBatchService 接口的实例。
|
||||
func NewPigBatchService(
|
||||
ctx context.Context,
|
||||
pigBatchRepo repository.PigBatchRepository,
|
||||
pigBatchLogRepo repository.PigBatchLogRepository,
|
||||
uow repository.UnitOfWork,
|
||||
@@ -113,6 +116,7 @@ func NewPigBatchService(
|
||||
sickSvc SickPigManager,
|
||||
) PigBatchService {
|
||||
return &pigBatchService{
|
||||
ctx: ctx,
|
||||
pigBatchRepo: pigBatchRepo,
|
||||
pigBatchLogRepo: pigBatchLogRepo,
|
||||
uow: uow,
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
package pig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// --- 领域服务实现 ---
|
||||
|
||||
// CreatePigBatch 实现了创建猪批次的逻辑,并同时创建初始批次日志。
|
||||
func (s *pigBatchService) CreatePigBatch(operatorID uint, batch *models.PigBatch) (*models.PigBatch, error) {
|
||||
func (s *pigBatchService) CreatePigBatch(ctx context.Context, operatorID uint, batch *models.PigBatch) (*models.PigBatch, error) {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreatePigBatch")
|
||||
// 业务规则可以在这里添加,例如检查批次号是否唯一等
|
||||
|
||||
var createdBatch *models.PigBatch
|
||||
err := s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
||||
err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||
// 1. 创建猪批次
|
||||
// 注意: 此处依赖一个假设存在的 pigBatchRepo.CreatePigBatchTx 方法
|
||||
var err error
|
||||
createdBatch, err = s.pigBatchRepo.CreatePigBatchTx(tx, batch)
|
||||
createdBatch, err = s.pigBatchRepo.CreatePigBatchTx(serviceCtx, tx, batch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建猪批次失败: %w", err)
|
||||
}
|
||||
@@ -38,7 +42,7 @@ func (s *pigBatchService) CreatePigBatch(operatorID uint, batch *models.PigBatch
|
||||
}
|
||||
|
||||
// 3. 记录批次日志
|
||||
if err := s.pigBatchLogRepo.CreateTx(tx, initialLog); err != nil {
|
||||
if err := s.pigBatchLogRepo.CreateTx(serviceCtx, tx, initialLog); err != nil {
|
||||
return fmt.Errorf("记录初始批次日志失败: %w", err)
|
||||
}
|
||||
|
||||
@@ -53,8 +57,9 @@ func (s *pigBatchService) CreatePigBatch(operatorID uint, batch *models.PigBatch
|
||||
}
|
||||
|
||||
// GetPigBatch 实现了获取单个猪批次的逻辑。
|
||||
func (s *pigBatchService) GetPigBatch(id uint) (*models.PigBatch, error) {
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByID(id)
|
||||
func (s *pigBatchService) GetPigBatch(ctx context.Context, id uint) (*models.PigBatch, error) {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetPigBatch")
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByID(serviceCtx, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, ErrPigBatchNotFound
|
||||
@@ -65,9 +70,10 @@ func (s *pigBatchService) GetPigBatch(id uint) (*models.PigBatch, error) {
|
||||
}
|
||||
|
||||
// UpdatePigBatch 实现了更新猪批次的逻辑。
|
||||
func (s *pigBatchService) UpdatePigBatch(batch *models.PigBatch) (*models.PigBatch, error) {
|
||||
func (s *pigBatchService) UpdatePigBatch(ctx context.Context, batch *models.PigBatch) (*models.PigBatch, error) {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePigBatch")
|
||||
// 可以在这里添加更新前的业务校验
|
||||
updatedBatch, rowsAffected, err := s.pigBatchRepo.UpdatePigBatch(batch)
|
||||
updatedBatch, rowsAffected, err := s.pigBatchRepo.UpdatePigBatch(serviceCtx, batch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -78,10 +84,11 @@ func (s *pigBatchService) UpdatePigBatch(batch *models.PigBatch) (*models.PigBat
|
||||
}
|
||||
|
||||
// DeletePigBatch 实现了删除猪批次的逻辑,并包含业务规则校验。
|
||||
func (s *pigBatchService) DeletePigBatch(id uint) error {
|
||||
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
||||
func (s *pigBatchService) DeletePigBatch(ctx context.Context, id uint) error {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeletePigBatch")
|
||||
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||
// 1. 获取猪批次信息
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, id) // 使用事务内方法
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, id) // 使用事务内方法
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrPigBatchNotFound
|
||||
@@ -96,20 +103,20 @@ func (s *pigBatchService) DeletePigBatch(id uint) error {
|
||||
|
||||
// 3. 释放所有关联的猪栏
|
||||
// 获取该批次下所有猪栏
|
||||
pensInBatch, err := s.transferSvc.GetPensByBatchID(tx, id)
|
||||
pensInBatch, err := s.transferSvc.GetPensByBatchID(serviceCtx, tx, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取猪批次 %d 关联猪栏失败: %w", id, err)
|
||||
}
|
||||
|
||||
// 逐一释放猪栏
|
||||
for _, pen := range pensInBatch {
|
||||
if err := s.transferSvc.ReleasePen(tx, pen.ID); err != nil {
|
||||
if err := s.transferSvc.ReleasePen(serviceCtx, tx, pen.ID); err != nil {
|
||||
return fmt.Errorf("释放猪栏 %d 失败: %w", pen.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 执行删除猪批次
|
||||
rowsAffected, err := s.pigBatchRepo.DeletePigBatchTx(tx, id)
|
||||
rowsAffected, err := s.pigBatchRepo.DeletePigBatchTx(serviceCtx, tx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -122,16 +129,18 @@ func (s *pigBatchService) DeletePigBatch(id uint) error {
|
||||
}
|
||||
|
||||
// ListPigBatches 实现了批量查询猪批次的逻辑。
|
||||
func (s *pigBatchService) ListPigBatches(isActive *bool) ([]*models.PigBatch, error) {
|
||||
return s.pigBatchRepo.ListPigBatches(isActive)
|
||||
func (s *pigBatchService) ListPigBatches(ctx context.Context, isActive *bool) ([]*models.PigBatch, error) {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListPigBatches")
|
||||
return s.pigBatchRepo.ListPigBatches(serviceCtx, isActive)
|
||||
}
|
||||
|
||||
// GetCurrentPigQuantity 实现了获取指定猪批次的当前猪只数量的逻辑。
|
||||
func (s *pigBatchService) GetCurrentPigQuantity(batchID uint) (int, error) {
|
||||
func (s *pigBatchService) GetCurrentPigQuantity(ctx context.Context, batchID uint) (int, error) {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetCurrentPigQuantity")
|
||||
var getErr error
|
||||
var quantity int
|
||||
err := s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
||||
quantity, getErr = s.getCurrentPigQuantityTx(tx, batchID)
|
||||
err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||
quantity, getErr = s.getCurrentPigQuantityTx(serviceCtx, tx, batchID)
|
||||
return getErr
|
||||
})
|
||||
if err != nil {
|
||||
@@ -141,9 +150,10 @@ func (s *pigBatchService) GetCurrentPigQuantity(batchID uint) (int, error) {
|
||||
}
|
||||
|
||||
// getCurrentPigQuantityTx 实现了获取指定猪批次的当前猪只数量的逻辑。
|
||||
func (s *pigBatchService) getCurrentPigQuantityTx(tx *gorm.DB, batchID uint) (int, error) {
|
||||
func (s *pigBatchService) getCurrentPigQuantityTx(ctx context.Context, tx *gorm.DB, batchID uint) (int, error) {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "getCurrentPigQuantityTx")
|
||||
// 1. 获取猪批次初始信息
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID)
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, batchID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, ErrPigBatchNotFound
|
||||
@@ -152,7 +162,7 @@ func (s *pigBatchService) getCurrentPigQuantityTx(tx *gorm.DB, batchID uint) (in
|
||||
}
|
||||
|
||||
// 2. 尝试获取该批次的最后一条日志记录
|
||||
lastLog, err := s.pigBatchLogRepo.GetLastLogByBatchIDTx(tx, batchID)
|
||||
lastLog, err := s.pigBatchLogRepo.GetLastLogByBatchIDTx(serviceCtx, tx, batchID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// 如果没有找到任何日志记录(除了初始创建),则当前数量就是初始数量
|
||||
@@ -165,14 +175,16 @@ func (s *pigBatchService) getCurrentPigQuantityTx(tx *gorm.DB, batchID uint) (in
|
||||
return lastLog.AfterCount, nil
|
||||
}
|
||||
|
||||
func (s *pigBatchService) UpdatePigBatchQuantity(operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error {
|
||||
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
||||
return s.updatePigBatchQuantityTx(tx, operatorID, batchID, changeType, changeAmount, changeReason, happenedAt)
|
||||
func (s *pigBatchService) UpdatePigBatchQuantity(ctx context.Context, operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePigBatchQuantity")
|
||||
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||
return s.updatePigBatchQuantityTx(serviceCtx, tx, operatorID, batchID, changeType, changeAmount, changeReason, happenedAt)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *pigBatchService) updatePigBatchQuantityTx(tx *gorm.DB, operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error {
|
||||
lastLog, err := s.pigBatchLogRepo.GetLastLogByBatchIDTx(tx, batchID)
|
||||
func (s *pigBatchService) updatePigBatchQuantityTx(ctx context.Context, tx *gorm.DB, operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "updatePigBatchQuantityTx")
|
||||
lastLog, err := s.pigBatchLogRepo.GetLastLogByBatchIDTx(serviceCtx, tx, batchID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -192,5 +204,5 @@ func (s *pigBatchService) updatePigBatchQuantityTx(tx *gorm.DB, operatorID uint,
|
||||
OperatorID: operatorID,
|
||||
HappenedAt: happenedAt,
|
||||
}
|
||||
return s.pigBatchLogRepo.CreateTx(tx, pigBatchLog)
|
||||
return s.pigBatchLogRepo.CreateTx(serviceCtx, tx, pigBatchLog)
|
||||
}
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
package pig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// executeTransferAndLog 是一个私有辅助方法,用于封装创建和记录迁移日志的通用逻辑。
|
||||
func (s *pigBatchService) executeTransferAndLog(tx *gorm.DB, fromBatchID, toBatchID, fromPenID, toPenID uint, quantity int, transferType models.PigTransferType, operatorID uint, remarks string) error {
|
||||
func (s *pigBatchService) executeTransferAndLog(ctx context.Context, tx *gorm.DB, fromBatchID, toBatchID, fromPenID, toPenID uint, quantity int, transferType models.PigTransferType, operatorID uint, remarks string) error {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "executeTransferAndLog")
|
||||
|
||||
// 通用校验:任何调出操作都不能超过源猪栏的当前存栏数
|
||||
if quantity < 0 { // 当调出时才需要检查
|
||||
currentPigsInFromPen, err := s.transferSvc.GetCurrentPigsInPen(tx, fromPenID)
|
||||
currentPigsInFromPen, err := s.transferSvc.GetCurrentPigsInPen(serviceCtx, tx, fromPenID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取源猪栏 %d 当前猪只数失败: %w", fromPenID, err)
|
||||
}
|
||||
@@ -51,10 +56,10 @@ func (s *pigBatchService) executeTransferAndLog(tx *gorm.DB, fromBatchID, toBatc
|
||||
}
|
||||
|
||||
// 4. 调用子服务记录日志
|
||||
if err := s.transferSvc.LogTransfer(tx, logOut); err != nil {
|
||||
if err := s.transferSvc.LogTransfer(serviceCtx, tx, logOut); err != nil {
|
||||
return fmt.Errorf("记录调出日志失败: %w", err)
|
||||
}
|
||||
if err := s.transferSvc.LogTransfer(tx, logIn); err != nil {
|
||||
if err := s.transferSvc.LogTransfer(serviceCtx, tx, logIn); err != nil {
|
||||
return fmt.Errorf("记录调入日志失败: %w", err)
|
||||
}
|
||||
|
||||
@@ -62,7 +67,8 @@ func (s *pigBatchService) executeTransferAndLog(tx *gorm.DB, fromBatchID, toBatc
|
||||
}
|
||||
|
||||
// TransferPigsWithinBatch 实现了同一个猪群内部的调栏业务。
|
||||
func (s *pigBatchService) TransferPigsWithinBatch(batchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error {
|
||||
func (s *pigBatchService) TransferPigsWithinBatch(ctx context.Context, batchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "TransferPigsWithinBatch")
|
||||
if fromPenID == toPenID {
|
||||
return errors.New("源猪栏和目标猪栏不能相同")
|
||||
}
|
||||
@@ -70,13 +76,13 @@ func (s *pigBatchService) TransferPigsWithinBatch(batchID uint, fromPenID uint,
|
||||
return errors.New("迁移数量不能为零")
|
||||
}
|
||||
|
||||
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
||||
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||
// 1. 核心业务规则校验
|
||||
fromPen, err := s.transferSvc.GetPenByID(tx, fromPenID)
|
||||
fromPen, err := s.transferSvc.GetPenByID(serviceCtx, tx, fromPenID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取源猪栏信息失败: %w", err)
|
||||
}
|
||||
toPen, err := s.transferSvc.GetPenByID(tx, toPenID)
|
||||
toPen, err := s.transferSvc.GetPenByID(serviceCtx, tx, toPenID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取目标猪栏信息失败: %w", err)
|
||||
}
|
||||
@@ -89,7 +95,7 @@ func (s *pigBatchService) TransferPigsWithinBatch(batchID uint, fromPenID uint,
|
||||
}
|
||||
|
||||
// 2. 调用通用辅助方法执行日志记录
|
||||
err = s.executeTransferAndLog(tx, batchID, batchID, fromPenID, toPenID, int(quantity), "群内调栏", operatorID, remarks)
|
||||
err = s.executeTransferAndLog(serviceCtx, tx, batchID, batchID, fromPenID, toPenID, int(quantity), "群内调栏", operatorID, remarks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -100,7 +106,8 @@ func (s *pigBatchService) TransferPigsWithinBatch(batchID uint, fromPenID uint,
|
||||
}
|
||||
|
||||
// TransferPigsAcrossBatches 实现了跨猪群的调栏业务。
|
||||
func (s *pigBatchService) TransferPigsAcrossBatches(sourceBatchID uint, destBatchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error {
|
||||
func (s *pigBatchService) TransferPigsAcrossBatches(ctx context.Context, sourceBatchID uint, destBatchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "TransferPigsAcrossBatches")
|
||||
if sourceBatchID == destBatchID {
|
||||
return errors.New("源猪群和目标猪群不能相同")
|
||||
}
|
||||
@@ -108,16 +115,16 @@ func (s *pigBatchService) TransferPigsAcrossBatches(sourceBatchID uint, destBatc
|
||||
return errors.New("迁移数量不能为零")
|
||||
}
|
||||
|
||||
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
||||
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||
// 1. 核心业务规则校验
|
||||
// 1.1 校验猪群存在
|
||||
if _, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, sourceBatchID); err != nil {
|
||||
if _, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, sourceBatchID); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fmt.Errorf("源猪群 %d 不存在", sourceBatchID)
|
||||
}
|
||||
return fmt.Errorf("获取源猪群信息失败: %w", err)
|
||||
}
|
||||
if _, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, destBatchID); err != nil {
|
||||
if _, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, destBatchID); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fmt.Errorf("目标猪群 %d 不存在", destBatchID)
|
||||
}
|
||||
@@ -125,7 +132,7 @@ func (s *pigBatchService) TransferPigsAcrossBatches(sourceBatchID uint, destBatc
|
||||
}
|
||||
|
||||
// 1.2 校验猪栏归属
|
||||
fromPen, err := s.transferSvc.GetPenByID(tx, fromPenID)
|
||||
fromPen, err := s.transferSvc.GetPenByID(serviceCtx, tx, fromPenID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取源猪栏信息失败: %w", err)
|
||||
}
|
||||
@@ -134,7 +141,7 @@ func (s *pigBatchService) TransferPigsAcrossBatches(sourceBatchID uint, destBatc
|
||||
}
|
||||
|
||||
// 2. 调用通用辅助方法执行猪只物理转移的日志记录
|
||||
err = s.executeTransferAndLog(tx, sourceBatchID, destBatchID, fromPenID, toPenID, int(quantity), "跨群调栏", operatorID, remarks)
|
||||
err = s.executeTransferAndLog(serviceCtx, tx, sourceBatchID, destBatchID, fromPenID, toPenID, int(quantity), "跨群调栏", operatorID, remarks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -143,14 +150,14 @@ func (s *pigBatchService) TransferPigsAcrossBatches(sourceBatchID uint, destBatc
|
||||
now := time.Now()
|
||||
// 3.1 记录源猪群数量减少
|
||||
reasonOut := fmt.Sprintf("跨群调栏: %d头猪从批次 %d 调出至批次 %d。备注: %s", quantity, sourceBatchID, destBatchID, remarks)
|
||||
err = s.updatePigBatchQuantityTx(tx, operatorID, sourceBatchID, models.ChangeTypeTransferOut, -int(quantity), reasonOut, now)
|
||||
err = s.updatePigBatchQuantityTx(serviceCtx, tx, operatorID, sourceBatchID, models.ChangeTypeTransferOut, -int(quantity), reasonOut, now)
|
||||
if err != nil {
|
||||
return fmt.Errorf("更新源猪群 %d 数量失败: %w", sourceBatchID, err)
|
||||
}
|
||||
|
||||
// 3.2 记录目标猪群数量增加
|
||||
reasonIn := fmt.Sprintf("跨群调栏: %d头猪从批次 %d 调入。备注: %s", quantity, sourceBatchID, remarks)
|
||||
err = s.updatePigBatchQuantityTx(tx, operatorID, destBatchID, models.ChangeTypeTransferIn, int(quantity), reasonIn, now)
|
||||
err = s.updatePigBatchQuantityTx(serviceCtx, tx, operatorID, destBatchID, models.ChangeTypeTransferIn, int(quantity), reasonIn, now)
|
||||
if err != nil {
|
||||
return fmt.Errorf("更新目标猪群 %d 数量失败: %w", destBatchID, err)
|
||||
}
|
||||
@@ -160,10 +167,11 @@ func (s *pigBatchService) TransferPigsAcrossBatches(sourceBatchID uint, destBatc
|
||||
}
|
||||
|
||||
// AssignEmptyPensToBatch 为猪群分配空栏
|
||||
func (s *pigBatchService) AssignEmptyPensToBatch(batchID uint, penIDs []uint, operatorID uint) error {
|
||||
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
||||
func (s *pigBatchService) AssignEmptyPensToBatch(ctx context.Context, batchID uint, penIDs []uint, operatorID uint) error {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "AssignEmptyPensToBatch")
|
||||
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||
// 1. 验证猪批次是否存在且活跃
|
||||
pigBatch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID)
|
||||
pigBatch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, batchID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrPigBatchNotFound
|
||||
@@ -176,7 +184,7 @@ func (s *pigBatchService) AssignEmptyPensToBatch(batchID uint, penIDs []uint, op
|
||||
|
||||
// 2. 遍历并校验每一个待分配的猪栏
|
||||
for _, penID := range penIDs {
|
||||
pen, err := s.transferSvc.GetPenByID(tx, penID)
|
||||
pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fmt.Errorf("猪栏 %d 不存在: %w", penID, ErrPenNotFound)
|
||||
@@ -197,7 +205,7 @@ func (s *pigBatchService) AssignEmptyPensToBatch(batchID uint, penIDs []uint, op
|
||||
"pig_batch_id": &batchID,
|
||||
"status": models.PenStatusOccupied,
|
||||
}
|
||||
if err := s.transferSvc.UpdatePenFields(tx, penID, updates); err != nil {
|
||||
if err := s.transferSvc.UpdatePenFields(serviceCtx, tx, penID, updates); err != nil {
|
||||
return fmt.Errorf("分配猪栏 %d 失败: %w", penID, err)
|
||||
}
|
||||
}
|
||||
@@ -207,14 +215,15 @@ func (s *pigBatchService) AssignEmptyPensToBatch(batchID uint, penIDs []uint, op
|
||||
}
|
||||
|
||||
// MovePigsIntoPen 将猪只从“虚拟库存”移入指定猪栏
|
||||
func (s *pigBatchService) MovePigsIntoPen(batchID uint, toPenID uint, quantity int, operatorID uint, remarks string) error {
|
||||
func (s *pigBatchService) MovePigsIntoPen(ctx context.Context, batchID uint, toPenID uint, quantity int, operatorID uint, remarks string) error {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "MovePigsIntoPen")
|
||||
if quantity <= 0 {
|
||||
return errors.New("迁移数量必须大于零")
|
||||
}
|
||||
|
||||
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
||||
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||
// 1. 验证猪批次是否存在且活跃
|
||||
pigBatch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID)
|
||||
pigBatch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, batchID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrPigBatchNotFound
|
||||
@@ -226,7 +235,7 @@ func (s *pigBatchService) MovePigsIntoPen(batchID uint, toPenID uint, quantity i
|
||||
}
|
||||
|
||||
// 2. 校验目标猪栏
|
||||
toPen, err := s.transferSvc.GetPenByID(tx, toPenID)
|
||||
toPen, err := s.transferSvc.GetPenByID(serviceCtx, tx, toPenID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fmt.Errorf("目标猪栏 %d 不存在: %w", toPenID, ErrPenNotFound)
|
||||
@@ -243,13 +252,13 @@ func (s *pigBatchService) MovePigsIntoPen(batchID uint, toPenID uint, quantity i
|
||||
}
|
||||
|
||||
// 3. 校验猪群中有足够的“未分配”猪只
|
||||
currentBatchTotal, err := s.getCurrentPigQuantityTx(tx, batchID)
|
||||
currentBatchTotal, err := s.getCurrentPigQuantityTx(serviceCtx, tx, batchID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取猪群 %d 当前总数量失败: %w", batchID, err)
|
||||
}
|
||||
|
||||
// 获取该批次下所有猪栏的当前总存栏数
|
||||
totalPigsInPens, err := s.transferSvc.GetTotalPigsInPensForBatchTx(tx, batchID)
|
||||
totalPigsInPens, err := s.transferSvc.GetTotalPigsInPensForBatchTx(serviceCtx, tx, batchID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("计算猪群 %d 下属猪栏总存栏失败: %w", batchID, err)
|
||||
}
|
||||
@@ -269,7 +278,7 @@ func (s *pigBatchService) MovePigsIntoPen(batchID uint, toPenID uint, quantity i
|
||||
OperatorID: operatorID,
|
||||
Remarks: remarks,
|
||||
}
|
||||
if err := s.transferSvc.LogTransfer(tx, logIn); err != nil {
|
||||
if err := s.transferSvc.LogTransfer(serviceCtx, tx, logIn); err != nil {
|
||||
return fmt.Errorf("记录入栏日志失败: %w", err)
|
||||
}
|
||||
|
||||
@@ -278,22 +287,23 @@ func (s *pigBatchService) MovePigsIntoPen(batchID uint, toPenID uint, quantity i
|
||||
}
|
||||
|
||||
// ReclassifyPenToNewBatch 连猪带栏,整体划拨到另一个猪群
|
||||
func (s *pigBatchService) ReclassifyPenToNewBatch(fromBatchID uint, toBatchID uint, penID uint, operatorID uint, remarks string) error {
|
||||
func (s *pigBatchService) ReclassifyPenToNewBatch(ctx context.Context, fromBatchID uint, toBatchID uint, penID uint, operatorID uint, remarks string) error {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "ReclassifyPenToNewBatch")
|
||||
if fromBatchID == toBatchID {
|
||||
return errors.New("源猪群和目标猪群不能相同")
|
||||
}
|
||||
|
||||
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
||||
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||
// 1. 核心业务规则校验
|
||||
// 1.1 校验猪群存在
|
||||
fromBatch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, fromBatchID)
|
||||
fromBatch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, fromBatchID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fmt.Errorf("源猪群 %d 不存在", fromBatchID)
|
||||
}
|
||||
return fmt.Errorf("获取源猪群信息失败: %w", err)
|
||||
}
|
||||
toBatch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, toBatchID)
|
||||
toBatch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, toBatchID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fmt.Errorf("目标猪群 %d 不存在", toBatchID)
|
||||
@@ -302,7 +312,7 @@ func (s *pigBatchService) ReclassifyPenToNewBatch(fromBatchID uint, toBatchID ui
|
||||
}
|
||||
|
||||
// 1.2 校验猪栏归属
|
||||
pen, err := s.transferSvc.GetPenByID(tx, penID)
|
||||
pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fmt.Errorf("猪栏 %d 不存在: %w", penID, ErrPenNotFound)
|
||||
@@ -314,7 +324,7 @@ func (s *pigBatchService) ReclassifyPenToNewBatch(fromBatchID uint, toBatchID ui
|
||||
}
|
||||
|
||||
// 2. 获取猪栏当前存栏数
|
||||
quantity, err := s.transferSvc.GetCurrentPigsInPen(tx, penID)
|
||||
quantity, err := s.transferSvc.GetCurrentPigsInPen(serviceCtx, tx, penID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取猪栏 %v 存栏数失败: %w", pen.PenNumber, err)
|
||||
}
|
||||
@@ -323,7 +333,7 @@ func (s *pigBatchService) ReclassifyPenToNewBatch(fromBatchID uint, toBatchID ui
|
||||
updates := map[string]interface{}{
|
||||
"pig_batch_id": &toBatchID,
|
||||
}
|
||||
if err := s.transferSvc.UpdatePenFields(tx, penID, updates); err != nil {
|
||||
if err := s.transferSvc.UpdatePenFields(serviceCtx, tx, penID, updates); err != nil {
|
||||
return fmt.Errorf("更新猪栏 %v 归属失败: %w", pen.PenNumber, err)
|
||||
}
|
||||
// 如果猪栏是空的,则只进行归属变更,不影响猪群数量
|
||||
@@ -343,7 +353,7 @@ func (s *pigBatchService) ReclassifyPenToNewBatch(fromBatchID uint, toBatchID ui
|
||||
OperatorID: operatorID,
|
||||
Remarks: fmt.Sprintf("整栏划拨迁出: %d头猪从批次 %v 随猪栏 %v 划拨至批次 %v。备注: %s", quantity, fromBatch.BatchNumber, pen.PenNumber, toBatch.BatchNumber, remarks),
|
||||
}
|
||||
if err := s.transferSvc.LogTransfer(tx, logOut); err != nil {
|
||||
if err := s.transferSvc.LogTransfer(serviceCtx, tx, logOut); err != nil {
|
||||
return fmt.Errorf("记录猪栏 %d 迁出日志失败: %w", penID, err)
|
||||
}
|
||||
|
||||
@@ -358,7 +368,7 @@ func (s *pigBatchService) ReclassifyPenToNewBatch(fromBatchID uint, toBatchID ui
|
||||
OperatorID: operatorID,
|
||||
Remarks: fmt.Sprintf("整栏划拨迁入: %v头猪随猪栏 %v 从批次 %v 划拨入。备注: %s", quantity, fromBatch.BatchNumber, pen.PenNumber, remarks),
|
||||
}
|
||||
if err := s.transferSvc.LogTransfer(tx, logIn); err != nil {
|
||||
if err := s.transferSvc.LogTransfer(serviceCtx, tx, logIn); err != nil {
|
||||
return fmt.Errorf("记录猪栏 %d 迁入日志失败: %w", penID, err)
|
||||
}
|
||||
|
||||
@@ -366,14 +376,14 @@ func (s *pigBatchService) ReclassifyPenToNewBatch(fromBatchID uint, toBatchID ui
|
||||
now := time.Now()
|
||||
// 7.1 记录源猪群数量减少
|
||||
reasonOutBatch := fmt.Sprintf("整栏划拨: %d头猪随猪栏 %v 从批次 %v 划拨至批次 %v。备注: %s", quantity, pen.PenNumber, fromBatch.BatchNumber, toBatchID, remarks)
|
||||
err = s.updatePigBatchQuantityTx(tx, operatorID, fromBatchID, models.ChangeTypeTransferOut, -quantity, reasonOutBatch, now)
|
||||
err = s.updatePigBatchQuantityTx(serviceCtx, tx, operatorID, fromBatchID, models.ChangeTypeTransferOut, -quantity, reasonOutBatch, now)
|
||||
if err != nil {
|
||||
return fmt.Errorf("更新源猪群 %v 数量失败: %w", fromBatch.BatchNumber, err)
|
||||
}
|
||||
|
||||
// 7.2 记录目标猪群数量增加
|
||||
reasonInBatch := fmt.Sprintf("整栏划拨: %v头猪随猪栏 %v 从批次 %v 划拨入。备注: %s", quantity, pen.PenNumber, fromBatch.BatchNumber, remarks)
|
||||
err = s.updatePigBatchQuantityTx(tx, operatorID, toBatchID, models.ChangeTypeTransferIn, quantity, reasonInBatch, now)
|
||||
err = s.updatePigBatchQuantityTx(serviceCtx, tx, operatorID, toBatchID, models.ChangeTypeTransferIn, quantity, reasonInBatch, now)
|
||||
if err != nil {
|
||||
return fmt.Errorf("更新目标猪群 %v 数量失败: %w", toBatch.BatchNumber, err)
|
||||
}
|
||||
@@ -382,10 +392,11 @@ func (s *pigBatchService) ReclassifyPenToNewBatch(fromBatchID uint, toBatchID ui
|
||||
})
|
||||
}
|
||||
|
||||
func (s *pigBatchService) RemoveEmptyPenFromBatch(batchID uint, penID uint) error {
|
||||
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
||||
func (s *pigBatchService) RemoveEmptyPenFromBatch(ctx context.Context, batchID uint, penID uint) error {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "RemoveEmptyPenFromBatch")
|
||||
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||
// 1. 检查猪批次是否存在且活跃
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID)
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, batchID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrPigBatchNotFound
|
||||
@@ -397,7 +408,7 @@ func (s *pigBatchService) RemoveEmptyPenFromBatch(batchID uint, penID uint) erro
|
||||
}
|
||||
|
||||
// 2. 检查猪栏是否存在
|
||||
pen, err := s.transferSvc.GetPenByID(tx, penID)
|
||||
pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrPenNotFound
|
||||
@@ -411,7 +422,7 @@ func (s *pigBatchService) RemoveEmptyPenFromBatch(batchID uint, penID uint) erro
|
||||
}
|
||||
|
||||
// 4. 检查猪栏是否为空
|
||||
pigsInPen, err := s.transferSvc.GetCurrentPigsInPen(tx, penID)
|
||||
pigsInPen, err := s.transferSvc.GetCurrentPigsInPen(serviceCtx, tx, penID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -420,17 +431,18 @@ func (s *pigBatchService) RemoveEmptyPenFromBatch(batchID uint, penID uint) erro
|
||||
}
|
||||
|
||||
// 5. 释放猪栏 (将 pig_batch_id 设置为 nil,状态设置为空闲)
|
||||
if err := s.transferSvc.ReleasePen(tx, penID); err != nil {
|
||||
if err := s.transferSvc.ReleasePen(serviceCtx, tx, penID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *pigBatchService) GetCurrentPigsInPen(penID uint) (int, error) {
|
||||
func (s *pigBatchService) GetCurrentPigsInPen(ctx context.Context, penID uint) (int, error) {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetCurrentPigsInPen")
|
||||
var currentPigs int
|
||||
err := s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
||||
pigs, err := s.transferSvc.GetCurrentPigsInPen(tx, penID)
|
||||
err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||
pigs, err := s.transferSvc.GetCurrentPigsInPen(serviceCtx, tx, penID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -441,10 +453,11 @@ func (s *pigBatchService) GetCurrentPigsInPen(penID uint) (int, error) {
|
||||
}
|
||||
|
||||
// GetTotalPigsInPensForBatch 实现了获取指定猪群下所有猪栏的当前总存栏数的逻辑。
|
||||
func (s *pigBatchService) GetTotalPigsInPensForBatch(batchID uint) (int, error) {
|
||||
func (s *pigBatchService) GetTotalPigsInPensForBatch(ctx context.Context, batchID uint) (int, error) {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetTotalPigsInPensForBatch")
|
||||
var totalPigs int
|
||||
err := s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
||||
pigs, err := s.transferSvc.GetTotalPigsInPensForBatchTx(tx, batchID)
|
||||
err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||
pigs, err := s.transferSvc.GetTotalPigsInPensForBatchTx(serviceCtx, tx, batchID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
package pig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// RecordSickPigs 记录新增病猪事件。
|
||||
func (s *pigBatchService) RecordSickPigs(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error {
|
||||
func (s *pigBatchService) RecordSickPigs(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "RecordSickPigs")
|
||||
if quantity <= 0 {
|
||||
return errors.New("新增病猪数量必须大于0")
|
||||
}
|
||||
|
||||
var err error
|
||||
// 1. 开启事务
|
||||
err = s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
||||
err = s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||
// 1.1 检查批次是否活跃
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID)
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, batchID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrPigBatchNotFound
|
||||
@@ -31,7 +35,7 @@ func (s *pigBatchService) RecordSickPigs(operatorID uint, batchID uint, penID ui
|
||||
}
|
||||
|
||||
// 1.2 检查猪栏是否关联
|
||||
pen, err := s.transferSvc.GetPenByID(tx, penID)
|
||||
pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrPenNotFound
|
||||
@@ -43,12 +47,12 @@ func (s *pigBatchService) RecordSickPigs(operatorID uint, batchID uint, penID ui
|
||||
}
|
||||
|
||||
// 1.3 检查剩余健康猪不能少于即将转化的病猪数量
|
||||
totalPigsInBatch, err := s.getCurrentPigQuantityTx(tx, batchID)
|
||||
totalPigsInBatch, err := s.getCurrentPigQuantityTx(serviceCtx, tx, batchID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取批次 %d 总猪只数量失败: %w", batchID, err)
|
||||
}
|
||||
|
||||
currentSickPigs, err := s.sickSvc.GetCurrentSickPigCount(tx, batchID)
|
||||
currentSickPigs, err := s.sickSvc.GetCurrentSickPigCount(serviceCtx, tx, batchID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取批次 %d 当前病猪数量失败: %w", batchID, err)
|
||||
}
|
||||
@@ -70,7 +74,7 @@ func (s *pigBatchService) RecordSickPigs(operatorID uint, batchID uint, penID ui
|
||||
HappenedAt: happenedAt,
|
||||
}
|
||||
|
||||
if err := s.sickSvc.ProcessSickPigLog(tx, sickLog); err != nil {
|
||||
if err := s.sickSvc.ProcessSickPigLog(serviceCtx, tx, sickLog); err != nil {
|
||||
return fmt.Errorf("处理病猪日志失败: %w", err)
|
||||
}
|
||||
|
||||
@@ -85,15 +89,16 @@ func (s *pigBatchService) RecordSickPigs(operatorID uint, batchID uint, penID ui
|
||||
}
|
||||
|
||||
// RecordSickPigRecovery 记录病猪康复事件。
|
||||
func (s *pigBatchService) RecordSickPigRecovery(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error {
|
||||
func (s *pigBatchService) RecordSickPigRecovery(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "RecordSickPigRecovery")
|
||||
if quantity <= 0 {
|
||||
return errors.New("康复猪只数量必须大于0")
|
||||
}
|
||||
|
||||
var err error
|
||||
err = s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
||||
err = s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||
// 1. 检查批次是否活跃
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID)
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, batchID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrPigBatchNotFound
|
||||
@@ -105,7 +110,7 @@ func (s *pigBatchService) RecordSickPigRecovery(operatorID uint, batchID uint, p
|
||||
}
|
||||
|
||||
// 2. 检查猪栏是否关联
|
||||
pen, err := s.transferSvc.GetPenByID(tx, penID)
|
||||
pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrPenNotFound
|
||||
@@ -117,7 +122,7 @@ func (s *pigBatchService) RecordSickPigRecovery(operatorID uint, batchID uint, p
|
||||
}
|
||||
|
||||
// 3. 检查当前病猪数量是否足够康复
|
||||
currentSickPigs, err := s.sickSvc.GetCurrentSickPigCount(tx, batchID)
|
||||
currentSickPigs, err := s.sickSvc.GetCurrentSickPigCount(serviceCtx, tx, batchID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取批次 %d 当前病猪数量失败: %w", batchID, err)
|
||||
}
|
||||
@@ -138,7 +143,7 @@ func (s *pigBatchService) RecordSickPigRecovery(operatorID uint, batchID uint, p
|
||||
HappenedAt: happenedAt,
|
||||
}
|
||||
|
||||
if err := s.sickSvc.ProcessSickPigLog(tx, sickLog); err != nil {
|
||||
if err := s.sickSvc.ProcessSickPigLog(serviceCtx, tx, sickLog); err != nil {
|
||||
return fmt.Errorf("处理病猪康复日志失败: %w", err)
|
||||
}
|
||||
|
||||
@@ -153,15 +158,16 @@ func (s *pigBatchService) RecordSickPigRecovery(operatorID uint, batchID uint, p
|
||||
}
|
||||
|
||||
// RecordSickPigDeath 记录病猪死亡事件。
|
||||
func (s *pigBatchService) RecordSickPigDeath(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error {
|
||||
func (s *pigBatchService) RecordSickPigDeath(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "RecordSickPigDeath")
|
||||
if quantity <= 0 {
|
||||
return errors.New("死亡猪只数量必须大于0")
|
||||
}
|
||||
|
||||
var err error
|
||||
err = s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
||||
err = s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||
// 1. 检查批次是否活跃
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID)
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, batchID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrPigBatchNotFound
|
||||
@@ -173,7 +179,7 @@ func (s *pigBatchService) RecordSickPigDeath(operatorID uint, batchID uint, penI
|
||||
}
|
||||
|
||||
// 2. 检查猪栏是否关联
|
||||
pen, err := s.transferSvc.GetPenByID(tx, penID)
|
||||
pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrPenNotFound
|
||||
@@ -185,7 +191,7 @@ func (s *pigBatchService) RecordSickPigDeath(operatorID uint, batchID uint, penI
|
||||
}
|
||||
|
||||
// 3. 检查当前病猪数量是否足够死亡
|
||||
currentSickPigs, err := s.sickSvc.GetCurrentSickPigCount(tx, batchID)
|
||||
currentSickPigs, err := s.sickSvc.GetCurrentSickPigCount(serviceCtx, tx, batchID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取批次 %d 当前病猪数量失败: %w", batchID, err)
|
||||
}
|
||||
@@ -195,7 +201,7 @@ func (s *pigBatchService) RecordSickPigDeath(operatorID uint, batchID uint, penI
|
||||
}
|
||||
|
||||
// 4. 检查猪栏内猪只数量是否足够死亡
|
||||
currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(tx, penID)
|
||||
currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(serviceCtx, tx, penID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取猪栏 %d 当前猪只数量失败: %w", penID, err)
|
||||
}
|
||||
@@ -214,12 +220,12 @@ func (s *pigBatchService) RecordSickPigDeath(operatorID uint, batchID uint, penI
|
||||
OperatorID: operatorID,
|
||||
HappenedAt: happenedAt,
|
||||
}
|
||||
if err := s.sickSvc.ProcessSickPigLog(tx, sickLog); err != nil {
|
||||
if err := s.sickSvc.ProcessSickPigLog(serviceCtx, tx, sickLog); err != nil {
|
||||
return fmt.Errorf("处理病猪死亡日志失败: %w", err)
|
||||
}
|
||||
|
||||
// 6. 更新批次总猪只数量 (减少批次总数)
|
||||
if err := s.UpdatePigBatchQuantity(operatorID, batchID, models.ChangeTypeDeath, -quantity, remarks, happenedAt); err != nil {
|
||||
if err := s.UpdatePigBatchQuantity(serviceCtx, operatorID, batchID, models.ChangeTypeDeath, -quantity, remarks, happenedAt); err != nil {
|
||||
return fmt.Errorf("更新批次 %d 总猪只数量失败: %w", batchID, err)
|
||||
}
|
||||
|
||||
@@ -233,7 +239,7 @@ func (s *pigBatchService) RecordSickPigDeath(operatorID uint, batchID uint, penI
|
||||
OperatorID: operatorID,
|
||||
Remarks: remarks,
|
||||
}
|
||||
if err := s.transferSvc.LogTransfer(tx, transferLog); err != nil {
|
||||
if err := s.transferSvc.LogTransfer(serviceCtx, tx, transferLog); err != nil {
|
||||
return fmt.Errorf("记录猪只死亡转移日志失败: %w", err)
|
||||
}
|
||||
|
||||
@@ -248,15 +254,16 @@ func (s *pigBatchService) RecordSickPigDeath(operatorID uint, batchID uint, penI
|
||||
}
|
||||
|
||||
// RecordSickPigCull 记录病猪淘汰事件。
|
||||
func (s *pigBatchService) RecordSickPigCull(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error {
|
||||
func (s *pigBatchService) RecordSickPigCull(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "RecordSickPigCull")
|
||||
if quantity <= 0 {
|
||||
return errors.New("淘汰猪只数量必须大于0")
|
||||
}
|
||||
|
||||
var err error
|
||||
err = s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
||||
err = s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||
// 1. 检查批次是否活跃
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID)
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, batchID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrPigBatchNotFound
|
||||
@@ -268,7 +275,7 @@ func (s *pigBatchService) RecordSickPigCull(operatorID uint, batchID uint, penID
|
||||
}
|
||||
|
||||
// 2. 检查猪栏是否关联
|
||||
pen, err := s.transferSvc.GetPenByID(tx, penID)
|
||||
pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrPenNotFound
|
||||
@@ -280,7 +287,7 @@ func (s *pigBatchService) RecordSickPigCull(operatorID uint, batchID uint, penID
|
||||
}
|
||||
|
||||
// 3. 检查当前病猪数量是否足够淘汰
|
||||
currentSickPigs, err := s.sickSvc.GetCurrentSickPigCount(tx, batchID)
|
||||
currentSickPigs, err := s.sickSvc.GetCurrentSickPigCount(serviceCtx, tx, batchID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取批次 %d 当前病猪数量失败: %w", batchID, err)
|
||||
}
|
||||
@@ -290,7 +297,7 @@ func (s *pigBatchService) RecordSickPigCull(operatorID uint, batchID uint, penID
|
||||
}
|
||||
|
||||
// 4. 检查猪栏内猪只数量是否足够淘汰
|
||||
currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(tx, penID)
|
||||
currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(serviceCtx, tx, penID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取猪栏 %d 当前猪只数量失败: %w", penID, err)
|
||||
}
|
||||
@@ -309,12 +316,12 @@ func (s *pigBatchService) RecordSickPigCull(operatorID uint, batchID uint, penID
|
||||
OperatorID: operatorID,
|
||||
HappenedAt: happenedAt,
|
||||
}
|
||||
if err := s.sickSvc.ProcessSickPigLog(tx, sickLog); err != nil {
|
||||
if err := s.sickSvc.ProcessSickPigLog(serviceCtx, tx, sickLog); err != nil {
|
||||
return fmt.Errorf("处理病猪淘汰日志失败: %w", err)
|
||||
}
|
||||
|
||||
// 6. 更新批次总猪只数量 (减少批次总数)
|
||||
if err := s.UpdatePigBatchQuantity(operatorID, batchID, models.ChangeTypeCull, -quantity, remarks, happenedAt); err != nil {
|
||||
if err := s.UpdatePigBatchQuantity(serviceCtx, operatorID, batchID, models.ChangeTypeCull, -quantity, remarks, happenedAt); err != nil {
|
||||
return fmt.Errorf("更新批次 %d 总猪只数量失败: %w", batchID, err)
|
||||
}
|
||||
|
||||
@@ -328,7 +335,7 @@ func (s *pigBatchService) RecordSickPigCull(operatorID uint, batchID uint, penID
|
||||
OperatorID: operatorID,
|
||||
Remarks: remarks,
|
||||
}
|
||||
if err := s.transferSvc.LogTransfer(tx, transferLog); err != nil {
|
||||
if err := s.transferSvc.LogTransfer(serviceCtx, tx, transferLog); err != nil {
|
||||
return fmt.Errorf("记录猪只淘汰转移日志失败: %w", err)
|
||||
}
|
||||
|
||||
@@ -343,15 +350,16 @@ func (s *pigBatchService) RecordSickPigCull(operatorID uint, batchID uint, penID
|
||||
}
|
||||
|
||||
// RecordDeath 记录正常猪只死亡事件。
|
||||
func (s *pigBatchService) RecordDeath(operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error {
|
||||
func (s *pigBatchService) RecordDeath(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "RecordDeath")
|
||||
if quantity <= 0 {
|
||||
return errors.New("死亡猪只数量必须大于0")
|
||||
}
|
||||
|
||||
var err error
|
||||
err = s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
||||
err = s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||
// 1. 检查批次是否活跃
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID)
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, batchID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrPigBatchNotFound
|
||||
@@ -363,7 +371,7 @@ func (s *pigBatchService) RecordDeath(operatorID uint, batchID uint, penID uint,
|
||||
}
|
||||
|
||||
// 2. 检查猪栏是否关联
|
||||
pen, err := s.transferSvc.GetPenByID(tx, penID)
|
||||
pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrPenNotFound
|
||||
@@ -375,7 +383,7 @@ func (s *pigBatchService) RecordDeath(operatorID uint, batchID uint, penID uint,
|
||||
}
|
||||
|
||||
// 3. 检查猪栏内猪只数量是否足够死亡
|
||||
currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(tx, penID)
|
||||
currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(serviceCtx, tx, penID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取猪栏 %d 当前猪只数量失败: %w", penID, err)
|
||||
}
|
||||
@@ -384,7 +392,7 @@ func (s *pigBatchService) RecordDeath(operatorID uint, batchID uint, penID uint,
|
||||
}
|
||||
|
||||
// 4. 更新批次总猪只数量 (减少批次总数)
|
||||
if err := s.UpdatePigBatchQuantity(operatorID, batchID, models.ChangeTypeDeath, -quantity, remarks, happenedAt); err != nil {
|
||||
if err := s.UpdatePigBatchQuantity(serviceCtx, operatorID, batchID, models.ChangeTypeDeath, -quantity, remarks, happenedAt); err != nil {
|
||||
return fmt.Errorf("更新批次 %d 总猪只数量失败: %w", batchID, err)
|
||||
}
|
||||
|
||||
@@ -398,7 +406,7 @@ func (s *pigBatchService) RecordDeath(operatorID uint, batchID uint, penID uint,
|
||||
OperatorID: operatorID,
|
||||
Remarks: remarks,
|
||||
}
|
||||
if err := s.transferSvc.LogTransfer(tx, transferLog); err != nil {
|
||||
if err := s.transferSvc.LogTransfer(serviceCtx, tx, transferLog); err != nil {
|
||||
return fmt.Errorf("记录猪只死亡转移日志失败: %w", err)
|
||||
}
|
||||
|
||||
@@ -413,15 +421,16 @@ func (s *pigBatchService) RecordDeath(operatorID uint, batchID uint, penID uint,
|
||||
}
|
||||
|
||||
// RecordCull 记录正常猪只淘汰事件。
|
||||
func (s *pigBatchService) RecordCull(operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error {
|
||||
func (s *pigBatchService) RecordCull(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "RecordCull")
|
||||
if quantity <= 0 {
|
||||
return errors.New("淘汰猪只数量必须大于0")
|
||||
}
|
||||
|
||||
var err error
|
||||
err = s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
||||
err = s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||
// 1. 检查批次是否活跃
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID)
|
||||
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, batchID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrPigBatchNotFound
|
||||
@@ -433,7 +442,7 @@ func (s *pigBatchService) RecordCull(operatorID uint, batchID uint, penID uint,
|
||||
}
|
||||
|
||||
// 2. 检查猪栏是否关联
|
||||
pen, err := s.transferSvc.GetPenByID(tx, penID)
|
||||
pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrPenNotFound
|
||||
@@ -445,7 +454,7 @@ func (s *pigBatchService) RecordCull(operatorID uint, batchID uint, penID uint,
|
||||
}
|
||||
|
||||
// 3. 检查猪栏内猪只数量是否足够淘汰
|
||||
currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(tx, penID)
|
||||
currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(serviceCtx, tx, penID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取猪栏 %d 当前猪只数量失败: %w", penID, err)
|
||||
}
|
||||
@@ -454,7 +463,7 @@ func (s *pigBatchService) RecordCull(operatorID uint, batchID uint, penID uint,
|
||||
}
|
||||
|
||||
// 4. 更新批次总猪只数量 (减少批次总数)
|
||||
if err := s.UpdatePigBatchQuantity(operatorID, batchID, models.ChangeTypeCull, -quantity, remarks, happenedAt); err != nil {
|
||||
if err := s.UpdatePigBatchQuantity(serviceCtx, operatorID, batchID, models.ChangeTypeCull, -quantity, remarks, happenedAt); err != nil {
|
||||
return fmt.Errorf("更新批次 %d 总猪只数量失败: %w", batchID, err)
|
||||
}
|
||||
|
||||
@@ -468,7 +477,7 @@ func (s *pigBatchService) RecordCull(operatorID uint, batchID uint, penID uint,
|
||||
OperatorID: operatorID,
|
||||
Remarks: remarks,
|
||||
}
|
||||
if err := s.transferSvc.LogTransfer(tx, transferLog); err != nil {
|
||||
if err := s.transferSvc.LogTransfer(serviceCtx, tx, transferLog); err != nil {
|
||||
return fmt.Errorf("记录猪只淘汰转移日志失败: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
package pig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// SellPigs 处理批量销售猪的业务逻辑。
|
||||
func (s *pigBatchService) SellPigs(batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error {
|
||||
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
||||
func (s *pigBatchService) SellPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "SellPigs")
|
||||
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||
if quantity <= 0 {
|
||||
return errors.New("销售数量必须大于0")
|
||||
}
|
||||
|
||||
// 1. 校验猪栏信息
|
||||
pen, err := s.transferSvc.GetPenByID(tx, penID)
|
||||
pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrPenNotFound
|
||||
@@ -31,7 +35,7 @@ func (s *pigBatchService) SellPigs(batchID uint, penID uint, quantity int, unitP
|
||||
}
|
||||
|
||||
// 2. 业务校验:检查销售数量是否超过猪栏当前猪只数
|
||||
currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(tx, penID)
|
||||
currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(serviceCtx, tx, penID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取猪栏 %d 当前猪只数失败: %w", penID, err)
|
||||
}
|
||||
@@ -51,7 +55,7 @@ func (s *pigBatchService) SellPigs(batchID uint, penID uint, quantity int, unitP
|
||||
Remarks: remarks,
|
||||
OperatorID: operatorID,
|
||||
}
|
||||
if err := s.tradeSvc.SellPig(tx, sale); err != nil {
|
||||
if err := s.tradeSvc.SellPig(serviceCtx, tx, sale); err != nil {
|
||||
return fmt.Errorf("记录销售交易失败: %w", err)
|
||||
}
|
||||
|
||||
@@ -65,12 +69,12 @@ func (s *pigBatchService) SellPigs(batchID uint, penID uint, quantity int, unitP
|
||||
OperatorID: operatorID,
|
||||
Remarks: fmt.Sprintf("销售给 %s", traderName),
|
||||
}
|
||||
if err := s.transferSvc.LogTransfer(tx, transferLog); err != nil {
|
||||
if err := s.transferSvc.LogTransfer(serviceCtx, tx, transferLog); err != nil {
|
||||
return fmt.Errorf("创建猪只转移日志失败: %w", err)
|
||||
}
|
||||
|
||||
// 5. 记录批次数量变更日志 (逻辑)
|
||||
if err := s.updatePigBatchQuantityTx(tx, operatorID, batchID, models.ChangeTypeSale, -quantity,
|
||||
if err := s.updatePigBatchQuantityTx(serviceCtx, tx, operatorID, batchID, models.ChangeTypeSale, -quantity,
|
||||
fmt.Sprintf("猪批次 %d 从猪栏 %d 销售 %d 头猪给 %s", batchID, penID, quantity, traderName),
|
||||
tradeDate); err != nil {
|
||||
return fmt.Errorf("更新猪批次数量失败: %w", err)
|
||||
@@ -81,14 +85,15 @@ func (s *pigBatchService) SellPigs(batchID uint, penID uint, quantity int, unitP
|
||||
}
|
||||
|
||||
// BuyPigs 处理批量购买猪的业务逻辑。
|
||||
func (s *pigBatchService) BuyPigs(batchID uint, penID uint, quantity int, unitPrice float64, totalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error {
|
||||
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
||||
func (s *pigBatchService) BuyPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float64, totalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "BuyPigs")
|
||||
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||
if quantity <= 0 {
|
||||
return errors.New("采购数量必须大于0")
|
||||
}
|
||||
|
||||
// 1. 校验猪栏信息
|
||||
pen, err := s.transferSvc.GetPenByID(tx, penID)
|
||||
pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrPenNotFound
|
||||
@@ -102,7 +107,7 @@ func (s *pigBatchService) BuyPigs(batchID uint, penID uint, quantity int, unitPr
|
||||
}
|
||||
|
||||
// 2. 业务校验:检查猪栏容量,如果超出,在备注中记录警告
|
||||
currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(tx, penID)
|
||||
currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(serviceCtx, tx, penID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取猪栏 %d 当前猪只数失败: %w", penID, err)
|
||||
}
|
||||
@@ -124,7 +129,7 @@ func (s *pigBatchService) BuyPigs(batchID uint, penID uint, quantity int, unitPr
|
||||
Remarks: remarks, // 用户传入的备注
|
||||
OperatorID: operatorID,
|
||||
}
|
||||
if err := s.tradeSvc.BuyPig(tx, purchase); err != nil {
|
||||
if err := s.tradeSvc.BuyPig(serviceCtx, tx, purchase); err != nil {
|
||||
return fmt.Errorf("记录采购交易失败: %w", err)
|
||||
}
|
||||
|
||||
@@ -138,12 +143,12 @@ func (s *pigBatchService) BuyPigs(batchID uint, penID uint, quantity int, unitPr
|
||||
OperatorID: operatorID,
|
||||
Remarks: transferRemarks, // 包含系统生成的备注和潜在的警告
|
||||
}
|
||||
if err := s.transferSvc.LogTransfer(tx, transferLog); err != nil {
|
||||
if err := s.transferSvc.LogTransfer(serviceCtx, tx, transferLog); err != nil {
|
||||
return fmt.Errorf("创建猪只转移日志失败: %w", err)
|
||||
}
|
||||
|
||||
// 5. 记录批次数量变更日志 (逻辑)
|
||||
if err := s.updatePigBatchQuantityTx(tx, operatorID, batchID, models.ChangeTypeBuy, quantity,
|
||||
if err := s.updatePigBatchQuantityTx(serviceCtx, tx, operatorID, batchID, models.ChangeTypeBuy, quantity,
|
||||
fmt.Sprintf("猪批次 %d 在猪栏 %d 采购 %d 头猪从 %s", batchID, penID, quantity, traderName),
|
||||
tradeDate); err != nil {
|
||||
return fmt.Errorf("更新猪批次数量失败: %w", err)
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package pig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"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/repository"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -15,31 +18,35 @@ type SickPigManager interface {
|
||||
// ProcessSickPigLog 处理病猪相关的日志事件。
|
||||
// log 包含事件的基本信息,如 PigBatchID, PenID, PigIDs, ChangeCount, Reason, TreatmentLocation, Remarks, OperatorID, HappenedAt。
|
||||
// Manager 内部会计算并填充 BeforeCount 和 AfterCount,并进行必要的业务校验和副作用处理。
|
||||
ProcessSickPigLog(tx *gorm.DB, log *models.PigSickLog) error
|
||||
ProcessSickPigLog(ctx context.Context, tx *gorm.DB, log *models.PigSickLog) error
|
||||
|
||||
// GetCurrentSickPigCount 获取指定批次当前患病猪只的总数
|
||||
GetCurrentSickPigCount(tx *gorm.DB, batchID uint) (int, error)
|
||||
GetCurrentSickPigCount(ctx context.Context, tx *gorm.DB, batchID uint) (int, error)
|
||||
}
|
||||
|
||||
// sickPigManager 是 SickPigManager 接口的具体实现。
|
||||
// 它依赖于仓库接口来执行数据持久化操作。
|
||||
type sickPigManager struct {
|
||||
ctx context.Context
|
||||
sickLogRepo repository.PigSickLogRepository
|
||||
medicationLogRepo repository.MedicationLogRepository
|
||||
}
|
||||
|
||||
// NewSickPigManager 是 sickPigManager 的构造函数。
|
||||
func NewSickPigManager(
|
||||
ctx context.Context,
|
||||
sickLogRepo repository.PigSickLogRepository,
|
||||
medicationLogRepo repository.MedicationLogRepository,
|
||||
) SickPigManager {
|
||||
return &sickPigManager{
|
||||
ctx: ctx,
|
||||
sickLogRepo: sickLogRepo,
|
||||
medicationLogRepo: medicationLogRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sickPigManager) ProcessSickPigLog(tx *gorm.DB, log *models.PigSickLog) error {
|
||||
func (s *sickPigManager) ProcessSickPigLog(ctx context.Context, tx *gorm.DB, log *models.PigSickLog) error {
|
||||
managerCtx := logs.AddFuncName(ctx, s.ctx, "ProcessSickPigLog")
|
||||
// 1. 输入校验
|
||||
if log == nil {
|
||||
return errors.New("病猪日志不能为空")
|
||||
@@ -93,7 +100,7 @@ func (s *sickPigManager) ProcessSickPigLog(tx *gorm.DB, log *models.PigSickLog)
|
||||
}
|
||||
|
||||
// 2. 获取当前病猪数量 (BeforeCount)
|
||||
beforeCount, err := s.GetCurrentSickPigCount(tx, log.PigBatchID)
|
||||
beforeCount, err := s.GetCurrentSickPigCount(managerCtx, tx, log.PigBatchID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取批次 %d 当前病猪数量失败: %w", log.PigBatchID, err)
|
||||
}
|
||||
@@ -108,15 +115,16 @@ func (s *sickPigManager) ProcessSickPigLog(tx *gorm.DB, log *models.PigSickLog)
|
||||
}
|
||||
|
||||
// 5. 持久化 PigSickLog
|
||||
if err := s.sickLogRepo.CreatePigSickLogTx(tx, log); err != nil {
|
||||
if err := s.sickLogRepo.CreatePigSickLogTx(managerCtx, tx, log); err != nil {
|
||||
return fmt.Errorf("创建 PigSickLog 失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sickPigManager) GetCurrentSickPigCount(tx *gorm.DB, batchID uint) (int, error) {
|
||||
lastLog, err := s.sickLogRepo.GetLastLogByBatchTx(tx, batchID)
|
||||
func (s *sickPigManager) GetCurrentSickPigCount(ctx context.Context, tx *gorm.DB, batchID uint) (int, error) {
|
||||
managerCtx := logs.AddFuncName(ctx, s.ctx, "GetCurrentSickPigCount")
|
||||
lastLog, err := s.sickLogRepo.GetLastLogByBatchTx(managerCtx, tx, batchID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, nil // 如果没有找到任何日志,表示当前病猪数量为0
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package pig
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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/repository" // 引入基础设施层的仓库接口
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -10,37 +14,41 @@ import (
|
||||
// 这是一个领域服务,负责协调业务逻辑。
|
||||
type PigTradeManager interface {
|
||||
// SellPig 处理卖猪的业务逻辑,通过仓库接口创建 PigSale 记录。
|
||||
SellPig(tx *gorm.DB, sale *models.PigSale) error
|
||||
SellPig(ctx context.Context, tx *gorm.DB, sale *models.PigSale) error
|
||||
|
||||
// BuyPig 处理买猪的业务逻辑,通过仓库接口创建 PigPurchase 记录。
|
||||
BuyPig(tx *gorm.DB, purchase *models.PigPurchase) error
|
||||
BuyPig(ctx context.Context, tx *gorm.DB, purchase *models.PigPurchase) error
|
||||
}
|
||||
|
||||
// pigTradeManager 是 PigTradeManager 接口的具体实现。
|
||||
// 它依赖于 repository.PigTradeRepository 接口来执行数据持久化操作。
|
||||
type pigTradeManager struct {
|
||||
tradeRepo repository.PigTradeRepository // 依赖于基础设施层定义的仓库接口
|
||||
ctx context.Context
|
||||
tradeRepo repository.PigTradeRepository
|
||||
}
|
||||
|
||||
// NewPigTradeManager 是 pigTradeManager 的构造函数。
|
||||
func NewPigTradeManager(tradeRepo repository.PigTradeRepository) PigTradeManager {
|
||||
func NewPigTradeManager(ctx context.Context, tradeRepo repository.PigTradeRepository) PigTradeManager {
|
||||
return &pigTradeManager{
|
||||
ctx: ctx,
|
||||
tradeRepo: tradeRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// SellPig 实现了卖猪的逻辑。
|
||||
// 它通过调用 tradeRepo 来持久化销售记录。
|
||||
func (s *pigTradeManager) SellPig(tx *gorm.DB, sale *models.PigSale) error {
|
||||
func (s *pigTradeManager) SellPig(ctx context.Context, tx *gorm.DB, sale *models.PigSale) error {
|
||||
managerCtx := logs.AddFuncName(ctx, s.ctx, "SellPig")
|
||||
// 在此处可以添加更复杂的卖猪前置校验或业务逻辑
|
||||
// 例如:检查猪只库存、更新猪只状态等。
|
||||
return s.tradeRepo.CreatePigSaleTx(tx, sale)
|
||||
return s.tradeRepo.CreatePigSaleTx(managerCtx, tx, sale)
|
||||
}
|
||||
|
||||
// BuyPig 实现了买猪的逻辑。
|
||||
// 它通过调用 tradeRepo 来持久化采购记录。
|
||||
func (s *pigTradeManager) BuyPig(tx *gorm.DB, purchase *models.PigPurchase) error {
|
||||
func (s *pigTradeManager) BuyPig(ctx context.Context, tx *gorm.DB, purchase *models.PigPurchase) error {
|
||||
managerCtx := logs.AddFuncName(ctx, s.ctx, "BuyPig")
|
||||
// 在此处可以添加更复杂的买猪前置校验或业务逻辑
|
||||
// 例如:检查资金、更新猪只状态等。
|
||||
return s.tradeRepo.CreatePigPurchaseTx(tx, purchase)
|
||||
return s.tradeRepo.CreatePigPurchaseTx(managerCtx, tx, purchase)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package plan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -14,78 +15,80 @@ import (
|
||||
// AnalysisPlanTaskManager 定义了分析计划任务管理器的接口。
|
||||
type AnalysisPlanTaskManager interface {
|
||||
// Refresh 同步数据库中的计划状态和待执行队列中的触发器任务。
|
||||
Refresh() error
|
||||
Refresh(ctx context.Context) error
|
||||
// CreateOrUpdateTrigger 为给定的 planID 创建其关联的触发任务。
|
||||
// 如果触发器已存在,会根据计划类型更新其执行时间。
|
||||
CreateOrUpdateTrigger(planID uint) error
|
||||
CreateOrUpdateTrigger(ctx context.Context, planID uint) error
|
||||
// EnsureAnalysisTaskDefinition 确保计划的分析任务定义存在于 tasks 表中。
|
||||
// 如果不存在,则会自动创建。此方法不涉及待执行队列。
|
||||
EnsureAnalysisTaskDefinition(planID uint) error
|
||||
EnsureAnalysisTaskDefinition(ctx context.Context, planID uint) error
|
||||
}
|
||||
|
||||
// analysisPlanTaskManagerImpl 负责管理分析计划的触发器任务。
|
||||
// 它确保数据库中可执行的计划在待执行队列中有对应的触发器,并移除无效的触发器。
|
||||
// 这是一个有状态的组件,包含一个互斥锁以确保并发安全。
|
||||
type analysisPlanTaskManagerImpl struct {
|
||||
ctx context.Context
|
||||
planRepo repository.PlanRepository
|
||||
pendingTaskRepo repository.PendingTaskRepository
|
||||
executionLogRepo repository.ExecutionLogRepository
|
||||
logger *logs.Logger
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewAnalysisPlanTaskManager 是 analysisPlanTaskManagerImpl 的构造函数。
|
||||
func NewAnalysisPlanTaskManager(
|
||||
ctx context.Context,
|
||||
planRepo repository.PlanRepository,
|
||||
pendingTaskRepo repository.PendingTaskRepository,
|
||||
executionLogRepo repository.ExecutionLogRepository,
|
||||
logger *logs.Logger,
|
||||
) AnalysisPlanTaskManager {
|
||||
return &analysisPlanTaskManagerImpl{
|
||||
ctx: ctx,
|
||||
planRepo: planRepo,
|
||||
pendingTaskRepo: pendingTaskRepo,
|
||||
executionLogRepo: executionLogRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh 同步数据库中的计划状态和待执行队列中的触发器任务。
|
||||
// 这是一个编排方法,将复杂的逻辑分解到多个内部方法中。
|
||||
func (m *analysisPlanTaskManagerImpl) Refresh() error {
|
||||
func (m *analysisPlanTaskManagerImpl) Refresh(ctx context.Context) error {
|
||||
managerCtx, logger := logs.Trace(ctx, m.ctx, "Refresh")
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
m.logger.Info("开始同步计划任务管理器...")
|
||||
logger.Info("开始同步计划任务管理器...")
|
||||
|
||||
// 1. 一次性获取所有需要的数据
|
||||
runnablePlans, invalidPlanIDs, pendingTasks, err := m.getRefreshData()
|
||||
runnablePlans, invalidPlanIDs, pendingTasks, err := m.getRefreshData(managerCtx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取刷新数据失败: %w", err)
|
||||
}
|
||||
|
||||
// 2. 清理所有与失效计划相关的待执行任务
|
||||
if err := m.cleanupInvalidTasks(invalidPlanIDs, pendingTasks); err != nil {
|
||||
if err := m.cleanupInvalidTasks(managerCtx, invalidPlanIDs, pendingTasks); err != nil {
|
||||
// 仅记录错误,清理失败不应阻止新任务的添加
|
||||
m.logger.Errorf("清理无效任务时出错: %v", err)
|
||||
logger.Errorf("清理无效任务时出错: %v", err)
|
||||
}
|
||||
|
||||
// 3. 添加或更新触发器
|
||||
if err := m.addOrUpdateTriggers(runnablePlans, pendingTasks); err != nil {
|
||||
if err := m.addOrUpdateTriggers(managerCtx, runnablePlans, pendingTasks); err != nil {
|
||||
return fmt.Errorf("添加或更新触发器时出错: %w", err)
|
||||
}
|
||||
|
||||
m.logger.Info("计划任务管理器同步完成.")
|
||||
logger.Info("计划任务管理器同步完成.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateOrUpdateTrigger 为给定的 planID 创建其关联的触发任务。
|
||||
// 如果触发器已存在,会根据计划类型更新其执行时间。
|
||||
func (m *analysisPlanTaskManagerImpl) CreateOrUpdateTrigger(planID uint) error {
|
||||
func (m *analysisPlanTaskManagerImpl) CreateOrUpdateTrigger(ctx context.Context, planID uint) error {
|
||||
managerCtx, logger := logs.Trace(ctx, m.ctx, "CreateOrUpdateTrigger")
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
// 检查计划是否可执行
|
||||
plan, err := m.planRepo.GetBasicPlanByID(planID)
|
||||
plan, err := m.planRepo.GetBasicPlanByID(managerCtx, planID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取计划基本信息失败: %w", err)
|
||||
}
|
||||
@@ -94,7 +97,7 @@ func (m *analysisPlanTaskManagerImpl) CreateOrUpdateTrigger(planID uint) error {
|
||||
}
|
||||
|
||||
// 查找现有触发器
|
||||
existingTrigger, err := m.pendingTaskRepo.FindPendingTriggerByPlanID(planID)
|
||||
existingTrigger, err := m.pendingTaskRepo.FindPendingTriggerByPlanID(managerCtx, planID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("查找现有触发器失败: %w", err)
|
||||
}
|
||||
@@ -109,7 +112,7 @@ func (m *analysisPlanTaskManagerImpl) CreateOrUpdateTrigger(planID uint) error {
|
||||
// 自动计划,根据 Cron 表达式计算下一次执行时间
|
||||
next, err := utils.GetNextCronTime(plan.CronExpression)
|
||||
if err != nil {
|
||||
m.logger.Errorf("为计划 #%d 解析Cron表达式失败,无法更新触发器: %v", plan.ID, err)
|
||||
logger.Errorf("为计划 #%d 解析Cron表达式失败,无法更新触发器: %v", plan.ID, err)
|
||||
return fmt.Errorf("解析 Cron 表达式失败: %w", err)
|
||||
}
|
||||
expectedExecuteAt = next
|
||||
@@ -117,47 +120,48 @@ func (m *analysisPlanTaskManagerImpl) CreateOrUpdateTrigger(planID uint) error {
|
||||
|
||||
// 如果计算出的执行时间与当前待执行任务的时间不一致,则更新
|
||||
if !existingTrigger.ExecuteAt.Equal(expectedExecuteAt) {
|
||||
m.logger.Infof("计划 #%d 的执行时间已变更,正在更新触发器 #%d 的执行时间从 %v 到 %v...", plan.ID, existingTrigger.ID, existingTrigger.ExecuteAt, expectedExecuteAt)
|
||||
if err := m.pendingTaskRepo.UpdatePendingTaskExecuteAt(existingTrigger.ID, expectedExecuteAt); err != nil {
|
||||
m.logger.Errorf("更新触发器 #%d 的执行时间失败: %v", existingTrigger.ID, err)
|
||||
logger.Infof("计划 #%d 的执行时间已变更,正在更新触发器 #%d 的执行时间从 %v 到 %v...", plan.ID, existingTrigger.ID, existingTrigger.ExecuteAt, expectedExecuteAt)
|
||||
if err := m.pendingTaskRepo.UpdatePendingTaskExecuteAt(managerCtx, existingTrigger.ID, expectedExecuteAt); err != nil {
|
||||
logger.Errorf("更新触发器 #%d 的执行时间失败: %v", existingTrigger.ID, err)
|
||||
return fmt.Errorf("更新触发器执行时间失败: %w", err)
|
||||
}
|
||||
} else {
|
||||
m.logger.Infof("计划 #%d 的触发器已存在且执行时间无需更新。", plan.ID)
|
||||
logger.Infof("计划 #%d 的触发器已存在且执行时间无需更新。", plan.ID)
|
||||
}
|
||||
return nil // 触发器已存在且已处理更新,直接返回
|
||||
}
|
||||
|
||||
// 如果触发器不存在,则创建新的触发器
|
||||
m.logger.Infof("为计划 #%d 创建新的触发器...", planID)
|
||||
return m.createTriggerTask(plan)
|
||||
logger.Infof("为计划 #%d 创建新的触发器...", planID)
|
||||
return m.createTriggerTask(managerCtx, plan)
|
||||
}
|
||||
|
||||
// EnsureAnalysisTaskDefinition 确保计划的分析任务定义存在于 tasks 表中。
|
||||
// 如果不存在,则会自动创建。此方法不涉及待执行队列。
|
||||
func (m *analysisPlanTaskManagerImpl) EnsureAnalysisTaskDefinition(planID uint) error {
|
||||
func (m *analysisPlanTaskManagerImpl) EnsureAnalysisTaskDefinition(ctx context.Context, planID uint) error {
|
||||
managerCtx, logger := logs.Trace(ctx, m.ctx, "EnsureAnalysisTaskDefinition")
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
plan, err := m.planRepo.GetBasicPlanByID(planID)
|
||||
plan, err := m.planRepo.GetBasicPlanByID(managerCtx, planID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("确保分析任务定义失败:获取计划 #%d 基本信息时出错: %w", planID, err)
|
||||
}
|
||||
|
||||
analysisTask, err := m.planRepo.FindPlanAnalysisTaskByPlanID(plan.ID)
|
||||
analysisTask, err := m.planRepo.FindPlanAnalysisTaskByPlanID(managerCtx, plan.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("确保分析任务定义失败:查找计划 #%d 的分析任务时出错: %w", plan.ID, err)
|
||||
}
|
||||
|
||||
if analysisTask == nil {
|
||||
m.logger.Infof("未找到计划 #%d 关联的 'plan_analysis' 任务定义,将自动创建...", plan.ID)
|
||||
_, err := m.planRepo.CreatePlanAnalysisTask(plan) // CreatePlanAnalysisTask returns *models.Task, error
|
||||
logger.Infof("未找到计划 #%d 关联的 'plan_analysis' 任务定义,将自动创建...", plan.ID)
|
||||
_, err := m.planRepo.CreatePlanAnalysisTask(managerCtx, plan) // CreatePlanAnalysisTask returns *models.Task, error
|
||||
if err != nil {
|
||||
return fmt.Errorf("自动创建 'plan_analysis' 任务定义失败: %w", err)
|
||||
}
|
||||
m.logger.Infof("已成功为计划 #%d 创建 'plan_analysis' 任务定义。", plan.ID)
|
||||
logger.Infof("已成功为计划 #%d 创建 'plan_analysis' 任务定义。", plan.ID)
|
||||
} else {
|
||||
m.logger.Infof("计划 #%d 的 'plan_analysis' 任务定义已存在。", plan.ID)
|
||||
logger.Infof("计划 #%d 的 'plan_analysis' 任务定义已存在。", plan.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -166,16 +170,17 @@ func (m *analysisPlanTaskManagerImpl) EnsureAnalysisTaskDefinition(planID uint)
|
||||
// --- 内部私有方法 ---
|
||||
|
||||
// getRefreshData 从数据库获取刷新所需的所有数据。
|
||||
func (m *analysisPlanTaskManagerImpl) getRefreshData() (runnablePlans []*models.Plan, invalidPlanIDs []uint, pendingTasks []models.PendingTask, err error) {
|
||||
runnablePlans, err = m.planRepo.FindRunnablePlans()
|
||||
func (m *analysisPlanTaskManagerImpl) getRefreshData(ctx context.Context) (runnablePlans []*models.Plan, invalidPlanIDs []uint, pendingTasks []models.PendingTask, err error) {
|
||||
managerCtx, logger := logs.Trace(ctx, m.ctx, "getRefreshData")
|
||||
runnablePlans, err = m.planRepo.FindRunnablePlans(managerCtx)
|
||||
if err != nil {
|
||||
m.logger.Errorf("获取可执行计划列表失败: %v", err)
|
||||
logger.Errorf("获取可执行计划列表失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
invalidPlans, err := m.planRepo.FindInactivePlans()
|
||||
invalidPlans, err := m.planRepo.FindInactivePlans(managerCtx)
|
||||
if err != nil {
|
||||
m.logger.Errorf("获取失效计划列表失败: %v", err)
|
||||
logger.Errorf("获取失效计划列表失败: %v", err)
|
||||
return
|
||||
}
|
||||
invalidPlanIDs = make([]uint, len(invalidPlans))
|
||||
@@ -183,16 +188,17 @@ func (m *analysisPlanTaskManagerImpl) getRefreshData() (runnablePlans []*models.
|
||||
invalidPlanIDs[i] = p.ID
|
||||
}
|
||||
|
||||
pendingTasks, err = m.pendingTaskRepo.FindAllPendingTasks()
|
||||
pendingTasks, err = m.pendingTaskRepo.FindAllPendingTasks(managerCtx)
|
||||
if err != nil {
|
||||
m.logger.Errorf("获取所有待执行任务失败: %v", err)
|
||||
logger.Errorf("获取所有待执行任务失败: %v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// cleanupInvalidTasks 清理所有与失效计划相关的待执行任务。
|
||||
func (m *analysisPlanTaskManagerImpl) cleanupInvalidTasks(invalidPlanIDs []uint, allPendingTasks []models.PendingTask) error {
|
||||
func (m *analysisPlanTaskManagerImpl) cleanupInvalidTasks(ctx context.Context, invalidPlanIDs []uint, allPendingTasks []models.PendingTask) error {
|
||||
managerCtx, logger := logs.Trace(ctx, m.ctx, "cleanupInvalidTasks")
|
||||
if len(invalidPlanIDs) == 0 {
|
||||
return nil // 没有需要清理的计划
|
||||
}
|
||||
@@ -219,24 +225,25 @@ func (m *analysisPlanTaskManagerImpl) cleanupInvalidTasks(invalidPlanIDs []uint,
|
||||
return nil // 没有找到需要清理的任务
|
||||
}
|
||||
|
||||
m.logger.Infof("准备从待执行队列中清理 %d 个与失效计划相关的任务...", len(tasksToDeleteIDs))
|
||||
logger.Infof("准备从待执行队列中清理 %d 个与失效计划相关的任务...", len(tasksToDeleteIDs))
|
||||
|
||||
// 批量删除待执行任务
|
||||
if err := m.pendingTaskRepo.DeletePendingTasksByIDs(tasksToDeleteIDs); err != nil {
|
||||
if err := m.pendingTaskRepo.DeletePendingTasksByIDs(managerCtx, tasksToDeleteIDs); err != nil {
|
||||
return fmt.Errorf("批量删除待执行任务失败: %w", err)
|
||||
}
|
||||
|
||||
// 批量更新相关执行日志状态为“已取消”
|
||||
if err := m.executionLogRepo.UpdateTaskExecutionLogStatusByIDs(logsToCancelIDs, models.ExecutionStatusCancelled); err != nil {
|
||||
if err := m.executionLogRepo.UpdateTaskExecutionLogStatusByIDs(managerCtx, logsToCancelIDs, models.ExecutionStatusCancelled); err != nil {
|
||||
// 这是一个非关键性错误,只记录日志
|
||||
m.logger.Warnf("批量更新日志状态为 'Cancelled' 失败: %v", err)
|
||||
logger.Warnf("批量更新日志状态为 'Cancelled' 失败: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addOrUpdateTriggers 检查、更新或创建触发器。
|
||||
func (m *analysisPlanTaskManagerImpl) addOrUpdateTriggers(runnablePlans []*models.Plan, allPendingTasks []models.PendingTask) error {
|
||||
func (m *analysisPlanTaskManagerImpl) addOrUpdateTriggers(ctx context.Context, runnablePlans []*models.Plan, allPendingTasks []models.PendingTask) error {
|
||||
managerCtx, logger := logs.Trace(ctx, m.ctx, "addOrUpdateTriggers")
|
||||
// 创建一个映射,存放所有已在队列中的计划触发器
|
||||
pendingTriggersMap := make(map[uint]models.PendingTask)
|
||||
for _, pt := range allPendingTasks {
|
||||
@@ -254,22 +261,22 @@ func (m *analysisPlanTaskManagerImpl) addOrUpdateTriggers(runnablePlans []*model
|
||||
if plan.ExecutionType == models.PlanExecutionTypeAutomatic {
|
||||
next, err := utils.GetNextCronTime(plan.CronExpression)
|
||||
if err != nil {
|
||||
m.logger.Errorf("为计划 #%d 解析Cron表达式失败,跳过更新: %v", plan.ID, err)
|
||||
logger.Errorf("为计划 #%d 解析Cron表达式失败,跳过更新: %v", plan.ID, err)
|
||||
continue
|
||||
}
|
||||
// 如果数据库中记录的执行时间与根据当前Cron表达式计算出的下一次时间不一致,则更新
|
||||
if !existingTrigger.ExecuteAt.Equal(next) {
|
||||
m.logger.Infof("计划 #%d 的执行时间已变更,正在更新触发器 #%d 的执行时间从 %v 到 %v...", plan.ID, existingTrigger.ID, existingTrigger.ExecuteAt, next)
|
||||
if err := m.pendingTaskRepo.UpdatePendingTaskExecuteAt(existingTrigger.ID, next); err != nil {
|
||||
m.logger.Errorf("更新触发器 #%d 的执行时间失败: %v", existingTrigger.ID, err)
|
||||
logger.Infof("计划 #%d 的执行时间已变更,正在更新触发器 #%d 的执行时间从 %v 到 %v...", plan.ID, existingTrigger.ID, existingTrigger.ExecuteAt, next)
|
||||
if err := m.pendingTaskRepo.UpdatePendingTaskExecuteAt(managerCtx, existingTrigger.ID, next); err != nil {
|
||||
logger.Errorf("更新触发器 #%d 的执行时间失败: %v", existingTrigger.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// --- 原有逻辑:为缺失的计划创建新触发器 ---
|
||||
m.logger.Infof("发现应执行但队列中缺失的计划 #%d,正在为其创建触发器...", plan.ID)
|
||||
if err := m.createTriggerTask(plan); err != nil {
|
||||
m.logger.Errorf("为计划 #%d 创建触发器失败: %v", plan.ID, err)
|
||||
logger.Infof("发现应执行但队列中缺失的计划 #%d,正在为其创建触发器...", plan.ID)
|
||||
if err := m.createTriggerTask(managerCtx, plan); err != nil {
|
||||
logger.Errorf("为计划 #%d 创建触发器失败: %v", plan.ID, err)
|
||||
// 继续处理下一个,不因单点失败而中断
|
||||
}
|
||||
}
|
||||
@@ -278,21 +285,22 @@ func (m *analysisPlanTaskManagerImpl) addOrUpdateTriggers(runnablePlans []*model
|
||||
}
|
||||
|
||||
// createTriggerTask 是创建触发器任务的内部核心逻辑。
|
||||
func (m *analysisPlanTaskManagerImpl) createTriggerTask(plan *models.Plan) error {
|
||||
analysisTask, err := m.planRepo.FindPlanAnalysisTaskByPlanID(plan.ID)
|
||||
func (m *analysisPlanTaskManagerImpl) createTriggerTask(ctx context.Context, plan *models.Plan) error {
|
||||
managerCtx, logger := logs.Trace(ctx, m.ctx, "createTriggerTask")
|
||||
analysisTask, err := m.planRepo.FindPlanAnalysisTaskByPlanID(managerCtx, plan.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("查找计划分析任务失败: %w", err)
|
||||
}
|
||||
|
||||
// --- 如果触发器任务定义不存在,则自动创建 ---
|
||||
if analysisTask == nil {
|
||||
m.logger.Warnf("未找到计划 #%d 关联的 'plan_analysis' 任务定义,将自动创建...", plan.ID)
|
||||
newAnalysisTask, err := m.planRepo.CreatePlanAnalysisTask(plan)
|
||||
logger.Warnf("未找到计划 #%d 关联的 'plan_analysis' 任务定义,将自动创建...", plan.ID)
|
||||
newAnalysisTask, err := m.planRepo.CreatePlanAnalysisTask(managerCtx, plan)
|
||||
if err != nil {
|
||||
return fmt.Errorf("自动创建 'plan_analysis' 任务定义失败: %w", err)
|
||||
}
|
||||
analysisTask = newAnalysisTask
|
||||
m.logger.Infof("已成功为计划 #%d 创建 'plan_analysis' 任务定义 (ID: %d)", plan.ID, analysisTask.ID)
|
||||
logger.Infof("已成功为计划 #%d 创建 'plan_analysis' 任务定义 (ID: %d)", plan.ID, analysisTask.ID)
|
||||
}
|
||||
|
||||
var executeAt time.Time
|
||||
@@ -310,7 +318,7 @@ func (m *analysisPlanTaskManagerImpl) createTriggerTask(plan *models.Plan) error
|
||||
TaskID: analysisTask.ID,
|
||||
Status: models.ExecutionStatusWaiting,
|
||||
}
|
||||
if err := m.executionLogRepo.CreateTaskExecutionLog(taskLog); err != nil {
|
||||
if err := m.executionLogRepo.CreateTaskExecutionLog(managerCtx, taskLog); err != nil {
|
||||
return fmt.Errorf("创建任务执行日志失败: %w", err)
|
||||
}
|
||||
|
||||
@@ -319,10 +327,10 @@ func (m *analysisPlanTaskManagerImpl) createTriggerTask(plan *models.Plan) error
|
||||
ExecuteAt: executeAt,
|
||||
TaskExecutionLogID: taskLog.ID,
|
||||
}
|
||||
if err := m.pendingTaskRepo.CreatePendingTask(pendingTask); err != nil {
|
||||
if err := m.pendingTaskRepo.CreatePendingTask(managerCtx, pendingTask); err != nil {
|
||||
return fmt.Errorf("创建待执行任务失败: %w", err)
|
||||
}
|
||||
|
||||
m.logger.Infof("成功为计划 #%d 创建触发器 (任务ID: %d),执行时间: %v", plan.ID, analysisTask.ID, executeAt)
|
||||
logger.Infof("成功为计划 #%d 创建触发器 (任务ID: %d),执行时间: %v", plan.ID, analysisTask.ID, executeAt)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
package plan
|
||||
@@ -1,6 +1,7 @@
|
||||
package plan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -16,9 +17,9 @@ import (
|
||||
// ExecutionManager 定义了计划执行管理器的接口。
|
||||
type ExecutionManager interface {
|
||||
// Start 启动计划执行管理器。
|
||||
Start()
|
||||
Start(ctx context.Context)
|
||||
// Stop 优雅地停止计划执行管理器。
|
||||
Stop()
|
||||
Stop(ctx context.Context)
|
||||
}
|
||||
|
||||
// ProgressTracker 仅用于在内存中提供计划执行的并发锁
|
||||
@@ -83,7 +84,7 @@ func (t *ProgressTracker) GetRunningPlanIDs() []uint {
|
||||
|
||||
// planExecutionManagerImpl 是核心的、持久化的任务调度器
|
||||
type planExecutionManagerImpl struct {
|
||||
logger *logs.Logger
|
||||
ctx context.Context
|
||||
pollingInterval time.Duration
|
||||
workers int
|
||||
pendingTaskRepo repository.PendingTaskRepository
|
||||
@@ -103,6 +104,7 @@ type planExecutionManagerImpl struct {
|
||||
|
||||
// NewPlanExecutionManager 创建一个新的调度器实例
|
||||
func NewPlanExecutionManager(
|
||||
ctx context.Context,
|
||||
pendingTaskRepo repository.PendingTaskRepository,
|
||||
executionLogRepo repository.ExecutionLogRepository,
|
||||
deviceRepo repository.DeviceRepository,
|
||||
@@ -110,12 +112,12 @@ func NewPlanExecutionManager(
|
||||
planRepo repository.PlanRepository,
|
||||
analysisPlanTaskManager AnalysisPlanTaskManager,
|
||||
taskFactory TaskFactory,
|
||||
logger *logs.Logger,
|
||||
deviceService device.Service,
|
||||
interval time.Duration,
|
||||
numWorkers int,
|
||||
) ExecutionManager {
|
||||
return &planExecutionManagerImpl{
|
||||
ctx: ctx,
|
||||
pendingTaskRepo: pendingTaskRepo,
|
||||
executionLogRepo: executionLogRepo,
|
||||
deviceRepo: deviceRepo,
|
||||
@@ -123,7 +125,6 @@ func NewPlanExecutionManager(
|
||||
planRepo: planRepo,
|
||||
analysisPlanTaskManager: analysisPlanTaskManager,
|
||||
taskFactory: taskFactory,
|
||||
logger: logger,
|
||||
deviceService: deviceService,
|
||||
pollingInterval: interval,
|
||||
workers: numWorkers,
|
||||
@@ -133,10 +134,11 @@ func NewPlanExecutionManager(
|
||||
}
|
||||
|
||||
// Start 启动调度器,包括初始化协程池和启动主轮询循环
|
||||
func (s *planExecutionManagerImpl) Start() {
|
||||
s.logger.Warnf("任务调度器正在启动,工作协程数: %d...", s.workers)
|
||||
func (s *planExecutionManagerImpl) Start(ctx context.Context) {
|
||||
managerCtx, logger := logs.Trace(ctx, s.ctx, "Start")
|
||||
logger.Warnf("任务调度器正在启动,工作协程数: %d...", s.workers)
|
||||
pool, err := ants.NewPool(s.workers, ants.WithPanicHandler(func(err interface{}) {
|
||||
s.logger.Errorf("[严重] 任务执行时发生 panic: %v", err)
|
||||
logger.Errorf("[严重] 任务执行时发生 panic: %v", err)
|
||||
}))
|
||||
if err != nil {
|
||||
panic("初始化协程池失败: " + err.Error())
|
||||
@@ -144,21 +146,23 @@ func (s *planExecutionManagerImpl) Start() {
|
||||
s.pool = pool
|
||||
|
||||
s.wg.Add(1)
|
||||
go s.run()
|
||||
s.logger.Warnf("任务调度器已成功启动")
|
||||
go s.run(managerCtx)
|
||||
logger.Warnf("任务调度器已成功启动")
|
||||
}
|
||||
|
||||
// Stop 优雅地停止调度器
|
||||
func (s *planExecutionManagerImpl) Stop() {
|
||||
s.logger.Warnf("正在停止任务调度器...")
|
||||
func (s *planExecutionManagerImpl) Stop(ctx context.Context) {
|
||||
logger := logs.TraceLogger(ctx, s.ctx, "Stop")
|
||||
logger.Warnf("正在停止任务调度器...")
|
||||
close(s.stopChan) // 1. 发出停止信号,停止主循环
|
||||
s.wg.Wait() // 2. 等待主循环完成
|
||||
s.pool.Release() // 3. 释放 ants 池 (等待所有已提交的任务执行完毕)
|
||||
s.logger.Warnf("任务调度器已安全停止")
|
||||
logger.Warnf("任务调度器已安全停止")
|
||||
}
|
||||
|
||||
// run 是主轮询循环,负责从数据库认领任务并提交到协程池
|
||||
func (s *planExecutionManagerImpl) run() {
|
||||
func (s *planExecutionManagerImpl) run(ctx context.Context) {
|
||||
managerCtx := logs.AddFuncName(ctx, s.ctx, "run")
|
||||
defer s.wg.Done()
|
||||
ticker := time.NewTicker(s.pollingInterval)
|
||||
defer ticker.Stop()
|
||||
@@ -170,19 +174,20 @@ func (s *planExecutionManagerImpl) run() {
|
||||
return
|
||||
case <-ticker.C:
|
||||
// 定时触发任务认领和提交
|
||||
go s.claimAndSubmit()
|
||||
go s.claimAndSubmit(managerCtx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// claimAndSubmit 实现了最终的“认领-锁定-执行 或 等待-放回”的健壮逻辑
|
||||
func (s *planExecutionManagerImpl) claimAndSubmit() {
|
||||
func (s *planExecutionManagerImpl) claimAndSubmit(ctx context.Context) {
|
||||
managerCtx, logger := logs.Trace(ctx, s.ctx, "claimAndSubmit")
|
||||
runningPlanIDs := s.progressTracker.GetRunningPlanIDs()
|
||||
|
||||
claimedLog, pendingTask, err := s.pendingTaskRepo.ClaimNextAvailableTask(runningPlanIDs)
|
||||
claimedLog, pendingTask, err := s.pendingTaskRepo.ClaimNextAvailableTask(managerCtx, runningPlanIDs)
|
||||
if err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
s.logger.Errorf("认领任务时发生错误: %v", err)
|
||||
logger.Errorf("认领任务时发生错误: %v", err)
|
||||
}
|
||||
// gorm.ErrRecordNotFound 说明没任务要执行
|
||||
return
|
||||
@@ -193,100 +198,103 @@ func (s *planExecutionManagerImpl) claimAndSubmit() {
|
||||
// 成功获取锁,正常派发任务
|
||||
err = s.pool.Submit(func() {
|
||||
defer s.progressTracker.Unlock(claimedLog.PlanExecutionLogID)
|
||||
s.processTask(claimedLog)
|
||||
s.processTask(managerCtx, claimedLog)
|
||||
})
|
||||
if err != nil {
|
||||
s.logger.Errorf("向协程池提交任务失败: %v", err)
|
||||
logger.Errorf("向协程池提交任务失败: %v", err)
|
||||
// 提交失败,必须释放刚刚获取的锁
|
||||
s.progressTracker.Unlock(claimedLog.PlanExecutionLogID)
|
||||
// 同样需要将任务安全放回
|
||||
s.handleRequeue(claimedLog.PlanExecutionLogID, pendingTask)
|
||||
s.handleRequeue(managerCtx, claimedLog.PlanExecutionLogID, pendingTask)
|
||||
}
|
||||
} else {
|
||||
// 获取锁失败,说明有“兄弟”任务正在执行。执行“锁定并安全放回”逻辑。
|
||||
s.handleRequeue(claimedLog.PlanExecutionLogID, pendingTask)
|
||||
s.handleRequeue(managerCtx, claimedLog.PlanExecutionLogID, pendingTask)
|
||||
}
|
||||
}
|
||||
|
||||
// handleRequeue 同步地、安全地将一个无法立即执行的任务放回队列。
|
||||
func (s *planExecutionManagerImpl) handleRequeue(planExecutionLogID uint, taskToRequeue *models.PendingTask) {
|
||||
s.logger.Warnf("计划 %d 正在执行,任务 %d (TaskID: %d) 将等待并重新入队...", planExecutionLogID, taskToRequeue.ID, taskToRequeue.TaskID)
|
||||
func (s *planExecutionManagerImpl) handleRequeue(ctx context.Context, planExecutionLogID uint, taskToRequeue *models.PendingTask) {
|
||||
managerCtx, logger := logs.Trace(ctx, s.ctx, "handleRequeue")
|
||||
logger.Warnf("计划 %d 正在执行,任务 %d (TaskID: %d) 将等待并重新入队...", planExecutionLogID, taskToRequeue.ID, taskToRequeue.TaskID)
|
||||
|
||||
// 1. 阻塞式地等待,直到可以获取到该计划的锁。
|
||||
s.progressTracker.Lock(planExecutionLogID)
|
||||
defer s.progressTracker.Unlock(planExecutionLogID)
|
||||
|
||||
// 2. 在持有锁的情况下,将任务安全地放回队列。
|
||||
if err := s.pendingTaskRepo.RequeueTask(taskToRequeue); err != nil {
|
||||
s.logger.Errorf("[严重] 任务重新入队失败, 原始PendingTaskID: %d, 错误: %v", taskToRequeue.ID, err)
|
||||
if err := s.pendingTaskRepo.RequeueTask(managerCtx, taskToRequeue); err != nil {
|
||||
logger.Errorf("[严重] 任务重新入队失败, 原始PendingTaskID: %d, 错误: %v", taskToRequeue.ID, err)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Warnf("任务 (原始ID: %d) 已成功重新入队,并已释放计划 %d 的锁。", taskToRequeue.ID, planExecutionLogID)
|
||||
logger.Warnf("任务 (原始ID: %d) 已成功重新入队,并已释放计划 %d 的锁。", taskToRequeue.ID, planExecutionLogID)
|
||||
}
|
||||
|
||||
// processTask 处理单个任务的逻辑
|
||||
func (s *planExecutionManagerImpl) processTask(claimedLog *models.TaskExecutionLog) {
|
||||
s.logger.Warnf("开始处理任务, 日志ID: %d, 任务ID: %d, 任务名称: %s, 描述: %s",
|
||||
func (s *planExecutionManagerImpl) processTask(ctx context.Context, claimedLog *models.TaskExecutionLog) {
|
||||
managerCtx, logger := logs.Trace(ctx, s.ctx, "processTask")
|
||||
logger.Warnf("开始处理任务, 日志ID: %d, 任务ID: %d, 任务名称: %s, 描述: %s",
|
||||
claimedLog.ID, claimedLog.TaskID, claimedLog.Task.Name, claimedLog.Task.Description)
|
||||
|
||||
claimedLog.StartedAt = time.Now()
|
||||
claimedLog.Status = models.ExecutionStatusCompleted // 先乐观假定任务成功, 后续失败了再改
|
||||
defer s.updateTaskExecutionLogStatus(claimedLog)
|
||||
defer s.updateTaskExecutionLogStatus(managerCtx, claimedLog)
|
||||
|
||||
// 执行任务
|
||||
err := s.runTask(claimedLog)
|
||||
err := s.runTask(managerCtx, claimedLog)
|
||||
if err != nil {
|
||||
claimedLog.Status = models.ExecutionStatusFailed
|
||||
claimedLog.Output = err.Error()
|
||||
|
||||
// 任务失败时,调用统一的终止服务
|
||||
s.handlePlanTermination(claimedLog.PlanExecutionLogID, "子任务执行失败: "+err.Error())
|
||||
s.handlePlanTermination(managerCtx, claimedLog.PlanExecutionLogID, "子任务执行失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 如果是计划分析任务,它的职责是解析和分发任务,到此即完成,不参与后续的计划完成度检查。
|
||||
if claimedLog.Task.Type == models.TaskPlanAnalysis {
|
||||
s.logger.Warnf("完成计划分析任务, 日志ID: %d", claimedLog.ID)
|
||||
logger.Warnf("完成计划分析任务, 日志ID: %d", claimedLog.ID)
|
||||
return
|
||||
}
|
||||
|
||||
// --- 以下是常规任务的完成逻辑 ---
|
||||
s.logger.Warnf("完成任务, 日志ID: %d", claimedLog.ID)
|
||||
logger.Warnf("完成任务, 日志ID: %d", claimedLog.ID)
|
||||
|
||||
// 检查是否是最后一个任务
|
||||
incompleteCount, err := s.executionLogRepo.CountIncompleteTasksByPlanLogID(claimedLog.PlanExecutionLogID)
|
||||
incompleteCount, err := s.executionLogRepo.CountIncompleteTasksByPlanLogID(managerCtx, claimedLog.PlanExecutionLogID)
|
||||
if err != nil {
|
||||
s.logger.Errorf("检查计划 %d 的未完成任务数时出错: %v", claimedLog.PlanExecutionLogID, err)
|
||||
logger.Errorf("检查计划 %d 的未完成任务数时出错: %v", claimedLog.PlanExecutionLogID, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 如果此计划执行中,未完成的任务只剩下当前这一个(因为当前任务的状态此时在数据库中仍为 'started'),
|
||||
// 则认为整个计划已完成。
|
||||
if incompleteCount == 1 {
|
||||
s.handlePlanCompletion(claimedLog.PlanExecutionLogID)
|
||||
s.handlePlanCompletion(managerCtx, claimedLog.PlanExecutionLogID)
|
||||
}
|
||||
}
|
||||
|
||||
// runTask 用于执行具体任务
|
||||
func (s *planExecutionManagerImpl) runTask(claimedLog *models.TaskExecutionLog) error {
|
||||
func (s *planExecutionManagerImpl) runTask(ctx context.Context, claimedLog *models.TaskExecutionLog) error {
|
||||
managerCtx, logger := logs.Trace(ctx, s.ctx, "runTask")
|
||||
// 这是个特殊任务, 用于解析Plan并将解析出的任务队列添加到待执行队列中
|
||||
if claimedLog.Task.Type == models.TaskPlanAnalysis {
|
||||
// 解析plan
|
||||
err := s.analysisPlan(claimedLog)
|
||||
err := s.analysisPlan(managerCtx, claimedLog)
|
||||
if err != nil {
|
||||
// TODO 这里要处理一下, 比如再插一个新的触发器回去
|
||||
s.logger.Errorf("[严重] 计划解析失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
|
||||
logger.Errorf("[严重] 计划解析失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
|
||||
return err
|
||||
}
|
||||
|
||||
} else {
|
||||
// 执行普通任务
|
||||
task := s.taskFactory.Production(claimedLog)
|
||||
task := s.taskFactory.Production(managerCtx, claimedLog)
|
||||
|
||||
if err := task.Execute(); err != nil {
|
||||
s.logger.Errorf("[严重] 任务执行失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
|
||||
task.OnFailure(err)
|
||||
if err := task.Execute(managerCtx); err != nil {
|
||||
logger.Errorf("[严重] 任务执行失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
|
||||
task.OnFailure(managerCtx, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -295,14 +303,15 @@ func (s *planExecutionManagerImpl) runTask(claimedLog *models.TaskExecutionLog)
|
||||
}
|
||||
|
||||
// analysisPlan 解析Plan并将解析出的Task列表插入待执行队列中
|
||||
func (s *planExecutionManagerImpl) analysisPlan(claimedLog *models.TaskExecutionLog) error {
|
||||
func (s *planExecutionManagerImpl) analysisPlan(ctx context.Context, claimedLog *models.TaskExecutionLog) error {
|
||||
managerCtx, logger := logs.Trace(ctx, s.ctx, "analysisPlan")
|
||||
// 创建Plan执行记录
|
||||
// 从任务的 Parameters 中解析出真实的 PlanID
|
||||
var params struct {
|
||||
PlanID uint `json:"plan_id"`
|
||||
}
|
||||
if err := claimedLog.Task.ParseParameters(¶ms); err != nil {
|
||||
s.logger.Errorf("解析任务参数中的计划ID失败,日志ID: %d, 错误: %v", claimedLog.ID, err)
|
||||
logger.Errorf("解析任务参数中的计划ID失败,日志ID: %d, 错误: %v", claimedLog.ID, err)
|
||||
return err
|
||||
}
|
||||
realPlanID := params.PlanID
|
||||
@@ -312,15 +321,15 @@ func (s *planExecutionManagerImpl) analysisPlan(claimedLog *models.TaskExecution
|
||||
Status: models.ExecutionStatusStarted,
|
||||
StartedAt: time.Now(),
|
||||
}
|
||||
if err := s.executionLogRepo.CreatePlanExecutionLog(planLog); err != nil {
|
||||
s.logger.Errorf("[严重] 创建计划执行日志失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
|
||||
if err := s.executionLogRepo.CreatePlanExecutionLog(managerCtx, planLog); err != nil {
|
||||
logger.Errorf("[严重] 创建计划执行日志失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 解析出Task列表
|
||||
tasks, err := s.planRepo.FlattenPlanTasks(realPlanID)
|
||||
tasks, err := s.planRepo.FlattenPlanTasks(managerCtx, realPlanID)
|
||||
if err != nil {
|
||||
s.logger.Errorf("[严重] 解析计划失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
|
||||
logger.Errorf("[严重] 解析计划失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -334,9 +343,9 @@ func (s *planExecutionManagerImpl) analysisPlan(claimedLog *models.TaskExecution
|
||||
}
|
||||
|
||||
}
|
||||
err = s.executionLogRepo.CreateTaskExecutionLogsInBatch(taskLogs)
|
||||
err = s.executionLogRepo.CreateTaskExecutionLogsInBatch(managerCtx, taskLogs)
|
||||
if err != nil {
|
||||
s.logger.Errorf("[严重] 写入执行历史, 日志ID: %d, 错误: %v", claimedLog.ID, err)
|
||||
logger.Errorf("[严重] 写入执行历史, 日志ID: %d, 错误: %v", claimedLog.ID, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -351,9 +360,9 @@ func (s *planExecutionManagerImpl) analysisPlan(claimedLog *models.TaskExecution
|
||||
ExecuteAt: time.Now().Add(time.Duration(i) * time.Second),
|
||||
}
|
||||
}
|
||||
err = s.pendingTaskRepo.CreatePendingTasksInBatch(pendingTasks)
|
||||
err = s.pendingTaskRepo.CreatePendingTasksInBatch(managerCtx, pendingTasks)
|
||||
if err != nil {
|
||||
s.logger.Errorf("[严重] 写入待执行队列, 日志ID: %d, 错误: %v", claimedLog.ID, err)
|
||||
logger.Errorf("[严重] 写入待执行队列, 日志ID: %d, 错误: %v", claimedLog.ID, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -361,18 +370,19 @@ func (s *planExecutionManagerImpl) analysisPlan(claimedLog *models.TaskExecution
|
||||
// 如果一个计划被解析后,发现其任务列表为空,
|
||||
// 那么它实际上已经“执行”完毕了,我们需要在这里手动为它创建下一次的触发器。
|
||||
if len(tasks) == 0 {
|
||||
s.handlePlanCompletion(planLog.ID)
|
||||
s.handlePlanCompletion(managerCtx, planLog.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateTaskExecutionLogStatus 修改任务历史中的执行状态
|
||||
func (s *planExecutionManagerImpl) updateTaskExecutionLogStatus(claimedLog *models.TaskExecutionLog) error {
|
||||
func (s *planExecutionManagerImpl) updateTaskExecutionLogStatus(ctx context.Context, claimedLog *models.TaskExecutionLog) error {
|
||||
managerCtx, logger := logs.Trace(ctx, s.ctx, "updateTaskExecutionLogStatus")
|
||||
claimedLog.EndedAt = time.Now()
|
||||
|
||||
if err := s.executionLogRepo.UpdateTaskExecutionLog(claimedLog); err != nil {
|
||||
s.logger.Errorf("[严重] 更新任务执行日志失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
|
||||
if err := s.executionLogRepo.UpdateTaskExecutionLog(managerCtx, claimedLog); err != nil {
|
||||
logger.Errorf("[严重] 更新任务执行日志失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -380,64 +390,66 @@ func (s *planExecutionManagerImpl) updateTaskExecutionLogStatus(claimedLog *mode
|
||||
}
|
||||
|
||||
// handlePlanTermination 集中处理计划的终止逻辑(失败或取消)
|
||||
func (s *planExecutionManagerImpl) handlePlanTermination(planLogID uint, reason string) {
|
||||
func (s *planExecutionManagerImpl) handlePlanTermination(ctx context.Context, planLogID uint, reason string) {
|
||||
managerCtx, logger := logs.Trace(ctx, s.ctx, "handlePlanTermination")
|
||||
// 1. 从待执行队列中删除所有相关的子任务
|
||||
if err := s.pendingTaskRepo.DeletePendingTasksByPlanLogID(planLogID); err != nil {
|
||||
s.logger.Errorf("从待执行队列中删除计划 %d 的后续任务时出错: %v", planLogID, err)
|
||||
if err := s.pendingTaskRepo.DeletePendingTasksByPlanLogID(managerCtx, planLogID); err != nil {
|
||||
logger.Errorf("从待执行队列中删除计划 %d 的后续任务时出错: %v", planLogID, err)
|
||||
}
|
||||
|
||||
// 2. 将父计划的执行日志标记为失败
|
||||
if err := s.executionLogRepo.FailPlanExecution(planLogID, reason); err != nil {
|
||||
s.logger.Errorf("标记计划执行日志 %d 为失败时出错: %v", planLogID, err)
|
||||
if err := s.executionLogRepo.FailPlanExecution(managerCtx, planLogID, reason); err != nil {
|
||||
logger.Errorf("标记计划执行日志 %d 为失败时出错: %v", planLogID, err)
|
||||
}
|
||||
|
||||
// 3. 将所有未完成的子任务日志标记为已取消
|
||||
if err := s.executionLogRepo.CancelIncompleteTasksByPlanLogID(planLogID, "父计划失败或被取消"); err != nil {
|
||||
s.logger.Errorf("取消计划 %d 的后续任务日志时出错: %v", planLogID, err)
|
||||
if err := s.executionLogRepo.CancelIncompleteTasksByPlanLogID(managerCtx, planLogID, "父计划失败或被取消"); err != nil {
|
||||
logger.Errorf("取消计划 %d 的后续任务日志时出错: %v", planLogID, err)
|
||||
}
|
||||
|
||||
// 4. 获取计划执行日志以获取顶层 PlanID
|
||||
planLog, err := s.executionLogRepo.FindPlanExecutionLogByID(planLogID)
|
||||
planLog, err := s.executionLogRepo.FindPlanExecutionLogByID(managerCtx, planLogID)
|
||||
if err != nil {
|
||||
s.logger.Errorf("无法找到计划执行日志 %d 以更新父计划状态: %v", planLogID, err)
|
||||
logger.Errorf("无法找到计划执行日志 %d 以更新父计划状态: %v", planLogID, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 5. 获取顶层计划的详细信息,以检查其类型
|
||||
topLevelPlan, err := s.planRepo.GetBasicPlanByID(planLog.PlanID)
|
||||
topLevelPlan, err := s.planRepo.GetBasicPlanByID(managerCtx, planLog.PlanID)
|
||||
if err != nil {
|
||||
s.logger.Errorf("获取顶层计划 %d 的基本信息失败: %v", planLog.PlanID, err)
|
||||
logger.Errorf("获取顶层计划 %d 的基本信息失败: %v", planLog.PlanID, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 6. 如果是系统任务,则不修改计划状态
|
||||
if topLevelPlan.PlanType == models.PlanTypeSystem {
|
||||
s.logger.Warnf("系统任务 %d (日志ID: %d) 执行失败,但根据策略不修改其计划状态。", topLevelPlan.ID, planLogID)
|
||||
logger.Warnf("系统任务 %d (日志ID: %d) 执行失败,但根据策略不修改其计划状态。", topLevelPlan.ID, planLogID)
|
||||
return
|
||||
}
|
||||
|
||||
// 7. 将计划本身的状态更新为失败 (仅对非系统任务执行)
|
||||
if err := s.planRepo.UpdatePlanStatus(planLog.PlanID, models.PlanStatusFailed); err != nil {
|
||||
s.logger.Errorf("更新计划 %d 状态为 '失败' 时出错: %v", planLog.PlanID, err)
|
||||
if err := s.planRepo.UpdatePlanStatus(managerCtx, planLog.PlanID, models.PlanStatusFailed); err != nil {
|
||||
logger.Errorf("更新计划 %d 状态为 '失败' 时出错: %v", planLog.PlanID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// handlePlanCompletion 集中处理计划成功完成后的所有逻辑
|
||||
func (s *planExecutionManagerImpl) handlePlanCompletion(planLogID uint) {
|
||||
s.logger.Infof("计划执行 %d 的所有任务已完成,开始处理计划完成逻辑...", planLogID)
|
||||
func (s *planExecutionManagerImpl) handlePlanCompletion(ctx context.Context, planLogID uint) {
|
||||
managerCtx, logger := logs.Trace(ctx, s.ctx, "handlePlanCompletion")
|
||||
logger.Infof("计划执行 %d 的所有任务已完成,开始处理计划完成逻辑...", planLogID)
|
||||
|
||||
// 1. 通过 PlanExecutionLog 反查正确的顶层 PlanID
|
||||
planExecutionLog, err := s.executionLogRepo.FindPlanExecutionLogByID(planLogID)
|
||||
planExecutionLog, err := s.executionLogRepo.FindPlanExecutionLogByID(managerCtx, planLogID)
|
||||
if err != nil {
|
||||
s.logger.Errorf("获取计划执行日志 %d 失败: %v", planLogID, err)
|
||||
logger.Errorf("获取计划执行日志 %d 失败: %v", planLogID, err)
|
||||
return
|
||||
}
|
||||
topLevelPlanID := planExecutionLog.PlanID // 这才是正确的顶层计划ID
|
||||
|
||||
// 2. 获取计划的最新数据,这里我们只需要基本信息来判断执行类型和次数
|
||||
plan, err := s.planRepo.GetBasicPlanByID(topLevelPlanID)
|
||||
plan, err := s.planRepo.GetBasicPlanByID(managerCtx, topLevelPlanID)
|
||||
if err != nil {
|
||||
s.logger.Errorf("获取计划 %d 的基本信息失败: %v", topLevelPlanID, err)
|
||||
logger.Errorf("获取计划 %d 的基本信息失败: %v", topLevelPlanID, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -448,27 +460,27 @@ func (s *planExecutionManagerImpl) handlePlanCompletion(planLogID uint) {
|
||||
// 如果是自动计划且达到执行次数上限,或计划是手动类型,则更新计划状态为已停止
|
||||
if (plan.ExecutionType == models.PlanExecutionTypeAutomatic && plan.ExecuteNum > 0 && newExecuteCount >= plan.ExecuteNum) || plan.ExecutionType == models.PlanExecutionTypeManual {
|
||||
newStatus = models.PlanStatusStopped
|
||||
s.logger.Infof("计划 %d 已完成执行,状态更新为 '执行完毕'。", topLevelPlanID)
|
||||
logger.Infof("计划 %d 已完成执行,状态更新为 '执行完毕'。", topLevelPlanID)
|
||||
}
|
||||
|
||||
// 4. 使用专门的方法来原子性地更新计数值和状态
|
||||
if err := s.planRepo.UpdatePlanStateAfterExecution(topLevelPlanID, newExecuteCount, newStatus); err != nil {
|
||||
s.logger.Errorf("更新计划 %d 的执行后状态失败: %v", topLevelPlanID, err)
|
||||
if err := s.planRepo.UpdatePlanStateAfterExecution(managerCtx, topLevelPlanID, newExecuteCount, newStatus); err != nil {
|
||||
logger.Errorf("更新计划 %d 的执行后状态失败: %v", topLevelPlanID, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 5. 更新计划执行日志状态为完成
|
||||
if err := s.executionLogRepo.UpdatePlanExecutionLogStatus(planLogID, models.ExecutionStatusCompleted); err != nil {
|
||||
s.logger.Errorf("更新计划执行日志 %d 状态为 '完成' 失败: %v", planLogID, err)
|
||||
if err := s.executionLogRepo.UpdatePlanExecutionLogStatus(managerCtx, planLogID, models.ExecutionStatusCompleted); err != nil {
|
||||
logger.Errorf("更新计划执行日志 %d 状态为 '完成' 失败: %v", planLogID, err)
|
||||
}
|
||||
|
||||
// 6. 调用共享的 Manager 来处理触发器更新逻辑
|
||||
// 只有当计划在本次执行后仍然是 Enabled 状态时,才需要创建下一次的触发器。
|
||||
if newStatus == models.PlanStatusEnabled {
|
||||
if err := s.analysisPlanTaskManager.CreateOrUpdateTrigger(topLevelPlanID); err != nil {
|
||||
s.logger.Errorf("为计划 %d 创建/更新触发器失败: %v", topLevelPlanID, err)
|
||||
if err := s.analysisPlanTaskManager.CreateOrUpdateTrigger(managerCtx, topLevelPlanID); err != nil {
|
||||
logger.Errorf("为计划 %d 创建/更新触发器失败: %v", topLevelPlanID, err)
|
||||
}
|
||||
} else {
|
||||
s.logger.Infof("计划 %d 状态为 '%d',无需创建下一次触发器。", topLevelPlanID, newStatus)
|
||||
logger.Infof("计划 %d 状态为 '%d',无需创建下一次触发器。", topLevelPlanID, newStatus)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package plan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"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/repository"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -30,80 +32,84 @@ var (
|
||||
// Service 定义了计划领域服务的接口。
|
||||
type Service interface {
|
||||
// Start 启动计划相关的后台服务,例如计划执行管理器。
|
||||
Start()
|
||||
Start(ctx context.Context)
|
||||
// Stop 停止计划相关的后台服务,例如计划执行管理器。
|
||||
Stop()
|
||||
Stop(ctx context.Context)
|
||||
// RefreshPlanTriggers 刷新计划触发器,同步数据库中的计划状态和待执行队列中的触发器任务。
|
||||
RefreshPlanTriggers() error
|
||||
RefreshPlanTriggers(ctx context.Context) error
|
||||
|
||||
// CreatePlan 创建一个新的计划
|
||||
CreatePlan(plan *models.Plan) (*models.Plan, error)
|
||||
CreatePlan(ctx context.Context, plan *models.Plan) (*models.Plan, error)
|
||||
// GetPlanByID 根据ID获取计划详情
|
||||
GetPlanByID(id uint) (*models.Plan, error)
|
||||
GetPlanByID(ctx context.Context, id uint) (*models.Plan, error)
|
||||
// ListPlans 获取计划列表,支持过滤和分页
|
||||
ListPlans(opts repository.ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error)
|
||||
ListPlans(ctx context.Context, opts repository.ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error)
|
||||
// UpdatePlan 更新计划
|
||||
UpdatePlan(plan *models.Plan) (*models.Plan, error)
|
||||
UpdatePlan(ctx context.Context, plan *models.Plan) (*models.Plan, error)
|
||||
// DeletePlan 删除计划(软删除)
|
||||
DeletePlan(id uint) error
|
||||
DeletePlan(ctx context.Context, id uint) error
|
||||
// StartPlan 启动计划
|
||||
StartPlan(id uint) error
|
||||
StartPlan(ctx context.Context, id uint) error
|
||||
// StopPlan 停止计划
|
||||
StopPlan(id uint) error
|
||||
StopPlan(ctx context.Context, id uint) error
|
||||
}
|
||||
|
||||
// planServiceImpl 是 Service 接口的具体实现。
|
||||
type planServiceImpl struct {
|
||||
ctx context.Context
|
||||
executionManager ExecutionManager
|
||||
taskManager AnalysisPlanTaskManager
|
||||
planRepo repository.PlanRepository
|
||||
deviceRepo repository.DeviceRepository
|
||||
unitOfWork repository.UnitOfWork
|
||||
taskFactory TaskFactory
|
||||
logger *logs.Logger
|
||||
}
|
||||
|
||||
// NewPlanService 创建一个新的 Service 实例。
|
||||
func NewPlanService(
|
||||
ctx context.Context,
|
||||
executionManager ExecutionManager,
|
||||
taskManager AnalysisPlanTaskManager,
|
||||
planRepo repository.PlanRepository,
|
||||
deviceRepo repository.DeviceRepository,
|
||||
unitOfWork repository.UnitOfWork,
|
||||
taskFactory TaskFactory,
|
||||
logger *logs.Logger,
|
||||
) Service {
|
||||
return &planServiceImpl{
|
||||
ctx: ctx,
|
||||
executionManager: executionManager,
|
||||
taskManager: taskManager,
|
||||
planRepo: planRepo,
|
||||
deviceRepo: deviceRepo,
|
||||
unitOfWork: unitOfWork,
|
||||
taskFactory: taskFactory,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Start 启动计划相关的后台服务。
|
||||
func (s *planServiceImpl) Start() {
|
||||
s.logger.Infof("PlanService 正在启动...")
|
||||
s.executionManager.Start()
|
||||
func (s *planServiceImpl) Start(ctx context.Context) {
|
||||
planCtx, logger := logs.Trace(ctx, s.ctx, "Start")
|
||||
logger.Infof("PlanService 正在启动...")
|
||||
s.executionManager.Start(planCtx)
|
||||
}
|
||||
|
||||
// Stop 停止计划相关的后台服务。
|
||||
func (s *planServiceImpl) Stop() {
|
||||
s.logger.Infof("PlanService 正在停止...")
|
||||
s.executionManager.Stop()
|
||||
func (s *planServiceImpl) Stop(ctx context.Context) {
|
||||
planCtx, logger := logs.Trace(ctx, s.ctx, "Stop")
|
||||
logger.Infof("PlanService 正在停止...")
|
||||
s.executionManager.Stop(planCtx)
|
||||
}
|
||||
|
||||
// RefreshPlanTriggers 刷新计划触发器。
|
||||
func (s *planServiceImpl) RefreshPlanTriggers() error {
|
||||
s.logger.Infof("PlanService 正在刷新计划触发器...")
|
||||
return s.taskManager.Refresh()
|
||||
func (s *planServiceImpl) RefreshPlanTriggers(ctx context.Context) error {
|
||||
planCtx, logger := logs.Trace(ctx, s.ctx, "RefreshPlanTriggers")
|
||||
logger.Infof("PlanService 正在刷新计划触发器...")
|
||||
return s.taskManager.Refresh(planCtx)
|
||||
}
|
||||
|
||||
// CreatePlan 创建一个新的计划
|
||||
func (s *planServiceImpl) CreatePlan(planToCreate *models.Plan) (*models.Plan, error) {
|
||||
func (s *planServiceImpl) CreatePlan(ctx context.Context, planToCreate *models.Plan) (*models.Plan, error) {
|
||||
planCtx, logger := logs.Trace(ctx, s.ctx, "CreatePlan")
|
||||
const actionType = "领域层:创建计划"
|
||||
|
||||
// 1. 业务规则处理
|
||||
@@ -119,7 +125,7 @@ func (s *planServiceImpl) CreatePlan(planToCreate *models.Plan) (*models.Plan, e
|
||||
|
||||
// 2. 验证和重排顺序 (领域逻辑)
|
||||
if err := planToCreate.ValidateExecutionOrder(); err != nil {
|
||||
s.logger.Errorf("%s: 计划 (ID: %d) 的执行顺序无效: %v", actionType, planToCreate.ID, err)
|
||||
logger.Errorf("%s: 计划 (ID: %d) 的执行顺序无效: %v", actionType, planToCreate.ID, err)
|
||||
return nil, err
|
||||
}
|
||||
planToCreate.ReorderSteps()
|
||||
@@ -128,14 +134,14 @@ func (s *planServiceImpl) CreatePlan(planToCreate *models.Plan) (*models.Plan, e
|
||||
for i := range planToCreate.Tasks {
|
||||
taskModel := &planToCreate.Tasks[i]
|
||||
// 使用工厂创建临时领域对象
|
||||
taskResolver, err := s.taskFactory.CreateTaskFromModel(taskModel)
|
||||
taskResolver, err := s.taskFactory.CreateTaskFromModel(planCtx, taskModel)
|
||||
if err != nil {
|
||||
// 如果一个任务类型不支持,我们可以选择跳过或报错
|
||||
s.logger.Warnf("跳过为任务类型 '%s' 解析设备ID: %v", taskModel.Type, err)
|
||||
logger.Warnf("跳过为任务类型 '%s' 解析设备ID: %v", taskModel.Type, err)
|
||||
continue
|
||||
}
|
||||
|
||||
deviceIDs, err := taskResolver.ResolveDeviceIDs()
|
||||
deviceIDs, err := taskResolver.ResolveDeviceIDs(planCtx)
|
||||
if err != nil {
|
||||
// 在事务外解析失败,直接返回错误
|
||||
return nil, fmt.Errorf("为任务 '%s' 提取设备ID失败: %w", taskModel.Name, err)
|
||||
@@ -151,71 +157,74 @@ func (s *planServiceImpl) CreatePlan(planToCreate *models.Plan) (*models.Plan, e
|
||||
}
|
||||
|
||||
// 4. 调用仓库方法创建计划,该方法内部会处理事务
|
||||
err := s.planRepo.CreatePlan(planToCreate)
|
||||
err := s.planRepo.CreatePlan(planCtx, planToCreate)
|
||||
if err != nil {
|
||||
s.logger.Errorf("%s: 数据库创建计划失败: %v", actionType, err)
|
||||
logger.Errorf("%s: 数据库创建计划失败: %v", actionType, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 5. 创建成功后,调用 manager 确保触发器任务定义存在,但不立即加入待执行队列
|
||||
if err := s.taskManager.EnsureAnalysisTaskDefinition(planToCreate.ID); err != nil {
|
||||
if err := s.taskManager.EnsureAnalysisTaskDefinition(planCtx, planToCreate.ID); err != nil {
|
||||
// 这是一个非阻塞性错误,我们只记录日志,因为主流程(创建计划)已经成功
|
||||
s.logger.Errorf("为新创建的计划 %d 确保触发器任务定义失败: %v", planToCreate.ID, err)
|
||||
logger.Errorf("为新创建的计划 %d 确保触发器任务定义失败: %v", planToCreate.ID, err)
|
||||
}
|
||||
|
||||
s.logger.Infof("%s: 计划创建成功, ID: %d", actionType, planToCreate.ID)
|
||||
logger.Infof("%s: 计划创建成功, ID: %d", actionType, planToCreate.ID)
|
||||
return planToCreate, nil
|
||||
}
|
||||
|
||||
// GetPlanByID 根据ID获取计划详情
|
||||
func (s *planServiceImpl) GetPlanByID(id uint) (*models.Plan, error) {
|
||||
func (s *planServiceImpl) GetPlanByID(ctx context.Context, id uint) (*models.Plan, error) {
|
||||
planCtx, logger := logs.Trace(ctx, s.ctx, "GetPlanByID")
|
||||
const actionType = "领域层:获取计划详情"
|
||||
|
||||
plan, err := s.planRepo.GetPlanByID(id)
|
||||
plan, err := s.planRepo.GetPlanByID(planCtx, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
s.logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
|
||||
logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
|
||||
return nil, ErrPlanNotFound
|
||||
}
|
||||
s.logger.Errorf("%s: 数据库查询失败: %v, ID: %d", actionType, err, id)
|
||||
logger.Errorf("%s: 数据库查询失败: %v, ID: %d", actionType, err, id)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.Infof("%s: 获取计划详情成功, ID: %d", actionType, id)
|
||||
logger.Infof("%s: 获取计划详情成功, ID: %d", actionType, id)
|
||||
return plan, nil
|
||||
}
|
||||
|
||||
// ListPlans 获取计划列表,支持过滤和分页
|
||||
func (s *planServiceImpl) ListPlans(opts repository.ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error) {
|
||||
func (s *planServiceImpl) ListPlans(ctx context.Context, opts repository.ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error) {
|
||||
planCtx, logger := logs.Trace(ctx, s.ctx, "ListPlans")
|
||||
const actionType = "领域层:获取计划列表"
|
||||
|
||||
plans, total, err := s.planRepo.ListPlans(opts, page, pageSize)
|
||||
plans, total, err := s.planRepo.ListPlans(planCtx, opts, page, pageSize)
|
||||
if err != nil {
|
||||
s.logger.Errorf("%s: 数据库查询失败: %v", actionType, err)
|
||||
logger.Errorf("%s: 数据库查询失败: %v", actionType, err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
s.logger.Infof("%s: 获取计划列表成功, 数量: %d", actionType, len(plans))
|
||||
logger.Infof("%s: 获取计划列表成功, 数量: %d", actionType, len(plans))
|
||||
return plans, total, nil
|
||||
}
|
||||
|
||||
// UpdatePlan 更新计划
|
||||
func (s *planServiceImpl) UpdatePlan(planToUpdate *models.Plan) (*models.Plan, error) {
|
||||
func (s *planServiceImpl) UpdatePlan(ctx context.Context, planToUpdate *models.Plan) (*models.Plan, error) {
|
||||
planCtx, logger := logs.Trace(ctx, s.ctx, "UpdatePlan")
|
||||
const actionType = "领域层:更新计划"
|
||||
|
||||
existingPlan, err := s.planRepo.GetBasicPlanByID(planToUpdate.ID)
|
||||
existingPlan, err := s.planRepo.GetBasicPlanByID(planCtx, planToUpdate.ID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
s.logger.Warnf("%s: 计划不存在, ID: %d", actionType, planToUpdate.ID)
|
||||
logger.Warnf("%s: 计划不存在, ID: %d", actionType, planToUpdate.ID)
|
||||
return nil, ErrPlanNotFound
|
||||
}
|
||||
s.logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, planToUpdate.ID)
|
||||
logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, planToUpdate.ID)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 系统计划不允许修改
|
||||
if existingPlan.PlanType == models.PlanTypeSystem {
|
||||
s.logger.Warnf("%s: 尝试修改系统计划, ID: %d", actionType, planToUpdate.ID)
|
||||
logger.Warnf("%s: 尝试修改系统计划, ID: %d", actionType, planToUpdate.ID)
|
||||
return nil, ErrPlanCannotBeModified
|
||||
}
|
||||
|
||||
@@ -228,25 +237,25 @@ func (s *planServiceImpl) UpdatePlan(planToUpdate *models.Plan) (*models.Plan, e
|
||||
|
||||
// 验证和重排顺序 (领域逻辑)
|
||||
if err := planToUpdate.ValidateExecutionOrder(); err != nil {
|
||||
s.logger.Errorf("%s: 计划 (ID: %d) 的执行顺序无效: %v", actionType, planToUpdate.ID, err)
|
||||
logger.Errorf("%s: 计划 (ID: %d) 的执行顺序无效: %v", actionType, planToUpdate.ID, err)
|
||||
return nil, err
|
||||
}
|
||||
planToUpdate.ReorderSteps()
|
||||
|
||||
// 只要是更新任务,就重置执行计数器
|
||||
planToUpdate.ExecuteCount = 0
|
||||
s.logger.Infof("计划 #%d 被更新,执行计数器已重置为 0。", planToUpdate.ID)
|
||||
logger.Infof("计划 #%d 被更新,执行计数器已重置为 0。", planToUpdate.ID)
|
||||
|
||||
// 在调用仓库前,准备好所有数据,包括设备关联
|
||||
for i := range planToUpdate.Tasks {
|
||||
taskModel := &planToUpdate.Tasks[i]
|
||||
taskResolver, err := s.taskFactory.CreateTaskFromModel(taskModel)
|
||||
taskResolver, err := s.taskFactory.CreateTaskFromModel(planCtx, taskModel)
|
||||
if err != nil {
|
||||
s.logger.Warnf("跳过为任务类型 '%s' 解析设备ID: %v", taskModel.Type, err)
|
||||
logger.Warnf("跳过为任务类型 '%s' 解析设备ID: %v", taskModel.Type, err)
|
||||
continue
|
||||
}
|
||||
|
||||
deviceIDs, err := taskResolver.ResolveDeviceIDs()
|
||||
deviceIDs, err := taskResolver.ResolveDeviceIDs(planCtx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("为任务 '%s' 提取设备ID失败: %w", taskModel.Name, err)
|
||||
}
|
||||
@@ -261,85 +270,87 @@ func (s *planServiceImpl) UpdatePlan(planToUpdate *models.Plan) (*models.Plan, e
|
||||
}
|
||||
|
||||
// 调用仓库方法更新计划,该方法内部会处理事务
|
||||
err = s.planRepo.UpdatePlanMetadataAndStructure(planToUpdate)
|
||||
err = s.planRepo.UpdatePlanMetadataAndStructure(planCtx, planToUpdate)
|
||||
if err != nil {
|
||||
s.logger.Errorf("%s: 数据库更新计划失败: %v, Plan: %+v", actionType, err, planToUpdate)
|
||||
logger.Errorf("%s: 数据库更新计划失败: %v, Plan: %+v", actionType, err, planToUpdate)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.taskManager.EnsureAnalysisTaskDefinition(planToUpdate.ID); err != nil {
|
||||
s.logger.Errorf("为更新后的计划 %d 确保触发器任务定义失败: %v", planToUpdate.ID, err)
|
||||
if err := s.taskManager.EnsureAnalysisTaskDefinition(planCtx, planToUpdate.ID); err != nil {
|
||||
logger.Errorf("为更新后的计划 %d 确保触发器任务定义失败: %v", planToUpdate.ID, err)
|
||||
}
|
||||
|
||||
updatedPlan, err := s.planRepo.GetPlanByID(planToUpdate.ID)
|
||||
updatedPlan, err := s.planRepo.GetPlanByID(planCtx, planToUpdate.ID)
|
||||
if err != nil {
|
||||
s.logger.Errorf("%s: 获取更新后计划详情失败: %v, ID: %d", actionType, err, planToUpdate.ID)
|
||||
logger.Errorf("%s: 获取更新后计划详情失败: %v, ID: %d", actionType, err, planToUpdate.ID)
|
||||
return nil, errors.New("获取更新后计划详情时发生内部错误")
|
||||
}
|
||||
|
||||
s.logger.Infof("%s: 计划更新成功, ID: %d", actionType, updatedPlan.ID)
|
||||
logger.Infof("%s: 计划更新成功, ID: %d", actionType, updatedPlan.ID)
|
||||
return updatedPlan, nil
|
||||
}
|
||||
|
||||
// DeletePlan 删除计划(软删除)
|
||||
func (s *planServiceImpl) DeletePlan(id uint) error {
|
||||
func (s *planServiceImpl) DeletePlan(ctx context.Context, id uint) error {
|
||||
planCtx, logger := logs.Trace(ctx, s.ctx, "DeletePlan")
|
||||
const actionType = "领域层:删除计划"
|
||||
|
||||
plan, err := s.planRepo.GetBasicPlanByID(id)
|
||||
plan, err := s.planRepo.GetBasicPlanByID(planCtx, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
s.logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
|
||||
logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
|
||||
return ErrPlanNotFound
|
||||
}
|
||||
s.logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id)
|
||||
logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// 系统计划不允许删除
|
||||
if plan.PlanType == models.PlanTypeSystem {
|
||||
s.logger.Warnf("%s: 尝试删除系统计划, ID: %d", actionType, id)
|
||||
logger.Warnf("%s: 尝试删除系统计划, ID: %d", actionType, id)
|
||||
return ErrPlanCannotBeDeleted
|
||||
}
|
||||
|
||||
// 如果计划处于启用状态,先停止它
|
||||
if plan.Status == models.PlanStatusEnabled {
|
||||
if err := s.planRepo.StopPlanTransactionally(id); err != nil {
|
||||
s.logger.Errorf("%s: 停止计划失败: %v, ID: %d", actionType, err, id)
|
||||
if err := s.planRepo.StopPlanTransactionally(planCtx, id); err != nil {
|
||||
logger.Errorf("%s: 停止计划失败: %v, ID: %d", actionType, err, id)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.planRepo.DeletePlan(id); err != nil {
|
||||
s.logger.Errorf("%s: 数据库删除失败: %v, ID: %d", actionType, err, id)
|
||||
if err := s.planRepo.DeletePlan(planCtx, id); err != nil {
|
||||
logger.Errorf("%s: 数据库删除失败: %v, ID: %d", actionType, err, id)
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.Infof("%s: 计划删除成功, ID: %d", actionType, id)
|
||||
logger.Infof("%s: 计划删除成功, ID: %d", actionType, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartPlan 启动计划
|
||||
func (s *planServiceImpl) StartPlan(id uint) error {
|
||||
func (s *planServiceImpl) StartPlan(ctx context.Context, id uint) error {
|
||||
planCtx, logger := logs.Trace(ctx, s.ctx, "StartPlan")
|
||||
const actionType = "领域层:启动计划"
|
||||
|
||||
plan, err := s.planRepo.GetBasicPlanByID(id)
|
||||
plan, err := s.planRepo.GetBasicPlanByID(planCtx, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
s.logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
|
||||
logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
|
||||
return ErrPlanNotFound
|
||||
}
|
||||
s.logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id)
|
||||
logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// 系统计划不允许手动启动
|
||||
if plan.PlanType == models.PlanTypeSystem {
|
||||
s.logger.Warnf("%s: 尝试手动启动系统计划, ID: %d", actionType, id)
|
||||
logger.Warnf("%s: 尝试手动启动系统计划, ID: %d", actionType, id)
|
||||
return ErrPlanCannotBeStarted
|
||||
}
|
||||
// 计划已处于启动状态,无需重复操作
|
||||
if plan.Status == models.PlanStatusEnabled {
|
||||
s.logger.Warnf("%s: 计划已处于启动状态,无需重复操作, ID: %d", actionType, id)
|
||||
logger.Warnf("%s: 计划已处于启动状态,无需重复操作, ID: %d", actionType, id)
|
||||
return ErrPlanAlreadyEnabled
|
||||
}
|
||||
|
||||
@@ -347,63 +358,64 @@ func (s *planServiceImpl) StartPlan(id uint) error {
|
||||
if plan.Status != models.PlanStatusEnabled {
|
||||
// 如果执行计数器大于0,重置为0
|
||||
if plan.ExecuteCount > 0 {
|
||||
if err := s.planRepo.UpdateExecuteCount(plan.ID, 0); err != nil {
|
||||
s.logger.Errorf("%s: 重置计划执行计数失败: %v, ID: %d", actionType, err, plan.ID)
|
||||
if err := s.planRepo.UpdateExecuteCount(planCtx, plan.ID, 0); err != nil {
|
||||
logger.Errorf("%s: 重置计划执行计数失败: %v, ID: %d", actionType, err, plan.ID)
|
||||
return err
|
||||
}
|
||||
s.logger.Infof("计划 #%d 的执行计数器已重置为 0。", plan.ID)
|
||||
logger.Infof("计划 #%d 的执行计数器已重置为 0。", plan.ID)
|
||||
}
|
||||
|
||||
// 更新计划状态为启用
|
||||
if err := s.planRepo.UpdatePlanStatus(plan.ID, models.PlanStatusEnabled); err != nil {
|
||||
s.logger.Errorf("%s: 更新计划状态失败: %v, ID: %d", actionType, err, plan.ID)
|
||||
if err := s.planRepo.UpdatePlanStatus(planCtx, plan.ID, models.PlanStatusEnabled); err != nil {
|
||||
logger.Errorf("%s: 更新计划状态失败: %v, ID: %d", actionType, err, plan.ID)
|
||||
return err
|
||||
}
|
||||
s.logger.Infof("已成功更新计划 #%d 的状态为 '已启动'。", plan.ID)
|
||||
logger.Infof("已成功更新计划 #%d 的状态为 '已启动'。", plan.ID)
|
||||
}
|
||||
|
||||
// 创建或更新触发器
|
||||
if err := s.taskManager.CreateOrUpdateTrigger(plan.ID); err != nil {
|
||||
s.logger.Errorf("%s: 创建或更新触发器失败: %v, ID: %d", actionType, err, plan.ID)
|
||||
if err := s.taskManager.CreateOrUpdateTrigger(planCtx, plan.ID); err != nil {
|
||||
logger.Errorf("%s: 创建或更新触发器失败: %v, ID: %d", actionType, err, plan.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.Infof("%s: 计划已成功启动, ID: %d", actionType, id)
|
||||
logger.Infof("%s: 计划已成功启动, ID: %d", actionType, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopPlan 停止计划
|
||||
func (s *planServiceImpl) StopPlan(id uint) error {
|
||||
func (s *planServiceImpl) StopPlan(ctx context.Context, id uint) error {
|
||||
planCtx, logger := logs.Trace(ctx, s.ctx, "StopPlan")
|
||||
const actionType = "领域层:停止计划"
|
||||
|
||||
plan, err := s.planRepo.GetBasicPlanByID(id)
|
||||
plan, err := s.planRepo.GetBasicPlanByID(planCtx, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
s.logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
|
||||
logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
|
||||
return ErrPlanNotFound
|
||||
}
|
||||
s.logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id)
|
||||
logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// 系统计划不允许停止
|
||||
if plan.PlanType == models.PlanTypeSystem {
|
||||
s.logger.Warnf("%s: 尝试停止系统计划, ID: %d", actionType, id)
|
||||
logger.Warnf("%s: 尝试停止系统计划, ID: %d", actionType, id)
|
||||
return ErrPlanCannotBeStopped
|
||||
}
|
||||
|
||||
// 计划当前不是启用状态
|
||||
if plan.Status != models.PlanStatusEnabled {
|
||||
s.logger.Warnf("%s: 计划当前不是启用状态, ID: %d, Status: %s", actionType, id, plan.Status)
|
||||
logger.Warnf("%s: 计划当前不是启用状态, ID: %d, Status: %s", actionType, id, plan.Status)
|
||||
return ErrPlanNotEnabled
|
||||
}
|
||||
|
||||
// 停止计划事务性操作
|
||||
if err := s.planRepo.StopPlanTransactionally(id); err != nil {
|
||||
s.logger.Errorf("%s: 停止计划失败: %v, ID: %d", actionType, err, id)
|
||||
if err := s.planRepo.StopPlanTransactionally(planCtx, id); err != nil {
|
||||
logger.Errorf("%s: 停止计划失败: %v, ID: %d", actionType, err, id)
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.Infof("%s: 计划已成功停止, ID: %d", actionType, id)
|
||||
logger.Infof("%s: 计划已成功停止, ID: %d", actionType, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package plan
|
||||
|
||||
import "git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
)
|
||||
|
||||
// Task 定义了所有可被调度器执行的任务必须实现的接口。
|
||||
type Task interface {
|
||||
@@ -8,12 +12,12 @@ type Task interface {
|
||||
// ctx: 用于控制任务的超时或取消。
|
||||
// log: 包含了当前任务执行的完整上下文信息,包括从数据库中加载的任务参数等。
|
||||
// 返回的 error 表示任务是否执行成功。调度器会根据返回的 error 是否为 nil 来决定任务状态。
|
||||
Execute() error
|
||||
Execute(ctx context.Context) error
|
||||
|
||||
// OnFailure 定义了当 Execute 方法返回错误时,需要执行的回滚或清理逻辑。
|
||||
// log: 任务执行的上下文。
|
||||
// executeErr: 从 Execute 方法返回的原始错误。
|
||||
OnFailure(executeErr error)
|
||||
OnFailure(ctx context.Context, executeErr error)
|
||||
|
||||
TaskDeviceIDResolver
|
||||
}
|
||||
@@ -22,13 +26,13 @@ type Task interface {
|
||||
type TaskDeviceIDResolver interface {
|
||||
// ResolveDeviceIDs 从任务配置中解析并返回所有关联的设备ID列表
|
||||
// 返回值: uint数组,每个字符串代表一个设备ID
|
||||
ResolveDeviceIDs() ([]uint, error)
|
||||
ResolveDeviceIDs(ctx context.Context) ([]uint, error)
|
||||
}
|
||||
|
||||
// TaskFactory 是一个工厂接口,用于根据任务执行日志创建任务实例。
|
||||
type TaskFactory interface {
|
||||
// Production 根据指定的任务执行日志创建一个任务实例。
|
||||
Production(claimedLog *models.TaskExecutionLog) Task
|
||||
Production(ctx context.Context, claimedLog *models.TaskExecutionLog) Task
|
||||
// CreateTaskFromModel 仅根据任务模型创建一个任务实例,用于非执行场景(如参数解析)。
|
||||
CreateTaskFromModel(taskModel *models.Task) (TaskDeviceIDResolver, error)
|
||||
CreateTaskFromModel(ctx context.Context, taskModel *models.Task) (TaskDeviceIDResolver, error)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -15,45 +16,47 @@ type DelayTaskParams struct {
|
||||
|
||||
// DelayTask 是一个用于模拟延迟的 Task 实现
|
||||
type DelayTask struct {
|
||||
ctx context.Context
|
||||
executionTask *models.TaskExecutionLog
|
||||
duration time.Duration
|
||||
logger *logs.Logger
|
||||
}
|
||||
|
||||
func NewDelayTask(logger *logs.Logger, executionTask *models.TaskExecutionLog) plan.Task {
|
||||
func NewDelayTask(ctx context.Context, executionTask *models.TaskExecutionLog) plan.Task {
|
||||
return &DelayTask{
|
||||
ctx: ctx,
|
||||
executionTask: executionTask,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Execute 执行延迟任务,等待指定的时间
|
||||
func (d *DelayTask) Execute() error {
|
||||
if err := d.parseParameters(); err != nil {
|
||||
func (d *DelayTask) Execute(ctx context.Context) error {
|
||||
taskCtx, logger := logs.Trace(ctx, d.ctx, "Execute")
|
||||
if err := d.parseParameters(taskCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.logger.Infof("任务 %v: 开始延迟 %v...", d.executionTask.TaskID, d.duration)
|
||||
logger.Infof("任务 %v: 开始延迟 %v...", d.executionTask.TaskID, d.duration)
|
||||
time.Sleep(d.duration)
|
||||
d.logger.Infof("任务 %v: 延迟结束。", d.executionTask.TaskID)
|
||||
logger.Infof("任务 %v: 延迟结束。", d.executionTask.TaskID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DelayTask) parseParameters() error {
|
||||
func (d *DelayTask) parseParameters(ctx context.Context) error {
|
||||
logger := logs.TraceLogger(ctx, d.ctx, "parseParameters")
|
||||
if d.executionTask.Task.Parameters == nil {
|
||||
d.logger.Errorf("任务 %v: 缺少参数", d.executionTask.TaskID)
|
||||
logger.Errorf("任务 %v: 缺少参数", d.executionTask.TaskID)
|
||||
return fmt.Errorf("任务 %v: 参数不全", d.executionTask.TaskID)
|
||||
}
|
||||
|
||||
var params DelayTaskParams
|
||||
err := d.executionTask.Task.ParseParameters(¶ms)
|
||||
if err != nil {
|
||||
d.logger.Errorf("任务 %v: 解析参数失败: %v", d.executionTask.TaskID, err)
|
||||
logger.Errorf("任务 %v: 解析参数失败: %v", d.executionTask.TaskID, err)
|
||||
return fmt.Errorf("任务 %v: 解析参数失败: %v", d.executionTask.TaskID, err)
|
||||
}
|
||||
|
||||
if params.DelayDuration <= 0 {
|
||||
d.logger.Errorf("任务 %v: 参数 delay_duration 缺失或无效 (必须大于0)", d.executionTask.TaskID)
|
||||
logger.Errorf("任务 %v: 参数 delay_duration 缺失或无效 (必须大于0)", d.executionTask.TaskID)
|
||||
return fmt.Errorf("任务 %v: 参数 delay_duration 缺失或无效 (必须大于0)", d.executionTask.TaskID)
|
||||
}
|
||||
|
||||
@@ -62,10 +65,11 @@ func (d *DelayTask) parseParameters() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DelayTask) OnFailure(executeErr error) {
|
||||
d.logger.Errorf("任务 %v: 执行失败: %v", d.executionTask.TaskID, executeErr)
|
||||
func (d *DelayTask) OnFailure(ctx context.Context, executeErr error) {
|
||||
logger := logs.TraceLogger(ctx, d.ctx, "OnFailure")
|
||||
logger.Errorf("任务 %v: 执行失败: %v", d.executionTask.TaskID, executeErr)
|
||||
}
|
||||
|
||||
func (d *DelayTask) ResolveDeviceIDs() ([]uint, error) {
|
||||
func (d *DelayTask) ResolveDeviceIDs(ctx context.Context) ([]uint, error) {
|
||||
return []uint{}, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/device"
|
||||
@@ -12,38 +13,39 @@ import (
|
||||
|
||||
// FullCollectionTask 实现了 plan.Task 接口,用于执行一次全量的设备数据采集
|
||||
type FullCollectionTask struct {
|
||||
ctx context.Context
|
||||
log *models.TaskExecutionLog
|
||||
deviceRepo repository.DeviceRepository
|
||||
deviceService device.Service
|
||||
logger *logs.Logger
|
||||
}
|
||||
|
||||
// NewFullCollectionTask 创建一个全量采集任务实例
|
||||
func NewFullCollectionTask(
|
||||
ctx context.Context,
|
||||
log *models.TaskExecutionLog,
|
||||
deviceRepo repository.DeviceRepository,
|
||||
deviceService device.Service,
|
||||
logger *logs.Logger,
|
||||
) plan.Task {
|
||||
return &FullCollectionTask{
|
||||
ctx: ctx,
|
||||
log: log,
|
||||
deviceRepo: deviceRepo,
|
||||
deviceService: deviceService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Execute 是任务的核心执行逻辑
|
||||
func (t *FullCollectionTask) Execute() error {
|
||||
t.logger.Infow("开始执行全量采集任务", "task_id", t.log.TaskID, "task_type", t.log.Task.Type, "log_id", t.log.ID)
|
||||
func (t *FullCollectionTask) Execute(ctx context.Context) error {
|
||||
taskCtx, logger := logs.Trace(ctx, t.ctx, "Execute")
|
||||
logger.Infow("开始执行全量采集任务", "task_id", t.log.TaskID, "task_type", t.log.Task.Type, "log_id", t.log.ID)
|
||||
|
||||
sensors, err := t.deviceRepo.ListAllSensors()
|
||||
sensors, err := t.deviceRepo.ListAllSensors(taskCtx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("全量采集任务: 从数据库获取所有传感器失败: %w", err)
|
||||
}
|
||||
|
||||
if len(sensors) == 0 {
|
||||
t.logger.Infow("全量采集任务: 未发现任何传感器设备,跳过本次采集", "task_id", t.log.TaskID, "task_type", t.log.Task.Type, "log_id", t.log.ID)
|
||||
logger.Infow("全量采集任务: 未发现任何传感器设备,跳过本次采集", "task_id", t.log.TaskID, "task_type", t.log.Task.Type, "log_id", t.log.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -54,15 +56,15 @@ func (t *FullCollectionTask) Execute() error {
|
||||
|
||||
var firstError error
|
||||
for controllerID, controllerSensors := range sensorsByController {
|
||||
t.logger.Infow("全量采集任务: 准备为区域主控下的传感器下发采集指令",
|
||||
logger.Infow("全量采集任务: 准备为区域主控下的传感器下发采集指令",
|
||||
"task_id", t.log.TaskID,
|
||||
"task_type", t.log.Task.Type,
|
||||
"log_id", t.log.ID,
|
||||
"controller_id", controllerID,
|
||||
"sensor_count", len(controllerSensors),
|
||||
)
|
||||
if err := t.deviceService.Collect(controllerID, controllerSensors); err != nil {
|
||||
t.logger.Errorw("全量采集任务: 为区域主控下发采集指令失败",
|
||||
if err := t.deviceService.Collect(taskCtx, controllerID, controllerSensors); err != nil {
|
||||
logger.Errorw("全量采集任务: 为区域主控下发采集指令失败",
|
||||
"task_id", t.log.TaskID,
|
||||
"task_type", t.log.Task.Type,
|
||||
"log_id", t.log.ID,
|
||||
@@ -79,13 +81,14 @@ func (t *FullCollectionTask) Execute() error {
|
||||
return fmt.Errorf("全量采集任务执行期间发生错误: %w", firstError)
|
||||
}
|
||||
|
||||
t.logger.Infow("全量采集任务执行完成", "task_id", t.log.TaskID, "task_type", t.log.Task.Type, "log_id", t.log.ID)
|
||||
logger.Infow("全量采集任务执行完成", "task_id", t.log.TaskID, "task_type", t.log.Task.Type, "log_id", t.log.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnFailure 定义了当 Execute 方法返回错误时,需要执行的回滚或清理逻辑
|
||||
func (t *FullCollectionTask) OnFailure(executeErr error) {
|
||||
t.logger.Errorw("全量采集任务执行失败",
|
||||
func (t *FullCollectionTask) OnFailure(ctx context.Context, executeErr error) {
|
||||
logger := logs.TraceLogger(ctx, t.ctx, "OnFailure")
|
||||
logger.Errorw("全量采集任务执行失败",
|
||||
"task_id", t.log.TaskID,
|
||||
"task_type", t.log.Task.Type,
|
||||
"log_id", t.log.ID,
|
||||
@@ -94,7 +97,7 @@ func (t *FullCollectionTask) OnFailure(executeErr error) {
|
||||
}
|
||||
|
||||
// ResolveDeviceIDs 获取当前任务需要使用的设备ID列表
|
||||
func (t *FullCollectionTask) ResolveDeviceIDs() ([]uint, error) {
|
||||
func (t *FullCollectionTask) ResolveDeviceIDs(ctx context.Context) ([]uint, error) {
|
||||
// 全量采集任务不和任何设备绑定, 每轮采集都会重新获取全量传感器
|
||||
return []uint{}, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
@@ -22,6 +23,8 @@ type ReleaseFeedWeightTaskParams struct {
|
||||
|
||||
// ReleaseFeedWeightTask 是一个控制下料口释放指定重量的任务
|
||||
type ReleaseFeedWeightTask struct {
|
||||
ctx context.Context
|
||||
|
||||
deviceRepo repository.DeviceRepository
|
||||
sensorDataRepo repository.SensorDataRepository
|
||||
claimedLog *models.TaskExecutionLog
|
||||
@@ -34,40 +37,39 @@ type ReleaseFeedWeightTask struct {
|
||||
|
||||
// onceParse 保证解析参数只执行一次
|
||||
onceParse sync.Once
|
||||
|
||||
logger *logs.Logger
|
||||
}
|
||||
|
||||
// NewReleaseFeedWeightTask 创建一个新的 ReleaseFeedWeightTask 实例
|
||||
func NewReleaseFeedWeightTask(
|
||||
ctx context.Context,
|
||||
claimedLog *models.TaskExecutionLog,
|
||||
sensorDataRepo repository.SensorDataRepository,
|
||||
deviceRepo repository.DeviceRepository,
|
||||
deviceService device.Service,
|
||||
logger *logs.Logger,
|
||||
) plan.Task {
|
||||
return &ReleaseFeedWeightTask{
|
||||
ctx: ctx,
|
||||
claimedLog: claimedLog,
|
||||
deviceRepo: deviceRepo,
|
||||
sensorDataRepo: sensorDataRepo,
|
||||
feedPort: deviceService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ReleaseFeedWeightTask) Execute() error {
|
||||
r.logger.Infof("任务 %v: 开始执行, 日志ID: %v", r.claimedLog.TaskID, r.claimedLog.ID)
|
||||
if err := r.parseParameters(); err != nil {
|
||||
func (r *ReleaseFeedWeightTask) Execute(ctx context.Context) error {
|
||||
taskCtx, logger := logs.Trace(ctx, r.ctx, "Execute")
|
||||
logger.Infof("任务 %v: 开始执行, 日志ID: %v", r.claimedLog.TaskID, r.claimedLog.ID)
|
||||
if err := r.parseParameters(taskCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
weight, err := r.getNowWeight()
|
||||
weight, err := r.getNowWeight(taskCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = r.feedPort.Switch(r.feedPortDevice, device.DeviceActionStart); err != nil {
|
||||
r.logger.Errorf("启动下料口(id=%v)失败: %v , 日志ID: %v", r.feedPortDevice.ID, err, r.claimedLog.ID)
|
||||
if err = r.feedPort.Switch(taskCtx, r.feedPortDevice, device.DeviceActionStart); err != nil {
|
||||
logger.Errorf("启动下料口(id=%v)失败: %v , 日志ID: %v", r.feedPortDevice.ID, err, r.claimedLog.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -76,33 +78,34 @@ func (r *ReleaseFeedWeightTask) Execute() error {
|
||||
|
||||
// TODO 这个判断有延迟, 尤其是LoRa通信本身延迟较高, 可以考虑根据信号质量或其他指标提前发送停止命令
|
||||
for targetWeight <= weight {
|
||||
weight, err = r.getNowWeight()
|
||||
weight, err = r.getNowWeight(taskCtx)
|
||||
if err != nil {
|
||||
errCount++
|
||||
if errCount > 3 { // 如果连续三次没成功采集到重量数据,则认为计划执行失败
|
||||
r.logger.Errorf("获取当前计划执行日志(id=%v)的当前搅拌罐重量失败: %v, 任务结束", r.claimedLog.ID, err)
|
||||
logger.Errorf("获取当前计划执行日志(id=%v)的当前搅拌罐重量失败: %v, 任务结束", r.claimedLog.ID, err)
|
||||
return err
|
||||
}
|
||||
r.logger.Warnf("第%v次尝试获取当前计划执行日志(id=%v)的当前搅拌罐重量失败: %v", errCount, r.claimedLog.ID, err)
|
||||
logger.Warnf("第%v次尝试获取当前计划执行日志(id=%v)的当前搅拌罐重量失败: %v", errCount, r.claimedLog.ID, err)
|
||||
continue
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
if err = r.feedPort.Switch(r.feedPortDevice, device.DeviceActionStop); err != nil {
|
||||
r.logger.Errorf("关闭下料口(id=%v)失败: %v , 日志ID: %v", r.feedPortDevice.ID, err, r.claimedLog.ID)
|
||||
if err = r.feedPort.Switch(taskCtx, r.feedPortDevice, device.DeviceActionStop); err != nil {
|
||||
logger.Errorf("关闭下料口(id=%v)失败: %v , 日志ID: %v", r.feedPortDevice.ID, err, r.claimedLog.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Infof("完成计划执行日志(id=%v)的当前计划, 完成下料 %vkg, 搅拌罐剩余重量 %vkg", r.claimedLog.ID, r.releaseWeight, weight)
|
||||
logger.Infof("完成计划执行日志(id=%v)的当前计划, 完成下料 %vkg, 搅拌罐剩余重量 %vkg", r.claimedLog.ID, r.releaseWeight, weight)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取当前搅拌罐重量
|
||||
func (r *ReleaseFeedWeightTask) getNowWeight() (float64, error) {
|
||||
sensorData, err := r.sensorDataRepo.GetLatestSensorDataByDeviceIDAndSensorType(r.mixingTankDeviceID, models.SensorTypeWeight)
|
||||
func (r *ReleaseFeedWeightTask) getNowWeight(ctx context.Context) (float64, error) {
|
||||
taskCtx, logger := logs.Trace(ctx, r.ctx, "getNowWeight")
|
||||
sensorData, err := r.sensorDataRepo.GetLatestSensorDataByDeviceIDAndSensorType(taskCtx, r.mixingTankDeviceID, models.SensorTypeWeight)
|
||||
if err != nil {
|
||||
r.logger.Errorf("获取设备 %v 最新传感器数据失败: %v , 日志ID: %v", r.mixingTankDeviceID, err, r.claimedLog.ID)
|
||||
logger.Errorf("获取设备 %v 最新传感器数据失败: %v , 日志ID: %v", r.mixingTankDeviceID, err, r.claimedLog.ID)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -113,47 +116,48 @@ func (r *ReleaseFeedWeightTask) getNowWeight() (float64, error) {
|
||||
wg := &models.WeightData{}
|
||||
err = json.Unmarshal(sensorData.Data, wg)
|
||||
if err != nil {
|
||||
r.logger.Errorf("反序列化设备 %v 最新传感器数据失败: %v , 日志ID: %v", r.mixingTankDeviceID, err, r.claimedLog.ID)
|
||||
logger.Errorf("反序列化设备 %v 最新传感器数据失败: %v , 日志ID: %v", r.mixingTankDeviceID, err, r.claimedLog.ID)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return wg.WeightKilograms, nil
|
||||
}
|
||||
|
||||
func (r *ReleaseFeedWeightTask) parseParameters() error {
|
||||
func (r *ReleaseFeedWeightTask) parseParameters(ctx context.Context) error {
|
||||
taskCtx, logger := logs.Trace(ctx, r.ctx, "parseParameters")
|
||||
var err error
|
||||
r.onceParse.Do(func() {
|
||||
if r.claimedLog.Task.Parameters == nil {
|
||||
r.logger.Errorf("任务 %v: 缺少参数", r.claimedLog.TaskID)
|
||||
logger.Errorf("任务 %v: 缺少参数", r.claimedLog.TaskID)
|
||||
err = fmt.Errorf("任务 %v: 参数不全", r.claimedLog.TaskID)
|
||||
}
|
||||
|
||||
var params ReleaseFeedWeightTaskParams
|
||||
err := r.claimedLog.Task.ParseParameters(¶ms)
|
||||
if err != nil {
|
||||
r.logger.Errorf("任务 %v: 解析参数失败: %v", r.claimedLog.TaskID, err)
|
||||
logger.Errorf("任务 %v: 解析参数失败: %v", r.claimedLog.TaskID, err)
|
||||
err = fmt.Errorf("任务 %v: 解析参数失败: %v", r.claimedLog.TaskID, err)
|
||||
}
|
||||
|
||||
// 校验参数是否存在
|
||||
if params.ReleaseWeight == 0 {
|
||||
r.logger.Errorf("任务 %v: 参数 release_weight 缺失或无效", r.claimedLog.TaskID)
|
||||
logger.Errorf("任务 %v: 参数 release_weight 缺失或无效", r.claimedLog.TaskID)
|
||||
err = fmt.Errorf("任务 %v: 参数 release_weight 缺失或无效", r.claimedLog.TaskID)
|
||||
}
|
||||
if params.FeedPortDeviceID == 0 {
|
||||
r.logger.Errorf("任务 %v: 参数 feed_port_device_id 缺失或无效", r.claimedLog.TaskID)
|
||||
logger.Errorf("任务 %v: 参数 feed_port_device_id 缺失或无效", r.claimedLog.TaskID)
|
||||
err = fmt.Errorf("任务 %v: 参数 feed_port_device_id 缺失或无效", r.claimedLog.TaskID)
|
||||
}
|
||||
if params.MixingTankDeviceID == 0 {
|
||||
r.logger.Errorf("任务 %v: 参数 mixing_tank_device_id 缺失或无效", r.claimedLog.TaskID)
|
||||
logger.Errorf("任务 %v: 参数 mixing_tank_device_id 缺失或无效", r.claimedLog.TaskID)
|
||||
err = fmt.Errorf("任务 %v: 参数 mixing_tank_device_id 缺失或无效", r.claimedLog.TaskID)
|
||||
}
|
||||
|
||||
r.releaseWeight = params.ReleaseWeight
|
||||
r.mixingTankDeviceID = params.MixingTankDeviceID
|
||||
r.feedPortDevice, err = r.deviceRepo.FindByID(params.FeedPortDeviceID)
|
||||
r.feedPortDevice, err = r.deviceRepo.FindByID(taskCtx, params.FeedPortDeviceID)
|
||||
if err != nil {
|
||||
r.logger.Errorf("任务 %v: 获取设备信息失败: %v", r.claimedLog.TaskID, err)
|
||||
logger.Errorf("任务 %v: 获取设备信息失败: %v", r.claimedLog.TaskID, err)
|
||||
err = fmt.Errorf("任务 %v: 获取设备信息失败: %v", r.claimedLog.TaskID, err)
|
||||
}
|
||||
|
||||
@@ -161,21 +165,23 @@ func (r *ReleaseFeedWeightTask) parseParameters() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *ReleaseFeedWeightTask) OnFailure(executeErr error) {
|
||||
r.logger.Errorf("开始善后处理, 日志ID:%v; 错误信息: %v", r.claimedLog.ID, executeErr)
|
||||
func (r *ReleaseFeedWeightTask) OnFailure(ctx context.Context, executeErr error) {
|
||||
taskCtx, logger := logs.Trace(ctx, r.ctx, "OnFailure")
|
||||
logger.Errorf("开始善后处理, 日志ID:%v; 错误信息: %v", r.claimedLog.ID, executeErr)
|
||||
if r.feedPort != nil {
|
||||
err := r.feedPort.Switch(r.feedPortDevice, device.DeviceActionStop)
|
||||
err := r.feedPort.Switch(taskCtx, r.feedPortDevice, device.DeviceActionStop)
|
||||
if err != nil {
|
||||
r.logger.Errorf("[严重] 下料口停止失败, 日志ID: %v, 错误: %v", r.claimedLog.ID, err)
|
||||
logger.Errorf("[严重] 下料口停止失败, 日志ID: %v, 错误: %v", r.claimedLog.ID, err)
|
||||
}
|
||||
} else {
|
||||
r.logger.Warnf("[警告] 下料口通信器尚未初始化, 不进行任何操作, 日志ID: %v", r.claimedLog.ID)
|
||||
logger.Warnf("[警告] 下料口通信器尚未初始化, 不进行任何操作, 日志ID: %v", r.claimedLog.ID)
|
||||
}
|
||||
r.logger.Errorf("善后处理完成, 日志ID:%v", r.claimedLog.ID)
|
||||
logger.Errorf("善后处理完成, 日志ID:%v", r.claimedLog.ID)
|
||||
}
|
||||
|
||||
func (r *ReleaseFeedWeightTask) ResolveDeviceIDs() ([]uint, error) {
|
||||
if err := r.parseParameters(); err != nil {
|
||||
func (r *ReleaseFeedWeightTask) ResolveDeviceIDs(ctx context.Context) ([]uint, error) {
|
||||
taskCtx := logs.AddFuncName(ctx, r.ctx, "ResolveDeviceIDs")
|
||||
if err := r.parseParameters(taskCtx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []uint{r.feedPortDevice.ID, r.mixingTankDeviceID}, nil
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/device"
|
||||
@@ -10,61 +11,70 @@ import (
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
CompNameDelayTask = "DelayTask"
|
||||
CompNameReleaseFeedWeight = "ReleaseFeedWeightTask"
|
||||
CompNameFullCollectionTask = "FullCollectionTask"
|
||||
)
|
||||
|
||||
type taskFactory struct {
|
||||
logger *logs.Logger
|
||||
ctx context.Context
|
||||
sensorDataRepo repository.SensorDataRepository
|
||||
deviceRepo repository.DeviceRepository
|
||||
deviceService device.Service
|
||||
}
|
||||
|
||||
func NewTaskFactory(
|
||||
logger *logs.Logger,
|
||||
ctx context.Context,
|
||||
sensorDataRepo repository.SensorDataRepository,
|
||||
deviceRepo repository.DeviceRepository,
|
||||
deviceService device.Service,
|
||||
) plan.TaskFactory {
|
||||
return &taskFactory{
|
||||
logger: logger,
|
||||
ctx: ctx,
|
||||
sensorDataRepo: sensorDataRepo,
|
||||
deviceRepo: deviceRepo,
|
||||
deviceService: deviceService,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *taskFactory) Production(claimedLog *models.TaskExecutionLog) plan.Task {
|
||||
func (t *taskFactory) Production(ctx context.Context, claimedLog *models.TaskExecutionLog) plan.Task {
|
||||
logger := logs.TraceLogger(ctx, t.ctx, "Production")
|
||||
baseCtx := context.Background()
|
||||
switch claimedLog.Task.Type {
|
||||
case models.TaskTypeWaiting:
|
||||
return NewDelayTask(t.logger, claimedLog)
|
||||
return NewDelayTask(logs.AddCompName(baseCtx, CompNameDelayTask), claimedLog)
|
||||
case models.TaskTypeReleaseFeedWeight:
|
||||
return NewReleaseFeedWeightTask(claimedLog, t.sensorDataRepo, t.deviceRepo, t.deviceService, t.logger)
|
||||
return NewReleaseFeedWeightTask(logs.AddCompName(baseCtx, CompNameReleaseFeedWeight), claimedLog, t.sensorDataRepo, t.deviceRepo, t.deviceService)
|
||||
case models.TaskTypeFullCollection:
|
||||
return NewFullCollectionTask(claimedLog, t.deviceRepo, t.deviceService, t.logger)
|
||||
return NewFullCollectionTask(logs.AddCompName(baseCtx, CompNameFullCollectionTask), claimedLog, t.deviceRepo, t.deviceService)
|
||||
default:
|
||||
// TODO 这里直接panic合适吗? 不过这个场景确实不该出现任何异常的任务类型
|
||||
t.logger.Panicf("不支持的任务类型: %s", claimedLog.Task.Type)
|
||||
logger.Panicf("不支持的任务类型: %s", claimedLog.Task.Type)
|
||||
panic("不支持的任务类型") // 显式panic防编译器报错
|
||||
}
|
||||
}
|
||||
|
||||
// CreateTaskFromModel 实现了 TaskFactory 接口,用于从模型创建任务实例。
|
||||
func (t *taskFactory) CreateTaskFromModel(taskModel *models.Task) (plan.TaskDeviceIDResolver, error) {
|
||||
func (t *taskFactory) CreateTaskFromModel(ctx context.Context, taskModel *models.Task) (plan.TaskDeviceIDResolver, error) {
|
||||
// 这个方法不关心 claimedLog 的其他字段,所以可以构造一个临时的
|
||||
// 它只用于访问那些不依赖于执行日志的方法,比如 ResolveDeviceIDs
|
||||
tempLog := &models.TaskExecutionLog{Task: *taskModel}
|
||||
baseCtx := context.Background()
|
||||
|
||||
switch taskModel.Type {
|
||||
case models.TaskTypeWaiting:
|
||||
return NewDelayTask(t.logger, tempLog), nil
|
||||
return NewDelayTask(logs.AddCompName(baseCtx, CompNameDelayTask), tempLog), nil
|
||||
case models.TaskTypeReleaseFeedWeight:
|
||||
return NewReleaseFeedWeightTask(
|
||||
logs.AddCompName(baseCtx, CompNameReleaseFeedWeight),
|
||||
tempLog,
|
||||
t.sensorDataRepo,
|
||||
t.deviceRepo,
|
||||
t.deviceService,
|
||||
t.logger,
|
||||
), nil
|
||||
case models.TaskTypeFullCollection:
|
||||
return NewFullCollectionTask(tempLog, t.deviceRepo, t.deviceService, t.logger), nil
|
||||
return NewFullCollectionTask(logs.AddCompName(baseCtx, CompNameFullCollectionTask), tempLog, t.deviceRepo, t.deviceService), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("不支持为类型 '%s' 的任务创建模型实例", taskModel.Type)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap/zapcore"
|
||||
@@ -38,7 +39,7 @@ type AlarmContent struct {
|
||||
// Notifier 定义了通知发送器的接口
|
||||
type Notifier interface {
|
||||
// Send 发送通知
|
||||
Send(content AlarmContent, toAddr string) error
|
||||
Send(ctx context.Context, content AlarmContent, toAddr string) error
|
||||
// Type 返回通知器的类型
|
||||
Type() NotifierType
|
||||
}
|
||||
|
||||
@@ -13,24 +13,24 @@ type Claims struct {
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
// Service 定义了 token 操作的接口
|
||||
type Service interface {
|
||||
// Generator 定义了 token 操作的接口
|
||||
type Generator interface {
|
||||
GenerateToken(userID uint) (string, error)
|
||||
ParseToken(tokenString string) (*Claims, error)
|
||||
}
|
||||
|
||||
// tokenService 是 Service 接口的实现
|
||||
type tokenService struct {
|
||||
// tokenGenerator 是 Generator 接口的实现
|
||||
type tokenGenerator struct {
|
||||
secret []byte
|
||||
}
|
||||
|
||||
// NewTokenService 创建并返回一个新的 Service 实例
|
||||
func NewTokenService(secret []byte) Service {
|
||||
return &tokenService{secret: secret}
|
||||
// NewTokenGenerator 创建并返回一个新的 Generator 实例
|
||||
func NewTokenGenerator(secret []byte) Generator {
|
||||
return &tokenGenerator{secret: secret}
|
||||
}
|
||||
|
||||
// GenerateToken 生成一个新的 JWT token
|
||||
func (s *tokenService) GenerateToken(userID uint) (string, error) {
|
||||
func (s *tokenGenerator) GenerateToken(userID uint) (string, error) {
|
||||
nowTime := time.Now()
|
||||
expireTime := nowTime.Add(24 * time.Hour) // Token 有效期为 24 小时
|
||||
|
||||
@@ -48,7 +48,7 @@ func (s *tokenService) GenerateToken(userID uint) (string, error) {
|
||||
}
|
||||
|
||||
// ParseToken 解析并验证 JWT token
|
||||
func (s *tokenService) ParseToken(tokenString string) (*Claims, error) {
|
||||
func (s *tokenGenerator) ParseToken(tokenString string) (*Claims, error) {
|
||||
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return s.secret, nil
|
||||
})
|
||||
Reference in New Issue
Block a user