mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-02-02 18:28:41 +08:00
Merge pull request #27 from bufanyun/v2.0
插件增加静态文件目录自动映射,优化插件模板引擎与主模块的耦合关系
This commit is contained in:
commit
d2be8496b4
@ -19,9 +19,8 @@
|
|||||||
|
|
||||||
1. /server/addons/hgexample/ # 插件模块目录
|
1. /server/addons/hgexample/ # 插件模块目录
|
||||||
2. /server/addons/modules/hgexample.go # 隐式注册插件文件
|
2. /server/addons/modules/hgexample.go # 隐式注册插件文件
|
||||||
3. /server/resource/template/addons/hgexample # pc模板目录
|
3. /web/src/api/addons/hgexample # webApi目录
|
||||||
4. /web/src/api/addons/hgexample # webApi目录
|
4. /web/src/views/addons/hgexample # web页面目录
|
||||||
5. /web/src/views/addons/hgexample # web页面目录
|
|
||||||
|
|
||||||
# 默认情况下没有为web页面生成菜单权限,因为在实际场景中插件不一定需要用到web页面,所以如有需要请手动到后台 权限管理 -> 菜单权限->自行添加菜单和配置权限
|
# 默认情况下没有为web页面生成菜单权限,因为在实际场景中插件不一定需要用到web页面,所以如有需要请手动到后台 权限管理 -> 菜单权限->自行添加菜单和配置权限
|
||||||
```
|
```
|
||||||
@ -170,7 +169,7 @@ func (s *sSysAddonsConfig) UpdateConfigByGroup(ctx context.Context, in sysin.Upd
|
|||||||
如:127.0.0.1:8000/admin/hgexample/index/test
|
如:127.0.0.1:8000/admin/hgexample/index/test
|
||||||
```
|
```
|
||||||
|
|
||||||
对应控制器路径:`server/addons/hgexample/controller/admin/sys/index.go`
|
- 对应控制器路径:`server/addons/hgexample/controller/admin/sys/index.go`
|
||||||
|
|
||||||
#### 前端API插件访问路径
|
#### 前端API插件访问路径
|
||||||
|
|
||||||
@ -179,7 +178,7 @@ func (s *sSysAddonsConfig) UpdateConfigByGroup(ctx context.Context, in sysin.Upd
|
|||||||
如:127.0.0.1:8000/api/hgexample/index/test
|
如:127.0.0.1:8000/api/hgexample/index/test
|
||||||
```
|
```
|
||||||
|
|
||||||
对应控制器路径:`server/addons/hgexample/controller/api/index.go`
|
- 对应控制器路径:`server/addons/hgexample/controller/api/index.go`
|
||||||
|
|
||||||
#### 前台页面插件访问路径
|
#### 前台页面插件访问路径
|
||||||
|
|
||||||
@ -188,7 +187,17 @@ func (s *sSysAddonsConfig) UpdateConfigByGroup(ctx context.Context, in sysin.Upd
|
|||||||
如:127.0.0.1:8000/home/hgexample/index/test
|
如:127.0.0.1:8000/home/hgexample/index/test
|
||||||
```
|
```
|
||||||
|
|
||||||
对应控制器路径:`server/addons/hgexample/controller/home/index.go`
|
- 对应控制器路径:`server/addons/hgexample/controller/home/index.go`
|
||||||
|
- 对应模板路径:`server/addons/hgexample/resource/public/template`
|
||||||
|
|
||||||
|
#### 静态资源插件访问路径
|
||||||
|
|
||||||
|
```
|
||||||
|
// IP+端口或域名/home/插件名称/API路径
|
||||||
|
如:127.0.0.1:8000/addons/hgexample/default
|
||||||
|
```
|
||||||
|
|
||||||
|
- 对应资源路径:`server/addons/hgexample/resource/public`
|
||||||
|
|
||||||
|
|
||||||
#### Websocket插件访问路径
|
#### Websocket插件访问路径
|
||||||
@ -198,9 +207,9 @@ func (s *sSysAddonsConfig) UpdateConfigByGroup(ctx context.Context, in sysin.Upd
|
|||||||
如:127.0.0.1:8000/socket/hgexample/index/test
|
如:127.0.0.1:8000/socket/hgexample/index/test
|
||||||
```
|
```
|
||||||
|
|
||||||
对应控制器路径:`server/addons/hgexample/controller/socket/index.go`
|
- 对应控制器路径:`server/addons/hgexample/controller/socket/index.go`
|
||||||
|
|
||||||
|
|
||||||
### 数据迁移
|
### 数据迁移
|
||||||
|
|
||||||
可以将数据迁移逻辑写进server/xxx插件/main.go 文件中的Install方法中,并遵循系统规范进行数据安装
|
- 可以将数据迁移逻辑写进server/xxx插件/main.go 文件中的Install方法中,并遵循系统规范进行数据安装
|
||||||
|
@ -21,6 +21,7 @@ type Skeleton struct {
|
|||||||
Author string `json:"author"` // 作者
|
Author string `json:"author"` // 作者
|
||||||
Version string `json:"version"` // 版本号
|
Version string `json:"version"` // 版本号
|
||||||
RootPath string `json:"rootPath"` // 根路径
|
RootPath string `json:"rootPath"` // 根路径
|
||||||
|
View *gview.View `json:"view"` // 模板引擎
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Skeleton) GetModule() Module {
|
func (s *Skeleton) GetModule() Module {
|
||||||
@ -79,26 +80,4 @@ func test(ctx context.Context) {
|
|||||||
|
|
||||||
#### 插件路由规则
|
#### 插件路由规则
|
||||||
- 如果你不喜欢现在的路由风格,可以自行调整。修改位置在:\server\internal\library\addons\addons.go的RouterPrefix方法。
|
- 如果你不喜欢现在的路由风格,可以自行调整。修改位置在:\server\internal\library\addons\addons.go的RouterPrefix方法。
|
||||||
- 调整后如web前端页面中有之前的路由风格也需同步修改。
|
- 注意调整后如web前端页面中如有之前的路由风格也需同步修改。
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
|
||||||
"hotgo/internal/consts"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RouterPrefix 路由前缀
|
|
||||||
// 最终效果:/应用名称/插件模块名称/xxx/xxx。如果你不喜欢现在的路由风格,可以自行调整
|
|
||||||
func RouterPrefix(ctx context.Context, app, name string) string {
|
|
||||||
var prefix = "/"
|
|
||||||
|
|
||||||
if app != "" {
|
|
||||||
prefix = g.Cfg().MustGet(ctx, "router."+app+".prefix", "/"+app+"").String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return prefix + "/" + name
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
@ -26,9 +26,12 @@ HotGo 入口文件->隐式注入(hotgo/addons/modules)->注册所有插件->初
|
|||||||
│ ├── xxx插件
|
│ ├── xxx插件
|
||||||
│ | ├── api
|
│ | ├── api
|
||||||
│ | ├── controller
|
│ | ├── controller
|
||||||
|
│ | ├── crons
|
||||||
│ | ├── global
|
│ | ├── global
|
||||||
│ | ├── logic
|
│ | ├── logic
|
||||||
│ | ├── model
|
│ | ├── model
|
||||||
|
│ | ├── queues
|
||||||
|
│ | ├── resource
|
||||||
│ | ├── router
|
│ | ├── router
|
||||||
│ | ├── service
|
│ | ├── service
|
||||||
│ | ├── main.go
|
│ | ├── main.go
|
||||||
|
@ -15,9 +15,12 @@
|
|||||||
│ ├── xxx插件
|
│ ├── xxx插件
|
||||||
│ | ├── api
|
│ | ├── api
|
||||||
│ | ├── controller
|
│ | ├── controller
|
||||||
|
│ | ├── crons
|
||||||
│ | ├── global
|
│ | ├── global
|
||||||
│ | ├── logic
|
│ | ├── logic
|
||||||
│ | ├── model
|
│ | ├── model
|
||||||
|
│ | ├── queues
|
||||||
|
│ | ├── resource
|
||||||
│ | ├── router
|
│ | ├── router
|
||||||
│ | ├── service
|
│ | ├── service
|
||||||
│ | ├── main.go
|
│ | ├── main.go
|
||||||
@ -68,10 +71,13 @@
|
|||||||
| --- --- --- home | 前台PC端页面 |
|
| --- --- --- home | 前台PC端页面 |
|
||||||
| --- --- --- websocket | 可同时为多应用提供websocket接口 |
|
| --- --- --- websocket | 可同时为多应用提供websocket接口 |
|
||||||
| --- --- controller | 接收/解析用户输入参数的入口/接口层,也可以理解为控制器 |
|
| --- --- controller | 接收/解析用户输入参数的入口/接口层,也可以理解为控制器 |
|
||||||
|
| --- --- crons | 项目中由系统统一接管的定时任务处理 |
|
||||||
| --- --- global | 项目内主要的全局变量和系统的一些初始化操作 |
|
| --- --- global | 项目内主要的全局变量和系统的一些初始化操作 |
|
||||||
| --- --- logic | 业务逻辑封装管理,特定的业务逻辑实现和封装往往是项目中最复杂的部分 |
|
| --- --- logic | 业务逻辑封装管理,特定的业务逻辑实现和封装往往是项目中最复杂的部分 |
|
||||||
| --- --- model | 数据结构管理模块,管理数据实体对象,以及输入与输出数据结构定义 |
|
| --- --- model | 数据结构管理模块,管理数据实体对象,以及输入与输出数据结构定义 |
|
||||||
| --- --- --- input | 对内接口。用于controller调用service或service之间调用时的输入/输出结构定义,以及输入过滤和预处理 |
|
| --- --- --- input | 对内接口。用于controller调用service或service之间调用时的输入/输出结构定义,以及输入过滤和预处理 |
|
||||||
|
| --- queues | 为项目内所有的消息队列的消费者提供统一的初始化和处理 |
|
||||||
|
| --- resource | 静态资源文件。这些文件往往可以通过 资源打包/镜像编译 的形式注入到发布文件中 |
|
||||||
| --- --- router | 注册对外接口和分组中间件 |
|
| --- --- router | 注册对外接口和分组中间件 |
|
||||||
| --- --- service | 用于业务模块解耦的接口定义层具体的接口实现在logic中进行注入 |
|
| --- --- service | 用于业务模块解耦的接口定义层具体的接口实现在logic中进行注入 |
|
||||||
| --- main.go | 插件始化文件和模块插拔接口 |
|
| --- main.go | 插件始化文件和模块插拔接口 |
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||||
// @Author Ms <133814250@qq.com>
|
// @Author Ms <133814250@qq.com>
|
||||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||||
//
|
|
||||||
package home
|
package home
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -11,7 +10,6 @@ import (
|
|||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/util/gconv"
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
"hotgo/addons/hgexample/api/home/index"
|
"hotgo/addons/hgexample/api/home/index"
|
||||||
"hotgo/addons/hgexample/global"
|
|
||||||
"hotgo/addons/hgexample/model/input/sysin"
|
"hotgo/addons/hgexample/model/input/sysin"
|
||||||
"hotgo/addons/hgexample/service"
|
"hotgo/addons/hgexample/service"
|
||||||
"hotgo/internal/model"
|
"hotgo/internal/model"
|
||||||
@ -39,7 +37,7 @@ func (a *cIndex) Index(ctx context.Context, req *index.TestReq) (res *index.Test
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isc.View().RenderTpl(ctx, global.Tpl("home/index.html"), model.View{Data: g.Map{
|
isc.View().RenderTpl(ctx, "home/index.html", model.View{Data: g.Map{
|
||||||
"name": data.Name,
|
"name": data.Name,
|
||||||
"module": data.Module,
|
"module": data.Module,
|
||||||
"time": data.Time,
|
"time": data.Time,
|
||||||
|
4
server/addons/hgexample/crons/crons.go
Normal file
4
server/addons/hgexample/crons/crons.go
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
package crons
|
||||||
|
|
||||||
|
// 定时任务.
|
||||||
|
// 插件中的定时任务可以统一在这里注册和处理
|
@ -20,7 +20,3 @@ func GetSkeleton() *addons.Skeleton {
|
|||||||
}
|
}
|
||||||
return skeleton
|
return skeleton
|
||||||
}
|
}
|
||||||
|
|
||||||
func Tpl(tpl string) string {
|
|
||||||
return addons.Tpl(skeleton.Name, tpl)
|
|
||||||
}
|
|
||||||
|
@ -9,10 +9,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"github.com/gogf/gf/v2/net/ghttp"
|
"github.com/gogf/gf/v2/net/ghttp"
|
||||||
"github.com/gogf/gf/v2/os/gctx"
|
"github.com/gogf/gf/v2/os/gctx"
|
||||||
|
_ "hotgo/addons/hgexample/crons"
|
||||||
"hotgo/addons/hgexample/global"
|
"hotgo/addons/hgexample/global"
|
||||||
_ "hotgo/addons/hgexample/logic"
|
_ "hotgo/addons/hgexample/logic"
|
||||||
|
_ "hotgo/addons/hgexample/queues"
|
||||||
"hotgo/addons/hgexample/router"
|
"hotgo/addons/hgexample/router"
|
||||||
"hotgo/internal/library/addons"
|
"hotgo/internal/library/addons"
|
||||||
|
"hotgo/internal/service"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -37,7 +40,6 @@ func newModule() {
|
|||||||
Description: "系统自带的功能使用示例及其说明,包含一些简单的交互",
|
Description: "系统自带的功能使用示例及其说明,包含一些简单的交互",
|
||||||
Author: "孟帅",
|
Author: "孟帅",
|
||||||
Version: "v1.0.0", // 当该版本号高于已安装的版本号时,会提示可以更新
|
Version: "v1.0.0", // 当该版本号高于已安装的版本号时,会提示可以更新
|
||||||
RootPath: addons.GetModulePath("hgexample"),
|
|
||||||
},
|
},
|
||||||
ctx: gctx.New(),
|
ctx: gctx.New(),
|
||||||
}
|
}
|
||||||
@ -54,6 +56,7 @@ func (m *module) Init(ctx context.Context) {
|
|||||||
// InitRouter 初始化WEB路由
|
// InitRouter 初始化WEB路由
|
||||||
func (m *module) InitRouter(ctx context.Context, group *ghttp.RouterGroup) {
|
func (m *module) InitRouter(ctx context.Context, group *ghttp.RouterGroup) {
|
||||||
m.Init(ctx)
|
m.Init(ctx)
|
||||||
|
group.Middleware(service.Middleware().Addon)
|
||||||
router.Admin(ctx, group)
|
router.Admin(ctx, group)
|
||||||
router.Api(ctx, group)
|
router.Api(ctx, group)
|
||||||
router.Home(ctx, group)
|
router.Home(ctx, group)
|
||||||
|
4
server/addons/hgexample/queues/queues.go
Normal file
4
server/addons/hgexample/queues/queues.go
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
package queues
|
||||||
|
|
||||||
|
// 消息队列.
|
||||||
|
// 插件中的消息队列消费者可以统一在这里注册和处理
|
1
server/addons/hgexample/resource/public/default
Normal file
1
server/addons/hgexample/resource/public/default
Normal file
@ -0,0 +1 @@
|
|||||||
|
Hello!这是创建插件 [功能案例] 时默认生成的一个静态目录文件,用于测试,当你看到这个提示时,说明已经联调成功啦!
|
@ -71,6 +71,9 @@ var (
|
|||||||
addons.RegisterModulesRouter(ctx, group)
|
addons.RegisterModulesRouter(ctx, group)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 设置插件静态目录映射
|
||||||
|
addons.AddStaticPath(ctx, s)
|
||||||
|
|
||||||
// 初始化casbin权限
|
// 初始化casbin权限
|
||||||
casbin.InitEnforcer(ctx)
|
casbin.InitEnforcer(ctx)
|
||||||
|
|
||||||
|
@ -11,16 +11,26 @@ import (
|
|||||||
"hotgo/internal/consts"
|
"hotgo/internal/consts"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetTag(name string) string {
|
// GetModulePath 获取指定模块相对路径
|
||||||
return consts.AddonsTag + name
|
func GetModulePath(name string) string {
|
||||||
|
return "./" + consts.AddonsDir + "/" + name
|
||||||
}
|
}
|
||||||
|
|
||||||
func Tpl(name, tpl string) string {
|
// ViewPath 默认的插件模板路径
|
||||||
return consts.AddonsDir + "/" + name + "/" + tpl
|
func ViewPath(name string) string {
|
||||||
|
return consts.AddonsDir + "/" + name + "/" + "resource/template"
|
||||||
|
}
|
||||||
|
|
||||||
|
// StaticPath 默认的插件静态路映射关系
|
||||||
|
// 最终效果:对外访问地址:/addons/插件模块名称;静态资源路径:/addons/插件模块名称/设置的子路径。
|
||||||
|
// 如果你不喜欢现在的路由风格,可以自行调整
|
||||||
|
func StaticPath(name, path string) (string, string) {
|
||||||
|
return "/" + consts.AddonsDir + "/" + name, consts.AddonsDir + "/" + name + "/" + path
|
||||||
}
|
}
|
||||||
|
|
||||||
// RouterPrefix 路由前缀
|
// RouterPrefix 路由前缀
|
||||||
// 最终效果:/应用名称/插件模块名称/xxx/xxx。如果你不喜欢现在的路由风格,可以自行调整
|
// 最终效果:/应用名称/插件模块名称/xxx/xxx。
|
||||||
|
// 如果你不喜欢现在的路由风格,可以自行调整
|
||||||
func RouterPrefix(ctx context.Context, app, name string) string {
|
func RouterPrefix(ctx context.Context, app, name string) string {
|
||||||
var prefix = "/"
|
var prefix = "/"
|
||||||
if app != "" {
|
if app != "" {
|
||||||
|
@ -2,7 +2,7 @@ package addons
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
"github.com/gogf/gf/v2/os/gfile"
|
"github.com/gogf/gf/v2/os/gfile"
|
||||||
"github.com/gogf/gf/v2/text/gstr"
|
"github.com/gogf/gf/v2/text/gstr"
|
||||||
"hotgo/internal/consts"
|
"hotgo/internal/consts"
|
||||||
@ -16,7 +16,6 @@ func Build(ctx context.Context, sk Skeleton, conf *model.BuildAddonConfig) (err
|
|||||||
var (
|
var (
|
||||||
buildPath = "./" + consts.AddonsDir + "/" + sk.Name
|
buildPath = "./" + consts.AddonsDir + "/" + sk.Name
|
||||||
modulesPath = "./" + consts.AddonsDir + "/modules/" + sk.Name + ".go"
|
modulesPath = "./" + consts.AddonsDir + "/modules/" + sk.Name + ".go"
|
||||||
templatePath = gstr.Replace(conf.TemplatePath, "{$name}", sk.Name)
|
|
||||||
webApiPath = gstr.Replace(conf.WebApiPath, "{$name}", sk.Name)
|
webApiPath = gstr.Replace(conf.WebApiPath, "{$name}", sk.Name)
|
||||||
webViewsPath = gstr.Replace(conf.WebViewsPath, "{$name}", sk.Name)
|
webViewsPath = gstr.Replace(conf.WebViewsPath, "{$name}", sk.Name)
|
||||||
replaces = map[string]string{
|
replaces = map[string]string{
|
||||||
@ -31,7 +30,7 @@ func Build(ctx context.Context, sk Skeleton, conf *model.BuildAddonConfig) (err
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if err = checkBuildDir(buildPath, modulesPath, templatePath, webApiPath, webViewsPath); err != nil {
|
if err = checkBuildDir(buildPath, modulesPath, webApiPath, webViewsPath); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +45,7 @@ func Build(ctx context.Context, sk Skeleton, conf *model.BuildAddonConfig) (err
|
|||||||
|
|
||||||
for _, path := range list {
|
for _, path := range list {
|
||||||
if !gfile.IsReadable(path) {
|
if !gfile.IsReadable(path) {
|
||||||
err = fmt.Errorf("file:%v is unreadable, please check permissions", path)
|
err = gerror.Newf("file:%v is unreadable, please check permissions", path)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,11 +71,6 @@ func Build(ctx context.Context, sk Skeleton, conf *model.BuildAddonConfig) (err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// home默认页面
|
|
||||||
if err = gfile.PutContents(templatePath+"/home/index.html", gstr.ReplaceByMap(homeLayout, replaces)); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// webApi
|
// webApi
|
||||||
if err = gfile.PutContents(webApiPath+"/config/index.ts", gstr.ReplaceByMap(webApiLayout, replaces)); err != nil {
|
if err = gfile.PutContents(webApiPath+"/config/index.ts", gstr.ReplaceByMap(webApiLayout, replaces)); err != nil {
|
||||||
return
|
return
|
||||||
@ -94,15 +88,15 @@ func Build(ctx context.Context, sk Skeleton, conf *model.BuildAddonConfig) (err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkBuildDir(paths ...string) error {
|
func checkBuildDir(paths ...string) (err error) {
|
||||||
if len(paths) == 0 {
|
if len(paths) == 0 {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
if gfile.Exists(path) {
|
if gfile.Exists(path) {
|
||||||
return fmt.Errorf("插件已存在,请换一个插件名称或者经确认无误后依次删除文件夹: [%v] 后重新生成", strings.Join(paths, "、\t"))
|
return gerror.Newf("插件已存在,请换一个插件名称或者经确认无误后依次删除文件夹: [%v] 后重新生成", strings.Join(paths, "、\t"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
@ -11,39 +11,6 @@ package modules
|
|||||||
import _ "hotgo/addons/@{.name}"
|
import _ "hotgo/addons/@{.name}"
|
||||||
`
|
`
|
||||||
|
|
||||||
homeLayout = `<!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>`
|
|
||||||
|
|
||||||
webApiLayout = `import { http } from '@/utils/http/axios';
|
webApiLayout = `import { http } from '@/utils/http/axios';
|
||||||
|
|
||||||
export function getConfig(params) {
|
export function getConfig(params) {
|
||||||
|
@ -23,11 +23,12 @@ type InstallRecord struct {
|
|||||||
UpdatedAt *gtime.Time `json:"updatedAt" description:"更新时间"`
|
UpdatedAt *gtime.Time `json:"updatedAt" description:"更新时间"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetModel(ctx context.Context) *gdb.Model {
|
||||||
|
return g.Model("sys_addons_install").Ctx(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
func ScanInstall(m Module) (record *InstallRecord, err error) {
|
func ScanInstall(m Module) (record *InstallRecord, err error) {
|
||||||
err = g.Model("sys_addons_install").
|
err = GetModel(m.Ctx()).Where("name", m.GetSkeleton().Name).Scan(&record)
|
||||||
Ctx(m.Ctx()).
|
|
||||||
Where("name", m.GetSkeleton().Name).
|
|
||||||
Scan(&record)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,23 +60,14 @@ func Install(m Module) (err error) {
|
|||||||
"version": m.GetSkeleton().Version,
|
"version": m.GetSkeleton().Version,
|
||||||
"status": consts.AddonsInstallStatusOk,
|
"status": consts.AddonsInstallStatusOk,
|
||||||
}
|
}
|
||||||
|
|
||||||
return g.DB().Transaction(m.Ctx(), func(ctx context.Context, tx gdb.TX) error {
|
return g.DB().Transaction(m.Ctx(), func(ctx context.Context, tx gdb.TX) error {
|
||||||
if record != nil {
|
if record != nil {
|
||||||
_, err = g.Model("sys_addons_install").
|
_, _ = GetModel(m.Ctx()).Where("id", record.Id).Delete()
|
||||||
Ctx(m.Ctx()).
|
|
||||||
Where("id", record.Id).
|
|
||||||
Delete()
|
|
||||||
}
|
}
|
||||||
_, err = g.Model("sys_addons_install").
|
|
||||||
Ctx(m.Ctx()).
|
|
||||||
Data(data).
|
|
||||||
Insert()
|
|
||||||
|
|
||||||
if err != nil {
|
if _, err = GetModel(m.Ctx()).Data(data).Insert(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.Install(ctx)
|
return m.Install(ctx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -94,18 +86,10 @@ func Upgrade(m Module) (err error) {
|
|||||||
data := g.Map{
|
data := g.Map{
|
||||||
"version": m.GetSkeleton().Version,
|
"version": m.GetSkeleton().Version,
|
||||||
}
|
}
|
||||||
|
|
||||||
return g.DB().Transaction(m.Ctx(), func(ctx context.Context, tx gdb.TX) error {
|
return g.DB().Transaction(m.Ctx(), func(ctx context.Context, tx gdb.TX) error {
|
||||||
_, err = g.Model("sys_addons_install").
|
if _, err = GetModel(m.Ctx()).Where("id", record.Id).Data(data).Update(); err != nil {
|
||||||
Ctx(m.Ctx()).
|
|
||||||
Where("id", record.Id).
|
|
||||||
Data(data).
|
|
||||||
Update()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.Upgrade(ctx)
|
return m.Upgrade(ctx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -125,18 +109,10 @@ func UnInstall(m Module) (err error) {
|
|||||||
"version": m.GetSkeleton().Version,
|
"version": m.GetSkeleton().Version,
|
||||||
"status": consts.AddonsInstallStatusUn,
|
"status": consts.AddonsInstallStatusUn,
|
||||||
}
|
}
|
||||||
|
|
||||||
return g.DB().Transaction(m.Ctx(), func(ctx context.Context, tx gdb.TX) error {
|
return g.DB().Transaction(m.Ctx(), func(ctx context.Context, tx gdb.TX) error {
|
||||||
_, err = g.Model("sys_addons_install").
|
if _, err = GetModel(m.Ctx()).Where("id", record.Id).Data(data).Update(); err != nil {
|
||||||
Ctx(m.Ctx()).
|
|
||||||
Where("id", record.Id).
|
|
||||||
Data(data).
|
|
||||||
Update()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.UnInstall(ctx)
|
return m.UnInstall(ctx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,10 @@ package addons
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/net/ghttp"
|
"github.com/gogf/gf/v2/net/ghttp"
|
||||||
"github.com/gogf/gf/v2/os/gfile"
|
"github.com/gogf/gf/v2/os/gfile"
|
||||||
"hotgo/internal/consts"
|
"github.com/gogf/gf/v2/os/gview"
|
||||||
"hotgo/internal/model/input/form"
|
"hotgo/internal/model/input/form"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
@ -26,6 +27,7 @@ type Skeleton struct {
|
|||||||
Author string `json:"author"` // 作者
|
Author string `json:"author"` // 作者
|
||||||
Version string `json:"version"` // 版本号
|
Version string `json:"version"` // 版本号
|
||||||
RootPath string `json:"rootPath"` // 根路径
|
RootPath string `json:"rootPath"` // 根路径
|
||||||
|
View *gview.View `json:"view"` // 模板引擎
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Skeleton) GetModule() Module {
|
func (s *Skeleton) GetModule() Module {
|
||||||
@ -71,6 +73,14 @@ func RegisterModule(m Module) Module {
|
|||||||
if ok {
|
if ok {
|
||||||
panic("module repeat registration, name:" + name)
|
panic("module repeat registration, name:" + name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sk := m.GetSkeleton()
|
||||||
|
if sk == nil {
|
||||||
|
panic("module skeleton not initialized, name:" + name)
|
||||||
|
}
|
||||||
|
|
||||||
|
sk.RootPath = GetModulePath(name)
|
||||||
|
sk.View = NewView(m.Ctx(), name)
|
||||||
modules[name] = m
|
modules[name] = m
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
@ -110,9 +120,45 @@ func GetModuleRealPath(name string) string {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetModulePath 获取指定模块相对路径
|
// NewView 初始化一个插件的模板引擎
|
||||||
func GetModulePath(name string) string {
|
func NewView(ctx context.Context, name string) *gview.View {
|
||||||
return "./" + consts.AddonsDir + "/" + name
|
view := gview.New()
|
||||||
|
|
||||||
|
if err := view.SetPath(ViewPath(name)); err != nil {
|
||||||
|
g.Log().Warningf(ctx, "NewView SetPath err:%+v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认和主模块使用一致的变量分隔符号
|
||||||
|
delimiters := g.Cfg().MustGet(ctx, "viewer.delimiters", []string{"@{", "}"}).Strings()
|
||||||
|
if len(delimiters) != 2 {
|
||||||
|
g.Log().Warning(ctx, "NewView delimiters config error")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
view.SetDelimiters(delimiters[0], delimiters[1])
|
||||||
|
|
||||||
|
//// 更多配置
|
||||||
|
//view.SetI18n()
|
||||||
|
//// ...
|
||||||
|
return 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]
|
||||||
|
}
|
||||||
|
|
||||||
|
if basePath == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, module := range filterInstalled() {
|
||||||
|
name := module.GetSkeleton().Name
|
||||||
|
prefix, path := StaticPath(name, basePath)
|
||||||
|
server.AddStaticPath(prefix, path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterInstalled 过滤已安装模块
|
// filterInstalled 过滤已安装模块
|
||||||
|
@ -133,7 +133,23 @@ func (s *sMiddleware) Addon(r *ghttp.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
contexts.SetAddonName(ctx, addons.GetModule(ss[0]).GetSkeleton().Name)
|
module := addons.GetModule(ss[0])
|
||||||
|
if module == nil {
|
||||||
|
g.Log().Warningf(ctx, "addon module = nil, name:%v", ss[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sk := module.GetSkeleton()
|
||||||
|
if sk == nil {
|
||||||
|
g.Log().Warningf(ctx, "addon skeleton = nil, name:%v", ss[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if sk.View != nil {
|
||||||
|
r.SetView(sk.View)
|
||||||
|
}
|
||||||
|
|
||||||
|
contexts.SetAddonName(ctx, sk.Name)
|
||||||
r.Middleware.Next()
|
r.Middleware.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +69,6 @@ type GenerateConfig struct {
|
|||||||
// BuildAddonConfig 构建插件模块配置
|
// BuildAddonConfig 构建插件模块配置
|
||||||
type BuildAddonConfig struct {
|
type BuildAddonConfig struct {
|
||||||
SrcPath string `json:"srcPath"`
|
SrcPath string `json:"srcPath"`
|
||||||
TemplatePath string `json:"templatePath"`
|
|
||||||
WebApiPath string `json:"webApiPath"`
|
WebApiPath string `json:"webApiPath"`
|
||||||
WebViewsPath string `json:"webViewsPath"`
|
WebViewsPath string `json:"webViewsPath"`
|
||||||
}
|
}
|
||||||
|
@ -301,6 +301,5 @@ hggen:
|
|||||||
# 生成插件模块,通过后台创建新插件时使用的模板,允许自定义,可以参考default模板进行改造
|
# 生成插件模块,通过后台创建新插件时使用的模板,允许自定义,可以参考default模板进行改造
|
||||||
addon:
|
addon:
|
||||||
srcPath: "./resource/generate/default/addon" # 生成模板路径
|
srcPath: "./resource/generate/default/addon" # 生成模板路径
|
||||||
templatePath: "./resource/template/addons/{$name}" # 页面模板路径
|
|
||||||
webApiPath: "../web/src/api/addons/{$name}" # webApi生成路径
|
webApiPath: "../web/src/api/addons/{$name}" # webApi生成路径
|
||||||
webViewsPath: "../web/src/views/addons/{$name}" # web页面生成路径
|
webViewsPath: "../web/src/views/addons/{$name}" # web页面生成路径
|
@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/util/gconv"
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
"hotgo/addons/@{.name}/api/home/index"
|
"hotgo/addons/@{.name}/api/home/index"
|
||||||
"hotgo/addons/@{.name}/global"
|
|
||||||
"hotgo/addons/@{.name}/model/input/sysin"
|
"hotgo/addons/@{.name}/model/input/sysin"
|
||||||
"hotgo/addons/@{.name}/service"
|
"hotgo/addons/@{.name}/service"
|
||||||
"hotgo/internal/model"
|
"hotgo/internal/model"
|
||||||
@ -38,7 +37,7 @@ func (a *cIndex) Index(ctx context.Context, req *index.TestReq) (res *index.Test
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isc.View().RenderTpl(ctx, global.Tpl("home/index.html"), model.View{Data: g.Map{
|
isc.View().RenderTpl(ctx, "home/index.html", model.View{Data: g.Map{
|
||||||
"name": data.Name,
|
"name": data.Name,
|
||||||
"module": data.Module,
|
"module": data.Module,
|
||||||
"time": data.Time,
|
"time": data.Time,
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
package crons
|
||||||
|
|
||||||
|
// 定时任务.
|
||||||
|
// 插件中的定时任务可以统一在这里注册和处理
|
@ -20,7 +20,3 @@ func GetSkeleton() *addons.Skeleton {
|
|||||||
}
|
}
|
||||||
return skeleton
|
return skeleton
|
||||||
}
|
}
|
||||||
|
|
||||||
func Tpl(tpl string) string {
|
|
||||||
return addons.Tpl(skeleton.Name, tpl)
|
|
||||||
}
|
|
||||||
|
@ -9,8 +9,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"github.com/gogf/gf/v2/net/ghttp"
|
"github.com/gogf/gf/v2/net/ghttp"
|
||||||
"github.com/gogf/gf/v2/os/gctx"
|
"github.com/gogf/gf/v2/os/gctx"
|
||||||
|
_ "hotgo/addons/@{.name}/crons"
|
||||||
"hotgo/addons/@{.name}/global"
|
"hotgo/addons/@{.name}/global"
|
||||||
_ "hotgo/addons/@{.name}/logic"
|
_ "hotgo/addons/@{.name}/logic"
|
||||||
|
_ "hotgo/addons/@{.name}/queues"
|
||||||
"hotgo/addons/@{.name}/router"
|
"hotgo/addons/@{.name}/router"
|
||||||
"hotgo/internal/library/addons"
|
"hotgo/internal/library/addons"
|
||||||
"hotgo/internal/service"
|
"hotgo/internal/service"
|
||||||
@ -38,11 +40,9 @@ func newModule() {
|
|||||||
Description: `@{.description}`,
|
Description: `@{.description}`,
|
||||||
Author: `@{.author}`,
|
Author: `@{.author}`,
|
||||||
Version: `@{.version}`, // 当该版本号高于已安装的版本号时,会提示可以更新
|
Version: `@{.version}`, // 当该版本号高于已安装的版本号时,会提示可以更新
|
||||||
RootPath: addons.GetModulePath("@{.name}"),
|
|
||||||
},
|
},
|
||||||
ctx: gctx.New(),
|
ctx: gctx.New(),
|
||||||
}
|
}
|
||||||
|
|
||||||
addons.RegisterModule(m)
|
addons.RegisterModule(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
package queues
|
||||||
|
|
||||||
|
// 消息队列.
|
||||||
|
// 插件中的消息队列消费者可以统一在这里注册和处理
|
@ -0,0 +1 @@
|
|||||||
|
Hello!这是创建插件 [@{.label}] 时默认生成的一个静态目录文件,用于测试,当你看到这个提示时,说明已经联调成功啦!
|
@ -0,0 +1,30 @@
|
|||||||
|
<!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>
|
@ -23,11 +23,7 @@
|
|||||||
<h1><p>Hello,@{.Data.name}!!</p></h1>
|
<h1><p>Hello,@{.Data.name}!!</p></h1>
|
||||||
<h2><p>当前版本:@{.Data.version}</p></h2>
|
<h2><p>当前版本:@{.Data.version}</p></h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -11,6 +11,9 @@
|
|||||||
<n-tab-pane name="home" tab="前台页面">
|
<n-tab-pane name="home" tab="前台页面">
|
||||||
<Form path="/home/hgexample/index/test?name=HotGo" />
|
<Form path="/home/hgexample/index/test?name=HotGo" />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="static" tab="静态目录">
|
||||||
|
<Form path="/addons/hgexample/default" />
|
||||||
|
</n-tab-pane>
|
||||||
<n-tab-pane name="websocket" tab="Websocket">
|
<n-tab-pane name="websocket" tab="Websocket">
|
||||||
<Form path="/socket/hgexample/index/test?name=HotGo" />
|
<Form path="/socket/hgexample/index/test?name=HotGo" />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
Loading…
Reference in New Issue
Block a user