uniapp通过Sts上传文件到阿里云OSS兼踩坑记录(APP端)

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啦

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值