《从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

新建群

%E6%88%AA%E5%B1%8F2023-01-08%20%E4%B8%8B%E5%8D%883.46.16.png

%E6%88%AA%E5%B1%8F2023-01-08%20%E4%B8%8B%E5%8D%883.47.51.png

加入群

%E6%88%AA%E5%B1%8F2023-01-08%20%E4%B8%8B%E5%8D%883.50.11.png

群列表

%E6%88%AA%E5%B1%8F2023-01-08%20%E4%B8%8B%E5%8D%883.51.33.png

总结

到这里整个关系结构就已经完成了,细心就会发现,整个项目各个模块的思路就是表设计、然后进行crud、最后对外暴露接口,到目前为止,我们整个项目就已经完成了用户模块,关系模块的开发接下来也就是要完成文件的上传(头像、发送图片等功能),消息发送和接收。

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!