diff --git a/server/internal/crons/test2.go b/server/internal/crons/test2.go
new file mode 100644
index 0000000..c312e13
--- /dev/null
+++ b/server/internal/crons/test2.go
@@ -0,0 +1,45 @@
+// Package crons
+// @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 crons
+
+import (
+ "context"
+ "github.com/gogf/gf/v2/errors/gerror"
+ "hotgo/internal/library/cron"
+ "time"
+)
+
+func init() {
+ cron.Register(Test2)
+}
+
+// Test2 测试2任务(带参数)
+var Test2 = &cTest2{name: "test2"}
+
+type cTest2 struct {
+ name string
+}
+
+func (c *cTest2) GetName() string {
+ return c.name
+}
+
+// Execute 执行任务
+func (c *cTest2) Execute(ctx context.Context, parser *cron.Parser) (err error) {
+ if len(parser.Args) != 3 {
+ err = gerror.New("传入参数不正确!")
+ return
+ }
+
+ var (
+ name = parser.Args[0]
+ age = parser.Args[1]
+ msg = parser.Args[2]
+ )
+
+ parser.Logger.Infof(ctx, "cron test2 Execute:%v, name:%v, age:%v, msg:%v", time.Now(), name, age, msg)
+ return
+}
diff --git a/server/internal/dao/.gitkeep b/server/internal/dao/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/server/internal/dao/addon_hgexample_table.go b/server/internal/dao/addon_hgexample_table.go
new file mode 100755
index 0000000..1191340
--- /dev/null
+++ b/server/internal/dao/addon_hgexample_table.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// addonHgexampleTableDao is the data access object for the table hg_addon_hgexample_table.
+// You can define custom methods on it to extend its functionality as needed.
+type addonHgexampleTableDao struct {
+ *internal.AddonHgexampleTableDao
+}
+
+var (
+ // AddonHgexampleTable is a globally accessible object for table hg_addon_hgexample_table operations.
+ AddonHgexampleTable = addonHgexampleTableDao{internal.NewAddonHgexampleTableDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/addon_hgexample_tenant_order.go b/server/internal/dao/addon_hgexample_tenant_order.go
new file mode 100755
index 0000000..d369ac6
--- /dev/null
+++ b/server/internal/dao/addon_hgexample_tenant_order.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// addonHgexampleTenantOrderDao is the data access object for the table hg_addon_hgexample_tenant_order.
+// You can define custom methods on it to extend its functionality as needed.
+type addonHgexampleTenantOrderDao struct {
+ *internal.AddonHgexampleTenantOrderDao
+}
+
+var (
+ // AddonHgexampleTenantOrder is a globally accessible object for table hg_addon_hgexample_tenant_order operations.
+ AddonHgexampleTenantOrder = addonHgexampleTenantOrderDao{internal.NewAddonHgexampleTenantOrderDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/admin_cash.go b/server/internal/dao/admin_cash.go
new file mode 100755
index 0000000..6bda8cf
--- /dev/null
+++ b/server/internal/dao/admin_cash.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// adminCashDao is the data access object for the table hg_admin_cash.
+// You can define custom methods on it to extend its functionality as needed.
+type adminCashDao struct {
+ *internal.AdminCashDao
+}
+
+var (
+ // AdminCash is a globally accessible object for table hg_admin_cash operations.
+ AdminCash = adminCashDao{internal.NewAdminCashDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/admin_credits_log.go b/server/internal/dao/admin_credits_log.go
new file mode 100755
index 0000000..b415991
--- /dev/null
+++ b/server/internal/dao/admin_credits_log.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// adminCreditsLogDao is the data access object for the table hg_admin_credits_log.
+// You can define custom methods on it to extend its functionality as needed.
+type adminCreditsLogDao struct {
+ *internal.AdminCreditsLogDao
+}
+
+var (
+ // AdminCreditsLog is a globally accessible object for table hg_admin_credits_log operations.
+ AdminCreditsLog = adminCreditsLogDao{internal.NewAdminCreditsLogDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/admin_dept.go b/server/internal/dao/admin_dept.go
new file mode 100755
index 0000000..4eeced7
--- /dev/null
+++ b/server/internal/dao/admin_dept.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// adminDeptDao is the data access object for the table hg_admin_dept.
+// You can define custom methods on it to extend its functionality as needed.
+type adminDeptDao struct {
+ *internal.AdminDeptDao
+}
+
+var (
+ // AdminDept is a globally accessible object for table hg_admin_dept operations.
+ AdminDept = adminDeptDao{internal.NewAdminDeptDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/admin_member.go b/server/internal/dao/admin_member.go
new file mode 100755
index 0000000..112f3f0
--- /dev/null
+++ b/server/internal/dao/admin_member.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// adminMemberDao is the data access object for the table hg_admin_member.
+// You can define custom methods on it to extend its functionality as needed.
+type adminMemberDao struct {
+ *internal.AdminMemberDao
+}
+
+var (
+ // AdminMember is a globally accessible object for table hg_admin_member operations.
+ AdminMember = adminMemberDao{internal.NewAdminMemberDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/admin_member_post.go b/server/internal/dao/admin_member_post.go
new file mode 100755
index 0000000..1c4ab68
--- /dev/null
+++ b/server/internal/dao/admin_member_post.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// adminMemberPostDao is the data access object for the table hg_admin_member_post.
+// You can define custom methods on it to extend its functionality as needed.
+type adminMemberPostDao struct {
+ *internal.AdminMemberPostDao
+}
+
+var (
+ // AdminMemberPost is a globally accessible object for table hg_admin_member_post operations.
+ AdminMemberPost = adminMemberPostDao{internal.NewAdminMemberPostDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/admin_member_role.go b/server/internal/dao/admin_member_role.go
new file mode 100755
index 0000000..7b48a86
--- /dev/null
+++ b/server/internal/dao/admin_member_role.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// adminMemberRoleDao is the data access object for the table hg_admin_member_role.
+// You can define custom methods on it to extend its functionality as needed.
+type adminMemberRoleDao struct {
+ *internal.AdminMemberRoleDao
+}
+
+var (
+ // AdminMemberRole is a globally accessible object for table hg_admin_member_role operations.
+ AdminMemberRole = adminMemberRoleDao{internal.NewAdminMemberRoleDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/admin_menu.go b/server/internal/dao/admin_menu.go
new file mode 100755
index 0000000..8bac378
--- /dev/null
+++ b/server/internal/dao/admin_menu.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// adminMenuDao is the data access object for the table hg_admin_menu.
+// You can define custom methods on it to extend its functionality as needed.
+type adminMenuDao struct {
+ *internal.AdminMenuDao
+}
+
+var (
+ // AdminMenu is a globally accessible object for table hg_admin_menu operations.
+ AdminMenu = adminMenuDao{internal.NewAdminMenuDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/admin_notice.go b/server/internal/dao/admin_notice.go
new file mode 100755
index 0000000..457c3e7
--- /dev/null
+++ b/server/internal/dao/admin_notice.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// adminNoticeDao is the data access object for the table hg_admin_notice.
+// You can define custom methods on it to extend its functionality as needed.
+type adminNoticeDao struct {
+ *internal.AdminNoticeDao
+}
+
+var (
+ // AdminNotice is a globally accessible object for table hg_admin_notice operations.
+ AdminNotice = adminNoticeDao{internal.NewAdminNoticeDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/admin_notice_read.go b/server/internal/dao/admin_notice_read.go
new file mode 100755
index 0000000..1a8f484
--- /dev/null
+++ b/server/internal/dao/admin_notice_read.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// adminNoticeReadDao is the data access object for the table hg_admin_notice_read.
+// You can define custom methods on it to extend its functionality as needed.
+type adminNoticeReadDao struct {
+ *internal.AdminNoticeReadDao
+}
+
+var (
+ // AdminNoticeRead is a globally accessible object for table hg_admin_notice_read operations.
+ AdminNoticeRead = adminNoticeReadDao{internal.NewAdminNoticeReadDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/admin_oauth.go b/server/internal/dao/admin_oauth.go
new file mode 100755
index 0000000..eed3b14
--- /dev/null
+++ b/server/internal/dao/admin_oauth.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// adminOauthDao is the data access object for the table hg_admin_oauth.
+// You can define custom methods on it to extend its functionality as needed.
+type adminOauthDao struct {
+ *internal.AdminOauthDao
+}
+
+var (
+ // AdminOauth is a globally accessible object for table hg_admin_oauth operations.
+ AdminOauth = adminOauthDao{internal.NewAdminOauthDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/admin_order.go b/server/internal/dao/admin_order.go
new file mode 100755
index 0000000..8fd4a80
--- /dev/null
+++ b/server/internal/dao/admin_order.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// adminOrderDao is the data access object for the table hg_admin_order.
+// You can define custom methods on it to extend its functionality as needed.
+type adminOrderDao struct {
+ *internal.AdminOrderDao
+}
+
+var (
+ // AdminOrder is a globally accessible object for table hg_admin_order operations.
+ AdminOrder = adminOrderDao{internal.NewAdminOrderDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/admin_post.go b/server/internal/dao/admin_post.go
new file mode 100755
index 0000000..ecf69cd
--- /dev/null
+++ b/server/internal/dao/admin_post.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// adminPostDao is the data access object for the table hg_admin_post.
+// You can define custom methods on it to extend its functionality as needed.
+type adminPostDao struct {
+ *internal.AdminPostDao
+}
+
+var (
+ // AdminPost is a globally accessible object for table hg_admin_post operations.
+ AdminPost = adminPostDao{internal.NewAdminPostDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/admin_role.go b/server/internal/dao/admin_role.go
new file mode 100755
index 0000000..8315e43
--- /dev/null
+++ b/server/internal/dao/admin_role.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// adminRoleDao is the data access object for the table hg_admin_role.
+// You can define custom methods on it to extend its functionality as needed.
+type adminRoleDao struct {
+ *internal.AdminRoleDao
+}
+
+var (
+ // AdminRole is a globally accessible object for table hg_admin_role operations.
+ AdminRole = adminRoleDao{internal.NewAdminRoleDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/admin_role_casbin.go b/server/internal/dao/admin_role_casbin.go
new file mode 100755
index 0000000..3557470
--- /dev/null
+++ b/server/internal/dao/admin_role_casbin.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// adminRoleCasbinDao is the data access object for the table hg_admin_role_casbin.
+// You can define custom methods on it to extend its functionality as needed.
+type adminRoleCasbinDao struct {
+ *internal.AdminRoleCasbinDao
+}
+
+var (
+ // AdminRoleCasbin is a globally accessible object for table hg_admin_role_casbin operations.
+ AdminRoleCasbin = adminRoleCasbinDao{internal.NewAdminRoleCasbinDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/admin_role_menu.go b/server/internal/dao/admin_role_menu.go
new file mode 100755
index 0000000..e180651
--- /dev/null
+++ b/server/internal/dao/admin_role_menu.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// adminRoleMenuDao is the data access object for the table hg_admin_role_menu.
+// You can define custom methods on it to extend its functionality as needed.
+type adminRoleMenuDao struct {
+ *internal.AdminRoleMenuDao
+}
+
+var (
+ // AdminRoleMenu is a globally accessible object for table hg_admin_role_menu operations.
+ AdminRoleMenu = adminRoleMenuDao{internal.NewAdminRoleMenuDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/internal/addon_hgexample_table.go b/server/internal/dao/internal/addon_hgexample_table.go
new file mode 100755
index 0000000..97a8551
--- /dev/null
+++ b/server/internal/dao/internal/addon_hgexample_table.go
@@ -0,0 +1,151 @@
+// ==========================================================================
+// 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"
+)
+
+// AddonHgexampleTableDao is the data access object for the table hg_addon_hgexample_table.
+type AddonHgexampleTableDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns AddonHgexampleTableColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// AddonHgexampleTableColumns defines and stores column names for the 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 // 标题
+ Description string // 描述
+ Content string // 内容
+ Image string // 单图
+ Images string // 多图
+ Attachfile string // 附件
+ Attachfiles string // 多附件
+ Map string // 动态键值对
+ Star string // 推荐星
+ Price string // 价格
+ Views string // 浏览次数
+ ActivityAt string // 活动时间
+ StartAt string // 开启时间
+ EndAt string // 结束时间
+ Switch string // 开关
+ Sort string // 排序
+ Avatar string // 头像
+ Sex string // 性别
+ Qq string // qq
+ Email string // 邮箱
+ Mobile string // 手机号码
+ Hobby string // 爱好
+ Channel string // 渠道
+ CityId string // 所在城市
+ Remark string // 备注
+ Status string // 状态
+ CreatedBy string // 创建者
+ UpdatedBy string // 更新者
+ CreatedAt string // 创建时间
+ UpdatedAt string // 修改时间
+ DeletedAt string // 删除时间
+}
+
+// addonHgexampleTableColumns holds the columns for the table hg_addon_hgexample_table.
+var addonHgexampleTableColumns = AddonHgexampleTableColumns{
+ Id: "id",
+ Pid: "pid",
+ Level: "level",
+ Tree: "tree",
+ CategoryId: "category_id",
+ Flag: "flag",
+ Title: "title",
+ Description: "description",
+ Content: "content",
+ Image: "image",
+ Images: "images",
+ Attachfile: "attachfile",
+ Attachfiles: "attachfiles",
+ Map: "map",
+ Star: "star",
+ Price: "price",
+ Views: "views",
+ ActivityAt: "activity_at",
+ StartAt: "start_at",
+ EndAt: "end_at",
+ Switch: "switch",
+ Sort: "sort",
+ Avatar: "avatar",
+ Sex: "sex",
+ Qq: "qq",
+ Email: "email",
+ Mobile: "mobile",
+ Hobby: "hobby",
+ Channel: "channel",
+ CityId: "city_id",
+ Remark: "remark",
+ Status: "status",
+ CreatedBy: "created_by",
+ UpdatedBy: "updated_by",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+ DeletedAt: "deleted_at",
+}
+
+// NewAddonHgexampleTableDao creates and returns a new DAO object for table data access.
+func NewAddonHgexampleTableDao(handlers ...gdb.ModelHandler) *AddonHgexampleTableDao {
+ return &AddonHgexampleTableDao{
+ group: "default",
+ table: "hg_addon_hgexample_table",
+ columns: addonHgexampleTableColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *AddonHgexampleTableDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *AddonHgexampleTableDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *AddonHgexampleTableDao) Columns() AddonHgexampleTableColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *AddonHgexampleTableDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *AddonHgexampleTableDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *AddonHgexampleTableDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/addon_hgexample_tenant_order.go b/server/internal/dao/internal/addon_hgexample_tenant_order.go
new file mode 100755
index 0000000..bf41fd8
--- /dev/null
+++ b/server/internal/dao/internal/addon_hgexample_tenant_order.go
@@ -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"
+)
+
+// AddonHgexampleTenantOrderDao is the data access object for the 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 the current DAO.
+ columns AddonHgexampleTenantOrderColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// AddonHgexampleTenantOrderColumns defines and stores column names for the 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 the 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(handlers ...gdb.ModelHandler) *AddonHgexampleTenantOrderDao {
+ return &AddonHgexampleTenantOrderDao{
+ group: "default",
+ table: "hg_addon_hgexample_tenant_order",
+ columns: addonHgexampleTenantOrderColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *AddonHgexampleTenantOrderDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *AddonHgexampleTenantOrderDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *AddonHgexampleTenantOrderDao) Columns() AddonHgexampleTenantOrderColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *AddonHgexampleTenantOrderDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *AddonHgexampleTenantOrderDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back 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)
+}
diff --git a/server/internal/dao/internal/admin_cash.go b/server/internal/dao/internal/admin_cash.go
new file mode 100755
index 0000000..a9bdb1b
--- /dev/null
+++ b/server/internal/dao/internal/admin_cash.go
@@ -0,0 +1,97 @@
+// ==========================================================================
+// 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"
+)
+
+// AdminCashDao is the data access object for the table hg_admin_cash.
+type AdminCashDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns AdminCashColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// AdminCashColumns defines and stores column names for the table hg_admin_cash.
+type AdminCashColumns struct {
+ Id string // ID
+ MemberId string // 管理员ID
+ Money string // 提现金额
+ Fee string // 手续费
+ LastMoney string // 最终到账金额
+ Ip string // 申请人IP
+ Status string // 状态码
+ Msg string // 处理结果
+ HandleAt string // 处理时间
+ CreatedAt string // 申请时间
+}
+
+// adminCashColumns holds the columns for the table hg_admin_cash.
+var adminCashColumns = AdminCashColumns{
+ Id: "id",
+ MemberId: "member_id",
+ Money: "money",
+ Fee: "fee",
+ LastMoney: "last_money",
+ Ip: "ip",
+ Status: "status",
+ Msg: "msg",
+ HandleAt: "handle_at",
+ CreatedAt: "created_at",
+}
+
+// NewAdminCashDao creates and returns a new DAO object for table data access.
+func NewAdminCashDao(handlers ...gdb.ModelHandler) *AdminCashDao {
+ return &AdminCashDao{
+ group: "default",
+ table: "hg_admin_cash",
+ columns: adminCashColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *AdminCashDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *AdminCashDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *AdminCashDao) Columns() AdminCashColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *AdminCashDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *AdminCashDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *AdminCashDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/admin_credits_log.go b/server/internal/dao/internal/admin_credits_log.go
new file mode 100755
index 0000000..0b3602e
--- /dev/null
+++ b/server/internal/dao/internal/admin_credits_log.go
@@ -0,0 +1,107 @@
+// ==========================================================================
+// 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"
+)
+
+// AdminCreditsLogDao is the data access object for the table hg_admin_credits_log.
+type AdminCreditsLogDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns AdminCreditsLogColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// AdminCreditsLogColumns defines and stores column names for the table hg_admin_credits_log.
+type AdminCreditsLogColumns struct {
+ Id string // 变动ID
+ MemberId string // 管理员ID
+ AppId string // 应用id
+ AddonsName string // 插件名称
+ CreditType string // 变动类型
+ CreditGroup string // 变动组别
+ BeforeNum string // 变动前
+ Num string // 变动数据
+ AfterNum string // 变动后
+ Remark string // 备注
+ Ip string // 操作人IP
+ MapId string // 关联ID
+ Status string // 状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 修改时间
+}
+
+// adminCreditsLogColumns holds the columns for the table hg_admin_credits_log.
+var adminCreditsLogColumns = AdminCreditsLogColumns{
+ Id: "id",
+ MemberId: "member_id",
+ AppId: "app_id",
+ AddonsName: "addons_name",
+ CreditType: "credit_type",
+ CreditGroup: "credit_group",
+ BeforeNum: "before_num",
+ Num: "num",
+ AfterNum: "after_num",
+ Remark: "remark",
+ Ip: "ip",
+ MapId: "map_id",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewAdminCreditsLogDao creates and returns a new DAO object for table data access.
+func NewAdminCreditsLogDao(handlers ...gdb.ModelHandler) *AdminCreditsLogDao {
+ return &AdminCreditsLogDao{
+ group: "default",
+ table: "hg_admin_credits_log",
+ columns: adminCreditsLogColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *AdminCreditsLogDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *AdminCreditsLogDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *AdminCreditsLogDao) Columns() AdminCreditsLogColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *AdminCreditsLogDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *AdminCreditsLogDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *AdminCreditsLogDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/admin_dept.go b/server/internal/dao/internal/admin_dept.go
new file mode 100755
index 0000000..becb639
--- /dev/null
+++ b/server/internal/dao/internal/admin_dept.go
@@ -0,0 +1,105 @@
+// ==========================================================================
+// 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"
+)
+
+// AdminDeptDao is the data access object for the table hg_admin_dept.
+type AdminDeptDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns AdminDeptColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// AdminDeptColumns defines and stores column names for the table hg_admin_dept.
+type AdminDeptColumns struct {
+ Id string // 部门ID
+ Pid string // 父部门ID
+ Name string // 部门名称
+ Code string // 部门编码
+ Type string // 部门类型
+ Leader string // 负责人
+ Phone string // 联系电话
+ Email string // 邮箱
+ Level string // 关系树等级
+ Tree string // 关系树
+ Sort string // 排序
+ Status string // 部门状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 更新时间
+}
+
+// adminDeptColumns holds the columns for the table hg_admin_dept.
+var adminDeptColumns = AdminDeptColumns{
+ Id: "id",
+ Pid: "pid",
+ Name: "name",
+ Code: "code",
+ Type: "type",
+ Leader: "leader",
+ Phone: "phone",
+ Email: "email",
+ Level: "level",
+ Tree: "tree",
+ Sort: "sort",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewAdminDeptDao creates and returns a new DAO object for table data access.
+func NewAdminDeptDao(handlers ...gdb.ModelHandler) *AdminDeptDao {
+ return &AdminDeptDao{
+ group: "default",
+ table: "hg_admin_dept",
+ columns: adminDeptColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *AdminDeptDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *AdminDeptDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *AdminDeptDao) Columns() AdminDeptColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *AdminDeptDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *AdminDeptDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *AdminDeptDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/admin_member.go b/server/internal/dao/internal/admin_member.go
new file mode 100755
index 0000000..93ebaa7
--- /dev/null
+++ b/server/internal/dao/internal/admin_member.go
@@ -0,0 +1,133 @@
+// ==========================================================================
+// 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"
+)
+
+// AdminMemberDao is the data access object for the table hg_admin_member.
+type AdminMemberDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns AdminMemberColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// AdminMemberColumns defines and stores column names for the table hg_admin_member.
+type AdminMemberColumns struct {
+ Id string // 管理员ID
+ DeptId string // 部门ID
+ RoleId string // 角色ID
+ RealName string // 真实姓名
+ Username string // 帐号
+ PasswordHash string // 密码
+ Salt string // 密码盐
+ PasswordResetToken string // 密码重置令牌
+ Integral string // 积分
+ Balance string // 余额
+ Avatar string // 头像
+ Sex string // 性别
+ Qq string // qq
+ Email string // 邮箱
+ Mobile string // 手机号码
+ Birthday string // 生日
+ CityId string // 城市编码
+ Address string // 联系地址
+ Pid string // 上级管理员ID
+ Level string // 关系树等级
+ Tree string // 关系树
+ InviteCode string // 邀请码
+ Cash string // 提现配置
+ LastActiveAt string // 最后活跃时间
+ Remark string // 备注
+ Status string // 状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 修改时间
+}
+
+// adminMemberColumns holds the columns for the table hg_admin_member.
+var adminMemberColumns = AdminMemberColumns{
+ Id: "id",
+ DeptId: "dept_id",
+ RoleId: "role_id",
+ RealName: "real_name",
+ Username: "username",
+ PasswordHash: "password_hash",
+ Salt: "salt",
+ PasswordResetToken: "password_reset_token",
+ Integral: "integral",
+ Balance: "balance",
+ Avatar: "avatar",
+ Sex: "sex",
+ Qq: "qq",
+ Email: "email",
+ Mobile: "mobile",
+ Birthday: "birthday",
+ CityId: "city_id",
+ Address: "address",
+ Pid: "pid",
+ Level: "level",
+ Tree: "tree",
+ InviteCode: "invite_code",
+ Cash: "cash",
+ LastActiveAt: "last_active_at",
+ Remark: "remark",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewAdminMemberDao creates and returns a new DAO object for table data access.
+func NewAdminMemberDao(handlers ...gdb.ModelHandler) *AdminMemberDao {
+ return &AdminMemberDao{
+ group: "default",
+ table: "hg_admin_member",
+ columns: adminMemberColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *AdminMemberDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *AdminMemberDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *AdminMemberDao) Columns() AdminMemberColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *AdminMemberDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *AdminMemberDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *AdminMemberDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/admin_member_post.go b/server/internal/dao/internal/admin_member_post.go
new file mode 100755
index 0000000..8a30c9e
--- /dev/null
+++ b/server/internal/dao/internal/admin_member_post.go
@@ -0,0 +1,81 @@
+// ==========================================================================
+// 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"
+)
+
+// AdminMemberPostDao is the data access object for the table hg_admin_member_post.
+type AdminMemberPostDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns AdminMemberPostColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// AdminMemberPostColumns defines and stores column names for the table hg_admin_member_post.
+type AdminMemberPostColumns struct {
+ MemberId string // 管理员ID
+ PostId string // 岗位ID
+}
+
+// adminMemberPostColumns holds the columns for the table hg_admin_member_post.
+var adminMemberPostColumns = AdminMemberPostColumns{
+ MemberId: "member_id",
+ PostId: "post_id",
+}
+
+// NewAdminMemberPostDao creates and returns a new DAO object for table data access.
+func NewAdminMemberPostDao(handlers ...gdb.ModelHandler) *AdminMemberPostDao {
+ return &AdminMemberPostDao{
+ group: "default",
+ table: "hg_admin_member_post",
+ columns: adminMemberPostColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *AdminMemberPostDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *AdminMemberPostDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *AdminMemberPostDao) Columns() AdminMemberPostColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *AdminMemberPostDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *AdminMemberPostDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *AdminMemberPostDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/admin_member_role.go b/server/internal/dao/internal/admin_member_role.go
new file mode 100755
index 0000000..094d663
--- /dev/null
+++ b/server/internal/dao/internal/admin_member_role.go
@@ -0,0 +1,81 @@
+// ==========================================================================
+// 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"
+)
+
+// AdminMemberRoleDao is the data access object for the table hg_admin_member_role.
+type AdminMemberRoleDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns AdminMemberRoleColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// AdminMemberRoleColumns defines and stores column names for the table hg_admin_member_role.
+type AdminMemberRoleColumns struct {
+ MemberId string // 管理员ID
+ RoleId string // 角色ID
+}
+
+// adminMemberRoleColumns holds the columns for the table hg_admin_member_role.
+var adminMemberRoleColumns = AdminMemberRoleColumns{
+ MemberId: "member_id",
+ RoleId: "role_id",
+}
+
+// NewAdminMemberRoleDao creates and returns a new DAO object for table data access.
+func NewAdminMemberRoleDao(handlers ...gdb.ModelHandler) *AdminMemberRoleDao {
+ return &AdminMemberRoleDao{
+ group: "default",
+ table: "hg_admin_member_role",
+ columns: adminMemberRoleColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *AdminMemberRoleDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *AdminMemberRoleDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *AdminMemberRoleDao) Columns() AdminMemberRoleColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *AdminMemberRoleDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *AdminMemberRoleDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *AdminMemberRoleDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/admin_menu.go b/server/internal/dao/internal/admin_menu.go
new file mode 100755
index 0000000..3312718
--- /dev/null
+++ b/server/internal/dao/internal/admin_menu.go
@@ -0,0 +1,129 @@
+// ==========================================================================
+// 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"
+)
+
+// AdminMenuDao is the data access object for the table hg_admin_menu.
+type AdminMenuDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns AdminMenuColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// AdminMenuColumns defines and stores column names for the table hg_admin_menu.
+type AdminMenuColumns struct {
+ Id string // 菜单ID
+ Pid string // 父菜单ID
+ Level string // 关系树等级
+ Tree string // 关系树
+ Title string // 菜单名称
+ Name string // 名称编码
+ Path string // 路由地址
+ Icon string // 菜单图标
+ Type string // 菜单类型(1目录 2菜单 3按钮)
+ Redirect string // 重定向地址
+ Permissions string // 菜单包含权限集合
+ PermissionName string // 权限名称
+ Component string // 组件路径
+ AlwaysShow string // 取消自动计算根路由模式
+ ActiveMenu string // 高亮菜单编码
+ IsRoot string // 是否跟路由
+ IsFrame string // 是否内嵌
+ FrameSrc string // 内联外部地址
+ KeepAlive string // 缓存该路由
+ Hidden string // 是否隐藏
+ Affix string // 是否固定
+ Sort string // 排序
+ Remark string // 备注
+ Status string // 菜单状态
+ UpdatedAt string // 更新时间
+ CreatedAt string // 创建时间
+}
+
+// adminMenuColumns holds the columns for the table hg_admin_menu.
+var adminMenuColumns = AdminMenuColumns{
+ Id: "id",
+ Pid: "pid",
+ Level: "level",
+ Tree: "tree",
+ Title: "title",
+ Name: "name",
+ Path: "path",
+ Icon: "icon",
+ Type: "type",
+ Redirect: "redirect",
+ Permissions: "permissions",
+ PermissionName: "permission_name",
+ Component: "component",
+ AlwaysShow: "always_show",
+ ActiveMenu: "active_menu",
+ IsRoot: "is_root",
+ IsFrame: "is_frame",
+ FrameSrc: "frame_src",
+ KeepAlive: "keep_alive",
+ Hidden: "hidden",
+ Affix: "affix",
+ Sort: "sort",
+ Remark: "remark",
+ Status: "status",
+ UpdatedAt: "updated_at",
+ CreatedAt: "created_at",
+}
+
+// NewAdminMenuDao creates and returns a new DAO object for table data access.
+func NewAdminMenuDao(handlers ...gdb.ModelHandler) *AdminMenuDao {
+ return &AdminMenuDao{
+ group: "default",
+ table: "hg_admin_menu",
+ columns: adminMenuColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *AdminMenuDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *AdminMenuDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *AdminMenuDao) Columns() AdminMenuColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *AdminMenuDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *AdminMenuDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *AdminMenuDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/admin_notice.go b/server/internal/dao/internal/admin_notice.go
new file mode 100755
index 0000000..ecb0cb9
--- /dev/null
+++ b/server/internal/dao/internal/admin_notice.go
@@ -0,0 +1,105 @@
+// ==========================================================================
+// 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"
+)
+
+// AdminNoticeDao is the data access object for the table hg_admin_notice.
+type AdminNoticeDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns AdminNoticeColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// AdminNoticeColumns defines and stores column names for the table hg_admin_notice.
+type AdminNoticeColumns struct {
+ Id string // 公告ID
+ Title string // 公告标题
+ Type string // 公告类型
+ Tag string // 标签
+ Content string // 公告内容
+ Receiver string // 接收者
+ Remark string // 备注
+ Sort string // 排序
+ Status string // 公告状态
+ CreatedBy string // 发送人
+ UpdatedBy string // 修改人
+ CreatedAt string // 创建时间
+ UpdatedAt string // 更新时间
+ DeletedAt string // 删除时间
+}
+
+// adminNoticeColumns holds the columns for the table hg_admin_notice.
+var adminNoticeColumns = AdminNoticeColumns{
+ Id: "id",
+ Title: "title",
+ Type: "type",
+ Tag: "tag",
+ Content: "content",
+ Receiver: "receiver",
+ Remark: "remark",
+ Sort: "sort",
+ Status: "status",
+ CreatedBy: "created_by",
+ UpdatedBy: "updated_by",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+ DeletedAt: "deleted_at",
+}
+
+// NewAdminNoticeDao creates and returns a new DAO object for table data access.
+func NewAdminNoticeDao(handlers ...gdb.ModelHandler) *AdminNoticeDao {
+ return &AdminNoticeDao{
+ group: "default",
+ table: "hg_admin_notice",
+ columns: adminNoticeColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *AdminNoticeDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *AdminNoticeDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *AdminNoticeDao) Columns() AdminNoticeColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *AdminNoticeDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *AdminNoticeDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *AdminNoticeDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/admin_notice_read.go b/server/internal/dao/internal/admin_notice_read.go
new file mode 100755
index 0000000..2ee0f7b
--- /dev/null
+++ b/server/internal/dao/internal/admin_notice_read.go
@@ -0,0 +1,89 @@
+// ==========================================================================
+// 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"
+)
+
+// AdminNoticeReadDao is the data access object for the table hg_admin_notice_read.
+type AdminNoticeReadDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns AdminNoticeReadColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// AdminNoticeReadColumns defines and stores column names for the table hg_admin_notice_read.
+type AdminNoticeReadColumns struct {
+ Id string // 记录ID
+ NoticeId string // 公告ID
+ MemberId string // 会员ID
+ Clicks string // 已读次数
+ UpdatedAt string // 更新时间
+ CreatedAt string // 阅读时间
+}
+
+// adminNoticeReadColumns holds the columns for the table hg_admin_notice_read.
+var adminNoticeReadColumns = AdminNoticeReadColumns{
+ Id: "id",
+ NoticeId: "notice_id",
+ MemberId: "member_id",
+ Clicks: "clicks",
+ UpdatedAt: "updated_at",
+ CreatedAt: "created_at",
+}
+
+// NewAdminNoticeReadDao creates and returns a new DAO object for table data access.
+func NewAdminNoticeReadDao(handlers ...gdb.ModelHandler) *AdminNoticeReadDao {
+ return &AdminNoticeReadDao{
+ group: "default",
+ table: "hg_admin_notice_read",
+ columns: adminNoticeReadColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *AdminNoticeReadDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *AdminNoticeReadDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *AdminNoticeReadDao) Columns() AdminNoticeReadColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *AdminNoticeReadDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *AdminNoticeReadDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *AdminNoticeReadDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/admin_oauth.go b/server/internal/dao/internal/admin_oauth.go
new file mode 100755
index 0000000..f812ac6
--- /dev/null
+++ b/server/internal/dao/internal/admin_oauth.go
@@ -0,0 +1,107 @@
+// ==========================================================================
+// 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"
+)
+
+// AdminOauthDao is the data access object for the table hg_admin_oauth.
+type AdminOauthDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns AdminOauthColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// AdminOauthColumns defines and stores column names for the table hg_admin_oauth.
+type AdminOauthColumns struct {
+ Id string // 主键
+ MemberId string // 用户ID
+ Unionid string // 唯一ID
+ OauthClient string // 授权组别
+ OauthOpenid string // 授权开放ID
+ Sex string // 性别
+ Nickname string // 昵称
+ HeadPortrait string // 头像
+ Birthday string // 生日
+ Country string // 国家
+ Province string // 省
+ City string // 市
+ Status string // 状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 修改时间
+}
+
+// adminOauthColumns holds the columns for the table hg_admin_oauth.
+var adminOauthColumns = AdminOauthColumns{
+ Id: "id",
+ MemberId: "member_id",
+ Unionid: "unionid",
+ OauthClient: "oauth_client",
+ OauthOpenid: "oauth_openid",
+ Sex: "sex",
+ Nickname: "nickname",
+ HeadPortrait: "head_portrait",
+ Birthday: "birthday",
+ Country: "country",
+ Province: "province",
+ City: "city",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewAdminOauthDao creates and returns a new DAO object for table data access.
+func NewAdminOauthDao(handlers ...gdb.ModelHandler) *AdminOauthDao {
+ return &AdminOauthDao{
+ group: "default",
+ table: "hg_admin_oauth",
+ columns: adminOauthColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *AdminOauthDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *AdminOauthDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *AdminOauthDao) Columns() AdminOauthColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *AdminOauthDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *AdminOauthDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *AdminOauthDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/admin_order.go b/server/internal/dao/internal/admin_order.go
new file mode 100755
index 0000000..8b3df6f
--- /dev/null
+++ b/server/internal/dao/internal/admin_order.go
@@ -0,0 +1,101 @@
+// ==========================================================================
+// 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"
+)
+
+// AdminOrderDao is the data access object for the table hg_admin_order.
+type AdminOrderDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns AdminOrderColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// AdminOrderColumns defines and stores column names for the table hg_admin_order.
+type AdminOrderColumns struct {
+ Id string // 主键
+ MemberId string // 管理员id
+ OrderType string // 订单类型
+ ProductId string // 产品id
+ OrderSn string // 关联订单号
+ Money string // 充值金额
+ Remark string // 备注
+ RefundReason string // 退款原因
+ RejectRefundReason string // 拒绝退款原因
+ Status string // 状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 修改时间
+}
+
+// adminOrderColumns holds the columns for the table hg_admin_order.
+var adminOrderColumns = AdminOrderColumns{
+ Id: "id",
+ MemberId: "member_id",
+ OrderType: "order_type",
+ ProductId: "product_id",
+ OrderSn: "order_sn",
+ Money: "money",
+ Remark: "remark",
+ RefundReason: "refund_reason",
+ RejectRefundReason: "reject_refund_reason",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewAdminOrderDao creates and returns a new DAO object for table data access.
+func NewAdminOrderDao(handlers ...gdb.ModelHandler) *AdminOrderDao {
+ return &AdminOrderDao{
+ group: "default",
+ table: "hg_admin_order",
+ columns: adminOrderColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *AdminOrderDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *AdminOrderDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *AdminOrderDao) Columns() AdminOrderColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *AdminOrderDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *AdminOrderDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *AdminOrderDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/admin_post.go b/server/internal/dao/internal/admin_post.go
new file mode 100755
index 0000000..986103d
--- /dev/null
+++ b/server/internal/dao/internal/admin_post.go
@@ -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"
+)
+
+// AdminPostDao is the data access object for the table hg_admin_post.
+type AdminPostDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns AdminPostColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// AdminPostColumns defines and stores column names for the table hg_admin_post.
+type AdminPostColumns struct {
+ Id string // 岗位ID
+ Code string // 岗位编码
+ Name string // 岗位名称
+ Remark string // 备注
+ Sort string // 排序
+ Status string // 状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 更新时间
+}
+
+// adminPostColumns holds the columns for the table hg_admin_post.
+var adminPostColumns = AdminPostColumns{
+ Id: "id",
+ Code: "code",
+ Name: "name",
+ Remark: "remark",
+ Sort: "sort",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewAdminPostDao creates and returns a new DAO object for table data access.
+func NewAdminPostDao(handlers ...gdb.ModelHandler) *AdminPostDao {
+ return &AdminPostDao{
+ group: "default",
+ table: "hg_admin_post",
+ columns: adminPostColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *AdminPostDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *AdminPostDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *AdminPostDao) Columns() AdminPostColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *AdminPostDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *AdminPostDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *AdminPostDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/admin_role.go b/server/internal/dao/internal/admin_role.go
new file mode 100755
index 0000000..6d8a324
--- /dev/null
+++ b/server/internal/dao/internal/admin_role.go
@@ -0,0 +1,103 @@
+// ==========================================================================
+// 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"
+)
+
+// AdminRoleDao is the data access object for the table hg_admin_role.
+type AdminRoleDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns AdminRoleColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// AdminRoleColumns defines and stores column names for the table hg_admin_role.
+type AdminRoleColumns struct {
+ Id string // 角色ID
+ Name string // 角色名称
+ Key string // 角色权限字符串
+ DataScope string // 数据范围
+ CustomDept string // 自定义部门权限
+ Pid string // 上级角色ID
+ Level string // 关系树等级
+ Tree string // 关系树
+ Remark string // 备注
+ Sort string // 排序
+ Status string // 角色状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 更新时间
+}
+
+// adminRoleColumns holds the columns for the table hg_admin_role.
+var adminRoleColumns = AdminRoleColumns{
+ Id: "id",
+ Name: "name",
+ Key: "key",
+ DataScope: "data_scope",
+ CustomDept: "custom_dept",
+ Pid: "pid",
+ Level: "level",
+ Tree: "tree",
+ Remark: "remark",
+ Sort: "sort",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewAdminRoleDao creates and returns a new DAO object for table data access.
+func NewAdminRoleDao(handlers ...gdb.ModelHandler) *AdminRoleDao {
+ return &AdminRoleDao{
+ group: "default",
+ table: "hg_admin_role",
+ columns: adminRoleColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *AdminRoleDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *AdminRoleDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *AdminRoleDao) Columns() AdminRoleColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *AdminRoleDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *AdminRoleDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *AdminRoleDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/admin_role_casbin.go b/server/internal/dao/internal/admin_role_casbin.go
new file mode 100755
index 0000000..e00b886
--- /dev/null
+++ b/server/internal/dao/internal/admin_role_casbin.go
@@ -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"
+)
+
+// AdminRoleCasbinDao is the data access object for the table hg_admin_role_casbin.
+type AdminRoleCasbinDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns AdminRoleCasbinColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// AdminRoleCasbinColumns defines and stores column names for the table hg_admin_role_casbin.
+type AdminRoleCasbinColumns struct {
+ Id string //
+ PType string //
+ V0 string //
+ V1 string //
+ V2 string //
+ V3 string //
+ V4 string //
+ V5 string //
+}
+
+// adminRoleCasbinColumns holds the columns for the table hg_admin_role_casbin.
+var adminRoleCasbinColumns = AdminRoleCasbinColumns{
+ Id: "id",
+ PType: "p_type",
+ V0: "v0",
+ V1: "v1",
+ V2: "v2",
+ V3: "v3",
+ V4: "v4",
+ V5: "v5",
+}
+
+// NewAdminRoleCasbinDao creates and returns a new DAO object for table data access.
+func NewAdminRoleCasbinDao(handlers ...gdb.ModelHandler) *AdminRoleCasbinDao {
+ return &AdminRoleCasbinDao{
+ group: "default",
+ table: "hg_admin_role_casbin",
+ columns: adminRoleCasbinColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *AdminRoleCasbinDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *AdminRoleCasbinDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *AdminRoleCasbinDao) Columns() AdminRoleCasbinColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *AdminRoleCasbinDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *AdminRoleCasbinDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *AdminRoleCasbinDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/admin_role_menu.go b/server/internal/dao/internal/admin_role_menu.go
new file mode 100755
index 0000000..9f9e4ff
--- /dev/null
+++ b/server/internal/dao/internal/admin_role_menu.go
@@ -0,0 +1,81 @@
+// ==========================================================================
+// 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"
+)
+
+// AdminRoleMenuDao is the data access object for the table hg_admin_role_menu.
+type AdminRoleMenuDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns AdminRoleMenuColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// AdminRoleMenuColumns defines and stores column names for the table hg_admin_role_menu.
+type AdminRoleMenuColumns struct {
+ RoleId string // 角色ID
+ MenuId string // 菜单ID
+}
+
+// adminRoleMenuColumns holds the columns for the table hg_admin_role_menu.
+var adminRoleMenuColumns = AdminRoleMenuColumns{
+ RoleId: "role_id",
+ MenuId: "menu_id",
+}
+
+// NewAdminRoleMenuDao creates and returns a new DAO object for table data access.
+func NewAdminRoleMenuDao(handlers ...gdb.ModelHandler) *AdminRoleMenuDao {
+ return &AdminRoleMenuDao{
+ group: "default",
+ table: "hg_admin_role_menu",
+ columns: adminRoleMenuColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *AdminRoleMenuDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *AdminRoleMenuDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *AdminRoleMenuDao) Columns() AdminRoleMenuColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *AdminRoleMenuDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *AdminRoleMenuDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *AdminRoleMenuDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/pay_log.go b/server/internal/dao/internal/pay_log.go
new file mode 100755
index 0000000..0ef4f61
--- /dev/null
+++ b/server/internal/dao/internal/pay_log.go
@@ -0,0 +1,137 @@
+// ==========================================================================
+// 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"
+)
+
+// PayLogDao is the data access object for the table hg_pay_log.
+type PayLogDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns PayLogColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// PayLogColumns defines and stores column names for the table hg_pay_log.
+type PayLogColumns struct {
+ Id string // 主键
+ MemberId string // 会员ID
+ AppId string // 应用ID
+ AddonsName string // 插件名称
+ OrderSn string // 关联订单号
+ OrderGroup string // 组别[默认统一支付类型]
+ Openid string // openid
+ MchId string // 商户支付账户
+ Subject string // 订单标题
+ Detail string // 支付商品详情
+ AuthCode string // 刷卡码
+ OutTradeNo string // 商户订单号
+ TransactionId string // 交易号
+ PayType string // 支付类型
+ PayAmount string // 支付金额
+ ActualAmount string // 实付金额
+ PayStatus string // 支付状态
+ PayAt string // 支付时间
+ TradeType string // 交易类型
+ RefundSn string // 退款单号
+ IsRefund string // 是否退款
+ Custom string // 自定义参数
+ CreateIp string // 创建者IP
+ PayIp string // 支付者IP
+ NotifyUrl string // 支付通知回调地址
+ ReturnUrl string // 买家付款成功跳转地址
+ TraceIds string // 链路ID集合
+ Status string // 状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 修改时间
+}
+
+// payLogColumns holds the columns for the table hg_pay_log.
+var payLogColumns = PayLogColumns{
+ Id: "id",
+ MemberId: "member_id",
+ AppId: "app_id",
+ AddonsName: "addons_name",
+ OrderSn: "order_sn",
+ OrderGroup: "order_group",
+ Openid: "openid",
+ MchId: "mch_id",
+ Subject: "subject",
+ Detail: "detail",
+ AuthCode: "auth_code",
+ OutTradeNo: "out_trade_no",
+ TransactionId: "transaction_id",
+ PayType: "pay_type",
+ PayAmount: "pay_amount",
+ ActualAmount: "actual_amount",
+ PayStatus: "pay_status",
+ PayAt: "pay_at",
+ TradeType: "trade_type",
+ RefundSn: "refund_sn",
+ IsRefund: "is_refund",
+ Custom: "custom",
+ CreateIp: "create_ip",
+ PayIp: "pay_ip",
+ NotifyUrl: "notify_url",
+ ReturnUrl: "return_url",
+ TraceIds: "trace_ids",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewPayLogDao creates and returns a new DAO object for table data access.
+func NewPayLogDao(handlers ...gdb.ModelHandler) *PayLogDao {
+ return &PayLogDao{
+ group: "default",
+ table: "hg_pay_log",
+ columns: payLogColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *PayLogDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *PayLogDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *PayLogDao) Columns() PayLogColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *PayLogDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *PayLogDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *PayLogDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/pay_refund.go b/server/internal/dao/internal/pay_refund.go
new file mode 100755
index 0000000..8b7684f
--- /dev/null
+++ b/server/internal/dao/internal/pay_refund.go
@@ -0,0 +1,103 @@
+// ==========================================================================
+// 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"
+)
+
+// PayRefundDao is the data access object for the table hg_pay_refund.
+type PayRefundDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns PayRefundColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// PayRefundColumns defines and stores column names for the table hg_pay_refund.
+type PayRefundColumns struct {
+ Id string // 主键ID
+ MemberId string // 会员ID
+ AppId string // 应用ID
+ OrderSn string // 业务订单号
+ RefundTradeNo string // 退款交易号
+ RefundMoney string // 退款金额
+ RefundWay string // 退款方式
+ Ip string // 申请者IP
+ Reason string // 申请退款原因
+ Remark string // 退款备注
+ Status string // 退款状态
+ CreatedAt string // 申请时间
+ UpdatedAt string // 更新时间
+}
+
+// payRefundColumns holds the columns for the table hg_pay_refund.
+var payRefundColumns = PayRefundColumns{
+ Id: "id",
+ MemberId: "member_id",
+ AppId: "app_id",
+ OrderSn: "order_sn",
+ RefundTradeNo: "refund_trade_no",
+ RefundMoney: "refund_money",
+ RefundWay: "refund_way",
+ Ip: "ip",
+ Reason: "reason",
+ Remark: "remark",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewPayRefundDao creates and returns a new DAO object for table data access.
+func NewPayRefundDao(handlers ...gdb.ModelHandler) *PayRefundDao {
+ return &PayRefundDao{
+ group: "default",
+ table: "hg_pay_refund",
+ columns: payRefundColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *PayRefundDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *PayRefundDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *PayRefundDao) Columns() PayRefundColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *PayRefundDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *PayRefundDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *PayRefundDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/sys_addons_config.go b/server/internal/dao/internal/sys_addons_config.go
new file mode 100755
index 0000000..fa73736
--- /dev/null
+++ b/server/internal/dao/internal/sys_addons_config.go
@@ -0,0 +1,105 @@
+// ==========================================================================
+// 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"
+)
+
+// SysAddonsConfigDao is the data access object for the table hg_sys_addons_config.
+type SysAddonsConfigDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns SysAddonsConfigColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// SysAddonsConfigColumns defines and stores column names for the table hg_sys_addons_config.
+type SysAddonsConfigColumns struct {
+ Id string // 配置ID
+ AddonName string // 插件名称
+ Group string // 分组
+ Name string // 参数名称
+ Type string // 键值类型:string,int,uint,bool,TIMESTAMP,date
+ Key string // 参数键名
+ Value string // 参数键值
+ DefaultValue string // 默认值
+ Sort string // 排序
+ Tip string // 变量描述
+ IsDefault string // 是否为系统默认
+ Status string // 状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 更新时间
+}
+
+// sysAddonsConfigColumns holds the columns for the table hg_sys_addons_config.
+var sysAddonsConfigColumns = SysAddonsConfigColumns{
+ Id: "id",
+ AddonName: "addon_name",
+ Group: "group",
+ Name: "name",
+ Type: "type",
+ Key: "key",
+ Value: "value",
+ DefaultValue: "default_value",
+ Sort: "sort",
+ Tip: "tip",
+ IsDefault: "is_default",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewSysAddonsConfigDao creates and returns a new DAO object for table data access.
+func NewSysAddonsConfigDao(handlers ...gdb.ModelHandler) *SysAddonsConfigDao {
+ return &SysAddonsConfigDao{
+ group: "default",
+ table: "hg_sys_addons_config",
+ columns: sysAddonsConfigColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *SysAddonsConfigDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *SysAddonsConfigDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *SysAddonsConfigDao) Columns() SysAddonsConfigColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *SysAddonsConfigDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *SysAddonsConfigDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *SysAddonsConfigDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/sys_addons_install.go b/server/internal/dao/internal/sys_addons_install.go
new file mode 100644
index 0000000..9611545
--- /dev/null
+++ b/server/internal/dao/internal/sys_addons_install.go
@@ -0,0 +1,89 @@
+// ==========================================================================
+// 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"
+)
+
+// SysAddonsInstallDao is the data access object for the table hg_sys_addons_install.
+type SysAddonsInstallDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns SysAddonsInstallColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// SysAddonsInstallColumns defines and stores column names for the table hg_sys_addons_install.
+type SysAddonsInstallColumns struct {
+ Id string // 主键
+ Name string // 插件名称
+ Version string // 版本号
+ Status string // 状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 更新时间
+}
+
+// sysAddonsInstallColumns holds the columns for the table hg_sys_addons_install.
+var sysAddonsInstallColumns = SysAddonsInstallColumns{
+ Id: "id",
+ Name: "name",
+ Version: "version",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewSysAddonsInstallDao creates and returns a new DAO object for table data access.
+func NewSysAddonsInstallDao(handlers ...gdb.ModelHandler) *SysAddonsInstallDao {
+ return &SysAddonsInstallDao{
+ group: "default",
+ table: "hg_sys_addons_install",
+ columns: sysAddonsInstallColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *SysAddonsInstallDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *SysAddonsInstallDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *SysAddonsInstallDao) Columns() SysAddonsInstallColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *SysAddonsInstallDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *SysAddonsInstallDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *SysAddonsInstallDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/sys_attachment.go b/server/internal/dao/internal/sys_attachment.go
new file mode 100755
index 0000000..dcd6558
--- /dev/null
+++ b/server/internal/dao/internal/sys_attachment.go
@@ -0,0 +1,111 @@
+// ==========================================================================
+// 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"
+)
+
+// SysAttachmentDao is the data access object for the table hg_sys_attachment.
+type SysAttachmentDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns SysAttachmentColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// SysAttachmentColumns defines and stores column names for the table hg_sys_attachment.
+type SysAttachmentColumns struct {
+ Id string // 文件ID
+ AppId string // 应用ID
+ MemberId string // 管理员ID
+ CateId string // 上传分类
+ Drive string // 上传驱动
+ Name string // 文件原始名
+ Kind string // 上传类型
+ MimeType string // 扩展类型
+ NaiveType string // NaiveUI类型
+ Path string // 本地路径
+ FileUrl string // url
+ Size string // 文件大小
+ Ext string // 扩展名
+ Md5 string // md5校验码
+ Status string // 状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 修改时间
+}
+
+// sysAttachmentColumns holds the columns for the table hg_sys_attachment.
+var sysAttachmentColumns = SysAttachmentColumns{
+ Id: "id",
+ AppId: "app_id",
+ MemberId: "member_id",
+ CateId: "cate_id",
+ Drive: "drive",
+ Name: "name",
+ Kind: "kind",
+ MimeType: "mime_type",
+ NaiveType: "naive_type",
+ Path: "path",
+ FileUrl: "file_url",
+ Size: "size",
+ Ext: "ext",
+ Md5: "md5",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewSysAttachmentDao creates and returns a new DAO object for table data access.
+func NewSysAttachmentDao(handlers ...gdb.ModelHandler) *SysAttachmentDao {
+ return &SysAttachmentDao{
+ group: "default",
+ table: "hg_sys_attachment",
+ columns: sysAttachmentColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *SysAttachmentDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *SysAttachmentDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *SysAttachmentDao) Columns() SysAttachmentColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *SysAttachmentDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *SysAttachmentDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *SysAttachmentDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/sys_blacklist.go b/server/internal/dao/internal/sys_blacklist.go
new file mode 100755
index 0000000..1838a14
--- /dev/null
+++ b/server/internal/dao/internal/sys_blacklist.go
@@ -0,0 +1,89 @@
+// ==========================================================================
+// 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"
+)
+
+// SysBlacklistDao is the data access object for the table hg_sys_blacklist.
+type SysBlacklistDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns SysBlacklistColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// SysBlacklistColumns defines and stores column names for the table hg_sys_blacklist.
+type SysBlacklistColumns struct {
+ Id string // 黑名单ID
+ Ip string // IP地址
+ Remark string // 备注
+ Status string // 状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 更新时间
+}
+
+// sysBlacklistColumns holds the columns for the table hg_sys_blacklist.
+var sysBlacklistColumns = SysBlacklistColumns{
+ Id: "id",
+ Ip: "ip",
+ Remark: "remark",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewSysBlacklistDao creates and returns a new DAO object for table data access.
+func NewSysBlacklistDao(handlers ...gdb.ModelHandler) *SysBlacklistDao {
+ return &SysBlacklistDao{
+ group: "default",
+ table: "hg_sys_blacklist",
+ columns: sysBlacklistColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *SysBlacklistDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *SysBlacklistDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *SysBlacklistDao) Columns() SysBlacklistColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *SysBlacklistDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *SysBlacklistDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *SysBlacklistDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/sys_config.go b/server/internal/dao/internal/sys_config.go
new file mode 100755
index 0000000..5c46adb
--- /dev/null
+++ b/server/internal/dao/internal/sys_config.go
@@ -0,0 +1,103 @@
+// ==========================================================================
+// 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"
+)
+
+// SysConfigDao is the data access object for the table hg_sys_config.
+type SysConfigDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns SysConfigColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// SysConfigColumns defines and stores column names for the table hg_sys_config.
+type SysConfigColumns struct {
+ Id string // 配置ID
+ Group string // 配置分组
+ Name string // 参数名称
+ Type string // 键值类型:string,int,uint,bool,TIMESTAMP,date
+ Key string // 参数键名
+ Value string // 参数键值
+ DefaultValue string // 默认值
+ Sort string // 排序
+ Tip string // 变量描述
+ IsDefault string // 是否为系统默认
+ Status string // 状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 更新时间
+}
+
+// sysConfigColumns holds the columns for the table hg_sys_config.
+var sysConfigColumns = SysConfigColumns{
+ Id: "id",
+ Group: "group",
+ Name: "name",
+ Type: "type",
+ Key: "key",
+ Value: "value",
+ DefaultValue: "default_value",
+ Sort: "sort",
+ Tip: "tip",
+ IsDefault: "is_default",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewSysConfigDao creates and returns a new DAO object for table data access.
+func NewSysConfigDao(handlers ...gdb.ModelHandler) *SysConfigDao {
+ return &SysConfigDao{
+ group: "default",
+ table: "hg_sys_config",
+ columns: sysConfigColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *SysConfigDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *SysConfigDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *SysConfigDao) Columns() SysConfigColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *SysConfigDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *SysConfigDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *SysConfigDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/sys_cron.go b/server/internal/dao/internal/sys_cron.go
new file mode 100755
index 0000000..a8a8dac
--- /dev/null
+++ b/server/internal/dao/internal/sys_cron.go
@@ -0,0 +1,103 @@
+// ==========================================================================
+// 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"
+)
+
+// SysCronDao is the data access object for the table hg_sys_cron.
+type SysCronDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns SysCronColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// SysCronColumns defines and stores column names for the table hg_sys_cron.
+type SysCronColumns struct {
+ Id string // 任务ID
+ GroupId string // 分组ID
+ Title string // 任务标题
+ Name string // 任务方法
+ Params string // 函数参数
+ Pattern string // 表达式
+ Policy string // 策略
+ Count string // 执行次数
+ Sort string // 排序
+ Remark string // 备注
+ Status string // 任务状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 更新时间
+}
+
+// sysCronColumns holds the columns for the table hg_sys_cron.
+var sysCronColumns = SysCronColumns{
+ Id: "id",
+ GroupId: "group_id",
+ Title: "title",
+ Name: "name",
+ Params: "params",
+ Pattern: "pattern",
+ Policy: "policy",
+ Count: "count",
+ Sort: "sort",
+ Remark: "remark",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewSysCronDao creates and returns a new DAO object for table data access.
+func NewSysCronDao(handlers ...gdb.ModelHandler) *SysCronDao {
+ return &SysCronDao{
+ group: "default",
+ table: "hg_sys_cron",
+ columns: sysCronColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *SysCronDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *SysCronDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *SysCronDao) Columns() SysCronColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *SysCronDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *SysCronDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *SysCronDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/sys_cron_group.go b/server/internal/dao/internal/sys_cron_group.go
new file mode 100755
index 0000000..983a238
--- /dev/null
+++ b/server/internal/dao/internal/sys_cron_group.go
@@ -0,0 +1,95 @@
+// ==========================================================================
+// 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"
+)
+
+// SysCronGroupDao is the data access object for the table hg_sys_cron_group.
+type SysCronGroupDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns SysCronGroupColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// SysCronGroupColumns defines and stores column names for the table hg_sys_cron_group.
+type SysCronGroupColumns struct {
+ Id string // 任务分组ID
+ Pid string // 父类任务分组ID
+ Name string // 分组名称
+ IsDefault string // 是否默认
+ Sort string // 排序
+ Remark string // 备注
+ Status string // 分组状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 更新时间
+}
+
+// sysCronGroupColumns holds the columns for the table hg_sys_cron_group.
+var sysCronGroupColumns = SysCronGroupColumns{
+ Id: "id",
+ Pid: "pid",
+ Name: "name",
+ IsDefault: "is_default",
+ Sort: "sort",
+ Remark: "remark",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewSysCronGroupDao creates and returns a new DAO object for table data access.
+func NewSysCronGroupDao(handlers ...gdb.ModelHandler) *SysCronGroupDao {
+ return &SysCronGroupDao{
+ group: "default",
+ table: "hg_sys_cron_group",
+ columns: sysCronGroupColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *SysCronGroupDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *SysCronGroupDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *SysCronGroupDao) Columns() SysCronGroupColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *SysCronGroupDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *SysCronGroupDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *SysCronGroupDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/sys_dict_data.go b/server/internal/dao/internal/sys_dict_data.go
new file mode 100755
index 0000000..67caa80
--- /dev/null
+++ b/server/internal/dao/internal/sys_dict_data.go
@@ -0,0 +1,101 @@
+// ==========================================================================
+// 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"
+)
+
+// SysDictDataDao is the data access object for the table hg_sys_dict_data.
+type SysDictDataDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns SysDictDataColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// SysDictDataColumns defines and stores column names for the table hg_sys_dict_data.
+type SysDictDataColumns struct {
+ Id string // 字典数据ID
+ Label string // 字典标签
+ Value string // 字典键值
+ ValueType string // 键值数据类型:string,int,uint,bool,TIMESTAMP,date
+ Type string // 字典类型
+ ListClass string // 表格回显样式
+ IsDefault string // 是否为系统默认
+ Sort string // 字典排序
+ Remark string // 备注
+ Status string // 状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 更新时间
+}
+
+// sysDictDataColumns holds the columns for the table hg_sys_dict_data.
+var sysDictDataColumns = SysDictDataColumns{
+ Id: "id",
+ Label: "label",
+ Value: "value",
+ ValueType: "value_type",
+ Type: "type",
+ ListClass: "list_class",
+ IsDefault: "is_default",
+ Sort: "sort",
+ Remark: "remark",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewSysDictDataDao creates and returns a new DAO object for table data access.
+func NewSysDictDataDao(handlers ...gdb.ModelHandler) *SysDictDataDao {
+ return &SysDictDataDao{
+ group: "default",
+ table: "hg_sys_dict_data",
+ columns: sysDictDataColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *SysDictDataDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *SysDictDataDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *SysDictDataDao) Columns() SysDictDataColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *SysDictDataDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *SysDictDataDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *SysDictDataDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/sys_dict_type.go b/server/internal/dao/internal/sys_dict_type.go
new file mode 100755
index 0000000..b0b05f7
--- /dev/null
+++ b/server/internal/dao/internal/sys_dict_type.go
@@ -0,0 +1,95 @@
+// ==========================================================================
+// 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"
+)
+
+// SysDictTypeDao is the data access object for the table hg_sys_dict_type.
+type SysDictTypeDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns SysDictTypeColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// SysDictTypeColumns defines and stores column names for the table hg_sys_dict_type.
+type SysDictTypeColumns struct {
+ Id string // 字典类型ID
+ Pid string // 父类字典类型ID
+ Name string // 字典类型名称
+ Type string // 字典类型
+ Sort string // 排序
+ Remark string // 备注
+ Status string // 字典类型状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 更新时间
+}
+
+// sysDictTypeColumns holds the columns for the table hg_sys_dict_type.
+var sysDictTypeColumns = SysDictTypeColumns{
+ Id: "id",
+ Pid: "pid",
+ Name: "name",
+ Type: "type",
+ Sort: "sort",
+ Remark: "remark",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewSysDictTypeDao creates and returns a new DAO object for table data access.
+func NewSysDictTypeDao(handlers ...gdb.ModelHandler) *SysDictTypeDao {
+ return &SysDictTypeDao{
+ group: "default",
+ table: "hg_sys_dict_type",
+ columns: sysDictTypeColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *SysDictTypeDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *SysDictTypeDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *SysDictTypeDao) Columns() SysDictTypeColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *SysDictTypeDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *SysDictTypeDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *SysDictTypeDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/sys_ems_log.go b/server/internal/dao/internal/sys_ems_log.go
new file mode 100755
index 0000000..3cac58d
--- /dev/null
+++ b/server/internal/dao/internal/sys_ems_log.go
@@ -0,0 +1,97 @@
+// ==========================================================================
+// 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"
+)
+
+// SysEmsLogDao is the data access object for the table hg_sys_ems_log.
+type SysEmsLogDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns SysEmsLogColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// SysEmsLogColumns defines and stores column names for the table hg_sys_ems_log.
+type SysEmsLogColumns struct {
+ Id string // 主键
+ Event string // 事件
+ Email string // 邮箱地址,多个用;隔开
+ Code string // 验证码
+ Times string // 验证次数
+ Content string // 邮件内容
+ Ip string // ip地址
+ Status string // 状态(1未验证,2已验证)
+ CreatedAt string // 创建时间
+ UpdatedAt string // 更新时间
+}
+
+// sysEmsLogColumns holds the columns for the table hg_sys_ems_log.
+var sysEmsLogColumns = SysEmsLogColumns{
+ Id: "id",
+ Event: "event",
+ Email: "email",
+ Code: "code",
+ Times: "times",
+ Content: "content",
+ Ip: "ip",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewSysEmsLogDao creates and returns a new DAO object for table data access.
+func NewSysEmsLogDao(handlers ...gdb.ModelHandler) *SysEmsLogDao {
+ return &SysEmsLogDao{
+ group: "default",
+ table: "hg_sys_ems_log",
+ columns: sysEmsLogColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *SysEmsLogDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *SysEmsLogDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *SysEmsLogDao) Columns() SysEmsLogColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *SysEmsLogDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *SysEmsLogDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *SysEmsLogDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/sys_gen_codes.go b/server/internal/dao/internal/sys_gen_codes.go
new file mode 100755
index 0000000..e909a3f
--- /dev/null
+++ b/server/internal/dao/internal/sys_gen_codes.go
@@ -0,0 +1,105 @@
+// ==========================================================================
+// 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"
+)
+
+// SysGenCodesDao is the data access object for the table hg_sys_gen_codes.
+type SysGenCodesDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns SysGenCodesColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// SysGenCodesColumns defines and stores column names for the table hg_sys_gen_codes.
+type SysGenCodesColumns struct {
+ Id string // 生成ID
+ GenType string // 生成类型
+ GenTemplate string // 生成模板
+ VarName string // 实体命名
+ Options string // 配置选项
+ DbName string // 数据库名称
+ TableName string // 主表名称
+ TableComment string // 主表注释
+ DaoName string // 主表dao模型
+ MasterColumns string // 主表字段
+ AddonName string // 插件名称
+ Status string // 生成状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 更新时间
+}
+
+// sysGenCodesColumns holds the columns for the table hg_sys_gen_codes.
+var sysGenCodesColumns = SysGenCodesColumns{
+ Id: "id",
+ GenType: "gen_type",
+ GenTemplate: "gen_template",
+ VarName: "var_name",
+ Options: "options",
+ DbName: "db_name",
+ TableName: "table_name",
+ TableComment: "table_comment",
+ DaoName: "dao_name",
+ MasterColumns: "master_columns",
+ AddonName: "addon_name",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewSysGenCodesDao creates and returns a new DAO object for table data access.
+func NewSysGenCodesDao(handlers ...gdb.ModelHandler) *SysGenCodesDao {
+ return &SysGenCodesDao{
+ group: "default",
+ table: "hg_sys_gen_codes",
+ columns: sysGenCodesColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *SysGenCodesDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *SysGenCodesDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *SysGenCodesDao) Columns() SysGenCodesColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *SysGenCodesDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *SysGenCodesDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *SysGenCodesDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/sys_gen_curd_demo.go b/server/internal/dao/internal/sys_gen_curd_demo.go
new file mode 100755
index 0000000..25bd694
--- /dev/null
+++ b/server/internal/dao/internal/sys_gen_curd_demo.go
@@ -0,0 +1,111 @@
+// ==========================================================================
+// 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"
+)
+
+// SysGenCurdDemoDao is the data access object for the table hg_sys_gen_curd_demo.
+type SysGenCurdDemoDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns SysGenCurdDemoColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// SysGenCurdDemoColumns defines and stores column names for the table hg_sys_gen_curd_demo.
+type SysGenCurdDemoColumns struct {
+ Id string // ID
+ CategoryId string // 分类ID
+ Title string // 标题
+ Description string // 描述
+ Content string // 内容
+ Image string // 单图
+ Attachfile string // 附件
+ CityId string // 所在城市
+ Switch string // 显示开关
+ Sort string // 排序
+ Status string // 状态
+ CreatedBy string // 创建者
+ UpdatedBy string // 更新者
+ DeletedBy string // 删除者
+ CreatedAt string // 创建时间
+ UpdatedAt string // 修改时间
+ DeletedAt string // 删除时间
+}
+
+// sysGenCurdDemoColumns holds the columns for the table hg_sys_gen_curd_demo.
+var sysGenCurdDemoColumns = SysGenCurdDemoColumns{
+ Id: "id",
+ CategoryId: "category_id",
+ Title: "title",
+ Description: "description",
+ Content: "content",
+ Image: "image",
+ Attachfile: "attachfile",
+ CityId: "city_id",
+ Switch: "switch",
+ Sort: "sort",
+ Status: "status",
+ CreatedBy: "created_by",
+ UpdatedBy: "updated_by",
+ DeletedBy: "deleted_by",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+ DeletedAt: "deleted_at",
+}
+
+// NewSysGenCurdDemoDao creates and returns a new DAO object for table data access.
+func NewSysGenCurdDemoDao(handlers ...gdb.ModelHandler) *SysGenCurdDemoDao {
+ return &SysGenCurdDemoDao{
+ group: "default",
+ table: "hg_sys_gen_curd_demo",
+ columns: sysGenCurdDemoColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *SysGenCurdDemoDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *SysGenCurdDemoDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *SysGenCurdDemoDao) Columns() SysGenCurdDemoColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *SysGenCurdDemoDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *SysGenCurdDemoDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *SysGenCurdDemoDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/sys_gen_tree_demo.go b/server/internal/dao/internal/sys_gen_tree_demo.go
new file mode 100755
index 0000000..5e3cdc5
--- /dev/null
+++ b/server/internal/dao/internal/sys_gen_tree_demo.go
@@ -0,0 +1,105 @@
+// ==========================================================================
+// 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 the 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 the current DAO.
+ columns SysGenTreeDemoColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// SysGenTreeDemoColumns defines and stores column names for the 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 the 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(handlers ...gdb.ModelHandler) *SysGenTreeDemoDao {
+ return &SysGenTreeDemoDao{
+ group: "default",
+ table: "hg_sys_gen_tree_demo",
+ columns: sysGenTreeDemoColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *SysGenTreeDemoDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *SysGenTreeDemoDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *SysGenTreeDemoDao) Columns() SysGenTreeDemoColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *SysGenTreeDemoDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *SysGenTreeDemoDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back 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)
+}
diff --git a/server/internal/dao/internal/sys_log.go b/server/internal/dao/internal/sys_log.go
new file mode 100755
index 0000000..79813b3
--- /dev/null
+++ b/server/internal/dao/internal/sys_log.go
@@ -0,0 +1,123 @@
+// ==========================================================================
+// 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"
+)
+
+// SysLogDao is the data access object for the table hg_sys_log.
+type SysLogDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns SysLogColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// SysLogColumns defines and stores column names for the table hg_sys_log.
+type SysLogColumns struct {
+ Id string // 日志ID
+ ReqId string // 对外ID
+ AppId string // 应用ID
+ MerchantId string // 商户ID
+ MemberId string // 用户ID
+ Method string // 提交类型
+ Module string // 访问模块
+ Url string // 提交url
+ GetData string // get数据
+ PostData string // post数据
+ HeaderData string // header数据
+ Ip string // IP地址
+ ProvinceId string // 省编码
+ CityId string // 市编码
+ ErrorCode string // 报错code
+ ErrorMsg string // 对外错误提示
+ ErrorData string // 报错日志
+ UserAgent string // UA信息
+ TakeUpTime string // 请求耗时
+ Timestamp string // 响应时间
+ Status string // 状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 修改时间
+}
+
+// sysLogColumns holds the columns for the table hg_sys_log.
+var sysLogColumns = SysLogColumns{
+ Id: "id",
+ ReqId: "req_id",
+ AppId: "app_id",
+ MerchantId: "merchant_id",
+ MemberId: "member_id",
+ Method: "method",
+ Module: "module",
+ Url: "url",
+ GetData: "get_data",
+ PostData: "post_data",
+ HeaderData: "header_data",
+ Ip: "ip",
+ ProvinceId: "province_id",
+ CityId: "city_id",
+ ErrorCode: "error_code",
+ ErrorMsg: "error_msg",
+ ErrorData: "error_data",
+ UserAgent: "user_agent",
+ TakeUpTime: "take_up_time",
+ Timestamp: "timestamp",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewSysLogDao creates and returns a new DAO object for table data access.
+func NewSysLogDao(handlers ...gdb.ModelHandler) *SysLogDao {
+ return &SysLogDao{
+ group: "default",
+ table: "hg_sys_log",
+ columns: sysLogColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *SysLogDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *SysLogDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *SysLogDao) Columns() SysLogColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *SysLogDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *SysLogDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *SysLogDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/sys_login_log.go b/server/internal/dao/internal/sys_login_log.go
new file mode 100755
index 0000000..0637049
--- /dev/null
+++ b/server/internal/dao/internal/sys_login_log.go
@@ -0,0 +1,105 @@
+// ==========================================================================
+// 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"
+)
+
+// SysLoginLogDao is the data access object for the table hg_sys_login_log.
+type SysLoginLogDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns SysLoginLogColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// SysLoginLogColumns defines and stores column names for the 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
+ ProvinceId string // 省编码
+ CityId string // 市编码
+ UserAgent string // UA信息
+ ErrMsg string // 错误提示
+ Status string // 状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 修改时间
+}
+
+// sysLoginLogColumns holds the columns for the 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",
+ 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.
+func NewSysLoginLogDao(handlers ...gdb.ModelHandler) *SysLoginLogDao {
+ return &SysLoginLogDao{
+ group: "default",
+ table: "hg_sys_login_log",
+ columns: sysLoginLogColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *SysLoginLogDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *SysLoginLogDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *SysLoginLogDao) Columns() SysLoginLogColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *SysLoginLogDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *SysLoginLogDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *SysLoginLogDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/sys_provinces.go b/server/internal/dao/internal/sys_provinces.go
new file mode 100755
index 0000000..377de59
--- /dev/null
+++ b/server/internal/dao/internal/sys_provinces.go
@@ -0,0 +1,101 @@
+// ==========================================================================
+// 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"
+)
+
+// SysProvincesDao is the data access object for the table hg_sys_provinces.
+type SysProvincesDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns SysProvincesColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// SysProvincesColumns defines and stores column names for the table hg_sys_provinces.
+type SysProvincesColumns struct {
+ Id string // 省市区ID
+ Title string // 栏目名称
+ Pinyin string // 拼音
+ Lng string // 经度
+ Lat string // 纬度
+ Pid string // 父栏目
+ Level string // 关系树等级
+ Tree string // 关系
+ Sort string // 排序
+ Status string // 状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 更新时间
+}
+
+// sysProvincesColumns holds the columns for the table hg_sys_provinces.
+var sysProvincesColumns = SysProvincesColumns{
+ Id: "id",
+ Title: "title",
+ Pinyin: "pinyin",
+ Lng: "lng",
+ Lat: "lat",
+ Pid: "pid",
+ Level: "level",
+ Tree: "tree",
+ Sort: "sort",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewSysProvincesDao creates and returns a new DAO object for table data access.
+func NewSysProvincesDao(handlers ...gdb.ModelHandler) *SysProvincesDao {
+ return &SysProvincesDao{
+ group: "default",
+ table: "hg_sys_provinces",
+ columns: sysProvincesColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *SysProvincesDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *SysProvincesDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *SysProvincesDao) Columns() SysProvincesColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *SysProvincesDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *SysProvincesDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *SysProvincesDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/sys_serve_license.go b/server/internal/dao/internal/sys_serve_license.go
new file mode 100755
index 0000000..46ef2f5
--- /dev/null
+++ b/server/internal/dao/internal/sys_serve_license.go
@@ -0,0 +1,111 @@
+// ==========================================================================
+// 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"
+)
+
+// SysServeLicenseDao is the data access object for the table hg_sys_serve_license.
+type SysServeLicenseDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns SysServeLicenseColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// SysServeLicenseColumns defines and stores column names for the table hg_sys_serve_license.
+type SysServeLicenseColumns struct {
+ Id string // 许可ID
+ Group string // 分组
+ Name string // 许可名称
+ Appid string // 应用ID
+ SecretKey string // 应用秘钥
+ RemoteAddr string // 最后连接地址
+ OnlineLimit string // 在线限制
+ LoginTimes string // 登录次数
+ LastLoginAt string // 最后登录时间
+ LastActiveAt string // 最后心跳
+ Routes string // 路由表,空使用默认分组路由
+ AllowedIps string // IP白名单
+ EndAt string // 授权有效期
+ Remark string // 备注
+ Status string // 状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 修改时间
+}
+
+// sysServeLicenseColumns holds the columns for the table hg_sys_serve_license.
+var sysServeLicenseColumns = SysServeLicenseColumns{
+ Id: "id",
+ Group: "group",
+ Name: "name",
+ Appid: "appid",
+ SecretKey: "secret_key",
+ RemoteAddr: "remote_addr",
+ OnlineLimit: "online_limit",
+ LoginTimes: "login_times",
+ LastLoginAt: "last_login_at",
+ LastActiveAt: "last_active_at",
+ Routes: "routes",
+ AllowedIps: "allowed_ips",
+ EndAt: "end_at",
+ Remark: "remark",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewSysServeLicenseDao creates and returns a new DAO object for table data access.
+func NewSysServeLicenseDao(handlers ...gdb.ModelHandler) *SysServeLicenseDao {
+ return &SysServeLicenseDao{
+ group: "default",
+ table: "hg_sys_serve_license",
+ columns: sysServeLicenseColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *SysServeLicenseDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *SysServeLicenseDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *SysServeLicenseDao) Columns() SysServeLicenseColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *SysServeLicenseDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *SysServeLicenseDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *SysServeLicenseDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/sys_serve_log.go b/server/internal/dao/internal/sys_serve_log.go
new file mode 100755
index 0000000..abf52f9
--- /dev/null
+++ b/server/internal/dao/internal/sys_serve_log.go
@@ -0,0 +1,97 @@
+// ==========================================================================
+// 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"
+)
+
+// SysServeLogDao is the data access object for the table hg_sys_serve_log.
+type SysServeLogDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns SysServeLogColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// SysServeLogColumns defines and stores column names for the table hg_sys_serve_log.
+type SysServeLogColumns struct {
+ Id string // 日志ID
+ TraceId string // 链路ID
+ LevelFormat string // 日志级别
+ Content string // 日志内容
+ Stack string // 打印堆栈
+ Line string // 调用行
+ TriggerNs string // 触发时间(ns)
+ Status string // 状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 修改时间
+}
+
+// sysServeLogColumns holds the columns for the table hg_sys_serve_log.
+var sysServeLogColumns = SysServeLogColumns{
+ Id: "id",
+ TraceId: "trace_id",
+ LevelFormat: "level_format",
+ Content: "content",
+ Stack: "stack",
+ Line: "line",
+ TriggerNs: "trigger_ns",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewSysServeLogDao creates and returns a new DAO object for table data access.
+func NewSysServeLogDao(handlers ...gdb.ModelHandler) *SysServeLogDao {
+ return &SysServeLogDao{
+ group: "default",
+ table: "hg_sys_serve_log",
+ columns: sysServeLogColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *SysServeLogDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *SysServeLogDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *SysServeLogDao) Columns() SysServeLogColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *SysServeLogDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *SysServeLogDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *SysServeLogDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/sys_sms_log.go b/server/internal/dao/internal/sys_sms_log.go
new file mode 100755
index 0000000..da73d87
--- /dev/null
+++ b/server/internal/dao/internal/sys_sms_log.go
@@ -0,0 +1,95 @@
+// ==========================================================================
+// 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"
+)
+
+// SysSmsLogDao is the data access object for the table hg_sys_sms_log.
+type SysSmsLogDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns SysSmsLogColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// SysSmsLogColumns defines and stores column names for the table hg_sys_sms_log.
+type SysSmsLogColumns struct {
+ Id string // 主键
+ Event string // 事件
+ Mobile string // 手机号
+ Code string // 验证码或短信内容
+ Times string // 验证次数
+ Ip string // ip地址
+ Status string // 状态(1未验证,2已验证)
+ CreatedAt string // 创建时间
+ UpdatedAt string // 更新时间
+}
+
+// sysSmsLogColumns holds the columns for the table hg_sys_sms_log.
+var sysSmsLogColumns = SysSmsLogColumns{
+ Id: "id",
+ Event: "event",
+ Mobile: "mobile",
+ Code: "code",
+ Times: "times",
+ Ip: "ip",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+}
+
+// NewSysSmsLogDao creates and returns a new DAO object for table data access.
+func NewSysSmsLogDao(handlers ...gdb.ModelHandler) *SysSmsLogDao {
+ return &SysSmsLogDao{
+ group: "default",
+ table: "hg_sys_sms_log",
+ columns: sysSmsLogColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *SysSmsLogDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *SysSmsLogDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *SysSmsLogDao) Columns() SysSmsLogColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *SysSmsLogDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *SysSmsLogDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *SysSmsLogDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/internal/test_category.go b/server/internal/dao/internal/test_category.go
new file mode 100755
index 0000000..1ebbc81
--- /dev/null
+++ b/server/internal/dao/internal/test_category.go
@@ -0,0 +1,97 @@
+// ==========================================================================
+// 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"
+)
+
+// TestCategoryDao is the data access object for the table hg_test_category.
+type TestCategoryDao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns TestCategoryColumns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// TestCategoryColumns defines and stores column names for the table hg_test_category.
+type TestCategoryColumns struct {
+ Id string // 分类ID
+ Name string // 分类名称
+ ShortName string // 简称
+ Description string // 描述
+ Sort string // 排序
+ Remark string // 备注
+ Status string // 状态
+ CreatedAt string // 创建时间
+ UpdatedAt string // 修改时间
+ DeletedAt string // 删除时间
+}
+
+// testCategoryColumns holds the columns for the table hg_test_category.
+var testCategoryColumns = TestCategoryColumns{
+ Id: "id",
+ Name: "name",
+ ShortName: "short_name",
+ Description: "description",
+ Sort: "sort",
+ Remark: "remark",
+ Status: "status",
+ CreatedAt: "created_at",
+ UpdatedAt: "updated_at",
+ DeletedAt: "deleted_at",
+}
+
+// NewTestCategoryDao creates and returns a new DAO object for table data access.
+func NewTestCategoryDao(handlers ...gdb.ModelHandler) *TestCategoryDao {
+ return &TestCategoryDao{
+ group: "default",
+ table: "hg_test_category",
+ columns: testCategoryColumns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *TestCategoryDao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *TestCategoryDao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *TestCategoryDao) Columns() TestCategoryColumns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *TestCategoryDao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *TestCategoryDao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *TestCategoryDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
diff --git a/server/internal/dao/pay_log.go b/server/internal/dao/pay_log.go
new file mode 100755
index 0000000..eef43f9
--- /dev/null
+++ b/server/internal/dao/pay_log.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// payLogDao is the data access object for the table hg_pay_log.
+// You can define custom methods on it to extend its functionality as needed.
+type payLogDao struct {
+ *internal.PayLogDao
+}
+
+var (
+ // PayLog is a globally accessible object for table hg_pay_log operations.
+ PayLog = payLogDao{internal.NewPayLogDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/pay_refund.go b/server/internal/dao/pay_refund.go
new file mode 100755
index 0000000..bc52795
--- /dev/null
+++ b/server/internal/dao/pay_refund.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// payRefundDao is the data access object for the table hg_pay_refund.
+// You can define custom methods on it to extend its functionality as needed.
+type payRefundDao struct {
+ *internal.PayRefundDao
+}
+
+var (
+ // PayRefund is a globally accessible object for table hg_pay_refund operations.
+ PayRefund = payRefundDao{internal.NewPayRefundDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/sys_addons_config.go b/server/internal/dao/sys_addons_config.go
new file mode 100755
index 0000000..47c7893
--- /dev/null
+++ b/server/internal/dao/sys_addons_config.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// sysAddonsConfigDao is the data access object for the table hg_sys_addons_config.
+// You can define custom methods on it to extend its functionality as needed.
+type sysAddonsConfigDao struct {
+ *internal.SysAddonsConfigDao
+}
+
+var (
+ // SysAddonsConfig is a globally accessible object for table hg_sys_addons_config operations.
+ SysAddonsConfig = sysAddonsConfigDao{internal.NewSysAddonsConfigDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/sys_addons_install.go b/server/internal/dao/sys_addons_install.go
new file mode 100644
index 0000000..3eb6bac
--- /dev/null
+++ b/server/internal/dao/sys_addons_install.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// sysAddonsInstallDao is the data access object for the table hg_sys_addons_install.
+// You can define custom methods on it to extend its functionality as needed.
+type sysAddonsInstallDao struct {
+ *internal.SysAddonsInstallDao
+}
+
+var (
+ // SysAddonsInstall is a globally accessible object for table hg_sys_addons_install operations.
+ SysAddonsInstall = sysAddonsInstallDao{internal.NewSysAddonsInstallDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/sys_attachment.go b/server/internal/dao/sys_attachment.go
new file mode 100755
index 0000000..2e40b56
--- /dev/null
+++ b/server/internal/dao/sys_attachment.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// sysAttachmentDao is the data access object for the table hg_sys_attachment.
+// You can define custom methods on it to extend its functionality as needed.
+type sysAttachmentDao struct {
+ *internal.SysAttachmentDao
+}
+
+var (
+ // SysAttachment is a globally accessible object for table hg_sys_attachment operations.
+ SysAttachment = sysAttachmentDao{internal.NewSysAttachmentDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/sys_blacklist.go b/server/internal/dao/sys_blacklist.go
new file mode 100755
index 0000000..84bdec1
--- /dev/null
+++ b/server/internal/dao/sys_blacklist.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// sysBlacklistDao is the data access object for the table hg_sys_blacklist.
+// You can define custom methods on it to extend its functionality as needed.
+type sysBlacklistDao struct {
+ *internal.SysBlacklistDao
+}
+
+var (
+ // SysBlacklist is a globally accessible object for table hg_sys_blacklist operations.
+ SysBlacklist = sysBlacklistDao{internal.NewSysBlacklistDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/sys_config.go b/server/internal/dao/sys_config.go
new file mode 100755
index 0000000..a868a27
--- /dev/null
+++ b/server/internal/dao/sys_config.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// sysConfigDao is the data access object for the table hg_sys_config.
+// You can define custom methods on it to extend its functionality as needed.
+type sysConfigDao struct {
+ *internal.SysConfigDao
+}
+
+var (
+ // SysConfig is a globally accessible object for table hg_sys_config operations.
+ SysConfig = sysConfigDao{internal.NewSysConfigDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/sys_cron.go b/server/internal/dao/sys_cron.go
new file mode 100755
index 0000000..1e0f647
--- /dev/null
+++ b/server/internal/dao/sys_cron.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// sysCronDao is the data access object for the table hg_sys_cron.
+// You can define custom methods on it to extend its functionality as needed.
+type sysCronDao struct {
+ *internal.SysCronDao
+}
+
+var (
+ // SysCron is a globally accessible object for table hg_sys_cron operations.
+ SysCron = sysCronDao{internal.NewSysCronDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/sys_cron_group.go b/server/internal/dao/sys_cron_group.go
new file mode 100755
index 0000000..25e1578
--- /dev/null
+++ b/server/internal/dao/sys_cron_group.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// sysCronGroupDao is the data access object for the table hg_sys_cron_group.
+// You can define custom methods on it to extend its functionality as needed.
+type sysCronGroupDao struct {
+ *internal.SysCronGroupDao
+}
+
+var (
+ // SysCronGroup is a globally accessible object for table hg_sys_cron_group operations.
+ SysCronGroup = sysCronGroupDao{internal.NewSysCronGroupDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/sys_dict_data.go b/server/internal/dao/sys_dict_data.go
new file mode 100755
index 0000000..05e78a3
--- /dev/null
+++ b/server/internal/dao/sys_dict_data.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// sysDictDataDao is the data access object for the table hg_sys_dict_data.
+// You can define custom methods on it to extend its functionality as needed.
+type sysDictDataDao struct {
+ *internal.SysDictDataDao
+}
+
+var (
+ // SysDictData is a globally accessible object for table hg_sys_dict_data operations.
+ SysDictData = sysDictDataDao{internal.NewSysDictDataDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/sys_dict_type.go b/server/internal/dao/sys_dict_type.go
new file mode 100755
index 0000000..102c167
--- /dev/null
+++ b/server/internal/dao/sys_dict_type.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// sysDictTypeDao is the data access object for the table hg_sys_dict_type.
+// You can define custom methods on it to extend its functionality as needed.
+type sysDictTypeDao struct {
+ *internal.SysDictTypeDao
+}
+
+var (
+ // SysDictType is a globally accessible object for table hg_sys_dict_type operations.
+ SysDictType = sysDictTypeDao{internal.NewSysDictTypeDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/sys_ems_log.go b/server/internal/dao/sys_ems_log.go
new file mode 100755
index 0000000..cad6a7b
--- /dev/null
+++ b/server/internal/dao/sys_ems_log.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// sysEmsLogDao is the data access object for the table hg_sys_ems_log.
+// You can define custom methods on it to extend its functionality as needed.
+type sysEmsLogDao struct {
+ *internal.SysEmsLogDao
+}
+
+var (
+ // SysEmsLog is a globally accessible object for table hg_sys_ems_log operations.
+ SysEmsLog = sysEmsLogDao{internal.NewSysEmsLogDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/sys_gen_codes.go b/server/internal/dao/sys_gen_codes.go
new file mode 100755
index 0000000..4e82527
--- /dev/null
+++ b/server/internal/dao/sys_gen_codes.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// sysGenCodesDao is the data access object for the table hg_sys_gen_codes.
+// You can define custom methods on it to extend its functionality as needed.
+type sysGenCodesDao struct {
+ *internal.SysGenCodesDao
+}
+
+var (
+ // SysGenCodes is a globally accessible object for table hg_sys_gen_codes operations.
+ SysGenCodes = sysGenCodesDao{internal.NewSysGenCodesDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/sys_gen_curd_demo.go b/server/internal/dao/sys_gen_curd_demo.go
new file mode 100755
index 0000000..e67ce1e
--- /dev/null
+++ b/server/internal/dao/sys_gen_curd_demo.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// sysGenCurdDemoDao is the data access object for the table hg_sys_gen_curd_demo.
+// You can define custom methods on it to extend its functionality as needed.
+type sysGenCurdDemoDao struct {
+ *internal.SysGenCurdDemoDao
+}
+
+var (
+ // SysGenCurdDemo is a globally accessible object for table hg_sys_gen_curd_demo operations.
+ SysGenCurdDemo = sysGenCurdDemoDao{internal.NewSysGenCurdDemoDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/sys_gen_tree_demo.go b/server/internal/dao/sys_gen_tree_demo.go
new file mode 100755
index 0000000..8cc7d86
--- /dev/null
+++ b/server/internal/dao/sys_gen_tree_demo.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// sysGenTreeDemoDao is the data access object for the table hg_sys_gen_tree_demo.
+// You can define custom methods on it to extend its functionality as needed.
+type sysGenTreeDemoDao struct {
+ *internal.SysGenTreeDemoDao
+}
+
+var (
+ // SysGenTreeDemo is a globally accessible object for table hg_sys_gen_tree_demo operations.
+ SysGenTreeDemo = sysGenTreeDemoDao{internal.NewSysGenTreeDemoDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/sys_log.go b/server/internal/dao/sys_log.go
new file mode 100755
index 0000000..fd4f72a
--- /dev/null
+++ b/server/internal/dao/sys_log.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// sysLogDao is the data access object for the table hg_sys_log.
+// You can define custom methods on it to extend its functionality as needed.
+type sysLogDao struct {
+ *internal.SysLogDao
+}
+
+var (
+ // SysLog is a globally accessible object for table hg_sys_log operations.
+ SysLog = sysLogDao{internal.NewSysLogDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/sys_login_log.go b/server/internal/dao/sys_login_log.go
new file mode 100755
index 0000000..f158383
--- /dev/null
+++ b/server/internal/dao/sys_login_log.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// sysLoginLogDao is the data access object for the table hg_sys_login_log.
+// You can define custom methods on it to extend its functionality as needed.
+type sysLoginLogDao struct {
+ *internal.SysLoginLogDao
+}
+
+var (
+ // SysLoginLog is a globally accessible object for table hg_sys_login_log operations.
+ SysLoginLog = sysLoginLogDao{internal.NewSysLoginLogDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/sys_provinces.go b/server/internal/dao/sys_provinces.go
new file mode 100755
index 0000000..7e35c53
--- /dev/null
+++ b/server/internal/dao/sys_provinces.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// sysProvincesDao is the data access object for the table hg_sys_provinces.
+// You can define custom methods on it to extend its functionality as needed.
+type sysProvincesDao struct {
+ *internal.SysProvincesDao
+}
+
+var (
+ // SysProvinces is a globally accessible object for table hg_sys_provinces operations.
+ SysProvinces = sysProvincesDao{internal.NewSysProvincesDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/sys_serve_license.go b/server/internal/dao/sys_serve_license.go
new file mode 100755
index 0000000..6397152
--- /dev/null
+++ b/server/internal/dao/sys_serve_license.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// sysServeLicenseDao is the data access object for the table hg_sys_serve_license.
+// You can define custom methods on it to extend its functionality as needed.
+type sysServeLicenseDao struct {
+ *internal.SysServeLicenseDao
+}
+
+var (
+ // SysServeLicense is a globally accessible object for table hg_sys_serve_license operations.
+ SysServeLicense = sysServeLicenseDao{internal.NewSysServeLicenseDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/sys_serve_log.go b/server/internal/dao/sys_serve_log.go
new file mode 100755
index 0000000..1fb569d
--- /dev/null
+++ b/server/internal/dao/sys_serve_log.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// sysServeLogDao is the data access object for the table hg_sys_serve_log.
+// You can define custom methods on it to extend its functionality as needed.
+type sysServeLogDao struct {
+ *internal.SysServeLogDao
+}
+
+var (
+ // SysServeLog is a globally accessible object for table hg_sys_serve_log operations.
+ SysServeLog = sysServeLogDao{internal.NewSysServeLogDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/sys_sms_log.go b/server/internal/dao/sys_sms_log.go
new file mode 100755
index 0000000..aa2c686
--- /dev/null
+++ b/server/internal/dao/sys_sms_log.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// sysSmsLogDao is the data access object for the table hg_sys_sms_log.
+// You can define custom methods on it to extend its functionality as needed.
+type sysSmsLogDao struct {
+ *internal.SysSmsLogDao
+}
+
+var (
+ // SysSmsLog is a globally accessible object for table hg_sys_sms_log operations.
+ SysSmsLog = sysSmsLogDao{internal.NewSysSmsLogDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/dao/test_category.go b/server/internal/dao/test_category.go
new file mode 100755
index 0000000..d9e5ab7
--- /dev/null
+++ b/server/internal/dao/test_category.go
@@ -0,0 +1,22 @@
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package dao
+
+import (
+ "hotgo/internal/dao/internal"
+)
+
+// testCategoryDao is the data access object for the table hg_test_category.
+// You can define custom methods on it to extend its functionality as needed.
+type testCategoryDao struct {
+ *internal.TestCategoryDao
+}
+
+var (
+ // TestCategory is a globally accessible object for table hg_test_category operations.
+ TestCategory = testCategoryDao{internal.NewTestCategoryDao()}
+)
+
+// Add your custom methods and functionality below.
diff --git a/server/internal/global/cluster.go b/server/internal/global/cluster.go
new file mode 100644
index 0000000..6a12ca1
--- /dev/null
+++ b/server/internal/global/cluster.go
@@ -0,0 +1,51 @@
+// Package global
+// @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 global
+
+import (
+ "context"
+ "fmt"
+ "github.com/gogf/gf/v2/frame/g"
+ "hotgo/internal/consts"
+ "hotgo/internal/library/hgrds/lock"
+ "hotgo/internal/library/hgrds/pubsub"
+ "hotgo/internal/service"
+ "hotgo/utility/simple"
+)
+
+// SubscribeClusterSync 订阅集群同步,可以用来集中同步数据、状态等
+func SubscribeClusterSync(ctx context.Context) {
+ if !simple.IsCluster(ctx) {
+ return
+ }
+
+ err := pubsub.SubscribeMap(map[string]pubsub.SubHandler{
+ consts.ClusterSyncSysconfig: service.SysConfig().ClusterSync, // 系统配置
+ consts.ClusterSyncSysBlacklist: service.SysBlacklist().ClusterSync, // 系统黑名单
+ consts.ClusterSyncSysSuperAdmin: service.AdminMember().ClusterSyncSuperAdmin, // 超管
+ })
+
+ if err != nil {
+ g.Log().Fatal(ctx, err)
+ }
+}
+
+// PublishClusterSync 推送集群同步消息,如果没有开启集群部署,则不进行推送
+func PublishClusterSync(ctx context.Context, channel string, message interface{}) {
+ if !simple.IsCluster(ctx) {
+ return
+ }
+
+ mutex := lock.Mutex(fmt.Sprintf("%s:%s", "lock", channel))
+ err := mutex.LockFunc(ctx, func() {
+ if _, err := pubsub.Publish(ctx, channel, message); err != nil {
+ g.Log().Warningf(ctx, "PublishClusterSync %v err:%v", channel, err)
+ }
+ })
+ if err != nil {
+ g.Log().Warningf(ctx, "PublishClusterSync %v LockFunc err:%v", channel, err)
+ }
+}
diff --git a/server/internal/global/global.go b/server/internal/global/global.go
new file mode 100644
index 0000000..bc2b3e1
--- /dev/null
+++ b/server/internal/global/global.go
@@ -0,0 +1,8 @@
+// Package global
+// @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 global
+
+// 在这里可以配置一些全局公用的变量
diff --git a/server/internal/global/httproutes.go b/server/internal/global/httproutes.go
new file mode 100644
index 0000000..8a4e535
--- /dev/null
+++ b/server/internal/global/httproutes.go
@@ -0,0 +1,125 @@
+// Package global
+// @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 global
+
+import (
+ "reflect"
+ "strings"
+ "sync"
+
+ "github.com/gogf/gf/v2/net/ghttp"
+ "github.com/gogf/gf/v2/util/gmeta"
+ "github.com/gogf/gf/v2/util/gtag"
+)
+
+// HTTPRouter http路由
+type HTTPRouter struct {
+ ghttp.RouterItem
+ Tags string `json:"tags" dc:"接口所属的标签,用于接口分类"`
+ Summary string `json:"summary" dc:"接口/参数概要描述"`
+ Description string `json:"description" dc:"接口/参数详细描述"`
+}
+
+var (
+ httpRoutes map[string]*HTTPRouter
+ routeMutex sync.Mutex
+ shortTypeMapForTag = map[string]string{
+ gtag.SummaryShort: gtag.Summary,
+ gtag.SummaryShort2: gtag.Summary,
+ gtag.DescriptionShort: gtag.Description,
+ gtag.DescriptionShort2: gtag.Description,
+ }
+)
+
+// GetRequestRoute 获取当前请求路由属性
+func GetRequestRoute(r *ghttp.Request) *HTTPRouter {
+ key := GenFilterRequestKey(r)
+ routes := LoadHTTPRoutes(r)
+ router, ok := routes[key]
+ if !ok {
+ return nil
+ }
+ return router
+}
+
+// GenFilterRequestKey 根据请求生成唯一key
+func GenFilterRequestKey(r *ghttp.Request) string {
+ return GenRouteKey(r.Method, r.Request.URL.Path)
+}
+
+// GenFilterRouteKey 根据路由生成唯一key
+func GenFilterRouteKey(r *ghttp.Router) string {
+ return GenRouteKey(r.Method, r.Uri)
+}
+
+// GenRouteKey 生成唯一key
+func GenRouteKey(method, path string) string {
+ return strings.ToUpper(method) + " " + path
+}
+
+func LoadHTTPRoutes(r *ghttp.Request) map[string]*HTTPRouter {
+ if httpRoutes == nil {
+ routeMutex.Lock()
+ defer routeMutex.Unlock()
+
+ if httpRoutes != nil {
+ return httpRoutes
+ }
+
+ httpRoutes = make(map[string]*HTTPRouter, len(r.Server.GetRoutes()))
+ for _, v := range r.Server.GetRoutes() {
+ key := GenFilterRouteKey(v.Handler.Router)
+ if _, ok := httpRoutes[key]; !ok {
+ router := new(HTTPRouter)
+ router.RouterItem = v
+ httpRoutes[key] = setRouterMeta(router)
+ }
+ }
+ }
+ return httpRoutes
+}
+
+func setRouterMeta(router *HTTPRouter) *HTTPRouter {
+ if !router.RouterItem.Handler.Info.IsStrictRoute {
+ return router
+ }
+
+ var reflectValue = reflect.ValueOf(router.Handler.Info.Value.Interface())
+ for reflectValue.Kind() == reflect.Ptr {
+ reflectValue = reflectValue.Elem()
+ }
+
+ if reflectValue.Kind() != reflect.Func {
+ return router
+ }
+
+ var reflectType = reflect.TypeOf(router.Handler.Info.Value.Interface())
+ if reflectType.NumIn() != 2 || reflectType.NumOut() != 2 {
+ return router
+ }
+
+ var inputObject reflect.Value
+ if reflectType.In(1).Kind() == reflect.Ptr {
+ inputObject = reflect.New(reflectType.In(1).Elem()).Elem()
+ } else {
+ inputObject = reflect.New(reflectType.In(1)).Elem()
+ }
+
+ inputMetaMap := fillMapWithShortTags(gmeta.Data(inputObject.Interface()))
+ router.Tags = inputMetaMap["tags"]
+ router.Summary = inputMetaMap[gtag.Summary]
+ router.Description = inputMetaMap[gtag.Description]
+ return router
+}
+
+func fillMapWithShortTags(m map[string]string) map[string]string {
+ for k, v := range shortTypeMapForTag {
+ if m[v] == "" && m[k] != "" {
+ m[v] = m[k]
+ }
+ }
+ return m
+}
diff --git a/server/internal/global/init.go b/server/internal/global/init.go
new file mode 100644
index 0000000..3666117
--- /dev/null
+++ b/server/internal/global/init.go
@@ -0,0 +1,168 @@
+// Package global
+// @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 global
+
+import (
+ "context"
+ "fmt"
+ "github.com/gogf/gf/contrib/trace/jaeger/v2"
+ "github.com/gogf/gf/v2"
+ "github.com/gogf/gf/v2/container/gvar"
+ "github.com/gogf/gf/v2/encoding/gjson"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gctx"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/glog"
+ "github.com/gogf/gf/v2/os/gtime"
+ "github.com/gogf/gf/v2/text/gstr"
+ "github.com/gogf/gf/v2/util/gmode"
+ "hotgo/internal/consts"
+ "hotgo/internal/library/cache"
+ "hotgo/internal/library/queue"
+ "hotgo/internal/model/entity"
+ "hotgo/internal/service"
+ "hotgo/utility/charset"
+ "hotgo/utility/simple"
+ "hotgo/utility/validate"
+ "runtime"
+ "strings"
+)
+
+func Init(ctx context.Context) {
+ // 设置gf运行模式
+ SetGFMode(ctx)
+
+ // 设置服务日志处理
+ glog.SetDefaultHandler(LoggingServeLogHandler)
+
+ // 默认上海时区
+ if err := gtime.SetTimeZone("Asia/Shanghai"); err != nil {
+ g.Log().Fatalf(ctx, "时区设置异常 err:%+v", err)
+ return
+ }
+
+ fmt.Printf("欢迎使用HotGo!\r\n当前运行环境:%v, 运行根路径为:%v \r\nHotGo版本:v%v, gf版本:%v \n", runtime.GOOS, gfile.Pwd(), consts.VersionApp, gf.VERSION)
+
+ // 初始化链路追踪
+ InitTrace(ctx)
+
+ // 设置缓存适配器
+ cache.SetAdapter(ctx)
+
+ // 初始化功能库配置
+ service.SysConfig().InitConfig(ctx)
+
+ // 加载超管数据
+ service.AdminMember().LoadSuperAdmin(ctx)
+
+ // 订阅集群同步
+ SubscribeClusterSync(ctx)
+}
+
+// LoggingServeLogHandler 服务日志处理
+// 需要将异常日志保存到服务日志时可以通过SetHandlers设置此方法
+func LoggingServeLogHandler(ctx context.Context, in *glog.HandlerInput) {
+ in.Next(ctx)
+
+ err := g.Try(ctx, func(ctx context.Context) {
+ var err error
+ defer func() {
+ if err != nil {
+ panic(err)
+ }
+ }()
+
+ // web服务日志不做记录,因为会导致重复记录
+ r := g.RequestFromCtx(ctx)
+ if r != nil && r.Server != nil && in.Logger.GetConfig().Path == r.Server.Logger().GetConfig().Path {
+ return
+ }
+
+ conf, err := service.SysConfig().GetLoadServeLog(ctx)
+ if err != nil {
+ return
+ }
+
+ if conf == nil {
+ return
+ }
+
+ if !conf.Switch {
+ return
+ }
+
+ if in.LevelFormat == "" || !gstr.InArray(conf.LevelFormat, in.LevelFormat) {
+ return
+ }
+
+ if in.Stack == "" {
+ in.Stack = in.Logger.GetStack()
+ }
+
+ if len(in.Content) == 0 {
+ in.Content = gstr.StrLimit(gvar.New(in.Values).String(), consts.MaxServeLogContentLen)
+ }
+
+ var data entity.SysServeLog
+ data.TraceId = gctx.CtxId(ctx)
+ data.LevelFormat = in.LevelFormat
+ data.Content = in.Content
+ data.Stack = gjson.New(charset.ParseStack(in.Stack))
+ data.Line = strings.TrimRight(in.CallerPath, ":")
+ data.TriggerNs = in.Time.UnixNano()
+ data.Status = consts.StatusEnabled
+
+ if gstr.Contains(in.Content, `exception recovered`) {
+ data.LevelFormat = "PANI"
+ }
+
+ if data.Stack.IsNil() {
+ data.Stack = gjson.New(consts.NilJsonToString)
+ }
+
+ if conf.Queue {
+ err = queue.Push(consts.QueueServeLogTopic, data)
+ } else {
+ err = service.SysServeLog().RealWrite(ctx, data)
+ }
+ })
+
+ if err != nil {
+ g.Dump("LoggingServeLogHandler err:", err)
+ }
+}
+
+// InitTrace 初始化链路追踪
+func InitTrace(ctx context.Context) {
+ if !g.Cfg().MustGet(ctx, "jaeger.switch").Bool() {
+ return
+ }
+
+ tp, err := jaeger.Init(simple.AppName(ctx), g.Cfg().MustGet(ctx, "jaeger.endpoint").String())
+ if err != nil {
+ g.Log().Fatal(ctx, err)
+ }
+
+ simple.Event().Register(consts.EventServerClose, func(ctx context.Context, args ...interface{}) {
+ _ = tp.Shutdown(ctx)
+ g.Log().Debug(ctx, "jaeger closed ..")
+ })
+}
+
+// SetGFMode 设置gf运行模式
+func SetGFMode(ctx context.Context) {
+ mode := g.Cfg().MustGet(ctx, "system.mode").String()
+ if len(mode) == 0 {
+ mode = gmode.NOT_SET
+ }
+
+ var modes = []string{gmode.DEVELOP, gmode.TESTING, gmode.STAGING, gmode.PRODUCT}
+
+ // 如果是有效的运行模式,就进行设置
+ if validate.InSlice(modes, mode) {
+ gmode.Set(mode)
+ }
+}
diff --git a/server/internal/library/addons/addons.go b/server/internal/library/addons/addons.go
new file mode 100644
index 0000000..dd5f128
--- /dev/null
+++ b/server/internal/library/addons/addons.go
@@ -0,0 +1,61 @@
+// Package addons
+// @Link https://github.com/bufanyun/hotgo
+// @Copyright Copyright (c) 2023 HotGo CLI
+// @Author Ms <133814250@qq.com>
+// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
+package addons
+
+import (
+ "context"
+ "github.com/gogf/gf/v2/frame/g"
+ "hotgo/internal/consts"
+)
+
+var cacheResourcePath string
+
+// GetResourcePath 获取插件资源路径
+func GetResourcePath(ctx context.Context) string {
+ if len(cacheResourcePath) > 0 {
+ return cacheResourcePath
+ }
+ basePath := g.Cfg().MustGet(ctx, "system.addonsResourcePath").String()
+ if basePath == "" {
+ g.Log().Warning(ctx, "addons GetResourcePath not config found:'system.addonsResourcePath', use default values:'resource'")
+ basePath = "resource"
+ }
+
+ cacheResourcePath = basePath
+ return basePath
+}
+
+// GetModulePath 获取指定模块相对路径
+func GetModulePath(name string) string {
+ return "./" + consts.AddonsDir + "/" + name
+}
+
+// ViewPath 默认的插件模板路径
+// 模板路径:resource/addons/插件模块名称/template
+// 例如:resource/addons/hgexample/template
+// 如果你不喜欢现在的风格,可以自行调整
+func ViewPath(name, basePath string) string {
+ return basePath + "/" + consts.AddonsDir + "/" + name + "/template"
+}
+
+// StaticPath 默认的插件静态路映射关系
+// 静态资源路径:resource/public/addons/插件模块名称/public
+// 例如访问:http://127.0.0.1:8000/addons/hgexample/default 则指向文件-> resource/addons/hgexample/public/default
+// 如果你不喜欢现在的风格,可以自行调整
+func StaticPath(name, basePath string) (string, string) {
+ return "/" + consts.AddonsDir + "/" + name, basePath + "/" + consts.AddonsDir + "/" + name + "/public"
+}
+
+// RouterPrefix 路由前缀
+// 最终效果:/应用名称/插件模块名称/xxx/xxx。
+// 如果你不喜欢现在的风格,可以自行调整
+func RouterPrefix(ctx context.Context, app, name string) string {
+ var prefix = "/"
+ if app != "" {
+ prefix = g.Cfg().MustGet(ctx, "router."+app+".prefix", "/"+app+"").String()
+ }
+ return prefix + "/" + name
+}
diff --git a/server/internal/library/addons/build.go b/server/internal/library/addons/build.go
new file mode 100644
index 0000000..6fab466
--- /dev/null
+++ b/server/internal/library/addons/build.go
@@ -0,0 +1,138 @@
+// Package addons
+// @Link https://github.com/bufanyun/hotgo
+// @Copyright Copyright (c) 2023 HotGo CLI
+// @Author Ms <133814250@qq.com>
+// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
+package addons
+
+import (
+ "context"
+ "fmt"
+ "github.com/gogf/gf/v2/errors/gerror"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/text/gstr"
+ "hotgo/internal/consts"
+ "hotgo/internal/model"
+ "hotgo/utility/validate"
+ "strconv"
+ "strings"
+)
+
+type BuildOption struct {
+ Skeleton Skeleton
+ Config *model.BuildAddonConfig
+ Extend []string `json:"extend" dc:"扩展功能"`
+}
+
+// Build 构建新插件
+func Build(ctx context.Context, option *BuildOption) (err error) {
+ var (
+ resourcePath = GetResourcePath(ctx)
+ buildPath = "./" + consts.AddonsDir + "/" + option.Skeleton.Name
+ modulesPath = "./" + consts.AddonsDir + "/modules/" + option.Skeleton.Name + ".go"
+ webApiPath = gstr.Replace(option.Config.WebApiPath, "{$name}", option.Skeleton.Name)
+ webViewsPath = gstr.Replace(option.Config.WebViewsPath, "{$name}", option.Skeleton.Name)
+ replaces = map[string]string{
+ "@{.label}": option.Skeleton.Label,
+ "@{.name}": option.Skeleton.Name,
+ "@{.group}": strconv.Itoa(option.Skeleton.Group),
+ "@{.brief}": option.Skeleton.Brief,
+ "@{.description}": option.Skeleton.Description,
+ "@{.author}": option.Skeleton.Author,
+ "@{.version}": option.Skeleton.Version,
+ "@{.hgVersion}": consts.VersionApp, // HG 版本
+ }
+ )
+
+ if resourcePath == "" {
+ err = gerror.New("请先设置一个有效的插件资源路径,配置名称:'system.addonsResourcePath'")
+ return
+ }
+
+ if err = checkBuildDir(buildPath, modulesPath, webApiPath, webViewsPath); err != nil {
+ return
+ }
+
+ // scans directory recursively
+ list, err := gfile.ScanDirFunc(option.Config.SrcPath, "*", true, func(path string) string {
+ return path
+ })
+
+ if err != nil {
+ return
+ }
+
+ for _, path := range list {
+ if !gfile.IsReadable(path) {
+ err = gerror.Newf("file:%v is unreadable, please check permissions", path)
+ return
+ }
+
+ if gfile.IsDir(path) {
+ continue
+ }
+
+ flowFile := gstr.ReplaceByMap(path, map[string]string{
+ gfile.RealPath(option.Config.SrcPath): "",
+ ".template": "",
+ })
+ flowFile = buildPath + "/" + flowFile
+
+ content := gstr.ReplaceByMap(gfile.GetContents(path), replaces)
+
+ if err = gfile.PutContents(flowFile, content); err != nil {
+ break
+ }
+ }
+
+ // 隐式注入插件
+ if err = gfile.PutContents(modulesPath, gstr.ReplaceByMap(importModules, replaces)); err != nil {
+ return
+ }
+
+ // webApi
+ if err = gfile.PutContents(webApiPath+"/config/index.ts", gstr.ReplaceByMap(webApiLayout, replaces)); err != nil {
+ return
+ }
+
+ // web插件配置主页面
+ if err = gfile.PutContents(webViewsPath+"/config/BasicSetting.vue", gstr.ReplaceByMap(webConfigBasicSetting, replaces)); err != nil {
+ return
+ }
+
+ // web插件基础配置页面
+ if err = gfile.PutContents(webViewsPath+"/config/system.vue", gstr.ReplaceByMap(webConfigSystem, replaces)); err != nil {
+ return
+ }
+
+ // 创建静态目录
+ if validate.InSlice(option.Extend, consts.AddonsExtendResourcePublic) {
+ _, staticPath := StaticPath(option.Skeleton.Name, resourcePath)
+ content := fmt.Sprintf(resourcePublicDefaultFile, option.Skeleton.Label)
+ if err = gfile.PutContents(staticPath+"/default", content); err != nil {
+ return
+ }
+ }
+
+ // 创建模板目录
+ if validate.InSlice(option.Extend, consts.AddonsExtendResourceTemplate) {
+ viewPath := ViewPath(option.Skeleton.Name, resourcePath)
+ if err = gfile.PutContents(viewPath+"/home/index.html", resourceTemplateHomeFile); err != nil {
+ return
+ }
+ }
+ return
+}
+
+func checkBuildDir(paths ...string) (err error) {
+ if len(paths) == 0 {
+ return
+ }
+
+ for _, path := range paths {
+ if gfile.Exists(path) {
+ return gerror.Newf("插件已存在,请换一个插件名称或者经确认无误后依次删除文件夹: [%v] 后重新生成", strings.Join(paths, "、\t"))
+ }
+ }
+ return
+}
diff --git a/server/internal/library/addons/build_layout.go b/server/internal/library/addons/build_layout.go
new file mode 100644
index 0000000..dd85e96
--- /dev/null
+++ b/server/internal/library/addons/build_layout.go
@@ -0,0 +1,232 @@
+// Package addons
+// @Link https://github.com/bufanyun/hotgo
+// @Copyright Copyright (c) 2023 HotGo CLI
+// @Author Ms <133814250@qq.com>
+// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
+package addons
+
+const (
+ importModules = `// Package modules
+// @Link https://github.com/bufanyun/hotgo
+// @Copyright Copyright (c) 2023 HotGo CLI
+// @Author Ms <133814250@qq.com>
+// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
+package modules
+
+import _ "hotgo/addons/@{.name}"
+`
+
+ webApiLayout = `import { http } from '@/utils/http/axios';
+
+export function getConfig(params) {
+ return http.request({
+ url: '/@{.name}/config/get',
+ method: 'get',
+ params,
+ });
+}
+
+export function updateConfig(params) {
+ return http.request({
+ url: '/@{.name}/config/update',
+ method: 'post',
+ params,
+ });
+}
+`
+
+ webConfigBasicSetting = `
+
+
+
+
+
+
+ 这是一个测试参数,每个插件都可以有独立的配置项,可以按需添加
+
+
+
+
+ 保存更新
+
+
+
+
+
+
+
+
+`
+
+ webConfigSystem = `
+
+
+
+
+
+ {{ item.name }}
+ {{ item.desc }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+`
+
+ resourcePublicDefaultFile = "Hello!这是创建插件 [%v] 时默认生成的一个静态目录文件,用于测试,当你看到这个提示时,说明已经联调成功啦!"
+
+ resourceTemplateHomeFile = `
+
+
+
+
+
+
+
+ @{.Title}
+
+
+
+
+
+
Hello,@{.Data.name}!!
+
@{.Data.module}
+
服务器时间:@{.Data.time}
+
+
+
+
+`
+)
diff --git a/server/internal/library/addons/install.go b/server/internal/library/addons/install.go
new file mode 100644
index 0000000..976aadd
--- /dev/null
+++ b/server/internal/library/addons/install.go
@@ -0,0 +1,120 @@
+// Package addons
+// @Link https://github.com/bufanyun/hotgo
+// @Copyright Copyright (c) 2023 HotGo CLI
+// @Author Ms <133814250@qq.com>
+// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
+package addons
+
+import (
+ "context"
+ "hotgo/internal/consts"
+ "hotgo/internal/dao"
+
+ "github.com/gogf/gf/v2/database/gdb"
+ "github.com/gogf/gf/v2/errors/gerror"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gtime"
+)
+
+// InstallRecord 安装记录
+type InstallRecord struct {
+ Id int64 `json:"id" description:"安装ID"`
+ Version string `json:"version" description:"安装版本"`
+ Status int `json:"status" description:"安装状态"`
+ CreatedAt *gtime.Time `json:"createdAt" description:"创建时间"`
+ UpdatedAt *gtime.Time `json:"updatedAt" description:"更新时间"`
+}
+
+func GetModel(ctx context.Context) *gdb.Model {
+ return dao.SysAddonsInstall.Ctx(ctx)
+}
+
+func ScanInstall(m Module) (record *InstallRecord, err error) {
+ err = GetModel(m.Ctx()).Where(dao.SysAddonsInstall.Columns().Name, m.GetSkeleton().Name).Scan(&record)
+ return
+}
+
+// IsInstall 模块是否已安装
+func IsInstall(m Module) bool {
+ record, err := ScanInstall(m)
+ if err != nil {
+ g.Log().Debugf(m.Ctx(), "addons.IsInstall err:%+v", err)
+ return false
+ }
+ if record == nil {
+ return false
+ }
+ return record.Status == consts.AddonsInstallStatusOk
+}
+
+// Install 安装模块
+func Install(m Module) (err error) {
+ record, err := ScanInstall(m)
+ if err != nil {
+ return
+ }
+ if record != nil && record.Status == consts.AddonsInstallStatusOk {
+ return gerror.New("插件已安装,无需重复操作!")
+ }
+
+ data := g.Map{
+ dao.SysAddonsInstall.Columns().Name: m.GetSkeleton().Name,
+ dao.SysAddonsInstall.Columns().Version: m.GetSkeleton().Version,
+ dao.SysAddonsInstall.Columns().Status: consts.AddonsInstallStatusOk,
+ }
+ return g.DB().Transaction(m.Ctx(), func(ctx context.Context, tx gdb.TX) error {
+ if record != nil {
+ _, _ = GetModel(ctx).Where(dao.SysAddonsInstall.Columns().Id, record.Id).Delete()
+ }
+
+ if _, err = GetModel(ctx).Data(data).OmitEmptyData().Insert(); err != nil {
+ return err
+ }
+ return m.Install(ctx)
+ })
+}
+
+// Upgrade 更新模块
+func Upgrade(m Module) (err error) {
+ record, err := ScanInstall(m)
+ if err != nil {
+ return
+ }
+
+ if record == nil || record.Status != consts.AddonsInstallStatusOk {
+ return gerror.New("插件未安装,请安装后操作!")
+ }
+
+ data := g.Map{
+ dao.SysAddonsInstall.Columns().Version: m.GetSkeleton().Version,
+ }
+ return g.DB().Transaction(m.Ctx(), func(ctx context.Context, tx gdb.TX) error {
+ if _, err = GetModel(ctx).Where(dao.SysAddonsInstall.Columns().Id, record.Id).Data(data).Update(); err != nil {
+ return err
+ }
+ return m.Upgrade(ctx)
+ })
+}
+
+// UnInstall 卸载模块
+func UnInstall(m Module) (err error) {
+ record, err := ScanInstall(m)
+ if err != nil {
+ return
+ }
+
+ if record == nil || record.Status != consts.AddonsInstallStatusOk {
+ return gerror.New("插件未安装,请安装后操作!")
+ }
+
+ data := g.Map{
+ dao.SysAddonsInstall.Columns().Version: m.GetSkeleton().Version,
+ dao.SysAddonsInstall.Columns().Status: consts.AddonsInstallStatusUn,
+ }
+ return g.DB().Transaction(m.Ctx(), func(ctx context.Context, tx gdb.TX) error {
+ if _, err = GetModel(ctx).Where(dao.SysAddonsInstall.Columns().Id, record.Id).Data(data).Update(); err != nil {
+ return err
+ }
+ return m.UnInstall(ctx)
+ })
+}
diff --git a/server/internal/library/addons/module.go b/server/internal/library/addons/module.go
new file mode 100644
index 0000000..092887f
--- /dev/null
+++ b/server/internal/library/addons/module.go
@@ -0,0 +1,186 @@
+// Package addons
+// @Link https://github.com/bufanyun/hotgo
+// @Copyright Copyright (c) 2023 HotGo CLI
+// @Author Ms <133814250@qq.com>
+// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
+package addons
+
+import (
+ "context"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/net/ghttp"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gres"
+ "hotgo/internal/model/input/form"
+ "sort"
+ "sync"
+ "time"
+)
+
+// Option 模块启动选项
+type Option struct {
+ Server *ghttp.Server // http服务器
+ // 更多选项参数
+ // ..
+}
+
+// Skeleton 模块骨架
+type Skeleton struct {
+ Label string `json:"label"` // 标识
+ Name string `json:"name"` // 名称
+ Group int `json:"group"` // 分组
+ Logo string `json:"logo"` // logo
+ Brief string `json:"brief"` // 简介
+ Description string `json:"description"` // 详细描述
+ Author string `json:"author"` // 作者
+ Version string `json:"version"` // 版本号
+ RootPath string `json:"rootPath"` // 根路径
+}
+
+func (s *Skeleton) GetModule() Module {
+ return GetModule(s.Name)
+}
+
+// Module 插件模块
+type Module interface {
+ Start(option *Option) (err error) // 启动模块
+ Stop() (err error) // 停止模块
+ Ctx() context.Context // 上下文
+ GetSkeleton() *Skeleton // 获取模块
+ Install(ctx context.Context) (err error) // 安装模块
+ Upgrade(ctx context.Context) (err error) // 更新模块
+ UnInstall(ctx context.Context) (err error) // 卸载模块
+}
+
+var (
+ modules = make(map[string]Module)
+ mLock sync.Mutex
+)
+
+// StartModules 启动所有已安装模块
+func StartModules(ctx context.Context, option *Option) (err error) {
+ for _, module := range filterInstalled() {
+ if err = module.Start(option); err != nil {
+ return
+ }
+ }
+
+ // 为所有已安装模块设置静态资源路径
+ AddStaticPath(ctx, option.Server)
+ return
+}
+
+// StopModules 停止所有已安装模块
+func StopModules(ctx context.Context) {
+ for _, module := range filterInstalled() {
+ if err := module.Stop(); err != nil {
+ g.Log().Warningf(ctx, "StopModules err:%v, module:%v", err.Error(), module.GetSkeleton().Name)
+ time.Sleep(time.Second)
+ }
+ }
+ return
+}
+
+// RegisterModule 注册模块
+func RegisterModule(m Module) Module {
+ mLock.Lock()
+ defer mLock.Unlock()
+ name := m.GetSkeleton().Name
+ _, ok := modules[name]
+ if ok {
+ panic("module repeat registration, name:" + name)
+ }
+
+ sk := m.GetSkeleton()
+ if sk == nil {
+ panic("module skeleton not initialized, name:" + name)
+ }
+
+ sk.RootPath = GetModulePath(name)
+ modules[name] = m
+ return m
+}
+
+// GetModule 获取指定名称模块
+func GetModule(name string) Module {
+ mLock.Lock()
+ defer mLock.Unlock()
+ m, ok := modules[name]
+ if !ok {
+ panic("implement not found for interface " + name + ", forgot register?")
+ }
+ return m
+}
+
+// GetSkeletons 获取所有模块骨架
+func GetSkeletons() (list []*Skeleton) {
+ var keys []string
+ for k := range modules {
+ keys = append(keys, k)
+ }
+
+ sort.Strings(keys)
+
+ for _, v := range keys {
+ list = append(list, GetModule(v).GetSkeleton())
+ }
+ return list
+}
+
+// GetModuleRealPath 获取指定模块绝对路径
+func GetModuleRealPath(name string) string {
+ path := gfile.RealPath(GetModulePath(name))
+ if path == "" {
+ panic("no path is found. please confirm that the path " + GetModulePath(name) + " exists?")
+ }
+ return path
+}
+
+// AddStaticPath 设置插件静态目录映射
+func AddStaticPath(ctx context.Context, server *ghttp.Server) {
+ basePath := GetResourcePath(ctx)
+ if basePath == "" {
+ return
+ }
+
+ for _, module := range filterInstalled() {
+ name := module.GetSkeleton().Name
+ prefix, path := StaticPath(name, basePath)
+ if !gres.Contains(path) {
+ if _, err := gfile.Search(path); err != nil {
+ g.Log().Warningf(ctx, `addons AddStaticPath failed: %v`, err)
+ continue
+ }
+ }
+ server.AddStaticPath(prefix, path)
+ }
+}
+
+// filterInstalled 过滤已安装模块
+func filterInstalled() []Module {
+ var ms []Module
+ for _, module := range modules {
+ if IsInstall(module) {
+ ms = append(ms, module)
+ }
+ }
+ return ms
+}
+
+// ModuleSelect 获取插件模块选项
+func ModuleSelect() form.Selects {
+ sks := GetSkeletons()
+ lst := make(form.Selects, 0)
+ if len(sks) == 0 {
+ return lst
+ }
+
+ for _, skeleton := range sks {
+ lst = append(lst, &form.Select{
+ Value: skeleton.Name,
+ Label: skeleton.Label,
+ Name: skeleton.Label,
+ })
+ }
+ return lst
+}
diff --git a/server/internal/library/cache/cache.go b/server/internal/library/cache/cache.go
new file mode 100644
index 0000000..d131061
--- /dev/null
+++ b/server/internal/library/cache/cache.go
@@ -0,0 +1,58 @@
+// Package cache
+// @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 cache
+
+import (
+ "context"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gcache"
+ "github.com/gogf/gf/v2/os/gfile"
+ "hotgo/internal/library/cache/file"
+)
+
+// cache 缓存驱动
+var cache *gcache.Cache
+
+// Instance 缓存实例
+func Instance() *gcache.Cache {
+ if cache == nil {
+ panic("cache uninitialized.")
+ }
+ return cache
+}
+
+// SetAdapter 设置缓存适配器
+func SetAdapter(ctx context.Context) {
+ var adapter gcache.Adapter
+
+ switch g.Cfg().MustGet(ctx, "cache.adapter").String() {
+ case "redis":
+ adapter = gcache.NewAdapterRedis(g.Redis())
+ case "file":
+ fileDir := g.Cfg().MustGet(ctx, "cache.fileDir").String()
+ if fileDir == "" {
+ g.Log().Fatal(ctx, "file path must be configured for file caching.")
+ return
+ }
+
+ if !gfile.Exists(fileDir) {
+ if err := gfile.Mkdir(fileDir); err != nil {
+ g.Log().Fatalf(ctx, "failed to create the cache directory. procedure, err:%+v", err)
+ return
+ }
+ }
+ adapter = file.NewAdapterFile(fileDir)
+ default:
+ adapter = gcache.NewAdapterMemory()
+ }
+
+ // 数据库缓存,默认和通用缓冲驱动一致,如果你不想使用默认的,可以自行调整
+ g.DB().GetCache().SetAdapter(adapter)
+
+ // 通用缓存
+ cache = gcache.New()
+ cache.SetAdapter(adapter)
+}
diff --git a/server/internal/library/cache/file/file.go b/server/internal/library/cache/file/file.go
new file mode 100644
index 0000000..26138ff
--- /dev/null
+++ b/server/internal/library/cache/file/file.go
@@ -0,0 +1,322 @@
+// Package file
+// @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 file
+
+import (
+ "context"
+ "crypto/sha256"
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "github.com/gogf/gf/v2/container/gvar"
+ "github.com/gogf/gf/v2/errors/gerror"
+ "github.com/gogf/gf/v2/os/gcache"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/util/gconv"
+ "os"
+ "path/filepath"
+ "time"
+)
+
+type (
+ // AdapterFile is the gcache adapter implements using file server.
+ AdapterFile struct {
+ dir string
+ }
+
+ fileContent struct {
+ Duration int64 `json:"duration"`
+ Data interface{} `json:"data,omitempty"`
+ }
+)
+
+const perm = 0o666
+
+var (
+ CacheExpiredErr = errors.New("cache expired")
+)
+
+// NewAdapterFile creates and returns a new memory cache object.
+func NewAdapterFile(dir string) gcache.Adapter {
+ return &AdapterFile{
+ dir: dir,
+ }
+}
+
+func (c *AdapterFile) Set(ctx context.Context, key interface{}, value interface{}, lifeTime time.Duration) (err error) {
+ fileKey := gconv.String(key)
+ if value == nil || lifeTime < 0 {
+ return c.Delete(fileKey)
+ }
+ return c.Save(fileKey, gconv.String(value), lifeTime)
+}
+
+func (c *AdapterFile) SetMap(ctx context.Context, data map[interface{}]interface{}, duration time.Duration) (err error) {
+ return gerror.New("implement me")
+}
+
+func (c *AdapterFile) SetIfNotExist(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (ok bool, err error) {
+ return false, gerror.New("implement me")
+}
+
+func (c *AdapterFile) SetIfNotExistFunc(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (ok bool, err error) {
+ return false, gerror.New("implement me")
+}
+
+func (c *AdapterFile) SetIfNotExistFuncLock(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (ok bool, err error) {
+ return false, gerror.New("implement me")
+}
+
+func (c *AdapterFile) Get(ctx context.Context, key interface{}) (*gvar.Var, error) {
+ fetch, err := c.Fetch(gconv.String(key))
+ if err != nil {
+ return nil, err
+ }
+ return gvar.New(fetch), nil
+}
+
+func (c *AdapterFile) GetOrSet(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (result *gvar.Var, err error) {
+ result, err = c.Get(ctx, key)
+ if err != nil && !errors.Is(err, CacheExpiredErr) {
+ return nil, err
+ }
+ if result.IsNil() {
+ return gvar.New(value), c.Set(ctx, key, value, duration)
+ }
+ return
+}
+
+func (c *AdapterFile) GetOrSetFunc(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (result *gvar.Var, err error) {
+ v, err := c.Get(ctx, key)
+ if err != nil && !errors.Is(err, CacheExpiredErr) {
+ return nil, err
+ }
+ if v.IsNil() {
+ value, err := f(ctx)
+ if err != nil {
+ return nil, err
+ }
+ if value == nil {
+ return nil, nil
+ }
+ return gvar.New(value), c.Set(ctx, key, value, duration)
+ } else {
+ return v, nil
+ }
+}
+
+func (c *AdapterFile) GetOrSetFuncLock(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (result *gvar.Var, err error) {
+ return c.GetOrSetFunc(ctx, key, f, duration)
+}
+
+func (c *AdapterFile) Contains(ctx context.Context, key interface{}) (bool, error) {
+ return c.Has(gconv.String(key)), nil
+}
+
+func (c *AdapterFile) Size(ctx context.Context) (size int, err error) {
+ return 0, nil
+}
+
+func (c *AdapterFile) Data(ctx context.Context) (data map[interface{}]interface{}, err error) {
+ return nil, gerror.New("implement me")
+}
+
+func (c *AdapterFile) Keys(ctx context.Context) (keys []interface{}, err error) {
+ return nil, gerror.New("implement me")
+}
+
+func (c *AdapterFile) Values(ctx context.Context) (values []interface{}, err error) {
+ return nil, gerror.New("implement me")
+}
+
+func (c *AdapterFile) Update(ctx context.Context, key interface{}, value interface{}) (oldValue *gvar.Var, exist bool, err error) {
+ return nil, false, gerror.New("implement me")
+}
+
+func (c *AdapterFile) UpdateExpire(ctx context.Context, key interface{}, duration time.Duration) (oldDuration time.Duration, err error) {
+ var (
+ v *gvar.Var
+ oldTTL int64
+ fileKey = gconv.String(key)
+ )
+ // TTL.
+ expire, err := c.GetExpire(ctx, fileKey)
+ if err != nil {
+ return
+ }
+ oldTTL = int64(expire)
+ if oldTTL == -2 {
+ // It does not exist.
+ return
+ }
+ oldDuration = time.Duration(oldTTL) * time.Second
+ // DEL.
+ if duration < 0 {
+ err = c.Delete(fileKey)
+ return
+ }
+ v, err = c.Get(ctx, fileKey)
+ if err != nil {
+ return
+ }
+ err = c.Set(ctx, fileKey, v.Val(), duration)
+ return
+}
+
+func (c *AdapterFile) GetExpire(ctx context.Context, key interface{}) (time.Duration, error) {
+ content, err := c.read(gconv.String(key))
+ if err != nil {
+ return -1, nil
+ }
+
+ if content.Duration <= time.Now().Unix() {
+ return -1, nil
+ }
+ return time.Duration(time.Now().Unix()-content.Duration) * time.Second, nil
+}
+
+func (c *AdapterFile) Remove(ctx context.Context, keys ...interface{}) (lastValue *gvar.Var, err error) {
+ if len(keys) == 0 {
+ return nil, nil
+ }
+ // Retrieves the last key value.
+ if lastValue, err = c.Get(ctx, gconv.String(keys[len(keys)-1])); err != nil {
+ return nil, err
+ }
+ // Deletes all given keys.
+ err = c.DeleteMulti(gconv.Strings(keys)...)
+ return
+}
+
+func (c *AdapterFile) Clear(ctx context.Context) error {
+ return c.Flush()
+}
+
+func (c *AdapterFile) Close(ctx context.Context) error {
+ return nil
+}
+
+func (c *AdapterFile) createName(key string) string {
+ h := sha256.New()
+ _, _ = h.Write([]byte(key))
+ hash := hex.EncodeToString(h.Sum(nil))
+ return filepath.Join(c.dir, fmt.Sprintf("%s.cache", hash))
+}
+
+func (c *AdapterFile) read(key string) (*fileContent, error) {
+ rp := gfile.RealPath(c.createName(key))
+ if rp == "" {
+ return nil, nil
+ }
+
+ value, err := os.ReadFile(rp)
+ if err != nil {
+ return nil, err
+ }
+
+ content := &fileContent{}
+ if err := json.Unmarshal(value, content); err != nil {
+ return nil, err
+ }
+
+ if content.Duration == 0 {
+ return content, nil
+ }
+
+ if content.Duration <= time.Now().Unix() {
+ _ = c.Delete(key)
+ return nil, CacheExpiredErr
+ }
+ return content, nil
+}
+
+// Has checks if the cached key exists into the File storage
+func (c *AdapterFile) Has(key string) bool {
+ fc, err := c.read(key)
+ return err == nil && fc != nil
+}
+
+// Delete the cached key from File storage
+func (c *AdapterFile) Delete(key string) error {
+ _, err := os.Stat(c.createName(key))
+ if err != nil && os.IsNotExist(err) {
+ return nil
+ }
+ return os.Remove(c.createName(key))
+}
+
+// DeleteMulti the cached key from File storage
+func (c *AdapterFile) DeleteMulti(keys ...string) (err error) {
+ for _, key := range keys {
+ if err = c.Delete(key); err != nil {
+ return
+ }
+ }
+ return
+}
+
+// Fetch retrieves the cached value from key of the File storage
+func (c *AdapterFile) Fetch(key string) (interface{}, error) {
+ content, err := c.read(key)
+ if err != nil {
+ return nil, err
+ }
+
+ if content == nil {
+ return nil, nil
+ }
+ return content.Data, nil
+}
+
+// FetchMulti retrieve multiple cached values from keys of the File storage
+func (c *AdapterFile) FetchMulti(keys []string) map[string]interface{} {
+ result := make(map[string]interface{})
+ for _, key := range keys {
+ if value, err := c.Fetch(key); err == nil {
+ result[key] = value
+ }
+ }
+ return result
+}
+
+// Flush removes all cached keys of the File storage
+func (c *AdapterFile) Flush() error {
+ dir, err := os.Open(c.dir)
+ if err != nil {
+ return err
+ }
+
+ defer func() {
+ _ = dir.Close()
+ }()
+
+ names, _ := dir.Readdirnames(-1)
+
+ for _, name := range names {
+ _ = os.Remove(filepath.Join(c.dir, name))
+ }
+ return nil
+}
+
+// Save a value in File storage by key
+func (c *AdapterFile) Save(key string, value string, lifeTime time.Duration) error {
+ duration := int64(0)
+
+ if lifeTime > 0 {
+ duration = time.Now().Unix() + int64(lifeTime.Seconds())
+ }
+
+ content := &fileContent{duration, value}
+
+ data, err := json.Marshal(content)
+ if err != nil {
+ return err
+ }
+
+ err = os.WriteFile(c.createName(key), data, perm)
+ return err
+}
diff --git a/server/internal/library/captcha/captcha.go b/server/internal/library/captcha/captcha.go
new file mode 100644
index 0000000..6cdca8c
--- /dev/null
+++ b/server/internal/library/captcha/captcha.go
@@ -0,0 +1,77 @@
+// Package captcha
+// @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 captcha
+
+import (
+ "context"
+ "hotgo/internal/consts"
+ "image/color"
+
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/text/gstr"
+ "github.com/mojocn/base64Captcha"
+)
+
+// store 验证码存储方式
+var store = base64Captcha.DefaultMemStore
+
+// Generate 生成验证码
+func Generate(ctx context.Context, captchaType int) (id string, base64 string) {
+ var err error
+
+ switch captchaType {
+ // 算数
+ case consts.CaptchaTypeMath:
+ driver := &base64Captcha.DriverMath{
+ Height: 42,
+ Width: 100,
+ NoiseCount: 0,
+ ShowLineOptions: 0,
+ BgColor: &color.RGBA{
+ R: 255,
+ G: 250,
+ B: 250,
+ A: 250,
+ },
+ Fonts: []string{"chromohv.ttf"},
+ }
+ c := base64Captcha.NewCaptcha(driver.ConvertFonts(), store)
+ id, base64, _, err = c.Generate()
+ // 字符
+ default:
+ driver := &base64Captcha.DriverString{
+ Height: 42,
+ Width: 100,
+ //NoiseCount: 50,
+ //ShowLineOptions: 20,
+ Length: 4,
+ BgColor: &color.RGBA{
+ R: 255,
+ G: 250,
+ B: 250,
+ A: 250,
+ },
+ Source: "abcdefghjkmnpqrstuvwxyz23456789", // abcdefghjkmnpqrstuvwxyz23456789
+ Fonts: []string{"chromohv.ttf"},
+ }
+ c := base64Captcha.NewCaptcha(driver.ConvertFonts(), store)
+ id, base64, _, err = c.Generate()
+ }
+
+ if err != nil {
+ g.Log().Errorf(ctx, "captcha.Generate err:%+v", err)
+ }
+
+ return
+}
+
+// Verify 验证输入的验证码是否正确
+func Verify(id, answer string) bool {
+ if id == "" || answer == "" {
+ return false
+ }
+ return store.Verify(id, gstr.ToLower(answer), true)
+}
diff --git a/server/internal/library/casbin/adapter.go b/server/internal/library/casbin/adapter.go
new file mode 100644
index 0000000..7264fa4
--- /dev/null
+++ b/server/internal/library/casbin/adapter.go
@@ -0,0 +1,333 @@
+// Package casbin
+// @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 casbin
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "hotgo/internal/consts"
+ "hotgo/internal/dao"
+ "math"
+ "strings"
+
+ "github.com/casbin/casbin/v2/model"
+ "github.com/casbin/casbin/v2/persist"
+ "github.com/gogf/gf/v2/database/gdb"
+)
+
+var defaultTableName = dao.AdminRoleCasbin.Table()
+
+const (
+ dropPolicyTableSql = `DROP TABLE IF EXISTS %s`
+ createPolicyTableSql = `
+CREATE TABLE IF NOT EXISTS %s (
+ id bigint(20) NOT NULL AUTO_INCREMENT,
+ p_type varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
+ v0 varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
+ v1 varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
+ v2 varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
+ v3 varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
+ v4 varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
+ v5 varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
+ PRIMARY KEY (id) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '管理员_casbin权限表' ROW_FORMAT = Dynamic;
+`
+ createPolicyTablePgSql = `CREATE TABLE IF NOT EXISTS "public"."%s" (
+ "id" int8 NOT NULL DEFAULT nextval('hg_admin_role_casbin_id_seq'::regclass),
+ "p_type" varchar(64) COLLATE "pg_catalog"."default",
+ "v0" varchar(256) COLLATE "pg_catalog"."default",
+ "v1" varchar(256) COLLATE "pg_catalog"."default",
+ "v2" varchar(256) COLLATE "pg_catalog"."default",
+ "v3" varchar(256) COLLATE "pg_catalog"."default",
+ "v4" varchar(256) COLLATE "pg_catalog"."default",
+ "v5" varchar(256) COLLATE "pg_catalog"."default",
+ CONSTRAINT "hg_admin_role_casbin_pkey" PRIMARY KEY ("id")
+)
+;
+
+ALTER TABLE "public"."%s"
+ OWNER TO "postgres";
+
+COMMENT ON TABLE "public"."%s" IS '管理员_casbin权限表';`
+)
+
+type (
+ adapter struct {
+ db gdb.DB
+ table string
+ }
+
+ policyColumns struct {
+ ID string // ID
+ PType string // PType
+ V0 string // V0
+ V1 string // V1
+ V2 string // V2
+ V3 string // V3
+ V4 string // V4
+ V5 string // V5
+ }
+
+ // policy rule entity
+ policyRule struct {
+ ID int64 `orm:"id" json:"id"`
+ PType string `orm:"p_type" json:"p_type"`
+ V0 string `orm:"v0" json:"v0"`
+ V1 string `orm:"v1" json:"v1"`
+ V2 string `orm:"v2" json:"v2"`
+ V3 string `orm:"v3" json:"v3"`
+ V4 string `orm:"v4" json:"v4"`
+ V5 string `orm:"v5" json:"v5"`
+ }
+)
+
+var (
+ errInvalidDatabaseLink = errors.New("invalid database link")
+ policyColumnsName = policyColumns{
+ ID: "id",
+ PType: "p_type",
+ V0: "v0",
+ V1: "v1",
+ V2: "v2",
+ V3: "v3",
+ V4: "v4",
+ V5: "v5",
+ }
+)
+
+// NewAdapter Create a casbin adapter
+func NewAdapter(link string) (adp *adapter, err error) {
+ adp = &adapter{table: defaultTableName}
+ config := strings.SplitN(link, ":", 2)
+
+ if len(config) != 2 {
+ err = errInvalidDatabaseLink
+ return
+ }
+
+ if adp.db, err = gdb.New(gdb.ConfigNode{Type: config[0], Link: config[1]}); err != nil {
+ return
+ }
+
+ err = adp.createPolicyTable()
+
+ return
+}
+
+func (a *adapter) model() *gdb.Model {
+ return a.db.Model(a.table).Safe().Ctx(context.TODO())
+}
+
+// create a policy table when it's not exists.
+func (a *adapter) createPolicyTable() (err error) {
+ if a.db.GetConfig().Type == consts.DBPgsql {
+ _, err = a.db.Exec(context.TODO(), fmt.Sprintf(createPolicyTablePgSql, a.table, a.table, a.table))
+ return
+ }
+ _, err = a.db.Exec(context.TODO(), fmt.Sprintf(createPolicyTableSql, a.table))
+ return
+}
+
+// drop policy table from the storage.
+func (a *adapter) dropPolicyTable() (err error) {
+ _, err = a.db.Exec(context.TODO(), fmt.Sprintf(dropPolicyTableSql, a.table))
+ return
+}
+
+// LoadPolicy loads all policy rules from the storage.
+func (a *adapter) LoadPolicy(model model.Model) (err error) {
+ var rules []policyRule
+ if err = a.model().Scan(&rules); err != nil {
+ return
+ }
+
+ for _, rule := range rules {
+ a.loadPolicyRule(rule, model)
+ }
+ return
+}
+
+// SavePolicy Saves all policy rules to the storage.
+func (a *adapter) SavePolicy(model model.Model) (err error) {
+ if err = a.dropPolicyTable(); err != nil {
+ return
+ }
+
+ if err = a.createPolicyTable(); err != nil {
+ return
+ }
+
+ policyRules := make([]policyRule, 0)
+
+ for ptype, ast := range model["p"] {
+ for _, rule := range ast.Policy {
+ policyRules = append(policyRules, a.buildPolicyRule(ptype, rule))
+ }
+ }
+
+ for ptype, ast := range model["g"] {
+ for _, rule := range ast.Policy {
+ policyRules = append(policyRules, a.buildPolicyRule(ptype, rule))
+ }
+ }
+
+ if count := len(policyRules); count > 0 {
+ if _, err = a.model().OmitEmptyData().FieldsEx(policyColumnsName.ID).Insert(policyRules); err != nil {
+ return
+ }
+ }
+ return
+}
+
+// AddPolicy adds a policy rule to the storage.
+func (a *adapter) AddPolicy(sec string, ptype string, rule []string) (err error) {
+ _, err = a.model().OmitEmptyData().FieldsEx(policyColumnsName.ID).Insert(a.buildPolicyRule(ptype, rule))
+ return
+}
+
+// AddPolicies adds policy rules to the storage.
+func (a *adapter) AddPolicies(sec string, ptype string, rules [][]string) (err error) {
+ if len(rules) == 0 {
+ return
+ }
+
+ policyRules := make([]policyRule, 0, len(rules))
+
+ for _, rule := range rules {
+ policyRules = append(policyRules, a.buildPolicyRule(ptype, rule))
+ }
+
+ _, err = a.model().OmitEmptyData().FieldsEx(policyColumnsName.ID).Insert(policyRules)
+ return
+}
+
+// RemovePolicy removes a policy rule from the storage.
+func (a *adapter) RemovePolicy(sec string, ptype string, rule []string) (err error) {
+ db := a.model()
+ db = db.Where(policyColumnsName.PType, ptype)
+ for index := 0; index < len(rule); index++ {
+ db = db.Where(fmt.Sprintf("v%d", index), rule[index])
+ }
+ _, err = db.Delete()
+ return err
+}
+
+// RemoveFilteredPolicy removes policy rules that match the filter from the storage.
+func (a *adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) (err error) {
+ db := a.model()
+ db = db.Where(policyColumnsName.PType, ptype)
+ for index := 0; index <= 5; index++ {
+ if fieldIndex <= index && index < fieldIndex+len(fieldValues) {
+ db = db.Where(fmt.Sprintf("v%d", index), fieldValues[index-fieldIndex])
+ }
+ }
+ _, err = db.Delete()
+ return
+}
+
+// RemovePolicies removes policy rules from the storage (implements the persist.BatchAdapter interface).
+func (a *adapter) RemovePolicies(sec string, ptype string, rules [][]string) (err error) {
+ db := a.model()
+
+ for _, rule := range rules {
+ where := map[string]interface{}{policyColumnsName.PType: ptype}
+
+ for i := 0; i <= 5; i++ {
+ if len(rule) > i {
+ where[fmt.Sprintf("v%d", i)] = rule[i]
+ }
+ }
+
+ db = db.WhereOr(where)
+ }
+
+ _, err = db.Delete()
+ return
+}
+
+// UpdatePolicy updates a policy rule from storage.
+func (a *adapter) UpdatePolicy(sec string, ptype string, oldRule, newRule []string) (err error) {
+ _, err = a.model().Update(a.buildPolicyRule(ptype, newRule), a.buildPolicyRule(ptype, oldRule))
+ return
+}
+
+// UpdatePolicies updates some policy rules to storage, like db, redis.
+func (a *adapter) UpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) (err error) {
+ if len(oldRules) == 0 || len(newRules) == 0 {
+ return
+ }
+ return a.db.Transaction(context.TODO(), func(ctx context.Context, tx gdb.TX) error {
+ for i := 0; i < int(math.Min(float64(len(oldRules)), float64(len(newRules)))); i++ {
+ if _, err = tx.Model(a.table).Update(a.buildPolicyRule(ptype, newRules[i]), a.buildPolicyRule(ptype, oldRules[i])); err != nil {
+ return err
+ }
+ }
+ return nil
+ })
+}
+
+// 加载策略规则
+func (a *adapter) loadPolicyRule(rule policyRule, model model.Model) {
+ ruleText := rule.PType
+
+ if rule.V0 != "" {
+ ruleText += ", " + rule.V0
+ }
+
+ if rule.V1 != "" {
+ ruleText += ", " + rule.V1
+ }
+
+ if rule.V2 != "" {
+ ruleText += ", " + rule.V2
+ }
+
+ if rule.V3 != "" {
+ ruleText += ", " + rule.V3
+ }
+
+ if rule.V4 != "" {
+ ruleText += ", " + rule.V4
+ }
+
+ if rule.V5 != "" {
+ ruleText += ", " + rule.V5
+ }
+ if err := persist.LoadPolicyLine(ruleText, model); err != nil {
+ panic(err)
+ }
+}
+
+// 构建策略规则
+func (a *adapter) buildPolicyRule(ptype string, data []string) policyRule {
+ rule := policyRule{PType: ptype}
+
+ if len(data) > 0 {
+ rule.V0 = data[0]
+ }
+
+ if len(data) > 1 {
+ rule.V1 = data[1]
+ }
+
+ if len(data) > 2 {
+ rule.V2 = data[2]
+ }
+
+ if len(data) > 3 {
+ rule.V3 = data[3]
+ }
+
+ if len(data) > 4 {
+ rule.V4 = data[4]
+ }
+
+ if len(data) > 5 {
+ rule.V5 = data[5]
+ }
+ return rule
+}
diff --git a/server/internal/library/casbin/casbin_test.go b/server/internal/library/casbin/casbin_test.go
new file mode 100644
index 0000000..6dafe7a
--- /dev/null
+++ b/server/internal/library/casbin/casbin_test.go
@@ -0,0 +1,67 @@
+// Package casbin
+// @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 casbin
+
+import (
+ "context"
+ "net/http"
+ "testing"
+)
+
+// TestNew description
+func TestNew(t *testing.T) {
+
+ InitEnforcer(context.TODO())
+
+ user := "admin"
+ path := "/"
+ method := http.MethodGet
+ t.Logf("\nuser:%v\npath:%v\nmethod:%v", user, path, method)
+
+ ok, err := Enforcer.DeletePermissionsForUser(user)
+ if err != nil {
+ t.Error(err)
+ }
+ t.Logf("delete user premission:%v", ok)
+ CheckPremission(t, user, path, method)
+ AddPremission(t, user, "*", ActionAll)
+ CheckPremission(t, user, path, method)
+
+ user1 := "user1"
+ path1 := "/api/v1/*"
+ checkPathTrue := "/api/v1/user/list"
+ checkPathFalse := "/api/v2/user/list"
+ AddPremission(t, user1, path1, ActionAll)
+ CheckPremission(t, user1, checkPathTrue, ActionPost)
+ CheckPremission(t, user1, checkPathFalse, http.MethodGet)
+ CheckPremission(t, user1, checkPathTrue, http.MethodGet)
+ CheckPremission(t, user1, "/api/v1/user/list2", http.MethodGet)
+
+ ok, err = Enforcer.DeletePermissionsForUser(user1)
+ if err != nil {
+ t.Error(err)
+ }
+ t.Logf("delete user premission:%v", ok)
+ CheckPremission(t, user1, "/api/v1/user1/list", http.MethodGet)
+}
+
+// CheckPremission description
+func CheckPremission(t *testing.T, user string, path string, method string) {
+ ok, err := Enforcer.Enforce(user, path, method)
+ if err != nil {
+ t.Error(err)
+ }
+ t.Logf("check \tuser[%s] \tpremission[%s] \tpath[%s] \tallow[%v]", user, method, path, ok)
+}
+
+// Add description
+func AddPremission(t *testing.T, user string, path string, method string) {
+ ok, err := Enforcer.AddPolicy(user, path, method)
+ if err != nil {
+ t.Error(err)
+ }
+ t.Logf("add \tuser[%s] \tpremission[%s] \tpath[%s] \tresult[%v]", user, method, path, ok)
+}
diff --git a/server/internal/library/casbin/enforcer.go b/server/internal/library/casbin/enforcer.go
new file mode 100644
index 0000000..3676d11
--- /dev/null
+++ b/server/internal/library/casbin/enforcer.go
@@ -0,0 +1,164 @@
+// Package casbin
+// @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 casbin
+
+import (
+ "context"
+ "fmt"
+ "hotgo/internal/consts"
+ "hotgo/internal/dao"
+ "net/http"
+ "strings"
+
+ "github.com/casbin/casbin/v2"
+ "github.com/casbin/casbin/v2/model"
+ _ "github.com/gogf/gf/contrib/drivers/mysql/v2"
+ "github.com/gogf/gf/v2/container/gvar"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gres"
+ "github.com/gogf/gf/v2/text/gstr"
+)
+
+const (
+ ActionGet = http.MethodGet
+ ActionPost = http.MethodPost
+ ActionPut = http.MethodPut
+ ActionDelete = http.MethodDelete
+ ActionAll = "GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD"
+)
+
+var Enforcer *casbin.Enforcer
+
+// InitEnforcer 初始化
+func InitEnforcer(ctx context.Context) {
+ var (
+ link = getDbLink(ctx)
+ a, err = NewAdapter(link.String())
+ )
+
+ if err != nil {
+ g.Log().Panicf(ctx, "casbin.NewAdapter 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)
+}
+
+// GetDbLink 获取数据库链接
+func getDbLink(ctx context.Context) *gvar.Var {
+
+ link := g.Cfg().MustGet(ctx, "database.default")
+ //读写分离
+ if !link.IsSlice() {
+ return g.Cfg().MustGet(ctx, "database.default.link")
+ }
+
+ for _, v := range link.Array() {
+ // 只获取主库
+ val := v.(map[string]interface{})
+ if val["role"] == "master" {
+ return gvar.New(val["link"])
+ }
+ }
+ return gvar.New("database.default.0.link")
+
+}
+
+func loadPermissions(ctx context.Context) {
+ type Policy struct {
+ Key string `json:"key"`
+ Permissions string `json:"permissions"`
+ }
+ var (
+ rules [][]string
+ polices []*Policy
+ err error
+ )
+ //别名拼接 r.key m.permissions
+ q := func(alias string, column string) string {
+ return fmt.Sprintf("%s.%s", alias, column)
+ }
+ err = g.Model(gstr.Join([]string{dao.AdminRole.Table(), "r"}, " ")).
+ LeftJoin(gstr.Join([]string{dao.AdminRoleMenu.Table(), "rm"}, " "), "r.id=rm.role_id").
+ LeftJoin(gstr.Join([]string{dao.AdminMenu.Table(), "m"}, " "), "rm.menu_id=m.id").
+ Fields(q("r", dao.AdminRole.Columns().Key), q("m", dao.AdminMenu.Columns().Permissions)).
+ Where(q("r", dao.AdminRole.Columns().Status), consts.StatusEnabled).
+ Where(q("m", dao.AdminMenu.Columns().Status), consts.StatusEnabled).
+ WhereNot(q("m", dao.AdminMenu.Columns().Permissions), "").
+ WhereNot(q("r", dao.AdminRole.Columns().Key), consts.SuperRoleKey).
+ Scan(&polices)
+ if err != nil {
+ g.Log().Fatalf(ctx, "loadPermissions Scan err:%v", err)
+ return
+ }
+
+ for _, policy := range polices {
+ if strings.Contains(policy.Permissions, ",") {
+ lst := strings.Split(policy.Permissions, ",")
+ for _, permissions := range lst {
+ rules = append(rules, []string{policy.Key, permissions, ActionAll})
+ }
+ } else {
+ rules = append(rules, []string{policy.Key, policy.Permissions, ActionAll})
+ }
+ }
+
+ if _, err = Enforcer.AddPolicies(rules); err != nil {
+ g.Log().Fatalf(ctx, "loadPermissions AddPolicies err:%v", err)
+ return
+ }
+}
+
+func Clear(ctx context.Context) (err error) {
+ policy, err := Enforcer.GetPolicy()
+ if err != nil {
+ g.Log().Warningf(ctx, "Enforcer RemovePolicies err:%+v", err)
+ return
+ }
+ _, err = Enforcer.RemovePolicies(policy)
+ if err != nil {
+ g.Log().Warningf(ctx, "Enforcer RemovePolicies err:%+v", err)
+ return
+ }
+
+ // 检查是否清理干净
+ if len(policy) > 0 {
+ return Clear(ctx)
+ }
+ return
+}
+
+func Refresh(ctx context.Context) (err error) {
+ if err = Clear(ctx); err != nil {
+ return err
+ }
+ loadPermissions(ctx)
+ return
+}
diff --git a/server/internal/library/contexts/context.go b/server/internal/library/contexts/context.go
new file mode 100644
index 0000000..b9cd6bd
--- /dev/null
+++ b/server/internal/library/contexts/context.go
@@ -0,0 +1,195 @@
+// Package contexts
+// @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 contexts
+
+import (
+ "context"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/net/ghttp"
+ "hotgo/internal/consts"
+ "hotgo/internal/model"
+)
+
+// Init 初始化上下文对象指针到上下文对象中,以便后续的请求流程中可以修改
+func Init(r *ghttp.Request, customCtx *model.Context) {
+ r.SetCtxVar(consts.ContextHTTPKey, customCtx)
+}
+
+// Get 获得上下文变量,如果没有设置,那么返回nil
+func Get(ctx context.Context) *model.Context {
+ value := ctx.Value(consts.ContextHTTPKey)
+ if value == nil {
+ return nil
+ }
+ if localCtx, ok := value.(*model.Context); ok {
+ return localCtx
+ }
+ return nil
+}
+
+// SetUser 将上下文信息设置到上下文请求中,注意是完整覆盖
+func SetUser(ctx context.Context, user *model.Identity) {
+ c := Get(ctx)
+ if c == nil {
+ g.Log().Warning(ctx, "contexts.SetUser, c == nil ")
+ return
+ }
+ c.User = user
+}
+
+// SetResponse 设置组件响应 用于访问日志使用
+func SetResponse(ctx context.Context, response *model.Response) {
+ c := Get(ctx)
+ if c == nil {
+ g.Log().Warning(ctx, "contexts.SetResponse, c == nil ")
+ return
+ }
+ c.Response = response
+}
+
+// SetModule 设置应用模块
+func SetModule(ctx context.Context, module string) {
+ c := Get(ctx)
+ if c == nil {
+ g.Log().Warning(ctx, "contexts.SetModule, c == nil ")
+ return
+ }
+ c.Module = module
+}
+
+// GetUser 获取用户信息
+func GetUser(ctx context.Context) *model.Identity {
+ c := Get(ctx)
+ if c == nil {
+ return nil
+ }
+ return c.User
+}
+
+// GetUserId 获取用户ID
+func GetUserId(ctx context.Context) int64 {
+ user := GetUser(ctx)
+ if user == nil {
+ return 0
+ }
+ return user.Id
+}
+
+// GetRoleId 获取用户角色ID
+func GetRoleId(ctx context.Context) int64 {
+ user := GetUser(ctx)
+ if user == nil {
+ return 0
+ }
+ return user.RoleId
+}
+
+// GetRoleKey 获取用户角色唯一编码
+func GetRoleKey(ctx context.Context) string {
+ user := GetUser(ctx)
+ if user == nil {
+ return ""
+ }
+ 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)
+ if c == nil {
+ return ""
+ }
+ return c.Module
+}
+
+// SetAddonName 设置插件信息
+func SetAddonName(ctx context.Context, name string) {
+ c := Get(ctx)
+ if c == nil {
+ g.Log().Warning(ctx, "contexts.SetAddonName, c == nil ")
+ return
+ }
+ Get(ctx).AddonName = name
+}
+
+// IsAddonRequest 是否为插件模块请求
+func IsAddonRequest(ctx context.Context) bool {
+ c := Get(ctx)
+ if c == nil {
+ return false
+ }
+ return GetAddonName(ctx) != ""
+}
+
+// GetAddonName 获取插件信息
+func GetAddonName(ctx context.Context) string {
+ c := Get(ctx)
+ if c == nil {
+ return ""
+ }
+ return Get(ctx).AddonName
+}
+
+// SetData 设置额外数据
+func SetData(ctx context.Context, k string, v interface{}) {
+ c := Get(ctx)
+ if c == nil {
+ g.Log().Warning(ctx, "contexts.SetData, c == nil ")
+ return
+ }
+ Get(ctx).Data[k] = v
+}
+
+// SetDataMap 设置额外数据
+func SetDataMap(ctx context.Context, vs g.Map) {
+ c := Get(ctx)
+ if c == nil {
+ g.Log().Warning(ctx, "contexts.SetData, c == nil ")
+ return
+ }
+
+ for k, v := range vs {
+ Get(ctx).Data[k] = v
+ }
+}
+
+// GetData 获取额外数据
+func GetData(ctx context.Context) g.Map {
+ c := Get(ctx)
+ if c == nil {
+ return nil
+ }
+ return c.Data
+}
diff --git a/server/internal/library/contexts/detached.go b/server/internal/library/contexts/detached.go
new file mode 100644
index 0000000..e7c1310
--- /dev/null
+++ b/server/internal/library/contexts/detached.go
@@ -0,0 +1,35 @@
+// Package contexts
+// @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 contexts
+
+import (
+ "context"
+ "time"
+)
+
+type detached struct {
+ ctx context.Context
+}
+
+func (detached) Deadline() (time.Time, bool) {
+ return time.Time{}, false
+}
+
+func (detached) Done() <-chan struct{} {
+ return nil
+}
+
+func (detached) Err() error {
+ return nil
+}
+
+func (d detached) Value(key interface{}) interface{} {
+ return d.ctx.Value(key)
+}
+
+func Detach(ctx context.Context) context.Context {
+ return detached{ctx: ctx}
+}
diff --git a/server/internal/library/cron/cron.go b/server/internal/library/cron/cron.go
new file mode 100644
index 0000000..450cf08
--- /dev/null
+++ b/server/internal/library/cron/cron.go
@@ -0,0 +1,313 @@
+// Package cron
+// @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 cron
+
+import (
+ "bufio"
+ "context"
+ "fmt"
+ "github.com/gogf/gf/v2/errors/gerror"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gcron"
+ "github.com/gogf/gf/v2/os/gctx"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/glog"
+ "github.com/gogf/gf/v2/os/gtime"
+ "hotgo/internal/consts"
+ "hotgo/internal/dao"
+ "hotgo/internal/model/entity"
+ "hotgo/utility/simple"
+ "os"
+ "strings"
+ "sync"
+)
+
+var crons = &cronManager{
+ tasks: make(map[string]*TaskItem),
+ loggers: make(map[string]*glog.Logger),
+}
+
+// Cron 定时任务接口
+type Cron interface {
+ // GetName 获取任务名称
+ GetName() string
+ // Execute 执行任务
+ Execute(ctx context.Context, parser *Parser) (err error)
+}
+
+// Parser 任务执行参数
+type Parser struct {
+ Args []string // 任务参数
+ Logger *glog.Logger // 日志管理实例
+}
+
+// Log 任务调度日志
+type Log struct {
+ FileName string `json:"fileName" dc:"文件名称"`
+ SizeFormat string `json:"sizeFormat" dc:"文件大小"`
+ Contents string `json:"contents" dc:"文件内容"`
+}
+
+// consumerManager 任务管理者
+type cronManager struct {
+ tasks map[string]*TaskItem
+ loggers map[string]*glog.Logger
+ sync.RWMutex
+}
+
+type TaskItem struct {
+ Pattern string // 表达式,参考:https://goframe.org/pages/viewpage.action?pageId=30736411
+ Name string // 唯一的任务名称
+ Params string // 函数参数,多个用,隔开
+ Fun gcron.JobFunc // 执行的函数接口
+ Policy int64 // 策略 1:并行 2:单例 3:单次 4:多次
+ Count int // 执行次数,仅Policy=4时有效
+}
+
+func Logger() *glog.Logger {
+ return g.Log("cron")
+}
+
+// Register 注册任务
+func Register(c Cron) {
+ crons.Lock()
+ defer crons.Unlock()
+
+ name := c.GetName()
+ if _, ok := crons.tasks[name]; ok {
+ Logger().Debugf(gctx.GetInitCtx(), "cron.Register name:%v duplicate registration.", name)
+ return
+ }
+
+ crons.tasks[name] = &TaskItem{Name: c.GetName(), Fun: GenExecuteFun(c.Execute)}
+}
+
+// StopALL 停止所有任务
+func StopALL() {
+ for _, v := range gcron.Entries() {
+ gcron.Remove(v.Name)
+ }
+}
+
+// StartALL 启动所有任务
+func StartALL(sysCron []*entity.SysCron) (err error) {
+ crons.Lock()
+ defer crons.Unlock()
+
+ if len(crons.tasks) == 0 {
+ g.Log().Debug(gctx.GetInitCtx(), "no scheduled task is available.")
+ return
+ }
+
+ for _, cron := range sysCron {
+ f, ok := crons.tasks[cron.Name]
+ if !ok {
+ return gerror.Newf("该任务没有加入任务列表:%v", cron.Name)
+ }
+
+ sn := GenCronSn(cron)
+
+ // 没有则添加
+ if gcron.Search(sn) == nil {
+ var (
+ t *gcron.Entry
+ ctx = GenCronCtx(cron)
+ )
+ switch cron.Policy {
+ case consts.CronPolicySame:
+ t, err = gcron.Add(ctx, cron.Pattern, f.Fun, sn)
+
+ case consts.CronPolicySingle:
+ t, err = gcron.AddSingleton(ctx, cron.Pattern, f.Fun, sn)
+
+ case consts.CronPolicyOnce:
+ t, err = gcron.AddOnce(ctx, cron.Pattern, f.Fun, sn)
+
+ case consts.CronPolicyTimes:
+ if f.Count <= 0 {
+ f.Count = 1
+ }
+ t, err = gcron.AddTimes(ctx, cron.Pattern, int(cron.Count), f.Fun, sn)
+
+ default:
+ return gerror.Newf("使用无效的策略, cron.Policy=%v", cron.Policy)
+ }
+
+ if err != nil {
+ return err
+ }
+ if t == nil {
+ return gerror.New("启动任务失败")
+ }
+ }
+
+ gcron.Start(sn)
+
+ // 执行完毕,单次和多次执行的任务更新状态
+ if cron.Policy == consts.CronPolicyOnce || cron.Policy == consts.CronPolicyTimes {
+ if _, err = dao.SysCron.Ctx(gctx.GetInitCtx()).Where(dao.SysCron.Columns().Id, cron.Id).Data(g.Map{dao.SysCron.Columns().Status: consts.StatusDisable, dao.SysCron.Columns().UpdatedAt: gtime.Now()}).Update(); err != nil {
+ err = gerror.Wrap(err, "定时任务执行失败!")
+ return err
+ }
+ }
+ }
+
+ Logger().Debug(gctx.GetInitCtx(), "load cron success..")
+ return nil
+}
+
+// RefreshStatus 刷新状态
+func RefreshStatus(sysCron *entity.SysCron) (err error) {
+ if sysCron == nil {
+ return
+ }
+
+ if sysCron.Status == consts.StatusEnabled {
+ return ResetStart(sysCron)
+ }
+ return Stop(sysCron)
+}
+
+// Stop 停止单个任务
+func Stop(sysCron *entity.SysCron) (err error) {
+ cr := gcron.Search(GenCronSn(sysCron))
+ if cr == nil {
+ return
+ }
+ cr.Stop()
+ return
+}
+
+// ResetStart 重置任务
+func ResetStart(sysCron *entity.SysCron) (err error) {
+ if err = Stop(sysCron); err != nil {
+ return
+ }
+ if err = Delete(sysCron); err != nil {
+ return
+ }
+ return Start(sysCron)
+}
+
+// Once 立即执行一次某个任务
+func Once(ctx context.Context, sysCron *entity.SysCron) error {
+ crons.RLock()
+ defer crons.RUnlock()
+
+ for _, v := range crons.tasks {
+ if v.Name == sysCron.Name {
+ simple.SafeGo(ctx, func(ctx context.Context) {
+ v.Fun(GenCronCtx(sysCron))
+ })
+ return nil
+ }
+ }
+ return gerror.Newf("定时任务不存在:%+v", sysCron.Name)
+}
+
+// Delete 删除任务
+func Delete(sysCron *entity.SysCron) (err error) {
+ if sysCron == nil {
+ return
+ }
+
+ for _, v := range gcron.Entries() {
+ if v.Name == GenCronSn(sysCron) {
+ gcron.Remove(v.Name)
+ }
+ }
+ return
+}
+
+// Start 启动单个任务
+func Start(sysCron *entity.SysCron) (err error) {
+ if sysCron == nil {
+ return
+ }
+
+ c := gcron.Search(GenCronSn(sysCron))
+ if c != nil {
+ c.Start()
+ return
+ }
+ return StartALL([]*entity.SysCron{sysCron})
+}
+
+// DispatchLog 查看指定任务的调度日志
+func DispatchLog(sysCron *entity.SysCron) (log *Log, err error) {
+ path := fmt.Sprintf("%v/%v", Logger().GetConfig().Path, GenCronSn(sysCron))
+ file, err := FindLastModifiedFile(path)
+ if err != nil {
+ return nil, err
+ }
+
+ if len(file) == 0 || !gfile.IsFile(file) {
+ err = gerror.New("未找到日志!")
+ return
+ }
+
+ log = new(Log)
+ log.FileName = file
+ log.SizeFormat = gfile.SizeFormat(file)
+
+ if gfile.Size(file) > 1024*50 {
+ log.Contents, err = ReadLastLines(file, 100)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ log.Contents = gfile.GetContents(file)
+ }
+ return
+}
+
+func ReadLastLines(filename string, lineCount int) (string, error) {
+ file, err := os.Open(filename)
+ if err != nil {
+ return "", err
+ }
+ defer file.Close()
+
+ scanner := bufio.NewScanner(file)
+
+ lines := make([]string, 0, lineCount)
+ for scanner.Scan() {
+ lines = append(lines, scanner.Text())
+ if len(lines) > lineCount {
+ lines = lines[1:]
+ }
+ }
+
+ if err = scanner.Err(); err != nil {
+ return "", err
+ }
+
+ result := strings.Join(lines, "\n")
+ return result, nil
+}
+
+func FindLastModifiedFile(dirPath string) (string, error) {
+ if !gfile.Exists(dirPath) {
+ return "", gerror.New("该任务暂未产生日志!")
+ }
+
+ files, err := gfile.ScanDir(dirPath, "*.log", true)
+ if err != nil {
+ return "", err
+ }
+
+ var lastModifiedFile string
+ var lastModifiedTime int64 = 0
+
+ for _, file := range files {
+ modTime := gfile.MTimestamp(file)
+ if modTime > lastModifiedTime {
+ lastModifiedTime = modTime
+ lastModifiedFile = file
+ }
+ }
+ return lastModifiedFile, nil
+}
diff --git a/server/internal/library/cron/cron_gen.go b/server/internal/library/cron/cron_gen.go
new file mode 100644
index 0000000..f19c308
--- /dev/null
+++ b/server/internal/library/cron/cron_gen.go
@@ -0,0 +1,88 @@
+// Package cron
+// @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 cron
+
+import (
+ "context"
+ "fmt"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gctx"
+ "github.com/gogf/gf/v2/os/glog"
+ "github.com/gogf/gf/v2/os/gtime"
+ "hotgo/internal/consts"
+ "hotgo/internal/model/entity"
+ "strings"
+)
+
+// GenCronSn 生成任务序列号
+func GenCronSn(sysCron *entity.SysCron) string {
+ return fmt.Sprintf("%s@%d", sysCron.Name, sysCron.Id)
+}
+
+// GenCronCtx 生成任务上下文
+func GenCronCtx(sysCron *entity.SysCron) (ctx context.Context) {
+ ctx = context.WithValue(gctx.New(), consts.ContextKeyCronArgs, strings.Split(sysCron.Params, consts.CronSplitStr))
+ ctx = context.WithValue(ctx, consts.ContextKeyCronSn, GenCronSn(sysCron))
+ return ctx
+}
+
+func GenLoggerByCtx(ctx context.Context) *glog.Logger {
+ sn, ok := ctx.Value(consts.ContextKeyCronSn).(string)
+ if !ok {
+ Logger().Panic(ctx, "获取定时任务序列号失败!")
+ }
+
+ crons.RLock()
+ logger, ok := crons.loggers[sn]
+ crons.RUnlock()
+ if ok {
+ return logger
+ }
+
+ logger = glog.New()
+ if err := logger.SetConfig(Logger().GetConfig()); err != nil {
+ Logger().Panic(ctx, err)
+ }
+
+ logger.SetFlags(glog.F_TIME_STD | glog.F_FILE_SHORT)
+ // 设置子路径
+ if err := logger.SetPath(fmt.Sprintf("%v/%v", logger.GetPath(), sn)); err != nil {
+ Logger().Panic(ctx, err)
+ }
+
+ crons.Lock()
+ defer crons.Unlock()
+ crons.loggers[sn] = logger
+ return logger
+}
+
+// GenExecuteFun 生成执行过程
+func GenExecuteFun(fun func(ctx context.Context, parser *Parser) (err error)) func(ctx context.Context) {
+ return func(ctx context.Context) {
+ args, ok := ctx.Value(consts.ContextKeyCronArgs).([]string)
+ if !ok {
+ Logger().Panic(ctx, "执行定时任务时,参数解析失败!")
+ return
+ }
+ parser := new(Parser)
+ parser.Args = args
+ parser.Logger = GenLoggerByCtx(ctx)
+
+ st := gtime.Now()
+ err := g.Try(ctx, func(ctx context.Context) {
+ if err := fun(ctx, parser); err != nil {
+ panic(err)
+ }
+ })
+
+ milliseconds := gtime.Now().Sub(st).Milliseconds() // 执行耗时
+ if err != nil {
+ parser.Logger.Errorf(ctx, "execute failed, took %vms, err:%+v", milliseconds, err)
+ return
+ }
+ parser.Logger.Infof(ctx, "execute success, took %vms.", milliseconds)
+ }
+}
diff --git a/server/internal/library/debris/debris.go b/server/internal/library/debris/debris.go
new file mode 100644
index 0000000..6defd0e
--- /dev/null
+++ b/server/internal/library/debris/debris.go
@@ -0,0 +1,9 @@
+// Package debris
+// @Link https://github.com/bufanyun/hotgo
+// @Copyright Copyright (c) 2023 HotGo CLI
+// @Author Ms <133814250@qq.com>
+// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
+//
+package debris
+
+// 碎片
diff --git a/server/internal/library/dict/dict.go b/server/internal/library/dict/dict.go
new file mode 100644
index 0000000..0bb04e5
--- /dev/null
+++ b/server/internal/library/dict/dict.go
@@ -0,0 +1,81 @@
+// Package dict
+// @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 dict
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "github.com/gogf/gf/v2/frame/g"
+ "hash/fnv"
+ "hotgo/internal/model"
+ "strconv"
+)
+
+const (
+ BuiltinId int64 = -1 // 内置字典ID
+ EnumsId int64 = -2 // 枚举字典ID
+ FuncId int64 = -3 // 方法字典ID
+)
+
+var NotExistKeyError = errors.New("not exist key")
+
+// GetOptions 获取内置选项
+func GetOptions(ctx context.Context, key string) (opts []*model.Option, err error) {
+ opts = GetEnumsOptions(key)
+ if opts != nil {
+ return
+ }
+ return GetFuncOptions(ctx, key)
+}
+
+// GetOptionsById 通过类型ID获取内置选项
+func GetOptionsById(ctx context.Context, id int64) (opts []*model.Option, err error) {
+ for _, v := range GetAllEnums() {
+ if v.Id == id {
+ return v.Opts, nil
+ }
+ }
+
+ 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)
+ }
+ }
+
+ err = NotExistKeyError
+ 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
+ h := fnv.New32a()
+ h.Write([]byte("dict" + str))
+
+ idStr := fmt.Sprintf("%d%d", prefix, int64(h.Sum32()))
+ id, _ := strconv.ParseInt(idStr, 10, 64)
+ return id
+}
diff --git a/server/internal/library/dict/dict_option.go b/server/internal/library/dict/dict_option.go
new file mode 100644
index 0000000..a628e26
--- /dev/null
+++ b/server/internal/library/dict/dict_option.go
@@ -0,0 +1,181 @@
+// Package dict
+// @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 dict
+
+import (
+ "hash/fnv"
+ "hotgo/internal/model"
+
+ "github.com/gogf/gf/v2/util/gconv"
+)
+
+// GenDefaultOption 生成默认表格回显样式
+func GenDefaultOption(key interface{}, label string, extra ...any) *model.Option {
+ e := interface{}(nil)
+ if len(extra) > 0 {
+ e = extra[0]
+ }
+ return &model.Option{
+ Key: key,
+ Label: label,
+ Value: key,
+ ListClass: "default",
+ Extra: e,
+ }
+}
+
+func GenSuccessOption(key interface{}, label string, extra ...any) *model.Option {
+ e := interface{}(nil)
+ if len(extra) > 0 {
+ e = extra[0]
+ }
+ return &model.Option{
+ Key: key,
+ Label: label,
+ Value: key,
+ ListClass: "success",
+ Extra: e,
+ }
+}
+
+func GenWarningOption(key interface{}, label string, extra ...any) *model.Option {
+ e := interface{}(nil)
+ if len(extra) > 0 {
+ e = extra[0]
+ }
+ return &model.Option{
+ Key: key,
+ Label: label,
+ Value: key,
+ ListClass: "warning",
+ Extra: e,
+ }
+}
+
+func GenErrorOption(key interface{}, label string, extra ...any) *model.Option {
+ e := interface{}(nil)
+ if len(extra) > 0 {
+ e = extra[0]
+ }
+ return &model.Option{
+ Key: key,
+ Label: label,
+ Value: key,
+ ListClass: "error",
+ Extra: e,
+ }
+}
+
+func GenInfoOption(key interface{}, label string, extra ...any) *model.Option {
+ e := interface{}(nil)
+ if len(extra) > 0 {
+ e = extra[0]
+ }
+ return &model.Option{
+ Key: key,
+ Label: label,
+ Value: key,
+ ListClass: "info",
+ Extra: e,
+ }
+}
+
+func GenPrimaryOption(key interface{}, label string, extra ...any) *model.Option {
+ e := interface{}(nil)
+ if len(extra) > 0 {
+ e = extra[0]
+ }
+ return &model.Option{
+ Key: key,
+ Label: label,
+ Value: key,
+ ListClass: "primary",
+ Extra: e,
+ }
+}
+
+// GenCustomOption 生成自定义表格回显样式
+func GenCustomOption(key interface{}, label string, custom string, extra ...any) *model.Option {
+ e := interface{}(nil)
+ if len(extra) > 0 {
+ e = extra[0]
+ }
+ return &model.Option{
+ Key: key,
+ Label: label,
+ Value: key,
+ ListClass: custom,
+ Extra: e,
+ }
+}
+
+// GenHashOption 根据不同label以hash算法生成表格回显样式
+func GenHashOption(key interface{}, label string, extra ...any) *model.Option {
+ e := interface{}(nil)
+ if len(extra) > 0 {
+ e = extra[0]
+ }
+ strings := []string{"default", "primary", "info", "success", "warning", "error"}
+ hash := fnv.New32()
+
+ tag := "default"
+ if _, err := hash.Write(gconv.Bytes(label)); err == nil {
+ index := uint32(hash.Sum32()) % uint32(len(strings))
+ if index < uint32(len(strings)) {
+ tag = strings[index]
+ }
+ }
+ return &model.Option{
+ Key: key,
+ Label: label,
+ Value: key,
+ ListClass: tag,
+ Extra: e,
+ }
+}
+
+// GetOption 通过key找到label
+func GetOption(ses []*model.Option, key interface{}) *model.Option {
+ for _, v := range ses {
+ if gconv.String(v.Key) == gconv.String(key) {
+ return v
+ }
+ }
+ return nil
+}
+
+// GetOptionLabel 通过key找到label
+func GetOptionLabel(ses []*model.Option, key interface{}) string {
+ for _, v := range ses {
+ if gconv.String(v.Key) == gconv.String(key) {
+ return v.Label
+ }
+ }
+ return ``
+}
+
+// HasOptionKey 是否存在指定key
+func HasOptionKey(ses []*model.Option, key interface{}) bool {
+ for _, v := range ses {
+ if gconv.String(v.Key) == gconv.String(key) {
+ return true
+ }
+ }
+ return false
+}
+
+// UniqueOption 去重选项
+func UniqueOption(src []*model.Option) (dst []*model.Option) {
+ temp := map[string]struct{}{}
+ for _, item := range src {
+ key := gconv.String(item.Key)
+ if _, ok := temp[key]; !ok {
+ temp[key] = struct{}{}
+ dst = append(dst, item)
+ }
+ }
+ return dst
+}
diff --git a/server/internal/library/dict/dict_register_enums.go b/server/internal/library/dict/dict_register_enums.go
new file mode 100644
index 0000000..434698b
--- /dev/null
+++ b/server/internal/library/dict/dict_register_enums.go
@@ -0,0 +1,70 @@
+// Package dict
+// @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 dict
+
+import (
+ "fmt"
+ "hotgo/internal/model"
+ "sync"
+)
+
+type EnumsOption struct {
+ Id int64 // 字典ID,由系统自动生成
+ Key string // 字典选项key
+ Label string // 字典选项标签名称
+ Opts []*model.Option // 数据选项
+}
+
+var (
+ enumsOptions = make(map[string]*EnumsOption)
+ eLock sync.Mutex
+)
+
+// GetAllEnums 获取所有枚举字典
+func GetAllEnums() map[string]*EnumsOption {
+ return enumsOptions
+}
+
+// RegisterEnums 注册枚举字典选项
+func RegisterEnums(key, label string, opts []*model.Option) {
+ eLock.Lock()
+ defer eLock.Unlock()
+
+ if len(key) == 0 {
+ panic("字典key不能为空")
+ }
+
+ if _, ok := enumsOptions[key]; ok {
+ panic(fmt.Sprintf("重复注册枚举字典选项:%v", key))
+ }
+
+ for _, v := range opts {
+ v.Type = key
+ }
+ enumsOptions[key] = &EnumsOption{
+ Id: GenIdHash(key, EnumsId),
+ Key: key,
+ Label: label,
+ Opts: opts,
+ }
+}
+
+// SaveEnums 更新枚举字典选项
+func SaveEnums(key, label string, opts []*model.Option) {
+ eLock.Lock()
+ defer eLock.Unlock()
+ delete(enumsOptions, key)
+ RegisterEnums(key, label, opts)
+}
+
+// GetEnumsOptions 获取指定枚举字典的数据选项
+func GetEnumsOptions(key string) []*model.Option {
+ enums, ok := enumsOptions[key]
+ if !ok {
+ return nil
+ }
+ return enums.Opts
+}
diff --git a/server/internal/library/dict/dict_register_func.go b/server/internal/library/dict/dict_register_func.go
new file mode 100644
index 0000000..cf75dcd
--- /dev/null
+++ b/server/internal/library/dict/dict_register_func.go
@@ -0,0 +1,129 @@
+// Package dict
+// @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 dict
+
+import (
+ "context"
+ "fmt"
+ "hotgo/internal/model"
+ "sync"
+)
+
+// FuncDict 方法字典,实现本接口即可使用内置方法字典
+type FuncDict func(ctx context.Context) (res []*model.Option, err error)
+
+type FuncOption struct {
+ Id int64 // 字典ID,由系统自动生成
+ Key string // 字典选项key
+ Label string // 字典选项标签名称
+ Fun FuncDict // 字典方法
+ Cache bool // 是否缓存数据选项
+ Opts []*model.Option // 缓存的数据选项
+ sync.Mutex
+}
+
+var (
+ funcOptions = make(map[string]*FuncOption)
+ fLock sync.Mutex
+)
+
+// GetAllFunc 获取所有方法字典
+func GetAllFunc() map[string]*FuncOption {
+ return funcOptions
+}
+
+// RegisterFunc 注册方法字典选项
+func RegisterFunc(key, label string, fun FuncDict, cache ...bool) {
+ fLock.Lock()
+ defer fLock.Unlock()
+
+ if len(key) == 0 {
+ panic("字典key不能为空")
+ }
+
+ if _, ok := funcOptions[key]; ok {
+ panic(fmt.Sprintf("重复注册方法选项:%v", key))
+ }
+
+ isCache := false
+ if len(cache) > 0 {
+ isCache = cache[0]
+ }
+
+ funcOptions[key] = &FuncOption{
+ Id: GenIdHash(key, FuncId),
+ Key: key,
+ Label: label,
+ Fun: fun,
+ Cache: isCache,
+ Opts: nil,
+ }
+}
+
+// SaveFunc 更新方法字典选项
+func SaveFunc(key, label string, fun FuncDict, cache ...bool) {
+ fLock.Lock()
+ defer fLock.Unlock()
+ delete(funcOptions, key)
+ RegisterFunc(key, label, fun, cache...)
+}
+
+// ClearFuncCache 清理指定方法缓存选项
+func ClearFuncCache(key string) (err error) {
+ fun, ok := funcOptions[key]
+ if !ok {
+ err = NotExistKeyError
+ return
+ }
+
+ fun.Lock()
+ defer fun.Unlock()
+
+ if fun.Opts != nil {
+ fun.Opts = nil
+ }
+ return
+}
+
+// GetFuncOptions 获取指定方法字典的数据选项
+func GetFuncOptions(ctx context.Context, key string) (res []*model.Option, err error) {
+ fun, ok := funcOptions[key]
+ if !ok {
+ err = NotExistKeyError
+ return
+ }
+ return LoadFuncOptions(ctx, fun)
+}
+
+// LoadFuncOptions 加载指定方法字典的数据选项
+func LoadFuncOptions(ctx context.Context, fun *FuncOption) (res []*model.Option, err error) {
+ if fun.Cache && fun.Opts != nil {
+ res = fun.Opts
+ return
+ }
+
+ fun.Lock()
+ defer fun.Unlock()
+
+ if fun.Cache && fun.Opts != nil {
+ res = fun.Opts
+ return
+ }
+
+ res, err = fun.Fun(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ for k := range res {
+ res[k].Type = fun.Key
+ }
+
+ if fun.Cache {
+ fun.Opts = res
+ }
+ return
+}
diff --git a/server/internal/library/ems/ems.go b/server/internal/library/ems/ems.go
new file mode 100644
index 0000000..e26fd45
--- /dev/null
+++ b/server/internal/library/ems/ems.go
@@ -0,0 +1,50 @@
+// Package ems
+// @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 ems
+
+import (
+ "github.com/gogf/gf/v2/errors/gerror"
+ "hotgo/internal/model"
+ "hotgo/utility/validate"
+ "net/smtp"
+ "strings"
+)
+
+// Send 发送邮件入口
+func Send(config *model.EmailConfig, to string, subject string, body string) error {
+ return sendToMail(config, to, subject, body, "html")
+}
+
+func sendToMail(config *model.EmailConfig, to, subject, body, mailType string) error {
+ if config == nil {
+ return gerror.New("邮件配置不能为空")
+ }
+
+ var (
+ contentType string
+ auth = smtp.PlainAuth("", config.User, config.Password, config.Host)
+ sendTo = strings.Split(to, ";")
+ )
+
+ if len(sendTo) == 0 {
+ return gerror.New("收件人不能为空")
+ }
+
+ for _, em := range sendTo {
+ if !validate.IsEmail(em) {
+ return gerror.Newf("邮件格式不正确,请检查:%v", em)
+ }
+ }
+
+ if mailType == "html" {
+ contentType = "Content-Type: text/" + mailType + "; charset=UTF-8"
+ } else {
+ contentType = "Content-Type: text/plain" + "; charset=UTF-8"
+ }
+
+ msg := []byte("To: " + to + "\r\nFrom: " + config.SendName + "<" + config.User + ">" + "\r\nSubject: " + subject + "\r\n" + contentType + "\r\n\r\n" + body)
+ return smtp.SendMail(config.Addr, auth, config.User, sendTo, msg)
+}
diff --git a/server/internal/library/hggen/hggen.go b/server/internal/library/hggen/hggen.go
new file mode 100644
index 0000000..cb23db3
--- /dev/null
+++ b/server/internal/library/hggen/hggen.go
@@ -0,0 +1,290 @@
+// Package hggen
+// @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 hggen
+
+import (
+ _ "hotgo/internal/library/hggen/internal/cmd/gendao"
+ "hotgo/internal/library/hggen/internal/utility/utils"
+ _ "unsafe"
+
+ "github.com/gogf/gf/v2/os/gfile"
+
+ "context"
+ "hotgo/internal/consts"
+ "hotgo/internal/library/addons"
+ "hotgo/internal/library/hggen/internal/cmd"
+ "hotgo/internal/library/hggen/internal/cmd/gendao"
+ "hotgo/internal/library/hggen/internal/cmd/genservice"
+ "hotgo/internal/library/hggen/views"
+ "hotgo/internal/model"
+ "hotgo/internal/model/input/form"
+ "hotgo/internal/model/input/sysin"
+ "hotgo/internal/service"
+ "sort"
+
+ "github.com/gogf/gf/v2/errors/gerror"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/util/gconv"
+)
+
+//go:linkname doGenDaoForArray hotgo/internal/library/hggen/internal/cmd/gendao.doGenDaoForArray
+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
+ 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.RemoveAll(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
+}
+
+// Service 生成业务接口
+func Service(ctx context.Context) (err error) {
+ return ServiceWithCfg(ctx, GetServiceConfig())
+}
+
+// ServiceWithCfg 生成业务接口
+func ServiceWithCfg(ctx context.Context, cfg ...genservice.CGenServiceInput) (err error) {
+ c := GetServiceConfig()
+ if len(cfg) > 0 {
+ c = cfg[0]
+ }
+ _, err = cmd.Gen.Service(ctx, c)
+ return
+}
+
+// TableColumns 获取指定表生成字段列表
+func TableColumns(ctx context.Context, in *sysin.GenCodesColumnListInp) (fields []*sysin.GenCodesColumnListModel, err error) {
+ return views.DoTableColumns(ctx, in, GetDaoConfig(in.Name))
+}
+
+func TableSelects(ctx context.Context, in *sysin.GenCodesSelectsInp) (res *sysin.GenCodesSelectsModel, err error) {
+ res = new(sysin.GenCodesSelectsModel)
+ res.GenType, err = GenTypeSelect(ctx)
+ if err != nil {
+ return
+ }
+
+ res.Db = DbSelect(ctx)
+
+ for k, v := range consts.GenCodesStatusNameMap {
+ res.Status = append(res.Status, &form.Select{
+ Value: k,
+ Name: v,
+ Label: v,
+ })
+ }
+ sort.Sort(res.Status)
+
+ for k, v := range consts.GenCodesJoinNameMap {
+ res.LinkMode = append(res.LinkMode, &form.Select{
+ Value: k,
+ Name: v,
+ Label: v,
+ })
+ }
+ sort.Sort(res.LinkMode)
+
+ for k, v := range consts.GenCodesBuildMethNameMap {
+ res.BuildMeth = append(res.BuildMeth, &form.Select{
+ Value: k,
+ Name: v,
+ Label: v,
+ })
+ }
+ sort.Sort(res.BuildMeth)
+
+ for _, v := range views.FormModes {
+ res.FormMode = append(res.FormMode, &form.Select{
+ Value: v,
+ Name: views.FormModeMap[v],
+ Label: views.FormModeMap[v],
+ })
+ }
+ sort.Sort(res.FormMode)
+
+ for k, v := range views.FormRoleMap {
+ res.FormRole = append(res.FormRole, &form.Select{
+ Value: k,
+ Name: v,
+ Label: v,
+ })
+ }
+ sort.Sort(res.FormRole)
+
+ dictMode, err := service.SysDictType().TreeSelect(ctx, &sysin.DictTreeSelectInp{})
+ if err != nil {
+ return
+ }
+ res.DictMode = dictMode
+
+ for _, v := range views.WhereModes {
+ res.WhereMode = append(res.WhereMode, &form.Select{
+ Value: v,
+ Name: v,
+ 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
+}
+
+// GenTypeSelect 获取生成类型选项
+func GenTypeSelect(ctx context.Context) (res sysin.GenTypeSelects, err error) {
+ for k, v := range consts.GenCodesTypeNameMap {
+ row := &sysin.GenTypeSelect{
+ Value: k,
+ Name: v,
+ Label: v,
+ Templates: make(sysin.GenTemplateSelects, 0),
+ }
+
+ km := k
+ if km == consts.GenCodesTypeTree {
+ km = consts.GenCodesTypeCurd
+ }
+ confName, ok := consts.GenCodesTypeConfMap[km]
+ if ok {
+ var temps []*model.GenerateAppCrudTemplate
+ err = g.Cfg().MustGet(ctx, "hggen.application."+confName+".templates").Scan(&temps)
+ if err != nil {
+ return
+ }
+ if len(temps) > 0 {
+ for index, temp := range temps {
+ row.Templates = append(row.Templates, &sysin.GenTemplateSelect{
+ Value: index,
+ Label: temp.Group,
+ Name: temp.Group,
+ IsAddon: temp.IsAddon,
+ })
+ }
+ sort.Sort(row.Templates)
+ }
+ }
+
+ res = append(res, row)
+ }
+ sort.Sort(res)
+ return
+}
+
+// DbSelect db选项
+func DbSelect(ctx context.Context) (res form.Selects) {
+ dbs := g.Cfg().MustGet(ctx, "hggen.selectDbs")
+ if len(dbs.Strings()) == 0 {
+ res = make(form.Selects, 0)
+ return res
+ }
+
+ for _, v := range dbs.Strings() {
+ res = append(res, &form.Select{
+ Value: v,
+ Label: v,
+ Name: v,
+ })
+ }
+ return res
+}
+
+// Preview 生成预览
+func Preview(ctx context.Context, in *sysin.GenCodesPreviewInp) (res *sysin.GenCodesPreviewModel, err error) {
+ genConfig, err := service.SysConfig().GetLoadGenerate(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ switch in.GenType {
+ case consts.GenCodesTypeCurd, consts.GenCodesTypeTree:
+ return views.Curd.DoPreview(ctx, &views.CurdPreviewInput{
+ In: in,
+ DaoConfig: GetDaoConfig(in.DbName),
+ Config: genConfig,
+ })
+ case consts.GenCodesTypeQueue:
+ err = gerror.Newf("生成类型开发中!")
+ return
+ default:
+ err = gerror.Newf("生成类型暂不支持!")
+ return
+ }
+}
+
+// Build 提交生成
+func Build(ctx context.Context, in *sysin.GenCodesBuildInp) (err error) {
+ genConfig, err := service.SysConfig().GetLoadGenerate(ctx)
+ if err != nil {
+ return err
+ }
+
+ switch in.GenType {
+ case consts.GenCodesTypeCurd, consts.GenCodesTypeTree:
+ pin := &sysin.GenCodesPreviewInp{SysGenCodes: in.SysGenCodes}
+ return views.Curd.DoBuild(ctx, &views.CurdBuildInput{
+ PreviewIn: &views.CurdPreviewInput{
+ In: pin,
+ DaoConfig: GetDaoConfig(in.DbName),
+ Config: genConfig,
+ },
+ BeforeEvent: views.CurdBuildEvent{"runDao": Dao},
+ AfterEvent: views.CurdBuildEvent{"runService": func(ctx context.Context) (err error) {
+ cfg := GetServiceConfig()
+
+ // 插件模块,切换到插件下运行gen service
+ if genConfig.Application.Crud.Templates[pin.GenTemplate].IsAddon {
+ // 依然使用配置中的参数,只是将生成路径指向插件模块路径
+ cfg.SrcFolder = "addons/" + pin.AddonName + "/logic"
+ cfg.DstFolder = "addons/" + pin.AddonName + "/service"
+ }
+ err = ServiceWithCfg(ctx, cfg)
+ return
+ }},
+ })
+ case consts.GenCodesTypeQueue:
+ err = gerror.Newf("生成类型开发中!")
+ return
+ default:
+ err = gerror.Newf("生成类型暂不支持!")
+ return
+ }
+}
diff --git a/server/internal/library/hggen/init.go b/server/internal/library/hggen/init.go
new file mode 100644
index 0000000..7ff306c
--- /dev/null
+++ b/server/internal/library/hggen/init.go
@@ -0,0 +1,130 @@
+// Package hggen
+// @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 hggen
+
+import (
+ "context"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/util/gconv"
+ "gopkg.in/yaml.v3"
+ "hotgo/internal/library/hggen/internal/cmd/gendao"
+ "hotgo/internal/library/hggen/internal/cmd/genservice"
+)
+
+const (
+ cliFolderName = `hack/config.yaml`
+ RequiredErrorTag = `the cli configuration file must be configured %s`
+)
+
+var (
+ config g.Map
+ daoConfig []interface{}
+ serviceConfig g.Map
+
+ // 生成service默认参数,请不要直接修改以下配置,如需调整请到/hack/config.yaml,可参考:https://goframe.org/pages/viewpage.action?pageId=49770772
+ defaultGenServiceInput = genservice.CGenServiceInput{
+ SrcFolder: "internal/logic",
+ DstFolder: "internal/service",
+ DstFileNameCase: "Snake",
+ StPattern: `s([A-Z]\w+)`,
+ Clear: false,
+ }
+
+ // 生成dao默认参数,请不要直接修改以下配置,如需调整请到/hack/config.yaml,可参考:https://goframe.org/pages/viewpage.action?pageId=3673173
+ defaultGenDaoInput = gendao.CGenDaoInput{
+ Path: "internal",
+ Group: "default",
+ JsonCase: "CamelLower",
+ DaoPath: "dao",
+ DoPath: "model/do",
+ EntityPath: "model/entity",
+ StdTime: false,
+ WithTime: false,
+ GJsonSupport: false,
+ OverwriteDao: false,
+ DescriptionTag: true,
+ NoJsonTag: false,
+ NoModelComment: false,
+ Clear: false,
+ }
+)
+
+func GetServiceConfig() genservice.CGenServiceInput {
+ inp := defaultGenServiceInput
+ _ = gconv.Scan(serviceConfig, &inp)
+ return inp
+}
+
+func GetDaoConfig(group string) gendao.CGenDaoInput {
+ find := func(group string) g.Map {
+ for _, v := range daoConfig {
+ if v.(g.Map)["group"].(string) == group {
+ return v.(g.Map)
+ }
+ }
+ return nil
+ }
+
+ v := find(group)
+ inp := defaultGenDaoInput
+ if v != nil {
+ if err := gconv.Scan(v, &inp); err != nil {
+ panic(err)
+ }
+ }
+ return inp
+}
+
+func InIt(ctx context.Context) {
+ path, err := gfile.Search(cliFolderName)
+ if err != nil {
+ g.Log().Fatalf(ctx, "get cli configuration file:%v, err:%+v", cliFolderName, err)
+ }
+ if path == "" {
+ g.Log().Fatalf(ctx, "get cli configuration file:%v fail", cliFolderName)
+ }
+ if config == nil {
+ config = make(g.Map)
+ }
+ err = yaml.Unmarshal(gfile.GetBytes(path), &config)
+ if err != nil {
+ g.Log().Fatalf(ctx, "load cli configuration file:%v, yaml err:%+v", cliFolderName, err)
+ }
+ loadConfig(ctx)
+}
+
+func loadConfig(ctx context.Context) {
+ if _, ok := config["gfcli"]; !ok {
+ g.Log().Fatalf(ctx, RequiredErrorTag, "gfcli")
+ }
+
+ if _, ok := config["gfcli"].(g.Map)["gen"]; !ok {
+ g.Log().Fatalf(ctx, RequiredErrorTag, "gfcli.gen")
+ }
+
+ dao, ok := config["gfcli"].(g.Map)["gen"].(map[string]interface{})["dao"]
+ if !ok {
+ g.Log().Fatalf(ctx, RequiredErrorTag, "gfcli.gen.dao")
+ }
+ daoConf, ok := dao.([]interface{})
+ if !ok {
+ g.Log().Fatalf(ctx, RequiredErrorTag, "gfcli.gen.dao format error")
+ }
+ daoConfig = daoConf
+ for _, v := range daoConfig {
+ if _, ok := v.(g.Map)["group"].(string); !ok {
+ g.Log().Fatalf(ctx, "group must be configured in %s: `gfcli.gen.dao` and must be the same as the database group", cliFolderName)
+ }
+ }
+
+ if serviceConf, ok := config["gfcli"].(g.Map)["gen"].(map[string]interface{})["service"]; ok {
+ if serviceConfig == nil {
+ serviceConfig = make(g.Map)
+ }
+ serviceConfig = serviceConf.(g.Map)
+ }
+}
diff --git a/server/internal/library/hggen/internal/cmd/cmd.go b/server/internal/library/hggen/internal/cmd/cmd.go
new file mode 100644
index 0000000..d3668ef
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/cmd.go
@@ -0,0 +1,79 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+// Package cmd provides the management of CLI commands for `gf` tool.
+package cmd
+
+import (
+ "context"
+ "strings"
+
+ "github.com/gogf/gf/v2"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gcmd"
+ "github.com/gogf/gf/v2/util/gtag"
+
+ "hotgo/internal/library/hggen/internal/service"
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+)
+
+// GF is the management object for `gf` command line tool.
+var GF = cGF{}
+
+type cGF struct {
+ g.Meta `name:"gf" ad:"{cGFAd}"`
+}
+
+const (
+ cGFAd = `
+ADDITIONAL
+ Use "gf COMMAND -h" for details about a command.
+`
+)
+
+func init() {
+ gtag.Sets(g.MapStrStr{
+ `cGFAd`: cGFAd,
+ })
+}
+
+type cGFInput struct {
+ g.Meta `name:"gf"`
+ Yes bool `short:"y" name:"yes" brief:"all yes for all command without prompt ask" orphan:"true"`
+ Version bool `short:"v" name:"version" brief:"show version information of current binary" orphan:"true"`
+ Debug bool `short:"d" name:"debug" brief:"show internal detailed debugging information" orphan:"true"`
+}
+
+type cGFOutput struct{}
+
+func (c cGF) Index(ctx context.Context, in cGFInput) (out *cGFOutput, err error) {
+ // Version.
+ if in.Version {
+ _, err = Version.Index(ctx, cVersionInput{})
+ return
+ }
+
+ answer := "n"
+ // No argument or option, do installation checks.
+ if data, isInstalled := service.Install.IsInstalled(); !isInstalled {
+ mlog.Print("hi, it seems it's the first time you installing gf cli.")
+ answer = gcmd.Scanf("do you want to install gf(%s) binary to your system? [y/n]: ", gf.VERSION)
+ } else if !data.IsSelf {
+ mlog.Print("hi, you have installed gf cli.")
+ answer = gcmd.Scanf("do you want to install gf(%s) binary to your system? [y/n]: ", gf.VERSION)
+ }
+ if strings.EqualFold(answer, "y") {
+ if err = service.Install.Run(ctx); err != nil {
+ return
+ }
+ gcmd.Scan("press `Enter` to exit...")
+ return
+ }
+
+ // Print help content.
+ gcmd.CommandFromCtx(ctx).Print()
+ return
+}
diff --git a/server/internal/library/hggen/internal/cmd/cmd_build.go b/server/internal/library/hggen/internal/cmd/cmd_build.go
new file mode 100644
index 0000000..7a7e245
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/cmd_build.go
@@ -0,0 +1,399 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package cmd
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "os"
+ "regexp"
+ "runtime"
+ "strings"
+
+ "github.com/gogf/gf/v2/encoding/gbase64"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gbuild"
+ "github.com/gogf/gf/v2/os/gcmd"
+ "github.com/gogf/gf/v2/os/genv"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gproc"
+ "github.com/gogf/gf/v2/os/gtime"
+ "github.com/gogf/gf/v2/text/gregex"
+ "github.com/gogf/gf/v2/text/gstr"
+ "github.com/gogf/gf/v2/util/gtag"
+
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+)
+
+var Build = cBuild{
+ nodeNameInConfigFile: "gfcli.build",
+ packedGoFileName: "internal/packed/build_pack_data.go",
+}
+
+type cBuild struct {
+ g.Meta `name:"build" brief:"{cBuildBrief}" dc:"{cBuildDc}" eg:"{cBuildEg}" ad:"{cBuildAd}"`
+ nodeNameInConfigFile string // nodeNameInConfigFile is the node name for compiler configurations in configuration file.
+ packedGoFileName string // packedGoFileName specifies the file name for packing common folders into one single go file.
+}
+
+const (
+ cBuildDefaultFile = "main.go"
+ cBuildBrief = `cross-building go project for lots of platforms`
+ cBuildEg = `
+gf build main.go
+gf build main.go --ps public,template
+gf build main.go --cgo
+gf build main.go -m none
+gf build main.go -n my-app -a all -s all
+gf build main.go -n my-app -a amd64,386 -s linux -p .
+gf build main.go -n my-app -v 1.0 -a amd64,386 -s linux,windows,darwin -p ./docker/bin
+`
+ cBuildDc = `
+The "build" command is most commonly used command, which is designed as a powerful wrapper for
+"go build" command for convenience cross-compiling usage.
+It provides much more features for building binary:
+1. Cross-Compiling for many platforms and architectures.
+2. Configuration file support for compiling.
+3. Build-In Variables.
+`
+ cBuildAd = `
+PLATFORMS
+ aix ppc64
+ android 386,amd64,arm,arm64
+ darwin amd64,arm64
+ dragonfly amd64
+ freebsd 386,amd64,arm
+ illumos amd64
+ ios arm64
+ js wasm
+ linux 386,amd64,arm,arm64,loong64,mips,mipsle,mips64,mips64le,ppc64,ppc64le,riscv64,s390x
+ netbsd 386,amd64,arm
+ openbsd 386,amd64,arm,arm64
+ plan9 386,amd64,arm
+ solaris amd64
+ wasip1 wasm
+ windows 386,amd64,arm,arm64
+`
+ // https://golang.google.cn/doc/install/source
+ cBuildPlatforms = `
+aix ppc64
+android 386
+android amd64
+android arm
+android arm64
+darwin amd64
+darwin arm64
+dragonfly amd64
+freebsd 386
+freebsd amd64
+freebsd arm
+illumos amd64
+ios arm64
+js wasm
+linux 386
+linux amd64
+linux arm
+linux arm64
+linux loong64
+linux mips
+linux mipsle
+linux mips64
+linux mips64le
+linux ppc64
+linux ppc64le
+linux riscv64
+linux s390x
+netbsd 386
+netbsd amd64
+netbsd arm
+openbsd 386
+openbsd amd64
+openbsd arm
+openbsd arm64
+plan9 386
+plan9 amd64
+plan9 arm
+solaris amd64
+wasip1 wasm
+windows 386
+windows amd64
+windows arm
+windows arm64
+`
+)
+
+func init() {
+ gtag.Sets(g.MapStrStr{
+ `cBuildBrief`: cBuildBrief,
+ `cBuildDc`: cBuildDc,
+ `cBuildEg`: cBuildEg,
+ `cBuildAd`: cBuildAd,
+ })
+}
+
+type cBuildInput struct {
+ g.Meta `name:"build" config:"gfcli.build"`
+ File string `name:"FILE" arg:"true" brief:"building file path"`
+ Name string `short:"n" name:"name" brief:"output binary name"`
+ Version string `short:"v" name:"version" brief:"output binary version"`
+ Arch string `short:"a" name:"arch" brief:"output binary architecture, multiple arch separated with ','"`
+ System string `short:"s" name:"system" brief:"output binary system, multiple os separated with ','"`
+ Output string `short:"o" name:"output" brief:"output binary path, used when building single binary file"`
+ Path string `short:"p" name:"path" brief:"output binary directory path, default is '.'" d:"."`
+ Extra string `short:"e" name:"extra" brief:"extra custom \"go build\" options"`
+ Mod string `short:"m" name:"mod" brief:"like \"-mod\" option of \"go build\", use \"-m none\" to disable go module"`
+ Cgo bool `short:"c" name:"cgo" brief:"enable or disable cgo feature, it's disabled in default" orphan:"true"`
+ VarMap g.Map `short:"r" name:"varMap" brief:"custom built embedded variable into binary"`
+ PackSrc string `short:"ps" name:"packSrc" brief:"pack one or more folders into one go file before building"`
+ PackDst string `short:"pd" name:"packDst" brief:"temporary go file path for pack, this go file will be automatically removed after built" d:"internal/packed/build_pack_data.go"`
+ ExitWhenError bool `short:"ew" name:"exitWhenError" brief:"exit building when any error occurs, specially for multiple arch and system buildings. default is false" orphan:"true"`
+ DumpENV bool `short:"de" name:"dumpEnv" brief:"dump current go build environment before building binary" orphan:"true"`
+}
+
+type cBuildOutput struct{}
+
+func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, err error) {
+ mlog.SetHeaderPrint(true)
+
+ mlog.Debugf(`build command input: %+v`, in)
+ // Necessary check.
+ if gproc.SearchBinary("go") == "" {
+ mlog.Fatalf(`command "go" not found in your environment, please install golang first to proceed this command`)
+ }
+
+ var (
+ parser = gcmd.ParserFromCtx(ctx)
+ file = in.File
+ )
+ if file == "" {
+ file = parser.GetArg(2).String()
+ // Check and use the main.go file.
+ if gfile.Exists(cBuildDefaultFile) {
+ file = cBuildDefaultFile
+ } else {
+ mlog.Fatal("build file path is empty or main.go not found in current working directory")
+ }
+ }
+ if in.Name == "" {
+ in.Name = gfile.Name(file)
+ }
+ if len(in.Name) < 1 || in.Name == "*" {
+ mlog.Fatal("name cannot be empty")
+ }
+ if in.Mod != "" && in.Mod != "none" {
+ mlog.Debugf(`mod is %s`, in.Mod)
+ if in.Extra == "" {
+ in.Extra = fmt.Sprintf(`-mod=%s`, in.Mod)
+ } else {
+ in.Extra = fmt.Sprintf(`-mod=%s %s`, in.Mod, in.Extra)
+ }
+ }
+ if in.Extra != "" {
+ in.Extra += " "
+ }
+ var (
+ customSystems = gstr.SplitAndTrim(in.System, ",")
+ customArches = gstr.SplitAndTrim(in.Arch, ",")
+ )
+ if len(in.Version) > 0 {
+ in.Path += "/" + in.Version
+ }
+ // System and arch checks.
+ var (
+ spaceRegex = regexp.MustCompile(`\s+`)
+ platformMap = make(map[string]map[string]bool)
+ )
+ for _, line := range strings.Split(strings.TrimSpace(cBuildPlatforms), "\n") {
+ line = gstr.Trim(line)
+ line = spaceRegex.ReplaceAllString(line, " ")
+ var (
+ array = strings.Split(line, " ")
+ system = strings.TrimSpace(array[0])
+ arch = strings.TrimSpace(array[1])
+ )
+ if platformMap[system] == nil {
+ platformMap[system] = make(map[string]bool)
+ }
+ platformMap[system][arch] = true
+ }
+ // Auto packing.
+ if in.PackSrc != "" {
+ if in.PackDst == "" {
+ mlog.Fatal(`parameter "packDst" should not be empty when "packSrc" is used`)
+ }
+ if gfile.Exists(in.PackDst) && !gfile.IsFile(in.PackDst) {
+ mlog.Fatalf(`parameter "packDst" path "%s" should be type of file not directory`, in.PackDst)
+ }
+ if !gfile.Exists(in.PackDst) {
+ // Remove the go file that is automatically packed resource.
+ defer func() {
+ _ = gfile.RemoveFile(in.PackDst)
+ mlog.Printf(`remove the automatically generated resource go file: %s`, in.PackDst)
+ }()
+ }
+ // remove black space in separator.
+ in.PackSrc, _ = gregex.ReplaceString(`,\s+`, `,`, in.PackSrc)
+ packCmd := fmt.Sprintf(`gf pack %s %s --keepPath=true`, in.PackSrc, in.PackDst)
+ mlog.Print(packCmd)
+ gproc.MustShellRun(ctx, packCmd)
+ }
+
+ // Injected information by building flags.
+ ldFlags := fmt.Sprintf(
+ `-X 'github.com/gogf/gf/v2/os/gbuild.builtInVarStr=%v'`,
+ c.getBuildInVarStr(ctx, in),
+ )
+
+ // start building
+ mlog.Print("start building...")
+ if in.Cgo {
+ genv.MustSet("CGO_ENABLED", "1")
+ } else {
+ genv.MustSet("CGO_ENABLED", "0")
+ }
+ // print used go env
+ if in.DumpENV {
+ _, _ = Env.Index(ctx, cEnvInput{})
+ }
+ for system, item := range platformMap {
+ if len(customSystems) > 0 && customSystems[0] != "all" && !gstr.InArray(customSystems, system) {
+ continue
+ }
+ for arch := range item {
+ if len(customArches) > 0 && customArches[0] != "all" && !gstr.InArray(customArches, arch) {
+ continue
+ }
+ if len(customSystems) == 0 && len(customArches) == 0 {
+ // Single binary building, output the binary to current working folder.
+ // For example:
+ // `gf build`
+ // `gf build -o main.exe`
+ c.doBinaryBuild(
+ ctx, file,
+ in.Output, in.Path,
+ runtime.GOOS, runtime.GOARCH, in.Name, ldFlags, in.Extra,
+ in.ExitWhenError,
+ true,
+ )
+ } else {
+ c.doBinaryBuild(
+ ctx, file,
+ in.Output, in.Path,
+ system, arch, in.Name, ldFlags, in.Extra,
+ in.ExitWhenError,
+ false,
+ )
+ }
+ // single binary building.
+ if len(customSystems) == 0 && len(customArches) == 0 {
+ goto buildDone
+ }
+ }
+ }
+
+buildDone:
+ mlog.Print("done!")
+ return
+}
+
+func (c cBuild) doBinaryBuild(
+ ctx context.Context,
+ filePath string,
+ outputPath, dirPath string,
+ system, arch, name, ldFlags, extra string,
+ exitWhenError bool,
+ singleBuild bool,
+) {
+ var (
+ cmd string
+ ext string
+ )
+ // Cross-building, output the compiled binary to specified path.
+ if system == "windows" {
+ ext = ".exe"
+ }
+ genv.MustSet("GOOS", system)
+ genv.MustSet("GOARCH", arch)
+
+ if outputPath != "" {
+ outputPath = "-o " + outputPath
+ } else {
+ if dirPath == "" {
+ dirPath = "."
+ } else {
+ dirPath = gstr.TrimRight(dirPath, "/")
+ }
+ if singleBuild {
+ outputPath = fmt.Sprintf(
+ "-o %s/%s%s",
+ dirPath, name, ext,
+ )
+ } else {
+ outputPath = fmt.Sprintf(
+ "-o %s/%s/%s%s",
+ dirPath, system+"_"+arch, name, ext,
+ )
+ }
+ }
+ cmd = fmt.Sprintf(
+ `go build %s -ldflags "%s" %s%s`,
+ outputPath, ldFlags, extra, filePath,
+ )
+ mlog.Debug(fmt.Sprintf("build for GOOS=%s GOARCH=%s", system, arch))
+ mlog.Debug(cmd)
+ // It's not necessary printing the complete command string, filtering ldFlags.
+ cmdShow, _ := gregex.ReplaceString(`\s+(-ldflags ".+?")\s+`, " ", cmd)
+ mlog.Print(cmdShow)
+ if result, err := gproc.ShellExec(ctx, cmd); err != nil {
+ mlog.Printf(
+ "failed to build, os:%s, arch:%s, error:\n%s\n\n%s\n",
+ system, arch, gstr.Trim(result),
+ `you may use command option "--debug" to enable debug info and check the details`,
+ )
+ if exitWhenError {
+ os.Exit(1)
+ }
+ } else {
+ mlog.Debug(gstr.Trim(result))
+ }
+}
+
+// getBuildInVarStr retrieves and returns the custom build-in variables in configuration
+// file as json.
+func (c cBuild) getBuildInVarStr(ctx context.Context, in cBuildInput) string {
+ buildInVarMap := in.VarMap
+ if buildInVarMap == nil {
+ buildInVarMap = make(g.Map)
+ }
+ buildInVarMap[gbuild.BuiltGit] = c.getGitCommit(ctx)
+ buildInVarMap[gbuild.BuiltTime] = gtime.Now().String()
+ buildInVarMap[gbuild.BuiltVersion] = in.Version
+ b, err := json.Marshal(buildInVarMap)
+ if err != nil {
+ mlog.Fatal(err)
+ }
+ return gbase64.EncodeToString(b)
+}
+
+// getGitCommit retrieves and returns the latest git commit hash string if present.
+func (c cBuild) getGitCommit(ctx context.Context) string {
+ if gproc.SearchBinary("git") == "" {
+ return ""
+ }
+ var (
+ cmd = `git log -1 --format="%cd %H" --date=format:"%Y-%m-%d %H:%M:%S"`
+ s, _ = gproc.ShellExec(ctx, cmd)
+ )
+ mlog.Debug(cmd)
+ if s != "" {
+ if !gstr.Contains(s, "fatal") {
+ return gstr.Trim(s)
+ }
+ }
+ return ""
+}
diff --git a/server/internal/library/hggen/internal/cmd/cmd_doc.go b/server/internal/library/hggen/internal/cmd/cmd_doc.go
new file mode 100644
index 0000000..163732d
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/cmd_doc.go
@@ -0,0 +1,184 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package cmd
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "path"
+ "path/filepath"
+ "time"
+
+ "github.com/gogf/gf/v2/encoding/gcompress"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gfile"
+
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+)
+
+const (
+ GitName = "gf-site"
+ BranchName = "gh-pages"
+
+ SiteFileName = GitName + "-" + BranchName
+ // DocURL is the download address of the document
+ DocURL = "https://github.com/gogf/" + GitName + "/archive/refs/heads/" + BranchName + ".zip"
+)
+
+var (
+ Doc = cDoc{}
+)
+
+type cDoc struct {
+ g.Meta `name:"doc" brief:"download https://pages.goframe.org/ to run locally"`
+}
+
+type cDocInput struct {
+ g.Meta `name:"doc" config:"gfcli.doc"`
+ Path string `short:"p" name:"path" brief:"download docs directory path, default is \"%temp%/goframe\""`
+ Port int `short:"o" name:"port" brief:"http server port, default is 8080" d:"8080"`
+ Update bool `short:"u" name:"update" brief:"clean docs directory and update docs"`
+ Clean bool `short:"c" name:"clean" brief:"clean docs directory"`
+ Proxy string `short:"x" name:"proxy" brief:"proxy for download, such as https://hub.gitmirror.com/;https://ghproxy.com/;https://ghproxy.net/;https://ghps.cc/"`
+}
+
+type cDocOutput struct{}
+
+func (c cDoc) Index(ctx context.Context, in cDocInput) (out *cDocOutput, err error) {
+ docs := NewDocSetting(ctx, in)
+ mlog.Print("Directory where the document is downloaded:", docs.TempDir)
+ if in.Clean {
+ mlog.Print("Cleaning document directory")
+ err = docs.Clean()
+ if err != nil {
+ mlog.Print("Failed to clean document directory:", err)
+ return
+ }
+ return
+ }
+ if in.Update {
+ mlog.Print("Cleaning old document directory")
+ err = docs.Clean()
+ if err != nil {
+ mlog.Print("Failed to clean old document directory:", err)
+ return
+ }
+ }
+ err = docs.DownloadDoc()
+ if err != nil {
+ mlog.Print("Failed to download document:", err)
+ return
+ }
+
+ http.Handle("/", http.FileServer(http.Dir(docs.DocDir)))
+ mlog.Printf("Access address http://127.0.0.1:%d in %s", in.Port, docs.DocDir)
+ err = http.ListenAndServe(fmt.Sprintf(":%d", in.Port), nil)
+ if err != nil {
+ return nil, err
+ }
+ return
+}
+
+// DocSetting doc setting
+type DocSetting struct {
+ TempDir string
+ DocURL string
+ DocDir string
+ DocZipFile string
+}
+
+// NewDocSetting new DocSetting
+func NewDocSetting(ctx context.Context, in cDocInput) *DocSetting {
+ fileName := SiteFileName + ".zip"
+ tempDir := in.Path
+ if tempDir == "" {
+ tempDir = gfile.Temp("goframe/docs")
+ } else {
+ tempDir = gfile.Abs(path.Join(tempDir, "docs"))
+ }
+
+ return &DocSetting{
+ TempDir: filepath.FromSlash(tempDir),
+ DocDir: filepath.FromSlash(path.Join(tempDir, SiteFileName)),
+ DocURL: in.Proxy + DocURL,
+ DocZipFile: filepath.FromSlash(path.Join(tempDir, fileName)),
+ }
+
+}
+
+// Clean cleans the temporary directory
+func (d *DocSetting) Clean() error {
+ if _, err := os.Stat(d.TempDir); err == nil {
+ err = gfile.RemoveAll(d.TempDir)
+ if err != nil {
+ mlog.Print("Failed to delete temporary directory:", err)
+ return err
+ }
+ }
+ return nil
+}
+
+// DownloadDoc download the document
+func (d *DocSetting) DownloadDoc() error {
+ if _, err := os.Stat(d.TempDir); err != nil {
+ err = gfile.Mkdir(d.TempDir)
+ if err != nil {
+ mlog.Print("Failed to create temporary directory:", err)
+ return nil
+ }
+ }
+ // Check if the file exists
+ if _, err := os.Stat(d.DocDir); err == nil {
+ mlog.Print("Document already exists, no need to download and unzip")
+ return nil
+ }
+
+ if _, err := os.Stat(d.DocZipFile); err == nil {
+ mlog.Print("File already exists, no need to download")
+ } else {
+ mlog.Printf("File does not exist, start downloading: %s", d.DocURL)
+ startTime := time.Now()
+ // Download the file
+ resp, err := http.Get(d.DocURL)
+ if err != nil {
+ mlog.Print("Failed to download file:", err)
+ return err
+ }
+ defer resp.Body.Close()
+
+ // Create the file
+ out, err := os.Create(d.DocZipFile)
+ if err != nil {
+ mlog.Print("Failed to create file:", err)
+ return err
+ }
+ defer out.Close()
+
+ // Write the response body to the file
+ _, err = io.Copy(out, resp.Body)
+ if err != nil {
+ mlog.Print("Failed to write file:", err)
+ return err
+ }
+ mlog.Printf("Download successful, time-consuming: %v", time.Since(startTime))
+ }
+
+ mlog.Print("Start unzipping the file...")
+ // Unzip the file
+ err := gcompress.UnZipFile(d.DocZipFile, d.TempDir)
+ if err != nil {
+ mlog.Print("Failed to unzip the file, please run again:", err)
+ _ = gfile.RemoveFile(d.DocZipFile)
+ return err
+ }
+
+ mlog.Print("Download and unzip successful")
+ return nil
+}
diff --git a/server/internal/library/hggen/internal/cmd/cmd_docker.go b/server/internal/library/hggen/internal/cmd/cmd_docker.go
new file mode 100644
index 0000000..3b53e74
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/cmd_docker.go
@@ -0,0 +1,179 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package cmd
+
+import (
+ "context"
+ "fmt"
+ "runtime"
+
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gproc"
+ "github.com/gogf/gf/v2/text/gstr"
+ "github.com/gogf/gf/v2/util/gtag"
+
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+)
+
+var (
+ Docker = cDocker{}
+)
+
+type cDocker struct {
+ g.Meta `name:"docker" usage:"{cDockerUsage}" brief:"{cDockerBrief}" eg:"{cDockerEg}" dc:"{cDockerDc}"`
+}
+
+const (
+ cDockerUsage = `gf docker [MAIN] [OPTION]`
+ cDockerBrief = `build docker image for current GoFrame project`
+ cDockerEg = `
+gf docker
+gf docker -t hub.docker.com/john/image:tag
+gf docker -p -t hub.docker.com/john/image:tag
+gf docker main.go
+gf docker main.go -t hub.docker.com/john/image:tag
+gf docker main.go -t hub.docker.com/john/image:tag
+gf docker main.go -p -t hub.docker.com/john/image:tag
+gf docker main.go -p -tp ["hub.docker.com/john","hub.docker.com/smith"] -tn image:tag
+`
+ cDockerDc = `
+The "docker" command builds the GF project to a docker images.
+It runs "gf build" firstly to compile the project to binary file.
+It then runs "docker build" command automatically to generate the docker image.
+You should have docker installed, and there must be a Dockerfile in the root of the project.
+`
+ cDockerMainBrief = `main file path for "gf build", it's "main.go" in default. empty string for no binary build`
+ cDockerBuildBrief = `binary build options before docker image build, it's "-a amd64 -s linux" in default`
+ cDockerFileBrief = `file path of the Dockerfile. it's "manifest/docker/Dockerfile" in default`
+ cDockerShellBrief = `path of the shell file which is executed before docker build`
+ cDockerPushBrief = `auto push the docker image to docker registry if "-t" option passed`
+ cDockerTagBrief = `full tag for this docker, pattern like "xxx.xxx.xxx/image:tag"`
+ cDockerTagNameBrief = `tag name for this docker, pattern like "image:tag". this option is required with TagPrefixes`
+ cDockerTagPrefixesBrief = `tag prefixes for this docker, which are used for docker push. this option is required with TagName`
+ cDockerExtraBrief = `extra build options passed to "docker image"`
+)
+
+func init() {
+ gtag.Sets(g.MapStrStr{
+ `cDockerUsage`: cDockerUsage,
+ `cDockerBrief`: cDockerBrief,
+ `cDockerEg`: cDockerEg,
+ `cDockerDc`: cDockerDc,
+ `cDockerMainBrief`: cDockerMainBrief,
+ `cDockerFileBrief`: cDockerFileBrief,
+ `cDockerShellBrief`: cDockerShellBrief,
+ `cDockerBuildBrief`: cDockerBuildBrief,
+ `cDockerPushBrief`: cDockerPushBrief,
+ `cDockerTagBrief`: cDockerTagBrief,
+ `cDockerTagNameBrief`: cDockerTagNameBrief,
+ `cDockerTagPrefixesBrief`: cDockerTagPrefixesBrief,
+ `cDockerExtraBrief`: cDockerExtraBrief,
+ })
+}
+
+type cDockerInput struct {
+ g.Meta `name:"docker" config:"gfcli.docker"`
+ Main string `name:"MAIN" arg:"true" brief:"{cDockerMainBrief}" d:"main.go"`
+ File string `name:"file" short:"f" brief:"{cDockerFileBrief}" d:"manifest/docker/Dockerfile"`
+ Shell string `name:"shell" short:"s" brief:"{cDockerShellBrief}" d:"manifest/docker/docker.sh"`
+ Build string `name:"build" short:"b" brief:"{cDockerBuildBrief}"`
+ Tag string `name:"tag" short:"t" brief:"{cDockerTagBrief}"`
+ TagName string `name:"tagName" short:"tn" brief:"{cDockerTagNameBrief}" v:"required-with:TagPrefixes"`
+ TagPrefixes []string `name:"tagPrefixes" short:"tp" brief:"{cDockerTagPrefixesBrief}" v:"required-with:TagName"`
+ Push bool `name:"push" short:"p" brief:"{cDockerPushBrief}" orphan:"true"`
+ Extra string `name:"extra" short:"e" brief:"{cDockerExtraBrief}"`
+}
+
+type cDockerOutput struct{}
+
+func (c cDocker) Index(ctx context.Context, in cDockerInput) (out *cDockerOutput, err error) {
+ // Necessary check.
+ if gproc.SearchBinary("docker") == "" {
+ mlog.Fatalf(`command "docker" not found in your environment, please install docker first to proceed this command`)
+ }
+
+ mlog.Debugf(`docker command input: %+v`, in)
+
+ // Binary build.
+ if in.Main != "" && in.Build != "" {
+ in.Build += " --exitWhenError"
+ if in.Main != "" {
+ if err = gproc.ShellRun(ctx, fmt.Sprintf(`gf build %s %s`, in.Main, in.Build)); err != nil {
+ mlog.Debugf(`build binary failed with error: %+v`, err)
+ return
+ }
+ }
+ }
+
+ // Shell executing.
+ if in.Shell != "" && gfile.Exists(in.Shell) {
+ if err = c.exeDockerShell(ctx, in.Shell); err != nil {
+ mlog.Debugf(`build docker failed with error: %+v`, err)
+ return
+ }
+ }
+ // Docker build.
+ var (
+ dockerBuildOptions string
+ dockerTags []string
+ dockerTagBase string
+ )
+ if len(in.TagPrefixes) > 0 {
+ for _, tagPrefix := range in.TagPrefixes {
+ tagPrefix = gstr.TrimRight(tagPrefix, "/")
+ dockerTags = append(dockerTags, fmt.Sprintf(`%s/%s`, tagPrefix, in.TagName))
+ }
+ }
+ if len(dockerTags) == 0 {
+ dockerTags = []string{in.Tag}
+ }
+ for i, dockerTag := range dockerTags {
+ if i > 0 {
+ err = gproc.ShellRun(ctx, fmt.Sprintf(`docker tag %s %s`, dockerTagBase, dockerTag))
+ if err != nil {
+ return
+ }
+ continue
+ }
+ dockerTagBase = dockerTag
+ dockerBuildOptions = ""
+ if dockerTag != "" {
+ dockerBuildOptions = fmt.Sprintf(`-t %s`, dockerTag)
+ }
+ if in.Extra != "" {
+ dockerBuildOptions = fmt.Sprintf(`%s %s`, dockerBuildOptions, in.Extra)
+ }
+ err = gproc.ShellRun(ctx, fmt.Sprintf(`docker build -f %s . %s`, in.File, dockerBuildOptions))
+ if err != nil {
+ return
+ }
+ }
+
+ // Docker push.
+ if !in.Push {
+ return
+ }
+ for _, dockerTag := range dockerTags {
+ if dockerTag == "" {
+ continue
+ }
+ err = gproc.ShellRun(ctx, fmt.Sprintf(`docker push %s`, dockerTag))
+ if err != nil {
+ return
+ }
+ }
+ return
+}
+
+func (c cDocker) exeDockerShell(ctx context.Context, shellFilePath string) error {
+ if gfile.ExtName(shellFilePath) == "sh" && runtime.GOOS == "windows" {
+ mlog.Debugf(`ignore shell file "%s", as it cannot be run on windows system`, shellFilePath)
+ return nil
+ }
+ return gproc.ShellRun(ctx, gfile.GetContents(shellFilePath))
+}
diff --git a/server/internal/library/hggen/internal/cmd/cmd_env.go b/server/internal/library/hggen/internal/cmd/cmd_env.go
new file mode 100644
index 0000000..88371cb
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/cmd_env.go
@@ -0,0 +1,85 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package cmd
+
+import (
+ "bytes"
+ "context"
+
+ "github.com/olekukonko/tablewriter"
+ "github.com/olekukonko/tablewriter/renderer"
+ "github.com/olekukonko/tablewriter/tw"
+
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gproc"
+ "github.com/gogf/gf/v2/text/gregex"
+ "github.com/gogf/gf/v2/text/gstr"
+
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+)
+
+var (
+ Env = cEnv{}
+)
+
+type cEnv struct {
+ g.Meta `name:"env" brief:"show current Golang environment variables"`
+}
+
+type cEnvInput struct {
+ g.Meta `name:"env"`
+}
+
+type cEnvOutput struct{}
+
+func (c cEnv) Index(ctx context.Context, in cEnvInput) (out *cEnvOutput, err error) {
+ result, err := gproc.ShellExec(ctx, "go env")
+ if err != nil {
+ mlog.Fatal(err)
+ }
+ if result == "" {
+ mlog.Fatal(`retrieving Golang environment variables failed, did you install Golang?`)
+ }
+ var (
+ lines = gstr.Split(result, "\n")
+ buffer = bytes.NewBuffer(nil)
+ )
+ array := make([][]string, 0)
+ for _, line := range lines {
+ line = gstr.Trim(line)
+ if line == "" {
+ continue
+ }
+ if gstr.Pos(line, "set ") == 0 {
+ line = line[4:]
+ }
+ match, _ := gregex.MatchString(`(.+?)=(.*)`, line)
+ if len(match) < 3 {
+ mlog.Fatalf(`invalid Golang environment variable: "%s"`, line)
+ }
+ array = append(array, []string{gstr.Trim(match[1]), gstr.Trim(match[2])})
+ }
+ table := tablewriter.NewTable(buffer,
+ tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{
+ Settings: tw.Settings{
+ Separators: tw.Separators{BetweenRows: tw.Off, BetweenColumns: tw.On},
+ },
+ Symbols: tw.NewSymbols(tw.StyleASCII),
+ })),
+ tablewriter.WithConfig(tablewriter.Config{
+ Row: tw.CellConfig{
+ Formatting: tw.CellFormatting{AutoWrap: tw.WrapNone},
+ Alignment: tw.CellAlignment{PerColumn: []tw.Align{tw.AlignLeft, tw.AlignLeft}},
+ ColMaxWidths: tw.CellWidth{Global: 84},
+ },
+ }),
+ )
+ table.Bulk(array)
+ table.Render()
+ mlog.Print(buffer.String())
+ return
+}
diff --git a/server/internal/library/hggen/internal/cmd/cmd_fix.go b/server/internal/library/hggen/internal/cmd/cmd_fix.go
new file mode 100644
index 0000000..0a338aa
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/cmd_fix.go
@@ -0,0 +1,152 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package cmd
+
+import (
+ "context"
+
+ "github.com/gogf/gf/v2/errors/gerror"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gproc"
+ "github.com/gogf/gf/v2/text/gregex"
+ "github.com/gogf/gf/v2/text/gstr"
+
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+)
+
+var (
+ Fix = cFix{}
+)
+
+type cFix struct {
+ g.Meta `name:"fix" brief:"auto fixing codes after upgrading to new GoFrame version" usage:"gf fix" `
+}
+
+type cFixInput struct {
+ g.Meta `name:"fix"`
+ Path string `name:"path" short:"p" brief:"directory path, it uses current working directory in default"`
+ Version string `name:"version" short:"v" brief:"custom specified version to fix, leave it empty to auto detect"`
+}
+
+type cFixOutput struct{}
+
+type cFixItem struct {
+ Version string
+ Func func(version string) error
+}
+
+func (c cFix) Index(ctx context.Context, in cFixInput) (out *cFixOutput, err error) {
+
+ if in.Path == "" {
+ in.Path = gfile.Pwd()
+ }
+ if in.Version == "" {
+ in.Version, err = c.autoDetectVersion(in)
+ if err != nil {
+ mlog.Fatal(err)
+ }
+ if in.Version == "" {
+ mlog.Print(`no GoFrame usage found, exit fixing`)
+ return
+ }
+ mlog.Debugf(`current GoFrame version auto detect "%s"`, in.Version)
+ }
+
+ if !gproc.IsChild() {
+ mlog.Printf(`start auto fixing directory path "%s"...`, in.Path)
+ defer mlog.Print(`done!`)
+ }
+
+ err = c.doFix(in)
+ return
+}
+
+func (c cFix) doFix(in cFixInput) (err error) {
+
+ var items = []cFixItem{
+ {Version: "v2.3", Func: c.doFixV23},
+ {Version: "v2.5", Func: c.doFixV25},
+ }
+ for _, item := range items {
+ if gstr.CompareVersionGo(in.Version, item.Version) < 0 {
+ mlog.Debugf(
+ `current GoFrame or contrib package version "%s" is lesser than "%s", nothing to do`,
+ in.Version, item.Version,
+ )
+ continue
+ }
+ if err = item.Func(in.Version); err != nil {
+ return
+ }
+ }
+ return
+}
+
+// doFixV23 fixes code when upgrading to GoFrame v2.3.
+func (c cFix) doFixV23(version string) error {
+ replaceFunc := func(path, content string) string {
+ // gdb.TX from struct to interface.
+ content = gstr.Replace(content, "*gdb.TX", "gdb.TX")
+ // function name changes for package gtcp/gudp.
+ if gstr.Contains(content, "/gf/v2/net/gtcp") || gstr.Contains(content, "/gf/v2/net/gudp") {
+ content = gstr.ReplaceByMap(content, g.MapStrStr{
+ ".SetSendDeadline": ".SetDeadlineSend",
+ ".SetReceiveDeadline": ".SetDeadlineRecv",
+ ".SetReceiveBufferWait": ".SetBufferWaitRecv",
+ })
+ }
+ return content
+ }
+ return gfile.ReplaceDirFunc(replaceFunc, ".", "*.go", true)
+}
+
+// doFixV25 fixes code when upgrading to GoFrame v2.5.
+func (c cFix) doFixV25(version string) (err error) {
+ replaceFunc := func(path, content string) string {
+ content, err = c.doFixV25Content(content)
+ return content
+ }
+ return gfile.ReplaceDirFunc(replaceFunc, ".", "*.go", true)
+}
+
+func (c cFix) doFixV25Content(content string) (newContent string, err error) {
+ newContent = content
+ if gstr.Contains(content, `.BindHookHandlerByMap(`) {
+ var pattern = `\.BindHookHandlerByMap\((.+?), map\[string\]ghttp\.HandlerFunc`
+ newContent, err = gregex.ReplaceString(
+ pattern,
+ `.BindHookHandlerByMap($1, map[ghttp.HookName]ghttp.HandlerFunc`,
+ content,
+ )
+ }
+ return
+}
+
+func (c cFix) autoDetectVersion(in cFixInput) (string, error) {
+ var (
+ err error
+ path = gfile.Join(in.Path, "go.mod")
+ version string
+ )
+ if !gfile.Exists(path) {
+ return "", gerror.Newf(`"%s" not found in current working directory`, path)
+ }
+ err = gfile.ReadLines(path, func(line string) error {
+ array := gstr.SplitAndTrim(line, " ")
+ if len(array) > 0 {
+ if gstr.HasPrefix(array[0], gfPackage) {
+ version = array[1]
+ }
+ }
+ return nil
+ })
+ if err != nil {
+ mlog.Fatal(err)
+ }
+ return version, nil
+}
diff --git a/server/internal/library/hggen/internal/cmd/cmd_gen.go b/server/internal/library/hggen/internal/cmd/cmd_gen.go
new file mode 100644
index 0000000..10f03da
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/cmd_gen.go
@@ -0,0 +1,42 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package cmd
+
+import (
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/util/gtag"
+)
+
+var (
+ Gen = cGen{}
+)
+
+type cGen struct {
+ g.Meta `name:"gen" brief:"{cGenBrief}" dc:"{cGenDc}"`
+ cGenDao
+ cGenEnums
+ cGenCtrl
+ cGenPb
+ cGenPbEntity
+ cGenService
+}
+
+const (
+ cGenBrief = `automatically generate go files for dao/do/entity/pb/pbentity`
+ cGenDc = `
+The "gen" command is designed for multiple generating purposes.
+It's currently supporting generating go files for ORM models, protobuf and protobuf entity files.
+Please use "gf gen dao -h" for specified type help.
+`
+)
+
+func init() {
+ gtag.Sets(g.MapStrStr{
+ `cGenBrief`: cGenBrief,
+ `cGenDc`: cGenDc,
+ })
+}
diff --git a/server/internal/library/hggen/internal/cmd/cmd_gen_ctrl.go b/server/internal/library/hggen/internal/cmd/cmd_gen_ctrl.go
new file mode 100644
index 0000000..cd116ac
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/cmd_gen_ctrl.go
@@ -0,0 +1,15 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package cmd
+
+import (
+ "hotgo/internal/library/hggen/internal/cmd/genctrl"
+)
+
+type (
+ cGenCtrl = genctrl.CGenCtrl
+)
diff --git a/server/internal/library/hggen/internal/cmd/cmd_gen_dao.go b/server/internal/library/hggen/internal/cmd/cmd_gen_dao.go
new file mode 100644
index 0000000..2781da5
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/cmd_gen_dao.go
@@ -0,0 +1,25 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package cmd
+
+import (
+ //_ "github.com/gogf/gf/contrib/drivers/clickhouse/v2"
+ //_ "github.com/gogf/gf/contrib/drivers/mssql/v2"
+ _ "github.com/gogf/gf/contrib/drivers/mysql/v2"
+ //_ "github.com/gogf/gf/contrib/drivers/oracle/v2"
+ _ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
+ //_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
+
+ // do not add dm in cli pre-compilation,
+ // the dm driver does not support certain target platforms.
+ // _ "github.com/gogf/gf/contrib/drivers/dm/v2"
+ "hotgo/internal/library/hggen/internal/cmd/gendao"
+)
+
+type (
+ cGenDao = gendao.CGenDao
+)
diff --git a/server/internal/library/hggen/internal/cmd/cmd_gen_enums.go b/server/internal/library/hggen/internal/cmd/cmd_gen_enums.go
new file mode 100644
index 0000000..35fc3b6
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/cmd_gen_enums.go
@@ -0,0 +1,15 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package cmd
+
+import (
+ "hotgo/internal/library/hggen/internal/cmd/genenums"
+)
+
+type (
+ cGenEnums = genenums.CGenEnums
+)
diff --git a/server/internal/library/hggen/internal/cmd/cmd_gen_pb.go b/server/internal/library/hggen/internal/cmd/cmd_gen_pb.go
new file mode 100644
index 0000000..1833d89
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/cmd_gen_pb.go
@@ -0,0 +1,13 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package cmd
+
+import "hotgo/internal/library/hggen/internal/cmd/genpb"
+
+type (
+ cGenPb = genpb.CGenPb
+)
diff --git a/server/internal/library/hggen/internal/cmd/cmd_gen_pbentity.go b/server/internal/library/hggen/internal/cmd/cmd_gen_pbentity.go
new file mode 100644
index 0000000..58b26d8
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/cmd_gen_pbentity.go
@@ -0,0 +1,13 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package cmd
+
+import "hotgo/internal/library/hggen/internal/cmd/genpbentity"
+
+type (
+ cGenPbEntity = genpbentity.CGenPbEntity
+)
diff --git a/server/internal/library/hggen/internal/cmd/cmd_gen_service.go b/server/internal/library/hggen/internal/cmd/cmd_gen_service.go
new file mode 100644
index 0000000..33b5689
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/cmd_gen_service.go
@@ -0,0 +1,15 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package cmd
+
+import (
+ "hotgo/internal/library/hggen/internal/cmd/genservice"
+)
+
+type (
+ cGenService = genservice.CGenService
+)
diff --git a/server/internal/library/hggen/internal/cmd/cmd_init.go b/server/internal/library/hggen/internal/cmd/cmd_init.go
new file mode 100644
index 0000000..319f730
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/cmd_init.go
@@ -0,0 +1,182 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package cmd
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gcmd"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gproc"
+ "github.com/gogf/gf/v2/os/gres"
+ "github.com/gogf/gf/v2/text/gstr"
+ "github.com/gogf/gf/v2/util/gtag"
+
+ "hotgo/internal/library/hggen/internal/utility/allyes"
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+ "hotgo/internal/library/hggen/internal/utility/utils"
+)
+
+var (
+ // Init .
+ Init = cInit{}
+)
+
+type cInit struct {
+ g.Meta `name:"init" brief:"{cInitBrief}" eg:"{cInitEg}"`
+}
+
+const (
+ cInitRepoPrefix = `github.com/gogf/`
+ cInitMonoRepo = `template-mono`
+ cInitMonoRepoApp = `template-mono-app`
+ cInitSingleRepo = `template-single`
+ cInitBrief = `create and initialize an empty GoFrame project`
+ cInitEg = `
+gf init my-project
+gf init my-mono-repo -m
+gf init my-mono-repo -a
+`
+ cInitNameBrief = `
+name for the project. It will create a folder with NAME in current directory.
+The NAME will also be the module name for the project.
+`
+ // cInitGitDir the git directory
+ cInitGitDir = ".git"
+ // cInitGitignore the gitignore file
+ cInitGitignore = ".gitignore"
+)
+
+func init() {
+ gtag.Sets(g.MapStrStr{
+ `cInitBrief`: cInitBrief,
+ `cInitEg`: cInitEg,
+ `cInitNameBrief`: cInitNameBrief,
+ })
+}
+
+type cInitInput struct {
+ g.Meta `name:"init"`
+ Name string `name:"NAME" arg:"true" v:"required" brief:"{cInitNameBrief}"`
+ Mono bool `name:"mono" short:"m" brief:"initialize a mono-repo instead a single-repo" orphan:"true"`
+ MonoApp bool `name:"monoApp" short:"a" brief:"initialize a mono-repo-app 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
+ if !gfile.IsEmpty(in.Name) && !allyes.Check() {
+ s := gcmd.Scanf(`the folder "%s" is not empty, files might be overwrote, continue? [y/n]: `, in.Name)
+ if strings.EqualFold(s, "n") {
+ return
+ }
+ overwrote = true
+ }
+ mlog.Print("initializing...")
+
+ // Create project folder and files.
+ var (
+ templateRepoName string
+ gitignoreFile = in.Name + "/" + cInitGitignore
+ )
+
+ if in.Mono {
+ templateRepoName = cInitMonoRepo
+ } else if in.MonoApp {
+ templateRepoName = cInitMonoRepoApp
+ } else {
+ templateRepoName = cInitSingleRepo
+ }
+
+ err = gres.Export(templateRepoName, in.Name, gres.ExportOption{
+ RemovePrefix: templateRepoName,
+ })
+ if err != nil {
+ return
+ }
+
+ // build ignoreFiles from the .gitignore file
+ ignoreFiles := make([]string, 0, 10)
+ ignoreFiles = append(ignoreFiles, cInitGitDir)
+ // in.MonoApp is a mono-repo-app, it should ignore the .gitignore file
+ if overwrote && !in.MonoApp {
+ 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 '
+ if line != "" && strings.HasPrefix(line, ".") {
+ ignoreFiles = append(ignoreFiles, line)
+ }
+ return nil
+ })
+
+ // if not found the .gitignore file will skip os.ErrNotExist error
+ if err != nil && !os.IsNotExist(err) {
+ return
+ }
+ }
+
+ // Get template name and module name.
+ if in.Module == "" {
+ in.Module = gfile.Basename(gfile.RealPath(in.Name))
+ }
+ if in.MonoApp {
+ pwd := gfile.Pwd() + string(os.PathSeparator) + in.Name
+ in.Module = utils.GetImportPath(pwd)
+ }
+
+ // Replace template name to project name.
+ err = gfile.ReplaceDirFunc(func(path, content string) string {
+ for _, ignoreFile := range ignoreFiles {
+ if strings.Contains(path, ignoreFile) {
+ return content
+ }
+ }
+ return gstr.Replace(gfile.GetContents(path), cInitRepoPrefix+templateRepoName, in.Module)
+ }, in.Name, "*", true)
+ if err != nil {
+ return
+ }
+
+ // Update the GoFrame version.
+ if in.Update {
+ mlog.Print("update goframe...")
+ // go get -u github.com/gogf/gf/v2@latest
+ updateCommand := `go get -u github.com/gogf/gf/v2@latest`
+ if in.Name != "." {
+ updateCommand = fmt.Sprintf(`cd %s && %s`, in.Name, updateCommand)
+ }
+ if err = gproc.ShellRun(ctx, updateCommand); err != nil {
+ mlog.Fatal(err)
+ }
+ // go mod tidy
+ gomModTidyCommand := `go mod tidy`
+ if in.Name != "." {
+ gomModTidyCommand = fmt.Sprintf(`cd %s && %s`, in.Name, gomModTidyCommand)
+ }
+ if err = gproc.ShellRun(ctx, gomModTidyCommand); err != nil {
+ mlog.Fatal(err)
+ }
+ }
+
+ mlog.Print("initialization done! ")
+ if !in.Mono {
+ enjoyCommand := `gf run main.go`
+ if in.Name != "." {
+ enjoyCommand = fmt.Sprintf(`cd %s && %s`, in.Name, enjoyCommand)
+ }
+ mlog.Printf(`you can now run "%s" to start your journey, enjoy!`, enjoyCommand)
+ }
+ return
+}
diff --git a/server/internal/library/hggen/internal/cmd/cmd_install.go b/server/internal/library/hggen/internal/cmd/cmd_install.go
new file mode 100644
index 0000000..0dd30ad
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/cmd_install.go
@@ -0,0 +1,34 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package cmd
+
+import (
+ "context"
+
+ "github.com/gogf/gf/v2/frame/g"
+
+ "hotgo/internal/library/hggen/internal/service"
+)
+
+var (
+ Install = cInstall{}
+)
+
+type cInstall struct {
+ g.Meta `name:"install" brief:"install gf binary to system (might need root/admin permission)"`
+}
+
+type cInstallInput struct {
+ g.Meta `name:"install"`
+}
+
+type cInstallOutput struct{}
+
+func (c cInstall) Index(ctx context.Context, in cInstallInput) (out *cInstallOutput, err error) {
+ err = service.Install.Run(ctx)
+ return
+}
diff --git a/server/internal/library/hggen/internal/cmd/cmd_pack.go b/server/internal/library/hggen/internal/cmd/cmd_pack.go
new file mode 100644
index 0000000..b6addd0
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/cmd_pack.go
@@ -0,0 +1,104 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package cmd
+
+import (
+ "context"
+ "strings"
+
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gcmd"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gres"
+ "github.com/gogf/gf/v2/util/gtag"
+
+ "hotgo/internal/library/hggen/internal/utility/allyes"
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+)
+
+var (
+ Pack = cPack{}
+)
+
+type cPack struct {
+ g.Meta `name:"pack" usage:"{cPackUsage}" brief:"{cPackBrief}" eg:"{cPackEg}"`
+}
+
+const (
+ cPackUsage = `gf pack SRC DST`
+ cPackBrief = `packing any file/directory to a resource file, or a go file`
+ cPackEg = `
+gf pack public data.bin
+gf pack public,template data.bin
+gf pack public,template packed/data.go
+gf pack public,template,config packed/data.go
+gf pack public,template,config packed/data.go -n=packed -p=/var/www/my-app
+gf pack /var/www/public packed/data.go -n=packed
+`
+ cPackSrcBrief = `source path for packing, which can be multiple source paths.`
+ cPackDstBrief = `
+destination file path for packed file. if extension of the filename is ".go" and "-n" option is given,
+it enables packing SRC to go file, or else it packs SRC into a binary file.
+`
+ cPackNameBrief = `package name for output go file, it's set as its directory name if no name passed`
+ cPackPrefixBrief = `prefix for each file packed into the resource file`
+ cPackKeepPathBrief = `keep the source path from system to resource file, usually for relative path`
+)
+
+func init() {
+ gtag.Sets(g.MapStrStr{
+ `cPackUsage`: cPackUsage,
+ `cPackBrief`: cPackBrief,
+ `cPackEg`: cPackEg,
+ `cPackSrcBrief`: cPackSrcBrief,
+ `cPackDstBrief`: cPackDstBrief,
+ `cPackNameBrief`: cPackNameBrief,
+ `cPackPrefixBrief`: cPackPrefixBrief,
+ `cPackKeepPathBrief`: cPackKeepPathBrief,
+ })
+}
+
+type cPackInput struct {
+ g.Meta `name:"pack" config:"gfcli.pack"`
+ Src string `name:"SRC" arg:"true" v:"required" brief:"{cPackSrcBrief}"`
+ Dst string `name:"DST" arg:"true" v:"required" brief:"{cPackDstBrief}"`
+ Name string `name:"name" short:"n" brief:"{cPackNameBrief}"`
+ Prefix string `name:"prefix" short:"p" brief:"{cPackPrefixBrief}"`
+ KeepPath bool `name:"keepPath" short:"k" brief:"{cPackKeepPathBrief}" orphan:"true"`
+}
+
+type cPackOutput struct{}
+
+func (c cPack) Index(ctx context.Context, in cPackInput) (out *cPackOutput, err error) {
+ if gfile.Exists(in.Dst) && gfile.IsDir(in.Dst) {
+ mlog.Fatalf("DST path '%s' cannot be a directory", in.Dst)
+ }
+ if !gfile.IsEmpty(in.Dst) && !allyes.Check() {
+ s := gcmd.Scanf("path '%s' is not empty, files might be overwrote, continue? [y/n]: ", in.Dst)
+ if strings.EqualFold(s, "n") {
+ return
+ }
+ }
+ if in.Name == "" && gfile.ExtName(in.Dst) == "go" {
+ in.Name = gfile.Basename(gfile.Dir(in.Dst))
+ }
+ var option = gres.Option{
+ Prefix: in.Prefix,
+ KeepPath: in.KeepPath,
+ }
+ if in.Name != "" {
+ if err = gres.PackToGoFileWithOption(in.Src, in.Dst, in.Name, option); err != nil {
+ mlog.Fatalf("pack failed: %v", err)
+ }
+ } else {
+ if err = gres.PackToFileWithOption(in.Src, in.Dst, option); err != nil {
+ mlog.Fatalf("pack failed: %v", err)
+ }
+ }
+ mlog.Print("done!")
+ return
+}
diff --git a/server/internal/library/hggen/internal/cmd/cmd_run.go b/server/internal/library/hggen/internal/cmd/cmd_run.go
new file mode 100644
index 0000000..7eef8fb
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/cmd_run.go
@@ -0,0 +1,283 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package cmd
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "time"
+
+ "github.com/gogf/gf/v2/container/gtype"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gfsnotify"
+ "github.com/gogf/gf/v2/os/gproc"
+ "github.com/gogf/gf/v2/os/gtime"
+ "github.com/gogf/gf/v2/os/gtimer"
+ "github.com/gogf/gf/v2/util/gtag"
+
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+)
+
+var Run = cRun{}
+
+type cRun struct {
+ g.Meta `name:"run" usage:"{cRunUsage}" brief:"{cRunBrief}" eg:"{cRunEg}" dc:"{cRunDc}"`
+}
+
+type cRunApp struct {
+ File string // Go run file name.
+ Path string // Directory storing built binary.
+ Options string // Extra "go run" options.
+ Args string // Custom arguments.
+ WatchPaths []string // Watch paths for live reload.
+}
+
+const (
+ cRunUsage = `gf run FILE [OPTION]`
+ cRunBrief = `running go codes with hot-compiled-like feature`
+ cRunEg = `
+gf run main.go
+gf run main.go --args "server -p 8080"
+gf run main.go -mod=vendor
+gf run main.go -w "manifest/config/*.yaml"
+`
+ cRunDc = `
+The "run" command is used for running go codes with hot-compiled-like feature,
+which compiles and runs the go codes asynchronously when codes change.
+`
+ cRunFileBrief = `building file path.`
+ cRunPathBrief = `output directory path for built binary file. it's "./" in default`
+ cRunExtraBrief = `the same options as "go run"/"go build" except some options as follows defined`
+ cRunArgsBrief = `custom arguments for your process`
+ cRunWatchPathsBrief = `watch additional paths for live reload, separated by ",". i.e. "manifest/config/*.yaml"`
+)
+
+var process *gproc.Process
+
+func init() {
+ gtag.Sets(g.MapStrStr{
+ `cRunUsage`: cRunUsage,
+ `cRunBrief`: cRunBrief,
+ `cRunEg`: cRunEg,
+ `cRunDc`: cRunDc,
+ `cRunFileBrief`: cRunFileBrief,
+ `cRunPathBrief`: cRunPathBrief,
+ `cRunExtraBrief`: cRunExtraBrief,
+ `cRunArgsBrief`: cRunArgsBrief,
+ `cRunWatchPathsBrief`: cRunWatchPathsBrief,
+ })
+}
+
+type (
+ cRunInput struct {
+ g.Meta `name:"run" config:"gfcli.run"`
+ File string `name:"FILE" arg:"true" brief:"{cRunFileBrief}" v:"required"`
+ Path string `name:"path" short:"p" brief:"{cRunPathBrief}" d:"./"`
+ Extra string `name:"extra" short:"e" brief:"{cRunExtraBrief}"`
+ Args string `name:"args" short:"a" brief:"{cRunArgsBrief}"`
+ WatchPaths []string `name:"watchPaths" short:"w" brief:"{cRunWatchPathsBrief}"`
+ }
+ cRunOutput struct{}
+)
+
+func (c cRun) Index(ctx context.Context, in cRunInput) (out *cRunOutput, err error) {
+ if !gfile.Exists(in.File) {
+ mlog.Fatalf(`given file "%s" not found`, in.File)
+ }
+ if !gfile.IsFile(in.File) {
+ mlog.Fatalf(`given "%s" is not a file`, in.File)
+ }
+ // Necessary check.
+ if gproc.SearchBinary("go") == "" {
+ mlog.Fatalf(`command "go" not found in your environment, please install golang first to proceed this command`)
+ }
+
+ if len(in.WatchPaths) == 1 {
+ in.WatchPaths = strings.Split(in.WatchPaths[0], ",")
+ mlog.Printf("watchPaths: %v", in.WatchPaths)
+ }
+
+ app := &cRunApp{
+ File: in.File,
+ Path: filepath.FromSlash(in.Path),
+ Options: in.Extra,
+ Args: in.Args,
+ WatchPaths: in.WatchPaths,
+ }
+ dirty := gtype.NewBool()
+
+ outputPath := app.genOutputPath()
+ callbackFunc := func(event *gfsnotify.Event) {
+ if !event.IsWrite() && !event.IsCreate() && !event.IsRemove() && !event.IsRename() {
+ return
+ }
+
+ if gfile.ExtName(event.Path) != "go" {
+ return
+ }
+
+ // Variable `dirty` is used for running the changes only one in one second.
+ if !dirty.Cas(false, true) {
+ return
+ }
+
+ // With some delay in case of multiple code changes in very short interval.
+ gtimer.SetTimeout(ctx, 1500*gtime.MS, func(ctx context.Context) {
+ defer dirty.Set(false)
+ mlog.Printf(`watched file changes: %s`, event.String())
+ app.Run(ctx, outputPath)
+ })
+ }
+
+ if len(app.WatchPaths) > 0 {
+ for _, path := range app.WatchPaths {
+ _, err = gfsnotify.Add(gfile.RealPath(path), callbackFunc)
+ if err != nil {
+ mlog.Fatal(err)
+ }
+ }
+ } else {
+ _, err = gfsnotify.Add(gfile.RealPath("."), callbackFunc)
+ if err != nil {
+ mlog.Fatal(err)
+ }
+ }
+
+ go app.Run(ctx, outputPath)
+
+ gproc.AddSigHandlerShutdown(func(sig os.Signal) {
+ app.End(ctx, sig, outputPath)
+ os.Exit(0)
+ })
+ gproc.Listen()
+
+ select {}
+}
+
+func (app *cRunApp) Run(ctx context.Context, outputPath string) {
+ // Rebuild and run the codes.
+ mlog.Printf("build: %s", app.File)
+
+ // In case of `pipe: too many open files` error.
+ // Build the app.
+ buildCommand := fmt.Sprintf(
+ `go build -o %s %s %s`,
+ outputPath,
+ app.Options,
+ app.File,
+ )
+ mlog.Print(buildCommand)
+ result, err := gproc.ShellExec(ctx, buildCommand)
+ if err != nil {
+ mlog.Printf("build error: \n%s%s", result, err.Error())
+ return
+ }
+ // Kill the old process if build successfully.
+ if process != nil {
+ if err := process.Kill(); err != nil {
+ mlog.Debugf("kill process error: %s", err.Error())
+ }
+ }
+ // Run the binary file.
+ runCommand := fmt.Sprintf(`%s %s`, outputPath, app.Args)
+ mlog.Print(runCommand)
+ if runtime.GOOS == "windows" {
+ // Special handling for windows platform.
+ // DO NOT USE "cmd /c" command.
+ process = gproc.NewProcess(outputPath, strings.Fields(app.Args))
+ } else {
+ process = gproc.NewProcessCmd(runCommand, nil)
+ }
+ if pid, err := process.Start(ctx); err != nil {
+ mlog.Printf("build running error: %s", err.Error())
+ } else {
+ mlog.Printf("build running pid: %d", pid)
+ }
+}
+
+func (app *cRunApp) End(ctx context.Context, sig os.Signal, outputPath string) {
+ // Delete the binary file.
+ // firstly, kill the process.
+ if process != nil {
+ if sig != nil && runtime.GOOS != "windows" {
+ if err := process.Signal(sig); err != nil {
+ mlog.Debugf("send signal to process error: %s", err.Error())
+ if err := process.Kill(); err != nil {
+ mlog.Debugf("kill process error: %s", err.Error())
+ }
+ } else {
+ waitCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
+ defer cancel()
+ done := make(chan error, 1)
+ go func() {
+ select {
+ case <-waitCtx.Done():
+ done <- waitCtx.Err()
+ case done <- process.Wait():
+ }
+ }()
+ err := <-done
+ if err != nil {
+ mlog.Debugf("process wait error: %s", err.Error())
+ if err := process.Kill(); err != nil {
+ mlog.Debugf("kill process error: %s", err.Error())
+ }
+ } else {
+ mlog.Debug("process exited gracefully")
+ }
+ }
+ } else {
+ if err := process.Kill(); err != nil {
+ mlog.Debugf("kill process error: %s", err.Error())
+ }
+ }
+ }
+ if err := gfile.RemoveFile(outputPath); err != nil {
+ mlog.Printf("delete binary file error: %s", err.Error())
+ } else {
+ mlog.Printf("deleted binary file: %s", outputPath)
+ }
+}
+
+func (app *cRunApp) genOutputPath() (outputPath string) {
+ var renamePath string
+ outputPath = gfile.Join(app.Path, gfile.Name(app.File))
+ if runtime.GOOS == "windows" {
+ outputPath += ".exe"
+ if gfile.Exists(outputPath) {
+ renamePath = outputPath + "~"
+ if err := gfile.Rename(outputPath, renamePath); err != nil {
+ mlog.Print(err)
+ }
+ }
+ }
+ return filepath.FromSlash(outputPath)
+}
+
+func matchWatchPaths(watchPaths []string, eventPath string) bool {
+ for _, path := range watchPaths {
+ absPath, err := filepath.Abs(path)
+ if err != nil {
+ mlog.Printf("match watchPath '%s' error: %s", path, err.Error())
+ continue
+ }
+ matched, err := filepath.Match(absPath, eventPath)
+ if err != nil {
+ mlog.Printf("match watchPath '%s' error: %s", path, err.Error())
+ continue
+ }
+ if matched {
+ return true
+ }
+ }
+ return false
+}
diff --git a/server/internal/library/hggen/internal/cmd/cmd_tpl.go b/server/internal/library/hggen/internal/cmd/cmd_tpl.go
new file mode 100644
index 0000000..4c132e5
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/cmd_tpl.go
@@ -0,0 +1,174 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package cmd
+
+import (
+ "context"
+
+ "github.com/gogf/gf/v2/encoding/gjson"
+ "github.com/gogf/gf/v2/errors/gerror"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/text/gstr"
+ "github.com/gogf/gf/v2/util/gtag"
+ "github.com/gogf/gf/v2/util/gutil"
+
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+)
+
+var (
+ Tpl = cTpl{}
+)
+
+type cTpl struct {
+ g.Meta `name:"tpl" brief:"{cTplBrief}" dc:"{cTplDc}"`
+}
+
+const (
+ cTplBrief = `template parsing and building commands`
+ cTplDc = `
+The "tpl" command is used for template parsing and building purpose.
+It can parse either template file or folder with multiple types of values support,
+like json/xml/yaml/toml/ini.
+`
+ cTplParseBrief = `parse either template file or folder with multiple types of values`
+ cTplParseEg = `
+gf tpl parse -p ./template -v values.json -r
+gf tpl parse -p ./template -v values.json -n *.tpl -r
+gf tpl parse -p ./template -v values.json -d '${{,}}' -r
+gf tpl parse -p ./template -v values.json -o ./template.parsed
+`
+ cTplSupportValuesFilePattern = `*.json,*.xml,*.yaml,*.yml,*.toml,*.ini`
+)
+
+type (
+ cTplParseInput struct {
+ g.Meta `name:"parse" config:"gfcli.tpl.parse" brief:"{cTplParseBrief}" eg:"{cTplParseEg}"`
+ Path string `name:"path" short:"p" brief:"template file or folder path" v:"required"`
+ Pattern string `name:"pattern" short:"n" brief:"template file pattern when path is a folder, default is:*" d:"*"`
+ Recursive bool `name:"recursive" short:"c" brief:"recursively parsing files if path is folder, default is:true" d:"true"`
+ Values string `name:"values" short:"v" brief:"template values file/folder, support file types like: json/xml/yaml/toml/ini" v:"required"`
+ Output string `name:"output" short:"o" brief:"output file/folder path"`
+ Delimiters string `name:"delimiters" short:"d" brief:"delimiters for template content parsing, default is:{{,}}" d:"{{,}}"`
+ Replace bool `name:"replace" short:"r" brief:"replace original files" orphan:"true"`
+ }
+ cTplParseOutput struct{}
+)
+
+func init() {
+ gtag.Sets(g.MapStrStr{
+ `cTplBrief`: cTplBrief,
+ `cTplDc`: cTplDc,
+ `cTplParseEg`: cTplParseEg,
+ `cTplParseBrief`: cTplParseBrief,
+ })
+}
+
+func (c *cTpl) Parse(ctx context.Context, in cTplParseInput) (out *cTplParseOutput, err error) {
+ if in.Output == "" && !in.Replace {
+ return nil, gerror.New(`parameter output and replace should not be both empty`)
+ }
+ delimiters := gstr.SplitAndTrim(in.Delimiters, ",")
+ mlog.Debugf("delimiters input:%s, parsed:%#v", in.Delimiters, delimiters)
+ if len(delimiters) != 2 {
+ return nil, gerror.Newf(`invalid delimiters: %s`, in.Delimiters)
+ }
+ g.View().SetDelimiters(delimiters[0], delimiters[1])
+ valuesMap, err := c.loadValues(ctx, in.Values)
+ if err != nil {
+ return nil, err
+ }
+ if len(valuesMap) == 0 {
+ return nil, gerror.Newf(`empty values loaded from values file/folder "%s"`, in.Values)
+ }
+ err = c.parsePath(ctx, valuesMap, in)
+ if err == nil {
+ mlog.Print("done!")
+ }
+ return
+}
+
+func (c *cTpl) parsePath(ctx context.Context, values g.Map, in cTplParseInput) (err error) {
+ if !gfile.Exists(in.Path) {
+ return gerror.Newf(`path "%s" does not exist`, in.Path)
+ }
+ var (
+ path string
+ files []string
+ relativePath string
+ outputPath string
+ )
+ path = gfile.RealPath(in.Path)
+ if gfile.IsDir(path) {
+ files, err = gfile.ScanDirFile(path, in.Pattern, in.Recursive)
+ if err != nil {
+ return err
+ }
+ for _, file := range files {
+ relativePath = gstr.Replace(file, path, "")
+ if in.Output != "" {
+ outputPath = gfile.Join(in.Output, relativePath)
+ }
+ if err = c.parseFile(ctx, file, outputPath, values, in); err != nil {
+ return
+ }
+ }
+ return
+ }
+ if in.Output != "" {
+ outputPath = in.Output
+ }
+ err = c.parseFile(ctx, path, outputPath, values, in)
+ return
+}
+
+func (c *cTpl) parseFile(ctx context.Context, file string, output string, values g.Map, in cTplParseInput) (err error) {
+ output = gstr.ReplaceByMap(output, g.MapStrStr{
+ `\\`: `\`,
+ `//`: `/`,
+ })
+ content, err := g.View().Parse(ctx, file, values)
+ if err != nil {
+ return err
+ }
+ if output != "" {
+ mlog.Printf(`parse file "%s" to "%s"`, file, output)
+ return gfile.PutContents(output, content)
+ }
+ if in.Replace {
+ mlog.Printf(`parse and replace file "%s"`, file)
+ return gfile.PutContents(file, content)
+ }
+ return nil
+}
+
+func (c *cTpl) loadValues(ctx context.Context, valuesPath string) (data g.Map, err error) {
+ if !gfile.Exists(valuesPath) {
+ return nil, gerror.Newf(`values file/folder "%s" does not exist`, valuesPath)
+ }
+ var j *gjson.Json
+ if gfile.IsDir(valuesPath) {
+ var valueFiles []string
+ valueFiles, err = gfile.ScanDirFile(valuesPath, cTplSupportValuesFilePattern, true)
+ if err != nil {
+ return nil, err
+ }
+ data = make(g.Map)
+ for _, file := range valueFiles {
+ if j, err = gjson.Load(file); err != nil {
+ return nil, err
+ }
+ gutil.MapMerge(data, j.Map())
+ }
+ return
+ }
+ if j, err = gjson.Load(valuesPath); err != nil {
+ return nil, err
+ }
+ data = j.Map()
+ return
+}
diff --git a/server/internal/library/hggen/internal/cmd/cmd_up.go b/server/internal/library/hggen/internal/cmd/cmd_up.go
new file mode 100644
index 0000000..1b12686
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/cmd_up.go
@@ -0,0 +1,279 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package cmd
+
+import (
+ "context"
+ "fmt"
+ "runtime"
+
+ "github.com/gogf/selfupdate"
+
+ "github.com/gogf/gf/v2/container/gset"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/genv"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gproc"
+ "github.com/gogf/gf/v2/text/gstr"
+ "github.com/gogf/gf/v2/util/gtag"
+
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+ "hotgo/internal/library/hggen/internal/utility/utils"
+)
+
+var (
+ Up = cUp{}
+)
+
+type cUp struct {
+ g.Meta `name:"up" brief:"upgrade GoFrame version/tool to latest one in current project" eg:"{cUpEg}" `
+}
+
+const (
+ gfPackage = `github.com/gogf/gf/`
+ cUpEg = `
+gf up
+gf up -a
+gf up -c
+gf up -cf
+gf up -a -m=install
+gf up -a -m=install -p=github.com/gogf/gf/cmd/gf/v2@latest
+`
+ cliMethodHttpDownload = "http"
+ cliMethodGoInstall = "install"
+)
+
+func init() {
+ gtag.Sets(g.MapStrStr{
+ `cUpEg`: cUpEg,
+ })
+}
+
+type cUpInput struct {
+ g.Meta `name:"up" config:"gfcli.up"`
+ All bool `name:"all" short:"a" brief:"upgrade both version and cli, auto fix codes" orphan:"true"`
+ Cli bool `name:"cli" short:"c" brief:"also upgrade CLI tool" orphan:"true"`
+ Fix bool `name:"fix" short:"f" brief:"auto fix codes(it only make sense if cli is to be upgraded)" orphan:"true"`
+ CliDownloadingMethod string `name:"cli-download-method" short:"m" brief:"cli upgrade method: http=download binary via HTTP GET, install=upgrade via go install" d:"http"`
+ // CliModulePath specifies the module path for CLI installation via go install.
+ // This is used when CliDownloadingMethod is set to "install".
+ CliModulePath string `name:"cli-module-path" short:"p" brief:"custom cli module path for upgrade CLI tool with go install method" d:"github.com/gogf/gf/cmd/gf/v2@latest"`
+}
+
+type cUpOutput struct{}
+
+func (c cUp) Index(ctx context.Context, in cUpInput) (out *cUpOutput, err error) {
+ defer func() {
+ if err == nil {
+ mlog.Print()
+ mlog.Print(`👏congratulations! you've upgraded to the latest version of GoFrame! enjoy it!👏`)
+ mlog.Print()
+ }
+ }()
+
+ var doUpgradeVersionOut *doUpgradeVersionOutput
+ if in.All {
+ in.Cli = true
+ in.Fix = true
+ }
+ if doUpgradeVersionOut, err = c.doUpgradeVersion(ctx, in); err != nil {
+ return nil, err
+ }
+
+ if in.Cli {
+ if err = c.doUpgradeCLI(ctx, in); err != nil {
+ return nil, err
+ }
+ }
+
+ if in.Cli && in.Fix {
+ if doUpgradeVersionOut != nil && len(doUpgradeVersionOut.Items) > 0 {
+ upgradedPathSet := gset.NewStrSet()
+ for _, item := range doUpgradeVersionOut.Items {
+ if !upgradedPathSet.AddIfNotExist(item.DirPath) {
+ continue
+ }
+ if err = c.doAutoFixing(ctx, item.DirPath, item.Version); err != nil {
+ return nil, err
+ }
+ }
+ }
+ }
+ return
+}
+
+type doUpgradeVersionOutput struct {
+ Items []doUpgradeVersionOutputItem
+}
+
+type doUpgradeVersionOutputItem struct {
+ DirPath string
+ Version string
+}
+
+func (c cUp) doUpgradeVersion(ctx context.Context, in cUpInput) (out *doUpgradeVersionOutput, err error) {
+ mlog.Print(`start upgrading version...`)
+ out = &doUpgradeVersionOutput{
+ Items: make([]doUpgradeVersionOutputItem, 0),
+ }
+ type Package struct {
+ Name string
+ Version string
+ }
+
+ var (
+ temp string
+ dirPath = gfile.Pwd()
+ goModPath = gfile.Join(dirPath, "go.mod")
+ )
+ // It recursively upgrades the go.mod from sub folder to its parent folders.
+ for {
+ if gfile.Exists(goModPath) {
+ var packages []Package
+ err = gfile.ReadLines(goModPath, func(line string) error {
+ line = gstr.Trim(line)
+ line = gstr.TrimLeftStr(line, "require ")
+ line = gstr.Trim(line)
+ if gstr.HasPrefix(line, gfPackage) {
+ array := gstr.SplitAndTrim(line, " ")
+ packages = append(packages, Package{
+ Name: array[0],
+ Version: array[1],
+ })
+ }
+ return nil
+ })
+ if err != nil {
+ return
+ }
+ for _, pkg := range packages {
+ mlog.Printf(`upgrading "%s" from "%s" to "latest"`, pkg.Name, pkg.Version)
+ mlog.Printf(`running command: go get %s@latest`, pkg.Name)
+ // go get @latest
+ command := fmt.Sprintf(`cd %s && go get %s@latest`, dirPath, pkg.Name)
+ if err = gproc.ShellRun(ctx, command); err != nil {
+ return
+ }
+ // go mod tidy
+ if err = utils.GoModTidy(ctx, dirPath); err != nil {
+ return nil, err
+ }
+ out.Items = append(out.Items, doUpgradeVersionOutputItem{
+ DirPath: dirPath,
+ Version: pkg.Version,
+ })
+ }
+ return
+ }
+ temp = gfile.Dir(dirPath)
+ if temp == "" || temp == dirPath {
+ return
+ }
+ dirPath = temp
+ goModPath = gfile.Join(dirPath, "go.mod")
+ }
+}
+
+// doUpgradeCLI downloads the new version binary with process.
+func (c cUp) doUpgradeCLI(ctx context.Context, in cUpInput) (err error) {
+ mlog.Print(`start upgrading cli...`)
+ fmt.Println(` cli upgrade method:`, in.CliDownloadingMethod)
+ switch in.CliDownloadingMethod {
+ case cliMethodHttpDownload:
+ return c.doUpgradeCLIWithHttpDownload(ctx)
+ case cliMethodGoInstall:
+ return c.doUpgradeCLIWithGoInstall(ctx, in)
+ default:
+ mlog.Fatalf(`invalid cli upgrade method: "%s", please use "http" or "install"`, in.CliDownloadingMethod)
+ }
+ return
+}
+
+func (c cUp) doUpgradeCLIWithHttpDownload(ctx context.Context) (err error) {
+ mlog.Print(`start upgrading cli with http get download...`)
+ var (
+ downloadUrl = fmt.Sprintf(
+ `https://github.com/gogf/gf/releases/latest/download/gf_%s_%s`,
+ runtime.GOOS, runtime.GOARCH,
+ )
+ localSaveFilePath = gfile.SelfPath() + "~"
+ )
+
+ if runtime.GOOS == "windows" {
+ downloadUrl += ".exe"
+ }
+
+ mlog.Printf(`start downloading "%s" to "%s", it may take some time`, downloadUrl, localSaveFilePath)
+ err = utils.HTTPDownloadFileWithPercent(downloadUrl, localSaveFilePath)
+ if err != nil {
+ return err
+ }
+
+ defer func() {
+ mlog.Printf(`new version cli binary is successfully installed to "%s"`, gfile.SelfPath())
+ mlog.Printf(`remove temporary buffer file "%s"`, localSaveFilePath)
+ _ = gfile.RemoveFile(localSaveFilePath)
+ }()
+
+ // It fails if file not exist or its size is less than 1MB.
+ if !gfile.Exists(localSaveFilePath) || gfile.Size(localSaveFilePath) < 1024*1024 {
+ mlog.Fatalf(`download "%s" to "%s" failed`, downloadUrl, localSaveFilePath)
+ }
+
+ newFile, err := gfile.Open(localSaveFilePath)
+ if err != nil {
+ return err
+ }
+ // selfupdate
+ err = selfupdate.Apply(newFile, selfupdate.Options{})
+ if err != nil {
+ return err
+ }
+ return
+}
+
+func (c cUp) doUpgradeCLIWithGoInstall(ctx context.Context, in cUpInput) (err error) {
+ mlog.Print(`upgrading cli with go install...`)
+ if !genv.Contains("GOPATH") {
+ mlog.Fatal(`"GOPATH" environment variable does not exist, please check your go installation`)
+ }
+
+ command := fmt.Sprintf(`go install %s`, in.CliModulePath)
+ mlog.Printf(`running command: %s`, command)
+ err = gproc.ShellRun(ctx, command)
+ if err != nil {
+ return err
+ }
+
+ cliFilePath := gfile.Join(genv.Get("GOPATH").String(), "bin/gf")
+ if runtime.GOOS == "windows" {
+ cliFilePath += ".exe"
+ }
+
+ // It fails if file not exist or its size is less than 1MB.
+ if !gfile.Exists(cliFilePath) || gfile.Size(cliFilePath) < 1024*1024 {
+ mlog.Fatalf(`go install %s failed, "%s" does not exist or its size is less than 1MB`, in.CliModulePath, cliFilePath)
+ }
+
+ newFile, err := gfile.Open(cliFilePath)
+ if err != nil {
+ return err
+ }
+ // selfupdate
+ err = selfupdate.Apply(newFile, selfupdate.Options{})
+ if err != nil {
+ return err
+ }
+ return
+}
+
+func (c cUp) doAutoFixing(ctx context.Context, dirPath string, version string) (err error) {
+ mlog.Printf(`auto fixing directory path "%s" from version "%s" ...`, dirPath, version)
+ command := fmt.Sprintf(`gf fix -p %s`, dirPath)
+ _ = gproc.ShellRun(ctx, command)
+ return
+}
diff --git a/server/internal/library/hggen/internal/cmd/cmd_version.go b/server/internal/library/hggen/internal/cmd/cmd_version.go
new file mode 100644
index 0000000..420d588
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/cmd_version.go
@@ -0,0 +1,162 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package cmd
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "runtime"
+ "strings"
+ "time"
+
+ "github.com/gogf/gf/v2"
+ "github.com/gogf/gf/v2/errors/gerror"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gbuild"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gproc"
+ "github.com/gogf/gf/v2/text/gregex"
+ "github.com/gogf/gf/v2/text/gstr"
+
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+)
+
+var (
+ Version = cVersion{}
+)
+
+const (
+ defaultIndent = "{{indent}}"
+)
+
+type cVersion struct {
+ g.Meta `name:"version" brief:"show version information of current binary"`
+}
+
+type cVersionInput struct {
+ g.Meta `name:"version"`
+}
+
+type cVersionOutput struct{}
+
+func (c cVersion) Index(ctx context.Context, in cVersionInput) (*cVersionOutput, error) {
+ detailBuffer := &detailBuffer{}
+ detailBuffer.WriteString(fmt.Sprintf("%s", gf.VERSION))
+
+ detailBuffer.appendLine(0, "Welcome to GoFrame!")
+
+ detailBuffer.appendLine(0, "Env Detail:")
+ goVersion, ok := getGoVersion()
+ if ok {
+ detailBuffer.appendLine(1, fmt.Sprintf("Go Version: %s", goVersion))
+ detailBuffer.appendLine(1, fmt.Sprintf("GF Version(go.mod): %s", getGoFrameVersion(2)))
+ } else {
+ v, err := c.getGFVersionOfCurrentProject()
+ if err == nil {
+ detailBuffer.appendLine(1, fmt.Sprintf("GF Version(go.mod): %s", v))
+ } else {
+ detailBuffer.appendLine(1, fmt.Sprintf("GF Version(go.mod): %s", err.Error()))
+ }
+ }
+
+ detailBuffer.appendLine(0, "CLI Detail:")
+ detailBuffer.appendLine(1, fmt.Sprintf("Installed At: %s", gfile.SelfPath()))
+ info := gbuild.Info()
+ if info.GoFrame == "" {
+ detailBuffer.appendLine(1, fmt.Sprintf("Built Go Version: %s", runtime.Version()))
+ detailBuffer.appendLine(1, fmt.Sprintf("Built GF Version: %s", gf.VERSION))
+ } else {
+ if info.Git == "" {
+ info.Git = "none"
+ }
+ detailBuffer.appendLine(1, fmt.Sprintf("Built Go Version: %s", info.Golang))
+ detailBuffer.appendLine(1, fmt.Sprintf("Built GF Version: %s", info.GoFrame))
+ detailBuffer.appendLine(1, fmt.Sprintf("Git Commit: %s", info.Git))
+ detailBuffer.appendLine(1, fmt.Sprintf("Built Time: %s", info.Time))
+ }
+
+ detailBuffer.appendLine(0, "Others Detail:")
+ detailBuffer.appendLine(1, "Docs: https://goframe.org")
+ detailBuffer.appendLine(1, fmt.Sprintf("Now : %s", time.Now().Format(time.RFC3339)))
+
+ mlog.Print(detailBuffer.replaceAllIndent(" "))
+ return nil, nil
+}
+
+// detailBuffer is a buffer for detail information.
+type detailBuffer struct {
+ bytes.Buffer
+}
+
+// appendLine appends a line to the buffer with given indent level.
+func (d *detailBuffer) appendLine(indentLevel int, line string) {
+ d.WriteString(fmt.Sprintf("\n%s%s", strings.Repeat(defaultIndent, indentLevel), line))
+}
+
+// replaceAllIndent replaces the tab with given indent string and prints the buffer content.
+func (d *detailBuffer) replaceAllIndent(indentStr string) string {
+ return strings.ReplaceAll(d.String(), defaultIndent, indentStr)
+}
+
+// getGoFrameVersion returns the goframe version of current project using.
+func getGoFrameVersion(indentLevel int) (gfVersion string) {
+ pkgInfo, err := gproc.ShellExec(context.Background(), `go list -f "{{if (not .Main)}}{{.Path}}@{{.Version}}{{end}}" -m all`)
+ if err != nil {
+ return "cannot find go.mod"
+ }
+ pkgList := gstr.Split(pkgInfo, "\n")
+ for _, v := range pkgList {
+ if strings.HasPrefix(v, "github.com/gogf/gf") {
+ gfVersion += fmt.Sprintf("\n%s%s", strings.Repeat(defaultIndent, indentLevel), v)
+ }
+ }
+ return
+}
+
+// getGoVersion returns the go version
+func getGoVersion() (goVersion string, ok bool) {
+ goVersion, err := gproc.ShellExec(context.Background(), "go version")
+ if err != nil {
+ return "", false
+ }
+ goVersion = gstr.TrimLeftStr(goVersion, "go version ")
+ goVersion = gstr.TrimRightStr(goVersion, "\n")
+ return goVersion, true
+}
+
+// getGFVersionOfCurrentProject checks and returns the GoFrame version current project using.
+func (c cVersion) getGFVersionOfCurrentProject() (string, error) {
+ goModPath := gfile.Join(gfile.Pwd(), "go.mod")
+ if gfile.Exists(goModPath) {
+ lines := gstr.SplitAndTrim(gfile.GetContents(goModPath), "\n")
+ for _, line := range lines {
+ line = gstr.Trim(line)
+ line = gstr.TrimLeftStr(line, "require ")
+ line = gstr.Trim(line)
+ // Version 1.
+ match, err := gregex.MatchString(`^github\.com/gogf/gf\s+(.+)$`, line)
+ if err != nil {
+ return "", err
+ }
+ if len(match) <= 1 {
+ // Version > 1.
+ match, err = gregex.MatchString(`^github\.com/gogf/gf/v\d\s+(.+)$`, line)
+ if err != nil {
+ return "", err
+ }
+ }
+ if len(match) > 1 {
+ return gstr.Trim(match[1]), nil
+ }
+ }
+
+ return "", gerror.New("cannot find goframe requirement in go.mod")
+ } else {
+ return "", gerror.New("cannot find go.mod")
+ }
+}
diff --git a/server/internal/library/hggen/internal/cmd/genctrl/genctrl.go b/server/internal/library/hggen/internal/cmd/genctrl/genctrl.go
new file mode 100644
index 0000000..c62ff22
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/genctrl/genctrl.go
@@ -0,0 +1,238 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package genctrl
+
+import (
+ "context"
+
+ "github.com/gogf/gf/v2/container/gset"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gtime"
+ "github.com/gogf/gf/v2/util/gconv"
+ "github.com/gogf/gf/v2/util/gtag"
+
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+)
+
+const (
+ CGenCtrlConfig = `gfcli.gen.ctrl`
+ CGenCtrlUsage = `gf gen ctrl [OPTION]`
+ CGenCtrlBrief = `parse api definitions to generate controller/sdk go files`
+ CGenCtrlEg = `
+gf gen ctrl
+`
+ CGenCtrlBriefSrcFolder = `source folder path to be parsed. default: api`
+ CGenCtrlBriefDstFolder = `destination folder path storing automatically generated go files. default: internal/controller`
+ CGenCtrlBriefWatchFile = `used in file watcher, it re-generates go files only if given file is under srcFolder`
+ CGenCtrlBriefSdkPath = `also generate SDK go files for api definitions to specified directory`
+ CGenCtrlBriefSdkStdVersion = `use standard version prefix for generated sdk request path`
+ CGenCtrlBriefSdkNoV1 = `do not add version suffix for interface module name if version is v1`
+ CGenCtrlBriefClear = `auto delete generated and unimplemented controller go files if api definitions are missing`
+ CGenCtrlControllerMerge = `generate all controller files into one go file by name of api definition source go file`
+)
+
+const (
+ PatternCtrlDefinition = `func\s+\(.+?\)\s+\w+\(.+?\*(\w+)\.(\w+)Req\)\s+\(.+?\*(\w+)\.(\w+)Res,\s+\w+\s+error\)\s+{`
+)
+
+const (
+ genCtrlFileLockSeconds = 10
+)
+
+func init() {
+ gtag.Sets(g.MapStrStr{
+ `CGenCtrlConfig`: CGenCtrlConfig,
+ `CGenCtrlUsage`: CGenCtrlUsage,
+ `CGenCtrlBrief`: CGenCtrlBrief,
+ `CGenCtrlEg`: CGenCtrlEg,
+ `CGenCtrlBriefSrcFolder`: CGenCtrlBriefSrcFolder,
+ `CGenCtrlBriefDstFolder`: CGenCtrlBriefDstFolder,
+ `CGenCtrlBriefWatchFile`: CGenCtrlBriefWatchFile,
+ `CGenCtrlBriefSdkPath`: CGenCtrlBriefSdkPath,
+ `CGenCtrlBriefSdkStdVersion`: CGenCtrlBriefSdkStdVersion,
+ `CGenCtrlBriefSdkNoV1`: CGenCtrlBriefSdkNoV1,
+ `CGenCtrlBriefClear`: CGenCtrlBriefClear,
+ `CGenCtrlControllerMerge`: CGenCtrlControllerMerge,
+ })
+}
+
+type (
+ CGenCtrl struct{}
+ CGenCtrlInput struct {
+ g.Meta `name:"ctrl" config:"{CGenCtrlConfig}" usage:"{CGenCtrlUsage}" brief:"{CGenCtrlBrief}" eg:"{CGenCtrlEg}"`
+ SrcFolder string `short:"s" name:"srcFolder" brief:"{CGenCtrlBriefSrcFolder}" d:"api"`
+ DstFolder string `short:"d" name:"dstFolder" brief:"{CGenCtrlBriefDstFolder}" d:"internal/controller"`
+ WatchFile string `short:"w" name:"watchFile" brief:"{CGenCtrlBriefWatchFile}"`
+ SdkPath string `short:"k" name:"sdkPath" brief:"{CGenCtrlBriefSdkPath}"`
+ SdkStdVersion bool `short:"v" name:"sdkStdVersion" brief:"{CGenCtrlBriefSdkStdVersion}" orphan:"true"`
+ SdkNoV1 bool `short:"n" name:"sdkNoV1" brief:"{CGenCtrlBriefSdkNoV1}" orphan:"true"`
+ Clear bool `short:"c" name:"clear" brief:"{CGenCtrlBriefClear}" orphan:"true"`
+ Merge bool `short:"m" name:"merge" brief:"{CGenCtrlControllerMerge}" orphan:"true"`
+ }
+ CGenCtrlOutput struct{}
+)
+
+func (c CGenCtrl) Ctrl(ctx context.Context, in CGenCtrlInput) (out *CGenCtrlOutput, err error) {
+ if in.WatchFile != "" {
+ err = c.generateByWatchFile(
+ in.WatchFile, in.SdkPath, in.SdkStdVersion, in.SdkNoV1, in.Clear, in.Merge,
+ )
+ mlog.Print(`done!`)
+ return
+ }
+
+ if !gfile.Exists(in.SrcFolder) {
+ mlog.Fatalf(`source folder path "%s" does not exist`, in.SrcFolder)
+ }
+ // retrieve all api modules.
+ apiModuleFolderPaths, err := gfile.ScanDir(in.SrcFolder, "*", false)
+ if err != nil {
+ return nil, err
+ }
+ for _, apiModuleFolderPath := range apiModuleFolderPaths {
+ if !gfile.IsDir(apiModuleFolderPath) {
+ continue
+ }
+ // generate go files by api module.
+ var (
+ module = gfile.Basename(apiModuleFolderPath)
+ dstModuleFolderPath = gfile.Join(in.DstFolder, module)
+ )
+ err = c.generateByModule(
+ apiModuleFolderPath, dstModuleFolderPath, in.SdkPath,
+ in.SdkStdVersion, in.SdkNoV1, in.Clear, in.Merge,
+ )
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ mlog.Print(`done!`)
+ return
+}
+
+func (c CGenCtrl) generateByWatchFile(watchFile, sdkPath string, sdkStdVersion, sdkNoV1, clear, merge bool) (err error) {
+ // File lock to avoid multiple processes.
+ var (
+ flockFilePath = gfile.Temp("gf.cli.gen.service.lock")
+ flockContent = gfile.GetContents(flockFilePath)
+ )
+ if flockContent != "" {
+ if gtime.Timestamp()-gconv.Int64(flockContent) < genCtrlFileLockSeconds {
+ // If another generating process is running, it just exits.
+ mlog.Debug(`another "gen service" process is running, exit`)
+ return
+ }
+ }
+ defer gfile.RemoveFile(flockFilePath)
+ _ = gfile.PutContents(flockFilePath, gtime.TimestampStr())
+
+ // check this updated file is an api file.
+ // watch file should be in standard goframe project structure.
+ var (
+ apiVersionPath = gfile.Dir(watchFile)
+ apiModuleFolderPath = gfile.Dir(apiVersionPath)
+ shouldBeNameOfAPi = gfile.Basename(gfile.Dir(apiModuleFolderPath))
+ )
+ if shouldBeNameOfAPi != "api" {
+ return nil
+ }
+ // watch file should have api definitions.
+ if gfile.Exists(watchFile) {
+ structsInfo, err := c.getStructsNameInSrc(watchFile)
+ if err != nil {
+ return err
+ }
+ if len(structsInfo) == 0 {
+ return nil
+ }
+ }
+
+ var (
+ projectRootPath = gfile.Dir(gfile.Dir(apiModuleFolderPath))
+ module = gfile.Basename(apiModuleFolderPath)
+ dstModuleFolderPath = gfile.Join(projectRootPath, "internal", "controller", module)
+ )
+ return c.generateByModule(
+ apiModuleFolderPath, dstModuleFolderPath, sdkPath, sdkStdVersion, sdkNoV1, clear, merge,
+ )
+}
+
+// parseApiModule parses certain api and generate associated go files by certain module, not all api modules.
+func (c CGenCtrl) generateByModule(
+ apiModuleFolderPath, dstModuleFolderPath, sdkPath string,
+ sdkStdVersion, sdkNoV1, clear, merge bool,
+) (err error) {
+ // parse src and dst folder go files.
+ apiItemsInSrc, err := c.getApiItemsInSrc(apiModuleFolderPath)
+ if err != nil {
+ return err
+ }
+ apiItemsInDst, err := c.getApiItemsInDst(dstModuleFolderPath)
+ if err != nil {
+ return err
+ }
+
+ // generate api interface go files.
+ if err = newApiInterfaceGenerator().Generate(apiModuleFolderPath, apiItemsInSrc); err != nil {
+ return
+ }
+
+ // generate controller go files.
+ // api filtering for already implemented api controllers.
+ var (
+ alreadyImplementedCtrlSet = gset.NewStrSet()
+ toBeImplementedApiItems = make([]apiItem, 0)
+ )
+ for _, item := range apiItemsInDst {
+ alreadyImplementedCtrlSet.Add(item.String())
+ }
+ for _, item := range apiItemsInSrc {
+ if alreadyImplementedCtrlSet.Contains(item.String()) {
+ continue
+ }
+ toBeImplementedApiItems = append(toBeImplementedApiItems, item)
+ }
+ if len(toBeImplementedApiItems) > 0 {
+ err = newControllerGenerator().Generate(dstModuleFolderPath, toBeImplementedApiItems, merge)
+ if err != nil {
+ return
+ }
+ }
+
+ // delete unimplemented controllers if api definitions are missing.
+ if clear {
+ var (
+ apiDefinitionSet = gset.NewStrSet()
+ extraApiItemsInCtrl = make([]apiItem, 0)
+ )
+ for _, item := range apiItemsInSrc {
+ apiDefinitionSet.Add(item.String())
+ }
+ for _, item := range apiItemsInDst {
+ if apiDefinitionSet.Contains(item.String()) {
+ continue
+ }
+ extraApiItemsInCtrl = append(extraApiItemsInCtrl, item)
+ }
+ if len(extraApiItemsInCtrl) > 0 {
+ err = newControllerClearer().Clear(dstModuleFolderPath, extraApiItemsInCtrl)
+ if err != nil {
+ return
+ }
+ }
+ }
+
+ // generate sdk go files.
+ if sdkPath != "" {
+ if err = newApiSdkGenerator().Generate(apiItemsInSrc, sdkPath, sdkStdVersion, sdkNoV1); err != nil {
+ return
+ }
+ }
+ return
+}
diff --git a/server/internal/library/hggen/internal/cmd/genctrl/genctrl_api_item.go b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_api_item.go
new file mode 100644
index 0000000..c502e3e
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_api_item.go
@@ -0,0 +1,37 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package genctrl
+
+import (
+ "fmt"
+
+ "github.com/gogf/gf/v2/text/gstr"
+)
+
+type apiItem struct {
+ Import string `eg:"demo.com/api/user/v1"`
+ FileName string `eg:"user"`
+ Module string `eg:"user"`
+ Version string `eg:"v1"`
+ MethodName string `eg:"GetList"`
+ Comment string `eg:"GetList get list"`
+}
+
+func (a apiItem) String() string {
+ return gstr.Join([]string{
+ a.Import, a.Module, a.Version, a.MethodName,
+ }, ",")
+}
+
+// GetComment returns the comment of apiItem.
+func (a apiItem) GetComment() string {
+ if a.Comment == "" {
+ return ""
+ }
+ // format for handling comments
+ return fmt.Sprintf("\n// %s %s", a.MethodName, a.Comment)
+}
diff --git a/server/internal/library/hggen/internal/cmd/genctrl/genctrl_ast_parse.go b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_ast_parse.go
new file mode 100644
index 0000000..9f87bf5
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_ast_parse.go
@@ -0,0 +1,95 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package genctrl
+
+import (
+ "bytes"
+ "go/ast"
+ "go/parser"
+ "go/printer"
+ "go/token"
+
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/text/gstr"
+)
+
+type structInfo struct {
+ structName string
+ comment string
+}
+
+// getStructsNameInSrc retrieves all struct names and comment
+// that end in "Req" and have "g.Meta" in their body.
+func (c CGenCtrl) getStructsNameInSrc(filePath string) (structInfos []*structInfo, err error) {
+ var (
+ fileContent = gfile.GetContents(filePath)
+ fileSet = token.NewFileSet()
+ )
+
+ node, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments)
+ if err != nil {
+ return
+ }
+
+ ast.Inspect(node, func(n ast.Node) bool {
+ if typeSpec, ok := n.(*ast.TypeSpec); ok {
+ structName := typeSpec.Name.Name
+ if !gstr.HasSuffix(structName, "Req") {
+ // ignore struct name that do not end in "Req"
+ return true
+ }
+ if structType, ok := typeSpec.Type.(*ast.StructType); ok {
+ var buf bytes.Buffer
+ if err := printer.Fprint(&buf, fileSet, structType); err != nil {
+ return false
+ }
+ // ignore struct name that match a request, but has no g.Meta in its body.
+ if !gstr.Contains(buf.String(), `g.Meta`) {
+ return true
+ }
+
+ comment := typeSpec.Doc.Text()
+ // remove the struct name from the comment
+ if gstr.HasPrefix(comment, structName) {
+ comment = gstr.TrimLeftStr(comment, structName, 1)
+ }
+ // remove the comment \n or space
+ comment = gstr.Trim(comment)
+
+ structInfos = append(structInfos, &structInfo{
+ structName: structName,
+ comment: comment,
+ })
+ }
+ }
+ return true
+ })
+
+ return
+}
+
+// getImportsInDst retrieves all import paths in the file.
+func (c CGenCtrl) getImportsInDst(filePath string) (imports []string, err error) {
+ var (
+ fileContent = gfile.GetContents(filePath)
+ fileSet = token.NewFileSet()
+ )
+
+ node, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments)
+ if err != nil {
+ return
+ }
+
+ ast.Inspect(node, func(n ast.Node) bool {
+ if imp, ok := n.(*ast.ImportSpec); ok {
+ imports = append(imports, imp.Path.Value)
+ }
+ return true
+ })
+
+ return
+}
diff --git a/server/internal/library/hggen/internal/cmd/genctrl/genctrl_ast_parse_clear.go b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_ast_parse_clear.go
new file mode 100644
index 0000000..a1bd739
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_ast_parse_clear.go
@@ -0,0 +1,43 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package genctrl
+
+import (
+ "bytes"
+ "go/ast"
+ "go/parser"
+ "go/printer"
+ "go/token"
+
+ "github.com/gogf/gf/v2/os/gfile"
+)
+
+// getFuncInDst retrieves all function declarations and bodies in the file.
+func (c *controllerClearer) getFuncInDst(filePath string) (funcs []string, err error) {
+ var (
+ fileContent = gfile.GetContents(filePath)
+ fileSet = token.NewFileSet()
+ )
+
+ node, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments)
+ if err != nil {
+ return
+ }
+
+ ast.Inspect(node, func(n ast.Node) bool {
+ if fun, ok := n.(*ast.FuncDecl); ok {
+ var buf bytes.Buffer
+ if err := printer.Fprint(&buf, fileSet, fun); err != nil {
+ return false
+ }
+ funcs = append(funcs, buf.String())
+ }
+ return true
+ })
+
+ return
+}
diff --git a/server/internal/library/hggen/internal/cmd/genctrl/genctrl_calculate.go b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_calculate.go
new file mode 100644
index 0000000..62818a4
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_calculate.go
@@ -0,0 +1,137 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package genctrl
+
+import (
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/text/gregex"
+ "github.com/gogf/gf/v2/text/gstr"
+
+ "hotgo/internal/library/hggen/internal/utility/utils"
+)
+
+func (c CGenCtrl) getApiItemsInSrc(apiModuleFolderPath string) (items []apiItem, err error) {
+ var importPath string
+ // The second level folders: versions.
+ apiVersionFolderPaths, err := gfile.ScanDir(apiModuleFolderPath, "*", false)
+ if err != nil {
+ return nil, err
+ }
+ for _, apiVersionFolderPath := range apiVersionFolderPaths {
+ if !gfile.IsDir(apiVersionFolderPath) {
+ continue
+ }
+ // The second level folders: versions.
+ apiFileFolderPaths, err := gfile.ScanDir(apiVersionFolderPath, "*.go", false)
+ if err != nil {
+ return nil, err
+ }
+ importPath = utils.GetImportPath(apiVersionFolderPath)
+ for _, apiFileFolderPath := range apiFileFolderPaths {
+ if gfile.IsDir(apiFileFolderPath) {
+ continue
+ }
+ structsInfo, err := c.getStructsNameInSrc(apiFileFolderPath)
+ if err != nil {
+ return nil, err
+ }
+ for _, s := range structsInfo {
+ // remove end "Req"
+ methodName := gstr.TrimRightStr(s.structName, "Req", 1)
+ item := apiItem{
+ Import: gstr.Trim(importPath, `"`),
+ FileName: gfile.Name(apiFileFolderPath),
+ Module: gfile.Basename(apiModuleFolderPath),
+ Version: gfile.Basename(apiVersionFolderPath),
+ MethodName: methodName,
+ Comment: s.comment,
+ }
+ items = append(items, item)
+ }
+ }
+ }
+ return
+}
+
+func (c CGenCtrl) getApiItemsInDst(dstFolder string) (items []apiItem, err error) {
+ if !gfile.Exists(dstFolder) {
+ return nil, nil
+ }
+ type importItem struct {
+ Path string
+ Alias string
+ }
+ filePaths, err := gfile.ScanDir(dstFolder, "*.go", true)
+ if err != nil {
+ return nil, err
+ }
+ for _, filePath := range filePaths {
+ var (
+ array []string
+ importItems []importItem
+ importLines []string
+ module = gfile.Basename(gfile.Dir(filePath))
+ )
+ importLines, err = c.getImportsInDst(filePath)
+ if err != nil {
+ return nil, err
+ }
+
+ // retrieve all imports.
+ for _, importLine := range importLines {
+ array = gstr.SplitAndTrim(importLine, " ")
+ if len(array) == 2 {
+ importItems = append(importItems, importItem{
+ Path: gstr.Trim(array[1], `"`),
+ Alias: array[0],
+ })
+ } else {
+ importItems = append(importItems, importItem{
+ Path: gstr.Trim(array[0], `"`),
+ })
+ }
+ }
+ // retrieve all api usages.
+ // retrieve it without using AST, but use regular expressions to retrieve.
+ // It's because the api definition is simple and regular.
+ // Use regular expressions to get better performance.
+ fileContent := gfile.GetContents(filePath)
+ matches, err := gregex.MatchAllString(PatternCtrlDefinition, fileContent)
+ if err != nil {
+ return nil, err
+ }
+ for _, match := range matches {
+ // try to find the import path of the api.
+ var (
+ importPath string
+ version = match[1]
+ methodName = match[2] // not the function name, but the method name in api definition.
+ )
+ for _, item := range importItems {
+ if item.Alias != "" {
+ if item.Alias == version {
+ importPath = item.Path
+ break
+ }
+ continue
+ }
+ if gfile.Basename(item.Path) == version {
+ importPath = item.Path
+ break
+ }
+ }
+ item := apiItem{
+ Import: gstr.Trim(importPath, `"`),
+ Module: module,
+ Version: gfile.Basename(importPath),
+ MethodName: methodName,
+ }
+ items = append(items, item)
+ }
+ }
+ return
+}
diff --git a/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_ctrl.go b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_ctrl.go
new file mode 100644
index 0000000..08cb33e
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_ctrl.go
@@ -0,0 +1,231 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package genctrl
+
+import (
+ "fmt"
+ "path/filepath"
+ "strings"
+
+ "github.com/gogf/gf/v2/container/gset"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/text/gstr"
+
+ "hotgo/internal/library/hggen/internal/consts"
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+)
+
+type controllerGenerator struct{}
+
+func newControllerGenerator() *controllerGenerator {
+ return &controllerGenerator{}
+}
+
+func (c *controllerGenerator) Generate(dstModuleFolderPath string, apiModuleApiItems []apiItem, merge bool) (err error) {
+ var (
+ doneApiItemSet = gset.NewStrSet()
+ )
+ for _, item := range apiModuleApiItems {
+ if doneApiItemSet.Contains(item.String()) {
+ continue
+ }
+ // retrieve all api items of the same module.
+ var (
+ subItems = c.getSubItemsByModuleAndVersion(apiModuleApiItems, item.Module, item.Version)
+ importPath = gstr.Replace(gfile.Dir(item.Import), "\\", "/", -1)
+ )
+ if err = c.doGenerateCtrlNewByModuleAndVersion(
+ dstModuleFolderPath, item.Module, item.Version, importPath,
+ ); err != nil {
+ return
+ }
+
+ // use -merge
+ if merge {
+ err = c.doGenerateCtrlMergeItem(dstModuleFolderPath, subItems, doneApiItemSet)
+ continue
+ }
+
+ for _, subItem := range subItems {
+ err = c.doGenerateCtrlItem(dstModuleFolderPath, subItem)
+ if err != nil {
+ return
+ }
+ doneApiItemSet.Add(subItem.String())
+ }
+ }
+ return
+}
+
+func (c *controllerGenerator) getSubItemsByModuleAndVersion(items []apiItem, module, version string) (subItems []apiItem) {
+ for _, item := range items {
+ if item.Module == module && item.Version == version {
+ subItems = append(subItems, item)
+ }
+ }
+ return
+}
+
+func (c *controllerGenerator) doGenerateCtrlNewByModuleAndVersion(
+ dstModuleFolderPath, module, version, importPath string,
+) (err error) {
+ var (
+ moduleFilePath = filepath.FromSlash(gfile.Join(dstModuleFolderPath, module+".go"))
+ moduleFilePathNew = filepath.FromSlash(gfile.Join(dstModuleFolderPath, module+"_new.go"))
+ ctrlName = fmt.Sprintf(`Controller%s`, gstr.UcFirst(version))
+ interfaceName = fmt.Sprintf(`%s.I%s%s`, module, gstr.CaseCamel(module), gstr.UcFirst(version))
+ newFuncName = fmt.Sprintf(`New%s`, gstr.UcFirst(version))
+ newFuncNameDefinition = fmt.Sprintf(`func %s()`, newFuncName)
+ alreadyCreated bool
+ )
+ if !gfile.Exists(moduleFilePath) {
+ content := gstr.ReplaceByMap(consts.TemplateGenCtrlControllerEmpty, g.MapStrStr{
+ "{Module}": module,
+ })
+ if err = gfile.PutContents(moduleFilePath, gstr.TrimLeft(content)); err != nil {
+ return err
+ }
+ mlog.Printf(`generated: %s`, gfile.RealPath(moduleFilePath))
+ }
+ if !gfile.Exists(moduleFilePathNew) {
+ content := gstr.ReplaceByMap(consts.TemplateGenCtrlControllerNewEmpty, g.MapStrStr{
+ "{Module}": module,
+ "{ImportPath}": fmt.Sprintf(`"%s"`, importPath),
+ })
+ if err = gfile.PutContents(moduleFilePathNew, gstr.TrimLeft(content)); err != nil {
+ return err
+ }
+ mlog.Printf(`generated: %s`, gfile.RealPath(moduleFilePathNew))
+ }
+ filePaths, err := gfile.ScanDir(dstModuleFolderPath, "*.go", false)
+ if err != nil {
+ return err
+ }
+ for _, filePath := range filePaths {
+ if gstr.Contains(gfile.GetContents(filePath), newFuncNameDefinition) {
+ alreadyCreated = true
+ break
+ }
+ }
+ if !alreadyCreated {
+ content := gstr.ReplaceByMap(consts.TemplateGenCtrlControllerNewFunc, g.MapStrStr{
+ "{CtrlName}": ctrlName,
+ "{NewFuncName}": newFuncName,
+ "{InterfaceName}": interfaceName,
+ })
+ err = gfile.PutContentsAppend(moduleFilePathNew, content)
+ if err != nil {
+ return err
+ }
+ }
+ return
+}
+
+func (c *controllerGenerator) doGenerateCtrlItem(dstModuleFolderPath string, item apiItem) (err error) {
+ var (
+ methodNameSnake = gstr.CaseSnake(item.MethodName)
+ ctrlName = fmt.Sprintf(`Controller%s`, gstr.UcFirst(item.Version))
+ methodFilePath = filepath.FromSlash(gfile.Join(dstModuleFolderPath, fmt.Sprintf(
+ `%s_%s_%s.go`, item.Module, item.Version, methodNameSnake,
+ )))
+ )
+ var content string
+
+ if gfile.Exists(methodFilePath) {
+ content = gstr.ReplaceByMap(consts.TemplateGenCtrlControllerMethodFuncMerge, g.MapStrStr{
+ "{Module}": item.Module,
+ "{CtrlName}": ctrlName,
+ "{Version}": item.Version,
+ "{MethodName}": item.MethodName,
+ "{MethodComment}": item.GetComment(),
+ })
+
+ if gstr.Contains(gfile.GetContents(methodFilePath), fmt.Sprintf(`func (c *%v) %v(`, ctrlName, item.MethodName)) {
+ return
+ }
+ if err = gfile.PutContentsAppend(methodFilePath, gstr.TrimLeft(content)); err != nil {
+ return err
+ }
+ } else {
+ content = gstr.ReplaceByMap(consts.TemplateGenCtrlControllerMethodFunc, g.MapStrStr{
+ "{Module}": item.Module,
+ "{ImportPath}": item.Import,
+ "{CtrlName}": ctrlName,
+ "{Version}": item.Version,
+ "{MethodName}": item.MethodName,
+ "{MethodComment}": item.GetComment(),
+ })
+ if err = gfile.PutContents(methodFilePath, gstr.TrimLeft(content)); err != nil {
+ return err
+ }
+ }
+ mlog.Printf(`generated: %s`, gfile.RealPath(methodFilePath))
+ return
+}
+
+// use -merge
+func (c *controllerGenerator) doGenerateCtrlMergeItem(dstModuleFolderPath string, apiItems []apiItem, doneApiSet *gset.StrSet) (err error) {
+
+ type controllerFileItem struct {
+ module string
+ version string
+ importPath string
+ // Each ctrlFileItem has multiple CTRLs
+ controllers strings.Builder
+ }
+ // It is possible that there are multiple files under one module
+ ctrlFileItemMap := make(map[string]*controllerFileItem)
+
+ for _, api := range apiItems {
+ ctrlFileItem, found := ctrlFileItemMap[api.FileName]
+ if !found {
+ ctrlFileItem = &controllerFileItem{
+ module: api.Module,
+ version: api.Version,
+ controllers: strings.Builder{},
+ importPath: api.Import,
+ }
+ ctrlFileItemMap[api.FileName] = ctrlFileItem
+ }
+
+ ctrl := gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlControllerMethodFuncMerge, g.MapStrStr{
+ "{Module}": api.Module,
+ "{CtrlName}": fmt.Sprintf(`Controller%s`, gstr.UcFirst(api.Version)),
+ "{Version}": api.Version,
+ "{MethodName}": api.MethodName,
+ "{MethodComment}": api.GetComment(),
+ }))
+ ctrlFileItem.controllers.WriteString(ctrl)
+ doneApiSet.Add(api.String())
+ }
+
+ for ctrlFileName, ctrlFileItem := range ctrlFileItemMap {
+ ctrlFilePath := gfile.Join(dstModuleFolderPath, fmt.Sprintf(
+ `%s_%s_%s.go`, ctrlFileItem.module, ctrlFileItem.version, ctrlFileName,
+ ))
+
+ // This logic is only followed when a new ctrlFileItem is generated
+ // Most of the rest of the time, the following logic is followed
+ if !gfile.Exists(ctrlFilePath) {
+ ctrlFileHeader := gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlControllerHeader, g.MapStrStr{
+ "{Module}": ctrlFileItem.module,
+ "{ImportPath}": ctrlFileItem.importPath,
+ }))
+ err = gfile.PutContents(ctrlFilePath, ctrlFileHeader)
+ if err != nil {
+ return err
+ }
+ }
+
+ if err = gfile.PutContentsAppend(ctrlFilePath, ctrlFileItem.controllers.String()); err != nil {
+ return err
+ }
+ mlog.Printf(`generated: %s`, gfile.RealPath(ctrlFilePath))
+ }
+ return
+}
diff --git a/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_ctrl_clear.go b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_ctrl_clear.go
new file mode 100644
index 0000000..b3fbcc8
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_ctrl_clear.go
@@ -0,0 +1,57 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package genctrl
+
+import (
+ "fmt"
+
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/text/gstr"
+
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+)
+
+type controllerClearer struct{}
+
+func newControllerClearer() *controllerClearer {
+ return &controllerClearer{}
+}
+
+func (c *controllerClearer) Clear(dstModuleFolderPath string, extraApiItemsInCtrl []apiItem) (err error) {
+ for _, item := range extraApiItemsInCtrl {
+ if err = c.doClear(dstModuleFolderPath, item); err != nil {
+ return err
+ }
+ }
+ return
+}
+
+func (c *controllerClearer) doClear(dstModuleFolderPath string, item apiItem) (err error) {
+ var (
+ methodNameSnake = gstr.CaseSnake(item.MethodName)
+ methodFilePath = gfile.Join(dstModuleFolderPath, fmt.Sprintf(
+ `%s_%s_%s.go`, item.Module, item.Version, methodNameSnake,
+ ))
+ )
+
+ funcs, err := c.getFuncInDst(methodFilePath)
+ if err != nil {
+ return err
+ }
+
+ if len(funcs) > 1 {
+ // One line.
+ if !gstr.Contains(funcs[0], "\n") && gstr.Contains(funcs[0], `CodeNotImplemented`) {
+ mlog.Printf(
+ `remove unimplemented and of no api definitions controller file: %s`,
+ methodFilePath,
+ )
+ err = gfile.RemoveFile(methodFilePath)
+ }
+ }
+ return
+}
diff --git a/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_interface.go b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_interface.go
new file mode 100644
index 0000000..a6cc5eb
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_interface.go
@@ -0,0 +1,118 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package genctrl
+
+import (
+ "fmt"
+ "path/filepath"
+
+ "github.com/gogf/gf/v2/container/gmap"
+ "github.com/gogf/gf/v2/container/gset"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/text/gstr"
+ "github.com/gogf/gf/v2/util/gconv"
+
+ "hotgo/internal/library/hggen/internal/consts"
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+ "hotgo/internal/library/hggen/internal/utility/utils"
+)
+
+type apiInterfaceGenerator struct{}
+
+func newApiInterfaceGenerator() *apiInterfaceGenerator {
+ return &apiInterfaceGenerator{}
+}
+
+func (c *apiInterfaceGenerator) Generate(apiModuleFolderPath string, apiModuleApiItems []apiItem) (err error) {
+ if len(apiModuleApiItems) == 0 {
+ return nil
+ }
+ var firstApiItem = apiModuleApiItems[0]
+ if err = c.doGenerate(apiModuleFolderPath, firstApiItem.Module, apiModuleApiItems); err != nil {
+ return
+ }
+ return
+}
+
+func (c *apiInterfaceGenerator) doGenerate(apiModuleFolderPath string, module string, items []apiItem) (err error) {
+ var (
+ moduleFilePath = filepath.FromSlash(gfile.Join(apiModuleFolderPath, fmt.Sprintf(`%s.go`, module)))
+ importPathMap = gmap.NewListMap()
+ importPaths []string
+ )
+ // if there's already exist file that with the same but not auto generated go file,
+ // it uses another file name.
+ if !utils.IsFileDoNotEdit(moduleFilePath) {
+ moduleFilePath = filepath.FromSlash(gfile.Join(apiModuleFolderPath, fmt.Sprintf(`%s.if.go`, module)))
+ }
+ // all import paths.
+ importPathMap.Set("\t"+`"context"`+"\n", 1)
+ for _, item := range items {
+ importPathMap.Set(fmt.Sprintf("\t"+`"%s"`, item.Import), 1)
+ }
+ importPaths = gconv.Strings(importPathMap.Keys())
+ // interface definitions.
+ var (
+ doneApiItemSet = gset.NewStrSet()
+ interfaceDefinition string
+ interfaceContent = gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlApiInterface, g.MapStrStr{
+ "{Module}": module,
+ "{ImportPaths}": gstr.Join(importPaths, "\n"),
+ }))
+ )
+ for _, item := range items {
+ if doneApiItemSet.Contains(item.String()) {
+ continue
+ }
+ // retrieve all api items of the same module.
+ subItems := c.getSubItemsByModuleAndVersion(items, item.Module, item.Version)
+ var (
+ method string
+ methods = make([]string, 0)
+ interfaceName = fmt.Sprintf(`I%s%s`, gstr.CaseCamel(item.Module), gstr.UcFirst(item.Version))
+ )
+ for _, subItem := range subItems {
+ method = fmt.Sprintf(
+ "\t%s(ctx context.Context, req *%s.%sReq) (res *%s.%sRes, err error)",
+ subItem.MethodName, subItem.Version, subItem.MethodName, subItem.Version, subItem.MethodName,
+ )
+ methods = append(methods, method)
+ doneApiItemSet.Add(subItem.String())
+ }
+ interfaceDefinition += fmt.Sprintf("type %s interface {", interfaceName)
+ interfaceDefinition += "\n"
+ interfaceDefinition += gstr.Join(methods, "\n")
+ interfaceDefinition += "\n"
+ interfaceDefinition += fmt.Sprintf("}")
+ interfaceDefinition += "\n\n"
+ }
+ interfaceContent = gstr.TrimLeft(gstr.ReplaceByMap(interfaceContent, g.MapStrStr{
+ "{Interfaces}": gstr.TrimRightStr(interfaceDefinition, "\n", 2),
+ }))
+ err = gfile.PutContents(moduleFilePath, interfaceContent)
+ mlog.Printf(`generated: %s`, gfile.RealPath(moduleFilePath))
+ return
+}
+
+func (c *apiInterfaceGenerator) getSubItemsByModule(items []apiItem, module string) (subItems []apiItem) {
+ for _, item := range items {
+ if item.Module == module {
+ subItems = append(subItems, item)
+ }
+ }
+ return
+}
+
+func (c *apiInterfaceGenerator) getSubItemsByModuleAndVersion(items []apiItem, module, version string) (subItems []apiItem) {
+ for _, item := range items {
+ if item.Module == module && item.Version == version {
+ subItems = append(subItems, item)
+ }
+ }
+ return
+}
diff --git a/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_sdk.go b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_sdk.go
new file mode 100644
index 0000000..17bb64a
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_sdk.go
@@ -0,0 +1,199 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package genctrl
+
+import (
+ "fmt"
+ "path/filepath"
+
+ "github.com/gogf/gf/v2/container/gset"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/text/gregex"
+ "github.com/gogf/gf/v2/text/gstr"
+
+ "hotgo/internal/library/hggen/internal/consts"
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+)
+
+type apiSdkGenerator struct{}
+
+func newApiSdkGenerator() *apiSdkGenerator {
+ return &apiSdkGenerator{}
+}
+
+func (c *apiSdkGenerator) Generate(apiModuleApiItems []apiItem, sdkFolderPath string, sdkStdVersion, sdkNoV1 bool) (err error) {
+ if err = c.doGenerateSdkPkgFile(sdkFolderPath); err != nil {
+ return
+ }
+
+ var doneApiItemSet = gset.NewStrSet()
+ for _, item := range apiModuleApiItems {
+ if doneApiItemSet.Contains(item.String()) {
+ continue
+ }
+ // retrieve all api items of the same module.
+ subItems := c.getSubItemsByModuleAndVersion(apiModuleApiItems, item.Module, item.Version)
+ if err = c.doGenerateSdkIClient(sdkFolderPath, item.Import, item.Module, item.Version, sdkNoV1); err != nil {
+ return
+ }
+ if err = c.doGenerateSdkImplementer(
+ subItems, sdkFolderPath, item.Import, item.Module, item.Version, sdkStdVersion, sdkNoV1,
+ ); err != nil {
+ return
+ }
+ for _, subItem := range subItems {
+ doneApiItemSet.Add(subItem.String())
+ }
+ }
+ return
+}
+
+func (c *apiSdkGenerator) doGenerateSdkPkgFile(sdkFolderPath string) (err error) {
+ var (
+ pkgName = gfile.Basename(sdkFolderPath)
+ pkgFilePath = filepath.FromSlash(gfile.Join(sdkFolderPath, fmt.Sprintf(`%s.go`, pkgName)))
+ fileContent string
+ )
+ if gfile.Exists(pkgFilePath) {
+ return nil
+ }
+ fileContent = gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlSdkPkgNew, g.MapStrStr{
+ "{PkgName}": pkgName,
+ }))
+ err = gfile.PutContents(pkgFilePath, fileContent)
+ mlog.Printf(`generated: %s`, gfile.RealPath(pkgFilePath))
+ return
+}
+
+func (c *apiSdkGenerator) doGenerateSdkIClient(
+ sdkFolderPath, versionImportPath, module, version string, sdkNoV1 bool,
+) (err error) {
+ var (
+ fileContent string
+ isDirty bool
+ isExist bool
+ pkgName = gfile.Basename(sdkFolderPath)
+ funcName = gstr.CaseCamel(module) + gstr.UcFirst(version)
+ interfaceName = fmt.Sprintf(`I%s`, funcName)
+ moduleImportPath = gstr.Replace(fmt.Sprintf(`"%s"`, gfile.Dir(versionImportPath)), "\\", "/", -1)
+ iClientFilePath = filepath.FromSlash(gfile.Join(sdkFolderPath, fmt.Sprintf(`%s.iclient.go`, pkgName)))
+ interfaceFuncDefinition = fmt.Sprintf(
+ `%s() %s.%s`,
+ gstr.CaseCamel(module)+gstr.UcFirst(version), module, interfaceName,
+ )
+ )
+ if sdkNoV1 && version == "v1" {
+ interfaceFuncDefinition = fmt.Sprintf(
+ `%s() %s.%s`,
+ gstr.CaseCamel(module), module, interfaceName,
+ )
+ }
+ if isExist = gfile.Exists(iClientFilePath); isExist {
+ fileContent = gfile.GetContents(iClientFilePath)
+ } else {
+ fileContent = gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlSdkIClient, g.MapStrStr{
+ "{PkgName}": pkgName,
+ }))
+ }
+
+ // append the import path to current import paths.
+ if !gstr.Contains(fileContent, moduleImportPath) {
+ isDirty = true
+ // It is without using AST, because it is from a template.
+ fileContent, err = gregex.ReplaceString(
+ `(import \([\s\S]*?)\)`,
+ fmt.Sprintf("$1\t%s\n)", moduleImportPath),
+ fileContent,
+ )
+ if err != nil {
+ return
+ }
+ }
+
+ // append the function definition to interface definition.
+ if !gstr.Contains(fileContent, interfaceFuncDefinition) {
+ isDirty = true
+ // It is without using AST, because it is from a template.
+ fileContent, err = gregex.ReplaceString(
+ `(type IClient interface {[\s\S]*?)}`,
+ fmt.Sprintf("$1\t%s\n}", interfaceFuncDefinition),
+ fileContent,
+ )
+ if err != nil {
+ return
+ }
+ }
+ if isDirty {
+ err = gfile.PutContents(iClientFilePath, fileContent)
+ if isExist {
+ mlog.Printf(`updated: %s`, gfile.RealPath(iClientFilePath))
+ } else {
+ mlog.Printf(`generated: %s`, gfile.RealPath(iClientFilePath))
+ }
+ }
+ return
+}
+
+func (c *apiSdkGenerator) doGenerateSdkImplementer(
+ items []apiItem, sdkFolderPath, versionImportPath, module, version string, sdkStdVersion, sdkNoV1 bool,
+) (err error) {
+ var (
+ pkgName = gfile.Basename(sdkFolderPath)
+ moduleNameCamel = gstr.CaseCamel(module)
+ moduleNameSnake = gstr.CaseSnake(module)
+ moduleImportPath = gstr.Replace(gfile.Dir(versionImportPath), "\\", "/", -1)
+ versionPrefix = ""
+ implementerName = moduleNameCamel + gstr.UcFirst(version)
+ implementerFilePath = filepath.FromSlash(gfile.Join(sdkFolderPath, fmt.Sprintf(
+ `%s_%s_%s.go`, pkgName, moduleNameSnake, version,
+ )))
+ )
+ if sdkNoV1 && version == "v1" {
+ implementerName = moduleNameCamel
+ }
+ // implementer file template.
+ var importPaths = make([]string, 0)
+ importPaths = append(importPaths, fmt.Sprintf("\t\"%s\"", moduleImportPath))
+ importPaths = append(importPaths, fmt.Sprintf("\t\"%s\"", versionImportPath))
+ implementerFileContent := gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlSdkImplementer, g.MapStrStr{
+ "{PkgName}": pkgName,
+ "{ImportPaths}": gstr.Join(importPaths, "\n"),
+ "{ImplementerName}": implementerName,
+ }))
+ // implementer new function definition.
+ if sdkStdVersion {
+ versionPrefix = fmt.Sprintf(`/api/%s`, version)
+ }
+ implementerFileContent += gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlSdkImplementerNew, g.MapStrStr{
+ "{Module}": module,
+ "{VersionPrefix}": versionPrefix,
+ "{ImplementerName}": implementerName,
+ }))
+ // implementer functions definitions.
+ for _, item := range items {
+ implementerFileContent += gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlSdkImplementerFunc, g.MapStrStr{
+ "{Version}": item.Version,
+ "{MethodName}": item.MethodName,
+ "{ImplementerName}": implementerName,
+ "{MethodComment}": item.GetComment(),
+ }))
+ implementerFileContent += "\n"
+ }
+ err = gfile.PutContents(implementerFilePath, implementerFileContent)
+ mlog.Printf(`generated: %s`, gfile.RealPath(implementerFilePath))
+ return
+}
+
+func (c *apiSdkGenerator) getSubItemsByModuleAndVersion(items []apiItem, module, version string) (subItems []apiItem) {
+ for _, item := range items {
+ if item.Module == module && item.Version == version {
+ subItems = append(subItems, item)
+ }
+ }
+ return
+}
diff --git a/server/internal/library/hggen/internal/cmd/gendao/gendao.go b/server/internal/library/hggen/internal/cmd/gendao/gendao.go
new file mode 100644
index 0000000..1c01c39
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/gendao/gendao.go
@@ -0,0 +1,413 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gendao
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "github.com/olekukonko/tablewriter"
+ "github.com/olekukonko/tablewriter/renderer"
+ "github.com/olekukonko/tablewriter/tw"
+ "golang.org/x/mod/modfile"
+
+ "github.com/gogf/gf/v2/container/garray"
+ "github.com/gogf/gf/v2/container/gset"
+ "github.com/gogf/gf/v2/database/gdb"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gproc"
+ "github.com/gogf/gf/v2/os/gtime"
+ "github.com/gogf/gf/v2/os/gview"
+ "github.com/gogf/gf/v2/text/gregex"
+ "github.com/gogf/gf/v2/text/gstr"
+
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+ "hotgo/internal/library/hggen/internal/utility/utils"
+)
+
+type (
+ CGenDao struct{}
+ CGenDaoInput struct {
+ g.Meta `name:"dao" config:"{CGenDaoConfig}" usage:"{CGenDaoUsage}" brief:"{CGenDaoBrief}" eg:"{CGenDaoEg}" ad:"{CGenDaoAd}"`
+ Path string `name:"path" short:"p" brief:"{CGenDaoBriefPath}" d:"internal"`
+ Link string `name:"link" short:"l" brief:"{CGenDaoBriefLink}"`
+ Tables string `name:"tables" short:"t" brief:"{CGenDaoBriefTables}"`
+ TablesEx string `name:"tablesEx" short:"x" brief:"{CGenDaoBriefTablesEx}"`
+ ShardingPattern []string `name:"shardingPattern" short:"sp" brief:"{CGenDaoBriefShardingPattern}"`
+ Group string `name:"group" short:"g" brief:"{CGenDaoBriefGroup}" d:"default"`
+ Prefix string `name:"prefix" short:"f" brief:"{CGenDaoBriefPrefix}"`
+ RemovePrefix string `name:"removePrefix" short:"r" brief:"{CGenDaoBriefRemovePrefix}"`
+ RemoveFieldPrefix string `name:"removeFieldPrefix" short:"rf" brief:"{CGenDaoBriefRemoveFieldPrefix}"`
+ JsonCase string `name:"jsonCase" short:"j" brief:"{CGenDaoBriefJsonCase}" d:"CamelLower"`
+ ImportPrefix string `name:"importPrefix" short:"i" brief:"{CGenDaoBriefImportPrefix}"`
+ DaoPath string `name:"daoPath" short:"d" brief:"{CGenDaoBriefDaoPath}" d:"dao"`
+ TablePath string `name:"tablePath" short:"tp" brief:"{CGenDaoBriefTablePath}" d:"table"`
+ DoPath string `name:"doPath" short:"o" brief:"{CGenDaoBriefDoPath}" d:"model/do"`
+ EntityPath string `name:"entityPath" short:"e" brief:"{CGenDaoBriefEntityPath}" d:"model/entity"`
+ TplDaoTablePath string `name:"tplDaoTablePath" short:"t0" brief:"{CGenDaoBriefTplDaoTablePath}"`
+ TplDaoIndexPath string `name:"tplDaoIndexPath" short:"t1" brief:"{CGenDaoBriefTplDaoIndexPath}"`
+ TplDaoInternalPath string `name:"tplDaoInternalPath" short:"t2" brief:"{CGenDaoBriefTplDaoInternalPath}"`
+ TplDaoDoPath string `name:"tplDaoDoPath" short:"t3" brief:"{CGenDaoBriefTplDaoDoPathPath}"`
+ TplDaoEntityPath string `name:"tplDaoEntityPath" short:"t4" brief:"{CGenDaoBriefTplDaoEntityPath}"`
+ StdTime bool `name:"stdTime" short:"s" brief:"{CGenDaoBriefStdTime}" orphan:"true"`
+ WithTime bool `name:"withTime" short:"w" brief:"{CGenDaoBriefWithTime}" orphan:"true"`
+ GJsonSupport bool `name:"gJsonSupport" short:"n" brief:"{CGenDaoBriefGJsonSupport}" orphan:"true"`
+ OverwriteDao bool `name:"overwriteDao" short:"v" brief:"{CGenDaoBriefOverwriteDao}" orphan:"true"`
+ DescriptionTag bool `name:"descriptionTag" short:"c" brief:"{CGenDaoBriefDescriptionTag}" orphan:"true"`
+ NoJsonTag bool `name:"noJsonTag" short:"k" brief:"{CGenDaoBriefNoJsonTag}" orphan:"true"`
+ NoModelComment bool `name:"noModelComment" short:"m" brief:"{CGenDaoBriefNoModelComment}" orphan:"true"`
+ Clear bool `name:"clear" short:"a" brief:"{CGenDaoBriefClear}" orphan:"true"`
+ GenTable bool `name:"genTable" short:"gt" brief:"{CGenDaoBriefGenTable}" orphan:"true"`
+
+ TypeMapping map[DBFieldTypeName]CustomAttributeType `name:"typeMapping" short:"y" brief:"{CGenDaoBriefTypeMapping}" orphan:"true"`
+ FieldMapping map[DBTableFieldName]CustomAttributeType `name:"fieldMapping" short:"fm" brief:"{CGenDaoBriefFieldMapping}" orphan:"true"`
+
+ // internal usage purpose.
+ genItems *CGenDaoInternalGenItems
+ }
+ CGenDaoOutput struct{}
+
+ CGenDaoInternalInput struct {
+ CGenDaoInput
+ DB gdb.DB
+ TableNames []string
+ NewTableNames []string
+ ShardingTableSet *gset.StrSet
+ }
+ DBTableFieldName = string
+ DBFieldTypeName = string
+ CustomAttributeType struct {
+ Type string `brief:"custom attribute type name"`
+ Import string `brief:"custom import for this type"`
+ }
+)
+
+var (
+ createdAt = gtime.Now()
+ tplView = gview.New()
+ defaultTypeMapping = map[DBFieldTypeName]CustomAttributeType{
+ "decimal": {
+ Type: "float64",
+ },
+ "money": {
+ Type: "float64",
+ },
+ "numeric": {
+ Type: "float64",
+ },
+ "smallmoney": {
+ Type: "float64",
+ },
+ }
+
+ // tablewriter Options
+ twRenderer = tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{
+ Borders: tw.Border{Top: tw.Off, Bottom: tw.Off, Left: tw.Off, Right: tw.Off},
+ Settings: tw.Settings{
+ Separators: tw.Separators{BetweenRows: tw.Off, BetweenColumns: tw.Off},
+ },
+ Symbols: tw.NewSymbols(tw.StyleASCII),
+ }))
+ twConfig = tablewriter.WithConfig(tablewriter.Config{
+ Row: tw.CellConfig{
+ Formatting: tw.CellFormatting{AutoWrap: tw.WrapNone},
+ },
+ })
+)
+
+func (c CGenDao) Dao(ctx context.Context, in CGenDaoInput) (out *CGenDaoOutput, err error) {
+ in.genItems = newCGenDaoInternalGenItems()
+ if in.Link != "" {
+ doGenDaoForArray(ctx, -1, in)
+ } else if g.Cfg().Available(ctx) {
+ v := g.Cfg().MustGet(ctx, CGenDaoConfig)
+ if v.IsSlice() {
+ for i := 0; i < len(v.Interfaces()); i++ {
+ doGenDaoForArray(ctx, i, in)
+ }
+ } else {
+ doGenDaoForArray(ctx, -1, in)
+ }
+ } else {
+ doGenDaoForArray(ctx, -1, in)
+ }
+ doClear(in.genItems)
+ mlog.Print("done!")
+ return
+}
+
+// doGenDaoForArray implements the "gen dao" command for configuration array.
+func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
+ if in.genItems == nil {
+ in.genItems = newCGenDaoInternalGenItems()
+ }
+
+ var (
+ err error
+ db gdb.DB
+ )
+ if index >= 0 {
+ err = g.Cfg().MustGet(
+ ctx,
+ fmt.Sprintf(`%s.%d`, CGenDaoConfig, index),
+ ).Scan(&in)
+ if err != nil {
+ mlog.Fatalf(`invalid configuration of "%s": %+v`, CGenDaoConfig, err)
+ }
+ }
+ if dirRealPath := gfile.RealPath(in.Path); dirRealPath == "" {
+ mlog.Fatalf(`path "%s" does not exist`, in.Path)
+ }
+ removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",")
+
+ // It uses user passed database configuration.
+ if in.Link != "" {
+ var tempGroup = gtime.TimestampNanoStr()
+ err = gdb.AddConfigNode(tempGroup, gdb.ConfigNode{
+ Link: in.Link,
+ })
+ if err != nil {
+ mlog.Fatalf(`database configuration failed: %+v`, err)
+ }
+ if db, err = gdb.Instance(tempGroup); err != nil {
+ mlog.Fatalf(`database initialization failed: %+v`, err)
+ }
+ } else {
+ db = g.DB(in.Group)
+ }
+ if db == nil {
+ mlog.Fatal(`database initialization failed, may be invalid database configuration`)
+ }
+
+ var tableNames []string
+ if in.Tables != "" {
+ tableNames = gstr.SplitAndTrim(in.Tables, ",")
+ } else {
+ tableNames, err = db.Tables(context.TODO())
+ if err != nil {
+ mlog.Fatalf("fetching tables failed: %+v", err)
+ }
+ }
+ // Table excluding.
+ if in.TablesEx != "" {
+ array := garray.NewStrArrayFrom(tableNames)
+ for _, p := range gstr.SplitAndTrim(in.TablesEx, ",") {
+ if gstr.Contains(p, "*") || gstr.Contains(p, "?") {
+ p = gstr.ReplaceByMap(p, map[string]string{
+ "\r": "",
+ "\n": "",
+ })
+ p = gstr.ReplaceByMap(p, map[string]string{
+ "*": "\r",
+ "?": "\n",
+ })
+ p = gregex.Quote(p)
+ p = gstr.ReplaceByMap(p, map[string]string{
+ "\r": ".*",
+ "\n": ".",
+ })
+ for _, v := range array.Clone().Slice() {
+ if gregex.IsMatchString(p, v) {
+ array.RemoveValue(v)
+ }
+ }
+ } else {
+ array.RemoveValue(p)
+ }
+ }
+ tableNames = array.Slice()
+ }
+
+ // merge default typeMapping to input typeMapping.
+ if in.TypeMapping == nil {
+ in.TypeMapping = defaultTypeMapping
+ } else {
+ for key, typeMapping := range defaultTypeMapping {
+ if _, ok := in.TypeMapping[key]; !ok {
+ in.TypeMapping[key] = typeMapping
+ }
+ }
+ }
+
+ // Generating dao & model go files one by one according to given table name.
+ var (
+ newTableNames = make([]string, len(tableNames))
+ shardingNewTableSet = gset.NewStrSet()
+ )
+ for i, tableName := range tableNames {
+ newTableName := tableName
+ for _, v := range removePrefixArray {
+ newTableName = gstr.TrimLeftStr(newTableName, v, 1)
+ }
+ if len(in.ShardingPattern) > 0 {
+ for _, pattern := range in.ShardingPattern {
+ var (
+ match []string
+ regPattern = gstr.Replace(pattern, "?", `(.+)`)
+ )
+ match, err = gregex.MatchString(regPattern, newTableName)
+ if err != nil {
+ mlog.Fatalf(`invalid sharding pattern "%s": %+v`, pattern, err)
+ }
+ if len(match) < 2 {
+ continue
+ }
+ newTableName = gstr.Replace(pattern, "?", "")
+ newTableName = gstr.Trim(newTableName, `_.-`)
+ if shardingNewTableSet.Contains(newTableName) {
+ tableNames[i] = ""
+ continue
+ }
+ // Add prefix to sharding table name, if not, the isSharding check would not match.
+ shardingNewTableSet.Add(in.Prefix + newTableName)
+ }
+ }
+ newTableName = in.Prefix + newTableName
+ if tableNames[i] != "" {
+ // If shardingNewTableSet contains newTableName (tableName is empty), it should not be added to tableNames, make it empty and filter later.
+ newTableNames[i] = newTableName
+ }
+ }
+ tableNames = garray.NewStrArrayFrom(tableNames).FilterEmpty().Slice()
+ newTableNames = garray.NewStrArrayFrom(newTableNames).FilterEmpty().Slice() // Filter empty table names. make sure that newTableNames and tableNames have the same length.
+ in.genItems.Scale()
+
+ // Dao: index and internal.
+ generateDao(ctx, CGenDaoInternalInput{
+ CGenDaoInput: in,
+ DB: db,
+ TableNames: tableNames,
+ NewTableNames: newTableNames,
+ ShardingTableSet: shardingNewTableSet,
+ })
+ // Table: table fields.
+ generateTable(ctx, CGenDaoInternalInput{
+ CGenDaoInput: in,
+ DB: db,
+ TableNames: tableNames,
+ NewTableNames: newTableNames,
+ ShardingTableSet: shardingNewTableSet,
+ })
+ // Do.
+ generateDo(ctx, CGenDaoInternalInput{
+ CGenDaoInput: in,
+ DB: db,
+ TableNames: tableNames,
+ NewTableNames: newTableNames,
+ })
+ // Entity.
+ generateEntity(ctx, CGenDaoInternalInput{
+ CGenDaoInput: in,
+ DB: db,
+ TableNames: tableNames,
+ NewTableNames: newTableNames,
+ })
+
+ in.genItems.SetClear(in.Clear)
+}
+
+func getImportPartContent(ctx context.Context, source string, isDo bool, appendImports []string) string {
+ var packageImportsArray = garray.NewStrArray()
+ if isDo {
+ packageImportsArray.Append(`"github.com/gogf/gf/v2/frame/g"`)
+ }
+
+ // Time package recognition.
+ if strings.Contains(source, "gtime.Time") {
+ packageImportsArray.Append(`"github.com/gogf/gf/v2/os/gtime"`)
+ } else if strings.Contains(source, "time.Time") {
+ packageImportsArray.Append(`"time"`)
+ }
+
+ // Json type.
+ if strings.Contains(source, "gjson.Json") {
+ packageImportsArray.Append(`"github.com/gogf/gf/v2/encoding/gjson"`)
+ }
+
+ // Check and update imports in go.mod
+ if len(appendImports) > 0 {
+ goModPath := utils.GetModPath()
+ if goModPath == "" {
+ mlog.Fatal("go.mod not found in current project")
+ }
+ mod, err := modfile.Parse(goModPath, gfile.GetBytes(goModPath), nil)
+ if err != nil {
+ mlog.Fatalf("parse go.mod failed: %+v", err)
+ }
+ for _, appendImport := range appendImports {
+ found := false
+ for _, require := range mod.Require {
+ if gstr.Contains(appendImport, require.Mod.Path) {
+ found = true
+ break
+ }
+ }
+ if !found {
+ if err = gproc.ShellRun(ctx, `go get `+appendImport); err != nil {
+ mlog.Fatalf(`%+v`, err)
+ }
+ }
+ packageImportsArray.Append(fmt.Sprintf(`"%s"`, appendImport))
+ }
+ }
+
+ // Generate and write content to golang file.
+ packageImportsStr := ""
+ if packageImportsArray.Len() > 0 {
+ packageImportsStr = fmt.Sprintf("import(\n%s\n)", packageImportsArray.Join("\n"))
+ }
+ return packageImportsStr
+}
+
+func assignDefaultVar(view *gview.View, in CGenDaoInternalInput) {
+ var (
+ tplCreatedAtDatetimeStr string
+ tplDatetimeStr = createdAt.String()
+ )
+ if in.WithTime {
+ tplCreatedAtDatetimeStr = fmt.Sprintf(`Created at %s`, tplDatetimeStr)
+ }
+ view.Assigns(g.Map{
+ tplVarDatetimeStr: tplDatetimeStr,
+ tplVarCreatedAtDatetimeStr: tplCreatedAtDatetimeStr,
+ })
+}
+
+func sortFieldKeyForDao(fieldMap map[string]*gdb.TableField) []string {
+ names := make(map[int]string)
+ for _, field := range fieldMap {
+ names[field.Index] = field.Name
+ }
+ var (
+ i = 0
+ j = 0
+ result = make([]string, len(names))
+ )
+ for {
+ if len(names) == 0 {
+ break
+ }
+ if val, ok := names[i]; ok {
+ result[j] = val
+ j++
+ delete(names, i)
+ }
+ i++
+ }
+ return result
+}
+
+func getTemplateFromPathOrDefault(filePath string, def string) string {
+ if filePath != "" {
+ if contents := gfile.GetContents(filePath); contents != "" {
+ return contents
+ }
+ }
+ return def
+}
diff --git a/server/internal/library/hggen/internal/cmd/gendao/gendao_clear.go b/server/internal/library/hggen/internal/cmd/gendao/gendao_clear.go
new file mode 100644
index 0000000..ae297e0
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/gendao/gendao_clear.go
@@ -0,0 +1,48 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gendao
+
+import (
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/text/gstr"
+
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+)
+
+func doClear(items *CGenDaoInternalGenItems) {
+ var allGeneratedFilePaths = make([]string, 0)
+ for _, item := range items.Items {
+ allGeneratedFilePaths = append(allGeneratedFilePaths, item.GeneratedFilePaths...)
+ }
+ for i, v := range allGeneratedFilePaths {
+ allGeneratedFilePaths[i] = gfile.RealPath(v)
+ }
+ for _, item := range items.Items {
+ if !item.Clear {
+ continue
+ }
+ doClearItem(item, allGeneratedFilePaths)
+ }
+}
+
+func doClearItem(item CGenDaoInternalGenItem, allGeneratedFilePaths []string) {
+ var generatedFilePaths = make([]string, 0)
+ for _, dirPath := range item.StorageDirPaths {
+ filePaths, err := gfile.ScanDirFile(dirPath, "*.go", true)
+ if err != nil {
+ mlog.Fatal(err)
+ }
+ generatedFilePaths = append(generatedFilePaths, filePaths...)
+ }
+ for _, filePath := range generatedFilePaths {
+ if !gstr.InArray(allGeneratedFilePaths, filePath) {
+ if err := gfile.RemoveFile(filePath); err != nil {
+ mlog.Print(err)
+ }
+ }
+ }
+}
diff --git a/server/internal/library/hggen/internal/cmd/gendao/gendao_dao.go b/server/internal/library/hggen/internal/cmd/gendao/gendao_dao.go
new file mode 100644
index 0000000..d1ebe85
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/gendao/gendao_dao.go
@@ -0,0 +1,260 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gendao
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "path/filepath"
+ "strings"
+
+ "github.com/olekukonko/tablewriter"
+
+ "github.com/gogf/gf/v2/database/gdb"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gview"
+ "github.com/gogf/gf/v2/text/gstr"
+
+ "hotgo/internal/library/hggen/internal/consts"
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+ "hotgo/internal/library/hggen/internal/utility/utils"
+)
+
+func generateDao(ctx context.Context, in CGenDaoInternalInput) {
+ var (
+ dirPathDao = gfile.Join(in.Path, in.DaoPath)
+ dirPathDaoInternal = gfile.Join(dirPathDao, "internal")
+ )
+ in.genItems.AppendDirPath(dirPathDao)
+ for i := 0; i < len(in.TableNames); i++ {
+ var (
+ realTableName = in.TableNames[i]
+ newTableName = in.NewTableNames[i]
+ )
+ generateDaoSingle(ctx, generateDaoSingleInput{
+ CGenDaoInternalInput: in,
+ TableName: realTableName,
+ NewTableName: newTableName,
+ DirPathDao: dirPathDao,
+ DirPathDaoInternal: dirPathDaoInternal,
+ IsSharding: in.ShardingTableSet.Contains(newTableName),
+ })
+ }
+}
+
+type generateDaoSingleInput struct {
+ CGenDaoInternalInput
+ // TableName specifies the table name of the table.
+ TableName string
+ // NewTableName specifies the prefix-stripped or custom edited name of the table.
+ NewTableName string
+ DirPathDao string
+ DirPathDaoInternal string
+ IsSharding bool
+}
+
+// generateDaoSingle generates the dao and model content of given table.
+func generateDaoSingle(ctx context.Context, in generateDaoSingleInput) {
+ // Generating table data preparing.
+ fieldMap, err := in.DB.TableFields(ctx, in.TableName)
+ if err != nil {
+ mlog.Fatalf(`fetching tables fields failed for table "%s": %+v`, in.TableName, err)
+ }
+ var (
+ tableNameCamelCase = formatFieldName(in.NewTableName, FieldNameCaseCamel)
+ tableNameCamelLowerCase = formatFieldName(in.NewTableName, FieldNameCaseCamelLower)
+ tableNameSnakeCase = gstr.CaseSnake(in.NewTableName)
+ importPrefix = in.ImportPrefix
+ )
+ if importPrefix == "" {
+ importPrefix = utils.GetImportPath(gfile.Join(in.Path, in.DaoPath))
+ } else {
+ importPrefix = gstr.Join(g.SliceStr{importPrefix, in.DaoPath}, "/")
+ }
+
+ fileName := gstr.Trim(tableNameSnakeCase, "-_.")
+ if len(fileName) > 5 && fileName[len(fileName)-5:] == "_test" {
+ // Add suffix to avoid the table name which contains "_test",
+ // which would make the go file a testing file.
+ fileName += "_table"
+ }
+
+ // dao - index
+ generateDaoIndex(generateDaoIndexInput{
+ generateDaoSingleInput: in,
+ TableNameCamelCase: tableNameCamelCase,
+ TableNameCamelLowerCase: tableNameCamelLowerCase,
+ ImportPrefix: importPrefix,
+ FileName: fileName,
+ })
+
+ // dao - internal
+ generateDaoInternal(generateDaoInternalInput{
+ generateDaoSingleInput: in,
+ TableNameCamelCase: tableNameCamelCase,
+ TableNameCamelLowerCase: tableNameCamelLowerCase,
+ ImportPrefix: importPrefix,
+ FileName: fileName,
+ FieldMap: fieldMap,
+ })
+}
+
+type generateDaoIndexInput struct {
+ generateDaoSingleInput
+ TableNameCamelCase string
+ TableNameCamelLowerCase string
+ ImportPrefix string
+ FileName string
+}
+
+func generateDaoIndex(in generateDaoIndexInput) {
+ path := filepath.FromSlash(gfile.Join(in.DirPathDao, in.FileName+".go"))
+ // It should add path to result slice whenever it would generate the path file or not.
+ in.genItems.AppendGeneratedFilePath(path)
+ if in.OverwriteDao || !gfile.Exists(path) {
+ var (
+ ctx = context.Background()
+ tplContent = getTemplateFromPathOrDefault(
+ in.TplDaoIndexPath, consts.TemplateGenDaoIndexContent,
+ )
+ )
+ tplView.ClearAssigns()
+ tplView.Assigns(gview.Params{
+ tplVarTableSharding: in.IsSharding,
+ tplVarTableShardingPrefix: in.NewTableName + "_",
+ tplVarImportPrefix: in.ImportPrefix,
+ tplVarTableName: in.TableName,
+ tplVarTableNameCamelCase: in.TableNameCamelCase,
+ tplVarTableNameCamelLowerCase: in.TableNameCamelLowerCase,
+ tplVarPackageName: filepath.Base(in.DaoPath),
+ })
+ indexContent, err := tplView.ParseContent(ctx, tplContent)
+ if err != nil {
+ mlog.Fatalf("parsing template content failed: %v", err)
+ }
+ if err = gfile.PutContents(path, strings.TrimSpace(indexContent)); err != nil {
+ mlog.Fatalf("writing content to '%s' failed: %v", path, err)
+ } else {
+ utils.GoFmt(path)
+ mlog.Print("generated:", gfile.RealPath(path))
+ }
+ }
+}
+
+type generateDaoInternalInput struct {
+ generateDaoSingleInput
+ TableNameCamelCase string
+ TableNameCamelLowerCase string
+ ImportPrefix string
+ FileName string
+ FieldMap map[string]*gdb.TableField
+}
+
+func generateDaoInternal(in generateDaoInternalInput) {
+ var (
+ ctx = context.Background()
+ removeFieldPrefixArray = gstr.SplitAndTrim(in.RemoveFieldPrefix, ",")
+ tplContent = getTemplateFromPathOrDefault(
+ in.TplDaoInternalPath, consts.TemplateGenDaoInternalContent,
+ )
+ )
+ tplView.ClearAssigns()
+ tplView.Assigns(gview.Params{
+ tplVarImportPrefix: in.ImportPrefix,
+ tplVarTableName: in.TableName,
+ tplVarGroupName: in.Group,
+ tplVarTableNameCamelCase: in.TableNameCamelCase,
+ tplVarTableNameCamelLowerCase: in.TableNameCamelLowerCase,
+ tplVarColumnDefine: gstr.Trim(generateColumnDefinitionForDao(in.FieldMap, removeFieldPrefixArray)),
+ tplVarColumnNames: gstr.Trim(generateColumnNamesForDao(in.FieldMap, removeFieldPrefixArray)),
+ })
+ assignDefaultVar(tplView, in.CGenDaoInternalInput)
+ modelContent, err := tplView.ParseContent(ctx, tplContent)
+ if err != nil {
+ mlog.Fatalf("parsing template content failed: %v", err)
+ }
+ path := filepath.FromSlash(gfile.Join(in.DirPathDaoInternal, in.FileName+".go"))
+ in.genItems.AppendGeneratedFilePath(path)
+ if err := gfile.PutContents(path, strings.TrimSpace(modelContent)); err != nil {
+ mlog.Fatalf("writing content to '%s' failed: %v", path, err)
+ } else {
+ utils.GoFmt(path)
+ mlog.Print("generated:", gfile.RealPath(path))
+ }
+}
+
+// generateColumnNamesForDao generates and returns the column names assignment content of column struct
+// for specified table.
+func generateColumnNamesForDao(fieldMap map[string]*gdb.TableField, removeFieldPrefixArray []string) string {
+ var (
+ buffer = bytes.NewBuffer(nil)
+ array = make([][]string, len(fieldMap))
+ names = sortFieldKeyForDao(fieldMap)
+ )
+
+ for index, name := range names {
+ field := fieldMap[name]
+
+ newFiledName := field.Name
+ for _, v := range removeFieldPrefixArray {
+ newFiledName = gstr.TrimLeftStr(newFiledName, v, 1)
+ }
+
+ array[index] = []string{
+ " #" + formatFieldName(newFiledName, FieldNameCaseCamel) + ":",
+ fmt.Sprintf(` #"%s",`, field.Name),
+ }
+ }
+ table := tablewriter.NewTable(buffer, twRenderer, twConfig)
+ table.Bulk(array)
+ table.Render()
+ namesContent := buffer.String()
+ // Let's do this hack of table writer for indent!
+ namesContent = gstr.Replace(namesContent, " #", "")
+ buffer.Reset()
+ buffer.WriteString(namesContent)
+ return buffer.String()
+}
+
+// generateColumnDefinitionForDao generates and returns the column names definition for specified table.
+func generateColumnDefinitionForDao(fieldMap map[string]*gdb.TableField, removeFieldPrefixArray []string) string {
+ var (
+ buffer = bytes.NewBuffer(nil)
+ array = make([][]string, len(fieldMap))
+ names = sortFieldKeyForDao(fieldMap)
+ )
+
+ for index, name := range names {
+ var (
+ field = fieldMap[name]
+ comment = gstr.Trim(gstr.ReplaceByArray(field.Comment, g.SliceStr{
+ "\n", " ",
+ "\r", " ",
+ }))
+ )
+ newFiledName := field.Name
+ for _, v := range removeFieldPrefixArray {
+ newFiledName = gstr.TrimLeftStr(newFiledName, v, 1)
+ }
+ array[index] = []string{
+ " #" + formatFieldName(newFiledName, FieldNameCaseCamel),
+ " # " + "string",
+ " #" + fmt.Sprintf(`// %s`, comment),
+ }
+ }
+ table := tablewriter.NewTable(buffer, twRenderer, twConfig)
+ table.Bulk(array)
+ table.Render()
+ defineContent := buffer.String()
+ // Let's do this hack of table writer for indent!
+ defineContent = gstr.Replace(defineContent, " #", "")
+ buffer.Reset()
+ buffer.WriteString(defineContent)
+ return buffer.String()
+}
diff --git a/server/internal/library/hggen/internal/cmd/gendao/gendao_do.go b/server/internal/library/hggen/internal/cmd/gendao/gendao_do.go
new file mode 100644
index 0000000..fd296cf
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/gendao/gendao_do.go
@@ -0,0 +1,100 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gendao
+
+import (
+ "context"
+ "fmt"
+ "path/filepath"
+ "strings"
+
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gview"
+ "github.com/gogf/gf/v2/text/gregex"
+ "github.com/gogf/gf/v2/text/gstr"
+
+ "hotgo/internal/library/hggen/internal/consts"
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+ "hotgo/internal/library/hggen/internal/utility/utils"
+)
+
+func generateDo(ctx context.Context, in CGenDaoInternalInput) {
+ var dirPathDo = filepath.FromSlash(gfile.Join(in.Path, in.DoPath))
+ in.genItems.AppendDirPath(dirPathDo)
+ in.NoJsonTag = true
+ in.DescriptionTag = false
+ in.NoModelComment = false
+ // Model content.
+ for i, tableName := range in.TableNames {
+ fieldMap, err := in.DB.TableFields(ctx, tableName)
+ if err != nil {
+ mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", tableName, err)
+ }
+ var (
+ newTableName = in.NewTableNames[i]
+ doFilePath = gfile.Join(dirPathDo, gstr.CaseSnake(newTableName)+".go")
+ structDefinition, _ = generateStructDefinition(ctx, generateStructDefinitionInput{
+ CGenDaoInternalInput: in,
+ TableName: tableName,
+ StructName: formatFieldName(newTableName, FieldNameCaseCamel),
+ FieldMap: fieldMap,
+ IsDo: true,
+ })
+ )
+ // replace all types to any.
+ structDefinition, _ = gregex.ReplaceStringFuncMatch(
+ "([A-Z]\\w*?)\\s+([\\w\\*\\.]+?)\\s+(//)",
+ structDefinition,
+ func(match []string) string {
+ // If the type is already a pointer/slice/map, it does nothing.
+ if !gstr.HasPrefix(match[2], "*") && !gstr.HasPrefix(match[2], "[]") && !gstr.HasPrefix(match[2], "map") {
+ return fmt.Sprintf(`%s any %s`, match[1], match[3])
+ }
+ return match[0]
+ },
+ )
+ modelContent := generateDoContent(
+ ctx,
+ in,
+ tableName,
+ formatFieldName(newTableName, FieldNameCaseCamel),
+ structDefinition,
+ )
+ in.genItems.AppendGeneratedFilePath(doFilePath)
+ err = gfile.PutContents(doFilePath, strings.TrimSpace(modelContent))
+ if err != nil {
+ mlog.Fatalf(`writing content to "%s" failed: %v`, doFilePath, err)
+ } else {
+ utils.GoFmt(doFilePath)
+ mlog.Print("generated:", gfile.RealPath(doFilePath))
+ }
+ }
+}
+
+func generateDoContent(
+ ctx context.Context, in CGenDaoInternalInput, tableName, tableNameCamelCase, structDefine string,
+) string {
+ var (
+ tplContent = getTemplateFromPathOrDefault(
+ in.TplDaoDoPath, consts.TemplateGenDaoDoContent,
+ )
+ )
+ tplView.ClearAssigns()
+ tplView.Assigns(gview.Params{
+ tplVarTableName: tableName,
+ tplVarPackageImports: getImportPartContent(ctx, structDefine, true, nil),
+ tplVarTableNameCamelCase: tableNameCamelCase,
+ tplVarStructDefine: structDefine,
+ tplVarPackageName: filepath.Base(in.DoPath),
+ })
+ assignDefaultVar(tplView, in)
+ doContent, err := tplView.ParseContent(ctx, tplContent)
+ if err != nil {
+ mlog.Fatalf("parsing template content failed: %v", err)
+ }
+ return doContent
+}
diff --git a/server/internal/library/hggen/internal/cmd/gendao/gendao_entity.go b/server/internal/library/hggen/internal/cmd/gendao/gendao_entity.go
new file mode 100644
index 0000000..5eeafe7
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/gendao/gendao_entity.go
@@ -0,0 +1,85 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gendao
+
+import (
+ "context"
+ "path/filepath"
+ "strings"
+
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gview"
+ "github.com/gogf/gf/v2/text/gstr"
+
+ "hotgo/internal/library/hggen/internal/consts"
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+ "hotgo/internal/library/hggen/internal/utility/utils"
+)
+
+func generateEntity(ctx context.Context, in CGenDaoInternalInput) {
+ var dirPathEntity = gfile.Join(in.Path, in.EntityPath)
+ in.genItems.AppendDirPath(dirPathEntity)
+ // Model content.
+ for i, tableName := range in.TableNames {
+ fieldMap, err := in.DB.TableFields(ctx, tableName)
+ if err != nil {
+ mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", tableName, err)
+ }
+
+ var (
+ newTableName = in.NewTableNames[i]
+ entityFilePath = filepath.FromSlash(gfile.Join(dirPathEntity, gstr.CaseSnake(newTableName)+".go"))
+ structDefinition, appendImports = generateStructDefinition(ctx, generateStructDefinitionInput{
+ CGenDaoInternalInput: in,
+ TableName: tableName,
+ StructName: formatFieldName(newTableName, FieldNameCaseCamel),
+ FieldMap: fieldMap,
+ IsDo: false,
+ })
+ entityContent = generateEntityContent(
+ ctx,
+ in,
+ newTableName,
+ formatFieldName(newTableName, FieldNameCaseCamel),
+ structDefinition,
+ appendImports,
+ )
+ )
+ in.genItems.AppendGeneratedFilePath(entityFilePath)
+ err = gfile.PutContents(entityFilePath, strings.TrimSpace(entityContent))
+ if err != nil {
+ mlog.Fatalf("writing content to '%s' failed: %v", entityFilePath, err)
+ } else {
+ utils.GoFmt(entityFilePath)
+ mlog.Print("generated:", gfile.RealPath(entityFilePath))
+ }
+ }
+}
+
+func generateEntityContent(
+ ctx context.Context, in CGenDaoInternalInput, tableName, tableNameCamelCase, structDefine string, appendImports []string,
+) string {
+ var (
+ tplContent = getTemplateFromPathOrDefault(
+ in.TplDaoEntityPath, consts.TemplateGenDaoEntityContent,
+ )
+ )
+ tplView.ClearAssigns()
+ tplView.Assigns(gview.Params{
+ tplVarTableName: tableName,
+ tplVarPackageImports: getImportPartContent(ctx, structDefine, false, appendImports),
+ tplVarTableNameCamelCase: tableNameCamelCase,
+ tplVarStructDefine: structDefine,
+ tplVarPackageName: filepath.Base(in.EntityPath),
+ })
+ assignDefaultVar(tplView, in)
+ entityContent, err := tplView.ParseContent(ctx, tplContent)
+ if err != nil {
+ mlog.Fatalf("parsing template content failed: %v", err)
+ }
+ return entityContent
+}
diff --git a/server/internal/library/hggen/internal/cmd/gendao/gendao_gen_item.go b/server/internal/library/hggen/internal/cmd/gendao/gendao_gen_item.go
new file mode 100644
index 0000000..fcb7bf9
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/gendao/gendao_gen_item.go
@@ -0,0 +1,53 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gendao
+
+type (
+ CGenDaoInternalGenItems struct {
+ index int
+ Items []CGenDaoInternalGenItem
+ }
+ CGenDaoInternalGenItem struct {
+ Clear bool
+ StorageDirPaths []string
+ GeneratedFilePaths []string
+ }
+)
+
+func newCGenDaoInternalGenItems() *CGenDaoInternalGenItems {
+ return &CGenDaoInternalGenItems{
+ index: -1,
+ Items: make([]CGenDaoInternalGenItem, 0),
+ }
+}
+
+func (i *CGenDaoInternalGenItems) Scale() {
+ i.Items = append(i.Items, CGenDaoInternalGenItem{
+ StorageDirPaths: make([]string, 0),
+ GeneratedFilePaths: make([]string, 0),
+ Clear: false,
+ })
+ i.index++
+}
+
+func (i *CGenDaoInternalGenItems) SetClear(clear bool) {
+ i.Items[i.index].Clear = clear
+}
+
+func (i *CGenDaoInternalGenItems) AppendDirPath(storageDirPath string) {
+ i.Items[i.index].StorageDirPaths = append(
+ i.Items[i.index].StorageDirPaths,
+ storageDirPath,
+ )
+}
+
+func (i *CGenDaoInternalGenItems) AppendGeneratedFilePath(generatedFilePath string) {
+ i.Items[i.index].GeneratedFilePaths = append(
+ i.Items[i.index].GeneratedFilePaths,
+ generatedFilePath,
+ )
+}
diff --git a/server/internal/library/hggen/internal/cmd/gendao/gendao_structure.go b/server/internal/library/hggen/internal/cmd/gendao/gendao_structure.go
new file mode 100644
index 0000000..9508489
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/gendao/gendao_structure.go
@@ -0,0 +1,229 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gendao
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "strings"
+
+ "github.com/olekukonko/tablewriter"
+
+ "github.com/gogf/gf/v2/database/gdb"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/text/gregex"
+ "github.com/gogf/gf/v2/text/gstr"
+)
+
+type generateStructDefinitionInput struct {
+ CGenDaoInternalInput
+ TableName string // Table name.
+ StructName string // Struct name.
+ FieldMap map[string]*gdb.TableField // Table field map.
+ IsDo bool // Is generating DTO struct.
+}
+
+func generateStructDefinition(ctx context.Context, in generateStructDefinitionInput) (string, []string) {
+ var appendImports []string
+ buffer := bytes.NewBuffer(nil)
+ array := make([][]string, len(in.FieldMap))
+ names := sortFieldKeyForDao(in.FieldMap)
+ for index, name := range names {
+ var imports string
+ field := in.FieldMap[name]
+ array[index], imports = generateStructFieldDefinition(ctx, field, in)
+ if imports != "" {
+ appendImports = append(appendImports, imports)
+ }
+ }
+ table := tablewriter.NewTable(buffer, twRenderer, twConfig)
+ table.Bulk(array)
+ table.Render()
+ stContent := buffer.String()
+ // Let's do this hack of table writer for indent!
+ stContent = gstr.Replace(stContent, " #", "")
+ stContent = gstr.Replace(stContent, "` ", "`")
+ stContent = gstr.Replace(stContent, "``", "")
+ buffer.Reset()
+ fmt.Fprintf(buffer, "type %s struct {\n", in.StructName)
+ if in.IsDo {
+ fmt.Fprintf(buffer, "g.Meta `orm:\"table:%s, do:true\"`\n", in.TableName)
+ }
+ buffer.WriteString(stContent)
+ buffer.WriteString("}")
+ return buffer.String(), appendImports
+}
+
+func getTypeMappingInfo(
+ ctx context.Context, fieldType string, inTypeMapping map[DBFieldTypeName]CustomAttributeType,
+) (typeNameStr, importStr string) {
+ if typeMapping, ok := inTypeMapping[strings.ToLower(fieldType)]; ok {
+ typeNameStr = typeMapping.Type
+ importStr = typeMapping.Import
+ return
+ }
+ tryTypeMatch, _ := gregex.MatchString(`(.+?)\(([^\(\)]+)\)([\s\)]*)`, fieldType)
+ var (
+ tryTypeName string
+ moreTry bool
+ )
+ if len(tryTypeMatch) == 4 {
+ tryTypeMatch3, _ := gregex.ReplaceString(`\s+`, "", tryTypeMatch[3])
+ tryTypeName = gstr.Trim(tryTypeMatch[1]) + tryTypeMatch3
+ moreTry = tryTypeMatch3 != ""
+ } else {
+ tryTypeName = gstr.Split(fieldType, " ")[0]
+ }
+ if tryTypeName != "" {
+ if typeMapping, ok := inTypeMapping[strings.ToLower(tryTypeName)]; ok {
+ typeNameStr = typeMapping.Type
+ importStr = typeMapping.Import
+ } else if moreTry {
+ typeNameStr, importStr = getTypeMappingInfo(ctx, tryTypeName, inTypeMapping)
+ }
+ }
+ return
+}
+
+// generateStructFieldDefinition generates and returns the attribute definition for specified field.
+func generateStructFieldDefinition(
+ ctx context.Context, field *gdb.TableField, in generateStructDefinitionInput,
+) (attrLines []string, appendImport string) {
+ var (
+ err error
+ localTypeName gdb.LocalType
+ localTypeNameStr string
+ jsonTag = gstr.CaseConvert(field.Name, gstr.CaseTypeMatch(in.JsonCase))
+ )
+
+ if in.TypeMapping != nil && len(in.TypeMapping) > 0 {
+ localTypeNameStr, appendImport = getTypeMappingInfo(ctx, field.Type, in.TypeMapping)
+ }
+
+ if localTypeNameStr == "" {
+ localTypeName, err = in.DB.CheckLocalTypeForField(ctx, field.Type, nil)
+ if err != nil {
+ panic(err)
+ }
+ localTypeNameStr = string(localTypeName)
+ switch localTypeName {
+ case gdb.LocalTypeDate, gdb.LocalTypeTime, gdb.LocalTypeDatetime:
+ if in.StdTime {
+ localTypeNameStr = "time.Time"
+ } else {
+ localTypeNameStr = "*gtime.Time"
+ }
+
+ case gdb.LocalTypeInt64Bytes:
+ localTypeNameStr = "int64"
+
+ case gdb.LocalTypeUint64Bytes:
+ localTypeNameStr = "uint64"
+
+ // Special type handle.
+ case gdb.LocalTypeJson, gdb.LocalTypeJsonb:
+ if in.GJsonSupport {
+ localTypeNameStr = "*gjson.Json"
+ } else {
+ localTypeNameStr = "string"
+ }
+ }
+ }
+
+ var (
+ tagKey = "`"
+ descriptionTag = gstr.Replace(formatComment(field.Comment), `"`, `\"`)
+ )
+ removeFieldPrefixArray := gstr.SplitAndTrim(in.RemoveFieldPrefix, ",")
+ newFiledName := field.Name
+ for _, v := range removeFieldPrefixArray {
+ newFiledName = gstr.TrimLeftStr(newFiledName, v, 1)
+ }
+
+ if in.FieldMapping != nil && len(in.FieldMapping) > 0 {
+ if typeMapping, ok := in.FieldMapping[fmt.Sprintf("%s.%s", in.TableName, newFiledName)]; ok {
+ localTypeNameStr = typeMapping.Type
+ appendImport = typeMapping.Import
+ }
+ }
+
+ attrLines = []string{
+ " #" + formatFieldName(newFiledName, FieldNameCaseCamel),
+ " #" + localTypeNameStr,
+ }
+ 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 {
+ v, _ = gregex.ReplaceString(`json:".+"`, ``, v)
+ }
+ if !in.DescriptionTag {
+ v, _ = gregex.ReplaceString(`description:".*"`, ``, v)
+ }
+ if in.NoModelComment {
+ v, _ = gregex.ReplaceString(`//.+`, ``, v)
+ }
+ attrLines[k] = v
+ }
+ return attrLines, appendImport
+}
+
+type FieldNameCase string
+
+const (
+ FieldNameCaseCamel FieldNameCase = "CaseCamel"
+ FieldNameCaseCamelLower FieldNameCase = "CaseCamelLower"
+)
+
+// formatFieldName formats and returns a new field name that is used for golang codes generating.
+func formatFieldName(fieldName string, nameCase FieldNameCase) string {
+ // For normal databases like mysql, pgsql, sqlite,
+ // field/table names of that are in normal case.
+ var newFieldName = fieldName
+ if isAllUpper(fieldName) {
+ // For special databases like dm, oracle,
+ // field/table names of that are in upper case.
+ newFieldName = strings.ToLower(fieldName)
+ }
+ switch nameCase {
+ case FieldNameCaseCamel:
+ return gstr.CaseCamel(newFieldName)
+ case FieldNameCaseCamelLower:
+ return gstr.CaseCamelLower(newFieldName)
+ default:
+ return ""
+ }
+}
+
+// isAllUpper checks and returns whether given `fieldName` all letters are upper case.
+func isAllUpper(fieldName string) bool {
+ for _, b := range fieldName {
+ if b >= 'a' && b <= 'z' {
+ return false
+ }
+ }
+ return true
+}
+
+// formatComment formats the comment string to fit the golang code without any lines.
+func formatComment(comment string) string {
+ comment = gstr.ReplaceByArray(comment, g.SliceStr{
+ "\n", " ",
+ "\r", " ",
+ })
+ comment = gstr.Replace(comment, `\n`, " ")
+ comment = gstr.Trim(comment)
+ return comment
+}
diff --git a/server/internal/library/hggen/internal/cmd/gendao/gendao_table.go b/server/internal/library/hggen/internal/cmd/gendao/gendao_table.go
new file mode 100644
index 0000000..f6587b0
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/gendao/gendao_table.go
@@ -0,0 +1,147 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gendao
+
+import (
+ "bytes"
+ "context"
+ "path/filepath"
+ "sort"
+ "strconv"
+ "strings"
+
+ "github.com/gogf/gf/v2/database/gdb"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gview"
+ "github.com/gogf/gf/v2/text/gstr"
+ "github.com/gogf/gf/v2/util/gconv"
+
+ "hotgo/internal/library/hggen/internal/consts"
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+ "hotgo/internal/library/hggen/internal/utility/utils"
+)
+
+// generateTable generates dao files for given tables.
+func generateTable(ctx context.Context, in CGenDaoInternalInput) {
+ dirPathTable := gfile.Join(in.Path, in.TablePath)
+ if !in.GenTable {
+ if gfile.Exists(dirPathTable) {
+ in.genItems.AppendDirPath(dirPathTable)
+ }
+ return
+ }
+ in.genItems.AppendDirPath(dirPathTable)
+ for i := 0; i < len(in.TableNames); i++ {
+ var (
+ realTableName = in.TableNames[i]
+ newTableName = in.NewTableNames[i]
+ )
+ generateTableSingle(ctx, generateTableSingleInput{
+ CGenDaoInternalInput: in,
+ TableName: realTableName,
+ NewTableName: newTableName,
+ DirPathTable: dirPathTable,
+ })
+ }
+}
+
+// generateTableSingleInput is the input parameter for generateTableSingle.
+type generateTableSingleInput struct {
+ CGenDaoInternalInput
+ // TableName specifies the table name of the table.
+ TableName string
+ // NewTableName specifies the prefix-stripped or custom edited name of the table.
+ NewTableName string
+ DirPathTable string
+}
+
+// generateTableSingle generates dao files for a single table.
+func generateTableSingle(ctx context.Context, in generateTableSingleInput) {
+ // Generating table data preparing.
+ fieldMap, err := in.DB.TableFields(ctx, in.TableName)
+ if err != nil {
+ mlog.Fatalf(`fetching tables fields failed for table "%s": %+v`, in.TableName, err)
+ }
+
+ tableNameSnakeCase := gstr.CaseSnake(in.NewTableName)
+ fileName := gstr.Trim(tableNameSnakeCase, "-_.")
+ if len(fileName) > 5 && fileName[len(fileName)-5:] == "_test" {
+ // Add suffix to avoid the table name which contains "_test",
+ // which would make the go file a testing file.
+ fileName += "_table"
+ }
+ path := filepath.FromSlash(gfile.Join(in.DirPathTable, fileName+".go"))
+ in.genItems.AppendGeneratedFilePath(path)
+ if in.OverwriteDao || !gfile.Exists(path) {
+ var (
+ ctx = context.Background()
+ tplContent = getTemplateFromPathOrDefault(
+ in.TplDaoTablePath, consts.TemplateGenTableContent,
+ )
+ )
+ tplView.ClearAssigns()
+ tplView.Assigns(gview.Params{
+ tplVarGroupName: in.Group,
+ tplVarTableName: in.TableName,
+ tplVarTableNameCamelCase: formatFieldName(in.NewTableName, FieldNameCaseCamel),
+ tplVarPackageName: filepath.Base(in.TablePath),
+ tplVarTableFields: generateTableFields(fieldMap),
+ })
+ indexContent, err := tplView.ParseContent(ctx, tplContent)
+ if err != nil {
+ mlog.Fatalf("parsing template content failed: %v", err)
+ }
+ if err = gfile.PutContents(path, strings.TrimSpace(indexContent)); err != nil {
+ mlog.Fatalf("writing content to '%s' failed: %v", path, err)
+ } else {
+ utils.GoFmt(path)
+ mlog.Print("generated:", gfile.RealPath(path))
+ }
+ }
+}
+
+// generateTableFields generates and returns the field definition content for specified table.
+func generateTableFields(fields map[string]*gdb.TableField) string {
+ var buf bytes.Buffer
+ fieldNames := make([]string, 0, len(fields))
+ for fieldName := range fields {
+ fieldNames = append(fieldNames, fieldName)
+ }
+ sort.Slice(fieldNames, func(i, j int) bool {
+ return fields[fieldNames[i]].Index < fields[fieldNames[j]].Index // asc
+ })
+ for index, fieldName := range fieldNames {
+ field := fields[fieldName]
+ buf.WriteString(" " + strconv.Quote(field.Name) + ": {\n")
+ buf.WriteString(" Index: " + gconv.String(field.Index) + ",\n")
+ buf.WriteString(" Name: " + strconv.Quote(field.Name) + ",\n")
+ buf.WriteString(" Type: " + strconv.Quote(field.Type) + ",\n")
+ buf.WriteString(" Null: " + gconv.String(field.Null) + ",\n")
+ buf.WriteString(" Key: " + strconv.Quote(field.Key) + ",\n")
+ buf.WriteString(" Default: " + generateDefaultValue(field.Default) + ",\n")
+ buf.WriteString(" Extra: " + strconv.Quote(field.Extra) + ",\n")
+ buf.WriteString(" Comment: " + strconv.Quote(field.Comment) + ",\n")
+ buf.WriteString(" },")
+ if index != len(fieldNames)-1 {
+ buf.WriteString("\n")
+ }
+ }
+ return buf.String()
+}
+
+// generateDefaultValue generates and returns the default value definition for specified field.
+func generateDefaultValue(value interface{}) string {
+ if value == nil {
+ return "nil"
+ }
+ switch v := value.(type) {
+ case string:
+ return strconv.Quote(v)
+ default:
+ return gconv.String(v)
+ }
+}
diff --git a/server/internal/library/hggen/internal/cmd/gendao/gendao_tag.go b/server/internal/library/hggen/internal/cmd/gendao/gendao_tag.go
new file mode 100644
index 0000000..25bc842
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/gendao/gendao_tag.go
@@ -0,0 +1,155 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gendao
+
+import (
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/util/gtag"
+)
+
+const (
+ CGenDaoConfig = `gfcli.gen.dao`
+ CGenDaoUsage = `gf gen dao [OPTION]`
+ CGenDaoBrief = `automatically generate go files for dao/do/entity`
+ CGenDaoEg = `
+gf gen dao
+gf gen dao -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
+gf gen dao -p ./model -g user-center -t user,user_detail,user_login
+gf gen dao -r user_
+`
+
+ CGenDaoAd = `
+CONFIGURATION SUPPORT
+ Options are also supported by configuration file.
+ It's suggested using configuration file instead of command line arguments making producing.
+ The configuration node name is "gfcli.gen.dao", which also supports multiple databases, for example(config.yaml):
+ gfcli:
+ gen:
+ dao:
+ - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
+ tables: "order,products"
+ jsonCase: "CamelLower"
+ - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary"
+ path: "./my-app"
+ prefix: "primary_"
+ tables: "user, userDetail"
+ typeMapping:
+ decimal:
+ type: decimal.Decimal
+ import: github.com/shopspring/decimal
+ numeric:
+ type: string
+ fieldMapping:
+ table_name.field_name:
+ type: decimal.Decimal
+ import: github.com/shopspring/decimal
+`
+ CGenDaoBriefPath = `directory path for generated files`
+ CGenDaoBriefLink = `database configuration, the same as the ORM configuration of GoFrame`
+ CGenDaoBriefTables = `generate models only for given tables, multiple table names separated with ','`
+ CGenDaoBriefTablesEx = `generate models excluding given tables, multiple table names separated with ','`
+ CGenDaoBriefPrefix = `add prefix for all table of specified link/database tables`
+ CGenDaoBriefRemovePrefix = `remove specified prefix of the table, multiple prefix separated with ','`
+ CGenDaoBriefRemoveFieldPrefix = `remove specified prefix of the field, multiple prefix separated with ','`
+ CGenDaoBriefStdTime = `use time.Time from stdlib instead of gtime.Time for generated time/date fields of tables`
+ CGenDaoBriefWithTime = `add created time for auto produced go files`
+ CGenDaoBriefGJsonSupport = `use gJsonSupport to use *gjson.Json instead of string for generated json fields of tables`
+ CGenDaoBriefImportPrefix = `custom import prefix for generated go files`
+ CGenDaoBriefDaoPath = `directory path for storing generated dao files under path`
+ CGenDaoBriefTablePath = `directory path for storing generated table files under path`
+ CGenDaoBriefDoPath = `directory path for storing generated do files under path`
+ CGenDaoBriefEntityPath = `directory path for storing generated entity files under path`
+ CGenDaoBriefOverwriteDao = `overwrite all dao files both inside/outside internal folder`
+ CGenDaoBriefModelFile = `custom file name for storing generated model content`
+ CGenDaoBriefModelFileForDao = `custom file name generating model for DAO operations like Where/Data. It's empty in default`
+ CGenDaoBriefDescriptionTag = `add comment to description tag for each field`
+ CGenDaoBriefNoJsonTag = `no json tag will be added for each field`
+ CGenDaoBriefNoModelComment = `no model comment will be added for each field`
+ CGenDaoBriefClear = `delete all generated go files that do not exist in database`
+ CGenDaoBriefGenTable = `generate table files`
+ CGenDaoBriefTypeMapping = `custom local type mapping for generated struct attributes relevant to fields of table`
+ CGenDaoBriefFieldMapping = `custom local type mapping for generated struct attributes relevant to specific fields of table`
+ CGenDaoBriefShardingPattern = `sharding pattern for table name, e.g. "users_?" will be replace tables "users_001,users_002,..." to "users" dao`
+ CGenDaoBriefGroup = `
+specifying the configuration group name of database for generated ORM instance,
+it's not necessary and the default value is "default"
+`
+ CGenDaoBriefJsonCase = `
+generated json tag case for model struct, cases are as follows:
+| Case | Example |
+|---------------- |--------------------|
+| Camel | AnyKindOfString |
+| CamelLower | anyKindOfString | default
+| Snake | any_kind_of_string |
+| SnakeScreaming | ANY_KIND_OF_STRING |
+| SnakeFirstUpper | rgb_code_md5 |
+| Kebab | any-kind-of-string |
+| KebabScreaming | ANY-KIND-OF-STRING |
+`
+ CGenDaoBriefTplDaoIndexPath = `template file path for dao index file`
+ CGenDaoBriefTplDaoInternalPath = `template file path for dao internal file`
+ CGenDaoBriefTplDaoDoPathPath = `template file path for dao do file`
+ CGenDaoBriefTplDaoEntityPath = `template file path for dao entity file`
+
+ tplVarTableName = `TplTableName`
+ tplVarTableNameCamelCase = `TplTableNameCamelCase`
+ tplVarTableNameCamelLowerCase = `TplTableNameCamelLowerCase`
+ tplVarTableSharding = `TplTableSharding`
+ tplVarTableShardingPrefix = `TplTableShardingPrefix`
+ tplVarTableFields = `TplTableFields`
+ tplVarPackageImports = `TplPackageImports`
+ tplVarImportPrefix = `TplImportPrefix`
+ tplVarStructDefine = `TplStructDefine`
+ tplVarColumnDefine = `TplColumnDefine`
+ tplVarColumnNames = `TplColumnNames`
+ tplVarGroupName = `TplGroupName`
+ tplVarDatetimeStr = `TplDatetimeStr`
+ tplVarCreatedAtDatetimeStr = `TplCreatedAtDatetimeStr`
+ tplVarPackageName = `TplPackageName`
+)
+
+func init() {
+ gtag.Sets(g.MapStrStr{
+ `CGenDaoConfig`: CGenDaoConfig,
+ `CGenDaoUsage`: CGenDaoUsage,
+ `CGenDaoBrief`: CGenDaoBrief,
+ `CGenDaoEg`: CGenDaoEg,
+ `CGenDaoAd`: CGenDaoAd,
+ `CGenDaoBriefPath`: CGenDaoBriefPath,
+ `CGenDaoBriefLink`: CGenDaoBriefLink,
+ `CGenDaoBriefTables`: CGenDaoBriefTables,
+ `CGenDaoBriefTablesEx`: CGenDaoBriefTablesEx,
+ `CGenDaoBriefPrefix`: CGenDaoBriefPrefix,
+ `CGenDaoBriefRemovePrefix`: CGenDaoBriefRemovePrefix,
+ `CGenDaoBriefRemoveFieldPrefix`: CGenDaoBriefRemoveFieldPrefix,
+ `CGenDaoBriefStdTime`: CGenDaoBriefStdTime,
+ `CGenDaoBriefWithTime`: CGenDaoBriefWithTime,
+ `CGenDaoBriefDaoPath`: CGenDaoBriefDaoPath,
+ `CGenDaoBriefTablePath`: CGenDaoBriefTablePath,
+ `CGenDaoBriefDoPath`: CGenDaoBriefDoPath,
+ `CGenDaoBriefEntityPath`: CGenDaoBriefEntityPath,
+ `CGenDaoBriefGJsonSupport`: CGenDaoBriefGJsonSupport,
+ `CGenDaoBriefImportPrefix`: CGenDaoBriefImportPrefix,
+ `CGenDaoBriefOverwriteDao`: CGenDaoBriefOverwriteDao,
+ `CGenDaoBriefModelFile`: CGenDaoBriefModelFile,
+ `CGenDaoBriefModelFileForDao`: CGenDaoBriefModelFileForDao,
+ `CGenDaoBriefDescriptionTag`: CGenDaoBriefDescriptionTag,
+ `CGenDaoBriefNoJsonTag`: CGenDaoBriefNoJsonTag,
+ `CGenDaoBriefNoModelComment`: CGenDaoBriefNoModelComment,
+ `CGenDaoBriefClear`: CGenDaoBriefClear,
+ `CGenDaoBriefGenTable`: CGenDaoBriefGenTable,
+ `CGenDaoBriefTypeMapping`: CGenDaoBriefTypeMapping,
+ `CGenDaoBriefFieldMapping`: CGenDaoBriefFieldMapping,
+ `CGenDaoBriefShardingPattern`: CGenDaoBriefShardingPattern,
+ `CGenDaoBriefGroup`: CGenDaoBriefGroup,
+ `CGenDaoBriefJsonCase`: CGenDaoBriefJsonCase,
+ `CGenDaoBriefTplDaoIndexPath`: CGenDaoBriefTplDaoIndexPath,
+ `CGenDaoBriefTplDaoInternalPath`: CGenDaoBriefTplDaoInternalPath,
+ `CGenDaoBriefTplDaoDoPathPath`: CGenDaoBriefTplDaoDoPathPath,
+ `CGenDaoBriefTplDaoEntityPath`: CGenDaoBriefTplDaoEntityPath,
+ })
+}
diff --git a/server/internal/library/hggen/internal/cmd/genenums/genenums.go b/server/internal/library/hggen/internal/cmd/genenums/genenums.go
new file mode 100644
index 0000000..18a34cd
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/genenums/genenums.go
@@ -0,0 +1,85 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package genenums
+
+import (
+ "context"
+
+ "golang.org/x/tools/go/packages"
+
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/text/gstr"
+ "github.com/gogf/gf/v2/util/gtag"
+
+ "hotgo/internal/library/hggen/internal/consts"
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+)
+
+type (
+ CGenEnums struct{}
+ CGenEnumsInput struct {
+ g.Meta `name:"enums" config:"{CGenEnumsConfig}" brief:"{CGenEnumsBrief}" eg:"{CGenEnumsEg}"`
+ Src string `name:"src" short:"s" dc:"source folder path to be parsed" d:"api"`
+ Path string `name:"path" short:"p" dc:"output go file path storing enums content" d:"internal/packed/packed_enums.go"`
+ Prefixes []string `name:"prefixes" short:"x" dc:"only exports packages that starts with specified prefixes"`
+ }
+ CGenEnumsOutput struct{}
+)
+
+const (
+ CGenEnumsConfig = `gfcli.gen.enums`
+ CGenEnumsBrief = `parse go files in current project and generate enums go file`
+ CGenEnumsEg = `
+gf gen enums
+gf gen enums -p internal/packed/packed_enums.go
+gf gen enums -p internal/packed/packed_enums.go -s .
+gf gen enums -x github.com/gogf
+`
+)
+
+func init() {
+ gtag.Sets(g.MapStrStr{
+ `CGenEnumsEg`: CGenEnumsEg,
+ `CGenEnumsBrief`: CGenEnumsBrief,
+ `CGenEnumsConfig`: CGenEnumsConfig,
+ })
+}
+
+func (c CGenEnums) Enums(ctx context.Context, in CGenEnumsInput) (out *CGenEnumsOutput, err error) {
+ realPath := gfile.RealPath(in.Src)
+ if realPath == "" {
+ mlog.Fatalf(`source folder path "%s" does not exist`, in.Src)
+ }
+ err = gfile.Chdir(realPath)
+ if err != nil {
+ mlog.Fatal(err)
+ }
+ mlog.Printf(`scanning for enums: %s`, realPath)
+ cfg := &packages.Config{
+ Dir: realPath,
+ Mode: pkgLoadMode,
+ Tests: false,
+ }
+ pkgs, err := packages.Load(cfg)
+ if err != nil {
+ mlog.Fatal(err)
+ }
+ p := NewEnumsParser(in.Prefixes)
+ p.ParsePackages(pkgs)
+ var enumsContent = gstr.ReplaceByMap(consts.TemplateGenEnums, g.MapStrStr{
+ "{PackageName}": gfile.Basename(gfile.Dir(in.Path)),
+ "{EnumsJson}": "`" + p.Export() + "`",
+ })
+ enumsContent = gstr.Trim(enumsContent)
+ if err = gfile.PutContents(in.Path, enumsContent); err != nil {
+ return
+ }
+ mlog.Printf(`generated enums go file: %s`, in.Path)
+ mlog.Print("done!")
+ return
+}
diff --git a/server/internal/library/hggen/internal/cmd/genenums/genenums_parser.go b/server/internal/library/hggen/internal/cmd/genenums/genenums_parser.go
new file mode 100644
index 0000000..e0ab942
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/genenums/genenums_parser.go
@@ -0,0 +1,149 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package genenums
+
+import (
+ "go/constant"
+ "go/types"
+
+ "golang.org/x/tools/go/packages"
+
+ "github.com/gogf/gf/v2/encoding/gjson"
+ "github.com/gogf/gf/v2/text/gstr"
+ "github.com/gogf/gf/v2/util/gconv"
+)
+
+const pkgLoadMode = 0xffffff
+
+type EnumsParser struct {
+ enums []EnumItem
+ parsedPkg map[string]struct{}
+ prefixes []string
+ standardPackages map[string]struct{}
+}
+
+type EnumItem struct {
+ Name string
+ Value string
+ Kind constant.Kind // String/Int/Bool/Float/Complex/Unknown
+ Type string // Pkg.ID + TypeName
+}
+
+func NewEnumsParser(prefixes []string) *EnumsParser {
+ return &EnumsParser{
+ enums: make([]EnumItem, 0),
+ parsedPkg: make(map[string]struct{}),
+ prefixes: prefixes,
+ standardPackages: getStandardPackages(),
+ }
+}
+
+func (p *EnumsParser) ParsePackages(pkgs []*packages.Package) {
+ for _, pkg := range pkgs {
+ p.ParsePackage(pkg)
+ }
+}
+
+func (p *EnumsParser) ParsePackage(pkg *packages.Package) {
+ // Ignore std packages.
+ if _, ok := p.standardPackages[pkg.ID]; ok {
+ return
+ }
+ // Ignore pared packages.
+ if _, ok := p.parsedPkg[pkg.ID]; ok {
+ return
+ }
+ p.parsedPkg[pkg.ID] = struct{}{}
+
+ // Only parse specified prefixes.
+ if len(p.prefixes) > 0 {
+ var hasPrefix bool
+ for _, prefix := range p.prefixes {
+ if hasPrefix = gstr.HasPrefix(pkg.ID, prefix); hasPrefix {
+ break
+ }
+ }
+ if !hasPrefix {
+ return
+ }
+ }
+
+ var (
+ scope = pkg.Types.Scope()
+ names = scope.Names()
+ )
+ for _, name := range names {
+ con, ok := scope.Lookup(name).(*types.Const)
+ if !ok {
+ // Only constants can be enums.
+ continue
+ }
+ if !con.Exported() {
+ // Ignore unexported values.
+ continue
+ }
+
+ var enumType = con.Type().String()
+ if !gstr.Contains(enumType, "/") {
+ // Ignore std types.
+ continue
+ }
+ var (
+ enumName = con.Name()
+ enumValue = con.Val().ExactString()
+ enumKind = con.Val().Kind()
+ )
+ if con.Val().Kind() == constant.String {
+ enumValue = constant.StringVal(con.Val())
+ }
+ p.enums = append(p.enums, EnumItem{
+ Name: enumName,
+ Value: enumValue,
+ Type: enumType,
+ Kind: enumKind,
+ })
+ }
+ for _, im := range pkg.Imports {
+ p.ParsePackage(im)
+ }
+}
+
+func (p *EnumsParser) Export() string {
+ var typeEnumMap = make(map[string][]any)
+ for _, enum := range p.enums {
+ if typeEnumMap[enum.Type] == nil {
+ typeEnumMap[enum.Type] = make([]any, 0)
+ }
+ var value any
+ switch enum.Kind {
+ case constant.Int:
+ value = gconv.Int64(enum.Value)
+ case constant.String:
+ value = enum.Value
+ case constant.Float:
+ value = gconv.Float64(enum.Value)
+ case constant.Bool:
+ value = gconv.Bool(enum.Value)
+ default:
+ value = enum.Value
+ }
+ typeEnumMap[enum.Type] = append(typeEnumMap[enum.Type], value)
+ }
+ return gjson.MustEncodeString(typeEnumMap)
+}
+
+func getStandardPackages() map[string]struct{} {
+ standardPackages := make(map[string]struct{})
+ stdPackages, err := packages.Load(nil, "std")
+ if err != nil {
+ panic(err)
+ }
+ for _, p := range stdPackages {
+ standardPackages[p.ID] = struct{}{}
+ }
+ return standardPackages
+}
diff --git a/server/internal/library/hggen/internal/cmd/genpb/genpb.go b/server/internal/library/hggen/internal/cmd/genpb/genpb.go
new file mode 100644
index 0000000..da4da07
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/genpb/genpb.go
@@ -0,0 +1,132 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package genpb
+
+import (
+ "context"
+
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gproc"
+ "github.com/gogf/gf/v2/util/gtag"
+
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+)
+
+type (
+ CGenPb struct{}
+ CGenPbInput struct {
+ g.Meta `name:"pb" config:"{CGenPbConfig}" brief:"{CGenPbBrief}" eg:"{CGenPbEg}"`
+ Path string `name:"path" short:"p" dc:"protobuf file folder path" d:"manifest/protobuf"`
+ OutputApi string `name:"api" short:"a" dc:"output folder path storing generated go files of api" d:"api"`
+ OutputCtrl string `name:"ctrl" short:"c" dc:"output folder path storing generated go files of controller" d:"internal/controller"`
+ }
+ CGenPbOutput struct{}
+)
+
+const (
+ CGenPbConfig = `gfcli.gen.pb`
+ CGenPbBrief = `parse proto files and generate protobuf go files`
+ CGenPbEg = `
+gf gen pb
+gf gen pb -p . -a . -p .
+`
+)
+
+func init() {
+ gtag.Sets(g.MapStrStr{
+ `CGenPbEg`: CGenPbEg,
+ `CGenPbBrief`: CGenPbBrief,
+ `CGenPbConfig`: CGenPbConfig,
+ })
+}
+
+func (c CGenPb) Pb(ctx context.Context, in CGenPbInput) (out *CGenPbOutput, err error) {
+ // Necessary check.
+ protoc := gproc.SearchBinary("protoc")
+ if protoc == "" {
+ mlog.Fatalf(`command "protoc" not found in your environment, please install protoc first: https://grpc.io/docs/languages/go/quickstart/`)
+ }
+
+ // protocol fold checks.
+ var (
+ protoPath = gfile.RealPath(in.Path)
+ isParsingPWD bool
+ )
+ if protoPath == "" {
+ // Use current working directory as protoPath if there are proto files under.
+ currentPath := gfile.Pwd()
+ currentFiles, _ := gfile.ScanDirFile(currentPath, "*.proto")
+ if len(currentFiles) > 0 {
+ protoPath = currentPath
+ isParsingPWD = true
+ } else {
+ mlog.Fatalf(`proto files folder "%s" does not exist`, in.Path)
+ }
+ }
+ // output path checks.
+ outputApiPath := gfile.RealPath(in.OutputApi)
+ if outputApiPath == "" {
+ if isParsingPWD {
+ outputApiPath = protoPath
+ } else {
+ mlog.Fatalf(`output api folder "%s" does not exist`, in.OutputApi)
+ }
+ }
+ outputCtrlPath := gfile.RealPath(in.OutputCtrl)
+ if outputCtrlPath == "" {
+ if isParsingPWD {
+ outputCtrlPath = ""
+ } else {
+ mlog.Fatalf(`output controller folder "%s" does not exist`, in.OutputCtrl)
+ }
+ }
+
+ // folder scanning.
+ files, err := gfile.ScanDirFile(protoPath, "*.proto", true)
+ if err != nil {
+ mlog.Fatal(err)
+ }
+ if len(files) == 0 {
+ mlog.Fatalf(`no proto files found in folder "%s"`, in.Path)
+ }
+
+ var originPwd = gfile.Pwd()
+ defer gfile.Chdir(originPwd)
+
+ if err = gfile.Chdir(protoPath); err != nil {
+ mlog.Fatal(err)
+ }
+ for _, file := range files {
+ var command = gproc.NewProcess(protoc, nil)
+ command.Args = append(command.Args, "--proto_path="+gfile.Pwd())
+ command.Args = append(command.Args, "--go_out=paths=source_relative:"+outputApiPath)
+ command.Args = append(command.Args, "--go-grpc_out=paths=source_relative:"+outputApiPath)
+ command.Args = append(command.Args, file)
+ mlog.Print(command.String())
+ if err = command.Run(ctx); err != nil {
+ mlog.Fatal(err)
+ }
+ }
+ // Generate struct tag according comment rules.
+ err = c.generateStructTag(ctx, generateStructTagInput{OutputApiPath: outputApiPath})
+ if err != nil {
+ return
+ }
+ // Generate controllers according comment rules.
+ if outputCtrlPath != "" {
+ err = c.generateController(ctx, generateControllerInput{
+ OutputApiPath: outputApiPath,
+ OutputCtrlPath: outputCtrlPath,
+ })
+ if err != nil {
+ return
+ }
+ }
+ mlog.Print("done!")
+ return
+}
diff --git a/server/internal/library/hggen/internal/cmd/genpb/genpb_controller.go b/server/internal/library/hggen/internal/cmd/genpb/genpb_controller.go
new file mode 100644
index 0000000..205e0c4
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/genpb/genpb_controller.go
@@ -0,0 +1,197 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package genpb
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/text/gregex"
+ "github.com/gogf/gf/v2/text/gstr"
+
+ "hotgo/internal/library/hggen/internal/utility/utils"
+)
+
+type generateControllerInput struct {
+ OutputApiPath string
+ OutputCtrlPath string
+}
+
+type generateCtrl struct {
+ Name string
+ Package string
+ Version string
+ Methods []generateCtrlMethod
+}
+
+type generateCtrlMethod struct {
+ Name string
+ Definition string
+}
+
+const (
+ controllerTemplate = `
+package {Package}
+
+type Controller struct {
+ {Version}.Unimplemented{Name}Server
+}
+
+func Register(s *grpcx.GrpcServer) {
+ {Version}.Register{Name}Server(s.Server, &Controller{})
+}
+`
+ controllerMethodTemplate = `
+func (*Controller) {Definition} {
+ return nil, gerror.NewCode(gcode.CodeNotImplemented)
+}
+`
+)
+
+func (c CGenPb) generateController(ctx context.Context, in generateControllerInput) (err error) {
+ files, err := gfile.ScanDirFile(in.OutputApiPath, "*_grpc.pb.go", true)
+ if err != nil {
+ return err
+ }
+ var controllers []generateCtrl
+ for _, file := range files {
+ fileControllers, err := c.parseControllers(file)
+ if err != nil {
+ return err
+ }
+ controllers = append(controllers, fileControllers...)
+ }
+ if len(controllers) == 0 {
+ return nil
+ }
+ // Generate controller files.
+ err = c.doGenerateControllers(in, controllers)
+ return
+}
+
+func (c CGenPb) parseControllers(filePath string) ([]generateCtrl, error) {
+ var (
+ controllers []generateCtrl
+ content = gfile.GetContents(filePath)
+ )
+ _, err := gregex.ReplaceStringFuncMatch(
+ `type (\w+)Server interface {([\s\S]+?)}`,
+ content,
+ func(match []string) string {
+ ctrl := generateCtrl{
+ Name: match[1],
+ Package: strings.ReplaceAll(gfile.Basename(gfile.Dir(gfile.Dir(filePath))), "-", "_"),
+ Version: gfile.Basename(gfile.Dir(filePath)),
+ Methods: make([]generateCtrlMethod, 0),
+ }
+ lines := gstr.Split(match[2], "\n")
+ for _, line := range lines {
+ line = gstr.Trim(line)
+ if line == "" || !gstr.IsLetterUpper(line[0]) {
+ continue
+ }
+ // Comment.
+ if gregex.IsMatchString(`^//.+`, line) {
+ continue
+ }
+ line, _ = gregex.ReplaceStringFuncMatch(
+ `^(\w+)\(context\.Context, \*(\w+)\) \(\*(\w+), error\)$`,
+ line,
+ func(match []string) string {
+ return fmt.Sprintf(
+ `%s(ctx context.Context, req *%s.%s) (res *%s.%s, err error)`,
+ match[1], ctrl.Version, match[2], ctrl.Version, match[3],
+ )
+ },
+ )
+ ctrl.Methods = append(ctrl.Methods, generateCtrlMethod{
+ Name: gstr.Split(line, "(")[0],
+ Definition: line,
+ })
+ }
+ if len(ctrl.Methods) > 0 {
+ controllers = append(controllers, ctrl)
+ }
+ return match[0]
+ },
+ )
+ return controllers, err
+}
+
+func (c CGenPb) doGenerateControllers(in generateControllerInput, controllers []generateCtrl) (err error) {
+ for _, controller := range controllers {
+ err = c.doGenerateController(in, controller)
+ if err != nil {
+ return err
+ }
+ }
+ err = utils.ReplaceGeneratedContentGFV2(in.OutputCtrlPath)
+ return nil
+}
+
+func (c CGenPb) doGenerateController(in generateControllerInput, controller generateCtrl) (err error) {
+ var (
+ folderPath = gfile.Join(in.OutputCtrlPath, controller.Package)
+ filePath = gfile.Join(folderPath, controller.Package+".go")
+ isDirty bool
+ )
+ if !gfile.Exists(folderPath) {
+ if err = gfile.Mkdir(folderPath); err != nil {
+ return err
+ }
+ }
+ if !gfile.Exists(filePath) {
+ templateContent := gstr.ReplaceByMap(controllerTemplate, g.MapStrStr{
+ "{Name}": controller.Name,
+ "{Version}": controller.Version,
+ "{Package}": controller.Package,
+ })
+ if err = gfile.PutContents(filePath, templateContent); err != nil {
+ return err
+ }
+ isDirty = true
+ }
+ // Exist controller content.
+ var ctrlContent string
+ files, err := gfile.ScanDirFile(folderPath, "*.go", false)
+ if err != nil {
+ return err
+ }
+ for _, file := range files {
+ if ctrlContent != "" {
+ ctrlContent += "\n"
+ }
+ ctrlContent += gfile.GetContents(file)
+ }
+ // Generate method content.
+ var generatedContent string
+ for _, method := range controller.Methods {
+ if gstr.Contains(ctrlContent, fmt.Sprintf(`%s(`, method.Name)) {
+ continue
+ }
+ if generatedContent != "" {
+ generatedContent += "\n"
+ }
+ generatedContent += gstr.ReplaceByMap(controllerMethodTemplate, g.MapStrStr{
+ "{Definition}": method.Definition,
+ })
+ }
+ if generatedContent != "" {
+ err = gfile.PutContentsAppend(filePath, generatedContent)
+ if err != nil {
+ return err
+ }
+ isDirty = true
+ }
+ if isDirty {
+ utils.GoFmt(filePath)
+ }
+ return nil
+}
diff --git a/server/internal/library/hggen/internal/cmd/genpb/genpb_tag.go b/server/internal/library/hggen/internal/cmd/genpb/genpb_tag.go
new file mode 100644
index 0000000..3dd9481
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/genpb/genpb_tag.go
@@ -0,0 +1,123 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package genpb
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/gogf/gf/v2/container/gmap"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/text/gregex"
+ "github.com/gogf/gf/v2/text/gstr"
+ "github.com/gogf/gf/v2/util/gconv"
+
+ "hotgo/internal/library/hggen/internal/utility/utils"
+)
+
+type generateStructTagInput struct {
+ OutputApiPath string
+}
+
+func (c CGenPb) generateStructTag(ctx context.Context, in generateStructTagInput) (err error) {
+ files, err := gfile.ScanDirFile(in.OutputApiPath, "*.pb.go", true)
+ if err != nil {
+ return err
+ }
+ var content string
+ for _, file := range files {
+ content = gfile.GetContents(file)
+ content, err = c.doTagReplacement(ctx, content)
+ if err != nil {
+ return err
+ }
+ if err = gfile.PutContents(file, content); err != nil {
+ return err
+ }
+ utils.GoFmt(file)
+ }
+ return
+}
+
+func (c CGenPb) doTagReplacement(ctx context.Context, content string) (string, error) {
+ content, err := gregex.ReplaceStringFuncMatch(`type (\w+) struct {([\s\S]+?)}`, content, func(match []string) string {
+ var (
+ topCommentMatch []string
+ tailCommentMatch []string
+ lines = gstr.Split(match[2], "\n")
+ lineTagMap = gmap.NewListMap()
+ )
+ for index, line := range lines {
+ line = gstr.Trim(line)
+ if line == "" {
+ continue
+ }
+ // Top comment.
+ topCommentMatch, _ = gregex.MatchString(`^/[/|\*](.+)`, line)
+ if len(topCommentMatch) > 1 {
+ c.tagCommentIntoListMap(gstr.Trim(topCommentMatch[1]), lineTagMap)
+ continue
+ }
+ // Tail comment.
+ tailCommentMatch, _ = gregex.MatchString(".+?`.+?`.+?//(.+)", line)
+ if len(tailCommentMatch) > 1 {
+ c.tagCommentIntoListMap(gstr.Trim(tailCommentMatch[1]), lineTagMap)
+ }
+ // Tag injection.
+ if !lineTagMap.IsEmpty() {
+ tagContent := c.listMapToStructTag(lineTagMap)
+ lineTagMap.Clear()
+ // If already have it, don't add it anymore
+ if gstr.Contains(gstr.StrTill(line, "` //"), tagContent) {
+ continue
+ }
+ line, _ = gregex.ReplaceString("`(.+)`", fmt.Sprintf("`$1 %s`", tagContent), line)
+ }
+ lines[index] = line
+ }
+ match[2] = gstr.Join(lines, "\n")
+ return fmt.Sprintf("type %s struct {%s}", match[1], match[2])
+ })
+ return content, err
+}
+
+func (c CGenPb) tagCommentIntoListMap(comment string, lineTagMap *gmap.ListMap) {
+ tagCommentMatch, _ := gregex.MatchString(`^(\w+):(.+)`, comment)
+ if len(tagCommentMatch) > 1 {
+ var (
+ tagName = gstr.Trim(tagCommentMatch[1])
+ tagContent = gstr.Trim(tagCommentMatch[2])
+ )
+ lineTagMap.Set(tagName, lineTagMap.GetVar(tagName).String()+tagContent)
+ } else {
+ var (
+ tagName = "dc"
+ // Convert backticks and double quotes to single quotes.
+ tagContent = gstr.ReplaceByMap(comment, g.MapStrStr{
+ "`": `'`,
+ `"`: `'`,
+ })
+ )
+ lineTagMap.Set(tagName, lineTagMap.GetVar(tagName).String()+tagContent)
+ }
+}
+
+func (c CGenPb) listMapToStructTag(lineTagMap *gmap.ListMap) string {
+ var tag string
+ lineTagMap.Iterator(func(key, value any) bool {
+ if tag != "" {
+ tag += " "
+ }
+ tag += fmt.Sprintf(
+ `%s:"%s"`,
+ key, gstr.Replace(gconv.String(value), `"`, `\"`),
+ )
+ return true
+ })
+ return tag
+}
diff --git a/server/internal/library/hggen/internal/cmd/genpbentity/genpbentity.go b/server/internal/library/hggen/internal/cmd/genpbentity/genpbentity.go
new file mode 100644
index 0000000..a80ec17
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/genpbentity/genpbentity.go
@@ -0,0 +1,591 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package genpbentity
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "path/filepath"
+ "regexp"
+ "strings"
+
+ "github.com/olekukonko/tablewriter"
+ "github.com/olekukonko/tablewriter/renderer"
+ "github.com/olekukonko/tablewriter/tw"
+
+ "github.com/gogf/gf/v2/container/garray"
+ "github.com/gogf/gf/v2/container/gset"
+ "github.com/gogf/gf/v2/database/gdb"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gctx"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gtime"
+ "github.com/gogf/gf/v2/text/gregex"
+ "github.com/gogf/gf/v2/text/gstr"
+ "github.com/gogf/gf/v2/util/gconv"
+ "github.com/gogf/gf/v2/util/gtag"
+
+ "hotgo/internal/library/hggen/internal/consts"
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+ "hotgo/internal/library/hggen/internal/utility/utils"
+)
+
+type (
+ CGenPbEntity struct{}
+ CGenPbEntityInput struct {
+ g.Meta `name:"pbentity" config:"{CGenPbEntityConfig}" brief:"{CGenPbEntityBrief}" eg:"{CGenPbEntityEg}" ad:"{CGenPbEntityAd}"`
+ Path string `name:"path" short:"p" brief:"{CGenPbEntityBriefPath}" d:"manifest/protobuf/pbentity"`
+ Package string `name:"package" short:"k" brief:"{CGenPbEntityBriefPackage}"`
+ GoPackage string `name:"goPackage" short:"g" brief:"{CGenPbEntityBriefGoPackage}"`
+ Link string `name:"link" short:"l" brief:"{CGenPbEntityBriefLink}"`
+ Tables string `name:"tables" short:"t" brief:"{CGenPbEntityBriefTables}"`
+ Prefix string `name:"prefix" short:"f" brief:"{CGenPbEntityBriefPrefix}"`
+ RemovePrefix string `name:"removePrefix" short:"r" brief:"{CGenPbEntityBriefRemovePrefix}"`
+ RemoveFieldPrefix string `name:"removeFieldPrefix" short:"rf" brief:"{CGenPbEntityBriefRemoveFieldPrefix}"`
+ TablesEx string `name:"tablesEx" short:"x" brief:"{CGenDaoBriefTablesEx}"`
+ NameCase string `name:"nameCase" short:"n" brief:"{CGenPbEntityBriefNameCase}" d:"Camel"`
+ JsonCase string `name:"jsonCase" short:"j" brief:"{CGenPbEntityBriefJsonCase}" d:"none"`
+ Option string `name:"option" short:"o" brief:"{CGenPbEntityBriefOption}"`
+ ShardingPattern []string `name:"shardingPattern" short:"sp" brief:"{CGenDaoBriefShardingPattern}"`
+
+ TypeMapping map[DBFieldTypeName]CustomAttributeType `name:"typeMapping" short:"y" brief:"{CGenPbEntityBriefTypeMapping}" orphan:"true"`
+ FieldMapping map[DBTableFieldName]CustomAttributeType `name:"fieldMapping" short:"fm" brief:"{CGenPbEntityBriefFieldMapping}" orphan:"true"`
+ }
+ CGenPbEntityOutput struct{}
+
+ CGenPbEntityInternalInput struct {
+ CGenPbEntityInput
+ DB gdb.DB
+ TableName string // TableName specifies the table name of the table.
+ NewTableName string // NewTableName specifies the prefix-stripped name of the table.
+ }
+
+ DBTableFieldName = string
+ DBFieldTypeName = string
+ CustomAttributeType struct {
+ Type string `brief:"custom attribute type name"`
+ Import string `brief:"custom import for this type"`
+ }
+)
+
+const (
+ defaultPackageSuffix = `api/pbentity`
+ CGenPbEntityConfig = `gfcli.gen.pbentity`
+ CGenPbEntityBrief = `generate entity message files in protobuf3 format`
+ CGenPbEntityEg = `
+gf gen pbentity
+gf gen pbentity -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
+gf gen pbentity -p ./protocol/demos/entity -t user,user_detail,user_login
+gf gen pbentity -r user_ -k github.com/gogf/gf/example/protobuf
+gf gen pbentity -r user_
+`
+
+ CGenPbEntityAd = `
+CONFIGURATION SUPPORT
+ Options are also supported by configuration file.
+ It's suggested using configuration file instead of command line arguments making producing.
+ The configuration node name is "gf.gen.pbentity", which also supports multiple databases, for example(config.yaml):
+ gfcli:
+ gen:
+ - pbentity:
+ link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
+ path: "protocol/demos/entity"
+ tables: "order,products"
+ package: "demos"
+ - pbentity:
+ link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary"
+ path: "protocol/demos/entity"
+ prefix: "primary_"
+ tables: "user, userDetail"
+ package: "demos"
+ option: |
+ option go_package = "protobuf/demos";
+ option java_package = "protobuf/demos";
+ option php_namespace = "protobuf/demos";
+ typeMapping:
+ json:
+ type: google.protobuf.Value
+ import: google/protobuf/struct.proto
+ jsonb:
+ type: google.protobuf.Value
+ import: google/protobuf/struct.proto
+`
+ CGenPbEntityBriefPath = `directory path for generated files storing`
+ CGenPbEntityBriefPackage = `package path for all entity proto files`
+ CGenPbEntityBriefGoPackage = `go package path for all entity proto files`
+ CGenPbEntityBriefLink = `database configuration, the same as the ORM configuration of GoFrame`
+ CGenPbEntityBriefTables = `generate models only for given tables, multiple table names separated with ','`
+ CGenPbEntityBriefPrefix = `add specified prefix for all entity names and entity proto files`
+ CGenPbEntityBriefRemovePrefix = `remove specified prefix of the table, multiple prefix separated with ','`
+ CGenPbEntityBriefTablesEx = `generate all models exclude the specified tables, multiple prefix separated with ','`
+ CGenPbEntityBriefRemoveFieldPrefix = `remove specified prefix of the field, multiple prefix separated with ','`
+ CGenPbEntityBriefOption = `extra protobuf options`
+ CGenPbEntityBriefShardingPattern = `sharding pattern for table name, e.g. "users_?" will replace tables "users_001,users_002,..." to "users" pbentity`
+ CGenPbEntityBriefGroup = `
+specifying the configuration group name of database for generated ORM instance,
+it's not necessary and the default value is "default"
+`
+
+ CGenPbEntityBriefNameCase = `
+case for message attribute names, default is "Camel":
+| Case | Example |
+|---------------- |--------------------|
+| Camel | AnyKindOfString |
+| CamelLower | anyKindOfString | default
+| Snake | any_kind_of_string |
+| SnakeScreaming | ANY_KIND_OF_STRING |
+| SnakeFirstUpper | rgb_code_md5 |
+| Kebab | any-kind-of-string |
+| KebabScreaming | ANY-KIND-OF-STRING |
+`
+
+ CGenPbEntityBriefJsonCase = `
+case for message json tag, cases are the same as "nameCase", default "CamelLower".
+set it to "none" to ignore json tag generating.
+`
+
+ CGenPbEntityBriefTypeMapping = `custom local type mapping for generated struct attributes relevant to fields of table`
+ CGenPbEntityBriefFieldMapping = `custom local type mapping for generated struct attributes relevant to specific fields of table`
+)
+
+var defaultTypeMapping = map[DBFieldTypeName]CustomAttributeType{
+ // gdb.LocalTypeString
+ "string": {
+ Type: "string",
+ },
+ // gdb.LocalTypeTime
+ // "time": {
+ // Type: "google.protobuf.Duration",
+ // Import: "google/protobuf/duration.proto",
+ // },
+ // gdb.LocalTypeDate
+ "date": {
+ Type: "google.protobuf.Timestamp",
+ Import: "google/protobuf/timestamp.proto",
+ },
+ // gdb.LocalTypeDatetime
+ "datetime": {
+ Type: "google.protobuf.Timestamp",
+ Import: "google/protobuf/timestamp.proto",
+ },
+ // gdb.LocalTypeInt
+ "int": {
+ Type: "int32",
+ },
+ // gdb.LocalTypeUint
+ "uint": {
+ Type: "uint32",
+ },
+ // gdb.LocalTypeInt64
+ "int64": {
+ Type: "int64",
+ },
+ // gdb.LocalTypeUint64
+ "uint64": {
+ Type: "uint64",
+ },
+ // gdb.LocalTypeIntSlice
+ "[]int": {
+ Type: "repeated int32",
+ },
+ // gdb.LocalTypeInt64Slice
+ "[]int64": {
+ Type: "repeated int64",
+ },
+ // gdb.LocalTypeUint64Slice
+ "[]uint64": {
+ Type: "repeated uint64",
+ },
+ // gdb.LocalTypeInt64Bytes
+ "int64-bytes": {
+ Type: "repeated int64",
+ },
+ // gdb.LocalTypeUint64Bytes
+ "uint64-bytes": {
+ Type: "repeated uint64",
+ },
+ // gdb.LocalTypeFloat32
+ "float32": {
+ Type: "float",
+ },
+ // gdb.LocalTypeFloat64
+ "float64": {
+ Type: "double",
+ },
+ // gdb.LocalTypeBytes
+ "[]byte": {
+ Type: "bytes",
+ },
+ // gdb.LocalTypeBool
+ "bool": {
+ Type: "bool",
+ },
+ // gdb.LocalTypeJson
+ // "json": {
+ // Type: "google.protobuf.Value",
+ // Import: "google/protobuf/struct.proto",
+ // },
+ // gdb.LocalTypeJsonb
+ // "jsonb": {
+ // Type: "google.protobuf.Value",
+ // Import: "google/protobuf/struct.proto",
+ // },
+}
+
+func init() {
+ gtag.Sets(g.MapStrStr{
+ `CGenPbEntityConfig`: CGenPbEntityConfig,
+ `CGenPbEntityBrief`: CGenPbEntityBrief,
+ `CGenPbEntityEg`: CGenPbEntityEg,
+ `CGenPbEntityAd`: CGenPbEntityAd,
+ `CGenPbEntityBriefPath`: CGenPbEntityBriefPath,
+ `CGenPbEntityBriefPackage`: CGenPbEntityBriefPackage,
+ `CGenPbEntityBriefGoPackage`: CGenPbEntityBriefGoPackage,
+ `CGenPbEntityBriefLink`: CGenPbEntityBriefLink,
+ `CGenPbEntityBriefTables`: CGenPbEntityBriefTables,
+ `CGenPbEntityBriefPrefix`: CGenPbEntityBriefPrefix,
+ `CGenPbEntityBriefRemovePrefix`: CGenPbEntityBriefRemovePrefix,
+ `CGenPbEntityBriefTablesEx`: CGenPbEntityBriefTablesEx,
+ `CGenPbEntityBriefRemoveFieldPrefix`: CGenPbEntityBriefRemoveFieldPrefix,
+ `CGenPbEntityBriefGroup`: CGenPbEntityBriefGroup,
+ `CGenPbEntityBriefNameCase`: CGenPbEntityBriefNameCase,
+ `CGenPbEntityBriefJsonCase`: CGenPbEntityBriefJsonCase,
+ `CGenPbEntityBriefOption`: CGenPbEntityBriefOption,
+ `CGenPbEntityBriefShardingPattern`: CGenPbEntityBriefShardingPattern,
+ `CGenPbEntityBriefTypeMapping`: CGenPbEntityBriefTypeMapping,
+ `CGenPbEntityBriefFieldMapping`: CGenPbEntityBriefFieldMapping,
+ })
+}
+
+func (c CGenPbEntity) PbEntity(ctx context.Context, in CGenPbEntityInput) (out *CGenPbEntityOutput, err error) {
+ var (
+ config = g.Cfg()
+ )
+ if config.Available(ctx) {
+ v := config.MustGet(ctx, CGenPbEntityConfig)
+ if v.IsSlice() {
+ for i := 0; i < len(v.Interfaces()); i++ {
+ doGenPbEntityForArray(ctx, i, in)
+ }
+ } else {
+ doGenPbEntityForArray(ctx, -1, in)
+ }
+ } else {
+ doGenPbEntityForArray(ctx, -1, in)
+ }
+ mlog.Print("done!")
+ return
+}
+
+func doGenPbEntityForArray(ctx context.Context, index int, in CGenPbEntityInput) {
+ var (
+ err error
+ db gdb.DB
+ )
+ if index >= 0 {
+ err = g.Cfg().MustGet(
+ ctx,
+ fmt.Sprintf(`%s.%d`, CGenPbEntityConfig, index),
+ ).Scan(&in)
+ if err != nil {
+ mlog.Fatalf(`invalid configuration of "%s": %+v`, CGenPbEntityConfig, err)
+ }
+ }
+ if in.Package == "" {
+ mlog.Debug(`package parameter is empty, trying calculating the package path using go.mod`)
+ modName := utils.GetImportPath(gfile.Pwd())
+ in.Package = modName + "/" + defaultPackageSuffix
+ }
+ removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",")
+
+ excludeTables := gset.NewStrSetFrom(gstr.SplitAndTrim(in.TablesEx, ","))
+
+ // It uses user passed database configuration.
+ if in.Link != "" {
+ var (
+ tempGroup = gtime.TimestampNanoStr()
+ match, _ = gregex.MatchString(`([a-z]+):(.+)`, in.Link)
+ )
+ if len(match) == 3 {
+ gdb.AddConfigNode(tempGroup, gdb.ConfigNode{
+ Type: gstr.Trim(match[1]),
+ Link: in.Link,
+ })
+ db, _ = gdb.Instance(tempGroup)
+ }
+ } else {
+ db = g.DB()
+ }
+ if db == nil {
+ mlog.Fatal("database initialization failed")
+ }
+
+ tableNames := ([]string)(nil)
+ shardingNewTableSet := gset.NewStrSet()
+ if in.Tables != "" {
+ tableNames = gstr.SplitAndTrim(in.Tables, ",")
+ } else {
+ tableNames, err = db.Tables(context.TODO())
+ if err != nil {
+ mlog.Fatalf("fetching tables failed: \n %v", err)
+ }
+ }
+ // merge default typeMapping to input typeMapping.
+ if in.TypeMapping == nil {
+ in.TypeMapping = defaultTypeMapping
+ } else {
+ for key, typeMapping := range defaultTypeMapping {
+ if _, ok := in.TypeMapping[key]; !ok {
+ in.TypeMapping[key] = typeMapping
+ }
+ }
+ }
+
+ for _, tableName := range tableNames {
+ if excludeTables.Contains(tableName) {
+ continue
+ }
+ newTableName := tableName
+ for _, v := range removePrefixArray {
+ newTableName = gstr.TrimLeftStr(newTableName, v, 1)
+ }
+ var shardingTableName string
+ if len(in.ShardingPattern) > 0 {
+ for _, pattern := range in.ShardingPattern {
+ var (
+ match []string
+ regPattern = gstr.Replace(pattern, "?", `(.+)`)
+ )
+ match, err = gregex.MatchString(regPattern, newTableName)
+ if err != nil {
+ mlog.Fatalf(`invalid sharding pattern "%s": %+v`, pattern, err)
+ }
+ if len(match) < 2 {
+ continue
+ }
+ shardingTableName = gstr.Replace(pattern, "?", "")
+ shardingTableName = gstr.Trim(shardingTableName, `_.-`)
+ }
+ }
+ if shardingTableName != "" {
+ if shardingNewTableSet.Contains(shardingTableName) {
+ continue
+ }
+ shardingNewTableSet.Add(shardingTableName)
+ newTableName = shardingTableName
+ }
+ generatePbEntityContentFile(ctx, CGenPbEntityInternalInput{
+ CGenPbEntityInput: in,
+ DB: db,
+ TableName: tableName,
+ NewTableName: newTableName,
+ })
+ }
+}
+
+// generatePbEntityContentFile generates the protobuf files for given table.
+func generatePbEntityContentFile(ctx context.Context, in CGenPbEntityInternalInput) {
+ fieldMap, err := in.DB.TableFields(ctx, in.TableName)
+ if err != nil {
+ mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", in.TableName, err)
+ }
+ // Change the `newTableName` if `Prefix` is given.
+ newTableName := in.Prefix + in.NewTableName
+ var (
+ tableNameCamelCase = gstr.CaseCamel(newTableName)
+ tableNameSnakeCase = gstr.CaseSnake(newTableName)
+ entityMessageDefine, appendImports = generateEntityMessageDefinition(tableNameCamelCase, fieldMap, in)
+ fileName = gstr.Trim(tableNameSnakeCase, "-_.")
+ path = filepath.FromSlash(gfile.Join(in.Path, fileName+".proto"))
+ )
+ packageImportStr := ""
+ var packageImportsArray = garray.NewStrArray()
+ if len(appendImports) > 0 {
+ for _, appendImport := range appendImports {
+ packageImportStr = fmt.Sprintf(`import "%s";`, appendImport)
+ if packageImportsArray.Search(packageImportStr) == -1 {
+ packageImportsArray.Append(packageImportStr)
+ }
+ }
+ }
+ if in.GoPackage == "" {
+ in.GoPackage = in.Package
+ }
+ entityContent := gstr.ReplaceByMap(getTplPbEntityContent(""), g.MapStrStr{
+ "{Imports}": packageImportsArray.Join("\n"),
+ "{PackageName}": gfile.Basename(in.Package),
+ "{GoPackage}": in.GoPackage,
+ "{OptionContent}": in.Option,
+ "{EntityMessage}": entityMessageDefine,
+ })
+ if err := gfile.PutContents(path, strings.TrimSpace(entityContent)); err != nil {
+ mlog.Fatalf("writing content to '%s' failed: %v", path, err)
+ } else {
+ mlog.Print("generated:", gfile.RealPath(path))
+ }
+}
+
+// generateEntityMessageDefinition generates and returns the message definition for specified table.
+func generateEntityMessageDefinition(entityName string, fieldMap map[string]*gdb.TableField, in CGenPbEntityInternalInput) (string, []string) {
+ var (
+ appendImports []string
+ buffer = bytes.NewBuffer(nil)
+ array = make([][]string, len(fieldMap))
+ names = sortFieldKeyForPbEntity(fieldMap)
+ )
+ for index, name := range names {
+ var imports string
+ array[index], imports = generateMessageFieldForPbEntity(index+1, fieldMap[name], in)
+ if imports != "" {
+ appendImports = append(appendImports, imports)
+ }
+ }
+ table := tablewriter.NewTable(buffer,
+ tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{
+ Borders: tw.Border{Top: tw.Off, Bottom: tw.Off, Left: tw.On, Right: tw.Off},
+ Settings: tw.Settings{
+ Separators: tw.Separators{BetweenRows: tw.Off, BetweenColumns: tw.Off},
+ },
+ Symbols: tw.NewSymbolCustom("Proto").WithColumn(" "),
+ })),
+ tablewriter.WithConfig(tablewriter.Config{
+ Row: tw.CellConfig{
+ Formatting: tw.CellFormatting{AutoWrap: tw.WrapNone},
+ },
+ }),
+ )
+ table.Bulk(array)
+ table.Render()
+ stContent := buffer.String()
+ // Let's do this hack of table writer for indent!
+ stContent = regexp.MustCompile(`\s+\n`).ReplaceAllString(gstr.Replace(stContent, " #", ""), "\n")
+ buffer.Reset()
+ buffer.WriteString(fmt.Sprintf("message %s {\n", entityName))
+ buffer.WriteString(stContent)
+ buffer.WriteString("}")
+ return buffer.String(), appendImports
+}
+
+// generateMessageFieldForPbEntity generates and returns the message definition for specified field.
+func generateMessageFieldForPbEntity(index int, field *gdb.TableField, in CGenPbEntityInternalInput) (attrLines []string, appendImport string) {
+ var (
+ localTypeNameStr string
+ localTypeName gdb.LocalType
+ comment string
+ jsonTagStr string
+ err error
+ ctx = gctx.GetInitCtx()
+ )
+ if in.TypeMapping != nil && len(in.TypeMapping) > 0 {
+ // match typeMapping after local type transform.
+ // eg: double => string, varchar => string etc.
+ localTypeName, err = in.DB.CheckLocalTypeForField(ctx, field.Type, nil)
+ if err != nil {
+ panic(err)
+ }
+ if localTypeName != "" {
+ if typeMappingLocal, localOk := in.TypeMapping[strings.ToLower(string(localTypeName))]; localOk {
+ localTypeNameStr = typeMappingLocal.Type
+ appendImport = typeMappingLocal.Import
+ }
+ }
+ // Try match unknown / string localTypeName with db type.
+ if localTypeName == "" || localTypeName == gdb.LocalTypeString {
+ formattedFieldType, _ := in.DB.GetFormattedDBTypeNameForField(field.Type)
+ if typeMapping, ok := in.TypeMapping[strings.ToLower(formattedFieldType)]; ok {
+ localTypeNameStr = typeMapping.Type
+ appendImport = typeMapping.Import
+ }
+ }
+ }
+
+ if localTypeNameStr == "" {
+ localTypeNameStr = "string"
+ }
+
+ comment = gstr.ReplaceByArray(field.Comment, g.SliceStr{
+ "\n", " ",
+ "\r", " ",
+ })
+ comment = gstr.Trim(comment)
+ comment = gstr.Replace(comment, `\n`, " ")
+ comment, _ = gregex.ReplaceString(`\s{2,}`, ` `, comment)
+ if jsonTagName := formatCase(field.Name, in.JsonCase); jsonTagName != "" {
+ jsonTagStr = fmt.Sprintf(`[json_name = "%s"]`, jsonTagName)
+ // beautiful indent.
+ if index < 10 {
+ // 3 spaces
+ jsonTagStr = " " + jsonTagStr
+ } else if index < 100 {
+ // 2 spaces
+ jsonTagStr = " " + jsonTagStr
+ } else {
+ // 1 spaces
+ jsonTagStr = " " + jsonTagStr
+ }
+ }
+
+ removeFieldPrefixArray := gstr.SplitAndTrim(in.RemoveFieldPrefix, ",")
+ newFiledName := field.Name
+ for _, v := range removeFieldPrefixArray {
+ newFiledName = gstr.TrimLeftStr(newFiledName, v, 1)
+ }
+
+ if in.FieldMapping != nil && len(in.FieldMapping) > 0 {
+ if typeMapping, ok := in.FieldMapping[fmt.Sprintf("%s.%s", in.TableName, newFiledName)]; ok {
+ localTypeNameStr = typeMapping.Type
+ appendImport = typeMapping.Import
+ }
+ }
+
+ return []string{
+ " #" + localTypeNameStr,
+ " #" + formatCase(newFiledName, in.NameCase),
+ " #= " + gconv.String(index) + jsonTagStr + ";",
+ " #" + fmt.Sprintf(`// %s`, comment),
+ }, appendImport
+}
+
+func getTplPbEntityContent(tplEntityPath string) string {
+ if tplEntityPath != "" {
+ return gfile.GetContents(tplEntityPath)
+ }
+ return consts.TemplatePbEntityMessageContent
+}
+
+// formatCase call gstr.Case* function to convert the s to specified case.
+func formatCase(str, caseStr string) string {
+ if caseStr == "none" {
+ return ""
+ }
+ return gstr.CaseConvert(str, gstr.CaseTypeMatch(caseStr))
+}
+
+func sortFieldKeyForPbEntity(fieldMap map[string]*gdb.TableField) []string {
+ names := make(map[int]string)
+ for _, field := range fieldMap {
+ names[field.Index] = field.Name
+ }
+ var (
+ result = make([]string, len(names))
+ i = 0
+ j = 0
+ )
+ for {
+ if len(names) == 0 {
+ break
+ }
+ if val, ok := names[i]; ok {
+ result[j] = val
+ j++
+ delete(names, i)
+ }
+ i++
+ }
+ return result
+}
diff --git a/server/internal/library/hggen/internal/cmd/genservice/genservice.go b/server/internal/library/hggen/internal/cmd/genservice/genservice.go
new file mode 100644
index 0000000..79f66ab
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/genservice/genservice.go
@@ -0,0 +1,391 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package genservice
+
+import (
+ "context"
+ "fmt"
+ "path/filepath"
+ "sync"
+ "sync/atomic"
+
+ "github.com/gogf/gf/v2/container/garray"
+ "github.com/gogf/gf/v2/container/gmap"
+ "github.com/gogf/gf/v2/container/gset"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gtime"
+ "github.com/gogf/gf/v2/text/gregex"
+ "github.com/gogf/gf/v2/text/gstr"
+ "github.com/gogf/gf/v2/util/gconv"
+ "github.com/gogf/gf/v2/util/gtag"
+
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+ "hotgo/internal/library/hggen/internal/utility/utils"
+)
+
+const (
+ CGenServiceConfig = `gfcli.gen.service`
+ CGenServiceUsage = `gf gen service [OPTION]`
+ CGenServiceBrief = `parse struct and associated functions from packages to generate service go file`
+ CGenServiceEg = `
+gf gen service
+gf gen service -f Snake
+`
+ CGenServiceBriefSrcFolder = `source folder path to be parsed. default: internal/logic`
+ CGenServiceBriefDstFolder = `destination folder path storing automatically generated go files. default: internal/service`
+ CGenServiceBriefFileNameCase = `
+destination file name storing automatically generated go files, cases are as follows:
+| Case | Example |
+|---------------- |--------------------|
+| Lower | anykindofstring |
+| Camel | AnyKindOfString |
+| CamelLower | anyKindOfString |
+| Snake | any_kind_of_string | default
+| SnakeScreaming | ANY_KIND_OF_STRING |
+| SnakeFirstUpper | rgb_code_md5 |
+| Kebab | any-kind-of-string |
+| KebabScreaming | ANY-KIND-OF-STRING |
+`
+ CGenServiceBriefWatchFile = `used in file watcher, it re-generates all service go files only if given file is under srcFolder`
+ CGenServiceBriefStPattern = `regular expression matching struct name for generating service. default: ^s([A-Z]\\\\w+)$`
+ CGenServiceBriefPackages = `produce go files only for given source packages(source folders)`
+ CGenServiceBriefImportPrefix = `custom import prefix to calculate import path for generated importing go file of logic`
+ CGenServiceBriefClear = `delete all generated go files that are not used any further`
+)
+
+func init() {
+ gtag.Sets(g.MapStrStr{
+ `CGenServiceConfig`: CGenServiceConfig,
+ `CGenServiceUsage`: CGenServiceUsage,
+ `CGenServiceBrief`: CGenServiceBrief,
+ `CGenServiceEg`: CGenServiceEg,
+ `CGenServiceBriefSrcFolder`: CGenServiceBriefSrcFolder,
+ `CGenServiceBriefDstFolder`: CGenServiceBriefDstFolder,
+ `CGenServiceBriefFileNameCase`: CGenServiceBriefFileNameCase,
+ `CGenServiceBriefWatchFile`: CGenServiceBriefWatchFile,
+ `CGenServiceBriefStPattern`: CGenServiceBriefStPattern,
+ `CGenServiceBriefPackages`: CGenServiceBriefPackages,
+ `CGenServiceBriefImportPrefix`: CGenServiceBriefImportPrefix,
+ `CGenServiceBriefClear`: CGenServiceBriefClear,
+ })
+}
+
+type (
+ CGenService struct{}
+ CGenServiceInput struct {
+ g.Meta `name:"service" config:"{CGenServiceConfig}" usage:"{CGenServiceUsage}" brief:"{CGenServiceBrief}" eg:"{CGenServiceEg}"`
+ SrcFolder string `short:"s" name:"srcFolder" brief:"{CGenServiceBriefSrcFolder}" d:"internal/logic"`
+ DstFolder string `short:"d" name:"dstFolder" brief:"{CGenServiceBriefDstFolder}" d:"internal/service"`
+ DstFileNameCase string `short:"f" name:"dstFileNameCase" brief:"{CGenServiceBriefFileNameCase}" d:"Snake"`
+ WatchFile string `short:"w" name:"watchFile" brief:"{CGenServiceBriefWatchFile}"`
+ StPattern string `short:"a" name:"stPattern" brief:"{CGenServiceBriefStPattern}" d:"^s([A-Z]\\w+)$"`
+ Packages []string `short:"p" name:"packages" brief:"{CGenServiceBriefPackages}"`
+ ImportPrefix string `short:"i" name:"importPrefix" brief:"{CGenServiceBriefImportPrefix}"`
+ Clear bool `short:"l" name:"clear" brief:"{CGenServiceBriefClear}" orphan:"true"`
+ }
+ CGenServiceOutput struct{}
+)
+
+const (
+ genServiceFileLockSeconds = 10
+)
+
+type fileInfo struct {
+ PkgItems []pkgItem
+ FuncItems []funcItem
+}
+
+type folderInfo struct {
+ SrcPackageName string
+ SrcImportedPackages *garray.SortedStrArray
+ SrcStructFunctions *gmap.ListMap
+ DstFilePath string
+
+ FileInfos []*fileInfo
+}
+
+func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGenServiceOutput, err error) {
+ in.SrcFolder = filepath.ToSlash(in.SrcFolder)
+ in.SrcFolder = gstr.TrimRight(in.SrcFolder, `/`)
+ in.WatchFile = filepath.ToSlash(in.WatchFile)
+ in.WatchFile = gstr.TrimRight(in.WatchFile, `/`)
+
+ // Watch file handling.
+ if in.WatchFile != "" {
+ // File lock to avoid multiple processes.
+ var (
+ flockFilePath = gfile.Temp("gf.cli.gen.service.lock")
+ flockContent = gfile.GetContents(flockFilePath)
+ )
+ if flockContent != "" {
+ if gtime.Timestamp()-gconv.Int64(flockContent) < genServiceFileLockSeconds {
+ // If another "gen service" process is running, it just exits.
+ mlog.Debug(`another "gen service" process is running, exit`)
+ return
+ }
+ }
+ defer gfile.RemoveFile(flockFilePath)
+ _ = gfile.PutContents(flockFilePath, gtime.TimestampStr())
+
+ // It works only if given WatchFile is in SrcFolder.
+ var (
+ watchFileDir = gfile.Dir(in.WatchFile)
+ srcFolderDir = gfile.Dir(watchFileDir)
+ )
+ mlog.Debug("watchFileDir:", watchFileDir)
+ mlog.Debug("logicFolderDir:", srcFolderDir)
+ if !gstr.HasSuffix(gstr.Replace(srcFolderDir, `\`, `/`), in.SrcFolder) {
+ mlog.Printf(`ignore watch file "%s", not in source path "%s"`, in.WatchFile, in.SrcFolder)
+ return
+ }
+ var newWorkingDir = gfile.Dir(gfile.Dir(srcFolderDir))
+ if err = gfile.Chdir(newWorkingDir); err != nil {
+ mlog.Fatalf(`%+v`, err)
+ }
+ mlog.Debug("Chdir:", newWorkingDir)
+
+ in.WatchFile = ""
+ in.Packages = []string{gfile.Basename(watchFileDir)}
+ return c.Service(ctx, in)
+ }
+
+ if !gfile.Exists(in.SrcFolder) {
+ mlog.Fatalf(`source folder path "%s" does not exist`, in.SrcFolder)
+ }
+
+ if in.ImportPrefix == "" {
+ in.ImportPrefix = utils.GetImportPath(in.SrcFolder)
+ }
+
+ var (
+ isDirty atomic.Value // Temp boolean.
+ files []string // Temp file array.
+ initImportSrcPackages []string // Used for generating logic.go.
+ inputPackages = in.Packages // Custom packages.
+ dstPackageName = gstr.ToLower(gfile.Basename(in.DstFolder)) // Package name for generated go files.
+ generatedDstFilePathSet = gset.NewStrSet() // All generated file path set.
+ )
+ isDirty.Store(false)
+
+ // The first level folders.
+ srcFolderPaths, err := gfile.ScanDir(in.SrcFolder, "*", false)
+ if err != nil {
+ return nil, err
+ }
+ // it will use goroutine to generate service files for each package.
+ var (
+ folderInfos []folderInfo
+ wg = sync.WaitGroup{}
+ allStructItems = make(map[string][]string)
+ )
+
+ for _, srcFolderPath := range srcFolderPaths {
+ if !gfile.IsDir(srcFolderPath) {
+ continue
+ }
+ // Only retrieve sub files, no recursively.
+ if files, err = gfile.ScanDir(srcFolderPath, "*.go", false); err != nil {
+ return nil, err
+ }
+ if len(files) == 0 {
+ continue
+ }
+
+ var (
+ srcPackageName = gfile.Basename(srcFolderPath)
+ srcImportedPackages = garray.NewSortedStrArray().SetUnique(true)
+ srcStructFunctions = gmap.NewListMap()
+ dstFilePath = gfile.Join(in.DstFolder,
+ c.getDstFileNameCase(srcPackageName, in.DstFileNameCase)+".go",
+ )
+ )
+
+ folder := folderInfo{
+ SrcPackageName: srcPackageName,
+ SrcImportedPackages: srcImportedPackages,
+ SrcStructFunctions: srcStructFunctions,
+ DstFilePath: dstFilePath,
+ }
+
+ for _, file := range files {
+ pkgItems, structItems, funcItems, err := c.parseItemsInSrc(file)
+ if err != nil {
+ return nil, err
+ }
+ for k, v := range structItems {
+ allStructItems[k] = v
+ }
+ folder.FileInfos = append(folder.FileInfos, &fileInfo{
+ PkgItems: pkgItems,
+ FuncItems: funcItems,
+ })
+ }
+
+ folderInfos = append(folderInfos, folder)
+ }
+
+ folderInfos = c.calculateStructEmbeddedFuncInfos(folderInfos, allStructItems)
+
+ for _, folder := range folderInfos {
+ // Parse single logic package folder.
+ var (
+ srcPackageName = folder.SrcPackageName
+ srcImportedPackages = folder.SrcImportedPackages
+ srcStructFunctions = folder.SrcStructFunctions
+ dstFilePath = folder.DstFilePath
+ )
+ generatedDstFilePathSet.Add(dstFilePath)
+ // if it were to use goroutine,
+ // it would cause the order of the generated functions in the file to be disordered.
+ for _, file := range folder.FileInfos {
+ pkgItems, funcItems := file.PkgItems, file.FuncItems
+
+ // Calculate imported packages for service generating.
+ err = c.calculateImportedItems(in, pkgItems, funcItems, srcImportedPackages)
+ if err != nil {
+ return nil, err
+ }
+
+ // Calculate functions and interfaces for service generating.
+ err = c.calculateFuncItems(in, funcItems, srcStructFunctions)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ initImportSrcPackages = append(
+ initImportSrcPackages,
+ fmt.Sprintf(`%s/%s`, in.ImportPrefix, srcPackageName),
+ )
+ // Ignore source packages if input packages given.
+ if len(inputPackages) > 0 && !gstr.InArray(inputPackages, srcPackageName) {
+ mlog.Debugf(
+ `ignore source package "%s" as it is not in desired packages: %+v`,
+ srcPackageName, inputPackages,
+ )
+ continue
+ }
+
+ // Generating service go file for single logic package.
+ wg.Add(1)
+ go func(generateServiceFilesInput generateServiceFilesInput) {
+ defer wg.Done()
+ ok, err := c.generateServiceFile(generateServiceFilesInput)
+ if err != nil {
+ mlog.Printf(`error generating service file "%s": %v`, generateServiceFilesInput.DstFilePath, err)
+ }
+ if !isDirty.Load().(bool) && ok {
+ isDirty.Store(true)
+ }
+ }(generateServiceFilesInput{
+ CGenServiceInput: in,
+ SrcPackageName: srcPackageName,
+ SrcImportedPackages: srcImportedPackages.Slice(),
+ SrcStructFunctions: srcStructFunctions,
+ DstPackageName: dstPackageName,
+ DstFilePath: dstFilePath,
+ })
+ }
+ wg.Wait()
+
+ if in.Clear {
+ files, err = gfile.ScanDirFile(in.DstFolder, "*.go", false)
+ if err != nil {
+ return nil, err
+ }
+ var relativeFilePath string
+ for _, file := range files {
+ relativeFilePath = gstr.SubStrFromR(file, in.DstFolder)
+ if !generatedDstFilePathSet.Contains(relativeFilePath) &&
+ utils.IsFileDoNotEdit(relativeFilePath) {
+
+ mlog.Printf(`remove no longer used service file: %s`, relativeFilePath)
+ if err = gfile.RemoveFile(file); err != nil {
+ return nil, err
+ }
+ }
+ }
+ }
+
+ if isDirty.Load().(bool) {
+ // Generate initialization go file.
+ if len(initImportSrcPackages) > 0 {
+ if err = c.generateInitializationFile(in, initImportSrcPackages); err != nil {
+ return
+ }
+ }
+
+ // Replace v1 to v2 for GoFrame.
+ if err = utils.ReplaceGeneratedContentGFV2(in.DstFolder); err != nil {
+ return nil, err
+ }
+ mlog.Printf(`gofmt go files in "%s"`, in.DstFolder)
+ utils.GoFmt(in.DstFolder)
+ }
+
+ // auto update main.go.
+ if err = c.checkAndUpdateMain(in.SrcFolder); err != nil {
+ return nil, err
+ }
+
+ mlog.Print(`done!`)
+ return
+}
+
+func (c CGenService) checkAndUpdateMain(srcFolder string) (err error) {
+ var (
+ logicPackageName = gstr.ToLower(gfile.Basename(srcFolder))
+ logicFilePath = gfile.Join(srcFolder, logicPackageName+".go")
+ importPath = utils.GetImportPath(srcFolder)
+ importStr = fmt.Sprintf(`_ "%s"`, importPath)
+ mainFilePath = gfile.Join(gfile.Dir(gfile.Dir(gfile.Dir(logicFilePath))), "main.go")
+ mainFileContent = gfile.GetContents(mainFilePath)
+ )
+ // No main content found.
+ if mainFileContent == "" {
+ return nil
+ }
+ if gstr.Contains(mainFileContent, importStr) {
+ return nil
+ }
+ match, err := gregex.MatchString(`import \(([\s\S]+?)\)`, mainFileContent)
+ if err != nil {
+ return err
+ }
+ // No match.
+ if len(match) < 2 {
+ return nil
+ }
+ lines := garray.NewStrArrayFrom(gstr.Split(match[1], "\n"))
+ for i, line := range lines.Slice() {
+ line = gstr.Trim(line)
+ if len(line) == 0 {
+ continue
+ }
+ if line[0] == '_' {
+ continue
+ }
+ // Insert the logic import into imports.
+ if err = lines.InsertBefore(i, fmt.Sprintf("\t%s\n\n", importStr)); err != nil {
+ return err
+ }
+ break
+ }
+ mainFileContent, err = gregex.ReplaceString(
+ `import \(([\s\S]+?)\)`,
+ fmt.Sprintf(`import (%s)`, lines.Join("\n")),
+ mainFileContent,
+ )
+ if err != nil {
+ return err
+ }
+ mlog.Print(`update main.go`)
+ err = gfile.PutContents(mainFilePath, mainFileContent)
+ utils.GoFmt(mainFilePath)
+ return
+}
diff --git a/server/internal/library/hggen/internal/cmd/genservice/genservice_ast_parse.go b/server/internal/library/hggen/internal/cmd/genservice/genservice_ast_parse.go
new file mode 100644
index 0000000..974ec7e
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/genservice/genservice_ast_parse.go
@@ -0,0 +1,319 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package genservice
+
+import (
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "strings"
+
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gstructs"
+ "github.com/gogf/gf/v2/text/gstr"
+)
+
+type pkgItem struct {
+ Alias string `eg:"gdbas"`
+ Path string `eg:"github.com/gogf/gf/v2/database/gdb"`
+ RawImport string `eg:"gdbas github.com/gogf/gf/v2/database/gdb"`
+}
+
+type funcItem struct {
+ Receiver string `eg:"sUser"`
+ MethodName string `eg:"GetList"`
+ Params []map[string]string `eg:"ctx: context.Context, cond: *SearchInput"`
+ Results []map[string]string `eg:"list: []*User, err: error"`
+ Comment string `eg:"Get user list"`
+}
+
+// parseItemsInSrc parses the pkgItem and funcItem from the specified file.
+// It can't skip the private methods.
+// It can't skip the imported packages of import alias equal to `_`.
+func (c CGenService) parseItemsInSrc(filePath string) (pkgItems []pkgItem, structItems map[string][]string, funcItems []funcItem, err error) {
+ var (
+ fileContent = gfile.GetContents(filePath)
+ fileSet = token.NewFileSet()
+ )
+
+ node, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments)
+ if err != nil {
+ return
+ }
+
+ structItems = make(map[string][]string)
+ pkg := node.Name.Name
+ pkgAliasMap := make(map[string]string)
+ ast.Inspect(node, func(n ast.Node) bool {
+ switch x := n.(type) {
+ case *ast.ImportSpec:
+ // parse the imported packages.
+ pkgItem := c.parseImportPackages(x)
+ pkgItems = append(pkgItems, pkgItem)
+ pkgPath := strings.Trim(pkgItem.Path, "\"")
+ pkgPath = strings.ReplaceAll(pkgPath, "\\", "/")
+ tmp := strings.Split(pkgPath, "/")
+ srcPkg := tmp[len(tmp)-1]
+ if srcPkg != pkgItem.Alias {
+ pkgAliasMap[pkgItem.Alias] = srcPkg
+ }
+ case *ast.TypeSpec: // type define
+ switch xType := x.Type.(type) {
+ case *ast.StructType: // define struct
+ // parse the struct declaration.
+ var structName = pkg + "." + x.Name.Name
+ var structEmbeddedStruct []string
+ for _, field := range xType.Fields.List {
+ if len(field.Names) > 0 || field.Tag == nil { // not anonymous field
+ continue
+ }
+
+ tagValue := strings.Trim(field.Tag.Value, "`")
+ tagValue = strings.TrimSpace(tagValue)
+ if len(tagValue) == 0 { // not set tag
+ continue
+ }
+ tags := gstructs.ParseTag(tagValue)
+
+ if v, ok := tags["gen"]; !ok || v != "extend" {
+ continue
+ }
+
+ var embeddedStruct string
+ switch v := field.Type.(type) {
+ case *ast.Ident:
+ if embeddedStruct, err = c.astExprToString(v); err != nil {
+ embeddedStruct = ""
+ break
+ }
+ embeddedStruct = pkg + "." + embeddedStruct
+ case *ast.StarExpr:
+ if embeddedStruct, err = c.astExprToString(v.X); err != nil {
+ embeddedStruct = ""
+ break
+ }
+ embeddedStruct = pkg + "." + embeddedStruct
+ case *ast.SelectorExpr:
+ var pkg string
+ if pkg, err = c.astExprToString(v.X); err != nil {
+ embeddedStruct = ""
+ break
+ }
+ if v, ok := pkgAliasMap[pkg]; ok {
+ pkg = v
+ }
+ if embeddedStruct, err = c.astExprToString(v.Sel); err != nil {
+ embeddedStruct = ""
+ break
+ }
+ embeddedStruct = pkg + "." + embeddedStruct
+ }
+
+ if embeddedStruct == "" {
+ continue
+ }
+ structEmbeddedStruct = append(structEmbeddedStruct, embeddedStruct)
+
+ }
+ if len(structEmbeddedStruct) > 0 {
+ structItems[structName] = structEmbeddedStruct
+ }
+ case *ast.Ident: // define ident
+ var (
+ structName = pkg + "." + x.Name.Name
+ typeName = pkg + "." + xType.Name
+ )
+ structItems[structName] = []string{typeName}
+ case *ast.SelectorExpr: // define selector
+ var (
+ structName = pkg + "." + x.Name.Name
+ selecotrPkg string
+ typeName string
+ )
+ if selecotrPkg, err = c.astExprToString(xType.X); err != nil {
+ break
+ }
+ if v, ok := pkgAliasMap[selecotrPkg]; ok {
+ selecotrPkg = v
+ }
+ if typeName, err = c.astExprToString(xType.Sel); err != nil {
+ break
+ }
+ typeName = selecotrPkg + "." + typeName
+ structItems[structName] = []string{typeName}
+ }
+
+ case *ast.FuncDecl:
+ // parse the function items.
+ if x.Recv == nil {
+ return true
+ }
+
+ var funcName = x.Name.Name
+ funcItems = append(funcItems, funcItem{
+ Receiver: c.parseFuncReceiverTypeName(x),
+ MethodName: funcName,
+ Params: c.parseFuncParams(x),
+ Results: c.parseFuncResults(x),
+ Comment: c.parseFuncComment(x),
+ })
+ }
+ return true
+ })
+ return
+}
+
+// parseImportPackages retrieves the imported packages from the specified ast.ImportSpec.
+func (c CGenService) parseImportPackages(node *ast.ImportSpec) (packages pkgItem) {
+ if node.Path == nil {
+ return
+ }
+ var (
+ alias string
+ path = node.Path.Value
+ rawImport string
+ )
+
+ if node.Name != nil {
+ alias = node.Name.Name
+ rawImport = node.Name.Name + " " + path
+ } else {
+ rawImport = path
+ }
+
+ // if the alias is empty, it will further retrieve the real alias.
+ if alias == "" {
+ alias = c.getRealAlias(path)
+ }
+
+ return pkgItem{
+ Alias: alias,
+ Path: path,
+ RawImport: rawImport,
+ }
+}
+
+// getRealAlias retrieves the real alias of the package.
+// If package is "github.com/gogf/gf", the alias is "gf".
+// If package is "github.com/gogf/gf/v2", the alias is "gf" instead of "v2".
+func (c CGenService) getRealAlias(importPath string) (pkgName string) {
+ importPath = gstr.Trim(importPath, `"`)
+ parts := gstr.Split(importPath, "/")
+ if len(parts) == 0 {
+ return
+ }
+
+ pkgName = parts[len(parts)-1]
+
+ if !gstr.HasPrefix(pkgName, "v") {
+ return pkgName
+ }
+
+ if len(parts) < 2 {
+ return pkgName
+ }
+
+ if gstr.IsNumeric(gstr.SubStr(pkgName, 1)) {
+ pkgName = parts[len(parts)-2]
+ }
+
+ return pkgName
+}
+
+// parseFuncReceiverTypeName retrieves the receiver type of the function.
+// For example:
+//
+// func(s *sArticle) -> *sArticle
+// func(s sArticle) -> sArticle
+func (c CGenService) parseFuncReceiverTypeName(node *ast.FuncDecl) (receiverType string) {
+ if node.Recv == nil {
+ return ""
+ }
+ receiverType, err := c.astExprToString(node.Recv.List[0].Type)
+ if err != nil {
+ return ""
+ }
+ return
+}
+
+// parseFuncParams retrieves the input parameters of the function.
+// It returns the name and type of the input parameters.
+// For example:
+//
+// []map[string]string{paramName:ctx paramType:context.Context, paramName:info paramType:struct{}}
+func (c CGenService) parseFuncParams(node *ast.FuncDecl) (params []map[string]string) {
+ if node.Type.Params == nil {
+ return
+ }
+ for _, param := range node.Type.Params.List {
+ if param.Names == nil {
+ // No name for the return value.
+ resultType, err := c.astExprToString(param.Type)
+ if err != nil {
+ continue
+ }
+ params = append(params, map[string]string{
+ "paramName": "",
+ "paramType": resultType,
+ })
+ continue
+ }
+ for _, name := range param.Names {
+ paramType, err := c.astExprToString(param.Type)
+ if err != nil {
+ continue
+ }
+ params = append(params, map[string]string{
+ "paramName": name.Name,
+ "paramType": paramType,
+ })
+ }
+ }
+ return
+}
+
+// parseFuncResults retrieves the output parameters of the function.
+// It returns the name and type of the output parameters.
+// For example:
+//
+// []map[string]string{resultName:list resultType:[]*User, resultName:err resultType:error}
+// []map[string]string{resultName: "", resultType: error}
+func (c CGenService) parseFuncResults(node *ast.FuncDecl) (results []map[string]string) {
+ if node.Type.Results == nil {
+ return
+ }
+ for _, result := range node.Type.Results.List {
+ if result.Names == nil {
+ // No name for the return value.
+ resultType, err := c.astExprToString(result.Type)
+ if err != nil {
+ continue
+ }
+ results = append(results, map[string]string{
+ "resultName": "",
+ "resultType": resultType,
+ })
+ continue
+ }
+ for _, name := range result.Names {
+ resultType, err := c.astExprToString(result.Type)
+ if err != nil {
+ continue
+ }
+ results = append(results, map[string]string{
+ "resultName": name.Name,
+ "resultType": resultType,
+ })
+ }
+ }
+ return
+}
+
+// parseFuncComment retrieves the comment of the function.
+func (c CGenService) parseFuncComment(node *ast.FuncDecl) string {
+ return c.astCommentToString(node.Doc)
+}
diff --git a/server/internal/library/hggen/internal/cmd/genservice/genservice_ast_utils.go b/server/internal/library/hggen/internal/cmd/genservice/genservice_ast_utils.go
new file mode 100644
index 0000000..8fc12e2
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/genservice/genservice_ast_utils.go
@@ -0,0 +1,48 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package genservice
+
+import (
+ "bytes"
+ "go/ast"
+ "go/format"
+ "go/token"
+ "strings"
+)
+
+// exprToString converts ast.Expr to string.
+// For example:
+//
+// ast.Expr -> "context.Context"
+// ast.Expr -> "*v1.XxxReq"
+// ast.Expr -> "error"
+// ast.Expr -> "int"
+func (c CGenService) astExprToString(expr ast.Expr) (string, error) {
+ var (
+ buf bytes.Buffer
+ err error
+ )
+ err = format.Node(&buf, token.NewFileSet(), expr)
+ if err != nil {
+ return "", err
+ }
+ return buf.String(), nil
+}
+
+// astCommentToString returns the raw (original) text of the comment.
+// It includes the comment markers (//, /*, and */).
+// It adds a newline at the end of the comment.
+func (c CGenService) astCommentToString(node *ast.CommentGroup) string {
+ if node == nil {
+ return ""
+ }
+ var b strings.Builder
+ for _, c := range node.List {
+ b.WriteString(c.Text + "\n")
+ }
+ return b.String()
+}
diff --git a/server/internal/library/hggen/internal/cmd/genservice/genservice_calculate.go b/server/internal/library/hggen/internal/cmd/genservice/genservice_calculate.go
new file mode 100644
index 0000000..4225fd9
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/genservice/genservice_calculate.go
@@ -0,0 +1,228 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package genservice
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/gogf/gf/v2/container/garray"
+ "github.com/gogf/gf/v2/container/gmap"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/text/gregex"
+ "github.com/gogf/gf/v2/text/gstr"
+
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+)
+
+func (c CGenService) calculateImportedItems(
+ in CGenServiceInput,
+ pkgItems []pkgItem, funcItems []funcItem,
+ srcImportedPackages *garray.SortedStrArray,
+) (err error) {
+ // allFuncParamType saves all the param and result types of the functions.
+ var allFuncParamType strings.Builder
+
+ for _, item := range funcItems {
+ for _, param := range item.Params {
+ allFuncParamType.WriteString(param["paramType"] + ",")
+ }
+ for _, result := range item.Results {
+ allFuncParamType.WriteString(result["resultType"] + ",")
+ }
+ }
+
+ for _, item := range pkgItems {
+ alias := item.Alias
+
+ // If the alias is _, it means that the package is not generated.
+ if alias == "_" {
+ mlog.Debugf(`ignore anonymous package: %s`, item.RawImport)
+ continue
+ }
+ // If the alias is empty, it will use the package name as the alias.
+ if alias == "" {
+ alias = gfile.Basename(gstr.Trim(item.Path, `"`))
+ }
+ if !gstr.Contains(allFuncParamType.String(), alias) {
+ mlog.Debugf(`ignore unused package: %s`, item.RawImport)
+ continue
+ }
+ srcImportedPackages.Add(item.RawImport)
+ }
+ return nil
+}
+
+func (c CGenService) calculateFuncItems(
+ in CGenServiceInput,
+ funcItems []funcItem,
+ srcPkgInterfaceMap *gmap.ListMap,
+) (err error) {
+ var srcPkgInterfaceFunc []map[string]string
+
+ for _, item := range funcItems {
+ var (
+ // eg: "sArticle"
+ receiverName string
+ receiverMatch []string
+
+ // eg: "GetList(ctx context.Context, req *v1.ArticleListReq) (list []*v1.Article, err error)"
+ funcHead string
+ )
+
+ // handle the receiver name.
+ if item.Receiver == "" {
+ continue
+ }
+ receiverName = item.Receiver
+ receiverName = gstr.Trim(receiverName, "*")
+ // Match and pick the struct name from receiver.
+ if receiverMatch, err = gregex.MatchString(in.StPattern, receiverName); err != nil {
+ return err
+ }
+ if len(receiverMatch) < 1 {
+ continue
+ }
+ receiverName = gstr.CaseCamel(receiverMatch[1])
+
+ // check if the func name is public.
+ if !gstr.IsLetterUpper(item.MethodName[0]) {
+ continue
+ }
+
+ if !srcPkgInterfaceMap.Contains(receiverName) {
+ srcPkgInterfaceFunc = make([]map[string]string, 0)
+ srcPkgInterfaceMap.Set(receiverName, srcPkgInterfaceFunc)
+ } else {
+ srcPkgInterfaceFunc = srcPkgInterfaceMap.Get(receiverName).([]map[string]string)
+ }
+
+ // make the func head.
+ paramsStr := c.tidyParam(item.Params)
+ resultsStr := c.tidyResult(item.Results)
+ funcHead = fmt.Sprintf("%s(%s) (%s)", item.MethodName, paramsStr, resultsStr)
+
+ srcPkgInterfaceFunc = append(srcPkgInterfaceFunc, map[string]string{
+ "funcHead": funcHead,
+ "funcComment": item.Comment,
+ })
+ srcPkgInterfaceMap.Set(receiverName, srcPkgInterfaceFunc)
+ }
+ return nil
+}
+
+// tidyParam tidies the input parameters.
+// For example:
+//
+// []map[string]string{paramName:ctx paramType:context.Context, paramName:info paramType:struct{}}
+// -> ctx context.Context, info struct{}
+func (c CGenService) tidyParam(paramSlice []map[string]string) (paramStr string) {
+ for i, param := range paramSlice {
+ if i > 0 {
+ paramStr += ", "
+ }
+ paramStr += fmt.Sprintf("%s %s", param["paramName"], param["paramType"])
+ }
+ return
+}
+
+// tidyResult tidies the output parameters.
+// For example:
+//
+// []map[string]string{resultName:list resultType:[]*User, resultName:err resultType:error}
+// -> list []*User, err error
+//
+// []map[string]string{resultName: "", resultType: error}
+// -> error
+func (c CGenService) tidyResult(resultSlice []map[string]string) (resultStr string) {
+ for i, result := range resultSlice {
+ if i > 0 {
+ resultStr += ", "
+ }
+ if result["resultName"] != "" {
+ resultStr += fmt.Sprintf("%s %s", result["resultName"], result["resultType"])
+ } else {
+ resultStr += result["resultType"]
+ }
+ }
+ return
+}
+
+func (c CGenService) getStructFuncItems(structName string, allStructItems map[string][]string, funcItemsWithoutEmbed map[string][]*funcItem) (funcItems []*funcItem) {
+ funcItemNameSet := map[string]struct{}{}
+
+ if items, ok := funcItemsWithoutEmbed[structName]; ok {
+ funcItems = append(funcItems, items...)
+ for _, item := range items {
+ funcItemNameSet[item.MethodName] = struct{}{}
+ }
+ }
+
+ embeddedStructNames, ok := allStructItems[structName]
+ if !ok {
+ return
+ }
+
+ for _, embeddedStructName := range embeddedStructNames {
+ items := c.getStructFuncItems(embeddedStructName, allStructItems, funcItemsWithoutEmbed)
+
+ for _, item := range items {
+ if _, ok := funcItemNameSet[item.MethodName]; ok {
+ continue
+ }
+ funcItemNameSet[item.MethodName] = struct{}{}
+ funcItems = append(funcItems, item)
+ }
+ }
+
+ return
+}
+
+func (c CGenService) calculateStructEmbeddedFuncInfos(folderInfos []folderInfo, allStructItems map[string][]string) (newFolerInfos []folderInfo) {
+ funcItemsWithoutEmbed := make(map[string][]*funcItem)
+ funcItemMap := make(map[string]*([]funcItem))
+ funcItemsWithoutEmbedMap := make(map[string]*funcItem)
+
+ newFolerInfos = append(newFolerInfos, folderInfos...)
+
+ for _, folder := range newFolerInfos {
+ for k := range folder.FileInfos {
+ fi := folder.FileInfos[k]
+ for k := range fi.FuncItems {
+ item := &fi.FuncItems[k]
+ receiver := folder.SrcPackageName + "." + strings.ReplaceAll(item.Receiver, "*", "")
+ funcItemMap[receiver] = &fi.FuncItems
+ funcItemsWithoutEmbed[receiver] = append(funcItemsWithoutEmbed[receiver], item)
+ funcItemsWithoutEmbedMap[fmt.Sprintf("%s:%s", receiver, item.MethodName)] = item
+ }
+ }
+ }
+
+ for receiver, structItems := range allStructItems {
+ receiverName := strings.ReplaceAll(receiver, "*", "")
+ for _, structName := range structItems {
+ // Get the list of methods for the corresponding structName.
+ for _, funcItem := range c.getStructFuncItems(structName, allStructItems, funcItemsWithoutEmbed) {
+ if _, ok := funcItemsWithoutEmbedMap[fmt.Sprintf("%s:%s", receiverName, funcItem.MethodName)]; ok {
+ continue
+ }
+ if funcItemsPtr, ok := funcItemMap[receiverName]; ok {
+ newFuncItem := *funcItem
+ newFuncItem.Receiver = getReceiverName(receiver)
+ (*funcItemsPtr) = append((*funcItemsPtr), newFuncItem)
+ }
+ }
+ }
+ }
+
+ return
+}
+
+func getReceiverName(receiver string) string {
+ ss := strings.Split(receiver, ".")
+ return ss[len(ss)-1]
+}
diff --git a/server/internal/library/hggen/internal/cmd/genservice/genservice_generate.go b/server/internal/library/hggen/internal/cmd/genservice/genservice_generate.go
new file mode 100644
index 0000000..c18249a
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/genservice/genservice_generate.go
@@ -0,0 +1,87 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package genservice
+
+import (
+ "bytes"
+ "fmt"
+
+ "github.com/gogf/gf/v2/container/gmap"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/text/gstr"
+
+ "hotgo/internal/library/hggen/internal/consts"
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+ "hotgo/internal/library/hggen/internal/utility/utils"
+)
+
+type generateServiceFilesInput struct {
+ CGenServiceInput
+ SrcPackageName string
+ SrcImportedPackages []string
+ SrcStructFunctions *gmap.ListMap
+ DstPackageName string
+ DstFilePath string // Absolute file path for generated service go file.
+}
+
+func (c CGenService) generateServiceFile(in generateServiceFilesInput) (ok bool, err error) {
+ var generatedContent bytes.Buffer
+
+ c.generatePackageImports(&generatedContent, in.DstPackageName, in.SrcImportedPackages)
+ c.generateType(&generatedContent, in.SrcStructFunctions, in.DstPackageName)
+ c.generateVar(&generatedContent, in.SrcStructFunctions)
+ c.generateFunc(&generatedContent, in.SrcStructFunctions)
+
+ // Write file content to disk.
+ if gfile.Exists(in.DstFilePath) {
+ if !utils.IsFileDoNotEdit(in.DstFilePath) {
+ mlog.Printf(`ignore file as it is manually maintained: %s`, in.DstFilePath)
+ return false, nil
+ }
+ }
+ mlog.Printf(`generating service go file: %s`, in.DstFilePath)
+ if err = gfile.PutBytes(in.DstFilePath, generatedContent.Bytes()); err != nil {
+ return true, err
+ }
+ return true, nil
+}
+
+// generateInitializationFile generates `logic.go`.
+func (c CGenService) generateInitializationFile(in CGenServiceInput, importSrcPackages []string) (err error) {
+ var (
+ logicPackageName = gstr.ToLower(gfile.Basename(in.SrcFolder))
+ logicFilePath = gfile.Join(in.SrcFolder, logicPackageName+".go")
+ logicImports string
+ generatedContent string
+ )
+ if !utils.IsFileDoNotEdit(logicFilePath) {
+ mlog.Debugf(`ignore file as it is manually maintained: %s`, logicFilePath)
+ return nil
+ }
+ for _, importSrcPackage := range importSrcPackages {
+ logicImports += fmt.Sprintf(`%s_ "%s"%s`, "\t", importSrcPackage, "\n")
+ }
+ generatedContent = gstr.ReplaceByMap(consts.TemplateGenServiceLogicContent, g.MapStrStr{
+ "{PackageName}": logicPackageName,
+ "{Imports}": logicImports,
+ })
+ mlog.Printf(`generating init go file: %s`, logicFilePath)
+ if err = gfile.PutContents(logicFilePath, generatedContent); err != nil {
+ return err
+ }
+ utils.GoFmt(logicFilePath)
+ return nil
+}
+
+// getDstFileNameCase call gstr.Case* function to convert the s to specified case.
+func (c CGenService) getDstFileNameCase(str, caseStr string) (newStr string) {
+ if newStr := gstr.CaseConvert(str, gstr.CaseTypeMatch(caseStr)); newStr != str {
+ return newStr
+ }
+ return gstr.CaseSnake(str)
+}
diff --git a/server/internal/library/hggen/internal/cmd/genservice/genservice_generate_template.go b/server/internal/library/hggen/internal/cmd/genservice/genservice_generate_template.go
new file mode 100644
index 0000000..21ecac4
--- /dev/null
+++ b/server/internal/library/hggen/internal/cmd/genservice/genservice_generate_template.go
@@ -0,0 +1,105 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package genservice
+
+import (
+ "bytes"
+ "fmt"
+
+ "github.com/gogf/gf/v2/container/gmap"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/text/gregex"
+ "github.com/gogf/gf/v2/text/gstr"
+
+ "hotgo/internal/library/hggen/internal/consts"
+)
+
+func (c CGenService) generatePackageImports(generatedContent *bytes.Buffer, packageName string, imports []string) {
+ generatedContent.WriteString(gstr.ReplaceByMap(consts.TemplateGenServiceContentHead, g.MapStrStr{
+ "{PackageName}": packageName,
+ "{Imports}": fmt.Sprintf(
+ "import (\n%s\n)", gstr.Join(imports, "\n"),
+ ),
+ }))
+}
+
+// generateType type definitions.
+// See: const.TemplateGenServiceContentInterface
+func (c CGenService) generateType(generatedContent *bytes.Buffer, srcStructFunctions *gmap.ListMap, dstPackageName string) {
+ generatedContent.WriteString("type(")
+ generatedContent.WriteString("\n")
+
+ srcStructFunctions.Iterator(func(key, value any) bool {
+ var (
+ funcContents = make([]string, 0)
+ funcContent string
+ )
+ structName, funcSlice := key.(string), value.([]map[string]string)
+ // Generating interface content.
+ for _, funcInfo := range funcSlice {
+ // Remove package name calls of `dstPackageName` in produced codes.
+ funcHead, _ := gregex.ReplaceString(
+ fmt.Sprintf(`\*{0,1}%s\.`, dstPackageName),
+ ``, funcInfo["funcHead"],
+ )
+ funcContent = funcInfo["funcComment"] + funcHead
+ funcContents = append(funcContents, funcContent)
+ }
+
+ // funcContents to string.
+ generatedContent.WriteString(
+ gstr.Trim(gstr.ReplaceByMap(consts.TemplateGenServiceContentInterface, g.MapStrStr{
+ "{InterfaceName}": "I" + structName,
+ "{FuncDefinition}": gstr.Join(funcContents, "\n\t"),
+ })),
+ )
+ generatedContent.WriteString("\n")
+ return true
+ })
+
+ generatedContent.WriteString(")")
+ generatedContent.WriteString("\n")
+}
+
+// generateVar variable definitions.
+// See: const.TemplateGenServiceContentVariable
+func (c CGenService) generateVar(generatedContent *bytes.Buffer, srcStructFunctions *gmap.ListMap) {
+ // Generating variable and register definitions.
+ var variableContent string
+
+ srcStructFunctions.Iterator(func(key, value any) bool {
+ structName := key.(string)
+ variableContent += gstr.Trim(gstr.ReplaceByMap(consts.TemplateGenServiceContentVariable, g.MapStrStr{
+ "{StructName}": structName,
+ "{InterfaceName}": "I" + structName,
+ }))
+ variableContent += "\n"
+ return true
+ })
+ if variableContent != "" {
+ generatedContent.WriteString("var(")
+ generatedContent.WriteString("\n")
+ generatedContent.WriteString(variableContent)
+ generatedContent.WriteString(")")
+ generatedContent.WriteString("\n")
+ }
+}
+
+// generateFunc function definitions.
+// See: const.TemplateGenServiceContentRegister
+func (c CGenService) generateFunc(generatedContent *bytes.Buffer, srcStructFunctions *gmap.ListMap) {
+ // Variable register function definitions.
+ srcStructFunctions.Iterator(func(key, value any) bool {
+ structName := key.(string)
+ generatedContent.WriteString(gstr.Trim(gstr.ReplaceByMap(consts.TemplateGenServiceContentRegister, g.MapStrStr{
+ "{StructName}": structName,
+ "{InterfaceName}": "I" + structName,
+ })))
+ generatedContent.WriteString("\n\n")
+ return true
+ })
+}
diff --git a/server/internal/library/hggen/internal/consts/consts.go b/server/internal/library/hggen/internal/consts/consts.go
new file mode 100644
index 0000000..cece25d
--- /dev/null
+++ b/server/internal/library/hggen/internal/consts/consts.go
@@ -0,0 +1,13 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package consts
+
+const (
+ // DoNotEditKey is used in generated files,
+ // which marks the files will be overwritten by CLI tool.
+ DoNotEditKey = `DO NOT EDIT`
+)
diff --git a/server/internal/library/hggen/internal/consts/consts_gen_ctrl_template.go b/server/internal/library/hggen/internal/consts/consts_gen_ctrl_template.go
new file mode 100644
index 0000000..ac6ee7d
--- /dev/null
+++ b/server/internal/library/hggen/internal/consts/consts_gen_ctrl_template.go
@@ -0,0 +1,87 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package consts
+
+const TemplateGenCtrlControllerEmpty = `
+// =================================================================================
+// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
+// =================================================================================
+
+package {Module}
+`
+
+const TemplateGenCtrlControllerNewEmpty = `
+// =================================================================================
+// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
+// =================================================================================
+
+package {Module}
+
+import (
+ {ImportPath}
+)
+`
+
+const TemplateGenCtrlControllerNewFunc = `
+type {CtrlName} struct{}
+
+func {NewFuncName}() {InterfaceName} {
+ return &{CtrlName}{}
+}
+`
+
+const TemplateGenCtrlControllerMethodFunc = `
+package {Module}
+
+import (
+ "context"
+
+ "github.com/gogf/gf/v2/errors/gcode"
+ "github.com/gogf/gf/v2/errors/gerror"
+
+ "{ImportPath}"
+)
+{MethodComment}
+func (c *{CtrlName}) {MethodName}(ctx context.Context, req *{Version}.{MethodName}Req) (res *{Version}.{MethodName}Res, err error) {
+ return nil, gerror.NewCode(gcode.CodeNotImplemented)
+}
+`
+
+const TemplateGenCtrlControllerHeader = `
+package {Module}
+
+import (
+ "context"
+
+ "github.com/gogf/gf/v2/errors/gcode"
+ "github.com/gogf/gf/v2/errors/gerror"
+
+ "{ImportPath}"
+)
+
+`
+
+const TemplateGenCtrlControllerMethodFuncMerge = `
+{MethodComment}
+func (c *{CtrlName}) {MethodName}(ctx context.Context, req *{Version}.{MethodName}Req) (res *{Version}.{MethodName}Res, err error) {
+ return nil, gerror.NewCode(gcode.CodeNotImplemented)
+}
+`
+
+const TemplateGenCtrlApiInterface = `
+// =================================================================================
+// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
+// =================================================================================
+
+package {Module}
+
+import (
+{ImportPaths}
+)
+
+{Interfaces}
+`
diff --git a/server/internal/library/hggen/internal/consts/consts_gen_ctrl_template_sdk.go b/server/internal/library/hggen/internal/consts/consts_gen_ctrl_template_sdk.go
new file mode 100644
index 0000000..f2c4670
--- /dev/null
+++ b/server/internal/library/hggen/internal/consts/consts_gen_ctrl_template_sdk.go
@@ -0,0 +1,89 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package consts
+
+const TemplateGenCtrlSdkPkgNew = `
+// =================================================================================
+// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
+// =================================================================================
+
+package {PkgName}
+
+import (
+ "fmt"
+
+ "github.com/gogf/gf/contrib/sdk/httpclient/v2"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/text/gstr"
+)
+
+type implementer struct {
+ config httpclient.Config
+}
+
+func New(config httpclient.Config) IClient {
+ return &implementer{
+ config: config,
+ }
+}
+
+`
+
+const TemplateGenCtrlSdkIClient = `
+// =================================================================================
+// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
+// =================================================================================
+
+package {PkgName}
+
+import (
+)
+
+type IClient interface {
+}
+`
+
+const TemplateGenCtrlSdkImplementer = `
+// =================================================================================
+// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
+// =================================================================================
+
+package {PkgName}
+
+import (
+ "context"
+
+ "github.com/gogf/gf/contrib/sdk/httpclient/v2"
+ "github.com/gogf/gf/v2/text/gstr"
+
+{ImportPaths}
+)
+
+type implementer{ImplementerName} struct {
+ *httpclient.Client
+}
+
+`
+
+const TemplateGenCtrlSdkImplementerNew = `
+func (i *implementer) {ImplementerName}() {Module}.I{ImplementerName} {
+ var (
+ client = httpclient.New(i.config)
+ prefix = gstr.TrimRight(i.config.URL, "/") + "{VersionPrefix}"
+ )
+ client.Client = client.Prefix(prefix)
+ return &implementer{ImplementerName}{client}
+}
+
+`
+
+const TemplateGenCtrlSdkImplementerFunc = `{MethodComment}
+func (i *implementer{ImplementerName}) {MethodName}(ctx context.Context, req *{Version}.{MethodName}Req) (res *{Version}.{MethodName}Res, err error) {
+ err = i.Request(ctx, req, &res)
+ return
+}
+`
diff --git a/server/internal/library/hggen/internal/consts/consts_gen_dao_template_dao.go b/server/internal/library/hggen/internal/consts/consts_gen_dao_template_dao.go
new file mode 100644
index 0000000..cffa30e
--- /dev/null
+++ b/server/internal/library/hggen/internal/consts/consts_gen_dao_template_dao.go
@@ -0,0 +1,139 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package consts
+
+const TemplateGenDaoIndexContent = `
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package {{.TplPackageName}}
+
+import (
+ "{{.TplImportPrefix}}/internal"
+)
+
+// {{.TplTableNameCamelLowerCase}}Dao is the data access object for the table {{.TplTableName}}.
+// You can define custom methods on it to extend its functionality as needed.
+type {{.TplTableNameCamelLowerCase}}Dao struct {
+ *internal.{{.TplTableNameCamelCase}}Dao
+}
+
+var (
+ // {{.TplTableNameCamelCase}} is a globally accessible object for table {{.TplTableName}} operations.
+ {{.TplTableNameCamelCase}} = {{.TplTableNameCamelLowerCase}}Dao{
+{{- if .TplTableSharding -}}
+ internal.New{{.TplTableNameCamelCase}}Dao({{.TplTableNameCamelLowerCase}}ShardingHandler),
+{{- else -}}
+ internal.New{{.TplTableNameCamelCase}}Dao(),
+{{- end -}}
+ }
+)
+
+{{if .TplTableSharding -}}
+// {{.TplTableNameCamelLowerCase}}ShardingHandler is the handler for sharding operations.
+// You can fill this sharding handler with your custom implementation.
+func {{.TplTableNameCamelLowerCase}}ShardingHandler(m *gdb.Model) *gdb.Model {
+ m = m.Sharding(gdb.ShardingConfig{
+ Table: gdb.ShardingTableConfig{
+ Enable: true,
+ Prefix: "{{.TplTableShardingPrefix}}",
+ // Replace Rule field with your custom sharding rule.
+ // Or you can use "&gdb.DefaultShardingRule{}" for default sharding rule.
+ Rule: nil,
+ },
+ Schema: gdb.ShardingSchemaConfig{},
+ })
+ return m
+}
+{{- end}}
+
+// Add your custom methods and functionality below.
+
+`
+
+const TemplateGenDaoInternalContent = `
+// ==========================================================================
+// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. {{.TplCreatedAtDatetimeStr}}
+// ==========================================================================
+
+package internal
+
+import (
+ "context"
+
+ "github.com/gogf/gf/v2/database/gdb"
+ "github.com/gogf/gf/v2/frame/g"
+)
+
+// {{.TplTableNameCamelCase}}Dao is the data access object for the table {{.TplTableName}}.
+type {{.TplTableNameCamelCase}}Dao struct {
+ table string // table is the underlying table name of the DAO.
+ group string // group is the database configuration group name of the current DAO.
+ columns {{.TplTableNameCamelCase}}Columns // columns contains all the column names of Table for convenient usage.
+ handlers []gdb.ModelHandler // handlers for customized model modification.
+}
+
+// {{.TplTableNameCamelCase}}Columns defines and stores column names for the table {{.TplTableName}}.
+type {{.TplTableNameCamelCase}}Columns struct {
+ {{.TplColumnDefine}}
+}
+
+// {{.TplTableNameCamelLowerCase}}Columns holds the columns for the table {{.TplTableName}}.
+var {{.TplTableNameCamelLowerCase}}Columns = {{.TplTableNameCamelCase}}Columns{
+ {{.TplColumnNames}}
+}
+
+// New{{.TplTableNameCamelCase}}Dao creates and returns a new DAO object for table data access.
+func New{{.TplTableNameCamelCase}}Dao(handlers ...gdb.ModelHandler) *{{.TplTableNameCamelCase}}Dao {
+ return &{{.TplTableNameCamelCase}}Dao{
+ group: "{{.TplGroupName}}",
+ table: "{{.TplTableName}}",
+ columns: {{.TplTableNameCamelLowerCase}}Columns,
+ handlers: handlers,
+ }
+}
+
+// DB retrieves and returns the underlying raw database management object of the current DAO.
+func (dao *{{.TplTableNameCamelCase}}Dao) DB() gdb.DB {
+ return g.DB(dao.group)
+}
+
+// Table returns the table name of the current DAO.
+func (dao *{{.TplTableNameCamelCase}}Dao) Table() string {
+ return dao.table
+}
+
+// Columns returns all column names of the current DAO.
+func (dao *{{.TplTableNameCamelCase}}Dao) Columns() {{.TplTableNameCamelCase}}Columns {
+ return dao.columns
+}
+
+// Group returns the database configuration group name of the current DAO.
+func (dao *{{.TplTableNameCamelCase}}Dao) Group() string {
+ return dao.group
+}
+
+// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
+func (dao *{{.TplTableNameCamelCase}}Dao) Ctx(ctx context.Context) *gdb.Model {
+ model := dao.DB().Model(dao.table)
+ for _, handler := range dao.handlers {
+ model = handler(model)
+ }
+ return model.Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rolls back the transaction and returns the error if function f returns a non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note: Do not commit or roll back the transaction in function f,
+// as it is automatically handled by this function.
+func (dao *{{.TplTableNameCamelCase}}Dao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
+ return dao.Ctx(ctx).Transaction(ctx, f)
+}
+`
diff --git a/server/internal/library/hggen/internal/consts/consts_gen_dao_template_do.go b/server/internal/library/hggen/internal/consts/consts_gen_dao_template_do.go
new file mode 100644
index 0000000..97a404e
--- /dev/null
+++ b/server/internal/library/hggen/internal/consts/consts_gen_dao_template_do.go
@@ -0,0 +1,20 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package consts
+
+const TemplateGenDaoDoContent = `
+// =================================================================================
+// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. {{.TplCreatedAtDatetimeStr}}
+// =================================================================================
+
+package {{.TplPackageName}}
+
+{{.TplPackageImports}}
+
+// {{.TplTableNameCamelCase}} is the golang structure of table {{.TplTableName}} for DAO operations like Where/Data.
+{{.TplStructDefine}}
+`
diff --git a/server/internal/library/hggen/internal/consts/consts_gen_dao_template_entity.go b/server/internal/library/hggen/internal/consts/consts_gen_dao_template_entity.go
new file mode 100644
index 0000000..c54713a
--- /dev/null
+++ b/server/internal/library/hggen/internal/consts/consts_gen_dao_template_entity.go
@@ -0,0 +1,20 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package consts
+
+const TemplateGenDaoEntityContent = `
+// =================================================================================
+// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. {{.TplCreatedAtDatetimeStr}}
+// =================================================================================
+
+package {{.TplPackageName}}
+
+{{.TplPackageImports}}
+
+// {{.TplTableNameCamelCase}} is the golang structure for table {{.TplTableName}}.
+{{.TplStructDefine}}
+`
diff --git a/server/internal/library/hggen/internal/consts/consts_gen_dao_template_table.go b/server/internal/library/hggen/internal/consts/consts_gen_dao_template_table.go
new file mode 100644
index 0000000..a60c92d
--- /dev/null
+++ b/server/internal/library/hggen/internal/consts/consts_gen_dao_template_table.go
@@ -0,0 +1,35 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package consts
+
+const TemplateGenTableContent = `
+// =================================================================================
+// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
+// =================================================================================
+
+package {{.TplPackageName}}
+
+import (
+ "context"
+
+ "github.com/gogf/gf/v2/database/gdb"
+)
+
+// {{.TplTableNameCamelCase}} defines the fields of table "{{.TplTableName}}" with their properties.
+// This map is used internally by GoFrame ORM to understand table structure.
+var {{.TplTableNameCamelCase}} = map[string]*gdb.TableField{
+{{.TplTableFields}}
+}
+
+// Set{{.TplTableNameCamelCase}}TableFields registers the table fields definition to the database instance.
+// db: database instance that implements gdb.DB interface.
+// schema: optional schema/namespace name, especially for databases that support schemas.
+func Set{{.TplTableNameCamelCase}}TableFields(ctx context.Context, db gdb.DB, schema ...string) error {
+ return db.GetCore().SetTableFields(ctx, "{{.TplTableName}}", {{.TplTableNameCamelCase}}, schema...)
+}
+
+`
diff --git a/server/internal/library/hggen/internal/consts/consts_gen_enums_template.go b/server/internal/library/hggen/internal/consts/consts_gen_enums_template.go
new file mode 100644
index 0000000..9477787
--- /dev/null
+++ b/server/internal/library/hggen/internal/consts/consts_gen_enums_template.go
@@ -0,0 +1,23 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package consts
+
+const TemplateGenEnums = `
+// ================================================================================
+// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
+// ================================================================================
+
+package {PackageName}
+
+import (
+ "github.com/gogf/gf/v2/util/gtag"
+)
+
+func init() {
+ gtag.SetGlobalEnums({EnumsJson})
+}
+`
diff --git a/server/internal/library/hggen/internal/consts/consts_gen_pbentity_template.go b/server/internal/library/hggen/internal/consts/consts_gen_pbentity_template.go
new file mode 100644
index 0000000..73b693e
--- /dev/null
+++ b/server/internal/library/hggen/internal/consts/consts_gen_pbentity_template.go
@@ -0,0 +1,23 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package consts
+
+const TemplatePbEntityMessageContent = `
+// ==========================================================================
+// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
+// ==========================================================================
+
+syntax = "proto3";
+
+package {PackageName};
+
+option go_package = "{GoPackage}";
+{OptionContent}
+{Imports}
+
+{EntityMessage}
+`
diff --git a/server/internal/library/hggen/internal/consts/consts_gen_service_template.go b/server/internal/library/hggen/internal/consts/consts_gen_service_template.go
new file mode 100644
index 0000000..f5c4f5a
--- /dev/null
+++ b/server/internal/library/hggen/internal/consts/consts_gen_service_template.go
@@ -0,0 +1,41 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package consts
+
+const TemplateGenServiceContentHead = `
+// ================================================================================
+// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
+// You can delete these comments if you wish manually maintain this interface file.
+// ================================================================================
+
+package {PackageName}
+
+{Imports}
+`
+
+const TemplateGenServiceContentInterface = `
+{InterfaceName} interface {
+ {FuncDefinition}
+}
+`
+
+const TemplateGenServiceContentVariable = `
+local{StructName} {InterfaceName}
+`
+
+const TemplateGenServiceContentRegister = `
+func {StructName}() {InterfaceName} {
+ if local{StructName} == nil {
+ panic("implement not found for interface {InterfaceName}, forgot register?")
+ }
+ return local{StructName}
+}
+
+func Register{StructName}(i {InterfaceName}) {
+ local{StructName} = i
+}
+`
diff --git a/server/internal/library/hggen/internal/consts/consts_gen_service_template_logic.go b/server/internal/library/hggen/internal/consts/consts_gen_service_template_logic.go
new file mode 100644
index 0000000..eb43a03
--- /dev/null
+++ b/server/internal/library/hggen/internal/consts/consts_gen_service_template_logic.go
@@ -0,0 +1,19 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package consts
+
+const TemplateGenServiceLogicContent = `
+// ==========================================================================
+// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
+// ==========================================================================
+
+package {PackageName}
+
+import(
+ {Imports}
+)
+`
diff --git a/server/internal/library/hggen/internal/service/install.go b/server/internal/library/hggen/internal/service/install.go
new file mode 100644
index 0000000..0c901b1
--- /dev/null
+++ b/server/internal/library/hggen/internal/service/install.go
@@ -0,0 +1,246 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package service
+
+import (
+ "context"
+ "runtime"
+ "strings"
+
+ "github.com/gogf/gf/v2/container/garray"
+ "github.com/gogf/gf/v2/container/gset"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/os/gcmd"
+ "github.com/gogf/gf/v2/os/genv"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/text/gstr"
+ "github.com/gogf/gf/v2/util/gconv"
+
+ "hotgo/internal/library/hggen/internal/utility/allyes"
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+)
+
+var (
+ Install = serviceInstall{}
+)
+
+type serviceInstall struct{}
+
+type serviceInstallAvailablePath struct {
+ dirPath string
+ filePath string
+ writable bool
+ installed bool
+ IsSelf bool
+}
+
+func (s serviceInstall) Run(ctx context.Context) (err error) {
+ // Ask where to install.
+ paths := s.getAvailablePaths()
+ if len(paths) <= 0 {
+ mlog.Printf("no path detected, you can manually install gf by copying the binary to path folder.")
+ return
+ }
+ mlog.Printf("I found some installable paths for you(from $PATH): ")
+ mlog.Printf(" %2s | %8s | %9s | %s", "Id", "Writable", "Installed", "Path")
+
+ // Print all paths status and determine the default selectedID value.
+ var (
+ selectedID = -1
+ newPaths []serviceInstallAvailablePath
+ pathSet = gset.NewStrSet() // Used for repeated items filtering.
+ )
+ for _, path := range paths {
+ if !pathSet.AddIfNotExist(path.dirPath) {
+ continue
+ }
+ newPaths = append(newPaths, path)
+ }
+ paths = newPaths
+ for id, path := range paths {
+ mlog.Printf(" %2d | %8t | %9t | %s", id, path.writable, path.installed, path.dirPath)
+ if selectedID == -1 {
+ // Use the previously installed path as the most priority choice.
+ if path.installed {
+ selectedID = id
+ }
+ }
+ }
+ // If there's no previously installed path, use the first writable path.
+ if selectedID == -1 {
+ // Order by choosing priority.
+ commonPaths := garray.NewStrArrayFrom(g.SliceStr{
+ s.getGoPathBin(),
+ `/usr/local/bin`,
+ `/usr/bin`,
+ `/usr/sbin`,
+ `C:\Windows`,
+ `C:\Windows\system32`,
+ `C:\Go\bin`,
+ `C:\Program Files`,
+ `C:\Program Files (x86)`,
+ })
+ // Check the common installation directories.
+ commonPaths.Iterator(func(k int, v string) bool {
+ for id, aPath := range paths {
+ if strings.EqualFold(aPath.dirPath, v) {
+ selectedID = id
+ return false
+ }
+ }
+ return true
+ })
+ if selectedID == -1 {
+ selectedID = 0
+ }
+ }
+
+ if allyes.Check() {
+ // Use the default selectedID.
+ mlog.Printf("please choose one installation destination [default %d]: %d", selectedID, selectedID)
+ } else {
+ for {
+ // Get input and update selectedID.
+ var (
+ inputID int
+ input = gcmd.Scanf("please choose one installation destination [default %d]: ", selectedID)
+ )
+ if input != "" {
+ inputID = gconv.Int(input)
+ } else {
+ break
+ }
+ // Check if out of range.
+ if inputID >= len(paths) || inputID < 0 {
+ mlog.Printf("invalid install destination Id: %d", inputID)
+ continue
+ }
+ selectedID = inputID
+ break
+ }
+ }
+
+ // Get selected destination path.
+ dstPath := paths[selectedID]
+
+ // Install the new binary.
+ mlog.Debugf(`copy file from "%s" to "%s"`, gfile.SelfPath(), dstPath.filePath)
+ err = gfile.CopyFile(gfile.SelfPath(), dstPath.filePath)
+ if err != nil {
+ mlog.Printf("install gf binary to '%s' failed: %v", dstPath.dirPath, err)
+ mlog.Printf("you can manually install gf by copying the binary to folder: %s", dstPath.dirPath)
+ } else {
+ mlog.Printf("gf binary is successfully installed to: %s", dstPath.filePath)
+ }
+ return
+}
+
+// IsInstalled checks and returns whether the binary is installed.
+func (s serviceInstall) IsInstalled() (*serviceInstallAvailablePath, bool) {
+ paths := s.getAvailablePaths()
+ for _, aPath := range paths {
+ if aPath.installed {
+ return &aPath, true
+ }
+ }
+ return nil, false
+}
+
+// getGoPathBin retrieves ad returns the GOPATH/bin path for binary.
+func (s serviceInstall) getGoPathBin() string {
+ if goPath := genv.Get(`GOPATH`).String(); goPath != "" {
+ return gfile.Join(goPath, "bin")
+ }
+ return ""
+}
+
+// getAvailablePaths returns the installation paths data for the binary.
+func (s serviceInstall) getAvailablePaths() []serviceInstallAvailablePath {
+ var (
+ folderPaths []serviceInstallAvailablePath
+ binaryFileName = "gf"
+ )
+
+ // Windows binary file name suffix.
+ if runtime.GOOS == "windows" {
+ binaryFileName += ".exe"
+ }
+
+ // $GOPATH/bin
+ if goPathBin := s.getGoPathBin(); goPathBin != "" {
+ folderPaths = s.checkAndAppendToAvailablePath(
+ folderPaths, goPathBin, binaryFileName,
+ )
+ }
+ switch runtime.GOOS {
+ case "darwin":
+ darwinInstallationCheckPaths := []string{"/usr/local/bin"}
+ for _, v := range darwinInstallationCheckPaths {
+ folderPaths = s.checkAndAppendToAvailablePath(
+ folderPaths, v, binaryFileName,
+ )
+ }
+ fallthrough
+
+ default:
+ // Search and find the writable directory path.
+ envPath := genv.Get("PATH", genv.Get("Path").String()).String()
+ if gstr.Contains(envPath, ";") {
+ // windows.
+ for _, v := range gstr.SplitAndTrim(envPath, ";") {
+ if v == "." {
+ continue
+ }
+ folderPaths = s.checkAndAppendToAvailablePath(
+ folderPaths, v, binaryFileName,
+ )
+ }
+ } else if gstr.Contains(envPath, ":") {
+ // *nix.
+ for _, v := range gstr.SplitAndTrim(envPath, ":") {
+ if v == "." {
+ continue
+ }
+ folderPaths = s.checkAndAppendToAvailablePath(
+ folderPaths, v, binaryFileName,
+ )
+ }
+ } else if envPath != "" {
+ folderPaths = s.checkAndAppendToAvailablePath(
+ folderPaths, envPath, binaryFileName,
+ )
+ } else {
+ folderPaths = s.checkAndAppendToAvailablePath(
+ folderPaths, "/usr/local/bin", binaryFileName,
+ )
+ }
+ }
+ return folderPaths
+}
+
+// checkAndAppendToAvailablePath checks if `path` is writable and already installed.
+// It adds the `path` to `folderPaths` if it is writable or already installed, or else it ignores the `path`.
+func (s serviceInstall) checkAndAppendToAvailablePath(folderPaths []serviceInstallAvailablePath, dirPath string, binaryFileName string) []serviceInstallAvailablePath {
+ var (
+ filePath = gfile.Join(dirPath, binaryFileName)
+ writable = gfile.IsWritable(dirPath)
+ installed = gfile.Exists(filePath)
+ self = gfile.SelfPath() == filePath
+ )
+ if !writable && !installed {
+ return folderPaths
+ }
+ return append(
+ folderPaths,
+ serviceInstallAvailablePath{
+ dirPath: dirPath,
+ writable: writable,
+ filePath: filePath,
+ installed: installed,
+ IsSelf: self,
+ })
+}
diff --git a/server/internal/library/hggen/internal/utility/allyes/allyes.go b/server/internal/library/hggen/internal/utility/allyes/allyes.go
new file mode 100644
index 0000000..f8f7d1e
--- /dev/null
+++ b/server/internal/library/hggen/internal/utility/allyes/allyes.go
@@ -0,0 +1,28 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package allyes
+
+import (
+ "github.com/gogf/gf/v2/os/gcmd"
+ "github.com/gogf/gf/v2/os/genv"
+)
+
+const (
+ EnvName = "GF_CLI_ALL_YES"
+)
+
+// Init initializes the package manually.
+func Init() {
+ if gcmd.GetOpt("y") != nil {
+ genv.MustSet(EnvName, "1")
+ }
+}
+
+// Check checks whether option allow all yes for command.
+func Check() bool {
+ return genv.Get(EnvName).String() == "1"
+}
diff --git a/server/internal/library/hggen/internal/utility/mlog/mlog.go b/server/internal/library/hggen/internal/utility/mlog/mlog.go
new file mode 100644
index 0000000..2181501
--- /dev/null
+++ b/server/internal/library/hggen/internal/utility/mlog/mlog.go
@@ -0,0 +1,76 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package mlog
+
+import (
+ "context"
+
+ "github.com/gogf/gf/v2/os/gcmd"
+ "github.com/gogf/gf/v2/os/genv"
+ "github.com/gogf/gf/v2/os/glog"
+)
+
+const (
+ headerPrintEnvName = "GF_CLI_MLOG_HEADER"
+)
+
+var (
+ ctx = context.TODO()
+ logger = glog.New()
+)
+
+func init() {
+ if genv.Get(headerPrintEnvName).String() == "1" {
+ logger.SetHeaderPrint(true)
+ } else {
+ logger.SetHeaderPrint(false)
+ }
+
+ if gcmd.GetOpt("debug") != nil || gcmd.GetOpt("gf.debug") != nil {
+ logger.SetHeaderPrint(true)
+ logger.SetStackSkip(4)
+ logger.SetFlags(logger.GetFlags() | glog.F_FILE_LONG)
+ logger.SetDebug(true)
+ } else {
+ logger.SetStack(false)
+ logger.SetDebug(false)
+ }
+}
+
+// SetHeaderPrint enables/disables header printing to stdout.
+func SetHeaderPrint(enabled bool) {
+ logger.SetHeaderPrint(enabled)
+ if enabled {
+ _ = genv.Set(headerPrintEnvName, "1")
+ } else {
+ _ = genv.Set(headerPrintEnvName, "0")
+ }
+}
+
+func Print(v ...any) {
+ logger.Print(ctx, v...)
+}
+
+func Printf(format string, v ...any) {
+ logger.Printf(ctx, format, v...)
+}
+
+func Fatal(v ...any) {
+ logger.Fatal(ctx, v...)
+}
+
+func Fatalf(format string, v ...any) {
+ logger.Fatalf(ctx, format, v...)
+}
+
+func Debug(v ...any) {
+ logger.Debug(ctx, v...)
+}
+
+func Debugf(format string, v ...any) {
+ logger.Debugf(ctx, format, v...)
+}
diff --git a/server/internal/library/hggen/internal/utility/utils/utils.go b/server/internal/library/hggen/internal/utility/utils/utils.go
new file mode 100644
index 0000000..05361fd
--- /dev/null
+++ b/server/internal/library/hggen/internal/utility/utils/utils.go
@@ -0,0 +1,135 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package utils
+
+import (
+ "context"
+ "fmt"
+
+ "golang.org/x/tools/imports"
+
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gproc"
+ "github.com/gogf/gf/v2/text/gregex"
+ "github.com/gogf/gf/v2/text/gstr"
+
+ "hotgo/internal/library/hggen/internal/consts"
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+)
+
+// GoFmt formats the source file and adds or removes import statements as necessary.
+func GoFmt(path string) {
+ replaceFunc := func(path, content string) string {
+ res, err := imports.Process(path, []byte(content), nil)
+ if err != nil {
+ mlog.Printf(`error format "%s" go files: %v`, path, err)
+ return content
+ }
+ return string(res)
+ }
+
+ var err error
+ if gfile.IsFile(path) {
+ // File format.
+ if gfile.ExtName(path) != "go" {
+ return
+ }
+ err = gfile.ReplaceFileFunc(replaceFunc, path)
+ } else {
+ // Folder format.
+ err = gfile.ReplaceDirFunc(replaceFunc, path, "*.go", true)
+ }
+ if err != nil {
+ mlog.Printf(`error format "%s" go files: %v`, path, err)
+ }
+}
+
+// GoModTidy executes `go mod tidy` at specified directory `dirPath`.
+func GoModTidy(ctx context.Context, dirPath string) error {
+ command := fmt.Sprintf(`cd %s && go mod tidy`, dirPath)
+ err := gproc.ShellRun(ctx, command)
+ return err
+}
+
+// IsFileDoNotEdit checks and returns whether file contains `do not edit` key.
+func IsFileDoNotEdit(filePath string) bool {
+ if !gfile.Exists(filePath) {
+ return true
+ }
+ return gstr.Contains(gfile.GetContents(filePath), consts.DoNotEditKey)
+}
+
+// ReplaceGeneratedContentGFV2 replaces generated go content from goframe v1 to v2.
+func ReplaceGeneratedContentGFV2(folderPath string) (err error) {
+ return gfile.ReplaceDirFunc(func(path, content string) string {
+ if gstr.Contains(content, `"github.com/gogf/gf`) && !gstr.Contains(content, `"github.com/gogf/gf/v2`) {
+ content = gstr.Replace(content, `"github.com/gogf/gf"`, `"github.com/gogf/gf/v2"`)
+ content = gstr.Replace(content, `"github.com/gogf/gf/`, `"github.com/gogf/gf/v2/`)
+ content = gstr.Replace(content, `"github.com/gogf/gf/v2/contrib/`, `"github.com/gogf/gf/contrib/`)
+ return content
+ }
+ return content
+ }, folderPath, "*.go", true)
+}
+
+// GetImportPath calculates and returns the golang import path for given `dirPath`.
+// Note that it needs a `go.mod` in current working directory or parent directories to detect the path.
+func GetImportPath(dirPath string) string {
+ // If `filePath` does not exist, create it firstly to find the import path.
+ var realPath = gfile.RealPath(dirPath)
+ if realPath == "" {
+ _ = gfile.Mkdir(dirPath)
+ realPath = gfile.RealPath(dirPath)
+ }
+
+ var (
+ newDir = gfile.Dir(realPath)
+ oldDir string
+ suffix = gfile.Basename(dirPath)
+ goModName = "go.mod"
+ goModPath string
+ importPath string
+ )
+ for {
+ goModPath = gfile.Join(newDir, goModName)
+ if gfile.Exists(goModPath) {
+ match, _ := gregex.MatchString(`^module\s+(.+)\s*`, gfile.GetContents(goModPath))
+ importPath = gstr.Trim(match[1]) + "/" + suffix
+ importPath = gstr.Replace(importPath, `\`, `/`)
+ importPath = gstr.TrimRight(importPath, `/`)
+ return importPath
+ }
+ oldDir = newDir
+ newDir = gfile.Dir(oldDir)
+ if newDir == oldDir {
+ return ""
+ }
+ suffix = gfile.Basename(oldDir) + "/" + suffix
+ }
+}
+
+// GetModPath retrieves and returns the file path of go.mod for current project.
+func GetModPath() string {
+ var (
+ oldDir = gfile.Pwd()
+ newDir = oldDir
+ goModName = "go.mod"
+ goModPath string
+ )
+ for {
+ goModPath = gfile.Join(newDir, goModName)
+ if gfile.Exists(goModPath) {
+ return goModPath
+ }
+ newDir = gfile.Dir(oldDir)
+ if newDir == oldDir {
+ break
+ }
+ oldDir = newDir
+ }
+ return ""
+}
diff --git a/server/internal/library/hggen/internal/utility/utils/utils_http_download.go b/server/internal/library/hggen/internal/utility/utils/utils_http_download.go
new file mode 100644
index 0000000..ecedc00
--- /dev/null
+++ b/server/internal/library/hggen/internal/utility/utils/utils_http_download.go
@@ -0,0 +1,55 @@
+// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package utils
+
+import (
+ "io"
+ "net/http"
+ "os"
+ "time"
+
+ "github.com/schollz/progressbar/v3"
+
+ "github.com/gogf/gf/v2/errors/gerror"
+
+ "hotgo/internal/library/hggen/internal/utility/mlog"
+)
+
+// HTTPDownloadFileWithPercent downloads target url file to local path with percent process printing.
+func HTTPDownloadFileWithPercent(url string, localSaveFilePath string) error {
+ start := time.Now()
+ out, err := os.Create(localSaveFilePath)
+ if err != nil {
+ return gerror.Wrapf(err, `download "%s" to "%s" failed`, url, localSaveFilePath)
+ }
+ defer out.Close()
+
+ headResp, err := http.Head(url)
+ if err != nil {
+ return gerror.Wrapf(err, `download "%s" to "%s" failed`, url, localSaveFilePath)
+ }
+ defer headResp.Body.Close()
+
+ resp, err := http.Get(url)
+ if err != nil {
+ return gerror.Wrapf(err, `download "%s" to "%s" failed`, url, localSaveFilePath)
+ }
+ defer resp.Body.Close()
+
+ bar := progressbar.NewOptions(int(resp.ContentLength), progressbar.OptionShowBytes(true), progressbar.OptionShowCount())
+ writer := io.MultiWriter(out, bar)
+ _, err = io.Copy(writer, resp.Body)
+
+ elapsed := time.Since(start)
+ if elapsed > time.Minute {
+ mlog.Printf(`download completed in %.0fm`, float64(elapsed)/float64(time.Minute))
+ } else {
+ mlog.Printf(`download completed in %.0fs`, elapsed.Seconds())
+ }
+
+ return nil
+}
diff --git a/server/internal/library/hggen/views/column.go b/server/internal/library/hggen/views/column.go
new file mode 100644
index 0000000..761f848
--- /dev/null
+++ b/server/internal/library/hggen/views/column.go
@@ -0,0 +1,305 @@
+// Package views
+// @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 views
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "hotgo/internal/consts"
+ "hotgo/internal/library/hggen/internal/cmd/gendao"
+ "hotgo/internal/model/input/sysin"
+
+ "github.com/gogf/gf/v2/database/gdb"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/text/gregex"
+ "github.com/gogf/gf/v2/text/gstr"
+ "github.com/gogf/gf/v2/util/gconv"
+)
+
+// DoTableColumns 获取指定表生成字段列表
+func DoTableColumns(ctx context.Context, in *sysin.GenCodesColumnListInp, config gendao.CGenDaoInput) (fields []*sysin.GenCodesColumnListModel, err error) {
+ var (
+ sql string
+ conf = g.DB(in.Name).GetConfig()
+ )
+
+ // 根据数据库类型使用不同的SQL
+ if conf.Type == consts.DBPgsql {
+ // PostgreSQL: 使用pg_catalog查询列详细信息
+ sql = `
+ SELECT
+ a.attnum as id,
+ a.attname as name,
+ COALESCE(col_description(a.attrelid, a.attnum), '') as dc,
+ t.typname as dataType,
+ CASE
+ WHEN a.atttypmod > 0 THEN t.typname || '(' || (a.atttypmod - 4) || ')'
+ ELSE t.typname
+ END as sqlType,
+ CASE
+ WHEN a.atttypmod > 0 THEN a.atttypmod - 4
+ ELSE NULL
+ END as length,
+ CASE WHEN a.attnotnull THEN 'NO' ELSE 'YES' END as isAllowNull,
+ pg_get_expr(ad.adbin, ad.adrelid) as defaultValue,
+ CASE
+ WHEN pk.contype = 'p' THEN 'PRI'
+ WHEN uk.contype = 'u' THEN 'UNI'
+ ELSE ''
+ END as "index",
+ CASE
+ WHEN ad.adbin IS NOT NULL AND ad.adbin LIKE '%nextval%' THEN 'auto_increment'
+ ELSE ''
+ END as extra
+ FROM pg_attribute a
+ JOIN pg_class c ON a.attrelid = c.oid
+ JOIN pg_namespace n ON c.relnamespace = n.oid
+ JOIN pg_type t ON a.atttypid = t.oid
+ LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
+ LEFT JOIN pg_constraint pk ON pk.conrelid = c.oid AND pk.contype = 'p' AND a.attnum = ANY(pk.conkey)
+ LEFT JOIN pg_constraint uk ON uk.conrelid = c.oid AND uk.contype = 'u' AND a.attnum = ANY(uk.conkey)
+ WHERE n.nspname = 'public'
+ AND c.relname = '` + in.Table + `'
+ AND a.attnum > 0
+ AND NOT a.attisdropped
+ ORDER BY a.attnum`
+ } else {
+ // MySQL: 使用information_schema.COLUMNS
+ sql = fmt.Sprintf("SELECT ORDINAL_POSITION as id, COLUMN_NAME as name, COLUMN_COMMENT as dc, DATA_TYPE as dataType, COLUMN_TYPE as sqlType, CHARACTER_MAXIMUM_LENGTH as length, IS_NULLABLE as isAllowNull, COLUMN_DEFAULT as defaultValue, COLUMN_KEY as `index`, EXTRA as extra FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s' ORDER BY id ASC", conf.Name, in.Table)
+ }
+
+ err = g.DB(in.Name).Ctx(ctx).Raw(sql).Scan(&fields)
+ if err != nil {
+ return nil, err
+ }
+
+ if len(fields) == 0 {
+ return
+ }
+
+ for _, field := range fields {
+ if in.IsLink == 1 {
+ CustomLinkAttributes(ctx, in.Alias, field, config)
+ } else {
+ CustomAttributes(ctx, field, config)
+ }
+ }
+ return
+}
+
+// CustomLinkAttributes 可自定义关联表的字段属性
+func CustomLinkAttributes(ctx context.Context, alias string, field *sysin.GenCodesColumnListModel, in gendao.CGenDaoInput) {
+ field.GoName, field.GoType, field.TsName, field.TsType = GenGotype(ctx, field, in)
+
+ field.GoName = gstr.UcFirst(alias + field.GoName)
+ field.TsName = gstr.LcFirst(field.GoName)
+
+ setDefaultQueryWhere(field)
+ setDefaultValue(field)
+}
+
+// CustomAttributes 可自定义的字段属性
+func CustomAttributes(ctx context.Context, field *sysin.GenCodesColumnListModel, in gendao.CGenDaoInput) {
+ field.GoName, field.GoType, field.TsName, field.TsType = GenGotype(ctx, field, in)
+ setDefault(field)
+}
+
+// GenGotype 生成字段的go类型
+func GenGotype(ctx context.Context, field *sysin.GenCodesColumnListModel, in gendao.CGenDaoInput) (goName, typeName, tsName string, tsType string) {
+ var err error
+ tsName = getJsonTagFromCase(field.Name, in.JsonCase)
+ goName = gstr.CaseCamel(field.Name)
+
+ typeName, err = CheckLocalTypeForField(ctx, field.DataType, nil)
+ if err != nil {
+ panic(err)
+ }
+
+ switch gdb.LocalType(typeName) {
+ case gdb.LocalTypeDate, gdb.LocalTypeDatetime:
+ if in.StdTime {
+ typeName = "time.Time"
+ } else {
+ typeName = "*gtime.Time"
+ }
+
+ case gdb.LocalTypeInt64Bytes:
+ typeName = "int64"
+
+ case gdb.LocalTypeUint64Bytes:
+ typeName = "uint64"
+
+ // Special type handle.
+ case gdb.LocalTypeJson, gdb.LocalTypeJsonb:
+ if in.GJsonSupport {
+ typeName = "*gjson.Json"
+ } else {
+ typeName = "string"
+ }
+ }
+
+ tsType = ShiftMap[typeName]
+ return
+}
+
+// CheckLocalTypeForField checks and returns corresponding type for given db type.
+func CheckLocalTypeForField(ctx context.Context, fieldType string, fieldValue interface{}) (string, error) {
+ var (
+ typeName string
+ typePattern string
+ )
+ match, _ := gregex.MatchString(`(.+?)\((.+)\)`, fieldType)
+ if len(match) == 3 {
+ typeName = gstr.Trim(match[1])
+ typePattern = gstr.Trim(match[2])
+ } else {
+ typeName = gstr.Split(fieldType, " ")[0]
+ }
+ typeName = strings.ToLower(typeName)
+ switch typeName {
+ case
+ "binary",
+ "varbinary",
+ "blob",
+ "tinyblob",
+ "mediumblob",
+ "longblob":
+ return string(gdb.LocalTypeBytes), nil
+
+ case
+ "int",
+ "tinyint",
+ "small_int",
+ "smallint",
+ "medium_int",
+ "mediumint",
+ "serial":
+ if gstr.ContainsI(fieldType, "unsigned") {
+ return string(gdb.LocalTypeUint), nil
+ }
+ return string(gdb.LocalTypeInt), nil
+
+ case
+ "big_int",
+ "bigint",
+ "bigserial":
+ if gstr.ContainsI(fieldType, "unsigned") {
+ return string(gdb.LocalTypeUint64), nil
+ }
+ return string(gdb.LocalTypeInt64), nil
+
+ case
+ "real":
+ return string(gdb.LocalTypeFloat32), nil
+
+ case
+ "float",
+ "double",
+ "decimal",
+ "money",
+ "numeric",
+ "smallmoney":
+ return string(gdb.LocalTypeFloat64), nil
+
+ case
+ "bit":
+ // It is suggested using bit(1) as boolean.
+ if typePattern == "1" {
+ return string(gdb.LocalTypeBool), nil
+ }
+ s := gconv.String(fieldValue)
+ // mssql is true|false string.
+ if strings.EqualFold(s, "true") || strings.EqualFold(s, "false") {
+ return string(gdb.LocalTypeBool), nil
+ }
+ if gstr.ContainsI(fieldType, "unsigned") {
+ return string(gdb.LocalTypeUint64Bytes), nil
+ }
+ return string(gdb.LocalTypeInt64Bytes), nil
+
+ case
+ "bool":
+ return string(gdb.LocalTypeBool), nil
+
+ case
+ "date":
+ return string(gdb.LocalTypeDate), nil
+
+ case
+ "datetime",
+ "timestamp",
+ "timestamptz":
+ return string(gdb.LocalTypeDatetime), nil
+
+ case
+ "json":
+ return string(gdb.LocalTypeJson), nil
+
+ case
+ "jsonb":
+ return string(gdb.LocalTypeJsonb), nil
+
+ default:
+ // Auto-detect field type, using key match.
+ switch {
+ case strings.Contains(typeName, "text") || strings.Contains(typeName, "char") || strings.Contains(typeName, "character"):
+ return string(gdb.LocalTypeString), nil
+
+ case strings.Contains(typeName, "float") || strings.Contains(typeName, "double") || strings.Contains(typeName, "numeric"):
+ return string(gdb.LocalTypeFloat64), nil
+
+ case strings.Contains(typeName, "bool"):
+ return string(gdb.LocalTypeBool), nil
+
+ case strings.Contains(typeName, "binary") || strings.Contains(typeName, "blob"):
+ return string(gdb.LocalTypeBytes), nil
+
+ case strings.Contains(typeName, "int"):
+ if gstr.ContainsI(fieldType, "unsigned") {
+ return string(gdb.LocalTypeUint), nil
+ }
+ return string(gdb.LocalTypeInt), nil
+
+ case strings.Contains(typeName, "time"):
+ return string(gdb.LocalTypeDatetime), nil
+
+ case strings.Contains(typeName, "date"):
+ return string(gdb.LocalTypeDatetime), nil
+
+ default:
+ return string(gdb.LocalTypeString), nil
+ }
+ }
+}
+
+// getJsonTagFromCase call gstr.Case* function to convert the s to specified case.
+func getJsonTagFromCase(str, caseStr string) string {
+ switch gstr.ToLower(caseStr) {
+ case gstr.ToLower("Camel"):
+ return gstr.CaseCamel(str)
+
+ case gstr.ToLower("CamelLower"):
+ return gstr.CaseCamelLower(str)
+
+ case gstr.ToLower("Kebab"):
+ return gstr.CaseKebab(str)
+
+ case gstr.ToLower("KebabScreaming"):
+ return gstr.CaseKebabScreaming(str)
+
+ case gstr.ToLower("Snake"):
+ return gstr.CaseSnake(str)
+
+ case gstr.ToLower("SnakeFirstUpper"):
+ return gstr.CaseSnakeFirstUpper(str)
+
+ case gstr.ToLower("SnakeScreaming"):
+ return gstr.CaseSnakeScreaming(str)
+ }
+ return str
+}
diff --git a/server/internal/library/hggen/views/column_default.go b/server/internal/library/hggen/views/column_default.go
new file mode 100644
index 0000000..ca44e60
--- /dev/null
+++ b/server/internal/library/hggen/views/column_default.go
@@ -0,0 +1,326 @@
+// Package views
+// @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 views
+
+import (
+ "github.com/gogf/gf/v2/text/gstr"
+ "hotgo/internal/consts"
+ "hotgo/internal/model/input/sysin"
+)
+
+// 默认表单组件映射 Ts -> 表单组件
+var defaultFormModeMap = map[string]string{
+ TsTypeString: FormModeInput,
+ TsTypeNumber: FormModeInputNumber,
+ TsTypeBoolean: FormModeInputNumber,
+ TsTypeArray: FormModeInputDynamic,
+ TsTypeTuple: FormModeInputDynamic,
+ TsTypeAny: FormModeInput,
+}
+
+var defaultEditFields = map[string]bool{
+ "id": false,
+ "created_at": false,
+ "updated_at": false,
+ "deleted_at": false,
+}
+
+var defaultEditSwitch = map[string]bool{
+ "id": false,
+ "level": false,
+ "tree": false,
+ "created_by": false,
+ "updated_by": false,
+ "deleted_by": false,
+ "created_at": false,
+ "updated_at": false,
+ "deleted_at": false,
+}
+
+var defaultListSwitch = map[string]bool{
+ "level": false,
+ "tree": false,
+ "deleted_at": false,
+}
+
+var defaultExportSwitch = map[string]bool{
+ "level": false,
+ "tree": false,
+ "deleted_at": false,
+}
+
+var defaultQuerySwitch = map[string]bool{
+ "level": false,
+ "tree": false,
+ "deleted_at": false,
+}
+
+var defaultSort = map[string]bool{
+ "id": true,
+ "sort": true,
+}
+
+var defaultTreeFields = []string{"pid", "level", "tree"}
+
+// 默认表单验证映射 物理类型命名识别
+var defaultFormRoleMap = map[string]string{
+ "mobile": FormRolePhone,
+ "qq": FormRoleQq,
+ "email": FormRoleEmail,
+ "id_card": FormRoleIdCard,
+ "bank_card": FormRoleBankCard,
+ "password": FormRolePassword,
+ "pass": FormRolePassword,
+ "price": FormRoleAmount,
+}
+
+// 默认查询条件映射 go类型识别
+var defaultWhereModeMap = map[string]string{
+ GoTypeString: WhereModeLike,
+ GoTypeDate: WhereModeEq,
+ GoTypeDatetime: WhereModeEq,
+ GoTypeInt: WhereModeEq,
+ GoTypeUint: WhereModeEq,
+ GoTypeInt64: WhereModeEq,
+ GoTypeUint64: WhereModeEq,
+ GoTypeIntSlice: WhereModeIn,
+ GoTypeInt64Slice: WhereModeIn,
+ GoTypeUint64Slice: WhereModeIn,
+ GoTypeFloat32: WhereModeEq,
+ GoTypeFloat64: WhereModeEq,
+ GoTypeBytes: WhereModeEq,
+ GoTypeTime: WhereModeEq,
+ GoTypeGTime: WhereModeEq,
+ GoTypeJson: WhereModeJsonContains,
+}
+
+// setDefault 设置默认属性
+func setDefault(field *sysin.GenCodesColumnListModel) {
+ setDefaultEdit(field)
+
+ setDefaultFormMode(field)
+
+ setDefaultFormRole(field)
+
+ setDefaultDictType(field)
+
+ setDefaultList(field)
+
+ setDefaultExport(field)
+
+ setDefaultQuery(field)
+
+ setDefaultQueryWhere(field)
+
+ setDefaultValue(field)
+
+ if field.IsAllowNull == "NO" {
+ field.Required = true
+ }
+
+ if IsIndexUNI(field.Index) {
+ field.Unique = true
+ }
+
+ if df, ok := defaultSort[field.Name]; ok {
+ field.IsSort = df
+ }
+
+ if field.Dc == "" {
+ field.Dc = field.Name
+ }
+}
+
+// setDefaultEdit 设置默认编辑
+func setDefaultEdit(field *sysin.GenCodesColumnListModel) {
+ field.IsEdit = true
+
+ if IsIndexPK(field.Index) {
+ field.IsEdit = false
+ return
+ }
+
+ if df, ok := defaultEditFields[field.Name]; ok {
+ field.IsEdit = df
+ }
+
+ if df, ok := defaultEditSwitch[field.Name]; ok {
+ field.IsEdit = df
+ }
+}
+
+// setDefaultFormMode 设置默认表单组件
+func setDefaultFormMode(field *sysin.GenCodesColumnListModel) {
+ field.FormMode = FormModeInput
+ if df, ok := defaultFormModeMap[field.TsType]; ok {
+ field.FormMode = df
+ }
+
+ if gstr.HasSuffix(field.GoName, "Status") && IsNumberType(field.GoType) {
+ field.FormMode = FormModeSelect
+ return
+ }
+
+ if field.GoName == "CreatedAt" {
+ field.FormMode = FormModeTimeRange
+ return
+ }
+
+ if (field.GoName == "ProvinceId" || field.GoName == "CityId") && IsNumberType(field.GoType) {
+ field.FormMode = FormModeCitySelector
+ return
+ }
+
+ if field.DataType == "datetime" || field.DataType == "timestamp" || field.DataType == "timestamptz" {
+ field.FormMode = FormModeTime
+ return
+ }
+
+ if field.DataType == "date" {
+ field.FormMode = FormModeDate
+ return
+ }
+
+ if field.GoType == GoTypeString && field.Length >= 256 && field.Length <= 512 {
+ field.FormMode = FormModeInputTextarea
+ return
+ }
+
+ if field.GoType == GoTypeString && field.Length > 512 {
+ field.FormMode = FormModeInputEditor
+ return
+ }
+}
+
+// setDefaultFormRole 设置默认表单验证
+func setDefaultFormRole(field *sysin.GenCodesColumnListModel) {
+ field.FormRole = FormRoleNone
+
+ switch field.GoType {
+ case GoTypeUint, GoTypeUint64:
+ field.FormRole = FormRoleNum
+ return
+ }
+
+ if df, ok := defaultFormRoleMap[field.Name]; ok {
+ field.FormRole = df
+ }
+}
+
+// setDefaultDictType 设置默认字典类型
+func setDefaultDictType(field *sysin.GenCodesColumnListModel) {
+ if gstr.HasSuffix(field.GoName, "Status") && IsNumberType(field.GoType) {
+ field.DictType = 3 // 默认系统状态ID
+ return
+ }
+}
+
+// setDefaultList 设置默认列表
+func setDefaultList(field *sysin.GenCodesColumnListModel) {
+ field.IsList = true
+ switch field.GoType {
+ case GoTypeIntSlice, GoTypeInt64Slice, GoTypeUint64Slice, GoTypeBytes, GoTypeJson:
+ field.IsList = false
+ return
+ }
+
+ if field.Length >= 500 {
+ field.IsList = false
+ return
+ }
+
+ if df, ok := defaultListSwitch[field.Name]; ok {
+ field.IsList = df
+ }
+}
+
+// setDefaultExport 设置默认导出
+func setDefaultExport(field *sysin.GenCodesColumnListModel) {
+ field.IsExport = true
+ switch field.GoType {
+ case GoTypeIntSlice, GoTypeInt64Slice, GoTypeUint64Slice, GoTypeBytes, GoTypeJson:
+ field.IsExport = false
+ return
+ }
+
+ if field.Length >= 500 {
+ field.IsExport = false
+ return
+ }
+
+ if df, ok := defaultExportSwitch[field.Name]; ok {
+ field.IsExport = df
+ }
+}
+
+// setDefaultQuery 设置默认查询
+func setDefaultQuery(field *sysin.GenCodesColumnListModel) {
+ field.IsQuery = false
+
+ if IsIndexPK(field.Index) {
+ field.IsQuery = true
+ return
+ }
+
+ if gstr.HasSuffix(field.GoName, "Status") && IsNumberType(field.GoType) {
+ field.IsQuery = true
+ return
+ }
+
+ if field.GoName == "CreatedAt" {
+ field.IsQuery = true
+ return
+ }
+
+ if df, ok := defaultQuerySwitch[field.Name]; ok {
+ field.IsQuery = df
+ }
+}
+
+// setDefaultQueryWhere 设置默认查询条件
+func setDefaultQueryWhere(field *sysin.GenCodesColumnListModel) {
+ field.QueryWhere = WhereModeEq
+
+ if field.GoName == "CreatedAt" {
+ field.QueryWhere = WhereModeBetween
+ return
+ }
+
+ if field.Length >= 500 {
+ field.QueryWhere = WhereModeLikeAll
+ return
+ }
+
+ if df, ok := defaultWhereModeMap[field.GoType]; ok {
+ field.QueryWhere = df
+ }
+}
+
+// setDefaultValue 设置默认value
+func setDefaultValue(field *sysin.GenCodesColumnListModel) {
+ var value interface{}
+ if field.DefaultValue == nil {
+ switch field.GoType {
+ case GoTypeString, GoTypeBytes, GoTypeDate, GoTypeDatetime, GoTypeTime, GoTypeGTime:
+ value = ""
+ case GoTypeIntSlice, GoTypeInt64Slice, GoTypeUint64Slice, GoTypeJson:
+ value = nil
+ case GoTypeInt, GoTypeUint, GoTypeInt64, GoTypeUint64:
+ value = 0
+ case GoTypeBool:
+ value = false
+ }
+ } else {
+ value = consts.ConvType(field.DefaultValue, field.GoType)
+ }
+
+ // 时间类型不做默认值处理
+ if field.GoType == GoTypeGTime {
+ value = ""
+ }
+
+ field.DefaultValue = value
+}
diff --git a/server/internal/library/hggen/views/column_map.go b/server/internal/library/hggen/views/column_map.go
new file mode 100644
index 0000000..1cbdfda
--- /dev/null
+++ b/server/internal/library/hggen/views/column_map.go
@@ -0,0 +1,343 @@
+// Package views
+// @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 views
+
+import (
+ "github.com/gogf/gf/v2/text/gstr"
+ "hotgo/internal/model/input/sysin"
+ "hotgo/utility/validate"
+)
+
+// 字段映射关系
+
+// go类型
+const (
+ GoTypeString = "string"
+ GoTypeDate = "date"
+ GoTypeDatetime = "datetime"
+ GoTypeInt = "int"
+ GoTypeUint = "uint"
+ GoTypeInt64 = "int64"
+ GoTypeUint64 = "uint64"
+ GoTypeIntSlice = "[]int"
+ GoTypeInt64Slice = "[]int64"
+ GoTypeUint64Slice = "[]uint64"
+ GoTypeFloat32 = "float32"
+ GoTypeFloat64 = "float64"
+ GoTypeBytes = "[]byte"
+ GoTypeBool = "bool"
+ GoTypeTime = "time.Time"
+ GoTypeGTime = "*gtime.Time"
+ GoTypeJson = "*gjson.Json"
+)
+
+var GoTypeNameMap = map[string]string{
+ GoTypeString: GoTypeString,
+ GoTypeDate: GoTypeDate,
+ GoTypeDatetime: GoTypeDatetime,
+ GoTypeInt: GoTypeInt,
+ GoTypeUint: GoTypeUint,
+ GoTypeInt64: GoTypeInt64,
+ GoTypeUint64: GoTypeUint64,
+ GoTypeIntSlice: GoTypeIntSlice,
+ GoTypeInt64Slice: GoTypeInt64Slice,
+ GoTypeUint64Slice: GoTypeUint64Slice,
+ GoTypeFloat32: GoTypeFloat32,
+ GoTypeFloat64: GoTypeFloat64,
+ GoTypeBytes: GoTypeBytes,
+ GoTypeBool: GoTypeBool,
+ GoTypeTime: GoTypeTime,
+ GoTypeGTime: GoTypeGTime,
+ GoTypeJson: GoTypeJson,
+}
+
+// ts类型
+const (
+ TsTypeString = "string"
+ TsTypeNumber = "number"
+ TsTypeBoolean = "boolean"
+ TsTypeArray = "array"
+ TsTypeTuple = "tuple"
+ TsTypeAny = "any"
+)
+
+var TsTypeNameMap = map[string]string{
+ TsTypeString: TsTypeString,
+ TsTypeNumber: TsTypeNumber,
+ TsTypeBoolean: TsTypeBoolean,
+ TsTypeArray: TsTypeArray,
+ TsTypeTuple: TsTypeTuple,
+ TsTypeAny: TsTypeAny,
+}
+
+// ShiftMap Go -> Ts 类型转换
+var ShiftMap = map[string]string{
+ GoTypeString: TsTypeString,
+ GoTypeDate: TsTypeString,
+ GoTypeDatetime: TsTypeString,
+ GoTypeInt: TsTypeNumber,
+ GoTypeUint: TsTypeNumber,
+ GoTypeInt64: TsTypeNumber,
+ GoTypeUint64: TsTypeNumber,
+ GoTypeIntSlice: TsTypeArray,
+ GoTypeInt64Slice: TsTypeArray,
+ GoTypeUint64Slice: TsTypeArray,
+ GoTypeFloat32: TsTypeNumber,
+ GoTypeFloat64: TsTypeNumber,
+ GoTypeBytes: TsTypeString,
+ GoTypeBool: TsTypeBoolean,
+ GoTypeTime: TsTypeString,
+ GoTypeGTime: TsTypeString,
+ GoTypeJson: TsTypeAny,
+}
+
+// 表单组件
+const (
+ FormModeInput = "Input" // 文本输入
+ FormModeInputNumber = "InputNumber" // 数字输入
+ FormModeInputTextarea = "InputTextarea" // 文本域
+ FormModeInputEditor = "InputEditor" // 富文本
+ FormModeInputYaml = "InputYaml" // YAML配置
+ FormModeInputDynamic = "InputDynamic" // 动态键值对
+ FormModeDate = "Date" // 日期选择(Y-M-D)
+ FormModeDateRange = "DateRange" // 日期范围选择
+ FormModeTime = "Time" // 时间选择(Y-M-D H:i:s)
+ FormModeTimeRange = "TimeRange" // 时间范围选择
+ FormModeRadio = "Radio" // 单选按钮
+ FormModeCheckbox = "Checkbox" // 复选按钮
+ FormModeSelect = "Select" // 单选下拉框
+ FormModeSelectMultiple = "SelectMultiple" // 多选下拉框
+ FormModeTreeSelect = "TreeSelect" // 树型选择
+ FormModeCascader = "Cascader" // 级联选择
+ FormModeUploadImage = "UploadImage" // 单图上传
+ FormModeUploadImages = "UploadImages" // 多图上传
+ FormModeUploadFile = "UploadFile" // 单文件上传
+ FormModeUploadFiles = "UploadFiles" // 多文件上传
+ FormModeSwitch = "Switch" // 开关
+ FormModeRate = "Rate" // 评分
+ FormModeCitySelector = "CitySelector" // 省市区选择
+ FormModePidTreeSelect = "PidTreeSelect" // 树型上级选择,树表生成专用
+)
+
+var FormModes = []string{
+ FormModeInput, FormModeInputNumber, FormModeInputTextarea, FormModeInputEditor, FormModeInputYaml, FormModeInputDynamic,
+ FormModeDate, FormModeDateRange, FormModeTime, FormModeTimeRange,
+ FormModeRadio, FormModeCheckbox, FormModeSelect, FormModeSelectMultiple, FormModeTreeSelect, FormModeCascader,
+ FormModeUploadImage, FormModeUploadImages, FormModeUploadFile, FormModeUploadFiles,
+ FormModeSwitch,
+ FormModeRate,
+ FormModeCitySelector,
+}
+
+var FormModeMap = map[string]string{
+ FormModeInput: "文本输入",
+ FormModeInputNumber: "数字输入",
+ FormModeInputTextarea: "文本域",
+ FormModeInputEditor: "富文本",
+ FormModeInputYaml: "YAML配置",
+ FormModeInputDynamic: "动态键值对",
+ FormModeDate: "日期选择(Y-M-D)",
+ FormModeDateRange: "日期范围选择",
+ FormModeTime: "时间选择(Y-M-D H:i:s)",
+ FormModeTimeRange: "时间范围选择",
+ FormModeRadio: "单选按钮",
+ FormModeCheckbox: "复选按钮",
+ FormModeSelect: "单选下拉框",
+ FormModeSelectMultiple: "多选下拉框",
+ FormModeTreeSelect: "树型选择",
+ FormModeCascader: "级联选择",
+ FormModeUploadImage: "单图上传",
+ FormModeUploadImages: "多图上传",
+ FormModeUploadFile: "单文件上传",
+ FormModeUploadFiles: "多文件上传",
+ FormModeSwitch: "开关",
+ FormModeRate: "评分",
+ FormModeCitySelector: "省市区选择",
+}
+
+// 表单验证
+const (
+ FormRoleNone = "none"
+ FormRoleIp = "ip"
+ FormRolePercentage = "percentage"
+ FormRoleTel = "tel"
+ FormRolePhone = "phone"
+ FormRoleQq = "qq"
+ FormRoleEmail = "email"
+ FormRoleIdCard = "idCard"
+ FormRoleNum = "num"
+ FormRoleBankCard = "bankCard"
+ FormRoleWeibo = "weibo"
+ FormRoleUserName = "userName"
+ FormRoleAccount = "account"
+ FormRolePassword = "password"
+ FormRoleAmount = "amount"
+ FormRoleYaml = "yaml"
+)
+
+var FormRoleMap = map[string]string{
+ FormRoleNone: "不验证",
+ FormRoleIp: "Ipv4或Ipv6",
+ FormRolePercentage: "0-100百分比",
+ FormRoleTel: "固话格式",
+ FormRolePhone: "手机号",
+ FormRoleQq: "QQ号码",
+ FormRoleEmail: "邮箱",
+ FormRoleIdCard: "身份证",
+ FormRoleNum: "非零正整数",
+ FormRoleBankCard: "银行卡",
+ FormRoleWeibo: "微博号",
+ FormRoleUserName: "用户名",
+ FormRoleAccount: "账号",
+ FormRolePassword: "密码",
+ FormRoleAmount: "金额",
+ FormRoleYaml: "YAML格式",
+}
+
+// 查询条件
+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(doc, val)" // JSON_CONTAINS(json_doc, val[, path]) // 判断是否包含某个json值
+)
+
+var WhereModes = []string{WhereModeEq,
+ WhereModeNeq, WhereModeGt, WhereModeGte, WhereModeLt, WhereModeLte,
+ WhereModeIn, WhereModeNotIn,
+ WhereModeBetween, WhereModeNotBetween,
+ WhereModeLike, WhereModeLikeAll, WhereModeNotLike,
+ WhereModeJsonContains,
+}
+
+// 表格列的排序方式
+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 {
+ case GoTypeInt, GoTypeUint, GoTypeInt64, GoTypeUint64:
+ return true
+ case GoTypeFloat32, GoTypeFloat64:
+ return true
+ }
+ return false
+}
+
+// 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.Name == column {
+ return true
+ }
+ }
+ return false
+}
+
+func HasColumnWithFormMode(fields []*sysin.GenCodesColumnListModel, formMode string) bool {
+ for _, field := range fields {
+ if field.FormMode == formMode {
+ return true
+ }
+ }
+ return false
+}
+
+func HasMaxSort(fields []*sysin.GenCodesColumnListModel) bool {
+ return HasColumn(fields, "sort")
+}
+
+func HasStatus(headOps []string, fields []*sysin.GenCodesColumnListModel) bool {
+ if !gstr.InArray(headOps, "status") {
+ return false
+ }
+ return HasColumn(fields, "status")
+}
+
+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
+}
diff --git a/server/internal/library/hggen/views/curd.go b/server/internal/library/hggen/views/curd.go
new file mode 100644
index 0000000..c0e31b8
--- /dev/null
+++ b/server/internal/library/hggen/views/curd.go
@@ -0,0 +1,1009 @@
+// Package views
+// @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 views
+
+import (
+ "context"
+ "hotgo/internal/consts"
+ "hotgo/internal/dao"
+ "hotgo/internal/library/hggen/internal/cmd/gendao"
+ "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"
+
+ "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"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gtime"
+ "github.com/gogf/gf/v2/os/gview"
+ "github.com/gogf/gf/v2/text/gstr"
+)
+
+var Curd = gCurd{}
+
+type gCurd struct{}
+
+type CurdStep struct {
+ 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 {
+ Uuid string `json:"uuid"`
+ LinkTable string `json:"linkTable"`
+ Alias string `json:"alias"`
+ LinkMode int `json:"linkMode"`
+ Field string `json:"field"`
+ MasterField string `json:"masterField"`
+ DaoName string `json:"daoName"`
+ Columns []*sysin.GenCodesColumnListModel `json:"columns"`
+}
+
+type CurdOptionsMenu struct {
+ Icon string `json:"icon"`
+ Pid int `json:"pid"`
+ Sort int `json:"sort"`
+}
+
+type 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配置
+ Config *model.GenerateConfig // 生成配置
+ view *gview.View // 视图模板
+ content *sysin.GenCodesPreviewModel // 页面代码
+ masterFields []*sysin.GenCodesColumnListModel // 主表字段属性
+ pk *sysin.GenCodesColumnListModel // 主键属性
+ options *CurdOptions // 生成选项
+}
+
+type CurdBuildEvent map[string]func(ctx context.Context) (err error)
+
+type CurdBuildInput struct {
+ PreviewIn *CurdPreviewInput // 预览参数
+ BeforeEvent CurdBuildEvent // 前置事件
+ AfterEvent CurdBuildEvent // 后置事件
+}
+
+func (l *gCurd) initInput(ctx context.Context, in *CurdPreviewInput) (err error) {
+ in.content = new(sysin.GenCodesPreviewModel)
+ in.content.Views = make(map[string]*sysin.GenFile)
+
+ // 初始化生成选项
+ if err = initOptions(in); err != nil {
+ return err
+ }
+
+ // 初始化表字段配置
+ if err = initTableField(ctx, in); err != nil {
+ return err
+ }
+
+ // 初始化树表
+ if err = initTableTree(in); err != nil {
+ return err
+ }
+
+ initStep(in)
+
+ // 初始化方法字典
+ 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("没有找到生成模板的配置,请检查!")
+ }
+
+ // api前缀
+ apiPrefix := gstr.LcFirst(in.In.VarName)
+ if in.Config.Application.Crud.Templates[in.In.GenTemplate].IsAddon {
+ apiPrefix = in.In.AddonName + "/" + apiPrefix
+ }
+ in.options.ApiPrefix = apiPrefix
+
+ if err = checkCurdPath(in.Config.Application.Crud.Templates[in.In.GenTemplate], in.In.AddonName); err != nil {
+ return
+ }
+ in.options.TemplateGroup = in.Config.Application.Crud.Templates[in.In.GenTemplate].MasterPackage
+ 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") && 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.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) {
+ temp := in.Config.Application.Crud.Templates[in.In.GenTemplate]
+ view := gview.New()
+ err = view.SetConfigWithMap(g.Map{
+ "Paths": temp.TemplatePath,
+ "Delimiters": in.Config.Delimiters,
+ })
+ if err != nil {
+ return
+ }
+
+ now := gtime.Now()
+ view.BindFuncMap(g.Map{
+ "NowYear": now.Year, // 当前年
+ "ToLower": strings.ToLower, // 全部小写
+ "LcFirst": gstr.LcFirst, // 首字母小写
+ "UcFirst": gstr.UcFirst, // 首字母大写
+ "ToTSArray": ToTSArray, // 转为ts数组格式
+ })
+
+ if err = l.generateWebModelDictOptions(ctx, in); err != nil {
+ return
+ }
+
+ modName, err := GetModName(ctx)
+ if err != nil {
+ return
+ }
+ importApi := gstr.Replace(temp.ApiPath, "./", modName+"/") + "/" + strings.ToLower(in.In.VarName)
+ importInput := gstr.Replace(temp.InputPath, "./", modName+"/")
+ importController := gstr.Replace(temp.ControllerPath, "./", modName+"/")
+ importService := "hotgo/internal/service"
+ if temp.IsAddon {
+ importService = "hotgo/addons/" + in.In.AddonName + "/service"
+ }
+
+ in.options.ImportWebApi = "@/api/" + gstr.LcFirst(in.In.VarName)
+ if temp.IsAddon {
+ in.options.ImportWebApi = "@/api/addons/" + in.In.AddonName + "/" + gstr.LcFirst(in.In.VarName)
+ }
+
+ componentPrefix := gstr.LcFirst(in.In.VarName)
+ if temp.IsAddon {
+ componentPrefix = "addons/" + in.In.AddonName + "/" + componentPrefix
+ }
+
+ nowTime := now.Format("Y-m-d H:i:s")
+ view.Assigns(gview.Params{
+ "templateGroup": in.options.TemplateGroup, // 生成模板分组名称
+ "servFunName": l.parseServFunName(in.options.TemplateGroup, in.In.VarName), // 业务服务名称
+ "nowTime": nowTime, // 当前时间
+ "version": runtime.Version(), // GO 版本
+ "hgVersion": consts.VersionApp, // HG 版本
+ "varName": in.In.VarName, // 实体名称
+ "tableComment": in.In.TableComment, // 对外名称
+ "daoName": in.In.DaoName, // ORM模型
+ "masterFields": in.masterFields, // 主表字段
+ "pk": in.pk, // 主键属性
+ "options": in.options, // 提交选项
+ "dictOptions": in.options.DictOps, // web字典选项
+ "importApi": importApi, // 导入goApi包
+ "importInput": importInput, // 导入input包
+ "importController": importController, // 导入控制器包
+ "importService": importService, // 导入业务服务
+ "importWebApi": in.options.ImportWebApi, // 导入webApi
+ "apiPrefix": in.options.ApiPrefix, // api前缀
+ "componentPrefix": componentPrefix, // vue子组件前缀
+ "in": in.In, // 在模版中使用`in`参数,如:插件目录名称
+ })
+
+ in.view = view
+ return
+}
+
+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
+ }
+
+ db, err := g.DB().Open(ParseDBConfigNodeLink(&gdb.ConfigNode{Link: in.PreviewIn.DaoConfig.Link}))
+ if err != nil {
+ err = gerror.Newf("连接数据库失败,请检查配置文件[server/hack/config.yaml]数据库配置是否正确!err:%v", err.Error())
+ return err
+ }
+
+ defer db.Close()
+ if err = db.Ping(); err != nil {
+ err = gerror.Newf("数据库访问异常,请检查配置文件[server/hack/config.yaml]数据库配置是否正确!err:%v", err.Error())
+ return
+ }
+
+ // 前置操作
+ if len(in.BeforeEvent) > 0 {
+ for name, f := range in.BeforeEvent {
+ if gstr.InArray(in.PreviewIn.options.AutoOps, name) {
+ if err = f(ctx); err != nil {
+ return gerror.Newf("in doBuild operation beforeEvent to '%s' failed:%v", name, err)
+ }
+ }
+ }
+ }
+
+ // 处理sql文件
+ handleSqlFile := func(vi *sysin.GenFile) (err error) {
+ // 无需生成
+ if vi.Meth != consts.GenCodesBuildMethCreate && vi.Meth != consts.GenCodesBuildMethCover {
+ return
+ }
+
+ if err = gfile.PutContents(vi.Path, strings.TrimSpace(vi.Content)); err != nil {
+ return gerror.Newf("writing content to '%s' failed: %v", vi.Path, err)
+ }
+
+ // 导入失败,将sql文件删除
+ if err = ImportSql(ctx, vi.Path); err != nil {
+ _ = gfile.RemoveAll(vi.Path)
+ }
+ return
+ }
+
+ // 将sql文件提取出来优先处理
+ // sql执行过程出错是高概率事件,后期在执行前要进行预效验,尽量减少在执行过程中出错的可能性
+ sqlGenFile, ok := preview.Views["source.sql"]
+ if ok {
+ delete(preview.Views, "source.sql")
+ if err = handleSqlFile(sqlGenFile); err != nil {
+ return
+ }
+ }
+
+ for _, vi := range preview.Views {
+ // 无需生成
+ if vi.Meth != consts.GenCodesBuildMethCreate && vi.Meth != consts.GenCodesBuildMethCover {
+ continue
+ }
+
+ 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 len(in.AfterEvent) > 0 {
+ for name, f := range in.AfterEvent {
+ if gstr.InArray(in.PreviewIn.options.AutoOps, name) {
+ if err = f(ctx); err != nil {
+ return gerror.Newf("in doBuild operation afterEvent to '%s' failed:%v", name, err)
+ }
+ }
+ }
+ }
+ g.Log().Debugf(ctx, "generate code operation completed, %vms", gtime.Now().Sub(st).Milliseconds())
+ return
+}
+
+func (l *gCurd) DoPreview(ctx context.Context, in *CurdPreviewInput) (res *sysin.GenCodesPreviewModel, err error) {
+ // 初始化
+ if err = l.initInput(ctx, in); err != nil {
+ return nil, err
+ }
+
+ // 加载模板
+ if err = l.loadView(ctx, in); err != nil {
+ return nil, err
+ }
+
+ if err = l.generateApiContent(ctx, in); err != nil {
+ return nil, err
+ }
+
+ if err = l.generateInputContent(ctx, in); err != nil {
+ return nil, err
+ }
+
+ if err = l.generateControllerContent(ctx, in); err != nil {
+ return nil, err
+ }
+
+ if err = l.generateLogicContent(ctx, in); err != nil {
+ return nil, err
+ }
+
+ if err = l.generateRouterContent(ctx, in); err != nil {
+ return nil, err
+ }
+
+ if err = l.generateWebApiContent(ctx, in); err != nil {
+ return nil, err
+ }
+
+ if err = l.generateWebModelContent(ctx, in); err != nil {
+ return nil, err
+ }
+
+ if err = l.generateWebIndexContent(ctx, in); err != nil {
+ return nil, err
+ }
+
+ if err = l.generateWebEditContent(ctx, in); err != nil {
+ return nil, err
+ }
+
+ if err = l.generateWebViewContent(ctx, in); err != nil {
+ return nil, err
+ }
+
+ if err = l.generateSqlContent(ctx, in); err != nil {
+ return nil, err
+ }
+
+ in.content.Config = in.Config
+ res = in.content
+ return
+}
+
+func (l *gCurd) generateApiContent(ctx context.Context, in *CurdPreviewInput) (err error) {
+ var (
+ name = "api.go"
+ tplData = g.Map{}
+ genFile = new(sysin.GenFile)
+ )
+ genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
+ if err != nil {
+ return err
+ }
+
+ genFile.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) {
+ genFile.Meth = consts.GenCodesBuildMethSkip
+ }
+
+ genFile.Required = true
+
+ if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") {
+ genFile.Meth = consts.GenCodesBuildMethCover
+ }
+
+ in.content.Views[name] = genFile
+ return
+}
+
+func (l *gCurd) generateInputContent(ctx context.Context, in *CurdPreviewInput) (err error) {
+ var (
+ name = "input.go"
+ genFile = new(sysin.GenFile)
+ )
+
+ tplData, err := l.inputTplData(ctx, in)
+ if err != nil {
+ return err
+ }
+
+ genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
+ if err != nil {
+ return err
+ }
+
+ genFile.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) {
+ genFile.Meth = consts.GenCodesBuildMethSkip
+ }
+ genFile.Required = true
+
+ if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") {
+ genFile.Meth = consts.GenCodesBuildMethCover
+ }
+
+ in.content.Views[name] = genFile
+ return
+}
+
+func (l *gCurd) generateControllerContent(ctx context.Context, in *CurdPreviewInput) (err error) {
+ var (
+ name = "controller.go"
+ tplData = g.Map{}
+ genFile = new(sysin.GenFile)
+ )
+
+ genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
+ 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) {
+ genFile.Meth = consts.GenCodesBuildMethSkip
+ }
+ genFile.Required = true
+
+ if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") {
+ genFile.Meth = consts.GenCodesBuildMethCover
+ }
+
+ in.content.Views[name] = genFile
+ return
+}
+
+func (l *gCurd) generateLogicContent(ctx context.Context, in *CurdPreviewInput) (err error) {
+ var (
+ name = "logic.go"
+ genFile = new(sysin.GenFile)
+ )
+
+ tplData, err := l.logicTplData(ctx, in)
+ if err != nil {
+ return err
+ }
+
+ genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
+ if err != nil {
+ return err
+ }
+
+ genFile.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) {
+ genFile.Meth = consts.GenCodesBuildMethSkip
+ }
+ genFile.Required = true
+
+ if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") {
+ genFile.Meth = consts.GenCodesBuildMethCover
+ }
+
+ in.content.Views[name] = genFile
+ return
+}
+
+func (l *gCurd) generateRouterContent(ctx context.Context, in *CurdPreviewInput) (err error) {
+ var (
+ name = "router.go"
+ tplData = g.Map{}
+ genFile = new(sysin.GenFile)
+ )
+ genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
+ if err != nil {
+ return err
+ }
+
+ genFile.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) {
+ genFile.Meth = consts.GenCodesBuildMethSkip
+ }
+ genFile.Required = true
+
+ if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") {
+ genFile.Meth = consts.GenCodesBuildMethCover
+ }
+
+ in.content.Views[name] = genFile
+ return
+}
+
+func (l *gCurd) generateWebApiContent(ctx context.Context, in *CurdPreviewInput) (err error) {
+ var (
+ name = "web.api.ts"
+ tplData = g.Map{}
+ genFile = new(sysin.GenFile)
+ )
+ genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
+ if err != nil {
+ return err
+ }
+
+ genFile.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) {
+ genFile.Meth = consts.GenCodesBuildMethSkip
+ }
+ genFile.Required = true
+
+ if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") {
+ genFile.Meth = consts.GenCodesBuildMethCover
+ }
+
+ in.content.Views[name] = genFile
+ return
+}
+
+func (l *gCurd) generateWebModelContent(ctx context.Context, in *CurdPreviewInput) (err error) {
+ var (
+ name = "web.model.ts"
+ genFile = new(sysin.GenFile)
+ )
+
+ tplData, err := l.webModelTplData(ctx, in)
+ if err != nil {
+ return
+ }
+
+ genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
+ if err != nil {
+ 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) {
+ genFile.Meth = consts.GenCodesBuildMethSkip
+ }
+ genFile.Required = true
+
+ if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") {
+ genFile.Meth = consts.GenCodesBuildMethCover
+ }
+ in.content.Views[name] = genFile
+ return
+}
+
+func (l *gCurd) generateWebIndexContent(ctx context.Context, in *CurdPreviewInput) (err error) {
+ var (
+ name = "web.index.vue"
+ genFile = new(sysin.GenFile)
+ )
+
+ tplData, err := l.webIndexTplData(ctx, in)
+ if err != nil {
+ return err
+ }
+
+ genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
+ if err != nil {
+ return err
+ }
+
+ genFile.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) {
+ genFile.Meth = consts.GenCodesBuildMethSkip
+ }
+ genFile.Required = true
+
+ if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") {
+ genFile.Meth = consts.GenCodesBuildMethCover
+ }
+ in.content.Views[name] = genFile
+ return
+}
+
+func (l *gCurd) generateWebEditContent(ctx context.Context, in *CurdPreviewInput) (err error) {
+ var (
+ name = "web.edit.vue"
+ genFile = new(sysin.GenFile)
+ )
+
+ tplData, err := l.webEditTplData(ctx, in)
+ if err != nil {
+ return err
+ }
+
+ genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
+ if err != nil {
+ return err
+ }
+
+ genFile.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) {
+ genFile.Meth = consts.GenCodesBuildMethSkip
+ }
+ genFile.Required = true
+ if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") {
+ genFile.Meth = consts.GenCodesBuildMethCover
+ }
+
+ if !in.options.Step.HasEdit {
+ genFile.Meth = consts.GenCodesBuildIgnore
+ genFile.Required = false
+ }
+
+ in.content.Views[name] = genFile
+ return
+}
+
+func (l *gCurd) generateWebViewContent(ctx context.Context, in *CurdPreviewInput) (err error) {
+ var (
+ name = "web.view.vue"
+ genFile = new(sysin.GenFile)
+ )
+
+ tplData, err := l.webViewTplData(ctx, in)
+ if err != nil {
+ return err
+ }
+
+ genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
+ if err != nil {
+ return err
+ }
+
+ genFile.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) {
+ genFile.Meth = consts.GenCodesBuildMethSkip
+ }
+ genFile.Required = true
+
+ if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") {
+ genFile.Meth = consts.GenCodesBuildMethCover
+ }
+
+ if !in.options.Step.HasView {
+ genFile.Meth = consts.GenCodesBuildIgnore
+ genFile.Required = false
+ }
+
+ in.content.Views[name] = genFile
+ return
+}
+
+func (l *gCurd) generateSqlContent(ctx context.Context, in *CurdPreviewInput) (err error) {
+ var (
+ name = "source.sql"
+ config = g.DB("default").GetConfig()
+ tplData = g.Map{
+ "dbName": config.Name,
+ "menuTable": config.Prefix + "admin_menu",
+ "mainComponent": "LAYOUT",
+ }
+ genFile = new(sysin.GenFile)
+ templateName = "source.sql.template"
+ )
+
+ // 根据数据库类型选择不同的模板
+ if config.Type == consts.DBPgsql {
+ templateName = "source_pgsql.sql.template"
+ }
+
+ 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
+ }
+
+ tplData["listLevel"] = tplData["dirLevel"].(int) + 1
+ 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
+ if gfile.Exists(genFile.Path) {
+ genFile.Meth = consts.GenCodesBuildMethSkip
+ }
+ genFile.Required = true
+
+ if !in.options.Step.HasMenu {
+ genFile.Meth = consts.GenCodesBuildIgnore
+ 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(dao.AdminMenu.Columns().Name).WhereIn(dao.AdminMenu.Columns().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, templateName, tplData)
+ if err != nil {
+ return err
+ }
+
+ in.content.Views[name] = genFile
+ return
+}
diff --git a/server/internal/library/hggen/views/curd_generate_input.go b/server/internal/library/hggen/views/curd_generate_input.go
new file mode 100644
index 0000000..8b4669c
--- /dev/null
+++ b/server/internal/library/hggen/views/curd_generate_input.go
@@ -0,0 +1,354 @@
+// Package views
+// @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 views
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "github.com/olekukonko/tablewriter/renderer"
+ "github.com/olekukonko/tablewriter/tw"
+ "strings"
+
+ "hotgo/internal/dao"
+ "hotgo/internal/model/input/sysin"
+
+ "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/olekukonko/tablewriter"
+)
+
+const (
+ InputTypeListInp = 1 // 列表输入
+ InputTypeListModel = 2 // 列表输出
+ InputTypeExportModel = 3 // 列表导出
+ 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"
+ EditInpValidatorYaml = "if err := validate.ValidateYAML(in.%s); err != nil {\n\t\treturn gerror.Newf(\"%s必须为有效的YAML格式: %%s\", err.Error())\n\t}\n"
+)
+
+var (
+ // tablewriter Options
+ twRenderer = tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{
+ Borders: tw.Border{Top: tw.Off, Bottom: tw.Off, Left: tw.Off, Right: tw.Off},
+ Settings: tw.Settings{
+ Separators: tw.Separators{BetweenRows: tw.Off, BetweenColumns: tw.Off},
+ },
+ Symbols: tw.NewSymbols(tw.StyleASCII),
+ }))
+ twConfig = tablewriter.WithConfig(tablewriter.Config{
+ Row: tw.CellConfig{
+ Formatting: tw.CellFormatting{AutoWrap: tw.WrapNone},
+ },
+ })
+)
+
+func (l *gCurd) inputTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) {
+ data = make(g.Map)
+ data["listInpColumns"] = l.generateInputListColumns(ctx, in, InputTypeListInp)
+ data["listModelColumns"] = l.generateInputListColumns(ctx, in, InputTypeListModel)
+ data["exportModelColumns"] = l.generateInputListColumns(ctx, in, InputTypeExportModel)
+ 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++
+ }
+ }
+
+ table := tablewriter.NewTable(buffer, twRenderer, twConfig)
+ table.Bulk(array)
+ table.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(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++
+ }
+ }
+ }
+
+ // 关联表
+ if len(in.options.Join) > 0 {
+ for _, v := range in.options.Join {
+ if !isEffectiveJoin(v) {
+ continue
+ }
+ for _, field := range v.Columns {
+ row := l.generateStructFieldDefinition(in, field, inputType, false)
+ if row != nil {
+ array[index] = row
+ index++
+ }
+ }
+ }
+ }
+
+ table := tablewriter.NewTable(buffer, twRenderer, twConfig)
+ table.Bulk(array)
+ table.Render()
+ stContent := buffer.String()
+ // Let's do this hack of table writer for indent!
+ stContent = gstr.Replace(stContent, " #", "")
+ stContent = gstr.Replace(stContent, "` ", "`")
+ stContent = gstr.Replace(stContent, "``", "")
+ stContent = removeEndWrap(stContent)
+
+ buffer.Reset()
+ buffer.WriteString(stContent)
+ return buffer.String()
+}
+
+// generateStructFieldForModel generates and returns the attribute definition for specified field.
+func (l *gCurd) generateStructFieldDefinition(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
+ }
+
+ isUpdateOrInsertInputFields := func() bool {
+ if !isMaster {
+ return field.IsEdit
+ }
+
+ if field.IsEdit {
+ return true
+ }
+
+ // 树表内部维护字段
+ if in.options.Step.IsTreeTable && gstr.InArray(defaultTreeFields, field.Name) {
+ return true
+ }
+
+ // 修改人
+ if inputType == InputTypeUpdateFields && field.GoName == "UpdatedBy" {
+ return true
+ }
+
+ // 创建人
+ if inputType == InputTypeInsertFields && field.GoName == "CreatedBy" {
+ return true
+ }
+ return false
+ }
+
+ isQuery := false
+
+ switch inputType {
+ case InputTypeListInp:
+ 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 {
+ // 查询用户摘要时,固定接收字符串类型
+ 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 IsIndexPK(field.Index) && isMaster {
+ addResult()
+ // 树表的pid字段
+ } else if in.options.Step.IsTreeTable && IsPidName(field.Name) {
+ addResult()
+ } else if field.IsList {
+ addResult()
+ } else {
+ return nil
+ }
+ case InputTypeExportModel:
+ if !field.IsExport {
+ return nil
+ }
+ addResult()
+ case InputTypeEditInpValidator:
+ if !field.IsEdit {
+ return nil
+ }
+ if !field.Required && (field.FormRole == "none" || field.FormRole == "") {
+ return nil
+ }
+ rule := "// 验证" + field.Dc + "\n"
+ if field.Required && (field.FormRole == FormRoleNone || field.FormRole == "") {
+ field.FormRole = "required"
+ }
+ if err, s := makeValidatorFunc(field); err != nil {
+ return nil
+ } else {
+ rule += s
+ }
+ result = []string{rule}
+ case InputTypeUpdateFields:
+ if !isUpdateOrInsertInputFields() {
+ return nil
+ }
+ addResult()
+ case InputTypeInsertFields:
+ if !isUpdateOrInsertInputFields() {
+ return nil
+ }
+ 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")
+ }
+ return result
+}
+
+func makeValidatorFunc(field *sysin.GenCodesColumnListModel) (err error, rule string) {
+ if field.FormRole == "required" {
+ rule = fmt.Sprintf(EditInpValidatorGenerally, "required", field.GoName, field.Dc+"不能为空")
+ } else if field.FormRole == FormRoleIp {
+ rule = fmt.Sprintf(EditInpValidatorGenerally, "ip", field.GoName, field.Dc+"必须为IPV4或IPV6")
+ } else if field.FormRole == FormRolePercentage {
+ rule = fmt.Sprintf(EditInpValidatorGenerally, "min:0|max:100", field.GoName, field.Dc+"必须0-100之间")
+ } else if field.FormRole == FormRoleTel {
+ rule = fmt.Sprintf(EditInpValidatorGenerally, "telephone", field.GoName, field.Dc+"不是座机号码")
+ } else if field.FormRole == FormRolePhone {
+ rule = fmt.Sprintf(EditInpValidatorGenerally, "phone", field.GoName, field.Dc+"不是手机号码")
+ } else if field.FormRole == FormRoleQq {
+ rule = fmt.Sprintf(EditInpValidatorGenerally, "qq", field.GoName, field.Dc+"不是QQ号码")
+ } else if field.FormRole == FormRoleEmail {
+ rule = fmt.Sprintf(EditInpValidatorGenerally, "email", field.GoName, field.Dc+"不是邮箱地址")
+ } else if field.FormRole == FormRoleIdCard {
+ rule = fmt.Sprintf(EditInpValidatorGenerally, "resident-id", field.GoName, field.Dc+"不是身份证号码")
+ } else if field.FormRole == FormRoleNum {
+ rule = fmt.Sprintf(EditInpValidatorGenerally, "min:1", field.GoName, field.Dc+"必须大于0")
+ } else if field.FormRole == FormRoleBankCard {
+ rule = fmt.Sprintf(EditInpValidatorGenerally, "bank-card", field.GoName, field.Dc+"不是银行卡号")
+ } else if field.FormRole == FormRoleWeibo {
+ rule = fmt.Sprintf(EditInpValidatorGenerally, "regex:^[0-9a-zA-Z\\u4e00-\\u9fa5_-]*$", field.GoName, field.Dc+"不是微博号")
+ } else if field.FormRole == FormRoleUserName {
+ rule = fmt.Sprintf(EditInpValidatorGenerally, "regex:^[0-9a-zA-Z]{6,16}$", field.GoName, field.Dc+"必须为6-16位由字母和数字组成")
+ } else if field.FormRole == FormRoleAccount {
+ rule = fmt.Sprintf(EditInpValidatorGenerally, "regex:^[\\w_\\d]{6,16}$", field.GoName, field.Dc+"必须为6-16位由字母、数字或下划线组成")
+ } else if field.FormRole == FormRolePassword {
+ rule = fmt.Sprintf(EditInpValidatorGenerally, "regex:^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,18}$", field.GoName, field.Dc+"必须包含6-18为字母和数字")
+ } else if field.FormRole == FormRoleAmount {
+ rule = fmt.Sprintf(EditInpValidatorGenerally, "regex:(^[0-9]{1,10}$)|(^[0-9]{1,10}[\\\\.]{1}[0-9]{1,2}$)", field.GoName, field.Dc+"最多允许输入10位整数及2位小数")
+ } else if field.FormRole == FormRoleYaml {
+ rule = fmt.Sprintf(EditInpValidatorYaml, field.GoName, field.Dc)
+ } else {
+ err = gerror.New("not support")
+ }
+
+ // 生成验证字典
+ if field.DictType > 0 {
+ var (
+ ctx = context.Background()
+ valueType gdb.Value
+ resultValue gdb.Result
+ )
+ valueType, err = dao.SysDictType.Ctx(ctx).Fields(dao.SysDictType.Columns().Type).Where(dao.SysDictType.Columns().Id, field.DictType).Value()
+ if err != nil {
+ return
+ }
+ resultValue, err = dao.SysDictData.Ctx(context.Background()).Fields(dao.SysDictData.Columns().Value).Where(dao.SysDictData.Columns().Type, valueType.String()).All()
+ if err != nil {
+ return
+ }
+ if resultValue.Len() > 0 {
+ names := make([]string, 0)
+ for _, item := range resultValue {
+ names = append(names, item["value"].String())
+ }
+ dictRule := "in:" + strings.Join(names, ",")
+ rule += fmt.Sprintf(EditInpValidatorGenerally, dictRule, field.GoName, field.Dc+"值不正确")
+ err = nil
+ }
+ }
+ return
+}
diff --git a/server/internal/library/hggen/views/curd_generate_logic.go b/server/internal/library/hggen/views/curd_generate_logic.go
new file mode 100644
index 0000000..78bc993
--- /dev/null
+++ b/server/internal/library/hggen/views/curd_generate_logic.go
@@ -0,0 +1,318 @@
+// Package views
+// @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 views
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/text/gstr"
+ "hotgo/internal/consts"
+ "hotgo/internal/model/input/sysin"
+)
+
+const (
+ LogicWhereComments = "\n\t// 查询%s\n"
+ LogicWhereNoSupport = "\t// TODO 暂不支持生成[ %s ]查询方式,请自行补充此处代码!"
+ 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).OmitEmptyData().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.%s); 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}"
+ LogicDeletedUpdate = "g.Map{\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)
+ data["switchUpdate"] = l.generateLogicSwitchUpdate(ctx, in)
+ data["statusUpdate"] = l.generateLogicStatusUpdate(ctx, in)
+ data["deletedUpdate"] = l.generateLogicDeletedUpdate(ctx, in)
+ return
+}
+
+func (l *gCurd) generateLogicDeletedUpdate(ctx context.Context, in *CurdPreviewInput) string {
+ isDestroy := false
+ var update string
+ for _, field := range in.masterFields {
+ if field.GoName == "DeletedBy" {
+ update += "\t\tdao." + in.In.DaoName + ".Columns().DeletedBy: contexts.GetUserId(ctx),\n"
+ isDestroy = true
+ }
+ if field.GoName == "DeletedAt" {
+ update += "\t\tdao." + in.In.DaoName + ".Columns().DeletedAt: gtime.Now(),\n"
+ }
+ }
+
+ if !isDestroy {
+ return ""
+ }
+
+ update += "\t"
+ return fmt.Sprintf(LogicDeletedUpdate, update)
+}
+
+func (l *gCurd) generateLogicStatusUpdate(ctx context.Context, in *CurdPreviewInput) string {
+ var update string
+ for _, field := range in.masterFields {
+ if field.GoName == "UpdatedBy" {
+ update += "\t\tdao." + in.In.DaoName + ".Columns().UpdatedBy: contexts.GetUserId(ctx),\n"
+ }
+ }
+
+ update += "\t"
+ return fmt.Sprintf(LogicStatusUpdate, in.In.DaoName, update)
+}
+
+func (l *gCurd) generateLogicSwitchUpdate(ctx context.Context, in *CurdPreviewInput) string {
+ var update string
+ for _, field := range in.masterFields {
+ if field.GoName == "UpdatedBy" {
+ update += "\t\tdao." + in.In.DaoName + ".Columns().UpdatedBy: contexts.GetUserId(ctx),\n"
+ }
+ }
+
+ update += "\t"
+ return fmt.Sprintf(LogicSwitchUpdate, update)
+}
+
+func (l *gCurd) generateLogicSwitchFields(ctx context.Context, in *CurdPreviewInput) string {
+ buffer := bytes.NewBuffer(nil)
+ if in.options.Step.HasSwitch {
+ for _, field := range in.masterFields {
+ if field.FormMode == "Switch" {
+ buffer.WriteString("\t\tdao." + in.In.DaoName + ".Columns()." + field.GoName + ",\n")
+ }
+ }
+ }
+ return buffer.String()
+}
+
+func (l *gCurd) generateLogicEdit(ctx context.Context, in *CurdPreviewInput) g.Map {
+ var (
+ data = make(g.Map)
+ updateBuffer = bytes.NewBuffer(nil)
+ insertBuffer = bytes.NewBuffer(nil)
+ uniqueBuffer = bytes.NewBuffer(nil)
+ validationBuffer = bytes.NewBuffer(nil)
+ )
+
+ for _, field := range in.masterFields {
+ if field.GoName == "UpdatedBy" {
+ updateBuffer.WriteString("\t\tin.UpdatedBy = contexts.GetUserId(ctx)\n")
+ }
+
+ if field.GoName == "CreatedBy" {
+ insertBuffer.WriteString("\tin.CreatedBy = contexts.GetUserId(ctx)\n")
+ }
+
+ if field.Unique {
+ uniqueBuffer.WriteString(fmt.Sprintf(LogicEditUnique, field.GoName, in.In.DaoName, in.In.DaoName, field.GoName, field.GoName, field.Dc, in.pk.GoName))
+ }
+
+ // 添加 YAML 格式验证
+ if field.IsEdit && field.FormRole == FormRoleYaml {
+ validationBuffer.WriteString(fmt.Sprintf(EditInpValidatorYaml, field.GoName, field.Dc))
+ }
+ }
+
+ notFilterAuth := ""
+ if gstr.InArray(in.options.ColumnOps, "notFilterAuth") {
+ notFilterAuth = ", &handler.Option{FilterAuth: false}"
+ }
+
+ updateBuffer.WriteString(fmt.Sprintf(LogicEditUpdate, notFilterAuth, in.options.TemplateGroup, in.In.VarName, in.pk.GoName, in.In.TableComment))
+ insertBuffer.WriteString(fmt.Sprintf(LogicEditInsert, in.options.TemplateGroup, in.In.VarName, in.In.TableComment))
+
+ data["update"] = updateBuffer.String()
+ data["insert"] = insertBuffer.String()
+ data["unique"] = uniqueBuffer.String()
+ data["validation"] = validationBuffer.String()
+ return data
+}
+
+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(" + statement + "dao." + in.In.DaoName + ".Columns().Sort).")
+ }
+ buffer.WriteString("OrderDesc(" + statement + "dao." + in.In.DaoName + ".Columns()." + in.pk.GoName + ")")
+ return buffer.String()
+}
+
+func (l *gCurd) generateLogicListJoin(ctx context.Context, in *CurdPreviewInput) (link string) {
+ connector := `"="`
+ if hasEffectiveJoins(in.options.Join) {
+ linkBuffer := bytes.NewBuffer(nil)
+ for _, join := range in.options.Join {
+ if isEffectiveJoin(join) {
+ 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")
+ }
+ }
+ link = linkBuffer.String()
+ }
+ 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, in.masterFields, in.In.DaoName, "")
+
+ // 关联表
+ if hasEffectiveJoins(in.options.Join) {
+ for _, v := range in.options.Join {
+ if isEffectiveJoin(v) {
+ l.generateLogicListWhereEach(buffer, in, v.Columns, v.DaoName, v.Alias)
+ }
+ }
+ }
+ return buffer.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 {
+ 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 == GoTypeBool {
+ linkMode = `true` // bool 类型始终可以查询
+ } else if field.GoType == GoTypeGTime {
+ linkMode = `in.` + field.GoName + ` != nil`
+ } else if field.GoType == GoTypeJson {
+ linkMode = `!in.` + field.GoName + `.IsNil()`
+ } else {
+ linkMode = `in.` + field.GoName + ` != ""`
+ }
+
+ if field.QueryWhere == WhereModeBetween || field.QueryWhere == WhereModeNotBetween {
+ linkMode = `len(in.` + field.GoName + `) == 2`
+ }
+
+ // 如果是关联表重新转换字段
+ columnName = field.GoName
+ if isLink {
+ columnName = gstr.CaseCamel(field.Name)
+ }
+
+ switch field.QueryWhere {
+ case WhereModeEq:
+ 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." + wherePrefix + "Not(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}"
+ case WhereModeGt:
+ 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." + wherePrefix + "GTE(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}"
+ case WhereModeLt:
+ 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." + wherePrefix + "LTE(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}"
+ case WhereModeIn:
+ 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." + wherePrefix + "NotIn(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}"
+ case WhereModeBetween:
+ 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." + wherePrefix + "NotBetween(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + "[0], in." + field.GoName + "[1])\n\t}"
+ case WhereModeLike:
+ fieldValue := "in." + field.GoName
+ if field.GoType != GoTypeString && field.GoType != GoTypeBytes {
+ fieldValue = "gconv.String(in." + field.GoName + ")"
+ }
+ whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "Like(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", " + fieldValue + ")\n\t}"
+ case WhereModeLikeAll:
+ val := `"%"+in.` + field.GoName + `+"%"`
+ if field.GoType != GoTypeString && field.GoType != GoTypeBytes {
+ val = `"%"+gconv.String(in.` + field.GoName + `)+"%"`
+ }
+ whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "Like(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", " + val + ")\n\t}"
+ case WhereModeNotLike:
+ fieldValue := "in." + field.GoName
+ if field.GoType != GoTypeString && field.GoType != GoTypeBytes {
+ fieldValue = "gconv.String(in." + field.GoName + ")"
+ }
+ whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "NotLike(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", " + fieldValue + ")\n\t}"
+ case WhereModeJsonContains:
+ val := tablePrefix + `"JSON_CONTAINS("+dao.` + daoName + `.Columns().` + columnName + `+",?)", in.` + field.GoName
+ whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "(" + val + ")\n\t}"
+
+ default:
+ buffer.WriteString(fmt.Sprintf(LogicWhereNoSupport, field.QueryWhere))
+ }
+
+ buffer.WriteString(whereTag + "\n")
+ }
+}
diff --git a/server/internal/library/hggen/views/curd_generate_web_edit.go b/server/internal/library/hggen/views/curd_generate_web_edit.go
new file mode 100644
index 0000000..25aeb10
--- /dev/null
+++ b/server/internal/library/hggen/views/curd_generate_web_edit.go
@@ -0,0 +1,232 @@
+// Package views
+// @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 views
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/text/gstr"
+)
+
+func (l *gCurd) webEditTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) {
+ data = make(g.Map)
+ data["formItem"] = l.generateWebEditFormItem(ctx, in)
+ data["script"] = l.generateWebEditScript(ctx, in)
+ return
+}
+
+func (l *gCurd) generateWebEditFormItem(ctx context.Context, in *CurdPreviewInput) string {
+ buffer := bytes.NewBuffer(nil)
+ for _, field := range in.masterFields {
+ if !field.IsEdit {
+ continue
+ }
+
+ if IsIndexPK(field.Index) {
+ continue
+ }
+
+ var (
+ defaultComponent = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.Dc, field.TsName)
+ component string
+ )
+
+ if in.options.Step.IsTreeTable && IsPidName(field.Name) {
+ field.FormMode = FormModePidTreeSelect
+ }
+
+ switch field.FormMode {
+ case FormModeInput:
+ component = defaultComponent
+
+ case FormModeInputNumber:
+ component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.Dc, field.TsName)
+
+ case FormModeInputTextarea:
+ component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.Dc, field.TsName)
+
+ case FormModeInputEditor:
+ component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName, field.TsName)
+
+ case FormModeInputYaml:
+ component = fmt.Sprintf(" \n handleYamlValidationChange(isValid, '%s')\"\n />\n ", field.Dc, field.TsName, field.TsName, field.TsName, field.Dc, field.TsName)
+
+ case FormModeInputDynamic:
+ component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName)
+
+ case FormModeDate:
+ component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName)
+
+ // case FormModeDateRange: // 必须要有两个字段,后面优化下
+
+ case FormModeTime:
+ component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName)
+
+ // case FormModeTimeRange: // 必须要有两个字段,后面优化下
+
+ case FormModeRadio:
+ component = fmt.Sprintf(" \n \n \n \n ", field.Dc, field.TsName, field.TsName, field.TsName, field.TsName, in.options.dictMap[field.TsName], field.TsName, field.TsName, field.TsName)
+
+ case FormModeCheckbox:
+ component = fmt.Sprintf(" \n \n \n \n \n \n ", field.Dc, field.TsName, field.TsName, in.options.dictMap[field.TsName])
+
+ case FormModeSelect:
+ if in.options.dictMap[field.TsName] != nil {
+ component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName, in.options.dictMap[field.TsName])
+ } else {
+ component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName)
+ }
+
+ case FormModeSelectMultiple:
+ component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName, in.options.dictMap[field.TsName])
+
+ case FormModeUploadImage:
+ component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName)
+
+ case FormModeUploadImages:
+ component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName)
+
+ case FormModeUploadFile:
+ component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName)
+
+ case FormModeUploadFiles:
+ component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName)
+
+ case FormModeSwitch:
+ component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName)
+
+ case FormModeRate:
+ component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName, field.GoName)
+
+ case FormModeCitySelector:
+ component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName)
+
+ case FormModePidTreeSelect:
+ component = fmt.Sprintf(`
+
+ `, field.Dc, in.pk.TsName, in.options.Tree.TitleField.TsName)
+
+ case FormModeTreeSelect:
+ component = fmt.Sprintf(`
+
+ `, field.Dc, field.TsName, field.Dc, field.TsName)
+
+ case FormModeCascader:
+ component = fmt.Sprintf(`
+
+ `, field.Dc, field.TsName, field.Dc, field.TsName)
+
+ default:
+ component = defaultComponent
+ }
+
+ buffer.WriteString(fmt.Sprintf("\n%v\n \n\n", field.FormGridSpan, component))
+ }
+ return buffer.String()
+}
+
+func (l *gCurd) generateWebEditScript(ctx context.Context, in *CurdPreviewInput) g.Map {
+ var (
+ data = make(g.Map)
+ importBuffer = bytes.NewBuffer(nil)
+ setupBuffer = bytes.NewBuffer(nil)
+ hasYamlField = false
+ )
+
+ // 导入字典
+ if in.options.DictOps.Has {
+ importBuffer.WriteString(" import { useDictStore } from '@/store/modules/dict';\n")
+ }
+
+ // 导入api
+ var importApiMethod = []string{"Edit", "View"}
+ if in.options.Step.HasMaxSort {
+ importApiMethod = append(importApiMethod, "MaxSort")
+ }
+ importBuffer.WriteString(" import " + ImportWebMethod(importApiMethod) + " from '" + in.options.ImportWebApi + "';\n")
+
+ // 导入model
+ var importModelMethod = []string{"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 {
+ continue
+ }
+ switch field.FormMode {
+ case FormModeDate, FormModeDateRange, FormModeTime, FormModeTimeRange:
+ if !gstr.Contains(importBuffer.String(), `import DatePicker`) {
+ importBuffer.WriteString(" import DatePicker from '@/components/DatePicker/datePicker.vue';\n")
+ }
+ case FormModeInputEditor:
+ if !gstr.Contains(importBuffer.String(), `import Editor`) {
+ importBuffer.WriteString(" import Editor from '@/components/Editor/editor.vue';\n")
+ }
+ case FormModeInputYaml:
+ if !gstr.Contains(importBuffer.String(), `import YamlEditor`) {
+ importBuffer.WriteString(" import YamlEditor from '@/components/YamlEditor/index.vue';\n")
+ }
+ hasYamlField = true
+ case FormModeUploadImage, FormModeUploadImages:
+ if !gstr.Contains(importBuffer.String(), `import UploadImage`) {
+ importBuffer.WriteString(" import UploadImage from '@/components/Upload/uploadImage.vue';\n")
+ }
+ case FormModeUploadFile, FormModeUploadFiles:
+ if !gstr.Contains(importBuffer.String(), `import UploadFile`) {
+ importBuffer.WriteString(" import UploadFile from '@/components/Upload/uploadFile.vue';\n")
+ }
+ case FormModeRate:
+ setupBuffer.WriteString(fmt.Sprintf(" function update%s(num) {\n formValue.value.%s = num;\n }\n", field.GoName, field.TsName))
+ case FormModeCitySelector:
+ if !gstr.Contains(importBuffer.String(), `import CitySelector`) {
+ importBuffer.WriteString(" import CitySelector from '@/components/CitySelector/citySelector.vue';\n")
+ }
+ }
+ }
+
+ // 根据是否有 YAML 字段添加相应的验证逻辑
+ if hasYamlField {
+ setupBuffer.WriteString(" const yamlValidationStates = ref(new Map());\n const isFormValid = computed(() => {\n for (const [fieldName, isValid] of yamlValidationStates.value) {\n if (!isValid) return false;\n }\n return true;\n });\n const handleYamlError = (error) => {\n console.error('YAML 验证错误:', error);\n };\n const handleYamlValid = (content) => {\n console.log('YAML 验证通过:', content);\n };\n const handleYamlValidationChange = (isValid, fieldName) => {\n yamlValidationStates.value.set(fieldName, isValid);\n };\n")
+ } else {
+ setupBuffer.WriteString(" const isFormValid = ref(true);\n")
+ }
+
+ data["import"] = importBuffer.String()
+ data["setup"] = setupBuffer.String()
+ return data
+}
diff --git a/server/internal/library/hggen/views/curd_generate_web_index.go b/server/internal/library/hggen/views/curd_generate_web_index.go
new file mode 100644
index 0000000..2193b21
--- /dev/null
+++ b/server/internal/library/hggen/views/curd_generate_web_index.go
@@ -0,0 +1,167 @@
+// Package views
+// @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 views
+
+import (
+ "bytes"
+ "context"
+
+ "github.com/gogf/gf/v2/frame/g"
+)
+
+func (l *gCurd) webIndexTplData(ctx context.Context, in *CurdPreviewInput) (g.Map, error) {
+ var (
+ data = make(g.Map)
+ importBuffer = bytes.NewBuffer(nil)
+ importVueMethod = []string{"h", "reactive", "ref", "computed"}
+ importApiMethod = []string{"List"}
+ importModelMethod = []string{"columns", "schemas", "State"}
+ importUtilsMethod = []string{"adaTableScrollX"}
+ importIcons []string
+ actionWidth int64 = 72
+ )
+
+ // 添加
+ if in.options.Step.HasAdd {
+ importIcons = append(importIcons, "PlusOutlined")
+ }
+
+ // 编辑
+ 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 {
+ importIcons = append(importIcons, "ExportOutlined")
+ importApiMethod = append(importApiMethod, "Export")
+ }
+
+ // 删除
+ 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 {
+ importApiMethod = append(importApiMethod, "Status")
+ in.options.Step.ActionColumnWidth += actionWidth
+ }
+
+ // 更多
+ // 查看详情
+ if in.options.Step.HasView {
+ in.options.Step.ActionColumnWidth += actionWidth
+ }
+
+ // 展开树
+ 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 { 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")
+
+ // 导入字典
+ if in.options.DictOps.Has {
+ importBuffer.WriteString(" import { useDictStore } from '@/store/modules/dict';\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")
+ }
+
+ // 没有需要查询的字段则隐藏搜索表单
+ isSearchForm := false
+ for _, field := range in.masterFields {
+ if field.IsQuery {
+ isSearchForm = true
+ break
+ }
+ }
+ if !isSearchForm {
+ if len(in.options.Join) > 0 {
+ LoopOut:
+ for _, v := range in.options.Join {
+ for _, column := range v.Columns {
+ if column.IsQuery {
+ isSearchForm = true
+ break LoopOut
+ }
+ }
+ }
+ }
+ }
+ data["isSearchForm"] = isSearchForm
+ data["import"] = importBuffer.String()
+ return data, nil
+}
diff --git a/server/internal/library/hggen/views/curd_generate_web_model.go b/server/internal/library/hggen/views/curd_generate_web_model.go
new file mode 100644
index 0000000..5ba4dd1
--- /dev/null
+++ b/server/internal/library/hggen/views/curd_generate_web_model.go
@@ -0,0 +1,493 @@
+// Package views
+// @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 views
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "hotgo/internal/dao"
+ "hotgo/internal/library/dict"
+ "hotgo/internal/model/input/sysin"
+ "hotgo/utility/convert"
+ "strings"
+
+ "github.com/gogf/gf/v2/errors/gerror"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/util/gconv"
+)
+
+type StateItem struct {
+ Name string
+ DefaultValue interface{}
+ Dc string
+ DataType string // 新增字段:存储字段类型信息
+}
+
+func (l *gCurd) webModelTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) {
+ data = make(g.Map)
+ 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"], data["const"] = l.generateWebModelImport(ctx, in)
+ return
+}
+
+func (l *gCurd) generateWebModelImport(ctx context.Context, in *CurdPreviewInput) (string, string) {
+ importBuffer := bytes.NewBuffer(nil)
+ constBuffer := bytes.NewBuffer(nil)
+
+ // 导入基础组件
+ 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 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.DictOps.Has {
+ importBuffer.WriteString("import { useDictStore } from '@/store/modules/dict';\n")
+ constBuffer.WriteString("const dict = useDictStore();\n")
+ }
+
+ if in.options.Step.HasSwitch {
+ importBuffer.WriteString("import { usePermission } from '@/hooks/web/usePermission';\n")
+ constBuffer.WriteString("const { hasPermission } = usePermission();\n")
+ constBuffer.WriteString("const $message = window['$message'];\n")
+ }
+
+ return importBuffer.String(), constBuffer.String()
+}
+
+func (l *gCurd) generateWebModelStateItems(ctx context.Context, in *CurdPreviewInput) (items []*StateItem) {
+ for _, field := range in.masterFields {
+ dataType := field.DataType
+ if dataType == "" && field.SqlType != "" {
+ if parts := strings.SplitN(field.SqlType, "(", 2); len(parts) > 0 {
+ dataType = parts[0]
+ }
+ }
+
+ isStr := isStringType(field.TsType, dataType)
+ isArray := strings.HasPrefix(dataType, "_") || strings.Contains(field.SqlType, "[]")
+
+ var value = field.DefaultValue
+ if value == nil {
+ if isArray {
+ value = "null"
+ } else if isStr {
+ value = ""
+ } else {
+ value = "null"
+ }
+ }
+ if value == "" {
+ if isArray || !isStr {
+ value = "null"
+ }
+ } else if valueStr, ok := value.(string); ok && isStr {
+ value = valueStr
+ }
+
+ // 选项组件默认值调整
+ if gconv.Int(value) == 0 && IsSelectFormMode(field.FormMode) {
+ value = "null"
+ }
+
+ if field.Name == "status" {
+ value = 1
+ }
+ if field.FormMode == FormModeSwitch {
+ value = 2
+ }
+ if field.FormMode == FormModeInputDynamic {
+ value = "[]"
+ }
+ items = append(items, &StateItem{
+ Name: field.TsName,
+ DefaultValue: value,
+ DataType: dataType,
+ 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",
+ DataType: dataType,
+ Dc: field.Dc + "摘要信息",
+ })
+ }
+ }
+ return
+}
+
+func (l *gCurd) generateWebModelDictOptions(ctx context.Context, in *CurdPreviewInput) error {
+ type DictType struct {
+ Id int64 `json:"id"`
+ Type string `json:"type"`
+ }
+
+ var (
+ dictTypeIds []int64
+ dictTypeList []*DictType
+ builtinDictTypeIds []int64
+ builtinDictTypeList []*DictType
+ )
+
+ for _, field := range in.masterFields {
+ if field.DictType > 0 {
+ dictTypeIds = append(dictTypeIds, field.DictType)
+ }
+
+ if field.DictType < 0 {
+ builtinDictTypeIds = append(builtinDictTypeIds, field.DictType)
+ }
+ }
+
+ dictTypeIds = convert.UniqueSlice(dictTypeIds)
+ builtinDictTypeIds = convert.UniqueSlice(builtinDictTypeIds)
+
+ if len(dictTypeIds) == 0 && len(builtinDictTypeIds) == 0 {
+ return nil
+ }
+
+ if len(dictTypeIds) > 0 {
+ err := dao.SysDictType.Ctx(ctx).
+ Fields(dao.SysDictType.Columns().Id, dao.SysDictType.Columns().Type).
+ WhereIn(dao.SysDictType.Columns().Id, dictTypeIds).
+ Scan(&dictTypeList)
+ if err != nil {
+ return err
+ }
+ }
+
+ if len(builtinDictTypeIds) > 0 {
+ for _, id := range builtinDictTypeIds {
+ typ, err := dict.GetTypeById(ctx, id)
+ if err != nil && !errors.Is(err, dict.NotExistKeyError) {
+ return err
+ }
+ if len(typ) > 0 {
+ row := new(DictType)
+ row.Id = id
+ row.Type = typ
+ builtinDictTypeList = append(builtinDictTypeList, row)
+ }
+ }
+ }
+
+ if len(dictTypeList) == 0 && len(builtinDictTypeList) == 0 {
+ return nil
+ }
+
+ if len(builtinDictTypeList) > 0 {
+ dictTypeList = append(dictTypeList, builtinDictTypeList...)
+ }
+
+ in.options.DictOps.Has = true
+
+ for _, v := range dictTypeList {
+ // 字段映射字典
+ for _, field := range in.masterFields {
+ if field.DictType != 0 && v.Id == field.DictType {
+ in.options.dictMap[field.TsName] = v.Type
+ in.options.DictOps.Schemas = append(in.options.DictOps.Schemas, &OptionsSchemasField{
+ Field: field.TsName,
+ Type: v.Type,
+ })
+ }
+ }
+ in.options.DictOps.Types = append(in.options.DictOps.Types, v.Type)
+ }
+ return nil
+}
+
+func (l *gCurd) generateWebModelRules(ctx context.Context, in *CurdPreviewInput) string {
+ buffer := bytes.NewBuffer(nil)
+ buffer.WriteString("export const rules = {\n")
+ for _, field := range in.masterFields {
+ if !field.IsEdit || (!field.Required && (field.FormRole == "" || field.FormRole == FormRoleNone)) {
+ 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))
+ }
+ }
+ buffer.WriteString("};\n")
+ return buffer.String()
+}
+
+func (l *gCurd) generateWebModelFormSchema(ctx context.Context, in *CurdPreviewInput) string {
+ buffer := bytes.NewBuffer(nil)
+ buffer.WriteString("export const schemas = ref([\n")
+
+ // 主表
+ l.generateWebModelFormSchemaEach(buffer, in.masterFields, in)
+
+ // 关联表
+ if len(in.options.Join) > 0 {
+ for _, v := range in.options.Join {
+ if !isEffectiveJoin(v) {
+ continue
+ }
+ l.generateWebModelFormSchemaEach(buffer, v.Columns, in)
+ }
+ }
+
+ buffer.WriteString("]);\n")
+ return buffer.String()
+}
+
+// generateWebDictOption 生产字典选项
+func (l *gCurd) generateWebDictOption(typ any) string {
+ return fmt.Sprintf(`dict.getOption('%v')`, typ)
+}
+
+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)
+ component string
+ )
+
+ // 这里根据编辑表单组件来进行推断,如果没有则使用默认input,这可能会导致和查询条件所需参数不符的情况
+ switch field.FormMode {
+ case FormModeInput, FormModeInputTextarea, FormModeInputEditor, FormModeInputYaml:
+ component = defaultComponent
+
+ case FormModeInputNumber:
+ component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n placeholder: '请输入%s',\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NInputNumber", field.Dc, field.Dc)
+
+ case FormModeDate:
+ component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n type: '%s',\n clearable: true,\n shortcuts: %s,\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NDatePicker", field.Dc, "date", "defShortcuts()")
+ 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
+ case FormModeRadio:
+ component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n giProps: {\n //span: 24,\n },\n componentProps: {\n options: %v,\n onUpdateChecked: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NRadioGroup", field.Dc, l.generateWebDictOption(in.options.dictMap[field.TsName]))
+
+ case FormModeCheckbox:
+ component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n giProps: {\n span: 1,\n },\n componentProps: {\n placeholder: '请选择%s',\n options: %v,\n onUpdateChecked: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NCheckbox", field.Dc, field.Dc, l.generateWebDictOption(in.options.dictMap[field.TsName]))
+
+ case FormModeSelect:
+ component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n defaultValue: null,\n componentProps: {\n placeholder: '请选择%s',\n options: %v,\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NSelect", field.Dc, field.Dc, l.generateWebDictOption(in.options.dictMap[field.TsName]))
+
+ case FormModeSelectMultiple:
+ component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n defaultValue: null,\n componentProps: {\n multiple: true,\n placeholder: '请选择%s',\n options: %v,\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NSelect", field.Dc, field.Dc, l.generateWebDictOption(in.options.dictMap[field.TsName]))
+
+ default:
+ component = defaultComponent
+ }
+
+ buffer.WriteString(component)
+ }
+}
+
+func (l *gCurd) generateWebModelColumns(ctx context.Context, in *CurdPreviewInput) (string, error) {
+ buffer := bytes.NewBuffer(nil)
+ buffer.WriteString("export const columns = [\n")
+
+ // 主表
+ if err := l.generateWebModelColumnsEach(buffer, in, in.masterFields); err != nil {
+ return "", err
+ }
+
+ // 关联表
+ if len(in.options.Join) > 0 {
+ for _, v := range in.options.Join {
+ if !isEffectiveJoin(v) {
+ continue
+ }
+ if err := l.generateWebModelColumnsEach(buffer, in, v.Columns); err != nil {
+ return "", err
+ }
+ }
+ }
+
+ buffer.WriteString("];\n")
+ return buffer.String(), nil
+}
+
+func (l *gCurd) generateWebModelColumnsEach(buffer *bytes.Buffer, in *CurdPreviewInput, fields []*sysin.GenCodesColumnListModel) (err error) {
+ for _, field := range fields {
+ if !field.IsList {
+ continue
+ }
+ var (
+ 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: State) {\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 align: '%v',\n width: %v,\n render(row: State) {\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
+ case FormModeSelect:
+ 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 align: '%v',\n width: %v,\n render(row: State) {\n return renderOptionTag('%v', row.%v);\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, in.options.dictMap[field.TsName], field.TsName)
+ in.options.Step.ImportModel.UtilsIndex = append(in.options.Step.ImportModel.UtilsIndex, "renderOptionTag")
+
+ 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 align: '%v',\n width: %v,\n render(row: State) {\n if (isNullObject(row.%s) || !Array.isArray(row.%s)) {\n return ``;\n }\n return row.%s.map((tagKey) => {\n return renderOptionTag('%s', tagKey)\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.Step.ImportModel.NaiveUI = append(in.options.Step.ImportModel.NaiveUI, "NTag")
+ in.options.Step.ImportModel.UtilsIs = append(in.options.Step.ImportModel.UtilsIs, "isNullObject")
+
+ case FormModeUploadImage:
+ component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row: State) {\n return renderImage(row.%v);\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, field.TsName)
+ in.options.Step.ImportModel.UtilsIndex = append(in.options.Step.ImportModel.UtilsIndex, "renderImage")
+
+ case FormModeUploadImages:
+ component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row: State) {\n return renderImageGroup(row.%v);\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, field.TsName)
+ in.options.Step.ImportModel.UtilsIndex = append(in.options.Step.ImportModel.UtilsIndex, "renderImageGroup")
+
+ case FormModeUploadFile:
+ component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row: State) {\n return renderFile(row.%v);\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, field.TsName)
+ in.options.Step.ImportModel.UtilsIndex = append(in.options.Step.ImportModel.UtilsIndex, "renderFile")
+
+ case FormModeUploadFiles:
+ component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row: State) {\n return renderFileGroup(row.%v);\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, field.TsName)
+ in.options.Step.ImportModel.UtilsIndex = append(in.options.Step.ImportModel.UtilsIndex, "renderFileGroup")
+
+ case FormModeSwitch:
+ component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row: State) {\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 align: '%v',\n width: %v,\n render(row: State) {\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
+ }
+
+ buffer.WriteString(component)
+ }
+ return
+}
+
+// isStringType 判断字段是否为字符串类型
+func isStringType(tsType, dataType string) bool {
+ // 根据 TypeScript 类型判断
+ if tsType == "string" {
+ return true
+ }
+
+ // 根据数据库数据类型判断
+ stringTypes := []string{
+ // mysql
+ "varchar", "char", "text", "longtext", "mediumtext", "tinytext",
+ "nvarchar", "nchar", "ntext",
+ "string", "enum", "set",
+ // pgsql
+ "bpchar", "date", "time", "timetz", "timestamp", "timestamptz", "interval",
+ "uuid", "bytea", "inet", "cidr", "macaddr", "macaddr8", "bit", "varbit", "json", "jsonb",
+ "point", "line", "lseg", "box", "path", "polygon", "circle", "money",
+ }
+
+ for _, t := range stringTypes {
+ if strings.EqualFold(dataType, t) {
+ return true
+ }
+ }
+ return false
+}
diff --git a/server/internal/library/hggen/views/curd_generate_web_view.go b/server/internal/library/hggen/views/curd_generate_web_view.go
new file mode 100644
index 0000000..dc932fd
--- /dev/null
+++ b/server/internal/library/hggen/views/curd_generate_web_view.go
@@ -0,0 +1,90 @@
+// Package views
+// @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 views
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+
+ "github.com/gogf/gf/v2/frame/g"
+)
+
+func (l *gCurd) webViewTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) {
+ data = make(g.Map)
+ item, hasUpload := l.generateWebViewItem(ctx, in)
+ data["item"] = item
+ data["hasUploadFile"] = hasUpload
+ return
+}
+
+func (l *gCurd) generateWebViewItem(ctx context.Context, in *CurdPreviewInput) (string, bool) {
+ buffer := bytes.NewBuffer(nil)
+ hasUploadFile := false
+
+ for _, field := range in.masterFields {
+ if !field.IsEdit {
+ continue
+ }
+
+ // 检测是否使用了文件上传相关的组件
+ if field.FormMode == FormModeUploadFile || field.FormMode == FormModeUploadFiles {
+ hasUploadFile = true
+ }
+
+ var (
+ defaultComponent = fmt.Sprintf("\n %s \n {{ formValue.%s }}\n ", field.Dc, field.TsName)
+ component string
+ )
+
+ switch field.FormMode {
+ case FormModeInputTextarea, FormModeInputEditor:
+ component = fmt.Sprintf("\n %s \n \n ", field.Dc, field.TsName)
+
+ case FormModeInputYaml:
+ component = fmt.Sprintf("\n %s \n {{ formValue.%s }}\n ", field.Dc, field.TsName)
+
+ case FormModeInputDynamic:
+ component = defaultComponent
+
+ case FormModeDate:
+ component = defaultComponent
+
+ case FormModeTime:
+ component = defaultComponent
+
+ case FormModeRadio, FormModeSelect:
+ component = fmt.Sprintf("\n \n {{ dict.getLabel('%s', formValue.%s) }}\n \n ", field.Dc, in.options.dictMap[field.TsName], field.TsName, in.options.dictMap[field.TsName], field.TsName)
+
+ case FormModeCheckbox, FormModeSelectMultiple:
+ component = fmt.Sprintf("\n \n \n {{ dict.getLabel('%s', item) }}\n \n \n ", field.Dc, field.TsName, in.options.dictMap[field.TsName], in.options.dictMap[field.TsName])
+
+ case FormModeUploadImage:
+ component = fmt.Sprintf("\n %s \n \n ", field.Dc, field.TsName)
+
+ case FormModeUploadImages:
+ component = fmt.Sprintf("\n %s \n \n \n \n \n \n \n \n ", field.Dc, field.TsName)
+
+ case FormModeUploadFile:
+ component = fmt.Sprintf("\n %s \n \n
\n
\n
\n {{ getFileExt(formValue.%s) }}\n
\n
\n
\n
\n ", field.Dc, field.TsName, field.TsName, field.TsName)
+
+ case FormModeUploadFiles:
+ component = fmt.Sprintf("\n %s \n \n
\n \n
\n
\n \n {{ getFileExt(item) }}\n \n
\n
\n
\n \n
\n ", field.Dc, field.TsName)
+
+ case FormModeSwitch:
+ component = fmt.Sprintf("\n \n ", field.Dc, field.TsName)
+
+ case FormModeRate:
+ component = fmt.Sprintf("\n \n ", field.Dc, field.TsName)
+
+ default:
+ component = defaultComponent
+ }
+
+ buffer.WriteString(" " + component + "\n\n")
+ }
+ return buffer.String(), hasUploadFile
+}
diff --git a/server/internal/library/hggen/views/gohtml/consts.go b/server/internal/library/hggen/views/gohtml/consts.go
new file mode 100644
index 0000000..625118c
--- /dev/null
+++ b/server/internal/library/hggen/views/gohtml/consts.go
@@ -0,0 +1,7 @@
+package gohtml
+
+const (
+ defaultIndentString = " "
+ startIndent = 0
+ defaultLastElement = "