From 03776ee64a857a8c3a157167279881786ace230f Mon Sep 17 00:00:00 2001 From: xiaomai Date: Tue, 7 Oct 2025 11:59:55 +0800 Subject: [PATCH] initial commit --- .env.example | 4 + .gitignore | 2 + README.md | 174 ++++++++++++++++++++++++++++++++++++++ client/test-client.js | 27 ++++++ docker-compose.yml | 20 +++++ docs/update-suggestion.md | 123 +++++++++++++++++++++++++++ server/Dockerfile | 20 +++++ server/index.js | 80 ++++++++++++++++++ server/package.json | 18 ++++ 9 files changed, 468 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 client/test-client.js create mode 100644 docker-compose.yml create mode 100644 docs/update-suggestion.md create mode 100644 server/Dockerfile create mode 100644 server/index.js create mode 100644 server/package.json diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d2feffe --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +# .env.example +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_PASSWORD=your_password_here diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b659ca7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +repomix-output.xml +.env \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2d5445b --- /dev/null +++ b/README.md @@ -0,0 +1,174 @@ +好的 👍 我来帮你写一份专业的 **README.md**,既能让开发者快速上手,又能作为项目的正式说明文档。这里我会结合你的代码仓库结构和实现特点来写。 + +--- + +# README.md + +# 🚀 WebSocket + Redis Pub/Sub Service + +本项目是一个基于 **WebSocket** 与 **Redis** 的实时消息推送服务,支持多客户端订阅、消息分发与发布。 +通过 `docker-compose` 可一键启动服务,方便部署与扩展。 + +--- + +## 📂 项目结构 + +``` +. +├── client/ +│ └── test-client.js # WebSocket 客户端示例(测试用) +├── server/ +│ ├── Dockerfile # 服务端 Docker 构建配置 +│ ├── index.js # WebSocket + Redis 服务端逻辑 +│ └── package.json # Node.js 依赖配置 +└── docker-compose.yml # 服务编排配置 +``` + +--- + +## ⚙️ 功能特性 + +* 🔑 **认证机制**:客户端需通过 `auth` 消息声明 `app` 名称。 +* 📡 **订阅/取消订阅**:客户端可动态订阅频道,实时接收消息。 +* 📤 **消息发布**:通过 Redis Pub/Sub 将消息广播至所有订阅者。 +* 🛠 **易于扩展**:支持分布式部署,多个 WebSocket 节点可共享 Redis。 + +--- + +## 🚀 快速开始 + +### 1️⃣ 启动服务 + +确保已安装 **Docker** 与 **Docker Compose**。 + +```bash +docker compose up -d +``` + +服务启动后默认监听: + +``` +ws://localhost:8080 +``` + +### 2️⃣ 运行测试客户端 + +```bash +node client/test-client.js +``` + +预期输出: + +``` +✅ 已连接 WebSocket +📩 收到消息: {"type":"auth_ok","app":"demo"} +📩 收到消息: {"type":"subscribed","channel":"chat"} +📩 收到消息: {"app":"demo","channel":"chat","event":"message","data":{"text":"Hello Redis WS 🚀"}} +``` + +--- + +## 📡 协议说明 + +所有消息均为 **JSON 格式**,字段如下: + +### 🔑 认证 + +```json +{ "type": "auth", "app": "demo" } +``` + +服务端响应: + +```json +{ "type": "auth_ok", "app": "demo" } +``` + +### 📌 订阅频道 + +```json +{ "type": "subscribe", "channel": "chat" } +``` + +服务端响应: + +```json +{ "type": "subscribed", "channel": "chat" } +``` + +### 📤 发布消息 + +```json +{ + "type": "publish", + "app": "demo", + "channel": "chat", + "event": "message", + "data": { "text": "Hello Redis WS 🚀" } +} +``` + +被订阅该频道的客户端将收到: + +```json +{ + "app": "demo", + "channel": "chat", + "event": "message", + "data": { "text": "Hello Redis WS 🚀" } +} +``` + +--- + +## 🐳 环境变量 + +通过 `docker-compose.yml` 设置: + +| 变量名 | 默认值 | 说明 | +| ---------------- | ------------ | -------------- | +| `REDIS_HOST` | `main_redis` | Redis 主机地址 | +| `REDIS_PORT` | `6379` | Redis 端口 | +| `REDIS_PASSWORD` | 空字符串 | Redis 密码 | + +--- + +## 📦 构建与部署 + +### 本地构建镜像 + +```bash +docker build -t websocket-server ./server +``` + +### 运行容器 + +```bash +docker run -d -p 8080:8080 --name ws-server websocket-server +``` + +--- + +## 📖 技术栈 + +* **WebSocket** (`ws`): 提供实时通信能力 +* **Redis** (`ioredis`): 实现分布式消息队列与广播 +* **Docker** + **Docker Compose**: 快速部署与服务编排 + +--- + +## 📝 注意事项 + +* 本项目的 `Repomix` 合并文件仅供分析和审查,请勿直接修改。 +* 如果需要调整功能,请修改原始仓库中的对应文件。 +* 确保 Redis 服务正常运行,否则消息发布/订阅功能不可用。 + +--- + +## 📜 License + +[ISC](./server/package.json) + +--- + +要不要我帮你在 README 里再加一张 **架构图(WebSocket + Redis Pub/Sub 流程)**?这样别人一眼就能看懂整个系统的消息流向 🔄。 diff --git a/client/test-client.js b/client/test-client.js new file mode 100644 index 0000000..a63921c --- /dev/null +++ b/client/test-client.js @@ -0,0 +1,27 @@ +import WebSocket from "ws"; + +const ws = new WebSocket("ws://localhost:8080"); + +ws.on("open", () => { + console.log("✅ 已连接 WebSocket"); + + ws.send(JSON.stringify({ type: "auth", app: "demo" })); + + ws.send(JSON.stringify({ type: "subscribe", channel: "chat" })); + + setTimeout(() => { + ws.send( + JSON.stringify({ + type: "publish", + app: "demo", + channel: "chat", + event: "message", + data: { text: "Hello Redis WS 🚀" }, + }) + ); + }, 2000); +}); + +ws.on("message", (msg) => { + console.log("📩 收到消息:", msg.toString()); +}); diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..559bfa1 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,20 @@ +services: + websocket: + build: ./server + container_name: my_websocket + ports: + - "8080:8080" + environment: + - REDIS_HOST=${REDIS_HOST} + - REDIS_PORT=${REDIS_PORT} + - REDIS_PASSWORD=${REDIS_PASSWORD} + volumes: + - ./server:/app + - /app/node_modules + command: node index.js + networks: + - 1panel-network + +networks: + 1panel-network: + external: true diff --git a/docs/update-suggestion.md b/docs/update-suggestion.md new file mode 100644 index 0000000..96d7c0a --- /dev/null +++ b/docs/update-suggestion.md @@ -0,0 +1,123 @@ +# 🔧 项目升级与完善规划 + +本项目目前已实现 **WebSocket + Redis Pub/Sub** 的基础功能: + +* 客户端认证 +* 频道订阅与消息分发 +* Redis 支撑的多实例共享消息通道 + +虽然已可用于基本的实时通信场景,但仍有较大的优化与升级空间。以下为可行的改进方向: + +--- + +## 1️⃣ 架构层面优化 + +* **水平扩展** + + * 通过 **Kubernetes / Docker Swarm** 部署多个 WebSocket 节点,借助 Redis 保持消息一致性。 + * 支持 **负载均衡**(Nginx / HAProxy / Traefik),实现高并发环境下的稳定服务。 + +* **可插拔协议支持** + + * 除 WebSocket 外,增加 **HTTP SSE**(Server-Sent Events)作为备用方案,兼容不支持 WebSocket 的环境。 + * 未来可扩展为 **gRPC streaming** 以支持更强大的微服务交互。 + +--- + +## 2️⃣ 功能升级 + +* **频道管理** + + * 支持 **取消订阅 (unsubscribe)** 功能。 + * 增加频道权限控制(如仅允许部分用户订阅)。 + * 频道分组/命名空间机制,避免全局频道名冲突。 + +* **消息增强** + + * 增加 **消息持久化**(可选存储到 MongoDB/PostgreSQL),实现断线重连后消息回溯。 + * 支持 **延时消息 / 定时任务**(借助 Redis 的 TTL/Stream 功能)。 + * 引入 **QoS(消息投递保证)**:保证至少一次或仅一次投递。 + +* **客户端管理** + + * 增加 **心跳机制** 检测连接状态,避免僵尸连接占用资源。 + * 提供 **在线用户列表 API**,供业务层调用。 + * 支持 **多租户模式**(不同 `app` 间消息隔离)。 + +--- + +## 3️⃣ 安全性增强 + +* **认证与鉴权** + + * 使用 **JWT / OAuth2** 代替简单的 `auth` 消息,保证身份可信。 + * 支持 **API Key / Token** 校验,防止非法客户端连接。 + +* **传输安全** + + * 支持 **wss\:// (TLS)**,避免明文传输敏感数据。 + * 考虑接入 **Cloudflare Tunnel / Nginx TLS 反代**,增强边界安全。 + +* **防护机制** + + * 增加 **速率限制 (Rate Limit)**,防止恶意刷消息。 + * 监控异常行为,如频繁订阅/发布,触发封禁或警报。 + +--- + +## 4️⃣ 运维与监控 + +* **日志与监控** + + * 集成 **Prometheus + Grafana**,监控连接数、消息吞吐、Redis 状态等。 + * 提供 **健康检查 API**,供 Kubernetes 自动探测容器状态。 + +* **可观测性** + + * 增加 **请求追踪 ID**,方便日志关联与问题定位。 + * 统计消息流量、订阅情况,生成实时仪表盘。 + +--- + +## 5️⃣ 开发体验提升 + +* **SDK 封装** + + * 提供 JavaScript/TypeScript 客户端 SDK,封装常见操作(认证、订阅、发布、心跳)。 + * 后续可扩展到 **Python/Go/Java SDK**,方便跨语言接入。 + +* **测试与 CI/CD** + + * 增加单元测试与集成测试,确保消息收发正确性。 + * 通过 **GitHub Actions / GitLab CI** 自动构建与发布 Docker 镜像。 + +--- + +## 6️⃣ 未来扩展方向 + +* **消息队列升级** + + * 采用 **Redis Streams** 或 **Kafka**,支持高吞吐与持久化订阅。 + +* **服务对接** + + * 与 **业务 API / 数据库** 集成,实现业务事件实时推送(如订单状态更新、聊天消息分发)。 + +* **跨平台应用** + + * 提供 **Web / Mobile / Desktop** 多端接入方案。 + * 支持 **Unity/Unreal SDK**,扩展到游戏开发场景。 + +--- + +## 📌 总结 + +本项目的核心定位是一个 **轻量级的实时消息推送服务**。 +后续升级的重点在于: + +1. **稳定性**(扩展架构 & 健壮性) +2. **安全性**(认证、加密、防护) +3. **可观测性**(监控 & 日志) +4. **易用性**(SDK & 管理 API) + +通过以上优化,可以逐步从一个 **Demo 项目** 演进为 **可商用的分布式实时通信平台**。 diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..5e0e844 --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,20 @@ +# 使用 Node.js 官方镜像 +FROM node:20-alpine + +# 设置工作目录 +WORKDIR /app + +# 拷贝依赖清单 +COPY package*.json ./ + +# 安装依赖(只安装生产依赖即可) +RUN npm install --production + +# 拷贝源码 +COPY . . + +# 暴露 WebSocket 端口 +EXPOSE 8080 + +# 启动命令 +CMD ["node", "index.js"] diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..6dc0e12 --- /dev/null +++ b/server/index.js @@ -0,0 +1,80 @@ +import { WebSocketServer } from "ws"; +import Redis from "ioredis"; + +const redisSub = new Redis({ + host: process.env.REDIS_HOST || "main_redis", + port: process.env.REDIS_PORT || 6379, + password: process.env.REDIS_PASSWORD || "", +}); + +const redisPub = new Redis({ + host: process.env.REDIS_HOST || "main_redis", + port: process.env.REDIS_PORT || 6379, + password: process.env.REDIS_PASSWORD || "", +}); + +const PORT = 8080; +const wss = new WebSocketServer({ port: PORT }); + +const clients = new Map(); // ws => { app, channels: Set } + +function parseMessage(msg) { + try { + return JSON.parse(msg); + } catch { + return null; + } +} + +wss.on("connection", (ws) => { + console.log("👤 新客户端连接"); + clients.set(ws, { app: null, channels: new Set() }); + + ws.on("message", async (raw) => { + const msg = parseMessage(raw); + if (!msg) return; + + const { type, app, channel, event, data, token } = msg; + + if (type === "auth") { + clients.get(ws).app = app; + ws.send(JSON.stringify({ type: "auth_ok", app })); + return; + } + + if (type === "subscribe") { + clients.get(ws).channels.add(channel); + ws.send(JSON.stringify({ type: "subscribed", channel })); + return; + } + + if (type === "publish") { + const payload = { app, channel, event, data }; + await redisPub.publish(channel, JSON.stringify(payload)); + return; + } + }); + + ws.on("close", () => { + clients.delete(ws); + console.log("❌ 客户端断开"); + }); +}); + +// Redis 订阅所有频道 +redisSub.psubscribe("*", (err) => { + if (err) console.error("Redis 订阅失败", err); +}); + +// 分发消息 +redisSub.on("pmessage", (pattern, channel, message) => { + const payload = JSON.parse(message); + + for (const [ws, info] of clients) { + if (info.channels.has(channel)) { + ws.send(JSON.stringify(payload)); + } + } +}); + +console.log(`✅ WebSocket 服务运行在 ws://0.0.0.0:${PORT}`); diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..4b9c8fb --- /dev/null +++ b/server/package.json @@ -0,0 +1,18 @@ +{ + "name": "ws.tootaio.com", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "node index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.17.0", + "dependencies": { + "ioredis": "^5.7.0", + "ws": "^8.18.3" + } +}