main.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. package main
  2. import (
  3. "fmt"
  4. "io"
  5. "net/http"
  6. "os"
  7. "path/filepath"
  8. "strconv"
  9. "sync"
  10. "github.com/gin-gonic/gin"
  11. )
  12. const uploadPath = "C:\\Users\\lx\\GolandProjects\\demo\\fileupload\\uploads"
  13. const progressFilePath = "C:\\Users\\lx\\GolandProjects\\demo\\fileupload\\uploads\\progress"
  14. var mu sync.Mutex // 使用互斥锁来确保并发时的进度文件读写安全
  15. func main() {
  16. router := gin.Default()
  17. // 确保上传和进度记录目录存在
  18. err := os.MkdirAll(uploadPath, os.ModePerm)
  19. if err != nil {
  20. panic(err)
  21. }
  22. err = os.MkdirAll(progressFilePath, os.ModePerm)
  23. if err != nil {
  24. panic(err)
  25. }
  26. // 定义分块上传接口,支持断点续传
  27. router.POST("/upload", upload)
  28. router.Run(":8080")
  29. }
  30. func upload(c *gin.Context) {
  31. // 获取上传的文件块
  32. file, header, err := c.Request.FormFile("file")
  33. if err != nil {
  34. c.String(http.StatusBadRequest, "Failed to get file: %s", err.Error())
  35. return
  36. }
  37. defer file.Close()
  38. // 获取文件块编号和总块数
  39. chunkIndex := c.PostForm("chunkIndex") // 当前块编号
  40. totalChunks := c.PostForm("totalChunks") // 总块数
  41. fileID := c.PostForm("fileID") // 文件标识符
  42. chunkIdx, err := strconv.Atoi(chunkIndex)
  43. if err != nil {
  44. c.String(http.StatusBadRequest, "Invalid chunk index")
  45. return
  46. }
  47. totalChunksNum, err := strconv.Atoi(totalChunks)
  48. if err != nil {
  49. c.String(http.StatusBadRequest, "Invalid total chunks")
  50. return
  51. }
  52. // 先检查该文件的进度文件,看看该块是否已经上传
  53. if isChunkUploaded(fileID, chunkIdx) {
  54. c.String(http.StatusOK, "Chunk %d already uploaded", chunkIdx)
  55. return
  56. }
  57. // 构造临时文件路径
  58. tempFilePath := filepath.Join(uploadPath, fmt.Sprintf("%s.tmp", fileID))
  59. // 打开或创建临时文件
  60. tempFile, err := os.OpenFile(tempFilePath, os.O_CREATE|os.O_WRONLY, os.ModePerm)
  61. if err != nil {
  62. c.String(http.StatusInternalServerError, "Failed to create temp file: %s", err.Error())
  63. return
  64. }
  65. defer tempFile.Close()
  66. // 计算文件块的偏移量
  67. chunkSize := header.Size
  68. offset := int64(chunkIdx) * chunkSize
  69. // 定位文件指针到正确的偏移量并写入块数据
  70. _, err = tempFile.Seek(offset, 0)
  71. if err != nil {
  72. c.String(http.StatusInternalServerError, "Failed to seek in temp file: %s", err.Error())
  73. return
  74. }
  75. // 将块写入临时文件
  76. _, err = io.Copy(tempFile, file)
  77. if err != nil {
  78. c.String(http.StatusInternalServerError, "Failed to write to temp file: %s", err.Error())
  79. return
  80. }
  81. // 记录该块已上传
  82. recordChunkProgress(fileID, chunkIdx)
  83. // 当所有块上传完毕时,将临时文件重命名为最终的文件
  84. if chunkIdx == totalChunksNum-1 {
  85. tempFile.Close()
  86. finalFilePath := filepath.Join(uploadPath, header.Filename)
  87. err = os.Rename(tempFilePath, finalFilePath)
  88. if err != nil {
  89. c.String(http.StatusInternalServerError, "Failed to finalize file: %s", err.Error())
  90. return
  91. }
  92. // 上传完成后删除进度记录文件
  93. deleteProgressFile(fileID)
  94. c.String(http.StatusOK, "Upload complete: %s", finalFilePath)
  95. } else {
  96. c.String(http.StatusOK, "Chunk %d of %d uploaded", chunkIdx+1, totalChunksNum)
  97. }
  98. }
  99. // 检查该文件的某个块是否已经上传
  100. func isChunkUploaded(fileID string, chunkIndex int) bool {
  101. progressFile := filepath.Join(progressFilePath, fileID+".progress")
  102. mu.Lock()
  103. defer mu.Unlock()
  104. if _, err := os.Stat(progressFile); os.IsNotExist(err) {
  105. return false
  106. }
  107. file, err := os.Open(progressFile)
  108. if err != nil {
  109. return false
  110. }
  111. defer file.Close()
  112. // 读取进度文件中的内容
  113. var uploadedChunk int
  114. for {
  115. _, err := fmt.Fscanf(file, "%d\n", &uploadedChunk)
  116. if err != nil {
  117. break
  118. }
  119. if uploadedChunk == chunkIndex {
  120. return true
  121. }
  122. }
  123. return false
  124. }
  125. // 记录某个文件的某块已经上传
  126. func recordChunkProgress(fileID string, chunkIndex int) {
  127. progressFile := filepath.Join(progressFilePath, fileID+".progress")
  128. mu.Lock()
  129. defer mu.Unlock()
  130. file, err := os.OpenFile(progressFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, os.ModePerm)
  131. if err != nil {
  132. fmt.Println("Failed to open progress file:", err)
  133. return
  134. }
  135. defer file.Close()
  136. _, err = fmt.Fprintf(file, "%d\n", chunkIndex)
  137. if err != nil {
  138. fmt.Println("Failed to write to progress file:", err)
  139. }
  140. }
  141. // 删除进度文件(在文件上传完成后)
  142. func deleteProgressFile(fileID string) {
  143. progressFile := filepath.Join(progressFilePath, fileID+".progress")
  144. mu.Lock()
  145. defer mu.Unlock()
  146. os.Remove(progressFile)
  147. }