打造自己的Crypto价格提醒服务

我最终构建了这个简单的加密货币价格提醒微服务,如果指定的币的价格超过或低于阈值价格,它将发送电子邮件通知。我们不必整天盯着市场查看价格。

打造自己的Crypto价格提醒服务
一键发币: SUI | SOL | BNB | ETH | BASE | ARB | OP | POLYGON | AVAX | FTM | OK

在这个世界上,每个人都很忙。我们不擅长记住事情(尤其是我)。受到上述两个陈述的启发,我最终构建了这个简单的加密货币价格提醒微服务,如果指定的币的价格超过或低于阈值价格,它将发送电子邮件通知。我们不必整天盯着市场查看价格。你所需要做的就是选择货币并指定阈值价格,仅此而已。你会通过提供的电子邮件地址收到通知。

我使用NodeJS进行实现。你可以按照步骤用其他编程语言构建相同的服务。本教程只是为了演示,它不是生产就绪或稳定的(因为我将使用内存存储警报以简化示例)。然而,我相信你会发现这个演示非常有趣,我们将实现队列处理通知、发送电子邮件、服务工作程序(Cron任务)等。你可以通过添加更多功能来改进它。

话虽如此,让我们先快速了解一下本项目的几个关键特性:

  • 设置价格提醒——针对高于或低于阈值价格的价格。
  • 获取所有活动的警报
  • 自动过期的警报。
  • 电子邮件通知。

在开始之前,你需要以下内容:

  • NodeJS:安装适用于你的操作系统的LTS版本
  • Postman:一个工具,用于实时测试API。在这个演示中,我们将使用此软件创建和检索警报。
  • Redis服务器本地安装和配置 或者如果你愿意,可以选择云服务提供商。我们需要Redis服务器来处理Bull MQ。
  • SendGrid API密钥:使用此链接创建新帐户并获取API密钥。需要此密钥来发送电子邮件通知。

设置好环境后,我们可以深入到实现部分。

1、初始化项目

请遵循以下步骤初始化一个空的NodeJS项目。

创建一个空目录:

mkdir price-alert

导航到该目录:

cd price-alert

初始化一个新的项目——npm init并回答问题。或者你可以使用npm init -y来使用默认配置。

2、安装依赖

我们的项目中将使用以下依赖项:

1.Express:处理来自客户端的HTTP请求。有助于构建API。

2.Axios:帮助我们向外部资源发起HTTP请求的库。

3.Bull:基于Redis的健壮消息队列。

4.Cron:用于作业调度。

5.Nodemailer:用于发送电子邮件通知。

现在让我们使用以下命令安装所有依赖项:

npm install express axios bull cron nodemailer --save
注意:你也可以根据喜好安装开发依赖项如nodemailer。

3、项目目录结构

4、开始吧

app.js文件中,让我们配置Express服务器。添加以下内容并保存文件。

const express = require("express");

const app = express();    // 初始化一个Express实例

const PORT = process.env.PORT || 5000;  // 定义端口
app.use(express.json());

// 健康检查端点(可选)
app.get("/", (req, res) => {
  return res.json({ status: "Up and running" });
});

// 开始监听请求
app.listen(PORT, () => console.log("Server started listening!"));

使用node app启动服务器。你应该看到以下输出:

Server started listening!

如果你导航到http://localhost:5000/。你应该看到状态为Up and running

太棒了!现在我们的应用程序可以处理HTTP请求了。

5、实现端点

让我们现在创建一个router.js文件,其中包含项目中使用的所有端点。

const express = require("express");

const router = express.Router();   // 实例化路由器

const Controller = require("./controller");

router.get("/prices", Controller.CurrentPrice);  // 获取当前价格

module.exports = router;

controller.js文件将包含每个端点的逻辑。

让我们定义端点的逻辑。首先,让我们从CurrentPrice端点开始。

exports.CurrentPrice = async (req, res) => {
  try {
    let prices = await currentPrice();
    if (prices.error) return res.status(500).json(errorObject);
    return res.status(200).json({
      success: true,
      price_data: prices.data,
    });
  } catch (error) {
    return res.status(500).json(errorObject);
  }
};

你可以看到我使用了currentPrice函数来获取当前市场价格。

让我们在helpers文件夹内实现“currentPrice”函数。这是最重要的函数,因为我们在应用的许多地方都会使用它。

为了简单起见,我在本演示中只实现了BTC和ETH价格提醒。你可以添加任何你喜欢的资产。

为了获取ETH和BTC的当前价格,我使用了nomics api。你可以从任何来源获取价格。

const axios = require("axios");

module.exports = async () => {
  try {
    let url =
      "https://api.nomics.com/v1/currencies/ticker?key=demo-6410726746980cead2a17c9db9ef29af&ids=BTC,ETH&interval=1m&convert=USD&per-page=2&page=1";
    const resp = await axios.get(url);
    return {
      error: false,
      data: { BTC: resp.data[0].price, ETH: resp.data[1].price },
    };
  } catch (error) {
    return { error: true };
  }
};

此外,我在config.js文件中定义了一个错误对象,可以在处理请求时发生未知错误时发送。

module.exports = {
  errorObject: {
    error: true,
    message: "Oops, something went wrong. Please try again later.",
  }
}

在控制器文件中导入错误对象和当前价格函数并保存。

现在让我们测试我们的GET /prices端点。启动服务器并导航到http://localhost:5000/prices。你可以在浏览器中看到价格对象显示,或者通过Postman访问端点。

太好了!我们成功地获取了ETH和BTC的当前市场价格(以美元计)。

让我们继续实现另外两个端点,用于创建警报和检索所有活动的警报。为了在应用中全局使用警报,我们将在alerts.js文件中创建一个导出空数组的文件。

module.exports = [];

controller.js文件顶部导入该文件。

var alerts = require('./alerts');

让我们定义创建新警报的逻辑。

exports.CreateAlert = async (req, res) => {
  try {
    const { asset, price, email, type } = req.body;

    if (!asset || !price || !email || !type)   // 检查是否传递了所有字段
      return res.status(400).json({
        error: true,
        message: "Please provide the required fields",
      });

    if (asset.toLowerCase() != "btc" && asset.toLowerCase() != "eth")
      return res.status(400).json({
        error: true,
        message: "You can set alerts for BTC and ETH only.",
      });

    // 通过将对象推送到警报数组中创建警报
    alerts.push({
      asset: asset,
      price: price,
      email: email,
      type: type.toLowerCase(),
      createdAt: new Date(),
    });

    return res.send({ success: true, message: "Alert created" }); // 发送响应

  } catch (error) {
    return res.status(500).json(errorObject);
  }
};

我将警报存储在内存中以保持演示简单。如果停止服务器,警报将丢失,因为它们临时存储在易失性内存上。如果你想在生产环境中使用,请使用数据库。

接下来,定义检索所有活动警报的逻辑。此端点很简单。我们只需要返回警报数组。

exports.GetAlerts = async (req, res) => {
  return res.send({ success: true, alerts: alerts });
};

就这样。我们需要定义创建和检索警报的端点。

router.get("/alerts", Controller.GetAlerts);
router.post("/alert", Controller.CreateAlert);

定义完所有三个路由后,router.js文件将如下所示:

const express = require("express");
const router = express.Router();
const Controller = require("./controller");

router.get("/prices", Controller.CurrentPrice);

router.get("/alerts", Controller.GetAlerts);

router.post("/alert", Controller.CreateAlert);

module.exports = router;

我们需要在app.js文件中导入并使用此路由器文件。

const express = require("express");
const routes = require("./router");  // 导入路由

const app = express();  // 初始化一个Express实例

const PORT = process.env.PORT || 5000;  // 定义端口
app.use(express.json());

// 健康检查端点(可选)
app.get("/", (req, res) => {
  return res.json({ status: "Up and running" });
});

app.use(routes);  // 加载端点

// 开始监听请求
app.listen(PORT, () => console.log("Server started listening!"));

现在是时候玩一玩了。让我们打开Postman并测试我们的端点。首先,我们将尝试创建一个BTC的警报,阈值价格低于49000.095美元。

你可以在响应中看到“Alert created”消息。类似地,你可以尝试为阈值价格创建多个警报,并且还可以指定接收警报的电子邮件地址。

现在我们可以尝试获取所有警报。

它有效!我们已经实现了本项目所需的全部端点。现在我们需要删除过期的警报。我将警报的过期时间设置为5分钟。你可以根据需要修改它。

6、实现Cron任务(调度器)

我们需要实现两个调度器。一个是删除过期的警报,另一个是在指定的币价格超过/低于阈值时发送警报。

首先,让我们实现removeExpired服务。为了保持简单,每10秒迭代所有活动的警报,并删除在5分钟前创建的警报。

const CronJob = require("cron").CronJob;
const alerts = require("../alerts");

var removeExpired = new CronJob("*/10 * * * * *", // 每10秒运行一次
  async function () {
    alerts.forEach((alert, index) => {  // 迭代所有警报
      // 转换为毫秒并比较
      if (new Date(alert.createdAt).getTime() + 5 * 60 * 1000 < new Date().getTime())
        // 如果警报创建时间+5分钟大于当前时间,则从数组中移除
        alerts.splice(index, 1);
    });
  });

removeExpired.start();

保存文件并在controller.js文件中引入:

require('./workers/removeExpired.js')

调度器会在应用启动时立即开始运行。

现在,启动服务器并添加一些警报。你可以看到在创建后的5分钟内,警报会被移除。你可以使用GET /alerts端点确认removeExpired服务按预期工作。

在实现第二个调度器sendAlert之前,我们需要实现sendEmailNotification辅助函数来发送电子邮件通知。

打开helpers/sendEmailNotification.js并添加以下内容:

const nodemailer = require("nodemailer");
const config = require("../config");

module.exports = async (email, message, title) => {
  try {
    const smtpEndpoint = "smtp.sendgrid.net";  // SMTP服务器

    const port = 465;

    const senderAddress = `${config.NAME} <${config.EMAIL_ADDRESS}>`;

    var toAddress = email;

    const smtpUsername = config.SENDGRID_USERNAME;

    const smtpPassword = config.SENDGRID_PASSWORD;

    var subject = title;

    var body_html = `<html><p>${message}</p></html>`;

    // 创建SMTP传输
    let transporter = nodemailer.createTransport({
      host: smtpEndpoint,
      port: port,
      secure: true, // 对于465为true,对于其他端口为false
      auth: {
        user: smtpUsername,
        pass: smtpPassword,
      },
    });

    // 指定邮件字段
    let mailOptions = {
      from: senderAddress,
      to: toAddress,
      subject: subject,
      html: body_html,
    };

    await transporter.sendMail(mailOptions);  // 发送邮件

    return { error: false };
  } catch (error) {
    console.error("send-email-error", error);
    return {
      error: true,
      message: "Couldn't send email",
    };
  }
};

保存文件。确保你在配置文件中添加了USERNAMEPASSWORDEMAILNAME

module.exports = {
  errorObject: {
    error: true,
    message: "Oops, something went wrong. Please try again later.",
  },

  //****这些值必须作为生产环境中的环境变量配置****

  SENDGRID_USERNAME: "",
  SENDGRID_PASSWORD: "",
  NAME: "",
  EMAIL_ADDRESS: "",

  REDIS_URL: "redis://127.0.0.1:6379",

};

workers/sendAlert.js中添加以下内容:

const CronJob = require("cron").CronJob;
var Queue = require("bull");

const alerts = require("../alerts");
const config = require("../config");

const currentPrice = require("../helpers/currentPrice");
const sendEmailNotification = require("../helpers/sendEmailNotification");

var alertQueue = new Queue("alerts", config.REDIS_URL); // 创建队列

alertQueue.process(async function (job, done) {   // 消费者进程
  const { recipient, title, message } = job.data;

  let sendEmailResponse = await sendEmailNotification(
    recipient,
    message,
    title
  );
  if (sendEmailResponse.error) {
    done(new Error("Error sending alert"));
  }
  done();
});

var sendAlert = new CronJob("*/25 * * * * *",   // 每25秒执行一次
async function () {

  const currentPrices = await currentPrice();
  if (currentPrices.error) return;

  let priceObj = {
    BTC: currentPrices.data.BTC,
    ETH: currentPrices.data.ETH,
  };

  alerts.forEach((alert, index) => {
    let message, title, recipient;
    if (
      alert.type == "above" &&
      parseFloat(alert.price) <= parseFloat(priceObj[alert.asset])
    ) {

      message = `Price of ${alert.asset} has just exceeded your alert price of ${alert.price} USD.
      Current price is ${priceObj[alert.asset]} USD.`;
      title = `${alert.asset} is up!`;
      recipient = alert.email;

      alertQueue.add(                    // 添加到队列(生产者)
        { message, recipient, title },
        {
          attempts: 3,                    // 尝试3次,每次间隔3秒
          backoff: 3000,
        }
      );

      alerts.splice(index, 1)  // 一旦推送到队列中,就从警报数组中移除

    } else if (
      alert.type == "below" &&
      parseFloat(alert.price) > parseFloat(priceObj[alert.asset])
    ) {
      message = `Price of ${alert.asset} fell below your alert price of ${alert.price}.
      Current price is ${priceObj[alert.asset]} USD.`;

      recipient = alert.email;
      title = `${alert.asset} is down!`;

      alertQueue.add(                   // 添加到队列(生产者)
        { message, recipient, title },
        {
          attempts: 3,
          backoff: 3000,
        }
      );

      alerts.splice(index, 1)  // 一旦推送到队列中,就从警报数组中移除
    }
  });
});

sendAlert.start();

我每25秒运行一次调度器。你可以根据需要更改它。一旦警报被推送到队列中,我们将从警报数组中移除它,以防止重复警报。

同时,请确保如果你使用本地主机,redis-server正在端口6379上运行,并且REDIS_URLconfig.js文件中正确输入。

我将最大重试次数设置为3,回退时间为3秒,这意味着如果发送电子邮件时发生错误,BullMQ的消费者进程将在3秒的时间间隔内尝试处理请求。如果请求失败3次,则会被丢弃。

现在保存文件并使用npm run start命令启动服务器。

打开Postman并添加一些警报。你会像这样收到指定电子邮件地址的通知。

如果邮件没有进入收件箱,请确保检查垃圾邮件和推广文件夹。同时,你需要在你的SendGrid账户中将发件人列入白名单,以便你的邮件能够进入收件箱。

你可以在此GitHub上找到本项目的完整源代码。这里

7、结束语

正如我之前提到的,除非你做一些改进,比如将警报保存到数据库、将密钥作为环境变量传递、配置进程管理器等,否则这个应用程序不适合生产环境。

试着通过添加更多功能将这个演示提升到新的水平。

你可以尝试:

  • 添加推送通知或短信提醒
  • 添加更多硬币/代币。
  • 向多个电子邮件地址发送警报。
  • 通过设计用户界面将其变成全栈应用
  • 通过部署到云端服务器上线。

原文链接:Build your own cryptocurrency price alert service using NodeJS

DefiPlot翻译整理,转载请标明出处

免责声明:本站资源仅用于学习目的,也不应被视为投资建议,读者在采取任何行动之前应自行研究并对自己的决定承担全部责任。
通过 NowPayments 打赏