「从0到1搭建一个IM项目」好友关系模块开发之群关系设计
[toc]
概况
前面把好友关系设计完成了,下面我们来设计群关系,主要内容:群关系表设计、群表设计、群列表、新键群、加入群等功能。
到目前为止,项目目录结构:
HiChat
├── common //放置公共文件
| |——md5.go
| |——resp.go
│
├── config //做配置文件
│
├── dao//数据库crud
│ |——user.go
| |——relation.go
|
├── global //放置各种连接池,配置等
│ |——global.go
|
├── initialize //项目初始化文件
│ |——db.go
| |——logger.go
|
├── middlewear //放置web中间件
| |——jwt.go
├── models //数据库表设计
│ |——user_basic.go
| |——relation.go
|
├── router //路由
| |——router.go
│
├── service //对外api
| |——user.go
| |——relation.go
│
├── test //测试文件
│
├── main.go //项目入口
├── go.mod //项目依赖管理
├── go.sum //项目依赖管理
群关系设计
群结构设计
首先来看一下群表的设计,在models目录下新建community.go文件,一样少不了model,需要引入user_basic.go中的Model结构体
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
群聊应该有哪些字段:
群名称
群拥有者
群类型
群头像
群描述
结构体:
type Community struct {
Model
Name string //群名称
OwnerId uint //群拥有者
Type int //群类型
Image string //头像
Desc string //描述
}
//FindUsers 获取群成员id
func FindUsers(groupId uint) (*[]uint, error) {
relation := make([]Relation, 0)
if tx := global.DB.Where("target_id = ? and type = 2", groupId).Find(&relation); tx.RowsAffected == 0 {
return nil, errors.New("未查询到成员信息")
}
userIDs := make([]uint, 0)
for _, v := range relation {
userId := v.OwnerId
userIDs = append(userIDs, userId)
}
return &userIDs, nil
}
然后使用gorm根据结构体生成表就完成了。
群关系设计
群关系设计,我们可以单独用一张表来存储,但是由于内容和用户关系设计一样,我们就把群关系和用户关系放在一张表中。
对于一个用户的好友关系,这里我们就以最简单的方式进行设计
当前用户id
加入的群id
关系类型
关系描述
结构体:
package models
type Relation struct {
Model
OwnerId uint //当前用户id
TargetID uint //加入的群id
Type int //关系类型:2 1表示好友关系 2表示群关系
Desc string //描述
}
func (r *Relation) RelTableName() string {
return "relation"
}
dao层的实现
在dao目录下新建community.go文件,然后完成下面函数
新建群聊
//CreateCommunity 新建群
func CreateCommunity(community models.Community) (int, error) {
com := models.Community{}
//查询群是否已经存在
if tx := global.DB.Where("name = ?", community.Name).First(&com); tx.RowsAffected == 1 {
return -1, errors.New("当前群记录已存在")
}
tx := global.DB.Begin()
if t := tx.Create(&community); t.RowsAffected == 0 {
tx.Rollback()
return -1, errors.New("群记录创建失败")
}
relation := models.Relation{}
relation.OwnerId = community.OwnerId //群主id
relation.TargetID = community.ID //群id
relation.Type = 2 //群
if t := tx.Create(&relation); t.RowsAffected == 0 {
tx.Rollback()
return -1, errors.New("群记录创建失败")
}
tx.Commit()
return 0, nil
}
获取群列表
/GetCommunityList 获取群列表
func GetCommunityList(ownerId uint) (*[]models.Community, error) {
//获取我加入的群
relation := make([]models.Relation, 0)
if tx := global.DB.Where("owner_id = ? and type = 2", ownerId).Find(&relation); tx.RowsAffected == 0 {
return nil, errors.New("不存在群记录")
}
communityID := make([]uint, 0)
for _, v := range relation {
cid := v.TargetID
communityID = append(communityID, cid)
}
community := make([]models.Community, 0)
if tx := global.DB.Where("id in ?", communityID).Find(&community); tx.RowsAffected == 0 {
return nil, errors.New("获取群数据失败")
}
return &community, nil
}
加入群聊
//JoinCommunity 根据群昵称搜索并加入群
func JoinCommunity(ownerId uint, cname string) (int, error) {
community := models.Community{}
if tx := global.DB.Where("name = ?", cname).First(&community); tx.RowsAffected == 0 {
return -1, errors.New("群记录不存在")
}
//重复加群
relation := models.Relation{}
if tx := global.DB.Where("owner_id = ? and target_id = ? and type = 2", ownerId, community.ID).First(&relation); tx.RowsAffected == 1 {
return -1, errors.New("该群已经加入")
}
relation = models.Relation{}
relation.OwnerId = ownerId
relation.TargetID = community.ID
relation.Type = 2
if tx := global.DB.Create(&relation); tx.RowsAffected == 0 {
return -1, errors.New("加入失败")
}
return 0, nil
}
完整代码
package dao
import (
"HiChat/global"
"HiChat/models"
"errors"
)
//CreateCommunity 新建群
func CreateCommunity(community models.Community) (int, error) {
com := models.Community{}
//查询群是否已经存在
if tx := global.DB.Where("name = ?", community.Name).First(&com); tx.RowsAffected == 1 {
return -1, errors.New("当前群记录已存在")
}
tx := global.DB.Begin()
if t := tx.Create(&community); t.RowsAffected == 0 {
tx.Rollback()
return -1, errors.New("群记录创建失败")
}
relation := models.Relation{}
relation.OwnerId = community.OwnerId //群主id
relation.TargetID = community.ID //群id
relation.Type = 2 //群
if t := tx.Create(&relation); t.RowsAffected == 0 {
tx.Rollback()
return -1, errors.New("群记录创建失败")
}
tx.Commit()
return 0, nil
}
//GetCommunityList 获取群列表
func GetCommunityList(ownerId uint) (*[]models.Community, error) {
//获取我加入的群
relation := make([]models.Relation, 0)
if tx := global.DB.Where("owner_id = ? and type = 2", ownerId).Find(&relation); tx.RowsAffected == 0 {
return nil, errors.New("不存在群记录")
}
communityID := make([]uint, 0)
for _, v := range relation {
cid := v.TargetID
communityID = append(communityID, cid)
}
community := make([]models.Community, 0)
if tx := global.DB.Where("id in ?", communityID).Find(&community); tx.RowsAffected == 0 {
return nil, errors.New("获取群数据失败")
}
return &community, nil
}
//JoinCommunity 根据群昵称搜索并加入群
func JoinCommunity(ownerId uint, cname string) (int, error) {
community := models.Community{}
if tx := global.DB.Where("name = ?", cname).First(&community); tx.RowsAffected == 0 {
return -1, errors.New("群记录不存在")
}
//重复加群
relation := models.Relation{}
if tx := global.DB.Where("owner_id = ? and target_id = ? and type = 2", ownerId, community.ID).First(&relation); tx.RowsAffected == 1 {
return -1, errors.New("该群已经加入")
}
relation = models.Relation{}
relation.OwnerId = ownerId
relation.TargetID = community.ID
relation.Type = 2
if tx := global.DB.Create(&relation); tx.RowsAffected == 0 {
return -1, errors.New("加入失败")
}
return 0, nil
}
群关系api的实现
我们将dao成的crud完成了,接下来将来完成对外api的实现,在service目录下的relation.go中进行编码
新建群
//NewGroup 新建群聊
func NewGroup(ctx *gin.Context) {
owner := ctx.PostForm("ownerId")
ownerId, err := strconv.Atoi(owner)
if err != nil {
zap.S().Info("owner类型转换失败", err)
return
}
ty := ctx.PostForm("cate")
Type, err := strconv.Atoi(ty)
if err != nil {
zap.S().Info("ty类型转换失败", err)
return
}
img := ctx.PostForm("icon")
name := ctx.PostForm("name")
desc := ctx.PostForm("desc")
community := models.Community{}
if ownerId == 0 {
ctx.JSON(200, gin.H{
"code": -1, // 0成功 -1失败
"message": "您未登录",
})
return
}
if name == "" {
ctx.JSON(200, gin.H{
"code": -1, // 0成功 -1失败
"message": "群名称不能为空",
})
return
}
if img != "" {
community.Image = img
}
if desc != "" {
community.Desc = desc
}
community.Name = name
community.Type = Type
community.OwnerId = uint(ownerId)
code, err := dao.CreateCommunity(community)
if err != nil {
HandleErr(code, ctx, err)
return
}
ctx.JSON(200, gin.H{
"code": 0, // 0成功 -1失败
"message": "键群成功",
})
}
群列表
//GroupList 获取群列表
func GroupList(ctx *gin.Context) {
owner := ctx.PostForm("ownerId")
ownerId, err := strconv.Atoi(owner)
if err != nil {
zap.S().Info("owner类型转换失败", err)
return
}
if ownerId == 0 {
ctx.JSON(200, gin.H{
"code": -1, // 0成功 -1失败
"message": "您未登录",
})
return
}
rsp, err := dao.GetCommunityList(uint(ownerId))
if err != nil {
zap.S().Info("获取群列表失败", err)
ctx.JSON(200, gin.H{
"code": -1, // 0成功 -1失败
"message": "你还没加入任何群聊",
})
return
}
common.RespOKList(ctx.Writer, rsp, len(*rsp))
}
加入群
//JoinGroup 加入群聊
func JoinGroup(ctx *gin.Context) {
comInfo := ctx.PostForm("comId")
if comInfo == "" {
ctx.JSON(200, gin.H{
"code": -1, // 0成功 -1失败
"message": "群名称不能为空",
})
return
}
user := ctx.PostForm("userId")
userId, err := strconv.Atoi(user)
if err != nil {
zap.S().Info("user类型转换失败")
}
if userId == 0 {
ctx.JSON(200, gin.H{
"code": -1, // 0成功 -1失败
"message": "你未登录",
})
return
}
code, err := dao.JoinCommunity(uint(userId), comInfo)
if err != nil {
HandleErr(code, ctx, err)
return
}
ctx.JSON(200, gin.H{
"code": 0, // 0成功 -1失败
"message": "加群成功",
})
}
完整代码
和用户关系结合起来,我们就完成了用户关系和群关系的开发,整个api的代码如下:
package service
import (
"strconv"
"HiChat/common"
"HiChat/dao"
"HiChat/models"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type user struct {
Name string
Avatar string
Gender string
Phone string
Email string
Identity string
}
func FriendList(ctx *gin.Context) {
id, _ := strconv.Atoi(ctx.Request.FormValue("userId"))
users, err := dao.FriendList(uint(id))
if err != nil {
zap.S().Info("获取好友列表失败", err)
ctx.JSON(200, gin.H{
"code": -1, // 0成功 -1失败
"message": "好友为空",
})
return
}
infos := make([]user, 0)
for _, v := range *users {
info := user{
Name: v.Name,
Avatar: v.Avatar,
Gender: v.Gender,
Phone: v.Phone,
Email: v.Email,
Identity: v.Identity,
}
infos = append(infos, info)
}
common.RespOKList(ctx.Writer, infos, len(infos))
}
//AddFriendByName 通过加好友
func AddFriendByName(ctx *gin.Context) {
user := ctx.PostForm("userId")
userId, err := strconv.Atoi(user)
if err != nil {
zap.S().Info("类型转换失败", err)
return
}
tar := ctx.PostForm("targetName")
target, err := strconv.Atoi(tar)
if err != nil {
code, err := dao.AddFriendByName(uint(userId), tar)
if err != nil {
HandleErr(code, ctx, err)
return
}
} else {
code, err := dao.AddFriend(uint(userId), uint(target))
if err != nil {
HandleErr(code, ctx, err)
return
}
}
ctx.JSON(200, gin.H{
"code": 0, // 0成功 -1失败
"message": "添加好友成功",
})
}
func HandleErr(code int, ctx *gin.Context, err error) {
switch code {
case -1:
ctx.JSON(200, gin.H{
"code": -1, // 0成功 -1失败
"message": err.Error(),
})
case 0:
ctx.JSON(200, gin.H{
"code": -1, // 0成功 -1失败
"message": "该好友已经存在",
})
case -2:
ctx.JSON(200, gin.H{
"code": -1, // 0成功 -1失败
"message": "不能添加自己",
})
}
}
func NewGroup(ctx *gin.Context) {
owner := ctx.PostForm("ownerId")
ownerId, err := strconv.Atoi(owner)
if err != nil {
zap.S().Info("owner类型转换失败", err)
return
}
ty := ctx.PostForm("cate")
Type, err := strconv.Atoi(ty)
if err != nil {
zap.S().Info("ty类型转换失败", err)
return
}
img := ctx.PostForm("icon")
name := ctx.PostForm("name")
desc := ctx.PostForm("desc")
community := models.Community{}
if ownerId == 0 {
ctx.JSON(200, gin.H{
"code": -1, // 0成功 -1失败
"message": "您未登录",
})
return
}
if name == "" {
ctx.JSON(200, gin.H{
"code": -1, // 0成功 -1失败
"message": "群名称不能为空",
})
return
}
if img != "" {
community.Image = img
}
if desc != "" {
community.Desc = desc
}
community.Name = name
community.Type = Type
community.OwnerId = uint(ownerId)
code, err := dao.CreateCommunity(community)
if err != nil {
HandleErr(code, ctx, err)
return
}
ctx.JSON(200, gin.H{
"code": 0, // 0成功 -1失败
"message": "键群成功",
})
}
func GroupList(ctx *gin.Context) {
owner := ctx.PostForm("ownerId")
ownerId, err := strconv.Atoi(owner)
if err != nil {
zap.S().Info("owner类型转换失败", err)
return
}
if ownerId == 0 {
ctx.JSON(200, gin.H{
"code": -1, // 0成功 -1失败
"message": "您未登录",
})
return
}
rsp, err := dao.GetCommunityList(uint(ownerId))
if err != nil {
zap.S().Info("获取群列表失败", err)
ctx.JSON(200, gin.H{
"code": -1, // 0成功 -1失败
"message": "你还没加入任何群聊",
})
return
}
common.RespOKList(ctx.Writer, rsp, len(*rsp))
}
func JoinGroup(ctx *gin.Context) {
comInfo := ctx.PostForm("comId")
if comInfo == "" {
ctx.JSON(200, gin.H{
"code": -1, // 0成功 -1失败
"message": "群名称不能为空",
})
return
}
user := ctx.PostForm("userId")
userId, err := strconv.Atoi(user)
if err != nil {
zap.S().Info("user类型转换失败")
}
if userId == 0 {
ctx.JSON(200, gin.H{
"code": -1, // 0成功 -1失败
"message": "你未登录",
})
return
}
code, err := dao.JoinCommunity(uint(userId), comInfo)
if err != nil {
HandleErr(code, ctx, err)
return
}
ctx.JSON(200, gin.H{
"code": 0, // 0成功 -1失败
"message": "加群成功",
})
}
匹配路由
在router目录下的router.go文件下:
//关系
relation := v1.Group("relation").Use(middlewear.JWY())
{
relation.POST("/list", service.FriendList)
relation.POST("/add", service.AddFriendByName)
relation.POST("/new_group", service.NewGroup)
relation.POST("/group_list", service.GroupList)
relation.POST("/join_group", service.JoinGroup)
}
测试
现在来测试新建群、加入群、群列表这三个api
新建群
加入群
群列表
总结
到这里整个关系结构就已经完成了,细心就会发现,整个项目各个模块的思路就是表设计、然后进行crud、最后对外暴露接口,到目前为止,我们整个项目就已经完成了用户模块,关系模块的开发接下来也就是要完成文件的上传(头像、发送图片等功能),消息发送和接收。