package crud import ( "context" "encoding/csv" "encoding/json" "fmt" "git.nspix.com/golang/micro/gateway/http" "git.nspix.com/golang/rest/orm/query" "git.nspix.com/golang/rest/orm/schema" "git.nspix.com/golang/rest/orm/validator" "git.nspix.com/golang/rest/scenario" "gorm.io/gorm" "gorm.io/gorm/clause" "reflect" "strconv" "strings" "sync/atomic" "time" ) var seq int64 const ( ScenarioCreate string = "create" ScenarioUpdate = "update" ScenarioDelete = "delete" ScenarioExport = "export" ScenarioList = "list" ScenarioView = "view" ) type Entity struct { Module string db *gorm.DB enable *int32 formatter *Formatter refType reflect.Type refValue reflect.Value stmt *gorm.Statement ctx context.Context prefix string naming struct { label string plural string singular string } seq int64 Value interface{} Scenarios []string Urls map[string]string Callbacks Callbacks instance *CRUD } type ( Entities []*Entity ) func (e Entities) Len() int { return len(e) } func (e Entities) Less(i, j int) bool { return e[i].seq < e[j].seq } func (e Entities) Swap(i, j int) { e[i], e[j] = e[j], e[i] } func (e *Entity) primaryKey() string { if e.stmt.Schema != nil { if len(e.stmt.Schema.PrimaryFieldDBNames) > 0 { return e.stmt.Schema.PrimaryFieldDBNames[0] } } return "id" } func (e *Entity) hasScenarios(name string) bool { for _, s := range e.Scenarios { if name == s { return true } } return false } func (e *Entity) buildConditions(ctx *http.Context, search *query.Query, fields []*schema.Schema, val interface{}, cb EachQueryCallback) { var ( err error ) for _, field := range fields { if cb != nil { if err = cb(val, field, search, ctx); err != nil { continue } } if fcb := e.instance.getCallbackByFormat(field.Format); fcb != nil { if err = fcb(val, field, search, ctx); err != nil { continue } } switch field.Format { case "text", "textarea", "string": search.AndFilterWhere(query.NewConditionWithOperator("like", field.Column, ctx.FormValue(field.Column))) case "datetime", "date", "time": ss := make([]string, 0) formValue := ctx.FormValue(field.Column) if formValue != "" { if err := json.Unmarshal([]byte(ctx.FormValue(field.Column)), &ss); err == nil { if len(ss) == 2 { search.AndFilterWhere( query.NewConditionWithOperator(">=", field.Column, ss[0]), query.NewConditionWithOperator("<=", field.Column, ss[1]), ) } else if len(ss) == 1 { search.AndFilterWhere( query.NewConditionWithOperator("=", field.Column, ss[0]), ) } } else { var sep string seps := []byte{',', '/'} for _, s := range seps { if strings.IndexByte(formValue, s) > -1 { sep = string(s) } } if ss := strings.Split(formValue, sep); len(ss) == 2 { search.AndFilterWhere( query.NewConditionWithOperator(">=", field.Column, strings.TrimSpace(ss[0])), query.NewConditionWithOperator("<=", field.Column, strings.TrimSpace(ss[1])), ) } } } case "duration", "number", "integer", "decimal": ss := make([]string, 0) formValue := ctx.FormValue(field.Column) if formValue != "" { if json.Valid([]byte(field.Column)) { if err := json.Unmarshal([]byte(ctx.FormValue(field.Column)), &ss); err == nil { if len(ss) == 2 { switch ss[0] { case ">", ">=", "<", "<=", "=": search.AndWhere(query.NewConditionWithOperator(ss[0], field.Column, ss[1])) default: search.AndWhere(query.NewConditionWithOperator("=", field.Column, ss[1])) } } else if len(ss) == 1 { search.AndWhere( query.NewConditionWithOperator("=", field.Column, ss[0]), ) } } } else { search.AndWhere(query.NewConditionWithOperator("=", field.Column, formValue)) } } default: if field.Type == "string" { search.AndFilterWhere(query.NewConditionWithOperator("like", field.Column, ctx.FormValue(field.Column))) } else { search.AndFilterWhere(query.NewConditionWithOperator("=", field.Column, ctx.FormValue(field.Column))) } } } } //构建排序规则 func (e *Entity) buildSortable(ctx *http.Context, search *query.Query) { sortPar := ctx.FormValue("sort") if sortPar != "" { sorts := strings.Split(sortPar, ",") for _, s := range sorts { if s[0] == '-' { search.OrderBy(s[1:], "DESC") } else { if s[0] == '+' { search.OrderBy(s[1:], "ASC") } else { search.OrderBy(s, "ASC") } } } } } func (e *Entity) actionIndex(c *http.Context) (err error) { page, _ := strconv.Atoi(c.FormValue("page")) pagesize, _ := strconv.Atoi(c.FormValue("pagesize")) if pagesize <= 0 { pagesize = 15 } pv := page if pv > 0 { pv-- } sliceValue := reflect.MakeSlice(reflect.SliceOf(e.refType), 0, 0) models := reflect.New(sliceValue.Type()) models.Elem().Set(sliceValue) search := query.New(e.db) if e.Callbacks.BeforeQuery != nil { if err = e.Callbacks.BeforeQuery(e.Value, search, c); err != nil { return c.Error(8004, err.Error()) } } searchSchemas := schema.VisibleField(e.Module, e.stmt.Table, scenario.Search) listSchemas := schema.VisibleField(e.Module, e.stmt.Table, scenario.List) e.buildConditions(c, search, searchSchemas, e.Value, e.Callbacks.EachQuery) e.buildSortable(c, search) search.Offset(pv * pagesize).Limit(pagesize) //添加排序支持 if err = search.All(models.Interface()); err != nil { return c.Error(8004, err.Error()) } return c.Success(map[string]interface{}{ "page": page, "pageSize": pagesize, "totalCount": search.Limit(0).Offset(0).Count(e.Value), "data": e.formatter.formatSchemas(models.Interface(), listSchemas, e.stmt), }) } func (e *Entity) actionCreate(c *http.Context) (err error) { val := reflect.New(e.refType).Interface() if err = c.Bind(val); err != nil { return c.Error(8002, err.Error()) } if e.Callbacks.BeforeInsert != nil { if err = e.Callbacks.BeforeInsert(val, c); err != nil { return c.Error(8004, err.Error()) } } if e.Callbacks.BeforeSave != nil { if err = e.Callbacks.BeforeSave(val, c); err != nil { return c.Error(8004, err.Error()) } } sess := e.db.Create(val) if err = sess.Error; err != nil { if err, ok := sess.Error.(*validator.StructError); ok { return c.Error(1001, err.Error()) } else { return c.Error(8004, sess.Error.Error()) } } if e.Callbacks.AfterInsert != nil { _ = e.Callbacks.AfterInsert(val, c) } if e.Callbacks.AfterSave != nil { _ = e.Callbacks.AfterSave(val, c) } return c.Success(map[string]interface{}{ "id": schema.PrimaryKeyValue(sess.Statement), "state": "success", }) } func (e *Entity) actionUpdate(c *http.Context) (err error) { idStr := c.ParamValue("id") val := reflect.New(e.refType).Interface() if err = e.db.Where(e.primaryKey()+"=?", idStr).First(val).Error; err != nil { return c.Error(8004, err.Error()) } if err = c.Bind(val); err != nil { return c.Error(8002, err.Error()) } if e.Callbacks.BeforeUpdate != nil { if err = e.Callbacks.BeforeUpdate(val, c); err != nil { return c.Error(8004, err.Error()) } } if e.Callbacks.BeforeSave != nil { if err = e.Callbacks.BeforeSave(val, c); err != nil { return c.Error(8004, err.Error()) } } sess := e.db.Model(val).Updates(val) if sess.Error != nil { if err, ok := sess.Error.(*validator.StructError); ok { return c.Error(1001, err.Error()) } else { return c.Error(8004, sess.Error.Error()) } } if e.Callbacks.AfterUpdate != nil { _ = e.Callbacks.AfterUpdate(val, c) } if e.Callbacks.AfterSave != nil { _ = e.Callbacks.AfterSave(val, c) } return c.Success(map[string]interface{}{ "id": idStr, "state": "success", }) } func (e *Entity) actionDelete(c *http.Context) (err error) { idStr := c.ParamValue("id") val := reflect.New(e.refType).Interface() if err = e.db.Where(e.primaryKey()+"=?", idStr).First(val).Error; err != nil { return c.Error(8004, err.Error()) } if e.Callbacks.BeforeDelete != nil { if err = e.Callbacks.BeforeDelete(val, c); err != nil { return c.Error(8004, err.Error()) } } if err = e.db.Where(e.primaryKey()+"=?", c.ParamValue("id")).Model(val).Delete(val).Error; err == nil { if e.Callbacks.AfterDelete != nil { _ = e.Callbacks.AfterDelete(val, c) } return c.Success(map[string]interface{}{ "state": "success", "id": idStr, }) } else { return c.Error(8009, err.Error()) } } func (e *Entity) actionView(c *http.Context) (err error) { val := reflect.New(e.refType).Interface() if err = e.db.Where(e.primaryKey()+"=?", c.ParamValue("id")).First(val).Error; err != nil { return c.Error(8004, err.Error()) } if c.FormValue("format") == "true" { listSchemas := schema.VisibleField(e.Module, e.stmt.Table, scenario.View) return c.Success(e.formatter.formatSchema(val, listSchemas, e.stmt)) } else { return c.Success(val) } } func (e *Entity) actionExport(c *http.Context) (err error) { sliceValue := reflect.MakeSlice(reflect.SliceOf(e.refType), 0, 0) models := reflect.New(sliceValue.Type()) models.Elem().Set(sliceValue) search := query.New(e.db) if e.Callbacks.BeforeQuery != nil { if err = e.Callbacks.BeforeQuery(e.Value, search, c); err != nil { return c.Error(8004, err.Error()) } } searchSchemas := schema.VisibleField(e.Module, e.stmt.Table, scenario.Search) e.buildConditions(c, search, searchSchemas, e.Value, e.Callbacks.EachQuery) //添加排序支持 e.buildSortable(c, search) if err = search.All(models.Interface()); err != nil { return c.Error(8004, err.Error()) } c.Response().Header().Set("Content-Type", "text/csv") c.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment;filename=%s.csv", e.naming.plural+"-"+time.Now().Format("20060102"))) exportSchemas := schema.VisibleField(e.Module, e.stmt.Table, scenario.List) value := e.formatter.formatSchemas(models.Interface(), exportSchemas, e.stmt) writer := csv.NewWriter(c.Response()) ss := make([]string, len(exportSchemas)) for i, field := range exportSchemas { ss[i] = field.Label } _ = writer.Write(ss) if values, ok := value.([]interface{}); ok { for _, val := range values { row, ok2 := val.(map[string]interface{}) if !ok2 { continue } for i, field := range exportSchemas { if v, ok := row[field.Column]; ok { ss[i] = fmt.Sprint(v) } else { ss[i] = "" } } _ = writer.Write(ss) } } writer.Flush() return } func (e *Entity) SetModule(module string) { e.Module = module } func newEntity(ctx context.Context, value interface{}, db *gorm.DB) *Entity { if ctx == nil { ctx = context.Background() } e := &Entity{ db: db, ctx: ctx, Value: value, Urls: make(map[string]string), seq: atomic.AddInt64(&seq, 1), } e.stmt = &gorm.Statement{ DB: db, ConnPool: db.ConnPool, Context: ctx, Clauses: map[string]clause.Clause{}, } if err := e.stmt.Parse(value); err != nil { panic(err.Error()) } e.refValue = reflect.Indirect(reflect.ValueOf(value)) e.refType = e.refValue.Type() return e }