目录
一、文件上传
二、文件下载
三、文件删除
4、文件共享
服务之间的通信都是用tcp,定义好数据的结构即可,在其他文章提及过
客户端使用QT编写,在上传文件过程中会首先会查询服务器是否有相同md5值文件,有则上传文件的基本数据
上传时会预先判断文件的大小,如果大于指定数值则对文件进行分割上传。
如果存在相同md5值文件,服务器只需数据库查询对应文件信息将其virtualDataID复制到新文件信息,完成此次文件上传
当所有文件块上传完毕后,服务器会合并数据并再次分割成适合存储元数据的大小,并生成对应的虚拟数据,再由虚拟数据根据冗余等级生成对应的元数据
经过改进版一致性Hash计算出对应元数据对应Filenode并进行存储
// 服务端处理新建文件
func ServerNewFileInsert(filedata Filedata, Tcpconn *TcpConn) error {
// var file Filedata
// err := json.Unmarshal(data, &file)
// if err != nil {
// return err
// }
var err error
Userid := filedata.UserId
//判断用户文件路径树书否存在 不存在则读取mysql生成
f, exit := UserFileMap[Userid]
if !exit {
f, err = BuildUserFileList(Userid)
if err != nil {
fmt.Println(err)
return err
}
UserFileMap[Userid] = f
}
// 查看用户文件夹是否有相同名称
file, err := File.ReturnPathFolder(filedata.Path, f)
if err != nil {
fmt.Println(err)
return err
}
//判断文件路径是否存在 判断文件名是否符合规则
same := false
for i := 0; i < len(file.FileNode); i++ {
if file.FileNode[i].FileName == filedata.Name {
same = true
break
}
}
if same {
fmt.Println("相同名称")
return fmt.Errorf("相同名称")
}
// 如果为文件
if filedata.Type == 0 {
// 查询是否有相同md5值的文件 如果存在直接拷贝其虚拟文件
filemd5 := filedata.Md5
copyfi, err := msql.SearchSameMd5File(filemd5)
fmt.Println(filemd5, copyfi)
if err != nil {
return err
}
filedata.CreateTime = time.Now().Format("2006-01-02 15:04:05")
// 存在 则直接复制一份
if copyfi != nil {
fmt.Println("相同md5直接复制")
filedata.FileId = GetId()
filedata.Size = copyfi.Size
filedata.VirtualDataId = copyfi.VirtualDataId
filedata.LastNodeId = file.FileId
file1 := File.NewFile()
file1.FilePath = filedata.Path
file1.FileName = filedata.Name
file1.FileLastNode = file
file1.FileId = GetId()
file1.Userid = filedata.UserId
file1.FileStatus = true
file1.FileType = 0
file1.FileMd5 = filedata.Md5
file1.FileCreateTime = filedata.CreateTime
file1.FileSize = copyfi.Size
file1.FileVirtualDataId = copyfi.VirtualDataId
file.FileNode = append(file.FileNode, file1)
fildata := FileToFileData(file)
fildata1 := FileToFileData(file1)
// 数据库记录文件列表
err = msql.FileDataInsert(*fildata1)
if err != nil {
fmt.Println("file", err)
return err
}
// 数据库更新文件
err = msql.FileDataUpdate(*fildata)
if err != nil {
fmt.Println(fildata1)
fmt.Println(err)
return err
}
RefreshFolder(filedata.Path, Tcpconn)
return nil
}
// 判断文件大小是否需要分割成小文件 需要则分割
// 生成虚拟数据 与 文件数据
//默认分割一份
filebyte := make([][]byte, 0) //默认
FileSize := len(filedata.Data)
// 如果大小大于5M 则进行分割
//fmt.Println(len(filedata.Data), "文件大小")
LimitSize := 5 * 1024 * 1024
if FileSize > LimitSize {
splidata := ByteSplit(filedata.Data, LimitSize)
filebyte = append(filebyte, splidata...)
// fmt.Println(len(filebyte))
} else {
filebyte = append(filebyte, filedata.Data)
}
virtualdatas := make([]VirtualData, 0)
FileId := GetId()
virtualids := make([]string, 0)
for a := 0; a < len(filebyte); a++ {
var virtualdata VirtualData
virtualdata.VirulaDataId = GetId()
virtualdata.Md5 = Md5(filebyte[a])
virtualdata.CreateTime = time.Now().Format("2006-01-02 15:04:05")
virtualdata.Size = len(filebyte[a])
virtualdata.Status = 1
virtualdata.Data = filebyte[a]
virtualdata.Fileindex = a
virtualids = append(virtualids, virtualdata.VirulaDataId)
virtualdatas = append(virtualdatas, virtualdata)
}
file1 := File.NewFile()
file1.FilePath = filedata.Path
file1.FileName = filedata.Name
file1.FileLastNode = file
file1.FileId = GetId()
file1.Userid = filedata.UserId
file1.FileStatus = true
file1.FileSize = filedata.Size
file1.FileType = 0
file1.FileCreateTime = filedata.CreateTime
file1.FileSize = filedata.Size
file1.FileMd5 = filedata.Md5
file1.FileVirtualDataId = strings.Join(virtualids, "--")
file.FileNode = append(file.FileNode, file1)
fildata := FileToFileData(file)
fildata1 := FileToFileData(file1)
// 数据库记录文件列表
err = msql.FileDataInsert(*fildata1)
if err != nil {
fmt.Println("file", err)
return err
}
// 数据库更新文件
err = msql.FileDataUpdate(*fildata)
if err != nil {
fmt.Println(fildata1)
fmt.Println(err)
return err
}
filedata.CreateTime = time.Now().Format("2006-01-02 15:04:05")
filedata.FileId = FileId
filedata.Size = len(filedata.Data)
filedata.Md5 = Md5(filedata.Data)
filedata.VirtualDataId = strings.Join(virtualids, "--")
// 数据库记录文件列表
// err = msql.FileDataInsert(filedata)
// if err != nil {
// fmt.Println("file", err)
// return err
// }
// 数据库记录虚拟文件列表 //其中可能出问题 后期改用mysql事务
for a := 0; a < len(filebyte); a++ {
err = msql.VirtualDataInsert(virtualdatas[a])
if err != nil {
fmt.Println(err)
return err
}
}
msgchan := make(chan TaskInfo, 2)
MetaDataNodeInsertByVirtualData(virtualdatas, msgchan)
// 文件 虚拟文件 信息记录完毕
for {
select {
case num := <-msgchan:
fmt.Println("创建文件成功", num.Type)
if Tcpconn != nil {
RefreshFolder(filedata.Path, Tcpconn)
}
return nil
case <-time.After(50 * time.Second):
fmt.Println("超时")
return fmt.Errorf("超时")
}
}
}
return nil
}
服务器直接向在线文件节点读取对应的元数据,并传给客户端
// 下载文件
func DownLoadFile(data *Data, Tcpconn *TcpConn, ID string) {
var file Filedata
err := json.Unmarshal([]byte(data.Data), &file)
if err != nil {
fmt.Println(err)
return
}
filedata, err := msql.ReadFileData(file.FileId)
if err != nil {
fmt.Println(err)
return
}
if filedata.UserId != Tcpconn.Id {
return
}
var DownloadResult DownloadFileResult
if file.Type == 0 {
data, err := ServerReadFile(file.FileId)
if err != nil {
DownloadResult.Result = false
DownloadResult.File = file
bytes, err := json.Marshal(DownloadResult)
if err != nil {
fmt.Println(err)
return
}
var Senddata Data
Senddata.DataType = "DownloadFileResult"
Senddata.Data = string(bytes)
Senddata.Id = ID
bytes, err = json.Marshal(Senddata)
if err != nil {
fmt.Println(err)
return
}
Tcpconn.WriteMsg(bytes)
return
}
// 判断是否需要分段传输
if len(data) > 5*1024*1024 {
fmt.Println("分段传输")
SplitData := ByteSplit(data, 5*1024*1024)
// p := STcpserver.Connmap["001"]
for i := 0; i < len(SplitData); i++ {
var s SegmentFile
s.Data = SplitData[i]
s.Name = file.Name
s.SegmentId = i
s.SegmentNums = len(SplitData)
bytes, err := json.Marshal(s)
if err != nil {
fmt.Println(err)
return
}
var Senddata Data
Senddata.DataType = "DownloadSegMentFileResult"
Senddata.Data = string(bytes)
Senddata.Id = ID
// fmt.Println(s.SegmentId, s.SegmentNums)
bytes, err = json.Marshal(Senddata)
if err != nil {
fmt.Println(err)
return
}
//p.WriteMsg(bytes)
Tcpconn.WriteMsg(bytes)
}
} else {
fmt.Println("直接传输")
DownloadResult.Result = true
DownloadResult.File = file
DownloadResult.File.Data = data
bytes, err := json.Marshal(DownloadResult)
if err != nil {
fmt.Println(err)
return
}
var Senddata Data
Senddata.DataType = "DownloadFileResult"
Senddata.Data = string(bytes)
Senddata.Id = ID
bytes, err = json.Marshal(Senddata)
if err != nil {
fmt.Println(err)
return
}
Tcpconn.WriteMsg(bytes)
}
// fmt.Println(string(bytes))
}
}
服务器删除数据库文件信息,在线文件服务器中的元数据。
// 删除文件
func DeleteFile(data *Data, Tcpconn *TcpConn) {
var file Filedata
err := json.Unmarshal([]byte(data.Data), &file)
if err != nil {
fmt.Println(err)
return
}
// 判断类型 type = 0 文件 type = 1文件夹
if file.Type == 0 {
err = DeleteSingleFile(file, Tcpconn.Id)
if err != nil {
fmt.Println(err)
}
// 用户刷新文件夹
RefreshFolder(file.Path, Tcpconn)
} else if file.Type == 1 {
// fmt.Println("开始删除")
err = DeleteFolder(file, Tcpconn.Id)
if err != nil {
fmt.Println(err)
}
// 用户刷新文件夹
RefreshFolder(file.Path, Tcpconn)
}
}
// 删除文件夹
func DeleteFolder(file Filedata, Userid string) error {
// 数据库查找文件对应的虚拟数据
filedata, err := msql.ReadFileData(file.FileId)
if err != nil {
fmt.Println(err)
return err
}
if filedata == nil {
fmt.Println("未找到该文件夹", file.Name)
return fmt.Errorf("未找到该文件夹")
}
//判断用户文件路径树书否存在 不存在则读取mysql生成
f, exit := UserFileMap[Userid]
if !exit {
f, err = BuildUserFileList(Userid)
if err != nil {
fmt.Println(err)
return err
}
UserFileMap[Userid] = f
}
// 获取上级目录
LastFile, err := File.ReturnPathFolder(filedata.Path, f)
if err != nil {
fmt.Println(err)
return err
}
// 获取当前目录
var CurrentFile *File.File
for i := 0; i < len(LastFile.FileNode); i++ {
if LastFile.FileNode[i].FileId == file.FileId {
CurrentFile = LastFile.FileNode[i]
break
}
}
if CurrentFile == nil {
fmt.Println("没有找到自身指针")
return fmt.Errorf("没找到自身指针")
}
// 对文件遍历
filenodes := make([]*File.File, 0)
for i := 0; i < len(CurrentFile.FileNode); i++ {
filenodes = append(filenodes, CurrentFile.FileNode[i])
//fmt.Printf("%+v\n", filenodes[i])
}
for i := 0; i < len(filenodes); i++ {
// fmt.Printf("%+v\n", filenodes[i], len(filenodes))
if filenodes[i].FileType == 0 {
//fmt.Println(filenodes[i].FileName, len(filenodes))
err = DeleteSingleFile(*FileToFileData(filenodes[i]), Userid)
if err != nil {
fmt.Println(err, "1")
return err
}
} else if filenodes[i].FileType == 1 {
err = DeleteFolder(*FileToFileData(filenodes[i]), Userid)
if err != nil {
fmt.Println(err, "2")
return err
}
}
}
//fmt.Println("删除", file.Name)
// 上级目录删除本文件信息
for i := 0; i < len(LastFile.FileNode); i++ {
if LastFile.FileNode[i].FileId == file.FileId {
LastFile.FileNode = append(LastFile.FileNode[0:i], LastFile.FileNode[i+1:]...)
break
}
}
// 修改上级目录数据库信息
f1 := FileToFileData(LastFile)
err = msql.FileDataUpdate(*f1)
if err != nil {
return err
}
// 数据库删除文件信息
err = msql.FileDataDelete(file.FileId)
if err != nil {
return err
}
return nil
}
// 删除单文件
func DeleteSingleFile(file Filedata, Userid string) error {
return ServerDeleteFile(file.FileId, Userid)
}
// 服务删除文件
func ServerDeleteFile(Fileid string, Userid string) error {
// 数据库查找文件对应的虚拟数据
file, err := msql.ReadFileData(Fileid)
if err != nil {
return err
}
if file == nil {
fmt.Println("未找到该文件", Fileid)
return fmt.Errorf("未找到该文件")
}
// fmt.Println("删除", file.Name)
//判断用户文件路径树书否存在 不存在则读取mysql生成
f, exit := UserFileMap[Userid]
if !exit {
f, err = BuildUserFileList(Userid)
if err != nil {
fmt.Println(err)
return err
}
UserFileMap[Userid] = f
}
// 获取上级目录
filedata, err := File.ReturnPathFolder(file.Path, f)
if err != nil {
fmt.Println(err)
return err
}
// 上级目录删除本文件信息
for i := 0; i < len(filedata.FileNode); i++ {
if filedata.FileNode[i].FileId == Fileid {
filedata.FileNode = append(filedata.FileNode[0:i], filedata.FileNode[i+1:]...)
break
}
}
// 修改上级目录数据库信息
f1 := FileToFileData(filedata)
err = msql.FileDataUpdate(*f1)
if err != nil {
return err
}
// 数据库删除文件信息
err = msql.FileDataDelete(file.FileId)
if err != nil {
return err
}
// 判断是否有相同的文件
file1, err := msql.SearchSameMd5File(file.Md5)
if err != nil {
return err
}
// 说明有相同的文件只需删除数据库文件即可
if file1 != nil {
fmt.Println("存在相同文件,不删除虚拟数据!")
return nil
}
// 说明没有其他文件
virtualid := strings.Split(file.VirtualDataId, "--")
datas := make([]VirtualData, 0)
for i := 0; i < len(virtualid); i++ {
v, err := msql.ReadVirtualData(virtualid[i])
if err != nil {
fmt.Println(err)
return err
}
datas = append(datas, *v)
}
// 获取到虚拟文件
for i := 0; i < len(virtualid); i++ {
go DeleteVirtualData(virtualid[i])
}
return nil
}
首先客户端先发出共享文件请求,服务器会生成一条链接对应文件,当用户访问链接时服务器读取数据库对应文件数据并返回。Web服务器使用了Go语言的Gin框架
func HandleDownloadFile(c *gin.Context) {
content := c.Query("id")
share, err := msql.GetShareDataByURL(content)
if err != nil {
c.JSON(200, gin.H{"status": 1, "result": "此链接失效1", "message": "Fail"})
return
}
// 查找文件信息
file, err := msql.ReadFileData(share.FileId)
if err != nil {
c.JSON(200, gin.H{"status": 1, "result": "此链接失效2", "message": "Fail"})
return
}
// 下载文件
data, err := ServerReadFile(file.FileId)
if err != nil {
c.JSON(200, gin.H{"status": 1, "result": "此链接失效3", "message": "Fail"})
return
}
c.Writer.WriteHeader(http.StatusOK)
c.Header("Content-Disposition", "attachment; filename="+file.Name)
c.Header("Content-Type", "application/text/plain")
c.Header("Accept-Length", fmt.Sprintf("%d", len(data)))
c.Writer.Write([]byte(data))
}