4.1 KiB
4.1 KiB
【Demo】OSS 直传(客户端签名 + PUT 上传)
源文件:
lib/pages/demo/oss_upload_demo.dart| 分类:网络 一句话:选图 → 后端分配路径 → 拿 STS → V4 签名 → PUT 直传 OSS,全程带进度。
一、这个 Demo 解决什么问题
图片上传不走"客户端 → 业务服务器 → OSS"的转发链路(浪费带宽、服务器压力大), 而是 STS 临时凭证 + 客户端直传 OSS:后端只负责分配路径和签发凭证,图片字节直接传阿里云。
完整链路(与 UniApp ossUpload.js 对照)
选图 → 后端分配路径(GetXxxOSSUploadUrl)
→ 拿 STS 凭证(GetAliOSSSetting)
→ V4 签名
→ PUT 直传 OSS(带进度)
→ 返回 CDN URL
二、怎么运行 / 演示内容
runApp(const MaterialApp(home: OssUploadDemoPage()));
- 选择上传场景:头像 / 笔记 / AI判题(
UploadScene枚举,对应后端 mode); - "选择图片"(走 PickerService,见 note_picker_demo.md);
- "上传到 OSS":读字节 → 4 步链路 → 显示 CDN URL + 进度。
⚠ 需要后端 OSS 接口可访问(本地/测试环境)。
三、核心知识点(新手向)
1. 双端读取图片字节(不用 dart:io!)
// web :image_picker 已读好字节
// 原生 :photo_manager 的 AssetEntity.originBytes(纯 Dart API)
final Uint8List bytes = image.bytes ??
await image.entity?.originBytes ??
Uint8List(0);
if (bytes.isEmpty) { /* 提示重新选择 */ }
关键:页面不 import dart:io,Web 才能编译(dart:io 在 Web 是禁用的)。
双端都能拿字节 → 上传逻辑统一。
2. 上传入口(OssUploader 封装)
final url = await OssUploader.uploadImage(
bytes: bytes,
fileName: fileName,
scene: _scene, // UploadScene 枚举:头像/笔记/AI判题
onSendProgress: (sent, total) {
if (total > 0) {
setState(() => _progress = sent / total); // total<=0 兜底
}
},
);
3. 403 的含义
403 = 签名或 STS 有问题 —— 看控制台 LogInterceptor 打印的响应体定位:
- AccessDenied:签名计算不一致(method/path/时间戳对不上);
- InvalidAccessKeyId / ExpiredToken:STS 凭证失效;
- SignatureDoesNotMatch:V4 签名算法细节错。
四、关键代码剖析
// 场景名映射(switch 表达式,Dart 3)
static String _sceneName(UploadScene s) => switch (s) {
UploadScene.avatar => '头像',
UploadScene.note => '笔记',
UploadScene.aiGrading => 'AI判题',
};
// 场景选择(上传中禁用切换)
Wrap(
spacing: 10.w,
children: UploadScene.values
.map((s) => ChoiceChip(
label: Text(_sceneName(s)),
selected: _scene == s,
onSelected: _uploading ? null : (_) => setState(() => _scene = s),
))
.toList(),
)
// 上传按钮(没选图或上传中 → 禁用)
FilledButton.icon(
onPressed: (_image == null || _uploading) ? null : _upload,
icon: const Icon(Icons.cloud_upload_outlined),
label: Text(_uploading ? '上传中...' : '上传到 OSS'),
)
五、踩坑记录
- 别用
Image.file双端判断 —— dart:io 在 Web 编译不过,统一走 bytes; - 403 优先查签名:method/URL 编码/时间格式与后端约定不一致是头号原因;
total <= 0时进度要兜底(和 dio_pro_demo 同一个坑);- 上传中要禁用场景切换和重复点击(
_uploading统一控制); - 文件名生成:
image.name ?? 'image_时间戳.jpg',避免空名。
六、扩展思考
- 防盗链/私有读:OSS 私有 Bucket 的 URL 需要签名,展示时再向后端换"临时公开 URL";
- 断点续传/分片:大文件(视频)用 OSS 分片上传 API,
uploadFile封装在 OssUploader 里扩展; - 与 dio_pro_demo.md 对照:PUT 直传的进度就是
onSendProgress的应用; - 笔记图片多图上传:先全部直传拿 URL,再一次性提交给"创建笔记"接口(见 note_picker_demo 的 TODO 注释)。