go-chat.使用Go基于WebSocket开发的web聊天应用。单聊,群聊。文字,图片,语音,视频消息,屏幕共享,剪切板图片,基于WebRTC的P2P语音通话,视频聊天。
go-chat.使用Go基于WebSocket开发的web聊天应用。单聊,群聊。文字,图片,语音,视频消息,屏幕共享,剪切板图片,基于WebRTC的P2P语音通话,视频聊天。
使用Go基于WebSocket的通讯聊天软件。
代码仓库 go中协程是非常轻量级的。在每个client接入的时候,为每一个client开启一个协程,能够在单机实现更大的并发。同时go的channel,可以非常完美的解耦client接入和消息的转发等操作。
通过go-chat,可以掌握channel的和Select的配合使用,ORM框架的使用,web框架Gin的使用,配置管理,日志操作,还包括proto buffer协议的使用,等一些列项目中常用的技术。
基于react,UI和基本组件是使用ant design。可以很方便搭建前端界面。
界面选择单页框架可以更加方便写聊天界面,比如像消息提醒,可以在一个界面接受到消息进行提醒,不会因为换页面或者查看其他内容影响消息接受。 前端代码仓库:
语音,文字,图片,视频消息
视频通话
屏幕共享
…
通过消息体能看出,消息大部分都是字符串或者整型类型。通过json就可以进行传输。那为什么要选择google的protocol buffer进行传输呢?
CREATE DATABASE go-chat;
创建表
将go-chat.sql里面的sql语句复制到控制台创建对应的表。
在user表里面添加初始化用户
手动添加用户或者通过前端进行注册用户。
初始用户名:user001,密码:user001
初始用户名:user002,密码:user002
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
docker build -t konenet/gochat:1.0 .
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"
docker-compose up -d
…
在根目录下执行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
如果修改了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
该文件是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。
### 上传录制的视频
上传语音同原理
* 获取视频调用权限。
* 通过mediaDevices获取视频流,或者音频流,或者屏幕分享的视频流。
* this.recorder.start(1000)设定每秒返回一段流。
* 通过MediaRecorder将流转换为二进制,存入dataChunks数组中。
* 松开按钮后,将dataChunks中的数据合成一段二进制。
* 通过FileReader,将blob转换为ArrayBuffer格式。
* 将ArrayBuffer内容转换为Uint8Array二进制,放在消息体。
* 通过protobuf将消息转换成对应协议。
* 通过socket进行传输。
* 最后,将本地的视频,音频追加到聊天框里面。
**特别注意: 获取视频,音频,屏幕分享调用权限,必须是https协议或者是localhost,127.0.0.1 本地IP地址,所有本地测试可以开启几个浏览器,或者分别用这两个本地IP进行2tab测试**
```javascript
/**
* 当按下按钮时录制视频
*/
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:
,
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 = []
}
No open issues yet, or sync has not completed.