Files
sub2api/backend/internal/service/backup_archive.go
T
李建琦 6d655c9903
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
2026-08-21 18:30:13 +08:00

107 lines
2.5 KiB
Go

package service
import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
"os"
)
const defaultBackupPartSizeBytes int64 = 4 * 1024 * 1024 * 1024
// BackupPart 描述一个 gzip 字节分卷。
type BackupPart struct {
Index int `json:"index"`
S3Key string `json:"s3_key"`
SizeBytes int64 `json:"size_bytes"`
SHA256 string `json:"sha256,omitempty"`
}
type localBackupPart struct {
Index int
Path string
SizeBytes int64
SHA256 string
}
func splitBackupFile(srcPath string, partSize int64) (parts []localBackupPart, err error) {
if partSize <= 0 {
return nil, fmt.Errorf("backup part size must be positive")
}
src, err := os.Open(srcPath)
if err != nil {
return nil, fmt.Errorf("open backup archive: %w", err)
}
defer func() {
if closeErr := src.Close(); err == nil && closeErr != nil {
err = fmt.Errorf("close backup archive: %w", closeErr)
}
if err != nil {
paths := make([]string, 0, len(parts))
for _, part := range parts {
paths = append(paths, part.Path)
}
_ = cleanupBackupFiles(paths...)
}
}()
info, err := src.Stat()
if err != nil {
return nil, fmt.Errorf("stat backup archive: %w", err)
}
if info.Size() <= 0 {
return nil, errors.New("backup archive is empty")
}
remaining := info.Size()
for index := 1; remaining > 0; index++ {
partFile, createErr := os.CreateTemp("", "sub2api-backup-part-*")
if createErr != nil {
return nil, fmt.Errorf("create backup part: %w", createErr)
}
partPath := partFile.Name()
partBytes := partSize
if remaining < partBytes {
partBytes = remaining
}
hash := sha256.New()
written, copyErr := io.CopyN(io.MultiWriter(partFile, hash), src, partBytes)
closeErr := partFile.Close()
if copyErr != nil {
_ = os.Remove(partPath)
return nil, fmt.Errorf("write backup part %d: %w", index, copyErr)
}
if closeErr != nil {
_ = os.Remove(partPath)
return nil, fmt.Errorf("close backup part %d: %w", index, closeErr)
}
parts = append(parts, localBackupPart{
Index: index,
Path: partPath,
SizeBytes: written,
SHA256: hex.EncodeToString(hash.Sum(nil)),
})
remaining -= written
}
return parts, nil
}
func cleanupBackupFiles(paths ...string) error {
var errs []error
for _, path := range paths {
if path == "" {
continue
}
if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) {
errs = append(errs, fmt.Errorf("remove %s: %w", path, err))
}
}
return errors.Join(errs...)
}