iOS项目中基于Firestore的股价阈值推送及订阅方案问询
Hey there! I’ve built a few stock alert systems with Firebase myself, so let’s walk through your questions step by step to get you unstuck.
1. Firestore Data Structure & Subscription Logic Best Practices
Your current structure (rates/stock#1 with price/name) works for storing stock data, but we need to add a dedicated structure for subscriptions to make alerting efficient. Here are two solid approaches depending on your use case:
Option 1: Subscription Records as Separate Documents (Most Flexible)
Create a top-level subscriptions collection where each document represents a single user’s subscription to a stock. This makes it easy to manage individual user subscriptions and query for all alerts tied to a specific stock.
Example structure:
/stocks/{stockId} - name: "Apple Inc." - price: 175.50 - lastUpdated: Timestamp /subscriptions/{subscriptionId} - userId: "user_123xyz" - stockId: "stock#1" - upperThreshold: 180.00 # Alert when price rises above this - lowerThreshold: 170.00 # Alert when price drops below this
Why this works:
- You can quickly query all subscriptions for a stock using
where("stockId", "==", targetStockId) - Easy to let users add/remove individual subscriptions without affecting others
- Supports multiple thresholds per user-stock pair
Option 2: Subscribers as a Subcollection Under Stocks (Faster for Bulk Alerts)
If you need to quickly access all subscribers for a stock (e.g., high-volume alerts), nest a subscribers subcollection directly under each stock document.
Example structure:
/stocks/{stockId} - name: "Apple Inc." - price: 175.50 - lastUpdated: Timestamp /subscribers/{userId} - upperThreshold: 180.00 - lowerThreshold: 170.00
Why this works:
- No cross-collection queries needed to get subscribers for a stock
- Lower latency when triggering bulk alerts for a single stock
Key Tips:
- Always store user IDs (not email/usernames) in subscriptions to link to user profiles
- Add a single-field index on
stockIdin thesubscriptionscollection (Firebase will prompt you to create this if you forget) - For users, store their FCM push token in a
users/{userId}document with afcmTokenfield—this is critical for sending alerts
2. Implementing Push Alerts with Cloud Functions
We’ll use a Firestore onUpdate trigger to listen for stock price changes, check subscriptions, and send alerts via Firebase Cloud Messaging (FCM).
Step 1: Set Up the Cloud Function
First, make sure you have the Firebase CLI installed and initialized. Then create a function like this:
const functions = require("firebase-functions"); const admin = require("firebase-admin"); admin.initializeApp(); exports.sendStockPriceAlerts = functions.firestore .document("stocks/{stockId}") .onUpdate(async (change, context) => { const stockId = context.params.stockId; const newStockData = change.after.data(); const oldStockData = change.before.data(); const newPrice = newStockData.price; const oldPrice = oldStockData.price; const stockName = newStockData.name; // Skip if price didn't change (prevents unnecessary triggers) if (newPrice === oldPrice) return null; // Fetch all subscriptions for this stock const subscriptionsSnapshot = await admin.firestore() .collection("subscriptions") .where("stockId", "==", stockId) .get(); // Track all push alert promises to wait for completion const alertPromises = []; subscriptionsSnapshot.forEach(async (subDoc) => { const subscription = subDoc.data(); const { userId, upperThreshold, lowerThreshold } = subscription; // Determine if we need to send an alert let alertMessage = ""; if (newPrice >= upperThreshold && oldPrice < upperThreshold) { alertMessage = `${stockName} hit your upper threshold! Current price: $${newPrice.toFixed(2)}`; } else if (newPrice <= lowerThreshold && oldPrice > lowerThreshold) { alertMessage = `${stockName} dropped below your lower threshold! Current price: $${newPrice.toFixed(2)}`; } if (alertMessage) { // Get the user's FCM token from their profile const userDoc = await admin.firestore().collection("users").doc(userId).get(); if (!userDoc.exists) return; const fcmToken = userDoc.data().fcmToken; if (!fcmToken) return; // Build the FCM message const pushMessage = { notification: { title: "Stock Alert!", body: alertMessage, sound: "default" }, token: fcmToken }; // Add the push task to our promise array alertPromises.push(admin.messaging().send(pushMessage)); } }); // Wait for all alerts to send before finishing the function return Promise.all(alertPromises); });
Step 2: Critical Notes for Production
- Error Handling: Add try/catch blocks around database queries and FCM sends to avoid function crashes
- Bulk Sending: If you have 500+ subscribers, use
admin.messaging().sendMulticast()instead of individual sends to stay within FCM limits - Throttling: If stock prices update frequently, add a cooldown period (e.g., 5 minutes) to avoid spamming users with repeated alerts
- Testing: Use Firebase Emulators to test the function locally before deploying to production
内容的提问来源于stack exchange,提问作者Manuel

