Go: IM系统接入ws进行消息发送以及群聊功能 (5)

07-21 1451阅读

概述

  • 在即时通讯(IM)系统中,实现多媒体消息(如文本、表情包、拍照、图片、音频、视频)的实时传输是一项核心功能
  • 随着HTML5和WebSocket技术的发展,现代Web应用能够支持更高效、更实时的通信方式
  • 本文将详细探讨如何使用Go语言结合WebSocket技术,在IM系统中实现多媒体消息的发送和接收

    基于MVC的目录设计

    im-project
    ├── go.mod
    ├── main.go          主程序
    ├── ctrl             控制器层
    │     └── chat.go
    ├── views            模板层
    │     └── chat
    │           ├── foot.html
    │           └── x.html
    

    主程序

    main.go 核心代码

    Go: IM系统接入ws进行消息发送以及群聊功能 (5)
    (图片来源网络,侵删)
    package main
    import (
    	"net/http"
    	"im-project/ctrl"
    )
    func main() {
    	// 1. 绑定请求和处理函数
    	http.HandleFunc("/chat", ctrl.Chat)
    	http.HandleFunc("/attach/upload", ctrl.Upload)
    	// 2. 指定目录的静态文件
    	http.Handle("/asset/",http.FileServer(http.Dir(".")))
    	http.Handle("/mnt/",http.FileServer(http.Dir(".")))
    	// 3. 启动
    	http.ListenAndServe(":80",nil)
    }
    

    控制器

    ctrl/attach.go

    package ctrl
    import (
    	"net/http"
    	"im-project/util"
    	"os"
    	"strings"
    	"fmt"
    	"time"
    	"math/rand"
    	"io"
    	"github.com/aliyun/aliyun-oss-go-sdk/oss"
    )
    func init(){
    	os.MkdirAll("./mnt",os.ModePerm)
    }
    func Upload(w http.ResponseWriter, r *http.Request){
    	//UploadLocal(w,r)
    	UploadOss(w,r)
    }
    // 1.存储位置 ./mnt,需要确保已经创建好
    // 2.url格式 /mnt/xxxx.png  需要确保网络能访问/mnt/
    func UploadLocal(writer http.ResponseWriter, request * http.Request) {
    	// 获得上传的源文件
        srcfile,head,err:=request.FormFile("file")
        if err!=nil{
        	util.RespFail(writer,err.Error())
    	}
    	// 创建一个新文件
    	suffix := ".png"
    	// 如果前端文件名称包含后缀 xx.xx.png
    	ofilename := head.Filename
    	tmp := strings.Split(ofilename,".")
    	if len(tmp)>1 {
    		suffix = "."+tmp[len(tmp)-1]
    	}
    	// 如果前端指定filetype
    	// formdata.append("filetype",".png")
    	filetype := request.FormValue("filetype")
    	if len(filetype)>0 {
    		suffix = filetype
    	}
    	// time.Now().Unix()
        filename := fmt.Sprintf("%d%04d%s", time.Now().Unix(), rand.Int31(), suffix)
        dstfile,err:= os.Create("./mnt/"+filename)
        if err!=nil {
        	util.RespFail(writer,err.Error())
        	return
    	}
    	// todo 将源文件内容copy到新文件
    	_,err = io.Copy(dstfile,srcfile)
    	if err!=nil{
    		util.RespFail(writer,err.Error())
    		return
    	}
    	// 将新文件路径转换成url地址
    	url := "/mnt/"+filename
    	// 响应到前端
    	util.RespOk(writer,url,"")
    }
    // 即将删掉,定期更新
    const (
    	AccessKeyId="5p2RZ******nMuQw9" // 填入自己的 key
    	AccessKeySecret="bsNmjU8Au08*****S5XIFAkK" // 填入自己的secret
    	EndPoint="oss-cn-shenzhen.aliyuncs.com"
    	Bucket="winliondev"
    )
    // 权限设置为公共读状态
    // 需要安装
    func UploadOss(writer http.ResponseWriter, request * http.Request) {
    	// 获得上传的文件
    	srcfile,head,err := request.FormFile("file")
    	if err!=nil {
    		util.RespFail(writer,err.Error())
    		return
    	}
    	// 获得文件后缀.png/.mp3
    	suffix := ".png"
    	//如果前端文件名称包含后缀 xx.xx.png
    	ofilename := head.Filename
    	tmp := strings.Split(ofilename,".")
    	if len(tmp)>1 {
    		suffix = "."+tmp[len(tmp)-1]
    	}
    	// 如果前端指定filetype
    	// formdata.append("filetype",".png")
    	filetype := request.FormValue("filetype")
    	if len(filetype)>0{
    		suffix = filetype
    	}
    	// 初始化ossclient
    	client,err:=oss.New(EndPoint,AccessKeyId,AccessKeySecret)
    	if err!=nil{
    		util.RespFail(writer,err.Error())
    		return
    	}
    	// todo 获得bucket
    	bucket,err := client.Bucket(Bucket)
    	if err!=nil{
    		util.RespFail(writer,err.Error())
    		return
    	}
    	// 设置文件名称
    	// time.Now().Unix()
    	filename := fmt.Sprintf("mnt/%d%04d%s", time.Now().Unix(), rand.Int31(), suffix)
    	// 通过bucket上传
    	err = bucket.PutObject(filename, srcfile)
    	if err!=nil {
    		util.RespFail(writer,err.Error())
    		return
    	}
    	// 获得url地址
    	url := "http://"+Bucket+"."+EndPoint+"/"+filename
    	// 响应到前端
    	util.RespOk(writer,url,"")
    }
    

    ctrl/chat.go

    package ctrl
    import (
    	"net/http"
    	"github.com/gorilla/websocket"
    	"gopkg.in/fatih/set.v0"
    	"sync"
    	"strconv"
    	"log"
    	"fmt"
    	"encoding/json"
    )
    const (
    	CMD_SINGLE_MSG = 10
    	CMD_ROOM_MSG   = 11
    	CMD_HEART      = 0
    )
    type Message struct {
    	Id      int64  `json:"id,omitempty" form:"id"` //消息ID
    	Userid  int64  `json:"userid,omitempty" form:"userid"` //谁发的
    	Cmd     int    `json:"cmd,omitempty" form:"cmd"` //群聊还是私聊
    	Dstid   int64  `json:"dstid,omitempty" form:"dstid"`//对端用户ID/群ID
    	Media   int    `json:"media,omitempty" form:"media"` //消息按照什么样式展示
    	Content string `json:"content,omitempty" form:"content"` //消息的内容
    	Pic     string `json:"pic,omitempty" form:"pic"` //预览图片
    	Url     string `json:"url,omitempty" form:"url"` //服务的URL
    	Memo    string `json:"memo,omitempty" form:"memo"` //简单描述
    	Amount  int    `json:"amount,omitempty" form:"amount"` //其他和数字相关的
    }
    /**
    消息发送结构体
    1、MEDIA_TYPE_TEXT
    {id:1,userid:2,dstid:3,cmd:10,media:1,content:"hello"}
    2、MEDIA_TYPE_News
    {id:1,userid:2,dstid:3,cmd:10,media:2,content:"标题",pic:"http://www.baidu.com/a/log,jpg",url:"http://www.a,com/dsturl","memo":"这是描述"}
    3、MEDIA_TYPE_VOICE,amount单位秒
    {id:1,userid:2,dstid:3,cmd:10,media:3,url:"http://www.a,com/dsturl.mp3",anount:40}
    4、MEDIA_TYPE_IMG
    {id:1,userid:2,dstid:3,cmd:10,media:4,url:"http://www.baidu.com/a/log,jpg"}
    5、MEDIA_TYPE_REDPACKAGR //红包amount 单位分
    {id:1,userid:2,dstid:3,cmd:10,media:5,url:"http://www.baidu.com/a/b/c/redpackageaddress?id=100000","amount":300,"memo":"恭喜发财"}
    6、MEDIA_TYPE_EMOJ 6
    {id:1,userid:2,dstid:3,cmd:10,media:6,"content":"cry"}
    7、MEDIA_TYPE_Link 6
    {id:1,userid:2,dstid:3,cmd:10,media:7,"url":"http://www.a,com/dsturl.html"}
    7、MEDIA_TYPE_Link 6
    {id:1,userid:2,dstid:3,cmd:10,media:7,"url":"http://www.a,com/dsturl.html"}
    8、MEDIA_TYPE_VIDEO 8
    {id:1,userid:2,dstid:3,cmd:10,media:8,pic:"http://www.baidu.com/a/log,jpg",url:"http://www.a,com/a.mp4"}
    9、MEDIA_TYPE_CONTACT 9
    {id:1,userid:2,dstid:3,cmd:10,media:9,"content":"10086","pic":"http://www.baidu.com/a/avatar,jpg","memo":"胡大力"}
    */
    // 本核心在于形成userid和Node的映射关系
    type Node struct {
    	Conn *websocket.Conn
    	//并行转串行,
    	DataQueue chan []byte
    	GroupSets set.Interface
    }
    // 映射关系表
    var clientMap map[int64]*Node = make(map[int64]*Node,0)
    // 读写锁
    var rwlocker sync.RWMutex
    // ws://127.0.0.1/chat?id=1&token=xxxx
    func Chat(writer http.ResponseWriter, request *http.Request) {
    	// 检验接入是否合法
        // checkToken(userId int64, token string)
        query := request.URL.Query()
        id := query.Get("id")
        token := query.Get("token")
        userId ,_ := strconv.ParseInt(id,10,64)
    	isvalida := checkToken(userId,token)
    	// 如果 isvalida = true
    	// isvalida = false
    	conn,err :=(&websocket.Upgrader {
    		CheckOrigin: func(r *http.Request) bool {
    			return isvalida
    		},
    	}).Upgrade(writer,request,nil)
    	
    	if err!=nil {
    		log.Println(err.Error())
    		return
    	}
    	//  获得conn
    	node := &Node {
    		Conn:conn,
    		DataQueue:make(chan []byte,50),
    		GroupSets:set.New(set.ThreadSafe),
    	}
    	// 获取用户全部群Id
    	comIds := contactService.SearchComunityIds(userId)
    	for _,v:=range comIds {
    		node.GroupSets.Add(v)
    	}
    	// userid和node形成绑定关系
    	rwlocker.Lock()
    	clientMap[userId] = node
    	rwlocker.Unlock()
    	// 完成发送逻辑, con
    	go sendproc(node)
    	// 完成接收逻辑
    	go recvproc(node)
    	sendMsg(userId,[]byte("hello,world!"))
    }
    //发送协程
    func sendproc(node *Node) {
    	for {
    		select {
    			case data:= 
    			    	log.Println(err.Error())
    			    	return
    				}
    		}
    	}
    }
    // 添加新的群ID到用户的groupset中
    func AddGroupId(userId,gid int64) {
    	// 取得node
    	rwlocker.Lock()
    	node,ok := clientMap[userId]
    	if ok {
    		node.GroupSets.Add(gid)
    	}
    	// clientMap[userId] = node
    	rwlocker.Unlock()
    	// 添加gid到set
    }
    // 接收协程
    func recvproc(node *Node) {
    	for {
    		_,data,err := node.Conn.ReadMessage()
    		if err!=nil {
    			log.Println(err.Error())
    			return
    		}
    		// 对data进一步处理
    		dispatch(data)
    		fmt.Printf("recv
VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]