AWS S3
近期稍微涉猎点 AWS 的东西,记录一下,S3 上传。
一句话总结:
- 限制文件大小10MB。使用 AWS Api gateway 和 Lambda 上传文件到 S3,限制文件大小10MB。
- 直连 S3 上传,限制小点,目测是tb级别。
所以基本上就不用考虑 api gateway 和 Lambda 的方式了。
解决办法:
- 使用 Flutter 的 Dio 通过 Pre-signed URL(预签名 URL)直连上传文件到 AWS S3。
- 拿到预签名 URL 后,使用 Dio 的
put
方法,将文件内容直接上传到该 Pre-signed URL,这里注意需要设置Content-Type
和Content-Length
头。content-type 类似application/octet-stream
,content-length 类似file.lengthSync()
。 - S3 会根据 URL 中的签名校验请求、并将内容存储到指定 Bucket 的对象中。
- 上传成功后,后期如何拿这个文件,需要另外一个预签名 URL,类似之前的预签名,这里是 get 请求。
flutter dio put aws3上直连,用PresignedUrl方式,如何put
遇到content-type问题
在 Flutter 中使用 Dio 通过 Pre-signed URL(预签名 URL)直连上传文件到 AWS S3,最核心的思路是:
- 先从后端或某个服务获取一个可用的 Pre-signed URL(此 URL 通常包含了有效期、Bucket 名、对象键、上传所需的签名等信息)。
- 使用 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');
}
}
注意事项
Content-Type
要与预签名中的要求一致- 如果你的后端生成 Pre-signed URL 时已经设定了
Content-Type: image/jpeg
,那么前端必须在请求头中声明相同的 Content-Type,否则会出现签名校验不通过或上传失败。
- 如果你的后端生成 Pre-signed URL 时已经设定了
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), );
- 某些服务端/S3 配置可能要求设置确切的内容长度 (
这样就能在控制台看到实时的上传进度,或者在 UI 中更新进度条。
3. 确保 S3 配置正确
- CORS 配置:若在 Web 环境使用(Flutter Web),需要在 S3 Bucket 中正确设置 CORS,让浏览器可以向该
presignedUrl
发起跨域 PUT 请求。 - IAM 策略:确保生成 Pre-signed URL 的用户/角色拥有
s3:PutObject
权限,并且对应的 Bucket 策略允许该操作。 - URL 有效期:预签名地址往往有一个有效期(默认 900 秒等),请在有效期内执行上传。
4. 总结
- 获取 Pre-signed URL:后端/AWS SDK 负责生成,返回给前端。
- Flutter 端(Dio)使用
put
:put(presignedUrl, data: fileStream, options: Options(headers: ...))
- 确保
Content-Type
、其他自定义头与预签名保持一致。
- 检查返回状态码:200 或 204 视为上传成功。
- (可选)添加进度监听、错误处理、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)
- Bucket 或 对象设置为
public-read
ACL(或者通过 Bucket Policy 允许所有人GET该对象)。 - 对象上传成功后,最终可访问链接通常是:
https://{bucketName}.s3.{region}.amazonaws.com/{objectKey}
- 例如:
https://my-bucket.s3.us-east-1.amazonaws.com/videos/2025/hello.mp4
- 例如:
- 一旦对象是公共可读,你就可以直接把这个链接给前端或任何客户端,用
<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 URL 或 Signed Cookie 来控制访问。
这种方案较为复杂,但适合流媒体分发、大文件或有全局用户的场景。
3. 如何在代码中拼出最终访问链接
不管你选择公共还是私有 + 签名,关键在于对象的 Key
。在你上传之前(或之后)你应该就能确定:
- Bucket 名:
my-bucket
- 对象 Key:
videos/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. 具体流程小结
- 前端调用后端,后端用 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的概念和如何获得最终的播放地址。祝开发顺利!