From 4455ba598f491ab59e11f4206c39b77fcb064b31 Mon Sep 17 00:00:00 2001 From: david_bai Date: Sat, 21 Jun 2025 17:08:11 +0800 Subject: [PATCH] translate comment --- backend/ecosystem.config.js | 4 +-- backend/scripts/export-tracking-data.js | 26 ++++++++--------- backend/src/config/env.ts | 12 ++++---- backend/src/config/server.ts | 12 ++++---- backend/src/routes/api.ts | 20 ++++++------- backend/src/server.ts | 8 ++--- backend/src/services/rateLimit.ts | 6 ++-- backend/src/services/redis.ts | 12 ++++---- backend/src/services/room.ts | 39 +++++++++++++------------ backend/src/socket/handlers.ts | 38 ++++++++++++------------ backend/src/types/socket.ts | 2 +- 11 files changed, 90 insertions(+), 89 deletions(-) diff --git a/backend/ecosystem.config.js b/backend/ecosystem.config.js index c8e832d..1f20e50 100644 --- a/backend/ecosystem.config.js +++ b/backend/ecosystem.config.js @@ -1,7 +1,7 @@ module.exports = { apps: [{ name: "signaling-server", - script: "./dist/server.js", // 指向编译后的文件 + script: "./dist/server.js", // Point to the compiled file watch: false, env: { "NODE_ENV": "production", @@ -13,6 +13,6 @@ module.exports = { max_memory_restart: "500M", instances: 1, exec_mode: "fork", - group: "ssl-cert" // 添加这行,指定进程运行的组 + group: "ssl-cert" // Add this line to specify the group the process runs as }] } \ No newline at end of file diff --git a/backend/scripts/export-tracking-data.js b/backend/scripts/export-tracking-data.js index 1621018..692adf6 100644 --- a/backend/scripts/export-tracking-data.js +++ b/backend/scripts/export-tracking-data.js @@ -3,7 +3,7 @@ const Redis = require('ioredis'); const fs = require('fs/promises'); const path = require('path'); -// Redis 客户端配置 +// Redis client configuration const redis = new Redis({ host: 'localhost', port: 6379, @@ -15,16 +15,16 @@ async function exportTrackingData() { const fileName = `tracking-data-${timestamp}.txt`; const filePath = path.join(__dirname, '../logs', fileName); - // 创建输出内容 + // Create output content let output = '=== Tracking Data Export ===\n'; output += `Generated at: ${new Date().toISOString()}\n\n`; - // 1. 获取所有来源 + // 1. Get all sources const sources = await redis.smembers('referrers:sources'); output += '=== Referral Sources ===\n'; output += sources.join(', ') + '\n\n'; - // 2. 获取总计数 + // 2. Get total counts const totalCounts = await redis.hgetall('referrers:count'); output += '=== Total Counts by Source ===\n'; for (const [source, count] of Object.entries(totalCounts)) { @@ -32,21 +32,21 @@ async function exportTrackingData() { } output += '\n'; - // 3. 获取每日统计数据并按日期排序 + // 3. Get daily statistics and sort by date output += '=== Daily Statistics ===\n'; const dailyKeys = await redis.keys('referrers:daily:*'); - // 将 key 转换为包含日期对象的数组,并按日期排序 + // Convert keys to an array of objects with dates and sort by date const dailyData = await Promise.all(dailyKeys.map(async (key) => { const date = key.split(':')[2]; const dailyStats = await redis.hgetall(key); return { date: new Date(date), data: dailyStats }; })); - dailyData.sort((a, b) => b.date.getTime() - a.date.getTime()); // 按日期降序排序(最近到最远) + dailyData.sort((a, b) => b.date.getTime() - a.date.getTime()); // Sort by date in descending order (most recent to oldest) for (const item of dailyData) { - const dateString = item.date.toISOString().split('T')[0]; //日期格式化字符串 + const dateString = item.date.toISOString().split('T')[0]; // Format date string output += `\nDate: ${dateString}\n`; for (const [source, count] of Object.entries(item.data)) { output += ` ${source}: ${count}\n`; @@ -55,14 +55,14 @@ async function exportTrackingData() { output += '\n'; - // 5. 添加基本统计信息 + // 5. Add basic statistics output += '\n=== Summary ===\n'; output += `Total Sources: ${sources.length}\n`; - // 确保日志目录存在 + // Ensure the log directory exists await fs.mkdir(path.join(__dirname, '../logs'), { recursive: true }); - // 写入文件 + // Write to file await fs.writeFile(filePath, output, 'utf8'); console.log(`Data exported successfully to: ${filePath}`); @@ -71,7 +71,7 @@ async function exportTrackingData() { console.log(output.slice(0, 500) + '...'); console.log('='.repeat(50)); - // 关闭 Redis 连接 + // Close Redis connection await redis.quit(); } catch (error) { @@ -80,5 +80,5 @@ async function exportTrackingData() { } } -// 执行导出 +// Execute export exportTrackingData(); diff --git a/backend/src/config/env.ts b/backend/src/config/env.ts index 95c1cf3..5de934d 100644 --- a/backend/src/config/env.ts +++ b/backend/src/config/env.ts @@ -1,7 +1,7 @@ import dotenv from "dotenv"; import path from "path"; -// 定义配置对象的类型 +// Define the type for the configuration object interface AppConfig { PORT: number; CORS_ORIGIN: string; @@ -12,23 +12,23 @@ interface AppConfig { }; } -// 根据环境加载对应的.env文件 +// Load the corresponding .env file based on the environment dotenv.config({ path: process.env.NODE_ENV === "production" ? path.resolve(process.cwd(), ".env.production.local") : path.resolve(process.cwd(), ".env.development.local"), }); -// 检查必要的 Redis 环境变量 +// Check for necessary Redis environment variables if (!process.env.REDIS_HOST) { console.error("FATAL ERROR: REDIS_HOST environment variable is not set."); - process.exit(1); // 或者抛出错误 new Error("REDIS_HOST environment variable is not set."); + process.exit(1); // Or throw an error: new Error("REDIS_HOST environment variable is not set."); } if (!process.env.REDIS_PORT) { console.error("FATAL ERROR: REDIS_PORT environment variable is not set."); - process.exit(1); // 或者抛出错误 new Error("REDIS_PORT environment variable is not set."); + process.exit(1); // Or throw an error: new Error("REDIS_PORT environment variable is not set."); } -// 导出类型安全的配置对象 +// Export the type-safe configuration object export const CONFIG: AppConfig = { PORT: parseInt(process.env.BACKEND_PORT || "3001", 10), CORS_ORIGIN: process.env.CORS_ORIGIN!, diff --git a/backend/src/config/server.ts b/backend/src/config/server.ts index 1e01d9b..0afaaa9 100644 --- a/backend/src/config/server.ts +++ b/backend/src/config/server.ts @@ -1,6 +1,6 @@ import { CorsOptions } from 'cors'; import { CONFIG } from './env'; -// 配置 CORS +// Configure CORS export const corsOptions: CorsOptions = CONFIG.NODE_ENV === 'production' ? { origin: CONFIG.CORS_ORIGIN, @@ -9,23 +9,23 @@ export const corsOptions: CorsOptions = CONFIG.NODE_ENV === 'production' allowedHeaders: ['Content-Type', 'Authorization'] } : { - origin: true, // 开发环境允许所有源 + origin: true, // Allow all origins in development environment credentials: true, methods: ['GET', 'POST', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'] }; -// 配置 Socket.IO 的 CORS +// Configure CORS for Socket.IO export const corsWSOptions = CONFIG.NODE_ENV === 'production' ? { - origin: CONFIG.CORS_ORIGIN,// 允许的源,替换为你的Next.js应用的URL + origin: CONFIG.CORS_ORIGIN, // Allowed origin, replace with your Next.js application's URL methods: ['GET', 'POST'], credentials: true } : { - // 开发环境下允许多个源 + // Allow multiple origins in development environment origin: [ CONFIG.CORS_ORIGIN, - /^http:\/\/192\.168\.\d+\.\d+:3000$/,// 匹配所有 192.168.x.x:3000 格式的局域网地址 + /^http:\/\/192\.168\.\d+\.\d+:3000$/, // Match all LAN addresses in the format 192.168.x.x:3000 ], methods: ['GET', 'POST'], credentials: true diff --git a/backend/src/routes/api.ts b/backend/src/routes/api.ts index bdfc375..126ded5 100644 --- a/backend/src/routes/api.ts +++ b/backend/src/routes/api.ts @@ -20,7 +20,7 @@ import { ReferrerTrack, LogMessage } from "../types/room"; const router = Router(); -// 定义接口提高代码可读性和类型安全性 +// Define interfaces to improve code readability and type safety interface CreateRoomRequest { roomId: string; } @@ -29,7 +29,7 @@ interface CheckRoomRequest { roomId: string; } -// 创建房间的路由处理函数 +// Route handler for creating a room const createRoomHandler: RequestHandler<{}, any, CreateRoomRequest> = async ( req, res @@ -58,7 +58,7 @@ const createRoomHandler: RequestHandler<{}, any, CreateRoomRequest> = async ( } }; -// 获取房间的路由处理函数 +// Route handler for getting a room const getRoomHandler: RequestHandler = async (req, res) => { try { const roomId = await roomService.getAvailableRoomId(); @@ -70,7 +70,7 @@ const getRoomHandler: RequestHandler = async (req, res) => { } }; -// 检查房间的路由处理函数 +// Route handler for checking a room const checkRoomHandler: RequestHandler<{}, any, CheckRoomRequest> = async ( req, res @@ -90,7 +90,7 @@ const checkRoomHandler: RequestHandler<{}, any, CheckRoomRequest> = async ( } }; -// 设置跟踪的路由处理函数 +// Route handler for setting tracking const setTrackHandler: RequestHandler<{}, any, ReferrerTrack> = async ( req, res @@ -102,16 +102,16 @@ const setTrackHandler: RequestHandler<{}, any, ReferrerTrack> = async ( try { const { ref, timestamp, path } = req.body; - // 按日期统计 + // Statistics by date const date = new Date(timestamp).toISOString().split("T")[0]; const dailyKey = `referrers:daily:${date}`; const thirtyDaysInSeconds = 30 * 24 * 60 * 60; - // 使用MULTI确保hincrby和expire的原子性 + // Use MULTI to ensure atomicity of hincrby and expire await redis .multi() .hincrby(dailyKey, ref, 1) // \"referrers:daily:2024-01-20\" : { \"producthunt\": \"5\", \"twitter\": \"3\" } - .expire(dailyKey, thirtyDaysInSeconds) // 设置30天过期 + .expire(dailyKey, thirtyDaysInSeconds) // Set a 30-day expiration .exec(); res.status(200).json({ success: true }); @@ -121,7 +121,7 @@ const setTrackHandler: RequestHandler<{}, any, ReferrerTrack> = async ( } }; -// 日志调试的路由处理函数 +// Route handler for log debugging const logsDebugHandler: RequestHandler<{}, any, LogMessage> = async ( req, res @@ -136,7 +136,7 @@ const logsDebugHandler: RequestHandler<{}, any, LogMessage> = async ( } }; -// 注册路由 +// Register routes router.post("/api/create_room", createRoomHandler); router.get("/api/get_room", getRoomHandler); router.post("/api/check_room", checkRoomHandler); diff --git a/backend/src/server.ts b/backend/src/server.ts index 8d1d4a2..25970b1 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -1,14 +1,14 @@ -import express from "express"; //express: 用于创建一个简洁且灵活的Node.js web应用框架 +import express from "express"; // express: A minimalist and flexible Node.js web application framework import cors from "cors"; import http from "http"; -import { Server } from "socket.io"; //实时通信库,基于WebSocket协议,实现双向通信 +import { Server } from "socket.io"; // socket.io: A library for real-time web applications, enables real-time, bi-directional communication between web clients and servers. import { CONFIG } from "./config/env"; import { corsOptions, corsWSOptions } from "./config/server"; import apiRouter from "./routes/api"; import { setupSocketHandlers } from "./socket/handlers"; -const app = express(); //创建一个Express应用 -app.use(cors(corsOptions)); // 添加 CORS 中间件 +const app = express(); // Create an Express application +app.use(cors(corsOptions)); // Add CORS middleware app.use(express.json()); app.use(apiRouter); diff --git a/backend/src/services/rateLimit.ts b/backend/src/services/rateLimit.ts index a341cc4..48c1469 100644 --- a/backend/src/services/rateLimit.ts +++ b/backend/src/services/rateLimit.ts @@ -20,8 +20,8 @@ import { redis } from './redis'; const RATE_LIMIT_PREFIX = 'ratelimit:join:'; -const RATE_WINDOW = 5; // 5秒时间窗口 -const RATE_LIMIT = 2; // 允许的最大请求次数 +const RATE_WINDOW = 5; // 5-second time window +const RATE_LIMIT = 2; // Maximum number of requests allowed export async function checkRateLimit(ip: string): Promise<{ allowed: boolean; @@ -32,7 +32,7 @@ export async function checkRateLimit(ip: string): Promise<{ const now = Date.now(); const windowStart = now - (RATE_WINDOW * 1000); - // 使用 Redis 的 MULTI 命令开启事务 + // Use Redis's MULTI command to start a transaction const pipeline = redis.pipeline(); // 1. Add current request's timestamp diff --git a/backend/src/services/redis.ts b/backend/src/services/redis.ts index cdcea8f..7afc370 100644 --- a/backend/src/services/redis.ts +++ b/backend/src/services/redis.ts @@ -1,21 +1,21 @@ import { Redis } from 'ioredis'; import { CONFIG } from '../config/env'; -// 房间前缀和过期时间(秒) +// Room prefix and expiration time (seconds) export const ROOM_PREFIX = 'room:'; export const SOCKET_PREFIX = 'socket:'; export const ROOM_EXPIRY = 3600 * 24; // 24 hours -// Redis 配置选项 +// Redis configuration options const redisConfig = { host: CONFIG.REDIS.HOST, port: CONFIG.REDIS.PORT, - // Redis 持久化配置需要在 redis.conf 中设置,而不是在客户端 - // appendonly: 'yes',// 启用 AOF 持久化 - // save: '900 1 300 10',// 启用 RDB 快照 + // Redis persistence configuration needs to be set in redis.conf, not in the client + // appendonly: 'yes',// Enable AOF persistence + // save: '900 1 300 10',// Enable RDB snapshot }; export const redis = new Redis(redisConfig); -// 可以在这里添加连接事件监听 +// Connection event listeners can be added here redis.on('connect', () => { console.log('Redis connected successfully'); }); diff --git a/backend/src/services/room.ts b/backend/src/services/room.ts index f52a133..6e12f66 100644 --- a/backend/src/services/room.ts +++ b/backend/src/services/room.ts @@ -29,7 +29,7 @@ import { redis, ROOM_PREFIX, SOCKET_PREFIX, ROOM_EXPIRY } from './redis'; const MAX_NUMERIC_ID_ATTEMPTS = 10; const MAX_ALPHANUMERIC_ID_ATTEMPTS = 50; -// 生成随机房间号--4位数字 +// Generate a random 4-digit numeric room ID function generateNumericRoomId(length: number = 4): string { let id = ''; for (let i = 0; i < length; i++) { @@ -37,6 +37,7 @@ function generateNumericRoomId(length: number = 4): string { } return id; } +// Generate a random 4-character alphanumeric room ID function generateAlphanumericRoomId(length: number = 4): string { const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let result = ''; @@ -46,11 +47,11 @@ function generateAlphanumericRoomId(length: number = 4): string { } return result; } -// 检查房间是否存在 +// Check if a room exists export async function isRoomExist(roomId: string): Promise { - return await redis.hexists(ROOM_PREFIX + roomId, 'created_at') === 1;//hset和hexists方法操作哈希,created_at是字段名 + return await redis.hexists(ROOM_PREFIX + roomId, 'created_at') === 1; // hset and hexists operate on hashes, 'created_at' is the field name } -// 创建新房间 +// Create a new room // (Hash) // "room:1234" : { // "created_at": "1705123456789" @@ -59,16 +60,16 @@ export async function createRoom(roomId: string): Promise { const roomKey = ROOM_PREFIX + roomId; const socketsKey = `${roomKey}:sockets`; await redis.multi() - .hset(roomKey, 'created_at', Date.now())//设置hash,存储房间的创建时间; - .expire(roomKey, ROOM_EXPIRY)//设置过期时间 + .hset(roomKey, 'created_at', Date.now()) // Set hash to store the room's creation time + .expire(roomKey, ROOM_EXPIRY) // Set expiration time .expire(socketsKey, ROOM_EXPIRY) .exec(); } -// 删除房间 +// Delete a room export async function deleteRoom(roomId: string): Promise { await redis.del(ROOM_PREFIX + roomId); } -// 刷新房间过期时间 +// Refresh a room's expiration time export async function refreshRoom(roomId: string, expiry: number = 0): Promise { const actualExpiry = expiry > 0 ? expiry : ROOM_EXPIRY; const roomKey = ROOM_PREFIX + roomId; @@ -79,7 +80,7 @@ export async function refreshRoom(roomId: string, expiry: number = 0): Promise { let roomId: string; let attempts = 0; @@ -106,32 +107,32 @@ export async function getAvailableRoomId(): Promise { } return roomId; } -// 将socket.id与房间号绑定 +// Bind a socket.id to a room ID export async function bindSocketToRoom(socketId: string, roomId: string): Promise { await redis.multi() - //字符串,存储与该socket ID相关联的房间号,"socket:abcd1234" : "1234" + // String, stores the room ID associated with this socket ID, e.g., "socket:abcd1234" : "1234" .set(SOCKET_PREFIX + socketId, roomId, 'EX', ROOM_EXPIRY+3600) // Set with expiry - //添加集合,房间内的 Socket 列表 (Set),"room:1234:sockets" : ["socket1", "socket2", ...] + // Set, list of sockets in the room, e.g., "room:1234:sockets" : ["socket1", "socket2", ...] .sadd(ROOM_PREFIX + roomId + ':sockets', socketId) .exec(); } -// 获取socket.id对应的房间号 +// Get the room ID for a given socket.id export async function getRoomBySocketId(socketId: string): Promise { return await redis.get(SOCKET_PREFIX + socketId); } -// 解绑socket.id与房间号 +// Unbind a socket.id from a room ID export async function unbindSocketFromRoom(socketId: string, roomId: string): Promise { await redis.multi() - .del(SOCKET_PREFIX + socketId)//解绑socket ID与房间号 - .srem(ROOM_PREFIX + roomId + ':sockets', socketId)//从房间的集合中移除socket ID + .del(SOCKET_PREFIX + socketId) // Unbind socket ID from room ID + .srem(ROOM_PREFIX + roomId + ':sockets', socketId) // Remove socket ID from the room's set .exec(); } -// 检查房间是否为空 +// Check if a room is empty export async function isRoomEmpty(roomId: string): Promise { - const count = await redis.scard(ROOM_PREFIX + roomId + ':sockets');//返回集合中元素的数量 + const count = await redis.scard(ROOM_PREFIX + roomId + ':sockets'); // Returns the number of elements in the set return count === 0; } -// 检查房间连接数 +// Get the number of connections in a room export async function roomNumOfConnection(roomId: string): Promise { return await redis.scard(ROOM_PREFIX + roomId + ':sockets'); } \ No newline at end of file diff --git a/backend/src/socket/handlers.ts b/backend/src/socket/handlers.ts index 5ba9181..b4fbf8a 100644 --- a/backend/src/socket/handlers.ts +++ b/backend/src/socket/handlers.ts @@ -2,12 +2,12 @@ import { Server, Socket } from 'socket.io'; import * as roomService from '../services/room'; import { JoinData, SignalingData, InitiatorData, RecipientData } from '../types/socket'; import { checkRateLimit } from '../services/rateLimit'; -// 房间管理: -// 使用 roomId 进行广播消息(socket.to(roomId).emit()) -// 场景:新用户加入通知、房间状态更新等 -// WebRTC 信令: -// 使用 peerId 进行点对点通信(socket.to(peerId).emit()) -// 场景:offer、answer、ice-candidate 等 WebRTC 连接建立过程中的所有信令 +// Room Management: +// Use roomId to broadcast messages (socket.to(roomId).emit()) +// Scenarios: Notifying new user joined, room status updates, etc. +// WebRTC Signaling: +// Use peerId for peer-to-peer communication (socket.to(peerId).emit()) +// Scenarios: All signaling during WebRTC connection setup, like offer, answer, ice-candidate. export function setupSocketHandlers(io: Server): void { io.on('connection', (socket: Socket) => { console.log('New client connected:', socket.id); @@ -15,10 +15,10 @@ export function setupSocketHandlers(io: Server): void { socket.on('join', async (data: JoinData) => { const { roomId: targetRoomId } = data; // Renamed for clarity try { - // 获取客户端IP + // Get client IP const clientIp = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address; - // 检查频率限制 + // Check rate limit const rateLimitCheck = await checkRateLimit(clientIp as string); if (!rateLimitCheck.allowed) { socket.emit('joinResponse', { @@ -35,16 +35,16 @@ export function setupSocketHandlers(io: Server): void { } const existingRoomId = await roomService.getRoomBySocketId(socket.id); - if (!existingRoomId) {//socket.id不在房间里面 才允许新连接进入房间 + if (!existingRoomId) { // Only allow new connection to join if the socket.id is not already in a room socket.join(targetRoomId); console.log(`Client ${socket.id} joined room ${targetRoomId}`); await roomService.bindSocketToRoom(socket.id, targetRoomId); } await roomService.refreshRoom(targetRoomId); - // 通知用户加入成功 + // Notify the user that the join was successful socket.emit('joinResponse', { success: true, message: 'Successfully joined room', roomId: targetRoomId }); - // 通知房间内所有其他用户有新成员加入 + // Notify all other users in the room that a new member has joined socket.to(targetRoomId).emit('ready', { peerId: socket.id }); } catch (error) { console.error('Error joining room:', error); @@ -55,16 +55,16 @@ export function setupSocketHandlers(io: Server): void { }); } }); - // 处理WebRTC信令--直接转发 - // offer, answer, ice-candidate: 这些事件处理WebRTC连接的信令。它们负责转发客户端之间的连接请求和网络协商消息。 - // offer: 当一个客户端发起连接请求时,会发送一个offer给服务器,服务器将其转发给相同房间中的其他客户端。 - // answer: 被邀请的客户端接收到offer后,生成一个answer,通过服务器返回给发起连接的客户端。 - // ice-candidate: 当WebRTC需要穿透NAT防火墙时,会生成ICE候选者,客户端通过服务器相互交换这些信息,帮助建立P2P连接。 + // Handle WebRTC signaling - direct forwarding + // offer, answer, ice-candidate: These events handle WebRTC connection signaling. They are responsible for forwarding connection requests and network negotiation messages between clients. + // offer: When a client initiates a connection request, it sends an offer to the server, which forwards it to other clients in the same room. + // answer: The invited client receives the offer, generates an answer, and sends it back to the initiating client via the server. + // ice-candidate: When WebRTC needs to traverse NAT firewalls, it generates ICE candidates. Clients exchange this information through the server to help establish a P2P connection. socket.on('offer', (data: SignalingData) => { socket.to(data.peerId).emit('offer', { offer: data.offer, from: data.from, - peerId: socket.id // 发送方的ID + peerId: socket.id // Sender's ID }); }); @@ -83,13 +83,13 @@ export function setupSocketHandlers(io: Server): void { peerId: socket.id }); }); - // 处理发起方重新上线的通知--广播给房间内的其他用户 + // Handle notification for initiator coming back online -- broadcast to other users in the room socket.on('initiator-online', (data: InitiatorData) => { socket.to(data.roomId).emit('initiator-online', { roomId: data.roomId }); }); - // 处理接收方的响应 + // Handle recipient's response socket.on('recipient-ready', (data: RecipientData) => { socket.to(data.roomId).emit('recipient-ready', { peerId: data.peerId diff --git a/backend/src/types/socket.ts b/backend/src/types/socket.ts index 1528244..3d49629 100644 --- a/backend/src/types/socket.ts +++ b/backend/src/types/socket.ts @@ -2,7 +2,7 @@ export interface JoinData { roomId: string; } -// 添加 WebRTC 相关类型定义 +// Add WebRTC related type definitions declare global { interface RTCSessionDescriptionInit { type: RTCSdpType;