[TOC]
ใช้ซอฟต์แวร์แชทการสื่อสารที่ใช้ WebSocket
Coroutines ในที่เก็บรหัสไปมีน้ำหนักเบามาก เมื่อมีการเข้าถึงลูกค้าแต่ละรายจะเปิดใช้งาน coroutine สำหรับไคลเอนต์แต่ละรายซึ่งสามารถบรรลุพร้อมกันได้มากขึ้นในเครื่องสแตนด์อโลน ในเวลาเดียวกันช่อง GO สามารถแยกการเข้าถึงไคลเอนต์และการส่งต่อข้อความและการดำเนินการอื่น ๆ ได้อย่างสมบูรณ์แบบ
ผ่าน Go-Chat คุณสามารถควบคุมการใช้ Channel และเลือกการใช้ ORM Framework, การใช้ Web Framework Gin, การจัดการการกำหนดค่า, การดำเนินการบันทึกและเทคโนโลยีอื่น ๆ ที่ใช้กันทั่วไปในบางโครงการ
ขึ้นอยู่กับปฏิกิริยา UI และส่วนประกอบพื้นฐานใช้กับการออกแบบมด สามารถสร้างอินเทอร์เฟซส่วนหน้าได้สะดวกมาก
การเลือกเฟรมหน้าเดียวบนอินเทอร์เฟซทำให้สะดวกในการเขียนอินเทอร์เฟซแชทมากขึ้น ตัวอย่างเช่นตัวอย่างการแจ้งเตือนข้อความคุณสามารถรับข้อความในอินเทอร์เฟซเดียวสำหรับการแจ้งเตือนและการยอมรับข้อความจะไม่ได้รับผลกระทบจากการเปลี่ยนหน้าหรือดูเนื้อหาอื่น ๆ ที่เก็บรหัส front-end: https://github.com/kone-net/go-chat-web
เสียงข้อความรูปภาพข้อความวิดีโอ
โทรวิดีโอ 
การแบ่งปันหน้าจอ 
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 ; // 如果是图片,文件,视频等的二进制
}จากเนื้อหาข้อความเราจะเห็นว่าข้อความส่วนใหญ่เป็นสตริงหรือประเภทจำนวนเต็ม การส่งผ่านสามารถทำได้ผ่าน JSON แล้วทำไมต้องเลือกบัฟเฟอร์โปรโตคอลของ Google สำหรับการส่ง
การกำหนดค่าพื้นฐานของสภาพแวดล้อม GO ...
ดึงรหัสแบ็กเอนด์
git clone https://github.com/kone-net/go-chatไปที่ไดเรกทอรี
cd go-chatการพึ่งพาที่จำเป็นสำหรับการดึงโปรแกรม
go mod downloadMySQL สร้างฐานข้อมูล
CREATE DATABASE chat ;แก้ไขไฟล์กำหนดค่าฐานข้อมูล
vim config.toml
[mysql]
host = " 127.0.0.1 "
name = " chat "
password = " root1234 "
port = 3306
table_prefix = " "
user = " root "
修改用户名user,密码password等信息。สร้างตาราง
将chat.sql里面的sql语句复制到控制台创建对应的表。เพิ่มผู้ใช้ที่เริ่มต้นในตารางผู้ใช้
手动添加用户。เรียกใช้โปรแกรม
go run cmd/main.goกำหนดค่า react basic environment เช่น nodejs ...
ดึงรหัส
git clone https://github.com/kone-net/go-chat-webไปที่ไดเรกทอรี
cd go-chat-webการพึ่งพาพื้นฐานสำหรับการติดตั้ง front-end
npm installหากจำเป็นต้องแก้ไขที่อยู่แบ็กเอนด์หรือหมายเลขพอร์ตที่อยู่แบ็กเอนด์จะต้องแก้ไขเมื่อเซิร์ฟเวอร์ทำงานอยู่
修改src/chat/common/param/Params.jsx里面的IP_PORTพอร์ตเริ่มต้นเริ่มต้นสำหรับการเรียกใช้รหัสส่วนหน้าคือ 3000
npm startเข้าถึงพอร์ทัลส่วนหน้า
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 上传的文件等
ดำเนินการคำสั่ง make mac ในไดเรกทอรีรูท
make build-darwin
实际执行命令是Makefile下的
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o bin/chat cmd/main.goลินเวกซ์
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หากไฟล์ Proto ไม่ได้ติดตั้งในเครื่องคุณจะต้องติดตั้งก่อนมิฉะนั้นจะไม่พบคำสั่ง ProTOC ใช้ gogoprotobuf
ติดตั้งไฟล์ไลบรารี protobuf
go get github.com/golang/protobuf/protoติดตั้ง protoc-gogo
go get github.com/gogo/protobuf/protoc-gen-gogoติดตั้งไฟล์ไลบรารี Gogoprotobuf
go get github.com/gogo/protobuf/protoทดสอบในไดเรกทอรีราก:
protoc --gogo_out=. protocol/ * .protoส่วนหน้าจำเป็นต้องติดตั้งไลบรารีบัฟเฟอร์โปรโตค
npm install protobufjsสร้างไฟล์ JS ของ ProToc เป็นไดเรกทอรี
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 是自己写的字段等ไฟล์นี้เป็นแผนที่เส้นทางของจินซึ่งจะทำให้การร้องขอ GET ธรรมดาและอัพเกรดเป็นซ็อกเก็ตการเชื่อมต่อ
// 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
// 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/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 )
...
...
}
}
}ในการอัปโหลดไฟล์คลิปบอร์ดก่อนอื่นเราต้องได้รับไฟล์คลิปบอร์ด ในรหัสต่อไปนี้:
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 )
}หลักการเดียวกันของการอัปโหลดเสียง
หมายเหตุพิเศษ: ในการรับสิทธิ์วิดีโอเสียงและการแชร์หน้าจอต้องเป็นโปรโตคอล HTTPS หรือ LocalHost, 127.0.0.1 ที่อยู่ IP ท้องถิ่น การทดสอบในพื้นที่ทั้งหมดสามารถเปิดเบราว์เซอร์หลายตัวหรือใช้ IPs สองแห่งนี้เพื่อทำการทดสอบ 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 = [ ]
}