实现 StopUpgrade

This commit is contained in:
2025-12-05 16:53:52 +08:00
parent b4ecee6626
commit 349a31518d
4 changed files with 77 additions and 14 deletions

View File

@@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"path/filepath"
"git.huangwc.com/pig/pig-farm-controller/internal/app/dto" "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/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/models"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/utils/file" "git.huangwc.com/pig/pig-farm-controller/internal/infra/utils/file"
"github.com/google/uuid" "github.com/google/uuid"
) )
@@ -168,7 +168,7 @@ func (s *areaControllerService) StartUpgrade(ctx context.Context, areaController
return nil, fmt.Errorf("读取固件文件内容失败: %w", err) return nil, fmt.Errorf("读取固件文件内容失败: %w", err)
} }
subDir := uuid.New().String() subDir := filepath.Join(models.OTADir, uuid.New().String())
var filePath string var filePath string
var actionErr error var actionErr error

View File

@@ -2,20 +2,27 @@ package device
import ( import (
"context" "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/models"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/utils/file"
) )
// otaServiceImpl 是 OtaService 接口的实现。 // otaServiceImpl 是 OtaService 接口的实现。
type otaServiceImpl struct { type otaServiceImpl struct {
ctx context.Context
otaRepo repository.OtaRepository otaRepo repository.OtaRepository
deviceRepo repository.DeviceRepository deviceRepo repository.DeviceRepository
} }
// NewOtaService 创建一个新的 OtaService 实例。 // 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{ return &otaServiceImpl{
ctx: ctx,
otaRepo: otaRepo, otaRepo: otaRepo,
deviceRepo: deviceRepo, 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 { func (o *otaServiceImpl) StopUpgrade(ctx context.Context, taskID uint32) error {
//TODO implement me serviceCtx, logger := logs.Trace(ctx, o.ctx, "StopUpgrade")
panic("implement me")
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
} }

View File

@@ -3,6 +3,7 @@ package task
import ( import (
"context" "context"
"fmt" "fmt"
"path/filepath"
"sync" "sync"
"time" "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/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "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"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/utils/file"
) )
// OtaCheckTaskParams 定义了 OTA 检查任务所需的参数。 // OtaCheckTaskParams 定义了 OTA 检查任务所需的参数。
@@ -91,6 +93,13 @@ func (t *otaCheckTask) Execute(ctx context.Context) error {
if err := t.otaRepo.Update(taskCtx, task); err != nil { if err := t.otaRepo.Update(taskCtx, task); err != nil {
// 仅记录错误,不中断整个检查任务,以确保其他超时任务能被处理 // 仅记录错误,不中断整个检查任务,以确保其他超时任务能被处理
logger.Errorf("更新超时的OTA任务 #%d 状态失败: %v", task.ID, err) 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)
}
} }
} }

View File

@@ -159,17 +159,24 @@ func (PendingCollection) TableName() string {
type OTATaskStatus string type OTATaskStatus string
const ( const (
OTATaskStatusPending OTATaskStatus = "待开始" // 任务已创建,等待下发 OTATaskStatusPending OTATaskStatus = "待开始" // 任务已创建,等待下发
OTATaskStatusInProgress OTATaskStatus = "进行中" // 任务已下发,设备正在处理
OTATaskStatusSuccess OTATaskStatus = "成功" // 设备报告升级成功,新固件已运行 OTATaskStatusInProgress OTATaskStatus = "进行中" // 任务已下发,设备正在处理
OTATaskStatusAlreadyUpToDate OTATaskStatus = "版本已是最新" // 设备报告版本已是最新,未执行升级
OTATaskStatusFailedPreCheck OTATaskStatus = "预检失败" // 设备报告升级前检查失败 (如拒绝降级、准备分区失败) OTATaskStatusSuccess OTATaskStatus = "成功" // 设备报告升级成功,新固件已运行
OTATaskStatusFailedDownload OTATaskStatus = "下载或校验失败" // 设备报告文件下载或校验失败 (包括清单文件和固件文件) OTATaskStatusAlreadyUpToDate OTATaskStatus = "版本已是最新" // 设备报告版本已是最新,未执行升级
OTATaskStatusFailedRollback OTATaskStatus = "固件回滚" // 新固件启动失败,设备自动回滚
OTATaskStatusTimedOut OTATaskStatus = "超时" // 平台在超时后仍未收到最终报告 OTATaskStatusFailedPreCheck OTATaskStatus = "预检失败" // 设备报告升级前检查失败 (如拒绝降级、准备分区失败)
OTATaskStatusPlatformError OTATaskStatus = "平台内部错误" // 平台处理过程中发生的非设备报告错误 OTATaskStatusFailedDownload OTATaskStatus = "下载或校验失败" // 设备报告文件下载或校验失败 (包括清单文件和固件文件)
OTATaskStatusFailedRollback OTATaskStatus = "固件回滚" // 新固件启动失败,设备自动回滚
OTATaskStatusTimedOut OTATaskStatus = "超时" // 平台在超时后仍未收到最终报告
OTATaskStatusPlatformError OTATaskStatus = "平台内部错误" // 平台处理过程中发生的非设备报告错误
OTATaskStatusStopped OTATaskStatus = "手动停止" // 手动停止
) )
// OTADir 是 OTA 升级相关的临时文件存储目录
const OTADir = "ota"
// OTATask 记录一次 OTA 升级任务的详细信息 // OTATask 记录一次 OTA 升级任务的详细信息
type OTATask struct { type OTATask struct {
// ID 是数据库自增主键,将作为 task_id 在平台与设备间通信 // ID 是数据库自增主键,将作为 task_id 在平台与设备间通信
@@ -186,6 +193,15 @@ type OTATask struct {
FinalReportedVersion string `gorm:"type:varchar(32);comment:任务结束后,设备上报的最终固件版本"` 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 使用的数据库表名 // TableName 自定义 GORM 使用的数据库表名
func (OTATask) TableName() string { func (OTATask) TableName() string {
return "ota_tasks" return "ota_tasks"