package rest import ( "context" "fmt" "git.nspix.com/golang/micro/gateway/http" "gorm.io/gorm" "gorm.io/gorm/clause" "strings" ) 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 { db *gorm.DB modules []*Restful delegate *delegate } 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 create new statement 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) RegisterDelegate(d Delegate) { r.delegate.Register(d) } //Attach 注册一个表 func (r *CRUD) Attach(ctx context.Context, model Model, cbs ...Option) (err error) { var ( opts *Options ) opts = newOptions() for _, cb := range cbs { cb(opts) } if opts.DB == nil { opts.DB = r.db } if err = r.db.AutoMigrate(model); err != nil { return } if err = r.migrateSchema(ctx, DefaultNamespace, model); err != nil { return } opts.Delegate = r.delegate opts.LookupFunc = r.VisibleSchemas r.modules = append(r.modules, newRestful(model, opts)) 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) { schemas = make([]*Schema, 0) if len(namespace) == 0 { namespace = DefaultNamespace } sess := r.db.Session(&gorm.Session{NewDB: true, Context: ctx}) err = sess.Where("`namespace`=? AND `module_name`=? AND `table_name`=?", namespace, moduleName, tableName).Order("position,id ASC").Find(&schemas).Error 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 } func (r *CRUD) handleListSchema(httpCtx *http.Context) (err error) { var ( schemas []*Schema ) ns := httpCtx.FormValue(NamespaceVariable) if ns == "" { ns = DefaultNamespace } if schemas, err = r.GetSchemas(httpCtx.Request().Context(), ns, httpCtx.ParamValue("module"), httpCtx.ParamValue("table")); err == nil { return httpCtx.Success(schemas) } else { return httpCtx.Error(HttpDatabaseQueryFailed, err.Error()) } } func (r *CRUD) handleSaveSchema(httpCtx *http.Context) (err error) { var ( rest *Restful ) ns := httpCtx.FormValue(NamespaceVariable) if ns == "" { ns = DefaultNamespace } moduleName := httpCtx.ParamValue("module") tableName := httpCtx.ParamValue("table") for _, row := range r.modules { if row.model.ModuleName() == moduleName && row.model.TableName() == tableName { rest = row break } } if rest == nil { return httpCtx.Error(HTTPUnknownFailed, fmt.Sprintf("module %s table %s schema not found", moduleName, tableName)) } schemas := make([]*Schema, 0) if err = httpCtx.Bind(&schemas); err != nil { return httpCtx.Error(HttpInvalidPayload, err.Error()) } if err = r.db.Transaction(func(tx *gorm.DB) error { var ( errTx error ) for _, scm := range schemas { if errTx = tx.Save(scm).Error; errTx != nil { return errTx } } return nil }); err == nil { return httpCtx.Success(map[string]interface{}{ "count": len(schemas), "state": "success", }) } else { return httpCtx.Error(HTTPUnknownFailed, err.Error()) } } func (r *CRUD) handleListSchemas(httpCtx *http.Context) (err error) { var ( moduleLabel string ) ts := make([]*treeValue, 0) isHandled := false for _, e := range r.modules { isHandled = false for _, tv := range ts { if tv.Value == e.model.ModuleName() { tv.Append(&treeValue{Value: e.model.TableName(), Label: e.model.TableName(), Type: TypeTable}) isHandled = true break } } if isHandled { continue } moduleLabel = e.model.ModuleName() tv := &treeValue{Label: moduleLabel, Value: e.model.ModuleName(), Type: TypeModule} tv.Append(&treeValue{Value: e.model.TableName(), Label: e.model.TableName(), Type: TypeTable}) ts = append(ts, tv) } return httpCtx.Success(ts) } func (r *CRUD) handleDeleteSchema(httpCtx *http.Context) (err error) { id := httpCtx.ParamValue("id") model := &Schema{} if err = r.db.Where("id=?", id).First(model).Error; err == nil { if err = r.db.Where("id=?", id).Delete(&Schema{}).Error; err == nil { return httpCtx.Success(map[string]string{ "id": id, "state": "success", }) } else { return httpCtx.Error(HttpDatabaseDeleteFailed, err.Error()) } } else { return httpCtx.Error(HttpDatabaseFindFailed, err.Error()) } } func (r *CRUD) Router(svr *http.Server, ms ...http.Middleware) { svr.Handle("GET", "/rest/schemas", r.handleListSchemas, ms...) svr.Handle("GET", "/rest/schema/:module/:table", r.handleListSchema, ms...) svr.Handle("PUT", "/rest/schema/:module/:table", r.handleSaveSchema, ms...) svr.Handle("DELETE", "/rest/schema/:id", r.handleDeleteSchema, ms...) for _, rest := range r.modules { if rest.hasScenario(ScenarioList) { svr.Handle("GET", rest.getScenarioUrl(ScenarioList), rest.getScenarioHandle(ScenarioList), ms...) } if rest.hasScenario(ScenarioCreate) { svr.Handle("POST", rest.getScenarioUrl(ScenarioCreate), rest.getScenarioHandle(ScenarioCreate), ms...) } if rest.hasScenario(ScenarioUpdate) { svr.Handle("PUT", rest.getScenarioUrl(ScenarioUpdate), rest.getScenarioHandle(ScenarioUpdate), ms...) } if rest.hasScenario(ScenarioDelete) { svr.Handle("DELETE", rest.getScenarioUrl(ScenarioDelete), rest.getScenarioHandle(ScenarioDelete), ms...) } if rest.hasScenario(ScenarioExport) { svr.Handle("GET", rest.getScenarioUrl(ScenarioExport), rest.getScenarioHandle(ScenarioExport), ms...) } if rest.hasScenario(ScenarioView) { svr.Handle("GET", rest.getScenarioUrl(ScenarioView), rest.getScenarioHandle(ScenarioView), ms...) } } } //New create new restful func New(dialer gorm.Dialector) (r *CRUD, err error) { r = &CRUD{ delegate: &delegate{}, } if r.db, err = gorm.Open(dialer); err != nil { return } r.db = r.db.Debug() err = r.db.AutoMigrate(&Schema{}) return }