package schema import ( "context" "encoding/json" "flag" "git.nspix.com/golang/micro/gateway/http" "git.nspix.com/golang/rest/internal/array" "git.nspix.com/golang/rest/scenario" "gorm.io/gorm" "gorm.io/gorm/clause" "gorm.io/gorm/schema" "reflect" "strconv" "strings" "time" ) var ( timeKind = reflect.TypeOf(time.Time{}).Kind() timePtrKind = reflect.TypeOf(&time.Time{}).Kind() driver Driver ) var ( driverFlag = flag.String("schema-driver", "local", "schema storage driver") remoteWriteUrlFlag = flag.String("schema-remote-write-url", "", "remote server") ) func init() { if *driverFlag == "local" { driver = &LocalDriver{} } else { driver = &RemoteDriver{ Url: *remoteWriteUrlFlag, } } } type ( Rule struct { Min int `json:"min"` Max int `json:"max"` Type string `json:"type"` Unique bool `json:"unique"` Required []string `json:"required"` Regular string `json:"regular"` } enumItem struct { Key interface{} `json:"key"` Val interface{} `json:"val"` } Options struct { Readonly []string `json:"readonly"` Disable []string `json:"disable"` Visible []string `json:"visible"` Items []enumItem `json:"items"` Description string `json:"description"` } Schema struct { Id uint64 `json:"id" gorm:"primary_key"` Module string `json:"module" gorm:"column:module_name;type:varchar(60)"` Table string `json:"table" gorm:"column:table_name;type:varchar(120)"` Enable bool `json:"enable"` Column string `json:"column" gorm:"type:varchar(120)"` Label string `json:"label" gorm:"type:varchar(120)"` Type string `json:"type" gorm:"type:varchar(120)"` Format string `json:"format" gorm:"type:varchar(120)"` Scenario string `json:"scenario" gorm:"type:varchar(120)"` Rules string `json:"rules" gorm:"type:varchar(2048)"` Options string `json:"options" gorm:"type:varchar(4096)"` Position int `json:"position"` options *Options } Field struct { Column string `json:"column" gorm:"type:varchar(120)"` Label string `json:"label" gorm:"type:varchar(120)"` Type string `json:"type" gorm:"type:varchar(120)"` Format string `json:"format" gorm:"type:varchar(120)"` Scenario string `json:"scenario" gorm:"type:varchar(120)"` Rules string `json:"rules" gorm:"type:varchar(2048)"` Options string `json:"options" gorm:"type:varchar(4096)"` } ) func (r *Rule) String() string { buf, _ := json.Marshal(r) return string(buf) } func (sc *Schema) GetOptions() *Options { if sc.options == nil { sc.options = &Options{} _ = json.Unmarshal([]byte(sc.Options), sc.options) } return sc.options } func (sc Schema) TableName() string { return "schemas" } func newStatement(db *gorm.DB) *gorm.Statement { return &gorm.Statement{ DB: db, ConnPool: db.Statement.ConnPool, Context: db.Statement.Context, Clauses: map[string]clause.Clause{}, } } func names(s string) string { var sb strings.Builder ss := strings.Split(s, "_") if len(ss) <= 0 { return "" } sb.WriteString(strings.Title(ss[0])) for _, i := range ss[1:] { sb.WriteString(" ") sb.WriteString(strings.Title(i)) } return sb.String() } func IsNewRecord(value reflect.Value, stmt *gorm.Statement) bool { for _, pf := range stmt.Schema.PrimaryFields { if _, isZero := pf.ValueOf(value); isZero { return true } } return false } func GetDriver() Driver { return driver } func SetDriver(d Driver) { driver = d } func VisibleField(module, table, scenario string) []*Schema { var ( values []*Schema result []*Schema ) values = driver.Columns(module, table) if scenario == "" { return values } result = make([]*Schema, 0) for _, model := range values { if strings.Index(model.Scenario, scenario) > -1 { result = append(result, model) } } return result } func extraSize(str string) int { var ( sb strings.Builder aflag = -1 ) b := make([]byte, 0) b = append(b, str...) for _, s := range b { if s >= '0' && s <= '9' { if aflag == -1 { aflag = 1 } sb.WriteByte(s) } else if aflag == 1 { break } } v, _ := strconv.Atoi(sb.String()) return v } func rules(field *schema.Field) string { r := &Rule{ Required: []string{}, } if field.GORMDataType == "string" { r.Max = extraSize(string(field.DataType)) } if field.PrimaryKey { r.Unique = true } return r.String() } func migrate(module string, db *gorm.DB, val interface{}) (err error) { schemas := make([]*Schema, 0) stmt := newStatement(db) if err = stmt.Parse(val); err != nil { return err } schemas = driver.Columns(module, stmt.Table) var ( i int format string label string fieldName string model *Schema isExists bool visible string dataType string count int enable bool ) for _, field := range stmt.Schema.Fields { fieldName = field.DBName if fieldName == "-" { continue } isExists = false if fieldName == "" { fieldName = field.Name } label = field.Tag.Get("comment") if label == "" { label = names(fieldName) } format = dataFormatOf(field) for _, fv := range schemas { if fv.Column == fieldName { isExists = true break } } if isExists { continue } if bv, _ := json.Marshal([]string{scenario.Search, scenario.List, scenario.Create, scenario.Update, scenario.Export, scenario.View}); bv != nil { visible = string(bv) } if !array.In(field.Name, []string{"ID", "CreatedAt", "UpdatedAt", "DeletedAt"}) && count < 10 { count++ enable = true } else { enable = false } dataType = strings.ToLower(dataTypeOf(field)) model = &Schema{ Module: module, Table: stmt.Table, Column: fieldName, Enable: enable, Label: label, Type: dataType, Format: format, Options: `{"readonly":[],"disable":[],"visible":[],"items":[],"lazy":{"enable":false,"type":"dropdown","url":""}}`, Scenario: visible, Rules: rules(field), Position: i, } i++ _ = driver.SaveColumn(model) } return } func Migrate(module string, db *gorm.DB, values ...interface{}) (err error) { for _, val := range values { if err = db.AutoMigrate(val); err == nil { if err = migrate(module, db, val); err != nil { return } } } return } func PrimaryKeyValue(statement *gorm.Statement) interface{} { var ( field *schema.Field refValue reflect.Value ) if len(statement.Schema.PrimaryFields) > 0 { field = statement.Schema.PrimaryFields[0] refValue = statement.ReflectValue.FieldByName(field.Name) if refValue.IsValid() && !refValue.IsZero() { return refValue.Interface() } } return nil } func Initialize(ctx context.Context, db *gorm.DB, svr *http.Server) (err error) { if db != nil { if err = db.AutoMigrate(&Schema{}); err != nil { return } } if d, ok := driver.(*LocalDriver); ok { d.db = db } if svr != nil && db != nil { routes(db, svr) } return }