如何用Golang的AWS SDK仅上传修改后的文件到S3存储桶?
实现AWS SDK Go的S3增量备份(类似
aws s3 sync) 要实现增量备份,核心是对比本地文件与S3对象的元数据,只上传新增或修改过的文件。AWS SDK Go没有直接提供s3 sync的封装,但可以通过以下步骤手动实现:
核心思路
- 遍历本地目标目录,收集所有文件的路径、修改时间、大小信息
- 列出S3存储桶中对应前缀下的所有对象,记录它们的键、最后修改时间、大小
- 对比两者,筛选出需要上传的文件:
- 本地存在但S3中没有的文件
- 本地文件修改时间晚于S3对象最后修改时间的文件
- 本地文件大小与S3对象大小不一致的文件(应对修改时间未更新但内容变更的极端情况)
代码实现示例
package main import ( "context" "fmt" "os" "path/filepath" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3/s3manager" ) // LocalFile 存储本地文件的元数据 type LocalFile struct { Path string LastModified time.Time Size int64 } // S3Object 存储S3对象的元数据 type S3Object struct { Key string LastModified time.Time Size int64 } func main() { // 配置参数 localDir := "./your-local-directory" s3Bucket := "your-bucket-name" s3Prefix := "backup/" // S3中存储备份的前缀,可留空 // 初始化AWS会话 sess, err := session.NewSession(&aws.Config{ Region: aws.String("us-east-1"), // 替换为你的区域 }) if err != nil { fmt.Printf("Failed to create session: %v\n", err) return } // 1. 收集本地文件元数据 localFiles, err := collectLocalFiles(localDir) if err != nil { fmt.Printf("Failed to collect local files: %v\n", err) return } // 2. 收集S3对象元数据 s3Objects, err := collectS3Objects(sess, s3Bucket, s3Prefix) if err != nil { fmt.Printf("Failed to collect S3 objects: %v\n", err) return } // 3. 筛选需要上传的文件 filesToUpload := filterFilesToUpload(localDir, s3Prefix, localFiles, s3Objects) // 4. 上传增量文件 uploader := s3manager.NewUploader(sess) for _, file := range filesToUpload { fmt.Printf("Uploading %s to s3://%s/%s\n", file.Path, s3Bucket, file.S3Key) f, err := os.Open(file.Path) if err != nil { fmt.Printf("Failed to open file %s: %v\n", file.Path, err) continue } defer f.Close() _, err = uploader.Upload(&s3manager.UploadInput{ Bucket: aws.String(s3Bucket), Key: aws.String(file.S3Key), Body: f, }) if err != nil { fmt.Printf("Failed to upload %s: %v\n", file.Path, err) } } fmt.Println("Incremental backup completed") } // collectLocalFiles 遍历本地目录,返回所有文件的元数据 func collectLocalFiles(rootDir string) (map[string]LocalFile, error) { files := make(map[string]LocalFile) err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } // 获取相对路径,用于映射到S3键 relPath, err := filepath.Rel(rootDir, path) if err != nil { return err } files[relPath] = LocalFile{ Path: path, LastModified: info.ModTime().UTC(), // 转UTC和S3统一时区 Size: info.Size(), } return nil }) return files, err } // collectS3Objects 列出S3桶中指定前缀的所有对象元数据 func collectS3Objects(sess *session.Session, bucket, prefix string) (map[string]S3Object, error) { objects := make(map[string]S3Object) svc := s3.New(sess) input := &s3.ListObjectsV2Input{ Bucket: aws.String(bucket), Prefix: aws.String(prefix), } for { result, err := svc.ListObjectsV2(input) if err != nil { return nil, err } for _, obj := range result.Contents { // 去掉前缀,得到相对路径,和本地文件的relPath对应 relKey := *obj.Key[len(prefix):] if relKey == "" { continue // 跳过前缀目录本身 } objects[relKey] = S3Object{ Key: *obj.Key, LastModified: *obj.LastModified, Size: *obj.Size, } } if !*result.IsTruncated { break } input.ContinuationToken = result.NextContinuationToken } return objects, nil } // 需要上传的文件信息 type FileToUpload struct { Path string S3Key string } // filterFilesToUpload 对比本地文件和S3对象,筛选需要上传的文件 func filterFilesToUpload(localDir, s3Prefix string, localFiles map[string]LocalFile, s3Objects map[string]S3Object) []FileToUpload { var toUpload []FileToUpload for relPath, localFile := range localFiles { s3Obj, exists := s3Objects[relPath] if !exists { // S3中不存在该文件,需要上传 toUpload = append(toUpload, FileToUpload{ Path: localFile.Path, S3Key: filepath.Join(s3Prefix, relPath), // 拼接S3前缀和相对路径 }) continue } // 对比修改时间和大小 if localFile.LastModified.After(s3Obj.LastModified) || localFile.Size != s3Obj.Size { toUpload = append(toUpload, FileToUpload{ Path: localFile.Path, S3Key: s3Obj.Key, }) } } return toUpload }
关键细节说明
- 时区统一:将本地文件的修改时间转换为UTC,和S3对象的
LastModified(默认UTC)保持一致,避免时区差异导致的错误对比 - 路径映射:用本地文件的相对路径对应S3对象的键(去掉前缀后的部分),确保路径结构一致
- 大小校验:除了修改时间,额外对比文件大小,应对修改时间未更新但内容已变更的极端场景
- 分页处理:S3的
ListObjectsV2会分页返回结果,代码中处理了分页逻辑,确保获取所有对象
内容的提问来源于stack exchange,提问作者Wolf
相关产品推荐
相关产品推荐

