package business import ( "context" "gorm.io/gorm" "gorm.io/gorm/clause" "kra/internal/biz/business" ) type bindingLockPO struct { SourceDB string `gorm:"column:source_db;primaryKey;size:50"` PlatformBusierID uint64 `gorm:"primaryKey;autoIncrement:false"` } func (bindingLockPO) TableName() string { return "sys_business_binding_locks" } type bindingRepo struct{ data Provider } func NewBindingRepo(data Provider) business.BindingRepo { return &bindingRepo{data: data} } type bindingTxKey struct{} func (r *bindingRepo) db(ctx context.Context) *gorm.DB { if tx, ok := ctx.Value(bindingTxKey{}).(*gorm.DB); ok { return tx.WithContext(ctx) } return r.data.DB().WithContext(ctx) } func (r *bindingRepo) WithPlatformLock(ctx context.Context, key business.PlatformKey, fn func(context.Context) error) error { err := r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { // 单独的键锁覆盖“尚无绑定记录”的并发场景,不修改平台数据库。 lock := bindingLockPO{SourceDB: key.SourceDB, PlatformBusierID: key.BusierID} if err := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&lock).Error; err != nil { return business.ErrStorage } if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("source_db = ? AND platform_busier_id = ?", key.SourceDB, key.BusierID).First(&lock).Error; err != nil { return business.ErrStorage } return fn(context.WithValue(ctx, bindingTxKey{}, tx)) }) return storageError(err) } func toBizBinding(po bindingPO) *business.Binding { return &business.Binding{UserID: po.UserID, PlatformKey: business.PlatformKey{SourceDB: po.SourceDB, BusierID: po.PlatformBusierID}, Account: po.PlatformBusierAccount, Status: po.Status, RejectReason: po.RejectReason, CreatedAt: po.CreateTime, ReviewTime: po.ReviewTime, ReviewAccount: po.ReviewAccount} } func newBinding(b *business.Binding) bindingPO { return bindingPO{UserID: b.UserID, SourceDB: b.SourceDB, PlatformBusierID: b.BusierID, PlatformBusierAccount: b.Account, Status: b.Status, RejectReason: b.RejectReason, CreateTime: b.CreatedAt, ReviewTime: b.ReviewTime, ReviewAccount: b.ReviewAccount} } func (r *bindingRepo) ForPlatform(ctx context.Context, key business.PlatformKey) ([]*business.Binding, error) { var pos []bindingPO if err := r.db(ctx).Where("source_db = ? AND platform_busier_id = ?", key.SourceDB, key.BusierID).Find(&pos).Error; err != nil { return nil, business.ErrStorage } rows := make([]*business.Binding, 0, len(pos)) for _, po := range pos { rows = append(rows, toBizBinding(po)) } return rows, nil } func (r *bindingRepo) Save(ctx context.Context, b *business.Binding) error { po := newBinding(b) err := r.db(ctx).Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "user_id"}, {Name: "source_db"}, {Name: "platform_busier_id"}}, DoUpdates: clause.AssignmentColumns([]string{"platform_busier_account", "status", "reject_reason", "create_time", "review_time", "review_account"})}).Create(&po).Error if err != nil { return business.ErrStorage } return nil } func (r *bindingRepo) Delete(ctx context.Context, userID uint, key business.PlatformKey) error { result := r.db(ctx).Where("user_id = ? AND source_db = ? AND platform_busier_id = ?", userID, key.SourceDB, key.BusierID).Delete(&bindingPO{}) if result.Error != nil { return business.ErrStorage } if result.RowsAffected == 0 { return business.ErrBindingNotFound } return nil } func (r *bindingRepo) List(ctx context.Context, scope business.UserScope, q business.BindingFilter, options ...business.ListOption) ([]*business.Binding, int64, error) { db := r.db(ctx).Table("sys_user_bind_platform b").Joins("JOIN sys_users u ON u.id=b.user_id AND u.deleted_at IS NULL") db = applyUserScope(db, scope, "u.") if q.UserID != 0 { db = db.Where("b.user_id = ?", q.UserID) } if len(q.SourceDBs) > 0 { db = db.Where("b.source_db IN ?", q.SourceDBs) } if q.Account != "" { db = db.Where("b.platform_busier_account LIKE ?", "%"+q.Account+"%") } if q.Status != "" { db = db.Where("b.status = ?", q.Status) } var total int64 if err := db.Count(&total).Error; err != nil { return nil, 0, business.ErrStorage } o := business.NewListOptions(options...) var pos []struct { Binding bindingPO `gorm:"embedded"` UserName string } if err := db.Select("b.*,u.username AS user_name").Order("b.create_time DESC").Order("b.source_db,b.platform_busier_id,b.user_id").Offset(o.Offset).Limit(o.Limit).Scan(&pos).Error; err != nil { return nil, 0, business.ErrStorage } rows := make([]*business.Binding, 0, len(pos)) keys := make(map[business.PlatformKey]bool) for _, po := range pos { b := toBizBinding(po.Binding) zero := int64(0) b.PromoteCount = &zero b.UserName = po.UserName rows = append(rows, b) if b.Status == "0" { keys[b.PlatformKey] = true } } if len(keys) == 0 { return rows, total, nil } available, err := businessTableAvailable(r.db(ctx), "tab_promote") if err != nil { return nil, 0, err } if !available { for _, b := range rows { if b.Status == "0" { b.PromoteCount = nil } } return rows, total, nil } query := r.db(ctx).Table("tab_promote").Where("promote_level = ?", 1) pairs := r.db(ctx).Session(&gorm.Session{NewDB: true}) for key := range keys { pairs = pairs.Or("(source_db = ? AND busier_id = ?)", key.SourceDB, key.BusierID) } var counts []struct { SourceDB string `gorm:"column:source_db"` BusierID uint64 Total int64 } if err := query.Where(pairs).Select("source_db,busier_id,COUNT(*) AS total").Group("source_db,busier_id").Scan(&counts).Error; err != nil { return nil, 0, business.ErrStorage } byKey := make(map[business.PlatformKey]int64, len(counts)) for _, c := range counts { byKey[business.PlatformKey{SourceDB: c.SourceDB, BusierID: c.BusierID}] = c.Total } for _, b := range rows { if b.Status == "0" { count := byKey[b.PlatformKey] b.PromoteCount = &count } } return rows, total, nil }