Files
st/server/service/app/extension.go
2026-02-11 23:44:09 +08:00

804 lines
25 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}