mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-11-14 05:03:49 +08:00
发布v2.13.1版本,更新内容请查看:https://github.com/bufanyun/hotgo/blob/v2.0/docs/guide-zh-CN/start-update-log.md
This commit is contained in:
@@ -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 != "" {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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>`
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user