《从0到1搭建一个IM项目》好友关系模块开发之关系结构的设计

[toc]

概况

前面部分我们简单了,用户模块发一下基本功能的开发,现在我们继续完善IM系统功能,作为聊天系统,用户之间必须存在着一定关系,如陌生人、好友、群友等,本篇的重点将来介绍如何设计用户关系结构。

到目前为止,项目目录结构:

HiChat   
    ├── common    //放置公共文件
    │  
    ├── config    //做配置文件
    │  
    ├── dao//数据库crud|——user.go
    |
    ├── global    //放置各种连接池,配置等|——global.go
    |
    ├── initialize  //项目初始化文件|——db.go
    |                |——logger.go
    |
    ├── middlewear  //放置web中间件
    │ 
    ├── models      //数据库表设计|——user_basic.go
    |
    ├── router           //路由
    |       |——router.go
    │   
    ├── service     //对外api
    |       |——user.go
    │   
    ├── test        //测试文件
    │  
    ├── main.go     //项目入口
    ├── go.mod            //项目依赖管理
    ├── go.sum            //项目依赖管理

用户关系表设计

首先每一张表都需要:

type Model struct {
    ID        uint `gorm:"primaryKey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt gorm.DeletedAt `gorm:"index"`
}

对于一个用户的好友关系,这里我们就以最简单的方式进行设计

谁的用户关系

对应的好友

关系类型

关系描述

结构体:

package models

type Relation struct {
    Model
    OwnerId  uint   //谁的关系信息
    TargetID uint   //对应的谁
    Type     int    //关系类型: 1表示好友关系 2表示群关系
    Desc     string //描述
}

func (r *Relation) RelTableName() string {
    return "relation"
}

生成表发方式在用户模块开发中已经介绍过,这里不做过多赘述

dao层的开发

好友列表

有了好友关系表,现在来完成好友列表功能的开发,在dao目录下新建relation.go文件

//FriendList 获取好友列表
func FriendList(userId uint) (*[]models.UserBasic, error) {
    relation := make([]models.Relation, 0)
    if tx := global.DB.Where("owner_id = ? and type=1", userId).Find(&relation); tx.RowsAffected == 0 {
        zap.S().Info("未查询到Relation数据")
        return nil, errors.New("未查到好友关系")
    }

    userID := make([]uint, 0)
    for _, v := range relation {
        userID = append(userID, v.TargetID)
    }

    user := make([]models.UserBasic, 0)
    if tx := global.DB.Where("id in ?", userID).Find(&user); tx.RowsAffected == 0 {
        zap.S().Info("未查询到Relation好友关系")
        return nil, errors.New("未查到好友")
    }
    return &user, nil
}

通过id添加好友

添加好友是一个双向的过程,一旦添加双发都将存在好友关系(数据库进行两次添加记录),不能出现任何问题,导致一方添加成功,而另一方添加失败的情况,所以这里需要使用数据库的事务特性(全部成功则修改数据库内容,否则不会改变数据库数据)解决问题。

//AddFriend 加好友
func AddFriend(userID, TargetId uint) (int, error) {

    if userID == TargetId {
        return -2, errors.New("userID和TargetId相等")
    }
    //通过id查询用户
    targetUser, err := FindUserID(TargetId)
    if err != nil {
        return -1, errors.New("未查询到用户")
    }
    if targetUser.ID == 0 {
        zap.S().Info("未查询到用户")
        return -1, errors.New("未查询到用户")
    }

    relation := models.Relation{}

    if tx := global.DB.Where("owner_id = ? and target_id = ? and type = 1", userID, TargetId).First(&relation); tx.RowsAffected == 1 {
        zap.S().Info("该好友存在")
        return 0, errors.New("好友已经存在")
    }

    if tx := global.DB.Where("owner_id = ? and target_id = ?  and type = 1", TargetId, userID).First(&relation); tx.RowsAffected == 1 {
        zap.S().Info("该好友存在")
        return 0, errors.New("好友已经存在")
    }

    //开启事务
    tx := global.DB.Begin()

    relation.OwnerId = userID
    relation.TargetID = targetUser.ID
    relation.Type = 1

    if t := tx.Create(&relation); t.RowsAffected == 0 {
        zap.S().Info("创建失败")

        //事务回滚
        tx.Rollback()
        return -1, errors.New("创建好友记录失败")
    }

    relation = models.Relation{}
    relation.OwnerId = TargetId
    relation.TargetID = userID
    relation.Type = 1

    if t := tx.Create(&relation); t.RowsAffected == 0 {
        zap.S().Info("创建失败")

        //事务回滚
        tx.Rollback()
        return -1, errors.New("创建好友记录失败")
    }

    //提交事务
    tx.Commit()
    return 1, nil
}

通过昵称添加

通过昵称获取到用户id,然后对id进行查找添加

//AddFriendByName 昵称加好友
func AddFriendByName(userId uint, targetName string) (int, error) {
    user, err := FindUserByName(targetName)
    if err != nil {
        return -1, errors.New("该用户不存在")
    }
    if user.ID == 0 {
        zap.S().Info("未查询到用户")
        return -1, errors.New("该用户不存在")
    }
    return AddFriend(userId, user.ID)
}

dao层完整代码:

package dao

import (
    "HiChat/global"
    "HiChat/models"

    "errors"

    "go.uber.org/zap"
)

//FriendList 获取好友列表
func FriendList(userId uint) (*[]models.UserBasic, error) {
    relation := make([]models.Relation, 0)
    if tx := global.DB.Where("owner_id = ? and type=1", userId).Find(&relation); tx.RowsAffected == 0 {
        zap.S().Info("未查询到Relation数据")
        return nil, errors.New("未查到好友关系")
    }

    userID := make([]uint, 0)
    for _, v := range relation {
        userID = append(userID, v.TargetID)
    }

    user := make([]models.UserBasic, 0)
    if tx := global.DB.Where("id in ?", userID).Find(&user); tx.RowsAffected == 0 {
        zap.S().Info("未查询到Relation好友关系")
        return nil, errors.New("未查到好友")
    }
    return &user, nil
}

//AddFriendByName 昵称加好友
func AddFriendByName(userId uint, targetName string) (int, error) {
    user, err := FindUserByName(targetName)
    if err != nil {
        return -1, errors.New("该用户不存在")
    }
    if user.ID == 0 {
        zap.S().Info("未查询到用户")
        return -1, errors.New("该用户不存在")
    }
    return AddFriend(userId, user.ID)
}

//AddFriend 加好友
func AddFriend(userID, TargetId uint) (int, error) {

    if userID == TargetId {
        return -2, errors.New("userID和TargetId相等")
    }
    //通过id查询用户
    targetUser, err := FindUserID(TargetId)
    if err != nil {
        return -1, errors.New("未查询到用户")
    }
    if targetUser.ID == 0 {
        zap.S().Info("未查询到用户")
        return -1, errors.New("未查询到用户")
    }

    relation := models.Relation{}

    if tx := global.DB.Where("owner_id = ? and target_id = ? and type = 1", userID, TargetId).First(&relation); tx.RowsAffected == 1 {
        zap.S().Info("该好友存在")
        return 0, errors.New("好友已经存在")
    }

    if tx := global.DB.Where("owner_id = ? and target_id = ?  and type = 1", TargetId, userID).First(&relation); tx.RowsAffected == 1 {
        zap.S().Info("该好友存在")
        return 0, errors.New("好友已经存在")
    }

    //开启事务
    tx := global.DB.Begin()

    relation.OwnerId = userID
    relation.TargetID = targetUser.ID
    relation.Type = 1

    if t := tx.Create(&relation); t.RowsAffected == 0 {
        zap.S().Info("创建失败")

        //事务回滚
        tx.Rollback()
        return -1, errors.New("创建好友记录失败")
    }

    relation = models.Relation{}
    relation.OwnerId = TargetId
    relation.TargetID = userID
    relation.Type = 1

    if t := tx.Create(&relation); t.RowsAffected == 0 {
        zap.S().Info("创建失败")

        //事务回滚
        tx.Rollback()
        return -1, errors.New("创建好友记录失败")
    }

    //提交事务
    tx.Commit()
    return 1, nil
}

返回结构统一

为了更统一的对api数据的返回,我们在common下新建resp.go

package common

import (
    "encoding/json"
    "fmt"
    "net/http"
)

type H struct {
    Code  int
    Msg   string
    Data  interface{}
    Rows  interface{}
    Total interface{}
}

func Resp(w http.ResponseWriter, code int, data interface{}, msg string) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    h := H{
        Code: code,
        Data: data,
        Msg:  msg,
    }
    ret, err := json.Marshal(h)
    if err != nil {
        fmt.Println(err)
    }
    w.Write(ret)
}
func RespList(w http.ResponseWriter, code int, data interface{}, total interface{}) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    h := H{
        Code:  code,
        Rows:  data,
        Total: total,
    }
    ret, err := json.Marshal(h)
    if err != nil {
        fmt.Println(err)
    }
    w.Write(ret)
}
func RespFail(w http.ResponseWriter, msg string) {
    Resp(w, -1, nil, msg)
}
func RespOK(w http.ResponseWriter, data interface{}, msg string) {
    Resp(w, 0, data, msg)
}
func RespOKList(w http.ResponseWriter, data interface{}, total interface{}) {
    RespList(w, 0, data, total)
}

service层的api的实现

在service目录下新建relation.go文件

需要引入包:

import (
    "strconv"

    "HiChat/common"
    "HiChat/dao"
    "HiChat/models"

    "github.com/gin-gonic/gin"
    "go.uber.org/zap"
)

好友列表

//user对返回数据进行屏蔽
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": "不能添加自己",
        })

    }
}

配置路由

//好友关系
    relation := v1.Group("relation").Use(middlewear.JWY())
    {
        relation.POST("/list", service.FriendList)
        relation.POST("/add", service.AddFriendByName)
    }

测试

好友列表

由于是post请求,使用postman就行测试

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

添加好友

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

好友列表:

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

总结

本篇文章我们介绍了好友关系如何设计,进一步提升事物的抽象能力,并且简单介绍了什么是事务,最后我们完成了好友列表和添加好友的功能,后续我们将介绍群关系设计,以及群列表、建群,加群等功能的实现。

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

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