I have a shopping app where users can post availability and other users can find them and add their availability.
I now have a chat service that is essentially for chatting. That is, customers can chat with the shopper to confirm details or other things. This chat should be one-on-one. So there might be 5 customers asking about a shopping post and I want the chat to be unique because customer A's chat about shopping should be separate from customer B's chat about the same shopping. Shoppers should be able to see the chat and respond.
This is what I currently have, but this seems to be broadcasting a message to everyone in the reference. I only want shoppers to receive messages from a specific sender without others having access to the chat.
"client.go"
type client struct { conn *websocket.conn chatrepository chat.chatrepository message chan *message id string `json:"id"` reference string `json:"reference"` username string `json:"username"` sender string `json:"sender"` recipient string `json:"recipient"` } type message struct { content string `json:"content"` reference string `json:"reference"` // username string `json:"username"` sender string `json:"sender"` } func (c *client) writemessage() { defer func() { c.conn.close() }() for { message, ok := <-c.message if !ok { return } uuid, err := uuid.newv4() if err != nil { log.fatalf("failed to generate uuid: %v", err) } chatmessage := chat.chatmessage{ id: uuid.string(), sender: message.sender, // recipient: recipient, timestamp: time.now(), content: message.content, } if c.sender == message.sender { _, errx := c.chatrepository.addmessage(message.reference, chatmessage) if err != nil { log.fatalf("failed to generate uuid: %v", errx) } } c.conn.writejson(chatmessage) } } func (c *client) readmessage(hub *hub) { defer func() { hub.unregister <- c c.conn.close() }() for { _, m, err := c.conn.readmessage() if err != nil { if websocket.isunexpectedcloseerror(err, websocket.closegoingaway, websocket.closeabnormalclosure) { log.printf("error: %v", err) } break } msg := &message{ content: string(m), reference: c.reference, sender: c.sender, // username: c.username, } hub.broadcast <- msg } }
“hub.go”
type room struct { id string `json:"id"` name string `json:"name"` clients map[string]*client `json:"clients"` } type hub struct { rooms map[string]*room register chan *client unregister chan *client broadcast chan *message emmiter events.emitter } func newhub(emmiter events.emitter) *hub { return &hub{ rooms: make(map[string]*room), register: make(chan *client), unregister: make(chan *client), broadcast: make(chan *message, 5), emmiter: emmiter, } } func (h *hub) run() { for { select { case cl := <-h.register: if _, ok := h.rooms[cl.reference]; ok { r := h.rooms[cl.reference] if _, ok := r.clients[cl.id]; !ok { r.clients[cl.id] = cl } } case cl := <-h.unregister: if _, ok := h.rooms[cl.reference]; ok { if _, ok := h.rooms[cl.reference].clients[cl.id]; ok { // if len(h.rooms[cl.reference].clients) != 0 { // h.broadcast <- &message{ // content: "user left the chat", // reference: cl.reference, // username: cl.username, // } // } delete(h.rooms[cl.reference].clients, cl.id) close(cl.message) } } case m := <-h.broadcast: if _, ok := h.rooms[m.reference]; ok { for _, cl := range h.rooms[m.reference].clients { cl.message <- m if m.sender != cl.recipient { notifications.sendpush(h.emmiter, cl.recipient, fmt.sprintf("new message from %v", cl.username), m.content) } } } } } }
"handler.go"
type handler struct { hub *hub chatrepository chat.chatrepository } func newhandler(h *hub, chatrepository chat.chatrepository) *handler { return &handler{ hub: h, chatrepository: chatrepository, } } var upgrader = websocket.upgrader{ readbuffersize: 1024, writebuffersize: 1024, checkorigin: func(r *http.request) bool { return true }, } func (h *handler) joinroom(c *gin.context) { conn, err := upgrader.upgrade(c.writer, c.request, nil) if err != nil { utils.handleerror(c, nil, "error creating chat connection", http.statusbadgateway) return } reference := c.param("reference") sender := c.query("sender") username := c.query("username") recipient := c.query("recipient") if reference == "" || sender == "" || username == "" || recipient == "" { utils.handleerror(c, nil, "required parameters missing", http.statusbadgateway) return } if _, ok := h.hub.rooms[reference]; !ok { // room doesn't exist, handle accordingly _, err1 := h.chatrepository.getchathistory(reference) if err1 != nil { log.printf("failed to retrieve chat history: %s", err1) errx := h.chatrepository.createchat(reference) if errx != nil { utils.handleerror(c, nil, "error storing connection", http.statusbadgateway) return } } h.hub.rooms[reference] = &room{ id: reference, name: sender, clients: make(map[string]*client), } } cl := &client{ conn: conn, chatrepository: h.chatrepository, message: make(chan *message, 10), id: sender, reference: reference, sender: sender, username: username, recipient: recipient, } h.hub.register <- cl go cl.writemessage() cl.readmessage(h.hub) }
"route.go"
hub := ws.newhub(events.neweventemitter(conn)) wshandler := ws.newhandler(hub, pr.newchatrepository(db, client)) go hub.run() v1.get("/chat/ws/:reference", g.guard([]string{"user", "admin", "dispatcher"}, nil), wshandler.joinroom)
"chat.model.go" type Chat struct { ID string `json:"id,omitempty" bson:"_id,omitempty"` Reference string `json:"reference" bson:"reference"` Messages []ChatMessage `json:"messages" bson:"messages"` } type ChatMessage struct { ID string `json:"id,omitempty" bson:"_id,omitempty"` Sender string `json:"sender" bson:"sender,omitempty"` Timestamp time.Time `json:"timestamp" bson:"timestamp,omitempty"` Content string `json:"content" bson:"content,omitempty"` }
The main reason your code broadcasts a message to everyone in the room with the same reference is that you are in hub
##broadcast The way messages are processed in the channel. In the current implementation, when a message is sent, it is forwarded to every client in the same room (i.e. with the same reference). This is done in the
run method of
hub:
case m := <-h.broadcast: if _, ok := h.rooms[m.reference]; ok { for _, cl := range h.rooms[m.reference].clients { cl.message <- m ...
If
reference is
postid and you want one-on-one communication between the shopper (the person who posted the availability post) and each customer, then you need to ensure that each Each chat is uniquely identifiable.
p>The unique key for each chat session should be the combination of
postid (
reference) and customer id (
sender). This ensures each customer has a unique chat session with the shopper in every post.
client structure to have a
chatid , which is a combination of
reference and
sender.
type client struct { ... chatid string `json:"chat_id"` }
hub to manage chat session maps (identified by
chatid) instead of rooms.
type hub struct { chats map[string]*chat ... }
chat is as follows:
type chat struct { shopper *client customer *client }
chatid.
broadcast logic:
case m := <-h.broadcast: chat, ok := h.chats[m.chatid] if ok { if m.sender == chat.customer.id { // message is from customer to shopper chat.shopper.message <- m } else if m.sender == chat.shopper.id { // message is from shopper to customer chat.customer.message <- m } }
m The variable is of type
*message and it has no
chatid field.
To resolve this issue, you should consider adding the
chatid field to the
message structure.
message structure:
type message struct { content string `json:"content"` chatid string `json:"chat_id"` sender string `json:"sender"` }
message in the
readmessage method of
client:
msg := &message{ content: string(m), chatid: c.chatid, sender: c.sender, }
chatID := reference + "_" + sender cl := &Client{ ... ChatID: chatID, } // If the chat session does not exist, create it. if _, ok := h.hub.Chats[chatID]; !ok { h.hub.Chats[chatID] = &Chat{ Customer: cl, Shopper: nil, // That will be set when the shopper joins. } } else { // That assumes only the customer or the shopper can join, not both at the same time. if h.hub.Chats[chatID].Shopper == nil { h.hub.Chats[chatID].Shopper = cl } else { h.hub.Chats[chatID].Customer = cl } }
The above is the detailed content of Chat one on one with golang socket. For more information, please follow other related articles on the PHP Chinese website!