添加存储桶删除功能
- 新增 DeleteBucketHandler 处理存储桶删除请求 - 添加 DELETE /buckets API 端点 - 在前端界面添加删除存储桶按钮功能 - 添加存储桶删除请求验证器 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
71a5ea5f41
commit
00a0e583a8
@ -35,6 +35,7 @@ func main() {
|
|||||||
downloadHandler := handlers.NewDownloadFileHandler(s3Repo)
|
downloadHandler := handlers.NewDownloadFileHandler(s3Repo)
|
||||||
createBucketHandler := handlers.NewCreateBucketHandler(s3Repo)
|
createBucketHandler := handlers.NewCreateBucketHandler(s3Repo)
|
||||||
listBucketsHandler := handlers.NewListBucketsHandler(s3Repo)
|
listBucketsHandler := handlers.NewListBucketsHandler(s3Repo)
|
||||||
|
deleteBucketHandler := handlers.NewDeleteBucketHandler(s3Repo)
|
||||||
// New Handlers
|
// New Handlers
|
||||||
listFilesHandler := handlers.NewListFilesHandler(s3Repo)
|
listFilesHandler := handlers.NewListFilesHandler(s3Repo)
|
||||||
previewHandler := handlers.NewGetFilePreviewHandler(s3Repo)
|
previewHandler := handlers.NewGetFilePreviewHandler(s3Repo)
|
||||||
@ -48,6 +49,7 @@ func main() {
|
|||||||
mediator.Register[handlers.DownloadFileQuery, io.ReadCloser](m, downloadHandler)
|
mediator.Register[handlers.DownloadFileQuery, io.ReadCloser](m, downloadHandler)
|
||||||
mediator.Register[handlers.CreateBucketCommand, string](m, createBucketHandler)
|
mediator.Register[handlers.CreateBucketCommand, string](m, createBucketHandler)
|
||||||
mediator.Register[handlers.ListBucketsQuery, []string](m, listBucketsHandler)
|
mediator.Register[handlers.ListBucketsQuery, []string](m, listBucketsHandler)
|
||||||
|
mediator.Register[handlers.DeleteBucketCommand, string](m, deleteBucketHandler)
|
||||||
// New Registrations
|
// New Registrations
|
||||||
mediator.Register[handlers.ListFilesQuery, *repository.ListFilesResult](m, listFilesHandler)
|
mediator.Register[handlers.ListFilesQuery, *repository.ListFilesResult](m, listFilesHandler)
|
||||||
mediator.Register[handlers.GetFilePreviewQuery, string](m, previewHandler)
|
mediator.Register[handlers.GetFilePreviewQuery, string](m, previewHandler)
|
||||||
@ -100,6 +102,7 @@ func main() {
|
|||||||
// Bucket operations
|
// Bucket operations
|
||||||
r.POST("/buckets", bucketEndpoint.CreateBucket)
|
r.POST("/buckets", bucketEndpoint.CreateBucket)
|
||||||
r.GET("/buckets", bucketEndpoint.ListBuckets)
|
r.GET("/buckets", bucketEndpoint.ListBuckets)
|
||||||
|
r.DELETE("/buckets", bucketEndpoint.DeleteBucket)
|
||||||
|
|
||||||
// Swagger
|
// Swagger
|
||||||
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||||
|
|||||||
@ -79,3 +79,41 @@ func (e *BucketEndpoint) ListBuckets(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, gin.H{"buckets": result})
|
c.JSON(http.StatusOK, gin.H{"buckets": result})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteBucket godoc
|
||||||
|
// @Summary 删除存储桶
|
||||||
|
// @Description 删除指定的 S3 存储桶(桶必须为空)
|
||||||
|
// @Tags 存储桶管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param request body requests.DeleteBucketRequest true "删除存储桶请求参数"
|
||||||
|
// @Success 200 {object} map[string]string "删除成功消息"
|
||||||
|
// @Failure 400 {object} map[string]string "参数错误"
|
||||||
|
// @Failure 500 {object} map[string]string "服务器内部错误"
|
||||||
|
// @Router /buckets [delete]
|
||||||
|
func (e *BucketEndpoint) DeleteBucket(c *gin.Context) {
|
||||||
|
var req requests.DeleteBucketRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := e.CreateBucketValidator.ValidateDelete(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := handlers.DeleteBucketCommand{BucketName: req.BucketName}
|
||||||
|
|
||||||
|
result, err := mediator.Send[handlers.DeleteBucketCommand, string](e.Mediator, c.Request.Context(), cmd)
|
||||||
|
if err != nil {
|
||||||
|
if be, ok := err.(*common.BusinessException); ok {
|
||||||
|
c.JSON(be.Code, gin.H{"error": be.Message})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": result})
|
||||||
|
}
|
||||||
|
|||||||
@ -5,3 +5,7 @@ type CreateBucketCommand struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ListBucketsQuery struct{}
|
type ListBucketsQuery struct{}
|
||||||
|
|
||||||
|
type DeleteBucketCommand struct {
|
||||||
|
BucketName string
|
||||||
|
}
|
||||||
|
|||||||
@ -32,3 +32,19 @@ func NewListBucketsHandler(repo repository.FileRepository) *ListBucketsHandler {
|
|||||||
func (h *ListBucketsHandler) Handle(ctx context.Context, query ListBucketsQuery) ([]string, error) {
|
func (h *ListBucketsHandler) Handle(ctx context.Context, query ListBucketsQuery) ([]string, error) {
|
||||||
return h.Repo.ListBuckets(ctx)
|
return h.Repo.ListBuckets(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeleteBucketHandler struct {
|
||||||
|
Repo repository.FileRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDeleteBucketHandler(repo repository.FileRepository) *DeleteBucketHandler {
|
||||||
|
return &DeleteBucketHandler{Repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DeleteBucketHandler) Handle(ctx context.Context, cmd DeleteBucketCommand) (string, error) {
|
||||||
|
err := h.Repo.DeleteBucket(ctx, cmd.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return "Bucket deleted successfully", nil
|
||||||
|
}
|
||||||
|
|||||||
@ -3,10 +3,9 @@ package handlers
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"file-system/internal/common"
|
"file-system/internal/common"
|
||||||
"file-sys
|
|
||||||
"file-system/internal/domain/repository"
|
"file-system/internal/domain/repository"
|
||||||
"io"
|
"io"
|
||||||
"file-system/internal/common"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Queries & Commands
|
// Queries & Commands
|
||||||
|
|||||||
@ -5,3 +5,7 @@ type CreateBucketRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ListBucketsRequest struct{}
|
type ListBucketsRequest struct{}
|
||||||
|
|
||||||
|
type DeleteBucketRequest struct {
|
||||||
|
BucketName string `json:"bucket_name"`
|
||||||
|
}
|
||||||
|
|||||||
@ -17,3 +17,10 @@ func (v *CreateBucketValidator) Validate(req *requests.CreateBucketRequest) erro
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *CreateBucketValidator) ValidateDelete(req *requests.DeleteBucketRequest) error {
|
||||||
|
if req.BucketName == "" {
|
||||||
|
return common.NewBusinessException("Bucket name cannot be empty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@ -47,13 +47,16 @@
|
|||||||
<button class="btn btn-sm btn-link" @click="loadBuckets"><i class="fas fa-sync-alt"></i></button>
|
<button class="btn btn-sm btn-link" @click="loadBuckets"><i class="fas fa-sync-alt"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-group list-group-flush">
|
<div class="list-group list-group-flush">
|
||||||
<a v-for="bucket in buckets" :key="bucket"
|
<div v-for="bucket in buckets" :key="bucket"
|
||||||
href="#"
|
class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
|
||||||
class="list-group-item list-group-item-action"
|
:class="{ active: currentBucket === bucket }">
|
||||||
:class="{ active: currentBucket === bucket }"
|
<a href="#" class="text-decoration-none flex-grow-1" :class="{ 'text-white': currentBucket === bucket }" @click.prevent="selectBucket(bucket)">
|
||||||
@click.prevent="selectBucket(bucket)">
|
<i class="fas fa-box me-2"></i>{{ bucket }}
|
||||||
<i class="fas fa-box me-2"></i>{{ bucket }}
|
</a>
|
||||||
</a>
|
<span class="action-btn" :class="currentBucket === bucket ? 'text-white' : 'text-danger'" @click.stop="deleteBucket(bucket)" title="删除存储桶">
|
||||||
|
<i class="fas fa-trash-alt"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<div v-if="buckets.length === 0" class="list-group-item text-muted text-center py-4">
|
<div v-if="buckets.length === 0" class="list-group-item text-muted text-center py-4">
|
||||||
暂无存储桶
|
暂无存储桶
|
||||||
</div>
|
</div>
|
||||||
@ -496,7 +499,7 @@
|
|||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Delete
|
// Delete File
|
||||||
const deleteFile = async (key) => {
|
const deleteFile = async (key) => {
|
||||||
if (!confirm(`确定要删除文件 "${key}" 吗?此操作不可恢复!`)) return;
|
if (!confirm(`确定要删除文件 "${key}" 吗?此操作不可恢复!`)) return;
|
||||||
try {
|
try {
|
||||||
@ -509,6 +512,23 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Delete Bucket
|
||||||
|
const deleteBucket = async (bucketName) => {
|
||||||
|
if (!confirm(`确定要删除存储桶 "${bucketName}" 吗?\n注意:存储桶必须为空才能删除!`)) return;
|
||||||
|
try {
|
||||||
|
await api.delete('/buckets', {
|
||||||
|
data: { bucket_name: bucketName }
|
||||||
|
});
|
||||||
|
if (currentBucket.value === bucketName) {
|
||||||
|
currentBucket.value = null;
|
||||||
|
files.value = [];
|
||||||
|
}
|
||||||
|
await loadBuckets();
|
||||||
|
} 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';
|
||||||
@ -544,7 +564,7 @@
|
|||||||
return {
|
return {
|
||||||
buckets, currentBucket, files, loadingFiles, nextToken, pageHistory, filters,
|
buckets, currentBucket, files, loadingFiles, nextToken, pageHistory, filters,
|
||||||
showCreateBucketModal, newBucketName, uploads, previewUrl, isPreviewImage, isPreviewVideo,
|
showCreateBucketModal, newBucketName, uploads, previewUrl, isPreviewImage, isPreviewVideo,
|
||||||
loadBuckets, createBucket, selectBucket, refreshFiles: () => loadFilesInitial(),
|
loadBuckets, createBucket, selectBucket, deleteBucket, refreshFiles: () => loadFilesInitial(),
|
||||||
nextPage: nextP, prevPage,
|
nextPage: nextP, prevPage,
|
||||||
triggerFileInput, handleFileSelect,
|
triggerFileInput, handleFileSelect,
|
||||||
previewFile, downloadFile, deleteFile,
|
previewFile, downloadFile, deleteFile,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user