package rest import ( "context" "fmt" "git.nspix.com/golang/rest/v3/cache" loggerpkg "git.nspix.com/golang/rest/v3/logger" "gorm.io/gorm" "gorm.io/gorm/clause" "gorm.io/gorm/logger" "strings" "time" ) const ( DefaultNamespace = "default" NamespaceVariable = "namespace" UserVariable = "@uid" DepartmentVariable = "@department" NamespaceField = "namespace" CreatedByField = "CreatedBy" CreatedDeptField = "CreatedDept" UpdatedByField = "UpdatedBy" UpdatedDeptField = "UpdatedDept" ) const ( TypeModule = "module" TypeTable = "table" ) type ( CRUD struct { api *Api db *gorm.DB modules []*Restful delegate *delegate enableCache bool } treeValue struct { Label string `json:"label"` Value string `json:"value"` Type string `json:"type"` Children []*treeValue `json:"children,omitempty"` } ) func (t *treeValue) Append(v *treeValue) { if t.Children == nil { t.Children = make([]*treeValue, 0) } t.Children = append(t.Children, v) } //createStatement 创建一个声明 func (r *CRUD) createStatement(db *gorm.DB) *gorm.Statement { return &gorm.Statement{ DB: db, ConnPool: db.Statement.ConnPool, Context: db.Statement.Context, Clauses: map[string]clause.Clause{}, } } func (r *CRUD) DB() *gorm.DB { return r.db } //DisableCache 禁用缓存 func (r *CRUD) DisableCache() *CRUD { r.enableCache = false return r } //WithCache 启用缓存 func (r *CRUD) WithCache() *CRUD { r.enableCache = true return r } //WithDebug 开启调试 func (r *CRUD) WithDebug() *CRUD { r.db.Logger = r.db.Logger.LogMode(logger.Info) return r } //RegisterDelegate 注册一个旁观者 func (r *CRUD) RegisterDelegate(d Delegate) { r.delegate.Register(d) } //Attach 注册一个模型 func (r *CRUD) Attach(ctx context.Context, model Model, cbs ...Option) (err error) { var ( ok bool opts *Options dynamicModel *Dynamic ) opts = newOptions() for _, cb := range cbs { cb(opts) } if opts.DB == nil { opts.DB = r.db } if dynamicModel, ok = model.(*Dynamic); ok { sess := r.db.Session(&gorm.Session{NewDB: true}) sess.Dialector = newDynamicDialector(model.TableName(), r.db.Dialector) if err = sess.AutoMigrate(dynamicModel.Interface()); err != nil { return } } else { if err = r.db.AutoMigrate(model); err != nil { return } //不启用schema if opts.Schema { if err = r.migrateSchema(ctx, DefaultNamespace, model); err != nil { return } } } opts.Delegate = r.delegate opts.LookupFunc = r.VisibleSchemas rt := newRestful(model, opts) r.modules = append(r.modules, rt) if r.api != nil { r.api.Attach(rt) } return } //migrateSchema 合并表的结构数据 func (r *CRUD) migrateSchema(ctx context.Context, namespace string, model Model) (err error) { var ( pos int columnLabel string columnName string columnIsExists bool stmt *gorm.Statement schemas []*Schema schemaModels []*Schema ) schemas, err = r.GetSchemas(ctx, namespace, model.ModuleName(), model.TableName()) stmt = r.createStatement(r.db) if err = stmt.Parse(model); err != nil { return } if len(schemas) > 0 { pos = len(schemas) } for index, field := range stmt.Schema.Fields { columnName = field.DBName if columnName == "-" { continue } if columnName == "" { columnName = field.Name } columnIsExists = false for _, sm := range schemas { if sm.Column == columnName { columnIsExists = true break } } if columnIsExists { continue } columnLabel = field.Tag.Get("comment") if columnLabel == "" { columnLabel = generateFieldName(field.DBName) } isPrimaryKey := uint8(0) if field.PrimaryKey { isPrimaryKey = 1 } schemaModel := &Schema{ Namespace: namespace, ModuleName: model.ModuleName(), TableName: model.TableName(), Enable: 1, Column: columnName, Label: columnLabel, Type: strings.ToLower(dataTypeOf(field)), Format: strings.ToLower(dataFormatOf(field)), Native: 1, IsPrimaryKey: isPrimaryKey, Scenarios: generateFieldScenario(index, field), Rule: generateFieldRule(field), Attribute: generateFieldAttribute(field), Position: pos, } schemaModels = append(schemaModels, schemaModel) pos++ } if len(schemaModels) > 0 { err = r.db.Create(schemaModels).Error } return } //GetSchemas 获取表的结构数据 func (r *CRUD) GetSchemas(ctx context.Context, namespace, moduleName, tableName string) (schemas []*Schema, err error) { cacheKey := fmt.Sprintf("schema:%s:%s:%s", namespace, moduleName, tableName) if r.enableCache { if v, ok := cache.Get(cacheKey); ok { return v.([]*Schema), nil } } schemas = make([]*Schema, 0) if len(namespace) == 0 { namespace = DefaultNamespace } sess := r.db.Session(&gorm.Session{NewDB: true, Context: ctx}) if err = sess.Where("`namespace`=? AND `module_name`=? AND `table_name`=?", namespace, moduleName, tableName).Order("position,id ASC").Find(&schemas).Error; err == nil { if r.enableCache { cache.SetEx(cacheKey, schemas, time.Minute*30) } } return } //VisibleSchemas 获取指定场景表的数据 func (r *CRUD) VisibleSchemas(ctx context.Context, ns, moduleName, tableName, scene string) (result []*Schema) { var ( err error schemas []*Schema ) if schemas, err = r.GetSchemas(ctx, ns, moduleName, tableName); err == nil { result = make([]*Schema, 0, len(schemas)) for _, s := range schemas { if s.Scenarios.Has(scene) || s.Attribute.PrimaryKey { result = append(result, s) } } } return } //New create new restful func New(dialer gorm.Dialector) (r *CRUD, err error) { r = &CRUD{ delegate: &delegate{}, enableCache: true, } if r.db, err = gorm.Open(dialer); err != nil { return } r.api = &Api{crud: r} r.db.Logger = loggerpkg.New() err = r.db.AutoMigrate(&Schema{}) return }