Golang 14 gin web项目实战 IM系统(3) 用户与群组

41

2025/7/21 15:00 - 2025/7/21 23:00

这一部分比较简单,并不会贴出所有代码,只挑关键的,另外测试api采用apifox软件,swag测试过于麻烦。

用户功能

User struct

User.go,指针代表该字段可以为null而不是存零值:

type User struct {
	gorm.Model
	Username      string       `json:"username"`         // 用户名
	Password      string       `json:"password"`         // 用户密码(加密后)
	Nickname      *string      `json:"nickname"`         // 昵称
	Phone         *string      `json:"phone"`            // 用户手机号
	Email         *string      `json:"email"`            // 用户邮箱
	Avatar        *string      `json:"avatar,omitempty"` // 用户头像URL(可选)
	ClientIp      string       `json:"client_ip"`        // 客户端IP地址
	ClientPort    string       `json:"client_port"`      // 客户端端口号
	LoginTime     int64        `json:"login_time"`       // 最近一次登录时间
	HeartbeatTime int64        `json:"heartbeat_time"`   // 最近一次心跳时间
	LogoutTime    int64        `json:"logout_time"`      // 最近一次登出时间
	Status        Status       `json:"status"`           // 用户状态(如激活、禁用等)
	OnlineStatus  OnlineStatus `json:"online_status"`    // 用户在线状态(如在线、离线、忙碌等)
	DeviceInfo    *string      `json:"device_info"`      // 客户端设备信息
}

接口

路径省略了前缀 http://localhost/api/v1

接口名 请求路径 参数 响应体
注册 /user/register { "username":string,
"password":string,
"rePassword":string }
200 { "code":200,
"message":"ok"
"data":nil }
登录 /user/login {
"username":string,
"password":string
}
200 { "code":200,
"message":"ok"
"data":token }
更新基本信息 /user/update
  1. 以其中注册接口为例:

创建request:

type RegisterRequest struct {
	Username   *string `json:"username" binding:"required"`    // 用户名
	Password   *string `json:"password" binding:"required"`    // 密码
	RePassword *string `json:"re_password" binding:"required"` // 确认密码
}
控制器中接收数据(可追加校验逻辑,比如账号密码长度等)并调用服务:
func (con UserController) Register(c *gin.Context) {
	registerRequest := &model.RegisterRequest{}
	if err := c.ShouldBindBodyWithJSON(registerRequest); err != nil {
		con.Error(c, "params error")
		return
	}
	userService := service.GetUserService()
	if err := userService.Register(registerRequest.Username, registerRequest.Password, registerRequest.RePassword); err != nil {
		con.Error(c, err.Error())
		return
	}
	con.Success(c)
	return
}

服务处理注册请求:

// Register 注册
func (u *UserService) Register(username, password, rePassword string) (err error) {
	if password != rePassword {
		return errors.New("密码不一致")
	}
	userRepository := repository.GetUserRepository()
	user, err := userRepository.GetByName(username)
	if err != nil {
		logUtil.Errorf("GetUserByName error: %v", err)
		return err
	}
	if user != nil {
		logUtil.Warnf("用户(%v)已存在", username)
		return errors.New(fmt.Sprintf("用户(%v)已存在", username))
	}
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return err
	}
	user = &models.User{Username: username, Password: string(hashedPassword), Nickname: username}
	err = userRepository.Save(user)
	if err != nil {
		logUtil.Errorf("保存用户失败: %v", err)
		return err
	}
	return nil
}
2. 登录、更新同理

新用到的库

  1. 加密
go get golang.org/x/crypto/bcrypt
  1. 校验
go get github.com/go-playground/validator/v10

群组功能

Group Struct

package model

import (
	"gorm.io/gorm"
)

type Group struct {
	gorm.Model
	Code    string `json:"code"`        //群号
	Name    string `json:"name"`        //群组名称
	Desc    string `json:"description"` //群组简介
	OwnerId uint   `json:"owner_id"`    //群主ID
	MaxNum  int    `json:"max_num"`     //群组最大人数
	Status  Status `json:"status" `     //群组状态(1=正常,0=关闭)
}

func (m *Group) TableName() string {
	return "groups"
}

GroupMember Struct

package model

import "gorm.io/gorm"

type GroupMember struct {
	gorm.Model
	GroupId   uint   `json:"group_id"`    //群ID
	MemberId  uint   `json:"member_id"`   //成员ID
	GNickName string `json:"g_nick_name"` //群昵称
	Role      Role   `json:"role"`        //成员角色(0=普通成员,1=群主,2=管理员)
	Status    Status `json:"status"`      //成员状态(0禁言1正常)
}

func (m *GroupMember) TableName() string {
	return "group_members"
}

接口

接口名 请求方式 请求路径 参数 响应体
创建群 post /group/create {
"user_id": 3,
"name": "Go 语言爱好者交流群",
"max_num":5,
"member_list": [3,4]
}
200 { "code":200,
"message":"ok"
"data":nil }
加入群 get /group/join user_id:jwt获取
group_id:QueryParams
200 { "code":200,
"message":"ok"
"data":nil }
退出群 get /group/quit user_id:jwt获取
group_id:QueryParams
200 { "code":200,
"message":"ok"
"data":nil }

部分代码 GroupService

package service

import (
    "errors"
    "go-chat/internal/db"
    "go-chat/internal/model"
    request "go-chat/internal/model/request"
    "go-chat/internal/repository"
    "go-chat/internal/utils/idUtil"
    "gorm.io/gorm"
    "sync"
)

type GroupService struct{}

var (
    groupServiceInstance *GroupService
    groupOnce            sync.Once
)

func GetGroupService() *GroupService {
    groupOnce.Do(func() {
        groupServiceInstance = &GroupService{}
    })
    return groupServiceInstance
}

// Create 创建群组
func (s GroupService) Create(req *request.GroupCreateRequest) error {
    err := db.Mysql.Transaction(func(tx *gorm.DB) error {
        groupRepo := repository.GetGroupRepository()
        groupMemberRepo := repository.GetGroupMemberRepository()
        userRepo := repository.GetUserRepository()

        maxAttempts := 3
        code := idUtil.GenerateId()
        for attempts := 0; attempts < maxAttempts; attempts++ {
            if exists := groupRepo.ExistsByCode(code, tx); !exists {
                break
            }
            code = idUtil.GenerateId()
        }

        if exists := groupRepo.ExistsByCode(code, tx); exists {
            return errors.New("error code, please try again")
        }

        group := &model.Group{
            OwnerId: req.UserId,
            Name:    req.Name,
            MaxNum:  req.MaxNum,
            Code:    code,
            Desc:    "群主很懒,什么也没留下~",
            Status:  model.Enable,
        }

        if err := groupRepo.Save(group, tx); err != nil {
            return err
        }

        if req.MemberList != nil && len(*req.MemberList) > 0 {
            groupMemberList := make([]*model.GroupMember, len(*req.MemberList))

            idNickNameMap, err := userRepo.GetNickNamesByIds(*req.MemberList, tx)
            if err != nil {
                return err
            }

            for i, memberId := range *req.MemberList {
                nickname := idNickNameMap[memberId]
                var role model.Role
                if req.UserId == memberId {
                    role = model.Owner // 群主角色
                } else {
                    role = model.Member // 普通成员角色
                }
                groupMemberList[i] = &model.GroupMember{
                    GroupId:   group.ID,
                    MemberId:  memberId,
                    GNickName: nickname,
                    Role:      role,
                    Status:    model.Enable,
                }
            }

            if err := groupMemberRepo.SaveBatch(groupMemberList, tx); err != nil {
                return err
            }
        }

        return nil
    })

    if err != nil {
        return err
    }

    return nil
}

func (s GroupService) Join(groupId uint, userId uint) error {
    return db.Mysql.Transaction(func(tx *gorm.DB) error {
        userRepo := repository.GetUserRepository()
        groupMemberRepo := repository.GetGroupMemberRepository()
        exists := groupMemberRepo.ExistsByGroupIdAndUserId(groupId, userId, tx)
        if exists {
            return errors.New("用户已加入该群组")
        }
        rejoin := groupMemberRepo.RejoinGroupIfDeleted(groupId, userId, tx)
        if rejoin {
            return nil
        }
        nickname, err := userRepo.GetNickNamesById(userId, tx)
        if err != nil {
            return err
        }
        member := &model.GroupMember{
            GroupId:   groupId,
            MemberId:  userId,
            GNickName: nickname,
            Role:      model.Member,
            Status:    model.Enable,
        }
        if err := groupMemberRepo.Save(member, tx); err != nil {
            return err
        }
        return nil
    })
}

func (s GroupService) Quit(groupId uint, memberId uint) error {
    memberRepository := repository.GetGroupMemberRepository()
	return memberRepository.DeleteByGroupIdAndUserId(groupId, memberId)
}

新用到库

  1. 随机id,用来当群号
go get github.com/jaevor/go-nanoid

简单支撑私聊、群聊消息的的基本模块,群组有更多操作但是与消息无关先放一放

posted on 2025-07-23 23:43  依只  阅读(23)  评论(0)    收藏  举报

导航