232 lines
5.8 KiB
Go
232 lines
5.8 KiB
Go
package plugin
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
"github.com/gogf/gf/v2/os/glog"
|
|
|
|
"github.com/qianfree/team-api/internal/dao"
|
|
do "github.com/qianfree/team-api/internal/model/do"
|
|
)
|
|
|
|
// Install 安装插件(管理后台 API 调用触发)。
|
|
func Install(ctx context.Context, name string) error {
|
|
entry := GetPlugin(name)
|
|
if entry == nil {
|
|
return gerror.New("插件不存在")
|
|
}
|
|
if entry.Status == StatusInstalled || entry.Status == StatusEnabled || entry.Status == StatusDisabled {
|
|
return gerror.New("插件已安装")
|
|
}
|
|
|
|
if err := checkDependencies(entry); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := entry.Plugin.Install(ctx); err != nil {
|
|
return gerror.Wrap(err, "插件安装失败")
|
|
}
|
|
|
|
if c, ok := entry.Plugin.(Configurable); ok {
|
|
if err := registerConfigSchema(ctx, name, c.ConfigSchema()); err != nil {
|
|
return gerror.Wrap(err, "插件配置注册失败")
|
|
}
|
|
}
|
|
|
|
_, err := dao.SysPlugins.Ctx(ctx).Data(do.SysPlugins{
|
|
Name: name,
|
|
Label: entry.Info.Label,
|
|
Version: entry.Info.Version,
|
|
Status: StatusInstalled,
|
|
Category: entry.Info.Category,
|
|
Config: "{}",
|
|
}).Insert()
|
|
if err != nil {
|
|
return gerror.Wrap(err, "写入安装记录失败")
|
|
}
|
|
|
|
entry.Status = StatusInstalled
|
|
glog.Infof(ctx, "plugin %s: installed successfully", name)
|
|
return nil
|
|
}
|
|
|
|
// Enable 启用插件。
|
|
func Enable(ctx context.Context, name string) error {
|
|
entry := GetPlugin(name)
|
|
if entry == nil {
|
|
return gerror.New("插件不存在")
|
|
}
|
|
if entry.Status != StatusInstalled && entry.Status != StatusDisabled {
|
|
return gerror.New("插件未安装,无法启用")
|
|
}
|
|
|
|
if err := startPlugin(ctx, entry); err != nil {
|
|
_, _ = dao.SysPlugins.Ctx(ctx).
|
|
Where("name", name).
|
|
Data(do.SysPlugins{Status: StatusError, ErrorMsg: err.Error()}).
|
|
Update()
|
|
return err
|
|
}
|
|
|
|
_, err := dao.SysPlugins.Ctx(ctx).
|
|
Where("name", name).
|
|
Data(do.SysPlugins{Status: StatusEnabled, ErrorMsg: ""}).
|
|
Update()
|
|
return err
|
|
}
|
|
|
|
// Disable 禁用插件(不卸载,保留数据)。
|
|
func Disable(ctx context.Context, name string) error {
|
|
entry := GetPlugin(name)
|
|
if entry == nil {
|
|
return gerror.New("插件不存在")
|
|
}
|
|
if entry.Status != StatusEnabled {
|
|
return gerror.New("插件未启用")
|
|
}
|
|
|
|
if err := entry.Plugin.Destroy(ctx); err != nil {
|
|
glog.Warningf(ctx, "plugin %s: destroy error during disable: %v", name, err)
|
|
}
|
|
|
|
app.Hook.RemoveHooks(name)
|
|
|
|
entry.Status = StatusDisabled
|
|
_, err := dao.SysPlugins.Ctx(ctx).
|
|
Where("name", name).
|
|
Data(do.SysPlugins{Status: StatusDisabled}).
|
|
Update()
|
|
return err
|
|
}
|
|
|
|
// Uninstall 卸载插件。
|
|
func Uninstall(ctx context.Context, name string, keepData bool) error {
|
|
entry := GetPlugin(name)
|
|
if entry == nil {
|
|
return gerror.New("插件不存在")
|
|
}
|
|
if entry.Status == StatusEnabled {
|
|
return gerror.New("请先禁用插件再卸载")
|
|
}
|
|
|
|
if !keepData {
|
|
if err := entry.Plugin.Uninstall(ctx); err != nil {
|
|
glog.Warningf(ctx, "plugin %s: uninstall cleanup failed: %v", name, err)
|
|
}
|
|
}
|
|
|
|
_, err := dao.SysPlugins.Ctx(ctx).Where("name", name).Delete()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = dao.TntTenantPlugins.Ctx(ctx).Where("plugin_name", name).Delete()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
entry.Status = StatusRegistered
|
|
glog.Infof(ctx, "plugin %s: uninstalled (keepData=%v)", name, keepData)
|
|
return nil
|
|
}
|
|
|
|
// Upgrade 升级插件。
|
|
func Upgrade(ctx context.Context, name string) error {
|
|
entry := GetPlugin(name)
|
|
if entry == nil {
|
|
return gerror.New("插件不存在")
|
|
}
|
|
|
|
if err := entry.Plugin.Upgrade(ctx); err != nil {
|
|
return gerror.Wrap(err, "插件升级失败")
|
|
}
|
|
|
|
_, err := dao.SysPlugins.Ctx(ctx).
|
|
Where("name", name).
|
|
Data(do.SysPlugins{Version: entry.Info.Version}).
|
|
Update()
|
|
return err
|
|
}
|
|
|
|
// EnableForTenant 为指定租户启用插件。
|
|
func EnableForTenant(ctx context.Context, pluginName string, tenantID int64) error {
|
|
entry := GetPlugin(pluginName)
|
|
if entry == nil {
|
|
return gerror.New("插件不存在")
|
|
}
|
|
if entry.Status != StatusEnabled {
|
|
return gerror.New("插件未全局启用")
|
|
}
|
|
|
|
if ta, ok := entry.Plugin.(TenantAware); ok {
|
|
if err := ta.OnTenantEnable(ctx, tenantID); err != nil {
|
|
return gerror.Wrap(err, "租户级插件启用失败")
|
|
}
|
|
}
|
|
|
|
_, err := dao.TntTenantPlugins.Ctx(ctx).
|
|
Data(do.TntTenantPlugins{
|
|
TenantId: tenantID,
|
|
PluginName: pluginName,
|
|
Enabled: true,
|
|
}).
|
|
OnConflict("tenant_id,plugin_name").
|
|
Save()
|
|
return err
|
|
}
|
|
|
|
// DisableForTenant 为指定租户禁用插件。
|
|
func DisableForTenant(ctx context.Context, pluginName string, tenantID int64) error {
|
|
entry := GetPlugin(pluginName)
|
|
if entry == nil {
|
|
return gerror.New("插件不存在")
|
|
}
|
|
|
|
if ta, ok := entry.Plugin.(TenantAware); ok {
|
|
if err := ta.OnTenantDisable(ctx, tenantID); err != nil {
|
|
glog.Warningf(ctx, "plugin %s: OnTenantDisable failed for tenant %d: %v", pluginName, tenantID, err)
|
|
}
|
|
}
|
|
|
|
_, err := dao.TntTenantPlugins.Ctx(ctx).
|
|
Data(do.TntTenantPlugins{Enabled: false}).
|
|
Where("tenant_id = ? AND plugin_name = ?", tenantID, pluginName).
|
|
Update()
|
|
return err
|
|
}
|
|
|
|
// --- 内部辅助函数 ---
|
|
|
|
func checkDependencies(entry *PluginEntry) error {
|
|
for _, dep := range entry.Info.Dependencies {
|
|
depEntry := GetPlugin(dep)
|
|
if depEntry == nil {
|
|
return gerror.Newf("依赖插件 %s 不存在", dep)
|
|
}
|
|
if depEntry.Status != StatusEnabled && depEntry.Status != StatusInstalled {
|
|
return gerror.Newf("依赖插件 %s 未安装", dep)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func registerConfigSchema(ctx context.Context, pluginName string, fields []ConfigFieldDef) error {
|
|
for _, field := range fields {
|
|
key := fmt.Sprintf("plugin.%s.%s", pluginName, field.Key)
|
|
_, err := dao.SysOptions.Ctx(ctx).
|
|
Data(do.SysOptions{
|
|
Key: key,
|
|
Value: fmt.Sprintf("%v", field.Default),
|
|
Description: field.Description,
|
|
}).
|
|
OnConflict("key").
|
|
Save()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|