【问题标题】:Firebase Like / Dislike systemFirebase 喜欢/不喜欢系统
【发布时间】:2018-05-27 07:13:34
【问题描述】:

我在事件的 firebase 数据库中实现系统。喜欢柜台在这里EventModel -> likesInfo -> likesNumber。问题是数据发散,例如如果两个用户很快(0.5秒)点赞/不喜欢(反之亦然),那么可能是测试完成后,两个用户都停在不喜欢,点赞数为1 (错误)有时差异可能是 4-6 个值。一开始我是用runTransactionBlock实现到应用程序中的,后来我想按活动感兴趣的用户数来统计,为此使用了云功能,但结果比在应用。我看过一些示例,包括 firebase 示例(其中one),但其中一些不适合,如果事件有很多数据,则需要在事件模型中存储大量数据喜欢。其他我用过,但没有阳性结果。我附上下面的代码。请告诉我如何最好地实施这个系统?

应用代码

  private func likeNumber(_ eventID: String, isLike: Bool, success: ((_ isCommited: Bool) -> Void)?, fail: ((_ error: Error) -> Void)?) {
        DispatchQueue.global(qos: .background).async {
            let eventRef = Database.database().reference().child(self.MainPath.events.rawValue).child(eventID).child(self.SubPath.likesInfo.rawValue).child(self.SubPath.likesNumber.rawValue)

            eventRef.runTransactionBlock({ (currentData) -> TransactionResult in
                var likesNumber = currentData.value as? Int ?? 0
                debugPrint("likesNumber", likesNumber)
                if isLike {
                    likesNumber += 1
                } else {
                    likesNumber -= 1
                }

                if likesNumber < 0 {
                    likesNumber = 0
                }

                debugPrint("sumarry likesNumber", likesNumber)

                currentData.value = likesNumber

                return TransactionResult.success(withValue: currentData)
            }, andCompletionBlock: { (error, commited, snap) in
                if let _error = error {
                    debugPrint("_error", _error.localizedDescription)
                    fail?(_error)
                    return
                }

                if commited {
                    debugPrint("commited", commited)
                }
                success?(commited)
                debugPrint("snap", snap?.value ?? "")
            })
        }
    }

云功能

var functions = require('firebase-functions');
var eventManager = require('../Managers/event-manager');

module.exports = functions.database.ref('eventLikedUsers/{eventID}/{userID}').onDelete(event => {
    const eventID = event.params.eventID;

    return eventManager.incrementLikesNumber(eventID, false);
});

module.exports = functions.database.ref('eventLikedUsers/{eventID}/{userID}').onCreate(event => {
    const eventID = event.params.eventID;

    return eventManager.incrementLikesNumber(eventID, true);
});

来自EventManager的代码

exports.incrementLikesNumber = function incrementLikesNumber(eventID, isLike) {
    return new Promise((resolve, reject) => {
        const eventRef = admin.database().ref()
            .child('events')
            .child(eventID)
            .child('likesInfo')
            .child('likesNumber');

        const prom = eventRef.transaction(currentData => {
            if (isLike) {
                return (currentData || 0) + 1;
            } else {
                return (currentData || 1) - 1;
            }
        });

        return prom
            .then(() => {
                return resolve('success operation')
            }).catch(error => {
                return reject(error)
            })
    });
};

【问题讨论】:

  • 我认为也可以通过 id 为一个事件创建一个函数队列,例如,在执行第一个函数之前,不要启动第二个函数,但它如何更好这样做?

标签: ios swift firebase firebase-realtime-database google-cloud-functions


【解决方案1】:

我在EventsLikeManager 中为一个和事件的喜欢/不喜欢功能做了一个队列。例如,如果为 Event id 1 执行了一个功能,并且请求为 Event id 1 执行相同的功能,则第二个请求排队并等待第一个请求被执行。没有重构但完全可以运行的代码通过了测试。

class EventsLikeManager {

    // MARK: - Init

    private init() { }

    static let shared = EventsLikeManager()

    private let MainPath = EventsPaths.MainPath.self
    private let SubPath = EventsPaths.SubPath.self

    private let systemQueue = DispatchQueue(label: "com.myapp.EventsLikeManager.systemQueue")

    // id, time,
    private var dict = [String : [EventSystemLikeDislikeModel]]()

    // MARK: - Like actions

    func likeEvents(_ event: EventModel, success: (() -> Void)?, fail: ((_ error: Error) -> Void)?) {
        guard newSystemEvent(event) else { return }
        _likeEvents(event, success: success, fail: fail)
    }

    private func _likeEvents(_ event: EventModel, success: (() -> Void)?, fail: ((_ error: Error) -> Void)?) {
        FirebasePerformanceManager.shared.likeDislikeAction(true)
        DispatchQueue.global(qos: .background).async {
            if event.systemInfo.sourceIndex == EventSystemModel.Source.firebase.index {
                self.eventLikeInternalSourceAction(event, success: success, fail: fail)
            } else {
                self.eventLikeOutsourceAction(event, success: success, fail: fail)
            }
        }
    }

    private func eventLikeOutsourceAction(_ event: EventModel, success: (() -> Void)?, fail: ((_ error: Error) -> Void)?) {
        DispatchQueue.global(qos: .background).async {
            Auth.auth().currentUser?.getIDTokenForcingRefresh(true, completion: { (token, error) in
                guard token != nil else { return }

                let timestamp = Date().currentTimestamp

                event.systemTimeProperties.creationTimestamp = timestamp
                event.systemTimeProperties.updateTimestamp = timestamp
                event.systemInfo.sourceIndex = EventSystemModel.Source.firebase.index

                self.createEvent(event, success: {
                    self.userLikedEvent(event.id, notExists: {                        self.likeAction(event, timestamp: timestamp, success: success, fail: fail)
                    }, success: { (likeModel) in
                        // dislike
                        self.dislikeAction(event, success: success, fail: fail)
                    }, fail: fail)
                }, fail: { (error) in

                })
            })
        }
    }

    private func eventLikeInternalSourceAction(_ event: EventModel, success: (() -> Void)?, fail: ((_ error: Error) -> Void)?) {
        userLikedEvent(event.id, notExists: {
            self.likeAction(event, success: success, fail: fail)
        }, success: { (likeModel) in
            // dislike
            self.dislikeAction(event, success: success, fail: fail)
        }, fail: fail)
    }

    func createEvent(_ event: EventModel, success: (() -> Void)?, fail: ((_ error: Error) -> Void)?) {
        DispatchQueue.global(qos: .background).async {
            let dispatchGroup = DispatchGroup()
            var commonError: Error?

            let eventRef = Database.database().reference().child(self.MainPath.events.rawValue).child(event.id)

            dispatchGroup.enter()
            eventRef.runTransactionBlock({ (currentData) -> TransactionResult in
                if (currentData.value as? [String : Any]) != nil {
                    debugPrint("TransactionResult.abort()")
                    return TransactionResult.abort()
                } else {
                    dispatchGroup.enter()
                    self.createEventLocation(event, success: {
                        dispatchGroup.leave()
                    }, fail: { (error) in
                        dispatchGroup.leave()
                    })

                    currentData.value = event.toJSON()
                    return TransactionResult.success(withValue: currentData)
                }
            }, andCompletionBlock: { (error, isCommited, snap) in
                commonError = error
                debugPrint("isCommited", isCommited)
                dispatchGroup.leave()
            })

            dispatchGroup.notify(queue: .global(qos: .background), execute: {
                if let error = commonError {
                    fail?(error)
                } else {
                    success?()
                }
            })
        }
    }

    private func createEventLocation(_ event: EventModel, success: (() -> Void)?, fail: ((_ error: Error) -> Void)?) {
        DispatchQueue.global(qos: .background).async {
            let eventLocationRef = Database.database().reference().child(self.MainPath.eventsLocations.rawValue)

            guard let geofire = GeoFire(firebaseRef: eventLocationRef) else { return }
            let coordinate = event.location.coordinate
            let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)

            geofire.setLocation(location, forKey: event.id, withCompletionBlock: { (error) in
                if let _error = error {
                    fail?(_error)
                } else {
                    success?()
                }
            })
        }
    }

}

// MARK: - Likes' functions

extension EventsLikeManager {

    private func likeAction(_ event: EventModel, timestamp: Double? = nil, success: (() -> Void)?, fail: ((_ error: Error) -> Void)?) {
        DispatchQueue.global(qos: .background).async {
            var finalTimestamp = Date().currentTimestamp

            if let _timestamp = timestamp {
                finalTimestamp = _timestamp
            }

            var commonError: Error?
            let dispatchGroup = DispatchGroup()

            dispatchGroup.enter()
            self.likeNumber(event.id, isLike: true, success: { (isCommited) in
                dispatchGroup.leave()
            }, fail: { (error) in
                commonError = error
                dispatchGroup.leave()
            })

            dispatchGroup.enter()
            self.addEventToUserList(event, timestamp: finalTimestamp, success: {
                dispatchGroup.leave()
            }, fail: { (error) in
                commonError = error
                dispatchGroup.leave()
            })

            dispatchGroup.enter()
            self.addUserToEventUsersList(event.id, timestamp: finalTimestamp, success: {
                dispatchGroup.leave()
            }, fail: { (error) in
                commonError = error
                dispatchGroup.leave()
            })

            dispatchGroup.notify(queue: .main, execute: {
                self.checkSystemEventQueue(event)
                if let error = commonError {
                    fail?(error)
                } else {
                    success?()
                }
                FirebasePerformanceManager.shared.likeDislikeAction(false)
            })
        }
    }

    private func likeNumber(_ eventID: String, isLike: Bool, success: ((_ isCommited: Bool) -> Void)?, fail: ((_ error: Error) -> Void)?) {
        DispatchQueue.global(qos: .background).async {
            let eventRef = Database.database().reference().child(self.MainPath.events.rawValue).child(eventID).child(self.SubPath.likesInfo.rawValue).child(self.SubPath.likesNumber.rawValue)

            eventRef.runTransactionBlock({ (currentData) -> TransactionResult in
                var likesNumber = currentData.value as? Int ?? 0
                debugPrint("likesNumber", likesNumber)
                if isLike {
                    likesNumber += 1
                } else {
                    likesNumber -= 1
                }

                if likesNumber < 0 {
                    likesNumber = 0
                }

                debugPrint("sumarry likesNumber", likesNumber)

                currentData.value = likesNumber

                return TransactionResult.success(withValue: currentData)
            }, andCompletionBlock: { (error, commited, snap) in
                if let _error = error {
                    debugPrint("_error", _error.localizedDescription)
                    fail?(_error)
                    return
                }

                if commited {
                    debugPrint("commited", commited)
                }
                success?(commited)
                debugPrint("snap", snap?.value ?? "")
            })
        }
    }

    private func addEventToUserList(_ event: EventModel, timestamp: Double, success: (() -> Void)?, fail: ((_ error: Error) -> Void)?) {
        DispatchQueue.main.async {
            guard let userID = RealmManager().getCurrentUser()?.id else { return }
            DispatchQueue.global(qos: .background).async {
                let ref = Database.database().reference().child(self.MainPath.userLikedEvents.rawValue).child(userID).child(event.id)
                let eventUserLike = EventUserLikeModel(eventID: event.id, systemInfo: event.systemInfo, dateInfo: event.dateProperties, systemTimestamp: timestamp)
                let json = eventUserLike.toJSON()

                ref.setValue(json, withCompletionBlock: { (error, ref) in
                    if let _error = error {
                        fail?(_error)
                    } else {
                        success?()
                    }
                })
            }
        }
    }

    private func addUserToEventUsersList(_ eventID: String, timestamp: Double, success: (() -> Void)?, fail: ((_ error: Error) -> Void)?) {
        DispatchQueue.main.async {
            guard let userID = RealmManager().getCurrentUser()?.id else { return }
            DispatchQueue.global(qos: .background).async {
                let ref = Database.database().reference().child(self.MainPath.eventLikedUsers.rawValue).child(eventID).child(userID)
                let json = ["userID" : userID, "timestamp" : timestamp] as [String : Any]

                ref.setValue(json, withCompletionBlock: { (error, ref) in
                    if let _error = error {
                        fail?(_error)
                    } else {
                        success?()
                    }
                })
            }
        }
    }

}

// MARK: - Dislikes' functions

extension EventsLikeManager {

    private func dislikeAction(_ event: EventModel, success: (() -> Void)?, fail: ((_ error: Error) -> Void)?) {
        DispatchQueue.global(qos: .background).async {
            var commonError: Error?
            let dispatchGroup = DispatchGroup()

            dispatchGroup.enter()
            self.likeNumber(event.id, isLike: false, success: { (isCommited) in
                dispatchGroup.leave()
            }, fail: { (error) in
                commonError = error
                dispatchGroup.leave()
            })

            dispatchGroup.enter()
            self.removeEventFromUserList(event.id, success: {
                dispatchGroup.leave()
            }, fail: { (error) in
                commonError = error
                dispatchGroup.leave()
            })

            dispatchGroup.enter()
            self.removeUserFromEventUsersList(event.id, success: {
                dispatchGroup.leave()
            }, fail: { (error) in
                commonError = error
                dispatchGroup.leave()
            })

            dispatchGroup.notify(queue: .main, execute: {
                self.checkSystemEventQueue(event)
                if let error = commonError {
                    fail?(error)
                } else {
                    success?()
                }
                FirebasePerformanceManager.shared.likeDislikeAction(false)
            })
        }
    }

    private func removeEventFromUserList(_ eventID: String, success: (() -> Void)?, fail: ((_ error: Error) -> Void)?) {
        DispatchQueue.main.async {
            guard let currentUserID = RealmManager().getCurrentUser()?.id else { return }
            DispatchQueue.global(qos: .background).async {
                let ref = Database.database().reference().child(self.MainPath.userLikedEvents.rawValue).child(currentUserID).child(eventID)

                ref.removeValue(completionBlock: { (error, ref) in
                    if let _error = error {
                        fail?(_error)
                    } else {
                        success?()
                    }
                })
            }
        }
    }

    private func removeUserFromEventUsersList(_ eventID: String, success: (() -> Void)?, fail: ((_ error: Error) -> Void)?) {
        DispatchQueue.main.async {
            guard let userID = RealmManager().getCurrentUser()?.id else { return }
            DispatchQueue.global(qos: .background).async {
                let ref = Database.database().reference().child(self.MainPath.eventLikedUsers.rawValue).child(eventID).child(userID)

                ref.removeValue(completionBlock: { (error, ref) in
                    if let _error = error {
                        fail?(_error)
                    } else {
                        success?()
                    }
                })
            }
        }
    }

}

// MARK: - Check events

extension EventsLikeManager {

    private func userLikedEvent(_ eventID: String, notExists: (() -> Void)?, success: ((_ userLike: EventUserLikeModel) -> Void)?, fail: ((_ error: Error) -> Void)?) {
        DispatchQueue.main.async {
            guard let currentUserID = RealmManager().getCurrentUser()?.id else { return }
            DispatchQueue.global(qos: .background).async {
                let userLikedEventRef = Database.database().reference().child(self.MainPath.userLikedEvents.rawValue).child(currentUserID).child(eventID)
                userLikedEventRef.observeSingleEvent(of: .value, with: { (snap) in
                    guard snap.exists() else {
                        notExists?()
                        return
                    }
                    guard let json = snap.value as? [String : Any] else { return }
                    guard let likedEvent = Mapper<EventUserLikeModel>().map(JSON: json) else { return }
                    success?(likedEvent)
                }, withCancel: { (error) in
                    fail?(error)
                })
            }
        }
    }

}

// MARK: - Like Actions Helpers

extension EventsLikeManager {

    private func getEventbriteAdditionalInfo(_ event: EventModel, success: (() -> Void)?, fail: ((_ error: Error) -> Void)?) {
        let eventbriteAPIManager = EventbriteAPIManager()
        eventbriteAPIManager.getAdditionalInfo(event, success: { (updatedEvent) in
            self.eventLikeOutsourceAction(updatedEvent, success: success, fail: fail)
        }, fail: fail)
    }

}

// MARK: - System

extension EventsLikeManager {

    private func newSystemEvent(_ event: EventModel) -> Bool {
        let timestamp = Date().currentTimestamp
        if dict[event.id] == nil {
            dict[event.id] = [EventSystemLikeDislikeModel(timestamp: timestamp)]
        } else if dict[event.id]?.count == 0 {
            dict[event.id] = [EventSystemLikeDislikeModel(timestamp: timestamp)]
        } else if var systemEvents = dict[event.id], systemEvents.count > 0 {
            debugPrint("systemEvents", systemEvents.count)
            let systemEvent = EventSystemLikeDislikeModel(timestamp: timestamp)
            systemEvents.append(systemEvent)
            dict[event.id] = systemEvents
            return false
        }
        return true
    }

    private func checkSystemEventQueue(_ event: EventModel) {
        systemQueue.sync {
            guard var array = dict[event.id] else { return }
            guard array.first != nil else { return }
            array.removeFirst()

            debugPrint("checkSystemEventQueue array.count", array.count)
            if array.count > 0 {
                dict[event.id] = array
            } else {
                dict[event.id] = nil
                return
            }
            _likeEvents(event, success: nil, fail: nil)
        }
    }

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-12-13
    • 1970-01-01
    • 1970-01-01
    • 2013-05-26
    • 1970-01-01
    • 1970-01-01
    • 2017-09-06
    • 2018-06-26
    相关资源
    最近更新 更多