修改domain包

This commit is contained in:
2025-11-05 21:40:19 +08:00
parent 203be4307d
commit 07d8c719ac
28 changed files with 943 additions and 793 deletions

View File

@@ -1,266 +1,266 @@
- **`internal/domain/audit/service.go` (`audit.Service`)** - **`internal/domain/audit/service.go` (`audit.Service`)**
- **结构体改造**: - **结构体改造**:
- [ ] 移除 `service` 结构体中的 `logger *logs.Logger` 成员。 - [x] 移除 `service` 结构体中的 `logger *logs.Logger` 成员。
- [ ] 新增 `selfCtx context.Context` 成员。 - [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewService`)**: - **构造函数改造 (`NewService`)**:
- [ ] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context` - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [ ] 在函数内部,为 `audit.Service` 创建其专属的 `selfCtx``selfCtx := logs.AddCompName(ctx, "AuditService")` - [x] 在函数内部,为 `audit.Service` 创建其专属的 `selfCtx``selfCtx := logs.AddCompName(ctx, "AuditService")`
- [ ] 将这个 `selfCtx` 赋值给 `service` 结构体的 `selfCtx` 成员。 - [x] 将这个 `selfCtx` 赋值给 `service` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`LogAction`)**: - **公共方法改造 (`LogAction`)**:
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "LogAction")` 获取新的 `context.Context` - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "LogAction")` 获取新的 `context.Context`
`logger` 实例。 `logger` 实例。
- [ ] 将所有对 `s.logger.Warnw``s.logger.Errorw` 的调用替换为 `logger.Warnw``logger.Errorw` - [x] 将所有对 `s.logger.Warnw``s.logger.Errorw` 的调用替换为 `logger.Warnw``logger.Errorw`
- [ ] 确保所有对 `s.userActionLogRepository.Create` 的调用都将 `newCtx` 作为第一个参数传递。 - [x] 确保所有对 `s.userActionLogRepository.Create` 的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/domain/device/general_device_service.go` (`device.Service`)** - **`internal/domain/device/general_device_service.go` (`device.Service`)**
- **结构体改造**: - **结构体改造**:
- [ ] 移除 `GeneralDeviceService` 结构体中的 `logger *logs.Logger` 成员。 - [x] 移除 `GeneralDeviceService` 结构体中的 `logger *logs.Logger` 成员。
- [ ] 新增 `selfCtx context.Context` 成员。 - [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGeneralDeviceService`)**: - **构造函数改造 (`NewGeneralDeviceService`)**:
- [ ] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context` - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [ ] 在函数内部,为 `GeneralDeviceService` 创建其专属的 `selfCtx` - [x] 在函数内部,为 `GeneralDeviceService` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "GeneralDeviceService")` `selfCtx := logs.AddCompName(ctx, "GeneralDeviceService")`
- [ ] 将这个 `selfCtx` 赋值给 `GeneralDeviceService` 结构体的 `selfCtx` 成员。 - [x] 将这个 `selfCtx` 赋值给 `GeneralDeviceService` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Switch`, `Collect`)**: - **公共方法改造 (`Switch`, `Collect`)**:
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, g.selfCtx, "MethodName")` 获取新的 - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, g.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。 `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` 的调用替换为 `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` 作为第一个参数传递。 `newCtx` 作为第一个参数传递。
- **`internal/domain/notify/notify.go` (`domain_notify.Service` - `failoverService` 实现)** - **`internal/domain/notify/notify.go` (`domain_notify.Service` - `failoverService` 实现)**
- **结构体改造**: - **结构体改造**:
- [ ] 移除 `failoverService` 结构体中的 `log *logs.Logger` 成员。 - [x] 移除 `failoverService` 结构体中的 `log *logs.Logger` 成员。
- [ ] 新增 `selfCtx context.Context` 成员。 - [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewFailoverService`)**: - **构造函数改造 (`NewFailoverService`)**:
- [ ] 修改函数签名,移除 `log *logs.Logger` 参数,改为接收 `ctx context.Context` - [x] 修改函数签名,移除 `log *logs.Logger` 参数,改为接收 `ctx context.Context`
- [ ] 在函数内部,为 `failoverService` 创建其专属的 `selfCtx` - [x] 在函数内部,为 `failoverService` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "FailoverService")` `selfCtx := logs.AddCompName(ctx, "FailoverService")`
- [ ] 将这个 `selfCtx` 赋值给 `failoverService` 结构体的 `selfCtx` 成员。 - [x] 将这个 `selfCtx` 赋值给 `failoverService` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`SendBatchAlarm`, `BroadcastAlarm`, `SendTestMessage`)**: - **公共方法改造 (`SendBatchAlarm`, `BroadcastAlarm`, `SendTestMessage`)**:
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。 `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` `logger.Warnw`
- [ ] 确保所有对 `s.userRepo`, `s.primaryNotifier.Send`, `s.notifiers`, `s.notificationRepo` 等依赖的调用都将 - [x] 确保所有对 `s.userRepo`, `s.primaryNotifier.Send`, `s.notifiers`, `s.notificationRepo` 等依赖的调用都将
`newCtx` 作为第一个参数传递。 `newCtx` 作为第一个参数传递。
- **内部辅助方法改造 (`sendAlarmToUser`, `recordNotificationAttempt`)**: - **内部辅助方法改造 (`sendAlarmToUser`, `recordNotificationAttempt`)**:
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。 `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` `logger.Warnw`
- [ ] 确保所有对 `s.userRepo`, `s.primaryNotifier.Send`, `s.notifiers`, `s.notificationRepo` 等依赖的调用都将 - [x] 确保所有对 `s.userRepo`, `s.primaryNotifier.Send`, `s.notifiers`, `s.notificationRepo` 等依赖的调用都将
`newCtx` 作为第一个参数传递。 `newCtx` 作为第一个参数传递。
- **`internal/domain/pig/pen_transfer_manager.go` (`pig.PigPenTransferManager`)** - **`internal/domain/pig/pen_transfer_manager.go` (`pig.PigPenTransferManager`)**
- **结构体改造**: - **结构体改造**:
- [ ] 移除 `pigPenTransferManager` 结构体中可能存在的 `logger *logs.Logger` 成员(如果未来添加)。 - [x] 移除 `pigPenTransferManager` 结构体中可能存在的 `logger *logs.Logger` 成员(如果未来添加)。
- [ ] 新增 `selfCtx context.Context` 成员。 - [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewPigPenTransferManager`)**: - **构造函数改造 (`NewPigPenTransferManager`)**:
- [ ] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在函数内部,为 `pigPenTransferManager` 创建其专属的 `selfCtx` - [x] 在函数内部,为 `pigPenTransferManager` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PigPenTransferManager")` `selfCtx := logs.AddCompName(ctx, "PigPenTransferManager")`
- [ ] 将这个 `selfCtx` 赋值给 `pigPenTransferManager` 结构体的 `selfCtx` 成员。 - [x] 将这个 `selfCtx` 赋值给 `pigPenTransferManager` 结构体的 `selfCtx` 成员。
- **公共方法改造 ( - **公共方法改造 (
所有方法,例如 `LogTransfer`, `GetPenByID`, `GetPensByBatchID`, `UpdatePenFields`, `GetCurrentPigsInPen`, 所有方法,例如 `LogTransfer`, `GetPenByID`, `GetPensByBatchID`, `UpdatePenFields`, `GetCurrentPigsInPen`,
`GetTotalPigsInPensForBatchTx`, `ReleasePen`)**: `GetTotalPigsInPensForBatchTx`, `ReleasePen`)**:
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(在 `tx *gorm.DB` 之前)。 - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(在 `tx *gorm.DB` 之前)。
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。 `context.Context``logger` 实例。
- [ ] 将所有对 `s.logger.Errorf``s.logger.Infof` 的调用替换为 `logger.Errorf``logger.Infof` - [x] 将所有对 `s.logger.Errorf``s.logger.Infof` 的调用替换为 `logger.Errorf``logger.Infof`
- [ ] 确保所有对 `s.penRepo`, `s.logRepo`, `s.pigBatchRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。 - [x] 确保所有对 `s.penRepo`, `s.logRepo`, `s.pigBatchRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/domain/pig/pig_trade_manager.go` (`pig.PigTradeManager`)** - **`internal/domain/pig/pig_trade_manager.go` (`pig.PigTradeManager`)**
- **结构体改造**: - **结构体改造**:
- [ ] 新增 `selfCtx context.Context` 成员。 - [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewPigTradeManager`)**: - **构造函数改造 (`NewPigTradeManager`)**:
- [ ] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在函数内部,为 `pigTradeManager` 创建其专属的 `selfCtx` - [x] 在函数内部,为 `pigTradeManager` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PigTradeManager")` `selfCtx := logs.AddCompName(ctx, "PigTradeManager")`
- [ ] 将这个 `selfCtx` 赋值给 `pigTradeManager` 结构体的 `selfCtx` 成员。 - [x] 将这个 `selfCtx` 赋值给 `pigTradeManager` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`SellPig`, `BuyPig`)**: - **公共方法改造 (`SellPig`, `BuyPig`)**:
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(在 `tx *gorm.DB` 之前)。 - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(在 `tx *gorm.DB` 之前)。
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。 `context.Context``logger` 实例。
- [ ] 确保所有对 `s.tradeRepo` 的调用都将 `newCtx` 作为第一个参数传递。 - [x] 确保所有对 `s.tradeRepo` 的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/domain/pig/pig_sick_manager.go` (`pig.SickPigManager`)** - **`internal/domain/pig/pig_sick_manager.go` (`pig.SickPigManager`)**
- **结构体改造**: - **结构体改造**:
- [ ] 新增 `selfCtx context.Context` 成员。 - [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewSickPigManager`)**: - **构造函数改造 (`NewSickPigManager`)**:
- [ ] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在函数内部,为 `sickPigManager` 创建其专属的 `selfCtx` - [x] 在函数内部,为 `sickPigManager` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "SickPigManager")` `selfCtx := logs.AddCompName(ctx, "SickPigManager")`
- [ ] 将这个 `selfCtx` 赋值给 `sickPigManager` 结构体的 `selfCtx` 成员。 - [x] 将这个 `selfCtx` 赋值给 `sickPigManager` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`ProcessSickPigLog`, `GetCurrentSickPigCount`)**: - **公共方法改造 (`ProcessSickPigLog`, `GetCurrentSickPigCount`)**:
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(在 `tx *gorm.DB` 之前)。 - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(在 `tx *gorm.DB` 之前)。
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。 `context.Context``logger` 实例。
- [ ] 确保所有对 `s.sickLogRepo`, `s.medicationLogRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。 - [x] 确保所有对 `s.sickLogRepo`, `s.medicationLogRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/domain/pig/pig_batch_service.go` (`pig.PigBatchService`)** - **`internal/domain/pig/pig_batch_service.go` (`pig.PigBatchService`)**
- **结构体改造**: - **结构体改造**:
- [ ] 新增 `selfCtx context.Context` 成员。 - [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewPigBatchService`)**: - **构造函数改造 (`NewPigBatchService`)**:
- [ ] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在函数内部,为 `pigBatchService` 创建其专属的 `selfCtx` - [x] 在函数内部,为 `pigBatchService` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PigBatchService")` `selfCtx := logs.AddCompName(ctx, "PigBatchService")`
- [ ] 将这个 `selfCtx` 赋值给 `pigBatchService` 结构体的 `selfCtx` 成员。 - [x] 将这个 `selfCtx` 赋值给 `pigBatchService` 结构体的 `selfCtx` 成员。
- **公共方法改造 ( - **公共方法改造 (
所有方法,例如 `CreatePigBatch`, `GetPigBatch`, `UpdatePigBatch`, `DeletePigBatch`, `ListPigBatches`, 所有方法,例如 `CreatePigBatch`, `GetPigBatch`, `UpdatePigBatch`, `DeletePigBatch`, `ListPigBatches`,
`AssignEmptyPensToBatch`, `MovePigsIntoPen`, `ReclassifyPenToNewBatch`, `RemoveEmptyPenFromBatch`, `AssignEmptyPensToBatch`, `MovePigsIntoPen`, `ReclassifyPenToNewBatch`, `RemoveEmptyPenFromBatch`,
`GetCurrentPigQuantity`, `GetCurrentPigsInPen`, `GetTotalPigsInPensForBatch`, `UpdatePigBatchQuantity`, `GetCurrentPigQuantity`, `GetCurrentPigsInPen`, `GetTotalPigsInPensForBatch`, `UpdatePigBatchQuantity`,
`SellPigs`, `BuyPigs`, `TransferPigsAcrossBatches`, `TransferPigsWithinBatch`, `RecordSickPigs`, `SellPigs`, `BuyPigs`, `TransferPigsAcrossBatches`, `TransferPigsWithinBatch`, `RecordSickPigs`,
`RecordSickPigRecovery`, `RecordSickPigDeath`, `RecordSickPigCull`, `RecordDeath`, `RecordCull`)**: `RecordSickPigRecovery`, `RecordSickPigDeath`, `RecordSickPigCull`, `RecordDeath`, `RecordCull`)**:
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。 `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` 作为第一个参数传递。 等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/domain/plan/analysis_plan_task_manager.go` (`plan.AnalysisPlanTaskManager`)** - **`internal/domain/plan/analysis_plan_task_manager.go` (`plan.AnalysisPlanTaskManager`)**
- **结构体改造**: - **结构体改造**:
- [ ] 移除 `analysisPlanTaskManagerImpl` 结构体中的 `logger *logs.Logger` 成员。 - [x] 移除 `analysisPlanTaskManagerImpl` 结构体中的 `logger *logs.Logger` 成员。
- [ ] 新增 `selfCtx context.Context` 成员。 - [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewAnalysisPlanTaskManager`)**: - **构造函数改造 (`NewAnalysisPlanTaskManager`)**:
- [ ] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context` - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [ ] 在函数内部,为 `analysisPlanTaskManagerImpl` 创建其专属的 `selfCtx` - [x] 在函数内部,为 `analysisPlanTaskManagerImpl` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "AnalysisPlanTaskManager")` `selfCtx := logs.AddCompName(ctx, "AnalysisPlanTaskManager")`
- [ ] 将这个 `selfCtx` 赋值给 `analysisPlanTaskManagerImpl` 结构体的 `selfCtx` 成员。 - [x] 将这个 `selfCtx` 赋值给 `analysisPlanTaskManagerImpl` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Refresh`, `CreateOrUpdateTrigger`, `EnsureAnalysisTaskDefinition`)**: - **公共方法改造 (`Refresh`, `CreateOrUpdateTrigger`, `EnsureAnalysisTaskDefinition`)**:
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, m.selfCtx, "MethodName")` 获取新的 - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, m.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。 `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` `logger.Errorf`, `logger.Warnf`
- [ ] 确保所有对 `m.planRepo`, `m.pendingTaskRepo`, `m.executionLogRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。 - [x] 确保所有对 `m.planRepo`, `m.pendingTaskRepo`, `m.executionLogRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **内部辅助方法改造 (`getRefreshData`, `cleanupInvalidTasks`, `addOrUpdateTriggers`, `createTriggerTask`)**: - **内部辅助方法改造 (`getRefreshData`, `cleanupInvalidTasks`, `addOrUpdateTriggers`, `createTriggerTask`)**:
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, m.selfCtx, "MethodName")` 获取新的 - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, m.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。 `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` `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`)** - **`internal/domain/plan/plan_execution_manager.go` (`plan.ExecutionManager`)**
- **结构体改造**: - **结构体改造**:
- [ ] 移除 `planExecutionManagerImpl` 结构体中的 `logger *logs.Logger` 成员。 - [x] 移除 `planExecutionManagerImpl` 结构体中的 `logger *logs.Logger` 成员。
- [ ] 新增 `selfCtx context.Context` 成员。 - [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewPlanExecutionManager`)**: - **构造函数改造 (`NewPlanExecutionManager`)**:
- [ ] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context` - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [ ] 在函数内部,为 `planExecutionManagerImpl` 创建其专属的 `selfCtx` - [x] 在函数内部,为 `planExecutionManagerImpl` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PlanExecutionManager")` `selfCtx := logs.AddCompName(ctx, "PlanExecutionManager")`
- [ ] 将这个 `selfCtx` 赋值给 `planExecutionManagerImpl` 结构体的 `selfCtx` 成员。 - [x] 将这个 `selfCtx` 赋值给 `planExecutionManagerImpl` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Start`, `Stop`)**: - **公共方法改造 (`Start`, `Stop`)**:
- [ ] 在 `Start` 方法中,使用 `newCtx, logger := logs.Trace(context.Background(), s.selfCtx, "Start")` 获取 - [x] 在 `Start` 方法中,使用 `newCtx, logger := logs.Trace(context.Background(), s.selfCtx, "Start")` 获取
`logger` 实例进行日志记录。 `logger` 实例进行日志记录。
- [ ] 在 `Stop` 方法中,使用 `newCtx, logger := logs.Trace(context.Background(), s.selfCtx, "Stop")` 获取 - [x] 在 `Stop` 方法中,使用 `newCtx, logger := logs.Trace(context.Background(), s.selfCtx, "Stop")` 获取
`logger` 实例进行日志记录。 `logger` 实例进行日志记录。
- **内部辅助方法改造 (`run`, `claimAndSubmit`, `handleRequeue`, `processTask`, `runTask`, `analysisPlan`, - **内部辅助方法改造 (`run`, `claimAndSubmit`, `handleRequeue`, `processTask`, `runTask`, `analysisPlan`,
`updateTaskExecutionLogStatus`, `handlePlanTermination`, `handlePlanCompletion`)**: `updateTaskExecutionLogStatus`, `handlePlanTermination`, `handlePlanCompletion`)**:
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(如果方法内部需要传递上下文)。 - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(如果方法内部需要传递上下文)。
- [ ] 在每个方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 - [x] 在每个方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。 `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` `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` 作为第一个参数传递。 `s.analysisPlanTaskManager`, `s.taskFactory`, `s.deviceService` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/domain/plan/plan_service.go` (`plan.Service`)** - **`internal/domain/plan/plan_service.go` (`plan.Service`)**
- **结构体改造**: - **结构体改造**:
- [ ] 移除 `planServiceImpl` 结构体中的 `logger *logs.Logger` 成员。 - [x] 移除 `planServiceImpl` 结构体中的 `logger *logs.Logger` 成员。
- [ ] 新增 `selfCtx context.Context` 成员。 - [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewPlanService`)**: - **构造函数改造 (`NewPlanService`)**:
- [ ] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context` - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [ ] 在函数内部,为 `planServiceImpl` 创建其专属的 `selfCtx` - [x] 在函数内部,为 `planServiceImpl` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PlanService")` `selfCtx := logs.AddCompName(ctx, "PlanService")`
- [ ] 将这个 `selfCtx` 赋值给 `planServiceImpl` 结构体的 `selfCtx` 成员。 - [x] 将这个 `selfCtx` 赋值给 `planServiceImpl` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Start`, `Stop`, `RefreshPlanTriggers`, `CreatePlan`, `GetPlanByID`, `ListPlans`, `UpdatePlan`, - **公共方法改造 (`Start`, `Stop`, `RefreshPlanTriggers`, `CreatePlan`, `GetPlanByID`, `ListPlans`, `UpdatePlan`,
`DeletePlan`, `StartPlan`, `StopPlan`)**: `DeletePlan`, `StartPlan`, `StopPlan`)**:
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。 `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` `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` 作为第一个参数传递。 `s.taskFactory` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/domain/task/task.go` (`task.TaskFactory`)** - **`internal/domain/task/task.go` (`task.TaskFactory`)**
- **结构体改造**: - **结构体改造**:
- [ ] 移除 `taskFactory` 结构体中的 `logger *logs.Logger` 成员。 - [x] 移除 `taskFactory` 结构体中的 `logger *logs.Logger` 成员。
- [ ] 新增 `selfCtx context.Context` 成员。 - [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewTaskFactory`)**: - **构造函数改造 (`NewTaskFactory`)**:
- [ ] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在函数内部,为 `taskFactory` 创建其专属的 `selfCtx``selfCtx := logs.AddCompName(ctx, "TaskFactory")` - [x] 在函数内部,为 `taskFactory` 创建其专属的 `selfCtx``selfCtx := logs.AddCompName(ctx, "TaskFactory")`
- [ ] 将这个 `selfCtx` 赋值给 `taskFactory` 结构体的 `selfCtx` 成员。 - [x] 将这个 `selfCtx` 赋值给 `taskFactory` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Production`, `CreateTaskFromModel`)**: - **公共方法改造 (`Production`, `CreateTaskFromModel`)**:
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在方法内部,使用 `newCtx, logger := logs.Trace(ctx, t.selfCtx, "MethodName")` 获取新的 `context.Context` - [x] 在方法内部,使用 `newCtx, logger := logs.Trace(ctx, t.selfCtx, "MethodName")` 获取新的 `context.Context`
`logger` 实例。 `logger` 实例。
- [ ] 将所有对 `t.logger.Panicf` 的调用替换为 `logger.Panicf` - [x] 将所有对 `t.logger.Panicf` 的调用替换为 `logger.Panicf`
- [ ] 将所有对 `NewDelayTask`, `NewReleaseFeedWeightTask`, `NewFullCollectionTask` 的调用,传递 `newCtx` - [x] 将所有对 `NewDelayTask`, `NewReleaseFeedWeightTask`, `NewFullCollectionTask` 的调用,传递 `newCtx`
- **`internal/domain/task/release_feed_weight_task.go`** - **`internal/domain/task/release_feed_weight_task.go`**
- **结构体改造**: - **结构体改造**:
- [ ] 移除 `ReleaseFeedWeightTask` 结构体中的 `logger *logs.Logger` 成员。 - [x] 移除 `ReleaseFeedWeightTask` 结构体中的 `logger *logs.Logger` 成员。
- [ ] 新增 `selfCtx context.Context` 成员。 - [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewReleaseFeedWeightTask`)**: - **构造函数改造 (`NewReleaseFeedWeightTask`)**:
- [ ] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在函数内部,为 `ReleaseFeedWeightTask` 创建其专属的 `selfCtx` - [x] 在函数内部,为 `ReleaseFeedWeightTask` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "ReleaseFeedWeightTask")` `selfCtx := logs.AddCompName(ctx, "ReleaseFeedWeightTask")`
- [ ] 将这个 `selfCtx` 赋值给 `ReleaseFeedWeightTask` 结构体的 `selfCtx` 成员。 - [x] 将这个 `selfCtx` 赋值给 `ReleaseFeedWeightTask` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Execute`, `OnFailure`, `ResolveDeviceIDs`)**: - **公共方法改造 (`Execute`, `OnFailure`, `ResolveDeviceIDs`)**:
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, r.selfCtx, "MethodName")` 获取新的 - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, r.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。 `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` `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`)**: - **内部辅助方法改造 (`getNowWeight`, `parseParameters`)**:
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, r.selfCtx, "MethodName")` 获取新的 - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, r.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。 `context.Context``logger` 实例。
- [ ] 将所有对 `r.logger.Errorf`, `r.logger.Warnf` 的调用替换为 `logger.Errorf`, `logger.Warnf` - [x] 将所有对 `r.logger.Errorf`, `r.logger.Warnf` 的调用替换为 `logger.Errorf`, `logger.Warnf`
- [ ] 确保所有对 `r.sensorDataRepo`, `r.deviceRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。 - [x] 确保所有对 `r.sensorDataRepo`, `r.deviceRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/domain/task/full_collection_task.go`** - **`internal/domain/task/full_collection_task.go`**
- **结构体改造**: - **结构体改造**:
- [ ] 移除 `FullCollectionTask` 结构体中的 `logger *logs.Logger` 成员。 - [x] 移除 `FullCollectionTask` 结构体中的 `logger *logs.Logger` 成员。
- [ ] 新增 `selfCtx context.Context` 成员。 - [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewFullCollectionTask`)**: - **构造函数改造 (`NewFullCollectionTask`)**:
- [ ] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在函数内部,为 `FullCollectionTask` 创建其专属的 `selfCtx` - [x] 在函数内部,为 `FullCollectionTask` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "FullCollectionTask")` `selfCtx := logs.AddCompName(ctx, "FullCollectionTask")`
- [ ] 将这个 `selfCtx` 赋值给 `FullCollectionTask` 结构体的 `selfCtx` 成员。 - [x] 将这个 `selfCtx` 赋值给 `FullCollectionTask` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Execute`, `OnFailure`, `ResolveDeviceIDs`)**: - **公共方法改造 (`Execute`, `OnFailure`, `ResolveDeviceIDs`)**:
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, t.selfCtx, "MethodName")` 获取新的 - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, t.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。 `context.Context``logger` 实例。
- [ ] 将所有对 `t.logger.Infow`, `t.logger.Errorw` 的调用替换为 `logger.Infow`, `logger.Errorw` - [x] 将所有对 `t.logger.Infow`, `t.logger.Errorw` 的调用替换为 `logger.Infow`, `logger.Errorw`
- [ ] 确保所有对 `t.deviceRepo`, `t.deviceService` 等依赖的调用都将 `newCtx` 作为第一个参数传递。 - [x] 确保所有对 `t.deviceRepo`, `t.deviceService` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/domain/task/delay_task.go`** - **`internal/domain/task/delay_task.go`**
- **结构体改造**: - **结构体改造**:
- [ ] 移除 `DelayTask` 结构体中的 `logger *logs.Logger` 成员。 - [x] 移除 `DelayTask` 结构体中的 `logger *logs.Logger` 成员。
- [ ] 新增 `selfCtx context.Context` 成员。 - [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewDelayTask`)**: - **构造函数改造 (`NewDelayTask`)**:
- [ ] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context` - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [ ] 在函数内部,为 `DelayTask` 创建其专属的 `selfCtx``selfCtx := logs.AddCompName(ctx, "DelayTask")` - [x] 在函数内部,为 `DelayTask` 创建其专属的 `selfCtx``selfCtx := logs.AddCompName(ctx, "DelayTask")`
- [ ] 将这个 `selfCtx` 赋值给 `DelayTask` 结构体的 `selfCtx` 成员。 - [x] 将这个 `selfCtx` 赋值给 `DelayTask` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Execute`, `OnFailure`, `ResolveDeviceIDs`)**: - **公共方法改造 (`Execute`, `OnFailure`, `ResolveDeviceIDs`)**:
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, d.selfCtx, "MethodName")` 获取新的 - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, d.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。 `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`)**: - **内部辅助方法改造 (`parseParameters`)**:
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, d.selfCtx, "MethodName")` 获取新的 - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, d.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。 `context.Context``logger` 实例。
- [ ] 将所有对 `d.logger.Errorf` 的调用替换为 `logger.Errorf` - [x] 将所有对 `d.logger.Errorf` 的调用替换为 `logger.Errorf`
- **`internal/domain/token/token_service.go` (`token.Service`)** - **`internal/domain/token/token_service.go` (`token.Service`)**
- **结构体改造**: - **结构体改造**:
- [ ] 新增 `selfCtx context.Context` 成员。 - [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewTokenService`)**: - **构造函数改造 (`NewTokenService`)**:
- [ ] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在函数内部,为 `tokenService` 创建其专属的 `selfCtx``selfCtx := logs.AddCompName(ctx, "TokenService")` - [x] 在函数内部,为 `tokenService` 创建其专属的 `selfCtx``selfCtx := logs.AddCompName(ctx, "TokenService")`
- [ ] 将这个 `selfCtx` 赋值给 `tokenService` 结构体的 `selfCtx` 成员。 - [x] 将这个 `selfCtx` 赋值给 `tokenService` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`GenerateToken`, `ParseToken`)**: - **公共方法改造 (`GenerateToken`, `ParseToken`)**:
- [ ] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [ ] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。 `context.Context``logger` 实例。
- [ ] 将所有对 `s.logger.*` 的调用替换为 `logger.*` (如果存在)。 - [x] 将所有对 `s.logger.*` 的调用替换为 `logger.*` (如果存在)。

View File

@@ -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/controller/user"
"git.huangwc.com/pig/pig-farm-controller/internal/app/service" "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/app/webhook"
"git.huangwc.com/pig/pig-farm-controller/internal/domain/audit"
domain_plan "git.huangwc.com/pig/pig-farm-controller/internal/domain/plan" 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/config"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" "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"
"github.com/labstack/echo/v4/middleware" "github.com/labstack/echo/v4/middleware"
@@ -42,8 +41,8 @@ type API struct {
echo *echo.Echo // Echo 引擎实例,用于处理 HTTP 请求 echo *echo.Echo // Echo 引擎实例,用于处理 HTTP 请求
Ctx context.Context // API 组件的上下文,包含日志记录器 Ctx context.Context // API 组件的上下文,包含日志记录器
userRepo repository.UserRepository // 用户数据仓库接口,用于用户数据操作 userRepo repository.UserRepository // 用户数据仓库接口,用于用户数据操作
tokenService token.Service // Token 服务接口,用于 JWT token 的生成和解析 tokenGenerator token.Generator // Token 服务接口,用于 JWT token 的生成和解析
auditService audit.Service // 审计服务,用于记录用户操作 auditService service.AuditService // 审计服务,用于记录用户操作
httpServer *http.Server // 标准库的 HTTP 服务器实例,用于启动和停止服务 httpServer *http.Server // 标准库的 HTTP 服务器实例,用于启动和停止服务
config config.ServerConfig // API 服务器的配置,使用 infra/config 包中的 ServerConfig config config.ServerConfig // API 服务器的配置,使用 infra/config 包中的 ServerConfig
userController *user.Controller // 用户控制器实例 userController *user.Controller // 用户控制器实例
@@ -67,8 +66,8 @@ func NewAPI(cfg config.ServerConfig,
deviceService service.DeviceService, deviceService service.DeviceService,
planService service.PlanService, planService service.PlanService,
userService service.UserService, userService service.UserService,
tokenService token.Service, auditService service.AuditService,
auditService audit.Service, tokenGenerator token.Generator,
listenHandler webhook.ListenHandler, listenHandler webhook.ListenHandler,
) *API { ) *API {
// 使用 echo.New() 创建一个 Echo 引擎实例 // 使用 echo.New() 创建一个 Echo 引擎实例
@@ -84,13 +83,13 @@ func NewAPI(cfg config.ServerConfig,
// 初始化 API 结构体 // 初始化 API 结构体
baseCtx := context.Background() baseCtx := context.Background()
api := &API{ api := &API{
echo: e, echo: e,
Ctx: ctx, Ctx: ctx,
userRepo: userRepo, userRepo: userRepo,
tokenService: tokenService, tokenGenerator: tokenGenerator,
auditService: auditService, auditService: auditService,
config: cfg, config: cfg,
listenHandler: listenHandler, listenHandler: listenHandler,
// 在 NewAPI 中初始化用户控制器,并将其作为 API 结构体的成员 // 在 NewAPI 中初始化用户控制器,并将其作为 API 结构体的成员
userController: user.NewController(baseCtx, userService), userController: user.NewController(baseCtx, userService),
// 在 NewAPI 中初始化设备控制器,并将其作为 API 结构体的成员 // 在 NewAPI 中初始化设备控制器,并将其作为 API 结构体的成员

View File

@@ -54,8 +54,8 @@ func (a *API) setupRoutes() {
// --- Authenticated Routes --- // --- Authenticated Routes ---
// 所有在此注册的路由都需要通过 JWT 身份验证 // 所有在此注册的路由都需要通过 JWT 身份验证
authGroup := a.echo.Group("/api/v1") 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. 审计日志中间件 authGroup.Use(middleware.AuditLogMiddleware(logs.AddCompName(context.Background(), "AuditLogMiddleware"), a.auditService)) // 2. 审计日志中间件
{ {
// 用户相关路由组 // 用户相关路由组
userGroup := authGroup.Group("/users") userGroup := authGroup.Group("/users")

View File

@@ -6,10 +6,10 @@ import (
"git.huangwc.com/pig/pig-farm-controller/internal/app/dto" "git.huangwc.com/pig/pig-farm-controller/internal/app/dto"
domain_notify "git.huangwc.com/pig/pig-farm-controller/internal/domain/notify" 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/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "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"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/utils/token"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -23,24 +23,24 @@ type UserService interface {
// userService 实现了 UserService 接口 // userService 实现了 UserService 接口
type userService struct { type userService struct {
ctx context.Context ctx context.Context
userRepo repository.UserRepository userRepo repository.UserRepository
tokenService token.Service tokenGenerator token.Generator
notifyService domain_notify.Service notifyService domain_notify.Service
} }
// NewUserService 创建并返回一个新的 UserService 实例 // NewUserService 创建并返回一个新的 UserService 实例
func NewUserService( func NewUserService(
ctx context.Context, ctx context.Context,
userRepo repository.UserRepository, userRepo repository.UserRepository,
tokenService token.Service, tokenGenerator token.Generator,
notifyService domain_notify.Service, notifyService domain_notify.Service,
) UserService { ) UserService {
return &userService{ return &userService{
ctx: ctx, ctx: ctx,
userRepo: userRepo, userRepo: userRepo,
tokenService: tokenService, tokenGenerator: tokenGenerator,
notifyService: notifyService, notifyService: notifyService,
} }
} }

View File

@@ -57,8 +57,8 @@ func NewApplication(configPath string) (*Application, error) {
appServices.deviceService, appServices.deviceService,
appServices.planService, appServices.planService,
appServices.userService, appServices.userService,
infra.tokenService,
appServices.auditService, appServices.auditService,
infra.tokenGenerator,
infra.lora.listenHandler, infra.lora.listenHandler,
) )
@@ -91,7 +91,7 @@ func (app *Application) Start() error {
} }
// 3. 启动后台工作协程 // 3. 启动后台工作协程
app.Domain.planService.Start() app.Domain.planService.Start(startCtx)
// 4. 启动 API 服务器 // 4. 启动 API 服务器
app.API.Start() app.API.Start()
@@ -107,14 +107,14 @@ func (app *Application) Start() error {
// Stop 优雅地关闭应用的所有组件。 // Stop 优雅地关闭应用的所有组件。
func (app *Application) Stop() error { func (app *Application) Stop() error {
logger := logs.TraceLogger(app.Ctx, app.Ctx, "Stop") stopCtx, logger := logs.Trace(app.Ctx, app.Ctx, "Stop")
logger.Info("应用关闭中...") logger.Info("应用关闭中...")
// 关闭 API 服务器 // 关闭 API 服务器
app.API.Stop() app.API.Stop()
// 关闭任务执行器 // 关闭任务执行器
app.Domain.planService.Stop() app.Domain.planService.Stop(stopCtx)
// 断开数据库连接 // 断开数据库连接
if err := app.Infra.storage.Disconnect(); err != nil { if err := app.Infra.storage.Disconnect(); err != nil {

View File

@@ -12,7 +12,6 @@ import (
"git.huangwc.com/pig/pig-farm-controller/internal/domain/pig" "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/plan"
"git.huangwc.com/pig/pig-farm-controller/internal/domain/task" "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/config"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/database" "git.huangwc.com/pig/pig-farm-controller/internal/infra/database"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
@@ -21,16 +20,17 @@ import (
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" "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"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/transport/lora" "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" "gorm.io/gorm"
) )
// Infrastructure 聚合了所有基础设施层的组件。 // Infrastructure 聚合了所有基础设施层的组件。
type Infrastructure struct { type Infrastructure struct {
storage database.Storage storage database.Storage
repos *Repositories repos *Repositories
lora *LoraComponents lora *LoraComponents
notifyService domain_notify.Service notifyService domain_notify.Service
tokenService token.Service tokenGenerator token.Generator
} }
// initInfrastructure 初始化所有基础设施层组件。 // initInfrastructure 初始化所有基础设施层组件。
@@ -52,14 +52,14 @@ func initInfrastructure(ctx context.Context, cfg *config.Config) (*Infrastructur
return nil, fmt.Errorf("初始化通知服务失败: %w", err) return nil, fmt.Errorf("初始化通知服务失败: %w", err)
} }
tokenService := token.NewTokenService([]byte(cfg.App.JWTSecret)) tokenGenerator := token.NewTokenGenerator([]byte(cfg.App.JWTSecret))
return &Infrastructure{ return &Infrastructure{
storage: storage, storage: storage,
repos: repos, repos: repos,
lora: lora, lora: lora,
notifyService: notifyService, notifyService: notifyService,
tokenService: tokenService, tokenGenerator: tokenGenerator,
}, nil }, 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) 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) pigTradeManager := pig.NewPigTradeManager(logs.AddCompName(baseCtx, "PigTradeManager"), infra.repos.pigTradeRepo)
pigSickManager := pig.NewSickPigManager(logs.AddCompName(baseCtx, "PigSickManager"), infra.repos.pigSickPigLogRepo, infra.repos.medicationLogRepo) 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, pigBatchDomain := pig.NewPigBatchService(
pigPenTransferManager, pigTradeManager, pigSickManager) logs.AddCompName(baseCtx, "PigBatchDomain"),
infra.repos.pigBatchRepo,
infra.repos.pigBatchLogRepo,
infra.repos.unitOfWork,
pigPenTransferManager,
pigTradeManager, pigSickManager,
)
// 通用设备服务 // 通用设备服务
generalDeviceService := device.NewGeneralDeviceService( 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) auditService := service.NewAuditService(logs.AddCompName(baseCtx, "AuditService"), infra.repos.userActionLogRepo)
planService := service.NewPlanService(logs.AddCompName(baseCtx, "AppPlanService"), domainServices.planService) 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{ return &AppServices{
pigFarmService: pigFarmService, pigFarmService: pigFarmService,

View File

@@ -1,6 +1,10 @@
package device 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 type DeviceAction string
@@ -21,10 +25,10 @@ var (
type Service interface { type Service interface {
// Switch 用于切换指定设备的状态, 比如启动和停止 // Switch 用于切换指定设备的状态, 比如启动和停止
Switch(device *models.Device, action DeviceAction) error Switch(ctx context.Context, device *models.Device, action DeviceAction) error
// Collect 用于发起对指定区域主控下的多个设备的批量采集请求。 // Collect 用于发起对指定区域主控下的多个设备的批量采集请求。
Collect(regionalControllerID uint, devicesToCollect []*models.Device) error Collect(ctx context.Context, regionalControllerID uint, devicesToCollect []*models.Device) error
} }
// 设备操作指令通用结构(最外层) // 设备操作指令通用结构(最外层)

View File

@@ -1,6 +1,7 @@
package device package device
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"time" "time"
@@ -17,31 +18,32 @@ import (
) )
type GeneralDeviceService struct { type GeneralDeviceService struct {
ctx context.Context
deviceRepo repository.DeviceRepository deviceRepo repository.DeviceRepository
deviceCommandLogRepo repository.DeviceCommandLogRepository deviceCommandLogRepo repository.DeviceCommandLogRepository
pendingCollectionRepo repository.PendingCollectionRepository pendingCollectionRepo repository.PendingCollectionRepository
logger *logs.Logger
comm transport.Communicator comm transport.Communicator
} }
// NewGeneralDeviceService 创建一个通用设备服务 // NewGeneralDeviceService 创建一个通用设备服务
func NewGeneralDeviceService( func NewGeneralDeviceService(
ctx context.Context,
deviceRepo repository.DeviceRepository, deviceRepo repository.DeviceRepository,
deviceCommandLogRepo repository.DeviceCommandLogRepository, deviceCommandLogRepo repository.DeviceCommandLogRepository,
pendingCollectionRepo repository.PendingCollectionRepository, pendingCollectionRepo repository.PendingCollectionRepository,
logger *logs.Logger,
comm transport.Communicator, comm transport.Communicator,
) Service { ) Service {
return &GeneralDeviceService{ return &GeneralDeviceService{
ctx: ctx,
deviceRepo: deviceRepo, deviceRepo: deviceRepo,
deviceCommandLogRepo: deviceCommandLogRepo, deviceCommandLogRepo: deviceCommandLogRepo,
pendingCollectionRepo: pendingCollectionRepo, pendingCollectionRepo: pendingCollectionRepo,
logger: logger,
comm: comm, 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 进行全面校验 // 1. 依赖模型自身的 SelfCheck 进行全面校验
if err := device.SelfCheck(); err != nil { if err := device.SelfCheck(); err != nil {
return fmt.Errorf("设备 %v(id=%v) 未通过自检: %w", device.Name, device.ID, err) 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. 发送指令 // 7. 发送指令
networkID := areaController.NetworkID networkID := areaController.NetworkID
sendResult, err := g.comm.Send(networkID, message) sendResult, err := g.comm.Send(serviceCtx, networkID, message)
if err != nil { if err != nil {
return fmt.Errorf("发送指令到 %s 失败: %w", networkID, err) return fmt.Errorf("发送指令到 %s 失败: %w", networkID, err)
} }
@@ -120,20 +122,21 @@ func (g *GeneralDeviceService) Switch(device *models.Device, action DeviceAction
logRecord.ReceivedSuccess = *sendResult.ReceivedSuccess 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 return nil
} }
// Collect 实现了 Service 接口,用于发起对指定区域主控下的多个设备的批量采集请求。 // 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 { if len(devicesToCollect) == 0 {
g.logger.Info("待采集设备列表为空,无需执行采集任务。") logger.Info("待采集设备列表为空,无需执行采集任务。")
return nil return nil
} }
@@ -153,25 +156,25 @@ func (g *GeneralDeviceService) Collect(regionalControllerID uint, devicesToColle
for _, dev := range devicesToCollect { for _, dev := range devicesToCollect {
// 依赖模型自身的 SelfCheck 进行全面校验 // 依赖模型自身的 SelfCheck 进行全面校验
if err := dev.SelfCheck(); err != nil { if err := dev.SelfCheck(); err != nil {
g.logger.Warnf("跳过设备 %d因其未通过自检: %v", dev.ID, err) logger.Warnf("跳过设备 %d因其未通过自检: %v", dev.ID, err)
continue continue
} }
if err := dev.DeviceTemplate.SelfCheck(); err != nil { if err := dev.DeviceTemplate.SelfCheck(); err != nil {
g.logger.Warnf("跳过设备 %d因其设备模板未通过自检: %v", dev.ID, err) logger.Warnf("跳过设备 %d因其设备模板未通过自检: %v", dev.ID, err)
continue continue
} }
// 使用模板的 ParseCommands 方法获取传感器指令参数 // 使用模板的 ParseCommands 方法获取传感器指令参数
var sensorCmd models.SensorCommands var sensorCmd models.SensorCommands
if err := dev.DeviceTemplate.ParseCommands(&sensorCmd); err != nil { 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 continue
} }
// 使用模型层预定义的 Bus485Properties 结构体解析设备属性 // 使用模型层预定义的 Bus485Properties 结构体解析设备属性
var deviceProps models.Bus485Properties var deviceProps models.Bus485Properties
if err := dev.ParseProperties(&deviceProps); err != nil { if err := dev.ParseProperties(&deviceProps); err != nil {
g.logger.Warnf("跳过设备 %d因其属性解析失败: %v", dev.ID, err) logger.Warnf("跳过设备 %d因其属性解析失败: %v", dev.ID, err)
continue continue
} }
@@ -183,10 +186,10 @@ func (g *GeneralDeviceService) Collect(regionalControllerID uint, devicesToColle
sensorCmd.ModbusQuantity, sensorCmd.ModbusQuantity,
) )
if err != nil { if err != nil {
g.logger.Warnf("跳过设备 %d因生成Modbus RTU读取指令失败: %v", dev.ID, err) logger.Warnf("跳过设备 %d因生成Modbus RTU读取指令失败: %v", dev.ID, err)
continue continue
} }
g.logger.Debugf("生成485指令: %v", modbusCommandBytes) logger.Debugf("生成485指令: %v", modbusCommandBytes)
// 构建 Raw485Command包含总线号 // 构建 Raw485Command包含总线号
raw485Cmd := &proto.Raw485Command{ raw485Cmd := &proto.Raw485Command{
@@ -216,11 +219,11 @@ func (g *GeneralDeviceService) Collect(regionalControllerID uint, devicesToColle
Status: models.PendingStatusPending, Status: models.PendingStatusPending,
CreatedAt: time.Now(), CreatedAt: time.Now(),
} }
if err := g.pendingCollectionRepo.Create(pendingReq); err != nil { if err := g.pendingCollectionRepo.Create(serviceCtx, pendingReq); err != nil {
g.logger.Errorf("创建待采集请求失败 (CorrelationID: %s): %v", correlationID, err) logger.Errorf("创建待采集请求失败 (CorrelationID: %s): %v", correlationID, err)
return err return err
} }
g.logger.Infof("成功创建待采集请求 (CorrelationID: %s, DeviceID: %d)", correlationID, regionalController.ID) logger.Infof("成功创建待采集请求 (CorrelationID: %s, DeviceID: %d)", correlationID, regionalController.ID)
// 5. 构建最终的空中载荷 // 5. 构建最终的空中载荷
batchCmd := &proto.BatchCollectCommand{ batchCmd := &proto.BatchCollectCommand{
@@ -234,15 +237,15 @@ func (g *GeneralDeviceService) Collect(regionalControllerID uint, devicesToColle
} }
payload, err := gproto.Marshal(instruction) payload, err := gproto.Marshal(instruction)
if err != nil { if err != nil {
g.logger.Errorf("序列化采集指令失败 (CorrelationID: %s): %v", correlationID, err) logger.Errorf("序列化采集指令失败 (CorrelationID: %s): %v", correlationID, err)
return err return err
} }
g.logger.Infof("构造空中载荷成功: networkID: %v, payload: %v", networkID, instruction) logger.Infof("构造空中载荷成功: networkID: %v, payload: %v", networkID, instruction)
if _, err := g.comm.Send(networkID, payload); err != nil { if _, err := g.comm.Send(serviceCtx, networkID, payload); err != nil {
g.logger.DPanicf("待采集请求 (CorrelationID: %s) 已创建,但发送到设备失败: %v。数据可能不一致", correlationID, err) logger.DPanicf("待采集请求 (CorrelationID: %s) 已创建,但发送到设备失败: %v。数据可能不一致", correlationID, err)
return err return err
} }
g.logger.Infof("成功将采集请求 (CorrelationID: %s) 发送到设备 %s", correlationID, networkID) logger.Infof("成功将采集请求 (CorrelationID: %s) 发送到设备 %s", correlationID, networkID)
return nil return nil
} }

View File

@@ -1,6 +1,7 @@
package notify package notify
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
"sync" "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/models"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/notify" "git.huangwc.com/pig/pig-farm-controller/internal/infra/notify"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
"go.uber.org/zap" "go.uber.org/zap"
) )
// Service 定义了通知领域的核心业务逻辑接口 // Service 定义了通知领域的核心业务逻辑接口
type Service interface { type Service interface {
// SendBatchAlarm 向一批用户发送告警通知。它会并发地为每个用户执行带故障转移的发送逻辑。 // SendBatchAlarm 向一批用户发送告警通知。它会并发地为每个用户执行带故障转移的发送逻辑。
SendBatchAlarm(userIDs []uint, content notify.AlarmContent) error SendBatchAlarm(ctx context.Context, userIDs []uint, content notify.AlarmContent) error
// BroadcastAlarm 向所有用户发送告警通知。它会并发地为每个用户执行带故障转移的发送逻辑。 // BroadcastAlarm 向所有用户发送告警通知。它会并发地为每个用户执行带故障转移的发送逻辑。
BroadcastAlarm(content notify.AlarmContent) error BroadcastAlarm(ctx context.Context, content notify.AlarmContent) error
// SendTestMessage 向指定用户发送一条测试消息,用于手动验证特定通知渠道的配置。 // SendTestMessage 向指定用户发送一条测试消息,用于手动验证特定通知渠道的配置。
SendTestMessage(userID uint, notifierType notify.NotifierType) error SendTestMessage(ctx context.Context, userID uint, notifierType notify.NotifierType) error
} }
// failoverService 是 Service 接口的实现,提供了故障转移功能 // failoverService 是 Service 接口的实现,提供了故障转移功能
type failoverService struct { type failoverService struct {
log *logs.Logger ctx context.Context
userRepo repository.UserRepository userRepo repository.UserRepository
notifiers map[notify.NotifierType]notify.Notifier notifiers map[notify.NotifierType]notify.Notifier
primaryNotifier notify.Notifier primaryNotifier notify.Notifier
@@ -38,7 +40,7 @@ type failoverService struct {
// NewFailoverService 创建一个新的故障转移通知服务 // NewFailoverService 创建一个新的故障转移通知服务
func NewFailoverService( func NewFailoverService(
log *logs.Logger, ctx context.Context,
userRepo repository.UserRepository, userRepo repository.UserRepository,
notifiers []notify.Notifier, notifiers []notify.Notifier,
primaryNotifierType notify.NotifierType, primaryNotifierType notify.NotifierType,
@@ -56,7 +58,7 @@ func NewFailoverService(
} }
return &failoverService{ return &failoverService{
log: log, ctx: ctx,
userRepo: userRepo, userRepo: userRepo,
notifiers: notifierMap, notifiers: notifierMap,
primaryNotifier: primaryNotifier, primaryNotifier: primaryNotifier,
@@ -67,18 +69,19 @@ func NewFailoverService(
} }
// SendBatchAlarm 实现了向多个用户并发发送告警的功能 // 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 wg sync.WaitGroup
var mu sync.Mutex var mu sync.Mutex
var allErrors []string var allErrors []string
s.log.Infow("开始批量发送告警...", "userCount", len(userIDs)) logger.Infow("开始批量发送告警...", "userCount", len(userIDs))
for _, userID := range userIDs { for _, userID := range userIDs {
wg.Add(1) wg.Add(1)
go func(id uint) { go func(id uint) {
defer wg.Done() defer wg.Done()
if err := s.sendAlarmToUser(id, content); err != nil { if err := s.sendAlarmToUser(serviceCtx, id, content); err != nil {
mu.Lock() mu.Lock()
allErrors = append(allErrors, fmt.Sprintf("发送失败 (用户ID: %d): %v", id, err)) allErrors = append(allErrors, fmt.Sprintf("发送失败 (用户ID: %d): %v", id, err))
mu.Unlock() mu.Unlock()
@@ -90,19 +93,20 @@ func (s *failoverService) SendBatchAlarm(userIDs []uint, content notify.AlarmCon
if len(allErrors) > 0 { if len(allErrors) > 0 {
finalError := fmt.Errorf("批量告警发送完成,但有 %d 个用户发送失败:\n%s", len(allErrors), strings.Join(allErrors, "\n")) finalError := fmt.Errorf("批量告警发送完成,但有 %d 个用户发送失败:\n%s", len(allErrors), strings.Join(allErrors, "\n"))
s.log.Error(finalError.Error()) logger.Error(finalError.Error())
return finalError return finalError
} }
s.log.Info("批量发送告警成功完成,所有用户均已通知。") logger.Info("批量发送告警成功完成,所有用户均已通知。")
return nil return nil
} }
// BroadcastAlarm 实现了向所有用户发送告警的功能 // BroadcastAlarm 实现了向所有用户发送告警的功能
func (s *failoverService) BroadcastAlarm(content notify.AlarmContent) error { func (s *failoverService) BroadcastAlarm(ctx context.Context, content notify.AlarmContent) error {
users, err := s.userRepo.FindAll() serviceCtx, logger := logs.Trace(ctx, s.ctx, "BroadcastAlarm")
users, err := s.userRepo.FindAll(serviceCtx)
if err != nil { if err != nil {
s.log.Errorw("广播告警失败:查找所有用户时出错", "error", err) logger.Errorw("广播告警失败:查找所有用户时出错", "error", err)
return fmt.Errorf("广播告警失败:查找所有用户时出错: %w", err) return fmt.Errorf("广播告警失败:查找所有用户时出错: %w", err)
} }
@@ -111,16 +115,17 @@ func (s *failoverService) BroadcastAlarm(content notify.AlarmContent) error {
userIDs = append(userIDs, user.ID) userIDs = append(userIDs, user.ID)
} }
s.log.Infow("开始广播告警给所有用户", "totalUsers", len(userIDs)) logger.Infow("开始广播告警给所有用户", "totalUsers", len(userIDs))
// 复用 SendBatchAlarm 的逻辑进行并发发送和错误处理 // 复用 SendBatchAlarm 的逻辑进行并发发送和错误处理
return s.SendBatchAlarm(userIDs, content) return s.SendBatchAlarm(serviceCtx, userIDs, content)
} }
// sendAlarmToUser 是为单个用户发送告警的内部方法,包含了完整的故障转移逻辑 // sendAlarmToUser 是为单个用户发送告警的内部方法,包含了完整的故障转移逻辑
func (s *failoverService) sendAlarmToUser(userID uint, content notify.AlarmContent) error { func (s *failoverService) sendAlarmToUser(ctx context.Context, userID uint, content notify.AlarmContent) error {
user, err := s.userRepo.FindByID(userID) serviceCtx, logger := logs.Trace(ctx, s.ctx, "sendAlarmToUser")
user, err := s.userRepo.FindByID(serviceCtx, userID)
if err != nil { if err != nil {
s.log.Errorw("发送告警失败:查找用户时出错", "userID", userID, "error", err) logger.Errorw("发送告警失败:查找用户时出错", "userID", userID, "error", err)
return fmt.Errorf("查找用户失败: %w", err) return fmt.Errorf("查找用户失败: %w", err)
} }
@@ -132,50 +137,50 @@ func (s *failoverService) sendAlarmToUser(userID uint, content notify.AlarmConte
addr := getAddressForNotifier(primaryType, user.Contact) addr := getAddressForNotifier(primaryType, user.Contact)
if addr == "" { 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) return fmt.Errorf("用户未配置首选通知方式 '%s' 的地址", primaryType)
} }
err = s.primaryNotifier.Send(content, addr) err = s.primaryNotifier.Send(serviceCtx, content, addr)
if err == nil { 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 { if failureCount > 0 {
s.log.Infow("首选渠道发送恢复正常", "userID", userID, "notifierType", primaryType) logger.Infow("首选渠道发送恢复正常", "userID", userID, "notifierType", primaryType)
s.failureCounters.Store(userID, 0) s.failureCounters.Store(userID, 0)
} }
return nil return nil
} }
// 记录失败通知 // 记录失败通知
s.recordNotificationAttempt(userID, primaryType, content, addr, models.NotificationStatusFailed, err) s.recordNotificationAttempt(serviceCtx, userID, primaryType, content, addr, models.NotificationStatusFailed, err)
newFailureCount := failureCount + 1 newFailureCount := failureCount + 1
s.failureCounters.Store(userID, newFailureCount) 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 failureCount = newFailureCount
} }
if failureCount >= s.failureThreshold { if failureCount >= s.failureThreshold {
s.log.Warnw("故障转移阈值已达到,开始广播通知", "userID", userID, "threshold", s.failureThreshold) logger.Warnw("故障转移阈值已达到,开始广播通知", "userID", userID, "threshold", s.failureThreshold)
var lastErr error var lastErr error
for _, notifier := range s.notifiers { for _, notifier := range s.notifiers {
addr := getAddressForNotifier(notifier.Type(), user.Contact) addr := getAddressForNotifier(notifier.Type(), user.Contact)
if addr == "" { 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 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.recordNotificationAttempt(serviceCtx, userID, notifier.Type(), content, addr, models.NotificationStatusSuccess, nil)
s.log.Infow("广播通知成功", "userID", userID, "notifierType", notifier.Type()) logger.Infow("广播通知成功", "userID", userID, "notifierType", notifier.Type())
s.failureCounters.Store(userID, 0) s.failureCounters.Store(userID, 0)
return nil 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 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) return fmt.Errorf("所有渠道均发送失败,最后一个错误: %w", lastErr)
} }
@@ -184,24 +189,25 @@ func (s *failoverService) sendAlarmToUser(userID uint, content notify.AlarmConte
} }
// SendTestMessage 实现了手动发送测试消息的功能 // SendTestMessage 实现了手动发送测试消息的功能
func (s *failoverService) SendTestMessage(userID uint, notifierType notify.NotifierType) error { func (s *failoverService) SendTestMessage(ctx context.Context, userID uint, notifierType notify.NotifierType) error {
user, err := s.userRepo.FindByID(userID) serviceCtx, logger := logs.Trace(ctx, s.ctx, "SendTestMessage")
user, err := s.userRepo.FindByID(serviceCtx, userID)
if err != nil { if err != nil {
s.log.Errorw("发送测试消息失败:查找用户时出错", "userID", userID, "error", err) logger.Errorw("发送测试消息失败:查找用户时出错", "userID", userID, "error", err)
return fmt.Errorf("查找用户失败: %w", err) return fmt.Errorf("查找用户失败: %w", err)
} }
notifier, ok := s.notifiers[notifierType] notifier, ok := s.notifiers[notifierType]
if !ok { if !ok {
s.log.Errorw("发送测试消息失败:通知器类型不存在", "userID", userID, "notifierType", notifierType) logger.Errorw("发送测试消息失败:通知器类型不存在", "userID", userID, "notifierType", notifierType)
return fmt.Errorf("指定的通知器类型 '%s' 不存在", notifierType) return fmt.Errorf("指定的通知器类型 '%s' 不存在", notifierType)
} }
addr := getAddressForNotifier(notifierType, user.Contact) addr := getAddressForNotifier(notifierType, user.Contact)
if addr == "" { 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: "通知服务测试", Title: "通知服务测试",
Message: fmt.Sprintf("这是一条来自【%s】渠道的测试消息。如果您收到此消息说明您的配置正确。", notifierType), Message: fmt.Sprintf("这是一条来自【%s】渠道的测试消息。如果您收到此消息说明您的配置正确。", notifierType),
Level: zap.InfoLevel, Level: zap.InfoLevel,
@@ -217,18 +223,18 @@ func (s *failoverService) SendTestMessage(userID uint, notifierType notify.Notif
Timestamp: time.Now(), Timestamp: time.Now(),
} }
s.log.Infow("正在发送测试消息...", "userID", userID, "notifierType", notifierType, "address", addr) logger.Infow("正在发送测试消息...", "userID", userID, "notifierType", notifierType, "address", addr)
err = notifier.Send(testContent, addr) err = notifier.Send(serviceCtx, testContent, addr)
if err != nil { 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 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 return nil
} }
@@ -256,6 +262,7 @@ func getAddressForNotifier(notifierType notify.NotifierType, contact models.Cont
// status: 发送尝试的状态 (成功、失败、跳过) // status: 发送尝试的状态 (成功、失败、跳过)
// err: 如果发送失败,记录的错误信息 // err: 如果发送失败,记录的错误信息
func (s *failoverService) recordNotificationAttempt( func (s *failoverService) recordNotificationAttempt(
ctx context.Context,
userID uint, userID uint,
notifierType notify.NotifierType, notifierType notify.NotifierType,
content notify.AlarmContent, content notify.AlarmContent,
@@ -263,6 +270,7 @@ func (s *failoverService) recordNotificationAttempt(
status models.NotificationStatus, status models.NotificationStatus,
err error, err error,
) { ) {
serviceCtx, logger := logs.Trace(ctx, s.ctx, "recordNotificationAttempt")
errorMessage := "" errorMessage := ""
if err != nil { if err != nil {
errorMessage = err.Error() errorMessage = err.Error()
@@ -280,8 +288,8 @@ func (s *failoverService) recordNotificationAttempt(
ErrorMessage: errorMessage, ErrorMessage: errorMessage,
} }
if saveErr := s.notificationRepo.Create(notification); saveErr != nil { if saveErr := s.notificationRepo.Create(serviceCtx, notification); saveErr != nil {
s.log.Errorw("无法保存通知发送记录到数据库", logger.Errorw("无法保存通知发送记录到数据库",
"userID", userID, "userID", userID,
"notifierType", notifierType, "notifierType", notifierType,
"status", status, "status", status,

View File

@@ -1,11 +1,14 @@
package pig package pig
import ( import (
"context"
"errors" "errors"
"fmt" "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/models"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -13,38 +16,40 @@ import (
// 它是一个内部服务,被主服务 PigBatchService 调用。 // 它是一个内部服务,被主服务 PigBatchService 调用。
type PigPenTransferManager interface { type PigPenTransferManager interface {
// LogTransfer 在数据库中创建一条猪只迁移日志。 // LogTransfer 在数据库中创建一条猪只迁移日志。
LogTransfer(tx *gorm.DB, log *models.PigTransferLog) error LogTransfer(ctx context.Context, tx *gorm.DB, log *models.PigTransferLog) error
// GetPenByID 用于获取猪栏的详细信息,供上层服务进行业务校验。 // GetPenByID 用于获取猪栏的详细信息,供上层服务进行业务校验。
GetPenByID(tx *gorm.DB, penID uint) (*models.Pen, error) GetPenByID(ctx context.Context, tx *gorm.DB, penID uint) (*models.Pen, error)
// GetPensByBatchID 获取一个猪群当前关联的所有猪栏。 // GetPensByBatchID 获取一个猪群当前关联的所有猪栏。
GetPensByBatchID(tx *gorm.DB, batchID uint) ([]*models.Pen, error) GetPensByBatchID(ctx context.Context, tx *gorm.DB, batchID uint) ([]*models.Pen, error)
// UpdatePenFields 更新一个猪栏的指定字段。 // 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 通过汇总猪只迁移日志,计算给定猪栏中的当前猪只数量。
GetCurrentPigsInPen(tx *gorm.DB, penID uint) (int, error) GetCurrentPigsInPen(ctx context.Context, tx *gorm.DB, penID uint) (int, error)
// GetTotalPigsInPensForBatchTx 计算指定猪群下所有猪栏的当前总存栏数 // GetTotalPigsInPensForBatchTx 计算指定猪群下所有猪栏的当前总存栏数
GetTotalPigsInPensForBatchTx(tx *gorm.DB, batchID uint) (int, error) GetTotalPigsInPensForBatchTx(ctx context.Context, tx *gorm.DB, batchID uint) (int, error)
// ReleasePen 将猪栏的猪群归属移除,并将其状态标记为空闲。 // ReleasePen 将猪栏的猪群归属移除,并将其状态标记为空闲。
ReleasePen(tx *gorm.DB, penID uint) error ReleasePen(ctx context.Context, tx *gorm.DB, penID uint) error
} }
// pigPenTransferManager 是 PigPenTransferManager 接口的具体实现。 // pigPenTransferManager 是 PigPenTransferManager 接口的具体实现。
// 它作为调栏管理器,处理底层的数据库交互。 // 它作为调栏管理器,处理底层的数据库交互。
type pigPenTransferManager struct { type pigPenTransferManager struct {
ctx context.Context
penRepo repository.PigPenRepository penRepo repository.PigPenRepository
logRepo repository.PigTransferLogRepository logRepo repository.PigTransferLogRepository
pigBatchRepo repository.PigBatchRepository pigBatchRepo repository.PigBatchRepository
} }
// NewPigPenTransferManager 是 pigPenTransferManager 的构造函数。 // 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{ return &pigPenTransferManager{
ctx: ctx,
penRepo: penRepo, penRepo: penRepo,
logRepo: logRepo, logRepo: logRepo,
pigBatchRepo: pigBatchRepo, pigBatchRepo: pigBatchRepo,
@@ -52,29 +57,34 @@ func NewPigPenTransferManager(penRepo repository.PigPenRepository, logRepo repos
} }
// LogTransfer 实现了在数据库中创建迁移日志的逻辑。 // LogTransfer 实现了在数据库中创建迁移日志的逻辑。
func (s *pigPenTransferManager) LogTransfer(tx *gorm.DB, log *models.PigTransferLog) error { func (s *pigPenTransferManager) LogTransfer(ctx context.Context, tx *gorm.DB, log *models.PigTransferLog) error {
return s.logRepo.CreatePigTransferLog(tx, log) managerCtx := logs.AddFuncName(ctx, s.ctx, "LogTransfer")
return s.logRepo.CreatePigTransferLog(managerCtx, tx, log)
} }
// GetPenByID 实现了获取猪栏信息的逻辑。 // GetPenByID 实现了获取猪栏信息的逻辑。
func (s *pigPenTransferManager) GetPenByID(tx *gorm.DB, penID uint) (*models.Pen, error) { func (s *pigPenTransferManager) GetPenByID(ctx context.Context, tx *gorm.DB, penID uint) (*models.Pen, error) {
return s.penRepo.GetPenByIDTx(tx, penID) managerCtx := logs.AddFuncName(ctx, s.ctx, "GetPenByID")
return s.penRepo.GetPenByIDTx(managerCtx, tx, penID)
} }
// GetPensByBatchID 实现了获取猪群关联猪栏列表的逻辑。 // GetPensByBatchID 实现了获取猪群关联猪栏列表的逻辑。
func (s *pigPenTransferManager) GetPensByBatchID(tx *gorm.DB, batchID uint) ([]*models.Pen, error) { func (s *pigPenTransferManager) GetPensByBatchID(ctx context.Context, tx *gorm.DB, batchID uint) ([]*models.Pen, error) {
return s.penRepo.GetPensByBatchIDTx(tx, batchID) managerCtx := logs.AddFuncName(ctx, s.ctx, "GetPensByBatchID")
return s.penRepo.GetPensByBatchIDTx(managerCtx, tx, batchID)
} }
// UpdatePenFields 实现了更新猪栏字段的逻辑。 // UpdatePenFields 实现了更新猪栏字段的逻辑。
func (s *pigPenTransferManager) UpdatePenFields(tx *gorm.DB, penID uint, updates map[string]interface{}) error { func (s *pigPenTransferManager) UpdatePenFields(ctx context.Context, tx *gorm.DB, penID uint, updates map[string]interface{}) error {
return s.penRepo.UpdatePenFieldsTx(tx, penID, updates) managerCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePenFields")
return s.penRepo.UpdatePenFieldsTx(managerCtx, tx, penID, updates)
} }
// GetCurrentPigsInPen 实现了计算猪栏当前猪只数量的逻辑。 // 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查出所属猪群信息 // 1. 通过猪栏ID查出所属猪群信息
pen, err := s.penRepo.GetPenByIDTx(tx, penID) pen, err := s.penRepo.GetPenByIDTx(managerCtx, tx, penID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return 0, ErrPenNotFound return 0, ErrPenNotFound
@@ -89,7 +99,7 @@ func (s *pigPenTransferManager) GetCurrentPigsInPen(tx *gorm.DB, penID uint) (in
currentBatchID := *pen.PigBatchID currentBatchID := *pen.PigBatchID
// 2. 根据猪群ID获取猪群的起始日期 // 2. 根据猪群ID获取猪群的起始日期
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, currentBatchID) batch, err := s.pigBatchRepo.GetPigBatchByIDTx(managerCtx, tx, currentBatchID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return 0, ErrPigBatchNotFound return 0, ErrPigBatchNotFound
@@ -99,7 +109,7 @@ func (s *pigPenTransferManager) GetCurrentPigsInPen(tx *gorm.DB, penID uint) (in
batchStartDate := batch.StartDate batchStartDate := batch.StartDate
// 3. 调用仓库方法,获取从猪群开始至今,该猪栏的所有倒序日志 // 3. 调用仓库方法,获取从猪群开始至今,该猪栏的所有倒序日志
logs, err := s.logRepo.GetLogsForPenSince(tx, penID, batchStartDate) logs, err := s.logRepo.GetLogsForPenSince(managerCtx, tx, penID, batchStartDate)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@@ -127,9 +137,10 @@ func (s *pigPenTransferManager) GetCurrentPigsInPen(tx *gorm.DB, penID uint) (in
// GetTotalPigsInPensForBatchTx 计算指定猪群下所有猪栏的当前总存栏数 // GetTotalPigsInPensForBatchTx 计算指定猪群下所有猪栏的当前总存栏数
// 该方法通过遍历猪群下的每个猪栏,并调用 GetCurrentPigsInPen 来累加存栏数。 // 该方法通过遍历猪群下的每个猪栏,并调用 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. 获取该批次下所有猪栏的列表 // 1. 获取该批次下所有猪栏的列表
pensInBatch, err := s.GetPensByBatchID(tx, batchID) pensInBatch, err := s.GetPensByBatchID(managerCtx, tx, batchID)
if err != nil { if err != nil {
return 0, fmt.Errorf("获取猪群 %d 下属猪栏失败: %w", batchID, err) return 0, fmt.Errorf("获取猪群 %d 下属猪栏失败: %w", batchID, err)
} }
@@ -137,7 +148,7 @@ func (s *pigPenTransferManager) GetTotalPigsInPensForBatchTx(tx *gorm.DB, batchI
totalPigs := 0 totalPigs := 0
// 2. 遍历每个猪栏,累加其存栏数 // 2. 遍历每个猪栏,累加其存栏数
for _, pen := range pensInBatch { for _, pen := range pensInBatch {
pigsInPen, err := s.GetCurrentPigsInPen(tx, pen.ID) pigsInPen, err := s.GetCurrentPigsInPen(managerCtx, tx, pen.ID)
if err != nil { if err != nil {
return 0, fmt.Errorf("获取猪栏 %d 存栏数失败: %w", pen.ID, err) return 0, fmt.Errorf("获取猪栏 %d 存栏数失败: %w", pen.ID, err)
} }
@@ -149,9 +160,10 @@ func (s *pigPenTransferManager) GetTotalPigsInPensForBatchTx(tx *gorm.DB, batchI
// ReleasePen 将猪栏的猪群归属移除,并将其状态标记为空闲。 // 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. 获取猪栏信息 // 1. 获取猪栏信息
pen, err := s.penRepo.GetPenByIDTx(tx, penID) pen, err := s.penRepo.GetPenByIDTx(managerCtx, tx, penID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("猪栏 %d 不存在: %w", penID, ErrPenNotFound) return fmt.Errorf("猪栏 %d 不存在: %w", penID, ErrPenNotFound)
@@ -167,7 +179,7 @@ func (s *pigPenTransferManager) ReleasePen(tx *gorm.DB, penID uint) error {
"status": models.PenStatusEmpty, "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) return fmt.Errorf("释放猪栏 %v 失败: %w", pen.PenNumber, err)
} }

View File

@@ -1,6 +1,7 @@
package pig package pig
import ( import (
"context"
"errors" "errors"
"time" "time"
@@ -37,63 +38,64 @@ var (
// 它抽象了所有与猪批次相关的操作,使得应用层可以依赖于此接口,而不是具体的实现。 // 它抽象了所有与猪批次相关的操作,使得应用层可以依赖于此接口,而不是具体的实现。
type PigBatchService interface { type PigBatchService interface {
// CreatePigBatch 创建猪批次,并记录初始日志。 // CreatePigBatch 创建猪批次,并记录初始日志。
CreatePigBatch(operatorID uint, batch *models.PigBatch) (*models.PigBatch, error) CreatePigBatch(ctx context.Context, operatorID uint, batch *models.PigBatch) (*models.PigBatch, error)
// GetPigBatch 获取单个猪批次。 // GetPigBatch 获取单个猪批次。
GetPigBatch(id uint) (*models.PigBatch, error) GetPigBatch(ctx context.Context, id uint) (*models.PigBatch, error)
// UpdatePigBatch 更新猪批次信息。 // UpdatePigBatch 更新猪批次信息。
UpdatePigBatch(batch *models.PigBatch) (*models.PigBatch, error) UpdatePigBatch(ctx context.Context, batch *models.PigBatch) (*models.PigBatch, error)
// DeletePigBatch 删除猪批次,包含业务规则校验。 // DeletePigBatch 删除猪批次,包含业务规则校验。
DeletePigBatch(id uint) error DeletePigBatch(ctx context.Context, id uint) error
// ListPigBatches 批量查询猪批次。 // ListPigBatches 批量查询猪批次。
ListPigBatches(isActive *bool) ([]*models.PigBatch, error) ListPigBatches(ctx context.Context, isActive *bool) ([]*models.PigBatch, error)
// AssignEmptyPensToBatch 为猪群分配空栏 // AssignEmptyPensToBatch 为猪群分配空栏
AssignEmptyPensToBatch(batchID uint, penIDs []uint, operatorID uint) error AssignEmptyPensToBatch(ctx context.Context, batchID uint, penIDs []uint, operatorID uint) error
// MovePigsIntoPen 将猪只从“虚拟库存”移入指定猪栏 // 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 连猪带栏,整体划拨到另一个猪群
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 将一个猪栏移除出猪群,此方法需要在猪栏为空的情况下执行。
RemoveEmptyPenFromBatch(batchID uint, penID uint) error RemoveEmptyPenFromBatch(ctx context.Context, batchID uint, penID uint) error
// GetCurrentPigQuantity 获取指定猪批次的当前猪只数量。 // GetCurrentPigQuantity 获取指定猪批次的当前猪只数量。
GetCurrentPigQuantity(batchID uint) (int, error) GetCurrentPigQuantity(ctx context.Context, batchID uint) (int, error)
// GetCurrentPigsInPen 获取指定猪栏的当前存栏量。 // GetCurrentPigsInPen 获取指定猪栏的当前存栏量。
GetCurrentPigsInPen(penID uint) (int, error) GetCurrentPigsInPen(ctx context.Context, penID uint) (int, error)
// GetTotalPigsInPensForBatch 获取指定猪群下所有猪栏的当前总存栏数 // 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 处理卖猪的业务逻辑。
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 处理买猪的业务逻辑。
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 TransferPigsAcrossBatches(ctx context.Context, 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 TransferPigsWithinBatch(ctx context.Context, batchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error
// --- 病猪管理相关方法 --- // --- 病猪管理相关方法 ---
// RecordSickPigs 记录新增病猪事件。 // 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 记录病猪康复事件。
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 记录病猪死亡事件。
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 记录病猪淘汰事件。
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 记录正常猪只死亡事件。
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 记录正常猪只淘汰事件。
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 接口的具体实现。 // pigBatchService 是 PigBatchService 接口的具体实现。
// 它作为猪群领域的主服务,封装了所有业务逻辑。 // 它作为猪群领域的主服务,封装了所有业务逻辑。
type pigBatchService struct { type pigBatchService struct {
ctx context.Context
pigBatchRepo repository.PigBatchRepository // 猪批次仓库 pigBatchRepo repository.PigBatchRepository // 猪批次仓库
pigBatchLogRepo repository.PigBatchLogRepository // 猪批次日志仓库 pigBatchLogRepo repository.PigBatchLogRepository // 猪批次日志仓库
uow repository.UnitOfWork // 工作单元,用于管理事务 uow repository.UnitOfWork // 工作单元,用于管理事务
@@ -105,6 +107,7 @@ type pigBatchService struct {
// NewPigBatchService 是 pigBatchService 的构造函数。 // NewPigBatchService 是 pigBatchService 的构造函数。
// 它通过依赖注入的方式,创建并返回一个 PigBatchService 接口的实例。 // 它通过依赖注入的方式,创建并返回一个 PigBatchService 接口的实例。
func NewPigBatchService( func NewPigBatchService(
ctx context.Context,
pigBatchRepo repository.PigBatchRepository, pigBatchRepo repository.PigBatchRepository,
pigBatchLogRepo repository.PigBatchLogRepository, pigBatchLogRepo repository.PigBatchLogRepository,
uow repository.UnitOfWork, uow repository.UnitOfWork,
@@ -113,6 +116,7 @@ func NewPigBatchService(
sickSvc SickPigManager, sickSvc SickPigManager,
) PigBatchService { ) PigBatchService {
return &pigBatchService{ return &pigBatchService{
ctx: ctx,
pigBatchRepo: pigBatchRepo, pigBatchRepo: pigBatchRepo,
pigBatchLogRepo: pigBatchLogRepo, pigBatchLogRepo: pigBatchLogRepo,
uow: uow, uow: uow,

View File

@@ -1,26 +1,30 @@
package pig package pig
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"time" "time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm" "gorm.io/gorm"
) )
// --- 领域服务实现 --- // --- 领域服务实现 ---
// CreatePigBatch 实现了创建猪批次的逻辑,并同时创建初始批次日志。 // 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 var createdBatch *models.PigBatch
err := s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
// 1. 创建猪批次 // 1. 创建猪批次
// 注意: 此处依赖一个假设存在的 pigBatchRepo.CreatePigBatchTx 方法 // 注意: 此处依赖一个假设存在的 pigBatchRepo.CreatePigBatchTx 方法
var err error var err error
createdBatch, err = s.pigBatchRepo.CreatePigBatchTx(tx, batch) createdBatch, err = s.pigBatchRepo.CreatePigBatchTx(serviceCtx, tx, batch)
if err != nil { if err != nil {
return fmt.Errorf("创建猪批次失败: %w", err) return fmt.Errorf("创建猪批次失败: %w", err)
} }
@@ -38,7 +42,7 @@ func (s *pigBatchService) CreatePigBatch(operatorID uint, batch *models.PigBatch
} }
// 3. 记录批次日志 // 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) return fmt.Errorf("记录初始批次日志失败: %w", err)
} }
@@ -53,8 +57,9 @@ func (s *pigBatchService) CreatePigBatch(operatorID uint, batch *models.PigBatch
} }
// GetPigBatch 实现了获取单个猪批次的逻辑。 // GetPigBatch 实现了获取单个猪批次的逻辑。
func (s *pigBatchService) GetPigBatch(id uint) (*models.PigBatch, error) { func (s *pigBatchService) GetPigBatch(ctx context.Context, id uint) (*models.PigBatch, error) {
batch, err := s.pigBatchRepo.GetPigBatchByID(id) serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetPigBatch")
batch, err := s.pigBatchRepo.GetPigBatchByID(serviceCtx, id)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrPigBatchNotFound return nil, ErrPigBatchNotFound
@@ -65,9 +70,10 @@ func (s *pigBatchService) GetPigBatch(id uint) (*models.PigBatch, error) {
} }
// UpdatePigBatch 实现了更新猪批次的逻辑。 // 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 { if err != nil {
return nil, err return nil, err
} }
@@ -78,10 +84,11 @@ func (s *pigBatchService) UpdatePigBatch(batch *models.PigBatch) (*models.PigBat
} }
// DeletePigBatch 实现了删除猪批次的逻辑,并包含业务规则校验。 // DeletePigBatch 实现了删除猪批次的逻辑,并包含业务规则校验。
func (s *pigBatchService) DeletePigBatch(id uint) error { func (s *pigBatchService) DeletePigBatch(ctx context.Context, id uint) error {
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeletePigBatch")
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
// 1. 获取猪批次信息 // 1. 获取猪批次信息
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, id) // 使用事务内方法 batch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, id) // 使用事务内方法
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPigBatchNotFound return ErrPigBatchNotFound
@@ -96,20 +103,20 @@ func (s *pigBatchService) DeletePigBatch(id uint) error {
// 3. 释放所有关联的猪栏 // 3. 释放所有关联的猪栏
// 获取该批次下所有猪栏 // 获取该批次下所有猪栏
pensInBatch, err := s.transferSvc.GetPensByBatchID(tx, id) pensInBatch, err := s.transferSvc.GetPensByBatchID(serviceCtx, tx, id)
if err != nil { if err != nil {
return fmt.Errorf("获取猪批次 %d 关联猪栏失败: %w", id, err) return fmt.Errorf("获取猪批次 %d 关联猪栏失败: %w", id, err)
} }
// 逐一释放猪栏 // 逐一释放猪栏
for _, pen := range pensInBatch { 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) return fmt.Errorf("释放猪栏 %d 失败: %w", pen.ID, err)
} }
} }
// 4. 执行删除猪批次 // 4. 执行删除猪批次
rowsAffected, err := s.pigBatchRepo.DeletePigBatchTx(tx, id) rowsAffected, err := s.pigBatchRepo.DeletePigBatchTx(serviceCtx, tx, id)
if err != nil { if err != nil {
return err return err
} }
@@ -122,16 +129,18 @@ func (s *pigBatchService) DeletePigBatch(id uint) error {
} }
// ListPigBatches 实现了批量查询猪批次的逻辑。 // ListPigBatches 实现了批量查询猪批次的逻辑。
func (s *pigBatchService) ListPigBatches(isActive *bool) ([]*models.PigBatch, error) { func (s *pigBatchService) ListPigBatches(ctx context.Context, isActive *bool) ([]*models.PigBatch, error) {
return s.pigBatchRepo.ListPigBatches(isActive) serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListPigBatches")
return s.pigBatchRepo.ListPigBatches(serviceCtx, isActive)
} }
// GetCurrentPigQuantity 实现了获取指定猪批次的当前猪只数量的逻辑。 // 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 getErr error
var quantity int var quantity int
err := s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
quantity, getErr = s.getCurrentPigQuantityTx(tx, batchID) quantity, getErr = s.getCurrentPigQuantityTx(serviceCtx, tx, batchID)
return getErr return getErr
}) })
if err != nil { if err != nil {
@@ -141,9 +150,10 @@ func (s *pigBatchService) GetCurrentPigQuantity(batchID uint) (int, error) {
} }
// getCurrentPigQuantityTx 实现了获取指定猪批次的当前猪只数量的逻辑。 // 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. 获取猪批次初始信息 // 1. 获取猪批次初始信息
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID) batch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, batchID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return 0, ErrPigBatchNotFound return 0, ErrPigBatchNotFound
@@ -152,7 +162,7 @@ func (s *pigBatchService) getCurrentPigQuantityTx(tx *gorm.DB, batchID uint) (in
} }
// 2. 尝试获取该批次的最后一条日志记录 // 2. 尝试获取该批次的最后一条日志记录
lastLog, err := s.pigBatchLogRepo.GetLastLogByBatchIDTx(tx, batchID) lastLog, err := s.pigBatchLogRepo.GetLastLogByBatchIDTx(serviceCtx, tx, batchID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
// 如果没有找到任何日志记录(除了初始创建),则当前数量就是初始数量 // 如果没有找到任何日志记录(除了初始创建),则当前数量就是初始数量
@@ -165,14 +175,16 @@ func (s *pigBatchService) getCurrentPigQuantityTx(tx *gorm.DB, batchID uint) (in
return lastLog.AfterCount, nil return lastLog.AfterCount, nil
} }
func (s *pigBatchService) UpdatePigBatchQuantity(operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error { func (s *pigBatchService) UpdatePigBatchQuantity(ctx context.Context, operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error {
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePigBatchQuantity")
return s.updatePigBatchQuantityTx(tx, operatorID, batchID, changeType, changeAmount, changeReason, happenedAt) 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 { 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 {
lastLog, err := s.pigBatchLogRepo.GetLastLogByBatchIDTx(tx, batchID) serviceCtx := logs.AddFuncName(ctx, s.ctx, "updatePigBatchQuantityTx")
lastLog, err := s.pigBatchLogRepo.GetLastLogByBatchIDTx(serviceCtx, tx, batchID)
if err != nil { if err != nil {
return err return err
} }
@@ -192,5 +204,5 @@ func (s *pigBatchService) updatePigBatchQuantityTx(tx *gorm.DB, operatorID uint,
OperatorID: operatorID, OperatorID: operatorID,
HappenedAt: happenedAt, HappenedAt: happenedAt,
} }
return s.pigBatchLogRepo.CreateTx(tx, pigBatchLog) return s.pigBatchLogRepo.CreateTx(serviceCtx, tx, pigBatchLog)
} }

View File

@@ -1,20 +1,25 @@
package pig package pig
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"time" "time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"github.com/google/uuid" "github.com/google/uuid"
"gorm.io/gorm" "gorm.io/gorm"
) )
// executeTransferAndLog 是一个私有辅助方法,用于封装创建和记录迁移日志的通用逻辑。 // 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 { // 当调出时才需要检查 if quantity < 0 { // 当调出时才需要检查
currentPigsInFromPen, err := s.transferSvc.GetCurrentPigsInPen(tx, fromPenID) currentPigsInFromPen, err := s.transferSvc.GetCurrentPigsInPen(serviceCtx, tx, fromPenID)
if err != nil { if err != nil {
return fmt.Errorf("获取源猪栏 %d 当前猪只数失败: %w", fromPenID, err) return fmt.Errorf("获取源猪栏 %d 当前猪只数失败: %w", fromPenID, err)
} }
@@ -51,10 +56,10 @@ func (s *pigBatchService) executeTransferAndLog(tx *gorm.DB, fromBatchID, toBatc
} }
// 4. 调用子服务记录日志 // 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) 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) return fmt.Errorf("记录调入日志失败: %w", err)
} }
@@ -62,7 +67,8 @@ func (s *pigBatchService) executeTransferAndLog(tx *gorm.DB, fromBatchID, toBatc
} }
// TransferPigsWithinBatch 实现了同一个猪群内部的调栏业务。 // 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 { if fromPenID == toPenID {
return errors.New("源猪栏和目标猪栏不能相同") return errors.New("源猪栏和目标猪栏不能相同")
} }
@@ -70,13 +76,13 @@ func (s *pigBatchService) TransferPigsWithinBatch(batchID uint, fromPenID uint,
return errors.New("迁移数量不能为零") return errors.New("迁移数量不能为零")
} }
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
// 1. 核心业务规则校验 // 1. 核心业务规则校验
fromPen, err := s.transferSvc.GetPenByID(tx, fromPenID) fromPen, err := s.transferSvc.GetPenByID(serviceCtx, tx, fromPenID)
if err != nil { if err != nil {
return fmt.Errorf("获取源猪栏信息失败: %w", err) return fmt.Errorf("获取源猪栏信息失败: %w", err)
} }
toPen, err := s.transferSvc.GetPenByID(tx, toPenID) toPen, err := s.transferSvc.GetPenByID(serviceCtx, tx, toPenID)
if err != nil { if err != nil {
return fmt.Errorf("获取目标猪栏信息失败: %w", err) return fmt.Errorf("获取目标猪栏信息失败: %w", err)
} }
@@ -89,7 +95,7 @@ func (s *pigBatchService) TransferPigsWithinBatch(batchID uint, fromPenID uint,
} }
// 2. 调用通用辅助方法执行日志记录 // 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 { if err != nil {
return err return err
} }
@@ -100,7 +106,8 @@ func (s *pigBatchService) TransferPigsWithinBatch(batchID uint, fromPenID uint,
} }
// TransferPigsAcrossBatches 实现了跨猪群的调栏业务。 // 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 { if sourceBatchID == destBatchID {
return errors.New("源猪群和目标猪群不能相同") return errors.New("源猪群和目标猪群不能相同")
} }
@@ -108,16 +115,16 @@ func (s *pigBatchService) TransferPigsAcrossBatches(sourceBatchID uint, destBatc
return errors.New("迁移数量不能为零") 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.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) { if errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("源猪群 %d 不存在", sourceBatchID) return fmt.Errorf("源猪群 %d 不存在", sourceBatchID)
} }
return fmt.Errorf("获取源猪群信息失败: %w", err) 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) { if errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("目标猪群 %d 不存在", destBatchID) return fmt.Errorf("目标猪群 %d 不存在", destBatchID)
} }
@@ -125,7 +132,7 @@ func (s *pigBatchService) TransferPigsAcrossBatches(sourceBatchID uint, destBatc
} }
// 1.2 校验猪栏归属 // 1.2 校验猪栏归属
fromPen, err := s.transferSvc.GetPenByID(tx, fromPenID) fromPen, err := s.transferSvc.GetPenByID(serviceCtx, tx, fromPenID)
if err != nil { if err != nil {
return fmt.Errorf("获取源猪栏信息失败: %w", err) return fmt.Errorf("获取源猪栏信息失败: %w", err)
} }
@@ -134,7 +141,7 @@ func (s *pigBatchService) TransferPigsAcrossBatches(sourceBatchID uint, destBatc
} }
// 2. 调用通用辅助方法执行猪只物理转移的日志记录 // 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 { if err != nil {
return err return err
} }
@@ -143,14 +150,14 @@ func (s *pigBatchService) TransferPigsAcrossBatches(sourceBatchID uint, destBatc
now := time.Now() now := time.Now()
// 3.1 记录源猪群数量减少 // 3.1 记录源猪群数量减少
reasonOut := fmt.Sprintf("跨群调栏: %d头猪从批次 %d 调出至批次 %d。备注: %s", quantity, sourceBatchID, destBatchID, remarks) 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 { if err != nil {
return fmt.Errorf("更新源猪群 %d 数量失败: %w", sourceBatchID, err) return fmt.Errorf("更新源猪群 %d 数量失败: %w", sourceBatchID, err)
} }
// 3.2 记录目标猪群数量增加 // 3.2 记录目标猪群数量增加
reasonIn := fmt.Sprintf("跨群调栏: %d头猪从批次 %d 调入。备注: %s", quantity, sourceBatchID, remarks) 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 { if err != nil {
return fmt.Errorf("更新目标猪群 %d 数量失败: %w", destBatchID, err) return fmt.Errorf("更新目标猪群 %d 数量失败: %w", destBatchID, err)
} }
@@ -160,10 +167,11 @@ func (s *pigBatchService) TransferPigsAcrossBatches(sourceBatchID uint, destBatc
} }
// AssignEmptyPensToBatch 为猪群分配空栏 // AssignEmptyPensToBatch 为猪群分配空栏
func (s *pigBatchService) AssignEmptyPensToBatch(batchID uint, penIDs []uint, operatorID uint) error { func (s *pigBatchService) AssignEmptyPensToBatch(ctx context.Context, batchID uint, penIDs []uint, operatorID uint) error {
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "AssignEmptyPensToBatch")
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
// 1. 验证猪批次是否存在且活跃 // 1. 验证猪批次是否存在且活跃
pigBatch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID) pigBatch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, batchID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPigBatchNotFound return ErrPigBatchNotFound
@@ -176,7 +184,7 @@ func (s *pigBatchService) AssignEmptyPensToBatch(batchID uint, penIDs []uint, op
// 2. 遍历并校验每一个待分配的猪栏 // 2. 遍历并校验每一个待分配的猪栏
for _, penID := range penIDs { for _, penID := range penIDs {
pen, err := s.transferSvc.GetPenByID(tx, penID) pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("猪栏 %d 不存在: %w", penID, ErrPenNotFound) return fmt.Errorf("猪栏 %d 不存在: %w", penID, ErrPenNotFound)
@@ -197,7 +205,7 @@ func (s *pigBatchService) AssignEmptyPensToBatch(batchID uint, penIDs []uint, op
"pig_batch_id": &batchID, "pig_batch_id": &batchID,
"status": models.PenStatusOccupied, "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) return fmt.Errorf("分配猪栏 %d 失败: %w", penID, err)
} }
} }
@@ -207,14 +215,15 @@ func (s *pigBatchService) AssignEmptyPensToBatch(batchID uint, penIDs []uint, op
} }
// MovePigsIntoPen 将猪只从“虚拟库存”移入指定猪栏 // 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 { if quantity <= 0 {
return errors.New("迁移数量必须大于零") return errors.New("迁移数量必须大于零")
} }
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
// 1. 验证猪批次是否存在且活跃 // 1. 验证猪批次是否存在且活跃
pigBatch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID) pigBatch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, batchID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPigBatchNotFound return ErrPigBatchNotFound
@@ -226,7 +235,7 @@ func (s *pigBatchService) MovePigsIntoPen(batchID uint, toPenID uint, quantity i
} }
// 2. 校验目标猪栏 // 2. 校验目标猪栏
toPen, err := s.transferSvc.GetPenByID(tx, toPenID) toPen, err := s.transferSvc.GetPenByID(serviceCtx, tx, toPenID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("目标猪栏 %d 不存在: %w", toPenID, ErrPenNotFound) return fmt.Errorf("目标猪栏 %d 不存在: %w", toPenID, ErrPenNotFound)
@@ -243,13 +252,13 @@ func (s *pigBatchService) MovePigsIntoPen(batchID uint, toPenID uint, quantity i
} }
// 3. 校验猪群中有足够的“未分配”猪只 // 3. 校验猪群中有足够的“未分配”猪只
currentBatchTotal, err := s.getCurrentPigQuantityTx(tx, batchID) currentBatchTotal, err := s.getCurrentPigQuantityTx(serviceCtx, tx, batchID)
if err != nil { if err != nil {
return fmt.Errorf("获取猪群 %d 当前总数量失败: %w", batchID, err) 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 { if err != nil {
return fmt.Errorf("计算猪群 %d 下属猪栏总存栏失败: %w", batchID, err) return fmt.Errorf("计算猪群 %d 下属猪栏总存栏失败: %w", batchID, err)
} }
@@ -269,7 +278,7 @@ func (s *pigBatchService) MovePigsIntoPen(batchID uint, toPenID uint, quantity i
OperatorID: operatorID, OperatorID: operatorID,
Remarks: remarks, 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) return fmt.Errorf("记录入栏日志失败: %w", err)
} }
@@ -278,22 +287,23 @@ func (s *pigBatchService) MovePigsIntoPen(batchID uint, toPenID uint, quantity i
} }
// ReclassifyPenToNewBatch 连猪带栏,整体划拨到另一个猪群 // 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 { if fromBatchID == toBatchID {
return errors.New("源猪群和目标猪群不能相同") 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.1 校验猪群存在 // 1.1 校验猪群存在
fromBatch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, fromBatchID) fromBatch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, fromBatchID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("源猪群 %d 不存在", fromBatchID) return fmt.Errorf("源猪群 %d 不存在", fromBatchID)
} }
return fmt.Errorf("获取源猪群信息失败: %w", err) return fmt.Errorf("获取源猪群信息失败: %w", err)
} }
toBatch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, toBatchID) toBatch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, toBatchID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("目标猪群 %d 不存在", toBatchID) return fmt.Errorf("目标猪群 %d 不存在", toBatchID)
@@ -302,7 +312,7 @@ func (s *pigBatchService) ReclassifyPenToNewBatch(fromBatchID uint, toBatchID ui
} }
// 1.2 校验猪栏归属 // 1.2 校验猪栏归属
pen, err := s.transferSvc.GetPenByID(tx, penID) pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("猪栏 %d 不存在: %w", penID, ErrPenNotFound) return fmt.Errorf("猪栏 %d 不存在: %w", penID, ErrPenNotFound)
@@ -314,7 +324,7 @@ func (s *pigBatchService) ReclassifyPenToNewBatch(fromBatchID uint, toBatchID ui
} }
// 2. 获取猪栏当前存栏数 // 2. 获取猪栏当前存栏数
quantity, err := s.transferSvc.GetCurrentPigsInPen(tx, penID) quantity, err := s.transferSvc.GetCurrentPigsInPen(serviceCtx, tx, penID)
if err != nil { if err != nil {
return fmt.Errorf("获取猪栏 %v 存栏数失败: %w", pen.PenNumber, err) return fmt.Errorf("获取猪栏 %v 存栏数失败: %w", pen.PenNumber, err)
} }
@@ -323,7 +333,7 @@ func (s *pigBatchService) ReclassifyPenToNewBatch(fromBatchID uint, toBatchID ui
updates := map[string]interface{}{ updates := map[string]interface{}{
"pig_batch_id": &toBatchID, "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) return fmt.Errorf("更新猪栏 %v 归属失败: %w", pen.PenNumber, err)
} }
// 如果猪栏是空的,则只进行归属变更,不影响猪群数量 // 如果猪栏是空的,则只进行归属变更,不影响猪群数量
@@ -343,7 +353,7 @@ func (s *pigBatchService) ReclassifyPenToNewBatch(fromBatchID uint, toBatchID ui
OperatorID: operatorID, OperatorID: operatorID,
Remarks: fmt.Sprintf("整栏划拨迁出: %d头猪从批次 %v 随猪栏 %v 划拨至批次 %v。备注: %s", quantity, fromBatch.BatchNumber, pen.PenNumber, toBatch.BatchNumber, remarks), 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) return fmt.Errorf("记录猪栏 %d 迁出日志失败: %w", penID, err)
} }
@@ -358,7 +368,7 @@ func (s *pigBatchService) ReclassifyPenToNewBatch(fromBatchID uint, toBatchID ui
OperatorID: operatorID, OperatorID: operatorID,
Remarks: fmt.Sprintf("整栏划拨迁入: %v头猪随猪栏 %v 从批次 %v 划拨入。备注: %s", quantity, fromBatch.BatchNumber, pen.PenNumber, remarks), 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) return fmt.Errorf("记录猪栏 %d 迁入日志失败: %w", penID, err)
} }
@@ -366,14 +376,14 @@ func (s *pigBatchService) ReclassifyPenToNewBatch(fromBatchID uint, toBatchID ui
now := time.Now() now := time.Now()
// 7.1 记录源猪群数量减少 // 7.1 记录源猪群数量减少
reasonOutBatch := fmt.Sprintf("整栏划拨: %d头猪随猪栏 %v 从批次 %v 划拨至批次 %v。备注: %s", quantity, pen.PenNumber, fromBatch.BatchNumber, toBatchID, remarks) 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 { if err != nil {
return fmt.Errorf("更新源猪群 %v 数量失败: %w", fromBatch.BatchNumber, err) return fmt.Errorf("更新源猪群 %v 数量失败: %w", fromBatch.BatchNumber, err)
} }
// 7.2 记录目标猪群数量增加 // 7.2 记录目标猪群数量增加
reasonInBatch := fmt.Sprintf("整栏划拨: %v头猪随猪栏 %v 从批次 %v 划拨入。备注: %s", quantity, pen.PenNumber, fromBatch.BatchNumber, remarks) 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 { if err != nil {
return fmt.Errorf("更新目标猪群 %v 数量失败: %w", toBatch.BatchNumber, err) 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 { func (s *pigBatchService) RemoveEmptyPenFromBatch(ctx context.Context, batchID uint, penID uint) error {
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "RemoveEmptyPenFromBatch")
return 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 err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPigBatchNotFound return ErrPigBatchNotFound
@@ -397,7 +408,7 @@ func (s *pigBatchService) RemoveEmptyPenFromBatch(batchID uint, penID uint) erro
} }
// 2. 检查猪栏是否存在 // 2. 检查猪栏是否存在
pen, err := s.transferSvc.GetPenByID(tx, penID) pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPenNotFound return ErrPenNotFound
@@ -411,7 +422,7 @@ func (s *pigBatchService) RemoveEmptyPenFromBatch(batchID uint, penID uint) erro
} }
// 4. 检查猪栏是否为空 // 4. 检查猪栏是否为空
pigsInPen, err := s.transferSvc.GetCurrentPigsInPen(tx, penID) pigsInPen, err := s.transferSvc.GetCurrentPigsInPen(serviceCtx, tx, penID)
if err != nil { if err != nil {
return err return err
} }
@@ -420,17 +431,18 @@ func (s *pigBatchService) RemoveEmptyPenFromBatch(batchID uint, penID uint) erro
} }
// 5. 释放猪栏 (将 pig_batch_id 设置为 nil状态设置为空闲) // 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 err
} }
return nil 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 var currentPigs int
err := s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
pigs, err := s.transferSvc.GetCurrentPigsInPen(tx, penID) pigs, err := s.transferSvc.GetCurrentPigsInPen(serviceCtx, tx, penID)
if err != nil { if err != nil {
return err return err
} }
@@ -441,10 +453,11 @@ func (s *pigBatchService) GetCurrentPigsInPen(penID uint) (int, error) {
} }
// GetTotalPigsInPensForBatch 实现了获取指定猪群下所有猪栏的当前总存栏数的逻辑。 // 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 var totalPigs int
err := s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
pigs, err := s.transferSvc.GetTotalPigsInPensForBatchTx(tx, batchID) pigs, err := s.transferSvc.GetTotalPigsInPensForBatchTx(serviceCtx, tx, batchID)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -1,25 +1,29 @@
package pig package pig
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"time" "time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm" "gorm.io/gorm"
) )
// RecordSickPigs 记录新增病猪事件。 // 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 { if quantity <= 0 {
return errors.New("新增病猪数量必须大于0") return errors.New("新增病猪数量必须大于0")
} }
var err error var err error
// 1. 开启事务 // 1. 开启事务
err = s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { err = s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
// 1.1 检查批次是否活跃 // 1.1 检查批次是否活跃
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID) batch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, batchID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPigBatchNotFound return ErrPigBatchNotFound
@@ -31,7 +35,7 @@ func (s *pigBatchService) RecordSickPigs(operatorID uint, batchID uint, penID ui
} }
// 1.2 检查猪栏是否关联 // 1.2 检查猪栏是否关联
pen, err := s.transferSvc.GetPenByID(tx, penID) pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPenNotFound return ErrPenNotFound
@@ -43,12 +47,12 @@ func (s *pigBatchService) RecordSickPigs(operatorID uint, batchID uint, penID ui
} }
// 1.3 检查剩余健康猪不能少于即将转化的病猪数量 // 1.3 检查剩余健康猪不能少于即将转化的病猪数量
totalPigsInBatch, err := s.getCurrentPigQuantityTx(tx, batchID) totalPigsInBatch, err := s.getCurrentPigQuantityTx(serviceCtx, tx, batchID)
if err != nil { if err != nil {
return fmt.Errorf("获取批次 %d 总猪只数量失败: %w", batchID, err) 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 { if err != nil {
return fmt.Errorf("获取批次 %d 当前病猪数量失败: %w", batchID, err) return fmt.Errorf("获取批次 %d 当前病猪数量失败: %w", batchID, err)
} }
@@ -70,7 +74,7 @@ func (s *pigBatchService) RecordSickPigs(operatorID uint, batchID uint, penID ui
HappenedAt: happenedAt, 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) return fmt.Errorf("处理病猪日志失败: %w", err)
} }
@@ -85,15 +89,16 @@ func (s *pigBatchService) RecordSickPigs(operatorID uint, batchID uint, penID ui
} }
// RecordSickPigRecovery 记录病猪康复事件。 // 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 { if quantity <= 0 {
return errors.New("康复猪只数量必须大于0") return errors.New("康复猪只数量必须大于0")
} }
var err error var err error
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 err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPigBatchNotFound return ErrPigBatchNotFound
@@ -105,7 +110,7 @@ func (s *pigBatchService) RecordSickPigRecovery(operatorID uint, batchID uint, p
} }
// 2. 检查猪栏是否关联 // 2. 检查猪栏是否关联
pen, err := s.transferSvc.GetPenByID(tx, penID) pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPenNotFound return ErrPenNotFound
@@ -117,7 +122,7 @@ func (s *pigBatchService) RecordSickPigRecovery(operatorID uint, batchID uint, p
} }
// 3. 检查当前病猪数量是否足够康复 // 3. 检查当前病猪数量是否足够康复
currentSickPigs, err := s.sickSvc.GetCurrentSickPigCount(tx, batchID) currentSickPigs, err := s.sickSvc.GetCurrentSickPigCount(serviceCtx, tx, batchID)
if err != nil { if err != nil {
return fmt.Errorf("获取批次 %d 当前病猪数量失败: %w", batchID, err) return fmt.Errorf("获取批次 %d 当前病猪数量失败: %w", batchID, err)
} }
@@ -138,7 +143,7 @@ func (s *pigBatchService) RecordSickPigRecovery(operatorID uint, batchID uint, p
HappenedAt: happenedAt, 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) return fmt.Errorf("处理病猪康复日志失败: %w", err)
} }
@@ -153,15 +158,16 @@ func (s *pigBatchService) RecordSickPigRecovery(operatorID uint, batchID uint, p
} }
// RecordSickPigDeath 记录病猪死亡事件。 // 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 { if quantity <= 0 {
return errors.New("死亡猪只数量必须大于0") return errors.New("死亡猪只数量必须大于0")
} }
var err error var err error
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 err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPigBatchNotFound return ErrPigBatchNotFound
@@ -173,7 +179,7 @@ func (s *pigBatchService) RecordSickPigDeath(operatorID uint, batchID uint, penI
} }
// 2. 检查猪栏是否关联 // 2. 检查猪栏是否关联
pen, err := s.transferSvc.GetPenByID(tx, penID) pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPenNotFound return ErrPenNotFound
@@ -185,7 +191,7 @@ func (s *pigBatchService) RecordSickPigDeath(operatorID uint, batchID uint, penI
} }
// 3. 检查当前病猪数量是否足够死亡 // 3. 检查当前病猪数量是否足够死亡
currentSickPigs, err := s.sickSvc.GetCurrentSickPigCount(tx, batchID) currentSickPigs, err := s.sickSvc.GetCurrentSickPigCount(serviceCtx, tx, batchID)
if err != nil { if err != nil {
return fmt.Errorf("获取批次 %d 当前病猪数量失败: %w", batchID, err) return fmt.Errorf("获取批次 %d 当前病猪数量失败: %w", batchID, err)
} }
@@ -195,7 +201,7 @@ func (s *pigBatchService) RecordSickPigDeath(operatorID uint, batchID uint, penI
} }
// 4. 检查猪栏内猪只数量是否足够死亡 // 4. 检查猪栏内猪只数量是否足够死亡
currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(tx, penID) currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(serviceCtx, tx, penID)
if err != nil { if err != nil {
return fmt.Errorf("获取猪栏 %d 当前猪只数量失败: %w", penID, err) return fmt.Errorf("获取猪栏 %d 当前猪只数量失败: %w", penID, err)
} }
@@ -214,12 +220,12 @@ func (s *pigBatchService) RecordSickPigDeath(operatorID uint, batchID uint, penI
OperatorID: operatorID, OperatorID: operatorID,
HappenedAt: happenedAt, 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) return fmt.Errorf("处理病猪死亡日志失败: %w", err)
} }
// 6. 更新批次总猪只数量 (减少批次总数) // 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) return fmt.Errorf("更新批次 %d 总猪只数量失败: %w", batchID, err)
} }
@@ -233,7 +239,7 @@ func (s *pigBatchService) RecordSickPigDeath(operatorID uint, batchID uint, penI
OperatorID: operatorID, OperatorID: operatorID,
Remarks: remarks, 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) return fmt.Errorf("记录猪只死亡转移日志失败: %w", err)
} }
@@ -248,15 +254,16 @@ func (s *pigBatchService) RecordSickPigDeath(operatorID uint, batchID uint, penI
} }
// RecordSickPigCull 记录病猪淘汰事件。 // 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 { if quantity <= 0 {
return errors.New("淘汰猪只数量必须大于0") return errors.New("淘汰猪只数量必须大于0")
} }
var err error var err error
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 err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPigBatchNotFound return ErrPigBatchNotFound
@@ -268,7 +275,7 @@ func (s *pigBatchService) RecordSickPigCull(operatorID uint, batchID uint, penID
} }
// 2. 检查猪栏是否关联 // 2. 检查猪栏是否关联
pen, err := s.transferSvc.GetPenByID(tx, penID) pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPenNotFound return ErrPenNotFound
@@ -280,7 +287,7 @@ func (s *pigBatchService) RecordSickPigCull(operatorID uint, batchID uint, penID
} }
// 3. 检查当前病猪数量是否足够淘汰 // 3. 检查当前病猪数量是否足够淘汰
currentSickPigs, err := s.sickSvc.GetCurrentSickPigCount(tx, batchID) currentSickPigs, err := s.sickSvc.GetCurrentSickPigCount(serviceCtx, tx, batchID)
if err != nil { if err != nil {
return fmt.Errorf("获取批次 %d 当前病猪数量失败: %w", batchID, err) return fmt.Errorf("获取批次 %d 当前病猪数量失败: %w", batchID, err)
} }
@@ -290,7 +297,7 @@ func (s *pigBatchService) RecordSickPigCull(operatorID uint, batchID uint, penID
} }
// 4. 检查猪栏内猪只数量是否足够淘汰 // 4. 检查猪栏内猪只数量是否足够淘汰
currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(tx, penID) currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(serviceCtx, tx, penID)
if err != nil { if err != nil {
return fmt.Errorf("获取猪栏 %d 当前猪只数量失败: %w", penID, err) return fmt.Errorf("获取猪栏 %d 当前猪只数量失败: %w", penID, err)
} }
@@ -309,12 +316,12 @@ func (s *pigBatchService) RecordSickPigCull(operatorID uint, batchID uint, penID
OperatorID: operatorID, OperatorID: operatorID,
HappenedAt: happenedAt, 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) return fmt.Errorf("处理病猪淘汰日志失败: %w", err)
} }
// 6. 更新批次总猪只数量 (减少批次总数) // 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) return fmt.Errorf("更新批次 %d 总猪只数量失败: %w", batchID, err)
} }
@@ -328,7 +335,7 @@ func (s *pigBatchService) RecordSickPigCull(operatorID uint, batchID uint, penID
OperatorID: operatorID, OperatorID: operatorID,
Remarks: remarks, 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) return fmt.Errorf("记录猪只淘汰转移日志失败: %w", err)
} }
@@ -343,15 +350,16 @@ func (s *pigBatchService) RecordSickPigCull(operatorID uint, batchID uint, penID
} }
// RecordDeath 记录正常猪只死亡事件。 // 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 { if quantity <= 0 {
return errors.New("死亡猪只数量必须大于0") return errors.New("死亡猪只数量必须大于0")
} }
var err error var err error
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 err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPigBatchNotFound return ErrPigBatchNotFound
@@ -363,7 +371,7 @@ func (s *pigBatchService) RecordDeath(operatorID uint, batchID uint, penID uint,
} }
// 2. 检查猪栏是否关联 // 2. 检查猪栏是否关联
pen, err := s.transferSvc.GetPenByID(tx, penID) pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPenNotFound return ErrPenNotFound
@@ -375,7 +383,7 @@ func (s *pigBatchService) RecordDeath(operatorID uint, batchID uint, penID uint,
} }
// 3. 检查猪栏内猪只数量是否足够死亡 // 3. 检查猪栏内猪只数量是否足够死亡
currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(tx, penID) currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(serviceCtx, tx, penID)
if err != nil { if err != nil {
return fmt.Errorf("获取猪栏 %d 当前猪只数量失败: %w", penID, err) return fmt.Errorf("获取猪栏 %d 当前猪只数量失败: %w", penID, err)
} }
@@ -384,7 +392,7 @@ func (s *pigBatchService) RecordDeath(operatorID uint, batchID uint, penID uint,
} }
// 4. 更新批次总猪只数量 (减少批次总数) // 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) return fmt.Errorf("更新批次 %d 总猪只数量失败: %w", batchID, err)
} }
@@ -398,7 +406,7 @@ func (s *pigBatchService) RecordDeath(operatorID uint, batchID uint, penID uint,
OperatorID: operatorID, OperatorID: operatorID,
Remarks: remarks, 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) return fmt.Errorf("记录猪只死亡转移日志失败: %w", err)
} }
@@ -413,15 +421,16 @@ func (s *pigBatchService) RecordDeath(operatorID uint, batchID uint, penID uint,
} }
// RecordCull 记录正常猪只淘汰事件。 // 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 { if quantity <= 0 {
return errors.New("淘汰猪只数量必须大于0") return errors.New("淘汰猪只数量必须大于0")
} }
var err error var err error
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 err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPigBatchNotFound return ErrPigBatchNotFound
@@ -433,7 +442,7 @@ func (s *pigBatchService) RecordCull(operatorID uint, batchID uint, penID uint,
} }
// 2. 检查猪栏是否关联 // 2. 检查猪栏是否关联
pen, err := s.transferSvc.GetPenByID(tx, penID) pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPenNotFound return ErrPenNotFound
@@ -445,7 +454,7 @@ func (s *pigBatchService) RecordCull(operatorID uint, batchID uint, penID uint,
} }
// 3. 检查猪栏内猪只数量是否足够淘汰 // 3. 检查猪栏内猪只数量是否足够淘汰
currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(tx, penID) currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(serviceCtx, tx, penID)
if err != nil { if err != nil {
return fmt.Errorf("获取猪栏 %d 当前猪只数量失败: %w", penID, err) return fmt.Errorf("获取猪栏 %d 当前猪只数量失败: %w", penID, err)
} }
@@ -454,7 +463,7 @@ func (s *pigBatchService) RecordCull(operatorID uint, batchID uint, penID uint,
} }
// 4. 更新批次总猪只数量 (减少批次总数) // 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) return fmt.Errorf("更新批次 %d 总猪只数量失败: %w", batchID, err)
} }
@@ -468,7 +477,7 @@ func (s *pigBatchService) RecordCull(operatorID uint, batchID uint, penID uint,
OperatorID: operatorID, OperatorID: operatorID,
Remarks: remarks, 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) return fmt.Errorf("记录猪只淘汰转移日志失败: %w", err)
} }

View File

@@ -1,23 +1,27 @@
package pig package pig
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"time" "time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm" "gorm.io/gorm"
) )
// SellPigs 处理批量销售猪的业务逻辑。 // 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 { 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 {
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "SellPigs")
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
if quantity <= 0 { if quantity <= 0 {
return errors.New("销售数量必须大于0") return errors.New("销售数量必须大于0")
} }
// 1. 校验猪栏信息 // 1. 校验猪栏信息
pen, err := s.transferSvc.GetPenByID(tx, penID) pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPenNotFound return ErrPenNotFound
@@ -31,7 +35,7 @@ func (s *pigBatchService) SellPigs(batchID uint, penID uint, quantity int, unitP
} }
// 2. 业务校验:检查销售数量是否超过猪栏当前猪只数 // 2. 业务校验:检查销售数量是否超过猪栏当前猪只数
currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(tx, penID) currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(serviceCtx, tx, penID)
if err != nil { if err != nil {
return fmt.Errorf("获取猪栏 %d 当前猪只数失败: %w", penID, err) return fmt.Errorf("获取猪栏 %d 当前猪只数失败: %w", penID, err)
} }
@@ -51,7 +55,7 @@ func (s *pigBatchService) SellPigs(batchID uint, penID uint, quantity int, unitP
Remarks: remarks, Remarks: remarks,
OperatorID: operatorID, 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) return fmt.Errorf("记录销售交易失败: %w", err)
} }
@@ -65,12 +69,12 @@ func (s *pigBatchService) SellPigs(batchID uint, penID uint, quantity int, unitP
OperatorID: operatorID, OperatorID: operatorID,
Remarks: fmt.Sprintf("销售给 %s", traderName), 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) return fmt.Errorf("创建猪只转移日志失败: %w", err)
} }
// 5. 记录批次数量变更日志 (逻辑) // 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), fmt.Sprintf("猪批次 %d 从猪栏 %d 销售 %d 头猪给 %s", batchID, penID, quantity, traderName),
tradeDate); err != nil { tradeDate); err != nil {
return fmt.Errorf("更新猪批次数量失败: %w", err) return fmt.Errorf("更新猪批次数量失败: %w", err)
@@ -81,14 +85,15 @@ func (s *pigBatchService) SellPigs(batchID uint, penID uint, quantity int, unitP
} }
// BuyPigs 处理批量购买猪的业务逻辑。 // 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 { 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 {
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "BuyPigs")
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
if quantity <= 0 { if quantity <= 0 {
return errors.New("采购数量必须大于0") return errors.New("采购数量必须大于0")
} }
// 1. 校验猪栏信息 // 1. 校验猪栏信息
pen, err := s.transferSvc.GetPenByID(tx, penID) pen, err := s.transferSvc.GetPenByID(serviceCtx, tx, penID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPenNotFound return ErrPenNotFound
@@ -102,7 +107,7 @@ func (s *pigBatchService) BuyPigs(batchID uint, penID uint, quantity int, unitPr
} }
// 2. 业务校验:检查猪栏容量,如果超出,在备注中记录警告 // 2. 业务校验:检查猪栏容量,如果超出,在备注中记录警告
currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(tx, penID) currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(serviceCtx, tx, penID)
if err != nil { if err != nil {
return fmt.Errorf("获取猪栏 %d 当前猪只数失败: %w", penID, err) return fmt.Errorf("获取猪栏 %d 当前猪只数失败: %w", penID, err)
} }
@@ -124,7 +129,7 @@ func (s *pigBatchService) BuyPigs(batchID uint, penID uint, quantity int, unitPr
Remarks: remarks, // 用户传入的备注 Remarks: remarks, // 用户传入的备注
OperatorID: operatorID, 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) return fmt.Errorf("记录采购交易失败: %w", err)
} }
@@ -138,12 +143,12 @@ func (s *pigBatchService) BuyPigs(batchID uint, penID uint, quantity int, unitPr
OperatorID: operatorID, OperatorID: operatorID,
Remarks: transferRemarks, // 包含系统生成的备注和潜在的警告 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) return fmt.Errorf("创建猪只转移日志失败: %w", err)
} }
// 5. 记录批次数量变更日志 (逻辑) // 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), fmt.Sprintf("猪批次 %d 在猪栏 %d 采购 %d 头猪从 %s", batchID, penID, quantity, traderName),
tradeDate); err != nil { tradeDate); err != nil {
return fmt.Errorf("更新猪批次数量失败: %w", err) return fmt.Errorf("更新猪批次数量失败: %w", err)

View File

@@ -1,11 +1,14 @@
package pig package pig
import ( import (
"context"
"errors" "errors"
"fmt" "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/models"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -15,31 +18,35 @@ type SickPigManager interface {
// ProcessSickPigLog 处理病猪相关的日志事件。 // ProcessSickPigLog 处理病猪相关的日志事件。
// log 包含事件的基本信息,如 PigBatchID, PenID, PigIDs, ChangeCount, Reason, TreatmentLocation, Remarks, OperatorID, HappenedAt。 // log 包含事件的基本信息,如 PigBatchID, PenID, PigIDs, ChangeCount, Reason, TreatmentLocation, Remarks, OperatorID, HappenedAt。
// Manager 内部会计算并填充 BeforeCount 和 AfterCount并进行必要的业务校验和副作用处理。 // Manager 内部会计算并填充 BeforeCount 和 AfterCount并进行必要的业务校验和副作用处理。
ProcessSickPigLog(tx *gorm.DB, log *models.PigSickLog) error ProcessSickPigLog(ctx context.Context, tx *gorm.DB, log *models.PigSickLog) error
// GetCurrentSickPigCount 获取指定批次当前患病猪只的总数 // GetCurrentSickPigCount 获取指定批次当前患病猪只的总数
GetCurrentSickPigCount(tx *gorm.DB, batchID uint) (int, error) GetCurrentSickPigCount(ctx context.Context, tx *gorm.DB, batchID uint) (int, error)
} }
// sickPigManager 是 SickPigManager 接口的具体实现。 // sickPigManager 是 SickPigManager 接口的具体实现。
// 它依赖于仓库接口来执行数据持久化操作。 // 它依赖于仓库接口来执行数据持久化操作。
type sickPigManager struct { type sickPigManager struct {
ctx context.Context
sickLogRepo repository.PigSickLogRepository sickLogRepo repository.PigSickLogRepository
medicationLogRepo repository.MedicationLogRepository medicationLogRepo repository.MedicationLogRepository
} }
// NewSickPigManager 是 sickPigManager 的构造函数。 // NewSickPigManager 是 sickPigManager 的构造函数。
func NewSickPigManager( func NewSickPigManager(
ctx context.Context,
sickLogRepo repository.PigSickLogRepository, sickLogRepo repository.PigSickLogRepository,
medicationLogRepo repository.MedicationLogRepository, medicationLogRepo repository.MedicationLogRepository,
) SickPigManager { ) SickPigManager {
return &sickPigManager{ return &sickPigManager{
ctx: ctx,
sickLogRepo: sickLogRepo, sickLogRepo: sickLogRepo,
medicationLogRepo: medicationLogRepo, 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. 输入校验 // 1. 输入校验
if log == nil { if log == nil {
return errors.New("病猪日志不能为空") return errors.New("病猪日志不能为空")
@@ -93,7 +100,7 @@ func (s *sickPigManager) ProcessSickPigLog(tx *gorm.DB, log *models.PigSickLog)
} }
// 2. 获取当前病猪数量 (BeforeCount) // 2. 获取当前病猪数量 (BeforeCount)
beforeCount, err := s.GetCurrentSickPigCount(tx, log.PigBatchID) beforeCount, err := s.GetCurrentSickPigCount(managerCtx, tx, log.PigBatchID)
if err != nil { if err != nil {
return fmt.Errorf("获取批次 %d 当前病猪数量失败: %w", log.PigBatchID, err) return fmt.Errorf("获取批次 %d 当前病猪数量失败: %w", log.PigBatchID, err)
} }
@@ -108,15 +115,16 @@ func (s *sickPigManager) ProcessSickPigLog(tx *gorm.DB, log *models.PigSickLog)
} }
// 5. 持久化 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 fmt.Errorf("创建 PigSickLog 失败: %w", err)
} }
return nil return nil
} }
func (s *sickPigManager) GetCurrentSickPigCount(tx *gorm.DB, batchID uint) (int, error) { func (s *sickPigManager) GetCurrentSickPigCount(ctx context.Context, tx *gorm.DB, batchID uint) (int, error) {
lastLog, err := s.sickLogRepo.GetLastLogByBatchTx(tx, batchID) managerCtx := logs.AddFuncName(ctx, s.ctx, "GetCurrentSickPigCount")
lastLog, err := s.sickLogRepo.GetLastLogByBatchTx(managerCtx, tx, batchID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return 0, nil // 如果没有找到任何日志表示当前病猪数量为0 return 0, nil // 如果没有找到任何日志表示当前病猪数量为0

View File

@@ -1,8 +1,12 @@
package pig package pig
import ( 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/models"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" // 引入基础设施层的仓库接口 "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -10,37 +14,41 @@ import (
// 这是一个领域服务,负责协调业务逻辑。 // 这是一个领域服务,负责协调业务逻辑。
type PigTradeManager interface { type PigTradeManager interface {
// SellPig 处理卖猪的业务逻辑,通过仓库接口创建 PigSale 记录。 // SellPig 处理卖猪的业务逻辑,通过仓库接口创建 PigSale 记录。
SellPig(tx *gorm.DB, sale *models.PigSale) error SellPig(ctx context.Context, tx *gorm.DB, sale *models.PigSale) error
// BuyPig 处理买猪的业务逻辑,通过仓库接口创建 PigPurchase 记录。 // BuyPig 处理买猪的业务逻辑,通过仓库接口创建 PigPurchase 记录。
BuyPig(tx *gorm.DB, purchase *models.PigPurchase) error BuyPig(ctx context.Context, tx *gorm.DB, purchase *models.PigPurchase) error
} }
// pigTradeManager 是 PigTradeManager 接口的具体实现。 // pigTradeManager 是 PigTradeManager 接口的具体实现。
// 它依赖于 repository.PigTradeRepository 接口来执行数据持久化操作。 // 它依赖于 repository.PigTradeRepository 接口来执行数据持久化操作。
type pigTradeManager struct { type pigTradeManager struct {
tradeRepo repository.PigTradeRepository // 依赖于基础设施层定义的仓库接口 ctx context.Context
tradeRepo repository.PigTradeRepository
} }
// NewPigTradeManager 是 pigTradeManager 的构造函数。 // NewPigTradeManager 是 pigTradeManager 的构造函数。
func NewPigTradeManager(tradeRepo repository.PigTradeRepository) PigTradeManager { func NewPigTradeManager(ctx context.Context, tradeRepo repository.PigTradeRepository) PigTradeManager {
return &pigTradeManager{ return &pigTradeManager{
ctx: ctx,
tradeRepo: tradeRepo, tradeRepo: tradeRepo,
} }
} }
// SellPig 实现了卖猪的逻辑。 // SellPig 实现了卖猪的逻辑。
// 它通过调用 tradeRepo 来持久化销售记录。 // 它通过调用 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 实现了买猪的逻辑。 // BuyPig 实现了买猪的逻辑。
// 它通过调用 tradeRepo 来持久化采购记录。 // 它通过调用 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)
} }

View File

@@ -1,6 +1,7 @@
package plan package plan
import ( import (
"context"
"fmt" "fmt"
"sync" "sync"
"time" "time"
@@ -14,78 +15,80 @@ import (
// AnalysisPlanTaskManager 定义了分析计划任务管理器的接口。 // AnalysisPlanTaskManager 定义了分析计划任务管理器的接口。
type AnalysisPlanTaskManager interface { type AnalysisPlanTaskManager interface {
// Refresh 同步数据库中的计划状态和待执行队列中的触发器任务。 // Refresh 同步数据库中的计划状态和待执行队列中的触发器任务。
Refresh() error Refresh(ctx context.Context) error
// CreateOrUpdateTrigger 为给定的 planID 创建其关联的触发任务。 // CreateOrUpdateTrigger 为给定的 planID 创建其关联的触发任务。
// 如果触发器已存在,会根据计划类型更新其执行时间。 // 如果触发器已存在,会根据计划类型更新其执行时间。
CreateOrUpdateTrigger(planID uint) error CreateOrUpdateTrigger(ctx context.Context, planID uint) error
// EnsureAnalysisTaskDefinition 确保计划的分析任务定义存在于 tasks 表中。 // EnsureAnalysisTaskDefinition 确保计划的分析任务定义存在于 tasks 表中。
// 如果不存在,则会自动创建。此方法不涉及待执行队列。 // 如果不存在,则会自动创建。此方法不涉及待执行队列。
EnsureAnalysisTaskDefinition(planID uint) error EnsureAnalysisTaskDefinition(ctx context.Context, planID uint) error
} }
// analysisPlanTaskManagerImpl 负责管理分析计划的触发器任务。 // analysisPlanTaskManagerImpl 负责管理分析计划的触发器任务。
// 它确保数据库中可执行的计划在待执行队列中有对应的触发器,并移除无效的触发器。 // 它确保数据库中可执行的计划在待执行队列中有对应的触发器,并移除无效的触发器。
// 这是一个有状态的组件,包含一个互斥锁以确保并发安全。 // 这是一个有状态的组件,包含一个互斥锁以确保并发安全。
type analysisPlanTaskManagerImpl struct { type analysisPlanTaskManagerImpl struct {
ctx context.Context
planRepo repository.PlanRepository planRepo repository.PlanRepository
pendingTaskRepo repository.PendingTaskRepository pendingTaskRepo repository.PendingTaskRepository
executionLogRepo repository.ExecutionLogRepository executionLogRepo repository.ExecutionLogRepository
logger *logs.Logger
mu sync.Mutex mu sync.Mutex
} }
// NewAnalysisPlanTaskManager 是 analysisPlanTaskManagerImpl 的构造函数。 // NewAnalysisPlanTaskManager 是 analysisPlanTaskManagerImpl 的构造函数。
func NewAnalysisPlanTaskManager( func NewAnalysisPlanTaskManager(
ctx context.Context,
planRepo repository.PlanRepository, planRepo repository.PlanRepository,
pendingTaskRepo repository.PendingTaskRepository, pendingTaskRepo repository.PendingTaskRepository,
executionLogRepo repository.ExecutionLogRepository, executionLogRepo repository.ExecutionLogRepository,
logger *logs.Logger,
) AnalysisPlanTaskManager { ) AnalysisPlanTaskManager {
return &analysisPlanTaskManagerImpl{ return &analysisPlanTaskManagerImpl{
ctx: ctx,
planRepo: planRepo, planRepo: planRepo,
pendingTaskRepo: pendingTaskRepo, pendingTaskRepo: pendingTaskRepo,
executionLogRepo: executionLogRepo, executionLogRepo: executionLogRepo,
logger: logger,
} }
} }
// Refresh 同步数据库中的计划状态和待执行队列中的触发器任务。 // 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() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
m.logger.Info("开始同步计划任务管理器...") logger.Info("开始同步计划任务管理器...")
// 1. 一次性获取所有需要的数据 // 1. 一次性获取所有需要的数据
runnablePlans, invalidPlanIDs, pendingTasks, err := m.getRefreshData() runnablePlans, invalidPlanIDs, pendingTasks, err := m.getRefreshData(managerCtx)
if err != nil { if err != nil {
return fmt.Errorf("获取刷新数据失败: %w", err) return fmt.Errorf("获取刷新数据失败: %w", err)
} }
// 2. 清理所有与失效计划相关的待执行任务 // 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. 添加或更新触发器 // 3. 添加或更新触发器
if err := m.addOrUpdateTriggers(runnablePlans, pendingTasks); err != nil { if err := m.addOrUpdateTriggers(managerCtx, runnablePlans, pendingTasks); err != nil {
return fmt.Errorf("添加或更新触发器时出错: %w", err) return fmt.Errorf("添加或更新触发器时出错: %w", err)
} }
m.logger.Info("计划任务管理器同步完成.") logger.Info("计划任务管理器同步完成.")
return nil return nil
} }
// CreateOrUpdateTrigger 为给定的 planID 创建其关联的触发任务。 // 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() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
// 检查计划是否可执行 // 检查计划是否可执行
plan, err := m.planRepo.GetBasicPlanByID(planID) plan, err := m.planRepo.GetBasicPlanByID(managerCtx, planID)
if err != nil { if err != nil {
return fmt.Errorf("获取计划基本信息失败: %w", err) 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 { if err != nil {
return fmt.Errorf("查找现有触发器失败: %w", err) return fmt.Errorf("查找现有触发器失败: %w", err)
} }
@@ -109,7 +112,7 @@ func (m *analysisPlanTaskManagerImpl) CreateOrUpdateTrigger(planID uint) error {
// 自动计划,根据 Cron 表达式计算下一次执行时间 // 自动计划,根据 Cron 表达式计算下一次执行时间
next, err := utils.GetNextCronTime(plan.CronExpression) next, err := utils.GetNextCronTime(plan.CronExpression)
if err != nil { 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) return fmt.Errorf("解析 Cron 表达式失败: %w", err)
} }
expectedExecuteAt = next expectedExecuteAt = next
@@ -117,47 +120,48 @@ func (m *analysisPlanTaskManagerImpl) CreateOrUpdateTrigger(planID uint) error {
// 如果计算出的执行时间与当前待执行任务的时间不一致,则更新 // 如果计算出的执行时间与当前待执行任务的时间不一致,则更新
if !existingTrigger.ExecuteAt.Equal(expectedExecuteAt) { if !existingTrigger.ExecuteAt.Equal(expectedExecuteAt) {
m.logger.Infof("计划 #%d 的执行时间已变更,正在更新触发器 #%d 的执行时间从 %v 到 %v...", plan.ID, existingTrigger.ID, existingTrigger.ExecuteAt, expectedExecuteAt) logger.Infof("计划 #%d 的执行时间已变更,正在更新触发器 #%d 的执行时间从 %v 到 %v...", plan.ID, existingTrigger.ID, existingTrigger.ExecuteAt, expectedExecuteAt)
if err := m.pendingTaskRepo.UpdatePendingTaskExecuteAt(existingTrigger.ID, expectedExecuteAt); err != nil { if err := m.pendingTaskRepo.UpdatePendingTaskExecuteAt(managerCtx, existingTrigger.ID, expectedExecuteAt); err != nil {
m.logger.Errorf("更新触发器 #%d 的执行时间失败: %v", existingTrigger.ID, err) logger.Errorf("更新触发器 #%d 的执行时间失败: %v", existingTrigger.ID, err)
return fmt.Errorf("更新触发器执行时间失败: %w", err) return fmt.Errorf("更新触发器执行时间失败: %w", err)
} }
} else { } else {
m.logger.Infof("计划 #%d 的触发器已存在且执行时间无需更新。", plan.ID) logger.Infof("计划 #%d 的触发器已存在且执行时间无需更新。", plan.ID)
} }
return nil // 触发器已存在且已处理更新,直接返回 return nil // 触发器已存在且已处理更新,直接返回
} }
// 如果触发器不存在,则创建新的触发器 // 如果触发器不存在,则创建新的触发器
m.logger.Infof("为计划 #%d 创建新的触发器...", planID) logger.Infof("为计划 #%d 创建新的触发器...", planID)
return m.createTriggerTask(plan) return m.createTriggerTask(managerCtx, plan)
} }
// EnsureAnalysisTaskDefinition 确保计划的分析任务定义存在于 tasks 表中。 // 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() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
plan, err := m.planRepo.GetBasicPlanByID(planID) plan, err := m.planRepo.GetBasicPlanByID(managerCtx, planID)
if err != nil { if err != nil {
return fmt.Errorf("确保分析任务定义失败:获取计划 #%d 基本信息时出错: %w", planID, err) 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 { if err != nil {
return fmt.Errorf("确保分析任务定义失败:查找计划 #%d 的分析任务时出错: %w", plan.ID, err) return fmt.Errorf("确保分析任务定义失败:查找计划 #%d 的分析任务时出错: %w", plan.ID, err)
} }
if analysisTask == nil { if analysisTask == nil {
m.logger.Infof("未找到计划 #%d 关联的 'plan_analysis' 任务定义,将自动创建...", plan.ID) logger.Infof("未找到计划 #%d 关联的 'plan_analysis' 任务定义,将自动创建...", plan.ID)
_, err := m.planRepo.CreatePlanAnalysisTask(plan) // CreatePlanAnalysisTask returns *models.Task, error _, err := m.planRepo.CreatePlanAnalysisTask(managerCtx, plan) // CreatePlanAnalysisTask returns *models.Task, error
if err != nil { if err != nil {
return fmt.Errorf("自动创建 'plan_analysis' 任务定义失败: %w", err) return fmt.Errorf("自动创建 'plan_analysis' 任务定义失败: %w", err)
} }
m.logger.Infof("已成功为计划 #%d 创建 'plan_analysis' 任务定义。", plan.ID) logger.Infof("已成功为计划 #%d 创建 'plan_analysis' 任务定义。", plan.ID)
} else { } else {
m.logger.Infof("计划 #%d 的 'plan_analysis' 任务定义已存在。", plan.ID) logger.Infof("计划 #%d 的 'plan_analysis' 任务定义已存在。", plan.ID)
} }
return nil return nil
@@ -166,16 +170,17 @@ func (m *analysisPlanTaskManagerImpl) EnsureAnalysisTaskDefinition(planID uint)
// --- 内部私有方法 --- // --- 内部私有方法 ---
// getRefreshData 从数据库获取刷新所需的所有数据。 // getRefreshData 从数据库获取刷新所需的所有数据。
func (m *analysisPlanTaskManagerImpl) getRefreshData() (runnablePlans []*models.Plan, invalidPlanIDs []uint, pendingTasks []models.PendingTask, err error) { func (m *analysisPlanTaskManagerImpl) getRefreshData(ctx context.Context) (runnablePlans []*models.Plan, invalidPlanIDs []uint, pendingTasks []models.PendingTask, err error) {
runnablePlans, err = m.planRepo.FindRunnablePlans() managerCtx, logger := logs.Trace(ctx, m.ctx, "getRefreshData")
runnablePlans, err = m.planRepo.FindRunnablePlans(managerCtx)
if err != nil { if err != nil {
m.logger.Errorf("获取可执行计划列表失败: %v", err) logger.Errorf("获取可执行计划列表失败: %v", err)
return return
} }
invalidPlans, err := m.planRepo.FindInactivePlans() invalidPlans, err := m.planRepo.FindInactivePlans(managerCtx)
if err != nil { if err != nil {
m.logger.Errorf("获取失效计划列表失败: %v", err) logger.Errorf("获取失效计划列表失败: %v", err)
return return
} }
invalidPlanIDs = make([]uint, len(invalidPlans)) invalidPlanIDs = make([]uint, len(invalidPlans))
@@ -183,16 +188,17 @@ func (m *analysisPlanTaskManagerImpl) getRefreshData() (runnablePlans []*models.
invalidPlanIDs[i] = p.ID invalidPlanIDs[i] = p.ID
} }
pendingTasks, err = m.pendingTaskRepo.FindAllPendingTasks() pendingTasks, err = m.pendingTaskRepo.FindAllPendingTasks(managerCtx)
if err != nil { if err != nil {
m.logger.Errorf("获取所有待执行任务失败: %v", err) logger.Errorf("获取所有待执行任务失败: %v", err)
return return
} }
return return
} }
// cleanupInvalidTasks 清理所有与失效计划相关的待执行任务。 // 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 { if len(invalidPlanIDs) == 0 {
return nil // 没有需要清理的计划 return nil // 没有需要清理的计划
} }
@@ -219,24 +225,25 @@ func (m *analysisPlanTaskManagerImpl) cleanupInvalidTasks(invalidPlanIDs []uint,
return nil // 没有找到需要清理的任务 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) 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 return nil
} }
// addOrUpdateTriggers 检查、更新或创建触发器。 // 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) pendingTriggersMap := make(map[uint]models.PendingTask)
for _, pt := range allPendingTasks { for _, pt := range allPendingTasks {
@@ -254,22 +261,22 @@ func (m *analysisPlanTaskManagerImpl) addOrUpdateTriggers(runnablePlans []*model
if plan.ExecutionType == models.PlanExecutionTypeAutomatic { if plan.ExecutionType == models.PlanExecutionTypeAutomatic {
next, err := utils.GetNextCronTime(plan.CronExpression) next, err := utils.GetNextCronTime(plan.CronExpression)
if err != nil { if err != nil {
m.logger.Errorf("为计划 #%d 解析Cron表达式失败跳过更新: %v", plan.ID, err) logger.Errorf("为计划 #%d 解析Cron表达式失败跳过更新: %v", plan.ID, err)
continue continue
} }
// 如果数据库中记录的执行时间与根据当前Cron表达式计算出的下一次时间不一致则更新 // 如果数据库中记录的执行时间与根据当前Cron表达式计算出的下一次时间不一致则更新
if !existingTrigger.ExecuteAt.Equal(next) { if !existingTrigger.ExecuteAt.Equal(next) {
m.logger.Infof("计划 #%d 的执行时间已变更,正在更新触发器 #%d 的执行时间从 %v 到 %v...", plan.ID, existingTrigger.ID, existingTrigger.ExecuteAt, next) logger.Infof("计划 #%d 的执行时间已变更,正在更新触发器 #%d 的执行时间从 %v 到 %v...", plan.ID, existingTrigger.ID, existingTrigger.ExecuteAt, next)
if err := m.pendingTaskRepo.UpdatePendingTaskExecuteAt(existingTrigger.ID, next); err != nil { if err := m.pendingTaskRepo.UpdatePendingTaskExecuteAt(managerCtx, existingTrigger.ID, next); err != nil {
m.logger.Errorf("更新触发器 #%d 的执行时间失败: %v", existingTrigger.ID, err) logger.Errorf("更新触发器 #%d 的执行时间失败: %v", existingTrigger.ID, err)
} }
} }
} }
} else { } else {
// --- 原有逻辑:为缺失的计划创建新触发器 --- // --- 原有逻辑:为缺失的计划创建新触发器 ---
m.logger.Infof("发现应执行但队列中缺失的计划 #%d正在为其创建触发器...", plan.ID) logger.Infof("发现应执行但队列中缺失的计划 #%d正在为其创建触发器...", plan.ID)
if err := m.createTriggerTask(plan); err != nil { if err := m.createTriggerTask(managerCtx, plan); err != nil {
m.logger.Errorf("为计划 #%d 创建触发器失败: %v", plan.ID, err) logger.Errorf("为计划 #%d 创建触发器失败: %v", plan.ID, err)
// 继续处理下一个,不因单点失败而中断 // 继续处理下一个,不因单点失败而中断
} }
} }
@@ -278,21 +285,22 @@ func (m *analysisPlanTaskManagerImpl) addOrUpdateTriggers(runnablePlans []*model
} }
// createTriggerTask 是创建触发器任务的内部核心逻辑。 // createTriggerTask 是创建触发器任务的内部核心逻辑。
func (m *analysisPlanTaskManagerImpl) createTriggerTask(plan *models.Plan) error { func (m *analysisPlanTaskManagerImpl) createTriggerTask(ctx context.Context, plan *models.Plan) error {
analysisTask, err := m.planRepo.FindPlanAnalysisTaskByPlanID(plan.ID) managerCtx, logger := logs.Trace(ctx, m.ctx, "createTriggerTask")
analysisTask, err := m.planRepo.FindPlanAnalysisTaskByPlanID(managerCtx, plan.ID)
if err != nil { if err != nil {
return fmt.Errorf("查找计划分析任务失败: %w", err) return fmt.Errorf("查找计划分析任务失败: %w", err)
} }
// --- 如果触发器任务定义不存在,则自动创建 --- // --- 如果触发器任务定义不存在,则自动创建 ---
if analysisTask == nil { if analysisTask == nil {
m.logger.Warnf("未找到计划 #%d 关联的 'plan_analysis' 任务定义,将自动创建...", plan.ID) logger.Warnf("未找到计划 #%d 关联的 'plan_analysis' 任务定义,将自动创建...", plan.ID)
newAnalysisTask, err := m.planRepo.CreatePlanAnalysisTask(plan) newAnalysisTask, err := m.planRepo.CreatePlanAnalysisTask(managerCtx, plan)
if err != nil { if err != nil {
return fmt.Errorf("自动创建 'plan_analysis' 任务定义失败: %w", err) return fmt.Errorf("自动创建 'plan_analysis' 任务定义失败: %w", err)
} }
analysisTask = newAnalysisTask 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 var executeAt time.Time
@@ -310,7 +318,7 @@ func (m *analysisPlanTaskManagerImpl) createTriggerTask(plan *models.Plan) error
TaskID: analysisTask.ID, TaskID: analysisTask.ID,
Status: models.ExecutionStatusWaiting, 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) return fmt.Errorf("创建任务执行日志失败: %w", err)
} }
@@ -319,10 +327,10 @@ func (m *analysisPlanTaskManagerImpl) createTriggerTask(plan *models.Plan) error
ExecuteAt: executeAt, ExecuteAt: executeAt,
TaskExecutionLogID: taskLog.ID, 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) 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 return nil
} }

View File

@@ -1 +0,0 @@
package plan

View File

@@ -1,6 +1,7 @@
package plan package plan
import ( import (
"context"
"errors" "errors"
"sync" "sync"
"time" "time"
@@ -16,9 +17,9 @@ import (
// ExecutionManager 定义了计划执行管理器的接口。 // ExecutionManager 定义了计划执行管理器的接口。
type ExecutionManager interface { type ExecutionManager interface {
// Start 启动计划执行管理器。 // Start 启动计划执行管理器。
Start() Start(ctx context.Context)
// Stop 优雅地停止计划执行管理器。 // Stop 优雅地停止计划执行管理器。
Stop() Stop(ctx context.Context)
} }
// ProgressTracker 仅用于在内存中提供计划执行的并发锁 // ProgressTracker 仅用于在内存中提供计划执行的并发锁
@@ -83,7 +84,7 @@ func (t *ProgressTracker) GetRunningPlanIDs() []uint {
// planExecutionManagerImpl 是核心的、持久化的任务调度器 // planExecutionManagerImpl 是核心的、持久化的任务调度器
type planExecutionManagerImpl struct { type planExecutionManagerImpl struct {
logger *logs.Logger ctx context.Context
pollingInterval time.Duration pollingInterval time.Duration
workers int workers int
pendingTaskRepo repository.PendingTaskRepository pendingTaskRepo repository.PendingTaskRepository
@@ -103,6 +104,7 @@ type planExecutionManagerImpl struct {
// NewPlanExecutionManager 创建一个新的调度器实例 // NewPlanExecutionManager 创建一个新的调度器实例
func NewPlanExecutionManager( func NewPlanExecutionManager(
ctx context.Context,
pendingTaskRepo repository.PendingTaskRepository, pendingTaskRepo repository.PendingTaskRepository,
executionLogRepo repository.ExecutionLogRepository, executionLogRepo repository.ExecutionLogRepository,
deviceRepo repository.DeviceRepository, deviceRepo repository.DeviceRepository,
@@ -110,12 +112,12 @@ func NewPlanExecutionManager(
planRepo repository.PlanRepository, planRepo repository.PlanRepository,
analysisPlanTaskManager AnalysisPlanTaskManager, analysisPlanTaskManager AnalysisPlanTaskManager,
taskFactory TaskFactory, taskFactory TaskFactory,
logger *logs.Logger,
deviceService device.Service, deviceService device.Service,
interval time.Duration, interval time.Duration,
numWorkers int, numWorkers int,
) ExecutionManager { ) ExecutionManager {
return &planExecutionManagerImpl{ return &planExecutionManagerImpl{
ctx: ctx,
pendingTaskRepo: pendingTaskRepo, pendingTaskRepo: pendingTaskRepo,
executionLogRepo: executionLogRepo, executionLogRepo: executionLogRepo,
deviceRepo: deviceRepo, deviceRepo: deviceRepo,
@@ -123,7 +125,6 @@ func NewPlanExecutionManager(
planRepo: planRepo, planRepo: planRepo,
analysisPlanTaskManager: analysisPlanTaskManager, analysisPlanTaskManager: analysisPlanTaskManager,
taskFactory: taskFactory, taskFactory: taskFactory,
logger: logger,
deviceService: deviceService, deviceService: deviceService,
pollingInterval: interval, pollingInterval: interval,
workers: numWorkers, workers: numWorkers,
@@ -133,10 +134,11 @@ func NewPlanExecutionManager(
} }
// Start 启动调度器,包括初始化协程池和启动主轮询循环 // Start 启动调度器,包括初始化协程池和启动主轮询循环
func (s *planExecutionManagerImpl) Start() { func (s *planExecutionManagerImpl) Start(ctx context.Context) {
s.logger.Warnf("任务调度器正在启动,工作协程数: %d...", s.workers) managerCtx, logger := logs.Trace(ctx, s.ctx, "Start")
logger.Warnf("任务调度器正在启动,工作协程数: %d...", s.workers)
pool, err := ants.NewPool(s.workers, ants.WithPanicHandler(func(err interface{}) { 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 { if err != nil {
panic("初始化协程池失败: " + err.Error()) panic("初始化协程池失败: " + err.Error())
@@ -144,21 +146,23 @@ func (s *planExecutionManagerImpl) Start() {
s.pool = pool s.pool = pool
s.wg.Add(1) s.wg.Add(1)
go s.run() go s.run(managerCtx)
s.logger.Warnf("任务调度器已成功启动") logger.Warnf("任务调度器已成功启动")
} }
// Stop 优雅地停止调度器 // Stop 优雅地停止调度器
func (s *planExecutionManagerImpl) Stop() { func (s *planExecutionManagerImpl) Stop(ctx context.Context) {
s.logger.Warnf("正在停止任务调度器...") logger := logs.TraceLogger(ctx, s.ctx, "Stop")
logger.Warnf("正在停止任务调度器...")
close(s.stopChan) // 1. 发出停止信号,停止主循环 close(s.stopChan) // 1. 发出停止信号,停止主循环
s.wg.Wait() // 2. 等待主循环完成 s.wg.Wait() // 2. 等待主循环完成
s.pool.Release() // 3. 释放 ants 池 (等待所有已提交的任务执行完毕) s.pool.Release() // 3. 释放 ants 池 (等待所有已提交的任务执行完毕)
s.logger.Warnf("任务调度器已安全停止") logger.Warnf("任务调度器已安全停止")
} }
// run 是主轮询循环,负责从数据库认领任务并提交到协程池 // run 是主轮询循环,负责从数据库认领任务并提交到协程池
func (s *planExecutionManagerImpl) run() { func (s *planExecutionManagerImpl) run(ctx context.Context) {
managerCtx := logs.AddFuncName(ctx, s.ctx, "run")
defer s.wg.Done() defer s.wg.Done()
ticker := time.NewTicker(s.pollingInterval) ticker := time.NewTicker(s.pollingInterval)
defer ticker.Stop() defer ticker.Stop()
@@ -170,19 +174,20 @@ func (s *planExecutionManagerImpl) run() {
return return
case <-ticker.C: case <-ticker.C:
// 定时触发任务认领和提交 // 定时触发任务认领和提交
go s.claimAndSubmit() go s.claimAndSubmit(managerCtx)
} }
} }
} }
// claimAndSubmit 实现了最终的“认领-锁定-执行 或 等待-放回”的健壮逻辑 // claimAndSubmit 实现了最终的“认领-锁定-执行 或 等待-放回”的健壮逻辑
func (s *planExecutionManagerImpl) claimAndSubmit() { func (s *planExecutionManagerImpl) claimAndSubmit(ctx context.Context) {
managerCtx, logger := logs.Trace(ctx, s.ctx, "claimAndSubmit")
runningPlanIDs := s.progressTracker.GetRunningPlanIDs() runningPlanIDs := s.progressTracker.GetRunningPlanIDs()
claimedLog, pendingTask, err := s.pendingTaskRepo.ClaimNextAvailableTask(runningPlanIDs) claimedLog, pendingTask, err := s.pendingTaskRepo.ClaimNextAvailableTask(managerCtx, runningPlanIDs)
if err != nil { if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) { if !errors.Is(err, gorm.ErrRecordNotFound) {
s.logger.Errorf("认领任务时发生错误: %v", err) logger.Errorf("认领任务时发生错误: %v", err)
} }
// gorm.ErrRecordNotFound 说明没任务要执行 // gorm.ErrRecordNotFound 说明没任务要执行
return return
@@ -193,100 +198,103 @@ func (s *planExecutionManagerImpl) claimAndSubmit() {
// 成功获取锁,正常派发任务 // 成功获取锁,正常派发任务
err = s.pool.Submit(func() { err = s.pool.Submit(func() {
defer s.progressTracker.Unlock(claimedLog.PlanExecutionLogID) defer s.progressTracker.Unlock(claimedLog.PlanExecutionLogID)
s.processTask(claimedLog) s.processTask(managerCtx, claimedLog)
}) })
if err != nil { if err != nil {
s.logger.Errorf("向协程池提交任务失败: %v", err) logger.Errorf("向协程池提交任务失败: %v", err)
// 提交失败,必须释放刚刚获取的锁 // 提交失败,必须释放刚刚获取的锁
s.progressTracker.Unlock(claimedLog.PlanExecutionLogID) s.progressTracker.Unlock(claimedLog.PlanExecutionLogID)
// 同样需要将任务安全放回 // 同样需要将任务安全放回
s.handleRequeue(claimedLog.PlanExecutionLogID, pendingTask) s.handleRequeue(managerCtx, claimedLog.PlanExecutionLogID, pendingTask)
} }
} else { } else {
// 获取锁失败,说明有“兄弟”任务正在执行。执行“锁定并安全放回”逻辑。 // 获取锁失败,说明有“兄弟”任务正在执行。执行“锁定并安全放回”逻辑。
s.handleRequeue(claimedLog.PlanExecutionLogID, pendingTask) s.handleRequeue(managerCtx, claimedLog.PlanExecutionLogID, pendingTask)
} }
} }
// handleRequeue 同步地、安全地将一个无法立即执行的任务放回队列。 // handleRequeue 同步地、安全地将一个无法立即执行的任务放回队列。
func (s *planExecutionManagerImpl) handleRequeue(planExecutionLogID uint, taskToRequeue *models.PendingTask) { func (s *planExecutionManagerImpl) handleRequeue(ctx context.Context, planExecutionLogID uint, taskToRequeue *models.PendingTask) {
s.logger.Warnf("计划 %d 正在执行,任务 %d (TaskID: %d) 将等待并重新入队...", planExecutionLogID, taskToRequeue.ID, taskToRequeue.TaskID) managerCtx, logger := logs.Trace(ctx, s.ctx, "handleRequeue")
logger.Warnf("计划 %d 正在执行,任务 %d (TaskID: %d) 将等待并重新入队...", planExecutionLogID, taskToRequeue.ID, taskToRequeue.TaskID)
// 1. 阻塞式地等待,直到可以获取到该计划的锁。 // 1. 阻塞式地等待,直到可以获取到该计划的锁。
s.progressTracker.Lock(planExecutionLogID) s.progressTracker.Lock(planExecutionLogID)
defer s.progressTracker.Unlock(planExecutionLogID) defer s.progressTracker.Unlock(planExecutionLogID)
// 2. 在持有锁的情况下,将任务安全地放回队列。 // 2. 在持有锁的情况下,将任务安全地放回队列。
if err := s.pendingTaskRepo.RequeueTask(taskToRequeue); err != nil { if err := s.pendingTaskRepo.RequeueTask(managerCtx, taskToRequeue); err != nil {
s.logger.Errorf("[严重] 任务重新入队失败, 原始PendingTaskID: %d, 错误: %v", taskToRequeue.ID, err) logger.Errorf("[严重] 任务重新入队失败, 原始PendingTaskID: %d, 错误: %v", taskToRequeue.ID, err)
return return
} }
s.logger.Warnf("任务 (原始ID: %d) 已成功重新入队,并已释放计划 %d 的锁。", taskToRequeue.ID, planExecutionLogID) logger.Warnf("任务 (原始ID: %d) 已成功重新入队,并已释放计划 %d 的锁。", taskToRequeue.ID, planExecutionLogID)
} }
// processTask 处理单个任务的逻辑 // processTask 处理单个任务的逻辑
func (s *planExecutionManagerImpl) processTask(claimedLog *models.TaskExecutionLog) { func (s *planExecutionManagerImpl) processTask(ctx context.Context, claimedLog *models.TaskExecutionLog) {
s.logger.Warnf("开始处理任务, 日志ID: %d, 任务ID: %d, 任务名称: %s, 描述: %s", 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.ID, claimedLog.TaskID, claimedLog.Task.Name, claimedLog.Task.Description)
claimedLog.StartedAt = time.Now() claimedLog.StartedAt = time.Now()
claimedLog.Status = models.ExecutionStatusCompleted // 先乐观假定任务成功, 后续失败了再改 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 { if err != nil {
claimedLog.Status = models.ExecutionStatusFailed claimedLog.Status = models.ExecutionStatusFailed
claimedLog.Output = err.Error() claimedLog.Output = err.Error()
// 任务失败时,调用统一的终止服务 // 任务失败时,调用统一的终止服务
s.handlePlanTermination(claimedLog.PlanExecutionLogID, "子任务执行失败: "+err.Error()) s.handlePlanTermination(managerCtx, claimedLog.PlanExecutionLogID, "子任务执行失败: "+err.Error())
return return
} }
// 如果是计划分析任务,它的职责是解析和分发任务,到此即完成,不参与后续的计划完成度检查。 // 如果是计划分析任务,它的职责是解析和分发任务,到此即完成,不参与后续的计划完成度检查。
if claimedLog.Task.Type == models.TaskPlanAnalysis { if claimedLog.Task.Type == models.TaskPlanAnalysis {
s.logger.Warnf("完成计划分析任务, 日志ID: %d", claimedLog.ID) logger.Warnf("完成计划分析任务, 日志ID: %d", claimedLog.ID)
return 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 { if err != nil {
s.logger.Errorf("检查计划 %d 的未完成任务数时出错: %v", claimedLog.PlanExecutionLogID, err) logger.Errorf("检查计划 %d 的未完成任务数时出错: %v", claimedLog.PlanExecutionLogID, err)
return return
} }
// 如果此计划执行中,未完成的任务只剩下当前这一个(因为当前任务的状态此时在数据库中仍为 'started' // 如果此计划执行中,未完成的任务只剩下当前这一个(因为当前任务的状态此时在数据库中仍为 'started'
// 则认为整个计划已完成。 // 则认为整个计划已完成。
if incompleteCount == 1 { if incompleteCount == 1 {
s.handlePlanCompletion(claimedLog.PlanExecutionLogID) s.handlePlanCompletion(managerCtx, claimedLog.PlanExecutionLogID)
} }
} }
// runTask 用于执行具体任务 // 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并将解析出的任务队列添加到待执行队列中 // 这是个特殊任务, 用于解析Plan并将解析出的任务队列添加到待执行队列中
if claimedLog.Task.Type == models.TaskPlanAnalysis { if claimedLog.Task.Type == models.TaskPlanAnalysis {
// 解析plan // 解析plan
err := s.analysisPlan(claimedLog) err := s.analysisPlan(managerCtx, claimedLog)
if err != nil { if err != nil {
// TODO 这里要处理一下, 比如再插一个新的触发器回去 // TODO 这里要处理一下, 比如再插一个新的触发器回去
s.logger.Errorf("[严重] 计划解析失败, 日志ID: %d, 错误: %v", claimedLog.ID, err) logger.Errorf("[严重] 计划解析失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
return err return err
} }
} else { } else {
// 执行普通任务 // 执行普通任务
task := s.taskFactory.Production(claimedLog) task := s.taskFactory.Production(managerCtx, claimedLog)
if err := task.Execute(); err != nil { if err := task.Execute(managerCtx); err != nil {
s.logger.Errorf("[严重] 任务执行失败, 日志ID: %d, 错误: %v", claimedLog.ID, err) logger.Errorf("[严重] 任务执行失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
task.OnFailure(err) task.OnFailure(managerCtx, err)
return err return err
} }
@@ -295,14 +303,15 @@ func (s *planExecutionManagerImpl) runTask(claimedLog *models.TaskExecutionLog)
} }
// analysisPlan 解析Plan并将解析出的Task列表插入待执行队列中 // 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执行记录 // 创建Plan执行记录
// 从任务的 Parameters 中解析出真实的 PlanID // 从任务的 Parameters 中解析出真实的 PlanID
var params struct { var params struct {
PlanID uint `json:"plan_id"` PlanID uint `json:"plan_id"`
} }
if err := claimedLog.Task.ParseParameters(&params); err != nil { if err := claimedLog.Task.ParseParameters(&params); err != nil {
s.logger.Errorf("解析任务参数中的计划ID失败日志ID: %d, 错误: %v", claimedLog.ID, err) logger.Errorf("解析任务参数中的计划ID失败日志ID: %d, 错误: %v", claimedLog.ID, err)
return err return err
} }
realPlanID := params.PlanID realPlanID := params.PlanID
@@ -312,15 +321,15 @@ func (s *planExecutionManagerImpl) analysisPlan(claimedLog *models.TaskExecution
Status: models.ExecutionStatusStarted, Status: models.ExecutionStatusStarted,
StartedAt: time.Now(), StartedAt: time.Now(),
} }
if err := s.executionLogRepo.CreatePlanExecutionLog(planLog); err != nil { if err := s.executionLogRepo.CreatePlanExecutionLog(managerCtx, planLog); err != nil {
s.logger.Errorf("[严重] 创建计划执行日志失败, 日志ID: %d, 错误: %v", claimedLog.ID, err) logger.Errorf("[严重] 创建计划执行日志失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
return err return err
} }
// 解析出Task列表 // 解析出Task列表
tasks, err := s.planRepo.FlattenPlanTasks(realPlanID) tasks, err := s.planRepo.FlattenPlanTasks(managerCtx, realPlanID)
if err != nil { if err != nil {
s.logger.Errorf("[严重] 解析计划失败, 日志ID: %d, 错误: %v", claimedLog.ID, err) logger.Errorf("[严重] 解析计划失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
return 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 { if err != nil {
s.logger.Errorf("[严重] 写入执行历史, 日志ID: %d, 错误: %v", claimedLog.ID, err) logger.Errorf("[严重] 写入执行历史, 日志ID: %d, 错误: %v", claimedLog.ID, err)
return err return err
} }
@@ -351,9 +360,9 @@ func (s *planExecutionManagerImpl) analysisPlan(claimedLog *models.TaskExecution
ExecuteAt: time.Now().Add(time.Duration(i) * time.Second), ExecuteAt: time.Now().Add(time.Duration(i) * time.Second),
} }
} }
err = s.pendingTaskRepo.CreatePendingTasksInBatch(pendingTasks) err = s.pendingTaskRepo.CreatePendingTasksInBatch(managerCtx, pendingTasks)
if err != nil { if err != nil {
s.logger.Errorf("[严重] 写入待执行队列, 日志ID: %d, 错误: %v", claimedLog.ID, err) logger.Errorf("[严重] 写入待执行队列, 日志ID: %d, 错误: %v", claimedLog.ID, err)
return err return err
} }
@@ -361,18 +370,19 @@ func (s *planExecutionManagerImpl) analysisPlan(claimedLog *models.TaskExecution
// 如果一个计划被解析后,发现其任务列表为空, // 如果一个计划被解析后,发现其任务列表为空,
// 那么它实际上已经“执行”完毕了,我们需要在这里手动为它创建下一次的触发器。 // 那么它实际上已经“执行”完毕了,我们需要在这里手动为它创建下一次的触发器。
if len(tasks) == 0 { if len(tasks) == 0 {
s.handlePlanCompletion(planLog.ID) s.handlePlanCompletion(managerCtx, planLog.ID)
} }
return nil return nil
} }
// updateTaskExecutionLogStatus 修改任务历史中的执行状态 // 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() claimedLog.EndedAt = time.Now()
if err := s.executionLogRepo.UpdateTaskExecutionLog(claimedLog); err != nil { if err := s.executionLogRepo.UpdateTaskExecutionLog(managerCtx, claimedLog); err != nil {
s.logger.Errorf("[严重] 更新任务执行日志失败, 日志ID: %d, 错误: %v", claimedLog.ID, err) logger.Errorf("[严重] 更新任务执行日志失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
return err return err
} }
@@ -380,64 +390,66 @@ func (s *planExecutionManagerImpl) updateTaskExecutionLogStatus(claimedLog *mode
} }
// handlePlanTermination 集中处理计划的终止逻辑(失败或取消) // 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. 从待执行队列中删除所有相关的子任务 // 1. 从待执行队列中删除所有相关的子任务
if err := s.pendingTaskRepo.DeletePendingTasksByPlanLogID(planLogID); err != nil { if err := s.pendingTaskRepo.DeletePendingTasksByPlanLogID(managerCtx, planLogID); err != nil {
s.logger.Errorf("从待执行队列中删除计划 %d 的后续任务时出错: %v", planLogID, err) logger.Errorf("从待执行队列中删除计划 %d 的后续任务时出错: %v", planLogID, err)
} }
// 2. 将父计划的执行日志标记为失败 // 2. 将父计划的执行日志标记为失败
if err := s.executionLogRepo.FailPlanExecution(planLogID, reason); err != nil { if err := s.executionLogRepo.FailPlanExecution(managerCtx, planLogID, reason); err != nil {
s.logger.Errorf("标记计划执行日志 %d 为失败时出错: %v", planLogID, err) logger.Errorf("标记计划执行日志 %d 为失败时出错: %v", planLogID, err)
} }
// 3. 将所有未完成的子任务日志标记为已取消 // 3. 将所有未完成的子任务日志标记为已取消
if err := s.executionLogRepo.CancelIncompleteTasksByPlanLogID(planLogID, "父计划失败或被取消"); err != nil { if err := s.executionLogRepo.CancelIncompleteTasksByPlanLogID(managerCtx, planLogID, "父计划失败或被取消"); err != nil {
s.logger.Errorf("取消计划 %d 的后续任务日志时出错: %v", planLogID, err) logger.Errorf("取消计划 %d 的后续任务日志时出错: %v", planLogID, err)
} }
// 4. 获取计划执行日志以获取顶层 PlanID // 4. 获取计划执行日志以获取顶层 PlanID
planLog, err := s.executionLogRepo.FindPlanExecutionLogByID(planLogID) planLog, err := s.executionLogRepo.FindPlanExecutionLogByID(managerCtx, planLogID)
if err != nil { if err != nil {
s.logger.Errorf("无法找到计划执行日志 %d 以更新父计划状态: %v", planLogID, err) logger.Errorf("无法找到计划执行日志 %d 以更新父计划状态: %v", planLogID, err)
return return
} }
// 5. 获取顶层计划的详细信息,以检查其类型 // 5. 获取顶层计划的详细信息,以检查其类型
topLevelPlan, err := s.planRepo.GetBasicPlanByID(planLog.PlanID) topLevelPlan, err := s.planRepo.GetBasicPlanByID(managerCtx, planLog.PlanID)
if err != nil { if err != nil {
s.logger.Errorf("获取顶层计划 %d 的基本信息失败: %v", planLog.PlanID, err) logger.Errorf("获取顶层计划 %d 的基本信息失败: %v", planLog.PlanID, err)
return return
} }
// 6. 如果是系统任务,则不修改计划状态 // 6. 如果是系统任务,则不修改计划状态
if topLevelPlan.PlanType == models.PlanTypeSystem { if topLevelPlan.PlanType == models.PlanTypeSystem {
s.logger.Warnf("系统任务 %d (日志ID: %d) 执行失败,但根据策略不修改其计划状态。", topLevelPlan.ID, planLogID) logger.Warnf("系统任务 %d (日志ID: %d) 执行失败,但根据策略不修改其计划状态。", topLevelPlan.ID, planLogID)
return return
} }
// 7. 将计划本身的状态更新为失败 (仅对非系统任务执行) // 7. 将计划本身的状态更新为失败 (仅对非系统任务执行)
if err := s.planRepo.UpdatePlanStatus(planLog.PlanID, models.PlanStatusFailed); err != nil { if err := s.planRepo.UpdatePlanStatus(managerCtx, planLog.PlanID, models.PlanStatusFailed); err != nil {
s.logger.Errorf("更新计划 %d 状态为 '失败' 时出错: %v", planLog.PlanID, err) logger.Errorf("更新计划 %d 状态为 '失败' 时出错: %v", planLog.PlanID, err)
} }
} }
// handlePlanCompletion 集中处理计划成功完成后的所有逻辑 // handlePlanCompletion 集中处理计划成功完成后的所有逻辑
func (s *planExecutionManagerImpl) handlePlanCompletion(planLogID uint) { func (s *planExecutionManagerImpl) handlePlanCompletion(ctx context.Context, planLogID uint) {
s.logger.Infof("计划执行 %d 的所有任务已完成,开始处理计划完成逻辑...", planLogID) managerCtx, logger := logs.Trace(ctx, s.ctx, "handlePlanCompletion")
logger.Infof("计划执行 %d 的所有任务已完成,开始处理计划完成逻辑...", planLogID)
// 1. 通过 PlanExecutionLog 反查正确的顶层 PlanID // 1. 通过 PlanExecutionLog 反查正确的顶层 PlanID
planExecutionLog, err := s.executionLogRepo.FindPlanExecutionLogByID(planLogID) planExecutionLog, err := s.executionLogRepo.FindPlanExecutionLogByID(managerCtx, planLogID)
if err != nil { if err != nil {
s.logger.Errorf("获取计划执行日志 %d 失败: %v", planLogID, err) logger.Errorf("获取计划执行日志 %d 失败: %v", planLogID, err)
return return
} }
topLevelPlanID := planExecutionLog.PlanID // 这才是正确的顶层计划ID topLevelPlanID := planExecutionLog.PlanID // 这才是正确的顶层计划ID
// 2. 获取计划的最新数据,这里我们只需要基本信息来判断执行类型和次数 // 2. 获取计划的最新数据,这里我们只需要基本信息来判断执行类型和次数
plan, err := s.planRepo.GetBasicPlanByID(topLevelPlanID) plan, err := s.planRepo.GetBasicPlanByID(managerCtx, topLevelPlanID)
if err != nil { if err != nil {
s.logger.Errorf("获取计划 %d 的基本信息失败: %v", topLevelPlanID, err) logger.Errorf("获取计划 %d 的基本信息失败: %v", topLevelPlanID, err)
return 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 { if (plan.ExecutionType == models.PlanExecutionTypeAutomatic && plan.ExecuteNum > 0 && newExecuteCount >= plan.ExecuteNum) || plan.ExecutionType == models.PlanExecutionTypeManual {
newStatus = models.PlanStatusStopped newStatus = models.PlanStatusStopped
s.logger.Infof("计划 %d 已完成执行,状态更新为 '执行完毕'。", topLevelPlanID) logger.Infof("计划 %d 已完成执行,状态更新为 '执行完毕'。", topLevelPlanID)
} }
// 4. 使用专门的方法来原子性地更新计数值和状态 // 4. 使用专门的方法来原子性地更新计数值和状态
if err := s.planRepo.UpdatePlanStateAfterExecution(topLevelPlanID, newExecuteCount, newStatus); err != nil { if err := s.planRepo.UpdatePlanStateAfterExecution(managerCtx, topLevelPlanID, newExecuteCount, newStatus); err != nil {
s.logger.Errorf("更新计划 %d 的执行后状态失败: %v", topLevelPlanID, err) logger.Errorf("更新计划 %d 的执行后状态失败: %v", topLevelPlanID, err)
return return
} }
// 5. 更新计划执行日志状态为完成 // 5. 更新计划执行日志状态为完成
if err := s.executionLogRepo.UpdatePlanExecutionLogStatus(planLogID, models.ExecutionStatusCompleted); err != nil { if err := s.executionLogRepo.UpdatePlanExecutionLogStatus(managerCtx, planLogID, models.ExecutionStatusCompleted); err != nil {
s.logger.Errorf("更新计划执行日志 %d 状态为 '完成' 失败: %v", planLogID, err) logger.Errorf("更新计划执行日志 %d 状态为 '完成' 失败: %v", planLogID, err)
} }
// 6. 调用共享的 Manager 来处理触发器更新逻辑 // 6. 调用共享的 Manager 来处理触发器更新逻辑
// 只有当计划在本次执行后仍然是 Enabled 状态时,才需要创建下一次的触发器。 // 只有当计划在本次执行后仍然是 Enabled 状态时,才需要创建下一次的触发器。
if newStatus == models.PlanStatusEnabled { if newStatus == models.PlanStatusEnabled {
if err := s.analysisPlanTaskManager.CreateOrUpdateTrigger(topLevelPlanID); err != nil { if err := s.analysisPlanTaskManager.CreateOrUpdateTrigger(managerCtx, topLevelPlanID); err != nil {
s.logger.Errorf("为计划 %d 创建/更新触发器失败: %v", topLevelPlanID, err) logger.Errorf("为计划 %d 创建/更新触发器失败: %v", topLevelPlanID, err)
} }
} else { } else {
s.logger.Infof("计划 %d 状态为 '%d',无需创建下一次触发器。", topLevelPlanID, newStatus) logger.Infof("计划 %d 状态为 '%d',无需创建下一次触发器。", topLevelPlanID, newStatus)
} }
} }

View File

@@ -1,12 +1,14 @@
package plan package plan
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -30,80 +32,84 @@ var (
// Service 定义了计划领域服务的接口。 // Service 定义了计划领域服务的接口。
type Service interface { type Service interface {
// Start 启动计划相关的后台服务,例如计划执行管理器。 // Start 启动计划相关的后台服务,例如计划执行管理器。
Start() Start(ctx context.Context)
// Stop 停止计划相关的后台服务,例如计划执行管理器。 // Stop 停止计划相关的后台服务,例如计划执行管理器。
Stop() Stop(ctx context.Context)
// RefreshPlanTriggers 刷新计划触发器,同步数据库中的计划状态和待执行队列中的触发器任务。 // RefreshPlanTriggers 刷新计划触发器,同步数据库中的计划状态和待执行队列中的触发器任务。
RefreshPlanTriggers() error RefreshPlanTriggers(ctx context.Context) error
// CreatePlan 创建一个新的计划 // CreatePlan 创建一个新的计划
CreatePlan(plan *models.Plan) (*models.Plan, error) CreatePlan(ctx context.Context, plan *models.Plan) (*models.Plan, error)
// GetPlanByID 根据ID获取计划详情 // GetPlanByID 根据ID获取计划详情
GetPlanByID(id uint) (*models.Plan, error) GetPlanByID(ctx context.Context, id uint) (*models.Plan, error)
// ListPlans 获取计划列表,支持过滤和分页 // 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 更新计划
UpdatePlan(plan *models.Plan) (*models.Plan, error) UpdatePlan(ctx context.Context, plan *models.Plan) (*models.Plan, error)
// DeletePlan 删除计划(软删除) // DeletePlan 删除计划(软删除)
DeletePlan(id uint) error DeletePlan(ctx context.Context, id uint) error
// StartPlan 启动计划 // StartPlan 启动计划
StartPlan(id uint) error StartPlan(ctx context.Context, id uint) error
// StopPlan 停止计划 // StopPlan 停止计划
StopPlan(id uint) error StopPlan(ctx context.Context, id uint) error
} }
// planServiceImpl 是 Service 接口的具体实现。 // planServiceImpl 是 Service 接口的具体实现。
type planServiceImpl struct { type planServiceImpl struct {
ctx context.Context
executionManager ExecutionManager executionManager ExecutionManager
taskManager AnalysisPlanTaskManager taskManager AnalysisPlanTaskManager
planRepo repository.PlanRepository planRepo repository.PlanRepository
deviceRepo repository.DeviceRepository deviceRepo repository.DeviceRepository
unitOfWork repository.UnitOfWork unitOfWork repository.UnitOfWork
taskFactory TaskFactory taskFactory TaskFactory
logger *logs.Logger
} }
// NewPlanService 创建一个新的 Service 实例。 // NewPlanService 创建一个新的 Service 实例。
func NewPlanService( func NewPlanService(
ctx context.Context,
executionManager ExecutionManager, executionManager ExecutionManager,
taskManager AnalysisPlanTaskManager, taskManager AnalysisPlanTaskManager,
planRepo repository.PlanRepository, planRepo repository.PlanRepository,
deviceRepo repository.DeviceRepository, deviceRepo repository.DeviceRepository,
unitOfWork repository.UnitOfWork, unitOfWork repository.UnitOfWork,
taskFactory TaskFactory, taskFactory TaskFactory,
logger *logs.Logger,
) Service { ) Service {
return &planServiceImpl{ return &planServiceImpl{
ctx: ctx,
executionManager: executionManager, executionManager: executionManager,
taskManager: taskManager, taskManager: taskManager,
planRepo: planRepo, planRepo: planRepo,
deviceRepo: deviceRepo, deviceRepo: deviceRepo,
unitOfWork: unitOfWork, unitOfWork: unitOfWork,
taskFactory: taskFactory, taskFactory: taskFactory,
logger: logger,
} }
} }
// Start 启动计划相关的后台服务。 // Start 启动计划相关的后台服务。
func (s *planServiceImpl) Start() { func (s *planServiceImpl) Start(ctx context.Context) {
s.logger.Infof("PlanService 正在启动...") planCtx, logger := logs.Trace(ctx, s.ctx, "Start")
s.executionManager.Start() logger.Infof("PlanService 正在启动...")
s.executionManager.Start(planCtx)
} }
// Stop 停止计划相关的后台服务。 // Stop 停止计划相关的后台服务。
func (s *planServiceImpl) Stop() { func (s *planServiceImpl) Stop(ctx context.Context) {
s.logger.Infof("PlanService 正在停止...") planCtx, logger := logs.Trace(ctx, s.ctx, "Stop")
s.executionManager.Stop() logger.Infof("PlanService 正在停止...")
s.executionManager.Stop(planCtx)
} }
// RefreshPlanTriggers 刷新计划触发器。 // RefreshPlanTriggers 刷新计划触发器。
func (s *planServiceImpl) RefreshPlanTriggers() error { func (s *planServiceImpl) RefreshPlanTriggers(ctx context.Context) error {
s.logger.Infof("PlanService 正在刷新计划触发器...") planCtx, logger := logs.Trace(ctx, s.ctx, "RefreshPlanTriggers")
return s.taskManager.Refresh() logger.Infof("PlanService 正在刷新计划触发器...")
return s.taskManager.Refresh(planCtx)
} }
// CreatePlan 创建一个新的计划 // 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 = "领域层:创建计划" const actionType = "领域层:创建计划"
// 1. 业务规则处理 // 1. 业务规则处理
@@ -119,7 +125,7 @@ func (s *planServiceImpl) CreatePlan(planToCreate *models.Plan) (*models.Plan, e
// 2. 验证和重排顺序 (领域逻辑) // 2. 验证和重排顺序 (领域逻辑)
if err := planToCreate.ValidateExecutionOrder(); err != nil { 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 return nil, err
} }
planToCreate.ReorderSteps() planToCreate.ReorderSteps()
@@ -128,14 +134,14 @@ func (s *planServiceImpl) CreatePlan(planToCreate *models.Plan) (*models.Plan, e
for i := range planToCreate.Tasks { for i := range planToCreate.Tasks {
taskModel := &planToCreate.Tasks[i] taskModel := &planToCreate.Tasks[i]
// 使用工厂创建临时领域对象 // 使用工厂创建临时领域对象
taskResolver, err := s.taskFactory.CreateTaskFromModel(taskModel) taskResolver, err := s.taskFactory.CreateTaskFromModel(planCtx, taskModel)
if err != nil { if err != nil {
// 如果一个任务类型不支持,我们可以选择跳过或报错 // 如果一个任务类型不支持,我们可以选择跳过或报错
s.logger.Warnf("跳过为任务类型 '%s' 解析设备ID: %v", taskModel.Type, err) logger.Warnf("跳过为任务类型 '%s' 解析设备ID: %v", taskModel.Type, err)
continue continue
} }
deviceIDs, err := taskResolver.ResolveDeviceIDs() deviceIDs, err := taskResolver.ResolveDeviceIDs(planCtx)
if err != nil { if err != nil {
// 在事务外解析失败,直接返回错误 // 在事务外解析失败,直接返回错误
return nil, fmt.Errorf("为任务 '%s' 提取设备ID失败: %w", taskModel.Name, err) 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. 调用仓库方法创建计划,该方法内部会处理事务 // 4. 调用仓库方法创建计划,该方法内部会处理事务
err := s.planRepo.CreatePlan(planToCreate) err := s.planRepo.CreatePlan(planCtx, planToCreate)
if err != nil { if err != nil {
s.logger.Errorf("%s: 数据库创建计划失败: %v", actionType, err) logger.Errorf("%s: 数据库创建计划失败: %v", actionType, err)
return nil, err return nil, err
} }
// 5. 创建成功后,调用 manager 确保触发器任务定义存在,但不立即加入待执行队列 // 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 return planToCreate, nil
} }
// GetPlanByID 根据ID获取计划详情 // 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 = "领域层:获取计划详情" const actionType = "领域层:获取计划详情"
plan, err := s.planRepo.GetPlanByID(id) plan, err := s.planRepo.GetPlanByID(planCtx, id)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
s.logger.Warnf("%s: 计划不存在, ID: %d", actionType, id) logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
return nil, ErrPlanNotFound 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 return nil, err
} }
s.logger.Infof("%s: 获取计划详情成功, ID: %d", actionType, id) logger.Infof("%s: 获取计划详情成功, ID: %d", actionType, id)
return plan, nil return plan, nil
} }
// ListPlans 获取计划列表,支持过滤和分页 // 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 = "领域层:获取计划列表" const actionType = "领域层:获取计划列表"
plans, total, err := s.planRepo.ListPlans(opts, page, pageSize) plans, total, err := s.planRepo.ListPlans(planCtx, opts, page, pageSize)
if err != nil { if err != nil {
s.logger.Errorf("%s: 数据库查询失败: %v", actionType, err) logger.Errorf("%s: 数据库查询失败: %v", actionType, err)
return nil, 0, err return nil, 0, err
} }
s.logger.Infof("%s: 获取计划列表成功, 数量: %d", actionType, len(plans)) logger.Infof("%s: 获取计划列表成功, 数量: %d", actionType, len(plans))
return plans, total, nil return plans, total, nil
} }
// UpdatePlan 更新计划 // 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 = "领域层:更新计划" const actionType = "领域层:更新计划"
existingPlan, err := s.planRepo.GetBasicPlanByID(planToUpdate.ID) existingPlan, err := s.planRepo.GetBasicPlanByID(planCtx, planToUpdate.ID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { 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 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 return nil, err
} }
// 系统计划不允许修改 // 系统计划不允许修改
if existingPlan.PlanType == models.PlanTypeSystem { 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 return nil, ErrPlanCannotBeModified
} }
@@ -228,25 +237,25 @@ func (s *planServiceImpl) UpdatePlan(planToUpdate *models.Plan) (*models.Plan, e
// 验证和重排顺序 (领域逻辑) // 验证和重排顺序 (领域逻辑)
if err := planToUpdate.ValidateExecutionOrder(); err != nil { 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 return nil, err
} }
planToUpdate.ReorderSteps() planToUpdate.ReorderSteps()
// 只要是更新任务,就重置执行计数器 // 只要是更新任务,就重置执行计数器
planToUpdate.ExecuteCount = 0 planToUpdate.ExecuteCount = 0
s.logger.Infof("计划 #%d 被更新,执行计数器已重置为 0。", planToUpdate.ID) logger.Infof("计划 #%d 被更新,执行计数器已重置为 0。", planToUpdate.ID)
// 在调用仓库前,准备好所有数据,包括设备关联 // 在调用仓库前,准备好所有数据,包括设备关联
for i := range planToUpdate.Tasks { for i := range planToUpdate.Tasks {
taskModel := &planToUpdate.Tasks[i] taskModel := &planToUpdate.Tasks[i]
taskResolver, err := s.taskFactory.CreateTaskFromModel(taskModel) taskResolver, err := s.taskFactory.CreateTaskFromModel(planCtx, taskModel)
if err != nil { if err != nil {
s.logger.Warnf("跳过为任务类型 '%s' 解析设备ID: %v", taskModel.Type, err) logger.Warnf("跳过为任务类型 '%s' 解析设备ID: %v", taskModel.Type, err)
continue continue
} }
deviceIDs, err := taskResolver.ResolveDeviceIDs() deviceIDs, err := taskResolver.ResolveDeviceIDs(planCtx)
if err != nil { if err != nil {
return nil, fmt.Errorf("为任务 '%s' 提取设备ID失败: %w", taskModel.Name, err) 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 { 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 return nil, err
} }
if err := s.taskManager.EnsureAnalysisTaskDefinition(planToUpdate.ID); err != nil { if err := s.taskManager.EnsureAnalysisTaskDefinition(planCtx, planToUpdate.ID); err != nil {
s.logger.Errorf("为更新后的计划 %d 确保触发器任务定义失败: %v", planToUpdate.ID, err) 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 { 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("获取更新后计划详情时发生内部错误") return nil, errors.New("获取更新后计划详情时发生内部错误")
} }
s.logger.Infof("%s: 计划更新成功, ID: %d", actionType, updatedPlan.ID) logger.Infof("%s: 计划更新成功, ID: %d", actionType, updatedPlan.ID)
return updatedPlan, nil return updatedPlan, nil
} }
// DeletePlan 删除计划(软删除) // 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 = "领域层:删除计划" const actionType = "领域层:删除计划"
plan, err := s.planRepo.GetBasicPlanByID(id) plan, err := s.planRepo.GetBasicPlanByID(planCtx, id)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
s.logger.Warnf("%s: 计划不存在, ID: %d", actionType, id) logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
return ErrPlanNotFound return ErrPlanNotFound
} }
s.logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id) logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id)
return err return err
} }
// 系统计划不允许删除 // 系统计划不允许删除
if plan.PlanType == models.PlanTypeSystem { if plan.PlanType == models.PlanTypeSystem {
s.logger.Warnf("%s: 尝试删除系统计划, ID: %d", actionType, id) logger.Warnf("%s: 尝试删除系统计划, ID: %d", actionType, id)
return ErrPlanCannotBeDeleted return ErrPlanCannotBeDeleted
} }
// 如果计划处于启用状态,先停止它 // 如果计划处于启用状态,先停止它
if plan.Status == models.PlanStatusEnabled { if plan.Status == models.PlanStatusEnabled {
if err := s.planRepo.StopPlanTransactionally(id); err != nil { if err := s.planRepo.StopPlanTransactionally(planCtx, id); err != nil {
s.logger.Errorf("%s: 停止计划失败: %v, ID: %d", actionType, err, id) logger.Errorf("%s: 停止计划失败: %v, ID: %d", actionType, err, id)
return err return err
} }
} }
if err := s.planRepo.DeletePlan(id); err != nil { if err := s.planRepo.DeletePlan(planCtx, id); err != nil {
s.logger.Errorf("%s: 数据库删除失败: %v, ID: %d", actionType, err, id) logger.Errorf("%s: 数据库删除失败: %v, ID: %d", actionType, err, id)
return err return err
} }
s.logger.Infof("%s: 计划删除成功, ID: %d", actionType, id) logger.Infof("%s: 计划删除成功, ID: %d", actionType, id)
return nil return nil
} }
// StartPlan 启动计划 // 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 = "领域层:启动计划" const actionType = "领域层:启动计划"
plan, err := s.planRepo.GetBasicPlanByID(id) plan, err := s.planRepo.GetBasicPlanByID(planCtx, id)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
s.logger.Warnf("%s: 计划不存在, ID: %d", actionType, id) logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
return ErrPlanNotFound return ErrPlanNotFound
} }
s.logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id) logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id)
return err return err
} }
// 系统计划不允许手动启动 // 系统计划不允许手动启动
if plan.PlanType == models.PlanTypeSystem { if plan.PlanType == models.PlanTypeSystem {
s.logger.Warnf("%s: 尝试手动启动系统计划, ID: %d", actionType, id) logger.Warnf("%s: 尝试手动启动系统计划, ID: %d", actionType, id)
return ErrPlanCannotBeStarted return ErrPlanCannotBeStarted
} }
// 计划已处于启动状态,无需重复操作 // 计划已处于启动状态,无需重复操作
if plan.Status == models.PlanStatusEnabled { if plan.Status == models.PlanStatusEnabled {
s.logger.Warnf("%s: 计划已处于启动状态,无需重复操作, ID: %d", actionType, id) logger.Warnf("%s: 计划已处于启动状态,无需重复操作, ID: %d", actionType, id)
return ErrPlanAlreadyEnabled return ErrPlanAlreadyEnabled
} }
@@ -347,63 +358,64 @@ func (s *planServiceImpl) StartPlan(id uint) error {
if plan.Status != models.PlanStatusEnabled { if plan.Status != models.PlanStatusEnabled {
// 如果执行计数器大于0重置为0 // 如果执行计数器大于0重置为0
if plan.ExecuteCount > 0 { if plan.ExecuteCount > 0 {
if err := s.planRepo.UpdateExecuteCount(plan.ID, 0); err != nil { if err := s.planRepo.UpdateExecuteCount(planCtx, plan.ID, 0); err != nil {
s.logger.Errorf("%s: 重置计划执行计数失败: %v, ID: %d", actionType, err, plan.ID) logger.Errorf("%s: 重置计划执行计数失败: %v, ID: %d", actionType, err, plan.ID)
return err 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 { if err := s.planRepo.UpdatePlanStatus(planCtx, plan.ID, models.PlanStatusEnabled); err != nil {
s.logger.Errorf("%s: 更新计划状态失败: %v, ID: %d", actionType, err, plan.ID) logger.Errorf("%s: 更新计划状态失败: %v, ID: %d", actionType, err, plan.ID)
return err return err
} }
s.logger.Infof("已成功更新计划 #%d 的状态为 '已启动'。", plan.ID) logger.Infof("已成功更新计划 #%d 的状态为 '已启动'。", plan.ID)
} }
// 创建或更新触发器 // 创建或更新触发器
if err := s.taskManager.CreateOrUpdateTrigger(plan.ID); err != nil { if err := s.taskManager.CreateOrUpdateTrigger(planCtx, plan.ID); err != nil {
s.logger.Errorf("%s: 创建或更新触发器失败: %v, ID: %d", actionType, err, plan.ID) logger.Errorf("%s: 创建或更新触发器失败: %v, ID: %d", actionType, err, plan.ID)
return err return err
} }
s.logger.Infof("%s: 计划已成功启动, ID: %d", actionType, id) logger.Infof("%s: 计划已成功启动, ID: %d", actionType, id)
return nil return nil
} }
// StopPlan 停止计划 // 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 = "领域层:停止计划" const actionType = "领域层:停止计划"
plan, err := s.planRepo.GetBasicPlanByID(id) plan, err := s.planRepo.GetBasicPlanByID(planCtx, id)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
s.logger.Warnf("%s: 计划不存在, ID: %d", actionType, id) logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
return ErrPlanNotFound return ErrPlanNotFound
} }
s.logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id) logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id)
return err return err
} }
// 系统计划不允许停止 // 系统计划不允许停止
if plan.PlanType == models.PlanTypeSystem { if plan.PlanType == models.PlanTypeSystem {
s.logger.Warnf("%s: 尝试停止系统计划, ID: %d", actionType, id) logger.Warnf("%s: 尝试停止系统计划, ID: %d", actionType, id)
return ErrPlanCannotBeStopped return ErrPlanCannotBeStopped
} }
// 计划当前不是启用状态 // 计划当前不是启用状态
if plan.Status != models.PlanStatusEnabled { 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 return ErrPlanNotEnabled
} }
// 停止计划事务性操作 // 停止计划事务性操作
if err := s.planRepo.StopPlanTransactionally(id); err != nil { if err := s.planRepo.StopPlanTransactionally(planCtx, id); err != nil {
s.logger.Errorf("%s: 停止计划失败: %v, ID: %d", actionType, err, id) logger.Errorf("%s: 停止计划失败: %v, ID: %d", actionType, err, id)
return err return err
} }
s.logger.Infof("%s: 计划已成功停止, ID: %d", actionType, id) logger.Infof("%s: 计划已成功停止, ID: %d", actionType, id)
return nil return nil
} }

View File

@@ -1,6 +1,10 @@
package plan 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 定义了所有可被调度器执行的任务必须实现的接口。 // Task 定义了所有可被调度器执行的任务必须实现的接口。
type Task interface { type Task interface {
@@ -8,12 +12,12 @@ type Task interface {
// ctx: 用于控制任务的超时或取消。 // ctx: 用于控制任务的超时或取消。
// log: 包含了当前任务执行的完整上下文信息,包括从数据库中加载的任务参数等。 // log: 包含了当前任务执行的完整上下文信息,包括从数据库中加载的任务参数等。
// 返回的 error 表示任务是否执行成功。调度器会根据返回的 error 是否为 nil 来决定任务状态。 // 返回的 error 表示任务是否执行成功。调度器会根据返回的 error 是否为 nil 来决定任务状态。
Execute() error Execute(ctx context.Context) error
// OnFailure 定义了当 Execute 方法返回错误时,需要执行的回滚或清理逻辑。 // OnFailure 定义了当 Execute 方法返回错误时,需要执行的回滚或清理逻辑。
// log: 任务执行的上下文。 // log: 任务执行的上下文。
// executeErr: 从 Execute 方法返回的原始错误。 // executeErr: 从 Execute 方法返回的原始错误。
OnFailure(executeErr error) OnFailure(ctx context.Context, executeErr error)
TaskDeviceIDResolver TaskDeviceIDResolver
} }
@@ -22,13 +26,13 @@ type Task interface {
type TaskDeviceIDResolver interface { type TaskDeviceIDResolver interface {
// ResolveDeviceIDs 从任务配置中解析并返回所有关联的设备ID列表 // ResolveDeviceIDs 从任务配置中解析并返回所有关联的设备ID列表
// 返回值: uint数组每个字符串代表一个设备ID // 返回值: uint数组每个字符串代表一个设备ID
ResolveDeviceIDs() ([]uint, error) ResolveDeviceIDs(ctx context.Context) ([]uint, error)
} }
// TaskFactory 是一个工厂接口,用于根据任务执行日志创建任务实例。 // TaskFactory 是一个工厂接口,用于根据任务执行日志创建任务实例。
type TaskFactory interface { type TaskFactory interface {
// Production 根据指定的任务执行日志创建一个任务实例。 // Production 根据指定的任务执行日志创建一个任务实例。
Production(claimedLog *models.TaskExecutionLog) Task Production(ctx context.Context, claimedLog *models.TaskExecutionLog) Task
// CreateTaskFromModel 仅根据任务模型创建一个任务实例,用于非执行场景(如参数解析)。 // CreateTaskFromModel 仅根据任务模型创建一个任务实例,用于非执行场景(如参数解析)。
CreateTaskFromModel(taskModel *models.Task) (TaskDeviceIDResolver, error) CreateTaskFromModel(ctx context.Context, taskModel *models.Task) (TaskDeviceIDResolver, error)
} }

View File

@@ -1,6 +1,7 @@
package task package task
import ( import (
"context"
"fmt" "fmt"
"time" "time"
@@ -15,45 +16,47 @@ type DelayTaskParams struct {
// DelayTask 是一个用于模拟延迟的 Task 实现 // DelayTask 是一个用于模拟延迟的 Task 实现
type DelayTask struct { type DelayTask struct {
ctx context.Context
executionTask *models.TaskExecutionLog executionTask *models.TaskExecutionLog
duration time.Duration 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{ return &DelayTask{
ctx: ctx,
executionTask: executionTask, executionTask: executionTask,
logger: logger,
} }
} }
// Execute 执行延迟任务,等待指定的时间 // Execute 执行延迟任务,等待指定的时间
func (d *DelayTask) Execute() error { func (d *DelayTask) Execute(ctx context.Context) error {
if err := d.parseParameters(); err != nil { taskCtx, logger := logs.Trace(ctx, d.ctx, "Execute")
if err := d.parseParameters(taskCtx); err != nil {
return err 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) time.Sleep(d.duration)
d.logger.Infof("任务 %v: 延迟结束。", d.executionTask.TaskID) logger.Infof("任务 %v: 延迟结束。", d.executionTask.TaskID)
return nil 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 { 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) return fmt.Errorf("任务 %v: 参数不全", d.executionTask.TaskID)
} }
var params DelayTaskParams var params DelayTaskParams
err := d.executionTask.Task.ParseParameters(&params) err := d.executionTask.Task.ParseParameters(&params)
if err != nil { 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) return fmt.Errorf("任务 %v: 解析参数失败: %v", d.executionTask.TaskID, err)
} }
if params.DelayDuration <= 0 { 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) return fmt.Errorf("任务 %v: 参数 delay_duration 缺失或无效 (必须大于0)", d.executionTask.TaskID)
} }
@@ -62,10 +65,11 @@ func (d *DelayTask) parseParameters() error {
return nil return nil
} }
func (d *DelayTask) OnFailure(executeErr error) { func (d *DelayTask) OnFailure(ctx context.Context, executeErr error) {
d.logger.Errorf("任务 %v: 执行失败: %v", d.executionTask.TaskID, executeErr) 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 return []uint{}, nil
} }

View File

@@ -1,6 +1,7 @@
package task package task
import ( import (
"context"
"fmt" "fmt"
"git.huangwc.com/pig/pig-farm-controller/internal/domain/device" "git.huangwc.com/pig/pig-farm-controller/internal/domain/device"
@@ -12,38 +13,39 @@ import (
// FullCollectionTask 实现了 plan.Task 接口,用于执行一次全量的设备数据采集 // FullCollectionTask 实现了 plan.Task 接口,用于执行一次全量的设备数据采集
type FullCollectionTask struct { type FullCollectionTask struct {
ctx context.Context
log *models.TaskExecutionLog log *models.TaskExecutionLog
deviceRepo repository.DeviceRepository deviceRepo repository.DeviceRepository
deviceService device.Service deviceService device.Service
logger *logs.Logger
} }
// NewFullCollectionTask 创建一个全量采集任务实例 // NewFullCollectionTask 创建一个全量采集任务实例
func NewFullCollectionTask( func NewFullCollectionTask(
ctx context.Context,
log *models.TaskExecutionLog, log *models.TaskExecutionLog,
deviceRepo repository.DeviceRepository, deviceRepo repository.DeviceRepository,
deviceService device.Service, deviceService device.Service,
logger *logs.Logger,
) plan.Task { ) plan.Task {
return &FullCollectionTask{ return &FullCollectionTask{
ctx: ctx,
log: log, log: log,
deviceRepo: deviceRepo, deviceRepo: deviceRepo,
deviceService: deviceService, deviceService: deviceService,
logger: logger,
} }
} }
// Execute 是任务的核心执行逻辑 // Execute 是任务的核心执行逻辑
func (t *FullCollectionTask) Execute() error { func (t *FullCollectionTask) Execute(ctx context.Context) error {
t.logger.Infow("开始执行全量采集任务", "task_id", t.log.TaskID, "task_type", t.log.Task.Type, "log_id", t.log.ID) 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 { if err != nil {
return fmt.Errorf("全量采集任务: 从数据库获取所有传感器失败: %w", err) return fmt.Errorf("全量采集任务: 从数据库获取所有传感器失败: %w", err)
} }
if len(sensors) == 0 { 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 return nil
} }
@@ -54,15 +56,15 @@ func (t *FullCollectionTask) Execute() error {
var firstError error var firstError error
for controllerID, controllerSensors := range sensorsByController { for controllerID, controllerSensors := range sensorsByController {
t.logger.Infow("全量采集任务: 准备为区域主控下的传感器下发采集指令", logger.Infow("全量采集任务: 准备为区域主控下的传感器下发采集指令",
"task_id", t.log.TaskID, "task_id", t.log.TaskID,
"task_type", t.log.Task.Type, "task_type", t.log.Task.Type,
"log_id", t.log.ID, "log_id", t.log.ID,
"controller_id", controllerID, "controller_id", controllerID,
"sensor_count", len(controllerSensors), "sensor_count", len(controllerSensors),
) )
if err := t.deviceService.Collect(controllerID, controllerSensors); err != nil { if err := t.deviceService.Collect(taskCtx, controllerID, controllerSensors); err != nil {
t.logger.Errorw("全量采集任务: 为区域主控下发采集指令失败", logger.Errorw("全量采集任务: 为区域主控下发采集指令失败",
"task_id", t.log.TaskID, "task_id", t.log.TaskID,
"task_type", t.log.Task.Type, "task_type", t.log.Task.Type,
"log_id", t.log.ID, "log_id", t.log.ID,
@@ -79,13 +81,14 @@ func (t *FullCollectionTask) Execute() error {
return fmt.Errorf("全量采集任务执行期间发生错误: %w", firstError) 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 return nil
} }
// OnFailure 定义了当 Execute 方法返回错误时,需要执行的回滚或清理逻辑 // OnFailure 定义了当 Execute 方法返回错误时,需要执行的回滚或清理逻辑
func (t *FullCollectionTask) OnFailure(executeErr error) { func (t *FullCollectionTask) OnFailure(ctx context.Context, executeErr error) {
t.logger.Errorw("全量采集任务执行失败", logger := logs.TraceLogger(ctx, t.ctx, "OnFailure")
logger.Errorw("全量采集任务执行失败",
"task_id", t.log.TaskID, "task_id", t.log.TaskID,
"task_type", t.log.Task.Type, "task_type", t.log.Task.Type,
"log_id", t.log.ID, "log_id", t.log.ID,
@@ -94,7 +97,7 @@ func (t *FullCollectionTask) OnFailure(executeErr error) {
} }
// ResolveDeviceIDs 获取当前任务需要使用的设备ID列表 // ResolveDeviceIDs 获取当前任务需要使用的设备ID列表
func (t *FullCollectionTask) ResolveDeviceIDs() ([]uint, error) { func (t *FullCollectionTask) ResolveDeviceIDs(ctx context.Context) ([]uint, error) {
// 全量采集任务不和任何设备绑定, 每轮采集都会重新获取全量传感器 // 全量采集任务不和任何设备绑定, 每轮采集都会重新获取全量传感器
return []uint{}, nil return []uint{}, nil
} }

View File

@@ -1,6 +1,7 @@
package task package task
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"sync" "sync"
@@ -22,6 +23,8 @@ type ReleaseFeedWeightTaskParams struct {
// ReleaseFeedWeightTask 是一个控制下料口释放指定重量的任务 // ReleaseFeedWeightTask 是一个控制下料口释放指定重量的任务
type ReleaseFeedWeightTask struct { type ReleaseFeedWeightTask struct {
ctx context.Context
deviceRepo repository.DeviceRepository deviceRepo repository.DeviceRepository
sensorDataRepo repository.SensorDataRepository sensorDataRepo repository.SensorDataRepository
claimedLog *models.TaskExecutionLog claimedLog *models.TaskExecutionLog
@@ -34,40 +37,39 @@ type ReleaseFeedWeightTask struct {
// onceParse 保证解析参数只执行一次 // onceParse 保证解析参数只执行一次
onceParse sync.Once onceParse sync.Once
logger *logs.Logger
} }
// NewReleaseFeedWeightTask 创建一个新的 ReleaseFeedWeightTask 实例 // NewReleaseFeedWeightTask 创建一个新的 ReleaseFeedWeightTask 实例
func NewReleaseFeedWeightTask( func NewReleaseFeedWeightTask(
ctx context.Context,
claimedLog *models.TaskExecutionLog, claimedLog *models.TaskExecutionLog,
sensorDataRepo repository.SensorDataRepository, sensorDataRepo repository.SensorDataRepository,
deviceRepo repository.DeviceRepository, deviceRepo repository.DeviceRepository,
deviceService device.Service, deviceService device.Service,
logger *logs.Logger,
) plan.Task { ) plan.Task {
return &ReleaseFeedWeightTask{ return &ReleaseFeedWeightTask{
ctx: ctx,
claimedLog: claimedLog, claimedLog: claimedLog,
deviceRepo: deviceRepo, deviceRepo: deviceRepo,
sensorDataRepo: sensorDataRepo, sensorDataRepo: sensorDataRepo,
feedPort: deviceService, feedPort: deviceService,
logger: logger,
} }
} }
func (r *ReleaseFeedWeightTask) Execute() error { func (r *ReleaseFeedWeightTask) Execute(ctx context.Context) error {
r.logger.Infof("任务 %v: 开始执行, 日志ID: %v", r.claimedLog.TaskID, r.claimedLog.ID) taskCtx, logger := logs.Trace(ctx, r.ctx, "Execute")
if err := r.parseParameters(); err != nil { logger.Infof("任务 %v: 开始执行, 日志ID: %v", r.claimedLog.TaskID, r.claimedLog.ID)
if err := r.parseParameters(taskCtx); err != nil {
return err return err
} }
weight, err := r.getNowWeight() weight, err := r.getNowWeight(taskCtx)
if err != nil { if err != nil {
return err return err
} }
if err = r.feedPort.Switch(r.feedPortDevice, device.DeviceActionStart); err != nil { if err = r.feedPort.Switch(taskCtx, r.feedPortDevice, device.DeviceActionStart); err != nil {
r.logger.Errorf("启动下料口(id=%v)失败: %v , 日志ID: %v", r.feedPortDevice.ID, err, r.claimedLog.ID) logger.Errorf("启动下料口(id=%v)失败: %v , 日志ID: %v", r.feedPortDevice.ID, err, r.claimedLog.ID)
return err return err
} }
@@ -76,33 +78,34 @@ func (r *ReleaseFeedWeightTask) Execute() error {
// TODO 这个判断有延迟, 尤其是LoRa通信本身延迟较高, 可以考虑根据信号质量或其他指标提前发送停止命令 // TODO 这个判断有延迟, 尤其是LoRa通信本身延迟较高, 可以考虑根据信号质量或其他指标提前发送停止命令
for targetWeight <= weight { for targetWeight <= weight {
weight, err = r.getNowWeight() weight, err = r.getNowWeight(taskCtx)
if err != nil { if err != nil {
errCount++ errCount++
if errCount > 3 { // 如果连续三次没成功采集到重量数据,则认为计划执行失败 if errCount > 3 { // 如果连续三次没成功采集到重量数据,则认为计划执行失败
r.logger.Errorf("获取当前计划执行日志(id=%v)的当前搅拌罐重量失败: %v, 任务结束", r.claimedLog.ID, err) logger.Errorf("获取当前计划执行日志(id=%v)的当前搅拌罐重量失败: %v, 任务结束", r.claimedLog.ID, err)
return 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 continue
} }
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
} }
if err = r.feedPort.Switch(r.feedPortDevice, device.DeviceActionStop); err != nil { if err = r.feedPort.Switch(taskCtx, r.feedPortDevice, device.DeviceActionStop); err != nil {
r.logger.Errorf("关闭下料口(id=%v)失败: %v , 日志ID: %v", r.feedPortDevice.ID, err, r.claimedLog.ID) logger.Errorf("关闭下料口(id=%v)失败: %v , 日志ID: %v", r.feedPortDevice.ID, err, r.claimedLog.ID)
return err 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 return nil
} }
// 获取当前搅拌罐重量 // 获取当前搅拌罐重量
func (r *ReleaseFeedWeightTask) getNowWeight() (float64, error) { func (r *ReleaseFeedWeightTask) getNowWeight(ctx context.Context) (float64, error) {
sensorData, err := r.sensorDataRepo.GetLatestSensorDataByDeviceIDAndSensorType(r.mixingTankDeviceID, models.SensorTypeWeight) taskCtx, logger := logs.Trace(ctx, r.ctx, "getNowWeight")
sensorData, err := r.sensorDataRepo.GetLatestSensorDataByDeviceIDAndSensorType(taskCtx, r.mixingTankDeviceID, models.SensorTypeWeight)
if err != nil { 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 0, err
} }
@@ -113,47 +116,48 @@ func (r *ReleaseFeedWeightTask) getNowWeight() (float64, error) {
wg := &models.WeightData{} wg := &models.WeightData{}
err = json.Unmarshal(sensorData.Data, wg) err = json.Unmarshal(sensorData.Data, wg)
if err != nil { 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 0, err
} }
return wg.WeightKilograms, nil 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 var err error
r.onceParse.Do(func() { r.onceParse.Do(func() {
if r.claimedLog.Task.Parameters == nil { 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) err = fmt.Errorf("任务 %v: 参数不全", r.claimedLog.TaskID)
} }
var params ReleaseFeedWeightTaskParams var params ReleaseFeedWeightTaskParams
err := r.claimedLog.Task.ParseParameters(&params) err := r.claimedLog.Task.ParseParameters(&params)
if err != nil { 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) err = fmt.Errorf("任务 %v: 解析参数失败: %v", r.claimedLog.TaskID, err)
} }
// 校验参数是否存在 // 校验参数是否存在
if params.ReleaseWeight == 0 { 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) err = fmt.Errorf("任务 %v: 参数 release_weight 缺失或无效", r.claimedLog.TaskID)
} }
if params.FeedPortDeviceID == 0 { 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) err = fmt.Errorf("任务 %v: 参数 feed_port_device_id 缺失或无效", r.claimedLog.TaskID)
} }
if params.MixingTankDeviceID == 0 { 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) err = fmt.Errorf("任务 %v: 参数 mixing_tank_device_id 缺失或无效", r.claimedLog.TaskID)
} }
r.releaseWeight = params.ReleaseWeight r.releaseWeight = params.ReleaseWeight
r.mixingTankDeviceID = params.MixingTankDeviceID r.mixingTankDeviceID = params.MixingTankDeviceID
r.feedPortDevice, err = r.deviceRepo.FindByID(params.FeedPortDeviceID) r.feedPortDevice, err = r.deviceRepo.FindByID(taskCtx, params.FeedPortDeviceID)
if err != nil { 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) err = fmt.Errorf("任务 %v: 获取设备信息失败: %v", r.claimedLog.TaskID, err)
} }
@@ -161,21 +165,23 @@ func (r *ReleaseFeedWeightTask) parseParameters() error {
return err return err
} }
func (r *ReleaseFeedWeightTask) OnFailure(executeErr error) { func (r *ReleaseFeedWeightTask) OnFailure(ctx context.Context, executeErr error) {
r.logger.Errorf("开始善后处理, 日志ID:%v; 错误信息: %v", r.claimedLog.ID, executeErr) taskCtx, logger := logs.Trace(ctx, r.ctx, "OnFailure")
logger.Errorf("开始善后处理, 日志ID:%v; 错误信息: %v", r.claimedLog.ID, executeErr)
if r.feedPort != nil { if r.feedPort != nil {
err := r.feedPort.Switch(r.feedPortDevice, device.DeviceActionStop) err := r.feedPort.Switch(taskCtx, r.feedPortDevice, device.DeviceActionStop)
if err != nil { if err != nil {
r.logger.Errorf("[严重] 下料口停止失败, 日志ID: %v, 错误: %v", r.claimedLog.ID, err) logger.Errorf("[严重] 下料口停止失败, 日志ID: %v, 错误: %v", r.claimedLog.ID, err)
} }
} else { } 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) { func (r *ReleaseFeedWeightTask) ResolveDeviceIDs(ctx context.Context) ([]uint, error) {
if err := r.parseParameters(); err != nil { taskCtx := logs.AddFuncName(ctx, r.ctx, "ResolveDeviceIDs")
if err := r.parseParameters(taskCtx); err != nil {
return nil, err return nil, err
} }
return []uint{r.feedPortDevice.ID, r.mixingTankDeviceID}, nil return []uint{r.feedPortDevice.ID, r.mixingTankDeviceID}, nil

View File

@@ -1,6 +1,7 @@
package task package task
import ( import (
"context"
"fmt" "fmt"
"git.huangwc.com/pig/pig-farm-controller/internal/domain/device" "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" "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
) )
const (
CompNameDelayTask = "DelayTask"
CompNameReleaseFeedWeight = "ReleaseFeedWeightTask"
CompNameFullCollectionTask = "FullCollectionTask"
)
type taskFactory struct { type taskFactory struct {
logger *logs.Logger ctx context.Context
sensorDataRepo repository.SensorDataRepository sensorDataRepo repository.SensorDataRepository
deviceRepo repository.DeviceRepository deviceRepo repository.DeviceRepository
deviceService device.Service deviceService device.Service
} }
func NewTaskFactory( func NewTaskFactory(
logger *logs.Logger, ctx context.Context,
sensorDataRepo repository.SensorDataRepository, sensorDataRepo repository.SensorDataRepository,
deviceRepo repository.DeviceRepository, deviceRepo repository.DeviceRepository,
deviceService device.Service, deviceService device.Service,
) plan.TaskFactory { ) plan.TaskFactory {
return &taskFactory{ return &taskFactory{
logger: logger, ctx: ctx,
sensorDataRepo: sensorDataRepo, sensorDataRepo: sensorDataRepo,
deviceRepo: deviceRepo, deviceRepo: deviceRepo,
deviceService: deviceService, 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 { switch claimedLog.Task.Type {
case models.TaskTypeWaiting: case models.TaskTypeWaiting:
return NewDelayTask(t.logger, claimedLog) return NewDelayTask(logs.AddCompName(baseCtx, CompNameDelayTask), claimedLog)
case models.TaskTypeReleaseFeedWeight: 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: case models.TaskTypeFullCollection:
return NewFullCollectionTask(claimedLog, t.deviceRepo, t.deviceService, t.logger) return NewFullCollectionTask(logs.AddCompName(baseCtx, CompNameFullCollectionTask), claimedLog, t.deviceRepo, t.deviceService)
default: default:
// TODO 这里直接panic合适吗? 不过这个场景确实不该出现任何异常的任务类型 // TODO 这里直接panic合适吗? 不过这个场景确实不该出现任何异常的任务类型
t.logger.Panicf("不支持的任务类型: %s", claimedLog.Task.Type) logger.Panicf("不支持的任务类型: %s", claimedLog.Task.Type)
panic("不支持的任务类型") // 显式panic防编译器报错 panic("不支持的任务类型") // 显式panic防编译器报错
} }
} }
// CreateTaskFromModel 实现了 TaskFactory 接口,用于从模型创建任务实例。 // 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 的其他字段,所以可以构造一个临时的 // 这个方法不关心 claimedLog 的其他字段,所以可以构造一个临时的
// 它只用于访问那些不依赖于执行日志的方法,比如 ResolveDeviceIDs // 它只用于访问那些不依赖于执行日志的方法,比如 ResolveDeviceIDs
tempLog := &models.TaskExecutionLog{Task: *taskModel} tempLog := &models.TaskExecutionLog{Task: *taskModel}
baseCtx := context.Background()
switch taskModel.Type { switch taskModel.Type {
case models.TaskTypeWaiting: case models.TaskTypeWaiting:
return NewDelayTask(t.logger, tempLog), nil return NewDelayTask(logs.AddCompName(baseCtx, CompNameDelayTask), tempLog), nil
case models.TaskTypeReleaseFeedWeight: case models.TaskTypeReleaseFeedWeight:
return NewReleaseFeedWeightTask( return NewReleaseFeedWeightTask(
logs.AddCompName(baseCtx, CompNameReleaseFeedWeight),
tempLog, tempLog,
t.sensorDataRepo, t.sensorDataRepo,
t.deviceRepo, t.deviceRepo,
t.deviceService, t.deviceService,
t.logger,
), nil ), nil
case models.TaskTypeFullCollection: 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: default:
return nil, fmt.Errorf("不支持为类型 '%s' 的任务创建模型实例", taskModel.Type) return nil, fmt.Errorf("不支持为类型 '%s' 的任务创建模型实例", taskModel.Type)
} }

View File

@@ -1,6 +1,7 @@
package notify package notify
import ( import (
"context"
"time" "time"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
@@ -38,7 +39,7 @@ type AlarmContent struct {
// Notifier 定义了通知发送器的接口 // Notifier 定义了通知发送器的接口
type Notifier interface { type Notifier interface {
// Send 发送通知 // Send 发送通知
Send(content AlarmContent, toAddr string) error Send(ctx context.Context, content AlarmContent, toAddr string) error
// Type 返回通知器的类型 // Type 返回通知器的类型
Type() NotifierType Type() NotifierType
} }

View File

@@ -13,24 +13,24 @@ type Claims struct {
jwt.RegisteredClaims jwt.RegisteredClaims
} }
// Service 定义了 token 操作的接口 // Generator 定义了 token 操作的接口
type Service interface { type Generator interface {
GenerateToken(userID uint) (string, error) GenerateToken(userID uint) (string, error)
ParseToken(tokenString string) (*Claims, error) ParseToken(tokenString string) (*Claims, error)
} }
// tokenService 是 Service 接口的实现 // tokenGenerator 是 Generator 接口的实现
type tokenService struct { type tokenGenerator struct {
secret []byte secret []byte
} }
// NewTokenService 创建并返回一个新的 Service 实例 // NewTokenGenerator 创建并返回一个新的 Generator 实例
func NewTokenService(secret []byte) Service { func NewTokenGenerator(secret []byte) Generator {
return &tokenService{secret: secret} return &tokenGenerator{secret: secret}
} }
// GenerateToken 生成一个新的 JWT token // GenerateToken 生成一个新的 JWT token
func (s *tokenService) GenerateToken(userID uint) (string, error) { func (s *tokenGenerator) GenerateToken(userID uint) (string, error) {
nowTime := time.Now() nowTime := time.Now()
expireTime := nowTime.Add(24 * time.Hour) // Token 有效期为 24 小时 expireTime := nowTime.Add(24 * time.Hour) // Token 有效期为 24 小时
@@ -48,7 +48,7 @@ func (s *tokenService) GenerateToken(userID uint) (string, error) {
} }
// ParseToken 解析并验证 JWT token // 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) { token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return s.secret, nil return s.secret, nil
}) })