human: 如何处理七牛云的对象存储AWS SDK for Swift

gpt: [对象存储](https://developer.qiniu.com/kodo) > 使用指南 >
[开发指南](https://developer.qiniu.com/kodo/development_guidelines/8596/development-
guidelines) > [AWS S3
兼容](https://developer.qiniu.com/kodo/development_guidelines/4086/aws-s3-compatible)
> [兼容 SDK 示例](https://developer.qiniu.com/kodo/development_guidelines/12553/S3
SDK sample) > AWS SDK for Swift

#  AWS SDK for Swift

最近更新时间: 2024-02-19 17:03:58

# 导入 AWS SDK for Swift

确保 Swift 5.7 或更高版本，以及 OpenSSL v1.x 已经被安装。

AWS SDK for Swift 支持以下平台：

  * iOS & iPadOS 13.0 或更高版本
  * macOS 10.15 或更高版本
  * Ubuntu Linux 16.04 LTS 或更高版本
  * Amazon Linux 2 或更高版本

初始化 Swift 项目

    
    
    swift package init --type executable
    

添加 AWS SDK for Swift 包，这里给出 `Package.swift` 的案例

    
    
    // swift-tools-version:5.5
    // The swift-tools-version declares the minimum version of Swift required to build this package.
    
    import PackageDescription
    
    let package = Package(
        name: "aws-swift-sdk-test-examples",
        dependencies: [
            .package(
                url: "https://github.com/awslabs/aws-sdk-swift",
                from: "0.36.0"
            )
        ],
        targets: [
            // Targets are the basic building blocks of a package. A target can define a module or a test suite.
            // Targets can depend on other targets in this package, and on products in packages this package depends on.
            .executableTarget(
                name: "aws-swift-sdk-test-examples",
                dependencies: [
                    .product(name: "AWSS3", package: "aws-sdk-swift"),
                    .product(name: "AWSSTS", package: "aws-sdk-swift")],
                path: "Sources")
        ]
    )
    

对于之后的每个代码示例，将代码创建在 `Sources/entry.swift` 后，执行

    
    
    swift run
    

即可执行代码。

# 对象上传

## 获取客户端上传 URL

创建 `Sources/entry.swift`

    
    
    import AWSClientRuntime
    import AWSS3
    
    @main
    struct Main {
        static func main() async throws {
            let config = try await S3Client.S3ClientConfiguration()
            config.credentialsProvider = try StaticCredentialsProvider(Credentials(
                accessKey: "<QiniuAccessKey>",
                secret: "<QiniuSecretKey>"
            ))
            config.region = "cn-east-1" // 华东-浙江区 region id
            config.endpoint = "https://s3.cn-east-1.qiniucs.com" // 华东-浙江区 endpoint
    
            let uploadRequest = PutObjectInput(
                bucket: "<Bucket>",
                key: "<Key>"
            )
            let url = try await uploadRequest.presignURL(config: config, expiration: 3600)
            print("\(String(describing: url))")
        }
    }
    

这段代码将生成一个经过预先签名的客户端上传 URL，有效期为 1 小时，客户端可以在过期时间内对该 URL 发送 HTTP PUT 请求将文件上传。

以下是用 curl 上传文件的案例：

    
    
    curl -X PUT --upload-file "<path/to/file>" "<presigned url>"
    

## 服务器端直传

### 单请求上传（文件）

创建 `Sources/entry.swift`

    
    
    import Foundation
    import ClientRuntime
    import AWSClientRuntime
    import AWSS3
    
    @main
    struct Main {
        static func main() async throws {
            let config = try await S3Client.S3ClientConfiguration()
            config.credentialsProvider = try StaticCredentialsProvider(Credentials(
                accessKey: "<QiniuAccessKey>",
                secret: "<QiniuSecretKey>"
            ))
            config.region = "cn-east-1" // 华东-浙江区 region id
            config.endpoint = "https://s3.cn-east-1.qiniucs.com" // 华东-浙江区 endpoint
    
            let client = S3Client(config: config)
    
            guard let file = FileHandle(forReadingAtPath: "<path/to/upload>") else {
                return
            }
            let response = try await client.putObject(input: PutObjectInput(
                body: ByteStream.from(fileHandle: file),
                bucket: "<Bucket>",
                key: "<Key>"
            ))
            print("ETag: \(String(describing: response.eTag))")
        }
    }
    

### 单请求上传（数据流）

创建 `Sources/entry.swift`

    
    
    import Foundation
    import ClientRuntime
    import AWSClientRuntime
    import AWSS3
    
    @main
    struct Main {
        static func main() async throws {
            let config = try await S3Client.S3ClientConfiguration()
            config.credentialsProvider = try StaticCredentialsProvider(Credentials(
                accessKey: "<QiniuAccessKey>",
                secret: "<QiniuSecretKey>"
            ))
            config.region = "cn-east-1" // 华东-浙江区 region id
            config.endpoint = "https://s3.cn-east-1.qiniucs.com" // 华东-浙江区 endpoint
    
            let client = S3Client(config: config)
    
            let response = try await client.putObject(input: PutObjectInput(
                body: ByteStream.data("Hello from Qiniu S3!".data(using: .utf8)),
                bucket: "<Bucket>",
                key: "<Key>"
            ))
            print("ETag: \(String(describing: response.eTag))")
        }
    }
    

### 分片上传（文件）

创建 `Sources/entry.swift`

    
    
    import Foundation
    import ClientRuntime
    import AWSClientRuntime
    import AWSS3
    
    @main
    struct Main {
        static func main() async throws {
            let config = try await S3Client.S3ClientConfiguration()
            config.credentialsProvider = try StaticCredentialsProvider(Credentials(
                accessKey: "<QiniuAccessKey>",
                secret: "<QiniuSecretKey>"
            ))
            config.region = "cn-east-1" // 华东-浙江区 region id
            config.endpoint = "https://s3.cn-east-1.qiniucs.com" // 华东-浙江区 endpoint
    
            let client = S3Client(config: config)
    
            guard let file = FileHandle(forReadingAtPath: "<path/to/upload>") else {
                return
            }
    
            let createMultipartUploadResponse = try await client.createMultipartUpload(input: CreateMultipartUploadInput(
                bucket: "<Bucket>",
                key: "<Key>"
            ))
            if createMultipartUploadResponse.uploadId == nil {
                return
            }
    
            let PART_SIZE = 5 * 1024 * 1024 // 分片大小为 5 MB
            var parts = [S3ClientTypes.CompletedPart]()
            var partNum = 1
    
            // 这里给出的案例是串行分片上传。可以自行改造成并行分片上传以进一步提升上传速度
            while true {
                let data = file.readData(ofLength: PART_SIZE)
                if data.count == 0 {
                    break
                }
    
                let uploadPartResponse = try await client.uploadPart(input: UploadPartInput(
                    body: ByteStream.data(data),
                    bucket: createMultipartUploadResponse.bucket,
                    key: createMultipartUploadResponse.key,
                    partNumber: partNum,
                    uploadId: createMultipartUploadResponse.uploadId
                ))
                if uploadPartResponse.eTag == nil {
                    return
                }
    
                parts.append(S3ClientTypes.CompletedPart(
                    eTag: uploadPartResponse.eTag,
                    partNumber: partNum
                ))
                partNum += 1
            }
    
            let completeMultipartUploadResponse = try await client.completeMultipartUpload(input: CompleteMultipartUploadInput(
                bucket: createMultipartUploadResponse.bucket,
                key: createMultipartUploadResponse.key,
                multipartUpload: S3ClientTypes.CompletedMultipartUpload(parts: parts),
                uploadId: createMultipartUploadResponse.uploadId
            ))
            print("ETag: \(String(describing: completeMultipartUploadResponse.eTag)))")
        }
    }
    

# 对象下载

## 获取客户端下载 URL

创建 `Sources/entry.swift`

    
    
    import AWSClientRuntime
    import AWSS3
    
    @main
    struct Main {
        static func main() async throws {
            let config = try await S3Client.S3ClientConfiguration()
            config.credentialsProvider = try StaticCredentialsProvider(Credentials(
                accessKey: "<QiniuAccessKey>",
                secret: "<QiniuSecretKey>"
            ))
            config.region = "cn-east-1" // 华东-浙江区 region id
            config.endpoint = "https://s3.cn-east-1.qiniucs.com" // 华东-浙江区 endpoint
    
            let downloadRequest = GetObjectInput(
                bucket: "<Bucket>",
                key: "<Key>"
            )
            let url = try await downloadRequest.presignURL(config: config, expiration: 3600)
            print("\(String(describing: url))")
        }
    }
    

这段代码将生成一个经过预先签名的客户端下载 URL，有效期为 1 小时，客户端可以在过期时间内对该 URL 发送 HTTP GET 请求将文件下载。

以下是用 curl 下载文件的案例：

    
    
    curl -o "<path/to/download>" "<presigned url>"
    

## 服务器端直接下载

创建 `Sources/entry.swift`

    
    
    import AWSClientRuntime
    import AWSS3
    
    @main
    struct Main {
        static func main() async throws {
            let config = try await S3Client.S3ClientConfiguration()
            config.credentialsProvider = try StaticCredentialsProvider(Credentials(
                accessKey: "<QiniuAccessKey>",
                secret: "<QiniuSecretKey>"
            ))
            config.region = "cn-east-1" // 华东-浙江区 region id
            config.endpoint = "https://s3.cn-east-1.qiniucs.com" // 华东-浙江区 endpoint
    
            let client = S3Client(config: config)
    
            let response = try await client.getObject(input: GetObjectInput(
                bucket: "<Bucket>",
                key: "<Key>"
            ))
            guard let body = response.body,
                  let data = try await body.readData() else {
                return
            }
            try data.write(to: URL(fileURLWithPath: "<path/to/download>"))
            print("ETag: \(String(describing: response.eTag))")
        }
    }
    

# 对象管理

## 获取对象信息

创建 `Sources/entry.swift`

    
    
    import AWSClientRuntime
    import AWSS3
    
    @main
    struct Main {
        static func main() async throws {
            let config = try await S3Client.S3ClientConfiguration()
            config.credentialsProvider = try StaticCredentialsProvider(Credentials(
                accessKey: "<QiniuAccessKey>",
                secret: "<QiniuSecretKey>"
            ))
            config.region = "cn-east-1" // 华东-浙江区 region id
            config.endpoint = "https://s3.cn-east-1.qiniucs.com" // 华东-浙江区 endpoint
    
            let client = S3Client(config: config)
    
            let response = try await client.headObject(input: HeadObjectInput(
                bucket: "<Bucket>",
                key: "<Key>"
            ))
            print("ETag: \(String(describing: response.eTag))")
        }
    }
    

## 修改对象 MimeType

创建 `Sources/entry.swift`

    
    
    import AWSClientRuntime
    import AWSS3
    
    @main
    struct Main {
        static func main() async throws {
            let config = try await S3Client.S3ClientConfiguration()
            config.credentialsProvider = try StaticCredentialsProvider(Credentials(
                accessKey: "<QiniuAccessKey>",
                secret: "<QiniuSecretKey>"
            ))
            config.region = "cn-east-1" // 华东-浙江区 region id
            config.endpoint = "https://s3.cn-east-1.qiniucs.com" // 华东-浙江区 endpoint
    
            let client = S3Client(config: config)
    
            let _ = try await client.copyObject(input: CopyObjectInput(
                bucket: "<Bucket>",
                contentType: "text/plain",
                copySource: "/<Bucket>/<Key>",
                key: "<Bucket>",
                metadataDirective: .replace
            ))
            print("Done")
        }
    }
    

## 修改对象存储类型

创建 `Sources/entry.swift`

    
    
    import AWSClientRuntime
    import AWSS3
    
    @main
    struct Main {
        static func main() async throws {
            let config = try await S3Client.S3ClientConfiguration()
            config.credentialsProvider = try StaticCredentialsProvider(Credentials(
                accessKey: "<QiniuAccessKey>",
                secret: "<QiniuSecretKey>"
            ))
            config.region = "cn-east-1" // 华东-浙江区 region id
            config.endpoint = "https://s3.cn-east-1.qiniucs.com" // 华东-浙江区 endpoint
    
            let client = S3Client(config: config)
    
            let _ = try await client.copyObject(input: CopyObjectInput(
                bucket: "<Bucket>",
                copySource: "/<Bucket>/<Key>",
                key: "<Key>",
                metadataDirective: .replace,
                storageClass: .glacier
            ))
            print("Done")
        }
    }
    

## 复制对象副本

创建 `Sources/entry.swift`

    
    
    import AWSClientRuntime
    import AWSS3
    
    @main
    struct Main {
        static func main() async throws {
            let config = try await S3Client.S3ClientConfiguration()
            config.credentialsProvider = try StaticCredentialsProvider(Credentials(
                accessKey: "<QiniuAccessKey>",
                secret: "<QiniuSecretKey>"
            ))
            config.region = "cn-east-1" // 华东-浙江区 region id
            config.endpoint = "https://s3.cn-east-1.qiniucs.com" // 华东-浙江区 endpoint
    
            let client = S3Client(config: config)
    
            let _ = try await client.copyObject(input: CopyObjectInput(
                bucket: "<ToBucket>",
                copySource: "/<FromBucket>/<FromKey>",
                key: "<ToKey>",
                metadataDirective: .copy
            ))
            print("Done")
        }
    }
    

## 复制对象副本（大于 5GB）

创建 `Sources/entry.swift`

    
    
    import Foundation
    import ClientRuntime
    import AWSClientRuntime
    import AWSS3
    
    @main
    struct Main {
        static func main() async throws {
            let config = try await S3Client.S3ClientConfiguration()
            config.credentialsProvider = try StaticCredentialsProvider(Credentials(
                accessKey: "<QiniuAccessKey>",
                secret: "<QiniuSecretKey>"
            ))
            config.region = "cn-east-1" // 华东-浙江区 region id
            config.endpoint = "https://s3.cn-east-1.qiniucs.com" // 华东-浙江区 endpoint
    
            let client = S3Client(config: config)
    
            let headObjectResponse = try await client.headObject(input: HeadObjectInput(
                bucket: "<FromBucket>",
                key: "<FromKey>"
            ))
    
            let createMultipartUploadResponse = try await client.createMultipartUpload(input: CreateMultipartUploadInput(
                bucket: "<ToBucket>",
                key: "<ToKey>"
            ))
            if createMultipartUploadResponse.uploadId == nil {
                return
            }
    
            let PART_SIZE = 5 * 1024 * 1024 // 分片大小为 5 MB
            var parts = [S3ClientTypes.CompletedPart]()
            var partNum = 1
            var copied = 0
    
            // 这里给出的案例是串行分片复制。可以自行改造成并行分片复制以进一步提升复制速度
            while copied < headObjectResponse.contentLength! {
                let partSize = min(headObjectResponse.contentLength! - copied, PART_SIZE)
    
                let uploadPartCopyResponse = try await client.uploadPartCopy(input: UploadPartCopyInput(
                    bucket: createMultipartUploadResponse.bucket,
                    copySource: "/<FromBucket>/<FromKey>",
                    copySourceRange: "bytes=\(copied)-\(copied+partSize-1)",
                    key: createMultipartUploadResponse.key,
                    partNumber: partNum,
                    uploadId: createMultipartUploadResponse.uploadId
                ))
                if uploadPartCopyResponse.copyPartResult?.eTag == nil {
                    return
                }
    
                parts.append(S3ClientTypes.CompletedPart(
                    eTag: uploadPartCopyResponse.copyPartResult?.eTag,
                    partNumber: partNum
                ))
                partNum += 1
                copied += partSize
            }
    
            let completeMultipartUploadResponse = try await client.completeMultipartUpload(input: CompleteMultipartUploadInput(
                bucket: createMultipartUploadResponse.bucket,
                key: createMultipartUploadResponse.key,
                multipartUpload: S3ClientTypes.CompletedMultipartUpload(parts: parts),
                uploadId: createMultipartUploadResponse.uploadId
            ))
            print("ETag: \(String(describing: completeMultipartUploadResponse.eTag)))")
        }
    }
    

## 删除空间中的文件

创建 `Sources/entry.swift`

    
    
    import AWSClientRuntime
    import AWSS3
    
    @main
    struct Main {
        static func main() async throws {
            let config = try await S3Client.S3ClientConfiguration()
            config.credentialsProvider = try StaticCredentialsProvider(Credentials(
                accessKey: "<QiniuAccessKey>",
                secret: "<QiniuSecretKey>"
            ))
            config.region = "cn-east-1" // 华东-浙江区 region id
            config.endpoint = "https://s3.cn-east-1.qiniucs.com" // 华东-浙江区 endpoint
    
            let client = S3Client(config: config)
    
            let _ = try await client.deleteObject(input: DeleteObjectInput(
                bucket: "<Bucket>",
                key: "<Key>"
            ))
            print("Done")
        }
    }
    

## 获取指定前缀的文件列表

创建 `Sources/entry.swift`

    
    
    import AWSClientRuntime
    import AWSS3
    
    @main
    struct Main {
        static func main() async throws {
            let config = try await S3Client.S3ClientConfiguration()
            config.credentialsProvider = try StaticCredentialsProvider(Credentials(
                accessKey: "<QiniuAccessKey>",
                secret: "<QiniuSecretKey>"
            ))
            config.region = "cn-east-1" // 华东-浙江区 region id
            config.endpoint = "https://s3.cn-east-1.qiniucs.com" // 华东-浙江区 endpoint
    
            let client = S3Client(config: config)
    
            let response = try await client.listObjectsV2(input: ListObjectsV2Input(
                bucket: "<Bucket>",
                prefix: "<KeyPrefix>"
            ))
            for content in response.contents ?? [] {
                print("Key: \(String(describing: content.key))")
                print("ETag: \(String(describing: content.eTag))")
            }
        }
    }
    

## 批量删除空间中的文件

创建 `Sources/entry.swift`

    
    
    import AWSClientRuntime
    import AWSS3
    
    @main
    struct Main {
        static func main() async throws {
            let config = try await S3Client.S3ClientConfiguration()
            config.credentialsProvider = try StaticCredentialsProvider(Credentials(
                accessKey: "<QiniuAccessKey>",
                secret: "<QiniuSecretKey>"
            ))
            config.region = "cn-east-1" // 华东-浙江区 region id
            config.endpoint = "https://s3.cn-east-1.qiniucs.com" // 华东-浙江区 endpoint
    
            let client = S3Client(config: config)
    
            let _ = try await client.deleteObjects(input: DeleteObjectsInput(
                bucket: "<Bucket>",
                delete: S3ClientTypes.Delete(objects: [
                    S3ClientTypes.ObjectIdentifier(key: "<Key1>"),
                    S3ClientTypes.ObjectIdentifier(key: "<Key2>"),
                    S3ClientTypes.ObjectIdentifier(key: "<Key3>"),
                ])
            ))
            print("Done")
        }
    }
    

# 临时安全凭证

创建 `Sources/entry.swift`

    
    
    import Foundation
    import ClientRuntime
    import AwsCommonRuntimeKit
    import AWSClientRuntime
    import AWSS3
    import AWSSTS
    
    struct StsCredentialsProvider: AWSClientRuntime.CredentialsProviding {
        private let stsClient: STSClient
        private let getFederationTokenInput: GetFederationTokenInput
    
        public init(_ stsClient: STSClient, getFederationTokenInput: GetFederationTokenInput) throws {
            self.stsClient = stsClient
            self.getFederationTokenInput = getFederationTokenInput
        }
    
        func getCredentials() async throws -> AWSClientRuntime.Credentials {
            let getFederationTokenOutput = try await self.stsClient.getFederationToken(input: self.getFederationTokenInput)
            return AWSClientRuntime.Credentials(
                accessKey: getFederationTokenOutput.credentials!.accessKeyId!,
                secret: getFederationTokenOutput.credentials!.secretAccessKey!,
                expirationTimeout: getFederationTokenOutput.credentials?.expiration,
                sessionToken: getFederationTokenOutput.credentials?.sessionToken)
        }
    }
    
    @main
    struct Main {
        static func main() async throws {
            let stsConfig = try await STSClient.STSClientConfiguration()
            stsConfig.credentialsProvider = try StaticCredentialsProvider(Credentials(
                accessKey: "<QiniuAccessKey>",
                secret: "<QiniuSecretKey>"
            ))
            stsConfig.region = "cn-east-1" // 华东-浙江区 region id
            stsConfig.signingRegion = stsConfig.region
            stsConfig.endpoint = "https://s3.cn-east-1.qiniucs.com" // 华东-浙江区 endpoint
    
            let stsCredentialsProvider = try StsCredentialsProvider(STSClient(config: stsConfig), getFederationTokenInput: GetFederationTokenInput(
                durationSeconds: 3600,
                name: "Bob",
                policy: "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"Stmt1\",\"Effect\":\"Allow\",\"Action\":[\"*\"],\"Resource\":[\"*\"]}]}"))
            let s3Config = try await S3Client.S3ClientConfiguration()
            s3Config.credentialsProvider = try AWSClientRuntime.CachedCredentialsProvider(source: stsCredentialsProvider, refreshTime: 0)
            s3Config.region = "cn-east-1" // 华东-浙江区 region id
            s3Config.signingRegion = s3Config.region
            s3Config.endpoint = "https://s3.cn-east-1.qiniucs.com" // 华东-浙江区 endpoint
    
            let client = S3Client(config: s3Config)
    
            // 可以使用这些临时凭证调用 S3 服务
            try await client.listBuckets(input: ListBucketsInput()).buckets?.forEach { bucket in
                print(bucket.name!)
            }
        }
    }
    

以上内容是否对您有帮助？