feat(release): migrate GeekAI v4.3.0 to open source

- Sync backend and frontend from GeekAI Plus v4.3.0

- Remove commercial License flows and update open-source deployment defaults

- Preserve Docker Compose deployment and bump image tags to v4.3.0

BREAKING CHANGE: commercial License configuration and related endpoints are removed
This commit is contained in:
RockYang
2026-08-11 14:51:20 +08:00
parent 37e024acea
commit 9ccff4efbc
219 changed files with 29212 additions and 10802 deletions
+167
View File
@@ -0,0 +1,167 @@
package oss
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
// * Use of this source code is governed by a Apache-2.0 license
// * that can be found in the LICENSE file.
// * @Author yangjian102621@163.com
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"bytes"
"context"
"encoding/base64"
"fmt"
"geekai/core/types"
"geekai/utils"
"net/http"
"net/url"
"path/filepath"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/tencentyun/cos-go-sdk-v5"
)
type TencentOss struct {
config types.TencentOssConfig
client *cos.Client
proxyURL string
}
func NewTencentOss(sysConfig *types.SystemConfig, appConfig *types.AppConfig) (*TencentOss, error) {
s := &TencentOss{
proxyURL: appConfig.ProxyURL,
}
err := s.UpdateConfig(sysConfig.OSS.Tencent)
if err != nil {
logger.Warnf("腾讯云COS初始化失败: %v", err)
}
return s, nil
}
func (s *TencentOss) UpdateConfig(config types.TencentOssConfig) error {
if config.Bucket == "" || config.Region == "" || config.SecretId == "" || config.SecretKey == "" {
// 配置不完整时不初始化客户端
s.config = config
return nil
}
// 构建 COS 客户端 URL
cosURL := fmt.Sprintf("https://%s.cos.%s.myqcloud.com", config.Bucket, config.Region)
u, err := url.Parse(cosURL)
if err != nil {
return fmt.Errorf("error parsing COS URL: %v", err)
}
// 创建 COS 客户端
b := &cos.BaseURL{BucketURL: u}
client := cos.NewClient(b, &http.Client{
Transport: &cos.AuthorizationTransport{
SecretID: config.SecretId,
SecretKey: config.SecretKey,
},
})
s.client = client
s.config = config
return nil
}
func (s TencentOss) PutFile(ctx *gin.Context, name string) (File, error) {
// 解析表单
file, err := ctx.FormFile(name)
if err != nil {
return File{}, err
}
// 打开上传文件
src, err := file.Open()
if err != nil {
return File{}, err
}
defer src.Close()
fileExt := filepath.Ext(file.Filename)
objectKey := fmt.Sprintf("%d%s", time.Now().UnixMicro(), fileExt)
// 上传文件
_, err = s.client.Object.Put(ctx, objectKey, src, nil)
if err != nil {
return File{}, err
}
// 生成文件 URL
fileURL := s.generateURL(objectKey)
return File{
Name: file.Filename,
ObjKey: objectKey,
URL: fileURL,
Ext: fileExt,
Size: file.Size,
}, nil
}
func (s TencentOss) PutUrlFile(fileURL string, ext string, useProxy bool) (string, error) {
var fileData []byte
var err error
if useProxy {
fileData, err = utils.DownloadImage(fileURL, s.proxyURL)
} else {
fileData, err = utils.DownloadImage(fileURL, "")
}
if err != nil {
return "", fmt.Errorf("error with download image: %v", err)
}
parse, err := url.Parse(fileURL)
if err != nil {
return "", fmt.Errorf("error with parse image URL: %v", err)
}
if ext == "" {
ext = filepath.Ext(parse.Path)
}
objectKey := fmt.Sprintf("%d%s", time.Now().UnixMicro(), ext)
// 上传文件字节数据
_, err = s.client.Object.Put(context.Background(), objectKey, bytes.NewReader(fileData), nil)
if err != nil {
return "", err
}
return s.generateURL(objectKey), nil
}
func (s TencentOss) PutBase64(base64Img string) (string, error) {
imageData, err := base64.StdEncoding.DecodeString(base64Img)
if err != nil {
return "", fmt.Errorf("error decoding base64:%v", err)
}
objectKey := fmt.Sprintf("%d.png", time.Now().UnixMicro())
// 上传文件字节数据
_, err = s.client.Object.Put(context.Background(), objectKey, bytes.NewReader(imageData), nil)
if err != nil {
return "", err
}
return s.generateURL(objectKey), nil
}
func (s TencentOss) Delete(fileURL string) error {
var objectKey string
if strings.HasPrefix(fileURL, "http") {
objectKey = filepath.Base(fileURL)
} else {
objectKey = fileURL
}
_, err := s.client.Object.Delete(context.Background(), objectKey)
return err
}
// generateURL 生成文件访问 URL
func (s TencentOss) generateURL(objectKey string) string {
if s.config.Domain != "" {
// 使用自定义域名
return fmt.Sprintf("%s/%s", strings.TrimSuffix(s.config.Domain, "/"), objectKey)
}
// 使用 COS 默认域名
return fmt.Sprintf("https://%s.cos.%s.myqcloud.com/%s", s.config.Bucket, s.config.Region, objectKey)
}
var _ Uploader = TencentOss{}
+1
View File
@@ -13,6 +13,7 @@ const Local = "local"
const Minio = "minio"
const QiNiu = "qiniu"
const AliYun = "aliyun"
const Tencent = "tencent"
type File struct {
Name string `json:"name"`
+89 -13
View File
@@ -8,32 +8,41 @@ package oss
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"fmt"
"geekai/core/types"
"strings"
logger2 "geekai/logger"
"geekai/log"
)
var logger = logger2.GetLogger()
var logger = log.GetLogger()
// 默认缩略图模板(本地存储格式)
const DefaultThumbTemplate = "?imageView2/4/w/{width}/h/{height}/q/75"
type UploaderManager struct {
local *LocalStorage
aliyun *AliYunOss
mini *MiniOss
qiniu *QiNiuOss
active string
local *LocalStorage
aliyun *AliYunOss
mini *MiniOss
qiniu *QiNiuOss
tencent *TencentOss
active string
ossConfig types.OSSConfig // 保存当前OSS配置
}
func NewUploaderManager(sysConfig *types.SystemConfig, local *LocalStorage, aliyun *AliYunOss, mini *MiniOss, qiniu *QiNiuOss) (*UploaderManager, error) {
func NewUploaderManager(sysConfig *types.SystemConfig, local *LocalStorage, aliyun *AliYunOss, mini *MiniOss, qiniu *QiNiuOss, tencent *TencentOss) (*UploaderManager, error) {
if sysConfig.OSS.Active == "" {
sysConfig.OSS.Active = Local
}
return &UploaderManager{
active: sysConfig.OSS.Active,
local: local,
aliyun: aliyun,
mini: mini,
qiniu: qiniu,
active: sysConfig.OSS.Active,
local: local,
aliyun: aliyun,
mini: mini,
qiniu: qiniu,
tencent: tencent,
ossConfig: sysConfig.OSS,
}, nil
}
@@ -47,6 +56,8 @@ func (m *UploaderManager) GetUploadHandler() Uploader {
return m.mini
case QiNiu:
return m.qiniu
case Tencent:
return m.tencent
}
return m.local
}
@@ -61,6 +72,71 @@ func (m *UploaderManager) UpdateConfig(config types.OSSConfig) {
m.mini.UpdateConfig(config.Minio)
case QiNiu:
m.qiniu.UpdateConfig(config.QiNiu)
case Tencent:
m.tencent.UpdateConfig(config.Tencent)
}
m.active = config.Active
m.ossConfig = config
}
// GetThumbURL 根据原始图片URL和尺寸生成缩略图URL
// 如果模板为空,使用默认模板(本地存储格式)
// 如果明确设置为空字符串(表示不支持缩略图),返回原图URL
func (m *UploaderManager) GetThumbURL(originalURL string, width, height int) string {
var template string
// 根据当前激活的存储引擎获取对应的模板
switch m.active {
case Local:
template = m.ossConfig.Local.ThumbTemplate
case AliYun:
template = m.ossConfig.AliYun.ThumbTemplate
case Minio:
template = m.ossConfig.Minio.ThumbTemplate
case QiNiu:
template = m.ossConfig.QiNiu.ThumbTemplate
case Tencent:
template = m.ossConfig.Tencent.ThumbTemplate
default:
template = m.ossConfig.Local.ThumbTemplate
}
// 如果模板为空,使用默认模板(兼容旧配置)
if template == "" {
template = DefaultThumbTemplate
}
// 替换变量
thumbURL := strings.ReplaceAll(template, "{width}", fmt.Sprintf("%d", width))
thumbURL = strings.ReplaceAll(thumbURL, "{height}", fmt.Sprintf("%d", height))
// 拼接原始URL和缩略图参数
return originalURL + thumbURL
}
// GetThumbTemplate 获取当前存储引擎的缩略图模板
func (m *UploaderManager) GetThumbTemplate() string {
var template string
switch m.active {
case Local:
template = m.ossConfig.Local.ThumbTemplate
case AliYun:
template = m.ossConfig.AliYun.ThumbTemplate
case Minio:
template = m.ossConfig.Minio.ThumbTemplate
case QiNiu:
template = m.ossConfig.QiNiu.ThumbTemplate
case Tencent:
template = m.ossConfig.Tencent.ThumbTemplate
default:
template = m.ossConfig.Local.ThumbTemplate
}
// 如果模板为空,返回默认模板
if template == "" {
return DefaultThumbTemplate
}
return template
}