0%

小程序上传图片到云存储再通过云函数到sm.ms

先说一下事请的起因,今天尝试用自己的小程序发帖时,发现传图不好使,并且返回都是403,应该是被sm.ms拉黑了(起初我调接口用的不是很正规的方法)

小程序上传文件服务器要求域名备案,无奈sm.ms没有备案的马甲,改吧。

然后就有了下边这个流程,并记录下其中遇到的坑

1. 贴一下我封装后的Promise代码,当伪代码看吧

1.1. 小程序选择图片

// 选择图片
const files = await wxchooseImage()
// files.tempFilePaths为文件全路径数组,截取出文件名file_name

1.2. 上传到云开发存储

const UPLOAD_PATH = 'upload/images/'
const DATE_PATH = new Date().Format('yyyyMMdd') + '/' // Format 函数自己实现

const cloudfile = await wxclouduploadFile({
cloudPath: UPLOAD_PATH + DATE_PATH + file_name,
filePath: file
})
// cloudfile.fileID为云存储的文件id,用来在云函数中下载

1.3. 调用云函数,传递fileID,得到sm.ms图片地址

const cloudResult = await wxcloudcallFunction({
name: 'getSmmsByFile', // 自己定义的云函数名
data: {
fileID: cloudfile.fileID
}
})

1.3.1. 云函数通过fileID下载文件

const file = await cloud.downloadFile({
fileID: conf.fileID,
})
// file.fileContent为文件Buffer对象
const file_ext = conf.fileID.substr(conf.fileID.lastIndexOf("."))

1.3.2. 云函数取得sm.ms的Token

不知从何时,sm.ms接口出了v2版,上传图片需要token,而且旧版接口已废弃

// 取得smms token
const token = await smms.getToken()
if (!token.success) {
result.msg = token.msg
return result
}

1.3.3. 云函数调用sm.ms上传接口

// 封装的request模块
const res = await ajax({
url: smms_host + 'upload',
headers: {
'Authorization': conf.token,
'Content-Type': 'multipart/form-data'
},
formData: {
smfile: {
value: file.fileContent,
options: {
filename: `图片${file_ext}`
}
},
format: 'json'
}
})

在最后上传这里,出了很多问题,有以下几点要注意: - request模块的POST multipart/form-data方式,传参需要放到formData中(而不是form),否则Content-Type会一直是x-www-form-urlencoded,即使你在headers设置了'Content-Type': 'multipart/form-data'实际发出的请求头依然为application/x-www-form-urlencoded。 - 搜索引擎搜到的上传File对象的代码基本都是下面这样

formData: {
smfile: fs.createReadStream(path)
}
尤其是当我看到Node.js关于File System的官方文档
fs.createReadStream(path[, options])
- path <string> | <Buffer> | <URL>
- options <string> | <Object>
...
可见,path支持路径或者Buffer对象或者URL,然而当你直接把云存储下载得到的文件Buffer对象作为入参时,像下边这样
formData: {
smfile: fs.createReadStream(file.fileContent)
}
又报错了:
The "path" argument must be of type string without null bytes. 
直到我发现这一篇issues,原来此Buffer非彼Buffer,它期望的是缓冲区形式的路径,而不是数据缓冲区,详情见issues。 - 使用fs.writeFileSync(path, file.fileContent)保存文件到云函数本地,再通过fs.createReadStream(path)是不是就可以了? 然而,经测试,云函数文件环境为Read-only,并不能成功保存文件😂。 - 直接使用Buffer对象上传时,像下边这样
formData: {
smfile: file.fileContent
}
此时接口通了,但上传接口又返回了如下错误
{"success":false,"msg":"Invalid file source"}
可见对方无法正常收取文件的内容。 - 最后使用如下这种方式,终于成功了🎉🎉🎉
formData: {
smfile: {
value: file.fileContent,
options: {
filename: `图片${file_ext}`
}
}
}
此种方法通过options的方式手动提供"文件"相关的信息,request官方文档也有此方式的详细介绍。

好了,终于传上去了🏆🍻🍗

请我喝杯咖啡吧 Coffee time !