mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-08-26 16:46:14 +08:00
发布v2.15.1版本,更新内容请查看:https://github.com/bufanyun/hotgo/blob/v2.0/docs/guide-zh-CN/start-update-log.md
This commit is contained in:
@@ -36,6 +36,8 @@ var (
|
||||
---------------------------------------------------------------------------------
|
||||
工具
|
||||
>> 释放casbin权限,用于清理无效的权限设置 [go run main.go tools -m=casbin -a1=refresh]
|
||||
>> 打印所有打包的资源文件列表 [go run main.go tools -m=gres -a1=dump]
|
||||
>> 打印指定打包的资源文件内容 [go run main.go tools -m=gres -a1=content -a2=resource/template/home/index.html]
|
||||
---------------------------------------------------------------------------------
|
||||
升级更新
|
||||
>> 修复菜单关系树 [go run main.go up -m=fix -a1=menuTree]
|
||||
|
@@ -23,8 +23,8 @@ var (
|
||||
|
||||
// signalHandlerForOverall 关闭信号处理
|
||||
func signalHandlerForOverall(sig os.Signal) {
|
||||
serverCloseSignal <- struct{}{}
|
||||
serverCloseEvent(gctx.GetInitCtx())
|
||||
serverCloseSignal <- struct{}{}
|
||||
}
|
||||
|
||||
// signalListen 信号监听
|
||||
|
@@ -7,9 +7,11 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gcmd"
|
||||
"github.com/gogf/gf/v2/os/gres"
|
||||
"hotgo/internal/library/casbin"
|
||||
)
|
||||
|
||||
@@ -35,6 +37,8 @@ var (
|
||||
switch method {
|
||||
case "casbin":
|
||||
err = handleCasbin(ctx, args)
|
||||
case "gres":
|
||||
err = handleGRes(ctx, args)
|
||||
default:
|
||||
err = gerror.Newf("tools method[%v] does not exist", method)
|
||||
}
|
||||
@@ -66,3 +70,38 @@ func handleCasbin(ctx context.Context, args map[string]string) (err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func handleGRes(ctx context.Context, args map[string]string) (err error) {
|
||||
a1, ok := args["a1"]
|
||||
if !ok {
|
||||
err = gerror.New("gres args cannot be empty.")
|
||||
return
|
||||
}
|
||||
|
||||
switch a1 {
|
||||
case "dump":
|
||||
gres.Dump()
|
||||
case "content":
|
||||
path, ok := args["a2"]
|
||||
if !ok {
|
||||
err = gerror.New("缺少查看文件路径参数:`a2`")
|
||||
return
|
||||
}
|
||||
|
||||
if !gres.Contains(path) {
|
||||
err = gerror.Newf("没有找到资源文件:%v", path)
|
||||
return
|
||||
}
|
||||
content := string(gres.GetContent(path))
|
||||
|
||||
if len(content) == 0 {
|
||||
err = gerror.Newf("没有找到资源文件内容,请确认传入`a2`参数是一个文件,a2:%v", path)
|
||||
return
|
||||
}
|
||||
fmt.Println("以下是资源文件内容:")
|
||||
fmt.Println(content)
|
||||
default:
|
||||
err = gerror.Newf("handleGRes a1 is invalid, a1:%v", a1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@@ -22,3 +22,10 @@ const (
|
||||
DefaultPageSize = 1 // 默认列表分页加载页码
|
||||
MaxSortIncr = 10 // 最大排序值增量
|
||||
)
|
||||
|
||||
// TenantField 租户字段
|
||||
const (
|
||||
TenantId = "tenant_id" // 租户ID
|
||||
MerchantId = "merchant_id" // 商户ID
|
||||
UserId = "user_id" // 用户ID
|
||||
)
|
||||
|
30
server/internal/consts/dept.go
Normal file
30
server/internal/consts/dept.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Package consts
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2024 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package consts
|
||||
|
||||
import (
|
||||
"hotgo/internal/library/dict"
|
||||
"hotgo/internal/model"
|
||||
)
|
||||
|
||||
func init() {
|
||||
dict.RegisterEnums("deptType", "部门类型选项", DeptTypeOptions)
|
||||
}
|
||||
|
||||
const (
|
||||
DeptTypeCompany = "company" // 公司
|
||||
DeptTypeTenant = "tenant" // 租户
|
||||
DeptTypeMerchant = "merchant" // 商户
|
||||
DeptTypeUser = "user" // 用户
|
||||
)
|
||||
|
||||
// DeptTypeOptions 部门类型选项
|
||||
var DeptTypeOptions = []*model.Option{
|
||||
dict.GenSuccessOption(DeptTypeCompany, "公司"),
|
||||
dict.GenErrorOption(DeptTypeTenant, "租户"),
|
||||
dict.GenInfoOption(DeptTypeMerchant, "商户"),
|
||||
dict.GenWarningOption(DeptTypeUser, "用户"),
|
||||
}
|
@@ -3,9 +3,13 @@
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package consts
|
||||
|
||||
import (
|
||||
"hotgo/internal/library/dict"
|
||||
"hotgo/internal/model"
|
||||
)
|
||||
|
||||
// 生成代码类型
|
||||
const (
|
||||
GenCodesTypeCurd = 10 // 增删改查列表
|
||||
@@ -16,7 +20,7 @@ const (
|
||||
|
||||
var GenCodesTypeNameMap = map[int]string{
|
||||
GenCodesTypeCurd: "增删改查列表",
|
||||
GenCodesTypeTree: "关系树列表(未实现)",
|
||||
GenCodesTypeTree: "关系树列表",
|
||||
GenCodesTypeQueue: "队列消费者(未实现)",
|
||||
GenCodesTypeCron: "定时任务(未实现)",
|
||||
}
|
||||
@@ -79,3 +83,14 @@ const (
|
||||
GenCodesIndexPK = "PRI" // 主键索引
|
||||
GenCodesIndexUNI = "UNI" // 唯一索引
|
||||
)
|
||||
|
||||
const (
|
||||
GenCodesTreeStyleTypeNormal = 1 // 普通树表格
|
||||
GenCodesTreeStyleTypeOption = 2 // 选项式树表
|
||||
)
|
||||
|
||||
// GenCodesTreeStyleTypeOptions 树表样式选项
|
||||
var GenCodesTreeStyleTypeOptions = []*model.Option{
|
||||
dict.GenSuccessOption(GenCodesTreeStyleTypeNormal, "普通树表格"),
|
||||
dict.GenInfoOption(GenCodesTreeStyleTypeOption, "选项式树表"),
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
func init() {
|
||||
dict.RegisterEnums("payType", "支付方式", PayTypeOptions)
|
||||
dict.RegisterEnums("payStatus", "支付状态", PayStatusOptions)
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -72,6 +73,12 @@ const (
|
||||
PayStatusOk = 2 // 已支付
|
||||
)
|
||||
|
||||
// PayStatusOptions 支付状态选项
|
||||
var PayStatusOptions = []*model.Option{
|
||||
dict.GenDefaultOption(PayStatusWait, "待支付"),
|
||||
dict.GenSuccessOption(PayStatusOk, "已支付"),
|
||||
}
|
||||
|
||||
// 退款状态
|
||||
|
||||
const (
|
||||
|
@@ -7,5 +7,5 @@ package consts
|
||||
|
||||
// VersionApp HotGo版本
|
||||
const (
|
||||
VersionApp = "2.13.1"
|
||||
VersionApp = "2.15.1"
|
||||
)
|
||||
|
@@ -54,12 +54,6 @@ func (c *cDept) List(ctx context.Context, req *dept.ListReq) (res *dept.ListRes,
|
||||
return
|
||||
}
|
||||
|
||||
// Status 更新部门状态
|
||||
func (c *cDept) Status(ctx context.Context, req *dept.StatusReq) (res *dept.StatusRes, err error) {
|
||||
err = service.AdminDept().Status(ctx, &req.DeptStatusInp)
|
||||
return
|
||||
}
|
||||
|
||||
// Option 获取部门选项树
|
||||
func (c *cDept) Option(ctx context.Context, req *dept.OptionReq) (res *dept.OptionRes, err error) {
|
||||
list, totalCount, err := service.AdminDept().Option(ctx, &req.DeptOptionInp)
|
||||
@@ -72,3 +66,10 @@ func (c *cDept) Option(ctx context.Context, req *dept.OptionReq) (res *dept.Opti
|
||||
res.PageRes.Pack(req, totalCount)
|
||||
return
|
||||
}
|
||||
|
||||
// TreeOption 获取部门管理关系树选项
|
||||
func (c *cDept) TreeOption(ctx context.Context, req *dept.TreeOptionReq) (res *dept.TreeOptionRes, err error) {
|
||||
data, err := service.AdminDept().TreeOption(ctx)
|
||||
res = (*dept.TreeOptionRes)(&data)
|
||||
return
|
||||
}
|
||||
|
@@ -8,7 +8,6 @@ package admin
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"hotgo/api/admin/monitor"
|
||||
@@ -103,12 +102,10 @@ func (c *cMonitor) UserOnlineList(ctx context.Context, req *monitor.UserOnlineLi
|
||||
return clients[i].FirstTime < clients[j].FirstTime
|
||||
})
|
||||
|
||||
isDemo := g.Cfg().MustGet(ctx, "hotgo.isDemo", false).Bool()
|
||||
_, perPage, offset := form.CalPage(req.Page, req.PerPage)
|
||||
|
||||
for k, v := range clients {
|
||||
if k >= offset && i <= perPage {
|
||||
if isDemo {
|
||||
if simple.IsDemo(ctx) {
|
||||
v.IP = consts.DemoTips
|
||||
}
|
||||
res.List = append(res.List, v)
|
||||
|
@@ -3,7 +3,7 @@
|
||||
// @Copyright Copyright (c) 2024 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
// @AutoGenerate Version 2.12.10
|
||||
// @AutoGenerate Version 2.13.1
|
||||
package sys
|
||||
|
||||
import (
|
||||
@@ -19,7 +19,7 @@ var (
|
||||
|
||||
type cCurdDemo struct{}
|
||||
|
||||
// List 查看生成演示列表
|
||||
// List 查看CURD列表列表
|
||||
func (c *cCurdDemo) List(ctx context.Context, req *curddemo.ListReq) (res *curddemo.ListRes, err error) {
|
||||
list, totalCount, err := service.SysCurdDemo().List(ctx, &req.CurdDemoListInp)
|
||||
if err != nil {
|
||||
@@ -36,19 +36,19 @@ func (c *cCurdDemo) List(ctx context.Context, req *curddemo.ListReq) (res *curdd
|
||||
return
|
||||
}
|
||||
|
||||
// Export 导出生成演示列表
|
||||
// Export 导出CURD列表列表
|
||||
func (c *cCurdDemo) Export(ctx context.Context, req *curddemo.ExportReq) (res *curddemo.ExportRes, err error) {
|
||||
err = service.SysCurdDemo().Export(ctx, &req.CurdDemoListInp)
|
||||
return
|
||||
}
|
||||
|
||||
// Edit 更新生成演示
|
||||
// Edit 更新CURD列表
|
||||
func (c *cCurdDemo) Edit(ctx context.Context, req *curddemo.EditReq) (res *curddemo.EditRes, err error) {
|
||||
err = service.SysCurdDemo().Edit(ctx, &req.CurdDemoEditInp)
|
||||
return
|
||||
}
|
||||
|
||||
// MaxSort 获取生成演示最大排序
|
||||
// MaxSort 获取CURD列表最大排序
|
||||
func (c *cCurdDemo) MaxSort(ctx context.Context, req *curddemo.MaxSortReq) (res *curddemo.MaxSortRes, err error) {
|
||||
data, err := service.SysCurdDemo().MaxSort(ctx, &req.CurdDemoMaxSortInp)
|
||||
if err != nil {
|
||||
@@ -60,7 +60,7 @@ func (c *cCurdDemo) MaxSort(ctx context.Context, req *curddemo.MaxSortReq) (res
|
||||
return
|
||||
}
|
||||
|
||||
// View 获取指定生成演示信息
|
||||
// View 获取指定CURD列表信息
|
||||
func (c *cCurdDemo) View(ctx context.Context, req *curddemo.ViewReq) (res *curddemo.ViewRes, err error) {
|
||||
data, err := service.SysCurdDemo().View(ctx, &req.CurdDemoViewInp)
|
||||
if err != nil {
|
||||
@@ -72,19 +72,13 @@ func (c *cCurdDemo) View(ctx context.Context, req *curddemo.ViewReq) (res *curdd
|
||||
return
|
||||
}
|
||||
|
||||
// Delete 删除生成演示
|
||||
// Delete 删除CURD列表
|
||||
func (c *cCurdDemo) Delete(ctx context.Context, req *curddemo.DeleteReq) (res *curddemo.DeleteRes, err error) {
|
||||
err = service.SysCurdDemo().Delete(ctx, &req.CurdDemoDeleteInp)
|
||||
return
|
||||
}
|
||||
|
||||
// Status 更新生成演示状态
|
||||
func (c *cCurdDemo) Status(ctx context.Context, req *curddemo.StatusReq) (res *curddemo.StatusRes, err error) {
|
||||
err = service.SysCurdDemo().Status(ctx, &req.CurdDemoStatusInp)
|
||||
return
|
||||
}
|
||||
|
||||
// Switch 更新生成演示开关状态
|
||||
// Switch 更新CURD列表开关状态
|
||||
func (c *cCurdDemo) Switch(ctx context.Context, req *curddemo.SwitchReq) (res *curddemo.SwitchRes, err error) {
|
||||
err = service.SysCurdDemo().Switch(ctx, &req.CurdDemoSwitchInp)
|
||||
return
|
||||
|
80
server/internal/controller/admin/sys/normal_tree_demo.go
Normal file
80
server/internal/controller/admin/sys/normal_tree_demo.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// Package sys
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2024 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
// @AutoGenerate Version 2.13.1
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hotgo/api/admin/normaltreedemo"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
)
|
||||
|
||||
var (
|
||||
NormalTreeDemo = cNormalTreeDemo{}
|
||||
)
|
||||
|
||||
type cNormalTreeDemo struct{}
|
||||
|
||||
// List 查看普通树表列表
|
||||
func (c *cNormalTreeDemo) List(ctx context.Context, req *normaltreedemo.ListReq) (res *normaltreedemo.ListRes, err error) {
|
||||
list, totalCount, err := service.SysNormalTreeDemo().List(ctx, &req.NormalTreeDemoListInp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if list == nil {
|
||||
list = []*sysin.NormalTreeDemoListModel{}
|
||||
}
|
||||
|
||||
res = new(normaltreedemo.ListRes)
|
||||
res.List = list
|
||||
res.PageRes.Pack(req, totalCount)
|
||||
return
|
||||
}
|
||||
|
||||
// Edit 更新普通树表
|
||||
func (c *cNormalTreeDemo) Edit(ctx context.Context, req *normaltreedemo.EditReq) (res *normaltreedemo.EditRes, err error) {
|
||||
err = service.SysNormalTreeDemo().Edit(ctx, &req.NormalTreeDemoEditInp)
|
||||
return
|
||||
}
|
||||
|
||||
// MaxSort 获取普通树表最大排序
|
||||
func (c *cNormalTreeDemo) MaxSort(ctx context.Context, req *normaltreedemo.MaxSortReq) (res *normaltreedemo.MaxSortRes, err error) {
|
||||
data, err := service.SysNormalTreeDemo().MaxSort(ctx, &req.NormalTreeDemoMaxSortInp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res = new(normaltreedemo.MaxSortRes)
|
||||
res.NormalTreeDemoMaxSortModel = data
|
||||
return
|
||||
}
|
||||
|
||||
// View 获取指定普通树表信息
|
||||
func (c *cNormalTreeDemo) View(ctx context.Context, req *normaltreedemo.ViewReq) (res *normaltreedemo.ViewRes, err error) {
|
||||
data, err := service.SysNormalTreeDemo().View(ctx, &req.NormalTreeDemoViewInp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res = new(normaltreedemo.ViewRes)
|
||||
res.NormalTreeDemoViewModel = data
|
||||
return
|
||||
}
|
||||
|
||||
// Delete 删除普通树表
|
||||
func (c *cNormalTreeDemo) Delete(ctx context.Context, req *normaltreedemo.DeleteReq) (res *normaltreedemo.DeleteRes, err error) {
|
||||
err = service.SysNormalTreeDemo().Delete(ctx, &req.NormalTreeDemoDeleteInp)
|
||||
return
|
||||
}
|
||||
|
||||
// TreeOption 获取普通树表关系树选项
|
||||
func (c *cNormalTreeDemo) TreeOption(ctx context.Context, req *normaltreedemo.TreeOptionReq) (res *normaltreedemo.TreeOptionRes, err error) {
|
||||
data, err := service.SysNormalTreeDemo().TreeOption(ctx)
|
||||
res = (*normaltreedemo.TreeOptionRes)(&data)
|
||||
return
|
||||
}
|
80
server/internal/controller/admin/sys/option_tree_demo.go
Normal file
80
server/internal/controller/admin/sys/option_tree_demo.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// Package sys
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2024 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
// @AutoGenerate Version 2.13.1
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hotgo/api/admin/optiontreedemo"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
)
|
||||
|
||||
var (
|
||||
OptionTreeDemo = cOptionTreeDemo{}
|
||||
)
|
||||
|
||||
type cOptionTreeDemo struct{}
|
||||
|
||||
// List 查看选项树表列表
|
||||
func (c *cOptionTreeDemo) List(ctx context.Context, req *optiontreedemo.ListReq) (res *optiontreedemo.ListRes, err error) {
|
||||
list, totalCount, err := service.SysOptionTreeDemo().List(ctx, &req.OptionTreeDemoListInp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if list == nil {
|
||||
list = []*sysin.OptionTreeDemoListModel{}
|
||||
}
|
||||
|
||||
res = new(optiontreedemo.ListRes)
|
||||
res.List = list
|
||||
res.PageRes.Pack(req, totalCount)
|
||||
return
|
||||
}
|
||||
|
||||
// Edit 更新选项树表
|
||||
func (c *cOptionTreeDemo) Edit(ctx context.Context, req *optiontreedemo.EditReq) (res *optiontreedemo.EditRes, err error) {
|
||||
err = service.SysOptionTreeDemo().Edit(ctx, &req.OptionTreeDemoEditInp)
|
||||
return
|
||||
}
|
||||
|
||||
// MaxSort 获取选项树表最大排序
|
||||
func (c *cOptionTreeDemo) MaxSort(ctx context.Context, req *optiontreedemo.MaxSortReq) (res *optiontreedemo.MaxSortRes, err error) {
|
||||
data, err := service.SysOptionTreeDemo().MaxSort(ctx, &req.OptionTreeDemoMaxSortInp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res = new(optiontreedemo.MaxSortRes)
|
||||
res.OptionTreeDemoMaxSortModel = data
|
||||
return
|
||||
}
|
||||
|
||||
// View 获取指定选项树表信息
|
||||
func (c *cOptionTreeDemo) View(ctx context.Context, req *optiontreedemo.ViewReq) (res *optiontreedemo.ViewRes, err error) {
|
||||
data, err := service.SysOptionTreeDemo().View(ctx, &req.OptionTreeDemoViewInp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res = new(optiontreedemo.ViewRes)
|
||||
res.OptionTreeDemoViewModel = data
|
||||
return
|
||||
}
|
||||
|
||||
// Delete 删除选项树表
|
||||
func (c *cOptionTreeDemo) Delete(ctx context.Context, req *optiontreedemo.DeleteReq) (res *optiontreedemo.DeleteRes, err error) {
|
||||
err = service.SysOptionTreeDemo().Delete(ctx, &req.OptionTreeDemoDeleteInp)
|
||||
return
|
||||
}
|
||||
|
||||
// TreeOption 获取选项树表关系树选项
|
||||
func (c *cOptionTreeDemo) TreeOption(ctx context.Context, req *optiontreedemo.TreeOptionReq) (res *optiontreedemo.TreeOptionRes, err error) {
|
||||
data, err := service.SysOptionTreeDemo().TreeOption(ctx)
|
||||
res = (*optiontreedemo.TreeOptionRes)(&data)
|
||||
return
|
||||
}
|
@@ -23,12 +23,6 @@ func (c *cSmsLog) Delete(ctx context.Context, req *smslog.DeleteReq) (res *smslo
|
||||
return
|
||||
}
|
||||
|
||||
// Edit 更新
|
||||
func (c *cSmsLog) Edit(ctx context.Context, req *smslog.EditReq) (res *smslog.EditRes, err error) {
|
||||
err = service.SysSmsLog().Edit(ctx, &req.SmsLogEditInp)
|
||||
return
|
||||
}
|
||||
|
||||
// View 获取指定信息
|
||||
func (c *cSmsLog) View(ctx context.Context, req *smslog.ViewReq) (res *smslog.ViewRes, err error) {
|
||||
data, err := service.SysSmsLog().View(ctx, &req.SmsLogViewInp)
|
||||
@@ -53,9 +47,3 @@ func (c *cSmsLog) List(ctx context.Context, req *smslog.ListReq) (res *smslog.Li
|
||||
res.PageRes.Pack(req, totalCount)
|
||||
return
|
||||
}
|
||||
|
||||
// Status 更新状态
|
||||
func (c *cSmsLog) Status(ctx context.Context, req *smslog.StatusReq) (res *smslog.StatusRes, err error) {
|
||||
err = service.SysSmsLog().Status(ctx, &req.SmsLogStatusInp)
|
||||
return
|
||||
}
|
||||
|
79
server/internal/controller/admin/sys/test_category.go
Normal file
79
server/internal/controller/admin/sys/test_category.go
Normal file
@@ -0,0 +1,79 @@
|
||||
// Package sys
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2024 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
// @AutoGenerate Version 2.13.1
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hotgo/api/admin/testcategory"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
)
|
||||
|
||||
var (
|
||||
TestCategory = cTestCategory{}
|
||||
)
|
||||
|
||||
type cTestCategory struct{}
|
||||
|
||||
// List 查看测试分类列表
|
||||
func (c *cTestCategory) List(ctx context.Context, req *testcategory.ListReq) (res *testcategory.ListRes, err error) {
|
||||
list, totalCount, err := service.SysTestCategory().List(ctx, &req.TestCategoryListInp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if list == nil {
|
||||
list = []*sysin.TestCategoryListModel{}
|
||||
}
|
||||
|
||||
res = new(testcategory.ListRes)
|
||||
res.List = list
|
||||
res.PageRes.Pack(req, totalCount)
|
||||
return
|
||||
}
|
||||
|
||||
// Edit 更新测试分类
|
||||
func (c *cTestCategory) Edit(ctx context.Context, req *testcategory.EditReq) (res *testcategory.EditRes, err error) {
|
||||
err = service.SysTestCategory().Edit(ctx, &req.TestCategoryEditInp)
|
||||
return
|
||||
}
|
||||
|
||||
// MaxSort 获取测试分类最大排序
|
||||
func (c *cTestCategory) MaxSort(ctx context.Context, req *testcategory.MaxSortReq) (res *testcategory.MaxSortRes, err error) {
|
||||
data, err := service.SysTestCategory().MaxSort(ctx, &req.TestCategoryMaxSortInp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res = new(testcategory.MaxSortRes)
|
||||
res.TestCategoryMaxSortModel = data
|
||||
return
|
||||
}
|
||||
|
||||
// View 获取指定测试分类信息
|
||||
func (c *cTestCategory) View(ctx context.Context, req *testcategory.ViewReq) (res *testcategory.ViewRes, err error) {
|
||||
data, err := service.SysTestCategory().View(ctx, &req.TestCategoryViewInp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res = new(testcategory.ViewRes)
|
||||
res.TestCategoryViewModel = data
|
||||
return
|
||||
}
|
||||
|
||||
// Delete 删除测试分类
|
||||
func (c *cTestCategory) Delete(ctx context.Context, req *testcategory.DeleteReq) (res *testcategory.DeleteRes, err error) {
|
||||
err = service.SysTestCategory().Delete(ctx, &req.TestCategoryDeleteInp)
|
||||
return
|
||||
}
|
||||
|
||||
// Status 更新测试分类状态
|
||||
func (c *cTestCategory) Status(ctx context.Context, req *testcategory.StatusReq) (res *testcategory.StatusRes, err error) {
|
||||
err = service.SysTestCategory().Status(ctx, &req.TestCategoryStatusInp)
|
||||
return
|
||||
}
|
@@ -1,26 +0,0 @@
|
||||
// Package user
|
||||
// @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 user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"hotgo/api/api/user"
|
||||
"hotgo/utility/simple"
|
||||
)
|
||||
|
||||
var (
|
||||
Hello = cHello{}
|
||||
)
|
||||
|
||||
type cHello struct{}
|
||||
|
||||
func (c *cHello) Hello(ctx context.Context, req *user.HelloReq) (res *user.HelloRes, err error) {
|
||||
res = &user.HelloRes{
|
||||
Tips: fmt.Sprintf("hello %v, this is the api for %v applications.", req.Name, simple.AppName(ctx)),
|
||||
}
|
||||
return
|
||||
}
|
@@ -21,6 +21,7 @@ import (
|
||||
"hotgo/internal/websocket"
|
||||
"hotgo/utility/file"
|
||||
"hotgo/utility/format"
|
||||
"hotgo/utility/simple"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
@@ -76,8 +77,7 @@ func (c *cMonitor) RunInfo(client *websocket.Client, req *websocket.WRequest) {
|
||||
"goSize": file.DirSize(pwd),
|
||||
}
|
||||
|
||||
isDemo := g.Cfg().MustGet(client.Context(), "hotgo.isDemo", false).Bool()
|
||||
if isDemo {
|
||||
if simple.IsDemo(client.Context()) {
|
||||
data["rootPath"] = consts.DemoTips
|
||||
data["pwd"] = consts.DemoTips
|
||||
data["intranet_ip"] = consts.DemoTips
|
||||
|
27
server/internal/dao/addon_hgexample_tenant_order.go
Normal file
27
server/internal/dao/addon_hgexample_tenant_order.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"hotgo/internal/dao/internal"
|
||||
)
|
||||
|
||||
// internalAddonHgexampleTenantOrderDao is internal type for wrapping internal DAO implements.
|
||||
type internalAddonHgexampleTenantOrderDao = *internal.AddonHgexampleTenantOrderDao
|
||||
|
||||
// addonHgexampleTenantOrderDao is the data access object for table hg_addon_hgexample_tenant_order.
|
||||
// You can define custom methods on it to extend its functionality as you wish.
|
||||
type addonHgexampleTenantOrderDao struct {
|
||||
internalAddonHgexampleTenantOrderDao
|
||||
}
|
||||
|
||||
var (
|
||||
// AddonHgexampleTenantOrder is globally public accessible object for table hg_addon_hgexample_tenant_order operations.
|
||||
AddonHgexampleTenantOrder = addonHgexampleTenantOrderDao{
|
||||
internal.NewAddonHgexampleTenantOrderDao(),
|
||||
}
|
||||
)
|
||||
|
||||
// Fill with you ideas below.
|
@@ -18,7 +18,7 @@ type adminDeptDao struct {
|
||||
}
|
||||
|
||||
var (
|
||||
// AdminDept is globally common accessible object for table hg_admin_dept operations.
|
||||
// AdminDept is globally public accessible object for table hg_admin_dept operations.
|
||||
AdminDept = adminDeptDao{
|
||||
internal.NewAdminDeptDao(),
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ type adminMemberDao struct {
|
||||
}
|
||||
|
||||
var (
|
||||
// AdminMember is globally common accessible object for table hg_admin_member operations.
|
||||
// AdminMember is globally public accessible object for table hg_admin_member operations.
|
||||
AdminMember = adminMemberDao{
|
||||
internal.NewAdminMemberDao(),
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ type adminMemberPostDao struct {
|
||||
}
|
||||
|
||||
var (
|
||||
// AdminMemberPost is globally common accessible object for table hg_admin_member_post operations.
|
||||
// AdminMemberPost is globally public accessible object for table hg_admin_member_post operations.
|
||||
AdminMemberPost = adminMemberPostDao{
|
||||
internal.NewAdminMemberPostDao(),
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ type adminMemberRoleDao struct {
|
||||
}
|
||||
|
||||
var (
|
||||
// AdminMemberRole is globally common accessible object for table hg_admin_member_role operations.
|
||||
// AdminMemberRole is globally public accessible object for table hg_admin_member_role operations.
|
||||
AdminMemberRole = adminMemberRoleDao{
|
||||
internal.NewAdminMemberRoleDao(),
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ type adminMenuDao struct {
|
||||
}
|
||||
|
||||
var (
|
||||
// AdminMenu is globally common accessible object for table hg_admin_menu operations.
|
||||
// AdminMenu is globally public accessible object for table hg_admin_menu operations.
|
||||
AdminMenu = adminMenuDao{
|
||||
internal.NewAdminMenuDao(),
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ type adminNoticeDao struct {
|
||||
}
|
||||
|
||||
var (
|
||||
// AdminNotice is globally common accessible object for table hg_admin_notice operations.
|
||||
// AdminNotice is globally public accessible object for table hg_admin_notice operations.
|
||||
AdminNotice = adminNoticeDao{
|
||||
internal.NewAdminNoticeDao(),
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ type adminPostDao struct {
|
||||
}
|
||||
|
||||
var (
|
||||
// AdminPost is globally common accessible object for table hg_admin_post operations.
|
||||
// AdminPost is globally public accessible object for table hg_admin_post operations.
|
||||
AdminPost = adminPostDao{
|
||||
internal.NewAdminPostDao(),
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ type adminRoleDao struct {
|
||||
}
|
||||
|
||||
var (
|
||||
// AdminRole is globally common accessible object for table hg_admin_role operations.
|
||||
// AdminRole is globally public accessible object for table hg_admin_role operations.
|
||||
AdminRole = adminRoleDao{
|
||||
internal.NewAdminRoleDao(),
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ type adminRoleMenuDao struct {
|
||||
}
|
||||
|
||||
var (
|
||||
// AdminRoleMenu is globally common accessible object for table hg_admin_role_menu operations.
|
||||
// AdminRoleMenu is globally public accessible object for table hg_admin_role_menu operations.
|
||||
AdminRoleMenu = adminRoleMenuDao{
|
||||
internal.NewAdminRoleMenuDao(),
|
||||
}
|
||||
|
@@ -21,6 +21,9 @@ type AddonHgexampleTableDao struct {
|
||||
// AddonHgexampleTableColumns defines and stores column names for table hg_addon_hgexample_table.
|
||||
type AddonHgexampleTableColumns struct {
|
||||
Id string // ID
|
||||
Pid string // 上级ID
|
||||
Level string // 树等级
|
||||
Tree string // 关系树
|
||||
CategoryId string // 分类ID
|
||||
Flag string // 标签
|
||||
Title string // 标题
|
||||
@@ -47,9 +50,6 @@ type AddonHgexampleTableColumns struct {
|
||||
Hobby string // 爱好
|
||||
Channel string // 渠道
|
||||
CityId string // 所在城市
|
||||
Pid string // 上级ID
|
||||
Level string // 树等级
|
||||
Tree string // 关系树
|
||||
Remark string // 备注
|
||||
Status string // 状态
|
||||
CreatedBy string // 创建者
|
||||
@@ -62,6 +62,9 @@ type AddonHgexampleTableColumns struct {
|
||||
// addonHgexampleTableColumns holds the columns for table hg_addon_hgexample_table.
|
||||
var addonHgexampleTableColumns = AddonHgexampleTableColumns{
|
||||
Id: "id",
|
||||
Pid: "pid",
|
||||
Level: "level",
|
||||
Tree: "tree",
|
||||
CategoryId: "category_id",
|
||||
Flag: "flag",
|
||||
Title: "title",
|
||||
@@ -88,9 +91,6 @@ var addonHgexampleTableColumns = AddonHgexampleTableColumns{
|
||||
Hobby: "hobby",
|
||||
Channel: "channel",
|
||||
CityId: "city_id",
|
||||
Pid: "pid",
|
||||
Level: "level",
|
||||
Tree: "tree",
|
||||
Remark: "remark",
|
||||
Status: "status",
|
||||
CreatedBy: "created_by",
|
||||
|
93
server/internal/dao/internal/addon_hgexample_tenant_order.go
Normal file
93
server/internal/dao/internal/addon_hgexample_tenant_order.go
Normal file
@@ -0,0 +1,93 @@
|
||||
// ==========================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// AddonHgexampleTenantOrderDao is the data access object for table hg_addon_hgexample_tenant_order.
|
||||
type AddonHgexampleTenantOrderDao 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 AddonHgexampleTenantOrderColumns // columns contains all the column names of Table for convenient usage.
|
||||
}
|
||||
|
||||
// AddonHgexampleTenantOrderColumns defines and stores column names for table hg_addon_hgexample_tenant_order.
|
||||
type AddonHgexampleTenantOrderColumns struct {
|
||||
Id string // 主键
|
||||
TenantId string // 租户ID
|
||||
MerchantId string // 商户ID
|
||||
UserId string // 用户ID
|
||||
ProductName string // 购买产品
|
||||
OrderSn string // 订单号
|
||||
Money string // 充值金额
|
||||
Remark string // 备注
|
||||
Status string // 订单状态
|
||||
CreatedAt string // 创建时间
|
||||
UpdatedAt string // 修改时间
|
||||
}
|
||||
|
||||
// addonHgexampleTenantOrderColumns holds the columns for table hg_addon_hgexample_tenant_order.
|
||||
var addonHgexampleTenantOrderColumns = AddonHgexampleTenantOrderColumns{
|
||||
Id: "id",
|
||||
TenantId: "tenant_id",
|
||||
MerchantId: "merchant_id",
|
||||
UserId: "user_id",
|
||||
ProductName: "product_name",
|
||||
OrderSn: "order_sn",
|
||||
Money: "money",
|
||||
Remark: "remark",
|
||||
Status: "status",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
}
|
||||
|
||||
// NewAddonHgexampleTenantOrderDao creates and returns a new DAO object for table data access.
|
||||
func NewAddonHgexampleTenantOrderDao() *AddonHgexampleTenantOrderDao {
|
||||
return &AddonHgexampleTenantOrderDao{
|
||||
group: "default",
|
||||
table: "hg_addon_hgexample_tenant_order",
|
||||
columns: addonHgexampleTenantOrderColumns,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of current DAO.
|
||||
func (dao *AddonHgexampleTenantOrderDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of current dao.
|
||||
func (dao *AddonHgexampleTenantOrderDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of current dao.
|
||||
func (dao *AddonHgexampleTenantOrderDao) Columns() AddonHgexampleTenantOrderColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the configuration group name of database of current dao.
|
||||
func (dao *AddonHgexampleTenantOrderDao) Group() string {
|
||||
return dao.group
|
||||
}
|
||||
|
||||
// Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation.
|
||||
func (dao *AddonHgexampleTenantOrderDao) 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 *AddonHgexampleTenantOrderDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
99
server/internal/dao/internal/sys_gen_tree_demo.go
Normal file
99
server/internal/dao/internal/sys_gen_tree_demo.go
Normal file
@@ -0,0 +1,99 @@
|
||||
// ==========================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// SysGenTreeDemoDao is the data access object for table hg_sys_gen_tree_demo.
|
||||
type SysGenTreeDemoDao 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 SysGenTreeDemoColumns // columns contains all the column names of Table for convenient usage.
|
||||
}
|
||||
|
||||
// SysGenTreeDemoColumns defines and stores column names for table hg_sys_gen_tree_demo.
|
||||
type SysGenTreeDemoColumns struct {
|
||||
Id string // ID
|
||||
Pid string // 上级ID
|
||||
Level string // 关系树级别
|
||||
Tree string // 关系树
|
||||
CategoryId string // 分类ID
|
||||
Title string // 标题
|
||||
Description string // 描述
|
||||
Sort string // 排序
|
||||
Status string // 状态
|
||||
CreatedBy string // 创建者
|
||||
UpdatedBy string // 更新者
|
||||
CreatedAt string // 创建时间
|
||||
UpdatedAt string // 修改时间
|
||||
DeletedAt string // 删除时间
|
||||
}
|
||||
|
||||
// sysGenTreeDemoColumns holds the columns for table hg_sys_gen_tree_demo.
|
||||
var sysGenTreeDemoColumns = SysGenTreeDemoColumns{
|
||||
Id: "id",
|
||||
Pid: "pid",
|
||||
Level: "level",
|
||||
Tree: "tree",
|
||||
CategoryId: "category_id",
|
||||
Title: "title",
|
||||
Description: "description",
|
||||
Sort: "sort",
|
||||
Status: "status",
|
||||
CreatedBy: "created_by",
|
||||
UpdatedBy: "updated_by",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
DeletedAt: "deleted_at",
|
||||
}
|
||||
|
||||
// NewSysGenTreeDemoDao creates and returns a new DAO object for table data access.
|
||||
func NewSysGenTreeDemoDao() *SysGenTreeDemoDao {
|
||||
return &SysGenTreeDemoDao{
|
||||
group: "default",
|
||||
table: "hg_sys_gen_tree_demo",
|
||||
columns: sysGenTreeDemoColumns,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of current DAO.
|
||||
func (dao *SysGenTreeDemoDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of current dao.
|
||||
func (dao *SysGenTreeDemoDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of current dao.
|
||||
func (dao *SysGenTreeDemoDao) Columns() SysGenTreeDemoColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the configuration group name of database of current dao.
|
||||
func (dao *SysGenTreeDemoDao) Group() string {
|
||||
return dao.group
|
||||
}
|
||||
|
||||
// Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation.
|
||||
func (dao *SysGenTreeDemoDao) 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 *SysGenTreeDemoDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
@@ -20,32 +20,38 @@ type SysLoginLogDao struct {
|
||||
|
||||
// SysLoginLogColumns defines and stores column names for table hg_sys_login_log.
|
||||
type SysLoginLogColumns struct {
|
||||
Id string // 日志ID
|
||||
ReqId string // 请求ID
|
||||
MemberId string // 用户ID
|
||||
Username string // 用户名
|
||||
Response string // 响应数据
|
||||
LoginAt string // 登录时间
|
||||
LoginIp string // 登录IP
|
||||
ErrMsg string // 错误提示
|
||||
Status string // 状态
|
||||
CreatedAt string // 创建时间
|
||||
UpdatedAt string // 修改时间
|
||||
Id string // 日志ID
|
||||
ReqId string // 请求ID
|
||||
MemberId string // 用户ID
|
||||
Username string // 用户名
|
||||
Response string // 响应数据
|
||||
LoginAt string // 登录时间
|
||||
LoginIp string // 登录IP
|
||||
ProvinceId string // 省编码
|
||||
CityId string // 市编码
|
||||
UserAgent string // UA信息
|
||||
ErrMsg string // 错误提示
|
||||
Status string // 状态
|
||||
CreatedAt string // 创建时间
|
||||
UpdatedAt string // 修改时间
|
||||
}
|
||||
|
||||
// sysLoginLogColumns holds the columns for table hg_sys_login_log.
|
||||
var sysLoginLogColumns = SysLoginLogColumns{
|
||||
Id: "id",
|
||||
ReqId: "req_id",
|
||||
MemberId: "member_id",
|
||||
Username: "username",
|
||||
Response: "response",
|
||||
LoginAt: "login_at",
|
||||
LoginIp: "login_ip",
|
||||
ErrMsg: "err_msg",
|
||||
Status: "status",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
Id: "id",
|
||||
ReqId: "req_id",
|
||||
MemberId: "member_id",
|
||||
Username: "username",
|
||||
Response: "response",
|
||||
LoginAt: "login_at",
|
||||
LoginIp: "login_ip",
|
||||
ProvinceId: "province_id",
|
||||
CityId: "city_id",
|
||||
UserAgent: "user_agent",
|
||||
ErrMsg: "err_msg",
|
||||
Status: "status",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
}
|
||||
|
||||
// NewSysLoginLogDao creates and returns a new DAO object for table data access.
|
||||
|
@@ -22,6 +22,7 @@ type TestCategoryDao struct {
|
||||
type TestCategoryColumns struct {
|
||||
Id string // 分类ID
|
||||
Name string // 分类名称
|
||||
ShortName string // 简称
|
||||
Description string // 描述
|
||||
Sort string // 排序
|
||||
Remark string // 备注
|
||||
@@ -35,6 +36,7 @@ type TestCategoryColumns struct {
|
||||
var testCategoryColumns = TestCategoryColumns{
|
||||
Id: "id",
|
||||
Name: "name",
|
||||
ShortName: "short_name",
|
||||
Description: "description",
|
||||
Sort: "sort",
|
||||
Remark: "remark",
|
||||
|
@@ -18,7 +18,7 @@ type sysConfigDao struct {
|
||||
}
|
||||
|
||||
var (
|
||||
// SysConfig is globally common accessible object for table hg_sys_config operations.
|
||||
// SysConfig is globally public accessible object for table hg_sys_config operations.
|
||||
SysConfig = sysConfigDao{
|
||||
internal.NewSysConfigDao(),
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ type sysDictDataDao struct {
|
||||
}
|
||||
|
||||
var (
|
||||
// SysDictData is globally common accessible object for table hg_sys_dict_data operations.
|
||||
// SysDictData is globally public accessible object for table hg_sys_dict_data operations.
|
||||
SysDictData = sysDictDataDao{
|
||||
internal.NewSysDictDataDao(),
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ type sysDictTypeDao struct {
|
||||
}
|
||||
|
||||
var (
|
||||
// SysDictType is globally common accessible object for table hg_sys_dict_type operations.
|
||||
// SysDictType is globally public accessible object for table hg_sys_dict_type operations.
|
||||
SysDictType = sysDictTypeDao{
|
||||
internal.NewSysDictTypeDao(),
|
||||
}
|
||||
|
27
server/internal/dao/sys_gen_tree_demo.go
Normal file
27
server/internal/dao/sys_gen_tree_demo.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"hotgo/internal/dao/internal"
|
||||
)
|
||||
|
||||
// internalSysGenTreeDemoDao is internal type for wrapping internal DAO implements.
|
||||
type internalSysGenTreeDemoDao = *internal.SysGenTreeDemoDao
|
||||
|
||||
// sysGenTreeDemoDao is the data access object for table hg_sys_gen_tree_demo.
|
||||
// You can define custom methods on it to extend its functionality as you wish.
|
||||
type sysGenTreeDemoDao struct {
|
||||
internalSysGenTreeDemoDao
|
||||
}
|
||||
|
||||
var (
|
||||
// SysGenTreeDemo is globally public accessible object for table hg_sys_gen_tree_demo operations.
|
||||
SysGenTreeDemo = sysGenTreeDemoDao{
|
||||
internal.NewSysGenTreeDemoDao(),
|
||||
}
|
||||
)
|
||||
|
||||
// Fill with you ideas below.
|
@@ -18,7 +18,7 @@ type sysLogDao struct {
|
||||
}
|
||||
|
||||
var (
|
||||
// SysLog is globally common accessible object for table hg_sys_log operations.
|
||||
// SysLog is globally public accessible object for table hg_sys_log operations.
|
||||
SysLog = sysLogDao{
|
||||
internal.NewSysLogDao(),
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ type sysProvincesDao struct {
|
||||
}
|
||||
|
||||
var (
|
||||
// SysProvinces is globally common accessible object for table hg_sys_provinces operations.
|
||||
// SysProvinces is globally public accessible object for table hg_sys_provinces operations.
|
||||
SysProvinces = sysProvincesDao{
|
||||
internal.NewSysProvincesDao(),
|
||||
}
|
||||
|
@@ -13,12 +13,12 @@ import (
|
||||
"hotgo/internal/library/hgrds/lock"
|
||||
"hotgo/internal/library/hgrds/pubsub"
|
||||
"hotgo/internal/service"
|
||||
"hotgo/utility/simple"
|
||||
)
|
||||
|
||||
// SubscribeClusterSync 订阅集群同步,可以用来集中同步数据、状态等
|
||||
func SubscribeClusterSync(ctx context.Context) {
|
||||
isCluster := g.Cfg().MustGet(ctx, "hotgo.isCluster").Bool()
|
||||
if !isCluster {
|
||||
if !simple.IsCluster(ctx) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -35,8 +35,7 @@ func SubscribeClusterSync(ctx context.Context) {
|
||||
|
||||
// PublishClusterSync 推送集群同步消息,如果没有开启集群部署,则不进行推送
|
||||
func PublishClusterSync(ctx context.Context, channel string, message interface{}) {
|
||||
isCluster := g.Cfg().MustGet(ctx, "hotgo.isCluster").Bool()
|
||||
if !isCluster {
|
||||
if !simple.IsCluster(ctx) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@@ -154,7 +154,7 @@ func InitTrace(ctx context.Context) {
|
||||
|
||||
// SetGFMode 设置gf运行模式
|
||||
func SetGFMode(ctx context.Context) {
|
||||
mode := g.Cfg().MustGet(ctx, "hotgo.mode").String()
|
||||
mode := g.Cfg().MustGet(ctx, "system.mode").String()
|
||||
if len(mode) == 0 {
|
||||
mode = gmode.NOT_SET
|
||||
}
|
||||
|
@@ -18,9 +18,9 @@ func GetResourcePath(ctx context.Context) string {
|
||||
if len(cacheResourcePath) > 0 {
|
||||
return cacheResourcePath
|
||||
}
|
||||
basePath := g.Cfg().MustGet(ctx, "hotgo.addonsResourcePath").String()
|
||||
basePath := g.Cfg().MustGet(ctx, "system.addonsResourcePath").String()
|
||||
if basePath == "" {
|
||||
g.Log().Warning(ctx, "addons GetResourcePath not config found:'hotgo.addonsResourcePath', use default values:'resource'")
|
||||
g.Log().Warning(ctx, "addons GetResourcePath not config found:'system.addonsResourcePath', use default values:'resource'")
|
||||
basePath = "resource"
|
||||
}
|
||||
|
||||
|
@@ -45,7 +45,7 @@ func Build(ctx context.Context, option *BuildOption) (err error) {
|
||||
)
|
||||
|
||||
if resourcePath == "" {
|
||||
err = gerror.New("请先设置一个有效的插件资源路径,配置名称:'hotgo.addonsResourcePath'")
|
||||
err = gerror.New("请先设置一个有效的插件资源路径,配置名称:'system.addonsResourcePath'")
|
||||
return
|
||||
}
|
||||
|
||||
|
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gres"
|
||||
"github.com/gogf/gf/v2/os/gview"
|
||||
"hotgo/internal/model/input/form"
|
||||
"sort"
|
||||
"sync"
|
||||
@@ -27,16 +26,15 @@ type Option struct {
|
||||
|
||||
// Skeleton 模块骨架
|
||||
type Skeleton struct {
|
||||
Label string `json:"label"` // 标识
|
||||
Name string `json:"name"` // 名称
|
||||
Group int `json:"group"` // 分组
|
||||
Logo string `json:"logo"` // logo
|
||||
Brief string `json:"brief"` // 简介
|
||||
Description string `json:"description"` // 详细描述
|
||||
Author string `json:"author"` // 作者
|
||||
Version string `json:"version"` // 版本号
|
||||
RootPath string `json:"rootPath"` // 根路径
|
||||
View *gview.View `json:"view"` // 模板引擎
|
||||
Label string `json:"label"` // 标识
|
||||
Name string `json:"name"` // 名称
|
||||
Group int `json:"group"` // 分组
|
||||
Logo string `json:"logo"` // logo
|
||||
Brief string `json:"brief"` // 简介
|
||||
Description string `json:"description"` // 详细描述
|
||||
Author string `json:"author"` // 作者
|
||||
Version string `json:"version"` // 版本号
|
||||
RootPath string `json:"rootPath"` // 根路径
|
||||
}
|
||||
|
||||
func (s *Skeleton) GetModule() Module {
|
||||
@@ -99,7 +97,6 @@ func RegisterModule(m Module) Module {
|
||||
}
|
||||
|
||||
sk.RootPath = GetModulePath(name)
|
||||
sk.View = NewView(m.Ctx(), name)
|
||||
modules[name] = m
|
||||
return m
|
||||
}
|
||||
@@ -139,40 +136,6 @@ func GetModuleRealPath(name string) string {
|
||||
return path
|
||||
}
|
||||
|
||||
// NewView 初始化一个插件的模板引擎
|
||||
func NewView(ctx context.Context, name string) *gview.View {
|
||||
basePath := GetResourcePath(ctx)
|
||||
if basePath == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
view := gview.New()
|
||||
path := ViewPath(name, basePath)
|
||||
|
||||
if !gfile.IsDir(gfile.RealPath(path)) {
|
||||
g.Log().Warningf(ctx, "NewView template path does not exist:%v,default use of main module template.", path)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := view.SetPath(path); err != nil {
|
||||
g.Log().Warningf(ctx, "NewView SetPath err:%+v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 默认和主模块使用一致的变量分隔符号
|
||||
delimiters := g.Cfg().MustGet(ctx, "viewer.delimiters", []string{"@{", "}"}).Strings()
|
||||
if len(delimiters) != 2 {
|
||||
g.Log().Warning(ctx, "NewView delimiters config error")
|
||||
return nil
|
||||
}
|
||||
view.SetDelimiters(delimiters[0], delimiters[1])
|
||||
|
||||
// 更多配置
|
||||
// view.SetI18n()
|
||||
// ...
|
||||
return view
|
||||
}
|
||||
|
||||
// AddStaticPath 设置插件静态目录映射
|
||||
func AddStaticPath(ctx context.Context, server *ghttp.Server) {
|
||||
basePath := GetResourcePath(ctx)
|
||||
|
@@ -48,7 +48,7 @@ func Generate(ctx context.Context) (id string, base64 string) {
|
||||
}
|
||||
|
||||
c := base64Captcha.NewCaptcha(driver.ConvertFonts(), base64Captcha.DefaultMemStore)
|
||||
id, base64, err := c.Generate()
|
||||
id, base64, _, err := c.Generate()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "captcha.Generate err:%+v", err)
|
||||
}
|
||||
|
@@ -8,8 +8,11 @@ package casbin
|
||||
import (
|
||||
"context"
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/casbin/casbin/v2/model"
|
||||
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gres"
|
||||
"hotgo/internal/consts"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -37,12 +40,29 @@ func InitEnforcer(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
Enforcer, err = casbin.NewEnforcer("./manifest/config/casbin.conf", a)
|
||||
if err != nil {
|
||||
g.Log().Panicf(ctx, "casbin.NewEnforcer err . %v", err)
|
||||
return
|
||||
path := "manifest/config/casbin.conf"
|
||||
|
||||
// 优先从本地加载casbin.conf,如果不存在就从资源文件中找
|
||||
modelContent := gfile.GetContents(path)
|
||||
if len(modelContent) == 0 {
|
||||
if !gres.IsEmpty() && gres.Contains(path) {
|
||||
modelContent = string(gres.GetContent(path))
|
||||
}
|
||||
}
|
||||
|
||||
if len(modelContent) == 0 {
|
||||
g.Log().Panicf(ctx, "casbin model file does not exist:%v", path)
|
||||
}
|
||||
|
||||
m, err := model.NewModelFromString(modelContent)
|
||||
if err != nil {
|
||||
g.Log().Panicf(ctx, "casbin NewModelFromString err:%v", err)
|
||||
}
|
||||
|
||||
Enforcer, err = casbin.NewEnforcer(m, a)
|
||||
if err != nil {
|
||||
g.Log().Panicf(ctx, "casbin NewEnforcer err:%v", err)
|
||||
}
|
||||
loadPermissions(ctx)
|
||||
}
|
||||
|
||||
|
@@ -96,6 +96,35 @@ func GetRoleKey(ctx context.Context) string {
|
||||
return user.RoleKey
|
||||
}
|
||||
|
||||
// GetDeptType 获取用户部门类型
|
||||
func GetDeptType(ctx context.Context) string {
|
||||
user := GetUser(ctx)
|
||||
if user == nil {
|
||||
return ""
|
||||
}
|
||||
return user.DeptType
|
||||
}
|
||||
|
||||
// IsCompanyDept 是否为公司部门
|
||||
func IsCompanyDept(ctx context.Context) bool {
|
||||
return GetDeptType(ctx) == consts.DeptTypeCompany
|
||||
}
|
||||
|
||||
// IsTenantDept 是否为租户部门
|
||||
func IsTenantDept(ctx context.Context) bool {
|
||||
return GetDeptType(ctx) == consts.DeptTypeTenant
|
||||
}
|
||||
|
||||
// IsMerchantDept 是否为商户部门
|
||||
func IsMerchantDept(ctx context.Context) bool {
|
||||
return GetDeptType(ctx) == consts.DeptTypeMerchant
|
||||
}
|
||||
|
||||
// IsUserDept 是否为普通用户部门
|
||||
func IsUserDept(ctx context.Context) bool {
|
||||
return GetDeptType(ctx) == consts.DeptTypeUser
|
||||
}
|
||||
|
||||
// GetModule 获取应用模块
|
||||
func GetModule(ctx context.Context) string {
|
||||
c := Get(ctx)
|
||||
|
@@ -9,6 +9,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"hash/fnv"
|
||||
"hotgo/internal/model"
|
||||
"strconv"
|
||||
@@ -40,6 +41,7 @@ func GetOptionsById(ctx context.Context, id int64) (opts []*model.Option, err er
|
||||
}
|
||||
|
||||
for _, v := range GetAllFunc() {
|
||||
g.Log().Warningf(ctx, "GetAllFunc GetOptionsById v:%v, %v", v.Id, v.Key)
|
||||
if v.Id == id {
|
||||
return LoadFuncOptions(ctx, v)
|
||||
}
|
||||
@@ -49,6 +51,24 @@ func GetOptionsById(ctx context.Context, id int64) (opts []*model.Option, err er
|
||||
return
|
||||
}
|
||||
|
||||
// GetTypeById 通过类型ID获取内置选项类型
|
||||
func GetTypeById(ctx context.Context, id int64) (typ string, err error) {
|
||||
for _, v := range GetAllEnums() {
|
||||
if v.Id == id {
|
||||
return v.Key, nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range GetAllFunc() {
|
||||
if v.Id == id {
|
||||
return v.Key, nil
|
||||
}
|
||||
}
|
||||
|
||||
err = NotExistKeyError
|
||||
return
|
||||
}
|
||||
|
||||
// GenIdHash 生成字典id
|
||||
func GenIdHash(str string, t int64) int64 {
|
||||
prefix := 10000 * t
|
||||
|
@@ -56,9 +56,7 @@ func RegisterEnums(key, label string, opts []*model.Option) {
|
||||
func SaveEnums(key, label string, opts []*model.Option) {
|
||||
eLock.Lock()
|
||||
defer eLock.Unlock()
|
||||
if _, ok := enumsOptions[key]; ok {
|
||||
delete(enumsOptions, key)
|
||||
}
|
||||
delete(enumsOptions, key)
|
||||
RegisterEnums(key, label, opts)
|
||||
}
|
||||
|
||||
|
@@ -67,9 +67,7 @@ func RegisterFunc(key, label string, fun FuncDict, cache ...bool) {
|
||||
func SaveFunc(key, label string, fun FuncDict, cache ...bool) {
|
||||
fLock.Lock()
|
||||
defer fLock.Unlock()
|
||||
if _, ok := funcOptions[key]; ok {
|
||||
delete(funcOptions, key)
|
||||
}
|
||||
delete(funcOptions, key)
|
||||
RegisterFunc(key, label, fun, cache...)
|
||||
}
|
||||
|
||||
|
@@ -6,7 +6,9 @@
|
||||
package hggen
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
_ "hotgo/internal/library/hggen/internal/cmd/gendao"
|
||||
"hotgo/internal/library/hggen/internal/utility/utils"
|
||||
_ "unsafe"
|
||||
|
||||
"context"
|
||||
@@ -31,13 +33,37 @@ func doGenDaoForArray(ctx context.Context, index int, in gendao.CGenDaoInput)
|
||||
|
||||
// Dao 生成数据库实体
|
||||
func Dao(ctx context.Context) (err error) {
|
||||
|
||||
// 在执行gf gen dao时,先将生成文件放在临时路径,生成完成后再拷贝到项目
|
||||
// 目的是希望减少触发gf热编译的几率,防止热编译运行时代码生成流程未结束被自动重启打断
|
||||
// gf gen dao 的执行时长主要取决于需要生成数据库表的数量,表越多速度越慢
|
||||
tempPathPrefix := views.GetTempGeneratePath(ctx) + "/dao"
|
||||
|
||||
for _, v := range daoConfig {
|
||||
inp := defaultGenDaoInput
|
||||
err = gconv.Scan(v, &inp)
|
||||
if err != nil {
|
||||
if err = gconv.Scan(v, &inp); err != nil {
|
||||
return
|
||||
}
|
||||
oldPath := inp.Path
|
||||
inp.ImportPrefix = utils.GetImportPath(inp.Path)
|
||||
inp.Path = tempPathPrefix + "/" + inp.Path
|
||||
|
||||
if err = gfile.Remove(inp.Path); err != nil {
|
||||
err = gerror.Newf("清理临时生成目录失败:%v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err = gfile.Mkdir(inp.Path); err != nil {
|
||||
err = gerror.Newf("创建临时生成目录失败:%v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
doGenDaoForArray(ctx, -1, inp)
|
||||
|
||||
if err = gfile.CopyDir(inp.Path, gfile.Pwd()+"/"+oldPath); err != nil {
|
||||
err = gerror.Newf("拷贝生成文件失败:%v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -129,8 +155,16 @@ func TableSelects(ctx context.Context, in *sysin.GenCodesSelectsInp) (res *sysin
|
||||
Label: v,
|
||||
})
|
||||
}
|
||||
for _, v := range views.TableAligns {
|
||||
res.TableAlign = append(res.TableAlign, &form.Select{
|
||||
Value: v,
|
||||
Name: views.TableAlignMap[v],
|
||||
Label: views.TableAlignMap[v],
|
||||
})
|
||||
}
|
||||
|
||||
res.Addons = addons.ModuleSelect()
|
||||
res.TreeStyleType = consts.GenCodesTreeStyleTypeOptions
|
||||
return
|
||||
}
|
||||
|
||||
@@ -196,15 +230,12 @@ func Preview(ctx context.Context, in *sysin.GenCodesPreviewInp) (res *sysin.GenC
|
||||
}
|
||||
|
||||
switch in.GenType {
|
||||
case consts.GenCodesTypeCurd:
|
||||
case consts.GenCodesTypeCurd, consts.GenCodesTypeTree:
|
||||
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
|
||||
@@ -222,7 +253,7 @@ func Build(ctx context.Context, in *sysin.GenCodesBuildInp) (err error) {
|
||||
}
|
||||
|
||||
switch in.GenType {
|
||||
case consts.GenCodesTypeCurd:
|
||||
case consts.GenCodesTypeCurd, consts.GenCodesTypeTree:
|
||||
pin := &sysin.GenCodesPreviewInp{SysGenCodes: in.SysGenCodes}
|
||||
return views.Curd.DoBuild(ctx, &views.CurdBuildInput{
|
||||
PreviewIn: &views.CurdPreviewInput{
|
||||
@@ -233,25 +264,17 @@ func Build(ctx context.Context, in *sysin.GenCodesBuildInp) (err error) {
|
||||
BeforeEvent: views.CurdBuildEvent{"runDao": Dao},
|
||||
AfterEvent: views.CurdBuildEvent{"runService": func(ctx context.Context) (err error) {
|
||||
cfg := GetServiceConfig()
|
||||
if err = ServiceWithCfg(ctx, cfg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 插件模块,同时运行模块下的gen service
|
||||
// 插件模块,切换到插件下运行gen service
|
||||
if genConfig.Application.Crud.Templates[pin.GenTemplate].IsAddon {
|
||||
// 依然使用配置中的参数,只是将生成路径指向插件模块路径
|
||||
cfg.SrcFolder = "addons/" + pin.AddonName + "/logic"
|
||||
cfg.DstFolder = "addons/" + pin.AddonName + "/service"
|
||||
if err = ServiceWithCfg(ctx, cfg); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = ServiceWithCfg(ctx, cfg)
|
||||
return
|
||||
}},
|
||||
})
|
||||
case consts.GenCodesTypeTree:
|
||||
err = gerror.Newf("生成类型开发中!")
|
||||
return
|
||||
case consts.GenCodesTypeQueue:
|
||||
err = gerror.Newf("生成类型开发中!")
|
||||
return
|
||||
|
@@ -25,6 +25,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// Init .
|
||||
Init = cInit{}
|
||||
)
|
||||
|
||||
@@ -64,14 +65,13 @@ type cInitInput struct {
|
||||
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"`
|
||||
Module string `name:"module" short:"g" brief:"custom go module"`
|
||||
}
|
||||
|
||||
type cInitOutput struct{}
|
||||
|
||||
func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err error) {
|
||||
var (
|
||||
overwrote = false
|
||||
)
|
||||
var overwrote = false
|
||||
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") {
|
||||
@@ -105,7 +105,7 @@ func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err
|
||||
err = gfile.ReadLines(gitignoreFile, func(line string) error {
|
||||
// Add only hidden files or directories
|
||||
// If other directories are added, it may cause the entire directory to be ignored
|
||||
// such as 'main' in the .gitignore file, but the path is 'D:\main\my-project'
|
||||
// such as 'main' in the .gitignore file, but the path is ' D:\main\my-project '
|
||||
if line != "" && strings.HasPrefix(line, ".") {
|
||||
ignoreFiles = append(ignoreFiles, line)
|
||||
}
|
||||
@@ -118,6 +118,11 @@ func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err
|
||||
}
|
||||
}
|
||||
|
||||
// Replace module name.
|
||||
if in.Module == "" {
|
||||
in.Module = gfile.Basename(gfile.RealPath(in.Name))
|
||||
}
|
||||
|
||||
// Replace template name to project name.
|
||||
err = gfile.ReplaceDirFunc(func(path, content string) string {
|
||||
for _, ignoreFile := range ignoreFiles {
|
||||
@@ -125,7 +130,7 @@ func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err
|
||||
return content
|
||||
}
|
||||
}
|
||||
return gstr.Replace(gfile.GetContents(path), cInitRepoPrefix+templateRepoName, gfile.Basename(gfile.RealPath(in.Name)))
|
||||
return gstr.Replace(gfile.GetContents(path), cInitRepoPrefix+templateRepoName, in.Module)
|
||||
}, in.Name, "*", true)
|
||||
if err != nil {
|
||||
return
|
||||
|
@@ -135,9 +135,14 @@ func generateStructFieldDefinition(
|
||||
" #" + gstr.CaseCamel(newFiledName),
|
||||
" #" + localTypeNameStr,
|
||||
}
|
||||
attrLines = append(attrLines, " #"+fmt.Sprintf(tagKey+`json:"%s"`, jsonTag))
|
||||
attrLines = append(attrLines, " #"+fmt.Sprintf(`description:"%s"`+tagKey, descriptionTag))
|
||||
attrLines = append(attrLines, " #"+fmt.Sprintf(`// %s`, formatComment(field.Comment)))
|
||||
attrLines = append(attrLines, fmt.Sprintf(` #%sjson:"%s"`, tagKey, jsonTag))
|
||||
// orm tag
|
||||
if !in.IsDo {
|
||||
// entity
|
||||
attrLines = append(attrLines, fmt.Sprintf(` #orm:"%s"`, field.Name))
|
||||
}
|
||||
attrLines = append(attrLines, fmt.Sprintf(` #description:"%s"%s`, descriptionTag, tagKey))
|
||||
attrLines = append(attrLines, fmt.Sprintf(` #// %s`, formatComment(field.Comment)))
|
||||
|
||||
for k, v := range attrLines {
|
||||
if in.NoJsonTag {
|
||||
|
@@ -181,12 +181,12 @@ func setDefaultFormMode(field *sysin.GenCodesColumnListModel) {
|
||||
return
|
||||
}
|
||||
|
||||
if field.GoType == GoTypeString && field.Length >= 200 && field.Length <= 500 {
|
||||
if field.GoType == GoTypeString && field.Length >= 256 && field.Length <= 512 {
|
||||
field.FormMode = FormModeInputTextarea
|
||||
return
|
||||
}
|
||||
|
||||
if field.GoType == GoTypeString && field.Length > 500 {
|
||||
if field.GoType == GoTypeString && field.Length > 512 {
|
||||
field.FormMode = FormModeInputEditor
|
||||
return
|
||||
}
|
||||
|
@@ -3,12 +3,12 @@
|
||||
// @Copyright Copyright (c) 2023 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"
|
||||
"hotgo/utility/validate"
|
||||
)
|
||||
|
||||
// 字段映射关系
|
||||
@@ -109,6 +109,8 @@ const (
|
||||
FormModeCheckbox = "Checkbox" // 复选按钮
|
||||
FormModeSelect = "Select" // 单选下拉框
|
||||
FormModeSelectMultiple = "SelectMultiple" // 多选下拉框
|
||||
FormModeTreeSelect = "TreeSelect" // 树型选择
|
||||
FormModeCascader = "Cascader" // 级联选择
|
||||
FormModeUploadImage = "UploadImage" // 单图上传
|
||||
FormModeUploadImages = "UploadImages" // 多图上传
|
||||
FormModeUploadFile = "UploadFile" // 单文件上传
|
||||
@@ -116,12 +118,13 @@ const (
|
||||
FormModeSwitch = "Switch" // 开关
|
||||
FormModeRate = "Rate" // 评分
|
||||
FormModeCitySelector = "CitySelector" // 省市区选择
|
||||
FormModePidTreeSelect = "PidTreeSelect" // 树型上级选择,树表生成专用
|
||||
)
|
||||
|
||||
var FormModes = []string{
|
||||
FormModeInput, FormModeInputNumber, FormModeInputTextarea, FormModeInputEditor, FormModeInputDynamic,
|
||||
FormModeDate, FormModeDateRange, FormModeTime, FormModeTimeRange,
|
||||
FormModeRadio, FormModeCheckbox, FormModeSelect, FormModeSelectMultiple,
|
||||
FormModeRadio, FormModeCheckbox, FormModeSelect, FormModeSelectMultiple, FormModeTreeSelect, FormModeCascader,
|
||||
FormModeUploadImage, FormModeUploadImages, FormModeUploadFile, FormModeUploadFiles,
|
||||
FormModeSwitch,
|
||||
FormModeRate,
|
||||
@@ -142,6 +145,8 @@ var FormModeMap = map[string]string{
|
||||
FormModeCheckbox: "复选按钮",
|
||||
FormModeSelect: "单选下拉框",
|
||||
FormModeSelectMultiple: "多选下拉框",
|
||||
FormModeTreeSelect: "树型选择",
|
||||
FormModeCascader: "级联选择",
|
||||
FormModeUploadImage: "单图上传",
|
||||
FormModeUploadImages: "多图上传",
|
||||
FormModeUploadFile: "单文件上传",
|
||||
@@ -190,20 +195,20 @@ var FormRoleMap = map[string]string{
|
||||
|
||||
// 查询条件
|
||||
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值
|
||||
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(doc, val)" // JSON_CONTAINS(json_doc, val[, path]) // 判断是否包含某个json值
|
||||
)
|
||||
|
||||
var WhereModes = []string{WhereModeEq,
|
||||
@@ -214,6 +219,21 @@ var WhereModes = []string{WhereModeEq,
|
||||
WhereModeJsonContains,
|
||||
}
|
||||
|
||||
// 表格列的排序方式
|
||||
const (
|
||||
TableAlignLeft = "left"
|
||||
TableAlignRight = "right"
|
||||
TableAlignCenter = "center"
|
||||
)
|
||||
|
||||
var TableAligns = []string{TableAlignLeft, TableAlignRight, TableAlignCenter}
|
||||
|
||||
var TableAlignMap = map[string]string{
|
||||
TableAlignLeft: "居左",
|
||||
TableAlignRight: "居右",
|
||||
TableAlignCenter: "居中",
|
||||
}
|
||||
|
||||
// IsNumberType 是否是数字类型
|
||||
func IsNumberType(goType string) bool {
|
||||
switch goType {
|
||||
@@ -225,8 +245,17 @@ func IsNumberType(goType string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func HasColumn(masterFields []*sysin.GenCodesColumnListModel, column string) bool {
|
||||
for _, field := range masterFields {
|
||||
// IsSelectFormMode 是否是选择器组件
|
||||
func IsSelectFormMode(formMode string) bool {
|
||||
switch formMode {
|
||||
case FormModeRadio, FormModeCheckbox, FormModeSelect, FormModeSelectMultiple, FormModeCitySelector, FormModeTreeSelect, FormModeCascader:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func HasColumn(fields []*sysin.GenCodesColumnListModel, column string) bool {
|
||||
for _, field := range fields {
|
||||
if field.GoName == column {
|
||||
return true
|
||||
}
|
||||
@@ -234,29 +263,77 @@ func HasColumn(masterFields []*sysin.GenCodesColumnListModel, column string) boo
|
||||
return false
|
||||
}
|
||||
|
||||
func HasColumnWithFormMode(masterFields []*sysin.GenCodesColumnListModel, column string) bool {
|
||||
for _, field := range masterFields {
|
||||
if field.FormMode == column {
|
||||
func HasColumnWithFormMode(fields []*sysin.GenCodesColumnListModel, formMode string) bool {
|
||||
for _, field := range fields {
|
||||
if field.FormMode == formMode {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func HasMaxSort(masterFields []*sysin.GenCodesColumnListModel) bool {
|
||||
return HasColumn(masterFields, "Sort")
|
||||
func HasMaxSort(fields []*sysin.GenCodesColumnListModel) bool {
|
||||
return HasColumn(fields, "Sort")
|
||||
}
|
||||
|
||||
func HasStatus(headOps []string, masterFields []*sysin.GenCodesColumnListModel) bool {
|
||||
func HasStatus(headOps []string, fields []*sysin.GenCodesColumnListModel) bool {
|
||||
if !gstr.InArray(headOps, "status") {
|
||||
return false
|
||||
}
|
||||
return HasColumn(masterFields, "Status")
|
||||
return HasColumn(fields, "Status")
|
||||
}
|
||||
|
||||
func HasSwitch(headOps []string, masterFields []*sysin.GenCodesColumnListModel) bool {
|
||||
if !gstr.InArray(headOps, "switch") {
|
||||
return false
|
||||
}
|
||||
return HasColumnWithFormMode(masterFields, "Switch")
|
||||
func HasSwitch(fields []*sysin.GenCodesColumnListModel) bool {
|
||||
return HasColumnWithFormMode(fields, FormModeSwitch)
|
||||
}
|
||||
|
||||
func HasHookMemberSummary(fields []*sysin.GenCodesColumnListModel) bool {
|
||||
for _, field := range fields {
|
||||
if IsMemberSummaryField(field.Name) {
|
||||
if field.IsList {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func HasQueryMemberSummary(fields []*sysin.GenCodesColumnListModel) bool {
|
||||
for _, field := range fields {
|
||||
if IsMemberSummaryField(field.Name) {
|
||||
if field.IsQuery {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsMemberSummaryField(name string) bool {
|
||||
switch name {
|
||||
case "created_by", "updated_by", "deleted_by":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ReviseFields 校正字段值,兼容版本升级前的老数据格式
|
||||
func ReviseFields(fields []*sysin.GenCodesColumnListModel) []*sysin.GenCodesColumnListModel {
|
||||
for _, field := range fields {
|
||||
if !validate.InSlice(TableAligns, field.Align) {
|
||||
field.Align = TableAlignLeft
|
||||
}
|
||||
|
||||
if field.Width < 1 {
|
||||
field.Width = -1
|
||||
}
|
||||
if field.Width > 2000 {
|
||||
field.Width = 2000
|
||||
}
|
||||
|
||||
if field.FormGridSpan < 1 {
|
||||
field.FormGridSpan = 1
|
||||
}
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ package views
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
@@ -17,12 +18,13 @@ import (
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/library/hggen/internal/cmd/gendao"
|
||||
"hotgo/internal/library/hggen/internal/utility/utils"
|
||||
"hotgo/internal/library/hgorm"
|
||||
"hotgo/internal/model"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
"hotgo/utility/convert"
|
||||
"hotgo/utility/file"
|
||||
"hotgo/utility/tree"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
@@ -32,18 +34,41 @@ 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"`
|
||||
HasNotFilterAuth bool `json:"hasNotFilterAuth"`
|
||||
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"`
|
||||
HasMaxSort bool // 最大排序
|
||||
HasAdd bool // 表单添加
|
||||
HasBatchDel bool // 批量删除
|
||||
HasExport bool // 表格导出
|
||||
HasNotFilterAuth bool // 不过滤认证权限
|
||||
HasEdit bool // 表单编辑
|
||||
HasDel bool // 删除
|
||||
HasView bool // 查看详情
|
||||
HasStatus bool // 修改状态
|
||||
HasSwitch bool // 数值开关
|
||||
HasCheck bool // 勾选列
|
||||
HasMenu bool // 菜单权限
|
||||
IsTreeTable bool // 树型列表
|
||||
IsOptionTreeTable bool // 选项式树型列表
|
||||
HasRules bool // 表单验证规则
|
||||
HasRulesValidator bool // 表单验证器
|
||||
HasSearchForm bool // 列表搜索
|
||||
HasDict bool // 字典
|
||||
HasFuncDict bool // 注册方法字典
|
||||
HasQueryMemberSummary bool // 查询用户摘要
|
||||
HasHookMemberSummary bool // hook用户摘要
|
||||
ImportModel ImportModel // 公用导包 - model.ts
|
||||
ActionColumnWidth int64 // 列表操作栏宽度
|
||||
IsAddon bool // 是否是插件
|
||||
}
|
||||
|
||||
// ImportModel 导包 - model.ts
|
||||
type ImportModel struct {
|
||||
NaiveUI []string
|
||||
UtilsIs []string
|
||||
UtilsUrl []string
|
||||
UtilsDate []string
|
||||
UtilsValidate []string
|
||||
UtilsHotGo []string
|
||||
UtilsIndex []string
|
||||
}
|
||||
|
||||
type CurdOptionsJoin struct {
|
||||
@@ -63,18 +88,52 @@ type CurdOptionsMenu struct {
|
||||
Sort int `json:"sort"`
|
||||
}
|
||||
|
||||
type OptionsTree struct {
|
||||
TitleColumn string `json:"titleColumn"`
|
||||
StyleType int `json:"styleType"`
|
||||
TitleField *sysin.GenCodesColumnListModel
|
||||
}
|
||||
|
||||
// PresetStep 预设生成流程参数
|
||||
type PresetStep struct {
|
||||
FormGridCols int `json:"formGridCols" dc:"表单显示的栅格数量"`
|
||||
}
|
||||
|
||||
type CurdOptions struct {
|
||||
AutoOps []string `json:"autoOps"`
|
||||
ColumnOps []string `json:"columnOps"`
|
||||
HeadOps []string `json:"headOps"`
|
||||
Join []*CurdOptionsJoin `json:"join"`
|
||||
Menu *CurdOptionsMenu `json:"menu"`
|
||||
Tree *OptionsTree `json:"tree"`
|
||||
TemplateGroup string `json:"templateGroup"`
|
||||
ApiPrefix string `json:"apiPrefix"`
|
||||
ImportWebApi string `json:"importWebApi"`
|
||||
FuncDict *FuncDict `json:"funcDict"`
|
||||
PresetStep *PresetStep `json:"presetStep"`
|
||||
Step *CurdStep // 转换后的流程控制条件
|
||||
DictOps CurdOptionsDict // 字典选项
|
||||
dictMap g.Map // 字典选项 -> 字段映射关系
|
||||
}
|
||||
|
||||
type FuncDict struct {
|
||||
ValueColumn string // 选项值
|
||||
LabelColumn string //选项名称
|
||||
Value *sysin.GenCodesColumnListModel
|
||||
Label *sysin.GenCodesColumnListModel
|
||||
}
|
||||
|
||||
type CurdOptionsDict struct {
|
||||
Has bool
|
||||
Types []string
|
||||
Schemas []*OptionsSchemasField
|
||||
}
|
||||
|
||||
type OptionsSchemasField struct {
|
||||
Field string
|
||||
Type string
|
||||
}
|
||||
|
||||
type CurdPreviewInput struct {
|
||||
In *sysin.GenCodesPreviewInp // 提交参数
|
||||
DaoConfig gendao.CGenDaoInput // 生成dao配置
|
||||
@@ -98,31 +157,44 @@ func (l *gCurd) initInput(ctx context.Context, in *CurdPreviewInput) (err error)
|
||||
in.content = new(sysin.GenCodesPreviewModel)
|
||||
in.content.Views = make(map[string]*sysin.GenFile)
|
||||
|
||||
// 加载主表配置
|
||||
if err = in.In.MasterColumns.Scan(&in.masterFields); err != nil {
|
||||
return
|
||||
// 初始化生成选项
|
||||
if err = initOptions(in); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(in.masterFields) == 0 {
|
||||
if in.masterFields, err = DoTableColumns(ctx, &sysin.GenCodesColumnListInp{Name: in.In.DbName, Table: in.In.TableName}, in.DaoConfig); err != nil {
|
||||
return
|
||||
}
|
||||
// 初始化表字段配置
|
||||
if err = initTableField(ctx, in); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 主键属性
|
||||
in.pk = l.getPkField(in)
|
||||
if in.pk == nil {
|
||||
return gerror.New("initInput no primary key is set in the table!")
|
||||
}
|
||||
|
||||
// 加载选项
|
||||
if err = in.In.Options.Scan(&in.options); err != nil {
|
||||
return
|
||||
// 初始化树表
|
||||
if err = initTableTree(in); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
initStep(in)
|
||||
in.options.dictMap = make(g.Map)
|
||||
|
||||
// 初始化方法字典
|
||||
if err = initFuncDict(in); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 初始化生成模板
|
||||
if err = initTemplate(in); err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func initOptions(in *CurdPreviewInput) (err error) {
|
||||
if err = in.In.Options.Scan(&in.options); err != nil {
|
||||
return
|
||||
}
|
||||
in.options.dictMap = make(g.Map)
|
||||
return
|
||||
}
|
||||
|
||||
func initTemplate(in *CurdPreviewInput) (err error) {
|
||||
if len(in.Config.Application.Crud.Templates)-1 < in.In.GenTemplate {
|
||||
return gerror.New("没有找到生成模板的配置,请检查!")
|
||||
}
|
||||
@@ -141,20 +213,126 @@ func (l *gCurd) initInput(ctx context.Context, in *CurdPreviewInput) (err error)
|
||||
return
|
||||
}
|
||||
|
||||
func initFuncDict(in *CurdPreviewInput) (err error) {
|
||||
if !in.options.Step.HasFuncDict || in.options.FuncDict == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(in.options.FuncDict.LabelColumn) == 0 || len(in.options.FuncDict.ValueColumn) == 0 {
|
||||
err = gerror.New("生成字典选项必须设置选项值和选项名称")
|
||||
return err
|
||||
}
|
||||
|
||||
for _, field := range in.masterFields {
|
||||
if field.Name == in.options.FuncDict.ValueColumn {
|
||||
in.options.FuncDict.Value = field
|
||||
}
|
||||
|
||||
if field.Name == in.options.FuncDict.LabelColumn {
|
||||
in.options.FuncDict.Label = field
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func initTableField(ctx context.Context, in *CurdPreviewInput) (err error) {
|
||||
// 加载主表配置
|
||||
if err = in.In.MasterColumns.Scan(&in.masterFields); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(in.masterFields) == 0 {
|
||||
if in.masterFields, err = DoTableColumns(ctx, &sysin.GenCodesColumnListInp{Name: in.In.DbName, Table: in.In.TableName}, in.DaoConfig); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 主键属性
|
||||
in.pk = getPkField(in)
|
||||
if in.pk == nil {
|
||||
return gerror.New("initInput no primary key is set in the table!")
|
||||
}
|
||||
|
||||
in.masterFields = ReviseFields(in.masterFields)
|
||||
|
||||
// 检查表命名
|
||||
var names = []string{in.In.DaoName}
|
||||
for _, v := range in.options.Join {
|
||||
v.Columns = ReviseFields(v.Columns)
|
||||
names = append(names, v.DaoName)
|
||||
}
|
||||
if err = CheckIllegalName("数据库表名", names...); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = CheckIllegalName("实体命名", in.In.VarName); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func initTableTree(in *CurdPreviewInput) (err error) {
|
||||
// 检查树表字段
|
||||
if in.In.GenType == consts.GenCodesTypeTree {
|
||||
if err = CheckTreeTableFields(in.masterFields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 解析选项树名称字段
|
||||
has := false
|
||||
for _, field := range in.masterFields {
|
||||
if in.options.Tree.TitleColumn == field.Name {
|
||||
in.options.Tree.TitleField = field
|
||||
has = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !has {
|
||||
err = gerror.New("请选择一个有效的树名称字段")
|
||||
return
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func initStep(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.HasBatchDel = gstr.InArray(in.options.HeadOps, "batchDel") && gstr.InArray(in.options.ColumnOps, "check")
|
||||
in.options.Step.HasExport = gstr.InArray(in.options.HeadOps, "export")
|
||||
in.options.Step.HasNotFilterAuth = gstr.InArray(in.options.ColumnOps, "notFilterAuth")
|
||||
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.HasSwitch = HasSwitch(in.masterFields)
|
||||
in.options.Step.HasCheck = gstr.InArray(in.options.ColumnOps, "check")
|
||||
in.options.Step.HasMenu = gstr.InArray(in.options.AutoOps, "genMenuPermissions")
|
||||
in.options.Step.HasQueryMemberSummary = HasQueryMemberSummary(in.masterFields)
|
||||
in.options.Step.HasHookMemberSummary = HasHookMemberSummary(in.masterFields)
|
||||
in.options.Step.IsTreeTable = in.In.GenType == consts.GenCodesTypeTree
|
||||
if in.options.Step.IsTreeTable {
|
||||
in.options.Step.IsOptionTreeTable = in.options.Tree.StyleType == consts.GenCodesTreeStyleTypeOption
|
||||
}
|
||||
in.options.Step.HasFuncDict = gstr.InArray(in.options.AutoOps, "genFuncDict")
|
||||
in.options.Step.IsAddon = in.Config.Application.Crud.Templates[in.In.GenTemplate].IsAddon
|
||||
if in.options.PresetStep.FormGridCols < 1 {
|
||||
in.options.PresetStep.FormGridCols = 1
|
||||
}
|
||||
}
|
||||
|
||||
// getPkField 获取主键
|
||||
func getPkField(in *CurdPreviewInput) *sysin.GenCodesColumnListModel {
|
||||
if len(in.masterFields) == 0 {
|
||||
panic("getPkField masterFields uninitialized.")
|
||||
}
|
||||
for _, field := range in.masterFields {
|
||||
if IsIndexPK(field.Index) {
|
||||
return field
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *gCurd) loadView(ctx context.Context, in *CurdPreviewInput) (err error) {
|
||||
@@ -170,14 +348,14 @@ func (l *gCurd) loadView(ctx context.Context, in *CurdPreviewInput) (err error)
|
||||
|
||||
now := gtime.Now()
|
||||
view.BindFuncMap(g.Map{
|
||||
"NowYear": now.Year, // 当前年
|
||||
"ToLower": strings.ToLower, // 全部小写
|
||||
"LcFirst": gstr.LcFirst, // 首字母小写
|
||||
"UcFirst": gstr.UcFirst, // 首字母大写
|
||||
"NowYear": now.Year, // 当前年
|
||||
"ToLower": strings.ToLower, // 全部小写
|
||||
"LcFirst": gstr.LcFirst, // 首字母小写
|
||||
"UcFirst": gstr.UcFirst, // 首字母大写
|
||||
"ToTSArray": ToTSArray, // 转为ts数组格式
|
||||
})
|
||||
|
||||
dictOptions, err := l.generateWebModelDictOptions(ctx, in)
|
||||
if err != nil {
|
||||
if err = l.generateWebModelDictOptions(ctx, in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -193,9 +371,9 @@ func (l *gCurd) loadView(ctx context.Context, in *CurdPreviewInput) (err error)
|
||||
importService = "hotgo/addons/" + in.In.AddonName + "/service"
|
||||
}
|
||||
|
||||
importWebApi := "@/api/" + gstr.LcFirst(in.In.VarName)
|
||||
in.options.ImportWebApi = "@/api/" + gstr.LcFirst(in.In.VarName)
|
||||
if temp.IsAddon {
|
||||
importWebApi = "@/api/addons/" + in.In.AddonName + "/" + gstr.LcFirst(in.In.VarName)
|
||||
in.options.ImportWebApi = "@/api/addons/" + in.In.AddonName + "/" + gstr.LcFirst(in.In.VarName)
|
||||
}
|
||||
|
||||
componentPrefix := gstr.LcFirst(in.In.VarName)
|
||||
@@ -216,12 +394,12 @@ func (l *gCurd) loadView(ctx context.Context, in *CurdPreviewInput) (err error)
|
||||
"masterFields": in.masterFields, // 主表字段
|
||||
"pk": in.pk, // 主键属性
|
||||
"options": in.options, // 提交选项
|
||||
"dictOptions": dictOptions, // web字典选项
|
||||
"dictOptions": in.options.DictOps, // web字典选项
|
||||
"importApi": importApi, // 导入goApi包
|
||||
"importInput": importInput, // 导入input包
|
||||
"importController": importController, // 导入控制器包
|
||||
"importService": importService, // 导入业务服务
|
||||
"importWebApi": importWebApi, // 导入webApi
|
||||
"importWebApi": in.options.ImportWebApi, // 导入webApi
|
||||
"apiPrefix": in.options.ApiPrefix, // api前缀
|
||||
"componentPrefix": componentPrefix, // vue子组件前缀
|
||||
})
|
||||
@@ -231,6 +409,7 @@ func (l *gCurd) loadView(ctx context.Context, in *CurdPreviewInput) (err error)
|
||||
}
|
||||
|
||||
func (l *gCurd) DoBuild(ctx context.Context, in *CurdBuildInput) (err error) {
|
||||
st := gtime.Now()
|
||||
preview, err := l.DoPreview(ctx, in.PreviewIn)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -296,10 +475,6 @@ func (l *gCurd) DoBuild(ctx context.Context, in *CurdBuildInput) (err error) {
|
||||
if err = gfile.PutContents(vi.Path, strings.TrimSpace(vi.Content)); err != nil {
|
||||
return gerror.Newf("writing content to '%s' failed: %v", vi.Path, err)
|
||||
}
|
||||
|
||||
if gstr.Str(vi.Path, `.`) == ".go" {
|
||||
utils.GoFmt(vi.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// 后置操作
|
||||
@@ -312,6 +487,7 @@ func (l *gCurd) DoBuild(ctx context.Context, in *CurdBuildInput) (err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
g.Log().Debugf(ctx, "generate code operation completed, %vms", gtime.Now().Sub(st).Milliseconds())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -386,6 +562,11 @@ func (l *gCurd) generateApiContent(ctx context.Context, in *CurdPreviewInput) (e
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Content, err = FormatGo(ctx, name, genFile.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -417,6 +598,12 @@ func (l *gCurd) generateInputContent(ctx context.Context, in *CurdPreviewInput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Content, err = FormatGo(ctx, name, genFile.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -443,6 +630,12 @@ func (l *gCurd) generateControllerContent(ctx context.Context, in *CurdPreviewIn
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Content, err = FormatGo(ctx, name, genFile.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -473,6 +666,12 @@ func (l *gCurd) generateLogicContent(ctx context.Context, in *CurdPreviewInput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Content, err = FormatGo(ctx, name, genFile.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -499,6 +698,11 @@ func (l *gCurd) generateRouterContent(ctx context.Context, in *CurdPreviewInput)
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Content, err = FormatGo(ctx, name, genFile.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -525,6 +729,8 @@ func (l *gCurd) generateWebApiContent(ctx context.Context, in *CurdPreviewInput)
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Content = FormatTs(genFile.Content)
|
||||
|
||||
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) {
|
||||
@@ -556,6 +762,8 @@ func (l *gCurd) generateWebModelContent(ctx context.Context, in *CurdPreviewInpu
|
||||
return
|
||||
}
|
||||
|
||||
genFile.Content = FormatTs(genFile.Content)
|
||||
|
||||
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) {
|
||||
@@ -586,6 +794,8 @@ func (l *gCurd) generateWebIndexContent(ctx context.Context, in *CurdPreviewInpu
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Content = FormatVue(genFile.Content)
|
||||
|
||||
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) {
|
||||
@@ -616,6 +826,8 @@ func (l *gCurd) generateWebEditContent(ctx context.Context, in *CurdPreviewInput
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Content = FormatVue(genFile.Content)
|
||||
|
||||
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) {
|
||||
@@ -651,6 +863,8 @@ func (l *gCurd) generateWebViewContent(ctx context.Context, in *CurdPreviewInput
|
||||
return err
|
||||
}
|
||||
|
||||
genFile.Content = FormatVue(genFile.Content)
|
||||
|
||||
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) {
|
||||
@@ -683,6 +897,11 @@ func (l *gCurd) generateSqlContent(ctx context.Context, in *CurdPreviewInput) (e
|
||||
genFile = new(sysin.GenFile)
|
||||
)
|
||||
|
||||
menus, err := service.AdminMenu().GetFastList(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tplData["dirPid"], tplData["dirLevel"], tplData["dirTree"], err = hgorm.AutoUpdateTree(ctx, &dao.AdminMenu, 0, int64(in.options.Menu.Pid))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -692,9 +911,30 @@ func (l *gCurd) generateSqlContent(ctx context.Context, in *CurdPreviewInput) (e
|
||||
tplData["btnLevel"] = tplData["dirLevel"].(int) + 2
|
||||
tplData["sortLevel"] = tplData["dirLevel"].(int) + 3
|
||||
|
||||
pageRedirect := ""
|
||||
if in.options.Menu.Pid > 0 {
|
||||
tplData["mainComponent"] = "ParentLayout"
|
||||
menu, ok := menus[int64(in.options.Menu.Pid)]
|
||||
if !ok {
|
||||
err = gerror.New("选择的上级菜单不存在")
|
||||
return
|
||||
}
|
||||
for _, id := range tree.GetIds(menu.Tree) {
|
||||
if v, ok2 := menus[id]; ok2 {
|
||||
if !gstr.HasSuffix(pageRedirect, "/") && !gstr.HasPrefix(v.Path, "/") {
|
||||
pageRedirect += "/"
|
||||
}
|
||||
pageRedirect += v.Path
|
||||
}
|
||||
}
|
||||
|
||||
if !gstr.HasSuffix(pageRedirect, "/") && !gstr.HasPrefix(menu.Path, "/") {
|
||||
pageRedirect += "/"
|
||||
}
|
||||
pageRedirect += menu.Path
|
||||
}
|
||||
pageRedirect += "/" + gstr.LcFirst(in.In.VarName) + "/index"
|
||||
tplData["pageRedirect"] = pageRedirect
|
||||
|
||||
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].SqlPath, convert.CamelCaseToUnderline(in.In.VarName)+"_menu.sql")
|
||||
genFile.Meth = consts.GenCodesBuildMethCreate
|
||||
@@ -708,6 +948,48 @@ func (l *gCurd) generateSqlContent(ctx context.Context, in *CurdPreviewInput) (e
|
||||
genFile.Required = false
|
||||
}
|
||||
|
||||
// 需要生成时,检查菜单命名是否存在
|
||||
if genFile.Meth == consts.GenCodesBuildMethCreate {
|
||||
menuNamePrefix := gstr.LcFirst(in.In.VarName)
|
||||
menuNames := []string{menuNamePrefix, menuNamePrefix + "Index"}
|
||||
if in.options.Step.HasEdit {
|
||||
menuNames = append(menuNames, menuNamePrefix+"Edit")
|
||||
menuNames = append(menuNames, menuNamePrefix+"View")
|
||||
}
|
||||
if in.options.Step.HasView {
|
||||
menuNames = append(menuNames, menuNamePrefix+"View")
|
||||
}
|
||||
if in.options.Step.HasMaxSort {
|
||||
menuNames = append(menuNames, menuNamePrefix+"MaxSort")
|
||||
}
|
||||
if in.options.Step.HasDel {
|
||||
menuNames = append(menuNames, menuNamePrefix+"Delete")
|
||||
}
|
||||
if in.options.Step.HasStatus {
|
||||
menuNames = append(menuNames, menuNamePrefix+"Status")
|
||||
}
|
||||
if in.options.Step.HasSwitch {
|
||||
menuNames = append(menuNames, menuNamePrefix+"Switch")
|
||||
}
|
||||
if in.options.Step.HasExport {
|
||||
menuNames = append(menuNames, menuNamePrefix+"Export")
|
||||
}
|
||||
if in.options.Step.IsTreeTable {
|
||||
menuNames = append(menuNames, menuNamePrefix+"TreeOption")
|
||||
}
|
||||
|
||||
menuNames = convert.UniqueSlice(menuNames)
|
||||
hasMenus, err := service.AdminMenu().Model(ctx).Fields("name").WhereIn("name", menuNames).Array()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(hasMenus) > 0 {
|
||||
err = gerror.Newf("要生成的菜单中有已存在的路由别名,请检查并删除:%v", strings.Join(gvar.New(hasMenus).Strings(), `、`))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
tplData["generatePath"] = genFile.Path
|
||||
genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
|
||||
if err != nil {
|
||||
|
@@ -28,6 +28,7 @@ const (
|
||||
InputTypeEditInpValidator = 4 // 添加&编辑验证器
|
||||
InputTypeUpdateFields = 5 // 编辑修改过滤字段
|
||||
InputTypeInsertFields = 6 // 编辑新增过滤字段
|
||||
InputTypeTreeOptionFields = 7 // 关系树查询字段
|
||||
EditInpValidatorGenerally = "if err := g.Validator().Rules(\"%s\").Data(in.%s).Messages(\"%s\").Run(ctx); err != nil {\n\t\treturn err.Current()\n\t}\n"
|
||||
)
|
||||
|
||||
@@ -39,21 +40,79 @@ func (l *gCurd) inputTplData(ctx context.Context, in *CurdPreviewInput) (data g.
|
||||
data["editInpValidator"] = l.generateInputListColumns(ctx, in, InputTypeEditInpValidator)
|
||||
data["updateFieldsColumns"] = l.generateInputListColumns(ctx, in, InputTypeUpdateFields)
|
||||
data["insertFieldsColumns"] = l.generateInputListColumns(ctx, in, InputTypeInsertFields)
|
||||
data["viewModelColumns"] = l.generateInputViewColumns(ctx, in)
|
||||
if in.options.Step.IsTreeTable {
|
||||
data["treeOptionFields"] = l.generateInputListColumns(ctx, in, InputTypeTreeOptionFields)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) generateInputViewColumns(ctx context.Context, in *CurdPreviewInput) string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
|
||||
index := 0
|
||||
array := make([][]string, 1000)
|
||||
// 主表
|
||||
for _, field := range in.masterFields {
|
||||
// 查询用户摘要
|
||||
if field.IsList && in.options.Step.HasHookMemberSummary && IsMemberSummaryField(field.Name) {
|
||||
tagKey := "`"
|
||||
descriptionTag := gstr.Replace(formatComment(field.Dc)+"摘要信息", `"`, `\"`)
|
||||
result := []string{" #" + field.GoName + "Summa"}
|
||||
result = append(result, " #*hook.MemberSumma")
|
||||
result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, field.TsName+"Summa"))
|
||||
result = append(result, " #"+fmt.Sprintf(`dc:"%s"`+tagKey, descriptionTag))
|
||||
array[index] = result
|
||||
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 "\tentity." + in.In.DaoName + "\n" + buffer.String()
|
||||
}
|
||||
|
||||
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)
|
||||
row := l.generateStructFieldDefinition(in, field, inputType, true)
|
||||
if row == nil {
|
||||
continue
|
||||
}
|
||||
array[index] = row
|
||||
index++
|
||||
|
||||
switch inputType {
|
||||
case InputTypeListModel:
|
||||
// 查询用户摘要
|
||||
if field.IsList && in.options.Step.HasHookMemberSummary && IsMemberSummaryField(field.Name) {
|
||||
tagKey := "`"
|
||||
descriptionTag := gstr.Replace(formatComment(field.Dc)+"摘要信息", `"`, `\"`)
|
||||
result := []string{" #" + field.GoName + "Summa"}
|
||||
result = append(result, " #*hook.MemberSumma")
|
||||
result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, field.TsName+"Summa"))
|
||||
result = append(result, " #"+fmt.Sprintf(`dc:"%s"`+tagKey, descriptionTag))
|
||||
array[index] = result
|
||||
index++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 关联表
|
||||
@@ -63,7 +122,7 @@ func (l *gCurd) generateInputListColumns(ctx context.Context, in *CurdPreviewInp
|
||||
continue
|
||||
}
|
||||
for _, field := range v.Columns {
|
||||
row := l.generateStructFieldDefinition(field, inputType)
|
||||
row := l.generateStructFieldDefinition(in, field, inputType, false)
|
||||
if row != nil {
|
||||
array[index] = row
|
||||
index++
|
||||
@@ -92,43 +151,62 @@ func (l *gCurd) generateInputListColumns(ctx context.Context, in *CurdPreviewInp
|
||||
}
|
||||
|
||||
// generateStructFieldForModel generates and returns the attribute definition for specified field.
|
||||
func (l *gCurd) generateStructFieldDefinition(field *sysin.GenCodesColumnListModel, inputType int) []string {
|
||||
func (l *gCurd) generateStructFieldDefinition(in *CurdPreviewInput, field *sysin.GenCodesColumnListModel, inputType int, isMaster bool) []string {
|
||||
var (
|
||||
tagKey = "`"
|
||||
result = []string{" #" + field.GoName}
|
||||
descriptionTag = gstr.Replace(formatComment(field.Dc), `"`, `\"`)
|
||||
)
|
||||
|
||||
addResult := func() []string {
|
||||
result = append(result, " #"+field.GoType)
|
||||
result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, field.TsName))
|
||||
result = append(result, " #"+fmt.Sprintf(`dc:"%s"`+tagKey, descriptionTag))
|
||||
return result
|
||||
}
|
||||
|
||||
isQuery := false
|
||||
|
||||
switch inputType {
|
||||
case InputTypeListInp:
|
||||
if !field.IsQuery {
|
||||
if in.options.Step.IsTreeTable && IsPidName(field.Name) {
|
||||
isQuery = true
|
||||
field.QueryWhere = WhereModeEq
|
||||
}
|
||||
if !field.IsQuery && !isQuery {
|
||||
return nil
|
||||
}
|
||||
|
||||
if field.QueryWhere == WhereModeBetween {
|
||||
result = append(result, " #[]"+field.GoType)
|
||||
} else {
|
||||
result = append(result, " #"+field.GoType)
|
||||
// 查询用户摘要时,固定接收字符串类型
|
||||
if field.IsQuery && in.options.Step.HasQueryMemberSummary && IsMemberSummaryField(field.Name) {
|
||||
result = append(result, " #string")
|
||||
} 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 {
|
||||
// 主表的主键
|
||||
if IsIndexPK(field.Index) && isMaster {
|
||||
addResult()
|
||||
// 树表的pid字段
|
||||
} else if in.options.Step.IsTreeTable && IsPidName(field.Name) {
|
||||
addResult()
|
||||
} else if field.IsList {
|
||||
addResult()
|
||||
} else {
|
||||
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))
|
||||
addResult()
|
||||
case InputTypeEditInpValidator:
|
||||
if !field.IsEdit {
|
||||
return nil
|
||||
@@ -150,18 +228,23 @@ func (l *gCurd) generateStructFieldDefinition(field *sysin.GenCodesColumnListMod
|
||||
if !field.IsEdit && field.GoName != "UpdatedBy" {
|
||||
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))
|
||||
addResult()
|
||||
case InputTypeInsertFields:
|
||||
if !field.IsEdit && field.GoName != "CreatedBy" {
|
||||
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))
|
||||
addResult()
|
||||
case InputTypeTreeOptionFields:
|
||||
if IsIndexPK(field.Index) {
|
||||
return addResult()
|
||||
}
|
||||
if IsPidName(field.Name) {
|
||||
return addResult()
|
||||
}
|
||||
if in.options.Tree.TitleColumn == field.Name {
|
||||
return addResult()
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
panic("inputType is invalid")
|
||||
}
|
||||
|
@@ -16,22 +16,20 @@ 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\n\t}"
|
||||
LogicListJoinSelect = "\t// 关联表select\n\tfields, err := hgorm.GenJoinSelect(ctx, %sin.%sListModel{}, &dao.%s, []*hgorm.Join{\n%v\t})\n\n\tif err != nil {\n\t\terr = gerror.Wrap(err, \"获取%s关联字段失败,请稍后重试!\")\n\t\treturn\n\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 = "\tif _, err = s.Model(ctx%s).\n\t\t\tFields(%sin.%sUpdateFields{}).\n\t\t\tWherePri(in.%s).Data(in).Update(); err != nil {\n\t\t\terr = gerror.Wrap(err, \"修改%s失败,请稍后重试!\")\n\t\t}\n\t\treturn"
|
||||
LogicEditInsert = "\tif _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).\n\t\tFields(%sin.%sInsertFields{}).\n\t\tData(in).Insert(); err != nil {\n\t\terr = gerror.Wrap(err, \"新增%s失败,请稍后重试!\")\n\t}"
|
||||
LogicEditUnique = "\t// 验证'%s'唯一\n\tif err = hgorm.IsUnique(ctx, &dao.%s, g.Map{dao.%s.Columns().%s: in.%s}, \"%s已存在\", in.Id); err != nil {\n\t\treturn\n\t}\n"
|
||||
LogicSwitchUpdate = "g.Map{\n\t\tin.Key: in.Value,\n%s}"
|
||||
LogicStatusUpdate = "g.Map{\n\t\tdao.%s.Columns().Status: in.Status,\n%s}"
|
||||
LogicWhereComments = "\n\t// 查询%s\n"
|
||||
LogicWhereNoSupport = "\t// TODO 暂不支持生成[ %s ]查询方式,请自行补充此处代码!"
|
||||
LogicEditUpdate = "\tif _, err = s.Model(ctx%s).\n\t\t\tFields(%sin.%sUpdateFields{}).\n\t\t\tWherePri(in.%s).Data(in).Update(); err != nil {\n\t\t\terr = gerror.Wrap(err, \"修改%s失败,请稍后重试!\")\n\t\t}\n\t\treturn"
|
||||
LogicEditInsert = "\tif _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).\n\t\tFields(%sin.%sInsertFields{}).\n\t\tData(in).Insert(); err != nil {\n\t\terr = gerror.Wrap(err, \"新增%s失败,请稍后重试!\")\n\t}"
|
||||
LogicEditUnique = "\t// 验证'%s'唯一\n\tif err = hgorm.IsUnique(ctx, &dao.%s, g.Map{dao.%s.Columns().%s: in.%s}, \"%s已存在\", in.Id); err != nil {\n\t\treturn\n\t}\n"
|
||||
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) {
|
||||
data = make(g.Map)
|
||||
data["listWhere"] = l.generateLogicListWhere(ctx, in)
|
||||
data["listJoin"] = l.generateLogicListJoin(ctx, in)
|
||||
data["listFields"] = l.generateLogicListFields(ctx, in)
|
||||
data["listOrder"] = l.generateLogicListOrder(ctx, in)
|
||||
data["edit"] = l.generateLogicEdit(ctx, in)
|
||||
data["switchFields"] = l.generateLogicSwitchFields(ctx, in)
|
||||
@@ -113,76 +111,109 @@ func (l *gCurd) generateLogicEdit(ctx context.Context, in *CurdPreviewInput) g.M
|
||||
}
|
||||
|
||||
func (l *gCurd) generateLogicListOrder(ctx context.Context, in *CurdPreviewInput) string {
|
||||
statement := ""
|
||||
if hasEffectiveJoins(in.options.Join) {
|
||||
statement = "dao." + in.In.DaoName + ".Table() + \".\" +"
|
||||
}
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if in.options.Step.HasMaxSort {
|
||||
buffer.WriteString("OrderAsc(dao." + in.In.DaoName + ".Columns().Sort).")
|
||||
buffer.WriteString("OrderAsc(" + statement + "dao." + in.In.DaoName + ".Columns().Sort).")
|
||||
}
|
||||
buffer.WriteString("OrderDesc(dao." + in.In.DaoName + ".Columns()." + in.pk.GoName + ")")
|
||||
buffer.WriteString("OrderDesc(" + statement + "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"] = ""
|
||||
func (l *gCurd) generateLogicListJoin(ctx context.Context, in *CurdPreviewInput) (link string) {
|
||||
connector := `"="`
|
||||
if hasEffectiveJoins(in.options.Join) {
|
||||
var (
|
||||
selectBuffer = bytes.NewBuffer(nil)
|
||||
linkBuffer = bytes.NewBuffer(nil)
|
||||
joinSelectRows string
|
||||
)
|
||||
|
||||
linkBuffer := bytes.NewBuffer(nil)
|
||||
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)))
|
||||
linkBuffer.WriteString("\tmod = mod." + consts.GenCodesJoinLinkMap[join.LinkMode] + "OnFields(dao." + join.DaoName + ".Table(), dao." + in.In.DaoName + ".Columns()." + gstr.CaseCamel(join.MasterField) + "," + connector + ", dao." + join.DaoName + ".Columns()." + gstr.CaseCamel(join.Field) + ")\n")
|
||||
}
|
||||
}
|
||||
|
||||
selectBuffer.WriteString(fmt.Sprintf(LogicListJoinSelect, in.options.TemplateGroup, in.In.VarName, in.In.DaoName, joinSelectRows, in.In.TableComment))
|
||||
|
||||
data["select"] = selectBuffer.String()
|
||||
data["fields"] = "fields"
|
||||
data["link"] = linkBuffer.String()
|
||||
} else {
|
||||
data["fields"] = fmt.Sprintf("%sin.%sListModel{}", in.options.TemplateGroup, in.In.VarName)
|
||||
link = linkBuffer.String()
|
||||
}
|
||||
return data
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) generateLogicListFields(ctx context.Context, in *CurdPreviewInput) (fields string) {
|
||||
selectBuffer := bytes.NewBuffer(nil)
|
||||
if hasEffectiveJoins(in.options.Join) {
|
||||
selectBuffer.WriteString("mod = mod.FieldsPrefix(dao." + in.In.DaoName + ".Table(), " + in.options.TemplateGroup + "in." + in.In.VarName + "ListModel{})\n")
|
||||
for _, join := range in.options.Join {
|
||||
if isEffectiveJoin(join) {
|
||||
selectBuffer.WriteString("mod = mod.Fields(hgorm.JoinFields(ctx, " + in.options.TemplateGroup + "in." + in.In.VarName + "ListModel{}, &dao." + join.DaoName + ", \"" + join.Alias + "\"))\n")
|
||||
}
|
||||
}
|
||||
fields = selectBuffer.String()
|
||||
} else {
|
||||
fields = fmt.Sprintf("mod = mod.Fields(%sin.%sListModel{})", in.options.TemplateGroup, in.In.VarName)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) generateLogicListWhere(ctx context.Context, in *CurdPreviewInput) string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
|
||||
// 主表
|
||||
l.generateLogicListWhereEach(buffer, in.masterFields, in.In.DaoName, "")
|
||||
l.generateLogicListWhereEach(buffer, in, 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)
|
||||
l.generateLogicListWhereEach(buffer, in, v.Columns, v.DaoName, v.Alias)
|
||||
}
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (l *gCurd) generateLogicListWhereEach(buffer *bytes.Buffer, fields []*sysin.GenCodesColumnListModel, daoName string, alias string) {
|
||||
func (l *gCurd) generateLogicListWhereEach(buffer *bytes.Buffer, in *CurdPreviewInput, fields []*sysin.GenCodesColumnListModel, daoName string, alias string) {
|
||||
isLink := false
|
||||
if alias != "" {
|
||||
alias = `"` + alias + `."+`
|
||||
isLink = true
|
||||
}
|
||||
|
||||
tablePrefix := ""
|
||||
wherePrefix := "Where"
|
||||
if isLink {
|
||||
wherePrefix = "WherePrefix"
|
||||
tablePrefix = "dao." + daoName + ".Table(), "
|
||||
}
|
||||
|
||||
for _, field := range fields {
|
||||
if !field.IsQuery || field.QueryWhere == "" {
|
||||
isQuery := false
|
||||
// 树表查询上级
|
||||
if in.options.Step.IsTreeTable && IsPidName(field.Name) {
|
||||
isQuery = true
|
||||
field.QueryWhere = WhereModeEq
|
||||
}
|
||||
|
||||
if (!field.IsQuery && !isQuery) || field.QueryWhere == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
buffer.WriteString(fmt.Sprintf(LogicWhereComments, field.Dc))
|
||||
|
||||
var (
|
||||
linkMode string
|
||||
whereTag string
|
||||
columnName string
|
||||
)
|
||||
|
||||
// 查询用户摘要
|
||||
if field.IsQuery && in.options.Step.HasQueryMemberSummary && IsMemberSummaryField(field.Name) {
|
||||
servicePackName := "service"
|
||||
if in.options.Step.IsAddon {
|
||||
servicePackName = "isc"
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf("if in.%v != \"\" {\n\t\t\t\tids, err := %v.AdminMember().GetIdsByKeyword(ctx, in.%v)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, 0, err\n\t\t\t\t}\n\t\t\t\tmod = mod.WhereIn(dao.%v.Columns().%v, ids)\n\t\t\t}\n", field.GoName, servicePackName, field.GoName, in.In.DaoName, field.GoName))
|
||||
continue
|
||||
}
|
||||
|
||||
if IsNumberType(field.GoType) {
|
||||
linkMode = `in.` + field.GoName + ` > 0`
|
||||
} else if field.GoType == GoTypeGTime {
|
||||
@@ -197,8 +228,6 @@ func (l *gCurd) generateLogicListWhereEach(buffer *bytes.Buffer, fields []*sysin
|
||||
linkMode = `len(in.` + field.GoName + `) == 2`
|
||||
}
|
||||
|
||||
buffer.WriteString(fmt.Sprintf(LogicWhereComments, field.Dc))
|
||||
|
||||
// 如果是关联表重新转换字段
|
||||
columnName = field.GoName
|
||||
if isLink {
|
||||
@@ -207,35 +236,35 @@ func (l *gCurd) generateLogicListWhereEach(buffer *bytes.Buffer, fields []*sysin
|
||||
|
||||
switch field.QueryWhere {
|
||||
case WhereModeEq:
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.Where(" + alias + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}"
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "(" + tablePrefix + "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}"
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "Not(" + tablePrefix + "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}"
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "GT(" + tablePrefix + "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}"
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "GTE(" + tablePrefix + "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}"
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "LT(" + tablePrefix + "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}"
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "LTE(" + tablePrefix + "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}"
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "In(" + tablePrefix + "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}"
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "NotIn(" + tablePrefix + "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}"
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "Between(" + tablePrefix + "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}"
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "NotBetween(" + tablePrefix + "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}"
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "Like(" + tablePrefix + "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}"
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "Like(" + tablePrefix + "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}"
|
||||
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "NotLike(" + tablePrefix + "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}"
|
||||
val := tablePrefix + "fmt.Sprintf(`JSON_CONTAINS(%s,'%v')`, dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")"
|
||||
whereTag = "\tif in." + field.GoName + linkMode + " {\n\t\tmod = mod." + wherePrefix + "(" + val + ")\n\t}"
|
||||
|
||||
default:
|
||||
buffer.WriteString(fmt.Sprintf(LogicWhereNoSupport, field.QueryWhere))
|
||||
|
@@ -22,7 +22,7 @@ func (l *gCurd) webEditTplData(ctx context.Context, in *CurdPreviewInput) (data
|
||||
|
||||
func (l *gCurd) generateWebEditFormItem(ctx context.Context, in *CurdPreviewInput) string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
for k, field := range in.masterFields {
|
||||
for _, field := range in.masterFields {
|
||||
if !field.IsEdit {
|
||||
continue
|
||||
}
|
||||
@@ -36,6 +36,10 @@ func (l *gCurd) generateWebEditFormItem(ctx context.Context, in *CurdPreviewInpu
|
||||
component string
|
||||
)
|
||||
|
||||
if in.options.Step.IsTreeTable && IsPidName(field.Name) {
|
||||
field.FormMode = FormModePidTreeSelect
|
||||
}
|
||||
|
||||
switch field.FormMode {
|
||||
case FormModeInput:
|
||||
component = defaultComponent
|
||||
@@ -98,16 +102,45 @@ func (l *gCurd) generateWebEditFormItem(ctx context.Context, in *CurdPreviewInpu
|
||||
|
||||
case FormModeCitySelector:
|
||||
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <CitySelector v-model:value=\"formValue.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
|
||||
|
||||
case FormModePidTreeSelect:
|
||||
component = fmt.Sprintf(`<n-form-item label="%v" path="pid">
|
||||
<n-tree-select
|
||||
:options="treeOption"
|
||||
v-model:value="formValue.pid"
|
||||
key-field="%v"
|
||||
label-field="%v"
|
||||
clearable
|
||||
filterable
|
||||
default-expand-all
|
||||
show-path
|
||||
/>
|
||||
</n-form-item>`, field.Dc, in.pk.TsName, in.options.Tree.TitleField.TsName)
|
||||
case FormModeTreeSelect:
|
||||
component = fmt.Sprintf(`<n-form-item label="%v" path="%v">
|
||||
<n-tree-select
|
||||
placeholder="请选择%v"
|
||||
v-model:value="formValue.%v"
|
||||
:options="[{ label: 'AA', key: 1, children: [{ label: 'BB', key: 2 }] }]"
|
||||
clearable
|
||||
filterable
|
||||
default-expand-all
|
||||
/>
|
||||
</n-form-item>`, field.Dc, field.TsName, field.Dc, field.TsName)
|
||||
case FormModeCascader:
|
||||
component = fmt.Sprintf(`<n-form-item label="%v" path="%v">
|
||||
<n-cascader
|
||||
placeholder="请选择%v"
|
||||
v-model:value="formValue.%v"
|
||||
:options="[{ label: 'AA', value: 1, children: [{ label: 'BB', value: 2 }] }]"
|
||||
clearable
|
||||
filterable
|
||||
/>
|
||||
</n-form-item>`, field.Dc, field.TsName, field.Dc, field.TsName)
|
||||
default:
|
||||
component = defaultComponent
|
||||
}
|
||||
|
||||
if len(in.masterFields) == k {
|
||||
buffer.WriteString(" " + component)
|
||||
} else {
|
||||
buffer.WriteString(" " + component + "\n\n")
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf("<n-gi span=\"%v\">%v</n-gi>\n\n", field.FormGridSpan, component))
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
@@ -119,23 +152,25 @@ func (l *gCurd) generateWebEditScript(ctx context.Context, in *CurdPreviewInput)
|
||||
setupBuffer = bytes.NewBuffer(nil)
|
||||
)
|
||||
|
||||
importBuffer.WriteString(" import { ref, computed } from 'vue';\n")
|
||||
|
||||
// 导入api
|
||||
var importApiMethod = []string{"Edit", "View"}
|
||||
if in.options.Step.HasMaxSort {
|
||||
importBuffer.WriteString(" import { ref } from 'vue';\n")
|
||||
if in.Config.Application.Crud.Templates[in.In.GenTemplate].IsAddon {
|
||||
importBuffer.WriteString(" import { Edit, MaxSort, View } from '@/api/addons/" + in.In.AddonName + "/" + gstr.LcFirst(in.In.VarName) + "';\n")
|
||||
} else {
|
||||
importBuffer.WriteString(" import { Edit, MaxSort, View } from '@/api/" + gstr.LcFirst(in.In.VarName) + "';\n")
|
||||
}
|
||||
setupBuffer.WriteString(" function openModal(state: State) {\n adaModalWidth(dialogWidth);\n showModal.value = true;\n loading.value = true;\n\n // 新增\n if (!state || state.id < 1) {\n formValue.value = newState(state);\n MaxSort()\n .then((res) => {\n formValue.value.sort = res.sort;\n })\n .finally(() => {\n loading.value = false;\n });\n return;\n }\n\n // 编辑\n View({ id: state.id })\n .then((res) => {\n formValue.value = res;\n })\n .finally(() => {\n loading.value = false;\n });\n }")
|
||||
} else {
|
||||
importBuffer.WriteString(" import { ref } from 'vue';\n")
|
||||
if in.Config.Application.Crud.Templates[in.In.GenTemplate].IsAddon {
|
||||
importBuffer.WriteString(" import { Edit, View } from '@/api/addons/" + in.In.AddonName + "/" + gstr.LcFirst(in.In.VarName) + "';\n")
|
||||
} else {
|
||||
importBuffer.WriteString(" import { Edit, View } from '@/api/" + gstr.LcFirst(in.In.VarName) + "';\n")
|
||||
}
|
||||
setupBuffer.WriteString(" function openModal(state: State) {\n adaModalWidth(dialogWidth);\n showModal.value = true;\n loading.value = true;\n\n // 新增\n if (!state || state.id < 1) {\n formValue.value = newState(state);\n return;\n }\n\n // 编辑\n View({ id: state.id })\n .then((res) => {\n formValue.value = res;\n })\n .finally(() => {\n loading.value = false;\n });\n }")
|
||||
importApiMethod = append(importApiMethod, "MaxSort")
|
||||
}
|
||||
importBuffer.WriteString(" import " + ImportWebMethod(importApiMethod) + " from '" + in.options.ImportWebApi + "';\n")
|
||||
|
||||
// 导入model
|
||||
var importModelMethod = []string{"options", "State", "newState"}
|
||||
if in.options.Step.IsTreeTable {
|
||||
importModelMethod = append(importModelMethod, []string{"treeOption", "loadTreeOption"}...)
|
||||
}
|
||||
|
||||
if in.options.Step.HasRules {
|
||||
importModelMethod = append(importModelMethod, "rules")
|
||||
}
|
||||
importBuffer.WriteString(" import " + ImportWebMethod(importModelMethod) + " from './model';\n")
|
||||
|
||||
for _, field := range in.masterFields {
|
||||
if !field.IsEdit {
|
||||
|
@@ -6,59 +6,135 @@
|
||||
package views
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
const (
|
||||
IndexApiImport = " import {%v } from '@/api/%s';" // 这里将导入的包路径写死了,后面可以优化成根据配置动态读取
|
||||
IndexApiAddonsImport = " import {%v } from '@/api/addons/%s/%s';"
|
||||
IndexIconsImport = " import {%v } from '@vicons/antd';"
|
||||
)
|
||||
|
||||
func (l *gCurd) webIndexTplData(ctx context.Context, in *CurdPreviewInput) (g.Map, error) {
|
||||
var (
|
||||
data = make(g.Map)
|
||||
apiImport = []string{" List"}
|
||||
iconsImport []string
|
||||
data = make(g.Map)
|
||||
importBuffer = bytes.NewBuffer(nil)
|
||||
importVueMethod = []string{"h", "reactive", "ref", "computed"}
|
||||
importApiMethod = []string{"List"}
|
||||
importModelMethod = []string{"columns", "schemas"}
|
||||
importUtilsMethod = []string{"adaTableScrollX"}
|
||||
importIcons []string
|
||||
actionWidth int64 = 72
|
||||
)
|
||||
|
||||
// 添加
|
||||
if in.options.Step.HasAdd {
|
||||
iconsImport = append(iconsImport, " PlusOutlined")
|
||||
importIcons = append(importIcons, "PlusOutlined")
|
||||
}
|
||||
|
||||
// 编辑
|
||||
// if in.options.Step.HasEdit {
|
||||
// }
|
||||
if in.options.Step.HasEdit {
|
||||
in.options.Step.ActionColumnWidth += actionWidth
|
||||
if in.options.Step.IsTreeTable && !in.options.Step.IsOptionTreeTable {
|
||||
in.options.Step.ActionColumnWidth += actionWidth
|
||||
}
|
||||
if in.options.Step.IsOptionTreeTable {
|
||||
importIcons = append(importIcons, "EditOutlined")
|
||||
}
|
||||
}
|
||||
|
||||
// 导出
|
||||
if in.options.Step.HasExport {
|
||||
iconsImport = append(iconsImport, " ExportOutlined")
|
||||
apiImport = append(apiImport, " Export")
|
||||
importIcons = append(importIcons, "ExportOutlined")
|
||||
importApiMethod = append(importApiMethod, "Export")
|
||||
}
|
||||
|
||||
// 删除
|
||||
if in.options.Step.HasDel || in.options.Step.HasBatchDel {
|
||||
iconsImport = append(iconsImport, " DeleteOutlined")
|
||||
apiImport = append(apiImport, " Delete")
|
||||
if in.options.Step.HasDel {
|
||||
importApiMethod = append(importApiMethod, "Delete")
|
||||
in.options.Step.ActionColumnWidth += actionWidth
|
||||
}
|
||||
|
||||
// 导出
|
||||
// 批量删除
|
||||
if in.options.Step.HasBatchDel {
|
||||
importIcons = append(importIcons, "DeleteOutlined")
|
||||
importApiMethod = append(importApiMethod, "Delete")
|
||||
}
|
||||
|
||||
// 修改状态
|
||||
if in.options.Step.HasStatus {
|
||||
apiImport = append(apiImport, " Status")
|
||||
importApiMethod = append(importApiMethod, "Status")
|
||||
importUtilsMethod = append(importUtilsMethod, "getOptionLabel")
|
||||
importModelMethod = append(importModelMethod, "options")
|
||||
in.options.Step.ActionColumnWidth += actionWidth
|
||||
}
|
||||
|
||||
if in.Config.Application.Crud.Templates[in.In.GenTemplate].IsAddon {
|
||||
data["apiImport"] = fmt.Sprintf(IndexApiAddonsImport, gstr.Implode(",", apiImport), in.In.AddonName, gstr.LcFirst(in.In.VarName))
|
||||
} else {
|
||||
data["apiImport"] = fmt.Sprintf(IndexApiImport, gstr.Implode(",", apiImport), gstr.LcFirst(in.In.VarName))
|
||||
// 更多
|
||||
// 查看详情
|
||||
if in.options.Step.HasView {
|
||||
in.options.Step.ActionColumnWidth += actionWidth
|
||||
}
|
||||
if len(iconsImport) > 0 {
|
||||
data["iconsImport"] = fmt.Sprintf(IndexIconsImport, gstr.Implode(",", iconsImport))
|
||||
|
||||
// 展开树
|
||||
if in.options.Step.IsTreeTable {
|
||||
importIcons = append(importIcons, "AlignLeftOutlined")
|
||||
}
|
||||
|
||||
// 存在字典数据选项
|
||||
if in.options.DictOps.Has {
|
||||
importVueMethod = append(importVueMethod, "onMounted")
|
||||
importModelMethod = append(importModelMethod, "loadOptions")
|
||||
}
|
||||
|
||||
// 普通树表
|
||||
if in.options.Step.IsTreeTable && !in.options.Step.IsOptionTreeTable {
|
||||
importUtilsMethod = append(importUtilsMethod, "convertListToTree")
|
||||
}
|
||||
|
||||
// 选项式树表
|
||||
if in.options.Step.IsOptionTreeTable {
|
||||
importVueMethod = append(importVueMethod, []string{"onMounted", "unref"}...)
|
||||
importIcons = append(importIcons, []string{"FormOutlined", "SearchOutlined"}...)
|
||||
importApiMethod = append(importApiMethod, "TreeOption")
|
||||
importUtilsMethod = append(importUtilsMethod, "getTreeKeys")
|
||||
importModelMethod = append(importModelMethod, []string{"loadTreeOption", "treeOption", "State"}...)
|
||||
}
|
||||
|
||||
// 操作按钮宽度最小值
|
||||
if in.options.Step.ActionColumnWidth > 0 && in.options.Step.ActionColumnWidth < actionWidth*2 {
|
||||
in.options.Step.ActionColumnWidth = 100
|
||||
}
|
||||
|
||||
// 导入基础包
|
||||
importBuffer.WriteString(" import " + ImportWebMethod(importVueMethod) + " from 'vue';\n")
|
||||
importBuffer.WriteString(" import { useDialog, useMessage } from 'naive-ui';\n")
|
||||
importBuffer.WriteString(" import { BasicTable, TableAction } from '@/components/Table';\n")
|
||||
importBuffer.WriteString(" import { BasicForm, useForm } from '@/components/Form/index';\n")
|
||||
importBuffer.WriteString(" import { usePermission } from '@/hooks/web/usePermission';\n")
|
||||
|
||||
// 导入api
|
||||
importBuffer.WriteString(" import " + ImportWebMethod(importApiMethod) + " from '" + in.options.ImportWebApi + "';\n")
|
||||
|
||||
// 导入icons
|
||||
if len(importIcons) > 0 {
|
||||
importBuffer.WriteString(" import " + ImportWebMethod(importIcons) + " from '@vicons/antd';\n")
|
||||
}
|
||||
|
||||
// 导入model
|
||||
if in.options.Step.IsTreeTable {
|
||||
importModelMethod = append(importModelMethod, "newState")
|
||||
}
|
||||
importBuffer.WriteString(" import " + ImportWebMethod(importModelMethod) + " from './model';\n")
|
||||
|
||||
// 导入utils
|
||||
if len(importUtilsMethod) > 0 {
|
||||
importBuffer.WriteString(" import " + ImportWebMethod(importUtilsMethod) + " from '@/utils/hotgo';\n")
|
||||
}
|
||||
|
||||
// 导入edit组件
|
||||
if in.options.Step.HasEdit {
|
||||
importBuffer.WriteString(" import Edit from './edit.vue';\n")
|
||||
}
|
||||
|
||||
// 导入view组件
|
||||
if in.options.Step.HasView {
|
||||
importBuffer.WriteString(" import View from './view.vue';\n")
|
||||
}
|
||||
|
||||
// 没有需要查询的字段则隐藏搜索表单
|
||||
@@ -83,5 +159,6 @@ func (l *gCurd) webIndexTplData(ctx context.Context, in *CurdPreviewInput) (g.Ma
|
||||
}
|
||||
}
|
||||
data["isSearchForm"] = isSearchForm
|
||||
data["import"] = importBuffer.String()
|
||||
return data, nil
|
||||
}
|
||||
|
@@ -12,61 +12,148 @@ import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"hotgo/internal/library/dict"
|
||||
"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();"
|
||||
)
|
||||
type StateItem struct {
|
||||
Name string
|
||||
DefaultValue interface{}
|
||||
Dc string
|
||||
}
|
||||
|
||||
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["stateItems"] = l.generateWebModelStateItems(ctx, in)
|
||||
data["rules"] = l.generateWebModelRules(ctx, in)
|
||||
data["formSchema"] = l.generateWebModelFormSchema(ctx, in)
|
||||
if data["columns"], err = l.generateWebModelColumns(ctx, in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 根据表单生成情况,按需导包
|
||||
data["import"] = l.generateWebModelImport(ctx, in)
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebModelState(ctx context.Context, in *CurdPreviewInput) string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
buffer.WriteString("export class State {\n")
|
||||
func (l *gCurd) generateWebModelImport(ctx context.Context, in *CurdPreviewInput) string {
|
||||
importBuffer := bytes.NewBuffer(nil)
|
||||
|
||||
importBuffer.WriteString("import { h, ref } from 'vue';\n")
|
||||
|
||||
// 导入基础组件
|
||||
if len(in.options.Step.ImportModel.NaiveUI) > 0 {
|
||||
importBuffer.WriteString("import " + ImportWebMethod(in.options.Step.ImportModel.NaiveUI) + " from 'naive-ui';\n")
|
||||
}
|
||||
|
||||
importBuffer.WriteString("import { cloneDeep } from 'lodash-es';\n")
|
||||
|
||||
// 导入表单搜索
|
||||
if in.options.Step.HasSearchForm {
|
||||
importBuffer.WriteString("import { FormSchema } from '@/components/Form';\n")
|
||||
}
|
||||
|
||||
// 导入字典选项
|
||||
if in.options.DictOps.Has {
|
||||
importBuffer.WriteString("import { Dicts } from '@/api/dict/dict';\n")
|
||||
}
|
||||
|
||||
// 导入工具类
|
||||
if len(in.options.Step.ImportModel.UtilsIs) > 0 {
|
||||
importBuffer.WriteString("import " + ImportWebMethod(in.options.Step.ImportModel.UtilsIs) + " from '@/utils/is';\n")
|
||||
}
|
||||
|
||||
if len(in.options.Step.ImportModel.UtilsUrl) > 0 {
|
||||
importBuffer.WriteString("import " + ImportWebMethod(in.options.Step.ImportModel.UtilsUrl) + " from '@/utils/urlUtils';\n")
|
||||
}
|
||||
|
||||
if len(in.options.Step.ImportModel.UtilsDate) > 0 {
|
||||
importBuffer.WriteString("import " + ImportWebMethod(in.options.Step.ImportModel.UtilsDate) + " from '@/utils/dateUtil';\n")
|
||||
}
|
||||
|
||||
if in.options.Step.HasRulesValidator {
|
||||
importBuffer.WriteString("import { validate } from '@/utils/validateUtil';\n")
|
||||
}
|
||||
|
||||
if len(in.options.Step.ImportModel.UtilsHotGo) > 0 {
|
||||
importBuffer.WriteString("import " + ImportWebMethod(in.options.Step.ImportModel.UtilsHotGo) + " from '@/utils/hotgo';\n")
|
||||
}
|
||||
|
||||
if len(in.options.Step.ImportModel.UtilsIndex) > 0 {
|
||||
importBuffer.WriteString("import " + ImportWebMethod(in.options.Step.ImportModel.UtilsIndex) + " from '@/utils';\n")
|
||||
}
|
||||
|
||||
// 导入api
|
||||
var importApiMethod []string
|
||||
if in.options.Step.HasSwitch {
|
||||
importApiMethod = append(importApiMethod, "Switch")
|
||||
}
|
||||
if in.options.Step.IsTreeTable {
|
||||
importApiMethod = append(importApiMethod, "TreeOption")
|
||||
}
|
||||
if len(importApiMethod) > 0 {
|
||||
importBuffer.WriteString("import " + ImportWebMethod(importApiMethod) + " from '" + in.options.ImportWebApi + "';\n")
|
||||
}
|
||||
|
||||
if in.options.Step.HasSwitch {
|
||||
importBuffer.WriteString("import { usePermission } from '@/hooks/web/usePermission';\n")
|
||||
importBuffer.WriteString("const { hasPermission } = usePermission();\n")
|
||||
importBuffer.WriteString("const $message = window['$message'];\n")
|
||||
}
|
||||
return importBuffer.String()
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebModelStateItems(ctx context.Context, in *CurdPreviewInput) (items []*StateItem) {
|
||||
for _, field := range in.masterFields {
|
||||
var value = field.DefaultValue
|
||||
if value == nil {
|
||||
value = "null"
|
||||
}
|
||||
if value == "" {
|
||||
value = "''"
|
||||
value = `''`
|
||||
}
|
||||
|
||||
// 选项组件默认值调整
|
||||
if gconv.Int(value) == 0 && IsSelectFormMode(field.FormMode) {
|
||||
value = "null"
|
||||
}
|
||||
|
||||
if field.Name == "status" {
|
||||
value = 1
|
||||
}
|
||||
if field.FormMode == "Switch" {
|
||||
if field.FormMode == FormModeSwitch {
|
||||
value = 2
|
||||
}
|
||||
if field.FormMode == "InputDynamic" {
|
||||
if field.FormMode == FormModeInputDynamic {
|
||||
value = "[]"
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf(" public %s = %v; // %s\n", field.TsName, value, field.Dc))
|
||||
items = append(items, &StateItem{
|
||||
Name: field.TsName,
|
||||
DefaultValue: value,
|
||||
Dc: field.Dc,
|
||||
})
|
||||
|
||||
// 查询用户摘要
|
||||
if field.IsList && in.options.Step.HasHookMemberSummary && IsMemberSummaryField(field.Name) {
|
||||
items = append(items, &StateItem{
|
||||
Name: field.TsName + "Summa?: null | MemberSumma",
|
||||
DefaultValue: "null",
|
||||
Dc: field.Dc + "摘要信息",
|
||||
})
|
||||
}
|
||||
}
|
||||
buffer.WriteString("\n constructor(state?: Partial<State>) {\n if (state) {\n Object.assign(this, state);\n }\n }")
|
||||
buffer.WriteString("}")
|
||||
return buffer.String()
|
||||
return
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebModelDictOptions(ctx context.Context, in *CurdPreviewInput) (g.Map, error) {
|
||||
func (l *gCurd) generateWebModelDictOptions(ctx context.Context, in *CurdPreviewInput) error {
|
||||
type DictType struct {
|
||||
Id int64 `json:"id"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
var (
|
||||
options = make(g.Map)
|
||||
dictTypeIds []int64
|
||||
dictTypeList []*DictType
|
||||
builtinDictTypeIds []int64
|
||||
@@ -87,8 +174,7 @@ func (l *gCurd) generateWebModelDictOptions(ctx context.Context, in *CurdPreview
|
||||
builtinDictTypeIds = convert.UniqueSlice(builtinDictTypeIds)
|
||||
|
||||
if len(dictTypeIds) == 0 && len(builtinDictTypeIds) == 0 {
|
||||
options["has"] = false
|
||||
return options, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(dictTypeIds) > 0 {
|
||||
@@ -97,71 +183,52 @@ func (l *gCurd) generateWebModelDictOptions(ctx context.Context, in *CurdPreview
|
||||
WhereIn("id", dictTypeIds).
|
||||
Scan(&dictTypeList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(builtinDictTypeIds) > 0 {
|
||||
for _, id := range builtinDictTypeIds {
|
||||
opts, err := dict.GetOptionsById(ctx, id)
|
||||
typ, err := dict.GetTypeById(ctx, id)
|
||||
if err != nil && !errors.Is(err, dict.NotExistKeyError) {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
if len(typ) > 0 {
|
||||
row := new(DictType)
|
||||
row.Id = id
|
||||
row.Type = opts[0].Type
|
||||
row.Type = typ
|
||||
builtinDictTypeList = append(builtinDictTypeList, row)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(dictTypeList) == 0 && len(builtinDictTypeList) == 0 {
|
||||
options["has"] = false
|
||||
return options, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(builtinDictTypeList) > 0 {
|
||||
dictTypeList = append(dictTypeList, builtinDictTypeList...)
|
||||
}
|
||||
|
||||
options["has"] = true
|
||||
in.options.DictOps.Has = true
|
||||
|
||||
var (
|
||||
awaitLoadOptions string
|
||||
switchLoadOptions string
|
||||
)
|
||||
|
||||
interfaceOptionsBuffer := bytes.NewBuffer(nil)
|
||||
interfaceOptionsBuffer.WriteString("export interface IOptions extends Options {\n")
|
||||
constOptionsBuffer := bytes.NewBuffer(nil)
|
||||
constOptionsBuffer.WriteString("export const options = ref<IOptions>({\n")
|
||||
// 导入选项包
|
||||
in.options.Step.ImportModel.UtilsHotGo = append(in.options.Step.ImportModel.UtilsHotGo, "Option")
|
||||
|
||||
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)
|
||||
in.options.DictOps.Schemas = append(in.options.DictOps.Schemas, &OptionsSchemasField{
|
||||
Field: field.TsName,
|
||||
Type: v.Type,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
awaitLoadOptions = fmt.Sprintf("%s '%s',\n", awaitLoadOptions, v.Type)
|
||||
interfaceOptionsBuffer.WriteString(" " + v.Type + ": Option[]; \n")
|
||||
constOptionsBuffer.WriteString(" " + v.Type + ": [],\n")
|
||||
in.options.DictOps.Types = append(in.options.DictOps.Types, v.Type)
|
||||
}
|
||||
|
||||
interfaceOptionsBuffer.WriteString("};\n")
|
||||
constOptionsBuffer.WriteString("});\n")
|
||||
|
||||
loadOptionsBuffer := bytes.NewBuffer(nil)
|
||||
loadOptionsBuffer.WriteString(fmt.Sprintf(ModelLoadOptionsTemplate, awaitLoadOptions, switchLoadOptions))
|
||||
|
||||
options["interface"] = interfaceOptionsBuffer.String()
|
||||
options["const"] = constOptionsBuffer.String()
|
||||
options["load"] = loadOptionsBuffer.String()
|
||||
return options, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebModelRules(ctx context.Context, in *CurdPreviewInput) string {
|
||||
@@ -172,9 +239,11 @@ func (l *gCurd) generateWebModelRules(ctx context.Context, in *CurdPreviewInput)
|
||||
continue
|
||||
}
|
||||
|
||||
in.options.Step.HasRules = true
|
||||
if field.FormRole == "" || field.FormRole == FormRoleNone || field.FormRole == "required" {
|
||||
buffer.WriteString(fmt.Sprintf(" %s: {\n required: %v,\n trigger: ['blur', 'input'],\n type: '%s',\n message: '请输入%s',\n },\n", field.TsName, field.Required, field.TsType, field.Dc))
|
||||
} else {
|
||||
in.options.Step.HasRulesValidator = true
|
||||
buffer.WriteString(fmt.Sprintf(" %s: {\n required: %v,\n trigger: ['blur', 'input'],\n type: '%s',\n validator: validate.%v,\n },\n", field.TsName, field.Required, field.TsType, field.FormRole))
|
||||
}
|
||||
}
|
||||
@@ -187,7 +256,7 @@ func (l *gCurd) generateWebModelFormSchema(ctx context.Context, in *CurdPreviewI
|
||||
buffer.WriteString("export const schemas = ref<FormSchema[]>([\n")
|
||||
|
||||
// 主表
|
||||
l.generateWebModelFormSchemaEach(buffer, in.masterFields)
|
||||
l.generateWebModelFormSchemaEach(buffer, in.masterFields, in)
|
||||
|
||||
// 关联表
|
||||
if len(in.options.Join) > 0 {
|
||||
@@ -195,7 +264,7 @@ func (l *gCurd) generateWebModelFormSchema(ctx context.Context, in *CurdPreviewI
|
||||
if !isEffectiveJoin(v) {
|
||||
continue
|
||||
}
|
||||
l.generateWebModelFormSchemaEach(buffer, v.Columns)
|
||||
l.generateWebModelFormSchemaEach(buffer, v.Columns, in)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,11 +272,18 @@ func (l *gCurd) generateWebModelFormSchema(ctx context.Context, in *CurdPreviewI
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebModelFormSchemaEach(buffer *bytes.Buffer, fields []*sysin.GenCodesColumnListModel) {
|
||||
func (l *gCurd) generateWebModelFormSchemaEach(buffer *bytes.Buffer, fields []*sysin.GenCodesColumnListModel, in *CurdPreviewInput) {
|
||||
for _, field := range fields {
|
||||
if !field.IsQuery {
|
||||
continue
|
||||
}
|
||||
in.options.Step.HasSearchForm = true
|
||||
|
||||
// 查询用户摘要
|
||||
if field.IsQuery && in.options.Step.HasQueryMemberSummary && IsMemberSummaryField(field.Name) {
|
||||
buffer.WriteString(fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n placeholder: '请输入ID|用户名|姓名|手机号',\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NInput", field.Dc))
|
||||
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)
|
||||
@@ -224,15 +300,19 @@ func (l *gCurd) generateWebModelFormSchemaEach(buffer *bytes.Buffer, fields []*s
|
||||
|
||||
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()")
|
||||
in.options.Step.ImportModel.UtilsDate = append(in.options.Step.ImportModel.UtilsDate, "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()")
|
||||
in.options.Step.ImportModel.UtilsDate = append(in.options.Step.ImportModel.UtilsDate, "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()")
|
||||
in.options.Step.ImportModel.UtilsDate = append(in.options.Step.ImportModel.UtilsDate, "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()")
|
||||
in.options.Step.ImportModel.UtilsDate = append(in.options.Step.ImportModel.UtilsDate, "defRangeShortcuts")
|
||||
|
||||
case FormModeSwitch:
|
||||
fallthrough
|
||||
@@ -287,14 +367,22 @@ func (l *gCurd) generateWebModelColumnsEach(buffer *bytes.Buffer, in *CurdPrevie
|
||||
continue
|
||||
}
|
||||
var (
|
||||
defaultComponent = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n },\n", field.Dc, field.TsName)
|
||||
defaultComponent = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n },\n", field.Dc, field.TsName, field.Align, field.Width)
|
||||
component string
|
||||
)
|
||||
|
||||
// 查询用户摘要
|
||||
if in.options.Step.HasHookMemberSummary && IsMemberSummaryField(field.Name) {
|
||||
buffer.WriteString(fmt.Sprintf(" {\n title: '%v',\n key: '%v',\n align: '%v',\n width: %v,\n render(row) {\n return renderPopoverMemberSumma(row.%vSumma);\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, field.TsName))
|
||||
in.options.Step.ImportModel.UtilsIndex = append(in.options.Step.ImportModel.UtilsIndex, []string{"renderPopoverMemberSumma", "MemberSumma"}...)
|
||||
continue
|
||||
}
|
||||
|
||||
// 这里根据编辑表单组件来进行推断,如果没有则使用默认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)
|
||||
component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row) {\n return formatToDate(row.%s);\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, field.TsName)
|
||||
in.options.Step.ImportModel.UtilsDate = append(in.options.Step.ImportModel.UtilsDate, "formatToDate")
|
||||
|
||||
case FormModeRadio:
|
||||
fallthrough
|
||||
@@ -303,32 +391,50 @@ func (l *gCurd) generateWebModelColumnsEach(buffer *bytes.Buffer, in *CurdPrevie
|
||||
err = gerror.Newf("设置单选下拉框选项时,必须选择字典类型,字段名称:%v", field.Name)
|
||||
return
|
||||
}
|
||||
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)
|
||||
component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\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.Align, field.Width, field.TsName, in.options.dictMap[field.TsName], field.TsName, in.options.dictMap[field.TsName], field.TsName)
|
||||
in.options.Step.ImportModel.NaiveUI = append(in.options.Step.ImportModel.NaiveUI, "NTag")
|
||||
in.options.Step.ImportModel.UtilsIs = append(in.options.Step.ImportModel.UtilsIs, "isNullObject")
|
||||
in.options.Step.ImportModel.UtilsHotGo = append(in.options.Step.ImportModel.UtilsHotGo, []string{"getOptionLabel", "getOptionTag"}...)
|
||||
|
||||
case FormModeSelectMultiple:
|
||||
if g.IsEmpty(in.options.dictMap[field.TsName]) {
|
||||
err = gerror.Newf("设置多选下拉框选项时,必须选择字典类型,字段名称:%v", field.Name)
|
||||
return
|
||||
}
|
||||
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])
|
||||
component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\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.Align, field.Width, field.TsName, field.TsName, field.TsName, in.options.dictMap[field.TsName], in.options.dictMap[field.TsName])
|
||||
in.options.Step.ImportModel.NaiveUI = append(in.options.Step.ImportModel.NaiveUI, "NTag")
|
||||
in.options.Step.ImportModel.UtilsIs = append(in.options.Step.ImportModel.UtilsIs, "isNullObject")
|
||||
in.options.Step.ImportModel.UtilsHotGo = append(in.options.Step.ImportModel.UtilsHotGo, []string{"getOptionLabel", "getOptionTag"}...)
|
||||
|
||||
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 onError: errorImg,\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)
|
||||
component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row) {\n return h(%s, {\n width: 32,\n height: 32,\n src: row.%s,\n fallbackSrc: errorImg,\n onError: errorImg,\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, field.Align, field.Width, "NImage", field.TsName)
|
||||
in.options.Step.ImportModel.NaiveUI = append(in.options.Step.ImportModel.NaiveUI, "NImage")
|
||||
in.options.Step.ImportModel.UtilsHotGo = append(in.options.Step.ImportModel.UtilsHotGo, "errorImg")
|
||||
|
||||
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 onError: errorImg,\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")
|
||||
component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\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 onError: errorImg,\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.Align, field.Width, field.TsName, field.TsName, "NImage")
|
||||
in.options.Step.ImportModel.NaiveUI = append(in.options.Step.ImportModel.NaiveUI, "NImage")
|
||||
in.options.Step.ImportModel.UtilsIs = append(in.options.Step.ImportModel.UtilsIs, "isArray")
|
||||
in.options.Step.ImportModel.UtilsHotGo = append(in.options.Step.ImportModel.UtilsHotGo, "errorImg")
|
||||
|
||||
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)
|
||||
component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\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.Align, field.Width, field.TsName, "NAvatar", field.TsName)
|
||||
in.options.Step.ImportModel.NaiveUI = append(in.options.Step.ImportModel.NaiveUI, "NAvatar")
|
||||
in.options.Step.ImportModel.UtilsUrl = append(in.options.Step.ImportModel.UtilsUrl, "getFileExt")
|
||||
|
||||
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")
|
||||
component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\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.Align, field.Width, field.TsName, field.TsName, "NAvatar")
|
||||
in.options.Step.ImportModel.NaiveUI = append(in.options.Step.ImportModel.NaiveUI, "NAvatar")
|
||||
in.options.Step.ImportModel.UtilsIs = append(in.options.Step.ImportModel.UtilsIs, "isNullObject")
|
||||
in.options.Step.ImportModel.UtilsUrl = append(in.options.Step.ImportModel.UtilsUrl, "getFileExt")
|
||||
|
||||
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, "/"+in.options.ApiPrefix+"/switch", field.TsName, in.pk.TsName, in.pk.TsName, convert.CamelCaseToUnderline(field.TsName), field.TsName)
|
||||
component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\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, field.Align, field.Width, "NSwitch", field.TsName, "/"+in.options.ApiPrefix+"/switch", field.TsName, in.pk.TsName, in.pk.TsName, convert.CamelCaseToUnderline(field.TsName), field.TsName)
|
||||
in.options.Step.ImportModel.NaiveUI = append(in.options.Step.ImportModel.NaiveUI, "NSwitch")
|
||||
|
||||
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)
|
||||
component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\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, field.Align, field.Width, "NRate", field.TsName)
|
||||
in.options.Step.ImportModel.NaiveUI = append(in.options.Step.ImportModel.NaiveUI, "NRate")
|
||||
|
||||
default:
|
||||
component = defaultComponent
|
||||
@@ -336,6 +442,5 @@ func (l *gCurd) generateWebModelColumnsEach(buffer *bytes.Buffer, in *CurdPrevie
|
||||
|
||||
buffer.WriteString(component)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
@@ -44,25 +44,25 @@ func (l *gCurd) generateWebViewItem(ctx context.Context, in *CurdPreviewInput) s
|
||||
component = defaultComponent
|
||||
|
||||
case FormModeRadio, FormModeSelect:
|
||||
component = fmt.Sprintf("<n-descriptions-item label=\"%s\">\n <n-tag\n :type=\"getOptionTag(options.%s, formValue?.%s)\"\n size=\"small\"\n class=\"min-left-space\"\n >{{ getOptionLabel(options.%s, formValue?.%s) }}</n-tag\n >\n </n-descriptions-item>", field.Dc, in.options.dictMap[field.TsName], field.TsName, in.options.dictMap[field.TsName], field.TsName)
|
||||
component = fmt.Sprintf("<n-descriptions-item label=\"%s\">\n <n-tag :type=\"getOptionTag(options.%s, formValue?.%s)\" size=\"small\" class=\"min-left-space\">{{ getOptionLabel(options.%s, formValue?.%s) }}</n-tag>\n </n-descriptions-item>", field.Dc, in.options.dictMap[field.TsName], field.TsName, in.options.dictMap[field.TsName], field.TsName)
|
||||
|
||||
case FormModeCheckbox, 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])
|
||||
component = fmt.Sprintf("<n-descriptions-item label=\"%s\">\n <template v-for=\"(item, key) in formValue?.%s\" :key=\"key\">\n <n-tag :type=\"getOptionTag(options.%s, item)\" size=\"small\" class=\"min-left-space\">{{ getOptionLabel(options.%s, item) }}</n-tag>\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)
|
||||
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-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)
|
||||
component = fmt.Sprintf("<n-descriptions-item>\n <template #label>%s</template>\n <div class=\"upload-card\" v-show=\"formValue.%s !== ''\" @click=\"download(formValue.%s)\">\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)
|
||||
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-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)
|
||||
|
7
server/internal/library/hggen/views/gohtml/consts.go
Normal file
7
server/internal/library/hggen/views/gohtml/consts.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package gohtml
|
||||
|
||||
const (
|
||||
defaultIndentString = " "
|
||||
startIndent = 0
|
||||
defaultLastElement = "</html>"
|
||||
)
|
2
server/internal/library/hggen/views/gohtml/doc.go
Normal file
2
server/internal/library/hggen/views/gohtml/doc.go
Normal file
@@ -0,0 +1,2 @@
|
||||
// Package gohtml provides an HTML formatting function.
|
||||
package gohtml
|
7
server/internal/library/hggen/views/gohtml/element.go
Normal file
7
server/internal/library/hggen/views/gohtml/element.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package gohtml
|
||||
|
||||
// An element represents an HTML element.
|
||||
type element interface {
|
||||
isInline() bool
|
||||
write(*formattedBuffer, bool) bool
|
||||
}
|
37
server/internal/library/hggen/views/gohtml/formatter.go
Normal file
37
server/internal/library/hggen/views/gohtml/formatter.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package gohtml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Format parses the input HTML string, formats it and returns the result.
|
||||
func Format(s string) string {
|
||||
return parse(strings.NewReader(s)).html()
|
||||
}
|
||||
|
||||
// FormatBytes parses input HTML as bytes, formats it and returns the result.
|
||||
func FormatBytes(b []byte) []byte {
|
||||
return parse(bytes.NewReader(b)).bytes()
|
||||
}
|
||||
|
||||
// Format parses the input HTML string, formats it and returns the result with line no.
|
||||
func FormatWithLineNo(s string) string {
|
||||
return AddLineNo(Format(s))
|
||||
}
|
||||
|
||||
func AddLineNo(s string) string {
|
||||
lines := strings.Split(s, "\n")
|
||||
maxLineNoStrLen := len(strconv.Itoa(len(lines)))
|
||||
bf := &bytes.Buffer{}
|
||||
for i, line := range lines {
|
||||
lineNoStr := strconv.Itoa(i + 1)
|
||||
if i > 0 {
|
||||
bf.WriteString("\n")
|
||||
}
|
||||
bf.WriteString(strings.Repeat(" ", maxLineNoStrLen-len(lineNoStr)) + lineNoStr + " " + line)
|
||||
}
|
||||
return bf.String()
|
||||
|
||||
}
|
54
server/internal/library/hggen/views/gohtml/html_document.go
Normal file
54
server/internal/library/hggen/views/gohtml/html_document.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package gohtml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// Column to wrap lines to (disabled by default)
|
||||
var LineWrapColumn = 0
|
||||
|
||||
// Maxmimum characters a long word can extend past LineWrapColumn without wrapping
|
||||
var LineWrapMaxSpillover = 5
|
||||
|
||||
// An htmlDocument represents an HTML document.
|
||||
type htmlDocument struct {
|
||||
elements []element
|
||||
}
|
||||
|
||||
// html generates an HTML source code and returns it.
|
||||
func (htmlDoc *htmlDocument) html() string {
|
||||
str := string(htmlDoc.bytes())
|
||||
str = replaceMultipleNewlinesWithSpaceAndTabs(str)
|
||||
return str
|
||||
}
|
||||
|
||||
func replaceMultipleNewlinesWithSpaceAndTabs(input string) string {
|
||||
re := regexp.MustCompile(`\n\s*\n+`)
|
||||
formattedString := re.ReplaceAllString(input, "\n")
|
||||
return formattedString
|
||||
}
|
||||
|
||||
// bytes reads from htmlDocument's internal array of elements and returns HTML source code
|
||||
func (htmlDoc *htmlDocument) bytes() []byte {
|
||||
bf := &formattedBuffer{
|
||||
buffer: &bytes.Buffer{},
|
||||
|
||||
lineWrapColumn: LineWrapColumn,
|
||||
lineWrapMaxSpillover: LineWrapMaxSpillover,
|
||||
|
||||
indentString: defaultIndentString,
|
||||
indentLevel: startIndent,
|
||||
}
|
||||
|
||||
isPreviousNodeInline := true
|
||||
for _, child := range htmlDoc.elements {
|
||||
isPreviousNodeInline = child.write(bf, isPreviousNodeInline)
|
||||
}
|
||||
return bf.buffer.Bytes()
|
||||
}
|
||||
|
||||
// append appends an element to the htmlDocument.
|
||||
func (htmlDoc *htmlDocument) append(e element) {
|
||||
htmlDoc.elements = append(htmlDoc.elements, e)
|
||||
}
|
127
server/internal/library/hggen/views/gohtml/parser.go
Normal file
127
server/internal/library/hggen/views/gohtml/parser.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package gohtml
|
||||
|
||||
import (
|
||||
"golang.org/x/net/html"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// parse parses a stirng and converts it into an html.
|
||||
func parse(r io.Reader) *htmlDocument {
|
||||
htmlDoc := &htmlDocument{}
|
||||
tokenizer := html.NewTokenizer(r)
|
||||
for {
|
||||
if errorToken, _, _ := parseToken(tokenizer, htmlDoc, nil); errorToken {
|
||||
break
|
||||
}
|
||||
}
|
||||
return htmlDoc
|
||||
}
|
||||
|
||||
// Function that identifies which tags will be treated as containing preformatted
|
||||
// content. Such tags will have the formatting of all its contents preserved
|
||||
// unchanged.
|
||||
// The opening tag html.Token is passed to this function.
|
||||
// By default, only <pre> and <textarea> tags are considered preformatted.
|
||||
var IsPreformatted = func(token html.Token) bool {
|
||||
return token.Data == "pre" || token.Data == "textarea"
|
||||
}
|
||||
|
||||
func parseToken(tokenizer *html.Tokenizer, htmlDoc *htmlDocument, parent *tagElement) (bool, bool, string) {
|
||||
tokenType := tokenizer.Next()
|
||||
raw := string(tokenizer.Raw())
|
||||
switch tokenType {
|
||||
case html.ErrorToken:
|
||||
return true, false, ""
|
||||
case html.TextToken:
|
||||
text := string(tokenizer.Raw())
|
||||
if strings.TrimSpace(text) == "" && (parent == nil || !parent.isRaw) {
|
||||
break
|
||||
}
|
||||
textElement := &textElement{text: text, parent: parent}
|
||||
appendElement(htmlDoc, parent, textElement)
|
||||
case html.StartTagToken:
|
||||
raw := string(tokenizer.Raw())
|
||||
token := tokenizer.Token()
|
||||
tagElement := &tagElement{
|
||||
tagName: string(token.Data),
|
||||
startTagRaw: raw,
|
||||
isRaw: IsPreformatted(token) || (parent != nil && parent.isRaw),
|
||||
parent: parent,
|
||||
}
|
||||
appendElement(htmlDoc, parent, tagElement)
|
||||
for {
|
||||
errorToken, parentEnded, unsetEndTag := parseToken(tokenizer, htmlDoc, tagElement)
|
||||
if errorToken {
|
||||
return true, false, ""
|
||||
}
|
||||
if parentEnded {
|
||||
if unsetEndTag != "" {
|
||||
return false, false, unsetEndTag
|
||||
}
|
||||
break
|
||||
}
|
||||
if unsetEndTag != "" {
|
||||
tagName := setEndTagRaw(tokenizer, tagElement, unsetEndTag)
|
||||
return false, false, tagName
|
||||
}
|
||||
}
|
||||
case html.EndTagToken:
|
||||
tagName := setEndTagRaw(tokenizer, parent, getTagName(tokenizer))
|
||||
return false, true, tagName
|
||||
case html.DoctypeToken, html.CommentToken:
|
||||
tagElement := &tagElement{
|
||||
tagName: getTagName(tokenizer),
|
||||
startTagRaw: string(tokenizer.Raw()),
|
||||
isRaw: parent != nil && parent.isRaw,
|
||||
parent: parent,
|
||||
}
|
||||
appendElement(htmlDoc, parent, tagElement)
|
||||
case html.SelfClosingTagToken:
|
||||
tagElement := &tagElement{
|
||||
tagName: getTagName(tokenizer),
|
||||
startTagRaw: raw,
|
||||
isRaw: parent != nil && parent.isRaw,
|
||||
parent: parent,
|
||||
}
|
||||
appendElement(htmlDoc, parent, tagElement)
|
||||
}
|
||||
return false, false, ""
|
||||
}
|
||||
|
||||
// appendElement appends the element to the htmlDocument or parent tagElement.
|
||||
func appendElement(htmlDoc *htmlDocument, parent *tagElement, e element) {
|
||||
if parent != nil {
|
||||
parent.appendChild(e)
|
||||
} else {
|
||||
htmlDoc.append(e)
|
||||
}
|
||||
}
|
||||
|
||||
// getTagName gets a tagName from tokenizer.
|
||||
func getTagName(tokenizer *html.Tokenizer) string {
|
||||
tagName, _ := tokenizer.TagName()
|
||||
return string(tagName)
|
||||
}
|
||||
|
||||
// setEndTagRaw sets an endTagRaw to the parent.
|
||||
func setEndTagRaw(tokenizer *html.Tokenizer, parent *tagElement, tagName string) string {
|
||||
if parent != nil && parent.tagName == tagName {
|
||||
parent.endTagRaw = `</` + fMustCompile(parent.startTagRaw) + `>` //string(tokenizer.Raw())
|
||||
return ""
|
||||
}
|
||||
return tagName
|
||||
}
|
||||
|
||||
func fMustCompile(input string) (result string) {
|
||||
re := regexp.MustCompile(`<([A-Za-z-]+)[\s\S]*?>`)
|
||||
match := re.FindStringSubmatch(input)
|
||||
|
||||
if len(match) > 1 {
|
||||
result = match[1]
|
||||
} else {
|
||||
result = input
|
||||
}
|
||||
return
|
||||
}
|
145
server/internal/library/hggen/views/gohtml/tag_element.go
Normal file
145
server/internal/library/hggen/views/gohtml/tag_element.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package gohtml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// A tagElement represents a tag element of an HTML document.
|
||||
type tagElement struct {
|
||||
tagName string
|
||||
startTagRaw string
|
||||
endTagRaw string
|
||||
|
||||
parent *tagElement
|
||||
children []element
|
||||
|
||||
isRaw bool
|
||||
isChildrenInlineCache *bool
|
||||
}
|
||||
|
||||
// Enable condensing a tag with only inline children onto a single line, or
|
||||
// completely inlining it with sibling nodes.
|
||||
// Tags to be treated as inline can be set in `InlineTags`.
|
||||
// Only inline tags will be completely inlined, while other condensable tags
|
||||
// will be given their own dedicated (single) line.
|
||||
var Condense bool
|
||||
|
||||
// Tags that are considered inline tags.
|
||||
// Note: Text nodes are always considered to be inline
|
||||
var InlineTags = map[string]bool{
|
||||
"a": true,
|
||||
"code": true,
|
||||
"em": true,
|
||||
"span": true,
|
||||
"strong": true,
|
||||
}
|
||||
|
||||
// Maximum length of an opening inline tag before it's un-inlined
|
||||
var InlineTagMaxLength = 40
|
||||
|
||||
func (e *tagElement) isInline() bool {
|
||||
if e.isRaw || !InlineTags[e.tagName] || len(e.startTagRaw) > InlineTagMaxLength {
|
||||
return false
|
||||
}
|
||||
return e.isChildrenInline()
|
||||
}
|
||||
|
||||
func (e *tagElement) isChildrenInline() bool {
|
||||
if !Condense {
|
||||
return false
|
||||
}
|
||||
if e.isChildrenInlineCache != nil {
|
||||
return *e.isChildrenInlineCache
|
||||
}
|
||||
|
||||
isInline := true
|
||||
for _, child := range e.children {
|
||||
isInline = isInline && child.isInline()
|
||||
}
|
||||
|
||||
e.isChildrenInlineCache = &isInline
|
||||
return isInline
|
||||
}
|
||||
|
||||
// write writes a tag to the buffer.
|
||||
func (e *tagElement) write(bf *formattedBuffer, isPreviousNodeInline bool) bool {
|
||||
if e.isRaw {
|
||||
if e.parent != nil && !e.parent.isRaw {
|
||||
bf.writeLineFeed()
|
||||
bf.writeIndent()
|
||||
bf.rawMode = true
|
||||
defer func() {
|
||||
bf.rawMode = false
|
||||
}()
|
||||
}
|
||||
bf.writeToken(e.startTagRaw, formatterTokenType_Tag)
|
||||
for _, child := range e.children {
|
||||
child.write(bf, true)
|
||||
}
|
||||
bf.writeToken(e.endTagRaw, formatterTokenType_Tag)
|
||||
return false
|
||||
}
|
||||
|
||||
if e.isChildrenInline() && (e.endTagRaw != "" || e.isInline()) {
|
||||
// Write the condensed output to a separate buffer, in case it doesn't work out
|
||||
condensedBuffer := *bf
|
||||
condensedBuffer.buffer = &bytes.Buffer{}
|
||||
|
||||
if bf.buffer.Len() > 0 && (!isPreviousNodeInline || !e.isInline()) {
|
||||
condensedBuffer.writeLineFeed()
|
||||
}
|
||||
condensedBuffer.writeToken(e.startTagRaw, formatterTokenType_Tag)
|
||||
if !isPreviousNodeInline && e.endTagRaw != "" {
|
||||
condensedBuffer.indentLevel++
|
||||
}
|
||||
|
||||
for _, child := range e.children {
|
||||
child.write(&condensedBuffer, true)
|
||||
}
|
||||
if e.endTagRaw != "" {
|
||||
condensedBuffer.writeToken(e.endTagRaw, formatterTokenType_Tag)
|
||||
if !isPreviousNodeInline {
|
||||
condensedBuffer.indentLevel--
|
||||
}
|
||||
}
|
||||
|
||||
if e.isInline() || bytes.IndexAny(condensedBuffer.buffer.Bytes()[1:], "\n") == -1 {
|
||||
// If we're an inline tag, or there were no newlines were in the buffer,
|
||||
// replace the original with the condensed version
|
||||
condensedBuffer.buffer = bytes.NewBuffer(bytes.Join([][]byte{
|
||||
bf.buffer.Bytes(), condensedBuffer.buffer.Bytes(),
|
||||
}, []byte{}))
|
||||
*bf = condensedBuffer
|
||||
|
||||
return e.isInline()
|
||||
}
|
||||
}
|
||||
|
||||
if bf.buffer.Len() > 0 {
|
||||
bf.writeLineFeed()
|
||||
}
|
||||
bf.writeToken(e.startTagRaw, formatterTokenType_Tag)
|
||||
if e.endTagRaw != "" {
|
||||
bf.indentLevel++
|
||||
}
|
||||
|
||||
isPreviousNodeInline = false
|
||||
for _, child := range e.children {
|
||||
isPreviousNodeInline = child.write(bf, isPreviousNodeInline)
|
||||
}
|
||||
|
||||
if e.endTagRaw != "" {
|
||||
if len(e.children) > 0 {
|
||||
bf.writeLineFeed()
|
||||
}
|
||||
bf.indentLevel--
|
||||
bf.writeToken(e.endTagRaw, formatterTokenType_Tag)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// appendChild append an element to the element's children.
|
||||
func (e *tagElement) appendChild(child element) {
|
||||
e.children = append(e.children, child)
|
||||
}
|
43
server/internal/library/hggen/views/gohtml/text_element.go
Normal file
43
server/internal/library/hggen/views/gohtml/text_element.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package gohtml
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A textElement represents a text element of an HTML document.
|
||||
type textElement struct {
|
||||
text string
|
||||
parent *tagElement
|
||||
}
|
||||
|
||||
func (e *textElement) isInline() bool {
|
||||
// Text nodes are always considered to be inline
|
||||
return true
|
||||
}
|
||||
|
||||
// write writes a text to the buffer.
|
||||
func (e *textElement) write(bf *formattedBuffer, isPreviousNodeInline bool) bool {
|
||||
text := unifyLineFeed(e.text)
|
||||
if e.parent != nil && e.parent.isRaw {
|
||||
bf.writeToken(text, formatterTokenType_Text)
|
||||
return true
|
||||
}
|
||||
|
||||
if !isPreviousNodeInline {
|
||||
bf.writeLineFeed()
|
||||
}
|
||||
|
||||
// Collapse leading and trailing spaces
|
||||
text = regexp.MustCompile(`^\s+|\s+$`).ReplaceAllString(text, " ")
|
||||
lines := strings.Split(text, "\n")
|
||||
for l, line := range lines {
|
||||
if l > 0 {
|
||||
bf.writeLineFeed()
|
||||
}
|
||||
for _, word := range strings.Split(line, " ") {
|
||||
bf.writeToken(word, formatterTokenType_Text)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
101
server/internal/library/hggen/views/gohtml/utils.go
Normal file
101
server/internal/library/hggen/views/gohtml/utils.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package gohtml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type formatterTokenType int
|
||||
|
||||
const (
|
||||
formatterTokenType_Nothing formatterTokenType = iota
|
||||
formatterTokenType_Tag
|
||||
formatterTokenType_Text
|
||||
)
|
||||
|
||||
type formattedBuffer struct {
|
||||
buffer *bytes.Buffer
|
||||
rawMode bool
|
||||
|
||||
indentString string
|
||||
indentLevel int
|
||||
|
||||
lineWrapColumn int
|
||||
lineWrapMaxSpillover int
|
||||
|
||||
curLineLength int
|
||||
prevTokenType formatterTokenType
|
||||
}
|
||||
|
||||
func (bf *formattedBuffer) writeLineFeed() {
|
||||
if !bf.rawMode {
|
||||
// Strip trailing newlines
|
||||
bf.buffer = bytes.NewBuffer(bytes.TrimRightFunc(
|
||||
bf.buffer.Bytes(),
|
||||
func(r rune) bool {
|
||||
return r != '\n' && unicode.IsSpace(r)
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
bf.buffer.WriteString("\n")
|
||||
bf.curLineLength = 0
|
||||
bf.prevTokenType = formatterTokenType_Nothing
|
||||
}
|
||||
|
||||
func (bf *formattedBuffer) writeIndent() {
|
||||
bf.buffer.WriteString(strings.Repeat(bf.indentString, bf.indentLevel))
|
||||
bf.curLineLength += len(bf.indentString) * bf.indentLevel
|
||||
}
|
||||
|
||||
func (bf *formattedBuffer) writeToken(token string, kind formatterTokenType) {
|
||||
if bf.rawMode {
|
||||
bf.buffer.WriteString(token)
|
||||
bf.curLineLength += len(token)
|
||||
return
|
||||
}
|
||||
|
||||
if bf.prevTokenType == formatterTokenType_Nothing && strings.TrimSpace(token) == "" {
|
||||
// It's a whitespace token, but we already have indentation which functions
|
||||
// the same, so we ignore it
|
||||
return
|
||||
}
|
||||
|
||||
toWrite := token
|
||||
if kind == formatterTokenType_Text && bf.prevTokenType == formatterTokenType_Text {
|
||||
toWrite = " " + token
|
||||
}
|
||||
|
||||
if bf.prevTokenType != formatterTokenType_Nothing && bf.lineWrapColumn > 0 {
|
||||
switch {
|
||||
case bf.curLineLength > bf.lineWrapColumn:
|
||||
// Current line is too long
|
||||
fallthrough
|
||||
|
||||
case bf.curLineLength+len(toWrite) > bf.lineWrapColumn+bf.lineWrapMaxSpillover:
|
||||
// Current line + new token is too long even with allowed spillover
|
||||
fallthrough
|
||||
|
||||
case bf.curLineLength+len(toWrite) > bf.lineWrapColumn &&
|
||||
bf.curLineLength > bf.lineWrapColumn-bf.lineWrapMaxSpillover:
|
||||
// Current line + new token is too long and doesn't quality for spillover
|
||||
|
||||
bf.writeLineFeed()
|
||||
bf.writeToken(token, kind)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if bf.curLineLength == 0 {
|
||||
bf.writeIndent()
|
||||
}
|
||||
bf.buffer.WriteString(toWrite)
|
||||
bf.curLineLength += len(toWrite)
|
||||
bf.prevTokenType = kind
|
||||
}
|
||||
|
||||
// unifyLineFeed unifies line feeds.
|
||||
func unifyLineFeed(s string) string {
|
||||
return strings.Replace(strings.Replace(s, "\r\n", "\n", -1), "\r", "\n", -1)
|
||||
}
|
33
server/internal/library/hggen/views/gohtml/writer.go
Normal file
33
server/internal/library/hggen/views/gohtml/writer.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package gohtml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
// A Writer represents a formatted HTML source codes writer.
|
||||
type Writer struct {
|
||||
writer io.Writer
|
||||
lastElement string
|
||||
bf *bytes.Buffer
|
||||
}
|
||||
|
||||
// SetLastElement set the lastElement to the Writer.
|
||||
func (wr *Writer) SetLastElement(lastElement string) *Writer {
|
||||
wr.lastElement = lastElement
|
||||
return wr
|
||||
}
|
||||
|
||||
// Write writes the parameter.
|
||||
func (wr *Writer) Write(p []byte) (n int, err error) {
|
||||
n, _ = wr.bf.Write(p) // (*bytes.Buffer).Write never produces an error
|
||||
if bytes.HasSuffix(p, []byte(wr.lastElement)) {
|
||||
_, err = wr.writer.Write([]byte(Format(wr.bf.String()) + "\n"))
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// NewWriter generates a Writer and returns it.
|
||||
func NewWriter(wr io.Writer) *Writer {
|
||||
return &Writer{writer: wr, lastElement: defaultLastElement, bf: &bytes.Buffer{}}
|
||||
}
|
@@ -7,6 +7,7 @@ package views
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
@@ -14,11 +15,18 @@ import (
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"golang.org/x/tools/imports"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/hggen/views/gohtml"
|
||||
"hotgo/internal/model"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/utility/convert"
|
||||
"hotgo/utility/simple"
|
||||
"hotgo/utility/validate"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// parseServFunName 解析业务服务名称
|
||||
@@ -30,19 +38,6 @@ func (l *gCurd) parseServFunName(templateGroup, varName string) string {
|
||||
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 IsIndexPK(field.Index) {
|
||||
return field
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// hasEffectiveJoin 存在有效的关联表
|
||||
func hasEffectiveJoins(joins []*CurdOptionsJoin) bool {
|
||||
for _, join := range joins {
|
||||
@@ -220,3 +215,107 @@ func ParseDBConfigNodeLink(node *gdb.ConfigNode) *gdb.ConfigNode {
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// ImportWebMethod 导入前端方法
|
||||
func ImportWebMethod(vs []string) string {
|
||||
vs = convert.UniqueSlice(vs)
|
||||
str := "{ " + strings.Join(vs, ", ") + " }"
|
||||
str = strings.TrimSuffix(str, ", ")
|
||||
return str
|
||||
}
|
||||
|
||||
// CheckTreeTableFields 检查树表字段
|
||||
func CheckTreeTableFields(columns []*sysin.GenCodesColumnListModel) (err error) {
|
||||
var fields = []string{"pid", "level", "tree"}
|
||||
for _, v := range columns {
|
||||
if validate.InSlice(fields, v.Name) {
|
||||
fields = convert.RemoveSlice(fields, v.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(fields) > 0 {
|
||||
err = gerror.Newf("树表必须包含[%v]字段", strings.Join(fields, "、"))
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CheckIllegalName 检查命名是否合理
|
||||
func CheckIllegalName(errPrefix string, names ...string) (err error) {
|
||||
for _, name := range names {
|
||||
name = strings.ToLower(name)
|
||||
match, _ := regexp.MatchString("^[a-z_][a-z0-9_]*$", name)
|
||||
if !match {
|
||||
err = gerror.Newf("%v存在格式不正确,必须全部小写且由字母、数字和下划线组成:%v", errPrefix, name)
|
||||
return
|
||||
}
|
||||
if strings.HasSuffix(name, "test") {
|
||||
err = gerror.Newf("%v当中不能以`test`结尾:%v", errPrefix, name)
|
||||
return
|
||||
}
|
||||
if StartsWithDigit(name) {
|
||||
err = gerror.Newf("%v当中不能以阿拉伯数字开头:%v", errPrefix, name)
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func StartsWithDigit(s string) bool {
|
||||
r := []rune(s)
|
||||
if len(r) > 0 {
|
||||
return unicode.IsDigit(r[0])
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsPidName 是否是树表的pid字段
|
||||
func IsPidName(name string) bool {
|
||||
return name == "pid"
|
||||
}
|
||||
|
||||
func ToTSArray(vs []string) string {
|
||||
formattedStrings := make([]string, len(vs))
|
||||
for i, str := range vs {
|
||||
formattedStrings[i] = fmt.Sprintf("'%s'", str)
|
||||
}
|
||||
return fmt.Sprintf("[%s]", strings.Join(formattedStrings, ", "))
|
||||
}
|
||||
|
||||
func FormatGo(ctx context.Context, name, code string) (string, error) {
|
||||
path := GetTempGeneratePath(ctx) + "/" + name
|
||||
if err := gfile.PutContents(path, code); err != nil {
|
||||
return "", err
|
||||
}
|
||||
res, err := imports.Process(path, []byte(code), nil)
|
||||
if err != nil {
|
||||
err = gerror.Newf(`FormatGo error format "%s" go files: %v`, path, err)
|
||||
return "", err
|
||||
}
|
||||
return string(res), nil
|
||||
}
|
||||
|
||||
func FormatVue(code string) string {
|
||||
endTag := `</template>`
|
||||
vueLen := gstr.PosR(code, endTag)
|
||||
vueCode := code[:vueLen+len(endTag)]
|
||||
tsCode := code[vueLen+len(endTag):]
|
||||
vueCode = gohtml.Format(vueCode)
|
||||
tsCode = FormatTs(tsCode)
|
||||
return vueCode + tsCode
|
||||
}
|
||||
|
||||
func FormatTs(code string) string {
|
||||
code = replaceEmptyLinesWithSpace(code)
|
||||
return code + "\n"
|
||||
}
|
||||
|
||||
func replaceEmptyLinesWithSpace(input string) string {
|
||||
re := regexp.MustCompile(`\n\s*\n`)
|
||||
result := re.ReplaceAllString(input, "\n\n")
|
||||
return result
|
||||
}
|
||||
|
||||
func GetTempGeneratePath(ctx context.Context) string {
|
||||
return gfile.Abs(gfile.Temp() + "/hotgo-generate/" + simple.AppName(ctx))
|
||||
}
|
||||
|
@@ -38,6 +38,39 @@ func GenJoinOnRelation(masterTable, masterField, joinTable, alias, onField strin
|
||||
return []string{joinTable, alias, relation}
|
||||
}
|
||||
|
||||
func JoinFields(ctx context.Context, entity interface{}, dao daoInstance, as string) (fs string) {
|
||||
entityFs, err := convert.GetEntityFieldTags(entity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(entityFs) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
fields, err := dao.Ctx(ctx).TableFields(dao.Table())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var columns []string
|
||||
for _, v := range entityFs {
|
||||
if !gstr.HasPrefix(v, as) {
|
||||
continue
|
||||
}
|
||||
|
||||
field := gstr.CaseSnakeFirstUpper(gstr.StrEx(v, as))
|
||||
if _, ok := fields[field]; ok {
|
||||
columns = append(columns, fmt.Sprintf("`%s`.`%s` as `%s`", dao.Table(), field, v))
|
||||
}
|
||||
}
|
||||
|
||||
if len(columns) > 0 {
|
||||
return gstr.Implode(",", convert.UniqueSlice(columns))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GenJoinSelect 生成关联表select
|
||||
// 这里会将实体中的字段驼峰转为下划线于数据库进行匹配,意味着数据库字段必须全部是小写字母+下划线的格式
|
||||
func GenJoinSelect(ctx context.Context, entity interface{}, dao daoInstance, joins []*Join) (allFields string, err error) {
|
||||
|
92
server/internal/library/hgorm/dao_tenant.go
Normal file
92
server/internal/library/hgorm/dao_tenant.go
Normal file
@@ -0,0 +1,92 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/utility/tree"
|
||||
)
|
||||
|
||||
// TenantRelation 租户关系
|
||||
type TenantRelation struct {
|
||||
DeptType string // 部门类型
|
||||
TenantId int64 // 租户ID
|
||||
MerchantId int64 // 商户ID
|
||||
UserId int64 // 用户ID
|
||||
}
|
||||
|
||||
// GetTenantRelation 获取租户关系
|
||||
func GetTenantRelation(ctx context.Context, memberId int64) (tr *TenantRelation, err error) {
|
||||
data, err := g.Model("hg_admin_member u").Ctx(ctx).
|
||||
LeftJoin("hg_admin_dept d", "u.dept_id=d.id").
|
||||
Fields("u.tree,d.type").
|
||||
Where("u.id", memberId).One()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if data.IsEmpty() {
|
||||
err = gerror.Newf("未找到用户[%v]的租户关系,该用户不存在", memberId)
|
||||
return
|
||||
}
|
||||
|
||||
ids := tree.GetIds(data["tree"].String())
|
||||
|
||||
getRelationId := func(deptType string) (int64, error) {
|
||||
id, err := g.Model("hg_admin_member u").Ctx(ctx).
|
||||
LeftJoin("hg_admin_dept d", "u.dept_id=d.id").
|
||||
Fields("u.id").
|
||||
WhereIn("u.id", ids).Where("d.type", deptType).
|
||||
OrderAsc("u.level"). // 确保是第一关系
|
||||
Limit(1).
|
||||
Value()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if id.Int64() < 1 {
|
||||
err = gerror.Newf("未找到有效的租户关系,memberId:%v,deptType:%v", memberId, deptType)
|
||||
return 0, err
|
||||
}
|
||||
return id.Int64(), nil
|
||||
}
|
||||
|
||||
tr = new(TenantRelation)
|
||||
tr.DeptType = data["type"].String()
|
||||
switch tr.DeptType {
|
||||
// 公司
|
||||
case consts.DeptTypeCompany:
|
||||
return
|
||||
// 租户
|
||||
case consts.DeptTypeTenant:
|
||||
tr.TenantId = memberId
|
||||
|
||||
// 商户
|
||||
case consts.DeptTypeMerchant:
|
||||
tr.TenantId, err = getRelationId(consts.DeptTypeTenant)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tr.MerchantId = memberId
|
||||
// 用户
|
||||
case consts.DeptTypeUser:
|
||||
tr.TenantId, err = getRelationId(consts.DeptTypeTenant)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tr.MerchantId, err = getRelationId(consts.DeptTypeMerchant)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tr.UserId = memberId
|
||||
default:
|
||||
err = gerror.Newf("未找到用户[%]的租户关系,部门类型[%v] 无效", memberId, tr.DeptType)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
@@ -13,6 +13,7 @@ import (
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/model/entity"
|
||||
"hotgo/utility/convert"
|
||||
"hotgo/utility/tree"
|
||||
)
|
||||
|
||||
@@ -22,7 +23,7 @@ func FilterAuth(m *gdb.Model) *gdb.Model {
|
||||
var (
|
||||
needAuth bool
|
||||
filterField string
|
||||
fields = escapeFieldsToSlice(m.GetFieldsStr())
|
||||
fields = convert.EscapeFieldsToSlice(m.GetFieldsStr())
|
||||
)
|
||||
|
||||
// 优先级:created_by > member_id
|
||||
@@ -99,11 +100,6 @@ func FilterAuthWithField(filterField string) func(m *gdb.Model) *gdb.Model {
|
||||
}
|
||||
}
|
||||
|
||||
// escapeFieldsToSlice 将转义过的字段转换为字段集切片
|
||||
func escapeFieldsToSlice(s string) []string {
|
||||
return gstr.Explode(",", gstr.Replace(gstr.Replace(s, "`,`", ","), "`", ""))
|
||||
}
|
||||
|
||||
// GetDeptAndSub 获取指定部门的所有下级,含本部门
|
||||
func GetDeptAndSub(ctx context.Context, deptId int64) (ids []int64) {
|
||||
array, err := g.Model("admin_dept").
|
||||
|
@@ -3,7 +3,6 @@
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package handler
|
||||
|
||||
// handler.
|
||||
@@ -13,8 +12,9 @@ import (
|
||||
|
||||
// Option 预处理选项
|
||||
type Option struct {
|
||||
FilterAuth bool // 过滤权限
|
||||
ForceCache bool // 强制缓存
|
||||
FilterAuth bool // 过滤权限
|
||||
ForceCache bool // 强制缓存
|
||||
FilterTenant bool // 过滤多租户权限
|
||||
}
|
||||
|
||||
// DefaultOption 默认预处理选项
|
||||
@@ -35,5 +35,8 @@ func Model(m *gdb.Model, opt ...*Option) *gdb.Model {
|
||||
if option.ForceCache {
|
||||
m = m.Handler(ForceCache)
|
||||
}
|
||||
if option.FilterTenant {
|
||||
m = m.Handler(FilterTenant)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
49
server/internal/library/hgorm/handler/tenant.go
Normal file
49
server/internal/library/hgorm/handler/tenant.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Package handler
|
||||
// @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 handler
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/utility/convert"
|
||||
)
|
||||
|
||||
// FilterTenant 过滤多租户数据权限
|
||||
// 根据部门类型识别当前租户、商户、用户身份,过滤只属于自己的数据
|
||||
func FilterTenant(m *gdb.Model) *gdb.Model {
|
||||
var (
|
||||
needAuth bool
|
||||
filterField string
|
||||
fields = convert.EscapeFieldsToSlice(m.GetFieldsStr())
|
||||
ctx = m.GetCtx()
|
||||
)
|
||||
|
||||
// 租户
|
||||
if contexts.IsTenantDept(ctx) && gstr.InArray(fields, "tenant_id") {
|
||||
needAuth = true
|
||||
filterField = "tenant_id"
|
||||
}
|
||||
|
||||
// 商户
|
||||
if contexts.IsMerchantDept(ctx) && gstr.InArray(fields, "merchant_id") {
|
||||
needAuth = true
|
||||
filterField = "merchant_id"
|
||||
}
|
||||
|
||||
// 用户
|
||||
if contexts.IsUserDept(ctx) && gstr.InArray(fields, "user_id") {
|
||||
needAuth = true
|
||||
filterField = "user_id"
|
||||
}
|
||||
|
||||
if !needAuth {
|
||||
return m
|
||||
}
|
||||
|
||||
m = m.Where(filterField, contexts.GetUserId(ctx))
|
||||
return m
|
||||
}
|
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"hotgo/utility/convert"
|
||||
)
|
||||
|
||||
// MemberInfo 后台用户信息
|
||||
@@ -61,3 +62,76 @@ var MemberInfo = gdb.HookHandler{
|
||||
return
|
||||
},
|
||||
}
|
||||
|
||||
type MemberSumma struct {
|
||||
Id int64 `json:"id" description:"管理员ID"`
|
||||
RealName string `json:"realName" description:"真实姓名"`
|
||||
Username string `json:"username" description:"帐号"`
|
||||
Avatar string `json:"avatar" description:"头像"`
|
||||
}
|
||||
|
||||
// MemberSummary 操作人摘要信息
|
||||
var MemberSummary = gdb.HookHandler{
|
||||
Select: func(ctx context.Context, in *gdb.HookSelectInput) (result gdb.Result, err error) {
|
||||
result, err = in.Next(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
createdByIds []int64
|
||||
updatedByIds []int64
|
||||
memberIds []int64
|
||||
)
|
||||
|
||||
for _, record := range result {
|
||||
if record["created_by"].Int64() > 0 {
|
||||
createdByIds = append(createdByIds, record["created_by"].Int64())
|
||||
}
|
||||
if record["updated_by"].Int64() > 0 {
|
||||
updatedByIds = append(updatedByIds, record["updated_by"].Int64())
|
||||
}
|
||||
if record["member_id"].Int64() > 0 {
|
||||
memberIds = append(memberIds, record["member_id"].Int64())
|
||||
}
|
||||
}
|
||||
|
||||
memberIds = append(memberIds, createdByIds...)
|
||||
memberIds = append(memberIds, updatedByIds...)
|
||||
memberIds = convert.UniqueSlice(memberIds)
|
||||
if len(memberIds) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var members []*MemberSumma
|
||||
if err = g.Model("admin_member").Ctx(ctx).WhereIn("id", memberIds).Scan(&members); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(members) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
findMember := func(id *gvar.Var) *MemberSumma {
|
||||
for _, v := range members {
|
||||
if v.Id == id.Int64() {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, record := range result {
|
||||
if record["created_by"].Int64() > 0 {
|
||||
record["createdBySumma"] = gvar.New(findMember(record["created_by"]))
|
||||
}
|
||||
if record["updated_by"].Int64() > 0 {
|
||||
record["updatedBySumma"] = gvar.New(findMember(record["updated_by"]))
|
||||
}
|
||||
if record["member_id"].Int64() > 0 {
|
||||
record["memberBySumma"] = gvar.New(findMember(record["member_id"]))
|
||||
}
|
||||
}
|
||||
return
|
||||
},
|
||||
}
|
||||
|
288
server/internal/library/hgorm/hook/tenant.go
Normal file
288
server/internal/library/hgorm/hook/tenant.go
Normal file
@@ -0,0 +1,288 @@
|
||||
// Package hook
|
||||
// @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 hook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"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"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/library/hgorm"
|
||||
"hotgo/utility/convert"
|
||||
)
|
||||
|
||||
// SaveTenant 自动维护更新租户关系字段
|
||||
// 根据部门类型识别当前租户、商户、用户身份
|
||||
var SaveTenant = gdb.HookHandler{
|
||||
Insert: func(ctx context.Context, in *gdb.HookInsertInput) (result sql.Result, err error) {
|
||||
h, err := newHookSaveTenant(ctx, in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.handle()
|
||||
},
|
||||
Update: func(ctx context.Context, in *gdb.HookUpdateInput) (result sql.Result, err error) {
|
||||
h, err := newHookSaveTenant(ctx, in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.handle()
|
||||
},
|
||||
}
|
||||
|
||||
type hookSaveTenant struct {
|
||||
ctx context.Context
|
||||
in any
|
||||
isNewRecord bool
|
||||
relations map[int64]*hgorm.TenantRelation
|
||||
}
|
||||
|
||||
func newHookSaveTenant(ctx context.Context, in any) (*hookSaveTenant, error) {
|
||||
h := new(hookSaveTenant)
|
||||
h.ctx = ctx
|
||||
h.in = in
|
||||
_, h.isNewRecord = in.(*gdb.HookInsertInput)
|
||||
h.relations = make(map[int64]*hgorm.TenantRelation)
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// getFields 获取表字段
|
||||
func (h *hookSaveTenant) getFields() []string {
|
||||
if h.isNewRecord {
|
||||
in := h.in.(*gdb.HookInsertInput)
|
||||
return convert.EscapeFieldsToSlice(in.Model.GetFieldsStr())
|
||||
}
|
||||
in := h.in.(*gdb.HookUpdateInput)
|
||||
return convert.EscapeFieldsToSlice(in.Model.GetFieldsStr())
|
||||
}
|
||||
|
||||
// getRelation 获取指定用户的租户关系
|
||||
func (h *hookSaveTenant) getRelation(id int64) (tr *hgorm.TenantRelation, err error) {
|
||||
v, ok := h.relations[id]
|
||||
if ok {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
h.relations[id], err = hgorm.GetTenantRelation(h.ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.relations[id], nil
|
||||
}
|
||||
|
||||
// getData 获取更新数据
|
||||
func (h *hookSaveTenant) getData() any {
|
||||
if h.isNewRecord {
|
||||
in := h.in.(*gdb.HookInsertInput)
|
||||
return in.Data
|
||||
}
|
||||
in := h.in.(*gdb.HookUpdateInput)
|
||||
return in.Data
|
||||
}
|
||||
|
||||
// setData 修改更新数据
|
||||
func (h *hookSaveTenant) setData(data any) {
|
||||
if h.isNewRecord {
|
||||
in := h.in.(*gdb.HookInsertInput)
|
||||
in.Data = data.(gdb.List)
|
||||
return
|
||||
}
|
||||
in := h.in.(*gdb.HookUpdateInput)
|
||||
in.Data = data
|
||||
}
|
||||
|
||||
func (h *hookSaveTenant) next() (result sql.Result, err error) {
|
||||
if h.isNewRecord {
|
||||
in := h.in.(*gdb.HookInsertInput)
|
||||
return in.Next(h.ctx)
|
||||
}
|
||||
in := h.in.(*gdb.HookUpdateInput)
|
||||
return in.Next(h.ctx)
|
||||
}
|
||||
|
||||
// checkRelationConsistent 检查关系是否一致
|
||||
func (h *hookSaveTenant) checkRelationConsistent(tid, mid, uid any) error {
|
||||
var (
|
||||
tenantId = gconv.Int64(tid)
|
||||
merchantId = gconv.Int64(mid)
|
||||
userId = gconv.Int64(uid)
|
||||
)
|
||||
|
||||
// 存在用户,优先用用户开始检查
|
||||
if userId > 0 {
|
||||
tr, err := h.getRelation(userId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tenantId > 0 && tr.TenantId != tenantId {
|
||||
err = gerror.Newf("租户[%v]与用户[%v]关系不匹配", tenantId, userId)
|
||||
return err
|
||||
}
|
||||
if merchantId > 0 && tr.MerchantId != merchantId {
|
||||
err = gerror.Newf("商户[%v]与用户[%v]关系不匹配", merchantId, userId)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if merchantId > 0 {
|
||||
tr, err := h.getRelation(merchantId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tenantId > 0 && tr.TenantId != tenantId {
|
||||
err = gerror.Newf("租户[%v]与商户[%v]关系不匹配", tenantId, userId)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkRelationSingle 检查单个用户关系
|
||||
func (h *hookSaveTenant) checkRelationSingle(idx any, relation, limitType string) (err error) {
|
||||
id := gconv.Int64(idx)
|
||||
if id < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
ok := false
|
||||
tr, err := h.getRelation(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tr.DeptType != limitType {
|
||||
err = gerror.Newf("用户[%v]关系验证不通过,类型身份不匹配[%v != %v]", id, tr.DeptType, limitType)
|
||||
return
|
||||
}
|
||||
|
||||
relationId := contexts.GetUserId(h.ctx)
|
||||
switch relation {
|
||||
case consts.DeptTypeTenant:
|
||||
if ok = tr.TenantId == relationId; !ok {
|
||||
err = gerror.Newf("%v的租户不是%v", id, relationId)
|
||||
return
|
||||
}
|
||||
case consts.DeptTypeMerchant:
|
||||
if ok = tr.MerchantId == relationId; !ok {
|
||||
err = gerror.Newf("%v的商户不是%v", id, relationId)
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 检查用户关系是否有效
|
||||
func (h *hookSaveTenant) checkRelation(deptType string, data gdb.Map) (err error) {
|
||||
switch deptType {
|
||||
// 公司和用户,检查关系是否一致
|
||||
case consts.DeptTypeCompany, consts.DeptTypeUser:
|
||||
if err = h.checkRelationConsistent(data[consts.TenantId], data[consts.MerchantId], data[consts.UserId]); err != nil {
|
||||
return
|
||||
}
|
||||
// 租户,检查商户和用户是否属于自己
|
||||
case consts.DeptTypeTenant:
|
||||
if err = h.checkRelationSingle(data[consts.MerchantId], consts.DeptTypeTenant, consts.DeptTypeMerchant); err != nil {
|
||||
return
|
||||
}
|
||||
if err = h.checkRelationSingle(data[consts.UserId], consts.DeptTypeTenant, consts.DeptTypeUser); err != nil {
|
||||
return
|
||||
}
|
||||
// 商户,检查用户是否属于自己
|
||||
case consts.DeptTypeMerchant:
|
||||
if err = h.checkRelationSingle(data[consts.UserId], consts.DeptTypeMerchant, consts.DeptTypeUser); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (h *hookSaveTenant) handle() (result sql.Result, err error) {
|
||||
var (
|
||||
update = make(g.Map)
|
||||
fields = h.getFields()
|
||||
memberId = contexts.GetUserId(h.ctx)
|
||||
deptType = contexts.GetDeptType(h.ctx)
|
||||
tr *hgorm.TenantRelation
|
||||
)
|
||||
|
||||
if memberId == 0 || len(deptType) == 0 {
|
||||
err = gerror.New("缺少用户上下文数据")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 非公司类型,加载自己的租户关系,用于重写关系
|
||||
if !contexts.IsCompanyDept(h.ctx) {
|
||||
tr, err = h.getRelation(memberId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
switch deptType {
|
||||
// 公司
|
||||
case consts.DeptTypeCompany:
|
||||
|
||||
// 租户
|
||||
case consts.DeptTypeTenant:
|
||||
if gstr.InArray(fields, consts.TenantId) {
|
||||
update[consts.TenantId] = tr.TenantId
|
||||
}
|
||||
|
||||
// 商户
|
||||
case consts.DeptTypeMerchant:
|
||||
if gstr.InArray(fields, consts.TenantId) {
|
||||
update[consts.TenantId] = tr.TenantId
|
||||
}
|
||||
if gstr.InArray(fields, consts.MerchantId) {
|
||||
update[consts.MerchantId] = tr.MerchantId
|
||||
}
|
||||
|
||||
// 用户
|
||||
case consts.DeptTypeUser:
|
||||
if gstr.InArray(fields, consts.TenantId) {
|
||||
update[consts.TenantId] = tr.TenantId
|
||||
}
|
||||
if gstr.InArray(fields, consts.MerchantId) {
|
||||
update[consts.MerchantId] = tr.MerchantId
|
||||
}
|
||||
if gstr.InArray(fields, consts.UserId) {
|
||||
update[consts.UserId] = tr.UserId
|
||||
}
|
||||
default:
|
||||
err = gerror.Newf("当前用户部门类型[%v] 找到有效的hook,请检查!", deptType)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch value := h.getData().(type) {
|
||||
case gdb.List:
|
||||
for i, data := range value {
|
||||
if err = h.checkRelation(deptType, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range update {
|
||||
data[k] = v
|
||||
}
|
||||
value[i] = data
|
||||
}
|
||||
h.setData(value)
|
||||
case gdb.Map:
|
||||
if err = h.checkRelation(deptType, value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range update {
|
||||
value[k] = v
|
||||
}
|
||||
h.setData(value)
|
||||
}
|
||||
return h.next()
|
||||
}
|
67
server/internal/library/hgorm/hook/tenant_test.go
Normal file
67
server/internal/library/hgorm/hook/tenant_test.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Package hook
|
||||
// @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 hook_test
|
||||
|
||||
import (
|
||||
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/util/grand"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/library/hgorm/hook"
|
||||
"hotgo/internal/model"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
g.DB().SetDryRun(true)
|
||||
g.DB().SetDebug(true)
|
||||
}
|
||||
|
||||
// TestSaveTenant_User 以用户身份增改购买订单数据
|
||||
func TestSaveTenant_User(t *testing.T) {
|
||||
|
||||
// 设置上下文用户身份为用户
|
||||
ctx := context.WithValue(gctx.New(), consts.ContextHTTPKey, &model.Context{
|
||||
// 为了测试只设置了hook中需要用到的数据
|
||||
User: &model.Identity{
|
||||
Id: 12,
|
||||
DeptType: consts.DeptTypeUser,
|
||||
},
|
||||
})
|
||||
|
||||
cols := dao.AddonHgexampleTenantOrder.Columns()
|
||||
orderSn := grand.Letters(32)
|
||||
|
||||
// 以用户身份插入购买订单数据,自动维护租户关系字段
|
||||
data := g.Map{
|
||||
//cols.TenantId: 8,
|
||||
//cols.MerchantId: 11,
|
||||
//cols.UserId: 12,
|
||||
cols.ProductName: "无线运动耳机",
|
||||
cols.OrderSn: orderSn,
|
||||
cols.Money: 99,
|
||||
cols.Status: consts.PayStatusWait,
|
||||
}
|
||||
|
||||
_, err := dao.AddonHgexampleTenantOrder.Ctx(ctx).Data(data).Hook(hook.SaveTenant).Insert()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 以用户身份插入修改订单数据,自动维护租户关系字段
|
||||
update := g.Map{
|
||||
cols.Status: consts.PayStatusOk,
|
||||
}
|
||||
|
||||
_, err = dao.AddonHgexampleTenantOrder.Ctx(ctx).Where(cols.OrderSn, orderSn).Data(update).Hook(hook.SaveTenant).Update()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
@@ -168,6 +168,7 @@ func (l *Lock) startWatchDog() {
|
||||
}
|
||||
case <-l.watchDog:
|
||||
// 已经解锁
|
||||
l.wg.Done()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@@ -6,9 +6,12 @@
|
||||
package lock_test
|
||||
|
||||
import (
|
||||
_ "github.com/gogf/gf/contrib/nosql/redis/v2"
|
||||
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"hotgo/internal/library/hgrds/lock"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -24,21 +27,43 @@ func TestDefaultLock(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Log("A加锁成功")
|
||||
time.Sleep(lock.DefaultTTL)
|
||||
err = l.Unlock(context.Background())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Log("A已释放锁")
|
||||
}()
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
l := lock.Mutex("test")
|
||||
err := l.TryLock(context.Background())
|
||||
if err != nil && !gerror.Is(err, lock.ErrLockFailed) {
|
||||
t.Error(err)
|
||||
for {
|
||||
l := lock.Mutex("test")
|
||||
err := l.TryLock(context.Background())
|
||||
if err == nil {
|
||||
t.Log("B加锁成功")
|
||||
|
||||
// 等待1s,模拟业务
|
||||
time.Sleep(time.Second)
|
||||
|
||||
err = l.Unlock(context.Background())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Log("B已释放锁")
|
||||
break
|
||||
}
|
||||
|
||||
if gerror.Is(err, lock.ErrLockFailed) {
|
||||
t.Log("B加锁失败,等待1s重试...")
|
||||
time.Sleep(time.Second)
|
||||
} else {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
@@ -116,3 +141,12 @@ func TestNewLock2(t *testing.T) {
|
||||
t.Errorf("count = %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Fix_watchDogMemoryLeak(t *testing.T) {
|
||||
i := 0
|
||||
for i < 5 {
|
||||
TestDefaultLock(t)
|
||||
t.Log("current goroutine num:", runtime.NumGoroutine())
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
45
server/internal/library/location/cache.go
Normal file
45
server/internal/library/location/cache.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Package location
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2024 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package location
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/container/gmap"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type IpCache struct {
|
||||
sync.Mutex
|
||||
data *gmap.Map
|
||||
}
|
||||
|
||||
var (
|
||||
cache = &IpCache{
|
||||
data: gmap.New(true),
|
||||
}
|
||||
)
|
||||
|
||||
func (c *IpCache) Contains(ip string) bool {
|
||||
return c.data.Contains(ip)
|
||||
}
|
||||
|
||||
func (c *IpCache) SetIpCache(ip string, data *IpLocationData) {
|
||||
if c.data.Size() > 10000 {
|
||||
c.data.Pops(2000)
|
||||
}
|
||||
c.data.Set(ip, data)
|
||||
}
|
||||
|
||||
func (c *IpCache) GetIpCache(ip string) (*IpLocationData, error) {
|
||||
value := c.data.Get(ip)
|
||||
data1, ok := value.(*IpLocationData)
|
||||
if !ok {
|
||||
c.data.Remove(ip)
|
||||
err := fmt.Errorf("data assertion failed in the cache ip:%v", ip)
|
||||
return nil, err
|
||||
}
|
||||
return data1, nil
|
||||
}
|
@@ -8,8 +8,8 @@ package location
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/container/gmap"
|
||||
"github.com/gogf/gf/v2/encoding/gcharset"
|
||||
"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"
|
||||
@@ -52,14 +52,12 @@ type WhoisRegionData struct {
|
||||
Err string `json:"err"`
|
||||
}
|
||||
|
||||
var cacheMap *gmap.Map
|
||||
|
||||
func init() {
|
||||
cacheMap = gmap.New(true)
|
||||
}
|
||||
var (
|
||||
defaultRetry int64 = 3 // 默认重试次数
|
||||
)
|
||||
|
||||
// WhoisLocation 通过Whois接口查询IP归属地
|
||||
func WhoisLocation(ctx context.Context, ip string) (*IpLocationData, error) {
|
||||
func WhoisLocation(ctx context.Context, ip string, retry ...int64) (*IpLocationData, error) {
|
||||
response, err := g.Client().Timeout(10*time.Second).Get(ctx, whoisApi+ip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -72,8 +70,22 @@ func WhoisLocation(ctx context.Context, ip string) (*IpLocationData, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 利用重试机制缓解高并发情况下限流的影响
|
||||
// 毕竟这是一个免费的接口,如果你对IP归属地定位要求毕竟高,可以考虑换个付费接口
|
||||
if response.StatusCode != 200 {
|
||||
retryCount := defaultRetry
|
||||
if len(retry) > 0 {
|
||||
retryCount = retry[0]
|
||||
}
|
||||
if retryCount > 0 {
|
||||
retryCount--
|
||||
return WhoisLocation(ctx, ip, retryCount)
|
||||
}
|
||||
}
|
||||
|
||||
var who *WhoisRegionData
|
||||
if err = gconv.Struct([]byte(str), &who); err != nil {
|
||||
if err = gconv.Scan([]byte(str), &who); err != nil {
|
||||
err = gerror.Newf("WhoisLocation Scan err:%v, str:%v", err, str)
|
||||
return nil, err
|
||||
}
|
||||
return &IpLocationData{
|
||||
@@ -109,17 +121,6 @@ func Cz88Find(ctx context.Context, ip string) (*IpLocationData, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IsJurisByIpTitle 判断地区名称是否为直辖市
|
||||
func IsJurisByIpTitle(title string) bool {
|
||||
lists := []string{"北京市", "天津市", "重庆市", "上海市"}
|
||||
for i := 0; i < len(lists); i++ {
|
||||
if gstr.Contains(lists[i], title) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetLocation 获取IP归属地信息
|
||||
func GetLocation(ctx context.Context, ip string) (data *IpLocationData, err error) {
|
||||
if !validate.IsIp(ip) {
|
||||
@@ -130,18 +131,18 @@ func GetLocation(ctx context.Context, ip string) (data *IpLocationData, err erro
|
||||
return // nil, fmt.Errorf("must be a public ip:%v", ip)
|
||||
}
|
||||
|
||||
if cacheMap.Contains(ip) {
|
||||
value := cacheMap.Get(ip)
|
||||
data1, ok := value.(*IpLocationData)
|
||||
if !ok {
|
||||
cacheMap.Remove(ip)
|
||||
err = fmt.Errorf("data assertion failed in the cache ip:%v", ip)
|
||||
return
|
||||
}
|
||||
return data1, nil
|
||||
if cache.Contains(ip) {
|
||||
return cache.GetIpCache(ip)
|
||||
}
|
||||
|
||||
mode := g.Cfg().MustGet(ctx, "hotgo.ipMethod", "cz88").String()
|
||||
cache.Lock()
|
||||
defer cache.Unlock()
|
||||
|
||||
if cache.Contains(ip) {
|
||||
return cache.GetIpCache(ip)
|
||||
}
|
||||
|
||||
mode := g.Cfg().MustGet(ctx, "system.ipMethod", "cz88").String()
|
||||
switch mode {
|
||||
case "whois":
|
||||
data, err = WhoisLocation(ctx, ip)
|
||||
@@ -150,10 +151,7 @@ func GetLocation(ctx context.Context, ip string) (data *IpLocationData, err erro
|
||||
}
|
||||
|
||||
if err == nil && data != nil {
|
||||
if cacheMap.Size() > 20000 {
|
||||
cacheMap.Clear()
|
||||
}
|
||||
cacheMap.Set(ip, data)
|
||||
cache.SetIpCache(ip, data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
72
server/internal/library/location/location_test.go
Normal file
72
server/internal/library/location/location_test.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Package location_test
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2024 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package location_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"hotgo/internal/library/location"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var ip = "120.12.151.65"
|
||||
|
||||
func Test_GetLocation(t *testing.T) {
|
||||
ctx := gctx.New()
|
||||
data, err := location.GetLocation(ctx, ip)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
t.Logf("data:%v", gjson.New(data).String())
|
||||
}
|
||||
|
||||
func Test_ParallelGetLocation(t *testing.T) {
|
||||
ctx := gctx.New()
|
||||
start := gtime.Now()
|
||||
|
||||
t.Log("start")
|
||||
for i := 0; i < 10; i++ {
|
||||
data, err := location.GetLocation(ctx, ip)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
t.Logf("index:%v, data:%v", i, gjson.New(data).String())
|
||||
}
|
||||
|
||||
t.Logf("总耗时:%vs", gtime.Now().Sub(start).Seconds())
|
||||
}
|
||||
|
||||
func Test_ConcurrentGetLocation(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
ctx := gctx.New()
|
||||
start := gtime.Now()
|
||||
var wg sync.WaitGroup
|
||||
|
||||
t.Log("start")
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
index := i
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
data, err := location.GetLocation(ctx, ip)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
t.Logf("index:%v, data:%v", index, gjson.New(data).String())
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
t.Logf("总耗时:%vs", gtime.Now().Sub(start).Seconds())
|
||||
})
|
||||
}
|
@@ -48,10 +48,6 @@ type Conn struct {
|
||||
|
||||
var idCounter int64
|
||||
|
||||
var pkgOption = gtcp.PkgOption{
|
||||
MaxDataSize: 0x7FFFFFFF,
|
||||
}
|
||||
|
||||
func NewConn(conn *gtcp.Conn, logger *glog.Logger, msgParser *MsgParser) *Conn {
|
||||
tcpConn := new(Conn)
|
||||
tcpConn.CID = atomic.AddInt64(&idCounter, 1)
|
||||
|
@@ -30,6 +30,6 @@ func ConsumerLog(ctx context.Context, topic string, mqMsg MqMsg, err error) {
|
||||
// ProducerLog 生产日志
|
||||
func ProducerLog(ctx context.Context, topic string, mqMsg MqMsg, err error) {
|
||||
if err != nil {
|
||||
Logger().Errorf(ctx, ProducerLogErrFormat, topic, string(mqMsg.Body), err)
|
||||
Logger().Infof(ctx, ProducerLogErrFormat, topic, string(mqMsg.Body), err)
|
||||
}
|
||||
}
|
||||
|
@@ -21,12 +21,16 @@ func Push(topic string, data interface{}) (err error) {
|
||||
}
|
||||
|
||||
// DelayPush 推送延迟队列
|
||||
func DelayPush(topic string, data interface{}, second int64) (err error) {
|
||||
// redis delay 传入 秒。如:10代表延迟10秒
|
||||
// rocketmq delay 传入 延迟级别。如:2代表延迟5秒
|
||||
// rocketmq reference delay level definition: 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
|
||||
// rocketmq delay level starts from 1. for example, if we set param level=1, then the delay time is 1s.
|
||||
func DelayPush(topic string, data interface{}, delay int64) (err error) {
|
||||
q, err := InstanceProducer()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
mqMsg, err := q.SendDelayMsg(topic, gconv.String(data), second)
|
||||
mqMsg, err := q.SendDelayMsg(topic, gconv.String(data), delay)
|
||||
ProducerLog(ctx, topic, mqMsg, err)
|
||||
return
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ import (
|
||||
type MqProducer interface {
|
||||
SendMsg(topic string, body string) (mqMsg MqMsg, err error)
|
||||
SendByteMsg(topic string, body []byte) (mqMsg MqMsg, err error)
|
||||
SendDelayMsg(topic string, body string, delaySecond int64) (mqMsg MqMsg, err error)
|
||||
SendDelayMsg(topic string, body string, delay int64) (mqMsg MqMsg, err error)
|
||||
}
|
||||
|
||||
type MqConsumer interface {
|
||||
@@ -34,7 +34,6 @@ const (
|
||||
type Config struct {
|
||||
Switch bool `json:"switch"`
|
||||
Driver string `json:"driver"`
|
||||
Retry int `json:"retry"`
|
||||
GroupName string `json:"groupName"`
|
||||
Redis RedisConf
|
||||
Rocketmq RocketmqConf
|
||||
@@ -45,9 +44,14 @@ type Config struct {
|
||||
type RedisConf struct {
|
||||
Timeout int64 `json:"timeout"`
|
||||
}
|
||||
|
||||
type RocketmqConf struct {
|
||||
Address []string `json:"address"`
|
||||
LogLevel string `json:"logLevel"`
|
||||
NameSrvAdders []string `json:"nameSrvAdders"`
|
||||
AccessKey string `json:"accessKey"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
BrokerAddr string `json:"brokerAddr"`
|
||||
Retry int `json:"retry"`
|
||||
LogLevel string `json:"logLevel"`
|
||||
}
|
||||
|
||||
type KafkaConf struct {
|
||||
@@ -106,11 +110,11 @@ func NewProducer(groupName string) (mqClient MqProducer, err error) {
|
||||
|
||||
switch config.Driver {
|
||||
case "rocketmq":
|
||||
if len(config.Rocketmq.Address) == 0 {
|
||||
err = gerror.New("queue rocketmq address is not support")
|
||||
if len(config.Rocketmq.NameSrvAdders) == 0 {
|
||||
err = gerror.New("queue.rocketmq.nameSrvAdders is empty.")
|
||||
return
|
||||
}
|
||||
mqClient, err = RegisterRocketProducer(config.Rocketmq.Address, groupName, config.Retry)
|
||||
mqClient, err = RegisterRocketProducer()
|
||||
case "kafka":
|
||||
if len(config.Kafka.Address) == 0 {
|
||||
err = gerror.New("queue kafka address is not support")
|
||||
@@ -142,7 +146,6 @@ func NewProducer(groupName string) (mqClient MqProducer, err error) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
mqProducerInstanceMap[groupName] = mqClient
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -155,11 +158,11 @@ func NewConsumer(groupName string) (mqClient MqConsumer, err error) {
|
||||
|
||||
switch config.Driver {
|
||||
case "rocketmq":
|
||||
if len(config.Rocketmq.Address) == 0 {
|
||||
err = gerror.New("queue.rocketmq.address is empty.")
|
||||
if len(config.Rocketmq.NameSrvAdders) == 0 {
|
||||
err = gerror.New("queue.rocketmq.nameSrvAdders is empty.")
|
||||
return
|
||||
}
|
||||
mqClient, err = RegisterRocketConsumer(config.Rocketmq.Address, groupName)
|
||||
mqClient, err = RegisterRocketConsumer()
|
||||
case "kafka":
|
||||
if len(config.Kafka.Address) == 0 {
|
||||
err = gerror.New("queue kafka address is not support")
|
||||
@@ -176,7 +179,7 @@ func NewConsumer(groupName string) (mqClient MqConsumer, err error) {
|
||||
return item, nil
|
||||
}
|
||||
|
||||
clientId := "HOTGO-Consumer-" + groupName
|
||||
clientId := "hotgo-consumer-" + groupName
|
||||
if config.Kafka.RandClient {
|
||||
clientId += "-" + randTag
|
||||
}
|
||||
|
@@ -66,7 +66,6 @@ func (r *RedisMq) SendByteMsg(topic string, body []byte) (mqMsg MqMsg, err error
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -119,7 +118,6 @@ func (r *RedisMq) SendDelayMsg(topic string, body string, delaySecond int64) (mq
|
||||
_, _ = conn.Expire(ctx, timePiece, r.timeout+delaySecond)
|
||||
_, _ = conn.Expire(ctx, key, r.timeout)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@@ -8,42 +8,114 @@ package queue
|
||||
import (
|
||||
"context"
|
||||
"github.com/apache/rocketmq-client-go/v2"
|
||||
"github.com/apache/rocketmq-client-go/v2/admin"
|
||||
"github.com/apache/rocketmq-client-go/v2/consumer"
|
||||
"github.com/apache/rocketmq-client-go/v2/primitive"
|
||||
"github.com/apache/rocketmq-client-go/v2/producer"
|
||||
"github.com/apache/rocketmq-client-go/v2/rlog"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/grpool"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/utility/simple"
|
||||
"hotgo/utility/validate"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RocketMq struct {
|
||||
endPoints []string
|
||||
producerIns rocketmq.Producer
|
||||
consumerIns rocketmq.PushConsumer
|
||||
}
|
||||
|
||||
// rewriteLog 重写日志
|
||||
func rewriteLog() {
|
||||
rlog.SetLogger(&RocketMqLogger{Flag: "[rocket_mq]", LevelLog: g.Cfg().MustGet(ctx, "queue.rocketmq.logLevel", "debug").String()})
|
||||
type RocketManager struct {
|
||||
Producer *RocketMq
|
||||
Consumer *RocketMq
|
||||
pMutex sync.Mutex
|
||||
cMutex sync.Mutex
|
||||
goPool *grpool.Pool
|
||||
}
|
||||
|
||||
var rocketManager = &RocketManager{}
|
||||
|
||||
func init() {
|
||||
setRocketCloseEvent()
|
||||
}
|
||||
|
||||
func setRocketCloseEvent() {
|
||||
simple.Event().Register(consts.EventServerClose, func(ctx context.Context, args ...interface{}) {
|
||||
if rocketManager == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if rocketManager.Producer != nil {
|
||||
err := rocketManager.Producer.producerIns.Shutdown()
|
||||
if err != nil {
|
||||
Logger().Warningf(ctx, "rocketmq producer close err:%v", err)
|
||||
return
|
||||
}
|
||||
Logger().Debug(ctx, "rocketmq producer close...")
|
||||
}
|
||||
|
||||
if rocketManager.Consumer != nil {
|
||||
err := rocketManager.Consumer.consumerIns.Shutdown()
|
||||
if err != nil {
|
||||
Logger().Warningf(ctx, "rocketmq consumer close err:%v", err)
|
||||
return
|
||||
}
|
||||
Logger().Debug(ctx, "rocketmq consumer close...")
|
||||
}
|
||||
|
||||
for rocketManager.goPool != nil && rocketManager.goPool.Size() != 0 {
|
||||
g.Log().Debugf(ctx, "waiting for eocketmq consumer to complete execution[%v][%v]...", rocketManager.goPool.Size(), rocketManager.goPool.Jobs())
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func GetRocketManager() *RocketManager {
|
||||
return rocketManager
|
||||
}
|
||||
|
||||
// RegisterRocketProducer 注册并启动生产者接口实现
|
||||
func RegisterRocketProducer(endPoints []string, groupName string, retry int) (client MqProducer, err error) {
|
||||
rewriteLog()
|
||||
client, err = RegisterRocketMqProducer(endPoints, groupName, retry)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
func RegisterRocketProducer() (client MqProducer, err error) {
|
||||
return RegisterRocketMqProducer()
|
||||
}
|
||||
|
||||
// RegisterRocketConsumer 注册消费者
|
||||
func RegisterRocketConsumer(endPoints []string, groupName string) (client MqConsumer, err error) {
|
||||
rewriteLog()
|
||||
client, err = RegisterRocketMqConsumer(endPoints, groupName)
|
||||
if err != nil {
|
||||
func RegisterRocketConsumer() (client MqConsumer, err error) {
|
||||
return RegisterRocketMqConsumer()
|
||||
}
|
||||
|
||||
// createTopicIfNotExists 主题不存在就自动创建
|
||||
func (r *RocketMq) createTopicIfNotExists(topic string) (err error) {
|
||||
if len(config.Rocketmq.BrokerAddr) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
client, err := admin.NewAdmin(
|
||||
admin.WithResolver(primitive.NewPassthroughResolver(config.Rocketmq.NameSrvAdders)),
|
||||
admin.WithCredentials(primitive.Credentials{
|
||||
AccessKey: config.Rocketmq.AccessKey,
|
||||
SecretKey: config.Rocketmq.SecretKey,
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
result, err := client.FetchAllTopicList(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if validate.InSlice(result.TopicList, topic) {
|
||||
return
|
||||
}
|
||||
|
||||
Logger().Debugf(ctx, "create topic:%v", topic)
|
||||
err = client.CreateTopic(ctx, admin.WithTopicCreate(topic), admin.WithBrokerAddrCreate(config.Rocketmq.BrokerAddr))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -58,7 +130,7 @@ func (r *RocketMq) SendByteMsg(topic string, body []byte) (mqMsg MqMsg, err erro
|
||||
return mqMsg, gerror.New("rocketMq producer not register")
|
||||
}
|
||||
|
||||
result, err := r.producerIns.SendSync(context.Background(), &primitive.Message{
|
||||
result, err := r.producerIns.SendSync(ctx, &primitive.Message{
|
||||
Topic: topic,
|
||||
Body: body,
|
||||
})
|
||||
@@ -79,9 +151,29 @@ func (r *RocketMq) SendByteMsg(topic string, body []byte) (mqMsg MqMsg, err erro
|
||||
return mqMsg, nil
|
||||
}
|
||||
|
||||
func (r *RocketMq) SendDelayMsg(topic string, body string, delaySecond int64) (mqMsg MqMsg, err error) {
|
||||
err = gerror.New("implement me")
|
||||
return
|
||||
func (r *RocketMq) SendDelayMsg(topic string, body string, delayTimeLevel int64) (mqMsg MqMsg, err error) {
|
||||
if r.producerIns == nil {
|
||||
return mqMsg, gerror.New("rocketMq producer not register")
|
||||
}
|
||||
|
||||
msg := primitive.NewMessage(topic, []byte(body))
|
||||
msg.WithDelayTimeLevel(int(delayTimeLevel))
|
||||
|
||||
result, err := r.producerIns.SendSync(ctx, msg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if result.Status != primitive.SendOK {
|
||||
return mqMsg, gerror.Newf("rocketMq producer send msg error status:%v", result.Status)
|
||||
}
|
||||
|
||||
mqMsg = MqMsg{
|
||||
RunType: SendMsg,
|
||||
Topic: topic,
|
||||
MsgId: result.MsgID,
|
||||
Body: []byte(body),
|
||||
}
|
||||
return mqMsg, nil
|
||||
}
|
||||
|
||||
// ListenReceiveMsgDo 消费数据
|
||||
@@ -90,13 +182,22 @@ func (r *RocketMq) ListenReceiveMsgDo(topic string, receiveDo func(mqMsg MqMsg))
|
||||
return gerror.New("rocketMq consumer not register")
|
||||
}
|
||||
|
||||
rocketManager.cMutex.Lock()
|
||||
defer rocketManager.cMutex.Unlock()
|
||||
|
||||
if err = r.createTopicIfNotExists(topic); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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,
|
||||
Topic: item.Topic,
|
||||
MsgId: item.MsgId,
|
||||
Body: item.Body,
|
||||
rocketManager.goPool.Add(ctx, func(ctx context.Context) {
|
||||
receiveDo(MqMsg{
|
||||
RunType: ReceiveMsg,
|
||||
Topic: item.Topic,
|
||||
MsgId: item.MsgId,
|
||||
Body: item.Body,
|
||||
})
|
||||
})
|
||||
}
|
||||
return consumer.ConsumeSuccess, nil
|
||||
@@ -114,53 +215,96 @@ func (r *RocketMq) ListenReceiveMsgDo(topic string, receiveDo func(mqMsg MqMsg))
|
||||
}
|
||||
|
||||
// RegisterRocketMqProducer 注册rocketmq生产者
|
||||
func RegisterRocketMqProducer(endPoints []string, groupName string, retry int) (mqIns *RocketMq, err error) {
|
||||
addr, err := primitive.NewNamesrvAddr(endPoints...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mqIns = &RocketMq{
|
||||
endPoints: endPoints,
|
||||
func RegisterRocketMqProducer() (mqIns *RocketMq, err error) {
|
||||
if rocketManager.Producer != nil {
|
||||
return rocketManager.Producer, nil
|
||||
}
|
||||
|
||||
rocketManager.pMutex.Lock()
|
||||
defer rocketManager.pMutex.Unlock()
|
||||
|
||||
if rocketManager.Producer != nil {
|
||||
return rocketManager.Producer, nil
|
||||
}
|
||||
|
||||
mqIns = new(RocketMq)
|
||||
retry := config.Rocketmq.Retry
|
||||
if retry <= 0 {
|
||||
retry = 0
|
||||
}
|
||||
|
||||
mqIns.producerIns, err = rocketmq.NewProducer(
|
||||
producer.WithNameServer(addr),
|
||||
producer.WithNsResolver(primitive.NewPassthroughResolver(config.Rocketmq.NameSrvAdders)),
|
||||
producer.WithRetry(retry),
|
||||
producer.WithGroupName(groupName),
|
||||
producer.WithGroupName(config.GroupName),
|
||||
producer.WithCredentials(primitive.Credentials{
|
||||
AccessKey: config.Rocketmq.AccessKey,
|
||||
SecretKey: config.Rocketmq.SecretKey,
|
||||
}),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = mqIns.producerIns.Start()
|
||||
if err != nil {
|
||||
if err = mqIns.producerIns.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mqIns, nil
|
||||
|
||||
_, err = mqIns.producerIns.SendSync(ctx, primitive.NewMessage("hotgo-ping", []byte("1")))
|
||||
if err != nil {
|
||||
err = gerror.Newf("连通性测试不通过,请检查`queue.rocketmq.nameSrvAdders`或权限配置是否有误。err:%+v", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
SetRLogLevel()
|
||||
rocketManager.Producer = mqIns
|
||||
return rocketManager.Producer, nil
|
||||
}
|
||||
|
||||
// RegisterRocketMqConsumer 注册rocketmq消费者
|
||||
func RegisterRocketMqConsumer(endPoints []string, groupName string) (mqIns *RocketMq, err error) {
|
||||
addr, err := primitive.NewNamesrvAddr(endPoints...)
|
||||
if err != nil {
|
||||
func RegisterRocketMqConsumer() (mqIns *RocketMq, err error) {
|
||||
if rocketManager.Consumer != nil {
|
||||
return rocketManager.Consumer, nil
|
||||
}
|
||||
|
||||
rocketManager.cMutex.Lock()
|
||||
defer rocketManager.cMutex.Unlock()
|
||||
|
||||
if rocketManager.Consumer != nil {
|
||||
return rocketManager.Consumer, nil
|
||||
}
|
||||
|
||||
// 利用生产者检查一下连通性
|
||||
if _, err = RegisterRocketMqProducer(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mqIns = &RocketMq{
|
||||
endPoints: endPoints,
|
||||
}
|
||||
|
||||
mqIns = new(RocketMq)
|
||||
mqIns.consumerIns, err = rocketmq.NewPushConsumer(
|
||||
consumer.WithNameServer(addr),
|
||||
consumer.WithConsumerModel(consumer.Clustering),
|
||||
consumer.WithGroupName(groupName),
|
||||
consumer.WithNsResolver(primitive.NewPassthroughResolver(config.Rocketmq.NameSrvAdders)),
|
||||
consumer.WithGroupName(config.GroupName),
|
||||
consumer.WithCredentials(primitive.Credentials{
|
||||
AccessKey: config.Rocketmq.AccessKey,
|
||||
SecretKey: config.Rocketmq.SecretKey,
|
||||
}),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mqIns, nil
|
||||
|
||||
// 开多携程处理消费任务,可以根据业务实际情况调整该配置
|
||||
rocketManager.goPool = grpool.New(5)
|
||||
|
||||
SetRLogLevel()
|
||||
rocketManager.Consumer = mqIns
|
||||
return rocketManager.Consumer, nil
|
||||
}
|
||||
|
||||
// SetRLogLevel 设置rocketmq日志输出等级
|
||||
func SetRLogLevel() {
|
||||
level := g.Cfg().MustGet(ctx, "queue.rocketmq.logLevel", "all").String()
|
||||
rlog.SetLogLevel(level)
|
||||
}
|
||||
|
@@ -1,83 +0,0 @@
|
||||
// Package queue
|
||||
// @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 queue
|
||||
|
||||
type RocketMqLogger struct {
|
||||
Flag string
|
||||
LevelLog string
|
||||
}
|
||||
|
||||
func (l *RocketMqLogger) Debug(msg string, fields map[string]interface{}) {
|
||||
if l.LevelLog == "close" {
|
||||
return
|
||||
}
|
||||
if msg == "" && len(fields) == 0 {
|
||||
return
|
||||
}
|
||||
if l.LevelLog == "debug" || l.LevelLog == "all" {
|
||||
Logger().Debug(ctx, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *RocketMqLogger) Level(level string) {
|
||||
Logger().Info(ctx, level)
|
||||
}
|
||||
|
||||
func (l *RocketMqLogger) OutputPath(path string) (err error) {
|
||||
Logger().Info(ctx, path)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *RocketMqLogger) Info(msg string, fields map[string]interface{}) {
|
||||
if l.LevelLog == "close" {
|
||||
return
|
||||
}
|
||||
if msg == "" && len(fields) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if l.LevelLog == "info" || l.LevelLog == "all" {
|
||||
Logger().Info(ctx, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *RocketMqLogger) Warning(msg string, fields map[string]interface{}) {
|
||||
if l.LevelLog == "close" {
|
||||
return
|
||||
}
|
||||
if msg == "" && len(fields) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if l.LevelLog == "warn" || l.LevelLog == "all" {
|
||||
Logger().Warning(ctx, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *RocketMqLogger) Error(msg string, fields map[string]interface{}) {
|
||||
if l.LevelLog == "close" {
|
||||
return
|
||||
}
|
||||
if msg == "" && len(fields) == 0 {
|
||||
return
|
||||
}
|
||||
if l.LevelLog == "error" || l.LevelLog == "all" {
|
||||
Logger().Error(ctx, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *RocketMqLogger) Fatal(msg string, fields map[string]interface{}) {
|
||||
if l.LevelLog == "close" {
|
||||
return
|
||||
}
|
||||
if msg == "" && len(fields) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if l.LevelLog == "fatal" || l.LevelLog == "all" {
|
||||
Logger().Fatal(ctx, msg)
|
||||
}
|
||||
}
|
@@ -236,7 +236,7 @@ func (s *sAdminCreditsLog) Export(ctx context.Context, in *adminin.CreditsLogLis
|
||||
}
|
||||
|
||||
var (
|
||||
fileName = "导出资产变动-" + gctx.CtxId(ctx) + ".xlsx"
|
||||
fileName = "导出资产变动-" + gctx.CtxId(ctx)
|
||||
sheetName = fmt.Sprintf("索引条件共%v行,共%v页,当前导出是第%v页,本页共%v行", totalCount, form.CalPageCount(totalCount, in.PerPage), in.Page, len(list))
|
||||
exports []adminin.CreditsLogExportModel
|
||||
)
|
||||
|
@@ -14,6 +14,7 @@ import (
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/library/hgorm"
|
||||
"hotgo/internal/library/hgorm/handler"
|
||||
"hotgo/internal/model/entity"
|
||||
"hotgo/internal/model/input/adminin"
|
||||
"hotgo/internal/model/input/form"
|
||||
@@ -33,10 +34,15 @@ func init() {
|
||||
service.RegisterAdminDept(NewAdminDept())
|
||||
}
|
||||
|
||||
// Model 部门ORM模型
|
||||
func (s *sAdminDept) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
return handler.Model(dao.AdminDept.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
// Delete 删除
|
||||
func (s *sAdminDept) Delete(ctx context.Context, in *adminin.DeptDeleteInp) (err error) {
|
||||
var models *entity.AdminDept
|
||||
if err = dao.AdminDept.Ctx(ctx).Where("id", in.Id).Scan(&models); err != nil {
|
||||
if err = s.Model(ctx).WherePri(in.Id).Scan(&models); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -44,7 +50,7 @@ func (s *sAdminDept) Delete(ctx context.Context, in *adminin.DeptDeleteInp) (err
|
||||
return gerror.New("数据不存在或已删除!")
|
||||
}
|
||||
|
||||
pidExist, err := dao.AdminDept.Ctx(ctx).Where("pid", models.Id).One()
|
||||
pidExist, err := s.Model(ctx).Where(dao.AdminDept.Columns().Pid, models.Id).One()
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, consts.ErrorORM)
|
||||
return err
|
||||
@@ -53,7 +59,7 @@ func (s *sAdminDept) Delete(ctx context.Context, in *adminin.DeptDeleteInp) (err
|
||||
return gerror.New("请先删除该部门下得所有子级!")
|
||||
}
|
||||
|
||||
_, err = dao.AdminDept.Ctx(ctx).Where("id", in.Id).Delete()
|
||||
_, err = s.Model(ctx).WherePri(in.Id).Delete()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -87,75 +93,41 @@ func (s *sAdminDept) VerifyUnique(ctx context.Context, in *adminin.VerifyUniqueI
|
||||
|
||||
// Edit 修改/新增
|
||||
func (s *sAdminDept) Edit(ctx context.Context, in *adminin.DeptEditInp) (err error) {
|
||||
where := g.Map{
|
||||
dao.AdminDept.Columns().Name: in.Name,
|
||||
dao.AdminDept.Columns().Code: in.Code,
|
||||
}
|
||||
// 验证唯一性
|
||||
err = s.VerifyUnique(ctx, &adminin.VerifyUniqueInp{
|
||||
Id: in.Id,
|
||||
Where: g.Map{
|
||||
dao.AdminDept.Columns().Name: in.Name,
|
||||
dao.AdminDept.Columns().Code: in.Code,
|
||||
},
|
||||
})
|
||||
err = s.VerifyUnique(ctx, &adminin.VerifyUniqueInp{Id: in.Id, Where: where})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 生成下级关系树
|
||||
if in.Pid, in.Level, in.Tree, err = hgorm.GenSubTree(ctx, &dao.AdminDept, in.Pid); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 修改
|
||||
if in.Id > 0 {
|
||||
err = dao.AdminDept.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
// 更新数据
|
||||
_, err = dao.AdminDept.Ctx(ctx).Fields(adminin.DeptUpdateFields{}).WherePri(in.Id).Data(in).Update()
|
||||
if err != nil {
|
||||
return err
|
||||
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||
in.Pid, in.Level, in.Tree, err = hgorm.AutoUpdateTree(ctx, &dao.AdminDept, in.Id, in.Pid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 修改
|
||||
if in.Id > 0 {
|
||||
if _, err = s.Model(ctx).WherePri(in.Id).Data(in).Update(); err != nil {
|
||||
err = gerror.Wrap(err, "修改部门管理失败,请稍后重试!")
|
||||
}
|
||||
|
||||
// 如果当前部门有子级,更新子级tree关系树
|
||||
return updateChildrenTree(ctx, in.Id, in.Level, in.Tree)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 新增
|
||||
_, err = dao.AdminDept.Ctx(ctx).Fields(adminin.DeptInsertFields{}).Data(in).Insert()
|
||||
return
|
||||
}
|
||||
|
||||
func updateChildrenTree(ctx context.Context, _id int64, _level int, _tree string) (err error) {
|
||||
var list []*entity.AdminDept
|
||||
if err = dao.AdminDept.Ctx(ctx).Where("pid", _id).Scan(&list); err != nil || list == nil {
|
||||
return
|
||||
}
|
||||
for _, child := range list {
|
||||
child.Level = _level + 1
|
||||
child.Tree = tree.GenLabel(_tree, child.Pid)
|
||||
|
||||
if _, err = dao.AdminDept.Ctx(ctx).Where("id", child.Id).Data("level", child.Level, "tree", child.Tree).Update(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = updateChildrenTree(ctx, child.Id, child.Level, child.Tree); err != nil {
|
||||
return
|
||||
// 新增
|
||||
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).Data(in).Insert(); err != nil {
|
||||
err = gerror.Wrap(err, "新增部门管理失败,请稍后重试!")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Status 更新部门状态
|
||||
func (s *sAdminDept) Status(ctx context.Context, in *adminin.DeptStatusInp) (err error) {
|
||||
if _, err = dao.AdminDept.Ctx(ctx).Where("id", in.Id).Data("status", in.Status).Update(); err != nil {
|
||||
err = gerror.Wrap(err, "更新部门状态失败!")
|
||||
}
|
||||
return
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// MaxSort 最大排序
|
||||
func (s *sAdminDept) MaxSort(ctx context.Context, in *adminin.DeptMaxSortInp) (res *adminin.DeptMaxSortModel, err error) {
|
||||
if in.Id > 0 {
|
||||
if err = dao.AdminDept.Ctx(ctx).Where("id", in.Id).Order("sort desc").Scan(&res); err != nil {
|
||||
if err = dao.AdminDept.Ctx(ctx).WherePri(in.Id).OrderDesc(dao.AdminDept.Columns().Sort).Scan(&res); err != nil {
|
||||
err = gerror.Wrap(err, "获取部门数据异常!")
|
||||
return
|
||||
}
|
||||
@@ -171,13 +143,146 @@ func (s *sAdminDept) MaxSort(ctx context.Context, in *adminin.DeptMaxSortInp) (r
|
||||
|
||||
// View 获取指定部门信息
|
||||
func (s *sAdminDept) View(ctx context.Context, in *adminin.DeptViewInp) (res *adminin.DeptViewModel, err error) {
|
||||
if err = dao.AdminDept.Ctx(ctx).Where("id", in.Id).Scan(&res); err != nil {
|
||||
if err = dao.AdminDept.Ctx(ctx).WherePri(in.Id).Scan(&res); err != nil {
|
||||
err = gerror.Wrap(err, "获取部门信息失败!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Option 选项
|
||||
// List 获取列表
|
||||
func (s *sAdminDept) List(ctx context.Context, in *adminin.DeptListInp) (res *adminin.DeptListModel, err error) {
|
||||
res = new(adminin.DeptListModel)
|
||||
|
||||
var (
|
||||
mod = dao.AdminDept.Ctx(ctx)
|
||||
cols = dao.AdminDept.Columns()
|
||||
)
|
||||
|
||||
// 部门名称
|
||||
if in.Name != "" {
|
||||
columns, err := dao.AdminDept.Ctx(ctx).Fields(cols.Id).WhereLike(cols.Name, "%"+in.Name+"%").Array()
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, "查询部门名称失败!")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(columns) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
res.Ids = append(res.Ids, g.NewVar(columns).Int64s()...)
|
||||
}
|
||||
|
||||
// 部门编码
|
||||
if in.Code != "" {
|
||||
columns, err := dao.AdminDept.Ctx(ctx).Fields(cols.Id).WhereLike(cols.Code, "%"+in.Code+"%").Array()
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, "查询部门编码失败!")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(columns) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
res.Ids = append(res.Ids, g.NewVar(columns).Int64s()...)
|
||||
}
|
||||
|
||||
// 负责人
|
||||
if in.Leader != "" {
|
||||
columns, err := dao.AdminDept.Ctx(ctx).Fields(cols.Id).Where(cols.Leader, in.Leader).Array()
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, "查询负责人失败!")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(columns) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
res.Ids = append(res.Ids, g.NewVar(columns).Int64s()...)
|
||||
}
|
||||
|
||||
// 创建时间
|
||||
if len(in.CreatedAt) == 2 {
|
||||
columns, err := dao.AdminDept.Ctx(ctx).Fields(cols.Id).WhereBetween(cols.CreatedAt, in.CreatedAt[0], in.CreatedAt[1]).Array()
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, "查询创建时间失败!")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(columns) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
res.Ids = append(res.Ids, g.NewVar(columns).Int64s()...)
|
||||
}
|
||||
|
||||
res.Ids = convert.UniqueSlice(res.Ids)
|
||||
if len(res.Ids) > 0 {
|
||||
// 找到匹配到的完整上级部门
|
||||
columns, err := dao.AdminDept.Ctx(ctx).Fields(cols.Tree).WhereIn(cols.Id, res.Ids).Array()
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, "查询部门失败,请稍后重试!")
|
||||
return nil, err
|
||||
}
|
||||
var pids []int64
|
||||
for _, tr := range g.NewVar(columns).Strings() {
|
||||
pids = append(pids, tree.GetIds(tr)...)
|
||||
}
|
||||
mod = mod.WhereIn(cols.Id, append(res.Ids, convert.UniqueSlice(pids)...))
|
||||
}
|
||||
|
||||
if err = mod.Order("pid asc,sort asc").Scan(&res.List); err != nil {
|
||||
err = gerror.Wrap(err, "获取部门列表失败!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetName 获取部门名称
|
||||
func (s *sAdminDept) GetName(ctx context.Context, id int64) (name string, err error) {
|
||||
var data *entity.AdminDept
|
||||
if err = dao.AdminDept.Ctx(ctx).Where("id", id).Fields("name").Scan(&data); err != nil {
|
||||
err = gerror.Wrap(err, "获取部门名称失败!")
|
||||
return
|
||||
}
|
||||
|
||||
if data == nil {
|
||||
err = gerror.Wrap(err, "部门不存在!")
|
||||
return
|
||||
}
|
||||
return data.Name, nil
|
||||
}
|
||||
|
||||
// VerifyDeptId 验证部门ID
|
||||
func (s *sAdminDept) VerifyDeptId(ctx context.Context, id int64) (err error) {
|
||||
var (
|
||||
pid int64 = 0
|
||||
mb = contexts.GetUser(ctx)
|
||||
mod = dao.AdminDept.Ctx(ctx).Fields(dao.AdminDept.Columns().Id)
|
||||
)
|
||||
|
||||
if mb == nil {
|
||||
err = gerror.New("用户信息获取失败!")
|
||||
return
|
||||
}
|
||||
|
||||
// 非超管只获取下级
|
||||
if !service.AdminMember().VerifySuperId(ctx, mb.Id) {
|
||||
pid = mb.DeptId
|
||||
mod = mod.WhereNot(dao.AdminDept.Columns().Id, pid).WhereLike(dao.AdminDept.Columns().Tree, "%"+tree.GetIdLabel(pid)+"%")
|
||||
}
|
||||
|
||||
columns, err := mod.Array()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !validate.InSlice(g.NewVar(columns).Int64s(), id) {
|
||||
err = gerror.New("部门ID是无效的")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Option 获取当前登录用户可选的部门选项
|
||||
func (s *sAdminDept) Option(ctx context.Context, in *adminin.DeptOptionInp) (res *adminin.DeptOptionModel, totalCount int, err error) {
|
||||
var (
|
||||
mod = dao.AdminDept.Ctx(ctx)
|
||||
@@ -209,104 +314,6 @@ func (s *sAdminDept) Option(ctx context.Context, in *adminin.DeptOptionInp) (res
|
||||
return
|
||||
}
|
||||
|
||||
// List 获取列表
|
||||
func (s *sAdminDept) List(ctx context.Context, in *adminin.DeptListInp) (res *adminin.DeptListModel, err error) {
|
||||
var (
|
||||
mod = dao.AdminDept.Ctx(ctx)
|
||||
cols = dao.AdminDept.Columns()
|
||||
models []*entity.AdminDept
|
||||
ids []int64
|
||||
pids []int64
|
||||
)
|
||||
|
||||
appends := func(columns []gdb.Value) {
|
||||
ds := g.NewVar(columns).Int64s()
|
||||
ids = append(ids, ds...)
|
||||
pids = append(pids, ds...)
|
||||
}
|
||||
|
||||
// 部门名称
|
||||
if in.Name != "" {
|
||||
columns, err := dao.AdminDept.Ctx(ctx).Fields(cols.Pid).WhereLike(cols.Name, "%"+in.Name+"%").Array()
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, "查询部门名称失败!")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(columns) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
appends(columns)
|
||||
}
|
||||
|
||||
if in.Code != "" {
|
||||
columns, err := dao.AdminDept.Ctx(ctx).Fields(cols.Pid).WhereLike(cols.Code, "%"+in.Code+"%").Array()
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, "查询部门编码失败!")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(columns) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
appends(columns)
|
||||
}
|
||||
|
||||
if in.Leader != "" {
|
||||
columns, err := dao.AdminDept.Ctx(ctx).Fields(cols.Pid).Where(cols.Leader, in.Leader).Array()
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, "查询负责人失败!")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(columns) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
appends(columns)
|
||||
}
|
||||
|
||||
if len(in.CreatedAt) == 2 {
|
||||
columns, err := dao.AdminDept.Ctx(ctx).Fields(cols.Pid).WhereBetween(cols.CreatedAt, in.CreatedAt[0], in.CreatedAt[1]).Array()
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, "查询创建时间失败!")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(columns) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
appends(columns)
|
||||
}
|
||||
|
||||
if len(ids) > 0 {
|
||||
mod = mod.Wheref(`id in (?) or pid in (?)`, convert.UniqueSlice(ids), convert.UniqueSlice(pids))
|
||||
}
|
||||
|
||||
if err = mod.Order("pid asc,sort asc").Scan(&models); err != nil {
|
||||
err = gerror.Wrap(err, "获取部门列表失败!")
|
||||
return
|
||||
}
|
||||
|
||||
res = new(adminin.DeptListModel)
|
||||
res.List = s.treeList(0, models)
|
||||
return
|
||||
}
|
||||
|
||||
// GetName 获取部门名称
|
||||
func (s *sAdminDept) GetName(ctx context.Context, id int64) (name string, err error) {
|
||||
var data *entity.AdminDept
|
||||
if err = dao.AdminDept.Ctx(ctx).Where("id", id).Fields("name").Scan(&data); err != nil {
|
||||
err = gerror.Wrap(err, "获取部门名称失败!")
|
||||
return
|
||||
}
|
||||
|
||||
if data == nil {
|
||||
err = gerror.Wrap(err, "部门不存在!")
|
||||
return
|
||||
}
|
||||
return data.Name, nil
|
||||
}
|
||||
|
||||
// treeList 树状列表
|
||||
func (s *sAdminDept) treeList(pid int64, nodes []*entity.AdminDept) (list []*adminin.DeptTree) {
|
||||
list = make([]*adminin.DeptTree, 0)
|
||||
@@ -327,34 +334,16 @@ func (s *sAdminDept) treeList(pid int64, nodes []*entity.AdminDept) (list []*adm
|
||||
return
|
||||
}
|
||||
|
||||
// VerifyDeptId 验证部门ID
|
||||
func (s *sAdminDept) VerifyDeptId(ctx context.Context, id int64) (err error) {
|
||||
var (
|
||||
pid int64 = 0
|
||||
mb = contexts.GetUser(ctx)
|
||||
mod = dao.AdminDept.Ctx(ctx).Fields(dao.AdminDept.Columns().Id)
|
||||
)
|
||||
|
||||
if mb == nil {
|
||||
err = gerror.New("用户信息获取失败!")
|
||||
// TreeOption 获取部门关系树选项
|
||||
func (s *sAdminDept) TreeOption(ctx context.Context) (nodes []tree.Node, err error) {
|
||||
var models []*adminin.DeptTreeOption
|
||||
if err = s.Model(ctx).Fields(adminin.DeptTreeOption{}).OrderAsc(dao.AdminDept.Columns().Pid).OrderAsc(dao.AdminDept.Columns().Sort).OrderDesc(dao.AdminDept.Columns().Id).Scan(&models); err != nil {
|
||||
err = gerror.Wrap(err, "获取部门关系树选项失败!")
|
||||
return
|
||||
}
|
||||
|
||||
// 非超管只获取下级
|
||||
if !service.AdminMember().VerifySuperId(ctx, mb.Id) {
|
||||
pid = mb.DeptId
|
||||
mod = mod.WhereNot(dao.AdminDept.Columns().Id, pid).WhereLike(dao.AdminDept.Columns().Tree, "%"+tree.GetIdLabel(pid)+"%")
|
||||
nodes = make([]tree.Node, len(models))
|
||||
for i, v := range models {
|
||||
nodes[i] = v
|
||||
}
|
||||
|
||||
columns, err := mod.Array()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ids := g.NewVar(columns).Int64s()
|
||||
if !validate.InSlice(ids, id) {
|
||||
err = gerror.New("部门ID是无效的")
|
||||
return
|
||||
}
|
||||
return
|
||||
return tree.ListToTree(0, nodes)
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ package admin
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/crypto/gmd5"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/database/gredis"
|
||||
@@ -719,6 +720,7 @@ func (s *sAdminMember) LoginMemberInfo(ctx context.Context) (res *adminin.LoginM
|
||||
res.Mobile = gstr.HideStr(res.Mobile, 40, `*`)
|
||||
res.Email = gstr.HideStr(res.Email, 40, `*`)
|
||||
res.OpenId, _ = service.CommonWechat().GetOpenId(ctx)
|
||||
res.DeptType = contexts.GetDeptType(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -772,6 +774,22 @@ func (s *sAdminMember) Select(ctx context.Context, in *adminin.MemberSelectInp)
|
||||
return
|
||||
}
|
||||
|
||||
// GetIdsByKeyword 根据关键词查找符合条件的用户ID
|
||||
func (s *sAdminMember) GetIdsByKeyword(ctx context.Context, ks string) (res []int64, err error) {
|
||||
ks = gstr.Trim(ks)
|
||||
if len(ks) == 0 {
|
||||
return
|
||||
}
|
||||
array, err := dao.AdminMember.Ctx(ctx).Fields("id").
|
||||
Where("`id` = ? or `real_name` = ? or `username` = ? or `mobile` = ?", ks, ks, ks, ks).
|
||||
Array()
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, "根据关键词获取用户ID失败,请稍后重试!")
|
||||
}
|
||||
res = gvar.New(array).Int64s()
|
||||
return
|
||||
}
|
||||
|
||||
// VerifySuperId 验证是否为超管
|
||||
func (s *sAdminMember) VerifySuperId(ctx context.Context, verifyId int64) bool {
|
||||
s.superAdmin.RLock()
|
||||
|
@@ -19,6 +19,7 @@ import (
|
||||
"hotgo/internal/library/casbin"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/library/hgorm"
|
||||
"hotgo/internal/library/hgorm/handler"
|
||||
"hotgo/internal/model/entity"
|
||||
"hotgo/internal/model/input/adminin"
|
||||
"hotgo/internal/service"
|
||||
@@ -36,6 +37,11 @@ func init() {
|
||||
service.RegisterAdminMenu(NewAdminMenu())
|
||||
}
|
||||
|
||||
// Model Orm模型
|
||||
func (s *sAdminMenu) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
return handler.Model(dao.AdminMenu.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
// Delete 删除
|
||||
func (s *sAdminMenu) Delete(ctx context.Context, in *adminin.MenuDeleteInp) (err error) {
|
||||
exist, err := dao.AdminMenu.Ctx(ctx).Where("pid", in.Id).One()
|
||||
@@ -282,3 +288,17 @@ func (s *sAdminMenu) treeList(pid int64, nodes []*entity.AdminMenu) (list []*adm
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetFastList 获取菜单列表
|
||||
func (s *sAdminMenu) GetFastList(ctx context.Context) (res map[int64]*entity.AdminMenu, err error) {
|
||||
var models []*entity.AdminMenu
|
||||
if err = dao.AdminMenu.Ctx(ctx).Scan(&models); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res = make(map[int64]*entity.AdminMenu, len(models))
|
||||
for _, v := range models {
|
||||
res[v.Id] = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@@ -317,7 +317,7 @@ func (s *sAdminOrder) Export(ctx context.Context, in *adminin.OrderListInp) (err
|
||||
}
|
||||
|
||||
var (
|
||||
fileName = "导出充值订单-" + gctx.CtxId(ctx) + ".xlsx"
|
||||
fileName = "导出充值订单-" + gctx.CtxId(ctx)
|
||||
sheetName = fmt.Sprintf("索引条件共%v行,共%v页,当前导出是第%v页,本页共%v行", totalCount, form.CalPageCount(totalCount, in.PerPage), in.Page, len(list))
|
||||
exports []adminin.OrderExportModel
|
||||
)
|
||||
|
@@ -226,6 +226,10 @@ func (s *sAdminRole) Delete(ctx context.Context, in *adminin.RoleDeleteInp) (err
|
||||
return gerror.New("数据不存在或已删除!")
|
||||
}
|
||||
|
||||
if models.Key == consts.SuperRoleKey {
|
||||
return gerror.New("超管角色禁止删除!")
|
||||
}
|
||||
|
||||
has, err := dao.AdminRole.Ctx(ctx).Where("pid", models.Id).One()
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, consts.ErrorORM)
|
||||
|
@@ -82,11 +82,6 @@ func (s *sAdminSite) Register(ctx context.Context, in *adminin.RegisterInp) (err
|
||||
return
|
||||
}
|
||||
|
||||
if len(config.PostIds) == 0 {
|
||||
err = gerror.New("管理员未配置默认岗位")
|
||||
return
|
||||
}
|
||||
|
||||
// 验证唯一性
|
||||
err = service.AdminMember().VerifyUnique(ctx, &adminin.VerifyUniqueInp{
|
||||
Where: g.Map{
|
||||
@@ -225,39 +220,18 @@ func (s *sAdminSite) MobileLogin(ctx context.Context, in *adminin.MobileLoginInp
|
||||
|
||||
// handleLogin .
|
||||
func (s *sAdminSite) handleLogin(ctx context.Context, mb *entity.AdminMember) (res *adminin.LoginModel, err error) {
|
||||
var ro *entity.AdminRole
|
||||
if err = dao.AdminRole.Ctx(ctx).Fields("id,key,status").Where("id", mb.RoleId).Scan(&ro); err != nil {
|
||||
err = gerror.Wrap(err, consts.ErrorORM)
|
||||
return
|
||||
}
|
||||
|
||||
if ro == nil {
|
||||
err = gerror.New("角色不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if ro.Status != consts.StatusEnabled {
|
||||
err = gerror.New("角色已被禁用")
|
||||
return
|
||||
}
|
||||
|
||||
var dept *entity.AdminDept
|
||||
if err = dao.AdminDept.Ctx(ctx).Fields("id,status").Where("id", mb.DeptId).Scan(&dept); err != nil || dept == nil {
|
||||
err = gerror.Wrap(err, "获取部门信息失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
|
||||
if dept.Status != consts.StatusEnabled {
|
||||
err = gerror.New("部门已被禁用,如有疑问请联系管理员")
|
||||
return
|
||||
role, dept, err := s.getLoginRoleAndDept(ctx, mb.RoleId, mb.DeptId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user := &model.Identity{
|
||||
Id: mb.Id,
|
||||
Pid: mb.Pid,
|
||||
DeptId: mb.DeptId,
|
||||
RoleId: ro.Id,
|
||||
RoleKey: ro.Key,
|
||||
DeptId: dept.Id,
|
||||
DeptType: dept.Type,
|
||||
RoleId: role.Id,
|
||||
RoleKey: role.Key,
|
||||
Username: mb.Username,
|
||||
RealName: mb.RealName,
|
||||
Avatar: mb.Avatar,
|
||||
@@ -281,10 +255,48 @@ func (s *sAdminSite) handleLogin(ctx context.Context, mb *entity.AdminMember) (r
|
||||
return
|
||||
}
|
||||
|
||||
// getLoginRoleAndDept 获取登录的角色和部门信息
|
||||
func (s *sAdminSite) getLoginRoleAndDept(ctx context.Context, roleId, deptId int64) (role *entity.AdminRole, dept *entity.AdminDept, err error) {
|
||||
if err = dao.AdminRole.Ctx(ctx).Fields("id,key,status").WherePri(roleId).Scan(&role); err != nil {
|
||||
err = gerror.Wrap(err, consts.ErrorORM)
|
||||
return
|
||||
}
|
||||
|
||||
if role == nil {
|
||||
err = gerror.New("角色不存在或已被删除")
|
||||
return
|
||||
}
|
||||
|
||||
if role.Status != consts.StatusEnabled {
|
||||
err = gerror.New("角色已被禁用,如有疑问请联系管理员")
|
||||
return
|
||||
}
|
||||
|
||||
if err = dao.AdminDept.Ctx(ctx).Fields("id,type,status").WherePri(deptId).Scan(&dept); err != nil {
|
||||
err = gerror.Wrap(err, "获取部门信息失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
|
||||
if dept == nil {
|
||||
err = gerror.New("部门不存在或已被删除")
|
||||
return
|
||||
}
|
||||
|
||||
if dept.Status != consts.StatusEnabled {
|
||||
err = gerror.New("部门已被禁用,如有疑问请联系管理员")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// BindUserContext 绑定用户上下文
|
||||
func (s *sAdminSite) BindUserContext(ctx context.Context, claims *model.Identity) (err error) {
|
||||
//// 如果不想每次访问都重新加载用户信息,可以放开注释。但在本次登录未失效前,用户信息不会刷新
|
||||
//contexts.SetUser(ctx, claims)
|
||||
//return
|
||||
|
||||
var mb *entity.AdminMember
|
||||
if err = dao.AdminMember.Ctx(ctx).Where("id", claims.Id).Scan(&mb); err != nil {
|
||||
if err = dao.AdminMember.Ctx(ctx).WherePri(claims.Id).Scan(&mb); err != nil {
|
||||
err = gerror.Wrap(err, "获取用户信息失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
@@ -299,32 +311,16 @@ func (s *sAdminSite) BindUserContext(ctx context.Context, claims *model.Identity
|
||||
return
|
||||
}
|
||||
|
||||
var role *entity.AdminRole
|
||||
if err = dao.AdminRole.Ctx(ctx).Fields("id,key,status").Where("id", mb.RoleId).Scan(&role); err != nil || role == nil {
|
||||
err = gerror.Wrap(err, "获取角色信息失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
|
||||
if role.Status != consts.StatusEnabled {
|
||||
err = gerror.New("角色已被禁用,如有疑问请联系管理员")
|
||||
return
|
||||
}
|
||||
|
||||
var dept *entity.AdminDept
|
||||
if err = dao.AdminDept.Ctx(ctx).Fields("id,status").Where("id", mb.DeptId).Scan(&dept); err != nil || dept == nil {
|
||||
err = gerror.Wrap(err, "获取部门信息失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
|
||||
if dept.Status != consts.StatusEnabled {
|
||||
err = gerror.New("部门已被禁用,如有疑问请联系管理员")
|
||||
return
|
||||
role, dept, err := s.getLoginRoleAndDept(ctx, mb.RoleId, mb.DeptId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user := &model.Identity{
|
||||
Id: mb.Id,
|
||||
Pid: mb.Pid,
|
||||
DeptId: mb.DeptId,
|
||||
DeptId: dept.Id,
|
||||
DeptType: dept.Type,
|
||||
RoleId: mb.RoleId,
|
||||
RoleKey: role.Key,
|
||||
Username: mb.Username,
|
||||
|
@@ -10,14 +10,19 @@ import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/service"
|
||||
"hotgo/utility/simple"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 忽略的请求方式
|
||||
var ignoredRequestMethods = []string{"HEAD", "PRI"}
|
||||
|
||||
// accessLog 访问日志
|
||||
func (s *sHook) accessLog(r *ghttp.Request) {
|
||||
if r.IsFileRequest() {
|
||||
if s.isIgnoredRequest(r) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -37,3 +42,15 @@ func (s *sHook) accessLog(r *ghttp.Request) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// isIgnoredRequest 是否忽略请求
|
||||
func (s *sHook) isIgnoredRequest(r *ghttp.Request) bool {
|
||||
if r.IsFileRequest() {
|
||||
return true
|
||||
}
|
||||
|
||||
if gstr.InArray(ignoredRequestMethods, strings.ToUpper(r.Method)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user