添加文件删除功能
This commit is contained in:
parent
c565eb25af
commit
71a5ea5f41
2
.vscode/settings.json
vendored
Normal file
2
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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})
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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"`
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -31,6 +31,9 @@ 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)
|
||||
|
||||
@ -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{
|
||||
|
||||
@ -113,6 +113,9 @@
|
||||
<span class="action-btn text-success" @click="downloadFile(file.Key)" title="下载">
|
||||
<i class="fas fa-download"></i>
|
||||
</span>
|
||||
<span class="action-btn text-danger" @click="deleteFile(file.Key)" title="删除">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="files.length === 0 && !loadingFiles">
|
||||
@ -493,6 +496,19 @@
|
||||
window.open(url, '_blank');
|
||||
};
|
||||
|
||||
// Delete
|
||||
const deleteFile = async (key) => {
|
||||
if (!confirm(`确定要删除文件 "${key}" 吗?此操作不可恢复!`)) return;
|
||||
try {
|
||||
await api.delete('/files/delete', {
|
||||
data: { bucket_name: currentBucket.value, object_key: key }
|
||||
});
|
||||
loadFilesWrapped(currentToken.value);
|
||||
} catch (err) {
|
||||
alert('删除失败: ' + (err.response?.data?.error || err.message));
|
||||
}
|
||||
};
|
||||
|
||||
// Utils
|
||||
const formatSize = (bytes) => {
|
||||
if (bytes === 0) return '0 B';
|
||||
@ -531,7 +547,7 @@
|
||||
loadBuckets, createBucket, selectBucket, refreshFiles: () => loadFilesInitial(),
|
||||
nextPage: nextP, prevPage,
|
||||
triggerFileInput, handleFileSelect,
|
||||
previewFile, downloadFile,
|
||||
previewFile, downloadFile, deleteFile,
|
||||
formatSize, formatDate, getFileIcon, getProgressBarClass
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user