Initial commit
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* 文件哈希计算
|
||||
*/
|
||||
|
||||
namespace plugin\utility\file_hash;
|
||||
|
||||
use app\Plugin;
|
||||
|
||||
class App extends Plugin
|
||||
{
|
||||
|
||||
public function index()
|
||||
{
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
{extend name="common/plugin_layout" /}
|
||||
{block name="title"}{$plugin.title} - {:config_get('title')}{/block}
|
||||
{block name="main"}
|
||||
<style>
|
||||
.btn-block {
|
||||
white-space:normal;
|
||||
}
|
||||
</style>
|
||||
<div class="container-xl" id="app">
|
||||
<div class="col-sm-12 col-md-10 col-xl-8 center-block">
|
||||
<div class="card card-preview">
|
||||
<div class="card-inner mt-3">
|
||||
<div class="nya-title nk-ibx-action-item progress-rating">
|
||||
<span class="nk-menu-text font-weight-bold">文件哈希计算</span>
|
||||
</div>
|
||||
<div class="progress progress-lg mt-1 mb-1">
|
||||
<div class="progress-bar progress-bar-striped progress-bar-animated" v-bind:style="{ width: progress + '%' }">{{progress}}%</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">文件选择</label>
|
||||
<div class="text-center pt-5 pb-5 btn btn-lg btn-block btn-outline-light mb-4 d-block">
|
||||
<div class="preview-icon-wrap"><em class="ni ni-upload"></em></div><span>拖拽文件到这里或者点击选择文件</span>
|
||||
<input type="file" id="file" multiple="multiple" style="opacity: 0;position: absolute;cursor: pointer;width: 100%;height: 100%;left: 0;top: 0;" @change="selectFile">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">生成选项:</label>
|
||||
<div class="form-control-wrap">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="customCheck1" v-model="set.md5"><label class="custom-control-label" for="customCheck1">MD5</label>
|
||||
</div>
|
||||
<div class="custom-control custom-checkbox ml-1">
|
||||
<input type="checkbox" class="custom-control-input" id="customCheck2" v-model="set.sha1"><label class="custom-control-label" for="customCheck2">SHA1</label>
|
||||
</div>
|
||||
<div class="custom-control custom-checkbox ml-1">
|
||||
<input type="checkbox" class="custom-control-input" id="customCheck5" v-model="set.sha256"><label class="custom-control-label" for="customCheck5">SHA256</label>
|
||||
</div>
|
||||
<div class="custom-control custom-checkbox ml-1">
|
||||
<input type="checkbox" class="custom-control-input" id="customCheck7" v-model="set.sha512"><label class="custom-control-label" for="customCheck7">SHA512</label>
|
||||
</div>
|
||||
<div class="custom-control custom-checkbox ml-1">
|
||||
<input type="checkbox" class="custom-control-input" id="customCheck8" v-model="set.ripemd160"><label class="custom-control-label" for="customCheck8">RIPEMD160</label>
|
||||
</div>
|
||||
<div class="custom-control custom-checkbox ml-1">
|
||||
<input type="checkbox" class="custom-control-input" id="customCheck9" v-model="set.crc32"><label class="custom-control-label" for="customCheck9">CRC32</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mt-3" id="resultdiv" v-show="result">
|
||||
<label class="form-label">结果:</label>
|
||||
<div class="border p-2" v-html="result">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card-preview">
|
||||
<div class="card-inner">
|
||||
<h6><em class="icon ni ni-info"></em> 工具说明</h6>
|
||||
<div class="accordion-inner">
|
||||
<p>可同时选择多个文件计算</p>
|
||||
<p>使用crypto-js 本地计算,文件不会被上传到服务器</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
||||
{block name="script"}
|
||||
<script src="{$cdn_cdnjs}vue/2.6.14/vue.min.js"></script>
|
||||
<script src="{$cdn_cdnjs}crypto-js/4.1.1/crypto-js.min.js"></script>
|
||||
<script src="/static/js/crc32.js"></script>
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
set: {
|
||||
md5: true,
|
||||
sha1: true,
|
||||
sha256: false,
|
||||
sha512: false,
|
||||
ripemd160: false,
|
||||
crc32: false,
|
||||
},
|
||||
algorithms: [
|
||||
'md5',
|
||||
'sha1',
|
||||
'sha256',
|
||||
'sha512',
|
||||
'ripemd160',
|
||||
'crc32',
|
||||
],
|
||||
result: '',
|
||||
progress: 0,
|
||||
},
|
||||
created() {
|
||||
},
|
||||
methods: {
|
||||
swapendian32(val) {
|
||||
return (((val & 0xFF) << 24)
|
||||
| ((val & 0xFF00) << 8)
|
||||
| ((val >> 8) & 0xFF00)
|
||||
| ((val >> 24) & 0xFF)) >>> 0;
|
||||
},
|
||||
arrayBufferToWordArray(arrayBuffer) {
|
||||
var fullWords = Math.floor(arrayBuffer.byteLength / 4);
|
||||
var bytesLeft = arrayBuffer.byteLength % 4;
|
||||
|
||||
var u32 = new Uint32Array(arrayBuffer, 0, fullWords);
|
||||
var u8 = new Uint8Array(arrayBuffer);
|
||||
|
||||
var cp = [];
|
||||
for (var i = 0; i < fullWords; ++i) {
|
||||
cp.push(this.swapendian32(u32[i]));
|
||||
}
|
||||
|
||||
if (bytesLeft) {
|
||||
var pad = 0;
|
||||
for (var i = bytesLeft; i > 0; --i) {
|
||||
pad = pad << 8;
|
||||
pad += u8[u8.byteLength - i];
|
||||
}
|
||||
|
||||
for (var i = 0; i < 4 - bytesLeft; ++i) {
|
||||
pad = pad << 8;
|
||||
}
|
||||
|
||||
cp.push(pad);
|
||||
}
|
||||
|
||||
return CryptoJS.lib.WordArray.create(cp, arrayBuffer.byteLength);
|
||||
},
|
||||
progressiveRead(file, work, done) {
|
||||
return new Promise((resolve) => {
|
||||
var chunkSize = 2097152; // 256KiB at a time
|
||||
var pos = 0;
|
||||
var reader = new FileReader();
|
||||
|
||||
function progressiveReadNext() {
|
||||
var end = Math.min(pos + chunkSize, file.size);
|
||||
|
||||
reader.onload = function (e) {
|
||||
pos = end;
|
||||
work(e.target.result, pos, file);
|
||||
if (pos < file.size) {
|
||||
progressiveReadNext();
|
||||
}
|
||||
else {
|
||||
// Done
|
||||
resolve(file);
|
||||
}
|
||||
}
|
||||
|
||||
if (file.slice) {
|
||||
var blob = file.slice(pos, end);
|
||||
}
|
||||
else if (file.webkitSlice) {
|
||||
var blob = file.webkitSlice(pos, end);
|
||||
}
|
||||
reader.readAsArrayBuffer(blob);
|
||||
}
|
||||
|
||||
progressiveReadNext();
|
||||
})
|
||||
},
|
||||
async hash(file,total,id) {
|
||||
var that = this;
|
||||
var contents = [];
|
||||
for (var v of Object.keys(this.set)) {
|
||||
if (this.set[v]){
|
||||
if(v == 'crc32'){
|
||||
var crc32intermediate = 0;
|
||||
}else{
|
||||
contents[v] = CryptoJS.algo[v.toUpperCase()].create();
|
||||
}
|
||||
}
|
||||
}
|
||||
await this.progressiveRead(file, function (data, pos, file) {
|
||||
var wordArray = that.arrayBufferToWordArray(data);
|
||||
for (var v of Object.keys(contents)) {
|
||||
contents[v].update(wordArray)
|
||||
}
|
||||
if(typeof crc32intermediate !== "undefined"){
|
||||
crc32intermediate = crc32(new Uint8Array(data), crc32intermediate);
|
||||
}
|
||||
var progressRate = Math.round(pos / file.size / total * 100 + (id-1) / total * 100);
|
||||
that.progress = progressRate;
|
||||
});
|
||||
var result = [];
|
||||
for (var v of Object.keys(contents)) {
|
||||
result[v] = contents[v].finalize().toString();
|
||||
}
|
||||
if(typeof crc32intermediate !== "undefined"){
|
||||
if(crc32intermediate < 0) crc32intermediate = 0xFFFFFFFF + crc32intermediate + 1;
|
||||
result['crc32'] = crc32intermediate.toString(16);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
async selectFile(e) {
|
||||
var total = e.target.files.length;
|
||||
let loading = layer.msg('正在计算中', {icon: 16,shade: 0.1,time: 0});
|
||||
let list = '';
|
||||
var i = 1;
|
||||
for (const file of e.target.files) {
|
||||
list += `<span class="font-weight-bold text-primary">${file.name}</span>(${file.size} bytes)<br/>`;
|
||||
await this.hash(file,total,i++).then(res => {
|
||||
for (var v of Object.keys(res)) {
|
||||
list += `<b>${v.toUpperCase()}:</b>${res[v]}<br/>`
|
||||
}
|
||||
})
|
||||
list += '<br/>';
|
||||
}
|
||||
this.result = list
|
||||
layer.close(loading);
|
||||
$("#file").val('');
|
||||
},
|
||||
reset(){
|
||||
this.result = '';
|
||||
this.progress = 0;
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
{/block}
|
||||
Reference in New Issue
Block a user