This commit is contained in:
孟帅
2023-02-23 17:53:04 +08:00
parent 7cf1b8ce8e
commit 61d0988d2c
402 changed files with 18340 additions and 35547 deletions

View File

@@ -0,0 +1,38 @@
// Package addons
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package addons
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"hotgo/internal/consts"
)
func GetTag(name string) string {
return consts.AddonsTag + name
}
func Tpl(name, tpl string) string {
return consts.AddonsDir + "/" + name + "/" + tpl
}
// RouterPrefix 路由前缀
// 最终效果:/应用名称/插件模块名称/xxx/xxx。如果你不喜欢现在的路由风格可以自行调整
func RouterPrefix(ctx context.Context, app, name string) string {
var prefix = "/"
switch app {
case consts.AppAdmin:
prefix = g.Cfg().MustGet(ctx, "router.admin.prefix", "/admin").String()
case consts.AppApi:
prefix = g.Cfg().MustGet(ctx, "router.api.prefix", "/api").String()
case consts.AppHome:
prefix = g.Cfg().MustGet(ctx, "router.home.prefix", "/home").String()
case consts.AppWebSocket:
prefix = g.Cfg().MustGet(ctx, "router.ws.prefix", "/socket").String()
}
return prefix + "/" + name
}

View File

@@ -0,0 +1,126 @@
package addons
import (
"context"
"fmt"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gstr"
"hotgo/internal/consts"
"hotgo/internal/model"
"strconv"
"strings"
)
// Build 构建新插件
func Build(ctx context.Context, sk Skeleton, conf *model.BuildAddonConfig) (err error) {
buildPath := "./" + consts.AddonsDir + "/" + sk.Name
modulesPath := "./" + consts.AddonsDir + "/modules/" + sk.Name + ".go"
templatePath := gstr.Replace(conf.TemplatePath, "{$name}", sk.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,
}
if err = checkBuildDir(buildPath, modulesPath, templatePath); err != nil {
return
}
// scans directory recursively
list, err := gfile.ScanDirFunc(conf.SrcPath, "*", true, func(path string) string {
return path
})
for _, path := range list {
if !gfile.IsReadable(path) {
err = fmt.Errorf("file%v is unreadable, please check permissions", path)
return
}
if gfile.IsDir(path) {
continue
}
flowFile := gstr.ReplaceByMap(path, map[string]string{
gfile.RealPath(conf.SrcPath): "",
".template": "",
})
flowFile = buildPath + "/" + flowFile
content := gstr.ReplaceByMap(gfile.GetContents(path), replaces)
if err = gfile.PutContents(flowFile, content); err != nil {
break
}
}
if err = gfile.PutContents(templatePath+"/home/index.html", homeLayout); err != nil {
return
}
err = gfile.PutContents(modulesPath, gstr.ReplaceByMap(importModules, replaces))
return
}
func checkBuildDir(paths ...string) error {
if len(paths) == 0 {
return nil
}
for _, path := range paths {
if gfile.Exists(path) {
return fmt.Errorf("插件已存在,请换一个插件名称或者经确认无误后依次删除文件夹: [%v] 后重新生成", strings.Join(paths, "、\t"))
}
}
return nil
}
const (
importModules = `// Package modules
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package modules
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>`
)

View File

@@ -0,0 +1,142 @@
// Package addons
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package addons
import (
"context"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"hotgo/internal/consts"
)
// InstallRecord 安装记录
type InstallRecord struct {
Id int64 `json:"id" description:"安装ID"`
Version string `json:"version" description:"安装版本"`
Status int `json:"status" description:"安装状态"`
CreatedAt *gtime.Time `json:"createdAt" description:"创建时间"`
UpdatedAt *gtime.Time `json:"updatedAt" description:"更新时间"`
}
func ScanInstall(m Module) (record *InstallRecord, err error) {
err = g.Model("sys_addons_install").
Ctx(m.Ctx()).
Where("name", m.GetSkeleton().Name).
Scan(&record)
return
}
// IsInstall 模块是否已安装
func IsInstall(m Module) bool {
record, err := ScanInstall(m)
if err != nil {
g.Log().Debugf(m.Ctx(), err.Error())
return false
}
if record == nil {
return false
}
return record.Status == consts.AddonsInstallStatusOk
}
// Install 安装模块
func Install(m Module) (err error) {
record, err := ScanInstall(m)
if err != nil {
return
}
if record != nil && record.Status == consts.AddonsInstallStatusOk {
return gerror.New("插件已安装,无需重复操作!")
}
data := g.Map{
"name": m.GetSkeleton().Name,
"version": m.GetSkeleton().Version,
"status": consts.AddonsInstallStatusOk,
}
return g.DB().Transaction(m.Ctx(), func(ctx context.Context, tx gdb.TX) error {
if record != nil {
_, err = g.Model("sys_addons_install").
Ctx(m.Ctx()).
Where("id", record.Id).
Delete()
}
_, err = g.Model("sys_addons_install").
Ctx(m.Ctx()).
Data(data).
Insert()
if err != nil {
return err
}
return m.Install(ctx)
})
}
// Upgrade 更新模块
func Upgrade(m Module) (err error) {
record, err := ScanInstall(m)
if err != nil {
return
}
if record == nil || record.Status != consts.AddonsInstallStatusOk {
return gerror.New("插件未安装,请安装后操作!")
}
data := g.Map{
"version": m.GetSkeleton().Version,
}
return g.DB().Transaction(m.Ctx(), func(ctx context.Context, tx gdb.TX) error {
_, err = g.Model("sys_addons_install").
Ctx(m.Ctx()).
Where("id", record.Id).
Data(data).
Update()
if err != nil {
return err
}
return m.Upgrade(ctx)
})
}
// UnInstall 卸载模块
func UnInstall(m Module) (err error) {
record, err := ScanInstall(m)
if err != nil {
return
}
if record == nil || record.Status != consts.AddonsInstallStatusOk {
return gerror.New("插件未安装,请安装后操作!")
}
data := g.Map{
"version": m.GetSkeleton().Version,
"status": consts.AddonsInstallStatusUn,
}
return g.DB().Transaction(m.Ctx(), func(ctx context.Context, tx gdb.TX) error {
_, err = g.Model("sys_addons_install").
Ctx(m.Ctx()).
Where("id", record.Id).
Data(data).
Update()
if err != nil {
return err
}
return m.UnInstall(ctx)
})
}

View File

@@ -0,0 +1,145 @@
// Package addons
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package addons
import (
"context"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gfile"
"hotgo/internal/consts"
"hotgo/internal/model/input/form"
"sort"
"sync"
)
// Skeleton 模块骨架
type Skeleton struct {
Label string `json:"label"` // 标识
Name string `json:"name"` // 名称
Group int `json:"group"` // 分组
Logo string `json:"logo"` // logo
Brief string `json:"brief"` // 简介
Description string `json:"description"` // 详细描述
Author string `json:"author"` // 作者
Version string `json:"version"` // 版本号
RootPath string `json:"rootPath"` // 根路径
}
func (s *Skeleton) GetModule() Module {
return GetModule(s.Name)
}
// 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 // 卸载模块
}
var (
modules = make(map[string]Module, 0)
mLock sync.Mutex
)
// InitModules 初始化所有已注册模块
func InitModules(ctx context.Context) {
for _, module := range modules {
module.Init(ctx)
}
}
// RegisterModulesRouter 注册所有已安装模块路由
func RegisterModulesRouter(ctx context.Context, group *ghttp.RouterGroup) {
for _, module := range filterInstalled() {
module.InitRouter(ctx, group)
}
}
// RegisterModule 注册模块
func RegisterModule(m Module) Module {
mLock.Lock()
defer mLock.Unlock()
_, ok := modules[m.GetSkeleton().Name]
if ok {
panic("module repeat registration, name:" + m.GetSkeleton().Name)
}
modules[m.GetSkeleton().Name] = m
return m
}
// GetModule 获取指定名称模块
func GetModule(name string) Module {
mLock.Lock()
defer mLock.Unlock()
m, ok := modules[name]
if !ok {
panic("implement not found for interface " + name + ", forgot register?")
}
return m
}
// GetSkeletons 获取所有模块骨架
func GetSkeletons() (list []*Skeleton) {
var keys []string
for k, _ := range modules {
keys = append(keys, k)
}
sort.Strings(keys)
for _, v := range keys {
list = append(list, GetModule(v).GetSkeleton())
}
return list
}
// GetModuleRealPath 获取指定模块绝对路径
func GetModuleRealPath(name string) string {
path := gfile.RealPath(GetModulePath(name))
if path == "" {
panic("no path is found. please confirm that the path " + GetModulePath(name) + " exists?")
}
return path
}
// GetModulePath 获取指定模块相对路径
func GetModulePath(name string) string {
return "./" + consts.AddonsDir + "/" + name
}
// filterInstalled 过滤已安装模块
func filterInstalled() []Module {
var ms []Module
for _, module := range modules {
if IsInstall(module) {
ms = append(ms, module)
}
}
return ms
}
// ModuleSelect 获取插件模块选项
func ModuleSelect() form.Selects {
sks := GetSkeletons()
lst := make(form.Selects, 0)
if len(sks) == 0 {
return lst
}
for _, skeleton := range sks {
lst = append(lst, &form.Select{
Value: skeleton.Name,
Label: skeleton.Label,
Name: skeleton.Label,
})
}
return lst
}

View File

@@ -1,24 +1,70 @@
// Package cache
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package cache
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/gfile"
"hotgo/internal/library/cache/file"
"hotgo/internal/model"
"hotgo/internal/service"
)
func New() *gcache.Cache {
c := gcache.New()
// cache 缓存驱动
var cache *gcache.Cache
//redis
adapter := gcache.NewAdapterRedis(g.Redis())
//内存
//adapter := gcache.NewAdapterMemory()
c.SetAdapter(adapter)
return c
// Instance 缓存实例
func Instance() *gcache.Cache {
if cache == nil {
panic("cache uninitialized.")
}
return cache
}
// SetAdapter 设置缓存适配器
func SetAdapter(ctx context.Context) {
var adapter gcache.Adapter
conf, err := service.SysConfig().GetLoadCache(ctx)
if err != nil {
g.Log().Fatalf(ctx, "cache init err:%+v", err)
return
}
if conf == nil {
conf = new(model.CacheConfig)
g.Log().Infof(ctx, "no cache driver is configured. default memory cache is used.")
}
switch conf.Adapter {
case "redis":
adapter = gcache.NewAdapterRedis(g.Redis())
case "file":
if conf.FileDir == "" {
g.Log().Fatalf(ctx, "file path must be configured for file caching.")
return
}
if !gfile.Exists(conf.FileDir) {
if err := gfile.Mkdir(conf.FileDir); err != nil {
g.Log().Fatalf(ctx, "Failed to create the cache directory. Procedure, err:%+v", err)
return
}
}
adapter = file.NewAdapterFile(conf.FileDir)
default:
adapter = gcache.NewAdapterMemory()
}
// 数据库缓存,默认和通用缓冲驱动一致,如果你不想使用默认的,可以自行调整
g.DB().GetCache().SetAdapter(adapter)
// 通用缓存
cache = gcache.New()
cache.SetAdapter(adapter)
return
}

View File

@@ -0,0 +1,290 @@
package file
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/util/gconv"
"io/ioutil"
"os"
"path/filepath"
"time"
)
type (
// AdapterFile is the gcache adapter implements using file server.
AdapterFile struct {
dir string
}
fileContent struct {
Duration int64 `json:"duration"`
Data interface{} `json:"data,omitempty"`
}
)
const perm = 0o666
// NewAdapterFile creates and returns a new memory cache object.
func NewAdapterFile(dir string) gcache.Adapter {
return &AdapterFile{
dir: dir,
}
}
func (c *AdapterFile) Set(ctx context.Context, key interface{}, value interface{}, lifeTime time.Duration) (err error) {
fileKey := gconv.String(key)
if value == nil || lifeTime < 0 {
return c.Delete(fileKey)
}
return c.Save(fileKey, gconv.String(value), lifeTime)
}
func (c *AdapterFile) SetMap(ctx context.Context, data map[interface{}]interface{}, duration time.Duration) (err error) {
return gerror.New("implement me")
}
func (c *AdapterFile) SetIfNotExist(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (ok bool, err error) {
return false, gerror.New("implement me")
}
func (c *AdapterFile) SetIfNotExistFunc(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (ok bool, err error) {
return false, gerror.New("implement me")
}
func (c *AdapterFile) SetIfNotExistFuncLock(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (ok bool, err error) {
return false, gerror.New("implement me")
}
func (c *AdapterFile) Get(ctx context.Context, key interface{}) (*gvar.Var, error) {
fetch, err := c.Fetch(gconv.String(key))
if err != nil {
return nil, err
}
return gvar.New(fetch), nil
}
func (c *AdapterFile) GetOrSet(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (result *gvar.Var, err error) {
return nil, gerror.New("implement me")
}
func (c *AdapterFile) GetOrSetFunc(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (result *gvar.Var, err error) {
return nil, gerror.New("implement me")
}
func (c *AdapterFile) GetOrSetFuncLock(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (result *gvar.Var, err error) {
return nil, gerror.New("implement me")
}
func (c *AdapterFile) Contains(ctx context.Context, key interface{}) (bool, error) {
return c.Has(gconv.String(key)), nil
}
func (c *AdapterFile) Size(ctx context.Context) (size int, err error) {
return 0, nil
}
func (c *AdapterFile) Data(ctx context.Context) (data map[interface{}]interface{}, err error) {
return nil, gerror.New("implement me")
}
func (c *AdapterFile) Keys(ctx context.Context) (keys []interface{}, err error) {
return nil, gerror.New("implement me")
}
func (c *AdapterFile) Values(ctx context.Context) (values []interface{}, err error) {
return nil, gerror.New("implement me")
}
func (c *AdapterFile) Update(ctx context.Context, key interface{}, value interface{}) (oldValue *gvar.Var, exist bool, err error) {
return nil, false, gerror.New("implement me")
}
func (c *AdapterFile) UpdateExpire(ctx context.Context, key interface{}, duration time.Duration) (oldDuration time.Duration, err error) {
var (
v *gvar.Var
oldTTL int64
fileKey = gconv.String(key)
)
// TTL.
expire, err := c.GetExpire(ctx, fileKey)
if err != nil {
return
}
oldTTL = int64(expire)
if oldTTL == -2 {
// It does not exist.
oldTTL = -1
return
}
oldDuration = time.Duration(oldTTL) * time.Second
// DEL.
if duration < 0 {
err = c.Delete(fileKey)
return
}
v, err = c.Get(ctx, fileKey)
if err != nil {
return
}
err = c.Set(ctx, fileKey, v.Val(), duration)
return
}
func (c *AdapterFile) GetExpire(ctx context.Context, key interface{}) (time.Duration, error) {
content, err := c.read(gconv.String(key))
if err != nil {
return -1, nil
}
if content.Duration <= time.Now().Unix() {
return -1, nil
}
return time.Duration(time.Now().Unix()-content.Duration) * time.Second, nil
}
func (c *AdapterFile) Remove(ctx context.Context, keys ...interface{}) (lastValue *gvar.Var, err error) {
if len(keys) == 0 {
return nil, nil
}
// Retrieves the last key value.
if lastValue, err = c.Get(ctx, gconv.String(keys[len(keys)-1])); err != nil {
return nil, err
}
// Deletes all given keys.
err = c.DeleteMulti(gconv.Strings(keys)...)
return
}
func (c *AdapterFile) Clear(ctx context.Context) error {
return c.Flush()
}
func (c *AdapterFile) Close(ctx context.Context) error {
return nil
}
func (c *AdapterFile) createName(key string) string {
h := sha256.New()
_, _ = h.Write([]byte(key))
hash := hex.EncodeToString(h.Sum(nil))
return filepath.Join(c.dir, fmt.Sprintf("%s.cache", hash))
}
func (c *AdapterFile) read(key string) (*fileContent, error) {
value, err := ioutil.ReadFile(c.createName(key))
if err != nil {
return nil, err
}
content := &fileContent{}
if err := json.Unmarshal(value, content); err != nil {
return nil, err
}
if content.Duration == 0 {
return content, nil
}
if content.Duration <= time.Now().Unix() {
_ = c.Delete(key)
return nil, errors.New("cache expired")
}
return content, nil
}
// Has checks if the cached key exists into the File storage
func (c *AdapterFile) Has(key string) bool {
_, err := c.read(key)
return err == nil
}
// Delete the cached key from File storage
func (c *AdapterFile) Delete(key string) error {
_, err := os.Stat(c.createName(key))
if err != nil && os.IsNotExist(err) {
return nil
}
return os.Remove(c.createName(key))
}
// DeleteMulti the cached key from File storage
func (c *AdapterFile) DeleteMulti(keys ...string) (err error) {
for _, key := range keys {
if err = c.Delete(key); err != nil {
return
}
}
return
}
// Fetch retrieves the cached value from key of the File storage
func (c *AdapterFile) Fetch(key string) (interface{}, error) {
content, err := c.read(key)
if err != nil {
return "", err
}
return content.Data, nil
}
// FetchMulti retrieve multiple cached values from keys of the File storage
func (c *AdapterFile) FetchMulti(keys []string) map[string]interface{} {
result := make(map[string]interface{})
for _, key := range keys {
if value, err := c.Fetch(key); err == nil {
result[key] = value
}
}
return result
}
// Flush removes all cached keys of the File storage
func (c *AdapterFile) Flush() error {
dir, err := os.Open(c.dir)
if err != nil {
return err
}
defer func() {
_ = dir.Close()
}()
names, _ := dir.Readdirnames(-1)
for _, name := range names {
_ = os.Remove(filepath.Join(c.dir, name))
}
return nil
}
// Save a value in File storage by key
func (c *AdapterFile) Save(key string, value string, lifeTime time.Duration) error {
duration := int64(0)
if lifeTime > 0 {
duration = time.Now().Unix() + int64(lifeTime.Seconds())
}
content := &fileContent{duration, value}
data, err := json.Marshal(content)
if err != nil {
return err
}
return ioutil.WriteFile(c.createName(key), data, perm)
}

View File

@@ -1,6 +1,6 @@
// Package captcha
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//

View File

@@ -1,6 +1,6 @@
// Package casbin
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//

View File

@@ -1,6 +1,6 @@
// Package casbin
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//

View File

@@ -1,6 +1,6 @@
// Package casbin
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//

View File

@@ -1,13 +1,13 @@
// Package contexts
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package contexts
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"hotgo/internal/consts"
"hotgo/internal/model"
@@ -32,22 +32,42 @@ func Get(ctx context.Context) *model.Context {
// SetUser 将上下文信息设置到上下文请求中,注意是完整覆盖
func SetUser(ctx context.Context, user *model.Identity) {
Get(ctx).User = user
c := Get(ctx)
if c == nil {
g.Log().Warningf(ctx, "contexts.SetUser, c == nil ")
return
}
c.User = user
}
// SetResponse 设置组件响应 用于访问日志使用
func SetResponse(ctx context.Context, response *model.Response) {
Get(ctx).Response = response
c := Get(ctx)
if c == nil {
g.Log().Warningf(ctx, "contexts.SetResponse, c == nil ")
return
}
c.Response = response
}
// SetModule 设置应用模块
func SetModule(ctx context.Context, module string) {
Get(ctx).Module = module
c := Get(ctx)
if c == nil {
g.Log().Warningf(ctx, "contexts.SetModule, c == nil ")
return
}
c.Module = module
}
// SetTakeUpTime 设置请求耗时
func SetTakeUpTime(ctx context.Context, takeUpTime int64) {
Get(ctx).TakeUpTime = takeUpTime
c := Get(ctx)
if c == nil {
g.Log().Warningf(ctx, "contexts.SetTakeUpTime, c == nil ")
return
}
c.TakeUpTime = takeUpTime
}
// GetUser 获取用户信息
@@ -62,30 +82,55 @@ func GetUser(ctx context.Context) *model.Identity {
// GetUserId 获取用户ID
func GetUserId(ctx context.Context) int64 {
user := Get(ctx).User
user := GetUser(ctx)
if user == nil {
return 0
}
return user.Id
}
// GetRoleId 获取用户角色ID
func GetRoleId(ctx context.Context) int64 {
user := Get(ctx).User
user := GetUser(ctx)
if user == nil {
return 0
}
return user.RoleId
}
// GetRoleKey 获取用户角色唯一编码
func GetRoleKey(ctx context.Context) string {
user := Get(ctx).User
user := GetUser(ctx)
if user == nil {
return ""
}
return user.RoleKey
}
// SetAddonName 设置插件信息
func SetAddonName(ctx context.Context, name string) {
c := Get(ctx)
if c == nil {
g.Log().Warningf(ctx, "contexts.SetAddonName, c == nil ")
return
}
Get(ctx).AddonName = name
}
// IsAddonRequest 是否为插件模块请求
func IsAddonRequest(ctx context.Context) bool {
c := Get(ctx)
if c == nil {
return false
}
return GetAddonName(ctx) != ""
}
// GetAddonName 获取插件信息
func GetAddonName(ctx context.Context) string {
c := Get(ctx)
if c == nil {
return ""
}
return Get(ctx).AddonName
}

View File

@@ -1,6 +1,6 @@
// Package ems
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//

View File

@@ -1,9 +1,8 @@
// Package hggen
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package hggen
import (
@@ -12,8 +11,10 @@ import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"hotgo/internal/consts"
"hotgo/internal/library/addons"
"hotgo/internal/library/hggen/internal/cmd"
"hotgo/internal/library/hggen/internal/cmd/gendao"
"hotgo/internal/library/hggen/internal/cmd/genservice"
"hotgo/internal/library/hggen/views"
"hotgo/internal/model"
"hotgo/internal/model/input/form"
@@ -38,7 +39,16 @@ func Dao(ctx context.Context) (err error) {
// Service 生成业务接口
func Service(ctx context.Context) (err error) {
_, err = cmd.Gen.Service(ctx, GetServiceConfig())
return ServiceWithCfg(ctx, GetServiceConfig())
}
// ServiceWithCfg 生成业务接口
func ServiceWithCfg(ctx context.Context, cfg ...genservice.CGenServiceInput) (err error) {
c := GetServiceConfig()
if len(cfg) > 0 {
c = cfg[0]
}
_, err = cmd.Gen.Service(ctx, c)
return
}
@@ -115,6 +125,8 @@ func TableSelects(ctx context.Context, in sysin.GenCodesSelectsInp) (res *sysin.
})
}
res.Addons = addons.ModuleSelect()
return
}
@@ -125,7 +137,7 @@ func GenTypeSelect(ctx context.Context) (res sysin.GenTypeSelects, err error) {
Value: k,
Name: v,
Label: v,
Templates: make(form.Selects, 0),
Templates: make(sysin.GenTemplateSelects, 0),
}
confName, ok := consts.GenCodesTypeConfMap[k]
@@ -137,10 +149,11 @@ func GenTypeSelect(ctx context.Context) (res sysin.GenTypeSelects, err error) {
}
if len(temps) > 0 {
for index, temp := range temps {
row.Templates = append(row.Templates, &form.Select{
Value: index,
Label: temp.Group,
Name: temp.Group,
row.Templates = append(row.Templates, &sysin.GenTemplateSelect{
Value: index,
Label: temp.Group,
Name: temp.Group,
IsAddon: temp.IsAddon,
})
}
sort.Sort(row.Templates)
@@ -207,14 +220,31 @@ func Build(ctx context.Context, in sysin.GenCodesBuildInp) (err error) {
switch in.GenType {
case consts.GenCodesTypeCurd:
pin := sysin.GenCodesPreviewInp(in)
return views.Curd.DoBuild(ctx, &views.CurdBuildInput{
PreviewIn: &views.CurdPreviewInput{
In: sysin.GenCodesPreviewInp(in),
In: pin,
DaoConfig: GetDaoConfig(in.DbName),
Config: genConfig,
},
BeforeEvent: views.CurdBuildEvent{"runDao": Dao},
AfterEvent: views.CurdBuildEvent{"runService": Service},
AfterEvent: views.CurdBuildEvent{"runService": func(ctx context.Context) (err error) {
cfg := GetServiceConfig()
if err = ServiceWithCfg(ctx, cfg); err != nil {
return
}
// 插件模块同时运行模块下的gen service
if genConfig.Application.Crud.Templates[pin.GenTemplate].IsAddon {
// 依然使用配置中的参数,只是将生成路径指向插件模块路径
cfg.SrcFolder = "addons/" + pin.AddonName + "/logic"
cfg.DstFolder = "addons/" + pin.AddonName + "/service"
if err = ServiceWithCfg(ctx, cfg); err != nil {
return
}
}
return
}},
})
case consts.GenCodesTypeTree:
err = gerror.Newf("生成类型开发中!")

View File

@@ -1,6 +1,6 @@
// Package hggen
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//

View File

@@ -1,6 +1,6 @@
// Package views
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//

View File

@@ -1,6 +1,6 @@
// Package views
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//

View File

@@ -1,6 +1,6 @@
// Package views
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//

View File

@@ -1,9 +1,8 @@
// Package views
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package views
import (
@@ -70,6 +69,7 @@ type CurdOptions struct {
Step *CurdStep // 转换后的流程控制条件
dictMap g.Map // 字典选项 -> 字段映射关系
TemplateGroup string `json:"templateGroup"`
ApiPrefix string `json:"apiPrefix"`
}
type CurdPreviewInput struct {
@@ -123,7 +123,14 @@ func (l *gCurd) initInput(ctx context.Context, in *CurdPreviewInput) (err error)
return gerror.New("没有找到生成模板的配置,请检查!")
}
err = checkCurdPath(in.Config.Application.Crud.Templates[in.In.GenTemplate])
// api前缀
apiPrefix := gstr.LcFirst(in.In.VarName)
if in.Config.Application.Crud.Templates[in.In.GenTemplate].IsAddon {
apiPrefix = in.In.AddonName + "/" + apiPrefix
}
in.options.ApiPrefix = apiPrefix
err = checkCurdPath(in.Config.Application.Crud.Templates[in.In.GenTemplate], in.In.AddonName)
if err != nil {
return
}
@@ -147,9 +154,10 @@ func initStep(ctx context.Context, in *CurdPreviewInput) {
}
func (l *gCurd) loadView(ctx context.Context, in *CurdPreviewInput) (err error) {
temp := in.Config.Application.Crud.Templates[in.In.GenTemplate]
view := gview.New()
err = view.SetConfigWithMap(g.Map{
"Paths": in.Config.Application.Crud.Templates[in.In.GenTemplate].TemplatePath,
"Paths": temp.TemplatePath,
"Delimiters": in.Config.Delimiters,
})
if err != nil {
@@ -168,19 +176,48 @@ func (l *gCurd) loadView(ctx context.Context, in *CurdPreviewInput) (err error)
return
}
modName, err := GetModName(ctx)
if err != nil {
return
}
importApi := gstr.Replace(temp.ApiPath, "./", modName+"/") + "/" + strings.ToLower(in.In.VarName)
importInput := gstr.Replace(temp.InputPath, "./", modName+"/")
importController := gstr.Replace(temp.ControllerPath, "./", modName+"/")
importService := "hotgo/internal/service"
if temp.IsAddon {
importService = "hotgo/addons/" + in.In.AddonName + "/service"
}
importWebApi := "@/api/" + gstr.LcFirst(in.In.VarName)
if temp.IsAddon {
importWebApi = "@/api/addons/" + in.In.AddonName + "/" + gstr.LcFirst(in.In.VarName)
}
componentPrefix := gstr.LcFirst(in.In.VarName)
if temp.IsAddon {
componentPrefix = "addons/" + in.In.AddonName + "/" + componentPrefix
}
view.Assigns(gview.Params{
"templateGroup": in.options.TemplateGroup, // 生成模板分组名称
"servFunName": l.parseServFunName(in.options.TemplateGroup, in.In.VarName), // 业务服务名称
"nowTime": gtime.Now().Format("Y-m-d H:i:s"), // 当前时间
"version": runtime.Version(), // GO 版本
"hgVersion": consts.VersionApp, // HG 版本
"varName": in.In.VarName, // 实体名称
"tableComment": in.In.TableComment, // 对外名称
"daoName": in.In.DaoName, // ORM模型
"masterFields": in.masterFields, // 主表字段
"pk": in.pk, // 主键属性
"options": in.options, // 提交选项
"dictOptions": dictOptions, // web字典选项
"templateGroup": in.options.TemplateGroup, // 生成模板分组名称
"servFunName": l.parseServFunName(in.options.TemplateGroup, in.In.VarName), // 业务服务名称
"nowTime": gtime.Now().Format("Y-m-d H:i:s"), // 当前时间
"version": runtime.Version(), // GO 版本
"hgVersion": consts.VersionApp, // HG 版本
"varName": in.In.VarName, // 实体名称
"tableComment": in.In.TableComment, // 对外名称
"daoName": in.In.DaoName, // ORM模型
"masterFields": in.masterFields, // 主表字段
"pk": in.pk, // 主键属性
"options": in.options, // 提交选项
"dictOptions": dictOptions, // web字典选项
"importApi": importApi, // 导入goApi包
"importInput": importInput, // 导入input包
"importController": importController, // 导入控制器包
"importService": importService, // 导入业务服务
"importWebApi": importWebApi, // 导入webApi
"apiPrefix": in.options.ApiPrefix, // api前缀
"componentPrefix": componentPrefix, // vue子组件前缀
})
in.view = view
return

View File

@@ -1,6 +1,6 @@
// Package views
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//

View File

@@ -1,6 +1,6 @@
// Package views
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//

View File

@@ -1,9 +1,8 @@
// Package views
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package views
import (
@@ -120,11 +119,19 @@ func (l *gCurd) generateWebEditScript(ctx context.Context, in *CurdPreviewInput)
if in.options.Step.HasMaxSort {
importBuffer.WriteString(" import { onMounted, ref, computed, watch } from 'vue';\n")
importBuffer.WriteString(" import { Edit, MaxSort, View } from '@/api/" + gstr.LcFirst(in.In.VarName) + "';\n")
if in.Config.Application.Crud.Templates[in.In.GenTemplate].IsAddon {
importBuffer.WriteString(" import { Edit, MaxSort, View } from '@/api/addons/" + in.In.AddonName + "/" + gstr.LcFirst(in.In.VarName) + "';\n")
} else {
importBuffer.WriteString(" import { Edit, MaxSort, View } from '@/api/" + gstr.LcFirst(in.In.VarName) + "';\n")
}
setupBuffer.WriteString(" function loadForm(value) {\n loading.value = true;\n\n // 新增\n if (value.id < 1) {\n params.value = newState(value);\n MaxSort()\n .then((res) => {\n params.value.sort = res.sort;\n })\n .finally(() => {\n loading.value = false;\n });\n return;\n }\n\n // 编辑\n View({ id: value.id })\n .then((res) => {\n params.value = res;\n })\n .finally(() => {\n loading.value = false;\n });\n }\n\n watch(\n () => props.formParams,\n (value) => {\n loadForm(value);\n }\n );")
} else {
importBuffer.WriteString(" import { onMounted, ref, computed } from 'vue';\n")
importBuffer.WriteString(" import { Edit, View } from '@/api/" + gstr.LcFirst(in.In.VarName) + "';\n")
if in.Config.Application.Crud.Templates[in.In.GenTemplate].IsAddon {
importBuffer.WriteString(" import { Edit, View } from '@/api/addons/" + in.In.AddonName + "/" + gstr.LcFirst(in.In.VarName) + "';\n")
} else {
importBuffer.WriteString(" import { Edit, View } from '@/api/" + gstr.LcFirst(in.In.VarName) + "';\n")
}
setupBuffer.WriteString(" function loadForm(value) {\n // 新增\n if (value.id < 1) {\n params.value = newState(value);\n loading.value = false;\n return;\n }\n\n loading.value = true;\n // 编辑\n View({ id: value.id })\n .then((res) => {\n params.value = res;\n })\n .finally(() => {\n loading.value = false;\n });\n }\n\n watch(\n () => props.formParams,\n (value) => {\n loadForm(value);\n }\n );")
}

View File

@@ -1,9 +1,8 @@
// Package views
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package views
import (
@@ -14,8 +13,9 @@ import (
)
const (
IndexApiImport = " import {%v } from '@/api/%s';"
IndexIconsImport = " import {%v } from '@vicons/antd';"
IndexApiImport = " import {%v } from '@/api/%s';" // 这里将导入的包路径写死了,后面可以优化成根据配置动态读取
IndexApiAddonsImport = " import {%v } from '@/api/addons/%s/%s';"
IndexIconsImport = " import {%v } from '@vicons/antd';"
)
func (l *gCurd) webIndexTplData(ctx context.Context, in *CurdPreviewInput) (g.Map, error) {
@@ -51,7 +51,11 @@ func (l *gCurd) webIndexTplData(ctx context.Context, in *CurdPreviewInput) (g.Ma
apiImport = append(apiImport, " Status")
}
data["apiImport"] = fmt.Sprintf(IndexApiImport, gstr.Implode(",", apiImport), gstr.LcFirst(in.In.VarName))
if in.Config.Application.Crud.Templates[in.In.GenTemplate].IsAddon {
data["apiImport"] = fmt.Sprintf(IndexApiAddonsImport, gstr.Implode(",", apiImport), in.In.AddonName, gstr.LcFirst(in.In.VarName))
} else {
data["apiImport"] = fmt.Sprintf(IndexApiImport, gstr.Implode(",", apiImport), gstr.LcFirst(in.In.VarName))
}
if len(iconsImport) > 0 {
data["iconsImport"] = fmt.Sprintf(IndexIconsImport, gstr.Implode(",", iconsImport))
}

View File

@@ -1,9 +1,8 @@
// Package views
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package views
import (
@@ -11,7 +10,6 @@ import (
"context"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/text/gstr"
"hotgo/internal/model/input/sysin"
"hotgo/utility/convert"
)
@@ -271,7 +269,7 @@ func (l *gCurd) generateWebModelColumnsEach(buffer *bytes.Buffer, in *CurdPrevie
component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n render(row) {\n if (isNullObject(row.%s)) {\n return ``;\n }\n return row.%s.map((attachfile) => {\n return h(\n %s,\n {\n size: 'small',\n style: {\n 'margin-left': '2px',\n },\n },\n {\n default: () => getFileExt(attachfile),\n }\n );\n });\n },\n },\n", field.Dc, field.TsName, field.TsName, field.TsName, "NAvatar")
case FormModeSwitch:
component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n width: 100,\n render(row) {\n return h(%s, {\n value: row.%s === 1,\n checked: '开启',\n unchecked: '关闭',\n disabled: !hasPermission(['%s']),\n onUpdateValue: function (e) {\n console.log('onUpdateValue e:' + JSON.stringify(e));\n row.%s = e ? 1 : 2;\n Switch({ %s: row.%s, key: '%s', value: row.%s }).then((_res) => {\n $message.success('操作成功');\n });\n },\n });\n },\n },\n", field.Dc, field.TsName, "NSwitch", field.TsName, "/"+gstr.LcFirst(in.In.VarName)+"/switch", field.TsName, in.pk.TsName, in.pk.TsName, field.TsName, field.TsName)
component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n width: 100,\n render(row) {\n return h(%s, {\n value: row.%s === 1,\n checked: '开启',\n unchecked: '关闭',\n disabled: !hasPermission(['%s']),\n onUpdateValue: function (e) {\n console.log('onUpdateValue e:' + JSON.stringify(e));\n row.%s = e ? 1 : 2;\n Switch({ %s: row.%s, key: '%s', value: row.%s }).then((_res) => {\n $message.success('操作成功');\n });\n },\n });\n },\n },\n", field.Dc, field.TsName, "NSwitch", field.TsName, "/"+in.options.ApiPrefix+"/switch", field.TsName, in.pk.TsName, in.pk.TsName, field.TsName, field.TsName)
case FormModeRate:
component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n render(row) {\n return h(%s, {\n allowHalf: true,\n readonly: true,\n defaultValue: row.%s,\n });\n },\n },\n", field.Dc, field.TsName, "NRate", field.TsName)

View File

@@ -1,6 +1,6 @@
// Package views
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//

View File

@@ -1,9 +1,8 @@
// Package views
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package views
import (
@@ -11,6 +10,7 @@ import (
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"hotgo/internal/consts"
"hotgo/internal/model"
@@ -98,40 +98,73 @@ func ImportSql(ctx context.Context, path string) error {
return nil
}
func checkCurdPath(temp *model.GenerateAppCrudTemplate) (err error) {
func checkCurdPath(temp *model.GenerateAppCrudTemplate, addonName string) (err error) {
if temp == nil {
return gerror.New("生成模板配置不能为空")
}
tip := `生成模板配置参数'%s'路径不存在,请先创建路径`
if temp.IsAddon {
temp.TemplatePath = gstr.Replace(temp.TemplatePath, "{$name}", addonName)
temp.ApiPath = gstr.Replace(temp.ApiPath, "{$name}", addonName)
temp.InputPath = gstr.Replace(temp.InputPath, "{$name}", addonName)
temp.ControllerPath = gstr.Replace(temp.ControllerPath, "{$name}", addonName)
temp.LogicPath = gstr.Replace(temp.LogicPath, "{$name}", addonName)
temp.RouterPath = gstr.Replace(temp.RouterPath, "{$name}", addonName)
temp.SqlPath = gstr.Replace(temp.SqlPath, "{$name}", addonName)
temp.WebApiPath = gstr.Replace(temp.WebApiPath, "{$name}", addonName)
temp.WebViewsPath = gstr.Replace(temp.WebViewsPath, "{$name}", addonName)
}
tip := `生成模板配置参数'%s'路径不存在,请先创建路径:%s`
if !gfile.Exists(temp.TemplatePath) {
return gerror.Newf(tip, "TemplatePath")
return gerror.Newf(tip, "TemplatePath", temp.TemplatePath)
}
if !gfile.Exists(temp.ApiPath) {
return gerror.Newf(tip, "ApiPath")
return gerror.Newf(tip, "ApiPath", temp.ApiPath)
}
if !gfile.Exists(temp.InputPath) {
return gerror.Newf(tip, "InputPath")
return gerror.Newf(tip, "InputPath", temp.InputPath)
}
if !gfile.Exists(temp.ControllerPath) {
return gerror.Newf(tip, "ControllerPath")
return gerror.Newf(tip, "ControllerPath", temp.ControllerPath)
}
if !gfile.Exists(temp.LogicPath) {
return gerror.Newf(tip, "LogicPath")
return gerror.Newf(tip, "LogicPath", temp.LogicPath)
}
if !gfile.Exists(temp.RouterPath) {
return gerror.Newf(tip, "RouterPath")
return gerror.Newf(tip, "RouterPath", temp.RouterPath)
}
if !gfile.Exists(temp.SqlPath) {
return gerror.Newf(tip, "SqlPath")
return gerror.Newf(tip, "SqlPath", temp.SqlPath)
}
if !gfile.Exists(temp.WebApiPath) {
return gerror.Newf(tip, "WebApiPath")
return gerror.Newf(tip, "WebApiPath", temp.WebApiPath)
}
if !gfile.Exists(temp.WebViewsPath) {
return gerror.Newf(tip, "WebViewsPath")
return gerror.Newf(tip, "WebViewsPath", temp.WebViewsPath)
}
return
}
// GetModName 获取主包名
func GetModName(ctx context.Context) (modName string, err error) {
if !gfile.Exists("go.mod") {
err = gerror.New("go.mod does not exist in current working directory")
return
}
var (
goModContent = gfile.GetContents("go.mod")
match, _ = gregex.MatchString(`^module\s+(.+)\s*`, goModContent)
)
if len(match) > 1 {
modName = gstr.Trim(match[1])
} else {
err = gerror.New("module name does not found in go.mod")
return
}
return
}

View File

@@ -1,8 +1,10 @@
package handler
import "github.com/gogf/gf/v2/database/gdb"
import (
"github.com/gogf/gf/v2/database/gdb"
)
// ForceCache 强制缓存
func ForceCache(m *gdb.Model) *gdb.Model {
return m.Cache(gdb.CacheOption{Duration: -1, Force: true})
return m.Cache(gdb.CacheOption{Duration: 0, Force: true})
}

View File

@@ -1,6 +1,6 @@
// Package handler
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//

View File

@@ -1,9 +1,8 @@
// Package jwt
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package jwt
import (
@@ -54,18 +53,17 @@ func GenerateLoginToken(ctx context.Context, user *model.Identity, isRefresh boo
var (
tokenStringMd5 = gmd5.MustEncryptString(tokenString)
// 绑定登录token
c = cache.New()
key = consts.RedisJwtToken + tokenStringMd5
key = consts.CacheJwtToken + tokenStringMd5
// 将有效期转为持续时间,单位:秒
expires, _ = time.ParseDuration(fmt.Sprintf("+%vs", user.Expires))
)
err = c.Set(ctx, key, tokenString, expires)
err = cache.Instance().Set(ctx, key, tokenString, expires)
if err != nil {
return "", err
}
err = c.Set(ctx, consts.RedisJwtUserBind+user.App+":"+gconv.String(user.Id), key, expires)
err = cache.Instance().Set(ctx, consts.CacheJwtUserBind+user.App+":"+gconv.String(user.Id), key, expires)
if err != nil {
return "", err
}

View File

@@ -1,6 +1,6 @@
// Package location
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
@@ -198,11 +198,11 @@ func GetClientIp(r *ghttp.Request) string {
// 如果存在多个,默认取第一个
if gstr.Contains(ip, ",") {
ip = gstr.TrimStr(ip, ",", -1)
ip = gstr.StrTillEx(ip, ",")
}
if gstr.Contains(ip, ", ") {
ip = gstr.TrimStr(ip, ", ", -1)
ip = gstr.StrTillEx(ip, ", ")
}
return ip

View File

@@ -0,0 +1,137 @@
package queue
import (
"encoding/json"
"fmt"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"hotgo/internal/library/queue/disk"
"sync"
"time"
)
type DiskProducerMq struct {
config *disk.Config
producers map[string]*disk.Queue
sync.Mutex
}
type DiskConsumerMq struct {
config *disk.Config
}
func RegisterDiskMqConsumer(config *disk.Config) (client MqConsumer, err error) {
return &DiskConsumerMq{
config: config,
}, nil
}
// ListenReceiveMsgDo 消费数据
func (q *DiskConsumerMq) ListenReceiveMsgDo(topic string, receiveDo func(mqMsg MqMsg)) (err error) {
if topic == "" {
return gerror.New("disk.ListenReceiveMsgDo topic is empty")
}
var (
queue = NewDiskQueue(topic, q.config)
sleep = time.Second
)
go func() {
for {
if index, offset, data, err := queue.Read(); err == nil {
var mqMsg MqMsg
if err = json.Unmarshal(data, &mqMsg); err != nil {
g.Log().Warningf(ctx, "disk.ListenReceiveMsgDo Unmarshal err:%+v, topic%v, data:%+v .", err, topic, string(data))
continue
}
if mqMsg.MsgId != "" {
receiveDo(mqMsg)
queue.Commit(index, offset)
sleep = time.Millisecond * 1
}
} else {
sleep = time.Second
}
time.Sleep(sleep)
}
}()
select {}
}
func RegisterDiskMqProducer(config *disk.Config) (client MqProducer, err error) {
return &DiskProducerMq{
config: config,
producers: make(map[string]*disk.Queue),
}, nil
}
// SendMsg 按字符串类型生产数据
func (d *DiskProducerMq) SendMsg(topic string, body string) (mqMsg MqMsg, err error) {
return d.SendByteMsg(topic, []byte(body))
}
// SendByteMsg 生产数据
func (d *DiskProducerMq) SendByteMsg(topic string, body []byte) (mqMsg MqMsg, err error) {
if topic == "" {
return mqMsg, gerror.New("DiskMq topic is empty")
}
mqMsg = MqMsg{
RunType: SendMsg,
Topic: topic,
MsgId: getRandMsgId(),
Body: body,
Timestamp: time.Now(),
}
mqMsgJson, err := json.Marshal(mqMsg)
if err != nil {
return mqMsg, gerror.New(fmt.Sprint("queue redis 生产者解析json消息失败:", err))
}
queue := d.getProducer(topic)
if err = queue.Write(mqMsgJson); err != nil {
return mqMsg, gerror.New(fmt.Sprint("queue disk 生产者添加消息失败:", err))
}
return
}
func (d *DiskProducerMq) getProducer(topic string) *disk.Queue {
queue, ok := d.producers[topic]
if ok {
return queue
}
queue = NewDiskQueue(topic, d.config)
d.Lock()
defer d.Unlock()
d.producers[topic] = queue
return queue
}
func NewDiskQueue(topic string, config *disk.Config) *disk.Queue {
conf := &disk.Config{
Path: fmt.Sprintf(config.Path + "/" + config.GroupName + "/" + topic),
BatchSize: config.BatchSize,
BatchTime: config.BatchTime * time.Second,
SegmentSize: config.SegmentSize,
SegmentLimit: config.SegmentLimit,
}
if !gfile.Exists(conf.Path) {
if err := gfile.Mkdir(conf.Path); err != nil {
g.Log().Errorf(ctx, "NewDiskQueue Failed to create the cache directory. Procedure, err:%+v", err)
return nil
}
}
queue, err := disk.New(conf)
if err != nil {
g.Log().Errorf(ctx, "NewDiskQueue err:%v", err)
return nil
}
return queue
}

View File

@@ -0,0 +1,118 @@
package disk
import (
"context"
"errors"
"io"
"os"
"sync"
"time"
)
const (
filePerm = 0600 // 数据写入权限
indexFile = ".index" // 消息索引文件
)
type Config struct {
GroupName string // 组群名称
Path string // 数据存放路径
BatchSize int64 // 每N条消息同步一次batchSize和batchTime满足其一就会同步一次
BatchTime time.Duration // 每N秒消息同步一次
SegmentSize int64 // 每个topic分片数据文件最大字节
SegmentLimit int64 // 每个topic最大分片数据文件数量
}
type Queue struct {
sync.RWMutex
close bool
ticker *time.Ticker
wg *sync.WaitGroup
ctx context.Context
cancel context.CancelFunc
writer *writer
reader *reader
}
func New(config *Config) (queue *Queue, err error) {
if _, err = os.Stat(config.Path); err != nil {
return
}
queue = &Queue{close: false, wg: &sync.WaitGroup{}, writer: &writer{config: config}, reader: &reader{config: config}}
queue.ticker = time.NewTicker(config.BatchTime)
queue.ctx, queue.cancel = context.WithCancel(context.TODO())
err = queue.reader.restore()
if err != nil {
return
}
go queue.sync()
return
}
// Write data
func (q *Queue) Write(data []byte) error {
if q.close {
return errors.New("closed")
}
q.Lock()
defer q.Unlock()
return q.writer.write(data)
}
// Read data
func (q *Queue) Read() (int64, int64, []byte, error) {
if q.close {
return 0, 0, nil, errors.New("closed")
}
q.RLock()
defer q.RUnlock()
index, offset, data, err := q.reader.read()
if err == io.EOF && (q.writer.file == nil || q.reader.file.Name() != q.writer.file.Name()) {
_ = q.reader.safeRotate()
}
return index, offset, data, err
}
// Commit index and offset
func (q *Queue) Commit(index int64, offset int64) {
if q.close {
return
}
ck := &q.reader.checkpoint
ck.Index, ck.Offset = index, offset
q.reader.sync()
}
// Close Queue
func (q *Queue) Close() {
if q.close {
return
}
q.close = true
q.cancel()
q.wg.Wait()
q.writer.close()
q.reader.close()
}
// sync data
func (q *Queue) sync() {
q.wg.Add(1)
defer q.wg.Done()
for {
select {
case <-q.ticker.C:
q.Lock()
q.writer.sync()
q.Unlock()
case <-q.ctx.Done():
return
}
}
}

View File

@@ -0,0 +1,176 @@
package disk
import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"sort"
"strconv"
)
var (
errorQueueEmpty = errors.New("queue is empty")
)
type reader struct {
file *os.File
index int64
offset int64
reader *bufio.Reader
checkpoint checkpoint
config *Config
}
type checkpoint struct {
Index int64 `json:"index"`
Offset int64 `json:"offset"`
}
// read data
func (r *reader) read() (int64, int64, []byte, error) {
if err := r.check(); err != nil {
return r.index, r.offset, nil, err
}
// read a line
data, err := r.reader.ReadBytes('\n')
if err != nil {
return r.index, r.offset, nil, err
}
data = bytes.TrimRight(data, "\n")
r.offset += int64(len(data)) + 1
return r.index, r.offset, data, err
}
// check a new segment
func (r *reader) check() error {
if r.file != nil {
return nil
}
file, err := r.next()
if err != nil {
return err
}
return r.open(file)
}
func (r *reader) open(file string) (err error) {
if r.file, err = os.OpenFile(file, os.O_RDONLY, filePerm); err != nil {
return err
}
// get file index
r.index = r.getIndex(file)
// seek read offset
if _, err = r.file.Seek(r.offset, 0); err != nil {
return err
}
r.reader = bufio.NewReader(r.file)
return nil
}
// safeRotate to next segment
func (r *reader) safeRotate() error {
// if there is no next file, it is not cleared
if _, err := r.next(); err == errorQueueEmpty {
return nil
}
return r.rotate()
}
// rotate to next segment
func (r *reader) rotate() error {
if r.file == nil {
return nil
}
// close segment
_ = r.file.Close()
r.file, r.offset, r.reader = nil, 0, nil
return nil
}
// close reader
func (r *reader) close() {
if r.file == nil {
return
}
if err := r.file.Close(); err != nil {
return
}
r.file, r.reader, r.index, r.offset = nil, nil, 0, 0
}
// sync index and offset
func (r *reader) sync() {
name := path.Join(r.config.Path, indexFile)
data, _ := json.Marshal(&r.checkpoint)
_ = ioutil.WriteFile(name, data, filePerm)
}
// restore index and offset
func (r *reader) restore() (err error) {
name := path.Join(r.config.Path, indexFile)
// uninitialized
if _, err1 := os.Stat(name); err1 != nil {
r.sync()
}
data, _ := ioutil.ReadFile(name)
_ = json.Unmarshal(data, &r.checkpoint)
r.index, r.offset = r.checkpoint.Index, r.checkpoint.Offset
if r.index == 0 {
return
}
if err = r.open(fmt.Sprintf("%s/%d.data", r.config.Path, r.index)); err != nil {
r.offset = 0
}
return
}
// next segment
func (r *reader) next() (string, error) {
files, err := filepath.Glob(filepath.Join(r.config.Path, "*.data"))
if err != nil {
return "", err
}
sort.Strings(files)
for _, file := range files {
index := r.getIndex(file)
if index < r.checkpoint.Index {
_ = os.Remove(file) // remove expired segment
}
if index > r.index {
return file, nil
}
}
return "", errorQueueEmpty
}
// get segment index
func (r *reader) getIndex(filename string) int64 {
base := filepath.Base(filename)
name := base[0 : len(base)-len(path.Ext(filename))]
index, _ := strconv.ParseInt(name, 10, 64)
return index
}

View File

@@ -0,0 +1,102 @@
package disk
import (
"bufio"
"errors"
"fmt"
"os"
"path"
"path/filepath"
"time"
)
type writer struct {
file *os.File
size int64
count int64
writer *bufio.Writer
config *Config
}
// write data
func (w *writer) write(data []byte) error {
// append newline
data = append(data, "\n"...)
size := int64(len(data))
// close current segment for rotate
if w.size+size > w.config.SegmentSize {
w.close()
}
// create a new segment
if w.file == nil {
if err := w.open(); err != nil {
return err
}
}
// write to buffer
if _, err := w.writer.Write(data); err != nil {
return err
}
w.size += size
// sync data to disk
w.count++
if w.count >= w.config.BatchSize {
w.sync()
}
return nil
}
// create a new segment
func (w *writer) open() (err error) {
if w.segmentNum() >= w.config.SegmentLimit {
return errors.New("segment num exceeds the limit")
}
name := path.Join(w.config.Path, fmt.Sprintf("%013d.data", time.Now().UnixNano()/1e6))
if w.file, err = os.OpenFile(name, os.O_CREATE|os.O_WRONLY, filePerm); err != nil {
return err
}
w.size = 0
// disable auto flush
w.writer = bufio.NewWriterSize(w.file, int(w.config.SegmentSize))
w.writer.Reset(w.file)
return err
}
// sync data to disk
func (w *writer) sync() {
if w.writer == nil {
return
}
if err := w.writer.Flush(); err == nil {
w.count = 0
}
}
// close segment
func (w *writer) close() {
if w.file == nil {
return
}
w.sync()
if err := w.file.Close(); err != nil {
return
}
w.size, w.file, w.writer = 0, nil, nil
}
// segment num
func (w *writer) segmentNum() int64 {
segments, _ := filepath.Glob(path.Join(w.config.Path, "*.data"))
return int64(len(segments))
}

View File

@@ -1,15 +1,15 @@
// Package queue
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package queue
import (
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"hotgo/internal/library/queue/disk"
"hotgo/utility/charset"
"sync"
"time"
@@ -31,21 +31,21 @@ const (
)
type Config struct {
Switch bool `json:"switch"`
Driver string `json:"driver"`
Retry int `json:"retry"`
MultiComsumer bool `json:"multiComsumer"`
GroupName string `json:"groupName"`
Redis RedisConf
Rocketmq RocketmqConf
Kafka KafkaConf
Switch bool `json:"switch"`
Driver string `json:"driver"`
Retry int `json:"retry"`
GroupName string `json:"groupName"`
Redis RedisConf
Rocketmq RocketmqConf
Kafka KafkaConf
Disk *disk.Config
}
type RedisConf struct {
Address string `json:"address"`
Db int `json:"db"`
Pass string `json:"pass"`
Timeout int `json:"timeout"`
Address string `json:"address"`
Db int `json:"db"`
Pass string `json:"pass"`
IdleTimeout int `json:"idleTimeout"`
}
type RocketmqConf struct {
Address []string `json:"address"`
@@ -53,9 +53,10 @@ type RocketmqConf struct {
}
type KafkaConf struct {
Address []string `json:"address"`
Version string `json:"version"`
RandClient bool `json:"randClient"`
Address []string `json:"address"`
Version string `json:"version"`
RandClient bool `json:"randClient"`
MultiConsumer bool `json:"multiConsumer"`
}
type MqMsg struct {
@@ -80,7 +81,7 @@ func init() {
mqProducerInstanceMap = make(map[string]MqProducer)
mqConsumerInstanceMap = make(map[string]MqConsumer)
if err := g.Cfg().MustGet(ctx, "queue").Scan(&config); err != nil {
g.Log().Infof(ctx, "queue init err:%+v", err)
g.Log().Warning(ctx, "queue init err:%+v", err)
}
}
@@ -132,11 +133,13 @@ func NewProducer(groupName string) (mqClient MqProducer, err error) {
Addr: config.Redis.Address,
Passwd: config.Redis.Pass,
DBnum: config.Redis.Db,
Timeout: config.Redis.Timeout,
Timeout: config.Redis.IdleTimeout,
}, PoolOption{
5, 50, 5,
}, groupName, config.Retry)
case "disk":
config.Disk.GroupName = groupName
mqClient, err = RegisterDiskMqProducer(config.Disk)
default:
err = gerror.New("queue driver is not support")
}
@@ -154,17 +157,6 @@ func NewProducer(groupName string) (mqClient MqProducer, err error) {
// NewConsumer 初始化消费者实例
func NewConsumer(groupName string) (mqClient MqConsumer, err error) {
randTag := string(charset.RandomCreateBytes(6))
// 是否支持创建多个消费者
if config.MultiComsumer == false {
randTag = "001"
}
if item, ok := mqConsumerInstanceMap[groupName+"-"+randTag]; ok {
return item, nil
}
if groupName == "" {
err = gerror.New("mq groupName is empty.")
return
@@ -183,6 +175,16 @@ func NewConsumer(groupName string) (mqClient MqConsumer, err error) {
return
}
randTag := string(charset.RandomCreateBytes(6))
// 是否支持创建多个消费者
if config.Kafka.MultiConsumer == false {
randTag = "001"
}
if item, ok := mqConsumerInstanceMap[groupName+"-"+randTag]; ok {
return item, nil
}
clientId := "HOTGO-Consumer-" + groupName
if config.Kafka.RandClient {
clientId += "-" + randTag
@@ -204,10 +206,13 @@ func NewConsumer(groupName string) (mqClient MqConsumer, err error) {
Addr: config.Redis.Address,
Passwd: config.Redis.Pass,
DBnum: config.Redis.Db,
Timeout: config.Redis.Timeout,
Timeout: config.Redis.IdleTimeout,
}, PoolOption{
5, 50, 5,
}, groupName)
case "disk":
config.Disk.GroupName = groupName
mqClient, err = RegisterDiskMqConsumer(config.Disk)
default:
err = gerror.New("queue driver is not support")
}

View File

@@ -1,6 +1,6 @@
// Package queue
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//

View File

@@ -1,6 +1,6 @@
// Package queue
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//

View File

@@ -1,6 +1,6 @@
// Package queue
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//

View File

@@ -1,6 +1,6 @@
// Package queue
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//

View File

@@ -1,6 +1,6 @@
// Package response
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//