Browse Source

add dynamic struct

fancl 2 years ago
parent
commit
0de8918fdb
5 changed files with 231 additions and 15 deletions
  1. 26 1
      cmd/main.go
  2. 18 7
      crud.go
  3. 158 0
      dynamic.go
  4. 10 0
      instance.go
  5. 19 7
      rest.go

+ 26 - 1
cmd/main.go

@@ -1,16 +1,41 @@
 package main
 
 import (
+	"context"
 	"fmt"
 	"git.nspix.com/golang/rest/v3"
 	"gorm.io/driver/mysql"
+	"log"
 )
 
 func main() {
-	if err := rest.Init(mysql.New(mysql.Config{
+	var (
+		err error
+	)
+	if err = rest.Init(mysql.New(mysql.Config{
 		DriverName: "mysql",
 		DSN:        "root:root@tcp(192.168.9.199:3306)/cci?checkConnLiveness=false&maxAllowedPacket=0",
 	})); err != nil {
 		fmt.Println(err.Error())
 	}
+
+	dynamicModel := rest.NewDynamic([]*rest.Schema{
+		{0, 0, 0, "", "test", "aaa", 1, "id", "ID", rest.TypeInteger, rest.FormatInteger, 1, 1, "", rest.Scenarios{}, rest.Rule{}, rest.Attribute{}, 0},
+		{0, 0, 0, "", "test", "aaa", 1, "name", "Name", rest.TypeString, rest.FormatString, 1, 0, "", rest.Scenarios{}, rest.Rule{Max: 24}, rest.Attribute{}, 0},
+	})
+	if err = rest.AttachModel(context.Background(), dynamicModel); err != nil {
+		log.Println(err)
+		return
+	}
+	dynamicModel2 := rest.NewDynamic([]*rest.Schema{
+		{0, 0, 0, "", "test", "aab", 1, "id", "ID", rest.TypeInteger, rest.FormatInteger, 1, 1, "", rest.Scenarios{}, rest.Rule{}, rest.Attribute{}, 0},
+		{0, 0, 0, "", "test", "aab", 1, "age", "Age", rest.TypeInteger, rest.FormatInteger, 1, 0, "", rest.Scenarios{}, rest.Rule{}, rest.Attribute{}, 0},
+		{0, 0, 0, "", "test", "aab", 1, "gender", "Gender", rest.TypeString, rest.FormatString, 1, 0, "", rest.Scenarios{}, rest.Rule{Max: 20}, rest.Attribute{}, 0},
+		{0, 0, 0, "", "test", "aab", 1, "score", "Score", rest.TypeInteger, rest.TypeInteger, 1, 0, "", rest.Scenarios{}, rest.Rule{}, rest.Attribute{}, 0},
+		{0, 0, 0, "", "test", "aab", 1, "enable", "Enable", rest.TypeBoolean, rest.FormatBoolean, 1, 0, "", rest.Scenarios{}, rest.Rule{}, rest.Attribute{}, 0},
+	})
+	if err = rest.AttachModel(context.Background(), dynamicModel2); err != nil {
+		log.Println(err)
+		return
+	}
 }

+ 18 - 7
crud.go

@@ -95,7 +95,9 @@ func (r *CRUD) RegisterDelegate(d Delegate) {
 //Attach 注册一个模型
 func (r *CRUD) Attach(ctx context.Context, model Model, cbs ...Option) (err error) {
 	var (
-		opts *Options
+		ok           bool
+		opts         *Options
+		dynamicModel *Dynamic
 	)
 	opts = newOptions()
 	for _, cb := range cbs {
@@ -104,14 +106,22 @@ func (r *CRUD) Attach(ctx context.Context, model Model, cbs ...Option) (err erro
 	if opts.DB == nil {
 		opts.DB = r.db
 	}
-	if err = r.db.AutoMigrate(model); err != nil {
-		return
-	}
-	//不启用schema
-	if opts.Schema {
-		if err = r.migrateSchema(ctx, DefaultNamespace, model); err != nil {
+	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
@@ -134,6 +144,7 @@ func (r *CRUD) migrateSchema(ctx context.Context, namespace string, model Model)
 		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 {

+ 158 - 0
dynamic.go

@@ -0,0 +1,158 @@
+package rest
+
+import (
+	"git.nspix.com/golang/rest/v3/inflector"
+	"gorm.io/gorm"
+	"reflect"
+	"strconv"
+	"strings"
+)
+
+type (
+	//Dynamic 实现动态结构体的操作
+	Dynamic struct {
+		moduleName string
+		tableName  string
+		schemas    []*Schema
+		fields     []reflect.StructField
+	}
+
+	Dialector struct {
+		gorm.Dialector
+		tableName string
+	}
+
+	Migrator struct {
+		gorm.Migrator
+		tableName string
+	}
+)
+
+func (d *Dialector) Migrator(db *gorm.DB) gorm.Migrator {
+	migrator := d.Dialector.Migrator(db)
+	if db.Statement != nil {
+		db.Statement.Table = d.tableName
+	}
+	return newDynamicMigrator(d.tableName, migrator)
+}
+
+func (m *Migrator) HasTable(dst interface{}) bool {
+	return m.Migrator.HasTable(m.tableName)
+}
+
+func newDynamicDialector(tableName string, dialector gorm.Dialector) *Dialector {
+	return &Dialector{dialector, tableName}
+}
+
+func newDynamicMigrator(tableName string, migrator gorm.Migrator) *Migrator {
+	mig := &Migrator{migrator, tableName}
+	return mig
+}
+
+//Interface 返回结构体的实体结构
+func (d *Dynamic) Interface() interface{} {
+	refValue := reflect.New(d.Type())
+	return refValue.Interface()
+}
+
+//Type 返回结构体类型
+func (d *Dynamic) Type() reflect.Type {
+	reflectType := reflect.StructOf(d.fields)
+	return reflectType
+}
+
+//Slice 返回结构体slice类型
+func (d *Dynamic) Slice() reflect.Type {
+	return reflect.SliceOf(d.Type())
+}
+
+//Reset 重置表结构
+func (d *Dynamic) Reset(schemas []*Schema) {
+	d.fields = make([]reflect.StructField, 0, 10)
+	d.schemas = schemas
+	d.buildField(schemas)
+}
+
+//ModuleName 模块名称
+func (d *Dynamic) ModuleName() string {
+	return d.moduleName
+}
+
+//TableName 表名称
+func (d *Dynamic) TableName() string {
+	return d.tableName
+}
+
+//typeOfSchema 通过schema生成结构体的类型
+func (d *Dynamic) typeOfSchema(schema *Schema) reflect.Type {
+	switch schema.Type {
+	case TypeBoolean:
+		return reflect.TypeOf(uint8(0))
+	case TypeInteger:
+		return reflect.TypeOf(int64(0))
+	case TypeFloat:
+		return reflect.TypeOf(float64(0))
+	default:
+		return reflect.TypeOf("")
+	}
+}
+
+//tagOfSchema 通过schema生成结构体的tag
+func (d *Dynamic) tagOfSchema(schema *Schema) reflect.StructTag {
+	var sb strings.Builder
+	var tags = make(map[string][]string)
+	tags["json"] = []string{schema.Column}
+	tags["yaml"] = []string{schema.Column}
+	gormPairs := make([]string, 0, 5)
+	if schema.IsPrimaryKey == 1 {
+		gormPairs = append(gormPairs, "primaryKey")
+	}
+	switch schema.Type {
+	case TypeInteger, TypeFloat:
+		gormPairs = append(gormPairs, "not null")
+		if schema.IsPrimaryKey == 0 {
+			gormPairs = append(gormPairs, "default:0")
+		}
+	case TypeBoolean:
+		gormPairs = append(gormPairs, "not null")
+	case TypeString:
+		if schema.Rule.Max > 0 {
+			gormPairs = append(gormPairs, "size:"+strconv.Itoa(schema.Rule.Max*2))
+		} else {
+			gormPairs = append(gormPairs, "size:1024")
+		}
+		gormPairs = append(gormPairs, "not null")
+		gormPairs = append(gormPairs, "default:''")
+	}
+	if schema.Rule.Unique {
+		gormPairs = append(gormPairs, "unique")
+	}
+	gormPairs = append(gormPairs, "comment:"+schema.Label)
+	tags["gorm"] = gormPairs
+	for tag, pairs := range tags {
+		sb.WriteString(tag + ":\"")
+		sb.WriteString(strings.Join(pairs, ";"))
+		sb.WriteString("\" ")
+	}
+	return reflect.StructTag(sb.String())
+}
+
+//buildField 通过schema构建字段
+func (d *Dynamic) buildField(schemas []*Schema) {
+	for _, schema := range schemas {
+		d.moduleName = schema.ModuleName
+		d.tableName = schema.TableName
+		field := reflect.StructField{
+			Name: inflector.Camelize(schema.Column),
+			Type: d.typeOfSchema(schema),
+			Tag:  d.tagOfSchema(schema),
+		}
+		d.fields = append(d.fields, field)
+	}
+}
+
+func NewDynamic(schemas []*Schema) *Dynamic {
+	model := &Dynamic{}
+	model.Reset(schemas)
+	return model
+}

+ 10 - 0
instance.go

@@ -31,6 +31,16 @@ func DB() (db *gorm.DB, err error) {
 	return std.db, nil
 }
 
+func MustDB() (db *gorm.DB) {
+	var (
+		err error
+	)
+	if db, err = DB(); err != nil {
+		panic(err)
+	}
+	return db
+}
+
 //WithDelegate 添加一个观察者
 func WithDelegate(ctx context.Context, d Delegate) (err error) {
 	if std == nil {

+ 19 - 7
rest.go

@@ -732,15 +732,22 @@ func (r *Restful) actionView(httpCtx *http.Context) (err error) {
 
 func newRestful(model Model, opts *Options) *Restful {
 	var (
-		err       error
-		tableName string
+		ok           bool
+		err          error
+		tableName    string
+		dynamicModel *Dynamic
 	)
 	r := &Restful{
-		opts:         opts,
-		model:        model,
-		reflectValue: reflect.Indirect(reflect.ValueOf(model)),
+		opts:  opts,
+		model: model,
+	}
+	if dynamicModel, ok = model.(*Dynamic); ok {
+		r.reflectValue = reflect.New(dynamicModel.Type()).Elem()
+		r.reflectType = dynamicModel.Type()
+	} else {
+		r.reflectValue = reflect.Indirect(reflect.ValueOf(model))
+		r.reflectType = r.reflectValue.Type()
 	}
-	r.reflectType = r.reflectValue.Type()
 	tableName = model.TableName()
 	r.singularName = inflector.Singularize(tableName)
 	r.pluralizeName = inflector.Pluralize(tableName)
@@ -750,7 +757,12 @@ func newRestful(model Model, opts *Options) *Restful {
 		ConnPool: r.opts.DB.ConnPool,
 		Clauses:  map[string]clause.Clause{},
 	}
-	if err = r.statement.Parse(model); err == nil {
+	if dynamicModel, ok = model.(*Dynamic); ok {
+		err = r.statement.Parse(dynamicModel.Interface())
+	} else {
+		err = r.statement.Parse(model)
+	}
+	if err == nil {
 		if r.statement.Schema != nil {
 			if r.statement.Schema.PrimaryFieldDBNames != nil && len(r.statement.Schema.PrimaryFieldDBNames) > 0 {
 				r.primaryKey = r.statement.Schema.PrimaryFieldDBNames[0]