From 71a5ea5f41045f213ae222b829c9e053668a9f14 Mon Sep 17 00:00:00 2001 From: root <1772105645@qq.com> Date: Fri, 19 Dec 2025 16:32:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=87=E4=BB=B6=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 2 ++ cmd/server/main.go | 7 +++- internal/api/endpoints/file_endpoint.go | 36 +++++++++++++++++++ internal/api/handlers/query_handlers.go | 25 ++++++++++++- .../api/requests/new_features_requests.go | 5 +++ .../api/validators/new_features_validators.go | 7 ++++ internal/domain/repository/file_repository.go | 7 ++-- .../infrastructure/s3/file_repository_impl.go | 9 +++++ web/index.html | 18 +++++++++- 9 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/cmd/server/main.go b/cmd/server/main.go index 95edbad..25b634a 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -41,6 +41,7 @@ func main() { initMultipartHandler := handlers.NewInitMultipartHandler(s3Repo) uploadPartHandler := handlers.NewUploadPartHandler(s3Repo) completeMultipartHandler := handlers.NewCompleteMultipartHandler(s3Repo) + deleteFileHandler := handlers.NewDeleteFileHandler(s3Repo) // Register Handlers mediator.Register[handlers.UploadFileCommand, string](m, uploadHandler) @@ -53,6 +54,7 @@ func main() { mediator.Register[handlers.InitMultipartCommand, string](m, initMultipartHandler) mediator.Register[handlers.UploadPartCommand, string](m, uploadPartHandler) mediator.Register[handlers.CompleteMultipartCommand, string](m, completeMultipartHandler) + mediator.Register[handlers.DeleteFileCommand, string](m, deleteFileHandler) // Validators uploadValidator := validators.NewUploadFileValidator() @@ -72,7 +74,7 @@ func main() { c.Writer.Header().Set("Access-Control-Allow-Origin", "*") c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") - c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT") + c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return @@ -87,6 +89,9 @@ func main() { r.GET("/files/list", fileEndpoint.ListFiles) r.GET("/files/preview", fileEndpoint.GetPreviewURL) + // Delete file + r.DELETE("/files/delete", fileEndpoint.DeleteFile) + // Multipart Upload r.POST("/files/multipart/init", fileEndpoint.InitMultipart) r.PUT("/files/multipart/part", fileEndpoint.UploadPart) diff --git a/internal/api/endpoints/file_endpoint.go b/internal/api/endpoints/file_endpoint.go index b7e628d..a20a384 100644 --- a/internal/api/endpoints/file_endpoint.go +++ b/internal/api/endpoints/file_endpoint.go @@ -321,6 +321,42 @@ func (e *FileEndpoint) CompleteMultipart(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"location": result}) } +// DeleteFile godoc +// @Summary 删除文件 +// @Description 从指定的存储桶删除文件 +// @Tags 文件操作 +// @Accept json +// @Produce json +// @Param request body requests.DeleteFileRequest true "请求参数" +// @Success 200 {object} map[string]string "删除成功消息" +// @Failure 400 {object} map[string]string "参数错误" +// @Failure 500 {object} map[string]string "服务器内部错误" +// @Router /files/delete [delete] +func (e *FileEndpoint) DeleteFile(c *gin.Context) { + var req requests.DeleteFileRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := e.NewFeaturesValidator.ValidateDeleteFile(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + cmd := handlers.DeleteFileCommand{ + BucketName: req.BucketName, + ObjectKey: req.ObjectKey, + } + + result, err := mediator.Send[handlers.DeleteFileCommand, string](e.Mediator, c.Request.Context(), cmd) + if err != nil { + handleError(c, err) + return + } + c.JSON(http.StatusOK, gin.H{"message": result}) +} + func handleError(c *gin.Context, err error) { if be, ok := err.(*common.BusinessException); ok { c.JSON(be.Code, gin.H{"error": be.Message}) diff --git a/internal/api/handlers/query_handlers.go b/internal/api/handlers/query_handlers.go index be864bc..b622262 100644 --- a/internal/api/handlers/query_handlers.go +++ b/internal/api/handlers/query_handlers.go @@ -2,9 +2,10 @@ package handlers import ( "context" + "file-system/internal/common" + "file-sys "file-system/internal/domain/repository" "io" - "time" "file-system/internal/common" ) @@ -67,3 +68,25 @@ func NewGetFilePreviewHandler(repo repository.FileRepository) *GetFilePreviewHan func (h *GetFilePreviewHandler) Handle(ctx context.Context, q GetFilePreviewQuery) (string, error) { return h.Repo.GeneratePresignedURL(ctx, q.BucketName, q.ObjectKey, q.Expiry) } + +// DeleteFileCommand 删除文件命令 +type DeleteFileCommand struct { + BucketName string + ObjectKey string +} + +type DeleteFileHandler struct { + Repo repository.FileRepository +} + +func NewDeleteFileHandler(repo repository.FileRepository) *DeleteFileHandler { + return &DeleteFileHandler{Repo: repo} +} + +func (h *DeleteFileHandler) Handle(ctx context.Context, cmd DeleteFileCommand) (string, error) { + err := h.Repo.DeleteFile(ctx, cmd.BucketName, cmd.ObjectKey) + if err != nil { + return "", err + } + return "File deleted successfully", nil +} diff --git a/internal/api/requests/new_features_requests.go b/internal/api/requests/new_features_requests.go index 4a6db39..c7cf52d 100644 --- a/internal/api/requests/new_features_requests.go +++ b/internal/api/requests/new_features_requests.go @@ -36,3 +36,8 @@ type CompleteMultipartRequest struct { UploadId string `json:"upload_id"` Parts []common.Part `json:"parts"` } + +type DeleteFileRequest struct { + BucketName string `json:"bucket_name"` + ObjectKey string `json:"object_key"` +} diff --git a/internal/api/validators/new_features_validators.go b/internal/api/validators/new_features_validators.go index 2978000..eeebfd7 100644 --- a/internal/api/validators/new_features_validators.go +++ b/internal/api/validators/new_features_validators.go @@ -48,3 +48,10 @@ func (v *NewFeaturesValidator) ValidateCompleteMultipart(req *requests.CompleteM } return nil } + +func (v *NewFeaturesValidator) ValidateDeleteFile(req *requests.DeleteFileRequest) error { + if req.BucketName == "" || req.ObjectKey == "" { + return common.NewBusinessException("Bucket name and Object key are required") + } + return nil +} diff --git a/internal/domain/repository/file_repository.go b/internal/domain/repository/file_repository.go index 1bb2859..da64886 100644 --- a/internal/domain/repository/file_repository.go +++ b/internal/domain/repository/file_repository.go @@ -15,7 +15,7 @@ type FileInfo struct { } type ListFilesResult struct { - Files []FileInfo + Files []FileInfo NextContinuationToken *string } @@ -30,7 +30,10 @@ type FileRepository interface { // 新增功能 ListObjectsV2(ctx context.Context, bucketName string, prefix string, maxKeys int32, continuationToken *string) (*ListFilesResult, error) GeneratePresignedURL(ctx context.Context, bucketName string, objectKey string, expiry time.Duration) (string, error) - + + // 删除文件 + DeleteFile(ctx context.Context, bucketName string, objectKey string) error + // 分片上传 CreateMultipartUpload(ctx context.Context, bucketName string, objectKey string) (string, error) UploadPart(ctx context.Context, bucketName string, objectKey string, uploadId string, partNumber int32, data io.Reader) (string, error) diff --git a/internal/infrastructure/s3/file_repository_impl.go b/internal/infrastructure/s3/file_repository_impl.go index 5c671b3..2c37843 100644 --- a/internal/infrastructure/s3/file_repository_impl.go +++ b/internal/infrastructure/s3/file_repository_impl.go @@ -85,6 +85,15 @@ func (r *S3FileRepository) ListObjects(ctx context.Context, bucketName string) ( return objects, nil } +// DeleteFile 删除文件 +func (r *S3FileRepository) DeleteFile(ctx context.Context, bucketName string, objectKey string) error { + _, err := r.client.Client.DeleteObject(ctx, &s3.DeleteObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(objectKey), + }) + return err +} + // ListObjectsV2 分页列出文件 func (r *S3FileRepository) ListObjectsV2(ctx context.Context, bucketName string, prefix string, maxKeys int32, continuationToken *string) (*repository.ListFilesResult, error) { input := &s3.ListObjectsV2Input{ diff --git a/web/index.html b/web/index.html index bf36fa9..da0db22 100644 --- a/web/index.html +++ b/web/index.html @@ -113,6 +113,9 @@ + + +