package handle import ( "allapp-go/internal/errors" "allapp-go/internal/httpx" "allapp-go/internal/types" "allapp-go/pkg/logger" "allapp-go/pkg/s3store" "fmt" "path/filepath" "strconv" "time" "github.com/gofiber/fiber/v3" "github.com/google/uuid" "go.uber.org/zap" ) func S3UploadFile(c fiber.Ctx) error { form, err := c.MultipartForm() if err != nil { return errors.WithStack(err) } files := form.File["files"] if len(files) == 0 { return httpx.OK(c, nil) } now := time.Now() folderPath, err := uploadFolderPath(c.FormValue("upload_type"), c.FormValue("family_id"), now) if err != nil { return httpx.Fail(c, err.Error()) } var result []string for _, fileHeader := range files { file, err := fileHeader.Open() if err != nil { return errors.WithStack(err) } ext := filepath.Ext(fileHeader.Filename) newName := fmt.Sprintf("%s%s", uuid.NewString(), ext) key := fmt.Sprintf("%s/%s", folderPath, newName) contentType := fileHeader.Header.Get("Content-Type") err = s3store.UploadToRustFS( c.Context(), file, key, fileHeader.Size, contentType, ) _ = file.Close() if err != nil { return errors.WithStack(err) } result = append(result, key) } return httpx.OK(c, result) } func S3CopyFile(c fiber.Ctx) error { var req types.S3CopyReq if err := httpx.BindAndValidate(c, &req); err != nil { return errors.WithStack(err) } if len(req.Keys) == 0 { return httpx.OK(c, []string{}) } folderPath := familyDateFolder(req.FamilyID, time.Now()) result := make([]string, 0, len(req.Keys)) for _, sourceKey := range req.Keys { ext := filepath.Ext(sourceKey) if ext == "" { ext = ".bin" } targetKey := fmt.Sprintf("%s/%s%s", folderPath, uuid.NewString(), ext) if err := s3store.CopyObject(c.Context(), sourceKey, targetKey); err != nil { return errors.WithStack(err) } result = append(result, targetKey) } return httpx.OK(c, result) } func S3DeleteFile(c fiber.Ctx) error { var req types.S3DeleteReq if err := httpx.BindAndValidate(c, &req); err != nil { return errors.WithStack(err) } log := logger.FromCtx(c.Context()) if err := s3store.DeleteBatch(c.Context(), req.Keys); err != nil { log.Warn("S3 批量删除失败", zap.Int("count", len(req.Keys)), zap.Error(err), ) // ❗仍然返回成功(符合你的要求) } return httpx.OK(c, nil) } func parseFamilyID(value string) (int64, error) { familyID, err := strconv.ParseInt(value, 10, 64) if err != nil || familyID <= 0 { return 0, fmt.Errorf("invalid family_id") } return familyID, nil } func familyDateFolder(familyID int64, now time.Time) string { return fmt.Sprintf("%d/%d/%02d/%02d", familyID, now.Year(), now.Month(), now.Day(), ) } func avatarDateFolder(now time.Time) string { return fmt.Sprintf("avatar/%d/%02d/%02d", now.Year(), now.Month(), now.Day(), ) } func uploadFolderPath(uploadType string, familyIDValue string, now time.Time) (string, error) { if uploadType == "avatar" { return avatarDateFolder(now), nil } familyID, err := parseFamilyID(familyIDValue) if err != nil { return "", fmt.Errorf("family_id不能为空") } return familyDateFolder(familyID, now), nil }