[TOC]
Utilisez le logiciel de chat de communication basé sur WebSocket de Go.
Les coroutines dans le référentiel de code Go sont très légères. Lorsque chaque client est accessible, une coroutine est activée pour chaque client, ce qui peut atteindre une plus grande concurrence sur une machine autonome. Dans le même temps, GO Channel peut parfaitement découpler l'accès des clients et le transfert de messages et d'autres opérations.
Grâce à Go-chat, vous pouvez maîtriser l'utilisation de Channel et SELECT, l'utilisation de l'ORM Framework, l'utilisation du gin Framework Web, la gestion de la configuration, le fonctionnement du journal et d'autres technologies couramment utilisées dans certains projets.
Basé sur React, l'interface utilisateur et les composants de base sont utilisés avec la conception de la fourmi. Il peut être très pratique de créer l'interface frontale.
La sélection d'une seule trame de page sur l'interface rend plus pratique d'écrire une interface de chat. Par exemple, par exemple, les rappels de messages, vous pouvez recevoir des messages dans une interface pour les rappels, et l'acceptation des messages ne sera pas affectée en modifiant les pages ou en affichant un autre contenu. Référentiel de code frontal: https://github.com/kone-net/go-chat-web
Voix, texte, images, messages vidéo 
Appel vidéo
Partage d'écran
syntax = "proto3" ;
package protocol;
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
int32 messageType = 8 ; // 消息类型,1.单聊 2.群聊
string url = 9 ; // 图片,视频,语音的路径
string fileSuffix = 10 ; // 文件后缀,如果通过二进制头不能解析文件后缀,使用该后缀
bytes file = 11 ; // 如果是图片,文件,视频等的二进制
}Dans le corps du message, nous pouvons voir que la plupart des messages sont des chaînes ou des types entiers. La transmission peut être effectuée via JSON. Alors pourquoi choisir le tampon de protocole de Google pour la transmission?
Configuration de base de l'environnement GO ...
Tirez le code backend
git clone https://github.com/kone-net/go-chatAller au répertoire
cd go-chatDépendances requises pour tirer le programme
go mod downloadMySQL crée une base de données
CREATE DATABASE chat ;Modifier le fichier de configuration de la base de données
vim config.toml
[mysql]
host = " 127.0.0.1 "
name = " chat "
password = " root1234 "
port = 3306
table_prefix = " "
user = " root "
修改用户名user,密码password等信息。Créer une table
将chat.sql里面的sql语句复制到控制台创建对应的表。Ajouter un utilisateur initialisé dans la table utilisateur
手动添加用户。Exécuter le programme
go run cmd/main.goConfigurer React Basic Environment, tels que NodeJS ...
Tirez le code
git clone https://github.com/kone-net/go-chat-webAller au répertoire
cd go-chat-webDépendances de base pour l'installation frontale
npm installSi l'adresse backend ou le numéro de port doit être modifié, l'adresse backend doit être modifiée lorsque le serveur est en cours d'exécution.
修改src/chat/common/param/Params.jsx里面的IP_PORTLe port de démarrage par défaut pour le code frontal en cours d'exécution est de 3000
npm startAccéder au portail frontal
http://127.0.0.1:3000/login
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
├── 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 上传的文件等
Exécuter la commande de marque Mac dans le répertoire racine
make build-darwin
实际执行命令是Makefile下的
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o bin/chat cmd/main.goLinux
make build
实际执行命令是Makefile下的
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/chat cmd/main.goSi message.proto est modifié, vous devez recompiler et générer le fichier go correspondant. Exécuter dans le répertoire racine
make proto
实际执行命令是Makefile下的
protoc --gogo_out=. protocol/ * .protoSi le fichier Proto n'est pas installé localement, vous devez d'abord l'installer, sinon la commande protoC ne sera pas trouvée. Utilisation de Gogoprotobuf
Installez le fichier de bibliothèque Protobuf
go get github.com/golang/protobuf/protoInstaller Protoc-Gen-Gogo
go get github.com/gogo/protobuf/protoc-gen-gogoInstaller le fichier de bibliothèque Gogoprotobuf
go get github.com/gogo/protobuf/protoTester dans le répertoire racine:
protoc --gogo_out=. protocol/ * .protoLe front-end doit installer la bibliothèque de tampons protoc
npm install protobufjsGénérer le fichier JS de Protoc vers le répertoire
npx pbjs -t json-module -w commonjs -o src/chat/proto/proto.js src/chat/proto/ * .proto
src/chat/proto/proto.js 是生成的文件的目录路径及其文件名称
src/chat/proto/ * .proto 是自己写的字段等Ce fichier est une carte de routage du gin, qui fera des demandes d'obtention ordinaire et de mise à niveau vers la connexion à 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
}Cette partie met à niveau la demande à WebSocket.
// 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 ()
}Ce sont les trois canaux du serveur.
// 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 )
...
...
}
}
}Pour télécharger le fichier de presse-papiers, nous devons d'abord obtenir le fichier de presse-papiers. Comme dans le code suivant:
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 )
}Le même principe de téléchargement de voix
Remarque spéciale: Pour obtenir des autorisations d'appel vidéo, audio et à l'écran, ce doit être un protocole HTTPS ou localhost, 127.0.0.1 Adresse IP locale. Tous les tests locaux peuvent ouvrir plusieurs navigateurs ou utiliser ces deux IP locaux pour effectuer des tests 2TAB respectivement.
/**
* 当按下按钮时录制视频
*/
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 = [ ]
}