|
|
|
|
@ -1,12 +1,9 @@
@@ -1,12 +1,9 @@
|
|
|
|
|
[<img src="https://api.gitsponsors.com/api/badge/img?id=429457754" height="20">](https://api.gitsponsors.com/api/badge/link?p=c/5vlYBJgIZjiZn7FQapOLz9nnCytHwTnVFIOXMfqp1+2UxziSw1w8gbJQFMDbw+GButn/N71zSgUnlb0NOkJXKefoukhE+kxSPlK5jGwtGVbRbFcWuBlWFFtkbXnA7QTQNPRtPDPpRH637lh1PfxQ==) |
|
|
|
|
# go-chat |
|
|
|
|
|
|
|
|
|
[TOC] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## go-chat |
|
|
|
|
使用Go基于WebSocket的通讯聊天软件。 |
|
|
|
|
|
|
|
|
|
### 功能列表: |
|
|
|
|
## 功能列表 |
|
|
|
|
|
|
|
|
|
* 登录注册 |
|
|
|
|
* 修改头像 |
|
|
|
|
* 群聊天 |
|
|
|
|
@ -25,13 +22,14 @@
@@ -25,13 +22,14 @@
|
|
|
|
|
* 分布式部署(通过kafka全局消息队列,统一消息传递,可以水平扩展系统) |
|
|
|
|
|
|
|
|
|
## 后端 |
|
|
|
|
|
|
|
|
|
[代码仓库](https://github.com/kone-net/go-chat) |
|
|
|
|
go中协程是非常轻量级的。在每个client接入的时候,为每一个client开启一个协程,能够在单机实现更大的并发。同时go的channel,可以非常完美的解耦client接入和消息的转发等操作。 |
|
|
|
|
|
|
|
|
|
通过go-chat,可以掌握channel的和Select的配合使用,ORM框架的使用,web框架Gin的使用,配置管理,日志操作,还包括proto buffer协议的使用,等一些列项目中常用的技术。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 后端技术和框架 |
|
|
|
|
|
|
|
|
|
* web框架Gin |
|
|
|
|
* 长连接WebSocket |
|
|
|
|
* 日志框架Uber的zap |
|
|
|
|
@ -43,14 +41,15 @@ go中协程是非常轻量级的。在每个client接入的时候,为每一个
@@ -43,14 +41,15 @@ go中协程是非常轻量级的。在每个client接入的时候,为每一个
|
|
|
|
|
* 图片文件二进制操作 |
|
|
|
|
|
|
|
|
|
## 前端 |
|
|
|
|
|
|
|
|
|
基于react,UI和基本组件是使用ant design。可以很方便搭建前端界面。 |
|
|
|
|
|
|
|
|
|
界面选择单页框架可以更加方便写聊天界面,比如像消息提醒,可以在一个界面接受到消息进行提醒,不会因为换页面或者查看其他内容影响消息接受。 |
|
|
|
|
[前端代码仓库](https://github.com/kone-net/go-chat-web): |
|
|
|
|
https://github.com/kone-net/go-chat-web |
|
|
|
|
|
|
|
|
|
<https://github.com/kone-net/go-chat-web> |
|
|
|
|
|
|
|
|
|
### 前端技术和框架 |
|
|
|
|
|
|
|
|
|
* React |
|
|
|
|
* Redux状态管理 |
|
|
|
|
* AntDesign |
|
|
|
|
@ -65,8 +64,8 @@ https://github.com/kone-net/go-chat-web
@@ -65,8 +64,8 @@ https://github.com/kone-net/go-chat-web
|
|
|
|
|
* 获取屏幕共享(mediaDevices) |
|
|
|
|
* WebRTC的p2p视频通话 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 截图 |
|
|
|
|
|
|
|
|
|
* 语音,文字,图片,视频消息 |
|
|
|
|
 |
|
|
|
|
|
|
|
|
|
@ -77,10 +76,13 @@ https://github.com/kone-net/go-chat-web
@@ -77,10 +76,13 @@ https://github.com/kone-net/go-chat-web
|
|
|
|
|
 |
|
|
|
|
|
|
|
|
|
## 消息协议 |
|
|
|
|
|
|
|
|
|
### protocol buffer协议 |
|
|
|
|
|
|
|
|
|
```go |
|
|
|
|
syntax = "proto3"; |
|
|
|
|
package protocol; |
|
|
|
|
option go_package = "pkg/protocol"; // 指定go的包名 |
|
|
|
|
|
|
|
|
|
message Message { |
|
|
|
|
string avatar = 1; //头像 |
|
|
|
|
@ -89,125 +91,117 @@ message Message {
@@ -89,125 +91,117 @@ message Message {
|
|
|
|
|
string to = 4; // 发送给对端用户的uuid |
|
|
|
|
string content = 5; // 文本消息内容 |
|
|
|
|
int32 contentType = 6; // 消息内容类型:1.文字 2.普通文件 3.图片 4.音频 5.视频 6.语音聊天 7.视频聊天 |
|
|
|
|
string type = 7; // 如果是心跳消息,该内容为heatbeat |
|
|
|
|
string type = 7; // 消息传输类型:如果是心跳消息,该内容为heatbeat,在线视频或者音频为webrtc |
|
|
|
|
int32 messageType = 8; // 消息类型,1.单聊 2.群聊 |
|
|
|
|
string url = 9; // 图片,视频,语音的路径 |
|
|
|
|
string fileSuffix = 10; // 文件后缀,如果通过二进制头不能解析文件后缀,使用该后缀 |
|
|
|
|
bytes file = 11; // 如果是图片,文件,视频等的二进制 |
|
|
|
|
} |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
### 选择协议原因 |
|
|
|
|
|
|
|
|
|
通过消息体能看出,消息大部分都是字符串或者整型类型。通过json就可以进行传输。那为什么要选择google的protocol buffer进行传输呢? |
|
|
|
|
* 一方面传输快 |
|
|
|
|
是因为protobuf序列化后的大小是json的10分之一,是xml格式的20分之一,但是性能却是它们的5~100倍. |
|
|
|
|
* 另一方面支持二进制 |
|
|
|
|
|
|
|
|
|
1. 传输快 |
|
|
|
|
因为protobuf序列化后的大小是json的10分之一,是xml格式的20分之一,但是性能却是它们的5~100倍. |
|
|
|
|
2. 支持二进制 |
|
|
|
|
当我们看到消息体最后一个字段,是定义的bytes,二进制类型。 |
|
|
|
|
我们在传输图片,文件,视频等内容的时候,可以将文件直接通过socket消息进行传输。 |
|
|
|
|
当然我们也可以将文件先通过http接口上传后,然后返回路径,再通过socket消息进行传输。但是这样只能实现固定大小文件的传输,如果我们是语音电话,或者视频电话的时候,就不能传输流。 |
|
|
|
|
|
|
|
|
|
## 快速运行 |
|
|
|
|
|
|
|
|
|
### MySQL创建数据库 |
|
|
|
|
|
|
|
|
|
```mysql |
|
|
|
|
CREATE DATABASE go-chat; |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
创建表 |
|
|
|
|
|
|
|
|
|
```shell |
|
|
|
|
将go-chat.sql里面的sql语句复制到控制台创建对应的表。 |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
在user表里面添加初始化用户 |
|
|
|
|
|
|
|
|
|
```shell |
|
|
|
|
手动添加用户或者通过前端进行注册用户。 |
|
|
|
|
初始用户名:user001,密码:user001 |
|
|
|
|
初始用户名:user002,密码:user002 |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
### 运行go程序 |
|
|
|
|
|
|
|
|
|
go环境的基本配置 |
|
|
|
|
... |
|
|
|
|
|
|
|
|
|
拉取后端代码 |
|
|
|
|
|
|
|
|
|
```shell |
|
|
|
|
git clone https://github.com/kone-net/go-chat |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
进入目录 |
|
|
|
|
|
|
|
|
|
```shell |
|
|
|
|
cd go-chat |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
拉取程序所需依赖 |
|
|
|
|
|
|
|
|
|
```shell |
|
|
|
|
go mod download |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
MySQL创建数据库 |
|
|
|
|
```mysql |
|
|
|
|
CREATE DATABASE chat; |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
修改数据库配置文件 |
|
|
|
|
|
|
|
|
|
修改数据库连接用户名,密码等信息。 |
|
|
|
|
|
|
|
|
|
```shell |
|
|
|
|
vim config.toml |
|
|
|
|
|
|
|
|
|
[mysql] |
|
|
|
|
host = "127.0.0.1" |
|
|
|
|
name = "chat" |
|
|
|
|
name = "go-chat" |
|
|
|
|
password = "root1234" |
|
|
|
|
port = 3306 |
|
|
|
|
table_prefix = "" |
|
|
|
|
user = "root" |
|
|
|
|
|
|
|
|
|
修改用户名user,密码password等信息。 |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
创建表 |
|
|
|
|
```shell |
|
|
|
|
将chat.sql里面的sql语句复制到控制台创建对应的表。 |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
在user表里面添加初始化用户 |
|
|
|
|
```shell |
|
|
|
|
手动添加用户。 |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
运行程序 |
|
|
|
|
|
|
|
|
|
```shell |
|
|
|
|
go run cmd/main.go |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
### 运行前端代码 |
|
|
|
|
配置React基本环境,比如nodejs |
|
|
|
|
... |
|
|
|
|
|
|
|
|
|
拉取代码 |
|
|
|
|
```shell |
|
|
|
|
git clone https://github.com/kone-net/go-chat-web |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
进入目录 |
|
|
|
|
```shell |
|
|
|
|
cd go-chat-web |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
安装前端基本依赖 |
|
|
|
|
```shell |
|
|
|
|
npm install |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
如果后端地址或者端口号需要修改 |
|
|
|
|
放在服务器运行时一定需要修改后端地址 |
|
|
|
|
```shell |
|
|
|
|
修改src/chat/common/param/Params.jsx里面的IP_PORT |
|
|
|
|
``` |
|
|
|
|
[前端代码仓库](https://github.com/kone-net/go-chat-web): |
|
|
|
|
<https://github.com/kone-net/go-chat-web> |
|
|
|
|
|
|
|
|
|
运行前端代码默认启动端口是3000 |
|
|
|
|
```shell |
|
|
|
|
npm start |
|
|
|
|
``` |
|
|
|
|
---- |
|
|
|
|
|
|
|
|
|
访问前端入口 |
|
|
|
|
``` |
|
|
|
|
http://127.0.0.1:3000/login |
|
|
|
|
``` |
|
|
|
|
## 分布式部署 |
|
|
|
|
|
|
|
|
|
### 分布式部署 |
|
|
|
|
* 拉取代码 |
|
|
|
|
将代码拉取到服务器,运行make build构建后端代码。 |
|
|
|
|
* 构建后端服务镜像 |
|
|
|
|
进入目录deployments/docker |
|
|
|
|
通过目录下的Dockerfile构建镜像 |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
```bash |
|
|
|
|
docker build -t konenet/gochat:1.0 . |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
* 部署服务 |
|
|
|
|
需要部署nginx进行反向代理,mysql保存数据,1个或者多个后端服务。 |
|
|
|
|
* 在config.toml中配置分布式消息队列 |
|
|
|
|
将msgChannelType中的channelType修改为kafka,就为分布式消息队列。需要填写消息队列对应的地址和topic |
|
|
|
|
|
|
|
|
|
```toml |
|
|
|
|
appName = "chat_room" |
|
|
|
|
|
|
|
|
|
@ -232,15 +226,19 @@ channelType = "kafka"
@@ -232,15 +226,19 @@ channelType = "kafka"
|
|
|
|
|
kafkaHosts = "kafka:9092" |
|
|
|
|
kafkaTopic = "go-chat-message" |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
* 启动服务 |
|
|
|
|
通过deployments/docker下的docker-compose.yml进行启动。 |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
```bash |
|
|
|
|
docker-compose up -d |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
* 注意:分布式部署后,上传的文件视频等,可能会因为负载到不同的机器上,导致文件找不到的情况,所以需要一个在线或者分布式文件服务器。 |
|
|
|
|
|
|
|
|
|
## 代码结构 |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
```text |
|
|
|
|
├── Makefile 代码编译,打包,结构化等操作 |
|
|
|
|
├── README.md |
|
|
|
|
├── api controller类,对外的接口,如添加好友,查找好友等。所有http请求的入口 |
|
|
|
|
@ -278,9 +276,12 @@ docker-compose up -d
@@ -278,9 +276,12 @@ docker-compose up -d
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
## Makefile |
|
|
|
|
### 程序打包 |
|
|
|
|
|
|
|
|
|
## 程序打包 |
|
|
|
|
|
|
|
|
|
在根目录下执行make命令 |
|
|
|
|
mac |
|
|
|
|
|
|
|
|
|
```bash |
|
|
|
|
make build-darwin |
|
|
|
|
|
|
|
|
|
@ -289,6 +290,7 @@ CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o bin/chat cmd/main.go
@@ -289,6 +290,7 @@ CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o bin/chat cmd/main.go
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
linux |
|
|
|
|
|
|
|
|
|
```bash |
|
|
|
|
make build |
|
|
|
|
|
|
|
|
|
@ -297,160 +299,158 @@ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/chat cmd/main.go
@@ -297,160 +299,158 @@ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/chat cmd/main.go
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
### 后端proto文件生成 |
|
|
|
|
|
|
|
|
|
如果修改了message.proto,就需要重新编译生成对应的go文件。 |
|
|
|
|
在根目录下执行 |
|
|
|
|
|
|
|
|
|
```bash |
|
|
|
|
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库文件 |
|
|
|
|
|
|
|
|
|
```bash |
|
|
|
|
go get github.com/golang/protobuf/proto |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
安装protoc-gen-gogo |
|
|
|
|
```bash |
|
|
|
|
go get github.com/gogo/protobuf/protoc-gen-gogo |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
安装gogoprotobuf库文件 |
|
|
|
|
```bash |
|
|
|
|
go get github.com/gogo/protobuf/proto |
|
|
|
|
``` |
|
|
|
|
安装protoc-gen-go |
|
|
|
|
|
|
|
|
|
在根目录测试: |
|
|
|
|
```bash |
|
|
|
|
protoc --gogo_out=. protocol/*.proto |
|
|
|
|
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
### 前端proto文件生成 |
|
|
|
|
前端需要安装protoc buffer库 |
|
|
|
|
|
|
|
|
|
```bash |
|
|
|
|
npm install protobufjs |
|
|
|
|
``` |
|
|
|
|
在根目录测试: |
|
|
|
|
|
|
|
|
|
生成protoc的js文件到目录 |
|
|
|
|
```bash |
|
|
|
|
npx pbjs -t json-module -w commonjs -o src/chat/proto/proto.js src/chat/proto/*.proto |
|
|
|
|
protoc --go_out=. pkg/protocol/*.proto |
|
|
|
|
|
|
|
|
|
src/chat/proto/proto.js 是生成的文件的目录路径及其文件名称 |
|
|
|
|
src/chat/proto/*.proto 是自己写的字段等 |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
## 代码说明 |
|
|
|
|
|
|
|
|
|
### WebSocket |
|
|
|
|
|
|
|
|
|
该文件是gin的路由映射,将普通的get请求,Upgrader为socket连接 |
|
|
|
|
|
|
|
|
|
```go |
|
|
|
|
// router/router.go |
|
|
|
|
func NewRouter() *gin.Engine { |
|
|
|
|
gin.SetMode(gin.ReleaseMode) |
|
|
|
|
gin.SetMode(gin.ReleaseMode) |
|
|
|
|
|
|
|
|
|
server := gin.Default() |
|
|
|
|
server.Use(Cors()) |
|
|
|
|
server.Use(Recovery) |
|
|
|
|
server := gin.Default() |
|
|
|
|
server.Use(Cors()) |
|
|
|
|
server.Use(Recovery) |
|
|
|
|
|
|
|
|
|
socket := RunSocekt |
|
|
|
|
socket := RunSocekt |
|
|
|
|
|
|
|
|
|
group := server.Group("") |
|
|
|
|
{ |
|
|
|
|
group := server.Group("") |
|
|
|
|
{ |
|
|
|
|
... |
|
|
|
|
|
|
|
|
|
group.GET("/socket.io", socket) |
|
|
|
|
} |
|
|
|
|
return server |
|
|
|
|
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进行消息的读取和发送 |
|
|
|
|
|
|
|
|
|
```go |
|
|
|
|
// router/socket.go |
|
|
|
|
var upGrader = websocket.Upgrader{ |
|
|
|
|
CheckOrigin: func(r *http.Request) bool { |
|
|
|
|
return true |
|
|
|
|
}, |
|
|
|
|
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() |
|
|
|
|
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进行发送。 |
|
|
|
|
* 如果消息为普通文本消息,可以直接转发到对应的客户端。 |
|
|
|
|
* 如果消息为视频文件,普通文件,照片之类的,需要先将文件进行保存,然后返回文件名称,前端根据名称调用接口获取文件。 |
|
|
|
|
* 如果是单聊,直接根据前端发送的uuid找到对应的client进行发送。 |
|
|
|
|
* 如果是群聊,需要在数据库查询该群所有的成员,在根据uuid找到对应的client进行发送。 |
|
|
|
|
* 如果消息为普通文本消息,可以直接转发到对应的客户端。 |
|
|
|
|
* 如果消息为视频文件,普通文件,照片之类的,需要先将文件进行保存,然后返回文件名称,前端根据名称调用接口获取文件。 |
|
|
|
|
|
|
|
|
|
```go |
|
|
|
|
// 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) |
|
|
|
|
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格式。 |
|
|
|
|
@ -459,6 +459,7 @@ func (s *Server) Start() {
@@ -459,6 +459,7 @@ func (s *Server) Start() {
|
|
|
|
|
* 通过protobuf将消息转换成对应协议。 |
|
|
|
|
* 通过socket进行传输。 |
|
|
|
|
* 最后,将本地的图片追加到聊天框里面。 |
|
|
|
|
|
|
|
|
|
```javascript |
|
|
|
|
bindParse = () => { |
|
|
|
|
document.getElementById("messageArea").addEventListener("paste", (e) => { |
|
|
|
|
@ -506,7 +507,9 @@ bindParse = () => {
@@ -506,7 +507,9 @@ bindParse = () => {
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
### 上传录制的视频 |
|
|
|
|
|
|
|
|
|
上传语音同原理 |
|
|
|
|
|
|
|
|
|
* 获取视频调用权限。 |
|
|
|
|
* 通过mediaDevices获取视频流,或者音频流,或者屏幕分享的视频流。 |
|
|
|
|
* this.recorder.start(1000)设定每秒返回一段流。 |
|
|
|
|
@ -519,6 +522,7 @@ bindParse = () => {
@@ -519,6 +522,7 @@ bindParse = () => {
|
|
|
|
|
* 最后,将本地的视频,音频追加到聊天框里面。 |
|
|
|
|
|
|
|
|
|
**特别注意: 获取视频,音频,屏幕分享调用权限,必须是https协议或者是localhost,127.0.0.1 本地IP地址,所有本地测试可以开启几个浏览器,或者分别用这两个本地IP进行2tab测试** |
|
|
|
|
|
|
|
|
|
```javascript |
|
|
|
|
/** |
|
|
|
|
* 当按下按钮时录制视频 |
|
|
|
|
|