This commit is contained in:
孟帅
2024-03-07 20:08:56 +08:00
parent 6dd8cbadad
commit 0fbc1ad47c
246 changed files with 9441 additions and 2293 deletions

View File

@@ -11,26 +11,47 @@ import (
"hotgo/internal/consts"
)
var cacheResourcePath string
// GetResourcePath 获取插件资源路径
func GetResourcePath(ctx context.Context) string {
if len(cacheResourcePath) > 0 {
return cacheResourcePath
}
basePath := g.Cfg().MustGet(ctx, "hotgo.addonsResourcePath").String()
if basePath == "" {
g.Log().Warning(ctx, "addons GetResourcePath not config found:'hotgo.addonsResourcePath', use default values:'resource'")
basePath = "resource"
}
cacheResourcePath = basePath
return basePath
}
// GetModulePath 获取指定模块相对路径
func GetModulePath(name string) string {
return "./" + consts.AddonsDir + "/" + name
}
// ViewPath 默认的插件模板路径
func ViewPath(name string) string {
return consts.AddonsDir + "/" + name + "/" + "resource/template"
// 模板路径resource/addons/插件模块名称/template
// 例如resource/addons/hgexample/template
// 如果你不喜欢现在的风格,可以自行调整
func ViewPath(name, basePath string) string {
return basePath + "/" + consts.AddonsDir + "/" + name + "/template"
}
// StaticPath 默认的插件静态路映射关系
// 最终效果:对外访问地址:/addons/插件模块名称;静态资源路径:/addons/插件模块名称/设置的子路径。
// 如果你不喜欢现在的路由风格,可以自行调整
func StaticPath(name, path string) (string, string) {
return "/" + consts.AddonsDir + "/" + name, consts.AddonsDir + "/" + name + "/" + path
// 静态资源路径:resource/public/addons/插件模块名称/public
// 例如访问http://127.0.0.1:8000/addons/hgexample/default 则指向文件-> resource/addons/hgexample/public/default
// 如果你不喜欢现在的风格,可以自行调整
func StaticPath(name, basePath string) (string, string) {
return "/" + consts.AddonsDir + "/" + name, basePath + "/" + consts.AddonsDir + "/" + name + "/public"
}
// RouterPrefix 路由前缀
// 最终效果:/应用名称/插件模块名称/xxx/xxx。
// 如果你不喜欢现在的路由风格,可以自行调整
// 如果你不喜欢现在的风格,可以自行调整
func RouterPrefix(ctx context.Context, app, name string) string {
var prefix = "/"
if app != "" {

View File

@@ -7,40 +7,54 @@ package addons
import (
"context"
"fmt"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gstr"
"hotgo/internal/consts"
"hotgo/internal/model"
"hotgo/utility/validate"
"strconv"
"strings"
)
type BuildOption struct {
Skeleton Skeleton
Config *model.BuildAddonConfig
Extend []string `json:"extend" dc:"扩展功能"`
}
// Build 构建新插件
func Build(ctx context.Context, sk Skeleton, conf *model.BuildAddonConfig) (err error) {
func Build(ctx context.Context, option *BuildOption) (err error) {
var (
buildPath = "./" + consts.AddonsDir + "/" + sk.Name
modulesPath = "./" + consts.AddonsDir + "/modules/" + sk.Name + ".go"
webApiPath = gstr.Replace(conf.WebApiPath, "{$name}", sk.Name)
webViewsPath = gstr.Replace(conf.WebViewsPath, "{$name}", sk.Name)
resourcePath = GetResourcePath(ctx)
buildPath = "./" + consts.AddonsDir + "/" + option.Skeleton.Name
modulesPath = "./" + consts.AddonsDir + "/modules/" + option.Skeleton.Name + ".go"
webApiPath = gstr.Replace(option.Config.WebApiPath, "{$name}", option.Skeleton.Name)
webViewsPath = gstr.Replace(option.Config.WebViewsPath, "{$name}", option.Skeleton.Name)
replaces = map[string]string{
"@{.label}": sk.Label,
"@{.name}": sk.Name,
"@{.group}": strconv.Itoa(sk.Group),
"@{.brief}": sk.Brief,
"@{.description}": sk.Description,
"@{.author}": sk.Author,
"@{.version}": sk.Version,
"@{.label}": option.Skeleton.Label,
"@{.name}": option.Skeleton.Name,
"@{.group}": strconv.Itoa(option.Skeleton.Group),
"@{.brief}": option.Skeleton.Brief,
"@{.description}": option.Skeleton.Description,
"@{.author}": option.Skeleton.Author,
"@{.version}": option.Skeleton.Version,
"@{.hgVersion}": consts.VersionApp, // HG 版本
}
)
if resourcePath == "" {
err = gerror.New("请先设置一个有效的插件资源路径,配置名称:'hotgo.addonsResourcePath'")
return
}
if err = checkBuildDir(buildPath, modulesPath, webApiPath, webViewsPath); err != nil {
return
}
// scans directory recursively
list, err := gfile.ScanDirFunc(conf.SrcPath, "*", true, func(path string) string {
list, err := gfile.ScanDirFunc(option.Config.SrcPath, "*", true, func(path string) string {
return path
})
@@ -59,8 +73,8 @@ func Build(ctx context.Context, sk Skeleton, conf *model.BuildAddonConfig) (err
}
flowFile := gstr.ReplaceByMap(path, map[string]string{
gfile.RealPath(conf.SrcPath): "",
".template": "",
gfile.RealPath(option.Config.SrcPath): "",
".template": "",
})
flowFile = buildPath + "/" + flowFile
@@ -90,6 +104,23 @@ func Build(ctx context.Context, sk Skeleton, conf *model.BuildAddonConfig) (err
if err = gfile.PutContents(webViewsPath+"/config/system.vue", gstr.ReplaceByMap(webConfigSystem, replaces)); err != nil {
return
}
// 创建静态目录
if validate.InSlice(option.Extend, consts.AddonsExtendResourcePublic) {
_, staticPath := StaticPath(option.Skeleton.Name, resourcePath)
content := fmt.Sprintf(resourcePublicDefaultFile, option.Skeleton.Label)
if err = gfile.PutContents(staticPath+"/default", content); err != nil {
return
}
}
// 创建模板目录
if validate.InSlice(option.Extend, consts.AddonsExtendResourceTemplate) {
viewPath := ViewPath(option.Skeleton.Name, resourcePath)
if err = gfile.PutContents(viewPath+"/home/index.html", resourceTemplateHomeFile); err != nil {
return
}
}
return
}

View File

@@ -194,4 +194,39 @@ export function updateConfig(params) {
}
</style>
`
resourcePublicDefaultFile = "Hello这是创建插件 [%v] 时默认生成的一个静态目录文件,用于测试,当你看到这个提示时,说明已经联调成功啦!"
resourceTemplateHomeFile = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0,user-scalable=no">
<meta name="keywords" content="@{.Keywords}"/>
<meta name="description" content="@{.Description}"/>
<title>@{.Title}</title>
<script type="text/javascript" src="/resource/home/js/jquery-3.6.0.min.js"></script>
<style>
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background-color: #f6f6f6;
}
</style>
</head>
<body>
<div style="padding-top: 100px;text-align:center;">
<h1><p>Hello@{.Data.name}!!</p></h1>
<h2><p>@{.Data.module}</p></h2>
<h2><p>服务器时间:@{.Data.time}</p></h2>
</div>
</body>
<script>
</script>
</html>`
)

View File

@@ -15,8 +15,16 @@ import (
"hotgo/internal/model/input/form"
"sort"
"sync"
"time"
)
// Option 模块启动选项
type Option struct {
Server *ghttp.Server // http服务器
// 更多选项参数
// ..
}
// Skeleton 模块骨架
type Skeleton struct {
Label string `json:"label"` // 标识
@@ -37,32 +45,42 @@ func (s *Skeleton) GetModule() Module {
// Module 插件模块
type Module interface {
Init(ctx context.Context) // 初始化
InitRouter(ctx context.Context, group *ghttp.RouterGroup) // 初始化并注册路由
Ctx() context.Context // 上下文
GetSkeleton() *Skeleton // 架子
Install(ctx context.Context) error // 安装模块
Upgrade(ctx context.Context) error // 更新模块
UnInstall(ctx context.Context) error // 卸载模块
Start(option *Option) (err error) // 启动模块
Stop() (err error) // 停止模块
Ctx() context.Context // 上下文
GetSkeleton() *Skeleton // 获取模块
Install(ctx context.Context) (err error) // 安装模块
Upgrade(ctx context.Context) (err error) // 更新模块
UnInstall(ctx context.Context) (err error) // 卸载模块
}
var (
modules = make(map[string]Module, 0)
modules = make(map[string]Module)
mLock sync.Mutex
)
// InitModules 初始化所有已注册模块
func InitModules(ctx context.Context) {
for _, module := range modules {
module.Init(ctx)
// StartModules 启动所有已安装模块
func StartModules(ctx context.Context, option *Option) (err error) {
for _, module := range filterInstalled() {
if err = module.Start(option); err != nil {
return
}
}
// 为所有已安装模块设置静态资源路径
AddStaticPath(ctx, option.Server)
return
}
// RegisterModulesRouter 注册所有已安装模块路由
func RegisterModulesRouter(ctx context.Context, group *ghttp.RouterGroup) {
// StopModules 停止所有已安装模块
func StopModules(ctx context.Context) {
for _, module := range filterInstalled() {
module.InitRouter(ctx, group)
if err := module.Stop(); err != nil {
g.Log().Warningf(ctx, "StopModules err:%v, module:%v", err.Error(), module.GetSkeleton().Name)
time.Sleep(time.Second)
}
}
return
}
// RegisterModule 注册模块
@@ -123,9 +141,20 @@ func GetModuleRealPath(name string) string {
// NewView 初始化一个插件的模板引擎
func NewView(ctx context.Context, name string) *gview.View {
view := gview.New()
basePath := GetResourcePath(ctx)
if basePath == "" {
return nil
}
if err := view.SetPath(ViewPath(name)); err != nil {
view := gview.New()
path := ViewPath(name, basePath)
if !gfile.IsDir(gfile.RealPath(path)) {
g.Log().Warningf(ctx, "NewView template path does not exist:%v,default use of main module template.", path)
return nil
}
if err := view.SetPath(path); err != nil {
g.Log().Warningf(ctx, "NewView SetPath err:%+v", err)
return nil
}
@@ -145,12 +174,8 @@ func NewView(ctx context.Context, name string) *gview.View {
}
// AddStaticPath 设置插件静态目录映射
func AddStaticPath(ctx context.Context, server *ghttp.Server, p ...string) {
basePath := g.Cfg().MustGet(ctx, "server.serverRoot").String()
if len(p) > 0 {
basePath = p[0]
}
func AddStaticPath(ctx context.Context, server *ghttp.Server) {
basePath := GetResourcePath(ctx)
if basePath == "" {
return
}
@@ -160,7 +185,7 @@ func AddStaticPath(ctx context.Context, server *ghttp.Server, p ...string) {
prefix, path := StaticPath(name, basePath)
if !gres.Contains(path) {
if _, err := gfile.Search(path); err != nil {
g.Log().Warningf(ctx, `AddStaticPath failed: %v`, err)
g.Log().Warningf(ctx, `addons AddStaticPath failed: %v`, err)
continue
}
}