一、 项目简述 我们做的是一套聊天软件, 目前的文件传输是无法取消发送的, 用户很有可能发了不该发的文件给别人, 却又无法取消, 只能默默等待上传结束的一瞬间选择撤回消息, 那需求就来了. (来自产品经理)“ 聊天模块中传输文件显示传输进度条,需要支持取消, 并且能够点击重新发送该文件”
二、项目结构 我们的聊天数据是采用better-sqlite3建立本地数据库,实现的消息存储,建立多张表(比如chat, message,user等), 发送文件是先生成一个local文件id, 通过axios post文件片段到文件服务器进行传输.
因此该需求要实现的变更显而易见:
- 想办法获取axios的上传进度, 并且记录下数据, 便于vue开发的组件可以获取该进度, 进行渲染.
- 由于存在传送多个文件的post请求, 因此需要在每个文件对应的post请求过程pending状态中, 能够取消对应文件的上传.
三、 确定方案 针对(二)中描述的问题, 通过查阅Axios官网API, 很快就敲定了解决策略
-
利用axios的请求配置中的 -> onUploadProgress 处理原生进度事件. (详情可以参考Axios官网 https://axios-http.com/zh/docs/req_config) -
取消文件的方式根据文档(https://axios-http.com/zh/docs/cancellation),有两种方案, 一是采用以 fetch API 方式取消请求(AbortController), 二是通过Axios的 cancel token. 其实更推荐用第一种方案, 不过要求是axios版本最低为0.22.0, 由于我们的项目即将重构, 老版本就还是暂用以前的0.19版本, 因此选择了CancelToken方案.
四、 实战部分 介绍完需求和方案确立过程, 开始尝试在项目中使用. (1) 文件传输进度条显示部分 1.针对数据库的操作 在ChatFile表中增加extra_param字段,存储上传过程中的进度数据. 在Message表中关联对应msg_id对应Chat, 通过chat_id关联chatFile的进度, 存储在file_upload_progress中, 在实际聊天中用于显示, 具体细节不做描述, 涉及业务架构.与本文无关.
2.在Axios请求配置中增加onUploadProgress用于记录当前的上传进度数据:
...
await uploadFile (param, {
onUploadProgress: (progress) => {
const uploadProgress = ((progress.loaded / progress.total) * 100) | 0;
chatFile.extra_param = String(uploadProgress);
ChatFileTable.updateChatFileById(db, localFileId, chatFile);
updateStoreWithChatIdMsgId(store, chatFile.chat_id, chatFile.msg_id);
},
},
}).then(...
...
- 在组件中读取进度,用于显示:(业务代码已省略)
<template>
...
<div v-if="uploadProgress" class="upload-progress">
{{ displayUploadProgress }}
<el-progress
v-if="progressPercentage"
:percentage="progressPercentage"
:show-text="false"
color="#FC8A3D"
/>
</div>
...
</template>
<script lang="ts">
get uploadProgress() {
return this.message.file_upload_progress || 0;
}
get displayUploadProgress() {
return `正在上传 ${this.uploadProgress}%`;
}
get progressPercentage() {
return Number(this.uploadProgress);
}
</script>
效果图:
(2) 取消传输
- 核心思想—— 采用vuex存储对应file_id以及它的post请求的cancelToken, 在组件中点击取消时, 调用CancelToken.source的API进行source.cancel()实现取消.
[MUTATIONS.CHAT_CORE_UPDATE_FILE_CANCEL_TOKEN](
state: IState,
{ file_id, cancelToken }: { file_id: string; cancelToken: CancelTokenSource }
) {
state.fileCancelTokenList.push({ file_id, cancelToken });
},
[MUTATIONS.CHAT_CORE_FILE_CANCEL_TOKEN_UNSET](
state: IState, file_id: string) {
if (file_id) {
state.fileCancelTokenList = state.fileCancelTokenList.filter((file) => file.file_id !== file_id);
}
},
- 增加Axios配置cancelToken.source
const updateFileCancelToken = (store: any, file_id: string, cancelToken: CancelTokenSource) => {
if (store) {
store.commit(MUTATIONS.CHAT_CORE_UPDATE_FILE_CANCEL_TOKEN, { file_id, cancelToken });
}
};
...
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
updateFileCancelToken(store, localFileId, source);
serverFileId = await uploadChatFile(uploadParams, {
onUploadProgress: (progress) => {
...
},
cancelToken: source.token,
}).then((f) => {
Logger.info(`uploadChatFile finished: ${f.id} `);
return f.id;
});
...
- 组件中执行取消传输
<template>
<i class="cancel-btn el-icon-circle-close"
@click.stop="cancelTransfer"
></i>
</template>
<script>
cancelTransfer() {
if (this.fileCancelTokenList) {
const fileCancelToken = this.fileCancelTokenList.find((file) => file.file_id === this.fileId);
const cancelToken = fileCancelToken.cancelToken;
if (cancelToken) {
cancelToken.cancel();
this.$store.commit(MUTATIONS.CHAT_CORE_FILE_CANCEL_TOKEN_UNSET, this.fileId);
} else {
console.log(`本地未找到fileId:${this.fileId},对应的 axios CancelToken`, this.fileCancelTokenList);
}
}
}
</script>
综上就实现了一整套文件上传进度条显示, 以及取消传输的功能了, 重传仅需再次执行该axios的post请求即可.
做的过程中非常希望能在国内论坛看见一套完整的业务实现方案, 但是结果是千篇一律的复制粘贴, 最后还是结合官网和stackoverflow并且加上自己的尝试做出来, 希望用这篇文章记录自己实现该功能的同时, 也能帮助到更多有相同业务的程序员们. 如果有更好的实现方法, 欢迎评论区各位大佬探讨
|