💬 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.

WebSocket Socket.IO Real-time Node.js Redis
Sau tài liệu này bạn sẽ
  • 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
Dành cho
Developers, DevOps, Tech Leads muốn xây dựng chat real-time
Yêu cầu trước
Cơ bản về Node.js, Docker, WebSocket. Đã đọc tài liệu n8n
⏱️ Thời gian đọc
~25 phút

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:

🏠
Community Chat

Chat nhóm theo chủ đề — AI, N8N, Vibe Coding, Blockchain… Mọi thành viên có thể tham gia và trao đổi.

💬
Chat 1-1

Nhắn tin riêng tư giữa 2 thành viên. Có inbox, unread badges, lịch sử tin nhắn.

🎲
Random Chat

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.

Architecture Overview
┌─────────────────────────────────────────────────────┐
│                   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:

  1. User đăng nhập WordPress → nhận JWT chat token
  2. Frontend kết nối WebSocket tới wss://khonggianai.com/ws/
  3. Chat server verify JWT qua WordPress REST API
  4. Messages được lưu vào MySQL, trạng thái online lưu Redis
  5. 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):

SQL — wp_kgai_chat_rooms
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;
SQL — wp_kgai_chat_messages
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;
SQL — wp_kgai_chat_members
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;
SQL — wp_kgai_chat_random
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:

Sequence Diagram
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:

PHP — inc/kgai-chat.php
// 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

RoomIconMô tả
#general💬Trao đổi chung, chào hỏi
#ai-tools🤖Công cụ AI, ChatGPT, Gemini, Claude
#n8n-automationN8N 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

JavaScript — Client 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:

  1. Client emit private:start với targetUserId
  2. Server tạo room private type (hoặc tìm room đã tồn tại)
  3. Cả 2 users được join vào room
  4. Messages hoạt động giống community nhưng room chỉ có 2 người
JavaScript — Private Chat Events
// 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:

Matching Algorithm
┌─────────────┐     ┌──────────────────┐     ┌─────────────┐
│   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
Node.js — Random Matching Service
// 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ápImplementation
AuthenticationJWT token, verify mỗi connection qua WP REST API
XSS PreventionSanitize HTML (DOMPurify), escape output
Rate LimitingMax 30 messages/phút/user (Redis counter + TTL)
Flood ProtectionDebounce typing events, max message 2000 chars
Report SystemNút báo cáo trong random chat, auto-ban sau 3 reports
Membership GateChỉ users đã đăng nhập mới được chat; VIP có thêm quyền
Connection LimitMax 3 concurrent connections/user
Node.js — Rate Limiter Middleware
// 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

Dockerfile — 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

YAML — docker-compose.prod.yml (thêm service)
  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

Nginx — thêm vào server block
# 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:

deploy.yml — updated script
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

PhaseTính năngTimelineStatus
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.