diff --git a/api/core/types/config.go b/api/core/types/config.go index f799cc48..5589e243 100644 --- a/api/core/types/config.go +++ b/api/core/types/config.go @@ -19,7 +19,8 @@ type AppConfig struct { AesEncryptKey string SmsConfig AliYunSmsConfig // AliYun send message service config ExtConfig ChatPlusExtConfig // ChatPlus extensions callback api config - MinioConfig MinioConfig + + OSS OSSConfig // OSS config } type ChatPlusApiConfig struct { @@ -40,6 +41,11 @@ type AliYunSmsConfig struct { Domain string } +type OSSConfig struct { + Active string + Local LocalStorageConfig + Minio MinioConfig +} type MinioConfig struct { Endpoint string AccessKey string @@ -49,6 +55,11 @@ type MinioConfig struct { Domain string } +type LocalStorageConfig struct { + BasePath string + BaseURL string +} + type RedisConfig struct { Host string Port int diff --git a/api/handler/mj_handler.go b/api/handler/mj_handler.go index afc791a6..445fd64e 100644 --- a/api/handler/mj_handler.go +++ b/api/handler/mj_handler.go @@ -4,6 +4,7 @@ import ( "chatplus/core" "chatplus/core/types" "chatplus/service/function" + "chatplus/service/oss" "chatplus/store" "chatplus/store/model" "chatplus/utils" @@ -36,23 +37,23 @@ type Image struct { type MidJourneyHandler struct { BaseHandler - leveldb *store.LevelDB - db *gorm.DB - mjFunc function.FuncMidJourney - //minio *service.MinioService + leveldb *store.LevelDB + db *gorm.DB + mjFunc function.FuncMidJourney + uploaderManager *oss.UploaderManager } func NewMidJourneyHandler( app *core.AppServer, leveldb *store.LevelDB, db *gorm.DB, - //minio *service.MinioService, + manager *oss.UploaderManager, functions map[string]function.Function) *MidJourneyHandler { h := MidJourneyHandler{ - leveldb: leveldb, - db: db, - //minio: minio, - mjFunc: functions[types.FuncMidJourney].(function.FuncMidJourney)} + leveldb: leveldb, + db: db, + uploaderManager: manager, + mjFunc: functions[types.FuncMidJourney].(function.FuncMidJourney)} h.App = app return &h } @@ -98,22 +99,15 @@ func (h *MidJourneyHandler) Notify(c *gin.Context) { resp.SUCCESS(c) return } - // TODO: 下载本地或者 OSS,提供可配置的选项 - // 下载图片到本地服务器 - filePath, err := utils.GenUploadPath(h.App.Config.StaticDir, data.Image.Filename) - if err != nil { - logger.Error("error with generate image dir: ", err) - resp.SUCCESS(c) - return - } - err = utils.DownloadFile(data.Image.URL, filePath, h.App.Config.ProxyURL) + // download image + imgURL, err := h.uploaderManager.GetActiveService().PutImg(data.Image.URL) if err != nil { logger.Error("error with download image: ", err) resp.SUCCESS(c) return } - data.Image.URL = utils.GenUploadUrl(h.App.Config.StaticDir, h.App.Config.StaticUrl, filePath) + data.Image.URL = imgURL message := model.HistoryMessage{ UserId: task.UserId, ChatId: task.ChatId, diff --git a/api/handler/upload_handler.go b/api/handler/upload_handler.go index ec52cc10..08baedf8 100644 --- a/api/handler/upload_handler.go +++ b/api/handler/upload_handler.go @@ -2,7 +2,7 @@ package handler import ( "chatplus/core" - "chatplus/utils" + "chatplus/service/oss" "chatplus/utils/resp" "fmt" "github.com/gin-gonic/gin" @@ -11,33 +11,22 @@ import ( type UploadHandler struct { BaseHandler - db *gorm.DB + db *gorm.DB + uploaderManager *oss.UploaderManager } -func NewUploadHandler(app *core.AppServer, db *gorm.DB) *UploadHandler { - handler := &UploadHandler{db: db} +func NewUploadHandler(app *core.AppServer, db *gorm.DB, manager *oss.UploaderManager) *UploadHandler { + handler := &UploadHandler{db: db, uploaderManager: manager} handler.App = app return handler } func (h *UploadHandler) Upload(c *gin.Context) { - file, err := c.FormFile("file") + fileURL, err := h.uploaderManager.GetActiveService().PutFile(c) if err != nil { resp.ERROR(c, fmt.Sprintf("文件上传失败: %s", err.Error())) return } - filePath, err := utils.GenUploadPath(h.App.Config.StaticDir, file.Filename) - if err != nil { - resp.ERROR(c, fmt.Sprintf("文件上传失败: %s", err.Error())) - return - } - // 将文件保存到指定路径 - err = c.SaveUploadedFile(file, filePath) - if err != nil { - resp.ERROR(c, fmt.Sprintf("文件保存失败: %s", err.Error())) - return - } - - resp.SUCCESS(c, utils.GenUploadUrl(h.App.Config.StaticDir, h.App.Config.StaticUrl, filePath)) + resp.SUCCESS(c, fileURL) } diff --git a/api/main.go b/api/main.go index 2b8f8a3e..1621d0db 100644 --- a/api/main.go +++ b/api/main.go @@ -8,6 +8,7 @@ import ( logger2 "chatplus/logger" "chatplus/service" "chatplus/service/function" + "chatplus/service/oss" "chatplus/store" "context" "embed" @@ -129,6 +130,7 @@ func main() { fx.Provide(func(config *types.AppConfig) *service.CaptchaService { return service.NewCaptchaService(config.ApiConfig) }), + fx.Provide(oss.NewUploaderManager), // 注册路由 fx.Invoke(func(s *core.AppServer, h *handler.ChatRoleHandler) { diff --git a/api/service/minio_service.go b/api/service/minio_service.go deleted file mode 100644 index c35339e7..00000000 --- a/api/service/minio_service.go +++ /dev/null @@ -1,54 +0,0 @@ -package service - -import ( - "chatplus/core/types" - "chatplus/utils" - "context" - "fmt" - "github.com/minio/minio-go/v7" - "github.com/minio/minio-go/v7/pkg/credentials" - "net/url" - "path" - "strings" -) - -type MinioService struct { - config *types.AppConfig - client *minio.Client -} - -func NewMinioService(config *types.AppConfig) (*MinioService, error) { - minioClient, err := minio.New(config.MinioConfig.Endpoint, &minio.Options{ - Creds: credentials.NewStaticV4(config.MinioConfig.AccessKey, config.MinioConfig.AccessSecret, ""), - Secure: config.MinioConfig.UseSSL, - }) - if err != nil { - return nil, err - } - return &MinioService{config: config, client: minioClient}, nil -} - -func (s *MinioService) UploadMjImg(imageURL string) (string, error) { - parsedURL, err := url.Parse(imageURL) - if err != nil { - return "", err - } - - filename := path.Base(parsedURL.Path) - imageBytes, err := utils.DownloadImage(imageURL, s.config.ProxyURL) - if err != nil { - return "", err - } - - info, err := s.client.PutObject( - context.Background(), - s.config.MinioConfig.Bucket, - filename, - strings.NewReader(string(imageBytes)), - int64(len(imageBytes)), - minio.PutObjectOptions{ContentType: "image/png"}) - if err != nil { - return "", err - } - return fmt.Sprintf("%s/%s/%s", s.config.MinioConfig.Domain, s.config.MinioConfig.Bucket, info.Key), nil -} diff --git a/api/service/oss/localstorage_service.go b/api/service/oss/localstorage_service.go new file mode 100644 index 00000000..853a2686 --- /dev/null +++ b/api/service/oss/localstorage_service.go @@ -0,0 +1,57 @@ +package oss + +import ( + "chatplus/core/types" + "chatplus/utils" + "fmt" + "github.com/gin-gonic/gin" + "path/filepath" +) + +type LocalStorageService struct { + config *types.LocalStorageConfig + proxyURL string +} + +func NewLocalStorageService(config *types.AppConfig) LocalStorageService { + return LocalStorageService{ + config: &config.OSS.Local, + proxyURL: config.ProxyURL, + } +} + +func (s LocalStorageService) PutFile(ctx *gin.Context) (string, error) { + file, err := ctx.FormFile("file") + if err != nil { + return "", fmt.Errorf("error with get form: %v", err) + } + + filePath, err := utils.GenUploadPath(s.config.BasePath, file.Filename) + if err != nil { + return "", fmt.Errorf("error with generate filename: %s", err.Error()) + } + // 将文件保存到指定路径 + err = ctx.SaveUploadedFile(file, filePath) + if err != nil { + return "", fmt.Errorf("error with save upload file: %s", err.Error()) + } + + return utils.GenUploadUrl(s.config.BasePath, s.config.BaseURL, filePath), nil +} + +func (s LocalStorageService) PutImg(imageURL string) (string, error) { + filename := filepath.Base(imageURL) + filePath, err := utils.GenUploadPath(s.config.BasePath, filename) + if err != nil { + return "", fmt.Errorf("error with generate image dir: %v", err) + } + + err = utils.DownloadFile(imageURL, filePath, s.proxyURL) + if err != nil { + return "", fmt.Errorf("error with download image: %v", err) + } + + return utils.GenUploadUrl(s.config.BasePath, s.config.BaseURL, filePath), nil +} + +var _ Uploader = LocalStorageService{} diff --git a/api/service/oss/minio_service.go b/api/service/oss/minio_service.go new file mode 100644 index 00000000..b431a238 --- /dev/null +++ b/api/service/oss/minio_service.go @@ -0,0 +1,78 @@ +package oss + +import ( + "chatplus/core/types" + "chatplus/utils" + "context" + "fmt" + "github.com/gin-gonic/gin" + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" + "path/filepath" + "strings" + "time" +) + +type MinioService struct { + config *types.MinioConfig + client *minio.Client + proxyURL string +} + +func NewMinioService(appConfig *types.AppConfig) (MinioService, error) { + config := &appConfig.OSS.Minio + minioClient, err := minio.New(config.Endpoint, &minio.Options{ + Creds: credentials.NewStaticV4(config.AccessKey, config.AccessSecret, ""), + Secure: config.UseSSL, + }) + if err != nil { + return MinioService{}, err + } + return MinioService{config: config, client: minioClient, proxyURL: appConfig.ProxyURL}, nil +} + +func (s MinioService) PutImg(imageURL string) (string, error) { + imageData, err := utils.DownloadImage(imageURL, s.proxyURL) + if err != nil { + return "", fmt.Errorf("error with download image: %v", err) + } + fileExt := filepath.Ext(filepath.Base(imageURL)) + filename := fmt.Sprintf("%d%s", time.Now().UnixNano(), fileExt) + info, err := s.client.PutObject( + context.Background(), + s.config.Bucket, + filename, + strings.NewReader(string(imageData)), + int64(len(imageData)), + minio.PutObjectOptions{ContentType: "image/png"}) + if err != nil { + return "", err + } + return fmt.Sprintf("%s/%s/%s", s.config.Domain, s.config.Bucket, info.Key), nil +} + +func (s MinioService) PutFile(ctx *gin.Context) (string, error) { + file, err := ctx.FormFile("file") + if err != nil { + return "", fmt.Errorf("error with get form: %v", err) + } + // Open the uploaded file + fileReader, err := file.Open() + if err != nil { + return "", fmt.Errorf("error opening file: %v", err) + } + defer fileReader.Close() + + fileExt := filepath.Ext(file.Filename) + filename := fmt.Sprintf("%d%s", time.Now().UnixNano(), fileExt) + info, err := s.client.PutObject(ctx, s.config.Bucket, filename, fileReader, file.Size, minio.PutObjectOptions{ + ContentType: file.Header.Get("Content-Type"), + }) + if err != nil { + return "", fmt.Errorf("error uploading to MinIO: %v", err) + } + + return fmt.Sprintf("%s/%s/%s", s.config.Domain, s.config.Bucket, info.Key), nil +} + +var _ Uploader = MinioService{} diff --git a/api/service/oss/uploader.go b/api/service/oss/uploader.go new file mode 100644 index 00000000..318f5746 --- /dev/null +++ b/api/service/oss/uploader.go @@ -0,0 +1,8 @@ +package oss + +import "github.com/gin-gonic/gin" + +type Uploader interface { + PutFile(ctx *gin.Context) (string, error) + PutImg(imageURL string) (string, error) +} diff --git a/api/service/oss/uploader_manager.go b/api/service/oss/uploader_manager.go new file mode 100644 index 00000000..f40a47be --- /dev/null +++ b/api/service/oss/uploader_manager.go @@ -0,0 +1,37 @@ +package oss + +import ( + "chatplus/core/types" + "strings" +) + +type UploaderManager struct { + active string + uploadServices map[string]Uploader +} + +const uploaderLocal = "LOCAL" +const uploaderMinio = "MINIO" + +func NewUploaderManager(config *types.AppConfig) (*UploaderManager, error) { + services := make(map[string]Uploader) + if config.OSS.Minio.AccessKey != "" { + minioService, err := NewMinioService(config) + if err != nil { + return nil, err + } + services[uploaderMinio] = minioService + } + if config.OSS.Local.BasePath != "" { + services[uploaderLocal] = NewLocalStorageService(config) + } + active := uploaderLocal + if config.OSS.Active != "" { + active = strings.ToUpper(config.OSS.Active) + } + return &UploaderManager{uploadServices: services, active: active}, nil +} + +func (m *UploaderManager) GetActiveService() Uploader { + return m.uploadServices[m.active] +} diff --git a/api/test/test.go b/api/test/test.go index b7074840..026a54da 100644 --- a/api/test/test.go +++ b/api/test/test.go @@ -4,7 +4,7 @@ import ( "bufio" "chatplus/core" "chatplus/core/types" - "chatplus/service" + "chatplus/service/oss" "chatplus/utils" "context" "encoding/json" @@ -15,12 +15,15 @@ import ( "log" "net/http" "os" + "path/filepath" "strings" "time" ) func main() { - minio() + imageURL := "https://cdn.discordapp.com/attachments/1139552247693443184/1141619433752768572/lisamiller4099_A_beautiful_fairy_sister_from_Chinese_mythology__3162726e-5ee4-4f60-932b-6b78b375eaef.png" + + fmt.Println(filepath.Ext(filepath.Base(imageURL))) } // Http client 取消操作 @@ -174,7 +177,7 @@ func extractFunction() error { func minio() { config := core.NewDefaultConfig() config.ProxyURL = "http://localhost:7777" - config.MinioConfig = types.MinioConfig{ + config.OSS.Minio = types.MinioConfig{ Endpoint: "localhost:9010", AccessKey: "ObWIEyXaQUHOYU26L0oI", AccessSecret: "AJW3HHhlGrprfPcmiC7jSOSzVCyrlhX4AnOAUzqI", @@ -182,12 +185,12 @@ func minio() { UseSSL: false, Domain: "http://localhost:9010", } - minioService, err := service.NewMinioService(config) + minioService, err := oss.NewMinioService(config) if err != nil { panic(err) } - url, err := minioService.UploadMjImg("https://cdn.discordapp.com/attachments/1139552247693443184/1141619433752768572/lisamiller4099_A_beautiful_fairy_sister_from_Chinese_mythology__3162726e-5ee4-4f60-932b-6b78b375eaef.png") + url, err := minioService.PutImg("https://cdn.discordapp.com/attachments/1139552247693443184/1141619433752768572/lisamiller4099_A_beautiful_fairy_sister_from_Chinese_mythology__3162726e-5ee4-4f60-932b-6b78b375eaef.png") if err != nil { panic(err) } diff --git a/api/utils/upload.go b/api/utils/upload.go index adac6f1f..f614e740 100644 --- a/api/utils/upload.go +++ b/api/utils/upload.go @@ -23,7 +23,7 @@ func GenUploadPath(basePath, filename string) (string, error) { } } fileExt := filepath.Ext(filename) - return fmt.Sprintf("%s/%d%s", dir, now.UnixMilli(), fileExt), nil + return fmt.Sprintf("%s/%d%s", dir, now.UnixNano(), fileExt), nil } // GenUploadUrl 生成上传文件 URL diff --git a/docker/minio/docker-compose.yaml b/docker/minio/docker-compose.yaml index 3e248ac8..7f338b82 100644 --- a/docker/minio/docker-compose.yaml +++ b/docker/minio/docker-compose.yaml @@ -2,11 +2,13 @@ version: '3' services: minio: image: minio/minio + container_name: minio volumes: - ./data:/data ports: - - 9000:9000 + - "9010:9000" + - "9011:9001" environment: MINIO_ROOT_USER: minio MINIO_ROOT_PASSWORD: minio@pass - command: server /data + command: server /data --console-address ":9001" --address ":9000" \ No newline at end of file