mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-11-13 12:43:45 +08:00
发布代码生成、更新20+表单组件,优化数据字典,gf版本更新到2.3.1
This commit is contained in:
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/casbin/casbin/v2/model"
|
||||
"github.com/casbin/casbin/v2/persist"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"log"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
@@ -119,7 +118,6 @@ func (a *adapter) dropPolicyTable() (err error) {
|
||||
|
||||
// LoadPolicy loads all policy rules from the storage.
|
||||
func (a *adapter) LoadPolicy(model model.Model) (err error) {
|
||||
log.Println("LoadPolicy...")
|
||||
var rules []policyRule
|
||||
|
||||
if err = a.model().Scan(&rules); err != nil {
|
||||
@@ -248,7 +246,7 @@ func (a *adapter) UpdatePolicies(sec string, ptype string, oldRules, newRules []
|
||||
return
|
||||
}
|
||||
|
||||
err = a.db.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
|
||||
err = a.db.Transaction(context.TODO(), func(ctx context.Context, tx gdb.TX) error {
|
||||
for i := 0; i < int(math.Min(float64(len(oldRules)), float64(len(newRules)))); i++ {
|
||||
if _, err = tx.Model(a.table).Update(a.buildPolicyRule(ptype, newRules[i]), a.buildPolicyRule(ptype, oldRules[i])); err != nil {
|
||||
return err
|
||||
|
||||
@@ -29,8 +29,8 @@ var Enforcer *casbin.Enforcer
|
||||
// InitEnforcer 初始化
|
||||
func InitEnforcer(ctx context.Context) {
|
||||
var (
|
||||
link, _ = g.Cfg().Get(ctx, "database.default.link")
|
||||
a, err = NewAdapter(link.String())
|
||||
link = g.Cfg().MustGet(ctx, "database.default.link")
|
||||
a, err = NewAdapter(link.String())
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
@@ -53,10 +53,10 @@ func loadPermissions(ctx context.Context) {
|
||||
Permissions string `json:"permissions"`
|
||||
}
|
||||
var (
|
||||
rules [][]string
|
||||
polices []*Policy
|
||||
err error
|
||||
superRoleKey, _ = g.Cfg().Get(ctx, "hotgo.admin.superRoleKey")
|
||||
rules [][]string
|
||||
polices []*Policy
|
||||
err error
|
||||
superRoleKey = g.Cfg().MustGet(ctx, "hotgo.admin.superRoleKey")
|
||||
)
|
||||
|
||||
err = g.Model("hg_admin_role r").
|
||||
|
||||
@@ -67,7 +67,7 @@ func GetRoleId(ctx context.Context) int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return user.Role
|
||||
return user.RoleId
|
||||
}
|
||||
|
||||
// GetRoleKey 获取用户角色唯一编码
|
||||
|
||||
13
server/internal/library/debris/debris.go
Normal file
13
server/internal/library/debris/debris.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Package debris
|
||||
// @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 debris
|
||||
|
||||
// 碎片
|
||||
|
||||
func Test() {
|
||||
|
||||
}
|
||||
192
server/internal/library/hggen/hggen.go
Normal file
192
server/internal/library/hggen/hggen.go
Normal file
@@ -0,0 +1,192 @@
|
||||
// Package hggen
|
||||
// @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 hggen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/hggen/internal/cmd"
|
||||
"hotgo/internal/library/hggen/internal/cmd/gendao"
|
||||
"hotgo/internal/library/hggen/views"
|
||||
"hotgo/internal/model/input/form"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Dao 生成数据库实体
|
||||
func Dao(ctx context.Context) (err error) {
|
||||
for _, v := range daoConfig {
|
||||
inp := defaultGenDaoInput
|
||||
err = gconv.Scan(v, &inp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
gendao.DoGenDaoForArray(ctx, inp)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Service 生成业务接口
|
||||
func Service(ctx context.Context) (err error) {
|
||||
_, err = cmd.Gen.Service(ctx, GetServiceConfig())
|
||||
return
|
||||
}
|
||||
|
||||
// TableColumns 获取指定表生成字段列表
|
||||
func TableColumns(ctx context.Context, in sysin.GenCodesColumnListInp) (fields []*sysin.GenCodesColumnListModel, err error) {
|
||||
return views.DoTableColumns(ctx, in, GetDaoConfig(in.Name))
|
||||
}
|
||||
|
||||
func TableSelects(ctx context.Context, in sysin.GenCodesSelectsInp) (res *sysin.GenCodesSelectsModel, err error) {
|
||||
res = new(sysin.GenCodesSelectsModel)
|
||||
for k, v := range consts.GenCodesTypeNameMap {
|
||||
res.GenType = append(res.GenType, &form.Select{
|
||||
Value: k,
|
||||
Name: v,
|
||||
Label: v,
|
||||
})
|
||||
}
|
||||
sort.Sort(res.GenType)
|
||||
res.Db = DbSelect(ctx)
|
||||
|
||||
for k, v := range consts.GenCodesStatusNameMap {
|
||||
res.Status = append(res.Status, &form.Select{
|
||||
Value: k,
|
||||
Name: v,
|
||||
Label: v,
|
||||
})
|
||||
}
|
||||
sort.Sort(res.Status)
|
||||
|
||||
for k, v := range consts.GenCodesJoinNameMap {
|
||||
res.LinkMode = append(res.LinkMode, &form.Select{
|
||||
Value: k,
|
||||
Name: v,
|
||||
Label: v,
|
||||
})
|
||||
}
|
||||
sort.Sort(res.LinkMode)
|
||||
|
||||
for k, v := range consts.GenCodesBuildMethNameMap {
|
||||
res.BuildMeth = append(res.BuildMeth, &form.Select{
|
||||
Value: k,
|
||||
Name: v,
|
||||
Label: v,
|
||||
})
|
||||
}
|
||||
sort.Sort(res.BuildMeth)
|
||||
|
||||
for _, v := range views.FormModes {
|
||||
res.FormMode = append(res.FormMode, &form.Select{
|
||||
Value: v,
|
||||
Name: views.FormModeMap[v],
|
||||
Label: views.FormModeMap[v],
|
||||
})
|
||||
}
|
||||
sort.Sort(res.FormMode)
|
||||
|
||||
for k, v := range views.FormRoleMap {
|
||||
res.FormRole = append(res.FormRole, &form.Select{
|
||||
Value: k,
|
||||
Name: v,
|
||||
Label: v,
|
||||
})
|
||||
}
|
||||
sort.Sort(res.FormRole)
|
||||
|
||||
dictMode, _ := service.SysDictType().TreeSelect(ctx, sysin.DictTreeSelectInp{})
|
||||
|
||||
res.DictMode = dictMode
|
||||
for _, v := range views.WhereModes {
|
||||
res.WhereMode = append(res.WhereMode, &form.Select{
|
||||
Value: v,
|
||||
Name: v,
|
||||
Label: v,
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DbSelect db选项
|
||||
func DbSelect(ctx context.Context) (res form.Selects) {
|
||||
dbs := g.Cfg().MustGet(ctx, "hggen.selectDbs")
|
||||
if len(dbs.Strings()) == 0 {
|
||||
res = make(form.Selects, 0)
|
||||
return res
|
||||
}
|
||||
|
||||
for _, v := range dbs.Strings() {
|
||||
res = append(res, &form.Select{
|
||||
Value: v,
|
||||
Label: v,
|
||||
Name: v,
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Preview 生成预览
|
||||
func Preview(ctx context.Context, in sysin.GenCodesPreviewInp) (res *sysin.GenCodesPreviewModel, err error) {
|
||||
genConfig, err := service.SysConfig().GetLoadGenerate(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch in.GenType {
|
||||
case consts.GenCodesTypeCurd:
|
||||
return views.Curd.DoPreview(ctx, &views.CurdPreviewInput{
|
||||
In: in,
|
||||
DaoConfig: GetDaoConfig(in.DbName),
|
||||
Config: genConfig,
|
||||
})
|
||||
case consts.GenCodesTypeTree:
|
||||
err = gerror.Newf("生成类型开发中!")
|
||||
return
|
||||
case consts.GenCodesTypeQueue:
|
||||
err = gerror.Newf("生成类型开发中!")
|
||||
return
|
||||
default:
|
||||
err = gerror.Newf("生成类型暂不支持!")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Build 提交生成
|
||||
func Build(ctx context.Context, in sysin.GenCodesBuildInp) (err error) {
|
||||
genConfig, err := service.SysConfig().GetLoadGenerate(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch in.GenType {
|
||||
case consts.GenCodesTypeCurd:
|
||||
return views.Curd.DoBuild(ctx, &views.CurdBuildInput{
|
||||
PreviewIn: &views.CurdPreviewInput{
|
||||
In: sysin.GenCodesPreviewInp(in),
|
||||
DaoConfig: GetDaoConfig(in.DbName),
|
||||
Config: genConfig,
|
||||
},
|
||||
BeforeEvent: views.CurdBuildEvent{"runDao": Dao},
|
||||
AfterEvent: views.CurdBuildEvent{"runService": Service},
|
||||
})
|
||||
case consts.GenCodesTypeTree:
|
||||
err = gerror.Newf("生成类型开发中!")
|
||||
return
|
||||
case consts.GenCodesTypeQueue:
|
||||
err = gerror.Newf("生成类型开发中!")
|
||||
return
|
||||
default:
|
||||
err = gerror.Newf("生成类型暂不支持!")
|
||||
return
|
||||
}
|
||||
}
|
||||
133
server/internal/library/hggen/init.go
Normal file
133
server/internal/library/hggen/init.go
Normal file
@@ -0,0 +1,133 @@
|
||||
// Package hggen
|
||||
// @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 hggen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"gopkg.in/yaml.v3"
|
||||
"hotgo/internal/library/hggen/internal/cmd/gendao"
|
||||
"hotgo/internal/library/hggen/internal/cmd/genservice"
|
||||
)
|
||||
|
||||
const (
|
||||
cliFolderName = `hack/config.yaml`
|
||||
RequiredErrorTag = `the cli configuration file must be configured %s`
|
||||
)
|
||||
|
||||
var (
|
||||
config g.Map
|
||||
daoConfig []interface{}
|
||||
serviceConfig g.Map
|
||||
|
||||
// 生成service默认参数,请不要直接修改以下配置,如需调整请到/hack/config.yaml,可参考:https://goframe.org/pages/viewpage.action?pageId=49770772
|
||||
defaultGenServiceInput = genservice.CGenServiceInput{
|
||||
SrcFolder: "internal/logic",
|
||||
DstFolder: "internal/service",
|
||||
DstFileNameCase: "Snake",
|
||||
StPattern: `s([A-Z]\w+)`,
|
||||
Clear: false,
|
||||
}
|
||||
|
||||
// 生成dao默认参数,请不要直接修改以下配置,如需调整请到/hack/config.yaml,可参考:https://goframe.org/pages/viewpage.action?pageId=3673173
|
||||
defaultGenDaoInput = gendao.CGenDaoInput{
|
||||
Path: "internal",
|
||||
Group: "default",
|
||||
JsonCase: "CamelLower",
|
||||
DaoPath: "dao",
|
||||
DoPath: "model/do",
|
||||
EntityPath: "model/entity",
|
||||
StdTime: false,
|
||||
WithTime: false,
|
||||
GJsonSupport: false,
|
||||
OverwriteDao: false,
|
||||
DescriptionTag: true,
|
||||
NoJsonTag: false,
|
||||
NoModelComment: false,
|
||||
Clear: false,
|
||||
}
|
||||
)
|
||||
|
||||
func GetServiceConfig() genservice.CGenServiceInput {
|
||||
inp := defaultGenServiceInput
|
||||
_ = gconv.Scan(serviceConfig, &inp)
|
||||
return inp
|
||||
}
|
||||
|
||||
func GetDaoConfig(group string) gendao.CGenDaoInput {
|
||||
inp := defaultGenDaoInput
|
||||
find := func(group string) g.Map {
|
||||
for _, v := range daoConfig {
|
||||
if v.(g.Map)["group"].(string) == group {
|
||||
return v.(g.Map)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
v := find(group)
|
||||
if v != nil {
|
||||
err := gconv.Scan(v, &inp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return inp
|
||||
}
|
||||
|
||||
func InIt(ctx context.Context) {
|
||||
path, err := gfile.Search(cliFolderName)
|
||||
if err != nil {
|
||||
g.Log().Fatalf(ctx, "get cli configuration file:%v, err:%+v", cliFolderName, err)
|
||||
}
|
||||
if path == "" {
|
||||
g.Log().Fatalf(ctx, "get cli configuration file:%v fail", cliFolderName)
|
||||
}
|
||||
if config == nil {
|
||||
config = make(g.Map)
|
||||
}
|
||||
err = yaml.Unmarshal(gfile.GetBytes(path), &config)
|
||||
if err != nil {
|
||||
g.Log().Fatalf(ctx, "load cli configuration file:%v, yaml err:%+v", cliFolderName, err)
|
||||
}
|
||||
loadConfig(ctx)
|
||||
}
|
||||
|
||||
func loadConfig(ctx context.Context) {
|
||||
if _, ok := config["gfcli"]; !ok {
|
||||
g.Log().Fatalf(ctx, RequiredErrorTag, "gfcli")
|
||||
}
|
||||
|
||||
if _, ok := config["gfcli"].(g.Map)["gen"]; !ok {
|
||||
g.Log().Fatalf(ctx, RequiredErrorTag, "gfcli.gen")
|
||||
}
|
||||
|
||||
dao, ok := config["gfcli"].(g.Map)["gen"].(map[string]interface{})["dao"]
|
||||
if !ok {
|
||||
g.Log().Fatalf(ctx, RequiredErrorTag, "gfcli.gen.dao")
|
||||
}
|
||||
daoConf, ok := dao.([]interface{})
|
||||
if !ok {
|
||||
g.Log().Fatalf(ctx, RequiredErrorTag, "gfcli.gen.dao format error")
|
||||
}
|
||||
daoConfig = daoConf
|
||||
for _, v := range daoConfig {
|
||||
if _, ok := v.(g.Map)["group"].(string); !ok {
|
||||
g.Log().Fatalf(ctx, "group must be configured in %s: `gfcli.gen.dao` and must be the same as the database group", cliFolderName)
|
||||
}
|
||||
}
|
||||
|
||||
if serviceConf, ok := config["gfcli"].(g.Map)["gen"].(map[string]interface{})["service"]; ok {
|
||||
if serviceConfig == nil {
|
||||
serviceConfig = make(g.Map)
|
||||
}
|
||||
serviceConfig = serviceConf.(g.Map)
|
||||
}
|
||||
}
|
||||
66
server/internal/library/hggen/internal/cmd/cmd.go
Normal file
66
server/internal/library/hggen/internal/cmd/cmd.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gcmd"
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
|
||||
"hotgo/internal/library/hggen/internal/service"
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
)
|
||||
|
||||
var (
|
||||
GF = cGF{}
|
||||
)
|
||||
|
||||
type cGF struct {
|
||||
g.Meta `name:"gf" ad:"{cGFAd}"`
|
||||
}
|
||||
|
||||
const (
|
||||
cGFAd = `
|
||||
ADDITIONAL
|
||||
Use "gf COMMAND -h" for details about a command.
|
||||
`
|
||||
)
|
||||
|
||||
func init() {
|
||||
gtag.Sets(g.MapStrStr{
|
||||
`cGFAd`: cGFAd,
|
||||
})
|
||||
}
|
||||
|
||||
type cGFInput struct {
|
||||
g.Meta `name:"gf"`
|
||||
Yes bool `short:"y" name:"yes" brief:"all yes for all command without prompt ask" orphan:"true"`
|
||||
Version bool `short:"v" name:"version" brief:"show version information of current binary" orphan:"true"`
|
||||
Debug bool `short:"d" name:"debug" brief:"show internal detailed debugging information" orphan:"true"`
|
||||
}
|
||||
|
||||
type cGFOutput struct{}
|
||||
|
||||
func (c cGF) Index(ctx context.Context, in cGFInput) (out *cGFOutput, err error) {
|
||||
// Version.
|
||||
if in.Version {
|
||||
_, err = Version.Index(ctx, cVersionInput{})
|
||||
return
|
||||
}
|
||||
// No argument or option, do installation checks.
|
||||
if !service.Install.IsInstalled() {
|
||||
mlog.Print("hi, it seams it's the first time you installing gf cli.")
|
||||
s := gcmd.Scanf("do you want to install gf binary to your system? [y/n]: ")
|
||||
if strings.EqualFold(s, "y") {
|
||||
if err = service.Install.Run(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
gcmd.Scan("press `Enter` to exit...")
|
||||
return
|
||||
}
|
||||
}
|
||||
// Print help content.
|
||||
gcmd.CommandFromCtx(ctx).Print()
|
||||
return
|
||||
}
|
||||
327
server/internal/library/hggen/internal/cmd/cmd_build.go
Normal file
327
server/internal/library/hggen/internal/cmd/cmd_build.go
Normal file
@@ -0,0 +1,327 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gbase64"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gcmd"
|
||||
"github.com/gogf/gf/v2/os/genv"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gproc"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
)
|
||||
|
||||
var (
|
||||
Build = cBuild{
|
||||
nodeNameInConfigFile: "gfcli.build",
|
||||
packedGoFileName: "internal/packed/build_pack_data.go",
|
||||
}
|
||||
)
|
||||
|
||||
type cBuild struct {
|
||||
g.Meta `name:"build" brief:"{cBuildBrief}" dc:"{cBuildDc}" eg:"{cBuildEg}" ad:"{cBuildAd}"`
|
||||
nodeNameInConfigFile string // nodeNameInConfigFile is the node name for compiler configurations in configuration file.
|
||||
packedGoFileName string // packedGoFileName specifies the file name for packing common folders into one single go file.
|
||||
}
|
||||
|
||||
const (
|
||||
cBuildBrief = `cross-building go project for lots of platforms`
|
||||
cBuildEg = `
|
||||
gf build main.go
|
||||
gf build main.go --pack public,template
|
||||
gf build main.go --cgo
|
||||
gf build main.go -m none
|
||||
gf build main.go -n my-app -a all -s all
|
||||
gf build main.go -n my-app -a amd64,386 -s linux -p .
|
||||
gf build main.go -n my-app -v 1.0 -a amd64,386 -s linux,windows,darwin -p ./docker/bin
|
||||
`
|
||||
cBuildDc = `
|
||||
The "build" command is most commonly used command, which is designed as a powerful wrapper for
|
||||
"go build" command for convenience cross-compiling usage.
|
||||
It provides much more features for building binary:
|
||||
1. Cross-Compiling for many platforms and architectures.
|
||||
2. Configuration file support for compiling.
|
||||
3. Build-In Variables.
|
||||
`
|
||||
cBuildAd = `
|
||||
PLATFORMS
|
||||
darwin amd64,arm64
|
||||
freebsd 386,amd64,arm
|
||||
linux 386,amd64,arm,arm64,ppc64,ppc64le,mips,mipsle,mips64,mips64le
|
||||
netbsd 386,amd64,arm
|
||||
openbsd 386,amd64,arm
|
||||
windows 386,amd64
|
||||
`
|
||||
// https://golang.google.cn/doc/install/source
|
||||
cBuildPlatforms = `
|
||||
darwin amd64
|
||||
darwin arm64
|
||||
ios amd64
|
||||
ios arm64
|
||||
freebsd 386
|
||||
freebsd amd64
|
||||
freebsd arm
|
||||
linux 386
|
||||
linux amd64
|
||||
linux arm
|
||||
linux arm64
|
||||
linux ppc64
|
||||
linux ppc64le
|
||||
linux mips
|
||||
linux mipsle
|
||||
linux mips64
|
||||
linux mips64le
|
||||
netbsd 386
|
||||
netbsd amd64
|
||||
netbsd arm
|
||||
openbsd 386
|
||||
openbsd amd64
|
||||
openbsd arm
|
||||
windows 386
|
||||
windows amd64
|
||||
android arm
|
||||
dragonfly amd64
|
||||
plan9 386
|
||||
plan9 amd64
|
||||
solaris amd64
|
||||
`
|
||||
)
|
||||
|
||||
func init() {
|
||||
gtag.Sets(g.MapStrStr{
|
||||
`cBuildBrief`: cBuildBrief,
|
||||
`cBuildDc`: cBuildDc,
|
||||
`cBuildEg`: cBuildEg,
|
||||
`cBuildAd`: cBuildAd,
|
||||
})
|
||||
}
|
||||
|
||||
type cBuildInput struct {
|
||||
g.Meta `name:"build" config:"gfcli.build"`
|
||||
File string `name:"FILE" arg:"true" brief:"building file path"`
|
||||
Name string `short:"n" name:"name" brief:"output binary name"`
|
||||
Version string `short:"v" name:"version" brief:"output binary version"`
|
||||
Arch string `short:"a" name:"arch" brief:"output binary architecture, multiple arch separated with ','"`
|
||||
System string `short:"s" name:"system" brief:"output binary system, multiple os separated with ','"`
|
||||
Output string `short:"o" name:"output" brief:"output binary path, used when building single binary file"`
|
||||
Path string `short:"p" name:"path" brief:"output binary directory path, default is './temp'" d:"./temp"`
|
||||
Extra string `short:"e" name:"extra" brief:"extra custom \"go build\" options"`
|
||||
Mod string `short:"m" name:"mod" brief:"like \"-mod\" option of \"go build\", use \"-m none\" to disable go module"`
|
||||
Cgo bool `short:"c" name:"cgo" brief:"enable or disable cgo feature, it's disabled in default" orphan:"true"`
|
||||
VarMap g.Map `short:"r" name:"varMap" brief:"custom built embedded variable into binary"`
|
||||
PackSrc string `short:"ps" name:"packSrc" brief:"pack one or more folders into one go file before building"`
|
||||
PackDst string `short:"pd" name:"packDst" brief:"temporary go file path for pack, this go file will be automatically removed after built" d:"internal/packed/build_pack_data.go"`
|
||||
ExitWhenError bool `short:"ew" name:"exitWhenError" brief:"exit building when any error occurs, default is false" orphan:"true"`
|
||||
}
|
||||
|
||||
type cBuildOutput struct{}
|
||||
|
||||
func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, err error) {
|
||||
mlog.SetHeaderPrint(true)
|
||||
|
||||
mlog.Debugf(`build input: %+v`, in)
|
||||
// Necessary check.
|
||||
if gproc.SearchBinary("go") == "" {
|
||||
mlog.Fatalf(`command "go" not found in your environment, please install golang first to proceed this command`)
|
||||
}
|
||||
|
||||
var (
|
||||
parser = gcmd.ParserFromCtx(ctx)
|
||||
file = parser.GetArg(2).String()
|
||||
)
|
||||
if len(file) < 1 {
|
||||
// Check and use the main.go file.
|
||||
if gfile.Exists("main.go") {
|
||||
file = "main.go"
|
||||
} else {
|
||||
mlog.Fatal("build file path cannot be empty")
|
||||
}
|
||||
}
|
||||
if in.Name == "" {
|
||||
in.Name = gfile.Name(file)
|
||||
}
|
||||
if len(in.Name) < 1 || in.Name == "*" {
|
||||
mlog.Fatal("name cannot be empty")
|
||||
}
|
||||
if in.Mod != "" && in.Mod != "none" {
|
||||
mlog.Debugf(`mod is %s`, in.Mod)
|
||||
if in.Extra == "" {
|
||||
in.Extra = fmt.Sprintf(`-mod=%s`, in.Mod)
|
||||
} else {
|
||||
in.Extra = fmt.Sprintf(`-mod=%s %s`, in.Mod, in.Extra)
|
||||
}
|
||||
}
|
||||
if in.Extra != "" {
|
||||
in.Extra += " "
|
||||
}
|
||||
var (
|
||||
customSystems = gstr.SplitAndTrim(in.System, ",")
|
||||
customArches = gstr.SplitAndTrim(in.Arch, ",")
|
||||
)
|
||||
if len(in.Version) > 0 {
|
||||
in.Path += "/" + in.Version
|
||||
}
|
||||
// System and arch checks.
|
||||
var (
|
||||
spaceRegex = regexp.MustCompile(`\s+`)
|
||||
platformMap = make(map[string]map[string]bool)
|
||||
)
|
||||
for _, line := range strings.Split(strings.TrimSpace(cBuildPlatforms), "\n") {
|
||||
line = gstr.Trim(line)
|
||||
line = spaceRegex.ReplaceAllString(line, " ")
|
||||
var (
|
||||
array = strings.Split(line, " ")
|
||||
system = strings.TrimSpace(array[0])
|
||||
arch = strings.TrimSpace(array[1])
|
||||
)
|
||||
if platformMap[system] == nil {
|
||||
platformMap[system] = make(map[string]bool)
|
||||
}
|
||||
platformMap[system][arch] = true
|
||||
}
|
||||
// Auto packing.
|
||||
if in.PackSrc != "" {
|
||||
if in.PackDst == "" {
|
||||
mlog.Fatal(`parameter "packDst" should not be empty when "packSrc" is used`)
|
||||
}
|
||||
if gfile.Exists(in.PackDst) && !gfile.IsFile(in.PackDst) {
|
||||
mlog.Fatalf(`parameter "packDst" path "%s" should be type of file not directory`, in.PackDst)
|
||||
}
|
||||
if !gfile.Exists(in.PackDst) {
|
||||
// Remove the go file that is automatically packed resource.
|
||||
defer func() {
|
||||
_ = gfile.Remove(in.PackDst)
|
||||
mlog.Printf(`remove the automatically generated resource go file: %s`, in.PackDst)
|
||||
}()
|
||||
}
|
||||
// remove black space in separator.
|
||||
in.PackSrc, _ = gregex.ReplaceString(`,\s+`, `,`, in.PackSrc)
|
||||
packCmd := fmt.Sprintf(`gf pack %s %s --keepPath=true`, in.PackSrc, in.PackDst)
|
||||
mlog.Print(packCmd)
|
||||
gproc.MustShellRun(ctx, packCmd)
|
||||
}
|
||||
|
||||
// Injected information by building flags.
|
||||
ldFlags := fmt.Sprintf(
|
||||
`-X 'github.com/gogf/gf/v2/os/gbuild.builtInVarStr=%v'`,
|
||||
c.getBuildInVarStr(ctx, in),
|
||||
)
|
||||
|
||||
// start building
|
||||
mlog.Print("start building...")
|
||||
if in.Cgo {
|
||||
genv.MustSet("CGO_ENABLED", "1")
|
||||
} else {
|
||||
genv.MustSet("CGO_ENABLED", "0")
|
||||
}
|
||||
var (
|
||||
cmd = ""
|
||||
ext = ""
|
||||
)
|
||||
for system, item := range platformMap {
|
||||
cmd = ""
|
||||
ext = ""
|
||||
if len(customSystems) > 0 && customSystems[0] != "all" && !gstr.InArray(customSystems, system) {
|
||||
continue
|
||||
}
|
||||
for arch, _ := range item {
|
||||
if len(customArches) > 0 && customArches[0] != "all" && !gstr.InArray(customArches, arch) {
|
||||
continue
|
||||
}
|
||||
if len(customSystems) == 0 && len(customArches) == 0 {
|
||||
if runtime.GOOS == "windows" {
|
||||
ext = ".exe"
|
||||
}
|
||||
// Single binary building, output the binary to current working folder.
|
||||
output := ""
|
||||
if len(in.Output) > 0 {
|
||||
output = "-o " + in.Output + ext
|
||||
} else {
|
||||
output = "-o " + in.Name + ext
|
||||
}
|
||||
cmd = fmt.Sprintf(`go build %s -ldflags "%s" %s %s`, output, ldFlags, in.Extra, file)
|
||||
} else {
|
||||
// Cross-building, output the compiled binary to specified path.
|
||||
if system == "windows" {
|
||||
ext = ".exe"
|
||||
}
|
||||
genv.MustSet("GOOS", system)
|
||||
genv.MustSet("GOARCH", arch)
|
||||
cmd = fmt.Sprintf(
|
||||
`go build -o %s/%s/%s%s -ldflags "%s" %s%s`,
|
||||
in.Path, system+"_"+arch, in.Name, ext, ldFlags, in.Extra, file,
|
||||
)
|
||||
}
|
||||
mlog.Debug(cmd)
|
||||
// It's not necessary printing the complete command string.
|
||||
cmdShow, _ := gregex.ReplaceString(`\s+(-ldflags ".+?")\s+`, " ", cmd)
|
||||
mlog.Print(cmdShow)
|
||||
if result, err := gproc.ShellExec(ctx, cmd); err != nil {
|
||||
mlog.Printf(
|
||||
"failed to build, os:%s, arch:%s, error:\n%s\n\n%s\n",
|
||||
system, arch, gstr.Trim(result),
|
||||
`you may use command option "--debug" to enable debug info and check the details`,
|
||||
)
|
||||
if in.ExitWhenError {
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
mlog.Debug(gstr.Trim(result))
|
||||
}
|
||||
// single binary building.
|
||||
if len(customSystems) == 0 && len(customArches) == 0 {
|
||||
goto buildDone
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildDone:
|
||||
mlog.Print("done!")
|
||||
return
|
||||
}
|
||||
|
||||
// getBuildInVarMapJson retrieves and returns the custom build-in variables in configuration
|
||||
// file as json.
|
||||
func (c cBuild) getBuildInVarStr(ctx context.Context, in cBuildInput) string {
|
||||
buildInVarMap := in.VarMap
|
||||
if buildInVarMap == nil {
|
||||
buildInVarMap = make(g.Map)
|
||||
}
|
||||
buildInVarMap["builtGit"] = c.getGitCommit(ctx)
|
||||
buildInVarMap["builtTime"] = gtime.Now().String()
|
||||
b, err := json.Marshal(buildInVarMap)
|
||||
if err != nil {
|
||||
mlog.Fatal(err)
|
||||
}
|
||||
return gbase64.EncodeToString(b)
|
||||
}
|
||||
|
||||
// getGitCommit retrieves and returns the latest git commit hash string if present.
|
||||
func (c cBuild) getGitCommit(ctx context.Context) string {
|
||||
if gproc.SearchBinary("git") == "" {
|
||||
return ""
|
||||
}
|
||||
var (
|
||||
cmd = `git log -1 --format="%cd %H" --date=format:"%Y-%m-%d %H:%M:%S"`
|
||||
s, _ = gproc.ShellExec(ctx, cmd)
|
||||
)
|
||||
mlog.Debug(cmd)
|
||||
if s != "" {
|
||||
if !gstr.Contains(s, "fatal") {
|
||||
return gstr.Trim(s)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
163
server/internal/library/hggen/internal/cmd/cmd_docker.go
Normal file
163
server/internal/library/hggen/internal/cmd/cmd_docker.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gproc"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
)
|
||||
|
||||
var (
|
||||
Docker = cDocker{}
|
||||
)
|
||||
|
||||
type cDocker struct {
|
||||
g.Meta `name:"docker" usage:"{cDockerUsage}" brief:"{cDockerBrief}" eg:"{cDockerEg}" dc:"{cDockerDc}"`
|
||||
}
|
||||
|
||||
const (
|
||||
cDockerUsage = `gf docker [MAIN] [OPTION]`
|
||||
cDockerBrief = `build docker image for current GoFrame project`
|
||||
cDockerEg = `
|
||||
gf docker
|
||||
gf docker -t hub.docker.com/john/image:tag
|
||||
gf docker -p -t hub.docker.com/john/image:tag
|
||||
gf docker main.go
|
||||
gf docker main.go -t hub.docker.com/john/image:tag
|
||||
gf docker main.go -t hub.docker.com/john/image:tag
|
||||
gf docker main.go -p -t hub.docker.com/john/image:tag
|
||||
`
|
||||
cDockerDc = `
|
||||
The "docker" command builds the GF project to a docker images.
|
||||
It runs "gf build" firstly to compile the project to binary file.
|
||||
It then runs "docker build" command automatically to generate the docker image.
|
||||
You should have docker installed, and there must be a Dockerfile in the root of the project.
|
||||
`
|
||||
cDockerMainBrief = `main file path for "gf build", it's "main.go" in default. empty string for no binary build`
|
||||
cDockerBuildBrief = `binary build options before docker image build, it's "-a amd64 -s linux" in default`
|
||||
cDockerFileBrief = `file path of the Dockerfile. it's "manifest/docker/Dockerfile" in default`
|
||||
cDockerShellBrief = `path of the shell file which is executed before docker build`
|
||||
cDockerPushBrief = `auto push the docker image to docker registry if "-t" option passed`
|
||||
cDockerTagNameBrief = `tag name for this docker, pattern like "image:tag". this option is required with TagPrefixes`
|
||||
cDockerTagPrefixesBrief = `tag prefixes for this docker, which are used for docker push. this option is required with TagName`
|
||||
cDockerExtraBrief = `extra build options passed to "docker image"`
|
||||
)
|
||||
|
||||
func init() {
|
||||
gtag.Sets(g.MapStrStr{
|
||||
`cDockerUsage`: cDockerUsage,
|
||||
`cDockerBrief`: cDockerBrief,
|
||||
`cDockerEg`: cDockerEg,
|
||||
`cDockerDc`: cDockerDc,
|
||||
`cDockerMainBrief`: cDockerMainBrief,
|
||||
`cDockerFileBrief`: cDockerFileBrief,
|
||||
`cDockerShellBrief`: cDockerShellBrief,
|
||||
`cDockerBuildBrief`: cDockerBuildBrief,
|
||||
`cDockerPushBrief`: cDockerPushBrief,
|
||||
`cDockerTagNameBrief`: cDockerTagNameBrief,
|
||||
`cDockerTagPrefixesBrief`: cDockerTagPrefixesBrief,
|
||||
`cDockerExtraBrief`: cDockerExtraBrief,
|
||||
})
|
||||
}
|
||||
|
||||
type cDockerInput struct {
|
||||
g.Meta `name:"docker" config:"gfcli.docker"`
|
||||
Main string `name:"MAIN" arg:"true" brief:"{cDockerMainBrief}" d:"main.go"`
|
||||
File string `name:"file" short:"f" brief:"{cDockerFileBrief}" d:"manifest/docker/Dockerfile"`
|
||||
Shell string `name:"shell" short:"s" brief:"{cDockerShellBrief}" d:"manifest/docker/docker.sh"`
|
||||
Build string `name:"build" short:"b" brief:"{cDockerBuildBrief}" d:"-a amd64 -s linux"`
|
||||
TagName string `name:"tagName" short:"tn" brief:"{cDockerTagNameBrief}" v:"required-with:TagPrefixes"`
|
||||
TagPrefixes []string `name:"tagPrefixes" short:"tp" brief:"{cDockerTagPrefixesBrief}" v:"required-with:TagName"`
|
||||
Push bool `name:"push" short:"p" brief:"{cDockerPushBrief}" orphan:"true"`
|
||||
Extra string `name:"extra" short:"e" brief:"{cDockerExtraBrief}"`
|
||||
}
|
||||
|
||||
type cDockerOutput struct{}
|
||||
|
||||
func (c cDocker) Index(ctx context.Context, in cDockerInput) (out *cDockerOutput, err error) {
|
||||
// Necessary check.
|
||||
if gproc.SearchBinary("docker") == "" {
|
||||
mlog.Fatalf(`command "docker" not found in your environment, please install docker first to proceed this command`)
|
||||
}
|
||||
|
||||
// Binary build.
|
||||
in.Build += " --exit"
|
||||
if in.Main != "" {
|
||||
if err = gproc.ShellRun(ctx, fmt.Sprintf(`gf build %s %s`, in.Main, in.Build)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Shell executing.
|
||||
if in.Shell != "" && gfile.Exists(in.Shell) {
|
||||
if err = c.exeDockerShell(ctx, in.Shell); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Docker build.
|
||||
var (
|
||||
dockerBuildOptions string
|
||||
dockerTags []string
|
||||
dockerTagBase string
|
||||
)
|
||||
if len(in.TagPrefixes) > 0 {
|
||||
for _, tagPrefix := range in.TagPrefixes {
|
||||
tagPrefix = gstr.TrimRight(tagPrefix, "/")
|
||||
dockerTags = append(dockerTags, fmt.Sprintf(`%s/%s`, tagPrefix, in.TagName))
|
||||
}
|
||||
}
|
||||
if len(dockerTags) == 0 {
|
||||
dockerTags = []string{""}
|
||||
}
|
||||
for i, dockerTag := range dockerTags {
|
||||
if i > 0 {
|
||||
err = gproc.ShellRun(ctx, fmt.Sprintf(`docker tag %s %s`, dockerTagBase, dockerTag))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
dockerTagBase = dockerTag
|
||||
dockerBuildOptions = ""
|
||||
if dockerTag != "" {
|
||||
dockerBuildOptions = fmt.Sprintf(`-t %s`, dockerTag)
|
||||
}
|
||||
if in.Extra != "" {
|
||||
dockerBuildOptions = fmt.Sprintf(`%s %s`, dockerBuildOptions, in.Extra)
|
||||
}
|
||||
err = gproc.ShellRun(ctx, fmt.Sprintf(`docker build -f %s . %s`, in.File, dockerBuildOptions))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Docker push.
|
||||
if !in.Push {
|
||||
return
|
||||
}
|
||||
for _, dockerTag := range dockerTags {
|
||||
if dockerTag == "" {
|
||||
continue
|
||||
}
|
||||
err = gproc.ShellRun(ctx, fmt.Sprintf(`docker push %s`, dockerTag))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c cDocker) exeDockerShell(ctx context.Context, shellFilePath string) error {
|
||||
if gfile.ExtName(shellFilePath) == "sh" && runtime.GOOS == "windows" {
|
||||
mlog.Debugf(`ignore shell file "%s", as it cannot be run on windows system`, shellFilePath)
|
||||
return nil
|
||||
}
|
||||
return gproc.ShellRun(ctx, gfile.GetContents(shellFilePath))
|
||||
}
|
||||
63
server/internal/library/hggen/internal/cmd/cmd_env.go
Normal file
63
server/internal/library/hggen/internal/cmd/cmd_env.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gproc"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
)
|
||||
|
||||
var (
|
||||
Env = cEnv{}
|
||||
)
|
||||
|
||||
type cEnv struct {
|
||||
g.Meta `name:"env" brief:"show current Golang environment variables"`
|
||||
}
|
||||
|
||||
type cEnvInput struct {
|
||||
g.Meta `name:"env"`
|
||||
}
|
||||
|
||||
type cEnvOutput struct{}
|
||||
|
||||
func (c cEnv) Index(ctx context.Context, in cEnvInput) (out *cEnvOutput, err error) {
|
||||
result, err := gproc.ShellExec(ctx, "go env")
|
||||
if err != nil {
|
||||
mlog.Fatal(err)
|
||||
}
|
||||
if result == "" {
|
||||
mlog.Fatal(`retrieving Golang environment variables failed, did you install Golang?`)
|
||||
}
|
||||
var (
|
||||
lines = gstr.Split(result, "\n")
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
)
|
||||
array := make([][]string, 0)
|
||||
for _, line := range lines {
|
||||
line = gstr.Trim(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
if gstr.Pos(line, "set ") == 0 {
|
||||
line = line[4:]
|
||||
}
|
||||
match, _ := gregex.MatchString(`(.+?)=(.*)`, line)
|
||||
if len(match) < 3 {
|
||||
mlog.Fatalf(`invalid Golang environment variable: "%s"`, line)
|
||||
}
|
||||
array = append(array, []string{gstr.Trim(match[1]), gstr.Trim(match[2])})
|
||||
}
|
||||
tw := tablewriter.NewWriter(buffer)
|
||||
tw.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT})
|
||||
tw.AppendBulk(array)
|
||||
tw.Render()
|
||||
mlog.Print(buffer.String())
|
||||
return
|
||||
}
|
||||
99
server/internal/library/hggen/internal/cmd/cmd_fix.go
Normal file
99
server/internal/library/hggen/internal/cmd/cmd_fix.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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/gstr"
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
)
|
||||
|
||||
var (
|
||||
Fix = cFix{}
|
||||
)
|
||||
|
||||
type cFix struct {
|
||||
g.Meta `name:"fix" brief:"auto fixing codes after upgrading to new GoFrame version" usage:"gf fix" `
|
||||
}
|
||||
|
||||
type cFixInput struct {
|
||||
g.Meta `name:"fix"`
|
||||
}
|
||||
|
||||
type cFixOutput struct{}
|
||||
|
||||
type cFixItem struct {
|
||||
Version string
|
||||
Func func(version string) error
|
||||
}
|
||||
|
||||
func (c cFix) Index(ctx context.Context, in cFixInput) (out *cFixOutput, err error) {
|
||||
mlog.Print(`start auto fixing...`)
|
||||
defer mlog.Print(`done!`)
|
||||
err = c.doFix()
|
||||
return
|
||||
}
|
||||
|
||||
func (c cFix) doFix() (err error) {
|
||||
version, err := c.getVersion()
|
||||
if err != nil {
|
||||
mlog.Fatal(err)
|
||||
}
|
||||
if version == "" {
|
||||
mlog.Print(`no GoFrame usage found, exit fixing`)
|
||||
return
|
||||
}
|
||||
mlog.Debugf(`current GoFrame version found "%s"`, version)
|
||||
|
||||
var items = []cFixItem{
|
||||
{Version: "v2.3", Func: c.doFixV23},
|
||||
}
|
||||
for _, item := range items {
|
||||
if gstr.CompareVersionGo(version, item.Version) < 0 {
|
||||
mlog.Debugf(
|
||||
`current GoFrame version "%s" is lesser than "%s", nothing to do`,
|
||||
version, item.Version,
|
||||
)
|
||||
continue
|
||||
}
|
||||
if err = item.Func(version); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// doFixV23 fixes code when upgrading to GoFrame v2.3.
|
||||
func (c cFix) doFixV23(version string) error {
|
||||
replaceFunc := func(path, content string) string {
|
||||
content = gstr.Replace(content, "*gdb.TX", "gdb.TX")
|
||||
return content
|
||||
}
|
||||
return gfile.ReplaceDirFunc(replaceFunc, ".", "*.go", true)
|
||||
}
|
||||
|
||||
func (c cFix) getVersion() (string, error) {
|
||||
var (
|
||||
err error
|
||||
path = "go.mod"
|
||||
version string
|
||||
)
|
||||
if !gfile.Exists(path) {
|
||||
return "", gerror.Newf(`"%s" not found in current working directory`, path)
|
||||
}
|
||||
err = gfile.ReadLines(path, func(line string) error {
|
||||
array := gstr.SplitAndTrim(line, " ")
|
||||
if len(array) > 0 {
|
||||
if array[0] == gfPackage {
|
||||
version = array[1]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
mlog.Fatal(err)
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
34
server/internal/library/hggen/internal/cmd/cmd_gen.go
Normal file
34
server/internal/library/hggen/internal/cmd/cmd_gen.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
)
|
||||
|
||||
var (
|
||||
Gen = cGen{}
|
||||
)
|
||||
|
||||
type cGen struct {
|
||||
g.Meta `name:"hggen" brief:"{cGenBrief}" dc:"{cGenDc}"`
|
||||
cGenDao
|
||||
cGenPb
|
||||
cGenPbEntity
|
||||
cGenService
|
||||
}
|
||||
|
||||
const (
|
||||
cGenBrief = `automatically generate go files for dao/do/entity/pb/pbentity`
|
||||
cGenDc = `
|
||||
The "hggen" command is designed for multiple generating purposes.
|
||||
It's currently supporting generating go files for ORM models, protobuf and protobuf entity files.
|
||||
Please use "gf hggen dao -h" for specified type help.
|
||||
`
|
||||
)
|
||||
|
||||
func init() {
|
||||
gtag.Sets(g.MapStrStr{
|
||||
`cGenBrief`: cGenBrief,
|
||||
`cGenDc`: cGenDc,
|
||||
})
|
||||
}
|
||||
15
server/internal/library/hggen/internal/cmd/cmd_gen_dao.go
Normal file
15
server/internal/library/hggen/internal/cmd/cmd_gen_dao.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
//_ "github.com/gogf/gf/contrib/drivers/mssql/v2"
|
||||
//_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
//_ "github.com/gogf/gf/contrib/drivers/oracle/v2"
|
||||
//_ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
|
||||
//_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
||||
|
||||
"hotgo/internal/library/hggen/internal/cmd/gendao"
|
||||
)
|
||||
|
||||
type (
|
||||
cGenDao = gendao.CGenDao
|
||||
)
|
||||
79
server/internal/library/hggen/internal/cmd/cmd_gen_pb.go
Normal file
79
server/internal/library/hggen/internal/cmd/cmd_gen_pb.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gset"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/genv"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gproc"
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
)
|
||||
|
||||
type (
|
||||
cGenPb struct{}
|
||||
cGenPbInput struct {
|
||||
g.Meta `name:"pb" brief:"parse proto files and generate protobuf go files"`
|
||||
}
|
||||
cGenPbOutput struct{}
|
||||
)
|
||||
|
||||
func (c cGenPb) Pb(ctx context.Context, in cGenPbInput) (out *cGenPbOutput, err error) {
|
||||
// Necessary check.
|
||||
if gproc.SearchBinary("protoc") == "" {
|
||||
mlog.Fatalf(`command "protoc" not found in your environment, please install protoc first to proceed this command`)
|
||||
}
|
||||
|
||||
// protocol fold checks.
|
||||
protoFolder := "protocol"
|
||||
if !gfile.Exists(protoFolder) {
|
||||
mlog.Fatalf(`proto files folder "%s" does not exist`, protoFolder)
|
||||
}
|
||||
// folder scanning.
|
||||
files, err := gfile.ScanDirFile(protoFolder, "*.proto", true)
|
||||
if err != nil {
|
||||
mlog.Fatal(err)
|
||||
}
|
||||
if len(files) == 0 {
|
||||
mlog.Fatalf(`no proto files found in folder "%s"`, protoFolder)
|
||||
}
|
||||
dirSet := gset.NewStrSet()
|
||||
for _, file := range files {
|
||||
dirSet.Add(gfile.Dir(file))
|
||||
}
|
||||
var (
|
||||
servicePath = gfile.RealPath(".")
|
||||
goPathSrc = gfile.RealPath(gfile.Join(genv.Get("GOPATH").String(), "src"))
|
||||
)
|
||||
dirSet.Iterator(func(protoDirPath string) bool {
|
||||
parsingCommand := fmt.Sprintf(
|
||||
"protoc --gofast_out=plugins=grpc:. %s/*.proto -I%s",
|
||||
protoDirPath,
|
||||
servicePath,
|
||||
)
|
||||
if goPathSrc != "" {
|
||||
parsingCommand += " -I" + goPathSrc
|
||||
}
|
||||
mlog.Print(parsingCommand)
|
||||
if output, err := gproc.ShellExec(ctx, parsingCommand); err != nil {
|
||||
mlog.Print(output)
|
||||
mlog.Fatal(err)
|
||||
}
|
||||
return true
|
||||
})
|
||||
// Custom replacement.
|
||||
//pbFolder := "protobuf"
|
||||
//_, _ = gfile.ScanDirFileFunc(pbFolder, "*.go", true, func(path string) string {
|
||||
// content := gfile.GetContents(path)
|
||||
// content = gstr.ReplaceByArray(content, g.SliceStr{
|
||||
// `gtime "gtime"`, `gtime "github.com/gogf/gf/v2/os/gtime"`,
|
||||
// })
|
||||
// _ = gfile.PutContents(path, content)
|
||||
// utils.GoFmt(path)
|
||||
// return path
|
||||
//})
|
||||
mlog.Print("done!")
|
||||
return
|
||||
}
|
||||
411
server/internal/library/hggen/internal/cmd/cmd_gen_pbentity.go
Normal file
411
server/internal/library/hggen/internal/cmd/cmd_gen_pbentity.go
Normal file
@@ -0,0 +1,411 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
|
||||
"hotgo/internal/library/hggen/internal/consts"
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
)
|
||||
|
||||
type (
|
||||
cGenPbEntity struct{}
|
||||
cGenPbEntityInput struct {
|
||||
g.Meta `name:"pbentity" config:"{cGenPbEntityConfig}" brief:"{cGenPbEntityBrief}" eg:"{cGenPbEntityEg}" ad:"{cGenPbEntityAd}"`
|
||||
Path string `name:"path" short:"p" brief:"{cGenPbEntityBriefPath}"`
|
||||
Package string `name:"package" short:"k" brief:"{cGenPbEntityBriefPackage}"`
|
||||
Link string `name:"link" short:"l" brief:"{cGenPbEntityBriefLink}"`
|
||||
Tables string `name:"tables" short:"t" brief:"{cGenPbEntityBriefTables}"`
|
||||
Prefix string `name:"prefix" short:"f" brief:"{cGenPbEntityBriefPrefix}"`
|
||||
RemovePrefix string `name:"removePrefix" short:"r" brief:"{cGenPbEntityBriefRemovePrefix}"`
|
||||
NameCase string `name:"nameCase" short:"n" brief:"{cGenPbEntityBriefNameCase}" d:"Camel"`
|
||||
JsonCase string `name:"jsonCase" short:"j" brief:"{cGenPbEntityBriefJsonCase}" d:"CamelLower"`
|
||||
Option string `name:"option" short:"o" brief:"{cGenPbEntityBriefOption}"`
|
||||
}
|
||||
cGenPbEntityOutput struct{}
|
||||
|
||||
cGenPbEntityInternalInput struct {
|
||||
cGenPbEntityInput
|
||||
TableName string // TableName specifies the table name of the table.
|
||||
NewTableName string // NewTableName specifies the prefix-stripped name of the table.
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
cGenPbEntityConfig = `gfcli.hggen.pbentity`
|
||||
cGenPbEntityBrief = `generate entity message files in protobuf3 format`
|
||||
cGenPbEntityEg = `
|
||||
gf hggen pbentity
|
||||
gf hggen pbentity -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
|
||||
gf hggen pbentity -p ./protocol/demos/entity -t user,user_detail,user_login
|
||||
gf hggen pbentity -r user_
|
||||
`
|
||||
|
||||
cGenPbEntityAd = `
|
||||
CONFIGURATION SUPPORT
|
||||
Options are also supported by configuration file.
|
||||
It's suggested using configuration file instead of command line arguments making producing.
|
||||
The configuration node name is "gf.hggen.pbentity", which also supports multiple databases, for example(config.yaml):
|
||||
gfcli:
|
||||
hggen:
|
||||
- pbentity:
|
||||
link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
|
||||
path: "protocol/demos/entity"
|
||||
tables: "order,products"
|
||||
package: "demos"
|
||||
- pbentity:
|
||||
link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary"
|
||||
path: "protocol/demos/entity"
|
||||
prefix: "primary_"
|
||||
tables: "user, userDetail"
|
||||
package: "demos"
|
||||
option: |
|
||||
option go_package = "protobuf/demos";
|
||||
option java_package = "protobuf/demos";
|
||||
option php_namespace = "protobuf/demos";
|
||||
`
|
||||
cGenPbEntityBriefPath = `directory path for generated files`
|
||||
cGenPbEntityBriefPackage = `package name for all entity proto files`
|
||||
cGenPbEntityBriefLink = `database configuration, the same as the ORM configuration of GoFrame`
|
||||
cGenPbEntityBriefTables = `generate models only for given tables, multiple table names separated with ','`
|
||||
cGenPbEntityBriefPrefix = `add specified prefix for all entity names and entity proto files`
|
||||
cGenPbEntityBriefRemovePrefix = `remove specified prefix of the table, multiple prefix separated with ','`
|
||||
cGenPbEntityBriefOption = `extra protobuf options`
|
||||
cGenPbEntityBriefGroup = `
|
||||
specifying the configuration group name of database for generated ORM instance,
|
||||
it's not necessary and the default value is "default"
|
||||
`
|
||||
|
||||
cGenPbEntityBriefNameCase = `
|
||||
case for message attribute names, default is "Camel":
|
||||
| Case | Example |
|
||||
|---------------- |--------------------|
|
||||
| Camel | AnyKindOfString |
|
||||
| CamelLower | anyKindOfString | default
|
||||
| Snake | any_kind_of_string |
|
||||
| SnakeScreaming | ANY_KIND_OF_STRING |
|
||||
| SnakeFirstUpper | rgb_code_md5 |
|
||||
| Kebab | any-kind-of-string |
|
||||
| KebabScreaming | ANY-KIND-OF-STRING |
|
||||
`
|
||||
|
||||
cGenPbEntityBriefJsonCase = `
|
||||
case for message json tag, cases are the same as "nameCase", default "CamelLower".
|
||||
set it to "none" to ignore json tag generating.
|
||||
`
|
||||
)
|
||||
|
||||
func init() {
|
||||
gtag.Sets(g.MapStrStr{
|
||||
`cGenPbEntityConfig`: cGenPbEntityConfig,
|
||||
`cGenPbEntityBrief`: cGenPbEntityBrief,
|
||||
`cGenPbEntityEg`: cGenPbEntityEg,
|
||||
`cGenPbEntityAd`: cGenPbEntityAd,
|
||||
`cGenPbEntityBriefPath`: cGenPbEntityBriefPath,
|
||||
`cGenPbEntityBriefPackage`: cGenPbEntityBriefPackage,
|
||||
`cGenPbEntityBriefLink`: cGenPbEntityBriefLink,
|
||||
`cGenPbEntityBriefTables`: cGenPbEntityBriefTables,
|
||||
`cGenPbEntityBriefPrefix`: cGenPbEntityBriefPrefix,
|
||||
`cGenPbEntityBriefRemovePrefix`: cGenPbEntityBriefRemovePrefix,
|
||||
`cGenPbEntityBriefGroup`: cGenPbEntityBriefGroup,
|
||||
`cGenPbEntityBriefNameCase`: cGenPbEntityBriefNameCase,
|
||||
`cGenPbEntityBriefJsonCase`: cGenPbEntityBriefJsonCase,
|
||||
`cGenPbEntityBriefOption`: cGenPbEntityBriefOption,
|
||||
})
|
||||
}
|
||||
|
||||
func (c cGenPbEntity) PbEntity(ctx context.Context, in cGenPbEntityInput) (out *cGenPbEntityOutput, err error) {
|
||||
var (
|
||||
config = g.Cfg()
|
||||
)
|
||||
if config.Available(ctx) {
|
||||
v := config.MustGet(ctx, cGenPbEntityConfig)
|
||||
if v.IsSlice() {
|
||||
for i := 0; i < len(v.Interfaces()); i++ {
|
||||
doGenPbEntityForArray(ctx, i, in)
|
||||
}
|
||||
} else {
|
||||
doGenPbEntityForArray(ctx, -1, in)
|
||||
}
|
||||
} else {
|
||||
doGenPbEntityForArray(ctx, -1, in)
|
||||
}
|
||||
mlog.Print("done!")
|
||||
return
|
||||
}
|
||||
|
||||
func doGenPbEntityForArray(ctx context.Context, index int, in cGenPbEntityInput) {
|
||||
var (
|
||||
err error
|
||||
db gdb.DB
|
||||
)
|
||||
if index >= 0 {
|
||||
err = g.Cfg().MustGet(
|
||||
ctx,
|
||||
fmt.Sprintf(`%s.%d`, cGenPbEntityConfig, index),
|
||||
).Scan(&in)
|
||||
if err != nil {
|
||||
mlog.Fatalf(`invalid configuration of "%s": %+v`, cGenPbEntityConfig, err)
|
||||
}
|
||||
}
|
||||
if in.Package == "" {
|
||||
mlog.Fatal("package name should not be empty")
|
||||
}
|
||||
removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",")
|
||||
// It uses user passed database configuration.
|
||||
if in.Link != "" {
|
||||
var (
|
||||
tempGroup = gtime.TimestampNanoStr()
|
||||
match, _ = gregex.MatchString(`([a-z]+):(.+)`, in.Link)
|
||||
)
|
||||
if len(match) == 3 {
|
||||
gdb.AddConfigNode(tempGroup, gdb.ConfigNode{
|
||||
Type: gstr.Trim(match[1]),
|
||||
Link: gstr.Trim(match[2]),
|
||||
})
|
||||
db, _ = gdb.Instance(tempGroup)
|
||||
}
|
||||
} else {
|
||||
db = g.DB()
|
||||
}
|
||||
if db == nil {
|
||||
mlog.Fatal("database initialization failed")
|
||||
}
|
||||
|
||||
tableNames := ([]string)(nil)
|
||||
if in.Tables != "" {
|
||||
tableNames = gstr.SplitAndTrim(in.Tables, ",")
|
||||
} else {
|
||||
tableNames, err = db.Tables(context.TODO())
|
||||
if err != nil {
|
||||
mlog.Fatalf("fetching tables failed: \n %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, tableName := range tableNames {
|
||||
newTableName := tableName
|
||||
for _, v := range removePrefixArray {
|
||||
newTableName = gstr.TrimLeftStr(newTableName, v, 1)
|
||||
}
|
||||
generatePbEntityContentFile(ctx, db, cGenPbEntityInternalInput{
|
||||
cGenPbEntityInput: in,
|
||||
TableName: tableName,
|
||||
NewTableName: newTableName,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// generatePbEntityContentFile generates the protobuf files for given table.
|
||||
func generatePbEntityContentFile(ctx context.Context, db gdb.DB, in cGenPbEntityInternalInput) {
|
||||
fieldMap, err := db.TableFields(ctx, in.TableName)
|
||||
if err != nil {
|
||||
mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", in.TableName, err)
|
||||
}
|
||||
// Change the `newTableName` if `Prefix` is given.
|
||||
newTableName := "Entity_" + in.Prefix + in.NewTableName
|
||||
var (
|
||||
tableNameCamelCase = gstr.CaseCamel(newTableName)
|
||||
tableNameSnakeCase = gstr.CaseSnake(newTableName)
|
||||
entityMessageDefine = generateEntityMessageDefinition(tableNameCamelCase, fieldMap, in)
|
||||
fileName = gstr.Trim(tableNameSnakeCase, "-_.")
|
||||
path = gfile.Join(in.Path, fileName+".proto")
|
||||
)
|
||||
entityContent := gstr.ReplaceByMap(getTplPbEntityContent(""), g.MapStrStr{
|
||||
"{PackageName}": in.Package,
|
||||
"{OptionContent}": in.Option,
|
||||
"{EntityMessage}": entityMessageDefine,
|
||||
})
|
||||
if err := gfile.PutContents(path, strings.TrimSpace(entityContent)); err != nil {
|
||||
mlog.Fatalf("writing content to '%s' failed: %v", path, err)
|
||||
} else {
|
||||
mlog.Print("generated:", path)
|
||||
}
|
||||
}
|
||||
|
||||
// generateEntityMessageDefinition generates and returns the message definition for specified table.
|
||||
func generateEntityMessageDefinition(entityName string, fieldMap map[string]*gdb.TableField, in cGenPbEntityInternalInput) string {
|
||||
var (
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
array = make([][]string, len(fieldMap))
|
||||
names = sortFieldKeyForPbEntity(fieldMap)
|
||||
)
|
||||
for index, name := range names {
|
||||
array[index] = generateMessageFieldForPbEntity(index+1, fieldMap[name], in)
|
||||
}
|
||||
tw := tablewriter.NewWriter(buffer)
|
||||
tw.SetBorder(false)
|
||||
tw.SetRowLine(false)
|
||||
tw.SetAutoWrapText(false)
|
||||
tw.SetColumnSeparator("")
|
||||
tw.AppendBulk(array)
|
||||
tw.Render()
|
||||
stContent := buffer.String()
|
||||
// Let's do this hack of table writer for indent!
|
||||
stContent = gstr.Replace(stContent, " #", "")
|
||||
buffer.Reset()
|
||||
buffer.WriteString(fmt.Sprintf("message %s {\n", entityName))
|
||||
buffer.WriteString(stContent)
|
||||
buffer.WriteString("}")
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// generateMessageFieldForPbEntity generates and returns the message definition for specified field.
|
||||
func generateMessageFieldForPbEntity(index int, field *gdb.TableField, in cGenPbEntityInternalInput) []string {
|
||||
var (
|
||||
typeName string
|
||||
comment string
|
||||
jsonTagStr string
|
||||
)
|
||||
t, _ := gregex.ReplaceString(`\(.+\)`, "", field.Type)
|
||||
t = gstr.Split(gstr.Trim(t), " ")[0]
|
||||
t = gstr.ToLower(t)
|
||||
switch t {
|
||||
case "binary", "varbinary", "blob", "tinyblob", "mediumblob", "longblob":
|
||||
typeName = "bytes"
|
||||
|
||||
case "bit", "int", "tinyint", "small_int", "smallint", "medium_int", "mediumint", "serial":
|
||||
if gstr.ContainsI(field.Type, "unsigned") {
|
||||
typeName = "uint32"
|
||||
} else {
|
||||
typeName = "int32"
|
||||
}
|
||||
|
||||
case "int8", "big_int", "bigint", "bigserial":
|
||||
if gstr.ContainsI(field.Type, "unsigned") {
|
||||
typeName = "uint64"
|
||||
} else {
|
||||
typeName = "int64"
|
||||
}
|
||||
|
||||
case "real":
|
||||
typeName = "float"
|
||||
|
||||
case "float", "double", "decimal", "smallmoney":
|
||||
typeName = "double"
|
||||
|
||||
case "bool":
|
||||
typeName = "bool"
|
||||
|
||||
case "datetime", "timestamp", "date", "time":
|
||||
typeName = "int64"
|
||||
|
||||
default:
|
||||
// Auto detecting type.
|
||||
switch {
|
||||
case strings.Contains(t, "int"):
|
||||
typeName = "int"
|
||||
case strings.Contains(t, "text") || strings.Contains(t, "char"):
|
||||
typeName = "string"
|
||||
case strings.Contains(t, "float") || strings.Contains(t, "double"):
|
||||
typeName = "double"
|
||||
case strings.Contains(t, "bool"):
|
||||
typeName = "bool"
|
||||
case strings.Contains(t, "binary") || strings.Contains(t, "blob"):
|
||||
typeName = "bytes"
|
||||
case strings.Contains(t, "date") || strings.Contains(t, "time"):
|
||||
typeName = "int64"
|
||||
default:
|
||||
typeName = "string"
|
||||
}
|
||||
}
|
||||
comment = gstr.ReplaceByArray(field.Comment, g.SliceStr{
|
||||
"\n", " ",
|
||||
"\r", " ",
|
||||
})
|
||||
comment = gstr.Trim(comment)
|
||||
comment = gstr.Replace(comment, `\n`, " ")
|
||||
comment, _ = gregex.ReplaceString(`\s{2,}`, ` `, comment)
|
||||
if jsonTagName := formatCase(field.Name, in.JsonCase); jsonTagName != "" {
|
||||
jsonTagStr = fmt.Sprintf(`[(gogoproto.jsontag) = "%s"]`, jsonTagName)
|
||||
// beautiful indent.
|
||||
if index < 10 {
|
||||
// 3 spaces
|
||||
jsonTagStr = " " + jsonTagStr
|
||||
} else if index < 100 {
|
||||
// 2 spaces
|
||||
jsonTagStr = " " + jsonTagStr
|
||||
} else {
|
||||
// 1 spaces
|
||||
jsonTagStr = " " + jsonTagStr
|
||||
}
|
||||
}
|
||||
return []string{
|
||||
" #" + typeName,
|
||||
" #" + formatCase(field.Name, in.NameCase),
|
||||
" #= " + gconv.String(index) + jsonTagStr + ";",
|
||||
" #" + fmt.Sprintf(`// %s`, comment),
|
||||
}
|
||||
}
|
||||
|
||||
func getTplPbEntityContent(tplEntityPath string) string {
|
||||
if tplEntityPath != "" {
|
||||
return gfile.GetContents(tplEntityPath)
|
||||
}
|
||||
return consts.TemplatePbEntityMessageContent
|
||||
}
|
||||
|
||||
// formatCase call gstr.Case* function to convert the s to specified case.
|
||||
func formatCase(str, caseStr string) string {
|
||||
switch gstr.ToLower(caseStr) {
|
||||
case gstr.ToLower("Camel"):
|
||||
return gstr.CaseCamel(str)
|
||||
|
||||
case gstr.ToLower("CamelLower"):
|
||||
return gstr.CaseCamelLower(str)
|
||||
|
||||
case gstr.ToLower("Kebab"):
|
||||
return gstr.CaseKebab(str)
|
||||
|
||||
case gstr.ToLower("KebabScreaming"):
|
||||
return gstr.CaseKebabScreaming(str)
|
||||
|
||||
case gstr.ToLower("Snake"):
|
||||
return gstr.CaseSnake(str)
|
||||
|
||||
case gstr.ToLower("SnakeFirstUpper"):
|
||||
return gstr.CaseSnakeFirstUpper(str)
|
||||
|
||||
case gstr.ToLower("SnakeScreaming"):
|
||||
return gstr.CaseSnakeScreaming(str)
|
||||
|
||||
case "none":
|
||||
return ""
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
func sortFieldKeyForPbEntity(fieldMap map[string]*gdb.TableField) []string {
|
||||
names := make(map[int]string)
|
||||
for _, field := range fieldMap {
|
||||
names[field.Index] = field.Name
|
||||
}
|
||||
var (
|
||||
result = make([]string, len(names))
|
||||
i = 0
|
||||
j = 0
|
||||
)
|
||||
for {
|
||||
if len(names) == 0 {
|
||||
break
|
||||
}
|
||||
if val, ok := names[i]; ok {
|
||||
result[j] = val
|
||||
j++
|
||||
delete(names, i)
|
||||
}
|
||||
i++
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"hotgo/internal/library/hggen/internal/cmd/genservice"
|
||||
)
|
||||
|
||||
type (
|
||||
cGenService = genservice.CGenService
|
||||
)
|
||||
126
server/internal/library/hggen/internal/cmd/cmd_init.go
Normal file
126
server/internal/library/hggen/internal/cmd/cmd_init.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gcmd"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gproc"
|
||||
"github.com/gogf/gf/v2/os/gres"
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
|
||||
"hotgo/internal/library/hggen/internal/utility/allyes"
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
)
|
||||
|
||||
var (
|
||||
Init = cInit{}
|
||||
)
|
||||
|
||||
type cInit struct {
|
||||
g.Meta `name:"init" brief:"{cInitBrief}" eg:"{cInitEg}"`
|
||||
}
|
||||
|
||||
const (
|
||||
cInitRepoPrefix = `github.com/gogf/`
|
||||
cInitMonoRepo = `template-mono`
|
||||
cInitSingleRepo = `template-single`
|
||||
cInitBrief = `create and initialize an empty GoFrame project`
|
||||
cInitEg = `
|
||||
gf init my-project
|
||||
gf init my-mono-repo -m
|
||||
`
|
||||
cInitNameBrief = `
|
||||
name for the project. It will create a folder with NAME in current directory.
|
||||
The NAME will also be the module name for the project.
|
||||
`
|
||||
)
|
||||
|
||||
func init() {
|
||||
gtag.Sets(g.MapStrStr{
|
||||
`cInitBrief`: cInitBrief,
|
||||
`cInitEg`: cInitEg,
|
||||
`cInitNameBrief`: cInitNameBrief,
|
||||
})
|
||||
}
|
||||
|
||||
type cInitInput struct {
|
||||
g.Meta `name:"init"`
|
||||
Name string `name:"NAME" arg:"true" v:"required" brief:"{cInitNameBrief}"`
|
||||
Mono bool `name:"mono" short:"m" brief:"initialize a mono-repo instead a single-repo" orphan:"true"`
|
||||
Update bool `name:"update" short:"u" brief:"update to the latest goframe version" orphan:"true"`
|
||||
}
|
||||
|
||||
type cInitOutput struct{}
|
||||
|
||||
func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err error) {
|
||||
if !gfile.IsEmpty(in.Name) && !allyes.Check() {
|
||||
s := gcmd.Scanf(`the folder "%s" is not empty, files might be overwrote, continue? [y/n]: `, in.Name)
|
||||
if strings.EqualFold(s, "n") {
|
||||
return
|
||||
}
|
||||
}
|
||||
mlog.Print("initializing...")
|
||||
|
||||
// Create project folder and files.
|
||||
var (
|
||||
templateRepoName string
|
||||
)
|
||||
if in.Mono {
|
||||
templateRepoName = cInitMonoRepo
|
||||
} else {
|
||||
templateRepoName = cInitSingleRepo
|
||||
}
|
||||
err = gres.Export(templateRepoName, in.Name, gres.ExportOption{
|
||||
RemovePrefix: templateRepoName,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Replace template name to project name.
|
||||
err = gfile.ReplaceDir(
|
||||
cInitRepoPrefix+templateRepoName,
|
||||
gfile.Basename(gfile.RealPath(in.Name)),
|
||||
in.Name,
|
||||
"*",
|
||||
true,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Update the GoFrame version.
|
||||
if in.Update {
|
||||
mlog.Print("update goframe...")
|
||||
// go get -u github.com/gogf/gf/v2@latest
|
||||
updateCommand := `go get -u github.com/gogf/gf/v2@latest`
|
||||
if in.Name != "." {
|
||||
updateCommand = fmt.Sprintf(`cd %s && %s`, in.Name, updateCommand)
|
||||
}
|
||||
if err = gproc.ShellRun(ctx, updateCommand); err != nil {
|
||||
mlog.Fatal(err)
|
||||
}
|
||||
// go mod tidy
|
||||
gomModTidyCommand := `go mod tidy`
|
||||
if in.Name != "." {
|
||||
gomModTidyCommand = fmt.Sprintf(`cd %s && %s`, in.Name, gomModTidyCommand)
|
||||
}
|
||||
if err = gproc.ShellRun(ctx, gomModTidyCommand); err != nil {
|
||||
mlog.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
mlog.Print("initialization done! ")
|
||||
if !in.Mono {
|
||||
enjoyCommand := `gf run main.go`
|
||||
if in.Name != "." {
|
||||
enjoyCommand = fmt.Sprintf(`cd %s && %s`, in.Name, enjoyCommand)
|
||||
}
|
||||
mlog.Printf(`you can now run "%s" to start your journey, enjoy!`, enjoyCommand)
|
||||
}
|
||||
return
|
||||
}
|
||||
28
server/internal/library/hggen/internal/cmd/cmd_install.go
Normal file
28
server/internal/library/hggen/internal/cmd/cmd_install.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
"hotgo/internal/library/hggen/internal/service"
|
||||
)
|
||||
|
||||
var (
|
||||
Install = cInstall{}
|
||||
)
|
||||
|
||||
type cInstall struct {
|
||||
g.Meta `name:"install" brief:"install gf binary to system (might need root/admin permission)"`
|
||||
}
|
||||
|
||||
type cInstallInput struct {
|
||||
g.Meta `name:"install"`
|
||||
}
|
||||
|
||||
type cInstallOutput struct{}
|
||||
|
||||
func (c cInstall) Index(ctx context.Context, in cInstallInput) (out *cInstallOutput, err error) {
|
||||
err = service.Install.Run(ctx)
|
||||
return
|
||||
}
|
||||
98
server/internal/library/hggen/internal/cmd/cmd_pack.go
Normal file
98
server/internal/library/hggen/internal/cmd/cmd_pack.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gcmd"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gres"
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
|
||||
"hotgo/internal/library/hggen/internal/utility/allyes"
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
)
|
||||
|
||||
var (
|
||||
Pack = cPack{}
|
||||
)
|
||||
|
||||
type cPack struct {
|
||||
g.Meta `name:"pack" usage:"{cPackUsage}" brief:"{cPackBrief}" eg:"{cPackEg}"`
|
||||
}
|
||||
|
||||
const (
|
||||
cPackUsage = `gf pack SRC DST`
|
||||
cPackBrief = `packing any file/directory to a resource file, or a go file`
|
||||
cPackEg = `
|
||||
gf pack public data.bin
|
||||
gf pack public,template data.bin
|
||||
gf pack public,template packed/data.go
|
||||
gf pack public,template,config packed/data.go
|
||||
gf pack public,template,config packed/data.go -n=packed -p=/var/www/my-app
|
||||
gf pack /var/www/public packed/data.go -n=packed
|
||||
`
|
||||
cPackSrcBrief = `source path for packing, which can be multiple source paths.`
|
||||
cPackDstBrief = `
|
||||
destination file path for packed file. if extension of the filename is ".go" and "-n" option is given,
|
||||
it enables packing SRC to go file, or else it packs SRC into a binary file.
|
||||
`
|
||||
cPackNameBrief = `package name for output go file, it's set as its directory name if no name passed`
|
||||
cPackPrefixBrief = `prefix for each file packed into the resource file`
|
||||
cPackKeepPathBrief = `keep the source path from system to resource file, usually for relative path`
|
||||
)
|
||||
|
||||
func init() {
|
||||
gtag.Sets(g.MapStrStr{
|
||||
`cPackUsage`: cPackUsage,
|
||||
`cPackBrief`: cPackBrief,
|
||||
`cPackEg`: cPackEg,
|
||||
`cPackSrcBrief`: cPackSrcBrief,
|
||||
`cPackDstBrief`: cPackDstBrief,
|
||||
`cPackNameBrief`: cPackNameBrief,
|
||||
`cPackPrefixBrief`: cPackPrefixBrief,
|
||||
`cPackKeepPathBrief`: cPackKeepPathBrief,
|
||||
})
|
||||
}
|
||||
|
||||
type cPackInput struct {
|
||||
g.Meta `name:"pack"`
|
||||
Src string `name:"SRC" arg:"true" v:"required" brief:"{cPackSrcBrief}"`
|
||||
Dst string `name:"DST" arg:"true" v:"required" brief:"{cPackDstBrief}"`
|
||||
Name string `name:"name" short:"n" brief:"{cPackNameBrief}"`
|
||||
Prefix string `name:"prefix" short:"p" brief:"{cPackPrefixBrief}"`
|
||||
KeepPath bool `name:"keepPath" short:"k" brief:"{cPackKeepPathBrief}" orphan:"true"`
|
||||
}
|
||||
|
||||
type cPackOutput struct{}
|
||||
|
||||
func (c cPack) Index(ctx context.Context, in cPackInput) (out *cPackOutput, err error) {
|
||||
if gfile.Exists(in.Dst) && gfile.IsDir(in.Dst) {
|
||||
mlog.Fatalf("DST path '%s' cannot be a directory", in.Dst)
|
||||
}
|
||||
if !gfile.IsEmpty(in.Dst) && !allyes.Check() {
|
||||
s := gcmd.Scanf("path '%s' is not empty, files might be overwrote, continue? [y/n]: ", in.Dst)
|
||||
if strings.EqualFold(s, "n") {
|
||||
return
|
||||
}
|
||||
}
|
||||
if in.Name == "" && gfile.ExtName(in.Dst) == "go" {
|
||||
in.Name = gfile.Basename(gfile.Dir(in.Dst))
|
||||
}
|
||||
var option = gres.Option{
|
||||
Prefix: in.Prefix,
|
||||
KeepPath: in.KeepPath,
|
||||
}
|
||||
if in.Name != "" {
|
||||
if err = gres.PackToGoFileWithOption(in.Src, in.Dst, in.Name, option); err != nil {
|
||||
mlog.Fatalf("pack failed: %v", err)
|
||||
}
|
||||
} else {
|
||||
if err = gres.PackToFileWithOption(in.Src, in.Dst, option); err != nil {
|
||||
mlog.Fatalf("pack failed: %v", err)
|
||||
}
|
||||
}
|
||||
mlog.Print("done!")
|
||||
return
|
||||
}
|
||||
166
server/internal/library/hggen/internal/cmd/cmd_run.go
Normal file
166
server/internal/library/hggen/internal/cmd/cmd_run.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gfsnotify"
|
||||
"github.com/gogf/gf/v2/os/gproc"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/os/gtimer"
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
)
|
||||
|
||||
var (
|
||||
Run = cRun{}
|
||||
)
|
||||
|
||||
type cRun struct {
|
||||
g.Meta `name:"run" usage:"{cRunUsage}" brief:"{cRunBrief}" eg:"{cRunEg}" dc:"{cRunDc}"`
|
||||
}
|
||||
|
||||
type cRunApp struct {
|
||||
File string // Go run file name.
|
||||
Path string // Directory storing built binary.
|
||||
Options string // Extra "go run" options.
|
||||
Args string // Custom arguments.
|
||||
}
|
||||
|
||||
const (
|
||||
cRunUsage = `gf run FILE [OPTION]`
|
||||
cRunBrief = `running go codes with hot-compiled-like feature`
|
||||
cRunEg = `
|
||||
gf run main.go
|
||||
gf run main.go --args "server -p 8080"
|
||||
gf run main.go -mod=vendor
|
||||
`
|
||||
cRunDc = `
|
||||
The "run" command is used for running go codes with hot-compiled-like feature,
|
||||
which compiles and runs the go codes asynchronously when codes change.
|
||||
`
|
||||
cRunFileBrief = `building file path.`
|
||||
cRunPathBrief = `output directory path for built binary file. it's "manifest/output" in default`
|
||||
cRunExtraBrief = `the same options as "go run"/"go build" except some options as follows defined`
|
||||
cRunArgsBrief = `custom arguments for your process`
|
||||
)
|
||||
|
||||
var (
|
||||
process *gproc.Process
|
||||
)
|
||||
|
||||
func init() {
|
||||
gtag.Sets(g.MapStrStr{
|
||||
`cRunUsage`: cRunUsage,
|
||||
`cRunBrief`: cRunBrief,
|
||||
`cRunEg`: cRunEg,
|
||||
`cRunDc`: cRunDc,
|
||||
`cRunFileBrief`: cRunFileBrief,
|
||||
`cRunPathBrief`: cRunPathBrief,
|
||||
`cRunExtraBrief`: cRunExtraBrief,
|
||||
`cRunArgsBrief`: cRunArgsBrief,
|
||||
})
|
||||
}
|
||||
|
||||
type (
|
||||
cRunInput struct {
|
||||
g.Meta `name:"run"`
|
||||
File string `name:"FILE" arg:"true" brief:"{cRunFileBrief}" v:"required"`
|
||||
Path string `name:"path" short:"p" brief:"{cRunPathBrief}" d:"./"`
|
||||
Extra string `name:"extra" short:"e" brief:"{cRunExtraBrief}"`
|
||||
Args string `name:"args" short:"a" brief:"{cRunArgsBrief}"`
|
||||
}
|
||||
cRunOutput struct{}
|
||||
)
|
||||
|
||||
func (c cRun) Index(ctx context.Context, in cRunInput) (out *cRunOutput, err error) {
|
||||
// Necessary check.
|
||||
if gproc.SearchBinary("go") == "" {
|
||||
mlog.Fatalf(`command "go" not found in your environment, please install golang first to proceed this command`)
|
||||
}
|
||||
|
||||
app := &cRunApp{
|
||||
File: in.File,
|
||||
Path: in.Path,
|
||||
Options: in.Extra,
|
||||
Args: in.Args,
|
||||
}
|
||||
dirty := gtype.NewBool()
|
||||
_, err = gfsnotify.Add(gfile.RealPath("."), func(event *gfsnotify.Event) {
|
||||
if gfile.ExtName(event.Path) != "go" {
|
||||
return
|
||||
}
|
||||
// Variable `dirty` is used for running the changes only one in one second.
|
||||
if !dirty.Cas(false, true) {
|
||||
return
|
||||
}
|
||||
// With some delay in case of multiple code changes in very short interval.
|
||||
gtimer.SetTimeout(ctx, 1500*gtime.MS, func(ctx context.Context) {
|
||||
defer dirty.Set(false)
|
||||
mlog.Printf(`go file changes: %s`, event.String())
|
||||
app.Run(ctx)
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
mlog.Fatal(err)
|
||||
}
|
||||
go app.Run(ctx)
|
||||
select {}
|
||||
}
|
||||
|
||||
func (app *cRunApp) Run(ctx context.Context) {
|
||||
// Rebuild and run the codes.
|
||||
renamePath := ""
|
||||
mlog.Printf("build: %s", app.File)
|
||||
outputPath := gfile.Join(app.Path, gfile.Name(app.File))
|
||||
if runtime.GOOS == "windows" {
|
||||
outputPath += ".exe"
|
||||
if gfile.Exists(outputPath) {
|
||||
renamePath = outputPath + "~"
|
||||
if err := gfile.Rename(outputPath, renamePath); err != nil {
|
||||
mlog.Print(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// In case of `pipe: too many open files` error.
|
||||
// Build the app.
|
||||
buildCommand := fmt.Sprintf(
|
||||
`go build -o %s %s %s`,
|
||||
outputPath,
|
||||
app.Options,
|
||||
app.File,
|
||||
)
|
||||
mlog.Print(buildCommand)
|
||||
result, err := gproc.ShellExec(ctx, buildCommand)
|
||||
if err != nil {
|
||||
mlog.Printf("build error: \n%s%s", result, err.Error())
|
||||
return
|
||||
}
|
||||
// Kill the old process if build successfully.
|
||||
if process != nil {
|
||||
if err := process.Kill(); err != nil {
|
||||
mlog.Debugf("kill process error: %s", err.Error())
|
||||
//return
|
||||
}
|
||||
}
|
||||
// Run the binary file.
|
||||
runCommand := fmt.Sprintf(`%s %s`, outputPath, app.Args)
|
||||
mlog.Print(runCommand)
|
||||
if runtime.GOOS == "windows" {
|
||||
// Special handling for windows platform.
|
||||
// DO NOT USE "cmd /c" command.
|
||||
process = gproc.NewProcess(runCommand, nil)
|
||||
} else {
|
||||
process = gproc.NewProcessCmd(runCommand, nil)
|
||||
}
|
||||
if pid, err := process.Start(ctx); err != nil {
|
||||
mlog.Printf("build running error: %s", err.Error())
|
||||
} else {
|
||||
mlog.Printf("build running pid: %d", pid)
|
||||
}
|
||||
}
|
||||
168
server/internal/library/hggen/internal/cmd/cmd_tpl.go
Normal file
168
server/internal/library/hggen/internal/cmd/cmd_tpl.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"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/gstr"
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
)
|
||||
|
||||
var (
|
||||
Tpl = cTpl{}
|
||||
)
|
||||
|
||||
type cTpl struct {
|
||||
g.Meta `name:"tpl" brief:"{cTplBrief}" dc:"{cTplDc}"`
|
||||
}
|
||||
|
||||
const (
|
||||
cTplBrief = `template parsing and building commands`
|
||||
cTplDc = `
|
||||
The "tpl" command is used for template parsing and building purpose.
|
||||
It can parse either template file or folder with multiple types of values support,
|
||||
like json/xml/yaml/toml/ini.
|
||||
`
|
||||
cTplParseBrief = `parse either template file or folder with multiple types of values`
|
||||
cTplParseEg = `
|
||||
gf tpl parse -p ./template -v values.json -r
|
||||
gf tpl parse -p ./template -v values.json -n *.tpl -r
|
||||
gf tpl parse -p ./template -v values.json -d '${,}}' -r
|
||||
gf tpl parse -p ./template -v values.json -o ./template.parsed
|
||||
`
|
||||
cTplSupportValuesFilePattern = `*.json,*.xml,*.yaml,*.yml,*.toml,*.ini`
|
||||
)
|
||||
|
||||
type (
|
||||
cTplParseInput struct {
|
||||
g.Meta `name:"parse" brief:"{cTplParseBrief}" eg:"{cTplParseEg}"`
|
||||
Path string `name:"path" short:"p" brief:"template file or folder path" v:"required"`
|
||||
Pattern string `name:"pattern" short:"n" brief:"template file pattern when path is a folder, default is:*" d:"*"`
|
||||
Recursive bool `name:"recursive" short:"c" brief:"recursively parsing files if path is folder, default is:true" d:"true"`
|
||||
Values string `name:"values" short:"v" brief:"template values file/folder, support file types like: json/xml/yaml/toml/ini" v:"required"`
|
||||
Output string `name:"output" short:"o" brief:"output file/folder path"`
|
||||
Delimiters string `name:"delimiters" short:"d" brief:"delimiters for template content parsing, default is:{{,}}" d:"{{,}}"`
|
||||
Replace bool `name:"replace" short:"r" brief:"replace original files" orphan:"true"`
|
||||
}
|
||||
cTplParseOutput struct{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
gtag.Sets(g.MapStrStr{
|
||||
`cTplBrief`: cTplBrief,
|
||||
`cTplDc`: cTplDc,
|
||||
`cTplParseEg`: cTplParseEg,
|
||||
`cTplParseBrief`: cTplParseBrief,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *cTpl) Parse(ctx context.Context, in cTplParseInput) (out *cTplParseOutput, err error) {
|
||||
if in.Output == "" && in.Replace == false {
|
||||
return nil, gerror.New(`parameter output and replace should not be both empty`)
|
||||
}
|
||||
delimiters := gstr.SplitAndTrim(in.Delimiters, ",")
|
||||
mlog.Debugf("delimiters input:%s, parsed:%#v", in.Delimiters, delimiters)
|
||||
if len(delimiters) != 2 {
|
||||
return nil, gerror.Newf(`invalid delimiters: %s`, in.Delimiters)
|
||||
}
|
||||
g.View().SetDelimiters(delimiters[0], delimiters[1])
|
||||
valuesMap, err := c.loadValues(ctx, in.Values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(valuesMap) == 0 {
|
||||
return nil, gerror.Newf(`empty values loaded from values file/folder "%s"`, in.Values)
|
||||
}
|
||||
err = c.parsePath(ctx, valuesMap, in)
|
||||
if err == nil {
|
||||
mlog.Print("done!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cTpl) parsePath(ctx context.Context, values g.Map, in cTplParseInput) (err error) {
|
||||
if !gfile.Exists(in.Path) {
|
||||
return gerror.Newf(`path "%s" does not exist`, in.Path)
|
||||
}
|
||||
var (
|
||||
path string
|
||||
files []string
|
||||
relativePath string
|
||||
outputPath string
|
||||
)
|
||||
path = gfile.RealPath(in.Path)
|
||||
if gfile.IsDir(path) {
|
||||
files, err = gfile.ScanDirFile(path, in.Pattern, in.Recursive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, file := range files {
|
||||
relativePath = gstr.Replace(file, path, "")
|
||||
if in.Output != "" {
|
||||
outputPath = gfile.Join(in.Output, relativePath)
|
||||
}
|
||||
if err = c.parseFile(ctx, file, outputPath, values, in); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if in.Output != "" {
|
||||
outputPath = in.Output
|
||||
}
|
||||
err = c.parseFile(ctx, path, outputPath, values, in)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cTpl) parseFile(ctx context.Context, file string, output string, values g.Map, in cTplParseInput) (err error) {
|
||||
output = gstr.ReplaceByMap(output, g.MapStrStr{
|
||||
`\\`: `\`,
|
||||
`//`: `/`,
|
||||
})
|
||||
content, err := g.View().Parse(ctx, file, values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if output != "" {
|
||||
mlog.Printf(`parse file "%s" to "%s"`, file, output)
|
||||
return gfile.PutContents(output, content)
|
||||
}
|
||||
if in.Replace {
|
||||
mlog.Printf(`parse and replace file "%s"`, file)
|
||||
return gfile.PutContents(file, content)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cTpl) loadValues(ctx context.Context, valuesPath string) (data g.Map, err error) {
|
||||
if !gfile.Exists(valuesPath) {
|
||||
return nil, gerror.Newf(`values file/folder "%s" does not exist`, valuesPath)
|
||||
}
|
||||
var j *gjson.Json
|
||||
if gfile.IsDir(valuesPath) {
|
||||
var valueFiles []string
|
||||
valueFiles, err = gfile.ScanDirFile(valuesPath, cTplSupportValuesFilePattern, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = make(g.Map)
|
||||
for _, file := range valueFiles {
|
||||
if j, err = gjson.Load(file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gutil.MapMerge(data, j.Map())
|
||||
}
|
||||
return
|
||||
}
|
||||
if j, err = gjson.Load(valuesPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = j.Map()
|
||||
return
|
||||
}
|
||||
121
server/internal/library/hggen/internal/cmd/cmd_up.go
Normal file
121
server/internal/library/hggen/internal/cmd/cmd_up.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gproc"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
)
|
||||
|
||||
var (
|
||||
Up = cUp{}
|
||||
)
|
||||
|
||||
type cUp struct {
|
||||
g.Meta `name:"up" brief:"upgrade GoFrame version/tool to latest one in current project" eg:"{cUpEg}" `
|
||||
}
|
||||
|
||||
const (
|
||||
gfPackage = `github.com/gogf/gf/v2`
|
||||
cUpEg = `
|
||||
gf up
|
||||
gf up -a
|
||||
gf up -c
|
||||
gf up -f -c
|
||||
`
|
||||
)
|
||||
|
||||
func init() {
|
||||
gtag.Sets(g.MapStrStr{
|
||||
`cUpEg`: cUpEg,
|
||||
})
|
||||
}
|
||||
|
||||
type cUpInput struct {
|
||||
g.Meta `name:"up" config:"gfcli.up"`
|
||||
All bool `name:"all" short:"a" brief:"upgrade both version and cli, auto fix codes" orphan:"true"`
|
||||
Fix bool `name:"fix" short:"f" brief:"auto fix codes" orphan:"true"`
|
||||
Cli bool `name:"cli" short:"c" brief:"also upgrade CLI tool (not supported yet)" orphan:"true"`
|
||||
}
|
||||
|
||||
type cUpOutput struct{}
|
||||
|
||||
func (c cUp) Index(ctx context.Context, in cUpInput) (out *cUpOutput, err error) {
|
||||
defer mlog.Print(`done!`)
|
||||
|
||||
if in.All {
|
||||
in.Cli = true
|
||||
in.Fix = true
|
||||
}
|
||||
if err = c.doUpgradeVersion(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if in.Fix {
|
||||
if err = c.doAutoFixing(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
//if in.Cli {
|
||||
// if err = c.doUpgradeCLI(ctx); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//}
|
||||
return
|
||||
}
|
||||
|
||||
func (c cUp) doUpgradeVersion(ctx context.Context) (err error) {
|
||||
mlog.Print(`start upgrading version...`)
|
||||
|
||||
var (
|
||||
dir = gfile.Pwd()
|
||||
temp string
|
||||
path = gfile.Join(dir, "go.mod")
|
||||
)
|
||||
for {
|
||||
if gfile.Exists(path) {
|
||||
var packages []string
|
||||
err = gfile.ReadLines(path, func(line string) error {
|
||||
line = gstr.Trim(line)
|
||||
if gstr.HasPrefix(line, gfPackage) {
|
||||
pkg := gstr.Explode(" ", line)[0]
|
||||
packages = append(packages, pkg)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, pkg := range packages {
|
||||
mlog.Printf(`upgrading %s`, pkg)
|
||||
command := fmt.Sprintf(`go get -u %s@latest`, pkg)
|
||||
if err = gproc.ShellRun(ctx, command); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
temp = gfile.Dir(dir)
|
||||
if temp == "" || temp == dir {
|
||||
return
|
||||
}
|
||||
dir = temp
|
||||
path = gfile.Join(dir, "go.mod")
|
||||
}
|
||||
}
|
||||
|
||||
func (c cUp) doUpgradeCLI(ctx context.Context) (err error) {
|
||||
mlog.Print(`start upgrading cli...`)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c cUp) doAutoFixing(ctx context.Context) (err error) {
|
||||
mlog.Print(`start auto fixing...`)
|
||||
err = cFix{}.doFix()
|
||||
return
|
||||
}
|
||||
89
server/internal/library/hggen/internal/cmd/cmd_version.go
Normal file
89
server/internal/library/hggen/internal/cmd/cmd_version.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gbuild"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
)
|
||||
|
||||
var (
|
||||
Version = cVersion{}
|
||||
)
|
||||
|
||||
type cVersion struct {
|
||||
g.Meta `name:"version" brief:"show version information of current binary"`
|
||||
}
|
||||
|
||||
type cVersionInput struct {
|
||||
g.Meta `name:"version"`
|
||||
}
|
||||
|
||||
type cVersionOutput struct{}
|
||||
|
||||
func (c cVersion) Index(ctx context.Context, in cVersionInput) (*cVersionOutput, error) {
|
||||
info := gbuild.Info()
|
||||
if info.Git == "" {
|
||||
info.Git = "none"
|
||||
}
|
||||
mlog.Printf(`GoFrame CLI Tool %s, https://goframe.org`, gf.VERSION)
|
||||
gfVersion, err := c.getGFVersionOfCurrentProject()
|
||||
if err != nil {
|
||||
gfVersion = err.Error()
|
||||
} else {
|
||||
gfVersion = gfVersion + " in current go.mod"
|
||||
}
|
||||
mlog.Printf(`GoFrame Version: %s`, gfVersion)
|
||||
mlog.Printf(`CLI Installed At: %s`, gfile.SelfPath())
|
||||
if info.GoFrame == "" {
|
||||
mlog.Print(`Current is a custom installed version, no installation information.`)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
mlog.Print(gstr.Trim(fmt.Sprintf(`
|
||||
CLI Built Detail:
|
||||
Go Version: %s
|
||||
GF Version: %s
|
||||
Git Commit: %s
|
||||
Build Time: %s
|
||||
`, info.Golang, info.GoFrame, info.Git, info.Time)))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// getGFVersionOfCurrentProject checks and returns the GoFrame version current project using.
|
||||
func (c cVersion) getGFVersionOfCurrentProject() (string, error) {
|
||||
goModPath := gfile.Join(gfile.Pwd(), "go.mod")
|
||||
if gfile.Exists(goModPath) {
|
||||
lines := gstr.SplitAndTrim(gfile.GetContents(goModPath), "\n")
|
||||
for _, line := range lines {
|
||||
line = gstr.Trim(line)
|
||||
// Version 1.
|
||||
match, err := gregex.MatchString(`^github\.com/gogf/gf\s+(.+)$`, line)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(match) <= 1 {
|
||||
// Version > 1.
|
||||
match, err = gregex.MatchString(`^github\.com/gogf/gf/v\d\s+(.+)$`, line)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if len(match) > 1 {
|
||||
return gstr.Trim(match[1]), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", gerror.New("cannot find goframe requirement in go.mod")
|
||||
} else {
|
||||
return "", gerror.New("cannot find go.mod")
|
||||
}
|
||||
}
|
||||
380
server/internal/library/hggen/internal/cmd/gendao/gendao.go
Normal file
380
server/internal/library/hggen/internal/cmd/gendao/gendao.go
Normal file
@@ -0,0 +1,380 @@
|
||||
package gendao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/container/garray"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
)
|
||||
|
||||
const (
|
||||
CGenDaoConfig = `gfcli.hggen.dao`
|
||||
CGenDaoUsage = `gf hggen dao [OPTION]`
|
||||
CGenDaoBrief = `automatically generate go files for dao/do/entity`
|
||||
CGenDaoEg = `
|
||||
gf hggen dao
|
||||
gf hggen dao -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
|
||||
gf hggen dao -p ./model -g user-center -t user,user_detail,user_login
|
||||
gf hggen dao -r user_
|
||||
`
|
||||
|
||||
CGenDaoAd = `
|
||||
CONFIGURATION SUPPORT
|
||||
Options are also supported by configuration file.
|
||||
It's suggested using configuration file instead of command line arguments making producing.
|
||||
The configuration node name is "gfcli.hggen.dao", which also supports multiple databases, for example(config.yaml):
|
||||
gfcli:
|
||||
hggen:
|
||||
dao:
|
||||
- link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
|
||||
tables: "order,products"
|
||||
jsonCase: "CamelLower"
|
||||
- link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary"
|
||||
path: "./my-app"
|
||||
prefix: "primary_"
|
||||
tables: "user, userDetail"
|
||||
`
|
||||
CGenDaoBriefPath = `directory path for generated files`
|
||||
CGenDaoBriefLink = `database configuration, the same as the ORM configuration of GoFrame`
|
||||
CGenDaoBriefTables = `generate models only for given tables, multiple table names separated with ','`
|
||||
CGenDaoBriefTablesEx = `generate models excluding given tables, multiple table names separated with ','`
|
||||
CGenDaoBriefPrefix = `add prefix for all table of specified link/database tables`
|
||||
CGenDaoBriefRemovePrefix = `remove specified prefix of the table, multiple prefix separated with ','`
|
||||
CGenDaoBriefStdTime = `use time.Time from stdlib instead of gtime.Time for generated time/date fields of tables`
|
||||
CGenDaoBriefWithTime = `add created time for auto produced go files`
|
||||
CGenDaoBriefGJsonSupport = `use gJsonSupport to use *gjson.Json instead of string for generated json fields of tables`
|
||||
CGenDaoBriefImportPrefix = `custom import prefix for generated go files`
|
||||
CGenDaoBriefDaoPath = `directory path for storing generated dao files under path`
|
||||
CGenDaoBriefDoPath = `directory path for storing generated do files under path`
|
||||
CGenDaoBriefEntityPath = `directory path for storing generated entity files under path`
|
||||
CGenDaoBriefOverwriteDao = `overwrite all dao files both inside/outside internal folder`
|
||||
CGenDaoBriefModelFile = `custom file name for storing generated model content`
|
||||
CGenDaoBriefModelFileForDao = `custom file name generating model for DAO operations like Where/Data. It's empty in default`
|
||||
CGenDaoBriefDescriptionTag = `add comment to description tag for each field`
|
||||
CGenDaoBriefNoJsonTag = `no json tag will be added for each field`
|
||||
CGenDaoBriefNoModelComment = `no model comment will be added for each field`
|
||||
CGenDaoBriefClear = `delete all generated go files that do not exist in database`
|
||||
CGenDaoBriefGroup = `
|
||||
specifying the configuration group name of database for generated ORM instance,
|
||||
it's not necessary and the default value is "default"
|
||||
`
|
||||
CGenDaoBriefJsonCase = `
|
||||
generated json tag case for model struct, cases are as follows:
|
||||
| Case | Example |
|
||||
|---------------- |--------------------|
|
||||
| Camel | AnyKindOfString |
|
||||
| CamelLower | anyKindOfString | default
|
||||
| Snake | any_kind_of_string |
|
||||
| SnakeScreaming | ANY_KIND_OF_STRING |
|
||||
| SnakeFirstUpper | rgb_code_md5 |
|
||||
| Kebab | any-kind-of-string |
|
||||
| KebabScreaming | ANY-KIND-OF-STRING |
|
||||
`
|
||||
CGenDaoBriefTplDaoIndexPath = `template file path for dao index file`
|
||||
CGenDaoBriefTplDaoInternalPath = `template file path for dao internal file`
|
||||
CGenDaoBriefTplDaoDoPathPath = `template file path for dao do file`
|
||||
CGenDaoBriefTplDaoEntityPath = `template file path for dao entity file`
|
||||
|
||||
tplVarTableName = `{TplTableName}`
|
||||
tplVarTableNameCamelCase = `{TplTableNameCamelCase}`
|
||||
tplVarTableNameCamelLowerCase = `{TplTableNameCamelLowerCase}`
|
||||
tplVarPackageImports = `{TplPackageImports}`
|
||||
tplVarImportPrefix = `{TplImportPrefix}`
|
||||
tplVarStructDefine = `{TplStructDefine}`
|
||||
tplVarColumnDefine = `{TplColumnDefine}`
|
||||
tplVarColumnNames = `{TplColumnNames}`
|
||||
tplVarGroupName = `{TplGroupName}`
|
||||
tplVarDatetimeStr = `{TplDatetimeStr}`
|
||||
tplVarCreatedAtDatetimeStr = `{TplCreatedAtDatetimeStr}`
|
||||
)
|
||||
|
||||
var (
|
||||
createdAt = gtime.Now()
|
||||
)
|
||||
|
||||
func init() {
|
||||
gtag.Sets(g.MapStrStr{
|
||||
`CGenDaoConfig`: CGenDaoConfig,
|
||||
`CGenDaoUsage`: CGenDaoUsage,
|
||||
`CGenDaoBrief`: CGenDaoBrief,
|
||||
`CGenDaoEg`: CGenDaoEg,
|
||||
`CGenDaoAd`: CGenDaoAd,
|
||||
`CGenDaoBriefPath`: CGenDaoBriefPath,
|
||||
`CGenDaoBriefLink`: CGenDaoBriefLink,
|
||||
`CGenDaoBriefTables`: CGenDaoBriefTables,
|
||||
`CGenDaoBriefTablesEx`: CGenDaoBriefTablesEx,
|
||||
`CGenDaoBriefPrefix`: CGenDaoBriefPrefix,
|
||||
`CGenDaoBriefRemovePrefix`: CGenDaoBriefRemovePrefix,
|
||||
`CGenDaoBriefStdTime`: CGenDaoBriefStdTime,
|
||||
`CGenDaoBriefWithTime`: CGenDaoBriefWithTime,
|
||||
`CGenDaoBriefDaoPath`: CGenDaoBriefDaoPath,
|
||||
`CGenDaoBriefDoPath`: CGenDaoBriefDoPath,
|
||||
`CGenDaoBriefEntityPath`: CGenDaoBriefEntityPath,
|
||||
`CGenDaoBriefGJsonSupport`: CGenDaoBriefGJsonSupport,
|
||||
`CGenDaoBriefImportPrefix`: CGenDaoBriefImportPrefix,
|
||||
`CGenDaoBriefOverwriteDao`: CGenDaoBriefOverwriteDao,
|
||||
`CGenDaoBriefModelFile`: CGenDaoBriefModelFile,
|
||||
`CGenDaoBriefModelFileForDao`: CGenDaoBriefModelFileForDao,
|
||||
`CGenDaoBriefDescriptionTag`: CGenDaoBriefDescriptionTag,
|
||||
`CGenDaoBriefNoJsonTag`: CGenDaoBriefNoJsonTag,
|
||||
`CGenDaoBriefNoModelComment`: CGenDaoBriefNoModelComment,
|
||||
`CGenDaoBriefClear`: CGenDaoBriefClear,
|
||||
`CGenDaoBriefGroup`: CGenDaoBriefGroup,
|
||||
`CGenDaoBriefJsonCase`: CGenDaoBriefJsonCase,
|
||||
`CGenDaoBriefTplDaoIndexPath`: CGenDaoBriefTplDaoIndexPath,
|
||||
`CGenDaoBriefTplDaoInternalPath`: CGenDaoBriefTplDaoInternalPath,
|
||||
`CGenDaoBriefTplDaoDoPathPath`: CGenDaoBriefTplDaoDoPathPath,
|
||||
`CGenDaoBriefTplDaoEntityPath`: CGenDaoBriefTplDaoEntityPath,
|
||||
})
|
||||
}
|
||||
|
||||
type (
|
||||
CGenDao struct{}
|
||||
CGenDaoInput struct {
|
||||
g.Meta `name:"dao" config:"{CGenDaoConfig}" usage:"{CGenDaoUsage}" brief:"{CGenDaoBrief}" eg:"{CGenDaoEg}" ad:"{CGenDaoAd}"`
|
||||
Path string `name:"path" short:"p" brief:"{CGenDaoBriefPath}" d:"internal"`
|
||||
Link string `name:"link" short:"l" brief:"{CGenDaoBriefLink}"`
|
||||
Tables string `name:"tables" short:"t" brief:"{CGenDaoBriefTables}"`
|
||||
TablesEx string `name:"tablesEx" short:"x" brief:"{CGenDaoBriefTablesEx}"`
|
||||
Group string `name:"group" short:"g" brief:"{CGenDaoBriefGroup}" d:"default"`
|
||||
Prefix string `name:"prefix" short:"f" brief:"{CGenDaoBriefPrefix}"`
|
||||
RemovePrefix string `name:"removePrefix" short:"r" brief:"{CGenDaoBriefRemovePrefix}"`
|
||||
JsonCase string `name:"jsonCase" short:"j" brief:"{CGenDaoBriefJsonCase}" d:"CamelLower"`
|
||||
ImportPrefix string `name:"importPrefix" short:"i" brief:"{CGenDaoBriefImportPrefix}"`
|
||||
DaoPath string `name:"daoPath" short:"d" brief:"{CGenDaoBriefDaoPath}" d:"dao"`
|
||||
DoPath string `name:"doPath" short:"o" brief:"{CGenDaoBriefDoPath}" d:"model/do"`
|
||||
EntityPath string `name:"entityPath" short:"e" brief:"{CGenDaoBriefEntityPath}" d:"model/entity"`
|
||||
TplDaoIndexPath string `name:"tplDaoIndexPath" short:"t1" brief:"{CGenDaoBriefTplDaoIndexPath}"`
|
||||
TplDaoInternalPath string `name:"tplDaoInternalPath" short:"t2" brief:"{CGenDaoBriefTplDaoInternalPath}"`
|
||||
TplDaoDoPath string `name:"tplDaoDoPath" short:"t3" brief:"{CGenDaoBriefTplDaoDoPathPath}"`
|
||||
TplDaoEntityPath string `name:"tplDaoEntityPath" short:"t4" brief:"{CGenDaoBriefTplDaoEntityPath}"`
|
||||
StdTime bool `name:"stdTime" short:"s" brief:"{CGenDaoBriefStdTime}" orphan:"true"`
|
||||
WithTime bool `name:"withTime" short:"w" brief:"{CGenDaoBriefWithTime}" orphan:"true"`
|
||||
GJsonSupport bool `name:"gJsonSupport" short:"n" brief:"{CGenDaoBriefGJsonSupport}" orphan:"true"`
|
||||
OverwriteDao bool `name:"overwriteDao" short:"v" brief:"{CGenDaoBriefOverwriteDao}" orphan:"true"`
|
||||
DescriptionTag bool `name:"descriptionTag" short:"c" brief:"{CGenDaoBriefDescriptionTag}" orphan:"true"`
|
||||
NoJsonTag bool `name:"noJsonTag" short:"k" brief:"{CGenDaoBriefNoJsonTag}" orphan:"true"`
|
||||
NoModelComment bool `name:"noModelComment" short:"m" brief:"{CGenDaoBriefNoModelComment}" orphan:"true"`
|
||||
Clear bool `name:"clear" short:"a" brief:"{CGenDaoBriefClear}" orphan:"true"`
|
||||
}
|
||||
CGenDaoOutput struct{}
|
||||
|
||||
CGenDaoInternalInput struct {
|
||||
CGenDaoInput
|
||||
DB gdb.DB
|
||||
TableNames []string
|
||||
NewTableNames []string
|
||||
ModName string // Module name of current golang project, which is used for import purpose.
|
||||
}
|
||||
)
|
||||
|
||||
func (c CGenDao) Dao(ctx context.Context, in CGenDaoInput) (out *CGenDaoOutput, err error) {
|
||||
g.Log().Warningf(ctx, "g.Cfg().Available(ctx):%v", g.Cfg().Available(ctx))
|
||||
if g.Cfg().Available(ctx) {
|
||||
v := g.Cfg().MustGet(ctx, CGenDaoConfig)
|
||||
if v.IsSlice() {
|
||||
for i := 0; i < len(v.Interfaces()); i++ {
|
||||
doGenDaoForArray(ctx, i, in)
|
||||
}
|
||||
} else {
|
||||
doGenDaoForArray(ctx, -1, in)
|
||||
}
|
||||
} else {
|
||||
doGenDaoForArray(ctx, -1, in)
|
||||
}
|
||||
mlog.Print("done!")
|
||||
return
|
||||
}
|
||||
|
||||
func DoGenDaoForArray(ctx context.Context, in CGenDaoInput) {
|
||||
doGenDaoForArray(ctx, -1, in)
|
||||
}
|
||||
|
||||
// doGenDaoForArray implements the "hggen dao" command for configuration array.
|
||||
func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
|
||||
var (
|
||||
err error
|
||||
db gdb.DB
|
||||
modName string // Go module name, eg: github.com/gogf/gf.
|
||||
)
|
||||
if index >= 0 {
|
||||
err = g.Cfg().MustGet(
|
||||
ctx,
|
||||
fmt.Sprintf(`%s.%d`, CGenDaoConfig, index),
|
||||
).Scan(&in)
|
||||
if err != nil {
|
||||
mlog.Fatalf(`invalid configuration of "%s": %+v`, CGenDaoConfig, err)
|
||||
}
|
||||
}
|
||||
if dirRealPath := gfile.RealPath(in.Path); dirRealPath == "" {
|
||||
mlog.Fatalf(`path "%s" does not exist`, in.Path)
|
||||
}
|
||||
removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",")
|
||||
if in.ImportPrefix == "" {
|
||||
if !gfile.Exists("go.mod") {
|
||||
mlog.Fatal("go.mod does not exist in current working directory")
|
||||
}
|
||||
var (
|
||||
goModContent = gfile.GetContents("go.mod")
|
||||
match, _ = gregex.MatchString(`^module\s+(.+)\s*`, goModContent)
|
||||
)
|
||||
if len(match) > 1 {
|
||||
modName = gstr.Trim(match[1])
|
||||
} else {
|
||||
mlog.Fatal("module name does not found in go.mod")
|
||||
}
|
||||
}
|
||||
|
||||
// It uses user passed database configuration.
|
||||
if in.Link != "" {
|
||||
var tempGroup = gtime.TimestampNanoStr()
|
||||
gdb.AddConfigNode(tempGroup, gdb.ConfigNode{
|
||||
Link: in.Link,
|
||||
})
|
||||
if db, err = gdb.Instance(tempGroup); err != nil {
|
||||
mlog.Fatalf(`database initialization failed: %+v`, err)
|
||||
}
|
||||
} else {
|
||||
db = g.DB(in.Group)
|
||||
}
|
||||
if db == nil {
|
||||
mlog.Fatal(`database initialization failed, may be invalid database configuration`)
|
||||
}
|
||||
|
||||
var tableNames []string
|
||||
if in.Tables != "" {
|
||||
tableNames = gstr.SplitAndTrim(in.Tables, ",")
|
||||
} else {
|
||||
tableNames, err = db.Tables(context.TODO())
|
||||
if err != nil {
|
||||
mlog.Fatalf("fetching tables failed: %+v", err)
|
||||
}
|
||||
}
|
||||
// Table excluding.
|
||||
if in.TablesEx != "" {
|
||||
array := garray.NewStrArrayFrom(tableNames)
|
||||
for _, v := range gstr.SplitAndTrim(in.TablesEx, ",") {
|
||||
array.RemoveValue(v)
|
||||
}
|
||||
tableNames = array.Slice()
|
||||
}
|
||||
|
||||
// Generating dao & model go files one by one according to given table name.
|
||||
newTableNames := make([]string, len(tableNames))
|
||||
for i, tableName := range tableNames {
|
||||
newTableName := tableName
|
||||
for _, v := range removePrefixArray {
|
||||
newTableName = gstr.TrimLeftStr(newTableName, v, 1)
|
||||
}
|
||||
newTableName = in.Prefix + newTableName
|
||||
newTableNames[i] = newTableName
|
||||
}
|
||||
// Dao: index and internal.
|
||||
generateDao(ctx, CGenDaoInternalInput{
|
||||
CGenDaoInput: in,
|
||||
DB: db,
|
||||
TableNames: tableNames,
|
||||
NewTableNames: newTableNames,
|
||||
ModName: modName,
|
||||
})
|
||||
// Do.
|
||||
generateDo(ctx, CGenDaoInternalInput{
|
||||
CGenDaoInput: in,
|
||||
DB: db,
|
||||
TableNames: tableNames,
|
||||
NewTableNames: newTableNames,
|
||||
ModName: modName,
|
||||
})
|
||||
// Entity.
|
||||
generateEntity(ctx, CGenDaoInternalInput{
|
||||
CGenDaoInput: in,
|
||||
DB: db,
|
||||
TableNames: tableNames,
|
||||
NewTableNames: newTableNames,
|
||||
ModName: modName,
|
||||
})
|
||||
}
|
||||
|
||||
func getImportPartContent(source string, isDo bool) string {
|
||||
var (
|
||||
packageImportsArray = garray.NewStrArray()
|
||||
)
|
||||
|
||||
if isDo {
|
||||
packageImportsArray.Append(`"github.com/gogf/gf/v2/frame/g"`)
|
||||
}
|
||||
|
||||
// Time package recognition.
|
||||
if strings.Contains(source, "gtime.Time") {
|
||||
packageImportsArray.Append(`"github.com/gogf/gf/v2/os/gtime"`)
|
||||
} else if strings.Contains(source, "time.Time") {
|
||||
packageImportsArray.Append(`"time"`)
|
||||
}
|
||||
|
||||
// Json type.
|
||||
if strings.Contains(source, "gjson.Json") {
|
||||
packageImportsArray.Append(`"github.com/gogf/gf/v2/encoding/gjson"`)
|
||||
}
|
||||
|
||||
// Generate and write content to golang file.
|
||||
packageImportsStr := ""
|
||||
if packageImportsArray.Len() > 0 {
|
||||
packageImportsStr = fmt.Sprintf("import(\n%s\n)", packageImportsArray.Join("\n"))
|
||||
}
|
||||
return packageImportsStr
|
||||
}
|
||||
|
||||
func replaceDefaultVar(in CGenDaoInternalInput, origin string) string {
|
||||
var tplCreatedAtDatetimeStr string
|
||||
var tplDatetimeStr string = createdAt.String()
|
||||
if in.WithTime {
|
||||
tplCreatedAtDatetimeStr = fmt.Sprintf(`Created at %s`, tplDatetimeStr)
|
||||
}
|
||||
return gstr.ReplaceByMap(origin, g.MapStrStr{
|
||||
tplVarDatetimeStr: tplDatetimeStr,
|
||||
tplVarCreatedAtDatetimeStr: tplCreatedAtDatetimeStr,
|
||||
})
|
||||
}
|
||||
|
||||
func sortFieldKeyForDao(fieldMap map[string]*gdb.TableField) []string {
|
||||
names := make(map[int]string)
|
||||
for _, field := range fieldMap {
|
||||
names[field.Index] = field.Name
|
||||
}
|
||||
var (
|
||||
i = 0
|
||||
j = 0
|
||||
result = make([]string, len(names))
|
||||
)
|
||||
for {
|
||||
if len(names) == 0 {
|
||||
break
|
||||
}
|
||||
if val, ok := names[i]; ok {
|
||||
result[j] = val
|
||||
j++
|
||||
delete(names, i)
|
||||
}
|
||||
i++
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func getTemplateFromPathOrDefault(filePath string, def string) string {
|
||||
if filePath != "" {
|
||||
if contents := gfile.GetContents(filePath); contents != "" {
|
||||
return contents
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package gendao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
"hotgo/internal/library/hggen/internal/utility/utils"
|
||||
)
|
||||
|
||||
func doClear(ctx context.Context, dirPath string) {
|
||||
files, err := gfile.ScanDirFile(dirPath, "*.go", true)
|
||||
if err != nil {
|
||||
mlog.Fatal(err)
|
||||
}
|
||||
for _, file := range files {
|
||||
if utils.IsFileDoNotEdit(file) {
|
||||
if err = gfile.Remove(file); err != nil {
|
||||
mlog.Print(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
228
server/internal/library/hggen/internal/cmd/gendao/gendao_dao.go
Normal file
228
server/internal/library/hggen/internal/cmd/gendao/gendao_dao.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package gendao
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"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"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
|
||||
"hotgo/internal/library/hggen/internal/consts"
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
"hotgo/internal/library/hggen/internal/utility/utils"
|
||||
)
|
||||
|
||||
func generateDao(ctx context.Context, in CGenDaoInternalInput) {
|
||||
var (
|
||||
dirPathDao = gfile.Join(in.Path, in.DaoPath)
|
||||
dirPathDaoInternal = gfile.Join(dirPathDao, "internal")
|
||||
)
|
||||
if in.Clear {
|
||||
doClear(ctx, dirPathDao)
|
||||
}
|
||||
for i := 0; i < len(in.TableNames); i++ {
|
||||
generateDaoSingle(ctx, generateDaoSingleInput{
|
||||
CGenDaoInternalInput: in,
|
||||
TableName: in.TableNames[i],
|
||||
NewTableName: in.NewTableNames[i],
|
||||
DirPathDao: dirPathDao,
|
||||
DirPathDaoInternal: dirPathDaoInternal,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type generateDaoSingleInput struct {
|
||||
CGenDaoInternalInput
|
||||
TableName string // TableName specifies the table name of the table.
|
||||
NewTableName string // NewTableName specifies the prefix-stripped name of the table.
|
||||
DirPathDao string
|
||||
DirPathDaoInternal string
|
||||
}
|
||||
|
||||
// generateDaoSingle generates the dao and model content of given table.
|
||||
func generateDaoSingle(ctx context.Context, in generateDaoSingleInput) {
|
||||
// Generating table data preparing.
|
||||
fieldMap, err := in.DB.TableFields(ctx, in.TableName)
|
||||
if err != nil {
|
||||
mlog.Fatalf(`fetching tables fields failed for table "%s": %+v`, in.TableName, err)
|
||||
}
|
||||
var (
|
||||
dirRealPath = gfile.RealPath(in.Path)
|
||||
tableNameCamelCase = gstr.CaseCamel(in.NewTableName)
|
||||
tableNameCamelLowerCase = gstr.CaseCamelLower(in.NewTableName)
|
||||
tableNameSnakeCase = gstr.CaseSnake(in.NewTableName)
|
||||
importPrefix = in.ImportPrefix
|
||||
)
|
||||
if importPrefix == "" {
|
||||
if dirRealPath == "" {
|
||||
dirRealPath = in.Path
|
||||
importPrefix = dirRealPath
|
||||
importPrefix = gstr.Trim(dirRealPath, "./")
|
||||
} else {
|
||||
importPrefix = gstr.Replace(dirRealPath, gfile.Pwd(), "")
|
||||
}
|
||||
importPrefix = gstr.Replace(importPrefix, gfile.Separator, "/")
|
||||
importPrefix = gstr.Join(g.SliceStr{in.ModName, importPrefix, in.DaoPath}, "/")
|
||||
importPrefix, _ = gregex.ReplaceString(`\/{2,}`, `/`, gstr.Trim(importPrefix, "/"))
|
||||
} else {
|
||||
importPrefix = gstr.Join(g.SliceStr{importPrefix, in.DaoPath}, "/")
|
||||
}
|
||||
|
||||
fileName := gstr.Trim(tableNameSnakeCase, "-_.")
|
||||
if len(fileName) > 5 && fileName[len(fileName)-5:] == "_test" {
|
||||
// Add suffix to avoid the table name which contains "_test",
|
||||
// which would make the go file a testing file.
|
||||
fileName += "_table"
|
||||
}
|
||||
|
||||
// dao - index
|
||||
generateDaoIndex(generateDaoIndexInput{
|
||||
generateDaoSingleInput: in,
|
||||
TableNameCamelCase: tableNameCamelCase,
|
||||
TableNameCamelLowerCase: tableNameCamelLowerCase,
|
||||
ImportPrefix: importPrefix,
|
||||
FileName: fileName,
|
||||
})
|
||||
|
||||
// dao - internal
|
||||
generateDaoInternal(generateDaoInternalInput{
|
||||
generateDaoSingleInput: in,
|
||||
TableNameCamelCase: tableNameCamelCase,
|
||||
TableNameCamelLowerCase: tableNameCamelLowerCase,
|
||||
ImportPrefix: importPrefix,
|
||||
FileName: fileName,
|
||||
FieldMap: fieldMap,
|
||||
})
|
||||
}
|
||||
|
||||
type generateDaoIndexInput struct {
|
||||
generateDaoSingleInput
|
||||
TableNameCamelCase string
|
||||
TableNameCamelLowerCase string
|
||||
ImportPrefix string
|
||||
FileName string
|
||||
}
|
||||
|
||||
func generateDaoIndex(in generateDaoIndexInput) {
|
||||
path := gfile.Join(in.DirPathDao, in.FileName+".go")
|
||||
if in.OverwriteDao || !gfile.Exists(path) {
|
||||
indexContent := gstr.ReplaceByMap(
|
||||
getTemplateFromPathOrDefault(in.TplDaoIndexPath, consts.TemplateGenDaoIndexContent),
|
||||
g.MapStrStr{
|
||||
tplVarImportPrefix: in.ImportPrefix,
|
||||
tplVarTableName: in.TableName,
|
||||
tplVarTableNameCamelCase: in.TableNameCamelCase,
|
||||
tplVarTableNameCamelLowerCase: in.TableNameCamelLowerCase,
|
||||
})
|
||||
indexContent = replaceDefaultVar(in.CGenDaoInternalInput, indexContent)
|
||||
if err := gfile.PutContents(path, strings.TrimSpace(indexContent)); err != nil {
|
||||
mlog.Fatalf("writing content to '%s' failed: %v", path, err)
|
||||
} else {
|
||||
utils.GoFmt(path)
|
||||
mlog.Print("generated:", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type generateDaoInternalInput struct {
|
||||
generateDaoSingleInput
|
||||
TableNameCamelCase string
|
||||
TableNameCamelLowerCase string
|
||||
ImportPrefix string
|
||||
FileName string
|
||||
FieldMap map[string]*gdb.TableField
|
||||
}
|
||||
|
||||
func generateDaoInternal(in generateDaoInternalInput) {
|
||||
path := gfile.Join(in.DirPathDaoInternal, in.FileName+".go")
|
||||
modelContent := gstr.ReplaceByMap(
|
||||
getTemplateFromPathOrDefault(in.TplDaoInternalPath, consts.TemplateGenDaoInternalContent),
|
||||
g.MapStrStr{
|
||||
tplVarImportPrefix: in.ImportPrefix,
|
||||
tplVarTableName: in.TableName,
|
||||
tplVarGroupName: in.Group,
|
||||
tplVarTableNameCamelCase: in.TableNameCamelCase,
|
||||
tplVarTableNameCamelLowerCase: in.TableNameCamelLowerCase,
|
||||
tplVarColumnDefine: gstr.Trim(generateColumnDefinitionForDao(in.FieldMap)),
|
||||
tplVarColumnNames: gstr.Trim(generateColumnNamesForDao(in.FieldMap)),
|
||||
})
|
||||
modelContent = replaceDefaultVar(in.CGenDaoInternalInput, modelContent)
|
||||
if err := gfile.PutContents(path, strings.TrimSpace(modelContent)); err != nil {
|
||||
mlog.Fatalf("writing content to '%s' failed: %v", path, err)
|
||||
} else {
|
||||
utils.GoFmt(path)
|
||||
mlog.Print("generated:", path)
|
||||
}
|
||||
}
|
||||
|
||||
// generateColumnNamesForDao generates and returns the column names assignment content of column struct
|
||||
// for specified table.
|
||||
func generateColumnNamesForDao(fieldMap map[string]*gdb.TableField) string {
|
||||
var (
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
array = make([][]string, len(fieldMap))
|
||||
names = sortFieldKeyForDao(fieldMap)
|
||||
)
|
||||
for index, name := range names {
|
||||
field := fieldMap[name]
|
||||
array[index] = []string{
|
||||
" #" + gstr.CaseCamel(field.Name) + ":",
|
||||
fmt.Sprintf(` #"%s",`, field.Name),
|
||||
}
|
||||
}
|
||||
tw := tablewriter.NewWriter(buffer)
|
||||
tw.SetBorder(false)
|
||||
tw.SetRowLine(false)
|
||||
tw.SetAutoWrapText(false)
|
||||
tw.SetColumnSeparator("")
|
||||
tw.AppendBulk(array)
|
||||
tw.Render()
|
||||
namesContent := buffer.String()
|
||||
// Let's do this hack of table writer for indent!
|
||||
namesContent = gstr.Replace(namesContent, " #", "")
|
||||
buffer.Reset()
|
||||
buffer.WriteString(namesContent)
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// generateColumnDefinitionForDao generates and returns the column names definition for specified table.
|
||||
func generateColumnDefinitionForDao(fieldMap map[string]*gdb.TableField) string {
|
||||
var (
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
array = make([][]string, len(fieldMap))
|
||||
names = sortFieldKeyForDao(fieldMap)
|
||||
)
|
||||
for index, name := range names {
|
||||
var (
|
||||
field = fieldMap[name]
|
||||
comment = gstr.Trim(gstr.ReplaceByArray(field.Comment, g.SliceStr{
|
||||
"\n", " ",
|
||||
"\r", " ",
|
||||
}))
|
||||
)
|
||||
array[index] = []string{
|
||||
" #" + gstr.CaseCamel(field.Name),
|
||||
" # " + "string",
|
||||
" #" + fmt.Sprintf(`// %s`, comment),
|
||||
}
|
||||
}
|
||||
tw := tablewriter.NewWriter(buffer)
|
||||
tw.SetBorder(false)
|
||||
tw.SetRowLine(false)
|
||||
tw.SetAutoWrapText(false)
|
||||
tw.SetColumnSeparator("")
|
||||
tw.AppendBulk(array)
|
||||
tw.Render()
|
||||
defineContent := buffer.String()
|
||||
// Let's do this hack of table writer for indent!
|
||||
defineContent = gstr.Replace(defineContent, " #", "")
|
||||
buffer.Reset()
|
||||
buffer.WriteString(defineContent)
|
||||
return buffer.String()
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package gendao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"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/library/hggen/internal/consts"
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
"hotgo/internal/library/hggen/internal/utility/utils"
|
||||
)
|
||||
|
||||
func generateDo(ctx context.Context, in CGenDaoInternalInput) {
|
||||
var dirPathDo = gfile.Join(in.Path, in.DoPath)
|
||||
if in.Clear {
|
||||
doClear(ctx, dirPathDo)
|
||||
}
|
||||
in.NoJsonTag = true
|
||||
in.DescriptionTag = false
|
||||
in.NoModelComment = false
|
||||
// Model content.
|
||||
for i, tableName := range in.TableNames {
|
||||
fieldMap, err := in.DB.TableFields(ctx, tableName)
|
||||
if err != nil {
|
||||
mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", tableName, err)
|
||||
}
|
||||
var (
|
||||
newTableName = in.NewTableNames[i]
|
||||
doFilePath = gfile.Join(dirPathDo, gstr.CaseSnake(newTableName)+".go")
|
||||
structDefinition = generateStructDefinition(ctx, generateStructDefinitionInput{
|
||||
CGenDaoInternalInput: in,
|
||||
TableName: tableName,
|
||||
StructName: gstr.CaseCamel(newTableName),
|
||||
FieldMap: fieldMap,
|
||||
IsDo: true,
|
||||
})
|
||||
)
|
||||
// replace all types to interface{}.
|
||||
structDefinition, _ = gregex.ReplaceStringFuncMatch(
|
||||
"([A-Z]\\w*?)\\s+([\\w\\*\\.]+?)\\s+(//)",
|
||||
structDefinition,
|
||||
func(match []string) string {
|
||||
// If the type is already a pointer/slice/map, it does nothing.
|
||||
if !gstr.HasPrefix(match[2], "*") && !gstr.HasPrefix(match[2], "[]") && !gstr.HasPrefix(match[2], "map") {
|
||||
return fmt.Sprintf(`%s interface{} %s`, match[1], match[3])
|
||||
}
|
||||
return match[0]
|
||||
},
|
||||
)
|
||||
modelContent := generateDoContent(
|
||||
in,
|
||||
tableName,
|
||||
gstr.CaseCamel(newTableName),
|
||||
structDefinition,
|
||||
)
|
||||
err = gfile.PutContents(doFilePath, strings.TrimSpace(modelContent))
|
||||
if err != nil {
|
||||
mlog.Fatalf(`writing content to "%s" failed: %v`, doFilePath, err)
|
||||
} else {
|
||||
utils.GoFmt(doFilePath)
|
||||
mlog.Print("generated:", doFilePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func generateDoContent(in CGenDaoInternalInput, tableName, tableNameCamelCase, structDefine string) string {
|
||||
doContent := gstr.ReplaceByMap(
|
||||
getTemplateFromPathOrDefault(in.TplDaoDoPath, consts.TemplateGenDaoDoContent),
|
||||
g.MapStrStr{
|
||||
tplVarTableName: tableName,
|
||||
tplVarPackageImports: getImportPartContent(structDefine, true),
|
||||
tplVarTableNameCamelCase: tableNameCamelCase,
|
||||
tplVarStructDefine: structDefine,
|
||||
})
|
||||
doContent = replaceDefaultVar(in, doContent)
|
||||
return doContent
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package gendao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
|
||||
"hotgo/internal/library/hggen/internal/consts"
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
"hotgo/internal/library/hggen/internal/utility/utils"
|
||||
)
|
||||
|
||||
func generateEntity(ctx context.Context, in CGenDaoInternalInput) {
|
||||
var dirPathEntity = gfile.Join(in.Path, in.EntityPath)
|
||||
if in.Clear {
|
||||
doClear(ctx, dirPathEntity)
|
||||
}
|
||||
// Model content.
|
||||
for i, tableName := range in.TableNames {
|
||||
fieldMap, err := in.DB.TableFields(ctx, tableName)
|
||||
if err != nil {
|
||||
mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", tableName, err)
|
||||
}
|
||||
var (
|
||||
newTableName = in.NewTableNames[i]
|
||||
entityFilePath = gfile.Join(dirPathEntity, gstr.CaseSnake(newTableName)+".go")
|
||||
entityContent = generateEntityContent(
|
||||
in,
|
||||
newTableName,
|
||||
gstr.CaseCamel(newTableName),
|
||||
generateStructDefinition(ctx, generateStructDefinitionInput{
|
||||
CGenDaoInternalInput: in,
|
||||
TableName: tableName,
|
||||
StructName: gstr.CaseCamel(newTableName),
|
||||
FieldMap: fieldMap,
|
||||
IsDo: false,
|
||||
}),
|
||||
)
|
||||
)
|
||||
err = gfile.PutContents(entityFilePath, strings.TrimSpace(entityContent))
|
||||
if err != nil {
|
||||
mlog.Fatalf("writing content to '%s' failed: %v", entityFilePath, err)
|
||||
} else {
|
||||
utils.GoFmt(entityFilePath)
|
||||
mlog.Print("generated:", entityFilePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func generateEntityContent(in CGenDaoInternalInput, tableName, tableNameCamelCase, structDefine string) string {
|
||||
entityContent := gstr.ReplaceByMap(
|
||||
getTemplateFromPathOrDefault(in.TplDaoEntityPath, consts.TemplateGenDaoEntityContent),
|
||||
g.MapStrStr{
|
||||
tplVarTableName: tableName,
|
||||
tplVarPackageImports: getImportPartContent(structDefine, false),
|
||||
tplVarTableNameCamelCase: tableNameCamelCase,
|
||||
tplVarStructDefine: structDefine,
|
||||
})
|
||||
entityContent = replaceDefaultVar(in, entityContent)
|
||||
return entityContent
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package gendao
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
type generateStructDefinitionInput struct {
|
||||
CGenDaoInternalInput
|
||||
TableName string // Table name.
|
||||
StructName string // Struct name.
|
||||
FieldMap map[string]*gdb.TableField // Table field map.
|
||||
IsDo bool // Is generating DTO struct.
|
||||
}
|
||||
|
||||
func generateStructDefinition(ctx context.Context, in generateStructDefinitionInput) string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
array := make([][]string, len(in.FieldMap))
|
||||
names := sortFieldKeyForDao(in.FieldMap)
|
||||
for index, name := range names {
|
||||
field := in.FieldMap[name]
|
||||
array[index] = generateStructFieldDefinition(ctx, field, in)
|
||||
}
|
||||
tw := tablewriter.NewWriter(buffer)
|
||||
tw.SetBorder(false)
|
||||
tw.SetRowLine(false)
|
||||
tw.SetAutoWrapText(false)
|
||||
tw.SetColumnSeparator("")
|
||||
tw.AppendBulk(array)
|
||||
tw.Render()
|
||||
stContent := buffer.String()
|
||||
// Let's do this hack of table writer for indent!
|
||||
stContent = gstr.Replace(stContent, " #", "")
|
||||
stContent = gstr.Replace(stContent, "` ", "`")
|
||||
stContent = gstr.Replace(stContent, "``", "")
|
||||
buffer.Reset()
|
||||
buffer.WriteString(fmt.Sprintf("type %s struct {\n", in.StructName))
|
||||
if in.IsDo {
|
||||
buffer.WriteString(fmt.Sprintf("g.Meta `orm:\"table:%s, do:true\"`\n", in.TableName))
|
||||
}
|
||||
buffer.WriteString(stContent)
|
||||
buffer.WriteString("}")
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// generateStructFieldForModel generates and returns the attribute definition for specified field.
|
||||
func generateStructFieldDefinition(
|
||||
ctx context.Context, field *gdb.TableField, in generateStructDefinitionInput,
|
||||
) []string {
|
||||
var (
|
||||
err error
|
||||
typeName string
|
||||
jsonTag = getJsonTagFromCase(field.Name, in.JsonCase)
|
||||
)
|
||||
typeName, err = in.DB.CheckLocalTypeForField(ctx, field.Type, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
switch typeName {
|
||||
case gdb.LocalTypeDate, gdb.LocalTypeDatetime:
|
||||
if in.StdTime {
|
||||
typeName = "time.Time"
|
||||
} else {
|
||||
typeName = "*gtime.Time"
|
||||
}
|
||||
|
||||
case gdb.LocalTypeInt64Bytes:
|
||||
typeName = "int64"
|
||||
|
||||
case gdb.LocalTypeUint64Bytes:
|
||||
typeName = "uint64"
|
||||
|
||||
// Special type handle.
|
||||
case gdb.LocalTypeJson, gdb.LocalTypeJsonb:
|
||||
if in.GJsonSupport {
|
||||
typeName = "*gjson.Json"
|
||||
} else {
|
||||
typeName = "string"
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
tagKey = "`"
|
||||
result = []string{
|
||||
" #" + gstr.CaseCamel(field.Name),
|
||||
" #" + typeName,
|
||||
}
|
||||
descriptionTag = gstr.Replace(formatComment(field.Comment), `"`, `\"`)
|
||||
)
|
||||
|
||||
result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, jsonTag))
|
||||
result = append(result, " #"+fmt.Sprintf(`description:"%s"`+tagKey, descriptionTag))
|
||||
result = append(result, " #"+fmt.Sprintf(`// %s`, formatComment(field.Comment)))
|
||||
|
||||
for k, v := range result {
|
||||
if in.NoJsonTag {
|
||||
v, _ = gregex.ReplaceString(`json:".+"`, ``, v)
|
||||
}
|
||||
if !in.DescriptionTag {
|
||||
v, _ = gregex.ReplaceString(`description:".*"`, ``, v)
|
||||
}
|
||||
if in.NoModelComment {
|
||||
v, _ = gregex.ReplaceString(`//.+`, ``, v)
|
||||
}
|
||||
result[k] = v
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// formatComment formats the comment string to fit the golang code without any lines.
|
||||
func formatComment(comment string) string {
|
||||
comment = gstr.ReplaceByArray(comment, g.SliceStr{
|
||||
"\n", " ",
|
||||
"\r", " ",
|
||||
})
|
||||
comment = gstr.Replace(comment, `\n`, " ")
|
||||
comment = gstr.Trim(comment)
|
||||
return comment
|
||||
}
|
||||
|
||||
// getJsonTagFromCase call gstr.Case* function to convert the s to specified case.
|
||||
func getJsonTagFromCase(str, caseStr string) string {
|
||||
switch gstr.ToLower(caseStr) {
|
||||
case gstr.ToLower("Camel"):
|
||||
return gstr.CaseCamel(str)
|
||||
|
||||
case gstr.ToLower("CamelLower"):
|
||||
return gstr.CaseCamelLower(str)
|
||||
|
||||
case gstr.ToLower("Kebab"):
|
||||
return gstr.CaseKebab(str)
|
||||
|
||||
case gstr.ToLower("KebabScreaming"):
|
||||
return gstr.CaseKebabScreaming(str)
|
||||
|
||||
case gstr.ToLower("Snake"):
|
||||
return gstr.CaseSnake(str)
|
||||
|
||||
case gstr.ToLower("SnakeFirstUpper"):
|
||||
return gstr.CaseSnakeFirstUpper(str)
|
||||
|
||||
case gstr.ToLower("SnakeScreaming"):
|
||||
return gstr.CaseSnakeScreaming(str)
|
||||
}
|
||||
return str
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
package genservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/container/garray"
|
||||
"github.com/gogf/gf/v2/container/gset"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gproc"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
"hotgo/internal/library/hggen/internal/utility/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
CGenServiceConfig = `gfcli.hggen.service`
|
||||
CGenServiceUsage = `gf hggen service [OPTION]`
|
||||
CGenServiceBrief = `parse struct and associated functions from packages to generate service go file`
|
||||
CGenServiceEg = `
|
||||
gf hggen service
|
||||
gf hggen service -f Snake
|
||||
`
|
||||
CGenServiceBriefSrcFolder = `source folder path to be parsed. default: internal/logic`
|
||||
CGenServiceBriefDstFolder = `destination folder path storing automatically generated go files. default: internal/service`
|
||||
CGenServiceBriefFileNameCase = `
|
||||
destination file name storing automatically generated go files, cases are as follows:
|
||||
| Case | Example |
|
||||
|---------------- |--------------------|
|
||||
| Lower | anykindofstring |
|
||||
| Camel | AnyKindOfString |
|
||||
| CamelLower | anyKindOfString |
|
||||
| Snake | any_kind_of_string | default
|
||||
| SnakeScreaming | ANY_KIND_OF_STRING |
|
||||
| SnakeFirstUpper | rgb_code_md5 |
|
||||
| Kebab | any-kind-of-string |
|
||||
| KebabScreaming | ANY-KIND-OF-STRING |
|
||||
`
|
||||
CGenServiceBriefWatchFile = `used in file watcher, it re-generates all service go files only if given file is under srcFolder`
|
||||
CGenServiceBriefStPattern = `regular expression matching struct name for generating service. default: ^s([A-Z]\\\\w+)$`
|
||||
CGenServiceBriefPackages = `produce go files only for given source packages`
|
||||
CGenServiceBriefImportPrefix = `custom import prefix to calculate import path for generated importing go file of logic`
|
||||
CGenServiceBriefClear = `delete all generated go files that are not used any further`
|
||||
)
|
||||
|
||||
func init() {
|
||||
gtag.Sets(g.MapStrStr{
|
||||
`CGenServiceConfig`: CGenServiceConfig,
|
||||
`CGenServiceUsage`: CGenServiceUsage,
|
||||
`CGenServiceBrief`: CGenServiceBrief,
|
||||
`CGenServiceEg`: CGenServiceEg,
|
||||
`CGenServiceBriefSrcFolder`: CGenServiceBriefSrcFolder,
|
||||
`CGenServiceBriefDstFolder`: CGenServiceBriefDstFolder,
|
||||
`CGenServiceBriefFileNameCase`: CGenServiceBriefFileNameCase,
|
||||
`CGenServiceBriefWatchFile`: CGenServiceBriefWatchFile,
|
||||
`CGenServiceBriefStPattern`: CGenServiceBriefStPattern,
|
||||
`CGenServiceBriefPackages`: CGenServiceBriefPackages,
|
||||
`CGenServiceBriefImportPrefix`: CGenServiceBriefImportPrefix,
|
||||
`CGenServiceBriefClear`: CGenServiceBriefClear,
|
||||
})
|
||||
}
|
||||
|
||||
type (
|
||||
CGenService struct{}
|
||||
CGenServiceInput struct {
|
||||
g.Meta `name:"service" config:"{CGenServiceConfig}" usage:"{CGenServiceUsage}" brief:"{CGenServiceBrief}" eg:"{CGenServiceEg}"`
|
||||
SrcFolder string `short:"s" name:"srcFolder" brief:"{CGenServiceBriefSrcFolder}" d:"internal/logic"`
|
||||
DstFolder string `short:"d" name:"dstFolder" brief:"{CGenServiceBriefDstFolder}" d:"internal/service"`
|
||||
DstFileNameCase string `short:"f" name:"dstFileNameCase" brief:"{CGenServiceBriefFileNameCase}" d:"Snake"`
|
||||
WatchFile string `short:"w" name:"watchFile" brief:"{CGenServiceBriefWatchFile}"`
|
||||
StPattern string `short:"a" name:"stPattern" brief:"{CGenServiceBriefStPattern}" d:"^s([A-Z]\\w+)$"`
|
||||
Packages []string `short:"p" name:"packages" brief:"{CGenServiceBriefPackages}"`
|
||||
ImportPrefix string `short:"i" name:"importPrefix" brief:"{CGenServiceBriefImportPrefix}"`
|
||||
Clear bool `short:"l" name:"clear" brief:"{CGenServiceBriefClear}" orphan:"true"`
|
||||
}
|
||||
CGenServiceOutput struct{}
|
||||
)
|
||||
|
||||
const (
|
||||
genServiceFileLockSeconds = 10
|
||||
)
|
||||
|
||||
func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGenServiceOutput, err error) {
|
||||
// File lock to avoid multiple processes.
|
||||
var (
|
||||
flockFilePath = gfile.Temp("gf.cli.hggen.service.lock")
|
||||
flockContent = gfile.GetContents(flockFilePath)
|
||||
)
|
||||
if flockContent != "" {
|
||||
if gtime.Timestamp()-gconv.Int64(flockContent) < genServiceFileLockSeconds {
|
||||
// If another "hggen service" process is running, it just exits.
|
||||
mlog.Debug(`another "hggen service" process is running, exit`)
|
||||
return
|
||||
}
|
||||
}
|
||||
defer gfile.Remove(flockFilePath)
|
||||
_ = gfile.PutContents(flockFilePath, gtime.TimestampStr())
|
||||
|
||||
in.SrcFolder = gstr.TrimRight(in.SrcFolder, `\/`)
|
||||
in.SrcFolder = gstr.Replace(in.SrcFolder, "\\", "/")
|
||||
in.WatchFile = gstr.TrimRight(in.WatchFile, `\/`)
|
||||
in.WatchFile = gstr.Replace(in.WatchFile, "\\", "/")
|
||||
|
||||
// Watch file handling.
|
||||
if in.WatchFile != "" {
|
||||
// It works only if given WatchFile is in SrcFolder.
|
||||
var (
|
||||
watchFileDir = gfile.Dir(in.WatchFile)
|
||||
srcFolderDir = gfile.Dir(watchFileDir)
|
||||
)
|
||||
mlog.Debug("watchFileDir:", watchFileDir)
|
||||
mlog.Debug("logicFolderDir:", srcFolderDir)
|
||||
if !gstr.HasSuffix(gstr.Replace(srcFolderDir, `\`, `/`), in.SrcFolder) {
|
||||
mlog.Printf(`ignore watch file "%s", not in source path "%s"`, in.WatchFile, in.SrcFolder)
|
||||
return
|
||||
}
|
||||
var newWorkingDir = gfile.Dir(gfile.Dir(srcFolderDir))
|
||||
if err = gfile.Chdir(newWorkingDir); err != nil {
|
||||
mlog.Fatalf(`%+v`, err)
|
||||
}
|
||||
mlog.Debug("Chdir:", newWorkingDir)
|
||||
_ = gfile.Remove(flockFilePath)
|
||||
var command = fmt.Sprintf(
|
||||
`%s hggen service -packages=%s`,
|
||||
gfile.SelfName(), gfile.Basename(watchFileDir),
|
||||
)
|
||||
err = gproc.ShellRun(ctx, command)
|
||||
return
|
||||
}
|
||||
|
||||
if !gfile.Exists(in.SrcFolder) {
|
||||
mlog.Fatalf(`source folder path "%s" does not exist`, in.SrcFolder)
|
||||
}
|
||||
|
||||
if in.ImportPrefix == "" {
|
||||
if !gfile.Exists("go.mod") {
|
||||
mlog.Fatal("ImportPrefix is empty and go.mod does not exist in current working directory")
|
||||
}
|
||||
var (
|
||||
goModContent = gfile.GetContents("go.mod")
|
||||
match, _ = gregex.MatchString(`^module\s+(.+)\s*`, goModContent)
|
||||
)
|
||||
if len(match) > 1 {
|
||||
in.ImportPrefix = fmt.Sprintf(`%s/%s`, gstr.Trim(match[1]), gstr.Replace(in.SrcFolder, `\`, `/`))
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
isDirty bool // Temp boolean.
|
||||
files []string // Temp file array.
|
||||
fileContent string // Temp file content for handling go file.
|
||||
initImportSrcPackages []string // Used for generating logic.go.
|
||||
inputPackages = in.Packages // Custom packages.
|
||||
dstPackageName = gstr.ToLower(gfile.Basename(in.DstFolder)) // Package name for generated go files.
|
||||
generatedDstFilePathSet = gset.NewStrSet() // All generated file path set.
|
||||
)
|
||||
// The first level folders.
|
||||
srcFolderPaths, err := gfile.ScanDir(in.SrcFolder, "*", false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, srcFolderPath := range srcFolderPaths {
|
||||
if !gfile.IsDir(srcFolderPath) {
|
||||
continue
|
||||
}
|
||||
// Only retrieve sub files, no recursively.
|
||||
if files, err = gfile.ScanDir(srcFolderPath, "*.go", false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(files) == 0 {
|
||||
continue
|
||||
}
|
||||
var (
|
||||
// StructName => FunctionDefinitions
|
||||
srcPkgInterfaceMap = make(map[string]*garray.StrArray)
|
||||
srcImportedPackages = garray.NewSortedStrArray().SetUnique(true)
|
||||
srcPackageName = gfile.Basename(srcFolderPath)
|
||||
ok bool
|
||||
dstFilePath = gfile.Join(in.DstFolder,
|
||||
c.getDstFileNameCase(srcPackageName, in.DstFileNameCase)+".go",
|
||||
)
|
||||
)
|
||||
generatedDstFilePathSet.Add(dstFilePath)
|
||||
for _, file := range files {
|
||||
fileContent = gfile.GetContents(file)
|
||||
// Calculate imported packages of source go files.
|
||||
err = c.calculateImportedPackages(fileContent, srcImportedPackages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Calculate functions and interfaces for service generating.
|
||||
err = c.calculateInterfaceFunctions(in, fileContent, srcPkgInterfaceMap, dstPackageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
initImportSrcPackages = append(
|
||||
initImportSrcPackages,
|
||||
fmt.Sprintf(`%s/%s`, in.ImportPrefix, srcPackageName),
|
||||
)
|
||||
// Ignore source packages if input packages given.
|
||||
if len(inputPackages) > 0 && !gstr.InArray(inputPackages, srcPackageName) {
|
||||
mlog.Debugf(
|
||||
`ignore source package "%s" as it is not in desired packages: %+v`,
|
||||
srcPackageName, inputPackages,
|
||||
)
|
||||
continue
|
||||
}
|
||||
// Generating service go file for logic.
|
||||
if ok, err = c.generateServiceFile(generateServiceFilesInput{
|
||||
CGenServiceInput: in,
|
||||
SrcStructFunctions: srcPkgInterfaceMap,
|
||||
SrcImportedPackages: srcImportedPackages.Slice(),
|
||||
SrcPackageName: srcPackageName,
|
||||
DstPackageName: dstPackageName,
|
||||
DstFilePath: dstFilePath,
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
if ok {
|
||||
isDirty = true
|
||||
}
|
||||
}
|
||||
|
||||
if in.Clear {
|
||||
files, err = gfile.ScanDirFile(in.DstFolder, "*.go", false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var relativeFilePath string
|
||||
for _, file := range files {
|
||||
relativeFilePath = gstr.SubStrFromR(file, in.DstFolder)
|
||||
if !generatedDstFilePathSet.Contains(relativeFilePath) && utils.IsFileDoNotEdit(relativeFilePath) {
|
||||
mlog.Printf(`remove no longer used service file: %s`, relativeFilePath)
|
||||
if err = gfile.Remove(file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isDirty {
|
||||
// Generate initialization go file.
|
||||
if len(initImportSrcPackages) > 0 {
|
||||
if err = c.generateInitializationFile(in, initImportSrcPackages); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Replace v1 to v2 for GoFrame.
|
||||
if err = c.replaceGeneratedServiceContentGFV2(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mlog.Printf(`gofmt go files in "%s"`, in.DstFolder)
|
||||
utils.GoFmt(in.DstFolder)
|
||||
}
|
||||
|
||||
mlog.Print(`done!`)
|
||||
return
|
||||
}
|
||||
|
||||
func (c CGenService) replaceGeneratedServiceContentGFV2(in CGenServiceInput) (err error) {
|
||||
return gfile.ReplaceDirFunc(func(path, content string) string {
|
||||
if gstr.Contains(content, `"github.com/gogf/gf`) && !gstr.Contains(content, `"github.com/gogf/gf/v2`) {
|
||||
content = gstr.Replace(content, `"github.com/gogf/gf"`, `"github.com/gogf/gf/v2"`)
|
||||
content = gstr.Replace(content, `"github.com/gogf/gf/`, `"github.com/gogf/gf/v2/`)
|
||||
return content
|
||||
}
|
||||
return content
|
||||
}, in.DstFolder, "*.go", false)
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package genservice
|
||||
|
||||
import (
|
||||
"go/parser"
|
||||
"go/token"
|
||||
|
||||
"github.com/gogf/gf/v2/container/garray"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
func (c CGenService) calculateImportedPackages(fileContent string, srcImportedPackages *garray.SortedStrArray) (err error) {
|
||||
f, err := parser.ParseFile(token.NewFileSet(), "", fileContent, parser.ImportsOnly)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range f.Imports {
|
||||
if s.Path != nil {
|
||||
if s.Name != nil {
|
||||
// If it has alias, and it is not `_`.
|
||||
if pkgAlias := s.Name.String(); pkgAlias != "_" {
|
||||
srcImportedPackages.Add(pkgAlias + " " + s.Path.Value)
|
||||
}
|
||||
} else {
|
||||
// no alias
|
||||
srcImportedPackages.Add(s.Path.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c CGenService) calculateInterfaceFunctions(
|
||||
in CGenServiceInput, fileContent string, srcPkgInterfaceMap map[string]*garray.StrArray, dstPackageName string,
|
||||
) (err error) {
|
||||
var (
|
||||
ok bool
|
||||
matches [][]string
|
||||
srcPkgInterfaceFuncArray *garray.StrArray
|
||||
)
|
||||
// calculate struct name and its functions according function definitions.
|
||||
matches, err = gregex.MatchAllString(`func \((.+?)\) ([\s\S]+?) {`, fileContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, match := range matches {
|
||||
var (
|
||||
structName string
|
||||
structMatch []string
|
||||
funcReceiver = gstr.Trim(match[1])
|
||||
receiverArray = gstr.SplitAndTrim(funcReceiver, " ")
|
||||
functionHead = gstr.Trim(gstr.Replace(match[2], "\n", ""))
|
||||
)
|
||||
if len(receiverArray) > 1 {
|
||||
structName = receiverArray[1]
|
||||
} else {
|
||||
structName = receiverArray[0]
|
||||
}
|
||||
structName = gstr.Trim(structName, "*")
|
||||
|
||||
// Case of:
|
||||
// Xxx(\n ctx context.Context, req *v1.XxxReq,\n) -> Xxx(ctx context.Context, req *v1.XxxReq)
|
||||
functionHead = gstr.Replace(functionHead, `,)`, `)`)
|
||||
functionHead, _ = gregex.ReplaceString(`\(\s+`, `(`, functionHead)
|
||||
functionHead, _ = gregex.ReplaceString(`\s{2,}`, ` `, functionHead)
|
||||
if !gstr.IsLetterUpper(functionHead[0]) {
|
||||
continue
|
||||
}
|
||||
// Match and pick the struct name from receiver.
|
||||
if structMatch, err = gregex.MatchString(in.StPattern, structName); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(structMatch) < 1 {
|
||||
continue
|
||||
}
|
||||
structName = gstr.CaseCamel(structMatch[1])
|
||||
if srcPkgInterfaceFuncArray, ok = srcPkgInterfaceMap[structName]; !ok {
|
||||
srcPkgInterfaceMap[structName] = garray.NewStrArray()
|
||||
srcPkgInterfaceFuncArray = srcPkgInterfaceMap[structName]
|
||||
}
|
||||
srcPkgInterfaceFuncArray.Append(functionHead)
|
||||
}
|
||||
// calculate struct name according type definitions.
|
||||
matches, err = gregex.MatchAllString(`type (.+) struct\s*{`, fileContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, match := range matches {
|
||||
var (
|
||||
structName string
|
||||
structMatch []string
|
||||
)
|
||||
if structMatch, err = gregex.MatchString(in.StPattern, match[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(structMatch) < 1 {
|
||||
continue
|
||||
}
|
||||
structName = gstr.CaseCamel(structMatch[1])
|
||||
if srcPkgInterfaceFuncArray, ok = srcPkgInterfaceMap[structName]; !ok {
|
||||
srcPkgInterfaceMap[structName] = garray.NewStrArray()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package genservice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/container/garray"
|
||||
"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/library/hggen/internal/consts"
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
"hotgo/internal/library/hggen/internal/utility/utils"
|
||||
)
|
||||
|
||||
type generateServiceFilesInput struct {
|
||||
CGenServiceInput
|
||||
DstFilePath string // Absolute file path for generated service go file.
|
||||
SrcStructFunctions map[string]*garray.StrArray
|
||||
SrcImportedPackages []string
|
||||
SrcPackageName string
|
||||
DstPackageName string
|
||||
}
|
||||
|
||||
func (c CGenService) generateServiceFile(in generateServiceFilesInput) (ok bool, err error) {
|
||||
var (
|
||||
generatedContent string
|
||||
allFuncArray = garray.NewStrArray() // Used for check whether interface dirty, going to change file content.
|
||||
importedPackagesContent = fmt.Sprintf(
|
||||
"import (\n%s\n)", gstr.Join(in.SrcImportedPackages, "\n"),
|
||||
)
|
||||
)
|
||||
generatedContent += gstr.ReplaceByMap(consts.TemplateGenServiceContentHead, g.MapStrStr{
|
||||
"{Imports}": importedPackagesContent,
|
||||
"{PackageName}": in.DstPackageName,
|
||||
})
|
||||
|
||||
// Type definitions.
|
||||
generatedContent += "type("
|
||||
generatedContent += "\n"
|
||||
for structName, funcArray := range in.SrcStructFunctions {
|
||||
allFuncArray.Append(funcArray.Slice()...)
|
||||
generatedContent += gstr.Trim(gstr.ReplaceByMap(consts.TemplateGenServiceContentInterface, g.MapStrStr{
|
||||
"{InterfaceName}": "I" + structName,
|
||||
"{FuncDefinition}": funcArray.Join("\n\t"),
|
||||
}))
|
||||
generatedContent += "\n"
|
||||
}
|
||||
generatedContent += ")"
|
||||
generatedContent += "\n"
|
||||
|
||||
// Generating variable and register definitions.
|
||||
var (
|
||||
variableContent string
|
||||
generatingInterfaceCheck string
|
||||
)
|
||||
// Variable definitions.
|
||||
for structName, _ := range in.SrcStructFunctions {
|
||||
generatingInterfaceCheck = fmt.Sprintf(`[^\w\d]+%s.I%s[^\w\d]`, in.DstPackageName, structName)
|
||||
if gregex.IsMatchString(generatingInterfaceCheck, generatedContent) {
|
||||
continue
|
||||
}
|
||||
variableContent += gstr.Trim(gstr.ReplaceByMap(consts.TemplateGenServiceContentVariable, g.MapStrStr{
|
||||
"{StructName}": structName,
|
||||
"{InterfaceName}": "I" + structName,
|
||||
}))
|
||||
variableContent += "\n"
|
||||
}
|
||||
if variableContent != "" {
|
||||
generatedContent += "var("
|
||||
generatedContent += "\n"
|
||||
generatedContent += variableContent
|
||||
generatedContent += ")"
|
||||
generatedContent += "\n"
|
||||
}
|
||||
// Variable register function definitions.
|
||||
for structName, _ := range in.SrcStructFunctions {
|
||||
generatingInterfaceCheck = fmt.Sprintf(`[^\w\d]+%s.I%s[^\w\d]`, in.DstPackageName, structName)
|
||||
if gregex.IsMatchString(generatingInterfaceCheck, generatedContent) {
|
||||
continue
|
||||
}
|
||||
generatedContent += gstr.Trim(gstr.ReplaceByMap(consts.TemplateGenServiceContentRegister, g.MapStrStr{
|
||||
"{StructName}": structName,
|
||||
"{InterfaceName}": "I" + structName,
|
||||
}))
|
||||
generatedContent += "\n\n"
|
||||
}
|
||||
|
||||
// Replace empty braces that have new line.
|
||||
generatedContent, _ = gregex.ReplaceString(`{[\s\t]+}`, `{}`, generatedContent)
|
||||
|
||||
// Remove package name calls of `dstPackageName` in produced codes.
|
||||
generatedContent, _ = gregex.ReplaceString(fmt.Sprintf(`\*{0,1}%s\.`, in.DstPackageName), ``, generatedContent)
|
||||
|
||||
// Write file content to disk.
|
||||
if gfile.Exists(in.DstFilePath) {
|
||||
if !utils.IsFileDoNotEdit(in.DstFilePath) {
|
||||
mlog.Printf(`ignore file as it is manually maintained: %s`, in.DstFilePath)
|
||||
return false, nil
|
||||
}
|
||||
if !c.isToGenerateServiceGoFile(in.DstPackageName, in.DstFilePath, allFuncArray) {
|
||||
mlog.Printf(`not dirty, ignore generating service go file: %s`, in.DstFilePath)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
mlog.Printf(`generating service go file: %s`, in.DstFilePath)
|
||||
if err = gfile.PutContents(in.DstFilePath, generatedContent); err != nil {
|
||||
return true, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// isToGenerateServiceGoFile checks and returns whether the service content dirty.
|
||||
func (c CGenService) isToGenerateServiceGoFile(dstPackageName, filePath string, funcArray *garray.StrArray) bool {
|
||||
var (
|
||||
fileContent = gfile.GetContents(filePath)
|
||||
generatedFuncArray = garray.NewSortedStrArrayFrom(funcArray.Slice())
|
||||
contentFuncArray = garray.NewSortedStrArray()
|
||||
)
|
||||
if fileContent == "" {
|
||||
return true
|
||||
}
|
||||
matches, _ := gregex.MatchAllString(`\s+interface\s+{([\s\S]+?)}`, fileContent)
|
||||
for _, match := range matches {
|
||||
contentFuncArray.Append(gstr.SplitAndTrim(match[1], "\n")...)
|
||||
}
|
||||
if generatedFuncArray.Len() != contentFuncArray.Len() {
|
||||
mlog.Debugf(
|
||||
`dirty, generatedFuncArray.Len()[%d] != contentFuncArray.Len()[%d]`,
|
||||
generatedFuncArray.Len(), contentFuncArray.Len(),
|
||||
)
|
||||
return true
|
||||
}
|
||||
var funcDefinition string
|
||||
for i := 0; i < generatedFuncArray.Len(); i++ {
|
||||
funcDefinition, _ = gregex.ReplaceString(
|
||||
fmt.Sprintf(`\*{0,1}%s\.`, dstPackageName), ``, generatedFuncArray.At(i),
|
||||
)
|
||||
if funcDefinition != contentFuncArray.At(i) {
|
||||
mlog.Debugf(`dirty, %s != %s`, funcDefinition, contentFuncArray.At(i))
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c CGenService) generateInitializationFile(in CGenServiceInput, importSrcPackages []string) (err error) {
|
||||
var (
|
||||
srcPackageName = gstr.ToLower(gfile.Basename(in.SrcFolder))
|
||||
srcFilePath = gfile.Join(in.SrcFolder, srcPackageName+".go")
|
||||
srcImports string
|
||||
generatedContent string
|
||||
)
|
||||
if !utils.IsFileDoNotEdit(srcFilePath) {
|
||||
mlog.Debugf(`ignore file as it is manually maintained: %s`, srcFilePath)
|
||||
return nil
|
||||
}
|
||||
for _, importSrcPackage := range importSrcPackages {
|
||||
srcImports += fmt.Sprintf(`%s_ "%s"%s`, "\t", importSrcPackage, "\n")
|
||||
}
|
||||
generatedContent = gstr.ReplaceByMap(consts.TemplateGenServiceLogicContent, g.MapStrStr{
|
||||
"{PackageName}": srcPackageName,
|
||||
"{Imports}": srcImports,
|
||||
})
|
||||
mlog.Printf(`generating init go file: %s`, srcFilePath)
|
||||
if err = gfile.PutContents(srcFilePath, generatedContent); err != nil {
|
||||
return err
|
||||
}
|
||||
utils.GoFmt(srcFilePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// getDstFileNameCase call gstr.Case* function to convert the s to specified case.
|
||||
func (c CGenService) getDstFileNameCase(str, caseStr string) string {
|
||||
switch gstr.ToLower(caseStr) {
|
||||
case gstr.ToLower("Lower"):
|
||||
return gstr.ToLower(str)
|
||||
|
||||
case gstr.ToLower("Camel"):
|
||||
return gstr.CaseCamel(str)
|
||||
|
||||
case gstr.ToLower("CamelLower"):
|
||||
return gstr.CaseCamelLower(str)
|
||||
|
||||
case gstr.ToLower("Kebab"):
|
||||
return gstr.CaseKebab(str)
|
||||
|
||||
case gstr.ToLower("KebabScreaming"):
|
||||
return gstr.CaseKebabScreaming(str)
|
||||
|
||||
case gstr.ToLower("SnakeFirstUpper"):
|
||||
return gstr.CaseSnakeFirstUpper(str)
|
||||
|
||||
case gstr.ToLower("SnakeScreaming"):
|
||||
return gstr.CaseSnakeScreaming(str)
|
||||
}
|
||||
return gstr.CaseSnake(str)
|
||||
}
|
||||
7
server/internal/library/hggen/internal/consts/consts.go
Normal file
7
server/internal/library/hggen/internal/consts/consts.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package consts
|
||||
|
||||
const (
|
||||
// DoNotEditKey is used in generated files,
|
||||
// which marks the files will be overwritten by CLI tool.
|
||||
DoNotEditKey = `DO NOT EDIT`
|
||||
)
|
||||
@@ -0,0 +1,108 @@
|
||||
package consts
|
||||
|
||||
const TemplateGenDaoIndexContent = `
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"{TplImportPrefix}/internal"
|
||||
)
|
||||
|
||||
// internal{TplTableNameCamelCase}Dao is internal type for wrapping internal DAO implements.
|
||||
type internal{TplTableNameCamelCase}Dao = *internal.{TplTableNameCamelCase}Dao
|
||||
|
||||
// {TplTableNameCamelLowerCase}Dao is the data access object for table {TplTableName}.
|
||||
// You can define custom methods on it to extend its functionality as you wish.
|
||||
type {TplTableNameCamelLowerCase}Dao struct {
|
||||
internal{TplTableNameCamelCase}Dao
|
||||
}
|
||||
|
||||
var (
|
||||
// {TplTableNameCamelCase} is globally public accessible object for table {TplTableName} operations.
|
||||
{TplTableNameCamelCase} = {TplTableNameCamelLowerCase}Dao{
|
||||
internal.New{TplTableNameCamelCase}Dao(),
|
||||
}
|
||||
)
|
||||
|
||||
// Fill with you ideas below.
|
||||
|
||||
`
|
||||
|
||||
const TemplateGenDaoInternalContent = `
|
||||
// ==========================================================================
|
||||
// Code generated by GoFrame CLI tool. DO NOT EDIT. {TplCreatedAtDatetimeStr}
|
||||
// ==========================================================================
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// {TplTableNameCamelCase}Dao is the data access object for table {TplTableName}.
|
||||
type {TplTableNameCamelCase}Dao struct {
|
||||
table string // table is the underlying table name of the DAO.
|
||||
group string // group is the database configuration group name of current DAO.
|
||||
columns {TplTableNameCamelCase}Columns // columns contains all the column names of Table for convenient usage.
|
||||
}
|
||||
|
||||
// {TplTableNameCamelCase}Columns defines and stores column names for table {TplTableName}.
|
||||
type {TplTableNameCamelCase}Columns struct {
|
||||
{TplColumnDefine}
|
||||
}
|
||||
|
||||
// {TplTableNameCamelLowerCase}Columns holds the columns for table {TplTableName}.
|
||||
var {TplTableNameCamelLowerCase}Columns = {TplTableNameCamelCase}Columns{
|
||||
{TplColumnNames}
|
||||
}
|
||||
|
||||
// New{TplTableNameCamelCase}Dao creates and returns a new DAO object for table data access.
|
||||
func New{TplTableNameCamelCase}Dao() *{TplTableNameCamelCase}Dao {
|
||||
return &{TplTableNameCamelCase}Dao{
|
||||
group: "{TplGroupName}",
|
||||
table: "{TplTableName}",
|
||||
columns: {TplTableNameCamelLowerCase}Columns,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of current DAO.
|
||||
func (dao *{TplTableNameCamelCase}Dao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of current dao.
|
||||
func (dao *{TplTableNameCamelCase}Dao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of current dao.
|
||||
func (dao *{TplTableNameCamelCase}Dao) Columns() {TplTableNameCamelCase}Columns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the configuration group name of database of current dao.
|
||||
func (dao *{TplTableNameCamelCase}Dao) Group() string {
|
||||
return dao.group
|
||||
}
|
||||
|
||||
// Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation.
|
||||
func (dao *{TplTableNameCamelCase}Dao) Ctx(ctx context.Context) *gdb.Model {
|
||||
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
|
||||
}
|
||||
|
||||
// Transaction wraps the transaction logic using function f.
|
||||
// It rollbacks the transaction and returns the error from function f if it returns non-nil error.
|
||||
// It commits the transaction and returns nil if function f returns nil.
|
||||
//
|
||||
// Note that, you should not Commit or Rollback the transaction in function f
|
||||
// as it is automatically handled by this function.
|
||||
func (dao *{TplTableNameCamelCase}Dao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
`
|
||||
@@ -0,0 +1,14 @@
|
||||
package consts
|
||||
|
||||
const TemplateGenDaoDoContent = `
|
||||
// =================================================================================
|
||||
// Code generated by GoFrame CLI tool. DO NOT EDIT. {TplCreatedAtDatetimeStr}
|
||||
// =================================================================================
|
||||
|
||||
package do
|
||||
|
||||
{TplPackageImports}
|
||||
|
||||
// {TplTableNameCamelCase} is the golang structure of table {TplTableName} for DAO operations like Where/Data.
|
||||
{TplStructDefine}
|
||||
`
|
||||
@@ -0,0 +1,14 @@
|
||||
package consts
|
||||
|
||||
const TemplateGenDaoEntityContent = `
|
||||
// =================================================================================
|
||||
// Code generated by GoFrame CLI tool. DO NOT EDIT. {TplCreatedAtDatetimeStr}
|
||||
// =================================================================================
|
||||
|
||||
package entity
|
||||
|
||||
{TplPackageImports}
|
||||
|
||||
// {TplTableNameCamelCase} is the golang structure for table {TplTableName}.
|
||||
{TplStructDefine}
|
||||
`
|
||||
@@ -0,0 +1,17 @@
|
||||
package consts
|
||||
|
||||
const TemplatePbEntityMessageContent = `
|
||||
// ==========================================================================
|
||||
// Code generated by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package {PackageName};
|
||||
|
||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
||||
|
||||
{OptionContent}
|
||||
|
||||
{EntityMessage}
|
||||
`
|
||||
@@ -0,0 +1,35 @@
|
||||
package consts
|
||||
|
||||
const TemplateGenServiceContentHead = `
|
||||
// ================================================================================
|
||||
// Code generated by GoFrame CLI tool. DO NOT EDIT.
|
||||
// You can delete these comments if you wish manually maintain this interface file.
|
||||
// ================================================================================
|
||||
|
||||
package {PackageName}
|
||||
|
||||
{Imports}
|
||||
`
|
||||
|
||||
const TemplateGenServiceContentInterface = `
|
||||
{InterfaceName} interface {
|
||||
{FuncDefinition}
|
||||
}
|
||||
`
|
||||
|
||||
const TemplateGenServiceContentVariable = `
|
||||
local{StructName} {InterfaceName}
|
||||
`
|
||||
|
||||
const TemplateGenServiceContentRegister = `
|
||||
func {StructName}() {InterfaceName} {
|
||||
if local{StructName} == nil {
|
||||
panic("implement not found for interface {InterfaceName}, forgot register?")
|
||||
}
|
||||
return local{StructName}
|
||||
}
|
||||
|
||||
func Register{StructName}(i {InterfaceName}) {
|
||||
local{StructName} = i
|
||||
}
|
||||
`
|
||||
@@ -0,0 +1,13 @@
|
||||
package consts
|
||||
|
||||
const TemplateGenServiceLogicContent = `
|
||||
// ==========================================================================
|
||||
// Code generated by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package {PackageName}
|
||||
|
||||
import(
|
||||
{Imports}
|
||||
)
|
||||
`
|
||||
1
server/internal/library/hggen/internal/packed/packed.go
Normal file
1
server/internal/library/hggen/internal/packed/packed.go
Normal file
@@ -0,0 +1 @@
|
||||
package packed
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
229
server/internal/library/hggen/internal/service/install.go
Normal file
229
server/internal/library/hggen/internal/service/install.go
Normal file
@@ -0,0 +1,229 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/container/garray"
|
||||
"github.com/gogf/gf/v2/container/gset"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gcmd"
|
||||
"github.com/gogf/gf/v2/os/genv"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
|
||||
"hotgo/internal/library/hggen/internal/utility/allyes"
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
)
|
||||
|
||||
var (
|
||||
Install = serviceInstall{}
|
||||
)
|
||||
|
||||
type serviceInstall struct{}
|
||||
|
||||
type serviceInstallAvailablePath struct {
|
||||
dirPath string
|
||||
filePath string
|
||||
writable bool
|
||||
installed bool
|
||||
}
|
||||
|
||||
func (s serviceInstall) Run(ctx context.Context) (err error) {
|
||||
// Ask where to install.
|
||||
paths := s.getAvailablePaths()
|
||||
if len(paths) <= 0 {
|
||||
mlog.Printf("no path detected, you can manually install gf by copying the binary to path folder.")
|
||||
return
|
||||
}
|
||||
mlog.Printf("I found some installable paths for you(from $PATH): ")
|
||||
mlog.Printf(" %2s | %8s | %9s | %s", "Id", "Writable", "Installed", "Path")
|
||||
|
||||
// Print all paths status and determine the default selectedID value.
|
||||
var (
|
||||
selectedID = -1
|
||||
newPaths []serviceInstallAvailablePath
|
||||
pathSet = gset.NewStrSet() // Used for repeated items filtering.
|
||||
)
|
||||
for _, path := range paths {
|
||||
if !pathSet.AddIfNotExist(path.dirPath) {
|
||||
continue
|
||||
}
|
||||
newPaths = append(newPaths, path)
|
||||
}
|
||||
paths = newPaths
|
||||
for id, path := range paths {
|
||||
mlog.Printf(" %2d | %8t | %9t | %s", id, path.writable, path.installed, path.dirPath)
|
||||
if selectedID == -1 {
|
||||
// Use the previously installed path as the most priority choice.
|
||||
if path.installed {
|
||||
selectedID = id
|
||||
}
|
||||
}
|
||||
}
|
||||
// If there's no previously installed path, use the first writable path.
|
||||
if selectedID == -1 {
|
||||
// Order by choosing priority.
|
||||
commonPaths := garray.NewStrArrayFrom(g.SliceStr{
|
||||
s.getGoPathBin(),
|
||||
`/usr/local/bin`,
|
||||
`/usr/bin`,
|
||||
`/usr/sbin`,
|
||||
`C:\Windows`,
|
||||
`C:\Windows\system32`,
|
||||
`C:\Go\bin`,
|
||||
`C:\Program Files`,
|
||||
`C:\Program Files (x86)`,
|
||||
})
|
||||
// Check the common installation directories.
|
||||
commonPaths.Iterator(func(k int, v string) bool {
|
||||
for id, aPath := range paths {
|
||||
if strings.EqualFold(aPath.dirPath, v) {
|
||||
selectedID = id
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if selectedID == -1 {
|
||||
selectedID = 0
|
||||
}
|
||||
}
|
||||
|
||||
if allyes.Check() {
|
||||
// Use the default selectedID.
|
||||
mlog.Printf("please choose one installation destination [default %d]: %d", selectedID, selectedID)
|
||||
} else {
|
||||
for {
|
||||
// Get input and update selectedID.
|
||||
var (
|
||||
inputID int
|
||||
input = gcmd.Scanf("please choose one installation destination [default %d]: ", selectedID)
|
||||
)
|
||||
if input != "" {
|
||||
inputID = gconv.Int(input)
|
||||
}
|
||||
// Check if out of range.
|
||||
if inputID >= len(paths) || inputID < 0 {
|
||||
mlog.Printf("invalid install destination Id: %d", inputID)
|
||||
continue
|
||||
}
|
||||
selectedID = inputID
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Get selected destination path.
|
||||
dstPath := paths[selectedID]
|
||||
|
||||
// Install the new binary.
|
||||
mlog.Debugf(`copy file from "%s" to "%s"`, gfile.SelfPath(), dstPath.filePath)
|
||||
err = gfile.CopyFile(gfile.SelfPath(), dstPath.filePath)
|
||||
if err != nil {
|
||||
mlog.Printf("install gf binary to '%s' failed: %v", dstPath.dirPath, err)
|
||||
mlog.Printf("you can manually install gf by copying the binary to folder: %s", dstPath.dirPath)
|
||||
} else {
|
||||
mlog.Printf("gf binary is successfully installed to: %s", dstPath.filePath)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// IsInstalled checks and returns whether the binary is installed.
|
||||
func (s serviceInstall) IsInstalled() bool {
|
||||
paths := s.getAvailablePaths()
|
||||
for _, aPath := range paths {
|
||||
if aPath.installed {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// getGoPathBinFilePath retrieves ad returns the GOPATH/bin path for binary.
|
||||
func (s serviceInstall) getGoPathBin() string {
|
||||
if goPath := genv.Get(`GOPATH`).String(); goPath != "" {
|
||||
return gfile.Join(goPath, "bin")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getAvailablePaths returns the installation paths data for the binary.
|
||||
func (s serviceInstall) getAvailablePaths() []serviceInstallAvailablePath {
|
||||
var (
|
||||
folderPaths []serviceInstallAvailablePath
|
||||
binaryFileName = "gf" + gfile.Ext(gfile.SelfPath())
|
||||
)
|
||||
// $GOPATH/bin
|
||||
if goPathBin := s.getGoPathBin(); goPathBin != "" {
|
||||
folderPaths = s.checkAndAppendToAvailablePath(
|
||||
folderPaths, goPathBin, binaryFileName,
|
||||
)
|
||||
}
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
darwinInstallationCheckPaths := []string{"/usr/local/bin"}
|
||||
for _, v := range darwinInstallationCheckPaths {
|
||||
folderPaths = s.checkAndAppendToAvailablePath(
|
||||
folderPaths, v, binaryFileName,
|
||||
)
|
||||
}
|
||||
fallthrough
|
||||
|
||||
default:
|
||||
// Search and find the writable directory path.
|
||||
envPath := genv.Get("PATH", genv.Get("Path").String()).String()
|
||||
if gstr.Contains(envPath, ";") {
|
||||
// windows.
|
||||
for _, v := range gstr.SplitAndTrim(envPath, ";") {
|
||||
if v == "." {
|
||||
continue
|
||||
}
|
||||
folderPaths = s.checkAndAppendToAvailablePath(
|
||||
folderPaths, v, binaryFileName,
|
||||
)
|
||||
}
|
||||
} else if gstr.Contains(envPath, ":") {
|
||||
// *nix.
|
||||
for _, v := range gstr.SplitAndTrim(envPath, ":") {
|
||||
if v == "." {
|
||||
continue
|
||||
}
|
||||
folderPaths = s.checkAndAppendToAvailablePath(
|
||||
folderPaths, v, binaryFileName,
|
||||
)
|
||||
}
|
||||
} else if envPath != "" {
|
||||
folderPaths = s.checkAndAppendToAvailablePath(
|
||||
folderPaths, envPath, binaryFileName,
|
||||
)
|
||||
} else {
|
||||
folderPaths = s.checkAndAppendToAvailablePath(
|
||||
folderPaths, "/usr/local/bin", binaryFileName,
|
||||
)
|
||||
}
|
||||
}
|
||||
return folderPaths
|
||||
}
|
||||
|
||||
// checkAndAppendToAvailablePath checks if `path` is writable and already installed.
|
||||
// It adds the `path` to `folderPaths` if it is writable or already installed, or else it ignores the `path`.
|
||||
func (s serviceInstall) checkAndAppendToAvailablePath(folderPaths []serviceInstallAvailablePath, dirPath string, binaryFileName string) []serviceInstallAvailablePath {
|
||||
var (
|
||||
filePath = gfile.Join(dirPath, binaryFileName)
|
||||
writable = gfile.IsWritable(dirPath)
|
||||
installed = gfile.Exists(filePath)
|
||||
)
|
||||
if !writable && !installed {
|
||||
return folderPaths
|
||||
}
|
||||
return append(
|
||||
folderPaths,
|
||||
serviceInstallAvailablePath{
|
||||
dirPath: dirPath,
|
||||
writable: writable,
|
||||
filePath: filePath,
|
||||
installed: installed,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package allyes
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gcmd"
|
||||
"github.com/gogf/gf/v2/os/genv"
|
||||
)
|
||||
|
||||
const (
|
||||
EnvName = "GF_CLI_ALL_YES"
|
||||
)
|
||||
|
||||
// Init initializes the package manually.
|
||||
func Init() {
|
||||
if gcmd.GetOpt("y") != nil {
|
||||
genv.MustSet(EnvName, "1")
|
||||
}
|
||||
}
|
||||
|
||||
// Check checks whether option allow all yes for command.
|
||||
func Check() bool {
|
||||
return genv.Get(EnvName).String() == "1"
|
||||
}
|
||||
66
server/internal/library/hggen/internal/utility/mlog/mlog.go
Normal file
66
server/internal/library/hggen/internal/utility/mlog/mlog.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package mlog
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gcmd"
|
||||
"github.com/gogf/gf/v2/os/genv"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
)
|
||||
|
||||
const (
|
||||
headerPrintEnvName = "GF_CLI_MLOG_HEADER"
|
||||
)
|
||||
|
||||
var (
|
||||
ctx = context.TODO()
|
||||
logger = glog.New()
|
||||
)
|
||||
|
||||
func init() {
|
||||
logger.SetStack(false)
|
||||
if genv.Get(headerPrintEnvName).String() == "1" {
|
||||
logger.SetHeaderPrint(true)
|
||||
} else {
|
||||
logger.SetHeaderPrint(false)
|
||||
}
|
||||
if gcmd.GetOpt("debug") != nil || gcmd.GetOpt("gf.debug") != nil {
|
||||
logger.SetDebug(true)
|
||||
} else {
|
||||
logger.SetDebug(false)
|
||||
}
|
||||
}
|
||||
|
||||
// SetHeaderPrint enables/disables header printing to stdout.
|
||||
func SetHeaderPrint(enabled bool) {
|
||||
logger.SetHeaderPrint(enabled)
|
||||
if enabled {
|
||||
_ = genv.Set(headerPrintEnvName, "1")
|
||||
} else {
|
||||
_ = genv.Set(headerPrintEnvName, "0")
|
||||
}
|
||||
}
|
||||
|
||||
func Print(v ...interface{}) {
|
||||
logger.Print(ctx, v...)
|
||||
}
|
||||
|
||||
func Printf(format string, v ...interface{}) {
|
||||
logger.Printf(ctx, format, v...)
|
||||
}
|
||||
|
||||
func Fatal(v ...interface{}) {
|
||||
logger.Fatal(ctx, v...)
|
||||
}
|
||||
|
||||
func Fatalf(format string, v ...interface{}) {
|
||||
logger.Fatalf(ctx, format, v...)
|
||||
}
|
||||
|
||||
func Debug(v ...interface{}) {
|
||||
logger.Debug(ctx, v...)
|
||||
}
|
||||
|
||||
func Debugf(format string, v ...interface{}) {
|
||||
logger.Debugf(ctx, format, v...)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"golang.org/x/tools/imports"
|
||||
|
||||
"hotgo/internal/library/hggen/internal/consts"
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
)
|
||||
|
||||
// GoFmt formats the source file and adds or removes import statements as necessary.
|
||||
func GoFmt(path string) {
|
||||
replaceFunc := func(path, content string) string {
|
||||
res, err := imports.Process(path, []byte(content), nil)
|
||||
if err != nil {
|
||||
mlog.Printf(`error format "%s" go files: %v`, path, err)
|
||||
return content
|
||||
}
|
||||
return string(res)
|
||||
}
|
||||
|
||||
var err error
|
||||
if gfile.IsFile(path) {
|
||||
// File format.
|
||||
if gfile.ExtName(path) != "go" {
|
||||
return
|
||||
}
|
||||
err = gfile.ReplaceFileFunc(replaceFunc, path)
|
||||
} else {
|
||||
// Folder format.
|
||||
err = gfile.ReplaceDirFunc(replaceFunc, path, "*.go", true)
|
||||
}
|
||||
if err != nil {
|
||||
mlog.Printf(`error format "%s" go files: %v`, path, err)
|
||||
}
|
||||
}
|
||||
|
||||
// IsFileDoNotEdit checks and returns whether file contains `do not edit` key.
|
||||
func IsFileDoNotEdit(filePath string) bool {
|
||||
if !gfile.Exists(filePath) {
|
||||
return true
|
||||
}
|
||||
return gstr.Contains(gfile.GetContents(filePath), consts.DoNotEditKey)
|
||||
}
|
||||
262
server/internal/library/hggen/views/column.go
Normal file
262
server/internal/library/hggen/views/column.go
Normal file
@@ -0,0 +1,262 @@
|
||||
// Package views
|
||||
// @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 views
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"hotgo/internal/library/hggen/internal/cmd/gendao"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DoTableColumns 获取指定表生成字段列表
|
||||
func DoTableColumns(ctx context.Context, in sysin.GenCodesColumnListInp, config gendao.CGenDaoInput) (fields []*sysin.GenCodesColumnListModel, err error) {
|
||||
var (
|
||||
sql = "select ORDINAL_POSITION as `id`, COLUMN_NAME as `name`, COLUMN_COMMENT as `dc`, DATA_TYPE as `dataType`, COLUMN_TYPE as `sqlType`, CHARACTER_MAXIMUM_LENGTH as `length`, IS_NULLABLE as `isAllowNull`, COLUMN_DEFAULT as `defaultValue`, COLUMN_KEY as `index`, EXTRA as `extra` from information_schema.COLUMNS where TABLE_SCHEMA = '%s' and TABLE_NAME = '%s' ORDER BY `id` ASC"
|
||||
conf = g.DB(in.Name).GetConfig()
|
||||
)
|
||||
|
||||
err = g.DB(in.Name).Ctx(ctx).Raw(fmt.Sprintf(sql, conf.Name, in.Table)).Scan(&fields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(fields) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, field := range fields {
|
||||
if in.IsLink == 1 {
|
||||
CustomLinkAttributes(ctx, in.Alias, field, config)
|
||||
} else {
|
||||
CustomAttributes(ctx, field, config)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CustomLinkAttributes 可自定义关联表的字段属性
|
||||
func CustomLinkAttributes(ctx context.Context, alias string, field *sysin.GenCodesColumnListModel, in gendao.CGenDaoInput) {
|
||||
field.GoName, field.GoType, field.TsName, field.TsType = GenGotype(ctx, field, in)
|
||||
|
||||
field.GoName = gstr.UcFirst(alias + field.GoName)
|
||||
field.TsName = gstr.LcFirst(field.GoName)
|
||||
|
||||
setDefaultQueryWhere(field)
|
||||
setDefaultValue(field)
|
||||
}
|
||||
|
||||
// CustomAttributes 可自定义的字段属性
|
||||
func CustomAttributes(ctx context.Context, field *sysin.GenCodesColumnListModel, in gendao.CGenDaoInput) {
|
||||
field.GoName, field.GoType, field.TsName, field.TsType = GenGotype(ctx, field, in)
|
||||
setDefault(field)
|
||||
}
|
||||
|
||||
// GenGotype 生成字段的go类型
|
||||
func GenGotype(ctx context.Context, field *sysin.GenCodesColumnListModel, in gendao.CGenDaoInput) (goName, typeName, tsName, tsType string) {
|
||||
var (
|
||||
err error
|
||||
)
|
||||
|
||||
tsName = getJsonTagFromCase(field.Name, in.JsonCase)
|
||||
goName = gstr.CaseCamel(field.Name)
|
||||
|
||||
typeName, err = CheckLocalTypeForField(ctx, field.DataType, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
switch typeName {
|
||||
case gdb.LocalTypeDate, gdb.LocalTypeDatetime:
|
||||
if in.StdTime {
|
||||
typeName = "time.Time"
|
||||
} else {
|
||||
typeName = "*gtime.Time"
|
||||
}
|
||||
|
||||
case gdb.LocalTypeInt64Bytes:
|
||||
typeName = "int64"
|
||||
|
||||
case gdb.LocalTypeUint64Bytes:
|
||||
typeName = "uint64"
|
||||
|
||||
// Special type handle.
|
||||
case gdb.LocalTypeJson, gdb.LocalTypeJsonb:
|
||||
if in.GJsonSupport {
|
||||
typeName = "*gjson.Json"
|
||||
} else {
|
||||
typeName = "string"
|
||||
}
|
||||
}
|
||||
|
||||
tsType = ShiftMap[typeName]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CheckLocalTypeForField checks and returns corresponding type for given db type.
|
||||
func CheckLocalTypeForField(ctx context.Context, fieldType string, fieldValue interface{}) (string, error) {
|
||||
var (
|
||||
typeName string
|
||||
typePattern string
|
||||
)
|
||||
match, _ := gregex.MatchString(`(.+?)\((.+)\)`, fieldType)
|
||||
if len(match) == 3 {
|
||||
typeName = gstr.Trim(match[1])
|
||||
typePattern = gstr.Trim(match[2])
|
||||
} else {
|
||||
typeName = gstr.Split(fieldType, " ")[0]
|
||||
}
|
||||
typeName = strings.ToLower(typeName)
|
||||
switch typeName {
|
||||
case
|
||||
"binary",
|
||||
"varbinary",
|
||||
"blob",
|
||||
"tinyblob",
|
||||
"mediumblob",
|
||||
"longblob":
|
||||
return gdb.LocalTypeBytes, nil
|
||||
|
||||
case
|
||||
"int",
|
||||
"tinyint",
|
||||
"small_int",
|
||||
"smallint",
|
||||
"medium_int",
|
||||
"mediumint",
|
||||
"serial":
|
||||
if gstr.ContainsI(fieldType, "unsigned") {
|
||||
return gdb.LocalTypeUint, nil
|
||||
}
|
||||
return gdb.LocalTypeInt, nil
|
||||
|
||||
case
|
||||
"big_int",
|
||||
"bigint",
|
||||
"bigserial":
|
||||
if gstr.ContainsI(fieldType, "unsigned") {
|
||||
return gdb.LocalTypeUint64, nil
|
||||
}
|
||||
return gdb.LocalTypeInt64, nil
|
||||
|
||||
case
|
||||
"real":
|
||||
return gdb.LocalTypeFloat32, nil
|
||||
|
||||
case
|
||||
"float",
|
||||
"double",
|
||||
"decimal",
|
||||
"money",
|
||||
"numeric",
|
||||
"smallmoney":
|
||||
return gdb.LocalTypeFloat64, nil
|
||||
|
||||
case
|
||||
"bit":
|
||||
// It is suggested using bit(1) as boolean.
|
||||
if typePattern == "1" {
|
||||
return gdb.LocalTypeBool, nil
|
||||
}
|
||||
s := gconv.String(fieldValue)
|
||||
// mssql is true|false string.
|
||||
if strings.EqualFold(s, "true") || strings.EqualFold(s, "false") {
|
||||
return gdb.LocalTypeBool, nil
|
||||
}
|
||||
if gstr.ContainsI(fieldType, "unsigned") {
|
||||
return gdb.LocalTypeUint64Bytes, nil
|
||||
}
|
||||
return gdb.LocalTypeInt64Bytes, nil
|
||||
|
||||
case
|
||||
"bool":
|
||||
return gdb.LocalTypeBool, nil
|
||||
|
||||
case
|
||||
"date":
|
||||
return gdb.LocalTypeDate, nil
|
||||
|
||||
case
|
||||
"datetime",
|
||||
"timestamp",
|
||||
"timestamptz":
|
||||
return gdb.LocalTypeDatetime, nil
|
||||
|
||||
case
|
||||
"json":
|
||||
return gdb.LocalTypeJson, nil
|
||||
|
||||
case
|
||||
"jsonb":
|
||||
return gdb.LocalTypeJsonb, nil
|
||||
|
||||
default:
|
||||
// Auto-detect field type, using key match.
|
||||
switch {
|
||||
case strings.Contains(typeName, "text") || strings.Contains(typeName, "char") || strings.Contains(typeName, "character"):
|
||||
return gdb.LocalTypeString, nil
|
||||
|
||||
case strings.Contains(typeName, "float") || strings.Contains(typeName, "double") || strings.Contains(typeName, "numeric"):
|
||||
return gdb.LocalTypeFloat64, nil
|
||||
|
||||
case strings.Contains(typeName, "bool"):
|
||||
return gdb.LocalTypeBool, nil
|
||||
|
||||
case strings.Contains(typeName, "binary") || strings.Contains(typeName, "blob"):
|
||||
return gdb.LocalTypeBytes, nil
|
||||
|
||||
case strings.Contains(typeName, "int"):
|
||||
if gstr.ContainsI(fieldType, "unsigned") {
|
||||
return gdb.LocalTypeUint, nil
|
||||
}
|
||||
return gdb.LocalTypeInt, nil
|
||||
|
||||
case strings.Contains(typeName, "time"):
|
||||
return gdb.LocalTypeDatetime, nil
|
||||
|
||||
case strings.Contains(typeName, "date"):
|
||||
return gdb.LocalTypeDatetime, nil
|
||||
|
||||
default:
|
||||
return gdb.LocalTypeString, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getJsonTagFromCase call gstr.Case* function to convert the s to specified case.
|
||||
func getJsonTagFromCase(str, caseStr string) string {
|
||||
switch gstr.ToLower(caseStr) {
|
||||
case gstr.ToLower("Camel"):
|
||||
return gstr.CaseCamel(str)
|
||||
|
||||
case gstr.ToLower("CamelLower"):
|
||||
return gstr.CaseCamelLower(str)
|
||||
|
||||
case gstr.ToLower("Kebab"):
|
||||
return gstr.CaseKebab(str)
|
||||
|
||||
case gstr.ToLower("KebabScreaming"):
|
||||
return gstr.CaseKebabScreaming(str)
|
||||
|
||||
case gstr.ToLower("Snake"):
|
||||
return gstr.CaseSnake(str)
|
||||
|
||||
case gstr.ToLower("SnakeFirstUpper"):
|
||||
return gstr.CaseSnakeFirstUpper(str)
|
||||
|
||||
case gstr.ToLower("SnakeScreaming"):
|
||||
return gstr.CaseSnakeScreaming(str)
|
||||
}
|
||||
return str
|
||||
}
|
||||
312
server/internal/library/hggen/views/column_default.go
Normal file
312
server/internal/library/hggen/views/column_default.go
Normal file
@@ -0,0 +1,312 @@
|
||||
// Package views
|
||||
// @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 views
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 默认表单组件映射 Ts -> 表单组件
|
||||
var defaultFormModeMap = map[string]string{
|
||||
TsTypeString: FormModeInput,
|
||||
TsTypeNumber: FormModeInputNumber,
|
||||
TsTypeBoolean: FormModeInputNumber,
|
||||
TsTypeArray: FormModeInputDynamic,
|
||||
TsTypeTuple: FormModeInputDynamic,
|
||||
TsTypeAny: FormModeInput,
|
||||
}
|
||||
|
||||
var defaultEditSwitch = map[string]bool{
|
||||
"id": false,
|
||||
"level": false,
|
||||
"tree": false,
|
||||
"created_by": false,
|
||||
"updated_by": false,
|
||||
"created_at": false,
|
||||
"updated_at": false,
|
||||
"deleted_at": false,
|
||||
}
|
||||
var defaultListSwitch = map[string]bool{
|
||||
"level": false,
|
||||
"tree": false,
|
||||
"deleted_at": false,
|
||||
}
|
||||
|
||||
var defaultExportSwitch = map[string]bool{
|
||||
"level": false,
|
||||
"tree": false,
|
||||
"deleted_at": false,
|
||||
}
|
||||
|
||||
var defaultQuerySwitch = map[string]bool{
|
||||
"level": false,
|
||||
"tree": false,
|
||||
"deleted_at": false,
|
||||
}
|
||||
|
||||
var defaultSort = map[string]bool{
|
||||
"id": true,
|
||||
"sort": true,
|
||||
}
|
||||
|
||||
// 默认表单验证映射 物理类型命名识别
|
||||
var defaultFormRoleMap = map[string]string{
|
||||
"mobile": FormRolePhone,
|
||||
"qq": FormRoleQq,
|
||||
"email": FormRoleEmail,
|
||||
"id_card": FormRoleIdCard,
|
||||
"bank_card": FormRoleBankCard,
|
||||
"password": FormRolePassword,
|
||||
"pass": FormRolePassword,
|
||||
"price": FormRoleAmount,
|
||||
}
|
||||
|
||||
// 默认查询条件映射 go类型识别
|
||||
var defaultWhereModeMap = map[string]string{
|
||||
GoTypeString: WhereModeLike,
|
||||
GoTypeDate: WhereModeEq,
|
||||
GoTypeDatetime: WhereModeEq,
|
||||
GoTypeInt: WhereModeEq,
|
||||
GoTypeUint: WhereModeEq,
|
||||
GoTypeInt64: WhereModeEq,
|
||||
GoTypeUint64: WhereModeEq,
|
||||
GoTypeIntSlice: WhereModeIn,
|
||||
GoTypeInt64Slice: WhereModeIn,
|
||||
GoTypeUint64Slice: WhereModeIn,
|
||||
GoTypeFloat32: WhereModeEq,
|
||||
GoTypeFloat64: WhereModeEq,
|
||||
GoTypeBytes: WhereModeEq,
|
||||
GoTypeTime: WhereModeEq,
|
||||
GoTypeGTime: WhereModeEq,
|
||||
GoTypeJson: WhereModeJsonContains,
|
||||
}
|
||||
|
||||
// setDefault 设置默认属性
|
||||
func setDefault(field *sysin.GenCodesColumnListModel) {
|
||||
|
||||
setDefaultEdit(field)
|
||||
|
||||
setDefaultFormMode(field)
|
||||
|
||||
setDefaultFormRole(field)
|
||||
|
||||
setDefaultDictType(field)
|
||||
|
||||
setDefaultList(field)
|
||||
|
||||
setDefaultExport(field)
|
||||
|
||||
setDefaultQuery(field)
|
||||
|
||||
setDefaultQueryWhere(field)
|
||||
|
||||
setDefaultValue(field)
|
||||
|
||||
if field.IsAllowNull == "YES" {
|
||||
field.Required = true
|
||||
}
|
||||
|
||||
if strings.Contains(field.Index, consts.GenCodesIndexUNI) {
|
||||
field.Unique = true
|
||||
}
|
||||
|
||||
if df, ok := defaultSort[field.Name]; ok {
|
||||
field.IsSort = df
|
||||
}
|
||||
|
||||
if field.Dc == "" {
|
||||
field.Dc = field.Name
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaultEdit 设置默认编辑
|
||||
func setDefaultEdit(field *sysin.GenCodesColumnListModel) {
|
||||
field.IsEdit = true
|
||||
|
||||
if field.Index == consts.GenCodesIndexPK {
|
||||
field.IsEdit = false
|
||||
return
|
||||
}
|
||||
|
||||
if df, ok := defaultEditSwitch[field.Name]; ok {
|
||||
field.IsEdit = df
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaultFormMode 设置默认表单组件
|
||||
func setDefaultFormMode(field *sysin.GenCodesColumnListModel) {
|
||||
field.FormMode = FormModeInput
|
||||
if df, ok := defaultFormModeMap[field.TsType]; ok {
|
||||
field.FormMode = df
|
||||
}
|
||||
|
||||
if gstr.HasSuffix(field.GoName, "Status") && IsNumberType(field.GoType) {
|
||||
field.FormMode = FormModeSelect
|
||||
return
|
||||
}
|
||||
|
||||
if field.GoName == "CreatedAt" {
|
||||
field.FormMode = FormModeTimeRange
|
||||
return
|
||||
}
|
||||
|
||||
if field.DataType == "datetime" || field.DataType == "timestamp" || field.DataType == "timestamptz" {
|
||||
field.FormMode = FormModeTime
|
||||
return
|
||||
}
|
||||
|
||||
if field.DataType == "date" {
|
||||
field.FormMode = FormModeDate
|
||||
return
|
||||
}
|
||||
|
||||
if field.GoType == GoTypeString && field.Length >= 200 && field.Length <= 500 {
|
||||
field.FormMode = FormModeInputTextarea
|
||||
return
|
||||
}
|
||||
|
||||
if field.GoType == GoTypeString && field.Length > 500 {
|
||||
field.FormMode = FormModeInputEditor
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// setDefaultFormRole 设置默认表单验证
|
||||
func setDefaultFormRole(field *sysin.GenCodesColumnListModel) {
|
||||
field.FormRole = FormRoleNone
|
||||
|
||||
switch field.GoType {
|
||||
case GoTypeUint, GoTypeUint64:
|
||||
field.FormRole = FormRoleNum
|
||||
return
|
||||
}
|
||||
|
||||
if df, ok := defaultFormRoleMap[field.Name]; ok {
|
||||
field.FormRole = df
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaultDictType 设置默认字典类型
|
||||
func setDefaultDictType(field *sysin.GenCodesColumnListModel) {
|
||||
if gstr.HasSuffix(field.GoName, "Status") && IsNumberType(field.GoType) {
|
||||
field.DictType = 3 // 默认系统状态ID
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaultList 设置默认列表
|
||||
func setDefaultList(field *sysin.GenCodesColumnListModel) {
|
||||
field.IsList = true
|
||||
switch field.GoType {
|
||||
case GoTypeIntSlice, GoTypeInt64Slice, GoTypeUint64Slice, GoTypeBytes, GoTypeJson:
|
||||
field.IsList = false
|
||||
return
|
||||
}
|
||||
|
||||
if field.Length >= 500 {
|
||||
field.IsList = false
|
||||
return
|
||||
}
|
||||
|
||||
if df, ok := defaultListSwitch[field.Name]; ok {
|
||||
field.IsList = df
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// setDefaultExport 设置默认导出
|
||||
func setDefaultExport(field *sysin.GenCodesColumnListModel) {
|
||||
field.IsExport = true
|
||||
switch field.GoType {
|
||||
case GoTypeIntSlice, GoTypeInt64Slice, GoTypeUint64Slice, GoTypeBytes, GoTypeJson:
|
||||
field.IsExport = false
|
||||
return
|
||||
}
|
||||
|
||||
if field.Length >= 500 {
|
||||
field.IsExport = false
|
||||
return
|
||||
}
|
||||
|
||||
if df, ok := defaultExportSwitch[field.Name]; ok {
|
||||
field.IsExport = df
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// setDefaultQuery 设置默认查询
|
||||
func setDefaultQuery(field *sysin.GenCodesColumnListModel) {
|
||||
field.IsQuery = false
|
||||
|
||||
if field.Index == consts.GenCodesIndexPK {
|
||||
field.IsQuery = true
|
||||
return
|
||||
}
|
||||
|
||||
if field.Index == consts.GenCodesIndexPK {
|
||||
field.IsQuery = true
|
||||
return
|
||||
}
|
||||
|
||||
if gstr.HasSuffix(field.GoName, "Status") && IsNumberType(field.GoType) {
|
||||
field.IsQuery = true
|
||||
return
|
||||
}
|
||||
|
||||
if field.GoName == "CreatedAt" {
|
||||
field.IsQuery = true
|
||||
return
|
||||
}
|
||||
|
||||
if df, ok := defaultQuerySwitch[field.Name]; ok {
|
||||
field.IsQuery = df
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaultQueryWhere 设置默认查询条件
|
||||
func setDefaultQueryWhere(field *sysin.GenCodesColumnListModel) {
|
||||
field.QueryWhere = WhereModeEq
|
||||
|
||||
if field.GoName == "CreatedAt" {
|
||||
field.QueryWhere = WhereModeBetween
|
||||
return
|
||||
}
|
||||
|
||||
if field.Length >= 500 {
|
||||
field.QueryWhere = WhereModeLikeAll
|
||||
return
|
||||
}
|
||||
|
||||
if df, ok := defaultWhereModeMap[field.GoType]; ok {
|
||||
field.QueryWhere = df
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaultValue 设置默认value
|
||||
func setDefaultValue(field *sysin.GenCodesColumnListModel) {
|
||||
var value interface{}
|
||||
if field.DefaultValue == nil {
|
||||
switch field.GoType {
|
||||
case GoTypeString, GoTypeBytes, GoTypeDate, GoTypeDatetime, GoTypeTime, GoTypeGTime:
|
||||
value = ""
|
||||
case GoTypeIntSlice, GoTypeInt64Slice, GoTypeUint64Slice, GoTypeJson:
|
||||
value = nil
|
||||
case GoTypeInt, GoTypeUint, GoTypeInt64, GoTypeUint64:
|
||||
value = 0
|
||||
case GoTypeBool:
|
||||
value = false
|
||||
}
|
||||
} else {
|
||||
value = consts.ConvType(field.DefaultValue, field.GoType)
|
||||
}
|
||||
|
||||
field.DefaultValue = value
|
||||
}
|
||||
248
server/internal/library/hggen/views/column_map.go
Normal file
248
server/internal/library/hggen/views/column_map.go
Normal file
@@ -0,0 +1,248 @@
|
||||
// Package views
|
||||
// @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 views
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
)
|
||||
|
||||
// 字段映射关系
|
||||
|
||||
// go类型
|
||||
const (
|
||||
GoTypeString = "string"
|
||||
GoTypeDate = "date"
|
||||
GoTypeDatetime = "datetime"
|
||||
GoTypeInt = "int"
|
||||
GoTypeUint = "uint"
|
||||
GoTypeInt64 = "int64"
|
||||
GoTypeUint64 = "uint64"
|
||||
GoTypeIntSlice = "[]int"
|
||||
GoTypeInt64Slice = "[]int64"
|
||||
GoTypeUint64Slice = "[]uint64"
|
||||
GoTypeFloat32 = "float32"
|
||||
GoTypeFloat64 = "float64"
|
||||
GoTypeBytes = "[]byte"
|
||||
GoTypeBool = "bool"
|
||||
GoTypeTime = "time.Time"
|
||||
GoTypeGTime = "*gtime.Time"
|
||||
GoTypeJson = "*gjson.Json"
|
||||
)
|
||||
|
||||
var GoTypeNameMap = map[string]string{
|
||||
GoTypeString: GoTypeString,
|
||||
GoTypeDate: GoTypeDate,
|
||||
GoTypeDatetime: GoTypeDatetime,
|
||||
GoTypeInt: GoTypeInt,
|
||||
GoTypeUint: GoTypeUint,
|
||||
GoTypeInt64: GoTypeInt64,
|
||||
GoTypeUint64: GoTypeUint64,
|
||||
GoTypeIntSlice: GoTypeIntSlice,
|
||||
GoTypeInt64Slice: GoTypeInt64Slice,
|
||||
GoTypeUint64Slice: GoTypeUint64Slice,
|
||||
GoTypeFloat32: GoTypeFloat32,
|
||||
GoTypeFloat64: GoTypeFloat64,
|
||||
GoTypeBytes: GoTypeBytes,
|
||||
GoTypeBool: GoTypeBool,
|
||||
GoTypeTime: GoTypeTime,
|
||||
GoTypeGTime: GoTypeGTime,
|
||||
GoTypeJson: GoTypeJson,
|
||||
}
|
||||
|
||||
// ts类型
|
||||
const (
|
||||
TsTypeString = "string"
|
||||
TsTypeNumber = "number"
|
||||
TsTypeBoolean = "boolean"
|
||||
TsTypeArray = "array"
|
||||
TsTypeTuple = "tuple"
|
||||
TsTypeAny = "any"
|
||||
)
|
||||
|
||||
var TsTypeNameMap = map[string]string{
|
||||
TsTypeString: TsTypeString,
|
||||
TsTypeNumber: TsTypeNumber,
|
||||
TsTypeBoolean: TsTypeBoolean,
|
||||
TsTypeArray: TsTypeArray,
|
||||
TsTypeTuple: TsTypeTuple,
|
||||
TsTypeAny: TsTypeAny,
|
||||
}
|
||||
|
||||
// ShiftMap Go -> Ts 类型转换
|
||||
var ShiftMap = map[string]string{
|
||||
GoTypeString: TsTypeString,
|
||||
GoTypeDate: TsTypeString,
|
||||
GoTypeDatetime: TsTypeString,
|
||||
GoTypeInt: TsTypeNumber,
|
||||
GoTypeUint: TsTypeNumber,
|
||||
GoTypeInt64: TsTypeNumber,
|
||||
GoTypeUint64: TsTypeNumber,
|
||||
GoTypeIntSlice: TsTypeArray,
|
||||
GoTypeInt64Slice: TsTypeArray,
|
||||
GoTypeUint64Slice: TsTypeArray,
|
||||
GoTypeFloat32: TsTypeNumber,
|
||||
GoTypeFloat64: TsTypeNumber,
|
||||
GoTypeBytes: TsTypeString,
|
||||
GoTypeBool: TsTypeBoolean,
|
||||
GoTypeTime: TsTypeString,
|
||||
GoTypeGTime: TsTypeString,
|
||||
GoTypeJson: TsTypeAny,
|
||||
}
|
||||
|
||||
// 表单组件
|
||||
const (
|
||||
FormModeInput = "Input" // 文本输入
|
||||
FormModeInputNumber = "InputNumber" // 数字输入
|
||||
FormModeInputTextarea = "InputTextarea" // 文本域
|
||||
FormModeInputEditor = "InputEditor" // 富文本
|
||||
FormModeInputDynamic = "InputDynamic" // 动态键值对
|
||||
FormModeDate = "Date" // 日期选择(Y-M-D)
|
||||
FormModeDateRange = "DateRange" // 日期范围选择
|
||||
FormModeTime = "Time" // 时间选择(Y-M-D H:i:s)
|
||||
FormModeTimeRange = "TimeRange" // 时间范围选择
|
||||
FormModeRadio = "Radio" // 单选按钮
|
||||
FormModeCheckbox = "Checkbox" // 复选按钮
|
||||
FormModeSelect = "Select" // 单选下拉框
|
||||
FormModeSelectMultiple = "SelectMultiple" // 多选下拉框
|
||||
FormModeUploadImage = "UploadImage" // 单图上传
|
||||
FormModeUploadImages = "UploadImages" // 多图上传
|
||||
FormModeUploadFile = "UploadFile" // 单文件上传
|
||||
FormModeUploadFiles = "UploadFiles" // 多文件上传
|
||||
FormModeSwitch = "Switch" // 开关
|
||||
FormModeRate = "Rate" // 评分
|
||||
)
|
||||
|
||||
var FormModes = []string{
|
||||
FormModeInput, FormModeInputNumber, FormModeInputTextarea, FormModeInputEditor, FormModeInputDynamic,
|
||||
FormModeDate, FormModeDateRange, FormModeTime, FormModeTimeRange,
|
||||
FormModeRadio, FormModeCheckbox, FormModeSelect, FormModeSelectMultiple,
|
||||
FormModeUploadImage, FormModeUploadImages, FormModeUploadFile, FormModeUploadFiles,
|
||||
FormModeSwitch,
|
||||
FormModeRate,
|
||||
}
|
||||
|
||||
var FormModeMap = map[string]string{
|
||||
FormModeInput: "文本输入",
|
||||
FormModeInputNumber: "数字输入",
|
||||
FormModeInputTextarea: "文本域",
|
||||
FormModeInputEditor: "富文本",
|
||||
FormModeInputDynamic: "动态键值对",
|
||||
FormModeDate: "日期选择(Y-M-D)",
|
||||
FormModeDateRange: "日期范围选择",
|
||||
FormModeTime: "时间选择(Y-M-D H:i:s)",
|
||||
FormModeTimeRange: "时间范围选择",
|
||||
FormModeRadio: "单选按钮",
|
||||
FormModeCheckbox: "复选按钮",
|
||||
FormModeSelect: "单选下拉框",
|
||||
FormModeSelectMultiple: "多选下拉框",
|
||||
FormModeUploadImage: "单图上传",
|
||||
FormModeUploadImages: "多图上传",
|
||||
FormModeUploadFile: "单文件上传",
|
||||
FormModeUploadFiles: "多文件上传",
|
||||
FormModeSwitch: "开关",
|
||||
FormModeRate: "评分",
|
||||
}
|
||||
|
||||
// 表单验证
|
||||
const (
|
||||
FormRoleNone = "none"
|
||||
FormRoleIp = "ip"
|
||||
FormRolePercentage = "percentage"
|
||||
FormRoleTel = "tel"
|
||||
FormRolePhone = "phone"
|
||||
FormRoleQq = "qq"
|
||||
FormRoleEmail = "email"
|
||||
FormRoleIdCard = "idCard"
|
||||
FormRoleNum = "num"
|
||||
FormRoleBankCard = "bankCard"
|
||||
FormRoleWeibo = "weibo"
|
||||
FormRoleUserName = "userName"
|
||||
FormRoleAccount = "account"
|
||||
FormRolePassword = "password"
|
||||
FormRoleAmount = "amount"
|
||||
)
|
||||
|
||||
var FormRoleMap = map[string]string{
|
||||
FormRoleNone: "不验证",
|
||||
FormRoleIp: "Ipv4或Ipv6",
|
||||
FormRolePercentage: "0-100百分比",
|
||||
FormRoleTel: "固话格式",
|
||||
FormRolePhone: "手机号",
|
||||
FormRoleQq: "QQ号码",
|
||||
FormRoleEmail: "邮箱",
|
||||
FormRoleIdCard: "身份证",
|
||||
FormRoleNum: "非零正整数",
|
||||
FormRoleBankCard: "银行卡",
|
||||
FormRoleWeibo: "微博号",
|
||||
FormRoleUserName: "用户名",
|
||||
FormRoleAccount: "账号",
|
||||
FormRolePassword: "密码",
|
||||
FormRoleAmount: "金额",
|
||||
}
|
||||
|
||||
// 查询条件
|
||||
const (
|
||||
WhereModeEq = "=" // =
|
||||
WhereModeNeq = "!=" // !=
|
||||
WhereModeGt = ">" // >
|
||||
WhereModeGte = ">=" // >=
|
||||
WhereModeLt = "<" // <
|
||||
WhereModeLte = "<=" // <=
|
||||
WhereModeIn = "IN" // IN (...)
|
||||
WhereModeNotIn = "NOT IN" // NOT IN (...)
|
||||
WhereModeBetween = "BETWEEN" // BETWEEN
|
||||
WhereModeNotBetween = "NOT BETWEEN" // NOT BETWEEN
|
||||
WhereModeLike = "LIKE" // LIKE
|
||||
WhereModeLikeAll = "LIKE %...%" // LIKE %...%
|
||||
WhereModeNotLike = "NOT LIKE" // NOT LIKE
|
||||
WhereModeJsonContains = "JSON_CONTAINS(json_doc, val)" // JSON_CONTAINS(json_doc, val[, path]) // 判断是否包含某个json值
|
||||
)
|
||||
|
||||
var WhereModes = []string{WhereModeEq,
|
||||
WhereModeNeq, WhereModeGt, WhereModeGte, WhereModeLt, WhereModeLte,
|
||||
WhereModeIn, WhereModeNotIn,
|
||||
WhereModeBetween, WhereModeNotBetween,
|
||||
WhereModeLike, WhereModeLikeAll, WhereModeNotLike,
|
||||
WhereModeJsonContains,
|
||||
}
|
||||
|
||||
// IsNumberType 是否是数字类型
|
||||
func IsNumberType(goType string) bool {
|
||||
switch goType {
|
||||
case GoTypeInt, GoTypeUint, GoTypeInt64, GoTypeUint64:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func HasColumn(masterFields []*sysin.GenCodesColumnListModel, column string) bool {
|
||||
for _, field := range masterFields {
|
||||
if field.GoName == column {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func HasMaxSort(masterFields []*sysin.GenCodesColumnListModel) bool {
|
||||
return HasColumn(masterFields, "Sort")
|
||||
}
|
||||
|
||||
func HasStatus(headOps []string, masterFields []*sysin.GenCodesColumnListModel) bool {
|
||||
if !gstr.InArray(headOps, "status") {
|
||||
return false
|
||||
}
|
||||
return HasColumn(masterFields, "Status")
|
||||
}
|
||||
|
||||
func HasSwitch(headOps []string, masterFields []*sysin.GenCodesColumnListModel) bool {
|
||||
if !gstr.InArray(headOps, "switch") {
|
||||
return false
|
||||
}
|
||||
return HasColumn(masterFields, "Switch")
|
||||
}
|
||||
632
server/internal/library/hggen/views/curd.go
Normal file
632
server/internal/library/hggen/views/curd.go
Normal file
@@ -0,0 +1,632 @@
|
||||
// Package views
|
||||
// @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 views
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"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/os/gtime"
|
||||
"github.com/gogf/gf/v2/os/gview"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/hggen/internal/cmd/gendao"
|
||||
"hotgo/internal/library/hggen/internal/utility/utils"
|
||||
"hotgo/internal/model"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/utility/convert"
|
||||
"hotgo/utility/file"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var Curd = gCurd{}
|
||||
|
||||
type gCurd struct{}
|
||||
|
||||
type CurdStep struct {
|
||||
HasMaxSort bool `json:"hasMaxSort"`
|
||||
HasAdd bool `json:"hasAdd"`
|
||||
HasBatchDel bool `json:"hasBatchDel"`
|
||||
HasExport bool `json:"hasExport"`
|
||||
HasEdit bool `json:"hasEdit"`
|
||||
HasDel bool `json:"hasDel"`
|
||||
HasView bool `json:"hasView"`
|
||||
HasStatus bool `json:"hasStatus"`
|
||||
HasSwitch bool `json:"hasSwitch"`
|
||||
HasCheck bool `json:"hasCheck"`
|
||||
HasMenu bool `json:"hasMenu"`
|
||||
}
|
||||
|
||||
type CurdOptionsJoin struct {
|
||||
Uuid string `json:"uuid"`
|
||||
LinkTable string `json:"linkTable"`
|
||||
Alias string `json:"alias"`
|
||||
LinkMode int `json:"linkMode"`
|
||||
Field string `json:"field"`
|
||||
MasterField string `json:"masterField"`
|
||||
DaoName string `json:"daoName"`
|
||||
Columns []*sysin.GenCodesColumnListModel `json:"columns"`
|
||||
}
|
||||
|
||||
type CurdOptionsMenu struct {
|
||||
Icon string `json:"icon"`
|
||||
Pid int `json:"pid"`
|
||||
Sort int `json:"sort"`
|
||||
}
|
||||
|
||||
type CurdOptions struct {
|
||||
AutoOps []string `json:"autoOps"`
|
||||
ColumnOps []string `json:"columnOps"`
|
||||
HeadOps []string `json:"headOps"`
|
||||
Join []*CurdOptionsJoin `json:"join"`
|
||||
Menu *CurdOptionsMenu `json:"menu"`
|
||||
Step *CurdStep // 转换后的流程控制条件
|
||||
dictMap g.Map // 字典选项 -> 字段映射关系
|
||||
TemplateGroup string `json:"templateGroup"`
|
||||
}
|
||||
|
||||
type CurdPreviewInput struct {
|
||||
In sysin.GenCodesPreviewInp // 提交参数
|
||||
DaoConfig gendao.CGenDaoInput // 生成dao配置
|
||||
Config *model.GenerateConfig // 生成配置
|
||||
view *gview.View // 视图模板
|
||||
content *sysin.GenCodesPreviewModel // 页面代码
|
||||
masterFields []*sysin.GenCodesColumnListModel // 主表字段属性
|
||||
pk *sysin.GenCodesColumnListModel // 主键属性
|
||||
options *CurdOptions // 生成选项
|
||||
}
|
||||
|
||||
type CurdBuildEvent map[string]func(ctx context.Context) (err error)
|
||||
|
||||
type CurdBuildInput struct {
|
||||
PreviewIn *CurdPreviewInput // 预览参数
|
||||
BeforeEvent CurdBuildEvent // 前置事件
|
||||
AfterEvent CurdBuildEvent // 后置事件
|
||||
}
|
||||
|
||||
func (l *gCurd) initInput(ctx context.Context, in *CurdPreviewInput) (err error) {
|
||||
in.content = new(sysin.GenCodesPreviewModel)
|
||||
in.content.Views = make(map[string]*sysin.GenFile)
|
||||
|
||||
// 加载主表配置
|
||||
err = in.In.MasterColumns.Scan(&in.masterFields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(in.masterFields) == 0 {
|
||||
in.masterFields, err = DoTableColumns(ctx, sysin.GenCodesColumnListInp{Name: in.In.DbName, Table: in.In.TableName}, in.DaoConfig)
|
||||
}
|
||||
|
||||
// 主键属性
|
||||
in.pk = l.getPkField(in)
|
||||
if in.pk == nil {
|
||||
return gerror.New("initInput no primary key is set in the table!")
|
||||
}
|
||||
|
||||
// 加载选项
|
||||
err = in.In.Options.Scan(&in.options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
initStep(ctx, in)
|
||||
in.options.dictMap = make(g.Map)
|
||||
in.options.TemplateGroup = "sys"
|
||||
return
|
||||
}
|
||||
|
||||
func initStep(ctx context.Context, in *CurdPreviewInput) {
|
||||
in.options.Step = new(CurdStep)
|
||||
in.options.Step.HasMaxSort = HasMaxSort(in.masterFields)
|
||||
in.options.Step.HasAdd = gstr.InArray(in.options.HeadOps, "add")
|
||||
in.options.Step.HasBatchDel = gstr.InArray(in.options.HeadOps, "batchDel")
|
||||
in.options.Step.HasExport = gstr.InArray(in.options.HeadOps, "export")
|
||||
in.options.Step.HasEdit = gstr.InArray(in.options.ColumnOps, "edit")
|
||||
in.options.Step.HasDel = gstr.InArray(in.options.ColumnOps, "del")
|
||||
in.options.Step.HasView = gstr.InArray(in.options.ColumnOps, "view")
|
||||
in.options.Step.HasStatus = HasStatus(in.options.ColumnOps, in.masterFields)
|
||||
in.options.Step.HasSwitch = HasSwitch(in.options.ColumnOps, in.masterFields)
|
||||
in.options.Step.HasCheck = gstr.InArray(in.options.ColumnOps, "check")
|
||||
in.options.Step.HasMenu = gstr.InArray(in.options.AutoOps, "genMenuPermissions")
|
||||
}
|
||||
|
||||
func (l *gCurd) loadView(ctx context.Context, in *CurdPreviewInput) error {
|
||||
view := gview.New()
|
||||
err := view.SetConfigWithMap(g.Map{
|
||||
"Paths": "./resource/template/generate/default/curd",
|
||||
"Delimiters": in.Config.Delimiters,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
view.BindFuncMap(g.Map{
|
||||
"NowYear": gtime.Now().Year, // 当前年
|
||||
"ToLower": strings.ToLower, // 全部小写
|
||||
"LcFirst": gstr.LcFirst, // 首字母小写
|
||||
"UcFirst": gstr.UcFirst, // 首字母大写
|
||||
})
|
||||
|
||||
dictOptions, err := l.generateWebModelDictOptions(ctx, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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字典选项
|
||||
})
|
||||
in.view = view
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *gCurd) DoBuild(ctx context.Context, in *CurdBuildInput) (err error) {
|
||||
preview, err := l.DoPreview(ctx, in.PreviewIn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 前置操作
|
||||
if len(in.BeforeEvent) > 0 {
|
||||
for name, f := range in.BeforeEvent {
|
||||
if gstr.InArray(in.PreviewIn.options.AutoOps, name) {
|
||||
if err = f(ctx); err != nil {
|
||||
return fmt.Errorf("in doBuild operation beforeEvent to '%s' failed::%v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var needExecSql bool
|
||||
for _, vi := range preview.Views {
|
||||
// 无需生成
|
||||
if vi.Meth != consts.GenCodesBuildMethCreate && vi.Meth != consts.GenCodesBuildMethCover {
|
||||
continue
|
||||
}
|
||||
|
||||
if gstr.Str(vi.Path, `.`) == ".sql" && !gfile.Exists(vi.Path) {
|
||||
needExecSql = true
|
||||
}
|
||||
|
||||
if err = gfile.PutContents(vi.Path, strings.TrimSpace(vi.Content)); err != nil {
|
||||
return fmt.Errorf("writing content to '%s' failed: %v", vi.Path, err)
|
||||
}
|
||||
|
||||
if gstr.Str(vi.Path, `.`) == ".sql" && needExecSql {
|
||||
if err = ImportSql(ctx, vi.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if gstr.Str(vi.Path, `.`) == ".go" {
|
||||
utils.GoFmt(vi.Path)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 后置操作
|
||||
if len(in.AfterEvent) > 0 {
|
||||
for name, f := range in.AfterEvent {
|
||||
if gstr.InArray(in.PreviewIn.options.AutoOps, name) {
|
||||
if err = f(ctx); err != nil {
|
||||
return fmt.Errorf("in doBuild operation afterEvent to '%s' failed::%v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) DoPreview(ctx context.Context, in *CurdPreviewInput) (res *sysin.GenCodesPreviewModel, err error) {
|
||||
// 初始化
|
||||
if err = l.initInput(ctx, in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 加载模板
|
||||
if err = l.loadView(ctx, in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = l.generateApiContent(ctx, in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = l.generateInputContent(ctx, in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = l.generateControllerContent(ctx, in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = l.generateLogicContent(ctx, in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = l.generateRouterContent(ctx, in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = l.generateWebApiContent(ctx, in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = l.generateWebModelContent(ctx, in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = l.generateWebIndexContent(ctx, in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = l.generateWebEditContent(ctx, in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = l.generateWebViewContent(ctx, in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = l.generateSqlContent(ctx, in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
in.content.Config = in.Config
|
||||
res = new(sysin.GenCodesPreviewModel)
|
||||
res = in.content
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) generateApiContent(ctx context.Context, in *CurdPreviewInput) (err error) {
|
||||
var (
|
||||
name = "api.go"
|
||||
tplData = g.Map{}
|
||||
genFile = new(sysin.GenFile)
|
||||
)
|
||||
genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[0].ApiPath, strings.ToLower(in.In.VarName), strings.ToLower(in.In.VarName)+".go")
|
||||
genFile.Meth = consts.GenCodesBuildMethCreate
|
||||
if gfile.Exists(genFile.Path) {
|
||||
genFile.Meth = consts.GenCodesBuildMethSkip
|
||||
}
|
||||
|
||||
genFile.Required = true
|
||||
|
||||
if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") {
|
||||
genFile.Meth = consts.GenCodesBuildMethCover
|
||||
}
|
||||
|
||||
in.content.Views[name] = genFile
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) generateInputContent(ctx context.Context, in *CurdPreviewInput) (err error) {
|
||||
var (
|
||||
name = "input.go"
|
||||
genFile = new(sysin.GenFile)
|
||||
)
|
||||
|
||||
tplData, err := l.inputTplData(ctx, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[0].InputPath, convert.CamelCaseToUnderline(in.In.VarName)+".go")
|
||||
genFile.Meth = consts.GenCodesBuildMethCreate
|
||||
if gfile.Exists(genFile.Path) {
|
||||
genFile.Meth = consts.GenCodesBuildMethSkip
|
||||
}
|
||||
genFile.Required = true
|
||||
|
||||
if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") {
|
||||
genFile.Meth = consts.GenCodesBuildMethCover
|
||||
}
|
||||
|
||||
in.content.Views[name] = genFile
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) generateControllerContent(ctx context.Context, in *CurdPreviewInput) (err error) {
|
||||
var (
|
||||
name = "controller.go"
|
||||
tplData = g.Map{"name": "test generateControllerContent..."}
|
||||
genFile = new(sysin.GenFile)
|
||||
)
|
||||
|
||||
genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[0].ControllerPath, convert.CamelCaseToUnderline(in.In.VarName)+".go")
|
||||
genFile.Meth = consts.GenCodesBuildMethCreate
|
||||
if gfile.Exists(genFile.Path) {
|
||||
genFile.Meth = consts.GenCodesBuildMethSkip
|
||||
}
|
||||
genFile.Required = true
|
||||
|
||||
if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") {
|
||||
genFile.Meth = consts.GenCodesBuildMethCover
|
||||
}
|
||||
|
||||
in.content.Views[name] = genFile
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) generateLogicContent(ctx context.Context, in *CurdPreviewInput) (err error) {
|
||||
var (
|
||||
name = "logic.go"
|
||||
genFile = new(sysin.GenFile)
|
||||
)
|
||||
|
||||
tplData, err := l.logicTplData(ctx, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[0].LogicPath, convert.CamelCaseToUnderline(in.In.VarName)+".go")
|
||||
genFile.Meth = consts.GenCodesBuildMethCreate
|
||||
if gfile.Exists(genFile.Path) {
|
||||
genFile.Meth = consts.GenCodesBuildMethSkip
|
||||
}
|
||||
genFile.Required = true
|
||||
|
||||
if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") {
|
||||
genFile.Meth = consts.GenCodesBuildMethCover
|
||||
}
|
||||
|
||||
in.content.Views[name] = genFile
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) generateRouterContent(ctx context.Context, in *CurdPreviewInput) (err error) {
|
||||
var (
|
||||
name = "router.go"
|
||||
tplData = g.Map{}
|
||||
genFile = new(sysin.GenFile)
|
||||
)
|
||||
genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[0].RouterPath, convert.CamelCaseToUnderline(in.In.VarName)+".go")
|
||||
genFile.Meth = consts.GenCodesBuildMethCreate
|
||||
if gfile.Exists(genFile.Path) {
|
||||
genFile.Meth = consts.GenCodesBuildMethSkip
|
||||
}
|
||||
genFile.Required = true
|
||||
|
||||
if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") {
|
||||
genFile.Meth = consts.GenCodesBuildMethCover
|
||||
}
|
||||
|
||||
in.content.Views[name] = genFile
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebApiContent(ctx context.Context, in *CurdPreviewInput) (err error) {
|
||||
var (
|
||||
name = "web.api.ts"
|
||||
tplData = g.Map{}
|
||||
genFile = new(sysin.GenFile)
|
||||
)
|
||||
genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[0].WebApiPath, gstr.LcFirst(in.In.VarName), "index.ts")
|
||||
genFile.Meth = consts.GenCodesBuildMethCreate
|
||||
if gfile.Exists(genFile.Path) {
|
||||
genFile.Meth = consts.GenCodesBuildMethSkip
|
||||
}
|
||||
genFile.Required = true
|
||||
|
||||
if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") {
|
||||
genFile.Meth = consts.GenCodesBuildMethCover
|
||||
}
|
||||
|
||||
in.content.Views[name] = genFile
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebModelContent(ctx context.Context, in *CurdPreviewInput) (err error) {
|
||||
var (
|
||||
name = "web.model.ts"
|
||||
genFile = new(sysin.GenFile)
|
||||
)
|
||||
|
||||
tplData, err := l.webModelTplData(ctx, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[0].WebViewsPath, gstr.LcFirst(in.In.VarName), "model.ts")
|
||||
genFile.Meth = consts.GenCodesBuildMethCreate
|
||||
if gfile.Exists(genFile.Path) {
|
||||
genFile.Meth = consts.GenCodesBuildMethSkip
|
||||
}
|
||||
genFile.Required = true
|
||||
|
||||
if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") {
|
||||
genFile.Meth = consts.GenCodesBuildMethCover
|
||||
}
|
||||
in.content.Views[name] = genFile
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebIndexContent(ctx context.Context, in *CurdPreviewInput) (err error) {
|
||||
var (
|
||||
name = "web.index.vue"
|
||||
genFile = new(sysin.GenFile)
|
||||
)
|
||||
|
||||
tplData, err := l.webIndexTplData(ctx, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[0].WebViewsPath, gstr.LcFirst(in.In.VarName), "index.vue")
|
||||
genFile.Meth = consts.GenCodesBuildMethCreate
|
||||
if gfile.Exists(genFile.Path) {
|
||||
genFile.Meth = consts.GenCodesBuildMethSkip
|
||||
}
|
||||
genFile.Required = true
|
||||
|
||||
if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") {
|
||||
genFile.Meth = consts.GenCodesBuildMethCover
|
||||
}
|
||||
in.content.Views[name] = genFile
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebEditContent(ctx context.Context, in *CurdPreviewInput) (err error) {
|
||||
var (
|
||||
name = "web.edit.vue"
|
||||
genFile = new(sysin.GenFile)
|
||||
)
|
||||
|
||||
tplData, err := l.webEditTplData(ctx, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[0].WebViewsPath, gstr.LcFirst(in.In.VarName), "edit.vue")
|
||||
genFile.Meth = consts.GenCodesBuildMethCreate
|
||||
if gfile.Exists(genFile.Path) {
|
||||
genFile.Meth = consts.GenCodesBuildMethSkip
|
||||
}
|
||||
genFile.Required = true
|
||||
if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") {
|
||||
genFile.Meth = consts.GenCodesBuildMethCover
|
||||
}
|
||||
|
||||
if !in.options.Step.HasEdit {
|
||||
genFile.Meth = consts.GenCodesBuildIgnore
|
||||
genFile.Required = false
|
||||
}
|
||||
|
||||
in.content.Views[name] = genFile
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebViewContent(ctx context.Context, in *CurdPreviewInput) (err error) {
|
||||
var (
|
||||
name = "web.view.vue"
|
||||
genFile = new(sysin.GenFile)
|
||||
)
|
||||
|
||||
tplData, err := l.webViewTplData(ctx, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[0].WebViewsPath, gstr.LcFirst(in.In.VarName), "view.vue")
|
||||
genFile.Meth = consts.GenCodesBuildMethCreate
|
||||
if gfile.Exists(genFile.Path) {
|
||||
genFile.Meth = consts.GenCodesBuildMethSkip
|
||||
}
|
||||
genFile.Required = true
|
||||
|
||||
if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") {
|
||||
genFile.Meth = consts.GenCodesBuildMethCover
|
||||
}
|
||||
|
||||
if !in.options.Step.HasView {
|
||||
genFile.Meth = consts.GenCodesBuildIgnore
|
||||
genFile.Required = false
|
||||
}
|
||||
|
||||
in.content.Views[name] = genFile
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) generateSqlContent(ctx context.Context, in *CurdPreviewInput) (err error) {
|
||||
var (
|
||||
name = "source.sql"
|
||||
config = g.DB(in.In.DbName).GetConfig()
|
||||
tplData = g.Map{
|
||||
"dbName": config.Name,
|
||||
"menuTable": config.Prefix + "admin_menu",
|
||||
"mainComponent": "LAYOUT",
|
||||
}
|
||||
genFile = new(sysin.GenFile)
|
||||
)
|
||||
|
||||
if in.options.Menu.Pid > 0 {
|
||||
tplData["mainComponent"] = "ParentLayout" //gstr.LcFirst(in.In.VarName)
|
||||
}
|
||||
|
||||
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[0].SqlPath, convert.CamelCaseToUnderline(in.In.VarName)+"_menu.sql")
|
||||
genFile.Meth = consts.GenCodesBuildMethCreate
|
||||
if gfile.Exists(genFile.Path) {
|
||||
genFile.Meth = consts.GenCodesBuildMethSkip
|
||||
}
|
||||
genFile.Required = true
|
||||
|
||||
if !in.options.Step.HasMenu {
|
||||
genFile.Meth = consts.GenCodesBuildIgnore
|
||||
genFile.Required = false
|
||||
}
|
||||
|
||||
tplData["generatePath"] = genFile.Path
|
||||
genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
in.content.Views[name] = genFile
|
||||
return
|
||||
}
|
||||
126
server/internal/library/hggen/views/curd_generate_input.go
Normal file
126
server/internal/library/hggen/views/curd_generate_input.go
Normal file
@@ -0,0 +1,126 @@
|
||||
// Package views
|
||||
// @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 views
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
)
|
||||
|
||||
const (
|
||||
InputTypeListInp = 1 // 列表输入
|
||||
InputTypeListModel = 2 // 列表输出
|
||||
InputTypeExportModel = 3 // 列表导出
|
||||
)
|
||||
|
||||
func (l *gCurd) inputTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) {
|
||||
data = make(g.Map)
|
||||
data["listInpColumns"] = l.generateInputListColumns(ctx, in, InputTypeListInp)
|
||||
data["listModelColumns"] = l.generateInputListColumns(ctx, in, InputTypeListModel)
|
||||
data["exportModelColumns"] = l.generateInputListColumns(ctx, in, InputTypeExportModel)
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) generateInputListColumns(ctx context.Context, in *CurdPreviewInput, inputType int) string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
index := 0
|
||||
array := make([][]string, 1000)
|
||||
// 主表
|
||||
for _, field := range in.masterFields {
|
||||
row := l.generateStructFieldDefinition(field, inputType)
|
||||
if row == nil {
|
||||
continue
|
||||
}
|
||||
array[index] = row
|
||||
index++
|
||||
}
|
||||
|
||||
// 关联表
|
||||
if len(in.options.Join) > 0 {
|
||||
for _, v := range in.options.Join {
|
||||
if !isEffectiveJoin(v) {
|
||||
continue
|
||||
}
|
||||
for _, field := range v.Columns {
|
||||
row := l.generateStructFieldDefinition(field, inputType)
|
||||
if row != nil {
|
||||
array[index] = row
|
||||
index++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tw := tablewriter.NewWriter(buffer)
|
||||
tw.SetBorder(false)
|
||||
tw.SetRowLine(false)
|
||||
tw.SetAutoWrapText(false)
|
||||
tw.SetColumnSeparator("")
|
||||
tw.AppendBulk(array)
|
||||
tw.Render()
|
||||
stContent := buffer.String()
|
||||
// Let's do this hack of table writer for indent!
|
||||
stContent = gstr.Replace(stContent, " #", "")
|
||||
stContent = gstr.Replace(stContent, "` ", "`")
|
||||
stContent = gstr.Replace(stContent, "``", "")
|
||||
stContent = removeEndWrap(stContent)
|
||||
|
||||
buffer.Reset()
|
||||
buffer.WriteString(stContent)
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// generateStructFieldForModel generates and returns the attribute definition for specified field.
|
||||
func (l *gCurd) generateStructFieldDefinition(field *sysin.GenCodesColumnListModel, inputType int) []string {
|
||||
var (
|
||||
tagKey = "`"
|
||||
result = []string{" #" + field.GoName}
|
||||
descriptionTag = gstr.Replace(formatComment(field.Dc), `"`, `\"`)
|
||||
)
|
||||
|
||||
switch inputType {
|
||||
case InputTypeListInp:
|
||||
if !field.IsQuery {
|
||||
return nil
|
||||
}
|
||||
|
||||
if field.QueryWhere == WhereModeBetween {
|
||||
result = append(result, " #[]"+field.GoType)
|
||||
} else {
|
||||
result = append(result, " #"+field.GoType)
|
||||
}
|
||||
result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, field.TsName))
|
||||
result = append(result, " #"+fmt.Sprintf(`dc:"%s"`+tagKey, descriptionTag))
|
||||
|
||||
case InputTypeListModel:
|
||||
if !field.IsList {
|
||||
return nil
|
||||
}
|
||||
|
||||
result = append(result, " #"+field.GoType)
|
||||
result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, field.TsName))
|
||||
result = append(result, " #"+fmt.Sprintf(`dc:"%s"`+tagKey, descriptionTag))
|
||||
case InputTypeExportModel:
|
||||
if !field.IsExport {
|
||||
return nil
|
||||
}
|
||||
|
||||
result = append(result, " #"+field.GoType)
|
||||
result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, field.TsName))
|
||||
result = append(result, " #"+fmt.Sprintf(`dc:"%s"`+tagKey, descriptionTag))
|
||||
|
||||
default:
|
||||
panic("inputType is invalid")
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
215
server/internal/library/hggen/views/curd_generate_logic.go
Normal file
215
server/internal/library/hggen/views/curd_generate_logic.go
Normal file
@@ -0,0 +1,215 @@
|
||||
// Package views
|
||||
// @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 views
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
)
|
||||
|
||||
const (
|
||||
LogicWhereComments = "\n\t// 查询%s\n"
|
||||
LogicWhereNoSupport = "\t// TODO 暂不支持生成[ %s ]查询方式,请自行补充此处代码!"
|
||||
LogicListSimpleSelect = "\tfields, err := hgorm.GenSelect(ctx, sysin.%sListModel{}, dao.%s)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}"
|
||||
LogicListJoinSelect = "\t//关联表select\n\tfields, err := hgorm.GenJoinSelect(ctx, %sin.%sListModel{}, dao.%s, []*hgorm.Join{\n%v\t})"
|
||||
LogicListJoinOnRelation = "\t// 关联表%s\n\tmod = mod.%s(hgorm.GenJoinOnRelation(\n\t\tdao.%s.Table(), dao.%s.Columns().%s, // 主表表名,关联条件\n\t\tdao.%s.Table(), \"%s\", dao.%s.Columns().%s, // 关联表表名,别名,关联条件\n\t)...)\n\n"
|
||||
LogicEditUpdate = "\t\t_, err = dao.%s.Ctx(ctx).\n\t\t\tFieldsEx(\n%s\t\t\t).\n\t\t\tWhere(dao.%s.Columns().%s, in.%s).Data(in).Update()\n\t\tif err != nil {\n\t\t\terr = gerror.Wrap(err, consts.ErrorORM)\n\t\t\treturn err\n\t\t}\n\t\treturn nil"
|
||||
LogicEditInsert = "\t_, err = dao.%s.Ctx(ctx).\n\t\tFieldsEx(\n%s\t\t).\n\t\tData(in).Insert()\n\tif err != nil {\n\t\terr = gerror.Wrap(err, consts.ErrorORM)\n\t\treturn err\n\t}"
|
||||
)
|
||||
|
||||
func (l *gCurd) logicTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) {
|
||||
data = make(g.Map)
|
||||
data["listWhere"] = l.generateLogicListWhere(ctx, in)
|
||||
data["listJoin"] = l.generateLogicListJoin(ctx, in)
|
||||
data["listOrder"] = l.generateLogicListOrder(ctx, in)
|
||||
data["edit"] = l.generateLogicEdit(ctx, in)
|
||||
data["switchFields"] = l.generateLogicSwitchFields(ctx, in)
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) generateLogicSwitchFields(ctx context.Context, in *CurdPreviewInput) string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if in.options.Step.HasSwitch {
|
||||
buffer.WriteString("\t\tdao." + in.In.DaoName + ".Columns().Switch,\n")
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (l *gCurd) generateLogicEdit(ctx context.Context, in *CurdPreviewInput) g.Map {
|
||||
var (
|
||||
data = make(g.Map)
|
||||
updateFieldsEx = ""
|
||||
updateBuffer = bytes.NewBuffer(nil)
|
||||
insertFieldsEx = ""
|
||||
insertBuffer = bytes.NewBuffer(nil)
|
||||
)
|
||||
|
||||
for _, field := range in.masterFields {
|
||||
if field.GoName == "UpdatedBy" {
|
||||
updateBuffer.WriteString("\t\tin.UpdatedBy = contexts.GetUserId(ctx)\n")
|
||||
}
|
||||
|
||||
if field.GoName == "CreatedBy" {
|
||||
insertBuffer.WriteString("\tin.CreatedBy = contexts.GetUserId(ctx)\n")
|
||||
}
|
||||
|
||||
if field.Index == consts.GenCodesIndexPK || field.GoName == "CreatedAt" || field.GoName == "CreatedBy" || field.GoName == "DeletedAt" {
|
||||
updateFieldsEx = updateFieldsEx + "\t\t\t\tdao." + in.In.DaoName + ".Columns()." + field.GoName + ",\n"
|
||||
}
|
||||
|
||||
if field.Index == consts.GenCodesIndexPK || field.GoName == "UpdatedBy" || field.GoName == "DeletedAt" {
|
||||
insertFieldsEx = insertFieldsEx + "\t\t\t\tdao." + in.In.DaoName + ".Columns()." + field.GoName + ",\n"
|
||||
}
|
||||
}
|
||||
|
||||
updateBuffer.WriteString(fmt.Sprintf(LogicEditUpdate, in.In.DaoName, updateFieldsEx, in.In.DaoName, in.pk.GoName, in.pk.GoName))
|
||||
insertBuffer.WriteString(fmt.Sprintf(LogicEditInsert, in.In.DaoName, insertFieldsEx))
|
||||
|
||||
data["update"] = updateBuffer.String()
|
||||
data["insert"] = insertBuffer.String()
|
||||
return data
|
||||
}
|
||||
|
||||
func (l *gCurd) generateLogicListOrder(ctx context.Context, in *CurdPreviewInput) string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if in.options.Step.HasMaxSort {
|
||||
buffer.WriteString("OrderAsc(dao." + in.In.DaoName + ".Columns().Sort).")
|
||||
}
|
||||
buffer.WriteString("OrderDesc(dao." + in.In.DaoName + ".Columns()." + in.pk.GoName + ")")
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (l *gCurd) generateLogicListJoin(ctx context.Context, in *CurdPreviewInput) g.Map {
|
||||
var data = make(g.Map)
|
||||
data["link"] = ""
|
||||
if hasEffectiveJoins(in.options.Join) {
|
||||
var (
|
||||
selectBuffer = bytes.NewBuffer(nil)
|
||||
linkBuffer = bytes.NewBuffer(nil)
|
||||
joinSelectRows string
|
||||
)
|
||||
|
||||
for _, join := range in.options.Join {
|
||||
if isEffectiveJoin(join) {
|
||||
joinSelectRows = joinSelectRows + fmt.Sprintf("\t\t{Dao: dao.%s, Alias: \"%s\"},\n", join.DaoName, join.Alias)
|
||||
linkBuffer.WriteString(fmt.Sprintf(LogicListJoinOnRelation, join.Alias, consts.GenCodesJoinLinkMap[join.LinkMode], in.In.DaoName, in.In.DaoName, gstr.CaseCamel(join.MasterField), join.DaoName, join.Alias, join.DaoName, gstr.CaseCamel(join.Field)))
|
||||
}
|
||||
}
|
||||
|
||||
selectBuffer.WriteString(fmt.Sprintf(LogicListJoinSelect, in.options.TemplateGroup, in.In.VarName, in.In.DaoName, joinSelectRows))
|
||||
|
||||
data["select"] = selectBuffer.String()
|
||||
data["link"] = linkBuffer.String()
|
||||
|
||||
} else {
|
||||
data["select"] = fmt.Sprintf(LogicListSimpleSelect, in.In.VarName, in.In.DaoName)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (l *gCurd) generateLogicListWhere(ctx context.Context, in *CurdPreviewInput) string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
|
||||
// 主表
|
||||
l.generateLogicListWhereEach(buffer, in.masterFields, in.In.DaoName, "")
|
||||
|
||||
// 关联表
|
||||
if hasEffectiveJoins(in.options.Join) {
|
||||
for _, v := range in.options.Join {
|
||||
if isEffectiveJoin(v) {
|
||||
l.generateLogicListWhereEach(buffer, v.Columns, v.DaoName, v.Alias)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (l *gCurd) generateLogicListWhereEach(buffer *bytes.Buffer, fields []*sysin.GenCodesColumnListModel, daoName string, alias string) {
|
||||
isLink := false
|
||||
if alias != "" {
|
||||
alias = `"` + alias + `."+`
|
||||
isLink = true
|
||||
}
|
||||
for _, field := range fields {
|
||||
if !field.IsQuery || field.QueryWhere == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var (
|
||||
linkMode string
|
||||
whereTag string
|
||||
columnName string
|
||||
)
|
||||
|
||||
if IsNumberType(field.GoType) {
|
||||
linkMode = `in.` + field.GoName + ` > 0`
|
||||
} else if field.GoType == GoTypeGTime {
|
||||
linkMode = `in.` + field.GoName + ` != nil`
|
||||
} else if field.GoType == GoTypeJson {
|
||||
linkMode = `!in.` + field.GoName + `.IsNil()`
|
||||
} else {
|
||||
linkMode = `in.` + field.GoName + ` != ""`
|
||||
}
|
||||
|
||||
if field.QueryWhere == WhereModeBetween || field.QueryWhere == WhereModeNotBetween {
|
||||
linkMode = `len(in.` + field.GoName + `) == 2`
|
||||
}
|
||||
|
||||
buffer.WriteString(fmt.Sprintf(LogicWhereComments, field.Dc))
|
||||
|
||||
// 如果是关联表重新转换字段
|
||||
columnName = field.GoName
|
||||
if isLink {
|
||||
columnName = gstr.CaseCamel(field.Name)
|
||||
}
|
||||
|
||||
switch field.QueryWhere {
|
||||
case WhereModeEq:
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.Where(" + alias + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}"
|
||||
case WhereModeNeq:
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereNot(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}"
|
||||
case WhereModeGt:
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereGT(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}"
|
||||
case WhereModeGte:
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereGTE(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}"
|
||||
case WhereModeLt:
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereLT(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}"
|
||||
case WhereModeLte:
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereLTE(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}"
|
||||
case WhereModeIn:
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereIn(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}"
|
||||
case WhereModeNotIn:
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereNotIn(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}"
|
||||
case WhereModeBetween:
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereBetween(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + "[0], in." + field.GoName + "[1])\n\t}"
|
||||
case WhereModeNotBetween:
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereNotBetween(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + "[0], in." + field.GoName + "[1])\n\t}"
|
||||
case WhereModeLike:
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereLike(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}"
|
||||
case WhereModeLikeAll:
|
||||
val := `"%"+in.` + field.GoName + `+"%"`
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereLike(dao." + daoName + ".Columns()." + columnName + ", " + val + ")\n\t}"
|
||||
case WhereModeNotLike:
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereNotLike(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}"
|
||||
case WhereModeJsonContains:
|
||||
val := "fmt.Sprintf(`JSON_CONTAINS(%s,'%v')`, dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")"
|
||||
whereTag = "\tif in." + field.GoName + linkMode + " {\n\t\tmod = mod.Where(" + val + ")\n\t}"
|
||||
|
||||
default:
|
||||
buffer.WriteString(fmt.Sprintf(LogicWhereNoSupport, field.QueryWhere))
|
||||
}
|
||||
|
||||
buffer.WriteString(whereTag + "\n")
|
||||
}
|
||||
}
|
||||
158
server/internal/library/hggen/views/curd_generate_web_edit.go
Normal file
158
server/internal/library/hggen/views/curd_generate_web_edit.go
Normal file
@@ -0,0 +1,158 @@
|
||||
// Package views
|
||||
// @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 views
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"hotgo/internal/consts"
|
||||
)
|
||||
|
||||
func (l *gCurd) webEditTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) {
|
||||
data = make(g.Map)
|
||||
data["formItem"] = l.generateWebEditFormItem(ctx, in)
|
||||
data["script"] = l.generateWebEditScript(ctx, in)
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebEditFormItem(ctx context.Context, in *CurdPreviewInput) string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
for k, field := range in.masterFields {
|
||||
if !field.IsEdit {
|
||||
continue
|
||||
}
|
||||
|
||||
if field.Index == consts.GenCodesIndexPK {
|
||||
continue
|
||||
}
|
||||
|
||||
var (
|
||||
defaultComponent = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-input placeholder=\"请输入%s\" v-model:value=\"params.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.Dc, field.TsName)
|
||||
component string
|
||||
)
|
||||
|
||||
switch field.FormMode {
|
||||
case FormModeInput:
|
||||
component = defaultComponent
|
||||
|
||||
case FormModeInputNumber:
|
||||
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-input-number placeholder=\"请输入%s\" v-model:value=\"params.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.Dc, field.TsName)
|
||||
|
||||
case FormModeInputTextarea:
|
||||
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-input type=\"textarea\" placeholder=\"%s\" v-model:value=\"params.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.Dc, field.TsName)
|
||||
|
||||
case FormModeInputEditor:
|
||||
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <Editor style=\"height: 450px\" v-model:value=\"params.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
|
||||
|
||||
case FormModeInputDynamic:
|
||||
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-dynamic-input\n v-model:value=\"params.%s\"\n preset=\"pair\"\n key-placeholder=\"键名\"\n value-placeholder=\"键值\"\n />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
|
||||
|
||||
case FormModeDate:
|
||||
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <DatePicker v-model:formValue=\"params.%s\" type=\"date\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
|
||||
|
||||
//case FormModeDateRange: // 必须要有两个字段,后面优化下
|
||||
|
||||
case FormModeTime:
|
||||
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <DatePicker v-model:formValue=\"params.%s\" type=\"datetime\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
|
||||
|
||||
//case FormModeTimeRange: // 必须要有两个字段,后面优化下
|
||||
|
||||
case FormModeRadio:
|
||||
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-radio-group v-model:value=\"params.%s\" name=\"%s\">\n <n-radio-button\n v-for=\"%s in options.%s\"\n :key=\"%s.value\"\n :value=\"%s.value\"\n :label=\"%s.label\"\n />\n </n-radio-group>\n </n-form-item>", field.Dc, field.TsName, field.TsName, field.TsName, field.TsName, in.options.dictMap[field.TsName], field.TsName, field.TsName, field.TsName)
|
||||
|
||||
case FormModeCheckbox:
|
||||
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-checkbox-group v-model:value=\"params.%s\">\n <n-space>\n <n-checkbox\n v-for=\"item in options.%s\"\n :key=\"item.value\"\n :value=\"item.value\"\n :label=\"item.label\"\n />\n </n-space>\n </n-checkbox-group>\n </n-form-item>", field.Dc, field.TsName, field.TsName, in.options.dictMap[field.TsName])
|
||||
|
||||
case FormModeSelect:
|
||||
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-select v-model:value=\"params.%s\" :options=\"options.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName, in.options.dictMap[field.TsName])
|
||||
|
||||
case FormModeSelectMultiple:
|
||||
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-select multiple v-model:value=\"params.%s\" :options=\"options.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName, in.options.dictMap[field.TsName])
|
||||
|
||||
case FormModeUploadImage:
|
||||
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <UploadImage :maxNumber=\"1\" v-model:value=\"params.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
|
||||
|
||||
case FormModeUploadImages:
|
||||
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <UploadImage :maxNumber=\"10\" v-model:value=\"params.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
|
||||
|
||||
case FormModeUploadFile:
|
||||
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <UploadFile :maxNumber=\"1\" v-model:value=\"params.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
|
||||
|
||||
case FormModeUploadFiles:
|
||||
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <UploadFile :maxNumber=\"10\" v-model:value=\"params.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
|
||||
|
||||
case FormModeSwitch:
|
||||
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-switch v-model:value=\"params.%s\"\n />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
|
||||
|
||||
case FormModeRate:
|
||||
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-rate allow-half :default-value=\"params.%s\" :on-update:value=\"update%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName, field.GoName)
|
||||
|
||||
default:
|
||||
component = defaultComponent
|
||||
}
|
||||
|
||||
if len(in.masterFields) == k {
|
||||
buffer.WriteString(" " + component)
|
||||
} else {
|
||||
buffer.WriteString(" " + component + "\n\n")
|
||||
}
|
||||
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebEditScript(ctx context.Context, in *CurdPreviewInput) g.Map {
|
||||
var (
|
||||
data = make(g.Map)
|
||||
importBuffer = bytes.NewBuffer(nil)
|
||||
setupBuffer = bytes.NewBuffer(nil)
|
||||
)
|
||||
|
||||
if in.options.Step.HasMaxSort {
|
||||
importBuffer.WriteString(" import { onMounted, ref, computed, watch } from 'vue';\n")
|
||||
importBuffer.WriteString(" import { Edit, MaxSort } from '@/api/" + gstr.LcFirst(in.In.VarName) + "';\n")
|
||||
setupBuffer.WriteString(" watch(\n () => params.value,\n (value) => {\n if (value.id === 0) {\n MaxSort().then((res) => {\n params.value.sort = res.sort;\n });\n }\n }\n );\n\n")
|
||||
} else {
|
||||
importBuffer.WriteString(" import { onMounted, ref, computed } from 'vue';\n")
|
||||
importBuffer.WriteString(" import { Edit } from '@/api/" + gstr.LcFirst(in.In.VarName) + "';\n")
|
||||
}
|
||||
|
||||
for _, field := range in.masterFields {
|
||||
if !field.IsEdit {
|
||||
continue
|
||||
}
|
||||
switch field.FormMode {
|
||||
case FormModeDate, FormModeDateRange, FormModeTime, FormModeTimeRange:
|
||||
if !gstr.Contains(importBuffer.String(), `import DatePicker`) {
|
||||
importBuffer.WriteString(" import DatePicker from '@/components/DatePicker/datePicker.vue';\n")
|
||||
}
|
||||
case FormModeInputEditor:
|
||||
if !gstr.Contains(importBuffer.String(), `import Editor`) {
|
||||
importBuffer.WriteString(" import Editor from '@/components/Editor/editor.vue';\n")
|
||||
}
|
||||
case FormModeUploadImage, FormModeUploadImages:
|
||||
if !gstr.Contains(importBuffer.String(), `import UploadImage`) {
|
||||
importBuffer.WriteString(" import UploadImage from '@/components/Upload/uploadImage.vue';\n")
|
||||
}
|
||||
case FormModeUploadFile, FormModeUploadFiles:
|
||||
if !gstr.Contains(importBuffer.String(), `import UploadFile`) {
|
||||
importBuffer.WriteString(" import UploadFile from '@/components/Upload/uploadFile.vue';\n")
|
||||
}
|
||||
case FormModeRate:
|
||||
setupBuffer.WriteString(fmt.Sprintf(" function update%s(num) {\n params.value.%s = num;\n }\n", field.GoName, field.TsName))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data["import"] = importBuffer.String()
|
||||
data["setup"] = setupBuffer.String()
|
||||
|
||||
return data
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Package views
|
||||
// @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 views
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
func (l *gCurd) webIndexTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) {
|
||||
data = make(g.Map)
|
||||
return
|
||||
}
|
||||
285
server/internal/library/hggen/views/curd_generate_web_model.go
Normal file
285
server/internal/library/hggen/views/curd_generate_web_model.go
Normal file
@@ -0,0 +1,285 @@
|
||||
// Package views
|
||||
// @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 views
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/utility/convert"
|
||||
)
|
||||
|
||||
const (
|
||||
ModelLoadOptionsTemplate = "async function loadOptions() {\n options.value = await Dicts({\n types: [\n %v ],\n });\n for (const item of schemas.value) {\n switch (item.field) {\n%v }\n }\n}\n\nawait loadOptions();"
|
||||
)
|
||||
|
||||
func (l *gCurd) webModelTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) {
|
||||
data = make(g.Map)
|
||||
data["state"] = l.generateWebModelState(ctx, in)
|
||||
data["defaultState"] = l.generateWebModelDefaultState(ctx, in)
|
||||
data["rules"] = l.generateWebModelRules(ctx, in)
|
||||
data["formSchema"] = l.generateWebModelFormSchema(ctx, in)
|
||||
data["columns"] = l.generateWebModelColumns(ctx, in)
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebModelState(ctx context.Context, in *CurdPreviewInput) string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
buffer.WriteString("export interface State {\n")
|
||||
for _, field := range in.masterFields {
|
||||
buffer.WriteString(fmt.Sprintf(" %s: %s;\n", field.TsName, field.TsType))
|
||||
}
|
||||
buffer.WriteString("}")
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebModelDefaultState(ctx context.Context, in *CurdPreviewInput) string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
buffer.WriteString("export const defaultState = {\n")
|
||||
for _, field := range in.masterFields {
|
||||
var value = field.DefaultValue
|
||||
if value == nil {
|
||||
value = "null"
|
||||
}
|
||||
if value == "" {
|
||||
value = "''"
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf(" %s: %v,\n", field.TsName, value))
|
||||
}
|
||||
buffer.WriteString("};")
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebModelDictOptions(ctx context.Context, in *CurdPreviewInput) (g.Map, error) {
|
||||
type DictType struct {
|
||||
Id int64 `json:"id"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
var (
|
||||
options = make(g.Map)
|
||||
dictTypeIds []int64
|
||||
dictTypeList []*DictType
|
||||
)
|
||||
|
||||
for _, field := range in.masterFields {
|
||||
if field.DictType > 0 {
|
||||
dictTypeIds = append(dictTypeIds, field.DictType)
|
||||
}
|
||||
}
|
||||
|
||||
dictTypeIds = convert.UniqueSliceInt64(dictTypeIds)
|
||||
if len(dictTypeIds) == 0 {
|
||||
options["has"] = false
|
||||
return options, nil
|
||||
}
|
||||
|
||||
err := g.Model("sys_dict_type").Ctx(ctx).
|
||||
Fields("id", "type").
|
||||
WhereIn("id", dictTypeIds).
|
||||
Scan(&dictTypeList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(dictTypeList) == 0 {
|
||||
options["has"] = false
|
||||
return options, nil
|
||||
}
|
||||
|
||||
options["has"] = true
|
||||
|
||||
var (
|
||||
awaitLoadOptions string
|
||||
switchLoadOptions string
|
||||
)
|
||||
|
||||
constOptionsBuffer := bytes.NewBuffer(nil)
|
||||
constOptionsBuffer.WriteString("export const options = ref<Options>({\n")
|
||||
|
||||
for _, v := range dictTypeList {
|
||||
// 字段映射字典
|
||||
for _, field := range in.masterFields {
|
||||
if field.DictType > 0 && v.Id == field.DictType {
|
||||
in.options.dictMap[field.TsName] = v.Type
|
||||
switchLoadOptions = fmt.Sprintf("%s case '%s':\n item.componentProps.options = options.value.%s;\n break;\n", switchLoadOptions, field.TsName, v.Type)
|
||||
}
|
||||
}
|
||||
|
||||
awaitLoadOptions = fmt.Sprintf("%s '%s',\n", awaitLoadOptions, v.Type)
|
||||
constOptionsBuffer.WriteString(" " + v.Type + ": [],\n")
|
||||
}
|
||||
|
||||
constOptionsBuffer.WriteString("});\n")
|
||||
|
||||
loadOptionsBuffer := bytes.NewBuffer(nil)
|
||||
loadOptionsBuffer.WriteString(fmt.Sprintf(ModelLoadOptionsTemplate, awaitLoadOptions, switchLoadOptions))
|
||||
|
||||
options["const"] = constOptionsBuffer.String()
|
||||
options["load"] = loadOptionsBuffer.String()
|
||||
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebModelRules(ctx context.Context, in *CurdPreviewInput) string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
buffer.WriteString("export const rules = {\n")
|
||||
for _, field := range in.masterFields {
|
||||
if field.FormRole == "" || field.FormRole == FormRoleNone {
|
||||
continue
|
||||
}
|
||||
|
||||
buffer.WriteString(fmt.Sprintf(" %s: {\n required: %v,\n trigger: ['blur', 'input'],\n message: '请输入%s',\n validator: validate.%v,\n },\n", field.TsName, field.Required, field.Dc, field.FormRole))
|
||||
}
|
||||
buffer.WriteString("};\n")
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebModelFormSchema(ctx context.Context, in *CurdPreviewInput) string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
buffer.WriteString("export const schemas = ref<FormSchema[]>([\n")
|
||||
|
||||
// 主表
|
||||
l.generateWebModelFormSchemaEach(buffer, in.masterFields)
|
||||
|
||||
// 关联表
|
||||
if len(in.options.Join) > 0 {
|
||||
for _, v := range in.options.Join {
|
||||
if !isEffectiveJoin(v) {
|
||||
continue
|
||||
}
|
||||
l.generateWebModelFormSchemaEach(buffer, v.Columns)
|
||||
}
|
||||
}
|
||||
|
||||
buffer.WriteString("]);\n")
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebModelFormSchemaEach(buffer *bytes.Buffer, fields []*sysin.GenCodesColumnListModel) {
|
||||
for _, field := range fields {
|
||||
if !field.IsQuery {
|
||||
continue
|
||||
}
|
||||
|
||||
var (
|
||||
defaultComponent = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n placeholder: '请输入%s',\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NInput", field.Dc, field.Dc)
|
||||
component string
|
||||
)
|
||||
|
||||
// 这里根据编辑表单组件来进行推断,如果没有则使用默认input,这可能会导致和查询条件所需参数不符的情况
|
||||
switch field.FormMode {
|
||||
case FormModeInput, FormModeInputTextarea, FormModeInputEditor:
|
||||
component = defaultComponent
|
||||
|
||||
case FormModeInputNumber:
|
||||
component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n placeholder: '请输入%s',\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NInputNumber", field.Dc, field.Dc)
|
||||
|
||||
case FormModeDate:
|
||||
component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n type: '%s',\n clearable: true,\n shortcuts: %s,\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NDatePicker", field.Dc, "date", "defShortcuts()")
|
||||
|
||||
case FormModeDateRange:
|
||||
component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n type: '%s',\n clearable: true,\n shortcuts: %s,\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NDatePicker", field.Dc, "daterange", "defRangeShortcuts()")
|
||||
|
||||
case FormModeTime:
|
||||
component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n type: '%s',\n clearable: true,\n shortcuts: %s,\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NDatePicker", field.Dc, "datetime", "defShortcuts()")
|
||||
|
||||
case FormModeTimeRange:
|
||||
component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n type: '%s',\n clearable: true,\n shortcuts: %s,\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NDatePicker", field.Dc, "datetimerange", "defRangeShortcuts()")
|
||||
|
||||
case FormModeRadio:
|
||||
component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n giProps: {\n //span: 24,\n },\n componentProps: {\n options: [],\n onUpdateChecked: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NRadioGroup", field.Dc)
|
||||
|
||||
case FormModeCheckbox:
|
||||
component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n giProps: {\n span: 1,\n },\n componentProps: {\n placeholder: '请选择%s',\n options: [],\n onUpdateChecked: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NCheckbox", field.Dc, field.Dc)
|
||||
|
||||
case FormModeSelect:
|
||||
component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n defaultValue: null,\n componentProps: {\n placeholder: '请选择%s',\n options: [],\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NSelect", field.Dc, field.Dc)
|
||||
|
||||
case FormModeSelectMultiple:
|
||||
component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n defaultValue: null,\n componentProps: {\n multiple: true,\n placeholder: '请选择%s',\n options: [],\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NSelect", field.Dc, field.Dc)
|
||||
|
||||
default:
|
||||
component = defaultComponent
|
||||
}
|
||||
|
||||
buffer.WriteString(component)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebModelColumns(ctx context.Context, in *CurdPreviewInput) string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
buffer.WriteString("export const columns = [\n")
|
||||
|
||||
// 主表
|
||||
l.generateWebModelColumnsEach(buffer, in, in.masterFields)
|
||||
|
||||
// 关联表
|
||||
if len(in.options.Join) > 0 {
|
||||
for _, v := range in.options.Join {
|
||||
if !isEffectiveJoin(v) {
|
||||
continue
|
||||
}
|
||||
l.generateWebModelColumnsEach(buffer, in, v.Columns)
|
||||
}
|
||||
}
|
||||
|
||||
buffer.WriteString("];\n")
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebModelColumnsEach(buffer *bytes.Buffer, in *CurdPreviewInput, fields []*sysin.GenCodesColumnListModel) {
|
||||
for _, field := range fields {
|
||||
if !field.IsList {
|
||||
continue
|
||||
}
|
||||
var (
|
||||
defaultComponent = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n },\n", field.Dc, field.TsName)
|
||||
component string
|
||||
)
|
||||
|
||||
// 这里根据编辑表单组件来进行推断,如果没有则使用默认input,这可能会导致和查询条件所需参数不符的情况
|
||||
switch field.FormMode {
|
||||
case FormModeDate:
|
||||
component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n render(row) {\n return formatToDate(row.%s);\n },\n },\n", field.Dc, field.TsName, field.TsName)
|
||||
|
||||
case FormModeSelect:
|
||||
component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n render(row) {\n if (isNullObject(row.%s)) {\n return ``;\n }\n return h(\n NTag,\n {\n style: {\n marginRight: '6px',\n },\n type: getOptionTag(options.value.%s, row.%s),\n bordered: false,\n },\n {\n default: () => getOptionLabel(options.value.%s, row.%s),\n }\n );\n },\n },\n", field.Dc, field.TsName, field.TsName, in.options.dictMap[field.TsName], field.TsName, in.options.dictMap[field.TsName], field.TsName)
|
||||
|
||||
case FormModeSelectMultiple:
|
||||
component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n render(row) {\n if (isNullObject(row.%s) || !isArray(row.%s)) {\n return ``;\n }\n return row.%s.map((tagKey) => {\n return h(\n NTag,\n {\n style: {\n marginRight: '6px',\n },\n type: getOptionTag(options.value.%s, tagKey),\n bordered: false,\n },\n {\n default: () => getOptionLabel(options.value.%s, tagKey),\n }\n );\n });\n },\n },\n", field.Dc, field.TsName, field.TsName, field.TsName, field.TsName, in.options.dictMap[field.TsName], in.options.dictMap[field.TsName])
|
||||
|
||||
case FormModeUploadImage:
|
||||
component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n render(row) {\n return h(%s, {\n width: 32,\n height: 32,\n src: row.%s,\n style: {\n width: '32px',\n height: '32px',\n 'max-width': '100%%',\n 'max-height': '100%%',\n },\n });\n },\n },\n", field.Dc, field.TsName, "NImage", field.TsName)
|
||||
|
||||
case FormModeUploadImages:
|
||||
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((image) => {\n return h(%s, {\n width: 32,\n height: 32,\n src: image,\n style: {\n width: '32px',\n height: '32px',\n 'max-width': '100%%',\n 'max-height': '100%%',\n 'margin-left': '2px',\n },\n });\n });\n },\n },\n", field.Dc, field.TsName, field.TsName, field.TsName, "NImage")
|
||||
|
||||
case FormModeUploadFile:
|
||||
component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n render(row) {\n if (row.%s === '') {\n return ``;\n }\n return h(\n %s,\n {\n size: 'small',\n },\n {\n default: () => getFileExt(row.%s),\n }\n );\n },\n },\n", field.Dc, field.TsName, field.TsName, "NAvatar", field.TsName)
|
||||
|
||||
case FormModeUploadFiles:
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
default:
|
||||
component = defaultComponent
|
||||
}
|
||||
|
||||
buffer.WriteString(component)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Package views
|
||||
// @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 views
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
func (l *gCurd) webViewTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) {
|
||||
data = make(g.Map)
|
||||
data["item"] = l.generateWebViewItem(ctx, in)
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebViewItem(ctx context.Context, in *CurdPreviewInput) string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
for _, field := range in.masterFields {
|
||||
if !field.IsEdit {
|
||||
continue
|
||||
}
|
||||
|
||||
var (
|
||||
defaultComponent = fmt.Sprintf("<n-descriptions-item>\n <template #label>%s</template>\n {{ formValue.%s }}\n </n-descriptions-item>", field.Dc, field.TsName)
|
||||
component string
|
||||
)
|
||||
|
||||
switch field.FormMode {
|
||||
|
||||
case FormModeInputTextarea, FormModeInputEditor:
|
||||
component = fmt.Sprintf("<n-descriptions-item>\n <template #label>%s</template>\n <span v-html=\"formValue.%s\"></span></n-descriptions-item>", field.Dc, field.TsName)
|
||||
|
||||
case FormModeInputDynamic:
|
||||
component = defaultComponent
|
||||
|
||||
case FormModeDate:
|
||||
component = defaultComponent
|
||||
|
||||
case FormModeTime:
|
||||
component = defaultComponent
|
||||
|
||||
case FormModeRadio, FormModeCheckbox, FormModeSelect, FormModeSelectMultiple:
|
||||
component = fmt.Sprintf("<n-descriptions-item label=\"%s\">\n <template v-for=\"(item, key) in formValue?.%s\" :key=\"key\">\n <n-tag\n :type=\"getOptionTag(options.%s, item)\"\n size=\"small\"\n class=\"min-left-space\"\n >{{ getOptionLabel(options.%s, item) }}</n-tag\n >\n </template>\n </n-descriptions-item>", field.Dc, field.TsName, in.options.dictMap[field.TsName], in.options.dictMap[field.TsName])
|
||||
|
||||
case FormModeUploadImage:
|
||||
component = fmt.Sprintf("<n-descriptions-item>\n <template #label>%s</template>\n <n-image style=\"margin-left: 10px; height: 100px; width: 100px\" :src=\"formValue.%s\"\n /></n-descriptions-item>", field.Dc, field.TsName)
|
||||
|
||||
case FormModeUploadImages:
|
||||
component = fmt.Sprintf("<n-descriptions-item>\n <template #label>%s</template>\n <n-image-group>\n <n-space>\n <span v-for=\"(item, key) in formValue?.%s\" :key=\"key\">\n <n-image style=\"margin-left: 10px; height: 100px; width: 100px\" :src=\"item\" />\n </span>\n </n-space>\n </n-image-group>\n </n-descriptions-item>", field.Dc, field.TsName)
|
||||
|
||||
case FormModeUploadFile:
|
||||
component = fmt.Sprintf("<n-descriptions-item>\n <template #label>%s</template>\n <div\n class=\"upload-card\"\n v-show=\"formValue.%s !== ''\"\n @click=\"download(formValue.%s)\"\n >\n <div class=\"upload-card-item\" style=\"height: 100px; width: 100px\">\n <div class=\"upload-card-item-info\">\n <div class=\"img-box\">\n <n-avatar :style=\"fileAvatarCSS\">{{ getFileExt(formValue.%s) }}</n-avatar>\n </div>\n </div>\n </div>\n </div>\n </n-descriptions-item>", field.Dc, field.TsName, field.TsName, field.TsName)
|
||||
|
||||
case FormModeUploadFiles:
|
||||
component = fmt.Sprintf("<n-descriptions-item>\n <template #label>%s</template>\n <div class=\"upload-card\">\n <n-space style=\"gap: 0px 0px\">\n <div\n class=\"upload-card-item\"\n style=\"height: 100px; width: 100px\"\n v-for=\"(item, key) in formValue.%s\"\n :key=\"key\"\n >\n <div class=\"upload-card-item-info\">\n <div class=\"img-box\">\n <n-avatar :style=\"fileAvatarCSS\" @click=\"download(item)\">{{\n getFileExt(item)\n }}</n-avatar>\n </div>\n </div>\n </div>\n </n-space>\n </div>\n </n-descriptions-item>", field.Dc, field.TsName)
|
||||
|
||||
case FormModeSwitch:
|
||||
component = fmt.Sprintf("<n-descriptions-item label=\"%s\">\n <n-switch v-model:value=\"formValue.%s\" :unchecked-value=\"2\" :checked-value=\"1\" :disabled=\"true\"\n /></n-descriptions-item>", field.Dc, field.TsName)
|
||||
|
||||
case FormModeRate:
|
||||
component = fmt.Sprintf("<n-descriptions-item label=\"%s\"\n ><n-rate readonly :default-value=\"formValue.%s\"\n /></n-descriptions-item>", field.Dc, field.TsName)
|
||||
|
||||
default:
|
||||
component = defaultComponent
|
||||
}
|
||||
|
||||
buffer.WriteString(" " + component + "\n\n")
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
96
server/internal/library/hggen/views/utils.go
Normal file
96
server/internal/library/hggen/views/utils.go
Normal file
@@ -0,0 +1,96 @@
|
||||
// Package hggen
|
||||
// @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 views
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// parseServFunName 解析业务服务名称
|
||||
func (l *gCurd) parseServFunName(templateGroup, varName string) string {
|
||||
templateGroup = gstr.UcFirst(templateGroup)
|
||||
if gstr.HasPrefix(varName, templateGroup) && varName != templateGroup {
|
||||
return varName
|
||||
}
|
||||
|
||||
return templateGroup + varName
|
||||
}
|
||||
|
||||
// getPkField 获取主键
|
||||
func (l *gCurd) getPkField(in *CurdPreviewInput) *sysin.GenCodesColumnListModel {
|
||||
if len(in.masterFields) == 0 {
|
||||
panic("getPkField masterFields uninitialized.")
|
||||
}
|
||||
for _, field := range in.masterFields {
|
||||
if field.Index == consts.GenCodesIndexPK {
|
||||
return field
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// hasEffectiveJoin 存在有效的关联表
|
||||
func hasEffectiveJoins(joins []*CurdOptionsJoin) bool {
|
||||
for _, join := range joins {
|
||||
if isEffectiveJoin(join) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isEffectiveJoin(join *CurdOptionsJoin) bool {
|
||||
return join.Alias != "" && join.Field != "" && join.LinkTable != "" && join.MasterField != "" && join.DaoName != "" && join.LinkMode > 0
|
||||
}
|
||||
|
||||
// formatComment formats the comment string to fit the golang code without any lines.
|
||||
func formatComment(comment string) string {
|
||||
comment = gstr.ReplaceByArray(comment, g.SliceStr{
|
||||
"\n", " ",
|
||||
"\r", " ",
|
||||
})
|
||||
comment = gstr.Replace(comment, `\n`, " ")
|
||||
comment = gstr.Trim(comment)
|
||||
return comment
|
||||
}
|
||||
|
||||
// 移除末尾的换行符
|
||||
func removeEndWrap(comment string) string {
|
||||
if len(comment) > 2 && comment[len(comment)-2:] == " \n" {
|
||||
comment = comment[:len(comment)-2]
|
||||
}
|
||||
return comment
|
||||
}
|
||||
|
||||
// ImportSql 导出sql文件
|
||||
func ImportSql(ctx context.Context, path string) error {
|
||||
rows, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sqlArr := strings.Split(string(rows), "\n")
|
||||
for _, sql := range sqlArr {
|
||||
sql = strings.TrimSpace(sql)
|
||||
if sql == "" || strings.HasPrefix(sql, "--") {
|
||||
continue
|
||||
}
|
||||
exec, err := g.DB().Exec(ctx, sql)
|
||||
g.Log().Infof(ctx, "views.ImportSql sql:%v, exec:%+v, err:%+v", sql, exec, err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
247
server/internal/library/hgorm/dao.go
Normal file
247
server/internal/library/hgorm/dao.go
Normal file
@@ -0,0 +1,247 @@
|
||||
// Package hgorm
|
||||
// @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 hgorm
|
||||
|
||||
// dao.
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"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/text/gstr"
|
||||
"hotgo/utility/convert"
|
||||
"hotgo/utility/tree"
|
||||
)
|
||||
|
||||
// GenJoinOnRelation 生成关联表关联条件
|
||||
func GenJoinOnRelation(masterTable, masterField, joinTable, alias, onField string) []string {
|
||||
return []string{
|
||||
joinTable,
|
||||
alias,
|
||||
fmt.Sprintf("`%s`.`%s` = `%s`.`%s`", alias, onField, masterTable, masterField),
|
||||
}
|
||||
}
|
||||
|
||||
type daoInstance interface {
|
||||
Table() string
|
||||
Ctx(ctx context.Context) *gdb.Model
|
||||
}
|
||||
|
||||
// Join 关联表属性
|
||||
type Join struct {
|
||||
Dao interface{} // 关联表dao实例
|
||||
Alias string // 别名
|
||||
fields map[string]*gdb.TableField // 表字段列表
|
||||
}
|
||||
|
||||
// GenJoinSelect 生成关联表select
|
||||
// 这里会将实体中的字段驼峰转为下划线于数据库进行匹配,意味着数据库字段必须全部是小写字母+下划线的格式
|
||||
func GenJoinSelect(ctx context.Context, entity interface{}, masterDao interface{}, joins []*Join) (allFields string, err error) {
|
||||
var tmpFields []string
|
||||
|
||||
md, ok := masterDao.(daoInstance)
|
||||
if !ok {
|
||||
err = errors.New("masterDao unimplemented interface format.daoInstance")
|
||||
return
|
||||
}
|
||||
|
||||
if len(joins) == 0 {
|
||||
err = errors.New("JoinFields joins len = 0")
|
||||
return
|
||||
}
|
||||
|
||||
for _, v := range joins {
|
||||
jd, ok := v.Dao.(daoInstance)
|
||||
if !ok {
|
||||
err = errors.New("joins index unimplemented interface format.daoInstance")
|
||||
return
|
||||
}
|
||||
v.fields, err = jd.Ctx(ctx).TableFields(jd.Table())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
masterFields, err := md.Ctx(ctx).TableFields(md.Table())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
entityFields, err := convert.GetEntityFieldTags(entity)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(entityFields) == 0 {
|
||||
return "*", nil
|
||||
}
|
||||
|
||||
// 是否为关联表字段
|
||||
getJoinAttribute := func(field string) (*Join, string) {
|
||||
for _, v := range joins {
|
||||
if gstr.HasPrefix(field, v.Alias) {
|
||||
return v, gstr.CaseSnakeFirstUpper(gstr.StrEx(field, v.Alias))
|
||||
}
|
||||
}
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
for _, field := range entityFields {
|
||||
// 关联表
|
||||
jd, joinField := getJoinAttribute(field)
|
||||
if jd != nil {
|
||||
if _, ok := jd.fields[joinField]; ok {
|
||||
tmpFields = append(tmpFields, fmt.Sprintf("`%s`.`%s` as `%s`", jd.Alias, joinField, field))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// 主表
|
||||
originalField := gstr.CaseSnakeFirstUpper(field)
|
||||
if _, ok := masterFields[originalField]; ok {
|
||||
tmpFields = append(tmpFields, fmt.Sprintf("`%s`.`%s`", md.Table(), originalField))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return gstr.Implode(",", convert.UniqueSliceString(tmpFields)), nil
|
||||
}
|
||||
|
||||
// GenSelect 生成select
|
||||
// 这里会将实体中的字段驼峰转为下划线于数据库进行匹配,意味着数据库字段必须全部是小写字母+下划线的格式
|
||||
func GenSelect(ctx context.Context, entity interface{}, dao interface{}) (allFields string, err error) {
|
||||
var tmpFields []string
|
||||
|
||||
md, ok := dao.(daoInstance)
|
||||
if !ok {
|
||||
err = errors.New("dao unimplemented interface format.daoInstance")
|
||||
return
|
||||
}
|
||||
|
||||
fields, err := md.Ctx(ctx).TableFields(md.Table())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
entityFields, err := convert.GetEntityFieldTags(entity)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(entityFields) == 0 {
|
||||
return "*", nil
|
||||
}
|
||||
|
||||
for _, field := range entityFields {
|
||||
originalField := gstr.CaseSnakeFirstUpper(field)
|
||||
if _, ok := fields[originalField]; ok {
|
||||
tmpFields = append(tmpFields, fmt.Sprintf("`%s`", originalField))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return gstr.Implode(",", convert.UniqueSliceString(tmpFields)), nil
|
||||
}
|
||||
|
||||
// GetPkField 获取dao实例中的主键名称
|
||||
func GetPkField(ctx context.Context, dao daoInstance) (string, error) {
|
||||
fields, err := dao.Ctx(ctx).TableFields(dao.Table())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(fields) == 0 {
|
||||
return "", errors.New("field not found")
|
||||
}
|
||||
|
||||
for _, field := range fields {
|
||||
if field.Key == "PRI" {
|
||||
return field.Name, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("no primary key")
|
||||
}
|
||||
|
||||
// IsUnique 是否唯一
|
||||
func IsUnique(ctx context.Context, dao interface{}, where g.Map, message string, pkId ...interface{}) error {
|
||||
d, ok := dao.(daoInstance)
|
||||
if !ok {
|
||||
return errors.New("IsUnique dao unimplemented interface format.daoInstance")
|
||||
}
|
||||
|
||||
if len(where) == 0 {
|
||||
return errors.New("where condition cannot be empty")
|
||||
}
|
||||
|
||||
m := d.Ctx(ctx).Where(where)
|
||||
if len(pkId) > 0 {
|
||||
field, err := GetPkField(ctx, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m = m.WhereNot(field, pkId[0])
|
||||
}
|
||||
|
||||
count, err := m.Count(1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
if message == "" {
|
||||
for k, _ := range where {
|
||||
message = fmt.Sprintf("in the table:%s, %v not uniqued", d.Table(), where[k])
|
||||
break
|
||||
}
|
||||
}
|
||||
return errors.New(message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenSubTree 生成下级关系树
|
||||
func GenSubTree(ctx context.Context, dao interface{}, oldPid int64) (newPid int64, newLevel int, subTree string, err error) {
|
||||
// 顶级树
|
||||
if oldPid == 0 {
|
||||
return 0, 1, "", nil
|
||||
}
|
||||
|
||||
d, ok := dao.(daoInstance)
|
||||
if !ok {
|
||||
return 0, 0, "", errors.New("GenTree dao unimplemented interface format.daoInstance")
|
||||
}
|
||||
field, err := GetPkField(ctx, d)
|
||||
if err != nil {
|
||||
return 0, 0, "", err
|
||||
}
|
||||
|
||||
models, err := d.Ctx(ctx).WhereNot(field, oldPid).One()
|
||||
if err != nil {
|
||||
return 0, 0, "", err
|
||||
}
|
||||
|
||||
if models.IsEmpty() {
|
||||
return 0, 0, "", gerror.New("上级信息不存在")
|
||||
}
|
||||
|
||||
level, ok := models["level"]
|
||||
if !ok {
|
||||
return 0, 0, "", gerror.New("表中必须包含`level`字段")
|
||||
}
|
||||
|
||||
supTree, ok := models["tree"]
|
||||
if !ok {
|
||||
return 0, 0, "", gerror.New("表中必须包含`tree`字段")
|
||||
}
|
||||
|
||||
newPid = oldPid
|
||||
newLevel = level.Int() + 1
|
||||
subTree = tree.GenLabel(supTree.String(), oldPid)
|
||||
return
|
||||
}
|
||||
91
server/internal/library/hgorm/handler.go
Normal file
91
server/internal/library/hgorm/handler.go
Normal file
@@ -0,0 +1,91 @@
|
||||
// Package hgorm
|
||||
// @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 hgorm
|
||||
|
||||
// 预处理
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/model/entity"
|
||||
)
|
||||
|
||||
// HandlerFilterAuth 过滤数据权限
|
||||
// 通过上下文中的用户角色权限和表中是否含有需要过滤的字段附加查询条件
|
||||
func HandlerFilterAuth(m *gdb.Model) *gdb.Model {
|
||||
var (
|
||||
needAuth bool
|
||||
filterField string
|
||||
roleModel *entity.AdminRole
|
||||
ctx = m.GetCtx()
|
||||
fields = escapeFieldsToSlice(m.GetFieldsStr())
|
||||
co = contexts.Get(ctx)
|
||||
)
|
||||
|
||||
if co == nil || co.User == nil {
|
||||
return m
|
||||
}
|
||||
|
||||
// 优先级:created_by > member_id
|
||||
if gstr.InArray(fields, "created_by") {
|
||||
needAuth = true
|
||||
filterField = "created_by"
|
||||
}
|
||||
|
||||
if !needAuth && gstr.InArray(fields, "member_id") {
|
||||
needAuth = true
|
||||
filterField = "member_id"
|
||||
}
|
||||
|
||||
if !needAuth {
|
||||
return m
|
||||
}
|
||||
|
||||
err := g.Model("admin_role").Where("id", co.User.RoleId).Scan(&roleModel)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("HandlerFilterAuth Failed to role information err:%+v", err))
|
||||
}
|
||||
|
||||
if roleModel == nil {
|
||||
panic(fmt.Sprintf("HandlerFilterAuth Failed to role information err2:%+v", err))
|
||||
}
|
||||
|
||||
switch roleModel.DataScope {
|
||||
case consts.RoleDataAll: // 全部权限
|
||||
// ...
|
||||
case consts.RoleDataNowDept: // 当前部门
|
||||
m = m.Where(filterField, co.User.DeptId)
|
||||
case consts.RoleDataDeptAndSub: // 当前部门及以下部门
|
||||
//m = m.Where(filterField, 1)
|
||||
case consts.RoleDataDeptCustom: // 自定义部门
|
||||
m = m.WhereIn(filterField, roleModel.CustomDept.Var().Ints())
|
||||
case consts.RoleDataSelf: // 仅自己
|
||||
m = m.Where(filterField, co.User.Id)
|
||||
case consts.RoleDataSelfAndSub: // 自己和直属下级
|
||||
//m = m.Where(filterField, 1)
|
||||
case consts.RoleDataSelfAndAllSub: // 自己和全部下级
|
||||
//m = m.Where(filterField, 1)
|
||||
|
||||
default:
|
||||
panic("HandlerFilterAuth dataScope is not registered")
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// HandlerForceCache 强制缓存
|
||||
func HandlerForceCache(m *gdb.Model) *gdb.Model {
|
||||
return m.Cache(gdb.CacheOption{Duration: -1, Force: true})
|
||||
}
|
||||
|
||||
// escapeFieldsToSlice 将转义过的字段转换为字段集切片
|
||||
func escapeFieldsToSlice(s string) []string {
|
||||
return gstr.Explode(",", gstr.Replace(gstr.Replace(s, "`,`", ","), "`", ""))
|
||||
}
|
||||
9
server/internal/library/hgorm/hook.go
Normal file
9
server/internal/library/hgorm/hook.go
Normal file
@@ -0,0 +1,9 @@
|
||||
// Package hgorm
|
||||
// @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 hgorm
|
||||
|
||||
// 常用钩子
|
||||
@@ -25,25 +25,27 @@ import (
|
||||
// GenerateLoginToken 为指定用户生成token
|
||||
func GenerateLoginToken(ctx context.Context, user *model.Identity, isRefresh bool) (interface{}, error) {
|
||||
var (
|
||||
jwtVersion, _ = g.Cfg().Get(ctx, "jwt.version", "1.0")
|
||||
jwtSign, _ = g.Cfg().Get(ctx, "jwt.sign", "hotGo")
|
||||
token = j.NewWithClaims(j.SigningMethodHS256, j.MapClaims{
|
||||
"id": user.Id,
|
||||
"username": user.Username,
|
||||
"realname": user.RealName,
|
||||
"avatar": user.Avatar,
|
||||
"email": user.Email,
|
||||
"mobile": user.Mobile,
|
||||
"last_time": user.LastTime,
|
||||
"last_ip": user.LastIp,
|
||||
"exp": user.Exp,
|
||||
"expires": user.Expires,
|
||||
"app": user.App,
|
||||
"role": user.Role,
|
||||
"role_key": user.RoleKey,
|
||||
"visit_count": user.VisitCount,
|
||||
"is_refresh": isRefresh,
|
||||
"jwt_version": jwtVersion.String(),
|
||||
jwtVersion = g.Cfg().MustGet(ctx, "jwt.version", "1.0")
|
||||
jwtSign = g.Cfg().MustGet(ctx, "jwt.sign", "hotGo")
|
||||
token = j.NewWithClaims(j.SigningMethodHS256, j.MapClaims{
|
||||
"id": user.Id,
|
||||
"pid": user.Pid,
|
||||
"deptId": user.DeptId,
|
||||
"roleId": user.RoleId,
|
||||
"roleKey": user.RoleKey,
|
||||
"username": user.Username,
|
||||
"realName": user.RealName,
|
||||
"avatar": user.Avatar,
|
||||
"email": user.Email,
|
||||
"mobile": user.Mobile,
|
||||
"lastTime": user.LastTime,
|
||||
"lastIp": user.LastIp,
|
||||
"exp": user.Exp,
|
||||
"expires": user.Expires,
|
||||
"app": user.App,
|
||||
"visitCount": user.VisitCount,
|
||||
"isRefresh": isRefresh,
|
||||
"jwtVersion": jwtVersion.String(),
|
||||
})
|
||||
)
|
||||
|
||||
@@ -81,7 +83,7 @@ func ParseToken(tokenString string, secret []byte) (j.MapClaims, error) {
|
||||
}
|
||||
token, err := j.Parse(tokenString, func(token *j.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*j.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
return nil, gerror.Newf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return secret, nil
|
||||
})
|
||||
|
||||
90
server/internal/library/location/geocode.go
Normal file
90
server/internal/library/location/geocode.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package location
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type AMapGeocodeAddressRes struct {
|
||||
Status string `json:"status"`
|
||||
Info string `json:"info"`
|
||||
Infocode string `json:"infocode"`
|
||||
Count string `json:"count"`
|
||||
Geocodes []struct {
|
||||
FormattedAddress string `json:"formatted_address"`
|
||||
Country string `json:"country"`
|
||||
Province string `json:"province"`
|
||||
Citycode string `json:"citycode"`
|
||||
City string `json:"city"`
|
||||
District string `json:"district"`
|
||||
Township []interface{} `json:"township"`
|
||||
Neighborhood struct {
|
||||
Name []interface{} `json:"name"`
|
||||
Type []interface{} `json:"type"`
|
||||
} `json:"neighborhood"`
|
||||
Building struct {
|
||||
Name []interface{} `json:"name"`
|
||||
Type []interface{} `json:"type"`
|
||||
} `json:"building"`
|
||||
Adcode string `json:"adcode"`
|
||||
Street []interface{} `json:"street"`
|
||||
Number []interface{} `json:"number"`
|
||||
Location string `json:"location"`
|
||||
Level string `json:"level"`
|
||||
} `json:"geocodes"`
|
||||
}
|
||||
|
||||
type AddressRegion struct {
|
||||
ProvinceName string `json:"provinceName"`
|
||||
CityName string `json:"cityName"`
|
||||
CountyName string `json:"countyName"`
|
||||
ProvinceCode string `json:"provinceCode"`
|
||||
CityCode string `json:"cityCode"`
|
||||
CountyCode string `json:"countyCode"`
|
||||
}
|
||||
|
||||
// AnalysisAddress 将地址解析出省市区编码
|
||||
func AnalysisAddress(ctx context.Context, address, key string) (region *AddressRegion, err error) {
|
||||
var (
|
||||
url = fmt.Sprintf("https://restapi.amap.com/v3/geocode/geo?address=%v&output=JSON&key=%v", address, key)
|
||||
responseMap = make(g.Map)
|
||||
response *AMapGeocodeAddressRes
|
||||
)
|
||||
|
||||
err = g.Client().GetVar(ctx, url).Scan(&responseMap)
|
||||
if err != nil {
|
||||
return nil, gerror.Newf("AMap AnalysisAddress err:%+v", err)
|
||||
}
|
||||
|
||||
err = gconv.Scan(responseMap, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 异常状态码
|
||||
if response.Status != "1" {
|
||||
return nil, gerror.Newf("AMap AnalysisAddress 错误码:%+v, 错误提示:%+v", response.Status, response.Info)
|
||||
}
|
||||
|
||||
if len(response.Geocodes) == 0 {
|
||||
return nil, gerror.New("AMap AnalysisAddress 没有解析到地区信息")
|
||||
}
|
||||
|
||||
region = new(AddressRegion)
|
||||
region.ProvinceName = response.Geocodes[0].Province
|
||||
region.CityName = response.Geocodes[0].City
|
||||
region.CountyName = response.Geocodes[0].District
|
||||
|
||||
// 有效区域编码
|
||||
if len(response.Geocodes[0].Adcode) == 6 {
|
||||
code := gconv.Int64(response.Geocodes[0].Adcode)
|
||||
if code > 0 {
|
||||
region.ProvinceCode = gconv.String(code / 10000 * 10000)
|
||||
region.CityCode = gconv.String(code / 100 * 100)
|
||||
region.CountyCode = response.Geocodes[0].Adcode
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -8,8 +8,8 @@ package location
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/axgle/mahonia"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
@@ -23,6 +23,11 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
whoisApi = "https://whois.pconline.com.cn/ipJson.jsp?json=true&ip="
|
||||
dyndns = "http://members.3322.org/dyndns/getip"
|
||||
)
|
||||
|
||||
type IpLocationData struct {
|
||||
Ip string `json:"ip"`
|
||||
Country string `json:"country"`
|
||||
@@ -35,49 +40,42 @@ type IpLocationData struct {
|
||||
AreaCode int64 `json:"area_code"`
|
||||
}
|
||||
|
||||
type WhoisRegionData struct {
|
||||
Ip string `json:"ip"`
|
||||
Pro string `json:"pro" `
|
||||
ProCode string `json:"proCode" `
|
||||
City string `json:"city" `
|
||||
CityCode string `json:"cityCode"`
|
||||
Region string `json:"region"`
|
||||
RegionCode string `json:"regionCode"`
|
||||
Addr string `json:"addr"`
|
||||
Err string `json:"err"`
|
||||
}
|
||||
|
||||
// WhoisLocation 通过Whois接口查询IP归属地
|
||||
func WhoisLocation(ctx context.Context, ip string) IpLocationData {
|
||||
|
||||
type whoisRegionData struct {
|
||||
Ip string `json:"ip"`
|
||||
Pro string `json:"pro" `
|
||||
ProCode string `json:"proCode" `
|
||||
City string `json:"city" `
|
||||
CityCode string `json:"cityCode"`
|
||||
Region string `json:"region"`
|
||||
RegionCode string `json:"regionCode"`
|
||||
Addr string `json:"addr"`
|
||||
Err string `json:"err"`
|
||||
}
|
||||
|
||||
func WhoisLocation(ctx context.Context, ip string) (*IpLocationData, error) {
|
||||
if !validate.IsIp(ip) {
|
||||
return IpLocationData{}
|
||||
return nil, fmt.Errorf("invalid input ip:%v", ip)
|
||||
}
|
||||
|
||||
response, err := g.Client().Timeout(10*time.Second).Get(ctx, "http://whois.pconline.com.cn/ipJson.jsp?ip="+ip+"&json=true")
|
||||
response, err := g.Client().Timeout(10*time.Second).Get(ctx, whoisApi+ip)
|
||||
if err != nil {
|
||||
err = gerror.New(err.Error())
|
||||
return IpLocationData{
|
||||
Ip: ip,
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer response.Close()
|
||||
|
||||
var enc mahonia.Decoder
|
||||
enc = mahonia.NewDecoder("gbk")
|
||||
data := enc.ConvertString(response.ReadAllString())
|
||||
whoisData := whoisRegionData{}
|
||||
if err := gconv.Struct(data, &whoisData); err != nil {
|
||||
err = gerror.New(err.Error())
|
||||
var (
|
||||
whoisData *WhoisRegionData
|
||||
enc = mahonia.NewDecoder("gbk")
|
||||
data = enc.ConvertString(response.ReadAllString())
|
||||
)
|
||||
|
||||
g.Log().Print(ctx, "err:", err)
|
||||
return IpLocationData{
|
||||
Ip: ip,
|
||||
}
|
||||
if err = gconv.Struct(data, &whoisData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return IpLocationData{
|
||||
return &IpLocationData{
|
||||
Ip: whoisData.Ip,
|
||||
//Country string `json:"country"`
|
||||
Region: whoisData.Addr,
|
||||
@@ -87,32 +85,26 @@ func WhoisLocation(ctx context.Context, ip string) IpLocationData {
|
||||
CityCode: gconv.Int64(whoisData.CityCode),
|
||||
Area: whoisData.Region,
|
||||
AreaCode: gconv.Int64(whoisData.RegionCode),
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Cz88Find 通过Cz88的IP库查询IP归属地
|
||||
func Cz88Find(ctx context.Context, ip string) IpLocationData {
|
||||
func Cz88Find(ctx context.Context, ip string) (*IpLocationData, error) {
|
||||
if !validate.IsIp(ip) {
|
||||
g.Log().Print(ctx, "ip格式错误:", ip)
|
||||
return IpLocationData{}
|
||||
return nil, fmt.Errorf("invalid input ip:%v", ip)
|
||||
}
|
||||
|
||||
loc, err := iploc.OpenWithoutIndexes("./storage/ip/qqwry-utf8.dat")
|
||||
loc, err := iploc.OpenWithoutIndexes("./resource/ip/qqwry-utf8.dat")
|
||||
if err != nil {
|
||||
err = gerror.New(err.Error())
|
||||
return IpLocationData{
|
||||
Ip: ip,
|
||||
}
|
||||
return nil, fmt.Errorf("%v for help, please go to: https://github.com/kayon/iploc", err.Error())
|
||||
}
|
||||
|
||||
detail := loc.Find(ip)
|
||||
if detail == nil {
|
||||
return IpLocationData{
|
||||
Ip: ip,
|
||||
}
|
||||
return nil, fmt.Errorf("no ip data is queried. procedure:%v", ip)
|
||||
}
|
||||
|
||||
locationData := IpLocationData{
|
||||
locationData := &IpLocationData{
|
||||
Ip: ip,
|
||||
Country: detail.Country,
|
||||
Region: detail.Region,
|
||||
@@ -121,18 +113,12 @@ func Cz88Find(ctx context.Context, ip string) IpLocationData {
|
||||
Area: detail.County,
|
||||
}
|
||||
|
||||
if gstr.LenRune(locationData.Province) == 0 {
|
||||
return locationData
|
||||
}
|
||||
|
||||
return locationData
|
||||
return locationData, nil
|
||||
}
|
||||
|
||||
// IsJurisByIpTitle 判断地区名称是否为直辖市
|
||||
func IsJurisByIpTitle(title string) bool {
|
||||
|
||||
lists := []string{"北京市", "天津市", "重庆市", "上海市"}
|
||||
|
||||
for i := 0; i < len(lists); i++ {
|
||||
if gstr.Contains(lists[i], title) {
|
||||
return true
|
||||
@@ -142,9 +128,8 @@ func IsJurisByIpTitle(title string) bool {
|
||||
}
|
||||
|
||||
// GetLocation 获取IP归属地信息
|
||||
func GetLocation(ctx context.Context, ip string) IpLocationData {
|
||||
method, _ := g.Cfg().Get(ctx, "hotgo.ipMethod", "cz88")
|
||||
|
||||
func GetLocation(ctx context.Context, ip string) (*IpLocationData, error) {
|
||||
method := g.Cfg().MustGet(ctx, "hotgo.ipMethod", "cz88")
|
||||
if method.String() == "whois" {
|
||||
return WhoisLocation(ctx, ip)
|
||||
}
|
||||
@@ -152,16 +137,28 @@ func GetLocation(ctx context.Context, ip string) IpLocationData {
|
||||
}
|
||||
|
||||
// GetPublicIP 获取公网IP
|
||||
func GetPublicIP() (ip string, err error) {
|
||||
response, err := http.Get("http://members.3322.org/dyndns/getip")
|
||||
func GetPublicIP(ctx context.Context) (ip string, err error) {
|
||||
var data *WhoisRegionData
|
||||
err = g.Client().Timeout(10*time.Second).GetVar(ctx, whoisApi).Scan(&data)
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "GetPublicIP alternatives are being tried err:%+v", err)
|
||||
return GetPublicIP2()
|
||||
}
|
||||
return data.Ip, nil
|
||||
}
|
||||
|
||||
func GetPublicIP2() (ip string, err error) {
|
||||
response, err := http.Get(dyndns)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
body, _ := ioutil.ReadAll(response.Body)
|
||||
ip = string(body)
|
||||
ip = strings.ReplaceAll(ip, "\n", "")
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ip = strings.ReplaceAll(string(body), "\n", "")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -79,11 +79,7 @@ var (
|
||||
func init() {
|
||||
mqProducerInstanceMap = make(map[string]MqProducer)
|
||||
mqConsumerInstanceMap = make(map[string]MqConsumer)
|
||||
get, err := g.Cfg().Get(ctx, "queue")
|
||||
if err != nil {
|
||||
g.Log().Fatalf(ctx, "queue config load fail, err .%v", err)
|
||||
return
|
||||
}
|
||||
get := g.Cfg().MustGet(ctx, "queue")
|
||||
get.Scan(&config)
|
||||
}
|
||||
|
||||
@@ -123,7 +119,7 @@ func NewProducer(groupName string) (mqClient MqProducer, err error) {
|
||||
Version: config.Kafka.Version,
|
||||
})
|
||||
case "redis":
|
||||
address, _ := g.Cfg().Get(ctx, "queue.redis.address", nil)
|
||||
address := g.Cfg().MustGet(ctx, "queue.redis.address", nil)
|
||||
if len(address.String()) == 0 {
|
||||
g.Log().Fatal(ctx, "queue redis address is not support")
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ type RocketMq struct {
|
||||
|
||||
// rewriteLog 重写日志
|
||||
func rewriteLog() {
|
||||
level, _ := g.Cfg().Get(ctx, "queue.rocketmq.logLevel", "debug")
|
||||
level := g.Cfg().MustGet(ctx, "queue.rocketmq.logLevel", "debug")
|
||||
rlog.SetLogger(&RocketMqLogger{Flag: "[rocket_mq]", LevelLog: level.String()})
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/model"
|
||||
"time"
|
||||
@@ -33,7 +33,7 @@ func RJson(r *ghttp.Request, code int, message string, data ...interface{}) {
|
||||
if len(data) > 0 {
|
||||
responseData = data[0]
|
||||
}
|
||||
Res := &model.Response{
|
||||
res := &model.Response{
|
||||
Code: code,
|
||||
Message: message,
|
||||
Timestamp: time.Now().Unix(),
|
||||
@@ -41,36 +41,38 @@ func RJson(r *ghttp.Request, code int, message string, data ...interface{}) {
|
||||
}
|
||||
|
||||
// 如果不是正常的返回,则将data转为error
|
||||
if consts.CodeOK == code {
|
||||
Res.Data = responseData
|
||||
if gcode.CodeOK.Code() == code {
|
||||
res.Data = responseData
|
||||
} else {
|
||||
Res.Error = responseData
|
||||
res.Error = responseData
|
||||
}
|
||||
|
||||
// 清空响应
|
||||
r.Response.ClearBuffer()
|
||||
|
||||
// 写入响应
|
||||
r.Response.WriteJson(Res)
|
||||
r.Response.WriteJson(res)
|
||||
|
||||
// 加入到上下文
|
||||
contexts.SetResponse(r.Context(), Res)
|
||||
contexts.SetResponse(r.Context(), res)
|
||||
}
|
||||
|
||||
// SusJson 返回成功JSON
|
||||
func SusJson(isExit bool, r *ghttp.Request, message string, data ...interface{}) {
|
||||
if isExit {
|
||||
JsonExit(r, consts.CodeOK, message, data...)
|
||||
JsonExit(r, gcode.CodeOK.Code(), message, data...)
|
||||
return
|
||||
}
|
||||
RJson(r, consts.CodeOK, message, data...)
|
||||
RJson(r, gcode.CodeOK.Code(), message, data...)
|
||||
}
|
||||
|
||||
// FailJson 返回失败JSON
|
||||
func FailJson(isExit bool, r *ghttp.Request, message string, data ...interface{}) {
|
||||
if isExit {
|
||||
JsonExit(r, consts.CodeNil, message, data...)
|
||||
JsonExit(r, gcode.CodeNil.Code(), message, data...)
|
||||
return
|
||||
}
|
||||
RJson(r, consts.CodeNil, message, data...)
|
||||
RJson(r, gcode.CodeNil.Code(), message, data...)
|
||||
}
|
||||
|
||||
// Redirect 重定向
|
||||
|
||||
Reference in New Issue
Block a user