411 lines
11 KiB
Go
411 lines
11 KiB
Go
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
|
||
}
|