PC 千牛小程序里,能够用于上传文件的 API 只有 my.uploadFile。关于此 API 的试用,详见 API 文档和补充使用说明。
因为上传图片是几乎每个小程序都需要使用到的功能,而我们经常接到上传图片相关的咨询,因此针对咨询中的热点问题,整理出本文档。
A:需要。请提交工单申请。
A:因为并不是所有阿里系域名的内容都是集团直接管控的。例如阿里云的用户内容,也会使用阿里云的域名。如果您使用 my.uploadFile 时报错为“no permission”,就说明是域名管控导致。
A:一般推荐使用商家云存储或图片空间。如果有需要上传到自有服务端,请通过小程序云对接。
原则上不允许直接上传到自有服务器,或者上传到公有云服务,除非业务确实有需要、运营同意开白名单。
A:参考文档:
前置业务流程:
示例代码:
import { Cloud } from '@tbmp/mp-cloud-sdk';
const cloud = new Cloud();
cloud.init({
env: 'test' // 线上版本请使用 online
});
// 本例为了简单起见,先让用户选择一个文件,得到 apFilePath,实际业务中可使用其他 API 产生的 apFilePath
my.chooseImage({
success: (res) => {
const apFilePath = res['apFilePaths'][0]
cloud.file.uploadFile({
filePath: apFilePath,
fileType: 'image',
fileName: 'test_file_name',
})
}
})
A:参考文档:
前置业务流程:
示例代码:
import { Cloud } from '@tbmp/mp-cloud-sdk';
const cloud = new Cloud();
cloud.init({
env: 'test' // 线上版本请使用 online
});
// 本例为了简单起见,先让用户选择一个文件,得到 apFilePath,实际业务中可使用其他 API 产生的 apFilePath
my.chooseImage({
success: (res) => {
const apFilePath = res['apFilePaths'][0]
cloud.file.uploadFile({
filePath: apFilePath,
fileType: 'image',
fileName: 'test_file_name',
seller: true
})
}
})
A:参考文档:
示例代码:
const btoa = require('btoa'); // 计算 base64 用
const hmacsha1 = require('hmacsha1') // 计算 hmac-sha1 用
// 本例为了简单起见,先让用户选择一个文件,得到 apFilePath,实际业务中可使用其他 API 产生的 apFilePath
my.chooseImage({
success: (res) => {
const apFilePath = res['apFilePaths'][0]
const accessKeyId = '<YOUR ACCESS KEY ID>'
const accessKeySecret = '<YOUR ACCESS KEY SECRET>'
const url = 'https://<BUCKET>.oss-cn-<REGION>.aliyuncs.com/'
const policy = {
expiration: new Date(new Date().getTime() + 60 * 1000).toISOString(),
conditions: [
["eq", "$key", "oss_file_name"] // 至少需要有一个条件,此处仅示意,请根据实际情况修改
]
}
const encodedPolicy = btoa(JSON.stringify(policy))
// 计算签名,文档中描述的算法为:Signature = base64(hmac-sha1(base64(policy), AccessKeySecret))
// 但因为我们这里用的 hmacsha1 库里面会做 base64,所以要省去最外层的 base64
const signature = hmacsha1(accessKeySecret, encodedPolicy)
my.uploadFile({
url: url,
filePath: apFilePath,
fileName: 'source_file_name',
formData: {
key: 'oss_file_name',
OSSAccessKeyId: accessKeyId,
policy: encodedPolicy,
Signature: signature,
},
complete: (res) => {
console.log(res)
}
})
}
})
A:使用 my.uploadFile,服务端接收 multipart/form-data 表单并处理文件即可。
服务端示例代码:
(简单起见,下列代码都是将被上传的文件保存到 /tmp,请根据情况自行调整)
Java Spring Boot:
@RequestMapping("/upload")
public ResponseEntity upload(MultipartFile[] files) throws IOException {
for (MultipartFile f : files) {
f.transferTo(new File("/tmp/" + f.getOriginalFilename()));
}
return new ResponseEntity(HttpStatus.OK);
}
Java Servlet:
@WebServlet(name = "Upload")
@MultipartConfig
public class Upload extends HttpServlet {
// For Servlet 3.0
private static String getSubmittedFileName(Part part) {
for (String cd : part.getHeader("content-disposition").split("; ")) {
if (cd.startsWith("filename=")) {
// filename="SUBMITTED_FILE_NAME"
return cd.substring(10, cd.length() - 1);
}
}
return null;
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Collection<Part> parts = request.getParts();
for (Part p : parts) {
String filename = getSubmittedFileName(p);
// Using p.getSubmittedFileName() for Servlet 3.1 or above
p.write("/tmp/" + filename); p.delete();
}
response.setStatus(HttpServletResponse.SC_OK);
}
}
Go:
func UploadHandler(w http.ResponseWriter, req *http.Request) {
req.ParseMultipartForm(1 * 1024 * 1024) // 简单起见,不处理错误,下同
for _, files := range req.MultipartForm.File {
for _, f := range files {
src, _ := f.Open()
dst, _ := os.Create("/tmp/" + f.Filename)
io.Copy(dst, src) dst.Close()
}
}
w.WriteHeader(http.StatusOK)
}
PHP:
<?php
foreach ($_FILES as $f) {
move_uploaded_file($f["tmp_name"], '/tmp/' . basename($f["name"]));
}
NodeJS:
const formidable = require('formidable');
const http = require('http');
const mv = require('mv');
http.createServer(function (request, response) {
if (request.url == '/upload' && request.method.toLowerCase() == 'post') {
const form = new formidable.IncomingForm()
form.parse(request, function (err, fields, files) {
for (let i in files) {
mv(files[i].path, '/tmp/' + files[i].name, {}, (err) => { })
}
response.writeHead(200)
response.end()
});
return;
}
}).listen(80);
Python:
class UploadServer(BaseHTTPRequestHandler):
def do_POST(self):
form = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ={
"REQUEST_METHOD": "POST",
})
for i in form:
with open('/tmp/' + form[i].filename, "wb") as f:
f.write(form[i].file.read())
self.send_response(200)
self.end_headers()
小程序端示例代码:
// 本例为了简单起见,先让用户选择一个文件,得到 apFilePath,实际业务中可使用其他 API 产生的 apFilePath
my.chooseImage({
success: (res) => {
const apFilePath = res['apFilePaths'][0]
my.uploadFile({
url: '<SERVER_URL>',
fileType: 'image',
fileName: 'client_file_name',
filePath: apFilePath,
complete: (res) => {
console.log(res);
},
});
},
})
A:由于小程序这边的 API 中,支持使用 multipart/form-data 协议直接上传的只有 my.uploadFile 这一个 API。而云应用、云函数通过 MTOP 网关中转,网关并不支持 multipart/form-data 协议,因此需要读取文件内容,然后当作一个普通字段传输。此外,MTOP 有 1MB 的报文大小限制,因此这种方式传输文件,文件内容也不能超过 1MB。
参考文档:
示例代码:
// 本例为了简单起见,先让用户选择一个文件,得到 apFilePath,实际业务中可使用其他 API 产生的 apFilePath
my.qn.chooseFileAndGetContent({
success: (res) => {
const fileContentMap = res['fileContentMap']
for (let name in fileContentMap) {
// 将 fileContentMap[name] 传给 cloud.function.invoke 或 cloud.application.httpRequest 等
}
},
})