打造自己的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",
};
}
};
保存文件。确保你在配置文件中添加了USERNAME
、PASSWORD
、EMAIL
和NAME
。
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_URL
在config.js
文件中正确输入。
我将最大重试次数设置为3,回退时间为3秒,这意味着如果发送电子邮件时发生错误,BullMQ的消费者进程将在3秒的时间间隔内尝试处理请求。如果请求失败3次,则会被丢弃。
现在保存文件并使用npm run start
命令启动服务器。
打开Postman并添加一些警报。你会像这样收到指定电子邮件地址的通知。

如果邮件没有进入收件箱,请确保检查垃圾邮件和推广文件夹。同时,你需要在你的SendGrid账户中将发件人列入白名单,以便你的邮件能够进入收件箱。
你可以在此GitHub上找到本项目的完整源代码。这里
7、结束语
正如我之前提到的,除非你做一些改进,比如将警报保存到数据库、将密钥作为环境变量传递、配置进程管理器等,否则这个应用程序不适合生产环境。
试着通过添加更多功能将这个演示提升到新的水平。
你可以尝试:
- 添加推送通知或短信提醒
- 添加更多硬币/代币。
- 向多个电子邮件地址发送警报。
- 通过设计用户界面将其变成全栈应用
- 通过部署到云端服务器上线。
原文链接:Build your own cryptocurrency price alert service using NodeJS
DefiPlot翻译整理,转载请标明出处
免责声明:本站资源仅用于学习目的,也不应被视为投资建议,读者在采取任何行动之前应自行研究并对自己的决定承担全部责任。