123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 |
- package main
- import (
- "fmt"
- "io"
- "net/http"
- "os"
- "path/filepath"
- "strconv"
- "sync"
- "github.com/gin-gonic/gin"
- )
- const uploadPath = "C:\\Users\\lx\\GolandProjects\\demo\\fileupload\\uploads"
- const progressFilePath = "C:\\Users\\lx\\GolandProjects\\demo\\fileupload\\uploads\\progress"
- var mu sync.Mutex // 使用互斥锁来确保并发时的进度文件读写安全
- func main() {
- router := gin.Default()
- // 确保上传和进度记录目录存在
- err := os.MkdirAll(uploadPath, os.ModePerm)
- if err != nil {
- panic(err)
- }
- err = os.MkdirAll(progressFilePath, os.ModePerm)
- if err != nil {
- panic(err)
- }
- // 定义分块上传接口,支持断点续传
- router.POST("/upload", upload)
- router.Run(":8080")
- }
- func upload(c *gin.Context) {
- // 获取上传的文件块
- file, header, err := c.Request.FormFile("file")
- if err != nil {
- c.String(http.StatusBadRequest, "Failed to get file: %s", err.Error())
- return
- }
- defer file.Close()
- // 获取文件块编号和总块数
- chunkIndex := c.PostForm("chunkIndex") // 当前块编号
- totalChunks := c.PostForm("totalChunks") // 总块数
- fileID := c.PostForm("fileID") // 文件标识符
- chunkIdx, err := strconv.Atoi(chunkIndex)
- if err != nil {
- c.String(http.StatusBadRequest, "Invalid chunk index")
- return
- }
- totalChunksNum, err := strconv.Atoi(totalChunks)
- if err != nil {
- c.String(http.StatusBadRequest, "Invalid total chunks")
- return
- }
- // 先检查该文件的进度文件,看看该块是否已经上传
- if isChunkUploaded(fileID, chunkIdx) {
- c.String(http.StatusOK, "Chunk %d already uploaded", chunkIdx)
- return
- }
- // 构造临时文件路径
- tempFilePath := filepath.Join(uploadPath, fmt.Sprintf("%s.tmp", fileID))
- // 打开或创建临时文件
- tempFile, err := os.OpenFile(tempFilePath, os.O_CREATE|os.O_WRONLY, os.ModePerm)
- if err != nil {
- c.String(http.StatusInternalServerError, "Failed to create temp file: %s", err.Error())
- return
- }
- defer tempFile.Close()
- // 计算文件块的偏移量
- chunkSize := header.Size
- offset := int64(chunkIdx) * chunkSize
- // 定位文件指针到正确的偏移量并写入块数据
- _, err = tempFile.Seek(offset, 0)
- if err != nil {
- c.String(http.StatusInternalServerError, "Failed to seek in temp file: %s", err.Error())
- return
- }
- // 将块写入临时文件
- _, err = io.Copy(tempFile, file)
- if err != nil {
- c.String(http.StatusInternalServerError, "Failed to write to temp file: %s", err.Error())
- return
- }
- // 记录该块已上传
- recordChunkProgress(fileID, chunkIdx)
- // 当所有块上传完毕时,将临时文件重命名为最终的文件
- if chunkIdx == totalChunksNum-1 {
- tempFile.Close()
- finalFilePath := filepath.Join(uploadPath, header.Filename)
- err = os.Rename(tempFilePath, finalFilePath)
- if err != nil {
- c.String(http.StatusInternalServerError, "Failed to finalize file: %s", err.Error())
- return
- }
- // 上传完成后删除进度记录文件
- deleteProgressFile(fileID)
- c.String(http.StatusOK, "Upload complete: %s", finalFilePath)
- } else {
- c.String(http.StatusOK, "Chunk %d of %d uploaded", chunkIdx+1, totalChunksNum)
- }
- }
- // 检查该文件的某个块是否已经上传
- func isChunkUploaded(fileID string, chunkIndex int) bool {
- progressFile := filepath.Join(progressFilePath, fileID+".progress")
- mu.Lock()
- defer mu.Unlock()
- if _, err := os.Stat(progressFile); os.IsNotExist(err) {
- return false
- }
- file, err := os.Open(progressFile)
- if err != nil {
- return false
- }
- defer file.Close()
- // 读取进度文件中的内容
- var uploadedChunk int
- for {
- _, err := fmt.Fscanf(file, "%d\n", &uploadedChunk)
- if err != nil {
- break
- }
- if uploadedChunk == chunkIndex {
- return true
- }
- }
- return false
- }
- // 记录某个文件的某块已经上传
- func recordChunkProgress(fileID string, chunkIndex int) {
- progressFile := filepath.Join(progressFilePath, fileID+".progress")
- mu.Lock()
- defer mu.Unlock()
- file, err := os.OpenFile(progressFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, os.ModePerm)
- if err != nil {
- fmt.Println("Failed to open progress file:", err)
- return
- }
- defer file.Close()
- _, err = fmt.Fprintf(file, "%d\n", chunkIndex)
- if err != nil {
- fmt.Println("Failed to write to progress file:", err)
- }
- }
- // 删除进度文件(在文件上传完成后)
- func deleteProgressFile(fileID string) {
- progressFile := filepath.Join(progressFilePath, fileID+".progress")
- mu.Lock()
- defer mu.Unlock()
- os.Remove(progressFile)
- }
|