You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 00:55:16