package admin import ( "errors" "fmt" "mime/multipart" "os" "path/filepath" "time" "quyun/app/models" "quyun/database/schemas/public/model" "quyun/pkg/utils" "quyun/providers/ali" "quyun/providers/app" "github.com/gofiber/fiber/v3" log "github.com/sirupsen/logrus" ) // @provider type uploads struct { app *app.Config ali *ali.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/admin/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/admin/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(), md5+filepath.Ext(body.Filename)) if err := os.Rename(targetFile, targetPath); err != nil { return err } fState, err := os.Stat(targetPath) if err != nil { return err } model := &model.Medias{ CreatedAt: time.Now(), Name: body.Filename, MimeType: body.Mime, Size: fState.Size(), // Updated to use fState.Size() Path: targetPath, } // save to db if err := models.Medias.Create(ctx.Context(), model); err != nil { return err } log.Infof("File %s uploaded successfully", body.Filename) return nil } // Token // @Router /v1/admin/uploads/token [get] func (up *uploads) Token(ctx fiber.Ctx) (*ali.PolicyToken, error) { return up.ali.GetToken("quyun") }