package service import ( "errors" "strconv" "strings" "affiliate_dash/internal/model" "gorm.io/gorm" "gorm.io/gorm/clause" ) // TenantService 负责商户成员关系与请求租户解析。 type TenantService struct { db *gorm.DB } func NewTenantService(db *gorm.DB) *TenantService { return &TenantService{db: db} } func (s *TenantService) ResolveMember(userID uint, merchantRef string) (*model.MerchantMember, error) { if userID == 0 { return nil, errors.New("无效的用户身份") } tx := s.db.Preload("Merchant"). Where("merchant_members.user_id = ? AND merchant_members.status = ?", userID, 1). Joins("JOIN merchants ON merchants.id = merchant_members.merchant_id AND merchants.status = ?", model.MerchantStatusActive) if merchantRef != "" { if id, err := strconv.ParseUint(merchantRef, 10, 64); err == nil { tx = tx.Where("merchant_members.merchant_id = ?", uint(id)) } else { tx = tx.Where("merchants.code = ?", merchantRef) } } var member model.MerchantMember err := tx.Order("merchant_members.is_default DESC, merchant_members.id ASC").First(&member).Error if err == nil { return &member, nil } if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("当前账号无权访问该商户") } return nil, err } func (s *TenantService) EnsureSelfMember(userID uint, role string, status int) error { var merchant model.Merchant if err := s.db.Where("code = ?", model.MerchantCodeSelfOperated).First(&merchant).Error; err != nil { return err } return s.EnsureMember(merchant.ID, userID, role, status, role == model.MemberRoleOwner) } func (s *TenantService) EnsureMember(merchantID, userID uint, role string, status int, isDefault bool) error { if merchantID == 0 || userID == 0 { return errors.New("商户和用户不能为空") } if !isValidMemberRole(role) { return errors.New("无效的商户成员角色") } member := model.MerchantMember{ MerchantID: merchantID, UserID: userID, Role: role, Status: status, IsDefault: isDefault, } return s.db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "merchant_id"}, {Name: "user_id"}}, DoUpdates: clause.Assignments(map[string]interface{}{ "role": role, "status": status, "is_default": isDefault, }), }).Create(&member).Error } func isValidMemberRole(role string) bool { switch role { case model.MemberRoleOwner, model.MemberRoleOperator, model.MemberRoleFinance, model.MemberRoleViewer: return true default: return false } } func MemberCanManage(memberRole string) bool { return memberRole == model.MemberRoleOwner || memberRole == model.MemberRoleOperator } func MemberCanManageFinance(memberRole string) bool { return memberRole == model.MemberRoleOwner || memberRole == model.MemberRoleFinance } func ParseScopes(scopes string) map[string]struct{} { out := make(map[string]struct{}) for _, scope := range strings.FieldsFunc(scopes, func(r rune) bool { return r == ',' || r == ' ' || r == '\n' || r == '\t' }) { if scope != "" { out[scope] = struct{}{} } } return out } func HasAnyScope(scopes string, wanted ...string) bool { set := ParseScopes(scopes) if _, ok := set["*"]; ok { return true } for _, scope := range wanted { if _, ok := set[scope]; ok { return true } } return false } func NormalizeMerchantFeatures(features string) string { set := ParseScopes(features) if len(set) == 0 { set = ParseScopes(model.DefaultMerchantFeatures) } valid := map[string]struct{}{ model.MerchantFeatureProducts: {}, model.MerchantFeatureOrders: {}, model.MerchantFeatureWallet: {}, model.MerchantFeatureAPI: {}, model.MerchantFeatureCallbacks: {}, } ordered := []string{ model.MerchantFeatureProducts, model.MerchantFeatureOrders, model.MerchantFeatureWallet, model.MerchantFeatureAPI, model.MerchantFeatureCallbacks, } out := make([]string, 0, len(ordered)) for _, feature := range ordered { if _, ok := set[feature]; ok { if _, allowed := valid[feature]; allowed { out = append(out, feature) } } } if len(out) == 0 { return model.DefaultMerchantFeatures } return strings.Join(out, ",") } func MerchantHasFeature(features string, wanted ...string) bool { set := ParseScopes(features) for _, feature := range wanted { if _, ok := set[feature]; ok { return true } } return false }