gin分片上传文件

这篇具有很好参考价值的文章主要介绍了gin分片上传文件。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

为什么要使用分片上传

这个为什么已经是老篇常谈了,主要的原因无非就是文件比较大,一次性上传如果网络中断等情况客户端又得重新上传,而且没法补充上传。

切片上传流程

客户端:
有一个大文件,对这个文件进行切片,依据实际业务进行拆分,每一个文件片说白了就是一个[]byte
保证文件的一致性:
需要对每一个切片进行md5处理生成这个块的md5
提交内容:
参数:
文件id - 这个是当前文件上传的序列,保证整个上传中是对哪一个文件进行的上传操作
文件key - 当前切片的文件md5 服务端会进行二次校验保证当前文件切片一致
文件keys - 主文件所有切片的md5
文件切片内容 - 当前切片内容[]byte
文件名称 - 这个是后端用于确定的文件名称

服务端:
直接粗暴的使用json接收都可以,因为是接收的文件切片内容,file实际是一个[]byte
然后判断文件key是否与接收的file进行的md5处理一致,如果不一致则抛出异常即可
将当前切片数据写入临时文件
判断文件keys是否均已写入临时文件,如果没有直接返回保存成功即可
如果文件keys均已写入临时文件,那么就依次读文件keys进行读临时文件,然后写入到新文件即可

依据实际业务可以删除原临时文件或者也可以直接将临时文件上传到你的文件存储服务器,由它进行合并

源码: 纯go语言编写客户端及服务端
首先我们需要一个请求结构体

package web

import (
    "crypto/md5"
    "errors"
    "fmt"
    "net/http"
    "os"
    "path/filepath"

    "github.com/gin-gonic/gin"
)

type ChunkFileRequest struct {
    FileId   string   `json:"fileId"`   // client create uuid
    FileName string   `json:"fileName"` // file name
    FileKeys []string `json:"fileKeys"` // file slice all key md5
    FileKey  string   `json:"fileKey"`  // file now key to md5 - if server read the slice to md5 eq key not eq then fail
    File     []byte   `json:"file"`     // now file

    ctx *gin.Context // ctx
}

func (cf *ChunkFileRequest) BindingForm(c *gin.Context) error {
    if err := c.ShouldBind(cf); err != nil {
        return err
    }

    cf.ctx = c
    return cf.md5()
}

func (cf *ChunkFileRequest) md5() error {
    fmt.Println(cf.FileKey)
    hash := fmt.Sprintf("%x", md5.Sum(cf.File))
    fmt.Println(hash)
    if hash != cf.FileKey {
        return errors.New("current file slice key error")
    }
    return nil
}

func (cf *ChunkFileRequest) SaveUploadedFile(tempPath, path string) (string, error) {
    tempFolder := filepath.Join(tempPath, cf.FileId)

    _, err := os.Stat(tempFolder)
    if os.IsNotExist(err) {
        err := os.MkdirAll(tempFolder, os.ModePerm)
        if err != nil {
            return "", err
        }
    }

    out, err := os.Create(filepath.Join(tempFolder, cf.FileKey))
    if err != nil {
        return "", err
    }
    defer out.Close()
    if _, err := out.Write(cf.File); err != nil {
        return "", err
    }

    for _, fileKey := range cf.FileKeys {
        tempFile := filepath.Join(tempFolder, fileKey)
        if _, err := os.Stat(tempFile); err != nil {
            return "", nil
        }
    }

    base := filepath.Dir(path)
    if _, err := os.Stat(base); err != nil {
        if os.IsNotExist(err) {
            err := os.MkdirAll(base, os.ModePerm)
            if err != nil {
                return "", err
            }
        }
    }

    file, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0664)
    if err != nil {
        return "", err
    }

    defer file.Close()

    for _, fileKey := range cf.FileKeys {
        tempFile := filepath.Join(tempFolder, fileKey)
        bt, err := os.ReadFile(tempFile)
        if err != nil {
            return "", err
        }
        file.Write(bt)
    }

    return tempFolder, nil
}

// param: fileId
// param: fileName
// param: fileKeys the file slice all file key md5
// param: fileKey  now file slice key md5
// param: file     now slice file
func ChunkFile(c *gin.Context) {
    var cf ChunkFileRequest

    if err := cf.BindingForm(c); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"code": "400", "msg": "bad file param", "err": err.Error()})
        return
    }

    tempFolder, err := cf.SaveUploadedFile("./temp", "./uploads/"+cf.FileName)
    if err != nil {
        c.JSON(http.StatusServiceUnavailable, gin.H{"code": "503", "msg": "bad save upload file", "err": err.Error()})
        return
    }
    c.JSON(http.StatusOK, gin.H{"code": "200", "msg": "success"})
    if tempFolder != "" {
        defer func(tempFolder string) {
            os.RemoveAll(tempFolder)
        }(tempFolder)
    }
}

服务端及客户端测试代码:

func TestChunkFileUploadServer(t *testing.T) {
    ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
    defer stop()

    w := gin.Default()
    w.POST("/chunkFile", web.ChunkFile)

    srv := &http.Server{
        Addr:    ":8080",
        Handler: w,
    }

    go func() {
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("listen: %s\n", err)
        }
    }()

    <-ctx.Done()
    stop()
    log.Println("shutting down gracefully, press Ctrl+C again to force")

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    if err := srv.Shutdown(ctx); err != nil {
        log.Fatal("Server forced to shutdown: ", err)
    }

    log.Println("Server exiting")
}

func TestChunkFileUploadClient(t *testing.T) {
    // your client file path
    filePath := ""
    fileName := filepath.Base(filePath)

    fileInfo, err := os.Stat(filePath)
    if err != nil {
        log.Fatalf("file stat fail: %v\n", err)
        return
    }

    const chunkSize = 1 << (10 * 2) * 30

    num := math.Ceil(float64(fileInfo.Size()) / float64(chunkSize))

    fi, err := os.OpenFile(filePath, os.O_RDONLY, os.ModePerm)
    if err != nil {
        log.Fatalf("open file fail: %v\n", err)
        return
    }

    fileKeyMap := make(map[string][]byte, 0)
    fileKeys := make([]string, 0)

    for i := 1; i <= int(num); i++ {
        file := make([]byte, chunkSize)
        fi.Seek((int64(i)-1)*chunkSize, 0)
        if len(file) > int(fileInfo.Size()-(int64(i)-1)*chunkSize) {
            file = make([]byte, fileInfo.Size()-(int64(i)-1)*chunkSize)
        }
        fi.Read(file)

        key := fmt.Sprintf("%x", md5.Sum(file))
        fileKeyMap[key] = file
        fileKeys = append(fileKeys, key)
    }

    fileId := uuid.NewString()

    for _, key := range fileKeys {
        req := web.ChunkFileRequest{
            FileId:   fileId,
            FileName: fileName,
            FileKey:  key,
            FileKeys: fileKeys,
            File:     fileKeyMap[key],
        }
        body, _ := json.Marshal(req)

        res, err := http.Post("http://127.0.0.1:8080/chunkFile", "application/json", bytes.NewBuffer(body))

        if err != nil {
            log.Fatalf("http post fail: %v", err)
            return
        }
        defer res.Body.Close()
        msg, _ := io.ReadAll(res.Body)
        fmt.Println(string(msg))
    }
}

参考链接:https://www.jianshu.com/p/6b2ab6d0a082文章来源地址https://www.toymoban.com/news/detail-699327.html

到了这里,关于gin分片上传文件的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Go语言web框架——Gin

    Gin是一个go语言写的Web框架 客户机通过TCP/IP协议建立到服务器的TCP连接 客户端向服务器发送HTTP协议请求 Request GET /url ,请求服务器里的资源文档 服务器向客户机发送HTTP协议应答Response,如果请求的资源包含有动态语言的内容,那么服务器会调用动态语言的解释引擎负责处理

    2023年04月14日
    浏览(72)
  • 前端文件上传(文件上传,分片上传,断点续传)

    普通文件上传 思路: 首先获取用户选择的文件对象,并将其添加到一个 FormData 对象中。然后,使用 axios 的 post 方法将 FormData 对象发送到服务器。在 then 和 catch 中,我们分别处理上传成功和失败的情况,并输出相应的信息。 需要注意,在使用 axios 进行文件上传时,必须将

    2024年02月22日
    浏览(31)
  • 前端上传大文件使用分片上传

    前提:分片上传针对于一些大的文件、普通大小的文件使用element中的上传组件可以实现效果,例如几G的文件就会比较卡,所以这时候就需要用到分片上传~ 前端及后端分片上传笔记 效果:(上传进度展示) 效果:(上传成功的效果展示)

    2024年01月25日
    浏览(34)
  • go语言Gin框架常见面试题(1)

    Gin框架是一种基于Go语言的轻量级Web框架,具有高效、快速、易用等优点。Gin采用了类似于Expres

    2024年02月08日
    浏览(55)
  • Go语言Web框架Gin常见用法

    Gin是目前Go语言最为常用的Web框架,日常工作中也少不了使用此框架,编写此使用总结文档以备后用。 此文档参考官方文档编写,仅用于自我学习总结和参考。 我一直认为编写文档的意义一方面是给其他人提供了些许帮助,另一方面则是让自己加深了对知识的理解并为自己提

    2024年02月03日
    浏览(40)
  • SpringBoot+vue文件上传&下载&预览&大文件分片上传&文件上传进度

    SpringBoot+vue 大文件分片下载 Blob File spark-md5根据文件内容生成hash 大文件分片上传(批量并发,手动上传)vue组件封装-form组件 vue上传大文件/视频前后端(java)代码 springboot+vue自定义上传图片及视频 SpringBoot + VUE实现前台上传文件获取实时进度( 使用commons-fileupload设置上传监听

    2024年02月05日
    浏览(65)
  • 基于go语言gin框架的web项目骨架

    节省时间与精力,更高效地打造稳定可靠的Web项目:基于Go语言和Gin框架的完善Web项目骨架。无需从零开始,直接利用这个骨架,快速搭建一个功能齐全、性能优异的Web应用。充分发挥Go语言和Gin框架的优势,轻松处理高并发、大流量的请求。构建可扩展性强、易于维护的代码

    2024年02月08日
    浏览(35)
  • Go语言工程实践之测试与Gin项目实践

    回归 测试 一般是QA(质量保证)同学 手动通过终端回归一些固定的主流程 场景 集成 测试 是对 系统功能维度做测试验证 ,通过服务暴露的某个接口,进行自动化测试 而 单元 测试 开发阶段,开发者 对单独的函数、模块做功能验证 层级从上至下, 测试成本逐渐减低 ,而测试 覆

    2024年02月13日
    浏览(35)
  • 【JavaScript】文件分片上传

    一般我们是用 FormData 上传,比较简单,注意 headers 加上 \\\"Content-Type\\\": \\\"multipart/form-data\\\" 当遇到文件太大、网络不好等情况时,如果发生连接中断、挂掉,那整个文件就白传,需要重头再传,这是非常不人性化的用户体验。所以我们可以根据网络情况将文件分成小碎片,最后在服

    2024年02月06日
    浏览(27)
  • Java实现文件分片上传

    Java实现文件分片上传 为什么要使用分片上传 在需要上传文件时,不可避免地会遇到上传文件内容过大,上传时间太长地问题,采用文件分片上传就可以解决这个问题。 什么是分片上传? 简单的说就是本来是需要一次搬一个很大的东西,比如是一大桶水,一次搬起来比较费

    2024年02月08日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包