AWS S3

近期稍微涉猎点 AWS 的东西,记录一下,S3 上传。
一句话总结:

  • 限制文件大小10MB。使用 AWS Api gateway 和 Lambda 上传文件到 S3,限制文件大小10MB。
  • 直连 S3 上传,限制小点,目测是tb级别。
    所以基本上就不用考虑 api gateway 和 Lambda 的方式了。

解决办法:

  1. 使用 Flutter 的 Dio 通过 Pre-signed URL(预签名 URL)直连上传文件到 AWS S3。
  2. 拿到预签名 URL 后,使用 Dio 的 put 方法,将文件内容直接上传到该 Pre-signed URL,这里注意需要设置 Content-TypeContent-Length 头。content-type 类似 application/octet-stream,content-length 类似 file.lengthSync()
  3. S3 会根据 URL 中的签名校验请求、并将内容存储到指定 Bucket 的对象中。
  4. 上传成功后,后期如何拿这个文件,需要另外一个预签名 URL,类似之前的预签名,这里是 get 请求。

flutter dio put aws3上直连,用PresignedUrl方式,如何put

遇到content-type问题

在 Flutter 中使用 Dio 通过 Pre-signed URL(预签名 URL)直连上传文件到 AWS S3,最核心的思路是:

  1. 先从后端或某个服务获取一个可用的 Pre-signed URL(此 URL 通常包含了有效期、Bucket 名、对象键、上传所需的签名等信息)。
  2. 使用 Dio 的 put 方法,将文件内容直接上传到该 Pre-signed URL。S3 会根据 URL 中的签名校验请求、并将内容存储到指定 Bucket 的对象中。

下面给出一个完整示例,你可以根据实际情况做适当修改(比如文件来源、Content-Type、额外 Header 等)。

1. 获取 Pre-signed URL

通常,这一步需要你的后端(或某个 AWS SDK)来生成。例如后端用 AWS SDK getSignedUrl('putObject', { ... }) 生成一个可 PUT 的预签名地址,并返回给前端:

final presignedUrl = "https://your-bucket.s3.amazonaws.com/your-object-key?X-Amz-Algorithm=AWS4-HMAC-SHA256&...";
// 这是从服务端或 AWS SDK 生成的预签名地址

具体生成方式取决于你的技术栈,比如 Node.js 用 aws-sdk / Python 用 boto3 等;或你也可以用 CLI / Postman 测试先拿到预签名 URL。

2. 使用 Dio PUT 上传

基础用法

import 'dart:io';
import 'package:dio/dio.dart';

Future<void> uploadToS3PresignedUrl({
  required String presignedUrl,
  required File file,
}) async {
  final dio = Dio();

  try {
    // 常见做法:指定 Content-Type
    // 如果你的 presignedUrl 已含 Content-Type 限制,请确保这里的 Header 与预签名设置一致
    final contentType = 'application/octet-stream'; 
    // 或者根据文件类型动态确定, e.g. "image/jpeg"

    // 请求头可根据需要加:contentLength, contentType
    final headers = {
      'Content-Type': contentType,
      // 如果 presignedUrl 要求其他自定义头,也可加在这里
    };

    final response = await dio.put(
      presignedUrl,
      data: file.openRead(), // 或者 file.readAsBytes(), 但 stream 更省内存
      options: Options(
        headers: headers,
        // 指定 content-length 一般是可选的,但某些后端会强制校验
        // content-length 也可以自动识别,但如果需要可手动添加:
        // headers: {
        //   Headers.contentLengthHeader: file.lengthSync(),
        // },
      ),
    );

    // S3 PUT 成功时返回状态码 200 或 204
    if (response.statusCode == 200 || response.statusCode == 204) {
      print('Upload success!');
    } else {
      print('Upload failed with status: ${response.statusCode}');
    }
  } on DioError catch (e) {
    print('Upload error: $e');
  }
}

注意事项

  1. Content-Type 要与预签名中的要求一致
    • 如果你的后端生成 Pre-signed URL 时已经设定了 Content-Type: image/jpeg,那么前端必须在请求头中声明相同的 Content-Type,否则会出现签名校验不通过或上传失败。
  2. content-length(可选)
    • 某些服务端/S3 配置可能要求设置确切的内容长度 (Content-Length)。如果需要,可使用 file.lengthSync() 来获取并放进 header:
           'Content-Length': file.lengthSync(),
           ```  
         - 但大多场景下只要 S3 Bucket CORS & IAM 策略允许,可能不强制要求此字段。  
      
      3. **返回码**  
         - 成功一般返回 `200` 或 `204`,视具体预签名配置而定。检查 `response.statusCode` 来确认上传是否成功。
      
      #### 进度回调(可选)
      
      如果想在上传过程中获取**进度**,可以使用 Dio 的 `onSendProgress`:
      
      ```dart
      await dio.put(
        presignedUrl,
        data: file.openRead(),
        onSendProgress: (int sent, int total) {
          final progress = (sent / total) * 100;
          print('Upload progress: $progress%');
        },
        options: Options(headers: headers),
      );

这样就能在控制台看到实时的上传进度,或者在 UI 中更新进度条。

3. 确保 S3 配置正确

  • CORS 配置:若在 Web 环境使用(Flutter Web),需要在 S3 Bucket 中正确设置 CORS,让浏览器可以向该 presignedUrl 发起跨域 PUT 请求。
  • IAM 策略:确保生成 Pre-signed URL 的用户/角色拥有 s3:PutObject 权限,并且对应的 Bucket 策略允许该操作。
  • URL 有效期:预签名地址往往有一个有效期(默认 900 秒等),请在有效期内执行上传。

4. 总结

  1. 获取 Pre-signed URL:后端/AWS SDK 负责生成,返回给前端。
  2. Flutter 端(Dio)使用 put
    • put(presignedUrl, data: fileStream, options: Options(headers: ...))
    • 确保 Content-Type、其他自定义头与预签名保持一致。
  3. 检查返回状态码:200 或 204 视为上传成功。
  4. (可选)添加进度监听、错误处理、CORS 设置等细节。

这样就能完成**「Flutter + Dio + Pre-signed URL」**直连上传文件到 AWS S3 的需求了。

获取最终可播放的 URL 的几种方式

确实,你用来 PUT 上传的 Pre-signed URL 只是一个一次性的「上传用地址」,并不是最终用于访问或播放视频的永久链接。在 AWS S3 场景下,最终要播放或者下载这个视频,需要一个可用的「可访问」URL(可能是一个公共链接,也可能是一个另外生成的 Pre-signed URL 来 GET 该对象)。

下面就为你介绍如何获取使用这个「最终可访问」链接。

1. 为什么上传 URL 与下载/播放 URL 不同

  • 上传 URL (PUT):是后端/AWS SDK 生成的一个带有 PUT 权限、一定有效期的预签名链接,只允许你往该对象写入数据,一旦上传完成,这个链接要么就失效,要么只能再次被 PUT(但通常不建议重复使用)。
  • 下载/播放 URL (GET):是一个可读(public-read) 的链接,或者通过另一个 Pre-signed GET 请求生成的地址,用于下载/播放对象

二者可以指向同一个 S3 对象(即相同的 bucketName + key),但权限操作不同:一个是 PUT、一个是 GET。

2. 获取最终可播放的 URL 的几种方式

方式 A:Bucket 设置为公共读 (Public Read)

  1. Bucket对象设置为 public-read ACL(或者通过 Bucket Policy 允许所有人GET该对象)。
  2. 对象上传成功后,最终可访问链接通常是:
    https://{bucketName}.s3.{region}.amazonaws.com/{objectKey}
    • 例如:https://my-bucket.s3.us-east-1.amazonaws.com/videos/2025/hello.mp4
  3. 一旦对象是公共可读,你就可以直接把这个链接给前端或任何客户端,用 <VideoPlayer><img> 等直接访问。

缺点:所有人都能访问,不安全,除非你明确想公开这个视频。

方式 B:每次也用 Pre-signed GET URL

  • 如果你不想让 Bucket 公共读,就保持对象私有。
  • 当前端需要播放视频(或下载)时,后端/AWS SDK 再生成一个 Pre-signed GET 链接
    // Node.js / Python / Java 等后端伪代码
    s3.getSignedUrl('getObject', {
      Bucket: 'my-private-bucket',
      Key: 'videos/2025/hello.mp4',
      Expires: 3600, // 1小时有效期
    });
  • 前端收到后,就可以把这个链接当作视频播放地址,在有效期内进行播放或下载。
  • 优点:私有、安全;缺点:需要后端/服务端额外生成 GET URL。

方式 C:CloudFront + Signed URL

  • 使用 CloudFront 做 CDN 加速、缓存,并对接 S3。
  • 如果视频需要大规模分发高吞吐,通常会用 CloudFront。
  • 同样可以针对 CloudFront 分发设置签名策略,通过 CloudFront Signed URLSigned Cookie 来控制访问。

这种方案较为复杂,但适合流媒体分发大文件或有全局用户的场景。

3. 如何在代码中拼出最终访问链接

不管你选择公共还是私有 + 签名,关键在于对象的 Key。在你上传之前(或之后)你应该就能确定

  1. Bucket 名my-bucket
  2. 对象 Keyvideos/2025/hello.mp4 (或者后端告诉你存在哪个目录、生成了怎样的文件名)

有了它,你就能直接拼出 S3 访问地址:

final bucketName = 'my-bucket';
final region = 'us-east-1';
final objectKey = 'videos/2025/hello.mp4';

final publicUrl = 'https://$bucketName.s3.$region.amazonaws.com/$objectKey';

如果你的 Bucket 配置为公共读,就可以直接访问 publicUrl 来播放视频。
如果你是私有,则要向后端请求一个 Pre-signed GET URL 或者用 CloudFront + Signed URL。

4. 具体流程小结

  1. 前端调用后端,后端用 AWS SDK 来生成一个 Pre-signed PUT 链接,例如:
       https://my-bucket.s3.amazonaws.com/videos/2025/hello.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&...
       ```  
    2. **前端用 Dio** 做 `PUT presignedUrl`,把文件数据上传到 S3。  
    3. **上传完成** 后,你就可以**在后端或前端** 拼出**视频对象的最终访问 Key**(`videos/2025/hello.mp4`)。  
    4. **如果该对象是公共读**,那么 `https://my-bucket.s3.amazonaws.com/videos/2025/hello.mp4` 就是可访问的播放链接;  
       - 若**私有**,则要**生成 GET Pre-signed URL** 给前端,如:
         ```plaintext
         https://my-bucket.s3.amazonaws.com/videos/2025/hello.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...
    这样在有效期内可以播放/下载。

总之,最关键的是拿到对象的 Key,然后决定你使用「公开访问」还是「签名访问」来观看视频。

5. 代码示例:前端如何管理「最终链接」

假设你已经知道对象存储路径是 videos/2025/hello.mp4。上传完成后,你想在 Flutter 上把最终播放地址显示给用户:

// 最简单公共读场景
String getFinalPlaybackUrl(String objectKey) {
  const bucketName = 'my-bucket';
  const region = 'us-east-1';
  return 'https://$bucketName.s3.$region.amazonaws.com/$objectKey';
}

...

// 上传完成后
final objectKey = 'videos/2025/hello.mp4'; // 可能由后端告知
final playbackUrl = getFinalPlaybackUrl(objectKey);

// 在 Flutter UI 里想播放
VideoPlayerController _videoController = VideoPlayerController.network(playbackUrl);

如果是私有对象,那就向后端请求一个**getPresignedUrlForPlayback(objectKey)**的接口来得到 GET 预签名,然后同样调用 VideoPlayerController.network(signedUrl)

6. 结论

  • 预签名 PUT URL 只解决「如何把文件上传到 S3」。
  • 你想「播放」或「下载」该文件,需要可访问的 GET URL。
  • 公共读:直接 https://bucket.s3.region.amazonaws.com/objectKey
  • 私有:需要再生成一个 Pre-signed GET URL(或 CloudFront)来访问该文件。

从而完成上传播放的完整流程。希望以上内容能帮助你理解预签名URL的概念和如何获得最终的播放地址。祝开发顺利!