天天看點

阿裡雲OSS使用RAM生成STS分片上傳大檔案Demo

阿裡雲普通Form表單上傳視訊

這是最簡單的一種上傳方式,使用背景生成Token 前端通過Form表單直傳OSS伺服器

背景生成Token代碼

/**
     * 生成阿裡雲上傳Token
     * @return
     * @throws UnsupportedEncodingException
     */
    @GetMapping("/token")
    public ResponseEntity<ApiResult> token() throws UnsupportedEncodingException {
        //上傳位址
        String uploadUrl = "http://bucketName.oss-cn-hangzhou.aliyuncs.com/";
        String dir = "";
        OSSClient client = new OSSClient(STSUtil.END_POINT, STSUtil.accessKeyId, STSUtil.accessKeySecret);
        long expireEndTime = System.currentTimeMillis() + STSUtil.expirationTime * 1000;
        Date expiration = new Date(expireEndTime);
        PolicyConditions policyConds = new PolicyConditions();
        policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
        policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
        String postPolicy = client.generatePostPolicy(expiration, policyConds);
        byte[] binaryData = postPolicy.getBytes("utf-8");
        String encodedPolicy = BinaryUtil.toBase64String(binaryData);
        String postSignature = client.calculatePostSignature(postPolicy);
        //OSS檔案名
        String key = String.format("%s.mp4", UidUtils.generateObjectId());
        JSONObject jasonCallback = new JSONObject();
        //你的回調URL
        jasonCallback.put("callbackUrl", "http://a.com/endpoint/aliyun/uploadSuccess");
		//阿裡雲回調傳回參數
        jasonCallback.put("callbackBody", "id=" + key + "&filename=${object}&type=video&etag=${etag}&size=${size}&mimeType=${mimeType}");
        jasonCallback.put("callbackBodyType", "application/x-www-form-urlencoded");
        String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());
        Map<String, String> res = new HashMap<>();
        res.put("callback", base64CallbackBody);
        res.put("key", key);
        res.put("OSSAccessKeyId", STSUtil.accessKeyId);
        res.put("policy", encodedPolicy);
        res.put("success_action_status", "200");
        res.put("signature", postSignature);
        res.put("dir", dir);
        res.put("uploadUrl", uploadUrl);
        res.put("expire", String.valueOf(expireEndTime / 1000));
        return renderOk(res);
    }
//傳回資料
{
    "code": 0,
    "data": {
        "OSSAccessKeyId": "LTAI4GHdsbaaaaJhK",
        "uploadUrl": "http://bucketName.oss-cn-hangzhou.aliyuncs.com/",
        "signature": "XdVOT2/HiOals03pQjBaR16Cd9o=",
        "success_action_status": "200",
        "expire": "1616640488",
        "callback": "eyJjYWxsYmFja0JvZmaamdHlwsmV9Jm1pbWVUeXBlPSR7bWltZVR5cGV9In0=",
        "dir": "",
        "key": "6688924142276.mp4",
        "policy": "eyJleHBpcmF0aW9uIjOC42MzBaIiiXV19"
    }
}

           

前端通過表單上傳

<div class="main">
    <input onchange="getToken()"
           accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx" id="file" name="file"
           class="ipt" type="file"/>
</div>

<script>
    function getToken() {
        var xhr = new XMLHttpRequest();
        xhr.open('POST',  "/token", true);
        var formData = new FormData();
        xhr.onload = function (e) {
            var res = JSON.parse(this.responseText);
            if (res.code == 0) {
                uploadFile(res, file);
            }
        };
        xhr.send(formData);
    }
    function uploadFile(data, file) {
        var xhr = new XMLHttpRequest();
        xhr.open('POST', data.uploadUrl, true);
        var formData, startDate;
        formData = new FormData();
        imgInfoId = data.id;
        formData.append("key",data.key)
        formData.append('policy', data.policy);
        formData.append('OSSAccessKeyId', data.OSSAccessKeyId);
        formData.append('success_action_status',200);
        formData.append('callback',data.callback);
        formData.append('signature',data.signature);
        formData.append('file', file.files[0]);
        var taking;
        xhr.upload.addEventListener("progress", function (evt) {
            if (evt.lengthComputable) {
                var nowDate = new Date().getTime();
                taking = nowDate - startDate;
                var x = (evt.loaded) / 1024;
                var y = taking / 1000;
                var uploadSpeed = (x / y);
                var formatSpeed;
                if (uploadSpeed > 1024) {
                    formatSpeed = (uploadSpeed / 1024).toFixed(2) + "Mb\/s";
                } else {
                    formatSpeed = uploadSpeed.toFixed(2) + "Kb\/s";
                }
                var percentComplete = Math.round(evt.loaded * 100 / evt.total);
                //計算進度及速度
                console.log(percentComplete, ",", formatSpeed);
            }
        }, false);

        xhr.onreadystatechange = function (response) {
            if (xhr.readyState == 4 && xhr.status == 200 && xhr.responseText != "") {
                console.info("success")
            } else if (xhr.status != 200 && xhr.responseText) {
				console.info("error")
            }
        };
        startDate = new Date().getTime();
        xhr.send(formData);

    }

</script>



           
阿裡雲OSS使用RAM生成STS分片上傳大檔案Demo

方法特點

這種上傳方式容易簡單,缺點是上傳速度慢,不支援大檔案上傳,檔案大小數量在G級别的無法使用,而且容易造成浏覽器卡頓,是以大檔案需要使用分片上傳

STS分片上傳

配置RAM

1.RAM建立使用者,并生成AK SK,上傳時使用這組AK/SK

阿裡雲OSS使用RAM生成STS分片上傳大檔案Demo

同時給這個使用者配置設定 生成STS和控制OSS權限

AliyunSTSAssumeRoleAccess

AliyunOSSFullAccess

阿裡雲OSS使用RAM生成STS分片上傳大檔案Demo

2.建立RAM角色,給ARN配置設定

阿裡雲OSS使用RAM生成STS分片上傳大檔案Demo

3.Bucket配置 OSS跨域增加Etag及x-oss-request-id 暴露Headers 允許跨越上傳

阿裡雲OSS使用RAM生成STS分片上傳大檔案Demo

後端生成STS Token

/**
     * 生成STS token
     * @return
     * @throws ClientException
     */
    @GetMapping("/sts")
    public ResponseEntity<ApiResult> sts() throws ClientException {
        Map<String, Object> response = new HashMap<>();
        Map<String, Object> callback = new HashMap<>();
        callback.put("callbackBodyType", "application/x-www-form-urlencoded");
        callback.put("callbackUrl", "http://a.com/endpoint/aliyun/uploadSuccess");
        callback.put("callbackBody", "id=123123123&filename=${object}&type=video&etag=${etag}&size=${size}&mimeType=${mimeType}");
        AssumeRoleResponse.Credentials credentials = STSUtil.createSTSForPutObject(STSUtil.bucketName, STSUtil.ROLE_ARN, STSUtil.accessKeyId, STSUtil.accessKeySecret, STSUtil.expirationTime);
        Map<String, Object> res = mapOf("credentials", credentials, "fileKey", UidUtils.generateObjectId() + ".mp4", "bucket", STSUtil.bucketName, "region", STSUtil.REGION_CN_HANGZHOU, "endpoint", STSUtil.END_POINT);
        response.put("callback", callback);
        response.putAll(res);
        return renderOk(response);
    }

           

前端分片上傳

<html>
<head>
       
    <script src="https://meizi-tm-5108-pub.oss-cn-hangzhou.aliyuncs.com/lib/aliyun-upload-sdk/lib/aliyun-oss-sdk-6.13.0.min.js"></script>
    <script src="https://meizi-tm-5108-pub.oss-cn-hangzhou.aliyuncs.com/lib/aliyun-upload-sdk/lib/base64.js"></script>

</head>
<body>
  <input type="file" id="file"/>
<span id="process"></span>
<span id="res"></span> 

<script type="text/javascript">
    document.getElementById('file').addEventListener('change', function (e) {
        var file = e.target.files[0];
        //擷取STS
        OSS.urllib.request("/sts",
            {method: 'GET'},
            function (err, response) {
                if (err) {
                    return alert(err);
                }
                try {
                    result = JSON.parse(response);
                } catch (e) {
                    return alert('parse sts response info error: ' + e.message);
                }
                //64位編碼 
                var parses = function (data) {
                    var base = new Base64();
                    return base.encode(JSON.stringify(data));
                }
                var client = new OSS({
                    accessKeyId: result.data.credentials.accessKeyId,
                    accessKeySecret: result.data.credentials.accessKeySecret,
                    stsToken: result.data.credentials.securityToken,
                    endpoint: result.data.endpoint,
                    bucket: result.data.bucket,
                    region: result.data.region,
                });
                client.multipartUpload(result.data.fileKey, file, {
                    parallel: 4,
                    partSize: 1024 * 1024,
                    mime: 'video/mp4',
                    headers: {
                        'x-oss-callback': parses(result.data.callback)
                    },
                    progress: function (p, checkpoint) {
                        document.getElementById('process').innerHTML = p * 100 + "%";
                    }
                }).then(function (result) {
                    console.log(result);
                    document.getElementById('res').innerHTML = "上傳成功:" + JSON.parse(result).data.filename;
                }).catch(function (err) {
                    console.log(err);
                });
            });
    });

</script>
</body>
</html>
           

完成分片上傳

調通過程中遇到了很多和權限相關的問題,阿裡這個RAM确實有點複雜,第一次上手有點門檻,不過這套權限管控确實很好,可以精确控制生成的AK/SK通路雲上任意資源的 CRUD權限,安全性是得到保障的。

阿裡雲OSS使用RAM生成STS分片上傳大檔案Demo

STSUtil.java

package com.zjrb.media.base.util.aliyun;

import com.alibaba.fastjson.JSON;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.http.ProtocolType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.aliyuncs.sts.model.v20150401.AssumeRoleRequest;
import com.aliyuncs.sts.model.v20150401.AssumeRoleResponse;
import com.aliyuncs.sts.model.v20150401.AssumeRoleResponse.Credentials;

import java.io.File;

/**
 * @version 1.0
 * @describe: OSS臨時通路憑證授權
 * @author:houkai
 * @Date: 2018/3/7 14:54
 */
public class STSUtil {

    /**
     * 目前隻有"cn-hangzhou"這個region可用, 不要使用填寫其他region的值
     */
    public static final String REGION_CN_HANGZHOU = "cn-hangzhou";
    /**
     * 目前 STS API 版本
     */
    public static final String STS_API_VERSION = "2015-04-01";
    /**
     * 必須是https請求
     */
    public static final ProtocolType PROTOCOL_TYPE = ProtocolType.HTTPS;
    /**
     * 指定角色的全局資源描述符(Aliyun Resource Name,簡稱Arn)
     */
    public static final String ROLE_ARN = "acs:ram::1717083:role/media-upload";
    /**
     * 使用者自定義參數。此參數用來區分不同的Token,可用于使用者級别的通路審計
     */
    public static final String ROLE_SESSION_NAME = "test";
    /**
     * 阿裡雲EndPoint
     */
    public static final String END_POINT = "http://oss-cn-hangzhou.aliyuncs.com/";
    /**
     * 上傳的Bucket
     */
    public static final String bucketName = "bucketName";
    /**
     * RAM裡的使用者 AK,不可用全局AK
     */
    public static final String accessKeyId = "aa";
    /**
     * RAM裡的使用者 SK,不可用全局SK
     */
    public static final String accessKeySecret = "aabb";
    /**
     * 生成的ak sk 過期時間
     */
    public static final  Long expirationTime = 900L;

    public static void main(String[] args) throws Exception {

        Credentials credentials = createSTSForPutObject(bucketName, ROLE_ARN, accessKeyId, accessKeySecret, expirationTime);
        System.out.println(JSON.toJSONString(credentials));
        OSS ossClient = new OSSClientBuilder().build(END_POINT, credentials.getAccessKeyId(), credentials.getAccessKeySecret(), credentials.getSecurityToken());
        ossClient.putObject(bucketName, "1.mp4", new File("C:\\Users\\Administrator\\Videos\\朝天門.mp4"));
    }

    /**
     * 建立上傳臨時賬号
     *
     * @param bucketName
     * @param roleArn         需要授權的角色名稱
     * @param accessKeyId     賬号
     * @param accessKeySecret 密碼
     * @param expirationTime  過期時間,機關為秒
     * @return
     */
    public static Credentials createSTSForPutObject(String bucketName, String roleArn, String accessKeyId, String accessKeySecret, Long expirationTime) throws ClientException {
        String policy = STSUtil.getPutObjectPolicy(bucketName);
        return createSTS(policy, roleArn, accessKeyId, accessKeySecret, expirationTime);
    }

    /**
     * 建立隻讀臨時授權
     *
     * @param bucketName
     * @param roleArn         需要授權的角色名稱
     * @param accessKeyId     賬号
     * @param accessKeySecret 密碼
     * @param expirationTime  過期時間,機關為秒
     * @return
     */
    public static Credentials createSTSForReadOnly(String bucketName, String roleArn, String accessKeyId, String accessKeySecret, Long expirationTime) throws ClientException {
        String policy = STSUtil.getOSSReadOnlyAccessPolicy(bucketName);
        return createSTS(policy, roleArn, accessKeyId, accessKeySecret, expirationTime);
    }

    /**
     * 建立STS
     *
     * @param policy          授權政策
     * @param roleArn         需要授權的角色名稱
     * @param accessKeyId     賬号
     * @param accessKeySecret 密碼
     * @param expirationTime  過期時間,機關為秒
     * @return
     */
    private static Credentials createSTS(String policy, String roleArn, String accessKeyId, String accessKeySecret, Long expirationTime) throws ClientException {
        IClientProfile profile = DefaultProfile.getProfile(REGION_CN_HANGZHOU, accessKeyId, accessKeySecret);
        DefaultAcsClient client = new DefaultAcsClient(profile);
        final AssumeRoleRequest request = new AssumeRoleRequest();
        request.setDurationSeconds(expirationTime);
        request.setVersion(STS_API_VERSION);
        request.setMethod(MethodType.POST);
        request.setProtocol(PROTOCOL_TYPE);
        request.setRoleArn(roleArn);
        request.setRoleSessionName(ROLE_SESSION_NAME);
        request.setPolicy(policy);
        //實體使用者擷取角色身份的安全令牌的方法
        AssumeRoleResponse response = client.getAcsResponse(request);
        Credentials credentials = response.getCredentials();
        return credentials;
    }

    /**
     * 自定義授權政策,對目前bucket下的檔案夾讀寫
     *
     * @param bucketName
     * @return
     */
    private static String getPutObjectPolicy(String bucketName) {
        return String.format(
                "{\n" +
                        "    \"Version\": \"1\", \n" +
                        "    \"Statement\": [\n" +
                        "        {\n" +
                        "            \"Action\": [\n" +
                        "                \"oss:PutObject\" \n" +
                        "            ], \n" +
                        "            \"Resource\": [\n" +
                        "                \"acs:oss:*:*:%s/*\"\n" +
                        "            ], \n" +
                        "            \"Effect\": \"Allow\"\n" +
                        "        }\n" +
                        "    ]\n" +
                        "}", bucketName);
    }

    /**
     * 隻讀通路該bucket對象存儲服務(OSS)的權限,授權政策
     *
     * @param bucketName
     * @return
     */
    private static String getOSSReadOnlyAccessPolicy(String bucketName) {
        return String.format("{\n" +
                "  \"Statement\": [\n" +
                "    {\n" +
                "      \"Action\": [\n" +
                "        \"oss:Get*\",\n" +
                "        \"oss:List*\"\n" +
                "      ],\n" +
                "      \"Effect\": \"Allow\",\n" +
                "      \"Resource\": [\n" +
                "        \"acs:oss:*:*:%s/*\"\n" +
                "      ]\n" +
                "    }\n" +
                "  ],\n" +
                "  \"Version\": \"1\"\n" +
                "}", bucketName);
    }

}