kone-net/go-chat

Go

alternative to SlackDiscordMicrosoft Teams

go-chat.使用Go基于WebSocket开发的web聊天应用。单聊,群聊。文字,图片,语音,视频消息,屏幕共享,剪切板图片,基于WebRTC的P2P语音通话,视频聊天。

gochatwebsocketchat-applicationgolangprotobufim
Star Growth
Stars
1.1k
Forks
238
Weekly Growth
+-1
Issues
1
5001k
Jan 2023Mar 2024Jun 2025Sep 2026
ArtifactsGo Modules
README

go-chat

使用Go基于WebSocket的通讯聊天软件。

功能列表

  • 登录注册
  • 修改头像
  • 群聊天
  • 群好友列表
  • 单人聊天
  • 添加好友
  • 添加群组
  • 文本消息
  • 剪切板图片
  • 图片消息
  • 文件发送
  • 语音消息
  • 视频消息
  • 屏幕共享(基于图片)
  • 视频通话(基于WebRTC的p2p视频通话)
  • 分布式部署(通过kafka全局消息队列,统一消息传递,可以水平扩展系统)

后端

代码仓库 go中协程是非常轻量级的。在每个client接入的时候,为每一个client开启一个协程,能够在单机实现更大的并发。同时go的channel,可以非常完美的解耦client接入和消息的转发等操作。

通过go-chat,可以掌握channel的和Select的配合使用,ORM框架的使用,web框架Gin的使用,配置管理,日志操作,还包括proto buffer协议的使用,等一些列项目中常用的技术。

后端技术和框架

  • web框架Gin
  • 长连接WebSocket
  • 日志框架Uber的zap
  • 配置管理viper
  • ORM框架gorm
  • 通讯协议Google的proto buffer
  • makefile 的编写
  • 数据库MySQL
  • 图片文件二进制操作

前端

基于react,UI和基本组件是使用ant design。可以很方便搭建前端界面。

界面选择单页框架可以更加方便写聊天界面,比如像消息提醒,可以在一个界面接受到消息进行提醒,不会因为换页面或者查看其他内容影响消息接受。 前端代码仓库https://github.com/kone-net/go-chat-web

前端技术和框架

  • React
  • Redux状态管理
  • AntDesign
  • proto buffer的使用
  • WebSocket
  • 剪切板的文件读取和操作
  • 聊天框发送文字显示底部
  • FileReader对文件操作
  • ArrayBuffer,Blob,Uint8Array之间的转换
  • 获取摄像头视频(mediaDevices)
  • 获取麦克风音频(Recorder)
  • 获取屏幕共享(mediaDevices)
  • WebRTC的p2p视频通话

截图

  • 语音,文字,图片,视频消息 go-chat-panel

  • 视频通话 video-chat

  • 屏幕共享 screen-share

消息协议

protocol buffer协议

syntax = "proto3";
package protocol;
option go_package = "pkg/protocol"; // 指定go的包名

message Message {
    string avatar = 1;       //头像
    string fromUsername = 2; // 发送消息用户的用户名
    string from = 3;         // 发送消息用户uuid
    string to = 4;           // 发送给对端用户的uuid
    string content = 5;      // 文本消息内容
    int32 contentType = 6;   // 消息内容类型:1.文字 2.普通文件 3.图片 4.音频 5.视频 6.语音聊天 7.视频聊天
    string type = 7;         // 消息传输类型:如果是心跳消息,该内容为heatbeat,在线视频或者音频为webrtc
    int32 messageType = 8;   // 消息类型,1.单聊 2.群聊
    string url = 9;          // 图片,视频,语音的路径
    string fileSuffix = 10;  // 文件后缀,如果通过二进制头不能解析文件后缀,使用该后缀
    bytes file = 11;         // 如果是图片,文件,视频等的二进制
}

选择协议原因

通过消息体能看出,消息大部分都是字符串或者整型类型。通过json就可以进行传输。那为什么要选择google的protocol buffer进行传输呢?

  1. 传输快 因为protobuf序列化后的大小是json的10分之一,是xml格式的20分之一,但是性能却是它们的5~100倍.
  2. 支持二进制 当我们看到消息体最后一个字段,是定义的bytes,二进制类型。 我们在传输图片,文件,视频等内容的时候,可以将文件直接通过socket消息进行传输。 当然我们也可以将文件先通过http接口上传后,然后返回路径,再通过socket消息进行传输。但是这样只能实现固定大小文件的传输,如果我们是语音电话,或者视频电话的时候,就不能传输流。

快速运行

MySQL创建数据库

CREATE DATABASE go-chat;

创建表

将go-chat.sql里面的sql语句复制到控制台创建对应的表。

在user表里面添加初始化用户

手动添加用户或者通过前端进行注册用户。
初始用户名:user001,密码:user001
初始用户名:user002,密码:user002

运行go程序

go环境的基本配置 ...

拉取后端代码

git clone https://github.com/kone-net/go-chat

进入目录

cd go-chat

拉取程序所需依赖

go mod download

修改数据库配置文件

修改数据库连接用户名,密码等信息。

vim config.toml

[mysql]
host = "127.0.0.1"
name = "go-chat"
password = "root1234"
port = 3306
table_prefix = ""
user = "root"

运行程序

go run cmd/main.go

运行前端代码

前端代码仓库https://github.com/kone-net/go-chat-web


分布式部署

  • 拉取代码 将代码拉取到服务器,运行make build构建后端代码。
  • 构建后端服务镜像 进入目录deployments/docker 通过目录下的Dockerfile构建镜像
docker build -t konenet/gochat:1.0 .
  • 部署服务 需要部署nginx进行反向代理,mysql保存数据,1个或者多个后端服务。
  • 在config.toml中配置分布式消息队列 将msgChannelType中的channelType修改为kafka,就为分布式消息队列。需要填写消息队列对应的地址和topic
appName = "chat_room"

[mysql]
host = "mysql8"
name = "go-chat-message"
password = "thepswdforroot"
port = 3306
tablePrefix = ""
user = "root"

[log]
level = "debug"
path = "logs/chat.log"

[staticPath]
filePath = "web/static/file/"

[msgChannelType]
channelType = "kafka"

kafkaHosts = "kafka:9092"
kafkaTopic = "go-chat-message"
  • 启动服务 通过deployments/docker下的docker-compose.yml进行启动。
docker-compose up -d
  • 注意:分布式部署后,上传的文件视频等,可能会因为负载到不同的机器上,导致文件找不到的情况,所以需要一个在线或者分布式文件服务器。

代码结构

├── Makefile             代码编译,打包,结构化等操作
├── README.md
├── api                  controller类,对外的接口,如添加好友,查找好友等。所有http请求的入口
│   └── v1
├── assets
│   └── screenshot       系统使用到的资源,markdown用到的截图文件
├── bin                  打包的二进制文件
├── chat.sql             整个项目的SQL
├── cmd
│   └── main.go          main函数入口,程序启动
├── config
│   └── toml_config.go   系统全局的配置文件配置类
├── config.toml          配置文件
├── deployments
│   └── docker           docker构建镜像,docker-compose.yml等文件
├── go.mod
├── go.sum
├── internal
│   ├── dao              数据库
│   ├── kafka            kafka消费者和生产者
│   ├── model            数据库模型,和表一一对应
│   ├── router           gin和controller类进行绑定
│   ├── server           WebSocket中消息的接受和转发的主要逻辑
│   └── service          调用的服务类
├── logs
├── pkg
│   ├── common           常量,工具类
│   ├── errors           封装的异常类
│   ├── global           封装的日志类,使用时不会出现第三方的包依赖
│   └── protocol         protoc buffer自动生成的文件,定义的protoc buffer字段
├── test
│   └── kafka_test.go
└── web
    └── static           上传的文件等

Makefile

程序打包

在根目录下执行make命令 mac

make build-darwin

实际执行命令是Makefile下的
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o bin/chat cmd/main.go

linux

make build

实际执行命令是Makefile下的
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/chat cmd/main.go

后端proto文件生成

如果修改了message.proto,就需要重新编译生成对应的go文件。 在根目录下执行

make proto

实际执行命令是Makefile下的
protoc --gogo_out=. protocol/*.proto
# https://github.com/protocolbuffers/protobuf-javascript/releases/tag/v4.0.0
# https://github.com/protocolbuffers/protobuf/releases

如果本地没有安装proto文件,需要先进行安装,不然找不到protoc命令。 使用gogoprotobuf

安装protobuf库文件

go get github.com/golang/protobuf/proto

安装protoc-gen-go

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

在根目录测试:

protoc --go_out=. pkg/protocol/*.proto

代码说明

WebSocket

该文件是gin的路由映射,将普通的get请求,Upgrader为socket连接

// router/router.go
func NewRouter() *gin.Engine {
 gin.SetMode(gin.ReleaseMode)

 server := gin.Default()
 server.Use(Cors())
 server.Use(Recovery)

 socket := RunSocekt

 group := server.Group("")
 {
        ...

  group.GET("/socket.io", socket)
 }
 return server
}

这部分对请求进行升级为WebSocket。

  • c.Query("user")用户登录后,会获取用户的uuid,在连接到socket时会携带用户的uuid。
  • 通过该uuid和connection进行关联。
  • server.MyServer.Register <- client将每个client实例,通过channel进行传达,Server实例的Select会对该实例进行保存。
  • client.Read(),client.Write()通过协程让每个client对自己独有的channel进行消息的读取和发送
// router/socket.go
var upGrader = websocket.Upgrader{
 CheckOrigin: func(r *http.Request) bool {
  return true
 },
}

func RunSocekt(c *gin.Context) {
 user := c.Query("user")
 if user == "" {
  return
 }
 log.Info("newUser", zap.String("newUser", user))
 ws, err := upGrader.Upgrade(c.Writer, c.Request, nil) //升级协议为WebSocket
 if err != nil {
  return
 }

 client := &server.Client{
  Name: user,
  Conn: ws,
  Send: make(chan []byte),
 }

 server.MyServer.Register <- client
 go client.Read()
 go client.Write()
}

这是Server的三个channel,

  • 用户登录后,将用户和connection绑定存放在map中
  • 用户离线后,将用户从map中剔除
  • 所有消息,每个client将消息获取后放入该channel中,统一在这里进行消息的分发
  • 分发消息:
    • 如果是单聊,直接根据前端发送的uuid找到对应的client进行发送。
    • 如果是群聊,需要在数据库查询该群所有的成员,在根据uuid找到对应的client进行发送。
    • 如果消息为普通文本消息,可以直接转发到对应的客户端。
    • 如果消息为视频文件,普通文件,照片之类的,需要先将文件进行保存,然后返回文件名称,前端根据名称调用接口获取文件。
// server/server.go
func (s *Server) Start() {
 log.Info("start server", log.Any("start server", "start server..."))
 for {
  select {
  case conn := <-s.Register:
   log.Info("login", log.Any("login", "new user login in"+conn.Name))
   s.Clients[conn.Name] = conn
   msg := &protocol.Message{
    From:    "System",
    To:      conn.Name,
    Content: "welcome!",
   }
   protoMsg, _ := proto.Marshal(msg)
   conn.Send <- protoMsg

  case conn := <-s.Ungister:
   log.Info("loginout", log.Any("loginout", conn.Name))
   if _, ok := s.Clients[conn.Name]; ok {
    close(conn.Send)
    delete(s.Clients, conn.Name)
   }

  case message := <-s.Broadcast:
   msg := &protocol.Message{}
   proto.Unmarshal(message, msg)
            ...
            ...
  }
 }
}

剪切板图片上传

上传剪切板的文件,首先我们需要获取剪切板文件。 如以下代码:

  • 通过在聊天输入框,绑定粘贴命令,获取粘贴板的内容。
  • 我们只获取文件信息,其他文字信息过滤掉。
  • 先获取文件的blob格式。
  • 通过FileReader,将blob转换为ArrayBuffer格式。
  • 将ArrayBuffer内容转换为Uint8Array二进制,放在消息体。
  • 通过protobuf将消息转换成对应协议。
  • 通过socket进行传输。
  • 最后,将本地的图片追加到聊天框里面。
bindParse = () => {
        document.getElementById("messageArea").addEventListener("paste", (e) => {
            var data = e.clipboardData
            if (!data.items) {
                return;
            }
            var items = data.items

            if (null == items || items.length <= 0) {
                return;
            }

            let item = items[0]
            if (item.kind !== 'file') {
                return;
            }
            let blob = item.getAsFile()

            let reader = new FileReader()
            reader.readAsArrayBuffer(blob)

            reader.onload = ((e) => {
                let imgData = e.target.result

                // 上传文件必须将ArrayBuffer转换为Uint8Array
                let data = {
                    fromUsername: localStorage.username,
                    from: this.state.fromUser,
                    to: this.state.toUser,
                    messageType: this.state.messageType,
                    content: this.state.value,
                    contentType: 3,
                    file: new Uint8Array(imgData)
                }
                let message = protobuf.lookup("protocol.Message")
                const messagePB = message.create(data)
                socket.send(message.encode(messagePB).finish())

                this.appendImgToPanel(imgData)
            })

        }, false)
    }

上传录制的视频

上传语音同原理

  • 获取视频调用权限。
  • 通过mediaDevices获取视频流,或者音频流,或者屏幕分享的视频流。
  • this.recorder.start(1000)设定每秒返回一段流。
  • 通过MediaRecorder将流转换为二进制,存入dataChunks数组中。
  • 松开按钮后,将dataChunks中的数据合成一段二进制。
  • 通过FileReader,将blob转换为ArrayBuffer格式。
  • 将ArrayBuffer内容转换为Uint8Array二进制,放在消息体。
  • 通过protobuf将消息转换成对应协议。
  • 通过socket进行传输。
  • 最后,将本地的视频,音频追加到聊天框里面。

特别注意: 获取视频,音频,屏幕分享调用权限,必须是https协议或者是localhost,127.0.0.1 本地IP地址,所有本地测试可以开启几个浏览器,或者分别用这两个本地IP进行2tab测试

/**
     * 当按下按钮时录制视频
     */
    dataChunks = [];
    recorder = null;
    startVideoRecord = (e) => {
        navigator.getUserMedia = navigator.getUserMedia ||
            navigator.webkitGetUserMedia ||
            navigator.mozGetUserMedia ||
            navigator.msGetUserMedia; //获取媒体对象(这里指摄像头)

        let preview = document.getElementById("preview");
        this.setState({
            isRecord: true
        })

        navigator.mediaDevices
            .getUserMedia({
                audio: true,
                video: true,
            }).then((stream) => {
                preview.srcObject = stream;
                this.recorder = new MediaRecorder(stream);

                this.recorder.ondataavailable = (event) => {
                    let data = event.data;
                    this.dataChunks.push(data);
                };
                this.recorder.start(1000);
            });
    }

    /**
     * 松开按钮发送视频到服务器
     * @param {事件} e 
     */
    stopVideoRecord = (e) => {
        this.setState({
            isRecord: false
        })

        let recordedBlob = new Blob(this.dataChunks, { type: "video/webm" });

        let reader = new FileReader()
        reader.readAsArrayBuffer(recordedBlob)

        reader.onload = ((e) => {
            let fileData = e.target.result

            // 上传文件必须将ArrayBuffer转换为Uint8Array
            let data = {
                fromUsername: localStorage.username,
                from: this.state.fromUser,
                to: this.state.toUser,
                messageType: this.state.messageType,
                content: this.state.value,
                contentType: 3,
                file: new Uint8Array(fileData)
            }
            let message = protobuf.lookup("protocol.Message")
            const messagePB = message.create(data)
            socket.send(message.encode(messagePB).finish())
        })

        this.setState({
            comments: [
                ...this.state.comments,
                {
                    author: localStorage.username,
                    avatar: this.state.user.avatar,
                    content: <p><video src={URL.createObjectURL(recordedBlob)} controls autoPlay={false} preload="auto" width='200px' /></p>,
                    datetime: moment().fromNow(),
                },
            ],
        }, () => {
            this.scrollToBottom()
        })
        if (this.recorder) {
            this.recorder.stop()
            this.recorder = null
        }
        let preview = document.getElementById("preview");
        preview.srcObject.getTracks().forEach((track) => track.stop());
        this.dataChunks = []
    }
Related repositories
avelino/awesome-go

A curated list of awesome Go frameworks, libraries and software

GoGo ModulesawesomeMIT Licensegolanggolang-library
awesome-go.com
183.4k13.5k
ollama/ollama

Get up and running with Kimi-K2.6, GLM-5.2, MiniMax, DeepSeek, gpt-oss, Qwen, Gemma and other models.

GoGo ModulesMIT Licensellamallm
ollama.com
180.3k17.7k
golang/go

The Go programming language

GoGo ModulesBSD 3-Clause "New" or "Revised" Licenseprogramming-languagelanguage
go.dev
137.6k19.4k
kubernetes/kubernetes

Production-Grade Container Scheduling and Management

GoGo ModulesApache License 2.0kubernetesgo
kubernetes.io
126.4k44k
fatedier/frp

A fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet.

GoGo ModulesApache License 2.0proxyreverse-proxy
109.2k15.2k
gohugoio/hugo

The world’s fastest framework for building websites.

GoGo ModuleslibraryApache License 2.0gohugo
gohugo.io
89.7k8.4k
gin-gonic/gin

Gin is a high-performance HTTP web framework written in Go. It provides a Martini-like API but with significantly better performance—up to 40 times faster—thanks to httprouter. Gin is designed for building REST APIs, web applications, and microservices.

GoGo ModuleslibraryMIT Licenseservermiddleware
gin-gonic.com
89.2k8.7k
syncthing/syncthing

Open Source Continuous File Synchronization

GoGo ModulesappMozilla Public License 2.0synchronizationgo
syncthing.net
88.4k5.5k
junegunn/fzf

:cherry_blossom: A command-line fuzzy finder

GoGo ModulescliMIT Licensefzfgo
junegunn.github.io/fzf/
82.8k2.9k
grafana/grafana

The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.

TypeScriptnpmGNU Affero General Public License v3.0grafanamonitoring
grafana.com
76.6k14.7k
caddyserver/caddy

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

GoGo ModulesApache License 2.0goweb-server
caddyserver.com
75.5k4.9k
moby/moby

The Moby Project - a collaborative project for the container ecosystem to assemble container-based systems

GoGo ModulesApache License 2.0dockercontainers
mobyproject.org
72.1k19.2k