package base import ( "database/sql" "fmt" "log" "reflect" "unicode" "github.com/samber/do" "github.com/samber/lo" "git.fsdpf.net/go/condition" "git.fsdpf.net/go/contracts" "git.fsdpf.net/go/db" "git.fsdpf.net/go/req" ) // 资源变更事件 const ResChangeEventTopic = "res-change-event-topic" // 资源变更数据 const ResChangeRecordTopic = "res-change-record-topic" type ResChangeEventTopicPayload struct { Type string // insert | delete | update Res Resource // 变更资源 Result sql.Result // 执行结果 } type ResChangeRecordTopicPayload struct { Type string // insert | delete | update User req.User // 操作用户 Res Resource // 变更资源 Result sql.Result // 执行结果 Old []map[string]any // 旧数据 New []map[string]any // 新数据 } // 资源 type Resource struct { container *do.Injector Uuid string `db:"uuid"` PUuid string `db:"pUuid"` Code string `db:"code"` Name string `db:"name"` IsResVirtual bool `db:"isVirtual"` Table string `db:"table"` Namespace string `db:"namespace"` Workspace string `db:"workspace"` Primarykey string `db:"primaryKey"` IsHistoryRecord bool `db:"isHistoryRecord"` HistoryCacheMax int `db:"historyCacheMax"` Fields ResFields `db:"fields"` Roles map[string]any `db:"roles"` UpdatedAt string `db:"updated_at"` CreatedAt string `db:"created_at"` } func (this *Resource) InitContainer(container *do.Injector) { this.container = container } // 资源UUID func (this Resource) GetUuid() string { return this.Uuid } // 资源CODE func (this Resource) GetCode() string { return this.Code } // 资源名 func (this Resource) GetName() string { return this.Name } // 主键 func (this Resource) GetPrimarykey() string { return this.Primarykey } // 是否虚拟资源 func (this Resource) IsVirtual() bool { return this.IsResVirtual } // 是否系统资源 func (this Resource) IsSystem() bool { return this.Namespace == "Framework\\Service" } // 资源字段 func (this Resource) GetFields() (result []req.ResField) { for _, item := range this.Fields { result = append(result, item) } return result } // 资源字段 func (this Resource) GetField(code string) (req.ResField, bool) { return lo.Find(this.GetFields(), func(v req.ResField) bool { return v.GetCode() == code }) } // 判断资源字段 func (this Resource) HasField(code string) bool { return lo.SomeBy(this.GetFields(), func(v req.ResField) bool { return v.GetCode() == code }) } // 开启事物 func (this Resource) BeginTransaction() (*db.Transaction, error) { return this.GetDBConn().BeginTransaction() } // 获取资源链接 func (this Resource) GetDBConn() *db.Connection { db := do.MustInvoke[db.DB](this.container) if this.IsSystem() { return db.Connection("service-support") } return db.Connection("default") } // 获取资源对应的数据库连接 func (this Resource) GetDBBuilder() *db.Builder { return this.GetDBConn().Query() } // 获取资源对应的数据表 func (this Resource) GetTable() db.Expression { if this.IsVirtual() { return db.Raw("(" + this.Table + ")") } return db.Raw(this.Table) } func (this Resource) GetDBDriver() string { return this.GetDBConn().GetConfig().Driver } func (this Resource) GetAuthDBTable(u req.User, params ...any) *db.Builder { builder := this.GetDBTable(append(params, u)...) // 数据权限过滤 builder.Before(func(b *db.Builder, t string, data ...map[string]any) { if t == db.TYPE_SELECT || t == db.TYPE_UPDATE || t == db.TYPE_DELETE { this.WithRolesCondition(b, t, u) } }) return builder } // GetDBTable("Test", contracts.User) func (this Resource) GetDBTable(params ...any) *db.Builder { builder := this.GetDBBuilder() var user req.User alias := this.Code for _, param := range params { switch v := param.(type) { case *db.Transaction: builder.Tx = v case string: alias = v case req.User: user = v } } // 格式化数据库存储数据 builder.Before(func(b *db.Builder, t string, data ...map[string]any) { if t == db.TYPE_UPDATE { // 格式化保存数据 this.formatSaveValue(data[0]) } if t == db.TYPE_INSERT { // 移除 table alias b.Table(string(this.GetTable())) for i := 0; i < len(data); i++ { // 格式化保存数据 this.formatSaveValue(data[i]) // 填充保存数据 this.fillSaveValue(data[i], user, db.TYPE_INSERT) } } }) // 资源事件 this.onResEvent(builder) // 用户事件 if this.IsHistoryRecord { this.onUserEvent(builder, user) } // 虚拟资源暂时不考虑鉴权 if !this.IsVirtual() { // 返回鉴权后的 DB Builder // return } return builder.Table(string(this.GetTable()), alias) } func (this Resource) WithRolesCondition(b *db.Builder, t string, u req.User) error { isFullRight := false isFullNot := false NewOrm := do.MustInvoke[contracts.NewOrm](this.container) NewOrmModel := do.MustInvoke[contracts.NewOrmModel](this.container) NewOrmJoin := do.MustInvoke[contracts.NewOrmJoin](this.container) GetResRelationResource := do.MustInvoke[GetResRelationResource](this.container) GetResRelations := do.MustInvoke[GetResRelations](this.container) GetResource := do.MustInvoke[contracts.GetResource](this.container) GetOrmConditions := do.MustInvoke[contracts.GetOrmConditions](this.container) items := do.MustInvoke[GetResRoles](this.container)(this.GetUuid(), u.Roles()...) subTables := lo.Reduce(items, func(carry string, item ResRole, _ int) string { db := this.GetDBBuilder().Table(string(this.GetTable()), this.GetCode()).Select(db.Raw("distinct `" + this.GetCode() + "`.*")) joins := lo.Filter(GetResRelations(item.Uuid), func(item ResRelation, _ int) bool { return item.Type == "inner" || item.Type == "left" || item.Type == "right" }) for i := 0; i < len(joins); i++ { oResource, ok := GetResource(joins[i].ResourceCode) if !ok { continue } rResource, ok := GetResRelationResource(joins[i]) if !ok { continue } join := NewOrmJoin(contracts.RelationType(joins[i].Type), oResource, joins[i].Code, joins[i].RelationResource, joins[i].RelationField, joins[i].RelationForeignKey) // 关联扩展条件 join.SetCondition(GetOrmConditions(joins[i].Uuid, condition.Describe("关联扩展条件"))) join.Inject(db, NewOrmModel(rResource, rResource.GetCode(), rResource.GetName())) } conditions := GetOrmConditions(item.Uuid, condition.Describe("关联扩展条件")) if len(joins) == 0 && conditions.IsEmpty() { // 无权限, 直接跳过这个 unoin 语句 if carry != "" { return carry } // 第一个无权限除外, 避免所有用户所属角色都是无权限 db.WhereRaw("false") isFullNot = true } else if len(joins) == 0 && conditions.IsNotEmpty() && conditions.IsAlwaysRight() /* 1=1 的这种条件*/ { // 只要有1个满权限, 直接返回单条语句 isFullRight = true return db.ToSql() } else if conditions.IsNotEmpty() { oOrm := NewOrm(this, nil) oOrm.SetGlobalParams(req.NewGlobalParam("{}", u)) db.Where(conditions.ToSql(oOrm.GetModel())) // 如果前面是无权限的sql查看, 这直接返回本次查询 if isFullNot { isFullNot = false return db.ToSql() } } if carry != "" { carry += " UNION " } return fmt.Sprintf("%s(%s)", carry, db.ToSql()) }, "") if isFullRight { return nil } if isFullNot { b.WhereRaw("false") } else if subTables != "" { if t == db.TYPE_SELECT { b.FromSub(subTables, b.TableAlias) } else { b.WhereRaw(fmt.Sprintf( "`%s`.`id` in (SELECT `temp`.`id` FROM (%s) as `temp`)", lo.Ternary(b.TableAlias != "", b.TableAlias, this.GetCode()), subTables, )) } } return nil } // 格式化保存数据 func (this Resource) formatSaveValue(data map[string]any) { // for k, v := range data { if k == "id" || k == "created_user" || k == "created_at" || k == "deleted_at" || k == "updated_at" { delete(data, k) } else if val, ok := v.(db.Expression); ok { data[k] = val } else if field, ok := this.GetField(k); ok { data[k] = field.ToValue(v) } } } // 填充保存数据 func (this Resource) fillSaveValue(data map[string]any, u req.User, t string) { for _, field := range this.GetFields() { fCode := field.GetCode() if fCode == "id" || fCode == "created_user" || fCode == "created_at" || fCode == "deleted_at" || fCode == "updated_at" || fCode == "owned_user" { continue } // 只有新增默认字段 if _, ok := data[fCode]; !ok { data[fCode] = field.GetRawDefault(this.GetDBDriver()) } } // 拥有者 if _, ok := data["owned_user"]; !ok { data["owned_user"] = u.Uuid() } // 创建者 data["created_user"] = u.Uuid() if this.GetDBDriver() == "sqlite" { // 更新时间 // sqlite 不能自动更新时间, "DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" data["updated_at"] = db.Raw("CURRENT_TIMESTAMP") } else { } } func (this Resource) GetStruct(extends ...reflect.StructField) any { fields := []reflect.StructField{} for _, field := range this.Fields { if unicode.IsLetter(rune(field.Code[0])) { fields = append(fields, field.ToStructField()) } else { log.Printf("资源字段错误, 必须以字母开头 <- %s", field.Code) } } fields = lo.UniqBy(append(fields, extends...), func(v reflect.StructField) string { return v.Name }) t := reflect.StructOf(fields) return reflect.New(t).Interface() } func (this Resource) GetSliceStruct(extends ...reflect.StructField) any { t := reflect.TypeOf(this.GetStruct(extends...)) st := reflect.SliceOf(t.Elem()) return reflect.New(st).Interface() } // 资源事件 func (this Resource) onResEvent(builder *db.Builder) { builder.After(func(b *db.Builder, t string, result sql.Result, err error, data ...map[string]any) { if err != nil || t == db.TYPE_SELECT { return } else if num, err := result.RowsAffected(); num == 0 || err != nil { return } // 全局触发器 // 1. 清除系统缓存 if err := do.MustInvoke[contracts.Queue](this.container).Publish(ResChangeEventTopic, ResChangeEventTopicPayload{ Type: t, Res: this, Result: result, }); err != nil { log.Println("Queue Publish Err:", ResChangeEventTopic, err) } }) } // 用户事件 func (this Resource) onUserEvent(builder *db.Builder, user req.User) { old := []map[string]any{} builder.Before(func(b *db.Builder, t string, data ...map[string]any) { if t != db.TYPE_UPDATE && t != db.TYPE_DELETE { return } // 查询保存之前的数据 if _, err := b.Get(&old); err != nil { panic(err) } }) builder.After(func(b *db.Builder, t string, result sql.Result, err error, data ...map[string]any) { if err != nil || t == db.TYPE_SELECT { return } else if num, err := result.RowsAffected(); num == 0 || err != nil { return } if user == nil { user = GetAnonymous() } // 触发消息队列 if err := do.MustInvoke[contracts.Queue](this.container).Publish(ResChangeRecordTopic, ResChangeRecordTopicPayload{ Type: t, User: user, Res: this, Old: old, New: data, Result: result, }); err != nil { log.Println("Queue Publish Err:", ResChangeRecordTopic, err) } }) } func NewVirtualResource(pRes Resource, code, name, sql string, fields []ResField) req.Resource { return &Resource{ Uuid: code, PUuid: pRes.Uuid, Code: code, Name: name, IsResVirtual: true, Table: sql, Namespace: pRes.Namespace, Workspace: pRes.Workspace, IsHistoryRecord: false, HistoryCacheMax: 0, Fields: fields, Roles: nil, } }