vue+webapi文件上传
话不多说直接上本人测试代码
VUE前端
<template>
<div class="upload-container">
<el-upload
drag
action="/"
multiple
ref="upload"
:limit="10"
:file-list="fileList"
:before-upload="beforeUpload"
:http-request="checkedFile"
:onRemove="removeFile"
accept="application/epub+zip"
class="image-upload"
>
<i class="el-icon-upload" />
<div v-if="fileList.length === 0" class="el-upload__text">
请将电子书拖入或 <em>点击上传</em>
</div>
<div v-else class="el-upload__text">图书已上传</div>
</el-upload>
</div>
</template>
<script>
// import SparkMD5 from 'spark-md5'
import store from "../../store";
import { uuid } from "vue-uuid";
import axios from "axios";
export default {
props: {
fileList: {
type: Array,
default() {
return [];
},
},
disabled: {
type: Boolean,
default: false,
},
},
data() {
return {
maxSize: 1024 * 1024 * 1024 * 1024, // 上传最大文件限制
multiUploadSize: 10 * 1024 * 1024, // 大于这个大小的文件使用分块上传(后端可以支持断点续传)
eachSize: 10 * 1024 * 1024, // 每块文件大小
requestCancelQueue: {}, // 请求方法队列(调用取消上传
cancelRequset: "",
};
},
mounted() {},
methods: {
beforeUpload(file) {
const { maxSize, getSize } = this;
if (file.size > maxSize) {
this.$message({
message: `您选择的文件大于${getSize(maxSize)}`,
type: "error",
});
return false;
}
return true;
},
checkedFile(options) {
const guid = uuid.v1();
this.uploadFile(options, guid);
const prom = new Promise((resolve, reject) => {});
prom.abort = () => {};
return prom;
},
async uploadFile(options, guid) {
const startTime = new Date();
const { multiUploadSize, splitUpload, singleUpload } = this;
const { file, onProgress, onSuccess, onError } = options;
const uploadFunc =
file.size > multiUploadSize ? splitUpload : singleUpload;
try {
await uploadFunc(file, guid, onProgress);
const endTime = new Date();
let time = this.getDateDiff(startTime, endTime, "second");
this.$message({
message: "上传成功 用时:" + time.toString() + "S",
type: "success",
});
onSuccess();
} catch (e) {
this.$message({
message: e.message,
type: "error",
});
onError();
}
},
// 格式化文件大小显示文字
getSize(size) {
return size > 1024
? size / 1024 > 1024
? size / (1024 * 1024) >= 1024
? (size / (1024 * 1024 * 1024)).toFixed(2) + "GB"
: (size / (1024 * 1024)).toFixed(2) + "MB"
: (size / 1024).toFixed(2) + "KB"
: size.toFixed(2) + "B";
},
// 单文件直接上传
singleUpload(file, guid, onProgress) {
return this.postFile(
{ file, guid: guid, fileName: file.fileName, fullSize: file.size },
onProgress
).then((res) => {
onProgress({ percent: 100 });
return Promise.resolve(res);
});
},
// 大文件分块上传
splitUpload(file, guid, onProgress) {
return new Promise(async (resolve, reject) => {
try {
const date = this.getDate();
const { eachSize } = this;
const chunks = Math.ceil(file.size / eachSize);
const fileChunks = await this.splitFile(file, eachSize, chunks);
let currentChunk = 0;
for (let i = 0; i < fileChunks.length; i++) {
// 服务端检测已经上传到第currentChunk块了,那就直接跳到第currentChunk块,实现断点续传
if (Number(currentChunk) === i) {
// 每块上传完后则返回需要提交的下一块的index
currentChunk = await this.postFile(
{
chunked: true,
chunk: i,
chunks,
eachSize,
fileName: file.name,
fullSize: file.size,
guid: guid,
date: date,
file: fileChunks[i],
},
onProgress
).then((res) => {
let percent = 0;
if (res < fileChunks.length - 1) {
percent = Number(
(
(((currentChunk + 1) * eachSize) / file.size) *
100
).toFixed(2)
);
} else {
percent = 99;
}
onProgress({ percent: percent });
return Promise.resolve(res);
});
}
}
const isValidate = await this.validateFile({
chunks: fileChunks.length,
fileName: file.name,
fullSize: file.size,
guid: guid,
});
if (!isValidate) {
throw new Error("文件校验异常");
}
resolve();
} catch (e) {
reject(e);
}
});
},
// 文件分块,利用Array.prototype.slice方法
splitFile(file, eachSize, chunks) {
try {
const fileChunk = [];
for (let chunk = 0; chunks > 0; chunks--) {
fileChunk.push(file.slice(chunk, chunk + eachSize));
chunk += eachSize;
}
return fileChunk;
} catch (e) {
console.error(e);
}
},
// 删除列表中的文件
async removeFile(file) {
this.cancelRequset = file.uid;
if (
this.requestCancelQueue[file.uid] !== null &&
this.requestCancelQueue[file.uid] !== undefined
) {
this.requestCancelQueue[file.uid]();
delete this.requestCancelQueue[file.uid];
} else {
let num = 0;
this.$refs.upload.uploadFiles.map((item) => {
console.log(item.uid);
if (item.uid === file.uid) {
this.$refs.upload.uploadFiles.splice(num, 1);
}
num++;
});
}
return true;
},
// 提交文件方法,将参数转换为FormData, 然后通过axios发起请求
async postFile(param, onProgress) {
const formData = new FormData();
for (let p in param) {
formData.append(p, param[p]);
}
let CancelToken = axios.CancelToken;
let self = this;
const config = {
cancelToken: new CancelToken(function executor(cancel) {
self.requestCancelQueue[param.uid] = cancel;
}),
headers: {
"Content-Type": "multipart/form-data",
},
onUploadProgress: (e) => {
if (param.chunked) {
e.percent = Number(
(
((param.chunk * (param.eachSize - 1) + e.loaded) /
param.fullSize) *
100
).toFixed(2)
);
} else {
e.percent = Number(((e.loaded / e.total) * 100).toFixed(2));
}
onProgress(e);
},
};
if (this.cancelRequset === param.uid) {
self.requestCancelQueue[param.uid]();
delete this.requestCancelQueue[param.uid];
this.cancelRequset = "";
console.log(this.cancelRequset);
throw new Error("取消上传");
} else {
let res = await store.dispatch("upload/fileSave", formData, config);
return res;
}
},
// 文件校验方法
async validateFile(file) {
let res = await store.dispatch("upload/fileMerge", file);
return res;
},
getDateDiff(startTime, endTime, diffType) {
diffType = diffType.toLowerCase();
let num = 1;
if (diffType === "second") {
num = 1000;
} else if (diffType === "minute") {
num = 1000 * 60;
} else if (diffType === "hour") {
num = 1000 * 60 * 60;
} else {
num = 1000 * 60 * 60 * 24;
}
return parseInt(
(endTime.getTime() - startTime.getTime()) / parseInt(num)
);
},
getDate() {
let nowDate = new Date();
let date = {
year: nowDate.getFullYear(),
month: nowDate.getMonth() + 1,
date: nowDate.getDate(),
};
let systemDate = date.year + "-" + date.month + "-" + date.date;
return systemDate;
},
},
};
</script>
后端
[HttpPost]
public ResultModel fileSave()
{
var files = HttpContext.Current.Request.Files;
var form = HttpContext.Current.Request.Form;
string chunk = form["chunk"];
string guid = form["guid"];
//文件保存目录路径
string SaveTempPath = "~/FileCollection/";
string dirTempPath = HttpContext.Current.Server.MapPath(SaveTempPath);
ResultModel message = new ResultModel();
var chunked = form["chunked"];
if (chunked != null && chunked == "true")
{
message.data = Convert.ToInt32(chunk) + 1;
string filePath = Path.Combine(dirTempPath, "TemporaryFiles", guid);
if (!Directory.Exists(filePath))
{
Directory.CreateDirectory(filePath);
}
for (int i = 0; i < files.Count; i++)
{
var file = files[i];
//如果有文件
if (file.ContentLength > 0)
{
string dirPath = Path.Combine(filePath, $"{file.FileName}_{chunk}");
file.SaveAs(dirPath);
}
}
}
else
{
string date = DateTime.Now.ToString("yyyy-MM-dd");
string filePath = Path.Combine(dirTempPath, date, guid);
if (!Directory.Exists(filePath))
{
Directory.CreateDirectory(filePath);
}
for (int i = 0; i < files.Count; i++)
{
var file = files[i];
//如果有文件
if (file.ContentLength > 0)
{
string dirPath = Path.Combine(filePath, file.FileName);
if (!File.Exists(dirPath))
{
file.SaveAs(dirPath);
string FilePath = $"/FileCollection/{date}/{guid}/{file.FileName}";
string SQL = string.Format(@"insert into tab_File (UserName,FileName,FileSize,FilePath) values('{0}','{1}','{2}','{3}')"
, "小范", file.FileName, Convert.ToInt64(form["fullSize"]), FilePath);
int flag = new DBHelper().Execute(SQL);
}
}
}
}
message.code = 0;
message.msg = "上传成功";
return message;
}
[HttpPost]
public async Task<ResultModel> fileMerge([FromBody] FileValidDto valid)
{
ResultModel message = new ResultModel { code = 0 };
string fileName = valid.fileName;
if (string.IsNullOrEmpty(fileName))
{
message.msg = "文件名不能为空";
message.data = false;
return message;
}
//最终上传完成后,请求合并返回合并消息
try
{
//文件保存目录路径
string SaveTempPath = "~/FileCollection/";
string dirTempPath = HttpContext.Current.Server.MapPath(SaveTempPath);
string guid = valid.guid;
int fileCount = valid.chunks;
string filePath = Path.Combine(dirTempPath, "TemporaryFiles", guid);
var files = Directory.GetFiles(filePath);
if (fileCount != files.Count())
{
Directory.Delete(filePath, true);
message.msg = "文件上传失败";
message.data = false;
return message;
}
string date = DateTime.Now.ToString("yyyy-MM-dd");
string fileFinalPath = Path.Combine(dirTempPath, date, guid);
if (!Directory.Exists(fileFinalPath))
{
Directory.CreateDirectory(fileFinalPath);
}
string fileFinalName = Path.Combine(fileFinalPath, fileName);
FileStream fs = new FileStream(fileFinalName, FileMode.Create);
foreach (var part in files.OrderBy(x => x.Length).ThenBy(x => x))
{
var bytes = File.ReadAllBytes(part);
await fs.WriteAsync(bytes, 0, bytes.Length);
File.Delete(part);//删除分块
}
fs.Close();
Directory.Delete(filePath);
string FilePath = $"/FileCollection/{date}/{guid}/{fileName}";
string SQL = string.Format(@"insert into tab_File (UserName,FileName,FileSize,FilePath) values('{0}','{1}','{2}','{3}')"
, "小范", fileName, valid.fullSize, FilePath);
int flag = new DBHelper().Execute(SQL);
message.msg = "上传成功";
message.data = true;
}
catch (Exception ex)
{
message.code = 1;
message.msg = "合并文件失败,请重新上传";
}
return message;
}
[HttpPost]
public ResultModel FileDownload()
{
var form = HttpContext.Current.Request.Form;
var fid = form["fid"];
try
{
DataSet ds = new DBHelper().GetDataSet("select * from tab_File where id=" + fid);
var list = Command.DataSetToList<DataBase.Model.FileModel>(ds).FirstOrDefault();
//文件保存目录路径
string filePath = Path.Combine(HttpContext.Current.Server.MapPath("~/"), list.FilePath);
return new ResultModel
{
code = 0,
msg = "下载",
data = filePath
};
}
catch (Exception ex)
{
return new ResultModel
{
code = 0,
msg = ex.Message
};
}
}
vue+webapi文件下载
VUE前端
事件
<el-button
size="mini"
@click="handleDownLoad(scope.$index, scope.row)"
>下载</el-button
>
方法
handleDownLoad(index, row) {
var param = new FormData();
param.append("fid", index);
param.append("fileName", row.FileName);
let res = store.dispatch("upload/getFileFrom", row.Id);
const blob = new Blob([res.data]); //new Blob([res])中不加data就会返回下图中[objece objece]内容(少取一层)
const fileName = row.FileName; //下载文件名称
const elink = document.createElement("a");
elink.download = fileName;
elink.style.display = "none";
elink.href = URL.createObjectURL(blob);
document.body.appendChild(elink);
elink.click();
URL.revokeObjectURL(elink.href); // 释放URL 对象
},
路由
import { getFileFrom } from '@/api/upload'
const actions = {
// getFileFrom
getFileFrom({ commit }, fid) {
return new Promise((resolve, reject) => {
getFileFrom(fid).then(response => {
resolve(response)
//resolve(response.data)
}).catch(error => {
reject(error)
})
})
}
}
export default {
namespaced: true,
actions
}
export function getFileFrom(fid) {
return request({
url: '/book/getFileFrom?fid=' + fid,
method: 'get',
})
}
webapi后端
//响应的MimeType类型
private const string MimeType = "application/octet-stream";
/// <summary>
/// 缓冲区大小
/// </summary>
private const int BufferSize = 80 * 1024;
[HttpGet]
public ResultModel GetFileFrom(int fid)
{
try
{
DataSet ds = new DBHelper().GetDataSet("select * from tab_File where id=" + fid);
var list = Command.DataSetToList<DataBase.Model.FileModel>(ds).FirstOrDefault();
var FilePath = System.Web.Hosting.HostingEnvironment.MapPath("~"+list.FilePath); //获取文件路径
var fullFilePath = FilePath;
if (!File.Exists(fullFilePath))
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
FileStream fileStream = File.Open(fullFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var response = new HttpResponseMessage();
response.Content = new StreamContent(fileStream, BufferSize);
response.Content.Headers.ContentDisposition
= new ContentDispositionHeaderValue("attachment") { FileName = list.FileName };//list.FileName 文件名称
response.Content.Headers.ContentType
= new MediaTypeHeaderValue(MimeType);
response.Content.Headers.ContentLength
= fileStream.Length;
//return response;
return new ResultModel
{
code = 0,
msg = "下载",
data = response
};
}
catch(Exception ex)
{
return new ResultModel
{
code = 0,
msg = ex.Message
};
}
}