Golang仿云盘项目- 5. 用户上传/查询文件/秒传
秒传原理
TODO
服务架构变迁
较之前的加入了用户文件表、hash计算。
- 唯一文件表:一个文件只存一条记录,文件的filesha1为主键
- 用户文件表:存储每个用户所有文件的元数据
- Hash计算:内潜在上传server里,作为内部逻辑模块存在;也可以单独抽出来作为独立的微服务,向外提供接口
无秒传的流程:
- 【用户】上传文件数据,【上传Server】接收到文件数据流后,
- 它会一边存储在【本地存储】,
- 一边通知【hash计算】模块,该模块计算当前上传那一块文件的哈希值,等到整个文件上传完成之后,就会得到整个文件的哈希值;
- 然后【上传server】将其哈希值写入到【唯一文件表】;
- 最后【上传server】把文件元信息关联到【用户文件表】。
后面用户就可以通过【用户文件表】查询相应文件元信息。
秒传的情况下:
- 没有实际的文件传输了,因为【上传Server】在比对哈希值后发现值相同后会直接忽略3,4步,直接跳到第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调试
本文来自博客园,作者:micromatrix,转载请注明原文链接:https://www.cnblogs.com/cenjw/p/16496050.html
posted on 2022-07-20 21:58 micromatrix 阅读(291) 评论(0) 编辑 收藏 举报