在微信小程序中 接入 Cloudbase 云存储上传
一句话定义:登录之后,用
wx.chooseMedia选图片或视频,经app.uploadFile推到 Cloudbase 云存储,把返回的fileID存数据库,渲染时再用app.getTempFileURL换成可直接<image>渲染的临时链接,完整链路。预计耗时:30 分钟 | 难度:进阶
适用场景
- 适用:已经接完 add-auth-wechat-miniprogram 的小程序,要做头像 / 商品图 / 订单凭证 / 短视频上传
- 适用:用
@cloudbase/js-sdk+@cloudbase/adapter-wx_mp接独立 Cloudbase 环境的玩法。本文不是给微信·云开发(wx.cloud)用的,那一套有自己的wx.cloud.uploadFile,接口形似但凭据体系不同 - 不适用:大文件分片上传(超过 100MB 的视频),建议先压缩或者分片上传到 COS 再回写 fileID
- 不适用:需要前端直接拿到永久 URL 的场景(默认是公有读才永久)。私有读文件每次访问都要换临时链接
环境要求
| 依赖 | 版本 |
|---|---|
@cloudbase/js-sdk | 2.27.3 |
@cloudbase/adapter-wx_mp | 1.3.1 |
| 微信开发者工具 | ≥ 1.06.x |
另外需要:
- 已完成 add-auth-wechat-miniprogram,
auth.hasLoginState()返回 true - 控制台「云存储」已开通,默认会有一个空 bucket
app.json里requiredPrivateInfos加上chooseMedia(必要),否则在审核版本上选媒体会失败
第一步:选媒体
wx.chooseMedia 是微信官方推荐的统一选媒体接口,替代了老的 chooseImage / chooseVideo。
// pages/upload/upload.js
async function pickMedia() {
const res = await new Promise((resolve, reject) => {
wx.chooseMedia({
count: 9, // 最多 9 张
mediaType: ['image'], // 只选图,要视频改成 ['video'] 或 ['mix']
sourceType: ['album', 'camera'], // 相册 + 拍照
sizeType: ['compressed'], // 压缩版,节省流量
success: resolve,
fail: reject,
});
});
return res.tempFiles;
}
返回的每个 tempFiles[i] 形如:
{
tempFilePath: 'http://tmp/...', // 小程序本地临时路径
size: 12345, // 字节
fileType: 'image',
width: 1080,
height: 1920,
}
第二步:上传到云存储
每个临时文件调一次 app.uploadFile。这里的 app 是 add-auth-wechat-miniprogram 第四步初始化的那个 Cloudbase app。
import app from '../../libs/cloudbase';
import { ensureLogin } from '../../libs/login';
async function uploadOne(tempFilePath, openid) {
const ext = tempFilePath.split('.').pop() || 'jpg';
// cloudPath 用「openid 路径 + 时间戳 + 随机数」组合,防冲突 + 后面好做权限隔离
const cloudPath = `users/${openid}/${Date.now()}_${Math.random().toString(36).slice(2, 8)}.${ext}`;
const res = await app.uploadFile({
cloudPath,
filePath: tempFilePath, // 小程序里直接传 tempFilePath
});
return res.fileID; // 'cloud://your-env.bucket/users/openid/..../xxx.jpg'
}
export async function uploadAll(tempFiles) {
const user = await ensureLogin();
const openid = user.customUserId; // 或 user.uid
// 串行上传,易于错误处理。要并行可换 Promise.all,但要注意小程序对并发请求数有上限
const fileIDs = [];
for (const f of tempFiles) {
const id = await uploadOne(f.tempFilePath, openid);
fileIDs.push(id);
}
return fileIDs;
}
cloudPath 命名上几个易疏漏的点:
- 不要以
/开头。Cloudbase 文件名规范禁止根目录起斜杠 - 不要出现
//。两个连续斜杠会被拒 - 中文文件名能存,但访问时会被 URL Encode,排查问题时不直观,建议统一英文 + 数字
- 用
openid/uid做目录前缀,后面配安全规则才好写「用户只能动自己目录下的文件」
第三步:把 fileID 存数据库
上传成功后拿到 fileID,业务侧通常把它写到对应的业务集合:
import { db } from '../../libs/cloudbase';
async function saveOrderImages(orderId, fileIDs) {
await db.collection('orders').doc(orderId).update({
images: fileIDs, // 直接存数组
updatedAt: db.serverDate(),
});
}
为什么存 fileID 不存 URL:
- URL 可能过期(私有读文件的临时链接有有效期),fileID 是永久不变的
- 公有读 / 私有读权限切换时,fileID 不动,业务代码不用改
- 不同环境(测试 / 正式)迁移时,fileID 也好替换