package http import ( "errors" "fmt" "mime/multipart" "os" "path/filepath" "quyun/pkg/utils" "quyun/providers/app" "github.com/gofiber/fiber/v3" log "github.com/sirupsen/logrus" ) // @provider type uploads struct { app *app.Config } func (up *uploads) storagePath() string { return filepath.Join(up.app.StoragePath, "uploads") } type UploadChunk struct { Chunk int `query:"chunk"` Md5 string `query:"md5"` } type UploadFileInfo struct { Md5 string `json:"md5"` Filename string `json:"filename"` Mime string `json:"mime"` Chunks int `json:"chunks"` } // Upload chunks // @Router /v1/uploads/:md5/chunks/:idx [post] // @Bind md5 path // @Bind idx path // @Bind file file func (up *uploads) Chunks(ctx fiber.Ctx, md5, idx string, file *multipart.FileHeader) error { tmpPath := filepath.Join(up.storagePath(), md5, idx) // if tmpPath not exists, create it if _, err := os.Stat(tmpPath); os.IsNotExist(err) { if err := os.MkdirAll(filepath.Dir(tmpPath), os.ModePerm); err != nil { log.WithError(err).Errorf("create tmpPath failed %s", tmpPath) return err } } // save file to tmpPath if err := ctx.SaveFile(file, tmpPath); err != nil { log.WithError(err).Errorf("save file to tmpPath failed %s", tmpPath) return err } return nil } // Complete uploads // @Router /v1/uploads/:md5/complete [post] // @Bind md5 path // @Bind body body func (up *uploads) Complete(ctx fiber.Ctx, md5 string, body *UploadFileInfo) error { // merge chunks path := filepath.Join(up.storagePath(), md5) defer os.RemoveAll(path) targetFile := filepath.Join(up.storagePath(), md5, body.Filename) // if targetFile not exists, create it tf, err := os.Create(targetFile) if err != nil { return err } for i := 0; i < body.Chunks; i++ { tmpPath := filepath.Join(up.storagePath(), md5, fmt.Sprintf("%d", i)) // open chunk file chunkFile, err := os.Open(tmpPath) if err != nil { tf.Close() return err } // copy chunk file to target file if _, err := tf.ReadFrom(chunkFile); err != nil { chunkFile.Close() tf.Close() return err } chunkFile.Close() } tf.Close() // validate md5 ok, err := utils.CompareFileMd5(targetFile, md5) if err != nil { return err } if !ok { return errors.New("md5 not match") } // save file to target path targetPath := filepath.Join(up.storagePath(), body.Filename) if err := os.Rename(targetFile, targetPath); err != nil { return err } // TODO: save file to database return nil }