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) }