聊天模块修改&聊天群组模块&用户好友关模块系完善

This commit is contained in:
2025-07-16 18:48:15 +08:00
parent 0dea3c0348
commit 42f4779fcf
12 changed files with 9477 additions and 16 deletions

View File

@@ -0,0 +1,415 @@
package mysql
import (
"acquaintances/biz/model"
"acquaintances/biz/model/user"
"errors"
"github.com/cloudwego/hertz/pkg/common/hlog"
"gorm.io/gorm"
)
// 创建群
func CreateChatGroupInfo(userId string, data *user.ChatGroupInfo) error {
// 参数校验
if userId == "" {
return errors.New("用户ID不能为空")
}
if data == nil {
return errors.New("群组信息不能为空")
}
if data.ChatGroupID == "" {
return errors.New("群组ID不能为空")
}
if data.ChatGroupName == "" {
return errors.New("群组名称不能为空")
}
// 执行事务操作
errTransaction := DB.Transaction(func(tx *gorm.DB) error {
// 创建群基本信息
if err := tx.Model(&model.ChatGroupInfo{}).Create(data).Error; err != nil {
hlog.Error("创建群基本信息失败", err, "group_id", data.ChatGroupID, "user_id", userId)
return errors.New("创建群信息失败")
}
hlog.Info("群基本信息创建成功", "group_id", data.ChatGroupID)
// 添加创始人到群,设置为群主
creater := model.GroupUserRelation{
ChatGroupID: data.ChatGroupID,
UserID: userId,
Role: model.GroupLeader,
}
if err := tx.Model(&model.GroupUserRelation{}).Create(&creater).Error; err != nil {
hlog.Error("添加群主到群失败", err, "group_id", data.ChatGroupID, "user_id", userId)
return errors.New("添加群主失败")
}
hlog.Info("群主添加到群成功", "group_id", data.ChatGroupID, "user_id", userId)
return nil
})
if errTransaction != nil {
hlog.Error("创建群事务执行失败", errTransaction, "group_id", data.ChatGroupID, "user_id", userId)
return errTransaction
}
hlog.Info("群创建成功", "group_id", data.ChatGroupID, "creator", userId)
return nil
}
// 解散群
func DeleteChatGroupInfo(chatGroupId, userId string) error {
// 参数校验
if chatGroupId == "" || userId == "" {
return errors.New("群组ID和用户ID不能为空")
}
// 检查用户是否为群主(只有群主可以解散群)
var role model.ChatGroupRole
err := DB.Model(&model.GroupUserRelation{}).
Where("chat_group_id = ? AND user_id = ?", chatGroupId, userId).
Pluck("role", &role).Error
if err != nil {
hlog.Error("查询用户群角色失败", err, "user_id", userId, "group_id", chatGroupId)
return errors.New("查询用户权限失败")
}
if role != model.GroupLeader {
hlog.Warn("非群主尝试解散群", "user_id", userId, "group_id", chatGroupId, "role", role)
return errors.New("只有群主可以解散群")
}
// 执行事务操作
errTransaction := DB.Transaction(func(tx *gorm.DB) error {
// 解除群与用户关系
relationResult := tx.Where("chat_group_id = ?", chatGroupId).Delete(&model.GroupUserRelation{})
if relationResult.Error != nil {
hlog.Error("解除群与用户关系失败", relationResult.Error, "group_id", chatGroupId)
return errors.New("解除群成员关系失败")
}
hlog.Info("解除群与用户关系成功", "group_id", chatGroupId, "affected_users", relationResult.RowsAffected)
// 删除群基本信息
groupResult := tx.Where("chat_group_id = ?", chatGroupId).Delete(&model.ChatGroupInfo{})
if groupResult.Error != nil {
hlog.Error("删除群基本信息失败", groupResult.Error, "group_id", chatGroupId)
return errors.New("删除群信息失败")
}
if groupResult.RowsAffected == 0 {
hlog.Warn("未找到要删除的群信息", "group_id", chatGroupId)
return errors.New("未找到对应的群")
}
hlog.Info("群信息删除成功", "group_id", chatGroupId)
return nil
})
if errTransaction != nil {
hlog.Error("解散群事务执行失败", errTransaction, "group_id", chatGroupId, "user_id", userId)
return errTransaction
}
hlog.Info("群解散成功", "group_id", chatGroupId, "operator", userId)
return nil
}
// 修改群信息
func UpdatesChatGroupInfo(cg *user.UpdateChatGroupReq) error {
// 参数基本校验
if cg == nil {
return errors.New("请求参数不能为空")
}
if cg.UserID == "" || cg.ChatGroupID == "" {
return errors.New("用户ID和群组ID不能为空")
}
// 检查用户是否有修改权限(群主或管理员)
var hasPermission bool
err := DB.Model(&model.GroupUserRelation{}).
Where("user_id = ? AND chat_group_id = ? AND role IN (?, ?)",
cg.UserID, cg.ChatGroupID, model.GroupLeader, model.Administrators).
Select("COUNT(*) > 0").
Scan(&hasPermission).Error
if err != nil {
hlog.Error("查询用户群权限失败", err, "user_id", cg.UserID, "group_id", cg.ChatGroupID)
return errors.New("查询权限失败")
}
if !hasPermission {
return errors.New("没有权限修改群信息")
}
// 构建更新字段映射
updateFields := make(map[string]interface{})
if cg.NewChatGroupName != "" {
updateFields["chat_group_name"] = cg.NewChatGroupName
}
if cg.ChatGroupNameImageURL != "" {
updateFields["chat_group_image_url"] = cg.ChatGroupNameImageURL
}
if cg.ChatGroupNotice != "" {
updateFields["chat_group_notice"] = cg.ChatGroupNotice
}
// 没有需要更新的字段
if len(updateFields) == 0 {
return errors.New("没有需要更新的群信息")
}
hlog.Info("准备更新群信息", "group_id", cg.ChatGroupID, "fields", updateFields)
// 执行更新操作
result := DB.Model(&model.ChatGroupInfo{}).
Where("chat_group_id = ?", cg.ChatGroupID).
Updates(updateFields)
if result.Error != nil {
hlog.Error("更新群信息失败", result.Error, "group_id", cg.ChatGroupID)
return errors.New("更新群信息失败")
}
if result.RowsAffected == 0 {
hlog.Warn("未找到对应群组或没有修改任何信息", "group_id", cg.ChatGroupID)
return errors.New("未找到对应群组")
}
hlog.Info("群信息更新成功", "group_id", cg.ChatGroupID, "rows_affected", result.RowsAffected)
return nil
}
// 加入群
func JoinChatGroup(joinData *user.JoinChatGroupReq) error {
// 参数校验
if joinData == nil {
return errors.New("请求参数不能为空")
}
if joinData.UserID == "" || joinData.ChatGroupID == "" {
return errors.New("用户ID和群组ID不能为空")
}
// 检查群组是否存在
var groupExists bool
err := DB.Model(&model.ChatGroupInfo{}).
Where("chat_group_id = ?", joinData.ChatGroupID).
Select("COUNT(*) > 0").
Scan(&groupExists).Error
if err != nil {
hlog.Error("查询群组存在性失败", err, "group_id", joinData.ChatGroupID)
return errors.New("查询群组信息失败")
}
if !groupExists {
return errors.New("群组不存在")
}
// 检查用户是否已在群中
var userInGroup bool
err = DB.Model(&model.GroupUserRelation{}).
Where("chat_group_id = ? AND user_id = ?", joinData.ChatGroupID, joinData.UserID).
Select("COUNT(*) > 0").
Scan(&userInGroup).Error
if err != nil {
hlog.Error("查询用户是否在群中失败", err, "group_id", joinData.ChatGroupID, "user_id", joinData.UserID)
return errors.New("查询用户群成员关系失败")
}
if userInGroup {
return errors.New("用户已在群中")
}
// 准备加入群的数据
joinUser := &model.GroupUserRelation{
UserID: joinData.UserID,
ChatGroupID: joinData.ChatGroupID,
Role: model.General,
}
hlog.Info("准备添加群成员", "group_id", joinData.ChatGroupID, "user_id", joinData.UserID)
// 执行添加操作
err = DB.Model(&model.GroupUserRelation{}).Create(joinUser).Error
if err != nil {
hlog.Error("添加群成员失败", err, "group_id", joinData.ChatGroupID, "user_id", joinData.UserID)
return errors.New("加入群失败")
}
hlog.Info("用户成功加入群", "group_id", joinData.ChatGroupID, "user_id", joinData.UserID)
return nil
}
// 退出群
func ExitChatGroup(leaveData *user.ExitChatGroupReq) error {
// 参数校验
if leaveData == nil {
return errors.New("请求参数不能为空")
}
if leaveData.UserID == "" || leaveData.ChatGroupID == "" {
return errors.New("用户ID和群组ID不能为空")
}
// 检查用户是否在群中
var userRelation model.GroupUserRelation
err := DB.Model(&model.GroupUserRelation{}).
Where("chat_group_id = ? AND user_id = ?", leaveData.ChatGroupID, leaveData.UserID).
First(&userRelation).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("用户不在该群中")
}
hlog.Error("查询用户群关系失败", err, "group_id", leaveData.ChatGroupID, "user_id", leaveData.UserID)
return errors.New("查询用户群成员关系失败")
}
// 检查是否为群主
if userRelation.Role == model.GroupLeader {
// 统计群成员数量
var memberCount int64
if err := DB.Model(&model.GroupUserRelation{}).
Where("chat_group_id = ?", leaveData.ChatGroupID).
Count(&memberCount).Error; err != nil {
hlog.Error("查询群成员数量失败", err, "group_id", leaveData.ChatGroupID)
return errors.New("查询群成员数量失败")
}
// 群主不能退出群,只能解散群(如果是最后一个成员则允许退出并解散)
if memberCount > 1 {
return errors.New("群主不能退出群,请先解散群或转让群主身份")
}
}
// 执行退出操作
result := DB.Model(&model.GroupUserRelation{}).
Where("chat_group_id = ? AND user_id = ?", leaveData.ChatGroupID, leaveData.UserID).
Delete(&model.GroupUserRelation{})
if result.Error != nil {
hlog.Error("退出群失败", result.Error, "group_id", leaveData.ChatGroupID, "user_id", leaveData.UserID)
return errors.New("退出群失败")
}
// 如果是群主且是最后一个成员,同时删除群
if userRelation.Role == model.GroupLeader {
var remainingCount int64
if err := DB.Model(&model.GroupUserRelation{}).
Where("chat_group_id = ?", leaveData.ChatGroupID).
Count(&remainingCount).Error; err != nil {
hlog.Error("查询剩余群成员失败", err, "group_id", leaveData.ChatGroupID)
return errors.New("查询剩余群成员失败")
}
if remainingCount == 0 {
// 最后一个成员(群主)退出,删除群
if err := DB.Where("chat_group_id = ?", leaveData.ChatGroupID).
Delete(&model.ChatGroupInfo{}).Error; err != nil {
hlog.Error("删除空群失败", err, "group_id", leaveData.ChatGroupID)
// 这里不返回错误,因为用户已经成功退出
hlog.Warn("用户已退出,但删除空群失败", "group_id", leaveData.ChatGroupID)
} else {
hlog.Info("群已解散(最后一个成员退出)", "group_id", leaveData.ChatGroupID)
}
}
}
hlog.Info("用户成功退出群", "group_id", leaveData.ChatGroupID, "user_id", leaveData.UserID)
return nil
}
// 获取群成员列表
func GetUserListByChatGroup(req *user.ListUserChatGroupReq) ([]*user.UserInfoReq, error) {
// 参数校验
if req == nil {
return nil, errors.New("请求参数不能为空")
}
if req.ChatGroupID == "" {
return nil, errors.New("群组ID不能为空")
}
// 检查群组是否存在
var groupCount int64
if err := DB.Model(&model.ChatGroupInfo{}).
Where("chat_group_id = ?", req.ChatGroupID).
Count(&groupCount).Error; err != nil {
hlog.Error("查询群组存在性失败", err, "chat_group_id", req.ChatGroupID)
return nil, errors.New("查询群组信息失败")
}
if groupCount == 0 {
return nil, errors.New("指定的群组不存在")
}
// 查询群内所有成员关系
var relations []model.GroupUserRelation
if err := DB.Model(&model.GroupUserRelation{}).
Where("chat_group_id = ?", req.ChatGroupID).
Find(&relations).Error; err != nil {
hlog.Error("查询群成员关系失败", err, "chat_group_id", req.ChatGroupID)
return nil, errors.New("获取群成员关系失败")
}
// 处理没有成员的情况
if len(relations) == 0 {
return []*user.UserInfoReq{}, nil // 返回空列表而非错误
}
// 提取成员ID列表
var userIDs []string
for _, relation := range relations {
userIDs = append(userIDs, relation.UserID)
}
// 批量获取用户信息
users, err := GetUsersById(userIDs)
if err != nil {
hlog.Error("批量获取用户信息失败", err, "user_ids_count", len(userIDs))
return nil, errors.New("获取用户信息失败")
}
hlog.Info("成功获取群成员列表", "chat_group_id", req.ChatGroupID, "member_count", len(users))
return users, nil
}
// 获取用户群列表
func GetChatGroupListByUser(req *user.ListChatGroupByUserReq) ([]*user.ChatGroupInfo, error) {
// 参数校验
if req == nil {
return nil, errors.New("请求参数不能为空")
}
if req.UserID == "" {
return nil, errors.New("用户ID不能为空")
}
// 查询用户加入的所有群的关系记录
var relations []model.GroupUserRelation
if err := DB.Model(&model.GroupUserRelation{}).
Where("user_id = ?", req.UserID).
Find(&relations).Error; err != nil {
hlog.Error("查询用户群关系失败", err, "user_id", req.UserID)
return nil, errors.New("获取用户群关系失败")
}
// 处理用户未加入任何群的情况
if len(relations) == 0 {
return []*user.ChatGroupInfo{}, nil // 返回空列表而非错误
}
// 提取所有群组ID
var groupIDs []string
for _, relation := range relations {
groupIDs = append(groupIDs, relation.ChatGroupID)
}
// 查询这些群组的详细信息
var groupInfos []*user.ChatGroupInfo
if err := DB.Model(&model.ChatGroupInfo{}).
Where("chat_group_id IN (?)", groupIDs).
Find(&groupInfos).Error; err != nil {
hlog.Error("查询群组信息失败", err, "group_ids_count", len(groupIDs))
return nil, errors.New("获取群组信息失败")
}
hlog.Info("成功获取用户群列表", "user_id", req.UserID, "group_count", len(groupInfos))
return groupInfos, nil
}

View File

@@ -0,0 +1,77 @@
package mysql
import (
"acquaintances/biz/model"
"acquaintances/biz/model/user"
"gorm.io/gorm"
)
// CreateFriendApplication 发起好友申请
func CreateFriendApplication(applicantID, targetUserID, message string) error {
tx := DB
// 检查是否已存在未处理的申请
var existing int64
if err := tx.Model(&model.FriendRelationship{}).
Where("applicant_id = ? AND target_user_id = ? AND status = ?",
applicantID, targetUserID, model.ApplicationPending).
Count(&existing).Error; err != nil {
return err
}
if existing > 0 {
return gorm.ErrDuplicatedKey // 已存在未处理的申请
}
application := &model.FriendRelationship{
ApplicantID: applicantID,
TargetUserID: targetUserID,
ApplicationMessage: message,
Status: model.ApplicationPending,
}
if err := tx.Create(application).Error; err != nil {
return err
}
return nil
}
// HandleFriendApplication 处理好友申请(接受/拒绝)
func HandleFriendApplication(applicationID uint64, status model.FriendApplicationStatus) error {
tx := DB
if !status.IsValid() {
return gorm.ErrInvalidData
}
var application model.FriendRelationship
if err := tx.Where("id = ? AND status = ?", applicationID, model.ApplicationPending).
First(&application).Error; err != nil {
return err
}
application.Status = status
if err := tx.Save(&application).Error; err != nil {
return err
}
return nil
}
// GetApplications 获取收到的好友申请/获取发出的好友申请
func GetApplications(req user.ListFriendRelationshipReq, status ...model.FriendApplicationStatus) ([]*user.FriendRelationshipInfo, error) {
tx := DB
var applications []*user.FriendRelationshipInfo
query := tx.Where("target_user_id = ? and applicant_id = ?", req.UserID, req.UserID)
if len(status) > 0 {
query = query.Where("status IN (?)", status)
}
if err := query.Order("created_at DESC").Find(&applications).Error; err != nil {
return nil, err
}
return applications, nil
}

View File

@@ -4,17 +4,26 @@ import (
"acquaintances/biz/model"
"acquaintances/biz/model/user"
"encoding/json"
"errors"
"time"
"github.com/cloudwego/hertz/pkg/common/hlog"
)
// 发起好申请
func CreateUserRelations(relations user.CreateUserRelationsReq) error {
// 参数校验
if relations.UserID == "" || relations.FriendID == "" {
return errors.New("用户ID和好友ID不能为空")
}
if relations.UserID == relations.FriendID {
return errors.New("不能添加自己为好友")
}
db := DB.Model(&model.UserRelations{})
var data model.UserRelations
friends, err := getFriend(relations.UserID)
if err != nil {
hlog.Error("DeleteUserRelations:", err)
hlog.Error("CreateUserRelations:", err)
return err
}
// 检查是否已存在
@@ -32,14 +41,19 @@ func CreateUserRelations(relations user.CreateUserRelationsReq) error {
friends = append(friends, newFriend)
data.FriendList, err = json.Marshal(friends)
if err != nil {
hlog.Error("DeleteUserRelations:", err)
hlog.Error("CreateUserRelations:", err)
return err
}
data.FriendCount = int64(len(friends))
return db.Create(data).Error
}
// 删除好友
func DeleteUserRelations(relations user.DeleteUserRelationsReq) error {
// 参数校验
if relations.UserID == "" || relations.FriendID == "" {
return errors.New("用户ID和好友ID不能为空")
}
db := DB.Model(&model.UserRelations{})
friends, err := getFriend(relations.UserID)
if err != nil {
@@ -63,7 +77,15 @@ func DeleteUserRelations(relations user.DeleteUserRelationsReq) error {
return db.Where("user_id = ?", relations.UserID).Updates(maps).Error
}
// 更新好友备注
func UpdateUserRelations(relations user.UpdateUserRelationsReq) error {
// 参数校验
if relations.UserID == "" || relations.FriendID == "" {
return errors.New("用户ID和好友ID不能为空")
}
if relations.Remark == "" {
return errors.New("备注信息不能为空")
}
db := DB.Model(&model.UserRelations{})
friends, err := getFriend(relations.UserID)
if err != nil {
@@ -84,22 +106,22 @@ func UpdateUserRelations(relations user.UpdateUserRelationsReq) error {
return db.Where("user_id = ?", relations.UserID).Updates(maps).Error
}
// 获取好友列表
func GetUserListRelations(relations user.ListUserRelationsReq) (*user.ListUserRelationsRes, error) {
var res *user.ListUserRelationsRes
friends, err := getFriend(relations.UserID)
if err != nil {
hlog.Error("GetUserListRelations:", err)
return nil,err
return nil, err
}
var ids []string
for _,v :=range friends{
for _, v := range friends {
ids = append(ids, v.FriendID)
}
res.Users,err = GetUsersById(ids)
if err != nil{
res.Users, err = GetUsersById(ids)
if err != nil {
hlog.Error("GetUserListRelations:", err)
return nil,err
return nil, err
}
res.Total = int64(len(friends))
return res, nil

View File

@@ -0,0 +1,156 @@
// Code generated by hertz generator.
package user
import (
"acquaintances/biz/dal/mysql"
"acquaintances/biz/utils"
"context"
user "acquaintances/biz/model/user"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)
// CreateChatGroup .
// @router /v1/cg/ [POST]
func CreateChatGroup(ctx context.Context, c *app.RequestContext) {
var err error
var req user.CreateChatGroupReq
err = c.BindAndValidate(&req)
if err != nil {
c.JSON(consts.StatusBadRequest, err.Error())
return
}
data := &user.ChatGroupInfo{ChatGroupID: utils.GenerateChatGroupID(), ChatGroupName: req.ChatGroupName}
err = mysql.CreateChatGroupInfo(req.UserID, data)
if err != nil {
c.JSON(consts.StatusInternalServerError, err.Error())
return
}
resp := new(user.CreateChatGroupRes)
c.JSON(consts.StatusOK, resp)
}
// DeleteChatGroup .
// @router /v1/cg/ [DELETE]
func DeleteChatGroup(ctx context.Context, c *app.RequestContext) {
var err error
var req user.DeleteChatGroupReq
err = c.BindAndValidate(&req)
if err != nil {
c.JSON(consts.StatusBadRequest, err.Error())
return
}
err = mysql.DeleteChatGroupInfo(req.ChatGroupID, req.UserID)
if err != nil {
c.JSON(consts.StatusInternalServerError, err.Error())
return
}
resp := new(user.DeleteChatGroupRes)
c.JSON(consts.StatusOK, resp)
}
// UpdateChatGroup .
// @router /v1/cg/ [PUT]
func UpdateChatGroup(ctx context.Context, c *app.RequestContext) {
var err error
var req user.UpdateChatGroupReq
err = c.BindAndValidate(&req)
if err != nil {
c.JSON(consts.StatusBadRequest, err.Error())
return
}
err = mysql.UpdatesChatGroupInfo(&req)
if err != nil {
c.JSON(consts.StatusInternalServerError, err.Error())
return
}
resp := new(user.UpdateChatGroupRes)
c.JSON(consts.StatusOK, resp)
}
// ListChatGroupByUser .
// @router /v1/cg/ [GET]
func ListChatGroupByUser(ctx context.Context, c *app.RequestContext) {
var err error
var req user.ListChatGroupByUserReq
err = c.BindAndValidate(&req)
if err != nil {
c.JSON(consts.StatusBadRequest, err.Error())
return
}
data, err := mysql.GetChatGroupListByUser(&req)
if err != nil {
c.JSON(consts.StatusInternalServerError, err.Error())
return
}
resp := new(user.ListChatGroupByUserRes)
resp.ChatGroups = data
resp.Total = int64(len(data))
c.JSON(consts.StatusOK, resp)
}
// ListUserChatGroup .
// @router /v1/cg/users/ [GET]
func ListUserChatGroup(ctx context.Context, c *app.RequestContext) {
var err error
var req user.ListUserChatGroupReq
err = c.BindAndValidate(&req)
if err != nil {
c.JSON(consts.StatusBadRequest, err.Error())
return
}
data, err := mysql.GetUserListByChatGroup(&req)
if err != nil {
c.JSON(consts.StatusInternalServerError, err.Error())
return
}
resp := new(user.ListUserChatGroupRes)
resp.Users = data
resp.Total = int64(len(data))
c.JSON(consts.StatusOK, resp)
}
// JoinChatGroup .
// @router /v1/cg/join/ [POST]
func JoinChatGroup(ctx context.Context, c *app.RequestContext) {
var err error
var req user.JoinChatGroupReq
err = c.BindAndValidate(&req)
if err != nil {
c.JSON(consts.StatusBadRequest, err.Error())
return
}
err = mysql.JoinChatGroup(&req)
if err != nil {
c.JSON(consts.StatusInternalServerError, err.Error())
return
}
resp := new(user.JoinChatGroupRes)
c.JSON(consts.StatusOK, resp)
}
// ExitChatGroup .
// @router /v1/cg/exit/ [POST]
func ExitChatGroup(ctx context.Context, c *app.RequestContext) {
var err error
var req user.ExitChatGroupReq
err = c.BindAndValidate(&req)
if err != nil {
c.JSON(consts.StatusBadRequest, err.Error())
return
}
err = mysql.ExitChatGroup(&req)
if err != nil {
c.JSON(consts.StatusInternalServerError, err.Error())
return
}
resp := new(user.ExitChatGroupRes)
c.JSON(consts.StatusOK, resp)
}

View File

@@ -0,0 +1,70 @@
// Code generated by hertz generator.
package user
import (
"acquaintances/biz/dal/mysql"
"acquaintances/biz/model"
"context"
user "acquaintances/biz/model/user"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)
// CreateFriendRelationship .
// @router /v1/user/friendRelationship/ [POST]
func CreateFriendRelationship(ctx context.Context, c *app.RequestContext) {
var err error
var req user.CreateFriendRelationshipReq
err = c.BindAndValidate(&req)
if err != nil {
c.String(consts.StatusBadRequest, err.Error())
return
}
err = mysql.CreateFriendApplication(req.ApplicantID, req.TargetUserID, req.ApplicationMessage)
if err != nil {
c.String(consts.StatusBadRequest, err.Error())
return
}
resp := new(user.CreateFriendRelationshipRes)
c.JSON(consts.StatusOK, resp)
}
// UpdateFriendRelationship .
// @router /v1/user/friendRelationship/ [PUT]
func UpdateFriendRelationship(ctx context.Context, c *app.RequestContext) {
var err error
var req user.UpdateFriendRelationshipReq
err = c.BindAndValidate(&req)
if err != nil {
c.String(consts.StatusBadRequest, err.Error())
return
}
resp := new(user.UpdateFriendRelationshipRes)
c.JSON(consts.StatusOK, resp)
}
// ListFriendRelationship .
// @router /v1/user/friendRelationship/ [GET]
func ListFriendRelationship(ctx context.Context, c *app.RequestContext) {
var err error
var req user.ListFriendRelationshipReq
err = c.BindAndValidate(&req)
if err != nil {
c.String(consts.StatusBadRequest, err.Error())
return
}
data, err := mysql.GetApplications(req, model.ApplicationPending, model.ApplicationAccepted, model.ApplicationRejected)
if err != nil {
c.String(consts.StatusBadRequest, err.Error())
return
}
resp := new(user.ListFriendRelationshipRes)
resp.FriendRelationshipInfos = data
resp.Total = int64(len(data))
c.JSON(consts.StatusOK, resp)
}

View File

@@ -0,0 +1,69 @@
package model
import "gorm.io/gorm"
// ChatGroupInfo 聊天群组信息结构体
// 存储群组的基本信息,如名称、头像、公告等
type ChatGroupInfo struct {
gorm.Model
ChatGroupID string `gorm:"column:chat_group_id;type:varchar(64);primary_key;comment:群唯一标识ID"` // 群组唯一ID
ChatGroupName string `gorm:"column:chat_group_name;type:varchar(128);not null;comment:群组名称"` // 群组名称
ChatGroupImageURL string `gorm:"column:chat_group_image_url;type:varchar(255);comment:群组头像图片URL地址"` // 群组头像URL
ChatGroupNotice string `gorm:"column:chat_group_notice;type:text;comment:群组公告内容"` // 群组公告
}
// TableName 指定数据库表名
func (c *ChatGroupInfo) TableName() string {
return "chat_group_info"
}
type ChatGroupRole int
const (
General ChatGroupRole = iota
Administrators
GroupLeader
)
// GroupUserRelation 群与用户关系结构体
// 记录用户与群组的关联关系及用户在群内的相关属性
type GroupUserRelation struct {
gorm.Model
ChatGroupID string `gorm:"column:chat_group_id;type:varchar(64);not null;index;comment:关联的群ID"` // 群组ID关联ChatGroupInfo表
UserID string `gorm:"column:user_id;type:varchar(64);not null;index;comment:关联的用户ID"` // 用户ID关联用户表
Role ChatGroupRole `gorm:"column:role;type:int;not null;default:0;comment:用户群内角色(0-普通成员,1-管理员,2-群主)"` // 用户在群内的角色
LastReadTime int64 `gorm:"column:last_read_time;type:bigint;default:0;comment:最后阅读消息时间(Unix时间戳)"` // 最后阅读时间(时间戳)
IsMuted bool `gorm:"column:is_muted;type:tinyint(1);default:false;comment:是否被禁言(true-是,false-否)"` // 是否被禁言
MuteExpireTime int64 `gorm:"column:mute_expire_time;type:bigint;default:0;comment:禁言过期时间(Unix时间戳,0表示永久)"` // 禁言过期时间
}
// TableName 指定数据库表名
func (g *GroupUserRelation) TableName() string {
return "group_user_relation"
}
// BeforeCreate 创建前钩子方法
// 用于创建联合索引,确保数据完整性
func (g *GroupUserRelation) BeforeCreate(tx *gorm.DB) error {
// 检查索引是否已存在,避免重复创建
var count int64
err := tx.Raw(`
SELECT COUNT(*)
FROM information_schema.statistics
WHERE table_name = 'group_user_relation'
AND index_name = 'idx_group_user'
`).Scan(&count).Error
if err != nil {
return err
}
if count == 0 {
// 创建唯一联合索引,确保一个用户在一个群中只有一条记录
if err := tx.Exec("CREATE UNIQUE INDEX idx_group_user ON group_user_relation(chat_group_id, user_id)").Error; err != nil {
return err
}
}
return nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,24 +2,52 @@ package model
import (
"encoding/json"
"gorm.io/gorm"
)
// UserRelations 好友关系表
type UserRelations struct {
gorm.Model
UserID string `gorm:"column:user_id;type:varchar(32);not null;index:idx_user_friend,unique;comment:用户id"`
FriendList json.RawMessage `gorm:"column:friend_list;type:json;comment:'好友列表'"`
FriendCount int64 `gorm:"column:friend_count;type:int;default:0;comment:好友数量"`
UserID string `gorm:"column:user_id;type:varchar(32);not null;index:idx_user_friend,unique;comment:用户id"`
FriendList json.RawMessage `gorm:"column:friend_list;type:json;comment:'好友列表'"`
FriendCount int64 `gorm:"column:friend_count;type:int;default:0;comment:好友数量"`
}
type Friend struct{
type Friend struct {
FriendID string `json:"friend_id"`
Remark string `json:"remark"` // 好友备注
Remark string `json:"remark"` // 好友备注
CreateTime int64 `json:"create_time"` // 创建时间
}
func (u *UserRelations) TableName() string {
return "user_relations"
}
// FriendApplicationStatus 好友申请状态
type FriendApplicationStatus int
const (
ApplicationPending FriendApplicationStatus = iota // 0-待处理
ApplicationAccepted // 1-已接受
ApplicationRejected //已拒绝
RelationshipBreakdown //删除
)
// FriendApplication 好友申请表
type FriendRelationship struct {
gorm.Model
ApplicantID string `gorm:"column:applicant_id;type:varchar(32);not null;index:idx_applicant_target,unique;comment:申请人ID"` // 发起申请者ID
TargetUserID string `gorm:"column:target_user_id;type:varchar(32);not null;index:idx_applicant_target,unique;comment:目标用户ID"` // 被申请用户ID
ApplicationMessage string `gorm:"column:application_message;type:varchar(255);comment:申请留言"` // 申请留言
Status FriendApplicationStatus `gorm:"column:status;type:int;not null;default:0;comment:申请状态(0-待处理,1-已接受,2-已拒绝.3-关系破裂)"` // 申请状态
}
// TableName 指定表名
func (f *FriendRelationship) TableName() string {
return "friend_relationship"
}
// IsValid 检查状态是否有效
func (s FriendApplicationStatus) IsValid() bool {
return s >= ApplicationPending && s <= RelationshipBreakdown
}

View File

@@ -120,3 +120,78 @@ func _updateuserrelationsMw() []app.HandlerFunc {
// your code...
return nil
}
func _cgMw() []app.HandlerFunc {
// your code...
return nil
}
func _deletechatgroupMw() []app.HandlerFunc {
// your code...
return nil
}
func _listchatgroupbyuserMw() []app.HandlerFunc {
// your code...
return nil
}
func _createchatgroupMw() []app.HandlerFunc {
// your code...
return nil
}
func _updatechatgroupMw() []app.HandlerFunc {
// your code...
return nil
}
func _exitMw() []app.HandlerFunc {
// your code...
return nil
}
func _exitchatgroupMw() []app.HandlerFunc {
// your code...
return nil
}
func _joinMw() []app.HandlerFunc {
// your code...
return nil
}
func _joinchatgroupMw() []app.HandlerFunc {
// your code...
return nil
}
func _usersMw() []app.HandlerFunc {
// your code...
return nil
}
func _listuserchatgroupMw() []app.HandlerFunc {
// your code...
return nil
}
func _friendrelationshipMw() []app.HandlerFunc {
// your code...
return nil
}
func _createfriendrelationshipMw() []app.HandlerFunc {
// your code...
return nil
}
func _updatefriendrelationshipMw() []app.HandlerFunc {
// your code...
return nil
}
func _listfriendrelationshipMw() []app.HandlerFunc {
// your code...
return nil
}

View File

@@ -19,12 +19,37 @@ func Register(r *server.Hertz) {
root := r.Group("/", rootMw()...)
{
_v1 := root.Group("/v1", _v1Mw()...)
{
_cg := _v1.Group("/cg", _cgMw()...)
_cg.DELETE("/", append(_deletechatgroupMw(), user.DeleteChatGroup)...)
_cg.GET("/", append(_listchatgroupbyuserMw(), user.ListChatGroupByUser)...)
_cg.POST("/", append(_createchatgroupMw(), user.CreateChatGroup)...)
_cg.PUT("/", append(_updatechatgroupMw(), user.UpdateChatGroup)...)
{
_exit := _cg.Group("/exit", _exitMw()...)
_exit.POST("/", append(_exitchatgroupMw(), user.ExitChatGroup)...)
}
{
_join := _cg.Group("/join", _joinMw()...)
_join.POST("/", append(_joinchatgroupMw(), user.JoinChatGroup)...)
}
{
_users := _cg.Group("/users", _usersMw()...)
_users.GET("/", append(_listuserchatgroupMw(), user.ListUserChatGroup)...)
}
}
{
_user := _v1.Group("/user", _userMw()...)
_user.DELETE("/", append(_deleteuserMw(), user.DeleteUser)...)
_user.GET("/", append(_infouserMw(), user.InfoUser)...)
_user.POST("/", append(_createuserMw(), user.CreateUser)...)
_user.PUT("/", append(_updateuserMw(), user.UpdateUser)...)
{
_friendrelationship := _user.Group("/friendRelationship", _friendrelationshipMw()...)
_friendrelationship.GET("/", append(_listfriendrelationshipMw(), user.ListFriendRelationship)...)
_friendrelationship.POST("/", append(_createfriendrelationshipMw(), user.CreateFriendRelationship)...)
_friendrelationship.PUT("/", append(_updatefriendrelationshipMw(), user.UpdateFriendRelationship)...)
}
{
_relation := _user.Group("/relation", _relationMw()...)
_relation.DELETE("/", append(_deleteuserrelationsMw(), user.DeleteUserRelations)...)

View File

@@ -137,3 +137,37 @@ func GetID() (int64, error) {
})
return generator.Generate()
}
// GenerateChatGroupID 生成唯一的群组ID
// 格式: group-{时间戳}-{随机字符串}
// 时间戳使用秒级,确保一定的时间顺序性
// 随机字符串确保在同一秒内生成的ID也能保持唯一
func GenerateChatGroupID() string {
// 获取当前时间戳(秒级)
timestamp := time.Now().Unix()
// 生成随机字节
randomBytes := make([]byte, 8) // 8字节=64位足够产生足够随机的字符串
_, err := rand.Read(randomBytes)
if err != nil {
// 极端情况下随机数生成失败,使用伪随机数作为 fallback
// 这种情况发生概率极低,但仍需处理以保证函数可用性
randStr := fmt.Sprintf("%016x", time.Now().UnixNano())
return fmt.Sprintf("group-%d-%s", timestamp, randStr[:12])
}
// 将随机字节转换为URL安全的Base64字符串去掉末尾可能的=号
randStr := base64.URLEncoding.EncodeToString(randomBytes)
randStr = trimBase64Padding(randStr)
// 组合成最终的群组ID
return fmt.Sprintf("group-%d-%s", timestamp, randStr)
}
// trimBase64Padding 移除Base64字符串末尾的填充字符=
func trimBase64Padding(s string) string {
for len(s) > 0 && s[len(s)-1] == '=' {
s = s[:len(s)-1]
}
return s
}

View File

@@ -172,3 +172,154 @@ service UserRelationsService {
UpdateUserRelationsRes UpdateUserRelations(1:UpdateUserRelationsReq req)(api.put="/v1/user/relation/") //修改好友备注
}
//好友申请
//发起好友申请
struct FriendRelationshipInfo {
1: string applicant_id
2: string target_user_id
3: string application_message
4: string status
}
struct CreateFriendRelationshipReq {
1: string applicant_id //id
2: string target_user_id //好友id
3: string application_message //好友id
}
struct CreateFriendRelationshipRes{
1: Code code
2: string msg
}
//同意/拒绝好友申请
struct UpdateFriendRelationshipReq {
1: string applicant_id //id
2: string target_user_id //好友id
3: string Status //好友id
}
struct UpdateFriendRelationshipRes{
1: Code code
2: string msg
}
//请求添加好友或被添加好友列表
struct ListFriendRelationshipReq{
1: string user_id //id
}
struct ListFriendRelationshipRes{
1: Code code
2: string msg
3: list<FriendRelationshipInfo> FriendRelationshipInfos
4: i64 total
}
service FriendRelationshipService {
CreateFriendRelationshipRes CreateFriendRelationship(1:CreateFriendRelationshipReq req)(api.post="/v1/user/friendRelationship/") //发起好友申请
UpdateFriendRelationshipRes UpdateFriendRelationship(1:UpdateFriendRelationshipReq req)(api.put="/v1/user/friendRelationship/") //同意或拒绝好友申请
ListFriendRelationshipRes ListFriendRelationship(1:ListFriendRelationshipReq req)(api.get="/v1/user/friendRelationship/") //获取添加或申请记录列表
}
//聊天群组
//创建群聊
struct ChatGroupInfo{
1: string chat_group_id
2: string chat_group_name
3: string chat_group_image_url
4: string chat_group_Notice
}
struct CreateChatGroupReq {
1: string user_id //创建者id
2: string chat_group_name //群名
}
struct CreateChatGroupRes{
1: Code code
2: string msg
}
//解散群聊
struct DeleteChatGroupReq {
1: string user_id //创建者id
2: string chat_group_id //群号
}
struct DeleteChatGroupRes{
1: Code code
2: string msg
}
//修改群信息
struct UpdateChatGroupReq {
1: string user_id //创建者id
2: string chat_group_id //群号
3: string new_chat_group_name //新群名
4: string chat_group_name_image_url //群头像
5: string chat_group_Notice //群公告
}
struct UpdateChatGroupRes{
1: Code code
2: string msg
}
//加入群聊
struct JoinChatGroupReq {
1: string user_id //申请者id
2: string chat_group_id //群号
}
struct JoinChatGroupRes{
1: Code code
2: string msg
}
//退出群聊
struct ExitChatGroupReq {
1: string user_id //申请者id
2: string chat_group_id //群号
}
struct ExitChatGroupRes{
1: Code code
2: string msg
}
//获取自己加入的群列表
struct ListChatGroupByUserReq {
1: string user_id //id
}
struct ListChatGroupByUserRes{
1: Code code
2: string msg
3: list<ChatGroupInfo> chat_groups
4: i64 total
}
//获取群成员列表
struct ListUserChatGroupReq {
1: string chat_group_id
}
struct ListUserChatGroupRes{
1: Code code
2: string msg
3: list<UserInfoReq> users
4: i64 total
}
service ChatGroupService {
CreateChatGroupRes CreateChatGroup(1:CreateChatGroupReq req)(api.post="/v1/cg/") //创建群聊
DeleteChatGroupRes DeleteChatGroup(1:DeleteChatGroupReq req)(api.delete="/v1/cg/") //删除群
UpdateChatGroupRes UpdateChatGroup(1:UpdateChatGroupReq req)(api.put="/v1/cg/") //修改群信息
ListChatGroupByUserRes ListChatGroupByUser(1: ListChatGroupByUserReq req)(api.get="/v1/cg/") //获取指定用户群列表
ListUserChatGroupRes ListUserChatGroup(1: ListUserChatGroupReq req)(api.get="/v1/cg/users/") //获取指定群用户列表
JoinChatGroupRes JoinChatGroup(1:JoinChatGroupReq req)(api.post="/v1/cg/join/") //入群
ExitChatGroupRes ExitChatGroup(1:ExitChatGroupReq req)(api.post="/v1/cg/exit/") //退出群
}