Golang仿云盘项目-5.用户上传/查询文件/秒传

秒传原理

TODO

服务架构变迁

Golang仿云盘项目-5.用户上传/查询文件/秒传
较之前的加入了用户文件表、hash计算。
  • 唯一文件表:一个文件只存一条记录,文件的filesha1为主键
  • 用户文件表:存储每个用户所有文件的元数据
  • Hash计算:内潜在上传server里,作为内部逻辑模块存在;也可以单独抽出来作为独立的微服务,向外提供接口

无秒传的流程:

  1. 【用户】上传文件数据,【上传Server】接收到文件数据流后,
  2. 它会一边存储在【本地存储】,
  3. 一边通知【hash计算】模块,该模块计算 当前上传 那一块文件的哈希值,等到整个文件上传完成之后,就会得到 整个文件的哈希值
  4. 然后【上传server】将其哈希值写入到【唯一文件表】;
  5. 最后【上传server】把文件元信息关联到【用户文件表】。
    后面用户就可以通过【用户文件表】查询相应文件元信息。

秒传的情况下:

  1. 没有实际的文件传输了,因为【上传Server】在比对哈希值后发现值相同后会直接忽略3,4步,直接跳到第5步(将文件元信息关联到用户文件表)

本节完成效果

Golang仿云盘项目-5.用户上传/查询文件/秒传

本文来自博客园,作者:Jayvee,转载请注明原文链接: https://www.cnblogs.com/cenjw/p/16496050.html

用户上传文件

db/userfile.go

点击查看代码

package db

import (
    mydb "FileStorageDisk/db/mysql"
    "fmt"
    "time"
)

// UserFile: 与用户文件表字段对应
type UserFile struct {
    UserName string
    FileHash string
    FileName string
    FileSize int64
    UploadAt string
    LastUpdated string
}

// OnUserFileUploadFinished : 更新用户文件表
func OnUserFileUploadOK(username, filehash, filename string, filesize int64) bool {
    stmt, err := mydb.DBConn().Prepare(
        "insert ignore into tbl_user_file (user_name,file_sha1,file_name," +
            "file_size,upload_at) values (?,?,?,?,?)")
    if err != nil {
        return false
    }
    defer stmt.Close()

    _, err = stmt.Exec(username, filehash, filename, filesize, time.Now())
    if err != nil {
        return false
    }
    return true
}

// QueryUserFileMetas : 批量获取用户文件信息
func QueryUserFileMetas(username string, limit int) ([]UserFile, error) {
    stmt, err := mydb.DBConn().Prepare(
        "select file_sha1,file_name,file_size,upload_at," +
            "last_update from tbl_user_file where user_name=? limit ?")
    if err != nil {
        return nil, err
    }
    defer stmt.Close()

    rows, err := stmt.Query(username, limit)
    if err != nil {
        return nil, err
    }

    var userFiles []UserFile
    for rows.Next() {
        ufile := UserFile{}
        err = rows.Scan(&ufile.FileHash, &ufile.FileName, &ufile.FileSize,
            &ufile.UploadAt, &ufile.LastUpdated)
        if err != nil {
            fmt.Println(err.Error())
            break
        }
        userFiles = append(userFiles, ufile)
    }
    // fmt.Printf("userFiles2: %v\n", userFiles)
    return userFiles, nil
}

用户批量查询文件接口

handler/handler.go

点击查看代码

// QueryFileHandler: 查询批量文件信息
func FileQueryHandler(w http.ResponseWriter, r *http.Request) {
    r.ParseForm()

    username := r.Form.Get("username")
    limitCnt, _ := strconv.Atoi(r.Form.Get("limit"))
    userFiles, err := dblayer.QueryUserFileMetas(username, limitCnt)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        return
    }
    // fmt.Printf("userFiles: %v\n", userFiles)

    data, err := json.Marshal(userFiles)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    w.Write(data)
}

// UploadHandler: 文件上传接口
func UploadHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method == "GET" {
        // 返回上传的HTML页面
        data, err := ioutil.ReadFile("./static/view/index.html")
        if err != nil {
            fmt.Println("Internal server error")
            return
        }
        io.WriteString(w, string(data))

    } else if r.Method == "POST" {
        // 1.获取文件句柄、文件头、错误(如果有)
        file, header, err := r.FormFile("file")
        if err != nil {
            fmt.Printf("Failed to get data, err:%s\n", err.Error())
            return
        }
        defer file.Close()

        // 5. 设置文件元信息
        fileMeta := meta.FileMeta{
            FileName: header.Filename,
            Location: "/tmp/" + header.Filename,
            UploadAt: time.Now().Format("2006-01-02 15:04:05"),
        }

        // 2.创建一个本地文件接收当前文件流
        // newFile, err := os.Create("/tmp/" + header.Filename)
        newFile, err := os.Create(fileMeta.Location)
        if err != nil {
            fmt.Printf("Failed to create file, err:%s\n", err.Error())
            return
        }
        // 3. 将内存中的文件拷贝到newFile的buffer区
        // _, err = io.Copy(newFile, file)
        fileMeta.FileSize, err = io.Copy(newFile, file)
        if err != nil {
            fmt.Printf("Failed to save data into file, err:%s\n", err.Error())
            return
        }

        // 6. 更新FileMeta
        newFile.Seek(0, 0) // 把文件句柄的位置移到开始位置
        fileMeta.FileSha1 = util.FileSha1(newFile)
        // meta.UpdateFileMeta(fileMeta)
        // 持久化到数据库
        _ = meta.UpdateFileMetaToDB(fileMeta)
        r.ParseForm()
        username := r.Form.Get("username")
        ok := dblayer.OnUserFileUploadOK(username, fileMeta.FileSha1, fileMeta.FileName, fileMeta.FileSize)
        if ok {
            // 4. 向客户端返回成功信息/或重定向到一个成功页面
            // http.Redirect(w, r, "/file/upload/suc", http.StatusFound)
            http.Redirect(w, r, "/static/view/home.html", http.StatusFound)
        }else {
            w.Write([]byte("Upload Failed."))
        }
    }
}

秒传接口

handler/handler.go

点击查看代码

// TryFastUploadHandler:尝试秒传接口
func TryFastUploadHandler(w http.ResponseWriter, r *http.Request) {
    r.ParseForm()

    // 1. 解析请求参数
    username := r.Form.Get("username")
    filehash := r.Form.Get("filehash")
    filename := r.Form.Get("filename")
    filesize, _ := strconv.Atoi(r.Form.Get("filesize"))

    // 2. 从文件表中查询相同hash的文件记录
    fileMeta, err := meta.GetFileMetaFromDB(filehash)
    // fmt.Printf("fileMeta: %v\n", fileMeta)
    if err != nil {
        fmt.Println(err.Error())
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    // 3. 查不到则返回秒传失败
    if fileMeta == nil {
        resp := util.RespMsg{
            Code: -1,
            Msg: "秒传失败,请访问普通上传接口",
        }
        w.Write(resp.JSONBytes())
        return
    }
    // 4. 上传过则将文件信息写入用户文件表,返回成功
    ok := dblayer.OnUserFileUploadOK(username, filehash, filename, int64(filesize))
    if ok {
        resp := util.RespMsg{
            Code: 0,
            Msg: "秒传成功!",
        }
        w.Write(resp.JSONBytes())
        return
    }

    resp := util.RespMsg{
        Code: -2,
        Msg: "秒传失败,请稍后重传!",
    }
    w.Write(resp.JSONBytes())
    return
}

修改表结构

alter table tbl_user_file drop index idx_user_file;

postman调试

Golang仿云盘项目-5.用户上传/查询文件/秒传

Golang仿云盘项目-5.用户上传/查询文件/秒传

Original: https://www.cnblogs.com/cenjw/p/16496050.html
Author: micromatrix
Title: Golang仿云盘项目-5.用户上传/查询文件/秒传

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/712524/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球