feat: add Go backend
This commit is contained in:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user