From 325a218e2014bb6274481bb28686c2b772971177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Mon, 29 Jul 2024 03:25:53 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/plugins/README.md | 3 + app/plugins/loader/loader.go | 17 +++ app/plugins/main.go | 6 + app/plugins/openresty/controller.go | 187 +++++++++++++++++++++++ app/plugins/openresty/main.go | 37 +++++ app/providers/event_service_provider.go | 3 +- app/providers/plugin_service_provider.go | 20 +++ app/providers/route_service_provider.go | 3 +- config/app.go | 1 + internal/plugin.go | 4 +- internal/services/plugin.go | 11 +- pkg/plugins/loader.go | 63 -------- pkg/types/plugin.go | 21 +-- 13 files changed, 293 insertions(+), 83 deletions(-) create mode 100644 app/plugins/README.md create mode 100644 app/plugins/loader/loader.go create mode 100644 app/plugins/main.go create mode 100644 app/plugins/openresty/controller.go create mode 100644 app/plugins/openresty/main.go create mode 100644 app/providers/plugin_service_provider.go delete mode 100644 pkg/plugins/loader.go diff --git a/app/plugins/README.md b/app/plugins/README.md new file mode 100644 index 00000000..0e6118a9 --- /dev/null +++ b/app/plugins/README.md @@ -0,0 +1,3 @@ +# 面板插件目录 + +文档待定 diff --git a/app/plugins/loader/loader.go b/app/plugins/loader/loader.go new file mode 100644 index 00000000..63cee120 --- /dev/null +++ b/app/plugins/loader/loader.go @@ -0,0 +1,17 @@ +package loader + +import ( + "github.com/TheTNB/panel/v2/pkg/types" +) + +var data []*types.Plugin + +// All 获取所有插件 +func All() []*types.Plugin { + return data +} + +// New 新注册插件 +func New(plugin *types.Plugin) { + data = append(data, plugin) +} diff --git a/app/plugins/main.go b/app/plugins/main.go new file mode 100644 index 00000000..0595f1c3 --- /dev/null +++ b/app/plugins/main.go @@ -0,0 +1,6 @@ +package plugins + +import _ "github.com/TheTNB/panel/v2/app/plugins/openresty" + +// Boot 启动所有插件 +func Boot() {} diff --git a/app/plugins/openresty/controller.go b/app/plugins/openresty/controller.go new file mode 100644 index 00000000..f37414dd --- /dev/null +++ b/app/plugins/openresty/controller.go @@ -0,0 +1,187 @@ +package openresty + +import ( + "fmt" + "regexp" + "time" + + "github.com/go-resty/resty/v2" + "github.com/goravel/framework/contracts/http" + "github.com/spf13/cast" + + "github.com/TheTNB/panel/v2/pkg/h" + "github.com/TheTNB/panel/v2/pkg/io" + "github.com/TheTNB/panel/v2/pkg/shell" + "github.com/TheTNB/panel/v2/pkg/str" + "github.com/TheTNB/panel/v2/pkg/systemctl" + "github.com/TheTNB/panel/v2/pkg/types" +) + +type Controller struct { + // Dependent services +} + +func NewController() *Controller { + return &Controller{} +} + +// GetConfig +// +// @Summary 获取配置 +// @Tags 插件-OpenResty +// @Produce json +// @Security BearerToken +// @Success 200 {object} h.SuccessResponse +// @Router /plugins/openresty/config [get] +func (r *Controller) GetConfig(ctx http.Context) http.Response { + config, err := io.Read("/www/server/openresty/conf/nginx.conf") + if err != nil { + return h.Error(ctx, http.StatusInternalServerError, "获取配置失败") + } + + return h.Success(ctx, config) +} + +// SaveConfig +// +// @Summary 保存配置 +// @Tags 插件-OpenResty +// @Produce json +// @Security BearerToken +// @Param config body string true "配置" +// @Success 200 {object} h.SuccessResponse +// @Router /plugins/openresty/config [post] +func (r *Controller) SaveConfig(ctx http.Context) http.Response { + config := ctx.Request().Input("config") + if len(config) == 0 { + return h.Error(ctx, http.StatusInternalServerError, "配置不能为空") + } + + if err := io.Write("/www/server/openresty/conf/nginx.conf", config, 0644); err != nil { + return h.Error(ctx, http.StatusInternalServerError, "保存配置失败") + } + + if err := systemctl.Reload("openresty"); err != nil { + _, err = shell.Execf("openresty -t") + return h.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重载服务失败: %v", err)) + } + + return h.Success(ctx, nil) +} + +// ErrorLog +// +// @Summary 获取错误日志 +// @Tags 插件-OpenResty +// @Produce json +// @Security BearerToken +// @Success 200 {object} h.SuccessResponse +// @Router /plugins/openresty/errorLog [get] +func (r *Controller) ErrorLog(ctx http.Context) http.Response { + if !io.Exists("/www/wwwlogs/nginx_error.log") { + return h.Success(ctx, "") + } + + out, err := shell.Execf("tail -n 100 /www/wwwlogs/openresty_error.log") + if err != nil { + return h.Error(ctx, http.StatusInternalServerError, out) + } + + return h.Success(ctx, out) +} + +// ClearErrorLog +// +// @Summary 清空错误日志 +// @Tags 插件-OpenResty +// @Produce json +// @Security BearerToken +// @Success 200 {object} h.SuccessResponse +// @Router /plugins/openresty/clearErrorLog [post] +func (r *Controller) ClearErrorLog(ctx http.Context) http.Response { + if out, err := shell.Execf("echo '' > /www/wwwlogs/openresty_error.log"); err != nil { + return h.Error(ctx, http.StatusInternalServerError, out) + } + + return h.Success(ctx, nil) +} + +// Load +// +// @Summary 获取负载状态 +// @Tags 插件-OpenResty +// @Produce json +// @Security BearerToken +// @Success 200 {object} h.SuccessResponse +// @Router /plugins/openresty/load [get] +func (r *Controller) Load(ctx http.Context) http.Response { + client := resty.New().SetTimeout(10 * time.Second) + resp, err := client.R().Get("http://127.0.0.1/nginx_status") + if err != nil || !resp.IsSuccess() { + return h.Success(ctx, []types.NV{}) + } + + raw := resp.String() + var data []types.NV + + workers, err := shell.Execf("ps aux | grep nginx | grep 'worker process' | wc -l") + if err != nil { + return h.Error(ctx, http.StatusInternalServerError, "获取负载失败") + } + data = append(data, types.NV{ + Name: "工作进程", + Value: workers, + }) + + out, err := shell.Execf("ps aux | grep nginx | grep 'worker process' | awk '{memsum+=$6};END {print memsum}'") + if err != nil { + return h.Error(ctx, http.StatusInternalServerError, "获取负载失败") + } + mem := str.FormatBytes(cast.ToFloat64(out)) + data = append(data, types.NV{ + Name: "内存占用", + Value: mem, + }) + + match := regexp.MustCompile(`Active connections:\s+(\d+)`).FindStringSubmatch(raw) + if len(match) == 2 { + data = append(data, types.NV{ + Name: "活跃连接数", + Value: match[1], + }) + } + + match = regexp.MustCompile(`server accepts handled requests\s+(\d+)\s+(\d+)\s+(\d+)`).FindStringSubmatch(raw) + if len(match) == 4 { + data = append(data, types.NV{ + Name: "总连接次数", + Value: match[1], + }) + data = append(data, types.NV{ + Name: "总握手次数", + Value: match[2], + }) + data = append(data, types.NV{ + Name: "总请求次数", + Value: match[3], + }) + } + + match = regexp.MustCompile(`Reading:\s+(\d+)\s+Writing:\s+(\d+)\s+Waiting:\s+(\d+)`).FindStringSubmatch(raw) + if len(match) == 4 { + data = append(data, types.NV{ + Name: "请求数", + Value: match[1], + }) + data = append(data, types.NV{ + Name: "响应数", + Value: match[2], + }) + data = append(data, types.NV{ + Name: "驻留进程", + Value: match[3], + }) + } + + return h.Success(ctx, data) +} diff --git a/app/plugins/openresty/main.go b/app/plugins/openresty/main.go new file mode 100644 index 00000000..70a6aaa4 --- /dev/null +++ b/app/plugins/openresty/main.go @@ -0,0 +1,37 @@ +package openresty + +import ( + "github.com/goravel/framework/contracts/foundation" + "github.com/goravel/framework/contracts/route" + + "github.com/TheTNB/panel/v2/app/http/middleware" + "github.com/TheTNB/panel/v2/app/plugins/loader" + "github.com/TheTNB/panel/v2/pkg/types" +) + +func init() { + loader.New(&types.Plugin{ + Name: "OpenResty", + Description: "OpenResty® 是一款基于 NGINX 和 LuaJIT 的 Web 平台", + Slug: "openresty", + Version: "1.25.3.1", + Requires: []string{}, + Excludes: []string{}, + Install: "bash /www/panel/scripts/openresty/install.sh", + Uninstall: "bash /www/panel/scripts/openresty/uninstall.sh", + Update: "bash /www/panel/scripts/openresty/install.sh", + Boot: func(app foundation.Application) { + RouteFacade := app.MakeRoute() + RouteFacade.Prefix("api/plugins/openresty").Middleware(middleware.Session(), middleware.MustInstall()).Group(func(r route.Router) { + r.Prefix("openresty").Group(func(route route.Router) { + controller := NewController() + route.Get("load", controller.Load) + route.Get("config", controller.GetConfig) + route.Post("config", controller.SaveConfig) + route.Get("errorLog", controller.ErrorLog) + route.Post("clearErrorLog", controller.ClearErrorLog) + }) + }) + }, + }) +} diff --git a/app/providers/event_service_provider.go b/app/providers/event_service_provider.go index 75c95ee7..24e66f81 100644 --- a/app/providers/event_service_provider.go +++ b/app/providers/event_service_provider.go @@ -6,8 +6,7 @@ import ( "github.com/goravel/framework/facades" ) -type EventServiceProvider struct { -} +type EventServiceProvider struct{} func (receiver *EventServiceProvider) Register(app foundation.Application) { facades.Event().Register(receiver.listen()) diff --git a/app/providers/plugin_service_provider.go b/app/providers/plugin_service_provider.go new file mode 100644 index 00000000..43858399 --- /dev/null +++ b/app/providers/plugin_service_provider.go @@ -0,0 +1,20 @@ +package providers + +import ( + "github.com/goravel/framework/contracts/foundation" + + "github.com/TheTNB/panel/v2/app/plugins" + "github.com/TheTNB/panel/v2/app/plugins/loader" +) + +type PluginServiceProvider struct{} + +func (receiver *PluginServiceProvider) Register(app foundation.Application) { + plugins.Boot() +} + +func (receiver *PluginServiceProvider) Boot(app foundation.Application) { + for _, plugin := range loader.All() { + plugin.Boot(app) + } +} diff --git a/app/providers/route_service_provider.go b/app/providers/route_service_provider.go index 97f3559d..3e215008 100644 --- a/app/providers/route_service_provider.go +++ b/app/providers/route_service_provider.go @@ -10,8 +10,7 @@ import ( "github.com/TheTNB/panel/v2/routes" ) -type RouteServiceProvider struct { -} +type RouteServiceProvider struct{} func (receiver *RouteServiceProvider) Register(app foundation.Application) { } diff --git a/config/app.go b/config/app.go index da846458..d7e9dd91 100644 --- a/config/app.go +++ b/config/app.go @@ -111,6 +111,7 @@ func init() { &providers.AppServiceProvider{}, &providers.AuthServiceProvider{}, &providers.RouteServiceProvider{}, + &providers.PluginServiceProvider{}, &providers.ConsoleServiceProvider{}, &providers.QueueServiceProvider{}, &providers.EventServiceProvider{}, diff --git a/internal/plugin.go b/internal/plugin.go index 02929b4c..9170e9f4 100644 --- a/internal/plugin.go +++ b/internal/plugin.go @@ -7,8 +7,8 @@ import ( type Plugin interface { AllInstalled() ([]models.Plugin, error) - All() []types.Plugin - GetBySlug(slug string) types.Plugin + All() []*types.Plugin + GetBySlug(slug string) *types.Plugin GetInstalledBySlug(slug string) models.Plugin Install(slug string) error Uninstall(slug string) error diff --git a/internal/services/plugin.go b/internal/services/plugin.go index 8617cf86..9ea0f3a1 100644 --- a/internal/services/plugin.go +++ b/internal/services/plugin.go @@ -7,6 +7,7 @@ import ( "github.com/goravel/framework/facades" "github.com/TheTNB/panel/v2/app/models" + "github.com/TheTNB/panel/v2/app/plugins/loader" "github.com/TheTNB/panel/v2/internal" "github.com/TheTNB/panel/v2/pkg/io" "github.com/TheTNB/panel/v2/pkg/types" @@ -33,8 +34,8 @@ func (r *PluginImpl) AllInstalled() ([]models.Plugin, error) { } // All 获取所有插件 -func (r *PluginImpl) All() []types.Plugin { - var plugins = []types.Plugin{ +func (r *PluginImpl) All() []*types.Plugin { + var _ = []types.Plugin{ types.PluginOpenResty, types.PluginMySQL57, types.PluginMySQL80, @@ -59,18 +60,18 @@ func (r *PluginImpl) All() []types.Plugin { types.PluginToolBox, } - return plugins + return loader.All() } // GetBySlug 根据 slug 获取插件 -func (r *PluginImpl) GetBySlug(slug string) types.Plugin { +func (r *PluginImpl) GetBySlug(slug string) *types.Plugin { for _, item := range r.All() { if item.Slug == slug { return item } } - return types.Plugin{} + return &types.Plugin{} } // GetInstalledBySlug 根据 slug 获取已安装的插件 diff --git a/pkg/plugins/loader.go b/pkg/plugins/loader.go deleted file mode 100644 index 204e4e7c..00000000 --- a/pkg/plugins/loader.go +++ /dev/null @@ -1,63 +0,0 @@ -package plugins - -import ( - "errors" - "runtime" - "strings" - "sync" - - "github.com/goravel/framework/contracts/route" - "github.com/goravel/framework/facades" - - "github.com/TheTNB/panel/v2/app/http/middleware" -) - -var plugins sync.Map - -type Plugin struct { - Name string // 插件名称 - Description string // 插件描述 - Slug string // 插件标识 - Version string // 插件版本 - Requires []string // 依赖插件 - Excludes []string // 排除插件 - Install string // 安装命令 - Uninstall string // 卸载命令 - Update string // 更新命令 - OnEnable func() error // 启用插件后执行的命令 - OnDisable func() error // 禁用插件后执行的命令 -} - -func NewPlugin() (*Plugin, error) { - pc, _, _, ok := runtime.Caller(1) - if !ok { - panic("unable to get caller") - } - name := runtime.FuncForPC(pc).Name() - a := strings.LastIndex(name, "/") - if a < 0 { - panic("invalid package name: " + name) - } - name = name[a+1:] - b := strings.Index(name, ".") - if b < 0 { - panic("invalid package name: " + name) - } - slug := name[:b] - - if _, ok = plugins.Load(slug); ok { - return nil, errors.New("plugin already exists") - } - - instance := &Plugin{ - Slug: slug, - } - plugins.Store(slug, instance) - - return instance, instance.OnEnable() -} - -// Route 注册路由 -func (r *Plugin) Route(group func(router route.Router)) { - facades.Route().Prefix("api/plugins/"+r.Slug).Middleware(middleware.Session(), middleware.MustInstall()).Group(group) -} diff --git a/pkg/types/plugin.go b/pkg/types/plugin.go index 822d3713..f664012e 100644 --- a/pkg/types/plugin.go +++ b/pkg/types/plugin.go @@ -1,14 +1,17 @@ package types +import "github.com/goravel/framework/contracts/foundation" + // Plugin 插件元数据结构 type Plugin struct { - Name string - Description string - Slug string - Version string - Requires []string - Excludes []string - Install string - Uninstall string - Update string + Name string // 插件名称 + Description string // 插件描述 + Slug string // 插件标识 + Version string // 插件版本 + Requires []string // 依赖插件 + Excludes []string // 排除插件 + Install string // 安装命令 + Uninstall string // 卸载命令 + Update string // 更新命令 + Boot func(app foundation.Application) // 启动时执行的命令 }