目录
是否已上传
不重复上传文件前提是知道文件是否已上传,判断方法可以使用接口:资源元信息查询。
该接口的参数为EncodedEntryURI,其格式为:
entry = '<Bucket>:<Key>'
encodedEntryURI = urlsafe_base64_encode(entry)
所以问题就变成了 Key 的问题。而关于Key,则可以分为 不自定义key 和 自定义Key 的情况。
不自定义key
成功上传的结果
此时hash自动作为为key
{"hash":"lg-Mj5RIysIeiN7FoccwjIh4-7D1","key":"lg-Mj5RIysIeiN7FoccwjIh4-7D1"}
外链格式
https://example.com/lg-Mj5RIysIeiN7FoccwjIh4-7D1
七牛 hash 计算方法
核心代码
需配合sha1.min.js
function getEtag(buffer) {
var shA1 = sha1.digest;
var blockSize = 4 * 1024 * 1024;
var sha1String = [];
var prefix = 0x16;
var blockCount = 0;
var bufferSize = buffer.size || buffer.length || buffer.byteLength;
blockCount = Math.ceil(bufferSize / blockSize);
for (var i = 0; i < blockCount; i++) {
sha1String.push(shA1(buffer.slice(i * blockSize, (i + 1) * blockSize)));
}
function concatArr2Uint8(s) {
var tmp = [];
for (var i of s) tmp = tmp.concat(i);
return new Uint8Array(tmp);
}
function Uint8ToBase64(u8Arr, urisafe) {
var CHUNK_SIZE = 0x8000;
var index = 0;
var length = u8Arr.length;
var result = '';
var slice;
while (index < length) {
slice = u8Arr.subarray(index, Math.min(index + CHUNK_SIZE, length));
result += String.fromCharCode.apply(null, slice);
index += CHUNK_SIZE;
}
return urisafe ? btoa(result).replace(/\//g, '_').replace(/\+/g, '-') : btoa(result);
}
function calcEtag() {
if (!sha1String.length) return 'Fto5o-5ea0sNMlW_75VgGJCv2AcJ';
var sha1Buffer = concatArr2Uint8(sha1String);
if (blockCount > 1) {
prefix = 0x96;
sha1Buffer = shA1(sha1Buffer.buffer);
} else {
sha1Buffer = Array.apply([], sha1Buffer);
}
sha1Buffer = concatArr2Uint8([[prefix], sha1Buffer]);
return Uint8ToBase64(sha1Buffer, true);
}
return (calcEtag());
}
后端部分php版本
需引入 PHP SDK
$accessKey = Qiniu_config::ACCESSKEY;
$secretKey = Qiniu_config::SECRETKEY;
$auth = new Auth($accessKey, $secretKey);
$bucket = Qiniu_config::BUCKET;
$key = $_GET['key'];
$config = new \Qiniu\Config();
$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
list($fileInfo, $err) = $bucketManager->stat($bucket, $key);
if ($err) {
$upToken = $auth->uploadToken($bucket);
echo json_encode([
'token' => $upToken
]);
} else {
echo json_encode([
'url' => 'https://example.com/'.$key,
]);
}
html
input
<form method="post" action="http://up-z2.qiniup.com" enctype="multipart/form-data">
<input name="token" type="hidden" value="" id="token">
<input name="file" type="file" onchange="fload(this)"/>
<input type="submit" value="上传"/>
</form>
<script src="./sha1.min.js"></script>
<script src="./qetag.js"></script>
<script>
function fload(e) {
let files = e.files
let file = files[0]
var fileReader = new FileReader();
fileReader.onload = function (e) {
let key =getEtag(e.target.result)
fetch('/get_token.php?key='+key)
.then(response => response.json())
.then(data => {
if(data.url){
// 获取到文件链接
}else{
document.getElementById('token').value = data.token
// 下一步进行上传
}
});
};
fileReader.readAsArrayBuffer(file)
}
</script>
plupload
官方demo,配合plupload见uploadWithOthers.js,这里不传递key,需要把对应代码注释掉
FilesAdded:function(up, files){
let file = files[0]
var fileReader = new FileReader();
fileReader.onload = function (e) {
let key =getEtag(e.target.result)
fetch('/get_token.php?key='+key)
.then(response => response.json())
.then(data => {
if(data.url){
up.removeFile(files[0])
}else{
files[0].token = data.token
}
});
};
fileReader.readAsArrayBuffer(file)
}
uploader.bind("BeforeUpload", function(uploader, file) {
var id = file.id;
chunk_size = uploader.getOption("chunk_size");
var directUpload = function() {
var multipart_params_obj = {};
multipart_params_obj.token = file.token;
var customVarList = qiniu.filterParams(putExtra.params);
for (var i = 0; i < customVarList.length; i++) {
var k = customVarList[i];
multipart_params_obj[k[0]] = k[1];
}
uploader.setOption({
url: uploadUrl,
multipart: true,
multipart_params: multipart_params_obj
});
};
自定义key
不自定义key可以方便的检查文件是否上传,但缺点是,如果要进行文件管理,比如之查看图片文件。则需自己准备个数据库表保存文件信息。而自定义key,则可以利用资源列举前缀匹配来简单的文件管理。
hash 方法
要实现不重复上传,取文件的指纹还是不可少的,比如md5,sha1等。这里就继续沿用qetag作为文件指纹作为示例。
下面以 图片 为例,key 前缀设置为 image/
成功上传的结果
{"hash":"lg-Mj5RIysIeiN7FoccwjIh4-7D1","key":"image/lg-Mj5RIysIeiN7FoccwjIh4-7D1"}
外链格式
https://example.com/image/lg-Mj5RIysIeiN7FoccwjIh4-7D1
后端
后端部分php版本
需引入 PHP SDK
注意:需要在方法 uploadToken 中填入key
$accessKey = Qiniu_config::ACCESSKEY;
$secretKey = Qiniu_config::SECRETKEY;
$auth = new Auth($accessKey, $secretKey);
$bucket = Qiniu_config::BUCKET;
$key = $_GET['key'];
$config = new \Qiniu\Config();
$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
list($fileInfo, $err) = $bucketManager->stat($bucket, $key);
if ($err) {
$upToken = $auth->uploadToken($bucket,$key);
echo json_encode([
'token' => $upToken
]);
} else {
echo json_encode([
'url' => 'https://example.com/'.$key,
]);
}
html
input
注意:上传时需要将key一起上传
<form method="post" action="http://up-z2.qiniup.com" enctype="multipart/form-data">
<input name="token" type="hidden" value="" id="token">
<input name="key" type="hidden" value="" id="key">
<input name="file" type="file" onchange="fload(this)" accept="image/*"/>
<input type="submit" value="上传"/>
</form>
<script src="./sha1.min.js"></script>
<script src="./qetag.js"></script>
<script>
function fload(e) {
let files = e.files
let file = files[0]
var fileReader = new FileReader();
fileReader.onload = function (e) {
// 自定义key
let key = 'image/' + getEtag(e.target.result)
fetch('index.php?key='+key)
.then(response => response.json())
.then(data => {
if(data.url){
// 获取到文件链接
}else{
document.getElementById('token').value = data.token
document.getElementById('key').value = key
// 下一步进行上传
}
});
};
fileReader.readAsArrayBuffer(file)
}
</script>
plupload
官方demo,配合plupload见uploadWithOthers.js,这里的key需要修改为自定义的key
FilesAdded:function(up, files){
let file = files[0]
var fileReader = new FileReader();
fileReader.onload = function (e) {
let key = 'image/' + getEtag(e.target.result)
fetch('/get_token.php?key='+key)
.then(response => response.json())
.then(data => {
if(data.url){
up.removeFile(files[0])
}else{
files[0].token = data.token
files[0].key= key
}
});
};
fileReader.readAsArrayBuffer(file)
}
uploader.bind("BeforeUpload", function(uploader, file) {
var id = file.id;
chunk_size = uploader.getOption("chunk_size");
var directUpload = function() {
var multipart_params_obj = {};
multipart_params_obj.token = file.token;
var customVarList = qiniu.filterParams(putExtra.params);
for (var i = 0; i < customVarList.length; i++) {
var k = customVarList[i];
multipart_params_obj[k[0]] = k[1];
}
uploader.setOption({
url: uploadUrl,
multipart: true,
multipart_params: multipart_params_obj
});
};
小结
这里简单的记录了七牛文件避免重复上传的浏览器思路。个人使用我会推荐自定义key,至少在登陆后台时可以呈现文件夹形式。使用资源列举等API,进行文件管理也是挺方便的。
参考
官方问答
多版本 qiniu/qetag
纯前端文件名生成算法(七牛ETag算法)示例