การดู : 292

22/04/2026 07:11น.

EP.35 การเก็บประวัติการแชทในระบบ WebSocket และ GraphQL Subscription

EP.35 การเก็บประวัติการแชทในระบบ WebSocket และ GraphQL Subscription

#Golang

#Go

#WebSocket API

#Chat Storage

#GraphQL API

#Chat Database

#Real-Time Chat

#GraphQL Subscriptions

#WebSocket

#Chat History

ทำไมต้องเก็บประวัติการแชท?

การเก็บประวัติการแชทช่วยให้ผู้ใช้สามารถดึงข้อมูลเก่า ดูประวัติข้อความ และใช้งานระบบแชทได้อย่างต่อเนื่อง ไม่ต้องสูญเสียข้อมูลเมื่อปิดแอปพลิเคชัน นอกจากนี้ยังช่วยให้ ระบบสามารถแสดงผลข้อความย้อนหลังได้ เช่น:

  • ดึงข้อความล่าสุดเมื่อผู้ใช้เข้าร่วมการสนทนา
  • โหลดประวัติแชทย้อนหลัง
  • บันทึกข้อความลงฐานข้อมูลเพื่อให้สามารถค้นหาได้ภายหลัง

 

โครงสร้างของระบบเก็บประวัติการแชท

  1. WebSocket Server - รับและส่งข้อความผ่าน WebSocket
  2. GraphQL API - จัดการการรับ-ส่งข้อความและเก็บข้อมูล
  3. Database (PostgreSQL / MongoDB) - ใช้เก็บประวัติการแชท
  4. GraphQL Subscription - ส่งข้อความใหม่ให้ผู้ใช้แบบเรียลไทม์

 

ติดตั้งไลบรารีที่จำเป็น

go get github.com/99designs/gqlgen
go get github.com/gorilla/websocket
go get github.com/jmoiron/sqlx

 

การตั้งค่าฐานข้อมูลสำหรับเก็บประวัติการแชท

ไฟล์ schema.sql

CREATE TABLE messages (
    id SERIAL PRIMARY KEY,
    content TEXT NOT NULL,
    sender TEXT NOT NULL,
    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

 

การสร้าง GraphQL Schema สำหรับเก็บประวัติแชท

ไฟล์ schema.graphql

type Query {
  messages: [Message!]!
}

type Mutation {
  sendMessage(content: String!, sender: String!): Message!
}

type Subscription {
  messageAdded: Message!
}

type Message {
  id: ID!
  content: String!
  sender: String!
  timestamp: String!
}

 

การสร้าง Resolver เพื่อเชื่อมต่อกับฐานข้อมูล

ไฟล์ resolver.go

package main

import (
    "context"
    "database/sql"
    "fmt"
    "sync"
    "time"

    _ "github.com/lib/pq"
)

type Message struct {
    ID        int       `json:"id"`
    Content   string    `json:"content"`
    Sender    string    `json:"sender"`
    Timestamp time.Time `json:"timestamp"`
}

type Resolver struct {
    mu          sync.Mutex
    db          *sql.DB
    subscribers map[string]chan Message
}

func NewResolver(db *sql.DB) *Resolver {
    return &Resolver{
        db:          db,
        subscribers: make(map[string]chan Message),
    }
}

func (r *Resolver) Query_messages(ctx context.Context) ([]Message, error) {
    rows, err := r.db.Query("SELECT id, content, sender, timestamp FROM messages ORDER BY timestamp DESC LIMIT 50")
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    var messages []Message
    for rows.Next() {
        var msg Message
        if err := rows.Scan(&msg.ID, &msg.Content, &msg.Sender, &msg.Timestamp); err != nil {
            return nil, err
        }
        messages = append(messages, msg)
    }
    return messages, nil
}

func (r *Resolver) Mutation_sendMessage(ctx context.Context, content string, sender string) (Message, error) {
    var message Message
    err := r.db.QueryRow("INSERT INTO messages (content, sender) VALUES ($1, $2) RETURNING id, content, sender, timestamp", content, sender).
        Scan(&message.ID, &message.Content, &message.Sender, &message.Timestamp)
    if err != nil {
        return Message{}, err
    }

    r.mu.Lock()
    for _, ch := range r.subscribers {
        ch <- message
    }
    r.mu.Unlock()
    return message, nil
}

func (r *Resolver) Subscription_messageAdded(ctx context.Context) (<-chan Message, error) {
    id := fmt.Sprintf("%d", len(r.subscribers))
    r.mu.Lock()
    ch := make(chan Message)
    r.subscribers[id] = ch
    r.mu.Unlock()

    go func() {
        <-ctx.Done()
        r.mu.Lock()
        delete(r.subscribers, id)
        close(ch)
        r.mu.Unlock()
    }()
    return ch, nil
}

 

สร้าง WebSocket Server และเชื่อมต่อกับฐานข้อมูล

ไฟล์ server.go

package main

import (
    "database/sql"
    "log"
    "net/http"
    "github.com/99designs/gqlgen/graphql/handler"
    "github.com/99designs/gqlgen/graphql/playground"
)

func main() {
    db, err := sql.Open("postgres", "user=postgres password=secret dbname=chat sslmode=disable")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    resolver := NewResolver(db)
    srv := handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: resolver}))

    http.Handle("/", playground.Handler("GraphQL Playground", "/query"))
    http.Handle("/query", srv)

    log.Println("Chat Server with Database running at http://localhost:8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

 

ท้าให้ลอง!

ลองเพิ่มฟีเจอร์ การค้นหาข้อความเก่า และ การกรองข้อความตามผู้ส่ง ในระบบแชทของคุณ


EP ถัดไป

ใน EP.36, เราจะเพิ่มระบบ User Authentication ให้กับ WebSocket Chat เพื่อควบคุมสิทธิ์การเข้าถึง 🚀