mirror of
https://github.com/quarkcloudio/quark-go.git
synced 2025-10-16 04:50:43 +08:00
473 lines
12 KiB
Go
473 lines
12 KiB
Go
package builder
|
||
|
||
import (
|
||
"io"
|
||
"net/http"
|
||
"reflect"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/jinzhu/copier"
|
||
"github.com/labstack/echo/v4"
|
||
"github.com/quarkcms/quark-go/pkg/dal"
|
||
"github.com/quarkcms/quark-go/pkg/github"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// Version of current package
|
||
const Version = "1.1.1"
|
||
|
||
// 静态文件URL
|
||
const RespositoryURL = "https://github.com/quarkcms/quark-go/tree/main/website/"
|
||
|
||
type Engine struct {
|
||
Echo *echo.Echo // 内置Echo
|
||
UseHandlers []func(ctx *Context) error // 中间件方法
|
||
config *Config // 配置
|
||
providers []interface{} // 服务列表
|
||
routerPaths []string // 路由路径列表
|
||
}
|
||
|
||
type RouteMapping struct {
|
||
Method string
|
||
Path string
|
||
HandlerName string
|
||
}
|
||
|
||
type DBConfig struct {
|
||
Dialector gorm.Dialector
|
||
Opts gorm.Option
|
||
}
|
||
|
||
type Config struct {
|
||
AppKey string // 应用加密Key,用于JWT认证
|
||
AppName string // 应用名称
|
||
DBConfig *DBConfig // 数据库配置
|
||
StaticPath string // 静态文件目录
|
||
Providers []interface{} // 服务列表
|
||
AdminLayout *AdminLayout // 后台布局
|
||
}
|
||
|
||
type AdminLayout struct {
|
||
Title string // layout 的左上角 的 title
|
||
Logo interface{} // layout 的左上角 的 logo
|
||
Actions interface{} // layout 的头部行为
|
||
Layout string // layout 的菜单模式,side:右侧导航,top:顶部导航,mix:混合模式
|
||
SplitMenus bool // layout 的菜单模式为mix时,是否自动分割菜单
|
||
ContentWidth string // layout 的内容模式,Fluid:定宽 1200px,Fixed:自适应
|
||
PrimaryColor string // 主题色,"#1890ff"
|
||
FixedHeader bool // 是否固定 header 到顶部
|
||
FixSiderbar bool // 是否固定导航
|
||
IconfontUrl string // 使用 IconFont 的图标配置
|
||
Locale string // 当前 layout 的语言设置,'zh-CN' | 'zh-TW' | 'en-US'
|
||
SiderWidth int // 侧边菜单宽度
|
||
Copyright string // 网站版权 time.Now().Format("2006") + " QuarkGo"
|
||
Links []map[string]interface{} // 友情链接
|
||
}
|
||
|
||
// Handle is a function that can be registered to a route to handle HTTP
|
||
// requests. Like http.HandlerFunc, but has a third parameter for the values of
|
||
// wildcards (path variables).
|
||
type Handle func(ctx *Context) error
|
||
|
||
// 初始化对象
|
||
func New(config *Config) *Engine {
|
||
|
||
// 初始化echo引擎
|
||
e := echo.New()
|
||
|
||
// 初始化数据库
|
||
if config.DBConfig != nil {
|
||
dal.InitDB(config.DBConfig.Dialector, config.DBConfig.Opts)
|
||
}
|
||
|
||
// 初始化后台布局
|
||
config.AdminLayout = initAdminLayout(config.AdminLayout)
|
||
|
||
// 定义结构体
|
||
engine := &Engine{
|
||
Echo: e,
|
||
providers: config.Providers,
|
||
config: config,
|
||
}
|
||
|
||
// 下载静态文件
|
||
github.Download(RespositoryURL, config.StaticPath)
|
||
|
||
// 初始化路由列表
|
||
engine.initRouterPaths()
|
||
|
||
// 调用初始化方法
|
||
return engine
|
||
}
|
||
|
||
// 初始化后台布局
|
||
func initAdminLayout(adminLayout *AdminLayout) *AdminLayout {
|
||
defalutAdminLayout := &AdminLayout{
|
||
"QuarkGo",
|
||
false,
|
||
nil,
|
||
"mix",
|
||
false,
|
||
"Fluid",
|
||
"#1890ff",
|
||
true,
|
||
true,
|
||
"//at.alicdn.com/t/font_1615691_3pgkh5uyob.js",
|
||
"zh-CN",
|
||
208,
|
||
time.Now().Format("2006") + " QuarkGo",
|
||
[]map[string]interface{}{
|
||
{
|
||
"key": "1",
|
||
"title": "Quark",
|
||
"href": "http://www.quarkcms.com/",
|
||
},
|
||
{
|
||
"key": "2",
|
||
"title": "爱小圈",
|
||
"href": "http://www.ixiaoquan.com",
|
||
},
|
||
{
|
||
"key": "3",
|
||
"title": "Github",
|
||
"href": "https://github.com/quarkcms",
|
||
},
|
||
},
|
||
}
|
||
|
||
// 设置布局
|
||
copier.CopyWithOption(defalutAdminLayout, adminLayout, copier.Option{IgnoreEmpty: true})
|
||
|
||
return defalutAdminLayout
|
||
}
|
||
|
||
// 获取当前配置
|
||
func (p *Engine) GetConfig() *Config {
|
||
return p.config
|
||
}
|
||
|
||
// 获取当前AdminLayout配置
|
||
func (p *Engine) GetAdminLayout() *AdminLayout {
|
||
return p.config.AdminLayout
|
||
}
|
||
|
||
// 创建上下文
|
||
func (p *Engine) NewContext(Writer http.ResponseWriter, Request *http.Request) *Context {
|
||
return &Context{
|
||
Engine: p,
|
||
Request: Request,
|
||
Writer: Writer,
|
||
}
|
||
}
|
||
|
||
// 转换Request、Response对象
|
||
func (p *Engine) TransformContext(fullPath string, header map[string][]string, method string, url string, body io.Reader, writer io.Writer) *Context {
|
||
// 转换为http.ResponseWriter
|
||
w := NewResponse(writer)
|
||
|
||
// 转换为http.Request
|
||
r := NewRequest(method, url, body)
|
||
|
||
// 转换Header
|
||
r.Header = header
|
||
|
||
// 创建上下文
|
||
ctx := p.NewContext(w, r)
|
||
|
||
// 设置当前路由
|
||
ctx.SetFullPath(fullPath)
|
||
|
||
// 返回对象
|
||
return ctx
|
||
}
|
||
|
||
// 初始化路由路径列表
|
||
func (p *Engine) initRouterPaths() {
|
||
if p.routerPaths != nil {
|
||
return
|
||
}
|
||
|
||
var routerPaths []string
|
||
for _, provider := range p.providers {
|
||
|
||
// 初始化
|
||
getTemplateInstance := provider.(interface {
|
||
Init() interface{}
|
||
}).Init()
|
||
|
||
// 获取模板定义的路由
|
||
templateInstanceRoutes := getTemplateInstance.(interface {
|
||
GetRouteMapping() []*RouteMapping
|
||
}).GetRouteMapping()
|
||
|
||
for _, v := range templateInstanceRoutes {
|
||
providerName := reflect.TypeOf(provider).String()
|
||
getNames := strings.Split(providerName, ".")
|
||
structName := getNames[len(getNames)-1]
|
||
|
||
if strings.Contains(v.Path, ":resource") {
|
||
path := strings.Replace(v.Path, ":resource", strings.ToLower(structName), -1)
|
||
//处理行为
|
||
if strings.Contains(path, ":uriKey") {
|
||
actions := getTemplateInstance.(interface {
|
||
Actions(ctx *Context) []interface{}
|
||
}).Actions(&Context{})
|
||
|
||
for _, av := range actions {
|
||
|
||
// uri唯一标识
|
||
uriKey := av.(interface {
|
||
GetUriKey(interface{}) string
|
||
}).GetUriKey(av)
|
||
|
||
actionType := av.(interface{ GetActionType() string }).GetActionType()
|
||
if actionType == "dropdown" {
|
||
dropdownActions := av.(interface{ GetActions() []interface{} }).GetActions()
|
||
for _, dropdownAction := range dropdownActions {
|
||
uriKey := dropdownAction.(interface {
|
||
GetUriKey(interface{}) string
|
||
}).GetUriKey(dropdownAction) // uri唯一标识
|
||
|
||
path = strings.Replace(path, ":uriKey", uriKey, -1)
|
||
}
|
||
} else {
|
||
path = strings.Replace(path, ":uriKey", uriKey, -1)
|
||
}
|
||
}
|
||
}
|
||
routerPaths = append(routerPaths, path)
|
||
}
|
||
}
|
||
}
|
||
|
||
p.routerPaths = routerPaths
|
||
}
|
||
|
||
// 获取路由路径列表
|
||
func (p *Engine) GetRouterPaths() []string {
|
||
return p.routerPaths
|
||
}
|
||
|
||
// 通用调用方法
|
||
func (p *Engine) Use(args interface{}) {
|
||
argsName := reflect.TypeOf(args).String()
|
||
|
||
switch argsName {
|
||
case "*builder.AdminLayout":
|
||
p.config.AdminLayout = initAdminLayout(args.(*AdminLayout))
|
||
case "func(*builder.Context) error":
|
||
p.UseHandlers = append(p.UseHandlers, args.(func(ctx *Context) error))
|
||
default:
|
||
panic(argsName + " arguments was not found")
|
||
}
|
||
}
|
||
|
||
// 解析模版方法
|
||
func (p *Engine) templateHandleParser(ctx *Context) error {
|
||
var (
|
||
result interface{}
|
||
err error
|
||
templateInstance interface{}
|
||
)
|
||
|
||
// 获取模板实例
|
||
templateInstance = ctx.Template
|
||
|
||
// 执行挂载的方法
|
||
templateInstanceRoutes := templateInstance.(interface {
|
||
GetRouteMapping() []*RouteMapping
|
||
}).GetRouteMapping()
|
||
|
||
for _, v := range templateInstanceRoutes {
|
||
if v.Path == ctx.FullPath() {
|
||
getResult := reflect.
|
||
ValueOf(templateInstance).
|
||
MethodByName(v.HandlerName).
|
||
Call([]reflect.Value{
|
||
reflect.ValueOf(ctx),
|
||
})
|
||
if len(getResult) == 1 {
|
||
result = getResult[0].Interface()
|
||
if v, ok := result.(error); ok {
|
||
err = v
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return err
|
||
}
|
||
|
||
// 渲染
|
||
func (p *Engine) Render(ctx *Context) error {
|
||
// 初始化模板
|
||
err := ctx.InitTemplateInstance()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 解析UseHandler方法
|
||
err = ctx.useHandlerParser()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 解析模版方法
|
||
return p.templateHandleParser(ctx)
|
||
}
|
||
|
||
// 处理模版上的路由映射关系
|
||
func (p *Engine) routeMappingParser() {
|
||
for _, provider := range p.providers {
|
||
|
||
// 初始化
|
||
getTemplateInstance := provider.(interface {
|
||
Init() interface{}
|
||
}).Init()
|
||
|
||
// 获取模板定义的路由
|
||
templateInstanceRoutes := getTemplateInstance.(interface {
|
||
GetRouteMapping() []*RouteMapping
|
||
}).GetRouteMapping()
|
||
|
||
for _, v := range templateInstanceRoutes {
|
||
switch v.Method {
|
||
case "GET":
|
||
p.GET(v.Path, func(ctx *Context) error {
|
||
return p.templateHandleParser(ctx)
|
||
})
|
||
case "HEAD":
|
||
p.HEAD(v.Path, func(ctx *Context) error {
|
||
return p.templateHandleParser(ctx)
|
||
})
|
||
case "OPTIONS":
|
||
p.OPTIONS(v.Path, func(ctx *Context) error {
|
||
return p.templateHandleParser(ctx)
|
||
})
|
||
case "POST":
|
||
p.POST(v.Path, func(ctx *Context) error {
|
||
return p.templateHandleParser(ctx)
|
||
})
|
||
case "PUT":
|
||
p.PUT(v.Path, func(ctx *Context) error {
|
||
return p.templateHandleParser(ctx)
|
||
})
|
||
case "PATCH":
|
||
p.PATCH(v.Path, func(ctx *Context) error {
|
||
return p.templateHandleParser(ctx)
|
||
})
|
||
case "DELETE":
|
||
p.DELETE(v.Path, func(ctx *Context) error {
|
||
return p.templateHandleParser(ctx)
|
||
})
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 适配Echo方法
|
||
func (p *Engine) echoHandle(path string, handle Handle, c echo.Context) error {
|
||
ctx := p.NewContext(c.Response().Writer, c.Request())
|
||
|
||
// 设置路由路径
|
||
ctx.SetFullPath(path)
|
||
|
||
// 初始化模板
|
||
ctx.InitTemplateInstance()
|
||
|
||
// 解析UseHandler方法
|
||
err := ctx.useHandlerParser()
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
|
||
// 执行方法
|
||
return handle(ctx)
|
||
}
|
||
|
||
// GET is a shortcut for router.Handle(http.MethodGet, path, handle)
|
||
func (p *Engine) GET(path string, handle Handle) error {
|
||
p.Echo.GET(path, func(c echo.Context) error {
|
||
return p.echoHandle(path, handle, c)
|
||
})
|
||
|
||
return nil
|
||
}
|
||
|
||
// HEAD is a shortcut for router.Handle(http.MethodHead, path, handle)
|
||
func (p *Engine) HEAD(path string, handle Handle) error {
|
||
p.Echo.HEAD(path, func(c echo.Context) error {
|
||
return p.echoHandle(path, handle, c)
|
||
})
|
||
|
||
return nil
|
||
}
|
||
|
||
// OPTIONS is a shortcut for router.Handle(http.MethodOptions, path, handle)
|
||
func (p *Engine) OPTIONS(path string, handle Handle) error {
|
||
p.Echo.OPTIONS(path, func(c echo.Context) error {
|
||
return p.echoHandle(path, handle, c)
|
||
})
|
||
|
||
return nil
|
||
}
|
||
|
||
// POST is a shortcut for router.Handle(http.MethodPost, path, handle)
|
||
func (p *Engine) POST(path string, handle Handle) error {
|
||
p.Echo.POST(path, func(c echo.Context) error {
|
||
return p.echoHandle(path, handle, c)
|
||
})
|
||
|
||
return nil
|
||
}
|
||
|
||
// PUT is a shortcut for router.Handle(http.MethodPut, path, handle)
|
||
func (p *Engine) PUT(path string, handle Handle) error {
|
||
p.Echo.PUT(path, func(c echo.Context) error {
|
||
return p.echoHandle(path, handle, c)
|
||
})
|
||
|
||
return nil
|
||
}
|
||
|
||
// PATCH is a shortcut for router.Handle(http.MethodPatch, path, handle)
|
||
func (p *Engine) PATCH(path string, handle Handle) error {
|
||
p.Echo.PATCH(path, func(c echo.Context) error {
|
||
return p.echoHandle(path, handle, c)
|
||
})
|
||
|
||
return nil
|
||
}
|
||
|
||
// DELETE is a shortcut for router.Handle(http.MethodDelete, path, handle)
|
||
func (p *Engine) DELETE(path string, handle Handle) error {
|
||
p.Echo.DELETE(path, func(c echo.Context) error {
|
||
return p.echoHandle(path, handle, c)
|
||
})
|
||
|
||
return nil
|
||
}
|
||
|
||
// Any is a shortcut for router.Handle(http.MethodGet, path, handle)
|
||
func (p *Engine) Any(path string, handle Handle) error {
|
||
p.Echo.Any(path, func(c echo.Context) error {
|
||
return p.echoHandle(path, handle, c)
|
||
})
|
||
|
||
return nil
|
||
}
|
||
|
||
// Run Server
|
||
func (p *Engine) Run(addr string) {
|
||
|
||
// 静态文件目录
|
||
p.Echo.Static("/", "./website")
|
||
|
||
// 处理模版上的路由映射关系
|
||
p.routeMappingParser()
|
||
|
||
// 启动服务
|
||
p.Echo.Logger.Fatal(p.Echo.Start(addr))
|
||
}
|