golang断点续传

golang断点续传

一、原理

把文件以固定大小拆分为多个分片,每次上传一个文件分片。我以每个分片2M(2*1024*1024)

我把功能分为两个接口:

1.检查文件上传状态接口

2.上传文件分片接口

二、接口描述

1.检查文件上传状态接口

描述:根据上传文件的md5和文件大小获取上传状态

类型:post

请求参数:

参数名

类型

是否必填

描述

md5

string

文件MD5

file_len

int64

文件长度

返回参数:

参数名

类型

是否必填

描述

countIndex

int64

一共有多少个分片

dataId

int64

数据记录ID

fileSize

int64

文件真实大小

startIndex

int64

当前上传第几个分片

status

int

例子:

{
    "code": 200,
    "data": {
        "countIndex": 1,
        "dataId": 36,
        "fileSize": 1206023,
        "startIndex": 0,
        "status": 0
    },
    "msg": "Success"
}

2.上传文件分片接口

描述:每个分片2M(2*1024*1024)每次上传一个分片

类型:post

请求参数:

参数名

类型

是否必填

描述

md5

string

文件MD5

data

blob

文件分片

fileName

string

文件名

fileSize

int64

文件长度

shardSize

int64

分片大小

shardCount

int64

分片总数

indexCurrent

int64

当前第几个分片

dataId

int64

数据记录ID

返回参数:

参数名

类型

是否必填

描述

code

int64

状态码,区分上分片上传成功,还是全部上传成功

三、代码

前端代码

upload.vue

<template>
  <a-modal
    title="上传"
    :visible="visible"
    :confirm-loading="loading"
    @ok="handleOk"
    @cancel="handleCancel"
  >
    <a-upload name="file" id="uploadFile" @beforeUpload="customRequest" @change="handleChange" >
      <a-button>上传文件</a-button>
    </a-upload>
  </a-modal>
</template>
  <script>
  import {isUpload} from "./model"
  import SparkMD5 from 'spark-md5'

  export default {
    name: "upload",
    data() {
      return {
        visible: true,
        loading: false,
        uploadFile:null,
        md5File:"",
        fileSize:0
      };
    },
    methods: {
      handleOk() {
        var vm = this;
        this.visible = true;
        console.log(vm.md5File);
        isUpload({
          "md5":vm.md5File,
          "file_len":vm.fileSize
        }).done(res=>{
          console.log("res:"+JSON.stringify(res));
          if(res.code==200){
            if(res.data.status==0){
                this.postFile(vm.uploadFile.file,res.data.startIndex,res.data.dataId);
            }else if(res.data.status==1){
               this.visible = false;  
            }
          }
        });
      },
      
      getToken(i) {
        return '?token=' + (localStorage.token || '')+"&i="+i;
      },
      async postFile(file,i,dataId){
        var vm = this;
        var name = file.name,                           //文件名
              size = file.size,                           //总大小shardSize = 2 * 1024 * 1024,
              shardSize = 2 * 1024 * 1024,                //以2MB为一个分片,每个分片的大小
              shardCount = Math.ceil(size / shardSize);  //总片数
          if(i >= shardCount){
              return;
          }
          //console.log(size,i+1,shardSize);  //文件总大小,第一次,分片大小//
          var start = i * shardSize ;
          var end = start + shardSize;
          end = end>size?size:end;
          var packet = file.originFileObj.slice(start, end);  //将文件进行切片
          /*  构建form表单进行提交  */
          var form = new FormData();
          form.append("md5", vm.md5File)
          form.append("data", packet); //slice方法用于切出文件的一部分
          form.append("fileName", encodeURI(name));
          form.append("fileSize", size);
          form.append("shardSize", shardSize);
          form.append("shardCount", shardCount); //总片数
          form.append("indexCurrent", i + 1); //当前是第几片
          form.append("dataId", dataId); //当前是第几片
          await vm.uploadAjax(form,file,i,dataId);
          form = '';
      },
      uploadAjax(form,file,i,dataId){
        var vm = this;
            $.ajax({
              url: "./datasource/file/uploadContinuation",
              type: "POST",
              data: form,
              //timeout:"10000",  //超时10秒
              async: true, //异步
              dataType:"json",
              processData: false, //很重要,告诉jquery不要对form进行处理
              contentType: false, //很重要,指定为false才能形成正确的Content-Type
              success: function (msg) {
                  console.log(msg);
                  if (msg.code == -400253) {
                      i++;
                      console.log("request upload i:"+i);
                      vm.postFile(file, i,dataId);
                  } else if (msg.code == -400254) {
                      /*  失败后,每2秒继续传一次分片文件  */
                      setInterval(function () { vm.postFile(file, i,dataId) }, 2000);
                  }else if (msg.code == -400255) {
                      /*  上传完成后,md5比对失败*/
                      alert("上传失败");
                  }else if (msg.code == 200) {
                    vm.visible = false;
                      alert("上传成功");
                  } else if (msg.code == 500) {
                      console.log('第'+msg.i+'次,上传文件有误!');
                  } else {
                      console.log('未知错误');
                  }
              },
              error:function(msg){
                      console.log("request upload failed:"+msg);
                    vm.uploadAjax(form,file,i,dataId);
              }
          })
      },
      handleCancel() {
        this.visible = false;
      },
       fileChange(file) {
         var vm = this;
         vm.fileSize= file.size
        var  fileReader=new FileReader()
        var Spark=new SparkMD5.ArrayBuffer();
        fileReader.readAsArrayBuffer(file)
        fileReader.onload=function(e){
            Spark.append(e.target.result)
            vm.md5File=Spark.end()
        }
     },
     customRequest(data){
       return false;
     },
      handleChange(info) {
        var vm = this;
        vm.uploadFile=info;
        vm.fileChange(vm.uploadFile.file.originFileObj);
      }
     
    },
  };
  </script>
  <style lang="less" scoped>
  </style>

model.js

'use strict';
import http from '../../commons/js/http.js'
define([], function() {

    var isUpload = function(param) {
        return http.postForm("./file/uploadStatus",param);
    };
    return {
        isUpload:isUpload
    };
});

后台代码

action.go

// @Summary 数据源文件上传状态
// @Tags 数据导入
// @Produce json
// @Param file_path formData string true "文件路径"
// @Success 200 {object} data_type.Response
// @Router /upload/start [post]
func (ds *DataSource) SourceUploadStatus(context *gin.Context) {
   md5 := context.GetString(consts.ValidatorPrefix + "md5")
   fileLen, _ := strconv.ParseInt(context.GetString(consts.ValidatorPrefix+"file_len"), 10, 64)
   fmt.Println("start upload status,md5: ", md5, ",fileSize:", fileLen)
   fileData := data_source.Md5ByUploadFile(md5, fileLen)
   dataId := data_source.SaveDataSource()
   fileData["dataId"] = dataId
   response.Success(context, consts.CurdStatusOkMsg, fileData)
}

// @Summary 数据源文件上传
// @Tags 数据导入
// @Produce json
// @Param file_path formData string true "文件路径"
// @Success 200 {object} data_type.Response
// @Router /upload/start [post]
func (ds *DataSource) SourceUploadContinuation(context *gin.Context) {
   fmt.Println("start upload continuation.......")
   formData, err := context.MultipartForm()
   if err != nil {
      response.Fail(context, consts.FilesUploadPartFailCode, consts.FilesUploadPartFialMsg, "")
      return
   }
   partData := formData.File["data"][0]
   md5 := context.GetString(consts.ValidatorPrefix + "md5")
   fileName := context.GetString(consts.ValidatorPrefix + "fileName")
   fileSize := context.GetFloat64(consts.ValidatorPrefix + "fileSize")
   shardSize := context.GetFloat64(consts.ValidatorPrefix + "shardSize")
   shardCount := context.GetFloat64(consts.ValidatorPrefix + "shardCount")
   indexCurrent := context.GetFloat64(consts.ValidatorPrefix + "indexCurrent")
   dataId := context.GetFloat64(consts.ValidatorPrefix + "dataId")

   fmt.Printf("fileName:%s ,md5:%s ,fileSize:%f ,shardSize:%f , shardCount:%f ,indexCurrent:%f ", fileName, md5, fileSize, shardSize, shardCount, indexCurrent)
   filePath := data_source.Md5ByUploadFileSavePart(md5, fileSize, shardSize, shardCount, indexCurrent, partData)
   if shardCount == indexCurrent {
      //获取MD5
      fileMD5 := data_source.GetFileMD5(filePath)
      //md5 比较
      if strings.Compare(fileMD5, md5) == 0 {
         fileNameResult := filePath + "_" + fileName
         os.Rename(filePath, fileNameResult)
         //to_do 更新数据库记录和状态
         response.Success(context, consts.FilesUploadingMsg, "")
         return
      } else {
         os.Remove(filePath)
      }
      response.Interactive(context, consts.FilesUploadPartFinishFailCode, consts.FilesUploadPartFinishFialMsg, "")
   } else {
      response.Interactive(context, consts.FilesUploadingCode, consts.FilesUploadingMsg, "")
   }

}

data_source.go

func Md5ByUploadFile(md5 string, fileLen int64) map[string]interface{} {
   savePath := variable.ConfigYml.GetString("UploadFile.savePath")
   filePath := path.Join(savePath, md5)
   fmt.Println(filePath)
   exists, err := files.PathExists(filePath)
   countIndex := (fileLen / consts.UploadFilePartSize) + 1
   file_status := make(map[string]interface{})
   file_status["startIndex"] = 0
   file_status["fileSize"] = fileLen
   file_status["countIndex"] = countIndex
   file_status["status"] = 0
   if err == nil || exists {
      file_size := files.FileSize(filePath)
      startIndex := file_size / consts.UploadFilePartSize
      if startIndex == 0 {
         file_status["startIndex"] = 0
      } else {
         file_status["startIndex"] = startIndex - 1
      }

   }
   return file_status
}

func Md5ByUploadFileSavePart(md5 string, fileSize float64, shardSize float64, shardCount float64, indexCurrent float64, partData *multipart.FileHeader) string {
   savePath := variable.ConfigYml.GetString("UploadFile.savePath")
   filePath := path.Join(savePath, md5)
   fmt.Println(filePath)
   start := (indexCurrent - 1) * shardSize
   WriteFile(partData, filePath, start)
   return filePath
}
func GetFileMD5(pathName string) string {
   f, err := os.Open(pathName)
   if err != nil {
      fmt.Println("Open", err)
      return ""
   }
   defer f.Close()

   md5hash := md5.New()
   if _, err := io.Copy(md5hash, f); err != nil {
      fmt.Println("Copy", err)
      return ""
   }
   has := md5hash.Sum(nil)
   md5str := fmt.Sprintf("%x", has)
   return md5str
}

/*
*
保存文件
*/
func WriteFile(partData *multipart.FileHeader, filePath string, start float64) (string, error) {
   filepoint, err := partData.Open() //打开文件
   if err != nil {
      return "", err
   }
   defer filepoint.Close()

   //创建新文件进行存储
   newfile, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0644)
   if err != nil {
      return "", err
   }
   defer newfile.Close()
   newfile.Seek(int64(start), 0)
   //把旧文件的内容放入新文件
   var context []byte = make([]byte, 1024)
   for {
      n, err := filepoint.Read(context)
      newfile.Write(context[:n])
      if err != nil {
         if err == io.EOF {
            return filePath, nil
         } else {
            return "", err
         }
      }
   }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值