发布代码生成、更新20+表单组件,优化数据字典,gf版本更新到2.3.1

This commit is contained in:
孟帅
2023-01-18 16:23:39 +08:00
parent 50207ded90
commit 87c27a17a3
386 changed files with 27926 additions and 44297 deletions

View File

@@ -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

View File

@@ -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").

View File

@@ -67,7 +67,7 @@ func GetRoleId(ctx context.Context) int64 {
return 0
}
return user.Role
return user.RoleId
}
// GetRoleKey 获取用户角色唯一编码

View 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() {
}

View 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
}
}

View 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)
}
}

View 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
}

View 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 ""
}

View 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))
}

View 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
}

View 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
}

View 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,
})
}

View 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
)

View 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
}

View 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
}

View File

@@ -0,0 +1,9 @@
package cmd
import (
"hotgo/internal/library/hggen/internal/cmd/genservice"
)
type (
cGenService = genservice.CGenService
)

View 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
}

View 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
}

View 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
}

View 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)
}
}

View 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
}

View 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
}

View 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")
}
}

View 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
}

View File

@@ -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)
}
}
}
}

View 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()
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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)
}

View 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`
)

View File

@@ -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)
}
`

View File

@@ -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}
`

View File

@@ -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}
`

View File

@@ -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}
`

View File

@@ -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
}
`

View File

@@ -0,0 +1,13 @@
package consts
const TemplateGenServiceLogicContent = `
// ==========================================================================
// Code generated by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
package {PackageName}
import(
{Imports}
)
`

View 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

View 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,
})
}

View File

@@ -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"
}

View 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...)
}

View File

@@ -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)
}

View 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
}

View 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
}

View 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")
}

View 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
}

View 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
}

View 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")
}
}

View 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
}

View File

@@ -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
}

View 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)
}
}

View File

@@ -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()
}

View 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
}

View 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
}

View 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, "`,`", ","), "`", ""))
}

View 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
// 常用钩子

View File

@@ -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
})

View 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
}

View File

@@ -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
}

View File

@@ -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")
}

View File

@@ -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()})
}

View File

@@ -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 重定向