简易文件系统-用Go语言从零开始设计(三) 文件上传 文件下载 文件删除 文件共享

14 篇文章 1 订阅

目录

一、文件上传

二、文件下载

三、文件删除

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
}

4、文件共享

首先客户端先发出共享文件请求,服务器会生成一条链接对应文件,当用户访问链接时服务器读取数据库对应文件数据并返回。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))
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值