Initial backend setup with Express and Socket.IO
This commit is contained in:
3
.env.example
Normal file
3
.env.example
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
PORT=5000
|
||||||
|
NODE_ENV=development
|
||||||
|
CORS_ORIGIN=http://localhost:3000
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules/
|
||||||
|
.env
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
1513
package-lock.json
generated
Normal file
1513
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
package.json
Normal file
28
package.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "dashboard-backend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Real-time cryptocurrency dashboard backend",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server.js",
|
||||||
|
"dev": "nodemon server.js"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"express",
|
||||||
|
"socket.io",
|
||||||
|
"realtime",
|
||||||
|
"crypto"
|
||||||
|
],
|
||||||
|
"author": "CloudForge",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.13.1",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^17.2.3",
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"socket.io": "^4.8.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^3.1.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
173
server.js
Normal file
173
server.js
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const http = require("http");
|
||||||
|
const socketIo = require("socket.io");
|
||||||
|
const cors = require("cors");
|
||||||
|
const axios = require("axios");
|
||||||
|
require("dotenv").config();
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
const server = http.createServer(app);
|
||||||
|
|
||||||
|
const io = socketIo(server, {
|
||||||
|
cors: {
|
||||||
|
origin: process.env.CORS_ORIGIN || "*",
|
||||||
|
methods: ["GET", "POST"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== (Middleware) ==========
|
||||||
|
|
||||||
|
app.use(cors());
|
||||||
|
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
// ========== HTTP Route ==========
|
||||||
|
|
||||||
|
app.get("/health", (req, res) => {
|
||||||
|
res.json({
|
||||||
|
status: "OK",
|
||||||
|
message: "Server is running",
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== Socket.IO Logic ==========
|
||||||
|
|
||||||
|
// 用於追蹤連接的客戶端數量
|
||||||
|
let connectedClients = 0;
|
||||||
|
|
||||||
|
// 當客戶端連接時觸發
|
||||||
|
io.on("connection", (socket) => {
|
||||||
|
// 增加客戶端計數
|
||||||
|
connectedClients++;
|
||||||
|
|
||||||
|
console.log(`✅ 客戶端已連接: ${socket.id}`);
|
||||||
|
console.log(`📊 當前連接數: ${connectedClients}`);
|
||||||
|
|
||||||
|
// 向所有客戶端廣播當前連接數
|
||||||
|
io.emit("clientCount", connectedClients);
|
||||||
|
|
||||||
|
// ========== 監聽客戶端事件 ==========
|
||||||
|
|
||||||
|
// 當客戶端要求數據時
|
||||||
|
socket.on("requestData", async (dataType) => {
|
||||||
|
console.log(`📤 數據請求: ${dataType}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
let data;
|
||||||
|
|
||||||
|
if (dataType === "crypto") {
|
||||||
|
// 從 CoinGecko API 獲取加密貨幣數據
|
||||||
|
const response = await axios.get(
|
||||||
|
"https://api.coingecko.com/api/v3/simple/price",
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
// 要查詢的加密貨幣
|
||||||
|
ids: "bitcoin,ethereum,cardano,solana,ripple",
|
||||||
|
// 使用 USD 幣種
|
||||||
|
vs_currencies: "usd",
|
||||||
|
// 包含市值
|
||||||
|
include_market_cap: true,
|
||||||
|
// 包含24小時交易量
|
||||||
|
include_24hr_vol: true,
|
||||||
|
// 包含24小時漲跌幅
|
||||||
|
include_24hr_change: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 組織數據格式
|
||||||
|
data = {
|
||||||
|
type: "crypto",
|
||||||
|
timestamp: new Date(),
|
||||||
|
prices: response.data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 發送數據給要求的客戶端
|
||||||
|
socket.emit("data", data);
|
||||||
|
console.log(`✅ 數據已發送給客戶端: ${socket.id}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ 獲取數據出錯:", error.message);
|
||||||
|
// 發送錯誤信息給客戶端
|
||||||
|
socket.emit("error", {
|
||||||
|
message: "Failed to fetch data",
|
||||||
|
details: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== 定期推送數據 ==========
|
||||||
|
|
||||||
|
// 創建定時器,每5秒推送一次數據給該客戶端
|
||||||
|
const dataInterval = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
// 獲取最新的加密貨幣數據
|
||||||
|
const response = await axios.get(
|
||||||
|
"https://api.coingecko.com/api/v3/simple/price",
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
ids: "bitcoin,ethereum,cardano,solana,ripple",
|
||||||
|
vs_currencies: "usd",
|
||||||
|
include_market_cap: true,
|
||||||
|
include_24hr_vol: true,
|
||||||
|
include_24hr_change: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 向該客戶端發送數據(實時更新)
|
||||||
|
socket.emit("data", {
|
||||||
|
type: "crypto",
|
||||||
|
timestamp: new Date(),
|
||||||
|
prices: response.data,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ 定時推送出錯:", error.message);
|
||||||
|
}
|
||||||
|
}, 5000); // 每5000毫秒(5秒)執行一次
|
||||||
|
|
||||||
|
// ========== 客戶端斷開連接 ==========
|
||||||
|
|
||||||
|
// 當客戶端斷開連接時觸發
|
||||||
|
socket.on("disconnect", () => {
|
||||||
|
connectedClients--;
|
||||||
|
console.log(`❌ 客戶端已斷開: ${socket.id}`);
|
||||||
|
console.log(`📊 當前連接數: ${connectedClients}`);
|
||||||
|
|
||||||
|
// 向所有客戶端廣播更新的連接數
|
||||||
|
io.emit("clientCount", connectedClients);
|
||||||
|
|
||||||
|
// 清除該客戶端的定時器
|
||||||
|
clearInterval(dataInterval);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== 啟動服務器 ==========
|
||||||
|
|
||||||
|
// 從環境變數獲取端口,或使用默認5000
|
||||||
|
const PORT = process.env.PORT || 5000;
|
||||||
|
|
||||||
|
// 啟動伺服器
|
||||||
|
server.listen(PORT, () => {
|
||||||
|
console.log("");
|
||||||
|
console.log("═══════════════════════════════════════");
|
||||||
|
console.log(`✅ 服務器運行在: http://localhost:${PORT}`);
|
||||||
|
console.log("📡 WebSocket 已準備接收連接");
|
||||||
|
console.log("🔗 CORS 來源:", process.env.CORS_ORIGIN || "*");
|
||||||
|
console.log("═══════════════════════════════════════");
|
||||||
|
console.log("");
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== 錯誤處理 ==========
|
||||||
|
|
||||||
|
// 處理未捕獲的異常
|
||||||
|
process.on("unhandledRejection", (err) => {
|
||||||
|
console.error("❌ 未捕獲的異常:", err);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on("uncaughtException", (err) => {
|
||||||
|
console.error("❌ 未捕獲的異常:", err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user