文件存储系统中有一个必不可少的性能就是文件的下载性能,下载的应该反对各种格局的文件。本我的项目中客户端应用的是vue3、axios和vuex,服务端应用的是Gin框架,下边就介绍文件下载局部的代码和次要步骤。
一、Gin框架局部:
- 前两个函数的调用都是依据参数查找files表和user_files表中是否存在文件记录,如果文件不存在,则返回404,如果存在再执行后边的操作。
第二局部是Header的配置,这里重点说三个配置信息。
- c.Header("Access-Control-Expose-Headers", "Content-Disposition"):Content-Disposition中配置有文件名信息,通过Content-Disposition能够把文件名传递给客户端,客户端在取出文件名作为下载文件的名称。然而这一项默认是不公开的,如果不设置c.Header("Access-Control-Expose-Headers", "Content-Disposition")的话content-type就裸露不进来,客户端也接管不了,这一项配置的作用就是裸露该项,也是必须配置的。
- c.Header("response-type", "blob"):如果客服端是以文件流的模式下载文件,服务端必须配置该项,否则下载下来的文件会报如下谬误:文件格式不正确或已损坏。
- c.File(fm.FileAddress):参数是文件的存储地址,这一项也是必须的,否则下载的文件是空的。
- 申请的形式post和get都能够。
// DownloadHandler文件下载接口func DownloadHandler(c *gin.Context) { fileSha1 := c.Request.FormValue("file_sha1") userId, _ := strconv.ParseInt(c.Request.FormValue("user_id"), 10, 64) userFile, err := se.FindUserFileByUserAndSha1(c, userId, fileSha1) if err != nil { fmt.Printf("Failed to find file, err is %s\n", err.Error()) c.JSON(http.StatusNotFound, gin.H{ "code": 0, "msg": "找不到文件", "data": nil, }) return } fm, err := se.FindFileMetaByFileSha1(c, fileSha1) if err != nil { fmt.Printf("Failed to find file, err is %s\n", err.Error()) c.JSON(http.StatusNotFound, gin.H{ "code": 0, "msg": "找不到文件", "data": nil, }) return } c.Header("Content-Type", "application/octet-stream") // 强制浏览器下载 c.Header("Content-Disposition", "attachment;filename=\""+userFile.FileName+"\"") // 浏览器下载或预览 c.Header("Content-Disposition", "inline;filename=\""+userFile.FileName+"\"") c.Header("Content-Transfer-Encoding", "binary") c.Header("Cache-Control", "no-cache") c.Header("Access-Control-Expose-Headers", "Content-Disposition") c.Header("response-type", "blob") // 以流的模式下载必须设置这一项,否则前端下载下来的文件会呈现格局不正确或已损坏的问题 c.File(fm.FileAddress)}
二、vue配置
vue应用的是3版本,因为下载文件的图标和文件夹下的文件列表不在一个vue文件中,须要传递全局变量,所以对变量的状态治理应用vuex来进行,很不便。
import { createStore} from "vuex";export default createStore({ // 变量 state: { downloadFileSha1: "", radioId: 0, }, mutations: { // 批改下载的文件的sha1 EditDownloadFileSha1(state, param) { state.downloadFileSha1 = param }, // 批改radioId的值 EditRadioId(state, value) { state.radioId = value; } }, actions: { // 批改下载的文件的sha1 editDownloadFileSha1(context, payload) { context.commit("EditDownloadFileSha1", payload); }, // 批改radioId的值 editRadioId(context, payload) { context.commit("EditRadioId", payload); } }, modules: {},})
- 文件列表页面抉择要下载的文件,批改全局变量downloadFileSha1和radioId
<el-table :data="JSON.parse(JSON.stringify(this.$store.state.folderFiles))" style="width: 100%" > <el-table-column width="50" label="单选"> <template #default="scope"> <el-radio :label="scope.$index + 1" v-model="this.$store.state.radioId" @change="change(scope)" ></el-radio> </template> </el-table-column> <el-table-column prop="name" label="文件名" width="380"> <template #default="scope" class="cell"> <span style="margin-left: 10px" class="folder">{{ scope.row.file_name }}</span> </template> </el-table-column> <el-table-column label="类型" width="180"> <template #default="scope" class="cell"> {{ scope.row.type }} </template> </el-table-column> <el-table-column prop="size" label="大小" width="180"> <template #default="scope" class="cell"> {{ scope.row.file_size }} </template> </el-table-column> <el-table-column label="批改工夫" width="180"> <template #default="scope" class="cell"> <span>{{ scope.row.upload_at }}</span> </template> </el-table-column> </el-table><script>import { FolderOpened } from "@element-plus/icons";export default { components: { FolderOpened, }, data() { return {}; }, methods: { // 单选框绑定值变动时触发的事件(选中的 Radio label 值) change(scope) { this.$store.dispatch("editRadioId", scope.$index + 1); this.$store.dispatch("editDownloadFileSha1", scope.row.file_sha1); }, },</script>
- request.js文件中封装下载文件的申请,api.js文件中封装downlod办法
// request.js文件export function download(url, data) { return axios({ method: "get", url: `${baseUrl}${url}`, params: data, responseType: "blob", // 服务器返回的数据类型,这一项是必须的,流模式下载 })}// api.js文件import { download } from "./request";const UserFileDownload = "/api/file/download"export function DownloadFile(data) { return download(UserFileDownload, data);}
- header页面点击下载文件的图标依据全局依据全局变量下载发送申请下载文件,申请下载的申请胜利返回之后须要对文件名做设置
<template><el-tooltip class="item" effect="light" content="下载文件" placement="bottom" > <el-icon :size="23" class="icons" @click="downloadFile"> <Download /> </el-icon></el-tooltip></template><script>import { Download } from "@element-plus/icons";import { DownloadFile } from "@/axios/api";export default { components: { Download, }, methods: { downloadFile() { var data = { file_sha1: this.$store.state.downloadFileSha1, user_id: localStorage.getItem("id"), }; this.$confirm("是否确定下载该文件?", "提醒", { confirmButtonText: "确定", cancelButtonText: "勾销", type: "warning", }) .then(() => { DownloadFile(data) .then((res) => { if (!res) { return; } // 依据content-disposition获取文件名 const disposition = res.headers["content-disposition"]; let file_name = disposition.substring( disposition.indexOf("filename=") + 9, disposition.length ); // iso8859-1的字符转换成中文 file_name = decodeURI(escape(file_name)); // 去掉双引号 file_name = file_name.replace(/\"/g, ""); let url = window.URL.createObjectURL(res.data); let link = document.createElement("a"); link.style.display = "none"; link.href = url; link.setAttribute("download", file_name); document.body.appendChild(link); link.click(); window.URL.revokeObjectURL(link.href); document.body.removeChild(link); }) .catch((err) => console.log(err)); this.$message({ type: "success", message: "下载胜利!", }); }) .catch(() => { this.$message({ type: "info", message: "已勾销下载", }); }); this.$store.dispatch("editDownloadFileSha1", ""); this.$store.dispatch("editRadioId", 0); },}</script>
至此,文件下载的的基本功能就开发完了。效果图如下:
我的项目地址:https://gitlab.com/liquanhui0...;我的项目还在开发中,性能正在欠缺中。