Проектирование масштабируемой системы рефералов с использованием Firebase для iOS/Swift приложения

Вопрос или проблема

Я разрабатываю приложение для iOS, используя Swift и Firebase, и ищу способ реализовать надежную реферальную систему. Основные требования к реферальной системе:

Коды рефералов пользователей: У каждого пользователя должен быть уникальный реферальный код, который он может делиться с другими.

Бонусы за рефералы: Когда пользователь использует реферальный код другого пользователя, как текущий пользователь, так и пригласивший пользователь должны получить бонус в кредиты (например, по 5 кредитов каждому).

Награды за рефералы: Пользователь, пригласивший, должен накапливать «награды за рефералы», которые отслеживают количество успешных рефералов, которые они сделали.

Безопасные обновления данных: Реферальная система должна быть разработана в соответствии с лучшими практиками безопасности Firebase Firestore, обеспечивая, что пользователи могут обновлять только свои данные и не обходить правила.

Система уведомлений: Когда реферальный код пользователя используется, пользователь-пригласитель должен получать push-уведомление, информирующее его об успешном реферале и полученном бонусе в кредитах.

Хранение изображений профиля пользователей: Пользователи должны иметь возможность загружать изображения профиля, которые должны храниться в Firebase Storage.

Я уже добился определенного прогресса в реализации реферальной системы, и привел соответствующий код ниже:

func generateAndSaveReferralCode(userId: String) async throws -> String {
    let code = String(UUID().uuidString.prefix(8)).uppercased()
    
    try await db.collection("users").document(userId).setData([
        "referralCode": code,
        "referralRewards": 0,
        "lastUpdated": FieldValue.serverTimestamp()
    ], merge: true)
    
    return code
}

func processReferralCode(_ code: String, currentUserId: String) async throws {
    let currentUserRef = db.collection("users").document(currentUserId)
    
    do {
        // Получаем документ текущего пользователя
        let currentUserDoc = try await currentUserRef.getDocument()
        guard let currentUserData = currentUserDoc.data() else {
            throw ReferralError.invalidCode
        }
        
        // Проверяем, был ли уже использован реферал
        if currentUserData["usedReferralCode"] as? Bool == true {
            throw ReferralError.alreadyUsedReferral
        }
        
        // Проверяем, используется ли собственный код
        if currentUserData["referralCode"] as? String == code {
            throw ReferralError.cannotUseSelfCode
        }
        
        // Получаем информацию о пригласившем
        let referrerQuery = db.collection("users")
            .whereField("referralCode", isEqualTo: code)
            .limit(to: 1)
        
        let referrerDocs = try await referrerQuery.getDocuments()
        guard let referrerDoc = referrerDocs.documents.first else {
            throw ReferralError.invalidCode
        }
        
        let referrerData = referrerDoc.data()
        let referrerId = referrerDoc.documentID
        
        // Получаем текущие значения
        let currentCredits = currentUserData["creditBalance"] as? Int ?? 0
        let currentReferralRewards = currentUserData["referralRewards"] as? Int ?? 0
        
        // Обновляем данные текущего пользователя
        try await currentUserRef.updateData([
            "creditBalance": currentCredits + 5,
            "usedReferralCode": true,
            "referredByCode": code,
            "referralRewards": currentReferralRewards + 1,
            "lastUpdated": FieldValue.serverTimestamp()
        ])
        
        // Обновляем данные реферера
        let referrerRef = db.collection("users").document(referrerId)
        try await referrerRef.updateData([
            "creditBalance": (referrerData["creditBalance"] as? Int ?? 0) + 5,
            "referralRewards": (referrerData["referralRewards"] as? Int ?? 0) + 1,
            "lastUpdated": FieldValue.serverTimestamp()
        ])
        
        // Создаем транзакции по кредитам
        let batch = db.batch()
        let timestamp = FieldValue.serverTimestamp()
        
        // Транзакция текущего пользователя
        let currentUserTransactionRef = db.collection("creditTransactions").document()
        batch.setData([
            "userId": currentUserId,
            "amount": 5,
            "type": "received_referral",
            "timestamp": timestamp,
            "status": "completed"
        ], forDocument: currentUserTransactionRef)
        
        // Транзакция реферера
        let referrerTransactionRef = db.collection("creditTransactions").document()
        batch.setData([
            "userId": referrerId,
            "amount": 5,
            "type": "referral_bonus",
            "timestamp": timestamp,
            "status": "completed"
        ], forDocument: referrerTransactionRef)
        
        try await batch.commit()
        
        // Отправляем уведомление пользователю-пригласителю
        await sendReferralNotification(to: referrerId, forCurrentUser: currentUserData)
    } catch {
        throw error
    }
    
    // Уведомляем управляющего кредитами
    DispatchQueue.main.async {
        NotificationCenter.default.post(name: .creditsDidChange, object: nil)
    }
}

private func sendReferralNotification(to referrerId: String, forCurrentUser currentUserData: [String: Any]) async {
    let referrerRef = db.collection("users").document(referrerId)
    
    do {
        // Получаем данные реферера
        let referrerDoc = try await referrerRef.getDocument()
        guard let referrerData = referrerDoc.data() else {
            return
        }
        
        // Подготавливаем данные для уведомления
        let notificationData: [String: Any] = [
            "referrerName": referrerData["displayName"] ?? "Друг",
            "currentUserName": currentUserData["displayName"] ?? "Вы",
            "creditAmount": 5,
            "referralRewards": (referrerData["referralRewards"] as? Int ?? 0) + 1
        ]
        
        // Отправляем push-уведомление рефереру
        let message = Messaging.messaging().newSendableMessage(withData: notificationData)
        try await Messaging.messaging().send(message)
    } catch {
        print("Ошибка при отправке уведомления о реферале: \(error.localizedDescription)")
    }
}

Я столкнулся с несколькими проблемами при реализации этой реферальной системы:

Правила безопасности Firebase Firestore: Мне трудно найти правильный баланс между разрешением пользователям обновлять свои данные (например, баланс кредитов, реферальный код, изображение профиля), и предотвращением изменения данных других пользователей.

Атомарность обновления кредитов: Мне нужно убедиться, что обновление кредитов как для текущего пользователя, так и для реферера происходит атомарно, чтобы один пользователь не упустил бонус.

Интеграция push-уведомлений: Я не уверен, как лучше интегрировать систему push-уведомлений с реферальным потоком, учитывая также аспекты безопасности.

Интеграция с Firebase Storage: Я хотел бы понять лучшие практики для хранения и извлечения изображений профиля пользователей в Firebase Storage и как защитить доступ к этим ресурсам.

Может кто-то предоставить рекомендации о том, как разработать масштабируемую и безопасную реферальную систему, используя Firebase для приложения iOS/Swift? Я открыт для архитектурных паттернов, подходов к дизайну и конкретных примеров кода, которые могли бы помочь решить возникающие проблемы.
Буду очень признателен за любую помощь!

Ответ или решение

Проектирование Масштабируемой Системы Реферальных Кодовых для Приложения iOS/Swift с Использованием Firebase

Введение

Создание масштабируемой реферальной системы для iOS приложения с использованием Firebase включает в себя ряд ключевых аспектов. Данная система должна поддерживать уникальные реферальные коды, бонусы для пользователей, защиту данных и интеграцию уведомлений. В этом руководстве мы рассмотрим лучшие практики проектирования такой системы, а также ответим на ваши конкретные вопросы.

Архитектура Системы

1. Пользовательские Реферальные Коды

Каждый пользователь вашего приложения должен иметь уникальный реферальный код. Для генерации таких кодов можно использовать функцию, аналогичную следующей:

func generateUniqueReferralCode() -> String {
    return String(UUID().uuidString.prefix(8)).uppercased()
}

Этот код гарантирует уникальность, автоматизируя процесс генерации.

2. Бонусы за Рефералы и Ведение Учетов

Когда пользователь использует реферальный код другого пользователя, необходимо обновить учетные записи обеих сторон. Для этого лучше использовать транзакции, чтобы обеспечить атомарность обновления данных:

let batch = db.batch()
// Обновление данных текущего пользователя
batch.updateData(["creditBalance": newCreditBalanceCurrent], forDocument: currentUserRef)
// Обновление данных реферера
batch.updateData(["creditBalance": newCreditBalanceReferrer], forDocument: referrerRef)
try await batch.commit()

Таким образом, мы гарантируем, что данные обновятся одновременно, предотвращая ситуации, когда один пользователь может потерять свой бонус.

3. Использование Безопасных Правил Firestore

Чтобы обеспечить безопасность данных в Firebase Firestore, используйте правила, которые позволят пользователям изменять только свои данные. Пример правил:

match /users/{userId} {
  allow read, write: if request.auth.uid == userId;
}

Эти правила гарантируют, что пользователи смогут изменять только свою информацию.

4. Интеграция Уведомлений

Интеграция системы пуш-уведомлений может быть выполнена с использованием Firebase Cloud Messaging (FCM). Для отправки уведомлений рефереру, необходимо сделать это после успешного обновления данных пользователя:

private func sendReferralNotification(to referrerId: String) {
    let notification = [
        "to": referrerId,
        "notification": [
            "title": "Новый реферал!",
            "body": "Вы получили бонус за реферал."
        ]
    ]
    // Отправка уведомления через FCM
    ...
}

5. Хранение Изображений Пользователей

Для хранения пользовательских изображений рекомендуется использовать Firebase Storage. Чтобы обеспечить безопасность, необходимо создать правила для доступа к изображениям:

service firebase.storage {
  match /b/{bucket}/o {
    match /profileImages/{imageId} {
      allow read, write: if request.auth.uid != null && request.auth.uid == imageId;
    }
  }
}

Это позволит пользователям загружать и загружать только свои изображения.

Заключение

Проектирование масштабируемой реферальной системы с использованием Firebase для iOS приложения требует учета различных аспектов: от безопасности данных до интеграции уведомлений. Применение предоставленных методов и рекомендаций поможет вам создать надежную и безопасную систему.

Если у вас возникли дальнейшие вопросы или потребность в подробной помощи, не стесняйтесь обращаться за дополнительной информацией. Удачи с вашим проектом!

Оцените материал
Добавить комментарий

Капча загружается...