欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Golang使用gin框架實(shí)現(xiàn)一個(gè)完整的聊天室功能

 更新時(shí)間:2023年08月14日 09:40:10   作者:paterl  
由于我們項(xiàng)目的需要,我就研究了一下關(guān)于websocket的相關(guān)內(nèi)容,去實(shí)現(xiàn)一個(gè)聊天室的功能,經(jīng)過(guò)幾天的探索,現(xiàn)在使用Gin框架實(shí)現(xiàn)了一個(gè)完整的聊天室+消息實(shí)時(shí)通知系統(tǒng),感興趣的小伙伴歡迎閱讀本文

用到的技術(shù)

websocket、gin、mysql、redis、協(xié)程、通道

實(shí)現(xiàn)思路

說(shuō)到聊天室可以有多種方法實(shí)現(xiàn),例如:使用單純的MySQL也可以實(shí)現(xiàn),但是為什么要選擇使用websocket去實(shí)現(xiàn)呢?有什么優(yōu)勢(shì)呢?

websocket是基于TCP/IP,獨(dú)立的HTTP協(xié)議的雙向通信協(xié)議,這就使實(shí)時(shí)的消息通知成為可能, 同時(shí)又符合Go高效處理高并發(fā)的語(yǔ)言特點(diǎn),結(jié)合聊天室又是高并發(fā)的,所以采取的室websocket進(jìn)行消息的轉(zhuǎn)接,MySQL持久化聊天消息,redis用于做一些判斷。

首先用戶(hù)在進(jìn)入App時(shí),客戶(hù)端和服務(wù)端建立一個(gè)websocket連接,并開(kāi)啟一個(gè)通道。

當(dāng)服務(wù)端收到客戶(hù)端的消息后,將消息寫(xiě)入通道里,服務(wù)端監(jiān)聽(tīng)通道的消息,并將消息取出,使用接收人的websocket連接將消息廣播到接收人那里。

實(shí)現(xiàn)代碼

下面開(kāi)始實(shí)現(xiàn):

創(chuàng)建模型,用于關(guān)系的確立及數(shù)據(jù)的傳輸

//數(shù)據(jù)庫(kù)存儲(chǔ)消息結(jié)構(gòu)體,用于持久化歷史記錄
type ChatMessage struct {
	gorm.Model
	Direction   string //這條消息是從誰(shuí)發(fā)給誰(shuí)的
	SendID      int    //發(fā)送者id
	RecipientID int    //接受者id
	GroupID     string //群id,該消息要發(fā)到哪個(gè)群里面去
	Content     string //內(nèi)容
	Read        bool   //是否讀了這條消息
}
//群聊結(jié)構(gòu)體
type Group struct {
	ID           string ` gorm:"primaryKey"` //群id
	CreatedAt    time.Time
	UpdatedAt    time.Time
	DeletedAt    gorm.DeletedAt `gorm:"index"`
	GroupName    string         `json:"group_name"`    //群名
	GroupContent string         `json:"group_content"` //群簽名
	GroupIcon    string         `json:"group_icon"`    //群頭像
	GroupNum     int            //群人數(shù)
	GroupOwnerId int            //群主id
	Users        []User         `gorm:"many2many:users_groups;"` //群成員
}
type UsersGroup struct {
	GroupId string `json:"group_id"`
	UserId  int    `json:"user_id"`
}
// 用于處理請(qǐng)求后返回一些數(shù)據(jù)
type ReplyMsg struct {
	From    string `json:"from"`
	Code    int    `json:"code"`
	Content string `json:"content"`
}
// 發(fā)送消息的類(lèi)型
type SendMsg struct {
	Type        int    `json:"type"`
	RecipientID int    `json:"recipient_id"` //接受者id
	Content     string `json:"content"`
}
// 用戶(hù)類(lèi)
type Client struct {
	ID          string          //消息的去向
	RecipientID int             //接受者id
	SendID      int             //發(fā)送人的id
	GroupID     string          //群聊id
	Socket      *websocket.Conn //websocket連接對(duì)象
	Send        chan []byte     //發(fā)送消息用的管道
}
// 廣播類(lèi),包括廣播內(nèi)容和源用戶(hù)
type Broadcast struct {
	Client  *Client
	Message []byte
	Type    int
}
// 用戶(hù)管理,用于管理用戶(hù)的連接及斷開(kāi)連接
type ClientManager struct {
	Clients    map[string]*Client
	Broadcast  chan *Broadcast
	Reply      chan *Client
	Register   chan *Client
	Unregister chan *Client
}
//創(chuàng)建一個(gè)用戶(hù)管理對(duì)象
var Manager = ClientManager{
	Clients:    make(map[string]*Client), // 參與連接的用戶(hù),出于性能的考慮,需要設(shè)置最大連接數(shù)
	Broadcast:  make(chan *Broadcast),
	Register:   make(chan *Client), //新建立的連接訪(fǎng)放入這里面
	Reply:      make(chan *Client),
	Unregister: make(chan *Client), //新斷開(kāi)的連接放入這里面
}

創(chuàng)建連接

func WsHandle(c *gin.Context) {
	myid := c.Query("myid")
	userid, err := strconv.Atoi(myid)
	if err != nil {
		zap.L().Error("轉(zhuǎn)換失敗", zap.Error(err))
		ResponseError(c, CodeParamError)
	}
	//將http協(xié)議升級(jí)為ws協(xié)議
	conn, err := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {
		return true
	}}).Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		http.NotFound(c.Writer, c.Request)
		return
	}
	//創(chuàng)建一個(gè)用戶(hù)客戶(hù)端實(shí)例,用于記錄該用戶(hù)的連接信息
	client := new(model.Client)
	client = &model.Client{
		ID:     myid + "->",
		SendID: userid,
		Socket: conn,
		Send:   make(chan []byte),
	}
	//使用管道將實(shí)例注冊(cè)到用戶(hù)管理上
	model.Manager.Register <- client
	//開(kāi)啟兩個(gè)協(xié)程用于讀寫(xiě)消息
	go Read(client)
	go Write(client)
}
//用于讀管道中的數(shù)據(jù)
func Read(c *model.Client) {
	//結(jié)束把通道關(guān)閉
	defer func() {
		model.Manager.Unregister <- c
		//關(guān)閉連接
		_ = c.Socket.Close()
	}()
	for {
		//先測(cè)試一下連接能不能連上
		c.Socket.PongHandler()
		sendMsg := new(model.SendMsg)
		err := c.Socket.ReadJSON(sendMsg)
		c.RecipientID = sendMsg.RecipientID
		if err != nil {
			zap.L().Error("數(shù)據(jù)格式不正確", zap.Error(err))
			model.Manager.Unregister <- c
			_ = c.Socket.Close()
			return
		}
		//根據(jù)要發(fā)送的消息類(lèi)型去判斷怎么處理
		//消息類(lèi)型的后端調(diào)度
		switch sendMsg.Type {
		case 1: //私信
			SingleChat(c, sendMsg)
		case 2: //獲取未讀消息
			UnreadMessages(c)
		case 3: //拉取歷史消息記錄
			HistoryMsg(c, sendMsg)
		case 4: //群聊消息廣播
			GroupChat(c, sendMsg)
		}
	}
}
//用于將數(shù)據(jù)寫(xiě)進(jìn)管道中
func Write(c *model.Client) {
	defer func() {
		_ = c.Socket.Close()
	}()
	for {
		select {
		//讀取管道里面的信息
		case message, ok := <-c.Send:
			//連接不到就返回消息
			if !ok {
				_ = c.Socket.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}
			fmt.Println(c.ID+"接收消息:", string(message))
			replyMsg := model.ReplyMsg{
				Code:    int(CodeConnectionSuccess),
				Content: fmt.Sprintf("%s", string(message)),
			}
			msg, _ := json.Marshal(replyMsg)
			//將接收的消息發(fā)送到對(duì)應(yīng)的websocket連接里
			rwLocker.Lock()
			_ = c.Socket.WriteMessage(websocket.TextMessage, msg)
			rwLocker.Unlock()
		}
	}
}

后端調(diào)度

//聊天的后端調(diào)度邏輯
//單聊
func SingleChat(c *model.Client, sendMsg *model.SendMsg) {
	//獲取當(dāng)前用戶(hù)發(fā)出到固定用戶(hù)的消息
	r1, _ := redis.REDIS.Get(context.Background(), c.ID).Result()
	//從redis中取出固定用戶(hù)發(fā)給當(dāng)前用戶(hù)的消息
	id := CreateId(strconv.Itoa(c.RecipientID), strconv.Itoa(c.SendID))
	r2, _ := redis.REDIS.Get(context.Background(), id).Result()
	//根據(jù)redis的結(jié)果去做未關(guān)注聊天次數(shù)限制
	if r2 >= "3" && r1 == "" {
		ResponseWebSocket(c.Socket, CodeLimiteTimes, "未相互關(guān)注,限制聊天次數(shù)")
		return
	} else {
		//將消息寫(xiě)入redis
		redis.REDIS.Incr(context.Background(), c.ID)
		//設(shè)置消息的過(guò)期時(shí)間
		_, _ = redis.REDIS.Expire(context.Background(), c.ID, time.Hour*24*30*3).Result()
	}
	fmt.Println(c.ID+"發(fā)送消息:", sendMsg.Content)
	//將消息廣播出去
	model.Manager.Broadcast <- &model.Broadcast{
		Client:  c,
		Message: []byte(sendMsg.Content),
	}
}
//查看未讀消息
func UnreadMessages(c *model.Client) {
	//獲取數(shù)據(jù)庫(kù)中的未讀消息
	msgs, err := mysql.GetMessageUnread(c.SendID)
	if err != nil {
		ResponseWebSocket(c.Socket, CodeServerBusy, "服務(wù)繁忙")
	}
	for i, msg := range msgs {
		replyMsg := model.ReplyMsg{
			From:    msg.Direction,
			Content: msg.Content,
		}
		message, _ := json.Marshal(replyMsg)
		_ = c.Socket.WriteMessage(websocket.TextMessage, message)
		//發(fā)送完后將消息設(shè)為已讀
		msgs[i].Read = true
		err := mysql.UpdateMessage(&msgs[i])
		if err != nil {
			ResponseWebSocket(c.Socket, CodeServerBusy, "服務(wù)繁忙")
		}
	}
}
//拉取歷史消息記錄
func HistoryMsg(c *model.Client, sendMsg *model.SendMsg) {
	//拿到傳過(guò)來(lái)的時(shí)間
	timeT := TimeStringToGoTime(sendMsg.Content)
	//查找聊天記錄
	//做一個(gè)分頁(yè)處理,一次查詢(xún)十條數(shù)據(jù),根據(jù)時(shí)間去限制次數(shù)
	//別人發(fā)給當(dāng)前用戶(hù)的
	direction := CreateId(strconv.Itoa(c.RecipientID), strconv.Itoa(c.SendID))
	//當(dāng)前用戶(hù)發(fā)出的
	id := CreateId(strconv.Itoa(c.SendID), strconv.Itoa(c.RecipientID))
	msgs, err := mysql.GetHistoryMsg(direction, id, timeT, 10)
	if err != nil {
		ResponseWebSocket(c.Socket, CodeServerBusy, "服務(wù)繁忙")
	}
	//把消息寫(xiě)給用戶(hù)
	for _, msg := range *msgs {
		replyMsg := model.ReplyMsg{
			From:    msg.Direction,
			Content: msg.Content,
		}
		message, _ := json.Marshal(replyMsg)
		_ = c.Socket.WriteMessage(websocket.TextMessage, message)
		//發(fā)送完后將消息設(shè)為已讀
		if err != nil {
			ResponseWebSocket(c.Socket, CodeServerBusy, "服務(wù)繁忙")
		}
	}
}
//群聊消息廣播
func GroupChat(c *model.Client, sendMsg *model.SendMsg) {
	//根據(jù)消息類(lèi)型判斷是否為群聊消息
	//先去數(shù)據(jù)庫(kù)查詢(xún)?cè)撊合碌乃杏脩?hù)
	users, err := mysql.GetAllGroupUser(strconv.Itoa(sendMsg.RecipientID))
	if err != nil {
		ResponseWebSocket(c.Socket, CodeServerBusy, "服務(wù)繁忙")
	}
	//向群里面的用戶(hù)廣播消息
	for _, user := range users {
		//獲取群里每個(gè)用戶(hù)的連接
		if int(user.ID) == c.SendID {
			continue
		}
		c.ID = strconv.Itoa(c.SendID) + "->"
		c.GroupID = strconv.Itoa(sendMsg.RecipientID)
		c.RecipientID = int(user.ID)
		model.Manager.Broadcast <- &model.Broadcast{
			Client:  c,
			Message: []byte(sendMsg.Content),
		}
	}
}

轉(zhuǎn)發(fā)消息

//用于在啟動(dòng)時(shí)進(jìn)行監(jiān)聽(tīng)
func Start(manager *model.ClientManager) {
	for {
		fmt.Println("<-----監(jiān)聽(tīng)通信管道----->")
		select {
		//監(jiān)測(cè)model.Manager.Register這個(gè)的變化,有新的東西加入管道時(shí)會(huì)被監(jiān)聽(tīng)到,從而建立連接
		case conn := <-model.Manager.Register:
			fmt.Println("建立新連接:", conn.ID)
			//將新建立的連接加入到用戶(hù)管理的map中,用于記錄連接對(duì)象,以連接人的id為鍵,以連接對(duì)象為值
			model.Manager.Clients[conn.ID] = conn
			//返回成功信息
			controller.ResponseWebSocket(conn.Socket, controller.CodeConnectionSuccess, "已連接至服務(wù)器")
		//斷開(kāi)連接,監(jiān)測(cè)到變化,有用戶(hù)斷開(kāi)連接
		case conn := <-model.Manager.Unregister:
			fmt.Println("連接失敗:", conn.ID)
			if _, ok := model.Manager.Clients[conn.ID]; ok {
				controller.ResponseWebSocket(conn.Socket, controller.CodeConnectionBreak, "連接已斷開(kāi)")
			}
			//關(guān)閉當(dāng)前用戶(hù)使用的管道
			//close(conn.Send)
			//刪除用戶(hù)管理中的已連接的用戶(hù)
			delete(model.Manager.Clients, conn.ID)
		case broadcast := <-model.Manager.Broadcast: //廣播消息
			message := broadcast.Message
			recipientID := broadcast.Client.RecipientID
			//給一個(gè)變量用于確定狀態(tài)
			flag := false
			contentid := createId(strconv.Itoa(broadcast.Client.SendID), strconv.Itoa(recipientID))
			rID := strconv.Itoa(recipientID) + "->"
			//遍歷客戶(hù)端連接map,查找該用戶(hù)有沒(méi)有在線(xiàn),判斷的是對(duì)方的連接例如:1要向2發(fā)消息,我現(xiàn)在是用戶(hù)1,那么我需要判斷2->1是否存在在用戶(hù)管理中
			for id, conn := range model.Manager.Clients {
				//如果找不到就說(shuō)明用戶(hù)不在線(xiàn),與接收人的id比較
				if id != rID {
					continue
				}
				//走到這一步,就說(shuō)明用戶(hù)在線(xiàn),就把消息放入管道里面
				select {
				case conn.Send <- message:
					flag = true
				default: //否則就把該連接從用戶(hù)管理中刪除
					close(conn.Send)
					delete(model.Manager.Clients, conn.ID)
				}
			}
			//判斷完之后就把將消息發(fā)給用戶(hù)
			if flag {
				fmt.Println("用戶(hù)在線(xiàn)應(yīng)答")
				controller.ResponseWebSocket(model.Manager.Clients[rID].Socket, controller.CodeConnectionSuccess, string(message))
				//把消息插到數(shù)據(jù)庫(kù)中
				msg := model.ChatMessage{
					Direction:   contentid,
					SendID:      broadcast.Client.SendID,
					RecipientID: recipientID,
					GroupID:     broadcast.Client.GroupID,
					Content:     string(message),
					Read:        true,
				}
				err := mysql.DB.Create(&msg).Error
				if err != nil {
					zap.L().Error("在線(xiàn)發(fā)送消息出現(xiàn)了錯(cuò)誤", zap.Error(err))
				}
			} else { //如果不在線(xiàn)
				controller.ResponseWebSocket(broadcast.Client.Socket, controller.CodeConnectionSuccess, "對(duì)方不在線(xiàn)")
				//把消息插到數(shù)據(jù)庫(kù)中
				msg := model.ChatMessage{
					Direction:   contentid,
					SendID:      broadcast.Client.SendID,
					RecipientID: recipientID,
					GroupID:     broadcast.Client.GroupID,
					Content:     string(message),
					Read:        false,
				}
				err := mysql.DB.Create(&msg).Error
				if err != nil {
					zap.L().Error("不在線(xiàn)發(fā)送消息出現(xiàn)了錯(cuò)誤", zap.Error(err))
				}
			}
		}
	}
}
func createId(uid, toUid string) string {
	return uid + "->" + toUid
}

到此這篇關(guān)于Golang使用gin框架實(shí)現(xiàn)一個(gè)完整的聊天室功能的文章就介紹到這了,更多相關(guān)Golang實(shí)現(xiàn)聊天室內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • golang中日期操作之日期格式化及日期轉(zhuǎn)換

    golang中日期操作之日期格式化及日期轉(zhuǎn)換

    在編程中,程序員會(huì)經(jīng)常使用到日期相關(guān)操作,下面這篇文章主要給大家介紹了關(guān)于golang中日期操作之日期格式化及日期轉(zhuǎn)換的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-11-11
  • Go中的動(dòng)態(tài)速率限制有效控制流量

    Go中的動(dòng)態(tài)速率限制有效控制流量

    這篇文章主要為大家介紹了Go中的動(dòng)態(tài)速率限制有效控制流量,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • Golang?Redis連接池實(shí)現(xiàn)原理及示例探究

    Golang?Redis連接池實(shí)現(xiàn)原理及示例探究

    這篇文章主要為大家介紹了Golang?Redis連接池實(shí)現(xiàn)示例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • golang string、int、int64 float 互相轉(zhuǎn)換方式

    golang string、int、int64 float 互相轉(zhuǎn)換方式

    這篇文章主要介紹了golang string、int、int64 float 互相轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • golang類(lèi)型斷言的實(shí)現(xiàn)示例

    golang類(lèi)型斷言的實(shí)現(xiàn)示例

    在Go語(yǔ)言中,類(lèi)型斷言用于從接口類(lèi)型獲取其具體類(lèi)型的值,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-10-10
  • Golang發(fā)送Get和Post請(qǐng)求的實(shí)現(xiàn)

    Golang發(fā)送Get和Post請(qǐng)求的實(shí)現(xiàn)

    做第三方接口有時(shí)需要用Get或者Post請(qǐng)求訪(fǎng)問(wèn),本文主要介紹了Golang發(fā)送Get和Post請(qǐng)求的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-05-05
  • go語(yǔ)言gin框架中間件詳解

    go語(yǔ)言gin框架中間件詳解

    在Go語(yǔ)言中,gin是一個(gè)常用的Web框架,用于構(gòu)建RESTful API和Web應(yīng)用程序。本文將通過(guò)代碼示例詳細(xì)介紹了gin框架中間件,感興趣的同學(xué)可以參考閱讀
    2023-04-04
  • 一文初探?Goroutine?與?channel基本用法

    一文初探?Goroutine?與?channel基本用法

    這篇文章主要為大家介紹了一文初探?Goroutine?與?channel基本用法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02
  • Golang的os標(biāo)準(zhǔn)庫(kù)中常用函數(shù)的整理介紹

    Golang的os標(biāo)準(zhǔn)庫(kù)中常用函數(shù)的整理介紹

    這篇文章主要介紹了Go語(yǔ)言的os標(biāo)準(zhǔn)庫(kù)中常用函數(shù),主要用來(lái)實(shí)現(xiàn)與操作系統(tǒng)的交互功能,需要的朋友可以參考下
    2015-10-10
  • GoLang 逃逸分析的機(jī)制詳解

    GoLang 逃逸分析的機(jī)制詳解

    這篇文章主要介紹了GoLang-逃逸分析的機(jī)制詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02

最新評(píng)論