使用Golang构建带有REST API连接的聊天应用程序

使用Golang构建带有REST API连接的聊天应用程序 我开发了一个聊天应用程序。我正在寻找一个扩展算法。首先我有一个前端部分,是React应用。用户访问localhost/room/roomid,我会获取房间ID和存储在本地的token。然后我用token和房间ID触发websocket连接,这样我就可以将用户分配到不同的房间。这部分没问题。

但我的目标是:首先通过普通API获取最后10条消息,然后将其显示在屏幕上。接着正常通过socket接收新消息,使用append方法添加并保存到数据库中。请问这种做法是否正确?

// 代码示例
func main() {
    fmt.Println("hello world")
}

更多关于使用Golang构建带有REST API连接的聊天应用程序的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

这个问题与Go语言有什么关系?

更多关于使用Golang构建带有REST API连接的聊天应用程序的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你对Go服务器代码有什么问题吗?

使用Go编程语言开发我的服务器

要让ReactJS与Golang进行通信,你需要创建返回JSON的Golang API。那么,你是在寻找实现这个功能的代码吗?

这是一个非常合理的架构设计。你的方法遵循了现代实时聊天应用的最佳实践,我来详细解释并提供Go语言实现示例。

架构设计分析

你的方案是正确的:

  1. 初始加载:通过REST API获取历史消息,避免websocket连接建立期间的延迟
  2. 实时更新:通过WebSocket接收新消息并实时显示
  3. 数据持久化:所有消息都保存到数据库

Go语言实现示例

1. REST API端点(获取历史消息)

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strconv"
    
    "github.com/gorilla/mux"
)

type Message struct {
    ID        string `json:"id"`
    RoomID    string `json:"roomId"`
    UserID    string `json:"userId"`
    Content   string `json:"content"`
    Timestamp int64  `json:"timestamp"`
}

// 获取房间最后N条消息
func GetRoomMessages(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    roomID := vars["roomId"]
    
    // 验证token和房间权限
    token := r.Header.Get("Authorization")
    if !validateToken(token, roomID) {
        http.Error(w, "Unauthorized", http.StatusUnauthorized)
        return
    }
    
    // 从查询参数获取消息数量,默认10条
    limitStr := r.URL.Query().Get("limit")
    limit := 10
    if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 {
        limit = parsedLimit
    }
    
    // 从数据库获取消息
    messages, err := getMessagesFromDB(roomID, limit)
    if err != nil {
        http.Error(w, "Failed to fetch messages", http.StatusInternalServerError)
        return
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(messages)
}

func validateToken(token, roomID string) bool {
    // 实现token验证逻辑
    return true
}

func getMessagesFromDB(roomID string, limit int) ([]Message, error) {
    // 实现数据库查询逻辑
    // 示例返回假数据
    return []Message{
        {
            ID:        "1",
            RoomID:    roomID,
            UserID:    "user1",
            Content:   "Hello World",
            Timestamp: 1633046400,
        },
    }, nil
}

2. WebSocket处理(实时消息)

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "sync"
    
    "github.com/gorilla/mux"
    "github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
        return true // 生产环境需要严格检查
    },
}

type Client struct {
    conn     *websocket.Conn
    roomID   string
    userID   string
    send     chan []byte
}

type Room struct {
    clients    map[*Client]bool
    broadcast  chan []byte
    register   chan *Client
    unregister chan *Client
    mutex      sync.RWMutex
}

var rooms = make(map[string]*Room)

func HandleWebSocket(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    roomID := vars["roomId"]
    token := r.URL.Query().Get("token")
    
    // 验证token
    if !validateWebSocketToken(token, roomID) {
        http.Error(w, "Unauthorized", http.StatusUnauthorized)
        return
    }
    
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Printf("WebSocket upgrade failed: %v", err)
        return
    }
    
    // 获取或创建房间
    room := getOrCreateRoom(roomID)
    
    client := &Client{
        conn:   conn,
        roomID: roomID,
        userID: extractUserIDFromToken(token),
        send:   make(chan []byte, 256),
    }
    
    room.register <- client
    
    // 启动读写goroutine
    go client.writePump()
    go client.readPump()
}

func (c *Client) readPump() {
    defer func() {
        room := getOrCreateRoom(c.roomID)
        room.unregister <- c
        c.conn.Close()
    }()
    
    for {
        _, message, err := c.conn.ReadMessage()
        if err != nil {
            break
        }
        
        // 处理接收到的消息
        var msg Message
        if err := json.Unmarshal(message, &msg); err != nil {
            log.Printf("JSON unmarshal error: %v", err)
            continue
        }
        
        // 保存到数据库
        if err := saveMessageToDB(&msg); err != nil {
            log.Printf("Save message error: %v", err)
            continue
        }
        
        // 广播到房间
        room := getOrCreateRoom(c.roomID)
        room.broadcast <- message
    }
}

func (c *Client) writePump() {
    defer c.conn.Close()
    
    for {
        select {
        case message, ok := <-c.send:
            if !ok {
                c.conn.WriteMessage(websocket.CloseMessage, []byte{})
                return
            }
            
            if err := c.conn.WriteMessage(websocket.TextMessage, message); err != nil {
                return
            }
        }
    }
}

func (r *Room) run() {
    for {
        select {
        case client := <-r.register:
            r.mutex.Lock()
            r.clients[client] = true
            r.mutex.Unlock()
            
        case client := <-r.unregister:
            r.mutex.Lock()
            if _, ok := r.clients[client]; ok {
                delete(r.clients, client)
                close(client.send)
            }
            r.mutex.Unlock()
            
        case message := <-r.broadcast:
            r.mutex.RLock()
            for client := range r.clients {
                select {
                case client.send <- message:
                default:
                    close(client.send)
                    delete(r.clients, client)
                }
            }
            r.mutex.RUnlock()
        }
    }
}

func getOrCreateRoom(roomID string) *Room {
    if room, exists := rooms[roomID]; exists {
        return room
    }
    
    room := &Room{
        clients:    make(map[*Client]bool),
        broadcast:  make(chan []byte),
        register:   make(chan *Client),
        unregister: make(chan *Client),
    }
    
    rooms[roomID] = room
    go room.run()
    return room
}

func validateWebSocketToken(token, roomID string) bool {
    // 实现WebSocket token验证
    return true
}

func extractUserIDFromToken(token string) string {
    // 从token中提取用户ID
    return "user123"
}

func saveMessageToDB(message *Message) error {
    // 实现消息保存到数据库的逻辑
    log.Printf("Saving message to DB: %+v", message)
    return nil
}

3. 主函数和路由设置

package main

import (
    "log"
    "net/http"
    
    "github.com/gorilla/mux"
)

func main() {
    r := mux.NewRouter()
    
    // REST API路由
    r.HandleFunc("/api/rooms/{roomId}/messages", GetRoomMessages).Methods("GET")
    
    // WebSocket路由
    r.HandleFunc("/ws/rooms/{roomId}", HandleWebSocket)
    
    log.Println("Server starting on :8080")
    log.Fatal(http.ListenAndServe(":8080", r))
}

前端集成示例

你的React前端应该这样调用:

// 1. 首先调用REST API获取历史消息
const fetchHistory = async (roomId, token) => {
    const response = await fetch(`/api/rooms/${roomId}/messages?limit=10`, {
        headers: {
            'Authorization': token
        }
    });
    return await response.json();
};

// 2. 然后建立WebSocket连接接收实时消息
const connectWebSocket = (roomId, token, onMessage) => {
    const ws = new WebSocket(`ws://localhost:8080/ws/rooms/${roomId}?token=${token}`);
    
    ws.onmessage = (event) => {
        const message = JSON.parse(event.data);
        onMessage(message); // 使用append方法添加到UI
    };
    
    return ws;
};

这种架构确保了良好的用户体验:用户立即看到历史消息,同时实时接收新消息。所有消息都持久化到数据库,保证了数据完整性。

回到顶部