添加文件删除功能
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)
|
initMultipartHandler := handlers.NewInitMultipartHandler(s3Repo)
|
||||||
uploadPartHandler := handlers.NewUploadPartHandler(s3Repo)
|
uploadPartHandler := handlers.NewUploadPartHandler(s3Repo)
|
||||||
completeMultipartHandler := handlers.NewCompleteMultipartHandler(s3Repo)
|
completeMultipartHandler := handlers.NewCompleteMultipartHandler(s3Repo)
|
||||||
|
deleteFileHandler := handlers.NewDeleteFileHandler(s3Repo)
|
||||||
|
|
||||||
// Register Handlers
|
// Register Handlers
|
||||||
mediator.Register[handlers.UploadFileCommand, string](m, uploadHandler)
|
mediator.Register[handlers.UploadFileCommand, string](m, uploadHandler)
|
||||||
@ -53,6 +54,7 @@ func main() {
|
|||||||
mediator.Register[handlers.InitMultipartCommand, string](m, initMultipartHandler)
|
mediator.Register[handlers.InitMultipartCommand, string](m, initMultipartHandler)
|
||||||
mediator.Register[handlers.UploadPartCommand, string](m, uploadPartHandler)
|
mediator.Register[handlers.UploadPartCommand, string](m, uploadPartHandler)
|
||||||
mediator.Register[handlers.CompleteMultipartCommand, string](m, completeMultipartHandler)
|
mediator.Register[handlers.CompleteMultipartCommand, string](m, completeMultipartHandler)
|
||||||
|
mediator.Register[handlers.DeleteFileCommand, string](m, deleteFileHandler)
|
||||||
|
|
||||||
// Validators
|
// Validators
|
||||||
uploadValidator := validators.NewUploadFileValidator()
|
uploadValidator := validators.NewUploadFileValidator()
|
||||||
@ -72,7 +74,7 @@ func main() {
|
|||||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
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-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" {
|
if c.Request.Method == "OPTIONS" {
|
||||||
c.AbortWithStatus(204)
|
c.AbortWithStatus(204)
|
||||||
return
|
return
|
||||||
@ -87,6 +89,9 @@ func main() {
|
|||||||
r.GET("/files/list", fileEndpoint.ListFiles)
|
r.GET("/files/list", fileEndpoint.ListFiles)
|
||||||
r.GET("/files/preview", fileEndpoint.GetPreviewURL)
|
r.GET("/files/preview", fileEndpoint.GetPreviewURL)
|
||||||
|
|
||||||
|
// Delete file
|
||||||
|
r.DELETE("/files/delete", fileEndpoint.DeleteFile)
|
||||||
|
|
||||||
// Multipart Upload
|
// Multipart Upload
|
||||||
r.POST("/files/multipart/init", fileEndpoint.InitMultipart)
|
r.POST("/files/multipart/init", fileEndpoint.InitMultipart)
|
||||||
r.PUT("/files/multipart/part", fileEndpoint.UploadPart)
|
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})
|
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) {
|
func handleError(c *gin.Context, err error) {
|
||||||
if be, ok := err.(*common.BusinessException); ok {
|
if be, ok := err.(*common.BusinessException); ok {
|
||||||
c.JSON(be.Code, gin.H{"error": be.Message})
|
c.JSON(be.Code, gin.H{"error": be.Message})
|
||||||
|
|||||||
@ -2,9 +2,10 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"file-system/internal/common"
|
||||||
|
"file-sys
|
||||||
"file-system/internal/domain/repository"
|
"file-system/internal/domain/repository"
|
||||||
"io"
|
"io"
|
||||||
"time"
|
|
||||||
"file-system/internal/common"
|
"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) {
|
func (h *GetFilePreviewHandler) Handle(ctx context.Context, q GetFilePreviewQuery) (string, error) {
|
||||||
return h.Repo.GeneratePresignedURL(ctx, q.BucketName, q.ObjectKey, q.Expiry)
|
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"`
|
UploadId string `json:"upload_id"`
|
||||||
Parts []common.Part `json:"parts"`
|
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
|
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
|
||||||
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ type FileInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ListFilesResult struct {
|
type ListFilesResult struct {
|
||||||
Files []FileInfo
|
Files []FileInfo
|
||||||
NextContinuationToken *string
|
NextContinuationToken *string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,7 +30,10 @@ type FileRepository interface {
|
|||||||
// 新增功能
|
// 新增功能
|
||||||
ListObjectsV2(ctx context.Context, bucketName string, prefix string, maxKeys int32, continuationToken *string) (*ListFilesResult, error)
|
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)
|
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)
|
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)
|
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
|
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 分页列出文件
|
// ListObjectsV2 分页列出文件
|
||||||
func (r *S3FileRepository) ListObjectsV2(ctx context.Context, bucketName string, prefix string, maxKeys int32, continuationToken *string) (*repository.ListFilesResult, error) {
|
func (r *S3FileRepository) ListObjectsV2(ctx context.Context, bucketName string, prefix string, maxKeys int32, continuationToken *string) (*repository.ListFilesResult, error) {
|
||||||
input := &s3.ListObjectsV2Input{
|
input := &s3.ListObjectsV2Input{
|
||||||
|
|||||||
@ -113,6 +113,9 @@
|
|||||||
<span class="action-btn text-success" @click="downloadFile(file.Key)" title="下载">
|
<span class="action-btn text-success" @click="downloadFile(file.Key)" title="下载">
|
||||||
<i class="fas fa-download"></i>
|
<i class="fas fa-download"></i>
|
||||||
</span>
|
</span>
|
||||||
|
<span class="action-btn text-danger" @click="deleteFile(file.Key)" title="删除">
|
||||||
|
<i class="fas fa-trash-alt"></i>
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="files.length === 0 && !loadingFiles">
|
<tr v-if="files.length === 0 && !loadingFiles">
|
||||||
@ -493,6 +496,19 @@
|
|||||||
window.open(url, '_blank');
|
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
|
// Utils
|
||||||
const formatSize = (bytes) => {
|
const formatSize = (bytes) => {
|
||||||
if (bytes === 0) return '0 B';
|
if (bytes === 0) return '0 B';
|
||||||
@ -531,7 +547,7 @@
|
|||||||
loadBuckets, createBucket, selectBucket, refreshFiles: () => loadFilesInitial(),
|
loadBuckets, createBucket, selectBucket, refreshFiles: () => loadFilesInitial(),
|
||||||
nextPage: nextP, prevPage,
|
nextPage: nextP, prevPage,
|
||||||
triggerFileInput, handleFileSelect,
|
triggerFileInput, handleFileSelect,
|
||||||
previewFile, downloadFile,
|
previewFile, downloadFile, deleteFile,
|
||||||
formatSize, formatDate, getFileIcon, getProgressBarClass
|
formatSize, formatDate, getFileIcon, getProgressBarClass
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user