golang实现分片下载及断点续载

在我们实际工作中,有时候下载的的东西过大,导致下载失败!重新下载又需要重新开始下载!

故,实现一个分片下载及继续在上次下载的方法,附带下载进度;话不多说上菜:

意:总大小的时候不要加Range: bytes=start-end

func BreakPointDownload(finalPath, downUrl string, count *ReaderCount) error {
	var (
		target        *os.File
		err           error
		start         int = 0
		end           int
		fileTotalSize int
	)
	tmpFile := fmt.Sprintf("%s.tmp", finalPath)
	if runtime.GOOS == "windows" {
		tmpFile = strings.ReplaceAll(tmpFile, "/", "\\")
	}
	os.MkdirAll(filepath.Dir(tmpFile), os.ModePerm)
BeginDown:
	if FileIsExisted(tmpFile) {
		// 如果存在,继续下载
		target, err = os.OpenFile(tmpFile, os.O_WRONLY|os.O_APPEND, 0666)
	} else {
		// 创建临时文件
		target, err = os.Create(tmpFile)
	}
	if err != nil {
		return err
	}
	down := func(start, end int, first bool) (int, error) {
		// fmt.Println("下载", start, end)
		req, err := http.NewRequest(http.MethodGet, downUrl, nil)
		if err != nil {
			return 0, err
		}

		if !first {
			req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", start, end))
		}
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			target.Close()
			return 0, err
		}
		if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
			return 0, errors.New("下载失败:http status" + resp.Status)
		}
		defer resp.Body.Close()
		fileSize := cast.ToInt(resp.Header.Get("Content-Length"))
		if first {
			return fileSize, nil
		}
		//var total uint64
		//total = cast.ToUint64(start)
		//count.SetFileSize(fileSize)
		//count.setTotal(&total)
		count.setReader(resp.Body)
		// 将下载的文件流拷贝到临时文件
		if _, err = io.Copy(target, count); err != nil {
			target.Close()
			return 0, err
		}
		return fileSize, nil
	}

	fileTotalSize, err = down(0, 1, true)
	if err != nil {
		return err
	}

	// 一次下载1000个字节
	length := 10000
	times := fileTotalSize/1000 + 1
	stat, err := target.Stat()
	if err != nil {
		target.Close()
		return err
	}

	// 查看文件的创建时间,如果创建时间超过5小时,删除重新下载
	lastModifyTime := stat.ModTime()
	if time.Now().Sub(lastModifyTime) > 2*time.Hour {
		// 如果修改时间超过两小时,删除重新下载
		target.Close()
		os.Remove(tmpFile)
		goto BeginDown
	}

	start = cast.ToInt(stat.Size())
	// 获取进度-设置参数
	var total uint64
	total = cast.ToUint64(start)
	count.SetFileSize(fileTotalSize)
	count.setTotal(&total)

	for t := 0; t < times; t++ {
		begin := t * length
		end = begin + length - 1
		if end > fileTotalSize {
			end = fileTotalSize
		}

		if end < start {
			continue
		}
		if begin < start && end >= start {
			begin = start
		}

		if begin >= end {
			continue
		}
		_, err = down(begin, end, false)
		if err != nil {
			return err
		}
	}

	// 查看最后文件大小
	info, err := target.Stat()
	if err != nil {
		target.Close()
		return err
	}
	// 关闭临时并修改临时文件为最终文件
	// 如果关闭放在rename后,windows汇报the process cannot access the file
	target.Close()
	if info.Size() == cast.ToInt64(fileTotalSize) {
		err = os.Rename(tmpFile, finalPath)
	}
	return err
}

type ReaderCount struct {
	FileSize  int
	Total     *uint64
	reader    io.ReadCloser
	over      bool
	err       error
	unzipOver bool
}

func (r *ReaderCount) Read(p []byte) (n int, err error) {
	read, err := r.reader.Read(p)
	if err == io.EOF {
		r.over = true
	}
	atomic.AddUint64(r.Total, cast.ToUint64(read))
	return read, err
}

func (r *ReaderCount) Close() error {
	return r.reader.Close()
}

func (r *ReaderCount) SetFileSize(filesize int) {
	r.FileSize = filesize
}
func (r *ReaderCount) setReader(reader io.ReadCloser) {
	r.reader = reader
}
func (r *ReaderCount) setTotal(total *uint64) {
	r.Total = total
}
func (r *ReaderCount) GetRate() string {
	if r.FileSize == 0 || r.Total == nil {
		return "0.0"
	}
	return fmt.Sprintf("%0.2f%%", cast.ToFloat64(r.Total)/cast.ToFloat64(r.FileSize)*100)
}
func (r *ReaderCount) IsOver() bool {
	return r.over
}
func (r *ReaderCount) SetErr(err error) {
	r.err = err
}

func (r *ReaderCount) GetErr() error {
	return r.err
}
func (r *ReaderCount) SetUnzipOver(b bool) {
	r.unzipOver = b
}
func (r *ReaderCount) GetUnzipOver() bool {
	return r.unzipOver
}
func NewReader() *ReaderCount {
	return &ReaderCount{}
}

测试用例:

func Test_BreakPointDownload(t *testing.T) {
	downUrl := `http://xxxx.zip`
	reader := utils.NewReader()
	go func() {
		timer := time.NewTicker(1 * time.Second)
		for {
			select {
			case <-timer.C:
				fmt.Println(reader.GetRate())
			}
		}
	}()
	dest := "./data/cms-linux-v2.1.59.zip"
	fmt.Println(utils.BreakPointDownload(dest, downUrl, reader))
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值