package transport import ( "io" "net/http" "strconv" "strings" "github.com/gin-gonic/gin" "github.com/geo-platform/tenant-api/internal/ops/app" "github.com/geo-platform/tenant-api/internal/shared/objectstorage" "github.com/geo-platform/tenant-api/internal/shared/response" ) const ( maxObjectStorageUploadBytes = 500 * 1024 * 1024 maxObjectStorageMultipartBytes = maxObjectStorageUploadBytes + 1*1024*1024 ) type moveObjectStorageObjectRequest struct { SourceKey string `json:"source_key" binding:"required"` DestinationKey string `json:"destination_key" binding:"required"` Overwrite bool `json:"overwrite"` DeleteSource *bool `json:"delete_source"` } type createObjectStorageFolderRequest struct { Prefix string `json:"prefix" binding:"required"` } func listObjectStorageObjectsHandler(svc *app.ObjectStorageService) gin.HandlerFunc { return func(c *gin.Context) { size, _ := strconv.Atoi(c.DefaultQuery("size", "100")) result, err := svc.List(c.Request.Context(), app.ObjectStorageListInput{ Prefix: c.Query("prefix"), Keyword: c.Query("keyword"), ContinuationToken: c.Query("continuation_token"), StartAfter: c.Query("start_after"), Recursive: c.Query("recursive") == "true", Size: size, }) if err != nil { response.Error(c, err) return } response.Success(c, result) } } func getObjectStorageUsageHandler(svc *app.ObjectStorageService) gin.HandlerFunc { return func(c *gin.Context) { result, err := svc.Usage(c.Request.Context()) if err != nil { response.Error(c, err) return } response.Success(c, result) } } func getObjectStorageObjectHandler(svc *app.ObjectStorageService) gin.HandlerFunc { return func(c *gin.Context) { result, err := svc.Get(c.Request.Context(), c.Query("key")) if err != nil { response.Error(c, err) return } response.Success(c, result) } } func resolveObjectStorageObjectURLHandler(svc *app.ObjectStorageService) gin.HandlerFunc { return func(c *gin.Context) { urlValue, err := svc.ResolvePublicURL(c.Request.Context(), c.Query("key")) if err != nil { response.Error(c, err) return } response.Success(c, gin.H{"url": urlValue}) } } func resolveObjectStorageObjectDownloadURLHandler(svc *app.ObjectStorageService) gin.HandlerFunc { return func(c *gin.Context) { urlValue, err := svc.ResolveDownloadURL(c.Request.Context(), c.Query("key")) if err != nil { response.Error(c, err) return } response.Success(c, gin.H{"url": urlValue}) } } func downloadObjectStorageObjectHandler(svc *app.ObjectStorageService) gin.HandlerFunc { return func(c *gin.Context) { key := c.Query("key") content, info, err := svc.Download(c.Request.Context(), key) if err != nil { response.Error(c, err) return } c.Header("Cache-Control", "no-store") c.Header("Content-Disposition", objectStorageDownloadDisposition(key)) c.Data(http.StatusOK, objectStorageDownloadContentType(info, content), content) } } func uploadObjectStorageObjectHandler(svc *app.ObjectStorageService) gin.HandlerFunc { return func(c *gin.Context) { if c.Request.ContentLength > maxObjectStorageMultipartBytes { response.Error(c, response.ErrBadRequest(40095, "object_storage_file_too_large", "单个对象不能超过 500MB")) return } c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxObjectStorageMultipartBytes) fileHeader, err := c.FormFile("file") if err != nil { if strings.Contains(err.Error(), "request body too large") { response.Error(c, response.ErrBadRequest(40095, "object_storage_file_too_large", "单个对象不能超过 500MB")) return } response.Error(c, response.ErrBadRequest(40094, "object_storage_file_required", "请选择要上传的文件")) return } file, err := fileHeader.Open() if err != nil { response.Error(c, response.ErrBadRequest(40094, "object_storage_file_open_failed", "文件读取失败")) return } defer file.Close() content, err := io.ReadAll(io.LimitReader(file, maxObjectStorageUploadBytes+1)) if err != nil { response.Error(c, response.ErrBadRequest(40094, "object_storage_file_read_failed", "文件读取失败")) return } if len(content) > maxObjectStorageUploadBytes { response.Error(c, response.ErrBadRequest(40095, "object_storage_file_too_large", "单个对象不能超过 500MB")) return } result, err := svc.Upload(c.Request.Context(), actorFromGin(c), app.ObjectStorageUploadInput{ ObjectKey: c.PostForm("key"), FileName: fileHeader.Filename, Content: content, ContentType: c.PostForm("content_type"), Overwrite: c.PostForm("overwrite") == "true", }) if err != nil { response.Error(c, err) return } response.Success(c, result) } } func moveObjectStorageObjectHandler(svc *app.ObjectStorageService) gin.HandlerFunc { return func(c *gin.Context) { var body moveObjectStorageObjectRequest if err := c.ShouldBindJSON(&body); err != nil { response.Error(c, response.ErrBadRequest(40000, "invalid_payload", err.Error())) return } deleteSource := true if body.DeleteSource != nil { deleteSource = *body.DeleteSource } result, err := svc.Move(c.Request.Context(), actorFromGin(c), app.ObjectStorageMoveInput{ SourceKey: body.SourceKey, DestinationKey: body.DestinationKey, Overwrite: body.Overwrite, DeleteSource: deleteSource, }) if err != nil { response.Error(c, err) return } response.Success(c, result) } } func deleteObjectStorageObjectHandler(svc *app.ObjectStorageService) gin.HandlerFunc { return func(c *gin.Context) { key := c.Query("key") if err := svc.Delete(c.Request.Context(), actorFromGin(c), key); err != nil { response.Error(c, err) return } response.Success(c, gin.H{"key": key}) } } func createObjectStorageFolderHandler(svc *app.ObjectStorageService) gin.HandlerFunc { return func(c *gin.Context) { var body createObjectStorageFolderRequest if err := c.ShouldBindJSON(&body); err != nil { response.Error(c, response.ErrBadRequest(40000, "invalid_payload", err.Error())) return } result, err := svc.CreateFolder(c.Request.Context(), actorFromGin(c), app.ObjectStorageFolderCreateInput{ Prefix: body.Prefix, }) if err != nil { response.Error(c, err) return } response.Success(c, result) } } func deleteObjectStorageFolderHandler(svc *app.ObjectStorageService) gin.HandlerFunc { return func(c *gin.Context) { prefix := c.Query("prefix") result, err := svc.DeleteFolder(c.Request.Context(), actorFromGin(c), prefix) if err != nil { response.Error(c, err) return } response.Success(c, result) } } func objectStorageDownloadDisposition(key string) string { return objectstorage.DownloadContentDisposition(key) } func objectStorageDownloadContentType(info *objectstorage.ObjectInfo, content []byte) string { if info != nil && strings.TrimSpace(info.ContentType) != "" { return info.ContentType } if len(content) > 0 { return http.DetectContentType(content) } return "application/octet-stream" }