💬 Real-time Chat System
Hệ thống chat real-time cho cộng đồng KhongGianAI — Community Chat, nhắn tin 1-1, và ghép đôi ngẫu nhiên. Xây dựng trên Node.js + Socket.IO, tích hợp hoàn toàn với WordPress.
- Hiểu kiến trúc microservice chat server tách biệt khỏi WordPress
- Nắm cơ chế xác thực JWT giữa WordPress ↔ Chat Server
- Biết cách triển khai Community Chat, Private Chat 1-1, và Random Chat
- Hiểu cách tích hợp Docker + Nginx WebSocket proxy
- Nắm database schema và matching engine cho random chat
1. Tổng quan hệ thống
Hệ thống chat real-time của KhongGianAI được thiết kế với 3 tính năng chính:
Chat nhóm theo chủ đề — AI, N8N, Vibe Coding, Blockchain… Mọi thành viên có thể tham gia và trao đổi.
Nhắn tin riêng tư giữa 2 thành viên. Có inbox, unread badges, lịch sử tin nhắn.
Ghép đôi ngẫu nhiên với người lạ ẩn danh. Kết thúc bất cứ lúc nào, tìm người mới chỉ 1 click.
Tại sao tách thành microservice? WordPress xử lý tốt HTTP request nhưng không phù hợp cho WebSocket (persistent connections). Node.js + Socket.IO được thiết kế chính xác cho realtime, hỗ trợ auto-reconnect, room management, và chỉ tốn ~50-80MB RAM.
2. Kiến trúc Microservice
Chat server chạy như một Docker container riêng biệt, giao tiếp với WordPress qua REST API và chia sẻ cùng database MySQL + Redis.
┌─────────────────────────────────────────────────────┐
│ Nginx (Reverse Proxy) │
│ https://khonggianai.com │
│ ┌──────────────┐ ┌───────────────────┐ │
│ │ HTTP /* │ │ WebSocket /ws/* │ │
│ └──────┬───────┘ └────────┬──────────┘ │
└──────────┼─────────────────────┼────────────────────┘
│ │
┌─────▼──────┐ ┌──────▼──────┐
│ WordPress │ │ Chat Server │
│ PHP :8083 │◄────►│ Node :3030 │
│ │ REST │ Socket.IO │
└─────┬──────┘ API └──────┬──────┘
│ │
┌─────▼─────────────────────▼──────┐
│ Docker Network │
│ ┌──────────┐ ┌──────────────┐ │
│ │ MySQL 8 │ │ Redis Alpine │ │
│ │ :3306 │ │ Pub/Sub + │ │
│ │ │ │ Sessions │ │
│ └──────────┘ └──────────────┘ │
└──────────────────────────────────┘
Luồng hoạt động:
- User đăng nhập WordPress → nhận JWT chat token
- Frontend kết nối WebSocket tới
wss://khonggianai.com/ws/ - Chat server verify JWT qua WordPress REST API
- Messages được lưu vào MySQL, trạng thái online lưu Redis
- Redis Pub/Sub đảm bảo broadcast realtime tới tất cả clients
3. Công nghệ sử dụng
| Layer | Công nghệ | Lý do chọn |
|---|---|---|
| Runtime | Node.js 20 LTS | Event-driven, non-blocking I/O — lý tưởng cho WebSocket |
| WebSocket | Socket.IO 4.x | Auto-reconnect, fallback polling, rooms, namespaces |
| Database | MySQL 8.0 (existing) | Tái sử dụng DB WordPress, ACID compliance |
| Cache/Pub-Sub | Redis Alpine (existing) | Online tracking, random queue, Pub/Sub broadcast |
| Auth | JWT (jsonwebtoken) | Stateless, verify qua WordPress REST API |
| Container | Docker + Compose | Tích hợp vào stack hiện tại, deploy 1 lệnh |
| Proxy | Nginx | WebSocket upgrade, SSL termination |
⚠️ Lưu ý về tài nguyên: VPS hiện tại chỉ có 3.7GB RAM (753MB available). Chat server Node.js chỉ tốn ~50-80MB + 30MB cho 100 concurrent connections. Không nên dùng các giải pháp nặng như Rocket.Chat (~512MB) hoặc Matrix (~1GB).
4. Database Schema
4 bảng mới được thêm vào cùng database WordPress (wp_kgai_* prefix):
CREATE TABLE wp_kgai_chat_rooms (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
slug VARCHAR(100) UNIQUE NOT NULL,
name VARCHAR(200) NOT NULL,
description TEXT,
type ENUM('community','private','random') DEFAULT 'community',
icon VARCHAR(50) DEFAULT '💬',
max_members INT DEFAULT 0, -- 0 = unlimited
is_active TINYINT(1) DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
CREATE TABLE wp_kgai_chat_messages (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
room_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
message TEXT NOT NULL,
message_type ENUM('text','image','link','system') DEFAULT 'text',
reply_to BIGINT DEFAULT NULL,
is_deleted TINYINT(1) DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_room_created (room_id, created_at),
INDEX idx_user (user_id),
FOREIGN KEY (room_id) REFERENCES wp_kgai_chat_rooms(id)
) ENGINE=InnoDB;
CREATE TABLE wp_kgai_chat_members (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
room_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
role ENUM('member','admin','moderator') DEFAULT 'member',
last_read_at DATETIME DEFAULT NULL,
joined_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_room_user (room_id, user_id),
FOREIGN KEY (room_id) REFERENCES wp_kgai_chat_rooms(id)
) ENGINE=InnoDB;
CREATE TABLE wp_kgai_chat_random (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_1 BIGINT NOT NULL,
user_2 BIGINT NOT NULL,
room_id BIGINT NOT NULL,
status ENUM('active','ended','reported') DEFAULT 'active',
started_at DATETIME DEFAULT CURRENT_TIMESTAMP,
ended_at DATETIME DEFAULT NULL,
FOREIGN KEY (room_id) REFERENCES wp_kgai_chat_rooms(id)
) ENGINE=InnoDB;
5. Authentication Flow (JWT)
Chat server không có database users riêng — nó xác thực qua WordPress REST API. Luồng:
Browser WordPress Chat Server Redis
│ │ │ │
│── Login ────────────►│ │ │
│◄── wp_cookie ────────│ │ │
│ │ │ │
│── GET /chat-token ──►│ │ │
│◄── JWT token ────────│ │ │
│ │ │ │
│── Connect WS + JWT ──────────────────────────►│ │
│ │◄── verify-token ──────│ │
│ │── { user_id, name } ──►│ │
│ │ │── store session ──►│
│◄────────────────── Connected ─────────────│ │
WordPress REST endpoint mới:
// Generate JWT cho authenticated user
register_rest_route('kgai/v1', '/chat-token', [
'methods' => 'GET',
'callback' => function() {
$user = wp_get_current_user();
$payload = [
'user_id' => $user->ID,
'name' => $user->display_name,
'avatar' => get_avatar_url($user->ID, ['size' => 80]),
'membership' => kgai_get_member($user->ID),
'exp' => time() + 86400 // 24h
];
return ['token' => kgai_jwt_encode($payload)];
},
'permission_callback' => 'is_user_logged_in'
]);
6. Community Chat
Community chat sử dụng Socket.IO rooms — mỗi chủ đề là một room. Users join/leave room, messages broadcast tới tất cả members trong room.
6.1 Danh sách rooms mặc định
| Room | Icon | Mô tả |
|---|---|---|
#general | 💬 | Trao đổi chung, chào hỏi |
#ai-tools | 🤖 | Công cụ AI, ChatGPT, Gemini, Claude |
#n8n-automation | ⚡ | N8N workflows, automation |
#vibe-coding | 💻 | Vibe coding, templates, dự án |
#blockchain | 🔗 | Web3, smart contracts, DeFi |
#help | 🆘 | Hỏi đáp, hỗ trợ kỹ thuật |
6.2 Socket Events
// Join room
socket.emit('room:join', { roomSlug: 'general' });
// Send message
socket.emit('message:send', {
roomId: 1,
message: 'Hello mọi người!',
type: 'text'
});
// Listen for new messages
socket.on('message:new', (data) => {
// { id, user: { id, name, avatar }, message, type, created_at }
renderMessage(data);
});
// Online users in room
socket.on('room:users', (users) => {
// [{ id, name, avatar }]
updateOnlineList(users);
});
// Typing indicator
socket.emit('typing:start', { roomId: 1 });
socket.on('typing:show', ({ user }) => showTyping(user));
7. Private Chat 1-1
Khi user A muốn nhắn tin riêng cho user B:
- Client emit
private:startvớitargetUserId - Server tạo room
privatetype (hoặc tìm room đã tồn tại) - Cả 2 users được join vào room
- Messages hoạt động giống community nhưng room chỉ có 2 người
// Start private conversation
socket.emit('private:start', { targetUserId: 42 });
socket.on('private:room', ({ roomId }) => {
// Navigate to private chat room
openPrivateChat(roomId);
});
// Get inbox (list of private conversations)
socket.emit('inbox:list');
socket.on('inbox:data', (conversations) => {
// [{ roomId, user: {...}, lastMessage, unreadCount, updatedAt }]
renderInbox(conversations);
});
8. Random Chat & Matching Engine
Random chat sử dụng Redis Queue (FIFO) để ghép đôi:
┌─────────────┐ ┌──────────────────┐ ┌─────────────┐
│ User A │────►│ Redis Queue │◄────│ User B │
│ "Tìm" │ │ FIFO matching │ │ "Tìm" │
└─────────────┘ └────────┬─────────┘ └─────────────┘
│
┌────────▼─────────┐
│ Match Found! │
│ Create room │
│ Notify both │
└──────────────────┘
Flow:
1. User A clicks "Tìm người" → RPUSH chat:random:queue userA
2. User B clicks "Tìm người" → LPOP chat:random:queue → gets userA
3. Server creates temporary room (type: random)
4. Both users auto-join room → start chatting
5. Either user clicks "Kết thúc" → room destroyed
6. Click "Tìm tiếp" → back to step 1
// services/matching.js
async function findMatch(userId) {
const redis = getRedis();
const queueKey = 'chat:random:queue';
// Try to find someone waiting
const matchedUser = await redis.lpop(queueKey);
if (matchedUser && matchedUser !== String(userId)) {
// Match found! Create room
const room = await createRoom({
slug: `random-${Date.now()}`,
type: 'random',
name: 'Random Chat'
});
return { matched: true, roomId: room.id, partnerId: matchedUser };
}
// No match, add to queue with 30s TTL
await redis.rpush(queueKey, String(userId));
await redis.expire(queueKey, 30);
return { matched: false, waiting: true };
}
⚠️ Safety: Random chat luôn có nút Báo cáo. User bị report 3 lần sẽ bị ban khỏi random chat 24h. Messages trong random chat không lưu vĩnh viễn — auto-delete sau 24h.
9. Bảo mật & Rate Limiting
| Biện pháp | Implementation |
|---|---|
| Authentication | JWT token, verify mỗi connection qua WP REST API |
| XSS Prevention | Sanitize HTML (DOMPurify), escape output |
| Rate Limiting | Max 30 messages/phút/user (Redis counter + TTL) |
| Flood Protection | Debounce typing events, max message 2000 chars |
| Report System | Nút báo cáo trong random chat, auto-ban sau 3 reports |
| Membership Gate | Chỉ users đã đăng nhập mới được chat; VIP có thêm quyền |
| Connection Limit | Max 3 concurrent connections/user |
// middleware/ratelimit.js
const rateLimits = new Map();
function checkRateLimit(userId) {
const key = `rate:${userId}`;
const now = Date.now();
const windowMs = 60000; // 1 minute
const maxMessages = 30;
if (!rateLimits.has(key)) {
rateLimits.set(key, []);
}
const timestamps = rateLimits.get(key)
.filter(t => now - t < windowMs);
if (timestamps.length >= maxMessages) {
return false; // Rate limited
}
timestamps.push(now);
rateLimits.set(key, timestamps);
return true;
}
10. Docker & CI/CD Deployment
10.1 Chat Server Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY src/ ./src/
EXPOSE 3030
CMD ["node", "src/index.js"]
10.2 Docker Compose Update
chat:
build:
context: ./chat-server
dockerfile: Dockerfile
container_name: kgai_chat
restart: always
depends_on:
- db
- redis
environment:
NODE_ENV: production
REDIS_URL: redis://redis:6379
MYSQL_HOST: db
MYSQL_USER: ${MYSQL_USER:-wordpress}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-wordpress}
MYSQL_DATABASE: ${MYSQL_DATABASE:-wordpress}
WP_API_URL: http://wordpress/wp-json/kgai/v1
JWT_SECRET: ${JWT_SECRET}
ports:
- "3030:3030"
networks:
- kgai_network
10.3 Nginx WebSocket Config
# WebSocket proxy cho Chat Server
location /ws/ {
proxy_pass http://127.0.0.1:3030/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 86400; # 24h keep-alive
}
10.4 GitHub Actions
CI/CD pipeline tự deploy khi push lên main. Chat server sẽ được build cùng lúc với WordPress:
script: |
cd /var/www/html/wp-kgai
git fetch origin main
git reset --hard origin/main
# Build & restart ALL services (including chat)
docker compose -f docker-compose.prod.yml down
docker compose -f docker-compose.prod.yml up -d --build
# Run chat DB migrations
docker exec kgai_chat node src/migrate.js 2>/dev/null || true
docker image prune -f
11. Roadmap phát triển
| Phase | Tính năng | Timeline | Status |
|---|---|---|---|
| Phase 1 | Chat server + DB + Auth + Basic room | 3-5 ngày | ⏳ Planned |
| Phase 2 | Community Chat UI + Rooms + History | 3-4 ngày | Backlog |
| Phase 3 | Private Chat 1-1 + Inbox + Notifications | 2-3 ngày | Backlog |
| Phase 4 | Random Chat + Matching + Report | 2-3 ngày | Backlog |
| Phase 5 | Polish: Dark/Light mode, Emoji, Responsive | 2 ngày | Backlog |
Estimated RAM: Chat server ~50-80MB + 30MB cho 100 concurrent users. Tổng thêm ~85-115MB — hoàn toàn khả thi với VPS hiện tại (753MB available). Có thể scale tới 500+ concurrent users trước khi cần nâng cấp.