diff --git a/design/provide-logger-with-mothed/implementation.md b/design/provide-logger-with-mothed/implementation.md new file mode 100644 index 0000000..9aba8c8 --- /dev/null +++ b/design/provide-logger-with-mothed/implementation.md @@ -0,0 +1,89 @@ +# 实现方案:纯 Context 驱动的调用链追踪 + +本方案旨在提供一个绝对安全、符合 Go 语言习惯且对业务代码侵入性最小的调用链追踪方案。其核心思想是:**调用链信息完全由标准的 `context.Context` 承载,并通过 `logs` 包提供的一系列无状态包级函数进行原子化、安全的操作。** + +### 1. 核心设计原则 + +- **`Context` 是唯一载体**: 所有的调用链信息,包括组件名(对象名)和函数名(方法名),都只存储在标准的 `context.Context` 中。我们不再定义任何自定义的 `Context` 接口,以保证最大的兼容性。 + +- **纯粹的包级函数**: `logs` 包将提供一系列纯粹的、无状态的包级函数,作为与 `Context` 交互的唯一 API。这些函数负责向 `Context` 中添加信息,或从 `Context` 中生成 `Logger`。 + +- **无状态的 `Logger`**: `Logger` 对象本身不再携带任何调用链信息。它在被 `logs.GetLogger(ctx)` 生成时,才被一次性地赋予包含完整调用链的配置。 + +### 2. 实现细节 + +#### a. Context 中存储的数据 + +`context.Context` 将通过 `context.WithValue` 在幕后存储两种核心信息,这两种信息都使用 `logs` 包内部的私有 `key` 类型,以避免与其他包的键冲突。 + +- **组件名 (`compNameKey`)**: 用于存储一个字符串,表示当前上下文环境属于哪个组件(例如 `\"组件1\"`)。 +- **调用链 (`chainKey`)**: 用于存储一个字符串切片 (`[]string`),记录了从请求开始到当前位置的完整调用路径(例如 `[\"组件2.Create\", \"组件1.Create\"]`)。 + +#### b. `logs` 包提供的核心 API + +`logs` 包需要对外提供以下四个核心的包级函数,以提供不同粒度的灵活性: + +1. **`logs.AddCompName(ctx, compName) context.Context`** + - **职责**: 将一个组件名(对象名)存入 `Context`,并返回一个包含该信息的新 `Context`。这通常在依赖注入时完成,用于创建组件的“身份名牌” `selfCtx`。 + - **实现细节**: 该函数接收一个 `context.Context` 和一个 `compName` 字符串。它内部使用 `context.WithValue`,以私有的 `compNameKey` 为键,将 `compName` 字符串存入 `Context`,然后返回这个全新的 `Context`。 + +2. **`logs.AddFuncName(upstreamCtx, selfCtx, funcName) context.Context`** + - **职责**: 这是构建调用链的核心原子操作。它智能地合并上游的调用链和当前组件的信息,生成并返回一个包含更新后调用链和当前组件名的 **新 `Context`**。此函数用于只需要传递调用链而不需要立即打印日志的场景。 + - **实现细节**: + 1. 函数接收 `upstreamCtx`, `selfCtx`, `funcName`。 + 2. **获取上游调用链**: 从 `upstreamCtx` 中,通过 `chainKey` 读取出已经存在的调用链(`oldChain []string`)。 + 3. **获取当前组件名**: 从 `selfCtx` 中,通过 `compNameKey` 读取出当前组件的名称(`compName string`)。 + 4. **构建新节点**: 将 `compName` 和 `funcName` 拼接成一个新节点(例如 `\"组件2.Create\"`)。 + 5. **生成新调用链**: 将这个新节点追加到 `oldChain` 的末尾,形成 `newChain []string`。 + 6. **创建新的 Context**: + - 使用 `context.WithValue`,以 `chainKey` 为键,将 `newChain` 存入一个新的 `Context`,我们称之为 `tmpCtx`。 + - 接着,**(关键修正)** 基于 `tmpCtx`,再次调用 `context.WithValue`,以 `compNameKey` 为键,将从 `selfCtx` 中获取的 `compName` 存入,得到最终的 `newCtx`。这确保了传向下游的 `Context` 正确地标识了当前组件。 + 7. **返回**: 返回 `newCtx`。 + +3. **`logs.GetLogger(ctx) *Logger`** + - **职责**: 从 `Context` 中生成最终的、可用于打印的 `Logger` 实例。 + - **实现细节**: + 1. 函数接收一个 `context.Context`。 + 2. 它从 `ctx` 中,通过 `chainKey` 读取出完整的调用链 `[]string`。 + 3. 如果调用链不存在或为空,它就生成一个不带 `trace` 字段的普通 `Logger` 实例并返回。 + 4. 如果调用链存在,它就用 `->` 符号将切片中的所有节点拼接成一个完整的 `trace` 字符串。 + 5. 最后,它创建一个**一次性**的 `Logger` 实例,将这个 `trace` 字符串和底层的 `zap` 配置传给它,然后返回这个准备就绪的 `Logger`。 + +4. **`logs.Trace(upstreamCtx, selfCtx, funcName) (context.Context, *Logger)`** + - **职责**: 作为 `AddFuncName` 和 `GetLogger` 的便捷封装,一步到位地完成调用链构建和 `Logger` 生成。用于需要立即打印日志的场景。 + - **实现细节**: + 1. 内部调用 `newCtx := logs.AddFuncName(upstreamCtx, selfCtx, funcName)`。 + 2. 内部调用 `logger := logs.GetLogger(newCtx)`。 + 3. 返回 `newCtx` 和 `logger`。 + +### 3. 最终使用模式 + +#### a. 依赖注入阶段 + +(保持不变)在应用启动和组装依赖时,我们不再注入 `Logger` 对象,而是为每个组件创建一个包含其自身名称的专属 `Context`,并将这个 `Context` 注入到组件实例中。 + +- **流程**: + 1. 在依赖注入的根源处,创建一个全局的、初始的 `context.Background()`。 + 2. 对于需要被追踪的组件(例如 `组件1`),调用 `logs.AddCompName(ctx, \"组件1\")` 来创建一个 `ctxForC1`。 + 3. 在创建 `组件1` 的实例时,将这个 `ctxForC1` 作为其成员变量(例如 `selfCtx`)保存起来。这个 `selfCtx` 就成了 `组件1` 的“身份名牌”。 + +#### b. 请求处理阶段 + +开发者可以根据需求灵活选择 API。 + +- **场景一:需要立即打印日志 (推荐)**: + 1. 在方法入口处,立即调用 `ctx, logger := logs.Trace(upstreamCtx, z.selfCtx, \"Create\")`。 + 2. 使用这个 `logger` 打印日志:`logger.Info(\"创建组件2\")`。 + 3. 当需要调用下游方法时,将返回的 **新 `Context` (`ctx`)** 传递下去。 + +- **场景二:只需要传递调用链**: + 1. 在方法入口处,调用 `ctx := logs.AddFuncName(upstreamCtx, z.selfCtx, \"Create\")`。 + 2. 这个方法本身不打印日志,但在调用下游方法时,将这个包含了更新后调用链的 **新 `Context` (`ctx`)** 传递下去。 + +- **场景三:在方法中间打印日志**: + 1. 一个方法可能在执行了一部分逻辑后才需要打印日志。 + 2. `ctx := logs.AddFuncName(upstreamCtx, z.selfCtx, \"Create\")` // 先在入口更新调用链 + 3. // ... 执行一些业务逻辑 ... + 4. `logger := logs.GetLogger(ctx)` // 在需要时,基于更新后的 ctx 获取 logger + 5. `logger.Info(\"业务逻辑执行到一半\")` + 6. // ... 继续执行并传递 ctx ... diff --git a/design/provide-logger-with-mothed/index.md b/design/provide-logger-with-mothed/index.md new file mode 100644 index 0000000..8aec52d --- /dev/null +++ b/design/provide-logger-with-mothed/index.md @@ -0,0 +1,21 @@ +# 需求 + +为日志系统提供一种机制,能够记录和追踪跨越不同对象和方法的调用链,以增强日志的可读性和可观测性。 + +## issue + +http://git.huangwc.com/pig/pig-farm-controller/issues/56 + +## 描述 + +### 初始问题 +在复杂的业务流程中,一个请求可能会流经多个服务或控制器。为了追踪一个完整的操作流程,开发者不得不在每个方法的日志中手动添加上下文信息(如 `actionType`),这非常繁琐且容易出错。 + +### 期望的演进 +1. **初步设想**: 提供一个 `logger.With()` 方法,调用后返回一个携带预设信息的日志记录器副本,后续日志自动附带此信息。 +2. **解决上下文覆盖**: 简单的 `With("action", "some_action")` 会导致同名字段被覆盖。因此,期望能将调用链信息存储在数组中,并在打印时拼接,如 `action: "action1·action2"`。 +3. **最终目标 (可观测性)**: 进一步区分“对象”和“方法”,构建出更具表现力的调用链。例如,当 `userController` 的 `Create` 方法调用 `userService` 的 `Create` 方法时,日志应能清晰地展示 `trace: "userController.Create->userService.Create"` 这样的调用关系。 + +## 实现方案 + +[实现方案](./implementation.md) diff --git a/design/provide-logger-with-mothed/task-api.md b/design/provide-logger-with-mothed/task-api.md new file mode 100644 index 0000000..ee74018 --- /dev/null +++ b/design/provide-logger-with-mothed/task-api.md @@ -0,0 +1,9 @@ +- **`internal/app/api/api.go` (`API`)**: + - [x] 修改 `NewAPI` 函数,移除 `logger` 参数,改为接收 `ctx context.Context`。 + - [x] 移除 `API` 结构体中的 `logger` 成员,改为保存 `Ctx context.Context`。 + - [x] 为 `API` 组件本身创建 `Ctx`:`Ctx := logs.AddCompName(ctx, 'API')`,并传递给所有 `Controller` + 的构造函数。 + - [x] 改造 `Start` 方法,从 `a.Ctx` 获取 `logger` 实例进行日志记录。 + - [x] 改造 `Stop` 方法,从 `a.Ctx` 获取 `logger` 实例进行日志记录。 +- **`internal/app/api/router.go` + - [x] 改造 `setupRoutes` 方法,从 `a.Ctx` 获取 `logger` 实例进行日志记录。 diff --git a/design/provide-logger-with-mothed/task-controller.md b/design/provide-logger-with-mothed/task-controller.md new file mode 100644 index 0000000..f2cbd9a --- /dev/null +++ b/design/provide-logger-with-mothed/task-controller.md @@ -0,0 +1,113 @@ +- **`internal/app/controller/user/user_controller.go` (`user.Controller`)** + - **结构体改造**: + - [x] 移除 `Controller` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `Ctx context.Context` 成员。 + - **构造函数改造 (`NewController`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 将这个 `Ctx` 赋值给 `Controller` 结构体的 `Ctx` 成员。 + - **公共方法改造 (`CreateUser`, `Login`, `SendTestNotification`)**: + - [x] 在每个方法入口处,从 `echo.Context` 中提取 `request.Context()` 作为 `upstreamCtx`。 + - [x] 使用 `newCtx, logger := logs.Trace(upstreamCtx, c.Ctx, "MethodName")` 获取新的 `context.Context` 和 + `logger` 实例。 + - [x] 将所有对 `c.logger.Errorf` 和 `c.logger.Infof` 的调用替换为 `logger.Errorf` 和 `logger.Infof`。 + - [x] 确保所有对 `c.userService` 的调用都将 `newCtx` 作为第一个参数传递。 +- **`internal/app/controller/device/device_controller.go` (`device.Controller`)** + - **结构体改造**: + - [x] 移除 `Controller` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `Ctx context.Context` 成员。 + - **构造函数改造 (`NewController`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 将这个 `Ctx` 赋值给 `Controller` 结构体的 `Ctx` 成员。 + - **公共方法改造 (`CreateDevice`, `GetDevice`, `ListDevices`, `UpdateDevice`, `DeleteDevice`, `ManualControl`, + `CreateAreaController`, `GetAreaController`, `ListAreaControllers`, `UpdateAreaController`, + `DeleteAreaController`, `CreateDeviceTemplate`, `GetDeviceTemplate`, `ListDeviceTemplates`, + `UpdateDeviceTemplate`, `DeleteDeviceTemplate`)**: + - [x] 在每个方法入口处,从 `echo.Context` 中提取 `request.Context()` 作为 `upstreamCtx`。 + - [x] 使用 `newCtx, logger := logs.Trace(upstreamCtx, c.Ctx, "MethodName")` 获取新的 `context.Context` 和 + `logger` 实例。 + - [x] 将所有对 `c.logger.Errorf`, `c.logger.Warnf`, `c.logger.Infof` 的调用替换为 `logger.Errorf`, + `logger.Warnf`, `logger.Infof`。 + - [x] 确保所有对 `c.deviceService` 的调用都将 `newCtx` 作为第一个参数传递。 +- **`internal/app/controller/plan/plan_controller.go` (`plan.Controller`)** + - **结构体改造**: + - [x] 移除 `Controller` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `Ctx context.Context` 成员。 + - **构造函数改造 (`NewController`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 将这个 `Ctx` 赋值给 `Controller` 结构体的 `Ctx` 成员。 + - **公共方法改造 (`CreatePlan`, `GetPlan`, `ListPlans`, `UpdatePlan`, `DeletePlan`, `StartPlan`, `StopPlan`)**: + - [x] 在每个方法入口处,从 `echo.Context` 中提取 `request.Context()` 作为 `upstreamCtx`。 + - [x] 使用 `newCtx, logger := logs.Trace(upstreamCtx, c.Ctx, "MethodName")` 获取新的 `context.Context` 和 + `logger` 实例。 + - [x] 将所有对 `c.logger.Errorf` 和 `c.logger.Infof` 的调用替换为 `logger.Errorf` 和 `logger.Infof`。 + - [x] 确保所有对 `c.planService` 的调用都将 `newCtx` 作为第一个参数传递。 +- **`internal/app/controller/management/pig_farm_controller.go` (`management.PigFarmController`)** + - **结构体改造**: + - [x] 移除 `PigFarmController` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `Ctx context.Context` 成员。 + - **构造函数改造 (`NewPigFarmController`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 将这个 `Ctx` 赋值给 `PigFarmController` 结构体的 `Ctx` 成员。 + - **公共方法改造 (`CreatePigHouse`, `GetPigHouse`, `ListPigHouses`, `UpdatePigHouse`, `DeletePigHouse`, `CreatePen`, + `GetPen`, `ListPens`, `UpdatePen`, `DeletePen`, `UpdatePenStatus`)**: + - [x] 在每个方法入口处,从 `echo.Context` 中提取 `request.Context()` 作为 `upstreamCtx`。 + - [x] 使用 `newCtx, logger := logs.Trace(upstreamCtx, c.Ctx, "MethodName")` 获取新的 `context.Context` 和 + `logger` 实例。 + - [x] 将所有对 `c.logger.Errorf` 和 `c.logger.Infof` 的调用替换为 `logger.Errorf` 和 `logger.Infof`。 + - [x] 确保所有对 `c.service` 的调用都将 `newCtx` 作为第一个参数传递。 +- **`internal/app/controller/management/pig_batch_controller.go` (`management.PigBatchController`)** + - **结构体改造**: + - [x] 移除 `PigBatchController` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `Ctx context.Context` 成员。 + - **构造函数改造 (`NewPigBatchController`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 将这个 `Ctx` 赋值给 `PigBatchController` 结构体的 `Ctx` 成员。 + - **公共方法改造 ( + 所有方法,例如 `CreatePigBatch`, `GetPigBatch`, `UpdatePigBatch`, `DeletePigBatch`, `ListPigBatches`, + `AssignEmptyPensToBatch`, `ReclassifyPenToNewBatch`, `RemoveEmptyPenFromBatch`, `MovePigsIntoPen`)**: + - [x] 这些方法通过调用 `controller_helpers.go` 中的 `handleAPIRequest...` 系列函数来处理请求。 + - [x] 确保传递给 `handleAPIRequest...` 系列函数的 `serviceExecutor` 匿名函数,其签名与 `controller_helpers.go` + 改造后期望的签名一致(即接收 `context.Context` 作为第一个参数)。 + - [x] 在 `serviceExecutor` 匿名函数内部,将接收到的 `context.Context` 传递给 `c.service` 的相应方法。 +- **`internal/app/controller/management/controller_helpers.go`** + - [x] 修改 `mapAndSendError` 函数中的 `c.logger.Errorf` 调用,改为从 `echo.Context` 中提取 `context.Context`,并使用 + `logs.Trace` 或 `logs.GetLogger` 获取 `logger` 实例进行日志记录。 + - [x] 检查其他函数是否直接或间接依赖 `*PigBatchController` 的 `logger` 成员,并进行相应改造。 +- **`internal/app/controller/management/pig_batch_trade_controller.go` (`management.PigBatchController`)** + - [x] 修改 `NewController` 函数,移除 `logger` 参数,改为接收 `Ctx context.Context`。 + - [x] 移除 `Controller` 结构体中的 `logger` 成员,改为保存 `Ctx`。 + - [x] **所有公共方法**: 接收 `echo.Context` 的方法,需要从中提取 `request.Context()` 作为 `upstreamCtx`。 +- **`internal/app/controller/management/pig_batch_health_controller.go` (`management.PigBatchController`)** + - [x] 修改 `NewController` 函数,移除 `logger` 参数,改为接收 `Ctx context.Context`。 + - [x] 移除 `Controller` 结构体中的 `logger` 成员,改为保存 `Ctx`。 + - [x] **所有公共方法**: 接收 `echo.Context` 的方法,需要从中提取 `request.Context()` 作为 `upstreamCtx`。 +- **`internal/app/controller/management/pig_batch_transfer_controller.go` (`management.PigBatchController`)** + - [x] 修改 `NewController` 函数,移除 `logger` 参数,改为接收 `Ctx context.Context`。 + - [x] 移除 `Controller` 结构体中的 `logger` 成员,改为保存 `Ctx`。 + - [x] **所有公共方法**: 接收 `echo.Context` 的方法,需要从中提取 `request.Context()` 作为 `upstreamCtx`。 +- **`internal/app/controller/monitor/monitor_controller.go` (`monitor.Controller`)** + - **结构体改造**: + - [x] 移除 `Controller` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `Ctx context.Context` 成员。 + - **构造函数改造 (`NewController`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 将这个 `Ctx` 赋值给 `Controller` 结构体的 `Ctx` 成员。 + - **公共方法改造 ( + 所有方法,例如 `ListSensorData`, `ListDeviceCommandLogs`, `ListPlanExecutionLogs`, `ListTaskExecutionLogs`, + `ListPendingCollections`, `ListUserActionLogs`, `ListRawMaterialPurchases`, `ListRawMaterialStockLogs`, + `ListFeedUsageRecords`, `ListMedicationLogs`, `ListPigBatchLogs`, `ListWeighingBatches`, `ListWeighingRecords`, + `ListPigTransferLogs`, `ListPigSickLogs`, `ListPigPurchases`, `ListPigSales`, `ListNotifications`)**: + - [x] 在每个方法入口处,从 `echo.Context` 中提取 `request.Context()` 作为 `upstreamCtx`。 + - [x] 使用 `newCtx, logger := logs.Trace(upstreamCtx, c.Ctx, actionType)` 获取新的 `context.Context` 和 + `logger` 实例(`actionType` 为方法内部定义的常量)。 + - [x] 将所有对 `c.logger.Errorf`, `c.logger.Warnf`, `c.logger.Infof` 的调用替换为 `logger.Errorf`, + `logger.Warnf`, `logger.Infof`。 + - [x] 确保所有对 `c.monitorService` 的调用都将 `newCtx` 作为第一个参数传递。 + + +- **`internal/app/controller/response.go`** + - [x] 修改 `SendSuccessWithAudit` 和 `SendErrorWithAudit` 函数签名,使其接收 `ctx context.Context` 作为第一个参数。 + - [x] 在 `setAuditDetails` 函数中,如果需要,从传入的 `context.Context` 中提取调用链信息,并将其添加到审计信息中。 +- **`internal/app/controller/auth_utils.go`** + - [x] 检查 `GetOperatorIDFromContext` 和 `GetOperatorFromContext` 函数,确保它们能够正确地从 `echo.Context` 中获取 + `request.Context()`,并从中提取用户信息。 \ No newline at end of file diff --git a/design/provide-logger-with-mothed/task-domain.md b/design/provide-logger-with-mothed/task-domain.md new file mode 100644 index 0000000..5c00668 --- /dev/null +++ b/design/provide-logger-with-mothed/task-domain.md @@ -0,0 +1,272 @@ +- **`internal/domain/audit/service.go` (`audit.Service`)** + - **结构体改造**: + - [x] 移除 `service` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewService`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,为 `audit.Service` 创建其专属的 `selfCtx`:`selfCtx := logs.AddCompName(ctx, "AuditService")`。 + - [x] 将这个 `selfCtx` 赋值给 `service` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (`LogAction`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "LogAction")` 获取新的 `context.Context` + 和 `logger` 实例。 + - [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`)** + - **结构体改造**: + - [x] 移除 `GeneralDeviceService` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGeneralDeviceService`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,为 `GeneralDeviceService` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "GeneralDeviceService")`。 + - [x] 将这个 `selfCtx` 赋值给 `GeneralDeviceService` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (`Switch`, `Collect`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, g.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [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`。 + - [x] 确保所有对 `g.deviceRepo`, `g.deviceCommandLogRepo`, `g.pendingCollectionRepo`, `g.comm` 等依赖的调用都将 + `newCtx` 作为第一个参数传递。 +- **`internal/domain/notify/notify.go` (`domain_notify.Service` - `failoverService` 实现)** + - **结构体改造**: + - [x] 移除 `failoverService` 结构体中的 `log *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewFailoverService`)**: + - [x] 修改函数签名,移除 `log *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,为 `failoverService` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "FailoverService")`。 + - [x] 将这个 `selfCtx` 赋值给 `failoverService` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (`SendBatchAlarm`, `BroadcastAlarm`, `SendTestMessage`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `s.log.Infow`, `s.log.Errorw`, `s.log.Warnw` 的调用替换为 `logger.Infow`, `logger.Errorw`, + `logger.Warnw`。 + - [x] 确保所有对 `s.userRepo`, `s.primaryNotifier.Send`, `s.notifiers`, `s.notificationRepo` 等依赖的调用都将 + `newCtx` 作为第一个参数传递。 + - **内部辅助方法改造 (`sendAlarmToUser`, `recordNotificationAttempt`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `s.log.Errorw`, `s.log.Infow`, `s.log.Warnw` 的调用替换为 `logger.Errorw`, `logger.Infow`, + `logger.Warnw`。 + - [x] 确保所有对 `s.userRepo`, `s.primaryNotifier.Send`, `s.notifiers`, `s.notificationRepo` 等依赖的调用都将 + `newCtx` 作为第一个参数传递。 +- **`internal/domain/pig/pen_transfer_manager.go` (`pig.PigPenTransferManager`)** + - **结构体改造**: + - [x] 移除 `pigPenTransferManager` 结构体中可能存在的 `logger *logs.Logger` 成员(如果未来添加)。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewPigPenTransferManager`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `pigPenTransferManager` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PigPenTransferManager")`。 + - [x] 将这个 `selfCtx` 赋值给 `pigPenTransferManager` 结构体的 `selfCtx` 成员。 + - **公共方法改造 ( + 所有方法,例如 `LogTransfer`, `GetPenByID`, `GetPensByBatchID`, `UpdatePenFields`, `GetCurrentPigsInPen`, + `GetTotalPigsInPensForBatchTx`, `ReleasePen`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(在 `tx *gorm.DB` 之前)。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [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`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewPigTradeManager`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `pigTradeManager` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PigTradeManager")`。 + - [x] 将这个 `selfCtx` 赋值给 `pigTradeManager` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (`SellPig`, `BuyPig`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(在 `tx *gorm.DB` 之前)。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 确保所有对 `s.tradeRepo` 的调用都将 `newCtx` 作为第一个参数传递。 +- **`internal/domain/pig/pig_sick_manager.go` (`pig.SickPigManager`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewSickPigManager`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `sickPigManager` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "SickPigManager")`。 + - [x] 将这个 `selfCtx` 赋值给 `sickPigManager` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (`ProcessSickPigLog`, `GetCurrentSickPigCount`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(在 `tx *gorm.DB` 之前)。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 确保所有对 `s.sickLogRepo`, `s.medicationLogRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。 +- **`internal/domain/pig/pig_batch_service.go` (`pig.PigBatchService`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewPigBatchService`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `pigBatchService` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PigBatchService")`。 + - [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`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 确保所有对 `s.pigBatchRepo`, `s.pigBatchLogRepo`, `s.uow`, `s.transferSvc`, `s.tradeSvc`, `s.sickSvc` + 等依赖的调用都将 `newCtx` 作为第一个参数传递。 +- **`internal/domain/plan/analysis_plan_task_manager.go` (`plan.AnalysisPlanTaskManager`)** + - **结构体改造**: + - [x] 移除 `analysisPlanTaskManagerImpl` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewAnalysisPlanTaskManager`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,为 `analysisPlanTaskManagerImpl` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "AnalysisPlanTaskManager")`。 + - [x] 将这个 `selfCtx` 赋值给 `analysisPlanTaskManagerImpl` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (`Refresh`, `CreateOrUpdateTrigger`, `EnsureAnalysisTaskDefinition`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, m.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `m.logger.Info`, `m.logger.Errorf`, `m.logger.Warnf` 的调用替换为 `logger.Info`, + `logger.Errorf`, `logger.Warnf`。 + - [x] 确保所有对 `m.planRepo`, `m.pendingTaskRepo`, `m.executionLogRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。 + - **内部辅助方法改造 (`getRefreshData`, `cleanupInvalidTasks`, `addOrUpdateTriggers`, `createTriggerTask`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, m.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `m.logger.Info`, `m.logger.Errorf`, `m.logger.Warnf` 的调用替换为 `logger.Info`, + `logger.Errorf`, `logger.Warnf`。 + - [x] 确保所有对 `m.planRepo`, `m.pendingTaskRepo`, `m.executionLogRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。 +- **`internal/domain/plan/plan_execution_manager.go` (`plan.ExecutionManager`)** + - **结构体改造**: + - [x] 移除 `planExecutionManagerImpl` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewPlanExecutionManager`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,为 `planExecutionManagerImpl` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PlanExecutionManager")`。 + - [x] 将这个 `selfCtx` 赋值给 `planExecutionManagerImpl` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (`Start`, `Stop`)**: + - [x] 在 `Start` 方法中,使用 `newCtx, logger := logs.Trace(context.Background(), s.selfCtx, "Start")` 获取 + `logger` 实例进行日志记录。 + - [x] 在 `Stop` 方法中,使用 `newCtx, logger := logs.Trace(context.Background(), s.selfCtx, "Stop")` 获取 + `logger` 实例进行日志记录。 + - **内部辅助方法改造 (`run`, `claimAndSubmit`, `handleRequeue`, `processTask`, `runTask`, `analysisPlan`, + `updateTaskExecutionLogStatus`, `handlePlanTermination`, `handlePlanCompletion`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(如果方法内部需要传递上下文)。 + - [x] 在每个方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `s.logger.Errorf`, `s.logger.Warnf`, `s.logger.Infof`, `s.logger.DPanicf` 的调用替换为 + `logger.Errorf`, `logger.Warnf`, `logger.Infof`, `logger.DPanicf`。 + - [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`)** + - **结构体改造**: + - [x] 移除 `planServiceImpl` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewPlanService`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,为 `planServiceImpl` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PlanService")`。 + - [x] 将这个 `selfCtx` 赋值给 `planServiceImpl` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (`Start`, `Stop`, `RefreshPlanTriggers`, `CreatePlan`, `GetPlanByID`, `ListPlans`, `UpdatePlan`, + `DeletePlan`, `StartPlan`, `StopPlan`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `s.logger.Errorf`, `s.logger.Infof`, `s.logger.Warnf` 的调用替换为 `logger.Errorf`, + `logger.Infof`, `logger.Warnf`。 + - [x] 确保所有对 `s.executionManager`, `s.taskManager`, `s.planRepo`, `s.deviceRepo`, `s.unitOfWork`, + `s.taskFactory` 等依赖的调用都将 `newCtx` 作为第一个参数传递。 +- **`internal/domain/task/task.go` (`task.TaskFactory`)** + - **结构体改造**: + - [x] 移除 `taskFactory` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewTaskFactory`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `taskFactory` 创建其专属的 `selfCtx`:`selfCtx := logs.AddCompName(ctx, "TaskFactory")`。 + - [x] 将这个 `selfCtx` 赋值给 `taskFactory` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (`Production`, `CreateTaskFromModel`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法内部,使用 `newCtx, logger := logs.Trace(ctx, t.selfCtx, "MethodName")` 获取新的 `context.Context` + 和 `logger` 实例。 + - [x] 将所有对 `t.logger.Panicf` 的调用替换为 `logger.Panicf`。 + - [x] 将所有对 `NewDelayTask`, `NewReleaseFeedWeightTask`, `NewFullCollectionTask` 的调用,传递 `newCtx`。 +- **`internal/domain/task/release_feed_weight_task.go`** + - **结构体改造**: + - [x] 移除 `ReleaseFeedWeightTask` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewReleaseFeedWeightTask`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `ReleaseFeedWeightTask` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "ReleaseFeedWeightTask")`。 + - [x] 将这个 `selfCtx` 赋值给 `ReleaseFeedWeightTask` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (`Execute`, `OnFailure`, `ResolveDeviceIDs`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, r.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `r.logger.Infof`, `r.logger.Errorf`, `r.logger.Warnf`, `r.logger.Debugf` 的调用替换为 + `logger.Infof`, `logger.Errorf`, `logger.Warnf`, `logger.Debugf`。 + - [x] 确保所有对 `r.deviceRepo`, `r.sensorDataRepo`, `r.feedPort` 等依赖的调用都将 `newCtx` 作为第一个参数传递。 + - **内部辅助方法改造 (`getNowWeight`, `parseParameters`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, r.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `r.logger.Errorf`, `r.logger.Warnf` 的调用替换为 `logger.Errorf`, `logger.Warnf`。 + - [x] 确保所有对 `r.sensorDataRepo`, `r.deviceRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。 +- **`internal/domain/task/full_collection_task.go`** + - **结构体改造**: + - [x] 移除 `FullCollectionTask` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewFullCollectionTask`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `FullCollectionTask` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "FullCollectionTask")`。 + - [x] 将这个 `selfCtx` 赋值给 `FullCollectionTask` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (`Execute`, `OnFailure`, `ResolveDeviceIDs`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, t.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `t.logger.Infow`, `t.logger.Errorw` 的调用替换为 `logger.Infow`, `logger.Errorw`。 + - [x] 确保所有对 `t.deviceRepo`, `t.deviceService` 等依赖的调用都将 `newCtx` 作为第一个参数传递。 +- **`internal/domain/task/delay_task.go`** + - **结构体改造**: + - [x] 移除 `DelayTask` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewDelayTask`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,为 `DelayTask` 创建其专属的 `selfCtx`:`selfCtx := logs.AddCompName(ctx, "DelayTask")`。 + - [x] 将这个 `selfCtx` 赋值给 `DelayTask` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (`Execute`, `OnFailure`, `ResolveDeviceIDs`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, d.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `d.logger.Infof` 和 `d.logger.Errorf` 的调用替换为 `logger.Infof` 和 `logger.Errorf`。 + - **内部辅助方法改造 (`parseParameters`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, d.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `d.logger.Errorf` 的调用替换为 `logger.Errorf`。 +- **`internal/domain/token/token_service.go` (`token.Service`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewTokenService`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `tokenService` 创建其专属的 `selfCtx`:`selfCtx := logs.AddCompName(ctx, "TokenService")`。 + - [x] 将这个 `selfCtx` 赋值给 `tokenService` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (`GenerateToken`, `ParseToken`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `s.logger.*` 的调用替换为 `logger.*` (如果存在)。 + + + + + + + + + diff --git a/design/provide-logger-with-mothed/task-infra.md b/design/provide-logger-with-mothed/task-infra.md new file mode 100644 index 0000000..595c63e --- /dev/null +++ b/design/provide-logger-with-mothed/task-infra.md @@ -0,0 +1,163 @@ +- **`internal/infra/database/storage.go` (`database.Storage`)** + - **工厂函数改造 (`NewStorage`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,将接收到的 `ctx` 作为第一个参数传递给 `NewPostgresStorage` 函数。 +- **`internal/infra/database/postgres.go`** + - **结构体改造**: + - [x] 移除 `PostgresStorage` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewPostgresStorage`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,为 `PostgresStorage` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PostgresStorage")`。 + - [x] 将这个 `selfCtx` 赋值给 `PostgresStorage` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (`Connect`, `Disconnect`, `Migrate`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, ps.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `ps.logger.Info`, `ps.logger.Errorw`, `ps.logger.Debugw` 的调用替换为 `logger.Info`, + `logger.Errorw`, `logger.Debugw`。 + - [x] 在 `Connect` 方法中,调用 `logs.NewGormLogger` 时,将 `newCtx` 传递给它。 + - [x] 在 `Connect` 方法中,调用 `gorm.Open` 时,使用 + `ps.db, err = gorm.Open(postgres.Open(ps.connectionString), &gorm.Config{Logger: logs.NewGormLogger(newCtx)})`。 + - [x] 在 `Migrate` 方法中,确保所有对 `ps.db.AutoMigrate` 和 `ps.db.Exec` 的调用都使用 `newCtx`。 + - **内部辅助方法改造 (`setupTimescaleDB`, `creatingHyperTable`, `applyCompressionPolicies`, `creatingIndex`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在每个方法入口处,使用 `newCtx, logger := logs.Trace(ctx, ps.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `ps.logger.Info`, `ps.logger.Errorw`, `ps.logger.Debugw`, `ps.logger.Warnw`, `ps.logger.Debug` + 的调用替换为 `logger.Info`, `logger.Errorw`, `logger.Debugw`, `logger.Warnw`, `logger.Debug`。 + - [x] 确保所有对 `ps.db.Exec` 的调用都使用 `newCtx`。 + +- **`internal/infra/notify/log_notifier.go` (`notify.LogNotifier`)** + - **结构体改造**: + - [x] 移除 `logNotifier` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewLogNotifier`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,为 `logNotifier` 创建其专属的 `selfCtx`:`selfCtx := logs.AddCompName(ctx, "LogNotifier")`。 + - [x] 将这个 `selfCtx` 赋值给 `logNotifier` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (`Send`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, l.selfCtx, "Send")` 获取新的 `context.Context` 和 + `logger` 实例。 + - [x] 将所有对 `l.logger.Infow` 的调用替换为 `logger.Infow`。 + - **公共方法 (`Type`)**: + - [x] 此方法不涉及日志或上下文传递,无需改造。 +- **`internal/infra/notify/lark.go` (`notify.LarkNotifier`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewLarkNotifier`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `larkNotifier` 创建其专属的 `selfCtx`:`selfCtx := logs.AddCompName(ctx, "LarkNotifier")`。 + - [x] 将这个 `selfCtx` 赋值给 `larkNotifier` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (`Send`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, l.selfCtx, "Send")` 获取新的 `context.Context` 和 + `logger` 实例。 + - [x] 将所有内部的错误日志(例如 `fmt.Errorf` 返回的错误,以及 `getAccessToken` 中的错误)通过 `logger.Errorf` + 记录。 + - **内部辅助方法改造 (`getAccessToken`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, l.selfCtx, "getAccessToken")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有内部的错误日志通过 `logger.Errorf` 记录。 +- **`internal/infra/notify/smtp.go` (`notify.SMTPNotifier`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewSMTPNotifier`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `smtpNotifier` 创建其专属的 `selfCtx`:`selfCtx := logs.AddCompName(ctx, "SMTPNotifier")`。 + - [x] 将这个 `selfCtx` 赋值给 `smtpNotifier` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (`Send`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "Send")` 获取新的 `context.Context` 和 + `logger` 实例。 + - [x] 将所有内部的错误日志(例如 `fmt.Errorf` 返回的错误)通过 `logger.Errorf` 记录。 +- **`internal/infra/notify/wechat.go` (`notify.WechatNotifier`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewWechatNotifier`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `wechatNotifier` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "WechatNotifier")`。 + - [x] 将这个 `selfCtx` 赋值给 `wechatNotifier` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (`Send`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, w.selfCtx, "Send")` 获取新的 `context.Context` 和 + `logger` 实例。 + - [x] 将所有内部的错误日志(例如 `fmt.Errorf` 返回的错误,以及 `getAccessToken` 中的错误)通过 `logger.Errorf` + 记录。 + - **内部辅助方法改造 (`getAccessToken`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, w.selfCtx, "getAccessToken")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有内部的错误日志通过 `logger.Errorf` 记录。 + +- **`internal/infra/transport/lora/chirp_stack.go` (`lora.ChirpStackTransport`)** + - **结构体改造**: + - [x] 移除 `ChirpStackTransport` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewChirpStackTransport`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,为 `ChirpStackTransport` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "ChirpStackTransport")`。 + - [x] 将这个 `selfCtx` 赋值给 `ChirpStackTransport` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (`Send`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, c.selfCtx, "Send")` 获取新的 `context.Context` 和 + `logger` 实例。 + - [x] 将所有对 `c.logger.Errorf` 和 `c.logger.Infof` 的调用替换为 `logger.Errorf` 和 `logger.Infof`。 + - [x] 在调用 `c.client.DeviceService.DeviceServiceEnqueue` 时,确保将 `newCtx` 传递给 + `params.WithContext(newCtx)`,以便 ChirpStack 客户端内部的 HTTP 请求也能携带上下文。 +- **`internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go` (`lora.LoRaMeshUartPassthroughTransport`)** + - **结构体改造**: + - [x] 移除 `LoRaMeshUartPassthroughTransport` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewLoRaMeshUartPassthroughTransport`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,为 `LoRaMeshUartPassthroughTransport` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "LoRaMeshUartPassthroughTransport")`。 + - [x] 将这个 `selfCtx` 赋值给 `LoRaMeshUartPassthroughTransport` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (`Listen`, `Send`, `Stop`)**: + - [x] 修改 `Listen` 方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 修改 `Send` 方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 修改 `Stop` 方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, t.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `t.logger.Info`, `t.logger.Errorf` 的调用替换为 `logger.Info`, `logger.Errorf`。 + - [x] 在 `Send` 方法中,确保 `t.sendChan <- req` 传递的 `req` 包含了 `newCtx`。 + - **内部辅助方法改造 (`workerLoop`, `runIdleState`, `runReceivingState`, `executeSend`, `handleFrame`, + `handleUpstreamMessage`, `recordSensorData`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在每个方法入口处,使用 `newCtx, logger := logs.Trace(ctx, t.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `t.logger.Info`, `t.logger.Errorf`, `t.logger.Warnf`, `t.logger.Infof`, `t.logger.Debugf` + 的调用替换为 `logger.Info`, `logger.Errorf`, `logger.Warnf`, `logger.Infof`, `logger.Debugf`。 + - [x] 确保所有对 `t.port`, `t.areaControllerRepo`, `t.pendingCollectionRepo`, `t.deviceRepo`, + `t.sensorDataRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。 +- **`internal/infra/transport/lora/placeholder_transport.go` (`lora.PlaceholderTransport`)** + - **结构体改造**: + - [x] 移除 `PlaceholderTransport` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewPlaceholderTransport`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,为 `PlaceholderTransport` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PlaceholderTransport")`。 + - [x] 将这个 `selfCtx` 赋值给 `PlaceholderTransport` 结构体的 `selfCtx` 成员。 + - [x] 使用 `newCtx, logger := logs.Trace(ctx, selfCtx, "NewPlaceholderTransport")` 获取 `logger` 实例,并替换 + `logger.Info` 调用。 + - **公共方法改造 (`Listen`, `Stop`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, p.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `p.logger.Warnf` 的调用替换为 `logger.Warnf`。 + + + + + + + + + diff --git a/design/provide-logger-with-mothed/task-list.md b/design/provide-logger-with-mothed/task-list.md new file mode 100644 index 0000000..ebe76f8 --- /dev/null +++ b/design/provide-logger-with-mothed/task-list.md @@ -0,0 +1,82 @@ +### 重构任务清单:纯 Context 驱动的调用链追踪 + +--- + +#### 1. 核心改造 (`logs` 包) + +- **`internal/infra/logs/logs.go` (`logs.Logger` - 内部实现,非注入)** + - **核心包级函数实现 (根据 `implementation.md` 描述)**: + - [x] 实现 `AddCompName(ctx context.Context, compName string) context.Context`。 + - [x] 实现 + `AddFuncName(upstreamCtx context.Context, selfCtx context.Context, funcName string) context.Context`。 + - [x] 实现 `GetLogger(ctx context.Context) *Logger`。 + - [x] 实现 + `Trace(upstreamCtx context.Context, selfCtx context.Context, funcName string) (context.Context, *Logger)`。 + - **`Logger` 结构体改造**: + - [x] 无 + - **`GormLogger` 改造**: + - [x] 修改 `GormLogger.Info` 方法,从传入的 `ctx` 中获取 `logger` 实例,并使用该实例进行日志记录。 + - [x] 修改 `GormLogger.Warn` 方法,从传入的 `ctx` 中获取 `logger` 实例,并使用该实例进行日志记录。 + - [x] 修改 `GormLogger.Error` 方法,从传入的 `ctx` 中获取 `logger` 实例,并使用该实例进行日志记录。 + - [x] 修改 `GormLogger.Trace` 方法,从传入的 `ctx` 中获取 `logger` 实例,并使用该实例进行日志记录。特别是 + `With(fields...)` 的调用需要调整。 + +--- + +#### 2. 依赖注入与结构体改造 + +- **`internal/core/application.go`**: + - [x] 移除 `Application` 结构体中的 `Logger *logs.Logger` 成员。 + - [x] 修改 `NewApplication` 函数,使其不再创建 `logger`,而是创建根 `context.Background()`。 + - [x] 调整 `NewApplication`,将根 `context` 传递给 `initInfrastructure`, `initDomainServices`, `initAppServices` 和 + `api.NewAPI`。 + - [x] 移除 `Application` 结构体中所有对 `app.Logger` 的直接调用,改为通过 `context` 获取 `Logger`。 + +- **`internal/core/component_initializers.go`**: + - [x] **修改所有组件结构体定义**: 遍历所有相关组件(Controllers, Services, Repositories 等),将其结构体中的 + `logger *logs.Logger` 成员变量替换为 `selfCtx context.Context`。 + - [x] **重构所有 `init...` 函数**: + - 移除所有 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - 在每个 `init...` 函数内部,为即将创建的组件生成其专属的 `selfCtx`。例如: + `selfCtx := logs.AddCompName(ctx, 'ComponentName')`。 + - 将这个 `selfCtx` 注入到组件的构造函数中,并由组件保存为 `selfCtx` 成员。 + +--- + +#### 3. 全局方法签名改造:传递 `context.Context` + +**以下所有列出的组件,其所有公共方法都需要进行签名改造,将 `ctx context.Context` 作为第一个参数。** + +##### 3.1. API 层 + +[api](./task-api.md) +[controller](./task-controller.md) +[middleware](./task-middleware.md) +[webhook](./task-webhook.md) + +##### 3.2. 应用层 (Application Services) + +[service](./task-service.md) + +##### 3.3. 领域层 (Domain Services) + +[domain](./task-domain.md) + +##### 3.4. 基础设施层 (Infrastructure) + +[repository](./task-repository.md) +[infra-other](./task-infra.md) + +--- + +#### 4. 日志调用点及方法内部逻辑改造 + +- [x] **遍历所有业务方法** (针对上述所有列出的组件的公共方法): + - [x] **定位旧日志**: 搜索所有对旧 `z.logger.*` 成员的调用。 + - [x] **改造方法入口** (对于非 Controller 方法): + 1. 在方法开始处,使用作为参数传入的 `ctx` (作为 `upstreamCtx`) 和组件自身持有的 `z.selfCtx`,调用 `logs.Trace`。 + - `newCtx, logger := logs.Trace(ctx, z.selfCtx, 'MethodName')` + 2. 将所有旧的 `z.logger.*(...)` 调用,替换为使用新获取的 `logger.*(...)`。 + - [x] **改造下游调用**: + 1. 在方法内部,当需要调用其他组件的方法时(如下游服务),**必须传递 `newCtx`**。 + - `err := z.downstreamService.DoSomething(newCtx, data)` diff --git a/design/provide-logger-with-mothed/task-middleware.md b/design/provide-logger-with-mothed/task-middleware.md new file mode 100644 index 0000000..ccd0b7f --- /dev/null +++ b/design/provide-logger-with-mothed/task-middleware.md @@ -0,0 +1,25 @@ +- **`internal/app/middleware/auth.go`** + - **中间件函数改造 (`AuthMiddleware`)**: + - [x] 在 `AuthMiddleware` 返回的 `echo.HandlerFunc` 内部,获取 `echo.Context` 的 `request.Context()` 作为 + `upstreamCtx`。 + - [x] 使用 `newCtx, logger := logs.Trace(upstreamCtx, context.Background(), "AuthMiddleware")` 来创建 `newCtx` + 和 `logger` 实例。 + - [x] 使用 `c.SetRequest(c.Request().WithContext(newCtx))` 将更新后的 `newCtx` 写入 `echo.Context`,以便后续处理链使用。 + - [x] 将所有对 `controller.SendErrorWithStatus` 的调用替换为 `controller.SendErrorWithAudit`。 + - [x] 确保 `controller.SendErrorWithAudit` 接收 `newCtx` 作为第一个参数,并提供适当的 `actionType`, + `description`, `targetResource`。 + - 例如,对于“请求未包含授权标头”的错误,`actionType` 可以是“认证失败”,`description` 是“请求未包含授权标头”, + `targetResource` 为 `nil`。 + - 对于“无效的Token”错误,`actionType` 可以是“认证失败”,`description` 是“无效的Token”,`targetResource` 是 + `tokenString`。 + - [x] 在 `AuthMiddleware` 内部,如果需要日志记录(例如 `tokenService.ParseToken` 失败或 `userRepo.FindByID` + 失败),使用新创建的 `logger` 实例进行日志输出。 + - [x] **关键**: 使用 `c.SetRequest(c.Request().WithContext(newCtx))` 将更新后的 `Context` 写回 `echo.Context` + ,以便传递给后续的中间件和 `Controller`。 +- **`internal/app/middleware/audit.go`** + - **改造动作**: + - [x] 检查并重构所有日志记录中间件。 + - [x] 中间件应该从请求的 `c.Request().Context()` 中提取 `upstreamCtx`。 + - [x] 使用 `logs.Trace` 或 `logs.AddFuncName` 创建新的 `Context` 和 `Logger`。 + - [x] **关键**: 使用 `c.SetRequest(c.Request().WithContext(newCtx))` 将更新后的 `Context` 写回 `echo.Context` + ,以便传递给后续的中间件和 `Controller`。 diff --git a/design/provide-logger-with-mothed/task-repository.md b/design/provide-logger-with-mothed/task-repository.md new file mode 100644 index 0000000..43592f6 --- /dev/null +++ b/design/provide-logger-with-mothed/task-repository.md @@ -0,0 +1,396 @@ +- **`internal/infra/repository/unit_of_work.go` (`repository.UnitOfWork` - `gormUnitOfWork` 实现)** + - **接口改造 (`UnitOfWork`)**: + - [x] 修改 `UnitOfWork` 接口中的 `ExecuteInTransaction` 方法签名,使其接收 `ctx context.Context` 作为第一个参数: + `ExecuteInTransaction(ctx context.Context, fn func(tx *gorm.DB) error) error`。 + - **结构体改造 (`gormUnitOfWork`)**: + - [x] 移除 `gormUnitOfWork` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormUnitOfWork`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,为 `gormUnitOfWork` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "GormUnitOfWork")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormUnitOfWork` 结构体的 `selfCtx` 成员。 + - **方法改造 (`ExecuteInTransaction`)**: + - [x] 修改方法签名,使其接收 `ctx context.Context` 作为第一个参数: + `(u *gormUnitOfWork) ExecuteInTransaction(ctx context.Context, fn func(tx *gorm.DB) error) error`。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, u.selfCtx, "ExecuteInTransaction")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `u.logger.Errorf` 的调用替换为 `logger.Errorf`。 + - [x] 在开启事务时,使用 `tx := u.db.WithContext(newCtx).Begin()`,确保事务 `tx` 携带了正确的上下文。 + +- **`internal/infra/repository/plan_repository.go` (`repository.PlanRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormPlanRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormPlanRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PlanRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormPlanRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/user_repository.go` (`repository.UserRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormUserRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormUserRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "UserRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormUserRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/device_repository.go` (`repository.DeviceRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormDeviceRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormDeviceRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "DeviceRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormDeviceRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/pig_pen_repository.go` (`repository.PigPenRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormPigPenRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormPigPenRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PigPenRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormPigPenRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/pig_farm_repository.go` (`repository.PigFarmRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormPigFarmRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormPigFarmRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PigFarmRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormPigFarmRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/pig_sick_repository.go` (`repository.PigSickLogRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormPigSickLogRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormPigSickLogRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PigSickLogRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormPigSickLogRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/pig_batch_repository.go` (`repository.PigBatchRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormPigBatchRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormPigBatchRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PigBatchRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormPigBatchRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/pig_trade_repository.go` (`repository.PigTradeRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormPigTradeRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormPigTradeRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PigTradeRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormPigTradeRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/sensor_data_repository.go` (`repository.SensorDataRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormSensorDataRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormSensorDataRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "SensorDataRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormSensorDataRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/notification_repository.go` (`repository.NotificationRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormNotificationRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormNotificationRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "NotificationRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormNotificationRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/pending_task_repository.go` (`repository.PendingTaskRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormPendingTaskRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormPendingTaskRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PendingTaskRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormPendingTaskRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/raw_material_repository.go` (`repository.RawMaterialRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormRawMaterialRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormRawMaterialRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "RawMaterialRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormRawMaterialRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/execution_log_repository.go` (`repository.ExecutionLogRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormExecutionLogRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormExecutionLogRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "ExecutionLogRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormExecutionLogRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/pig_batch_log_repository.go` (`repository.PigBatchLogRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormPigBatchLogRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormPigBatchLogRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PigBatchLogRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormPigBatchLogRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/medication_log_repository.go` (`repository.MedicationLogRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormMedicationLogRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormMedicationLogRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "MedicationLogRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormMedicationLogRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/area_controller_repository.go` (`repository.AreaControllerRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormAreaControllerRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormAreaControllerRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "AreaControllerRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormAreaControllerRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/device_template_repository.go` (`repository.DeviceTemplateRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormDeviceTemplateRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormDeviceTemplateRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "DeviceTemplateRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormDeviceTemplateRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/user_action_log_repository.go` (`repository.UserActionLogRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormUserActionLogRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormUserActionLogRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "UserActionLogRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormUserActionLogRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/pig_transfer_log_repository.go` (`repository.PigTransferLogRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormPigTransferLogRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormPigTransferLogRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PigTransferLogRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormPigTransferLogRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/device_command_log_repository.go` (`repository.DeviceCommandLogRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormDeviceCommandLogRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormDeviceCommandLogRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "DeviceCommandLogRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormDeviceCommandLogRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/pending_collection_repository.go` (`repository.PendingCollectionRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormPendingCollectionRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormPendingCollectionRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PendingCollectionRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormPendingCollectionRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/raw_material_repository.go` (`repository.RawMaterialRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormRawMaterialRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormRawMaterialRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "RawMaterialRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormRawMaterialRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/execution_log_repository.go` (`repository.ExecutionLogRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormExecutionLogRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormExecutionLogRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "ExecutionLogRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormExecutionLogRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/pig_batch_log_repository.go` (`repository.PigBatchLogRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormPigBatchLogRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormPigBatchLogRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PigBatchLogRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormPigBatchLogRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/medication_log_repository.go` (`repository.MedicationLogRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormMedicationLogRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormMedicationLogRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "MedicationLogRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormMedicationLogRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/area_controller_repository.go` (`repository.AreaControllerRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormAreaControllerRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormAreaControllerRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "AreaControllerRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormAreaControllerRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/device_template_repository.go` (`repository.DeviceTemplateRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormDeviceTemplateRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormDeviceTemplateRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "DeviceTemplateRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormDeviceTemplateRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/user_action_log_repository.go` (`repository.UserActionLogRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormUserActionLogRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormUserActionLogRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "UserActionLogRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormUserActionLogRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(newCtx)`。 + +- **`internal/infra/repository/pig_transfer_log_repository.go` (`repository.PigTransferLogRepository`)** + - **结构体改造**: + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewGormPigTransferLogRepository`)**: + - [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在函数内部,为 `gormPigTransferLogRepository` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PigTransferLogRepository")`。 + - [x] 将这个 `selfCtx` 赋值给 `gormPigTransferLogRepository` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有公共方法)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`。 + - [x] 确保所有对 `r.db` 和 `tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(new \ No newline at end of file diff --git a/design/provide-logger-with-mothed/task-service.md b/design/provide-logger-with-mothed/task-service.md new file mode 100644 index 0000000..9e5d1f4 --- /dev/null +++ b/design/provide-logger-with-mothed/task-service.md @@ -0,0 +1,113 @@ +- **`internal/app/service/pig_farm_service.go` (`service.PigFarmService`)** + - **结构体改造**: + - [x] 移除 `PigFarmService` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - [x] 移除 `PigFarmService` 结构体中的 `repo repository.PigFarmRepository` 成员,改为 + `repo repository.PigFarmRepository`。 + - **构造函数改造 (`NewPigFarmService`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,为 `PigFarmService` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PigFarmService")`。 + - [x] 将这个 `selfCtx` 赋值给 `PigFarmService` 结构体的 `selfCtx` 成员。 + - **公共方法改造 ( + 所有方法,例如 `CreatePigHouse`, `GetPigHouseByID`, `ListPigHouses`, `UpdatePigHouse`, `DeletePigHouse`, + `CreatePen`, `GetPenByID`, `ListPens`, `UpdatePen`, `DeletePen`, `UpdatePenStatus`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `s.logger.Errorf` 和 `s.logger.Infof` 的调用替换为 `logger.Errorf` 和 `logger.Infof`。 + - [x] 确保所有对 `s.repo` 的调用都将 `newCtx` 作为第一个参数传递。 + +- **`internal/app/service/pig_batch_service.go` (`service.PigBatchService`)** + - **结构体改造**: + - [x] 移除 `PigBatchService` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewPigBatchService`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,为 `PigBatchService` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PigBatchService")`。 + - [x] 将这个 `selfCtx` 赋值给 `PigBatchService` 结构体的 `selfCtx` 成员。 + - **公共方法改造 ( + 所有方法,例如 `CreatePigBatch`, `GetPigBatch`, `UpdatePigBatch`, `DeletePigBatch`, `ListPigBatches`, + `AssignEmptyPensToBatch`, `ReclassifyPenToNewBatch`, `RemoveEmptyPenFromBatch`, `MovePigsIntoPen`, `SellPigs`, + `BuyPigs`, `RecordSickPigs`, `RecordSickPigRecovery`, `RecordSickPigDeath`, `RecordSickPigCull`, `RecordDeath`, + `RecordCull`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `s.logger.Errorf` 和 `s.logger.Infof` 的调用替换为 `logger.Errorf` 和 `logger.Infof`。 + - [x] 确保所有对 `s.pigBatchRepo`, `s.pigBatchLogRepo`, `s.uow`, `s.transferSvc`, `s.tradeSvc`, `s.sickSvc` + 等依赖的调用都将 `newCtx` 作为第一个参数传递。 + +- **`internal/app/service/monitor_service.go` (`service.MonitorService`)** + - **结构体改造**: + - [x] 移除 `MonitorService` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewMonitorService`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,为 `MonitorService` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "MonitorService")`。 + - [x] 将这个 `selfCtx` 赋值给 `MonitorService` 结构体的 `selfCtx` 成员。 + - **公共方法改造 ( + 所有方法,例如 `ListSensorData`, `ListDeviceCommandLogs`, `ListPlanExecutionLogs`, `ListTaskExecutionLogs`, + `ListPendingCollections`, `ListUserActionLogs`, `ListRawMaterialPurchases`, `ListRawMaterialStockLogs`, + `ListFeedUsageRecords`, `ListMedicationLogs`, `ListPigBatchLogs`, `ListWeighingBatches`, `ListWeighingRecords`, + `ListPigTransferLogs`, `ListPigSickLogs`, `ListPigPurchases`, `ListPigSales`, `ListNotifications`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `s.logger.Errorf` 和 `s.logger.Infof` 的调用替换为 `logger.Errorf` 和 `logger.Infof`。 + - [x] 确保所有对 `s.repo` 的调用都将 `newCtx` 作为第一个参数传递。 + +- **`internal/app/service/device_service.go` (`service.DeviceService`)** + - **结构体改造**: + - [x] 移除 `DeviceService` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewDeviceService`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,为 `DeviceService` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "DeviceService")`。 + - [x] 将这个 `selfCtx` 赋值给 `DeviceService` 结构体的 `selfCtx` 成员。 + - **公共方法改造 ( + 所有方法,例如 `CreateDevice`, `GetDevice`, `ListDevices`, `UpdateDevice`, `DeleteDevice`, `ManualControl`, + `CreateAreaController`, `GetAreaController`, `ListAreaControllers`, `UpdateAreaController`, + `DeleteAreaController`, `CreateDeviceTemplate`, `GetDeviceTemplate`, `ListDeviceTemplates`, + `UpdateDeviceTemplate`, `DeleteDeviceTemplate`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `s.logger.Errorf`, `s.logger.Warnf`, `s.logger.Infof` 的调用替换为 `logger.Errorf`, + `logger.Warnf`, `logger.Infof`。 + - [x] 确保所有对 `s.repo`, `s.generalDeviceService` 等依赖的调用都将 `newCtx` 作为第一个参数传递。 + +- **`internal/app/service/plan_service.go` (`service.PlanService`)** + - **结构体改造**: + - [x] 移除 `PlanService` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewPlanService`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,为 `PlanService` 创建其专属的 `selfCtx`:`selfCtx := logs.AddCompName(ctx, "PlanService")`。 + - [x] 将这个 `selfCtx` 赋值给 `PlanService` 结构体的 `selfCtx` 成员。 + - **公共方法改造 ( + 所有方法,例如 `CreatePlan`, `GetPlanByID`, `ListPlans`, `UpdatePlan`, `DeletePlan`, `StartPlan`, `StopPlan`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `s.logger.Errorf` 和 `s.logger.Infof` 的调用替换为 `logger.Errorf` 和 `logger.Infof`。 + - [x] 确保所有对 `s.repo`, `s.planExecutionManager`, `s.analysisPlanTaskManager` 等依赖的调用都将 `newCtx` + 作为第一个参数传递。 + +- **`internal/app/service/user_service.go` (`service.UserService`)** + - **结构体改造**: + - [x] 移除 `UserService` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewUserService`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,为 `UserService` 创建其专属的 `selfCtx`:`selfCtx := logs.AddCompName(ctx, "UserService")`。 + - [x] 将这个 `selfCtx` 赋值给 `UserService` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (所有方法,例如 `CreateUser`, `Login`, `SendTestNotification`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `s.logger.Errorf` 和 `s.logger.Infof` 的调用替换为 `logger.Errorf` 和 `logger.Infof`。 + - [x] 确保所有对 `s.repo`, `s.tokenService`, `s.notifier` 等依赖的调用都将 `newCtx` 作为第一个参数传递。 \ No newline at end of file diff --git a/design/provide-logger-with-mothed/task-webhook.md b/design/provide-logger-with-mothed/task-webhook.md new file mode 100644 index 0000000..1042dbf --- /dev/null +++ b/design/provide-logger-with-mothed/task-webhook.md @@ -0,0 +1,39 @@ +- **`internal/app/webhook/chirp_stack.go` (`webhook.ChirpStackListener`)** + - **结构体改造**: + - [x] 移除 `ChirpStackListener` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewChirpStackListener`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,为 `ChirpStackListener` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "ChirpStackListener")`。 + - [x] 将这个 `selfCtx` 赋值给 `ChirpStackListener` 结构体的 `selfCtx` 成员。 + - **公共方法改造 (`Handler`)**: + - [x] `Handler` 方法返回 `http.HandlerFunc`。在返回的 `http.HandlerFunc` 内部,获取 `r.Context()` 作为 + `upstreamCtx`。 + - [x] 在 `go c.handler(b, event)` 调用之前,将 `upstreamCtx` 传递给 `c.handler` 方法,即 + `go c.handler(upstreamCtx, b, event)`。 + - [x] 将所有对 `c.logger.Errorf` 的调用替换为 `logger.Errorf` (在 `handler` 方法中处理)。 + - **内部辅助方法改造 (`handler`, `handleUpEvent`, `handleStatusEvent`, `handleAckEvent`, `handleLogEvent`, + `handleJoinEvent`, `handleTxAckEvent`, `handleLocationEvent`, `handleIntegrationEvent`, `recordSensorData`)**: + - [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。 + - [x] 在每个方法入口处,使用 `newCtx, logger := logs.Trace(ctx, c.selfCtx, "MethodName")` 获取新的 + `context.Context` 和 `logger` 实例。 + - [x] 将所有对 `c.logger.Errorf`, `c.logger.Infof`, `c.logger.Warnf` 的调用替换为 `logger.Errorf`, + `logger.Infof`, `logger.Warnf`。 + - [x] 确保所有对 `c.sensorDataRepo`, `c.deviceRepo`, `c.areaControllerRepo`, `c.deviceCommandLogRepo`, + `c.pendingCollectionRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。 +- **`internal/app/webhook/placeholder_listener.go` (`webhook.PlaceholderListener`)** + - **结构体改造**: + - [x] 移除 `PlaceholderListener` 结构体中的 `logger *logs.Logger` 成员。 + - [x] 新增 `selfCtx context.Context` 成员。 + - **构造函数改造 (`NewPlaceholderListener`)**: + - [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`。 + - [x] 在函数内部,为 `PlaceholderListener` 创建其专属的 `selfCtx`: + `selfCtx := logs.AddCompName(ctx, "PlaceholderListener")`。 + - [x] 将这个 `selfCtx` 赋值给 `PlaceholderListener` 结构体的 `selfCtx` 成员。 + - [x] 使用 `newCtx, logger := logs.Trace(ctx, selfCtx, "NewPlaceholderListener")` 获取 `logger` 实例,并替换 + `logger.Info` 调用。 + - **公共方法改造 (`Handler`)**: + - [x] 在 `Handler` 方法返回的 `http.HandlerFunc` 内部,获取 `r.Context()` 作为 `upstreamCtx`。 + - [x] 使用 `newCtx, logger := logs.Trace(upstreamCtx, p.selfCtx, "Handler")` 获取 `logger` 实例。 + - [x] 将所有对 `p.logger.Warn` 的调用替换为 `logger.Warn`。 \ No newline at end of file diff --git a/docs/docs.go b/docs/docs.go index 65016db..4b50bd2 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -998,6 +998,7 @@ const docTemplate = `{ }, { "enum": [ + 7, -1, 0, 1, @@ -1007,12 +1008,12 @@ const docTemplate = `{ 5, -1, 5, - 6, - 7 + 6 ], "type": "integer", "format": "int32", "x-enum-varnames": [ + "_numLevels", "DebugLevel", "InfoLevel", "WarnLevel", @@ -1022,8 +1023,7 @@ const docTemplate = `{ "FatalLevel", "_minLevel", "_maxLevel", - "InvalidLevel", - "_numLevels" + "InvalidLevel" ], "name": "level", "in": "query" @@ -6859,6 +6859,7 @@ const docTemplate = `{ "type": "integer", "format": "int32", "enum": [ + 7, -1, 0, 1, @@ -6868,10 +6869,10 @@ const docTemplate = `{ 5, -1, 5, - 6, - 7 + 6 ], "x-enum-varnames": [ + "_numLevels", "DebugLevel", "InfoLevel", "WarnLevel", @@ -6881,8 +6882,7 @@ const docTemplate = `{ "FatalLevel", "_minLevel", "_maxLevel", - "InvalidLevel", - "_numLevels" + "InvalidLevel" ] } }, diff --git a/docs/swagger.json b/docs/swagger.json index 3351636..c6061bd 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -990,6 +990,7 @@ }, { "enum": [ + 7, -1, 0, 1, @@ -999,12 +1000,12 @@ 5, -1, 5, - 6, - 7 + 6 ], "type": "integer", "format": "int32", "x-enum-varnames": [ + "_numLevels", "DebugLevel", "InfoLevel", "WarnLevel", @@ -1014,8 +1015,7 @@ "FatalLevel", "_minLevel", "_maxLevel", - "InvalidLevel", - "_numLevels" + "InvalidLevel" ], "name": "level", "in": "query" @@ -6851,6 +6851,7 @@ "type": "integer", "format": "int32", "enum": [ + 7, -1, 0, 1, @@ -6860,10 +6861,10 @@ 5, -1, 5, - 6, - 7 + 6 ], "x-enum-varnames": [ + "_numLevels", "DebugLevel", "InfoLevel", "WarnLevel", @@ -6873,8 +6874,7 @@ "FatalLevel", "_minLevel", "_maxLevel", - "InvalidLevel", - "_numLevels" + "InvalidLevel" ] } }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index c37a73a..c143ecf 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1950,6 +1950,7 @@ definitions: - PlanTypeFilterSystem zapcore.Level: enum: + - 7 - -1 - 0 - 1 @@ -1960,10 +1961,10 @@ definitions: - -1 - 5 - 6 - - 7 format: int32 type: integer x-enum-varnames: + - _numLevels - DebugLevel - InfoLevel - WarnLevel @@ -1974,7 +1975,6 @@ definitions: - _minLevel - _maxLevel - InvalidLevel - - _numLevels info: contact: email: divano@example.com @@ -2546,6 +2546,7 @@ paths: name: end_time type: string - enum: + - 7 - -1 - 0 - 1 @@ -2556,12 +2557,12 @@ paths: - -1 - 5 - 6 - - 7 format: int32 in: query name: level type: integer x-enum-varnames: + - _numLevels - DebugLevel - InfoLevel - WarnLevel @@ -2572,7 +2573,6 @@ paths: - _minLevel - _maxLevel - InvalidLevel - - _numLevels - enum: - 邮件 - 企业微信 diff --git a/internal/app/api/api.go b/internal/app/api/api.go index 404c190..c2f7165 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" @@ -40,10 +39,10 @@ import ( // API 结构体定义了 HTTP 服务器及其依赖 type API struct { echo *echo.Echo // Echo 引擎实例,用于处理 HTTP 请求 - logger *logs.Logger // 日志记录器,用于输出日志信息 + 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 // 用户控制器实例 @@ -59,7 +58,7 @@ type API struct { // NewAPI 创建并返回一个新的 API 实例 // 负责初始化 Echo 引擎、设置全局中间件,并注入所有必要的依赖。 func NewAPI(cfg config.ServerConfig, - logger *logs.Logger, + ctx context.Context, userRepo repository.UserRepository, pigFarmService service.PigFarmService, pigBatchService service.PigBatchService, @@ -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 引擎实例 @@ -82,26 +81,27 @@ func NewAPI(cfg config.ServerConfig, e.Use(middleware.Recover()) // 初始化 API 结构体 + baseCtx := context.Background() api := &API{ - echo: e, - logger: logger, - 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(userService, logger), + userController: user.NewController(logs.AddCompName(baseCtx, "UserController"), userService), // 在 NewAPI 中初始化设备控制器,并将其作为 API 结构体的成员 - deviceController: device.NewController(deviceService, logger), + deviceController: device.NewController(logs.AddCompName(baseCtx, "DeviceController"), deviceService), // 在 NewAPI 中初始化计划控制器,并将其作为 API 结构体的成员 - planController: plan.NewController(logger, planService), + planController: plan.NewController(logs.AddCompName(baseCtx, "PlanController"), planService), // 在 NewAPI 中初始化猪场管理控制器 - pigFarmController: management.NewPigFarmController(logger, pigFarmService), + pigFarmController: management.NewPigFarmController(logs.AddCompName(baseCtx, "PigFarmController"), pigFarmService), // 在 NewAPI 中初始化猪群控制器 - pigBatchController: management.NewPigBatchController(logger, pigBatchService), + pigBatchController: management.NewPigBatchController(logs.AddCompName(baseCtx, "PigBatchController"), pigBatchService), // 在 NewAPI 中初始化数据监控控制器 - monitorController: monitor.NewController(monitorService, logger), + monitorController: monitor.NewController(logs.AddCompName(baseCtx, "MonitorController"), monitorService), } api.setupRoutes() // 设置所有路由 @@ -112,6 +112,7 @@ func NewAPI(cfg config.ServerConfig, // 接收一个地址字符串 (例如 ":8080"),并在一个新的 goroutine 中启动服务器, // 以便主线程可以继续执行其他任务(例如监听操作系统信号)。 func (a *API) Start() { + logger := logs.TraceLogger(a.Ctx, a.Ctx, "Start") // 构建监听地址字符串 addr := fmt.Sprintf(":%d", a.config.Port) @@ -126,18 +127,19 @@ func (a *API) Start() { go func() { // 启动服务器,并检查错误。http.ErrServerClosed 是正常关闭时的错误,无需处理。 if err := a.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { - a.logger.Fatalf("HTTP 服务器监听失败: %s", err) + logger.Fatalf("HTTP 服务器监听失败: %s", err) } }() // 记录服务器已启动的信息 - a.logger.Infof("HTTP 服务器正在监听 %s", addr) + logger.Infof("HTTP 服务器正在监听 %s", addr) } // Stop 优雅地停止 HTTP 服务器 // 在停止服务器时,会给一个超时时间,确保正在处理的请求能够完成。 func (a *API) Stop() { + logger := logs.TraceLogger(a.Ctx, a.Ctx, "Stop") // 记录服务器正在关闭的信息 - a.logger.Info("正在关闭 HTTP 服务器...") + logger.Info("正在关闭 HTTP 服务器...") // 创建一个带有 5 秒超时时间的上下文 // 在此时间内,服务器会尝试完成所有活跃的连接。 @@ -148,8 +150,8 @@ func (a *API) Stop() { // 如果在超时时间内未能关闭,Shutdown 会返回错误。 if err := a.httpServer.Shutdown(ctx); err != nil { // 如果关闭失败,记录致命错误并退出 - a.logger.Fatalf("HTTP 服务器关闭失败: %s", err) + logger.Fatalf("HTTP 服务器关闭失败: %s", err) } // 记录服务器已停止的信息 - a.logger.Info("HTTP 服务器已停止。") + logger.Info("HTTP 服务器已停止。") } diff --git a/internal/app/api/router.go b/internal/app/api/router.go index 53a4127..bf94dbd 100644 --- a/internal/app/api/router.go +++ b/internal/app/api/router.go @@ -1,10 +1,13 @@ package api import ( + "context" "net/http" "net/http/pprof" "git.huangwc.com/pig/pig-farm-controller/internal/app/middleware" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "github.com/labstack/echo/v4" echoSwagger "github.com/swaggo/echo-swagger" ) @@ -12,7 +15,7 @@ import ( // setupRoutes 设置所有 API 路由 // 在此方法中,使用已初始化的控制器实例将其路由注册到 Echo 引擎中。 func (a *API) setupRoutes() { - a.logger.Info("开始初始化所有 API 路由") + logger := logs.TraceLogger(a.Ctx, a.Ctx, "SetupRoutes") // --- Public Routes --- // 这些路由不需要身份验证 @@ -20,7 +23,7 @@ func (a *API) setupRoutes() { // 用户注册和登录 a.echo.POST("/api/v1/users", a.userController.CreateUser) // 注册新用户 a.echo.POST("/api/v1/users/login", a.userController.Login) // 用户登录 - a.logger.Debug("公开接口注册成功:用户注册、登录") + logger.Debug("公开接口注册成功:用户注册、登录") // 注册 pprof 路由 pprofGroup := a.echo.Group("/debug/pprof") @@ -38,28 +41,28 @@ func (a *API) setupRoutes() { pprofGroup.GET("/mutex", echo.WrapHandler(pprof.Handler("mutex"))) // pprof 互斥锁 pprofGroup.GET("/threadcreate", echo.WrapHandler(pprof.Handler("threadcreate"))) } - a.logger.Debug("pprof 接口注册成功") + logger.Debug("pprof 接口注册成功") // 上行事件监听路由 a.echo.POST("/upstream", echo.WrapHandler(a.listenHandler.Handler())) // 处理设备上行事件 - a.logger.Debug("上行事件监听接口注册成功") + logger.Debug("上行事件监听接口注册成功") // 添加 Swagger UI 路由, Swagger UI可在 /swagger/index.html 上找到 a.echo.GET("/swagger/*any", echoSwagger.WrapHandler) // Swagger UI 接口 - a.logger.Debug("Swagger UI 接口注册成功") + logger.Debug("Swagger UI 接口注册成功") // --- Authenticated Routes --- // 所有在此注册的路由都需要通过 JWT 身份验证 authGroup := a.echo.Group("/api/v1") - authGroup.Use(middleware.AuthMiddleware(a.tokenService, a.userRepo)) // 1. 身份认证中间件 - authGroup.Use(middleware.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") { userGroup.POST("/:id/notifications/test", a.userController.SendTestNotification) } - a.logger.Debug("用户相关接口注册成功 (需要认证和审计)") + logger.Debug("用户相关接口注册成功 (需要认证和审计)") // 设备相关路由组 deviceGroup := authGroup.Group("/devices") @@ -71,7 +74,7 @@ func (a *API) setupRoutes() { deviceGroup.DELETE("/:id", a.deviceController.DeleteDevice) // 删除设备 deviceGroup.POST("/manual-control/:id", a.deviceController.ManualControl) // 手动控制设备 } - a.logger.Debug("设备相关接口注册成功 (需要认证和审计)") + logger.Debug("设备相关接口注册成功 (需要认证和审计)") // 区域主控相关路由组 areaControllerGroup := authGroup.Group("/area-controllers") @@ -82,7 +85,7 @@ func (a *API) setupRoutes() { areaControllerGroup.PUT("/:id", a.deviceController.UpdateAreaController) // 更新区域主控 areaControllerGroup.DELETE("/:id", a.deviceController.DeleteAreaController) // 删除区域主控 } - a.logger.Debug("区域主控相关接口注册成功 (需要认证和审计)") + logger.Debug("区域主控相关接口注册成功 (需要认证和审计)") // 设备模板相关路由组 deviceTemplateGroup := authGroup.Group("/device-templates") @@ -93,7 +96,7 @@ func (a *API) setupRoutes() { deviceTemplateGroup.PUT("/:id", a.deviceController.UpdateDeviceTemplate) // 更新设备模板 deviceTemplateGroup.DELETE("/:id", a.deviceController.DeleteDeviceTemplate) // 删除设备模板 } - a.logger.Debug("设备模板相关接口注册成功 (需要认证和审计)") + logger.Debug("设备模板相关接口注册成功 (需要认证和审计)") // 计划相关路由组 planGroup := authGroup.Group("/plans") @@ -106,7 +109,7 @@ func (a *API) setupRoutes() { planGroup.POST("/:id/start", a.planController.StartPlan) // 启动计划 planGroup.POST("/:id/stop", a.planController.StopPlan) // 停止计划 } - a.logger.Debug("计划相关接口注册成功 (需要认证和审计)") + logger.Debug("计划相关接口注册成功 (需要认证和审计)") // 猪舍相关路由组 pigHouseGroup := authGroup.Group("/pig-houses") @@ -117,7 +120,7 @@ func (a *API) setupRoutes() { pigHouseGroup.PUT("/:id", a.pigFarmController.UpdatePigHouse) // 更新猪舍 pigHouseGroup.DELETE("/:id", a.pigFarmController.DeletePigHouse) // 删除猪舍 } - a.logger.Debug("猪舍相关接口注册成功 (需要认证和审计)") + logger.Debug("猪舍相关接口注册成功 (需要认证和审计)") // 猪圈相关路由组 penGroup := authGroup.Group("/pens") @@ -129,7 +132,7 @@ func (a *API) setupRoutes() { penGroup.DELETE("/:id", a.pigFarmController.DeletePen) // 删除猪圈 penGroup.PUT("/:id/status", a.pigFarmController.UpdatePenStatus) // 更新猪圈状态 } - a.logger.Debug("猪圈相关接口注册成功 (需要认证和审计)") + logger.Debug("猪圈相关接口注册成功 (需要认证和审计)") // 猪群相关路由组 pigBatchGroup := authGroup.Group("/pig-batches") @@ -154,7 +157,7 @@ func (a *API) setupRoutes() { pigBatchGroup.POST("/record-death/:id", a.pigBatchController.RecordDeath) // 记录正常猪只死亡事件 pigBatchGroup.POST("/record-cull/:id", a.pigBatchController.RecordCull) // 记录正常猪只淘汰事件 } - a.logger.Debug("猪群相关接口注册成功 (需要认证和审计)") + logger.Debug("猪群相关接口注册成功 (需要认证和审计)") // 数据监控相关路由组 monitorGroup := authGroup.Group("/monitor") @@ -178,8 +181,8 @@ func (a *API) setupRoutes() { monitorGroup.GET("/pig-sales", a.monitorController.ListPigSales) monitorGroup.GET("/notifications", a.monitorController.ListNotifications) } - a.logger.Debug("数据监控相关接口注册成功 (需要认证和审计)") + logger.Debug("数据监控相关接口注册成功 (需要认证和审计)") } - a.logger.Debug("所有接口注册成功") + logger.Debug("所有接口注册成功") } diff --git a/internal/app/controller/auth_utils.go b/internal/app/controller/auth_utils.go index aa8f447..25d28b8 100644 --- a/internal/app/controller/auth_utils.go +++ b/internal/app/controller/auth_utils.go @@ -4,6 +4,7 @@ import ( "errors" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "github.com/labstack/echo/v4" ) diff --git a/internal/app/controller/device/device_controller.go b/internal/app/controller/device/device_controller.go index e3eca11..451b22b 100644 --- a/internal/app/controller/device/device_controller.go +++ b/internal/app/controller/device/device_controller.go @@ -1,6 +1,7 @@ package device import ( + "context" "errors" "strconv" @@ -8,24 +9,25 @@ import ( "git.huangwc.com/pig/pig-farm-controller/internal/app/dto" "git.huangwc.com/pig/pig-farm-controller/internal/app/service" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "github.com/labstack/echo/v4" "gorm.io/gorm" ) // Controller 设备控制器,封装了所有与设备和区域主控相关的业务逻辑 type Controller struct { + ctx context.Context deviceService service.DeviceService - logger *logs.Logger } // NewController 创建一个新的设备控制器实例 func NewController( + ctx context.Context, deviceService service.DeviceService, - logger *logs.Logger, ) *Controller { return &Controller{ + ctx: ctx, deviceService: deviceService, - logger: logger, } } @@ -42,20 +44,22 @@ func NewController( // @Success 200 {object} controller.Response{data=dto.DeviceResponse} // @Router /api/v1/devices [post] func (c *Controller) CreateDevice(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "CreateDevice") + const actionType = "创建设备" var req dto.CreateDeviceRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) } - resp, err := c.deviceService.CreateDevice(&req) + resp, err := c.deviceService.CreateDevice(reqCtx, &req) if err != nil { - c.logger.Errorf("%s: 服务层创建失败: %v", actionType, err) + logger.Errorf("%s: 服务层创建失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "创建设备失败: "+err.Error(), actionType, "服务层创建失败", req) } - c.logger.Infof("%s: 设备创建成功, ID: %d", actionType, resp.ID) + logger.Infof("%s: 设备创建成功, ID: %d", actionType, resp.ID) return controller.SendSuccessWithAudit(ctx, controller.CodeCreated, "设备创建成功", resp, actionType, "设备创建成功", resp) } @@ -69,26 +73,28 @@ func (c *Controller) CreateDevice(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.DeviceResponse} // @Router /api/v1/devices/{id} [get] func (c *Controller) GetDevice(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "GetDevice") + const actionType = "获取设备" deviceID := ctx.Param("id") id, err := strconv.ParseUint(deviceID, 10, 64) if err != nil { - c.logger.Errorf("%s: 无效的ID: %s", actionType, deviceID) + logger.Errorf("%s: 无效的ID: %s", actionType, deviceID) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID: "+deviceID, actionType, "无效的ID", deviceID) } - resp, err := c.deviceService.GetDevice(uint(id)) + resp, err := c.deviceService.GetDevice(reqCtx, uint(id)) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - c.logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID) + logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID) return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "设备未找到", actionType, "设备不存在", deviceID) } - c.logger.Errorf("%s: 服务层获取失败: %v, ID: %s", actionType, err, deviceID) + logger.Errorf("%s: 服务层获取失败: %v, ID: %s", actionType, err, deviceID) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备信息失败: "+err.Error(), actionType, "服务层获取失败", deviceID) } - c.logger.Infof("%s: 获取设备信息成功, ID: %d", actionType, resp.ID) + logger.Infof("%s: 获取设备信息成功, ID: %d", actionType, resp.ID) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取设备信息成功", resp, actionType, "获取设备信息成功", resp) } @@ -101,14 +107,16 @@ func (c *Controller) GetDevice(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=[]dto.DeviceResponse} // @Router /api/v1/devices [get] func (c *Controller) ListDevices(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListDevices") + const actionType = "获取设备列表" - resp, err := c.deviceService.ListDevices() + resp, err := c.deviceService.ListDevices(reqCtx) if err != nil { - c.logger.Errorf("%s: 服务层获取列表失败: %v", actionType, err) + logger.Errorf("%s: 服务层获取列表失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备列表失败: "+err.Error(), actionType, "服务层获取列表失败", nil) } - c.logger.Infof("%s: 获取设备列表成功, 数量: %d", actionType, len(resp)) + logger.Infof("%s: 获取设备列表成功, 数量: %d", actionType, len(resp)) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取设备列表成功", resp, actionType, "获取设备列表成功", resp) } @@ -124,32 +132,34 @@ func (c *Controller) ListDevices(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.DeviceResponse} // @Router /api/v1/devices/{id} [put] func (c *Controller) UpdateDevice(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "UpdateDevice") + const actionType = "更新设备" deviceID := ctx.Param("id") var req dto.UpdateDeviceRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) } id, err := strconv.ParseUint(deviceID, 10, 64) if err != nil { - c.logger.Errorf("%s: 无效的ID: %s", actionType, deviceID) + logger.Errorf("%s: 无效的ID: %s", actionType, deviceID) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID: "+deviceID, actionType, "无效的ID", deviceID) } - resp, err := c.deviceService.UpdateDevice(uint(id), &req) + resp, err := c.deviceService.UpdateDevice(reqCtx, uint(id), &req) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - c.logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID) + logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID) return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "设备未找到", actionType, "设备不存在", deviceID) } - c.logger.Errorf("%s: 服务层更新失败: %v, ID: %s", actionType, err, deviceID) + logger.Errorf("%s: 服务层更新失败: %v, ID: %s", actionType, err, deviceID) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新设备失败: "+err.Error(), actionType, "服务层更新失败", deviceID) } - c.logger.Infof("%s: 设备更新成功, ID: %d", actionType, resp.ID) + logger.Infof("%s: 设备更新成功, ID: %d", actionType, resp.ID) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "设备更新成功", resp, actionType, "设备更新成功", resp) } @@ -163,31 +173,33 @@ func (c *Controller) UpdateDevice(ctx echo.Context) error { // @Success 200 {object} controller.Response // @Router /api/v1/devices/{id} [delete] func (c *Controller) DeleteDevice(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "DeleteDevice") + const actionType = "删除设备" deviceID := ctx.Param("id") id, err := strconv.ParseUint(deviceID, 10, 64) if err != nil { - c.logger.Errorf("%s: 无效的ID: %s", actionType, deviceID) + logger.Errorf("%s: 无效的ID: %s", actionType, deviceID) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID: "+deviceID, actionType, "无效的ID", deviceID) } - if err := c.deviceService.DeleteDevice(uint(id)); err != nil { + if err := c.deviceService.DeleteDevice(reqCtx, uint(id)); err != nil { switch { case errors.Is(err, gorm.ErrRecordNotFound): - c.logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID) + logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID) return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "设备未找到", actionType, "设备不存在", deviceID) case errors.Is(err, service.ErrDeviceInUse): - c.logger.Warnf("%s: 尝试删除正在被使用的设备, ID: %s", actionType, deviceID) + logger.Warnf("%s: 尝试删除正在被使用的设备, ID: %s", actionType, deviceID) // 返回 409 Conflict 状态码,表示请求与服务器当前状态冲突 return controller.SendErrorWithAudit(ctx, controller.CodeConflict, err.Error(), actionType, "设备正在被使用", deviceID) default: - c.logger.Errorf("%s: 服务层删除失败: %v, ID: %s", actionType, err, deviceID) + logger.Errorf("%s: 服务层删除失败: %v, ID: %s", actionType, err, deviceID) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除设备失败: "+err.Error(), actionType, "服务层删除失败", deviceID) } } - c.logger.Infof("%s: 设备删除成功, ID: %s", actionType, deviceID) + logger.Infof("%s: 设备删除成功, ID: %s", actionType, deviceID) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "设备删除成功", nil, actionType, "设备删除成功", deviceID) } @@ -203,27 +215,29 @@ func (c *Controller) DeleteDevice(ctx echo.Context) error { // @Success 200 {object} controller.Response // @Router /api/v1/devices/manual-control/{id} [post] func (c *Controller) ManualControl(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ManualControlDevice") + const actionType = "手动控制设备" deviceID := ctx.Param("id") var req dto.ManualControlDeviceRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) } id, err := strconv.ParseUint(deviceID, 10, 64) if err != nil { - c.logger.Errorf("%s: 无效的ID: %s", actionType, deviceID) + logger.Errorf("%s: 无效的ID: %s", actionType, deviceID) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID: "+deviceID, actionType, "无效的ID", deviceID) } - if err := c.deviceService.ManualControl(uint(id), &req); err != nil { + if err := c.deviceService.ManualControl(reqCtx, uint(id), &req); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - c.logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID) + logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID) return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "设备未找到", actionType, "设备不存在", deviceID) } - c.logger.Errorf("%s: 服务层手动控制失败: %v, ID: %s", actionType, err, deviceID) + logger.Errorf("%s: 服务层手动控制失败: %v, ID: %s", actionType, err, deviceID) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "手动控制失败: "+err.Error(), actionType, "服务层手动控制失败", deviceID) } @@ -243,20 +257,22 @@ func (c *Controller) ManualControl(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.AreaControllerResponse} // @Router /api/v1/area-controllers [post] func (c *Controller) CreateAreaController(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "CreateAreaController") + const actionType = "创建区域主控" var req dto.CreateAreaControllerRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) } - resp, err := c.deviceService.CreateAreaController(&req) + resp, err := c.deviceService.CreateAreaController(reqCtx, &req) if err != nil { - c.logger.Errorf("%s: 服务层创建失败: %v", actionType, err) + logger.Errorf("%s: 服务层创建失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "创建区域主控失败: "+err.Error(), actionType, "服务层创建失败", req) } - c.logger.Infof("%s: 区域主控创建成功, ID: %d", actionType, resp.ID) + logger.Infof("%s: 区域主控创建成功, ID: %d", actionType, resp.ID) return controller.SendSuccessWithAudit(ctx, controller.CodeCreated, "区域主控创建成功", resp, actionType, "区域主控创建成功", resp) } @@ -270,26 +286,28 @@ func (c *Controller) CreateAreaController(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.AreaControllerResponse} // @Router /api/v1/area-controllers/{id} [get] func (c *Controller) GetAreaController(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "GetAreaController") + const actionType = "获取区域主控" acID := ctx.Param("id") id, err := strconv.ParseUint(acID, 10, 64) if err != nil { - c.logger.Errorf("%s: 无效的ID: %s", actionType, acID) + logger.Errorf("%s: 无效的ID: %s", actionType, acID) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID: "+acID, actionType, "无效的ID", acID) } - resp, err := c.deviceService.GetAreaController(uint(id)) + resp, err := c.deviceService.GetAreaController(reqCtx, uint(id)) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - c.logger.Warnf("%s: 区域主控不存在, ID: %s", actionType, acID) + logger.Warnf("%s: 区域主控不存在, ID: %s", actionType, acID) return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "区域主控未找到", actionType, "区域主控不存在", acID) } - c.logger.Errorf("%s: 服务层获取失败: %v, ID: %s", actionType, err, acID) + logger.Errorf("%s: 服务层获取失败: %v, ID: %s", actionType, err, acID) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取区域主控信息失败: "+err.Error(), actionType, "服务层获取失败", acID) } - c.logger.Infof("%s: 获取区域主控信息成功, ID: %d", actionType, resp.ID) + logger.Infof("%s: 获取区域主控信息成功, ID: %d", actionType, resp.ID) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取区域主控信息成功", resp, actionType, "获取区域主控信息成功", resp) } @@ -302,14 +320,16 @@ func (c *Controller) GetAreaController(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=[]dto.AreaControllerResponse} // @Router /api/v1/area-controllers [get] func (c *Controller) ListAreaControllers(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListAreaControllers") + const actionType = "获取区域主控列表" - resp, err := c.deviceService.ListAreaControllers() + resp, err := c.deviceService.ListAreaControllers(reqCtx) if err != nil { - c.logger.Errorf("%s: 服务层获取列表失败: %v", actionType, err) + logger.Errorf("%s: 服务层获取列表失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取区域主控列表失败: "+err.Error(), actionType, "服务层获取列表失败", nil) } - c.logger.Infof("%s: 获取区域主控列表成功, 数量: %d", actionType, len(resp)) + logger.Infof("%s: 获取区域主控列表成功, 数量: %d", actionType, len(resp)) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取区域主控列表成功", resp, actionType, "获取区域主控列表成功", resp) } @@ -325,31 +345,33 @@ func (c *Controller) ListAreaControllers(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.AreaControllerResponse} // @Router /api/v1/area-controllers/{id} [put] func (c *Controller) UpdateAreaController(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "UpdateAreaController") + const actionType = "更新区域主控" acID := ctx.Param("id") var req dto.UpdateAreaControllerRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) } id, err := strconv.ParseUint(acID, 10, 64) if err != nil { - c.logger.Errorf("%s: 无效的ID: %s", actionType, acID) + logger.Errorf("%s: 无效的ID: %s", actionType, acID) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID: "+acID, actionType, "无效的ID", acID) } - resp, err := c.deviceService.UpdateAreaController(uint(id), &req) + resp, err := c.deviceService.UpdateAreaController(reqCtx, uint(id), &req) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - c.logger.Warnf("%s: 区域主控不存在, ID: %s", actionType, acID) + logger.Warnf("%s: 区域主控不存在, ID: %s", actionType, acID) return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "区域主控未找到", actionType, "区域主控不存在", acID) } - c.logger.Errorf("%s: 服务层更新失败: %v, ID: %s", actionType, err, acID) + logger.Errorf("%s: 服务层更新失败: %v, ID: %s", actionType, err, acID) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新区域主控失败: "+err.Error(), actionType, "服务层更新失败", acID) } - c.logger.Infof("%s: 区域主控更新成功, ID: %d", actionType, resp.ID) + logger.Infof("%s: 区域主控更新成功, ID: %d", actionType, resp.ID) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "区域主控更新成功", resp, actionType, "区域主控更新成功", resp) } @@ -363,30 +385,32 @@ func (c *Controller) UpdateAreaController(ctx echo.Context) error { // @Success 200 {object} controller.Response // @Router /api/v1/area-controllers/{id} [delete] func (c *Controller) DeleteAreaController(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "DeleteAreaController") + const actionType = "删除区域主控" acID := ctx.Param("id") id, err := strconv.ParseUint(acID, 10, 64) if err != nil { - c.logger.Errorf("%s: 无效的ID: %s", actionType, acID) + logger.Errorf("%s: 无效的ID: %s", actionType, acID) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID: "+acID, actionType, "无效的ID", acID) } - if err := c.deviceService.DeleteAreaController(uint(id)); err != nil { + if err := c.deviceService.DeleteAreaController(reqCtx, uint(id)); err != nil { switch { case errors.Is(err, gorm.ErrRecordNotFound): - c.logger.Warnf("%s: 区域主控不存在, ID: %s", actionType, acID) + logger.Warnf("%s: 区域主控不存在, ID: %s", actionType, acID) return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "区域主控未找到", actionType, "区域主控不存在", acID) case errors.Is(err, service.ErrAreaControllerInUse): - c.logger.Warnf("%s: 尝试删除正在被使用的主控, ID: %s", actionType, acID) + logger.Warnf("%s: 尝试删除正在被使用的主控, ID: %s", actionType, acID) return controller.SendErrorWithAudit(ctx, controller.CodeConflict, err.Error(), actionType, "主控正在被使用", acID) default: - c.logger.Errorf("%s: 服务层删除失败: %v, ID: %s", actionType, err, acID) + logger.Errorf("%s: 服务层删除失败: %v, ID: %s", actionType, err, acID) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除区域主控失败: "+err.Error(), actionType, "服务层删除失败", acID) } } - c.logger.Infof("%s: 区域主控删除成功, ID: %s", actionType, acID) + logger.Infof("%s: 区域主控删除成功, ID: %s", actionType, acID) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "区域主控删除成功", nil, actionType, "区域主控删除成功", acID) } @@ -403,20 +427,22 @@ func (c *Controller) DeleteAreaController(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.DeviceTemplateResponse} // @Router /api/v1/device-templates [post] func (c *Controller) CreateDeviceTemplate(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "CreateDeviceTemplate") + const actionType = "创建设备模板" var req dto.CreateDeviceTemplateRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) } - resp, err := c.deviceService.CreateDeviceTemplate(&req) + resp, err := c.deviceService.CreateDeviceTemplate(reqCtx, &req) if err != nil { - c.logger.Errorf("%s: 服务层创建失败: %v", actionType, err) + logger.Errorf("%s: 服务层创建失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "创建设备模板失败: "+err.Error(), actionType, "服务层创建失败", req) } - c.logger.Infof("%s: 设备模板创建成功, ID: %d", actionType, resp.ID) + logger.Infof("%s: 设备模板创建成功, ID: %d", actionType, resp.ID) return controller.SendSuccessWithAudit(ctx, controller.CodeCreated, "设备模板创建成功", resp, actionType, "设备模板创建成功", resp) } @@ -430,26 +456,28 @@ func (c *Controller) CreateDeviceTemplate(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.DeviceTemplateResponse} // @Router /api/v1/device-templates/{id} [get] func (c *Controller) GetDeviceTemplate(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "GetDeviceTemplate") + const actionType = "获取设备模板" dtID := ctx.Param("id") id, err := strconv.ParseUint(dtID, 10, 64) if err != nil { - c.logger.Errorf("%s: 无效的ID: %s", actionType, dtID) + logger.Errorf("%s: 无效的ID: %s", actionType, dtID) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID: "+dtID, actionType, "无效的ID", dtID) } - resp, err := c.deviceService.GetDeviceTemplate(uint(id)) + resp, err := c.deviceService.GetDeviceTemplate(reqCtx, uint(id)) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - c.logger.Warnf("%s: 设备模板不存在, ID: %s", actionType, dtID) + logger.Warnf("%s: 设备模板不存在, ID: %s", actionType, dtID) return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "设备模板未找到", actionType, "设备模板不存在", dtID) } - c.logger.Errorf("%s: 服务层获取失败: %v, ID: %s", actionType, err, dtID) + logger.Errorf("%s: 服务层获取失败: %v, ID: %s", actionType, err, dtID) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备模板信息失败: "+err.Error(), actionType, "服务层获取失败", dtID) } - c.logger.Infof("%s: 获取设备模板信息成功, ID: %d", actionType, resp.ID) + logger.Infof("%s: 获取设备模板信息成功, ID: %d", actionType, resp.ID) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取设备模板信息成功", resp, actionType, "获取设备模板信息成功", resp) } @@ -462,14 +490,16 @@ func (c *Controller) GetDeviceTemplate(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=[]dto.DeviceTemplateResponse} // @Router /api/v1/device-templates [get] func (c *Controller) ListDeviceTemplates(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListDeviceTemplates") + const actionType = "获取设备模板列表" - resp, err := c.deviceService.ListDeviceTemplates() + resp, err := c.deviceService.ListDeviceTemplates(reqCtx) if err != nil { - c.logger.Errorf("%s: 服务层获取列表失败: %v", actionType, err) + logger.Errorf("%s: 服务层获取列表失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备模板列表失败: "+err.Error(), actionType, "服务层获取列表失败", nil) } - c.logger.Infof("%s: 获取设备模板列表成功, 数量: %d", actionType, len(resp)) + logger.Infof("%s: 获取设备模板列表成功, 数量: %d", actionType, len(resp)) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取设备模板列表成功", resp, actionType, "获取设备模板列表成功", resp) } @@ -485,32 +515,34 @@ func (c *Controller) ListDeviceTemplates(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.DeviceTemplateResponse} // @Router /api/v1/device-templates/{id} [put] func (c *Controller) UpdateDeviceTemplate(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "UpdateDeviceTemplate") + const actionType = "更新设备模板" dtID := ctx.Param("id") var req dto.UpdateDeviceTemplateRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) } id, err := strconv.ParseUint(dtID, 10, 64) if err != nil { - c.logger.Errorf("%s: 无效的ID: %s", actionType, dtID) + logger.Errorf("%s: 无效的ID: %s", actionType, dtID) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID: "+dtID, actionType, "无效的ID", dtID) } - resp, err := c.deviceService.UpdateDeviceTemplate(uint(id), &req) + resp, err := c.deviceService.UpdateDeviceTemplate(reqCtx, uint(id), &req) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - c.logger.Warnf("%s: 设备模板不存在, ID: %s", actionType, dtID) + logger.Warnf("%s: 设备模板不存在, ID: %s", actionType, dtID) return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "设备模板未找到", actionType, "设备模板不存在", dtID) } - c.logger.Errorf("%s: 服务层更新失败: %v, ID: %s", actionType, err, dtID) + logger.Errorf("%s: 服务层更新失败: %v, ID: %s", actionType, err, dtID) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新设备模板失败: "+err.Error(), actionType, "服务层更新失败", dtID) } - c.logger.Infof("%s: 设备模板更新成功, ID: %d", actionType, resp.ID) + logger.Infof("%s: 设备模板更新成功, ID: %d", actionType, resp.ID) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "设备模板更新成功", resp, actionType, "设备模板更新成功", resp) } @@ -524,29 +556,31 @@ func (c *Controller) UpdateDeviceTemplate(ctx echo.Context) error { // @Success 200 {object} controller.Response // @Router /api/v1/device-templates/{id} [delete] func (c *Controller) DeleteDeviceTemplate(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "DeleteDeviceTemplate") + const actionType = "删除设备模板" dtID := ctx.Param("id") id, err := strconv.ParseUint(dtID, 10, 64) if err != nil { - c.logger.Errorf("%s: 无效的ID: %s", actionType, dtID) + logger.Errorf("%s: 无效的ID: %s", actionType, dtID) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID: "+dtID, actionType, "无效的ID", dtID) } - if err := c.deviceService.DeleteDeviceTemplate(uint(id)); err != nil { + if err := c.deviceService.DeleteDeviceTemplate(reqCtx, uint(id)); err != nil { switch { case errors.Is(err, gorm.ErrRecordNotFound): - c.logger.Warnf("%s: 设备模板不存在, ID: %s", actionType, dtID) + logger.Warnf("%s: 设备模板不存在, ID: %s", actionType, dtID) return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "设备模板未找到", actionType, "设备模板不存在", dtID) case errors.Is(err, service.ErrDeviceTemplateInUse): - c.logger.Warnf("%s: 尝试删除正在被使用的模板, ID: %s", actionType, dtID) + logger.Warnf("%s: 尝试删除正在被使用的模板, ID: %s", actionType, dtID) return controller.SendErrorWithAudit(ctx, controller.CodeConflict, err.Error(), actionType, "模板正在被使用", dtID) default: - c.logger.Errorf("%s: 服务层删除失败: %v, ID: %s", actionType, err, dtID) + logger.Errorf("%s: 服务层删除失败: %v, ID: %s", actionType, err, dtID) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除设备模板失败: "+err.Error(), actionType, "服务层删除失败", dtID) } } - c.logger.Infof("%s: 设备模板删除成功, ID: %s", actionType, dtID) + logger.Infof("%s: 设备模板删除成功, ID: %s", actionType, dtID) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "设备模板删除成功", nil, actionType, "设备模板删除成功", dtID) } diff --git a/internal/app/controller/management/controller_helpers.go b/internal/app/controller/management/controller_helpers.go index 97cec5b..8e68435 100644 --- a/internal/app/controller/management/controller_helpers.go +++ b/internal/app/controller/management/controller_helpers.go @@ -1,18 +1,21 @@ package management import ( + "context" "errors" "fmt" "strconv" "git.huangwc.com/pig/pig-farm-controller/internal/app/controller" "git.huangwc.com/pig/pig-farm-controller/internal/app/service" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "github.com/labstack/echo/v4" ) // mapAndSendError 统一映射服务层错误并发送响应。 // 这个函数将服务层返回的错误转换为控制器层应返回的HTTP状态码和审计信息。 -func mapAndSendError(c *PigBatchController, ctx echo.Context, action string, err error, id uint) error { +func mapAndSendError(reqContext context.Context, c *PigBatchController, ctx echo.Context, action string, err error, id uint) error { if errors.Is(err, service.ErrPigBatchNotFound) || errors.Is(err, service.ErrPenNotFound) || errors.Is(err, service.ErrPenNotAssociatedWithBatch) { @@ -25,7 +28,7 @@ func mapAndSendError(c *PigBatchController, ctx echo.Context, action string, err errors.Is(err, service.ErrPenNotEmpty) { return controller.SendErrorWithAudit(ctx, controller.CodeConflict, err.Error(), action, err.Error(), id) } else { - c.logger.Errorf("操作[%s]业务逻辑失败: %v", action, err) + logs.GetLogger(reqContext).Errorf("操作[%s]业务逻辑失败: %v", action, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, fmt.Sprintf("操作失败: %v", err), action, err.Error(), id) } } @@ -85,6 +88,7 @@ func extractOperatorAndPrimaryID( // handleAPIRequest 封装了控制器中处理带有请求体和路径参数的API请求的通用逻辑。 // 它负责请求体绑定、操作员ID获取、服务层调用、错误映射和响应发送。 func handleAPIRequest[Req any]( + reqContext context.Context, c *PigBatchController, ctx echo.Context, action string, @@ -107,7 +111,7 @@ func handleAPIRequest[Req any]( // 3. 执行服务层逻辑 err = serviceExecutor(ctx, operatorID, primaryID, reqDTO) if err != nil { - return mapAndSendError(c, ctx, action, err, primaryID) + return mapAndSendError(reqContext, c, ctx, action, err, primaryID) } // 4. 发送成功响应 @@ -116,6 +120,7 @@ func handleAPIRequest[Req any]( // handleNoBodyAPIRequest 封装了处理不带请求体,但有路径参数和操作员ID的API请求的通用逻辑。 func handleNoBodyAPIRequest( + reqContext context.Context, c *PigBatchController, ctx echo.Context, action string, @@ -132,7 +137,7 @@ func handleNoBodyAPIRequest( // 2. 执行服务层逻辑 err = serviceExecutor(ctx, operatorID, primaryID) if err != nil { - return mapAndSendError(c, ctx, action, err, primaryID) + return mapAndSendError(reqContext, c, ctx, action, err, primaryID) } // 3. 发送成功响应 @@ -141,6 +146,7 @@ func handleNoBodyAPIRequest( // handleAPIRequestWithResponse 封装了控制器中处理带有请求体、路径参数并返回响应DTO的API请求的通用逻辑。 func handleAPIRequestWithResponse[Req any, Resp any]( + reqContext context.Context, c *PigBatchController, ctx echo.Context, action string, @@ -163,7 +169,7 @@ func handleAPIRequestWithResponse[Req any, Resp any]( // 3. 执行服务层逻辑 respDTO, err := serviceExecutor(ctx, operatorID, primaryID, reqDTO) if err != nil { - return mapAndSendError(c, ctx, action, err, primaryID) + return mapAndSendError(reqContext, c, ctx, action, err, primaryID) } // 4. 发送成功响应 @@ -172,6 +178,7 @@ func handleAPIRequestWithResponse[Req any, Resp any]( // handleNoBodyAPIRequestWithResponse 封装了处理不带请求体,但有路径参数和操作员ID,并返回响应DTO的API请求的通用逻辑。 func handleNoBodyAPIRequestWithResponse[Resp any]( + reqContext context.Context, c *PigBatchController, ctx echo.Context, action string, @@ -188,7 +195,7 @@ func handleNoBodyAPIRequestWithResponse[Resp any]( // 2. 执行服务层逻辑 respDTO, err := serviceExecutor(ctx, operatorID, primaryID) if err != nil { - return mapAndSendError(c, ctx, action, err, primaryID) + return mapAndSendError(reqContext, c, ctx, action, err, primaryID) } // 3. 发送成功响应 @@ -197,6 +204,7 @@ func handleNoBodyAPIRequestWithResponse[Resp any]( // handleQueryAPIRequestWithResponse 封装了处理带有查询参数并返回响应DTO的API请求的通用逻辑。 func handleQueryAPIRequestWithResponse[Query any, Resp any]( + reqContext context.Context, c *PigBatchController, ctx echo.Context, action string, @@ -219,7 +227,7 @@ func handleQueryAPIRequestWithResponse[Query any, Resp any]( respDTO, err := serviceExecutor(ctx, operatorID, queryDTO) if err != nil { // 对于列表查询,通常没有primaryID,所以传递0 - return mapAndSendError(c, ctx, action, err, 0) + return mapAndSendError(reqContext, c, ctx, action, err, 0) } // 4. 发送成功响应 diff --git a/internal/app/controller/management/pig_batch_controller.go b/internal/app/controller/management/pig_batch_controller.go index 0d7a17d..6a82809 100644 --- a/internal/app/controller/management/pig_batch_controller.go +++ b/internal/app/controller/management/pig_batch_controller.go @@ -1,6 +1,7 @@ package management import ( + "context" "strconv" "git.huangwc.com/pig/pig-farm-controller/internal/app/dto" @@ -12,14 +13,14 @@ import ( // PigBatchController 负责处理猪批次相关的API请求 type PigBatchController struct { - logger *logs.Logger + ctx context.Context service service.PigBatchService } // NewPigBatchController 创建一个新的 PigBatchController 实例 -func NewPigBatchController(logger *logs.Logger, service service.PigBatchService) *PigBatchController { +func NewPigBatchController(ctx context.Context, service service.PigBatchService) *PigBatchController { return &PigBatchController{ - logger: logger, + ctx: ctx, service: service, } } @@ -35,14 +36,16 @@ func NewPigBatchController(logger *logs.Logger, service service.PigBatchService) // @Success 201 {object} controller.Response{data=dto.PigBatchResponseDTO} "创建成功" // @Router /api/v1/pig-batches [post] func (c *PigBatchController) CreatePigBatch(ctx echo.Context) error { + reqCtx := logs.AddFuncName(ctx.Request().Context(), c.ctx, "CreatePigBatch") + const action = "创建猪批次" var req dto.PigBatchCreateDTO return handleAPIRequestWithResponse( - c, ctx, action, &req, + reqCtx, c, ctx, action, &req, func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.PigBatchCreateDTO) (*dto.PigBatchResponseDTO, error) { // 对于创建操作,primaryID通常不从路径中获取,而是由服务层生成 - return c.service.CreatePigBatch(operatorID, req) + return c.service.CreatePigBatch(reqCtx, operatorID, req) }, "创建成功", nil, // 无需自定义ID提取器,primaryID将为0 @@ -59,12 +62,14 @@ func (c *PigBatchController) CreatePigBatch(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.PigBatchResponseDTO} "获取成功" // @Router /api/v1/pig-batches/{id} [get] func (c *PigBatchController) GetPigBatch(ctx echo.Context) error { + reqCtx := logs.AddFuncName(ctx.Request().Context(), c.ctx, "GetPigBatch") + const action = "获取猪批次" return handleNoBodyAPIRequestWithResponse( - c, ctx, action, + reqCtx, c, ctx, action, func(ctx echo.Context, operatorID uint, primaryID uint) (*dto.PigBatchResponseDTO, error) { - return c.service.GetPigBatch(primaryID) + return c.service.GetPigBatch(reqCtx, primaryID) }, "获取成功", nil, // 默认从 ":id" 路径参数提取ID @@ -83,13 +88,15 @@ func (c *PigBatchController) GetPigBatch(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.PigBatchResponseDTO} "更新成功" // @Router /api/v1/pig-batches/{id} [put] func (c *PigBatchController) UpdatePigBatch(ctx echo.Context) error { + reqCtx := logs.AddFuncName(ctx.Request().Context(), c.ctx, "UpdatePigBatch") + const action = "更新猪批次" var req dto.PigBatchUpdateDTO return handleAPIRequestWithResponse( - c, ctx, action, &req, + reqCtx, c, ctx, action, &req, func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.PigBatchUpdateDTO) (*dto.PigBatchResponseDTO, error) { - return c.service.UpdatePigBatch(primaryID, req) + return c.service.UpdatePigBatch(reqCtx, primaryID, req) }, "更新成功", nil, // 默认从 ":id" 路径参数提取ID @@ -106,12 +113,14 @@ func (c *PigBatchController) UpdatePigBatch(ctx echo.Context) error { // @Success 200 {object} controller.Response "删除成功" // @Router /api/v1/pig-batches/{id} [delete] func (c *PigBatchController) DeletePigBatch(ctx echo.Context) error { + reqCtx := logs.AddFuncName(ctx.Request().Context(), c.ctx, "DeletePigBatch") + const action = "删除猪批次" return handleNoBodyAPIRequest( - c, ctx, action, + reqCtx, c, ctx, action, func(ctx echo.Context, operatorID uint, primaryID uint) error { - return c.service.DeletePigBatch(primaryID) + return c.service.DeletePigBatch(reqCtx, primaryID) }, "删除成功", nil, // 默认从 ":id" 路径参数提取ID @@ -128,13 +137,15 @@ func (c *PigBatchController) DeletePigBatch(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=[]dto.PigBatchResponseDTO} "获取成功" // @Router /api/v1/pig-batches [get] func (c *PigBatchController) ListPigBatches(ctx echo.Context) error { + reqCtx := logs.AddFuncName(ctx.Request().Context(), c.ctx, "ListPigBatches") + const action = "获取猪批次列表" var query dto.PigBatchQueryDTO return handleQueryAPIRequestWithResponse( - c, ctx, action, &query, + reqCtx, c, ctx, action, &query, func(ctx echo.Context, operatorID uint, query *dto.PigBatchQueryDTO) ([]*dto.PigBatchResponseDTO, error) { - return c.service.ListPigBatches(query.IsActive) + return c.service.ListPigBatches(reqCtx, query.IsActive) }, "获取成功", ) @@ -152,13 +163,15 @@ func (c *PigBatchController) ListPigBatches(ctx echo.Context) error { // @Success 200 {object} controller.Response "分配成功" // @Router /api/v1/pig-batches/assign-pens/{id} [post] func (c *PigBatchController) AssignEmptyPensToBatch(ctx echo.Context) error { + reqCtx := logs.AddFuncName(ctx.Request().Context(), c.ctx, "AssignEmptyPensToBatch") + const action = "为猪批次分配空栏" var req dto.AssignEmptyPensToBatchRequest return handleAPIRequest( - c, ctx, action, &req, + reqCtx, c, ctx, action, &req, func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.AssignEmptyPensToBatchRequest) error { - return c.service.AssignEmptyPensToBatch(primaryID, req.PenIDs, operatorID) + return c.service.AssignEmptyPensToBatch(reqCtx, primaryID, req.PenIDs, operatorID) }, "分配成功", nil, // 默认从 ":id" 路径参数提取ID @@ -177,14 +190,16 @@ func (c *PigBatchController) AssignEmptyPensToBatch(ctx echo.Context) error { // @Success 200 {object} controller.Response "划拨成功" // @Router /api/v1/pig-batches/reclassify-pen/{fromBatchID} [post] func (c *PigBatchController) ReclassifyPenToNewBatch(ctx echo.Context) error { + reqCtx := logs.AddFuncName(ctx.Request().Context(), c.ctx, "ReclassifyPenToNewBatch") + const action = "划拨猪栏到新批次" var req dto.ReclassifyPenToNewBatchRequest return handleAPIRequest( - c, ctx, action, &req, + reqCtx, c, ctx, action, &req, func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.ReclassifyPenToNewBatchRequest) error { // primaryID 在这里是 fromBatchID - return c.service.ReclassifyPenToNewBatch(primaryID, req.ToBatchID, req.PenID, operatorID, req.Remarks) + return c.service.ReclassifyPenToNewBatch(reqCtx, primaryID, req.ToBatchID, req.PenID, operatorID, req.Remarks) }, "划拨成功", func(ctx echo.Context) (uint, error) { // 自定义ID提取器,从 ":fromBatchID" 路径参数提取 @@ -209,10 +224,12 @@ func (c *PigBatchController) ReclassifyPenToNewBatch(ctx echo.Context) error { // @Success 200 {object} controller.Response "移除成功" // @Router /api/v1/pig-batches/remove-pen/{penID}/{batchID} [delete] func (c *PigBatchController) RemoveEmptyPenFromBatch(ctx echo.Context) error { + reqCtx := logs.AddFuncName(ctx.Request().Context(), c.ctx, "RemoveEmptyPenFromBatch") + const action = "从猪批次移除空栏" return handleNoBodyAPIRequest( - c, ctx, action, + reqCtx, c, ctx, action, func(ctx echo.Context, operatorID uint, primaryID uint) error { // primaryID 在这里是 batchID penIDParam := ctx.Param("penID") @@ -220,7 +237,7 @@ func (c *PigBatchController) RemoveEmptyPenFromBatch(ctx echo.Context) error { if err != nil { return err // 返回错误,因为 penID 格式无效 } - return c.service.RemoveEmptyPenFromBatch(primaryID, uint(parsedPenID)) + return c.service.RemoveEmptyPenFromBatch(reqCtx, primaryID, uint(parsedPenID)) }, "移除成功", func(ctx echo.Context) (uint, error) { // 自定义ID提取器,从 ":batchID" 路径参数提取 @@ -246,13 +263,15 @@ func (c *PigBatchController) RemoveEmptyPenFromBatch(ctx echo.Context) error { // @Success 200 {object} controller.Response "移入成功" // @Router /api/v1/pig-batches/move-pigs-into-pen/{id} [post] func (c *PigBatchController) MovePigsIntoPen(ctx echo.Context) error { + reqCtx := logs.AddFuncName(ctx.Request().Context(), c.ctx, "MovePigsIntoPen") + const action = "将猪只移入猪栏" var req dto.MovePigsIntoPenRequest return handleAPIRequest( - c, ctx, action, &req, + reqCtx, c, ctx, action, &req, func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.MovePigsIntoPenRequest) error { - return c.service.MovePigsIntoPen(primaryID, req.ToPenID, req.Quantity, operatorID, req.Remarks) + return c.service.MovePigsIntoPen(reqCtx, primaryID, req.ToPenID, req.Quantity, operatorID, req.Remarks) }, "移入成功", nil, // 默认从 ":id" 路径参数提取ID diff --git a/internal/app/controller/management/pig_batch_health_controller.go b/internal/app/controller/management/pig_batch_health_controller.go index 00d51ff..cd82e8b 100644 --- a/internal/app/controller/management/pig_batch_health_controller.go +++ b/internal/app/controller/management/pig_batch_health_controller.go @@ -2,6 +2,8 @@ package management import ( "git.huangwc.com/pig/pig-farm-controller/internal/app/dto" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "github.com/labstack/echo/v4" ) @@ -17,13 +19,15 @@ import ( // @Success 200 {object} controller.Response "记录成功" // @Router /api/v1/pig-batches/record-sick-pigs/{id} [post] func (c *PigBatchController) RecordSickPigs(ctx echo.Context) error { + reqCtx := logs.AddFuncName(ctx.Request().Context(), c.ctx, "RecordSickPigs") + const action = "记录新增病猪事件" var req dto.RecordSickPigsRequest return handleAPIRequest( - c, ctx, action, &req, + reqCtx, c, ctx, action, &req, func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.RecordSickPigsRequest) error { - return c.service.RecordSickPigs(operatorID, primaryID, req.PenID, req.Quantity, req.TreatmentLocation, req.HappenedAt, req.Remarks) + return c.service.RecordSickPigs(reqCtx, operatorID, primaryID, req.PenID, req.Quantity, req.TreatmentLocation, req.HappenedAt, req.Remarks) }, "记录成功", nil, // 默认从 ":id" 路径参数提取ID @@ -42,13 +46,15 @@ func (c *PigBatchController) RecordSickPigs(ctx echo.Context) error { // @Success 200 {object} controller.Response "记录成功" // @Router /api/v1/pig-batches/record-sick-pig-recovery/{id} [post] func (c *PigBatchController) RecordSickPigRecovery(ctx echo.Context) error { + reqCtx := logs.AddFuncName(ctx.Request().Context(), c.ctx, "RecordSickPigRecovery") + const action = "记录病猪康复事件" var req dto.RecordSickPigRecoveryRequest return handleAPIRequest( - c, ctx, action, &req, + reqCtx, c, ctx, action, &req, func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.RecordSickPigRecoveryRequest) error { - return c.service.RecordSickPigRecovery(operatorID, primaryID, req.PenID, req.Quantity, req.TreatmentLocation, req.HappenedAt, req.Remarks) + return c.service.RecordSickPigRecovery(reqCtx, operatorID, primaryID, req.PenID, req.Quantity, req.TreatmentLocation, req.HappenedAt, req.Remarks) }, "记录成功", nil, // 默认从 ":id" 路径参数提取ID @@ -67,13 +73,15 @@ func (c *PigBatchController) RecordSickPigRecovery(ctx echo.Context) error { // @Success 200 {object} controller.Response "记录成功" // @Router /api/v1/pig-batches/record-sick-pig-death/{id} [post] func (c *PigBatchController) RecordSickPigDeath(ctx echo.Context) error { + reqCtx := logs.AddFuncName(ctx.Request().Context(), c.ctx, "RecordSickPigDeath") + const action = "记录病猪死亡事件" var req dto.RecordSickPigDeathRequest return handleAPIRequest( - c, ctx, action, &req, + reqCtx, c, ctx, action, &req, func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.RecordSickPigDeathRequest) error { - return c.service.RecordSickPigDeath(operatorID, primaryID, req.PenID, req.Quantity, req.TreatmentLocation, req.HappenedAt, req.Remarks) + return c.service.RecordSickPigDeath(reqCtx, operatorID, primaryID, req.PenID, req.Quantity, req.TreatmentLocation, req.HappenedAt, req.Remarks) }, "记录成功", nil, // 默认从 ":id" 路径参数提取ID @@ -92,13 +100,15 @@ func (c *PigBatchController) RecordSickPigDeath(ctx echo.Context) error { // @Success 200 {object} controller.Response "记录成功" // @Router /api/v1/pig-batches/record-sick-pig-cull/{id} [post] func (c *PigBatchController) RecordSickPigCull(ctx echo.Context) error { + reqCtx := logs.AddFuncName(ctx.Request().Context(), c.ctx, "RecordSickPigCull") + const action = "记录病猪淘汰事件" var req dto.RecordSickPigCullRequest return handleAPIRequest( - c, ctx, action, &req, + reqCtx, c, ctx, action, &req, func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.RecordSickPigCullRequest) error { - return c.service.RecordSickPigCull(operatorID, primaryID, req.PenID, req.Quantity, req.TreatmentLocation, req.HappenedAt, req.Remarks) + return c.service.RecordSickPigCull(reqCtx, operatorID, primaryID, req.PenID, req.Quantity, req.TreatmentLocation, req.HappenedAt, req.Remarks) }, "记录成功", nil, // 默认从 ":id" 路径参数提取ID @@ -117,13 +127,15 @@ func (c *PigBatchController) RecordSickPigCull(ctx echo.Context) error { // @Success 200 {object} controller.Response "记录成功" // @Router /api/v1/pig-batches/record-death/{id} [post] func (c *PigBatchController) RecordDeath(ctx echo.Context) error { + reqCtx := logs.AddFuncName(ctx.Request().Context(), c.ctx, "RecordDeath") + const action = "记录正常猪只死亡事件" var req dto.RecordDeathRequest return handleAPIRequest( - c, ctx, action, &req, + reqCtx, c, ctx, action, &req, func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.RecordDeathRequest) error { - return c.service.RecordDeath(operatorID, primaryID, req.PenID, req.Quantity, req.HappenedAt, req.Remarks) + return c.service.RecordDeath(reqCtx, operatorID, primaryID, req.PenID, req.Quantity, req.HappenedAt, req.Remarks) }, "记录成功", nil, // 默认从 ":id" 路径参数提取ID @@ -142,13 +154,15 @@ func (c *PigBatchController) RecordDeath(ctx echo.Context) error { // @Success 200 {object} controller.Response "记录成功" // @Router /api/v1/pig-batches/record-cull/{id} [post] func (c *PigBatchController) RecordCull(ctx echo.Context) error { + reqCtx := logs.AddFuncName(ctx.Request().Context(), c.ctx, "RecordCull") + const action = "记录正常猪只淘汰事件" var req dto.RecordCullRequest return handleAPIRequest( - c, ctx, action, &req, + reqCtx, c, ctx, action, &req, func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.RecordCullRequest) error { - return c.service.RecordCull(operatorID, primaryID, req.PenID, req.Quantity, req.HappenedAt, req.Remarks) + return c.service.RecordCull(reqCtx, operatorID, primaryID, req.PenID, req.Quantity, req.HappenedAt, req.Remarks) }, "记录成功", nil, // 默认从 ":id" 路径参数提取ID diff --git a/internal/app/controller/management/pig_batch_trade_controller.go b/internal/app/controller/management/pig_batch_trade_controller.go index 37ad5dd..6380c40 100644 --- a/internal/app/controller/management/pig_batch_trade_controller.go +++ b/internal/app/controller/management/pig_batch_trade_controller.go @@ -2,6 +2,8 @@ package management import ( "git.huangwc.com/pig/pig-farm-controller/internal/app/dto" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "github.com/labstack/echo/v4" ) @@ -17,13 +19,14 @@ import ( // @Success 200 {object} controller.Response "卖猪成功" // @Router /api/v1/pig-batches/sell-pigs/{id} [post] func (c *PigBatchController) SellPigs(ctx echo.Context) error { + reqCtx := logs.AddFuncName(ctx.Request().Context(), c.ctx, "SellPigs") const action = "卖猪" var req dto.SellPigsRequest return handleAPIRequest( - c, ctx, action, &req, + reqCtx, c, ctx, action, &req, func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.SellPigsRequest) error { - return c.service.SellPigs(primaryID, req.PenID, req.Quantity, req.UnitPrice, req.TotalPrice, req.TraderName, req.TradeDate, req.Remarks, operatorID) + return c.service.SellPigs(reqCtx, primaryID, req.PenID, req.Quantity, req.UnitPrice, req.TotalPrice, req.TraderName, req.TradeDate, req.Remarks, operatorID) }, "卖猪成功", nil, // 默认从 ":id" 路径参数提取ID @@ -42,13 +45,14 @@ func (c *PigBatchController) SellPigs(ctx echo.Context) error { // @Success 200 {object} controller.Response "买猪成功" // @Router /api/v1/pig-batches/buy-pigs/{id} [post] func (c *PigBatchController) BuyPigs(ctx echo.Context) error { + reqCtx := logs.AddFuncName(ctx.Request().Context(), c.ctx, "BuyPigs") const action = "买猪" var req dto.BuyPigsRequest return handleAPIRequest( - c, ctx, action, &req, + reqCtx, c, ctx, action, &req, func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.BuyPigsRequest) error { - return c.service.BuyPigs(primaryID, req.PenID, req.Quantity, req.UnitPrice, req.TotalPrice, req.TraderName, req.TradeDate, req.Remarks, operatorID) + return c.service.BuyPigs(reqCtx, primaryID, req.PenID, req.Quantity, req.UnitPrice, req.TotalPrice, req.TraderName, req.TradeDate, req.Remarks, operatorID) }, "买猪成功", nil, // 默认从 ":id" 路径参数提取ID diff --git a/internal/app/controller/management/pig_batch_transfer_controller.go b/internal/app/controller/management/pig_batch_transfer_controller.go index e57a45a..1d8c7b4 100644 --- a/internal/app/controller/management/pig_batch_transfer_controller.go +++ b/internal/app/controller/management/pig_batch_transfer_controller.go @@ -4,6 +4,8 @@ import ( "strconv" "git.huangwc.com/pig/pig-farm-controller/internal/app/dto" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "github.com/labstack/echo/v4" ) @@ -19,14 +21,16 @@ import ( // @Success 200 {object} controller.Response "调栏成功" // @Router /api/v1/pig-batches/transfer-across-batches/{sourceBatchID} [post] func (c *PigBatchController) TransferPigsAcrossBatches(ctx echo.Context) error { + reqCtx := logs.AddFuncName(ctx.Request().Context(), c.ctx, "TransferPigsAcrossBatches") + const action = "跨猪群调栏" var req dto.TransferPigsAcrossBatchesRequest return handleAPIRequest( - c, ctx, action, &req, + reqCtx, c, ctx, action, &req, func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.TransferPigsAcrossBatchesRequest) error { // primaryID 在这里是 sourceBatchID - return c.service.TransferPigsAcrossBatches(primaryID, req.DestBatchID, req.FromPenID, req.ToPenID, req.Quantity, operatorID, req.Remarks) + return c.service.TransferPigsAcrossBatches(reqCtx, primaryID, req.DestBatchID, req.FromPenID, req.ToPenID, req.Quantity, operatorID, req.Remarks) }, "调栏成功", func(ctx echo.Context) (uint, error) { // 自定义ID提取器,从 ":sourceBatchID" 路径参数提取 @@ -52,14 +56,16 @@ func (c *PigBatchController) TransferPigsAcrossBatches(ctx echo.Context) error { // @Success 200 {object} controller.Response "调栏成功" // @Router /api/v1/pig-batches/transfer-within-batch/{id} [post] func (c *PigBatchController) TransferPigsWithinBatch(ctx echo.Context) error { + reqCtx := logs.AddFuncName(ctx.Request().Context(), c.ctx, "TransferPigsWithinBatch") + const action = "群内调栏" var req dto.TransferPigsWithinBatchRequest return handleAPIRequest( - c, ctx, action, &req, + reqCtx, c, ctx, action, &req, func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.TransferPigsWithinBatchRequest) error { // primaryID 在这里是 batchID - return c.service.TransferPigsWithinBatch(primaryID, req.FromPenID, req.ToPenID, req.Quantity, operatorID, req.Remarks) + return c.service.TransferPigsWithinBatch(reqCtx, primaryID, req.FromPenID, req.ToPenID, req.Quantity, operatorID, req.Remarks) }, "调栏成功", nil, // 默认从 ":id" 路径参数提取ID diff --git a/internal/app/controller/management/pig_farm_controller.go b/internal/app/controller/management/pig_farm_controller.go index 45f311c..0a6733c 100644 --- a/internal/app/controller/management/pig_farm_controller.go +++ b/internal/app/controller/management/pig_farm_controller.go @@ -1,6 +1,7 @@ package management import ( + "context" "errors" "strconv" @@ -8,6 +9,7 @@ import ( "git.huangwc.com/pig/pig-farm-controller/internal/app/dto" "git.huangwc.com/pig/pig-farm-controller/internal/app/service" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "github.com/labstack/echo/v4" ) @@ -15,14 +17,14 @@ import ( // PigFarmController 负责处理猪舍和猪栏相关的API请求 type PigFarmController struct { - logger *logs.Logger + ctx context.Context service service.PigFarmService } // NewPigFarmController 创建一个新的 PigFarmController 实例 -func NewPigFarmController(logger *logs.Logger, service service.PigFarmService) *PigFarmController { +func NewPigFarmController(ctx context.Context, service service.PigFarmService) *PigFarmController { return &PigFarmController{ - logger: logger, + ctx: ctx, service: service, } } @@ -40,16 +42,17 @@ func NewPigFarmController(logger *logs.Logger, service service.PigFarmService) * // @Success 201 {object} controller.Response{data=dto.PigHouseResponse} "创建成功" // @Router /api/v1/pig-houses [post] func (c *PigFarmController) CreatePigHouse(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "CreatePigHouse") const action = "创建猪舍" var req dto.CreatePigHouseRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", action, err) + logger.Errorf("%s: 参数绑定失败: %v", action, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体", action, "请求体绑定失败", req) } - house, err := c.service.CreatePigHouse(req.Name, req.Description) + house, err := c.service.CreatePigHouse(reqCtx, req.Name, req.Description) if err != nil { - c.logger.Errorf("%s: 业务逻辑失败: %v", action, err) + logger.Errorf("%s: 业务逻辑失败: %v", action, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "创建猪舍失败", action, "业务逻辑失败", req) } @@ -66,18 +69,19 @@ func (c *PigFarmController) CreatePigHouse(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.PigHouseResponse} "获取成功" // @Router /api/v1/pig-houses/{id} [get] func (c *PigFarmController) GetPigHouse(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "GetPigHouse") const action = "获取猪舍" id, err := strconv.ParseUint(ctx.Param("id"), 10, 32) if err != nil { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID格式", action, "ID格式错误", ctx.Param("id")) } - house, err := c.service.GetPigHouseByID(uint(id)) + house, err := c.service.GetPigHouseByID(reqCtx, uint(id)) if err != nil { if errors.Is(err, service.ErrHouseNotFound) { return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "猪舍不存在", action, "猪舍不存在", id) } - c.logger.Errorf("%s: 业务逻辑失败: %v", action, err) + logger.Errorf("%s: 业务逻辑失败: %v", action, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取猪舍失败", action, "业务逻辑失败", id) } @@ -93,10 +97,11 @@ func (c *PigFarmController) GetPigHouse(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=[]dto.PigHouseResponse} "获取成功" // @Router /api/v1/pig-houses [get] func (c *PigFarmController) ListPigHouses(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListPigHouses") const action = "获取猪舍列表" - houses, err := c.service.ListPigHouses() + houses, err := c.service.ListPigHouses(reqCtx) if err != nil { - c.logger.Errorf("%s: 业务逻辑失败: %v", action, err) + logger.Errorf("%s: 业务逻辑失败: %v", action, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取列表失败", action, "业务逻辑失败", nil) } @@ -115,6 +120,7 @@ func (c *PigFarmController) ListPigHouses(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.PigHouseResponse} "更新成功" // @Router /api/v1/pig-houses/{id} [put] func (c *PigFarmController) UpdatePigHouse(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "UpdatePigHouse") const action = "更新猪舍" id, err := strconv.ParseUint(ctx.Param("id"), 10, 32) if err != nil { @@ -126,12 +132,12 @@ func (c *PigFarmController) UpdatePigHouse(ctx echo.Context) error { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体", action, "请求体绑定失败", req) } - house, err := c.service.UpdatePigHouse(uint(id), req.Name, req.Description) + house, err := c.service.UpdatePigHouse(reqCtx, uint(id), req.Name, req.Description) if err != nil { if errors.Is(err, service.ErrHouseNotFound) { return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "猪舍不存在", action, "猪舍不存在", id) } - c.logger.Errorf("%s: 业务逻辑失败: %v", action, err) + logger.Errorf("%s: 业务逻辑失败: %v", action, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新失败", action, "业务逻辑失败", req) } @@ -148,13 +154,14 @@ func (c *PigFarmController) UpdatePigHouse(ctx echo.Context) error { // @Success 200 {object} controller.Response "删除成功" // @Router /api/v1/pig-houses/{id} [delete] func (c *PigFarmController) DeletePigHouse(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "DeletePigHouse") const action = "删除猪舍" id, err := strconv.ParseUint(ctx.Param("id"), 10, 32) if err != nil { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID格式", action, "ID格式错误", ctx.Param("id")) } - if err := c.service.DeletePigHouse(uint(id)); err != nil { + if err := c.service.DeletePigHouse(reqCtx, uint(id)); err != nil { if errors.Is(err, service.ErrHouseNotFound) { return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "猪舍不存在", action, "猪舍不存在", id) } @@ -162,7 +169,7 @@ func (c *PigFarmController) DeletePigHouse(ctx echo.Context) error { if errors.Is(err, service.ErrHouseContainsPens) { return controller.SendErrorWithAudit(ctx, controller.CodeConflict, err.Error(), action, err.Error(), id) } - c.logger.Errorf("%s: 业务逻辑失败: %v", action, err) + logger.Errorf("%s: 业务逻辑失败: %v", action, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除失败", action, "业务逻辑失败", id) } @@ -182,19 +189,20 @@ func (c *PigFarmController) DeletePigHouse(ctx echo.Context) error { // @Success 201 {object} controller.Response{data=dto.PenResponse} "创建成功" // @Router /api/v1/pens [post] func (c *PigFarmController) CreatePen(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "CreatePen") const action = "创建猪栏" var req dto.CreatePenRequest if err := ctx.Bind(&req); err != nil { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体", action, "请求体绑定失败", req) } - pen, err := c.service.CreatePen(req.PenNumber, req.HouseID, req.Capacity) + pen, err := c.service.CreatePen(reqCtx, req.PenNumber, req.HouseID, req.Capacity) if err != nil { // 检查是否是业务逻辑错误 if errors.Is(err, service.ErrHouseNotFound) { return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), action, err.Error(), req) } - c.logger.Errorf("%s: 业务逻辑失败: %v", action, err) + logger.Errorf("%s: 业务逻辑失败: %v", action, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "创建猪栏失败", action, "业务逻辑失败", req) } @@ -211,18 +219,19 @@ func (c *PigFarmController) CreatePen(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.PenResponse} "获取成功" // @Router /api/v1/pens/{id} [get] func (c *PigFarmController) GetPen(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "GetPen") const action = "获取猪栏" id, err := strconv.ParseUint(ctx.Param("id"), 10, 32) if err != nil { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID格式", action, "ID格式错误", ctx.Param("id")) } - pen, err := c.service.GetPenByID(uint(id)) + pen, err := c.service.GetPenByID(reqCtx, uint(id)) if err != nil { if errors.Is(err, service.ErrPenNotFound) { return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "猪栏不存在", action, "猪栏不存在", id) } - c.logger.Errorf("%s: 业务逻辑失败: %v", action, err) + logger.Errorf("%s: 业务逻辑失败: %v", action, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取猪栏失败", action, "业务逻辑失败", id) } @@ -238,10 +247,11 @@ func (c *PigFarmController) GetPen(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=[]dto.PenResponse} "获取成功" // @Router /api/v1/pens [get] func (c *PigFarmController) ListPens(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListPens") const action = "获取猪栏列表" - pens, err := c.service.ListPens() + pens, err := c.service.ListPens(reqCtx) if err != nil { - c.logger.Errorf("%s: 业务逻辑失败: %v", action, err) + logger.Errorf("%s: 业务逻辑失败: %v", action, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取列表失败", action, "业务逻辑失败", nil) } @@ -260,6 +270,7 @@ func (c *PigFarmController) ListPens(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.PenResponse} "更新成功" // @Router /api/v1/pens/{id} [put] func (c *PigFarmController) UpdatePen(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "UpdatePen") const action = "更新猪栏" id, err := strconv.ParseUint(ctx.Param("id"), 10, 32) if err != nil { @@ -271,13 +282,13 @@ func (c *PigFarmController) UpdatePen(ctx echo.Context) error { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体", action, "请求体绑定失败", req) } - pen, err := c.service.UpdatePen(uint(id), req.PenNumber, req.HouseID, req.Capacity, req.Status) + pen, err := c.service.UpdatePen(reqCtx, uint(id), req.PenNumber, req.HouseID, req.Capacity, req.Status) if err != nil { if errors.Is(err, service.ErrPenNotFound) { return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "猪栏不存在", action, "猪栏不存在", id) } // 其他业务逻辑错误可以在这里添加处理 - c.logger.Errorf("%s: 业务逻辑失败: %v", action, err) + logger.Errorf("%s: 业务逻辑失败: %v", action, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新失败", action, "业务逻辑失败", req) } @@ -294,13 +305,14 @@ func (c *PigFarmController) UpdatePen(ctx echo.Context) error { // @Success 200 {object} controller.Response "删除成功" // @Router /api/v1/pens/{id} [delete] func (c *PigFarmController) DeletePen(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "DeletePen") const action = "删除猪栏" id, err := strconv.ParseUint(ctx.Param("id"), 10, 32) if err != nil { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID格式", action, "ID格式错误", ctx.Param("id")) } - if err := c.service.DeletePen(uint(id)); err != nil { + if err := c.service.DeletePen(reqCtx, uint(id)); err != nil { if errors.Is(err, service.ErrPenNotFound) { return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "猪栏不存在", action, "猪栏不存在", id) } @@ -308,7 +320,7 @@ func (c *PigFarmController) DeletePen(ctx echo.Context) error { if errors.Is(err, service.ErrPenInUse) { return controller.SendErrorWithAudit(ctx, controller.CodeConflict, err.Error(), action, err.Error(), id) } - c.logger.Errorf("%s: 业务逻辑失败: %v", action, err) + logger.Errorf("%s: 业务逻辑失败: %v", action, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除失败", action, "业务逻辑失败", id) } @@ -327,6 +339,7 @@ func (c *PigFarmController) DeletePen(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.PenResponse} "更新成功" // @Router /api/v1/pens/{id}/status [put] func (c *PigFarmController) UpdatePenStatus(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "UpdatePenStatus") const action = "更新猪栏状态" id, err := strconv.ParseUint(ctx.Param("id"), 10, 32) if err != nil { @@ -338,14 +351,14 @@ func (c *PigFarmController) UpdatePenStatus(ctx echo.Context) error { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体", action, "请求体绑定失败", req) } - pen, err := c.service.UpdatePenStatus(uint(id), req.Status) + pen, err := c.service.UpdatePenStatus(reqCtx, uint(id), req.Status) if err != nil { if errors.Is(err, service.ErrPenNotFound) { return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), action, err.Error(), id) } else if errors.Is(err, service.ErrPenStatusInvalidForOccupiedPen) || errors.Is(err, service.ErrPenStatusInvalidForUnoccupiedPen) { return controller.SendErrorWithAudit(ctx, controller.CodeConflict, err.Error(), action, err.Error(), id) } - c.logger.Errorf("%s: 业务逻辑失败: %v", action, err) + logger.Errorf("%s: 业务逻辑失败: %v", action, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新猪栏状态失败", action, err.Error(), id) } diff --git a/internal/app/controller/monitor/monitor_controller.go b/internal/app/controller/monitor/monitor_controller.go index c060614..ae787ad 100644 --- a/internal/app/controller/monitor/monitor_controller.go +++ b/internal/app/controller/monitor/monitor_controller.go @@ -1,6 +1,7 @@ package monitor import ( + "context" "errors" "git.huangwc.com/pig/pig-farm-controller/internal/app/controller" @@ -8,20 +9,21 @@ import ( "git.huangwc.com/pig/pig-farm-controller/internal/app/service" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" + "github.com/labstack/echo/v4" ) // Controller 监控控制器,封装了所有与数据监控相关的业务逻辑 type Controller struct { + ctx context.Context monitorService service.MonitorService - logger *logs.Logger } // NewController 创建一个新的监控控制器实例 -func NewController(monitorService service.MonitorService, logger *logs.Logger) *Controller { +func NewController(ctx context.Context, monitorService service.MonitorService) *Controller { return &Controller{ + ctx: ctx, monitorService: monitorService, - logger: logger, } } @@ -35,26 +37,27 @@ func NewController(monitorService service.MonitorService, logger *logs.Logger) * // @Success 200 {object} controller.Response{data=dto.ListSensorDataResponse} // @Router /api/v1/monitor/sensor-data [get] func (c *Controller) ListSensorData(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListSensorData") const actionType = "获取传感器数据列表" var req dto.ListSensorDataRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) } - resp, err := c.monitorService.ListSensorData(&req) + resp, err := c.monitorService.ListSensorData(reqCtx, &req) if err != nil { if errors.Is(err, repository.ErrInvalidPagination) { - c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) + logger.Warnf("%s: 无效的分页参数: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) } - c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) + logger.Errorf("%s: 服务层查询失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取传感器数据失败: "+err.Error(), actionType, "服务层查询失败", req) } - c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) + logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取传感器数据成功", resp, actionType, "获取传感器数据成功", req) } @@ -68,26 +71,27 @@ func (c *Controller) ListSensorData(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.ListDeviceCommandLogResponse} // @Router /api/v1/monitor/device-command-logs [get] func (c *Controller) ListDeviceCommandLogs(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListDeviceCommandLogs") const actionType = "获取设备命令日志列表" var req dto.ListDeviceCommandLogRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) } - resp, err := c.monitorService.ListDeviceCommandLogs(&req) + resp, err := c.monitorService.ListDeviceCommandLogs(reqCtx, &req) if err != nil { if errors.Is(err, repository.ErrInvalidPagination) { - c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) + logger.Warnf("%s: 无效的分页参数: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) } - c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) + logger.Errorf("%s: 服务层查询失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备命令日志失败: "+err.Error(), actionType, "服务层查询失败", req) } - c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) + logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取设备命令日志成功", resp, actionType, "获取设备命令日志成功", req) } @@ -101,26 +105,27 @@ func (c *Controller) ListDeviceCommandLogs(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.ListPlanExecutionLogResponse} // @Router /api/v1/monitor/plan-execution-logs [get] func (c *Controller) ListPlanExecutionLogs(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListPlanExecutionLogs") const actionType = "获取计划执行日志列表" var req dto.ListPlanExecutionLogRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) } - resp, err := c.monitorService.ListPlanExecutionLogs(&req) + resp, err := c.monitorService.ListPlanExecutionLogs(reqCtx, &req) if err != nil { if errors.Is(err, repository.ErrInvalidPagination) { - c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) + logger.Warnf("%s: 无效的分页参数: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) } - c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) + logger.Errorf("%s: 服务层查询失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划执行日志失败: "+err.Error(), actionType, "服务层查询失败", req) } - c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) + logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取计划执行日志成功", resp, actionType, "获取计划执行日志成功", req) } @@ -134,26 +139,27 @@ func (c *Controller) ListPlanExecutionLogs(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.ListTaskExecutionLogResponse} // @Router /api/v1/monitor/task-execution-logs [get] func (c *Controller) ListTaskExecutionLogs(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListTaskExecutionLogs") const actionType = "获取任务执行日志列表" var req dto.ListTaskExecutionLogRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) } - resp, err := c.monitorService.ListTaskExecutionLogs(&req) + resp, err := c.monitorService.ListTaskExecutionLogs(reqCtx, &req) if err != nil { if errors.Is(err, repository.ErrInvalidPagination) { - c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) + logger.Warnf("%s: 无效的分页参数: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) } - c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) + logger.Errorf("%s: 服务层查询失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取任务执行日志失败: "+err.Error(), actionType, "服务层查询失败", req) } - c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) + logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取任务执行日志成功", resp, actionType, "获取任务执行日志成功", req) } @@ -167,26 +173,27 @@ func (c *Controller) ListTaskExecutionLogs(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.ListPendingCollectionResponse} // @Router /api/v1/monitor/pending-collections [get] func (c *Controller) ListPendingCollections(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListPendingCollections") const actionType = "获取待采集请求列表" var req dto.ListPendingCollectionRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) } - resp, err := c.monitorService.ListPendingCollections(&req) + resp, err := c.monitorService.ListPendingCollections(reqCtx, &req) if err != nil { if errors.Is(err, repository.ErrInvalidPagination) { - c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) + logger.Warnf("%s: 无效的分页参数: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) } - c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) + logger.Errorf("%s: 服务层查询失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取待采集请求失败: "+err.Error(), actionType, "服务层查询失败", req) } - c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) + logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取待采集请求成功", resp, actionType, "获取待采集请求成功", req) } @@ -200,26 +207,27 @@ func (c *Controller) ListPendingCollections(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.ListUserActionLogResponse} // @Router /api/v1/monitor/user-action-logs [get] func (c *Controller) ListUserActionLogs(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListUserActionLogs") const actionType = "获取用户操作日志列表" var req dto.ListUserActionLogRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) } - resp, err := c.monitorService.ListUserActionLogs(&req) + resp, err := c.monitorService.ListUserActionLogs(reqCtx, &req) if err != nil { if errors.Is(err, repository.ErrInvalidPagination) { - c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) + logger.Warnf("%s: 无效的分页参数: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) } - c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) + logger.Errorf("%s: 服务层查询失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取用户操作日志失败: "+err.Error(), actionType, "服务层查询失败", req) } - c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) + logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取用户操作日志成功", resp, actionType, "获取用户操作日志成功", req) } @@ -233,26 +241,27 @@ func (c *Controller) ListUserActionLogs(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.ListRawMaterialPurchaseResponse} // @Router /api/v1/monitor/raw-material-purchases [get] func (c *Controller) ListRawMaterialPurchases(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListRawMaterialPurchases") const actionType = "获取原料采购记录列表" var req dto.ListRawMaterialPurchaseRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) } - resp, err := c.monitorService.ListRawMaterialPurchases(&req) + resp, err := c.monitorService.ListRawMaterialPurchases(reqCtx, &req) if err != nil { if errors.Is(err, repository.ErrInvalidPagination) { - c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) + logger.Warnf("%s: 无效的分页参数: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) } - c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) + logger.Errorf("%s: 服务层查询失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取原料采购记录失败: "+err.Error(), actionType, "服务层查询失败", req) } - c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) + logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取原料采购记录成功", resp, actionType, "获取原料采购记录成功", req) } @@ -266,26 +275,27 @@ func (c *Controller) ListRawMaterialPurchases(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.ListRawMaterialStockLogResponse} // @Router /api/v1/monitor/raw-material-stock-logs [get] func (c *Controller) ListRawMaterialStockLogs(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListRawMaterialStockLogs") const actionType = "获取原料库存日志列表" var req dto.ListRawMaterialStockLogRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) } - resp, err := c.monitorService.ListRawMaterialStockLogs(&req) + resp, err := c.monitorService.ListRawMaterialStockLogs(reqCtx, &req) if err != nil { if errors.Is(err, repository.ErrInvalidPagination) { - c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) + logger.Warnf("%s: 无效的分页参数: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) } - c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) + logger.Errorf("%s: 服务层查询失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取原料库存日志失败: "+err.Error(), actionType, "服务层查询失败", req) } - c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) + logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取原料库存日志成功", resp, actionType, "获取原料库存日志成功", req) } @@ -299,26 +309,27 @@ func (c *Controller) ListRawMaterialStockLogs(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.ListFeedUsageRecordResponse} // @Router /api/v1/monitor/feed-usage-records [get] func (c *Controller) ListFeedUsageRecords(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListFeedUsageRecords") const actionType = "获取饲料使用记录列表" var req dto.ListFeedUsageRecordRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) } - resp, err := c.monitorService.ListFeedUsageRecords(&req) + resp, err := c.monitorService.ListFeedUsageRecords(reqCtx, &req) if err != nil { if errors.Is(err, repository.ErrInvalidPagination) { - c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) + logger.Warnf("%s: 无效的分页参数: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) } - c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) + logger.Errorf("%s: 服务层查询失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取饲料使用记录失败: "+err.Error(), actionType, "服务层查询失败", req) } - c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) + logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取饲料使用记录成功", resp, actionType, "获取饲料使用记录成功", req) } @@ -332,26 +343,27 @@ func (c *Controller) ListFeedUsageRecords(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.ListMedicationLogResponse} // @Router /api/v1/monitor/medication-logs [get] func (c *Controller) ListMedicationLogs(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListMedicationLogs") const actionType = "获取用药记录列表" var req dto.ListMedicationLogRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) } - resp, err := c.monitorService.ListMedicationLogs(&req) + resp, err := c.monitorService.ListMedicationLogs(reqCtx, &req) if err != nil { if errors.Is(err, repository.ErrInvalidPagination) { - c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) + logger.Warnf("%s: 无效的分页参数: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) } - c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) + logger.Errorf("%s: 服务层查询失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取用药记录失败: "+err.Error(), actionType, "服务层查询失败", req) } - c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) + logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取用药记录成功", resp, actionType, "获取用药记录成功", req) } @@ -365,26 +377,27 @@ func (c *Controller) ListMedicationLogs(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.ListPigBatchLogResponse} // @Router /api/v1/monitor/pig-batch-logs [get] func (c *Controller) ListPigBatchLogs(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListPigBatchLogs") const actionType = "获取猪批次日志列表" var req dto.ListPigBatchLogRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) } - resp, err := c.monitorService.ListPigBatchLogs(&req) + resp, err := c.monitorService.ListPigBatchLogs(reqCtx, &req) if err != nil { if errors.Is(err, repository.ErrInvalidPagination) { - c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) + logger.Warnf("%s: 无效的分页参数: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) } - c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) + logger.Errorf("%s: 服务层查询失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取猪批次日志失败: "+err.Error(), actionType, "服务层查询失败", req) } - c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) + logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取猪批次日志成功", resp, actionType, "获取猪批次日志成功", req) } @@ -398,26 +411,27 @@ func (c *Controller) ListPigBatchLogs(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.ListWeighingBatchResponse} // @Router /api/v1/monitor/weighing-batches [get] func (c *Controller) ListWeighingBatches(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListWeighingBatches") const actionType = "获取批次称重记录列表" var req dto.ListWeighingBatchRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) } - resp, err := c.monitorService.ListWeighingBatches(&req) + resp, err := c.monitorService.ListWeighingBatches(reqCtx, &req) if err != nil { if errors.Is(err, repository.ErrInvalidPagination) { - c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) + logger.Warnf("%s: 无效的分页参数: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) } - c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) + logger.Errorf("%s: 服务层查询失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取批次称重记录失败: "+err.Error(), actionType, "服务层查询失败", req) } - c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) + logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取批次称重记录成功", resp, actionType, "获取批次称重记录成功", req) } @@ -431,26 +445,27 @@ func (c *Controller) ListWeighingBatches(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.ListWeighingRecordResponse} // @Router /api/v1/monitor/weighing-records [get] func (c *Controller) ListWeighingRecords(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListWeighingRecords") const actionType = "获取单次称重记录列表" var req dto.ListWeighingRecordRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) } - resp, err := c.monitorService.ListWeighingRecords(&req) + resp, err := c.monitorService.ListWeighingRecords(reqCtx, &req) if err != nil { if errors.Is(err, repository.ErrInvalidPagination) { - c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) + logger.Warnf("%s: 无效的分页参数: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) } - c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) + logger.Errorf("%s: 服务层查询失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取单次称重记录失败: "+err.Error(), actionType, "服务层查询失败", req) } - c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) + logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取单次称重记录成功", resp, actionType, "获取单次称重记录成功", req) } @@ -464,26 +479,27 @@ func (c *Controller) ListWeighingRecords(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.ListPigTransferLogResponse} // @Router /api/v1/monitor/pig-transfer-logs [get] func (c *Controller) ListPigTransferLogs(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListPigTransferLogs") const actionType = "获取猪只迁移日志列表" var req dto.ListPigTransferLogRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) } - resp, err := c.monitorService.ListPigTransferLogs(&req) + resp, err := c.monitorService.ListPigTransferLogs(reqCtx, &req) if err != nil { if errors.Is(err, repository.ErrInvalidPagination) { - c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) + logger.Warnf("%s: 无效的分页参数: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) } - c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) + logger.Errorf("%s: 服务层查询失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取猪只迁移日志失败: "+err.Error(), actionType, "服务层查询失败", req) } - c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) + logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取猪只迁移日志成功", resp, actionType, "获取猪只迁移日志成功", req) } @@ -497,26 +513,27 @@ func (c *Controller) ListPigTransferLogs(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.ListPigSickLogResponse} // @Router /api/v1/monitor/pig-sick-logs [get] func (c *Controller) ListPigSickLogs(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListPigSickLogs") const actionType = "获取病猪日志列表" var req dto.ListPigSickLogRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) } - resp, err := c.monitorService.ListPigSickLogs(&req) + resp, err := c.monitorService.ListPigSickLogs(reqCtx, &req) if err != nil { if errors.Is(err, repository.ErrInvalidPagination) { - c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) + logger.Warnf("%s: 无效的分页参数: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) } - c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) + logger.Errorf("%s: 服务层查询失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取病猪日志失败: "+err.Error(), actionType, "服务层查询失败", req) } - c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) + logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取病猪日志成功", resp, actionType, "获取病猪日志成功", req) } @@ -530,26 +547,27 @@ func (c *Controller) ListPigSickLogs(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.ListPigPurchaseResponse} // @Router /api/v1/monitor/pig-purchases [get] func (c *Controller) ListPigPurchases(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListPigPurchases") const actionType = "获取猪只采购记录列表" var req dto.ListPigPurchaseRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) } - resp, err := c.monitorService.ListPigPurchases(&req) + resp, err := c.monitorService.ListPigPurchases(reqCtx, &req) if err != nil { if errors.Is(err, repository.ErrInvalidPagination) { - c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) + logger.Warnf("%s: 无效的分页参数: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) } - c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) + logger.Errorf("%s: 服务层查询失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取猪只采购记录失败: "+err.Error(), actionType, "服务层查询失败", req) } - c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) + logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取猪只采购记录成功", resp, actionType, "获取猪只采购记录成功", req) } @@ -563,26 +581,27 @@ func (c *Controller) ListPigPurchases(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.ListPigSaleResponse} // @Router /api/v1/monitor/pig-sales [get] func (c *Controller) ListPigSales(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListPigSales") const actionType = "获取猪只售卖记录列表" var req dto.ListPigSaleRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) } - resp, err := c.monitorService.ListPigSales(&req) + resp, err := c.monitorService.ListPigSales(reqCtx, &req) if err != nil { if errors.Is(err, repository.ErrInvalidPagination) { - c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) + logger.Warnf("%s: 无效的分页参数: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) } - c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) + logger.Errorf("%s: 服务层查询失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取猪只售卖记录失败: "+err.Error(), actionType, "服务层查询失败", req) } - c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) + logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取猪只售卖记录成功", resp, actionType, "获取猪只售卖记录成功", req) } @@ -596,25 +615,26 @@ func (c *Controller) ListPigSales(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.ListNotificationResponse} // @Router /api/v1/monitor/notifications [get] func (c *Controller) ListNotifications(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListNotifications") const actionType = "批量查询通知" var req dto.ListNotificationRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) } - resp, err := c.monitorService.ListNotifications(&req) + resp, err := c.monitorService.ListNotifications(reqCtx, &req) if err != nil { if errors.Is(err, repository.ErrInvalidPagination) { - c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) + logger.Warnf("%s: 无效的分页参数: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) } - c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) + logger.Errorf("%s: 服务层查询失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "批量查询通知失败: "+err.Error(), actionType, "服务层查询失败", req) } - c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) + logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "批量查询通知成功", resp, actionType, "批量查询通知成功", req) } diff --git a/internal/app/controller/plan/plan_controller.go b/internal/app/controller/plan/plan_controller.go index a249707..35fa6b5 100644 --- a/internal/app/controller/plan/plan_controller.go +++ b/internal/app/controller/plan/plan_controller.go @@ -1,6 +1,7 @@ package plan import ( + "context" "errors" "strconv" @@ -9,6 +10,7 @@ import ( "git.huangwc.com/pig/pig-farm-controller/internal/app/service" "git.huangwc.com/pig/pig-farm-controller/internal/domain/plan" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "github.com/labstack/echo/v4" ) @@ -16,14 +18,14 @@ import ( // Controller 定义了计划相关的控制器 type Controller struct { - logger *logs.Logger + ctx context.Context planService service.PlanService } // NewController 创建一个新的 Controller 实例 -func NewController(logger *logs.Logger, planService service.PlanService) *Controller { +func NewController(ctx context.Context, planService service.PlanService) *Controller { return &Controller{ - logger: logger, + ctx: ctx, planService: planService, } } @@ -41,17 +43,18 @@ func NewController(logger *logs.Logger, planService service.PlanService) *Contro // @Success 200 {object} controller.Response{data=dto.PlanResponse} "业务码为201代表创建成功" // @Router /api/v1/plans [post] func (c *Controller) CreatePlan(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "CreatePlan") var req dto.CreatePlanRequest const actionType = "创建计划" if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) } // 调用服务层创建计划 - resp, err := c.planService.CreatePlan(&req) + resp, err := c.planService.CreatePlan(reqCtx, &req) if err != nil { - c.logger.Errorf("%s: 服务层创建计划失败: %v", actionType, err) + logger.Errorf("%s: 服务层创建计划失败: %v", actionType, err) // 根据服务层返回的错误类型,转换为相应的HTTP状态码 if errors.Is(err, plan.ErrPlanNotFound) { // 修改为 plan.ErrPlanNotFound return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), actionType, "计划数据校验失败或关联计划不存在", req) @@ -60,7 +63,7 @@ func (c *Controller) CreatePlan(ctx echo.Context) error { } // 使用统一的成功响应函数 - c.logger.Infof("%s: 计划创建成功, ID: %d", actionType, resp.ID) + logger.Infof("%s: 计划创建成功, ID: %d", actionType, resp.ID) return controller.SendSuccessWithAudit(ctx, controller.CodeCreated, "计划创建成功", resp, actionType, "计划创建成功", resp) } @@ -74,19 +77,20 @@ func (c *Controller) CreatePlan(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.PlanResponse} "业务码为200代表成功获取" // @Router /api/v1/plans/{id} [get] func (c *Controller) GetPlan(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "GetPlan") const actionType = "获取计划详情" // 1. 从 URL 路径中获取 ID idStr := ctx.Param("id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { - c.logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr) + logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的计划ID格式", actionType, "计划ID格式错误", idStr) } // 调用服务层获取计划详情 - resp, err := c.planService.GetPlanByID(uint(id)) + resp, err := c.planService.GetPlanByID(reqCtx, uint(id)) if err != nil { - c.logger.Errorf("%s: 服务层获取计划详情失败: %v, ID: %d", actionType, err, id) + logger.Errorf("%s: 服务层获取计划详情失败: %v, ID: %d", actionType, err, id) if errors.Is(err, plan.ErrPlanNotFound) { // 修改为 plan.ErrPlanNotFound return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), actionType, "计划不存在", id) } @@ -94,7 +98,7 @@ func (c *Controller) GetPlan(ctx echo.Context) error { } // 4. 发送成功响应 - c.logger.Infof("%s: 获取计划详情成功, ID: %d", actionType, id) + logger.Infof("%s: 获取计划详情成功, ID: %d", actionType, id) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取计划详情成功", resp, actionType, "获取计划详情成功", resp) } @@ -108,21 +112,22 @@ func (c *Controller) GetPlan(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.ListPlansResponse} "业务码为200代表成功获取列表" // @Router /api/v1/plans [get] func (c *Controller) ListPlans(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListPlans") const actionType = "获取计划列表" var query dto.ListPlansQuery if err := ctx.Bind(&query); err != nil { - c.logger.Errorf("%s: 查询参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 查询参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "查询参数绑定失败", query) } // 调用服务层获取计划列表 - resp, err := c.planService.ListPlans(&query) + resp, err := c.planService.ListPlans(reqCtx, &query) if err != nil { - c.logger.Errorf("%s: 服务层获取计划列表失败: %v", actionType, err) + logger.Errorf("%s: 服务层获取计划列表失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划列表失败: "+err.Error(), actionType, "服务层获取计划列表失败", nil) } - c.logger.Infof("%s: 获取计划列表成功, 数量: %d", actionType, len(resp.Plans)) + logger.Infof("%s: 获取计划列表成功, 数量: %d", actionType, len(resp.Plans)) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取计划列表成功", resp, actionType, "获取计划列表成功", resp) } @@ -138,26 +143,27 @@ func (c *Controller) ListPlans(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.PlanResponse} "业务码为200代表更新成功" // @Router /api/v1/plans/{id} [put] func (c *Controller) UpdatePlan(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "UpdatePlan") const actionType = "更新计划" // 1. 从 URL 路径中获取 ID idStr := ctx.Param("id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { - c.logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr) + logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的计划ID格式", actionType, "计划ID格式错误", idStr) } // 2. 绑定请求体 var req dto.UpdatePlanRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) } // 调用服务层更新计划 - resp, err := c.planService.UpdatePlan(uint(id), &req) + resp, err := c.planService.UpdatePlan(reqCtx, uint(id), &req) if err != nil { - c.logger.Errorf("%s: 服务层更新计划失败: %v, ID: %d", actionType, err, id) + logger.Errorf("%s: 服务层更新计划失败: %v, ID: %d", actionType, err, id) if errors.Is(err, plan.ErrPlanNotFound) { // 修改为 plan.ErrPlanNotFound return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), actionType, "计划不存在", id) } else if errors.Is(err, plan.ErrPlanCannotBeModified) { // 修改为 plan.ErrPlanCannotBeModified @@ -167,7 +173,7 @@ func (c *Controller) UpdatePlan(ctx echo.Context) error { } // 9. 发送成功响应 - c.logger.Infof("%s: 计划更新成功, ID: %d", actionType, resp.ID) + logger.Infof("%s: 计划更新成功, ID: %d", actionType, resp.ID) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "计划更新成功", resp, actionType, "计划更新成功", resp) } @@ -181,19 +187,20 @@ func (c *Controller) UpdatePlan(ctx echo.Context) error { // @Success 200 {object} controller.Response "业务码为200代表删除成功" // @Router /api/v1/plans/{id} [delete] func (c *Controller) DeletePlan(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "DeletePlan") const actionType = "删除计划" // 1. 从 URL 路径中获取 ID idStr := ctx.Param("id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { - c.logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr) + logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的计划ID格式", actionType, "计划ID格式错误", idStr) } // 调用服务层删除计划 - err = c.planService.DeletePlan(uint(id)) + err = c.planService.DeletePlan(reqCtx, uint(id)) if err != nil { - c.logger.Errorf("%s: 服务层删除计划失败: %v, ID: %d", actionType, err, id) + logger.Errorf("%s: 服务层删除计划失败: %v, ID: %d", actionType, err, id) if errors.Is(err, plan.ErrPlanNotFound) { // 修改为 plan.ErrPlanNotFound return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), actionType, "计划不存在", id) } else if errors.Is(err, plan.ErrPlanCannotBeDeleted) { // 修改为 plan.ErrPlanCannotBeDeleted @@ -203,7 +210,7 @@ func (c *Controller) DeletePlan(ctx echo.Context) error { } // 6. 发送成功响应 - c.logger.Infof("%s: 计划删除成功, ID: %d", actionType, id) + logger.Infof("%s: 计划删除成功, ID: %d", actionType, id) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "计划删除成功", nil, actionType, "计划删除成功", id) } @@ -217,19 +224,20 @@ func (c *Controller) DeletePlan(ctx echo.Context) error { // @Success 200 {object} controller.Response "业务码为200代表成功启动计划" // @Router /api/v1/plans/{id}/start [post] func (c *Controller) StartPlan(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "StartPlan") const actionType = "启动计划" // 1. 从 URL 路径中获取 ID idStr := ctx.Param("id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { - c.logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr) + logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的计划ID格式", actionType, "计划ID格式错误", idStr) } // 调用服务层启动计划 - err = c.planService.StartPlan(uint(id)) + err = c.planService.StartPlan(reqCtx, uint(id)) if err != nil { - c.logger.Errorf("%s: 服务层启动计划失败: %v, ID: %d", actionType, err, id) + logger.Errorf("%s: 服务层启动计划失败: %v, ID: %d", actionType, err, id) if errors.Is(err, plan.ErrPlanNotFound) { // 修改为 plan.ErrPlanNotFound return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), actionType, "计划不存在", id) } else if errors.Is(err, plan.ErrPlanCannotBeStarted) { // 修改为 plan.ErrPlanCannotBeStarted @@ -241,7 +249,7 @@ func (c *Controller) StartPlan(ctx echo.Context) error { } // 6. 发送成功响应 - c.logger.Infof("%s: 计划已成功启动, ID: %d", actionType, id) + logger.Infof("%s: 计划已成功启动, ID: %d", actionType, id) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "计划已成功启动", nil, actionType, "计划已成功启动", id) } @@ -255,19 +263,20 @@ func (c *Controller) StartPlan(ctx echo.Context) error { // @Success 200 {object} controller.Response "业务码为200代表成功停止计划" // @Router /api/v1/plans/{id}/stop [post] func (c *Controller) StopPlan(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "StopPlan") const actionType = "停止计划" // 1. 从 URL 路径中获取 ID idStr := ctx.Param("id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { - c.logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr) + logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的计划ID格式", actionType, "计划ID格式错误", idStr) } // 调用服务层停止计划 - err = c.planService.StopPlan(uint(id)) + err = c.planService.StopPlan(reqCtx, uint(id)) if err != nil { - c.logger.Errorf("%s: 服务层停止计划失败: %v, ID: %d", actionType, err, id) + logger.Errorf("%s: 服务层停止计划失败: %v, ID: %d", actionType, err, id) if errors.Is(err, plan.ErrPlanNotFound) { // 修改为 plan.ErrPlanNotFound return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), actionType, "计划不存在", id) } else if errors.Is(err, plan.ErrPlanCannotBeStopped) { // 修改为 plan.ErrPlanCannotBeStopped @@ -279,6 +288,6 @@ func (c *Controller) StopPlan(ctx echo.Context) error { } // 6. 发送成功响应 - c.logger.Infof("%s: 计划已成功停止, ID: %d", actionType, id) + logger.Infof("%s: 计划已成功停止, ID: %d", actionType, id) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "计划已成功停止", nil, actionType, "计划已成功停止", id) } diff --git a/internal/app/controller/response.go b/internal/app/controller/response.go index 5986159..4fdaae8 100644 --- a/internal/app/controller/response.go +++ b/internal/app/controller/response.go @@ -4,6 +4,7 @@ import ( "net/http" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "github.com/labstack/echo/v4" ) diff --git a/internal/app/controller/user/user_controller.go b/internal/app/controller/user/user_controller.go index 8e3df2b..1aa38c3 100644 --- a/internal/app/controller/user/user_controller.go +++ b/internal/app/controller/user/user_controller.go @@ -1,29 +1,31 @@ package user import ( + "context" "strconv" "git.huangwc.com/pig/pig-farm-controller/internal/app/controller" "git.huangwc.com/pig/pig-farm-controller/internal/app/dto" "git.huangwc.com/pig/pig-farm-controller/internal/app/service" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "github.com/labstack/echo/v4" ) // Controller 用户控制器 type Controller struct { + ctx context.Context userService service.UserService - logger *logs.Logger } // NewController 创建用户控制器实例 func NewController( + ctx context.Context, userService service.UserService, - logger *logs.Logger, ) *Controller { return &Controller{ + ctx: ctx, userService: userService, - logger: logger, } } @@ -39,15 +41,17 @@ func NewController( // @Success 200 {object} controller.Response{data=dto.CreateUserResponse} "业务码为201代表创建成功" // @Router /api/v1/users [post] func (c *Controller) CreateUser(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "CreateUser") + var req dto.CreateUserRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("创建用户: 参数绑定失败: %v", err) + logger.Errorf("创建用户: 参数绑定失败: %v", err) return controller.SendErrorResponse(ctx, controller.CodeBadRequest, err.Error()) } - resp, err := c.userService.CreateUser(&req) + resp, err := c.userService.CreateUser(reqCtx, &req) if err != nil { - c.logger.Errorf("创建用户: 服务层调用失败: %v", err) + logger.Errorf("创建用户: 服务层调用失败: %v", err) return controller.SendErrorResponse(ctx, controller.CodeInternalError, err.Error()) } @@ -64,15 +68,17 @@ func (c *Controller) CreateUser(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=dto.LoginResponse} "业务码为200代表登录成功" // @Router /api/v1/users/login [post] func (c *Controller) Login(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "Login") + var req dto.LoginRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("登录: 参数绑定失败: %v", err) + logger.Errorf("登录: 参数绑定失败: %v", err) return controller.SendErrorResponse(ctx, controller.CodeBadRequest, err.Error()) } - resp, err := c.userService.Login(&req) + resp, err := c.userService.Login(reqCtx, &req) if err != nil { - c.logger.Errorf("登录: 服务层调用失败: %v", err) + logger.Errorf("登录: 服务层调用失败: %v", err) return controller.SendErrorResponse(ctx, controller.CodeUnauthorized, err.Error()) } @@ -91,30 +97,31 @@ func (c *Controller) Login(ctx echo.Context) error { // @Success 200 {object} controller.Response{data=string} "成功响应" // @Router /api/v1/users/{id}/notifications/test [post] func (c *Controller) SendTestNotification(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "SendTestNotification") const actionType = "发送测试通知" // 1. 从 URL 中获取用户 ID userID, err := strconv.ParseUint(ctx.Param("id"), 10, 32) if err != nil { - c.logger.Errorf("%s: 无效的用户ID格式: %v", actionType, err) + logger.Errorf("%s: 无效的用户ID格式: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的用户ID格式", actionType, "无效的用户ID格式", ctx.Param("id")) } // 2. 从请求体 (JSON Body) 中获取要测试的通知类型 var req dto.SendTestNotificationRequest if err := ctx.Bind(&req); err != nil { - c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "请求体格式错误或缺少 'type' 字段: "+err.Error(), actionType, "请求体绑定失败", req) } // 3. 调用服务层 - err = c.userService.SendTestNotification(uint(userID), &req) + err = c.userService.SendTestNotification(reqCtx, uint(userID), &req) if err != nil { - c.logger.Errorf("%s: 服务层调用失败: %v", actionType, err) + logger.Errorf("%s: 服务层调用失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "发送测试消息失败: "+err.Error(), actionType, "服务层调用失败", map[string]interface{}{"userID": userID, "type": req.Type}) } // 4. 返回成功响应 - c.logger.Infof("%s: 成功为用户 %d 发送类型为 %s 的测试消息", actionType, userID, req.Type) + logger.Infof("%s: 成功为用户 %d 发送类型为 %s 的测试消息", actionType, userID, req.Type) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "测试消息已发送,请检查您的接收端。", nil, actionType, "测试消息发送成功", map[string]interface{}{"userID": userID, "type": req.Type}) } diff --git a/internal/app/dto/notification_converter.go b/internal/app/dto/notification_converter.go index 5df25a8..fbc49b7 100644 --- a/internal/app/dto/notification_converter.go +++ b/internal/app/dto/notification_converter.go @@ -2,6 +2,7 @@ package dto import ( "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "go.uber.org/zap/zapcore" ) diff --git a/internal/app/dto/notification_dto.go b/internal/app/dto/notification_dto.go index 9cbc9b0..b493d4e 100644 --- a/internal/app/dto/notification_dto.go +++ b/internal/app/dto/notification_dto.go @@ -5,6 +5,7 @@ import ( "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "git.huangwc.com/pig/pig-farm-controller/internal/infra/notify" + "go.uber.org/zap/zapcore" ) diff --git a/internal/app/middleware/audit.go b/internal/app/middleware/audit.go index 7479dfe..9af8dea 100644 --- a/internal/app/middleware/audit.go +++ b/internal/app/middleware/audit.go @@ -1,16 +1,22 @@ package middleware import ( - "git.huangwc.com/pig/pig-farm-controller/internal/domain/audit" + "context" + + "git.huangwc.com/pig/pig-farm-controller/internal/app/service" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "github.com/labstack/echo/v4" ) // AuditLogMiddleware 创建一个Echo中间件,用于在请求结束后记录用户操作审计日志。 // 它依赖于控制器通过调用 SendSuccessWithAudit 或 SendErrorWithAudit 在上下文中设置的审计信息。 -func AuditLogMiddleware(auditService audit.Service) echo.MiddlewareFunc { +func AuditLogMiddleware(ctx context.Context, auditService service.AuditService) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { + newCtx := logs.AddFuncName(c.Request().Context(), ctx, "AuditLogMiddleware") + // 首先执行请求链中的后续处理程序(即业务控制器) err := next(c) @@ -29,8 +35,8 @@ func AuditLogMiddleware(auditService audit.Service) echo.MiddlewareFunc { user, _ = userCtx.(*models.User) } - // 构建 RequestContext - reqCtx := audit.RequestContext{ + // 构建 AuditRequestContext + reqCtx := service.AuditRequestContext{ ClientIP: c.RealIP(), HTTPPath: c.Request().URL.Path, HTTPMethod: c.Request().Method, @@ -42,8 +48,12 @@ func AuditLogMiddleware(auditService audit.Service) echo.MiddlewareFunc { status, _ := c.Get(models.ContextAuditStatus.String()).(models.AuditStatus) resultDetails, _ := c.Get(models.ContextAuditResultDetails.String()).(string) + // 为异步任务创建一个分离的 Context,以防止原始请求的 Context 被取消 + detachedCtx := logs.DetachContext(newCtx) + // 调用审计服务记录日志(异步) auditService.LogAction( + detachedCtx, user, reqCtx, actionType, diff --git a/internal/app/middleware/auth.go b/internal/app/middleware/auth.go index 57a757d..1607e14 100644 --- a/internal/app/middleware/auth.go +++ b/internal/app/middleware/auth.go @@ -2,23 +2,28 @@ package middleware import ( + "context" "errors" "net/http" "strings" "git.huangwc.com/pig/pig-farm-controller/internal/app/controller" - "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" + "github.com/labstack/echo/v4" "gorm.io/gorm" ) // AuthMiddleware 创建一个Echo中间件,用于JWT身份验证 // 它依赖于 TokenService 来解析和验证 token,并使用 UserRepository 来获取完整的用户信息 -func AuthMiddleware(tokenService token.Service, userRepo repository.UserRepository) echo.MiddlewareFunc { +func AuthMiddleware(ctx context.Context, tokenGenerator token.Generator, userRepo repository.UserRepository) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { + reqCtx := logs.AddFuncName(c.Request().Context(), ctx, "AuthMiddleware") + // 从 Authorization header 获取 token authHeader := c.Request().Header.Get("Authorization") if authHeader == "" { @@ -34,13 +39,13 @@ func AuthMiddleware(tokenService token.Service, userRepo repository.UserReposito tokenString := parts[1] // 解析和验证 token - claims, err := tokenService.ParseToken(tokenString) + claims, err := tokenGenerator.ParseToken(tokenString) if err != nil { return controller.SendErrorWithStatus(c, http.StatusUnauthorized, controller.CodeUnauthorized, "无效的Token") } // 根据 token 中的用户ID,从数据库中获取完整的用户信息 - user, err := userRepo.FindByID(claims.UserID) + user, err := userRepo.FindByID(reqCtx, claims.UserID) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { // Token有效,但对应的用户已不存在 diff --git a/internal/app/service/audit_service.go b/internal/app/service/audit_service.go new file mode 100644 index 0000000..4324a5b --- /dev/null +++ b/internal/app/service/audit_service.go @@ -0,0 +1,74 @@ +// Package audit 提供了用户操作审计相关的功能 +package service + +import ( + "context" + "time" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" +) + +// AuditRequestContext 封装了审计日志所需的请求上下文信息 +type AuditRequestContext struct { + ClientIP string + HTTPPath string + HTTPMethod string +} + +// AuditService 定义了审计服务的接口 +type AuditService interface { + LogAction(ctx context.Context, user *models.User, reqCtx AuditRequestContext, actionType, description string, targetResource interface{}, status models.AuditStatus, resultDetails string) +} + +// auditService 是 AuditService 接口的实现 +type auditService struct { + ctx context.Context + userActionLogRepository repository.UserActionLogRepository +} + +// NewAuditService 创建一个新的审计服务实例 +func NewAuditService(ctx context.Context, repo repository.UserActionLogRepository) AuditService { + return &auditService{ + ctx: ctx, + userActionLogRepository: repo, + } +} + +// LogAction 记录一个用户操作。它在一个新的 goroutine 中异步执行,以避免阻塞主请求。 +func (s *auditService) LogAction(ctx context.Context, user *models.User, reqCtx AuditRequestContext, actionType, description string, targetResource interface{}, status models.AuditStatus, resultDetails string) { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "LogAction") + + // 不再从 context 中获取用户信息,直接使用传入的 user 对象 + if user == nil { + logger.Warnw("无法记录审计日志:传入的用户对象为 nil") + return + } + + log := &models.UserActionLog{ + Time: time.Now(), + UserID: user.ID, + Username: user.Username, // 用户名快照 + SourceIP: reqCtx.ClientIP, + ActionType: actionType, + Description: description, + Status: status, + HTTPPath: reqCtx.HTTPPath, + HTTPMethod: reqCtx.HTTPMethod, + ResultDetails: resultDetails, + } + + // 使用模型提供的方法来设置 TargetResource + if err := log.SetTargetResource(targetResource); err != nil { + logger.Errorw("无法记录审计日志:序列化 targetResource 失败", "error", err) + // 即使序列化失败,我们可能仍然希望记录操作本身,所以不在此处 return + } + + // 异步写入数据库,不阻塞当前请求 + go func() { + if err := s.userActionLogRepository.Create(serviceCtx, log); err != nil { + logger.Errorw("异步保存审计日志失败", "error", err) + } + }() +} diff --git a/internal/app/service/device_service.go b/internal/app/service/device_service.go index 82733a2..d00080e 100644 --- a/internal/app/service/device_service.go +++ b/internal/app/service/device_service.go @@ -1,12 +1,14 @@ package service import ( + "context" "encoding/json" "errors" "fmt" "git.huangwc.com/pig/pig-farm-controller/internal/app/dto" "git.huangwc.com/pig/pig-farm-controller/internal/domain/device" + "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" ) @@ -25,42 +27,45 @@ var ( // DeviceService 定义了应用层的设备服务接口,用于协调设备相关的业务逻辑。 // DeviceService 定义了应用层的设备服务接口,用于协调设备相关的业务逻辑。 type DeviceService interface { - CreateDevice(req *dto.CreateDeviceRequest) (*dto.DeviceResponse, error) - GetDevice(id uint) (*dto.DeviceResponse, error) - ListDevices() ([]*dto.DeviceResponse, error) - UpdateDevice(id uint, req *dto.UpdateDeviceRequest) (*dto.DeviceResponse, error) - DeleteDevice(id uint) error - ManualControl(id uint, req *dto.ManualControlDeviceRequest) error + CreateDevice(ctx context.Context, req *dto.CreateDeviceRequest) (*dto.DeviceResponse, error) + GetDevice(ctx context.Context, id uint) (*dto.DeviceResponse, error) + ListDevices(ctx context.Context) ([]*dto.DeviceResponse, error) + UpdateDevice(ctx context.Context, id uint, req *dto.UpdateDeviceRequest) (*dto.DeviceResponse, error) + DeleteDevice(ctx context.Context, id uint) error + ManualControl(ctx context.Context, id uint, req *dto.ManualControlDeviceRequest) error - CreateAreaController(req *dto.CreateAreaControllerRequest) (*dto.AreaControllerResponse, error) - GetAreaController(id uint) (*dto.AreaControllerResponse, error) - ListAreaControllers() ([]*dto.AreaControllerResponse, error) - UpdateAreaController(id uint, req *dto.UpdateAreaControllerRequest) (*dto.AreaControllerResponse, error) - DeleteAreaController(id uint) error + CreateAreaController(ctx context.Context, req *dto.CreateAreaControllerRequest) (*dto.AreaControllerResponse, error) + GetAreaController(ctx context.Context, id uint) (*dto.AreaControllerResponse, error) + ListAreaControllers(ctx context.Context) ([]*dto.AreaControllerResponse, error) + UpdateAreaController(ctx context.Context, id uint, req *dto.UpdateAreaControllerRequest) (*dto.AreaControllerResponse, error) + DeleteAreaController(ctx context.Context, id uint) error - CreateDeviceTemplate(req *dto.CreateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error) - GetDeviceTemplate(id uint) (*dto.DeviceTemplateResponse, error) - ListDeviceTemplates() ([]*dto.DeviceTemplateResponse, error) - UpdateDeviceTemplate(id uint, req *dto.UpdateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error) - DeleteDeviceTemplate(id uint) error + CreateDeviceTemplate(ctx context.Context, req *dto.CreateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error) + GetDeviceTemplate(ctx context.Context, id uint) (*dto.DeviceTemplateResponse, error) + ListDeviceTemplates(ctx context.Context) ([]*dto.DeviceTemplateResponse, error) + UpdateDeviceTemplate(ctx context.Context, id uint, req *dto.UpdateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error) + DeleteDeviceTemplate(ctx context.Context, id uint) error } // deviceService 是 DeviceService 接口的具体实现。 type deviceService struct { + ctx context.Context deviceRepo repository.DeviceRepository areaControllerRepo repository.AreaControllerRepository deviceTemplateRepo repository.DeviceTemplateRepository - deviceDomainSvc device.Service // 依赖领域服务 + deviceDomainSvc device.Service } // NewDeviceService 创建一个新的 DeviceService 实例。 func NewDeviceService( + ctx context.Context, deviceRepo repository.DeviceRepository, areaControllerRepo repository.AreaControllerRepository, deviceTemplateRepo repository.DeviceTemplateRepository, deviceDomainSvc device.Service, ) DeviceService { return &deviceService{ + ctx: ctx, deviceRepo: deviceRepo, areaControllerRepo: areaControllerRepo, deviceTemplateRepo: deviceTemplateRepo, @@ -70,7 +75,8 @@ func NewDeviceService( // --- Devices --- -func (s *deviceService) CreateDevice(req *dto.CreateDeviceRequest) (*dto.DeviceResponse, error) { +func (s *deviceService) CreateDevice(ctx context.Context, req *dto.CreateDeviceRequest) (*dto.DeviceResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreateDevice") propertiesJSON, err := json.Marshal(req.Properties) if err != nil { return nil, err // Consider wrapping this error for better context @@ -88,11 +94,11 @@ func (s *deviceService) CreateDevice(req *dto.CreateDeviceRequest) (*dto.DeviceR return nil, err } - if err := s.deviceRepo.Create(device); err != nil { + if err := s.deviceRepo.Create(serviceCtx, device); err != nil { return nil, err } - createdDevice, err := s.deviceRepo.FindByID(device.ID) + createdDevice, err := s.deviceRepo.FindByID(serviceCtx, device.ID) if err != nil { return nil, err } @@ -100,24 +106,27 @@ func (s *deviceService) CreateDevice(req *dto.CreateDeviceRequest) (*dto.DeviceR return dto.NewDeviceResponse(createdDevice) } -func (s *deviceService) GetDevice(id uint) (*dto.DeviceResponse, error) { - device, err := s.deviceRepo.FindByID(id) +func (s *deviceService) GetDevice(ctx context.Context, id uint) (*dto.DeviceResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetDevice") + device, err := s.deviceRepo.FindByID(serviceCtx, id) if err != nil { return nil, err } return dto.NewDeviceResponse(device) } -func (s *deviceService) ListDevices() ([]*dto.DeviceResponse, error) { - devices, err := s.deviceRepo.ListAll() +func (s *deviceService) ListDevices(ctx context.Context) ([]*dto.DeviceResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListDevices") + devices, err := s.deviceRepo.ListAll(serviceCtx) if err != nil { return nil, err } return dto.NewListDeviceResponse(devices) } -func (s *deviceService) UpdateDevice(id uint, req *dto.UpdateDeviceRequest) (*dto.DeviceResponse, error) { - existingDevice, err := s.deviceRepo.FindByID(id) +func (s *deviceService) UpdateDevice(ctx context.Context, id uint, req *dto.UpdateDeviceRequest) (*dto.DeviceResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateDevice") + existingDevice, err := s.deviceRepo.FindByID(serviceCtx, id) if err != nil { return nil, err } @@ -137,11 +146,11 @@ func (s *deviceService) UpdateDevice(id uint, req *dto.UpdateDeviceRequest) (*dt return nil, err } - if err := s.deviceRepo.Update(existingDevice); err != nil { + if err := s.deviceRepo.Update(serviceCtx, existingDevice); err != nil { return nil, err } - updatedDevice, err := s.deviceRepo.FindByID(existingDevice.ID) + updatedDevice, err := s.deviceRepo.FindByID(serviceCtx, existingDevice.ID) if err != nil { return nil, err } @@ -149,16 +158,17 @@ func (s *deviceService) UpdateDevice(id uint, req *dto.UpdateDeviceRequest) (*dt return dto.NewDeviceResponse(updatedDevice) } -func (s *deviceService) DeleteDevice(id uint) error { +func (s *deviceService) DeleteDevice(ctx context.Context, id uint) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeleteDevice") // 检查设备是否存在 - _, err := s.deviceRepo.FindByID(id) + _, err := s.deviceRepo.FindByID(serviceCtx, id) if err != nil { return err // 如果未找到,会返回 gorm.ErrRecordNotFound } // 在删除前检查设备是否被任务使用 - inUse, err := s.deviceRepo.IsDeviceInUse(id) + inUse, err := s.deviceRepo.IsDeviceInUse(serviceCtx, id) if err != nil { // 如果检查过程中发生数据库错误,则返回错误 return fmt.Errorf("检查设备使用情况失败: %w", err) @@ -169,17 +179,18 @@ func (s *deviceService) DeleteDevice(id uint) error { } // 只有在未被使用时,才执行删除操作 - return s.deviceRepo.Delete(id) + return s.deviceRepo.Delete(serviceCtx, id) } -func (s *deviceService) ManualControl(id uint, req *dto.ManualControlDeviceRequest) error { - dev, err := s.deviceRepo.FindByID(id) +func (s *deviceService) ManualControl(ctx context.Context, id uint, req *dto.ManualControlDeviceRequest) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ManualControl") + dev, err := s.deviceRepo.FindByID(serviceCtx, id) if err != nil { return err } if req.Action == nil { - return s.deviceDomainSvc.Collect(dev.AreaControllerID, []*models.Device{dev}) + return s.deviceDomainSvc.Collect(serviceCtx, dev.AreaControllerID, []*models.Device{dev}) } else { action := device.DeviceActionStart switch *req.Action { @@ -190,13 +201,14 @@ func (s *deviceService) ManualControl(id uint, req *dto.ManualControlDeviceReque default: return errors.New("invalid action") } - return s.deviceDomainSvc.Switch(dev, action) + return s.deviceDomainSvc.Switch(serviceCtx, dev, action) } } // --- Area Controllers --- -func (s *deviceService) CreateAreaController(req *dto.CreateAreaControllerRequest) (*dto.AreaControllerResponse, error) { +func (s *deviceService) CreateAreaController(ctx context.Context, req *dto.CreateAreaControllerRequest) (*dto.AreaControllerResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreateAreaController") propertiesJSON, err := json.Marshal(req.Properties) if err != nil { return nil, err @@ -213,31 +225,34 @@ func (s *deviceService) CreateAreaController(req *dto.CreateAreaControllerReques return nil, err } - if err := s.areaControllerRepo.Create(ac); err != nil { + if err := s.areaControllerRepo.Create(serviceCtx, ac); err != nil { return nil, err } return dto.NewAreaControllerResponse(ac) } -func (s *deviceService) GetAreaController(id uint) (*dto.AreaControllerResponse, error) { - ac, err := s.areaControllerRepo.FindByID(id) +func (s *deviceService) GetAreaController(ctx context.Context, id uint) (*dto.AreaControllerResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetAreaController") + ac, err := s.areaControllerRepo.FindByID(serviceCtx, id) if err != nil { return nil, err } return dto.NewAreaControllerResponse(ac) } -func (s *deviceService) ListAreaControllers() ([]*dto.AreaControllerResponse, error) { - acs, err := s.areaControllerRepo.ListAll() +func (s *deviceService) ListAreaControllers(ctx context.Context) ([]*dto.AreaControllerResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListAreaControllers") + acs, err := s.areaControllerRepo.ListAll(serviceCtx) if err != nil { return nil, err } return dto.NewListAreaControllerResponse(acs) } -func (s *deviceService) UpdateAreaController(id uint, req *dto.UpdateAreaControllerRequest) (*dto.AreaControllerResponse, error) { - existingAC, err := s.areaControllerRepo.FindByID(id) +func (s *deviceService) UpdateAreaController(ctx context.Context, id uint, req *dto.UpdateAreaControllerRequest) (*dto.AreaControllerResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateAreaController") + existingAC, err := s.areaControllerRepo.FindByID(serviceCtx, id) if err != nil { return nil, err } @@ -256,23 +271,24 @@ func (s *deviceService) UpdateAreaController(id uint, req *dto.UpdateAreaControl return nil, err } - if err := s.areaControllerRepo.Update(existingAC); err != nil { + if err := s.areaControllerRepo.Update(serviceCtx, existingAC); err != nil { return nil, err } return dto.NewAreaControllerResponse(existingAC) } -func (s *deviceService) DeleteAreaController(id uint) error { +func (s *deviceService) DeleteAreaController(ctx context.Context, id uint) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeleteAreaController") // 1. 检查是否存在 - _, err := s.areaControllerRepo.FindByID(id) + _, err := s.areaControllerRepo.FindByID(serviceCtx, id) if err != nil { return err // 如果未找到,gorm会返回 ErrRecordNotFound } // 2. 检查是否被使用(业务逻辑) - inUse, err := s.deviceRepo.IsAreaControllerInUse(id) + inUse, err := s.deviceRepo.IsAreaControllerInUse(serviceCtx, id) if err != nil { return err // 返回数据库检查错误 } @@ -281,12 +297,13 @@ func (s *deviceService) DeleteAreaController(id uint) error { } // 3. 执行删除 - return s.areaControllerRepo.Delete(id) + return s.areaControllerRepo.Delete(serviceCtx, id) } // --- Device Templates --- -func (s *deviceService) CreateDeviceTemplate(req *dto.CreateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error) { +func (s *deviceService) CreateDeviceTemplate(ctx context.Context, req *dto.CreateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreateDeviceTemplate") commandsJSON, err := json.Marshal(req.Commands) if err != nil { return nil, err @@ -310,31 +327,34 @@ func (s *deviceService) CreateDeviceTemplate(req *dto.CreateDeviceTemplateReques return nil, err } - if err := s.deviceTemplateRepo.Create(deviceTemplate); err != nil { + if err := s.deviceTemplateRepo.Create(serviceCtx, deviceTemplate); err != nil { return nil, err } return dto.NewDeviceTemplateResponse(deviceTemplate) } -func (s *deviceService) GetDeviceTemplate(id uint) (*dto.DeviceTemplateResponse, error) { - deviceTemplate, err := s.deviceTemplateRepo.FindByID(id) +func (s *deviceService) GetDeviceTemplate(ctx context.Context, id uint) (*dto.DeviceTemplateResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetDeviceTemplate") + deviceTemplate, err := s.deviceTemplateRepo.FindByID(serviceCtx, id) if err != nil { return nil, err } return dto.NewDeviceTemplateResponse(deviceTemplate) } -func (s *deviceService) ListDeviceTemplates() ([]*dto.DeviceTemplateResponse, error) { - deviceTemplates, err := s.deviceTemplateRepo.ListAll() +func (s *deviceService) ListDeviceTemplates(ctx context.Context) ([]*dto.DeviceTemplateResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListDeviceTemplates") + deviceTemplates, err := s.deviceTemplateRepo.ListAll(serviceCtx) if err != nil { return nil, err } return dto.NewListDeviceTemplateResponse(deviceTemplates) } -func (s *deviceService) UpdateDeviceTemplate(id uint, req *dto.UpdateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error) { - existingDeviceTemplate, err := s.deviceTemplateRepo.FindByID(id) +func (s *deviceService) UpdateDeviceTemplate(ctx context.Context, id uint, req *dto.UpdateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateDeviceTemplate") + existingDeviceTemplate, err := s.deviceTemplateRepo.FindByID(serviceCtx, id) if err != nil { return nil, err } @@ -360,23 +380,24 @@ func (s *deviceService) UpdateDeviceTemplate(id uint, req *dto.UpdateDeviceTempl return nil, err } - if err := s.deviceTemplateRepo.Update(existingDeviceTemplate); err != nil { + if err := s.deviceTemplateRepo.Update(serviceCtx, existingDeviceTemplate); err != nil { return nil, err } return dto.NewDeviceTemplateResponse(existingDeviceTemplate) } -func (s *deviceService) DeleteDeviceTemplate(id uint) error { +func (s *deviceService) DeleteDeviceTemplate(ctx context.Context, id uint) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeleteDeviceTemplate") // 1. 检查是否存在 - _, err := s.deviceTemplateRepo.FindByID(id) + _, err := s.deviceTemplateRepo.FindByID(serviceCtx, id) if err != nil { return err } // 2. 检查是否被使用(业务逻辑) - inUse, err := s.deviceTemplateRepo.IsInUse(id) + inUse, err := s.deviceTemplateRepo.IsInUse(serviceCtx, id) if err != nil { return err } @@ -385,5 +406,5 @@ func (s *deviceService) DeleteDeviceTemplate(id uint) error { } // 3. 执行删除 - return s.deviceTemplateRepo.Delete(id) + return s.deviceTemplateRepo.Delete(serviceCtx, id) } diff --git a/internal/app/service/monitor_service.go b/internal/app/service/monitor_service.go index 4f2f9e5..ea3bbab 100644 --- a/internal/app/service/monitor_service.go +++ b/internal/app/service/monitor_service.go @@ -1,35 +1,39 @@ package service import ( + "context" + "git.huangwc.com/pig/pig-farm-controller/internal/app/dto" + "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" ) // MonitorService 定义了监控相关的业务逻辑服务接口 type MonitorService interface { - ListSensorData(req *dto.ListSensorDataRequest) (*dto.ListSensorDataResponse, error) - ListDeviceCommandLogs(req *dto.ListDeviceCommandLogRequest) (*dto.ListDeviceCommandLogResponse, error) - ListPlanExecutionLogs(req *dto.ListPlanExecutionLogRequest) (*dto.ListPlanExecutionLogResponse, error) - ListTaskExecutionLogs(req *dto.ListTaskExecutionLogRequest) (*dto.ListTaskExecutionLogResponse, error) - ListPendingCollections(req *dto.ListPendingCollectionRequest) (*dto.ListPendingCollectionResponse, error) - ListUserActionLogs(req *dto.ListUserActionLogRequest) (*dto.ListUserActionLogResponse, error) - ListRawMaterialPurchases(req *dto.ListRawMaterialPurchaseRequest) (*dto.ListRawMaterialPurchaseResponse, error) - ListRawMaterialStockLogs(req *dto.ListRawMaterialStockLogRequest) (*dto.ListRawMaterialStockLogResponse, error) - ListFeedUsageRecords(req *dto.ListFeedUsageRecordRequest) (*dto.ListFeedUsageRecordResponse, error) - ListMedicationLogs(req *dto.ListMedicationLogRequest) (*dto.ListMedicationLogResponse, error) - ListPigBatchLogs(req *dto.ListPigBatchLogRequest) (*dto.ListPigBatchLogResponse, error) - ListWeighingBatches(req *dto.ListWeighingBatchRequest) (*dto.ListWeighingBatchResponse, error) - ListWeighingRecords(req *dto.ListWeighingRecordRequest) (*dto.ListWeighingRecordResponse, error) - ListPigTransferLogs(req *dto.ListPigTransferLogRequest) (*dto.ListPigTransferLogResponse, error) - ListPigSickLogs(req *dto.ListPigSickLogRequest) (*dto.ListPigSickLogResponse, error) - ListPigPurchases(req *dto.ListPigPurchaseRequest) (*dto.ListPigPurchaseResponse, error) - ListPigSales(req *dto.ListPigSaleRequest) (*dto.ListPigSaleResponse, error) - ListNotifications(req *dto.ListNotificationRequest) (*dto.ListNotificationResponse, error) + ListSensorData(ctx context.Context, req *dto.ListSensorDataRequest) (*dto.ListSensorDataResponse, error) + ListDeviceCommandLogs(ctx context.Context, req *dto.ListDeviceCommandLogRequest) (*dto.ListDeviceCommandLogResponse, error) + ListPlanExecutionLogs(ctx context.Context, req *dto.ListPlanExecutionLogRequest) (*dto.ListPlanExecutionLogResponse, error) + ListTaskExecutionLogs(ctx context.Context, req *dto.ListTaskExecutionLogRequest) (*dto.ListTaskExecutionLogResponse, error) + ListPendingCollections(ctx context.Context, req *dto.ListPendingCollectionRequest) (*dto.ListPendingCollectionResponse, error) + ListUserActionLogs(ctx context.Context, req *dto.ListUserActionLogRequest) (*dto.ListUserActionLogResponse, error) + ListRawMaterialPurchases(ctx context.Context, req *dto.ListRawMaterialPurchaseRequest) (*dto.ListRawMaterialPurchaseResponse, error) + ListRawMaterialStockLogs(ctx context.Context, req *dto.ListRawMaterialStockLogRequest) (*dto.ListRawMaterialStockLogResponse, error) + ListFeedUsageRecords(ctx context.Context, req *dto.ListFeedUsageRecordRequest) (*dto.ListFeedUsageRecordResponse, error) + ListMedicationLogs(ctx context.Context, req *dto.ListMedicationLogRequest) (*dto.ListMedicationLogResponse, error) + ListPigBatchLogs(ctx context.Context, req *dto.ListPigBatchLogRequest) (*dto.ListPigBatchLogResponse, error) + ListWeighingBatches(ctx context.Context, req *dto.ListWeighingBatchRequest) (*dto.ListWeighingBatchResponse, error) + ListWeighingRecords(ctx context.Context, req *dto.ListWeighingRecordRequest) (*dto.ListWeighingRecordResponse, error) + ListPigTransferLogs(ctx context.Context, req *dto.ListPigTransferLogRequest) (*dto.ListPigTransferLogResponse, error) + ListPigSickLogs(ctx context.Context, req *dto.ListPigSickLogRequest) (*dto.ListPigSickLogResponse, error) + ListPigPurchases(ctx context.Context, req *dto.ListPigPurchaseRequest) (*dto.ListPigPurchaseResponse, error) + ListPigSales(ctx context.Context, req *dto.ListPigSaleRequest) (*dto.ListPigSaleResponse, error) + ListNotifications(ctx context.Context, req *dto.ListNotificationRequest) (*dto.ListNotificationResponse, error) } // monitorService 是 MonitorService 接口的具体实现 type monitorService struct { + ctx context.Context sensorDataRepo repository.SensorDataRepository deviceCommandLogRepo repository.DeviceCommandLogRepository executionLogRepo repository.ExecutionLogRepository @@ -48,6 +52,7 @@ type monitorService struct { // NewMonitorService 创建一个新的 MonitorService 实例 func NewMonitorService( + ctx context.Context, sensorDataRepo repository.SensorDataRepository, deviceCommandLogRepo repository.DeviceCommandLogRepository, executionLogRepo repository.ExecutionLogRepository, @@ -64,6 +69,7 @@ func NewMonitorService( notificationRepo repository.NotificationRepository, ) MonitorService { return &monitorService{ + ctx: ctx, sensorDataRepo: sensorDataRepo, deviceCommandLogRepo: deviceCommandLogRepo, executionLogRepo: executionLogRepo, @@ -82,7 +88,8 @@ func NewMonitorService( } // ListSensorData 负责处理查询传感器数据列表的业务逻辑 -func (s *monitorService) ListSensorData(req *dto.ListSensorDataRequest) (*dto.ListSensorDataResponse, error) { +func (s *monitorService) ListSensorData(ctx context.Context, req *dto.ListSensorDataRequest) (*dto.ListSensorDataResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListSensorData") opts := repository.SensorDataListOptions{ DeviceID: req.DeviceID, OrderBy: req.OrderBy, @@ -94,7 +101,7 @@ func (s *monitorService) ListSensorData(req *dto.ListSensorDataRequest) (*dto.Li opts.SensorType = &sensorType } - data, total, err := s.sensorDataRepo.List(opts, req.Page, req.PageSize) + data, total, err := s.sensorDataRepo.List(serviceCtx, opts, req.Page, req.PageSize) if err != nil { return nil, err } @@ -103,7 +110,8 @@ func (s *monitorService) ListSensorData(req *dto.ListSensorDataRequest) (*dto.Li } // ListDeviceCommandLogs 负责处理查询设备命令日志列表的业务逻辑 -func (s *monitorService) ListDeviceCommandLogs(req *dto.ListDeviceCommandLogRequest) (*dto.ListDeviceCommandLogResponse, error) { +func (s *monitorService) ListDeviceCommandLogs(ctx context.Context, req *dto.ListDeviceCommandLogRequest) (*dto.ListDeviceCommandLogResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListDeviceCommandLogs") opts := repository.DeviceCommandLogListOptions{ DeviceID: req.DeviceID, ReceivedSuccess: req.ReceivedSuccess, @@ -112,7 +120,7 @@ func (s *monitorService) ListDeviceCommandLogs(req *dto.ListDeviceCommandLogRequ EndTime: req.EndTime, } - data, total, err := s.deviceCommandLogRepo.List(opts, req.Page, req.PageSize) + data, total, err := s.deviceCommandLogRepo.List(serviceCtx, opts, req.Page, req.PageSize) if err != nil { return nil, err } @@ -121,7 +129,8 @@ func (s *monitorService) ListDeviceCommandLogs(req *dto.ListDeviceCommandLogRequ } // ListPlanExecutionLogs 负责处理查询计划执行日志列表的业务逻辑 -func (s *monitorService) ListPlanExecutionLogs(req *dto.ListPlanExecutionLogRequest) (*dto.ListPlanExecutionLogResponse, error) { +func (s *monitorService) ListPlanExecutionLogs(ctx context.Context, req *dto.ListPlanExecutionLogRequest) (*dto.ListPlanExecutionLogResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListPlanExecutionLogs") opts := repository.PlanExecutionLogListOptions{ PlanID: req.PlanID, OrderBy: req.OrderBy, @@ -133,7 +142,7 @@ func (s *monitorService) ListPlanExecutionLogs(req *dto.ListPlanExecutionLogRequ opts.Status = &status } - planLogs, total, err := s.executionLogRepo.ListPlanExecutionLogs(opts, req.Page, req.PageSize) + planLogs, total, err := s.executionLogRepo.ListPlanExecutionLogs(serviceCtx, opts, req.Page, req.PageSize) if err != nil { return nil, err } @@ -151,7 +160,7 @@ func (s *monitorService) ListPlanExecutionLogs(req *dto.ListPlanExecutionLogRequ planIds = append(planIds, datum.PlanID) } } - plans, err := s.planRepository.GetPlansByIDs(planIds) + plans, err := s.planRepository.GetPlansByIDs(serviceCtx, planIds) if err != nil { return nil, err } @@ -159,7 +168,8 @@ func (s *monitorService) ListPlanExecutionLogs(req *dto.ListPlanExecutionLogRequ } // ListTaskExecutionLogs 负责处理查询任务执行日志列表的业务逻辑 -func (s *monitorService) ListTaskExecutionLogs(req *dto.ListTaskExecutionLogRequest) (*dto.ListTaskExecutionLogResponse, error) { +func (s *monitorService) ListTaskExecutionLogs(ctx context.Context, req *dto.ListTaskExecutionLogRequest) (*dto.ListTaskExecutionLogResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListTaskExecutionLogs") opts := repository.TaskExecutionLogListOptions{ PlanExecutionLogID: req.PlanExecutionLogID, TaskID: req.TaskID, @@ -172,7 +182,7 @@ func (s *monitorService) ListTaskExecutionLogs(req *dto.ListTaskExecutionLogRequ opts.Status = &status } - data, total, err := s.executionLogRepo.ListTaskExecutionLogs(opts, req.Page, req.PageSize) + data, total, err := s.executionLogRepo.ListTaskExecutionLogs(serviceCtx, opts, req.Page, req.PageSize) if err != nil { return nil, err } @@ -181,7 +191,8 @@ func (s *monitorService) ListTaskExecutionLogs(req *dto.ListTaskExecutionLogRequ } // ListPendingCollections 负责处理查询待采集请求列表的业务逻辑 -func (s *monitorService) ListPendingCollections(req *dto.ListPendingCollectionRequest) (*dto.ListPendingCollectionResponse, error) { +func (s *monitorService) ListPendingCollections(ctx context.Context, req *dto.ListPendingCollectionRequest) (*dto.ListPendingCollectionResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListPendingCollections") opts := repository.PendingCollectionListOptions{ DeviceID: req.DeviceID, OrderBy: req.OrderBy, @@ -193,7 +204,7 @@ func (s *monitorService) ListPendingCollections(req *dto.ListPendingCollectionRe opts.Status = &status } - data, total, err := s.pendingCollectionRepo.List(opts, req.Page, req.PageSize) + data, total, err := s.pendingCollectionRepo.List(serviceCtx, opts, req.Page, req.PageSize) if err != nil { return nil, err } @@ -202,7 +213,8 @@ func (s *monitorService) ListPendingCollections(req *dto.ListPendingCollectionRe } // ListUserActionLogs 负责处理查询用户操作日志列表的业务逻辑 -func (s *monitorService) ListUserActionLogs(req *dto.ListUserActionLogRequest) (*dto.ListUserActionLogResponse, error) { +func (s *monitorService) ListUserActionLogs(ctx context.Context, req *dto.ListUserActionLogRequest) (*dto.ListUserActionLogResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListUserActionLogs") opts := repository.UserActionLogListOptions{ UserID: req.UserID, Username: req.Username, @@ -216,7 +228,7 @@ func (s *monitorService) ListUserActionLogs(req *dto.ListUserActionLogRequest) ( opts.Status = &status } - data, total, err := s.userActionLogRepo.List(opts, req.Page, req.PageSize) + data, total, err := s.userActionLogRepo.List(serviceCtx, opts, req.Page, req.PageSize) if err != nil { return nil, err } @@ -225,7 +237,8 @@ func (s *monitorService) ListUserActionLogs(req *dto.ListUserActionLogRequest) ( } // ListRawMaterialPurchases 负责处理查询原料采购记录列表的业务逻辑 -func (s *monitorService) ListRawMaterialPurchases(req *dto.ListRawMaterialPurchaseRequest) (*dto.ListRawMaterialPurchaseResponse, error) { +func (s *monitorService) ListRawMaterialPurchases(ctx context.Context, req *dto.ListRawMaterialPurchaseRequest) (*dto.ListRawMaterialPurchaseResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListRawMaterialPurchases") opts := repository.RawMaterialPurchaseListOptions{ RawMaterialID: req.RawMaterialID, Supplier: req.Supplier, @@ -234,7 +247,7 @@ func (s *monitorService) ListRawMaterialPurchases(req *dto.ListRawMaterialPurcha EndTime: req.EndTime, } - data, total, err := s.rawMaterialRepo.ListRawMaterialPurchases(opts, req.Page, req.PageSize) + data, total, err := s.rawMaterialRepo.ListRawMaterialPurchases(serviceCtx, opts, req.Page, req.PageSize) if err != nil { return nil, err } @@ -243,7 +256,8 @@ func (s *monitorService) ListRawMaterialPurchases(req *dto.ListRawMaterialPurcha } // ListRawMaterialStockLogs 负责处理查询原料库存日志列表的业务逻辑 -func (s *monitorService) ListRawMaterialStockLogs(req *dto.ListRawMaterialStockLogRequest) (*dto.ListRawMaterialStockLogResponse, error) { +func (s *monitorService) ListRawMaterialStockLogs(ctx context.Context, req *dto.ListRawMaterialStockLogRequest) (*dto.ListRawMaterialStockLogResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListRawMaterialStockLogs") opts := repository.RawMaterialStockLogListOptions{ RawMaterialID: req.RawMaterialID, SourceID: req.SourceID, @@ -256,7 +270,7 @@ func (s *monitorService) ListRawMaterialStockLogs(req *dto.ListRawMaterialStockL opts.SourceType = &sourceType } - data, total, err := s.rawMaterialRepo.ListRawMaterialStockLogs(opts, req.Page, req.PageSize) + data, total, err := s.rawMaterialRepo.ListRawMaterialStockLogs(serviceCtx, opts, req.Page, req.PageSize) if err != nil { return nil, err } @@ -265,7 +279,8 @@ func (s *monitorService) ListRawMaterialStockLogs(req *dto.ListRawMaterialStockL } // ListFeedUsageRecords 负责处理查询饲料使用记录列表的业务逻辑 -func (s *monitorService) ListFeedUsageRecords(req *dto.ListFeedUsageRecordRequest) (*dto.ListFeedUsageRecordResponse, error) { +func (s *monitorService) ListFeedUsageRecords(ctx context.Context, req *dto.ListFeedUsageRecordRequest) (*dto.ListFeedUsageRecordResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListFeedUsageRecords") opts := repository.FeedUsageRecordListOptions{ PenID: req.PenID, FeedFormulaID: req.FeedFormulaID, @@ -275,7 +290,7 @@ func (s *monitorService) ListFeedUsageRecords(req *dto.ListFeedUsageRecordReques EndTime: req.EndTime, } - data, total, err := s.rawMaterialRepo.ListFeedUsageRecords(opts, req.Page, req.PageSize) + data, total, err := s.rawMaterialRepo.ListFeedUsageRecords(serviceCtx, opts, req.Page, req.PageSize) if err != nil { return nil, err } @@ -284,7 +299,8 @@ func (s *monitorService) ListFeedUsageRecords(req *dto.ListFeedUsageRecordReques } // ListMedicationLogs 负责处理查询用药记录列表的业务逻辑 -func (s *monitorService) ListMedicationLogs(req *dto.ListMedicationLogRequest) (*dto.ListMedicationLogResponse, error) { +func (s *monitorService) ListMedicationLogs(ctx context.Context, req *dto.ListMedicationLogRequest) (*dto.ListMedicationLogResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListMedicationLogs") opts := repository.MedicationLogListOptions{ PigBatchID: req.PigBatchID, MedicationID: req.MedicationID, @@ -298,7 +314,7 @@ func (s *monitorService) ListMedicationLogs(req *dto.ListMedicationLogRequest) ( opts.Reason = &reason } - data, total, err := s.medicationRepo.ListMedicationLogs(opts, req.Page, req.PageSize) + data, total, err := s.medicationRepo.ListMedicationLogs(serviceCtx, opts, req.Page, req.PageSize) if err != nil { return nil, err } @@ -307,7 +323,8 @@ func (s *monitorService) ListMedicationLogs(req *dto.ListMedicationLogRequest) ( } // ListPigBatchLogs 负责处理查询猪批次日志列表的业务逻辑 -func (s *monitorService) ListPigBatchLogs(req *dto.ListPigBatchLogRequest) (*dto.ListPigBatchLogResponse, error) { +func (s *monitorService) ListPigBatchLogs(ctx context.Context, req *dto.ListPigBatchLogRequest) (*dto.ListPigBatchLogResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListPigBatchLogs") opts := repository.PigBatchLogListOptions{ PigBatchID: req.PigBatchID, OperatorID: req.OperatorID, @@ -320,7 +337,7 @@ func (s *monitorService) ListPigBatchLogs(req *dto.ListPigBatchLogRequest) (*dto opts.ChangeType = &changeType } - data, total, err := s.pigBatchLogRepo.List(opts, req.Page, req.PageSize) + data, total, err := s.pigBatchLogRepo.List(serviceCtx, opts, req.Page, req.PageSize) if err != nil { return nil, err } @@ -329,7 +346,8 @@ func (s *monitorService) ListPigBatchLogs(req *dto.ListPigBatchLogRequest) (*dto } // ListWeighingBatches 负责处理查询批次称重记录列表的业务逻辑 -func (s *monitorService) ListWeighingBatches(req *dto.ListWeighingBatchRequest) (*dto.ListWeighingBatchResponse, error) { +func (s *monitorService) ListWeighingBatches(ctx context.Context, req *dto.ListWeighingBatchRequest) (*dto.ListWeighingBatchResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListWeighingBatches") opts := repository.WeighingBatchListOptions{ PigBatchID: req.PigBatchID, OrderBy: req.OrderBy, @@ -337,7 +355,7 @@ func (s *monitorService) ListWeighingBatches(req *dto.ListWeighingBatchRequest) EndTime: req.EndTime, } - data, total, err := s.pigBatchRepo.ListWeighingBatches(opts, req.Page, req.PageSize) + data, total, err := s.pigBatchRepo.ListWeighingBatches(serviceCtx, opts, req.Page, req.PageSize) if err != nil { return nil, err } @@ -346,7 +364,8 @@ func (s *monitorService) ListWeighingBatches(req *dto.ListWeighingBatchRequest) } // ListWeighingRecords 负责处理查询单次称重记录列表的业务逻辑 -func (s *monitorService) ListWeighingRecords(req *dto.ListWeighingRecordRequest) (*dto.ListWeighingRecordResponse, error) { +func (s *monitorService) ListWeighingRecords(ctx context.Context, req *dto.ListWeighingRecordRequest) (*dto.ListWeighingRecordResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListWeighingRecords") opts := repository.WeighingRecordListOptions{ WeighingBatchID: req.WeighingBatchID, PenID: req.PenID, @@ -356,7 +375,7 @@ func (s *monitorService) ListWeighingRecords(req *dto.ListWeighingRecordRequest) EndTime: req.EndTime, } - data, total, err := s.pigBatchRepo.ListWeighingRecords(opts, req.Page, req.PageSize) + data, total, err := s.pigBatchRepo.ListWeighingRecords(serviceCtx, opts, req.Page, req.PageSize) if err != nil { return nil, err } @@ -365,7 +384,8 @@ func (s *monitorService) ListWeighingRecords(req *dto.ListWeighingRecordRequest) } // ListPigTransferLogs 负责处理查询猪只迁移日志列表的业务逻辑 -func (s *monitorService) ListPigTransferLogs(req *dto.ListPigTransferLogRequest) (*dto.ListPigTransferLogResponse, error) { +func (s *monitorService) ListPigTransferLogs(ctx context.Context, req *dto.ListPigTransferLogRequest) (*dto.ListPigTransferLogResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListPigTransferLogs") opts := repository.PigTransferLogListOptions{ PigBatchID: req.PigBatchID, PenID: req.PenID, @@ -380,7 +400,7 @@ func (s *monitorService) ListPigTransferLogs(req *dto.ListPigTransferLogRequest) opts.TransferType = &transferType } - data, total, err := s.pigTransferLogRepo.ListPigTransferLogs(opts, req.Page, req.PageSize) + data, total, err := s.pigTransferLogRepo.ListPigTransferLogs(serviceCtx, opts, req.Page, req.PageSize) if err != nil { return nil, err } @@ -389,7 +409,8 @@ func (s *monitorService) ListPigTransferLogs(req *dto.ListPigTransferLogRequest) } // ListPigSickLogs 负责处理查询病猪日志列表的业务逻辑 -func (s *monitorService) ListPigSickLogs(req *dto.ListPigSickLogRequest) (*dto.ListPigSickLogResponse, error) { +func (s *monitorService) ListPigSickLogs(ctx context.Context, req *dto.ListPigSickLogRequest) (*dto.ListPigSickLogResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListPigSickLogs") opts := repository.PigSickLogListOptions{ PigBatchID: req.PigBatchID, PenID: req.PenID, @@ -407,7 +428,7 @@ func (s *monitorService) ListPigSickLogs(req *dto.ListPigSickLogRequest) (*dto.L opts.TreatmentLocation = &treatmentLocation } - data, total, err := s.pigSickLogRepo.ListPigSickLogs(opts, req.Page, req.PageSize) + data, total, err := s.pigSickLogRepo.ListPigSickLogs(serviceCtx, opts, req.Page, req.PageSize) if err != nil { return nil, err } @@ -416,7 +437,8 @@ func (s *monitorService) ListPigSickLogs(req *dto.ListPigSickLogRequest) (*dto.L } // ListPigPurchases 负责处理查询猪只采购记录列表的业务逻辑 -func (s *monitorService) ListPigPurchases(req *dto.ListPigPurchaseRequest) (*dto.ListPigPurchaseResponse, error) { +func (s *monitorService) ListPigPurchases(ctx context.Context, req *dto.ListPigPurchaseRequest) (*dto.ListPigPurchaseResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListPigPurchases") opts := repository.PigPurchaseListOptions{ PigBatchID: req.PigBatchID, Supplier: req.Supplier, @@ -426,7 +448,7 @@ func (s *monitorService) ListPigPurchases(req *dto.ListPigPurchaseRequest) (*dto EndTime: req.EndTime, } - data, total, err := s.pigTradeRepo.ListPigPurchases(opts, req.Page, req.PageSize) + data, total, err := s.pigTradeRepo.ListPigPurchases(serviceCtx, opts, req.Page, req.PageSize) if err != nil { return nil, err } @@ -435,7 +457,8 @@ func (s *monitorService) ListPigPurchases(req *dto.ListPigPurchaseRequest) (*dto } // ListPigSales 负责处理查询猪只销售记录列表的业务逻辑 -func (s *monitorService) ListPigSales(req *dto.ListPigSaleRequest) (*dto.ListPigSaleResponse, error) { +func (s *monitorService) ListPigSales(ctx context.Context, req *dto.ListPigSaleRequest) (*dto.ListPigSaleResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListPigSales") opts := repository.PigSaleListOptions{ PigBatchID: req.PigBatchID, Buyer: req.Buyer, @@ -445,7 +468,7 @@ func (s *monitorService) ListPigSales(req *dto.ListPigSaleRequest) (*dto.ListPig EndTime: req.EndTime, } - data, total, err := s.pigTradeRepo.ListPigSales(opts, req.Page, req.PageSize) + data, total, err := s.pigTradeRepo.ListPigSales(serviceCtx, opts, req.Page, req.PageSize) if err != nil { return nil, err } @@ -454,7 +477,8 @@ func (s *monitorService) ListPigSales(req *dto.ListPigSaleRequest) (*dto.ListPig } // ListNotifications 负责处理查询通知列表的业务逻辑 -func (s *monitorService) ListNotifications(req *dto.ListNotificationRequest) (*dto.ListNotificationResponse, error) { +func (s *monitorService) ListNotifications(ctx context.Context, req *dto.ListNotificationRequest) (*dto.ListNotificationResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListNotifications") opts := repository.NotificationListOptions{ UserID: req.UserID, NotifierType: req.NotifierType, @@ -465,7 +489,7 @@ func (s *monitorService) ListNotifications(req *dto.ListNotificationRequest) (*d Status: req.Status, } - data, total, err := s.notificationRepo.List(opts, req.Page, req.PageSize) + data, total, err := s.notificationRepo.List(serviceCtx, opts, req.Page, req.PageSize) if err != nil { return nil, err } diff --git a/internal/app/service/pig_batch_service.go b/internal/app/service/pig_batch_service.go index e5bc777..4ee78ac 100644 --- a/internal/app/service/pig_batch_service.go +++ b/internal/app/service/pig_batch_service.go @@ -1,6 +1,7 @@ package service import ( + "context" "time" "git.huangwc.com/pig/pig-farm-controller/internal/app/dto" @@ -11,47 +12,47 @@ import ( // PigBatchService 接口定义保持不变,继续作为应用层对外的契约。 type PigBatchService interface { - CreatePigBatch(operatorID uint, dto *dto.PigBatchCreateDTO) (*dto.PigBatchResponseDTO, error) - GetPigBatch(id uint) (*dto.PigBatchResponseDTO, error) - UpdatePigBatch(id uint, dto *dto.PigBatchUpdateDTO) (*dto.PigBatchResponseDTO, error) - DeletePigBatch(id uint) error - ListPigBatches(isActive *bool) ([]*dto.PigBatchResponseDTO, error) + CreatePigBatch(ctx context.Context, operatorID uint, dto *dto.PigBatchCreateDTO) (*dto.PigBatchResponseDTO, error) + GetPigBatch(ctx context.Context, id uint) (*dto.PigBatchResponseDTO, error) + UpdatePigBatch(ctx context.Context, id uint, dto *dto.PigBatchUpdateDTO) (*dto.PigBatchResponseDTO, error) + DeletePigBatch(ctx context.Context, id uint) error + ListPigBatches(ctx context.Context, isActive *bool) ([]*dto.PigBatchResponseDTO, error) // Pig Pen Management - AssignEmptyPensToBatch(batchID uint, penIDs []uint, operatorID uint) error - ReclassifyPenToNewBatch(fromBatchID uint, toBatchID uint, penID uint, operatorID uint, remarks string) error - RemoveEmptyPenFromBatch(batchID uint, penID uint) error - MovePigsIntoPen(batchID uint, toPenID uint, quantity int, operatorID uint, remarks string) error + AssignEmptyPensToBatch(ctx context.Context, batchID uint, penIDs []uint, operatorID uint) error + ReclassifyPenToNewBatch(ctx context.Context, fromBatchID uint, toBatchID uint, penID uint, operatorID uint, remarks string) error + RemoveEmptyPenFromBatch(ctx context.Context, batchID uint, penID uint) error + MovePigsIntoPen(ctx context.Context, batchID uint, toPenID uint, quantity int, operatorID uint, remarks string) error // Trade Sub-service - SellPigs(batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error - BuyPigs(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(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error // Transfer Sub-service - 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 // Sick Pig Management - RecordSickPigs(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error - RecordSickPigRecovery(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error - RecordSickPigDeath(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error - RecordSickPigCull(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(ctx context.Context, 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(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error // Normal Pig Management - RecordDeath(operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error - RecordCull(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(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error } // pigBatchService 的实现现在依赖于领域服务接口。 type pigBatchService struct { - logger *logs.Logger - domainService domain_pig.PigBatchService // 依赖注入领域服务 + ctx context.Context + domainService domain_pig.PigBatchService } // NewPigBatchService 构造函数被修改,以注入领域服务。 -func NewPigBatchService(domainService domain_pig.PigBatchService, logger *logs.Logger) PigBatchService { +func NewPigBatchService(ctx context.Context, domainService domain_pig.PigBatchService) PigBatchService { return &pigBatchService{ - logger: logger, + ctx: ctx, domainService: domainService, } } @@ -78,7 +79,8 @@ func (s *pigBatchService) toPigBatchResponseDTO(batch *models.PigBatch, currentT } // CreatePigBatch 现在将请求委托给领域服务处理。 -func (s *pigBatchService) CreatePigBatch(operatorID uint, dto *dto.PigBatchCreateDTO) (*dto.PigBatchResponseDTO, error) { +func (s *pigBatchService) CreatePigBatch(ctx context.Context, operatorID uint, dto *dto.PigBatchCreateDTO) (*dto.PigBatchResponseDTO, error) { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "CreatePigBatch") // 1. DTO -> 领域模型 batch := &models.PigBatch{ BatchNumber: dto.BatchNumber, @@ -89,9 +91,9 @@ func (s *pigBatchService) CreatePigBatch(operatorID uint, dto *dto.PigBatchCreat } // 2. 调用领域服务 - createdBatch, err := s.domainService.CreatePigBatch(operatorID, batch) + createdBatch, err := s.domainService.CreatePigBatch(serviceCtx, operatorID, batch) if err != nil { - s.logger.Errorf("应用层: 创建猪批次失败: %v", err) + logger.Errorf("应用层: 创建猪批次失败: %v", err) return nil, MapDomainError(err) } @@ -100,31 +102,33 @@ func (s *pigBatchService) CreatePigBatch(operatorID uint, dto *dto.PigBatchCreat } // GetPigBatch 从领域服务获取数据并转换为DTO,同时处理错误转换。 -func (s *pigBatchService) GetPigBatch(id uint) (*dto.PigBatchResponseDTO, error) { - batch, err := s.domainService.GetPigBatch(id) +func (s *pigBatchService) GetPigBatch(ctx context.Context, id uint) (*dto.PigBatchResponseDTO, error) { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "GetPigBatch") + batch, err := s.domainService.GetPigBatch(serviceCtx, id) if err != nil { - s.logger.Warnf("应用层: 获取猪批次失败, ID: %d, 错误: %v", id, err) + logger.Warnf("应用层: 获取猪批次失败, ID: %d, 错误: %v", id, err) return nil, MapDomainError(err) } - currentTotalQuantity, err := s.domainService.GetCurrentPigQuantity(id) + currentTotalQuantity, err := s.domainService.GetCurrentPigQuantity(serviceCtx, id) if err != nil { - s.logger.Warnf("应用层: 获取猪批次总数失败, ID: %d, 错误: %v", id, err) + logger.Warnf("应用层: 获取猪批次总数失败, ID: %d, 错误: %v", id, err) return nil, MapDomainError(err) } - currentTotalPigsInPens, err := s.domainService.GetTotalPigsInPensForBatch(id) + currentTotalPigsInPens, err := s.domainService.GetTotalPigsInPensForBatch(serviceCtx, id) if err != nil { - s.logger.Warnf("应用层: 获取猪批次存栏总数失败, ID: %d, 错误: %v", id, err) + logger.Warnf("应用层: 获取猪批次存栏总数失败, ID: %d, 错误: %v", id, err) return nil, MapDomainError(err) } return s.toPigBatchResponseDTO(batch, currentTotalQuantity, currentTotalPigsInPens), nil } // UpdatePigBatch 协调获取、更新和保存的流程,并处理错误转换。 -func (s *pigBatchService) UpdatePigBatch(id uint, dto *dto.PigBatchUpdateDTO) (*dto.PigBatchResponseDTO, error) { +func (s *pigBatchService) UpdatePigBatch(ctx context.Context, id uint, dto *dto.PigBatchUpdateDTO) (*dto.PigBatchResponseDTO, error) { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "UpdatePigBatch") // 1. 先获取最新的领域模型 - existingBatch, err := s.domainService.GetPigBatch(id) + existingBatch, err := s.domainService.GetPigBatch(serviceCtx, id) if err != nil { - s.logger.Warnf("应用层: 更新猪批次失败,获取原批次信息错误, ID: %d, 错误: %v", id, err) + logger.Warnf("应用层: 更新猪批次失败,获取原批次信息错误, ID: %d, 错误: %v", id, err) return nil, MapDomainError(err) } @@ -149,21 +153,21 @@ func (s *pigBatchService) UpdatePigBatch(id uint, dto *dto.PigBatchUpdateDTO) (* } // 3. 调用领域服务执行更新 - updatedBatch, err := s.domainService.UpdatePigBatch(existingBatch) + updatedBatch, err := s.domainService.UpdatePigBatch(serviceCtx, existingBatch) if err != nil { - s.logger.Errorf("应用层: 更新猪批次失败, ID: %d, 错误: %v", id, err) + logger.Errorf("应用层: 更新猪批次失败, ID: %d, 错误: %v", id, err) return nil, MapDomainError(err) } // 4. 填充猪群信息 - currentTotalQuantity, err := s.domainService.GetCurrentPigQuantity(id) + currentTotalQuantity, err := s.domainService.GetCurrentPigQuantity(serviceCtx, id) if err != nil { - s.logger.Warnf("应用层: 获取猪批次总数失败, ID: %d, 错误: %v", id, err) + logger.Warnf("应用层: 获取猪批次总数失败, ID: %d, 错误: %v", id, err) return nil, MapDomainError(err) } - currentTotalPigsInPens, err := s.domainService.GetTotalPigsInPensForBatch(id) + currentTotalPigsInPens, err := s.domainService.GetTotalPigsInPensForBatch(serviceCtx, id) if err != nil { - s.logger.Warnf("应用层: 获取猪批次存栏总数失败, ID: %d, 错误: %v", id, err) + logger.Warnf("应用层: 获取猪批次存栏总数失败, ID: %d, 错误: %v", id, err) return nil, MapDomainError(err) } @@ -172,33 +176,35 @@ func (s *pigBatchService) UpdatePigBatch(id uint, dto *dto.PigBatchUpdateDTO) (* } // DeletePigBatch 将删除操作委托给领域服务,并转换领域错误为应用层错误。 -func (s *pigBatchService) DeletePigBatch(id uint) error { - err := s.domainService.DeletePigBatch(id) +func (s *pigBatchService) DeletePigBatch(ctx context.Context, id uint) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "DeletePigBatch") + err := s.domainService.DeletePigBatch(serviceCtx, id) if err != nil { - s.logger.Errorf("应用层: 删除猪批次失败, ID: %d, 错误: %v", id, err) + logger.Errorf("应用层: 删除猪批次失败, ID: %d, 错误: %v", id, err) return MapDomainError(err) } return nil } // ListPigBatches 从领域服务获取列表并进行转换。 -func (s *pigBatchService) ListPigBatches(isActive *bool) ([]*dto.PigBatchResponseDTO, error) { - batches, err := s.domainService.ListPigBatches(isActive) +func (s *pigBatchService) ListPigBatches(ctx context.Context, isActive *bool) ([]*dto.PigBatchResponseDTO, error) { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "ListPigBatches") + batches, err := s.domainService.ListPigBatches(serviceCtx, isActive) if err != nil { - s.logger.Errorf("应用层: 批量查询猪批次失败: %v", err) + logger.Errorf("应用层: 批量查询猪批次失败: %v", err) return nil, MapDomainError(err) } var responseDTOs []*dto.PigBatchResponseDTO for _, batch := range batches { - currentTotalQuantity, err := s.domainService.GetCurrentPigQuantity(batch.ID) + currentTotalQuantity, err := s.domainService.GetCurrentPigQuantity(serviceCtx, batch.ID) if err != nil { - s.logger.Warnf("应用层: 获取猪批次总数失败, ID: %d, 错误: %v", batch.ID, err) + logger.Warnf("应用层: 获取猪批次总数失败, ID: %d, 错误: %v", batch.ID, err) return nil, MapDomainError(err) } - currentTotalPigsInPens, err := s.domainService.GetTotalPigsInPensForBatch(batch.ID) + currentTotalPigsInPens, err := s.domainService.GetTotalPigsInPensForBatch(serviceCtx, batch.ID) if err != nil { - s.logger.Warnf("应用层: 获取猪批次存栏总数失败, ID: %d, 错误: %v", batch.ID, err) + logger.Warnf("应用层: 获取猪批次存栏总数失败, ID: %d, 错误: %v", batch.ID, err) return nil, MapDomainError(err) } responseDTOs = append(responseDTOs, s.toPigBatchResponseDTO(batch, currentTotalQuantity, currentTotalPigsInPens)) @@ -208,140 +214,154 @@ func (s *pigBatchService) ListPigBatches(isActive *bool) ([]*dto.PigBatchRespons } // AssignEmptyPensToBatch 委托给领域服务 -func (s *pigBatchService) AssignEmptyPensToBatch(batchID uint, penIDs []uint, operatorID uint) error { - err := s.domainService.AssignEmptyPensToBatch(batchID, penIDs, operatorID) +func (s *pigBatchService) AssignEmptyPensToBatch(ctx context.Context, batchID uint, penIDs []uint, operatorID uint) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "AssignEmptyPensToBatch") + err := s.domainService.AssignEmptyPensToBatch(serviceCtx, batchID, penIDs, operatorID) if err != nil { - s.logger.Errorf("应用层: 为猪批次分配空栏失败, 批次ID: %d, 错误: %v", batchID, err) + logger.Errorf("应用层: 为猪批次分配空栏失败, 批次ID: %d, 错误: %v", batchID, err) return MapDomainError(err) } return nil } // ReclassifyPenToNewBatch 委托给领域服务 -func (s *pigBatchService) ReclassifyPenToNewBatch(fromBatchID uint, toBatchID uint, penID uint, operatorID uint, remarks string) error { - err := s.domainService.ReclassifyPenToNewBatch(fromBatchID, toBatchID, penID, operatorID, remarks) +func (s *pigBatchService) ReclassifyPenToNewBatch(ctx context.Context, fromBatchID uint, toBatchID uint, penID uint, operatorID uint, remarks string) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "ReclassifyPenToNewBatch") + err := s.domainService.ReclassifyPenToNewBatch(serviceCtx, fromBatchID, toBatchID, penID, operatorID, remarks) if err != nil { - s.logger.Errorf("应用层: 划拨猪栏到新批次失败, 源批次ID: %d, 错误: %v", fromBatchID, err) + logger.Errorf("应用层: 划拨猪栏到新批次失败, 源批次ID: %d, 错误: %v", fromBatchID, err) return MapDomainError(err) } return nil } // RemoveEmptyPenFromBatch 委托给领域服务 -func (s *pigBatchService) RemoveEmptyPenFromBatch(batchID uint, penID uint) error { - err := s.domainService.RemoveEmptyPenFromBatch(batchID, penID) +func (s *pigBatchService) RemoveEmptyPenFromBatch(ctx context.Context, batchID uint, penID uint) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "RemoveEmptyPenFromBatch") + err := s.domainService.RemoveEmptyPenFromBatch(serviceCtx, batchID, penID) if err != nil { - s.logger.Errorf("应用层: 从猪批次移除空栏失败, 批次ID: %d, 猪栏ID: %d, 错误: %v", batchID, penID, err) + logger.Errorf("应用层: 从猪批次移除空栏失败, 批次ID: %d, 猪栏ID: %d, 错误: %v", batchID, penID, err) return MapDomainError(err) } return nil } // MovePigsIntoPen 委托给领域服务 -func (s *pigBatchService) MovePigsIntoPen(batchID uint, toPenID uint, quantity int, operatorID uint, remarks string) error { - err := s.domainService.MovePigsIntoPen(batchID, toPenID, quantity, operatorID, remarks) +func (s *pigBatchService) MovePigsIntoPen(ctx context.Context, batchID uint, toPenID uint, quantity int, operatorID uint, remarks string) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "MovePigsIntoPen") + err := s.domainService.MovePigsIntoPen(serviceCtx, batchID, toPenID, quantity, operatorID, remarks) if err != nil { - s.logger.Errorf("应用层: 将猪只移入猪栏失败, 批次ID: %d, 目标猪栏ID: %d, 错误: %v", batchID, toPenID, err) + logger.Errorf("应用层: 将猪只移入猪栏失败, 批次ID: %d, 目标猪栏ID: %d, 错误: %v", batchID, toPenID, err) return MapDomainError(err) } return nil } // 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 { - err := s.domainService.SellPigs(batchID, penID, quantity, unitPrice, tatalPrice, traderName, tradeDate, remarks, operatorID) +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, logger := logs.Trace(ctx, s.ctx, "SellPigs") + err := s.domainService.SellPigs(serviceCtx, batchID, penID, quantity, unitPrice, tatalPrice, traderName, tradeDate, remarks, operatorID) if err != nil { - s.logger.Errorf("应用层: 卖猪失败, 批次ID: %d, 错误: %v", batchID, err) + logger.Errorf("应用层: 卖猪失败, 批次ID: %d, 错误: %v", batchID, err) return MapDomainError(err) } return nil } // BuyPigs 委托给领域服务 -func (s *pigBatchService) BuyPigs(batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error { - err := s.domainService.BuyPigs(batchID, penID, quantity, unitPrice, tatalPrice, traderName, tradeDate, remarks, operatorID) +func (s *pigBatchService) BuyPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "BuyPigs") + err := s.domainService.BuyPigs(serviceCtx, batchID, penID, quantity, unitPrice, tatalPrice, traderName, tradeDate, remarks, operatorID) if err != nil { - s.logger.Errorf("应用层: 买猪失败, 批次ID: %d, 错误: %v", batchID, err) + logger.Errorf("应用层: 买猪失败, 批次ID: %d, 错误: %v", batchID, err) return MapDomainError(err) } return nil } // TransferPigsAcrossBatches 委托给领域服务 -func (s *pigBatchService) TransferPigsAcrossBatches(sourceBatchID uint, destBatchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error { - err := s.domainService.TransferPigsAcrossBatches(sourceBatchID, destBatchID, fromPenID, toPenID, quantity, operatorID, remarks) +func (s *pigBatchService) TransferPigsAcrossBatches(ctx context.Context, sourceBatchID uint, destBatchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "TransferPigsAcrossBatches") + err := s.domainService.TransferPigsAcrossBatches(serviceCtx, sourceBatchID, destBatchID, fromPenID, toPenID, quantity, operatorID, remarks) if err != nil { - s.logger.Errorf("应用层: 跨群调栏失败, 源批次ID: %d, 错误: %v", sourceBatchID, err) + logger.Errorf("应用层: 跨群调栏失败, 源批次ID: %d, 错误: %v", sourceBatchID, err) return MapDomainError(err) } return nil } // TransferPigsWithinBatch 委托给领域服务 -func (s *pigBatchService) TransferPigsWithinBatch(batchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error { - err := s.domainService.TransferPigsWithinBatch(batchID, fromPenID, toPenID, quantity, operatorID, remarks) +func (s *pigBatchService) TransferPigsWithinBatch(ctx context.Context, batchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "TransferPigsWithinBatch") + err := s.domainService.TransferPigsWithinBatch(serviceCtx, batchID, fromPenID, toPenID, quantity, operatorID, remarks) if err != nil { - s.logger.Errorf("应用层: 群内调栏失败, 批次ID: %d, 错误: %v", batchID, err) + logger.Errorf("应用层: 群内调栏失败, 批次ID: %d, 错误: %v", batchID, err) return MapDomainError(err) } return nil } // RecordSickPigs 委托给领域服务 -func (s *pigBatchService) RecordSickPigs(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { - err := s.domainService.RecordSickPigs(operatorID, batchID, penID, quantity, treatmentLocation, happenedAt, remarks) +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, logger := logs.Trace(ctx, s.ctx, "RecordSickPigs") + err := s.domainService.RecordSickPigs(serviceCtx, operatorID, batchID, penID, quantity, treatmentLocation, happenedAt, remarks) if err != nil { - s.logger.Errorf("应用层: 记录病猪事件失败, 批次ID: %d, 错误: %v", batchID, err) + logger.Errorf("应用层: 记录病猪事件失败, 批次ID: %d, 错误: %v", batchID, err) return MapDomainError(err) } return nil } // RecordSickPigRecovery 委托给领域服务 -func (s *pigBatchService) RecordSickPigRecovery(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { - err := s.domainService.RecordSickPigRecovery(operatorID, batchID, penID, quantity, treatmentLocation, happenedAt, remarks) +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, logger := logs.Trace(ctx, s.ctx, "RecordSickPigRecovery") + err := s.domainService.RecordSickPigRecovery(serviceCtx, operatorID, batchID, penID, quantity, treatmentLocation, happenedAt, remarks) if err != nil { - s.logger.Errorf("应用层: 记录病猪康复事件失败, 批次ID: %d, 错误: %v", batchID, err) + logger.Errorf("应用层: 记录病猪康复事件失败, 批次ID: %d, 错误: %v", batchID, err) return MapDomainError(err) } return nil } // RecordSickPigDeath 委托给领域服务 -func (s *pigBatchService) RecordSickPigDeath(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { - err := s.domainService.RecordSickPigDeath(operatorID, batchID, penID, quantity, treatmentLocation, happenedAt, remarks) +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, logger := logs.Trace(ctx, s.ctx, "RecordSickPigDeath") + err := s.domainService.RecordSickPigDeath(serviceCtx, operatorID, batchID, penID, quantity, treatmentLocation, happenedAt, remarks) if err != nil { - s.logger.Errorf("应用层: 记录病猪死亡事件失败, 批次ID: %d, 错误: %v", batchID, err) + logger.Errorf("应用层: 记录病猪死亡事件失败, 批次ID: %d, 错误: %v", batchID, err) return MapDomainError(err) } return nil } // RecordSickPigCull 委托给领域服务 -func (s *pigBatchService) RecordSickPigCull(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { - err := s.domainService.RecordSickPigCull(operatorID, batchID, penID, quantity, treatmentLocation, happenedAt, remarks) +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, logger := logs.Trace(ctx, s.ctx, "RecordSickPigCull") + err := s.domainService.RecordSickPigCull(serviceCtx, operatorID, batchID, penID, quantity, treatmentLocation, happenedAt, remarks) if err != nil { - s.logger.Errorf("应用层: 记录病猪淘汰事件失败, 批次ID: %d, 错误: %v", batchID, err) + logger.Errorf("应用层: 记录病猪淘汰事件失败, 批次ID: %d, 错误: %v", batchID, err) return MapDomainError(err) } return nil } // RecordDeath 委托给领域服务 -func (s *pigBatchService) RecordDeath(operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error { - err := s.domainService.RecordDeath(operatorID, batchID, penID, quantity, happenedAt, remarks) +func (s *pigBatchService) RecordDeath(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "RecordDeath") + err := s.domainService.RecordDeath(serviceCtx, operatorID, batchID, penID, quantity, happenedAt, remarks) if err != nil { - s.logger.Errorf("应用层: 记录正常猪只死亡事件失败, 批次ID: %d, 错误: %v", batchID, err) + logger.Errorf("应用层: 记录正常猪只死亡事件失败, 批次ID: %d, 错误: %v", batchID, err) return MapDomainError(err) } return nil } // RecordCull 委托给领域服务 -func (s *pigBatchService) RecordCull(operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error { - err := s.domainService.RecordCull(operatorID, batchID, penID, quantity, happenedAt, remarks) +func (s *pigBatchService) RecordCull(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "RecordCull") + err := s.domainService.RecordCull(serviceCtx, operatorID, batchID, penID, quantity, happenedAt, remarks) if err != nil { - s.logger.Errorf("应用层: 记录正常猪只淘汰事件失败, 批次ID: %d, 错误: %v", batchID, err) + logger.Errorf("应用层: 记录正常猪只淘汰事件失败, 批次ID: %d, 错误: %v", batchID, err) return MapDomainError(err) } return nil diff --git a/internal/app/service/pig_farm_service.go b/internal/app/service/pig_farm_service.go index b24f65f..4ea98f3 100644 --- a/internal/app/service/pig_farm_service.go +++ b/internal/app/service/pig_farm_service.go @@ -1,6 +1,7 @@ package service import ( + "context" "errors" "fmt" @@ -16,40 +17,41 @@ import ( // PigFarmService 提供了猪场资产管理的业务逻辑 type PigFarmService interface { // PigHouse methods - CreatePigHouse(name, description string) (*dto.PigHouseResponse, error) - GetPigHouseByID(id uint) (*dto.PigHouseResponse, error) - ListPigHouses() ([]dto.PigHouseResponse, error) - UpdatePigHouse(id uint, name, description string) (*dto.PigHouseResponse, error) - DeletePigHouse(id uint) error + CreatePigHouse(ctx context.Context, name, description string) (*dto.PigHouseResponse, error) + GetPigHouseByID(ctx context.Context, id uint) (*dto.PigHouseResponse, error) + ListPigHouses(ctx context.Context) ([]dto.PigHouseResponse, error) + UpdatePigHouse(ctx context.Context, id uint, name, description string) (*dto.PigHouseResponse, error) + DeletePigHouse(ctx context.Context, id uint) error // Pen methods - CreatePen(penNumber string, houseID uint, capacity int) (*dto.PenResponse, error) - GetPenByID(id uint) (*dto.PenResponse, error) - ListPens() ([]*dto.PenResponse, error) - UpdatePen(id uint, penNumber string, houseID uint, capacity int, status models.PenStatus) (*dto.PenResponse, error) - DeletePen(id uint) error + CreatePen(ctx context.Context, penNumber string, houseID uint, capacity int) (*dto.PenResponse, error) + GetPenByID(ctx context.Context, id uint) (*dto.PenResponse, error) + ListPens(ctx context.Context) ([]*dto.PenResponse, error) + UpdatePen(ctx context.Context, id uint, penNumber string, houseID uint, capacity int, status models.PenStatus) (*dto.PenResponse, error) + DeletePen(ctx context.Context, id uint) error // UpdatePenStatus 更新猪栏状态 - UpdatePenStatus(id uint, newStatus models.PenStatus) (*dto.PenResponse, error) + UpdatePenStatus(ctx context.Context, id uint, newStatus models.PenStatus) (*dto.PenResponse, error) } type pigFarmService struct { - logger *logs.Logger + ctx context.Context farmRepository repository.PigFarmRepository penRepository repository.PigPenRepository batchRepository repository.PigBatchRepository - pigBatchService domain_pig.PigBatchService // Add domain PigBatchService dependency - uow repository.UnitOfWork // 工作单元,用于事务管理 + pigBatchService domain_pig.PigBatchService + uow repository.UnitOfWork // 工作单元,用于事务管理 } // NewPigFarmService 创建一个新的 PigFarmService 实例 -func NewPigFarmService(farmRepository repository.PigFarmRepository, +func NewPigFarmService(ctx context.Context, + farmRepository repository.PigFarmRepository, penRepository repository.PigPenRepository, batchRepository repository.PigBatchRepository, pigBatchService domain_pig.PigBatchService, uow repository.UnitOfWork, - logger *logs.Logger) PigFarmService { +) PigFarmService { return &pigFarmService{ - logger: logger, + ctx: ctx, farmRepository: farmRepository, penRepository: penRepository, batchRepository: batchRepository, @@ -60,12 +62,13 @@ func NewPigFarmService(farmRepository repository.PigFarmRepository, // --- PigHouse Implementation --- -func (s *pigFarmService) CreatePigHouse(name, description string) (*dto.PigHouseResponse, error) { +func (s *pigFarmService) CreatePigHouse(ctx context.Context, name, description string) (*dto.PigHouseResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreatePigHouse") house := &models.PigHouse{ Name: name, Description: description, } - err := s.farmRepository.CreatePigHouse(house) + err := s.farmRepository.CreatePigHouse(serviceCtx, house) if err != nil { return nil, err } @@ -76,8 +79,9 @@ func (s *pigFarmService) CreatePigHouse(name, description string) (*dto.PigHouse }, nil } -func (s *pigFarmService) GetPigHouseByID(id uint) (*dto.PigHouseResponse, error) { - house, err := s.farmRepository.GetPigHouseByID(id) +func (s *pigFarmService) GetPigHouseByID(ctx context.Context, id uint) (*dto.PigHouseResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetPigHouseByID") + house, err := s.farmRepository.GetPigHouseByID(serviceCtx, id) if err != nil { return nil, err } @@ -88,8 +92,9 @@ func (s *pigFarmService) GetPigHouseByID(id uint) (*dto.PigHouseResponse, error) }, nil } -func (s *pigFarmService) ListPigHouses() ([]dto.PigHouseResponse, error) { - houses, err := s.farmRepository.ListPigHouses() +func (s *pigFarmService) ListPigHouses(ctx context.Context) ([]dto.PigHouseResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListPigHouses") + houses, err := s.farmRepository.ListPigHouses(serviceCtx) if err != nil { return nil, err } @@ -104,13 +109,14 @@ func (s *pigFarmService) ListPigHouses() ([]dto.PigHouseResponse, error) { return resp, nil } -func (s *pigFarmService) UpdatePigHouse(id uint, name, description string) (*dto.PigHouseResponse, error) { +func (s *pigFarmService) UpdatePigHouse(ctx context.Context, id uint, name, description string) (*dto.PigHouseResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePigHouse") house := &models.PigHouse{ Model: gorm.Model{ID: id}, Name: name, Description: description, } - rowsAffected, err := s.farmRepository.UpdatePigHouse(house) + rowsAffected, err := s.farmRepository.UpdatePigHouse(serviceCtx, house) if err != nil { return nil, err } @@ -118,7 +124,7 @@ func (s *pigFarmService) UpdatePigHouse(id uint, name, description string) (*dto return nil, ErrHouseNotFound } // 返回更新后的完整信息 - updatedHouse, err := s.farmRepository.GetPigHouseByID(id) + updatedHouse, err := s.farmRepository.GetPigHouseByID(serviceCtx, id) if err != nil { return nil, err } @@ -129,9 +135,10 @@ func (s *pigFarmService) UpdatePigHouse(id uint, name, description string) (*dto }, nil } -func (s *pigFarmService) DeletePigHouse(id uint) error { +func (s *pigFarmService) DeletePigHouse(ctx context.Context, id uint) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeletePigHouse") // 业务逻辑:检查猪舍是否包含猪栏 - penCount, err := s.farmRepository.CountPensInHouse(id) + penCount, err := s.farmRepository.CountPensInHouse(serviceCtx, id) if err != nil { return err } @@ -140,7 +147,7 @@ func (s *pigFarmService) DeletePigHouse(id uint) error { } // 调用仓库层进行删除 - rowsAffected, err := s.farmRepository.DeletePigHouse(id) + rowsAffected, err := s.farmRepository.DeletePigHouse(serviceCtx, id) if err != nil { return err } @@ -152,9 +159,10 @@ func (s *pigFarmService) DeletePigHouse(id uint) error { // --- Pen Implementation --- -func (s *pigFarmService) CreatePen(penNumber string, houseID uint, capacity int) (*dto.PenResponse, error) { +func (s *pigFarmService) CreatePen(ctx context.Context, penNumber string, houseID uint, capacity int) (*dto.PenResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreatePen") // 业务逻辑:验证所属猪舍是否存在 - _, err := s.farmRepository.GetPigHouseByID(houseID) + _, err := s.farmRepository.GetPigHouseByID(serviceCtx, houseID) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrHouseNotFound @@ -168,7 +176,7 @@ func (s *pigFarmService) CreatePen(penNumber string, houseID uint, capacity int) Capacity: capacity, Status: models.PenStatusEmpty, } - err = s.penRepository.CreatePen(pen) + err = s.penRepository.CreatePen(serviceCtx, pen) if err != nil { return nil, err } @@ -181,15 +189,16 @@ func (s *pigFarmService) CreatePen(penNumber string, houseID uint, capacity int) }, nil } -func (s *pigFarmService) GetPenByID(id uint) (*dto.PenResponse, error) { - pen, err := s.penRepository.GetPenByID(id) +func (s *pigFarmService) GetPenByID(ctx context.Context, id uint) (*dto.PenResponse, error) { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "GetPenByID") + pen, err := s.penRepository.GetPenByID(serviceCtx, id) if err != nil { return nil, err } - currentPigCount, err := s.pigBatchService.GetCurrentPigsInPen(id) + currentPigCount, err := s.pigBatchService.GetCurrentPigsInPen(serviceCtx, id) if err != nil { - s.logger.Errorf("获取猪栏 %d 存栏量失败: %v", id, err) + logger.Errorf("获取猪栏 %d 存栏量失败: %v", id, err) currentPigCount = 0 // 如果获取计数时出错,则默认为0 } @@ -209,17 +218,18 @@ func (s *pigFarmService) GetPenByID(id uint) (*dto.PenResponse, error) { return response, nil } -func (s *pigFarmService) ListPens() ([]*dto.PenResponse, error) { - pens, err := s.penRepository.ListPens() +func (s *pigFarmService) ListPens(ctx context.Context) ([]*dto.PenResponse, error) { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "ListPens") + pens, err := s.penRepository.ListPens(serviceCtx) if err != nil { return nil, err } var response []*dto.PenResponse for _, pen := range pens { - currentPigCount, err := s.pigBatchService.GetCurrentPigsInPen(pen.ID) + currentPigCount, err := s.pigBatchService.GetCurrentPigsInPen(serviceCtx, pen.ID) if err != nil { - s.logger.Errorf("获取猪栏 %d 存栏量失败: %v", pen.ID, err) + logger.Errorf("获取猪栏 %d 存栏量失败: %v", pen.ID, err) currentPigCount = 0 // 如果获取计数时出错,则默认为0 } @@ -241,9 +251,10 @@ func (s *pigFarmService) ListPens() ([]*dto.PenResponse, error) { return response, nil } -func (s *pigFarmService) UpdatePen(id uint, penNumber string, houseID uint, capacity int, status models.PenStatus) (*dto.PenResponse, error) { +func (s *pigFarmService) UpdatePen(ctx context.Context, id uint, penNumber string, houseID uint, capacity int, status models.PenStatus) (*dto.PenResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePen") // 业务逻辑:验证所属猪舍是否存在 - _, err := s.farmRepository.GetPigHouseByID(houseID) + _, err := s.farmRepository.GetPigHouseByID(serviceCtx, houseID) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrHouseNotFound @@ -258,7 +269,7 @@ func (s *pigFarmService) UpdatePen(id uint, penNumber string, houseID uint, capa Capacity: capacity, Status: status, } - rowsAffected, err := s.penRepository.UpdatePen(pen) + rowsAffected, err := s.penRepository.UpdatePen(serviceCtx, pen) if err != nil { return nil, err } @@ -266,7 +277,7 @@ func (s *pigFarmService) UpdatePen(id uint, penNumber string, houseID uint, capa return nil, ErrPenNotFound } // 返回更新后的完整信息 - updatedPen, err := s.penRepository.GetPenByID(id) + updatedPen, err := s.penRepository.GetPenByID(serviceCtx, id) if err != nil { return nil, err } @@ -280,9 +291,10 @@ func (s *pigFarmService) UpdatePen(id uint, penNumber string, houseID uint, capa }, nil } -func (s *pigFarmService) DeletePen(id uint) error { +func (s *pigFarmService) DeletePen(ctx context.Context, id uint) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeletePen") // 业务逻辑:检查猪栏是否被活跃批次使用 - pen, err := s.penRepository.GetPenByID(id) + pen, err := s.penRepository.GetPenByID(serviceCtx, id) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrPenNotFound // 猪栏不存在 @@ -293,7 +305,7 @@ func (s *pigFarmService) DeletePen(id uint) error { // 检查猪栏是否关联了活跃批次 // 注意:pen.PigBatchID 是指针类型,需要检查是否为 nil if pen.PigBatchID != nil && *pen.PigBatchID != 0 { - pigBatch, err := s.batchRepository.GetPigBatchByID(*pen.PigBatchID) + pigBatch, err := s.batchRepository.GetPigBatchByID(serviceCtx, *pen.PigBatchID) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return err } @@ -304,7 +316,7 @@ func (s *pigFarmService) DeletePen(id uint) error { } // 调用仓库层进行删除 - rowsAffected, err := s.penRepository.DeletePen(id) + rowsAffected, err := s.penRepository.DeletePen(serviceCtx, id) if err != nil { return err } @@ -315,15 +327,16 @@ func (s *pigFarmService) DeletePen(id uint) error { } // UpdatePenStatus 更新猪栏状态 -func (s *pigFarmService) UpdatePenStatus(id uint, newStatus models.PenStatus) (*dto.PenResponse, error) { +func (s *pigFarmService) UpdatePenStatus(ctx context.Context, id uint, newStatus models.PenStatus) (*dto.PenResponse, error) { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "UpdatePenStatus") var updatedPen *models.Pen - err := s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { - pen, err := s.penRepository.GetPenByIDTx(tx, id) + err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error { + pen, err := s.penRepository.GetPenByIDTx(serviceCtx, tx, id) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrPenNotFound } - s.logger.Errorf("更新猪栏状态失败: 获取猪栏 %d 信息错误: %v", id, err) + logger.Errorf("更新猪栏状态失败: 获取猪栏 %d 信息错误: %v", id, err) return fmt.Errorf("获取猪栏 %d 信息失败: %w", id, err) } @@ -348,15 +361,15 @@ func (s *pigFarmService) UpdatePenStatus(id uint, newStatus models.PenStatus) (* "status": newStatus, } - if err := s.penRepository.UpdatePenFieldsTx(tx, id, updates); err != nil { - s.logger.Errorf("更新猪栏 %d 状态失败: %v", id, err) + if err := s.penRepository.UpdatePenFieldsTx(serviceCtx, tx, id, updates); err != nil { + logger.Errorf("更新猪栏 %d 状态失败: %v", id, err) return fmt.Errorf("更新猪栏 %d 状态失败: %w", id, err) } // 获取更新后的猪栏信息 - updatedPen, err = s.penRepository.GetPenByIDTx(tx, id) + updatedPen, err = s.penRepository.GetPenByIDTx(serviceCtx, tx, id) if err != nil { - s.logger.Errorf("更新猪栏状态后获取猪栏 %d 信息失败: %v", id, err) + logger.Errorf("更新猪栏状态后获取猪栏 %d 信息失败: %v", id, err) return fmt.Errorf("更新猪栏状态后获取猪栏 %d 信息失败: %w", id, err) } return nil diff --git a/internal/app/service/plan_service.go b/internal/app/service/plan_service.go index 5678fe0..ba8b416 100644 --- a/internal/app/service/plan_service.go +++ b/internal/app/service/plan_service.go @@ -1,6 +1,7 @@ package service import ( + "context" "errors" "git.huangwc.com/pig/pig-farm-controller/internal/app/dto" @@ -12,100 +13,103 @@ import ( // PlanService 定义了计划相关的应用服务接口 type PlanService interface { // CreatePlan 创建一个新的计划 - CreatePlan(req *dto.CreatePlanRequest) (*dto.PlanResponse, error) + CreatePlan(ctx context.Context, req *dto.CreatePlanRequest) (*dto.PlanResponse, error) // GetPlanByID 根据ID获取计划详情 - GetPlanByID(id uint) (*dto.PlanResponse, error) + GetPlanByID(ctx context.Context, id uint) (*dto.PlanResponse, error) // ListPlans 获取计划列表,支持过滤和分页 - ListPlans(query *dto.ListPlansQuery) (*dto.ListPlansResponse, error) + ListPlans(ctx context.Context, query *dto.ListPlansQuery) (*dto.ListPlansResponse, error) // UpdatePlan 更新计划 - UpdatePlan(id uint, req *dto.UpdatePlanRequest) (*dto.PlanResponse, error) + UpdatePlan(ctx context.Context, id uint, req *dto.UpdatePlanRequest) (*dto.PlanResponse, 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 } // planService 是 PlanService 接口的实现 type planService struct { - logger *logs.Logger - domainPlanService plan.Service // 替换为领域层的服务接口 + ctx context.Context + domainPlanService plan.Service } // NewPlanService 创建一个新的 PlanService 实例 func NewPlanService( - logger *logs.Logger, - domainPlanService plan.Service, // 接收领域层服务 + ctx context.Context, + domainPlanService plan.Service, ) PlanService { return &planService{ - logger: logger, - domainPlanService: domainPlanService, // 注入领域层服务 + ctx: ctx, + domainPlanService: domainPlanService, } } // CreatePlan 创建一个新的计划 -func (s *planService) CreatePlan(req *dto.CreatePlanRequest) (*dto.PlanResponse, error) { +func (s *planService) CreatePlan(ctx context.Context, req *dto.CreatePlanRequest) (*dto.PlanResponse, error) { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "CreatePlan") const actionType = "应用服务层:创建计划" // 使用 DTO 转换函数将请求转换为领域实体 planToCreate, err := dto.NewPlanFromCreateRequest(req) if err != nil { - s.logger.Errorf("%s: 计划数据校验失败: %v", actionType, err) + logger.Errorf("%s: 计划数据校验失败: %v", actionType, err) return nil, err } // 调用领域服务创建计划 - createdPlan, err := s.domainPlanService.CreatePlan(planToCreate) + createdPlan, err := s.domainPlanService.CreatePlan(serviceCtx, planToCreate) if err != nil { - s.logger.Errorf("%s: 领域服务创建计划失败: %v", actionType, err) + logger.Errorf("%s: 领域服务创建计划失败: %v", actionType, err) return nil, err // 直接返回领域层错误 } // 将领域实体转换为响应 DTO resp, err := dto.NewPlanToResponse(createdPlan) if err != nil { - s.logger.Errorf("%s: 序列化响应失败: %v, Plan: %+v", actionType, err, createdPlan) + logger.Errorf("%s: 序列化响应失败: %v, Plan: %+v", actionType, err, createdPlan) return nil, errors.New("计划创建成功,但响应生成失败") } - s.logger.Infof("%s: 计划创建成功, ID: %d", actionType, createdPlan.ID) + logger.Infof("%s: 计划创建成功, ID: %d", actionType, createdPlan.ID) return resp, nil } // GetPlanByID 根据ID获取计划详情 -func (s *planService) GetPlanByID(id uint) (*dto.PlanResponse, error) { +func (s *planService) GetPlanByID(ctx context.Context, id uint) (*dto.PlanResponse, error) { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "GetPlanByID") const actionType = "应用服务层:获取计划详情" // 调用领域服务获取计划 - plan, err := s.domainPlanService.GetPlanByID(id) + plan, err := s.domainPlanService.GetPlanByID(serviceCtx, id) if err != nil { - s.logger.Errorf("%s: 领域服务获取计划详情失败: %v, ID: %d", actionType, err, id) + logger.Errorf("%s: 领域服务获取计划详情失败: %v, ID: %d", actionType, err, id) return nil, err // 直接返回领域层错误 } // 将领域实体转换为响应 DTO resp, err := dto.NewPlanToResponse(plan) if err != nil { - s.logger.Errorf("%s: 序列化响应失败: %v, Plan: %+v", actionType, err, plan) + logger.Errorf("%s: 序列化响应失败: %v, Plan: %+v", actionType, err, plan) return nil, errors.New("获取计划详情失败: 内部数据格式错误") } - s.logger.Infof("%s: 获取计划详情成功, ID: %d", actionType, id) + logger.Infof("%s: 获取计划详情成功, ID: %d", actionType, id) return resp, nil } // ListPlans 获取计划列表,支持过滤和分页 -func (s *planService) ListPlans(query *dto.ListPlansQuery) (*dto.ListPlansResponse, error) { +func (s *planService) ListPlans(ctx context.Context, query *dto.ListPlansQuery) (*dto.ListPlansResponse, error) { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "ListPlans") const actionType = "应用服务层:获取计划列表" // 将 DTO 查询参数转换为领域层可接受的选项 opts := repository.ListPlansOptions{PlanType: query.PlanType} // 调用领域服务获取计划列表 - plans, total, err := s.domainPlanService.ListPlans(opts, query.Page, query.PageSize) + plans, total, err := s.domainPlanService.ListPlans(serviceCtx, opts, query.Page, query.PageSize) if err != nil { - s.logger.Errorf("%s: 领域服务获取计划列表失败: %v", actionType, err) + logger.Errorf("%s: 领域服务获取计划列表失败: %v", actionType, err) return nil, err // 直接返回领域层错误 } @@ -114,7 +118,7 @@ func (s *planService) ListPlans(query *dto.ListPlansQuery) (*dto.ListPlansRespon for _, p := range plans { resp, err := dto.NewPlanToResponse(&p) if err != nil { - s.logger.Errorf("%s: 序列化单个计划响应失败: %v, Plan: %+v", actionType, err, p) + logger.Errorf("%s: 序列化单个计划响应失败: %v, Plan: %+v", actionType, err, p) // 这里选择跳过有问题的计划,并记录错误,而不是中断整个列表的返回 continue } @@ -125,81 +129,85 @@ func (s *planService) ListPlans(query *dto.ListPlansQuery) (*dto.ListPlansRespon Plans: planResponses, Total: total, } - s.logger.Infof("%s: 获取计划列表成功, 数量: %d", actionType, len(planResponses)) + logger.Infof("%s: 获取计划列表成功, 数量: %d", actionType, len(planResponses)) return resp, nil } // UpdatePlan 更新计划 -func (s *planService) UpdatePlan(id uint, req *dto.UpdatePlanRequest) (*dto.PlanResponse, error) { +func (s *planService) UpdatePlan(ctx context.Context, id uint, req *dto.UpdatePlanRequest) (*dto.PlanResponse, error) { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "UpdatePlan") const actionType = "应用服务层:更新计划" // 使用 DTO 转换函数将请求转换为领域实体 planToUpdate, err := dto.NewPlanFromUpdateRequest(req) if err != nil { - s.logger.Errorf("%s: 计划数据校验失败: %v", actionType, err) + logger.Errorf("%s: 计划数据校验失败: %v", actionType, err) return nil, err } planToUpdate.ID = id // 确保ID被设置 // 调用领域服务更新计划 - updatedPlan, err := s.domainPlanService.UpdatePlan(planToUpdate) + updatedPlan, err := s.domainPlanService.UpdatePlan(serviceCtx, planToUpdate) if err != nil { - s.logger.Errorf("%s: 领域服务更新计划失败: %v, ID: %d", actionType, err, id) + logger.Errorf("%s: 领域服务更新计划失败: %v, ID: %d", actionType, err, id) return nil, err // 直接返回领域层错误 } // 将领域实体转换为响应 DTO resp, err := dto.NewPlanToResponse(updatedPlan) if err != nil { - s.logger.Errorf("%s: 序列化响应失败: %v, Updated Plan: %+v", actionType, err, updatedPlan) + logger.Errorf("%s: 序列化响应失败: %v, Updated Plan: %+v", actionType, err, updatedPlan) return nil, errors.New("计划更新成功,但响应生成失败") } - s.logger.Infof("%s: 计划更新成功, ID: %d", actionType, updatedPlan.ID) + logger.Infof("%s: 计划更新成功, ID: %d", actionType, updatedPlan.ID) return resp, nil } // DeletePlan 删除计划(软删除) -func (s *planService) DeletePlan(id uint) error { +func (s *planService) DeletePlan(ctx context.Context, id uint) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "DeletePlan") const actionType = "应用服务层:删除计划" // 调用领域服务删除计划 - err := s.domainPlanService.DeletePlan(id) + err := s.domainPlanService.DeletePlan(serviceCtx, id) if err != nil { - s.logger.Errorf("%s: 领域服务删除计划失败: %v, ID: %d", actionType, err, id) + 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 *planService) StartPlan(id uint) error { +func (s *planService) StartPlan(ctx context.Context, id uint) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "StartPlan") const actionType = "应用服务层:启动计划" // 调用领域服务启动计划 - err := s.domainPlanService.StartPlan(id) + err := s.domainPlanService.StartPlan(serviceCtx, id) if err != nil { - s.logger.Errorf("%s: 领域服务启动计划失败: %v, ID: %d", actionType, err, id) + 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 } // StopPlan 停止计划 -func (s *planService) StopPlan(id uint) error { +func (s *planService) StopPlan(ctx context.Context, id uint) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "StopPlan") const actionType = "应用服务层:停止计划" // 调用领域服务停止计划 - err := s.domainPlanService.StopPlan(id) + err := s.domainPlanService.StopPlan(serviceCtx, id) if err != nil { - s.logger.Errorf("%s: 领域服务停止计划失败: %v, ID: %d", actionType, err, id) + 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/app/service/user_service.go b/internal/app/service/user_service.go index 49de862..3efabc8 100644 --- a/internal/app/service/user_service.go +++ b/internal/app/service/user_service.go @@ -1,59 +1,62 @@ package service import ( + "context" "errors" "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" ) // UserService 定义用户服务接口 type UserService interface { - CreateUser(req *dto.CreateUserRequest) (*dto.CreateUserResponse, error) - Login(req *dto.LoginRequest) (*dto.LoginResponse, error) - SendTestNotification(userID uint, req *dto.SendTestNotificationRequest) error + CreateUser(ctx context.Context, req *dto.CreateUserRequest) (*dto.CreateUserResponse, error) + Login(ctx context.Context, req *dto.LoginRequest) (*dto.LoginResponse, error) + SendTestNotification(ctx context.Context, userID uint, req *dto.SendTestNotificationRequest) error } // userService 实现了 UserService 接口 type userService struct { - userRepo repository.UserRepository - tokenService token.Service - notifyService domain_notify.Service - logger *logs.Logger + 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, - logger *logs.Logger, ) UserService { return &userService{ - userRepo: userRepo, - tokenService: tokenService, - notifyService: notifyService, - logger: logger, + ctx: ctx, + userRepo: userRepo, + tokenGenerator: tokenGenerator, + notifyService: notifyService, } } // CreateUser 创建新用户 -func (s *userService) CreateUser(req *dto.CreateUserRequest) (*dto.CreateUserResponse, error) { +func (s *userService) CreateUser(ctx context.Context, req *dto.CreateUserRequest) (*dto.CreateUserResponse, error) { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "CreateUser") user := &models.User{ Username: req.Username, Password: req.Password, // 密码会在 BeforeSave 钩子中哈希 } - if err := s.userRepo.Create(user); err != nil { - s.logger.Errorf("创建用户: 创建用户失败: %v", err) + if err := s.userRepo.Create(serviceCtx, user); err != nil { + logger.Errorf("创建用户: 创建用户失败: %v", err) // 尝试查询用户,以判断是否是用户名重复导致的错误 - _, findErr := s.userRepo.FindByUsername(req.Username) + _, findErr := s.userRepo.FindByUsername(serviceCtx, req.Username) if findErr == nil { // 如果能找到用户,说明是用户名重复 return nil, errors.New("用户名已存在") } @@ -69,14 +72,15 @@ func (s *userService) CreateUser(req *dto.CreateUserRequest) (*dto.CreateUserRes } // Login 用户登录 -func (s *userService) Login(req *dto.LoginRequest) (*dto.LoginResponse, error) { +func (s *userService) Login(ctx context.Context, req *dto.LoginRequest) (*dto.LoginResponse, error) { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "Login") // 使用新的方法,通过唯一标识符(用户名、邮箱等)查找用户 - user, err := s.userRepo.FindUserForLogin(req.Identifier) + user, err := s.userRepo.FindUserForLogin(serviceCtx, req.Identifier) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("登录凭证不正确") } - s.logger.Errorf("登录: 查询用户失败: %v", err) + logger.Errorf("登录: 查询用户失败: %v", err) return nil, errors.New("登录失败") } @@ -85,9 +89,9 @@ func (s *userService) Login(req *dto.LoginRequest) (*dto.LoginResponse, error) { } // 登录成功,生成 JWT token - tokenString, err := s.tokenService.GenerateToken(user.ID) + tokenString, err := s.tokenGenerator.GenerateToken(user.ID) if err != nil { - s.logger.Errorf("登录: 生成令牌失败: %v", err) + logger.Errorf("登录: 生成令牌失败: %v", err) return nil, errors.New("登录失败,无法生成认证信息") } @@ -99,12 +103,13 @@ func (s *userService) Login(req *dto.LoginRequest) (*dto.LoginResponse, error) { } // SendTestNotification 发送测试通知 -func (s *userService) SendTestNotification(userID uint, req *dto.SendTestNotificationRequest) error { - err := s.notifyService.SendTestMessage(userID, req.Type) +func (s *userService) SendTestNotification(ctx context.Context, userID uint, req *dto.SendTestNotificationRequest) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "SendTestNotification") + err := s.notifyService.SendTestMessage(serviceCtx, userID, req.Type) if err != nil { - s.logger.Errorf("发送测试通知: 服务层调用失败: %v", err) + logger.Errorf("发送测试通知: 服务层调用失败: %v", err) return errors.New("发送测试消息失败: " + err.Error()) } - s.logger.Infof("发送测试通知: 成功为用户 %d 发送类型为 %s 的测试消息", userID, req.Type) + logger.Infof("发送测试通知: 成功为用户 %d 发送类型为 %s 的测试消息", userID, req.Type) return nil } diff --git a/internal/app/webhook/chirp_stack.go b/internal/app/webhook/chirp_stack.go index 7be960e..1af2d83 100644 --- a/internal/app/webhook/chirp_stack.go +++ b/internal/app/webhook/chirp_stack.go @@ -1,6 +1,7 @@ package webhook import ( + "context" "encoding/base64" "encoding/json" "io" @@ -30,7 +31,7 @@ const ( // ChirpStackListener 是一个监听器, 用于监听ChirpStack反馈的设备上行事件 type ChirpStackListener struct { - logger *logs.Logger + ctx context.Context sensorDataRepo repository.SensorDataRepository deviceRepo repository.DeviceRepository areaControllerRepo repository.AreaControllerRepository @@ -40,15 +41,15 @@ type ChirpStackListener struct { // NewChirpStackListener 创建一个新的 ChirpStackListener 实例 func NewChirpStackListener( - logger *logs.Logger, + ctx context.Context, sensorDataRepo repository.SensorDataRepository, deviceRepo repository.DeviceRepository, areaControllerRepo repository.AreaControllerRepository, deviceCommandLogRepo repository.DeviceCommandLogRepository, pendingCollectionRepo repository.PendingCollectionRepository, -) ListenHandler { // 返回接口类型 +) ListenHandler { return &ChirpStackListener{ - logger: logger, + ctx: ctx, sensorDataRepo: sensorDataRepo, deviceRepo: deviceRepo, areaControllerRepo: areaControllerRepo, @@ -60,11 +61,13 @@ func NewChirpStackListener( // Handler 监听ChirpStack反馈的事件, 因为这是个Webhook, 所以直接回复掉再慢慢处理信息 func (c *ChirpStackListener) Handler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + ctx, logger := logs.Trace(r.Context(), c.ctx, "ChirpStackListener") + defer r.Body.Close() b, err := io.ReadAll(r.Body) if err != nil { - c.logger.Errorf("读取请求体失败: %v", err) + logger.Errorf("读取请求体失败: %v", err) http.Error(w, "failed to read body", http.StatusBadRequest) return } @@ -74,100 +77,102 @@ func (c *ChirpStackListener) Handler() http.HandlerFunc { w.WriteHeader(http.StatusOK) // 将异步处理逻辑委托给 handler 方法 - go c.handler(b, event) + go c.handler(ctx, b, event) } } // handler 用于处理 ChirpStack 发送的事件 -func (c *ChirpStackListener) handler(data []byte, eventType string) { +func (c *ChirpStackListener) handler(ctx context.Context, data []byte, eventType string) { + reqCtx, logger := logs.Trace(ctx, c.ctx, "ChirpStackListener.handler") switch eventType { case eventTypeUp: var msg UpEvent if err := json.Unmarshal(data, &msg); err != nil { - c.logger.Errorf("解析 'up' 事件失败: %v, data: %s", err, string(data)) + logger.Errorf("解析 'up' 事件失败: %v, data: %s", err, string(data)) return } - c.handleUpEvent(&msg) + c.handleUpEvent(reqCtx, &msg) case eventTypeJoin: var msg JoinEvent if err := json.Unmarshal(data, &msg); err != nil { - c.logger.Errorf("解析 'join' 事件失败: %v, data: %s", err, string(data)) + logger.Errorf("解析 'join' 事件失败: %v, data: %s", err, string(data)) return } - c.handleJoinEvent(&msg) + c.handleJoinEvent(reqCtx, &msg) case eventTypeAck: var msg AckEvent if err := json.Unmarshal(data, &msg); err != nil { - c.logger.Errorf("解析 'ack' 事件失败: %v, data: %s", err, string(data)) + logger.Errorf("解析 'ack' 事件失败: %v, data: %s", err, string(data)) return } - c.handleAckEvent(&msg) + c.handleAckEvent(reqCtx, &msg) case eventTypeTxAck: var msg TxAckEvent if err := json.Unmarshal(data, &msg); err != nil { - c.logger.Errorf("解析 'txack' 事件失败: %v, data: %s", err, string(data)) + logger.Errorf("解析 'txack' 事件失败: %v, data: %s", err, string(data)) return } - c.handleTxAckEvent(&msg) + c.handleTxAckEvent(reqCtx, &msg) case eventTypeStatus: var msg StatusEvent if err := json.Unmarshal(data, &msg); err != nil { - c.logger.Errorf("解析 'status' 事件失败: %v, data: %s", err, string(data)) + logger.Errorf("解析 'status' 事件失败: %v, data: %s", err, string(data)) return } - c.handleStatusEvent(&msg) + c.handleStatusEvent(reqCtx, &msg) case eventTypeLog: var msg LogEvent if err := json.Unmarshal(data, &msg); err != nil { - c.logger.Errorf("解析 'log' 事件失败: %v, data: %s", err, string(data)) + logger.Errorf("解析 'log' 事件失败: %v, data: %s", err, string(data)) return } - c.handleLogEvent(&msg) + c.handleLogEvent(reqCtx, &msg) case eventTypeLocation: var msg LocationEvent if err := json.Unmarshal(data, &msg); err != nil { - c.logger.Errorf("解析 'location' 事件失败: %v, data: %s", err, string(data)) + logger.Errorf("解析 'location' 事件失败: %v, data: %s", err, string(data)) return } - c.handleLocationEvent(&msg) + c.handleLocationEvent(reqCtx, &msg) case eventTypeIntegration: var msg IntegrationEvent if err := json.Unmarshal(data, &msg); err != nil { - c.logger.Errorf("解析 'integration' 事件失败: %v, data: %s", err, string(data)) + logger.Errorf("解析 'integration' 事件失败: %v, data: %s", err, string(data)) return } - c.handleIntegrationEvent(&msg) + c.handleIntegrationEvent(reqCtx, &msg) default: - c.logger.Errorf("未知的ChirpStack事件: %s, data: %s", eventType, string(data)) + logger.Errorf("未知的ChirpStack事件: %s, data: %s", eventType, string(data)) } } // --- 业务处理函数 --- // handleUpEvent 处理上行数据事件 -func (c *ChirpStackListener) handleUpEvent(event *UpEvent) { - c.logger.Infof("开始处理 'up' 事件, DevEui: %s", event.DeviceInfo.DevEui) +func (c *ChirpStackListener) handleUpEvent(ctx context.Context, event *UpEvent) { + reqCtx, logger := logs.Trace(ctx, c.ctx, "ChirpStackListener.handleUpEvent") + logger.Infof("开始处理 'up' 事件, DevEui: %s", event.DeviceInfo.DevEui) // 1. 查找区域主控设备 - regionalController, err := c.areaControllerRepo.FindByNetworkID(event.DeviceInfo.DevEui) + regionalController, err := c.areaControllerRepo.FindByNetworkID(reqCtx, event.DeviceInfo.DevEui) if err != nil { - c.logger.Errorf("处理 'up' 事件失败:无法通过 DevEui '%s' 找到区域主控设备: %v", event.DeviceInfo.DevEui, err) + logger.Errorf("处理 'up' 事件失败:无法通过 DevEui '%s' 找到区域主控设备: %v", event.DeviceInfo.DevEui, err) return } // 依赖 SelfCheck 确保区域主控有效 if err := regionalController.SelfCheck(); err != nil { - c.logger.Errorf("处理 'up' 事件失败:区域主控 %v(ID: %d) 未通过自检: %v", regionalController.Name, regionalController.ID, err) + logger.Errorf("处理 'up' 事件失败:区域主控 %v(ID: %d) 未通过自检: %v", regionalController.Name, regionalController.ID, err) return } - c.logger.Infof("找到区域主控: %s (ID: %d)", regionalController.Name, regionalController.ID) + logger.Infof("找到区域主控: %s (ID: %d)", regionalController.Name, regionalController.ID) // 2. 记录区域主控的信号强度 (如果存在) if len(event.RxInfo) > 0 { @@ -182,29 +187,29 @@ func (c *ChirpStackListener) handleUpEvent(event *UpEvent) { } // 记录信号强度 - c.recordSensorData(regionalController.ID, regionalController.ID, event.Time, models.SensorTypeSignalMetrics, signalMetrics) - c.logger.Infof("已记录区域主控 (ID: %d) 的信号强度: RSSI=%d, SNR=%.2f", regionalController.ID, rx.Rssi, rx.Snr) + c.recordSensorData(reqCtx, regionalController.ID, regionalController.ID, event.Time, models.SensorTypeSignalMetrics, signalMetrics) + logger.Infof("已记录区域主控 (ID: %d) 的信号强度: RSSI=%d, SNR=%.2f", regionalController.ID, rx.Rssi, rx.Snr) } else { - c.logger.Warnf("处理 'up' 事件时未找到 RxInfo,无法记录信号数据。DevEui: %s", event.DeviceInfo.DevEui) + logger.Warnf("处理 'up' 事件时未找到 RxInfo,无法记录信号数据。DevEui: %s", event.DeviceInfo.DevEui) } // 3. 处理上报的传感器数据 if event.Data == "" { - c.logger.Warnf("处理 'up' 事件时 Data 字段为空,无需记录上行数据。DevEui: %s", event.DeviceInfo.DevEui) + logger.Warnf("处理 'up' 事件时 Data 字段为空,无需记录上行数据。DevEui: %s", event.DeviceInfo.DevEui) return } // 3.1 Base64 解码 decodedData, err := base64.StdEncoding.DecodeString(event.Data) if err != nil { - c.logger.Errorf("Base64 解码 'up' 事件的 Data 失败: %v, Data: %s", err, event.Data) + logger.Errorf("Base64 解码 'up' 事件的 Data 失败: %v, Data: %s", err, event.Data) return } // 3.2 解析外层 "信封" var instruction proto.Instruction if err := gproto.Unmarshal(decodedData, &instruction); err != nil { - c.logger.Errorf("解析上行 Instruction Protobuf 失败: %v, Decoded Data: %x", err, decodedData) + logger.Errorf("解析上行 Instruction Protobuf 失败: %v, Decoded Data: %x", err, decodedData) return } @@ -215,29 +220,29 @@ func (c *ChirpStackListener) handleUpEvent(event *UpEvent) { collectResp = p.CollectResult default: // 如果上行的数据不是采集结果,记录日志并忽略 - c.logger.Infof("收到一个非采集响应的上行指令 (Type: %T),无需处理。", p) + logger.Infof("收到一个非采集响应的上行指令 (Type: %T),无需处理。", p) return } // 检查 collectResp 是否为 nil,虽然在 type switch 成功的情况下不太可能 if collectResp == nil { - c.logger.Errorf("从 Instruction 中提取的 CollectResult 为 nil") + logger.Errorf("从 Instruction 中提取的 CollectResult 为 nil") return } correlationID := collectResp.CorrelationId - c.logger.Infof("成功解析采集响应 (CorrelationID: %s),包含 %d 个值。", correlationID, len(collectResp.Values)) + logger.Infof("成功解析采集响应 (CorrelationID: %s),包含 %d 个值。", correlationID, len(collectResp.Values)) // 4. 根据 CorrelationID 查找待处理请求 - pendingReq, err := c.pendingCollectionRepo.FindByCorrelationID(correlationID) + pendingReq, err := c.pendingCollectionRepo.FindByCorrelationID(reqCtx, correlationID) if err != nil { - c.logger.Errorf("处理采集响应失败:无法找到待处理请求 (CorrelationID: %s): %v", correlationID, err) + logger.Errorf("处理采集响应失败:无法找到待处理请求 (CorrelationID: %s): %v", correlationID, err) return } // 检查状态,防止重复处理 if pendingReq.Status != models.PendingStatusPending && pendingReq.Status != models.PendingStatusTimedOut { - c.logger.Warnf("收到一个已处理过的采集响应 (CorrelationID: %s, Status: %s),将忽略。", correlationID, pendingReq.Status) + logger.Warnf("收到一个已处理过的采集响应 (CorrelationID: %s, Status: %s),将忽略。", correlationID, pendingReq.Status) return } @@ -245,11 +250,11 @@ func (c *ChirpStackListener) handleUpEvent(event *UpEvent) { deviceIDs := pendingReq.CommandMetadata values := collectResp.Values if len(deviceIDs) != len(values) { - c.logger.Errorf("数据不匹配:下行指令要求采集 %d 个设备,但上行响应包含 %d 个值 (CorrelationID: %s)", len(deviceIDs), len(values), correlationID) + logger.Errorf("数据不匹配:下行指令要求采集 %d 个设备,但上行响应包含 %d 个值 (CorrelationID: %s)", len(deviceIDs), len(values), correlationID) // 即使数量不匹配,也更新状态为完成,以防止请求永远 pending - err = c.pendingCollectionRepo.UpdateStatusToFulfilled(correlationID, event.Time) + err = c.pendingCollectionRepo.UpdateStatusToFulfilled(reqCtx, correlationID, event.Time) if err != nil { - c.logger.Errorf("处理采集响应失败:无法更新待处理请求 (CorrelationID: %s) 的状态为完成: %v", correlationID, err) + logger.Errorf("处理采集响应失败:无法更新待处理请求 (CorrelationID: %s) 的状态为完成: %v", correlationID, err) } return } @@ -259,35 +264,35 @@ func (c *ChirpStackListener) handleUpEvent(event *UpEvent) { // 检查设备上报的值是否为 NaN (Not a Number),如果是则跳过 if math.IsNaN(float64(rawSensorValue)) { - c.logger.Warnf("设备 (ID: %d) 上报了一个无效的 NaN 值,已跳过当前值的记录。", deviceID) + logger.Warnf("设备 (ID: %d) 上报了一个无效的 NaN 值,已跳过当前值的记录。", deviceID) continue } // 5.1 获取设备及其模板 - dev, err := c.deviceRepo.FindByID(deviceID) + dev, err := c.deviceRepo.FindByID(reqCtx, deviceID) if err != nil { - c.logger.Errorf("处理采集数据失败:无法找到设备 (ID: %d): %v", deviceID, err) + logger.Errorf("处理采集数据失败:无法找到设备 (ID: %d): %v", deviceID, err) continue } // 依赖 SelfCheck 确保设备和模板有效 if err := dev.SelfCheck(); err != nil { - c.logger.Warnf("跳过设备 %d,因其未通过自检: %v", dev.ID, err) + logger.Warnf("跳过设备 %d,因其未通过自检: %v", dev.ID, err) continue } if err := dev.DeviceTemplate.SelfCheck(); err != nil { - c.logger.Warnf("跳过设备 %d,因其设备模板未通过自检: %v", dev.ID, err) + logger.Warnf("跳过设备 %d,因其设备模板未通过自检: %v", dev.ID, err) continue } // 5.2 从设备模板中解析 ValueDescriptor var valueDescriptors []*models.ValueDescriptor if err := dev.DeviceTemplate.ParseValues(&valueDescriptors); err != nil { - c.logger.Warnf("跳过设备 %d,因其设备模板的 Values 属性解析失败: %v", dev.ID, err) + logger.Warnf("跳过设备 %d,因其设备模板的 Values 属性解析失败: %v", dev.ID, err) continue } // 根据 DeviceTemplate.SelfCheck,这里应该只有一个 ValueDescriptor if len(valueDescriptors) == 0 { - c.logger.Warnf("跳过设备 %d,因其设备模板缺少 ValueDescriptor 定义", dev.ID) + logger.Warnf("跳过设备 %d,因其设备模板缺少 ValueDescriptor 定义", dev.ID) continue } valueDescriptor := valueDescriptors[0] @@ -306,31 +311,32 @@ func (c *ChirpStackListener) handleUpEvent(event *UpEvent) { dataToRecord = models.WeightData{WeightKilograms: parsedValue} default: // TODO 未知传感器的数据需要记录吗 - c.logger.Warnf("未知的传感器类型 '%s',将使用通用格式记录", valueDescriptor.Type) + logger.Warnf("未知的传感器类型 '%s',将使用通用格式记录", valueDescriptor.Type) dataToRecord = map[string]float64{"value": parsedValue} } // 5.5 记录传感器数据 - c.recordSensorData(regionalController.ID, dev.ID, event.Time, valueDescriptor.Type, dataToRecord) - c.logger.Infof("成功记录传感器数据: 设备ID=%d, 类型=%s, 原始值=%f, 解析值=%.2f", dev.ID, valueDescriptor.Type, rawSensorValue, parsedValue) + c.recordSensorData(reqCtx, regionalController.ID, dev.ID, event.Time, valueDescriptor.Type, dataToRecord) + logger.Infof("成功记录传感器数据: 设备ID=%d, 类型=%s, 原始值=%f, 解析值=%.2f", dev.ID, valueDescriptor.Type, rawSensorValue, parsedValue) } // 6. 更新请求状态为“已完成” - if err := c.pendingCollectionRepo.UpdateStatusToFulfilled(correlationID, event.Time); err != nil { - c.logger.Errorf("更新待采集请求状态为 'fulfilled' 失败 (CorrelationID: %s): %v", correlationID, err) + if err := c.pendingCollectionRepo.UpdateStatusToFulfilled(reqCtx, correlationID, event.Time); err != nil { + logger.Errorf("更新待采集请求状态为 'fulfilled' 失败 (CorrelationID: %s): %v", correlationID, err) } else { - c.logger.Infof("成功完成并关闭采集请求 (CorrelationID: %s)", correlationID) + logger.Infof("成功完成并关闭采集请求 (CorrelationID: %s)", correlationID) } } // handleStatusEvent 处理设备状态事件 -func (c *ChirpStackListener) handleStatusEvent(event *StatusEvent) { - c.logger.Infof("处接收到理 'status' 事件: %+v", event) +func (c *ChirpStackListener) handleStatusEvent(ctx context.Context, event *StatusEvent) { + reqCtx, logger := logs.Trace(ctx, c.ctx, "handleStatusEvent") + logger.Infof("处接收到理 'status' 事件: %+v", event) // 查找区域主控设备 - regionalController, err := c.areaControllerRepo.FindByNetworkID(event.DeviceInfo.DevEui) + regionalController, err := c.areaControllerRepo.FindByNetworkID(reqCtx, event.DeviceInfo.DevEui) if err != nil { - c.logger.Errorf("处理 'status' 事件失败:无法通过 DevEui '%s' 找到区域主控设备: %v", event.DeviceInfo.DevEui, err) + logger.Errorf("处理 'status' 事件失败:无法通过 DevEui '%s' 找到区域主控设备: %v", event.DeviceInfo.DevEui, err) return } @@ -338,8 +344,8 @@ func (c *ChirpStackListener) handleStatusEvent(event *StatusEvent) { signalMetrics := models.SignalMetrics{ MarginDb: event.Margin, } - c.recordSensorData(regionalController.ID, regionalController.ID, event.Time, models.SensorTypeSignalMetrics, signalMetrics) - c.logger.Infof("已记录区域主控 (ID: %d) 的信号状态: %+v", regionalController.ID, signalMetrics) + c.recordSensorData(reqCtx, regionalController.ID, regionalController.ID, event.Time, models.SensorTypeSignalMetrics, signalMetrics) + logger.Infof("已记录区域主控 (ID: %d) 的信号状态: %+v", regionalController.ID, signalMetrics) // 记录电量 batteryLevel := models.BatteryLevel{ @@ -347,68 +353,74 @@ func (c *ChirpStackListener) handleStatusEvent(event *StatusEvent) { BatteryLevelUnavailable: event.BatteryLevelUnavailable, ExternalPower: event.ExternalPower, } - c.recordSensorData(regionalController.ID, regionalController.ID, event.Time, models.SensorTypeBatteryLevel, batteryLevel) - c.logger.Infof("已记录区域主控 (ID: %d) 的电池状态: %+v", regionalController.ID, batteryLevel) + c.recordSensorData(reqCtx, regionalController.ID, regionalController.ID, event.Time, models.SensorTypeBatteryLevel, batteryLevel) + logger.Infof("已记录区域主控 (ID: %d) 的电池状态: %+v", regionalController.ID, batteryLevel) } // handleAckEvent 处理下行确认事件 -func (c *ChirpStackListener) handleAckEvent(event *AckEvent) { - c.logger.Infof("接收到 'ack' 事件: %+v", event) +func (c *ChirpStackListener) handleAckEvent(ctx context.Context, event *AckEvent) { + reqCtx, logger := logs.Trace(ctx, c.ctx, "handleAckEvent") + logger.Infof("接收到 'ack' 事件: %+v", event) // 更新下行任务记录的确认时间及接收成功状态 - err := c.deviceCommandLogRepo.UpdateAcknowledgedAt(event.DeduplicationID, event.Time, event.Acknowledged) + err := c.deviceCommandLogRepo.UpdateAcknowledgedAt(reqCtx, event.DeduplicationID, event.Time, event.Acknowledged) if err != nil { - c.logger.Errorf("更新下行任务记录的确认时间及接收成功状态失败 (MessageID: %s, DevEui: %s, Acknowledged: %t): %v", + logger.Errorf("更新下行任务记录的确认时间及接收成功状态失败 (MessageID: %s, DevEui: %s, Acknowledged: %t): %v", event.DeduplicationID, event.DeviceInfo.DevEui, event.Acknowledged, err) return } - c.logger.Infof("成功更新下行任务记录确认时间及接收成功状态 (MessageID: %s, DevEui: %s, Acknowledged: %t, AcknowledgedAt: %s)", + logger.Infof("成功更新下行任务记录确认时间及接收成功状态 (MessageID: %s, DevEui: %s, Acknowledged: %t, AcknowledgedAt: %s)", event.DeduplicationID, event.DeviceInfo.DevEui, event.Acknowledged, event.Time.Format(time.RFC3339)) } // handleLogEvent 处理日志事件 -func (c *ChirpStackListener) handleLogEvent(event *LogEvent) { +func (c *ChirpStackListener) handleLogEvent(ctx context.Context, event *LogEvent) { + logger := logs.TraceLogger(ctx, c.ctx, "handleLogEvent") // 首先,打印完整的事件结构体,用于详细排查 - c.logger.Infof("接收到 'log' 事件的完整内容: %+v", event) + logger.Infof("接收到 'log' 事件的完整内容: %+v", event) // 接着,根据 ChirpStack 日志的级别,使用我们自己的 logger 对应级别来打印核心信息 logMessage := "ChirpStack 日志: [%s] %s (DevEui: %s)" switch event.Level { case "INFO": - c.logger.Infof(logMessage, event.Code, event.Description, event.DeviceInfo.DevEui) + logger.Infof(logMessage, event.Code, event.Description, event.DeviceInfo.DevEui) case "WARNING": - c.logger.Warnf(logMessage, event.Code, event.Description, event.DeviceInfo.DevEui) + logger.Warnf(logMessage, event.Code, event.Description, event.DeviceInfo.DevEui) case "ERROR": - c.logger.Errorf(logMessage, event.Code, event.Description, event.DeviceInfo.DevEui) + logger.Errorf(logMessage, event.Code, event.Description, event.DeviceInfo.DevEui) default: // 对于未知级别,使用 Warn 级别打印,并明确指出级别未知 - c.logger.Warnf("ChirpStack 日志: [未知级别: %s] %s %s (DevEui: %s)", + logger.Warnf("ChirpStack 日志: [未知级别: %s] %s %s (DevEui: %s)", event.Level, event.Code, event.Description, event.DeviceInfo.DevEui) } } // handleJoinEvent 处理入网事件 -func (c *ChirpStackListener) handleJoinEvent(event *JoinEvent) { - c.logger.Infof("接收到 'join' 事件: %+v", event) +func (c *ChirpStackListener) handleJoinEvent(ctx context.Context, event *JoinEvent) { + logger := logs.TraceLogger(ctx, c.ctx, "handleJoinEvent") + logger.Infof("接收到 'join' 事件: %+v", event) // 在这里添加您的业务逻辑 } // handleTxAckEvent 处理网关发送确认事件 -func (c *ChirpStackListener) handleTxAckEvent(event *TxAckEvent) { - c.logger.Infof("接收到 'txack' 事件: %+v", event) +func (c *ChirpStackListener) handleTxAckEvent(ctx context.Context, event *TxAckEvent) { + logger := logs.TraceLogger(ctx, c.ctx, "handleTxAckEvent") + logger.Infof("接收到 'txack' 事件: %+v", event) // 在这里添加您的业务逻辑 } // handleLocationEvent 处理位置事件 -func (c *ChirpStackListener) handleLocationEvent(event *LocationEvent) { - c.logger.Infof("接收到 'location' 事件: %+v", event) +func (c *ChirpStackListener) handleLocationEvent(ctx context.Context, event *LocationEvent) { + logger := logs.TraceLogger(ctx, c.ctx, "handleLocationEvent") + logger.Infof("接收到 'location' 事件: %+v", event) // 在这里添加您的业务逻辑 } // handleIntegrationEvent 处理集成事件 -func (c *ChirpStackListener) handleIntegrationEvent(event *IntegrationEvent) { - c.logger.Infof("接收到 'integration' 事件: %+v", event) +func (c *ChirpStackListener) handleIntegrationEvent(ctx context.Context, event *IntegrationEvent) { + logger := logs.TraceLogger(ctx, c.ctx, "handleIntegrationEvent") + logger.Infof("接收到 'integration' 事件: %+v", event) // 在这里添加您的业务逻辑 } @@ -417,11 +429,12 @@ func (c *ChirpStackListener) handleIntegrationEvent(event *IntegrationEvent) { // sensorDeviceID: 实际产生传感器数据的普通设备的ID // sensorType: 传感器值的类型 (例如 models.SensorTypeTemperature) // data: 具体的传感器数据结构体实例 (例如 models.TemperatureData) -func (c *ChirpStackListener) recordSensorData(regionalControllerID uint, sensorDeviceID uint, eventTime time.Time, sensorType models.SensorType, data interface{}) { +func (c *ChirpStackListener) recordSensorData(ctx context.Context, regionalControllerID uint, sensorDeviceID uint, eventTime time.Time, sensorType models.SensorType, data interface{}) { + reqCtx, logger := logs.Trace(ctx, c.ctx, "recordSensorData") // 1. 将传入的结构体序列化为 JSON jsonData, err := json.Marshal(data) if err != nil { - c.logger.Errorf("记录传感器数据失败:序列化数据为 JSON 时出错: %v", err) + logger.Errorf("记录传感器数据失败:序列化数据为 JSON 时出错: %v", err) return } @@ -435,7 +448,7 @@ func (c *ChirpStackListener) recordSensorData(regionalControllerID uint, sensorD } // 3. 调用仓库创建记录 - if err := c.sensorDataRepo.Create(sensorData); err != nil { - c.logger.Errorf("记录传感器数据失败:存入数据库时出错: %v", err) + if err := c.sensorDataRepo.Create(reqCtx, sensorData); err != nil { + logger.Errorf("记录传感器数据失败:存入数据库时出错: %v", err) } } diff --git a/internal/app/webhook/placeholder_listener.go b/internal/app/webhook/placeholder_listener.go index ffcf216..2d3979d 100644 --- a/internal/app/webhook/placeholder_listener.go +++ b/internal/app/webhook/placeholder_listener.go @@ -1,6 +1,7 @@ package webhook import ( + "context" "net/http" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" @@ -8,15 +9,14 @@ import ( // PlaceholderListener 是一个占位符, 用于在非 LoRaWAN 配置下满足 ListenHandler 接口 type PlaceholderListener struct { - logger *logs.Logger + ctx context.Context } // NewPlaceholderListener 创建一个新的 PlaceholderListener 实例 // 它只打印一条日志, 表明 ChirpStack webhook 未被激活 -func NewPlaceholderListener(logger *logs.Logger) ListenHandler { - logger.Info("当前配置非 LoRaWAN, ChirpStack webhook 监听器未激活。") +func NewPlaceholderListener(ctx context.Context) ListenHandler { return &PlaceholderListener{ - logger: logger, + ctx: ctx, } } @@ -24,7 +24,8 @@ func NewPlaceholderListener(logger *logs.Logger) ListenHandler { // 理论上, 在占位符生效的模式下, 这个 Handler 不应该被调用 func (p *PlaceholderListener) Handler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - p.logger.Warn("PlaceholderListener 的 Handler 被调用, 这通常是意料之外的。") + logger := logs.TraceLogger(r.Context(), p.ctx, "PlaceholderListener") + logger.Warn("PlaceholderListener 的 Handler 被调用, 这通常是意料之外的。") w.WriteHeader(http.StatusNotImplemented) } } diff --git a/internal/core/application.go b/internal/core/application.go index 6636638..7c5c3a3 100644 --- a/internal/core/application.go +++ b/internal/core/application.go @@ -1,6 +1,7 @@ package core import ( + "context" "fmt" "os" "os/signal" @@ -14,7 +15,7 @@ import ( // Application 是整个应用的核心,封装了所有组件和生命周期。 type Application struct { Config *config.Config - Logger *logs.Logger + Ctx context.Context API *api.API Infra *Infrastructure @@ -30,20 +31,25 @@ func NewApplication(configPath string) (*Application, error) { if err := cfg.Load(configPath); err != nil { return nil, fmt.Errorf("无法加载配置: %w", err) } - logger := logs.NewLogger(cfg.Log) + // 初始化全局日志记录器 + logs.InitDefaultLogger(cfg.Log) + + // 为 Application 本身创建 Ctx + selfCtx := logs.AddCompName(context.Background(), "Application") + ctx := logs.AddFuncName(selfCtx, selfCtx, "NewApplication") // 2. 初始化所有分层服务 - infra, err := initInfrastructure(cfg, logger) + infra, err := initInfrastructure(ctx, cfg) if err != nil { return nil, fmt.Errorf("初始化基础设施失败: %w", err) } - domain := initDomainServices(cfg, infra, logger) - appServices := initAppServices(infra, domain, logger) + domain := initDomainServices(ctx, cfg, infra) + appServices := initAppServices(ctx, infra, domain) // 3. 初始化 API 入口点 apiServer := api.NewAPI( cfg.Server, - logger, + logs.AddCompName(context.Background(), "API"), infra.repos.userRepo, appServices.pigFarmService, appServices.pigBatchService, @@ -51,15 +57,15 @@ func NewApplication(configPath string) (*Application, error) { appServices.deviceService, appServices.planService, appServices.userService, - infra.tokenService, appServices.auditService, + infra.tokenGenerator, infra.lora.listenHandler, ) // 4. 组装 Application 对象 app := &Application{ Config: cfg, - Logger: logger, + Ctx: selfCtx, API: apiServer, Infra: infra, Domain: domain, @@ -71,20 +77,21 @@ func NewApplication(configPath string) (*Application, error) { // Start 启动应用的所有组件并阻塞,直到接收到关闭信号。 func (app *Application) Start() error { - app.Logger.Info("应用启动中...") + startCtx, logger := logs.Trace(app.Ctx, app.Ctx, "Start") + logger.Info("应用启动中...") // 1. 启动底层监听器 - if err := app.Infra.lora.loraListener.Listen(); err != nil { + if err := app.Infra.lora.loraListener.Listen(startCtx); err != nil { return fmt.Errorf("启动 LoRa Mesh 监听器失败: %w", err) } // 2. 初始化应用状态 (清理、刷新任务等) - if err := app.initializeState(); err != nil { + if err := app.initializeState(startCtx); err != nil { return fmt.Errorf("初始化应用状态失败: %w", err) } // 3. 启动后台工作协程 - app.Domain.planService.Start() + app.Domain.planService.Start(startCtx) // 4. 启动 API 服务器 app.API.Start() @@ -100,27 +107,28 @@ func (app *Application) Start() error { // Stop 优雅地关闭应用的所有组件。 func (app *Application) Stop() error { - app.Logger.Info("应用关闭中...") + 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 { - app.Logger.Errorw("数据库连接断开失败", "error", err) + if err := app.Infra.storage.Disconnect(stopCtx); err != nil { + logger.Errorw("数据库连接断开失败", "error", err) } // 关闭 LoRa Mesh 监听器 - if err := app.Infra.lora.loraListener.Stop(); err != nil { - app.Logger.Errorw("LoRa Mesh 监听器关闭失败", "error", err) + if err := app.Infra.lora.loraListener.Stop(stopCtx); err != nil { + logger.Errorw("LoRa Mesh 监听器关闭失败", "error", err) } // 刷新日志缓冲区 - _ = app.Logger.Sync() + _ = logger.Sync() - app.Logger.Info("应用已成功关闭") + logger.Info("应用已成功关闭") return nil } diff --git a/internal/core/component_initializers.go b/internal/core/component_initializers.go index a04a258..fb94a45 100644 --- a/internal/core/component_initializers.go +++ b/internal/core/component_initializers.go @@ -1,18 +1,17 @@ package core import ( + "context" "fmt" "time" "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" "git.huangwc.com/pig/pig-farm-controller/internal/domain/device" domain_notify "git.huangwc.com/pig/pig-farm-controller/internal/domain/notify" "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,45 +20,46 @@ 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 初始化所有基础设施层组件。 -func initInfrastructure(cfg *config.Config, logger *logs.Logger) (*Infrastructure, error) { - storage, err := initStorage(cfg.Database, logger) +func initInfrastructure(ctx context.Context, cfg *config.Config) (*Infrastructure, error) { + storage, err := initStorage(ctx, cfg.Database) if err != nil { return nil, err } - repos := initRepositories(storage.GetDB(), logger) + repos := initRepositories(ctx, storage.GetDB(ctx)) - lora, err := initLora(cfg, logger, repos) + lora, err := initLora(ctx, cfg, repos) if err != nil { return nil, err } - notifyService, err := initNotifyService(cfg.Notify, logger, repos.userRepo, repos.notificationRepo) + notifyService, err := initNotifyService(ctx, cfg.Notify, repos.userRepo, repos.notificationRepo) if err != nil { 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 } @@ -90,30 +90,31 @@ type Repositories struct { } // initRepositories 初始化所有的仓库。 -func initRepositories(db *gorm.DB, logger *logs.Logger) *Repositories { +func initRepositories(ctx context.Context, db *gorm.DB) *Repositories { + baseCtx := context.Background() return &Repositories{ - userRepo: repository.NewGormUserRepository(db), - deviceRepo: repository.NewGormDeviceRepository(db), - areaControllerRepo: repository.NewGormAreaControllerRepository(db), - deviceTemplateRepo: repository.NewGormDeviceTemplateRepository(db), - planRepo: repository.NewGormPlanRepository(db), - pendingTaskRepo: repository.NewGormPendingTaskRepository(db), - executionLogRepo: repository.NewGormExecutionLogRepository(db), - sensorDataRepo: repository.NewGormSensorDataRepository(db), - deviceCommandLogRepo: repository.NewGormDeviceCommandLogRepository(db), - pendingCollectionRepo: repository.NewGormPendingCollectionRepository(db), - userActionLogRepo: repository.NewGormUserActionLogRepository(db), - pigBatchRepo: repository.NewGormPigBatchRepository(db), - pigBatchLogRepo: repository.NewGormPigBatchLogRepository(db), - pigFarmRepo: repository.NewGormPigFarmRepository(db), - pigPenRepo: repository.NewGormPigPenRepository(db), - pigTransferLogRepo: repository.NewGormPigTransferLogRepository(db), - pigTradeRepo: repository.NewGormPigTradeRepository(db), - pigSickPigLogRepo: repository.NewGormPigSickLogRepository(db), - medicationLogRepo: repository.NewGormMedicationLogRepository(db), - rawMaterialRepo: repository.NewGormRawMaterialRepository(db), - notificationRepo: repository.NewGormNotificationRepository(db), - unitOfWork: repository.NewGormUnitOfWork(db, logger), + userRepo: repository.NewGormUserRepository(logs.AddCompName(baseCtx, "UserRepo"), db), + deviceRepo: repository.NewGormDeviceRepository(logs.AddCompName(baseCtx, "DeviceRepo"), db), + areaControllerRepo: repository.NewGormAreaControllerRepository(logs.AddCompName(baseCtx, "AreaControllerRepo"), db), + deviceTemplateRepo: repository.NewGormDeviceTemplateRepository(logs.AddCompName(baseCtx, "DeviceTemplateRepo"), db), + planRepo: repository.NewGormPlanRepository(logs.AddCompName(baseCtx, "PlanRepo"), db), + pendingTaskRepo: repository.NewGormPendingTaskRepository(logs.AddCompName(baseCtx, "PendingTaskRepo"), db), + executionLogRepo: repository.NewGormExecutionLogRepository(logs.AddCompName(baseCtx, "ExecutionLogRepo"), db), + sensorDataRepo: repository.NewGormSensorDataRepository(logs.AddCompName(baseCtx, "SensorDataRepo"), db), + deviceCommandLogRepo: repository.NewGormDeviceCommandLogRepository(logs.AddCompName(baseCtx, "DeviceCommandLogRepo"), db), + pendingCollectionRepo: repository.NewGormPendingCollectionRepository(logs.AddCompName(baseCtx, "PendingCollectionRepo"), db), + userActionLogRepo: repository.NewGormUserActionLogRepository(logs.AddCompName(baseCtx, "UserActionLogRepo"), db), + pigBatchRepo: repository.NewGormPigBatchRepository(logs.AddCompName(baseCtx, "PigBatchRepo"), db), + pigBatchLogRepo: repository.NewGormPigBatchLogRepository(logs.AddCompName(baseCtx, "PigBatchLogRepo"), db), + pigFarmRepo: repository.NewGormPigFarmRepository(logs.AddCompName(baseCtx, "PigFarmRepo"), db), + pigPenRepo: repository.NewGormPigPenRepository(logs.AddCompName(baseCtx, "PigPenRepo"), db), + pigTransferLogRepo: repository.NewGormPigTransferLogRepository(logs.AddCompName(baseCtx, "PigTransferLogRepo"), db), + pigTradeRepo: repository.NewGormPigTradeRepository(logs.AddCompName(baseCtx, "PigTradeRepo"), db), + pigSickPigLogRepo: repository.NewGormPigSickLogRepository(logs.AddCompName(baseCtx, "PigSickPigLogRepo"), db), + medicationLogRepo: repository.NewGormMedicationLogRepository(logs.AddCompName(baseCtx, "MedicationLogRepo"), db), + rawMaterialRepo: repository.NewGormRawMaterialRepository(logs.AddCompName(baseCtx, "RawMaterialRepo"), db), + notificationRepo: repository.NewGormNotificationRepository(logs.AddCompName(baseCtx, "NotificationRepo"), db), + unitOfWork: repository.NewGormUnitOfWork(logs.AddCompName(baseCtx, "UnitOfWork"), db), } } @@ -131,31 +132,40 @@ type DomainServices struct { } // initDomainServices 初始化所有的领域服务。 -func initDomainServices(cfg *config.Config, infra *Infrastructure, logger *logs.Logger) *DomainServices { +func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastructure) *DomainServices { + baseCtx := context.Background() + // 猪群管理相关 - pigPenTransferManager := pig.NewPigPenTransferManager(infra.repos.pigPenRepo, infra.repos.pigTransferLogRepo, infra.repos.pigBatchRepo) - pigTradeManager := pig.NewPigTradeManager(infra.repos.pigTradeRepo) - pigSickManager := pig.NewSickPigManager(infra.repos.pigSickPigLogRepo, infra.repos.medicationLogRepo) - pigBatchDomain := pig.NewPigBatchService(infra.repos.pigBatchRepo, infra.repos.pigBatchLogRepo, infra.repos.unitOfWork, - pigPenTransferManager, pigTradeManager, pigSickManager) + 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, + ) // 通用设备服务 generalDeviceService := device.NewGeneralDeviceService( + logs.AddCompName(baseCtx, "GeneralDeviceService"), infra.repos.deviceRepo, infra.repos.deviceCommandLogRepo, infra.repos.pendingCollectionRepo, - logger, infra.lora.comm, ) // 任务工厂 - taskFactory := task.NewTaskFactory(logger, infra.repos.sensorDataRepo, infra.repos.deviceRepo, generalDeviceService) + taskFactory := task.NewTaskFactory(logs.AddCompName(baseCtx, "TaskFactory"), infra.repos.sensorDataRepo, infra.repos.deviceRepo, generalDeviceService) // 计划任务管理器 - analysisPlanTaskManager := plan.NewAnalysisPlanTaskManager(infra.repos.planRepo, infra.repos.pendingTaskRepo, infra.repos.executionLogRepo, logger) + analysisPlanTaskManager := plan.NewAnalysisPlanTaskManager(logs.AddCompName(baseCtx, "AnalysisPlanTaskManager"), infra.repos.planRepo, infra.repos.pendingTaskRepo, infra.repos.executionLogRepo) // 任务执行器 planExecutionManager := plan.NewPlanExecutionManager( + logs.AddCompName(baseCtx, "PlanExecutionManager"), infra.repos.pendingTaskRepo, infra.repos.executionLogRepo, infra.repos.deviceRepo, @@ -163,7 +173,6 @@ func initDomainServices(cfg *config.Config, infra *Infrastructure, logger *logs. infra.repos.planRepo, analysisPlanTaskManager, taskFactory, - logger, generalDeviceService, time.Duration(cfg.Task.Interval)*time.Second, cfg.Task.NumWorkers, @@ -171,13 +180,14 @@ func initDomainServices(cfg *config.Config, infra *Infrastructure, logger *logs. // 计划管理器 planService := plan.NewPlanService( + logs.AddCompName(baseCtx, "PlanService"), planExecutionManager, analysisPlanTaskManager, infra.repos.planRepo, infra.repos.deviceRepo, infra.repos.unitOfWork, taskFactory, - logger) + ) return &DomainServices{ pigPenTransferManager: pigPenTransferManager, @@ -200,14 +210,16 @@ type AppServices struct { deviceService service.DeviceService planService service.PlanService userService service.UserService - auditService audit.Service + auditService service.AuditService } // initAppServices 初始化所有的应用服务。 -func initAppServices(infra *Infrastructure, domainServices *DomainServices, logger *logs.Logger) *AppServices { - pigFarmService := service.NewPigFarmService(infra.repos.pigFarmRepo, infra.repos.pigPenRepo, infra.repos.pigBatchRepo, domainServices.pigBatchDomain, infra.repos.unitOfWork, logger) - pigBatchService := service.NewPigBatchService(domainServices.pigBatchDomain, logger) +func initAppServices(ctx context.Context, infra *Infrastructure, domainServices *DomainServices) *AppServices { + baseCtx := context.Background() + pigFarmService := service.NewPigFarmService(logs.AddCompName(baseCtx, "PigFarmService"), infra.repos.pigFarmRepo, infra.repos.pigPenRepo, infra.repos.pigBatchRepo, domainServices.pigBatchDomain, infra.repos.unitOfWork) + pigBatchService := service.NewPigBatchService(logs.AddCompName(baseCtx, "PigBatchService"), domainServices.pigBatchDomain) monitorService := service.NewMonitorService( + logs.AddCompName(baseCtx, "MonitorService"), infra.repos.sensorDataRepo, infra.repos.deviceCommandLogRepo, infra.repos.executionLogRepo, @@ -224,14 +236,15 @@ func initAppServices(infra *Infrastructure, domainServices *DomainServices, logg infra.repos.notificationRepo, ) deviceService := service.NewDeviceService( + logs.AddCompName(baseCtx, "DeviceService"), infra.repos.deviceRepo, infra.repos.areaControllerRepo, infra.repos.deviceTemplateRepo, domainServices.generalDeviceService, ) - auditService := audit.NewService(infra.repos.userActionLogRepo, logger) - planService := service.NewPlanService(logger, domainServices.planService) - userService := service.NewUserService(infra.repos.userRepo, infra.tokenService, infra.notifyService, logger) + auditService := service.NewAuditService(logs.AddCompName(baseCtx, "AuditService"), infra.repos.userActionLogRepo) + planService := service.NewPlanService(logs.AddCompName(baseCtx, "AppPlanService"), domainServices.planService) + userService := service.NewUserService(logs.AddCompName(baseCtx, "UserService"), infra.repos.userRepo, infra.tokenGenerator, infra.notifyService) return &AppServices{ pigFarmService: pigFarmService, @@ -253,23 +266,25 @@ type LoraComponents struct { // initLora 根据配置初始化 LoRa 相关组件。 func initLora( + ctx context.Context, cfg *config.Config, - logger *logs.Logger, repos *Repositories, ) (*LoraComponents, error) { var listenHandler webhook.ListenHandler var comm transport.Communicator var loraListener transport.Listener + baseCtx := context.Background() + logger := logs.GetLogger(ctx) if cfg.Lora.Mode == config.LoraMode_LoRaWAN { logger.Info("当前运行模式: lora_wan。初始化 ChirpStack 监听器和传输层。") - listenHandler = webhook.NewChirpStackListener(logger, repos.sensorDataRepo, repos.deviceRepo, repos.areaControllerRepo, repos.deviceCommandLogRepo, repos.pendingCollectionRepo) - comm = lora.NewChirpStackTransport(cfg.ChirpStack, logger) - loraListener = lora.NewPlaceholderTransport(logger) + listenHandler = webhook.NewChirpStackListener(logs.AddCompName(baseCtx, "ChirpStackListener"), repos.sensorDataRepo, repos.deviceRepo, repos.areaControllerRepo, repos.deviceCommandLogRepo, repos.pendingCollectionRepo) + comm = lora.NewChirpStackTransport(logs.AddCompName(baseCtx, "ChirpStackTransport"), cfg.ChirpStack) + loraListener = lora.NewPlaceholderTransport(logs.AddCompName(baseCtx, "PlaceholderTransport")) } else { logger.Info("当前运行模式: lora_mesh。初始化 LoRa Mesh 传输层和占位符监听器。") - listenHandler = webhook.NewPlaceholderListener(logger) - tp, err := lora.NewLoRaMeshUartPassthroughTransport(cfg.LoraMesh, logger, repos.areaControllerRepo, repos.pendingCollectionRepo, repos.deviceRepo, repos.sensorDataRepo) + listenHandler = webhook.NewPlaceholderListener(logs.AddCompName(baseCtx, "PlaceholderListener")) + tp, err := lora.NewLoRaMeshUartPassthroughTransport(logs.AddCompName(baseCtx, "LoRaMeshTransport"), cfg.LoraMesh, repos.areaControllerRepo, repos.pendingCollectionRepo, repos.deviceRepo, repos.sensorDataRepo) if err != nil { return nil, fmt.Errorf("无法初始化 LoRa Mesh 模块: %w", err) } @@ -287,21 +302,24 @@ func initLora( // initNotifyService 根据配置初始化并返回一个通知领域服务。 // 它确保至少有一个 LogNotifier 总是可用,并根据配置启用其他通知器。 func initNotifyService( + ctx context.Context, cfg config.NotifyConfig, - log *logs.Logger, userRepo repository.UserRepository, notificationRepo repository.NotificationRepository, ) (domain_notify.Service, error) { var availableNotifiers []notify.Notifier + logger := logs.GetLogger(ctx) + baseCtx := context.Background() // 1. 总是创建 LogNotifier 作为所有告警的最终记录渠道 - logNotifier := notify.NewLogNotifier(log) + logNotifier := notify.NewLogNotifier(logs.AddCompName(baseCtx, "LogNotifier")) availableNotifiers = append(availableNotifiers, logNotifier) - log.Info("Log通知器已启用 (作为所有告警的最终记录渠道)") + logger.Info("Log通知器已启用 (作为所有告警的最终记录渠道)") // 2. 根据配置,按需创建并收集所有启用的其他 Notifier 实例 if cfg.SMTP.Enabled { smtpNotifier := notify.NewSMTPNotifier( + logs.AddCompName(baseCtx, "SMTPNotifier"), cfg.SMTP.Host, cfg.SMTP.Port, cfg.SMTP.Username, @@ -309,26 +327,28 @@ func initNotifyService( cfg.SMTP.Sender, ) availableNotifiers = append(availableNotifiers, smtpNotifier) - log.Info("SMTP通知器已启用") + logger.Info("SMTP通知器已启用") } if cfg.WeChat.Enabled { wechatNotifier := notify.NewWechatNotifier( + logs.AddCompName(baseCtx, "WechatNotifier"), cfg.WeChat.CorpID, cfg.WeChat.AgentID, cfg.WeChat.Secret, ) availableNotifiers = append(availableNotifiers, wechatNotifier) - log.Info("企业微信通知器已启用") + logger.Info("企业微信通知器已启用") } if cfg.Lark.Enabled { larkNotifier := notify.NewLarkNotifier( + logs.AddCompName(baseCtx, "LarkNotifier"), cfg.Lark.AppID, cfg.Lark.AppSecret, ) availableNotifiers = append(availableNotifiers, larkNotifier) - log.Info("飞书通知器已启用") + logger.Info("飞书通知器已启用") } // 3. 动态确定首选通知器 @@ -346,12 +366,12 @@ func initNotifyService( // 如果用户指定的主渠道未启用或未指定,则自动选择第一个可用的 (这将是 LogNotifier,如果其他都未启用) if primaryNotifier == nil { primaryNotifier = availableNotifiers[0] // 确保总能找到一个,因为 LogNotifier 总是存在的 - log.Warnf("配置的首选渠道 '%s' 未启用或未指定,已自动降级使用 '%s' 作为首选渠道。", cfg.Primary, primaryNotifier.Type()) + logger.Warnf("配置的首选渠道 '%s' 未启用或未指定,已自动降级使用 '%s' 作为首选渠道。", cfg.Primary, primaryNotifier.Type()) } // 4. 使用创建的 Notifier 列表和 notificationRepo 来组装领域服务 notifyService, err := domain_notify.NewFailoverService( - log, + logs.AddCompName(baseCtx, "FailoverNotifyService"), userRepo, availableNotifiers, primaryNotifier.Type(), @@ -362,24 +382,24 @@ func initNotifyService( return nil, fmt.Errorf("创建故障转移通知服务失败: %w", err) } - log.Infof("通知服务初始化成功,首选渠道: %s, 故障阈值: %d", primaryNotifier.Type(), cfg.FailureThreshold) + logger.Infof("通知服务初始化成功,首选渠道: %s, 故障阈值: %d", primaryNotifier.Type(), cfg.FailureThreshold) return notifyService, nil } // initStorage 封装了数据库的初始化、连接和迁移逻辑。 -func initStorage(cfg config.DatabaseConfig, logger *logs.Logger) (database.Storage, error) { +func initStorage(ctx context.Context, cfg config.DatabaseConfig) (database.Storage, error) { // 创建存储实例 - storage := database.NewStorage(cfg, logger) - if err := storage.Connect(); err != nil { + storage := database.NewStorage(logs.AddCompName(context.Background(), "Storage"), cfg) + if err := storage.Connect(ctx); err != nil { // 错误已在 Connect 内部被记录,这里只需包装并返回 return nil, fmt.Errorf("数据库连接失败: %w", err) } // 执行数据库迁移 - if err := storage.Migrate(models.GetAllModels()...); err != nil { + if err := storage.Migrate(ctx, models.GetAllModels()...); err != nil { return nil, fmt.Errorf("数据库迁移失败: %w", err) } - logger.Info("数据库初始化完成。") + logs.GetLogger(ctx).Info("数据库初始化完成。") return storage, nil } diff --git a/internal/core/data_initializer.go b/internal/core/data_initializer.go index dba1cbc..a827c24 100644 --- a/internal/core/data_initializer.go +++ b/internal/core/data_initializer.go @@ -1,8 +1,10 @@ package core import ( + "context" "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" ) @@ -14,19 +16,20 @@ const ( // initializeState 在应用启动时准备其初始数据状态。 // 这包括清理任何因上次异常关闭而留下的悬空任务或请求。 -func (app *Application) initializeState() error { +func (app *Application) initializeState(ctx context.Context) error { + appCtx, logger := logs.Trace(ctx, app.Ctx, "InitializeState") // 初始化预定义系统计划 (致命错误) - if err := app.initializeSystemPlans(); err != nil { + if err := app.initializeSystemPlans(ctx); err != nil { return fmt.Errorf("初始化预定义系统计划失败: %w", err) } // 清理待采集任务 (非致命错误) - if err := app.initializePendingCollections(); err != nil { - app.Logger.Errorw("清理待采集任务时发生非致命错误", "error", err) + if err := app.initializePendingCollections(appCtx); err != nil { + logger.Errorw("清理待采集任务时发生非致命错误", "error", err) } // 初始化待执行任务列表 (致命错误) - if err := app.initializePendingTasks(); err != nil { + if err := app.initializePendingTasks(appCtx); err != nil { return fmt.Errorf("初始化待执行任务列表失败: %w", err) } @@ -34,14 +37,15 @@ func (app *Application) initializeState() error { } // initializeSystemPlans 确保预定义的系统计划在数据库中存在并保持最新。 -func (app *Application) initializeSystemPlans() error { - app.Logger.Info("开始检查并更新预定义的系统计划...") +func (app *Application) initializeSystemPlans(ctx context.Context) error { + appCtx, logger := logs.Trace(ctx, app.Ctx, "InitializeSystemPlans") + logger.Info("开始检查并更新预定义的系统计划...") // 动态构建预定义计划列表 predefinedSystemPlans := app.getPredefinedSystemPlans() // 1. 获取所有已存在的系统计划 - existingPlans, _, err := app.Infra.repos.planRepo.ListPlans(repository.ListPlansOptions{ + existingPlans, _, err := app.Infra.repos.planRepo.ListPlans(appCtx, repository.ListPlansOptions{ PlanType: repository.PlanTypeFilterSystem, }, 1, 99999) // 使用一个较大的 pageSize 来获取所有系统计划 if err != nil { @@ -60,29 +64,29 @@ func (app *Application) initializeSystemPlans() error { if foundExistingPlan, ok := existingPlanMap[predefinedPlan.Name]; ok { // 如果计划存在,则进行无差别更新 - app.Logger.Infof("预定义计划 '%s' 已存在,正在进行无差别更新...", predefinedPlan.Name) + logger.Infof("预定义计划 '%s' 已存在,正在进行无差别更新...", predefinedPlan.Name) // 将数据库中已存在的计划的ID和运行时状态字段赋值给预定义计划 predefinedPlan.ID = foundExistingPlan.ID predefinedPlan.ExecuteCount = foundExistingPlan.ExecuteCount - if err := app.Infra.repos.planRepo.UpdatePlan(predefinedPlan); err != nil { + if err := app.Infra.repos.planRepo.UpdatePlan(appCtx, predefinedPlan); err != nil { return fmt.Errorf("更新预定义计划 '%s' 失败: %w", predefinedPlan.Name, err) } else { - app.Logger.Infof("成功更新预定义计划 '%s'。", predefinedPlan.Name) + logger.Infof("成功更新预定义计划 '%s'。", predefinedPlan.Name) } } else { // 如果计划不存在, 则创建 - app.Logger.Infof("预定义计划 '%s' 不存在,正在创建...", predefinedPlan.Name) - if err := app.Infra.repos.planRepo.CreatePlan(predefinedPlan); err != nil { + logger.Infof("预定义计划 '%s' 不存在,正在创建...", predefinedPlan.Name) + if err := app.Infra.repos.planRepo.CreatePlan(appCtx, predefinedPlan); err != nil { return fmt.Errorf("创建预定义计划 '%s' 失败: %w", predefinedPlan.Name, err) } else { - app.Logger.Infof("成功创建预定义计划 '%s'。", predefinedPlan.Name) + logger.Infof("成功创建预定义计划 '%s'。", predefinedPlan.Name) } } } - app.Logger.Info("预定义系统计划检查完成。") + logger.Info("预定义系统计划检查完成。") return nil } @@ -119,25 +123,26 @@ func (app *Application) getPredefinedSystemPlans() []models.Plan { // initializePendingCollections 在应用启动时处理所有未完成的采集请求。 // 我们的策略是:任何在程序重启前仍处于“待处理”状态的请求,都应被视为已失败。 // 这保证了系统在每次启动时都处于一个干净、确定的状态。 -func (app *Application) initializePendingCollections() error { - app.Logger.Info("开始清理所有未完成的采集请求...") +func (app *Application) initializePendingCollections(ctx context.Context) error { + appCtx, logger := logs.Trace(ctx, app.Ctx, "InitializePendingCollections") + logger.Info("开始清理所有未完成的采集请求...") // 直接将所有 'pending' 状态的请求更新为 'timed_out'。 - count, err := app.Infra.repos.pendingCollectionRepo.MarkAllPendingAsTimedOut() + count, err := app.Infra.repos.pendingCollectionRepo.MarkAllPendingAsTimedOut(appCtx) if err != nil { return fmt.Errorf("清理未完成的采集请求失败: %v", err) } else if count > 0 { - app.Logger.Infof("成功将 %d 个未完成的采集请求标记为超时。", count) + logger.Infof("成功将 %d 个未完成的采集请求标记为超时。", count) } else { - app.Logger.Info("没有需要清理的采集请求。") + logger.Info("没有需要清理的采集请求。") } return nil } // initializePendingTasks 在应用启动时清理并刷新待执行任务列表。 -func (app *Application) initializePendingTasks() error { - logger := app.Logger +func (app *Application) initializePendingTasks(ctx context.Context) error { + appCtx, logger := logs.Trace(ctx, app.Ctx, "InitializePendingTasks") planRepo := app.Infra.repos.planRepo pendingTaskRepo := app.Infra.repos.pendingTaskRepo executionLogRepo := app.Infra.repos.executionLogRepo @@ -147,7 +152,7 @@ func (app *Application) initializePendingTasks() error { // 阶段一:修正因崩溃导致状态不一致的固定次数计划 logger.Info("阶段一:开始修正因崩溃导致状态不一致的固定次数计划...") - plansToCorrect, err := planRepo.FindPlansWithPendingTasks() + plansToCorrect, err := planRepo.FindPlansWithPendingTasks(appCtx) if err != nil { return fmt.Errorf("查找需要修正的计划失败: %w", err) } @@ -167,7 +172,7 @@ func (app *Application) initializePendingTasks() error { } // 保存更新后的计划 - if err := planRepo.UpdatePlan(plan); err != nil { + if err := planRepo.UpdatePlan(appCtx, plan); err != nil { logger.Errorf("修正计划 #%d 状态失败: %v", plan.ID, err) // 这是一个非阻塞性错误,继续处理其他计划 } @@ -179,7 +184,7 @@ func (app *Application) initializePendingTasks() error { // --- 新增逻辑:处理因崩溃导致状态不一致的计划主表状态 --- // 1. 查找所有未完成的计划执行日志 (状态为 Started 或 Waiting) - incompletePlanLogs, err := executionLogRepo.FindIncompletePlanExecutionLogs() + incompletePlanLogs, err := executionLogRepo.FindIncompletePlanExecutionLogs(appCtx) if err != nil { return fmt.Errorf("查找未完成的计划执行日志失败: %w", err) } @@ -193,7 +198,7 @@ func (app *Application) initializePendingTasks() error { // 3. 对于每个受影响的 PlanID,重置其 execute_count 并将其状态设置为 Failed, 系统计划不受此影响 for planID := range affectedPlanIDs { // 首先,获取计划的详细信息以判断其类型 - plan, err := planRepo.GetBasicPlanByID(planID) + plan, err := planRepo.GetBasicPlanByID(appCtx, planID) if err != nil { logger.Errorf("在尝试修正计划状态时,获取计划 #%d 的基本信息失败: %v", planID, err) continue // 获取失败,跳过此计划 @@ -208,7 +213,7 @@ func (app *Application) initializePendingTasks() error { // 对于非系统计划,执行原有的失败标记逻辑 logger.Warnf("检测到计划 #%d 在应用崩溃前处于未完成状态,将重置其计数并标记为失败。", planID) // 使用 UpdatePlanStateAfterExecution 来更新主表状态,避免影响关联数据 - if err := planRepo.UpdatePlanStateAfterExecution(planID, 0, models.PlanStatusFailed); err != nil { + if err := planRepo.UpdatePlanStateAfterExecution(appCtx, planID, 0, models.PlanStatusFailed); err != nil { logger.Errorf("重置计划 #%d 计数并标记为失败时出错: %v", planID, err) // 这是一个非阻塞性错误,继续处理其他计划 } @@ -216,26 +221,26 @@ func (app *Application) initializePendingTasks() error { logger.Info("阶段二:计划主表状态修正完成。") // 直接调用新的方法来更新计划执行日志状态为失败 - if err := executionLogRepo.FailAllIncompletePlanExecutionLogs(); err != nil { + if err := executionLogRepo.FailAllIncompletePlanExecutionLogs(appCtx); err != nil { logger.Errorf("更新所有未完成计划执行日志状态为失败失败: %v", err) // 这是一个非阻塞性错误,继续执行 } // 直接调用新的方法来更新任务执行日志状态为取消 - if err := executionLogRepo.CancelAllIncompleteTaskExecutionLogs(); err != nil { + if err := executionLogRepo.CancelAllIncompleteTaskExecutionLogs(appCtx); err != nil { logger.Errorf("更新所有未完成任务执行日志状态为取消失败: %v", err) // 这是一个非阻塞性错误,继续执行 } // 清空待执行列表 - if err := pendingTaskRepo.ClearAllPendingTasks(); err != nil { + if err := pendingTaskRepo.ClearAllPendingTasks(appCtx); err != nil { return fmt.Errorf("清空待执行任务列表失败: %w", err) } logger.Info("阶段二:待执行任务和相关日志清理完成。") // 阶段三:初始刷新 logger.Info("阶段三:开始刷新待执行列表...") - if err := planService.RefreshPlanTriggers(); err != nil { + if err := planService.RefreshPlanTriggers(appCtx); err != nil { return fmt.Errorf("刷新待执行任务列表失败: %w", err) } logger.Info("阶段三:待执行任务列表初始化完成。") diff --git a/internal/domain/audit/service.go b/internal/domain/audit/service.go deleted file mode 100644 index 6f72396..0000000 --- a/internal/domain/audit/service.go +++ /dev/null @@ -1,69 +0,0 @@ -// Package audit 提供了用户操作审计相关的功能 -package audit - -import ( - "time" - - "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" - "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" - "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" - // 移除对 "github.com/gin-gonic/gin" 的直接依赖 -) - -// RequestContext 封装了审计日志所需的请求上下文信息 -type RequestContext struct { - ClientIP string - HTTPPath string - HTTPMethod string -} - -// Service 定义了审计服务的接口 -type Service interface { - LogAction(user *models.User, reqCtx RequestContext, actionType, description string, targetResource interface{}, status models.AuditStatus, resultDetails string) -} - -// service 是 Service 接口的实现 -type service struct { - userActionLogRepository repository.UserActionLogRepository - logger *logs.Logger -} - -// NewService 创建一个新的审计服务实例 -func NewService(repo repository.UserActionLogRepository, logger *logs.Logger) Service { - return &service{userActionLogRepository: repo, logger: logger} -} - -// LogAction 记录一个用户操作。它在一个新的 goroutine 中异步执行,以避免阻塞主请求。 -func (s *service) LogAction(user *models.User, reqCtx RequestContext, actionType, description string, targetResource interface{}, status models.AuditStatus, resultDetails string) { - // 不再从 context 中获取用户信息,直接使用传入的 user 对象 - if user == nil { - s.logger.Warnw("无法记录审计日志:传入的用户对象为 nil") - return - } - - log := &models.UserActionLog{ - Time: time.Now(), - UserID: user.ID, - Username: user.Username, // 用户名快照 - SourceIP: reqCtx.ClientIP, - ActionType: actionType, - Description: description, - Status: status, - HTTPPath: reqCtx.HTTPPath, - HTTPMethod: reqCtx.HTTPMethod, - ResultDetails: resultDetails, - } - - // 使用模型提供的方法来设置 TargetResource - if err := log.SetTargetResource(targetResource); err != nil { - s.logger.Errorw("无法记录审计日志:序列化 targetResource 失败", "error", err) - // 即使序列化失败,我们可能仍然希望记录操作本身,所以不在此处 return - } - - // 异步写入数据库,不阻塞当前请求 - go func() { - if err := s.userActionLogRepository.Create(log); err != nil { - s.logger.Errorw("异步保存审计日志失败", "error", err) - } - }() -} 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/database/postgres.go b/internal/infra/database/postgres.go index 1a40804..7b29ebd 100644 --- a/internal/infra/database/postgres.go +++ b/internal/infra/database/postgres.go @@ -4,12 +4,13 @@ package database import ( + "context" "fmt" "time" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" - "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "gorm.io/driver/postgres" "gorm.io/gorm" ) @@ -17,35 +18,36 @@ import ( // PostgresStorage 代表基于PostgreSQL的存储实现 // 使用GORM作为ORM库 type PostgresStorage struct { + ctx context.Context db *gorm.DB isTimescaleDB bool connectionString string maxOpenConns int maxIdleConns int connMaxLifetime int - logger *logs.Logger // 依赖注入的 logger } // NewPostgresStorage 创建并返回一个新的PostgreSQL存储实例 // 它接收一个 logger 实例,而不是自己创建 -func NewPostgresStorage(connectionString string, isTimescaleDB bool, maxOpenConns, maxIdleConns, connMaxLifetime int, logger *logs.Logger) *PostgresStorage { +func NewPostgresStorage(ctx context.Context, connectionString string, isTimescaleDB bool, maxOpenConns, maxIdleConns, connMaxLifetime int) *PostgresStorage { return &PostgresStorage{ + ctx: ctx, connectionString: connectionString, isTimescaleDB: isTimescaleDB, maxOpenConns: maxOpenConns, maxIdleConns: maxIdleConns, connMaxLifetime: connMaxLifetime, - logger: logger, // 注入 logger } } // Connect 建立与PostgreSQL数据库的连接 // 使用GORM建立数据库连接,并使用自定义的 logger 接管 GORM 日志 -func (ps *PostgresStorage) Connect() error { - ps.logger.Info("正在连接PostgreSQL数据库") +func (ps *PostgresStorage) Connect(ctx context.Context) error { + storageCtx, logger := logs.Trace(ctx, ps.ctx, "Connect") + logger.Info("正在连接PostgreSQL数据库") // 创建 GORM 的 logger 适配器 - gormLogger := logs.NewGormLogger(ps.logger) + gormLogger := logs.NewGormLogger(logs.GetLogger(logs.AddCompName(context.Background(), "GORM"))) var err error // 在 gorm.Open 时传入我们自定义的 logger @@ -53,19 +55,19 @@ func (ps *PostgresStorage) Connect() error { Logger: gormLogger, }) if err != nil { - ps.logger.Errorw("数据库连接失败", "error", err) + logger.Errorw("数据库连接失败", "error", err) return fmt.Errorf("数据库连接失败: %w", err) // 使用 %w 进行错误包装 } // 测试连接 - sqlDB, err := ps.db.DB() + sqlDB, err := ps.db.WithContext(storageCtx).DB() if err != nil { - ps.logger.Errorw("获取数据库实例失败", "error", err) + logger.Errorw("获取数据库实例失败", "error", err) return fmt.Errorf("获取数据库实例失败: %w", err) } if err = sqlDB.Ping(); err != nil { - ps.logger.Errorw("数据库连接测试失败", "error", err) + logger.Errorw("数据库连接测试失败", "error", err) return fmt.Errorf("数据库连接测试失败: %w", err) } @@ -77,59 +79,62 @@ func (ps *PostgresStorage) Connect() error { // gorm会根据字段名自动创建外键约束, 但触发器Task的PlanID是不存在的, 所以需要关闭, 这个关闭对 ps.db.DisableForeignKeyConstraintWhenMigrating = true - ps.logger.Info("PostgreSQL数据库连接成功") + logger.Info("PostgreSQL数据库连接成功") return nil } // Disconnect 断开与PostgreSQL数据库的连接 // 安全地关闭所有数据库连接 -func (ps *PostgresStorage) Disconnect() error { +func (ps *PostgresStorage) Disconnect(ctx context.Context) error { + storageCtx, logger := logs.Trace(ctx, ps.ctx, "Disconnect") if ps.db != nil { - ps.logger.Info("正在断开PostgreSQL数据库连接") + logger.Info("正在断开PostgreSQL数据库连接") - sqlDB, err := ps.db.DB() + sqlDB, err := ps.db.WithContext(storageCtx).DB() if err != nil { - ps.logger.Errorw("获取数据库实例失败", "error", err) + logger.Errorw("获取数据库实例失败", "error", err) return fmt.Errorf("获取数据库实例失败: %w", err) } if err := sqlDB.Close(); err != nil { - ps.logger.Errorw("关闭数据库连接失败", "error", err) + logger.Errorw("关闭数据库连接失败", "error", err) return fmt.Errorf("关闭数据库连接失败: %w", err) } - ps.logger.Info("PostgreSQL数据库连接已断开") + logger.Info("PostgreSQL数据库连接已断开") } return nil } // GetDB 获取GORM数据库实例 // 用于执行具体的数据库操作 -func (ps *PostgresStorage) GetDB() *gorm.DB { - return ps.db +func (ps *PostgresStorage) GetDB(ctx context.Context) *gorm.DB { + storageCtx := logs.AddFuncName(ctx, ps.ctx, "GetDB") + return ps.db.WithContext(storageCtx) } // Migrate 执行数据库迁移 -func (ps *PostgresStorage) Migrate(models ...interface{}) error { +func (ps *PostgresStorage) Migrate(ctx context.Context, models ...interface{}) error { + storageCtx, logger := logs.Trace(ctx, ps.ctx, "Migrate") if len(models) == 0 { - ps.logger.Info("没有需要迁移的数据库模型,跳过迁移步骤") + logger.Info("没有需要迁移的数据库模型,跳过迁移步骤") return nil } - ps.logger.Info("正在自动迁移数据库表结构") - if err := ps.db.AutoMigrate(models...); err != nil { - ps.logger.Errorw("数据库表结构迁移失败", "error", err) + logger.Info("正在自动迁移数据库表结构") + if err := ps.db.WithContext(storageCtx).AutoMigrate(models...); err != nil { + logger.Errorw("数据库表结构迁移失败", "error", err) return fmt.Errorf("数据库表结构迁移失败: %w", err) } - ps.logger.Info("数据库表结构迁移完成") + logger.Info("数据库表结构迁移完成") // -- 处理gorm做不到的初始化逻辑 -- - if err := ps.creatingIndex(); err != nil { + if err := ps.creatingIndex(storageCtx); err != nil { return err } // 如果是 TimescaleDB, 则将部分表转换为 hypertable if ps.isTimescaleDB { - ps.logger.Info("检测到 TimescaleDB, 准备进行超表转换和压缩策略配置") - if err := ps.setupTimescaleDB(); err != nil { + logger.Info("检测到 TimescaleDB, 准备进行超表转换和压缩策略配置") + if err := ps.setupTimescaleDB(storageCtx); err != nil { return err } } @@ -137,18 +142,20 @@ func (ps *PostgresStorage) Migrate(models ...interface{}) error { } // setupTimescaleDB 统一处理所有 TimescaleDB 相关的设置 -func (ps *PostgresStorage) setupTimescaleDB() error { - if err := ps.creatingHyperTable(); err != nil { +func (ps *PostgresStorage) setupTimescaleDB(ctx context.Context) error { + storageCtx := logs.AddFuncName(ctx, ps.ctx, "setupTimescaleDB") + if err := ps.creatingHyperTable(storageCtx); err != nil { return err } - if err := ps.applyCompressionPolicies(); err != nil { + if err := ps.applyCompressionPolicies(storageCtx); err != nil { return err } return nil } // creatingHyperTable 用于在数据库是 TimescaleDB 时创建超表 -func (ps *PostgresStorage) creatingHyperTable() error { +func (ps *PostgresStorage) creatingHyperTable(ctx context.Context) error { + storageCtx, logger := logs.Trace(ctx, ps.ctx, "creatingHyperTable") // 定义一个辅助结构体来管理超表转换 tablesToConvert := []struct { model interface{ TableName() string } @@ -177,20 +184,21 @@ func (ps *PostgresStorage) creatingHyperTable() error { for _, table := range tablesToConvert { tableName := table.model.TableName() chunkInterval := "1 days" // 统一设置为1天 - ps.logger.Debugw("准备将表转换为超表", "table", tableName, "chunk_interval", chunkInterval) + logger.Debugw("准备将表转换为超表", "table", tableName, "chunk_interval", chunkInterval) sql := fmt.Sprintf("SELECT create_hypertable('%s', '%s', chunk_time_interval => INTERVAL '%s', if_not_exists => TRUE);", tableName, table.timeColumn, chunkInterval) - if err := ps.db.Exec(sql).Error; err != nil { - ps.logger.Errorw("转换为超表失败", "table", tableName, "error", err) + if err := ps.db.WithContext(storageCtx).Exec(sql).Error; err != nil { + logger.Errorw("转换为超表失败", "table", tableName, "error", err) return fmt.Errorf("将 %s 转换为超表失败: %w", tableName, err) } - ps.logger.Debugw("成功将表转换为超表 (或已转换)", "table", tableName) + logger.Debugw("成功将表转换为超表 (或已转换)", "table", tableName) } return nil } // applyCompressionPolicies 为超表配置自动压缩策略 -func (ps *PostgresStorage) applyCompressionPolicies() error { +func (ps *PostgresStorage) applyCompressionPolicies(ctx context.Context) error { + storageCtx, logger := logs.Trace(ctx, ps.ctx, "applyCompressionPolicies") policies := []struct { model interface{ TableName() string } segmentColumn string @@ -220,50 +228,51 @@ func (ps *PostgresStorage) applyCompressionPolicies() error { compressAfter := "3 days" // 统一设置为2天后(即进入第3天)开始压缩 // 1. 开启表的压缩设置,并指定分段列 - ps.logger.Debugw("为表启用压缩设置", "table", tableName, "segment_by", policy.segmentColumn) + logger.Debugw("为表启用压缩设置", "table", tableName, "segment_by", policy.segmentColumn) // 使用 + 而非Sprintf以规避goland静态检查报错 alterSQL := "ALTER TABLE" + " " + tableName + " SET (timescaledb.compress, timescaledb.compress_segmentby = '" + policy.segmentColumn + "');" - if err := ps.db.Exec(alterSQL).Error; err != nil { + if err := ps.db.WithContext(storageCtx).Exec(alterSQL).Error; err != nil { // 忽略错误,因为这个设置可能是不可变的,重复执行会报错 - ps.logger.Warnw("启用压缩设置时遇到问题 (可能已设置,可忽略)", "table", tableName, "error", err) + logger.Warnw("启用压缩设置时遇到问题 (可能已设置,可忽略)", "table", tableName, "error", err) } - ps.logger.Debugw("成功为表启用压缩设置 (或已启用)", "table", tableName) + logger.Debugw("成功为表启用压缩设置 (或已启用)", "table", tableName) // 2. 添加压缩策略 - ps.logger.Debugw("为表添加压缩策略", "table", tableName, "compress_after", compressAfter) + logger.Debugw("为表添加压缩策略", "table", tableName, "compress_after", compressAfter) policySQL := fmt.Sprintf("SELECT add_compression_policy('%s', INTERVAL '%s', if_not_exists => TRUE);", tableName, compressAfter) - if err := ps.db.Exec(policySQL).Error; err != nil { - ps.logger.Errorw("添加压缩策略失败", "table", tableName, "error", err) + if err := ps.db.WithContext(storageCtx).Exec(policySQL).Error; err != nil { + logger.Errorw("添加压缩策略失败", "table", tableName, "error", err) return fmt.Errorf("为 %s 添加压缩策略失败: %w", tableName, err) } - ps.logger.Debugw("成功为表添加压缩策略 (或已存在)", "table", tableName) + logger.Debugw("成功为表添加压缩策略 (或已存在)", "table", tableName) } return nil } // creatingIndex 用于创建gorm无法处理的索引, 如gin索引 -func (ps *PostgresStorage) creatingIndex() error { +func (ps *PostgresStorage) creatingIndex(ctx context.Context) error { + storageCtx, logger := logs.Trace(ctx, ps.ctx, "creatingIndex") // 使用 IF NOT EXISTS 保证幂等性 // 如果索引已存在,此命令不会报错 // 为 sensor_data 表的 data 字段创建 GIN 索引 - ps.logger.Debug("正在为 sensor_data 表的 data 字段创建 GIN 索引") + logger.Debug("正在为 sensor_data 表的 data 字段创建 GIN 索引") ginSensorDataIndexSQL := "CREATE INDEX IF NOT EXISTS idx_sensor_data_data_gin ON sensor_data USING GIN (data);" - if err := ps.db.Exec(ginSensorDataIndexSQL).Error; err != nil { - ps.logger.Errorw("为 sensor_data 的 data 字段创建 GIN 索引失败", "error", err) + if err := ps.db.WithContext(storageCtx).Exec(ginSensorDataIndexSQL).Error; err != nil { + logger.Errorw("为 sensor_data 的 data 字段创建 GIN 索引失败", "error", err) return fmt.Errorf("为 sensor_data 的 data 字段创建 GIN 索引失败: %w", err) } - ps.logger.Debug("成功为 sensor_data 的 data 字段创建 GIN 索引 (或已存在)") + logger.Debug("成功为 sensor_data 的 data 字段创建 GIN 索引 (或已存在)") // 为 tasks.parameters 创建 GIN 索引 - ps.logger.Debug("正在为 tasks 表的 parameters 字段创建 GIN 索引") + logger.Debug("正在为 tasks 表的 parameters 字段创建 GIN 索引") taskGinIndexSQL := "CREATE INDEX IF NOT EXISTS idx_tasks_parameters_gin ON tasks USING GIN (parameters);" - if err := ps.db.Exec(taskGinIndexSQL).Error; err != nil { - ps.logger.Errorw("为 tasks 的 parameters 字段创建 GIN 索引失败", "error", err) + if err := ps.db.WithContext(storageCtx).Exec(taskGinIndexSQL).Error; err != nil { + logger.Errorw("为 tasks 的 parameters 字段创建 GIN 索引失败", "error", err) return fmt.Errorf("为 tasks 的 parameters 字段创建 GIN 索引失败: %w", err) } - ps.logger.Debug("成功为 tasks 的 parameters 字段创建 GIN 索引 (或已存在)") + logger.Debug("成功为 tasks 的 parameters 字段创建 GIN 索引 (或已存在)") return nil } diff --git a/internal/infra/database/storage.go b/internal/infra/database/storage.go index ceeb84c..b2472e9 100644 --- a/internal/infra/database/storage.go +++ b/internal/infra/database/storage.go @@ -4,10 +4,12 @@ package database import ( + "context" "fmt" "git.huangwc.com/pig/pig-farm-controller/internal/infra/config" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "gorm.io/gorm" ) @@ -15,22 +17,22 @@ import ( // 所有存储实现都需要实现此接口定义的方法 type Storage interface { // Connect 建立与存储后端的连接 - Connect() error + Connect(ctx context.Context) error // Disconnect 断开与存储后端的连接 - Disconnect() error + Disconnect(ctx context.Context) error // GetDB 获取数据库实例 - GetDB() *gorm.DB + GetDB(ctx context.Context) *gorm.DB // Migrate 执行数据库迁移 // 参数为需要迁移的 GORM 模型 - Migrate(models ...interface{}) error + Migrate(ctx context.Context, models ...interface{}) error } // NewStorage 创建并返回一个存储实例 // 根据配置返回相应的存储实现 -func NewStorage(cfg config.DatabaseConfig, logger *logs.Logger) Storage { +func NewStorage(ctx context.Context, cfg config.DatabaseConfig) Storage { // 构建数据库连接字符串 connectionString := fmt.Sprintf( "user=%s password=%s dbname=%s host=%s port=%d sslmode=%s", @@ -45,11 +47,11 @@ func NewStorage(cfg config.DatabaseConfig, logger *logs.Logger) Storage { // 当前默认返回PostgreSQL存储实现,并将 logger 注入 // 当前默认返回PostgreSQL存储实现,并将 logger 注入 return NewPostgresStorage( + logs.AddCompName(context.Background(), "PostgresStorage"), connectionString, - cfg.IsTimescaleDB, // <--- 添加 IsTimescaleDB + cfg.IsTimescaleDB, cfg.MaxOpenConns, cfg.MaxIdleConns, cfg.ConnMaxLifetime, - logger, ) } diff --git a/internal/infra/logs/context.go b/internal/infra/logs/context.go new file mode 100644 index 0000000..7a8c0c3 --- /dev/null +++ b/internal/infra/logs/context.go @@ -0,0 +1,90 @@ +package logs + +import ( + "context" + "fmt" + "strings" +) + +// contextKey 是用于在 context.Context 中存储值的私有类型,避免键冲突。 +type contextKey string + +const ( + // compNameKey 用于存储组件名称。 + compNameKey contextKey = "compName" + // chainKey 用于存储调用链。 + chainKey contextKey = "chain" +) + +// AddCompName 将一个组件名(对象名)存入 Context,并返回一个包含该信息的新 Context。 +// 这通常在依赖注入时完成,用于创建组件的“身份名牌” selfCtx。 +func AddCompName(ctx context.Context, compName string) context.Context { + return context.WithValue(ctx, compNameKey, compName) +} + +// AddFuncName 这是构建调用链的核心原子操作。它智能地合并上游的调用链和当前组件的信息, +// 生成并返回一个包含更新后调用链和当前组件名的 新 Context。 +// 此函数用于只需要传递调用链而不需要立即打印日志的场景。 +func AddFuncName(upstreamCtx context.Context, selfCtx context.Context, funcName string) context.Context { + // 1. 获取上游调用链 + var oldChain []string + if val := upstreamCtx.Value(chainKey); val != nil { + if chain, ok := val.([]string); ok { + oldChain = chain + } + } + + // 2. 获取当前组件名 + compName, ok := selfCtx.Value(compNameKey).(string) + if !ok { + // 如果 selfCtx 中没有 compName,则无法构建节点,直接返回 upstreamCtx + // 这种情况通常不应该发生,因为 selfCtx 应该在依赖注入时通过 AddCompName 初始化 + return upstreamCtx + } + + // 3. 构建新节点 + newNode := fmt.Sprintf("%s.%s", compName, funcName) + + // 4. 生成新调用链 + // 创建一个新的切片,并将旧链和新节点复制进去 + newChain := make([]string, len(oldChain)+1) + copy(newChain, oldChain) + newChain[len(oldChain)] = newNode + + // 5. 创建新的 Context + // 使用 context.WithValue,以 chainKey 为键,将 newChain 存入一个新的 Context,我们称之为 tmpCtx。 + tmpCtx := context.WithValue(upstreamCtx, chainKey, newChain) + + // 接着,基于 tmpCtx,再次调用 context.WithValue,以 compNameKey 为键, + // 将从 selfCtx 中获取的 compName 存入,得到最终的 newCtx。 + // 这确保了传向下游的 Context 正确地标识了当前组件。 + newCtx := context.WithValue(tmpCtx, compNameKey, compName) + + return newCtx +} + +// DetachContext 创建一个“分离”的 Context。 +// 新的 Context 会继承原始 Context 中的所有值(特别是用于日志追踪的 chainKey 和 compNameKey), +// 但它会使用 context.Background() 作为其父级,从而“丢弃”原始 Context 的取消信号。 +// 这对于需要在请求结束后继续执行的异步任务(如记录审计日志)至关重要,可以防止出现 "context canceled" 错误。 +func DetachContext(ctx context.Context) context.Context { + detachedCtx := context.Background() + + // 复制我们关心的、用于日志追踪的所有值 + if val := ctx.Value(chainKey); val != nil { + detachedCtx = context.WithValue(detachedCtx, chainKey, val) + } + if val := ctx.Value(compNameKey); val != nil { + detachedCtx = context.WithValue(detachedCtx, compNameKey, val) + } + + return detachedCtx +} + +// 获取context中的调用链字符串 +func GetTraceStr(ctx context.Context) string { + if trace, ok := ctx.Value(chainKey).([]string); ok { + return strings.Join(trace, "->") + } + return "" +} diff --git a/internal/infra/logs/encoder.go b/internal/infra/logs/encoder.go new file mode 100644 index 0000000..317db18 --- /dev/null +++ b/internal/infra/logs/encoder.go @@ -0,0 +1,102 @@ +package logs + +import ( + "bytes" + + "go.uber.org/zap/buffer" + "go.uber.org/zap/zapcore" +) + +// traceKey 是日志中用于调用链的字段名,必须与 GetLogger 中使用的保持一致。 +const traceKey = "Trace" + +// coloredConsoleEncoder 是一个自定义的 zapcore.Encoder,它实现了两个核心功能: +// 1. 为控制台输出的日志行根据级别添加 ANSI 颜色。 +// 2. 确保代表“调用链”的结构化字段总是出现在日志行的末尾。 +type coloredConsoleEncoder struct { + zapcore.Encoder // 嵌入一个标准的 ConsoleEncoder,复用其大部分能力。 +} + +// NewColoredConsoleEncoder 创建一个新的 coloredConsoleEncoder 实例。 +func NewColoredConsoleEncoder(cfg zapcore.EncoderConfig) zapcore.Encoder { + return &coloredConsoleEncoder{ + Encoder: zapcore.NewConsoleEncoder(cfg), + } +} + +// EncodeEntry 重写了核心的编码方法。 +func (c *coloredConsoleEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) { + // 1. 从所有字段中分离出“调用链”字段和其他字段 + var traceField zapcore.Field + otherFields := make([]zapcore.Field, 0, len(fields)) + for _, field := range fields { + if field.Key == traceKey { + traceField = field + } else { + otherFields = append(otherFields, field) + } + } + + // 2. 使用内嵌的 ConsoleEncoder 先编码日志条目和“其他”字段 + // 这会生成不包含调用链的日志主体部分。 + line, err := c.Encoder.EncodeEntry(entry, otherFields) + if err != nil { + return nil, err + } + + // 3. 如果存在“调用链”字段,则将其手动编码并追加到末尾 + if traceField.Key != "" { + // 为了保持格式一致,我们创建一个临时的 Encoder 来只编码这一个字段 + // 注意:这里我们不能直接使用 c.Encoder,因为它会添加不必要的前缀(如时间、级别等) + tempEncoder := zapcore.NewConsoleEncoder(zapcore.EncoderConfig{}) + traceLine, err := tempEncoder.EncodeEntry(zapcore.Entry{}, []zapcore.Field{traceField}) + if err != nil { + return nil, err // 理论上不应失败 + } + + // 将编码后的调用链字段(例如 "逻辑调用链=xxx->yyy")追加到日志主体后 + // TrimSpace 用于移除 tempEncoder 可能产生的额外换行符 + line.AppendString("\t") // 使用制表符分隔 + line.AppendString(string(bytes.TrimSpace(traceLine.Bytes()))) + } + + // 4. 为最终的日志行(不包括调用链)添加颜色 + var color string + switch entry.Level { + case zapcore.DebugLevel: + color = blue + case zapcore.InfoLevel: + color = green + case zapcore.WarnLevel: + color = yellow + case zapcore.ErrorLevel: + color = red + case zapcore.DPanicLevel, zapcore.PanicLevel, zapcore.FatalLevel: + color = bold + red + default: + color = reset + } + + // 创建一个新的 buffer,将颜色、日志内容和重置代码包裹起来 + finalBuf := buffer.NewPool().Get() + finalBuf.AppendString(color) + finalBuf.Write(line.Bytes()) // 写入已包含调用链的日志行 + finalBuf.AppendString(reset) + + // 如果原始日志行末尾没有换行符,则添加一个 + if line.Bytes()[line.Len()-1] != '\n' { + finalBuf.AppendByte('\n') + } + + line.Free() // 释放由 c.Encoder.EncodeEntry 创建的 buffer + + return finalBuf, nil +} + +// Clone 必须被重写,以确保克隆时返回的是我们自定义的 encoder 类型。 +func (c *coloredConsoleEncoder) Clone() zapcore.Encoder { + // 克隆内嵌的 Encoder,并用它来创建一个新的 coloredConsoleEncoder + return &coloredConsoleEncoder{ + Encoder: c.Encoder.Clone(), + } +} diff --git a/internal/infra/logs/logs.go b/internal/infra/logs/logs.go index 0b758eb..13e8c4d 100644 --- a/internal/infra/logs/logs.go +++ b/internal/infra/logs/logs.go @@ -9,11 +9,11 @@ import ( "io" "os" "strings" + "sync" "time" "git.huangwc.com/pig/pig-farm-controller/internal/infra/config" "go.uber.org/zap" - "go.uber.org/zap/buffer" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" gormlogger "gorm.io/gorm/logger" @@ -32,6 +32,11 @@ const ( bold = "\033[1m" ) +var ( + defaultLogger *Logger + once sync.Once +) + // Logger 是一个封装了 zap.SugaredLogger 的日志记录器。 // 它提供了结构化日志记录的各种方法,并实现了 io.Writer 接口以兼容 Gin。 type Logger struct { @@ -64,10 +69,59 @@ func NewLogger(cfg config.LogConfig) *Logger { return &Logger{zapLogger.Sugar()} } +// InitDefaultLogger 初始化包级单例的 defaultLogger。 +// 此函数应在应用程序启动时调用一次。 +func InitDefaultLogger(cfg config.LogConfig) { + once.Do(func() { + defaultLogger = NewLogger(cfg) + }) +} + +// GetLogger 从 Context 中提取调用链信息,并返回一个携带这些信息的 Logger 副本。 +// 如果 Context 中没有调用链信息,则直接返回默认的 Logger。 +func GetLogger(ctx context.Context) *Logger { + if defaultLogger == nil { + // 在调用 InitDefaultLogger 之前,提供一个备用的、仅输出到控制台的 Logger + fallbackCfg := config.LogConfig{Level: "info", Format: "console"} + return NewLogger(fallbackCfg) + } + + val := ctx.Value(chainKey) + if val == nil { + return defaultLogger + } + + chain := GetTraceStr(ctx) + if chain == "" { + return defaultLogger + } + + // 使用 With 方法创建带有 traceKey 字段的 Logger 副本 + newSugaredLogger := defaultLogger.With(traceKey, chain) + return &Logger{newSugaredLogger} +} + +// Trace 是构建和记录调用链的核心函数。 +// 它首先使用 AddFuncName 创建一个包含新调用节点的新 Context, +// 然后返回这个新 Context 和一个包含完整调用链的 Logger 实例。 +func Trace(upstreamCtx context.Context, selfCtx context.Context, funcName string) (context.Context, *Logger) { + newCtx := AddFuncName(upstreamCtx, selfCtx, funcName) + logger := GetLogger(newCtx) + return newCtx, logger +} + +// TraceLogger 类似于 Trace,但只返回一个包含完整调用链的 Logger 实例, +// 而不返回更新后的 Context。适用于调用链的末端或不需要向下传递 Context 的场景。 +func TraceLogger(upstreamCtx context.Context, selfCtx context.Context, funcName string) *Logger { + newCtx := AddFuncName(upstreamCtx, selfCtx, funcName) + logger := GetLogger(newCtx) + return logger +} + // GetEncoder 根据指定的格式返回一个 zapcore.Encoder。 func GetEncoder(format string) zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() - encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // 时间格式: 2006-01-02T15:04:05.000Z0700 + encoderConfig.EncodeTime = zapcore.RFC3339TimeEncoder // 时间格式: 2006-01-02T15:04:05Z07:00 encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder // 日志级别大写: INFO (由 coloredConsoleEncoder 处理颜色) if format == "json" { @@ -77,63 +131,6 @@ func GetEncoder(format string) zapcore.Encoder { return NewColoredConsoleEncoder(encoderConfig) } -// coloredConsoleEncoder 是一个自定义的 zapcore.Encoder,用于为整个日志行添加颜色。 -type coloredConsoleEncoder struct { - zapcore.Encoder // 嵌入默认的 ConsoleEncoder,以便委托其大部分功能 - cfg zapcore.EncoderConfig -} - -// NewColoredConsoleEncoder 创建一个新的 coloredConsoleEncoder 实例。 -func NewColoredConsoleEncoder(cfg zapcore.EncoderConfig) zapcore.Encoder { - // 创建一个标准的 ConsoleEncoder 作为基础 - baseEncoder := zapcore.NewConsoleEncoder(cfg) - return &coloredConsoleEncoder{ - Encoder: baseEncoder, - cfg: cfg, - } -} - -// EncodeEntry 重写 EncodeEntry 方法,在原始日志输出前后添加颜色代码。 -func (c *coloredConsoleEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) { - // 首先,让嵌入的默认编码器生成原始的日志行 - buf, err := c.Encoder.EncodeEntry(entry, fields) - if err != nil { - return nil, err - } - - // 根据日志级别确定颜色 - var color string - switch entry.Level { - case zapcore.DebugLevel: - color = blue - case zapcore.InfoLevel: - color = green - case zapcore.WarnLevel: - color = yellow - case zapcore.ErrorLevel: - color = red - case zapcore.DPanicLevel, zapcore.PanicLevel, zapcore.FatalLevel: - color = bold + red - default: - color = reset - } - - // 创建一个新的 buffer 来存储带颜色的日志行 - coloredBuf := buffer.NewPool().Get() - coloredBuf.AppendString(color) - coloredBuf.Write(buf.Bytes()) - coloredBuf.AppendString(reset) - - buf.Free() // 释放原始 buffer 回池中 - - return coloredBuf, nil -} - -// Clone 方法也需要重写,以确保返回一个新的 coloredConsoleEncoder 实例。 -func (c *coloredConsoleEncoder) Clone() zapcore.Encoder { - return NewColoredConsoleEncoder(c.cfg) -} - // getWriteSyncer 根据配置创建日志写入目标。 func getWriteSyncer(cfg config.LogConfig) zapcore.WriteSyncer { writers := []zapcore.WriteSyncer{os.Stdout} @@ -190,17 +187,17 @@ func (g *GormLogger) LogMode(level gormlogger.LogLevel) gormlogger.Interface { // Info 打印 Info 级别的日志。 func (g *GormLogger) Info(ctx context.Context, msg string, data ...interface{}) { - g.ZapLogger.Infof(msg, data...) + GetLogger(ctx).Infof(msg, data...) } // Warn 打印 Warn 级别的日志。 func (g *GormLogger) Warn(ctx context.Context, msg string, data ...interface{}) { - g.ZapLogger.Warnf(msg, data...) + GetLogger(ctx).Warnf(msg, data...) } // Error 打印 Error 级别的日志。 func (g *GormLogger) Error(ctx context.Context, msg string, data ...interface{}) { - g.ZapLogger.Errorf(msg, data...) + GetLogger(ctx).Errorf(msg, data...) } // Trace 打印 SQL 查询日志,这是 GORM 日志的核心。 @@ -214,25 +211,30 @@ func (g *GormLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql "elapsed", fmt.Sprintf("%.3fms", float64(elapsed.Nanoseconds())/1e6), } - // --- 逻辑修复开始 --- + // 附加调用链信息 + chain := GetTraceStr(ctx) + if chain != "" { + fields = append(fields, traceKey, chain) + } + if err != nil { // 如果是 "record not found" 错误且我们配置了跳过,则直接返回 if g.SkipErrRecordNotFound && strings.Contains(err.Error(), "record not found") { return } // 否则,记录为错误日志 - g.ZapLogger.With(fields...).Errorf("[GORM] error: %s", err) + defaultLogger.With(fields...).Errorf("[GORM] error: %s", err) return } // 如果查询时间超过慢查询阈值,则记录警告 if g.SlowThreshold != 0 && elapsed > g.SlowThreshold { - g.ZapLogger.With(fields...).Warnf("[GORM] slow query") + defaultLogger.With(fields...).Warnf("[GORM] slow query") return } // 正常情况,记录 Debug 级别的 SQL 查询 - g.ZapLogger.With(fields...).Debugf("[GORM] trace") + defaultLogger.With(fields...).Debugf("[GORM] trace") // --- 逻辑修复结束 --- } diff --git a/internal/infra/models/device_template.go b/internal/infra/models/device_template.go index 7b65e77..bbb432b 100644 --- a/internal/infra/models/device_template.go +++ b/internal/infra/models/device_template.go @@ -6,6 +6,7 @@ import ( "fmt" "git.huangwc.com/pig/pig-farm-controller/internal/infra/utils/command_generater" + "gorm.io/datatypes" "gorm.io/gorm" ) diff --git a/internal/infra/notify/lark.go b/internal/infra/notify/lark.go index 586aa91..c1971ca 100644 --- a/internal/infra/notify/lark.go +++ b/internal/infra/notify/lark.go @@ -2,11 +2,14 @@ package notify import ( "bytes" + "context" "encoding/json" "fmt" "net/http" "sync" "time" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" ) const ( @@ -18,6 +21,8 @@ const ( // larkNotifier 实现了 Notifier 接口,用于通过飞书自建应用发送私聊消息。 type larkNotifier struct { + ctx context.Context + appID string // 应用 ID appSecret string // 应用密钥 @@ -29,8 +34,9 @@ type larkNotifier struct { // NewLarkNotifier 创建一个新的 larkNotifier 实例。 // 调用者需要注入飞书应用的 AppID 和 AppSecret。 -func NewLarkNotifier(appID, appSecret string) Notifier { +func NewLarkNotifier(ctx context.Context, appID, appSecret string) Notifier { return &larkNotifier{ + ctx: ctx, appID: appID, appSecret: appSecret, } @@ -38,9 +44,10 @@ func NewLarkNotifier(appID, appSecret string) Notifier { // Send 向指定用户发送一条飞书消息卡片。 // toAddr 参数是接收者的邮箱地址。 -func (l *larkNotifier) Send(content AlarmContent, toAddr string) error { +func (l *larkNotifier) Send(ctx context.Context, content AlarmContent, toAddr string) error { + notifierCtx := logs.AddFuncName(ctx, l.ctx, "Send") // 1. 获取有效的 tenant_access_token - token, err := l.getAccessToken() + token, err := l.getAccessToken(notifierCtx) if err != nil { return err } @@ -115,7 +122,7 @@ func (l *larkNotifier) Send(content AlarmContent, toAddr string) error { } // getAccessToken 获取并缓存 tenant_access_token,处理了线程安全和自动刷新。 -func (l *larkNotifier) getAccessToken() (string, error) { +func (l *larkNotifier) getAccessToken(ctx context.Context) (string, error) { l.mu.Lock() defer l.mu.Unlock() diff --git a/internal/infra/notify/log_notifier.go b/internal/infra/notify/log_notifier.go index 887e524..001bdb6 100644 --- a/internal/infra/notify/log_notifier.go +++ b/internal/infra/notify/log_notifier.go @@ -1,26 +1,29 @@ package notify import ( + "context" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" ) // logNotifier 实现了 Notifier 接口,用于将告警信息记录到日志中。 type logNotifier struct { - logger *logs.Logger + ctx context.Context } // NewLogNotifier 创建一个新的 logNotifier 实例。 // 它接收一个日志记录器,用于实际的日志输出。 -func NewLogNotifier(logger *logs.Logger) Notifier { +func NewLogNotifier(ctx context.Context) Notifier { return &logNotifier{ - logger: logger, + ctx: ctx, } } // Send 将告警内容以结构化的方式记录到日志中。 // toAddr 参数在这里表示告警的预期接收者地址,也会被记录。 -func (l *logNotifier) Send(content AlarmContent, toAddr string) error { - l.logger.Infow("告警已记录到日志", +func (l *logNotifier) Send(ctx context.Context, content AlarmContent, toAddr string) error { + logger := logs.TraceLogger(ctx, l.ctx, "Send") + logger.Infow("告警已记录到日志", "notifierType", NotifierTypeLog, "title", content.Title, "message", content.Message, 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/infra/notify/smtp.go b/internal/infra/notify/smtp.go index f889556..131e788 100644 --- a/internal/infra/notify/smtp.go +++ b/internal/infra/notify/smtp.go @@ -1,6 +1,7 @@ package notify import ( + "context" "fmt" "net/smtp" "strings" @@ -8,6 +9,7 @@ import ( // smtpNotifier 实现了 Notifier 接口,用于通过 SMTP 发送邮件通知。 type smtpNotifier struct { + ctx context.Context host string // SMTP 服务器地址 port int // SMTP 服务器端口 username string // 发件人邮箱地址 @@ -17,8 +19,9 @@ type smtpNotifier struct { // NewSMTPNotifier 创建一个新的 smtpNotifier 实例。 // 调用者需要注入 SMTP 相关的配置。 -func NewSMTPNotifier(host string, port int, username, password, sender string) Notifier { +func NewSMTPNotifier(ctx context.Context, host string, port int, username, password, sender string) Notifier { return &smtpNotifier{ + ctx: ctx, host: host, port: port, username: username, @@ -28,7 +31,7 @@ func NewSMTPNotifier(host string, port int, username, password, sender string) N } // Send 使用 net/smtp 包发送一封邮件。 -func (s *smtpNotifier) Send(content AlarmContent, toAddr string) error { +func (s *smtpNotifier) Send(ctx context.Context, content AlarmContent, toAddr string) error { // 1. 设置认证信息 auth := smtp.PlainAuth("", s.username, s.password, s.host) diff --git a/internal/infra/notify/wechat.go b/internal/infra/notify/wechat.go index 9e7e79e..ec5c33d 100644 --- a/internal/infra/notify/wechat.go +++ b/internal/infra/notify/wechat.go @@ -2,6 +2,7 @@ package notify import ( "bytes" + "context" "encoding/json" "fmt" "net/http" @@ -19,6 +20,8 @@ const ( // wechatNotifier 实现了 Notifier 接口,用于通过企业微信自建应用发送私聊消息。 type wechatNotifier struct { + ctx context.Context + corpID string // 企业ID (CorpID) agentID string // 应用ID (AgentID) secret string // 应用密钥 (Secret) @@ -31,8 +34,9 @@ type wechatNotifier struct { // NewWechatNotifier 创建一个新的 wechatNotifier 实例。 // 调用者需要注入企业微信应用的 CorpID, AgentID 和 Secret。 -func NewWechatNotifier(corpID, agentID, secret string) Notifier { +func NewWechatNotifier(ctx context.Context, corpID, agentID, secret string) Notifier { return &wechatNotifier{ + ctx: ctx, corpID: corpID, agentID: agentID, secret: secret, @@ -41,7 +45,7 @@ func NewWechatNotifier(corpID, agentID, secret string) Notifier { // Send 向指定用户发送一条 markdown 格式的私聊消息。 // toAddr 参数是接收者的 UserID 列表,用逗号或竖线分隔。 -func (w *wechatNotifier) Send(content AlarmContent, toAddr string) error { +func (w *wechatNotifier) Send(ctx context.Context, content AlarmContent, toAddr string) error { // 1. 获取有效的 access_token token, err := w.getAccessToken() if err != nil { diff --git a/internal/infra/repository/area_controller_repository.go b/internal/infra/repository/area_controller_repository.go index 45b5737..4c0b62f 100644 --- a/internal/infra/repository/area_controller_repository.go +++ b/internal/infra/repository/area_controller_repository.go @@ -1,72 +1,85 @@ package repository import ( + "context" "fmt" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/gorm" ) // AreaControllerRepository 定义了对 AreaController 模型的数据库操作接口 type AreaControllerRepository interface { - FindByID(id uint) (*models.AreaController, error) - FindByNetworkID(networkID string) (*models.AreaController, error) - Create(ac *models.AreaController) error - ListAll() ([]*models.AreaController, error) - Update(ac *models.AreaController) error - Delete(id uint) error + FindByID(ctx context.Context, id uint) (*models.AreaController, error) + FindByNetworkID(ctx context.Context, networkID string) (*models.AreaController, error) + Create(ctx context.Context, ac *models.AreaController) error + ListAll(ctx context.Context) ([]*models.AreaController, error) + Update(ctx context.Context, ac *models.AreaController) error + Delete(ctx context.Context, id uint) error } // gormAreaControllerRepository 是 AreaControllerRepository 的 GORM 实现。 type gormAreaControllerRepository struct { - db *gorm.DB + ctx context.Context + db *gorm.DB } // NewGormAreaControllerRepository 创建一个新的 AreaControllerRepository GORM 实现实例。 -func NewGormAreaControllerRepository(db *gorm.DB) AreaControllerRepository { - return &gormAreaControllerRepository{db: db} +func NewGormAreaControllerRepository(ctx context.Context, db *gorm.DB) AreaControllerRepository { + return &gormAreaControllerRepository{ + ctx: ctx, + db: db, + } } // Create 创建一个新的 AreaController 记录。 -func (r *gormAreaControllerRepository) Create(ac *models.AreaController) error { - return r.db.Create(ac).Error +func (r *gormAreaControllerRepository) Create(ctx context.Context, ac *models.AreaController) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "Create") + return r.db.WithContext(repoCtx).Create(ac).Error } // ListAll 返回所有 AreaController 的列表。 -func (r *gormAreaControllerRepository) ListAll() ([]*models.AreaController, error) { +func (r *gormAreaControllerRepository) ListAll(ctx context.Context) ([]*models.AreaController, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListAll") var areaControllers []*models.AreaController - if err := r.db.Find(&areaControllers).Error; err != nil { + if err := r.db.WithContext(repoCtx).Find(&areaControllers).Error; err != nil { return nil, err } return areaControllers, nil } // Update 更新一个已存在的 AreaController 记录。 -func (r *gormAreaControllerRepository) Update(ac *models.AreaController) error { - return r.db.Save(ac).Error +func (r *gormAreaControllerRepository) Update(ctx context.Context, ac *models.AreaController) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "Update") + return r.db.WithContext(repoCtx).Save(ac).Error } // Delete 删除一个 AreaController 记录。 -func (r *gormAreaControllerRepository) Delete(id uint) error { - if err := r.db.Delete(&models.AreaController{}, id).Error; err != nil { +func (r *gormAreaControllerRepository) Delete(ctx context.Context, id uint) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "Delete") + if err := r.db.WithContext(repoCtx).Delete(&models.AreaController{}, id).Error; err != nil { return fmt.Errorf("删除区域主控失败: %w", err) } return nil } // FindByID 通过 ID 查找一个 AreaController。 -func (r *gormAreaControllerRepository) FindByID(id uint) (*models.AreaController, error) { +func (r *gormAreaControllerRepository) FindByID(ctx context.Context, id uint) (*models.AreaController, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByID") var areaController models.AreaController - if err := r.db.First(&areaController, id).Error; err != nil { + if err := r.db.WithContext(repoCtx).First(&areaController, id).Error; err != nil { return nil, err } return &areaController, nil } // FindByNetworkID 通过 NetworkID 查找一个 AreaController。 -func (r *gormAreaControllerRepository) FindByNetworkID(networkID string) (*models.AreaController, error) { +func (r *gormAreaControllerRepository) FindByNetworkID(ctx context.Context, networkID string) (*models.AreaController, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByNetworkID") var areaController models.AreaController - if err := r.db.Where("network_id = ?", networkID).First(&areaController).Error; err != nil { + if err := r.db.WithContext(repoCtx).Where("network_id = ?", networkID).First(&areaController).Error; err != nil { return nil, err } return &areaController, nil diff --git a/internal/infra/repository/device_command_log_repository.go b/internal/infra/repository/device_command_log_repository.go index b8a219b..b4bc0bb 100644 --- a/internal/infra/repository/device_command_log_repository.go +++ b/internal/infra/repository/device_command_log_repository.go @@ -1,9 +1,12 @@ package repository import ( + "context" "time" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/gorm" ) @@ -18,40 +21,44 @@ type DeviceCommandLogListOptions struct { // DeviceCommandLogRepository 定义了设备下行命令历史记录的数据访问接口 type DeviceCommandLogRepository interface { - Create(record *models.DeviceCommandLog) error - FindByMessageID(messageID string) (*models.DeviceCommandLog, error) - UpdateAcknowledgedAt(messageID string, acknowledgedAt time.Time, receivedSuccess bool) error + Create(ctx context.Context, record *models.DeviceCommandLog) error + FindByMessageID(ctx context.Context, messageID string) (*models.DeviceCommandLog, error) + UpdateAcknowledgedAt(ctx context.Context, messageID string, acknowledgedAt time.Time, receivedSuccess bool) error // List 支持分页和过滤的列表查询 - List(opts DeviceCommandLogListOptions, page, pageSize int) ([]models.DeviceCommandLog, int64, error) + List(ctx context.Context, opts DeviceCommandLogListOptions, page, pageSize int) ([]models.DeviceCommandLog, int64, error) } // gormDeviceCommandLogRepository 是 DeviceCommandLogRepository 接口的 GORM 实现 type gormDeviceCommandLogRepository struct { - db *gorm.DB + ctx context.Context + db *gorm.DB } // NewGormDeviceCommandLogRepository 创建一个新的 DeviceCommandLogRepository GORM 实现 -func NewGormDeviceCommandLogRepository(db *gorm.DB) DeviceCommandLogRepository { - return &gormDeviceCommandLogRepository{db: db} +func NewGormDeviceCommandLogRepository(ctx context.Context, db *gorm.DB) DeviceCommandLogRepository { + return &gormDeviceCommandLogRepository{ctx: ctx, db: db} } // Create 实现 DeviceCommandLogRepository 接口的 Create 方法 -func (r *gormDeviceCommandLogRepository) Create(record *models.DeviceCommandLog) error { - return r.db.Create(record).Error +func (r *gormDeviceCommandLogRepository) Create(ctx context.Context, record *models.DeviceCommandLog) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "Create") + return r.db.WithContext(repoCtx).Create(record).Error } // FindByMessageID 实现 DeviceCommandLogRepository 接口的 FindByMessageID 方法 -func (r *gormDeviceCommandLogRepository) FindByMessageID(messageID string) (*models.DeviceCommandLog, error) { +func (r *gormDeviceCommandLogRepository) FindByMessageID(ctx context.Context, messageID string) (*models.DeviceCommandLog, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByMessageID") var record models.DeviceCommandLog - if err := r.db.Where("message_id = ?", messageID).First(&record).Error; err != nil { + if err := r.db.WithContext(repoCtx).Where("message_id = ?", messageID).First(&record).Error; err != nil { return nil, err } return &record, nil } // UpdateAcknowledgedAt 实现 DeviceCommandLogRepository 接口的 UpdateAcknowledgedAt 方法 -func (r *gormDeviceCommandLogRepository) UpdateAcknowledgedAt(messageID string, acknowledgedAt time.Time, receivedSuccess bool) error { - return r.db.Model(&models.DeviceCommandLog{}). +func (r *gormDeviceCommandLogRepository) UpdateAcknowledgedAt(ctx context.Context, messageID string, acknowledgedAt time.Time, receivedSuccess bool) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateAcknowledgedAt") + return r.db.WithContext(repoCtx).Model(&models.DeviceCommandLog{}). Where("message_id = ?", messageID). Updates(map[string]interface{}{ "acknowledged_at": acknowledgedAt, @@ -60,7 +67,8 @@ func (r *gormDeviceCommandLogRepository) UpdateAcknowledgedAt(messageID string, } // List 实现了分页和过滤查询设备命令日志的功能 -func (r *gormDeviceCommandLogRepository) List(opts DeviceCommandLogListOptions, page, pageSize int) ([]models.DeviceCommandLog, int64, error) { +func (r *gormDeviceCommandLogRepository) List(ctx context.Context, opts DeviceCommandLogListOptions, page, pageSize int) ([]models.DeviceCommandLog, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "List") // --- 校验分页参数 --- if page <= 0 || pageSize <= 0 { return nil, 0, ErrInvalidPagination @@ -69,7 +77,7 @@ func (r *gormDeviceCommandLogRepository) List(opts DeviceCommandLogListOptions, var results []models.DeviceCommandLog var total int64 - query := r.db.Model(&models.DeviceCommandLog{}) + query := r.db.WithContext(repoCtx).Model(&models.DeviceCommandLog{}) // --- 应用过滤条件 --- if opts.DeviceID != nil { diff --git a/internal/infra/repository/device_repository.go b/internal/infra/repository/device_repository.go index 8b2d167..32757e3 100644 --- a/internal/infra/repository/device_repository.go +++ b/internal/infra/repository/device_repository.go @@ -1,10 +1,13 @@ package repository import ( + "context" "fmt" "strconv" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/gorm" ) @@ -12,83 +15,89 @@ import ( // 这一层抽象使得上层业务逻辑无需关心底层数据库的具体实现。 type DeviceRepository interface { // Create 创建一个新设备记录 - Create(device *models.Device) error + Create(ctx context.Context, device *models.Device) error // FindByID 根据主键 ID 查找设备 - FindByID(id uint) (*models.Device, error) + FindByID(ctx context.Context, id uint) (*models.Device, error) // FindByIDString 根据字符串形式的主键 ID 查找设备 - FindByIDString(id string) (*models.Device, error) + FindByIDString(ctx context.Context, id string) (*models.Device, error) // ListAll 获取所有设备的列表 - ListAll() ([]*models.Device, error) + ListAll(ctx context.Context) ([]*models.Device, error) // ListAllSensors 获取所有传感器类型的设备列表 - ListAllSensors() ([]*models.Device, error) + ListAllSensors(ctx context.Context) ([]*models.Device, error) // ListByAreaControllerID 根据区域主控 ID 列出所有子设备 - ListByAreaControllerID(areaControllerID uint) ([]*models.Device, error) + ListByAreaControllerID(ctx context.Context, areaControllerID uint) ([]*models.Device, error) // FindByDeviceTemplateID 根据设备模板ID查找所有使用该模板的设备 - FindByDeviceTemplateID(deviceTemplateID uint) ([]*models.Device, error) + FindByDeviceTemplateID(ctx context.Context, deviceTemplateID uint) ([]*models.Device, error) // Update 更新一个已有的设备信息 - Update(device *models.Device) error + Update(ctx context.Context, device *models.Device) error // Delete 根据主键 ID 删除一个设备 - Delete(id uint) error + Delete(ctx context.Context, id uint) error // FindByAreaControllerAndPhysicalAddress 根据区域主控ID和物理地址(总线号、总线地址)查找设备 - FindByAreaControllerAndPhysicalAddress(areaControllerID uint, busNumber int, busAddress int) (*models.Device, error) + FindByAreaControllerAndPhysicalAddress(ctx context.Context, areaControllerID uint, busNumber int, busAddress int) (*models.Device, error) // GetDevicesByIDsTx 在指定事务中根据ID列表获取设备 - GetDevicesByIDsTx(tx *gorm.DB, ids []uint) ([]models.Device, error) + GetDevicesByIDsTx(ctx context.Context, tx *gorm.DB, ids []uint) ([]models.Device, error) // IsDeviceInUse 检查设备是否被任何任务使用 - IsDeviceInUse(deviceID uint) (bool, error) + IsDeviceInUse(ctx context.Context, deviceID uint) (bool, error) // IsAreaControllerInUse 检查区域主控是否被任何设备使用 - IsAreaControllerInUse(areaControllerID uint) (bool, error) + IsAreaControllerInUse(ctx context.Context, areaControllerID uint) (bool, error) } // gormDeviceRepository 是 DeviceRepository 的 GORM 实现 type gormDeviceRepository struct { - db *gorm.DB + ctx context.Context + db *gorm.DB } // NewGormDeviceRepository 创建一个新的 DeviceRepository GORM 实现实例 -func NewGormDeviceRepository(db *gorm.DB) DeviceRepository { - return &gormDeviceRepository{db: db} +func NewGormDeviceRepository(ctx context.Context, db *gorm.DB) DeviceRepository { + return &gormDeviceRepository{ctx: ctx, db: db} } // Create 创建一个新的设备记录 -func (r *gormDeviceRepository) Create(device *models.Device) error { - return r.db.Create(device).Error +func (r *gormDeviceRepository) Create(ctx context.Context, device *models.Device) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "Create") + // BeforeSave 钩子会在这里被自动触发 + return r.db.WithContext(repoCtx).Create(device).Error } // FindByID 根据 ID 查找设备 -func (r *gormDeviceRepository) FindByID(id uint) (*models.Device, error) { +func (r *gormDeviceRepository) FindByID(ctx context.Context, id uint) (*models.Device, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByID") var device models.Device - if err := r.db.Preload("AreaController").Preload("DeviceTemplate").First(&device, id).Error; err != nil { + if err := r.db.WithContext(repoCtx).Preload("AreaController").Preload("DeviceTemplate").First(&device, id).Error; err != nil { return nil, err } return &device, nil } // GetDevicesByIDsTx 在指定事务中根据ID列表获取设备 -func (r *gormDeviceRepository) GetDevicesByIDsTx(tx *gorm.DB, ids []uint) ([]models.Device, error) { +func (r *gormDeviceRepository) GetDevicesByIDsTx(ctx context.Context, tx *gorm.DB, ids []uint) ([]models.Device, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "GetDevicesByIDsTx") var devices []models.Device if len(ids) == 0 { return devices, nil } - if err := tx.Where("id IN ?", ids).Find(&devices).Error; err != nil { + if err := tx.WithContext(repoCtx).Where("id IN ?", ids).Find(&devices).Error; err != nil { return nil, err } return devices, nil } // FindByIDString 根据字符串形式的主键 ID 查找设备 -func (r *gormDeviceRepository) FindByIDString(id string) (*models.Device, error) { +func (r *gormDeviceRepository) FindByIDString(ctx context.Context, id string) (*models.Device, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByIDString") // 将字符串ID转换为uint64 idInt, err := strconv.ParseUint(id, 10, 64) if err != nil { @@ -96,22 +105,24 @@ func (r *gormDeviceRepository) FindByIDString(id string) (*models.Device, error) return nil, fmt.Errorf("无效的设备ID格式: %w", err) } // 调用已有的 FindByID 方法 - return r.FindByID(uint(idInt)) + return r.FindByID(repoCtx, uint(idInt)) } // ListAll 获取所有设备的列表 -func (r *gormDeviceRepository) ListAll() ([]*models.Device, error) { +func (r *gormDeviceRepository) ListAll(ctx context.Context) ([]*models.Device, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListAll") var devices []*models.Device - if err := r.db.Preload("AreaController").Preload("DeviceTemplate").Find(&devices).Error; err != nil { + if err := r.db.WithContext(repoCtx).Preload("AreaController").Preload("DeviceTemplate").Find(&devices).Error; err != nil { return nil, err } return devices, nil } // ListAllSensors 检索归类为传感器的所有设备 -func (r *gormDeviceRepository) ListAllSensors() ([]*models.Device, error) { +func (r *gormDeviceRepository) ListAllSensors(ctx context.Context) ([]*models.Device, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListAllSensors") var sensors []*models.Device - err := r.db.Preload("AreaController").Preload("DeviceTemplate"). + err := r.db.WithContext(repoCtx).Preload("AreaController").Preload("DeviceTemplate"). Joins("JOIN device_templates ON device_templates.id = devices.device_template_id"). Where("device_templates.category = ?", models.CategorySensor). Find(&sensors).Error @@ -122,9 +133,10 @@ func (r *gormDeviceRepository) ListAllSensors() ([]*models.Device, error) { } // ListByAreaControllerID 根据区域主控 ID 列出所有子设备 -func (r *gormDeviceRepository) ListByAreaControllerID(areaControllerID uint) ([]*models.Device, error) { +func (r *gormDeviceRepository) ListByAreaControllerID(ctx context.Context, areaControllerID uint) ([]*models.Device, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListByAreaControllerID") var devices []*models.Device - err := r.db.Preload("AreaController").Preload("DeviceTemplate").Where("area_controller_id = ?", areaControllerID).Find(&devices).Error + err := r.db.WithContext(repoCtx).Preload("AreaController").Preload("DeviceTemplate").Where("area_controller_id = ?", areaControllerID).Find(&devices).Error if err != nil { return nil, err } @@ -132,9 +144,10 @@ func (r *gormDeviceRepository) ListByAreaControllerID(areaControllerID uint) ([] } // FindByDeviceTemplateID 根据设备模板ID查找所有使用该模板的设备 -func (r *gormDeviceRepository) FindByDeviceTemplateID(deviceTemplateID uint) ([]*models.Device, error) { +func (r *gormDeviceRepository) FindByDeviceTemplateID(ctx context.Context, deviceTemplateID uint) ([]*models.Device, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByDeviceTemplateID") var devices []*models.Device - err := r.db.Where("device_template_id = ?", deviceTemplateID).Find(&devices).Error + err := r.db.WithContext(repoCtx).Where("device_template_id = ?", deviceTemplateID).Find(&devices).Error if err != nil { return nil, fmt.Errorf("查询使用设备模板ID %d 的设备失败: %w", deviceTemplateID, err) } @@ -143,20 +156,23 @@ func (r *gormDeviceRepository) FindByDeviceTemplateID(deviceTemplateID uint) ([] // Update 更新一个已有的设备信息 // GORM 的 Save 方法会自动处理主键存在时更新,不存在时创建的逻辑,但这里我们明确用于更新。 -func (r *gormDeviceRepository) Update(device *models.Device) error { - return r.db.Save(device).Error +func (r *gormDeviceRepository) Update(ctx context.Context, device *models.Device) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "Update") + return r.db.WithContext(repoCtx).Save(device).Error } // Delete 根据 ID 删除一个设备 // GORM 使用软删除,记录不会从数据库中物理移除,而是设置 DeletedAt 字段。 -func (r *gormDeviceRepository) Delete(id uint) error { - return r.db.Delete(&models.Device{}, id).Error +func (r *gormDeviceRepository) Delete(ctx context.Context, id uint) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "Delete") + return r.db.WithContext(repoCtx).Delete(&models.Device{}, id).Error } // FindByAreaControllerAndPhysicalAddress 根据区域主控ID和物理地址(总线号、总线地址)查找设备 -func (r *gormDeviceRepository) FindByAreaControllerAndPhysicalAddress(areaControllerID uint, busNumber int, busAddress int) (*models.Device, error) { +func (r *gormDeviceRepository) FindByAreaControllerAndPhysicalAddress(ctx context.Context, areaControllerID uint, busNumber int, busAddress int) (*models.Device, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByAreaControllerAndPhysicalAddress") var device models.Device - err := r.db.Preload("AreaController").Preload("DeviceTemplate"). + err := r.db.WithContext(repoCtx).Preload("AreaController").Preload("DeviceTemplate"). Where("area_controller_id = ?", areaControllerID). Where("properties->>'bus_number' = ?", strconv.Itoa(busNumber)). Where("properties->>'bus_address' = ?", strconv.Itoa(busAddress)). @@ -169,10 +185,11 @@ func (r *gormDeviceRepository) FindByAreaControllerAndPhysicalAddress(areaContro } // IsDeviceInUse 检查设备是否被任何任务使用 -func (r *gormDeviceRepository) IsDeviceInUse(deviceID uint) (bool, error) { +func (r *gormDeviceRepository) IsDeviceInUse(ctx context.Context, deviceID uint) (bool, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "IsDeviceInUse") var count int64 // 直接对 device_tasks 关联表进行 COUNT 操作,性能最高 - err := r.db.Model(&models.DeviceTask{}).Where("device_id = ?", deviceID).Count(&count).Error + err := r.db.WithContext(repoCtx).Model(&models.DeviceTask{}).Where("device_id = ?", deviceID).Count(&count).Error if err != nil { return false, fmt.Errorf("查询设备任务关联失败: %w", err) } @@ -180,9 +197,10 @@ func (r *gormDeviceRepository) IsDeviceInUse(deviceID uint) (bool, error) { } // IsAreaControllerInUse 检查区域主控是否被任何设备使用 -func (r *gormDeviceRepository) IsAreaControllerInUse(areaControllerID uint) (bool, error) { +func (r *gormDeviceRepository) IsAreaControllerInUse(ctx context.Context, areaControllerID uint) (bool, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "IsAreaControllerInUse") var count int64 - if err := r.db.Model(&models.Device{}).Where("area_controller_id = ?", areaControllerID).Count(&count).Error; err != nil { + if err := r.db.WithContext(repoCtx).Model(&models.Device{}).Where("area_controller_id = ?", areaControllerID).Count(&count).Error; err != nil { return false, fmt.Errorf("检查区域主控使用情况失败: %w", err) } return count > 0, nil diff --git a/internal/infra/repository/device_template_repository.go b/internal/infra/repository/device_template_repository.go index 5872f50..dd9c0b1 100644 --- a/internal/infra/repository/device_template_repository.go +++ b/internal/infra/repository/device_template_repository.go @@ -1,43 +1,49 @@ package repository import ( + "context" "errors" "fmt" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/gorm" ) // DeviceTemplateRepository 定义了设备模板数据访问的接口 type DeviceTemplateRepository interface { - Create(deviceTemplate *models.DeviceTemplate) error - FindByID(id uint) (*models.DeviceTemplate, error) - FindByName(name string) (*models.DeviceTemplate, error) - ListAll() ([]*models.DeviceTemplate, error) - Update(deviceTemplate *models.DeviceTemplate) error - Delete(id uint) error - IsInUse(id uint) (bool, error) + Create(ctx context.Context, deviceTemplate *models.DeviceTemplate) error + FindByID(ctx context.Context, id uint) (*models.DeviceTemplate, error) + FindByName(ctx context.Context, name string) (*models.DeviceTemplate, error) + ListAll(ctx context.Context) ([]*models.DeviceTemplate, error) + Update(ctx context.Context, deviceTemplate *models.DeviceTemplate) error + Delete(ctx context.Context, id uint) error + IsInUse(ctx context.Context, id uint) (bool, error) } // gormDeviceTemplateRepository 是 DeviceTemplateRepository 的 GORM 实现 type gormDeviceTemplateRepository struct { - db *gorm.DB + ctx context.Context + db *gorm.DB } // NewGormDeviceTemplateRepository 创建一个新的 gormDeviceTemplateRepository 实例 -func NewGormDeviceTemplateRepository(db *gorm.DB) DeviceTemplateRepository { - return &gormDeviceTemplateRepository{db: db} +func NewGormDeviceTemplateRepository(ctx context.Context, db *gorm.DB) DeviceTemplateRepository { + return &gormDeviceTemplateRepository{ctx: ctx, db: db} } // Create 在数据库中创建一个新的设备模板 -func (r *gormDeviceTemplateRepository) Create(deviceTemplate *models.DeviceTemplate) error { - return r.db.Create(deviceTemplate).Error +func (r *gormDeviceTemplateRepository) Create(ctx context.Context, deviceTemplate *models.DeviceTemplate) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "Create") + return r.db.WithContext(repoCtx).Create(deviceTemplate).Error } // FindByID 根据ID查找设备模板 -func (r *gormDeviceTemplateRepository) FindByID(id uint) (*models.DeviceTemplate, error) { +func (r *gormDeviceTemplateRepository) FindByID(ctx context.Context, id uint) (*models.DeviceTemplate, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByID") var deviceTemplate models.DeviceTemplate - if err := r.db.First(&deviceTemplate, id).Error; err != nil { + if err := r.db.WithContext(repoCtx).First(&deviceTemplate, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fmt.Errorf("设备模板未找到: %w", err) } @@ -47,9 +53,10 @@ func (r *gormDeviceTemplateRepository) FindByID(id uint) (*models.DeviceTemplate } // FindByName 根据名称查找设备模板 -func (r *gormDeviceTemplateRepository) FindByName(name string) (*models.DeviceTemplate, error) { +func (r *gormDeviceTemplateRepository) FindByName(ctx context.Context, name string) (*models.DeviceTemplate, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByName") var deviceTemplate models.DeviceTemplate - if err := r.db.Where("name = ?", name).First(&deviceTemplate).Error; err != nil { + if err := r.db.WithContext(repoCtx).Where("name = ?", name).First(&deviceTemplate).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fmt.Errorf("设备模板未找到: %w", err) } @@ -59,31 +66,35 @@ func (r *gormDeviceTemplateRepository) FindByName(name string) (*models.DeviceTe } // ListAll 获取所有设备模板 -func (r *gormDeviceTemplateRepository) ListAll() ([]*models.DeviceTemplate, error) { +func (r *gormDeviceTemplateRepository) ListAll(ctx context.Context) ([]*models.DeviceTemplate, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListAll") var deviceTemplates []*models.DeviceTemplate - if err := r.db.Find(&deviceTemplates).Error; err != nil { + if err := r.db.WithContext(repoCtx).Find(&deviceTemplates).Error; err != nil { return nil, fmt.Errorf("获取设备模板列表失败: %w", err) } return deviceTemplates, nil } // Update 更新数据库中的设备模板 -func (r *gormDeviceTemplateRepository) Update(deviceTemplate *models.DeviceTemplate) error { - return r.db.Save(deviceTemplate).Error +func (r *gormDeviceTemplateRepository) Update(ctx context.Context, deviceTemplate *models.DeviceTemplate) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "Update") + return r.db.WithContext(repoCtx).Save(deviceTemplate).Error } // IsInUse 检查设备模板是否正在被设备使用 -func (r *gormDeviceTemplateRepository) IsInUse(id uint) (bool, error) { +func (r *gormDeviceTemplateRepository) IsInUse(ctx context.Context, id uint) (bool, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "IsInUse") var count int64 - if err := r.db.Model(&models.Device{}).Where("device_template_id = ?", id).Count(&count).Error; err != nil { + if err := r.db.WithContext(repoCtx).Model(&models.Device{}).Where("device_template_id = ?", id).Count(&count).Error; err != nil { return false, fmt.Errorf("检查设备模板使用情况失败: %w", err) } return count > 0, nil } // Delete 软删除数据库中的设备模板 -func (r *gormDeviceTemplateRepository) Delete(id uint) error { - if err := r.db.Delete(&models.DeviceTemplate{}, id).Error; err != nil { +func (r *gormDeviceTemplateRepository) Delete(ctx context.Context, id uint) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "Delete") + if err := r.db.WithContext(repoCtx).Delete(&models.DeviceTemplate{}, id).Error; err != nil { return fmt.Errorf("删除设备模板失败: %w", err) } return nil diff --git a/internal/infra/repository/execution_log_repository.go b/internal/infra/repository/execution_log_repository.go index beac35e..bd4a1fb 100644 --- a/internal/infra/repository/execution_log_repository.go +++ b/internal/infra/repository/execution_log_repository.go @@ -1,10 +1,13 @@ package repository import ( + "context" "errors" "time" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/gorm" ) @@ -30,61 +33,63 @@ type TaskExecutionLogListOptions struct { // ExecutionLogRepository 定义了与执行日志交互的接口。 type ExecutionLogRepository interface { // --- Existing methods --- - UpdateTaskExecutionLogStatusByIDs(logIDs []uint, status models.ExecutionStatus) error - UpdateTaskExecutionLogStatus(logID uint, status models.ExecutionStatus) error - CreateTaskExecutionLog(log *models.TaskExecutionLog) error - CreatePlanExecutionLog(log *models.PlanExecutionLog) error - UpdatePlanExecutionLog(log *models.PlanExecutionLog) error - CreateTaskExecutionLogsInBatch(logs []*models.TaskExecutionLog) error - UpdateTaskExecutionLog(log *models.TaskExecutionLog) error - FindTaskExecutionLogByID(id uint) (*models.TaskExecutionLog, error) + UpdateTaskExecutionLogStatusByIDs(ctx context.Context, logIDs []uint, status models.ExecutionStatus) error + UpdateTaskExecutionLogStatus(ctx context.Context, logID uint, status models.ExecutionStatus) error + CreateTaskExecutionLog(ctx context.Context, log *models.TaskExecutionLog) error + CreatePlanExecutionLog(ctx context.Context, log *models.PlanExecutionLog) error + UpdatePlanExecutionLog(ctx context.Context, log *models.PlanExecutionLog) error + CreateTaskExecutionLogsInBatch(ctx context.Context, logs []*models.TaskExecutionLog) error + UpdateTaskExecutionLog(ctx context.Context, log *models.TaskExecutionLog) error + FindTaskExecutionLogByID(ctx context.Context, id uint) (*models.TaskExecutionLog, error) // UpdatePlanExecutionLogStatus 更新计划执行日志的状态 - UpdatePlanExecutionLogStatus(logID uint, status models.ExecutionStatus) error + UpdatePlanExecutionLogStatus(ctx context.Context, logID uint, status models.ExecutionStatus) error // UpdatePlanExecutionLogsStatusByIDs 批量更新计划执行日志的状态 - UpdatePlanExecutionLogsStatusByIDs(logIDs []uint, status models.ExecutionStatus) error + UpdatePlanExecutionLogsStatusByIDs(ctx context.Context, logIDs []uint, status models.ExecutionStatus) error // FindIncompletePlanExecutionLogs 查找所有未完成的计划执行日志 - FindIncompletePlanExecutionLogs() ([]models.PlanExecutionLog, error) + FindIncompletePlanExecutionLogs(ctx context.Context) ([]models.PlanExecutionLog, error) // FindInProgressPlanExecutionLogByPlanID 根据 PlanID 查找正在进行的计划执行日志 - FindInProgressPlanExecutionLogByPlanID(planID uint) (*models.PlanExecutionLog, error) + FindInProgressPlanExecutionLogByPlanID(ctx context.Context, planID uint) (*models.PlanExecutionLog, error) // FindIncompleteTaskExecutionLogsByPlanLogID 根据计划日志ID查找所有未完成的任务日志 - FindIncompleteTaskExecutionLogsByPlanLogID(planLogID uint) ([]models.TaskExecutionLog, error) + FindIncompleteTaskExecutionLogsByPlanLogID(ctx context.Context, planLogID uint) ([]models.TaskExecutionLog, error) // FailAllIncompletePlanExecutionLogs 将所有状态为 ExecutionStatusStarted 和 ExecutionStatusWaiting 的计划状态都修改为 ExecutionStatusFailed - FailAllIncompletePlanExecutionLogs() error + FailAllIncompletePlanExecutionLogs(ctx context.Context) error // CancelAllIncompleteTaskExecutionLogs 将所有状态为 ExecutionStatusStarted 和 ExecutionStatusWaiting 的任务状态修改为 ExecutionStatusCancelled - CancelAllIncompleteTaskExecutionLogs() error + CancelAllIncompleteTaskExecutionLogs(ctx context.Context) error // FindPlanExecutionLogByID 根据ID查找计划执行日志 - FindPlanExecutionLogByID(id uint) (*models.PlanExecutionLog, error) + FindPlanExecutionLogByID(ctx context.Context, id uint) (*models.PlanExecutionLog, error) // CountIncompleteTasksByPlanLogID 计算一个计划执行中未完成的任务数量 - CountIncompleteTasksByPlanLogID(planLogID uint) (int64, error) + CountIncompleteTasksByPlanLogID(ctx context.Context, planLogID uint) (int64, error) // FailPlanExecution 将指定的计划执行标记为失败 - FailPlanExecution(planLogID uint, errorMessage string) error + FailPlanExecution(ctx context.Context, planLogID uint, errorMessage string) error // CancelIncompleteTasksByPlanLogID 取消一个计划执行中的所有未完成任务 - CancelIncompleteTasksByPlanLogID(planLogID uint, reason string) error + CancelIncompleteTasksByPlanLogID(ctx context.Context, planLogID uint, reason string) error // --- New methods --- - ListPlanExecutionLogs(opts PlanExecutionLogListOptions, page, pageSize int) ([]models.PlanExecutionLog, int64, error) - ListTaskExecutionLogs(opts TaskExecutionLogListOptions, page, pageSize int) ([]models.TaskExecutionLog, int64, error) + ListPlanExecutionLogs(ctx context.Context, opts PlanExecutionLogListOptions, page, pageSize int) ([]models.PlanExecutionLog, int64, error) + ListTaskExecutionLogs(ctx context.Context, opts TaskExecutionLogListOptions, page, pageSize int) ([]models.TaskExecutionLog, int64, error) } // gormExecutionLogRepository 是使用 GORM 的具体实现。 type gormExecutionLogRepository struct { - db *gorm.DB + ctx context.Context + db *gorm.DB } // NewGormExecutionLogRepository 创建一个新的执行日志仓库。 -func NewGormExecutionLogRepository(db *gorm.DB) ExecutionLogRepository { - return &gormExecutionLogRepository{db: db} +func NewGormExecutionLogRepository(ctx context.Context, db *gorm.DB) ExecutionLogRepository { + return &gormExecutionLogRepository{ctx: ctx, db: db} } // ListPlanExecutionLogs 实现了分页和过滤查询计划执行日志的功能 -func (r *gormExecutionLogRepository) ListPlanExecutionLogs(opts PlanExecutionLogListOptions, page, pageSize int) ([]models.PlanExecutionLog, int64, error) { +func (r *gormExecutionLogRepository) ListPlanExecutionLogs(ctx context.Context, opts PlanExecutionLogListOptions, page, pageSize int) ([]models.PlanExecutionLog, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListPlanExecutionLogs") if page <= 0 || pageSize <= 0 { return nil, 0, ErrInvalidPagination } @@ -92,7 +97,7 @@ func (r *gormExecutionLogRepository) ListPlanExecutionLogs(opts PlanExecutionLog var results []models.PlanExecutionLog var total int64 - query := r.db.Model(&models.PlanExecutionLog{}) + query := r.db.WithContext(repoCtx).Model(&models.PlanExecutionLog{}) if opts.PlanID != nil { query = query.Where("plan_id = ?", *opts.PlanID) @@ -124,7 +129,8 @@ func (r *gormExecutionLogRepository) ListPlanExecutionLogs(opts PlanExecutionLog } // ListTaskExecutionLogs 实现了分页和过滤查询任务执行日志的功能 -func (r *gormExecutionLogRepository) ListTaskExecutionLogs(opts TaskExecutionLogListOptions, page, pageSize int) ([]models.TaskExecutionLog, int64, error) { +func (r *gormExecutionLogRepository) ListTaskExecutionLogs(ctx context.Context, opts TaskExecutionLogListOptions, page, pageSize int) ([]models.TaskExecutionLog, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListTaskExecutionLogs") if page <= 0 || pageSize <= 0 { return nil, 0, ErrInvalidPagination } @@ -132,7 +138,7 @@ func (r *gormExecutionLogRepository) ListTaskExecutionLogs(opts TaskExecutionLog var results []models.TaskExecutionLog var total int64 - query := r.db.Model(&models.TaskExecutionLog{}) + query := r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}) if opts.PlanExecutionLogID != nil { query = query.Where("plan_execution_log_id = ?", *opts.PlanExecutionLogID) @@ -169,56 +175,64 @@ func (r *gormExecutionLogRepository) ListTaskExecutionLogs(opts TaskExecutionLog // --- Existing method implementations --- -func (r *gormExecutionLogRepository) UpdateTaskExecutionLogStatusByIDs(logIDs []uint, status models.ExecutionStatus) error { +func (r *gormExecutionLogRepository) UpdateTaskExecutionLogStatusByIDs(ctx context.Context, logIDs []uint, status models.ExecutionStatus) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateTaskExecutionLogStatusByIDs") if len(logIDs) == 0 { return nil } - return r.db.Model(&models.TaskExecutionLog{}).Where("id IN ?", logIDs).Update("status", status).Error + return r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}).Where("id IN ?", logIDs).Update("status", status).Error } -func (r *gormExecutionLogRepository) UpdateTaskExecutionLogStatus(logID uint, status models.ExecutionStatus) error { - return r.db.Model(&models.TaskExecutionLog{}).Where("id = ?", logID).Update("status", status).Error +func (r *gormExecutionLogRepository) UpdateTaskExecutionLogStatus(ctx context.Context, logID uint, status models.ExecutionStatus) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateTaskExecutionLogStatus") + return r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}).Where("id = ?", logID).Update("status", status).Error } -func (r *gormExecutionLogRepository) CreateTaskExecutionLog(log *models.TaskExecutionLog) error { - return r.db.Create(log).Error +func (r *gormExecutionLogRepository) CreateTaskExecutionLog(ctx context.Context, log *models.TaskExecutionLog) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreateTaskExecutionLog") + return r.db.WithContext(repoCtx).Create(log).Error } // CreatePlanExecutionLog 为一次计划执行创建一条新的日志条目。 -func (r *gormExecutionLogRepository) CreatePlanExecutionLog(log *models.PlanExecutionLog) error { - return r.db.Create(log).Error +func (r *gormExecutionLogRepository) CreatePlanExecutionLog(ctx context.Context, log *models.PlanExecutionLog) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePlanExecutionLog") + return r.db.WithContext(repoCtx).Create(log).Error } // UpdatePlanExecutionLog 使用 Updates 方法更新一个计划执行日志。 // GORM 的 Updates 传入 struct 时,只会更新非零值字段。 // 在这里,我们期望传入的对象一定包含一个有效的 ID。 -func (r *gormExecutionLogRepository) UpdatePlanExecutionLog(log *models.PlanExecutionLog) error { - return r.db.Updates(log).Error +func (r *gormExecutionLogRepository) UpdatePlanExecutionLog(ctx context.Context, log *models.PlanExecutionLog) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePlanExecutionLog") + return r.db.WithContext(repoCtx).Updates(log).Error } // CreateTaskExecutionLogsInBatch 在一次数据库调用中创建多个任务执行日志条目。 // 这是“预写日志”步骤的关键。 -func (r *gormExecutionLogRepository) CreateTaskExecutionLogsInBatch(logs []*models.TaskExecutionLog) error { - if len(logs) == 0 { +func (r *gormExecutionLogRepository) CreateTaskExecutionLogsInBatch(ctx context.Context, executionLogs []*models.TaskExecutionLog) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreateTaskExecutionLogsInBatch") + if len(executionLogs) == 0 { return nil } // GORM 的 CreateTx 传入一个切片指针会执行批量插入。 - return r.db.Create(&logs).Error + return r.db.WithContext(repoCtx).Create(&executionLogs).Error } // UpdateTaskExecutionLog 使用 Updates 方法更新一个任务执行日志。 // GORM 的 Updates 传入 struct 时,只会更新非零值字段。 // 这种方式代码更直观,上层服务可以直接修改模型对象后进行保存。 -func (r *gormExecutionLogRepository) UpdateTaskExecutionLog(log *models.TaskExecutionLog) error { - return r.db.Updates(log).Error +func (r *gormExecutionLogRepository) UpdateTaskExecutionLog(ctx context.Context, log *models.TaskExecutionLog) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateTaskExecutionLog") + return r.db.WithContext(repoCtx).Updates(log).Error } // FindTaskExecutionLogByID 根据 ID 查找单个任务执行日志。 // 它会预加载关联的 Task 信息。 -func (r *gormExecutionLogRepository) FindTaskExecutionLogByID(id uint) (*models.TaskExecutionLog, error) { +func (r *gormExecutionLogRepository) FindTaskExecutionLogByID(ctx context.Context, id uint) (*models.TaskExecutionLog, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindTaskExecutionLogByID") var log models.TaskExecutionLog // 使用 Preload("Task") 来确保关联的任务信息被一并加载 - err := r.db.Preload("Task").First(&log, id).Error + err := r.db.WithContext(repoCtx).Preload("Task").First(&log, id).Error if err != nil { return nil, err } @@ -226,29 +240,33 @@ func (r *gormExecutionLogRepository) FindTaskExecutionLogByID(id uint) (*models. } // UpdatePlanExecutionLogStatus 更新计划执行日志的状态 -func (r *gormExecutionLogRepository) UpdatePlanExecutionLogStatus(logID uint, status models.ExecutionStatus) error { - return r.db.Model(&models.PlanExecutionLog{}).Where("id = ?", logID).Update("status", status).Error +func (r *gormExecutionLogRepository) UpdatePlanExecutionLogStatus(ctx context.Context, logID uint, status models.ExecutionStatus) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePlanExecutionLogStatus") + return r.db.WithContext(repoCtx).Model(&models.PlanExecutionLog{}).Where("id = ?", logID).Update("status", status).Error } // UpdatePlanExecutionLogsStatusByIDs 批量更新计划执行日志的状态 -func (r *gormExecutionLogRepository) UpdatePlanExecutionLogsStatusByIDs(logIDs []uint, status models.ExecutionStatus) error { +func (r *gormExecutionLogRepository) UpdatePlanExecutionLogsStatusByIDs(ctx context.Context, logIDs []uint, status models.ExecutionStatus) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePlanExecutionLogsStatusByIDs") if len(logIDs) == 0 { return nil } - return r.db.Model(&models.PlanExecutionLog{}).Where("id IN ?", logIDs).Update("status", status).Error + return r.db.WithContext(repoCtx).Model(&models.PlanExecutionLog{}).Where("id IN ?", logIDs).Update("status", status).Error } // FindIncompletePlanExecutionLogs 查找所有未完成的计划执行日志 -func (r *gormExecutionLogRepository) FindIncompletePlanExecutionLogs() ([]models.PlanExecutionLog, error) { +func (r *gormExecutionLogRepository) FindIncompletePlanExecutionLogs(ctx context.Context) ([]models.PlanExecutionLog, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindIncompletePlanExecutionLogs") var logs []models.PlanExecutionLog - err := r.db.Where("status = ? OR status = ?", models.ExecutionStatusStarted, models.ExecutionStatusWaiting).Find(&logs).Error + err := r.db.WithContext(repoCtx).Where("status = ? OR status = ?", models.ExecutionStatusStarted, models.ExecutionStatusWaiting).Find(&logs).Error return logs, err } // FindInProgressPlanExecutionLogByPlanID 根据 PlanID 查找正在进行的计划执行日志 -func (r *gormExecutionLogRepository) FindInProgressPlanExecutionLogByPlanID(planID uint) (*models.PlanExecutionLog, error) { +func (r *gormExecutionLogRepository) FindInProgressPlanExecutionLogByPlanID(ctx context.Context, planID uint) (*models.PlanExecutionLog, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindInProgressPlanExecutionLogByPlanID") var log models.PlanExecutionLog - err := r.db.Where("plan_id = ? AND status = ?", planID, models.ExecutionStatusStarted).First(&log).Error + err := r.db.WithContext(repoCtx).Where("plan_id = ? AND status = ?", planID, models.ExecutionStatusStarted).First(&log).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { // 未找到不是一个需要上报的错误,代表计划当前没有在运行 @@ -261,31 +279,35 @@ func (r *gormExecutionLogRepository) FindInProgressPlanExecutionLogByPlanID(plan } // FindIncompleteTaskExecutionLogsByPlanLogID 根据计划日志ID查找所有未完成的任务日志 -func (r *gormExecutionLogRepository) FindIncompleteTaskExecutionLogsByPlanLogID(planLogID uint) ([]models.TaskExecutionLog, error) { +func (r *gormExecutionLogRepository) FindIncompleteTaskExecutionLogsByPlanLogID(ctx context.Context, planLogID uint) ([]models.TaskExecutionLog, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindIncompleteTaskExecutionLogsByPlanLogID") var logs []models.TaskExecutionLog - err := r.db.Where("plan_execution_log_id = ? AND (status = ? OR status = ?)", + err := r.db.WithContext(repoCtx).Where("plan_execution_log_id = ? AND (status = ? OR status = ?)", planLogID, models.ExecutionStatusWaiting, models.ExecutionStatusStarted).Find(&logs).Error return logs, err } // FailAllIncompletePlanExecutionLogs 将所有状态为 ExecutionStatusStarted 和 ExecutionStatusWaiting 的计划状态都修改为 ExecutionStatusFailed -func (r *gormExecutionLogRepository) FailAllIncompletePlanExecutionLogs() error { - return r.db.Model(&models.PlanExecutionLog{}). +func (r *gormExecutionLogRepository) FailAllIncompletePlanExecutionLogs(ctx context.Context) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FailAllIncompletePlanExecutionLogs") + return r.db.WithContext(repoCtx).Model(&models.PlanExecutionLog{}). Where("status IN (?, ?)", models.ExecutionStatusStarted, models.ExecutionStatusWaiting). Updates(map[string]interface{}{"status": models.ExecutionStatusFailed, "ended_at": time.Now(), "error": "系统中断"}).Error } // CancelAllIncompleteTaskExecutionLogs 将所有状态为 ExecutionStatusStarted 和 ExecutionStatusWaiting 的任务状态修改为 ExecutionStatusCancelled -func (r *gormExecutionLogRepository) CancelAllIncompleteTaskExecutionLogs() error { - return r.db.Model(&models.TaskExecutionLog{}). +func (r *gormExecutionLogRepository) CancelAllIncompleteTaskExecutionLogs(ctx context.Context) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CancelAllIncompleteTaskExecutionLogs") + return r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}). Where("status IN (?, ?)", models.ExecutionStatusStarted, models.ExecutionStatusWaiting). Updates(map[string]interface{}{"status": models.ExecutionStatusCancelled, "ended_at": time.Now(), "output": "系统中断"}).Error } // FindPlanExecutionLogByID 根据ID查找计划执行日志 -func (r *gormExecutionLogRepository) FindPlanExecutionLogByID(id uint) (*models.PlanExecutionLog, error) { +func (r *gormExecutionLogRepository) FindPlanExecutionLogByID(ctx context.Context, id uint) (*models.PlanExecutionLog, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindPlanExecutionLogByID") var log models.PlanExecutionLog - err := r.db.First(&log, id).Error + err := r.db.WithContext(repoCtx).First(&log, id).Error if err != nil { return nil, err } @@ -293,9 +315,10 @@ func (r *gormExecutionLogRepository) FindPlanExecutionLogByID(id uint) (*models. } // CountIncompleteTasksByPlanLogID 计算一个计划执行中未完成的任务数量 -func (r *gormExecutionLogRepository) CountIncompleteTasksByPlanLogID(planLogID uint) (int64, error) { +func (r *gormExecutionLogRepository) CountIncompleteTasksByPlanLogID(ctx context.Context, planLogID uint) (int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CountIncompleteTasksByPlanLogID") var count int64 - err := r.db.Model(&models.TaskExecutionLog{}). + err := r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}). Where("plan_execution_log_id = ? AND status IN (?, ?)", planLogID, models.ExecutionStatusWaiting, models.ExecutionStatusStarted). Count(&count).Error @@ -303,8 +326,9 @@ func (r *gormExecutionLogRepository) CountIncompleteTasksByPlanLogID(planLogID u } // FailPlanExecution 将指定的计划执行标记为失败 -func (r *gormExecutionLogRepository) FailPlanExecution(planLogID uint, errorMessage string) error { - return r.db.Model(&models.PlanExecutionLog{}). +func (r *gormExecutionLogRepository) FailPlanExecution(ctx context.Context, planLogID uint, errorMessage string) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FailPlanExecution") + return r.db.WithContext(repoCtx).Model(&models.PlanExecutionLog{}). Where("id = ?", planLogID). Updates(map[string]interface{}{ "status": models.ExecutionStatusFailed, @@ -314,8 +338,9 @@ func (r *gormExecutionLogRepository) FailPlanExecution(planLogID uint, errorMess } // CancelIncompleteTasksByPlanLogID 取消一个计划执行中的所有未完成任务 -func (r *gormExecutionLogRepository) CancelIncompleteTasksByPlanLogID(planLogID uint, reason string) error { - return r.db.Model(&models.TaskExecutionLog{}). +func (r *gormExecutionLogRepository) CancelIncompleteTasksByPlanLogID(ctx context.Context, planLogID uint, reason string) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CancelIncompleteTasksByPlanLogID") + return r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}). Where("plan_execution_log_id = ? AND status IN (?, ?)", planLogID, models.ExecutionStatusWaiting, models.ExecutionStatusStarted). Updates(map[string]interface{}{ diff --git a/internal/infra/repository/medication_log_repository.go b/internal/infra/repository/medication_log_repository.go index 1d91de0..6db863d 100644 --- a/internal/infra/repository/medication_log_repository.go +++ b/internal/infra/repository/medication_log_repository.go @@ -1,9 +1,12 @@ package repository import ( + "context" "time" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/gorm" ) @@ -20,27 +23,30 @@ type MedicationLogListOptions struct { // MedicationLogRepository 定义了与群体用药日志模型相关的数据库操作接口。 type MedicationLogRepository interface { - CreateMedicationLog(log *models.MedicationLog) error - ListMedicationLogs(opts MedicationLogListOptions, page, pageSize int) ([]models.MedicationLog, int64, error) + CreateMedicationLog(ctx context.Context, log *models.MedicationLog) error + ListMedicationLogs(ctx context.Context, opts MedicationLogListOptions, page, pageSize int) ([]models.MedicationLog, int64, error) } // gormMedicationLogRepository 是 MedicationLogRepository 接口的 GORM 实现。 type gormMedicationLogRepository struct { - db *gorm.DB + ctx context.Context + db *gorm.DB } // NewGormMedicationLogRepository 创建一个新的 MedicationLogRepository GORM 实现实例。 -func NewGormMedicationLogRepository(db *gorm.DB) MedicationLogRepository { - return &gormMedicationLogRepository{db: db} +func NewGormMedicationLogRepository(ctx context.Context, db *gorm.DB) MedicationLogRepository { + return &gormMedicationLogRepository{ctx: ctx, db: db} } // CreateMedicationLog 创建一条新的群体用药日志记录 -func (r *gormMedicationLogRepository) CreateMedicationLog(log *models.MedicationLog) error { - return r.db.Create(log).Error +func (r *gormMedicationLogRepository) CreateMedicationLog(ctx context.Context, log *models.MedicationLog) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreateMedicationLog") + return r.db.WithContext(repoCtx).Create(log).Error } // ListMedicationLogs 实现了分页和过滤查询用药记录的功能 -func (r *gormMedicationLogRepository) ListMedicationLogs(opts MedicationLogListOptions, page, pageSize int) ([]models.MedicationLog, int64, error) { +func (r *gormMedicationLogRepository) ListMedicationLogs(ctx context.Context, opts MedicationLogListOptions, page, pageSize int) ([]models.MedicationLog, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListMedicationLogs") if page <= 0 || pageSize <= 0 { return nil, 0, ErrInvalidPagination } @@ -48,7 +54,7 @@ func (r *gormMedicationLogRepository) ListMedicationLogs(opts MedicationLogListO var results []models.MedicationLog var total int64 - query := r.db.Model(&models.MedicationLog{}) + query := r.db.WithContext(repoCtx).Model(&models.MedicationLog{}) if opts.PigBatchID != nil { query = query.Where("pig_batch_id = ?", *opts.PigBatchID) diff --git a/internal/infra/repository/notification_repository.go b/internal/infra/repository/notification_repository.go index 5843502..4055966 100644 --- a/internal/infra/repository/notification_repository.go +++ b/internal/infra/repository/notification_repository.go @@ -1,10 +1,13 @@ package repository import ( + "context" "time" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "git.huangwc.com/pig/pig-farm-controller/internal/infra/notify" + "go.uber.org/zap/zapcore" "gorm.io/gorm" ) @@ -23,44 +26,49 @@ type NotificationListOptions struct { // NotificationRepository 定义了与通知记录相关的数据库操作接口。 type NotificationRepository interface { // Create 将一条新的通知记录插入数据库。 - Create(notification *models.Notification) error + Create(ctx context.Context, notification *models.Notification) error // CreateInTx 在给定的事务中插入一条新的通知记录。 - CreateInTx(tx *gorm.DB, notification *models.Notification) error + CreateInTx(ctx context.Context, tx *gorm.DB, notification *models.Notification) error // BatchCreate 批量插入多条通知记录。 - BatchCreate(notifications []*models.Notification) error + BatchCreate(ctx context.Context, notifications []*models.Notification) error // List 支持分页和过滤的通知列表查询。 // 返回通知列表、总记录数和错误。 - List(opts NotificationListOptions, page, pageSize int) ([]models.Notification, int64, error) + List(ctx context.Context, opts NotificationListOptions, page, pageSize int) ([]models.Notification, int64, error) } // gormNotificationRepository 是 NotificationRepository 的 GORM 实现。 type gormNotificationRepository struct { - db *gorm.DB + ctx context.Context + db *gorm.DB } // NewGormNotificationRepository 创建一个新的 NotificationRepository GORM 实现实例。 -func NewGormNotificationRepository(db *gorm.DB) NotificationRepository { - return &gormNotificationRepository{db: db} +func NewGormNotificationRepository(ctx context.Context, db *gorm.DB) NotificationRepository { + return &gormNotificationRepository{ctx: ctx, db: db} } // Create 将一条新的通知记录插入数据库。 -func (r *gormNotificationRepository) Create(notification *models.Notification) error { - return r.db.Create(notification).Error +func (r *gormNotificationRepository) Create(ctx context.Context, notification *models.Notification) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "Create") + return r.db.WithContext(repoCtx).Create(notification).Error } // CreateInTx 在给定的事务中插入一条新的通知记录。 -func (r *gormNotificationRepository) CreateInTx(tx *gorm.DB, notification *models.Notification) error { - return tx.Create(notification).Error +func (r *gormNotificationRepository) CreateInTx(ctx context.Context, tx *gorm.DB, notification *models.Notification) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreateInTx") + return tx.WithContext(repoCtx).Create(notification).Error } // BatchCreate 批量插入多条通知记录。 -func (r *gormNotificationRepository) BatchCreate(notifications []*models.Notification) error { +func (r *gormNotificationRepository) BatchCreate(ctx context.Context, notifications []*models.Notification) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "BatchCreate") // GORM 的 Create 方法在传入切片时会自动进行批量插入 - return r.db.Create(¬ifications).Error + return r.db.WithContext(repoCtx).Create(¬ifications).Error } // List 实现了分页和过滤查询通知记录的功能 -func (r *gormNotificationRepository) List(opts NotificationListOptions, page, pageSize int) ([]models.Notification, int64, error) { +func (r *gormNotificationRepository) List(ctx context.Context, opts NotificationListOptions, page, pageSize int) ([]models.Notification, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "List") // --- 校验分页参数 --- if page <= 0 || pageSize <= 0 { return nil, 0, ErrInvalidPagination // 复用已定义的错误 @@ -69,7 +77,7 @@ func (r *gormNotificationRepository) List(opts NotificationListOptions, page, pa var results []models.Notification var total int64 - query := r.db.Model(&models.Notification{}) + query := r.db.WithContext(repoCtx).Model(&models.Notification{}) // --- 应用过滤条件 --- if opts.UserID != nil { diff --git a/internal/infra/repository/pending_collection_repository.go b/internal/infra/repository/pending_collection_repository.go index 68adf05..77ab3ad 100644 --- a/internal/infra/repository/pending_collection_repository.go +++ b/internal/infra/repository/pending_collection_repository.go @@ -1,9 +1,12 @@ package repository import ( + "context" "time" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/gorm" ) @@ -19,48 +22,52 @@ type PendingCollectionListOptions struct { // PendingCollectionRepository 定义了与待采集请求相关的数据库操作接口。 type PendingCollectionRepository interface { // Create 创建一个新的待采集请求。 - Create(req *models.PendingCollection) error + Create(ctx context.Context, req *models.PendingCollection) error // FindByCorrelationID 根据关联ID查找一个待采集请求。 - FindByCorrelationID(correlationID string) (*models.PendingCollection, error) + FindByCorrelationID(ctx context.Context, correlationID string) (*models.PendingCollection, error) // UpdateStatusToFulfilled 将指定关联ID的请求状态更新为“已完成”。 - UpdateStatusToFulfilled(correlationID string, fulfilledAt time.Time) error + UpdateStatusToFulfilled(ctx context.Context, correlationID string, fulfilledAt time.Time) error // MarkAllPendingAsTimedOut 将所有“待处理”请求更新为“已超时”。 - MarkAllPendingAsTimedOut() (int64, error) + MarkAllPendingAsTimedOut(ctx context.Context) (int64, error) // List 支持分页和过滤的列表查询 - List(opts PendingCollectionListOptions, page, pageSize int) ([]models.PendingCollection, int64, error) + List(ctx context.Context, opts PendingCollectionListOptions, page, pageSize int) ([]models.PendingCollection, int64, error) } // gormPendingCollectionRepository 是 PendingCollectionRepository 的 GORM 实现。 type gormPendingCollectionRepository struct { - db *gorm.DB + ctx context.Context + db *gorm.DB } // NewGormPendingCollectionRepository 创建一个新的 PendingCollectionRepository GORM 实现实例。 -func NewGormPendingCollectionRepository(db *gorm.DB) PendingCollectionRepository { - return &gormPendingCollectionRepository{db: db} +func NewGormPendingCollectionRepository(ctx context.Context, db *gorm.DB) PendingCollectionRepository { + return &gormPendingCollectionRepository{ctx: ctx, db: db} } // Create 创建一个新的待采集请求。 -func (r *gormPendingCollectionRepository) Create(req *models.PendingCollection) error { - return r.db.Create(req).Error +func (r *gormPendingCollectionRepository) Create(ctx context.Context, req *models.PendingCollection) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "Create") + return r.db.WithContext(repoCtx).Create(req).Error } // FindByCorrelationID 根据关联ID查找一个待采集请求。 -func (r *gormPendingCollectionRepository) FindByCorrelationID(correlationID string) (*models.PendingCollection, error) { +func (r *gormPendingCollectionRepository) FindByCorrelationID(ctx context.Context, correlationID string) (*models.PendingCollection, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByCorrelationID") var req models.PendingCollection - if err := r.db.First(&req, "correlation_id = ?", correlationID).Error; err != nil { + if err := r.db.WithContext(repoCtx).First(&req, "correlation_id = ?", correlationID).Error; err != nil { return nil, err } return &req, nil } // UpdateStatusToFulfilled 将指定关联ID的请求状态更新为“已完成”。 -func (r *gormPendingCollectionRepository) UpdateStatusToFulfilled(correlationID string, fulfilledAt time.Time) error { - return r.db.Model(&models.PendingCollection{}). +func (r *gormPendingCollectionRepository) UpdateStatusToFulfilled(ctx context.Context, correlationID string, fulfilledAt time.Time) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateStatusToFulfilled") + return r.db.WithContext(repoCtx).Model(&models.PendingCollection{}). Where("correlation_id = ?", correlationID). Updates(map[string]interface{}{ "status": models.PendingStatusFulfilled, @@ -70,8 +77,9 @@ func (r *gormPendingCollectionRepository) UpdateStatusToFulfilled(correlationID // MarkAllPendingAsTimedOut 将所有状态为 'pending' 的记录更新为 'timed_out'。 // 返回被更新的记录数量和错误。 -func (r *gormPendingCollectionRepository) MarkAllPendingAsTimedOut() (int64, error) { - result := r.db.Model(&models.PendingCollection{}). +func (r *gormPendingCollectionRepository) MarkAllPendingAsTimedOut(ctx context.Context) (int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "MarkAllPendingAsTimedOut") + result := r.db.WithContext(repoCtx).Model(&models.PendingCollection{}). Where("status = ?", models.PendingStatusPending). Update("status", models.PendingStatusTimedOut) @@ -79,7 +87,8 @@ func (r *gormPendingCollectionRepository) MarkAllPendingAsTimedOut() (int64, err } // List 实现了分页和过滤查询待采集请求的功能 -func (r *gormPendingCollectionRepository) List(opts PendingCollectionListOptions, page, pageSize int) ([]models.PendingCollection, int64, error) { +func (r *gormPendingCollectionRepository) List(ctx context.Context, opts PendingCollectionListOptions, page, pageSize int) ([]models.PendingCollection, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "List") if page <= 0 || pageSize <= 0 { return nil, 0, ErrInvalidPagination } @@ -87,7 +96,7 @@ func (r *gormPendingCollectionRepository) List(opts PendingCollectionListOptions var results []models.PendingCollection var total int64 - query := r.db.Model(&models.PendingCollection{}) + query := r.db.WithContext(repoCtx).Model(&models.PendingCollection{}) if opts.DeviceID != nil { query = query.Where("device_id = ?", *opts.DeviceID) diff --git a/internal/infra/repository/pending_task_repository.go b/internal/infra/repository/pending_task_repository.go index ceae6bb..06824df 100644 --- a/internal/infra/repository/pending_task_repository.go +++ b/internal/infra/repository/pending_task_repository.go @@ -1,63 +1,69 @@ package repository 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" "gorm.io/gorm/clause" ) // PendingTaskRepository 定义了与待执行任务队列交互的接口。 type PendingTaskRepository interface { - FindAllPendingTasks() ([]models.PendingTask, error) - FindPendingTriggerByPlanID(planID uint) (*models.PendingTask, error) - DeletePendingTasksByIDs(ids []uint) error - CreatePendingTask(task *models.PendingTask) error - CreatePendingTasksInBatch(tasks []*models.PendingTask) error + FindAllPendingTasks(ctx context.Context) ([]models.PendingTask, error) + FindPendingTriggerByPlanID(ctx context.Context, planID uint) (*models.PendingTask, error) + DeletePendingTasksByIDs(ctx context.Context, ids []uint) error + CreatePendingTask(ctx context.Context, task *models.PendingTask) error + CreatePendingTasksInBatch(ctx context.Context, tasks []*models.PendingTask) error // UpdatePendingTaskExecuteAt 更新指定待执行任务的执行时间 - UpdatePendingTaskExecuteAt(id uint, executeAt time.Time) error + UpdatePendingTaskExecuteAt(ctx context.Context, id uint, executeAt time.Time) error // ClearAllPendingTasks 清空所有待执行任务 - ClearAllPendingTasks() error + ClearAllPendingTasks(ctx context.Context) error // ClaimNextAvailableTask 原子地认领下一个可用的任务。 // 它会同时返回被认领任务对应的日志对象,以及被删除的待办任务对象的内存副本。 - ClaimNextAvailableTask(excludePlanIDs []uint) (*models.TaskExecutionLog, *models.PendingTask, error) + ClaimNextAvailableTask(ctx context.Context, excludePlanIDs []uint) (*models.TaskExecutionLog, *models.PendingTask, error) // RequeueTask 安全地将一个任务重新放回队列。 - RequeueTask(originalPendingTask *models.PendingTask) error + RequeueTask(ctx context.Context, originalPendingTask *models.PendingTask) error // FindPendingTasksByTaskLogIDs 根据 TaskExecutionLogID 列表查找对应的待执行任务 - FindPendingTasksByTaskLogIDs(taskLogIDs []uint) ([]models.PendingTask, error) + FindPendingTasksByTaskLogIDs(ctx context.Context, taskLogIDs []uint) ([]models.PendingTask, error) // DeletePendingTasksByPlanLogID 删除与指定计划执行日志ID相关的所有待执行任务 - DeletePendingTasksByPlanLogID(planLogID uint) error + DeletePendingTasksByPlanLogID(ctx context.Context, planLogID uint) error } // gormPendingTaskRepository 是使用 GORM 的具体实现。 type gormPendingTaskRepository struct { - db *gorm.DB + ctx context.Context + db *gorm.DB } // NewGormPendingTaskRepository 创建一个新的待执行任务队列仓库。 -func NewGormPendingTaskRepository(db *gorm.DB) PendingTaskRepository { - return &gormPendingTaskRepository{db: db} +func NewGormPendingTaskRepository(ctx context.Context, db *gorm.DB) PendingTaskRepository { + return &gormPendingTaskRepository{ctx: ctx, db: db} } -func (r *gormPendingTaskRepository) FindAllPendingTasks() ([]models.PendingTask, error) { +func (r *gormPendingTaskRepository) FindAllPendingTasks(ctx context.Context) ([]models.PendingTask, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindAllPendingTasks") var tasks []models.PendingTask // 预加载 Task 以便后续访问 Task.PlanID // 预加载 TaskExecutionLog 以便后续访问 PlanExecutionLogID - err := r.db.Preload("Task").Preload("TaskExecutionLog").Find(&tasks).Error + err := r.db.WithContext(repoCtx).Preload("Task").Preload("TaskExecutionLog").Find(&tasks).Error return tasks, err } -func (r *gormPendingTaskRepository) FindPendingTriggerByPlanID(planID uint) (*models.PendingTask, error) { +func (r *gormPendingTaskRepository) FindPendingTriggerByPlanID(ctx context.Context, planID uint) (*models.PendingTask, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindPendingTriggerByPlanID") var pendingTask models.PendingTask // 关键修改:通过 JOIN tasks 表并查询 parameters JSON 字段来查找触发器,而不是依赖 task.plan_id - err := r.db. + err := r.db.WithContext(repoCtx). Joins("JOIN tasks ON tasks.id = pending_tasks.task_id"). Where("tasks.type = ? AND tasks.parameters->>'plan_id' = ?", models.TaskPlanAnalysis, fmt.Sprintf("%d", planID)). First(&pendingTask).Error @@ -67,42 +73,48 @@ func (r *gormPendingTaskRepository) FindPendingTriggerByPlanID(planID uint) (*mo return &pendingTask, err } -func (r *gormPendingTaskRepository) DeletePendingTasksByIDs(ids []uint) error { +func (r *gormPendingTaskRepository) DeletePendingTasksByIDs(ctx context.Context, ids []uint) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "DeletePendingTasksByIDs") if len(ids) == 0 { return nil } - return r.db.Where("id IN ?", ids).Delete(&models.PendingTask{}).Error + return r.db.WithContext(repoCtx).Where("id IN ?", ids).Delete(&models.PendingTask{}).Error } -func (r *gormPendingTaskRepository) CreatePendingTask(task *models.PendingTask) error { - return r.db.Create(task).Error +func (r *gormPendingTaskRepository) CreatePendingTask(ctx context.Context, task *models.PendingTask) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePendingTask") + return r.db.WithContext(repoCtx).Create(task).Error } // CreatePendingTasksInBatch 在一次数据库调用中创建多个待执行任务条目。 -func (r *gormPendingTaskRepository) CreatePendingTasksInBatch(tasks []*models.PendingTask) error { +func (r *gormPendingTaskRepository) CreatePendingTasksInBatch(ctx context.Context, tasks []*models.PendingTask) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePendingTasksInBatch") if len(tasks) == 0 { return nil } - return r.db.Create(&tasks).Error + return r.db.WithContext(repoCtx).Create(&tasks).Error } // UpdatePendingTaskExecuteAt 更新指定待执行任务的执行时间 -func (r *gormPendingTaskRepository) UpdatePendingTaskExecuteAt(id uint, executeAt time.Time) error { - return r.db.Model(&models.PendingTask{}).Where("id = ?", id).Update("execute_at", executeAt).Error +func (r *gormPendingTaskRepository) UpdatePendingTaskExecuteAt(ctx context.Context, id uint, executeAt time.Time) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePendingTaskExecuteAt") + return r.db.WithContext(repoCtx).Model(&models.PendingTask{}).Where("id = ?", id).Update("execute_at", executeAt).Error } // ClearAllPendingTasks 清空所有待执行任务 -func (r *gormPendingTaskRepository) ClearAllPendingTasks() error { - return r.db.Where("1 = 1").Delete(&models.PendingTask{}).Error +func (r *gormPendingTaskRepository) ClearAllPendingTasks(ctx context.Context) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ClearAllPendingTasks") + return r.db.WithContext(repoCtx).Where("1 = 1").Delete(&models.PendingTask{}).Error } // ClaimNextAvailableTask 以原子方式认领下一个可用的任务。 -func (r *gormPendingTaskRepository) ClaimNextAvailableTask(excludePlanIDs []uint) (*models.TaskExecutionLog, *models.PendingTask, error) { +func (r *gormPendingTaskRepository) ClaimNextAvailableTask(ctx context.Context, excludePlanIDs []uint) (*models.TaskExecutionLog, *models.PendingTask, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ClaimNextAvailableTask") var log models.TaskExecutionLog var pendingTask models.PendingTask - err := r.db.Transaction(func(tx *gorm.DB) error { - query := tx.Clauses(clause.Locking{Strength: "UPDATE"}). + err := r.db.WithContext(repoCtx).Transaction(func(tx *gorm.DB) error { + query := tx.WithContext(repoCtx).Clauses(clause.Locking{Strength: "UPDATE"}). Where("execute_at <= ?", time.Now()). Order("execute_at ASC") @@ -114,7 +126,7 @@ func (r *gormPendingTaskRepository) ClaimNextAvailableTask(excludePlanIDs []uint return err } - if err := tx.Unscoped().Delete(&pendingTask).Error; err != nil { + if err := tx.WithContext(repoCtx).Unscoped().Delete(&pendingTask).Error; err != nil { return err } @@ -122,12 +134,12 @@ func (r *gormPendingTaskRepository) ClaimNextAvailableTask(excludePlanIDs []uint "status": models.ExecutionStatusStarted, "started_at": time.Now(), } - if err := tx.Model(&models.TaskExecutionLog{}).Where("id = ?", pendingTask.TaskExecutionLogID).Updates(updates).Error; err != nil { + if err := tx.WithContext(repoCtx).Model(&models.TaskExecutionLog{}).Where("id = ?", pendingTask.TaskExecutionLogID).Updates(updates).Error; err != nil { return err } // 关键修改:在 Preload("Task") 时,使用 Unscoped() 来忽略 Task 的软删除状态 - if err := tx.Preload("Task", func(db *gorm.DB) *gorm.DB { + if err := tx.WithContext(repoCtx).Preload("Task", func(db *gorm.DB) *gorm.DB { return db.Unscoped() }).First(&log, pendingTask.TaskExecutionLogID).Error; err != nil { return err @@ -145,10 +157,11 @@ func (r *gormPendingTaskRepository) ClaimNextAvailableTask(excludePlanIDs []uint // RequeueTask 安全地将一个任务重新放回队列。 // 它通过将原始 PendingTask 的 ID 重置为 0,并重新创建它来实现。 -func (r *gormPendingTaskRepository) RequeueTask(originalPendingTask *models.PendingTask) error { - return r.db.Transaction(func(tx *gorm.DB) error { +func (r *gormPendingTaskRepository) RequeueTask(ctx context.Context, originalPendingTask *models.PendingTask) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "RequeueTask") + return r.db.WithContext(repoCtx).Transaction(func(tx *gorm.DB) error { // 1. 将日志状态恢复为 waiting - if err := tx.Model(&models.TaskExecutionLog{}).Where("id = ?", originalPendingTask.TaskExecutionLogID).Update("status", models.ExecutionStatusWaiting).Error; err != nil { + if err := tx.WithContext(repoCtx).Model(&models.TaskExecutionLog{}).Where("id = ?", originalPendingTask.TaskExecutionLogID).Update("status", models.ExecutionStatusWaiting).Error; err != nil { return err } @@ -157,25 +170,27 @@ func (r *gormPendingTaskRepository) RequeueTask(originalPendingTask *models.Pend originalPendingTask.ID = 0 // 3. 重新创建待办任务。GORM 会忽略掉已被重置的 ID,并让数据库生成一个新的主键。 - return tx.Create(originalPendingTask).Error + return tx.WithContext(repoCtx).Create(originalPendingTask).Error }) } // FindPendingTasksByTaskLogIDs 根据 TaskExecutionLogID 列表查找对应的待执行任务 -func (r *gormPendingTaskRepository) FindPendingTasksByTaskLogIDs(taskLogIDs []uint) ([]models.PendingTask, error) { +func (r *gormPendingTaskRepository) FindPendingTasksByTaskLogIDs(ctx context.Context, taskLogIDs []uint) ([]models.PendingTask, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindPendingTasksByTaskLogIDs") if len(taskLogIDs) == 0 { return []models.PendingTask{}, nil } var pendingTasks []models.PendingTask - err := r.db.Where("task_execution_log_id IN ?", taskLogIDs).Find(&pendingTasks).Error + err := r.db.WithContext(repoCtx).Where("task_execution_log_id IN ?", taskLogIDs).Find(&pendingTasks).Error return pendingTasks, err } // DeletePendingTasksByPlanLogID 删除与指定计划执行日志ID相关的所有待执行任务 -func (r *gormPendingTaskRepository) DeletePendingTasksByPlanLogID(planLogID uint) error { +func (r *gormPendingTaskRepository) DeletePendingTasksByPlanLogID(ctx context.Context, planLogID uint) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "DeletePendingTasksByPlanLogID") // 使用子查询找到所有与 planLogID 相关的 task_execution_log_id - subQuery := r.db.Model(&models.TaskExecutionLog{}).Select("id").Where("plan_execution_log_id = ?", planLogID) + subQuery := r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}).Select("id").Where("plan_execution_log_id = ?", planLogID) // 使用子查询的结果来删除待执行任务 - return r.db.Where("task_execution_log_id IN (?)", subQuery).Delete(&models.PendingTask{}).Error + return r.db.WithContext(repoCtx).Where("task_execution_log_id IN (?)", subQuery).Delete(&models.PendingTask{}).Error } diff --git a/internal/infra/repository/pig_batch_log_repository.go b/internal/infra/repository/pig_batch_log_repository.go index a85edb1..c07b70c 100644 --- a/internal/infra/repository/pig_batch_log_repository.go +++ b/internal/infra/repository/pig_batch_log_repository.go @@ -1,9 +1,12 @@ package repository import ( + "context" "time" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/gorm" ) @@ -20,37 +23,40 @@ type PigBatchLogListOptions struct { // PigBatchLogRepository 定义了与猪批次日志相关的数据库操作接口。 type PigBatchLogRepository interface { // CreateTx 在指定的事务中创建一条新的猪批次日志。 - CreateTx(tx *gorm.DB, log *models.PigBatchLog) error + CreateTx(ctx context.Context, tx *gorm.DB, log *models.PigBatchLog) error // GetLogsByBatchIDAndDateRangeTx 在指定的事务中,获取指定批次在特定时间范围内的所有日志记录。 - GetLogsByBatchIDAndDateRangeTx(tx *gorm.DB, batchID uint, startDate, endDate time.Time) ([]*models.PigBatchLog, error) + GetLogsByBatchIDAndDateRangeTx(ctx context.Context, tx *gorm.DB, batchID uint, startDate, endDate time.Time) ([]*models.PigBatchLog, error) // GetLastLogByBatchIDTx 在指定的事务中,获取某批次的最后一条日志记录。 - GetLastLogByBatchIDTx(tx *gorm.DB, batchID uint) (*models.PigBatchLog, error) + GetLastLogByBatchIDTx(ctx context.Context, tx *gorm.DB, batchID uint) (*models.PigBatchLog, error) // List 支持分页和过滤的列表查询 - List(opts PigBatchLogListOptions, page, pageSize int) ([]models.PigBatchLog, int64, error) + List(ctx context.Context, opts PigBatchLogListOptions, page, pageSize int) ([]models.PigBatchLog, int64, error) } // gormPigBatchLogRepository 是 PigBatchLogRepository 的 GORM 实现。 type gormPigBatchLogRepository struct { - db *gorm.DB + ctx context.Context + db *gorm.DB } // NewGormPigBatchLogRepository 创建一个新的 PigBatchLogRepository 实例。 -func NewGormPigBatchLogRepository(db *gorm.DB) PigBatchLogRepository { - return &gormPigBatchLogRepository{db: db} +func NewGormPigBatchLogRepository(ctx context.Context, db *gorm.DB) PigBatchLogRepository { + return &gormPigBatchLogRepository{ctx: ctx, db: db} } // CreateTx 实现了在事务中创建猪批次日志的逻辑。 -func (r *gormPigBatchLogRepository) CreateTx(tx *gorm.DB, log *models.PigBatchLog) error { - return tx.Create(log).Error +func (r *gormPigBatchLogRepository) CreateTx(ctx context.Context, tx *gorm.DB, log *models.PigBatchLog) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreateTx") + return tx.WithContext(repoCtx).Create(log).Error } // GetLogsByBatchIDAndDateRangeTx 实现了在指定的事务中,获取指定批次在特定时间范围内的所有日志记录的逻辑。 -func (r *gormPigBatchLogRepository) GetLogsByBatchIDAndDateRangeTx(tx *gorm.DB, batchID uint, startDate, endDate time.Time) ([]*models.PigBatchLog, error) { +func (r *gormPigBatchLogRepository) GetLogsByBatchIDAndDateRangeTx(ctx context.Context, tx *gorm.DB, batchID uint, startDate, endDate time.Time) ([]*models.PigBatchLog, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "GetLogsByBatchIDAndDateRangeTx") var logs []*models.PigBatchLog - err := tx.Where("pig_batch_id = ? AND created_at >= ? AND created_at <= ?", batchID, startDate, endDate).Find(&logs).Error + err := tx.WithContext(repoCtx).Where("pig_batch_id = ? AND created_at >= ? AND created_at <= ?", batchID, startDate, endDate).Find(&logs).Error if err != nil { return nil, err } @@ -58,9 +64,10 @@ func (r *gormPigBatchLogRepository) GetLogsByBatchIDAndDateRangeTx(tx *gorm.DB, } // GetLastLogByBatchIDTx 实现了在指定的事务中,获取某批次的最后一条日志记录的逻辑。 -func (r *gormPigBatchLogRepository) GetLastLogByBatchIDTx(tx *gorm.DB, batchID uint) (*models.PigBatchLog, error) { +func (r *gormPigBatchLogRepository) GetLastLogByBatchIDTx(ctx context.Context, tx *gorm.DB, batchID uint) (*models.PigBatchLog, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "GetLastLogByBatchIDTx") var log models.PigBatchLog - err := tx.Where("pig_batch_id = ?", batchID).Order("id DESC").First(&log).Error + err := tx.WithContext(repoCtx).Where("pig_batch_id = ?", batchID).Order("id DESC").First(&log).Error if err != nil { return nil, err } @@ -68,7 +75,8 @@ func (r *gormPigBatchLogRepository) GetLastLogByBatchIDTx(tx *gorm.DB, batchID u } // List 实现了分页和过滤查询猪批次日志的功能 -func (r *gormPigBatchLogRepository) List(opts PigBatchLogListOptions, page, pageSize int) ([]models.PigBatchLog, int64, error) { +func (r *gormPigBatchLogRepository) List(ctx context.Context, opts PigBatchLogListOptions, page, pageSize int) ([]models.PigBatchLog, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "List") if page <= 0 || pageSize <= 0 { return nil, 0, ErrInvalidPagination } @@ -76,7 +84,7 @@ func (r *gormPigBatchLogRepository) List(opts PigBatchLogListOptions, page, page var results []models.PigBatchLog var total int64 - query := r.db.Model(&models.PigBatchLog{}) + query := r.db.WithContext(repoCtx).Model(&models.PigBatchLog{}) if opts.PigBatchID != nil { query = query.Where("pig_batch_id = ?", *opts.PigBatchID) diff --git a/internal/infra/repository/pig_batch_repository.go b/internal/infra/repository/pig_batch_repository.go index 520b98c..ae37ecd 100644 --- a/internal/infra/repository/pig_batch_repository.go +++ b/internal/infra/repository/pig_batch_repository.go @@ -1,30 +1,33 @@ package repository import ( + "context" "time" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/gorm" ) // PigBatchRepository 定义了与猪批次相关的数据库操作接口 type PigBatchRepository interface { - CreatePigBatch(batch *models.PigBatch) (*models.PigBatch, error) - CreatePigBatchTx(tx *gorm.DB, batch *models.PigBatch) (*models.PigBatch, error) - GetPigBatchByID(id uint) (*models.PigBatch, error) - GetPigBatchByIDTx(tx *gorm.DB, id uint) (*models.PigBatch, error) + CreatePigBatch(ctx context.Context, batch *models.PigBatch) (*models.PigBatch, error) + CreatePigBatchTx(ctx context.Context, tx *gorm.DB, batch *models.PigBatch) (*models.PigBatch, error) + GetPigBatchByID(ctx context.Context, id uint) (*models.PigBatch, error) + GetPigBatchByIDTx(ctx context.Context, tx *gorm.DB, id uint) (*models.PigBatch, error) // UpdatePigBatch 更新一个猪批次,返回更新后的批次、受影响的行数和错误 - UpdatePigBatch(batch *models.PigBatch) (*models.PigBatch, int64, error) + UpdatePigBatch(ctx context.Context, batch *models.PigBatch) (*models.PigBatch, int64, error) // DeletePigBatch 根据ID删除一个猪批次,返回受影响的行数和错误 - DeletePigBatch(id uint) (int64, error) - DeletePigBatchTx(tx *gorm.DB, id uint) (int64, error) - ListPigBatches(isActive *bool) ([]*models.PigBatch, error) + DeletePigBatch(ctx context.Context, id uint) (int64, error) + DeletePigBatchTx(ctx context.Context, tx *gorm.DB, id uint) (int64, error) + ListPigBatches(ctx context.Context, isActive *bool) ([]*models.PigBatch, error) // ListWeighingBatches 支持分页和过滤的批次称重列表查询 - ListWeighingBatches(opts WeighingBatchListOptions, page, pageSize int) ([]models.WeighingBatch, int64, error) + ListWeighingBatches(ctx context.Context, opts WeighingBatchListOptions, page, pageSize int) ([]models.WeighingBatch, int64, error) // ListWeighingRecords 支持分页和过滤的单次称重记录列表查询 - ListWeighingRecords(opts WeighingRecordListOptions, page, pageSize int) ([]models.WeighingRecord, int64, error) + ListWeighingRecords(ctx context.Context, opts WeighingRecordListOptions, page, pageSize int) ([]models.WeighingRecord, int64, error) } // WeighingBatchListOptions 定义了查询批次称重记录时的可选参数 @@ -47,35 +50,40 @@ type WeighingRecordListOptions struct { // gormPigBatchRepository 是 PigBatchRepository 的 GORM 实现 type gormPigBatchRepository struct { - db *gorm.DB + ctx context.Context + db *gorm.DB } // NewGormPigBatchRepository 创建一个新的 PigBatchRepository GORM 实现实例 -func NewGormPigBatchRepository(db *gorm.DB) PigBatchRepository { - return &gormPigBatchRepository{db: db} +func NewGormPigBatchRepository(ctx context.Context, db *gorm.DB) PigBatchRepository { + return &gormPigBatchRepository{ctx: ctx, db: db} } // CreatePigBatch 创建一个新的猪批次 -func (r *gormPigBatchRepository) CreatePigBatch(batch *models.PigBatch) (*models.PigBatch, error) { - return r.CreatePigBatchTx(r.db, batch) +func (r *gormPigBatchRepository) CreatePigBatch(ctx context.Context, batch *models.PigBatch) (*models.PigBatch, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePigBatch") + return r.CreatePigBatchTx(repoCtx, r.db, batch) } // CreatePigBatchTx 在指定的事务中,创建一个新的猪批次 -func (r *gormPigBatchRepository) CreatePigBatchTx(tx *gorm.DB, batch *models.PigBatch) (*models.PigBatch, error) { - if err := tx.Create(batch).Error; err != nil { +func (r *gormPigBatchRepository) CreatePigBatchTx(ctx context.Context, tx *gorm.DB, batch *models.PigBatch) (*models.PigBatch, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePigBatchTx") + if err := tx.WithContext(repoCtx).Create(batch).Error; err != nil { return nil, err } return batch, nil } // GetPigBatchByID 根据ID获取单个猪批次 -func (r *gormPigBatchRepository) GetPigBatchByID(id uint) (*models.PigBatch, error) { - return r.GetPigBatchByIDTx(r.db, id) +func (r *gormPigBatchRepository) GetPigBatchByID(ctx context.Context, id uint) (*models.PigBatch, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPigBatchByID") + return r.GetPigBatchByIDTx(repoCtx, r.db, id) } // UpdatePigBatch 更新一个猪批次 -func (r *gormPigBatchRepository) UpdatePigBatch(batch *models.PigBatch) (*models.PigBatch, int64, error) { - result := r.db.Model(&models.PigBatch{}).Where("id = ?", batch.ID).Updates(batch) +func (r *gormPigBatchRepository) UpdatePigBatch(ctx context.Context, batch *models.PigBatch) (*models.PigBatch, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePigBatch") + result := r.db.WithContext(repoCtx).Model(&models.PigBatch{}).Where("id = ?", batch.ID).Updates(batch) if result.Error != nil { return nil, 0, result.Error } @@ -84,12 +92,14 @@ func (r *gormPigBatchRepository) UpdatePigBatch(batch *models.PigBatch) (*models } // DeletePigBatch 根据ID删除一个猪批次 (GORM 会执行软删除) -func (r *gormPigBatchRepository) DeletePigBatch(id uint) (int64, error) { - return r.DeletePigBatchTx(r.db, id) +func (r *gormPigBatchRepository) DeletePigBatch(ctx context.Context, id uint) (int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "DeletePigBatch") + return r.DeletePigBatchTx(repoCtx, r.db, id) } -func (r *gormPigBatchRepository) DeletePigBatchTx(tx *gorm.DB, id uint) (int64, error) { - result := tx.Delete(&models.PigBatch{}, id) +func (r *gormPigBatchRepository) DeletePigBatchTx(ctx context.Context, tx *gorm.DB, id uint) (int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "DeletePigBatchTx") + result := tx.WithContext(repoCtx).Delete(&models.PigBatch{}, id) if result.Error != nil { return 0, result.Error } @@ -98,9 +108,10 @@ func (r *gormPigBatchRepository) DeletePigBatchTx(tx *gorm.DB, id uint) (int64, } // ListPigBatches 批量查询猪批次,支持根据 IsActive 筛选 -func (r *gormPigBatchRepository) ListPigBatches(isActive *bool) ([]*models.PigBatch, error) { +func (r *gormPigBatchRepository) ListPigBatches(ctx context.Context, isActive *bool) ([]*models.PigBatch, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListPigBatches") var batches []*models.PigBatch - query := r.db.Model(&models.PigBatch{}) + query := r.db.WithContext(repoCtx).Model(&models.PigBatch{}) if isActive != nil { if *isActive { @@ -119,16 +130,18 @@ func (r *gormPigBatchRepository) ListPigBatches(isActive *bool) ([]*models.PigBa } // GetPigBatchByIDTx 在指定的事务中,通过ID获取单个猪批次 -func (r *gormPigBatchRepository) GetPigBatchByIDTx(tx *gorm.DB, id uint) (*models.PigBatch, error) { +func (r *gormPigBatchRepository) GetPigBatchByIDTx(ctx context.Context, tx *gorm.DB, id uint) (*models.PigBatch, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPigBatchByIDTx") var batch models.PigBatch - if err := tx.First(&batch, id).Error; err != nil { + if err := tx.WithContext(repoCtx).First(&batch, id).Error; err != nil { return nil, err } return &batch, nil } // ListWeighingBatches 实现了分页和过滤查询批次称重记录的功能 -func (r *gormPigBatchRepository) ListWeighingBatches(opts WeighingBatchListOptions, page, pageSize int) ([]models.WeighingBatch, int64, error) { +func (r *gormPigBatchRepository) ListWeighingBatches(ctx context.Context, opts WeighingBatchListOptions, page, pageSize int) ([]models.WeighingBatch, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListWeighingBatches") if page <= 0 || pageSize <= 0 { return nil, 0, ErrInvalidPagination } @@ -136,7 +149,7 @@ func (r *gormPigBatchRepository) ListWeighingBatches(opts WeighingBatchListOptio var results []models.WeighingBatch var total int64 - query := r.db.Model(&models.WeighingBatch{}) + query := r.db.WithContext(repoCtx).Model(&models.WeighingBatch{}) if opts.PigBatchID != nil { query = query.Where("pig_batch_id = ?", *opts.PigBatchID) @@ -165,7 +178,8 @@ func (r *gormPigBatchRepository) ListWeighingBatches(opts WeighingBatchListOptio } // ListWeighingRecords 实现了分页和过滤查询单次称重记录的功能 -func (r *gormPigBatchRepository) ListWeighingRecords(opts WeighingRecordListOptions, page, pageSize int) ([]models.WeighingRecord, int64, error) { +func (r *gormPigBatchRepository) ListWeighingRecords(ctx context.Context, opts WeighingRecordListOptions, page, pageSize int) ([]models.WeighingRecord, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListWeighingRecords") if page <= 0 || pageSize <= 0 { return nil, 0, ErrInvalidPagination } @@ -173,7 +187,7 @@ func (r *gormPigBatchRepository) ListWeighingRecords(opts WeighingRecordListOpti var results []models.WeighingRecord var total int64 - query := r.db.Model(&models.WeighingRecord{}) + query := r.db.WithContext(repoCtx).Model(&models.WeighingRecord{}) if opts.WeighingBatchID != nil { query = query.Where("weighing_batch_id = ?", *opts.WeighingBatchID) diff --git a/internal/infra/repository/pig_farm_repository.go b/internal/infra/repository/pig_farm_repository.go index 954f1f5..f70c75b 100644 --- a/internal/infra/repository/pig_farm_repository.go +++ b/internal/infra/repository/pig_farm_repository.go @@ -1,61 +1,70 @@ package repository import ( + "context" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/gorm" ) // PigFarmRepository 定义了与猪场资产(猪舍、猪栏)相关的数据库操作接口 type PigFarmRepository interface { // PigHouse methods - CreatePigHouse(house *models.PigHouse) error - GetPigHouseByID(id uint) (*models.PigHouse, error) - ListPigHouses() ([]models.PigHouse, error) + CreatePigHouse(ctx context.Context, house *models.PigHouse) error + GetPigHouseByID(ctx context.Context, id uint) (*models.PigHouse, error) + ListPigHouses(ctx context.Context) ([]models.PigHouse, error) // UpdatePigHouse 更新一个猪舍,返回受影响的行数和错误 - UpdatePigHouse(house *models.PigHouse) (int64, error) + UpdatePigHouse(ctx context.Context, house *models.PigHouse) (int64, error) // DeletePigHouse 根据ID删除一个猪舍,返回受影响的行数和错误 - DeletePigHouse(id uint) (int64, error) - CountPensInHouse(houseID uint) (int64, error) + DeletePigHouse(ctx context.Context, id uint) (int64, error) + CountPensInHouse(ctx context.Context, houseID uint) (int64, error) } // gormPigFarmRepository 是 PigFarmRepository 的 GORM 实现 type gormPigFarmRepository struct { - db *gorm.DB + ctx context.Context + db *gorm.DB } // NewGormPigFarmRepository 创建一个新的 PigFarmRepository GORM 实现实例 -func NewGormPigFarmRepository(db *gorm.DB) PigFarmRepository { - return &gormPigFarmRepository{db: db} +func NewGormPigFarmRepository(ctx context.Context, db *gorm.DB) PigFarmRepository { + return &gormPigFarmRepository{ctx: ctx, db: db} } // --- PigHouse Implementation --- // CreatePigHouse 创建一个新的猪舍 -func (r *gormPigFarmRepository) CreatePigHouse(house *models.PigHouse) error { - return r.db.Create(house).Error +func (r *gormPigFarmRepository) CreatePigHouse(ctx context.Context, house *models.PigHouse) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePigHouse") + return r.db.WithContext(repoCtx).Create(house).Error } // GetPigHouseByID 根据ID获取单个猪舍 -func (r *gormPigFarmRepository) GetPigHouseByID(id uint) (*models.PigHouse, error) { +func (r *gormPigFarmRepository) GetPigHouseByID(ctx context.Context, id uint) (*models.PigHouse, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPigHouseByID") var house models.PigHouse - if err := r.db.First(&house, id).Error; err != nil { + if err := r.db.WithContext(repoCtx).First(&house, id).Error; err != nil { return nil, err } return &house, nil } // ListPigHouses 列出所有猪舍 -func (r *gormPigFarmRepository) ListPigHouses() ([]models.PigHouse, error) { +func (r *gormPigFarmRepository) ListPigHouses(ctx context.Context) ([]models.PigHouse, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListPigHouses") var houses []models.PigHouse - if err := r.db.Find(&houses).Error; err != nil { + if err := r.db.WithContext(repoCtx).Find(&houses).Error; err != nil { return nil, err } return houses, nil } // UpdatePigHouse 更新一个猪舍,返回受影响的行数和错误 -func (r *gormPigFarmRepository) UpdatePigHouse(house *models.PigHouse) (int64, error) { - result := r.db.Model(&models.PigHouse{}).Where("id = ?", house.ID).Updates(house) +func (r *gormPigFarmRepository) UpdatePigHouse(ctx context.Context, house *models.PigHouse) (int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePigHouse") + result := r.db.WithContext(repoCtx).Model(&models.PigHouse{}).Where("id = ?", house.ID).Updates(house) if result.Error != nil { return 0, result.Error } @@ -63,8 +72,9 @@ func (r *gormPigFarmRepository) UpdatePigHouse(house *models.PigHouse) (int64, e } // DeletePigHouse 根据ID删除一个猪舍,返回受影响的行数和错误 -func (r *gormPigFarmRepository) DeletePigHouse(id uint) (int64, error) { - result := r.db.Delete(&models.PigHouse{}, id) +func (r *gormPigFarmRepository) DeletePigHouse(ctx context.Context, id uint) (int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "DeletePigHouse") + result := r.db.WithContext(repoCtx).Delete(&models.PigHouse{}, id) if result.Error != nil { return 0, result.Error } @@ -72,8 +82,9 @@ func (r *gormPigFarmRepository) DeletePigHouse(id uint) (int64, error) { } // CountPensInHouse 统计猪舍中的猪栏数量 -func (r *gormPigFarmRepository) CountPensInHouse(houseID uint) (int64, error) { +func (r *gormPigFarmRepository) CountPensInHouse(ctx context.Context, houseID uint) (int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CountPensInHouse") var count int64 - err := r.db.Model(&models.Pen{}).Where("house_id = ?", houseID).Count(&count).Error + err := r.db.WithContext(repoCtx).Model(&models.Pen{}).Where("house_id = ?", houseID).Count(&count).Error return count, err } diff --git a/internal/infra/repository/pig_pen_repository.go b/internal/infra/repository/pig_pen_repository.go index 84a1b69..ac83df9 100644 --- a/internal/infra/repository/pig_pen_repository.go +++ b/internal/infra/repository/pig_pen_repository.go @@ -1,69 +1,79 @@ package repository import ( + "context" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/gorm" ) // PigPenRepository 定义了与猪栏模型相关的数据库操作接口。 type PigPenRepository interface { - CreatePen(pen *models.Pen) error + CreatePen(ctx context.Context, pen *models.Pen) error // GetPenByID 根据ID获取单个猪栏 (非事务性) - GetPenByID(id uint) (*models.Pen, error) + GetPenByID(ctx context.Context, id uint) (*models.Pen, error) // GetPenByIDTx 根据ID获取单个猪栏 (事务性) - GetPenByIDTx(tx *gorm.DB, id uint) (*models.Pen, error) - ListPens() ([]models.Pen, error) + GetPenByIDTx(ctx context.Context, tx *gorm.DB, id uint) (*models.Pen, error) + ListPens(ctx context.Context) ([]models.Pen, error) // UpdatePen 更新一个猪栏,返回受影响的行数和错误 - UpdatePen(pen *models.Pen) (int64, error) + UpdatePen(ctx context.Context, pen *models.Pen) (int64, error) // DeletePen 根据ID删除一个猪栏,返回受影响的行数和错误 - DeletePen(id uint) (int64, error) + DeletePen(ctx context.Context, id uint) (int64, error) // GetPensByBatchIDTx 根据批次ID获取所有关联的猪栏 (事务性) - GetPensByBatchIDTx(tx *gorm.DB, batchID uint) ([]*models.Pen, error) + GetPensByBatchIDTx(ctx context.Context, tx *gorm.DB, batchID uint) ([]*models.Pen, error) // UpdatePenFieldsTx 更新猪栏的指定字段 (事务性) - UpdatePenFieldsTx(tx *gorm.DB, penID uint, updates map[string]interface{}) error + UpdatePenFieldsTx(ctx context.Context, tx *gorm.DB, penID uint, updates map[string]interface{}) error } // gormPigPenRepository 是 PigPenRepository 接口的 GORM 实现。 type gormPigPenRepository struct { - db *gorm.DB + ctx context.Context + db *gorm.DB } // NewGormPigPenRepository 创建一个新的 PigPenRepository GORM 实现实例。 -func NewGormPigPenRepository(db *gorm.DB) PigPenRepository { - return &gormPigPenRepository{db: db} +func NewGormPigPenRepository(ctx context.Context, db *gorm.DB) PigPenRepository { + return &gormPigPenRepository{ctx: ctx, db: db} } // CreatePen 创建一个新的猪栏 -func (r *gormPigPenRepository) CreatePen(pen *models.Pen) error { - return r.db.Create(pen).Error +func (r *gormPigPenRepository) CreatePen(ctx context.Context, pen *models.Pen) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePen") + return r.db.WithContext(repoCtx).Create(pen).Error } // GetPenByID 根据ID获取单个猪栏 (非事务性) -func (r *gormPigPenRepository) GetPenByID(id uint) (*models.Pen, error) { - return r.GetPenByIDTx(r.db, id) // 非Tx方法直接调用Tx方法 +func (r *gormPigPenRepository) GetPenByID(ctx context.Context, id uint) (*models.Pen, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPenByID") + return r.GetPenByIDTx(repoCtx, r.db, id) // 非Tx方法直接调用Tx方法 } // GetPenByIDTx 在指定的事务中,通过ID获取单个猪栏信息。 -func (r *gormPigPenRepository) GetPenByIDTx(tx *gorm.DB, id uint) (*models.Pen, error) { +func (r *gormPigPenRepository) GetPenByIDTx(ctx context.Context, tx *gorm.DB, id uint) (*models.Pen, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPenByIDTx") var pen models.Pen - if err := tx.First(&pen, id).Error; err != nil { + if err := tx.WithContext(repoCtx).First(&pen, id).Error; err != nil { return nil, err } return &pen, nil } // ListPens 列出所有猪栏 -func (r *gormPigPenRepository) ListPens() ([]models.Pen, error) { +func (r *gormPigPenRepository) ListPens(ctx context.Context) ([]models.Pen, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListPens") var pens []models.Pen - if err := r.db.Find(&pens).Error; err != nil { + if err := r.db.WithContext(repoCtx).Find(&pens).Error; err != nil { return nil, err } return pens, nil } // UpdatePen 更新一个猪栏,返回受影响的行数和错误 -func (r *gormPigPenRepository) UpdatePen(pen *models.Pen) (int64, error) { - result := r.db.Model(&models.Pen{}).Where("id = ?", pen.ID).Updates(pen) +func (r *gormPigPenRepository) UpdatePen(ctx context.Context, pen *models.Pen) (int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePen") + result := r.db.WithContext(repoCtx).Model(&models.Pen{}).Where("id = ?", pen.ID).Updates(pen) if result.Error != nil { return 0, result.Error } @@ -71,8 +81,9 @@ func (r *gormPigPenRepository) UpdatePen(pen *models.Pen) (int64, error) { } // DeletePen 根据ID删除一个猪栏,返回受影响的行数和错误 -func (r *gormPigPenRepository) DeletePen(id uint) (int64, error) { - result := r.db.Delete(&models.Pen{}, id) +func (r *gormPigPenRepository) DeletePen(ctx context.Context, id uint) (int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "DeletePen") + result := r.db.WithContext(repoCtx).Delete(&models.Pen{}, id) if result.Error != nil { return 0, result.Error } @@ -80,10 +91,11 @@ func (r *gormPigPenRepository) DeletePen(id uint) (int64, error) { } // GetPensByBatchIDTx 在指定的事务中,获取一个猪群当前关联的所有猪栏。 -func (r *gormPigPenRepository) GetPensByBatchIDTx(tx *gorm.DB, batchID uint) ([]*models.Pen, error) { +func (r *gormPigPenRepository) GetPensByBatchIDTx(ctx context.Context, tx *gorm.DB, batchID uint) ([]*models.Pen, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPensByBatchIDTx") var pens []*models.Pen // 注意:PigBatchID 是指针类型,需要处理 nil 值 - result := tx.Where("pig_batch_id = ?", batchID).Find(&pens) + result := tx.WithContext(repoCtx).Where("pig_batch_id = ?", batchID).Find(&pens) if result.Error != nil { return nil, result.Error } @@ -91,7 +103,8 @@ func (r *gormPigPenRepository) GetPensByBatchIDTx(tx *gorm.DB, batchID uint) ([] } // UpdatePenFieldsTx 在指定的事务中,更新一个猪栏的指定字段。 -func (r *gormPigPenRepository) UpdatePenFieldsTx(tx *gorm.DB, penID uint, updates map[string]interface{}) error { - result := tx.Model(&models.Pen{}).Where("id = ?", penID).Updates(updates) +func (r *gormPigPenRepository) UpdatePenFieldsTx(ctx context.Context, tx *gorm.DB, penID uint, updates map[string]interface{}) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePenFieldsTx") + result := tx.WithContext(repoCtx).Model(&models.Pen{}).Where("id = ?", penID).Updates(updates) return result.Error } diff --git a/internal/infra/repository/pig_sick_repository.go b/internal/infra/repository/pig_sick_repository.go index 749446a..61f63e9 100644 --- a/internal/infra/repository/pig_sick_repository.go +++ b/internal/infra/repository/pig_sick_repository.go @@ -1,10 +1,13 @@ package repository import ( + "context" "errors" "time" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/gorm" ) @@ -23,38 +26,42 @@ type PigSickLogListOptions struct { // PigSickLogRepository 定义了与病猪日志模型相关的数据库操作接口。 type PigSickLogRepository interface { // CreatePigSickLog 创建一条新的病猪日志记录 - CreatePigSickLog(log *models.PigSickLog) error - CreatePigSickLogTx(tx *gorm.DB, log *models.PigSickLog) error + CreatePigSickLog(ctx context.Context, log *models.PigSickLog) error + CreatePigSickLogTx(ctx context.Context, tx *gorm.DB, log *models.PigSickLog) error // GetLastLogByBatchTx 在事务中获取指定批次和猪栏的最新一条 PigSickLog 记录 - GetLastLogByBatchTx(tx *gorm.DB, batchID uint) (*models.PigSickLog, error) + GetLastLogByBatchTx(ctx context.Context, tx *gorm.DB, batchID uint) (*models.PigSickLog, error) // ListPigSickLogs 支持分页和过滤的病猪日志列表查询 - ListPigSickLogs(opts PigSickLogListOptions, page, pageSize int) ([]models.PigSickLog, int64, error) + ListPigSickLogs(ctx context.Context, opts PigSickLogListOptions, page, pageSize int) ([]models.PigSickLog, int64, error) } // gormPigSickLogRepository 是 PigSickLogRepository 接口的 GORM 实现。 type gormPigSickLogRepository struct { - db *gorm.DB + ctx context.Context + db *gorm.DB } // NewGormPigSickLogRepository 创建一个新的 PigSickLogRepository GORM 实现实例。 -func NewGormPigSickLogRepository(db *gorm.DB) PigSickLogRepository { - return &gormPigSickLogRepository{db: db} +func NewGormPigSickLogRepository(ctx context.Context, db *gorm.DB) PigSickLogRepository { + return &gormPigSickLogRepository{ctx: ctx, db: db} } // CreatePigSickLog 创建一条新的病猪日志记录 -func (r *gormPigSickLogRepository) CreatePigSickLog(log *models.PigSickLog) error { - return r.CreatePigSickLogTx(r.db, log) +func (r *gormPigSickLogRepository) CreatePigSickLog(ctx context.Context, log *models.PigSickLog) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePigSickLog") + return r.CreatePigSickLogTx(repoCtx, r.db, log) } -func (r *gormPigSickLogRepository) CreatePigSickLogTx(tx *gorm.DB, log *models.PigSickLog) error { - return tx.Create(log).Error +func (r *gormPigSickLogRepository) CreatePigSickLogTx(ctx context.Context, tx *gorm.DB, log *models.PigSickLog) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePigSickLogTx") + return tx.WithContext(repoCtx).Create(log).Error } // GetLastLogByBatchTx 在事务中获取指定批次和猪栏的最新一条 PigSickLog 记录 -func (r *gormPigSickLogRepository) GetLastLogByBatchTx(tx *gorm.DB, batchID uint) (*models.PigSickLog, error) { +func (r *gormPigSickLogRepository) GetLastLogByBatchTx(ctx context.Context, tx *gorm.DB, batchID uint) (*models.PigSickLog, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "GetLastLogByBatchTx") var lastLog models.PigSickLog - err := tx. + err := tx.WithContext(repoCtx). Where("pig_batch_id = ?", batchID). Order("happened_at DESC"). // 按时间降序排列 First(&lastLog).Error // 获取第一条记录 (即最新一条) @@ -69,7 +76,8 @@ func (r *gormPigSickLogRepository) GetLastLogByBatchTx(tx *gorm.DB, batchID uint } // ListPigSickLogs 实现了分页和过滤查询病猪日志的功能 -func (r *gormPigSickLogRepository) ListPigSickLogs(opts PigSickLogListOptions, page, pageSize int) ([]models.PigSickLog, int64, error) { +func (r *gormPigSickLogRepository) ListPigSickLogs(ctx context.Context, opts PigSickLogListOptions, page, pageSize int) ([]models.PigSickLog, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListPigSickLogs") if page <= 0 || pageSize <= 0 { return nil, 0, ErrInvalidPagination } @@ -77,7 +85,7 @@ func (r *gormPigSickLogRepository) ListPigSickLogs(opts PigSickLogListOptions, p var results []models.PigSickLog var total int64 - query := r.db.Model(&models.PigSickLog{}) + query := r.db.WithContext(repoCtx).Model(&models.PigSickLog{}) if opts.PigBatchID != nil { query = query.Where("pig_batch_id = ?", *opts.PigBatchID) diff --git a/internal/infra/repository/pig_trade_repository.go b/internal/infra/repository/pig_trade_repository.go index eeb0c21..3e6cd25 100644 --- a/internal/infra/repository/pig_trade_repository.go +++ b/internal/infra/repository/pig_trade_repository.go @@ -1,9 +1,12 @@ package repository import ( + "context" "time" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/gorm" ) @@ -31,40 +34,44 @@ type PigSaleListOptions struct { // 领域服务通过此接口与数据层交互,实现解耦。 type PigTradeRepository interface { // CreatePigSaleTx 在数据库中创建一条猪只销售记录。 - CreatePigSaleTx(tx *gorm.DB, sale *models.PigSale) error + CreatePigSaleTx(ctx context.Context, tx *gorm.DB, sale *models.PigSale) error // CreatePigPurchaseTx 在数据库中创建一条猪只采购记录。 - CreatePigPurchaseTx(tx *gorm.DB, purchase *models.PigPurchase) error + CreatePigPurchaseTx(ctx context.Context, tx *gorm.DB, purchase *models.PigPurchase) error // ListPigPurchases 支持分页和过滤的猪只采购记录列表查询 - ListPigPurchases(opts PigPurchaseListOptions, page, pageSize int) ([]models.PigPurchase, int64, error) + ListPigPurchases(ctx context.Context, opts PigPurchaseListOptions, page, pageSize int) ([]models.PigPurchase, int64, error) // ListPigSales 支持分页和过滤的猪只销售记录列表查询 - ListPigSales(opts PigSaleListOptions, page, pageSize int) ([]models.PigSale, int64, error) + ListPigSales(ctx context.Context, opts PigSaleListOptions, page, pageSize int) ([]models.PigSale, int64, error) } // gormPigTradeRepository 是 PigTradeRepository 接口的 GORM 实现。 type gormPigTradeRepository struct { - db *gorm.DB + ctx context.Context + db *gorm.DB } // NewGormPigTradeRepository 创建一个新的 PigTradeRepository GORM 实现实例。 -func NewGormPigTradeRepository(db *gorm.DB) PigTradeRepository { - return &gormPigTradeRepository{db: db} +func NewGormPigTradeRepository(ctx context.Context, db *gorm.DB) PigTradeRepository { + return &gormPigTradeRepository{ctx: ctx, db: db} } // CreatePigSaleTx 实现了在数据库中创建猪只销售记录的逻辑。 -func (r *gormPigTradeRepository) CreatePigSaleTx(tx *gorm.DB, sale *models.PigSale) error { - return tx.Create(sale).Error +func (r *gormPigTradeRepository) CreatePigSaleTx(ctx context.Context, tx *gorm.DB, sale *models.PigSale) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePigSaleTx") + return tx.WithContext(repoCtx).Create(sale).Error } // CreatePigPurchaseTx 实现了在数据库中创建猪只采购记录的逻辑。 -func (r *gormPigTradeRepository) CreatePigPurchaseTx(tx *gorm.DB, purchase *models.PigPurchase) error { - return tx.Create(purchase).Error +func (r *gormPigTradeRepository) CreatePigPurchaseTx(ctx context.Context, tx *gorm.DB, purchase *models.PigPurchase) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePigPurchaseTx") + return tx.WithContext(repoCtx).Create(purchase).Error } // ListPigPurchases 实现了分页和过滤查询猪只采购记录的功能 -func (r *gormPigTradeRepository) ListPigPurchases(opts PigPurchaseListOptions, page, pageSize int) ([]models.PigPurchase, int64, error) { +func (r *gormPigTradeRepository) ListPigPurchases(ctx context.Context, opts PigPurchaseListOptions, page, pageSize int) ([]models.PigPurchase, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListPigPurchases") if page <= 0 || pageSize <= 0 { return nil, 0, ErrInvalidPagination } @@ -72,7 +79,7 @@ func (r *gormPigTradeRepository) ListPigPurchases(opts PigPurchaseListOptions, p var results []models.PigPurchase var total int64 - query := r.db.Model(&models.PigPurchase{}) + query := r.db.WithContext(repoCtx).Model(&models.PigPurchase{}) if opts.PigBatchID != nil { query = query.Where("pig_batch_id = ?", *opts.PigBatchID) @@ -107,7 +114,8 @@ func (r *gormPigTradeRepository) ListPigPurchases(opts PigPurchaseListOptions, p } // ListPigSales 实现了分页和过滤查询猪只销售记录的功能 -func (r *gormPigTradeRepository) ListPigSales(opts PigSaleListOptions, page, pageSize int) ([]models.PigSale, int64, error) { +func (r *gormPigTradeRepository) ListPigSales(ctx context.Context, opts PigSaleListOptions, page, pageSize int) ([]models.PigSale, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListPigSales") if page <= 0 || pageSize <= 0 { return nil, 0, ErrInvalidPagination } @@ -115,7 +123,7 @@ func (r *gormPigTradeRepository) ListPigSales(opts PigSaleListOptions, page, pag var results []models.PigSale var total int64 - query := r.db.Model(&models.PigSale{}) + query := r.db.WithContext(repoCtx).Model(&models.PigSale{}) if opts.PigBatchID != nil { query = query.Where("pig_batch_id = ?", *opts.PigBatchID) diff --git a/internal/infra/repository/pig_transfer_log_repository.go b/internal/infra/repository/pig_transfer_log_repository.go index 9db57e1..ca0d5e5 100644 --- a/internal/infra/repository/pig_transfer_log_repository.go +++ b/internal/infra/repository/pig_transfer_log_repository.go @@ -1,9 +1,12 @@ package repository import ( + "context" "time" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/gorm" ) @@ -22,39 +25,43 @@ type PigTransferLogListOptions struct { // PigTransferLogRepository 定义了猪只迁移日志数据持久化的接口。 type PigTransferLogRepository interface { // CreatePigTransferLog 在数据库中创建一条猪只迁移日志记录。 - CreatePigTransferLog(tx *gorm.DB, log *models.PigTransferLog) error + CreatePigTransferLog(ctx context.Context, tx *gorm.DB, log *models.PigTransferLog) error // GetLogsForPenSince 获取指定猪栏自特定时间点以来的所有迁移日志,按时间倒序排列。 - GetLogsForPenSince(tx *gorm.DB, penID uint, since time.Time) ([]*models.PigTransferLog, error) + GetLogsForPenSince(ctx context.Context, tx *gorm.DB, penID uint, since time.Time) ([]*models.PigTransferLog, error) // ListPigTransferLogs 支持分页和过滤的猪只迁移日志列表查询 - ListPigTransferLogs(opts PigTransferLogListOptions, page, pageSize int) ([]models.PigTransferLog, int64, error) + ListPigTransferLogs(ctx context.Context, opts PigTransferLogListOptions, page, pageSize int) ([]models.PigTransferLog, int64, error) } // gormPigTransferLogRepository 是 PigTransferLogRepository 接口的 GORM 实现。 type gormPigTransferLogRepository struct { - db *gorm.DB + ctx context.Context + db *gorm.DB } // NewGormPigTransferLogRepository 创建一个新的 PigTransferLogRepository GORM 实现实例。 -func NewGormPigTransferLogRepository(db *gorm.DB) PigTransferLogRepository { - return &gormPigTransferLogRepository{db: db} +func NewGormPigTransferLogRepository(ctx context.Context, db *gorm.DB) PigTransferLogRepository { + return &gormPigTransferLogRepository{ctx: ctx, db: db} } // CreatePigTransferLog 实现了在数据库中创建猪只迁移日志记录的逻辑。 -func (r *gormPigTransferLogRepository) CreatePigTransferLog(tx *gorm.DB, log *models.PigTransferLog) error { - return tx.Create(log).Error +func (r *gormPigTransferLogRepository) CreatePigTransferLog(ctx context.Context, tx *gorm.DB, log *models.PigTransferLog) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePigTransferLog") + return tx.WithContext(repoCtx).Create(log).Error } // GetLogsForPenSince 实现了获取猪栏自特定时间点以来所有迁移日志的逻辑。 -func (r *gormPigTransferLogRepository) GetLogsForPenSince(tx *gorm.DB, penID uint, since time.Time) ([]*models.PigTransferLog, error) { +func (r *gormPigTransferLogRepository) GetLogsForPenSince(ctx context.Context, tx *gorm.DB, penID uint, since time.Time) ([]*models.PigTransferLog, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "GetLogsForPenSince") var logs []*models.PigTransferLog - err := tx.Where("pen_id = ? AND transfer_time >= ?", penID, since).Order("transfer_time DESC").Find(&logs).Error + err := tx.WithContext(repoCtx).Where("pen_id = ? AND transfer_time >= ?", penID, since).Order("transfer_time DESC").Find(&logs).Error return logs, err } // ListPigTransferLogs 实现了分页和过滤查询猪只迁移日志的功能 -func (r *gormPigTransferLogRepository) ListPigTransferLogs(opts PigTransferLogListOptions, page, pageSize int) ([]models.PigTransferLog, int64, error) { +func (r *gormPigTransferLogRepository) ListPigTransferLogs(ctx context.Context, opts PigTransferLogListOptions, page, pageSize int) ([]models.PigTransferLog, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListPigTransferLogs") if page <= 0 || pageSize <= 0 { return nil, 0, ErrInvalidPagination } @@ -62,7 +69,7 @@ func (r *gormPigTransferLogRepository) ListPigTransferLogs(opts PigTransferLogLi var results []models.PigTransferLog var total int64 - query := r.db.Model(&models.PigTransferLog{}) + query := r.db.WithContext(repoCtx).Model(&models.PigTransferLog{}) if opts.PigBatchID != nil { query = query.Where("pig_batch_id = ?", *opts.PigBatchID) diff --git a/internal/infra/repository/plan_repository.go b/internal/infra/repository/plan_repository.go index 25fdbc0..cfaf01d 100644 --- a/internal/infra/repository/plan_repository.go +++ b/internal/infra/repository/plan_repository.go @@ -1,11 +1,14 @@ package repository import ( + "context" "encoding/json" "errors" "fmt" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/datatypes" "gorm.io/gorm" ) @@ -39,67 +42,71 @@ type ListPlansOptions struct { // 这是为了让业务逻辑层依赖于抽象,而不是具体的数据库实现 type PlanRepository interface { // ListPlans 获取计划列表,支持过滤和分页 - ListPlans(opts ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error) + ListPlans(ctx context.Context, opts ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error) // GetBasicPlanByID 根据ID获取计划的基本信息,不包含子计划和任务详情 - GetBasicPlanByID(id uint) (*models.Plan, error) + GetBasicPlanByID(ctx context.Context, id uint) (*models.Plan, error) // GetPlanByID 根据ID获取计划,包含子计划和任务详情 - GetPlanByID(id uint) (*models.Plan, error) + GetPlanByID(ctx context.Context, id uint) (*models.Plan, error) // GetPlansByIDs 根据ID列表获取计划,不包含子计划和任务详情 - GetPlansByIDs(ids []uint) ([]models.Plan, error) + GetPlansByIDs(ctx context.Context, ids []uint) ([]models.Plan, error) // CreatePlan 创建一个新的计划 - CreatePlan(plan *models.Plan) error + CreatePlan(ctx context.Context, plan *models.Plan) error // CreatePlanTx 在指定事务中创建一个新的计划 - CreatePlanTx(tx *gorm.DB, plan *models.Plan) error + CreatePlanTx(ctx context.Context, tx *gorm.DB, plan *models.Plan) error // UpdatePlanMetadataAndStructure 更新计划的元数据和结构,但不包括状态等运行时信息 - UpdatePlanMetadataAndStructure(plan *models.Plan) error + UpdatePlanMetadataAndStructure(ctx context.Context, plan *models.Plan) error // UpdatePlan 更新计划的所有字段 - UpdatePlan(plan *models.Plan) error + UpdatePlan(ctx context.Context, plan *models.Plan) error // UpdatePlanStatus 更新指定计划的状态 - UpdatePlanStatus(id uint, status models.PlanStatus) error + UpdatePlanStatus(ctx context.Context, id uint, status models.PlanStatus) error // UpdateExecuteCount 更新指定计划的执行计数 - UpdateExecuteCount(id uint, count uint) error + UpdateExecuteCount(ctx context.Context, id uint, count uint) error // DeletePlan 根据ID删除计划,同时删除其关联的任务(非子任务)或子计划关联 - DeletePlan(id uint) error + DeletePlan(ctx context.Context, id uint) error // FlattenPlanTasks 递归展开计划,返回按执行顺序排列的所有任务列表 - FlattenPlanTasks(planID uint) ([]models.Task, error) + FlattenPlanTasks(ctx context.Context, planID uint) ([]models.Task, error) // DeleteTask 根据ID删除任务 - DeleteTask(id int) error + DeleteTask(ctx context.Context, id int) error // FindPlanAnalysisTaskByParamsPlanID 根据Parameters中的ParamsPlanID字段值查找TaskPlanAnalysis类型的Task - FindPlanAnalysisTaskByParamsPlanID(paramsPlanID uint) (*models.Task, error) + FindPlanAnalysisTaskByParamsPlanID(ctx context.Context, paramsPlanID uint) (*models.Task, error) // FindRunnablePlans 获取所有应执行的计划 - FindRunnablePlans() ([]*models.Plan, error) + FindRunnablePlans(ctx context.Context) ([]*models.Plan, error) // FindInactivePlans 获取所有已禁用或已停止的计划 - FindInactivePlans() ([]*models.Plan, error) + FindInactivePlans(ctx context.Context) ([]*models.Plan, error) + // FindPlanAnalysisTaskByPlanID 根据 PlanID 找到其关联的 'plan_analysis' 任务 - FindPlanAnalysisTaskByPlanID(planID uint) (*models.Task, error) + FindPlanAnalysisTaskByPlanID(ctx context.Context, planID uint) (*models.Task, error) // CreatePlanAnalysisTask 创建一个 plan_analysis 类型的任务并返回它 - CreatePlanAnalysisTask(plan *models.Plan) (*models.Task, error) + CreatePlanAnalysisTask(ctx context.Context, plan *models.Plan) (*models.Task, error) // FindPlansWithPendingTasks 查找所有正在执行的计划 - FindPlansWithPendingTasks() ([]*models.Plan, error) + FindPlansWithPendingTasks(ctx context.Context) ([]*models.Plan, error) // StopPlanTransactionally 停止一个计划的执行,包括更新状态、移除待执行任务和更新执行日志 - StopPlanTransactionally(planID uint) error + StopPlanTransactionally(ctx context.Context, planID uint) error // UpdatePlanStateAfterExecution 更新计划执行后的状态(计数和状态) - UpdatePlanStateAfterExecution(planID uint, newCount uint, newStatus models.PlanStatus) error + UpdatePlanStateAfterExecution(ctx context.Context, planID uint, newCount uint, newStatus models.PlanStatus) error } // gormPlanRepository 是 PlanRepository 的 GORM 实现 type gormPlanRepository struct { - db *gorm.DB + ctx context.Context + db *gorm.DB } // NewGormPlanRepository 创建一个新的 PlanRepository GORM 实现实例 -func NewGormPlanRepository(db *gorm.DB) PlanRepository { +func NewGormPlanRepository(ctx context.Context, db *gorm.DB) PlanRepository { return &gormPlanRepository{ - db: db, + ctx: ctx, + db: db, } } // ListPlans 获取计划列表,支持过滤和分页 -func (r *gormPlanRepository) ListPlans(opts ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error) { +func (r *gormPlanRepository) ListPlans(ctx context.Context, opts ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListPlans") if page <= 0 || pageSize <= 0 { return nil, 0, ErrInvalidPagination } @@ -107,7 +114,7 @@ func (r *gormPlanRepository) ListPlans(opts ListPlansOptions, page, pageSize int var plans []models.Plan var total int64 - query := r.db.Model(&models.Plan{}) + query := r.db.WithContext(repoCtx).Model(&models.Plan{}) switch opts.PlanType { case PlanTypeFilterCustom: @@ -132,10 +139,11 @@ func (r *gormPlanRepository) ListPlans(opts ListPlansOptions, page, pageSize int } // GetBasicPlanByID 根据ID获取计划的基本信息,不包含子计划和任务详情 -func (r *gormPlanRepository) GetBasicPlanByID(id uint) (*models.Plan, error) { +func (r *gormPlanRepository) GetBasicPlanByID(ctx context.Context, id uint) (*models.Plan, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "GetBasicPlanByID") var plan models.Plan // GORM 默认不会加载关联,除非使用 Preload,所以直接 First 即可满足要求 - result := r.db.First(&plan, id) + result := r.db.WithContext(repoCtx).First(&plan, id) if result.Error != nil { return nil, result.Error } @@ -143,12 +151,13 @@ func (r *gormPlanRepository) GetBasicPlanByID(id uint) (*models.Plan, error) { } // GetPlansByIDs 根据ID列表获取计划,不包含子计划和任务详情 -func (r *gormPlanRepository) GetPlansByIDs(ids []uint) ([]models.Plan, error) { +func (r *gormPlanRepository) GetPlansByIDs(ctx context.Context, ids []uint) ([]models.Plan, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPlansByIDs") var plans []models.Plan if len(ids) == 0 { return plans, nil } - err := r.db.Where("id IN ?", ids).Find(&plans).Error + err := r.db.WithContext(repoCtx).Where("id IN ?", ids).Find(&plans).Error if err != nil { return nil, err } @@ -156,11 +165,12 @@ func (r *gormPlanRepository) GetPlansByIDs(ids []uint) ([]models.Plan, error) { } // GetPlanByID 根据ID获取计划,包含子计划和任务详情 -func (r *gormPlanRepository) GetPlanByID(id uint) (*models.Plan, error) { +func (r *gormPlanRepository) GetPlanByID(ctx context.Context, id uint) (*models.Plan, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPlanByID") var plan models.Plan // 先获取基本计划信息 - result := r.db.First(&plan, id) + result := r.db.WithContext(repoCtx).First(&plan, id) if result.Error != nil { return nil, result.Error } @@ -170,14 +180,14 @@ func (r *gormPlanRepository) GetPlanByID(id uint) (*models.Plan, error) { case models.PlanContentTypeSubPlans: // 加载子计划引用 var subPlans []models.SubPlan - result = r.db.Where("parent_plan_id = ?", plan.ID).Order("execution_order").Find(&subPlans) + result = r.db.WithContext(repoCtx).Where("parent_plan_id = ?", plan.ID).Order("execution_order").Find(&subPlans) if result.Error != nil { return nil, result.Error } // 递归加载每个子计划的完整信息 for i := range subPlans { - childPlan, err := r.GetPlanByID(subPlans[i].ChildPlanID) + childPlan, err := r.GetPlanByID(repoCtx, subPlans[i].ChildPlanID) if err != nil { return nil, err } @@ -187,7 +197,7 @@ func (r *gormPlanRepository) GetPlanByID(id uint) (*models.Plan, error) { plan.SubPlans = subPlans case models.PlanContentTypeTasks: // 加载任务 - result = r.db.Preload("Tasks", func(taskDB *gorm.DB) *gorm.DB { + result = r.db.WithContext(repoCtx).Preload("Tasks", func(taskDB *gorm.DB) *gorm.DB { return taskDB.Order("execution_order") }).First(&plan, id) if result.Error != nil { @@ -201,12 +211,14 @@ func (r *gormPlanRepository) GetPlanByID(id uint) (*models.Plan, error) { } // CreatePlan 创建一个新的计划 -func (r *gormPlanRepository) CreatePlan(plan *models.Plan) error { - return r.CreatePlanTx(r.db, plan) +func (r *gormPlanRepository) CreatePlan(ctx context.Context, plan *models.Plan) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePlan") + return r.CreatePlanTx(repoCtx, r.db, plan) } // CreatePlanTx 在指定事务中创建一个新的计划 -func (r *gormPlanRepository) CreatePlanTx(tx *gorm.DB, plan *models.Plan) error { +func (r *gormPlanRepository) CreatePlanTx(ctx context.Context, tx *gorm.DB, plan *models.Plan) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePlanTx") // 1. 前置校验 if plan.ID != 0 { return ErrCreateWithNonZeroID @@ -239,7 +251,7 @@ func (r *gormPlanRepository) CreatePlanTx(tx *gorm.DB, plan *models.Plan) error if len(ids) > 0 { var count int64 - if err := tx.Model(&models.Plan{}).Where("id IN ?", ids).Count(&count).Error; err != nil { + if err := tx.WithContext(repoCtx).Model(&models.Plan{}).Where("id IN ?", ids).Count(&count).Error; err != nil { return fmt.Errorf("验证子计划存在性失败: %w", err) } if int(count) != len(ids) { @@ -251,13 +263,13 @@ func (r *gormPlanRepository) CreatePlanTx(tx *gorm.DB, plan *models.Plan) error // 2. 创建根计划 // GORM 会自动处理关联的 Tasks (如果 ContentType 是 tasks 且 Task.ID 为 0), // 以及 Tasks 内部已经填充好的 Devices 关联。 - if err := tx.Create(plan).Error; err != nil { + if err := tx.WithContext(repoCtx).Create(plan).Error; err != nil { return err } // 3. 创建触发器Task // 关键修改:调用 createPlanAnalysisTask 并处理其返回的 Task 对象 - _, err := r.createPlanAnalysisTask(tx, plan) + _, err := r.createPlanAnalysisTask(repoCtx, tx, plan) if err != nil { return err } @@ -265,32 +277,36 @@ func (r *gormPlanRepository) CreatePlanTx(tx *gorm.DB, plan *models.Plan) error } // UpdatePlan 更新计划 -func (r *gormPlanRepository) UpdatePlan(plan *models.Plan) error { - return r.db.Save(plan).Error +func (r *gormPlanRepository) UpdatePlan(ctx context.Context, plan *models.Plan) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePlan") + return r.db.WithContext(repoCtx).Save(plan).Error } // UpdatePlanMetadataAndStructure 是更新计划元数据和结构的公共入口点 -func (r *gormPlanRepository) UpdatePlanMetadataAndStructure(plan *models.Plan) error { - return r.db.Transaction(func(tx *gorm.DB) error { - return r.updatePlanMetadataAndStructureTx(tx, plan) +func (r *gormPlanRepository) UpdatePlanMetadataAndStructure(ctx context.Context, plan *models.Plan) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePlanMetadataAndStructure") + return r.db.WithContext(repoCtx).Transaction(func(tx *gorm.DB) error { + return r.updatePlanMetadataAndStructureTx(repoCtx, tx, plan) }) } // updatePlanMetadataAndStructureTx 在事务中协调整个更新过程 -func (r *gormPlanRepository) updatePlanMetadataAndStructureTx(tx *gorm.DB, plan *models.Plan) error { - if err := r.validatePlanTree(tx, plan); err != nil { +func (r *gormPlanRepository) updatePlanMetadataAndStructureTx(ctx context.Context, tx *gorm.DB, plan *models.Plan) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "updatePlanMetadataAndStructureTx") + if err := r.validatePlanTree(repoCtx, tx, plan); err != nil { return err } - if err := r.reconcilePlanNode(tx, plan); err != nil { + if err := r.reconcilePlanNode(repoCtx, tx, plan); err != nil { return err } // 更新Plan触发器 - return r.updatePlanAnalysisTask(tx, plan) + return r.updatePlanAnalysisTask(repoCtx, tx, plan) } // validatePlanTree 对整个计划树进行全面的只读健康检查 -func (r *gormPlanRepository) validatePlanTree(tx *gorm.DB, plan *models.Plan) error { +func (r *gormPlanRepository) validatePlanTree(ctx context.Context, tx *gorm.DB, plan *models.Plan) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "validatePlanTree") // 1. 检查根节点 if plan == nil || plan.ID == 0 { return ErrUpdateWithInvalidRoot @@ -319,7 +335,7 @@ func (r *gormPlanRepository) validatePlanTree(tx *gorm.DB, plan *models.Plan) er if len(idsToCheck) > 0 { var count int64 - if err := tx.Model(&models.Plan{}).Where("id IN ?", idsToCheck).Count(&count).Error; err != nil { + if err := tx.WithContext(repoCtx).Model(&models.Plan{}).Where("id IN ?", idsToCheck).Count(&count).Error; err != nil { return fmt.Errorf("检查计划存在性时出错: %w", err) } if int(count) != len(idsToCheck) { @@ -358,12 +374,13 @@ func validateNodeAndDetectCycles(plan *models.Plan, allIDs, recursionStack map[u } // reconcilePlanNode 递归地同步数据库状态以匹配给定的计划节点 -func (r *gormPlanRepository) reconcilePlanNode(tx *gorm.DB, plan *models.Plan) error { +func (r *gormPlanRepository) reconcilePlanNode(ctx context.Context, tx *gorm.DB, plan *models.Plan) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "reconcilePlanNode") if plan == nil { return nil } // 1. 更新节点本身的基础字段 - if err := tx.Model(plan).Select("Name", "Description", "ExecutionType", "ExecuteNum", "CronExpression", "ContentType").Updates(plan).Error; err != nil { + if err := tx.WithContext(repoCtx).Model(plan).Select("Name", "Description", "ExecutionType", "ExecuteNum", "CronExpression", "ContentType").Updates(plan).Error; err != nil { return err } @@ -371,26 +388,27 @@ func (r *gormPlanRepository) reconcilePlanNode(tx *gorm.DB, plan *models.Plan) e switch plan.ContentType { case models.PlanContentTypeTasks: // 清理旧的子计划关联 - if err := tx.Where("parent_plan_id = ?", plan.ID).Delete(&models.SubPlan{}).Error; err != nil { + if err := tx.WithContext(repoCtx).Where("parent_plan_id = ?", plan.ID).Delete(&models.SubPlan{}).Error; err != nil { return fmt.Errorf("更新时清理旧的子计划关联失败: %w", err) } // 协调任务列表 - return r.reconcileTasks(tx, plan) + return r.reconcileTasks(repoCtx, tx, plan) case models.PlanContentTypeSubPlans: // 清理旧的任务 - if err := tx.Where("plan_id = ?", plan.ID).Delete(&models.Task{}).Error; err != nil { + if err := tx.WithContext(repoCtx).Where("plan_id = ?", plan.ID).Delete(&models.Task{}).Error; err != nil { return fmt.Errorf("更新时清理旧的任务失败: %w", err) } // 协调子计划关联 - return r.reconcileSubPlans(tx, plan) + return r.reconcileSubPlans(repoCtx, tx, plan) } return nil } // reconcileTasks 精确同步任务列表 -func (r *gormPlanRepository) reconcileTasks(tx *gorm.DB, plan *models.Plan) error { +func (r *gormPlanRepository) reconcileTasks(ctx context.Context, tx *gorm.DB, plan *models.Plan) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "reconcileTasks") var existingTasks []models.Task - if err := tx.Where("plan_id = ?", plan.ID).Find(&existingTasks).Error; err != nil { + if err := tx.WithContext(repoCtx).Where("plan_id = ?", plan.ID).Find(&existingTasks).Error; err != nil { return err } @@ -403,12 +421,12 @@ func (r *gormPlanRepository) reconcileTasks(tx *gorm.DB, plan *models.Plan) erro task := &plan.Tasks[i] if task.ID == 0 { task.PlanID = plan.ID - if err := tx.Create(task).Error; err != nil { + if err := tx.WithContext(repoCtx).Create(task).Error; err != nil { return err } } else { delete(existingTaskMap, task.ID) // 从待删除map中移除 - if err := tx.Model(task).Updates(task).Error; err != nil { + if err := tx.WithContext(repoCtx).Model(task).Updates(task).Error; err != nil { return err } } @@ -420,15 +438,16 @@ func (r *gormPlanRepository) reconcileTasks(tx *gorm.DB, plan *models.Plan) erro } if len(tasksToDelete) > 0 { - return r.deleteTasksTx(tx, tasksToDelete) + return r.deleteTasksTx(repoCtx, tx, tasksToDelete) } return nil } // reconcileSubPlans 精确同步子计划关联并递归 -func (r *gormPlanRepository) reconcileSubPlans(tx *gorm.DB, plan *models.Plan) error { +func (r *gormPlanRepository) reconcileSubPlans(ctx context.Context, tx *gorm.DB, plan *models.Plan) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "reconcileSubPlans") var existingLinks []models.SubPlan - if err := tx.Where("parent_plan_id = ?", plan.ID).Find(&existingLinks).Error; err != nil { + if err := tx.WithContext(repoCtx).Where("parent_plan_id = ?", plan.ID).Find(&existingLinks).Error; err != nil { return err } @@ -441,12 +460,12 @@ func (r *gormPlanRepository) reconcileSubPlans(tx *gorm.DB, plan *models.Plan) e link := &plan.SubPlans[i] link.ParentPlanID = plan.ID if link.ID == 0 { - if err := tx.Create(link).Error; err != nil { + if err := tx.WithContext(repoCtx).Create(link).Error; err != nil { return err } } else { delete(existingLinkMap, link.ID) // 从待删除map中移除 - if err := tx.Model(link).Updates(link).Error; err != nil { + if err := tx.WithContext(repoCtx).Model(link).Updates(link).Error; err != nil { return err } } @@ -458,7 +477,7 @@ func (r *gormPlanRepository) reconcileSubPlans(tx *gorm.DB, plan *models.Plan) e } if len(linksToDelete) > 0 { - if err := tx.Delete(&models.SubPlan{}, linksToDelete).Error; err != nil { + if err := tx.WithContext(repoCtx).Delete(&models.SubPlan{}, linksToDelete).Error; err != nil { return err } } @@ -466,11 +485,12 @@ func (r *gormPlanRepository) reconcileSubPlans(tx *gorm.DB, plan *models.Plan) e } // DeletePlan 根据ID删除计划,同时删除其关联的任务(非子任务)或子计划关联 -func (r *gormPlanRepository) DeletePlan(id uint) error { - return r.db.Transaction(func(tx *gorm.DB) error { +func (r *gormPlanRepository) DeletePlan(ctx context.Context, id uint) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "DeletePlan") + return r.db.WithContext(repoCtx).Transaction(func(tx *gorm.DB) error { // 1. 检查该计划是否是其他计划的子计划 var count int64 - if err := tx.Model(&models.SubPlan{}).Where("child_plan_id = ?", id).Count(&count).Error; err != nil { + if err := tx.WithContext(repoCtx).Model(&models.SubPlan{}).Where("child_plan_id = ?", id).Count(&count).Error; err != nil { return fmt.Errorf("检查计划是否为子计划失败: %w", err) } if count > 0 { @@ -479,7 +499,7 @@ func (r *gormPlanRepository) DeletePlan(id uint) error { var plan models.Plan // 2. 获取计划以确定其内容类型 - if err := tx.First(&plan, id).Error; err != nil { + if err := tx.WithContext(repoCtx).First(&plan, id).Error; err != nil { return err } @@ -487,18 +507,18 @@ func (r *gormPlanRepository) DeletePlan(id uint) error { switch plan.ContentType { case models.PlanContentTypeTasks: // 删除与此计划关联的所有非子任务 - if err := tx.Where("plan_id = ?", id).Delete(&models.Task{}).Error; err != nil { + if err := tx.WithContext(repoCtx).Where("plan_id = ?", id).Delete(&models.Task{}).Error; err != nil { return fmt.Errorf("删除计划ID %d 的任务失败: %w", id, err) } case models.PlanContentTypeSubPlans: // 删除与此计划关联的所有子计划链接 - if err := tx.Where("parent_plan_id = ?", id).Delete(&models.SubPlan{}).Error; err != nil { + if err := tx.WithContext(repoCtx).Where("parent_plan_id = ?", id).Delete(&models.SubPlan{}).Error; err != nil { return fmt.Errorf("删除计划ID %d 的子计划关联失败: %w", id, err) } } // 4. 删除计划本身 - if err := tx.Delete(&models.Plan{}, id).Error; err != nil { + if err := tx.WithContext(repoCtx).Delete(&models.Plan{}, id).Error; err != nil { return fmt.Errorf("删除计划ID %d 失败: %w", id, err) } @@ -507,17 +527,19 @@ func (r *gormPlanRepository) DeletePlan(id uint) error { } // FlattenPlanTasks 递归展开计划,返回按执行顺序排列的所有任务列表 -func (r *gormPlanRepository) FlattenPlanTasks(planID uint) ([]models.Task, error) { - plan, err := r.GetPlanByID(planID) +func (r *gormPlanRepository) FlattenPlanTasks(ctx context.Context, planID uint) ([]models.Task, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FlattenPlanTasks") + plan, err := r.GetPlanByID(repoCtx, planID) if err != nil { return nil, fmt.Errorf("获取计划(ID: %d)失败: %w", planID, err) } - return r.flattenPlanTasksRecursive(plan) + return r.flattenPlanTasksRecursive(repoCtx, plan) } // flattenPlanTasksRecursive 递归展开计划的内部实现 -func (r *gormPlanRepository) flattenPlanTasksRecursive(plan *models.Plan) ([]models.Task, error) { +func (r *gormPlanRepository) flattenPlanTasksRecursive(ctx context.Context, plan *models.Plan) ([]models.Task, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "flattenPlanTasksRecursive") var tasks []models.Task switch plan.ContentType { @@ -535,10 +557,10 @@ func (r *gormPlanRepository) flattenPlanTasksRecursive(plan *models.Plan) ([]mod // 确保子计划已经被加载 if subPlan.ChildPlan != nil { - subTasks, err = r.flattenPlanTasksRecursive(subPlan.ChildPlan) + subTasks, err = r.flattenPlanTasksRecursive(repoCtx, subPlan.ChildPlan) } else { // 如果子计划未加载,则从数据库获取并递归展开 - subTasks, err = r.FlattenPlanTasks(subPlan.ChildPlanID) + subTasks, err = r.FlattenPlanTasks(repoCtx, subPlan.ChildPlanID) } if err != nil { @@ -556,22 +578,24 @@ func (r *gormPlanRepository) flattenPlanTasksRecursive(plan *models.Plan) ([]mod } // DeleteTask 根据ID删除任务 -func (r *gormPlanRepository) DeleteTask(id int) error { +func (r *gormPlanRepository) DeleteTask(ctx context.Context, id int) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "DeleteTask") // 使用事务确保操作的原子性 - return r.db.Transaction(func(tx *gorm.DB) error { - return r.deleteTasksTx(tx, []int{id}) + return r.db.WithContext(repoCtx).Transaction(func(tx *gorm.DB) error { + return r.deleteTasksTx(repoCtx, tx, []int{id}) }) } // deleteTasksTx 在事务中批量软删除任务,并物理删除其在关联表中的记录 -func (r *gormPlanRepository) deleteTasksTx(tx *gorm.DB, ids []int) error { +func (r *gormPlanRepository) deleteTasksTx(ctx context.Context, tx *gorm.DB, ids []int) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "deleteTasksTx") if len(ids) == 0 { return nil } // 检查是否有待执行任务引用了这些任务 var pendingTaskCount int64 - if err := tx.Model(&models.PendingTask{}).Where("task_id IN ?", ids).Count(&pendingTaskCount).Error; err != nil { + if err := tx.WithContext(repoCtx).Model(&models.PendingTask{}).Where("task_id IN ?", ids).Count(&pendingTaskCount).Error; err != nil { return fmt.Errorf("检查待执行任务时出错: %w", err) } @@ -584,12 +608,12 @@ func (r *gormPlanRepository) deleteTasksTx(tx *gorm.DB, ids []int) error { // 1. 直接、高效地从关联表中物理删除所有相关记录 // 这是最关键的优化,避免了不必要的查询和循环 - if err := tx.Where("task_id IN ?", ids).Delete(&models.DeviceTask{}).Error; err != nil { + if err := tx.WithContext(repoCtx).Where("task_id IN ?", ids).Delete(&models.DeviceTask{}).Error; err != nil { return fmt.Errorf("清理任务的设备关联失败: %w", err) } // 2. 对任务本身进行软删除 - result := tx.Delete(&models.Task{}, ids) + result := tx.WithContext(repoCtx).Delete(&models.Task{}, ids) if result.Error != nil { return result.Error } @@ -603,13 +627,15 @@ func (r *gormPlanRepository) deleteTasksTx(tx *gorm.DB, ids []int) error { } // FindPlanAnalysisTaskByParamsPlanID 根据Parameters中的ParamsPlanID字段值查找TaskPlanAnalysis类型的Task -func (r *gormPlanRepository) FindPlanAnalysisTaskByParamsPlanID(paramsPlanID uint) (*models.Task, error) { - return r.findPlanAnalysisTask(r.db, paramsPlanID) +func (r *gormPlanRepository) FindPlanAnalysisTaskByParamsPlanID(ctx context.Context, paramsPlanID uint) (*models.Task, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindPlanAnalysisTaskByParamsPlanID") + return r.findPlanAnalysisTask(repoCtx, r.db, paramsPlanID) } // createPlanAnalysisTask 用于创建一个TaskPlanAnalysis类型的Task // 关键修改:Task.PlanID 设置为 0,实际 PlanID 存储在 Parameters 中,并返回创建的 Task -func (r *gormPlanRepository) createPlanAnalysisTask(tx *gorm.DB, plan *models.Plan) (*models.Task, error) { +func (r *gormPlanRepository) createPlanAnalysisTask(ctx context.Context, tx *gorm.DB, plan *models.Plan) (*models.Task, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "createPlanAnalysisTask") m := map[string]interface{}{ models.ParamsPlanID: plan.ID, } @@ -627,22 +653,23 @@ func (r *gormPlanRepository) createPlanAnalysisTask(tx *gorm.DB, plan *models.Pl Parameters: datatypes.JSON(parameters), } - if err := tx.Create(task).Error; err != nil { + if err := tx.WithContext(repoCtx).Create(task).Error; err != nil { return nil, err } return task, nil } // updatePlanAnalysisTask 使用更安全的方式更新触发器任务 -func (r *gormPlanRepository) updatePlanAnalysisTask(tx *gorm.DB, plan *models.Plan) error { - task, err := r.findPlanAnalysisTask(tx, plan.ID) +func (r *gormPlanRepository) updatePlanAnalysisTask(ctx context.Context, tx *gorm.DB, plan *models.Plan) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "updatePlanAnalysisTask") + task, err := r.findPlanAnalysisTask(repoCtx, tx, plan.ID) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return fmt.Errorf("查找现有计划分析任务失败: %w", err) } // 如果触发器任务不存在,则创建一个 if task == nil { - _, err := r.createPlanAnalysisTask(tx, plan) + _, err := r.createPlanAnalysisTask(repoCtx, tx, plan) return err } @@ -650,24 +677,26 @@ func (r *gormPlanRepository) updatePlanAnalysisTask(tx *gorm.DB, plan *models.Pl task.Name = fmt.Sprintf("'%s'计划触发器", plan.Name) task.Description = fmt.Sprintf("计划名: %s, 计划ID: %d", plan.Name, plan.ID) - return tx.Save(task).Error + return tx.WithContext(repoCtx).Save(task).Error } -func (r *gormPlanRepository) FindRunnablePlans() ([]*models.Plan, error) { +func (r *gormPlanRepository) FindRunnablePlans(ctx context.Context) ([]*models.Plan, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindRunnablePlans") var plans []*models.Plan - err := r.db. + err := r.db.WithContext(repoCtx). Where("status = ?", models.PlanStatusEnabled). Where( - r.db.Where("execution_type = ?", models.PlanExecutionTypeManual). + r.db.WithContext(repoCtx).Where("execution_type = ?", models.PlanExecutionTypeManual). Or("execution_type = ? AND (execute_num = 0 OR execute_count < execute_num)", models.PlanExecutionTypeAutomatic), ). Find(&plans).Error return plans, err } -func (r *gormPlanRepository) FindInactivePlans() ([]*models.Plan, error) { +func (r *gormPlanRepository) FindInactivePlans(ctx context.Context) ([]*models.Plan, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindInactivePlans") var plans []*models.Plan - err := r.db. + err := r.db.WithContext(repoCtx). Where("status != ?", models.PlanStatusEnabled). Find(&plans).Error return plans, err @@ -675,9 +704,10 @@ func (r *gormPlanRepository) FindInactivePlans() ([]*models.Plan, error) { // findPlanAnalysisTask 是一个内部使用的、更高效的查找方法 // 关键修改:通过查询 parameters JSON 字段来查找 -func (r *gormPlanRepository) findPlanAnalysisTask(tx *gorm.DB, planID uint) (*models.Task, error) { +func (r *gormPlanRepository) findPlanAnalysisTask(ctx context.Context, tx *gorm.DB, planID uint) (*models.Task, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "findPlanAnalysisTask") var task models.Task - err := tx.Where("type = ? AND parameters->>'plan_id' = ?", models.TaskPlanAnalysis, fmt.Sprintf("%d", planID)).First(&task).Error + err := tx.WithContext(repoCtx).Where("type = ? AND parameters->>'plan_id' = ?", models.TaskPlanAnalysis, fmt.Sprintf("%d", planID)).First(&task).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil // 未找到不是错误,返回nil, nil } @@ -686,28 +716,31 @@ func (r *gormPlanRepository) findPlanAnalysisTask(tx *gorm.DB, planID uint) (*mo // FindPlanAnalysisTaskByPlanID 是暴露给外部的公共方法 // 关键修改:通过查询 parameters JSON 字段来查找 -func (r *gormPlanRepository) FindPlanAnalysisTaskByPlanID(planID uint) (*models.Task, error) { - return r.findPlanAnalysisTask(r.db, planID) +func (r *gormPlanRepository) FindPlanAnalysisTaskByPlanID(ctx context.Context, planID uint) (*models.Task, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindPlanAnalysisTaskByPlanID") + return r.findPlanAnalysisTask(repoCtx, r.db, planID) } // CreatePlanAnalysisTask 创建一个 plan_analysis 类型的任务并返回它 // 这个方法是公开的,主要由 TaskManager 在发现触发器任务定义丢失时调用。 -func (r *gormPlanRepository) CreatePlanAnalysisTask(plan *models.Plan) (*models.Task, error) { +func (r *gormPlanRepository) CreatePlanAnalysisTask(ctx context.Context, plan *models.Plan) (*models.Task, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePlanAnalysisTask") var createdTask *models.Task - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(repoCtx).Transaction(func(tx *gorm.DB) error { var err error - createdTask, err = r.createPlanAnalysisTask(tx, plan) + createdTask, err = r.createPlanAnalysisTask(repoCtx, tx, plan) return err }) return createdTask, err } // FindPlansWithPendingTasks 查找所有正在执行的计划 -func (r *gormPlanRepository) FindPlansWithPendingTasks() ([]*models.Plan, error) { +func (r *gormPlanRepository) FindPlansWithPendingTasks(ctx context.Context) ([]*models.Plan, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindPlansWithPendingTasks") var plans []*models.Plan // 关联 pending_tasks, task_execution_logs, tasks 表来查找符合条件的计划 - err := r.db.Table("plans"). + err := r.db.WithContext(repoCtx).Table("plans"). Joins("JOIN tasks ON plans.id = tasks.plan_id"). Joins("JOIN task_execution_logs ON tasks.id = task_execution_logs.task_id"). Joins("JOIN pending_tasks ON task_execution_logs.id = pending_tasks.task_execution_log_id"). @@ -718,20 +751,21 @@ func (r *gormPlanRepository) FindPlansWithPendingTasks() ([]*models.Plan, error) } // StopPlanTransactionally 停止一个计划的执行,包括更新状态、移除待执行任务和更新执行日志。 -func (r *gormPlanRepository) StopPlanTransactionally(planID uint) error { - return r.db.Transaction(func(tx *gorm.DB) error { +func (r *gormPlanRepository) StopPlanTransactionally(ctx context.Context, planID uint) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "StopPlanTransactionally") + return r.db.WithContext(repoCtx).Transaction(func(tx *gorm.DB) error { // 使用事务创建新的仓库实例,确保所有操作都在同一个事务中 - planRepoTx := NewGormPlanRepository(tx) - executionLogRepoTx := NewGormExecutionLogRepository(tx) - pendingTaskRepoTx := NewGormPendingTaskRepository(tx) + planRepoTx := NewGormPlanRepository(repoCtx, tx) + executionLogRepoTx := NewGormExecutionLogRepository(repoCtx, tx) + pendingTaskRepoTx := NewGormPendingTaskRepository(repoCtx, tx) // 1. 更新计划状态为“已停止” - if err := planRepoTx.UpdatePlanStatus(planID, models.PlanStatusDisabled); err != nil { + if err := planRepoTx.UpdatePlanStatus(repoCtx, planID, models.PlanStatusDisabled); err != nil { return fmt.Errorf("更新计划 #%d 状态为 '已停止' 失败: %w", planID, err) } // 2. 查找当前正在进行的计划执行日志 - planLog, err := executionLogRepoTx.FindInProgressPlanExecutionLogByPlanID(planID) + planLog, err := executionLogRepoTx.FindInProgressPlanExecutionLogByPlanID(repoCtx, planID) if err != nil { return fmt.Errorf("查找计划 #%d 正在进行的执行日志失败: %w", planID, err) } @@ -742,7 +776,7 @@ func (r *gormPlanRepository) StopPlanTransactionally(planID uint) error { } // 3. 查找所有需要被取消的任务执行日志 - taskLogs, err := executionLogRepoTx.FindIncompleteTaskExecutionLogsByPlanLogID(planLog.ID) + taskLogs, err := executionLogRepoTx.FindIncompleteTaskExecutionLogsByPlanLogID(repoCtx, planLog.ID) if err != nil { return fmt.Errorf("查找计划执行日志 #%d 下未完成的任务日志失败: %w", planLog.ID, err) } @@ -754,12 +788,12 @@ func (r *gormPlanRepository) StopPlanTransactionally(planID uint) error { } // 3.1 批量更新任务执行日志状态为“已取消” - if err := executionLogRepoTx.UpdateTaskExecutionLogStatusByIDs(taskLogIDs, models.ExecutionStatusCancelled); err != nil { + if err := executionLogRepoTx.UpdateTaskExecutionLogStatusByIDs(repoCtx, taskLogIDs, models.ExecutionStatusCancelled); err != nil { return fmt.Errorf("批量更新任务执行日志状态为 '已取消' 失败: %w", err) } // 3.2 查找并删除待执行队列中对应的任务 - pendingTasks, err := pendingTaskRepoTx.FindPendingTasksByTaskLogIDs(taskLogIDs) + pendingTasks, err := pendingTaskRepoTx.FindPendingTasksByTaskLogIDs(repoCtx, taskLogIDs) if err != nil { return fmt.Errorf("查找计划执行日志 #%d 下对应的待执行任务失败: %w", planLog.ID, err) } @@ -769,14 +803,14 @@ func (r *gormPlanRepository) StopPlanTransactionally(planID uint) error { for _, pt := range pendingTasks { pendingTaskIDs = append(pendingTaskIDs, pt.ID) } - if err := pendingTaskRepoTx.DeletePendingTasksByIDs(pendingTaskIDs); err != nil { + if err := pendingTaskRepoTx.DeletePendingTasksByIDs(repoCtx, pendingTaskIDs); err != nil { return fmt.Errorf("批量删除待执行任务失败: %w", err) } } } // 4. 更新计划执行历史的总状态为“失败” - if err := executionLogRepoTx.UpdatePlanExecutionLogStatus(planLog.ID, models.ExecutionStatusFailed); err != nil { + if err := executionLogRepoTx.UpdatePlanExecutionLogStatus(repoCtx, planLog.ID, models.ExecutionStatusFailed); err != nil { return fmt.Errorf("更新计划执行日志 #%d 状态为 '失败' 失败: %w", planLog.ID, err) } @@ -785,8 +819,9 @@ func (r *gormPlanRepository) StopPlanTransactionally(planID uint) error { } // UpdatePlanStatus 更新指定计划的状态 -func (r *gormPlanRepository) UpdatePlanStatus(id uint, status models.PlanStatus) error { - result := r.db.Model(&models.Plan{}).Where("id = ?", id).Update("status", status) +func (r *gormPlanRepository) UpdatePlanStatus(ctx context.Context, id uint, status models.PlanStatus) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePlanStatus") + result := r.db.WithContext(repoCtx).Model(&models.Plan{}).Where("id = ?", id).Update("status", status) if result.Error != nil { return result.Error } @@ -796,16 +831,18 @@ func (r *gormPlanRepository) UpdatePlanStatus(id uint, status models.PlanStatus) return nil } -func (r *gormPlanRepository) UpdatePlanStateAfterExecution(planID uint, newCount uint, newStatus models.PlanStatus) error { - return r.db.Model(&models.Plan{}).Where("id = ?", planID).Updates(map[string]interface{}{ +func (r *gormPlanRepository) UpdatePlanStateAfterExecution(ctx context.Context, planID uint, newCount uint, newStatus models.PlanStatus) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePlanStateAfterExecution") + return r.db.WithContext(repoCtx).Model(&models.Plan{}).Where("id = ?", planID).Updates(map[string]interface{}{ "execute_count": newCount, "status": newStatus, }).Error } // UpdateExecuteCount 更新指定计划的执行计数 -func (r *gormPlanRepository) UpdateExecuteCount(id uint, count uint) error { - result := r.db.Model(&models.Plan{}).Where("id = ?", id).Update("execute_count", count) +func (r *gormPlanRepository) UpdateExecuteCount(ctx context.Context, id uint, count uint) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateExecuteCount") + result := r.db.WithContext(repoCtx).Model(&models.Plan{}).Where("id = ?", id).Update("execute_count", count) if result.Error != nil { return result.Error } diff --git a/internal/infra/repository/raw_material_repository.go b/internal/infra/repository/raw_material_repository.go index 1d6d230..ba6a757 100644 --- a/internal/infra/repository/raw_material_repository.go +++ b/internal/infra/repository/raw_material_repository.go @@ -1,9 +1,12 @@ package repository import ( + "context" "time" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/gorm" ) @@ -38,23 +41,25 @@ type FeedUsageRecordListOptions struct { // RawMaterialRepository 定义了与原料相关的数据库操作接口 type RawMaterialRepository interface { - ListRawMaterialPurchases(opts RawMaterialPurchaseListOptions, page, pageSize int) ([]models.RawMaterialPurchase, int64, error) - ListRawMaterialStockLogs(opts RawMaterialStockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error) - ListFeedUsageRecords(opts FeedUsageRecordListOptions, page, pageSize int) ([]models.FeedUsageRecord, int64, error) + ListRawMaterialPurchases(ctx context.Context, opts RawMaterialPurchaseListOptions, page, pageSize int) ([]models.RawMaterialPurchase, int64, error) + ListRawMaterialStockLogs(ctx context.Context, opts RawMaterialStockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error) + ListFeedUsageRecords(ctx context.Context, opts FeedUsageRecordListOptions, page, pageSize int) ([]models.FeedUsageRecord, int64, error) } // gormRawMaterialRepository 是 RawMaterialRepository 的 GORM 实现 type gormRawMaterialRepository struct { - db *gorm.DB + ctx context.Context + db *gorm.DB } // NewGormRawMaterialRepository 创建一个新的 RawMaterialRepository GORM 实现实例 -func NewGormRawMaterialRepository(db *gorm.DB) RawMaterialRepository { - return &gormRawMaterialRepository{db: db} +func NewGormRawMaterialRepository(ctx context.Context, db *gorm.DB) RawMaterialRepository { + return &gormRawMaterialRepository{ctx: ctx, db: db} } // ListRawMaterialPurchases 实现了分页和过滤查询原料采购记录的功能 -func (r *gormRawMaterialRepository) ListRawMaterialPurchases(opts RawMaterialPurchaseListOptions, page, pageSize int) ([]models.RawMaterialPurchase, int64, error) { +func (r *gormRawMaterialRepository) ListRawMaterialPurchases(ctx context.Context, opts RawMaterialPurchaseListOptions, page, pageSize int) ([]models.RawMaterialPurchase, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListRawMaterialPurchases") if page <= 0 || pageSize <= 0 { return nil, 0, ErrInvalidPagination } @@ -62,7 +67,7 @@ func (r *gormRawMaterialRepository) ListRawMaterialPurchases(opts RawMaterialPur var results []models.RawMaterialPurchase var total int64 - query := r.db.Model(&models.RawMaterialPurchase{}) + query := r.db.WithContext(repoCtx).Model(&models.RawMaterialPurchase{}) if opts.RawMaterialID != nil { query = query.Where("raw_material_id = ?", *opts.RawMaterialID) @@ -94,7 +99,8 @@ func (r *gormRawMaterialRepository) ListRawMaterialPurchases(opts RawMaterialPur } // ListRawMaterialStockLogs 实现了分页和过滤查询原料库存日志的功能 -func (r *gormRawMaterialRepository) ListRawMaterialStockLogs(opts RawMaterialStockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error) { +func (r *gormRawMaterialRepository) ListRawMaterialStockLogs(ctx context.Context, opts RawMaterialStockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListRawMaterialStockLogs") if page <= 0 || pageSize <= 0 { return nil, 0, ErrInvalidPagination } @@ -102,7 +108,7 @@ func (r *gormRawMaterialRepository) ListRawMaterialStockLogs(opts RawMaterialSto var results []models.RawMaterialStockLog var total int64 - query := r.db.Model(&models.RawMaterialStockLog{}) + query := r.db.WithContext(repoCtx).Model(&models.RawMaterialStockLog{}) if opts.RawMaterialID != nil { query = query.Where("raw_material_id = ?", *opts.RawMaterialID) @@ -137,7 +143,8 @@ func (r *gormRawMaterialRepository) ListRawMaterialStockLogs(opts RawMaterialSto } // ListFeedUsageRecords 实现了分页和过滤查询饲料使用记录的功能 -func (r *gormRawMaterialRepository) ListFeedUsageRecords(opts FeedUsageRecordListOptions, page, pageSize int) ([]models.FeedUsageRecord, int64, error) { +func (r *gormRawMaterialRepository) ListFeedUsageRecords(ctx context.Context, opts FeedUsageRecordListOptions, page, pageSize int) ([]models.FeedUsageRecord, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListFeedUsageRecords") if page <= 0 || pageSize <= 0 { return nil, 0, ErrInvalidPagination } @@ -145,7 +152,7 @@ func (r *gormRawMaterialRepository) ListFeedUsageRecords(opts FeedUsageRecordLis var results []models.FeedUsageRecord var total int64 - query := r.db.Model(&models.FeedUsageRecord{}) + query := r.db.WithContext(repoCtx).Model(&models.FeedUsageRecord{}) if opts.PenID != nil { query = query.Where("pen_id = ?", *opts.PenID) diff --git a/internal/infra/repository/sensor_data_repository.go b/internal/infra/repository/sensor_data_repository.go index ee01117..70c8810 100644 --- a/internal/infra/repository/sensor_data_repository.go +++ b/internal/infra/repository/sensor_data_repository.go @@ -1,9 +1,12 @@ package repository import ( + "context" "time" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/gorm" ) @@ -18,39 +21,43 @@ type SensorDataListOptions struct { // SensorDataRepository 定义了与传感器数据相关的数据库操作接口。 type SensorDataRepository interface { - Create(sensorData *models.SensorData) error - GetLatestSensorDataByDeviceIDAndSensorType(deviceID uint, sensorType models.SensorType) (*models.SensorData, error) + Create(ctx context.Context, sensorData *models.SensorData) error + GetLatestSensorDataByDeviceIDAndSensorType(ctx context.Context, deviceID uint, sensorType models.SensorType) (*models.SensorData, error) // List 支持分页和过滤的列表查询 - List(opts SensorDataListOptions, page, pageSize int) ([]models.SensorData, int64, error) + List(ctx context.Context, opts SensorDataListOptions, page, pageSize int) ([]models.SensorData, int64, error) } // gormSensorDataRepository 是 SensorDataRepository 的 GORM 实现。 type gormSensorDataRepository struct { - db *gorm.DB + ctx context.Context + db *gorm.DB } // NewGormSensorDataRepository 创建一个新的 SensorDataRepository GORM 实现实例。 -func NewGormSensorDataRepository(db *gorm.DB) SensorDataRepository { - return &gormSensorDataRepository{db: db} +func NewGormSensorDataRepository(ctx context.Context, db *gorm.DB) SensorDataRepository { + return &gormSensorDataRepository{ctx: ctx, db: db} } // Create 将一条新的传感器数据记录插入数据库。 -func (r *gormSensorDataRepository) Create(sensorData *models.SensorData) error { - return r.db.Create(sensorData).Error +func (r *gormSensorDataRepository) Create(ctx context.Context, sensorData *models.SensorData) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "Create") + return r.db.WithContext(repoCtx).Create(sensorData).Error } // GetLatestSensorDataByDeviceIDAndSensorType 根据设备ID和传感器类型查询最新的传感器数据。 -func (r *gormSensorDataRepository) GetLatestSensorDataByDeviceIDAndSensorType(deviceID uint, sensorType models.SensorType) (*models.SensorData, error) { +func (r *gormSensorDataRepository) GetLatestSensorDataByDeviceIDAndSensorType(ctx context.Context, deviceID uint, sensorType models.SensorType) (*models.SensorData, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "GetLatestSensorDataByDeviceIDAndSensorType") var sensorData models.SensorData // 增加一个时间范围来缩小查询范围, 从而加快查找速度, 当使用时序数据库时时间范围可以让数据库忽略时间靠前的分片 - err := r.db.Where("device_id = ? AND sensor_type = ? AND time >=?", deviceID, sensorType, time.Now().Add(-24*time.Hour)). + err := r.db.WithContext(repoCtx).Where("device_id = ? AND sensor_type = ? AND time >=?", deviceID, sensorType, time.Now().Add(-24*time.Hour)). Order("time DESC"). First(&sensorData).Error return &sensorData, err } // List 实现了分页和过滤查询传感器数据的功能 -func (r *gormSensorDataRepository) List(opts SensorDataListOptions, page, pageSize int) ([]models.SensorData, int64, error) { +func (r *gormSensorDataRepository) List(ctx context.Context, opts SensorDataListOptions, page, pageSize int) ([]models.SensorData, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "List") // --- 校验分页参数 --- if page <= 0 || pageSize <= 0 { return nil, 0, ErrInvalidPagination @@ -59,7 +66,7 @@ func (r *gormSensorDataRepository) List(opts SensorDataListOptions, page, pageSi var results []models.SensorData var total int64 - query := r.db.Model(&models.SensorData{}) + query := r.db.WithContext(repoCtx).Model(&models.SensorData{}) // --- 应用过滤条件 --- if opts.DeviceID != nil { diff --git a/internal/infra/repository/unit_of_work.go b/internal/infra/repository/unit_of_work.go index 7929476..9e189cc 100644 --- a/internal/infra/repository/unit_of_work.go +++ b/internal/infra/repository/unit_of_work.go @@ -1,9 +1,11 @@ package repository import ( + "context" "fmt" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "gorm.io/gorm" ) @@ -12,35 +14,40 @@ type UnitOfWork interface { // ExecuteInTransaction 在一个数据库事务中执行给定的函数。 // 如果函数返回错误,事务将被回滚;否则,事务将被提交。 // tx 参数是当前事务的 GORM DB 实例,应传递给所有仓库方法。 - ExecuteInTransaction(fn func(tx *gorm.DB) error) error + ExecuteInTransaction(ctx context.Context, fn func(tx *gorm.DB) error) error } // gormUnitOfWork 是 UnitOfWork 接口的 GORM 实现 type gormUnitOfWork struct { - db *gorm.DB - logger *logs.Logger // 添加日志记录器 + ctx context.Context + db *gorm.DB } // NewGormUnitOfWork 创建一个新的 gormUnitOfWork 实例 -func NewGormUnitOfWork(db *gorm.DB, logger *logs.Logger) UnitOfWork { - return &gormUnitOfWork{db: db, logger: logger} +func NewGormUnitOfWork(ctx context.Context, db *gorm.DB) UnitOfWork { + return &gormUnitOfWork{ + ctx: ctx, + db: db, + } } // ExecuteInTransaction 实现了 UnitOfWork 接口的事务执行逻辑 -func (u *gormUnitOfWork) ExecuteInTransaction(fn func(tx *gorm.DB) error) error { - tx := u.db.Begin() +func (u *gormUnitOfWork) ExecuteInTransaction(ctx context.Context, fn func(tx *gorm.DB) error) error { + uowCtx, logger := logs.Trace(ctx, u.ctx, "ExecuteInTransaction") + + tx := u.db.WithContext(uowCtx).Begin() if tx.Error != nil { - u.logger.Errorf("开启数据库事务失败: %v", tx.Error) // 记录错误日志 + logger.Errorf("开启数据库事务失败: %v", tx.Error) // 记录错误日志 return fmt.Errorf("开启事务失败: %w", tx.Error) } defer func() { if r := recover(); r != nil { tx.Rollback() - u.logger.Errorf("事务中发生 panic,已回滚: %v", r) // 记录 panic 日志 + logger.Errorf("事务中发生 panic,已回滚: %v", r) // 记录 panic 日志 } else if tx.Error != nil { // 如果函数执行过程中返回错误,或者事务本身有错误,则回滚 tx.Rollback() - u.logger.Errorf("事务执行失败,已回滚: %v", tx.Error) // 记录错误日志 + logger.Errorf("事务执行失败,已回滚: %v", tx.Error) // 记录错误日志 } }() @@ -52,7 +59,7 @@ func (u *gormUnitOfWork) ExecuteInTransaction(fn func(tx *gorm.DB) error) error // 提交事务 if err := tx.Commit().Error; err != nil { - u.logger.Errorf("提交数据库事务失败: %v", err) // 记录错误日志 + logger.Errorf("提交数据库事务失败: %v", err) // 记录错误日志 return fmt.Errorf("提交事务失败: %w", err) } diff --git a/internal/infra/repository/user_action_log_repository.go b/internal/infra/repository/user_action_log_repository.go index b501c0a..e0102e9 100644 --- a/internal/infra/repository/user_action_log_repository.go +++ b/internal/infra/repository/user_action_log_repository.go @@ -1,9 +1,12 @@ package repository import ( + "context" "time" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/gorm" ) @@ -20,27 +23,30 @@ type UserActionLogListOptions struct { // UserActionLogRepository 定义了与用户操作日志相关的数据库操作接口 type UserActionLogRepository interface { - Create(log *models.UserActionLog) error - List(opts UserActionLogListOptions, page, pageSize int) ([]models.UserActionLog, int64, error) + Create(ctx context.Context, log *models.UserActionLog) error + List(ctx context.Context, opts UserActionLogListOptions, page, pageSize int) ([]models.UserActionLog, int64, error) } // gormUserActionLogRepository 是 UserActionLogRepository 的 GORM 实现 type gormUserActionLogRepository struct { - db *gorm.DB + ctx context.Context + db *gorm.DB } // NewGormUserActionLogRepository 创建一个新的 UserActionLogRepository GORM 实现实例 -func NewGormUserActionLogRepository(db *gorm.DB) UserActionLogRepository { - return &gormUserActionLogRepository{db: db} +func NewGormUserActionLogRepository(ctx context.Context, db *gorm.DB) UserActionLogRepository { + return &gormUserActionLogRepository{ctx: ctx, db: db} } // Create 创建一条新的用户操作日志记录 -func (r *gormUserActionLogRepository) Create(log *models.UserActionLog) error { - return r.db.Create(log).Error +func (r *gormUserActionLogRepository) Create(ctx context.Context, log *models.UserActionLog) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "Create") + return r.db.WithContext(repoCtx).Create(log).Error } // List 根据选项查询用户操作日志,并返回总数 -func (r *gormUserActionLogRepository) List(opts UserActionLogListOptions, page, pageSize int) ([]models.UserActionLog, int64, error) { +func (r *gormUserActionLogRepository) List(ctx context.Context, opts UserActionLogListOptions, page, pageSize int) ([]models.UserActionLog, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "List") if page <= 0 || pageSize <= 0 { return nil, 0, ErrInvalidPagination } @@ -48,7 +54,7 @@ func (r *gormUserActionLogRepository) List(opts UserActionLogListOptions, page, var logs []models.UserActionLog var total int64 - query := r.db.Model(&models.UserActionLog{}) + query := r.db.WithContext(repoCtx).Model(&models.UserActionLog{}) if opts.UserID != nil { query = query.Where("user_id = ?", *opts.UserID) diff --git a/internal/infra/repository/user_repository.go b/internal/infra/repository/user_repository.go index 71c2503..97b8bb2 100644 --- a/internal/infra/repository/user_repository.go +++ b/internal/infra/repository/user_repository.go @@ -2,40 +2,47 @@ package repository import ( + "context" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/gorm" ) // UserRepository 定义了与用户模型相关的数据库操作接口 // 这是为了让业务逻辑层依赖于抽象,而不是具体的数据库实现 type UserRepository interface { - Create(user *models.User) error - FindByUsername(username string) (*models.User, error) - FindByID(id uint) (*models.User, error) - FindUserForLogin(identifier string) (*models.User, error) - FindAll() ([]*models.User, error) + Create(ctx context.Context, user *models.User) error + FindByUsername(ctx context.Context, username string) (*models.User, error) + FindByID(ctx context.Context, id uint) (*models.User, error) + FindUserForLogin(ctx context.Context, identifier string) (*models.User, error) + FindAll(ctx context.Context) ([]*models.User, error) } // gormUserRepository 是 UserRepository 的 GORM 实现 type gormUserRepository struct { - db *gorm.DB + ctx context.Context + db *gorm.DB } // NewGormUserRepository 创建一个新的 UserRepository GORM 实现实例 -func NewGormUserRepository(db *gorm.DB) UserRepository { - return &gormUserRepository{db: db} +func NewGormUserRepository(ctx context.Context, db *gorm.DB) UserRepository { + return &gormUserRepository{ctx: ctx, db: db} } // Create 创建一个新的用户记录 -func (r *gormUserRepository) Create(user *models.User) error { +func (r *gormUserRepository) Create(ctx context.Context, user *models.User) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "Create") // BeforeSave 钩子会在这里被自动触发 - return r.db.Create(user).Error + return r.db.WithContext(repoCtx).Create(user).Error } // FindByUsername 根据用户名查找用户 -func (r *gormUserRepository) FindByUsername(username string) (*models.User, error) { +func (r *gormUserRepository) FindByUsername(ctx context.Context, username string) (*models.User, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByUsername") var user models.User - if err := r.db.Where("username = ?", username).First(&user).Error; err != nil { + if err := r.db.WithContext(repoCtx).Where("username = ?", username).First(&user).Error; err != nil { return nil, err } return &user, nil @@ -43,10 +50,11 @@ func (r *gormUserRepository) FindByUsername(username string) (*models.User, erro // FindUserForLogin 根据提供的标识符查找用户,可用于登录验证 // 标识符可以是用户名、邮箱、手机号、微信号或飞书账号 -func (r *gormUserRepository) FindUserForLogin(identifier string) (*models.User, error) { +func (r *gormUserRepository) FindUserForLogin(ctx context.Context, identifier string) (*models.User, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindUserForLogin") var user models.User // 使用 ->> 操作符来查询 JSONB 字段中的文本值 - err := r.db.Where( + err := r.db.WithContext(repoCtx).Where( "username = ? OR contact ->> 'email' = ? OR contact ->> 'phone' = ? OR contact ->> 'wechat' = ? OR contact ->> 'feishu' = ?", identifier, identifier, identifier, identifier, identifier, ).First(&user).Error @@ -58,18 +66,20 @@ func (r *gormUserRepository) FindUserForLogin(identifier string) (*models.User, } // FindByID 根据 ID 查找用户 -func (r *gormUserRepository) FindByID(id uint) (*models.User, error) { +func (r *gormUserRepository) FindByID(ctx context.Context, id uint) (*models.User, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByID") var user models.User - if err := r.db.First(&user, id).Error; err != nil { + if err := r.db.WithContext(repoCtx).First(&user, id).Error; err != nil { return nil, err } return &user, nil } // FindAll 返回数据库中的所有用户 -func (r *gormUserRepository) FindAll() ([]*models.User, error) { +func (r *gormUserRepository) FindAll(ctx context.Context) ([]*models.User, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindAll") var users []*models.User - if err := r.db.Where("1 = 1").Find(&users).Error; err != nil { + if err := r.db.WithContext(repoCtx).Where("1 = 1").Find(&users).Error; err != nil { return nil, err } return users, nil diff --git a/internal/infra/transport/lora/chirp_stack.go b/internal/infra/transport/lora/chirp_stack.go index 6bc4a2c..92a0925 100644 --- a/internal/infra/transport/lora/chirp_stack.go +++ b/internal/infra/transport/lora/chirp_stack.go @@ -1,32 +1,33 @@ package lora import ( + "context" "errors" "time" "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/transport" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/transport/lora/chirp_stack_proto/client" "git.huangwc.com/pig/pig-farm-controller/internal/infra/transport/lora/chirp_stack_proto/client/device_service" + "github.com/go-openapi/runtime" httptransport "github.com/go-openapi/runtime/client" "github.com/go-openapi/strfmt" - - "git.huangwc.com/pig/pig-farm-controller/internal/infra/transport/lora/chirp_stack_proto/client" ) // ChirpStackTransport 是一个客户端,用于封装与 ChirpStack REST API 的交互。 type ChirpStackTransport struct { + ctx context.Context client *client.ChirpStackRESTAPI authInfo runtime.ClientAuthInfoWriter config config.ChirpStackConfig - logger *logs.Logger } // NewChirpStackTransport 创建一个新的通信实例,用于与 ChirpStack 通信。 func NewChirpStackTransport( + ctx context.Context, config config.ChirpStackConfig, - logger *logs.Logger, ) *ChirpStackTransport { // 使用配置中的服务器地址创建一个 HTTP transport。 // 它会使用生成的客户端中定义的默认 base path 和 schemes。 @@ -39,14 +40,15 @@ func NewChirpStackTransport( authInfo := httptransport.APIKeyAuth("grpc-metadata-authorization", "header", config.GenerateAPIKey()) return &ChirpStackTransport{ + ctx: ctx, client: apiClient, authInfo: authInfo, config: config, - logger: logger, } } -func (c *ChirpStackTransport) Send(address string, payload []byte) (*transport.SendResult, error) { +func (c *ChirpStackTransport) Send(ctx context.Context, address string, payload []byte) (*transport.SendResult, error) { + logger := logs.TraceLogger(ctx, c.ctx, "Send") // 1. 构建 API 请求体。 // - Confirmed: true 表示确认消息, 设为false将不保证消息送达(但可以节约下行容量)。 // - Data: 经过 Base64 编码的数据。 @@ -72,18 +74,18 @@ func (c *ChirpStackTransport) Send(address string, payload []byte) (*transport.S // c.authInfo 是您在 NewChirpStackTransport 中创建的认证信息。 resp, err := c.client.DeviceService.DeviceServiceEnqueue(params, c.authInfo) if err != nil { - c.logger.Errorf("设备 %s 调用ChirpStack Enqueue失败: %v", address, err) + logger.Errorf("设备 %s 调用ChirpStack Enqueue失败: %v", address, err) return nil, err } if resp == nil || resp.Payload == nil || resp.Payload.ID == "" { // 这是一个需要明确处理的错误情况,因为调用方依赖 MessageID。 errMsg := "ChirpStack Enqueue 响应未包含 MessageID (ID)" - c.logger.Errorf(errMsg) + logger.Errorf(errMsg) return nil, errors.New(errMsg) } - c.logger.Infof("成功将 payload 发送到设备 %s 的队列 (MessageID: %s)", address, resp.Payload.ID) + logger.Infof("成功将 payload 发送到设备 %s 的队列 (MessageID: %s)", address, resp.Payload.ID) // 将 MessageID 包装在 SendResult 中返回 result := &transport.SendResult{ diff --git a/internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go b/internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go index 7212f77..bd51957 100644 --- a/internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go +++ b/internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go @@ -2,6 +2,7 @@ package lora import ( "bytes" + "context" "encoding/binary" "encoding/json" "fmt" @@ -17,6 +18,7 @@ import ( "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" "git.huangwc.com/pig/pig-farm-controller/internal/infra/transport" "git.huangwc.com/pig/pig-farm-controller/internal/infra/transport/proto" + "github.com/google/uuid" "github.com/tarm/serial" gproto "google.golang.org/protobuf/proto" @@ -41,8 +43,8 @@ type message struct { // LoRaMeshUartPassthroughTransport 实现了 transport.Communicator 和 transport.Listener 接口 type LoRaMeshUartPassthroughTransport struct { + ctx context.Context config config.LoraMeshConfig - logger *logs.Logger port *serial.Port mu sync.Mutex // 用于保护对外的公共方法(如Send)的并发调用 @@ -87,8 +89,8 @@ type reassemblyBuffer struct { // NewLoRaMeshUartPassthroughTransport 创建一个新的 LoRaMeshUartPassthroughTransport 实例 func NewLoRaMeshUartPassthroughTransport( + ctx context.Context, config config.LoraMeshConfig, - logger *logs.Logger, areaControllerRepo repository.AreaControllerRepository, pendingCollectionRepo repository.PendingCollectionRepository, deviceRepo repository.DeviceRepository, @@ -106,8 +108,8 @@ func NewLoRaMeshUartPassthroughTransport( } t := &LoRaMeshUartPassthroughTransport{ + ctx: ctx, config: config, - logger: logger, port: port, state: stateIdle, stopChan: make(chan struct{}), @@ -126,15 +128,16 @@ func NewLoRaMeshUartPassthroughTransport( } // Listen 启动后台监听协程(非阻塞) -func (t *LoRaMeshUartPassthroughTransport) Listen() error { +func (t *LoRaMeshUartPassthroughTransport) Listen(ctx context.Context) error { + loraCtx, logger := logs.Trace(ctx, t.ctx, "Listen") t.wg.Add(1) - go t.workerLoop() - t.logger.Info("LoRa传输层工作协程已启动") + go t.workerLoop(loraCtx) + logger.Info("LoRa传输层工作协程已启动") return nil } // Send 将发送任务提交给worker协程 -func (t *LoRaMeshUartPassthroughTransport) Send(address string, payload []byte) (*transport.SendResult, error) { +func (t *LoRaMeshUartPassthroughTransport) Send(ctx context.Context, address string, payload []byte) (*transport.SendResult, error) { t.mu.Lock() defer t.mu.Unlock() @@ -156,14 +159,16 @@ func (t *LoRaMeshUartPassthroughTransport) Send(address string, payload []byte) } // Stop 停止传输层 -func (t *LoRaMeshUartPassthroughTransport) Stop() error { +func (t *LoRaMeshUartPassthroughTransport) Stop(ctx context.Context) error { close(t.stopChan) t.wg.Wait() return t.port.Close() } // workerLoop 是核心的状态机和调度器 -func (t *LoRaMeshUartPassthroughTransport) workerLoop() { +func (t *LoRaMeshUartPassthroughTransport) workerLoop(ctx context.Context) { + loraCtx, logger := logs.Trace(ctx, t.ctx, "workerLoop") + defer t.wg.Done() readBuffer := make([]byte, 1024) @@ -176,7 +181,7 @@ func (t *LoRaMeshUartPassthroughTransport) workerLoop() { if t.reassemblyTimeout != nil { t.reassemblyTimeout.Stop() } - t.logger.Info("LoRa传输层工作协程已停止") + logger.Info("LoRa传输层工作协程已停止") return default: } @@ -188,7 +193,7 @@ func (t *LoRaMeshUartPassthroughTransport) workerLoop() { } if err != nil && err != io.EOF { // 忽略预期的超时错误(io.EOF),只记录真正的IO错误 - t.logger.Errorf("从串口读取数据时发生错误: %v", err) + logger.Errorf("从串口读取数据时发生错误: %v", err) } // 3. 循环解析缓冲区中的完整物理帧 @@ -197,27 +202,29 @@ func (t *LoRaMeshUartPassthroughTransport) workerLoop() { if frame == nil { break // 缓冲区中没有更多完整帧了 } - t.handleFrame(frame) + t.handleFrame(loraCtx, frame) } // 4. 根据当前状态执行主要逻辑 switch t.state { case stateIdle: - t.runIdleState() + t.runIdleState(loraCtx) case stateReceiving: - t.runReceivingState() + t.runReceivingState(loraCtx) default: } } } // runIdleState 处理空闲状态下的逻辑,主要是检查并启动发送任务 -func (t *LoRaMeshUartPassthroughTransport) runIdleState() { +func (t *LoRaMeshUartPassthroughTransport) runIdleState(ctx context.Context) { + loraCtx := logs.AddFuncName(ctx, t.ctx, "Listen") + select { case req := <-t.sendChan: t.state = stateSending // 此处为阻塞式发送 - result, err := t.executeSend(req) + result, err := t.executeSend(loraCtx, req) req.result <- &sendResultTuple{result: result, err: err} t.state = stateIdle default: @@ -226,10 +233,11 @@ func (t *LoRaMeshUartPassthroughTransport) runIdleState() { } // runReceivingState 处理接收状态下的逻辑,主要是检查超时 -func (t *LoRaMeshUartPassthroughTransport) runReceivingState() { +func (t *LoRaMeshUartPassthroughTransport) runReceivingState(ctx context.Context) { + logger := logs.TraceLogger(ctx, t.ctx, "runReceivingState") select { case sourceAddr := <-t.reassemblyTimeoutCh: - t.logger.Warnf("接收来自 0x%04X 的消息超时", sourceAddr) + logger.Warnf("接收来自 0x%04X 的消息超时", sourceAddr) delete(t.reassemblyBuffers, sourceAddr) t.state = stateIdle default: @@ -238,7 +246,8 @@ func (t *LoRaMeshUartPassthroughTransport) runReceivingState() { } // executeSend 执行完整的发送流程(分片、构建、写入) -func (t *LoRaMeshUartPassthroughTransport) executeSend(req *sendRequest) (*transport.SendResult, error) { +func (t *LoRaMeshUartPassthroughTransport) executeSend(ctx context.Context, req *sendRequest) (*transport.SendResult, error) { + logger := logs.TraceLogger(ctx, t.ctx, "executeSend") chunks := splitPayload(req.payload, t.config.MaxChunkSize) totalChunks := uint8(len(chunks)) @@ -257,7 +266,7 @@ func (t *LoRaMeshUartPassthroughTransport) executeSend(req *sendRequest) (*trans frame.WriteByte(currentChunk) // 当前包序号 frame.Write(chunk) // 数据块 - t.logger.Infof("构建LoRa数据包: %v", frame.Bytes()) + logger.Infof("构建LoRa数据包: %v", frame.Bytes()) _, err := t.port.Write(frame.Bytes()) if err != nil { return nil, fmt.Errorf("写入串口失败: %w", err) @@ -272,9 +281,10 @@ func (t *LoRaMeshUartPassthroughTransport) executeSend(req *sendRequest) (*trans } // handleFrame 处理一个从串口解析出的完整物理帧 -func (t *LoRaMeshUartPassthroughTransport) handleFrame(frame []byte) { +func (t *LoRaMeshUartPassthroughTransport) handleFrame(ctx context.Context, frame []byte) { + loraCtx, logger := logs.Trace(ctx, t.ctx, "handleFrame") if len(frame) < 8 { - t.logger.Warnf("收到了一个无效长度的帧: %d", len(frame)) + logger.Warnf("收到了一个无效长度的帧: %d", len(frame)) return } @@ -291,7 +301,7 @@ func (t *LoRaMeshUartPassthroughTransport) handleFrame(frame []byte) { DestAddr: fmt.Sprintf("%04X", destAddr), Payload: chunkData, } - go t.handleUpstreamMessage(msg) + go t.handleUpstreamMessage(loraCtx, msg) return } @@ -316,18 +326,18 @@ func (t *LoRaMeshUartPassthroughTransport) handleFrame(frame []byte) { t.reassemblyTimeoutCh <- sourceAddr }) } else { - t.logger.Warnf("在空闲状态下收到了一个来自 0x%04X 的非首包分片,已忽略。", sourceAddr) + logger.Warnf("在空闲状态下收到了一个来自 0x%04X 的非首包分片,已忽略。", sourceAddr) } case stateReceiving: if sourceAddr != t.currentRecvSource { - t.logger.Warnf("正在接收来自 0x%04X 的数据时,收到了另一个源 0x%04X 的分片,已忽略。", t.currentRecvSource, sourceAddr) + logger.Warnf("正在接收来自 0x%04X 的数据时,收到了另一个源 0x%04X 的分片,已忽略。", t.currentRecvSource, sourceAddr) return } buffer, ok := t.reassemblyBuffers[sourceAddr] if !ok { - t.logger.Errorf("内部错误: 处于接收状态,但没有为 0x%04X 找到缓冲区", sourceAddr) + logger.Errorf("内部错误: 处于接收状态,但没有为 0x%04X 找到缓冲区", sourceAddr) t.state = stateIdle // 重置状态 return } @@ -352,23 +362,27 @@ func (t *LoRaMeshUartPassthroughTransport) handleFrame(frame []byte) { DestAddr: fmt.Sprintf("%04X", destAddr), Payload: fullPayload.Bytes(), } - go t.handleUpstreamMessage(msg) + go t.handleUpstreamMessage(loraCtx, msg) // 清理并返回空闲状态 delete(t.reassemblyBuffers, sourceAddr) t.state = stateIdle } + default: + logger.Errorf("内部错误: 状态机处于未知状态 %d", t.state) } } // handleUpstreamMessage 在独立的协程中处理单个上行的、完整的消息。 -func (t *LoRaMeshUartPassthroughTransport) handleUpstreamMessage(msg *message) { - t.logger.Infof("开始处理来自 %s 的上行消息", msg.SourceAddr) +func (t *LoRaMeshUartPassthroughTransport) handleUpstreamMessage(ctx context.Context, msg *message) { + loraCtx, logger := logs.Trace(ctx, t.ctx, "handleUpstreamMessage") + + logger.Infof("开始处理来自 %s 的上行消息", msg.SourceAddr) // 1. 解析外层 "信封" var instruction proto.Instruction if err := gproto.Unmarshal(msg.Payload, &instruction); err != nil { - t.logger.Errorf("解析上行 Instruction Protobuf 失败: %v, 源地址: %s, 原始数据: %x", err, msg.SourceAddr, msg.Payload) + logger.Errorf("解析上行 Instruction Protobuf 失败: %v, 源地址: %s, 原始数据: %x", err, msg.SourceAddr, msg.Payload) return } @@ -379,39 +393,39 @@ func (t *LoRaMeshUartPassthroughTransport) handleUpstreamMessage(msg *message) { collectResp = p.CollectResult default: // 如果上行的数据不是采集结果,记录日志并忽略 - t.logger.Infof("收到一个非采集响应的上行指令 (类型: %T),无需处理。源地址: %s", p, msg.SourceAddr) + logger.Infof("收到一个非采集响应的上行指令 (类型: %T),无需处理。源地址: %s", p, msg.SourceAddr) return } if collectResp == nil { - t.logger.Errorf("从 Instruction 中提取的 CollectResult 为 nil。源地址: %s", msg.SourceAddr) + logger.Errorf("从 Instruction 中提取的 CollectResult 为 nil。源地址: %s", msg.SourceAddr) return } correlationID := collectResp.CorrelationId - t.logger.Infof("成功解析采集响应 (CorrelationID: %s),包含 %d 个值。", correlationID, len(collectResp.Values)) + logger.Infof("成功解析采集响应 (CorrelationID: %s),包含 %d 个值。", correlationID, len(collectResp.Values)) // 3. 查找区域主控 (注意:LoRa Mesh 的 SourceAddr 对应于区域主控的 NetworkID) - regionalController, err := t.areaControllerRepo.FindByNetworkID(msg.SourceAddr) + regionalController, err := t.areaControllerRepo.FindByNetworkID(loraCtx, msg.SourceAddr) if err != nil { - t.logger.Errorf("处理上行消息失败:无法通过源地址 '%s' 找到区域主控设备: %v", msg.SourceAddr, err) + logger.Errorf("处理上行消息失败:无法通过源地址 '%s' 找到区域主控设备: %v", msg.SourceAddr, err) return } if err := regionalController.SelfCheck(); err != nil { - t.logger.Errorf("处理上行消息失败:区域主控 %v(ID: %d) 未通过自检: %v", regionalController.Name, regionalController.ID, err) + logger.Errorf("处理上行消息失败:区域主控 %v(ID: %d) 未通过自检: %v", regionalController.Name, regionalController.ID, err) return } // 4. 根据 CorrelationID 查找待处理请求 - pendingReq, err := t.pendingCollectionRepo.FindByCorrelationID(correlationID) + pendingReq, err := t.pendingCollectionRepo.FindByCorrelationID(loraCtx, correlationID) if err != nil { - t.logger.Errorf("处理采集响应失败:无法找到待处理请求 (CorrelationID: %s): %v", correlationID, err) + logger.Errorf("处理采集响应失败:无法找到待处理请求 (CorrelationID: %s): %v", correlationID, err) return } // 检查状态,防止重复处理 if pendingReq.Status != models.PendingStatusPending && pendingReq.Status != models.PendingStatusTimedOut { - t.logger.Warnf("收到一个已处理过的采集响应 (CorrelationID: %s, Status: %s),将忽略。", correlationID, pendingReq.Status) + logger.Warnf("收到一个已处理过的采集响应 (CorrelationID: %s, Status: %s),将忽略。", correlationID, pendingReq.Status) return } @@ -419,10 +433,10 @@ func (t *LoRaMeshUartPassthroughTransport) handleUpstreamMessage(msg *message) { deviceIDs := pendingReq.CommandMetadata values := collectResp.Values if len(deviceIDs) != len(values) { - t.logger.Errorf("数据不匹配:下行指令要求采集 %d 个设备,但上行响应包含 %d 个值 (CorrelationID: %s)", len(deviceIDs), len(values), correlationID) - err = t.pendingCollectionRepo.UpdateStatusToFulfilled(correlationID, time.Now()) + logger.Errorf("数据不匹配:下行指令要求采集 %d 个设备,但上行响应包含 %d 个值 (CorrelationID: %s)", len(deviceIDs), len(values), correlationID) + err = t.pendingCollectionRepo.UpdateStatusToFulfilled(loraCtx, correlationID, time.Now()) if err != nil { - t.logger.Errorf("处理采集响应失败:无法更新待处理请求 (CorrelationID: %s) 的状态为完成: %v", correlationID, err) + logger.Errorf("处理采集响应失败:无法更新待处理请求 (CorrelationID: %s) 的状态为完成: %v", correlationID, err) } return } @@ -431,31 +445,31 @@ func (t *LoRaMeshUartPassthroughTransport) handleUpstreamMessage(msg *message) { rawSensorValue := values[i] if math.IsNaN(float64(rawSensorValue)) { - t.logger.Warnf("设备 (ID: %d) 上报了一个无效的 NaN 值,已跳过当前值的记录。", deviceID) + logger.Warnf("设备 (ID: %d) 上报了一个无效的 NaN 值,已跳过当前值的记录。", deviceID) continue } - dev, err := t.deviceRepo.FindByID(deviceID) + dev, err := t.deviceRepo.FindByID(loraCtx, deviceID) if err != nil { - t.logger.Errorf("处理采集数据失败:无法找到设备 (ID: %d): %v", deviceID, err) + logger.Errorf("处理采集数据失败:无法找到设备 (ID: %d): %v", deviceID, err) continue } if err := dev.SelfCheck(); err != nil { - t.logger.Warnf("跳过设备 %d,因其未通过自检: %v", dev.ID, err) + logger.Warnf("跳过设备 %d,因其未通过自检: %v", dev.ID, err) continue } if err := dev.DeviceTemplate.SelfCheck(); err != nil { - t.logger.Warnf("跳过设备 %d,因其设备模板未通过自检: %v", dev.ID, err) + logger.Warnf("跳过设备 %d,因其设备模板未通过自检: %v", dev.ID, err) continue } var valueDescriptors []*models.ValueDescriptor if err := dev.DeviceTemplate.ParseValues(&valueDescriptors); err != nil { - t.logger.Warnf("跳过设备 %d,因其设备模板的 Values 属性解析失败: %v", dev.ID, err) + logger.Warnf("跳过设备 %d,因其设备模板的 Values 属性解析失败: %v", dev.ID, err) continue } if len(valueDescriptors) == 0 { - t.logger.Warnf("跳过设备 %d,因其设备模板缺少 ValueDescriptor 定义", dev.ID) + logger.Warnf("跳过设备 %d,因其设备模板缺少 ValueDescriptor 定义", dev.ID) continue } valueDescriptor := valueDescriptors[0] @@ -471,27 +485,29 @@ func (t *LoRaMeshUartPassthroughTransport) handleUpstreamMessage(msg *message) { case models.SensorTypeWeight: dataToRecord = models.WeightData{WeightKilograms: parsedValue} default: - t.logger.Warnf("未知的传感器类型 '%s',将使用通用格式记录", valueDescriptor.Type) + logger.Warnf("未知的传感器类型 '%s',将使用通用格式记录", valueDescriptor.Type) dataToRecord = map[string]float64{"value": parsedValue} } - t.recordSensorData(regionalController.ID, dev.ID, time.Now(), valueDescriptor.Type, dataToRecord) - t.logger.Infof("成功记录传感器数据: 设备ID=%d, 类型=%s, 原始值=%f, 解析值=%.2f", dev.ID, valueDescriptor.Type, rawSensorValue, parsedValue) + t.recordSensorData(loraCtx, regionalController.ID, dev.ID, time.Now(), valueDescriptor.Type, dataToRecord) + logger.Infof("成功记录传感器数据: 设备ID=%d, 类型=%s, 原始值=%f, 解析值=%.2f", dev.ID, valueDescriptor.Type, rawSensorValue, parsedValue) } // 6. 更新请求状态为“已完成” - if err := t.pendingCollectionRepo.UpdateStatusToFulfilled(correlationID, time.Now()); err != nil { - t.logger.Errorf("更新待采集请求状态为 'fulfilled' 失败 (CorrelationID: %s): %v", correlationID, err) + if err := t.pendingCollectionRepo.UpdateStatusToFulfilled(loraCtx, correlationID, time.Now()); err != nil { + logger.Errorf("更新待采集请求状态为 'fulfilled' 失败 (CorrelationID: %s): %v", correlationID, err) } else { - t.logger.Infof("成功完成并关闭采集请求 (CorrelationID: %s)", correlationID) + logger.Infof("成功完成并关闭采集请求 (CorrelationID: %s)", correlationID) } } // recordSensorData 是一个通用方法,用于将传感器数据存入数据库。 -func (t *LoRaMeshUartPassthroughTransport) recordSensorData(regionalControllerID uint, sensorDeviceID uint, eventTime time.Time, sensorType models.SensorType, data interface{}) { +func (t *LoRaMeshUartPassthroughTransport) recordSensorData(ctx context.Context, regionalControllerID uint, sensorDeviceID uint, eventTime time.Time, sensorType models.SensorType, data interface{}) { + loraCtx, logger := logs.Trace(ctx, t.ctx, "recordSensorData") + jsonData, err := json.Marshal(data) if err != nil { - t.logger.Errorf("记录传感器数据失败:序列化数据为 JSON 时出错: %v", err) + logger.Errorf("记录传感器数据失败:序列化数据为 JSON 时出错: %v", err) return } @@ -503,8 +519,8 @@ func (t *LoRaMeshUartPassthroughTransport) recordSensorData(regionalControllerID Data: datatypes.JSON(jsonData), } - if err := t.sensorDataRepo.Create(sensorData); err != nil { - t.logger.Errorf("记录传感器数据失败:存入数据库时出错: %v", err) + if err := t.sensorDataRepo.Create(loraCtx, sensorData); err != nil { + logger.Errorf("记录传感器数据失败:存入数据库时出错: %v", err) } } diff --git a/internal/infra/transport/lora/placeholder_transport.go b/internal/infra/transport/lora/placeholder_transport.go index 967b0b9..90091ec 100644 --- a/internal/infra/transport/lora/placeholder_transport.go +++ b/internal/infra/transport/lora/placeholder_transport.go @@ -1,27 +1,28 @@ package lora import ( + "context" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/transport" ) type PlaceholderTransport struct { - logger *logs.Logger + ctx context.Context } -func NewPlaceholderTransport(logger *logs.Logger) transport.Listener { - logger.Info("当前配置非 LoRaMesh, LoRaMesh UART 透传传输器未激活。") +func NewPlaceholderTransport(ctx context.Context) transport.Listener { return &PlaceholderTransport{ - logger: logger, + ctx: ctx, } } -func (p *PlaceholderTransport) Listen() error { - p.logger.Warnf("当前不是LoRa Mesh 模式, 这只是个占位监听器") +func (p *PlaceholderTransport) Listen(ctx context.Context) error { + logs.TraceLogger(ctx, p.ctx, "Listen").Warnf("当前不是LoRa Mesh 模式, 这只是个占位监听器") return nil } -func (p *PlaceholderTransport) Stop() error { - p.logger.Warnf("当前不是LoRa Mesh 模式, 占位监听器停止工作") +func (p *PlaceholderTransport) Stop(ctx context.Context) error { + logs.TraceLogger(ctx, p.ctx, "Stop").Warnf("当前不是LoRa Mesh 模式, 占位监听器停止工作") return nil } diff --git a/internal/infra/transport/transport.go b/internal/infra/transport/transport.go index fc29ab8..ff5a3d4 100644 --- a/internal/infra/transport/transport.go +++ b/internal/infra/transport/transport.go @@ -1,12 +1,15 @@ package transport -import "time" +import ( + "context" + "time" +) // Communicator 用于其他设备通信 type Communicator interface { // Send 用于发送一条单向数据(不等待回信) // 成功时,它返回一个包含 MessageID 的 SendResult,以便调用方追踪。 - Send(address string, payload []byte) (*SendResult, error) + Send(ctx context.Context, address string, payload []byte) (*SendResult, error) } // SendResult 包含了 SendGo 方法成功执行后返回的结果。 @@ -27,8 +30,8 @@ type SendResult struct { // Listener 用于监听其他设备发送过来的数据 type Listener interface { // Listen 用于开始监听其他设备发送过来的数据 - Listen() error + Listen(ctx context.Context) error // Stop 用于停止监听 - Stop() error + Stop(ctx context.Context) error } 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 }) diff --git a/main.go b/main.go index ebfc9a0..67e64a2 100644 --- a/main.go +++ b/main.go @@ -3,8 +3,8 @@ package main import ( "log" - _ "git.huangwc.com/pig/pig-farm-controller/docs" // 引入 swag 生成的 docs "git.huangwc.com/pig/pig-farm-controller/internal/core" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" ) func main() { @@ -22,6 +22,6 @@ func main() { if err := app.Start(); err != nil { // 如果 Start 过程(主要是优雅关闭阶段)出现错误, // 此时我们的 logger 已经是可用的,可以用它来记录错误。 - app.Logger.Errorf("应用启动或关闭时发生错误: %v", err) + logs.GetLogger(app.Ctx).Errorf("应用启动或关闭时发生错误: %v", err) } } diff --git a/project_structure.txt b/project_structure.txt index 767be01..d7b9dd9 100644 --- a/project_structure.txt +++ b/project_structure.txt @@ -10,15 +10,26 @@ RELAY_API.md TODO-List.txt config.example.yml config.yml -design/verification-before-device-deletion/add_get_device_id_configs_to_task.md -design/verification-before-device-deletion/check_before_device_deletion.md -design/verification-before-device-deletion/device_task_association_maintenance.md -design/verification-before-device-deletion/device_task_many_to_many_design.md -design/verification-before-device-deletion/index.md -design/verification-before-device-deletion/plan_service_refactor.md -design/verification-before-device-deletion/plan_service_refactor_to_domain.md -design/verification-before-device-deletion/refactor_deletion_check.md -design/verification-before-device-deletion/refactor_id_conversion.md +design/archive/2025-11-3-verification-before-device-deletion/add_get_device_id_configs_to_task.md +design/archive/2025-11-3-verification-before-device-deletion/check_before_device_deletion.md +design/archive/2025-11-3-verification-before-device-deletion/device_task_association_maintenance.md +design/archive/2025-11-3-verification-before-device-deletion/device_task_many_to_many_design.md +design/archive/2025-11-3-verification-before-device-deletion/index.md +design/archive/2025-11-3-verification-before-device-deletion/plan_service_refactor.md +design/archive/2025-11-3-verification-before-device-deletion/plan_service_refactor_to_domain.md +design/archive/2025-11-3-verification-before-device-deletion/refactor_deletion_check.md +design/archive/2025-11-3-verification-before-device-deletion/refactor_id_conversion.md +design/provide-logger-with-mothed/implementation.md +design/provide-logger-with-mothed/index.md +design/provide-logger-with-mothed/task-api.md +design/provide-logger-with-mothed/task-controller.md +design/provide-logger-with-mothed/task-domain.md +design/provide-logger-with-mothed/task-infra.md +design/provide-logger-with-mothed/task-list.md +design/provide-logger-with-mothed/task-middleware.md +design/provide-logger-with-mothed/task-repository.md +design/provide-logger-with-mothed/task-service.md +design/provide-logger-with-mothed/task-webhook.md docs/docs.go docs/swagger.json docs/swagger.yaml @@ -51,6 +62,7 @@ internal/app/dto/plan_dto.go internal/app/dto/user_dto.go internal/app/middleware/audit.go internal/app/middleware/auth.go +internal/app/service/audit_service.go internal/app/service/device_service.go internal/app/service/monitor_service.go internal/app/service/pig_batch_service.go @@ -65,7 +77,6 @@ internal/app/webhook/transport.go internal/core/application.go internal/core/component_initializers.go internal/core/data_initializer.go -internal/domain/audit/service.go internal/domain/device/device_service.go internal/domain/device/general_device_service.go internal/domain/notify/notify.go @@ -78,7 +89,6 @@ internal/domain/pig/pig_batch_service_pig_trade.go internal/domain/pig/pig_sick_manager.go internal/domain/pig/pig_trade_manager.go internal/domain/plan/analysis_plan_task_manager.go -internal/domain/plan/device_id_extractor.go internal/domain/plan/plan_execution_manager.go internal/domain/plan/plan_service.go internal/domain/plan/task.go @@ -86,10 +96,11 @@ internal/domain/task/delay_task.go internal/domain/task/full_collection_task.go internal/domain/task/release_feed_weight_task.go internal/domain/task/task.go -internal/domain/token/token_service.go internal/infra/config/config.go internal/infra/database/postgres.go internal/infra/database/storage.go +internal/infra/logs/context.go +internal/infra/logs/encoder.go internal/infra/logs/logs.go internal/infra/models/device.go internal/infra/models/device_template.go @@ -143,6 +154,7 @@ internal/infra/transport/proto/device.proto internal/infra/transport/transport.go internal/infra/utils/command_generater/modbus_rtu.go internal/infra/utils/time.go +internal/infra/utils/token/token_service.go internal/infra/utils/validation.go main.go openspec/AGENTS.md