package schema import ( "context" "encoding/json" "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" "sync" "time" ) var ( timeKind = reflect.TypeOf(time.Time{}).Kind() timePtrKind = reflect.TypeOf(&time.Time{}).Kind() cached map[string][]*Schema cacheLocker sync.RWMutex ) func init() { cached = make(map[string][]*Schema) } 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:"field" 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:"visible" 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 InvalidCache(module, table string) { cacheLocker.Lock() delete(cached, table+"@"+module) cacheLocker.Unlock() } func VisibleField(module, table, scenario string, db *gorm.DB) []*Schema { var ( ok bool err error sess *gorm.DB values []*Schema result []*Schema ) cacheLocker.RLock() values, ok = cached[table+"@"+module] cacheLocker.RUnlock() if ok { goto __end } values = make([]*Schema, 0) sess = db.Scopes(func(db *gorm.DB) *gorm.DB { s := db.Session(&gorm.Session{}) s.Statement = newStatement(s) return s }) if err = sess.Where("module_name=? AND table_name=?", module, table).Order("position,id ASC").Find(&values).Error; err != nil { return values } cacheLocker.Lock() cached[table+"@"+module] = values cacheLocker.Unlock() __end: 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 flag = -1 ) b := make([]byte, 0) b = append(b, str...) for _, s := range b { if s >= '0' && s <= '9' { if flag == -1 { flag = 1 } sb.WriteByte(s) } else if flag == 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) { fields := make([]*Schema, 0) stmt := newStatement(db) if err = stmt.Parse(val); err != nil { return err } db.Model(&Schema{}).Find(&fields, "table_name=?", stmt.Table) err = db.Transaction(func(tx *gorm.DB) error { 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 fields { 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++ if err2 := tx.Save(model).Error; err2 != nil { return err2 } } return nil }) 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 svr != nil && db != nil { routes(db, svr) } return }