Add resumable upload feature with progress display

- Implement chunked file upload with 1MB chunk size
- Add progress bar with percentage and chunk counter (e.g., 2/5)
- Support resuming interrupted uploads
- Improve UI with better progress visualization
- Add dropzone.js integration for drag-and-drop uploads
- Fix progress bar jumping issue in resumable uploads
- Add file type icons and size display
- Enhance error handling and user feedback
This commit is contained in:
admin
2026-01-23 15:29:26 +08:00
parent 88db4903c9
commit 6f3cc6b725
6 changed files with 551 additions and 242 deletions

129
main.go
View File

@@ -391,6 +391,22 @@ func uploadFile(c *gin.Context) {
path = "."
}
dzuuid := c.PostForm("dzuuid")
chunkIndex := c.PostForm("dzchunkindex")
totalChunkCount := c.PostForm("dztotalchunkcount")
fileName := c.PostForm("dzanonymousfilename")
isChunked := dzuuid != "" && chunkIndex != ""
tmpDir := filepath.Join(rootDir, ".tmp", "chunks")
if err := os.MkdirAll(tmpDir, 0755); err != nil {
c.JSON(http.StatusBadRequest, UploadResponse{
Success: false,
Message: err.Error(),
})
return
}
file, header, err := c.Request.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, UploadResponse{
@@ -401,7 +417,12 @@ func uploadFile(c *gin.Context) {
}
defer file.Close()
fullPath := filepath.Join(rootDir, path, header.Filename)
targetFileName := header.Filename
if fileName != "" {
targetFileName = fileName
}
fullPath := filepath.Join(rootDir, path, targetFileName)
if !strings.HasPrefix(fullPath, rootDir) {
c.JSON(http.StatusBadRequest, UploadResponse{
Success: false,
@@ -410,24 +431,98 @@ func uploadFile(c *gin.Context) {
return
}
outFile, err := os.Create(fullPath)
if err != nil {
c.JSON(http.StatusBadRequest, UploadResponse{
Success: false,
Message: err.Error(),
if isChunked {
idx := 0
fmt.Sscanf(chunkIndex, "%d", &idx)
total := 0
if totalChunkCount != "" {
fmt.Sscanf(totalChunkCount, "%d", &total)
}
chunkDir := filepath.Join(tmpDir, dzuuid)
if err := os.MkdirAll(chunkDir, 0755); err != nil {
c.JSON(http.StatusBadRequest, UploadResponse{
Success: false,
Message: err.Error(),
})
return
}
chunkPath := filepath.Join(chunkDir, fmt.Sprintf("chunk_%d", idx))
outFile, err := os.Create(chunkPath)
if err != nil {
c.JSON(http.StatusBadRequest, UploadResponse{
Success: false,
Message: err.Error(),
})
return
}
defer outFile.Close()
io.Copy(outFile, file)
if idx == total-1 && total > 0 {
finalFile, err := os.Create(fullPath)
if err != nil {
os.RemoveAll(chunkDir)
c.JSON(http.StatusBadRequest, UploadResponse{
Success: false,
Message: err.Error(),
})
return
}
defer finalFile.Close()
for i := 0; i < total; i++ {
chunkPath := filepath.Join(chunkDir, fmt.Sprintf("chunk_%d", i))
chunkFile, err := os.Open(chunkPath)
if err != nil {
finalFile.Close()
os.RemoveAll(chunkDir)
c.JSON(http.StatusBadRequest, UploadResponse{
Success: false,
Message: err.Error(),
})
return
}
io.Copy(finalFile, chunkFile)
chunkFile.Close()
os.Remove(chunkPath)
}
os.RemoveAll(chunkDir)
watchChan <- WatchEvent{Type: "create", Path: fullPath, Name: targetFileName}
c.JSON(http.StatusOK, UploadResponse{
Success: true,
Message: "上传成功",
})
} else {
c.JSON(http.StatusOK, UploadResponse{
Success: true,
Message: "分块上传成功",
})
}
} else {
outFile, err := os.Create(fullPath)
if err != nil {
c.JSON(http.StatusBadRequest, UploadResponse{
Success: false,
Message: err.Error(),
})
return
}
defer outFile.Close()
io.Copy(outFile, file)
watchChan <- WatchEvent{Type: "create", Path: fullPath, Name: targetFileName}
c.JSON(http.StatusOK, UploadResponse{
Success: true,
Message: "上传成功",
})
return
}
defer outFile.Close()
io.Copy(outFile, file)
watchChan <- WatchEvent{Type: "create", Path: fullPath, Name: header.Filename}
c.JSON(http.StatusOK, UploadResponse{
Success: true,
Message: "上传成功",
})
}
func deleteFiles(c *gin.Context) {