package service import ( "context" "encoding/json" "strings" "time" "backupx/server/internal/apperror" "backupx/server/internal/backup" "backupx/server/internal/model" "backupx/server/internal/repository" ) type BackupRecordListInput struct { TaskID *uint Status string DateFrom *time.Time DateTo *time.Time Limit int Offset int } type BackupRecordSummary struct { ID uint `json:"id"` TaskID uint `json:"taskId"` TaskName string `json:"taskName"` StorageTargetID uint `json:"storageTargetId"` StorageTargetName string `json:"storageTargetName"` Status string `json:"status"` FileName string `json:"fileName"` FileSize int64 `json:"fileSize"` Checksum string `json:"checksum"` StoragePath string `json:"storagePath"` DurationSeconds int `json:"durationSeconds"` ErrorMessage string `json:"errorMessage"` StartedAt time.Time `json:"startedAt"` CompletedAt *time.Time `json:"completedAt,omitempty"` } type BackupRecordDetail struct { BackupRecordSummary LogContent string `json:"logContent"` LogEvents []backup.LogEvent `json:"logEvents,omitempty"` StorageUploadResults []StorageUploadResultItem `json:"storageUploadResults,omitempty"` } type BackupRecordService struct { records repository.BackupRecordRepository execution *BackupExecutionService logHub *backup.LogHub } func NewBackupRecordService(records repository.BackupRecordRepository, execution *BackupExecutionService, logHub *backup.LogHub) *BackupRecordService { return &BackupRecordService{records: records, execution: execution, logHub: logHub} } func (s *BackupRecordService) List(ctx context.Context, input BackupRecordListInput) ([]BackupRecordSummary, error) { items, err := s.records.List(ctx, repository.BackupRecordListOptions{TaskID: input.TaskID, Status: strings.TrimSpace(input.Status), DateFrom: input.DateFrom, DateTo: input.DateTo, Limit: input.Limit, Offset: input.Offset}) if err != nil { return nil, apperror.Internal("BACKUP_RECORD_LIST_FAILED", "无法获取备份记录列表", err) } result := make([]BackupRecordSummary, 0, len(items)) for _, item := range items { result = append(result, toBackupRecordSummary(&item)) } return result, nil } func (s *BackupRecordService) Get(ctx context.Context, id uint) (*BackupRecordDetail, error) { item, err := s.records.FindByID(ctx, id) if err != nil { return nil, apperror.Internal("BACKUP_RECORD_GET_FAILED", "无法获取备份记录详情", err) } if item == nil { return nil, apperror.New(404, "BACKUP_RECORD_NOT_FOUND", "备份记录不存在", err) } return toBackupRecordDetail(item, s.logHub), nil } func (s *BackupRecordService) SubscribeLogs(ctx context.Context, id uint, buffer int) (<-chan backup.LogEvent, func(), error) { item, err := s.records.FindByID(ctx, id) if err != nil { return nil, nil, apperror.Internal("BACKUP_RECORD_GET_FAILED", "无法获取备份记录详情", err) } if item == nil { return nil, nil, apperror.New(404, "BACKUP_RECORD_NOT_FOUND", "备份记录不存在", err) } channel, cancel := s.logHub.Subscribe(id, buffer) return channel, cancel, nil } func (s *BackupRecordService) Download(ctx context.Context, id uint) (*DownloadedArtifact, error) { return s.execution.DownloadRecord(ctx, id) } func (s *BackupRecordService) Restore(ctx context.Context, id uint) error { return s.execution.RestoreRecord(ctx, id) } func (s *BackupRecordService) Delete(ctx context.Context, id uint) error { return s.execution.DeleteRecord(ctx, id) } func toBackupRecordSummary(item *model.BackupRecord) BackupRecordSummary { return BackupRecordSummary{ ID: item.ID, TaskID: item.TaskID, TaskName: item.Task.Name, StorageTargetID: item.StorageTargetID, StorageTargetName: item.StorageTarget.Name, Status: item.Status, FileName: item.FileName, FileSize: item.FileSize, Checksum: item.Checksum, StoragePath: item.StoragePath, DurationSeconds: item.DurationSeconds, ErrorMessage: item.ErrorMessage, StartedAt: item.StartedAt, CompletedAt: item.CompletedAt, } } func toBackupRecordDetail(item *model.BackupRecord, logHub *backup.LogHub) *BackupRecordDetail { detail := &BackupRecordDetail{BackupRecordSummary: toBackupRecordSummary(item), LogContent: item.LogContent} if item.Status == "running" && logHub != nil { events := logHub.Snapshot(item.ID) detail.LogEvents = events if len(events) > 0 { lines := make([]string, 0, len(events)) for _, event := range events { lines = append(lines, event.Message) } detail.LogContent = strings.Join(lines, "\n") } } // 解析多目标上传结果 if strings.TrimSpace(item.StorageUploadResults) != "" { var uploadResults []StorageUploadResultItem if err := json.Unmarshal([]byte(item.StorageUploadResults), &uploadResults); err == nil { detail.StorageUploadResults = uploadResults } } return detail }