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