版本预发布

This commit is contained in:
孟帅
2023-02-08 20:29:34 +08:00
parent f11c7c5bf2
commit 2068d05c93
269 changed files with 16122 additions and 12075 deletions

View File

@@ -33,7 +33,7 @@ func Generate(ctx context.Context) (id string, base64 string) {
// Fonts: []string{"chromohv.ttf"},
//}
//
// 算
driver := &base64Captcha.DriverMath{
Height: 42,
Width: 100,

View File

@@ -50,6 +50,16 @@ func SetTakeUpTime(ctx context.Context, takeUpTime int64) {
Get(ctx).TakeUpTime = takeUpTime
}
// GetUser 获取用户信息
func GetUser(ctx context.Context) *model.Identity {
c := Get(ctx)
if c == nil {
return nil
}
return c.User
}
// GetUserId 获取用户ID
func GetUserId(ctx context.Context) int64 {
user := Get(ctx).User

View File

@@ -7,7 +7,3 @@
package debris
// 碎片
func Test() {
}

View File

@@ -19,24 +19,6 @@ func Send(config *model.EmailConfig, to string, subject string, body string) err
return sendToMail(config, to, subject, body, "html")
}
// SendTestMail 发送测试邮件
func SendTestMail(config *model.EmailConfig, to string) error {
subject := "这是一封来自HotGo的测试邮件"
body := `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="iso-8859-15">
<title>这是一封来自HotGo的测试邮件</title>
</head>
<body>
当你收到这封邮件的时候,说明已经联调成功了,恭喜你!
</body>
</html>`
return Send(config, to, subject, body)
}
func sendToMail(config *model.EmailConfig, to, subject, body, mailType string) error {
if config == nil {

View File

@@ -15,6 +15,7 @@ import (
"hotgo/internal/library/hggen/internal/cmd"
"hotgo/internal/library/hggen/internal/cmd/gendao"
"hotgo/internal/library/hggen/views"
"hotgo/internal/model"
"hotgo/internal/model/input/form"
"hotgo/internal/model/input/sysin"
"hotgo/internal/service"
@@ -49,11 +50,33 @@ func TableColumns(ctx context.Context, in sysin.GenCodesColumnListInp) (fields [
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,
})
row := &sysin.GenTypeSelect{
Value: k,
Name: v,
Label: v,
Templates: make(form.Selects, 0),
}
confName, ok := consts.GenCodesTypeConfMap[k]
if ok {
var temps []*model.GenerateAppCrudTemplate
err = g.Cfg().MustGet(ctx, "hggen.application."+confName+".templates").Scan(&temps)
if err != nil {
return
}
if len(temps) > 0 {
for index, temp := range temps {
row.Templates = append(row.Templates, &form.Select{
Value: index,
Label: temp.Group,
Name: temp.Group,
})
}
sort.Sort(row.Templates)
}
}
res.GenType = append(res.GenType, row)
}
sort.Sort(res.GenType)
res.Db = DbSelect(ctx)

View File

@@ -61,7 +61,6 @@ func GetServiceConfig() genservice.CGenServiceInput {
}
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 {
@@ -72,9 +71,9 @@ func GetDaoConfig(group string) gendao.CGenDaoInput {
}
v := find(group)
inp := defaultGenDaoInput
if v != nil {
err := gconv.Scan(v, &inp)
if err != nil {
if err := gconv.Scan(v, &inp); err != nil {
panic(err)
}
}

View File

@@ -10,7 +10,7 @@ var (
)
type cGen struct {
g.Meta `name:"hggen" brief:"{cGenBrief}" dc:"{cGenDc}"`
g.Meta `name:"gen" brief:"{cGenBrief}" dc:"{cGenDc}"`
cGenDao
cGenPb
cGenPbEntity
@@ -20,9 +20,9 @@ type cGen struct {
const (
cGenBrief = `automatically generate go files for dao/do/entity/pb/pbentity`
cGenDc = `
The "hggen" command is designed for multiple generating purposes.
The "gen" 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.
Please use "gf gen dao -h" for specified type help.
`
)

View File

@@ -44,22 +44,22 @@ type (
)
const (
cGenPbEntityConfig = `gfcli.hggen.pbentity`
cGenPbEntityConfig = `gfcli.gen.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_
gf gen pbentity
gf gen pbentity -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
gf gen pbentity -p ./protocol/demos/entity -t user,user_detail,user_login
gf gen 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):
The configuration node name is "gf.gen.pbentity", which also supports multiple databases, for example(config.yaml):
gfcli:
hggen:
gen:
- pbentity:
link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
path: "protocol/demos/entity"

View File

@@ -18,23 +18,23 @@ import (
)
const (
CGenDaoConfig = `gfcli.hggen.dao`
CGenDaoUsage = `gf hggen dao [OPTION]`
CGenDaoConfig = `gfcli.gen.dao`
CGenDaoUsage = `gf gen 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_
gf gen dao
gf gen dao -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
gf gen dao -p ./model -g user-center -t user,user_detail,user_login
gf gen 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):
The configuration node name is "gfcli.gen.dao", which also supports multiple databases, for example(config.yaml):
gfcli:
hggen:
gen:
dao:
- link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
tables: "order,products"
@@ -179,7 +179,6 @@ type (
)
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() {
@@ -200,7 +199,7 @@ func DoGenDaoForArray(ctx context.Context, in CGenDaoInput) {
doGenDaoForArray(ctx, -1, in)
}
// doGenDaoForArray implements the "hggen dao" command for configuration array.
// doGenDaoForArray implements the "gen dao" command for configuration array.
func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
var (
err error

View File

@@ -20,12 +20,12 @@ import (
)
const (
CGenServiceConfig = `gfcli.hggen.service`
CGenServiceUsage = `gf hggen service [OPTION]`
CGenServiceConfig = `gfcli.gen.service`
CGenServiceUsage = `gf gen service [OPTION]`
CGenServiceBrief = `parse struct and associated functions from packages to generate service go file`
CGenServiceEg = `
gf hggen service
gf hggen service -f Snake
gf gen service
gf gen 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`
@@ -89,13 +89,13 @@ const (
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")
flockFilePath = gfile.Temp("gf.cli.gen.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`)
// If another "gen service" process is running, it just exits.
mlog.Debug(`another "gen service" process is running, exit`)
return
}
}
@@ -127,7 +127,7 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
mlog.Debug("Chdir:", newWorkingDir)
_ = gfile.Remove(flockFilePath)
var command = fmt.Sprintf(
`%s hggen service -packages=%s`,
`%s gen service -packages=%s`,
gfile.SelfName(), gfile.Basename(watchFileDir),
)
err = gproc.ShellRun(ctx, command)

View File

@@ -157,6 +157,11 @@ func setDefaultFormMode(field *sysin.GenCodesColumnListModel) {
return
}
if (field.GoName == "ProvinceId" || field.GoName == "CityId") && IsNumberType(field.GoType) {
field.FormMode = FormModeCitySelector
return
}
if field.DataType == "datetime" || field.DataType == "timestamp" || field.DataType == "timestamptz" {
field.FormMode = FormModeTime
return

View File

@@ -115,6 +115,7 @@ const (
FormModeUploadFiles = "UploadFiles" // 多文件上传
FormModeSwitch = "Switch" // 开关
FormModeRate = "Rate" // 评分
FormModeCitySelector = "CitySelector" // 省市区选择
)
var FormModes = []string{
@@ -124,6 +125,7 @@ var FormModes = []string{
FormModeUploadImage, FormModeUploadImages, FormModeUploadFile, FormModeUploadFiles,
FormModeSwitch,
FormModeRate,
FormModeCitySelector,
}
var FormModeMap = map[string]string{
@@ -146,6 +148,7 @@ var FormModeMap = map[string]string{
FormModeUploadFiles: "多文件上传",
FormModeSwitch: "开关",
FormModeRate: "评分",
FormModeCitySelector: "省市区选择",
}
// 表单验证

View File

@@ -118,7 +118,16 @@ func (l *gCurd) initInput(ctx context.Context, in *CurdPreviewInput) (err error)
initStep(ctx, in)
in.options.dictMap = make(g.Map)
in.options.TemplateGroup = "sys"
if len(in.Config.Application.Crud.Templates)-1 < in.In.GenTemplate {
return gerror.New("没有找到生成模板的配置,请检查!")
}
err = checkCurdPath(in.Config.Application.Crud.Templates[in.In.GenTemplate])
if err != nil {
return
}
in.options.TemplateGroup = in.Config.Application.Crud.Templates[in.In.GenTemplate].MasterPackage
return
}
@@ -137,14 +146,14 @@ func initStep(ctx context.Context, in *CurdPreviewInput) {
in.options.Step.HasMenu = gstr.InArray(in.options.AutoOps, "genMenuPermissions")
}
func (l *gCurd) loadView(ctx context.Context, in *CurdPreviewInput) error {
func (l *gCurd) loadView(ctx context.Context, in *CurdPreviewInput) (err error) {
view := gview.New()
err := view.SetConfigWithMap(g.Map{
"Paths": "./resource/template/generate/default/curd",
err = view.SetConfigWithMap(g.Map{
"Paths": in.Config.Application.Crud.Templates[in.In.GenTemplate].TemplatePath,
"Delimiters": in.Config.Delimiters,
})
if err != nil {
return err
return
}
view.BindFuncMap(g.Map{
@@ -156,7 +165,7 @@ func (l *gCurd) loadView(ctx context.Context, in *CurdPreviewInput) error {
dictOptions, err := l.generateWebModelDictOptions(ctx, in)
if err != nil {
return err
return
}
view.Assigns(gview.Params{
@@ -174,13 +183,13 @@ func (l *gCurd) loadView(ctx context.Context, in *CurdPreviewInput) error {
"dictOptions": dictOptions, // web字典选项
})
in.view = view
return nil
return
}
func (l *gCurd) DoBuild(ctx context.Context, in *CurdBuildInput) (err error) {
preview, err := l.DoPreview(ctx, in.PreviewIn)
if err != nil {
return err
return
}
// 前置操作
@@ -307,7 +316,7 @@ func (l *gCurd) generateApiContent(ctx context.Context, in *CurdPreviewInput) (e
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.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].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
@@ -338,7 +347,7 @@ func (l *gCurd) generateInputContent(ctx context.Context, in *CurdPreviewInput)
if err != nil {
return err
}
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[0].InputPath, convert.CamelCaseToUnderline(in.In.VarName)+".go")
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].InputPath, convert.CamelCaseToUnderline(in.In.VarName)+".go")
genFile.Meth = consts.GenCodesBuildMethCreate
if gfile.Exists(genFile.Path) {
genFile.Meth = consts.GenCodesBuildMethSkip
@@ -364,7 +373,7 @@ func (l *gCurd) generateControllerContent(ctx context.Context, in *CurdPreviewIn
if err != nil {
return err
}
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[0].ControllerPath, convert.CamelCaseToUnderline(in.In.VarName)+".go")
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].ControllerPath, convert.CamelCaseToUnderline(in.In.VarName)+".go")
genFile.Meth = consts.GenCodesBuildMethCreate
if gfile.Exists(genFile.Path) {
genFile.Meth = consts.GenCodesBuildMethSkip
@@ -394,7 +403,7 @@ func (l *gCurd) generateLogicContent(ctx context.Context, in *CurdPreviewInput)
if err != nil {
return err
}
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[0].LogicPath, convert.CamelCaseToUnderline(in.In.VarName)+".go")
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].LogicPath, convert.CamelCaseToUnderline(in.In.VarName)+".go")
genFile.Meth = consts.GenCodesBuildMethCreate
if gfile.Exists(genFile.Path) {
genFile.Meth = consts.GenCodesBuildMethSkip
@@ -420,7 +429,7 @@ func (l *gCurd) generateRouterContent(ctx context.Context, in *CurdPreviewInput)
return err
}
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[0].RouterPath, convert.CamelCaseToUnderline(in.In.VarName)+".go")
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].RouterPath, convert.CamelCaseToUnderline(in.In.VarName)+".go")
genFile.Meth = consts.GenCodesBuildMethCreate
if gfile.Exists(genFile.Path) {
genFile.Meth = consts.GenCodesBuildMethSkip
@@ -446,7 +455,7 @@ func (l *gCurd) generateWebApiContent(ctx context.Context, in *CurdPreviewInput)
return err
}
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[0].WebApiPath, gstr.LcFirst(in.In.VarName), "index.ts")
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].WebApiPath, gstr.LcFirst(in.In.VarName), "index.ts")
genFile.Meth = consts.GenCodesBuildMethCreate
if gfile.Exists(genFile.Path) {
genFile.Meth = consts.GenCodesBuildMethSkip
@@ -477,7 +486,7 @@ func (l *gCurd) generateWebModelContent(ctx context.Context, in *CurdPreviewInpu
return err
}
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[0].WebViewsPath, gstr.LcFirst(in.In.VarName), "model.ts")
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].WebViewsPath, gstr.LcFirst(in.In.VarName), "model.ts")
genFile.Meth = consts.GenCodesBuildMethCreate
if gfile.Exists(genFile.Path) {
genFile.Meth = consts.GenCodesBuildMethSkip
@@ -507,7 +516,7 @@ func (l *gCurd) generateWebIndexContent(ctx context.Context, in *CurdPreviewInpu
return err
}
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[0].WebViewsPath, gstr.LcFirst(in.In.VarName), "index.vue")
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].WebViewsPath, gstr.LcFirst(in.In.VarName), "index.vue")
genFile.Meth = consts.GenCodesBuildMethCreate
if gfile.Exists(genFile.Path) {
genFile.Meth = consts.GenCodesBuildMethSkip
@@ -538,7 +547,7 @@ func (l *gCurd) generateWebEditContent(ctx context.Context, in *CurdPreviewInput
return err
}
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[0].WebViewsPath, gstr.LcFirst(in.In.VarName), "edit.vue")
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].WebViewsPath, gstr.LcFirst(in.In.VarName), "edit.vue")
genFile.Meth = consts.GenCodesBuildMethCreate
if gfile.Exists(genFile.Path) {
genFile.Meth = consts.GenCodesBuildMethSkip
@@ -573,7 +582,7 @@ func (l *gCurd) generateWebViewContent(ctx context.Context, in *CurdPreviewInput
return err
}
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[0].WebViewsPath, gstr.LcFirst(in.In.VarName), "view.vue")
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].WebViewsPath, gstr.LcFirst(in.In.VarName), "view.vue")
genFile.Meth = consts.GenCodesBuildMethCreate
if gfile.Exists(genFile.Path) {
genFile.Meth = consts.GenCodesBuildMethSkip
@@ -609,7 +618,7 @@ func (l *gCurd) generateSqlContent(ctx context.Context, in *CurdPreviewInput) (e
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.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].SqlPath, convert.CamelCaseToUnderline(in.In.VarName)+"_menu.sql")
genFile.Meth = consts.GenCodesBuildMethCreate
if gfile.Exists(genFile.Path) {
genFile.Meth = consts.GenCodesBuildMethSkip

View File

@@ -19,11 +19,13 @@ import (
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}"
LogicListSimpleSelect = "\tfields, err := hgorm.GenSelect(ctx, sysin.%sListModel{}, dao.%s)\n\tif err != nil {\n\t\treturn\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}"
LogicEditUpdate = "\t\t_, err = s.Model(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\treturn "
LogicEditInsert = "\t_, err = s.Model(ctx, &handler.Option{FilterAuth: false}).\n\t\tFieldsEx(\n%s\t\t).\n\t\tData(in).Insert()"
LogicSwitchUpdate = "g.Map{\n\t\tin.Key: in.Value,\n%s}"
LogicStatusUpdate = "g.Map{\n\t\tdao.%s.Columns().Status: in.Status,\n%s}"
)
func (l *gCurd) logicTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) {
@@ -33,9 +35,35 @@ func (l *gCurd) logicTplData(ctx context.Context, in *CurdPreviewInput) (data g.
data["listOrder"] = l.generateLogicListOrder(ctx, in)
data["edit"] = l.generateLogicEdit(ctx, in)
data["switchFields"] = l.generateLogicSwitchFields(ctx, in)
data["switchUpdate"] = l.generateLogicSwitchUpdate(ctx, in)
data["statusUpdate"] = l.generateLogicStatusUpdate(ctx, in)
return
}
func (l *gCurd) generateLogicStatusUpdate(ctx context.Context, in *CurdPreviewInput) string {
var update string
for _, field := range in.masterFields {
if field.GoName == "UpdatedBy" {
update += "\t\tdao." + in.In.DaoName + ".Columns().UpdatedBy: contexts.GetUserId(ctx),\n"
}
}
update += "\t"
return fmt.Sprintf(LogicStatusUpdate, in.In.DaoName, update)
}
func (l *gCurd) generateLogicSwitchUpdate(ctx context.Context, in *CurdPreviewInput) string {
var update string
for _, field := range in.masterFields {
if field.GoName == "UpdatedBy" {
update += "\t\tdao." + in.In.DaoName + ".Columns().UpdatedBy: contexts.GetUserId(ctx),\n"
}
}
update += "\t"
return fmt.Sprintf(LogicSwitchUpdate, update)
}
func (l *gCurd) generateLogicSwitchFields(ctx context.Context, in *CurdPreviewInput) string {
buffer := bytes.NewBuffer(nil)
if in.options.Step.HasSwitch {
@@ -71,8 +99,8 @@ func (l *gCurd) generateLogicEdit(ctx context.Context, in *CurdPreviewInput) g.M
}
}
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))
updateBuffer.WriteString(fmt.Sprintf(LogicEditUpdate, updateFieldsEx, in.In.DaoName, in.pk.GoName, in.pk.GoName))
insertBuffer.WriteString(fmt.Sprintf(LogicEditInsert, insertFieldsEx))
data["update"] = updateBuffer.String()
data["insert"] = insertBuffer.String()

View File

@@ -34,7 +34,7 @@ func (l *gCurd) generateWebEditFormItem(ctx context.Context, in *CurdPreviewInpu
}
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)
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
)
@@ -43,65 +43,68 @@ func (l *gCurd) generateWebEditFormItem(ctx context.Context, in *CurdPreviewInpu
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)
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)
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)
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)
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)
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)
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)
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])
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])
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])
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)
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)
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)
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)
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)
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-switch :unchecked-value=\"2\" :checked-value=\"1\" 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)
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)
case FormModeCitySelector:
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <CitySelector v-model:value=\"params.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
default:
component = defaultComponent
}
if len(in.masterFields) == k {
buffer.WriteString(" " + component)
buffer.WriteString(" " + component)
} else {
buffer.WriteString(" " + component + "\n\n")
buffer.WriteString(" " + component + "\n\n")
}
}
@@ -117,11 +120,12 @@ func (l *gCurd) generateWebEditScript(ctx context.Context, in *CurdPreviewInput)
if in.options.Step.HasMaxSort {
importBuffer.WriteString(" import { onMounted, ref, computed, watch } from 'vue';\n")
importBuffer.WriteString(" import { Edit, MaxSort } 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")
importBuffer.WriteString(" import { Edit, MaxSort, View } from '@/api/" + gstr.LcFirst(in.In.VarName) + "';\n")
setupBuffer.WriteString(" function loadForm(value) {\n loading.value = true;\n\n // 新增\n if (value.id < 1) {\n params.value = newState(value);\n MaxSort()\n .then((res) => {\n params.value.sort = res.sort;\n })\n .finally(() => {\n loading.value = false;\n });\n return;\n }\n\n // 编辑\n View({ id: value.id })\n .then((res) => {\n params.value = res;\n })\n .finally(() => {\n loading.value = false;\n });\n }\n\n watch(\n () => props.formParams,\n (value) => {\n loadForm(value);\n }\n );")
} else {
importBuffer.WriteString(" import { onMounted, ref, computed } from 'vue';\n")
importBuffer.WriteString(" import { Edit } from '@/api/" + gstr.LcFirst(in.In.VarName) + "';\n")
importBuffer.WriteString(" import { Edit, View } from '@/api/" + gstr.LcFirst(in.In.VarName) + "';\n")
setupBuffer.WriteString(" function loadForm(value) {\n // 新增\n if (value.id < 1) {\n params.value = newState(value);\n loading.value = false;\n return;\n }\n\n loading.value = true;\n // 编辑\n View({ id: value.id })\n .then((res) => {\n params.value = res;\n })\n .finally(() => {\n loading.value = false;\n });\n }\n\n watch(\n () => props.formParams,\n (value) => {\n loadForm(value);\n }\n );")
}
for _, field := range in.masterFields {
@@ -147,12 +151,15 @@ func (l *gCurd) generateWebEditScript(ctx context.Context, in *CurdPreviewInput)
}
case FormModeRate:
setupBuffer.WriteString(fmt.Sprintf(" function update%s(num) {\n params.value.%s = num;\n }\n", field.GoName, field.TsName))
case FormModeCitySelector:
if !gstr.Contains(importBuffer.String(), `import CitySelector`) {
importBuffer.WriteString(" import CitySelector from '@/components/CitySelector/citySelector.vue';\n")
}
}
}
data["import"] = importBuffer.String()
data["setup"] = setupBuffer.String()
return data
}

View File

@@ -1,4 +1,4 @@
// Package hggen
// Package views
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Author Ms <133814250@qq.com>
@@ -8,9 +8,12 @@ package views
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/consts"
"hotgo/internal/model"
"hotgo/internal/model/input/sysin"
"io/ioutil"
"strings"
@@ -94,3 +97,41 @@ func ImportSql(ctx context.Context, path string) error {
return nil
}
func checkCurdPath(temp *model.GenerateAppCrudTemplate) (err error) {
if temp == nil {
return gerror.New("生成模板配置不能为空")
}
tip := `生成模板配置参数'%s'路径不存在,请先创建路径`
if !gfile.Exists(temp.TemplatePath) {
return gerror.Newf(tip, "TemplatePath")
}
if !gfile.Exists(temp.ApiPath) {
return gerror.Newf(tip, "ApiPath")
}
if !gfile.Exists(temp.InputPath) {
return gerror.Newf(tip, "InputPath")
}
if !gfile.Exists(temp.ControllerPath) {
return gerror.Newf(tip, "ControllerPath")
}
if !gfile.Exists(temp.LogicPath) {
return gerror.Newf(tip, "LogicPath")
}
if !gfile.Exists(temp.RouterPath) {
return gerror.Newf(tip, "RouterPath")
}
if !gfile.Exists(temp.SqlPath) {
return gerror.Newf(tip, "SqlPath")
}
if !gfile.Exists(temp.WebApiPath) {
return gerror.Newf(tip, "WebApiPath")
}
if !gfile.Exists(temp.WebViewsPath) {
return gerror.Newf(tip, "WebViewsPath")
}
return
}

View File

@@ -19,15 +19,6 @@ import (
"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
@@ -40,6 +31,15 @@ type Join struct {
fields map[string]*gdb.TableField // 表字段列表
}
// GenJoinOnRelation 生成关联表关联条件
func GenJoinOnRelation(masterTable, masterField, joinTable, alias, onField string) []string {
return []string{
joinTable,
alias,
fmt.Sprintf("`%s`.`%s` = `%s`.`%s`", alias, onField, masterTable, masterField),
}
}
// GenJoinSelect 生成关联表select
// 这里会将实体中的字段驼峰转为下划线于数据库进行匹配,意味着数据库字段必须全部是小写字母+下划线的格式
func GenJoinSelect(ctx context.Context, entity interface{}, masterDao interface{}, joins []*Join) (allFields string, err error) {

View File

@@ -1,14 +1,6 @@
// 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
package handler
// 预处理
import (
"fmt"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/text/gstr"
@@ -18,22 +10,15 @@ import (
"hotgo/utility/tree"
)
// HandlerFilterAuth 过滤数据权限
// FilterAuth 过滤数据权限
// 通过上下文中的用户角色权限和表中是否含有需要过滤的字段附加查询条件
func HandlerFilterAuth(m *gdb.Model) *gdb.Model {
func FilterAuth(m *gdb.Model) *gdb.Model {
var (
needAuth bool
filterField string
role *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
@@ -49,43 +34,54 @@ func HandlerFilterAuth(m *gdb.Model) *gdb.Model {
return m
}
err := g.Model("admin_role").Where("id", co.User.RoleId).Scan(&role)
if err != nil {
panic(fmt.Sprintf("failed to role information err:%+v", err))
}
if role == nil {
panic("failed to role information roleModel == nil")
}
sq := g.Model("admin_member").Fields("id")
switch role.DataScope {
case consts.RoleDataAll: // 全部权限
// ...
case consts.RoleDataNowDept: // 当前部门
m = m.WhereIn(filterField, sq.Where("dept_id", co.User.DeptId))
case consts.RoleDataDeptAndSub: // 当前部门及以下部门
m = m.WhereIn(filterField, sq.WhereIn("dept_id", GetDeptAndSub(co.User.DeptId)))
case consts.RoleDataDeptCustom: // 自定义部门
m = m.WhereIn(filterField, sq.WhereIn("dept_id", role.CustomDept.Var().Ints()))
case consts.RoleDataSelf: // 仅自己
m = m.Where(filterField, co.User.Id)
case consts.RoleDataSelfAndSub: // 自己和直属下级
m = m.WhereIn(filterField, GetSelfAndSub(co.User.Id))
case consts.RoleDataSelfAndAllSub: // 自己和全部下级
m = m.WhereIn(filterField, GetSelfAndAllSub(co.User.Id))
default:
panic("dataScope is not registered")
}
return m
return m.Handler(FilterAuthWithField(filterField))
}
// HandlerForceCache 强制缓存
func HandlerForceCache(m *gdb.Model) *gdb.Model {
return m.Cache(gdb.CacheOption{Duration: -1, Force: true})
// FilterAuthWithField 过滤数据权限,设置指定字段
func FilterAuthWithField(filterField string) func(m *gdb.Model) *gdb.Model {
return func(m *gdb.Model) *gdb.Model {
var (
role *entity.AdminRole
ctx = m.GetCtx()
co = contexts.Get(ctx)
)
if co == nil || co.User == nil {
return m
}
err := g.Model("admin_role").Where("id", co.User.RoleId).Scan(&role)
if err != nil {
g.Log().Fatalf(ctx, "failed to role information err:%+v", err)
}
if role == nil {
g.Log().Fatalf(ctx, "failed to role information roleModel == nil")
}
sq := g.Model("admin_member").Fields("id")
switch role.DataScope {
case consts.RoleDataAll: // 全部权限
// ...
case consts.RoleDataNowDept: // 当前部门
m = m.WhereIn(filterField, sq.Where("dept_id", co.User.DeptId))
case consts.RoleDataDeptAndSub: // 当前部门及以下部门
m = m.WhereIn(filterField, sq.WhereIn("dept_id", GetDeptAndSub(co.User.DeptId)))
case consts.RoleDataDeptCustom: // 自定义部门
m = m.WhereIn(filterField, sq.WhereIn("dept_id", role.CustomDept.Var().Ints()))
case consts.RoleDataSelf: // 仅自己
m = m.Where(filterField, co.User.Id)
case consts.RoleDataSelfAndSub: // 自己和直属下级
m = m.WhereIn(filterField, GetSelfAndSub(co.User.Id))
case consts.RoleDataSelfAndAllSub: // 自己和全部下级
m = m.WhereIn(filterField, GetSelfAndAllSub(co.User.Id))
default:
g.Log().Fatalf(ctx, "dataScope is not registered")
}
return m
}
}
// escapeFieldsToSlice 将转义过的字段转换为字段集切片

View File

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

View File

@@ -0,0 +1,39 @@
// Package handler
// @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 handler
// handler.
import (
"github.com/gogf/gf/v2/database/gdb"
)
// Option 预处理选项
type Option struct {
FilterAuth bool // 过滤权限
ForceCache bool // 强制缓存
}
// DefaultOption 默认预处理选项
var DefaultOption = &Option{
FilterAuth: true,
}
func Model(m *gdb.Model, opt ...*Option) *gdb.Model {
var option *Option
if len(opt) > 0 {
option = opt[0]
} else {
option = DefaultOption
}
if option.FilterAuth {
m = m.Handler(FilterAuth)
}
if option.ForceCache {
m = m.Handler(ForceCache)
}
return m
}

View File

@@ -1,9 +0,0 @@
// 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

@@ -0,0 +1,3 @@
package hook
// hook.

View File

@@ -0,0 +1,58 @@
package hook
import (
"context"
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// MemberInfo 后台用户信息
var MemberInfo = gdb.HookHandler{
Select: func(ctx context.Context, in *gdb.HookSelectInput) (result gdb.Result, err error) {
result, err = in.Next(ctx)
if err != nil {
return
}
for i, record := range result {
// 部门
if !record["dept_id"].IsEmpty() {
deptName, err := g.Model("admin_dept").Ctx(ctx).
Fields("name").
Where("id", record["dept_id"]).
Value()
if err != nil {
break
}
record["deptName"] = deptName
}
// 角色
if !record["role_id"].IsEmpty() {
roleName, err := g.Model("admin_role").Ctx(ctx).
Fields("name").
Where("id", record["role_id"]).
Value()
if err != nil {
break
}
record["roleName"] = roleName
}
if !record["password_hash"].IsEmpty() {
record["password_hash"] = gvar.New("")
}
if !record["salt"].IsEmpty() {
record["salt"] = gvar.New("")
}
if !record["auth_key"].IsEmpty() {
record["auth_key"] = gvar.New("")
}
result[i] = record
}
return
},
}

View File

@@ -0,0 +1,59 @@
package hook
import (
"context"
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"hotgo/internal/library/location"
)
// CityLabel 城市地区标签
var CityLabel = gdb.HookHandler{
Select: func(ctx context.Context, in *gdb.HookSelectInput) (result gdb.Result, err error) {
result, err = in.Next(ctx)
if err != nil {
return
}
parse := func(id int64, index int) {
cityLabel, err := location.ParseSimpleRegion(ctx, id)
if err != nil {
g.Log().Warningf(ctx, "hook.CityLabel parse err:%+v", err)
}
result[index]["cityLabel"] = gvar.New(cityLabel)
return
}
for i, record := range result {
// 优先级: 区 > 市 > 省
cityId, ok := record["city_id"]
if ok && !cityId.IsEmpty() {
parse(cityId.Int64(), i)
continue
}
provinceId, ok := record["province_id"]
if ok && !provinceId.IsEmpty() {
parse(cityId.Int64(), i)
continue
}
// 以下是默认关联表 省市区字段
sysLogCityId, ok := record["sysLogCityId"]
if ok && !sysLogCityId.IsEmpty() {
parse(sysLogCityId.Int64(), i)
continue
}
sysLogProvinceId, ok := record["sysLogProvinceId"]
if ok && !sysLogProvinceId.IsEmpty() {
parse(cityId.Int64(), i)
continue
}
}
return
},
}

View File

@@ -38,12 +38,9 @@ func GenerateLoginToken(ctx context.Context, user *model.Identity, isRefresh boo
"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(),
})

View File

@@ -46,8 +46,8 @@ type AddressRegion struct {
CountyCode string `json:"countyCode"`
}
// AnalysisAddress 将地址解析出省市区编码
func AnalysisAddress(ctx context.Context, address, key string) (region *AddressRegion, err error) {
// ParseAddress 将地址解析出省市区编码
func ParseAddress(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)

View File

@@ -141,12 +141,12 @@ 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)
g.Log().Infof(ctx, "GetPublicIP alternatives are being tried err:%+v", err)
return GetPublicIP2()
}
if data == nil {
g.Log().Warningf(ctx, "publicIP address Parsing failure, check the network and firewall blocking.")
g.Log().Infof(ctx, "publicIP address Parsing failure, check the network and firewall blocking.")
return "0.0.0.0", nil
}
return data.Ip, nil

View File

@@ -0,0 +1,104 @@
package location
import (
"context"
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"hotgo/internal/consts"
"hotgo/internal/model/entity"
"hotgo/utility/tree"
)
// ParseSimpleRegion 通过地区ID解析地区名称自动加入上级地区
func ParseSimpleRegion(ctx context.Context, id int64, spilt ...string) (string, error) {
if id == 0 {
return "", nil
}
var (
models *entity.SysProvinces
err error
)
if err = g.Model("sys_provinces").Ctx(ctx).Fields("title,level,tree").Where("id", id).Scan(&models); err != nil {
return "", err
}
if models == nil {
return "", gerror.Newf("the area code :%v is not in the database", id)
}
if models.Level == 1 {
return models.Title, nil
}
ids := tree.GetIds(models.Tree)
if models.Level == 2 {
if len(ids) != 1 {
return "", gerror.Newf("the region code is incorrectly configured, models:%+v, ids:%v", models, ids)
}
return ParseRegion(ctx, ids[0], id, 0, spilt...)
}
if models.Level == 3 {
if len(ids) != 2 {
return "", gerror.Newf("the region code is incorrectly configured, models:%+v, ids:%v", models, ids)
}
return ParseRegion(ctx, ids[0], ids[1], id, spilt...)
}
return "", gerror.New("currently, it is only supported to regional areas")
}
// ParseRegion 解析省市编码对应的地区名称
func ParseRegion(ctx context.Context, province int64, city int64, county int64, spilt ...string) (string, error) {
var (
provinceName *gvar.Var
cityName *gvar.Var
countyName *gvar.Var
err error
)
// 分隔符
sp := consts.RegionSpilt
if len(spilt) > 0 {
sp = spilt[0]
}
if province > 0 && province < 999999 {
provinceName, err = g.Model("sys_provinces").Ctx(ctx).Where("id", province).Fields("title").Value()
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return "", err
}
if city > 0 {
cityName, err = g.Model("sys_provinces").Ctx(ctx).Where("id", city).Fields("title").Value()
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return "", err
}
}
if county > 0 {
countyName, err = g.Model("sys_provinces").Ctx(ctx).Where("id", county).Fields("title").Value()
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return "", err
}
}
} else {
return "保留地址", nil
}
if province > 0 && city > 0 && county > 0 {
return provinceName.String() + sp + cityName.String() + sp + countyName.String(), nil
}
if province > 0 && city > 0 {
return provinceName.String() + sp + cityName.String(), nil
}
return provinceName.String(), nil
}

View File

@@ -79,8 +79,9 @@ var (
func init() {
mqProducerInstanceMap = make(map[string]MqProducer)
mqConsumerInstanceMap = make(map[string]MqConsumer)
get := g.Cfg().MustGet(ctx, "queue")
get.Scan(&config)
if err := g.Cfg().MustGet(ctx, "queue").Scan(&config); err != nil {
g.Log().Infof(ctx, "queue init err:%+v", err)
}
}
// InstanceConsumer 实例化消费者
@@ -100,30 +101,34 @@ func NewProducer(groupName string) (mqClient MqProducer, err error) {
}
if groupName == "" {
return mqClient, gerror.New("mq groupName is empty.")
err = gerror.New("mq groupName is empty.")
return
}
switch config.Driver {
case "rocketmq":
if len(config.Rocketmq.Address) == 0 {
g.Log().Fatal(ctx, "queue rocketmq address is not support")
err = gerror.New("queue rocketmq address is not support")
return
}
mqClient = RegisterRocketProducerMust(config.Rocketmq.Address, groupName, config.Retry)
mqClient, err = RegisterRocketProducer(config.Rocketmq.Address, groupName, config.Retry)
case "kafka":
if len(config.Kafka.Address) == 0 {
g.Log().Fatal(ctx, "queue kafka address is not support")
err = gerror.New("queue kafka address is not support")
return
}
mqClient = RegisterKafkaProducerMust(KafkaConfig{
mqClient, err = RegisterKafkaProducer(KafkaConfig{
Brokers: config.Kafka.Address,
GroupID: groupName,
Version: config.Kafka.Version,
})
case "redis":
address := g.Cfg().MustGet(ctx, "queue.redis.address", nil)
if len(address.String()) == 0 {
g.Log().Fatal(ctx, "queue redis address is not support")
address := g.Cfg().MustGet(ctx, "queue.redis.address", nil).String()
if len(address) == 0 {
err = gerror.New("queue redis address is not support")
return
}
mqClient = RegisterRedisMqProducerMust(RedisOption{
mqClient, err = RegisterRedisMqProducer(RedisOption{
Addr: config.Redis.Address,
Passwd: config.Redis.Pass,
DBnum: config.Redis.Db,
@@ -133,14 +138,18 @@ func NewProducer(groupName string) (mqClient MqProducer, err error) {
}, groupName, config.Retry)
default:
g.Log().Fatal(ctx, "queue driver is not support")
err = gerror.New("queue driver is not support")
}
if err != nil {
return
}
mutex.Lock()
defer mutex.Unlock()
mqProducerInstanceMap[groupName] = mqClient
return mqClient, nil
return
}
// NewConsumer 初始化消费者实例
@@ -157,18 +166,21 @@ func NewConsumer(groupName string) (mqClient MqConsumer, err error) {
}
if groupName == "" {
return mqClient, gerror.New("mq groupName is empty.")
err = gerror.New("mq groupName is empty.")
return
}
switch config.Driver {
case "rocketmq":
if len(config.Rocketmq.Address) == 0 {
return nil, gerror.New("queue.rocketmq.address is empty.")
err = gerror.New("queue.rocketmq.address is empty.")
return
}
mqClient = RegisterRocketConsumerMust(config.Rocketmq.Address, groupName)
mqClient, err = RegisterRocketConsumer(config.Rocketmq.Address, groupName)
case "kafka":
if len(config.Kafka.Address) == 0 {
g.Log().Fatal(ctx, "queue kafka address is not support")
err = gerror.New("queue kafka address is not support")
return
}
clientId := "HOTGO-Consumer-" + groupName
@@ -176,7 +188,7 @@ func NewConsumer(groupName string) (mqClient MqConsumer, err error) {
clientId += "-" + randTag
}
mqClient = RegisterKafkaMqConsumerMust(KafkaConfig{
mqClient, err = RegisterKafkaMqConsumer(KafkaConfig{
Brokers: config.Kafka.Address,
GroupID: groupName,
Version: config.Kafka.Version,
@@ -184,10 +196,11 @@ func NewConsumer(groupName string) (mqClient MqConsumer, err error) {
})
case "redis":
if len(config.Redis.Address) == 0 {
g.Log().Fatal(ctx, "queue redis address is not support")
err = gerror.New("queue redis address is not support")
return
}
mqClient = RegisterRedisMqConsumerMust(RedisOption{
mqClient, err = RegisterRedisMqConsumer(RedisOption{
Addr: config.Redis.Address,
Passwd: config.Redis.Pass,
DBnum: config.Redis.Db,
@@ -196,14 +209,18 @@ func NewConsumer(groupName string) (mqClient MqConsumer, err error) {
5, 50, 5,
}, groupName)
default:
g.Log().Fatal(ctx, "queue driver is not support")
err = gerror.New("queue driver is not support")
}
if err != nil {
return
}
mutex.Lock()
defer mutex.Unlock()
mqConsumerInstanceMap[groupName] = mqClient
return mqClient, nil
return
}
// BodyString 返回消息体

View File

@@ -48,12 +48,13 @@ func (r *KafkaMq) SendByteMsg(topic string, body []byte) (mqMsg MqMsg, err error
}
if r.producerIns == nil {
return mqMsg, gerror.New("queue kafka producerIns is nil")
err = gerror.New("queue kafka producerIns is nil")
return
}
r.producerIns.Input() <- msg
ctx, cancle := context.WithTimeout(context.Background(), 5*time.Second)
defer cancle()
sendCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
select {
case info := <-r.producerIns.Successes():
@@ -68,7 +69,7 @@ func (r *KafkaMq) SendByteMsg(topic string, body []byte) (mqMsg MqMsg, err error
if nil != fail {
return mqMsg, fail.Err
}
case <-ctx.Done():
case <-sendCtx.Done():
return mqMsg, gerror.New("send mqMst timeout")
}
@@ -86,22 +87,23 @@ func (r *KafkaMq) ListenReceiveMsgDo(topic string, receiveDo func(mqMsg MqMsg))
receiveDoFun: receiveDo,
}
ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context) {
consumerCtx, cancel := context.WithCancel(context.Background())
go func(consumerCtx context.Context) {
for {
if err := r.consumerIns.Consume(ctx, []string{topic}, &consumer); err != nil {
if err = r.consumerIns.Consume(consumerCtx, []string{topic}, &consumer); err != nil {
g.Log().Fatalf(ctx, "kafka Error from consumer, err%+v", err)
}
if ctx.Err() != nil {
g.Log().Debugf(ctx, fmt.Sprintf("kafka consoumer stop : %v", ctx.Err()))
if consumerCtx.Err() != nil {
g.Log().Debugf(ctx, fmt.Sprintf("kafka consoumer stop : %v", consumerCtx.Err()))
return
}
consumer.ready = make(chan bool)
}
}(ctx)
}(consumerCtx)
<-consumer.ready // Await till the consumer has been set up
// await till the consumer has been set up
<-consumer.ready
g.Log().Debug(ctx, "kafka consumer up and running!...")
signal.AppDefer(func() {
@@ -115,85 +117,94 @@ func (r *KafkaMq) ListenReceiveMsgDo(topic string, receiveDo func(mqMsg MqMsg))
return
}
// RegisterKafkaMqConsumerMust 注册消费者
func RegisterKafkaMqConsumerMust(connOpt KafkaConfig) (client MqConsumer) {
// RegisterKafkaMqConsumer 注册消费者
func RegisterKafkaMqConsumer(connOpt KafkaConfig) (client MqConsumer, err error) {
mqIns := &KafkaMq{}
kfkVersion, _ := sarama.ParseKafkaVersion(connOpt.Version)
kfkVersion, err := sarama.ParseKafkaVersion(connOpt.Version)
if err != nil {
return
}
if validateVersion(kfkVersion) == false {
kfkVersion = sarama.V2_4_0_0
}
brokers := connOpt.Brokers
config := sarama.NewConfig()
config.Consumer.Return.Errors = true
config.Version = kfkVersion
conf := sarama.NewConfig()
conf.Consumer.Return.Errors = true
conf.Version = kfkVersion
if connOpt.UserName != "" {
config.Net.SASL.Enable = true
config.Net.SASL.User = connOpt.UserName
config.Net.SASL.Password = connOpt.Password
conf.Net.SASL.Enable = true
conf.Net.SASL.User = connOpt.UserName
conf.Net.SASL.Password = connOpt.Password
}
// 默认按随机方式消费
config.Consumer.Group.Rebalance.Strategy = sarama.BalanceStrategyRange
config.Consumer.Offsets.Initial = sarama.OffsetNewest
config.Consumer.Offsets.AutoCommit.Interval = 10 * time.Millisecond
config.ClientID = connOpt.ClientId
conf.Consumer.Group.Rebalance.Strategy = sarama.BalanceStrategyRange
conf.Consumer.Offsets.Initial = sarama.OffsetNewest
conf.Consumer.Offsets.AutoCommit.Interval = 10 * time.Millisecond
conf.ClientID = connOpt.ClientId
consumerClient, err := sarama.NewConsumerGroup(brokers, connOpt.GroupID, config)
consumerClient, err := sarama.NewConsumerGroup(brokers, connOpt.GroupID, conf)
if err != nil {
g.Log().Fatal(ctx, err)
return
}
mqIns.consumerIns = consumerClient
return mqIns
return mqIns, err
}
// RegisterKafkaProducerMust 注册并启动生产者接口实现
func RegisterKafkaProducerMust(connOpt KafkaConfig) (client MqProducer) {
// RegisterKafkaProducer 注册并启动生产者接口实现
func RegisterKafkaProducer(connOpt KafkaConfig) (client MqProducer, err error) {
mqIns := &KafkaMq{}
connOpt.ClientId = "HOTGO-Producer"
RegisterKafkaProducer(connOpt, mqIns) //这里如果使用go程需要处理chan同步问题
return mqIns
// 这里如果使用go程需要处理chan同步问题
if err = doRegisterKafkaProducer(connOpt, mqIns); err != nil {
return nil, err
}
return mqIns, nil
}
// RegisterKafkaProducer 注册同步类型实例
func RegisterKafkaProducer(connOpt KafkaConfig, mqIns *KafkaMq) {
kfkVersion, _ := sarama.ParseKafkaVersion(connOpt.Version)
// doRegisterKafkaProducer 注册同步类型实例
func doRegisterKafkaProducer(connOpt KafkaConfig, mqIns *KafkaMq) (err error) {
kfkVersion, err := sarama.ParseKafkaVersion(connOpt.Version)
if err != nil {
return
}
if validateVersion(kfkVersion) == false {
kfkVersion = sarama.V2_4_0_0
}
brokers := connOpt.Brokers
config := sarama.NewConfig()
conf := sarama.NewConfig()
// 等待服务器所有副本都保存成功后的响应
config.Producer.RequiredAcks = sarama.WaitForAll
conf.Producer.RequiredAcks = sarama.WaitForAll
// 随机向partition发送消息
config.Producer.Partitioner = sarama.NewRandomPartitioner
conf.Producer.Partitioner = sarama.NewRandomPartitioner
// 是否等待成功和失败后的响应,只有上面的RequireAcks设置不是NoReponse这里才有用.
config.Producer.Return.Successes = true
conf.Producer.Return.Successes = true
config.Producer.Return.Errors = true
config.Producer.Compression = sarama.CompressionNone
config.ClientID = connOpt.ClientId
conf.Producer.Return.Errors = true
conf.Producer.Compression = sarama.CompressionNone
conf.ClientID = connOpt.ClientId
config.Version = kfkVersion
conf.Version = kfkVersion
if connOpt.UserName != "" {
config.Net.SASL.Enable = true
config.Net.SASL.User = connOpt.UserName
config.Net.SASL.Password = connOpt.Password
conf.Net.SASL.Enable = true
conf.Net.SASL.User = connOpt.UserName
conf.Net.SASL.Password = connOpt.Password
}
var err error
mqIns.producerIns, err = sarama.NewAsyncProducer(brokers, config)
mqIns.producerIns, err = sarama.NewAsyncProducer(brokers, conf)
if err != nil {
g.Log().Fatal(ctx, err)
return
}
signal.AppDefer(func() {
g.Log().Debug(ctx, "kafka producer AsyncClose...")
mqIns.producerIns.AsyncClose()
})
return
}
// validateVersion 验证版本是否有效

View File

@@ -1,7 +1,6 @@
package queue
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
@@ -9,8 +8,7 @@ import (
func Push(topic string, data interface{}) (err error) {
q, err := InstanceProducer()
if err != nil {
g.Log().Fatalf(ctx, "queue.InstanceProducer err:%+v", err)
return err
return
}
mqMsg, err := q.SendMsg(topic, gconv.String(data))
ProducerLog(ctx, topic, mqMsg.MsgId, err)

View File

@@ -146,25 +146,23 @@ func (r *RedisMq) loopReadQueue(queueName string) (mqMsgList []MqMsg) {
return mqMsgList
}
func RegisterRedisMqProducerMust(connOpt RedisOption, poolOpt PoolOption, groupName string, retry int) (client MqProducer) {
var err error
func RegisterRedisMqProducer(connOpt RedisOption, poolOpt PoolOption, groupName string, retry int) (client MqProducer, err error) {
client, err = RegisterRedisMq(connOpt, poolOpt, groupName, retry)
if err != nil {
g.Log().Fatal(ctx, "RegisterRedisMqProducerMust err:%+v", err)
err = gerror.Newf("RegisterRedisMqProducer err:%+v", err)
return
}
return client
return
}
// RegisterRedisMqConsumerMust 注册消费者
func RegisterRedisMqConsumerMust(connOpt RedisOption, poolOpt PoolOption, groupName string) (client MqConsumer) {
var err error
// RegisterRedisMqConsumer 注册消费者
func RegisterRedisMqConsumer(connOpt RedisOption, poolOpt PoolOption, groupName string) (client MqConsumer, err error) {
client, err = RegisterRedisMq(connOpt, poolOpt, groupName, 0)
if err != nil {
g.Log().Fatal(ctx, "RegisterRedisMqConsumerMust err:%+v", err)
err = gerror.Newf("RegisterRedisMqConsumer err:%+v", err)
return
}
return client
return
}
// RegisterRedisMq 注册redis实例
@@ -200,12 +198,12 @@ func registerRedis(host, pass string, dbNum int, opt PoolOption) (poolName strin
return nil, err
}
if pass != "" {
if _, err := conn.Do("AUTH", pass); err != nil {
if _, err = conn.Do("AUTH", pass); err != nil {
return nil, err
}
}
if dbNum > 0 {
if _, err := conn.Do("SELECT", dbNum); err != nil {
if _, err = conn.Do("SELECT", dbNum); err != nil {
return nil, err
}
}
@@ -270,19 +268,20 @@ func getRedis(poolName string, retry int) (db redis.Conn, put func(), err error)
if err != nil {
return nil, put, err
}
put = func() {
redisPool.Put(conn)
if err = redisPool.Put(conn); err != nil {
return
}
}
db = conn.(redis.Conn)
return db, put, nil
}
func getRandMsgId() (msgId string) {
func getRandMsgId() string {
rand.Seed(time.Now().UnixNano())
radium := rand.Intn(999) + 1
timeCode := time.Now().UnixNano()
msgId = fmt.Sprintf("%d%.4d", timeCode, radium)
return msgId
return fmt.Sprintf("%d%.4d", timeCode, radium)
}

View File

@@ -26,30 +26,27 @@ type RocketMq struct {
// rewriteLog 重写日志
func rewriteLog() {
level := g.Cfg().MustGet(ctx, "queue.rocketmq.logLevel", "debug").String()
rlog.SetLogger(&RocketMqLogger{Flag: "[rocket_mq]", LevelLog: level})
rlog.SetLogger(&RocketMqLogger{Flag: "[rocket_mq]", LevelLog: g.Cfg().MustGet(ctx, "queue.rocketmq.logLevel", "debug").String()})
}
// RegisterRocketProducerMust 注册并启动生产者接口实现
func RegisterRocketProducerMust(endPoints []string, groupName string, retry int) (client MqProducer) {
// RegisterRocketProducer 注册并启动生产者接口实现
func RegisterRocketProducer(endPoints []string, groupName string, retry int) (client MqProducer, err error) {
rewriteLog()
var err error
client, err = RegisterRocketMqProducer(endPoints, groupName, retry)
if err != nil {
panic(err)
return
}
return client
return
}
// RegisterRocketConsumerMust 注册消费者
func RegisterRocketConsumerMust(endPoints []string, groupName string) (client MqConsumer) {
// RegisterRocketConsumer 注册消费者
func RegisterRocketConsumer(endPoints []string, groupName string) (client MqConsumer, err error) {
rewriteLog()
var err error
client, err = RegisterRocketMqConsumer(endPoints, groupName)
if err != nil {
panic(err)
return
}
return client
return
}
// SendMsg 按字符串类型生产数据
@@ -90,8 +87,7 @@ func (r *RocketMq) ListenReceiveMsgDo(topic string, receiveDo func(mqMsg MqMsg))
return errors.New("RocketMq consumer not register")
}
err = r.consumerIns.Subscribe(topic, consumer.MessageSelector{}, func(ctx context.Context,
msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
err = r.consumerIns.Subscribe(topic, consumer.MessageSelector{}, func(ctx context.Context, msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
for _, item := range msgs {
go receiveDo(MqMsg{
RunType: ReceiveMsg,