最近在微信公众号工作,需要上传图片到阿里云OSS。在做这个功能的过程中,走了很多弯路,尝试了很多方法,终于开发出一种方便又美观的方式。现将这些方法和思路记录下来,以免遗忘。1、通过浏览器直接传输到OSS是最简单的方式。因为微信公众号的跳转页是基于QQ浏览器的,可以直接使用HTML输入元素来选择图片。OSS有一个PostObject接口,允许HTML表单上传文件。除了文件,还有一些其他的字段,比如保存到OSS的路径(key),策略(policy),accessKeyId,自己OSS应用的签名(signature)等等,所以需要构造表单。一般有两种方式:1)构造一个DOM节点,提交并上传表单像下面的代码构造一个表单元素,然后使用$('form').submit()进行提交。2)使用Html5的FormData对象上传如下FormData对象,然后通过ajax或者fetchpostformdata。constformData=newFormData();formData.append('key',filePath);//OSS保存路径formData.append('policy',policy);//策略formData.append('OSSAccessKeyId',accessKeyId);//OSS对象的标识formData.append('success_action_status','200');//成功返回码formData.append('signature',signature);//签名formData.append('file',file);//图片文件,$('input[name="pic"]').files[0]2.从服务器下载微信图片传给OSS的方法简单直接,但是只能选择图片从专辑中。如果要拍照上传,必须通过微信JS-SDK调用相机。wx.chooseImage({count:1,//default9sizeType:['original','compressed'],//可以指定原图或压缩图,默认都有sourceType:['album','camera'],//可以指定来源是相册还是相机,默认都成功:function(res){//返回选中照片的本地ID列表,localId可以作为img标签的src属性显示图像varlocalIds=res.localIds;}});这里有一个问题。微信JS-SDK选择图片后,返回的是图片的id,而不是实际的图片文件,所以无法构建表单上传OSS。那么该怎么办?思路:先将图片上传到微信服务器(最多保存3天),再通过微信下载多媒体文件接口(http://file.api.weixin.qq.com...),然后上传到OSS(虽然有点绕,但是可行)。客户端代码:wx.chooseImage({count:1,//默认9sizeType:['original','compressed'],sourceType:['album','camera'],success:function(res){varlocalIds=res.localIds;wx.uploadImage({localId:localIds[0],//待上传图片的本地ID,从chooseImage接口获取isShowProgressTips:1,//默认为1,显示进度提示成功:function(res){varserverId=res.serverId;//返回图片的服务器ID//dosomething...//调用自己搭建的服务器的api,传入serverId,做相关操作获取微信图片并上传OSSdoSomething();}});}});提示:只要在选择图片的时候选择压缩,微信会自动帮我们压缩图片。官方文档中还写明了对上传的多媒体文件的格式和大小进行控制,图片控制为jpg格式和1M以下尺寸。所以基本上不用考虑图片过大的问题。实测中,8M的图片压缩后只有120KB左右。服务端的代码经过3次改进:1)使用fs将图片写入本地constfs=require('fs');constrequest=require('require');constOSS=require('ali-oss').Wrapper;constossClient=newOSS({accessKeyId:'你的访问密钥',accessKeySecret:'你的访问密码',bucket:'你的bucket名称',region:'oss-cn-hangzhou'});//我们需要获取微信accessToken,这里不再赘述constaccessToken='accesstoken';constmediaId='xxxxxxx';//微信多媒体文件idconstdestPath=`weixin/images/201702/${mediaId}.jpg`;//OSS文件路径,根据自己喜好构造constwxReq=request(`http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=${accessToken}&media_id=${mediaId}`);//将文件流通过管道传输到本地文件wxReq.pipe(fs.createWriteStream(`${mediaId}.jpg`));wxReq.on('end',()=>{co(function*(){constresult=yieldossClient.putStream(destPath,fs.createReadStream(`${mediaId}.jpg`),{timeout:30*60*1000});console.log('图片上传到阿里云的结果',result);fs.unlink(`${mediaId}.jpg`);//res.status(200).json(result);}).catch(err=>{console.警告(错误);//res.status(500).send('上传文件出错');});});这种方法需要经常写入和删除文件,完全没有geek的感觉2)使用memory-streams模块将图片写入内存constrequest=require('require');constOSS=require('ali-oss').Wrapper;conststreams=require('memory-streams');constossClient=newOSS({accessKeyId:'你的accesskey',accessKeySecret:'你的accesssecret',bucket:'你的bucket名称',region:'oss-cn-hangzhou'});constaccessToken='访问令牌';constmediaId='xxxxxxx';//微信多媒体文件idconstdestPath=`weixin/images/201702/${mediaId}.jpg`;//OSS文件路径constwriter=newstreams.WritableStream();constwxReq=request(`http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=${accessToken}&media_id=${mediaId}`);wxReq.pipe(writer);wxReq.on('end',()=>{co(function*(){constresult=yieldossClient.put(destPath,writer.toBuffer(),{timeout:30*60*1000});console.log('图片上传到阿里云结果',result);//res.status(200).json(result);}).catch(err=>{console.warn(err);//res.status(500).send('上传文件错误');});});这个方法是把图片暂时存放在内存中,那么如果并发量大,是不是呢?内存要炸了?还是觉得不可取3)折腾了半天把下载的图片流直接写入OSS文件,发现原来可以这么简单优雅:constrequest=require('require');constOSS=require('ali-oss').包装纸;constossClient=newOSS({accessKeyId:'你的accesskey',accessKeySecret:'你的accesssecret',bucket:'你的bucket名称',region:'oss-cn-hangzhou'});constaccessToken='访问令牌';constmediaId='xxxxxxx';//微信多媒体文件idconstdestPath=`weixin/images/201702/${mediaId}.jpg`;//OSS文件路径constwxReq=request(`http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=${accessToken}&media_id=${mediaId}`);wxReq.on('response',(response)=>{//请求response可以读取结果response并流式传输到ossClientco(function*(){constresult=yieldossClient.putStream(destPath,response,{timeout:30*60*1000});console.log('图片上传到阿里云的结果',result);//res.status(200).json(result);}).catch(err=>{console.warn(err);//res.status(500).send('上传文件出错');});});这种方法省去了前两种方法的中间步骤,更加简洁直接,个人认为是最好的。