perf: 优化实现文件夹下载以及多文件压缩下载功能

This commit is contained in:
ganzizi 2023-11-16 17:01:49 +08:00
parent 08fccf4adc
commit ed4afa63f0
5 changed files with 210 additions and 30 deletions

View File

@ -11,6 +11,7 @@ use App\Models\FileUser;
use App\Models\User;
use App\Module\Base;
use App\Module\Ihttp;
use Swoole\Coroutine;
use Carbon\Carbon;
use Redirect;
use Request;
@ -1014,23 +1015,25 @@ class FileController extends AbstractController
}
/**
* @api {get} api/file/download/zip 20. 打包下载
* @api {get} api/file/download/pack 20. 打包文件
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName download__zip
* @apiName download__pack
*
* @apiParam {Array} [ids] 文件ID格式: [id, id2, id3]
* @apiParam {Array} [ids] 文件ID格式: [id, id2, id3]
* @apiParam {String} [name] 下载文件名
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function download__zip()
public function download__pack()
{
$user = User::auth();
$ids = Request::input('ids');
$downName = Request::input('name');
if (!is_array($ids) || empty($ids)) {
abort(403, "Please select the file or folder to download.");
@ -1053,9 +1056,7 @@ class FileController extends AbstractController
}
$zip = new \ZipArchive();
// 下载文件名
$downName = count($ids) > 1 ? 'file_'. date("YmdHis") : $files[0]->name;
$zipName = 'temp/download/' . date("Ym") . '/' . $user->userid . '/' . $downName . '.zip';
$zipName = 'temp/download/' . date("Ym") . '/' . $user->userid . '/' . $downName;
$zipPath = storage_path('app/'.$zipName);
Base::makeDir(dirname($zipPath));
@ -1063,12 +1064,56 @@ class FileController extends AbstractController
abort(403, "Failed to create compressed file.");
}
array_walk($files, function($file) use ($zip) {
File::addFileTreeToZip($zip, $file);
go(function() use ($zip, $files, $downName) {
Coroutine::sleep(0.1);
// 压缩进度
$progress = 0;
$zip->registerProgressCallback(0.05, function($ratio) use ($downName, $progress) {
$progress = round($ratio * 100);
File::filePushMsg('compress', [
'name'=> $downName,
'progress' => $progress
]);
});
foreach ($files as $file) {
File::addFileTreeToZip($zip, $file);
}
$zip->close();
if ($progress < 100) {
File::filePushMsg('compress', [
'name'=> $downName,
'progress' => 100
]);
}
});
$zip->close();
return Base::retSuccess('success');
}
return response()->download($zipPath, $downName .'.zip')->deleteFileAfterSend(true);
/**
* @api {get} api/file/download/confirm 21. 确认下载
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName download__confirm
*
* @apiParam {String} [name] 下载文件名
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function download__confirm()
{
$user = User::auth();
$downName = Request::input('name');
$zipName = 'temp/download/' . date("Ym") . '/' . $user->userid . '/' . $downName;
$zipPath = storage_path('app/'.$zipName);
if (!file_exists($zipPath)) {
abort(403, "The file does not exist.");
}
return response()->download($zipPath)->deleteFileAfterSend(true);
}
}

View File

@ -875,13 +875,13 @@ class File extends AbstractModel
/**
* 文件夹文件添加到压缩文件
*
* @param [type] $zip
* @param \ZipArchive $zip
* @param object $file
* @return void
*/
public static function addFileTreeToZip($zip, $file)
{
if ($file->type != 'folder') {
if ($file->type != 'folder' && $file->name != '') {
$content = FileContent::whereFid($file->id)->orderByDesc('id')->first();
$content = Base::json2array($content?->content ?: []);
$typeExtensions = [
@ -892,7 +892,7 @@ class File extends AbstractModel
if (array_key_exists($file->type, $typeExtensions)) {
$filePath = empty($content) ? public_path('assets/office/empty.' . $typeExtensions[$file->type]) : public_path($content['url']);
}
//
$relativePath = $file->path . '.' . $file->ext;
if (file_exists($filePath)) {
$zip->addFile($filePath, $relativePath);
@ -917,4 +917,31 @@ class File extends AbstractModel
$zip->addEmptyDir($file->path);
}
}
/**
* 文件推送消息
* @param $action
* @param array|null $data 发送内容
* @param array $userid 会员ID
*/
public static function filePushMsg($action, $data = null, $userid = null)
{
//
$userid = User::auth()->userid();
if (empty($userid)) {
return;
}
//
$msg = [
'type' => 'file',
'action' => $action,
'data' => $data,
];
$params = [
'userid' => $userid,
'msg' => $msg
];
$task = new PushTask($params, false);
Task::deliver($task);
}
}

View File

@ -10,9 +10,13 @@
<h1>{{$L('文件')}}</h1>
<div v-if="loadIng == 0" class="file-refresh" @click="getFileList"><i class="taskfont">&#xe6ae;</i></div>
</div>
<div v-if="uploadList.length > 0" class="file-status" @click="uploadShow=true">
<div v-if="uploadList.length > 0" class="file-status" @click="[uploadShow=true, packShow=false]">
<Loading v-if="uploadList.find(({status}) => status !== 'finished')"/>
<Button v-else shape="circle" icon="md-checkmark"></Button>
<Button v-else shape="circle" icon="md-arrow-round-up"></Button>
</div>
<div v-if="packList.length > 0" class="file-status" @click="[packShow=true, uploadShow=false]">
<Loading v-if="packList.find(({status}) => status !== 'finished')"/>
<Button v-else shape="circle" icon="md-arrow-round-down"></Button>
</div>
<div :class="['file-search', searchKey ? 'has-value' : '']" @click="onSearchFocus" @mouseenter="onSearchFocus">
<Input v-model="searchKey" ref="searchInput" suffix="ios-search" @on-change="onSearchChange" :placeholder="$L('搜索名称')"/>
@ -240,6 +244,24 @@
</div>
</div>
<div v-if="packShow && packList.length > 0" class="file-upload-list">
<div class="upload-wrap">
<div class="title">
打包列表 ({{packList.length}})
<em v-if="packList.find(({status}) => status === 'finished')" @click="packClear">清空已完成</em>
</div>
<ul class="content">
<li v-for="(item, index) in packList" :key="index" v-if="index < 100">
<AutoTip class="file-name">{{packName(item)}}</AutoTip>
<AutoTip v-if="item.status === 'finished' && item.response && item.response.ret !== 1" class="file-error">{{item.response.msg}}</AutoTip>
<Progress v-else :percent="packPercentageParse(item.percentage)" :stroke-width="5" />
<Icon class="file-close" type="ios-close-circle-outline" @click="packList.splice(index, 1)"/>
</li>
</ul>
<Icon class="close" type="md-close" @click="packShow=false"/>
</div>
</div>
<!--上传文件-->
<Upload
name="files"
@ -407,6 +429,7 @@
</template>
<script>
import axios from "axios";
import {mapState} from "vuex";
import {sortBy} from "lodash";
import DrawerOverlay from "../../components/DrawerOverlay";
@ -423,6 +446,9 @@ export default {
directives: {longpress},
data() {
return {
packList: [],
packShow: false,
loadIng: 0,
searchKey: '',
searchTimeout: null,
@ -1419,6 +1445,65 @@ export default {
});
},
/********************文件打包下载部分************************/
packName(item) {
return item.name;
},
packPercentageParse(val) {
return parseInt(val, 10);
},
packClear() {
this.packList = this.packList.filter(item => item.status !== 'finished');
this.packShow = false;
},
async startPack(filePackName) {
const file = { name: filePackName, status: 'packing', percentage: 0 };
this.packList.push(file);
this.uploadShow = false; //
this.packShow = true; //
const downloadInterval = setInterval(async () => {
const pack = this.$store.state.packLists.find(({name}) => name == filePackName)
file.percentage = Math.max(1, pack.progress);
if (file.percentage >= 100) {
file.status = 'finished';
clearInterval(downloadInterval);
//
await this.downloadPackFile(filePackName);
}
}, 1000);
},
async downloadPackFile(filePackName) {
const downloadUrl = $A.apiUrl(`file/download/confirm?name=${filePackName}&token=${this.userToken}`);
try {
const response = await axios({
url: downloadUrl,
method: 'GET',
responseType: 'blob',
});
if (!response.data) {
console.error('No data received from server');
return;
}
const blob = new Blob([response.data], { type: response.data.type });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `${filePackName}`);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} catch (error) {
}
},
downloadZipFile(ids){
if (ids.length === 0) {
return
@ -1427,26 +1512,26 @@ export default {
const allFolder = !ids.some(id => this.fileLists.some(({ type, id: itemId }) => type !== 'folder' && itemId === id));
const typeName = allFolder ? "文件夹" : "文件";
const fileName = ids.length === 1 ? `${firstFile.name}${typeName}` : `${firstFile.name}】等${ids.length}${typeName}`;
const filePackName = ids.length === 1 ? `${firstFile.name}_${$A.formatDate("YmdHis")}.zip` : `file_${$A.formatDate("YmdHis")}.zip`;
$A.modalConfirm({
title: '打包下载',
content: `你确定要打包下载${fileName}吗?`,
okText: '确定',
onOk: () => {
return new Promise((resolve, reject) => {
this.$store.dispatch("call", {
onOk: async () => {
try {
const { msg } = await this.$store.dispatch("call", {
url: 'file/download/check',
data: {
ids,
},
}).then(({msg}) => {
const idsParam = ids.join('&ids[]=');
this.$store.dispatch('downUrl', $A.apiUrl(`file/download/zip?ids[]=${idsParam}`));
}).catch(({msg}) => {
$A.modalError(msg)
data: { ids },
});
})
this.startPack(filePackName);
await this.$store.dispatch("call", {
url: 'file/download/pack',
data: { ids, name: filePackName },
});
} catch ({ msg }) {
$A.modalError(msg);
}
}
});
},
@ -1884,6 +1969,7 @@ export default {
handleBeforeUpload() {
//
this.uploadShow = true;
this.packShow = false;
return true;
},
}

View File

@ -920,6 +920,24 @@ export default {
})
},
/**
* 获取压缩进度
* @param state
* @param dispatch
* @param data
*/
packProgress({state, dispatch}, data) {
$A.execMainDispatch("packProgress", data)
//
const index = state.packLists.findIndex(({name}) => name == data.name);
if (index > -1) {
state.packLists[index].progress = data.progress;
} else {
state.packLists.push(data);
$A.IDBSave("packLists", state.packLists, 600)
}
},
/**
* 获取文件
* @param state
@ -2781,7 +2799,7 @@ export default {
state.dialogMsgs = state.dialogMsgs.filter(({dialog_id}) => dialog_id !== data.dialog_id)
$A.IDBSave("dialogMsgs", state.dialogMsgs, 600)
}
//
//
data.pagesize = 25;
//
dispatch("call", {
@ -3379,6 +3397,9 @@ export default {
case 'delete':
dispatch("forgetFile", data.id);
break;
case 'compress':
dispatch("packProgress", data);
break;
}
})(msgDetail);
break;

View File

@ -118,6 +118,7 @@ export default {
// 文件
fileLists: [],
fileLinks: [],
packLists: [],
// 项目任务
projectId: 0,