Browse Source

feat: video chat, resgister user, join group and refactor struct

pull/7/head
konenet 3 years ago
parent
commit
b054bf6644
  1. 213
      README.md
  2. 46
      api/v1/file_controller.go
  3. 17
      api/v1/file_router.go
  4. 2
      api/v1/group_controller.go
  5. 4
      api/v1/message_controller.go
  6. 11
      api/v1/user_controller.go
  7. 0
      common/request/friend_request.go
  8. 0
      common/request/message_request.go
  9. 0
      common/response/group_response.go
  10. 0
      common/response/message_response.go
  11. 0
      common/response/response_msg.go
  12. 8
      common/response/search_response.go
  13. 2
      common/util/file_suffix.go
  14. 39
      protocol/message.pb.go
  15. 21
      protocol/message.proto
  16. 6
      router/router.go
  17. 4
      server/server.go
  18. 4
      service/group_service.go
  19. 4
      service/message_service.go
  20. 33
      service/user_service.go
  21. BIN
      static/screenshot/screen-share.png
  22. BIN
      static/screenshot/video-chat.png

213
README.md

@ -1,21 +1,27 @@ @@ -1,21 +1,27 @@
[TOC]
##go-chat
## go-chat
使用Go基于WebSocket的通讯聊天软件。
### 功能列表:
* 登录注册
* 修改头像
* 群聊天
* 群好友列表
* 单人聊天
* 添加好友
* 添加群组
* 文本消息
* 剪切板图片
* 图片消息
* 文件发送
* 语音消息
* 视频消息
* 屏幕共享
* 视频聊天
* 屏幕共享(基于图片)
* 视频通话(基于WebRTC的p2p视频通话)
##后端
## 后端
[代码仓库](https://github.com/kone-net/go-chat)
go中协程是非常轻量级的。在每个client接入的时候,为每一个client开启一个协程,能够在单机实现更大的并发。同时go的channel,可以非常完美的解耦client接入和消息的转发等操作。
@ -33,7 +39,7 @@ go中协程是非常轻量级的。在每个client接入的时候,为每一个 @@ -33,7 +39,7 @@ go中协程是非常轻量级的。在每个client接入的时候,为每一个
* 数据库MySQL
* 图片文件二进制操作
##前端
## 前端
基于react,UI和基本组件是使用ant design。可以很方便搭建前端界面。
界面选择单页框架可以更加方便写聊天界面,比如像消息提醒,可以在一个界面接受到消息进行提醒,不会因为换页面或者查看其他内容影响消息接受。
@ -43,6 +49,7 @@ https://github.com/kone-net/go-chat-web @@ -43,6 +49,7 @@ https://github.com/kone-net/go-chat-web
### 前端技术和框架
* React
* Redux状态管理
* AntDesign
* proto buffer的使用
* WebSocket
@ -53,15 +60,53 @@ https://github.com/kone-net/go-chat-web @@ -53,15 +60,53 @@ https://github.com/kone-net/go-chat-web
* 获取摄像头视频(mediaDevices)
* 获取麦克风音频(Recorder)
* 获取屏幕共享(mediaDevices)
* WebRTC的p2p视频通话
### 截图
* 语音,文字,图片,视频消息
![go-chat-panel](/static/screenshot/go-chat-panel.jpeg)
* 视频通话
![video-chat](/static/screenshot/video-chat.png)
* 屏幕共享
![screen-share](/static/screenshot/screen-share.png)
## 消息协议
### protocol buffer协议
```go
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的protocol buffer进行传输呢?
* 一方面传输快
是因为protobuf序列化后的大小是json的10分之一,是xml格式的20分之一,但是性能却是它们的5~100倍.
* 另一方面支持二进制
当我们看到消息体最后一个字段,是定义的bytes,二进制类型。
我们在传输图片,文件,视频等内容的时候,可以将文件直接通过socket消息进行传输。
当然我们也可以将文件先通过http接口上传后,然后返回路径,再通过socket消息进行传输。但是这样只能实现固定大小文件的传输,如果我们是语音电话,或者视频电话的时候,就不能传输流。
## 快速运行
### 运行go程序
go环境的基本配置
...
拉取后端代码
```shell
git clone https://github.com/kone-net/go-chat
@ -351,3 +396,161 @@ func (s *Server) Start() { @@ -351,3 +396,161 @@ func (s *Server) Start() {
```
### 剪切板图片上传
上传剪切板的文件,首先我们需要获取剪切板文件。
如以下代码:
* 通过在聊天输入框,绑定粘贴命令,获取粘贴板的内容。
* 我们只获取文件信息,其他文字信息过滤掉。
* 先获取文件的blob格式。
* 通过FileReader,将blob转换为ArrayBuffer格式。
* 将ArrayBuffer内容转换为Uint8Array二进制,放在消息体。
* 通过protobuf将消息转换成对应协议。
* 通过socket进行传输。
* 最后,将本地的图片追加到聊天框里面。
```javascript
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测试**
```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: <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 = []
}
```

46
api/v1/file_controller.go

@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
package v1
import (
"io/ioutil"
"net/http"
"strings"
"chat-room/global/log"
"chat-room/common/response"
"chat-room/service"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// 前端通过文件名称获取文件流,显示文件
func GetFile(c *gin.Context) {
fileName := c.Param("fileName")
log.Info(fileName)
data, _ := ioutil.ReadFile("static/img/" + fileName)
c.Writer.Write(data)
}
// 上传头像等文件
func SaveFile(c *gin.Context) {
namePreffix := uuid.New().String()
userUuid := c.PostForm("uuid")
file, _ := c.FormFile("file")
fileName := file.Filename
index := strings.LastIndex(fileName, ".")
suffix := fileName[index:]
newFileName := namePreffix + suffix
log.Info("file", log.Any("file name", "static/img/"+newFileName))
log.Info("userUuid", log.Any("userUuid name", userUuid))
c.SaveUploadedFile(file, "static/img/"+newFileName)
err := service.UserService.ModifyUserAvatar(newFileName, userUuid)
if err != nil {
c.JSON(http.StatusOK, response.FailMsg(err.Error()))
}
c.JSON(http.StatusOK, response.SuccessMsg(newFileName))
}

17
api/v1/file_router.go

@ -1,17 +0,0 @@ @@ -1,17 +0,0 @@
package v1
import (
"io/ioutil"
"chat-room/global/log"
"github.com/gin-gonic/gin"
)
// 前端通过文件名称获取文件流,显示文件
func GetFile(c *gin.Context) {
fileName := c.Param("fileName")
log.Info(fileName)
data, _ := ioutil.ReadFile("static/img/" + fileName)
c.Writer.Write(data)
}

2
api/v1/group_router.go → api/v1/group_controller.go

@ -2,7 +2,7 @@ package v1 @@ -2,7 +2,7 @@ package v1
import (
"chat-room/model"
"chat-room/response"
"chat-room/common/response"
"chat-room/service"
"net/http"

4
api/v1/message_router.go → api/v1/message_controller.go

@ -4,8 +4,8 @@ import ( @@ -4,8 +4,8 @@ import (
"net/http"
"chat-room/global/log"
"chat-room/model/request"
"chat-room/response"
"chat-room/common/request"
"chat-room/common/response"
"chat-room/service"
"github.com/gin-gonic/gin"

11
api/v1/user_router.go → api/v1/user_controller.go

@ -5,8 +5,8 @@ import ( @@ -5,8 +5,8 @@ import (
"chat-room/global/log"
"chat-room/model"
"chat-room/model/request"
"chat-room/response"
"chat-room/common/request"
"chat-room/common/response"
"chat-room/service"
"github.com/gin-gonic/gin"
@ -56,6 +56,13 @@ func GetUserDetails(c *gin.Context) { @@ -56,6 +56,13 @@ func GetUserDetails(c *gin.Context) {
c.JSON(http.StatusOK, response.SuccessMsg(service.UserService.GetUserDetails(uuid)))
}
// 通过用户名获取用户信息
func GetUserOrGroupByName(c *gin.Context) {
name := c.Query("name")
c.JSON(http.StatusOK, response.SuccessMsg(service.UserService.GetUserOrGroupByName(name)))
}
func GetUserList(c *gin.Context) {
uuid := c.Query("uuid")
c.JSON(http.StatusOK, response.SuccessMsg(service.UserService.GetUserList(uuid)))

0
model/request/friend_request.go → common/request/friend_request.go

0
model/request/message_request.go → common/request/message_request.go

0
model/response/group_response.go → common/response/group_response.go

0
model/response/message_response.go → common/response/message_response.go

0
response/response_msg.go → common/response/response_msg.go

8
common/response/search_response.go

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
package response
import "chat-room/model"
type SearchResponse struct {
User model.User `json:"user"`
Group model.Group `json:"group"`
}

2
common/util/file_suffix.go

@ -119,7 +119,7 @@ func GetFileType(fSrc []byte) string { @@ -119,7 +119,7 @@ func GetFileType(fSrc []byte) string {
}
func GetContentTypeBySuffix(suffix string) int32 {
imgList := []string{"jpg", "png", "gif", "tif", "bmp", "dwg"}
imgList := []string{"jpeg", "jpg", "png", "gif", "tif", "bmp", "dwg"}
exists := arrays.Contains(imgList, suffix)
if exists >= 0 {
return constant.IMAGE

39
protocol/message.pb.go

@ -30,7 +30,8 @@ type Message struct { @@ -30,7 +30,8 @@ type Message struct {
Type string `protobuf:"bytes,7,opt,name=type,proto3" json:"type,omitempty"`
MessageType int32 `protobuf:"varint,8,opt,name=messageType,proto3" json:"messageType,omitempty"`
Url string `protobuf:"bytes,9,opt,name=url,proto3" json:"url,omitempty"`
File []byte `protobuf:"bytes,10,opt,name=file,proto3" json:"file,omitempty"`
FileSuffix string `protobuf:"bytes,10,opt,name=fileSuffix,proto3" json:"fileSuffix,omitempty"`
File []byte `protobuf:"bytes,11,opt,name=file,proto3" json:"file,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -123,6 +124,13 @@ func (m *Message) GetUrl() string { @@ -123,6 +124,13 @@ func (m *Message) GetUrl() string {
return ""
}
func (m *Message) GetFileSuffix() string {
if m != nil {
return m.FileSuffix
}
return ""
}
func (m *Message) GetFile() []byte {
if m != nil {
return m.File
@ -137,18 +145,19 @@ func init() { @@ -137,18 +145,19 @@ func init() {
func init() { proto.RegisterFile("protocol/message.proto", fileDescriptor_89254f84d2f8e90f) }
var fileDescriptor_89254f84d2f8e90f = []byte{
// 201 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x90, 0xcd, 0x8a, 0x83, 0x30,
0x14, 0x85, 0x49, 0xfc, 0xbf, 0x23, 0xc3, 0x70, 0x17, 0x72, 0x97, 0xc1, 0x95, 0xab, 0x99, 0xc5,
0x3c, 0x47, 0x37, 0xd2, 0x3e, 0x40, 0x2a, 0x69, 0x29, 0xa8, 0x91, 0x98, 0x16, 0xba, 0xec, 0x9b,
0x97, 0x5c, 0x15, 0xec, 0xee, 0x3b, 0xdf, 0xe1, 0x5c, 0x34, 0x50, 0x4d, 0xce, 0x7a, 0xdb, 0xd9,
0xfe, 0x6f, 0x30, 0xf3, 0xac, 0xaf, 0xe6, 0x97, 0x05, 0xe6, 0x9b, 0xaf, 0x5f, 0x12, 0xb2, 0xc3,
0xd2, 0x61, 0x05, 0xa9, 0x7e, 0x68, 0xaf, 0x1d, 0x09, 0x25, 0x9a, 0xa2, 0x5d, 0x13, 0xd6, 0x50,
0x5e, 0x9c, 0x1d, 0x4e, 0xb3, 0x71, 0xa3, 0x1e, 0x0c, 0x49, 0x6e, 0x3f, 0x1c, 0x22, 0xc4, 0x21,
0x53, 0xc4, 0x1d, 0x33, 0x7e, 0x83, 0xf4, 0x96, 0x62, 0x36, 0xd2, 0x5b, 0x24, 0xc8, 0x3a, 0x3b,
0x7a, 0x33, 0x7a, 0x4a, 0x58, 0x6e, 0x11, 0x15, 0x7c, 0xad, 0x78, 0x7c, 0x4e, 0x86, 0x52, 0x25,
0x9a, 0xa4, 0xdd, 0xab, 0x70, 0xdf, 0x87, 0x2a, 0x5b, 0xee, 0x07, 0x0e, 0xab, 0xf5, 0xb7, 0x78,
0x95, 0x2f, 0xab, 0x9d, 0xc2, 0x1f, 0x88, 0xee, 0xae, 0xa7, 0x82, 0x47, 0x01, 0xf9, 0x3b, 0x6f,
0xbd, 0x21, 0x50, 0xa2, 0x29, 0x5b, 0xe6, 0x73, 0xca, 0xaf, 0xf1, 0xff, 0x0e, 0x00, 0x00, 0xff,
0xff, 0x51, 0xf6, 0xb0, 0xa3, 0x2e, 0x01, 0x00, 0x00,
// 219 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x90, 0xc1, 0x4a, 0xc4, 0x30,
0x10, 0x86, 0x49, 0x76, 0xb7, 0xdd, 0x9d, 0x5d, 0x44, 0xe6, 0xb0, 0xcc, 0x49, 0xc2, 0x9e, 0x7a,
0xd2, 0x83, 0xcf, 0xe1, 0xa5, 0xea, 0x03, 0xc4, 0x32, 0x91, 0x42, 0xda, 0x94, 0x34, 0x15, 0x7d,
0x1c, 0xdf, 0x54, 0x32, 0x6d, 0xa1, 0xde, 0xfe, 0xff, 0xfb, 0xf8, 0x03, 0x13, 0xb8, 0x0e, 0x31,
0xa4, 0xd0, 0x04, 0xff, 0xd4, 0xf1, 0x38, 0xda, 0x4f, 0x7e, 0x14, 0x80, 0xc7, 0x95, 0xdf, 0x7e,
0x35, 0x94, 0x2f, 0xb3, 0xc3, 0x2b, 0x14, 0xf6, 0xcb, 0x26, 0x1b, 0x49, 0x19, 0x55, 0x9d, 0xea,
0xa5, 0xe1, 0x0d, 0x2e, 0x2e, 0x86, 0xee, 0x7d, 0xe4, 0xd8, 0xdb, 0x8e, 0x49, 0x8b, 0xfd, 0xc7,
0x10, 0x61, 0x9f, 0x3b, 0xed, 0xc4, 0x49, 0xc6, 0x3b, 0xd0, 0x29, 0xd0, 0x5e, 0x88, 0x4e, 0x01,
0x09, 0xca, 0x26, 0xf4, 0x89, 0xfb, 0x44, 0x07, 0x81, 0x6b, 0x45, 0x03, 0xe7, 0x25, 0xbe, 0xfd,
0x0c, 0x4c, 0x85, 0x51, 0xd5, 0xa1, 0xde, 0xa2, 0xfc, 0x7e, 0xca, 0xaa, 0x9c, 0xdf, 0xcf, 0x39,
0xaf, 0x96, 0xb3, 0x64, 0x75, 0x9c, 0x57, 0x1b, 0x84, 0xf7, 0xb0, 0x9b, 0xa2, 0xa7, 0x93, 0x8c,
0x72, 0xc4, 0x07, 0x00, 0xd7, 0x7a, 0x7e, 0x9d, 0x9c, 0x6b, 0xbf, 0x09, 0x44, 0x6c, 0x88, 0xdc,
0xd1, 0x7a, 0xa6, 0xb3, 0x51, 0xd5, 0xa5, 0x96, 0xfc, 0x51, 0xc8, 0x6f, 0x3d, 0xff, 0x05, 0x00,
0x00, 0xff, 0xff, 0xb2, 0x77, 0xfe, 0xe2, 0x4e, 0x01, 0x00, 0x00,
}

21
protocol/message.proto

@ -2,14 +2,15 @@ syntax = "proto3"; @@ -2,14 +2,15 @@ syntax = "proto3";
package protocol;
message Message {
string avatar = 1;
string fromUsername = 2;
string from = 3;
string to = 4;
string content = 5;
int32 contentType = 6;
string type = 7;
int32 messageType = 8;
string url = 9;
bytes file = 10;
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; //
}

6
router/router.go

@ -3,7 +3,7 @@ package router @@ -3,7 +3,7 @@ package router
import (
"chat-room/api/v1"
"chat-room/global/log"
"chat-room/response"
"chat-room/common/response"
"net/http"
"github.com/gin-gonic/gin"
@ -24,6 +24,7 @@ func NewRouter() *gin.Engine { @@ -24,6 +24,7 @@ func NewRouter() *gin.Engine {
{
group.GET("/user", v1.GetUserList)
group.GET("/user/:uuid", v1.GetUserDetails)
group.GET("/user/name", v1.GetUserOrGroupByName)
group.POST("/user/register", v1.Register)
group.POST("/user/login", v1.Login)
group.PUT("/user", v1.ModifyUserInfo)
@ -33,10 +34,11 @@ func NewRouter() *gin.Engine { @@ -33,10 +34,11 @@ func NewRouter() *gin.Engine {
group.GET("/message", v1.GetMessage)
group.GET("/file/:fileName", v1.GetFile)
group.POST("/file", v1.SaveFile)
group.GET("/group/:uuid", v1.GetGroup)
group.POST("/group/:uuid", v1.SaveGroup)
group.POST("/group/join/:userUid/:groupUuid", v1.JoinGroup)
group.POST("/group/join/:userUuid/:groupUuid", v1.JoinGroup)
group.GET("/group/user/:uuid", v1.GetGroupUsers)
group.GET("/socket.io", socket)

4
server/server.go

@ -163,6 +163,10 @@ func saveMessage(message *protocol.Message) { @@ -163,6 +163,10 @@ func saveMessage(message *protocol.Message) {
} else if message.ContentType == 3 {
// 普通的文件二进制上传
fileSuffix := util.GetFileType(message.File)
nullStr := ""
if nullStr == fileSuffix {
fileSuffix = strings.ToLower(message.FileSuffix)
}
contentType := util.GetContentTypeBySuffix(fileSuffix)
url := uuid.New().String() + "." + fileSuffix
err := ioutil.WriteFile("static/img/"+url, message.File, 0666)

4
service/group_service.go

@ -3,7 +3,7 @@ package service @@ -3,7 +3,7 @@ package service
import (
"chat-room/dao/pool"
"chat-room/errors"
"chat-room/model/response"
"chat-room/common/response"
"chat-room/model"
@ -101,7 +101,7 @@ func (g *groupService) JoinGroup(groupUuid, userUuid string) error { @@ -101,7 +101,7 @@ func (g *groupService) JoinGroup(groupUuid, userUuid string) error {
Nickname: nickname,
Mute: 0,
}
db.Save(groupMemberInsert)
db.Save(&groupMemberInsert)
return nil
}

4
service/message_service.go

@ -5,11 +5,11 @@ import ( @@ -5,11 +5,11 @@ import (
"chat-room/dao/pool"
"chat-room/errors"
"chat-room/global/log"
"chat-room/model/response"
"chat-room/common/response"
"chat-room/protocol"
"chat-room/model"
"chat-room/model/request"
"chat-room/common/request"
"gorm.io/gorm"
)

33
service/user_service.go

@ -3,11 +3,12 @@ package service @@ -3,11 +3,12 @@ package service
import (
"time"
"chat-room/common/request"
"chat-room/common/response"
"chat-room/dao/pool"
"chat-room/errors"
"chat-room/global/log"
"chat-room/model"
"chat-room/model/request"
"github.com/google/uuid"
)
@ -70,6 +71,22 @@ func (u *userService) GetUserDetails(uuid string) model.User { @@ -70,6 +71,22 @@ func (u *userService) GetUserDetails(uuid string) model.User {
return *queryUser
}
// 通过名称查找群组或者用户
func (u *userService) GetUserOrGroupByName(name string) response.SearchResponse {
var queryUser *model.User
db := pool.GetDB()
db.Select("uuid", "username", "nickname", "avatar").First(&queryUser, "username = ?", name)
var queryGroup *model.Group
db.Select("uuid", "name").First(&queryGroup, "name = ?", name)
search := response.SearchResponse{
User: *queryUser,
Group: *queryGroup,
}
return search
}
func (u *userService) GetUserList(uuid string) []model.User {
db := pool.GetDB()
@ -119,3 +136,17 @@ func (u *userService) AddFriend(userFriendRequest *request.FriendRequest) error @@ -119,3 +136,17 @@ func (u *userService) AddFriend(userFriendRequest *request.FriendRequest) error
return nil
}
// 修改头像
func (u *userService) ModifyUserAvatar(avatar string, userUuid string) error {
var queryUser *model.User
db := pool.GetDB()
db.First(&queryUser, "uuid = ?", userUuid)
if NULL_ID == queryUser.Id {
return errors.New("用户不存在")
}
db.Model(&queryUser).Update("avatar", avatar)
return nil
}

BIN
static/screenshot/screen-share.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
static/screenshot/video-chat.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Loading…
Cancel
Save