如何基于Node.js实现大规模加密货币价格邮件提醒?
加密货币价格邮件提醒程序规模化方案咨询
我正在开发一款加密货币价格邮件提醒程序,用户可选择加密货币、填写邮箱,当货币价格达到其设定值时发送邮件提醒。目前已实现邮件发送功能,且能通过Coingecko API获取价格数据,但不清楚如何存储用户数据,以及每次获取价格后如何向多名订阅用户批量发送提醒。
目前仅支持单个用户,希望扩展为支持50名及以上用户:用户输入邮箱和价格后,后端持续轮询API,当货币价格低于用户设定值时及时发送提醒,请问如何实现支持50名及以上用户的规模化方案?
本人是Node.js新手,感谢各位的建议与帮助。
当前已实现的代码
获取价格数据
async function getPriceData() { try { const response = await axios.get( "https://api.coingecko.com/api/v3/coins/bitcoin", {} ); const data = response.data; const price = data.market_data.current_price.usd; return price; } catch (error) { throw error; } }
邮件发送函数(单用户版本)
当前用setTimeout每60秒轮询API,Nodemailer邮件发送功能正常,客户端数据可正常提交至服务器并用于该函数:
const emailFunction = (price, recieverEmail) => { getPriceData().then((response) => { const btcPrice = response; const selectedPrice = price; if (btcPrice < selectedPrice) { const transport = nodemailer.createTransport({ host: "smtp.sendgrid.net", port: 587, auth: { user: "apikey", pass: sendgridKey, }, }); const message = { from: senderEmail, to: recieverEmail, subject: `BTC PRICE BELOW ${selectedPrice}!`, text: `Bitcoin price is below your selected price of ${selectedPrice}, and is currently ${btcPrice}. This presents a good buying opportunity.`, }; transport.sendMail(message, (err, info) => { if (err) { console.log(err); } else { console.log(info); } }); } setTimeout(emailFunction, 60000); }); };
规模化实现方案
1. 用户数据存储
- 选择轻量数据库:作为Node.js新手,推荐用
sqlite3(文件型数据库,无需额外服务)或MongoDB(文档型,上手简单),两者都有成熟的Node.js驱动。 - 用户数据结构:每条用户记录至少包含:
email:用户邮箱(设为唯一键,避免重复订阅)targetPrice:设定的提醒价格coinType:订阅的加密货币(比如bitcoin、ethereum,方便后续扩展多币种)isNotified:标记是否已发送过提醒(避免重复发送,可选)
- SQLite表结构示例:
CREATE TABLE IF NOT EXISTS subscribers ( id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT UNIQUE NOT NULL, targetPrice REAL NOT NULL, coinType TEXT DEFAULT 'bitcoin', isNotified INTEGER DEFAULT 0 );
2. 批量处理与提醒逻辑优化
- 统一轮询+批量检查:不要为每个用户单独轮询API,改成一次轮询获取价格后,遍历所有用户检查条件,减少API调用次数(避免触发Coingecko的请求限制)。
- 复用Nodemailer传输对象:全局初始化一次
transport,不要每次发送邮件都创建新实例,提升性能。 - 异步批量发送:对符合条件的用户,用
Promise.all批量处理邮件发送,避免阻塞主线程。
3. 轮询方案优化
- 替换
setTimeout为setInterval:setTimeout易出现时间漂移,setInterval固定间隔执行更稳定。 - 添加异常捕获:轮询过程中API请求失败时,需捕获错误并记录,避免整个流程中断。
重构后的代码示例
const axios = require('axios'); const nodemailer = require('nodemailer'); const sqlite3 = require('sqlite3').verbose(); const express = require('express'); // 初始化数据库连接 const db = new sqlite3.Database('./subscribers.db'); // 创建用户表 db.run(`CREATE TABLE IF NOT EXISTS subscribers ( id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT UNIQUE NOT NULL, targetPrice REAL NOT NULL, coinType TEXT DEFAULT 'bitcoin', isNotified INTEGER DEFAULT 0 )`); // 全局初始化Nodemailer传输对象 const transport = nodemailer.createTransport({ host: "smtp.sendgrid.net", port: 587, auth: { user: "apikey", pass: sendgridKey, }, }); // 获取指定币种价格 async function getPriceData(coinType = 'bitcoin') { try { const response = await axios.get( `https://api.coingecko.com/api/v3/coins/${coinType}` ); return response.data.market_data.current_price.usd; } catch (error) { console.error('获取价格失败:', error.message); throw error; } } // 批量检查并发送提醒 async function checkAndSendAlerts() { try { const btcPrice = await getPriceData('bitcoin'); console.log(`当前BTC价格: ${btcPrice} USD`); // 查询未接收过提醒的BTC订阅用户 db.all(`SELECT * FROM subscribers WHERE coinType = 'bitcoin' AND isNotified = 0`, async (err, users) => { if (err) { console.error('查询用户失败:', err); return; } const eligibleUsers = users.filter(user => btcPrice < user.targetPrice); if (eligibleUsers.length === 0) { console.log('无符合条件的用户'); return; } // 批量发送邮件并更新提醒状态 const sendPromises = eligibleUsers.map(user => { const message = { from: senderEmail, to: user.email, subject: `BTC PRICE BELOW ${user.targetPrice}!`, text: `Bitcoin price is below your selected price of ${user.targetPrice}, and is currently ${btcPrice}. This presents a good buying opportunity.`, }; return transport.sendMail(message) .then(() => { db.run(`UPDATE subscribers SET isNotified = 1 WHERE email = ?`, [user.email]); console.log(`已发送提醒至: ${user.email}`); }) .catch(err => console.error(`发送邮件失败至${user.email}:`, err)); }); await Promise.all(sendPromises); }); } catch (error) { console.error('提醒流程出错:', error); } } // 启动轮询,每60秒执行一次 setInterval(checkAndSendAlerts, 60000); // 订阅接口(基于Express) const app = express(); app.use(express.json()); app.post('/subscribe', (req, res) => { const { email, targetPrice, coinType = 'bitcoin' } = req.body; if (!email || !targetPrice) { return res.status(400).send('邮箱和目标价格为必填项'); } // 存在则更新价格并重置提醒状态,不存在则新增 db.run(`INSERT OR REPLACE INTO subscribers (email, targetPrice, coinType, isNotified) VALUES (?, ?, ?, 0)`, [email, targetPrice, coinType], (err) => { if (err) return res.status(500).send('订阅失败'); res.send('订阅成功'); } ); }); app.listen(3000, () => console.log('服务器运行在端口3000'));
额外建议
- 去重与更新:用户重复订阅时,自动更新目标价格并重置提醒状态。
- 失败重试:邮件发送失败的用户,不要直接标记为已提醒,可添加重试机制或记录到失败队列。
- API限流处理:Coingecko免费版有请求次数限制,可适当延长轮询间隔,或缓存5-10分钟内的价格数据。
- 日志记录:添加详细日志,方便排查订阅、价格获取、邮件发送环节的问题。
内容的提问来源于stack exchange,提问作者cros21
相关产品推荐
相关产品推荐

