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 } // ResolveMerchantForAdmin 允许平台管理员显式进入任一启用商户的后台管理上下文。 func (s *TenantService) ResolveMerchantForAdmin(merchantRef string) (*model.Merchant, error) { if strings.TrimSpace(merchantRef) == "" { return nil, errors.New("请选择商户") } tx := s.db.Where("status = ?", model.MerchantStatusActive) if id, err := strconv.ParseUint(merchantRef, 10, 64); err == nil { tx = tx.Where("id = ?", uint(id)) } else { tx = tx.Where("code = ?", merchantRef) } var merchant model.Merchant if err := tx.First(&merchant).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("商户不存在或已禁用") } return nil, err } return &merchant, nil } 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.MemberRoleViewer: return true default: return false } } // MerchantRolePermissions 返回保留角色或商户自定义角色的权限集合。 // 负责人始终拥有该商户的全部后台权限,避免被自定义角色配置锁死。 func (s *TenantService) MerchantRolePermissions(merchantID uint, role string) (map[string]struct{}, error) { permissions := builtinMerchantRolePermissions(role) if permissions != nil { return permissions, nil } var merchantRole model.MerchantRole if err := s.db.Where("merchant_id = ? AND code = ? AND status = ?", merchantID, role, 1).First(&merchantRole).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("商户角色不存在或已禁用") } return nil, err } return ParseScopes(merchantRole.Permissions), nil } func builtinMerchantRolePermissions(role string) map[string]struct{} { all := func(values ...string) map[string]struct{} { return ParseScopes(strings.Join(values, ",")) } switch role { case model.MemberRoleOwner: return all(model.PermissionMembersManage, model.PermissionProductsManage, model.PermissionOrdersManage, model.PermissionWalletView, model.PermissionWalletLedger, model.PermissionRechargeManage, model.PermissionAPIManage, model.PermissionCallbacksManage) case model.MemberRoleViewer: return map[string]struct{}{} default: return nil } } 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 }