diff --git a/common.go b/common.go new file mode 100644 index 0000000..d899c3f --- /dev/null +++ b/common.go @@ -0,0 +1,30 @@ +package gormx + +import ( + "time" + + "gorm.io/gorm" +) + +type Common struct { + // gorm.Model + Id int64 `json:"id" gorm:"column:id;auto_increment;primary_key;comment:主键"` + CreateTime int64 `json:"create_time" binding:"-" gorm:"type:bigint unsigned;column:create_time;default:0;not null;autoCreateTime:milli;comment:创建时间"` + UpdateTime int64 `json:"update_time" binding:"-" gorm:"type:bigint unsigned;column:update_time;default:0;not null;autoCreateTime:milli;autoUpdateTime:milli;comment:更新时间"` + // DeletedAt *time.Time `json:"delete_time" binding:"-" gorm:"index;type:bigint unsigned;column:delete_time;default:0;not null;comment:删除时间"` + DeleteTime DeleteTime `json:"delete_time" binding:"-" gorm:"index;type:bigint unsigned;column:delete_time;default:0;not null;comment:删除时间"` + // DeleteTime sql.NullInt64 `json:"delete_time" gorm:"index;type:bigint unsigned;column:delete_time;default:0;not null;comment:删除时间"` +} + +func (u *Common) BeforeCreate(tx *gorm.DB) (err error) { + u.CreateTime = time.Now().UnixMilli() + u.UpdateTime = time.Now().UnixMilli() + return +} + +func (c *Common) BeforeUpdate(tx *gorm.DB) (err error) { + c.UpdateTime = time.Now().UnixMilli() + return +} + +// Unscoped() 会忽略默认的软删除条件 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0e04845 --- /dev/null +++ b/go.mod @@ -0,0 +1,23 @@ +module code.yun.ink/pkg/gormx + +go 1.20 + +require ( + github.com/zeromicro/go-zero v1.6.1 + gorm.io/driver/mysql v1.5.2 + gorm.io/gorm v1.25.5 +) + +require ( + github.com/fatih/color v1.16.0 // indirect + github.com/go-sql-driver/mysql v1.7.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect + go.opentelemetry.io/otel v1.19.0 // indirect + go.opentelemetry.io/otel/trace v1.19.0 // indirect + go.uber.org/automaxprocs v1.5.3 // indirect + golang.org/x/sys v0.15.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..02204ca --- /dev/null +++ b/go.sum @@ -0,0 +1,43 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/zeromicro/go-zero v1.6.1 h1:E8fRkMPiYODk8+jUIrxQQIEG+MTgWfXKiH7sjc9l6Vs= +github.com/zeromicro/go-zero v1.6.1/go.mod h1:slLvzqPP/H/h9ABq9ykNOuX6pYLjA8Uy3Rb8adkXTGw= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= +gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= +gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= diff --git a/softDelete.go b/softDelete.go new file mode 100644 index 0000000..3a8ce72 --- /dev/null +++ b/softDelete.go @@ -0,0 +1,173 @@ +package gormx + +import ( + "database/sql" + "database/sql/driver" + "encoding/json" + "reflect" + "strconv" + + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" +) + +type DeleteTime sql.NullInt64 + +func (n *DeleteTime) Scan(value interface{}) error { + return (*sql.NullInt64)(n).Scan(value) +} + +func (n DeleteTime) Value() (driver.Value, error) { + if !n.Valid { + return 0, nil + } + return n.Int64, nil +} + +func (n DeleteTime) MarshalJSON() ([]byte, error) { + if n.Valid { + return json.Marshal(n.Int64) + } + return json.Marshal(nil) +} + +func (n *DeleteTime) UnmarshalJSON(b []byte) error { + if string(b) == "null" || string(b) == "0" { + n.Valid = false + return nil + } + err := json.Unmarshal(b, &n.Int64) + if err == nil { + n.Valid = true + } + return err +} + +func (DeleteTime) QueryClauses(f *schema.Field) []clause.Interface { + return []clause.Interface{SoftDeleteQueryClause{Field: f, ZeroValue: parseZeroValueTag(f)}} +} + +func parseZeroValueTag(f *schema.Field) sql.NullInt64 { + if v, ok := f.TagSettings["ZEROVALUE"]; ok { + // 字符串转int64 + i, err := strconv.ParseInt(v, 10, 64) + if err == nil { + return sql.NullInt64{Int64: i, Valid: true} + } + } + return sql.NullInt64{Valid: false} +} + +type SoftDeleteQueryClause struct { + ZeroValue sql.NullInt64 + Field *schema.Field +} + +func (s SoftDeleteQueryClause) Name() string { + return "" +} + +func (s SoftDeleteQueryClause) Build(clause.Builder) { +} + +func (s SoftDeleteQueryClause) MergeClause(*clause.Clause) { +} + +func (sd SoftDeleteQueryClause) ModifyStatement(stmt *gorm.Statement) { + if _, ok := stmt.Clauses["soft_delete_enabled"]; !ok && !stmt.Statement.Unscoped { + if c, ok := stmt.Clauses["WHERE"]; ok { + if where, ok := c.Expression.(clause.Where); ok && len(where.Exprs) >= 1 { + for _, expr := range where.Exprs { + if orCond, ok := expr.(clause.OrConditions); ok && len(orCond.Exprs) == 1 { + where.Exprs = []clause.Expression{clause.And(where.Exprs...)} + + c.Expression = where + stmt.Clauses["WHERE"] = c + break + } + } + } + } + // fmt.Println("where:6") + + stmt.AddClause(clause.Where{Exprs: []clause.Expression{ + clause.Eq{Column: clause.Column{Table: clause.CurrentTable, Name: sd.Field.DBName}, Value: sd.ZeroValue.Int64}, + }}) + stmt.Clauses["soft_delete_enabled"] = clause.Clause{} + } +} + +func (DeleteTime) UpdateClauses(f *schema.Field) []clause.Interface { + return []clause.Interface{SoftDeleteUpdateClause{Field: f}} +} + +type SoftDeleteUpdateClause struct { + ZeroValue sql.NullInt64 + Field *schema.Field +} + +func (sd SoftDeleteUpdateClause) Name() string { + return "" +} +func (sd SoftDeleteUpdateClause) Build(clause.Builder) { +} +func (sd SoftDeleteUpdateClause) MergeClause(*clause.Clause) { +} + +func (sd SoftDeleteUpdateClause) ModifyStatement(stmt *gorm.Statement) { + if stmt.SQL.Len() == 0 && !stmt.Statement.Unscoped { + SoftDeleteQueryClause(sd).ModifyStatement(stmt) + } +} +func (DeleteTime) DeleteClauses(f *schema.Field) []clause.Interface { + return []clause.Interface{SoftDeleteDeleteClause{Field: f, ZeroValue: parseZeroValueTag(f)}} +} + +type SoftDeleteDeleteClause struct { + ZeroValue sql.NullInt64 + Field *schema.Field +} + +func (sd SoftDeleteDeleteClause) Name() string { + return "" +} + +func (sd SoftDeleteDeleteClause) Build(clause.Builder) { +} + +func (sd SoftDeleteDeleteClause) MergeClause(*clause.Clause) { +} + +func (sd SoftDeleteDeleteClause) ModifyStatement(stmt *gorm.Statement) { + if stmt.SQL.Len() == 0 && !stmt.Statement.Unscoped { + curTime := stmt.DB.NowFunc().UnixMilli() + // curTime := time.Now().UnixMilli() + stmt.AddClause(clause.Set{{Column: clause.Column{Name: sd.Field.DBName}, Value: curTime}}) + stmt.SetColumn(sd.Field.DBName, curTime, true) + + if stmt.Schema != nil { + _, queryValues := schema.GetIdentityFieldValuesMap(stmt.Context, stmt.ReflectValue, stmt.Schema.PrimaryFields) + column, values := schema.ToQueryValues(stmt.Table, stmt.Schema.PrimaryFieldDBNames, queryValues) + + if len(values) > 0 { + stmt.AddClause(clause.Where{Exprs: []clause.Expression{clause.IN{Column: column, Values: values}}}) + } + + if stmt.ReflectValue.CanAddr() && stmt.Dest != stmt.Model && stmt.Model != nil { + _, queryValues = schema.GetIdentityFieldValuesMap(stmt.Context, reflect.ValueOf(stmt.Model), stmt.Schema.PrimaryFields) + column, values = schema.ToQueryValues(stmt.Table, stmt.Schema.PrimaryFieldDBNames, queryValues) + + if len(values) > 0 { + stmt.AddClause(clause.Where{Exprs: []clause.Expression{clause.IN{Column: column, Values: values}}}) + } + } + } + + // fmt.Println("where:") + + SoftDeleteQueryClause(sd).ModifyStatement(stmt) + stmt.AddClauseIfNotExists(clause.Update{}) + stmt.Build(stmt.DB.Callback().Update().Clauses...) + } +}