1.先说说需求
由于本人的服务器较为辣鸡,前端上传到服务器后再上传到Oss比较难受,因此采用前端直传,但是呢,为了安全我采取了阿里云的STS策略,详情请移步到:https://help.aliyun.com/document_detail/100624.html?spm=5176.12818093.0.dexternal.312f16d0q3Ox9r
一切想得很美好,好了,开始踩坑
2.踩坑开始
本人后端采用的是JAVA。总体来说后端的坑比较少
第一次踩坑(我觉得不太行):
刚开始满怀欣喜看得阿里云的的js的SDK,很美好,便想着得到STS临时凭证到让前端自己上传。
package com.aliyun.sts.sample;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.aliyuncs.sts.model.v20150401.AssumeRoleRequest;
import com.aliyuncs.sts.model.v20150401.AssumeRoleResponse;
public class StsServiceSample {
public static void main(String[] args) {
String endpoint = "<sts-endpoint>";
String AccessKeyId = "<access-key-id>";
String accessKeySecret = "<access-key-secret>";
String roleArn = "<role-arn>";
String roleSessionName = "<session-name>";
String policy = "{\n" +
" \"Version\": \"1\", \n" +
" \"Statement\": [\n" +
" {\n" +
" \"Action\": [\n" +
" \"oss:*\"\n" +
" ], \n" +
" \"Resource\": [\n" +
" \"acs:oss:*:*:*\" \n" +
" ], \n" +
" \"Effect\": \"Allow\"\n" +
" }\n" +
" ]\n" +
"}";
try {
// 添加endpoint(直接使用STS endpoint,前两个参数留空,无需添加region ID)
DefaultProfile.addEndpoint("", "", "Sts", endpoint);
// 构造default profile(参数留空,无需添加region ID)
IClientProfile profile = DefaultProfile.getProfile("", AccessKeyId, accessKeySecret);
// 用profile构造client
DefaultAcsClient client = new DefaultAcsClient(profile);
final AssumeRoleRequest request = new AssumeRoleRequest();
request.setMethod(MethodType.POST);
request.setRoleArn(roleArn);
request.setRoleSessionName(roleSessionName);
request.setPolicy(policy); // 若policy为空,则用户将获得该角色下所有权限
request.setDurationSeconds(1000L); // 设置凭证有效时间
final AssumeRoleResponse response = client.getAcsResponse(request);
System.out.println("Expiration: " + response.getCredentials().getExpiration());
System.out.println("Access Key Id: " + response.getCredentials().getAccessKeyId());
System.out.println("Access Key Secret: " + response.getCredentials().getAccessKeySecret());
System.out.println("Security Token: " + response.getCredentials().getSecurityToken());
System.out.println("RequestId: " + response.getRequestId());
} catch (ClientException e) {
System.out.println("Failed:");
System.out.println("Error code: " + e.getErrCode());
System.out.println("Error message: " + e.getErrMsg());
System.out.println("RequestId: " + e.getRequestId());
}
}
}
上述是阿里云提供的官网代码,没有任何问题
主要坑在前端(或者自己不小心)
需求是选择图片上传,想到了uniapp的chooseImage
好像能返回File对象喔,看看阿里云的SDK
// 支持File对象、Blob数据以及OSS Buffer。
const data = '<File Object>';
// or const data = new Blob('content');
// or const data = new OSS.Buffer('content'));
async function putObject () {
try {
// object-key可以自定义为文件名(例如file.txt)或目录(例如abc/test/file.txt)的形式,实现将文件上传至当前Bucket或Bucket下的指定目录。
let result = await client.put('object-key', data);
console.log(result);
} catch (e) {
console.log(e);
}
}
实际是不行的,这个chooseImage返回的File对象跟File没有一点关系。
第二次踩坑
既然他没有File对象,那我有路径,我打开这个文件不就行了吗
翻了翻Html5的手册,发现了plus.io.resolveLocalFileSystemURL( url, succesCB, errorCB );好像可以实现,并且能通过FileReader.readAsDataURL获得DataUrl,然后转成Blob对象不就行了,
后来发现行不通,因为APP端并没有该对象,没法new,也支持H5,或许可以自己封装一个?没有尝试
第三次踩坑(解决)
通过表单上传呗,写了个Oss类
后端需要多做一件事,就是要返回一个签名以及Policy
public Result getOssSignatureAndPolicy(String stsAccessKeyId,String stsAccessKeySecret){
OSS ossClient = new OSSClientBuilder().build(endpoint, stsAccessKeyId, stsAccessKeySecret);
try {
String bucket = "cim-server";
String host = "https://" + bucket + "." + endpoint;
String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
long expireTime = 60;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE,0,100*1024*1024);
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes("utf-8");
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
String postSignature = ossClient.calculatePostSignature(postPolicy);
Result result = new Result();
result.addData("polocyBase64",encodedPolicy);
result.addData("signature",postSignature);
result.addData("expireEndTime",expireEndTime);
return result;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}finally {
ossClient.shutdown();
}
看看阿里云官方的表单https://help.aliyun.com/document_detail/31988.html?spm=a2c4g.11186623.6.1662.3ff76711WvdBEU
主要是必须得那几个必须得参数,看着应该能对的上
前端写了个类
class oss_util {
constructor(accessid, accesskey, host, stsToken, dir, signature, policyBase64) {
this.accessid = accessid;
this.accesskey = accesskey;
this.host = host;
this.stsToken = stsToken;
this.dir = dir;
this.signature = signature;
this.policyBase64 = policyBase64;
return this;
}
// function get_signature() {
// policyBase64 = Base64.encode(JSON.stringify(policyText))
// message = policyBase64
// var bytes = Crypto.HMAC(Crypto.SHA1, message, accesskey, { asBytes: true }) ;
// this.signature = Crypto.util.bytesToBase64(bytes);
// }
random_string(len) {
len = len || 32;
var chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
var maxPos = chars.length;
var pwd = '';
for (let i = 0; i < len; i++) {
pwd += chars.charAt(Math.floor(Math.random() * maxPos));
}
return pwd;
}
get_path(filename) {
return this.dir + this.random_string(32) + this.get_suffix(filename);
}
get_suffix(filename) {
var pos = filename.lastIndexOf('.')
var suffix = ''
if (pos != -1) {
suffix = filename.substring(pos)
}
return suffix;
}
get_upload_param(filename) {
var path = this.get_path(filename);
var new_multipart_params = {
'key': path,
'policy': this.policyBase64,
'OSSAccessKeyId': this.accessid,
'success_action_status': '200', //让服务端返回200,不然,默认会返回204
'signature': this.signature,
'x-oss-security-token': this.stsToken,
};
return new_multipart_params;
}
}
export default function (accessid, accesskey, host, stsToken, dir, signature, policyBase64) {
return new oss_util(accessid, accesskey, host, stsToken, dir, signature, policyBase64);
}
主要是new_multipart_params 这个东西对应每个必须的请求Header,可以看那个网址,
采用的是uni.uploadFile
uni.uploadFile({
url: url,
filePath: filename,
fileType:'image',
formData: new_multipart_params,
success: (res) => {
console.info(res);
},
fail: (res) => {
console.info(res);
}
})
这里的new_multipart_params就是通过上面的工具类获得的
在chorme调试一切正常,到真机调试
{
"data": "",
"statusCode": 400,
"errMsg": "uploadFile:ok"
}
然后我看到阿里的文档中
于是抓了个包
这是chorme的
莫得问题有那个file的表单域
真机的
显然file表单域没有了返回400了
解决:
uni.unloadFile 中有一个属性
对上了,就是告诉二进制文件的name是啥
uni.uploadFile({
url: url,
filePath: filename,
fileType:'image',
name:'file',
formData: new_multipart_params,
success: (res) => {
console.info(res);
},
fail: (res) => {
console.info(res);
}
})
完美解决了,关键加上这一条name:‘file’, 感觉就是这个name是指定二进制数据的名字
再抓包
file的表单域有了
总结:
好像用表单上传就不能分片上传,限制在100M了,或许可以自己封装个File对象来支持阿里云的SDK(等大佬实现),最好uniapp官方能提供新的Api啦