简介
本文介绍如何不依赖 SDK,用简单的代码,在小程序直传文件到腾讯云COS的存储桶。
注意:
本文档内容基于 XML 版本的 API。
前期条件
登录对象存储控制台,创建存储桶,设置 BucketName(存储桶名称) 和 Region(地域名称),详情请参见创建存储桶文档。
登录访问管理控制台,进入 API 密钥管理页面,获取您的项目 SecretId 和 SecretKey。
注意:
目前腾讯云有COS特惠活动,新人1元起
实践步骤
1. 配置小程序域名白名单
小程序里请求腾讯云COS需要登录到微信公众平台,在“开发”->“开发设置”中,配置域名白名单。SDK 用到了两个接口:wx.uploadFile 和 wx.request。
cos.postObject 使用 wx.uploadFile 发送请求。
其他方法使用 wx.request 发送请求。
两者都需要在对应白名单里,配置 COS 域名。白名单里配置的域名格式有两种:
如果只用到一个存储桶,可以配置 Bucket 域名作为白名单域名,例如examplebucket-1250000000.cos.ap-guangzhou.myqcloud.com。
如果用到多个存储桶,可以选择后缀式请求 COS,把 bucket 放在 pathname 里请求,这种方式需要配置地域域名作为白名单,例如cos.ap-guangzhou.myqcloud.com。具体可参考下文代码中的注释。
2. 获取临时密钥和计算签名
出于安全考虑,签名使用临时密钥,服务端搭建临时密钥服务,请参见PHP 示例、Nodejs 示例。
如有其他语言或自行实现可参考以下流程:
(1)向服务端获取临时密钥,服务端首先使用固定密钥 SecretId、SecretKey 向 STS 服务获取临时密钥,得到临时密钥 tmpSecretId、tmpSecretKey、sessionToken,详情请参见临时密钥生成及使用指引或cos-sts-sdk。
注意:
根据使用的请求是put还是post,STS 的 policy action 要加上允许 "name/cos:PutObject"或"name/cos:PostObject"。
(2)前端通过 tmpSecretId、tmpSecretKey,以及 method、pathname 计算签名,可参考下文使用cos-auth.js来计算签名,如果业务需要也可以放在后端计算签名。
(3)将计算得到的签名和 sessionToken,分别放到
post请求的formData 的 Signature 和 x-cos-security-token 字段里,向 COS API 发出上传请求。
put请求的headers 的 Signature 和 x-cos-security-token 字段里,向 COS API 发出上传请求。
注意:
正式部署时服务端请加一层您的网站本身的权限检验。
3. 后缀式请求
COS API 一般的请求格式都类似POST http://examplebucket-1250000000.cos.ap-beijing.myqcloud.com/,请求的域名是存储桶域名。这样如果在小程序里用到多个存储桶,则需要配置这个存储桶域名作为白名单域名。解决方法如下:
COS 提供了后缀式请求格式POST http://cos.ap-beijing.myqcloud.com/examplebucket-1250000000/,请求的域名是地域域名,存储桶名称放在请求的路径里。在小程序里用到同一个地域多个存储桶,只需要配置一个域名cos.ap-beijing.myqcloud.com作为白名单域名。
后缀式请求格式需要注意,签名时使用的路径要用以存储桶名称作为前缀的路径,例如/examplebucket-1250000000/。
4. 直传示例代码
以下代码同时举例了PUT Object 接口(推荐使用)和POST Object 接口,操作指引如下:
varCosAuth=require('./cos-auth');// 这里引用了 cos-auth.js,下载地址为 https://unpkg.com/cos-js-sdk-v5/demo/common/cos-auth.min.js varBucket='examplebucket-1250000000';varRegion='ap-shanghai';varForcePathStyle=false;// 是否使用后缀式,涉及签名计算和域名白名单配置,后缀式说明看上文varuploadFile =function() {// 请求用到的参数varprefix ='https://'+Bucket+'.cos.'+Region+'.myqcloud.com/';if(ForcePathStyle) {// 后缀式请求在签名时域名使用地域域名,而不是存储桶域名,具体说明见本文上述“3.后缀式请求”prefix ='https://cos.'+Region+'.myqcloud.com/'+Bucket+'/'; }// 对更多字符编码的 url encode 格式varcamSafeUrlEncode =function(str) {returnencodeURIComponent(str) .replace(/!/g,'%21') .replace(/'/g,'%27') .replace(/\(/g,'%28') .replace(/\)/g,'%29') .replace(/\*/g,'%2A'); };// 获取临时密钥varstsCache;vargetCredentials =function(callback) {if(stsCache &&Date.now() /1000+30< stsCache.expiredTime) {callback(data.credentials);return; } wx.request({method:'GET',url:'https://example.com/sts.php',// 服务端签名,参考上文说的获取临时密钥dataType:'json',success:function(result) {vardata = result.data;varcredentials = data.credentials;if(credentials) { stsCache = data }else{ wx.showModal({title:'临时密钥获取失败',content:JSON.stringify(data),showCancel:false}); }callback(stsCache && stsCache.credentials); },error:function(err) { wx.showModal({title:'临时密钥获取失败',content:JSON.stringify(err),showCancel:false}); } }); };// 计算签名vargetAuthorization =function(options, callback) {getCredentials(function(credentials) {callback({XCosSecurityToken: credentials.sessionToken,Authorization:CosAuth({SecretId: credentials.tmpSecretId,SecretKey: credentials.tmpSecretKey,Method: options.Method,Pathname: options.Pathname, }) }); }); };// post上传文件varpostFile =function(filePath) {varKey= filePath.substr(filePath.lastIndexOf('/') +1);// 这里指定上传的文件名varsignPathname ='/';// PostObject 接口 Key 是放在 Body 传输,所以请求路径和签名路径是 /if(ForcePathStyle) {// 后缀式请求在签名时用的路径,要包含存储桶名称,具体说明见本文上述“3.后缀式请求”signPathname ='/'+Bucket+'/'; }getAuthorization({Method:'POST',Pathname: signPathname},function(AuthData) {varrequestTask = wx.uploadFile({url: prefix,name:'file',filePath: filePath,formData: {'key':Key,'success_action_status':200,'Signature':AuthData.Authorization,'x-cos-security-token':AuthData.XCosSecurityToken,'Content-Type':'', },success:function(res) {varurl = prefix +camSafeUrlEncode(Key).replace(/%2F/g,'/');console.log(res.statusCode);console.log(url);if(/^2\d\d$/.test(''+ res.statusCode)) { wx.showModal({title:'上传成功',content: url,showCancel:false}); }else{ wx.showModal({title:'上传失败',content:JSON.stringify(res),showCancel:false}); } },fail:function(res) { wx.showModal({title:'上传失败',content:JSON.stringify(res),showCancel:false}); } }); requestTask.onProgressUpdate(function(res) {console.log('进度:', res); }); }); };// put上传文件varputFile =function(filePath) {varKey= filePath.substr(filePath.lastIndexOf('/') +1);// 这里指定上传的文件名varsignPathname ='/'+Key;// PutObject 接口 Key 是放在 url 传输,所以请求路径和签名路径是 /Keyif(ForcePathStyle) {// 后缀式请求在签名时用的路径,要包含存储桶名称,具体说明见本文上述“3.后缀式请求”signPathname ='/'+Bucket+'/'+Key; }getAuthorization({Method:'PUT',Pathname: signPathname},function(AuthData) {// put请求需要从文件临时路径读取出文件内容varwxfs = wx.getFileSystemManager(); wxfs.readFile({filePath: filePath,success:function(fileRes) {varrequestTask = wx.request({url: prefix + signPathname.substr(signPathname.lastIndexOf('/') +1),method:'PUT',header: {'Authorization':AuthData.Authorization,'x-cos-security-token':AuthData.XCosSecurityToken, },data: fileRes.data,success:functionsuccess(res) {varurl = prefix +camSafeUrlEncode(Key).replace(/%2F/g,'/');if(res.statusCode===200) { wx.showModal({title:'上传成功',content: url,showCancel:false}); }else{ wx.showModal({title:'上传失败',content:JSON.stringify(res),showCancel:false}); }console.log(res.statusCode);console.log(url); },fail:functionfail(res) { wx.showModal({title:'上传失败',content:JSON.stringify(res),showCancel:false}); }, }); requestTask.onProgressUpdate(function(res) {console.log('正在进度:', res); }); }, }); }); };// 选择文件wx.chooseImage({count:1,// 默认9sizeType: ['original'],// 可以指定是原图还是压缩图,这里默认用原图sourceType: ['album','camera'],// 可以指定来源是相册还是相机,默认二者都有success:function(res) {putFile(res.tempFiles[0].path);// put请求上传,推荐使用// postFile(res.tempFiles[0].path); // post请求上传} })};