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