feat: 新增文件文本内容接口,修复Markdown预览
- 新增 GET /files/content 接口,后端直接读取S3文件文本内容返回 - Repository 新增 GetFileContent 方法 - CQRS: 新增 GetFileContentQuery / GetFileContentHandler - 前端 Markdown 预览改为调用后端接口获取内容,用 marked.js 渲染 - 解决 presigned URL CORS 和下载头导致 MD 文件无法预览的问题 - config.go: AuthAPIKey 默认值恢复为 xn001624. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8c180082d5
commit
d861be0d6e
@ -19,10 +19,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// @title RustFS File System API
|
// @title RustFS File System API
|
||||||
// @version 1.1
|
// @version 1.2
|
||||||
// @description RustFS 文件存储系统 API,支持分片上传、文件预览、分页查询等高级功能。
|
// @description RustFS 文件存储系统 API,支持分片上传、文件预览、分页查询等高级功能。
|
||||||
// @host localhost:8080
|
// @host localhost:8080
|
||||||
// @BasePath /
|
// @BasePath /
|
||||||
|
// @securityDefinitions.apikey ApiKeyAuth
|
||||||
|
// @in header
|
||||||
|
// @name X-API-Key
|
||||||
|
// @description 在请求头中传入 API 密钥进行身份验证
|
||||||
func main() {
|
func main() {
|
||||||
cfg := common.LoadConfig()
|
cfg := common.LoadConfig()
|
||||||
|
|
||||||
@ -44,6 +48,7 @@ func main() {
|
|||||||
uploadPartHandler := handlers.NewUploadPartHandler(s3Repo)
|
uploadPartHandler := handlers.NewUploadPartHandler(s3Repo)
|
||||||
completeMultipartHandler := handlers.NewCompleteMultipartHandler(s3Repo)
|
completeMultipartHandler := handlers.NewCompleteMultipartHandler(s3Repo)
|
||||||
deleteFileHandler := handlers.NewDeleteFileHandler(s3Repo)
|
deleteFileHandler := handlers.NewDeleteFileHandler(s3Repo)
|
||||||
|
fileContentHandler := handlers.NewGetFileContentHandler(s3Repo)
|
||||||
loginHandler := handlers.NewLoginHandler(cfg.AuthAPIKey)
|
loginHandler := handlers.NewLoginHandler(cfg.AuthAPIKey)
|
||||||
|
|
||||||
// Register Handlers
|
// Register Handlers
|
||||||
@ -59,6 +64,7 @@ func main() {
|
|||||||
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)
|
mediator.Register[handlers.DeleteFileCommand, string](m, deleteFileHandler)
|
||||||
|
mediator.Register[handlers.GetFileContentQuery, string](m, fileContentHandler)
|
||||||
mediator.Register[handlers.LoginQuery, handlers.LoginResult](m, loginHandler)
|
mediator.Register[handlers.LoginQuery, handlers.LoginResult](m, loginHandler)
|
||||||
|
|
||||||
// Validators
|
// Validators
|
||||||
@ -100,6 +106,7 @@ func main() {
|
|||||||
api.GET("/files/download", fileEndpoint.DownloadFile)
|
api.GET("/files/download", fileEndpoint.DownloadFile)
|
||||||
api.GET("/files/list", fileEndpoint.ListFiles)
|
api.GET("/files/list", fileEndpoint.ListFiles)
|
||||||
api.GET("/files/preview", fileEndpoint.GetPreviewURL)
|
api.GET("/files/preview", fileEndpoint.GetPreviewURL)
|
||||||
|
api.GET("/files/content", fileEndpoint.GetFileContent)
|
||||||
|
|
||||||
// Delete file
|
// Delete file
|
||||||
api.DELETE("/files/delete", fileEndpoint.DeleteFile)
|
api.DELETE("/files/delete", fileEndpoint.DeleteFile)
|
||||||
|
|||||||
@ -32,14 +32,16 @@ func NewFileEndpoint(m *mediator.Mediator, uv *validators.UploadFileValidator, d
|
|||||||
|
|
||||||
// UploadFile godoc
|
// UploadFile godoc
|
||||||
// @Summary 上传文件 (简单上传)
|
// @Summary 上传文件 (简单上传)
|
||||||
// @Description 上传小文件到指定的存储桶
|
// @Description 上传小文件到指定的存储桶,支持 multipart/form-data 格式
|
||||||
// @Tags 文件操作
|
// @Tags 文件操作
|
||||||
// @Accept multipart/form-data
|
// @Accept multipart/form-data
|
||||||
// @Produce json
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
// @Param bucket_name formData string true "存储桶名称"
|
// @Param bucket_name formData string true "存储桶名称"
|
||||||
// @Param file formData file true "要上传的文件"
|
// @Param file formData file true "要上传的文件"
|
||||||
// @Success 200 {object} map[string]string "上传成功消息"
|
// @Success 200 {object} map[string]string "上传成功消息"
|
||||||
// @Failure 400 {object} map[string]string "参数错误"
|
// @Failure 400 {object} map[string]string "参数错误"
|
||||||
|
// @Failure 401 {object} map[string]string "未授权:API 密钥无效或缺失"
|
||||||
// @Failure 500 {object} map[string]string "服务器内部错误"
|
// @Failure 500 {object} map[string]string "服务器内部错误"
|
||||||
// @Router /files/upload [post]
|
// @Router /files/upload [post]
|
||||||
func (e *FileEndpoint) UploadFile(c *gin.Context) {
|
func (e *FileEndpoint) UploadFile(c *gin.Context) {
|
||||||
@ -83,14 +85,16 @@ func (e *FileEndpoint) UploadFile(c *gin.Context) {
|
|||||||
|
|
||||||
// DownloadFile godoc
|
// DownloadFile godoc
|
||||||
// @Summary 下载文件
|
// @Summary 下载文件
|
||||||
// @Description 从指定的存储桶下载文件
|
// @Description 从指定的存储桶下载文件,返回文件流
|
||||||
// @Tags 文件操作
|
// @Tags 文件操作
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce octet-stream
|
// @Produce octet-stream
|
||||||
|
// @Security ApiKeyAuth
|
||||||
// @Param bucket_name query string true "存储桶名称"
|
// @Param bucket_name query string true "存储桶名称"
|
||||||
// @Param object_key query string true "对象键(文件名)"
|
// @Param object_key query string true "对象键(文件名)"
|
||||||
// @Success 200 {file} file "文件流"
|
// @Success 200 {file} file "文件流"
|
||||||
// @Failure 400 {object} map[string]string "参数错误"
|
// @Failure 400 {object} map[string]string "参数错误"
|
||||||
|
// @Failure 401 {object} map[string]string "未授权:API 密钥无效或缺失"
|
||||||
// @Failure 500 {object} map[string]string "服务器内部错误"
|
// @Failure 500 {object} map[string]string "服务器内部错误"
|
||||||
// @Router /files/download [get]
|
// @Router /files/download [get]
|
||||||
func (e *FileEndpoint) DownloadFile(c *gin.Context) {
|
func (e *FileEndpoint) DownloadFile(c *gin.Context) {
|
||||||
@ -124,17 +128,19 @@ func (e *FileEndpoint) DownloadFile(c *gin.Context) {
|
|||||||
|
|
||||||
// ListFiles godoc
|
// ListFiles godoc
|
||||||
// @Summary 文件列表 (分页)
|
// @Summary 文件列表 (分页)
|
||||||
// @Description 分页查询存储桶中的文件
|
// @Description 分页查询存储桶中的文件,支持前缀筛选和分页
|
||||||
// @Tags 文件操作
|
// @Tags 文件操作
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
// @Param bucket_name query string true "存储桶名称"
|
// @Param bucket_name query string true "存储桶名称"
|
||||||
// @Param prefix query string false "文件名前缀筛选"
|
// @Param prefix query string false "文件名前缀筛选"
|
||||||
// @Param max_keys query int false "每页数量"
|
// @Param max_keys query int false "每页数量(默认20)"
|
||||||
// @Param token query string false "分页Token"
|
// @Param token query string false "分页Token(下一页的凭证)"
|
||||||
// @Success 200 {object} repository.ListFilesResult
|
// @Success 200 {object} repository.ListFilesResult
|
||||||
// @Failure 400 {object} map[string]string
|
// @Failure 400 {object} map[string]string "参数错误"
|
||||||
// @Failure 500 {object} map[string]string
|
// @Failure 401 {object} map[string]string "未授权:API 密钥无效或缺失"
|
||||||
|
// @Failure 500 {object} map[string]string "服务器内部错误"
|
||||||
// @Router /files/list [get]
|
// @Router /files/list [get]
|
||||||
func (e *FileEndpoint) ListFiles(c *gin.Context) {
|
func (e *FileEndpoint) ListFiles(c *gin.Context) {
|
||||||
var req requests.ListFilesRequest
|
var req requests.ListFilesRequest
|
||||||
@ -170,15 +176,17 @@ func (e *FileEndpoint) ListFiles(c *gin.Context) {
|
|||||||
|
|
||||||
// GetPreviewURL godoc
|
// GetPreviewURL godoc
|
||||||
// @Summary 获取预览链接
|
// @Summary 获取预览链接
|
||||||
// @Description 生成文件的临时预览链接 (24小时有效)
|
// @Description 生成文件的临时预览链接(24小时有效),支持图片/视频/文档等
|
||||||
// @Tags 文件操作
|
// @Tags 文件操作
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
// @Param bucket_name query string true "存储桶名称"
|
// @Param bucket_name query string true "存储桶名称"
|
||||||
// @Param object_key query string true "对象键"
|
// @Param object_key query string true "对象键(文件名)"
|
||||||
// @Success 200 {object} map[string]string
|
// @Success 200 {object} map[string]string "返回预览 URL"
|
||||||
// @Failure 400 {object} map[string]string
|
// @Failure 400 {object} map[string]string "参数错误"
|
||||||
// @Failure 500 {object} map[string]string
|
// @Failure 401 {object} map[string]string "未授权:API 密钥无效或缺失"
|
||||||
|
// @Failure 500 {object} map[string]string "服务器内部错误"
|
||||||
// @Router /files/preview [get]
|
// @Router /files/preview [get]
|
||||||
func (e *FileEndpoint) GetPreviewURL(c *gin.Context) {
|
func (e *FileEndpoint) GetPreviewURL(c *gin.Context) {
|
||||||
var req requests.GetFilePreviewRequest
|
var req requests.GetFilePreviewRequest
|
||||||
@ -206,16 +214,57 @@ func (e *FileEndpoint) GetPreviewURL(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, gin.H{"url": result})
|
c.JSON(http.StatusOK, gin.H{"url": result})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFileContent godoc
|
||||||
|
// @Summary 获取文件文本内容
|
||||||
|
// @Description 读取文件的文本内容,用于 Markdown 等文本文件的在线预览
|
||||||
|
// @Tags 文件操作
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param bucket_name query string true "存储桶名称"
|
||||||
|
// @Param object_key query string true "对象键(文件名)"
|
||||||
|
// @Success 200 {object} map[string]string "返回文件文本内容"
|
||||||
|
// @Failure 400 {object} map[string]string "参数错误"
|
||||||
|
// @Failure 401 {object} map[string]string "未授权:API 密钥无效或缺失"
|
||||||
|
// @Failure 500 {object} map[string]string "服务器内部错误"
|
||||||
|
// @Router /files/content [get]
|
||||||
|
func (e *FileEndpoint) GetFileContent(c *gin.Context) {
|
||||||
|
var req requests.GetFilePreviewRequest
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := e.NewFeaturesValidator.ValidatePreview(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
query := handlers.GetFileContentQuery{
|
||||||
|
BucketName: req.BucketName,
|
||||||
|
ObjectKey: req.ObjectKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := mediator.Send[handlers.GetFileContentQuery, string](e.Mediator, c.Request.Context(), query)
|
||||||
|
if err != nil {
|
||||||
|
handleError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, gin.H{"content": result})
|
||||||
|
}
|
||||||
|
|
||||||
// InitMultipart godoc
|
// InitMultipart godoc
|
||||||
// @Summary 初始化分片上传
|
// @Summary 初始化分片上传
|
||||||
// @Description 开始一个新的大文件分片上传任务
|
// @Description 开始一个新的大文件分片上传任务,返回 upload_id 用于后续分片上传
|
||||||
// @Tags 大文件上传
|
// @Tags 大文件上传
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
// @Param request body requests.InitMultipartRequest true "请求参数"
|
// @Param request body requests.InitMultipartRequest true "请求参数"
|
||||||
// @Success 200 {object} map[string]string "返回 upload_id"
|
// @Success 200 {object} map[string]string "返回 upload_id"
|
||||||
// @Failure 400 {object} map[string]string
|
// @Failure 400 {object} map[string]string "参数错误"
|
||||||
// @Failure 500 {object} map[string]string
|
// @Failure 401 {object} map[string]string "未授权:API 密钥无效或缺失"
|
||||||
|
// @Failure 500 {object} map[string]string "服务器内部错误"
|
||||||
// @Router /files/multipart/init [post]
|
// @Router /files/multipart/init [post]
|
||||||
func (e *FileEndpoint) InitMultipart(c *gin.Context) {
|
func (e *FileEndpoint) InitMultipart(c *gin.Context) {
|
||||||
var req requests.InitMultipartRequest
|
var req requests.InitMultipartRequest
|
||||||
@ -239,18 +288,20 @@ func (e *FileEndpoint) InitMultipart(c *gin.Context) {
|
|||||||
|
|
||||||
// UploadPart godoc
|
// UploadPart godoc
|
||||||
// @Summary 上传分片
|
// @Summary 上传分片
|
||||||
// @Description 上传单个文件分片
|
// @Description 上传单个文件分片,建议每个分片 5MB,支持失败重试
|
||||||
// @Tags 大文件上传
|
// @Tags 大文件上传
|
||||||
// @Accept multipart/form-data
|
// @Accept multipart/form-data
|
||||||
// @Produce json
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
// @Param bucket_name formData string true "存储桶名称"
|
// @Param bucket_name formData string true "存储桶名称"
|
||||||
// @Param object_key formData string true "对象键"
|
// @Param object_key formData string true "对象键"
|
||||||
// @Param upload_id formData string true "上传ID"
|
// @Param upload_id formData string true "上传ID(由初始化接口返回)"
|
||||||
// @Param part_number formData int true "分片序号 (从1开始)"
|
// @Param part_number formData int true "分片序号(从1开始)"
|
||||||
// @Param file formData file true "分片文件数据"
|
// @Param file formData file true "分片文件数据"
|
||||||
// @Success 200 {object} map[string]string "返回 ETag"
|
// @Success 200 {object} map[string]string "返回 ETag"
|
||||||
// @Failure 400 {object} map[string]string
|
// @Failure 400 {object} map[string]string "参数错误"
|
||||||
// @Failure 500 {object} map[string]string
|
// @Failure 401 {object} map[string]string "未授权:API 密钥无效或缺失"
|
||||||
|
// @Failure 500 {object} map[string]string "服务器内部错误"
|
||||||
// @Router /files/multipart/part [put]
|
// @Router /files/multipart/part [put]
|
||||||
func (e *FileEndpoint) UploadPart(c *gin.Context) {
|
func (e *FileEndpoint) UploadPart(c *gin.Context) {
|
||||||
var req requests.UploadPartRequest
|
var req requests.UploadPartRequest
|
||||||
@ -287,14 +338,16 @@ func (e *FileEndpoint) UploadPart(c *gin.Context) {
|
|||||||
|
|
||||||
// CompleteMultipart godoc
|
// CompleteMultipart godoc
|
||||||
// @Summary 完成分片上传
|
// @Summary 完成分片上传
|
||||||
// @Description 合并所有分片完成上传
|
// @Description 合并所有分片完成上传,需传入所有分片的 PartNumber 和 ETag
|
||||||
// @Tags 大文件上传
|
// @Tags 大文件上传
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
// @Param request body requests.CompleteMultipartRequest true "请求参数"
|
// @Param request body requests.CompleteMultipartRequest true "请求参数"
|
||||||
// @Success 200 {object} map[string]string "返回文件位置"
|
// @Success 200 {object} map[string]string "返回文件位置"
|
||||||
// @Failure 400 {object} map[string]string
|
// @Failure 400 {object} map[string]string "参数错误"
|
||||||
// @Failure 500 {object} map[string]string
|
// @Failure 401 {object} map[string]string "未授权:API 密钥无效或缺失"
|
||||||
|
// @Failure 500 {object} map[string]string "服务器内部错误"
|
||||||
// @Router /files/multipart/complete [post]
|
// @Router /files/multipart/complete [post]
|
||||||
func (e *FileEndpoint) CompleteMultipart(c *gin.Context) {
|
func (e *FileEndpoint) CompleteMultipart(c *gin.Context) {
|
||||||
var req requests.CompleteMultipartRequest
|
var req requests.CompleteMultipartRequest
|
||||||
@ -323,13 +376,15 @@ func (e *FileEndpoint) CompleteMultipart(c *gin.Context) {
|
|||||||
|
|
||||||
// DeleteFile godoc
|
// DeleteFile godoc
|
||||||
// @Summary 删除文件
|
// @Summary 删除文件
|
||||||
// @Description 从指定的存储桶删除文件
|
// @Description 从指定的存储桶删除文件,此操作不可恢复
|
||||||
// @Tags 文件操作
|
// @Tags 文件操作
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
// @Param request body requests.DeleteFileRequest true "请求参数"
|
// @Param request body requests.DeleteFileRequest true "请求参数"
|
||||||
// @Success 200 {object} map[string]string "删除成功消息"
|
// @Success 200 {object} map[string]string "删除成功消息"
|
||||||
// @Failure 400 {object} map[string]string "参数错误"
|
// @Failure 400 {object} map[string]string "参数错误"
|
||||||
|
// @Failure 401 {object} map[string]string "未授权:API 密钥无效或缺失"
|
||||||
// @Failure 500 {object} map[string]string "服务器内部错误"
|
// @Failure 500 {object} map[string]string "服务器内部错误"
|
||||||
// @Router /files/delete [delete]
|
// @Router /files/delete [delete]
|
||||||
func (e *FileEndpoint) DeleteFile(c *gin.Context) {
|
func (e *FileEndpoint) DeleteFile(c *gin.Context) {
|
||||||
|
|||||||
@ -22,6 +22,12 @@ type GetFilePreviewQuery struct {
|
|||||||
Expiry time.Duration
|
Expiry time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFileContentQuery 获取文件文本内容查询
|
||||||
|
type GetFileContentQuery struct {
|
||||||
|
BucketName string
|
||||||
|
ObjectKey string
|
||||||
|
}
|
||||||
|
|
||||||
type InitMultipartCommand struct {
|
type InitMultipartCommand struct {
|
||||||
BucketName string
|
BucketName string
|
||||||
ObjectKey string
|
ObjectKey string
|
||||||
@ -68,6 +74,19 @@ func (h *GetFilePreviewHandler) Handle(ctx context.Context, q GetFilePreviewQuer
|
|||||||
return h.Repo.GeneratePresignedURL(ctx, q.BucketName, q.ObjectKey, q.Expiry)
|
return h.Repo.GeneratePresignedURL(ctx, q.BucketName, q.ObjectKey, q.Expiry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFileContentHandler 获取文件文本内容处理器
|
||||||
|
type GetFileContentHandler struct {
|
||||||
|
Repo repository.FileRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGetFileContentHandler(repo repository.FileRepository) *GetFileContentHandler {
|
||||||
|
return &GetFileContentHandler{Repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *GetFileContentHandler) Handle(ctx context.Context, q GetFileContentQuery) (string, error) {
|
||||||
|
return h.Repo.GetFileContent(ctx, q.BucketName, q.ObjectKey)
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteFileCommand 删除文件命令
|
// DeleteFileCommand 删除文件命令
|
||||||
type DeleteFileCommand struct {
|
type DeleteFileCommand struct {
|
||||||
BucketName string
|
BucketName string
|
||||||
|
|||||||
@ -31,6 +31,9 @@ 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)
|
||||||
|
|
||||||
|
// 获取文件文本内容(用于文本文件预览)
|
||||||
|
GetFileContent(ctx context.Context, bucketName string, objectKey string) (string, error)
|
||||||
|
|
||||||
// 删除文件
|
// 删除文件
|
||||||
DeleteFile(ctx context.Context, bucketName string, objectKey string) error
|
DeleteFile(ctx context.Context, bucketName string, objectKey string) error
|
||||||
|
|
||||||
|
|||||||
@ -85,6 +85,23 @@ func (r *S3FileRepository) ListObjects(ctx context.Context, bucketName string) (
|
|||||||
return objects, nil
|
return objects, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFileContent 获取文件文本内容(用于 Markdown 等文本文件预览)
|
||||||
|
func (r *S3FileRepository) GetFileContent(ctx context.Context, bucketName string, objectKey string) (string, error) {
|
||||||
|
resp, err := r.client.Client.GetObject(ctx, &s3.GetObjectInput{
|
||||||
|
Bucket: aws.String(bucketName),
|
||||||
|
Key: aws.String(objectKey),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteFile 删除文件
|
// DeleteFile 删除文件
|
||||||
func (r *S3FileRepository) DeleteFile(ctx context.Context, bucketName string, objectKey string) error {
|
func (r *S3FileRepository) DeleteFile(ctx context.Context, bucketName string, objectKey string) error {
|
||||||
_, err := r.client.Client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
_, err := r.client.Client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
||||||
|
|||||||
@ -58,6 +58,9 @@
|
|||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h1><i class="fas fa-cloud-upload-alt text-primary me-2"></i>RustFS 文件管理系统</h1>
|
<h1><i class="fas fa-cloud-upload-alt text-primary me-2"></i>RustFS 文件管理系统</h1>
|
||||||
<div>
|
<div>
|
||||||
|
<a href="/web/guide.html" target="_blank" class="btn btn-outline-info me-2">
|
||||||
|
<i class="fas fa-plug me-1"></i>对接指南
|
||||||
|
</a>
|
||||||
<a href="/swagger/index.html" target="_blank" class="btn btn-outline-secondary me-2">
|
<a href="/swagger/index.html" target="_blank" class="btn btn-outline-secondary me-2">
|
||||||
<i class="fas fa-book me-1"></i>API 文档
|
<i class="fas fa-book me-1"></i>API 文档
|
||||||
</a>
|
</a>
|
||||||
@ -480,26 +483,32 @@
|
|||||||
// Preview
|
// Preview
|
||||||
const previewFile = async (key) => {
|
const previewFile = async (key) => {
|
||||||
try {
|
try {
|
||||||
|
const ext = key.split('.').pop().toLowerCase();
|
||||||
|
if (['md', 'markdown'].includes(ext)) {
|
||||||
|
// Markdown 文件:通过后端接口获取文本内容,前端渲染
|
||||||
|
previewUrl.value = 'loading';
|
||||||
|
previewType.value = 'markdown';
|
||||||
|
const res = await api.get('/files/content', {
|
||||||
|
params: { bucket_name: currentBucket.value, object_key: key }
|
||||||
|
});
|
||||||
|
markdownHtml.value = marked.parse(res.data.content);
|
||||||
|
} else {
|
||||||
|
// 其他文件:获取 presigned URL 预览
|
||||||
const res = await api.get('/files/preview', {
|
const res = await api.get('/files/preview', {
|
||||||
params: { bucket_name: currentBucket.value, object_key: key }
|
params: { bucket_name: currentBucket.value, object_key: key }
|
||||||
});
|
});
|
||||||
previewUrl.value = res.data.url;
|
previewUrl.value = res.data.url;
|
||||||
|
if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(ext)) {
|
||||||
const ext = key.split('.').pop().toLowerCase();
|
|
||||||
if (['md', 'markdown'].includes(ext)) {
|
|
||||||
// Markdown 文件:获取内容并渲染
|
|
||||||
previewType.value = 'markdown';
|
|
||||||
const mdRes = await axios.get(res.data.url);
|
|
||||||
markdownHtml.value = marked.parse(mdRes.data);
|
|
||||||
} else if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(ext)) {
|
|
||||||
previewType.value = 'image';
|
previewType.value = 'image';
|
||||||
} else if (['mp4', 'webm', 'ogg'].includes(ext)) {
|
} else if (['mp4', 'webm', 'ogg'].includes(ext)) {
|
||||||
previewType.value = 'video';
|
previewType.value = 'video';
|
||||||
} else {
|
} else {
|
||||||
previewType.value = 'other';
|
previewType.value = 'other';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert('无法获取预览链接');
|
previewUrl.value = null;
|
||||||
|
alert('无法获取预览内容');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user