支持cf的r2存储
This commit is contained in:
@@ -4,12 +4,12 @@ DB_PATH=./data/memory.db
|
||||
JWT_SECRET=your-secret-key-change-in-production
|
||||
BASE_URL=http://192.168.0.100:8080
|
||||
|
||||
# 本地存储 (优先使用,留空则使用R2)
|
||||
LOCAL_UPLOAD_PATH=./uploads
|
||||
# 本地存储 (留空则使用R2)
|
||||
LOCAL_UPLOAD_PATH=
|
||||
|
||||
# Cloudflare R2 (可选,LOCAL_UPLOAD_PATH为空时使用)
|
||||
# Cloudflare R2
|
||||
R2_ACCOUNT_ID=your-account-id
|
||||
R2_ACCESS_KEY_ID=your-access-key-id
|
||||
R2_ACCESS_KEY_SECRET=your-access-key-secret
|
||||
R2_BUCKET_NAME=memory
|
||||
R2_PUBLIC_URL=https://your-bucket.r2.dev
|
||||
R2_PUBLIC_URL=
|
||||
|
||||
@@ -99,11 +99,11 @@ func (h *UploadHandler) Upload(c *gin.Context) {
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
// 优先使用本地存储
|
||||
if h.cfg.LocalUploadPath != "" {
|
||||
url, err := h.saveLocal(src, filename)
|
||||
// 优先使用 R2 存储
|
||||
if h.s3Client != nil {
|
||||
url, err := h.uploadToR2(src, filename, contentType)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save file"})
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to upload file: " + err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
@@ -113,11 +113,11 @@ func (h *UploadHandler) Upload(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 使用 R2 存储
|
||||
if h.s3Client != nil {
|
||||
url, err := h.uploadToR2(src, filename, contentType)
|
||||
// 使用本地存储
|
||||
if h.cfg.LocalUploadPath != "" {
|
||||
url, err := h.saveLocal(src, filename)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to upload file"})
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save file"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
@@ -166,6 +166,47 @@ func (h *UploadHandler) uploadToR2(src io.Reader, filename, contentType string)
|
||||
return "", err
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/%s", strings.TrimSuffix(h.cfg.R2PublicURL, "/"), filename)
|
||||
return url, nil
|
||||
// 如果配置了公开 URL,直接返回
|
||||
if h.cfg.R2PublicURL != "" {
|
||||
return fmt.Sprintf("%s/%s", strings.TrimSuffix(h.cfg.R2PublicURL, "/"), filename), nil
|
||||
}
|
||||
|
||||
// 否则通过服务端代理访问
|
||||
return fmt.Sprintf("%s/files/%s", strings.TrimSuffix(h.cfg.BaseURL, "/"), filename), nil
|
||||
}
|
||||
|
||||
// GetFile 代理访问 R2 文件
|
||||
func (h *UploadHandler) GetFile(c *gin.Context) {
|
||||
// 获取文件路径 (格式: 2024/01/uuid.jpg)
|
||||
filepath := c.Param("filepath")
|
||||
if filepath == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "filepath required"})
|
||||
return
|
||||
}
|
||||
|
||||
// 从 R2 获取文件
|
||||
if h.s3Client != nil {
|
||||
result, err := h.s3Client.GetObject(context.TODO(), &s3.GetObjectInput{
|
||||
Bucket: aws.String(h.cfg.R2BucketName),
|
||||
Key: aws.String(filepath),
|
||||
})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "file not found"})
|
||||
return
|
||||
}
|
||||
defer result.Body.Close()
|
||||
|
||||
// 设置响应头
|
||||
if result.ContentType != nil {
|
||||
c.Header("Content-Type", *result.ContentType)
|
||||
}
|
||||
c.Header("Cache-Control", "public, max-age=31536000")
|
||||
|
||||
// 流式传输
|
||||
c.Status(http.StatusOK)
|
||||
io.Copy(c.Writer, result.Body)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "file not found"})
|
||||
}
|
||||
|
||||
@@ -40,6 +40,9 @@ func Setup(db *sql.DB, cfg *config.Config) *gin.Engine {
|
||||
searchHandler := handler.NewSearchHandler(db, cfg)
|
||||
uploadHandler := handler.NewUploadHandler(cfg)
|
||||
|
||||
// R2 文件代理 (公开访问)
|
||||
r.GET("/files/*filepath", uploadHandler.GetFile)
|
||||
|
||||
// 公开接口
|
||||
r.POST("/api/auth/register", authHandler.Register)
|
||||
r.POST("/api/auth/login", authHandler.Login)
|
||||
|
||||
Reference in New Issue
Block a user