Files
st/server/service/app/extension.go
2026-02-14 06:20:05 +08:00

411 lines
11 KiB
Go
Raw Permalink 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"
"path"
"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 (s *ExtensionService) CreateExtension(userID uint, req *request.CreateExtensionRequest) (*app.AIExtension, error) {
// 检查扩展名是否重复
var count int64
global.GVA_DB.Model(&app.AIExtension{}).Where("user_id = ? AND name = ?", userID, req.Name).Count(&count)
if count > 0 {
return nil, errors.New("同名扩展已存在")
}
now := time.Now().Unix()
ext := 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,
ExtensionType: req.ExtensionType,
Category: req.Category,
ScriptPath: req.ScriptPath,
StylePath: req.StylePath,
InstallSource: req.InstallSource,
SourceURL: req.SourceURL,
Branch: req.Branch,
IsInstalled: true,
IsEnabled: false,
InstallDate: &now,
}
// 设置默认值
if ext.Version == "" {
ext.Version = "1.0.0"
}
if ext.Branch == "" {
ext.Branch = "main"
}
// 序列化 JSON 字段
if req.Tags != nil {
ext.Tags = mustMarshal(req.Tags)
}
if req.Dependencies != nil {
ext.Dependencies = mustMarshal(req.Dependencies)
}
if req.Conflicts != nil {
ext.Conflicts = mustMarshal(req.Conflicts)
}
if req.AssetPaths != nil {
ext.AssetPaths = mustMarshal(req.AssetPaths)
}
if req.ManifestData != nil {
ext.ManifestData = mustMarshal(req.ManifestData)
}
if req.Settings != nil {
ext.Settings = mustMarshal(req.Settings)
}
if req.Options != nil {
ext.Options = mustMarshal(req.Options)
}
if req.Metadata != nil {
ext.Metadata = mustMarshal(req.Metadata)
}
if err := global.GVA_DB.Create(&ext).Error; err != nil {
global.GVA_LOG.Error("创建扩展失败", zap.Error(err))
return nil, err
}
return &ext, nil
}
// UpdateExtension 更新扩展
func (s *ExtensionService) UpdateExtension(userID, extID uint, req *request.UpdateExtensionRequest) error {
var ext app.AIExtension
if err := global.GVA_DB.Where("id = ? AND user_id = ?", extID, userID).First(&ext).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("扩展不存在")
}
return err
}
updates := make(map[string]interface{})
if req.DisplayName != "" {
updates["display_name"] = req.DisplayName
}
if req.Description != "" {
updates["description"] = req.Description
}
if req.Version != "" {
updates["version"] = req.Version
}
if req.Author != "" {
updates["author"] = req.Author
}
if req.Homepage != "" {
updates["homepage"] = req.Homepage
}
if req.Repository != "" {
updates["repository"] = req.Repository
}
if req.License != "" {
updates["license"] = req.License
}
if req.ExtensionType != "" {
updates["extension_type"] = req.ExtensionType
}
if req.Category != "" {
updates["category"] = req.Category
}
if req.ScriptPath != "" {
updates["script_path"] = req.ScriptPath
}
if req.StylePath != "" {
updates["style_path"] = req.StylePath
}
if req.Tags != nil {
updates["tags"] = datatypes.JSON(mustMarshal(req.Tags))
}
if req.Dependencies != nil {
updates["dependencies"] = datatypes.JSON(mustMarshal(req.Dependencies))
}
if req.Conflicts != nil {
updates["conflicts"] = datatypes.JSON(mustMarshal(req.Conflicts))
}
if req.AssetPaths != nil {
updates["asset_paths"] = datatypes.JSON(mustMarshal(req.AssetPaths))
}
if req.ManifestData != nil {
updates["manifest_data"] = datatypes.JSON(mustMarshal(req.ManifestData))
}
if req.Settings != nil {
updates["settings"] = datatypes.JSON(mustMarshal(req.Settings))
}
if req.Options != nil {
updates["options"] = datatypes.JSON(mustMarshal(req.Options))
}
if req.Metadata != nil {
updates["metadata"] = datatypes.JSON(mustMarshal(req.Metadata))
}
if len(updates) == 0 {
return nil
}
return global.GVA_DB.Model(&app.AIExtension{}).Where("id = ? AND user_id = ?", extID, userID).Updates(updates).Error
}
// DeleteExtension 删除扩展
func (s *ExtensionService) DeleteExtension(userID, extID uint) error {
result := global.GVA_DB.Where("id = ? AND user_id = ?", extID, userID).Delete(&app.AIExtension{})
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return errors.New("扩展不存在或无权删除")
}
return nil
}
// GetExtension 获取扩展详情
func (s *ExtensionService) GetExtension(userID, extID uint) (*app.AIExtension, error) {
var ext app.AIExtension
if err := global.GVA_DB.Where("id = ? AND user_id = ?", extID, userID).First(&ext).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("扩展不存在")
}
return nil, err
}
return &ext, nil
}
// GetExtensionList 获取扩展列表
func (s *ExtensionService) GetExtensionList(userID uint, req *request.ExtensionListRequest) ([]app.AIExtension, int64, error) {
var extensions []app.AIExtension
var total int64
db := global.GVA_DB.Model(&app.AIExtension{}).Where("user_id = ?", userID)
// 关键词搜索
if req.Keyword != "" {
keyword := "%" + req.Keyword + "%"
db = db.Where("(name ILIKE ? OR display_name ILIKE ? OR description ILIKE ?)", keyword, keyword, keyword)
}
// 名称过滤
if req.Name != "" {
db = db.Where("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 @> ?", datatypes.JSON(mustMarshal([]string{req.Tag})))
}
// 获取总数
if err := db.Count(&total).Error; err != nil {
return nil, 0, err
}
// 分页查询
offset := (req.Page - 1) * req.PageSize
if err := db.Order("created_at DESC").Offset(offset).Limit(req.PageSize).Find(&extensions).Error; err != nil {
return nil, 0, err
}
return extensions, total, nil
}
// GetEnabledExtensions 获取启用的扩展列表
func (s *ExtensionService) GetEnabledExtensions(userID uint) ([]app.AIExtension, error) {
var extensions []app.AIExtension
if err := global.GVA_DB.Where("user_id = ? AND is_enabled = ? AND is_installed = ?", userID, true, true).
Order("created_at ASC").Find(&extensions).Error; err != nil {
return nil, err
}
return extensions, nil
}
// ToggleExtension 启用/禁用扩展
func (s *ExtensionService) ToggleExtension(userID, extID uint, isEnabled bool) error {
updates := map[string]interface{}{
"is_enabled": isEnabled,
}
if isEnabled {
now := time.Now().Unix()
updates["last_enabled"] = &now
}
result := global.GVA_DB.Model(&app.AIExtension{}).Where("id = ? AND user_id = ?", extID, userID).Updates(updates)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return errors.New("扩展不存在")
}
return nil
}
// GetExtensionSettings 获取扩展设置
func (s *ExtensionService) GetExtensionSettings(userID, extID uint) (map[string]interface{}, error) {
var ext app.AIExtension
if err := global.GVA_DB.Select("settings").Where("id = ? AND user_id = ?", extID, userID).First(&ext).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("扩展不存在")
}
return nil, err
}
var settings map[string]interface{}
if len(ext.Settings) > 0 {
_ = json.Unmarshal(ext.Settings, &settings)
}
if settings == nil {
settings = make(map[string]interface{})
}
return settings, nil
}
// UpdateExtensionSettings 更新扩展设置
func (s *ExtensionService) UpdateExtensionSettings(userID, extID uint, settings map[string]interface{}) error {
result := global.GVA_DB.Model(&app.AIExtension{}).
Where("id = ? AND user_id = ?", extID, userID).
Update("settings", datatypes.JSON(mustMarshal(settings)))
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return errors.New("扩展不存在")
}
return nil
}
// GetExtensionManifest 获取扩展 manifest
func (s *ExtensionService) GetExtensionManifest(userID, extID uint) (map[string]interface{}, error) {
var ext app.AIExtension
if err := global.GVA_DB.Select("manifest_data").Where("id = ? AND user_id = ?", extID, userID).First(&ext).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("扩展不存在")
}
return nil, err
}
var manifest map[string]interface{}
if len(ext.ManifestData) > 0 {
_ = json.Unmarshal(ext.ManifestData, &manifest)
}
if manifest == nil {
manifest = make(map[string]interface{})
}
return manifest, nil
}
// UpdateExtensionStats 更新扩展统计信息
func (s *ExtensionService) UpdateExtensionStats(userID, extID uint, action string, value int) error {
var updateExpr string
switch action {
case "usage":
updateExpr = "usage_count"
case "error":
updateExpr = "error_count"
case "load":
// load 直接设置加载时间ms不累加
return global.GVA_DB.Model(&app.AIExtension{}).
Where("id = ? AND user_id = ?", extID, userID).
Update("load_time", value).Error
default:
return errors.New("无效的统计类型")
}
return global.GVA_DB.Model(&app.AIExtension{}).
Where("id = ? AND user_id = ?", extID, userID).
Update(updateExpr, gorm.Expr(updateExpr+" + ?", value)).Error
}
// ExportExtension 导出扩展数据
func (s *ExtensionService) ExportExtension(userID, extID uint) (*response.ExtensionResponse, error) {
ext, err := s.GetExtension(userID, extID)
if err != nil {
return nil, err
}
resp := response.ToExtensionResponse(ext)
return &resp, nil
}
// ImportExtension 从文件导入扩展(支持 zip 和 json
func (s *ExtensionService) ImportExtension(userID uint, filename string, fileData []byte) (*app.AIExtension, error) {
ext := strings.ToLower(path.Ext(filename))
switch ext {
case ".json":
// JSON 文件:直接解析为 CreateExtensionRequest
var req request.CreateExtensionRequest
if err := json.Unmarshal(fileData, &req); err != nil {
return nil, errors.New("JSON 文件格式错误: " + err.Error())
}
if req.Name == "" {
req.Name = strings.TrimSuffix(filename, path.Ext(filename))
}
if req.ExtensionType == "" {
req.ExtensionType = "ui"
}
req.InstallSource = "file"
return s.CreateExtension(userID, &req)
case ".zip":
// ZIP 文件:解压到扩展目录,解析 manifest.json创建数据库记录
return s.ImportExtensionFromZip(userID, filename, fileData)
default:
return nil, errors.New("不支持的文件格式,请上传 .zip 或 .json 文件")
}
}
// UpgradeExtension 升级扩展(从源地址重新安装)
func (s *ExtensionService) UpgradeExtension(userID, extID uint) (*app.AIExtension, error) {
return s.UpgradeExtensionFromSource(userID, extID)
}
// mustMarshal JSON 序列化辅助函数
func mustMarshal(v interface{}) []byte {
data, err := json.Marshal(v)
if err != nil {
return []byte("{}")
}
return data
}