文件分片上传
文件分片上传,解决大文件上传缓慢问题
实现思路:
1.使用js将文件按照指定的分片文件大小进行拆分
2.构建form表单数据
i:file 分片文件
ii: chunkindex 当前切片数据
iii: chunktotal 分片总数
iv: filesize 文件总大小
3.使用ajax逐渐上传文件
4.后台保存每一个上传文件
5.生成新文件,计算上传的每个分片文件总大小是否于前端传的文件总大小是否相等,相等则合并分片文件
6.删除分片文件
完整文件代码
https://github.com/yunziyuan/cntech-go/tree/main/chunkfile</a>
开发http服务
// 文件服务
func fileServer() {
http.HandleFunc("/chunkfile", chunkFile)
// 监听8001端口
err := http.ListenAndServe("0.0.0.0:8001", nil)
if err != nil {
log.Fatal("服务启动失败")
}
}
处理文件
// 上传文件
func chunkFile(w http.ResponseWriter, r *http.Request) {
// 设置跨域
w.Header().Set("Access-Control-Allow-Origin", "*") //允许访问所有域
w.Header().Add("Access-Control-Allow-Headers", "Content-Type") //header的类型
w.Header().Set("content-type", "application/json") //返回数据格式是json
// 合并
res, err := mergeChunk(r)
if err != nil {
_, _ = w.Write([]byte(err.Error()))
} else {
// 保存上传节点
_, _ = w.Write([]byte("上传成功:" + strconv.Itoa(res)))
}
}
分片上传文件
// 分片上传
func chunkUpload(r *http.Request) (int, error) {
// 分片序号
chunkIndex := r.FormValue("chunkindex")
// 获取上传文件
upFile, fileHeader, err := r.FormFile("file")
if err != nil {
return 0, errors.New("上传文件错误")
}
// 新文件创建
filePath := tmpFilePath + fileHeader.Filename + "_" + chunkIndex
fileBool, err := createFile(filePath)
if !fileBool {
return 0, err
}
// 获取现在文件大小
fi, _ := os.Stat(filePath)
// 判断文件是否传输完成
if fi.Size() == fileHeader.Size {
return 0, errors.New("文件已存在, 不继续上传")
}
start := strconv.Itoa(int(fi.Size()))
// 进行断点上传
// 打开之前上传文件
file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY, os.ModePerm)
defer file.Close()
if err != nil {
return 0, errors.New("打开之前上传文件不存在")
}
// 将数据写入文件
count, _ := strconv.ParseInt(start, 10, 64)
total, err := uploadFile(upFile, count, file, count)
return total, err
}
// 上传文件
func uploadFile(upfile multipart.File, upSeek int64, file *os.File, fSeek int64) (int, error) {
// 上传文件大小记录
fileSzie := 0
// 设置上传偏移量
upfile.Seek(upSeek, 0)
// 设置文件偏移量
file.Seek(fSeek, 0)
data := make([]byte, 1024, 1024)
for {
total, err := upfile.Read(data)
if err == io.EOF {
//fmt.Println("文件复制完毕")
break
}
len, err := file.Write(data[:total])
if err != nil {
return 0, errors.New("文件上传失败")
}
// 记录上传长度
fileSzie += len
}
return fileSzie, nil
}
合并文件
// 合并切片文件
func mergeFile(i int, fileName, filePath string) {
// 打开之前上传文件
file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY, os.ModePerm)
defer file.Close()
if err != nil {
log.Fatal("打开之前上传文件不存在")
//return 0, errors.New("打开之前上传文件不存在")
}
// 分片大小获取
fi, _ := os.Stat(tmpFilePath + fileName + "_0")
chunkSize := fi.Size()
// 设置文件写入偏移量
file.Seek(chunkSize*int64(i), 0)
iSize := strconv.Itoa(i)
chunkFilePath := tmpFilePath + fileName + "_" + iSize
fmt.Printf("分片路径:", chunkFilePath)
chunkFileObj, err := os.Open(chunkFilePath)
defer chunkFileObj.Close()
if err != nil {
log.Fatal("打开分片文件失败")
//return 0, errors.New("打开分片文件失败")
}
// 上传总数
totalLen := 0
// 写入数据
data := make([]byte, 1024, 1024)
for {
tal, err := chunkFileObj.Read(data)
if err == io.EOF {
// 删除文件 需要先关闭改文件
chunkFileObj.Close()
err := os.Remove(chunkFilePath)
if err != nil {
fmt.Println("临时记录文件删除失败", err)
}
fmt.Println("文件复制完毕")
break
}
len, err := file.Write(data[:tal])
if err != nil {
log.Fatal("文件上传失败")
//return 0, errors.New("文件上传失败")
}
totalLen += len
}
lock.Done()
//return totalLen,nil
}
html主要代码
<script type="text/javascript">
// 每个文件切片大小定为10M
var chunksize = 1024 * 1024 * 50;
// 定义上传总切片数
var chunktotal;
// 设置上传成功数量记录
successTotal = 0
function upload() {
var file = document.getElementById("file").files[0];
var start = 0;
var end;
var index = 0;
var filesize = file.size;
var filename = file.name;
// 计算总的切片数
chunktotal = Math.ceil(filesize / chunksize);
while(start < filesize) {
end = start + chunksize;
if(end > filesize) {
end = filesize;
}
var chunk = file.slice(start,end);//切割文件
var chunkindex = index;
var formData = new FormData();
// 新增切片文件
formData.append("file", chunk, filename);
// 切片索引
formData.append("chunkindex", chunkindex);
// 切片总数
formData.append("chunktotal", chunktotal);
// 文件总大小
formData.append("filesize",filesize)
// 使用ajax提交
\$.ajax({
url: 'http://127.0.0.1:8001/chunkfile',
type: 'POST',
cache: false,
data: formData,
processData: false,
contentType: false,
success:function (res){
successTotal = successTotal + 1
}
}).done(function(res){
console.log(res)
}).fail(function(res) {
console.log(res)
});
start = end;
index++;
}
}
</script>