前端页面展示
1.背景
大文件如果不采用分片上传会导致卡死、内存占用过高导致程序奔溃等一些列问题。 通常在文件大于100 MB的情况下,建议采用分片上传的方法,通过断点续传和重试,提高上传成功率。如果在文件小于100 MB的情况下使用分片上传,且partSize设置不合理的情况下,可能会出现无法完整显示上传进度的情况。对于小于100 MB的文件,建议使用简单上传的方式。
2.使用STS临时访问凭证访问OSS
因为采用前端js上传,为避免暴露阿里云账号访问密钥(AccessKey ID和AccessKey Secret),强烈建议您使用临时访问凭证的方式执行OSS相关操作
请访问阿里云官网 : 阿里云官网STS操作
临时访问凭证包括临时访问密钥(AccessKey ID和AccessKey Secret)和安全令牌(SecurityToken)。
3.从后台获取临时token获取操作:
public class OssGetStsToken {
private static final Logger log = LoggerFactory.getLogger(OssGetStsToken.class);
private static String accessKeyId = "您的accessKeyId";
private static String accessKeySecret = "您的accessKeySecret";
private static String roleArn = "您的roleArn";
private static String roleSessionName = "您的roleSessionName ";
/**
* token失效时间,单位秒(不设置默认1小时,这里设置20分钟)
*/
private static final Long durationSeconds = 1200L;
private static final String ENDPOINT = "sts.aliyuncs.com";
/**
* 获取STStoken接口
*
* @param:
* @return: StsTokenVO
*/
public static StsTokenVO getStsToken(String bucketName) {
StsTokenVO tokenVO = new StsTokenVO();
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.setDurationSeconds(durationSeconds);
// 针对该临时权限可以根据该属性赋予规则,格式为json,没有特殊要求,默认为空
// request.setPolicy(policy); // Optional
final AssumeRoleResponse response = client.getAcsResponse(request);
AssumeRoleResponse.Credentials credentials = response.getCredentials();
tokenVO.setKeyId(credentials.getAccessKeyId());
tokenVO.setKeySecret(credentials.getAccessKeySecret());
tokenVO.setSecurityToken(credentials.getSecurityToken());
tokenVO.setBucketName(bucketName);
return tokenVO;
} catch (ClientException e) {
log.error("获取阿里云STS临时授权权限失败,错误信息:" + e);
throw new BaseException("获取阿里云STS临时授权权限失败,错误信息:" + e);
}
}
}
StsTokenVO
@Data
public class StsTokenVO implements Serializable {
/**
* 访问密钥标识
*/
private String keyId;
/**
* 访问密钥
*/
private String keySecret;
/**
* 安全令牌
*/
private String securityToken;
/**
* oss-bucket
*/
private String bucketName;
}
4.js直传代码
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OSS上传大文件</title>
</head>
<head>
<th:block th:include="include :: header('新增ota文件')"/>
<th:block th:include="include :: jasny-bootstrap-css"/>
</head>
<form>
<div class="form-group">
<label class="col-sm-3 control-label">选择ota包:</label>
<div class="fileinput fileinput-new col-sm-6" data-provides="fileinput">
<span id="changeFile" class="btn btn-white btn-file">
<span class="fileinput-new">选择ota包</span>
<span class="fileinput-exists">更改</span>
<input type="file" id="firmware" required multiple="multiple"/>
</span>
<span class="fileinput-filename"></span>
<a href="#" id="labels" class="close fileinput-exists" onclick="empty()" data-dismiss="fileinput" style="float: none">×</a>
</div>
<!-- 进度条展示 -->
<div id="up_wrap" style="margin-left: 420px;width: 800px;"></div>
</div>
</form>
<div class="row" style="line-height:95px">
<div class="col-sm-offset-5 col-sm-10">
<button type="button" class="btn btn-sm btn-warning" id="pause" onclick="pause()"><i class="fa fa-close"> </i> 暂停</button>
<button type="button" class="btn btn-sm btn-success" id="resume" onclick="resume()"><i class="fa fa-cloud-upload"> </i> 恢复上传</button>
<button type="button" class="btn btn-sm btn-primary" id="btnsubmit" onclick="submitHandler()"><i class="fa fa-check"> </i> 保 存</button>
<button type="button" class="btn btn-sm btn-danger" id="closesubmit" onclick="closeItem()"><i class="fa fa-reply-all"> </i> 关 闭 </button>
</div>
</div>
<body>
<th:block th:include="include :: jasny-bootstrap-js"/>
<script th:src="@{/js/plugins/spark-md5.js}"></script>
<script th:src="@{/js/aliyun-oss-sdk-6.16.0.min.js}"></script>
<script th:inline="javascript">
// 表单提交的路径
const prefix = ctx + "web/ota"
// 获取STSToken路径
const stsRequest = ctx + "web/stsToken";
// 表单校验
$("#form-ota-add").validate({
focusCleanup: true
});
//初始化文件上传队列
var upfiles = [];
$(function () {
$("#firmware").change(function (e) {
var ufiles = $(this).prop('files');
for (var i = 0; i < ufiles.length; i++) {
upfiles.push({
num: i,
name: ufiles[i].name,
file: ufiles[i]
})
}
console.log('upfiles:', upfiles);
})
})
// 初始化加载client对象
var bucket = ''; // bucket名称
var region = 'oss-cn-shenzhen'; // 地区
var Buffer = OSS.Buffer;
var OSS = OSS;
var md5 = '';
// 定义中断点数据保存。
let client;
let abortCheckpoint;
var suffix ='';
var applyTokenDo = function (func) {
$.ajax({
url: stsRequest + "/credential",
type: "get",
data: '',
contentType: false,
processData: false,
success: function (result) {
console.log(result);
client = new OSS({
region: region,
accessKeyId: result.keyId, //访问密钥标识
accessKeySecret: result.keySecret, // 访问密钥
stsToken: result.securityToken, // 安全令牌
bucket: result.bucketName,
});
bucket=result.bucketName;
return func(client);
}
});
};
function empty() {
upfiles = [];
$("#firmware").val("");
let a=document.getElementById("firmware");
a.length=0
}
var path ='ota/beta/';
// 上传文件 并且计算进度,以进度条方式展示
var uploadFile = function (client) {
console.log(client);
if (upfiles.length < 1) {
return;
}
var timestamp = (new Date()).valueOf();
var file = upfiles[0].file;
var key = upfiles[0].name;
$('#btnsubmit').attr('disabled', true);
$('#closesubmit').attr('disabled', true);
$("#pause").show();
$("#resume").show();
suffix = path + timestamp + key.substring(key.length - 4);
// 分片上传
return client.multipartUpload(suffix, file, {
progress: function (p, cpt, res) {
abortCheckpoint = cpt;
let ps = parseInt((p.toFixed(2)) * 100);
console.log("上传进度:", ps + '%');
console.log("cpt:", cpt);
let html = '';
html = '<dl><dt></dt><dd><div class="progress"><div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width:' + ps + '%"><span>' + ps + '%</span></div></div></dd></dl>';
$("#up_wrap").html(html);
if (p == 1) {
const url = `https://${bucket}.${region}.aliyuncs.com/${suffix}`; // 最终上传完成后的下载url
console.log("url:", url);
// oss key(删除文件需要用到)
$("#ossKey").val(suffix);
// 完整的下载地址
$("#downloadUrl").val(url);
// 文件大小
$("#fileSize").val(file.size);
// 表单提交
if ($.validate.form()) {
$.operate.save(prefix + "/add", $('#form-ota-add').serialize());
}
}
if (cpt != undefined) {
var content = JSON.stringify(cpt);
client.put(suffix, new Buffer(content));
}
return function (done) {
var bar = document.getElementById('progress-bar_' + upfile.num);
bar.style.width = Math.floor(p * 100) + '%';
bar.innerHTML = Math.floor(p * 100) + '%';
done();
}
}
}).then(function (res) {
console.log('上传成功: ', res);
upfiles.shift();
client.delete(key);
applyTokenDo(uploadFile);
});
};
// 表单提交
function submitHandler() {
var form = $("#form-ota-add").get(0);
var formdata = new FormData(form);
console.log(formdata);
if ($.validate.form()) {
$("#closesubmit").attr("disabled",true);
$("#btnsubmit").attr("disabled",true);
$("#changeFile").css("pointer-events", "none");
$("#labels").css("visibility","hidden");
$.ajax({
url: prefix + "/addCheck",
type: "post",
data: formdata,
contentType: false,
processData: false,
success: function (result) {
if (result){
applyTokenDo(uploadFile);
}else {
$.modal.alertError("已有存在的版本号和型号文件!");
$("#closesubmit").attr("disabled",false);
$("#btnsubmit").attr("disabled",false);
empty();
}
}
});
}
}
// 监听续传按钮,单击”恢复上传“后继续上传。
// 暂停
function pause() {
console.log(client);
client.cancel();
}
// 恢复上传
function resume() {
resumeUpload();
}
// 隐藏按钮
$(function(){
$("#pause").hide();
$("#resume").hide();
});
// 异步断点续传方法 (恢复上传)
const resumeUpload = async () => {
console.log(client);
if (upfiles.length < 1) {
return;
}
var file = upfiles[0].file;
var key = upfiles[0].name;
return client.multipartUpload(suffix, file, {
checkpoint: abortCheckpoint,
progress: function (p, cpt, res) {
abortCheckpoint = cpt;
let ps = parseInt((p.toFixed(2)) * 100);
console.log("上传进度:", ps + '%');
console.log("cpt:", cpt);
let html = '';
html = '<dl><dt></dt><dd><div class="progress"><div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width:' + ps + '%"><span>' + ps + '%</span></div></div></dd></dl>';
$("#up_wrap").html(html);
if (p == 1) {
const url = `https://${bucket}.${region}.aliyuncs.com/${suffix}`; // 最终上传完成后的下载url
console.log("url:", url);
$("#ossKey").val(suffix);
$("#downloadUrl").val(url);
$("#fileSize").val(file.size);
// 表单提交
if ($.validate.form()) {
$.operate.save(prefix + "/add", $('#form-ota-add').serialize());
}
}
if (cpt != undefined) {
var content = JSON.stringify(cpt);
client.put(suffix, new Buffer(content));
}
return function (done) {
var bar = document.getElementById('progress-bar_' + upfile.num);
bar.style.width = Math.floor(p * 100) + '%';
bar.innerHTML = Math.floor(p * 100) + '%';
done();
}
}
}).then(function (res) {
console.log('上传成功: ', res);
upfiles.shift();
client.delete(key);
applyTokenDo(uploadFile);
});
};
/**
* 根据选择的文件生成 MD5值
*/
document.getElementById('firmware').addEventListener('change', function () {
var sta = new Date().getTime();
var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
file = this.files[0],
chunkSize = 52428800, // 分片大小 (50MB)
chunks = Math.ceil(file.size / chunkSize),
currentChunk = 0,
spark = new SparkMD5.ArrayBuffer(),
fileReader = new FileReader();
fileReader.onload = function (e) {
console.log('read chunk nr', currentChunk + 1, 'of', chunks);
spark.append(e.target.result); // Append array buffer
currentChunk++;
if (currentChunk < chunks) {
loadNext();
} else {
var end = new Date().getTime() - sta;
console.info('耗时:', end)
$("#otaMd5").val(spark.end());
console.info('MD5:', $("#otaMd5").val())
}
};
fileReader.onerror = function () {
console.warn('oops, something went wrong.');
};
function loadNext() {
var start = currentChunk * chunkSize,
end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
}
loadNext();
});
</script>
</body>
</html>