微信小程序直传阿里云 OSS 的实践总结

做小程序图片上传时,最容易踩坑的不是选图 UI,而是鉴权、签名、实际上传、回调收尾这一整条链路。下面整理一套在生产环境跑通过的做法,接口路径和业务字段已做脱敏,重点讲流程和容易翻车的地方。


整体思路

小程序端不持有 AccessKey,也不自己算 OSS 签名。正确姿势是:

  1. 前端选文件、算 MD5、组装元信息
  2. 调自家后端「上传鉴权」接口,拿到 OSS 上传凭证
  3. 根据后端返回的上传方式,走表单上传 / 预签名 PUT / 分片 PUT
  4. OSS 上传成功后,再调后端「完成上传」接口,拿到可读 URL 和 fileId

这样密钥只在服务端,小程序只拿临时 policy、signature 或预签名 URL。

选图 → 算 MD5 → 鉴权接口 → 直传 OSS → 完成上传接口 → 回写业务表单

第一步:选文件与本地预处理

选图

用 wx.chooseImage,注意 count 要扣掉已选数量,别让用户超上限。

wx.chooseImage({
  count: remainCount,
  sizeType: ['compressed'],
  sourceType: ['album', 'camera'],
  success(res) {
  // 把 tempFiles 转成内部 fileList 结构
  }
});

本地文件建议单独标记 isLocal: true,和已上传的远程图分开管理,避免重复上传。

算 MD5

上传前先调 wx.getFileInfo,digestAlgorithm 设为 md5:

wx.getFileInfo({
  filePath,
  digestAlgorithm: 'md5',
  success(res) {
    const fileMd5 = res.digest;
  }
});

MD5 有两个用处:

  • 传给后端做秒传 / 去重(后端若发现文件已存在,可直接返回成功,不用再传 OSS)
  • 拼进文件名,降低重名概率

生成 OSS 对象名

别直接用微信临时路径当文件名。常见做法:

{业务前缀}_{md5前12位}_{时间戳}_{三位随机数}.{后缀}

后缀从原路径截取,MIME 类型也按后缀推断(jpg/png/gif/webp)。


第二步:调鉴权接口

鉴权请求体大致包含这些字段(字段名按你们后端约定来):

字段说明
bucket_pre公有/私有桶标识,如 public / private
file_dirOSS 目录,如 weixin/images
file_name上一步生成的对象名
file_md5文件 MD5
file_size字节大小
typeMIME,如 image/jpeg
part_size / chunk_count分片相关,小图通常 chunk_count=1

鉴权响应里重点关注:

字段含义
upload_status上传状态码,决定是否还要传 OSS
server_type存储类型,值为 oss 时走 OSS 直传
file_pathOSS 对象 key
file_id业务文件 ID,收尾接口要用
host / bucket / region拼 OSS 上传域名
policy / signature / access_id表单上传凭证(V1)
security_tokenSTS 临时凭证
chunk_urls分片预签名 URL 列表

upload_status 的快速路径

生产里后端常会返回这几种状态(数字仅作示例,以你们接口为准):

  • 已存在、待收尾:文件在 OSS 有了,前端跳过直传,直接调「完成上传」
  • 完全秒传:连收尾都省了,直接返回 read_path / file_id

这两种情况能省不少流量和时间,前端一定要判,别无脑再传一遍。


第三步:实际上传 OSS

后端返回 type === 'oss' 时,按优先级依次尝试:

方式一:表单 POST(推荐,小文件首选)

条件:host + file_path + policy + signature + accessKeyId 齐全,且不是 OSS V4 签名。

用 wx.uploadFile,不要用 wx.request:

wx.uploadFile({
  url: 'https://{bucket}.{region}.aliyuncs.com',
  filePath: tempPath,
  name: 'file',
  formData: {
    key: file_path,
    OSSAccessKeyId: access_id,
    policy: policy,
    signature: signature,
    success_action_status: '200',
    'x-oss-security-token': security_token  // STS 场景必传
  },
  timeout: 30000
});

成功状态码一般是 200 或 204。uploadFile 自带进度回调,UI 好做。

V4 签名字段不同,formData 要换成:

key
policy
x-oss-signature-version
x-oss-credential
x-oss-date
x-oss-signature
x-oss-security-token(如有)
success_action_status

V4 和 V1 不能混用字段,判错了一直 403。

方式二:预签名 PUT

鉴权接口直接给了带 signature= / x-oss-signature= / security-token= 的 URL 时,用 PUT 整文件上传:

const fs = wx.getFileSystemManager();
fs.readFile({
  filePath: tempPath,
  success(readRes) {
    wx.request({
      url: presignedPutUrl,
      method: 'PUT',
      data: readRes.data,
      header: {
        'Content-Type': 'image/jpeg',
        'x-oss-security-token': security_token  // URL 里没带 token 时才加 header
      }
    });
  }
});

注意:

  • PUT 要先 readFile 读进内存,大图会占内存,图片场景一般还能接受,视频就要考虑分片
  • Content-Type 要和签名时一致,否则验签失败
  • 域名必须是 https

方式三:二次要签名

鉴权响应里没有完整 policy,但有 file_path,再调一次「获取签名」接口,拿到 policy/signature 后仍走表单上传。这是兜底路径,别把它当主流程,否则多一次 RTT。

方式四:分片 PUT(非 OSS 或后端自建分片)

server_type 不是 oss,或返回了 chunk_urls 数组时,按分片顺序 PUT:

fs.readFile({
  filePath,
  position: start,
  length: chunkSize,
  success(readRes) {
    wx.request({
      url: chunkItem.upload_url,
      method: 'PUT',
      data: readRes.data,
      header: { 'Content-Type': 'application/octet-stream' }
    });
  }
});

分片大小常见 15MB,按 file_size / chunk_size 算片数,顺序上传,每片成功再传下一片,最后调完成接口。


第四步:完成上传

OSS 返回 200/204 只代表对象进去了,业务库里的文件记录通常还没落。必须再调「完成上传」接口,把 file_id、md5、路径等传回去,拿到:

  • read_path / url:前端展示、表单提交用
  • file_id:业务关联用
  • view_path:有时和 read_path 不同(CDN、鉴权访问等)

漏掉这一步,后台看不到文件,表单提交也会缺 fileId。


小程序侧必配项

1. 服务器域名白名单

微信公众平台 → 开发 → 开发管理 → 开发设置 → 服务器域名:

  • uploadFile 合法域名:OSS 的 bucket 域名,如 https://xxx.oss-cn-hangzhou.aliyuncs.com
  • request 合法域名:自家 API 域名 + 若用 PUT 直传 OSS,OSS 域名也要加

域名不对的表现:uploadFile:fail url not in domain list,不是代码 bug。

2. 只用 HTTPS

OSS 和小程序都要求 HTTPS,代码里把 http:// 统一替换成 https://。

3. 超时

单文件上传建议设 30s 超时,超时后标记失败、允许重试,别一直 loading。


容易踩的坑

1. 用 wx.request 模拟 multipart 表单

表单上传请用 wx.uploadFile。自己拼 multipart body 在微信里又麻烦又容易错 boundary。

2. V1 / V4 签名混用

鉴权响应里如果出现 x-oss-signature-version 且含 OSS4,就不能再走 OSSAccessKeyId + policy + signature 那套 V1 formData。

3. STS 漏传 token

临时凭证场景,x-oss-security-token 表单字段或 PUT header 缺了必 403。预签名 URL 里已带 token 就别重复加 header。

4. Content-Type 不一致

PUT 上传时 header 里的类型必须和签名时一致,jpg 就传 image/jpeg,别默认 octet-stream(分片除外)。

5. 大文件 readFile 爆内存

整文件 PUT 会把文件读进内存。图片压缩后一般 OK;大视频要分片或走后端中转。

6. 只传 OSS 不调完成接口

对象在桶里有了,业务系统没记录,等于白传。

7. 文件名 / key 重复

纯时间戳命名高并发会撞名。MD5 + 随机数能缓解,最终仍以 OSS 返回为准。

8. 进度条假死

uploadFile 有 onProgressUpdate;PUT 分片要自己按已传字节算百分比。

9. 秒传逻辑没接

后端都返回「文件已存在」了,前端还在传 OSS,浪费用户流量和时间。


组件层的一些实用细节

如果封装成上传组件,这几件事值得做:

  1. 本地 / 远程分状态:isLocal 标记待传文件,已有 fileId 的跳过
  2. 外部值同步加锁:value / netImgs / netFileIds 多个 prop 互相触发 observer 时,用 isSyncing 防循环 setData
  3. 批量顺序上传:for...await 一张一张传,比并发更稳,OSS 和小程序连接数都有限
  4. 统一 emit:上传完抛 change(urls + fileIds)和 uploadComplete,表单页只关心结果
  5. 失败可重试:失败把 uploading 置 false、progress 归零,别卡在半成功状态

后端需要配合什么

前端能跑通,后端至少提供三个能力:

  1. 鉴权:根据 md5/大小/目录生成 OSS key,返回上传凭证或秒传结果
  2. 签名(可选):V4 或动态 policy 时单独签发
  3. 完成上传:OSS 对象落库,返回可读地址和 fileId

密钥、RAM 角色、STS 有效期、bucket 策略都在服务端配,别下发到小程序。


最小可用流程(伪代码)

async function uploadOne(tempPath, meta) {
  const md5 = await getFileMd5(tempPath);
  const fileName = buildFileName(tempPath, md5);

  const auth = await api.post('/upload/auth', {
    file_md5: md5,
    file_name: fileName,
    file_size: meta.size,
    file_dir: 'wx_app/images',
    type: meta.mime
  });

  if (auth.upload_status === 'INSTANT') {
    return auth; // 秒传
  }

  if (auth.server_type === 'oss') {
    if (hasFormPolicy(auth)) {
      await ossFormUpload(tempPath, auth);
    } else if (hasPresignedUrl(auth)) {
      await ossPutUpload(tempPath, auth);
    } else {
      const sign = await api.post('/upload/sign', { file_path: auth.file_path });
      await ossFormUpload(tempPath, { ...auth, ...sign });
    }
  } else {
    await chunkPutUpload(tempPath, auth.chunk_urls);
  }

  return api.post('/upload/finish', { file_id: auth.file_id, file_md5: md5 });
}

小结

微信小程序传 OSS,核心就一句话:后端签发、前端直传、完成后回调。

选型上,小图优先 wx.uploadFile 表单 POST;有预签名 URL 再用 PUT;大文件走分片。上线前把 OSS 域名加进白名单、STS token 传对、完成接口接上,这三件做到位,大部分 403 和「传了但保存不了」的问题都能避开。

如果你也在做类似组件,建议先把鉴权响应打日志(脱敏后)看清楚后端到底走哪条分支,再写上传逻辑,比一上来硬套官方 demo 省时间得多。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐