阿里云普通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>
方法特点
这种上传方式容易简单,缺点是上传速度慢,不支持大文件上传,文件大小数量在G级别的无法使用,而且容易造成浏览器卡顿,所以大文件需要使用分片上传
STS分片上传
配置RAM
1.RAM创建用户,并生成AK SK,上传时使用这组AK/SK
同时给这个用户分配 生成STS和控制OSS权限
AliyunSTSAssumeRoleAccess
AliyunOSSFullAccess
2.创建RAM角色,给ARN分配
3.Bucket配置 OSS跨域增加Etag及x-oss-request-id 暴露Headers 允许跨越上传
后端生成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权限,安全性是得到保障的。
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);
}
}