804 lines
25 KiB
Go
804 lines
25 KiB
Go
package app
|
||
|
||
import (
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"strings"
|
||
"time"
|
||
|
||
"git.echol.cn/loser/st/server/global"
|
||
"git.echol.cn/loser/st/server/model/app"
|
||
"git.echol.cn/loser/st/server/model/app/request"
|
||
"git.echol.cn/loser/st/server/model/app/response"
|
||
"go.uber.org/zap"
|
||
"gorm.io/datatypes"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
type ExtensionService struct{}
|
||
|
||
// CreateExtension 创建/安装扩展
|
||
func (es *ExtensionService) CreateExtension(userID uint, req *request.CreateExtensionRequest) (*app.AIExtension, error) {
|
||
// 检查扩展是否已存在
|
||
var existing app.AIExtension
|
||
err := global.GVA_DB.Where("user_id = ? AND name = ?", userID, req.Name).First(&existing).Error
|
||
if err == nil {
|
||
return nil, errors.New("扩展已存在")
|
||
}
|
||
if err != gorm.ErrRecordNotFound {
|
||
return nil, err
|
||
}
|
||
|
||
// 序列化 JSON 字段
|
||
tagsJSON, _ := json.Marshal(req.Tags)
|
||
dependenciesJSON, _ := json.Marshal(req.Dependencies)
|
||
conflictsJSON, _ := json.Marshal(req.Conflicts)
|
||
manifestJSON, _ := json.Marshal(req.ManifestData)
|
||
assetsJSON, _ := json.Marshal(req.AssetsPaths)
|
||
settingsJSON, _ := json.Marshal(req.Settings)
|
||
optionsJSON, _ := json.Marshal(req.Options)
|
||
metadataJSON, _ := json.Marshal(req.Metadata)
|
||
|
||
extension := &app.AIExtension{
|
||
UserID: userID,
|
||
Name: req.Name,
|
||
DisplayName: req.DisplayName,
|
||
Version: req.Version,
|
||
Author: req.Author,
|
||
Description: req.Description,
|
||
Homepage: req.Homepage,
|
||
Repository: req.Repository,
|
||
License: req.License,
|
||
Tags: datatypes.JSON(tagsJSON),
|
||
ExtensionType: req.ExtensionType,
|
||
Category: req.Category,
|
||
Dependencies: datatypes.JSON(dependenciesJSON),
|
||
Conflicts: datatypes.JSON(conflictsJSON),
|
||
ManifestData: datatypes.JSON(manifestJSON),
|
||
ScriptPath: req.ScriptPath,
|
||
StylePath: req.StylePath,
|
||
AssetsPaths: datatypes.JSON(assetsJSON),
|
||
Settings: datatypes.JSON(settingsJSON),
|
||
Options: datatypes.JSON(optionsJSON),
|
||
IsEnabled: false,
|
||
IsInstalled: true,
|
||
IsSystemExt: false,
|
||
InstallSource: req.InstallSource,
|
||
SourceURL: req.SourceURL,
|
||
Branch: req.Branch,
|
||
AutoUpdate: req.AutoUpdate,
|
||
InstallDate: time.Now(),
|
||
Metadata: datatypes.JSON(metadataJSON),
|
||
}
|
||
|
||
if err := global.GVA_DB.Create(extension).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
global.GVA_LOG.Info("扩展安装成功", zap.Uint("extensionID", extension.ID), zap.String("name", extension.Name))
|
||
return extension, nil
|
||
}
|
||
|
||
// UpdateExtension 更新扩展
|
||
func (es *ExtensionService) UpdateExtension(userID, extensionID uint, req *request.UpdateExtensionRequest) error {
|
||
var extension app.AIExtension
|
||
if err := global.GVA_DB.Where("id = ? AND user_id = ?", extensionID, userID).First(&extension).Error; err != nil {
|
||
return errors.New("扩展不存在")
|
||
}
|
||
|
||
// 系统内置扩展不允许修改
|
||
if extension.IsSystemExt {
|
||
return errors.New("系统内置扩展不允许修改")
|
||
}
|
||
|
||
updates := map[string]interface{}{}
|
||
if req.DisplayName != "" {
|
||
updates["display_name"] = req.DisplayName
|
||
}
|
||
if req.Description != "" {
|
||
updates["description"] = req.Description
|
||
}
|
||
if req.Settings != nil {
|
||
settingsJSON, _ := json.Marshal(req.Settings)
|
||
updates["settings"] = datatypes.JSON(settingsJSON)
|
||
}
|
||
if req.Options != nil {
|
||
optionsJSON, _ := json.Marshal(req.Options)
|
||
updates["options"] = datatypes.JSON(optionsJSON)
|
||
}
|
||
if req.Metadata != nil {
|
||
metadataJSON, _ := json.Marshal(req.Metadata)
|
||
updates["metadata"] = datatypes.JSON(metadataJSON)
|
||
}
|
||
|
||
if err := global.GVA_DB.Model(&extension).Updates(updates).Error; err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// DeleteExtension 删除/卸载扩展
|
||
func (es *ExtensionService) DeleteExtension(userID, extensionID uint, deleteFiles bool) error {
|
||
var extension app.AIExtension
|
||
if err := global.GVA_DB.Where("id = ? AND user_id = ?", extensionID, userID).First(&extension).Error; err != nil {
|
||
return errors.New("扩展不存在")
|
||
}
|
||
|
||
// 系统内置扩展不允许删除
|
||
if extension.IsSystemExt {
|
||
return errors.New("系统内置扩展不允许删除")
|
||
}
|
||
|
||
// TODO: 如果 deleteFiles=true,删除扩展文件
|
||
// 这需要文件系统支持
|
||
|
||
// 删除扩展(配置已经在扩展记录的 Settings 字段中,无需单独删除)
|
||
if err := global.GVA_DB.Delete(&extension).Error; err != nil {
|
||
return err
|
||
}
|
||
|
||
global.GVA_LOG.Info("扩展卸载成功", zap.Uint("extensionID", extensionID))
|
||
return nil
|
||
}
|
||
|
||
// GetExtension 获取扩展详情
|
||
func (es *ExtensionService) GetExtension(userID, extensionID uint) (*app.AIExtension, error) {
|
||
var extension app.AIExtension
|
||
if err := global.GVA_DB.Where("id = ? AND user_id = ?", extensionID, userID).First(&extension).Error; err != nil {
|
||
return nil, errors.New("扩展不存在")
|
||
}
|
||
return &extension, nil
|
||
}
|
||
|
||
// GetExtensionList 获取扩展列表
|
||
func (es *ExtensionService) GetExtensionList(userID uint, req *request.ExtensionListRequest) (*response.ExtensionListResponse, error) {
|
||
var extensions []app.AIExtension
|
||
var total int64
|
||
|
||
db := global.GVA_DB.Model(&app.AIExtension{}).Where("user_id = ?", userID)
|
||
|
||
// 过滤条件
|
||
if req.Name != "" {
|
||
db = db.Where("name ILIKE ? OR display_name ILIKE ?", "%"+req.Name+"%", "%"+req.Name+"%")
|
||
}
|
||
if req.ExtensionType != "" {
|
||
db = db.Where("extension_type = ?", req.ExtensionType)
|
||
}
|
||
if req.Category != "" {
|
||
db = db.Where("category = ?", req.Category)
|
||
}
|
||
if req.IsEnabled != nil {
|
||
db = db.Where("is_enabled = ?", *req.IsEnabled)
|
||
}
|
||
if req.IsInstalled != nil {
|
||
db = db.Where("is_installed = ?", *req.IsInstalled)
|
||
}
|
||
if req.Tag != "" {
|
||
db = db.Where("tags @> ?", fmt.Sprintf(`["%s"]`, req.Tag))
|
||
}
|
||
|
||
// 统计总数
|
||
if err := db.Count(&total).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 分页查询
|
||
if err := db.Scopes(req.Paginate()).Order("created_at DESC").Find(&extensions).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 转换响应
|
||
result := make([]response.ExtensionResponse, 0, len(extensions))
|
||
for i := range extensions {
|
||
result = append(result, response.ToExtensionResponse(&extensions[i]))
|
||
}
|
||
|
||
return &response.ExtensionListResponse{
|
||
List: result,
|
||
Total: total,
|
||
Page: req.Page,
|
||
PageSize: req.PageSize,
|
||
}, nil
|
||
}
|
||
|
||
// ToggleExtension 启用/禁用扩展
|
||
func (es *ExtensionService) ToggleExtension(userID, extensionID uint, isEnabled bool) error {
|
||
var extension app.AIExtension
|
||
if err := global.GVA_DB.Where("id = ? AND user_id = ?", extensionID, userID).First(&extension).Error; err != nil {
|
||
return errors.New("扩展不存在")
|
||
}
|
||
|
||
// 检查依赖
|
||
if isEnabled {
|
||
if err := es.checkDependencies(userID, &extension); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
// 检查冲突
|
||
if isEnabled {
|
||
if err := es.checkConflicts(userID, &extension); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
updates := map[string]interface{}{
|
||
"is_enabled": isEnabled,
|
||
}
|
||
if isEnabled {
|
||
updates["last_enabled"] = time.Now()
|
||
}
|
||
|
||
if err := global.GVA_DB.Model(&extension).Updates(updates).Error; err != nil {
|
||
return err
|
||
}
|
||
|
||
global.GVA_LOG.Info("扩展状态更新", zap.Uint("extensionID", extensionID), zap.Bool("enabled", isEnabled))
|
||
return nil
|
||
}
|
||
|
||
// UpdateExtensionSettings 更新扩展配置
|
||
func (es *ExtensionService) UpdateExtensionSettings(userID, extensionID uint, settings map[string]interface{}) error {
|
||
var extension app.AIExtension
|
||
if err := global.GVA_DB.Where("id = ? AND user_id = ?", extensionID, userID).First(&extension).Error; err != nil {
|
||
return errors.New("扩展不存在")
|
||
}
|
||
|
||
settingsJSON, err := json.Marshal(settings)
|
||
if err != nil {
|
||
return errors.New("序列化配置失败")
|
||
}
|
||
|
||
// 直接更新扩展表的 settings 字段
|
||
return global.GVA_DB.Model(&extension).Update("settings", datatypes.JSON(settingsJSON)).Error
|
||
}
|
||
|
||
// GetExtensionSettings 获取扩展配置
|
||
func (es *ExtensionService) GetExtensionSettings(userID, extensionID uint) (map[string]interface{}, error) {
|
||
// 获取扩展信息
|
||
var extension app.AIExtension
|
||
if err := global.GVA_DB.Where("id = ? AND user_id = ?", extensionID, userID).First(&extension).Error; err != nil {
|
||
return nil, errors.New("扩展不存在")
|
||
}
|
||
|
||
// 从扩展的 Settings 字段读取用户配置
|
||
var settings map[string]interface{}
|
||
if len(extension.Settings) > 0 {
|
||
if err := json.Unmarshal([]byte(extension.Settings), &settings); err != nil {
|
||
return nil, errors.New("解析配置失败: " + err.Error())
|
||
}
|
||
}
|
||
|
||
// 如果 ManifestData 中有默认配置,合并进来
|
||
if len(extension.ManifestData) > 0 {
|
||
var manifest map[string]interface{}
|
||
if err := json.Unmarshal([]byte(extension.ManifestData), &manifest); err == nil {
|
||
if manifestSettings, ok := manifest["settings"].(map[string]interface{}); ok && manifestSettings != nil {
|
||
// 只添加用户未设置的默认值
|
||
if settings == nil {
|
||
settings = make(map[string]interface{})
|
||
}
|
||
for k, v := range manifestSettings {
|
||
if _, exists := settings[k]; !exists {
|
||
settings[k] = v
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if settings == nil {
|
||
settings = make(map[string]interface{})
|
||
}
|
||
|
||
return settings, nil
|
||
}
|
||
|
||
// UpdateExtensionStats 更新扩展统计
|
||
func (es *ExtensionService) UpdateExtensionStats(userID, extensionID uint, action string, value int) error {
|
||
var extension app.AIExtension
|
||
if err := global.GVA_DB.Where("id = ? AND user_id = ?", extensionID, userID).First(&extension).Error; err != nil {
|
||
return errors.New("扩展不存在")
|
||
}
|
||
|
||
updates := map[string]interface{}{}
|
||
switch action {
|
||
case "usage":
|
||
updates["usage_count"] = gorm.Expr("usage_count + ?", value)
|
||
case "error":
|
||
updates["error_count"] = gorm.Expr("error_count + ?", value)
|
||
case "load":
|
||
// 计算平均加载时间
|
||
newAvg := (extension.LoadTime*extension.UsageCount + value) / (extension.UsageCount + 1)
|
||
updates["load_time"] = newAvg
|
||
default:
|
||
return errors.New("未知的统计类型")
|
||
}
|
||
|
||
return global.GVA_DB.Model(&extension).Updates(updates).Error
|
||
}
|
||
|
||
// GetExtensionManifest 获取扩展 manifest
|
||
func (es *ExtensionService) GetExtensionManifest(userID, extensionID uint) (*response.ExtensionManifestResponse, error) {
|
||
var extension app.AIExtension
|
||
if err := global.GVA_DB.Where("id = ? AND user_id = ?", extensionID, userID).First(&extension).Error; err != nil {
|
||
return nil, errors.New("扩展不存在")
|
||
}
|
||
|
||
var manifestData map[string]interface{}
|
||
if extension.ManifestData != nil {
|
||
_ = json.Unmarshal([]byte(extension.ManifestData), &manifestData)
|
||
}
|
||
|
||
// 从 manifestData 构建响应
|
||
manifest := &response.ExtensionManifestResponse{
|
||
Name: extension.Name,
|
||
DisplayName: extension.DisplayName,
|
||
Version: extension.Version,
|
||
Description: extension.Description,
|
||
Author: extension.Author,
|
||
Homepage: extension.Homepage,
|
||
Repository: extension.Repository,
|
||
License: extension.License,
|
||
Type: extension.ExtensionType,
|
||
Category: extension.Category,
|
||
Entry: extension.ScriptPath,
|
||
Style: extension.StylePath,
|
||
}
|
||
|
||
// 解析数组和对象
|
||
if extension.Tags != nil {
|
||
_ = json.Unmarshal([]byte(extension.Tags), &manifest.Tags)
|
||
}
|
||
if extension.Dependencies != nil {
|
||
_ = json.Unmarshal([]byte(extension.Dependencies), &manifest.Dependencies)
|
||
}
|
||
if extension.Conflicts != nil {
|
||
_ = json.Unmarshal([]byte(extension.Conflicts), &manifest.Conflicts)
|
||
}
|
||
if extension.AssetsPaths != nil {
|
||
_ = json.Unmarshal([]byte(extension.AssetsPaths), &manifest.Assets)
|
||
}
|
||
if extension.Settings != nil {
|
||
_ = json.Unmarshal([]byte(extension.Settings), &manifest.Settings)
|
||
}
|
||
if extension.Options != nil {
|
||
_ = json.Unmarshal([]byte(extension.Options), &manifest.Options)
|
||
}
|
||
if extension.Metadata != nil {
|
||
_ = json.Unmarshal([]byte(extension.Metadata), &manifest.Metadata)
|
||
}
|
||
|
||
return manifest, nil
|
||
}
|
||
|
||
// ImportExtension 导入扩展(从文件)
|
||
func (es *ExtensionService) ImportExtension(userID uint, manifestData []byte) (*app.AIExtension, error) {
|
||
// 解析 manifest.json
|
||
var manifest app.AIExtensionManifest
|
||
if err := json.Unmarshal(manifestData, &manifest); err != nil {
|
||
return nil, errors.New("无效的 manifest.json 格式")
|
||
}
|
||
|
||
// 验证必填字段
|
||
if manifest.Name == "" || manifest.Version == "" {
|
||
return nil, errors.New("manifest 缺少必填字段")
|
||
}
|
||
|
||
// 构建创建请求
|
||
req := &request.CreateExtensionRequest{
|
||
Name: manifest.Name,
|
||
DisplayName: manifest.DisplayName,
|
||
Version: manifest.Version,
|
||
Author: manifest.Author,
|
||
Description: manifest.Description,
|
||
Homepage: manifest.Homepage,
|
||
Repository: manifest.Repository,
|
||
License: manifest.License,
|
||
Tags: manifest.Tags,
|
||
ExtensionType: manifest.Type,
|
||
Category: manifest.Category,
|
||
Dependencies: manifest.Dependencies,
|
||
Conflicts: manifest.Conflicts,
|
||
ScriptPath: manifest.Entry,
|
||
StylePath: manifest.Style,
|
||
AssetsPaths: manifest.Assets,
|
||
Settings: manifest.Settings,
|
||
Options: manifest.Options,
|
||
InstallSource: "file",
|
||
Metadata: manifest.Metadata,
|
||
}
|
||
|
||
// 将 manifest 原始数据也保存
|
||
var manifestMap map[string]interface{}
|
||
_ = json.Unmarshal(manifestData, &manifestMap)
|
||
req.ManifestData = manifestMap
|
||
|
||
return es.CreateExtension(userID, req)
|
||
}
|
||
|
||
// ExportExtension 导出扩展
|
||
func (es *ExtensionService) ExportExtension(userID, extensionID uint) ([]byte, error) {
|
||
manifest, err := es.GetExtensionManifest(userID, extensionID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return json.MarshalIndent(manifest, "", " ")
|
||
}
|
||
|
||
// checkDependencies 检查扩展依赖
|
||
func (es *ExtensionService) checkDependencies(userID uint, extension *app.AIExtension) error {
|
||
if extension.Dependencies == nil || len(extension.Dependencies) == 0 {
|
||
return nil
|
||
}
|
||
|
||
var dependencies map[string]string
|
||
_ = json.Unmarshal([]byte(extension.Dependencies), &dependencies)
|
||
|
||
for depName := range dependencies {
|
||
var depExt app.AIExtension
|
||
err := global.GVA_DB.Where("user_id = ? AND name = ? AND is_enabled = true", userID, depName).First(&depExt).Error
|
||
if err != nil {
|
||
return fmt.Errorf("缺少依赖扩展: %s", depName)
|
||
}
|
||
// TODO: 检查版本号是否满足要求
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// checkConflicts 检查扩展冲突
|
||
func (es *ExtensionService) checkConflicts(userID uint, extension *app.AIExtension) error {
|
||
if extension.Conflicts == nil || len(extension.Conflicts) == 0 {
|
||
return nil
|
||
}
|
||
|
||
var conflicts []string
|
||
_ = json.Unmarshal([]byte(extension.Conflicts), &conflicts)
|
||
|
||
for _, conflictName := range conflicts {
|
||
var conflictExt app.AIExtension
|
||
err := global.GVA_DB.Where("user_id = ? AND name = ? AND is_enabled = true", userID, conflictName).First(&conflictExt).Error
|
||
if err == nil {
|
||
return fmt.Errorf("扩展 %s 与 %s 冲突", extension.Name, conflictName)
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetEnabledExtensions 获取用户启用的所有扩展(用于前端加载)
|
||
func (es *ExtensionService) GetEnabledExtensions(userID uint) ([]response.ExtensionResponse, error) {
|
||
var extensions []app.AIExtension
|
||
if err := global.GVA_DB.Where("user_id = ? AND is_enabled = true AND is_installed = true", userID).
|
||
Order("created_at ASC").Find(&extensions).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
result := make([]response.ExtensionResponse, 0, len(extensions))
|
||
for i := range extensions {
|
||
result = append(result, response.ToExtensionResponse(&extensions[i]))
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// InstallExtensionFromURL 智能安装扩展(自动识别 Git URL 或 Manifest URL)
|
||
func (es *ExtensionService) InstallExtensionFromURL(userID uint, url string, branch string) (*app.AIExtension, error) {
|
||
global.GVA_LOG.Info("开始从 URL 安装扩展", zap.String("url", url), zap.String("branch", branch))
|
||
|
||
// 智能识别 URL 类型
|
||
if isGitURL(url) {
|
||
global.GVA_LOG.Info("检测到 Git 仓库 URL,使用 Git 安装")
|
||
if branch == "" {
|
||
branch = "main"
|
||
}
|
||
return es.InstallExtensionFromGit(userID, url, branch)
|
||
}
|
||
|
||
// 否则作为 manifest.json URL 处理
|
||
global.GVA_LOG.Info("作为 Manifest URL 处理")
|
||
return es.downloadAndInstallFromManifestURL(userID, url)
|
||
}
|
||
|
||
// isGitURL 判断是否为 Git 仓库 URL
|
||
func isGitURL(url string) bool {
|
||
// Git 仓库特征:
|
||
// 1. 包含 .git 后缀
|
||
// 2. 包含常见的 Git 托管平台域名(github.com, gitlab.com, gitee.com 等)
|
||
// 3. 不以 /manifest.json 或 .json 结尾
|
||
|
||
url = strings.ToLower(url)
|
||
|
||
// 如果明确以 .json 结尾,不是 Git URL
|
||
if strings.HasSuffix(url, ".json") {
|
||
return false
|
||
}
|
||
|
||
// 如果包含 .git 后缀,是 Git URL
|
||
if strings.HasSuffix(url, ".git") {
|
||
return true
|
||
}
|
||
|
||
// 检查是否包含 Git 托管平台域名
|
||
gitHosts := []string{
|
||
"github.com",
|
||
"gitlab.com",
|
||
"gitee.com",
|
||
"bitbucket.org",
|
||
"gitea.io",
|
||
"codeberg.org",
|
||
}
|
||
|
||
for _, host := range gitHosts {
|
||
if strings.Contains(url, host) {
|
||
// 如果包含 Git 平台且不是 raw 文件 URL,则认为是 Git 仓库
|
||
if !strings.Contains(url, "/raw/") && !strings.Contains(url, "/blob/") {
|
||
return true
|
||
}
|
||
}
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
// downloadAndInstallFromManifestURL 从 Manifest URL 下载并安装
|
||
func (es *ExtensionService) downloadAndInstallFromManifestURL(userID uint, manifestURL string) (*app.AIExtension, error) {
|
||
// 创建 HTTP 客户端
|
||
client := &http.Client{
|
||
Timeout: 30 * time.Second,
|
||
}
|
||
|
||
// 下载 manifest.json
|
||
resp, err := client.Get(manifestURL)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("下载 manifest.json 失败: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode != http.StatusOK {
|
||
return nil, fmt.Errorf("下载 manifest.json 失败: HTTP %d", resp.StatusCode)
|
||
}
|
||
|
||
// 读取响应内容
|
||
manifestData, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("读取 manifest.json 失败: %w", err)
|
||
}
|
||
|
||
// 解析 manifest
|
||
var manifest app.AIExtensionManifest
|
||
if err := json.Unmarshal(manifestData, &manifest); err != nil {
|
||
return nil, fmt.Errorf("解析 manifest.json 失败: %w", err)
|
||
}
|
||
|
||
// 验证必填字段
|
||
if manifest.Name == "" {
|
||
return nil, errors.New("manifest.json 缺少 name 字段")
|
||
}
|
||
|
||
// 检查扩展是否已存在
|
||
var existing app.AIExtension
|
||
err = global.GVA_DB.Where("user_id = ? AND name = ?", userID, manifest.Name).First(&existing).Error
|
||
if err == nil {
|
||
return nil, fmt.Errorf("扩展 %s 已安装", manifest.Name)
|
||
}
|
||
if err != gorm.ErrRecordNotFound {
|
||
return nil, err
|
||
}
|
||
|
||
// 将 manifest 转换为 map[string]interface{}
|
||
var manifestMap map[string]interface{}
|
||
if err := json.Unmarshal(manifestData, &manifestMap); err != nil {
|
||
return nil, fmt.Errorf("转换 manifest 失败: %w", err)
|
||
}
|
||
|
||
// 构建创建请求
|
||
createReq := &request.CreateExtensionRequest{
|
||
Name: manifest.Name,
|
||
DisplayName: manifest.DisplayName,
|
||
Version: manifest.Version,
|
||
Author: manifest.Author,
|
||
Description: manifest.Description,
|
||
Homepage: manifest.Homepage,
|
||
Repository: manifest.Repository, // 使用 manifest 中的 repository
|
||
License: manifest.License,
|
||
Tags: manifest.Tags,
|
||
ExtensionType: manifest.Type,
|
||
Category: manifest.Category,
|
||
Dependencies: manifest.Dependencies,
|
||
Conflicts: manifest.Conflicts,
|
||
ManifestData: manifestMap,
|
||
ScriptPath: manifest.Entry,
|
||
StylePath: manifest.Style,
|
||
AssetsPaths: manifest.Assets,
|
||
Settings: manifest.Settings,
|
||
Options: manifest.Options,
|
||
InstallSource: "url",
|
||
SourceURL: manifestURL, // 记录原始 URL 用于更新
|
||
AutoUpdate: manifest.AutoUpdate,
|
||
Metadata: nil,
|
||
}
|
||
|
||
// 确保扩展类型有效
|
||
if createReq.ExtensionType == "" {
|
||
createReq.ExtensionType = "ui"
|
||
}
|
||
|
||
// 创建扩展
|
||
extension, err := es.CreateExtension(userID, createReq)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建扩展失败: %w", err)
|
||
}
|
||
|
||
global.GVA_LOG.Info("从 URL 安装扩展成功",
|
||
zap.Uint("extensionID", extension.ID),
|
||
zap.String("name", extension.Name),
|
||
zap.String("url", manifestURL))
|
||
|
||
return extension, nil
|
||
}
|
||
|
||
// UpgradeExtension 升级扩展版本(根据安装来源自动选择更新方式)
|
||
func (es *ExtensionService) UpgradeExtension(userID, extensionID uint, force bool) (*app.AIExtension, error) {
|
||
// 获取扩展信息
|
||
var extension app.AIExtension
|
||
if err := global.GVA_DB.Where("id = ? AND user_id = ?", extensionID, userID).First(&extension).Error; err != nil {
|
||
return nil, errors.New("扩展不存在")
|
||
}
|
||
|
||
global.GVA_LOG.Info("开始升级扩展",
|
||
zap.Uint("extensionID", extensionID),
|
||
zap.String("name", extension.Name),
|
||
zap.String("installSource", extension.InstallSource),
|
||
zap.String("sourceUrl", extension.SourceURL))
|
||
|
||
// 根据安装来源选择更新方式
|
||
switch extension.InstallSource {
|
||
case "git":
|
||
return es.updateExtensionFromGit(userID, &extension, force)
|
||
case "url":
|
||
return es.updateExtensionFromURL(userID, &extension)
|
||
default:
|
||
return nil, fmt.Errorf("不支持的安装来源: %s", extension.InstallSource)
|
||
}
|
||
}
|
||
|
||
// updateExtensionFromGit 从 Git 仓库更新扩展
|
||
func (es *ExtensionService) updateExtensionFromGit(userID uint, extension *app.AIExtension, force bool) (*app.AIExtension, error) {
|
||
if extension.SourceURL == "" {
|
||
return nil, errors.New("缺少 Git 仓库 URL")
|
||
}
|
||
|
||
global.GVA_LOG.Info("从 Git 更新扩展",
|
||
zap.String("name", extension.Name),
|
||
zap.String("sourceUrl", extension.SourceURL),
|
||
zap.String("branch", extension.Branch))
|
||
|
||
// 重新克隆(简单方式,避免处理本地修改)
|
||
return es.InstallExtensionFromGit(userID, extension.SourceURL, extension.Branch)
|
||
}
|
||
|
||
// updateExtensionFromURL 从 URL 更新扩展(重新下载 manifest.json)
|
||
func (es *ExtensionService) updateExtensionFromURL(userID uint, extension *app.AIExtension) (*app.AIExtension, error) {
|
||
if extension.SourceURL == "" {
|
||
return nil, errors.New("缺少 Manifest URL")
|
||
}
|
||
|
||
global.GVA_LOG.Info("从 URL 更新扩展",
|
||
zap.String("name", extension.Name),
|
||
zap.String("sourceUrl", extension.SourceURL))
|
||
|
||
// 重新下载并安装
|
||
return es.downloadAndInstallFromManifestURL(userID, extension.SourceURL)
|
||
}
|
||
|
||
// InstallExtensionFromGit 从 Git URL 安装扩展
|
||
func (es *ExtensionService) InstallExtensionFromGit(userID uint, gitUrl, branch string) (*app.AIExtension, error) {
|
||
// 验证 Git URL
|
||
if !strings.Contains(gitUrl, "://") && !strings.HasSuffix(gitUrl, ".git") {
|
||
return nil, errors.New("无效的 Git URL")
|
||
}
|
||
|
||
// 创建临时目录
|
||
tempDir, err := os.MkdirTemp("", "extension-*")
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建临时目录失败: %w", err)
|
||
}
|
||
defer os.RemoveAll(tempDir) // 确保清理临时目录
|
||
|
||
global.GVA_LOG.Info("开始从 Git 克隆扩展",
|
||
zap.String("gitUrl", gitUrl),
|
||
zap.String("branch", branch),
|
||
zap.String("tempDir", tempDir))
|
||
|
||
// 执行 git clone
|
||
cmd := exec.Command("git", "clone", "--depth=1", "--branch="+branch, gitUrl, tempDir)
|
||
output, err := cmd.CombinedOutput()
|
||
if err != nil {
|
||
global.GVA_LOG.Error("Git clone 失败",
|
||
zap.String("gitUrl", gitUrl),
|
||
zap.String("output", string(output)),
|
||
zap.Error(err))
|
||
return nil, fmt.Errorf("Git clone 失败: %s", string(output))
|
||
}
|
||
|
||
// 读取 manifest.json
|
||
manifestPath := filepath.Join(tempDir, "manifest.json")
|
||
manifestData, err := os.ReadFile(manifestPath)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("读取 manifest.json 失败: %w", err)
|
||
}
|
||
|
||
// 解析 manifest
|
||
var manifest app.AIExtensionManifest
|
||
if err := json.Unmarshal(manifestData, &manifest); err != nil {
|
||
return nil, fmt.Errorf("解析 manifest.json 失败: %w", err)
|
||
}
|
||
|
||
// 检查扩展是否已存在
|
||
var existing app.AIExtension
|
||
err = global.GVA_DB.Where("user_id = ? AND name = ?", userID, manifest.Name).First(&existing).Error
|
||
if err == nil {
|
||
return nil, fmt.Errorf("扩展 %s 已安装", manifest.Name)
|
||
}
|
||
if err != gorm.ErrRecordNotFound {
|
||
return nil, err
|
||
}
|
||
|
||
// 将 manifest 转换为 map[string]interface{}
|
||
var manifestMap map[string]interface{}
|
||
if err := json.Unmarshal(manifestData, &manifestMap); err != nil {
|
||
return nil, fmt.Errorf("转换 manifest 失败: %w", err)
|
||
}
|
||
|
||
// 构建创建请求
|
||
createReq := &request.CreateExtensionRequest{
|
||
Name: manifest.Name,
|
||
DisplayName: manifest.DisplayName,
|
||
Version: manifest.Version,
|
||
Author: manifest.Author,
|
||
Description: manifest.Description,
|
||
Homepage: manifest.Homepage,
|
||
Repository: manifest.Repository, // 使用 manifest 中的 repository
|
||
License: manifest.License,
|
||
Tags: manifest.Tags,
|
||
ExtensionType: manifest.Type,
|
||
Category: manifest.Category,
|
||
Dependencies: manifest.Dependencies,
|
||
Conflicts: manifest.Conflicts,
|
||
ManifestData: manifestMap,
|
||
ScriptPath: manifest.Entry,
|
||
StylePath: manifest.Style,
|
||
AssetsPaths: manifest.Assets,
|
||
Settings: manifest.Settings,
|
||
Options: manifest.Options,
|
||
InstallSource: "git",
|
||
SourceURL: gitUrl, // 记录 Git URL 用于更新
|
||
Branch: branch, // 记录分支
|
||
AutoUpdate: manifest.AutoUpdate,
|
||
Metadata: manifest.Metadata,
|
||
}
|
||
|
||
// 创建扩展记录
|
||
extension, err := es.CreateExtension(userID, createReq)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建扩展记录失败: %w", err)
|
||
}
|
||
|
||
global.GVA_LOG.Info("从 Git 安装扩展成功",
|
||
zap.Uint("extensionID", extension.ID),
|
||
zap.String("name", extension.Name),
|
||
zap.String("version", extension.Version))
|
||
|
||
return extension, nil
|
||
}
|