| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 |
- 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...)
- }
|