From 349a31518d17e9a0f027099981e5dec8ee8eb428 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Fri, 5 Dec 2025 16:53:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20StopUpgrade?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/service/area_controller_service.go | 4 +- internal/domain/device/ota_service.go | 44 +++++++++++++++++-- internal/domain/task/ota_check_task.go | 9 ++++ internal/infra/models/execution.go | 34 ++++++++++---- 4 files changed, 77 insertions(+), 14 deletions(-) diff --git a/internal/app/service/area_controller_service.go b/internal/app/service/area_controller_service.go index 28a7cda..f8ac1a5 100644 --- a/internal/app/service/area_controller_service.go +++ b/internal/app/service/area_controller_service.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "path/filepath" "git.huangwc.com/pig/pig-farm-controller/internal/app/dto" "git.huangwc.com/pig/pig-farm-controller/internal/domain/device" @@ -12,7 +13,6 @@ import ( "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/file" - "github.com/google/uuid" ) @@ -168,7 +168,7 @@ func (s *areaControllerService) StartUpgrade(ctx context.Context, areaController return nil, fmt.Errorf("读取固件文件内容失败: %w", err) } - subDir := uuid.New().String() + subDir := filepath.Join(models.OTADir, uuid.New().String()) var filePath string var actionErr error diff --git a/internal/domain/device/ota_service.go b/internal/domain/device/ota_service.go index 8553c62..7730fb3 100644 --- a/internal/domain/device/ota_service.go +++ b/internal/domain/device/ota_service.go @@ -2,20 +2,27 @@ package device import ( "context" + "fmt" + "path/filepath" + "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" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/utils/file" ) // otaServiceImpl 是 OtaService 接口的实现。 type otaServiceImpl struct { + ctx context.Context otaRepo repository.OtaRepository deviceRepo repository.DeviceRepository } // NewOtaService 创建一个新的 OtaService 实例。 -func NewOtaService(otaRepo repository.OtaRepository, deviceRepo repository.DeviceRepository) OtaService { +func NewOtaService(ctx context.Context, otaRepo repository.OtaRepository, deviceRepo repository.DeviceRepository) OtaService { return &otaServiceImpl{ + ctx: ctx, otaRepo: otaRepo, deviceRepo: deviceRepo, } @@ -32,6 +39,37 @@ func (o *otaServiceImpl) GetUpgradeProgress(ctx context.Context, taskID uint32) } func (o *otaServiceImpl) StopUpgrade(ctx context.Context, taskID uint32) error { - //TODO implement me - panic("implement me") + serviceCtx, logger := logs.Trace(ctx, o.ctx, "StopUpgrade") + + task, err := o.otaRepo.FindByID(serviceCtx, taskID) + if err != nil { + logger.Errorf("查找 OTA 任务失败: %v, 任务ID: %d", err, taskID) + return fmt.Errorf("查找 OTA 任务失败: %w", err) + } + + // 幂等性检查:如果任务已处于终态,则直接返回成功 + if task.IsOver() { + logger.Infof("OTA 任务 %d 已处于终态 %s,无需停止", taskID, task.Status) + return nil + } + + now := time.Now() + task.Status = models.OTATaskStatusStopped + task.CompletedAt = &now + task.ErrorMessage = "任务被用户手动停止" + + if err := o.otaRepo.Update(serviceCtx, task); err != nil { + logger.Errorf("更新 OTA 任务状态失败: %v, 任务ID: %d", err, taskID) + return fmt.Errorf("更新 OTA 任务状态失败: %w", err) + } + + // 清理相关文件目录 + dirToRemove := filepath.Join(models.OTADir, fmt.Sprintf("%d", taskID)) + if err := file.RemoveTempDir(dirToRemove); err != nil { + // 文件清理失败不应阻塞主流程,但需要记录日志 + logger.Warnf("清理 OTA 任务 %d 的文件目录 %s 失败: %v", taskID, dirToRemove, err) + } + + logger.Infof("OTA 任务 %d 已被成功标记为手动停止", taskID) + return nil } diff --git a/internal/domain/task/ota_check_task.go b/internal/domain/task/ota_check_task.go index 72f3914..c6ba2c7 100644 --- a/internal/domain/task/ota_check_task.go +++ b/internal/domain/task/ota_check_task.go @@ -3,6 +3,7 @@ package task import ( "context" "fmt" + "path/filepath" "sync" "time" @@ -10,6 +11,7 @@ import ( "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/file" ) // OtaCheckTaskParams 定义了 OTA 检查任务所需的参数。 @@ -91,6 +93,13 @@ func (t *otaCheckTask) Execute(ctx context.Context) error { if err := t.otaRepo.Update(taskCtx, task); err != nil { // 仅记录错误,不中断整个检查任务,以确保其他超时任务能被处理 logger.Errorf("更新超时的OTA任务 #%d 状态失败: %v", task.ID, err) + } else { + // 数据库更新成功后,清理文件 + logger.Infof("OTA任务 #%d 状态已更新为超时,现在开始清理文件。", task.ID) + dirToRemove := filepath.Join(models.OTADir, fmt.Sprintf("%d", task.ID)) + if removeErr := file.RemoveTempDir(dirToRemove); removeErr != nil { + logger.Warnf("清理超时的OTA任务 #%d 的文件目录 %s 失败: %v", task.ID, dirToRemove, removeErr) + } } } diff --git a/internal/infra/models/execution.go b/internal/infra/models/execution.go index 47a3bf0..c5e9ea7 100644 --- a/internal/infra/models/execution.go +++ b/internal/infra/models/execution.go @@ -159,17 +159,24 @@ func (PendingCollection) TableName() string { type OTATaskStatus string const ( - OTATaskStatusPending OTATaskStatus = "待开始" // 任务已创建,等待下发 - OTATaskStatusInProgress OTATaskStatus = "进行中" // 任务已下发,设备正在处理 - OTATaskStatusSuccess OTATaskStatus = "成功" // 设备报告升级成功,新固件已运行 - OTATaskStatusAlreadyUpToDate OTATaskStatus = "版本已是最新" // 设备报告版本已是最新,未执行升级 - OTATaskStatusFailedPreCheck OTATaskStatus = "预检失败" // 设备报告升级前检查失败 (如拒绝降级、准备分区失败) - OTATaskStatusFailedDownload OTATaskStatus = "下载或校验失败" // 设备报告文件下载或校验失败 (包括清单文件和固件文件) - OTATaskStatusFailedRollback OTATaskStatus = "固件回滚" // 新固件启动失败,设备自动回滚 - OTATaskStatusTimedOut OTATaskStatus = "超时" // 平台在超时后仍未收到最终报告 - OTATaskStatusPlatformError OTATaskStatus = "平台内部错误" // 平台处理过程中发生的非设备报告错误 + OTATaskStatusPending OTATaskStatus = "待开始" // 任务已创建,等待下发 + + OTATaskStatusInProgress OTATaskStatus = "进行中" // 任务已下发,设备正在处理 + + OTATaskStatusSuccess OTATaskStatus = "成功" // 设备报告升级成功,新固件已运行 + OTATaskStatusAlreadyUpToDate OTATaskStatus = "版本已是最新" // 设备报告版本已是最新,未执行升级 + + OTATaskStatusFailedPreCheck OTATaskStatus = "预检失败" // 设备报告升级前检查失败 (如拒绝降级、准备分区失败) + OTATaskStatusFailedDownload OTATaskStatus = "下载或校验失败" // 设备报告文件下载或校验失败 (包括清单文件和固件文件) + OTATaskStatusFailedRollback OTATaskStatus = "固件回滚" // 新固件启动失败,设备自动回滚 + OTATaskStatusTimedOut OTATaskStatus = "超时" // 平台在超时后仍未收到最终报告 + OTATaskStatusPlatformError OTATaskStatus = "平台内部错误" // 平台处理过程中发生的非设备报告错误 + OTATaskStatusStopped OTATaskStatus = "手动停止" // 手动停止 ) +// OTADir 是 OTA 升级相关的临时文件存储目录 +const OTADir = "ota" + // OTATask 记录一次 OTA 升级任务的详细信息 type OTATask struct { // ID 是数据库自增主键,将作为 task_id 在平台与设备间通信 @@ -186,6 +193,15 @@ type OTATask struct { FinalReportedVersion string `gorm:"type:varchar(32);comment:任务结束后,设备上报的最终固件版本"` } +func (o OTATask) IsOver() bool { + switch o.Status { + case OTATaskStatusPending, OTATaskStatusInProgress: + return false + default: + return true + } +} + // TableName 自定义 GORM 使用的数据库表名 func (OTATask) TableName() string { return "ota_tasks"