修改domain包

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

View File

@@ -1,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(&params)
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
}

View File

@@ -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
}

View File

@@ -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(&params)
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

View File

@@ -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)
}