Files
team-api/internal/plugin/lifecycle.go
T
2026-06-03 15:34:38 +08:00

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
}