diff --git a/server/.gitignore b/server/.gitignore index 752433a..e48bf87 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -24,6 +24,7 @@ resource/ssl/server.key temp/ main.exe main.exe~ +*.exe *.log *.zip .idea diff --git a/server/api/home/base/site.go b/server/api/home/base/site.go new file mode 100644 index 0000000..6d06972 --- /dev/null +++ b/server/api/home/base/site.go @@ -0,0 +1,16 @@ +// Package base +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2022 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// +package base + +import "github.com/gogf/gf/v2/frame/g" + +type SiteIndexReq struct { + g.Meta `path:"/index" method:"get" summary:"首页" tags:"首页"` +} +type SiteIndexRes struct { + g.Meta `mime:"text/html" type:"string" example:"
"` +} diff --git a/server/internal/cmd/cmd.go b/server/internal/cmd/cmd.go index 938bddd..b6e6db5 100644 --- a/server/internal/cmd/cmd.go +++ b/server/internal/cmd/cmd.go @@ -17,7 +17,7 @@ var ( serverCloseSignal chan struct{} Main = &gcmd.Command{ Description: ` - 欢迎使用HotGo! + 命令提示符 --------------------------------------------------------------------------------- 启动服务 >> HTTP服务 [go run main.go http] @@ -34,8 +34,8 @@ var ( Name: "help", Brief: "查看帮助", Description: ` - 欢迎使用 HotGo - 当前版本:v2.0.0 + github地址:https://github.com/bufanyun/hotgo + 文档地址:文档正在书写中,请耐心等一等。 `, } diff --git a/server/internal/cmd/http.go b/server/internal/cmd/http.go index a17baa9..ed02e62 100644 --- a/server/internal/cmd/http.go +++ b/server/internal/cmd/http.go @@ -11,6 +11,8 @@ import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/os/gcmd" + baseApi "hotgo/api/home/base" + "hotgo/internal/controller/home/base" "hotgo/internal/library/casbin" "hotgo/internal/model" "hotgo/internal/router" @@ -55,7 +57,7 @@ var ( // 注册默认首页路由 group.ALL("/", func(r *ghttp.Request) { - r.Response.Write("hello hotGo!!") + _, _ = base.Site.Index(r.Context(), &baseApi.SiteIndexReq{}) return }) @@ -72,6 +74,8 @@ var ( // 注册websocket路由 router.WebSocket(ctx, group) + // 注册前台页面路由 + router.Home(ctx, group) }) // 启动定时任务 diff --git a/server/internal/controller/home/base/site.go b/server/internal/controller/home/base/site.go new file mode 100644 index 0000000..98cd63c --- /dev/null +++ b/server/internal/controller/home/base/site.go @@ -0,0 +1,29 @@ +// Package base +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2022 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// +package base + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "hotgo/api/home/base" + "hotgo/internal/consts" + "hotgo/internal/model" + "hotgo/internal/service" +) + +// Site 基础 +var Site = cSite{} + +type cSite struct{} + +func (a *cSite) Index(ctx context.Context, req *base.SiteIndexReq) (res *base.SiteIndexRes, err error) { + service.View().Render(ctx, model.View{Data: g.Map{ + "name": "HotGo", + "version": consts.VersionApp, + }}) + return +} diff --git a/server/internal/logic/logic.go b/server/internal/logic/logic.go index dcd6856..8e2ca5a 100644 --- a/server/internal/logic/logic.go +++ b/server/internal/logic/logic.go @@ -10,4 +10,5 @@ import ( _ "hotgo/internal/logic/hook" _ "hotgo/internal/logic/middleware" _ "hotgo/internal/logic/sys" + _ "hotgo/internal/logic/view" ) diff --git a/server/internal/logic/middleware/response.go b/server/internal/logic/middleware/response.go index f99ff03..f4cb78e 100644 --- a/server/internal/logic/middleware/response.go +++ b/server/internal/logic/middleware/response.go @@ -29,6 +29,12 @@ func (s *sMiddleware) ResponseHandler(r *ghttp.Request) { err error ) + // 模板页面响应 + if "text/html" == r.Response.Header().Get("Content-Type") { + r.Middleware.Next() + return + } + if err := r.GetError(); err != nil { g.Log().Print(ctx, err) // 记录到自定义错误日志文件 diff --git a/server/internal/logic/view/view.go b/server/internal/logic/view/view.go new file mode 100644 index 0000000..a0193c3 --- /dev/null +++ b/server/internal/logic/view/view.go @@ -0,0 +1,192 @@ +// Package view +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2022 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// +package view + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gconv" + "hotgo/internal/model" + "hotgo/internal/service" +) + +type sView struct{} + +func init() { + service.RegisterView(New()) +} + +func New() *sView { + return &sView{} +} + +// GetBreadCrumb 前台系统-获取面包屑列表 +func (s *sView) GetBreadCrumb(ctx context.Context, in *model.ViewGetBreadCrumbInput) []model.ViewBreadCrumb { + breadcrumb := []model.ViewBreadCrumb{ + {Name: "首页", Url: "/"}, + } + return breadcrumb +} + +// GetTitle 前台系统-获取标题 +func (s *sView) GetTitle(ctx context.Context, in *model.ViewGetTitleInput) string { + return "title" +} + +// RenderTpl 渲染指定模板页面 +func (s *sView) RenderTpl(ctx context.Context, tpl string, data ...model.View) { + var ( + viewObj = model.View{} + viewData = make(g.Map) + request = g.RequestFromCtx(ctx) + ) + if len(data) > 0 { + viewObj = data[0] + } + if viewObj.Title == "" { + viewObj.Title = g.Cfg().MustGet(ctx, `setting.title`).String() + } else { + viewObj.Title = viewObj.Title + ` - ` + g.Cfg().MustGet(ctx, `setting.title`).String() + } + if viewObj.Keywords == "" { + viewObj.Keywords = g.Cfg().MustGet(ctx, `setting.keywords`).String() + } + if viewObj.Description == "" { + viewObj.Description = g.Cfg().MustGet(ctx, `setting.description`).String() + } + if viewObj.IpcCode == "" { + viewObj.IpcCode = g.Cfg().MustGet(ctx, `setting.icpCode`).String() + } + + if viewObj.GET == nil { + viewObj.GET = request.GetQueryMap() + } + + // 去掉空数据 + viewData = gconv.Map(viewObj) + for k, v := range viewData { + if g.IsEmpty(v) { + delete(viewData, k) + } + } + // 内置对象 + viewData["BuildIn"] = &viewBuildIn{httpRequest: request} + + // 渲染模板 + _ = request.Response.WriteTpl(tpl, viewData) +} + +// Render 渲染默认模板页面 +func (s *sView) Render(ctx context.Context, data ...model.View) { + s.RenderTpl(ctx, g.Cfg().MustGet(ctx, "viewer.homeLayout").String(), data...) +} + +// Render302 跳转中间页面 +func (s *sView) Render302(ctx context.Context, data ...model.View) { + view := model.View{} + if len(data) > 0 { + view = data[0] + } + if view.Title == "" { + view.Title = "页面跳转中" + } + //view.MainTpl = s.getViewFolderName(ctx) + "/pages/302.html" + //s.Render(ctx, view) + s.RenderTpl(ctx, "default/pages/302.html", view) +} + +// Render401 401页面 +func (s *sView) Render401(ctx context.Context, data ...model.View) { + view := model.View{} + if len(data) > 0 { + view = data[0] + } + if view.Title == "" { + view.Title = "无访问权限" + } + s.RenderTpl(ctx, "default/pages/401.html", view) +} + +// Render403 403页面 +func (s *sView) Render403(ctx context.Context, data ...model.View) { + view := model.View{} + if len(data) > 0 { + view = data[0] + } + if view.Title == "" { + view.Title = "无访问权限" + } + s.RenderTpl(ctx, "default/pages/403.html", view) +} + +// Render404 404页面 +func (s *sView) Render404(ctx context.Context, data ...model.View) { + view := model.View{} + if len(data) > 0 { + view = data[0] + } + if view.Title == "" { + view.Title = "资源不存在" + } + s.RenderTpl(ctx, "default/pages/404.html", view) +} + +// Render500 500页面 +func (s *sView) Render500(ctx context.Context, data ...model.View) { + view := model.View{} + if len(data) > 0 { + view = data[0] + } + if view.Title == "" { + view.Title = "请求执行错误" + } + s.RenderTpl(ctx, "default/pages/500.html", view) +} + +func (s *sView) Error(ctx context.Context, err error) { + view := model.View{ + Title: "错误提示", + Error: err.Error(), + } + s.RenderTpl(ctx, "default/pages/500.html", view) +} + +// 获取视图存储目录 +func (s *sView) getViewFolderName(ctx context.Context) string { + return gstr.Split(g.Cfg().MustGet(ctx, "viewer.indexLayout").String(), "/")[0] +} + +// 获取自动设置的MainTpl +func (s *sView) getDefaultMainTpl(ctx context.Context) string { + var ( + viewFolderPrefix = s.getViewFolderName(ctx) + urlPathArray = gstr.SplitAndTrim(g.RequestFromCtx(ctx).URL.Path, "/") + mainTpl string + ) + if len(urlPathArray) > 0 && urlPathArray[0] == viewFolderPrefix { + urlPathArray = urlPathArray[1:] + } + + switch { + case len(urlPathArray) == 2: + // 如果2级路由为数字,那么为模块的详情页面,那么路由固定为/xxx/detail。 + // 如果需要定制化内容模板,请在具体路由方法中设置MainTpl。 + if gstr.IsNumeric(urlPathArray[1]) { + urlPathArray[1] = "detail" + } + mainTpl = viewFolderPrefix + "/" + gfile.Join(urlPathArray[0], urlPathArray[1]) + ".html" + case len(urlPathArray) == 1: + mainTpl = viewFolderPrefix + "/" + urlPathArray[0] + "/index.html" + default: + // 默认首页内容 + mainTpl = viewFolderPrefix + "/index/index.html" + } + + return gstr.TrimLeft(mainTpl, "/") +} diff --git a/server/internal/logic/view/view_buildin.go b/server/internal/logic/view/view_buildin.go new file mode 100644 index 0000000..92686fc --- /dev/null +++ b/server/internal/logic/view/view_buildin.go @@ -0,0 +1,91 @@ +// Package view +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2022 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// +package view + +import ( + "fmt" + "hotgo/internal/consts" + + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gconv" + "github.com/gogf/gf/v2/util/gmode" +) + +// 视图自定义方法管理对象 +type viewBuildIn struct { + httpRequest *ghttp.Request +} + +// Page 创建分页HTML内容 +func (s *viewBuildIn) Page(total, size int) string { + page := s.httpRequest.GetPage(total, size) + page.LinkStyle = "page-link" + page.SpanStyle = "page-link" + page.PrevPageTag = "«" + page.NextPageTag = "»" + content := page.PrevPage() + page.PageBar() + page.NextPage() + content = gstr.ReplaceByMap(content, map[string]string{ + "": "/span>", + "": "/a>", + }) + return content +} + +// UrlPath 获取当前页面的Url Path. +func (s *viewBuildIn) UrlPath() string { + return s.httpRequest.URL.Path +} + +// FormatTime 格式化时间 +func (s *viewBuildIn) FormatTime(gt *gtime.Time) string { + if gt == nil { + return "" + } + n := gtime.Now().Timestamp() + t := gt.Timestamp() + + var ys int64 = 31536000 + var ds int64 = 86400 + var hs int64 = 3600 + var ms int64 = 60 + var ss int64 = 1 + + var rs string + + d := n - t + switch { + case d > ys: + rs = fmt.Sprintf("%d年前", int(d/ys)) + case d > ds: + rs = fmt.Sprintf("%d天前", int(d/ds)) + case d > hs: + rs = fmt.Sprintf("%d小时前", int(d/hs)) + case d > ms: + rs = fmt.Sprintf("%d分钟前", int(d/ms)) + case d > ss: + rs = fmt.Sprintf("%d秒前", int(d/ss)) + default: + rs = "刚刚" + } + + return rs +} + +// Version 随机数 开发环境时间戳,线上为前端版本号 +func (s *viewBuildIn) Version() string { + var rand string + if gmode.IsDevelop() { + rand = gconv.String(gtime.TimestampMilli()) + } else { + rand = consts.VersionApp + } + return rand +} diff --git a/server/internal/model/view.go b/server/internal/model/view.go new file mode 100644 index 0000000..76c52fb --- /dev/null +++ b/server/internal/model/view.go @@ -0,0 +1,42 @@ +// Package model +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2022 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// +package model + +// View 视图渲染内容对象 +type View struct { + Title string // 页面标题 + Keywords string // 页面Keywords + Description string // 页面Description + IpcCode string // ICP备案号 + Error string // 错误信息 + MainTpl string // 自定义MainTpl展示模板文件 + Redirect string // 引导页面跳转 + ContentType string // 内容模型 + BreadCrumb []ViewBreadCrumb // 面包屑 + GET map[string]interface{} // GET参数 + Data interface{} // 页面参数 +} + +// ViewBreadCrumb 视图面包屑结构 +type ViewBreadCrumb struct { + Name string // 显示名称 + Url string // 链接地址,当为空时表示被选中 +} + +// ViewGetBreadCrumbInput 获取面包屑请求 +type ViewGetBreadCrumbInput struct { + ContentId uint // (可选)内容ID + ContentType string // (可选)内容类型 + CategoryId uint // (可选)栏目ID +} + +// ViewGetTitleInput 获取title请求 +type ViewGetTitleInput struct { + ContentType string // (可选)内容类型 + CategoryId uint // (可选)栏目ID + CurrentName string // (可选)当前名称 +} diff --git a/server/internal/router/home.go b/server/internal/router/home.go new file mode 100644 index 0000000..efed567 --- /dev/null +++ b/server/internal/router/home.go @@ -0,0 +1,26 @@ +// Package router +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2022 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// +package router + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "hotgo/internal/controller/home/base" +) + +// Home 前台页面路由 +func Home(ctx context.Context, group *ghttp.RouterGroup) { + routerPrefix, _ := g.Cfg().Get(ctx, "router.home.prefix", "/home") + + group.Group(routerPrefix.String(), func(group *ghttp.RouterGroup) { + group.Bind( + base.Site, // 基础 + ) + + }) +} diff --git a/server/internal/service/admin.go b/server/internal/service/admin.go index 3ace13f..66ec6a4 100644 --- a/server/internal/service/admin.go +++ b/server/internal/service/admin.go @@ -92,13 +92,13 @@ type ( ) var ( + localAdminMemberPost IAdminMemberPost + localAdminMenu IAdminMenu localAdminNotice IAdminNotice localAdminPost IAdminPost localAdminRole IAdminRole localAdminDept IAdminDept localAdminMember IAdminMember - localAdminMemberPost IAdminMemberPost - localAdminMenu IAdminMenu ) func AdminMemberPost() IAdminMemberPost { diff --git a/server/internal/service/sys.go b/server/internal/service/sys.go index d8783f5..b5fbca0 100644 --- a/server/internal/service/sys.go +++ b/server/internal/service/sys.go @@ -16,12 +16,6 @@ import ( ) type ( - ISysDictType interface { - Tree(ctx context.Context) (list []g.Map, err error) - Delete(ctx context.Context, in sysin.DictTypeDeleteInp) error - Edit(ctx context.Context, in sysin.DictTypeEditInp) (err error) - Select(ctx context.Context, in sysin.DictTypeSelectInp) (list sysin.DictTypeSelectModel, err error) - } ISysLog interface { Export(ctx context.Context, in sysin.LogListInp) (err error) RealWrite(ctx context.Context, commonLog entity.SysLog) error @@ -32,6 +26,15 @@ type ( Delete(ctx context.Context, in sysin.LogDeleteInp) error List(ctx context.Context, in sysin.LogListInp) (list []*sysin.LogListModel, totalCount int64, err error) } + ISysAttachment interface { + Delete(ctx context.Context, in sysin.AttachmentDeleteInp) error + Edit(ctx context.Context, in sysin.AttachmentEditInp) (err error) + Status(ctx context.Context, in sysin.AttachmentStatusInp) (err error) + MaxSort(ctx context.Context, in sysin.AttachmentMaxSortInp) (*sysin.AttachmentMaxSortModel, error) + View(ctx context.Context, in sysin.AttachmentViewInp) (res *sysin.AttachmentViewModel, err error) + List(ctx context.Context, in sysin.AttachmentListInp) (list []*sysin.AttachmentListModel, totalCount int64, err error) + Add(ctx context.Context, meta *sysin.UploadFileMeta, fullPath, drive string) (data *entity.SysAttachment, err error) + } ISysBlacklist interface { Delete(ctx context.Context, in sysin.BlacklistDeleteInp) error Edit(ctx context.Context, in sysin.BlacklistEditInp) (err error) @@ -40,12 +43,6 @@ type ( View(ctx context.Context, in sysin.BlacklistViewInp) (res *sysin.BlacklistViewModel, err error) List(ctx context.Context, in sysin.BlacklistListInp) (list []*sysin.BlacklistListModel, totalCount int64, err error) } - ISysConfig interface { - GetSmtp(ctx context.Context) (conf *model.EmailConfig, err error) - GetConfigByGroup(ctx context.Context, in sysin.GetConfigInp) (*sysin.GetConfigModel, error) - ConversionType(ctx context.Context, models *entity.SysConfig) (value interface{}, err error) - UpdateConfigByGroup(ctx context.Context, in sysin.UpdateConfigInp) error - } ISysCron interface { StartCron(ctx context.Context) Delete(ctx context.Context, in sysin.CronDeleteInp) error @@ -55,6 +52,23 @@ type ( View(ctx context.Context, in sysin.CronViewInp) (res *sysin.CronViewModel, err error) List(ctx context.Context, in sysin.CronListInp) (list []*sysin.CronListModel, totalCount int64, err error) } + ISysDictData interface { + Delete(ctx context.Context, in sysin.DictDataDeleteInp) error + Edit(ctx context.Context, in sysin.DictDataEditInp) (err error) + List(ctx context.Context, in sysin.DictDataListInp) (list []*sysin.DictDataListModel, totalCount int64, err error) + } + ISysDictType interface { + Tree(ctx context.Context) (list []g.Map, err error) + Delete(ctx context.Context, in sysin.DictTypeDeleteInp) error + Edit(ctx context.Context, in sysin.DictTypeEditInp) (err error) + Select(ctx context.Context, in sysin.DictTypeSelectInp) (list sysin.DictTypeSelectModel, err error) + } + ISysConfig interface { + GetSmtp(ctx context.Context) (conf *model.EmailConfig, err error) + GetConfigByGroup(ctx context.Context, in sysin.GetConfigInp) (*sysin.GetConfigModel, error) + ConversionType(ctx context.Context, models *entity.SysConfig) (value interface{}, err error) + UpdateConfigByGroup(ctx context.Context, in sysin.UpdateConfigInp) error + } ISysCronGroup interface { Delete(ctx context.Context, in sysin.CronGroupDeleteInp) error Edit(ctx context.Context, in sysin.CronGroupEditInp) (err error) @@ -64,20 +78,6 @@ type ( List(ctx context.Context, in sysin.CronGroupListInp) (list []*sysin.CronGroupListModel, totalCount int64, err error) Select(ctx context.Context, in sysin.CronGroupSelectInp) (list sysin.CronGroupSelectModel, err error) } - ISysDictData interface { - Delete(ctx context.Context, in sysin.DictDataDeleteInp) error - Edit(ctx context.Context, in sysin.DictDataEditInp) (err error) - List(ctx context.Context, in sysin.DictDataListInp) (list []*sysin.DictDataListModel, totalCount int64, err error) - } - ISysAttachment interface { - Delete(ctx context.Context, in sysin.AttachmentDeleteInp) error - Edit(ctx context.Context, in sysin.AttachmentEditInp) (err error) - Status(ctx context.Context, in sysin.AttachmentStatusInp) (err error) - MaxSort(ctx context.Context, in sysin.AttachmentMaxSortInp) (*sysin.AttachmentMaxSortModel, error) - View(ctx context.Context, in sysin.AttachmentViewInp) (res *sysin.AttachmentViewModel, err error) - List(ctx context.Context, in sysin.AttachmentListInp) (list []*sysin.AttachmentListModel, totalCount int64, err error) - Add(ctx context.Context, meta *sysin.UploadFileMeta, fullPath, drive string) (data *entity.SysAttachment, err error) - } ISysProvinces interface { Delete(ctx context.Context, in sysin.ProvincesDeleteInp) error Edit(ctx context.Context, in sysin.ProvincesEditInp) (err error) @@ -89,50 +89,17 @@ type ( ) var ( - localSysConfig ISysConfig + localSysBlacklist ISysBlacklist localSysCron ISysCron - localSysCronGroup ISysCronGroup localSysDictData ISysDictData localSysDictType ISysDictType localSysLog ISysLog - localSysBlacklist ISysBlacklist - localSysProvinces ISysProvinces localSysAttachment ISysAttachment + localSysCronGroup ISysCronGroup + localSysProvinces ISysProvinces + localSysConfig ISysConfig ) -func SysAttachment() ISysAttachment { - if localSysAttachment == nil { - panic("implement not found for interface ISysAttachment, forgot register?") - } - return localSysAttachment -} - -func RegisterSysAttachment(i ISysAttachment) { - localSysAttachment = i -} - -func SysProvinces() ISysProvinces { - if localSysProvinces == nil { - panic("implement not found for interface ISysProvinces, forgot register?") - } - return localSysProvinces -} - -func RegisterSysProvinces(i ISysProvinces) { - localSysProvinces = i -} - -func SysBlacklist() ISysBlacklist { - if localSysBlacklist == nil { - panic("implement not found for interface ISysBlacklist, forgot register?") - } - return localSysBlacklist -} - -func RegisterSysBlacklist(i ISysBlacklist) { - localSysBlacklist = i -} - func SysConfig() ISysConfig { if localSysConfig == nil { panic("implement not found for interface ISysConfig, forgot register?") @@ -144,17 +111,6 @@ func RegisterSysConfig(i ISysConfig) { localSysConfig = i } -func SysCron() ISysCron { - if localSysCron == nil { - panic("implement not found for interface ISysCron, forgot register?") - } - return localSysCron -} - -func RegisterSysCron(i ISysCron) { - localSysCron = i -} - func SysCronGroup() ISysCronGroup { if localSysCronGroup == nil { panic("implement not found for interface ISysCronGroup, forgot register?") @@ -166,6 +122,17 @@ func RegisterSysCronGroup(i ISysCronGroup) { localSysCronGroup = i } +func SysProvinces() ISysProvinces { + if localSysProvinces == nil { + panic("implement not found for interface ISysProvinces, forgot register?") + } + return localSysProvinces +} + +func RegisterSysProvinces(i ISysProvinces) { + localSysProvinces = i +} + func SysDictData() ISysDictData { if localSysDictData == nil { panic("implement not found for interface ISysDictData, forgot register?") @@ -198,3 +165,36 @@ func SysLog() ISysLog { func RegisterSysLog(i ISysLog) { localSysLog = i } + +func SysAttachment() ISysAttachment { + if localSysAttachment == nil { + panic("implement not found for interface ISysAttachment, forgot register?") + } + return localSysAttachment +} + +func RegisterSysAttachment(i ISysAttachment) { + localSysAttachment = i +} + +func SysBlacklist() ISysBlacklist { + if localSysBlacklist == nil { + panic("implement not found for interface ISysBlacklist, forgot register?") + } + return localSysBlacklist +} + +func RegisterSysBlacklist(i ISysBlacklist) { + localSysBlacklist = i +} + +func SysCron() ISysCron { + if localSysCron == nil { + panic("implement not found for interface ISysCron, forgot register?") + } + return localSysCron +} + +func RegisterSysCron(i ISysCron) { + localSysCron = i +} diff --git a/server/internal/service/view.go b/server/internal/service/view.go new file mode 100644 index 0000000..a757343 --- /dev/null +++ b/server/internal/service/view.go @@ -0,0 +1,41 @@ +// ================================================================================ +// Code generated by GoFrame CLI tool. DO NOT EDIT. +// You can delete these comments if you wish manually maintain this interface file. +// ================================================================================ + +package service + +import ( + "context" + "hotgo/internal/model" +) + +type ( + IView interface { + GetBreadCrumb(ctx context.Context, in *model.ViewGetBreadCrumbInput) []model.ViewBreadCrumb + GetTitle(ctx context.Context, in *model.ViewGetTitleInput) string + RenderTpl(ctx context.Context, tpl string, data ...model.View) + Render(ctx context.Context, data ...model.View) + Render302(ctx context.Context, data ...model.View) + Render401(ctx context.Context, data ...model.View) + Render403(ctx context.Context, data ...model.View) + Render404(ctx context.Context, data ...model.View) + Render500(ctx context.Context, data ...model.View) + Error(ctx context.Context, err error) + } +) + +var ( + localView IView +) + +func View() IView { + if localView == nil { + panic("implement not found for interface IView, forgot register?") + } + return localView +} + +func RegisterView(i IView) { + localView = i +} diff --git a/server/internal/websocket/client.go b/server/internal/websocket/client.go index 6465e57..f4e2f25 100644 --- a/server/internal/websocket/client.go +++ b/server/internal/websocket/client.go @@ -46,6 +46,7 @@ type Client struct { Socket *websocket.Conn // 用户连接 Send chan *WResponse // 待发送的数据 SendClose bool // 发送是否关闭 + closeSignal chan struct{} // 关闭信号 FirstTime uint64 // 首次连接时间 HeartbeatTime uint64 // 用户上次心跳时间 Tags garray.StrArray // 标签 @@ -63,6 +64,7 @@ func NewClient(r *ghttp.Request, socket *websocket.Conn, firstTime uint64) (clie Socket: socket, Send: make(chan *WResponse, 100), SendClose: false, + closeSignal: make(chan struct{}, 1), FirstTime: firstTime, HeartbeatTime: firstTime, User: contexts.Get(r.Context()).User, @@ -107,6 +109,9 @@ func (c *Client) write() { }() for { select { + case <-c.closeSignal: + g.Log().Infof(ctxManager, "websocket client quit, user:%+v", c.User) + return case message, ok := <-c.Send: if !ok { // 发送数据错误 关闭连接 @@ -159,12 +164,13 @@ func (c *Client) close() { return } c.SendClose = true - if _, ok := <-c.Send; !ok { - g.Log().Warningf(ctxManager, "close of closed channel, client.id:%v", c.ID) - } else { - // 关闭 chan - close(c.Send) - } + //if _, ok := <-c.Send; !ok { + // g.Log().Warningf(ctxManager, "close of closed channel, client.id:%v", c.ID) + //} else { + // // 关闭 chan + // close(c.Send) + //} + c.closeSignal <- struct{}{} } // Close 关闭指定客户端连接 diff --git a/server/manifest/config/config.example.yaml b/server/manifest/config/config.example.yaml index 3f6b8c0..58402f2 100644 --- a/server/manifest/config/config.example.yaml +++ b/server/manifest/config/config.example.yaml @@ -50,6 +50,18 @@ server: pprofEnabled: true # 是否开启PProf性能调试特性。默认为false pprofPattern: "/pprof" # 开启PProf时有效,表示PProf特性的页面访问路径,对当前Server绑定的所有域名有效。 +viewer: + paths: "resource/template" + defaultFile: "index.html" + delimiters: ["@{", "}"] + homeLayout: "home/index.html" + +# 内容设置 +setting: + title: "HotGo" + keywords: "中后台解决方案,gf框架,vue3" + description: "hotgo 是一个基于 goframe2,vue3,vite2,TypeScript,uinapp 的中后台解决方案,它可以帮助你快速搭建企业级中后台项目,相信不管是从新技术使用还是其他方面,都能帮助到你,持续更新中。" + # 路由配置 router: @@ -82,6 +94,12 @@ router: # 不需要验证登录的路由地址 exceptLogin: [ ] + # 前台页面 + home: + # 前缀 + prefix: "/home" + # 不需要验证登录的路由地址 + exceptPath: [ ] #JWT jwt: diff --git a/server/resource/public/resource/home/js/jquery-3.6.0.min.js b/server/resource/public/resource/home/js/jquery-3.6.0.min.js new file mode 100644 index 0000000..c4c6022 --- /dev/null +++ b/server/resource/public/resource/home/js/jquery-3.6.0.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0Hello,@{.Data.name}!!
+当前版本:@{.Data.version}
+