✨ 新增正则和扩展模块
This commit is contained in:
803
server/service/app/extension.go
Normal file
803
server/service/app/extension.go
Normal file
@@ -0,0 +1,803 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user